Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package transactional-update for 
openSUSE:Factory checked in at 2021-08-05 20:47:53
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/transactional-update (Old)
 and      /work/SRC/openSUSE:Factory/.transactional-update.new.1899 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "transactional-update"

Thu Aug  5 20:47:53 2021 rev:74 rq:909993 version:3.5.1

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/transactional-update/transactional-update.changes    
    2021-06-16 20:33:25.350974952 +0200
+++ 
/work/SRC/openSUSE:Factory/.transactional-update.new.1899/transactional-update.changes
      2021-08-05 20:48:13.583923733 +0200
@@ -1,0 +2,32 @@
+Tue Aug  3 12:41:41 UTC 2021 - Ignaz Forster <ifors...@suse.com>
+
+- Version 3.5.1
+  - t-u: Disable status file generation by default
+    The new experimental `status` command requires the availability of
+    /etc/YaST2/control.xml, which is not present on all systems. Hide the
+    creation of the corresponding status file behind a new EXPERIMENTAL_STATUS
+    option to try out this functionality.
+  - Increase library version
+
+-------------------------------------------------------------------
+Tue Aug  3 07:37:36 UTC 2021 - Ignaz Forster <ifors...@suse.com>
+
+- Add tukit.conf to spec file
+
+-------------------------------------------------------------------
+Mon Aug  2 15:58:09 UTC 2021 - Ignaz Forster <ifors...@suse.com>
+
+- Version 3.5.0
+  - Add alias setDiscardIfUnchanged for setDiscard. The old method name
+    wasn't really clear and will be removed if we should have an API break
+    in the future
+  - Replace mkinitrd with direct dracut call [boo#1186213]
+  - tukit: Add configuration file support (/etc/tukit.conf)
+  - Allow users to configure additional bind mounts (see /usr/etc/tukit.conf
+    for an example and limitations) [bsc#1188322]
+  - Add 'transactional-update status' call. This is a POC for obtaining a
+    hash of a system to verify its integrity. The functionality is still
+    experimental!
+  - Internal bugfixes / optimizations
+
+-------------------------------------------------------------------

Old:
----
  transactional-update-3.4.0.tar.gz

New:
----
  transactional-update-3.5.1.tar.gz

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

Other differences:
------------------
++++++ transactional-update.spec ++++++
--- /var/tmp/diff_new_pack.GXIzTi/_old  2021-08-05 20:48:14.079923176 +0200
+++ /var/tmp/diff_new_pack.GXIzTi/_new  2021-08-05 20:48:14.083923172 +0200
@@ -26,7 +26,7 @@
 %{!?_distconfdir: %global _distconfdir %{_prefix}%{_sysconfdir}}
 
 Name:           transactional-update
-Version:        3.4.0
+Version:        3.5.1
 Release:        0
 Summary:        Transactional Updates with btrfs and snapshots
 License:        GPL-2.0-or-later AND LGPL-2.1-or-later
@@ -212,6 +212,7 @@
 %dir %{_distconfdir}
 %endif
 %{_distconfdir}/transactional-update.conf
+%{_distconfdir}/tukit.conf
 %{_mandir}/man5/transactional-update.conf.5*
 %{_mandir}/man8/transactional-update.8*
 %{_mandir}/man8/transactional-update.timer.8*

++++++ transactional-update-3.4.0.tar.gz -> transactional-update-3.5.1.tar.gz 
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transactional-update-3.4.0/NEWS 
new/transactional-update-3.5.1/NEWS
--- old/transactional-update-3.4.0/NEWS 2021-06-14 00:37:42.000000000 +0200
+++ new/transactional-update-3.5.1/NEWS 2021-08-03 14:34:57.000000000 +0200
@@ -2,6 +2,37 @@
 
 Copyright (C) 2016-2020 Thorsten Kukuk, Ignaz Forster et al.
 
+Version 3.5.1
+* t-u: Disable status file generation by default
+  The new experimental `status` command requires the availability of
+  /etc/YaST2/control.xml, which is not present on all systems. Hide the
+  creation of the corresponding status file behind a new EXPERIMENTAL_STATUS
+  option to try out this functionality.
+* Increase library version
+
+Version 3.5.0
+* Add alias setDiscardIfUnchanged for setDiscard. The old method name
+  wasn't really clear and will be removed if we should have an API break
+  in the future
+* Replace mkinitrd with direct dracut call [boo#1186213]
+* tukit: Add configuration file support (/etc/tukit.conf)
+* Allow users to configure additional bind mounts (see /usr/etc/tukit.conf
+  for an example and limitations) [bsc#1188322]
+* Add 'transactional-update status' call. This is a POC for obtaining a
+  hash of a system to verify its integrity. The functionality is still
+  experimental!
+* Internal bugfixes / optimizations
+
+Version 3.4.0
+* Apply SElinux context on /etc in transaction [boo#1185625], [boo#1185766]
+  [bsc#1186842], [boo#1186775]
+* Implement inotify handling in C instead of Bash; this makes the
+  --drop-if-no-change option work on SLE Micro [bsc#1184529]
+* Use `tukit call` for up, dup and patch to allow resuming an update after
+  zypper updated itself in the snapshot [bsc#1185226]
+* Fix obsolete output type messages in initrd [boo#1177149]
+* Make different base snapshot warning more visible [bsc#1185224]
+
 Version 3.3.0
 * Add support for more package managers by bind mounting their directories
 * Support snapshots without dedicated overlay [boo#1183539], [bsc#1183539]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transactional-update-3.4.0/configure.ac 
new/transactional-update-3.5.1/configure.ac
--- old/transactional-update-3.4.0/configure.ac 2021-06-14 00:37:42.000000000 
+0200
+++ new/transactional-update-3.5.1/configure.ac 2021-08-03 14:34:57.000000000 
+0200
@@ -1,11 +1,11 @@
 dnl Process this file with autoconf to produce a configure script.
-AC_INIT(transactional-update, 3.3.0)
+AC_INIT(transactional-update, 3.5.1)
 # Increase on any interface change and reset revision
-LIBTOOL_CURRENT=2
+LIBTOOL_CURRENT=3
 # Increase or reset on any VERSION update
-LIBTOOL_REVISION=2
+LIBTOOL_REVISION=1
 # Increase if interface change is backwards compatible, reset otherwise
-LIBTOOL_AGE=2
+LIBTOOL_AGE=3
 AC_CANONICAL_SYSTEM
 AM_INIT_AUTOMAKE([foreign])
 AC_CONFIG_FILES([tukit.pc])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transactional-update-3.4.0/etc/Makefile.am 
new/transactional-update-3.5.1/etc/Makefile.am
--- old/transactional-update-3.4.0/etc/Makefile.am      2021-06-14 
00:37:42.000000000 +0200
+++ new/transactional-update-3.5.1/etc/Makefile.am      2021-08-03 
14:34:57.000000000 +0200
@@ -1,12 +1,6 @@
-#
-# Copyright (c) 2018 Ignaz Forster <ifors...@suse.com>
-#
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2018-2021 SUSE LLC
 
-EXTRA_DIST = transactional-update.conf
-
-install-data-local:
-       $(mkinstalldirs) $(DESTDIR)$(prefix)$(sysconfdir)
-       $(INSTALL_DATA) $(srcdir)/$(EXTRA_DIST) $(DESTDIR)$(prefix)$(sysconfdir)
-
-uninstall-local:
-       -rm $(DESTDIR)$(prefix)$(sysconfdir)/$(EXTRA_DIST)
+EXTRA_DIST = transactional-update.conf tukit.conf
+configdir = $(prefix)$(sysconfdir)
+config_DATA = $(EXTRA_DIST)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transactional-update-3.4.0/etc/tukit.conf 
new/transactional-update-3.5.1/etc/tukit.conf
--- old/transactional-update-3.4.0/etc/tukit.conf       1970-01-01 
01:00:00.000000000 +0100
+++ new/transactional-update-3.5.1/etc/tukit.conf       2021-08-03 
14:34:57.000000000 +0200
@@ -0,0 +1,13 @@
+# Bind mount additional directories into the update environment; to add
+# multiple directories add a separate entry with a different id between the
+# brackets for each.
+# Be aware that transactions are only done the root file system itself, i.e.
+# any additional mounts added here will *not* get a seperate snapshot (so
+# their contents cannot be rolled back) and the changes will be visible to
+# the running system immediately (breaking the transactional concept and
+# possibly resulting in a mix between old and new files if there are also
+# changes to the root file system until the next reboot).
+# By default only /opt will be bind-mounted, as this directory is often
+# used by third-party packages, but the applications in there are usually
+# self-contained.
+BINDDIRS[0]="/opt"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transactional-update-3.4.0/lib/Configuration.cpp 
new/transactional-update-3.5.1/lib/Configuration.cpp
--- old/transactional-update-3.4.0/lib/Configuration.cpp        2021-06-14 
00:37:42.000000000 +0200
+++ new/transactional-update-3.5.1/lib/Configuration.cpp        2021-08-03 
14:34:57.000000000 +0200
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
-/* SPDX-FileCopyrightText: 2020 SUSE LLC */
+/* SPDX-FileCopyrightText: 2020-2021 SUSE LLC */
 
 /*
   Retrieves configuration values, set via configuration file or using
@@ -9,13 +9,15 @@
 #include "Configuration.hpp"
 #include "Util.hpp"
 #include <map>
+#include <regex>
 #include <stdexcept>
 #include <libeconf.h>
 
 namespace TransactionalUpdate {
 
 Configuration::Configuration() {
-    econf_err error = econf_newIniFile(&key_file);
+    econf_file *kf_defaults;
+    econf_err error = econf_newIniFile(&kf_defaults);
     if (error)
         throw std::runtime_error{"Could not create default configuration."};
     std::map<const char*, const char*> defaults = {
@@ -24,9 +26,31 @@
         {"OVERLAY_DIR", "/var/lib/overlay"}
     };
     for(auto &[key, value] : defaults) {
-        error = econf_setStringValue(key_file, "", key, value);
-        if (error)
+        error = econf_setStringValue(kf_defaults, "", key, value);
+        if (error) {
+            econf_freeFile(kf_defaults);
             throw std::runtime_error{"Could not set default value for '" + 
std::string(key) + "'."};
+        }
+    }
+
+    econf_file *kf_conffiles;
+    error = econf_readDirs(&kf_conffiles, (std::string(PREFIX) + 
CONFDIR).c_str(), CONFDIR, "tukit", ".conf", "=", "#");
+    if (error && error != ECONF_NOFILE) {
+        econf_freeFile(kf_defaults);
+        econf_freeFile(kf_conffiles);
+        throw std::runtime_error{"Couldn't read configuration file: " + 
std::string(econf_errString(error))};
+    }
+
+    if (error == ECONF_SUCCESS) {
+        error = econf_mergeFiles(&key_file, kf_defaults, kf_conffiles);
+        econf_freeFile(kf_defaults);
+        econf_freeFile(kf_conffiles);
+        if (error) {
+            throw std::runtime_error{"Couldn't merge configuration: " + 
std::string(econf_errString(error))};
+        }
+    } else {
+        econf_freeFile(key_file);
+        key_file = std::move(kf_defaults);
     }
 }
 
@@ -42,4 +66,32 @@
     return std::string(val);
 }
 
+std::vector<std::string> Configuration::getArray(const std::string &key) {
+    std::vector<std::string> ret;
+    econf_err error;
+
+    size_t len = 0;
+    char** confkeys;
+    error = econf_getKeys(key_file, "", &len, &confkeys);
+    if (error)
+        throw std::runtime_error{"Could not read keys."};
+
+    std::regex exp(key + "\\[.*\\]");
+    for (size_t i = 0; i < len; i++) {
+        if (std::regex_match(confkeys[i], exp)) {
+            CString val;
+            error = econf_getStringValue(key_file, "", confkeys[i], &val.ptr);
+            if (error) {
+                econf_freeArray(confkeys);
+                throw std::runtime_error{"Could not read key '" + 
std::string(confkeys[i]) + "'."};
+            }
+            if (val != nullptr)
+                ret.push_back(std::string(val));
+        }
+    }
+    econf_freeArray(confkeys);
+
+    return ret;
+}
+
 } // namespace TransactionalUpdate
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transactional-update-3.4.0/lib/Configuration.hpp 
new/transactional-update-3.5.1/lib/Configuration.hpp
--- old/transactional-update-3.4.0/lib/Configuration.hpp        2021-06-14 
00:37:42.000000000 +0200
+++ new/transactional-update-3.5.1/lib/Configuration.hpp        2021-08-03 
14:34:57.000000000 +0200
@@ -10,6 +10,7 @@
 #define T_U_CONFIGURATION_H
 
 #include <string>
+#include <vector>
 
 typedef struct econf_file econf_file;
 
@@ -22,6 +23,7 @@
     Configuration(const Configuration&) = delete;
     void operator=(const Configuration&) = delete;
     std::string get(const std::string &key);
+    std::vector<std::string> getArray(const std::string &key);
 private:
     econf_file *key_file;
 };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transactional-update-3.4.0/lib/Makefile.am 
new/transactional-update-3.5.1/lib/Makefile.am
--- old/transactional-update-3.4.0/lib/Makefile.am      2021-06-14 
00:37:42.000000000 +0200
+++ new/transactional-update-3.5.1/lib/Makefile.am      2021-08-03 
14:34:57.000000000 +0200
@@ -9,6 +9,6 @@
 noinst_HEADERS=Snapshot.hpp Snapshot/Snapper.hpp \
         Mount.hpp Overlay.hpp Log.hpp Configuration.hpp \
         Util.hpp Supplement.hpp Exceptions.hpp
-libtukit_la_CPPFLAGS=$(ECONF_CFLAGS) $(LIBMOUNT_CFLAGS) $(SELINUX_CFLAGS)
+libtukit_la_CPPFLAGS=-DPREFIX=\"$(prefix)\" -DCONFDIR=\"$(sysconfdir)\" 
$(ECONF_CFLAGS) $(LIBMOUNT_CFLAGS) $(SELINUX_CFLAGS)
 libtukit_la_LDFLAGS=$(ECONF_LIBS) $(LIBMOUNT_LIBS) $(SELINUX_LIBS) \
        -version-info $(LIBTOOL_CURRENT):$(LIBTOOL_REVISION):$(LIBTOOL_AGE)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transactional-update-3.4.0/lib/Transaction.cpp 
new/transactional-update-3.5.1/lib/Transaction.cpp
--- old/transactional-update-3.4.0/lib/Transaction.cpp  2021-06-14 
00:37:42.000000000 +0200
+++ new/transactional-update-3.5.1/lib/Transaction.cpp  2021-08-03 
14:34:57.000000000 +0200
@@ -20,7 +20,6 @@
 #include <cstdlib>
 #include <cstring>
 #include <fstream>
-#include <future>
 #include <ftw.h>
 #include <limits.h>
 #include <poll.h>
@@ -40,13 +39,12 @@
     void mount();
     int runCommand(char* argv[], bool inChroot);
     static int inotifyAdd(const char *pathname, const struct stat *sbuf, int 
type, struct FTW *ftwb);
-    static void inotifyRead();
+    int inotifyRead();
     std::unique_ptr<Snapshot> snapshot;
     std::string bindDir;
     std::vector<std::unique_ptr<Mount>> dirsToMount;
     Supplements supplements;
     pid_t pidCmd;
-    std::future<void> inotifyListener;
     bool discardIfNoChange = false;
 };
 
@@ -97,7 +95,11 @@
 void Transaction::impl::mount() {
     dirsToMount.push_back(std::make_unique<PropagatedBindMount>("/dev"));
     dirsToMount.push_back(std::make_unique<BindMount>("/var/log"));
-    dirsToMount.push_back(std::make_unique<BindMount>("/opt"));
+
+    std::vector<std::string> customDirs = config.getArray("BINDDIRS");
+    for (auto it = customDirs.begin(); it != customDirs.end(); ++it) {
+        dirsToMount.push_back(std::make_unique<BindMount>(*it));
+    }
 
     Mount mntVar{"/var"};
     if (mntVar.isMount()) {
@@ -235,18 +237,21 @@
     }
 }
 
-void Transaction::setDiscard(bool discard) {
+void Transaction::setDiscardIfUnchanged(bool discard) {
     pImpl->discardIfNoChange = discard;
 }
+void Transaction::setDiscard(bool discard) {
+    setDiscardIfUnchanged(discard);
+}
 
-void Transaction::impl::inotifyRead() {
+int Transaction::impl::inotifyRead() {
     size_t bufLen = sizeof(struct inotify_event) + NAME_MAX + 1;
     char buf[bufLen] __attribute__((aligned(8)));
     ssize_t numRead;
     int ret;
 
     struct pollfd pfd = {inotifyFd, POLLIN, 0};
-    while ((ret = (poll(&pfd, 1, 500))) == 0) {}
+    ret = (poll(&pfd, 1, 500));
     if (ret == -1) {
         throw std::runtime_error{"Polling inotify file descriptior failed: " + 
std::string(strerror(errno))};
     } else if (ret > 0) {
@@ -254,9 +259,10 @@
         if (numRead == 0)
             throw std::runtime_error{"Read() from inotify fd returned 0!"};
         if (numRead == -1)
-            throw  std::runtime_error{"read"};
+            throw std::runtime_error{"Reading from inotify fd failed: " + 
std::string(strerror(errno))};
         tulog.debug("inotify: Exiting after event on ", ((struct inotify_event 
*)buf)->name);
     }
+    return ret;
 }
 
 int Transaction::impl::runCommand(char* argv[], bool inChroot) {
@@ -267,8 +273,6 @@
 
         // Recursively register all directories of the root file system
         nftw(snapshot->getRoot().c_str(), inotifyAdd, 20, FTW_MOUNT | 
FTW_PHYS);
-
-        inotifyListener = std::async(inotifyRead);
     }
 
     std::string opts = "Executing `";
@@ -302,6 +306,7 @@
         if (execvp(argv[0], (char* const*)argv) < 0) {
             throw std::runtime_error{"Calling " + std::string(argv[0]) + " 
failed: " + std::string(strerror(errno))};
         }
+        ret = -1;
     } else {
         this->pidCmd = pid;
         ret = waitpid(pid, &status, 0);
@@ -347,7 +352,7 @@
 void Transaction::finalize() {
     sync();
     if (pImpl->discardIfNoChange &&
-            ((inotifyFd != 0 && 
pImpl->inotifyListener.wait_for(std::chrono::milliseconds(0)) != 
std::future_status::ready) ||
+            ((inotifyFd != 0 && pImpl->inotifyRead() == 0) ||
             (inotifyFd == 0 && fs::exists(pImpl->snapshot->getRoot() / 
"discardIfNoChange")))) {
         tulog.info("No changes to the root file system - discarding 
snapshot.");
 
@@ -383,7 +388,7 @@
 
 void Transaction::keep() {
     sync();
-    if (fs::exists(pImpl->snapshot->getRoot() / "discardIfNoChange") && 
(inotifyFd != 0 && 
pImpl->inotifyListener.wait_for(std::chrono::milliseconds(0)) == 
std::future_status::ready)) {
+    if (fs::exists(pImpl->snapshot->getRoot() / "discardIfNoChange") && 
(inotifyFd != 0 && pImpl->inotifyRead() > 0)) {
         tulog.debug("Snapshot was changed, removing discard flagfile.");
         fs::remove(pImpl->snapshot->getRoot() / "discardIfNoChange");
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transactional-update-3.4.0/lib/Transaction.hpp 
new/transactional-update-3.5.1/lib/Transaction.hpp
--- old/transactional-update-3.4.0/lib/Transaction.hpp  2021-06-14 
00:37:42.000000000 +0200
+++ new/transactional-update-3.5.1/lib/Transaction.hpp  2021-08-03 
14:34:57.000000000 +0200
@@ -68,6 +68,8 @@
      * Be aware that inotify registration may fail, e.g. if a system has a lot 
of open inotify
      * listeners already. Changes may not be detected correctly in this case.
      */
+    void setDiscardIfUnchanged(bool discard);
+    /** Temporary legacy name - remove on the next incompatible interface 
change. **/
     void setDiscard(bool discard);
 
     /**
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/transactional-update-3.4.0/man/transactional-update.8.xml 
new/transactional-update-3.5.1/man/transactional-update.8.xml
--- old/transactional-update-3.4.0/man/transactional-update.8.xml       
2021-06-14 00:37:42.000000000 +0200
+++ new/transactional-update-3.5.1/man/transactional-update.8.xml       
2021-08-03 14:34:57.000000000 +0200
@@ -441,6 +441,64 @@
       </para>
     </listitem>
   </varlistentry>
+  <varlistentry>
+    <term><option>status</option></term>
+    <listitem>
+      <warning>
+       This command is under development and will change in the
+       future. The status command is currently using a non-stable
+       feature of MicroOS, the control.xml file, that will change the
+       scope and location in future releases.
+      </warning>
+      <para>
+       Shows the status of current and older snapshots. The status
+       try to represent how the system diverges from an ideal base
+       image, and the list of patterns and packages added and removed
+       from it.
+      </para>
+      <para>
+       The status is represented via a hash (currently sha256) of a
+       manifest file that contains the list of expected packages and
+       the build time of those packages. The list of expected
+       packages are calculated using the <command>libsolv</command>
+       library, and resolving it for the list of default patterns
+       associated with the role used during the installation.
+      </para>
+      <para>
+       Two manifest files are created. One will use the information
+       from the repositories used for the installation. The list of
+       packages (including the full version and architecture) and the
+       build time will be extracted from the repository information.
+       The other one will use the same list of packages, but using
+       the build time from the current system (if the package is
+       present).
+      </para>
+      <para>
+       Both manifest files will be the same if we have a fresh
+       installed system. But in distributions like MicroOS, that are
+       released based on a rolling model, the subsequent updates will
+       make compromises that will result in a diverged state. In that
+       way, if two systems have the same BASE_MANIFEST_DIGEST (the
+       digest calculated from the repository information), we can
+       assume that are in the same state even if the
+       SYSTEM_MANIFEST_DIGEST is different.
+      </para>
+      <para>
+       We can require of two system to be in the exact same status,
+       and for that we can compare both digests.
+      </para>
+      <para>
+       The status also shows the differences in patterns and packages
+       from the expected situation, and will save a copy of both
+       manifest for further audit of the system.
+      </para>
+      <para>
+       To write the necessary state file
+       <option>EXPERIMENTAL_STATUS=1</option>has to be set in
+       <filename>transactional-update.conf</filename>.
+      </para>
+    </listitem>
+  </varlistentry>
 </variablelist>
 </refsect2>
 </refsect1>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/transactional-update-3.4.0/sbin/transactional-update.in 
new/transactional-update-3.5.1/sbin/transactional-update.in
--- old/transactional-update-3.4.0/sbin/transactional-update.in 2021-06-14 
00:37:42.000000000 +0200
+++ new/transactional-update-3.5.1/sbin/transactional-update.in 2021-08-03 
14:34:57.000000000 +0200
@@ -38,6 +38,8 @@
 DO_SELF_UPDATE=1
 DO_REGISTRATION=0
 DO_RUN=0
+DO_STATUS=0
+DO_STATUS_LAST=0
 REGISTRATION_ARGS=""
 ROLLBACK_SNAPSHOT=0
 REBOOT_AFTERWARDS=0
@@ -55,6 +57,10 @@
 NEW_SNAPSHOT_FLAG="/var/lib/overlay/transactional-update.newsnapshot"
 NEEDS_RESTARTING_FILE="/var/run/reboot-needed"
 LOCKFILE="/var/run/transactional-update.pid"
+STATUS_FILE="@libdir@/sysimage/tu/transactional-update.status"
+EXPERIMENTAL_STATUS=0
+BASE_MANIFEST_FILE="@libdir@/sysimage/tu/base.manifest"
+SYSTEM_MANIFEST_FILE="@libdir@/sysimage/tu/system.manifest"
 ZYPPER_AUTO_IMPORT_KEYS=0
 
ETC_OVERLAY_PATTERN='^[^[:space:]]\+[[:space:]]\+\/etc[[:space:]]\+overlay[[:space:]]\+\([^[:space:]]*,\|\)workdir=\/sysroot\/var\/lib\/overlay\/work-etc[,[:space:]]'
 NON_ROOTFS_WHITELIST=("/var/lib/YaST2/cookies" "/var/lib/rpm" 
"/var/lib/systemd/migrated" "/var/run/zypp.pid")
@@ -80,6 +86,10 @@
 TMPFILE=""
 DO_CALLEXT=0
 
+declare -A ROLES=()
+REPOS=()
+declare -A BUILDTIME=()
+
 # Create stderr alias for things that shouldn't be logged into logfile
 if [ ! -e /proc/$$/fd/4 ]; then
        exec 4>&2
@@ -152,6 +162,7 @@
     echo "Standalone Commands:"
     echo "rollback [<number>]        Set the current or given snapshot as 
default snapshot"
     echo "rollback last              Set the last working snapshot as default 
snapshot"
+    echo "status                     Status and history report [experimental - 
enabled: ${EXPERIMENTAL_STATUS}]"
     echo ""
     echo "Options:"
     echo "--interactive, -i          Use interactive mode for package command"
@@ -177,6 +188,10 @@
     fi
 }
 
+log_to_file() {
+    echo -e `date "+%Y-%m-%d %H:%M"` "$@" >> ${LOGFILE}
+}
+
 log_error() {
     TELEM_PAYLOAD="${TELEM_PAYLOAD}\nmessage=$@"
     echo -e `date "+%Y-%m-%d %H:%M"` "$@" >> ${LOGFILE}
@@ -349,6 +364,401 @@
     touch "${VARDIR}/check-registration"
 }
 
+# Return the optimized list of mount points
+root_mount_points() {
+    local prev=0
+    for mount in $(findmnt --noheadings --submounts --target / --output TARGET 
--raw | sort | tail -n +2); do
+       if [ "${prev}/" != "${mount:0:$((${#prev}+1))}" ]; then
+           echo "${mount}"
+           prev="${mount}"
+       fi
+    done
+}
+
+# Call tukit callext with the correct parameters
+callext() {
+    tukit ${TUKIT_OPTS} --quiet callext "${SNAPSHOT_ID}" "$@"
+}
+
+# Helper function to grep perl regular expressions
+re_findall() {
+    grep --only-matching --perl-regexp "$1" "${2:--}"
+}
+
+# XXX bsc#1170709 can impact here, but is not expected tha the base
+# product change.
+# Get the baseproduct name
+baseproduct() {
+    local file="/etc/products.d/baseproduct"
+    callext rpm --root {} --quiet --query --file "$file" || log_to_file "File 
$file not owned: check bsc#1170709"
+
+    re_findall '(?<=<name>).*?(?=</name>)' "${SNAPSHOT_DIR}${file}"
+}
+
+# XXX bsc#1170709 will impact a lot here. We need a more consistent
+# proposal.
+# Read all the roles from control.xml and store in a global variable
+load_roles() {
+    local file="/etc/YaST2/control.xml"
+    callext rpm --root {} --quiet --query --file "$file" || log_to_file "File 
$file not owned: check bsc#1170709"
+
+    # TODO: detect the microos_selinux pattern
+
+    # Parsing control.xml cannot be done directly by grep, as the
+    # logic of default_patterns can be a bit complex in the case of
+    # MicroOS.  We can have some general default_patterns, and roles
+    # without default_patterns.
+    local xpath="//*[local-name()='productDefines']"
+    xpath+="/*[local-name()='software']"
+    xpath+="/*[local-name()='default_patterns']/text()"
+
+    local default_patterns=$(xmllint --xpath "$xpath" "${SNAPSHOT_DIR}${file}")
+
+    xpath="//*[local-name()='system_role']"
+    xpath+="/*[local-name()='id']/text()"
+    local role patterns
+    while read role; do
+       xpath="//*[local-name()='system_role'][*[local-name()='id']='"$role"']"
+       xpath+="/*[local-name()='software']"
+       xpath+="/*[local-name()='default_patterns']/text()"
+       patterns=$(xmllint --xpath "$xpath" "${SNAPSHOT_DIR}${file}" 2> 
/dev/null)
+       ROLES["$role"]="${patterns:-$default_patterns}"
+    done <<< $(xmllint --xpath "$xpath" "${SNAPSHOT_DIR}${file}")
+}
+
+# Read all the repo aliases and the priority, reverse sorted by
+# priority
+load_repo_alias() {
+    local regex='(?<=alias=").*?(?=")'
+    regex+='|(?<=priority=").*?(?=")'
+    regex+='|(?<=enabled=").*?(?=")'
+    regex+='|(?<=<url>).*?(?=</url>)'
+
+    # Get the repositories, remove the ones that have duplicated URL,
+    # and sort by priority
+    local line
+    while read line; do
+       line=($line)
+       [ "${line[2]}" != "0" ] && REPOS+=("${line[0]} ${line[1]}")
+    done <<< $(callext zypper --root {} --xmlout lr | re_findall "$regex" | 
xargs -n4 | LC_COLLATE=C sort -k4 | uniq -f3 | LC_COLLATE=C sort -k2 -r)
+}
+
+# Read all the build time of repo packages and store in a file
+load_buildtime_for_repo() {
+    local file="/var/cache/zypp/raw/$1/repodata/*primary.xml.gz"
+    local regex='(?<=<name>).*(?=</name>)'
+    regex+='|(?<=<arch>).*(?=</arch>)'
+    regex+='|(?<=<version ).*?(?=/>)'
+    regex+='|(?<=<time ).*?(?=/>)'
+
+    local name arch line epoch ver rel file build
+    while read name; do
+       read arch
+       read line; eval "$line"
+       read line; eval "$line"
+       [ ! -z "$name" ] && echo "${name}-${ver}-${rel}.${arch} $build" >> "$2"
+    done <<< $(zcat $file | re_findall "$regex")
+}
+
+# Read all the build time of installed packages
+load_buildtime() {
+    local line
+    while read line; do
+       line=($line)
+       BUILDTIME["${line[0]}"]="${line[1]}"
+    done <<< $(callext rpm --root {} --query --all --queryformat='%{NVRA} 
%{BUILDTIME}\n')
+}
+
+# Return the timestamp of a repository from the metadata
+timestamp_for_repo() {
+    local file="/var/cache/zypp/raw/$1/repodata/repomd.xml"
+    re_findall '(?<=<revision>).*?(?=</revision>)' "$file"
+}
+
+# Return 0 if Zypper will ignore recommended packages
+only_requires() {
+    local file="/etc/zypp/zypp.conf"
+    callext grep --quiet '^solver.onlyRequires\s*=\s*true' 
"${SNAPSHOT_DIR}${file}"
+    return #?
+}
+
+# Return 0 if Zypper allow vendor change
+allow_vendor_change() {
+    local file="/etc/zypp/zypp.conf"
+    callext grep --quiet '^solver.allowVendorChange\s*=\s*true' 
"${SNAPSHOT_DIR}${file}"
+    return #?
+}
+
+# Create a libsolv testcase for a role
+testcase_for_role() {
+    local arch="$(uname -m)"
+
+    echo "system $arch rpm"
+
+    local repo
+    for repo in "${REPOS[@]}"; do
+       repo=($repo)
+       echo "repo ${repo[0]} ${repo[1]} solv 
/var/cache/zypp/solv/${repo[0]}/solv"
+    done
+
+    local flags=""
+    only_requires && flags="ignorerecommended"
+    allow_vendor_change && flags+=" allowvendorchange"
+    [ ! -z "$flags" ] && echo "solverflags $flags"
+
+    echo "job install name product:$(baseproduct)"
+
+    local role
+    for role in ${ROLES[$1]}; do
+       echo "job install name pattern:$role"
+    done
+}
+
+# Given a role, calculate the list of all packages and store it in a
+# file
+load_packages_for_role() {
+    local testcase_file="$(mktemp)"
+
+    testcase_for_role "$1" > "$testcase_file"
+
+    # Execute in a subshell because of a testsolv bug, that do not
+    # accept full path names
+    (
+       cd $(dirname "$testcase_file")
+       testsolv $(basename "$testcase_file") | re_findall '(?<=  - ).*' | 
LC_COLLATE=C sort > "$2"
+    )
+
+    rm -f "$testcase_file"
+}
+
+# Calculate the manifest document using the repository information
+base_manifest() {
+    local pkg
+    while read pkg; do
+       grep "^$pkg " "$1" || echo "$pkg"
+    done <"$2"
+}
+
+# Calculate the manifest document from the current system
+system_manifest() {
+    local pkg
+    while read pkg; do
+       if [ "${BUILDTIME["$pkg"]+_}" ]; then
+           echo "$pkg ${BUILDTIME["$pkg"]}"
+       else
+           echo "$pkg"
+       fi
+    done <"$1"
+}
+
+# List the installed patterns by the user
+installed_patterns() {
+    callext zypper --root {} --disable-repositories se --installed-only 
--sort-by-name --type pattern \
+       | re_findall '(?<=i\+ \| ).*?(?= )'
+}
+
+# List all the installed patterns (including hiddens one)
+all_installed_patterns() {
+    callext rpm --root {} --query --provides --whatprovides 'pattern()' \
+       | re_findall '(?<=pattern\(\) = ).*' \
+       | LC_COLLATE=C sort
+}
+
+# List the installed packages by the user
+installed_packages() {
+    callext zypper --root {} --disable-repositories se --installed-only 
--sort-by-name --type package \
+       | re_findall '(?<=i\+ \| ).*?(?= )'
+}
+
+# List all installed packages
+all_installed_packages() {
+    callext rpm --root {} --query --all --queryformat='%{NAME}\n' \
+       | LC_COLLATE=C sort
+}
+
+# List the extra patterns installed by the user
+added_patterns() {
+    # TODO The selinux one should be detected
+    local exclude='microos_selinux'
+    local inst_patterns="$(installed_patterns | grep --invert-match 
--extended-regexp "$exclude")"
+    local role_patterns="$(echo "${ROLES[$1]}" | tr ' ' '\n' | LC_COLLATE=C 
sort)"
+
+    LC_COLLATE=C comm -23 <(echo "${inst_patterns}") <(echo "${role_patterns}")
+}
+
+# List the patterns from the role that are not installed
+removed_patterns() {
+    local inst_patterns="$(all_installed_patterns)"
+    local role_patterns="$(echo "${ROLES[$1]}" | tr ' ' '\n' | LC_COLLATE=C 
sort)"
+
+    LC_COLLATE=C comm -23 <(echo "${role_patterns}") <(echo "${inst_patterns}")
+}
+
+# List the extra packages installed by the user
+added_packages() {
+    # TODO What others are installed by YaST?
+    # TODO Maybe add them into packages_for_role_extended, or add them
+    # in the configuration file
+    local exclude='patterns-|kernel-|snapper|grub2|btrfsprogs'
+    local inst_packages="$(installed_packages | grep --invert-match 
--extended-regexp "$exclude")"
+    local role_packages="$(cat "$1" | rev | cut -d"-" -f3- | rev | 
LC_COLLATE=C sort)"
+
+    LC_COLLATE=C comm -23 <(echo "${inst_packages}") <(echo "${role_packages}")
+}
+
+# List the packages from the role that are not installed
+removed_packages() {
+    local exclude='pattern:|product:'
+    local inst_packages="$(all_installed_packages)"
+    local role_packages="$(cat "$1" | grep --invert-match --extended-regexp 
"$exclude" | rev | cut -d"-" -f3- | rev | LC_COLLATE=C sort)"
+
+    LC_COLLATE=C comm -23 <(echo "${role_packages}") <(echo "${inst_packages}")
+}
+
+# Calculate the Jaccard index for two sets
+jaccard() {
+    local union_card=$(echo "$1 $2" | tr ' ' '\n' | LC_COLLATE=C sort | uniq | 
wc -l)
+    local intersection_card=$(echo "$1 $2" | tr ' ' '\n' | LC_COLLATE=C sort | 
uniq -D | uniq | wc -l)
+    echo $((100 * $intersection_card / $union_card))
+}
+
+# Based on the installed patterns, find the installed role
+find_closer_role() {
+    local patterns="$(installed_patterns)"
+    local max_index=0
+    local max_role=
+    local index=
+
+    for role in "${!ROLES[@]}"; do
+       index="$(jaccard "$patterns" "${ROLES["$role"]}")"
+       if [ "$index" -gt "$max_index" ]; then
+           max_index="$index"
+           max_role="$role"
+       fi
+    done
+
+    echo "$max_role"
+}
+
+# Wait for a series of PIDs
+wait_all() {
+    local pid
+    for pid in "$@"; do
+       wait "$pid"
+    done
+}
+
+# Create the status file and all the assets to validate it in the
+# future by the user
+create_status_file() {
+    # Start from a clean state
+    rm -fr "$(dirname ${SNAPSHOT_DIR}${STATUS_FILE})"
+    mkdir -p "$(dirname ${SNAPSHOT_DIR}${STATUS_FILE})"
+
+    echo "DATE=\"$(date +"%Y-%m-%d %T")\"" >> "${SNAPSHOT_DIR}${STATUS_FILE}"
+    echo "PRODUCT=\"$(baseproduct)\"" >> "${SNAPSHOT_DIR}${STATUS_FILE}"
+    grep VERSION_ID "${SNAPSHOT_DIR}/etc/os-release" >> 
"${SNAPSHOT_DIR}${STATUS_FILE}"
+
+    load_roles
+    local role="$(find_closer_role)"
+    echo "ROLE=\"$role\"" >> "${SNAPSHOT_DIR}${STATUS_FILE}"
+
+    load_repo_alias
+
+    local repo_buildtime_file="$(mktemp)"
+    local repo
+    local pids=()
+    for repo in "${REPOS[@]}"; do
+       repo=($repo)
+       log_info "Parsing repo ${repo[0]}"
+       # Do the load in the background, and store the data in the
+       # file.  The OS will take care of synchronizing the file
+       # access (or at least seems so)
+       load_buildtime_for_repo "${repo[0]}" "$repo_buildtime_file" & pids+=($!)
+    done
+    load_buildtime
+    wait_all "${pids[@]}"
+
+    local packages_for_role_file="$(mktemp)"
+    load_packages_for_role "$role" "$packages_for_role_file"
+
+    base_manifest "$repo_buildtime_file" "$packages_for_role_file" > 
"${SNAPSHOT_DIR}${BASE_MANIFEST_FILE}"
+    system_manifest "$packages_for_role_file" > 
"${SNAPSHOT_DIR}${SYSTEM_MANIFEST_FILE}"
+
+    local base_manifest_digest="$(sha256sum 
"${SNAPSHOT_DIR}${BASE_MANIFEST_FILE}" | cut -d' ' -f1)"
+    local system_manifest_digest="$(sha256sum 
"${SNAPSHOT_DIR}${SYSTEM_MANIFEST_FILE}" | cut -d' ' -f1)"
+
+    echo "BASE_MANIFEST_DIGEST=\"$base_manifest_digest\"" >> 
"${SNAPSHOT_DIR}${STATUS_FILE}"
+    echo "SYSTEM_MANIFEST_DIGEST=\"$system_manifest_digest\"" >> 
"${SNAPSHOT_DIR}${STATUS_FILE}"
+
+    pids=()
+    gzip "${SNAPSHOT_DIR}${BASE_MANIFEST_FILE}" & pids+=($!)
+    gzip "${SNAPSHOT_DIR}${SYSTEM_MANIFEST_FILE}" & pids+=($!)
+
+    local items=$(added_patterns "$role" | xargs)
+    echo "ADDED_PATTERNS=\"$items\"" >> "${SNAPSHOT_DIR}${STATUS_FILE}"
+    items=$(removed_patterns "$role" | xargs)
+    echo "REMOVED_PATTERNS=\"$items\"" >> "${SNAPSHOT_DIR}${STATUS_FILE}"
+
+    items=$(added_packages "$packages_for_role_file" | xargs)
+    echo "ADDED_PACKAGES=\"$items\"" >> "${SNAPSHOT_DIR}${STATUS_FILE}"
+    items=$(removed_packages "$packages_for_role_file" | xargs)
+    echo "REMOVED_PACKAGES=\"$items\"" >> "${SNAPSHOT_DIR}${STATUS_FILE}"
+
+    wait_all "${pids[@]}"
+
+    rm -f "$repo_buildtime_file" "$packages_for_role_file"
+}
+
+# Get the snapshot number
+snapshot_num() {
+    local regex='(?<=<num>).*?(?=</num>)'
+    re_findall "$regex" "${1}info.xml"
+}
+
+# Get the snapshot date, in the same format that the status file
+snapshot_date() {
+    local regex='(?<=<date>).*?(?=</date>)'
+    re_findall "$regex" "${1}info.xml"
+}
+
+# Get the snapshot description line
+snapshot_description() {
+    local regex='(?<=<description>).*?(?=</description>)'
+    re_findall "$regex" "${1}info.xml"
+}
+
+# Show the status of a snapshot
+show_snapshot_status() {
+    printf "%0.s-" $(seq 1 $(tput cols)); echo
+
+    local ss_id=$(snapshot_num "$1")
+    if [ "$CURRENT_SNAPSHOT_ID" -eq "$ss_id" ]; then
+       ss_id+="*"
+    elif [ "$DEFAULT_SNAPSHOT_ID" -eq "$ss_id" ]; then
+       ss_id+="+"
+    fi
+
+    local ss_date="$(snapshot_date "$1")"
+    local ss_description="$(snapshot_description "$1")"
+    echo "Snapshot:          ${ss_id} [$(snapshot_date "$1")]"
+    echo "Description:       $(snapshot_description "$1")"
+    if [ -f "${1}snapshot${STATUS_FILE}" ]; then
+       . "${1}snapshot${STATUS_FILE}"
+       echo "Report date:       ${DATE}"
+       echo "Version:           ${PRODUCT} ${VERSION_ID}"
+       echo "Base OS hash:      ${BASE_MANIFEST_DIGEST}"
+       echo "Effective OS hash: ${SYSTEM_MANIFEST_DIGEST}"
+       [ ! -z "${ADDED_PATTERNS}" ] && echo "Added patterns:    
${ADDED_PATTERNS}"
+       [ ! -z "${REMOVED_PATTERNS}" ] && echo "Removed patterns:  
${REMOVED_PATTERNS}"
+       [ ! -z "${ADDED_PACKAGES}" ] && echo "Added packages:    
${ADDED_PACKAGES}"
+       [ ! -z "${REMOVED_PACKAGES}" ] && echo "Removed packages:  
${REMOVED_PACKAGES}"
+    else
+       echo "(Missing status file)"
+    fi
+}
+
+
 ORIG_ARGS=("$@")
 
 while [ 1 ]; do
@@ -511,6 +921,23 @@
            SETUP_SELINUX=1
            shift
            ;;
+       status)
+           DO_STATUS=1
+           DO_SELF_UPDATE=0
+           shift
+           while [ 1 ]; do
+               [ $# -eq 0 ] && break
+               case "$1" in
+                   last)
+                       DO_STATUS_LAST=1
+                       shift
+                       ;;
+                   *)
+                       usage 1;
+                       ;;
+               esac
+           done
+           ;;
        -i|--interactive)
            ZYPPER_NONINTERACTIVE=""
            shift
@@ -752,6 +1179,14 @@
     exit 0
 fi
 
+if [ "${DO_STATUS}" -eq 1 ]; then
+    for snapshot in $(ls -d /.snapshots/*/ | sort --reverse --numeric-sort); do
+       show_snapshot_status "$snapshot"
+       [ "${DO_STATUS_LAST}" -eq 1 ] && break
+    done
+    exit 0
+fi
+
 #
 # Cleanup part: make sure old root file systems will be removed after they are 
no longer active.
 #
@@ -912,6 +1347,11 @@
            fi
        fi
 
+       # Create the status for the new snapshot, before closing it
+       if [ "${EXPERIMENTAL_STATUS}" -eq 1 ]; then
+           create_status_file
+       fi
+
        if [ $RETVAL -eq 0 -o $RETVAL -eq 102 -o $RETVAL -eq 103 -o \( $DO_DUP 
-eq 0 -a $RETVAL -eq 106 \) ]; then
            REBUILD_KDUMP_INITRD=1
            # check if products are updated and we need to re-register
@@ -965,9 +1405,9 @@
 
     if [ ${REWRITE_INITRD} -eq 1 ]; then
        log_info "Creating new initrd"
-       tukit ${TUKIT_OPTS} call "${SNAPSHOT_ID}" /sbin/mkinitrd
+       tukit ${TUKIT_OPTS} call "${SNAPSHOT_ID}" dracut --force 
--regenerate-all
        if [ $? -ne 0 ]; then
-           log_error "ERROR: mkinitrd failed!"
+           log_error "ERROR: initrd creation failed!"
            EXITCODE=1
        else
            REBUILD_KDUMP_INITRD=1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transactional-update-3.4.0/tukit/Makefile.am 
new/transactional-update-3.5.1/tukit/Makefile.am
--- old/transactional-update-3.4.0/tukit/Makefile.am    2021-06-14 
00:37:42.000000000 +0200
+++ new/transactional-update-3.5.1/tukit/Makefile.am    2021-08-03 
14:34:57.000000000 +0200
@@ -4,4 +4,4 @@
         tukit.cpp
 noinst_HEADERS=tukit.hpp
 tukit_CPPFLAGS = -I $(top_srcdir)/lib $(ECONF_CFLAGS)
-tukit_LDADD = $(top_builddir)/lib/libtukit.la $(ECONF_LIBS) -lmount -lpthread
+tukit_LDADD = $(top_builddir)/lib/libtukit.la $(ECONF_LIBS) -lmount
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transactional-update-3.4.0/tukit/tukit.cpp 
new/transactional-update-3.5.1/tukit/tukit.cpp
--- old/transactional-update-3.4.0/tukit/tukit.cpp      2021-06-14 
00:37:42.000000000 +0200
+++ new/transactional-update-3.5.1/tukit/tukit.cpp      2021-08-03 
14:34:57.000000000 +0200
@@ -112,7 +112,7 @@
     string arg = argv[0];
     if (arg == "execute") {
         if (discardSnapshot) {
-            transaction.setDiscard(true);
+            transaction.setDiscardIfUnchanged(true);
         }
         transaction.init(baseSnapshot);
         int status = transaction.execute(&argv[1]); // All remaining arguments
@@ -125,7 +125,7 @@
     }
     else if (arg == "open") {
         if (discardSnapshot) {
-            transaction.setDiscard(true);
+            transaction.setDiscardIfUnchanged(true);
         }
         transaction.init(baseSnapshot);
         cout << "ID: " << transaction.getSnapshot() << endl;

Reply via email to