Blob Blame History Raw
/* 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 <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
    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;
    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;
    }

    /* 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 as 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());

    /* 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;
    }

    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;
}