Hello community,
here is the log from the commit of package transactional-update for
openSUSE:Factory checked in at 2018-04-25 10:01:55
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/transactional-update (Old)
and /work/SRC/openSUSE:Factory/.transactional-update.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "transactional-update"
Wed Apr 25 10:01:55 2018 rev:26 rq:600622 version:2.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/transactional-update/transactional-update.changes
2018-04-22 14:40:26.123240052 +0200
+++
/work/SRC/openSUSE:Factory/.transactional-update.new/transactional-update.changes
2018-04-25 10:02:00.753387693 +0200
@@ -1,0 +2,7 @@
+Tue Apr 24 14:23:41 CEST 2018 - [email protected]
+
+- Update to version 2.0
+ - Create missing directories from rpm database during boot
+ - Merge /etc overlay with root subvolume during update
+
+-------------------------------------------------------------------
Old:
----
transactional-update-1.29.tar.bz2
New:
----
transactional-update-2.0.tar.bz2
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ transactional-update.spec ++++++
--- /var/tmp/diff_new_pack.ds9jfE/_old 2018-04-25 10:02:02.329329871 +0200
+++ /var/tmp/diff_new_pack.ds9jfE/_new 2018-04-25 10:02:02.333329724 +0200
@@ -17,20 +17,25 @@
Name: transactional-update
-Version: 1.29
+Version: 2.0
Release: 0
Summary: Transactional Updates with btrfs and snapshots
License: GPL-2.0-or-later
Group: System/Base
-Url: https://github.com/thkukuk/transactional-update
+URL: https://github.com/thkukuk/transactional-update
Source: %{name}-%{version}.tar.bz2
BuildRequires: fdupes
BuildRequires: gcc-c++
+BuildRequires: pkgconfig
+BuildRequires: rpm-devel
BuildRequires: pkgconfig(libzypp)
Requires: logrotate
Requires: lsof
# psmisc is needed because of fuser
+Requires: perl-HTML-Parser
Requires: psmisc
+Requires: rsync
+Conflicts: snapper-zypp-plugin
Recommends: rebootmgr
%description
@@ -49,16 +54,16 @@
%fdupes %{buildroot}%{_mandir}
%pre
-%service_add_pre %{name}.service %{name}.timer
+%service_add_pre %{name}.service %{name}.timer create-dirs-from-rpmdb.service
%post
-%service_add_post %{name}.service %{name}.timer
+%service_add_post %{name}.service %{name}.timer create-dirs-from-rpmdb.service
%preun
-%service_del_preun %{name}.service %{name}.timer
+%service_del_preun %{name}.service %{name}.timer create-dirs-from-rpmdb.service
%postun
-%service_del_postun %{name}.service %{name}.timer
+%service_del_postun %{name}.service %{name}.timer
create-dirs-from-rpmdb.service
%files
%license COPYING
@@ -66,14 +71,19 @@
%config(noreplace) %{_sysconfdir}/logrotate.d/transactional-update
%{_unitdir}/transactional-update.service
%{_unitdir}/transactional-update.timer
+%{_unitdir}/create-dirs-from-rpmdb.service
+%{_sbindir}/create_dirs_from_rpmdb
%{_sbindir}/transactional-update
%{_sbindir}/tu-rebuild-kdump-initrd
%{_sbindir}/transactional-update-helper
%dir %{_prefix}%{_sysconfdir}
%{_prefix}%{_sysconfdir}/transactional-update.conf
-%{_mandir}/man5/transactional-update.conf.5%{ext_man}
-%{_mandir}/man8/transactional-update.8%{ext_man}
-%{_mandir}/man8/transactional-update.timer.8%{ext_man}
-%{_mandir}/man8/transactional-update.service.8%{ext_man}
+%dir %{_prefix}/lib/dracut
+%dir %{_prefix}/lib/dracut/modules.d
+%{_prefix}/lib/dracut/modules.d/50transactional-update/
+%{_mandir}/man5/transactional-update.conf.5%{?ext_man}
+%{_mandir}/man8/transactional-update.8%{?ext_man}
+%{_mandir}/man8/transactional-update.timer.8%{?ext_man}
+%{_mandir}/man8/transactional-update.service.8%{?ext_man}
%changelog
++++++ transactional-update-1.29.tar.bz2 -> transactional-update-2.0.tar.bz2
++++++
++++ 3510 lines of diff (skipped)
++++ retrying with extended exclude list
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh
old/transactional-update-1.29/Makefile.am
new/transactional-update-2.0/Makefile.am
--- old/transactional-update-1.29/Makefile.am 2018-01-22 13:01:43.000000000
+0100
+++ new/transactional-update-2.0/Makefile.am 2018-04-20 18:34:52.000000000
+0200
@@ -5,7 +5,7 @@
#
AUTOMAKE_OPTIONS = 1.6 foreign check-news dist-bzip2
#
-SUBDIRS = sbin man systemd logrotate doc etc
+SUBDIRS = sbin man systemd logrotate dracut doc etc
CLEANFILES = *~
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh
old/transactional-update-1.29/NEWS new/transactional-update-2.0/NEWS
--- old/transactional-update-1.29/NEWS 2018-04-20 15:55:53.000000000 +0200
+++ new/transactional-update-2.0/NEWS 2018-04-23 10:03:28.000000000 +0200
@@ -2,6 +2,11 @@
Copyright (C) 2016, 2017, 2018 Thorsten Kukuk
+Version 2.0
+* Create missing directories from rpm database during boot
+* Merge /etc overlay with root subvolume during update
+* Implement register option
+
Version 1.29
* Implement self-update
* Disable optical media on dup
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh
old/transactional-update-1.29/configure.ac
new/transactional-update-2.0/configure.ac
--- old/transactional-update-1.29/configure.ac 2018-04-20 15:55:00.000000000
+0200
+++ new/transactional-update-2.0/configure.ac 2018-04-23 09:57:43.000000000
+0200
@@ -1,5 +1,5 @@
dnl Process this file with autoconf to produce a configure script.
-AC_INIT(transactional-update, 1.29)
+AC_INIT(transactional-update, 2.0)
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([sbin/transactional-update.in])
AC_PREFIX_DEFAULT(/usr)
@@ -16,25 +16,30 @@
ISSUEDIR=${prefix}/lib/issue.d
UDEVRULESDIR=${prefix}/lib/udev/rules.d
SYSTEMDDIR=${prefix}/lib/systemd/system
+ DRACUTDIR=${prefix}/lib/dracut/modules.d
else
TMPFILESDIR=${exec_prefix}/lib/tmpfiles.d
ISSUEDIR=${exec_prefix}/lib/issue.d
UDEVRULESDIR=${exec_prefix}/lib/udev/rules.d
SYSTEMDDIR=${exec_prefix}/lib/systemd/system
+ DRACUTDIR=${exec_prefix}/lib/dracut/modules.d
fi
else
TMPFILESDIR=${libexecdir}/tmpfiles.d
ISSUEDIR=${libexecdir}/issue.d
UDEVRULESDIR=${libexecdir}/udev/rules.d
SYSTEMDDIR=${libexecdir}/systemd/system
+ DRACUTDIR=${libexecdir}/dracut/modules.d
fi
AC_SUBST(TMPFILESDIR)
AC_SUBST(ISSUEDIR)
AC_SUBST(UDEVRULESDIR)
AC_SUBST(SYSTEMDDIR)
+AC_SUBST(DRACUTDIR)
LOGROTATEDDIR=${sysconfdir}/logrotate.d
AC_SUBST(LOGROTATEDDIR)
+AC_PROG_CC
AC_PROG_CXX
AC_PROG_INSTALL
AC_PROG_LN_S
@@ -47,6 +52,11 @@
PKG_CHECK_MODULES(LIBZYPP, libzypp)
dnl
+dnl Check for librpm
+dnl
+PKG_CHECK_MODULES([LIBRPM], [rpm])
+
+dnl
dnl Check for xsltproc
dnl
enable_man=yes
@@ -73,5 +83,5 @@
fi
AC_OUTPUT([Makefile sbin/Makefile man/Makefile systemd/Makefile \
- logrotate/Makefile doc/Makefile etc/Makefile \
+ logrotate/Makefile dracut/Makefile doc/Makefile etc/Makefile \
sbin/transactional-update])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh
old/transactional-update-1.29/dracut/Makefile.am
new/transactional-update-2.0/dracut/Makefile.am
--- old/transactional-update-1.29/dracut/Makefile.am 1970-01-01
01:00:00.000000000 +0100
+++ new/transactional-update-2.0/dracut/Makefile.am 2018-04-20
18:34:52.000000000 +0200
@@ -0,0 +1,9 @@
+#
+# Copyright (c) 2018 Ignaz Forster <[email protected]>
+#
+
+modulesdir = @DRACUTDIR@/50transactional-update
+
+modules_SCRIPTS = transactional-update-etc-cleaner.sh
transactional-update-etc-cleaner.service module-setup.sh
+
+EXTRA_DIST = $(SCRIPTS)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh
old/transactional-update-1.29/dracut/module-setup.sh
new/transactional-update-2.0/dracut/module-setup.sh
--- old/transactional-update-1.29/dracut/module-setup.sh 1970-01-01
01:00:00.000000000 +0100
+++ new/transactional-update-2.0/dracut/module-setup.sh 2018-04-20
18:34:52.000000000 +0200
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+# called by dracut
+check() {
+ test -f /etc/fstab.sys || [[ -n $add_fstab || -n $fstab_lines ]]
+}
+
+# called by dracut
+depends() {
+ echo fstab-sys
+}
+
+# called by dracut
+install() {
+ inst_script "$moddir/transactional-update-etc-cleaner.sh"
/bin/transactional-update-etc-cleaner
+ inst_simple "$moddir/transactional-update-etc-cleaner.service"
$systemdsystemunitdir/transactional-update-etc-cleaner.service
+ mkdir -p "${initdir}/$systemdsystemunitdir/initrd.target.wants"
+ ln_r "$systemdsystemunitdir/transactional-update-etc-cleaner.service"
"$systemdsystemunitdir/initrd.target.wants/transactional-update-etc-cleaner.service"
+ inst_multiple stat rmdir
+ inst_multiple -o getfattr
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh
old/transactional-update-1.29/dracut/transactional-update-etc-cleaner.service
new/transactional-update-2.0/dracut/transactional-update-etc-cleaner.service
---
old/transactional-update-1.29/dracut/transactional-update-etc-cleaner.service
1970-01-01 01:00:00.000000000 +0100
+++
new/transactional-update-2.0/dracut/transactional-update-etc-cleaner.service
2018-04-20 18:34:52.000000000 +0200
@@ -0,0 +1,16 @@
+[Unit]
+Description=transactional-update /etc overlay cleaner
+Requires=dracut-pre-pivot.service
+After=dracut-pre-pivot.service
+Before=initrd-cleanup.service
+DefaultDependencies=no
+ConditionPathExists=/sysroot/var/lib/overlay/etc/transactional-update.newsnapshot
+
+[Service]
+Type=oneshot
+Environment=DRACUT_SYSTEMD=1
+Environment=NEWROOT=/sysroot
+StandardInput=null
+StandardOutput=syslog
+StandardError=syslog+console
+ExecStart=/bin/transactional-update-etc-cleaner
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh
old/transactional-update-1.29/dracut/transactional-update-etc-cleaner.sh
new/transactional-update-2.0/dracut/transactional-update-etc-cleaner.sh
--- old/transactional-update-1.29/dracut/transactional-update-etc-cleaner.sh
1970-01-01 01:00:00.000000000 +0100
+++ new/transactional-update-2.0/dracut/transactional-update-etc-cleaner.sh
2018-04-20 18:34:52.000000000 +0200
@@ -0,0 +1,133 @@
+#!/bin/bash -e
+#
+# Purge contents of etc overlay on first boot after creating new snapshot
+#
+# Author: Ignaz Forster <[email protected]>
+# Copyright (C) 2018 SUSE Linux GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ETC_OVERLAY="${NEWROOT}/var/lib/overlay/etc"
+TU_FLAGFILE="${ETC_OVERLAY}/transactional-update.newsnapshot"
+
+# Import common dracut variables
+. /dracut-state.sh 2>/dev/null
+
+same_file() {
+ # Primary indicators for changes: File size, attributes and modification time
+ local overlay_stat="`stat --format="%a %u:%g %s %t/%T %F %Y" -- "$1"`"
+ local lowerdir_stat="`stat --format="%a %u:%g %s %t/%T %F %Y" -- "$2"`"
+ if [ "${overlay_stat}" != "${lowerdir_stat}" ]; then
+ return 1
+ fi
+
+ # Compare extended attributes if available
+ if [ -x /usr/bin/getfattr ]; then
+ overlay_stat="`getfattr --dump --no-dereference --absolute-names -- "$1" |
sed 1d`"
+ lowerdir_stat="`getfattr --dump --no-dereference --absolute-names -- "$2"
| sed 1d`"
+ if [ "${overlay_stat}" != "${lowerdir_stat}" ]; then
+ return 1
+ fi
+ fi
+
+ # Files seem to be identical
+ return 0
+}
+
+# Remove files from overlay with safety checks
+# etc directory mustn't be deleted completely, as it's still mounted and the
kernel won't be able to recover from that (different inode?)
+clean_overlay() {
+ local dir="${1:-.}"
+ local file
+ local snapdir="${NEWROOT}/${PREV_SNAPSHOT_DIR}/etc/${dir}"
+
+ pushd "${ETC_OVERLAY}/${dir}" >/dev/null
+ for file in .[^.]* ..?* *; do
+ # Filter unexpanded globs of "for" loop
+ if [ ! -e "${file}" ]; then
+ continue
+ fi
+
+ # Recursively process directories
+ if [ -d "${file}" ]; then
+ clean_overlay "${dir}/${file}"
+ if same_file "${file}" "${snapdir}/${file}"; then
+ rmdir --ignore-fail-on-non-empty -- "${file}"
+ fi
+ # Overlayfs creates a character device with device number 0/0 for removed
files / directories
+ elif [ -c "${file}" -a "`stat --format="%t/%T" -- "${file}"`" = "0/0" -a !
-e "${snapdir}/${file}" ]; then
+ echo "Removing character device ${dir}/${file} from overlay..."
+ rm -- "${file}"
+ # Verify that files in the overlay haven't changed since taking the
snapshot
+ elif same_file "${file}" "${snapdir}/${file}"; then
+ echo "Removing copy of ${dir}/${file} from overlay..."
+ rm -- "${file}"
+ # File seems to have been modified, warn user
+ else
+ echo "Warning: Not removing ${dir}/${file} - modified after snapshot
creation."
+ fi
+ done
+ popd >/dev/null
+}
+
+# Delete all contents of the overlay
+remove_overlay() {
+ echo "Previous snapshot ${PREV_SNAPSHOT_ID} not available; deleting all
overlay contents."
+ cd "${ETC_OVERLAY}"
+ rm -rf -- .[^.]* ..?* *
+}
+
+# Mount directories necessary for file comparison
+# Note: Will not break execution if snapshot is not available
+prepare_environment() {
+ if ! findmnt "${NEWROOT}/.snapshots" >/dev/null; then
+ if ! mount -t btrfs -o ro,subvol=@/.snapshots ${root#block:*}
${NEWROOT}/.snapshots; then
+ echo "Could not mount .snapshots!"
+ return 1
+ fi
+ UMOUNT_DOT_SNAPSHOTS=1
+ fi
+ CURRENT_SNAPSHOT_ID="`btrfs subvolume get-default /${NEWROOT} | sed
's#.*/.snapshots/\(.*\)/snapshot#\1#g'`"
+ PREV_SNAPSHOT_ID="`sed -n 's#.*<pre_num>\([^>]\+\)</pre_num>#\1#p'
${NEWROOT}/.snapshots/${CURRENT_SNAPSHOT_ID}/info.xml`"
+ PREV_SNAPSHOT_DIR="`btrfs subvolume list /${NEWROOT} | sed -n
's#.*\(/.snapshots/'${PREV_SNAPSHOT_ID}'/snapshot\)#\1#p'`"
+ if [ -n "${PREV_SNAPSHOT_DIR}" ] && ! findmnt
"${NEWROOT}/${PREV_SNAPSHOT_DIR}" >/dev/null; then
+ if ! mount -t btrfs -o ro,subvol=@${PREV_SNAPSHOT_DIR} ${root#block:*}
"${NEWROOT}/${PREV_SNAPSHOT_DIR}"; then
+ echo "Warning: Could not mount old snapshot directory
${PREV_SNAPSHOT_DIR}!"
+ return 1
+ fi
+ UMOUNT_PREV_SNAPSHOT=1
+ fi
+ return 0
+}
+
+release_environment() {
+ if [ -n "${UMOUNT_PREV_SNAPSHOT}" ]; then
+ umount "${NEWROOT}/${PREV_SNAPSHOT_DIR}"
+ fi
+ if [ -n "${UMOUNT_DOT_SNAPSHOTS}" ]; then
+ umount "${NEWROOT}/.snapshots"
+ fi
+}
+
+if [ -e "${ETC_OVERLAY}" -a -e "${TU_FLAGFILE}" ]; then
+ # Previous snapshot may not be available; just delete all overlay contents
in this case
+ rm "${TU_FLAGFILE}"
+ if prepare_environment; then
+ clean_overlay
+ else
+ remove_overlay
+ fi
+ mount -o remount "${NEWROOT}/etc"
+ release_environment
+fi
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh
old/transactional-update-1.29/sbin/Makefile.am
new/transactional-update-2.0/sbin/Makefile.am
--- old/transactional-update-1.29/sbin/Makefile.am 2018-04-20
15:48:00.000000000 +0200
+++ new/transactional-update-2.0/sbin/Makefile.am 2018-04-20
18:34:52.000000000 +0200
@@ -3,9 +3,11 @@
#
sbin_SCRIPTS = transactional-update tu-rebuild-kdump-initrd
-sbin_PROGRAMS = transactional-update-helper
+sbin_PROGRAMS = transactional-update-helper create_dirs_from_rpmdb
transactional_update_helper_SOURCES = transactional-update-helper.cpp
transactional_update_helper_LDADD = @LIBZYPP_LIBS@
+create_dirs_from_rpmdb_SOURCES = create_dirs_from_rpmdb.c
+create_dirs_from_rpmdb_LDADD = @LIBRPM_LIBS@
CLEANFILES = transactional-update
EXTRA_DIST = transactional-update.in tu-rebuild-kdump-initrd
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh
old/transactional-update-1.29/sbin/create_dirs_from_rpmdb.c
new/transactional-update-2.0/sbin/create_dirs_from_rpmdb.c
--- old/transactional-update-1.29/sbin/create_dirs_from_rpmdb.c 1970-01-01
01:00:00.000000000 +0100
+++ new/transactional-update-2.0/sbin/create_dirs_from_rpmdb.c 2018-04-24
14:06:06.000000000 +0200
@@ -0,0 +1,300 @@
+/* create_dirs_from_rpmdb - Create missing directories in /srv,/var during boot
+
+ Copyright (C) 2018 SUSE Linux GmbH
+ Author: Thorsten Kukuk <[email protected]>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <time.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <rpm/rpmcli.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmdb.h>
+
+static int debug_flag = 0;
+static int verbose_flag = 0;
+
+/* Print the version information. */
+static void
+print_version (void)
+{
+ fprintf (stdout, "create_dirs_from_rpmdb (%s) %s\n", PACKAGE, VERSION);
+}
+
+static void
+print_usage (FILE *stream)
+{
+ fprintf (stream, "Usage: create_dirs_from_rpmdb [-V|--version] [--debug]
[-v|--verbose]\n");
+}
+
+static void
+print_error (void)
+{
+ fprintf (stderr,
+ "Try `create_dirs_from_rpmdb --help' or `create_dirs_from_rpmdb
--usage' for more information.\n");
+}
+
+
+static char *
+fmode2str (int mode)
+{
+ char *str = strdup("----------");
+
+ if (str == NULL)
+ {
+ fprintf (stderr, "Out of memory\n");
+ exit (1);
+ }
+
+ if (S_ISREG(mode))
+ str[0] = '-';
+ else if (S_ISDIR(mode))
+ str[0] = 'd';
+ else if (S_ISCHR(mode))
+ str[0] = 'c';
+ else if (S_ISBLK(mode))
+ str[0] = 'b';
+ else if (S_ISFIFO(mode))
+ str[0] = 'p';
+ else if (S_ISLNK(mode))
+ str[0] = 'l';
+ else if (S_ISSOCK(mode))
+ str[0] = 's';
+ else
+ str[0] = '?';
+
+ if (mode & S_IRUSR) str[1] = 'r';
+ if (mode & S_IWUSR) str[2] = 'w';
+ if (mode & S_IXUSR) str[3] = 'x';
+
+ if (mode & S_IRGRP) str[4] = 'r';
+ if (mode & S_IWGRP) str[5] = 'w';
+ if (mode & S_IXGRP) str[6] = 'x';
+
+ if (mode & S_IROTH) str[7] = 'r';
+ if (mode & S_IWOTH) str[8] = 'w';
+ if (mode & S_IXOTH) str[9] = 'x';
+
+ if (mode & S_ISUID)
+ str[3] = ((mode & S_IXUSR) ? 's' : 'S');
+
+ if (mode & S_ISGID)
+ str[6] = ((mode & S_IXGRP) ? 's' : 'S');
+
+ if (mode & S_ISVTX)
+ str[9] = ((mode & S_IXOTH) ? 't' : 'T');
+
+ return str;
+}
+
+int
+check_package (rpmts ts, Header h)
+{
+ int ec = 0;
+ rpmfi fi = NULL;
+ rpmfiFlags fiflags = (RPMFI_NOHEADER | RPMFI_FLAGS_QUERY);
+
+ fi = rpmfiNew(ts, h, RPMTAG_BASENAMES, fiflags);
+ if (rpmfiFC(fi) <= 0)
+ goto exit;
+
+ fi = rpmfiInit (fi, 0);
+ while (rpmfiNext (fi) >= 0)
+ {
+ rpm_mode_t fmode = rpmfiFMode(fi);
+
+ if (S_ISDIR(fmode))
+ {
+ const char *prefixes[] = {"/var/", "/srv/"};
+ const char *fn = rpmfiFN(fi);
+ rpmfileAttrs fflags = rpmfiFFlags(fi);
+ int i;
+
+ for (i = 0; i < sizeof (prefixes)/sizeof(char *); i++)
+ {
+ if (!(fflags & RPMFILE_GHOST) &&
+ strncmp (prefixes[i], fn, strlen (prefixes[i]))== 0 &&
+ access (fn, F_OK) == -1)
+ {
+ int rc = 0;
+ struct tm * tm;
+ char timefield[100];
+ rpm_time_t fmtime = rpmfiFMtime(fi);
+ time_t mtime = fmtime; /* important if sizeof(int32_t) !
sizeof(time_t) */
+ const char *fuser = rpmfiFUser(fi);
+ const char *fgroup = rpmfiFGroup(fi);
+ uid_t user_id;
+ gid_t group_id;
+ struct passwd *pwd;
+ struct group *grp;
+ struct timeval stamps[2] = {
+ { .tv_sec = mtime, .tv_usec = 0 },
+ { .tv_sec = mtime, .tv_usec = 0 }};
+
+
+ if (debug_flag)
+ {
+ char *perms = fmode2str (fmode);
+
+ /* Convert file mtime to display format */
+ tm = localtime(&mtime);
+ timefield[0] = '\0';
+ if (tm != NULL)
+ {
+ const char *fmt = "%F,%H:%M";
+ (void)strftime(timefield, sizeof(timefield) - 1, fmt,
tm);
+ }
+
+ printf ("Create %s (%s,%s,%s,%s)\n", fn, perms, fuser,
+ fgroup, timefield);
+ free (perms);
+ }
+ else if (verbose_flag)
+ printf ("Create %s\n", fn);
+
+ rc = mkdir (fn, fmode);
+ if (rc < 0)
+ {
+ fprintf (stderr, "Failed to create directory '%s':
%m\n", fn);
+ ec = 1;
+ goto exit;
+ }
+
+ pwd = getpwnam (fuser);
+ grp = getgrnam (fgroup);
+
+ if (pwd == NULL || grp == NULL)
+ {
+ fprintf (stderr, "Failed to resolve %s/%s\n", fuser,
fgroup);
+ rmdir (fn);
+ ec = 1;
+ goto exit;
+ }
+
+ user_id = pwd->pw_uid;
+ group_id = grp->gr_gid;
+
+ rc = chown (fn, user_id, group_id);
+ if (rc < 0)
+ {
+ fprintf (stderr, "Failed to set owner/group for '%s':
%m\n", fn);
+ /* wrong permissions are bad, remove dir and continue */
+ rmdir (fn);
+ ec = 1;
+ goto exit;
+ }
+ /* ignore errors here, time stamps are not critical */
+ utimes (fn, stamps);
+ }
+ }
+ }
+ }
+
+ exit:
+ rpmfiFree(fi);
+
+ return ec;
+}
+
+int
+main (int argc, char *argv[])
+{
+ Header h;
+ rpmts ts = NULL;
+ int ec = 0;
+
+
+ while (1)
+ {
+ int c;
+ int option_index = 0;
+ static struct option long_options[] = {
+ {"version", no_argument, NULL, 'V' },
+ {"usage", no_argument, NULL, 'u' },
+ {"debug", no_argument, NULL, 254 },
+ {"verbose", no_argument, NULL, 'v' },
+ {"help", no_argument, NULL, 255 },
+ {NULL, 0, NULL, 0 }
+ };
+
+ /* Don't let getopt print error messages, we do it ourself. */
+ opterr = 0;
+
+ c = getopt_long (argc, argv, "uVv",
+ long_options, &option_index);
+
+ if (c == (-1))
+ break;
+
+ switch (c)
+ {
+ case 'V':
+ print_version ();
+ return 0;
+ case 255:
+ case 'u':
+ print_usage (stdout);
+ return 0;
+ case 'v':
+ verbose_flag = 1;
+ break;
+ case 254:
+ debug_flag = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0)
+ {
+ fprintf (stderr, "create_dirs_from_rpmdb: Too many arguments.\n");
+ print_error ();
+ return 1;
+ }
+
+ rpmReadConfigFiles (NULL, NULL);
+
+ ts = rpmtsCreate ();
+ rpmtsSetRootDir (ts, rpmcliRootDir);
+
+ rpmdbMatchIterator mi = rpmtsInitIterator (ts, RPMDBI_PACKAGES, NULL, 0);
+ if (mi == NULL)
+ return 1;
+
+ while ((h = rpmdbNextIterator (mi)) != NULL)
+ {
+ int rc;
+ /* rpmsqPoll (); */
+ if ((rc = check_package (ts, h)) != 0)
+ ec = rc;
+ }
+
+ rpmdbFreeIterator (mi);
+ rpmtsFree (ts);
+
+ return ec;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh
old/transactional-update-1.29/sbin/transactional-update-helper.cpp
new/transactional-update-2.0/sbin/transactional-update-helper.cpp
--- old/transactional-update-1.29/sbin/transactional-update-helper.cpp
2018-04-20 11:09:40.000000000 +0200
+++ new/transactional-update-2.0/sbin/transactional-update-helper.cpp
2018-04-20 18:34:52.000000000 +0200
@@ -1,5 +1,6 @@
/* transactional-update-helper - native helper scripts for transactional-update
+ Author: Ignaz Forster <[email protected]>
Copyright (C) 2018 SUSE Linux GmbH
This program is free software: you can redistribute it and/or modify
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh
old/transactional-update-1.29/sbin/transactional-update.in
new/transactional-update-2.0/sbin/transactional-update.in
--- old/transactional-update-1.29/sbin/transactional-update.in 2018-04-20
15:47:38.000000000 +0200
+++ new/transactional-update-2.0/sbin/transactional-update.in 2018-04-20
20:40:09.000000000 +0200
@@ -34,6 +34,8 @@
DO_DUP=0
DO_ROLLBACK=0
DO_SELF_UPDATE=1
+DO_REGISTRATION=0
+REGISTRATION_ARGS=""
ROLLBACK_SNAPSHOT=0
REBOOT_AFTERWARDS=0
REBOOT_METHOD="auto"
@@ -43,17 +45,14 @@
SYSTEMCONFFILE="@prefix@@sysconfdir@/transactional-update.conf"
LOGFILE="/var/log/transactional-update.log"
STATE_FILE="/var/lib/misc/transactional-update.state"
+NEW_SNAPSHOT_FLAG="/var/lib/overlay/etc/transactional-update.newsnapshot"
SELF_PATH="`dirname $0`"
PACKAGE_UPDATES=0
ZYPPER_AUTO_IMPORT_KEYS=0
HAS_SEPARATE_VAR=0
SNAPSHOT_ID=""
-SECOND_SNAPSHOT_ID=""
+BACKUP_SNAPSHOT_ID=""
SNAPPER_NO_DBUS=""
-KDUMP_SYSCONFIG="/etc/sysconfig/kdump"
-# Config files which the user could have modified and which should
-# be copied to the snapshot.
-CONFIG_FILES_TO_COPY="/etc/default/grub"
# Load config
if [ -r ${SYSTEMCONFFILE} ]; then
@@ -85,6 +84,7 @@
echo " transactional-update [--no-selfupdate]
[cleanup][up|dup|patch|initrd][kdump][reboot]"
echo " transactional-update [--no-selfupdate] [cleanup] [reboot] pkg
install|remove|update PKG1..PKGN"
echo " transactional-update [--no-selfupdate] migration"
+ echo " transactional-update [--no-selfupdate] register -p <product>
[-r <registration code>]"
echo " transactional-update rollback [number]"
exit $1
}
@@ -152,51 +152,19 @@
test -f /usr/lib/systemd/system/kdump.service || return
systemctl is-enabled --quiet kdump.service
if [ $? = 0 -a -x ${MOUNT_DIR}/usr/sbin/tu-rebuild-kdump-initrd ]; then
- if [ ${KDUMP_SYSCONFIG} -nt ${MOUNT_DIR}/${KDUMP_SYSCONFIG} ]; then
- cp -a ${KDUMP_SYSCONFIG} ${MOUNT_DIR}/${KDUMP_SYSCONFIG}
- fi
chroot ${MOUNT_DIR} /usr/sbin/tu-rebuild-kdump-initrd |& tee -a
${LOGFILE}
fi
}
-# If the SHA256 sum of passwd, group or shadow has changed then copy the files
-# to /usr/etc. If libnss_usrfiles is installed those files will be taken into
-# account as a secondary source if an entry is not found in /etc.
-calc_user_group_sha256sum () {
- SHA256_passwd=`sha256sum ${MOUNT_DIR}/etc/passwd | awk '{ print $1 }'`
- SHA256_group=`sha256sum ${MOUNT_DIR}/etc/group | awk '{ print $1 }'`
- SHA256_shadow=`sha256sum ${MOUNT_DIR}/etc/shadow | awk '{ print $1 }'`
-}
-
-copy_user_group_accounts () {
- # Only copy files to usr/etc if libnss_usrfiles is installed.
- test -d ${MOUNT_DIR}/usr/etc || return
-
- NEWSUM=`sha256sum ${MOUNT_DIR}/etc/passwd | awk '{ print $1 }'`
- if [ ${NEWSUM} != ${SHA256_passwd} ]; then
- cp -a ${MOUNT_DIR}/etc/passwd ${MOUNT_DIR}/usr/etc/
- fi
-
- NEWSUM=`sha256sum ${MOUNT_DIR}/etc/group | awk '{ print $1 }'`
- if [ ${NEWSUM} != ${SHA256_group} ]; then
- cp -a ${MOUNT_DIR}/etc/group ${MOUNT_DIR}/usr/etc/
- fi
-
- NEWSUM=`sha256sum ${MOUNT_DIR}/etc/shadow | awk '{ print $1 }'`
- if [ ${NEWSUM} != ${SHA256_shadow} ]; then
- cp -a ${MOUNT_DIR}/etc/shadow ${MOUNT_DIR}/usr/etc/
- fi
-}
-
# Only called in error case; reverts everything to previous state.
quit() {
if [ -n "${SNAPSHOT_ID}" ] ; then
log_error "Removing snapshot #${SNAPSHOT_ID}..."
snapper ${SNAPPER_NO_DBUS} delete ${SNAPSHOT_ID} |& tee -a ${LOGFILE}
fi
- if [ -n "${SECOND_SNAPSHOT_ID}" ] ; then
- log_error "Removing snapshot #${SECOND_SNAPSHOT_ID}..."
- snapper ${SNAPPER_NO_DBUS} delete ${SECOND_SNAPSHOT_ID} |& tee -a
${LOGFILE}
+ if [ -n "${BACKUP_SNAPSHOT_ID}" ] ; then
+ log_error "Removing snapshot #${BACKUP_SNAPSHOT_ID}..."
+ snapper ${SNAPPER_NO_DBUS} delete ${BACKUP_SNAPSHOT_ID} |& tee -a
${LOGFILE}
fi
if [ $USE_SALT_GRAINS -eq 1 ]; then
if [ -f /etc/salt/grains ]; then
@@ -406,6 +374,24 @@
DO_SELF_UPDATE=0
shift
;;
+ register)
+ DO_REGISTRATION=1
+ shift
+
+ # Collect arguments for Registration
+ if [ $# -eq 0 ]; then
+ usage 1
+ fi
+
+ while [ 1 ]; do
+ if [ $# -eq 0 ]; then
+ break;
+ else
+ REGISTRATION_ARGS="${REGISTRATION_ARGS} $1";
+ shift
+ fi
+ done
+ ;;
-h|--help)
usage 0
;;
@@ -581,10 +567,13 @@
if [ -n "${ZYPPER_ARG}" -o ${REWRITE_GRUB_CFG} -eq 1 \
-o ${REWRITE_INITRD} -eq 1 -o ${REBUILD_KDUMP_INITRD} -eq 1 \
- -o ${RUN_SHELL} -eq 1 -o ${REWRITE_BOOTLOADER} -eq 1 ]; then
+ -o ${RUN_SHELL} -eq 1 -o ${REWRITE_BOOTLOADER} -eq 1 \
+ -o ${DO_REGISTRATION} -eq 1 ]; then
- # Check if there are updates at all.
- if [ -n "${ZYPPER_ARG}" -a -n "${ZYPPER_NONINTERACTIVE}" ]; then
+ # Check if there are updates at all. Don't check if we do
+ # a registration, as this can change the zypper result.
+ if [ -n "${ZYPPER_ARG}" -a -n "${ZYPPER_NONINTERACTIVE}" \
+ -a ${DO_REGISTRATION} -eq 0 ]; then
if [ $DO_DUP -eq 1 ]; then
"${SELF_PATH}"/transactional-update-helper disable-optical
fi
@@ -616,25 +605,41 @@
fi
fi
- # If the current root file system is not read-only, a read-only copy for
rollback has to be created first.
+ # Create a backup snapshot of the current system state (i.e. preserve the
/etc overlay contents on a read-only
+ # system or the general system state on a read-write file system, similar
to what zypper / snapper does).
# Hint: The rw subvolume is not shown in grub2.
- if [ ${RO_ROOT} == "false" ]; then
- log_info "Creating read-only snapshot of current read-write root
filesystem (#${CURRENT_SNAPSHOT_ID})"
- SECOND_SNAPSHOT_ID=`snapper create -p -c number -u "important=yes" -d
"RO-Clone of #${CURRENT_SNAPSHOT_ID}"`
+ log_info "Creating read-only snapshot of current system state
(#${CURRENT_SNAPSHOT_ID})"
+ BACKUP_SNAPSHOT_ID=`snapper create -p -t pre -c number -u "important=yes"
-d "RO-Clone of #${CURRENT_SNAPSHOT_ID}"`
+ BACKUP_SNAPSHOT_DIR=/.snapshots/${BACKUP_SNAPSHOT_ID}/snapshot
+ if [ $? -ne 0 ]; then
+ SNAPPER_NO_DBUS="--no-dbus"
+ BACKUP_SNAPSHOT_ID=`snapper --no-dbus create -p -t pre -c number -u
"important=yes" -d "RO-Clone of #${CURRENT_SNAPSHOT_ID}"`
if [ $? -ne 0 ]; then
- SNAPPER_NO_DBUS="--no-dbus"
- SECOND_SNAPSHOT_ID=`snapper --no-dbus create -p -c number -u
"important=yes" -d "RO-Clone of #${CURRENT_SNAPSHOT_ID}"`
- if [ $? -ne 0 ]; then
- log_error "ERROR: snapper create failed!"
- exit 1
- fi
+ log_error "ERROR: snapper create failed!"
+ exit 1
fi
fi
- SNAPSHOT_ID=`snapper create -p -u "transactional-update-in-progress=yes"
-d "Snapshot Update"`
+ # Make the backup snapshot read-write:
+ btrfs property set ${BACKUP_SNAPSHOT_DIR} ro false
+ if [ $? -ne 0 ]; then
+ log_error "ERROR: changing ${BACKUP_SNAPSHOT_DIR} to read-write failed!"
+ quit 1;
+ fi
+
+ # Copy the contents of /etc into the backup snapshot
+ log_info "Copying /etc state into backup snapshot"
+ rsync --archive --xattrs --acls --quiet --exclude "${NEW_SNAPSHOT_FLAG}"
--delete-excluded /etc ${BACKUP_SNAPSHOT_DIR}
+ if [ $? -ne 0 ]; then
+ log_error "ERROR: copying of /etc into backup snapshot failed!"
+ quit 1;
+ fi
+
+ # Create the working snapshot
+ SNAPSHOT_ID=`snapper create --type post --pre-number ${BACKUP_SNAPSHOT_ID}
-p -u "transactional-update-in-progress=yes" -d "Snapshot Update"`
if [ $? -ne 0 ]; then
SNAPPER_NO_DBUS="--no-dbus"
- SNAPSHOT_ID=`snapper --no-dbus create -p -u
"transactional-update-in-progress=yes" -d "Snapshot Update"`
+ SNAPSHOT_ID=`snapper --no-dbus create --type post --pre-number
${BACKUP_SNAPSHOT_ID} -p -u "transactional-update-in-progress=yes" -d "Snapshot
Update"`
if [ $? -ne 0 ]; then
log_error "ERROR: snapper create failed!"
quit 1
@@ -675,11 +680,7 @@
rm -f ${SNAPSHOT_DIR}/var/update_snapshot.test
fi
- # On a read only system, make sure that /etc/zypp in the
- # snapshot is current, could come from a overlayfs which
- # means not part of the snapshot itself
if [ ${RO_ROOT} == "true" ]; then
- DIR_TO_MOUNT="${DIR_TO_MOUNT} etc/zypp"
if [ ${RUN_SHELL} -eq 1 ]; then
DIR_TO_MOUNT="${DIR_TO_MOUNT} root"
fi
@@ -722,6 +723,14 @@
fi
done
+ # Copy the contents of /etc
+ log_info "Copying /etc state into snapshot"
+ rsync --archive --xattrs --acls --quiet --exclude "${NEW_SNAPSHOT_FLAG}"
--delete-excluded /etc ${SNAPSHOT_DIR}
+ if [ $? -ne 0 ]; then
+ log_error "ERROR: copying of /etc into snapshot failed!"
+ quit 1;
+ fi
+
# If we have a seperate /var, create some directories which we
# will delete again later.
if [ ${HAS_SEPARATE_VAR} -eq 1 ]; then
@@ -752,17 +761,12 @@
# transactional update
export TRANSACTIONAL_UPDATE=true
- # Copy modified config files to the snapshot
- for CF in ${CONFIG_FILES_TO_COPY} ; do
- if [ ${CF} -nt ${MOUNT_DIR}/${CF} ]; then
- cp -a ${CF} ${MOUNT_DIR}/${CF}
- fi
- done
+ if [ ${DO_REGISTRATION} -eq 1 ]; then
+ SUSEConnect --root ${MOUNT_DIR} ${REGISTRATION_ARGS}
+ fi
if [ -n "${ZYPPER_ARG}" ]; then
- calc_user_group_sha256sum
-
log_info "Calling zypper ${ZYPPER_ARG}"
if [ -n "${ZYPPER_NONINTERACTIVE}" ]; then
env DISABLE_RESTART_ON_UPDATE=yes zypper -R ${MOUNT_DIR}
${ZYPPER_ARG} ${ZYPPER_NONINTERACTIVE} ${ZYPPER_ARG_PKGS} |& tee -a ${LOGFILE}
@@ -786,7 +790,6 @@
fi
if [ $RETVAL -eq 0 -o $RETVAL -eq 102 -o $RETVAL -eq 103 -o \( $DO_DUP
-eq 0 -a $RETVAL -eq 106 \) ]; then
- copy_user_group_accounts
REBUILD_KDUMP_INITRD=1
# check if products are updated and we need to re-register
# at next boot.
@@ -893,6 +896,8 @@
# Save the old snapshot or else it will get lost.
add_unique_id ${CURRENT_SNAPSHOT_ID}
save_state_file ${SNAPSHOT_ID}
+ # Create flag file for overlay purging
+ touch "${NEW_SNAPSHOT_FLAG}"
# Reset in-progress flag
snapper ${SNAPPER_NO_DBUS} modify -u
"transactional-update-in-progress=" ${SNAPSHOT_ID}
fi
@@ -905,6 +910,13 @@
log_error "ERROR: changing ${SNAPSHOT_DIR} to ro=${RO_ROOT} failed!"
EXITCODE=1
fi
+ if [ -n ${BACKUP_SNAPSHOT_DIR} ]; then
+ btrfs property set ${BACKUP_SNAPSHOT_DIR} ro ${RO_ROOT}
+ if [ $? -ne 0 ]; then
+ log_error "ERROR: changing ${BACKUP_SNAPSHOT_DIR} to ro=${RO_ROOT}
failed!"
+ EXITCODE=1
+ fi
+ fi
if [ ${EXITCODE} -ne 0 ]; then
quit ${EXITCODE}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh
old/transactional-update-1.29/systemd/Makefile.am
new/transactional-update-2.0/systemd/Makefile.am
--- old/transactional-update-1.29/systemd/Makefile.am 2018-04-20
15:48:00.000000000 +0200
+++ new/transactional-update-2.0/systemd/Makefile.am 2018-04-20
18:34:52.000000000 +0200
@@ -4,6 +4,7 @@
systemddir = @SYSTEMDDIR@
-systemd_DATA = transactional-update.timer transactional-update.service
+systemd_DATA = transactional-update.timer transactional-update.service \
+ create-dirs-from-rpmdb.service
EXTRA_DIST = $(DATA)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh
old/transactional-update-1.29/systemd/create-dirs-from-rpmdb.service
new/transactional-update-2.0/systemd/create-dirs-from-rpmdb.service
--- old/transactional-update-1.29/systemd/create-dirs-from-rpmdb.service
1970-01-01 01:00:00.000000000 +0100
+++ new/transactional-update-2.0/systemd/create-dirs-from-rpmdb.service
2018-04-24 13:38:42.000000000 +0200
@@ -0,0 +1,12 @@
+[Unit]
+Description=Create missing directories from rpmdb
+Documentation=man:create_dirs_from_rpmdb(8)
+After=local-fs.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/usr/sbin/create_dirs_from_rpmdb -v
+
+[Install]
+WantedBy=multi-user.target