/* This file is part of libdnf-plugin-txnupd.
*
* Copyright © 2020 Neal Gompa
*
* Licensed under the GNU Lesser General Public License Version 2.1
* Fedora-License-Identifier: LGPLv2+
* SPDX-2.0-License-Identifier: LGPL-2.1+
* SPDX-3.0-License-Identifier: LGPL-2.1-or-later
*
* This program is free software.
* For more information on the license, see LICENSE.
* For more information on free software, see
* <https://www.gnu.org/philosophy/free-sw.en.html>.
*/
// Standard library includes
#include <cstdlib>
#include <filesystem>
#include <string>
// POSIX standard library includes
#include <sys/types.h>
#include <unistd.h>
// libdnf includes
#include <libdnf/libdnf.h>
#include <libdnf/log.hpp>
#include <libdnf/plugin/plugin.h>
// tukit includes
#include <tukit/Transaction.hpp>
// Basic structs
// Information about this plugin
// Pointer to this structure is returned by pluginGetInfo().
static const PluginInfo info = {.name = "TxnUpd", .version = TXNUPD_PLUGIN_VERSION};
// plugin context private data
// Pointer to instance of this structure is returned by pluginInitHandle() as handle.
struct _PluginHandle {
PluginMode mode;
DnfContext * context; // store plugin context specific init data
bool transactional_update; // flag to determine if doing a transactional update
bool ext_transactional_update; // flag to determine if within an existing transactional update
TransactionalUpdate::Transaction transaction{}; // transactional update object
};
// Returns general information about this plugin.
// Can be called at any time.
const PluginInfo * pluginGetInfo(void) {
return &info;
}
// Creates new instance of this plugin. Returns its handle.
PluginHandle * pluginInitHandle(int version, PluginMode mode, DnfPluginInitData * initData) {
auto logger(libdnf::Log::getLogger());
if (version != 1) {
auto msg = std::string(info.name) + ": " + __func__ + ": Error: Unsupported API version";
logger->error(msg);
return nullptr;
}
if (mode != PLUGIN_MODE_CONTEXT) {
auto msg = std::string(info.name) + ": " + __func__ + ": Warning: Unsupported mode";
logger->warning(msg);
return nullptr;
}
auto handle = new PluginHandle;
handle->mode = mode;
handle->context = pluginGetContext(initData);
handle->transactional_update = false;
handle->ext_transactional_update = (std::getenv("TRANSACTIONAL_UPDATE") != NULL);
return handle;
}
// Destroys the plugin instance identified by given handle.
void pluginFreeHandle(PluginHandle * handle) {
if (handle)
delete handle;
}
/**
* setupTransactionalUpdate
* @handle: a #PluginHandle instance.
*
* Sets up the transactional update and configures the DNF context to use it.
*
* Returns: %true for success, %false otherwise
**/
static bool setupTransactionalUpdate(PluginHandle * handle) {
auto logger(libdnf::Log::getLogger());
/* Let's assume that if we're in a situation that doesn't
* require transactional updates if we're in an alternate
* installroot. We only want to work with the "real" system.
*/
const std::string installRoot = dnf_context_get_install_root(handle->context);
if (!installRoot.empty() && installRoot.compare("/") != 0) {
auto instroot_msg =
std::string(info.name) + ": " + __func__ + ": Warning: Using installroot, disabling transactional-update!";
logger->warning(instroot_msg);
return true;
}
/* Also, all of the transactional-update stuff only works as root.
* If we're not root, assume we're running in the test suite or
* we're part of e.g. rpm-ostree which is trying to run totally
* as non-root.
*/
if (getuid() != 0) {
auto rootuid_msg = std::string(info.name) + ": " + __func__ +
": Warning: operating as non-root, disabling transactional-update!";
logger->warning(rootuid_msg);
return true;
}
/* Check if we're already in a transactional update, possibly
* externally invoked by tukit or some other consumer of tukit.
* In this scenario, bail early to stop us from attempting to
* create a transactional update inside of one in progress.
*/
if (handle->ext_transactional_update) {
// Disable SWDB in transactional update mode
dnf_context_set_write_history(handle->context, FALSE);
auto txnupdsnap_msg = std::string(info.name) + ": " + __func__ +
": Warning: running within transactional update, disabling snapshot handling!";
logger->warning(txnupdsnap_msg);
return true;
}
/* Now that we've gotten the basic early-bail conditions handled,
* let's set up the transaction object to configure transactional
* updates.
*/
handle->transaction.init("default");
/* Let's check to see if we succeeded in creating a snapshot before
* we try to proceed any further.
*/
if (!(handle->transaction.isInitialized())) {
auto txnfail_msg =
std::string(info.name) + ": " + __func__ + ": Error: failed to initialize transactional update!";
logger->error(txnfail_msg);
return false;
}
auto txnupd_snapid_msg =
std::string(info.name) + ": " + __func__ + ": Info: Created snapshot ID " + handle->transaction.getSnapshot();
logger->debug(txnupd_snapid_msg);
auto transactionRoot = handle->transaction.getRoot();
dnf_context_set_install_root(handle->context, transactionRoot.c_str());
/* Disable SWDB when in transactional update mode, as it
* it currently causes crashes with transactional updates,
* and it is not particularly useful for our case anyway.
*/
dnf_context_set_write_history(handle->context, FALSE);
/* Mark as being in the transactional update mode, so that
* other functions using the handle can do the right thing later.
*/
handle->transactional_update = true;
return true;
}
/**
* configureTransactionContext
* @handle: a #PluginHandle instance.
*
* Applies additional configuration to the DNF context for transactional updates
*
* Returns: %true for success, %false otherwise
**/
static bool configureTransactionContext(PluginHandle * handle) {
auto logger(libdnf::Log::getLogger());
/* Check to see if we are actually in transactional update mode,
* and bail out early if we are not.
*/
if ((!(handle->transactional_update)) && (!(handle->ext_transactional_update))) {
auto txnupd_msg =
std::string(info.name) + ": " + __func__ + ": Warning: Not a transactional update, so doing nothing!";
logger->warning(txnupd_msg);
return true;
}
/* Set installonlypkgs to nothing to disable multiversioned kernel installation,
* as we need it disabled for this type of transaction.
*/
if (!dnf_conf_main_set_option("installonlypkgs", DNF_CONF_PLUGINDEFAULT, "", NULL)) {
auto instonlypkgs_msg =
std::string(info.name) + ": " + __func__ + ": Error: installonlypkgs was not successfully overridden!";
logger->error(instonlypkgs_msg);
return false;
}
/* Disable protection of the running kernel, as this type of transaction never
* impacts the running system and it could misguidedly stop upgrades.
*/
if (!dnf_conf_main_set_option("protect_running_kernel", DNF_CONF_PLUGINDEFAULT, "0", NULL)) {
auto protectkernel_msg =
std::string(info.name) + ": " + __func__ + ": Error: protect_running_kernel was not successfully disabled!";
logger->error(protectkernel_msg);
return false;
}
return true;
}
/**
* completeTransactionalUpdate
* @handle: a #PluginHandle instance.
*
* Completes the transactional update and configures for next boot
*
* Returns: %true for success, %false otherwise
**/
static bool completeTransactionalUpdate(PluginHandle * handle) {
auto logger(libdnf::Log::getLogger());
/* Check to see if we are actually in transactional update mode,
* and bail out early if we are not.
*/
if (!(handle->transactional_update)) {
auto txnupd_msg =
std::string(info.name) + ": " + __func__ + ": Warning: Not a transactional update, so doing nothing!";
logger->warning(txnupd_msg);
return true;
}
/* Finally, complete the transaction and finalize
* the update for next boot.
*/
handle->transaction.finalize();
return true;
}
// The plugin "main loop"
int pluginHook(PluginHandle * handle, PluginHookId id, DnfPluginHookData * hookData, DnfPluginError * error) {
if (!handle)
return 1;
switch (id) {
case PLUGIN_HOOK_ID_CONTEXT_PRE_REPOS_RELOAD:
return setupTransactionalUpdate(handle);
case PLUGIN_HOOK_ID_CONTEXT_CONF:
return configureTransactionContext(handle);
case PLUGIN_HOOK_ID_CONTEXT_TRANSACTION:
return completeTransactionalUpdate(handle);
default:
break;
}
return 1;
}