From 5e458b6ee6d46652588214c75e76634ccfed5b35 Mon Sep 17 00:00:00 2001 From: Neal Gompa Date: Dec 23 2020 22:38:33 +0000 Subject: Initial implementation of the plugin This sets up the basic behavior and structure for the plugin, which should work as a starting point to implement the full behavior for transactional updates. --- diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..604e113 --- /dev/null +++ b/.clang-format @@ -0,0 +1,195 @@ +--- +Language: Cpp +BasedOnStyle: Google +Standard: Cpp11 + + +# Characters per line +ColumnLimit: 120 + + +# Do not wrap comments according to ColumnLimit +ReflowComments: false + + +# Indentation +IndentWidth: 4 +AccessModifierOffset: -4 +UseTab: false + +# Keep up to 2 empty lines +MaxEmptyLinesToKeep: 2 + + +# sort and group includes: c++, system, project +SortIncludes: true +IncludeBlocks: Regroup +IncludeCategories: + # C++ Standard Library headers + - Regex: '<[[:alnum:]_-]+>' + Priority: 5 + # system libraries + - Regex: '<.+>' + Priority: 4 + # project includes - libdnf absolute paths + - Regex: '"libdnf/.+"' + Priority: 3 + # project includes - libdnf-cli absolute paths + - Regex: '"libdnf-cli/.+"' + Priority: 2 + # project includes + - Regex: '".+"' + Priority: 1 + + +# Always break after an open bracket, if the parameters don't fit on a single line, e.g.: +# +# someLongFunction( +# argument1, argument2); +# +AlignAfterOpenBracket: AlwaysBreak + + +# Forbid simple braced statements on a single line. +# +# Allowed: +# if (a) { +# return; +# } +# +# Forbidden: +# if (a) { return; } +# +AllowShortBlocksOnASingleLine: false + + +# Forbid short case labels on a single line. +# +# Allowed: +# switch (a) { +# case 1: +# x = 1; +# break; +# } +# +# Forbidden: +# switch (a) { +# case 1: x = 1; break; +# case 2: return; +# } +# +AllowShortCaseLabelsOnASingleLine: false + + +# Allow only single line methods defined inside a class. +# +# Allowed: +# class Foo { +# void f() { foo(); } +# }; +# void f() { +# foo(); +# } +# +# Forbidden: +# void f() { foo(); } +AllowShortFunctionsOnASingleLine: Inline + + +# Forbid if statements on a single line. +# +# Allowed: +# if (a) +# return ; +# else { +# return; +# } +# +# Forbidden: +# if (a) return; +# else +# return; +# +AllowShortIfStatementsOnASingleLine: Never + + +# Forbid loops on a single line. +# +# Allowed: +# while (i < 1) { +# i--; +# } +# +# Forbidden: +# while (i < 1) i--; +# +AllowShortLoopsOnASingleLine: false + + +# Force middle pointer alignment. +# +# Examples: +# char * str; +# const std::string & str; +# +DerivePointerAlignment: false +PointerAlignment: Middle + + +# Allow only one argument/parameter per line. +# +# Allowed: +# void f(int aaaaaaaaaaaaaaaaaaaa, +# int aaaaaaaaaaaaaaaaaaaa, +# int aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) {} +# +# Forbidden: +# void f(int aaaaaaaaaaaaaaaaaaaa, int aaaaaaaaaaaaaaaaaaaa, +# int aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) {} +# +BinPackArguments: false +BinPackParameters: false + + +# If the function declaration doesn't fit on a line, put all parameters on the next line. +# +# Allowed: +# void myFunction( +# int a, int b, int c, int d, int e); +# +# Forbidden: +# void myFunction(int a, +# int b, +# int c, +# int d, +# int e); +# +AllowAllParametersOfDeclarationOnNextLine: true + + +# Allow only per line constructor intitializers. +# +# Allowed: +# MyClass::MyClass() +# : member0(0) +# , member1(2) +# +# Forbidden: +# MyClass::MyClass() : +# member0(0), member1(2) +# +AllowAllConstructorInitializersOnNextLine: false +BreakConstructorInitializers: BeforeComma + + +# Align consecutive C/C++ preprocessor macros. +# +# Example: +# #define SHORT_NAME 42 +# #define LONGER_NAME 0x007f +# #define EVEN_LONGER_NAME (2) +# #define foo(x) (x * x) +# #define bar(y, z) (y + z) +# +AlignConsecutiveMacros: true +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..6f50d12 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,31 @@ +--- +Checks: '*,readability-*,-google-runtime-references,-modernize-use-trailing-return-type,-hicpp-signed-bitwise,-fuchsia*,-modernize-use-nodiscard' +WarningsAsErrors: '' +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +FormatStyle: none +CheckOptions: + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.FunctionCase + value: lower_case + - key: readability-identifier-naming.GlobalConstantCase + value: UPPER_CASE + - key: readability-identifier-naming.NamespaceCase + value: lower_case + - key: readability-identifier-naming.ParameterCase + value: lower_case + - key: readability-identifier-naming.StructCase + value: CamelCase + - key: readability-identifier-naming.VariableCase + value: lower_case + + - key: readability-implicit-bool-conversion.AllowIntegerConditions + value: '0' + - key: readability-implicit-bool-conversion.AllowPointerConditions + value: '1' + - key: readability-inconsistent-declaration-parameter-name.IgnoreMacros + value: '1' + - key: readability-inconsistent-declaration-parameter-name.Strict + value: '1' +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8cb9bfd --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +# Created by https://www.toptal.com/developers/gitignore/api/c++,meson +# Edit at https://www.toptal.com/developers/gitignore?templates=c++,meson + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### Meson ### +# subproject directories +/subprojects/* +!/subprojects/*.wrap + +# Meson Directories +meson-logs +meson-private + +# Meson Files +meson_benchmark_setup.dat +meson_test_setup.dat +sanitycheckcpp.cc # C++ specific +sanitycheckcpp.exe # C++ specific + +# Ninja +build.ninja +.ninja_deps +.ninja_logs + +# Misc +compile_commands.json + +# End of https://www.toptal.com/developers/gitignore/api/c++,meson diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..43a7983 --- /dev/null +++ b/meson.build @@ -0,0 +1,51 @@ +project('libdnf-plugin-txnupd', 'cpp', + version : '0.0.0', + license : 'LGPL-2.1-or-later', + default_options : [ + 'buildtype=debugoptimized', + 'b_asneeded=True', + 'b_lundef=True', + 'b_pie=true', + 'cpp_std=gnu++17', + 'warning_level=3', + ], + meson_version : '>=0.49.0') + +cc = meson.get_compiler('cpp') +test_cppflags = [ + '-fstrict-aliasing', + '-Wformat=2', + '-Wunused', + '-Wuninitialized', + '-Wstrict-prototypes', + '-Wmissing-prototypes', + '-Werror=init-self', + '-Werror=main', +] +foreach cppflag : test_cppflags + if cc.has_argument(cppflag) + add_project_arguments(cppflag, language : 'cpp') + endif +endforeach + +add_project_arguments( + '-DTXNUPD_PLUGIN_VERSION="@0@"'.format(meson.project_version()), + language : 'cpp', +) + +libdnf = dependency('libdnf', version : '>=0.54.2') +tukit = dependency('tukit') + +libdnf_plugin_libdir = join_paths(get_option('prefix'), get_option('libdir'), 'libdnf', 'plugins') + +libdnf_plugin_src = files( + 'src/txnupd.cpp', +) + +libdnf_plugin = shared_module('txnupd', + libdnf_plugin_src, + dependencies : [libdnf, tukit], + name_prefix : '', + install : true, + install_dir : libdnf_plugin_libdir, +) diff --git a/src/txnupd.cpp b/src/txnupd.cpp new file mode 100644 index 0000000..90944c5 --- /dev/null +++ b/src/txnupd.cpp @@ -0,0 +1,188 @@ +/* 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 + * . + */ + +// Standard library includes +#include +#include + +// POSIX standard library includes +#include +#include + +// libdnf includes +#include +#include +#include + +// tukit includes +#include + +// 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()); + + /* Let's mark us 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; +} + +/** + * 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_TRANSACTION: + return completeTransactionalUpdate(handle); + default: + break; + } + return 1; +}