Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package libdnf-plugin-txnupd for 
openSUSE:Factory checked in at 2026-11-10 16:29:32
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/libdnf-plugin-txnupd (Old)
 and      /work/SRC/openSUSE:Factory/.libdnf-plugin-txnupd.new.3759290 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "libdnf-plugin-txnupd"

Tue Nov 10 16:29:32 2026 rev:5 rq:1396201 version:0.4.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/libdnf-plugin-txnupd/libdnf-plugin-txnupd.changes    
    2026-06-17 16:17:00.211759885 +0200
+++ 
/work/SRC/openSUSE:Factory/.libdnf-plugin-txnupd.new.3759290/libdnf-plugin-txnupd.changes
   2026-11-10 16:29:34.000650352 +0100
@@ -1,0 +2,12 @@
+Fri Nov  6 20:50:24 UTC 2026 - Shawn Dunn <[email protected]>
+
+- Update to 0.4.0:
+  * Fix linking with libtukit
+  * Finalize snapshots after libdnf5 closes rpmdb
+  * Log finish() messages in logger
+  * Report snapshot create failures, instead of aborting
+  * apply-live: Sync /etc state before applying snapshot
+  * Drop version information from manpage
+- Drop 3add4c39b5506f8bfafd1daca3bf6165e55c5e18.patch
+
+-------------------------------------------------------------------

Old:
----
  3add4c39b5506f8bfafd1daca3bf6165e55c5e18.patch
  libdnf-plugin-txnupd-0.3.0.tar.gz

New:
----
  libdnf-plugin-txnupd-0.4.0.tar.gz

----------(Old B)----------
  Old:  * Drop version information from manpage
- Drop 3add4c39b5506f8bfafd1daca3bf6165e55c5e18.patch
----------(Old E)----------

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ libdnf-plugin-txnupd.spec ++++++
--- /var/tmp/diff_new_pack.sBd0Hr/_old  2026-11-10 16:29:35.159698833 +0100
+++ /var/tmp/diff_new_pack.sBd0Hr/_new  2026-11-10 16:29:35.161698917 +0100
@@ -2,6 +2,7 @@
 # spec file for package libdnf-plugin-txnupd
 #
 # Copyright (c) 2026 Neal Gompa.
+# Copyright (c) 2026 Shawn W Dunn <[email protected]>
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -22,16 +23,13 @@
 
 
 Name:           libdnf-plugin-txnupd
-Version:        0.3.0
+Version:        0.4.0
 Release:        0
 Summary:        Plugin for libdnf5 to implement transactional updates
 License:        LGPL-2.1-or-later
 URL:            
https://gitlab.com/VelocityLimitless/Projects/libdnf-plugin-txnupd
 Source0:        %{url}/-/archive/%{version}/%{name}-%{version}.tar.gz
 
-# Backports from upstream
-Patch0001:      %{url}/-/commit/3add4c39b5506f8bfafd1daca3bf6165e55c5e18.patch
-
 BuildRequires:  cmake
 BuildRequires:  make
 BuildRequires:  gcc-c++ >= 13

++++++ libdnf-plugin-txnupd-0.3.0.tar.gz -> libdnf-plugin-txnupd-0.4.0.tar.gz 
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-plugin-txnupd-0.3.0/CMakeLists.txt 
new/libdnf-plugin-txnupd-0.4.0/CMakeLists.txt
--- old/libdnf-plugin-txnupd-0.3.0/CMakeLists.txt       2026-05-31 
19:55:44.000000000 +0200
+++ new/libdnf-plugin-txnupd-0.4.0/CMakeLists.txt       2026-11-06 
16:30:26.000000000 +0100
@@ -1,6 +1,6 @@
 cmake_minimum_required(VERSION 3.18)
 project(libdnf-plugin-txnupd
-    VERSION 0.3.0
+    VERSION 0.4.0
     LANGUAGES CXX
 )
 
