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 <[email protected]>
+
+- 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 <[email protected]>
+
+- Add tukit.conf to spec file
+
+-------------------------------------------------------------------
+Mon Aug 2 15:58:09 UTC 2021 - Ignaz Forster <[email protected]>
+
+- 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 <[email protected]>
-#
+# 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;