@@ -52,6 +52,7 @@
 # Shared utility code (PUBLIC includes so plugin subdirs inherit the path)
 add_library(txnupd_util STATIC src/txnupd_util.cpp)
 target_include_directories(txnupd_util PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
+target_link_libraries(txnupd_util PUBLIC PkgConfig::TUKIT)
 
 add_subdirectory(src/libdnf5-plugin)
 add_subdirectory(src/dnf5-plugin)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-plugin-txnupd-0.3.0/doc/dnf5-txnupd.8.scd 
new/libdnf-plugin-txnupd-0.4.0/doc/dnf5-txnupd.8.scd
--- old/libdnf-plugin-txnupd-0.3.0/doc/dnf5-txnupd.8.scd        2026-05-31 
19:55:44.000000000 +0200
+++ new/libdnf-plugin-txnupd-0.4.0/doc/dnf5-txnupd.8.scd        2026-11-06 
16:30:26.000000000 +0100
@@ -1,4 +1,4 @@
-dnf5-txnupd(8) "libdnf-plugin-txnupd 0.2.0" "System Administration"
+dnf5-txnupd(8) "libdnf-plugin-txnupd" "System Administration"
 
 # NAME
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libdnf-plugin-txnupd-0.3.0/src/dnf5-plugin/CMakeLists.txt 
new/libdnf-plugin-txnupd-0.4.0/src/dnf5-plugin/CMakeLists.txt
--- old/libdnf-plugin-txnupd-0.3.0/src/dnf5-plugin/CMakeLists.txt       
2026-05-31 19:55:44.000000000 +0200
+++ new/libdnf-plugin-txnupd-0.4.0/src/dnf5-plugin/CMakeLists.txt       
2026-11-06 16:30:26.000000000 +0100
@@ -8,6 +8,7 @@
 target_link_libraries(txnupd_dnf5_cmd PRIVATE
     PkgConfig::LIBDNF5
     PkgConfig::LIBDNF5_CLI
+    PkgConfig::TUKIT
     ${DNF5_LIB}
     txnupd_util
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libdnf-plugin-txnupd-0.3.0/src/libdnf5-plugin/txnupd.cpp 
new/libdnf-plugin-txnupd-0.4.0/src/libdnf5-plugin/txnupd.cpp
--- old/libdnf-plugin-txnupd-0.3.0/src/libdnf5-plugin/txnupd.cpp        
2026-05-31 19:55:44.000000000 +0200
+++ new/libdnf-plugin-txnupd-0.4.0/src/libdnf5-plugin/txnupd.cpp        
2026-11-06 16:30:26.000000000 +0100
@@ -14,10 +14,13 @@
  */
 
 // Standard library includes
+#include <chrono>
 #include <cstdint>
 #include <cstdlib>
+#include <ctime>
 #include <filesystem>
 #include <iostream>
+#include <stdexcept>
 #include <string>
 
 // POSIX standard library includes
@@ -64,12 +67,48 @@
     const char * get_name() const noexcept override { return "TxnUpdError"; }
 };
 
+/* Minimal logger for use from finish().
+ *
+ * libdnf5 calls the plugins' finish() hook while the Base is being destroyed,
+ * at which point Base::get_logger() is no longer usable (the log router is
+ * destroyed before the plugins), so dnf5.log can not be written any more.
+ *
+ * On an interactive terminal only warnings and errors are shown here, the
+ * informational messages are printed in plain form by the caller. Otherwise
+ * (cron, systemd units, scripts, ...) nobody sees that plain form, so emit
+ * everything down to INFO as timestamped log lines on stderr, where the
+ * caller's journal or log file picks them up.
+ */
+class FinishLogger : public libdnf5::Logger {
+public:
+    void write(
+       const std::chrono::time_point<std::chrono::system_clock> & time,
+       pid_t pid,
+       Level level,
+       const std::string & message) noexcept override {
+       const bool interactive = isatty(STDOUT_FILENO);
+       if (level > (interactive ? Level::WARNING : Level::INFO))
+           return;
+       if (interactive) {
+           std::cerr << message << std::endl;
+           return;
+       }
+       const std::time_t t = std::chrono::system_clock::to_time_t(time);
+       std::tm tm{};
+       char stamp[32] = "";
+       if (localtime_r(&t, &tm))
+           std::strftime(stamp, sizeof(stamp), "%Y-%m-%dT%H:%M:%S%z", &tm);
+       std::cerr << stamp << " [" << pid << "] " << level_to_cstr(level) << " 
" << message << std::endl;
+    }
+};
+
 class TxnUpd : public plugin::IPlugin2_1 {
 private:
     bool transactional_update = false;
     bool ext_transactional_update = false;
     bool apply_after_transaction = false;
     bool print_reboot_msg = false;
+    bool finalize_pending = false;
     TransactionalUpdate::Transaction * txnupd_transaction = nullptr;
     std::string base_snapshot_id;
     std::string snapshot_id;
@@ -147,9 +186,23 @@
         * updates. The object is manually instantiated to avoid failures
         * before the checks evaluate in the plugin.
         */
-       base_snapshot_id = 
TransactionalUpdate::SnapshotFactory::get()->getDefault();
-       txnupd_transaction = new TransactionalUpdate::Transaction{};
-       txnupd_transaction->init("default");
+       /* tukit reports failures (for example a failing snapper plugin) by
+        * throwing exceptions that are not libdnf5 errors. dnf5 only handles
+        * libdnf5::Error and std::runtime_error, so if they escape the
+        * process is terminated with a core dump. dnf5 does report a
+        * std::runtime_error cleanly, so translate them into one.
+        */
+       try {
+           base_snapshot_id = 
TransactionalUpdate::SnapshotFactory::get()->getDefault();
+           txnupd_transaction = new TransactionalUpdate::Transaction{};
+           txnupd_transaction->init("default");
+       } catch (const std::exception & e) {
+           /* Deleting the transaction aborts a snapshot that was created but
+            * could not be set up. */
+           delete txnupd_transaction;
+           txnupd_transaction = nullptr;
+           throw std::runtime_error(std::string("txnupd plugin : Failed to 
create a snapshot: ") + e.what());
+       }
 
        /* Let's check to see if we succeeded in creating a snapshot before
         * we try to proceed any further.
@@ -221,10 +274,53 @@
            logger.warning("txnupd plugin : No package changes in transaction, 
skipping snapshot finalization.");
            return;
        }
-       /* Finally, complete the transaction and finalize
-        * the update for next boot.
+       /* The snapshot must not be finalized here. libdnf5 still holds the
+        * rpm database of the snapshot (installroot) open at this point and
+        * only closes it - which checkpoints the sqlite WAL and so writes to
+        * the memory mapped -shm file - after all post_transaction hooks have
+        * run. Finalizing makes the snapshot read-only and unmounts it, so
+        * that last write would fail with SIGBUS. Defer it to finish(),
+        * which runs after the rpm transaction has been freed.
         */
-       txnupd_transaction->finalize();
+       logger.debug("txnupd plugin : Deferring finalization of snapshot #" + 
snapshot_id + " until libdnf5 is done.");
+       finalize_pending = true;
+
+       return;
+    }
+
+    /* Complete the transaction and finalize the update for next boot.
+     *
+     * Called by libdnf5 when the Base is destroyed, i.e. after the rpm
+     * transaction (and with it the rpm database in the snapshot) has been
+     * closed. Neither Base nor its logger may be used from here on.
+     */
+    void finish() noexcept override {
+       if (!finalize_pending || txnupd_transaction == nullptr)
+           return;
+       finalize_pending = false;
+
+       FinishLogger logger;
+       std::string failure;
+       try {
+           txnupd_transaction->finalize();
+       } catch (const std::exception & e) {
+           failure = e.what();
+       } catch (...) {
+           /* tukit plugins report failures by throwing their exit status */
+           failure = "unknown error";
+       }
+       if (!failure.empty()) {
+           /* The snapshot is not activated. Let tukit's destructor abort it
+            * and do not pretend that the update worked: finish() cannot
+            * report errors to dnf5, so end the process with a failure
+            * status instead of returning normally. */
+           delete txnupd_transaction;
+           txnupd_transaction = nullptr;
+           std::cout.flush();
+           logger.error("txnupd plugin : Could not finalize snapshot #" + 
snapshot_id + ": " + failure);
+           logger.error("txnupd plugin : The changes will NOT be active after 
a reboot!");
+           std::_Exit(EXIT_FAILURE);
+       }
        delete txnupd_transaction;
        txnupd_transaction = nullptr;
 
@@ -249,8 +345,6 @@
            logger.info("txnupd plugin : Please reboot your machine to activate 
the changes and avoid data loss.");
            print_reboot_msg = true;
        }
-
-       return;
     }
 };
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-plugin-txnupd-0.3.0/src/txnupd_util.cpp 
new/libdnf-plugin-txnupd-0.4.0/src/txnupd_util.cpp
--- old/libdnf-plugin-txnupd-0.3.0/src/txnupd_util.cpp  2026-05-31 
19:55:44.000000000 +0200
+++ new/libdnf-plugin-txnupd-0.4.0/src/txnupd_util.cpp  2026-11-06 
16:30:26.000000000 +0100
@@ -33,6 +33,17 @@
 
 namespace {
 
+// True if path exists, is a regular file (symlinks are followed) and has an
+// execute permission bit set.
+bool is_program(const std::filesystem::path & path) {
+    std::error_code ec;
+    const auto st = std::filesystem::status(path, ec);
+    if (ec || !std::filesystem::is_regular_file(st))
+       return false;
+    using std::filesystem::perms;
+    return (st.permissions() & (perms::owner_exec | perms::group_exec | 
perms::others_exec)) != perms::none;
+}
+
 std::string run_output(const std::string & cmd) {
     std::array<char, 512> buf;
     std::string result;
@@ -183,6 +194,27 @@
        }
     }
 
+    // Changes made to the running /etc after the snapshot was created exist
+    // only in the old /etc and would be hidden by the rbind below. Merge them
+    // into the snapshot's /etc first, as the boot-time sync (and
+    // `transactional-update apply`) does. Only possible if the snapshot has
+    // a sync point, i.e. /etc is a subvolume created by tukit's snapper 
plugin.
+    const std::filesystem::path snapshot_etc = 
std::filesystem::path("/.snapshots") / snapshot_id / "snapshot/etc";
+    const std::filesystem::path syncpoint = snapshot_etc / "etc.syncpoint";
+    const std::filesystem::path sync_etc_state = 
"/usr/libexec/transactional-update-sync-etc-state";
+    if (is_program(sync_etc_state) && std::filesystem::exists(syncpoint)) {
+       logger.info("txnupd_util : Syncing /etc state from running system...");
+       if (isatty(STDOUT_FILENO))
+           std::cout << "Syncing /etc state from running system..." << 
std::endl;
+       int ret = run_argv({sync_etc_state.string(), "/etc", 
snapshot_etc.string(), syncpoint.string()});
+       if (ret != 0) {
+           cleanup();
+           throw std::runtime_error(
+               "apply-live: syncing /etc state failed (exit code " + 
std::to_string(ret) +
+               "), the running system was not modified");
+       }
+    }
+
     logger.info("txnupd_util : Applying snapshot " + snapshot_id + " to 
running system...");
     if (isatty(STDOUT_FILENO))
        std::cout << "Applying snapshot " << snapshot_id << " to running 
system..." << std::endl;

Reply via email to