diff --git a/SOURCES/0430-device-make-sure-we-emit-PropertiesChanged-signal-on.patch b/SOURCES/0430-device-make-sure-we-emit-PropertiesChanged-signal-on.patch
new file mode 100644
index 0000000..8d202a4
--- /dev/null
+++ b/SOURCES/0430-device-make-sure-we-emit-PropertiesChanged-signal-on.patch
@@ -0,0 +1,26 @@
+From 91dddaafe0b6fcc9c0a57d2feef599b82ce2a146 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= <msekleta@redhat.com>
+Date: Thu, 26 Mar 2020 13:34:20 +0100
+Subject: [PATCH] device: make sure we emit PropertiesChanged signal once we
+ set sysfs
+
+(cherry picked from commit 7c4d139485139eae95b17a1d54cb51ae958abd70)
+
+Related: #1793533
+---
+ src/core/device.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/core/device.c b/src/core/device.c
+index a2d00a0fbe..21fe3802bd 100644
+--- a/src/core/device.c
++++ b/src/core/device.c
+@@ -81,6 +81,8 @@ static int device_set_sysfs(Device *d, const char *sysfs) {
+         }
+ 
+         d->sysfs = TAKE_PTR(copy);
++        unit_add_to_dbus_queue(UNIT(d));
++
+         return 0;
+ }
+ 
diff --git a/SOURCES/0431-device-don-t-emit-PropetiesChanged-needlessly.patch b/SOURCES/0431-device-don-t-emit-PropetiesChanged-needlessly.patch
new file mode 100644
index 0000000..e0986d5
--- /dev/null
+++ b/SOURCES/0431-device-don-t-emit-PropetiesChanged-needlessly.patch
@@ -0,0 +1,44 @@
+From a4cefc9f8bf24b2fdcc62cc0d2685698814374d4 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= <msekleta@redhat.com>
+Date: Thu, 26 Mar 2020 13:35:11 +0100
+Subject: [PATCH] device: don't emit PropetiesChanged needlessly
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Functions called from device_setup_unit() already make sure that unit is
+enqueued in case it is a new unit or properties exported on the bus have
+changed.
+
+This should prevent unnecessary DBus wakeups and associated DBus traffic
+when device_setup_unit() was called while reparsing /proc/self/mountinfo
+due to the mountinfo notifications. Note that we parse
+/proc/self/mountinfo quite often on the busy systems (e.g. k8s container
+hosts) but majority of the time mounts didn't change, only some mount
+got added. Thus we don't need to generate PropertiesChanged for devices
+associated with the mounts that didn't change.
+
+Thanks to Renaud Métrich <rmetrich@redhat.com> for debugging the
+problem and providing draft version of the patch.
+
+(cherry picked from commit 2e129d5d6bd6bd8be4b5359e81a880cbf72a44b8)
+
+Resolves: #1793533
+---
+ src/core/device.c | 3 ---
+ 1 file changed, 3 deletions(-)
+
+diff --git a/src/core/device.c b/src/core/device.c
+index 21fe3802bd..021c28dfbd 100644
+--- a/src/core/device.c
++++ b/src/core/device.c
+@@ -549,9 +549,6 @@ static int device_setup_unit(Manager *m, struct udev_device *dev, const char *pa
+         if (dev && device_is_bound_by_mounts(DEVICE(u), dev))
+                 device_upgrade_mount_deps(u);
+ 
+-        /* Note that this won't dispatch the load queue, the caller has to do that if needed and appropriate */
+-        unit_add_to_dbus_queue(u);
+-
+         return 0;
+ 
+ fail:
diff --git a/SOURCES/0432-units-add-generic-boot-complete.target.patch b/SOURCES/0432-units-add-generic-boot-complete.target.patch
new file mode 100644
index 0000000..912b230
--- /dev/null
+++ b/SOURCES/0432-units-add-generic-boot-complete.target.patch
@@ -0,0 +1,46 @@
+From dd573e5fbac858c20628052acfa19401d3e0d135 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Fri, 22 Jun 2018 12:52:28 +0200
+Subject: [PATCH] units: add generic boot-complete.target
+
+(cherry picked from commit 329d20db3cb02d789473b8f7e4a59526fcbf5728)
+
+Resolves: #1872243
+---
+ units/boot-complete.target | 14 ++++++++++++++
+ units/meson.build          |  1 +
+ 2 files changed, 15 insertions(+)
+ create mode 100644 units/boot-complete.target
+
+diff --git a/units/boot-complete.target b/units/boot-complete.target
+new file mode 100644
+index 0000000000..f0b9e57e7c
+--- /dev/null
++++ b/units/boot-complete.target
+@@ -0,0 +1,14 @@
++#  SPDX-License-Identifier: LGPL-2.1+
++#
++#  This file is part of systemd.
++#
++#  systemd is free software; you can redistribute it and/or modify it
++#  under the terms of the GNU Lesser General Public License as published by
++#  the Free Software Foundation; either version 2.1 of the License, or
++#  (at your option) any later version.
++
++[Unit]
++Description=Boot Completion Check
++Documentation=man:systemd.special(7)
++Requires=sysinit.target
++After=sysinit.target
+diff --git a/units/meson.build b/units/meson.build
+index e118d81888..a1cd2524dc 100644
+--- a/units/meson.build
++++ b/units/meson.build
+@@ -3,6 +3,7 @@
+ units = [
+         ['basic.target',                        ''],
+         ['bluetooth.target',                    ''],
++        ['boot-complete.target',                ''],
+         ['cryptsetup-pre.target',               'HAVE_LIBCRYPTSETUP'],
+         ['cryptsetup.target',                   'HAVE_LIBCRYPTSETUP',
+          'sysinit.target.wants/'],
diff --git a/SOURCES/0433-man-document-new-boot-complete.target-unit.patch b/SOURCES/0433-man-document-new-boot-complete.target-unit.patch
new file mode 100644
index 0000000..376958c
--- /dev/null
+++ b/SOURCES/0433-man-document-new-boot-complete.target-unit.patch
@@ -0,0 +1,53 @@
+From 8ad89170001c9aba8849630ddb5da81d9e24a1bc Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Mon, 25 Jun 2018 17:21:34 +0200
+Subject: [PATCH] man: document new "boot-complete.target" unit
+
+(cherry picked from commit 82ea38258c0f4964c2f3ad3691c6e4554c4f0bb0)
+
+Related: #1872243
+---
+ man/systemd.special.xml | 23 +++++++++++++++++++++++
+ 1 file changed, 23 insertions(+)
+
+diff --git a/man/systemd.special.xml b/man/systemd.special.xml
+index fb12805fff..c9d4345016 100644
+--- a/man/systemd.special.xml
++++ b/man/systemd.special.xml
+@@ -29,6 +29,7 @@
+     <filename>cryptsetup-pre.target</filename>,
+     <filename>cryptsetup.target</filename>,
+     <filename>ctrl-alt-del.target</filename>,
++    <filename>boot-complete.target</filename>,
+     <filename>default.target</filename>,
+     <filename>emergency.target</filename>,
+     <filename>exit.target</filename>,
+@@ -646,6 +647,28 @@
+           </para>
+         </listitem>
+       </varlistentry>
++        <varlistentry>
++          <term><filename>boot-complete.target</filename></term>
++          <listitem>
++            <para>This target is intended as generic synchronization point for services that shall determine or act on
++            whether the boot process completed successfully. Order units that are required to succeed for a boot process
++            to be considered successful before this unit, and add a <varname>Requires=</varname> dependency from the
++            target unit to them. Order units that shall only run when the boot process is considered successful after the
++            target unit and pull in the target from it, also with <varname>Requires=</varname>. Note that by default this
++            target unit is not part of the initial boot transaction, but is supposed to be pulled in only if required by
++            units that want to run only on successful boots.</para>
++
++            <para>See
++            <citerefentry><refentrytitle>systemd-boot-check-no-failures.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
++            for a service that implements a generic system health check and orders itself before
++            <filename>boot-complete.target</filename>.</para>
++
++            <para>See
++            <citerefentry><refentrytitle>systemd-bless-boot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
++            for a service that propagates boot success information to the boot loader, and orders itself after
++            <filename>boot-complete.target</filename>.</para>
++          </listitem>
++        </varlistentry>
+       <varlistentry>
+         <term><filename>syslog.socket</filename></term>
+         <listitem>
diff --git a/SOURCES/0434-core-make-sure-to-restore-the-control-command-id-too.patch b/SOURCES/0434-core-make-sure-to-restore-the-control-command-id-too.patch
new file mode 100644
index 0000000..c5bd3e1
--- /dev/null
+++ b/SOURCES/0434-core-make-sure-to-restore-the-control-command-id-too.patch
@@ -0,0 +1,30 @@
+From 37f2576684d7494c916fd1f13275982f3c43f44f Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Wed, 22 Apr 2020 20:34:02 +0200
+Subject: [PATCH] core: make sure to restore the control command id, too
+
+Fixes: #15356
+(cherry picked from commit e9da62b18af647bfa73807e1c7fc3bfa4bb4b2ac)
+
+Resolves: #1829867
+---
+ src/core/service.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/src/core/service.c b/src/core/service.c
+index 89b41f6783..7cff419e4e 100644
+--- a/src/core/service.c
++++ b/src/core/service.c
+@@ -2703,9 +2703,10 @@ static int service_deserialize_exec_command(Unit *u, const char *key, const char
+                                 break;
+         }
+ 
+-        if (command && control)
++        if (command && control) {
+                 s->control_command = command;
+-        else if (command)
++                s->control_command_id = id;
++        } else if (command)
+                 s->main_command = command;
+         else
+                 log_unit_warning(u, "Current command vanished from the unit file, execution of the command list won't be resumed.");
diff --git a/SOURCES/0435-cgroup-freezer-action-must-be-NOP-when-cgroup-v2-fre.patch b/SOURCES/0435-cgroup-freezer-action-must-be-NOP-when-cgroup-v2-fre.patch
new file mode 100644
index 0000000..04c1355
--- /dev/null
+++ b/SOURCES/0435-cgroup-freezer-action-must-be-NOP-when-cgroup-v2-fre.patch
@@ -0,0 +1,48 @@
+From 45d093a37b6f8c2ceae9bfd090c5265f35413b46 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= <msekleta@redhat.com>
+Date: Tue, 8 Sep 2020 14:51:39 +0200
+Subject: [PATCH] cgroup: freezer action must be NOP when cgroup v2 freezer is
+ not available
+
+Low-level cgroup freezer state manipulation is invoked directly from the
+job engine when we are about to execute the job in order to make sure
+the unit is not frozen and job execution is not blocked because of
+that.
+
+Currently with cgroup v1 we would needlessly do a bunch of work in the
+function and even falsely update the freezer state. Don't do any of this
+and skip the function silently when v2 freezer is not available.
+
+Following bug is fixed by this commit,
+
+$ systemd-run --unit foo.service /bin/sleep infinity
+$ systemctl restart foo.service
+$ systemctl show -p FreezerState foo.service
+
+Before (cgroup v1, i.e. full "legacy" mode):
+FreezerState=thawing
+
+After:
+FreezerState=running
+
+(cherry picked from commit 9a1e90aee556b7a30d87553a891a4175ae77ed68)
+
+Resolves: #1868831
+---
+ src/core/cgroup.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/core/cgroup.c b/src/core/cgroup.c
+index e0eb184fd2..f1ce070f9a 100644
+--- a/src/core/cgroup.c
++++ b/src/core/cgroup.c
+@@ -2936,6 +2936,9 @@ int unit_cgroup_freezer_action(Unit *u, FreezerAction action) {
+         assert(u);
+         assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW));
+ 
++        if (!cg_freezer_supported())
++                return 0;
++
+         if (!u->cgroup_realized)
+                 return -EBUSY;
+ 
diff --git a/SOURCES/0436-logind-don-t-print-warning-when-user-.service-templa.patch b/SOURCES/0436-logind-don-t-print-warning-when-user-.service-templa.patch
new file mode 100644
index 0000000..961f814
--- /dev/null
+++ b/SOURCES/0436-logind-don-t-print-warning-when-user-.service-templa.patch
@@ -0,0 +1,32 @@
+From 65e96327360ab41d44d5383dcecc82a19fad198c Mon Sep 17 00:00:00 2001
+From: Michal Sekletar <msekleta@redhat.com>
+Date: Fri, 22 Feb 2019 15:50:55 +0100
+Subject: [PATCH] logind: don't print warning when user@.service template is
+ masked
+
+User instance of systemd is optional feature and if user@.service
+template is masked then administrator most likely doesn't want --user
+instances of systemd for logged in users. We don't need to be verbose
+about it.
+
+(cherry picked from commit 03b6fa0c5b51b0d39334ff6ba183a3391443bcf6)
+
+Resolves: #1880270
+---
+ src/login/logind-user.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/src/login/logind-user.c b/src/login/logind-user.c
+index 8c4cd54a29..56b8066f12 100644
+--- a/src/login/logind-user.c
++++ b/src/login/logind-user.c
+@@ -326,7 +326,8 @@ static int user_start_service(User *u) {
+                         &job);
+         if (r < 0)
+                 /* we don't fail due to this, let's try to continue */
+-                log_error_errno(r, "Failed to start user service, ignoring: %s", bus_error_message(&error, r));
++                log_full_errno(sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED) ? LOG_DEBUG : LOG_WARNING, r,
++                               "Failed to start user service '%s', ignoring: %s", u->service, bus_error_message(&error, r));
+         else
+                 u->service_job = job;
+ 
diff --git a/SOURCES/0437-build-use-simple-project-version-in-pkgconfig-files.patch b/SOURCES/0437-build-use-simple-project-version-in-pkgconfig-files.patch
new file mode 100644
index 0000000..6ea481d
--- /dev/null
+++ b/SOURCES/0437-build-use-simple-project-version-in-pkgconfig-files.patch
@@ -0,0 +1,80 @@
+From a6d76bf2d21e01a2e031e204966d946925ecc3f6 Mon Sep 17 00:00:00 2001
+From: Jan Synacek <jsynacek@redhat.com>
+Date: Mon, 17 Aug 2020 14:29:04 +0200
+Subject: [PATCH] build: use simple project version in pkgconfig files
+
+Loosely based on commit a67c318df8800ba98d7361308937ed276dc73982.
+
+Resolves: #1862714
+---
+ meson.build                     | 2 ++
+ src/core/systemd.pc.in          | 2 +-
+ src/libsystemd/libsystemd.pc.in | 2 +-
+ src/libudev/libudev.pc.in       | 2 +-
+ src/udev/udev.pc.in             | 2 +-
+ 5 files changed, 6 insertions(+), 4 deletions(-)
+
+diff --git a/meson.build b/meson.build
+index 0ba3f924ea..65c1d0785e 100644
+--- a/meson.build
++++ b/meson.build
+@@ -27,12 +27,14 @@ endif
+ # names, sometimes. Not all variables are included in every
+ # set. Ugh, ugh, ugh!
+ conf = configuration_data()
++conf.set_quoted('PROJECT_VERSION', meson.project_version())
+ conf.set_quoted('PACKAGE_STRING',  meson.project_name() + ' ' + dist_version)
+ conf.set_quoted('PACKAGE_VERSION', dist_version)
+ 
+ substs = configuration_data()
+ substs.set('PACKAGE_URL',          'https://www.freedesktop.org/wiki/Software/systemd')
+ substs.set('PACKAGE_VERSION',      dist_version)
++substs.set('PROJECT_VERSION',      meson.project_version())
+ 
+ #####################################################################
+ 
+diff --git a/src/core/systemd.pc.in b/src/core/systemd.pc.in
+index 655773ea8a..a350737cf2 100644
+--- a/src/core/systemd.pc.in
++++ b/src/core/systemd.pc.in
+@@ -37,4 +37,4 @@ containeruidbasemax=@containeruidbasemax@
+ Name: systemd
+ Description: systemd System and Service Manager
+ URL: @PACKAGE_URL@
+-Version: @PACKAGE_VERSION@
++Version: @PROJECT_VERSION@
+diff --git a/src/libsystemd/libsystemd.pc.in b/src/libsystemd/libsystemd.pc.in
+index c861905b67..85d6ebf293 100644
+--- a/src/libsystemd/libsystemd.pc.in
++++ b/src/libsystemd/libsystemd.pc.in
+@@ -15,6 +15,6 @@ includedir=@includedir@
+ Name: systemd
+ Description: systemd Library
+ URL: @PACKAGE_URL@
+-Version: @PACKAGE_VERSION@
++Version: @PROJECT_VERSION@
+ Libs: -L${libdir} -lsystemd
+ Cflags: -I${includedir}
+diff --git a/src/libudev/libudev.pc.in b/src/libudev/libudev.pc.in
+index 69f5c6463e..40b340362e 100644
+--- a/src/libudev/libudev.pc.in
++++ b/src/libudev/libudev.pc.in
+@@ -14,6 +14,6 @@ includedir=@includedir@
+ 
+ Name: libudev
+ Description: Library to access udev device information
+-Version: @PACKAGE_VERSION@
++Version: @PROJECT_VERSION@
+ Libs: -L${libdir} -ludev
+ Cflags: -I${includedir}
+diff --git a/src/udev/udev.pc.in b/src/udev/udev.pc.in
+index e384a6f7c9..5acbb2d01a 100644
+--- a/src/udev/udev.pc.in
++++ b/src/udev/udev.pc.in
+@@ -1,5 +1,5 @@
+ Name: udev
+ Description: udev
+-Version: @PACKAGE_VERSION@
++Version: @PROJECT_VERSION@
+ 
+ udevdir=@udevlibexecdir@
diff --git a/SOURCES/0438-basic-virt-try-the-proc-1-sched-hack-also-for-PID1.patch b/SOURCES/0438-basic-virt-try-the-proc-1-sched-hack-also-for-PID1.patch
new file mode 100644
index 0000000..4237fa1
--- /dev/null
+++ b/SOURCES/0438-basic-virt-try-the-proc-1-sched-hack-also-for-PID1.patch
@@ -0,0 +1,57 @@
+From 2f584bd93d64a75ab11b5a5aa31d0b7145da5a86 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Fri, 26 Apr 2019 13:37:31 +0200
+Subject: [PATCH] basic/virt: try the /proc/1/sched hack also for PID1
+
+If a container manager does not set $container, we could end up
+in a strange situation when detect-virt returns container-other when
+run as non-pid-1 and none when run as pid-1.
+
+(cherry picked from commit 342bed02084c4396dd2f1054bd559bfb2699cfcb)
+Resolves: #1868877
+---
+ src/basic/virt.c | 16 +++++++++++-----
+ 1 file changed, 11 insertions(+), 5 deletions(-)
+
+diff --git a/src/basic/virt.c b/src/basic/virt.c
+index e05b3e6d99..dfa1525219 100644
+--- a/src/basic/virt.c
++++ b/src/basic/virt.c
+@@ -427,7 +427,6 @@ finish:
+ }
+ 
+ int detect_container(void) {
+-
+         static const struct {
+                 const char *value;
+                 int id;
+@@ -456,9 +455,15 @@ int detect_container(void) {
+         }
+ 
+         if (getpid_cached() == 1) {
+-                /* If we are PID 1 we can just check our own environment variable, and that's authoritative. */
+-
++                /* If we are PID 1 we can just check our own environment variable, and that's authoritative.
++                 * We distinguish three cases:
++                 * - the variable is not defined → we jump to other checks
++                 * - the variable is defined to an empty value → we are not in a container
++                 * - anything else → some container, either one of the known ones or "container-other"
++                 */
+                 e = getenv("container");
++                if (!e)
++                        goto check_sched;
+                 if (isempty(e)) {
+                         r = VIRTUALIZATION_NONE;
+                         goto finish;
+@@ -486,8 +491,9 @@ int detect_container(void) {
+         if (r < 0) /* This only works if we have CAP_SYS_PTRACE, hence let's better ignore failures here */
+                 log_debug_errno(r, "Failed to read $container of PID 1, ignoring: %m");
+ 
+-        /* Interestingly /proc/1/sched actually shows the host's PID for what we see as PID 1. Hence, if the PID shown
+-         * there is not 1, we know we are in a PID namespace. and hence a container. */
++        /* Interestingly /proc/1/sched actually shows the host's PID for what we see as PID 1. If the PID
++         * shown there is not 1, we know we are in a PID namespace and hence a container. */
++ check_sched:
+         r = read_one_line_file("/proc/1/sched", &m);
+         if (r >= 0) {
+                 const char *t;
diff --git a/SOURCES/0439-seccomp-rework-how-the-S-UG-ID-filter-is-installed.patch b/SOURCES/0439-seccomp-rework-how-the-S-UG-ID-filter-is-installed.patch
new file mode 100644
index 0000000..f824c4d
--- /dev/null
+++ b/SOURCES/0439-seccomp-rework-how-the-S-UG-ID-filter-is-installed.patch
@@ -0,0 +1,287 @@
+From 8cc497e735104080f6830a8f468b2724ae372990 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Wed, 3 Apr 2019 13:11:00 +0200
+Subject: [PATCH] seccomp: rework how the S[UG]ID filter is installed
+
+If we know that a syscall is undefined on the given architecture, don't
+even try to add it.
+
+Try to install the filter even if some syscalls fail. Also use a helper
+function to make the whole a bit less magic.
+
+This allows the S[UG]ID test to pass on arm64.
+
+(cherry picked from commit da4dc9a6748797e804b6bc92ad513d509abf581c)
+
+Resolves: #1860374
+---
+ src/shared/seccomp-util.c | 244 +++++++++++++++++++++-----------------
+ 1 file changed, 138 insertions(+), 106 deletions(-)
+
+diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c
+index fd46b9f88d..d91fb4e269 100644
+--- a/src/shared/seccomp-util.c
++++ b/src/shared/seccomp-util.c
+@@ -1750,9 +1750,139 @@ int seccomp_lock_personality(unsigned long personality) {
+         return 0;
+ }
+ 
++static int seccomp_restrict_sxid(scmp_filter_ctx seccomp, mode_t m) {
++        /* Checks the mode_t parameter of the following system calls:
++         *
++         *       → chmod() + fchmod() + fchmodat()
++         *       → open() + creat() + openat()
++         *       → mkdir() + mkdirat()
++         *       → mknod() + mknodat()
++         *
++         * Returns error if *everything* failed, and 0 otherwise.
++         */
++        int r = 0;
++        bool any = false;
++
++        r = seccomp_rule_add_exact(
++                        seccomp,
++                        SCMP_ACT_ERRNO(EPERM),
++                        SCMP_SYS(chmod),
++                        1,
++                        SCMP_A1(SCMP_CMP_MASKED_EQ, m, m));
++        if (r < 0)
++                log_debug_errno(r, "Failed to add filter for chmod: %m");
++        else
++                any = true;
++
++        r = seccomp_rule_add_exact(
++                        seccomp,
++                        SCMP_ACT_ERRNO(EPERM),
++                        SCMP_SYS(fchmod),
++                        1,
++                        SCMP_A1(SCMP_CMP_MASKED_EQ, m, m));
++        if (r < 0)
++                log_debug_errno(r, "Failed to add filter for fchmod: %m");
++        else
++                any = true;
++
++        r = seccomp_rule_add_exact(
++                        seccomp,
++                        SCMP_ACT_ERRNO(EPERM),
++                        SCMP_SYS(fchmodat),
++                        1,
++                        SCMP_A2(SCMP_CMP_MASKED_EQ, m, m));
++        if (r < 0)
++                log_debug_errno(r, "Failed to add filter for fchmodat: %m");
++        else
++                any = true;
++
++        r = seccomp_rule_add_exact(
++                        seccomp,
++                        SCMP_ACT_ERRNO(EPERM),
++                        SCMP_SYS(mkdir),
++                        1,
++                        SCMP_A1(SCMP_CMP_MASKED_EQ, m, m));
++        if (r < 0)
++                log_debug_errno(r, "Failed to add filter for mkdir: %m");
++        else
++                any = true;
++
++        r = seccomp_rule_add_exact(
++                        seccomp,
++                        SCMP_ACT_ERRNO(EPERM),
++                        SCMP_SYS(mkdirat),
++                        1,
++                        SCMP_A2(SCMP_CMP_MASKED_EQ, m, m));
++        if (r < 0)
++                log_debug_errno(r, "Failed to add filter for mkdirat: %m");
++        else
++                any = true;
++
++        r = seccomp_rule_add_exact(
++                        seccomp,
++                        SCMP_ACT_ERRNO(EPERM),
++                        SCMP_SYS(mknod),
++                        1,
++                        SCMP_A1(SCMP_CMP_MASKED_EQ, m, m));
++        if (r < 0)
++                log_debug_errno(r, "Failed to add filter for mknod: %m");
++        else
++                any = true;
++
++        r = seccomp_rule_add_exact(
++                        seccomp,
++                        SCMP_ACT_ERRNO(EPERM),
++                        SCMP_SYS(mknodat),
++                        1,
++                        SCMP_A2(SCMP_CMP_MASKED_EQ, m, m));
++        if (r < 0)
++                log_debug_errno(r, "Failed to add filter for mknodat: %m");
++        else
++                any = true;
++
++#if SCMP_SYS(open) > 0
++        r = seccomp_rule_add_exact(
++                        seccomp,
++                        SCMP_ACT_ERRNO(EPERM),
++                        SCMP_SYS(open),
++                        2,
++                        SCMP_A1(SCMP_CMP_MASKED_EQ, O_CREAT, O_CREAT),
++                        SCMP_A2(SCMP_CMP_MASKED_EQ, m, m));
++        if (r < 0)
++                log_debug_errno(r, "Failed to add filter for open: %m");
++        else
++                any = true;
++#endif
++
++        r = seccomp_rule_add_exact(
++                        seccomp,
++                        SCMP_ACT_ERRNO(EPERM),
++                        SCMP_SYS(openat),
++                        2,
++                        SCMP_A2(SCMP_CMP_MASKED_EQ, O_CREAT, O_CREAT),
++                        SCMP_A3(SCMP_CMP_MASKED_EQ, m, m));
++        if (r < 0)
++                log_debug_errno(r, "Failed to add filter for openat: %m");
++        else
++                any = true;
++
++        r = seccomp_rule_add_exact(
++                        seccomp,
++                        SCMP_ACT_ERRNO(EPERM),
++                        SCMP_SYS(creat),
++                        1,
++                        SCMP_A1(SCMP_CMP_MASKED_EQ, m, m));
++        if (r < 0)
++                log_debug_errno(r, "Failed to add filter for creat: %m");
++        else
++                any = true;
++
++        return any ? 0 : r;
++}
++
+ int seccomp_restrict_suid_sgid(void) {
+         uint32_t arch;
+-        int r;
++        int r, k;
+ 
+         SECCOMP_FOREACH_LOCAL_ARCH(arch) {
+                 _cleanup_(seccomp_releasep) scmp_filter_ctx seccomp = NULL;
+@@ -1761,114 +1891,16 @@ int seccomp_restrict_suid_sgid(void) {
+                 if (r < 0)
+                         return r;
+ 
+-                /* Checks the mode_t parameter of the following system calls:
+-                 *
+-                 *       → chmod() + fchmod() + fchmodat()
+-                 *       → open() + creat() + openat()
+-                 *       → mkdir() + mkdirat()
+-                 *       → mknod() + mknodat()
+-                 */
+-
+-                for (unsigned bit = 0; bit < 2; bit ++) {
+-                        /* Block S_ISUID in the first iteration, S_ISGID in the second */
+-                        mode_t m = bit == 0 ? S_ISUID : S_ISGID;
+-
+-                        r = seccomp_rule_add_exact(
+-                                        seccomp,
+-                                        SCMP_ACT_ERRNO(EPERM),
+-                                        SCMP_SYS(chmod),
+-                                        1,
+-                                        SCMP_A1(SCMP_CMP_MASKED_EQ, m, m));
+-                        if (r < 0)
+-                                break;
+-
+-                        r = seccomp_rule_add_exact(
+-                                        seccomp,
+-                                        SCMP_ACT_ERRNO(EPERM),
+-                                        SCMP_SYS(fchmod),
+-                                        1,
+-                                        SCMP_A1(SCMP_CMP_MASKED_EQ, m, m));
+-                        if (r < 0)
+-                                break;
+-
+-                        r = seccomp_rule_add_exact(
+-                                        seccomp,
+-                                        SCMP_ACT_ERRNO(EPERM),
+-                                        SCMP_SYS(fchmodat),
+-                                        1,
+-                                        SCMP_A2(SCMP_CMP_MASKED_EQ, m, m));
+-                        if (r < 0)
+-                                break;
+-
+-                        r = seccomp_rule_add_exact(
+-                                        seccomp,
+-                                        SCMP_ACT_ERRNO(EPERM),
+-                                        SCMP_SYS(mkdir),
+-                                        1,
+-                                        SCMP_A1(SCMP_CMP_MASKED_EQ, m, m));
+-                        if (r < 0)
+-                                break;
+-
+-                        r = seccomp_rule_add_exact(
+-                                        seccomp,
+-                                        SCMP_ACT_ERRNO(EPERM),
+-                                        SCMP_SYS(mkdirat),
+-                                        1,
+-                                        SCMP_A2(SCMP_CMP_MASKED_EQ, m, m));
+-                        if (r < 0)
+-                                break;
+-
+-                        r = seccomp_rule_add_exact(
+-                                        seccomp,
+-                                        SCMP_ACT_ERRNO(EPERM),
+-                                        SCMP_SYS(mknod),
+-                                        1,
+-                                        SCMP_A1(SCMP_CMP_MASKED_EQ, m, m));
+-                        if (r < 0)
+-                                break;
+-
+-                        r = seccomp_rule_add_exact(
+-                                        seccomp,
+-                                        SCMP_ACT_ERRNO(EPERM),
+-                                        SCMP_SYS(mknodat),
+-                                        1,
+-                                        SCMP_A2(SCMP_CMP_MASKED_EQ, m, m));
+-                        if (r < 0)
+-                                break;
+-
+-                        r = seccomp_rule_add_exact(
+-                                        seccomp,
+-                                        SCMP_ACT_ERRNO(EPERM),
+-                                        SCMP_SYS(open),
+-                                        2,
+-                                        SCMP_A1(SCMP_CMP_MASKED_EQ, O_CREAT, O_CREAT),
+-                                        SCMP_A2(SCMP_CMP_MASKED_EQ, m, m));
+-                        if (r < 0)
+-                                break;
++                r = seccomp_restrict_sxid(seccomp, S_ISUID);
++                if (r < 0)
++                        log_debug_errno(r, "Failed to add suid rule for architecture %s, ignoring: %m", seccomp_arch_to_string(arch));
+ 
+-                        r = seccomp_rule_add_exact(
+-                                        seccomp,
+-                                        SCMP_ACT_ERRNO(EPERM),
+-                                        SCMP_SYS(openat),
+-                                        2,
+-                                        SCMP_A2(SCMP_CMP_MASKED_EQ, O_CREAT, O_CREAT),
+-                                        SCMP_A3(SCMP_CMP_MASKED_EQ, m, m));
+-                        if (r < 0)
+-                                break;
++                k = seccomp_restrict_sxid(seccomp, S_ISGID);
++                if (k < 0)
++                        log_debug_errno(r, "Failed to add sgid rule for architecture %s, ignoring: %m", seccomp_arch_to_string(arch));
+ 
+-                        r = seccomp_rule_add_exact(
+-                                        seccomp,
+-                                        SCMP_ACT_ERRNO(EPERM),
+-                                        SCMP_SYS(creat),
+-                                        1,
+-                                        SCMP_A1(SCMP_CMP_MASKED_EQ, m, m));
+-                        if (r < 0)
+-                                break;
+-                }
+-                if (r < 0) {
+-                        log_debug_errno(r, "Failed to add suid/sgid rule for architecture %s, skipping: %m", seccomp_arch_to_string(arch));
++                if (r < 0 && k < 0)
+                         continue;
+-                }
+ 
+                 r = seccomp_load(seccomp);
+                 if (IN_SET(r, -EPERM, -EACCES))
diff --git a/SOURCES/0440-vconsole-setup-downgrade-log-message-when-setting-fo.patch b/SOURCES/0440-vconsole-setup-downgrade-log-message-when-setting-fo.patch
new file mode 100644
index 0000000..09874a7
--- /dev/null
+++ b/SOURCES/0440-vconsole-setup-downgrade-log-message-when-setting-fo.patch
@@ -0,0 +1,91 @@
+From 860749038f508617c8fc31b8292b4019b1e621ba Mon Sep 17 00:00:00 2001
+From: Franck Bui <fbui@suse.com>
+Date: Thu, 16 Jul 2020 21:22:37 +0200
+Subject: [PATCH] vconsole-setup: downgrade log message when setting font fails
+ on dummy console
+
+Since commit 883eb9be985fd86d9cabe967eeeab91cdd396a81, vconsole-setup might be
+called again to operate on dummy console where font operations are not
+supported but where it's still important to have the correct keymap set [0][1].
+
+vconsole-setup is mainly called by udev but can also be run via a dependency of
+an early service. Both cases might end up calling vconsole-setup on the dummy
+console.
+
+The first case can happen during early boot even on systems that use (instead
+of the dummy console) a "simple" video console driver supporting font
+operations (such as vgacon) until a more specific driver (such as i915) takes
+the console over. While this is happening vgacon is deactivated and temporarly
+replaced by the dummy console [2].
+
+There are also other cases where systemd-vconsole-setup might be called on
+dummy console especially during (very) early boot. Indeed
+systemd-vconsole-setup.service might be pulled in by early interactive services
+such as 'dracut-cmdline-ask.service` which is run before udev.
+
+If that happens on platforms with no grapical HWs (such as embedded ARM) or
+with dummy console initially installed until a driver takes over (like Xen and
+xen-fbfront) then setting font will fail.
+
+Therefore this patch downgrades the log message emitted when setting font fails
+to LOG_DEBUG and when font operations is not implemented like it's the case for
+the dummy console.
+
+Fixes: #16406.
+
+[0] https://github.com/systemd/systemd/issues/10826
+[1] https://bugzilla.redhat.com/show_bug.cgi?id=1652473
+[2] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/vga/vgaarb.c?h=v5.7#n204
+
+(cherry picked from commit 0ef1adf51274960358e852d3bc36ae6c288a70d9)
+
+Resolves: #1889996
+---
+ src/vconsole/vconsole-setup.c | 18 ++++++++++++++----
+ 1 file changed, 14 insertions(+), 4 deletions(-)
+
+diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c
+index f162d29220..1b406c0bc5 100644
+--- a/src/vconsole/vconsole-setup.c
++++ b/src/vconsole/vconsole-setup.c
+@@ -222,6 +222,7 @@ static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) {
+         _cleanup_free_ struct unipair* unipairs = NULL;
+         _cleanup_free_ void *fontbuf = NULL;
+         unsigned i;
++        int log_level;
+         int r;
+ 
+         unipairs = new(struct unipair, USHRT_MAX);
+@@ -230,11 +231,20 @@ static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) {
+                 return;
+         }
+ 
++        log_level = LOG_WARNING;
++
+         /* get metadata of the current font (width, height, count) */
+         r = ioctl(src_fd, KDFONTOP, &cfo);
+-        if (r < 0)
+-                log_warning_errno(errno, "KD_FONT_OP_GET failed while trying to get the font metadata: %m");
+-        else {
++        if (r < 0) {
++                /* We might be called to operate on the dummy console (to setup keymap
++                 * mainly) when fbcon deferred takeover is used for example. In such case,
++                 * setting font is not supported and is expected to fail. */
++                if (errno == ENOSYS)
++                        log_level = LOG_DEBUG;
++
++                log_full_errno(log_level, errno,
++                               "KD_FONT_OP_GET failed while trying to get the font metadata: %m");
++        } else {
+                 /* verify parameter sanity first */
+                 if (cfo.width > 32 || cfo.height > 32 || cfo.charcount > 512)
+                         log_warning("Invalid font metadata - width: %u (max 32), height: %u (max 32), count: %u (max 512)",
+@@ -269,7 +279,7 @@ static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) {
+         }
+ 
+         if (cfo.op != KD_FONT_OP_SET)
+-                log_warning("Fonts will not be copied to remaining consoles");
++                log_full(log_level, "Fonts will not be copied to remaining consoles");
+ 
+         for (i = 1; i <= 63; i++) {
+                 char ttyname[sizeof("/dev/tty63")];
diff --git a/SOURCES/0441-units-fix-systemd.special-man-page-reference-in-syst.patch b/SOURCES/0441-units-fix-systemd.special-man-page-reference-in-syst.patch
new file mode 100644
index 0000000..4d6bd2e
--- /dev/null
+++ b/SOURCES/0441-units-fix-systemd.special-man-page-reference-in-syst.patch
@@ -0,0 +1,26 @@
+From 46fa8ff1a62e3334582a971cc6bbd9b8a16680d5 Mon Sep 17 00:00:00 2001
+From: Michael Biebl <biebl@debian.org>
+Date: Thu, 7 Mar 2019 12:02:53 +0100
+Subject: [PATCH] units: fix systemd.special man page reference in
+ system-update-cleanup.service
+
+(cherry picked from commit faab72d16b310c17be4b908cfe15eca122d16ae4)
+
+Resolves: #1871827
+---
+ units/system-update-cleanup.service | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/units/system-update-cleanup.service b/units/system-update-cleanup.service
+index 58baab3023..d5eca2546b 100644
+--- a/units/system-update-cleanup.service
++++ b/units/system-update-cleanup.service
+@@ -9,7 +9,7 @@
+ 
+ [Unit]
+ Description=Remove the Offline System Updates symlink
+-Documentation=man:systemd.special(5) man:systemd.offline-updates(7)
++Documentation=man:systemd.special(7) man:systemd.offline-updates(7)
+ After=system-update.target
+ DefaultDependencies=no
+ Conflicts=shutdown.target
diff --git a/SOURCES/0442-units-drop-reference-to-sushell-man-page.patch b/SOURCES/0442-units-drop-reference-to-sushell-man-page.patch
new file mode 100644
index 0000000..d998724
--- /dev/null
+++ b/SOURCES/0442-units-drop-reference-to-sushell-man-page.patch
@@ -0,0 +1,27 @@
+From 5aa59d172189adcbd7f9dedb3b909c6bf9b609f2 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Mon, 29 Apr 2019 16:10:51 +0200
+Subject: [PATCH] units: drop reference to sushell man page
+
+sushell was a Fedoraism, and has been removed since. Hence our upstream
+unit files shouldn't reference it either.
+
+(cherry picked from commit 6dc14d73664390682d47d7e5bcbdbb362d04f623)
+
+Resolves: #1871827
+---
+ units/debug-shell.service.in | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/units/debug-shell.service.in b/units/debug-shell.service.in
+index 1127e68b63..9f3868e106 100644
+--- a/units/debug-shell.service.in
++++ b/units/debug-shell.service.in
+@@ -9,7 +9,6 @@
+ 
+ [Unit]
+ Description=Early root shell on @DEBUGTTY@ FOR DEBUGGING ONLY
+-Documentation=man:sushell(8)
+ Documentation=man:systemd-debug-generator(8)
+ DefaultDependencies=no
+ IgnoreOnIsolate=yes
diff --git a/SOURCES/0443-sd-bus-break-the-loop-in-bus_ensure_running-if-the-b.patch b/SOURCES/0443-sd-bus-break-the-loop-in-bus_ensure_running-if-the-b.patch
new file mode 100644
index 0000000..a53c450
--- /dev/null
+++ b/SOURCES/0443-sd-bus-break-the-loop-in-bus_ensure_running-if-the-b.patch
@@ -0,0 +1,44 @@
+From 6a50c735a3bbf98d06fbfa7815f7bdc14ea96f9f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Wed, 14 Oct 2020 14:03:13 +0200
+Subject: [PATCH] sd-bus: break the loop in bus_ensure_running() if the bus is
+ not connecting
+
+This might fix #17025:
+> the call trace is
+> bus_ensure_running -> sd_bus_process -> bus_process_internal -> process_closeing --> sd_bus_close
+>                                                                                  |
+>                                                                                  \-> process_match
+
+We ended doing callouts to the Disconnected matches from bus_ensure_running()
+and shouldn't. bus_ensure_running() should never do callouts. This change
+should fix this however: once we notice that the connection is going down we
+will now fail instantly with ENOTOCONN instead of calling any callbacks.
+
+(cherry picked from commit 93a59b1ae5d3bcb0ec1488ebc13d0d1ff4d1729a)
+
+Resolves: #1885553
+---
+ src/libsystemd/sd-bus/sd-bus.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c
+index a3509f7e89..c65e24b2d1 100644
+--- a/src/libsystemd/sd-bus/sd-bus.c
++++ b/src/libsystemd/sd-bus/sd-bus.c
+@@ -2059,12 +2059,13 @@ int bus_ensure_running(sd_bus *bus) {
+ 
+         assert(bus);
+ 
+-        if (IN_SET(bus->state, BUS_UNSET, BUS_CLOSED, BUS_CLOSING))
+-                return -ENOTCONN;
+         if (bus->state == BUS_RUNNING)
+                 return 1;
+ 
+         for (;;) {
++                if (IN_SET(bus->state, BUS_UNSET, BUS_CLOSED, BUS_CLOSING))
++                        return -ENOTCONN;
++
+                 r = sd_bus_process(bus, NULL);
+                 if (r < 0)
+                         return r;
diff --git a/SOURCES/0444-core-add-new-API-for-enqueing-a-job-with-returning-t.patch b/SOURCES/0444-core-add-new-API-for-enqueing-a-job-with-returning-t.patch
new file mode 100644
index 0000000..ccd3ae1
--- /dev/null
+++ b/SOURCES/0444-core-add-new-API-for-enqueing-a-job-with-returning-t.patch
@@ -0,0 +1,831 @@
+From 7155c010ef8c620295d230c284849636c07b40c0 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Fri, 22 Mar 2019 20:57:30 +0100
+Subject: [PATCH] core: add new API for enqueing a job with returning the
+ transaction data
+
+(cherry picked from commit 50cbaba4fe5a32850998682699322d012e597e4a)
+
+Related: #846319
+---
+ src/analyze/analyze-verify.c |   2 +-
+ src/core/automount.c         |   4 +-
+ src/core/dbus-manager.c      |  23 +++++-
+ src/core/dbus-unit.c         | 153 +++++++++++++++++++++++++++++++----
+ src/core/dbus-unit.h         |   8 +-
+ src/core/dbus.c              |   2 +-
+ src/core/device.c            |   2 +-
+ src/core/emergency-action.c  |   5 +-
+ src/core/main.c              |   4 +-
+ src/core/manager.c           |  38 +++++----
+ src/core/manager.h           |   6 +-
+ src/core/path.c              |   2 +-
+ src/core/service.c           |   2 +-
+ src/core/socket.c            |   4 +-
+ src/core/timer.c             |   2 +-
+ src/core/transaction.c       |  22 ++++-
+ src/core/transaction.h       |   2 +-
+ src/core/unit.c              |  16 ++--
+ src/test/test-engine.c       |  20 ++---
+ 19 files changed, 244 insertions(+), 73 deletions(-)
+
+diff --git a/src/analyze/analyze-verify.c b/src/analyze/analyze-verify.c
+index ed369532d4..1e143511b2 100644
+--- a/src/analyze/analyze-verify.c
++++ b/src/analyze/analyze-verify.c
+@@ -205,7 +205,7 @@ static int verify_unit(Unit *u, bool check_man) {
+                 unit_dump(u, stdout, "\t");
+ 
+         log_unit_debug(u, "Creating %s/start job", u->id);
+-        r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, &err, NULL);
++        r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, NULL, &err, NULL);
+         if (r < 0)
+                 log_unit_error_errno(u, r, "Failed to create %s/start: %s", u->id, bus_error_message(&err, r));
+ 
+diff --git a/src/core/automount.c b/src/core/automount.c
+index b1a155d8d4..76e70f4dac 100644
+--- a/src/core/automount.c
++++ b/src/core/automount.c
+@@ -776,7 +776,7 @@ static void automount_enter_running(Automount *a) {
+                 goto fail;
+         }
+ 
+-        r = manager_add_job(UNIT(a)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL);
++        r = manager_add_job(UNIT(a)->manager, JOB_START, trigger, JOB_REPLACE, NULL, &error, NULL);
+         if (r < 0) {
+                 log_unit_warning(UNIT(a), "Failed to queue mount startup job: %s", bus_error_message(&error, r));
+                 goto fail;
+@@ -1032,7 +1032,7 @@ static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, vo
+                         goto fail;
+                 }
+ 
+-                r = manager_add_job(UNIT(a)->manager, JOB_STOP, trigger, JOB_REPLACE, &error, NULL);
++                r = manager_add_job(UNIT(a)->manager, JOB_STOP, trigger, JOB_REPLACE, NULL, &error, NULL);
+                 if (r < 0) {
+                         log_unit_warning(UNIT(a), "Failed to queue umount startup job: %s", bus_error_message(&error, r));
+                         goto fail;
+diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
+index a0777f63d5..0a1d3df42f 100644
+--- a/src/core/dbus-manager.c
++++ b/src/core/dbus-manager.c
+@@ -549,6 +549,26 @@ static int method_reload_or_try_restart_unit(sd_bus_message *message, void *user
+         return method_start_unit_generic(message, userdata, JOB_TRY_RESTART, true, error);
+ }
+ 
++static int method_enqueue_unit_job(sd_bus_message *message, void *userdata, sd_bus_error *error) {
++        Manager *m = userdata;
++        const char *name;
++        Unit *u;
++        int r;
++
++        assert(message);
++        assert(m);
++
++        r = sd_bus_message_read(message, "s", &name);
++        if (r < 0)
++                return r;
++
++        r = manager_load_unit(m, name, NULL, error, &u);
++        if (r < 0)
++                return r;
++
++        return bus_unit_method_enqueue_job(message, u, error);
++}
++
+ static int method_start_unit_replace(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+         Manager *m = userdata;
+         const char *old_name;
+@@ -978,7 +998,7 @@ static int method_start_transient_unit(sd_bus_message *message, void *userdata,
+                 return r;
+ 
+         /* Finally, start it */
+-        return bus_unit_queue_job(message, u, JOB_START, mode, false, error);
++        return bus_unit_queue_job(message, u, JOB_START, mode, 0, error);
+ }
+ 
+ static int method_get_job(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+@@ -2547,6 +2567,7 @@ const sd_bus_vtable bus_manager_vtable[] = {
+         SD_BUS_METHOD("TryRestartUnit", "ss", "o", method_try_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED),
+         SD_BUS_METHOD("ReloadOrRestartUnit", "ss", "o", method_reload_or_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED),
+         SD_BUS_METHOD("ReloadOrTryRestartUnit", "ss", "o", method_reload_or_try_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED),
++        SD_BUS_METHOD("EnqueueUnitJob", "sss", "uososa(uosos)", method_enqueue_unit_job, SD_BUS_VTABLE_UNPRIVILEGED),
+         SD_BUS_METHOD("KillUnit", "ssi", NULL, method_kill_unit, SD_BUS_VTABLE_UNPRIVILEGED),
+         SD_BUS_METHOD("FreezeUnit", "s", NULL, method_freeze_unit, SD_BUS_VTABLE_UNPRIVILEGED),
+         SD_BUS_METHOD("ThawUnit", "s", NULL, method_thaw_unit, SD_BUS_VTABLE_UNPRIVILEGED),
+diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
+index ce81103e92..549a166abc 100644
+--- a/src/core/dbus-unit.c
++++ b/src/core/dbus-unit.c
+@@ -314,6 +314,14 @@ static int bus_verify_manage_units_async_full(
+                         error);
+ }
+ 
++static const char *const polkit_message_for_job[_JOB_TYPE_MAX] = {
++        [JOB_START]       = N_("Authentication is required to start '$(unit)'."),
++        [JOB_STOP]        = N_("Authentication is required to stop '$(unit)'."),
++        [JOB_RELOAD]      = N_("Authentication is required to reload '$(unit)'."),
++        [JOB_RESTART]     = N_("Authentication is required to restart '$(unit)'."),
++        [JOB_TRY_RESTART] = N_("Authentication is required to restart '$(unit)'."),
++};
++
+ int bus_unit_method_start_generic(
+                 sd_bus_message *message,
+                 Unit *u,
+@@ -324,13 +332,6 @@ int bus_unit_method_start_generic(
+         const char *smode;
+         JobMode mode;
+         _cleanup_free_ char *verb = NULL;
+-        static const char *const polkit_message_for_job[_JOB_TYPE_MAX] = {
+-                [JOB_START]       = N_("Authentication is required to start '$(unit)'."),
+-                [JOB_STOP]        = N_("Authentication is required to stop '$(unit)'."),
+-                [JOB_RELOAD]      = N_("Authentication is required to reload '$(unit)'."),
+-                [JOB_RESTART]     = N_("Authentication is required to restart '$(unit)'."),
+-                [JOB_TRY_RESTART] = N_("Authentication is required to restart '$(unit)'."),
+-        };
+         int r;
+ 
+         assert(message);
+@@ -372,7 +373,8 @@ int bus_unit_method_start_generic(
+         if (r == 0)
+                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+ 
+-        return bus_unit_queue_job(message, u, job_type, mode, reload_if_possible, error);
++        return bus_unit_queue_job(message, u, job_type, mode,
++                                  reload_if_possible ? BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE : 0, error);
+ }
+ 
+ static int method_start(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+@@ -403,6 +405,62 @@ static int method_reload_or_try_restart(sd_bus_message *message, void *userdata,
+         return bus_unit_method_start_generic(message, userdata, JOB_TRY_RESTART, true, error);
+ }
+ 
++int bus_unit_method_enqueue_job(sd_bus_message *message, void *userdata, sd_bus_error *error) {
++        BusUnitQueueFlags flags = BUS_UNIT_QUEUE_VERBOSE_REPLY;
++        const char *jtype, *smode;
++        Unit *u = userdata;
++        JobType type;
++        JobMode mode;
++        int r;
++
++        assert(message);
++        assert(u);
++
++        r = sd_bus_message_read(message, "ss", &jtype, &smode);
++        if (r < 0)
++                return r;
++
++        /* Parse the two magic reload types "reload-or-…" manually */
++        if (streq(jtype, "reload-or-restart")) {
++                type = JOB_RESTART;
++                flags |= BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE;
++        } else if (streq(jtype, "reload-or-try-restart")) {
++                type = JOB_TRY_RESTART;
++                flags |= BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE;
++        } else {
++                /* And the rest generically */
++                type = job_type_from_string(jtype);
++                if (type < 0)
++                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Job type %s invalid", jtype);
++        }
++
++        mode = job_mode_from_string(smode);
++        if (mode < 0)
++                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Job mode %s invalid", smode);
++
++        r = mac_selinux_unit_access_check(
++                        u, message,
++                        job_type_to_access_method(type),
++                        error);
++        if (r < 0)
++                return r;
++
++        r = bus_verify_manage_units_async_full(
++                        u,
++                        jtype,
++                        CAP_SYS_ADMIN,
++                        polkit_message_for_job[type],
++                        true,
++                        message,
++                        error);
++        if (r < 0)
++                return r;
++        if (r == 0)
++                return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
++
++        return bus_unit_queue_job(message, u, type, mode, flags, error);
++}
++
+ int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+         Unit *u = userdata;
+         const char *swho;
+@@ -722,6 +780,7 @@ const sd_bus_vtable bus_unit_vtable[] = {
+         SD_BUS_METHOD("TryRestart", "s", "o", method_try_restart, SD_BUS_VTABLE_UNPRIVILEGED),
+         SD_BUS_METHOD("ReloadOrRestart", "s", "o", method_reload_or_restart, SD_BUS_VTABLE_UNPRIVILEGED),
+         SD_BUS_METHOD("ReloadOrTryRestart", "s", "o", method_reload_or_try_restart, SD_BUS_VTABLE_UNPRIVILEGED),
++        SD_BUS_METHOD("EnqueueJob", "ss", "uososa(uosos)", bus_unit_method_enqueue_job, SD_BUS_VTABLE_UNPRIVILEGED),
+         SD_BUS_METHOD("Kill", "si", NULL, bus_unit_method_kill, SD_BUS_VTABLE_UNPRIVILEGED),
+         SD_BUS_METHOD("ResetFailed", NULL, NULL, bus_unit_method_reset_failed, SD_BUS_VTABLE_UNPRIVILEGED),
+         SD_BUS_METHOD("SetProperties", "ba(sv)", NULL, bus_unit_method_set_properties, SD_BUS_VTABLE_UNPRIVILEGED),
+@@ -1354,11 +1413,14 @@ int bus_unit_queue_job(
+                 Unit *u,
+                 JobType type,
+                 JobMode mode,
+-                bool reload_if_possible,
++                BusUnitQueueFlags flags,
+                 sd_bus_error *error) {
+ 
+-        _cleanup_free_ char *path = NULL;
+-        Job *j;
++        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
++        _cleanup_free_ char *job_path = NULL, *unit_path = NULL;
++        _cleanup_(set_freep) Set *affected = NULL;
++        Iterator i;
++        Job *j, *a;
+         int r;
+ 
+         assert(message);
+@@ -1373,7 +1435,7 @@ int bus_unit_queue_job(
+         if (r < 0)
+                 return r;
+ 
+-        if (reload_if_possible && unit_can_reload(u)) {
++        if (FLAGS_SET(flags, BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE) && unit_can_reload(u)) {
+                 if (type == JOB_RESTART)
+                         type = JOB_RELOAD_OR_START;
+                 else if (type == JOB_TRY_RESTART)
+@@ -1391,7 +1453,13 @@ int bus_unit_queue_job(
+             (type == JOB_RELOAD_OR_START && job_type_collapse(type, u) == JOB_START && u->refuse_manual_start))
+                 return sd_bus_error_setf(error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, unit %s may be requested by dependency only (it is configured to refuse manual start/stop).", u->id);
+ 
+-        r = manager_add_job(u->manager, type, u, mode, error, &j);
++        if (FLAGS_SET(flags, BUS_UNIT_QUEUE_VERBOSE_REPLY)) {
++                affected = set_new(NULL);
++                if (!affected)
++                        return -ENOMEM;
++        }
++
++        r = manager_add_job(u->manager, type, u, mode, affected, error, &j);
+         if (r < 0)
+                 return r;
+ 
+@@ -1399,11 +1467,64 @@ int bus_unit_queue_job(
+         if (r < 0)
+                 return r;
+ 
+-        path = job_dbus_path(j);
+-        if (!path)
++        job_path = job_dbus_path(j);
++        if (!job_path)
+                 return -ENOMEM;
+ 
+-        return sd_bus_reply_method_return(message, "o", path);
++        /* The classic response is just a job object path */
++        if (!FLAGS_SET(flags, BUS_UNIT_QUEUE_VERBOSE_REPLY))
++                return sd_bus_reply_method_return(message, "o", job_path);
++
++        /* In verbose mode respond with the anchor job plus everything that has been affected */
++        r = sd_bus_message_new_method_return(message, &reply);
++        if (r < 0)
++                return r;
++
++        unit_path = unit_dbus_path(j->unit);
++        if (!unit_path)
++                return -ENOMEM;
++
++        r = sd_bus_message_append(reply, "uosos",
++                                  j->id, job_path,
++                                  j->unit->id, unit_path,
++                                  job_type_to_string(j->type));
++        if (r < 0)
++                return r;
++
++        r = sd_bus_message_open_container(reply, 'a', "(uosos)");
++        if (r < 0)
++                return r;
++
++        SET_FOREACH(a, affected, i) {
++
++                if (a->id == j->id)
++                        continue;
++
++                /* Free paths from previous iteration */
++                job_path = mfree(job_path);
++                unit_path = mfree(unit_path);
++
++                job_path = job_dbus_path(a);
++                if (!job_path)
++                        return -ENOMEM;
++
++                unit_path = unit_dbus_path(a->unit);
++                if (!unit_path)
++                        return -ENOMEM;
++
++                r = sd_bus_message_append(reply, "(uosos)",
++                                          a->id, job_path,
++                                          a->unit->id, unit_path,
++                                          job_type_to_string(a->type));
++                if (r < 0)
++                        return r;
++        }
++
++        r = sd_bus_message_close_container(reply);
++        if (r < 0)
++                return r;
++
++        return sd_bus_send(NULL, reply, NULL);
+ }
+ 
+ static int bus_unit_set_live_property(
+diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h
+index 39aa1bb53c..d298fcc99e 100644
+--- a/src/core/dbus-unit.h
++++ b/src/core/dbus-unit.h
+@@ -15,6 +15,7 @@ int bus_unit_send_pending_freezer_message(Unit *u);
+ void bus_unit_send_removed_signal(Unit *u);
+ 
+ int bus_unit_method_start_generic(sd_bus_message *message, Unit *u, JobType job_type, bool reload_if_possible, sd_bus_error *error);
++int bus_unit_method_enqueue_job(sd_bus_message *message, void *userdata, sd_bus_error *error);
+ int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error);
+ int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error);
+ 
+@@ -27,7 +28,12 @@ int bus_unit_method_unref(sd_bus_message *message, void *userdata, sd_bus_error
+ int bus_unit_method_freeze(sd_bus_message *message, void *userdata, sd_bus_error *error);
+ int bus_unit_method_thaw(sd_bus_message *message, void *userdata, sd_bus_error *error);
+ 
+-int bus_unit_queue_job(sd_bus_message *message, Unit *u, JobType type, JobMode mode, bool reload_if_possible, sd_bus_error *error);
++typedef enum BusUnitQueueFlags {
++        BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE = 1 << 0,
++        BUS_UNIT_QUEUE_VERBOSE_REPLY      = 1 << 1,
++} BusUnitQueueFlags;
++
++int bus_unit_queue_job(sd_bus_message *message, Unit *u, JobType type, JobMode mode, BusUnitQueueFlags flags, sd_bus_error *error);
+ int bus_unit_validate_load_state(Unit *u, sd_bus_error *error);
+ 
+ int bus_unit_track_add_name(Unit *u, const char *name);
+diff --git a/src/core/dbus.c b/src/core/dbus.c
+index b69c11c519..584a8a1b01 100644
+--- a/src/core/dbus.c
++++ b/src/core/dbus.c
+@@ -176,7 +176,7 @@ static int signal_activation_request(sd_bus_message *message, void *userdata, sd
+                 goto failed;
+         }
+ 
+-        r = manager_add_job(m, JOB_START, u, JOB_REPLACE, &error, NULL);
++        r = manager_add_job(m, JOB_START, u, JOB_REPLACE, NULL, &error, NULL);
+         if (r < 0)
+                 goto failed;
+ 
+diff --git a/src/core/device.c b/src/core/device.c
+index 021c28dfbd..cb8b66dfc5 100644
+--- a/src/core/device.c
++++ b/src/core/device.c
+@@ -419,7 +419,7 @@ static int device_add_udev_wants(Unit *u, struct udev_device *dev) {
+                         if (strv_contains(d->wants_property, *i)) /* Was this unit already listed before? */
+                                 continue;
+ 
+-                        r = manager_add_job_by_name(u->manager, JOB_START, *i, JOB_FAIL, &error, NULL);
++                        r = manager_add_job_by_name(u->manager, JOB_START, *i, JOB_FAIL, NULL, &error, NULL);
+                         if (r < 0)
+                                 log_unit_warning_errno(u, r, "Failed to enqueue SYSTEMD_WANTS= job, ignoring: %s", bus_error_message(&error, r));
+                 }
+diff --git a/src/core/emergency-action.c b/src/core/emergency-action.c
+index 76e1124cff..766a3b4d2b 100644
+--- a/src/core/emergency-action.c
++++ b/src/core/emergency-action.c
+@@ -54,8 +54,7 @@ int emergency_action(
+                 log_and_status(m, "Rebooting", reason);
+ 
+                 (void) update_reboot_parameter_and_warn(reboot_arg);
+-                (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL);
+-
++                (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL);
+                 break;
+ 
+         case EMERGENCY_ACTION_REBOOT_FORCE:
+@@ -83,7 +82,7 @@ int emergency_action(
+ 
+         case EMERGENCY_ACTION_POWEROFF:
+                 log_and_status(m, "Powering off", reason);
+-                (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_POWEROFF_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL);
++                (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_POWEROFF_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL);
+                 break;
+ 
+         case EMERGENCY_ACTION_POWEROFF_FORCE:
+diff --git a/src/core/main.c b/src/core/main.c
+index 25536054b3..d897155644 100644
+--- a/src/core/main.c
++++ b/src/core/main.c
+@@ -1952,13 +1952,13 @@ static int do_queue_default_job(
+ 
+         assert(target->load_state == UNIT_LOADED);
+ 
+-        r = manager_add_job(m, JOB_START, target, JOB_ISOLATE, &error, &default_unit_job);
++        r = manager_add_job(m, JOB_START, target, JOB_ISOLATE, NULL, &error, &default_unit_job);
+         if (r == -EPERM) {
+                 log_debug_errno(r, "Default target could not be isolated, starting instead: %s", bus_error_message(&error, r));
+ 
+                 sd_bus_error_free(&error);
+ 
+-                r = manager_add_job(m, JOB_START, target, JOB_REPLACE, &error, &default_unit_job);
++                r = manager_add_job(m, JOB_START, target, JOB_REPLACE, NULL, &error, &default_unit_job);
+                 if (r < 0) {
+                         *ret_error_message = "Failed to start default target";
+                         return log_emergency_errno(r, "Failed to start default target: %s", bus_error_message(&error, r));
+diff --git a/src/core/manager.c b/src/core/manager.c
+index 4c04896aaa..012615e537 100644
+--- a/src/core/manager.c
++++ b/src/core/manager.c
+@@ -1242,7 +1242,7 @@ static unsigned manager_dispatch_stop_when_unneeded_queue(Manager *m) {
+                 }
+ 
+                 /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */
+-                r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL);
++                r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, NULL, &error, NULL);
+                 if (r < 0)
+                         log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r));
+         }
+@@ -1685,9 +1685,17 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
+         return 0;
+ }
+ 
+-int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_error *e, Job **_ret) {
+-        int r;
++int manager_add_job(
++                Manager *m,
++                JobType type,
++                Unit *unit,
++                JobMode mode,
++                Set *affected_jobs,
++                sd_bus_error *error,
++                Job **ret) {
++
+         Transaction *tr;
++        int r;
+ 
+         assert(m);
+         assert(type < _JOB_TYPE_MAX);
+@@ -1695,10 +1703,10 @@ int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_e
+         assert(mode < _JOB_MODE_MAX);
+ 
+         if (mode == JOB_ISOLATE && type != JOB_START)
+-                return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Isolate is only valid for start.");
++                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Isolate is only valid for start.");
+ 
+         if (mode == JOB_ISOLATE && !unit->allow_isolate)
+-                return sd_bus_error_setf(e, BUS_ERROR_NO_ISOLATION, "Operation refused, unit may not be isolated.");
++                return sd_bus_error_setf(error, BUS_ERROR_NO_ISOLATION, "Operation refused, unit may not be isolated.");
+ 
+         log_unit_debug(unit, "Trying to enqueue job %s/%s/%s", unit->id, job_type_to_string(type), job_mode_to_string(mode));
+ 
+@@ -1710,7 +1718,7 @@ int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_e
+ 
+         r = transaction_add_job_and_dependencies(tr, type, unit, NULL, true, false,
+                                                  IN_SET(mode, JOB_IGNORE_DEPENDENCIES, JOB_IGNORE_REQUIREMENTS),
+-                                                 mode == JOB_IGNORE_DEPENDENCIES, e);
++                                                 mode == JOB_IGNORE_DEPENDENCIES, error);
+         if (r < 0)
+                 goto tr_abort;
+ 
+@@ -1720,7 +1728,7 @@ int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_e
+                         goto tr_abort;
+         }
+ 
+-        r = transaction_activate(tr, m, mode, e);
++        r = transaction_activate(tr, m, mode, affected_jobs, error);
+         if (r < 0)
+                 goto tr_abort;
+ 
+@@ -1728,8 +1736,8 @@ int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_e
+                        "Enqueued job %s/%s as %u", unit->id,
+                        job_type_to_string(type), (unsigned) tr->anchor_job->id);
+ 
+-        if (_ret)
+-                *_ret = tr->anchor_job;
++        if (ret)
++                *ret = tr->anchor_job;
+ 
+         transaction_free(tr);
+         return 0;
+@@ -1740,7 +1748,7 @@ tr_abort:
+         return r;
+ }
+ 
+-int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, sd_bus_error *e, Job **ret) {
++int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, Set *affected_jobs, sd_bus_error *e, Job **ret) {
+         Unit *unit = NULL;  /* just to appease gcc, initialization is not really necessary */
+         int r;
+ 
+@@ -1754,10 +1762,10 @@ int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode
+                 return r;
+         assert(unit);
+ 
+-        return manager_add_job(m, type, unit, mode, e, ret);
++        return manager_add_job(m, type, unit, mode, affected_jobs, e, ret);
+ }
+ 
+-int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Job **ret) {
++int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Set *affected_jobs, Job **ret) {
+         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+         int r;
+ 
+@@ -1766,7 +1774,7 @@ int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name,
+         assert(name);
+         assert(mode < _JOB_MODE_MAX);
+ 
+-        r = manager_add_job_by_name(m, type, name, mode, &error, ret);
++        r = manager_add_job_by_name(m, type, name, mode, affected_jobs, &error, ret);
+         if (r < 0)
+                 return log_warning_errno(r, "Failed to enqueue %s job for %s: %s", job_mode_to_string(mode), name, bus_error_message(&error, r));
+ 
+@@ -1794,7 +1802,7 @@ int manager_propagate_reload(Manager *m, Unit *unit, JobMode mode, sd_bus_error
+         /* Failure in adding individual dependencies is ignored, so this always succeeds. */
+         transaction_add_propagate_reload_jobs(tr, unit, tr->anchor_job, mode == JOB_IGNORE_DEPENDENCIES, e);
+ 
+-        r = transaction_activate(tr, m, mode, e);
++        r = transaction_activate(tr, m, mode, NULL, e);
+         if (r < 0)
+                 goto tr_abort;
+ 
+@@ -2512,7 +2520,7 @@ static void manager_start_target(Manager *m, const char *name, JobMode mode) {
+ 
+         log_debug("Activating special unit %s", name);
+ 
+-        r = manager_add_job_by_name(m, JOB_START, name, mode, &error, NULL);
++        r = manager_add_job_by_name(m, JOB_START, name, mode, NULL, &error, NULL);
+         if (r < 0)
+                 log_error("Failed to enqueue %s job: %s", name, bus_error_message(&error, r));
+ }
+diff --git a/src/core/manager.h b/src/core/manager.h
+index 40568d3c8b..c4b8e80093 100644
+--- a/src/core/manager.h
++++ b/src/core/manager.h
+@@ -397,9 +397,9 @@ int manager_load_unit(Manager *m, const char *name, const char *path, sd_bus_err
+ int manager_load_startable_unit_or_warn(Manager *m, const char *name, const char *path, Unit **ret);
+ int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u);
+ 
+-int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_error *e, Job **_ret);
+-int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, sd_bus_error *e, Job **_ret);
+-int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Job **ret);
++int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, Set *affected_jobs, sd_bus_error *e, Job **_ret);
++int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, Set *affected_jobs, sd_bus_error *e, Job **_ret);
++int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Set *affected_jobs,  Job **ret);
+ int manager_propagate_reload(Manager *m, Unit *unit, JobMode mode, sd_bus_error *e);
+ 
+ void manager_dump_units(Manager *s, FILE *f, const char *prefix);
+diff --git a/src/core/path.c b/src/core/path.c
+index dda4a3036b..ed40bc6c19 100644
+--- a/src/core/path.c
++++ b/src/core/path.c
+@@ -474,7 +474,7 @@ static void path_enter_running(Path *p) {
+                 return;
+         }
+ 
+-        r = manager_add_job(UNIT(p)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL);
++        r = manager_add_job(UNIT(p)->manager, JOB_START, trigger, JOB_REPLACE, NULL, &error, NULL);
+         if (r < 0)
+                 goto fail;
+ 
+diff --git a/src/core/service.c b/src/core/service.c
+index 7cff419e4e..5e3e75b5ae 100644
+--- a/src/core/service.c
++++ b/src/core/service.c
+@@ -2176,7 +2176,7 @@ static void service_enter_restart(Service *s) {
+          * restarted. We use JOB_RESTART (instead of the more obvious
+          * JOB_START) here so that those dependency jobs will be added
+          * as well. */
+-        r = manager_add_job(UNIT(s)->manager, JOB_RESTART, UNIT(s), JOB_REPLACE, &error, NULL);
++        r = manager_add_job(UNIT(s)->manager, JOB_RESTART, UNIT(s), JOB_REPLACE, NULL, &error, NULL);
+         if (r < 0)
+                 goto fail;
+ 
+diff --git a/src/core/socket.c b/src/core/socket.c
+index 7c6d3dfad1..fe061eb73b 100644
+--- a/src/core/socket.c
++++ b/src/core/socket.c
+@@ -2274,7 +2274,7 @@ static void socket_enter_running(Socket *s, int cfd) {
+                                 goto fail;
+                         }
+ 
+-                        r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT_DEREF(s->service), JOB_REPLACE, &error, NULL);
++                        r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT_DEREF(s->service), JOB_REPLACE, NULL, &error, NULL);
+                         if (r < 0)
+                                 goto fail;
+                 }
+@@ -2349,7 +2349,7 @@ static void socket_enter_running(Socket *s, int cfd) {
+ 
+                 service->peer = TAKE_PTR(p); /* Pass ownership of the peer reference */
+ 
+-                r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, &error, NULL);
++                r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, NULL, &error, NULL);
+                 if (r < 0) {
+                         /* We failed to activate the new service, but it still exists. Let's make sure the service
+                          * closes and forgets the connection fd again, immediately. */
+diff --git a/src/core/timer.c b/src/core/timer.c
+index 2876d54a59..281ac7f97f 100644
+--- a/src/core/timer.c
++++ b/src/core/timer.c
+@@ -566,7 +566,7 @@ static void timer_enter_running(Timer *t) {
+                 return;
+         }
+ 
+-        r = manager_add_job(UNIT(t)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL);
++        r = manager_add_job(UNIT(t)->manager, JOB_START, trigger, JOB_REPLACE, NULL, &error, NULL);
+         if (r < 0)
+                 goto fail;
+ 
+diff --git a/src/core/transaction.c b/src/core/transaction.c
+index 045930838b..cdaaff4f55 100644
+--- a/src/core/transaction.c
++++ b/src/core/transaction.c
+@@ -585,7 +585,12 @@ rescan:
+         }
+ }
+ 
+-static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
++static int transaction_apply(
++                Transaction *tr,
++                Manager *m,
++                JobMode mode,
++                Set *affected_jobs) {
++
+         Iterator i;
+         Job *j;
+         int r;
+@@ -642,6 +647,11 @@ static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
+                 job_add_to_dbus_queue(j);
+                 job_start_timer(j, false);
+                 job_shutdown_magic(j);
++
++                /* When 'affected' is specified, let's track all in it all jobs that were touched because of
++                 * this transaction. */
++                if (affected_jobs)
++                        (void) set_put(affected_jobs, j);
+         }
+ 
+         return 0;
+@@ -654,7 +664,13 @@ rollback:
+         return r;
+ }
+ 
+-int transaction_activate(Transaction *tr, Manager *m, JobMode mode, sd_bus_error *e) {
++int transaction_activate(
++                Transaction *tr,
++                Manager *m,
++                JobMode mode,
++                Set *affected_jobs,
++                sd_bus_error *e) {
++
+         Iterator i;
+         Job *j;
+         int r;
+@@ -731,7 +747,7 @@ int transaction_activate(Transaction *tr, Manager *m, JobMode mode, sd_bus_error
+                 return log_notice_errno(r, "Requested transaction contradicts existing jobs: %s", bus_error_message(e, r));
+ 
+         /* Tenth step: apply changes */
+-        r = transaction_apply(tr, m, mode);
++        r = transaction_apply(tr, m, mode, affected_jobs);
+         if (r < 0)
+                 return log_warning_errno(r, "Failed to apply transaction: %m");
+ 
+diff --git a/src/core/transaction.h b/src/core/transaction.h
+index 70d74a4ccb..4b5620f5c8 100644
+--- a/src/core/transaction.h
++++ b/src/core/transaction.h
+@@ -29,6 +29,6 @@ int transaction_add_job_and_dependencies(
+                 bool ignore_requirements,
+                 bool ignore_order,
+                 sd_bus_error *e);
+-int transaction_activate(Transaction *tr, Manager *m, JobMode mode, sd_bus_error *e);
++int transaction_activate(Transaction *tr, Manager *m, JobMode mode, Set *affected, sd_bus_error *e);
+ int transaction_add_isolate_jobs(Transaction *tr, Manager *m);
+ void transaction_abort(Transaction *tr);
+diff --git a/src/core/unit.c b/src/core/unit.c
+index 29ce6c1fb7..ffbf3cfd48 100644
+--- a/src/core/unit.c
++++ b/src/core/unit.c
+@@ -2033,7 +2033,7 @@ static void unit_check_binds_to(Unit *u) {
+         log_unit_info(u, "Unit is bound to inactive unit %s. Stopping, too.", other->id);
+ 
+         /* A unit we need to run is gone. Sniff. Let's stop this. */
+-        r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL);
++        r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, NULL, &error, NULL);
+         if (r < 0)
+                 log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r));
+ }
+@@ -2049,25 +2049,25 @@ static void retroactively_start_dependencies(Unit *u) {
+         HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REQUIRES], i)
+                 if (!hashmap_get(u->dependencies[UNIT_AFTER], other) &&
+                     !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
+-                        manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL);
++                        manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL, NULL);
+ 
+         HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BINDS_TO], i)
+                 if (!hashmap_get(u->dependencies[UNIT_AFTER], other) &&
+                     !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
+-                        manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL);
++                        manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL, NULL);
+ 
+         HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_WANTS], i)
+                 if (!hashmap_get(u->dependencies[UNIT_AFTER], other) &&
+                     !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
+-                        manager_add_job(u->manager, JOB_START, other, JOB_FAIL, NULL, NULL);
++                        manager_add_job(u->manager, JOB_START, other, JOB_FAIL, NULL, NULL, NULL);
+ 
+         HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_CONFLICTS], i)
+                 if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+-                        manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL);
++                        manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL, NULL);
+ 
+         HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_CONFLICTED_BY], i)
+                 if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+-                        manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL);
++                        manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL, NULL);
+ }
+ 
+ static void retroactively_stop_dependencies(Unit *u) {
+@@ -2081,7 +2081,7 @@ static void retroactively_stop_dependencies(Unit *u) {
+         /* Pull down units which are bound to us recursively if enabled */
+         HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BOUND_BY], i)
+                 if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+-                        manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL);
++                        manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL, NULL);
+ }
+ 
+ void unit_start_on_failure(Unit *u) {
+@@ -2100,7 +2100,7 @@ void unit_start_on_failure(Unit *u) {
+         HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_ON_FAILURE], i) {
+                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ 
+-                r = manager_add_job(u->manager, JOB_START, other, u->on_failure_job_mode, &error, NULL);
++                r = manager_add_job(u->manager, JOB_START, other, u->on_failure_job_mode, NULL, &error, NULL);
+                 if (r < 0)
+                         log_unit_warning_errno(u, r, "Failed to enqueue OnFailure= job, ignoring: %s", bus_error_message(&error, r));
+         }
+diff --git a/src/test/test-engine.c b/src/test/test-engine.c
+index 0f3e244dc1..f2e327b3f5 100644
+--- a/src/test/test-engine.c
++++ b/src/test/test-engine.c
+@@ -46,7 +46,7 @@ int main(int argc, char *argv[]) {
+         manager_dump_units(m, stdout, "\t");
+ 
+         printf("Test1: (Trivial)\n");
+-        r = manager_add_job(m, JOB_START, c, JOB_REPLACE, &err, &j);
++        r = manager_add_job(m, JOB_START, c, JOB_REPLACE, NULL, &err, &j);
+         if (sd_bus_error_is_set(&err))
+                 log_error("error: %s: %s", err.name, err.message);
+         assert_se(r == 0);
+@@ -59,15 +59,15 @@ int main(int argc, char *argv[]) {
+         manager_dump_units(m, stdout, "\t");
+ 
+         printf("Test2: (Cyclic Order, Unfixable)\n");
+-        assert_se(manager_add_job(m, JOB_START, d, JOB_REPLACE, NULL, &j) == -EDEADLK);
++        assert_se(manager_add_job(m, JOB_START, d, JOB_REPLACE, NULL, NULL, &j) == -EDEADLK);
+         manager_dump_jobs(m, stdout, "\t");
+ 
+         printf("Test3: (Cyclic Order, Fixable, Garbage Collector)\n");
+-        assert_se(manager_add_job(m, JOB_START, e, JOB_REPLACE, NULL, &j) == 0);
++        assert_se(manager_add_job(m, JOB_START, e, JOB_REPLACE, NULL, NULL, &j) == 0);
+         manager_dump_jobs(m, stdout, "\t");
+ 
+         printf("Test4: (Identical transaction)\n");
+-        assert_se(manager_add_job(m, JOB_START, e, JOB_FAIL, NULL, &j) == 0);
++        assert_se(manager_add_job(m, JOB_START, e, JOB_FAIL, NULL, NULL, &j) == 0);
+         manager_dump_jobs(m, stdout, "\t");
+ 
+         printf("Load3:\n");
+@@ -75,21 +75,21 @@ int main(int argc, char *argv[]) {
+         manager_dump_units(m, stdout, "\t");
+ 
+         printf("Test5: (Colliding transaction, fail)\n");
+-        assert_se(manager_add_job(m, JOB_START, g, JOB_FAIL, NULL, &j) == -EDEADLK);
++        assert_se(manager_add_job(m, JOB_START, g, JOB_FAIL, NULL, NULL, &j) == -EDEADLK);
+ 
+         printf("Test6: (Colliding transaction, replace)\n");
+-        assert_se(manager_add_job(m, JOB_START, g, JOB_REPLACE, NULL, &j) == 0);
++        assert_se(manager_add_job(m, JOB_START, g, JOB_REPLACE, NULL, NULL, &j) == 0);
+         manager_dump_jobs(m, stdout, "\t");
+ 
+         printf("Test7: (Unmergeable job type, fail)\n");
+-        assert_se(manager_add_job(m, JOB_STOP, g, JOB_FAIL, NULL, &j) == -EDEADLK);
++        assert_se(manager_add_job(m, JOB_STOP, g, JOB_FAIL, NULL, NULL, &j) == -EDEADLK);
+ 
+         printf("Test8: (Mergeable job type, fail)\n");
+-        assert_se(manager_add_job(m, JOB_RESTART, g, JOB_FAIL, NULL, &j) == 0);
++        assert_se(manager_add_job(m, JOB_RESTART, g, JOB_FAIL, NULL, NULL, &j) == 0);
+         manager_dump_jobs(m, stdout, "\t");
+ 
+         printf("Test9: (Unmergeable job type, replace)\n");
+-        assert_se(manager_add_job(m, JOB_STOP, g, JOB_REPLACE, NULL, &j) == 0);
++        assert_se(manager_add_job(m, JOB_STOP, g, JOB_REPLACE, NULL, NULL, &j) == 0);
+         manager_dump_jobs(m, stdout, "\t");
+ 
+         printf("Load4:\n");
+@@ -97,7 +97,7 @@ int main(int argc, char *argv[]) {
+         manager_dump_units(m, stdout, "\t");
+ 
+         printf("Test10: (Unmergeable job type of auxiliary job, fail)\n");
+-        assert_se(manager_add_job(m, JOB_START, h, JOB_FAIL, NULL, &j) == 0);
++        assert_se(manager_add_job(m, JOB_START, h, JOB_FAIL, NULL, NULL, &j) == 0);
+         manager_dump_jobs(m, stdout, "\t");
+ 
+         assert_se(!hashmap_get(a->dependencies[UNIT_PROPAGATES_RELOAD_TO], b));
diff --git a/SOURCES/0445-systemctl-replace-switch-statement-by-table-of-struc.patch b/SOURCES/0445-systemctl-replace-switch-statement-by-table-of-struc.patch
new file mode 100644
index 0000000..b95326c
--- /dev/null
+++ b/SOURCES/0445-systemctl-replace-switch-statement-by-table-of-struc.patch
@@ -0,0 +1,115 @@
+From 8b34041ee97069bee8bf03ae5ba651b34b1b8460 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Tue, 26 Mar 2019 15:20:26 +0100
+Subject: [PATCH] systemctl: replace switch statement by table of structures
+
+(cherry picked from commit c45e5fb877033c9e3f9b79121644ed71032af379)
+
+Related: #846319
+---
+ src/systemctl/systemctl.c | 68 ++++++++++++---------------------------
+ 1 file changed, 21 insertions(+), 47 deletions(-)
+
+diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
+index e963f19b0a..04e24691d8 100644
+--- a/src/systemctl/systemctl.c
++++ b/src/systemctl/systemctl.c
+@@ -3179,64 +3179,38 @@ static int logind_set_wall_message(void) {
+ }
+ #endif
+ 
+-/* Ask systemd-logind, which might grant access to unprivileged users
+- * through PolicyKit */
++/* Ask systemd-logind, which might grant access to unprivileged users through polkit */
+ static int logind_reboot(enum action a) {
+ #if ENABLE_LOGIND
++        static const struct {
++                const char *method;
++                const char *description;
++        } actions[_ACTION_MAX] = {
++                [ACTION_POWEROFF]               = { "PowerOff",             "power off system"                },
++                [ACTION_REBOOT]                 = { "Reboot",               "reboot system"                   },
++                [ACTION_HALT]                   = { "Halt",                 "halt system"                     },
++                [ACTION_SUSPEND]                = { "Suspend",              "suspend system"                  },
++                [ACTION_HIBERNATE]              = { "Hibernate",            "hibernate system"                },
++                [ACTION_HYBRID_SLEEP]           = { "HybridSleep",          "put system into hybrid sleep"    },
++                [ACTION_SUSPEND_THEN_HIBERNATE] = { "SuspendThenHibernate", "suspend system, hibernate later" },
++        };
++
+         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+-        const char *method, *description;
+         sd_bus *bus;
+         int r;
+ 
++        if (a < 0 || a >= _ACTION_MAX || !actions[a].method)
++                return -EINVAL;
++
+         r = acquire_bus(BUS_FULL, &bus);
+         if (r < 0)
+                 return r;
+ 
+-        switch (a) {
+-
+-        case ACTION_POWEROFF:
+-                method = "PowerOff";
+-                description = "power off system";
+-                break;
+-
+-        case ACTION_REBOOT:
+-                method = "Reboot";
+-                description = "reboot system";
+-                break;
+-
+-        case ACTION_HALT:
+-                method = "Halt";
+-                description = "halt system";
+-                break;
+-
+-        case ACTION_SUSPEND:
+-                method = "Suspend";
+-                description = "suspend system";
+-                break;
+-
+-        case ACTION_HIBERNATE:
+-                method = "Hibernate";
+-                description = "hibernate system";
+-                break;
+-
+-        case ACTION_HYBRID_SLEEP:
+-                method = "HybridSleep";
+-                description = "put system into hybrid sleep";
+-                break;
+-
+-        case ACTION_SUSPEND_THEN_HIBERNATE:
+-                method = "SuspendThenHibernate";
+-                description = "put system into suspend followed by hibernate";
+-                break;
+-
+-        default:
+-                return -EINVAL;
+-        }
+-
+         polkit_agent_open_maybe();
+         (void) logind_set_wall_message();
+ 
+-        log_debug("%s org.freedesktop.login1.Manager %s dbus call.", arg_dry_run ? "Would execute" : "Executing", method);
++        log_debug("%s org.freedesktop.login1.Manager %s dbus call.", arg_dry_run ? "Would execute" : "Executing", actions[a].method);
++
+         if (arg_dry_run)
+                 return 0;
+ 
+@@ -3245,12 +3219,12 @@ static int logind_reboot(enum action a) {
+                         "org.freedesktop.login1",
+                         "/org/freedesktop/login1",
+                         "org.freedesktop.login1.Manager",
+-                        method,
++                        actions[a].method,
+                         &error,
+                         NULL,
+                         "b", arg_ask_password);
+         if (r < 0)
+-                return log_error_errno(r, "Failed to %s via logind: %s", description, bus_error_message(&error, r));
++                return log_error_errno(r, "Failed to %s via logind: %s", actions[a].description, bus_error_message(&error, r));
+ 
+         return 0;
+ #else
diff --git a/SOURCES/0446-systemctl-reindent-table.patch b/SOURCES/0446-systemctl-reindent-table.patch
new file mode 100644
index 0000000..22ad788
--- /dev/null
+++ b/SOURCES/0446-systemctl-reindent-table.patch
@@ -0,0 +1,53 @@
+From 26d2d89c6216672cedf6abfe1b73081adebbdcf8 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Tue, 26 Mar 2019 15:49:52 +0100
+Subject: [PATCH] systemctl: reindent table
+
+(cherry picked from commit 5fd77930ad9980af5257f9f871556d6973db736c)
+
+Related: #846319
+---
+ src/systemctl/systemctl.c | 30 +++++++++++++++---------------
+ 1 file changed, 15 insertions(+), 15 deletions(-)
+
+diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
+index 04e24691d8..f057dc829c 100644
+--- a/src/systemctl/systemctl.c
++++ b/src/systemctl/systemctl.c
+@@ -2974,21 +2974,21 @@ static const struct {
+         const char *verb;
+         const char *mode;
+ } action_table[_ACTION_MAX] = {
+-        [ACTION_HALT]                 = { SPECIAL_HALT_TARGET,                     "halt",                   "replace-irreversibly" },
+-        [ACTION_POWEROFF]             = { SPECIAL_POWEROFF_TARGET,                 "poweroff",               "replace-irreversibly" },
+-        [ACTION_REBOOT]               = { SPECIAL_REBOOT_TARGET,                   "reboot",                 "replace-irreversibly" },
+-        [ACTION_KEXEC]                = { SPECIAL_KEXEC_TARGET,                    "kexec",                  "replace-irreversibly" },
+-        [ACTION_RUNLEVEL2]            = { SPECIAL_MULTI_USER_TARGET,               NULL,                     "isolate" },
+-        [ACTION_RUNLEVEL3]            = { SPECIAL_MULTI_USER_TARGET,               NULL,                     "isolate" },
+-        [ACTION_RUNLEVEL4]            = { SPECIAL_MULTI_USER_TARGET,               NULL,                     "isolate" },
+-        [ACTION_RUNLEVEL5]            = { SPECIAL_GRAPHICAL_TARGET,                NULL,                     "isolate" },
+-        [ACTION_RESCUE]               = { SPECIAL_RESCUE_TARGET,                   "rescue",                 "isolate" },
+-        [ACTION_EMERGENCY]            = { SPECIAL_EMERGENCY_TARGET,                "emergency",              "isolate" },
+-        [ACTION_DEFAULT]              = { SPECIAL_DEFAULT_TARGET,                  "default",                "isolate" },
+-        [ACTION_EXIT]                 = { SPECIAL_EXIT_TARGET,                     "exit",                   "replace-irreversibly" },
+-        [ACTION_SUSPEND]              = { SPECIAL_SUSPEND_TARGET,                  "suspend",                "replace-irreversibly" },
+-        [ACTION_HIBERNATE]            = { SPECIAL_HIBERNATE_TARGET,                "hibernate",              "replace-irreversibly" },
+-        [ACTION_HYBRID_SLEEP]         = { SPECIAL_HYBRID_SLEEP_TARGET,             "hybrid-sleep",           "replace-irreversibly" },
++        [ACTION_HALT]                   = { SPECIAL_HALT_TARGET,                   "halt",                   "replace-irreversibly" },
++        [ACTION_POWEROFF]               = { SPECIAL_POWEROFF_TARGET,               "poweroff",               "replace-irreversibly" },
++        [ACTION_REBOOT]                 = { SPECIAL_REBOOT_TARGET,                 "reboot",                 "replace-irreversibly" },
++        [ACTION_KEXEC]                  = { SPECIAL_KEXEC_TARGET,                  "kexec",                  "replace-irreversibly" },
++        [ACTION_RUNLEVEL2]              = { SPECIAL_MULTI_USER_TARGET,             NULL,                     "isolate"              },
++        [ACTION_RUNLEVEL3]              = { SPECIAL_MULTI_USER_TARGET,             NULL,                     "isolate"              },
++        [ACTION_RUNLEVEL4]              = { SPECIAL_MULTI_USER_TARGET,             NULL,                     "isolate"              },
++        [ACTION_RUNLEVEL5]              = { SPECIAL_GRAPHICAL_TARGET,              NULL,                     "isolate"              },
++        [ACTION_RESCUE]                 = { SPECIAL_RESCUE_TARGET,                 "rescue",                 "isolate"              },
++        [ACTION_EMERGENCY]              = { SPECIAL_EMERGENCY_TARGET,              "emergency",              "isolate"              },
++        [ACTION_DEFAULT]                = { SPECIAL_DEFAULT_TARGET,                "default",                "isolate"              },
++        [ACTION_EXIT]                   = { SPECIAL_EXIT_TARGET,                   "exit",                   "replace-irreversibly" },
++        [ACTION_SUSPEND]                = { SPECIAL_SUSPEND_TARGET,                "suspend",                "replace-irreversibly" },
++        [ACTION_HIBERNATE]              = { SPECIAL_HIBERNATE_TARGET,              "hibernate",              "replace-irreversibly" },
++        [ACTION_HYBRID_SLEEP]           = { SPECIAL_HYBRID_SLEEP_TARGET,           "hybrid-sleep",           "replace-irreversibly" },
+         [ACTION_SUSPEND_THEN_HIBERNATE] = { SPECIAL_SUSPEND_THEN_HIBERNATE_TARGET, "suspend-then-hibernate", "replace-irreversibly" },
+ };
+ 
diff --git a/SOURCES/0447-systemctl-Only-wait-when-there-s-something-to-wait-f.patch b/SOURCES/0447-systemctl-Only-wait-when-there-s-something-to-wait-f.patch
new file mode 100644
index 0000000..2cd38d4
--- /dev/null
+++ b/SOURCES/0447-systemctl-Only-wait-when-there-s-something-to-wait-f.patch
@@ -0,0 +1,29 @@
+From 91c83bde0904581fbc33eb7821119e665b9505ce Mon Sep 17 00:00:00 2001
+From: Filipe Brandenburger <filbranden@google.com>
+Date: Fri, 20 Jul 2018 11:32:55 -0700
+Subject: [PATCH] systemctl: Only wait when there's something to wait for.
+
+Tested:
+- `systemctl --wait start i-do-not-exist.service` does not wait.
+- `systemctl --wait start i-do-not-exist.service valid-unit.service` does.
+
+(cherry picked from commit 46f2579c2ac9f6780d5afec1000764defc6b581e)
+
+Related: #846319
+---
+ src/systemctl/systemctl.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
+index f057dc829c..1929692480 100644
+--- a/src/systemctl/systemctl.c
++++ b/src/systemctl/systemctl.c
+@@ -3130,7 +3130,7 @@ static int start_unit(int argc, char *argv[], void *userdata) {
+                                 check_triggering_units(bus, *name);
+         }
+ 
+-        if (r >= 0 && arg_wait) {
++        if (r >= 0 && arg_wait && !set_isempty(wait_context.unit_paths)) {
+                 int q;
+                 q = sd_event_loop(wait_context.event);
+                 if (q < 0)
diff --git a/SOURCES/0448-systemctl-clean-up-start_unit_one-error-handling.patch b/SOURCES/0448-systemctl-clean-up-start_unit_one-error-handling.patch
new file mode 100644
index 0000000..ffd9a76
--- /dev/null
+++ b/SOURCES/0448-systemctl-clean-up-start_unit_one-error-handling.patch
@@ -0,0 +1,99 @@
+From 7569756d005d4f780fffd2504eb6f461982833f2 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Sat, 13 Oct 2018 14:38:46 +0200
+Subject: [PATCH] systemctl: clean up start_unit_one() error handling
+
+Let's split exit code handling in two: "r" is only used for errno-style
+errors, and "ret" is used for exit() codes. Then, let's use EXIT_SUCCESS
+for checking whether the latter is already used.
+
+This way it should always be clear what kind of error we are processing,
+and when we propaate one into the other.
+
+Moreover this allows us to drop "q" form all inner loops, avoiding
+confusion when to use "q" and when "r" to store received errors.
+
+Fixes: #9704
+(cherry picked from commit 0e8d9c0c4d7e71487c486f626c59853cfb031d16)
+
+Related: #846319
+---
+ src/systemctl/systemctl.c | 32 +++++++++++++++-----------------
+ 1 file changed, 15 insertions(+), 17 deletions(-)
+
+diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
+index 1929692480..4af9deb98d 100644
+--- a/src/systemctl/systemctl.c
++++ b/src/systemctl/systemctl.c
+@@ -3004,12 +3004,12 @@ static enum action verb_to_action(const char *verb) {
+ 
+ static int start_unit(int argc, char *argv[], void *userdata) {
+         _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
++        _cleanup_(wait_context_free) WaitContext wait_context = {};
+         const char *method, *mode, *one_name, *suffix = NULL;
+         _cleanup_strv_free_ char **names = NULL;
++        int r, ret = EXIT_SUCCESS;
+         sd_bus *bus;
+-        _cleanup_(wait_context_free) WaitContext wait_context = {};
+         char **name;
+-        int r = 0;
+ 
+         if (arg_wait && !STR_IN_SET(argv[0], "start", "restart")) {
+                 log_error("--wait may only be used with the 'start' or 'restart' commands.");
+@@ -3096,16 +3096,15 @@ static int start_unit(int argc, char *argv[], void *userdata) {
+ 
+         STRV_FOREACH(name, names) {
+                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+-                int q;
+ 
+-                q = start_unit_one(bus, method, *name, mode, &error, w, arg_wait ? &wait_context : NULL);
+-                if (r >= 0 && q < 0)
+-                        r = translate_bus_error_to_exit_status(q, &error);
++                r = start_unit_one(bus, method, *name, mode, &error, w, arg_wait ? &wait_context : NULL);
++                if (ret == EXIT_SUCCESS && r < 0)
++                        ret = translate_bus_error_to_exit_status(r, &error);
+         }
+ 
+         if (!arg_no_block) {
+-                int q, arg_count = 0;
+                 const char* extra_args[4] = {};
++                int arg_count = 0;
+ 
+                 if (arg_scope != UNIT_FILE_SYSTEM)
+                         extra_args[arg_count++] = "--user";
+@@ -3119,9 +3118,9 @@ static int start_unit(int argc, char *argv[], void *userdata) {
+                         extra_args[arg_count++] = arg_host;
+                 }
+ 
+-                q = bus_wait_for_jobs(w, arg_quiet, extra_args);
+-                if (q < 0)
+-                        return q;
++                r = bus_wait_for_jobs(w, arg_quiet, extra_args);
++                if (r < 0)
++                        return r;
+ 
+                 /* When stopping units, warn if they can still be triggered by
+                  * another active unit (socket, path, timer) */
+@@ -3130,16 +3129,15 @@ static int start_unit(int argc, char *argv[], void *userdata) {
+                                 check_triggering_units(bus, *name);
+         }
+ 
+-        if (r >= 0 && arg_wait && !set_isempty(wait_context.unit_paths)) {
+-                int q;
+-                q = sd_event_loop(wait_context.event);
+-                if (q < 0)
+-                        return log_error_errno(q, "Failed to run event loop: %m");
++        if (ret == EXIT_SUCCESS && arg_wait && !set_isempty(wait_context.unit_paths)) {
++                r = sd_event_loop(wait_context.event);
++                if (r < 0)
++                        return log_error_errno(r, "Failed to run event loop: %m");
+                 if (wait_context.any_failed)
+-                        r = EXIT_FAILURE;
++                        ret = EXIT_FAILURE;
+         }
+ 
+-        return r;
++        return ret;
+ }
+ 
+ #if ENABLE_LOGIND
diff --git a/SOURCES/0449-systemctl-split-out-extra-args-generation-into-helpe.patch b/SOURCES/0449-systemctl-split-out-extra-args-generation-into-helpe.patch
new file mode 100644
index 0000000..a580c7a
--- /dev/null
+++ b/SOURCES/0449-systemctl-split-out-extra-args-generation-into-helpe.patch
@@ -0,0 +1,68 @@
+From cfb124260a0a9e68102a373c0d136f792e2d4ea7 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Tue, 26 Mar 2019 16:19:35 +0100
+Subject: [PATCH] systemctl: split out extra args generation into helper
+ function of its own
+
+(cherry picked from commit 94369fc0663255bbd327f97dba288ececf51a514)
+
+Related: #846319
+---
+ src/systemctl/systemctl.c | 36 +++++++++++++++++++++---------------
+ 1 file changed, 21 insertions(+), 15 deletions(-)
+
+diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
+index 4af9deb98d..e7a8fd559f 100644
+--- a/src/systemctl/systemctl.c
++++ b/src/systemctl/systemctl.c
+@@ -3002,6 +3002,25 @@ static enum action verb_to_action(const char *verb) {
+         return _ACTION_INVALID;
+ }
+ 
++static const char** make_extra_args(const char *extra_args[static 4]) {
++        size_t n = 0;
++
++        if (arg_scope != UNIT_FILE_SYSTEM)
++                extra_args[n++] = "--user";
++
++        if (arg_transport == BUS_TRANSPORT_REMOTE) {
++                extra_args[n++] = "-H";
++                extra_args[n++] = arg_host;
++        } else if (arg_transport == BUS_TRANSPORT_MACHINE) {
++                extra_args[n++] = "-M";
++                extra_args[n++] = arg_host;
++        } else
++                assert(arg_transport == BUS_TRANSPORT_LOCAL);
++
++        extra_args[n] = NULL;
++        return extra_args;
++}
++
+ static int start_unit(int argc, char *argv[], void *userdata) {
+         _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
+         _cleanup_(wait_context_free) WaitContext wait_context = {};
+@@ -3103,22 +3122,9 @@ static int start_unit(int argc, char *argv[], void *userdata) {
+         }
+ 
+         if (!arg_no_block) {
+-                const char* extra_args[4] = {};
+-                int arg_count = 0;
+-
+-                if (arg_scope != UNIT_FILE_SYSTEM)
+-                        extra_args[arg_count++] = "--user";
+-
+-                assert(IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_REMOTE, BUS_TRANSPORT_MACHINE));
+-                if (arg_transport == BUS_TRANSPORT_REMOTE) {
+-                        extra_args[arg_count++] = "-H";
+-                        extra_args[arg_count++] = arg_host;
+-                } else if (arg_transport == BUS_TRANSPORT_MACHINE) {
+-                        extra_args[arg_count++] = "-M";
+-                        extra_args[arg_count++] = arg_host;
+-                }
++                const char* extra_args[4];
+ 
+-                r = bus_wait_for_jobs(w, arg_quiet, extra_args);
++                r = bus_wait_for_jobs(w, arg_quiet, make_extra_args(extra_args));
+                 if (r < 0)
+                         return r;
+ 
diff --git a/SOURCES/0450-systemctl-add-new-show-transaction-switch.patch b/SOURCES/0450-systemctl-add-new-show-transaction-switch.patch
new file mode 100644
index 0000000..1e804b0
--- /dev/null
+++ b/SOURCES/0450-systemctl-add-new-show-transaction-switch.patch
@@ -0,0 +1,362 @@
+From cacf7a619cdccd9a6da6c2fe7361eac121b9ea0b Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Fri, 22 Mar 2019 20:58:13 +0100
+Subject: [PATCH] systemctl: add new --show-transaction switch
+
+This new switch uses the new method call EnqueueUnitJob() for enqueuing
+a job and showing the jobs it enqueued.
+
+Fixes: #2297
+(cherry picked from commit 85d9b5981ba6b7ee3955f95fa6cf3bb8cdf3444d)
+
+Resolves: #846319
+---
+ src/systemctl/systemctl.c | 183 ++++++++++++++++++++++++++------------
+ 1 file changed, 128 insertions(+), 55 deletions(-)
+
+diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
+index e7a8fd559f..8bec798373 100644
+--- a/src/systemctl/systemctl.c
++++ b/src/systemctl/systemctl.c
+@@ -126,6 +126,7 @@ static bool arg_dry_run = false;
+ static bool arg_quiet = false;
+ static bool arg_full = false;
+ static bool arg_recursive = false;
++static bool arg_show_transaction = false;
+ static int arg_force = 0;
+ static bool arg_ask_password = false;
+ static bool arg_runtime = false;
+@@ -734,7 +735,6 @@ static int get_unit_list_recursive(
+                 *_machines = NULL;
+ 
+         *_unit_infos = TAKE_PTR(unit_infos);
+-
+         *_replies = TAKE_PTR(replies);
+ 
+         return c;
+@@ -2688,25 +2688,26 @@ static int check_triggering_units(sd_bus *bus, const char *name) {
+ }
+ 
+ static const struct {
+-        const char *verb;
+-        const char *method;
++        const char *verb;      /* systemctl verb */
++        const char *method;    /* Name of the specific D-Bus method */
++        const char *job_type;  /* Job type when passing to the generic EnqueueUnitJob() method */
+ } unit_actions[] = {
+-        { "start",                 "StartUnit" },
+-        { "stop",                  "StopUnit" },
+-        { "condstop",              "StopUnit" },
+-        { "reload",                "ReloadUnit" },
+-        { "restart",               "RestartUnit" },
+-        { "try-restart",           "TryRestartUnit" },
+-        { "condrestart",           "TryRestartUnit" },
+-        { "reload-or-restart",     "ReloadOrRestartUnit" },
+-        { "try-reload-or-restart", "ReloadOrTryRestartUnit" },
+-        { "reload-or-try-restart", "ReloadOrTryRestartUnit" },
+-        { "condreload",            "ReloadOrTryRestartUnit" },
+-        { "force-reload",          "ReloadOrTryRestartUnit" }
++        { "start",                 "StartUnit",              "start"                 },
++        { "stop",                  "StopUnit",               "stop"                  },
++        { "condstop",              "StopUnit",               "stop"                  }, /* legacy alias */
++        { "reload",                "ReloadUnit",             "reload"                },
++        { "restart",               "RestartUnit",            "restart"               },
++        { "try-restart",           "TryRestartUnit",         "try-restart"           },
++        { "condrestart",           "TryRestartUnit",         "try-restart"           }, /* legacy alias */
++        { "reload-or-restart",     "ReloadOrRestartUnit",    "reload-or-restart"     },
++        { "try-reload-or-restart", "ReloadOrTryRestartUnit", "reload-or-try-restart" },
++        { "reload-or-try-restart", "ReloadOrTryRestartUnit", "reload-or-try-restart" }, /* legacy alias */
++        { "condreload",            "ReloadOrTryRestartUnit", "reload-or-try-restart" }, /* legacy alias */
++        { "force-reload",          "ReloadOrTryRestartUnit", "reload-or-try-restart" }, /* legacy alias */
+ };
+ 
+ static const char *verb_to_method(const char *verb) {
+-       uint i;
++       size_t i;
+ 
+        for (i = 0; i < ELEMENTSOF(unit_actions); i++)
+                 if (streq_ptr(unit_actions[i].verb, verb))
+@@ -2715,14 +2716,14 @@ static const char *verb_to_method(const char *verb) {
+        return "StartUnit";
+ }
+ 
+-static const char *method_to_verb(const char *method) {
+-       uint i;
++static const char *verb_to_job_type(const char *verb) {
++       size_t i;
+ 
+        for (i = 0; i < ELEMENTSOF(unit_actions); i++)
+-                if (streq_ptr(unit_actions[i].method, method))
+-                        return unit_actions[i].verb;
++                if (streq_ptr(unit_actions[i].verb, verb))
++                        return unit_actions[i].job_type;
+ 
+-       return "n/a";
++       return "start";
+ }
+ 
+ typedef struct {
+@@ -2805,7 +2806,8 @@ static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error
+ 
+ static int start_unit_one(
+                 sd_bus *bus,
+-                const char *method,
++                const char *method,    /* When using classic per-job bus methods */
++                const char *job_type,  /* When using new-style EnqueueUnitJob() */
+                 const char *name,
+                 const char *mode,
+                 sd_bus_error *error,
+@@ -2814,6 +2816,7 @@ static int start_unit_one(
+ 
+         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+         const char *path;
++        bool done = false;
+         int r;
+ 
+         assert(method);
+@@ -2859,44 +2862,81 @@ static int start_unit_one(
+         log_debug("%s dbus call org.freedesktop.systemd1.Manager %s(%s, %s)",
+                   arg_dry_run ? "Would execute" : "Executing",
+                   method, name, mode);
++
+         if (arg_dry_run)
+                 return 0;
+ 
+-        r = sd_bus_call_method(
+-                        bus,
+-                        "org.freedesktop.systemd1",
+-                        "/org/freedesktop/systemd1",
+-                        "org.freedesktop.systemd1.Manager",
+-                        method,
+-                        error,
+-                        &reply,
+-                        "ss", name, mode);
+-        if (r < 0) {
+-                const char *verb;
++        if (arg_show_transaction) {
++                _cleanup_(sd_bus_error_free) sd_bus_error enqueue_error = SD_BUS_ERROR_NULL;
+ 
+-                /* There's always a fallback possible for legacy actions. */
+-                if (arg_action != ACTION_SYSTEMCTL)
+-                        return r;
++                /* Use the new, fancy EnqueueUnitJob() API if the user wants us to print the transaction */
++                r = sd_bus_call_method(
++                                bus,
++                                "org.freedesktop.systemd1",
++                                "/org/freedesktop/systemd1",
++                                "org.freedesktop.systemd1.Manager",
++                                "EnqueueUnitJob",
++                                &enqueue_error,
++                                &reply,
++                                "sss",
++                                name, job_type, mode);
++                if (r < 0) {
++                        if (!sd_bus_error_has_name(&enqueue_error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
++                                (void) sd_bus_error_copy(error, &enqueue_error);
++                                sd_bus_error_free(&enqueue_error);
++                                goto fail;
++                        }
++
++                        /* Hmm, the API is not yet available. Let's use the classic API instead (see below). */
++                        log_notice("--show-transaction not supported by this service manager, proceeding without.");
++                } else {
++                        const char *u, *jt;
++                        uint32_t id;
+ 
+-                verb = method_to_verb(method);
++                        r = sd_bus_message_read(reply, "uosos", &id, &path, &u, NULL, &jt);
++                        if (r < 0)
++                                return bus_log_parse_error(r);
+ 
+-                log_error("Failed to %s %s: %s", verb, name, bus_error_message(error, r));
++                        log_info("Enqueued anchor job %" PRIu32 " %s/%s.", id, u, jt);
+ 
+-                if (!sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) &&
+-                    !sd_bus_error_has_name(error, BUS_ERROR_UNIT_MASKED) &&
+-                    !sd_bus_error_has_name(error, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE))
+-                        log_error("See %s logs and 'systemctl%s status%s %s' for details.",
+-                                   arg_scope == UNIT_FILE_SYSTEM ? "system" : "user",
+-                                   arg_scope == UNIT_FILE_SYSTEM ? "" : " --user",
+-                                   name[0] == '-' ? " --" : "",
+-                                   name);
++                        r = sd_bus_message_enter_container(reply, 'a', "(uosos)");
++                        if (r < 0)
++                                return bus_log_parse_error(r);
++                        for (;;) {
++                                r = sd_bus_message_read(reply, "(uosos)", &id, NULL, &u, NULL, &jt);
++                                if (r < 0)
++                                        return bus_log_parse_error(r);
++                                if (r == 0)
++                                        break;
+ 
+-                return r;
++                                log_info("Enqueued auxiliary job %" PRIu32 " %s/%s.", id, u, jt);
++                        }
++
++                        r = sd_bus_message_exit_container(reply);
++                        if (r < 0)
++                                return bus_log_parse_error(r);
++
++                        done = true;
++                }
+         }
+ 
+-        r = sd_bus_message_read(reply, "o", &path);
+-        if (r < 0)
+-                return bus_log_parse_error(r);
++        if (!done) {
++                r = sd_bus_call_method(
++                                bus,
++                                "org.freedesktop.systemd1",
++                                "/org/freedesktop/systemd1",
++                                "org.freedesktop.systemd1.Manager",
++                                method,
++                                error,
++                                &reply,
++                                "ss", name, mode);
++                if (r < 0)
++                        goto fail;
++
++                r = sd_bus_message_read(reply, "o", &path);
++                if (r < 0)
++                        return bus_log_parse_error(r);
++        }
+ 
+         if (need_daemon_reload(bus, name) > 0)
+                 warn_unit_file_changed(name);
+@@ -2909,6 +2949,24 @@ static int start_unit_one(
+         }
+ 
+         return 0;
++
++fail:
++        /* There's always a fallback possible for legacy actions. */
++        if (arg_action != ACTION_SYSTEMCTL)
++                return r;
++
++        log_error_errno(r, "Failed to %s %s: %s", job_type, name, bus_error_message(error, r));
++
++        if (!sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) &&
++            !sd_bus_error_has_name(error, BUS_ERROR_UNIT_MASKED) &&
++            !sd_bus_error_has_name(error, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE))
++                log_error("See %s logs and 'systemctl%s status%s %s' for details.",
++                          arg_scope == UNIT_FILE_SYSTEM ? "system" : "user",
++                          arg_scope == UNIT_FILE_SYSTEM ? "" : " --user",
++                          name[0] == '-' ? " --" : "",
++                          name);
++
++        return r;
+ }
+ 
+ static int expand_names(sd_bus *bus, char **names, const char* suffix, char ***ret) {
+@@ -2965,7 +3023,6 @@ static int expand_names(sd_bus *bus, char **names, const char* suffix, char ***r
+         }
+ 
+         *ret = TAKE_PTR(mangled);
+-
+         return 0;
+ }
+ 
+@@ -3024,7 +3081,7 @@ static const char** make_extra_args(const char *extra_args[static 4]) {
+ static int start_unit(int argc, char *argv[], void *userdata) {
+         _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
+         _cleanup_(wait_context_free) WaitContext wait_context = {};
+-        const char *method, *mode, *one_name, *suffix = NULL;
++        const char *method, *job_type, *mode, *one_name, *suffix = NULL;
+         _cleanup_strv_free_ char **names = NULL;
+         int r, ret = EXIT_SUCCESS;
+         sd_bus *bus;
+@@ -3050,27 +3107,34 @@ static int start_unit(int argc, char *argv[], void *userdata) {
+                 action = verb_to_action(argv[0]);
+ 
+                 if (action != _ACTION_INVALID) {
++                        /* A command in style "systemctl reboot", "systemctl poweroff", … */
+                         method = "StartUnit";
++                        job_type = "start";
+                         mode = action_table[action].mode;
+                         one_name = action_table[action].target;
+                 } else {
+                         if (streq(argv[0], "isolate")) {
++                                /* A "systemctl isolate <unit1> <unit2> …" command */
+                                 method = "StartUnit";
++                                job_type = "start";
+                                 mode = "isolate";
+-
+                                 suffix = ".target";
+                         } else {
++                                /* A command in style of "systemctl start <unit1> <unit2> …", "sysemctl stop <unit1> <unit2> …" and so on */
+                                 method = verb_to_method(argv[0]);
++                                job_type = verb_to_job_type(argv[0]);
+                                 mode = arg_job_mode;
+                         }
+                         one_name = NULL;
+                 }
+         } else {
++                /* A SysV legacy command such as "halt", "reboot", "poweroff", … */
+                 assert(arg_action >= 0 && arg_action < _ACTION_MAX);
+                 assert(action_table[arg_action].target);
+                 assert(action_table[arg_action].mode);
+ 
+                 method = "StartUnit";
++                job_type = "start";
+                 mode = action_table[arg_action].mode;
+                 one_name = action_table[arg_action].target;
+         }
+@@ -3105,9 +3169,11 @@ static int start_unit(int argc, char *argv[], void *userdata) {
+                                 NULL);
+                 if (r < 0)
+                         return log_error_errno(r, "Failed to enable subscription: %m");
++
+                 r = sd_event_default(&wait_context.event);
+                 if (r < 0)
+                         return log_error_errno(r, "Failed to allocate event loop: %m");
++
+                 r = sd_bus_attach_event(bus, wait_context.event, 0);
+                 if (r < 0)
+                         return log_error_errno(r, "Failed to attach bus to event loop: %m");
+@@ -3116,7 +3182,7 @@ static int start_unit(int argc, char *argv[], void *userdata) {
+         STRV_FOREACH(name, names) {
+                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ 
+-                r = start_unit_one(bus, method, *name, mode, &error, w, arg_wait ? &wait_context : NULL);
++                r = start_unit_one(bus, method, job_type, *name, mode, &error, w, arg_wait ? &wait_context : NULL);
+                 if (ret == EXIT_SUCCESS && r < 0)
+                         ret = translate_bus_error_to_exit_status(r, &error);
+         }
+@@ -7167,6 +7233,8 @@ static void systemctl_help(void) {
+                "     --reverse        Show reverse dependencies with 'list-dependencies'\n"
+                "     --job-mode=MODE  Specify how to deal with already queued jobs, when\n"
+                "                      queueing a new job\n"
++               "  -T --show-transaction\n"
++               "                      When enqueuing a unit job, show full transaction\n"
+                "     --show-types     When showing sockets, explicitly show their type\n"
+                "     --value          When showing properties, only print the value\n"
+                "  -i --ignore-inhibitors\n"
+@@ -7482,6 +7550,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
+                 { "firmware-setup",      no_argument,       NULL, ARG_FIRMWARE_SETUP      },
+                 { "now",                 no_argument,       NULL, ARG_NOW                 },
+                 { "message",             required_argument, NULL, ARG_MESSAGE             },
++                { "show-transaction",    no_argument,       NULL, 'T'                     },
+                 {}
+         };
+ 
+@@ -7494,7 +7563,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
+         /* we default to allowing interactive authorization only in systemctl (not in the legacy commands) */
+         arg_ask_password = true;
+ 
+-        while ((c = getopt_long(argc, argv, "ht:p:alqfs:H:M:n:o:ir", options, NULL)) >= 0)
++        while ((c = getopt_long(argc, argv, "ht:p:alqfs:H:M:n:o:iTr", options, NULL)) >= 0)
+ 
+                 switch (c) {
+ 
+@@ -7815,6 +7884,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
+                                 return log_oom();
+                         break;
+ 
++                case 'T':
++                        arg_show_transaction = true;
++                        break;
++
+                 case '?':
+                         return -EINVAL;
+ 
diff --git a/SOURCES/0451-test-add-some-basic-testing-that-systemctl-start-T-d.patch b/SOURCES/0451-test-add-some-basic-testing-that-systemctl-start-T-d.patch
new file mode 100644
index 0000000..13c3808
--- /dev/null
+++ b/SOURCES/0451-test-add-some-basic-testing-that-systemctl-start-T-d.patch
@@ -0,0 +1,31 @@
+From f31afbfd2fa68e20a10a8432fb4714a6d4e1170a Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Tue, 26 Mar 2019 17:39:36 +0100
+Subject: [PATCH] test: add some basic testing that "systemctl start -T" does
+ something
+
+(cherry picked from commit f087c7e072bb338d5c7c0781c9fbc900612efd18)
+
+Related: #846319
+---
+ test/TEST-03-JOBS/test-jobs.sh | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/test/TEST-03-JOBS/test-jobs.sh b/test/TEST-03-JOBS/test-jobs.sh
+index e66ea53621..42190cf478 100755
+--- a/test/TEST-03-JOBS/test-jobs.sh
++++ b/test/TEST-03-JOBS/test-jobs.sh
+@@ -26,6 +26,13 @@ grep 'sleep\.service.*running' /root/list-jobs.txt
+ grep 'hello\.service' /root/list-jobs.txt && exit 1
+ systemctl stop sleep.service hello-after-sleep.target
+ 
++# Some basic testing that --show-transaction does something useful
++! systemctl is-active systemd-importd
++systemctl -T start systemd-importd
++systemctl is-active systemd-importd
++systemctl --show-transaction stop systemd-importd
++! systemctl is-active systemd-importd
++
+ # Test for a crash when enqueuing a JOB_NOP when other job already exists
+ systemctl start --no-block hello-after-sleep.target
+ # hello.service should still be waiting, so these try-restarts will collapse
diff --git a/SOURCES/0452-man-document-the-new-systemctl-show-transaction-opti.patch b/SOURCES/0452-man-document-the-new-systemctl-show-transaction-opti.patch
new file mode 100644
index 0000000..1f8b8ce
--- /dev/null
+++ b/SOURCES/0452-man-document-the-new-systemctl-show-transaction-opti.patch
@@ -0,0 +1,37 @@
+From 588e3e8008d24021ec025d54318fb07d2212293c Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Tue, 26 Mar 2019 18:02:49 +0100
+Subject: [PATCH] man: document the new systemctl --show-transaction option
+
+(cherry picked from commit df4a7cb7323c8cf00553d766913312c5b7ccd508)
+
+Related: #846319
+---
+ man/systemctl.xml | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+diff --git a/man/systemctl.xml b/man/systemctl.xml
+index 6145486123..fa08ab6c0a 100644
+--- a/man/systemctl.xml
++++ b/man/systemctl.xml
+@@ -298,6 +298,20 @@
+ 
+       </varlistentry>
+ 
++      <varlistentry>
++        <term><option>-T</option></term>
++        <term><option>--show-transaction</option></term>
++
++        <listitem>
++          <para>When enqueuing a unit job (for example as effect of a <command>systemctl start</command>
++          invocation or similar), show brief information about all jobs enqueued, covering both the requested
++          job and any added because of unit dependencies. Note that the output will only include jobs
++          immediately part of the transaction requested. It is possible that service start-up program code
++          run as effect of the enqueued jobs might request further jobs to be pulled in. This means that
++          completion of the listed jobs might ultimately entail more jobs than the listed ones.</para>
++        </listitem>
++      </varlistentry>
++
+       <varlistentry>
+         <term><option>--fail</option></term>
+ 
diff --git a/SOURCES/0453-socket-New-option-FlushPending-boolean-to-flush-sock.patch b/SOURCES/0453-socket-New-option-FlushPending-boolean-to-flush-sock.patch
new file mode 100644
index 0000000..357f076
--- /dev/null
+++ b/SOURCES/0453-socket-New-option-FlushPending-boolean-to-flush-sock.patch
@@ -0,0 +1,153 @@
+From 262544a451c11c38e92c45047ec2adeaeb2a0a7e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Renaud=20M=C3=A9trich?= <rmetrich@redhat.com>
+Date: Thu, 20 Aug 2020 13:00:37 +0200
+Subject: [PATCH] socket: New option 'FlushPending' (boolean) to flush socket
+ before entering listening state
+
+Disabled by default. When Enabled, before listening on the socket, flush the content.
+Applies when Accept=no only.
+
+(cherry picked from commit 3e5f04bf6468fcb79c080f02b0eab08f258bff0c)
+
+Resolves: #1870638
+---
+ doc/TRANSIENT-SETTINGS.md             |  1 +
+ man/systemd.socket.xml                | 12 ++++++++++++
+ src/core/dbus-socket.c                |  4 ++++
+ src/core/load-fragment-gperf.gperf.m4 |  1 +
+ src/core/socket.c                     | 11 +++++++++++
+ src/core/socket.h                     |  1 +
+ src/shared/bus-unit-util.c            |  3 ++-
+ 7 files changed, 32 insertions(+), 1 deletion(-)
+
+diff --git a/doc/TRANSIENT-SETTINGS.md b/doc/TRANSIENT-SETTINGS.md
+index 1a4e79190a..995b8797ef 100644
+--- a/doc/TRANSIENT-SETTINGS.md
++++ b/doc/TRANSIENT-SETTINGS.md
+@@ -388,6 +388,7 @@ Most socket unit settings are available to transient units.
+ ✓ SocketMode=
+ ✓ DirectoryMode=
+ ✓ Accept=
++✓ FlushPending=
+ ✓ Writable=
+ ✓ MaxConnections=
+ ✓ MaxConnectionsPerSource=
+diff --git a/man/systemd.socket.xml b/man/systemd.socket.xml
+index 19c2ca9907..8676b4e03f 100644
+--- a/man/systemd.socket.xml
++++ b/man/systemd.socket.xml
+@@ -425,6 +425,18 @@
+         false, in read-only mode. Defaults to false.</para></listitem>
+       </varlistentry>
+ 
++      <varlistentry>
++        <term><varname>FlushPending=</varname></term>
++        <listitem><para>Takes a boolean argument. May only be used when
++        <option>Accept=no</option>. If yes, the socket's buffers are cleared after the
++        triggered service exited. This causes any pending data to be
++        flushed and any pending incoming connections to be rejected. If no, the
++        socket's buffers won't be cleared, permitting the service to handle any
++        pending connections after restart, which is the usually expected behaviour.
++        Defaults to <option>no</option>.
++        </para></listitem>
++      </varlistentry>
++
+       <varlistentry>
+         <term><varname>MaxConnections=</varname></term>
+         <listitem><para>The maximum number of connections to
+diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c
+index 913cc74918..bb77539030 100644
+--- a/src/core/dbus-socket.c
++++ b/src/core/dbus-socket.c
+@@ -85,6 +85,7 @@ const sd_bus_vtable bus_socket_vtable[] = {
+         SD_BUS_PROPERTY("SocketMode", "u", bus_property_get_mode, offsetof(Socket, socket_mode), SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Socket, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("Accept", "b", bus_property_get_bool, offsetof(Socket, accept), SD_BUS_VTABLE_PROPERTY_CONST),
++        SD_BUS_PROPERTY("FlushPending", "b", bus_property_get_bool, offsetof(Socket, flush_pending), SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("Writable", "b", bus_property_get_bool, offsetof(Socket, writable), SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("KeepAlive", "b", bus_property_get_bool, offsetof(Socket, keep_alive), SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("KeepAliveTimeUSec", "t", bus_property_get_usec, offsetof(Socket, keep_alive_time), SD_BUS_VTABLE_PROPERTY_CONST),
+@@ -177,6 +178,9 @@ static int bus_socket_set_transient_property(
+         if (streq(name, "Accept"))
+                 return bus_set_transient_bool(u, name, &s->accept, message, flags, error);
+ 
++        if (streq(name, "FlushPending"))
++                return bus_set_transient_bool(u, name, &s->flush_pending, message, flags, error);
++
+         if (streq(name, "Writable"))
+                 return bus_set_transient_bool(u, name, &s->writable, message, flags, error);
+ 
+diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
+index 6d21b2e433..24ee5ae6fe 100644
+--- a/src/core/load-fragment-gperf.gperf.m4
++++ b/src/core/load-fragment-gperf.gperf.m4
+@@ -359,6 +359,7 @@ Socket.SocketGroup,              config_parse_user_group,            0,
+ Socket.SocketMode,               config_parse_mode,                  0,                             offsetof(Socket, socket_mode)
+ Socket.DirectoryMode,            config_parse_mode,                  0,                             offsetof(Socket, directory_mode)
+ Socket.Accept,                   config_parse_bool,                  0,                             offsetof(Socket, accept)
++Socket.FlushPending,             config_parse_bool,                  0,                             offsetof(Socket, flush_pending)
+ Socket.Writable,                 config_parse_bool,                  0,                             offsetof(Socket, writable)
+ Socket.MaxConnections,           config_parse_unsigned,              0,                             offsetof(Socket, max_connections)
+ Socket.MaxConnectionsPerSource,  config_parse_unsigned,              0,                             offsetof(Socket, max_connections_per_source)
+diff --git a/src/core/socket.c b/src/core/socket.c
+index fe061eb73b..97c3a7fc9a 100644
+--- a/src/core/socket.c
++++ b/src/core/socket.c
+@@ -70,6 +70,7 @@ static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = {
+ 
+ static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata);
+ static int socket_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata);
++static void flush_ports(Socket *s);
+ 
+ static void socket_init(Unit *u) {
+         Socket *s = SOCKET(u);
+@@ -703,6 +704,11 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) {
+                         prefix, s->n_connections,
+                         prefix, s->max_connections,
+                         prefix, s->max_connections_per_source);
++        else
++                fprintf(f,
++                        "%sFlushPending: %s\n",
++                         prefix, yes_no(s->flush_pending));
++
+ 
+         if (s->priority >= 0)
+                 fprintf(f,
+@@ -2111,6 +2117,11 @@ static void socket_enter_listening(Socket *s) {
+         int r;
+         assert(s);
+ 
++        if (!s->accept && s->flush_pending) {
++                log_unit_debug(UNIT(s), "Flushing socket before listening.");
++                flush_ports(s);
++        }
++
+         r = socket_watch_fds(s);
+         if (r < 0) {
+                 log_unit_warning_errno(UNIT(s), r, "Failed to watch sockets: %m");
+diff --git a/src/core/socket.h b/src/core/socket.h
+index c4e25db1fc..b7a25d91fd 100644
+--- a/src/core/socket.h
++++ b/src/core/socket.h
+@@ -109,6 +109,7 @@ struct Socket {
+         bool accept;
+         bool remove_on_stop;
+         bool writable;
++        bool flush_pending;
+ 
+         int socket_protocol;
+ 
+diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
+index 77788f0fe2..7029aa5615 100644
+--- a/src/shared/bus-unit-util.c
++++ b/src/shared/bus-unit-util.c
+@@ -1468,7 +1468,8 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons
+ 
+         if (STR_IN_SET(field,
+                        "Accept", "Writable", "KeepAlive", "NoDelay", "FreeBind", "Transparent", "Broadcast",
+-                       "PassCredentials", "PassSecurity", "ReusePort", "RemoveOnStop", "SELinuxContextFromNet"))
++                       "PassCredentials", "PassSecurity", "ReusePort", "RemoveOnStop", "SELinuxContextFromNet",
++                       "FlushPending"))
+ 
+                 return bus_append_parse_boolean(m, field, eq);
+ 
diff --git a/SOURCES/0454-core-remove-support-for-API-bus-started-outside-our-.patch b/SOURCES/0454-core-remove-support-for-API-bus-started-outside-our-.patch
new file mode 100644
index 0000000..2d454d8
--- /dev/null
+++ b/SOURCES/0454-core-remove-support-for-API-bus-started-outside-our-.patch
@@ -0,0 +1,62 @@
+From 9c0ed82f661a2296784970678746b0b4f130870e Mon Sep 17 00:00:00 2001
+From: Alan Jenkins <alan.christopher.jenkins@gmail.com>
+Date: Thu, 21 Jun 2018 14:12:30 +0100
+Subject: [PATCH] core: remove support for API bus "started outside our own
+ logic"
+
+Looking at a recent Bad Day, my log contains over 100 lines of
+
+    systemd[23895]: Failed to connect to API bus: Connection refused
+
+It is due to "systemd --user" retrying to connect to an API bus.[*]  I
+would prefer to avoid spamming the logs.  I don't think it is good for us
+to retry so much like this.
+
+systemd was mislead by something setting DBUS_SESSION_BUS_ADDRESS.  My best
+guess is an unfortunate series of events caused gdm to set this.  gdm has
+code to start a session dbus if there is not a bus available already (and
+in this case it exports the environment variable).  I believe it does not
+normally do this when running under systemd, because "systemd --user" and
+hence "dbus.service" would already have been started by pam_systemd.
+
+I see two possibilities
+
+1. Rip out the check for DBUS_SESSION_BUS_ADDRESS entirely.
+2. Only check for DBUS_SESSION_BUS_ADDRESS on startup.  Not in the
+   "recheck" logic.
+
+The justification for 2), is that the recheck is called from unit_notify(),
+this is used to check whether the service just started (or stopped) was
+"dbus.service".  This reason for rechecking does not apply if we think
+the session bus was started outside our logic.
+
+But I think we can justify 1).  dbus-daemon ships a statically-enabled
+/usr/lib/systemd/user/dbus.service, which would conflict with an attempt to
+use an external dbus.  Also "systemd --user" is started from user@.service;
+if you try to start it manually so that it inherits an environment
+variable, it will conflict if user@.service was started by pam_systemd
+(or loginctl enable-linger).
+
+(cherry picked from commit d3243f55ca9b5f305306ba4105ab29768e372a78)
+
+Resolves: #1764282
+---
+ src/core/manager.c | 5 -----
+ 1 file changed, 5 deletions(-)
+
+diff --git a/src/core/manager.c b/src/core/manager.c
+index 012615e537..3c44ad3dbc 100644
+--- a/src/core/manager.c
++++ b/src/core/manager.c
+@@ -1519,11 +1519,6 @@ static bool manager_dbus_is_running(Manager *m, bool deserialized) {
+         if (m->test_run_flags != 0)
+                 return false;
+ 
+-        /* If we are in the user instance, and the env var is already set for us, then this means D-Bus is ran
+-         * somewhere outside of our own logic. Let's use it */
+-        if (MANAGER_IS_USER(m) && getenv("DBUS_SESSION_BUS_ADDRESS"))
+-                return true;
+-
+         u = manager_get_unit(m, SPECIAL_DBUS_SOCKET);
+         if (!u)
+                 return false;
diff --git a/SOURCES/0455-mount-setup-fix-segfault-in-mount_cgroup_controllers.patch b/SOURCES/0455-mount-setup-fix-segfault-in-mount_cgroup_controllers.patch
new file mode 100644
index 0000000..a99e7de
--- /dev/null
+++ b/SOURCES/0455-mount-setup-fix-segfault-in-mount_cgroup_controllers.patch
@@ -0,0 +1,56 @@
+From 1c8d1c3bbdf8bdfb2ee4f85b9f559f54c6e4cac4 Mon Sep 17 00:00:00 2001
+From: Wen Yang <wenyang@linux.alibaba.com>
+Date: Wed, 1 Jul 2020 04:45:33 +0800
+Subject: [PATCH] mount-setup: fix segfault in mount_cgroup_controllers when
+ using gcc9 compiler
+
+According to the documentation:
+https://gcc.gnu.org/gcc-9/porting_to.html#complit
+
+The 'join_controllers' that relied on the extended lifetime needs
+to be fixed, move the compound literals to the function scope it
+need to accessible in.
+
+Resolves: #1868877
+---
+ src/core/mount-setup.c | 24 +++++++++++++-----------
+ 1 file changed, 13 insertions(+), 11 deletions(-)
+
+diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c
+index 16880e6157..a6594580e5 100644
+--- a/src/core/mount-setup.c
++++ b/src/core/mount-setup.c
+@@ -237,20 +237,22 @@ int mount_cgroup_controllers(char ***join_controllers) {
+         if (!cg_is_legacy_wanted())
+                 return 0;
+ 
++        /* The defaults:
++         * mount "cpu" + "cpuacct" together, and "net_cls" + "net_prio".
++         *
++         * We'd like to add "cpuset" to the mix, but "cpuset" doesn't really
++         * work for groups with no initialized attributes.
++         */
++        char ***default_join_controllers = (char**[]) {
++                STRV_MAKE("cpu", "cpuacct"),
++                STRV_MAKE("net_cls", "net_prio"),
++                NULL,
++        };
++
+         /* Mount all available cgroup controllers that are built into the kernel. */
+ 
+         if (!has_argument)
+-                /* The defaults:
+-                 * mount "cpu" + "cpuacct" together, and "net_cls" + "net_prio".
+-                 *
+-                 * We'd like to add "cpuset" to the mix, but "cpuset" doesn't really
+-                 * work for groups with no initialized attributes.
+-                 */
+-                join_controllers = (char**[]) {
+-                        STRV_MAKE("cpu", "cpuacct"),
+-                        STRV_MAKE("net_cls", "net_prio"),
+-                        NULL,
+-                };
++                join_controllers = default_join_controllers;
+ 
+         r = cg_kernel_controllers(&controllers);
+         if (r < 0)
diff --git a/SOURCES/0456-dbus-execute-make-transfer-of-CPUAffinity-endian-saf.patch b/SOURCES/0456-dbus-execute-make-transfer-of-CPUAffinity-endian-saf.patch
new file mode 100644
index 0000000..71758ee
--- /dev/null
+++ b/SOURCES/0456-dbus-execute-make-transfer-of-CPUAffinity-endian-saf.patch
@@ -0,0 +1,39 @@
+From 1730f7bb306e13689a7684fd93ae5b8383a28d2f Mon Sep 17 00:00:00 2001
+From: Michal Sekletar <msekletar@users.noreply.github.com>
+Date: Fri, 31 May 2019 15:23:23 +0200
+Subject: [PATCH] dbus-execute: make transfer of CPUAffinity endian safe
+ (#12711)
+
+We store the affinity mask in the native endian. However, over D-Bus we
+must transfer the mask in little endian byte order.
+
+This is the second part of c367f996f5f091a63f812f0140b304c649be77fc.
+
+(cherry picked from commit 75e40119a471454516ad0acc96f6f4094e7fb652)
+
+Related: #1740657
+---
+ src/core/dbus-execute.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
+index f9527e56b2..d5acca384f 100644
+--- a/src/core/dbus-execute.c
++++ b/src/core/dbus-execute.c
+@@ -215,12 +215,15 @@ static int property_get_cpu_affinity(
+                 sd_bus_error *error) {
+ 
+         ExecContext *c = userdata;
++        _cleanup_free_ uint8_t *array = NULL;
++        size_t allocated;
+ 
+         assert(bus);
+         assert(reply);
+         assert(c);
+ 
+-        return sd_bus_message_append_array(reply, 'y', c->cpu_set.set, c->cpu_set.allocated);
++        (void) cpu_set_to_dbus(&c->cpu_set, &array, &allocated);
++        return sd_bus_message_append_array(reply, 'y', array, allocated);
+ }
+ 
+ static int property_get_numa_mask(
diff --git a/SOURCES/0457-core-add-support-for-setting-CPUAffinity-to-special-.patch b/SOURCES/0457-core-add-support-for-setting-CPUAffinity-to-special-.patch
new file mode 100644
index 0000000..3118171
--- /dev/null
+++ b/SOURCES/0457-core-add-support-for-setting-CPUAffinity-to-special-.patch
@@ -0,0 +1,426 @@
+From 1a822dbe19ab6634ffb2c0d3ce92b27b503e1612 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= <msekleta@redhat.com>
+Date: Mon, 17 Feb 2020 13:50:31 +0100
+Subject: [PATCH] core: add support for setting CPUAffinity= to special "numa"
+ value
+
+systemd will automatically derive CPU affinity mask from NUMA node
+mask.
+
+Fixes #13248
+
+(cherry picked from commit e2b2fb7f566d13a3de61952b5356cd4d2eaee917)
+
+Resolves: #1740657
+---
+ man/systemd.exec.xml                 |  9 +++---
+ src/basic/cpu-set-util.c             | 43 ++++++++++++++++++++++++--
+ src/basic/cpu-set-util.h             |  1 +
+ src/core/dbus-execute.c              | 30 +++++++++++++++++-
+ src/core/execute.c                   | 46 ++++++++++++++++++++++++++--
+ src/core/execute.h                   |  3 ++
+ src/core/load-fragment.c             | 14 ++++++++-
+ src/shared/bus-unit-util.c           |  9 ++++++
+ src/test/test-cpu-set-util.c         |  6 ++--
+ test/TEST-36-NUMAPOLICY/testsuite.sh | 18 +++++++++++
+ 10 files changed, 166 insertions(+), 13 deletions(-)
+
+diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
+index e2a5ede968..696438c4ef 100644
+--- a/man/systemd.exec.xml
++++ b/man/systemd.exec.xml
+@@ -706,10 +706,11 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
+         <term><varname>CPUAffinity=</varname></term>
+ 
+         <listitem><para>Controls the CPU affinity of the executed processes. Takes a list of CPU indices or ranges
+-        separated by either whitespace or commas. CPU ranges are specified by the lower and upper CPU indices separated
+-        by a dash. This option may be specified more than once, in which case the specified CPU affinity masks are
+-        merged. If the empty string is assigned, the mask is reset, all assignments prior to this will have no
+-        effect. See
++        separated by either whitespace or commas. Alternatively, takes a special "numa" value in which case systemd
++        automatically derives allowed CPU range based on the value of <varname>NUMAMask=</varname> option. CPU ranges
++        are specified by the lower and upper CPU indices separated by a dash. This option may be specified more than
++        once, in which case the specified CPU affinity masks are merged. If the empty string is assigned, the mask
++        is reset, all assignments prior to this will have no effect. See
+         <citerefentry><refentrytitle>sched_setaffinity</refentrytitle><manvolnum>2</manvolnum></citerefentry> for
+         details.</para></listitem>
+       </varlistentry>
+diff --git a/src/basic/cpu-set-util.c b/src/basic/cpu-set-util.c
+index 51752ad1a6..1922c95864 100644
+--- a/src/basic/cpu-set-util.c
++++ b/src/basic/cpu-set-util.c
+@@ -12,12 +12,14 @@
+ #include "cpu-set-util.h"
+ #include "dirent-util.h"
+ #include "extract-word.h"
++#include "fileio.h"
+ #include "fd-util.h"
+ #include "log.h"
+ #include "macro.h"
+ #include "missing.h"
+ #include "parse-util.h"
+ #include "stat-util.h"
++#include "stdio-util.h"
+ #include "string-util.h"
+ #include "string-table.h"
+ #include "strv.h"
+@@ -179,7 +181,7 @@ int cpu_set_add_all(CPUSet *a, const CPUSet *b) {
+                                 return r;
+                 }
+ 
+-        return 0;
++        return 1;
+ }
+ 
+ int parse_cpu_set_full(
+@@ -264,7 +266,7 @@ int parse_cpu_set_extend(
+         if (!old->set) {
+                 *old = cpuset;
+                 cpuset = (CPUSet) {};
+-                return 0;
++                return 1;
+         }
+ 
+         return cpu_set_add_all(old, &cpuset);
+@@ -417,6 +419,43 @@ int apply_numa_policy(const NUMAPolicy *policy) {
+         return 0;
+ }
+ 
++int numa_to_cpu_set(const NUMAPolicy *policy, CPUSet *ret) {
++        int r;
++        size_t i;
++        _cleanup_(cpu_set_reset) CPUSet s = {};
++
++        assert(policy);
++        assert(ret);
++
++        for (i = 0; i < policy->nodes.allocated * 8; i++) {
++                _cleanup_free_ char *l = NULL;
++                char p[STRLEN("/sys/devices/system/node/node//cpulist") + DECIMAL_STR_MAX(size_t) + 1];
++                _cleanup_(cpu_set_reset) CPUSet part = {};
++
++                if (!CPU_ISSET_S(i, policy->nodes.allocated, policy->nodes.set))
++                        continue;
++
++                xsprintf(p, "/sys/devices/system/node/node%zu/cpulist", i);
++
++                r = read_one_line_file(p, &l);
++                if (r < 0)
++                        return r;
++
++                r = parse_cpu_set(l, &part);
++                if (r < 0)
++                        return r;
++
++                r = cpu_set_add_all(&s, &part);
++                if (r < 0)
++                        return r;
++        }
++
++        *ret = s;
++        s = (CPUSet) {};
++
++        return 0;
++}
++
+ static const char* const mpol_table[] = {
+         [MPOL_DEFAULT]    = "default",
+         [MPOL_PREFERRED]  = "preferred",
+diff --git a/src/basic/cpu-set-util.h b/src/basic/cpu-set-util.h
+index 8519a9b6c8..795be807af 100644
+--- a/src/basic/cpu-set-util.h
++++ b/src/basic/cpu-set-util.h
+@@ -78,6 +78,7 @@ static inline void numa_policy_reset(NUMAPolicy *p) {
+ }
+ 
+ int apply_numa_policy(const NUMAPolicy *policy);
++int numa_to_cpu_set(const NUMAPolicy *policy, CPUSet *ret);
+ 
+ const char* mpol_to_string(int i) _const_;
+ int mpol_from_string(const char *s) _pure_;
+diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
+index d5acca384f..0fe4c14e48 100644
+--- a/src/core/dbus-execute.c
++++ b/src/core/dbus-execute.c
+@@ -58,6 +58,8 @@ static BUS_DEFINE_PROPERTY_GET2(property_get_ioprio_priority, "i", ExecContext,
+ static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_empty_string, "s", NULL);
+ static BUS_DEFINE_PROPERTY_GET_REF(property_get_syslog_level, "i", int, LOG_PRI);
+ static BUS_DEFINE_PROPERTY_GET_REF(property_get_syslog_facility, "i", int, LOG_FAC);
++static BUS_DEFINE_PROPERTY_GET(property_get_cpu_affinity_from_numa, "b", ExecContext, exec_context_get_cpu_affinity_from_numa);
++
+ 
+ static int property_get_environment_files(
+                 sd_bus *bus,
+@@ -215,6 +217,7 @@ static int property_get_cpu_affinity(
+                 sd_bus_error *error) {
+ 
+         ExecContext *c = userdata;
++        _cleanup_(cpu_set_reset) CPUSet s = {};
+         _cleanup_free_ uint8_t *array = NULL;
+         size_t allocated;
+ 
+@@ -222,7 +225,16 @@ static int property_get_cpu_affinity(
+         assert(reply);
+         assert(c);
+ 
+-        (void) cpu_set_to_dbus(&c->cpu_set, &array, &allocated);
++        if (c->cpu_affinity_from_numa) {
++                int r;
++
++                r = numa_to_cpu_set(&c->numa_policy, &s);
++                if (r < 0)
++                        return r;
++        }
++
++        (void) cpu_set_to_dbus(c->cpu_affinity_from_numa ? &s : &c->cpu_set,  &array, &allocated);
++
+         return sd_bus_message_append_array(reply, 'y', array, allocated);
+ }
+ 
+@@ -743,6 +755,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
+         SD_BUS_PROPERTY("CPUSchedulingPolicy", "i", property_get_cpu_sched_policy, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("CPUSchedulingPriority", "i", property_get_cpu_sched_priority, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("CPUAffinity", "ay", property_get_cpu_affinity, 0, SD_BUS_VTABLE_PROPERTY_CONST),
++        SD_BUS_PROPERTY("CPUAffinityFromNUMA", "b", property_get_cpu_affinity_from_numa, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("NUMAPolicy", "i", property_get_numa_policy, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("NUMAMask", "ay", property_get_numa_mask, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("TimerSlackNSec", "t", property_get_timer_slack_nsec, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+@@ -1639,6 +1652,20 @@ int bus_exec_context_set_transient_property(
+ 
+                 return 1;
+ 
++        } else if (streq(name, "CPUAffinityFromNUMA")) {
++                int q;
++
++                r = sd_bus_message_read_basic(message, 'b', &q);
++                if (r < 0)
++                        return r;
++
++                if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
++                        c->cpu_affinity_from_numa = q;
++                        unit_write_settingf(u, flags, name, "%s=%s", "CPUAffinity", "numa");
++                }
++
++                return 1;
++
+         } else if (streq(name, "NUMAPolicy")) {
+                 int32_t type;
+ 
+@@ -1653,6 +1680,7 @@ int bus_exec_context_set_transient_property(
+                         c->numa_policy.type = type;
+ 
+                 return 1;
++
+         } else if (streq(name, "IOSchedulingClass")) {
+                 int32_t q;
+ 
+diff --git a/src/core/execute.c b/src/core/execute.c
+index 3c54ac1110..d528d08830 100644
+--- a/src/core/execute.c
++++ b/src/core/execute.c
+@@ -2750,6 +2750,33 @@ static int compile_suggested_paths(const ExecContext *c, const ExecParameters *p
+ 
+ static char *exec_command_line(char **argv);
+ 
++static int exec_context_cpu_affinity_from_numa(const ExecContext *c, CPUSet *ret) {
++        _cleanup_(cpu_set_reset) CPUSet s = {};
++        int r;
++
++        assert(c);
++        assert(ret);
++
++        if (!c->numa_policy.nodes.set) {
++                log_debug("Can't derive CPU affinity mask from NUMA mask because NUMA mask is not set, ignoring");
++                return 0;
++        }
++
++        r = numa_to_cpu_set(&c->numa_policy, &s);
++        if (r < 0)
++                return r;
++
++        cpu_set_reset(ret);
++
++        return cpu_set_add_all(ret, &s);
++}
++
++bool exec_context_get_cpu_affinity_from_numa(const ExecContext *c) {
++        assert(c);
++
++        return c->cpu_affinity_from_numa;
++}
++
+ static int exec_child(
+                 Unit *unit,
+                 const ExecCommand *command,
+@@ -3012,11 +3039,26 @@ static int exec_child(
+                 }
+         }
+ 
+-        if (context->cpu_set.set)
+-                if (sched_setaffinity(0, context->cpu_set.allocated, context->cpu_set.set) < 0) {
++        if (context->cpu_affinity_from_numa || context->cpu_set.set) {
++                _cleanup_(cpu_set_reset) CPUSet converted_cpu_set = {};
++                const CPUSet *cpu_set;
++
++                if (context->cpu_affinity_from_numa) {
++                        r = exec_context_cpu_affinity_from_numa(context, &converted_cpu_set);
++                        if (r < 0) {
++                                *exit_status = EXIT_CPUAFFINITY;
++                                return log_unit_error_errno(unit, r, "Failed to derive CPU affinity mask from NUMA mask: %m");
++                        }
++
++                        cpu_set = &converted_cpu_set;
++                } else
++                        cpu_set = &context->cpu_set;
++
++                if (sched_setaffinity(0, cpu_set->allocated, cpu_set->set) < 0) {
+                         *exit_status = EXIT_CPUAFFINITY;
+                         return log_unit_error_errno(unit, errno, "Failed to set up CPU affinity: %m");
+                 }
++        }
+ 
+         if (mpol_is_valid(numa_policy_get_type(&context->numa_policy))) {
+                 r = apply_numa_policy(&context->numa_policy);
+diff --git a/src/core/execute.h b/src/core/execute.h
+index 86c1cee84c..62c6229621 100644
+--- a/src/core/execute.h
++++ b/src/core/execute.h
+@@ -152,6 +152,7 @@ struct ExecContext {
+ 
+         CPUSet cpu_set;
+         NUMAPolicy numa_policy;
++        bool cpu_affinity_from_numa;
+ 
+         ExecInput std_input;
+         ExecOutput std_output;
+@@ -375,6 +376,8 @@ int exec_runtime_deserialize_compat(Unit *u, const char *key, const char *value,
+ void exec_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds);
+ void exec_runtime_vacuum(Manager *m);
+ 
++bool exec_context_get_cpu_affinity_from_numa(const ExecContext *c);
++
+ const char* exec_output_to_string(ExecOutput i) _const_;
+ ExecOutput exec_output_from_string(const char *s) _pure_;
+ 
+diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
+index 33fdb82754..740401a582 100644
+--- a/src/core/load-fragment.c
++++ b/src/core/load-fragment.c
+@@ -1251,13 +1251,25 @@ int config_parse_exec_cpu_affinity(const char *unit,
+                                    void *userdata) {
+ 
+         ExecContext *c = data;
++        int r;
+ 
+         assert(filename);
+         assert(lvalue);
+         assert(rvalue);
+         assert(data);
+ 
+-        return parse_cpu_set_extend(rvalue, &c->cpu_set, true, unit, filename, line, lvalue);
++        if (streq(rvalue, "numa")) {
++                c->cpu_affinity_from_numa = true;
++                cpu_set_reset(&c->cpu_set);
++
++                return 0;
++        }
++
++        r = parse_cpu_set_extend(rvalue, &c->cpu_set, true, unit, filename, line, lvalue);
++        if (r >= 0)
++                c->cpu_affinity_from_numa = false;
++
++        return r;
+ }
+ 
+ int config_parse_capability_set(
+diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
+index 7029aa5615..daa2c2dce5 100644
+--- a/src/shared/bus-unit-util.c
++++ b/src/shared/bus-unit-util.c
+@@ -26,6 +26,8 @@
+ #include "securebits-util.h"
+ #include "signal-util.h"
+ #include "socket-protocol-list.h"
++#include "socket-util.h"
++#include "stdio-util.h"
+ #include "string-util.h"
+ #include "syslog-util.h"
+ #include "terminal-util.h"
+@@ -997,6 +999,13 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
+                 _cleanup_free_ uint8_t *array = NULL;
+                 size_t allocated;
+ 
++                if (eq && streq(eq, "numa")) {
++                        r = sd_bus_message_append(m, "(sv)", "CPUAffinityFromNUMA", "b", true);
++                        if (r < 0)
++                                return bus_log_create_error(r);
++                        return r;
++                }
++
+                 r = parse_cpu_set(eq, &cpuset);
+                 if (r < 0)
+                         return log_error_errno(r, "Failed to parse %s value: %s", field, eq);
+diff --git a/src/test/test-cpu-set-util.c b/src/test/test-cpu-set-util.c
+index 136eaca82d..1b7be5df4e 100644
+--- a/src/test/test-cpu-set-util.c
++++ b/src/test/test-cpu-set-util.c
+@@ -218,12 +218,12 @@ static void test_parse_cpu_set_extend(void) {
+ 
+         log_info("/* %s */", __func__);
+ 
+-        assert_se(parse_cpu_set_extend("1 3", &c, true, NULL, "fake", 1, "CPUAffinity") == 0);
++        assert_se(parse_cpu_set_extend("1 3", &c, true, NULL, "fake", 1, "CPUAffinity") == 1);
+         assert_se(CPU_COUNT_S(c.allocated, c.set) == 2);
+         assert_se(s1 = cpu_set_to_string(&c));
+         log_info("cpu_set_to_string: %s", s1);
+ 
+-        assert_se(parse_cpu_set_extend("4", &c, true, NULL, "fake", 1, "CPUAffinity") == 0);
++        assert_se(parse_cpu_set_extend("4", &c, true, NULL, "fake", 1, "CPUAffinity") == 1);
+         assert_se(CPU_COUNT_S(c.allocated, c.set) == 3);
+         assert_se(s2 = cpu_set_to_string(&c));
+         log_info("cpu_set_to_string: %s", s2);
+@@ -240,7 +240,7 @@ static void test_cpu_set_to_from_dbus(void) {
+ 
+         log_info("/* %s */", __func__);
+ 
+-        assert_se(parse_cpu_set_extend("1 3 8 100-200", &c, true, NULL, "fake", 1, "CPUAffinity") == 0);
++        assert_se(parse_cpu_set_extend("1 3 8 100-200", &c, true, NULL, "fake", 1, "CPUAffinity") == 1);
+         assert_se(s = cpu_set_to_string(&c));
+         log_info("cpu_set_to_string: %s", s);
+         assert_se(CPU_COUNT_S(c.allocated, c.set) == 104);
+diff --git a/test/TEST-36-NUMAPOLICY/testsuite.sh b/test/TEST-36-NUMAPOLICY/testsuite.sh
+index bffac4ffe6..7ccaa5b412 100755
+--- a/test/TEST-36-NUMAPOLICY/testsuite.sh
++++ b/test/TEST-36-NUMAPOLICY/testsuite.sh
+@@ -279,6 +279,18 @@ else
+     # Maks must be ignored
+     grep -E "set_mempolicy\((MPOL_LOCAL|0x4 [^,]*), NULL" $straceLog
+ 
++    echo "Unit file CPUAffinity=NUMA support"
++    writeTestUnitNUMAPolicy "bind" "0"
++    echo "CPUAffinity=numa" >> $testUnitNUMAConf
++    systemctl daemon-reload
++    systemctl start $testUnit
++    systemctlCheckNUMAProperties $testUnit "bind" "0"
++    pid=$(systemctl show --value -p MainPID $testUnit)
++    cpulist=$(cat /sys/devices/system/node/node0/cpulist)
++    affinity_systemd=$(systemctl show --value -p CPUAffinity $testUnit)
++    [ $cpulist = $affinity_systemd ]
++    pid1StopUnit $testUnit
++
+     echo "systemd-run NUMAPolicy support"
+     runUnit='numa-systemd-run-test.service'
+ 
+@@ -309,6 +321,12 @@ else
+     systemd-run -p NUMAPolicy=local -p NUMAMask=0 --unit $runUnit sleep 1000
+     systemctlCheckNUMAProperties $runUnit "local" ""
+     pid1StopUnit $runUnit
++
++    systemd-run -p NUMAPolicy=local -p NUMAMask=0 -p CPUAffinity=numa --unit $runUnit sleep 1000
++    systemctlCheckNUMAProperties $runUnit "local" ""
++    systemctl cat $runUnit | grep -q 'CPUAffinity=numa'
++    pid1StopUnit $runUnit
++
+ fi
+ 
+ # Cleanup
diff --git a/SOURCES/0458-basic-user-util-always-use-base-10-for-user-group-nu.patch b/SOURCES/0458-basic-user-util-always-use-base-10-for-user-group-nu.patch
new file mode 100644
index 0000000..59daa5f
--- /dev/null
+++ b/SOURCES/0458-basic-user-util-always-use-base-10-for-user-group-nu.patch
@@ -0,0 +1,93 @@
+From 57d2e6e64ba490054f8de1a2aad4ffae7778eddc Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Sun, 31 May 2020 18:21:09 +0200
+Subject: [PATCH] basic/user-util: always use base 10 for user/group numbers
+
+We would parse numbers with base prefixes as user identifiers. For example,
+"0x2b3bfa0" would be interpreted as UID==45334432 and "01750" would be
+interpreted as UID==1000. This parsing was used also in cases where either a
+user/group name or number may be specified. This means that names like
+0x2b3bfa0 would be ambiguous: they are a valid user name according to our
+documented relaxed rules, but they would also be parsed as numeric uids.
+
+This behaviour is definitely not expected by users, since tools generally only
+accept decimal numbers (e.g. id, getent passwd), while other tools only accept
+user names and thus will interpret such strings as user names without even
+attempting to convert them to numbers (su, ssh). So let's follow suit and only
+accept numbers in decimal notation. Effectively this means that we will reject
+such strings as a username/uid/groupname/gid where strict mode is used, and try
+to look up a user/group with such a name in relaxed mode.
+
+Since the function changed is fairly low-level and fairly widely used, this
+affects multiple tools: loginctl show-user/enable-linger/disable-linger foo',
+the third argument in sysusers.d, fourth and fifth arguments in tmpfiles.d,
+etc.
+
+Fixes #15985.
+
+(cherry picked from commit 156a5fd297b61bce31630d7a52c15614bf784843)
+
+Resolves: #1848373
+---
+ src/basic/parse-util.h    |  8 ++++++--
+ src/basic/user-util.c     |  2 +-
+ src/test/test-user-util.c | 10 ++++++++++
+ 3 files changed, 17 insertions(+), 3 deletions(-)
+
+diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h
+index f3267f4cfe..1fc1af7615 100644
+--- a/src/basic/parse-util.h
++++ b/src/basic/parse-util.h
+@@ -50,9 +50,13 @@ static inline int safe_atoux16(const char *s, uint16_t *ret) {
+ 
+ int safe_atoi16(const char *s, int16_t *ret);
+ 
+-static inline int safe_atou32(const char *s, uint32_t *ret_u) {
++static inline int safe_atou32_full(const char *s, unsigned base, uint32_t *ret_u) {
+         assert_cc(sizeof(uint32_t) == sizeof(unsigned));
+-        return safe_atou(s, (unsigned*) ret_u);
++        return safe_atou_full(s, base, (unsigned*) ret_u);
++}
++
++static inline int safe_atou32(const char *s, uint32_t *ret_u) {
++        return safe_atou32_full(s, 0, (unsigned*) ret_u);
+ }
+ 
+ static inline int safe_atoi32(const char *s, int32_t *ret_i) {
+diff --git a/src/basic/user-util.c b/src/basic/user-util.c
+index d92969c966..10eeb256cd 100644
+--- a/src/basic/user-util.c
++++ b/src/basic/user-util.c
+@@ -49,7 +49,7 @@ int parse_uid(const char *s, uid_t *ret) {
+         assert(s);
+ 
+         assert_cc(sizeof(uid_t) == sizeof(uint32_t));
+-        r = safe_atou32(s, &uid);
++        r = safe_atou32_full(s, 10, &uid);
+         if (r < 0)
+                 return r;
+ 
+diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c
+index 9114d30b8c..8bf3dcd567 100644
+--- a/src/test/test-user-util.c
++++ b/src/test/test-user-util.c
+@@ -46,9 +46,19 @@ static void test_parse_uid(void) {
+ 
+         r = parse_uid("65535", &uid);
+         assert_se(r == -ENXIO);
++        assert_se(uid == 100);
++
++        r = parse_uid("0x1234", &uid);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 100);
++
++        r = parse_uid("01234", &uid);
++        assert_se(r == 0);
++        assert_se(uid == 1234);
+ 
+         r = parse_uid("asdsdas", &uid);
+         assert_se(r == -EINVAL);
++        assert_se(uid == 1234);
+ }
+ 
+ static void test_uid_ptr(void) {
diff --git a/SOURCES/0459-parse-util-sometimes-it-is-useful-to-check-if-a-stri.patch b/SOURCES/0459-parse-util-sometimes-it-is-useful-to-check-if-a-stri.patch
new file mode 100644
index 0000000..7d41bb9
--- /dev/null
+++ b/SOURCES/0459-parse-util-sometimes-it-is-useful-to-check-if-a-stri.patch
@@ -0,0 +1,149 @@
+From 2fd9a21a8b6a93c4fb2747839766adca15faa008 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Thu, 14 Nov 2019 14:49:40 +0100
+Subject: [PATCH] parse-util: sometimes it is useful to check if a string is a
+ valid integer, but not actually parse it
+
+(cherry picked from commit 22810041c2200fe72b0e0c985d0e404f8b80f9e2)
+
+Related: #1848373
+---
+ src/basic/parse-util.c | 34 ++++++++++++++++++++--------------
+ 1 file changed, 20 insertions(+), 14 deletions(-)
+
+diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
+index 6becf85878..056e56765e 100644
+--- a/src/basic/parse-util.c
++++ b/src/basic/parse-util.c
+@@ -383,7 +383,6 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
+         unsigned long l;
+ 
+         assert(s);
+-        assert(ret_u);
+         assert(base <= 16);
+ 
+         /* strtoul() is happy to parse negative values, and silently
+@@ -407,7 +406,9 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
+         if ((unsigned long) (unsigned) l != l)
+                 return -ERANGE;
+ 
+-        *ret_u = (unsigned) l;
++        if (ret_u)
++                *ret_u = (unsigned) l;
++
+         return 0;
+ }
+ 
+@@ -416,7 +417,6 @@ int safe_atoi(const char *s, int *ret_i) {
+         long l;
+ 
+         assert(s);
+-        assert(ret_i);
+ 
+         errno = 0;
+         l = strtol(s, &x, 0);
+@@ -427,7 +427,9 @@ int safe_atoi(const char *s, int *ret_i) {
+         if ((long) (int) l != l)
+                 return -ERANGE;
+ 
+-        *ret_i = (int) l;
++        if (ret_i)
++                *ret_i = (int) l;
++
+         return 0;
+ }
+ 
+@@ -436,7 +438,6 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) {
+         unsigned long long l;
+ 
+         assert(s);
+-        assert(ret_llu);
+ 
+         s += strspn(s, WHITESPACE);
+ 
+@@ -449,7 +450,9 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) {
+         if (*s == '-')
+                 return -ERANGE;
+ 
+-        *ret_llu = l;
++        if (ret_llu)
++                *ret_llu = l;
++
+         return 0;
+ }
+ 
+@@ -458,7 +461,6 @@ int safe_atolli(const char *s, long long int *ret_lli) {
+         long long l;
+ 
+         assert(s);
+-        assert(ret_lli);
+ 
+         errno = 0;
+         l = strtoll(s, &x, 0);
+@@ -467,7 +469,9 @@ int safe_atolli(const char *s, long long int *ret_lli) {
+         if (!x || x == s || *x != 0)
+                 return -EINVAL;
+ 
+-        *ret_lli = l;
++        if (ret_lli)
++                *ret_lli = l;
++
+         return 0;
+ }
+ 
+@@ -476,7 +480,6 @@ int safe_atou8(const char *s, uint8_t *ret) {
+         unsigned long l;
+ 
+         assert(s);
+-        assert(ret);
+ 
+         s += strspn(s, WHITESPACE);
+ 
+@@ -491,7 +494,8 @@ int safe_atou8(const char *s, uint8_t *ret) {
+         if ((unsigned long) (uint8_t) l != l)
+                 return -ERANGE;
+ 
+-        *ret = (uint8_t) l;
++        if (ret)
++                *ret = (uint8_t) l;
+         return 0;
+ }
+ 
+@@ -525,7 +529,6 @@ int safe_atoi16(const char *s, int16_t *ret) {
+         long l;
+ 
+         assert(s);
+-        assert(ret);
+ 
+         errno = 0;
+         l = strtol(s, &x, 0);
+@@ -536,7 +539,9 @@ int safe_atoi16(const char *s, int16_t *ret) {
+         if ((long) (int16_t) l != l)
+                 return -ERANGE;
+ 
+-        *ret = (int16_t) l;
++        if (ret)
++                *ret = (int16_t) l;
++
+         return 0;
+ }
+ 
+@@ -546,7 +551,6 @@ int safe_atod(const char *s, double *ret_d) {
+         double d = 0;
+ 
+         assert(s);
+-        assert(ret_d);
+ 
+         loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
+         if (loc == (locale_t) 0)
+@@ -559,7 +563,9 @@ int safe_atod(const char *s, double *ret_d) {
+         if (!x || x == s || *x != 0)
+                 return -EINVAL;
+ 
+-        *ret_d = (double) d;
++        if (ret_d)
++                *ret_d = (double) d;
++
+         return 0;
+ }
+ 
diff --git a/SOURCES/0460-basic-parse-util-add-safe_atoux64.patch b/SOURCES/0460-basic-parse-util-add-safe_atoux64.patch
new file mode 100644
index 0000000..0405582
--- /dev/null
+++ b/SOURCES/0460-basic-parse-util-add-safe_atoux64.patch
@@ -0,0 +1,130 @@
+From bd47a98d3ce2c5e1d74deb7bc384e416a9070b96 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Thu, 9 Apr 2020 11:18:26 +0200
+Subject: [PATCH] basic/parse-util: add safe_atoux64()
+
+(cherry picked from commit ce51632a357d347737bf40d3817df331cd8874cb)
+
+Related: #1848373
+---
+ src/basic/parse-util.c     |  4 ++--
+ src/basic/parse-util.h     | 12 +++++++++++-
+ src/test/test-parse-util.c | 39 ++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 52 insertions(+), 3 deletions(-)
+
+diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
+index 056e56765e..67056c0434 100644
+--- a/src/basic/parse-util.c
++++ b/src/basic/parse-util.c
+@@ -433,7 +433,7 @@ int safe_atoi(const char *s, int *ret_i) {
+         return 0;
+ }
+ 
+-int safe_atollu(const char *s, long long unsigned *ret_llu) {
++int safe_atollu_full(const char *s, unsigned base, long long unsigned *ret_llu) {
+         char *x = NULL;
+         unsigned long long l;
+ 
+@@ -442,7 +442,7 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) {
+         s += strspn(s, WHITESPACE);
+ 
+         errno = 0;
+-        l = strtoull(s, &x, 0);
++        l = strtoull(s, &x, base);
+         if (errno > 0)
+                 return -errno;
+         if (!x || x == s || *x != 0)
+diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h
+index 1fc1af7615..8a49257050 100644
+--- a/src/basic/parse-util.h
++++ b/src/basic/parse-util.h
+@@ -33,7 +33,6 @@ static inline int safe_atou(const char *s, unsigned *ret_u) {
+ }
+ 
+ int safe_atoi(const char *s, int *ret_i);
+-int safe_atollu(const char *s, unsigned long long *ret_u);
+ int safe_atolli(const char *s, long long int *ret_i);
+ 
+ int safe_atou8(const char *s, uint8_t *ret);
+@@ -64,6 +63,12 @@ static inline int safe_atoi32(const char *s, int32_t *ret_i) {
+         return safe_atoi(s, (int*) ret_i);
+ }
+ 
++int safe_atollu_full(const char *s, unsigned base, long long unsigned *ret_llu);
++
++static inline int safe_atollu(const char *s, long long unsigned *ret_llu) {
++        return safe_atollu_full(s, 0, ret_llu);
++}
++
+ static inline int safe_atou64(const char *s, uint64_t *ret_u) {
+         assert_cc(sizeof(uint64_t) == sizeof(unsigned long long));
+         return safe_atollu(s, (unsigned long long*) ret_u);
+@@ -74,6 +79,11 @@ static inline int safe_atoi64(const char *s, int64_t *ret_i) {
+         return safe_atolli(s, (long long int*) ret_i);
+ }
+ 
++static inline int safe_atoux64(const char *s, uint64_t *ret) {
++        assert_cc(sizeof(int64_t) == sizeof(long long unsigned));
++        return safe_atollu_full(s, 16, (long long unsigned*) ret);
++}
++
+ #if LONG_MAX == INT_MAX
+ static inline int safe_atolu(const char *s, unsigned long *ret_u) {
+         assert_cc(sizeof(unsigned long) == sizeof(unsigned));
+diff --git a/src/test/test-parse-util.c b/src/test/test-parse-util.c
+index e9aef5e882..8b182d9bdc 100644
+--- a/src/test/test-parse-util.c
++++ b/src/test/test-parse-util.c
+@@ -561,6 +561,44 @@ static void test_safe_atoi64(void) {
+         assert_se(r == -EINVAL);
+ }
+ 
++static void test_safe_atoux64(void) {
++        int r;
++        uint64_t l;
++
++        r = safe_atoux64("12345", &l);
++        assert_se(r == 0);
++        assert_se(l == 0x12345);
++
++        r = safe_atoux64("  12345", &l);
++        assert_se(r == 0);
++        assert_se(l == 0x12345);
++
++        r = safe_atoux64("0x12345", &l);
++        assert_se(r == 0);
++        assert_se(l == 0x12345);
++
++        r = safe_atoux64("18446744073709551617", &l);
++        assert_se(r == -ERANGE);
++
++        r = safe_atoux64("-1", &l);
++        assert_se(r == -ERANGE);
++
++        r = safe_atoux64("  -1", &l);
++        assert_se(r == -ERANGE);
++
++        r = safe_atoux64("junk", &l);
++        assert_se(r == -EINVAL);
++
++        r = safe_atoux64("123x", &l);
++        assert_se(r == -EINVAL);
++
++        r = safe_atoux64("12.3", &l);
++        assert_se(r == -EINVAL);
++
++        r = safe_atoux64("", &l);
++        assert_se(r == -EINVAL);
++}
++
+ static void test_safe_atod(void) {
+         int r;
+         double d;
+@@ -836,6 +874,7 @@ int main(int argc, char *argv[]) {
+         test_safe_atoux16();
+         test_safe_atou64();
+         test_safe_atoi64();
++        test_safe_atoux64();
+         test_safe_atod();
+         test_parse_percent();
+         test_parse_percent_unbounded();
diff --git a/SOURCES/0461-parse-util-allow-tweaking-how-to-parse-integers.patch b/SOURCES/0461-parse-util-allow-tweaking-how-to-parse-integers.patch
new file mode 100644
index 0000000..3e97f8b
--- /dev/null
+++ b/SOURCES/0461-parse-util-allow-tweaking-how-to-parse-integers.patch
@@ -0,0 +1,137 @@
+From 1d11e79fefea34b4395043e8e951414c5b7817ba Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Mon, 1 Jun 2020 17:06:19 +0200
+Subject: [PATCH] parse-util: allow tweaking how to parse integers
+
+This allows disabling a few alternative ways to decode integers
+formatted as strings, for safety reasons.
+
+See: #15991
+(cherry picked from commit 707e93aff8f358f8a62117e54b857530d6594e4b)
+
+Related: #1848373
+---
+ src/basic/parse-util.c | 65 +++++++++++++++++++++++++++++++++---------
+ src/basic/parse-util.h |  6 ++++
+ 2 files changed, 58 insertions(+), 13 deletions(-)
+
+diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
+index 67056c0434..6cc4fc3e57 100644
+--- a/src/basic/parse-util.c
++++ b/src/basic/parse-util.c
+@@ -383,20 +383,35 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
+         unsigned long l;
+ 
+         assert(s);
+-        assert(base <= 16);
++        assert(SAFE_ATO_MASK_FLAGS(base) <= 16);
+ 
+-        /* strtoul() is happy to parse negative values, and silently
+-         * converts them to unsigned values without generating an
+-         * error. We want a clean error, hence let's look for the "-"
+-         * prefix on our own, and generate an error. But let's do so
+-         * only after strtoul() validated that the string is clean
+-         * otherwise, so that we return EINVAL preferably over
+-         * ERANGE. */
++        /* strtoul() is happy to parse negative values, and silently converts them to unsigned values without
++         * generating an error. We want a clean error, hence let's look for the "-" prefix on our own, and
++         * generate an error. But let's do so only after strtoul() validated that the string is clean
++         * otherwise, so that we return EINVAL preferably over ERANGE. */
++
++        if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_WHITESPACE) &&
++            strchr(WHITESPACE, s[0]))
++                return -EINVAL;
+ 
+         s += strspn(s, WHITESPACE);
+ 
++        if (FLAGS_SET(base, SAFE_ATO_REFUSE_PLUS_MINUS) &&
++            IN_SET(s[0], '+', '-'))
++                return -EINVAL; /* Note that we check the "-" prefix again a second time below, but return a
++                                 * different error. I.e. if the SAFE_ATO_REFUSE_PLUS_MINUS flag is set we
++                                 * blanket refuse +/- prefixed integers, while if it is missing we'll just
++                                 * return ERANGE, because the string actually parses correctly, but doesn't
++                                 * fit in the return type. */
++
++        if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_ZERO) &&
++            s[0] == '0' && !streq(s, "0"))
++                return -EINVAL; /* This is particularly useful to avoid ambiguities between C's octal
++                                 * notation and assumed-to-be-decimal integers with a leading zero. */
++
+         errno = 0;
+-        l = strtoul(s, &x, base);
++        l = strtoul(s, &x, SAFE_ATO_MASK_FLAGS(base) /* Let's mask off the flags bits so that only the actual
++                                                      * base is left */);
+         if (errno > 0)
+                 return -errno;
+         if (!x || x == s || *x != 0)
+@@ -438,11 +453,24 @@ int safe_atollu_full(const char *s, unsigned base, long long unsigned *ret_llu)
+         unsigned long long l;
+ 
+         assert(s);
++        assert(SAFE_ATO_MASK_FLAGS(base) <= 16);
++
++        if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_WHITESPACE) &&
++            strchr(WHITESPACE, s[0]))
++                return -EINVAL;
+ 
+         s += strspn(s, WHITESPACE);
+ 
++        if (FLAGS_SET(base, SAFE_ATO_REFUSE_PLUS_MINUS) &&
++            IN_SET(s[0], '+', '-'))
++                return -EINVAL;
++
++        if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_ZERO) &&
++            s[0] == '0' && s[1] != 0)
++                return -EINVAL;
++
+         errno = 0;
+-        l = strtoull(s, &x, base);
++        l = strtoull(s, &x, SAFE_ATO_MASK_FLAGS(base));
+         if (errno > 0)
+                 return -errno;
+         if (!x || x == s || *x != 0)
+@@ -504,13 +532,24 @@ int safe_atou16_full(const char *s, unsigned base, uint16_t *ret) {
+         unsigned long l;
+ 
+         assert(s);
+-        assert(ret);
+-        assert(base <= 16);
++        assert(SAFE_ATO_MASK_FLAGS(base) <= 16);
++
++        if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_WHITESPACE) &&
++            strchr(WHITESPACE, s[0]))
++                return -EINVAL;
+ 
+         s += strspn(s, WHITESPACE);
+ 
++        if (FLAGS_SET(base, SAFE_ATO_REFUSE_PLUS_MINUS) &&
++            IN_SET(s[0], '+', '-'))
++                return -EINVAL;
++
++        if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_ZERO) &&
++            s[0] == '0' && s[1] != 0)
++                return -EINVAL;
++
+         errno = 0;
+-        l = strtoul(s, &x, base);
++        l = strtoul(s, &x, SAFE_ATO_MASK_FLAGS(base));
+         if (errno > 0)
+                 return -errno;
+         if (!x || x == s || *x != 0)
+diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h
+index 8a49257050..c6bbc98dff 100644
+--- a/src/basic/parse-util.h
++++ b/src/basic/parse-util.h
+@@ -26,6 +26,12 @@ int parse_syscall_and_errno(const char *in, char **name, int *error);
+ #define FORMAT_BYTES_MAX 8
+ char *format_bytes(char *buf, size_t l, uint64_t t);
+ 
++#define SAFE_ATO_REFUSE_PLUS_MINUS (1U << 30)
++#define SAFE_ATO_REFUSE_LEADING_ZERO (1U << 29)
++#define SAFE_ATO_REFUSE_LEADING_WHITESPACE (1U << 28)
++#define SAFE_ATO_ALL_FLAGS (SAFE_ATO_REFUSE_PLUS_MINUS|SAFE_ATO_REFUSE_LEADING_ZERO|SAFE_ATO_REFUSE_LEADING_WHITESPACE)
++#define SAFE_ATO_MASK_FLAGS(base) ((base) & ~SAFE_ATO_ALL_FLAGS)
++
+ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u);
+ 
+ static inline int safe_atou(const char *s, unsigned *ret_u) {
diff --git a/SOURCES/0462-parse-util-allow-0-as-alternative-to-0-and-0.patch b/SOURCES/0462-parse-util-allow-0-as-alternative-to-0-and-0.patch
new file mode 100644
index 0000000..29884dc
--- /dev/null
+++ b/SOURCES/0462-parse-util-allow-0-as-alternative-to-0-and-0.patch
@@ -0,0 +1,60 @@
+From 1c8e5070d8a88f35b5577e091de66727fa785ef7 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Mon, 1 Jun 2020 17:08:38 +0200
+Subject: [PATCH] parse-util: allow '-0' as alternative to '0' and '+0'
+
+Let's allow "-0" as alternative to "+0" and "0" when parsing integers,
+unless the new SAFE_ATO_REFUSE_PLUS_MINUS flag is specified.
+
+In cases where allowing the +/- syntax shall not be allowed
+SAFE_ATO_REFUSE_PLUS_MINUS is the right flag to use, but this also means
+that -0 as only negative integer that fits into an unsigned value should
+be acceptable if the flag is not specified.
+
+(cherry picked from commit c78eefc13562a8fc0c22c00a6d3001af89860258)
+
+Related: #1848373
+---
+ src/basic/parse-util.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
+index 6cc4fc3e57..53d181dd60 100644
+--- a/src/basic/parse-util.c
++++ b/src/basic/parse-util.c
+@@ -416,7 +416,7 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
+                 return -errno;
+         if (!x || x == s || *x != 0)
+                 return -EINVAL;
+-        if (s[0] == '-')
++        if (l != 0 && s[0] == '-')
+                 return -ERANGE;
+         if ((unsigned long) (unsigned) l != l)
+                 return -ERANGE;
+@@ -475,7 +475,7 @@ int safe_atollu_full(const char *s, unsigned base, long long unsigned *ret_llu)
+                 return -errno;
+         if (!x || x == s || *x != 0)
+                 return -EINVAL;
+-        if (*s == '-')
++        if (l != 0 && s[0] == '-')
+                 return -ERANGE;
+ 
+         if (ret_llu)
+@@ -517,7 +517,7 @@ int safe_atou8(const char *s, uint8_t *ret) {
+                 return -errno;
+         if (!x || x == s || *x != 0)
+                 return -EINVAL;
+-        if (s[0] == '-')
++        if (l != 0 && s[0] == '-')
+                 return -ERANGE;
+         if ((unsigned long) (uint8_t) l != l)
+                 return -ERANGE;
+@@ -554,7 +554,7 @@ int safe_atou16_full(const char *s, unsigned base, uint16_t *ret) {
+                 return -errno;
+         if (!x || x == s || *x != 0)
+                 return -EINVAL;
+-        if (s[0] == '-')
++        if (l != 0 && s[0] == '-')
+                 return -ERANGE;
+         if ((unsigned long) (uint16_t) l != l)
+                 return -ERANGE;
diff --git a/SOURCES/0463-parse-util-make-return-parameter-optional-in-safe_at.patch b/SOURCES/0463-parse-util-make-return-parameter-optional-in-safe_at.patch
new file mode 100644
index 0000000..8cb4a65
--- /dev/null
+++ b/SOURCES/0463-parse-util-make-return-parameter-optional-in-safe_at.patch
@@ -0,0 +1,31 @@
+From 91ed5edcdea79773f6918e739637521e47129b07 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Mon, 1 Jun 2020 17:10:27 +0200
+Subject: [PATCH] parse-util: make return parameter optional in
+ safe_atou16_full()
+
+All other safe_atoXYZ_full() functions have the parameter optional,
+let's make it optoinal here, too.
+
+(cherry picked from commit aa85e4d3cef8ca8436e480bce9fa4ce72876b636)
+
+Related: #1848373
+---
+ src/basic/parse-util.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
+index 53d181dd60..7a7cefe6ff 100644
+--- a/src/basic/parse-util.c
++++ b/src/basic/parse-util.c
+@@ -559,7 +559,9 @@ int safe_atou16_full(const char *s, unsigned base, uint16_t *ret) {
+         if ((unsigned long) (uint16_t) l != l)
+                 return -ERANGE;
+ 
+-        *ret = (uint16_t) l;
++        if (ret)
++                *ret = (uint16_t) l;
++
+         return 0;
+ }
+ 
diff --git a/SOURCES/0464-parse-util-rewrite-parse_mode-on-top-of-safe_atou_fu.patch b/SOURCES/0464-parse-util-rewrite-parse_mode-on-top-of-safe_atou_fu.patch
new file mode 100644
index 0000000..318e18b
--- /dev/null
+++ b/SOURCES/0464-parse-util-rewrite-parse_mode-on-top-of-safe_atou_fu.patch
@@ -0,0 +1,59 @@
+From 147a3696b45a872e0e21fb74e1497f02543ce871 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Mon, 1 Jun 2020 17:16:04 +0200
+Subject: [PATCH] parse-util: rewrite parse_mode() on top of safe_atou_full()
+
+Parsing is hard, hence let's use our own careful wrappers wherever
+possible.
+
+(cherry picked from commit c44702a8bd8cc8b7f2f1df21db9308d9af7dda5b)
+
+Related: #1848373
+---
+ src/basic/parse-util.c | 28 +++++++++++++---------------
+ 1 file changed, 13 insertions(+), 15 deletions(-)
+
+diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
+index 7a7cefe6ff..68c156c543 100644
+--- a/src/basic/parse-util.c
++++ b/src/basic/parse-util.c
+@@ -54,26 +54,24 @@ int parse_pid(const char *s, pid_t* ret_pid) {
+ }
+ 
+ int parse_mode(const char *s, mode_t *ret) {
+-        char *x;
+-        long l;
++        unsigned m;
++        int r;
+ 
+         assert(s);
+-        assert(ret);
+ 
+-        s += strspn(s, WHITESPACE);
+-        if (s[0] == '-')
+-                return -ERANGE;
+-
+-        errno = 0;
+-        l = strtol(s, &x, 8);
+-        if (errno > 0)
+-                return -errno;
+-        if (!x || x == s || *x != 0)
+-                return -EINVAL;
+-        if (l < 0 || l  > 07777)
++        r = safe_atou_full(s, 8 |
++                           SAFE_ATO_REFUSE_PLUS_MINUS, /* Leading '+' or even '-' char? that's just weird,
++                                                        * refuse. User might have wanted to add mode flags or
++                                                        * so, but this parser doesn't allow that, so let's
++                                                        * better be safe. */
++                           &m);
++        if (r < 0)
++                return r;
++        if (m > 07777)
+                 return -ERANGE;
+ 
+-        *ret = (mode_t) l;
++        if (ret)
++                *ret = m;
+         return 0;
+ }
+ 
diff --git a/SOURCES/0465-user-util-be-stricter-in-parse_uid.patch b/SOURCES/0465-user-util-be-stricter-in-parse_uid.patch
new file mode 100644
index 0000000..79cb570
--- /dev/null
+++ b/SOURCES/0465-user-util-be-stricter-in-parse_uid.patch
@@ -0,0 +1,78 @@
+From 87c22d3bb794118d25bc138108fd5bdd607365ef Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Mon, 1 Jun 2020 17:16:46 +0200
+Subject: [PATCH] user-util: be stricter in parse_uid()
+
+Let's refuse "+" and "-" prefixed UIDs. Let's refuse whitespace-prefixed
+UIDS, Let's refuse zero-prefixed UIDs. Let's be safe than sorry.
+
+(cherry picked from commit f5979b63cc305ba217dfd174b1bf0583bcf75a73)
+
+Related: #1848373
+---
+ src/basic/user-util.c     | 10 +++++++++-
+ src/test/test-user-util.c | 26 +++++++++++++++++++++++---
+ 2 files changed, 32 insertions(+), 4 deletions(-)
+
+diff --git a/src/basic/user-util.c b/src/basic/user-util.c
+index 10eeb256cd..40f4e45db6 100644
+--- a/src/basic/user-util.c
++++ b/src/basic/user-util.c
+@@ -49,7 +49,15 @@ int parse_uid(const char *s, uid_t *ret) {
+         assert(s);
+ 
+         assert_cc(sizeof(uid_t) == sizeof(uint32_t));
+-        r = safe_atou32_full(s, 10, &uid);
++
++        /* We are very strict when parsing UIDs, and prohibit +/- as prefix, leading zero as prefix, and
++         * whitespace. We do this, since this call is often used in a context where we parse things as UID
++         * first, and if that doesn't work we fall back to NSS. Thus we really want to make sure that UIDs
++         * are parsed as UIDs only if they really really look like UIDs. */
++        r = safe_atou32_full(s, 10
++                             | SAFE_ATO_REFUSE_PLUS_MINUS
++                             | SAFE_ATO_REFUSE_LEADING_ZERO
++                             | SAFE_ATO_REFUSE_LEADING_WHITESPACE, &uid);
+         if (r < 0)
+                 return r;
+ 
+diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c
+index 8bf3dcd567..99203f7e48 100644
+--- a/src/test/test-user-util.c
++++ b/src/test/test-user-util.c
+@@ -52,13 +52,33 @@ static void test_parse_uid(void) {
+         assert_se(r == -EINVAL);
+         assert_se(uid == 100);
+ 
++        r = parse_uid("+1234", &uid);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 100);
++
++        r = parse_uid("-1234", &uid);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 100);
++
++        r = parse_uid(" 1234", &uid);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 100);
++
+         r = parse_uid("01234", &uid);
+-        assert_se(r == 0);
+-        assert_se(uid == 1234);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 100);
++
++        r = parse_uid("-0", &uid);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 100);
++
++        r = parse_uid("+0", &uid);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 100);
+ 
+         r = parse_uid("asdsdas", &uid);
+         assert_se(r == -EINVAL);
+-        assert_se(uid == 1234);
++        assert_se(uid == 100);
+ }
+ 
+ static void test_uid_ptr(void) {
diff --git a/SOURCES/0466-strv-add-new-macro-STARTSWITH_SET.patch b/SOURCES/0466-strv-add-new-macro-STARTSWITH_SET.patch
new file mode 100644
index 0000000..58a4b5a
--- /dev/null
+++ b/SOURCES/0466-strv-add-new-macro-STARTSWITH_SET.patch
@@ -0,0 +1,75 @@
+From 50b103a982dfd6f1b2bf98bbc98a8063fa153e89 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Fri, 23 Nov 2018 16:27:15 +0100
+Subject: [PATCH] strv: add new macro STARTSWITH_SET()
+
+This is to startswith() what PATH_STARTSWITH_SET() is to
+path_startswith().
+
+Or in other words, checks if the specified string has any of the listed
+prefixes, and if so, returns the remainder of the string.
+
+(cherry picked from commit 52f1552073047195d51901f7e5a5a4fa3189034e)
+
+Related: #1848373
+---
+ src/basic/strv.h     | 12 ++++++++++++
+ src/test/test-strv.c | 15 +++++++++++++++
+ 2 files changed, 27 insertions(+)
+
+diff --git a/src/basic/strv.h b/src/basic/strv.h
+index 51d03db940..c1e4c973b6 100644
+--- a/src/basic/strv.h
++++ b/src/basic/strv.h
+@@ -136,6 +136,18 @@ void strv_print(char **l);
+                 _x && strv_contains(STRV_MAKE(__VA_ARGS__), _x); \
+         })
+ 
++#define STARTSWITH_SET(p, ...)                                  \
++        ({                                                      \
++                const char *_p = (p);                           \
++                char  *_found = NULL, **_i;                     \
++                STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) {      \
++                        _found = startswith(_p, *_i);           \
++                        if (_found)                             \
++                                break;                          \
++                }                                               \
++                _found;                                         \
++        })
++
+ #define FOREACH_STRING(x, ...)                               \
+         for (char **_l = ({                                  \
+                 char **_ll = STRV_MAKE(__VA_ARGS__);         \
+diff --git a/src/test/test-strv.c b/src/test/test-strv.c
+index 1c192239a2..79d999d3ed 100644
+--- a/src/test/test-strv.c
++++ b/src/test/test-strv.c
+@@ -56,6 +56,20 @@ static void test_strptr_in_set(void) {
+         assert_se(!STRPTR_IN_SET(NULL, NULL));
+ }
+ 
++static void test_startswith_set(void) {
++        assert_se(!STARTSWITH_SET("foo", "bar", "baz", "waldo"));
++        assert_se(!STARTSWITH_SET("foo", "bar"));
++
++        assert_se(STARTSWITH_SET("abc", "a", "ab", "abc"));
++        assert_se(STARTSWITH_SET("abc", "ax", "ab", "abc"));
++        assert_se(STARTSWITH_SET("abc", "ax", "abx", "abc"));
++        assert_se(!STARTSWITH_SET("abc", "ax", "abx", "abcx"));
++
++        assert_se(streq_ptr(STARTSWITH_SET("foobar", "hhh", "kkk", "foo", "zzz"), "bar"));
++        assert_se(streq_ptr(STARTSWITH_SET("foobar", "hhh", "kkk", "", "zzz"), "foobar"));
++        assert_se(streq_ptr(STARTSWITH_SET("", "hhh", "kkk", "zzz", ""), ""));
++}
++
+ static const char* const input_table_multiple[] = {
+         "one",
+         "two",
+@@ -700,6 +714,7 @@ int main(int argc, char *argv[]) {
+         test_specifier_printf();
+         test_str_in_set();
+         test_strptr_in_set();
++        test_startswith_set();
+         test_strv_foreach();
+         test_strv_foreach_backwards();
+         test_strv_foreach_pair();
diff --git a/SOURCES/0467-parse-util-also-parse-integers-prefixed-with-0b-and-.patch b/SOURCES/0467-parse-util-also-parse-integers-prefixed-with-0b-and-.patch
new file mode 100644
index 0000000..d088a5a
--- /dev/null
+++ b/SOURCES/0467-parse-util-also-parse-integers-prefixed-with-0b-and-.patch
@@ -0,0 +1,164 @@
+From e67e29d91a1ef90af545e4130c7b4c4cfde6202a Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Mon, 1 Jun 2020 17:31:51 +0200
+Subject: [PATCH] parse-util: also parse integers prefixed with 0b and 0o
+
+Let's adopt Python 3 style 0b and 0x syntaxes, because it makes a ton of
+sense, in particular in bitmask settings.
+
+(cherry picked from commit fc80cabcf584a8b486bdff5be0c074fec4059cdc)
+
+Related: #1848373
+---
+ src/basic/parse-util.c | 56 ++++++++++++++++++++++++++++++++++++++----
+ 1 file changed, 51 insertions(+), 5 deletions(-)
+
+diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
+index 68c156c543..992ea3605b 100644
+--- a/src/basic/parse-util.c
++++ b/src/basic/parse-util.c
+@@ -17,6 +17,7 @@
+ #include "parse-util.h"
+ #include "process-util.h"
+ #include "string-util.h"
++#include "strv.h"
+ 
+ int parse_boolean(const char *v) {
+         assert(v);
+@@ -373,7 +374,32 @@ char *format_bytes(char *buf, size_t l, uint64_t t) {
+ finish:
+         buf[l-1] = 0;
+         return buf;
++}
++
++static const char *mangle_base(const char *s, unsigned *base) {
++        const char *k;
++
++        assert(s);
++        assert(base);
++
++        /* Base already explicitly specified, then don't do anything. */
++        if (SAFE_ATO_MASK_FLAGS(*base) != 0)
++                return s;
+ 
++        /* Support Python 3 style "0b" and 0x" prefixes, because they truly make sense, much more than C's "0" prefix for octal. */
++        k = STARTSWITH_SET(s, "0b", "0B");
++        if (k) {
++                *base = 2 | (*base & SAFE_ATO_ALL_FLAGS);
++                return k;
++        }
++
++        k = STARTSWITH_SET(s, "0o", "0O");
++        if (k) {
++                *base = 8 | (*base & SAFE_ATO_ALL_FLAGS);
++                return k;
++        }
++
++        return s;
+ }
+ 
+ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
+@@ -407,6 +433,8 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
+                 return -EINVAL; /* This is particularly useful to avoid ambiguities between C's octal
+                                  * notation and assumed-to-be-decimal integers with a leading zero. */
+ 
++        s = mangle_base(s, &base);
++
+         errno = 0;
+         l = strtoul(s, &x, SAFE_ATO_MASK_FLAGS(base) /* Let's mask off the flags bits so that only the actual
+                                                       * base is left */);
+@@ -426,13 +454,17 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
+ }
+ 
+ int safe_atoi(const char *s, int *ret_i) {
++        unsigned base = 0;
+         char *x = NULL;
+         long l;
+ 
+         assert(s);
+ 
++        s += strspn(s, WHITESPACE);
++        s = mangle_base(s, &base);
++
+         errno = 0;
+-        l = strtol(s, &x, 0);
++        l = strtol(s, &x, base);
+         if (errno > 0)
+                 return -errno;
+         if (!x || x == s || *x != 0)
+@@ -467,6 +499,8 @@ int safe_atollu_full(const char *s, unsigned base, long long unsigned *ret_llu)
+             s[0] == '0' && s[1] != 0)
+                 return -EINVAL;
+ 
++        s = mangle_base(s, &base);
++
+         errno = 0;
+         l = strtoull(s, &x, SAFE_ATO_MASK_FLAGS(base));
+         if (errno > 0)
+@@ -483,13 +517,17 @@ int safe_atollu_full(const char *s, unsigned base, long long unsigned *ret_llu)
+ }
+ 
+ int safe_atolli(const char *s, long long int *ret_lli) {
++        unsigned base = 0;
+         char *x = NULL;
+         long long l;
+ 
+         assert(s);
+ 
++        s += strspn(s, WHITESPACE);
++        s = mangle_base(s, &base);
++
+         errno = 0;
+-        l = strtoll(s, &x, 0);
++        l = strtoll(s, &x, base);
+         if (errno > 0)
+                 return -errno;
+         if (!x || x == s || *x != 0)
+@@ -502,15 +540,17 @@ int safe_atolli(const char *s, long long int *ret_lli) {
+ }
+ 
+ int safe_atou8(const char *s, uint8_t *ret) {
+-        char *x = NULL;
++        unsigned base = 0;
+         unsigned long l;
++        char *x = NULL;
+ 
+         assert(s);
+ 
+         s += strspn(s, WHITESPACE);
++        s = mangle_base(s, &base);
+ 
+         errno = 0;
+-        l = strtoul(s, &x, 0);
++        l = strtoul(s, &x, base);
+         if (errno > 0)
+                 return -errno;
+         if (!x || x == s || *x != 0)
+@@ -546,6 +586,8 @@ int safe_atou16_full(const char *s, unsigned base, uint16_t *ret) {
+             s[0] == '0' && s[1] != 0)
+                 return -EINVAL;
+ 
++        s = mangle_base(s, &base);
++
+         errno = 0;
+         l = strtoul(s, &x, SAFE_ATO_MASK_FLAGS(base));
+         if (errno > 0)
+@@ -564,13 +606,17 @@ int safe_atou16_full(const char *s, unsigned base, uint16_t *ret) {
+ }
+ 
+ int safe_atoi16(const char *s, int16_t *ret) {
++        unsigned base = 0;
+         char *x = NULL;
+         long l;
+ 
+         assert(s);
+ 
++        s += strspn(s, WHITESPACE);
++        s = mangle_base(s, &base);
++
+         errno = 0;
+-        l = strtol(s, &x, 0);
++        l = strtol(s, &x, base);
+         if (errno > 0)
+                 return -errno;
+         if (!x || x == s || *x != 0)
diff --git a/SOURCES/0468-tests-beef-up-integer-parsing-tests.patch b/SOURCES/0468-tests-beef-up-integer-parsing-tests.patch
new file mode 100644
index 0000000..c33c38f
--- /dev/null
+++ b/SOURCES/0468-tests-beef-up-integer-parsing-tests.patch
@@ -0,0 +1,204 @@
+From 457eada27f606e39f0efc6adc226542fd11eb815 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Mon, 1 Jun 2020 17:48:41 +0200
+Subject: [PATCH] tests: beef up integer parsing tests
+
+(cherry picked from commit 53c6db99fa4b52f97e19977f21d3133f8ceb3dcd)
+
+Related: #1848373
+---
+ src/test/test-parse-util.c | 58 ++++++++++++++++++++++++++++++++++++++
+ src/test/test-user-util.c  | 40 ++++++++++++++++++++++++++
+ 2 files changed, 98 insertions(+)
+
+diff --git a/src/test/test-parse-util.c b/src/test/test-parse-util.c
+index 8b182d9bdc..699499b665 100644
+--- a/src/test/test-parse-util.c
++++ b/src/test/test-parse-util.c
+@@ -75,14 +75,22 @@ static void test_parse_mode(void) {
+         mode_t m;
+ 
+         assert_se(parse_mode("-1", &m) < 0);
++        assert_se(parse_mode("+1", &m) < 0);
+         assert_se(parse_mode("", &m) < 0);
+         assert_se(parse_mode("888", &m) < 0);
+         assert_se(parse_mode("77777", &m) < 0);
+ 
+         assert_se(parse_mode("544", &m) >= 0 && m == 0544);
++        assert_se(parse_mode("0544", &m) >= 0 && m == 0544);
++        assert_se(parse_mode("00544", &m) >= 0 && m == 0544);
+         assert_se(parse_mode("777", &m) >= 0 && m == 0777);
++        assert_se(parse_mode("0777", &m) >= 0 && m == 0777);
++        assert_se(parse_mode("00777", &m) >= 0 && m == 0777);
+         assert_se(parse_mode("7777", &m) >= 0 && m == 07777);
++        assert_se(parse_mode("07777", &m) >= 0 && m == 07777);
++        assert_se(parse_mode("007777", &m) >= 0 && m == 07777);
+         assert_se(parse_mode("0", &m) >= 0 && m == 0);
++        assert_se(parse_mode(" 1", &m) >= 0 && m == 1);
+ }
+ 
+ static void test_parse_size(void) {
+@@ -358,6 +366,18 @@ static void test_safe_atolli(void) {
+         assert_se(r == 0);
+         assert_se(l == -12345);
+ 
++        r = safe_atolli("0x5", &l);
++        assert_se(r == 0);
++        assert_se(l == 5);
++
++        r = safe_atolli("0o6", &l);
++        assert_se(r == 0);
++        assert_se(l == 6);
++
++        r = safe_atolli("0B101", &l);
++        assert_se(r == 0);
++        assert_se(l == 5);
++
+         r = safe_atolli("12345678901234567890", &l);
+         assert_se(r == -ERANGE);
+ 
+@@ -431,6 +451,14 @@ static void test_safe_atoi16(void) {
+         assert_se(r == 0);
+         assert_se(l == 32767);
+ 
++        r = safe_atoi16("0o11", &l);
++        assert_se(r == 0);
++        assert_se(l == 9);
++
++        r = safe_atoi16("0B110", &l);
++        assert_se(r == 0);
++        assert_se(l == 6);
++
+         r = safe_atoi16("36536", &l);
+         assert_se(r == -ERANGE);
+ 
+@@ -475,6 +503,13 @@ static void test_safe_atoux16(void) {
+         r = safe_atoux16("  -1", &l);
+         assert_se(r == -ERANGE);
+ 
++        r = safe_atoux16("0b1", &l);
++        assert_se(r == 0);
++        assert_se(l == 177);
++
++        r = safe_atoux16("0o70", &l);
++        assert_se(r == -EINVAL);
++
+         r = safe_atoux16("junk", &l);
+         assert_se(r == -EINVAL);
+ 
+@@ -500,6 +535,14 @@ static void test_safe_atou64(void) {
+         assert_se(r == 0);
+         assert_se(l == 12345);
+ 
++        r = safe_atou64("0o11", &l);
++        assert_se(r == 0);
++        assert_se(l == 9);
++
++        r = safe_atou64("0b11", &l);
++        assert_se(r == 0);
++        assert_se(l == 3);
++
+         r = safe_atou64("18446744073709551617", &l);
+         assert_se(r == -ERANGE);
+ 
+@@ -542,6 +585,14 @@ static void test_safe_atoi64(void) {
+         assert_se(r == 0);
+         assert_se(l == 32767);
+ 
++        r = safe_atoi64("  0o20", &l);
++        assert_se(r == 0);
++        assert_se(l == 16);
++
++        r = safe_atoi64("  0b01010", &l);
++        assert_se(r == 0);
++        assert_se(l == 10);
++
+         r = safe_atoi64("9223372036854775813", &l);
+         assert_se(r == -ERANGE);
+ 
+@@ -577,6 +628,13 @@ static void test_safe_atoux64(void) {
+         assert_se(r == 0);
+         assert_se(l == 0x12345);
+ 
++        r = safe_atoux64("0b11011", &l);
++        assert_se(r == 0);
++        assert_se(l == 11603985);
++
++        r = safe_atoux64("0o11011", &l);
++        assert_se(r == -EINVAL);
++
+         r = safe_atoux64("18446744073709551617", &l);
+         assert_se(r == -ERANGE);
+ 
+diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c
+index 99203f7e48..04e86f5ac3 100644
+--- a/src/test/test-user-util.c
++++ b/src/test/test-user-util.c
+@@ -40,6 +40,22 @@ static void test_parse_uid(void) {
+ 
+         log_info("/* %s */", __func__);
+ 
++        r = parse_uid("0", &uid);
++        assert_se(r == 0);
++        assert_se(uid == 0);
++
++        r = parse_uid("1", &uid);
++        assert_se(r == 0);
++        assert_se(uid == 1);
++
++        r = parse_uid("01", &uid);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 1);
++
++        r = parse_uid("001", &uid);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 1);
++
+         r = parse_uid("100", &uid);
+         assert_se(r == 0);
+         assert_se(uid == 100);
+@@ -52,6 +68,14 @@ static void test_parse_uid(void) {
+         assert_se(r == -EINVAL);
+         assert_se(uid == 100);
+ 
++        r = parse_uid("0o1234", &uid);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 100);
++
++        r = parse_uid("0b1234", &uid);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 100);
++
+         r = parse_uid("+1234", &uid);
+         assert_se(r == -EINVAL);
+         assert_se(uid == 100);
+@@ -68,6 +92,14 @@ static void test_parse_uid(void) {
+         assert_se(r == -EINVAL);
+         assert_se(uid == 100);
+ 
++        r = parse_uid("001234", &uid);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 100);
++
++        r = parse_uid("0001234", &uid);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 100);
++
+         r = parse_uid("-0", &uid);
+         assert_se(r == -EINVAL);
+         assert_se(uid == 100);
+@@ -76,6 +108,14 @@ static void test_parse_uid(void) {
+         assert_se(r == -EINVAL);
+         assert_se(uid == 100);
+ 
++        r = parse_uid("00", &uid);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 100);
++
++        r = parse_uid("000", &uid);
++        assert_se(r == -EINVAL);
++        assert_se(uid == 100);
++
+         r = parse_uid("asdsdas", &uid);
+         assert_se(r == -EINVAL);
+         assert_se(uid == 100);
diff --git a/SOURCES/0469-shared-user-util-add-compat-forms-of-user-name-check.patch b/SOURCES/0469-shared-user-util-add-compat-forms-of-user-name-check.patch
new file mode 100644
index 0000000..eefc47b
--- /dev/null
+++ b/SOURCES/0469-shared-user-util-add-compat-forms-of-user-name-check.patch
@@ -0,0 +1,270 @@
+From 1e4ec1b29d15684a305bbc9ab54c6c8321504e7b Mon Sep 17 00:00:00 2001
+From: David Tardon <dtardon@redhat.com>
+Date: Tue, 27 Oct 2020 10:31:05 +0100
+Subject: [PATCH] shared/user-util: add compat forms of user name checking
+ functions
+
+New functions are called valid_user_group_name_compat() and
+valid_user_group_name_or_id_compat() and accept dots in the user
+or group name. No functional change except the tests.
+
+(cherry picked from commit 1a29610f5fa1bcb2eeb37d2c6b79d8d1a6dbb865)
+
+This completes previous partial cherry-pick of the same commit (commit
+76176de0889c3e8b9b3a176da24e4f8dbbd380a3).
+
+Related: #1848373
+---
+ src/basic/user-util.c     | 32 +++++++-------
+ src/basic/user-util.h     | 16 ++++++-
+ src/test/test-user-util.c | 91 +++++++++++++++++++++++++++++++++++++--
+ 3 files changed, 117 insertions(+), 22 deletions(-)
+
+diff --git a/src/basic/user-util.c b/src/basic/user-util.c
+index 40f4e45db6..03cbbc2503 100644
+--- a/src/basic/user-util.c
++++ b/src/basic/user-util.c
+@@ -576,7 +576,7 @@ int take_etc_passwd_lock(const char *root) {
+         return fd;
+ }
+ 
+-bool valid_user_group_name(const char *u) {
++bool valid_user_group_name_full(const char *u, bool strict) {
+         const char *i;
+         long sz;
+ 
+@@ -585,12 +585,12 @@ bool valid_user_group_name(const char *u) {
+          *
+          * - We require that names fit into the appropriate utmp field
+          * - We don't allow empty user names
++         * - No dots or digits in the first character
+          *
+-         * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters.
++         * If strict==true, additionally:
++         * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator)
+          *
+-         * jsynacek: We now allow dots in user names. The checks are not exhaustive as user names like "..." are allowed
+-         * and valid according to POSIX, but can't be created using useradd. However, ".user" can be created. Let's not
+-         * complicate the code by adding additional checks for weird corner cases like these,  as they don't really matter here.
++         * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters.
+          */
+ 
+         if (isempty(u))
+@@ -598,16 +598,16 @@ bool valid_user_group_name(const char *u) {
+ 
+         if (!(u[0] >= 'a' && u[0] <= 'z') &&
+             !(u[0] >= 'A' && u[0] <= 'Z') &&
+-            u[0] != '_' && u[0] != '.')
++            u[0] != '_')
+                 return false;
+ 
+-        for (i = u+1; *i; i++) {
+-                if (!(*i >= 'a' && *i <= 'z') &&
+-                    !(*i >= 'A' && *i <= 'Z') &&
+-                    !(*i >= '0' && *i <= '9') &&
+-                    !IN_SET(*i, '_', '-', '.'))
++        for (i = u+1; *i; i++)
++                if (!((*i >= 'a' && *i <= 'z') ||
++                      (*i >= 'A' && *i <= 'Z') ||
++                      (*i >= '0' && *i <= '9') ||
++                      IN_SET(*i, '_', '-') ||
++                      (!strict && *i == '.')))
+                         return false;
+-        }
+ 
+         sz = sysconf(_SC_LOGIN_NAME_MAX);
+         assert_se(sz > 0);
+@@ -621,15 +621,15 @@ bool valid_user_group_name(const char *u) {
+         return true;
+ }
+ 
+-bool valid_user_group_name_or_id(const char *u) {
++bool valid_user_group_name_or_id_full(const char *u, bool strict) {
+ 
+-        /* Similar as above, but is also fine with numeric UID/GID specifications, as long as they are in the right
+-         * range, and not the invalid user ids. */
++        /* Similar as above, but is also fine with numeric UID/GID specifications, as long as they are in the
++         * right range, and not the invalid user ids. */
+ 
+         if (isempty(u))
+                 return false;
+ 
+-        if (valid_user_group_name(u))
++        if (valid_user_group_name_full(u, strict))
+                 return true;
+ 
+         return parse_uid(u, NULL) >= 0;
+diff --git a/src/basic/user-util.h b/src/basic/user-util.h
+index b74f168859..5ad0b2a2f9 100644
+--- a/src/basic/user-util.h
++++ b/src/basic/user-util.h
+@@ -78,8 +78,20 @@ static inline bool userns_supported(void) {
+         return access("/proc/self/uid_map", F_OK) >= 0;
+ }
+ 
+-bool valid_user_group_name(const char *u);
+-bool valid_user_group_name_or_id(const char *u);
++bool valid_user_group_name_full(const char *u, bool strict);
++bool valid_user_group_name_or_id_full(const char *u, bool strict);
++static inline bool valid_user_group_name(const char *u) {
++        return valid_user_group_name_full(u, true);
++}
++static inline bool valid_user_group_name_or_id(const char *u) {
++        return valid_user_group_name_or_id_full(u, true);
++}
++static inline bool valid_user_group_name_compat(const char *u) {
++        return valid_user_group_name_full(u, false);
++}
++static inline bool valid_user_group_name_or_id_compat(const char *u) {
++        return valid_user_group_name_or_id_full(u, false);
++}
+ bool valid_gecos(const char *d);
+ bool valid_home(const char *p);
+ 
+diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c
+index 04e86f5ac3..3a4211655d 100644
+--- a/src/test/test-user-util.c
++++ b/src/test/test-user-util.c
+@@ -131,6 +131,43 @@ static void test_uid_ptr(void) {
+         assert_se(PTR_TO_UID(UID_TO_PTR(1000)) == 1000);
+ }
+ 
++static void test_valid_user_group_name_compat(void) {
++        log_info("/* %s */", __func__);
++
++        assert_se(!valid_user_group_name_compat(NULL));
++        assert_se(!valid_user_group_name_compat(""));
++        assert_se(!valid_user_group_name_compat("1"));
++        assert_se(!valid_user_group_name_compat("65535"));
++        assert_se(!valid_user_group_name_compat("-1"));
++        assert_se(!valid_user_group_name_compat("-kkk"));
++        assert_se(!valid_user_group_name_compat("rööt"));
++        assert_se(!valid_user_group_name_compat("."));
++        assert_se(!valid_user_group_name_compat(".eff"));
++        assert_se(!valid_user_group_name_compat("foo\nbar"));
++        assert_se(!valid_user_group_name_compat("0123456789012345678901234567890123456789"));
++        assert_se(!valid_user_group_name_or_id_compat("aaa:bbb"));
++        assert_se(!valid_user_group_name_compat("."));
++        assert_se(!valid_user_group_name_compat(".1"));
++        assert_se(!valid_user_group_name_compat(".65535"));
++        assert_se(!valid_user_group_name_compat(".-1"));
++        assert_se(!valid_user_group_name_compat(".-kkk"));
++        assert_se(!valid_user_group_name_compat(".rööt"));
++        assert_se(!valid_user_group_name_or_id_compat(".aaa:bbb"));
++
++        assert_se(valid_user_group_name_compat("root"));
++        assert_se(valid_user_group_name_compat("lennart"));
++        assert_se(valid_user_group_name_compat("LENNART"));
++        assert_se(valid_user_group_name_compat("_kkk"));
++        assert_se(valid_user_group_name_compat("kkk-"));
++        assert_se(valid_user_group_name_compat("kk-k"));
++        assert_se(valid_user_group_name_compat("eff.eff"));
++        assert_se(valid_user_group_name_compat("eff."));
++
++        assert_se(valid_user_group_name_compat("some5"));
++        assert_se(!valid_user_group_name_compat("5some"));
++        assert_se(valid_user_group_name_compat("INNER5NUMBER"));
++}
++
+ static void test_valid_user_group_name(void) {
+         log_info("/* %s */", __func__);
+ 
+@@ -141,9 +178,18 @@ static void test_valid_user_group_name(void) {
+         assert_se(!valid_user_group_name("-1"));
+         assert_se(!valid_user_group_name("-kkk"));
+         assert_se(!valid_user_group_name("rööt"));
++        assert_se(!valid_user_group_name("."));
++        assert_se(!valid_user_group_name(".eff"));
+         assert_se(!valid_user_group_name("foo\nbar"));
+         assert_se(!valid_user_group_name("0123456789012345678901234567890123456789"));
+         assert_se(!valid_user_group_name_or_id("aaa:bbb"));
++        assert_se(!valid_user_group_name("."));
++        assert_se(!valid_user_group_name(".1"));
++        assert_se(!valid_user_group_name(".65535"));
++        assert_se(!valid_user_group_name(".-1"));
++        assert_se(!valid_user_group_name(".-kkk"));
++        assert_se(!valid_user_group_name(".rööt"));
++        assert_se(!valid_user_group_name_or_id(".aaa:bbb"));
+ 
+         assert_se(valid_user_group_name("root"));
+         assert_se(valid_user_group_name("lennart"));
+@@ -151,14 +197,47 @@ static void test_valid_user_group_name(void) {
+         assert_se(valid_user_group_name("_kkk"));
+         assert_se(valid_user_group_name("kkk-"));
+         assert_se(valid_user_group_name("kk-k"));
+-        assert_se(valid_user_group_name(".moo"));
+-        assert_se(valid_user_group_name("eff.eff"));
++        assert_se(!valid_user_group_name("eff.eff"));
++        assert_se(!valid_user_group_name("eff."));
+ 
+         assert_se(valid_user_group_name("some5"));
+         assert_se(!valid_user_group_name("5some"));
+         assert_se(valid_user_group_name("INNER5NUMBER"));
+ }
+ 
++static void test_valid_user_group_name_or_id_compat(void) {
++        log_info("/* %s */", __func__);
++
++        assert_se(!valid_user_group_name_or_id_compat(NULL));
++        assert_se(!valid_user_group_name_or_id_compat(""));
++        assert_se(valid_user_group_name_or_id_compat("0"));
++        assert_se(valid_user_group_name_or_id_compat("1"));
++        assert_se(valid_user_group_name_or_id_compat("65534"));
++        assert_se(!valid_user_group_name_or_id_compat("65535"));
++        assert_se(valid_user_group_name_or_id_compat("65536"));
++        assert_se(!valid_user_group_name_or_id_compat("-1"));
++        assert_se(!valid_user_group_name_or_id_compat("-kkk"));
++        assert_se(!valid_user_group_name_or_id_compat("rööt"));
++        assert_se(!valid_user_group_name_or_id_compat("."));
++        assert_se(!valid_user_group_name_or_id_compat(".eff"));
++        assert_se(valid_user_group_name_or_id_compat("eff.eff"));
++        assert_se(valid_user_group_name_or_id_compat("eff."));
++        assert_se(!valid_user_group_name_or_id_compat("foo\nbar"));
++        assert_se(!valid_user_group_name_or_id_compat("0123456789012345678901234567890123456789"));
++        assert_se(!valid_user_group_name_or_id_compat("aaa:bbb"));
++
++        assert_se(valid_user_group_name_or_id_compat("root"));
++        assert_se(valid_user_group_name_or_id_compat("lennart"));
++        assert_se(valid_user_group_name_or_id_compat("LENNART"));
++        assert_se(valid_user_group_name_or_id_compat("_kkk"));
++        assert_se(valid_user_group_name_or_id_compat("kkk-"));
++        assert_se(valid_user_group_name_or_id_compat("kk-k"));
++
++        assert_se(valid_user_group_name_or_id_compat("some5"));
++        assert_se(!valid_user_group_name_or_id_compat("5some"));
++        assert_se(valid_user_group_name_or_id_compat("INNER5NUMBER"));
++}
++
+ static void test_valid_user_group_name_or_id(void) {
+         log_info("/* %s */", __func__);
+ 
+@@ -172,6 +251,10 @@ static void test_valid_user_group_name_or_id(void) {
+         assert_se(!valid_user_group_name_or_id("-1"));
+         assert_se(!valid_user_group_name_or_id("-kkk"));
+         assert_se(!valid_user_group_name_or_id("rööt"));
++        assert_se(!valid_user_group_name_or_id("."));
++        assert_se(!valid_user_group_name_or_id(".eff"));
++        assert_se(!valid_user_group_name_or_id("eff.eff"));
++        assert_se(!valid_user_group_name_or_id("eff."));
+         assert_se(!valid_user_group_name_or_id("foo\nbar"));
+         assert_se(!valid_user_group_name_or_id("0123456789012345678901234567890123456789"));
+         assert_se(!valid_user_group_name_or_id("aaa:bbb"));
+@@ -182,8 +265,6 @@ static void test_valid_user_group_name_or_id(void) {
+         assert_se(valid_user_group_name_or_id("_kkk"));
+         assert_se(valid_user_group_name_or_id("kkk-"));
+         assert_se(valid_user_group_name_or_id("kk-k"));
+-        assert_se(valid_user_group_name_or_id(".moo"));
+-        assert_se(valid_user_group_name_or_id("eff.eff"));
+ 
+         assert_se(valid_user_group_name_or_id("some5"));
+         assert_se(!valid_user_group_name_or_id("5some"));
+@@ -286,7 +367,9 @@ int main(int argc, char*argv[]) {
+         test_parse_uid();
+         test_uid_ptr();
+ 
++        test_valid_user_group_name_compat();
+         test_valid_user_group_name();
++        test_valid_user_group_name_or_id_compat();
+         test_valid_user_group_name_or_id();
+         test_valid_gecos();
+         test_valid_home();
diff --git a/SOURCES/0470-shared-user-util-emit-a-warning-on-names-with-dots.patch b/SOURCES/0470-shared-user-util-emit-a-warning-on-names-with-dots.patch
new file mode 100644
index 0000000..83422bc
--- /dev/null
+++ b/SOURCES/0470-shared-user-util-emit-a-warning-on-names-with-dots.patch
@@ -0,0 +1,50 @@
+From fa1fa19951fdadd63f2b5df6224678f91753f260 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Wed, 28 Aug 2019 12:05:52 +0200
+Subject: [PATCH] shared/user-util: emit a warning on names with dots
+
+(cherry picked from commit 88e2ed0b5bf6f08f5a2d4d64b1fefdc7192b9aac)
+
+Related: #1848373
+---
+ src/basic/user-util.c | 27 ++++++++++++++++++++-------
+ 1 file changed, 20 insertions(+), 7 deletions(-)
+
+diff --git a/src/basic/user-util.c b/src/basic/user-util.c
+index 03cbbc2503..359da08a83 100644
+--- a/src/basic/user-util.c
++++ b/src/basic/user-util.c
+@@ -601,13 +601,26 @@ bool valid_user_group_name_full(const char *u, bool strict) {
+             u[0] != '_')
+                 return false;
+ 
+-        for (i = u+1; *i; i++)
+-                if (!((*i >= 'a' && *i <= 'z') ||
+-                      (*i >= 'A' && *i <= 'Z') ||
+-                      (*i >= '0' && *i <= '9') ||
+-                      IN_SET(*i, '_', '-') ||
+-                      (!strict && *i == '.')))
+-                        return false;
++        bool warned = false;
++
++        for (i = u+1; *i; i++) {
++                if (((*i >= 'a' && *i <= 'z') ||
++                     (*i >= 'A' && *i <= 'Z') ||
++                     (*i >= '0' && *i <= '9') ||
++                     IN_SET(*i, '_', '-')))
++                        continue;
++
++                if (*i == '.' && !strict) {
++                        if (!warned) {
++                                log_warning("Bad user or group name \"%s\", accepting for compatibility.", u);
++                                warned = true;
++                        }
++
++                        continue;
++                }
++
++                return false;
++        }
+ 
+         sz = sysconf(_SC_LOGIN_NAME_MAX);
+         assert_se(sz > 0);
diff --git a/SOURCES/0471-user-util-Allow-names-starting-with-a-digit.patch b/SOURCES/0471-user-util-Allow-names-starting-with-a-digit.patch
new file mode 100644
index 0000000..52a315a
--- /dev/null
+++ b/SOURCES/0471-user-util-Allow-names-starting-with-a-digit.patch
@@ -0,0 +1,103 @@
+From f06434cc51eedd72f7d4a640a1fa118f57a5e68e Mon Sep 17 00:00:00 2001
+From: Balint Reczey <balint.reczey@canonical.com>
+Date: Wed, 18 Mar 2020 18:29:02 +0100
+Subject: [PATCH] user-util: Allow names starting with a digit
+
+In 1a29610f5fa1bcb2eeb37d2c6b79d8d1a6dbb865 the change inadvertedly
+disabled names with digit as the first character. This follow-up change
+allows a digit as the first character in compat mode.
+
+Fixes: #15141
+(cherry picked from commit 93c23c9297e48e594785e0bb9c51504aae5fbe3e)
+
+Related: #1848373
+---
+ src/basic/user-util.c     | 20 +++++++++++++++++---
+ src/test/test-user-util.c |  4 ++--
+ 2 files changed, 19 insertions(+), 5 deletions(-)
+
+diff --git a/src/basic/user-util.c b/src/basic/user-util.c
+index 359da08a83..7dd2bb2c84 100644
+--- a/src/basic/user-util.c
++++ b/src/basic/user-util.c
+@@ -579,16 +579,18 @@ int take_etc_passwd_lock(const char *root) {
+ bool valid_user_group_name_full(const char *u, bool strict) {
+         const char *i;
+         long sz;
++        bool warned = false;
+ 
+         /* Checks if the specified name is a valid user/group name. Also see POSIX IEEE Std 1003.1-2008, 2016 Edition,
+          * 3.437. We are a bit stricter here however. Specifically we deviate from POSIX rules:
+          *
+          * - We require that names fit into the appropriate utmp field
+          * - We don't allow empty user names
+-         * - No dots or digits in the first character
++         * - No dots in the first character
+          *
+          * If strict==true, additionally:
+          * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator)
++         * - We don't allow a digit as the first character
+          *
+          * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters.
+          */
+@@ -598,17 +600,26 @@ bool valid_user_group_name_full(const char *u, bool strict) {
+ 
+         if (!(u[0] >= 'a' && u[0] <= 'z') &&
+             !(u[0] >= 'A' && u[0] <= 'Z') &&
++            !(u[0] >= '0' && u[0] <= '9' && !strict) &&
+             u[0] != '_')
+                 return false;
+ 
+-        bool warned = false;
++        bool only_digits_seen = u[0] >= '0' && u[0] <= '9';
++
++        if (only_digits_seen) {
++                log_warning("User or group name \"%s\" starts with a digit, accepting for compatibility.", u);
++                warned = true;
++        }
+ 
+         for (i = u+1; *i; i++) {
+                 if (((*i >= 'a' && *i <= 'z') ||
+                      (*i >= 'A' && *i <= 'Z') ||
+                      (*i >= '0' && *i <= '9') ||
+-                     IN_SET(*i, '_', '-')))
++                     IN_SET(*i, '_', '-'))) {
++                        if (!(*i >= '0' && *i <= '9'))
++                                only_digits_seen = false;
+                         continue;
++                        }
+ 
+                 if (*i == '.' && !strict) {
+                         if (!warned) {
+@@ -622,6 +633,9 @@ bool valid_user_group_name_full(const char *u, bool strict) {
+                 return false;
+         }
+ 
++        if (only_digits_seen)
++                return false;
++
+         sz = sysconf(_SC_LOGIN_NAME_MAX);
+         assert_se(sz > 0);
+ 
+diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c
+index 3a4211655d..56079f1486 100644
+--- a/src/test/test-user-util.c
++++ b/src/test/test-user-util.c
+@@ -164,7 +164,7 @@ static void test_valid_user_group_name_compat(void) {
+         assert_se(valid_user_group_name_compat("eff."));
+ 
+         assert_se(valid_user_group_name_compat("some5"));
+-        assert_se(!valid_user_group_name_compat("5some"));
++        assert_se(valid_user_group_name_compat("5some"));
+         assert_se(valid_user_group_name_compat("INNER5NUMBER"));
+ }
+ 
+@@ -234,7 +234,7 @@ static void test_valid_user_group_name_or_id_compat(void) {
+         assert_se(valid_user_group_name_or_id_compat("kk-k"));
+ 
+         assert_se(valid_user_group_name_or_id_compat("some5"));
+-        assert_se(!valid_user_group_name_or_id_compat("5some"));
++        assert_se(valid_user_group_name_or_id_compat("5some"));
+         assert_se(valid_user_group_name_or_id_compat("INNER5NUMBER"));
+ }
+ 
diff --git a/SOURCES/0472-shared-user-util-allow-usernames-with-dots-in-specif.patch b/SOURCES/0472-shared-user-util-allow-usernames-with-dots-in-specif.patch
new file mode 100644
index 0000000..37544d6
--- /dev/null
+++ b/SOURCES/0472-shared-user-util-allow-usernames-with-dots-in-specif.patch
@@ -0,0 +1,193 @@
+From 40dff18947fa198810db4cd3e5291349fc84a0e8 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Thu, 1 Aug 2019 10:02:14 +0200
+Subject: [PATCH] shared/user-util: allow usernames with dots in specific
+ fields
+
+People do have usernames with dots, and it makes them very unhappy that systemd
+doesn't like their that. It seems that there is no actual problem with allowing
+dots in the username. In particular chown declares ":" as the official
+separator, and internally in systemd we never rely on "." as the seperator
+between user and group (nor do we call chown directly). Using dots in the name
+is probably not a very good idea, but we don't need to care. Debian tools
+(adduser) do not allow users with dots to be created.
+
+This patch allows *existing* names with dots to be used in User, Group,
+SupplementaryGroups, SocketUser, SocketGroup fields, both in unit files and on
+the command line. DynamicUsers and sysusers still follow the strict policy.
+user@.service and tmpfiles already allowed arbitrary user names, and this
+remains unchanged.
+
+Fixes #12754.
+
+(cherry picked from commit ae480f0b09aec815b64579bb1828ea935d8ee236)
+
+Related: #1848373
+---
+ src/core/dbus-execute.c               | 12 ++++++------
+ src/core/dbus-socket.c                |  4 ++--
+ src/core/dbus-util.c                  |  2 +-
+ src/core/dbus-util.h                  |  2 +-
+ src/core/load-fragment-gperf.gperf.m4 | 10 +++++-----
+ src/core/load-fragment.c              |  8 ++++----
+ src/core/load-fragment.h              |  4 ++--
+ 7 files changed, 21 insertions(+), 21 deletions(-)
+
+diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
+index 0fe4c14e48..e004fb55c9 100644
+--- a/src/core/dbus-execute.c
++++ b/src/core/dbus-execute.c
+@@ -1113,10 +1113,10 @@ int bus_exec_context_set_transient_property(
+         flags |= UNIT_PRIVATE;
+ 
+         if (streq(name, "User"))
+-                return bus_set_transient_user(u, name, &c->user, message, flags, error);
++                return bus_set_transient_user_compat(u, name, &c->user, message, flags, error);
+ 
+         if (streq(name, "Group"))
+-                return bus_set_transient_user(u, name, &c->group, message, flags, error);
++                return bus_set_transient_user_compat(u, name, &c->group, message, flags, error);
+ 
+         if (streq(name, "TTYPath"))
+                 return bus_set_transient_path(u, name, &c->tty_path, message, flags, error);
+@@ -1297,10 +1297,10 @@ int bus_exec_context_set_transient_property(
+                 if (r < 0)
+                         return r;
+ 
+-                STRV_FOREACH(p, l) {
+-                        if (!isempty(*p) && !valid_user_group_name_or_id(*p))
+-                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid supplementary group names");
+-                }
++                STRV_FOREACH(p, l)
++                        if (!isempty(*p) && !valid_user_group_name_or_id_compat(*p))
++                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
++                                                         "Invalid supplementary group names");
+ 
+                 if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+                         if (strv_isempty(l)) {
+diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c
+index bb77539030..8fdbc05409 100644
+--- a/src/core/dbus-socket.c
++++ b/src/core/dbus-socket.c
+@@ -281,10 +281,10 @@ static int bus_socket_set_transient_property(
+                 return bus_set_transient_fdname(u, name, &s->fdname, message, flags, error);
+ 
+         if (streq(name, "SocketUser"))
+-                return bus_set_transient_user(u, name, &s->user, message, flags, error);
++                return bus_set_transient_user_compat(u, name, &s->user, message, flags, error);
+ 
+         if (streq(name, "SocketGroup"))
+-                return bus_set_transient_user(u, name, &s->group, message, flags, error);
++                return bus_set_transient_user_compat(u, name, &s->group, message, flags, error);
+ 
+         if (streq(name, "BindIPv6Only"))
+                 return bus_set_transient_bind_ipv6_only(u, name, &s->bind_ipv6_only, message, flags, error);
+diff --git a/src/core/dbus-util.c b/src/core/dbus-util.c
+index f4fbb72cb9..7862beaacb 100644
+--- a/src/core/dbus-util.c
++++ b/src/core/dbus-util.c
+@@ -30,7 +30,7 @@ int bus_property_get_triggered_unit(
+ 
+ BUS_DEFINE_SET_TRANSIENT(mode_t, "u", uint32_t, mode_t, "%040o");
+ BUS_DEFINE_SET_TRANSIENT(unsigned, "u", uint32_t, unsigned, "%" PRIu32);
+-BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(user, valid_user_group_name_or_id);
++BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(user_compat, valid_user_group_name_or_id_compat);
+ BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(path, path_is_absolute);
+ 
+ int bus_set_transient_string(
+diff --git a/src/core/dbus-util.h b/src/core/dbus-util.h
+index 12b055e4ac..a3316c6701 100644
+--- a/src/core/dbus-util.h
++++ b/src/core/dbus-util.h
+@@ -235,7 +235,7 @@ int bus_property_get_triggered_unit(sd_bus *bus, const char *path, const char *i
+ 
+ int bus_set_transient_mode_t(Unit *u, const char *name, mode_t *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+ int bus_set_transient_unsigned(Unit *u, const char *name, unsigned *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+-int bus_set_transient_user(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
++int bus_set_transient_user_compat(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+ int bus_set_transient_path(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+ int bus_set_transient_string(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+ int bus_set_transient_bool(Unit *u, const char *name, bool *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
+index 24ee5ae6fe..156a4d0a6d 100644
+--- a/src/core/load-fragment-gperf.gperf.m4
++++ b/src/core/load-fragment-gperf.gperf.m4
+@@ -25,9 +25,9 @@ m4_define(`EXEC_CONTEXT_CONFIG_ITEMS',
+ `$1.WorkingDirectory,            config_parse_working_directory,     0,                             offsetof($1, exec_context)
+ $1.RootDirectory,                config_parse_unit_path_printf,      true,                          offsetof($1, exec_context.root_directory)
+ $1.RootImage,                    config_parse_unit_path_printf,      true,                          offsetof($1, exec_context.root_image)
+-$1.User,                         config_parse_user_group,            0,                             offsetof($1, exec_context.user)
+-$1.Group,                        config_parse_user_group,            0,                             offsetof($1, exec_context.group)
+-$1.SupplementaryGroups,          config_parse_user_group_strv,       0,                             offsetof($1, exec_context.supplementary_groups)
++$1.User,                         config_parse_user_group_compat,     0,                             offsetof($1, exec_context.user)
++$1.Group,                        config_parse_user_group_compat,     0,                             offsetof($1, exec_context.group)
++$1.SupplementaryGroups,          config_parse_user_group_strv_compat, 0,                            offsetof($1, exec_context.supplementary_groups)
+ $1.Nice,                         config_parse_exec_nice,             0,                             offsetof($1, exec_context)
+ $1.OOMScoreAdjust,               config_parse_exec_oom_score_adjust, 0,                             offsetof($1, exec_context)
+ $1.IOSchedulingClass,            config_parse_exec_io_class,         0,                             offsetof($1, exec_context)
+@@ -354,8 +354,8 @@ Socket.ExecStartPost,            config_parse_exec,                  SOCKET_EXEC
+ Socket.ExecStopPre,              config_parse_exec,                  SOCKET_EXEC_STOP_PRE,          offsetof(Socket, exec_command)
+ Socket.ExecStopPost,             config_parse_exec,                  SOCKET_EXEC_STOP_POST,         offsetof(Socket, exec_command)
+ Socket.TimeoutSec,               config_parse_sec_fix_0,             0,                             offsetof(Socket, timeout_usec)
+-Socket.SocketUser,               config_parse_user_group,            0,                             offsetof(Socket, user)
+-Socket.SocketGroup,              config_parse_user_group,            0,                             offsetof(Socket, group)
++Socket.SocketUser,               config_parse_user_group_compat,     0,                             offsetof(Socket, user)
++Socket.SocketGroup,              config_parse_user_group_compat,     0,                             offsetof(Socket, group)
+ Socket.SocketMode,               config_parse_mode,                  0,                             offsetof(Socket, socket_mode)
+ Socket.DirectoryMode,            config_parse_mode,                  0,                             offsetof(Socket, directory_mode)
+ Socket.Accept,                   config_parse_bool,                  0,                             offsetof(Socket, accept)
+diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
+index 740401a582..ba81d94504 100644
+--- a/src/core/load-fragment.c
++++ b/src/core/load-fragment.c
+@@ -1899,7 +1899,7 @@ int config_parse_sec_fix_0(
+         return 0;
+ }
+ 
+-int config_parse_user_group(
++int config_parse_user_group_compat(
+                 const char *unit,
+                 const char *filename,
+                 unsigned line,
+@@ -1932,7 +1932,7 @@ int config_parse_user_group(
+                 return -ENOEXEC;
+         }
+ 
+-        if (!valid_user_group_name_or_id(k)) {
++        if (!valid_user_group_name_or_id_compat(k)) {
+                 log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID: %s", k);
+                 return -ENOEXEC;
+         }
+@@ -1940,7 +1940,7 @@ int config_parse_user_group(
+         return free_and_replace(*user, k);
+ }
+ 
+-int config_parse_user_group_strv(
++int config_parse_user_group_strv_compat(
+                 const char *unit,
+                 const char *filename,
+                 unsigned line,
+@@ -1986,7 +1986,7 @@ int config_parse_user_group_strv(
+                         return -ENOEXEC;
+                 }
+ 
+-                if (!valid_user_group_name_or_id(k)) {
++                if (!valid_user_group_name_or_id_compat(k)) {
+                         log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID: %s", k);
+                         return -ENOEXEC;
+                 }
+diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
+index 65a94d53cc..f9d34d484d 100644
+--- a/src/core/load-fragment.h
++++ b/src/core/load-fragment.h
+@@ -96,8 +96,8 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_utmp_mode);
+ CONFIG_PARSER_PROTOTYPE(config_parse_working_directory);
+ CONFIG_PARSER_PROTOTYPE(config_parse_fdname);
+ CONFIG_PARSER_PROTOTYPE(config_parse_sec_fix_0);
+-CONFIG_PARSER_PROTOTYPE(config_parse_user_group);
+-CONFIG_PARSER_PROTOTYPE(config_parse_user_group_strv);
++CONFIG_PARSER_PROTOTYPE(config_parse_user_group_compat);
++CONFIG_PARSER_PROTOTYPE(config_parse_user_group_strv_compat);
+ CONFIG_PARSER_PROTOTYPE(config_parse_restrict_namespaces);
+ CONFIG_PARSER_PROTOTYPE(config_parse_bind_paths);
+ CONFIG_PARSER_PROTOTYPE(config_parse_exec_keyring_mode);
diff --git a/SOURCES/0473-user-util-switch-order-of-checks-in-valid_user_group.patch b/SOURCES/0473-user-util-switch-order-of-checks-in-valid_user_group.patch
new file mode 100644
index 0000000..b982f8c
--- /dev/null
+++ b/SOURCES/0473-user-util-switch-order-of-checks-in-valid_user_group.patch
@@ -0,0 +1,38 @@
+From 7569168bea3d7e11cd3afe6167fcf4a3ac65a1a6 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Mon, 30 Mar 2020 21:46:01 +0200
+Subject: [PATCH] user-util: switch order of checks in
+ valid_user_group_name_or_id_full()
+
+When we are supposed to accept numeric UIDs formatted as string, then
+let's check that first, before passing things on to
+valid_user_group_name_full(), since that might log about, and not the
+other way round.
+
+See: #15201
+Follow-up for: 93c23c9297e48e594785e0bb9c51504aae5fbe3e
+
+(cherry picked from commit a85daa0dfb3eb03be9845760e90e54b9af8fb00e)
+
+Related: #1848373
+---
+ src/basic/user-util.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/basic/user-util.c b/src/basic/user-util.c
+index 7dd2bb2c84..68a924770b 100644
+--- a/src/basic/user-util.c
++++ b/src/basic/user-util.c
+@@ -656,10 +656,10 @@ bool valid_user_group_name_or_id_full(const char *u, bool strict) {
+         if (isempty(u))
+                 return false;
+ 
+-        if (valid_user_group_name_full(u, strict))
++        if (parse_uid(u, NULL) >= 0)
+                 return true;
+ 
+-        return parse_uid(u, NULL) >= 0;
++        return valid_user_group_name_full(u, strict);
+ }
+ 
+ bool valid_gecos(const char *d) {
diff --git a/SOURCES/0474-user-util-rework-how-we-validate-user-names.patch b/SOURCES/0474-user-util-rework-how-we-validate-user-names.patch
new file mode 100644
index 0000000..9607f81
--- /dev/null
+++ b/SOURCES/0474-user-util-rework-how-we-validate-user-names.patch
@@ -0,0 +1,803 @@
+From 33b851f0c30e47fe71a293e2c990ef26573efe86 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Sat, 4 Apr 2020 12:23:02 +0200
+Subject: [PATCH] user-util: rework how we validate user names
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This reworks the user validation infrastructure. There are now two
+modes. In regular mode we are strict and test against a strict set of
+valid chars. And in "relaxed" mode we just filter out some really
+obvious, dangerous stuff. i.e. strict is whitelisting what is OK, but
+"relaxed" is blacklisting what is really not OK.
+
+The idea is that we use strict mode whenver we allocate a new user
+(i.e. in sysusers.d or homed), while "relaxed" mode is when we process
+users registered elsewhere, (i.e. userdb, logind, …)
+
+The requirements on user name validity vary wildly. SSSD thinks its fine
+to embedd "@" for example, while the suggested NAME_REGEX field on
+Debian does not even allow uppercase chars…
+
+This effectively liberaralizes a lot what we expect from usernames.
+
+The code that warns about questionnable user names is now optional and
+only used at places such as unit file parsing, so that it doesn't show
+up on every userdb query, but only when processing configuration files
+that know better.
+
+Fixes: #15149 #15090
+(cherry picked from commit 7a8867abfab10e5bbca10590ec2aa40c5b27d8fb)
+
+Resolves: #1848373
+---
+ src/basic/user-util.c         | 185 +++++++++++++----------
+ src/basic/user-util.h         |  21 +--
+ src/core/dbus-execute.c       |   6 +-
+ src/core/dbus-manager.c       |   2 +-
+ src/core/dbus-socket.c        |   4 +-
+ src/core/dbus-util.c          |   7 +-
+ src/core/dbus-util.h          |   2 +-
+ src/core/dynamic-user.c       |   2 +-
+ src/core/load-fragment.c      |   4 +-
+ src/core/unit.c               |   2 +-
+ src/nss-systemd/nss-systemd.c |   6 +-
+ src/systemd/sd-messages.h     |   3 +
+ src/sysusers/sysusers.c       |   4 +-
+ src/test/test-user-util.c     | 271 ++++++++++++++++++----------------
+ 14 files changed, 287 insertions(+), 232 deletions(-)
+
+diff --git a/src/basic/user-util.c b/src/basic/user-util.c
+index 68a924770b..cd870c4361 100644
+--- a/src/basic/user-util.c
++++ b/src/basic/user-util.c
+@@ -14,6 +14,8 @@
+ #include <unistd.h>
+ #include <utmp.h>
+ 
++#include "sd-messages.h"
++
+ #include "alloc-util.h"
+ #include "fd-util.h"
+ #include "fileio.h"
+@@ -576,92 +578,125 @@ int take_etc_passwd_lock(const char *root) {
+         return fd;
+ }
+ 
+-bool valid_user_group_name_full(const char *u, bool strict) {
++bool valid_user_group_name(const char *u, ValidUserFlags flags) {
+         const char *i;
+-        long sz;
+-        bool warned = false;
+ 
+-        /* Checks if the specified name is a valid user/group name. Also see POSIX IEEE Std 1003.1-2008, 2016 Edition,
+-         * 3.437. We are a bit stricter here however. Specifically we deviate from POSIX rules:
+-         *
+-         * - We require that names fit into the appropriate utmp field
+-         * - We don't allow empty user names
+-         * - No dots in the first character
++        /* Checks if the specified name is a valid user/group name. There are two flavours of this call:
++         * strict mode is the default which is POSIX plus some extra rules; and relaxed mode where we accept
++         * pretty much everything except the really worst offending names.
+          *
+-         * If strict==true, additionally:
+-         * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator)
+-         * - We don't allow a digit as the first character
+-         *
+-         * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters.
+-         */
++         * Whenever we synthesize users ourselves we should use the strict mode. But when we process users
++         * created by other stuff, let's be more liberal. */
+ 
+-        if (isempty(u))
++        if (isempty(u)) /* An empty user name is never valid */
+                 return false;
+ 
+-        if (!(u[0] >= 'a' && u[0] <= 'z') &&
+-            !(u[0] >= 'A' && u[0] <= 'Z') &&
+-            !(u[0] >= '0' && u[0] <= '9' && !strict) &&
+-            u[0] != '_')
+-                return false;
+-
+-        bool only_digits_seen = u[0] >= '0' && u[0] <= '9';
+-
+-        if (only_digits_seen) {
+-                log_warning("User or group name \"%s\" starts with a digit, accepting for compatibility.", u);
+-                warned = true;
+-        }
+-
+-        for (i = u+1; *i; i++) {
+-                if (((*i >= 'a' && *i <= 'z') ||
+-                     (*i >= 'A' && *i <= 'Z') ||
+-                     (*i >= '0' && *i <= '9') ||
+-                     IN_SET(*i, '_', '-'))) {
+-                        if (!(*i >= '0' && *i <= '9'))
+-                                only_digits_seen = false;
+-                        continue;
+-                        }
+-
+-                if (*i == '.' && !strict) {
+-                        if (!warned) {
+-                                log_warning("Bad user or group name \"%s\", accepting for compatibility.", u);
+-                                warned = true;
+-                        }
+-
+-                        continue;
+-                }
+-
+-                return false;
++        if (parse_uid(u, NULL) >= 0) /* Something that parses as numeric UID string is valid exactly when the
++                                      * flag for it is set */
++                return FLAGS_SET(flags, VALID_USER_ALLOW_NUMERIC);
++
++        if (FLAGS_SET(flags, VALID_USER_RELAX)) {
++
++                /* In relaxed mode we just check very superficially. Apparently SSSD and other stuff is
++                 * extremely liberal (way too liberal if you ask me, even inserting "@" in user names, which
++                 * is bound to cause problems for example when used with an MTA), hence only filter the most
++                 * obvious cases, or where things would result in an invalid entry if such a user name would
++                 * show up in /etc/passwd (or equivalent getent output).
++                 *
++                 * Note that we stepped far out of POSIX territory here. It's not our fault though, but
++                 * SSSD's, Samba's and everybody else who ignored POSIX on this. (I mean, I am happy to step
++                 * outside of POSIX' bounds any day, but I must say in this case I probably wouldn't
++                 * have...) */
++
++                if (startswith(u, " ") || endswith(u, " ")) /* At least expect whitespace padding is removed
++                                                             * at front and back (accept in the middle, since
++                                                             * that's apparently a thing on Windows). Note
++                                                             * that this also blocks usernames consisting of
++                                                             * whitespace only. */
++                        return false;
++
++                if (!utf8_is_valid(u)) /* We want to synthesize JSON from this, hence insist on UTF-8 */
++                        return false;
++
++                if (string_has_cc(u, NULL)) /* CC characters are just dangerous (and \n in particular is the
++                                             * record separator in /etc/passwd), so we can't allow that. */
++                        return false;
++
++                if (strpbrk(u, ":/")) /* Colons are the field separator in /etc/passwd, we can't allow
++                                       * that. Slashes are special to file systems paths and user names
++                                       * typically show up in the file system as home directories, hence
++                                       * don't allow slashes. */
++                        return false;
++
++                if (in_charset(u, "0123456789")) /* Don't allow fully numeric strings, they might be confused
++                                                  * with with UIDs (note that this test is more broad than
++                                                  * the parse_uid() test above, as it will cover more than
++                                                  * the 32bit range, and it will detect 65535 (which is in
++                                                  * invalid UID, even though in the unsigned 32 bit range) */
++                        return false;
++
++                if (u[0] == '-' && in_charset(u + 1, "0123456789")) /* Don't allow negative fully numeric
++                                                                     * strings either. After all some people
++                                                                     * write 65535 as -1 (even though that's
++                                                                     * not even true on 32bit uid_t
++                                                                     * anyway) */
++                        return false;
++
++                if (dot_or_dot_dot(u)) /* User names typically become home directory names, and these two are
++                                        * special in that context, don't allow that. */
++                        return false;
++
++                /* Compare with strict result and warn if result doesn't match */
++                if (FLAGS_SET(flags, VALID_USER_WARN) && !valid_user_group_name(u, 0))
++                        log_struct(LOG_NOTICE,
++                                   "MESSAGE=Accepting user/group name '%s', which does not match strict user/group name rules.", u,
++                                   "USER_GROUP_NAME=%s", u,
++                                   "MESSAGE_ID=" SD_MESSAGE_UNSAFE_USER_NAME_STR);
++
++                /* Note that we make no restrictions on the length in relaxed mode! */
++        } else {
++                long sz;
++                size_t l;
++
++                /* Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, 3.437. We are a bit stricter here
++                 * however. Specifically we deviate from POSIX rules:
++                 *
++                 * - We don't allow empty user names (see above)
++                 * - We require that names fit into the appropriate utmp field
++                 * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator)
++                 * - We don't allow dashes or digit as the first character
++                 *
++                 * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters.
++                 */
++
++                if (!(u[0] >= 'a' && u[0] <= 'z') &&
++                    !(u[0] >= 'A' && u[0] <= 'Z') &&
++                    u[0] != '_')
++                        return false;
++
++                for (i = u+1; *i; i++)
++                        if (!(*i >= 'a' && *i <= 'z') &&
++                            !(*i >= 'A' && *i <= 'Z') &&
++                            !(*i >= '0' && *i <= '9') &&
++                            !IN_SET(*i, '_', '-'))
++                                return false;
++
++                l = i - u;
++
++                sz = sysconf(_SC_LOGIN_NAME_MAX);
++                assert_se(sz > 0);
++
++                if (l > (size_t) sz)
++                        return false;
++                if (l > FILENAME_MAX)
++                        return false;
++                if (l > UT_NAMESIZE - 1)
++                        return false;
+         }
+ 
+-        if (only_digits_seen)
+-                return false;
+-
+-        sz = sysconf(_SC_LOGIN_NAME_MAX);
+-        assert_se(sz > 0);
+-
+-        if ((size_t) (i-u) > (size_t) sz)
+-                return false;
+-
+-        if ((size_t) (i-u) > UT_NAMESIZE - 1)
+-                return false;
+-
+         return true;
+ }
+ 
+-bool valid_user_group_name_or_id_full(const char *u, bool strict) {
+-
+-        /* Similar as above, but is also fine with numeric UID/GID specifications, as long as they are in the
+-         * right range, and not the invalid user ids. */
+-
+-        if (isempty(u))
+-                return false;
+-
+-        if (parse_uid(u, NULL) >= 0)
+-                return true;
+-
+-        return valid_user_group_name_full(u, strict);
+-}
+-
+ bool valid_gecos(const char *d) {
+ 
+         if (!d)
+diff --git a/src/basic/user-util.h b/src/basic/user-util.h
+index 5ad0b2a2f9..939bded40d 100644
+--- a/src/basic/user-util.h
++++ b/src/basic/user-util.h
+@@ -78,20 +78,13 @@ static inline bool userns_supported(void) {
+         return access("/proc/self/uid_map", F_OK) >= 0;
+ }
+ 
+-bool valid_user_group_name_full(const char *u, bool strict);
+-bool valid_user_group_name_or_id_full(const char *u, bool strict);
+-static inline bool valid_user_group_name(const char *u) {
+-        return valid_user_group_name_full(u, true);
+-}
+-static inline bool valid_user_group_name_or_id(const char *u) {
+-        return valid_user_group_name_or_id_full(u, true);
+-}
+-static inline bool valid_user_group_name_compat(const char *u) {
+-        return valid_user_group_name_full(u, false);
+-}
+-static inline bool valid_user_group_name_or_id_compat(const char *u) {
+-        return valid_user_group_name_or_id_full(u, false);
+-}
++typedef enum ValidUserFlags {
++        VALID_USER_RELAX         = 1 << 0,
++        VALID_USER_WARN          = 1 << 1,
++        VALID_USER_ALLOW_NUMERIC = 1 << 2,
++} ValidUserFlags;
++
++bool valid_user_group_name(const char *u, ValidUserFlags flags);
+ bool valid_gecos(const char *d);
+ bool valid_home(const char *p);
+ 
+diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
+index e004fb55c9..8348663000 100644
+--- a/src/core/dbus-execute.c
++++ b/src/core/dbus-execute.c
+@@ -1113,10 +1113,10 @@ int bus_exec_context_set_transient_property(
+         flags |= UNIT_PRIVATE;
+ 
+         if (streq(name, "User"))
+-                return bus_set_transient_user_compat(u, name, &c->user, message, flags, error);
++                return bus_set_transient_user_relaxed(u, name, &c->user, message, flags, error);
+ 
+         if (streq(name, "Group"))
+-                return bus_set_transient_user_compat(u, name, &c->group, message, flags, error);
++                return bus_set_transient_user_relaxed(u, name, &c->group, message, flags, error);
+ 
+         if (streq(name, "TTYPath"))
+                 return bus_set_transient_path(u, name, &c->tty_path, message, flags, error);
+@@ -1298,7 +1298,7 @@ int bus_exec_context_set_transient_property(
+                         return r;
+ 
+                 STRV_FOREACH(p, l)
+-                        if (!isempty(*p) && !valid_user_group_name_or_id_compat(*p))
++                        if (!isempty(*p) && !valid_user_group_name(*p, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX|VALID_USER_WARN))
+                                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+                                                          "Invalid supplementary group names");
+ 
+diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
+index 0a1d3df42f..7488f22116 100644
+--- a/src/core/dbus-manager.c
++++ b/src/core/dbus-manager.c
+@@ -1762,7 +1762,7 @@ static int method_lookup_dynamic_user_by_name(sd_bus_message *message, void *use
+ 
+         if (!MANAGER_IS_SYSTEM(m))
+                 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Dynamic users are only supported in the system instance.");
+-        if (!valid_user_group_name(name))
++        if (!valid_user_group_name(name, VALID_USER_RELAX))
+                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name invalid: %s", name);
+ 
+         r = dynamic_user_lookup_name(m, name, &uid);
+diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c
+index 8fdbc05409..fa6bbe2c6f 100644
+--- a/src/core/dbus-socket.c
++++ b/src/core/dbus-socket.c
+@@ -281,10 +281,10 @@ static int bus_socket_set_transient_property(
+                 return bus_set_transient_fdname(u, name, &s->fdname, message, flags, error);
+ 
+         if (streq(name, "SocketUser"))
+-                return bus_set_transient_user_compat(u, name, &s->user, message, flags, error);
++                return bus_set_transient_user_relaxed(u, name, &s->user, message, flags, error);
+ 
+         if (streq(name, "SocketGroup"))
+-                return bus_set_transient_user_compat(u, name, &s->group, message, flags, error);
++                return bus_set_transient_user_relaxed(u, name, &s->group, message, flags, error);
+ 
+         if (streq(name, "BindIPv6Only"))
+                 return bus_set_transient_bind_ipv6_only(u, name, &s->bind_ipv6_only, message, flags, error);
+diff --git a/src/core/dbus-util.c b/src/core/dbus-util.c
+index 7862beaacb..951450e53d 100644
+--- a/src/core/dbus-util.c
++++ b/src/core/dbus-util.c
+@@ -30,7 +30,12 @@ int bus_property_get_triggered_unit(
+ 
+ BUS_DEFINE_SET_TRANSIENT(mode_t, "u", uint32_t, mode_t, "%040o");
+ BUS_DEFINE_SET_TRANSIENT(unsigned, "u", uint32_t, unsigned, "%" PRIu32);
+-BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(user_compat, valid_user_group_name_or_id_compat);
++
++static inline bool valid_user_group_name_or_id_relaxed(const char *u) {
++        return valid_user_group_name(u, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX);
++}
++
++BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(user_relaxed, valid_user_group_name_or_id_relaxed);
+ BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(path, path_is_absolute);
+ 
+ int bus_set_transient_string(
+diff --git a/src/core/dbus-util.h b/src/core/dbus-util.h
+index a3316c6701..713b464dd9 100644
+--- a/src/core/dbus-util.h
++++ b/src/core/dbus-util.h
+@@ -235,7 +235,7 @@ int bus_property_get_triggered_unit(sd_bus *bus, const char *path, const char *i
+ 
+ int bus_set_transient_mode_t(Unit *u, const char *name, mode_t *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+ int bus_set_transient_unsigned(Unit *u, const char *name, unsigned *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+-int bus_set_transient_user_compat(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
++int bus_set_transient_user_relaxed(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+ int bus_set_transient_path(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+ int bus_set_transient_string(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+ int bus_set_transient_bool(Unit *u, const char *name, bool *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+diff --git a/src/core/dynamic-user.c b/src/core/dynamic-user.c
+index 021fd93a76..548b3cc9df 100644
+--- a/src/core/dynamic-user.c
++++ b/src/core/dynamic-user.c
+@@ -108,7 +108,7 @@ static int dynamic_user_acquire(Manager *m, const char *name, DynamicUser** ret)
+                 return 0;
+         }
+ 
+-        if (!valid_user_group_name_or_id(name))
++        if (!valid_user_group_name(name, VALID_USER_ALLOW_NUMERIC))
+                 return -EINVAL;
+ 
+         if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, storage_socket) < 0)
+diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
+index ba81d94504..e0d7b8f7f8 100644
+--- a/src/core/load-fragment.c
++++ b/src/core/load-fragment.c
+@@ -1932,7 +1932,7 @@ int config_parse_user_group_compat(
+                 return -ENOEXEC;
+         }
+ 
+-        if (!valid_user_group_name_or_id_compat(k)) {
++        if (!valid_user_group_name(k, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX|VALID_USER_WARN)) {
+                 log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID: %s", k);
+                 return -ENOEXEC;
+         }
+@@ -1986,7 +1986,7 @@ int config_parse_user_group_strv_compat(
+                         return -ENOEXEC;
+                 }
+ 
+-                if (!valid_user_group_name_or_id_compat(k)) {
++                if (!valid_user_group_name(k, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX|VALID_USER_WARN)) {
+                         log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID: %s", k);
+                         return -ENOEXEC;
+                 }
+diff --git a/src/core/unit.c b/src/core/unit.c
+index ffbf3cfd48..cd3e7c806d 100644
+--- a/src/core/unit.c
++++ b/src/core/unit.c
+@@ -4088,7 +4088,7 @@ static int user_from_unit_name(Unit *u, char **ret) {
+         if (r < 0)
+                 return r;
+ 
+-        if (valid_user_group_name(n)) {
++        if (valid_user_group_name(n, 0)) {
+                 *ret = TAKE_PTR(n);
+                 return 0;
+         }
+diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c
+index f8db27ae27..615c710257 100644
+--- a/src/nss-systemd/nss-systemd.c
++++ b/src/nss-systemd/nss-systemd.c
+@@ -123,7 +123,7 @@ static int direct_lookup_uid(uid_t uid, char **ret) {
+         r = readlink_malloc(path, &s);
+         if (r < 0)
+                 return r;
+-        if (!valid_user_group_name(s)) { /* extra safety check */
++        if (!valid_user_group_name(s, VALID_USER_RELAX)) { /* extra safety check */
+                 free(s);
+                 return -EINVAL;
+         }
+@@ -153,7 +153,7 @@ enum nss_status _nss_systemd_getpwnam_r(
+ 
+         /* If the username is not valid, then we don't know it. Ideally libc would filter these for us anyway. We don't
+          * generate EINVAL here, because it isn't really out business to complain about invalid user names. */
+-        if (!valid_user_group_name(name))
++        if (!valid_user_group_name(name, VALID_USER_RELAX))
+                 return NSS_STATUS_NOTFOUND;
+ 
+         /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */
+@@ -356,7 +356,7 @@ enum nss_status _nss_systemd_getgrnam_r(
+         assert(name);
+         assert(gr);
+ 
+-        if (!valid_user_group_name(name))
++        if (!valid_user_group_name(name, VALID_USER_RELAX))
+                 return NSS_STATUS_NOTFOUND;
+ 
+         /* Synthesize records for root and nobody, in case they are missing form /etc/group */
+diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h
+index bdd4fd3974..847b698ba4 100644
+--- a/src/systemd/sd-messages.h
++++ b/src/systemd/sd-messages.h
+@@ -152,6 +152,9 @@ _SD_BEGIN_DECLARATIONS;
+ #define SD_MESSAGE_DNSSEC_DOWNGRADE       SD_ID128_MAKE(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57)
+ #define SD_MESSAGE_DNSSEC_DOWNGRADE_STR   SD_ID128_MAKE_STR(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57)
+ 
++#define SD_MESSAGE_UNSAFE_USER_NAME       SD_ID128_MAKE(b6,1f,da,c6,12,e9,4b,91,82,28,5b,99,88,43,06,1f)
++#define SD_MESSAGE_UNSAFE_USER_NAME_STR   SD_ID128_MAKE_STR(b6,1f,da,c6,12,e9,4b,91,82,28,5b,99,88,43,06,1f)
++
+ _SD_END_DECLARATIONS;
+ 
+ #endif
+diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c
+index 33959d3c11..a374ebaaf4 100644
+--- a/src/sysusers/sysusers.c
++++ b/src/sysusers/sysusers.c
+@@ -1413,7 +1413,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
+                         return r;
+                 }
+ 
+-                if (!valid_user_group_name(resolved_name)) {
++                if (!valid_user_group_name(resolved_name, 0)) {
+                         log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_name);
+                         return -EINVAL;
+                 }
+@@ -1524,7 +1524,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
+                         return -EINVAL;
+                 }
+ 
+-                if (!valid_user_group_name(resolved_id)) {
++                if (!valid_user_group_name(resolved_id, 0)) {
+                         log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_id);
+                         return -EINVAL;
+                 }
+diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c
+index 56079f1486..31ac018da9 100644
+--- a/src/test/test-user-util.c
++++ b/src/test/test-user-util.c
+@@ -131,144 +131,163 @@ static void test_uid_ptr(void) {
+         assert_se(PTR_TO_UID(UID_TO_PTR(1000)) == 1000);
+ }
+ 
+-static void test_valid_user_group_name_compat(void) {
++static void test_valid_user_group_name_relaxed(void) {
+         log_info("/* %s */", __func__);
+ 
+-        assert_se(!valid_user_group_name_compat(NULL));
+-        assert_se(!valid_user_group_name_compat(""));
+-        assert_se(!valid_user_group_name_compat("1"));
+-        assert_se(!valid_user_group_name_compat("65535"));
+-        assert_se(!valid_user_group_name_compat("-1"));
+-        assert_se(!valid_user_group_name_compat("-kkk"));
+-        assert_se(!valid_user_group_name_compat("rööt"));
+-        assert_se(!valid_user_group_name_compat("."));
+-        assert_se(!valid_user_group_name_compat(".eff"));
+-        assert_se(!valid_user_group_name_compat("foo\nbar"));
+-        assert_se(!valid_user_group_name_compat("0123456789012345678901234567890123456789"));
+-        assert_se(!valid_user_group_name_or_id_compat("aaa:bbb"));
+-        assert_se(!valid_user_group_name_compat("."));
+-        assert_se(!valid_user_group_name_compat(".1"));
+-        assert_se(!valid_user_group_name_compat(".65535"));
+-        assert_se(!valid_user_group_name_compat(".-1"));
+-        assert_se(!valid_user_group_name_compat(".-kkk"));
+-        assert_se(!valid_user_group_name_compat(".rööt"));
+-        assert_se(!valid_user_group_name_or_id_compat(".aaa:bbb"));
+-
+-        assert_se(valid_user_group_name_compat("root"));
+-        assert_se(valid_user_group_name_compat("lennart"));
+-        assert_se(valid_user_group_name_compat("LENNART"));
+-        assert_se(valid_user_group_name_compat("_kkk"));
+-        assert_se(valid_user_group_name_compat("kkk-"));
+-        assert_se(valid_user_group_name_compat("kk-k"));
+-        assert_se(valid_user_group_name_compat("eff.eff"));
+-        assert_se(valid_user_group_name_compat("eff."));
+-
+-        assert_se(valid_user_group_name_compat("some5"));
+-        assert_se(valid_user_group_name_compat("5some"));
+-        assert_se(valid_user_group_name_compat("INNER5NUMBER"));
++        assert_se(!valid_user_group_name(NULL, VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("", VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("1", VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("65535", VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("-1", VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("foo\nbar", VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_RELAX|VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name(".aaa:bbb", VALID_USER_RELAX|VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name(".", VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("..", VALID_USER_RELAX));
++
++        assert_se(valid_user_group_name("root", VALID_USER_RELAX));
++        assert_se(valid_user_group_name("lennart", VALID_USER_RELAX));
++        assert_se(valid_user_group_name("LENNART", VALID_USER_RELAX));
++        assert_se(valid_user_group_name("_kkk", VALID_USER_RELAX));
++        assert_se(valid_user_group_name("kkk-", VALID_USER_RELAX));
++        assert_se(valid_user_group_name("kk-k", VALID_USER_RELAX));
++        assert_se(valid_user_group_name("eff.eff", VALID_USER_RELAX));
++        assert_se(valid_user_group_name("eff.", VALID_USER_RELAX));
++        assert_se(valid_user_group_name("-kkk", VALID_USER_RELAX));
++        assert_se(valid_user_group_name("rööt", VALID_USER_RELAX));
++        assert_se(valid_user_group_name(".eff", VALID_USER_RELAX));
++        assert_se(valid_user_group_name(".1", VALID_USER_RELAX));
++        assert_se(valid_user_group_name(".65535", VALID_USER_RELAX));
++        assert_se(valid_user_group_name(".-1", VALID_USER_RELAX));
++        assert_se(valid_user_group_name(".-kkk", VALID_USER_RELAX));
++        assert_se(valid_user_group_name(".rööt", VALID_USER_RELAX));
++        assert_se(valid_user_group_name("...", VALID_USER_RELAX));
++
++        assert_se(valid_user_group_name("some5", VALID_USER_RELAX));
++        assert_se(valid_user_group_name("5some", VALID_USER_RELAX));
++        assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_RELAX));
++
++        assert_se(valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_RELAX));
++        assert_se(valid_user_group_name("Dāvis", VALID_USER_RELAX));
+ }
+ 
+ static void test_valid_user_group_name(void) {
+         log_info("/* %s */", __func__);
+ 
+-        assert_se(!valid_user_group_name(NULL));
+-        assert_se(!valid_user_group_name(""));
+-        assert_se(!valid_user_group_name("1"));
+-        assert_se(!valid_user_group_name("65535"));
+-        assert_se(!valid_user_group_name("-1"));
+-        assert_se(!valid_user_group_name("-kkk"));
+-        assert_se(!valid_user_group_name("rööt"));
+-        assert_se(!valid_user_group_name("."));
+-        assert_se(!valid_user_group_name(".eff"));
+-        assert_se(!valid_user_group_name("foo\nbar"));
+-        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789"));
+-        assert_se(!valid_user_group_name_or_id("aaa:bbb"));
+-        assert_se(!valid_user_group_name("."));
+-        assert_se(!valid_user_group_name(".1"));
+-        assert_se(!valid_user_group_name(".65535"));
+-        assert_se(!valid_user_group_name(".-1"));
+-        assert_se(!valid_user_group_name(".-kkk"));
+-        assert_se(!valid_user_group_name(".rööt"));
+-        assert_se(!valid_user_group_name_or_id(".aaa:bbb"));
+-
+-        assert_se(valid_user_group_name("root"));
+-        assert_se(valid_user_group_name("lennart"));
+-        assert_se(valid_user_group_name("LENNART"));
+-        assert_se(valid_user_group_name("_kkk"));
+-        assert_se(valid_user_group_name("kkk-"));
+-        assert_se(valid_user_group_name("kk-k"));
+-        assert_se(!valid_user_group_name("eff.eff"));
+-        assert_se(!valid_user_group_name("eff."));
+-
+-        assert_se(valid_user_group_name("some5"));
+-        assert_se(!valid_user_group_name("5some"));
+-        assert_se(valid_user_group_name("INNER5NUMBER"));
++        assert_se(!valid_user_group_name(NULL, 0));
++        assert_se(!valid_user_group_name("", 0));
++        assert_se(!valid_user_group_name("1", 0));
++        assert_se(!valid_user_group_name("65535", 0));
++        assert_se(!valid_user_group_name("-1", 0));
++        assert_se(!valid_user_group_name("-kkk", 0));
++        assert_se(!valid_user_group_name("rööt", 0));
++        assert_se(!valid_user_group_name(".", 0));
++        assert_se(!valid_user_group_name(".eff", 0));
++        assert_se(!valid_user_group_name("foo\nbar", 0));
++        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", 0));
++        assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name(".", 0));
++        assert_se(!valid_user_group_name("..", 0));
++        assert_se(!valid_user_group_name("...", 0));
++        assert_se(!valid_user_group_name(".1", 0));
++        assert_se(!valid_user_group_name(".65535", 0));
++        assert_se(!valid_user_group_name(".-1", 0));
++        assert_se(!valid_user_group_name(".-kkk", 0));
++        assert_se(!valid_user_group_name(".rööt", 0));
++        assert_se(!valid_user_group_name(".aaa:bbb", VALID_USER_ALLOW_NUMERIC));
++
++        assert_se(valid_user_group_name("root", 0));
++        assert_se(valid_user_group_name("lennart", 0));
++        assert_se(valid_user_group_name("LENNART", 0));
++        assert_se(valid_user_group_name("_kkk", 0));
++        assert_se(valid_user_group_name("kkk-", 0));
++        assert_se(valid_user_group_name("kk-k", 0));
++        assert_se(!valid_user_group_name("eff.eff", 0));
++        assert_se(!valid_user_group_name("eff.", 0));
++
++        assert_se(valid_user_group_name("some5", 0));
++        assert_se(!valid_user_group_name("5some", 0));
++        assert_se(valid_user_group_name("INNER5NUMBER", 0));
++
++        assert_se(!valid_user_group_name("piff.paff@ad.domain.example", 0));
++        assert_se(!valid_user_group_name("Dāvis", 0));
+ }
+ 
+-static void test_valid_user_group_name_or_id_compat(void) {
++static void test_valid_user_group_name_or_numeric_relaxed(void) {
+         log_info("/* %s */", __func__);
+ 
+-        assert_se(!valid_user_group_name_or_id_compat(NULL));
+-        assert_se(!valid_user_group_name_or_id_compat(""));
+-        assert_se(valid_user_group_name_or_id_compat("0"));
+-        assert_se(valid_user_group_name_or_id_compat("1"));
+-        assert_se(valid_user_group_name_or_id_compat("65534"));
+-        assert_se(!valid_user_group_name_or_id_compat("65535"));
+-        assert_se(valid_user_group_name_or_id_compat("65536"));
+-        assert_se(!valid_user_group_name_or_id_compat("-1"));
+-        assert_se(!valid_user_group_name_or_id_compat("-kkk"));
+-        assert_se(!valid_user_group_name_or_id_compat("rööt"));
+-        assert_se(!valid_user_group_name_or_id_compat("."));
+-        assert_se(!valid_user_group_name_or_id_compat(".eff"));
+-        assert_se(valid_user_group_name_or_id_compat("eff.eff"));
+-        assert_se(valid_user_group_name_or_id_compat("eff."));
+-        assert_se(!valid_user_group_name_or_id_compat("foo\nbar"));
+-        assert_se(!valid_user_group_name_or_id_compat("0123456789012345678901234567890123456789"));
+-        assert_se(!valid_user_group_name_or_id_compat("aaa:bbb"));
+-
+-        assert_se(valid_user_group_name_or_id_compat("root"));
+-        assert_se(valid_user_group_name_or_id_compat("lennart"));
+-        assert_se(valid_user_group_name_or_id_compat("LENNART"));
+-        assert_se(valid_user_group_name_or_id_compat("_kkk"));
+-        assert_se(valid_user_group_name_or_id_compat("kkk-"));
+-        assert_se(valid_user_group_name_or_id_compat("kk-k"));
+-
+-        assert_se(valid_user_group_name_or_id_compat("some5"));
+-        assert_se(valid_user_group_name_or_id_compat("5some"));
+-        assert_se(valid_user_group_name_or_id_compat("INNER5NUMBER"));
++        assert_se(!valid_user_group_name(NULL, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("0", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("1", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("65534", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("65535", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("65536", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("-1", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("foo\nbar", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(!valid_user_group_name(".", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(!valid_user_group_name("..", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++
++        assert_se(valid_user_group_name("root", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("lennart", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("LENNART", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("_kkk", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("kkk-", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("kk-k", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("-kkk", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("rööt", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name(".eff", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("eff.eff", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("eff.", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("...", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++
++        assert_se(valid_user_group_name("some5", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("5some", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++
++        assert_se(valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
++        assert_se(valid_user_group_name("Dāvis", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+ }
+ 
+-static void test_valid_user_group_name_or_id(void) {
++static void test_valid_user_group_name_or_numeric(void) {
+         log_info("/* %s */", __func__);
+ 
+-        assert_se(!valid_user_group_name_or_id(NULL));
+-        assert_se(!valid_user_group_name_or_id(""));
+-        assert_se(valid_user_group_name_or_id("0"));
+-        assert_se(valid_user_group_name_or_id("1"));
+-        assert_se(valid_user_group_name_or_id("65534"));
+-        assert_se(!valid_user_group_name_or_id("65535"));
+-        assert_se(valid_user_group_name_or_id("65536"));
+-        assert_se(!valid_user_group_name_or_id("-1"));
+-        assert_se(!valid_user_group_name_or_id("-kkk"));
+-        assert_se(!valid_user_group_name_or_id("rööt"));
+-        assert_se(!valid_user_group_name_or_id("."));
+-        assert_se(!valid_user_group_name_or_id(".eff"));
+-        assert_se(!valid_user_group_name_or_id("eff.eff"));
+-        assert_se(!valid_user_group_name_or_id("eff."));
+-        assert_se(!valid_user_group_name_or_id("foo\nbar"));
+-        assert_se(!valid_user_group_name_or_id("0123456789012345678901234567890123456789"));
+-        assert_se(!valid_user_group_name_or_id("aaa:bbb"));
+-
+-        assert_se(valid_user_group_name_or_id("root"));
+-        assert_se(valid_user_group_name_or_id("lennart"));
+-        assert_se(valid_user_group_name_or_id("LENNART"));
+-        assert_se(valid_user_group_name_or_id("_kkk"));
+-        assert_se(valid_user_group_name_or_id("kkk-"));
+-        assert_se(valid_user_group_name_or_id("kk-k"));
+-
+-        assert_se(valid_user_group_name_or_id("some5"));
+-        assert_se(!valid_user_group_name_or_id("5some"));
+-        assert_se(valid_user_group_name_or_id("INNER5NUMBER"));
++        assert_se(!valid_user_group_name(NULL, VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name("", VALID_USER_ALLOW_NUMERIC));
++        assert_se(valid_user_group_name("0", VALID_USER_ALLOW_NUMERIC));
++        assert_se(valid_user_group_name("1", VALID_USER_ALLOW_NUMERIC));
++        assert_se(valid_user_group_name("65534", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name("65535", VALID_USER_ALLOW_NUMERIC));
++        assert_se(valid_user_group_name("65536", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name("-1", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name("-kkk", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name("rööt", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name(".", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name("..", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name("...", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name(".eff", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name("eff.eff", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name("eff.", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name("foo\nbar", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC));
++
++        assert_se(valid_user_group_name("root", VALID_USER_ALLOW_NUMERIC));
++        assert_se(valid_user_group_name("lennart", VALID_USER_ALLOW_NUMERIC));
++        assert_se(valid_user_group_name("LENNART", VALID_USER_ALLOW_NUMERIC));
++        assert_se(valid_user_group_name("_kkk", VALID_USER_ALLOW_NUMERIC));
++        assert_se(valid_user_group_name("kkk-", VALID_USER_ALLOW_NUMERIC));
++        assert_se(valid_user_group_name("kk-k", VALID_USER_ALLOW_NUMERIC));
++
++        assert_se(valid_user_group_name("some5", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name("5some", VALID_USER_ALLOW_NUMERIC));
++        assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_ALLOW_NUMERIC));
++
++        assert_se(!valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_ALLOW_NUMERIC));
++        assert_se(!valid_user_group_name("Dāvis", VALID_USER_ALLOW_NUMERIC));
+ }
+ 
+ static void test_valid_gecos(void) {
+@@ -367,10 +386,10 @@ int main(int argc, char*argv[]) {
+         test_parse_uid();
+         test_uid_ptr();
+ 
+-        test_valid_user_group_name_compat();
++        test_valid_user_group_name_relaxed();
+         test_valid_user_group_name();
+-        test_valid_user_group_name_or_id_compat();
+-        test_valid_user_group_name_or_id();
++        test_valid_user_group_name_or_numeric_relaxed();
++        test_valid_user_group_name_or_numeric();
+         test_valid_gecos();
+         test_valid_home();
+ 
diff --git a/SOURCES/0475-man-mention-System-Administrator-s-Guide-in-systemct.patch b/SOURCES/0475-man-mention-System-Administrator-s-Guide-in-systemct.patch
new file mode 100644
index 0000000..0fed0ab
--- /dev/null
+++ b/SOURCES/0475-man-mention-System-Administrator-s-Guide-in-systemct.patch
@@ -0,0 +1,35 @@
+From 11a9ea82827d7b57dbce307b77ef8233a4cc028a Mon Sep 17 00:00:00 2001
+From: Lukas Nykryn <lnykryn@redhat.com>
+Date: Thu, 28 Aug 2014 15:12:10 +0200
+Subject: [PATCH] man: mention System Administrator's Guide in systemctl
+ manpage
+
+(cherry picked from commit d4582346f47064de24470b5f92e418966004925f)
+
+Resolves: #1623116
+---
+ man/systemctl.xml | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+diff --git a/man/systemctl.xml b/man/systemctl.xml
+index fa08ab6c0a..56f94d084c 100644
+--- a/man/systemctl.xml
++++ b/man/systemctl.xml
+@@ -2000,6 +2000,17 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
+     <xi:include href="less-variables.xml" xpointer="lesscharset"/>
+   </refsect1>
+ 
++  <refsect1>
++    <title>Examples</title>
++    <para>
++            For examples how to use systemctl in comparsion
++            with old service and chkconfig command please see:
++            <ulink url="https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/sect-Managing_Services_with_systemd-Services.html">
++                    Managing System Services
++            </ulink>
++    </para>
++  </refsect1>
++
+   <refsect1>
+     <title>See Also</title>
+     <para>
diff --git a/SOURCES/0476-udev-introduce-udev-net_id-naming-schemes.patch b/SOURCES/0476-udev-introduce-udev-net_id-naming-schemes.patch
new file mode 100644
index 0000000..d218efb
--- /dev/null
+++ b/SOURCES/0476-udev-introduce-udev-net_id-naming-schemes.patch
@@ -0,0 +1,257 @@
+From 08ac9f7f55c138678c6415139e7510a05a75b81d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= <msekleta@redhat.com>
+Date: Wed, 14 Oct 2020 16:57:44 +0200
+Subject: [PATCH] udev: introduce udev net_id "naming schemes"
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+With this we can stabilize how naming works for network interfaces. A
+user can request through a kernel cmdline option or an env var which
+scheme to follow. The idea is that installers use this to set into stone
+(a very soft stone though) the scheme used during installation so that
+interface naming doesn't change afterwards anymore.
+
+Why use env vars and kernel cmdline options, and not a config file of
+its own?
+
+Well, first of all there's no obvious existing one to use. But more
+importantly: I have the feeling that this logic is kind of an incomplete
+hack, and I simply don't want to do advertise this as a perfectly
+working solution. So far we used env vars for the non-so-official
+options and proper config files for the official stuff. Given how
+incomplete this logic is (i.e. the big variable for naming remains the
+kernel, which might expose sysfs attributes in newer versions that we
+check for and didn't exist in older versions — and other problems like
+this), I am simply not confident in giving this first-class exposure in
+a primary configuration file.
+
+Fixes: #10448
+
+(cherry-picked from commit f7e81fd96fdfe0ac6dcdb72de43f7cb4720e363a)
+
+Related: #1827462
+
+[msekleta: note that we are introducing our own naming schemes based on
+RHEL-8 minor versions. Also we are not backporting all naming scheme
+features that appeared in the original commit. We are backporting only
+features relevant for v239 while original commit also converted
+changes introduced in v240 into naming scheme flags.]
+---
+ doc/ENVIRONMENT.md             |   9 +++
+ man/kernel-command-line.xml    |   1 +
+ man/systemd-udevd.service.xml  |  16 +++++
+ src/udev/udev-builtin-net_id.c | 106 ++++++++++++++++++++++++++++++++-
+ 4 files changed, 130 insertions(+), 2 deletions(-)
+
+diff --git a/doc/ENVIRONMENT.md b/doc/ENVIRONMENT.md
+index 39a36a52cc..1a4aa01ef4 100644
+--- a/doc/ENVIRONMENT.md
++++ b/doc/ENVIRONMENT.md
+@@ -76,6 +76,15 @@ systemd-logind:
+   hibernation is available even if the swap devices do not provide enough room
+   for it.
+ 
++* `$NET_NAMING_SCHEME=` – if set, takes a network naming scheme (i.e. one of
++  v238, v239, v240 …) as parameter. If specified udev's net_id builtin will
++  follow the specified naming scheme when determining stable network interface
++  names. This may be used to revert to naming schemes of older udev versions,
++  in order to provide more stable naming across updates. This environment
++  variable takes precedence over the kernel command line option
++  `net.naming-scheme=`, except if the value is prefixed with `:` in which case
++  the kernel command line option takes precedence, if it is specified as well.
++
+ installed systemd tests:
+ 
+ * `$SYSTEMD_TEST_DATA` — override the location of test data. This is useful if
+diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml
+index 4d8cb4e50e..b753d0592c 100644
+--- a/man/kernel-command-line.xml
++++ b/man/kernel-command-line.xml
+@@ -246,6 +246,7 @@
+         <term><varname>udev.event_timeout=</varname></term>
+         <term><varname>rd.udev.event_timeout=</varname></term>
+         <term><varname>net.ifnames=</varname></term>
++        <term><varname>net.naming-scheme=</varname></term>
+ 
+         <listitem>
+           <para>Parameters understood by the device event managing
+diff --git a/man/systemd-udevd.service.xml b/man/systemd-udevd.service.xml
+index 73c77ea690..6449103441 100644
+--- a/man/systemd-udevd.service.xml
++++ b/man/systemd-udevd.service.xml
+@@ -170,6 +170,22 @@
+           when possible. It is enabled by default; specifying 0 disables it.</para>
+         </listitem>
+       </varlistentry>
++      <varlistentry>
++        <term><varname>net.naming-scheme=</varname></term>
++        <listitem>
++          <para>Network interfaces are renamed to give them predictable names when possible (unless
++          <varname>net.ifnames=0</varname> is specified, see above). The names are derived from various device metadata
++          fields. Newer versions of <filename>systemd-udevd.service</filename> take more of these fields into account,
++          improving (and thus possibly changing) the names used for the same devices. With this kernel command line
++          option it is possible to pick a specific version of this algorithm. It expects a naming scheme identifier as
++          argument. Currently the following identifiers are known: <literal>v238</literal>, <literal>v239</literal>,
++          <literal>v240</literal> which each implement the naming scheme that was the default in the indicated systemd
++          version. Note that selecting a specific scheme is not sufficient to fully stabilize interface naming: the
++          naming is generally derived from driver attributes exposed by the kernel. As the kernel is updated,
++          previously missing attributes <filename>systemd-udevd.service</filename> is checking might appear, which
++          affects older name derivation algorithms, too.</para>
++        </listitem>
++      </varlistentry>
+     </variablelist>
+     <!-- when adding entries here, consider also adding them
+          in kernel-command-line.xml -->
+diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c
+index 147e04ab8c..148696183e 100644
+--- a/src/udev/udev-builtin-net_id.c
++++ b/src/udev/udev-builtin-net_id.c
+@@ -96,6 +96,7 @@
+ #include "fileio.h"
+ #include "fs-util.h"
+ #include "parse-util.h"
++#include "proc-cmdline.h"
+ #include "stdio-util.h"
+ #include "string-util.h"
+ #include "udev.h"
+@@ -103,6 +104,52 @@
+ 
+ #define ONBOARD_INDEX_MAX (16*1024-1)
+ 
++/* So here's the deal: net_id is supposed to be an excercise in providing stable names for network devices. However, we
++ * also want to keep updating the naming scheme used in future versions of net_id. These two goals of course are
++ * contradictory: on one hand we want things to not change and on the other hand we want them to improve. Our way out
++ * of this dilemma is to introduce the "naming scheme" concept: each time we improve the naming logic we define a new
++ * flag for it. Then, we keep a list of schemes, each identified by a name associated with the flags it implements. Via
++ * a kernel command line and environment variable we then allow the user to pick the scheme they want us to follow:
++ * installers could "freeze" the used scheme at the moment of installation this way.
++ *
++ * Developers: each time you tweak the naming logic here, define a new flag below, and condition the tweak with
++ * it. Each time we do a release we'll then add a new scheme entry and include all newly defined flags.
++ *
++ * Note that this is only half a solution to the problem though: not only udev/net_id gets updated all the time, the
++ * kernel gets too. And thus a kernel that previously didn't expose some sysfs attribute we look for might eventually
++ * do, and thus affect our naming scheme too. Thus, enforcing a naming scheme will make interfacing more stable across
++ * OS versions, but not fully stabilize them. */
++typedef enum NamingSchemeFlags {
++        /* First, the individual features */
++        NAMING_SR_IOV_V        = 1 << 0, /* Use "v" suffix for SR-IOV, see 609948c7043a40008b8299529c978ed8e11de8f6*/
++        NAMING_NPAR_ARI        = 1 << 1, /* Use NPAR "ARI", see 6bc04997b6eab35d1cb9fa73889892702c27be09 */
++
++        /* And now the masks that combine the features above */
++        NAMING_V238 = 0,
++        NAMING_V239 = NAMING_V238|NAMING_SR_IOV_V|NAMING_NPAR_ARI,
++        NAMING_RHEL_8_0 = NAMING_V239,
++        NAMING_RHEL_8_1 = NAMING_V239,
++        NAMING_RHEL_8_2 = NAMING_V239,
++        NAMING_RHEL_8_3 = NAMING_V239,
++
++        _NAMING_SCHEME_FLAGS_INVALID = -1,
++} NamingSchemeFlags;
++
++typedef struct NamingScheme {
++        const char *name;
++        NamingSchemeFlags flags;
++} NamingScheme;
++
++static const NamingScheme naming_schemes[] = {
++        { "v238", NAMING_V238 },
++        { "v239", NAMING_V239 },
++        { "rhel-8.0", NAMING_RHEL_8_0 },
++        { "rhel-8.1", NAMING_RHEL_8_1 },
++        { "rhel-8.2", NAMING_RHEL_8_2 },
++        { "rhel-8.3", NAMING_RHEL_8_3 },
++        /* … add more schemes here, as the logic to name devices is updated … */
++};
++
+ enum netname_type{
+         NET_UNDEF,
+         NET_PCI,
+@@ -138,6 +185,56 @@ struct virtfn_info {
+         char suffix[IFNAMSIZ];
+ };
+ 
++static const NamingScheme* naming_scheme(void) {
++        static const NamingScheme *cache = NULL;
++        _cleanup_free_ char *buffer = NULL;
++        const char *e, *k;
++
++        if (cache)
++                return cache;
++
++        /* Acquire setting from the kernel command line */
++        (void) proc_cmdline_get_key("net.naming-scheme", 0, &buffer);
++
++        /* Also acquire it from an env var */
++        e = getenv("NET_NAMING_SCHEME");
++        if (e) {
++                if (*e == ':') {
++                        /* If prefixed with ':' the kernel cmdline takes precedence */
++                        k = buffer ?: e + 1;
++                } else
++                        k = e; /* Otherwise the env var takes precedence */
++        } else
++                k = buffer;
++
++        if (k) {
++                size_t i;
++
++                for (i = 0; i < ELEMENTSOF(naming_schemes); i++)
++                        if (streq(naming_schemes[i].name, k)) {
++                                cache = naming_schemes + i;
++                                break;
++                        }
++
++                if (!cache)
++                        log_warning("Unknown interface naming scheme '%s' requested, ignoring.", k);
++        }
++
++        if (cache)
++                log_info("Using interface naming scheme '%s'.", cache->name);
++        else {
++                /* RHEL-only: here we differ from the upstream and if no naming scheme was selected we default to naming from systemd-239 */
++                cache = &naming_schemes[2];
++                log_info("Using default interface naming scheme '%s'.", cache->name);
++        }
++
++        return cache;
++}
++
++static bool naming_scheme_has(NamingSchemeFlags flags) {
++        return FLAGS_SET(naming_scheme()->flags, flags);
++}
++
+ /* skip intermediate virtio devices */
+ static struct udev_device *skip_virtio(struct udev_device *dev) {
+         struct udev_device *parent = dev;
+@@ -299,7 +396,9 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) {
+ 
+         if (sscanf(udev_device_get_sysname(names->pcidev), "%x:%x:%x.%u", &domain, &bus, &slot, &func) != 4)
+                 return -ENOENT;
+-        if (is_pci_ari_enabled(names->pcidev))
++
++        if (naming_scheme_has(NAMING_NPAR_ARI) &&
++            is_pci_ari_enabled(names->pcidev))
+                 /* ARI devices support up to 256 functions on a single device ("slot"), and interpret the
+                  * traditional 5-bit slot and 3-bit function number as a single 8-bit function number,
+                  * where the slot makes up the upper 5 bits. */
+@@ -494,7 +593,8 @@ static int names_pci(struct udev_device *dev, struct netnames *names) {
+                         return -ENOENT;
+         }
+ 
+-        if (get_virtfn_info(dev, names, &vf_info) >= 0) {
++        if (naming_scheme_has(NAMING_SR_IOV_V) &&
++            get_virtfn_info(dev, names, &vf_info) >= 0) {
+                 /* If this is an SR-IOV virtual device, get base name using physical device and add virtfn suffix. */
+                 vf_names.pcidev = vf_info.physfn_pcidev;
+                 dev_pci_onboard(dev, &vf_names);
+@@ -741,6 +841,8 @@ static int builtin_net_id(struct udev_device *dev, int argc, char *argv[], bool
+                         prefix = "ww";
+         }
+ 
++        udev_builtin_add_property(dev, test, "ID_NET_NAMING_SCHEME", naming_scheme()->name);
++
+         err = names_mac(dev, &names);
+         if (err >= 0 && names.mac_valid) {
+                 char str[IFNAMSIZ];
diff --git a/SOURCES/0477-meson-make-net.naming-scheme-default-configurable.patch b/SOURCES/0477-meson-make-net.naming-scheme-default-configurable.patch
new file mode 100644
index 0000000..0c9f850
--- /dev/null
+++ b/SOURCES/0477-meson-make-net.naming-scheme-default-configurable.patch
@@ -0,0 +1,188 @@
+From 8c263758fe196624005f19bd6f46d63e3841c5be Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Tue, 11 Dec 2018 23:28:29 +0100
+Subject: [PATCH] meson: make net.naming-scheme= default configurable
+
+This is useful for distributions, where the stability of interface names should
+be preseved after an upgrade of systemd. So when some specific release of the
+distro is made available, systemd defaults to the latest & greatest naming
+scheme, and subsequent updates set the same default. This default may still
+be overriden through the kernel and env var options.
+
+A special value "latest" is also allowed. Without a specific name, it is harder
+to verride from meson. In case of 'combo' options, meson reads the default
+during the initial configuration, and "remembers" this choice. When systemd is
+updated, old build/ directories could keep the old default, which would be
+annoying. Hence, "latest" is introduced to make it explicit, yet follow the
+upstream. This is actually useful for the user too, because it may be used
+as an override, without having to actually specify a version.
+
+(cherry picked from commit 06da5c63dd697ea4087e76c6d809b60b5780b87c)
+
+Related: #1827462
+
+[msekleta: note that our default is not latest but rhel-8.0]
+---
+ doc/ENVIRONMENT.md             | 15 +++++++-------
+ man/systemd-udevd.service.xml  | 24 ++++++++++++---------
+ meson.build                    |  4 ++++
+ meson_options.txt              |  3 +++
+ src/udev/udev-builtin-net_id.c | 38 ++++++++++++++++++++--------------
+ 5 files changed, 51 insertions(+), 33 deletions(-)
+
+diff --git a/doc/ENVIRONMENT.md b/doc/ENVIRONMENT.md
+index 1a4aa01ef4..0e763b6302 100644
+--- a/doc/ENVIRONMENT.md
++++ b/doc/ENVIRONMENT.md
+@@ -77,13 +77,14 @@ systemd-logind:
+   for it.
+ 
+ * `$NET_NAMING_SCHEME=` – if set, takes a network naming scheme (i.e. one of
+-  v238, v239, v240 …) as parameter. If specified udev's net_id builtin will
+-  follow the specified naming scheme when determining stable network interface
+-  names. This may be used to revert to naming schemes of older udev versions,
+-  in order to provide more stable naming across updates. This environment
+-  variable takes precedence over the kernel command line option
+-  `net.naming-scheme=`, except if the value is prefixed with `:` in which case
+-  the kernel command line option takes precedence, if it is specified as well.
++  "rhel-8.0", "rhel-8.1", "rhel-8.2"…, or the special value "latest") as
++  parameter. If specified udev's net_id builtin will follow the specified
++  naming scheme when determining stable network interface names. This may be
++  used to revert to naming schemes of older udev versions, in order to provide
++  more stable naming across updates. This environment variable takes precedence
++  over the kernel command line option `net.naming-scheme=`, except if the value
++  is prefixed with `:` in which case the kernel command line option takes
++  precedence, if it is specified as well.
+ 
+ installed systemd tests:
+ 
+diff --git a/man/systemd-udevd.service.xml b/man/systemd-udevd.service.xml
+index 6449103441..b738591c93 100644
+--- a/man/systemd-udevd.service.xml
++++ b/man/systemd-udevd.service.xml
+@@ -174,16 +174,20 @@
+         <term><varname>net.naming-scheme=</varname></term>
+         <listitem>
+           <para>Network interfaces are renamed to give them predictable names when possible (unless
+-          <varname>net.ifnames=0</varname> is specified, see above). The names are derived from various device metadata
+-          fields. Newer versions of <filename>systemd-udevd.service</filename> take more of these fields into account,
+-          improving (and thus possibly changing) the names used for the same devices. With this kernel command line
+-          option it is possible to pick a specific version of this algorithm. It expects a naming scheme identifier as
+-          argument. Currently the following identifiers are known: <literal>v238</literal>, <literal>v239</literal>,
+-          <literal>v240</literal> which each implement the naming scheme that was the default in the indicated systemd
+-          version. Note that selecting a specific scheme is not sufficient to fully stabilize interface naming: the
+-          naming is generally derived from driver attributes exposed by the kernel. As the kernel is updated,
+-          previously missing attributes <filename>systemd-udevd.service</filename> is checking might appear, which
+-          affects older name derivation algorithms, too.</para>
++          <varname>net.ifnames=0</varname> is specified, see above). The names are derived from various
++          device metadata fields. Newer versions of <filename>systemd-udevd.service</filename> take more of
++          these fields into account, improving (and thus possibly changing) the names used for the same
++          devices. With this kernel command line option it is possible to pick a specific version of this
++          algorithm. It expects a naming scheme identifier as argument. Currently the following identifiers
++          are known: <literal>rhel-8.0</literal>, <literal>rhel-8.1</literal>, <literal>rhel-8.2</literal>,
++          <literal>rhel-8.3</literal> which each implement the naming scheme that was the default in the
++          indicated Red Hat Enterprise Linux minor version. In addition, <literal>latest</literal> may be
++          used to designate the latest scheme known (to this particular version of
++          <filename>systemd-udevd.service</filename>).</para>
++          <para>Note that selecting a specific scheme is not sufficient to fully stabilize interface naming:
++          the naming is generally derived from driver attributes exposed by the kernel. As the kernel is
++          updated, previously missing attributes <filename>systemd-udevd.service</filename> is checking might
++          appear, which affects older name derivation algorithms, too.</para>
+         </listitem>
+       </varlistentry>
+     </variablelist>
+diff --git a/meson.build b/meson.build
+index 65c1d0785e..57de947367 100644
+--- a/meson.build
++++ b/meson.build
+@@ -639,6 +639,9 @@ else
+         conf.set('DEFAULT_HIERARCHY', 'CGROUP_UNIFIED_ALL')
+ endif
+ 
++default_net_naming_scheme = get_option('default-net-naming-scheme')
++conf.set_quoted('DEFAULT_NET_NAMING_SCHEME', default_net_naming_scheme)
++
+ time_epoch = get_option('time-epoch')
+ if time_epoch == ''
+         NEWS = files('NEWS')
+@@ -2925,6 +2928,7 @@ status = [
+         'default DNSSEC mode:               @0@'.format(default_dnssec),
+         'default DNS-over-TLS mode:         @0@'.format(default_dns_over_tls),
+         'default cgroup hierarchy:          @0@'.format(default_hierarchy),
++        'default net.naming-scheme setting: @0@'.format(default_net_naming_scheme),
+         'default KillUserProcesses setting: @0@'.format(kill_user_processes)]
+ 
+ alt_dns_servers = '\n                                            '.join(dns_servers.split(' '))
+diff --git a/meson_options.txt b/meson_options.txt
+index 0996891177..213079ac15 100644
+--- a/meson_options.txt
++++ b/meson_options.txt
+@@ -158,6 +158,9 @@ option('default-hierarchy', type : 'combo',
+        description : 'default cgroup hierarchy')
+ option('time-epoch', type : 'string',
+        description : 'time epoch for time clients')
++option('default-net-naming-scheme', type : 'combo',
++       choices : ['rhel-8.0', 'rhel-8.1', 'rhel-8.2', 'rhel-8.3', 'latest'],
++       description : 'default net.naming-scheme= value')
+ option('system-uid-max', type : 'string',
+        description : 'maximum system UID')
+ option('system-gid-max', type : 'string',
+diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c
+index 148696183e..d85dc2848b 100644
+--- a/src/udev/udev-builtin-net_id.c
++++ b/src/udev/udev-builtin-net_id.c
+@@ -185,6 +185,19 @@ struct virtfn_info {
+         char suffix[IFNAMSIZ];
+ };
+ 
++static const NamingScheme* naming_scheme_from_name(const char *name) {
++        size_t i;
++
++        if (streq(name, "latest"))
++                return naming_schemes + ELEMENTSOF(naming_schemes) - 1;
++
++        for (i = 0; i < ELEMENTSOF(naming_schemes); i++)
++                if (streq(naming_schemes[i].name, name))
++                        return naming_schemes + i;
++
++        return NULL;
++}
++
+ static const NamingScheme* naming_scheme(void) {
+         static const NamingScheme *cache = NULL;
+         _cleanup_free_ char *buffer = NULL;
+@@ -208,25 +221,18 @@ static const NamingScheme* naming_scheme(void) {
+                 k = buffer;
+ 
+         if (k) {
+-                size_t i;
+-
+-                for (i = 0; i < ELEMENTSOF(naming_schemes); i++)
+-                        if (streq(naming_schemes[i].name, k)) {
+-                                cache = naming_schemes + i;
+-                                break;
+-                        }
++                cache = naming_scheme_from_name(k);
++                if (cache) {
++                        log_info("Using interface naming scheme '%s'.", cache->name);
++                        return cache;
++                }
+ 
+-                if (!cache)
+-                        log_warning("Unknown interface naming scheme '%s' requested, ignoring.", k);
++                log_warning("Unknown interface naming scheme '%s' requested, ignoring.", k);
+         }
+ 
+-        if (cache)
+-                log_info("Using interface naming scheme '%s'.", cache->name);
+-        else {
+-                /* RHEL-only: here we differ from the upstream and if no naming scheme was selected we default to naming from systemd-239 */
+-                cache = &naming_schemes[2];
+-                log_info("Using default interface naming scheme '%s'.", cache->name);
+-        }
++        cache = naming_scheme_from_name(DEFAULT_NET_NAMING_SCHEME);
++        assert(cache);
++        log_info("Using default interface naming scheme '%s'.", cache->name);
+ 
+         return cache;
+ }
diff --git a/SOURCES/0478-man-describe-naming-schemes-in-a-new-man-page.patch b/SOURCES/0478-man-describe-naming-schemes-in-a-new-man-page.patch
new file mode 100644
index 0000000..be9f964
--- /dev/null
+++ b/SOURCES/0478-man-describe-naming-schemes-in-a-new-man-page.patch
@@ -0,0 +1,522 @@
+From af528dcffaab1efea760395cc6676fe4b01e89b5 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Thu, 9 May 2019 12:34:30 +0200
+Subject: [PATCH] man: describe naming schemes in a new man page
+
+I decided to make this a separate man page because it is freakin' long.
+This content could equally well go in systemd-udevd.service(8), systemd.link(5),
+or a new man page for the net_id builtin.
+
+v2:
+- rename to systemd.net-naming-scheme
+- add udevadm test-builtin net_id example
+
+(cherry picked from commit 0b1e5b6ed8c6b9a2bc53709eb75e381d360f05bf)
+
+Related: #1827462
+
+[msekleta: I've removed parts that describe features which are not
+available in RHEL-8]
+---
+ man/rules/meson.build             |   1 +
+ man/systemd-udevd.service.xml     |  19 +-
+ man/systemd.link.xml              |  10 +-
+ man/systemd.net-naming-scheme.xml | 385 ++++++++++++++++++++++++++++++
+ src/udev/udev-builtin-net_id.c    |   1 +
+ 5 files changed, 402 insertions(+), 14 deletions(-)
+ create mode 100644 man/systemd.net-naming-scheme.xml
+
+diff --git a/man/rules/meson.build b/man/rules/meson.build
+index 7ae94ea265..e6c0a99bbd 100644
+--- a/man/rules/meson.build
++++ b/man/rules/meson.build
+@@ -714,6 +714,7 @@ manpages = [
+  ['systemd.kill', '5', [], ''],
+  ['systemd.link', '5', [], ''],
+  ['systemd.mount', '5', [], ''],
++ ['systemd.net-naming-scheme', '7', [], ''],
+  ['systemd.netdev', '5', [], 'ENABLE_NETWORKD'],
+  ['systemd.network', '5', [], 'ENABLE_NETWORKD'],
+  ['systemd.nspawn', '5', [], ''],
+diff --git a/man/systemd-udevd.service.xml b/man/systemd-udevd.service.xml
+index b738591c93..f4cdb2f1e7 100644
+--- a/man/systemd-udevd.service.xml
++++ b/man/systemd-udevd.service.xml
+@@ -174,15 +174,11 @@
+         <term><varname>net.naming-scheme=</varname></term>
+         <listitem>
+           <para>Network interfaces are renamed to give them predictable names when possible (unless
+-          <varname>net.ifnames=0</varname> is specified, see above). The names are derived from various
+-          device metadata fields. Newer versions of <filename>systemd-udevd.service</filename> take more of
+-          these fields into account, improving (and thus possibly changing) the names used for the same
+-          devices. With this kernel command line option it is possible to pick a specific version of this
+-          algorithm. It expects a naming scheme identifier as argument. Currently the following identifiers
+-          are known: <literal>rhel-8.0</literal>, <literal>rhel-8.1</literal>, <literal>rhel-8.2</literal>,
+-          <literal>rhel-8.3</literal> which each implement the naming scheme that was the default in the
+-          indicated Red Hat Enterprise Linux minor version. In addition, <literal>latest</literal> may be
+-          used to designate the latest scheme known (to this particular version of
++          <varname>net.ifnames=0</varname> is specified, see above). With this kernel command line option it
++          is possible to pick a specific version of this algorithm and override the default chosen at
++          compilation time. Expects one of the naming scheme identifiers listed in
++          <citerefentry><refentrytitle>systemd.net-naming-scheme</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
++          or <literal>latest</literal> to select the latest scheme known (to this particular version of
+           <filename>systemd-udevd.service</filename>).</para>
+           <para>Note that selecting a specific scheme is not sufficient to fully stabilize interface naming:
+           the naming is generally derived from driver attributes exposed by the kernel. As the kernel is
+@@ -191,9 +187,8 @@
+         </listitem>
+       </varlistentry>
+     </variablelist>
+-    <!-- when adding entries here, consider also adding them
+-         in kernel-command-line.xml -->
+- </refsect1>
++    <!-- when adding entries here, consider also adding them in kernel-command-line.xml -->
++  </refsect1>
+ 
+   <refsect1>
+     <title>See Also</title>
+diff --git a/man/systemd.link.xml b/man/systemd.link.xml
+index 6708753e82..32657308d0 100644
+--- a/man/systemd.link.xml
++++ b/man/systemd.link.xml
+@@ -286,6 +286,7 @@
+                 <para>The name is set based on information given by
+                 the firmware for on-board devices, as exported by the
+                 udev property <literal>ID_NET_NAME_ONBOARD</literal>.
++                See <citerefentry><refentrytitle>systemd.net-naming-scheme</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
+                 </para>
+               </listitem>
+             </varlistentry>
+@@ -295,6 +296,7 @@
+                 <para>The name is set based on information given by
+                 the firmware for hot-plug devices, as exported by the
+                 udev property <literal>ID_NET_NAME_SLOT</literal>.
++                See <citerefentry><refentrytitle>systemd.net-naming-scheme</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
+                 </para>
+               </listitem>
+             </varlistentry>
+@@ -303,7 +305,9 @@
+               <listitem>
+                 <para>The name is set based on the device's physical
+                 location, as exported by the udev property
+-                <literal>ID_NET_NAME_PATH</literal>.</para>
++                <literal>ID_NET_NAME_PATH</literal>.
++                See <citerefentry><refentrytitle>systemd.net-naming-scheme</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
++                </para>
+               </listitem>
+             </varlistentry>
+             <varlistentry>
+@@ -311,7 +315,9 @@
+               <listitem>
+                 <para>The name is set based on the device's persistent
+                 MAC address, as exported by the udev property
+-                <literal>ID_NET_NAME_MAC</literal>.</para>
++                <literal>ID_NET_NAME_MAC</literal>.
++                See <citerefentry><refentrytitle>systemd.net-naming-scheme</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
++                </para>
+               </listitem>
+             </varlistentry>
+           </variablelist>
+diff --git a/man/systemd.net-naming-scheme.xml b/man/systemd.net-naming-scheme.xml
+new file mode 100644
+index 0000000000..a12cc3c460
+--- /dev/null
++++ b/man/systemd.net-naming-scheme.xml
+@@ -0,0 +1,385 @@
++<?xml version='1.0'?>
++<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
++  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
++<!-- SPDX-License-Identifier: LGPL-2.1+ -->
++
++<refentry id="systemd.net-naming-scheme">
++  <refentryinfo>
++    <title>systemd.net-naming-scheme</title>
++    <productname>systemd</productname>
++  </refentryinfo>
++
++  <refmeta>
++    <refentrytitle>systemd.net-naming-scheme</refentrytitle>
++    <manvolnum>7</manvolnum>
++  </refmeta>
++
++  <refnamediv>
++    <refname>systemd.net-naming-scheme</refname>
++    <refpurpose>Network device naming schemes</refpurpose>
++  </refnamediv>
++
++  <refsect1>
++    <title>Description</title>
++
++    <para>Network interfaces may be renamed to give them predictable names when there's enough information to
++    generate appropriate names and the use of certain types of names is configured. This page describes the
++    first part, i.e. what possible names may be generated. Those names are generated by the
++    <citerefentry><refentrytitle>systemd-udevd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
++    builtin <command>net_id</command> and exported as udev properties
++    (<varname>ID_NET_NAME_ONBOARD=</varname>, <varname>ID_NET_LABEL_ONBOARD=</varname>,
++    <varname>ID_NET_NAME_PATH=</varname>, <varname>ID_NET_NAME_SLOT=</varname>).</para>
++
++    <para>Names are derived from various device metadata attributes. Newer versions of udev take more of
++    these attributes into account, improving (and thus possibly changing) the names used for the same
++    devices. Differents version of the naming rules are called "naming schemes". The default naming scheme is
++    chosen at compilation time. Usually this will be the latest implemented version, but it is also possible
++    to set one of the older versions to preserve compatibility. This may be useful for example for
++    distributions, which may introduce new versions of systemd in stable releases without changing the naming
++    scheme. The naming scheme may also be overriden using the <varname>net.naming-scheme=</varname> kernel
++    command line switch, see
++    <citerefentry><refentrytitle>systemd-udevd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
++    Available naming schemes are described below.</para>
++
++    <para>After the udev proprties have been generated, appropriate udev rules may be used to actually rename
++    devices based on those properties. See the description of <varname>NamePolicy=</varname> in
++    <citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
++    </para>
++  </refsect1>
++
++  <refsect1>
++    <title>Naming</title>
++
++    <para>All names start with a two-character prefix that signifies the interface type.</para>
++
++    <table>
++      <title>Two character prefixes based on the type of interface</title>
++
++      <tgroup cols='2'>
++        <thead>
++          <row>
++            <entry>Prefix</entry>
++            <entry>Description</entry>
++          </row>
++        </thead>
++        <tbody>
++          <row>
++            <entry><constant>en</constant></entry>
++            <entry>Ethernet</entry>
++          </row>
++          <row>
++            <entry><constant>sl</constant></entry>
++            <entry>serial line IP (slip)</entry>
++          </row>
++          <row>
++            <entry><constant>wl</constant></entry>
++            <entry>Wireless local area network (WLAN)</entry>
++          </row>
++          <row>
++            <entry><constant>ww</constant></entry>
++            <entry>Wireless wide area network (WWAN)</entry>
++          </row>
++        </tbody>
++      </tgroup>
++    </table>
++
++    <para>The udev <command>net_id</command> builtin exports the following udev device properties:</para>
++
++    <variablelist>
++        <varlistentry>
++          <term><varname>ID_NET_NAME_ONBOARD=<replaceable>prefix</replaceable><constant>o</constant><replaceable>number</replaceable></varname></term>
++
++          <listitem><para>This name is set based on the ordering information given by the firmware for
++          on-board devices. The name consists of the prefix, letter <constant>o</constant>, and a number
++          specified by the firmware. This is only available for PCI devices.</para>
++          </listitem>
++        </varlistentry>
++
++        <varlistentry>
++          <term><varname>ID_NET_LABEL_ONBOARD=<replaceable>prefix</replaceable> <replaceable>label</replaceable></varname></term>
++
++          <listitem><para>This property is set based on label given by the firmware for on-board devices. The
++          name consists of the prefix concatenated with the label. This is only available for PCI devices.
++          </para>
++          </listitem>
++        </varlistentry>
++
++        <varlistentry>
++          <term><varname>ID_NET_NAME_MAC=<replaceable>prefix</replaceable><constant>x</constant><replaceable>AABBCCDDEEFF</replaceable></varname></term>
++
++          <listitem><para>This name consists of the prefix, letter <constant>x</constant>, and 12 hexadecimal
++          digits of the MAC address. It is available if the device has a fixed MAC address. Because this name
++          is based on an attribute of the card itself, it remains "stable" when the device is moved (even
++          between machines), but will change when the hardware is replaced.</para>
++          </listitem>
++        </varlistentry>
++
++        <varlistentry>
++          <term><varname>ID_NET_NAME_SLOT=<replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]</varname></term>
++          <term><varname>ID_NET_NAME_SLOT=<replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]<constant>b</constant><replaceable>number</replaceable></varname></term>
++          <term><varname>ID_NET_NAME_SLOT=<replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]<constant>u</constant><replaceable>port</replaceable>…[<constant>c</constant><replaceable>config</replaceable>][<constant>i</constant><replaceable>interface</replaceable>]</varname></term>
++          <term><varname>ID_NET_NAME_SLOT=<replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]<constant>v</constant><replaceable>slot</replaceable></varname></term>
++
++          <listitem><para>This property describes the slot position. Different schemes are used depending on
++          the bus type, as described in the table below. In all cases, PCI slot information must be known. In
++          case of USB, BCMA, and SR-VIO devices, the full name consists of the prefix, PCI slot identifier,
++          and USB or BCMA or SR-VIO slot identifier. The first two parts are denoted as "…" in the table
++          below.</para>
++
++          <table>
++            <title>Slot naming schemes</title>
++
++            <tgroup cols='2'>
++              <thead>
++                <row>
++                  <entry>Format</entry>
++                  <entry>Description</entry>
++                </row>
++              </thead>
++
++              <tbody>
++                <row>
++                  <entry><replaceable>prefix</replaceable> [<constant>P</constant><replaceable>domain</replaceable>] <constant>s</constant><replaceable>slot</replaceable> [<constant>f</constant><replaceable>function</replaceable>] [<constant>n</constant><replaceable>port_name</replaceable> | <constant>d</constant><replaceable>dev_port</replaceable>]</entry>
++                  <entry>PCI slot number</entry>
++                </row>
++
++                <row>
++                  <entry>… <constant>b</constant><replaceable>number</replaceable></entry>
++                  <entry>Broadcom bus (BCMA) core number</entry>
++                </row>
++
++                <row>
++                  <entry>… <constant>u</constant><replaceable>port</replaceable>… [<constant>c</constant><replaceable>config</replaceable>] [<constant>i</constant><replaceable>interface</replaceable>]</entry>
++                  <entry>USB port number chain</entry>
++                </row>
++
++                <row>
++                  <entry>… <constant>v</constant><replaceable>slot</replaceable></entry>
++                  <entry>SR-VIO slot number</entry>
++                </row>
++              </tbody>
++            </tgroup>
++          </table>
++
++          <para>The PCI domain is only prepended when it is not 0. All multi-function PCI devices will carry
++          the <constant>f<replaceable>function</replaceable></constant> number in the device name, including
++          the function 0 device. For non-multi-function devices, the number is suppressed if 0. The port name
++          <replaceable>port_name</replaceable> is used, or the port number
++          <constant>d</constant><replaceable>dev_port</replaceable> if the name is not known.</para>
++
++          <para>For BCMA devices, the core number is suppressed when 0.</para>
++
++          <para>For USB devices the full chain of port numbers of hubs is composed. If the name gets longer
++          than the maximum number of 15 characters, the name is not exported. The usual USB configuration
++          number 1 and interface number 0 values are suppressed.</para>
++          </listitem>
++
++          <para>SR-IOV virtual devices are named based on the name of the parent interface, with a suffix of
++          <constant>v</constant> and the virtual device number, with any leading zeros removed. The bus
++          number is ignored. This device type is found in IBM PowerVMs.</para>
++        </varlistentry>
++
++        <varlistentry>
++          <term><varname>ID_NET_NAME_PATH=<replaceable>prefix</replaceable><constant>c</constant><replaceable>bus_id</replaceable></varname></term>
++          <term><varname>ID_NET_NAME_PATH=<replaceable>prefix</replaceable><constant>a</constant><replaceable>vendor</replaceable><replaceable>model</replaceable><constant>i</constant><replaceable>instance</replaceable></varname></term>
++          <term><varname>ID_NET_NAME_PATH=<replaceable>prefix</replaceable><constant>i</constant><replaceable>address</replaceable><constant>n</constant><replaceable>port_name</replaceable></varname></term>
++          <term><varname>ID_NET_NAME_PATH=<replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>p</constant><replaceable>bus</replaceable><constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>phys_port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]</varname></term>
++          <term><varname>ID_NET_NAME_PATH=<replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>p</constant><replaceable>bus</replaceable><constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>phys_port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]<constant>b</constant><replaceable>number</replaceable></varname></term>
++          <term><varname>ID_NET_NAME_PATH=<replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>p</constant><replaceable>bus</replaceable><constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>phys_port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]<constant>u</constant><replaceable>port</replaceable>…[<constant>c</constant><replaceable>config</replaceable>][<constant>i</constant><replaceable>interface</replaceable>]</varname></term>
++
++          <listitem><para>This property describes the device installation location. Different schemes are
++          used depending on the bus type, as described in the table below. For BCMA and USB devices, PCI path
++          information must known, and the full name consists of the prefix, PCI slot identifier, and USB or
++          BCMA location. The first two parts are denoted as "…" in the table below.</para>
++
++          <table>
++            <title>Path naming schemes</title>
++
++            <tgroup cols='2'>
++              <thead>
++                <row>
++                  <entry>Format</entry>
++                  <entry>Description</entry>
++                </row>
++              </thead>
++
++              <tbody>
++                <row>
++                  <entry><replaceable>prefix</replaceable> <constant>c</constant><replaceable>bus_id</replaceable></entry>
++                  <entry>CCW or grouped CCW device identifier</entry>
++                </row>
++
++                <row>
++                  <entry><replaceable>prefix</replaceable> <constant>a</constant><replaceable>vendor</replaceable> <replaceable>model</replaceable> <constant>i</constant><replaceable>instance</replaceable></entry>
++                  <entry>ACPI path names for ARM64 platform devices</entry>
++                </row>
++
++                <row>
++                  <entry><replaceable>prefix</replaceable> [<constant>P</constant><replaceable>domain</replaceable>] <constant>p</constant><replaceable>bus</replaceable> <constant>s</constant><replaceable>slot</replaceable> [<constant>f</constant><replaceable>function</replaceable>] [<constant>n</constant><replaceable>phys_port_name</replaceable> | <constant>d</constant><replaceable>dev_port</replaceable>]</entry>
++                  <entry>PCI geographical location</entry>
++                </row>
++
++                <row>
++                  <entry>… <constant>b</constant><replaceable>number</replaceable></entry>
++                  <entry>Broadcom bus (BCMA) core number</entry>
++                </row>
++
++                <row>
++                  <entry>… <constant>u</constant><replaceable>port</replaceable>… [<constant>c</constant><replaceable>config</replaceable>] [<constant>i</constant><replaceable>interface</replaceable>]</entry>
++                  <entry>USB port number chain</entry>
++                </row>
++
++              </tbody>
++            </tgroup>
++          </table>
++
++          <para>CCW and grouped CCW devices are found in IBM System Z mainframes. Any leading zeros and
++          dots are suppressed.</para>
++
++          <para>For PCI, BCMA, and USB devices, the same rules as described above for slot naming are
++          used.</para>
++          </listitem>
++        </varlistentry>
++    </variablelist>
++  </refsect1>
++
++  <refsect1>
++    <title>History</title>
++
++    <para>The following "naming schemes" have been defined:</para>
++
++    <variablelist>
++        <varlistentry>
++          <term><constant>rhel-8.0</constant></term>
++
++          <listitem><para>Naming was changed for virtual network interfaces created with SR-IOV and NPAR and
++          for devices where the PCI network controller device does not have a slot number associated.</para>
++
++          <para>SR-IOV virtual devices are named based on the name of the parent interface, with a suffix of
++          <literal>v<replaceable>port</replaceable></literal>, where <replaceable>port</replaceable> is the
++          virtual device number. Previously those virtual devices were named as if completely independent.
++          </para>
++
++          <para>The ninth and later NPAR virtual devices are named following the scheme used for the first
++          eight NPAR partitions. Previously those devices were not renamed and the kernel default
++          ("eth<replaceable>N</replaceable>") was used.</para>
++
++          <para>Names are also generated for PCI devices where the PCI network controller device does not
++          have an associated slot number itself, but one of its parents does. Previously those devices were
++          not renamed and the kernel default was used.</para>
++          </listitem>
++        </varlistentry>
++
++        <varlistentry>
++          <term><constant>rhel-8.1</constant></term>
++
++          <para>Same as naming scheme <constant>rhel-8.0</constant>.</para>
++        </varlistentry>
++
++        <varlistentry>
++          <term><constant>rhel-8.2</constant></term>
++
++          <para>Same as naming scheme <constant>rhel-8.0</constant>.</para>
++        </varlistentry>
++
++        <varlistentry>
++          <term><constant>rhel-8.3</constant></term>
++
++          <para>Same as naming scheme <constant>rhel-8.0</constant>.</para>
++        </varlistentry>
++
++        <para>Note that <constant>latest</constant> may be used to denote the latest scheme known (to this
++        particular version of systemd.</para>
++    </variablelist>
++  </refsect1>
++
++  <refsect1>
++    <title>Examples</title>
++
++    <example>
++      <title>Using <command>udevadm test-builtin</command> to display device properties</title>
++
++      <programlisting>$ udevadm test-builtin net_id /sys/class/net/enp0s31f6
++...
++Using default interface naming scheme 'rhel-8.3'.
++ID_NET_NAMING_SCHEME=rhel-8.3
++ID_NET_NAME_MAC=enx54ee75cb1dc0
++ID_OUI_FROM_DATABASE=Wistron InfoComm(Kunshan)Co.,Ltd.
++ID_NET_NAME_PATH=enp0s31f6
++...</programlisting>
++    </example>
++
++    <example>
++      <title>PCI Ethernet card with firmware index "1"</title>
++
++      <programlisting>ID_NET_NAME_ONBOARD=eno1
++ID_NET_NAME_ONBOARD_LABEL=enEthernet Port 1
++      </programlisting>
++      <!-- FIXME: nuke the prefix! -->
++    </example>
++
++    <example>
++      <title>PCI Ethernet card in hotplug slot with firmware index number</title>
++
++      <programlisting># /sys/devices/pci0000:00/0000:00:1c.3/0000:05:00.0/net/ens1
++ID_NET_NAME_MAC=enx000000000466
++ID_NET_NAME_PATH=enp5s0
++ID_NET_NAME_SLOT=ens1</programlisting>
++    </example>
++
++    <example>
++      <title>PCI Ethernet multi-function card with 2 ports</title>
++
++      <programlisting># /sys/devices/pci0000:00/0000:00:1c.0/0000:02:00.0/net/enp2s0f0
++ID_NET_NAME_MAC=enx78e7d1ea46da
++ID_NET_NAME_PATH=enp2s0f0
++
++# /sys/devices/pci0000:00/0000:00:1c.0/0000:02:00.1/net/enp2s0f1
++ID_NET_NAME_MAC=enx78e7d1ea46dc
++ID_NET_NAME_PATH=enp2s0f1</programlisting>
++    </example>
++
++    <example>
++      <title>PCI WLAN card</title>
++
++      <programlisting># /sys/devices/pci0000:00/0000:00:1c.1/0000:03:00.0/net/wlp3s0
++ID_NET_NAME_MAC=wlx0024d7e31130
++ID_NET_NAME_PATH=wlp3s0</programlisting>
++    </example>
++
++    <example>
++      <title>USB built-in 3G modem</title>
++
++      <programlisting># /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4/2-1.4:1.6/net/wwp0s29u1u4i6
++ID_NET_NAME_MAC=wwx028037ec0200
++ID_NET_NAME_PATH=wwp0s29u1u4i6</programlisting>
++    </example>
++
++    <example>
++      <title>USB Android phone</title>
++
++      <programlisting># /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/net/enp0s29u1u2
++ID_NET_NAME_MAC=enxd626b3450fb5
++ID_NET_NAME_PATH=enp0s29u1u2</programlisting>
++    </example>
++
++    <example>
++      <title>s390 grouped CCW interface</title>
++
++      <programlisting># /sys/devices/css0/0.0.0007/0.0.f5f0/group_device/net/encf5f0
++ID_NET_NAME_MAC=enx026d3c00000a
++ID_NET_NAME_PATH=encf5f0</programlisting>
++    </example>
++  </refsect1>
++
++  <refsect1>
++    <title>See Also</title>
++    <para>
++      <citerefentry><refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
++      <citerefentry><refentrytitle>udevadm</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
++      <ulink url="https://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames">the
++      original page describing stable interface names</ulink>
++    </para>
++  </refsect1>
++
++</refentry>
+diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c
+index d85dc2848b..aa553d5ade 100644
+--- a/src/udev/udev-builtin-net_id.c
++++ b/src/udev/udev-builtin-net_id.c
+@@ -78,6 +78,7 @@
+  *  /sys/devices/css0/0.0.0007/0.0.f5f0/group_device/net/encf5f0
+  *  ID_NET_NAME_MAC=enx026d3c00000a
+  *  ID_NET_NAME_PATH=encf5f0
++ * When the code here is changed, man/systemd.net-naming-scheme.xml must be updated too.
+  */
+ 
+ #include <errno.h>
diff --git a/SOURCES/0479-udev-net_id-parse-_SUN-ACPI-index-as-a-signed-intege.patch b/SOURCES/0479-udev-net_id-parse-_SUN-ACPI-index-as-a-signed-intege.patch
new file mode 100644
index 0000000..ea6dfa2
--- /dev/null
+++ b/SOURCES/0479-udev-net_id-parse-_SUN-ACPI-index-as-a-signed-intege.patch
@@ -0,0 +1,51 @@
+From 462420bc7ea22a05bfc2d021d395aade2b8ee7dc Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= <msekleta@redhat.com>
+Date: Mon, 19 Oct 2020 10:56:11 +0200
+Subject: [PATCH] udev/net_id: parse _SUN ACPI index as a signed integer
+
+Negative value means there is no match between a PCI device and any of
+the slots. In the following commit we will extend this and value of 0
+will indicate that there is a match between some slot and PCI device,
+but that device is a PCI bridge.
+
+(cherry picked from commit 3e545ae5abcf258791eacbee60c829c100a33274)
+
+Related: #1827462
+---
+ src/udev/udev-builtin-net_id.c | 11 ++++++-----
+ 1 file changed, 6 insertions(+), 5 deletions(-)
+
+diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c
+index aa553d5ade..ede24dee41 100644
+--- a/src/udev/udev-builtin-net_id.c
++++ b/src/udev/udev-builtin-net_id.c
+@@ -391,7 +391,8 @@ static bool is_pci_ari_enabled(struct udev_device *dev) {
+ 
+ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) {
+         struct udev *udev = udev_device_get_udev(names->pcidev);
+-        unsigned domain, bus, slot, func, dev_port = 0, hotplug_slot = 0;
++        unsigned domain, bus, slot, func, dev_port = 0;
++        int hotplug_slot = -1;
+         size_t l;
+         char *s;
+         const char *attr, *port_name;
+@@ -449,15 +450,15 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) {
+         hotplug_slot_dev = names->pcidev;
+         while (hotplug_slot_dev) {
+                 FOREACH_DIRENT_ALL(dent, dir, break) {
+-                        unsigned i;
+-                        int r;
++                        int i, r;
+                         char str[PATH_MAX];
+                         _cleanup_free_ char *address = NULL;
+ 
+                         if (dent->d_name[0] == '.')
+                                 continue;
+-                        r = safe_atou_full(dent->d_name, 10, &i);
+-                        if (i < 1 || r < 0)
++
++                        r = safe_atoi(dent->d_name, &i);
++                        if (r < 0 || i <= 0)
+                                 continue;
+ 
+                         if (snprintf_ok(str, sizeof str, "%s/%s/address", slots, dent->d_name) &&
diff --git a/SOURCES/0480-udev-net_id-don-t-generate-slot-based-names-if-multi.patch b/SOURCES/0480-udev-net_id-don-t-generate-slot-based-names-if-multi.patch
new file mode 100644
index 0000000..2bd2478
--- /dev/null
+++ b/SOURCES/0480-udev-net_id-don-t-generate-slot-based-names-if-multi.patch
@@ -0,0 +1,124 @@
+From bb6114af097da0cd9c5081e42db718559130687f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= <msekleta@redhat.com>
+Date: Mon, 19 Oct 2020 11:10:31 +0200
+Subject: [PATCH] udev/net_id: don't generate slot based names if multiple
+ devices might claim the same slot
+
+(cherry picked from commit 2c8ec0095e6fd2e72879d4915ff8a9e5c0664d0b)
+
+Resolves: #1827462
+---
+ man/systemd.net-naming-scheme.xml | 15 ++++++++++-
+ src/udev/udev-builtin-net_id.c    | 41 ++++++++++++++++++++++++++-----
+ 2 files changed, 49 insertions(+), 7 deletions(-)
+
+diff --git a/man/systemd.net-naming-scheme.xml b/man/systemd.net-naming-scheme.xml
+index a12cc3c460..10e71dcb15 100644
+--- a/man/systemd.net-naming-scheme.xml
++++ b/man/systemd.net-naming-scheme.xml
+@@ -176,7 +176,10 @@
+ 
+           <para>SR-IOV virtual devices are named based on the name of the parent interface, with a suffix of
+           <constant>v</constant> and the virtual device number, with any leading zeros removed. The bus
+-          number is ignored. This device type is found in IBM PowerVMs.</para>
++          number is ignored.</para>
++
++          <para>In some configurations a parent PCI bridge of a given network controller may be associated
++          with a slot. In such case we don't generate this device property to avoid possible naming conflicts.</para>
+         </varlistentry>
+ 
+         <varlistentry>
+@@ -288,6 +291,16 @@
+           <para>Same as naming scheme <constant>rhel-8.0</constant>.</para>
+         </varlistentry>
+ 
++        <varlistentry>
++          <term><constant>rhel-8.4</constant></term>
++
++          <listitem><para>If the PCI slot is assocated with PCI bridge and that has multiple child network
++          controllers then all of them might derive the same value of <varname>ID_NET_NAME_SLOT</varname>
++          property. That could cause naming conflict if the property is selected as a device name. Now, we detect the
++          situation, slot - bridge relation, and we don't produce the <varname>ID_NET_NAME_SLOT</varname> property to
++          avoid possible naming conflict.</para></listitem>
++        </varlistentry>
++
+         <para>Note that <constant>latest</constant> may be used to denote the latest scheme known (to this
+         particular version of systemd.</para>
+     </variablelist>
+diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c
+index ede24dee41..d8c56b62bb 100644
+--- a/src/udev/udev-builtin-net_id.c
++++ b/src/udev/udev-builtin-net_id.c
+@@ -124,6 +124,7 @@ typedef enum NamingSchemeFlags {
+         /* First, the individual features */
+         NAMING_SR_IOV_V        = 1 << 0, /* Use "v" suffix for SR-IOV, see 609948c7043a40008b8299529c978ed8e11de8f6*/
+         NAMING_NPAR_ARI        = 1 << 1, /* Use NPAR "ARI", see 6bc04997b6eab35d1cb9fa73889892702c27be09 */
++        NAMING_BRIDGE_NO_SLOT  = 1 << 9, /* Don't use PCI hotplug slot information if the corresponding device is a PCI bridge */
+ 
+         /* And now the masks that combine the features above */
+         NAMING_V238 = 0,
+@@ -132,6 +133,7 @@ typedef enum NamingSchemeFlags {
+         NAMING_RHEL_8_1 = NAMING_V239,
+         NAMING_RHEL_8_2 = NAMING_V239,
+         NAMING_RHEL_8_3 = NAMING_V239,
++        NAMING_RHEL_8_4 = NAMING_V239|NAMING_BRIDGE_NO_SLOT,
+ 
+         _NAMING_SCHEME_FLAGS_INVALID = -1,
+ } NamingSchemeFlags;
+@@ -389,6 +391,26 @@ static bool is_pci_ari_enabled(struct udev_device *dev) {
+         return streq_ptr(udev_device_get_sysattr_value(dev, "ari_enabled"), "1");
+ }
+ 
++static bool is_pci_bridge(struct udev_device *dev) {
++        const char *v, *p;
++
++        v = udev_device_get_sysattr_value(dev, "modalias");
++        if (!v)
++                return false;
++
++        if (!startswith(v, "pci:"))
++                return false;
++
++        p = strrchr(v, 's');
++        if (!p)
++                return false;
++        if (p[1] != 'c')
++                return false;
++
++        /* PCI device subclass 04 corresponds to PCI bridge */
++        return strneq(p + 2, "04", 2);
++}
++
+ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) {
+         struct udev *udev = udev_device_get_udev(names->pcidev);
+         unsigned domain, bus, slot, func, dev_port = 0;
+@@ -461,16 +483,23 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) {
+                         if (r < 0 || i <= 0)
+                                 continue;
+ 
++                        /* match slot address with device by stripping the function */
+                         if (snprintf_ok(str, sizeof str, "%s/%s/address", slots, dent->d_name) &&
+-                            read_one_line_file(str, &address) >= 0)
+-                                /* match slot address with device by stripping the function */
+-                                if (startswith(udev_device_get_sysname(hotplug_slot_dev), address))
+-                                        hotplug_slot = i;
++                            read_one_line_file(str, &address) >= 0 &&
++                            startswith(udev_device_get_sysname(hotplug_slot_dev), address)) {
++                                hotplug_slot = i;
++
++                                /* We found the match between PCI device and slot. However, we won't use the
++                                 * slot index if the device is a PCI bridge, because it can have other child
++                                 * devices that will try to claim the same index and that would create name
++                                 * collision. */
++                                if (naming_scheme_has(NAMING_BRIDGE_NO_SLOT) && is_pci_bridge(hotplug_slot_dev))
++                                        hotplug_slot = 0;
+ 
+-                        if (hotplug_slot > 0)
+                                 break;
++                        }
+                 }
+-                if (hotplug_slot > 0)
++                if (hotplug_slot >= 0)
+                         break;
+                 rewinddir(dir);
+                 hotplug_slot_dev = udev_device_get_parent_with_subsystem_devtype(hotplug_slot_dev, "pci", NULL);
diff --git a/SOURCES/0481-fix-typo-in-ProtectSystem-option.patch b/SOURCES/0481-fix-typo-in-ProtectSystem-option.patch
new file mode 100644
index 0000000..06f5f4c
--- /dev/null
+++ b/SOURCES/0481-fix-typo-in-ProtectSystem-option.patch
@@ -0,0 +1,25 @@
+From 573229efeb2c5ade25794deee8cfe2f967414ef7 Mon Sep 17 00:00:00 2001
+From: David Tardon <dtardon@redhat.com>
+Date: Fri, 6 Nov 2020 10:13:19 +0100
+Subject: [PATCH] fix typo in ProtectSystem= option
+
+This was introduced by commit d9ae3222cfbd5d2a48e6dbade6617085cc76f1c1 .
+
+Resolves: #1871139
+---
+ units/systemd-resolved.service.in | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/units/systemd-resolved.service.in b/units/systemd-resolved.service.in
+index aad1a53a5f..f10f1d1690 100644
+--- a/units/systemd-resolved.service.in
++++ b/units/systemd-resolved.service.in
+@@ -30,7 +30,7 @@ CapabilityBoundingSet=CAP_SETPCAP CAP_NET_RAW CAP_NET_BIND_SERVICE
+ AmbientCapabilities=CAP_SETPCAP CAP_NET_RAW CAP_NET_BIND_SERVICE
+ PrivateTmp=yes
+ PrivateDevices=yes
+-ProtectSystems=strict
++ProtectSystem=strict
+ ProtectHome=yes
+ ProtectControlGroups=yes
+ ProtectKernelTunables=yes
diff --git a/SOURCES/0482-remove-references-of-non-existent-man-pages.patch b/SOURCES/0482-remove-references-of-non-existent-man-pages.patch
new file mode 100644
index 0000000..31ae5e0
--- /dev/null
+++ b/SOURCES/0482-remove-references-of-non-existent-man-pages.patch
@@ -0,0 +1,34 @@
+From 5e74048399c4610da27b5f7fbbb53784030aeb70 Mon Sep 17 00:00:00 2001
+From: David Tardon <dtardon@redhat.com>
+Date: Mon, 9 Nov 2020 09:27:02 +0100
+Subject: [PATCH] remove references of non-existent man pages
+
+This is a follow-up to commit 8ad89170001c9aba8849630ddb5da81d9e24a1bc,
+which introduced the man page change.
+
+Resolves: #1876807
+---
+ man/systemd.special.xml | 10 ----------
+ 1 file changed, 10 deletions(-)
+
+diff --git a/man/systemd.special.xml b/man/systemd.special.xml
+index c9d4345016..fe6324a4a0 100644
+--- a/man/systemd.special.xml
++++ b/man/systemd.special.xml
+@@ -657,16 +657,6 @@
+             target unit and pull in the target from it, also with <varname>Requires=</varname>. Note that by default this
+             target unit is not part of the initial boot transaction, but is supposed to be pulled in only if required by
+             units that want to run only on successful boots.</para>
+-
+-            <para>See
+-            <citerefentry><refentrytitle>systemd-boot-check-no-failures.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+-            for a service that implements a generic system health check and orders itself before
+-            <filename>boot-complete.target</filename>.</para>
+-
+-            <para>See
+-            <citerefentry><refentrytitle>systemd-bless-boot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+-            for a service that propagates boot success information to the boot loader, and orders itself after
+-            <filename>boot-complete.target</filename>.</para>
+           </listitem>
+         </varlistentry>
+       <varlistentry>
diff --git a/SOURCES/0483-log-Prefer-logging-to-CLI-unless-JOURNAL_STREAM-is-s.patch b/SOURCES/0483-log-Prefer-logging-to-CLI-unless-JOURNAL_STREAM-is-s.patch
new file mode 100644
index 0000000..72141ba
--- /dev/null
+++ b/SOURCES/0483-log-Prefer-logging-to-CLI-unless-JOURNAL_STREAM-is-s.patch
@@ -0,0 +1,91 @@
+From b14c82dd9f9fcc42810614cf02efe8651897d36f Mon Sep 17 00:00:00 2001
+From: Daan De Meyer <daan.j.demeyer@gmail.com>
+Date: Wed, 10 Jun 2020 20:19:41 +0200
+Subject: [PATCH] log: Prefer logging to CLI unless JOURNAL_STREAM is set
+
+(cherry picked from commit bc694c06e60505efeb09e5278a7b22cdfa23975e)
+
+Resolves: #1865840
+---
+ src/basic/log.c               | 32 +++++++++++++++++++++++++++++---
+ test/TEST-21-SYSUSERS/test.sh |  3 +--
+ 2 files changed, 30 insertions(+), 5 deletions(-)
+
+diff --git a/src/basic/log.c b/src/basic/log.c
+index 48c094b548..9387e56a57 100644
+--- a/src/basic/log.c
++++ b/src/basic/log.c
+@@ -10,6 +10,7 @@
+ #include <string.h>
+ #include <sys/signalfd.h>
+ #include <sys/socket.h>
++#include <sys/stat.h>
+ #include <sys/time.h>
+ #include <sys/uio.h>
+ #include <sys/un.h>
+@@ -19,6 +20,7 @@
+ #include "sd-messages.h"
+ 
+ #include "alloc-util.h"
++#include "extract-word.h"
+ #include "fd-util.h"
+ #include "format-util.h"
+ #include "io-util.h"
+@@ -220,6 +222,32 @@ fail:
+         return r;
+ }
+ 
++static bool stderr_is_journal(void) {
++        _cleanup_free_ char *w = NULL;
++        const char *e;
++        uint64_t dev, ino;
++        struct stat st;
++
++        e = getenv("JOURNAL_STREAM");
++        if (!e)
++                return false;
++
++        if (extract_first_word(&e, &w, ":", EXTRACT_DONT_COALESCE_SEPARATORS) <= 0)
++                return false;
++        if (!e)
++                return false;
++
++        if (safe_atou64(w, &dev) < 0)
++                return false;
++        if (safe_atou64(e, &ino) < 0)
++                return false;
++
++        if (fstat(STDERR_FILENO, &st) < 0)
++                return false;
++
++        return st.st_dev == dev && st.st_ino == ino;
++}
++
+ int log_open(void) {
+         int r;
+ 
+@@ -239,9 +267,7 @@ int log_open(void) {
+                 return 0;
+         }
+ 
+-        if (log_target != LOG_TARGET_AUTO ||
+-            getpid_cached() == 1 ||
+-            isatty(STDERR_FILENO) <= 0) {
++        if (log_target != LOG_TARGET_AUTO || getpid_cached() == 1 || stderr_is_journal()) {
+ 
+                 if (!prohibit_ipc &&
+                     IN_SET(log_target, LOG_TARGET_AUTO,
+diff --git a/test/TEST-21-SYSUSERS/test.sh b/test/TEST-21-SYSUSERS/test.sh
+index b1049e720d..3460d71f22 100755
+--- a/test/TEST-21-SYSUSERS/test.sh
++++ b/test/TEST-21-SYSUSERS/test.sh
+@@ -108,8 +108,7 @@ test_run() {
+                 echo "*** Running test $f"
+                 prepare_testdir ${f%.input}
+                 cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
+-                systemd-sysusers --root=$TESTDIR 2> /dev/null
+-                journalctl -t systemd-sysusers -o cat | tail -n1 > $TESTDIR/tmp/err
++                systemd-sysusers --root=$TESTDIR 2>&1 | tail -n1 > $TESTDIR/tmp/err
+                 if ! diff -u $TESTDIR/tmp/err  ${f%.*}.expected-err; then
+                         echo "**** Unexpected error output for $f"
+                         cat $TESTDIR/tmp/err
diff --git a/SOURCES/0484-locale-util-add-new-helper-locale_is_installed.patch b/SOURCES/0484-locale-util-add-new-helper-locale_is_installed.patch
new file mode 100644
index 0000000..902a685
--- /dev/null
+++ b/SOURCES/0484-locale-util-add-new-helper-locale_is_installed.patch
@@ -0,0 +1,58 @@
+From f0d9e0cb24958bc11c8d83f0a3de651def2aa1d6 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Thu, 30 Apr 2020 18:30:56 +0200
+Subject: [PATCH] locale-util: add new helper locale_is_installed()
+
+This new helper checks whether the specified locale is installed. It's
+distinct from locale_is_valid() which just superficially checks if a
+string looks like something that could be a valid locale.
+
+Heavily inspired by @jsynacek's #13964.
+
+Replaces: #13964
+(cherry picked from commit 23fa786ca67ed3a32930ff1a7b175ac823db187c)
+
+Related: #1755287
+---
+ src/basic/locale-util.c | 15 +++++++++++++++
+ src/basic/locale-util.h |  1 +
+ 2 files changed, 16 insertions(+)
+
+diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c
+index 7cd143ea6f..42ef309ebd 100644
+--- a/src/basic/locale-util.c
++++ b/src/basic/locale-util.c
+@@ -204,6 +204,21 @@ bool locale_is_valid(const char *name) {
+         return true;
+ }
+ 
++int locale_is_installed(const char *name) {
++        if (!locale_is_valid(name))
++                return false;
++
++        if (STR_IN_SET(name, "C", "POSIX")) /* These ones are always OK */
++                return true;
++
++        _cleanup_(freelocalep) locale_t loc =
++                newlocale(LC_ALL_MASK, name, 0);
++        if (loc == (locale_t) 0)
++                return errno == ENOMEM ? -ENOMEM : false;
++
++        return true;
++}
++
+ void init_gettext(void) {
+         setlocale(LC_ALL, "");
+         textdomain(GETTEXT_PACKAGE);
+diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h
+index 368675f286..b40f9c641a 100644
+--- a/src/basic/locale-util.h
++++ b/src/basic/locale-util.h
+@@ -31,6 +31,7 @@ typedef enum LocaleVariable {
+ 
+ int get_locales(char ***l);
+ bool locale_is_valid(const char *name);
++int locale_is_installed(const char *name);
+ 
+ #define _(String) gettext(String)
+ #define N_(String) String
diff --git a/SOURCES/0485-test-add-test-case-for-locale_is_installed.patch b/SOURCES/0485-test-add-test-case-for-locale_is_installed.patch
new file mode 100644
index 0000000..57abbe8
--- /dev/null
+++ b/SOURCES/0485-test-add-test-case-for-locale_is_installed.patch
@@ -0,0 +1,53 @@
+From 3d08c7971a80370f60dd14b068779851e0f82c24 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Thu, 30 Apr 2020 18:32:55 +0200
+Subject: [PATCH] test: add test case for locale_is_installed()
+
+(cherry picked from commit b45b0a69bb7ef3e6e66d443eae366b6d1c387cab)
+
+Related: #1755287
+---
+ src/test/test-locale-util.c | 23 +++++++++++++++++++++++
+ 1 file changed, 23 insertions(+)
+
+diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c
+index 0c3f6a62ed..0d50c33ce5 100644
+--- a/src/test/test-locale-util.c
++++ b/src/test/test-locale-util.c
+@@ -34,6 +34,28 @@ static void test_locale_is_valid(void) {
+         assert_se(!locale_is_valid("\x01gar\x02 bage\x03"));
+ }
+ 
++static void test_locale_is_installed(void) {
++        log_info("/* %s */", __func__);
++
++        /* Always available */
++        assert_se(locale_is_installed("POSIX") > 0);
++        assert_se(locale_is_installed("C") > 0);
++
++        /* Might, or might not be installed. */
++        assert_se(locale_is_installed("en_EN.utf8") >= 0);
++        assert_se(locale_is_installed("fr_FR.utf8") >= 0);
++        assert_se(locale_is_installed("fr_FR@euro") >= 0);
++        assert_se(locale_is_installed("fi_FI") >= 0);
++
++        /* Definitely not valid */
++        assert_se(locale_is_installed("") == 0);
++        assert_se(locale_is_installed("/usr/bin/foo") == 0);
++        assert_se(locale_is_installed("\x01gar\x02 bage\x03") == 0);
++
++        /* Definitely not installed */
++        assert_se(locale_is_installed("zz_ZZ") == 0);
++}
++
+ static void test_keymaps(void) {
+         _cleanup_strv_free_ char **kmaps = NULL;
+         char **p;
+@@ -95,6 +117,7 @@ static void dump_special_glyphs(void) {
+ int main(int argc, char *argv[]) {
+         test_get_locales();
+         test_locale_is_valid();
++        test_locale_is_installed();
+         test_keymaps();
+ 
+         dump_special_glyphs();
diff --git a/SOURCES/0486-tree-wide-port-various-bits-over-to-locale_is_instal.patch b/SOURCES/0486-tree-wide-port-various-bits-over-to-locale_is_instal.patch
new file mode 100644
index 0000000..cb4ec8d
--- /dev/null
+++ b/SOURCES/0486-tree-wide-port-various-bits-over-to-locale_is_instal.patch
@@ -0,0 +1,232 @@
+From 5813180a75aa1ef90f6d3459fc5beb099b815cfb Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Thu, 30 Apr 2020 18:32:44 +0200
+Subject: [PATCH] tree-wide: port various bits over to locale_is_installed()
+
+(cherry picked from commit a00a78b84e2ab352b3144bfae8bc578d172303be)
+
+Resolves: #1755287
+---
+ src/firstboot/firstboot.c | 30 ++++++++------
+ src/locale/localed.c      | 87 ++++++++++++++++++++++++---------------
+ 2 files changed, 71 insertions(+), 46 deletions(-)
+
+diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c
+index a98e53b3a3..7e177a50fa 100644
+--- a/src/firstboot/firstboot.c
++++ b/src/firstboot/firstboot.c
+@@ -192,6 +192,14 @@ static int prompt_loop(const char *text, char **l, bool (*is_valid)(const char *
+         }
+ }
+ 
++static bool locale_is_ok(const char *name) {
++
++        if (arg_root)
++                return locale_is_valid(name);
++
++        return locale_is_installed(name) > 0;
++}
++
+ static int prompt_locale(void) {
+         _cleanup_strv_free_ char **locales = NULL;
+         int r;
+@@ -215,14 +223,14 @@ static int prompt_locale(void) {
+ 
+         putchar('\n');
+ 
+-        r = prompt_loop("Please enter system locale name or number", locales, locale_is_valid, &arg_locale);
++        r = prompt_loop("Please enter system locale name or number", locales, locale_is_ok, &arg_locale);
+         if (r < 0)
+                 return r;
+ 
+         if (isempty(arg_locale))
+                 return 0;
+ 
+-        r = prompt_loop("Please enter system message locale name or number", locales, locale_is_valid, &arg_locale_messages);
++        r = prompt_loop("Please enter system message locale name or number", locales, locale_is_ok, &arg_locale_messages);
+         if (r < 0)
+                 return r;
+ 
+@@ -780,11 +788,6 @@ static int parse_argv(int argc, char *argv[]) {
+                         break;
+ 
+                 case ARG_LOCALE:
+-                        if (!locale_is_valid(optarg)) {
+-                                log_error("Locale %s is not valid.", optarg);
+-                                return -EINVAL;
+-                        }
+-
+                         r = free_and_strdup(&arg_locale, optarg);
+                         if (r < 0)
+                                 return log_oom();
+@@ -792,11 +795,6 @@ static int parse_argv(int argc, char *argv[]) {
+                         break;
+ 
+                 case ARG_LOCALE_MESSAGES:
+-                        if (!locale_is_valid(optarg)) {
+-                                log_error("Locale %s is not valid.", optarg);
+-                                return -EINVAL;
+-                        }
+-
+                         r = free_and_strdup(&arg_locale_messages, optarg);
+                         if (r < 0)
+                                 return log_oom();
+@@ -922,6 +920,14 @@ static int parse_argv(int argc, char *argv[]) {
+                         assert_not_reached("Unhandled option");
+                 }
+ 
++        /* We check if the specified locale strings are valid down here, so that we can take --root= into
++         * account when looking for the locale files. */
++
++        if (arg_locale && !locale_is_ok(arg_locale))
++                return log_error_errno(EINVAL, "Locale %s is not installed.", arg_locale);
++        if (arg_locale_messages && !locale_is_ok(arg_locale_messages))
++                return log_error_errno(EINVAL, "Locale %s is not installed.", arg_locale_messages);
++
+         return 1;
+ }
+ 
+diff --git a/src/locale/localed.c b/src/locale/localed.c
+index 253973fd49..d6ed40babe 100644
+--- a/src/locale/localed.c
++++ b/src/locale/localed.c
+@@ -259,18 +259,57 @@ static void locale_free(char ***l) {
+                 (*l)[p] = mfree((*l)[p]);
+ }
+ 
++static int process_locale_list_item(
++                const char *assignment,
++                char *new_locale[static _VARIABLE_LC_MAX],
++                sd_bus_error *error) {
++
++        assert(assignment);
++        assert(new_locale);
++
++        for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) {
++                const char *name, *e;
++
++                assert_se(name = locale_variable_to_string(p));
++
++                e = startswith(assignment, name);
++                if (!e)
++                        continue;
++
++                if (*e != '=')
++                        continue;
++
++                e++;
++
++                if (!locale_is_valid(e))
++                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale %s is not valid, refusing.", e);
++                if (locale_is_installed(e) <= 0)
++                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale %s not installed, refusing.", e);
++                if (new_locale[p])
++                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale variable %s set twice, refusing.", name);
++
++                new_locale[p] = strdup(e);
++                if (!new_locale[p])
++                        return -ENOMEM;
++
++                return 0;
++        }
++
++        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale assignment %s not valid, refusing.", assignment);
++}
++
+ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+         Context *c = userdata;
+         _cleanup_strv_free_ char **settings = NULL, **l = NULL;
+         char *new_locale[_VARIABLE_LC_MAX] = {}, **i;
+         _cleanup_(locale_free) _unused_ char **dummy = new_locale;
+         bool modified = false;
+-        int interactive, p, r;
++        int interactive, r;
+ 
+         assert(m);
+         assert(c);
+ 
+-        r = bus_message_read_strv_extend(m, &l);
++        r = sd_bus_message_read_strv(m, &l);
+         if (r < 0)
+                 return r;
+ 
+@@ -279,11 +318,13 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
+                 return r;
+ 
+         /* If single locale without variable name is provided, then we assume it is LANG=. */
+-        if (strv_length(l) == 1 && !strchr(*l, '=')) {
+-                if (!locale_is_valid(*l))
+-                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
++        if (strv_length(l) == 1 && !strchr(l[0], '=')) {
++                if (!locale_is_valid(l[0]))
++                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid locale specification: %s", l[0]);
++                if (locale_is_installed(l[0]) <= 0)
++                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified locale is not installed: %s", l[0]);
+ 
+-                new_locale[VARIABLE_LANG] = strdup(*l);
++                new_locale[VARIABLE_LANG] = strdup(l[0]);
+                 if (!new_locale[VARIABLE_LANG])
+                         return -ENOMEM;
+ 
+@@ -292,31 +333,9 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
+ 
+         /* Check whether a variable is valid */
+         STRV_FOREACH(i, l) {
+-                bool valid = false;
+-
+-                for (p = 0; p < _VARIABLE_LC_MAX; p++) {
+-                        size_t k;
+-                        const char *name;
+-
+-                        name = locale_variable_to_string(p);
+-                        assert(name);
+-
+-                        k = strlen(name);
+-                        if (startswith(*i, name) &&
+-                            (*i)[k] == '=' &&
+-                            locale_is_valid((*i) + k + 1)) {
+-                                valid = true;
+-
+-                                new_locale[p] = strdup((*i) + k + 1);
+-                                if (!new_locale[p])
+-                                        return -ENOMEM;
+-
+-                                break;
+-                        }
+-                }
+-
+-                if (!valid)
+-                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
++                r = process_locale_list_item(*i, new_locale, error);
++                if (r < 0)
++                        return r;
+         }
+ 
+         /* If LANG was specified, but not LANGUAGE, check if we should
+@@ -339,7 +358,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
+         }
+ 
+         /* Merge with the current settings */
+-        for (p = 0; p < _VARIABLE_LC_MAX; p++)
++        for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++)
+                 if (!isempty(c->locale[p]) && isempty(new_locale[p])) {
+                         new_locale[p] = strdup(c->locale[p]);
+                         if (!new_locale[p])
+@@ -348,7 +367,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
+ 
+         locale_simplify(new_locale);
+ 
+-        for (p = 0; p < _VARIABLE_LC_MAX; p++)
++        for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++)
+                 if (!streq_ptr(c->locale[p], new_locale[p])) {
+                         modified = true;
+                         break;
+@@ -373,7 +392,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
+         if (r == 0)
+                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+ 
+-        for (p = 0; p < _VARIABLE_LC_MAX; p++)
++        for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++)
+                 free_and_replace(c->locale[p], new_locale[p]);
+ 
+         r = locale_write_data(c, &settings);
diff --git a/SOURCES/0487-install-allow-instantiated-units-to-be-enabled-via-p.patch b/SOURCES/0487-install-allow-instantiated-units-to-be-enabled-via-p.patch
new file mode 100644
index 0000000..363f424
--- /dev/null
+++ b/SOURCES/0487-install-allow-instantiated-units-to-be-enabled-via-p.patch
@@ -0,0 +1,339 @@
+From 4c41ad9418058aefb2d2732b0b65da9c7cdf5151 Mon Sep 17 00:00:00 2001
+From: Ruixin Bao <rubao@redhat.com>
+Date: Tue, 21 Aug 2018 20:40:56 +0000
+Subject: [PATCH] install: allow instantiated units to be enabled via presets
+
+This patch implements https://github.com/systemd/systemd/issues/9421.
+
+The .preset file now is able to take a rule in the format of:(e.g)
+enable foo@.service bar0 bar1 bar2
+
+In the above example, when preset-all is called, all three instances of
+foo@bar0.service, foo@bar1.service and foo@bar2.service will be enabled.
+
+When preset is called on a single service(e.g: foo@bar1.service), only
+the mentioned one(foo@bar1.service) will be enabled.
+
+Tests are added for future regression.
+
+(cherry picked from commit 4c9565eea534cd233a913c8c21f7920dba229743)
+
+Resolves: #1812972
+---
+ src/shared/install.c         | 155 ++++++++++++++++++++++++++++++-----
+ src/test/test-install-root.c |  57 +++++++++++++
+ 2 files changed, 193 insertions(+), 19 deletions(-)
+
+diff --git a/src/shared/install.c b/src/shared/install.c
+index 77ae812878..1d4beaa83b 100644
+--- a/src/shared/install.c
++++ b/src/shared/install.c
+@@ -60,6 +60,7 @@ typedef enum {
+ typedef struct {
+         char *pattern;
+         PresetAction action;
++        char **instances;
+ } PresetRule;
+ 
+ typedef struct {
+@@ -87,8 +88,10 @@ static inline void presets_freep(Presets *p) {
+         if (!p)
+                 return;
+ 
+-        for (i = 0; i < p->n_rules; i++)
++        for (i = 0; i < p->n_rules; i++) {
+                 free(p->rules[i].pattern);
++                strv_free(p->rules[i].instances);
++        }
+ 
+         free(p->rules);
+         p->n_rules = 0;
+@@ -2755,6 +2758,39 @@ int unit_file_exists(UnitFileScope scope, const LookupPaths *paths, const char *
+         return 1;
+ }
+ 
++static int split_pattern_into_name_and_instances(const char *pattern, char **out_unit_name, char ***out_instances) {
++        _cleanup_strv_free_ char **instances = NULL;
++        _cleanup_free_ char *unit_name = NULL;
++        int r;
++
++        assert(pattern);
++        assert(out_instances);
++        assert(out_unit_name);
++
++        r = extract_first_word(&pattern, &unit_name, NULL, 0);
++        if (r < 0)
++                return r;
++
++        /* We handle the instances logic when unit name is extracted */
++        if (pattern) {
++                /* We only create instances when a rule of templated unit
++                 * is seen. A rule like enable foo@.service a b c will
++                 * result in an array of (a, b, c) as instance names */
++                if (!unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE))
++                        return -EINVAL;
++
++                instances = strv_split(pattern, WHITESPACE);
++                if (!instances)
++                        return -ENOMEM;
++
++                *out_instances = TAKE_PTR(instances);
++        }
++
++        *out_unit_name = TAKE_PTR(unit_name);
++
++        return 0;
++}
++
+ static int read_presets(UnitFileScope scope, const char *root_dir, Presets *presets) {
+         _cleanup_(presets_freep) Presets ps = {};
+         size_t n_allocated = 0;
+@@ -2824,15 +2860,20 @@ static int read_presets(UnitFileScope scope, const char *root_dir, Presets *pres
+ 
+                         parameter = first_word(l, "enable");
+                         if (parameter) {
+-                                char *pattern;
++                                char *unit_name;
++                                char **instances = NULL;
+ 
+-                                pattern = strdup(parameter);
+-                                if (!pattern)
+-                                        return -ENOMEM;
++                                /* Unit_name will remain the same as parameter when no instances are specified */
++                                r = split_pattern_into_name_and_instances(parameter, &unit_name, &instances);
++                                if (r < 0) {
++                                        log_syntax(NULL, LOG_WARNING, *p, n, 0, "Couldn't parse line '%s'. Ignoring.", line);
++                                        continue;
++                                }
+ 
+                                 rule = (PresetRule) {
+-                                        .pattern = pattern,
++                                        .pattern = unit_name,
+                                         .action = PRESET_ENABLE,
++                                        .instances = instances,
+                                 };
+                         }
+ 
+@@ -2868,15 +2909,71 @@ static int read_presets(UnitFileScope scope, const char *root_dir, Presets *pres
+         return 0;
+ }
+ 
+-static int query_presets(const char *name, const Presets presets) {
++static int pattern_match_multiple_instances(
++                        const PresetRule rule,
++                        const char *unit_name,
++                        char ***ret) {
++
++        _cleanup_free_ char *templated_name = NULL;
++        int r;
++
++        /* If no ret is needed or the rule itself does not have instances
++         * initalized, we return not matching */
++        if (!ret || !rule.instances)
++                return 0;
++
++        r = unit_name_template(unit_name, &templated_name);
++        if (r < 0)
++                return r;
++        if (!streq(rule.pattern, templated_name))
++                return 0;
++
++        /* Compose a list of specified instances when unit name is a template  */
++        if (unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) {
++                _cleanup_free_ char *prefix = NULL;
++                _cleanup_strv_free_ char **out_strv = NULL;
++                char **iter;
++
++                r = unit_name_to_prefix(unit_name, &prefix);
++                if (r < 0)
++                        return r;
++
++                STRV_FOREACH(iter, rule.instances) {
++                        _cleanup_free_ char *name = NULL;
++                        r = unit_name_build(prefix, *iter, ".service", &name);
++                        if (r < 0)
++                                return r;
++                        r = strv_extend(&out_strv, name);
++                        if (r < 0)
++                                return r;
++                }
++
++                *ret = TAKE_PTR(out_strv);
++                return 1;
++        } else {
++                /* We now know the input unit name is an instance name */
++                _cleanup_free_ char *instance_name = NULL;
++
++                r = unit_name_to_instance(unit_name, &instance_name);
++                if (r < 0)
++                        return r;
++
++                if (strv_find(rule.instances, instance_name))
++                        return 1;
++        }
++        return 0;
++}
++
++static int query_presets(const char *name, const Presets presets, char ***instance_name_list) {
+         PresetAction action = PRESET_UNKNOWN;
+         size_t i;
+-
++        char **s;
+         if (!unit_name_is_valid(name, UNIT_NAME_ANY))
+                 return -EINVAL;
+ 
+         for (i = 0; i < presets.n_rules; i++)
+-                if (fnmatch(presets.rules[i].pattern, name, FNM_NOESCAPE) == 0) {
++                if (pattern_match_multiple_instances(presets.rules[i], name, instance_name_list) > 0 ||
++                    fnmatch(presets.rules[i].pattern, name, FNM_NOESCAPE) == 0) {
+                         action = presets.rules[i].action;
+                         break;
+                 }
+@@ -2886,7 +2983,11 @@ static int query_presets(const char *name, const Presets presets) {
+                 log_debug("Preset files don't specify rule for %s. Enabling.", name);
+                 return 1;
+         case PRESET_ENABLE:
+-                log_debug("Preset files say enable %s.", name);
++                if (instance_name_list && *instance_name_list)
++                        STRV_FOREACH(s, *instance_name_list)
++                                log_debug("Preset files say enable %s.", *s);
++                else
++                        log_debug("Preset files say enable %s.", name);
+                 return 1;
+         case PRESET_DISABLE:
+                 log_debug("Preset files say disable %s.", name);
+@@ -2904,7 +3005,7 @@ int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char
+         if (r < 0)
+                 return r;
+ 
+-        return query_presets(name, presets);
++        return query_presets(name, presets, NULL);
+ }
+ 
+ static int execute_preset(
+@@ -2964,6 +3065,7 @@ static int preset_prepare_one(
+                 size_t *n_changes) {
+ 
+         _cleanup_(install_context_done) InstallContext tmp = {};
++        _cleanup_strv_free_ char **instance_name_list = NULL;
+         UnitFileInstallInfo *i;
+         int r;
+ 
+@@ -2979,19 +3081,34 @@ static int preset_prepare_one(
+                 return 0;
+         }
+ 
+-        r = query_presets(name, presets);
++        r = query_presets(name, presets, &instance_name_list);
+         if (r < 0)
+                 return r;
+ 
+         if (r > 0) {
+-                r = install_info_discover(scope, plus, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
+-                                          &i, changes, n_changes);
+-                if (r < 0)
+-                        return r;
++                if (instance_name_list) {
++                        char **s;
++                        STRV_FOREACH(s, instance_name_list) {
++                                r = install_info_discover(scope, plus, paths, *s, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
++                                                          &i, changes, n_changes);
++                                if (r < 0)
++                                        return r;
++
++                                r = install_info_may_process(i, paths, changes, n_changes);
++                                if (r < 0)
++                                        return r;
++                        }
++                } else {
++                        r = install_info_discover(scope, plus, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
++                                                  &i, changes, n_changes);
++                        if (r < 0)
++                                return r;
++
++                        r = install_info_may_process(i, paths, changes, n_changes);
++                        if (r < 0)
++                                return r;
++                }
+ 
+-                r = install_info_may_process(i, paths, changes, n_changes);
+-                if (r < 0)
+-                        return r;
+         } else
+                 r = install_info_discover(scope, minus, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS,
+                                           &i, changes, n_changes);
+diff --git a/src/test/test-install-root.c b/src/test/test-install-root.c
+index 15dd3c6966..dbbcfe4297 100644
+--- a/src/test/test-install-root.c
++++ b/src/test/test-install-root.c
+@@ -983,6 +983,62 @@ static void test_with_dropin_template(const char *root) {
+         assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "with-dropin-3@instance-2.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
+ }
+ 
++static void test_preset_multiple_instances(const char *root) {
++        UnitFileChange *changes = NULL;
++        size_t n_changes = 0;
++        const char *p;
++        UnitFileState state;
++
++        /* Set up template service files and preset file */
++        p = strjoina(root, "/usr/lib/systemd/system/foo@.service");
++        assert_se(write_string_file(p,
++                                    "[Install]\n"
++                                    "DefaultInstance=def\n"
++                                    "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0);
++
++        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
++
++        p = strjoina(root, "/usr/lib/systemd/system-preset/test.preset");
++        assert_se(write_string_file(p,
++                                    "enable foo@.service bar0 bar1 bartest\n"
++                                    "enable emptylist@.service\n" /* This line ensures the old functionality for templated unit still works */
++                                    "disable *\n" , WRITE_STRING_FILE_CREATE) >= 0);
++
++        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bar0.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
++
++        /* Preset a single instantiated unit specified in the list */
++        assert_se(unit_file_preset(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("foo@bar0.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
++        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bar0.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
++        assert_se(n_changes == 1);
++        assert_se(changes[0].type == UNIT_FILE_SYMLINK);
++        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/foo@bar0.service");
++        assert_se(streq(changes[0].path, p));
++        unit_file_changes_free(changes, n_changes);
++        changes = NULL; n_changes = 0;
++
++        assert_se(unit_file_disable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("foo@bar0.service"), &changes, &n_changes) >= 0);
++        assert_se(n_changes == 1);
++        assert_se(changes[0].type == UNIT_FILE_UNLINK);
++        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/foo@bar0.service");
++        assert_se(streq(changes[0].path, p));
++        unit_file_changes_free(changes, n_changes);
++        changes = NULL; n_changes = 0;
++
++        /* Check for preset-all case, only instances on the list should be enabled, not including the default instance */
++        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
++        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bar1.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
++        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bartest.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
++
++        assert_se(unit_file_preset_all(UNIT_FILE_SYSTEM, 0, root, UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
++        assert_se(n_changes > 0);
++
++        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
++        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bar0.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
++        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bar1.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
++        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bartest.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
++
++}
++
+ int main(int argc, char *argv[]) {
+         char root[] = "/tmp/rootXXXXXX";
+         const char *p;
+@@ -1012,6 +1068,7 @@ int main(int argc, char *argv[]) {
+         test_indirect(root);
+         test_preset_and_list(root);
+         test_preset_order(root);
++        test_preset_multiple_instances(root);
+         test_revert(root);
+         test_static_instance(root);
+         test_with_dropin(root);
diff --git a/SOURCES/0488-install-small-refactor-to-combine-two-function-calls.patch b/SOURCES/0488-install-small-refactor-to-combine-two-function-calls.patch
new file mode 100644
index 0000000..68e7ae2
--- /dev/null
+++ b/SOURCES/0488-install-small-refactor-to-combine-two-function-calls.patch
@@ -0,0 +1,127 @@
+From eacb511fc0d1e3c5857cb041ad162fb78b4381cc Mon Sep 17 00:00:00 2001
+From: Ruixin Bao <rubao@redhat.com>
+Date: Sun, 26 Aug 2018 20:00:03 +0000
+Subject: [PATCH] install: small refactor to combine two function calls into
+ one function
+
+Combine consecutive function calls of install_info_discover and
+install_info_may_process into one short helper function.
+
+(cherry picked from commit 1e475a0ab4c46eb07f3df3fb24f5a7c3e1fa20b1)
+
+Related: #1812972
+---
+ src/shared/install.c | 61 ++++++++++++++++++++++----------------------
+ 1 file changed, 30 insertions(+), 31 deletions(-)
+
+diff --git a/src/shared/install.c b/src/shared/install.c
+index 1d4beaa83b..263b239f10 100644
+--- a/src/shared/install.c
++++ b/src/shared/install.c
+@@ -1676,6 +1676,25 @@ static int install_info_discover(
+         return r;
+ }
+ 
++static int install_info_discover_and_check(
++                        UnitFileScope scope,
++                        InstallContext *c,
++                        const LookupPaths *paths,
++                        const char *name,
++                        SearchFlags flags,
++                        UnitFileInstallInfo **ret,
++                        UnitFileChange **changes,
++                        size_t *n_changes) {
++
++        int r;
++
++        r = install_info_discover(scope, c, paths, name, flags, ret, changes, n_changes);
++        if (r < 0)
++                return r;
++
++        return install_info_may_process(ret ? *ret : NULL, paths, changes, n_changes);
++}
++
+ static int install_info_symlink_alias(
+                 UnitFileInstallInfo *i,
+                 const LookupPaths *paths,
+@@ -2399,11 +2418,8 @@ int unit_file_add_dependency(
+         if (!config_path)
+                 return -ENXIO;
+ 
+-        r = install_info_discover(scope, &c, &paths, target, SEARCH_FOLLOW_CONFIG_SYMLINKS,
+-                                  &target_info, changes, n_changes);
+-        if (r < 0)
+-                return r;
+-        r = install_info_may_process(target_info, &paths, changes, n_changes);
++        r = install_info_discover_and_check(scope, &c, &paths, target, SEARCH_FOLLOW_CONFIG_SYMLINKS,
++                                            &target_info, changes, n_changes);
+         if (r < 0)
+                 return r;
+ 
+@@ -2412,11 +2428,8 @@ int unit_file_add_dependency(
+         STRV_FOREACH(f, files) {
+                 char ***l;
+ 
+-                r = install_info_discover(scope, &c, &paths, *f, SEARCH_FOLLOW_CONFIG_SYMLINKS,
+-                                          &i, changes, n_changes);
+-                if (r < 0)
+-                        return r;
+-                r = install_info_may_process(i, &paths, changes, n_changes);
++                r = install_info_discover_and_check(scope, &c, &paths, *f, SEARCH_FOLLOW_CONFIG_SYMLINKS,
++                                                    &i, changes, n_changes);
+                 if (r < 0)
+                         return r;
+ 
+@@ -2467,11 +2480,8 @@ int unit_file_enable(
+                 return -ENXIO;
+ 
+         STRV_FOREACH(f, files) {
+-                r = install_info_discover(scope, &c, &paths, *f, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
+-                                          &i, changes, n_changes);
+-                if (r < 0)
+-                        return r;
+-                r = install_info_may_process(i, &paths, changes, n_changes);
++                r = install_info_discover_and_check(scope, &c, &paths, *f, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
++                                                    &i, changes, n_changes);
+                 if (r < 0)
+                         return r;
+ 
+@@ -2585,10 +2595,7 @@ int unit_file_set_default(
+         if (r < 0)
+                 return r;
+ 
+-        r = install_info_discover(scope, &c, &paths, name, 0, &i, changes, n_changes);
+-        if (r < 0)
+-                return r;
+-        r = install_info_may_process(i, &paths, changes, n_changes);
++        r = install_info_discover_and_check(scope, &c, &paths, name, 0, &i, changes, n_changes);
+         if (r < 0)
+                 return r;
+ 
+@@ -3089,22 +3096,14 @@ static int preset_prepare_one(
+                 if (instance_name_list) {
+                         char **s;
+                         STRV_FOREACH(s, instance_name_list) {
+-                                r = install_info_discover(scope, plus, paths, *s, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
+-                                                          &i, changes, n_changes);
+-                                if (r < 0)
+-                                        return r;
+-
+-                                r = install_info_may_process(i, paths, changes, n_changes);
++                                r = install_info_discover_and_check(scope, plus, paths, *s, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
++                                                                    &i, changes, n_changes);
+                                 if (r < 0)
+                                         return r;
+                         }
+                 } else {
+-                        r = install_info_discover(scope, plus, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
+-                                                  &i, changes, n_changes);
+-                        if (r < 0)
+-                                return r;
+-
+-                        r = install_info_may_process(i, paths, changes, n_changes);
++                        r = install_info_discover_and_check(scope, plus, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
++                                                            &i, changes, n_changes);
+                         if (r < 0)
+                                 return r;
+                 }
diff --git a/SOURCES/0489-test-fix-a-memleak.patch b/SOURCES/0489-test-fix-a-memleak.patch
new file mode 100644
index 0000000..d01dea9
--- /dev/null
+++ b/SOURCES/0489-test-fix-a-memleak.patch
@@ -0,0 +1,28 @@
+From 7444c6ed3628484dfed2f204c5b78a06a50f4bd8 Mon Sep 17 00:00:00 2001
+From: Yu Watanabe <watanabe.yu+github@gmail.com>
+Date: Wed, 29 Aug 2018 23:27:42 +0900
+Subject: [PATCH] test: fix a memleak
+
+Follow-up for #9901.
+
+Fixes #9968.
+
+(cherry picked from commit efa146369398fdb73f1cd177eb2522822ebf559c)
+
+Related: #1812972
+---
+ src/test/test-install-root.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/src/test/test-install-root.c b/src/test/test-install-root.c
+index dbbcfe4297..fe1ca5b16f 100644
+--- a/src/test/test-install-root.c
++++ b/src/test/test-install-root.c
+@@ -1037,6 +1037,7 @@ static void test_preset_multiple_instances(const char *root) {
+         assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bar1.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
+         assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bartest.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
+ 
++        unit_file_changes_free(changes, n_changes);
+ }
+ 
+ int main(int argc, char *argv[]) {
diff --git a/SOURCES/0490-docs-Add-syntax-for-templated-units-to-systemd.prese.patch b/SOURCES/0490-docs-Add-syntax-for-templated-units-to-systemd.prese.patch
new file mode 100644
index 0000000..2f726d0
--- /dev/null
+++ b/SOURCES/0490-docs-Add-syntax-for-templated-units-to-systemd.prese.patch
@@ -0,0 +1,55 @@
+From 55df2fd634f900419b718ed354132cc86cd533dd Mon Sep 17 00:00:00 2001
+From: Joerg Behrmann <behrmann@physik.fu-berlin.de>
+Date: Tue, 10 Mar 2020 16:34:13 +0100
+Subject: [PATCH] docs: Add syntax for templated units to systemd.preset man
+ page
+
+This documents the syntax
+
+     enable template@.service foo bar baz
+
+that was introduced in #9901 to preset templated units.
+
+(cherry picked from commit 1f667d8a7cff4355cd23ebebeb4d7179e3498eb8)
+
+Related: #1812972
+---
+ man/systemd.preset.xml | 18 ++++++++++++++++--
+ 1 file changed, 16 insertions(+), 2 deletions(-)
+
+diff --git a/man/systemd.preset.xml b/man/systemd.preset.xml
+index cf807bd4c8..df401f00f3 100644
+--- a/man/systemd.preset.xml
++++ b/man/systemd.preset.xml
+@@ -71,8 +71,11 @@
+     either the word <literal>enable</literal> or
+     <literal>disable</literal> followed by a space and a unit name
+     (possibly with shell style wildcards), separated by newlines.
+-    Empty lines and lines whose first non-whitespace character is # or
+-    ; are ignored.</para>
++    Empty lines and lines whose first non-whitespace character is <literal>#</literal> or
++    <literal>;</literal> are ignored. Multiple instance names for unit
++    templates may be specified as a space separated list at the end of
++    the line instead of the customary position between <literal>@</literal>
++    and the unit suffix.</para>
+ 
+     <para>Presets must refer to the "real" unit file, and not to any aliases. See
+     <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+@@ -124,6 +127,17 @@ disable *</programlisting>
+     <literal>99-</literal>, it will be read last and hence can easily
+     be overridden by spin or administrator preset policy.</para>
+ 
++    <example>
++      <title>Enable multiple template instances</title>
++
++      <programlisting># /usr/lib/systemd/system-preset/80-dirsrv.preset
++
++enable dirsrv@.service foo bar baz</programlisting>
++    </example>
++
++    <para>This enables all three of <filename>dirsrv@foo.service</filename>,
++    <filename>dirsrv@bar.service</filename> and <filename>dirsrv@baz.service</filename>.</para>
++
+     <example>
+       <title>A GNOME spin</title>
+ 
diff --git a/SOURCES/0491-shared-install-fix-preset-operations-for-non-service.patch b/SOURCES/0491-shared-install-fix-preset-operations-for-non-service.patch
new file mode 100644
index 0000000..ea936c9
--- /dev/null
+++ b/SOURCES/0491-shared-install-fix-preset-operations-for-non-service.patch
@@ -0,0 +1,45 @@
+From db2816ee32fc81ba339175469e46b5dca7af8833 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Sat, 22 Aug 2020 11:58:15 +0200
+Subject: [PATCH] shared/install: fix preset operations for non-service
+ instantiated units
+
+Fixes https://github.com/coreos/ignition/issues/1064.
+
+(cherry picked from commit 47ab95fe4315b3f7ee5a3694460a744bb88c52fd)
+
+Related: #1812972
+---
+ src/shared/install.c | 13 +++++--------
+ 1 file changed, 5 insertions(+), 8 deletions(-)
+
+diff --git a/src/shared/install.c b/src/shared/install.c
+index 263b239f10..c2847df3f8 100644
+--- a/src/shared/install.c
++++ b/src/shared/install.c
+@@ -2937,20 +2937,17 @@ static int pattern_match_multiple_instances(
+ 
+         /* Compose a list of specified instances when unit name is a template  */
+         if (unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) {
+-                _cleanup_free_ char *prefix = NULL;
+                 _cleanup_strv_free_ char **out_strv = NULL;
+-                char **iter;
+-
+-                r = unit_name_to_prefix(unit_name, &prefix);
+-                if (r < 0)
+-                        return r;
+ 
++                char **iter;
+                 STRV_FOREACH(iter, rule.instances) {
+                         _cleanup_free_ char *name = NULL;
+-                        r = unit_name_build(prefix, *iter, ".service", &name);
++
++                        r = unit_name_replace_instance(unit_name, *iter, &name);
+                         if (r < 0)
+                                 return r;
+-                        r = strv_extend(&out_strv, name);
++
++                        r = strv_consume(&out_strv, TAKE_PTR(name));
+                         if (r < 0)
+                                 return r;
+                 }
diff --git a/SOURCES/0492-introduce-setsockopt_int-helper.patch b/SOURCES/0492-introduce-setsockopt_int-helper.patch
new file mode 100644
index 0000000..7991184
--- /dev/null
+++ b/SOURCES/0492-introduce-setsockopt_int-helper.patch
@@ -0,0 +1,30 @@
+From 8cff80d7fc28ca04bd6c8e2257b46d96bea338ce Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Thu, 18 Oct 2018 19:48:18 +0200
+Subject: [PATCH] introduce setsockopt_int() helper
+
+As suggested by @heftig:
+
+https://github.com/systemd/systemd/commit/6d5e65f6454212cd400d0ebda34978a9f20cc26a#commitcomment-30938667
+(cherry picked from commit 2ff48e981e6cd1ccbfae49943274d9c8319a5e5d)
+
+Related: #1887181
+---
+ src/basic/socket-util.h | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h
+index 82781a0de1..616f2e0d05 100644
+--- a/src/basic/socket-util.h
++++ b/src/basic/socket-util.h
+@@ -183,3 +183,10 @@ struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t leng
+         })
+ 
+ int socket_ioctl_fd(void);
++
++static inline int setsockopt_int(int fd, int level, int optname, int value) {
++        if (setsockopt(fd, level, optname, &value, sizeof(value)) < 0)
++                return -errno;
++
++        return 0;
++}
diff --git a/SOURCES/0493-socket-util-add-generic-socket_pass_pktinfo-helper.patch b/SOURCES/0493-socket-util-add-generic-socket_pass_pktinfo-helper.patch
new file mode 100644
index 0000000..aa0679b
--- /dev/null
+++ b/SOURCES/0493-socket-util-add-generic-socket_pass_pktinfo-helper.patch
@@ -0,0 +1,57 @@
+From 96681723232e9eb0182279086fef291283004806 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Wed, 27 May 2020 19:27:51 +0200
+Subject: [PATCH] socket-util: add generic socket_pass_pktinfo() helper
+
+The helper turns on the protocol specific "packet info" structure cmsg
+for three relevant protocols we know.
+
+(cherry picked from commit 35a3eb9bdc95d1e6ba25bc65c78959ea104e45a1)
+
+Related: #1887181
+---
+ src/basic/socket-util.c | 23 +++++++++++++++++++++++
+ src/basic/socket-util.h |  2 ++
+ 2 files changed, 25 insertions(+)
+
+diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c
+index 986bc6e67f..053bcba670 100644
+--- a/src/basic/socket-util.c
++++ b/src/basic/socket-util.c
+@@ -1246,3 +1246,26 @@ int socket_ioctl_fd(void) {
+ 
+         return fd;
+ }
++
++int socket_pass_pktinfo(int fd, bool b) {
++        int af;
++        socklen_t sl = sizeof(af);
++
++        if (getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &af, &sl) < 0)
++                return -errno;
++
++        switch (af) {
++
++        case AF_INET:
++                return setsockopt_int(fd, IPPROTO_IP, IP_PKTINFO, b);
++
++        case AF_INET6:
++                return setsockopt_int(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, b);
++
++        case AF_NETLINK:
++                return setsockopt_int(fd, SOL_NETLINK, NETLINK_PKTINFO, b);
++
++        default:
++                return -EAFNOSUPPORT;
++        }
++}
+diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h
+index 616f2e0d05..c7c9ad34d6 100644
+--- a/src/basic/socket-util.h
++++ b/src/basic/socket-util.h
+@@ -190,3 +190,5 @@ static inline int setsockopt_int(int fd, int level, int optname, int value) {
+ 
+         return 0;
+ }
++
++int socket_pass_pktinfo(int fd, bool b);
diff --git a/SOURCES/0494-core-add-new-PassPacketInfo-socket-unit-property.patch b/SOURCES/0494-core-add-new-PassPacketInfo-socket-unit-property.patch
new file mode 100644
index 0000000..561e77c
--- /dev/null
+++ b/SOURCES/0494-core-add-new-PassPacketInfo-socket-unit-property.patch
@@ -0,0 +1,156 @@
+From 905a97ce65352d80af7260d34b74fd8342792c35 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Wed, 27 May 2020 19:36:56 +0200
+Subject: [PATCH] core: add new PassPacketInfo= socket unit property
+
+(cherry picked from commit a3d19f5d99c44940831a33df8b5bece4aaf749f7)
+
+Resolves: #1887181
+---
+ doc/TRANSIENT-SETTINGS.md                   | 1 +
+ man/systemd.socket.xml                      | 9 +++++++++
+ src/core/dbus-socket.c                      | 4 ++++
+ src/core/load-fragment-gperf.gperf.m4       | 1 +
+ src/core/socket.c                           | 8 ++++++++
+ src/core/socket.h                           | 1 +
+ src/shared/bus-unit-util.c                  | 3 +--
+ test/fuzz/fuzz-unit-file/directives.service | 1 +
+ 8 files changed, 26 insertions(+), 2 deletions(-)
+
+diff --git a/doc/TRANSIENT-SETTINGS.md b/doc/TRANSIENT-SETTINGS.md
+index 995b8797ef..de0ef9cc49 100644
+--- a/doc/TRANSIENT-SETTINGS.md
++++ b/doc/TRANSIENT-SETTINGS.md
+@@ -410,6 +410,7 @@ Most socket unit settings are available to transient units.
+ ✓ Broadcast=
+ ✓ PassCredentials=
+ ✓ PassSecurity=
++✓ PassPacketInfo=
+ ✓ TCPCongestion=
+ ✓ ReusePort=
+ ✓ MessageQueueMaxMessages=
+diff --git a/man/systemd.socket.xml b/man/systemd.socket.xml
+index 8676b4e03f..a908d5b6d8 100644
+--- a/man/systemd.socket.xml
++++ b/man/systemd.socket.xml
+@@ -712,6 +712,15 @@
+         Defaults to <option>false</option>.</para></listitem>
+       </varlistentry>
+ 
++      <varlistentry>
++        <term><varname>PassPacketInfo=</varname></term>
++        <listitem><para>Takes a boolean value. This controls the <constant>IP_PKTINFO</constant>,
++        <constant>IPV6_RECVPKTINFO</constant> and <constant>NETLINK_PKTINFO</constant> socket options, which
++        enable reception of additional per-packet metadata as ancillary message, on
++        <constant>AF_INET</constant>, <constant>AF_INET6</constant> and <constant>AF_UNIX</constant> sockets.
++        Defaults to <option>false</option>.</para></listitem>
++      </varlistentry>
++
+       <varlistentry>
+         <term><varname>TCPCongestion=</varname></term>
+         <listitem><para>Takes a string value. Controls the TCP
+diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c
+index fa6bbe2c6f..17494b80c8 100644
+--- a/src/core/dbus-socket.c
++++ b/src/core/dbus-socket.c
+@@ -104,6 +104,7 @@ const sd_bus_vtable bus_socket_vtable[] = {
+         SD_BUS_PROPERTY("Broadcast", "b", bus_property_get_bool, offsetof(Socket, broadcast), SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("PassCredentials", "b", bus_property_get_bool, offsetof(Socket, pass_cred), SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("PassSecurity", "b", bus_property_get_bool, offsetof(Socket, pass_sec), SD_BUS_VTABLE_PROPERTY_CONST),
++        SD_BUS_PROPERTY("PassPacketInfo", "b", bus_property_get_bool, offsetof(Socket, pass_pktinfo), SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("RemoveOnStop", "b", bus_property_get_bool, offsetof(Socket, remove_on_stop), SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("Listen", "a(ss)", property_get_listen, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+         SD_BUS_PROPERTY("Symlinks", "as", NULL, offsetof(Socket, symlinks), SD_BUS_VTABLE_PROPERTY_CONST),
+@@ -205,6 +206,9 @@ static int bus_socket_set_transient_property(
+         if (streq(name, "PassSecurity"))
+                 return bus_set_transient_bool(u, name, &s->pass_sec, message, flags, error);
+ 
++        if (streq(name, "PassPacketInfo"))
++                return bus_set_transient_bool(u, name, &s->pass_pktinfo, message, flags, error);
++
+         if (streq(name, "ReusePort"))
+                 return bus_set_transient_bool(u, name, &s->reuse_port, message, flags, error);
+ 
+diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
+index 156a4d0a6d..7d683cc84b 100644
+--- a/src/core/load-fragment-gperf.gperf.m4
++++ b/src/core/load-fragment-gperf.gperf.m4
+@@ -381,6 +381,7 @@ Socket.Transparent,              config_parse_bool,                  0,
+ Socket.Broadcast,                config_parse_bool,                  0,                             offsetof(Socket, broadcast)
+ Socket.PassCredentials,          config_parse_bool,                  0,                             offsetof(Socket, pass_cred)
+ Socket.PassSecurity,             config_parse_bool,                  0,                             offsetof(Socket, pass_sec)
++Socket.PassPacketInfo,           config_parse_bool,                  0,                             offsetof(Socket, pass_pktinfo)
+ Socket.TCPCongestion,            config_parse_string,                0,                             offsetof(Socket, tcp_congestion)
+ Socket.ReusePort,                config_parse_bool,                  0,                             offsetof(Socket, reuse_port)
+ Socket.MessageQueueMaxMessages,  config_parse_long,                  0,                             offsetof(Socket, mq_maxmsg)
+diff --git a/src/core/socket.c b/src/core/socket.c
+index 97c3a7fc9a..50c32ed8f4 100644
+--- a/src/core/socket.c
++++ b/src/core/socket.c
+@@ -660,6 +660,7 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) {
+                 "%sBroadcast: %s\n"
+                 "%sPassCredentials: %s\n"
+                 "%sPassSecurity: %s\n"
++                "%sPassPacketInfo: %s\n"
+                 "%sTCPCongestion: %s\n"
+                 "%sRemoveOnStop: %s\n"
+                 "%sWritable: %s\n"
+@@ -678,6 +679,7 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) {
+                 prefix, yes_no(s->broadcast),
+                 prefix, yes_no(s->pass_cred),
+                 prefix, yes_no(s->pass_sec),
++                prefix, yes_no(s->pass_pktinfo),
+                 prefix, strna(s->tcp_congestion),
+                 prefix, yes_no(s->remove_on_stop),
+                 prefix, yes_no(s->writable),
+@@ -1099,6 +1101,12 @@ static void socket_apply_socket_options(Socket *s, int fd) {
+                         log_unit_warning_errno(UNIT(s), errno, "SO_PASSSEC failed: %m");
+         }
+ 
++        if (s->pass_pktinfo) {
++                r = socket_pass_pktinfo(fd, true);
++                if (r < 0)
++                        log_unit_warning_errno(UNIT(s), r, "Failed to enable packet info socket option: %m");
++        }
++
+         if (s->priority >= 0)
+                 if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &s->priority, sizeof(s->priority)) < 0)
+                         log_unit_warning_errno(UNIT(s), errno, "SO_PRIORITY failed: %m");
+diff --git a/src/core/socket.h b/src/core/socket.h
+index b7a25d91fd..2409dbf2a0 100644
+--- a/src/core/socket.h
++++ b/src/core/socket.h
+@@ -121,6 +121,7 @@ struct Socket {
+         bool broadcast;
+         bool pass_cred;
+         bool pass_sec;
++        bool pass_pktinfo;
+ 
+         /* Only for INET6 sockets: issue IPV6_V6ONLY sockopt */
+         SocketAddressBindIPv6Only bind_ipv6_only;
+diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
+index daa2c2dce5..9010448aaf 100644
+--- a/src/shared/bus-unit-util.c
++++ b/src/shared/bus-unit-util.c
+@@ -1478,8 +1478,7 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons
+         if (STR_IN_SET(field,
+                        "Accept", "Writable", "KeepAlive", "NoDelay", "FreeBind", "Transparent", "Broadcast",
+                        "PassCredentials", "PassSecurity", "ReusePort", "RemoveOnStop", "SELinuxContextFromNet",
+-                       "FlushPending"))
+-
++                       "FlushPending", "PassPacketInfo"))
+                 return bus_append_parse_boolean(m, field, eq);
+ 
+         if (STR_IN_SET(field, "Priority", "IPTTL", "Mark"))
+diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service
+index 9d0530df72..8fde27fc90 100644
+--- a/test/fuzz/fuzz-unit-file/directives.service
++++ b/test/fuzz/fuzz-unit-file/directives.service
+@@ -161,6 +161,7 @@ PIDFile=
+ PartOf=
+ PassCredentials=
+ PassSecurity=
++PassPacketInfo=
+ PathChanged=
+ PathExists=
+ PathExistsGlob=
diff --git a/SOURCES/0495-resolved-tweak-cmsg-calculation.patch b/SOURCES/0495-resolved-tweak-cmsg-calculation.patch
new file mode 100644
index 0000000..5ce13b6
--- /dev/null
+++ b/SOURCES/0495-resolved-tweak-cmsg-calculation.patch
@@ -0,0 +1,30 @@
+From 6ece87bef14ac5741fc870644504737b00607546 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Wed, 27 May 2020 19:38:38 +0200
+Subject: [PATCH] resolved: tweak cmsg calculation
+
+We ask for the TTL, then have enough space for it.
+
+We probably can drop the extra cmsg space now, but let's figure that out
+another time, since the extra cmsg space is used elsewhere in resolved
+as well.
+
+(cherry picked from commit 08ab18618ec59022582f1513c0718ba369f5ba85)
+
+Related: #1887181
+---
+ src/resolve/resolved-dns-stream.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c
+index 066daef96e..555e200a23 100644
+--- a/src/resolve/resolved-dns-stream.c
++++ b/src/resolve/resolved-dns-stream.c
+@@ -70,6 +70,7 @@ static int dns_stream_identify(DnsStream *s) {
+         union {
+                 struct cmsghdr header; /* For alignment */
+                 uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo))
++                               + CMSG_SPACE(int) + /* for the TTL */
+                                + EXTRA_CMSG_SPACE /* kernel appears to require extra space */];
+         } control;
+         struct msghdr mh = {};
diff --git a/SOURCES/0496-ci-PowerTools-repo-was-renamed-to-powertools-in-RHEL.patch b/SOURCES/0496-ci-PowerTools-repo-was-renamed-to-powertools-in-RHEL.patch
new file mode 100644
index 0000000..cd054ce
--- /dev/null
+++ b/SOURCES/0496-ci-PowerTools-repo-was-renamed-to-powertools-in-RHEL.patch
@@ -0,0 +1,26 @@
+From 07b154fbc817e93f58c597644570a633c38d1c72 Mon Sep 17 00:00:00 2001
+From: Frantisek Sumsal <frantisek@sumsal.cz>
+Date: Fri, 15 Jan 2021 12:51:02 +0100
+Subject: [PATCH] ci: PowerTools repo was renamed to powertools in RHEL 8.3
+
+See: https://wiki.centos.org/Manuals/ReleaseNotes/CentOS8.2011#Yum_repo_file_and_repoid_changes
+
+rhel-only
+Related: #1871827
+---
+ ci/travis-centos-rhel8.sh | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/ci/travis-centos-rhel8.sh b/ci/travis-centos-rhel8.sh
+index cd0857fd29..43e2cb2585 100755
+--- a/ci/travis-centos-rhel8.sh
++++ b/ci/travis-centos-rhel8.sh
+@@ -95,7 +95,7 @@ for phase in "${PHASES[@]}"; do
+             # Upgrade the container to get the most recent environment
+             $DOCKER_EXEC dnf -y upgrade
+             # Install systemd's build dependencies
+-            $DOCKER_EXEC dnf -q -y --enablerepo "PowerTools" builddep systemd
++            $DOCKER_EXEC dnf -q -y --enablerepo "powertools" builddep systemd
+             ;;
+         RUN)
+             info "Run phase"
diff --git a/SOURCES/0497-ci-use-quay.io-instead-of-Docker-Hub-to-avoid-rate-l.patch b/SOURCES/0497-ci-use-quay.io-instead-of-Docker-Hub-to-avoid-rate-l.patch
new file mode 100644
index 0000000..db85595
--- /dev/null
+++ b/SOURCES/0497-ci-use-quay.io-instead-of-Docker-Hub-to-avoid-rate-l.patch
@@ -0,0 +1,33 @@
+From 2dd82aad646bde5a0d49df8562e2578c8b3d04f4 Mon Sep 17 00:00:00 2001
+From: Frantisek Sumsal <frantisek@sumsal.cz>
+Date: Fri, 15 Jan 2021 13:00:33 +0100
+Subject: [PATCH] ci: use quay.io instead of Docker Hub to avoid rate limits
+
+Docker Hub introduced rate limits for anonymous users (100 requests per
+six hours), which break our CI in the busier periods. Let's try to use
+the quay.io CentOS image to mitigate this.
+
+rhel-only
+Related: #1871827
+---
+ ci/travis-centos-rhel8.sh | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/ci/travis-centos-rhel8.sh b/ci/travis-centos-rhel8.sh
+index 43e2cb2585..ffe5813b1a 100755
+--- a/ci/travis-centos-rhel8.sh
++++ b/ci/travis-centos-rhel8.sh
+@@ -81,11 +81,11 @@ for phase in "${PHASES[@]}"; do
+             info "Setup phase"
+             info "Using Travis $CENTOS_RELEASE"
+             # Pull a Docker image and start a new container
+-            docker pull centos:$CENTOS_RELEASE
++            docker pull quay.io/centos/centos:$CENTOS_RELEASE
+             info "Starting container $CONT_NAME"
+             $DOCKER_RUN -v $REPO_ROOT:/build:rw \
+                         -w /build --privileged=true --name $CONT_NAME \
+-                        -dit --net=host centos:$CENTOS_RELEASE /sbin/init
++                        -dit --net=host quay.io/centos/centos:$CENTOS_RELEASE /sbin/init
+             # Beautiful workaround for Fedora's version of Docker
+             sleep 1
+             $DOCKER_EXEC dnf makecache
diff --git a/SOURCES/0498-ci-move-jobs-from-Travis-CI-to-GH-Actions.patch b/SOURCES/0498-ci-move-jobs-from-Travis-CI-to-GH-Actions.patch
new file mode 100644
index 0000000..70ba33f
--- /dev/null
+++ b/SOURCES/0498-ci-move-jobs-from-Travis-CI-to-GH-Actions.patch
@@ -0,0 +1,338 @@
+From 88ac207cc619935c64923e6f8fdef324a5b733d8 Mon Sep 17 00:00:00 2001
+From: Frantisek Sumsal <frantisek@sumsal.cz>
+Date: Fri, 15 Jan 2021 15:13:53 +0100
+Subject: [PATCH] ci: move jobs from Travis CI to GH Actions
+
+The OSS version of Travis CI is going to be merged with the commercial
+one soon, essentially dropping the free tier, so let's move the CI jobs
+to GitHub Actions to keep them up.
+
+rhel-only
+Related: #1871827
+---
+ .../workflows/unit_tests.sh                   | 28 +++----
+ .github/workflows/unit_tests.yml              | 28 +++++++
+ .travis.yml                                   | 48 ------------
+ ci/travis-centos-rhel7.sh                     | 73 -------------------
+ ci/travis_wait.bash                           | 61 ----------------
+ 5 files changed, 37 insertions(+), 201 deletions(-)
+ rename ci/travis-centos-rhel8.sh => .github/workflows/unit_tests.sh (82%)
+ create mode 100644 .github/workflows/unit_tests.yml
+ delete mode 100644 .travis.yml
+ delete mode 100755 ci/travis-centos-rhel7.sh
+ delete mode 100644 ci/travis_wait.bash
+
+diff --git a/ci/travis-centos-rhel8.sh b/.github/workflows/unit_tests.sh
+similarity index 82%
+rename from ci/travis-centos-rhel8.sh
+rename to .github/workflows/unit_tests.sh
+index ffe5813b1a..ea4f7e7592 100755
+--- a/ci/travis-centos-rhel8.sh
++++ b/.github/workflows/unit_tests.sh
+@@ -1,18 +1,9 @@
+ #!/bin/bash
+ 
+-# Run this script from the root of the systemd's git repository
+-# or set REPO_ROOT to a correct path.
+-#
+-# Example execution on Fedora:
+-# dnf install docker
+-# systemctl start docker
+-# export CONT_NAME="my-fancy-container"
+-# ci/travis-centos.sh SETUP RUN CLEANUP
+-
+ PHASES=(${@:-SETUP RUN CLEANUP})
+ CENTOS_RELEASE="${CENTOS_RELEASE:-latest}"
+ CONT_NAME="${CONT_NAME:-centos-$CENTOS_RELEASE-$RANDOM}"
+-DOCKER_EXEC="${DOCKER_EXEC:-docker exec -it $CONT_NAME}"
++DOCKER_EXEC="${DOCKER_EXEC:-docker exec $CONT_NAME}"
+ DOCKER_RUN="${DOCKER_RUN:-docker run}"
+ REPO_ROOT="${REPO_ROOT:-$PWD}"
+ ADDITIONAL_DEPS=(libasan libubsan net-tools strace nc e2fsprogs quota dnsmasq diffutils)
+@@ -71,9 +62,7 @@ function info() {
+     echo -e "\033[33;1m$1\033[0m"
+ }
+ 
+-set -e
+-
+-source "$(dirname $0)/travis_wait.bash"
++set -ex
+ 
+ for phase in "${PHASES[@]}"; do
+     case $phase in
+@@ -86,6 +75,7 @@ for phase in "${PHASES[@]}"; do
+             $DOCKER_RUN -v $REPO_ROOT:/build:rw \
+                         -w /build --privileged=true --name $CONT_NAME \
+                         -dit --net=host quay.io/centos/centos:$CENTOS_RELEASE /sbin/init
++
+             # Beautiful workaround for Fedora's version of Docker
+             sleep 1
+             $DOCKER_EXEC dnf makecache
+@@ -97,10 +87,10 @@ for phase in "${PHASES[@]}"; do
+             # Install systemd's build dependencies
+             $DOCKER_EXEC dnf -q -y --enablerepo "powertools" builddep systemd
+             ;;
+-        RUN)
++        RUN|RUN_GCC)
+             info "Run phase"
+             # Build systemd
+-            docker exec -it -e CFLAGS='-g -O0 -ftrapv' $CONT_NAME meson build -Dtests=unsafe -Dslow-tests=true "${CONFIGURE_OPTS[@]}"
++            docker exec -e CFLAGS='-g -O0 -ftrapv' $CONT_NAME meson build -Dtests=unsafe -Dslow-tests=true "${CONFIGURE_OPTS[@]}"
+             $DOCKER_EXEC ninja -v -C build
+             # Let's install the new systemd and "reboot" the container to avoid
+             # unexpected fails due to incompatibilities with older systemd
+@@ -108,16 +98,16 @@ for phase in "${PHASES[@]}"; do
+             docker restart $CONT_NAME
+             $DOCKER_EXEC ninja -C build test
+             ;;
+-        RUN_ASAN|RUN_CLANG_ASAN)
++        RUN_ASAN|RUN_GCC_ASAN|RUN_CLANG_ASAN)
+             if [[ "$phase" = "RUN_CLANG_ASAN" ]]; then
+                 ENV_VARS="-e CC=clang -e CXX=clang++"
+                 MESON_ARGS="-Db_lundef=false" # See https://github.com/mesonbuild/meson/issues/764
+             fi
+-            docker exec $ENV_VARS -it $CONT_NAME meson build --werror -Dtests=unsafe -Db_sanitize=address,undefined $MESON_ARGS "${CONFIGURE_OPTS[@]}"
+-            docker exec -it $CONT_NAME ninja -v -C build
++            docker exec $ENV_VARS $CONT_NAME meson build --werror -Dtests=unsafe -Db_sanitize=address,undefined $MESON_ARGS "${CONFIGURE_OPTS[@]}"
++            docker exec $CONT_NAME ninja -v -C build
+ 
+             # Never remove halt_on_error from UBSAN_OPTIONS. See https://github.com/systemd/systemd/commit/2614d83aa06592aedb.
+-            travis_wait docker exec --interactive=false \
++            docker exec --interactive=false \
+                 -e UBSAN_OPTIONS=print_stacktrace=1:print_summary=1:halt_on_error=1 \
+                 -e ASAN_OPTIONS=strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1 \
+                 -e "TRAVIS=$TRAVIS" \
+diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml
+new file mode 100644
+index 0000000000..15f5127a75
+--- /dev/null
++++ b/.github/workflows/unit_tests.yml
+@@ -0,0 +1,28 @@
++---
++# vi: ts=2 sw=2 et:
++#
++name: Unit tests
++on:
++  pull_request:
++    branches:
++      - master
++
++jobs:
++  build:
++    runs-on: ubuntu-20.04
++    env:
++      CENTOS_RELEASE: "centos8"
++      CONT_NAME:      "systemd-centos8-ci"
++    strategy:
++      fail-fast: false
++      matrix:
++        run_phase: [GCC, GCC_ASAN]
++    steps:
++      - name: Repository checkout
++        uses: actions/checkout@v1
++      - name: Install build dependencies
++        run: sudo -E .github/workflows/unit_tests.sh SETUP
++      - name: Build & test (${{ matrix.run_phase }})
++        run: sudo -E .github/workflows/unit_tests.sh RUN_${{ matrix.run_phase }}
++      - name: Cleanup
++        run: sudo -E .github/workflows/unit_tests.sh CLEANUP
+diff --git a/.travis.yml b/.travis.yml
+deleted file mode 100644
+index 70c60cf24e..0000000000
+--- a/.travis.yml
++++ /dev/null
+@@ -1,48 +0,0 @@
+-sudo: required
+-dist: xenial
+-services:
+-    - docker
+-
+-env:
+-    global:
+-        - CI_ROOT="$TRAVIS_BUILD_DIR/ci/"
+-
+-jobs:
+-    include:
+-        - name: CentOS 8
+-          language: bash
+-          env:
+-              - CENTOS_RELEASE="centos8"
+-              - CONT_NAME="systemd-centos-$CENTOS_RELEASE"
+-              - DOCKER_EXEC="docker exec -ti $CONT_NAME"
+-          before_install:
+-              - sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce
+-              - docker --version
+-          install:
+-              - $CI_ROOT/travis-centos-rhel8.sh SETUP
+-          script:
+-              - set -e
+-              # Build systemd
+-              - $CI_ROOT/travis-centos-rhel8.sh RUN
+-              - set +e
+-          after_script:
+-              - $CI_ROOT/travis-centos-rhel8.sh CLEANUP
+-
+-        - name: CentOS 8 (ASan+UBSan)
+-          language: bash
+-          env:
+-              - CENTOS_RELEASE="centos8"
+-              - CONT_NAME="systemd-centos-$CENTOS_RELEASE"
+-              - DOCKER_EXEC="docker exec -ti $CONT_NAME"
+-          before_install:
+-              - sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce
+-              - docker --version
+-          install:
+-              - $CI_ROOT/travis-centos-rhel8.sh SETUP
+-          script:
+-              - set -e
+-              # Build systemd
+-              - $CI_ROOT/travis-centos-rhel8.sh RUN_ASAN
+-              - set +e
+-          after_script:
+-              - $CI_ROOT/travis-centos-rhel8.sh CLEANUP
+diff --git a/ci/travis-centos-rhel7.sh b/ci/travis-centos-rhel7.sh
+deleted file mode 100755
+index 73416798ed..0000000000
+--- a/ci/travis-centos-rhel7.sh
++++ /dev/null
+@@ -1,73 +0,0 @@
+-#!/bin/bash
+-
+-# Run this script from the root of the systemd's git repository
+-# or set REPO_ROOT to a correct path.
+-#
+-# Example execution on Fedora:
+-# dnf install docker
+-# systemctl start docker
+-# export CONT_NAME="my-fancy-container"
+-# ci/travis-centos.sh SETUP RUN CLEANUP
+-
+-PHASES=(${@:-SETUP RUN CLEANUP})
+-CENTOS_RELEASE="${CENTOS_RELEASE:-latest}"
+-CONT_NAME="${CONT_NAME:-centos-$CENTOS_RELEASE-$RANDOM}"
+-DOCKER_EXEC="${DOCKER_EXEC:-docker exec -it $CONT_NAME}"
+-DOCKER_RUN="${DOCKER_RUN:-docker run}"
+-REPO_ROOT="${REPO_ROOT:-$PWD}"
+-ADDITIONAL_DEPS=(yum-utils iputils hostname libasan libubsan clang llvm)
+-
+-function info() {
+-    echo -e "\033[33;1m$1\033[0m"
+-}
+-
+-set -e
+-
+-source "$(dirname $0)/travis_wait.bash"
+-
+-for phase in "${PHASES[@]}"; do
+-    case $phase in
+-        SETUP)
+-            info "Setup phase"
+-            info "Using Travis $CENTOS_RELEASE"
+-            # Pull a Docker image and start a new container
+-            docker pull centos:$CENTOS_RELEASE
+-            info "Starting container $CONT_NAME"
+-            $DOCKER_RUN -v $REPO_ROOT:/build:rw \
+-                        -w /build --privileged=true --name $CONT_NAME \
+-                        -dit --net=host centos:$CENTOS_RELEASE /sbin/init
+-            # Beautiful workaround for Fedora's version of Docker
+-            sleep 1
+-            $DOCKER_EXEC yum makecache
+-            # Install necessary build/test requirements
+-            $DOCKER_EXEC yum -y upgrade
+-            $DOCKER_EXEC yum -y install "${ADDITIONAL_DEPS[@]}"
+-            $DOCKER_EXEC yum-builddep -y systemd
+-            ;;
+-        RUN)
+-            info "Run phase"
+-            # Build systemd
+-            $DOCKER_EXEC ./autogen.sh
+-            $DOCKER_EXEC ./configure --disable-timesyncd --disable-kdbus --disable-terminal \
+-                                     --enable-gtk-doc --enable-compat-libs --disable-sysusers \
+-                                     --disable-ldconfig --enable-lz4 --with-sysvinit-path=/etc/rc.d/init.d
+-            $DOCKER_EXEC make
+-            # Let's install the new systemd and "reboot" the container to avoid
+-            # unexpected fails due to incompatibilities with older systemd
+-            $DOCKER_EXEC make install
+-            docker restart $CONT_NAME
+-            if ! $DOCKER_EXEC make check; then
+-                $DOCKER_EXEC cat test-suite.log
+-                exit 1
+-            fi
+-            ;;
+-        CLEANUP)
+-            info "Cleanup phase"
+-            docker stop $CONT_NAME
+-            docker rm -f $CONT_NAME
+-            ;;
+-        *)
+-            echo >&2 "Unknown phase '$phase'"
+-            exit 1
+-    esac
+-done
+diff --git a/ci/travis_wait.bash b/ci/travis_wait.bash
+deleted file mode 100644
+index acf6ad15e4..0000000000
+--- a/ci/travis_wait.bash
++++ /dev/null
+@@ -1,61 +0,0 @@
+-# This was borrowed from https://github.com/travis-ci/travis-build/tree/master/lib/travis/build/bash
+-# to get around https://github.com/travis-ci/travis-ci/issues/9979. It should probably be removed
+-# as soon as Travis CI has started to provide an easy way to export the functions to bash scripts.
+-
+-travis_jigger() {
+-  local cmd_pid="${1}"
+-  shift
+-  local timeout="${1}"
+-  shift
+-  local count=0
+-
+-  echo -e "\\n"
+-
+-  while [[ "${count}" -lt "${timeout}" ]]; do
+-    count="$((count + 1))"
+-    echo -ne "Still running (${count} of ${timeout}): ${*}\\r"
+-    sleep 60
+-  done
+-
+-  echo -e "\\n${ANSI_RED}Timeout (${timeout} minutes) reached. Terminating \"${*}\"${ANSI_RESET}\\n"
+-  kill -9 "${cmd_pid}"
+-}
+-
+-travis_wait() {
+-  local timeout="${1}"
+-
+-  if [[ "${timeout}" =~ ^[0-9]+$ ]]; then
+-    shift
+-  else
+-    timeout=20
+-  fi
+-
+-  local cmd=("${@}")
+-  local log_file="travis_wait_${$}.log"
+-
+-  "${cmd[@]}" &>"${log_file}" &
+-  local cmd_pid="${!}"
+-
+-  travis_jigger "${!}" "${timeout}" "${cmd[@]}" &
+-  local jigger_pid="${!}"
+-  local result
+-
+-  {
+-    set +e
+-    wait "${cmd_pid}" 2>/dev/null
+-    result="${?}"
+-    ps -p"${jigger_pid}" &>/dev/null && kill "${jigger_pid}"
+-    set -e
+-  }
+-
+-  if [[ "${result}" -eq 0 ]]; then
+-    echo -e "\\n${ANSI_GREEN}The command ${cmd[*]} exited with ${result}.${ANSI_RESET}"
+-  else
+-    echo -e "\\n${ANSI_RED}The command ${cmd[*]} exited with ${result}.${ANSI_RESET}"
+-  fi
+-
+-  echo -e "\\n${ANSI_GREEN}Log:${ANSI_RESET}\\n"
+-  cat "${log_file}"
+-
+-  return "${result}"
+-}
diff --git a/SOURCES/0499-unit-make-UNIT-cast-function-deal-with-NULL-pointers.patch b/SOURCES/0499-unit-make-UNIT-cast-function-deal-with-NULL-pointers.patch
new file mode 100644
index 0000000..9f2143b
--- /dev/null
+++ b/SOURCES/0499-unit-make-UNIT-cast-function-deal-with-NULL-pointers.patch
@@ -0,0 +1,31 @@
+From a11334f0eae67b5159a416193e2e37634281000a Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Thu, 8 Nov 2018 09:33:31 +0100
+Subject: [PATCH] unit: make UNIT() cast function deal with NULL pointers
+
+Fixes: #10681
+(cherry picked from commit bbf11206230d1b089118971f98a047151cb5c4fa)
+
+Related: #1871827
+---
+ src/core/unit.h | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/src/core/unit.h b/src/core/unit.h
+index 6e37fd6f5a..ec45b5fb48 100644
+--- a/src/core/unit.h
++++ b/src/core/unit.h
+@@ -597,7 +597,12 @@ extern const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX];
+         }
+ 
+ /* For casting the various unit types into a unit */
+-#define UNIT(u) (&(u)->meta)
++#define UNIT(u)                                         \
++        ({                                              \
++                typeof(u) _u_ = (u);                    \
++                Unit *_w_ = _u_ ? &(_u_)->meta : NULL;  \
++                _w_;                                    \
++        })
+ 
+ #define UNIT_HAS_EXEC_CONTEXT(u) (UNIT_VTABLE(u)->exec_context_offset > 0)
+ #define UNIT_HAS_CGROUP_CONTEXT(u) (UNIT_VTABLE(u)->cgroup_context_offset > 0)
diff --git a/SOURCES/0500-use-link-to-RHEL-8-docs.patch b/SOURCES/0500-use-link-to-RHEL-8-docs.patch
new file mode 100644
index 0000000..ecc4968
--- /dev/null
+++ b/SOURCES/0500-use-link-to-RHEL-8-docs.patch
@@ -0,0 +1,25 @@
+From 6fb6c218fda0d5c3404049243b9392e9b0c7d537 Mon Sep 17 00:00:00 2001
+From: David Tardon <dtardon@redhat.com>
+Date: Fri, 11 Dec 2020 09:34:19 +0100
+Subject: [PATCH] use link to RHEL-8 docs
+
+RHEL-only
+
+Related: #1623116
+---
+ man/systemctl.xml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/man/systemctl.xml b/man/systemctl.xml
+index 56f94d084c..ed60a0739f 100644
+--- a/man/systemctl.xml
++++ b/man/systemctl.xml
+@@ -2005,7 +2005,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
+     <para>
+             For examples how to use systemctl in comparsion
+             with old service and chkconfig command please see:
+-            <ulink url="https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/sect-Managing_Services_with_systemd-Services.html">
++            <ulink url="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_basic_system_settings/managing-services-with-systemd_configuring-basic-system-settings">
+                     Managing System Services
+             </ulink>
+     </para>
diff --git a/SOURCES/0501-cgroup-Also-set-blkio.bfq.weight.patch b/SOURCES/0501-cgroup-Also-set-blkio.bfq.weight.patch
new file mode 100644
index 0000000..88ad896
--- /dev/null
+++ b/SOURCES/0501-cgroup-Also-set-blkio.bfq.weight.patch
@@ -0,0 +1,37 @@
+From af9f03ba48dd75be8c6a923f70da9804b3a3a2c3 Mon Sep 17 00:00:00 2001
+From: Pavel Hrdina <phrdina@redhat.com>
+Date: Wed, 25 Nov 2020 09:05:36 +0100
+Subject: [PATCH] cgroup: Also set blkio.bfq.weight
+
+Commit [1] added a workaround when unified cgroups are used but missed
+legacy cgroups where there is the same issue.
+
+[1] <https://github.com/systemd/systemd/commit/2dbc45aea747f25cc1c3848fded2ec0062f96bcf>
+
+Signed-off-by: Pavel Hrdina <phrdina@redhat.com>
+(cherry picked from commit 35e7a62ca32a30169a94693b831e53c832251984)
+
+Resolves: #1657810
+---
+ src/core/cgroup.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/src/core/cgroup.c b/src/core/cgroup.c
+index f1ce070f9a..71e30fd4db 100644
+--- a/src/core/cgroup.c
++++ b/src/core/cgroup.c
+@@ -1063,6 +1063,14 @@ static void cgroup_context_apply(
+                                 log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
+                                               "Failed to set blkio.weight: %m");
+ 
++                        /* FIXME: drop this when distro kernels properly support BFQ through "blkio.weight"
++                         * See also: https://github.com/systemd/systemd/pull/13335 */
++                        xsprintf(buf, "%" PRIu64 "\n", weight);
++                        r = cg_set_attribute("blkio", path, "blkio.bfq.weight", buf);
++                        if (r < 0)
++                                log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
++                                              "Failed to set blkio.bfq.weight: %m");
++
+                         if (has_io) {
+                                 CGroupIODeviceWeight *w;
+ 
diff --git a/SOURCES/0502-units-make-sure-initrd-cleanup.service-terminates-be.patch b/SOURCES/0502-units-make-sure-initrd-cleanup.service-terminates-be.patch
new file mode 100644
index 0000000..3fdb46f
--- /dev/null
+++ b/SOURCES/0502-units-make-sure-initrd-cleanup.service-terminates-be.patch
@@ -0,0 +1,37 @@
+From ea425381a675a2ce4d9519d534fe27c1012ac92e Mon Sep 17 00:00:00 2001
+From: Franck Bui <fbui@suse.com>
+Date: Mon, 28 Jan 2019 12:07:37 +0100
+Subject: [PATCH] units: make sure initrd-cleanup.service terminates before
+ switching to rootfs
+
+A follow-up for commit a8cb1dc3e0fa81aff.
+
+Commit a8cb1dc3e0fa81aff made sure that initrd-cleanup.service won't be stopped
+when initrd-switch-root.target is isolated.
+
+However even with this change, it might happen that initrd-cleanup.service
+survives the switch to rootfs (since it has no ordering constraints against
+initrd-switch-root.target) and is stopped right after when default.target is
+isolated. This led to initrd-cleanup.service entering in failed state as it
+happens when oneshot services are stopped.
+
+This patch along with a8cb1dc3e0fa81aff should fix issue #4343.
+
+Fixes: #4343
+(cherry picked from commit e2c7c94ea35fe7e669afb51bfc2251158b522ea5)
+
+Related: #1657810
+---
+ units/initrd-switch-root.target | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/units/initrd-switch-root.target b/units/initrd-switch-root.target
+index ad82245121..ea4f02618f 100644
+--- a/units/initrd-switch-root.target
++++ b/units/initrd-switch-root.target
+@@ -15,4 +15,4 @@ Requires=initrd-switch-root.service
+ Before=initrd-switch-root.service
+ AllowIsolate=yes
+ Wants=initrd-udevadm-cleanup-db.service initrd-root-fs.target initrd-fs.target systemd-journald.service initrd-cleanup.service
+-After=initrd-udevadm-cleanup-db.service initrd-root-fs.target initrd-fs.target emergency.service emergency.target
++After=initrd-udevadm-cleanup-db.service initrd-root-fs.target initrd-fs.target emergency.service emergency.target initrd-cleanup.service
diff --git a/SOURCES/0503-core-reload-SELinux-label-cache-on-daemon-reload.patch b/SOURCES/0503-core-reload-SELinux-label-cache-on-daemon-reload.patch
new file mode 100644
index 0000000..5e5d3d4
--- /dev/null
+++ b/SOURCES/0503-core-reload-SELinux-label-cache-on-daemon-reload.patch
@@ -0,0 +1,73 @@
+From c67be1c7d69a0662ab85720aa0209110c39479f9 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= <cgzones@googlemail.com>
+Date: Wed, 27 Nov 2019 19:43:47 +0100
+Subject: [PATCH] core: reload SELinux label cache on daemon-reload
+
+Reloading the SELinux label cache here enables a light-wight follow-up of a SELinux policy change, e.g. adding a label for a RuntimeDirectory.
+
+Closes: #13363
+(cherry picked from commit a9dfac21ec850eb5dcaf1ae9ef729389e4c12802)
+
+Resolves: #1888912
+---
+ src/basic/selinux-util.c | 20 ++++++++++++++++++++
+ src/basic/selinux-util.h |  1 +
+ src/core/main.c          |  2 ++
+ 3 files changed, 23 insertions(+)
+
+diff --git a/src/basic/selinux-util.c b/src/basic/selinux-util.c
+index e15bd7e1fa..f69d88eb1e 100644
+--- a/src/basic/selinux-util.c
++++ b/src/basic/selinux-util.c
+@@ -105,6 +105,26 @@ void mac_selinux_finish(void) {
+ #endif
+ }
+ 
++void mac_selinux_reload(void) {
++
++#if HAVE_SELINUX
++        struct selabel_handle *backup_label_hnd;
++
++        if (!label_hnd)
++                return;
++
++        backup_label_hnd = TAKE_PTR(label_hnd);
++
++        /* try to initialize new handle
++         *    on success close backup
++         *    on failure restore backup */
++        if (mac_selinux_init() == 0)
++                selabel_close(backup_label_hnd);
++        else
++                label_hnd = backup_label_hnd;
++#endif
++}
++
+ int mac_selinux_fix(const char *path, LabelFixFlags flags) {
+ 
+ #if HAVE_SELINUX
+diff --git a/src/basic/selinux-util.h b/src/basic/selinux-util.h
+index 08314057fb..abcfabe777 100644
+--- a/src/basic/selinux-util.h
++++ b/src/basic/selinux-util.h
+@@ -13,6 +13,7 @@ void mac_selinux_retest(void);
+ 
+ int mac_selinux_init(void);
+ void mac_selinux_finish(void);
++void mac_selinux_reload(void);
+ 
+ int mac_selinux_fix(const char *path, LabelFixFlags flags);
+ int mac_selinux_apply(const char *path, const char *label);
+diff --git a/src/core/main.c b/src/core/main.c
+index d897155644..d5c41da0c4 100644
+--- a/src/core/main.c
++++ b/src/core/main.c
+@@ -1682,6 +1682,8 @@ static int invoke_main_loop(
+                         saved_log_level = m->log_level_overridden ? log_get_max_level() : -1;
+                         saved_log_target = m->log_target_overridden ? log_get_target() : _LOG_TARGET_INVALID;
+ 
++                        mac_selinux_reload();
++
+                         (void) parse_configuration(saved_rlimit_nofile, saved_rlimit_memlock);
+ 
+                         set_manager_defaults(m);
diff --git a/SOURCES/0504-selinux-introduce-mac_selinux_create_file_prepare_at.patch b/SOURCES/0504-selinux-introduce-mac_selinux_create_file_prepare_at.patch
new file mode 100644
index 0000000..4b51123
--- /dev/null
+++ b/SOURCES/0504-selinux-introduce-mac_selinux_create_file_prepare_at.patch
@@ -0,0 +1,140 @@
+From 4f4e8bbd9ad46fc146a36f52790bc4920f42ef1f Mon Sep 17 00:00:00 2001
+From: Franck Bui <fbui@suse.com>
+Date: Mon, 2 Jul 2018 10:22:56 +0200
+Subject: [PATCH] selinux: introduce mac_selinux_create_file_prepare_at()
+
+(cherry picked from commit 7e531a5265687aef5177b070c36ca4ceab42e768)
+
+Related: #1888912
+---
+ src/basic/selinux-util.c | 83 ++++++++++++++++++++++++++++++----------
+ src/basic/selinux-util.h |  1 +
+ 2 files changed, 63 insertions(+), 21 deletions(-)
+
+diff --git a/src/basic/selinux-util.c b/src/basic/selinux-util.c
+index f69d88eb1e..a078ce23ef 100644
+--- a/src/basic/selinux-util.c
++++ b/src/basic/selinux-util.c
+@@ -336,48 +336,89 @@ char* mac_selinux_free(char *label) {
+         return NULL;
+ }
+ 
+-int mac_selinux_create_file_prepare(const char *path, mode_t mode) {
+-
+ #if HAVE_SELINUX
++static int selinux_create_file_prepare_abspath(const char *abspath, mode_t mode) {
+         _cleanup_freecon_ char *filecon = NULL;
++        _cleanup_free_ char *path = NULL;
+         int r;
+ 
+-        assert(path);
+-
+-        if (!label_hnd)
+-                return 0;
+-
+-        if (path_is_absolute(path))
+-                r = selabel_lookup_raw(label_hnd, &filecon, path, mode);
+-        else {
+-                _cleanup_free_ char *newpath = NULL;
+-
+-                r = path_make_absolute_cwd(path, &newpath);
+-                if (r < 0)
+-                        return r;
+-
+-                r = selabel_lookup_raw(label_hnd, &filecon, newpath, mode);
+-        }
++        assert(abspath);
++        assert(path_is_absolute(abspath));
+ 
++        r = selabel_lookup_raw(label_hnd, &filecon, abspath, mode);
+         if (r < 0) {
+                 /* No context specified by the policy? Proceed without setting it. */
+                 if (errno == ENOENT)
+                         return 0;
+ 
+-                log_enforcing_errno(errno, "Failed to determine SELinux security context for %s: %m", path);
++                log_enforcing_errno(errno, "Failed to determine SELinux security context for %s: %m", abspath);
+         } else {
+                 if (setfscreatecon_raw(filecon) >= 0)
+                         return 0; /* Success! */
+ 
+-                log_enforcing_errno(errno, "Failed to set SELinux security context %s for %s: %m", filecon, path);
++                log_enforcing_errno(errno, "Failed to set SELinux security context %s for %s: %m", filecon, abspath);
+         }
+ 
+         if (security_getenforce() > 0)
+                 return -errno;
+ 
+-#endif
+         return 0;
+ }
++#endif
++
++int mac_selinux_create_file_prepare_at(int dirfd, const char *path, mode_t mode) {
++        int r = 0;
++
++#if HAVE_SELINUX
++        _cleanup_free_ char *abspath = NULL;
++        _cleanup_close_ int fd = -1;
++
++        assert(path);
++
++        if (!label_hnd)
++                return 0;
++
++        if (!path_is_absolute(path)) {
++                _cleanup_free_ char *p = NULL;
++
++                if (dirfd == AT_FDCWD)
++                        r = safe_getcwd(&p);
++                else
++                        r = fd_get_path(dirfd, &p);
++                if (r < 0)
++                        return r;
++
++                abspath = path_join(NULL, p, path);
++                if (!abspath)
++                        return -ENOMEM;
++
++                path = abspath;
++        }
++
++        r = selinux_create_file_prepare_abspath(path, mode);
++#endif
++        return r;
++}
++
++int mac_selinux_create_file_prepare(const char *path, mode_t mode) {
++        int r = 0;
++
++#if HAVE_SELINUX
++        _cleanup_free_ char *abspath = NULL;
++
++        assert(path);
++
++        if (!label_hnd)
++                return 0;
++
++        r = path_make_absolute_cwd(path, &abspath);
++        if (r < 0)
++                return r;
++
++        r = selinux_create_file_prepare_abspath(abspath, mode);
++#endif
++        return r;
++}
+ 
+ void mac_selinux_create_file_clear(void) {
+ 
+diff --git a/src/basic/selinux-util.h b/src/basic/selinux-util.h
+index abcfabe777..639c35b687 100644
+--- a/src/basic/selinux-util.h
++++ b/src/basic/selinux-util.h
+@@ -24,6 +24,7 @@ int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *
+ char* mac_selinux_free(char *label);
+ 
+ int mac_selinux_create_file_prepare(const char *path, mode_t mode);
++int mac_selinux_create_file_prepare_at(int dirfd, const char *path, mode_t mode);
+ void mac_selinux_create_file_clear(void);
+ 
+ int mac_selinux_create_socket_prepare(const char *label);
diff --git a/SOURCES/0505-selinux-add-trigger-for-policy-reload-to-refresh-int.patch b/SOURCES/0505-selinux-add-trigger-for-policy-reload-to-refresh-int.patch
new file mode 100644
index 0000000..4bba960
--- /dev/null
+++ b/SOURCES/0505-selinux-add-trigger-for-policy-reload-to-refresh-int.patch
@@ -0,0 +1,135 @@
+From 4e48673172b012a06575e4f5b681d3554eded2e2 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= <cgzones@googlemail.com>
+Date: Mon, 6 Jan 2020 15:27:23 +0100
+Subject: [PATCH] selinux: add trigger for policy reload to refresh internal
+ selabel cache
+
+Reload the internal selabel cache automatically on SELinux policy reloads so non pid-1 daemons are participating.
+
+Run the reload function `mac_selinux_reload()` not manually on daemon-reload, but rather pass it as callback to libselinux.
+Trigger the callback prior usage of the systemd internal selabel cache by depleting the selinux netlink socket via `avc_netlink_check_nb()`.
+
+Improves: a9dfac21ec85 ("core: reload SELinux label cache on daemon-reload")
+Improves: #13363
+(cherry picked from commit 61f3e897f13101f29fb8027e8839498a469ad58e)
+
+Related: #1888912
+---
+ src/basic/selinux-util.c | 23 +++++++++++++++++++----
+ src/basic/selinux-util.h |  1 -
+ src/core/main.c          |  2 --
+ 3 files changed, 19 insertions(+), 7 deletions(-)
+
+diff --git a/src/basic/selinux-util.c b/src/basic/selinux-util.c
+index a078ce23ef..bfe3d015aa 100644
+--- a/src/basic/selinux-util.c
++++ b/src/basic/selinux-util.c
+@@ -10,6 +10,7 @@
+ #include <syslog.h>
+ 
+ #if HAVE_SELINUX
++#include <selinux/avc.h>
+ #include <selinux/context.h>
+ #include <selinux/label.h>
+ #include <selinux/selinux.h>
+@@ -32,6 +33,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(context_t, context_free);
+ #define _cleanup_freecon_ _cleanup_(freeconp)
+ #define _cleanup_context_free_ _cleanup_(context_freep)
+ 
++static int mac_selinux_reload(int seqno);
++
+ static int cached_use = -1;
+ static struct selabel_handle *label_hnd = NULL;
+ 
+@@ -63,6 +66,8 @@ int mac_selinux_init(void) {
+         usec_t before_timestamp, after_timestamp;
+         struct mallinfo before_mallinfo, after_mallinfo;
+ 
++        selinux_set_callback(SELINUX_CB_POLICYLOAD, (union selinux_callback) mac_selinux_reload);
++
+         if (label_hnd)
+                 return 0;
+ 
+@@ -105,13 +110,12 @@ void mac_selinux_finish(void) {
+ #endif
+ }
+ 
+-void mac_selinux_reload(void) {
+-
+ #if HAVE_SELINUX
++static int mac_selinux_reload(int seqno) {
+         struct selabel_handle *backup_label_hnd;
+ 
+         if (!label_hnd)
+-                return;
++                return 0;
+ 
+         backup_label_hnd = TAKE_PTR(label_hnd);
+ 
+@@ -122,8 +126,10 @@ void mac_selinux_reload(void) {
+                 selabel_close(backup_label_hnd);
+         else
+                 label_hnd = backup_label_hnd;
+-#endif
++
++        return 0;
+ }
++#endif
+ 
+ int mac_selinux_fix(const char *path, LabelFixFlags flags) {
+ 
+@@ -152,6 +158,9 @@ int mac_selinux_fix(const char *path, LabelFixFlags flags) {
+         if (fstat(fd, &st) < 0)
+                 return -errno;
+ 
++        /* Check for policy reload so 'label_hnd' is kept up-to-date by callbacks */
++        (void) avc_netlink_check_nb();
++
+         if (selabel_lookup_raw(label_hnd, &fcon, path, st.st_mode) < 0) {
+                 r = -errno;
+ 
+@@ -345,6 +354,9 @@ static int selinux_create_file_prepare_abspath(const char *abspath, mode_t mode)
+         assert(abspath);
+         assert(path_is_absolute(abspath));
+ 
++        /* Check for policy reload so 'label_hnd' is kept up-to-date by callbacks */
++        (void) avc_netlink_check_nb();
++
+         r = selabel_lookup_raw(label_hnd, &filecon, abspath, mode);
+         if (r < 0) {
+                 /* No context specified by the policy? Proceed without setting it. */
+@@ -496,6 +508,9 @@ int mac_selinux_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) {
+ 
+         path = strndupa(un->sun_path, addrlen - offsetof(struct sockaddr_un, sun_path));
+ 
++        /* Check for policy reload so 'label_hnd' is kept up-to-date by callbacks */
++        (void) avc_netlink_check_nb();
++
+         if (path_is_absolute(path))
+                 r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFSOCK);
+         else {
+diff --git a/src/basic/selinux-util.h b/src/basic/selinux-util.h
+index 639c35b687..bd5207c318 100644
+--- a/src/basic/selinux-util.h
++++ b/src/basic/selinux-util.h
+@@ -13,7 +13,6 @@ void mac_selinux_retest(void);
+ 
+ int mac_selinux_init(void);
+ void mac_selinux_finish(void);
+-void mac_selinux_reload(void);
+ 
+ int mac_selinux_fix(const char *path, LabelFixFlags flags);
+ int mac_selinux_apply(const char *path, const char *label);
+diff --git a/src/core/main.c b/src/core/main.c
+index d5c41da0c4..d897155644 100644
+--- a/src/core/main.c
++++ b/src/core/main.c
+@@ -1682,8 +1682,6 @@ static int invoke_main_loop(
+                         saved_log_level = m->log_level_overridden ? log_get_max_level() : -1;
+                         saved_log_target = m->log_target_overridden ? log_get_target() : _LOG_TARGET_INVALID;
+ 
+-                        mac_selinux_reload();
+-
+                         (void) parse_configuration(saved_rlimit_nofile, saved_rlimit_memlock);
+ 
+                         set_manager_defaults(m);
diff --git a/SOURCES/0506-udev-net_id-give-RHEL-8.4-naming-scheme-a-name.patch b/SOURCES/0506-udev-net_id-give-RHEL-8.4-naming-scheme-a-name.patch
new file mode 100644
index 0000000..0ce9150
--- /dev/null
+++ b/SOURCES/0506-udev-net_id-give-RHEL-8.4-naming-scheme-a-name.patch
@@ -0,0 +1,24 @@
+From fb58a56c6c1c2749ba634abd9ad76f4e718269a1 Mon Sep 17 00:00:00 2001
+From: Michal Sekletar <msekleta@redhat.com>
+Date: Tue, 5 Jan 2021 12:30:15 +0100
+Subject: [PATCH] udev/net_id: give RHEL-8.4 naming scheme a name
+
+Follow-up for bb6114af097da0cd9c5081e42db718559130687f
+
+Related: #1827462
+---
+ src/udev/udev-builtin-net_id.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c
+index d8c56b62bb..7c153f0aef 100644
+--- a/src/udev/udev-builtin-net_id.c
++++ b/src/udev/udev-builtin-net_id.c
+@@ -150,6 +150,7 @@ static const NamingScheme naming_schemes[] = {
+         { "rhel-8.1", NAMING_RHEL_8_1 },
+         { "rhel-8.2", NAMING_RHEL_8_2 },
+         { "rhel-8.3", NAMING_RHEL_8_3 },
++        { "rhel-8.4", NAMING_RHEL_8_4 },
+         /* … add more schemes here, as the logic to name devices is updated … */
+ };
+ 
diff --git a/SOURCES/0507-basic-stat-util-make-mtime-check-stricter-and-use-en.patch b/SOURCES/0507-basic-stat-util-make-mtime-check-stricter-and-use-en.patch
new file mode 100644
index 0000000..e9ac657
--- /dev/null
+++ b/SOURCES/0507-basic-stat-util-make-mtime-check-stricter-and-use-en.patch
@@ -0,0 +1,63 @@
+From 29c5b8dd6228c4401f034ca0aa85f99ac42cf8dd Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= <msekleta@redhat.com>
+Date: Thu, 5 Nov 2020 17:55:25 +0100
+Subject: [PATCH] basic/stat-util: make mtime check stricter and use entire
+ timestamp
+
+Note that st_mtime member of struct stat is defined as follows,
+
+ #define st_mtime st_mtim.tv_sec
+
+Hence we omitted checking nanosecond part of the timestamp (struct
+timespec) and possibly would miss modifications that happened within the
+same second.
+
+(cherry picked from commit a59b0a9f768f6e27b25f4f1bab6de08842e78d74)
+
+Related: #1642728
+---
+ src/basic/stat-util.c | 22 ++++++++++++++++++++++
+ src/basic/stat-util.h |  2 ++
+ 2 files changed, 24 insertions(+)
+
+diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c
+index 26aee9bad6..c61c4c0517 100644
+--- a/src/basic/stat-util.c
++++ b/src/basic/stat-util.c
+@@ -287,3 +287,25 @@ int fd_verify_regular(int fd) {
+ 
+         return stat_verify_regular(&st);
+ }
++
++bool stat_inode_unmodified(const struct stat *a, const struct stat *b) {
++
++        /* Returns if the specified stat structures reference the same, unmodified inode. This check tries to
++         * be reasonably careful when detecting changes: we check both inode and mtime, to cater for file
++         * systems where mtimes are fixed to 0 (think: ostree/nixos type installations). We also check file
++         * size, backing device, inode type and if this refers to a device not the major/minor.
++         *
++         * Note that we don't care if file attributes such as ownership or access mode change, this here is
++         * about contents of the file. The purpose here is to detect file contents changes, and nothing
++         * else. */
++
++        return a && b &&
++                (a->st_mode & S_IFMT) != 0 && /* We use the check for .st_mode if the structure was ever initialized */
++                ((a->st_mode ^ b->st_mode) & S_IFMT) == 0 &&  /* same inode type */
++                a->st_mtim.tv_sec == b->st_mtim.tv_sec &&
++                a->st_mtim.tv_nsec == b->st_mtim.tv_nsec &&
++                (!S_ISREG(a->st_mode) || a->st_size == b->st_size) && /* if regular file, compare file size */
++                a->st_dev == b->st_dev &&
++                a->st_ino == b->st_ino &&
++                (!(S_ISCHR(a->st_mode) || S_ISBLK(a->st_mode)) || a->st_rdev == b->st_rdev); /* if device node, also compare major/minor, because we can */
++}
+\ No newline at end of file
+diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h
+index f8014ed30b..9e1a2b70da 100644
+--- a/src/basic/stat-util.h
++++ b/src/basic/stat-util.h
+@@ -58,3 +58,5 @@ int path_is_temporary_fs(const char *path);
+ 
+ int stat_verify_regular(const struct stat *st);
+ int fd_verify_regular(int fd);
++
++bool stat_inode_unmodified(const struct stat *a, const struct stat *b);
diff --git a/SOURCES/0508-udev-make-algorithm-that-selects-highest-priority-de.patch b/SOURCES/0508-udev-make-algorithm-that-selects-highest-priority-de.patch
new file mode 100644
index 0000000..e20137b
--- /dev/null
+++ b/SOURCES/0508-udev-make-algorithm-that-selects-highest-priority-de.patch
@@ -0,0 +1,457 @@
+From 1d5f966c1758eb620755fcae54abd07a1ac36d3d Mon Sep 17 00:00:00 2001
+From: Michal Sekletar <msekleta@redhat.com>
+Date: Wed, 6 Jan 2021 11:43:50 +0100
+Subject: [PATCH] udev: make algorithm that selects highest priority devlink
+ less susceptible to race conditions
+
+Previously it was very likely, when multiple contenders for the symlink
+appear in parallel, that algorithm would select wrong symlink (i.e. one
+with lower-priority).
+
+Now the algorithm is much more defensive and when we detect change in
+set of contenders for the symlink we reevaluate the selection. Same
+happens when new symlink replaces already existing symlink that points
+to different device node.
+
+Resolves: #1642728
+---
+ src/udev/udev-event.c |  71 +++++++-----
+ src/udev/udev-node.c  | 244 ++++++++++++++++++++++++++++++------------
+ 2 files changed, 216 insertions(+), 99 deletions(-)
+
+diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c
+index fd8406d959..9004634f65 100644
+--- a/src/udev/udev-event.c
++++ b/src/udev/udev-event.c
+@@ -833,6 +833,41 @@ static int rename_netif(struct udev_event *event) {
+         return 0;
+ }
+ 
++static void update_devnode(struct udev_event *event) {
++        struct udev_device *dev = event->dev;
++
++        if (major(udev_device_get_devnum(dev)) > 0) {
++                bool apply;
++
++                /* remove/update possible left-over symlinks from old database entry */
++                if (event->dev_db != NULL)
++                        udev_node_update_old_links(dev, event->dev_db);
++
++                if (!event->owner_set)
++                        event->uid = udev_device_get_devnode_uid(dev);
++
++                if (!event->group_set)
++                        event->gid = udev_device_get_devnode_gid(dev);
++
++                if (!event->mode_set) {
++                        if (udev_device_get_devnode_mode(dev) > 0) {
++                                /* kernel supplied value */
++                                event->mode = udev_device_get_devnode_mode(dev);
++                        } else if (event->gid > 0) {
++                                /* default 0660 if a group is assigned */
++                                event->mode = 0660;
++                        }
++                        else {
++                                /* default 0600 */
++                                event->mode = 0600;
++                        }
++                }
++
++                apply = streq(udev_device_get_action(dev), "add") || event->owner_set || event->group_set || event->mode_set;
++                udev_node_add(dev, apply, event->mode, event->uid, event->gid, &event->seclabel_list);
++        }
++}
++
+ void udev_event_execute_rules(struct udev_event *event,
+                               usec_t timeout_usec, usec_t timeout_warn_usec,
+                               struct udev_list *properties_list,
+@@ -891,35 +926,7 @@ void udev_event_execute_rules(struct udev_event *event,
+                         }
+                 }
+ 
+-                if (major(udev_device_get_devnum(dev)) > 0) {
+-                        bool apply;
+-
+-                        /* remove/update possible left-over symlinks from old database entry */
+-                        if (event->dev_db != NULL)
+-                                udev_node_update_old_links(dev, event->dev_db);
+-
+-                        if (!event->owner_set)
+-                                event->uid = udev_device_get_devnode_uid(dev);
+-
+-                        if (!event->group_set)
+-                                event->gid = udev_device_get_devnode_gid(dev);
+-
+-                        if (!event->mode_set) {
+-                                if (udev_device_get_devnode_mode(dev) > 0) {
+-                                        /* kernel supplied value */
+-                                        event->mode = udev_device_get_devnode_mode(dev);
+-                                } else if (event->gid > 0) {
+-                                        /* default 0660 if a group is assigned */
+-                                        event->mode = 0660;
+-                                } else {
+-                                        /* default 0600 */
+-                                        event->mode = 0600;
+-                                }
+-                        }
+-
+-                        apply = streq(udev_device_get_action(dev), "add") || event->owner_set || event->group_set || event->mode_set;
+-                        udev_node_add(dev, apply, event->mode, event->uid, event->gid, &event->seclabel_list);
+-                }
++                update_devnode(event);
+ 
+                 /* preserve old, or get new initialization timestamp */
+                 udev_device_ensure_usec_initialized(event->dev, event->dev_db);
+@@ -927,6 +934,12 @@ void udev_event_execute_rules(struct udev_event *event,
+                 /* (re)write database file */
+                 udev_device_tag_index(dev, event->dev_db, true);
+                 udev_device_update_db(dev);
++
++                /* Yes, we run update_devnode() twice, because in the first invocation, that is before update of udev database,
++                 * it could happen that two contenders are replacing each other's symlink. Hence we run it again to make sure
++                 * symlinks point to devices that claim them with the highest priority. */
++                update_devnode(event);
++
+                 udev_device_set_is_initialized(dev);
+ 
+                 event->dev_db = udev_device_unref(event->dev_db);
+diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c
+index 333dcae6b9..2eeeccdd3a 100644
+--- a/src/udev/udev-node.c
++++ b/src/udev/udev-node.c
+@@ -13,19 +13,27 @@
+ #include <unistd.h>
+ 
+ #include "device-nodes.h"
++#include "device-private.h"
+ #include "dirent-util.h"
++#include "fd-util.h"
+ #include "format-util.h"
+ #include "fs-util.h"
++#include "sd-device.h"
+ #include "selinux-util.h"
+ #include "smack-util.h"
++#include "stat-util.h"
+ #include "stdio-util.h"
+ #include "string-util.h"
+ #include "udev.h"
++#include "libudev-device-internal.h"
+ 
+-static int node_symlink(struct udev_device *dev, const char *node, const char *slink) {
++#define LINK_UPDATE_MAX_RETRIES 128
++
++static int node_symlink(sd_device *dev, const char *node, const char *slink) {
+         struct stat stats;
+         char target[UTIL_PATH_SIZE];
+         char *s;
++        const char *id_filename;
+         size_t l;
+         char slink_tmp[UTIL_PATH_SIZE + 32];
+         int i = 0;
+@@ -89,7 +97,10 @@ static int node_symlink(struct udev_device *dev, const char *node, const char *s
+         }
+ 
+         log_debug("atomically replace '%s'", slink);
+-        strscpyl(slink_tmp, sizeof(slink_tmp), slink, ".tmp-", udev_device_get_id_filename(dev), NULL);
++        err = device_get_id_filename(dev, &id_filename);
++        if (err < 0)
++                return log_error_errno(err, "Failed to get id_filename: %m");
++        strscpyl(slink_tmp, sizeof(slink_tmp), slink, ".tmp-", id_filename, NULL);
+         unlink(slink_tmp);
+         do {
+                 err = mkdir_parents_label(slink_tmp, 0755);
+@@ -109,104 +120,187 @@ static int node_symlink(struct udev_device *dev, const char *node, const char *s
+         if (err != 0) {
+                 log_error_errno(errno, "rename '%s' '%s' failed: %m", slink_tmp, slink);
+                 unlink(slink_tmp);
+-        }
++        } else
++                /* Tell caller that we replaced already existing symlink. */
++                return 1;
+ exit:
+         return err;
+ }
+ 
+ /* find device node of device with highest priority */
+-static const char *link_find_prioritized(struct udev_device *dev, bool add, const char *stackdir, char *buf, size_t bufsize) {
+-        struct udev *udev = udev_device_get_udev(dev);
+-        DIR *dir;
++static int link_find_prioritized(sd_device *dev, bool add, const char *stackdir, char **ret) {
++        _cleanup_closedir_ DIR *dir = NULL;
++        _cleanup_free_ char *target = NULL;
+         struct dirent *dent;
+-        int priority = 0;
+-        const char *target = NULL;
++        int r, priority = 0;
++
++        assert(!add || dev);
++        assert(stackdir);
++        assert(ret);
+ 
+         if (add) {
+-                priority = udev_device_get_devlink_priority(dev);
+-                strscpy(buf, bufsize, udev_device_get_devnode(dev));
+-                target = buf;
++                const char *devnode;
++
++                r = device_get_devlink_priority(dev, &priority);
++                if (r < 0)
++                        return r;
++
++                r = sd_device_get_devname(dev, &devnode);
++                if (r < 0)
++                        return r;
++
++                target = strdup(devnode);
++                if (!target)
++                        return -ENOMEM;
+         }
+ 
+         dir = opendir(stackdir);
+-        if (dir == NULL)
+-                return target;
++        if (!dir) {
++                if (target) {
++                        *ret = TAKE_PTR(target);
++                        return 0;
++                }
++
++                return -errno;
++        }
++
+         FOREACH_DIRENT_ALL(dent, dir, break) {
+-                struct udev_device *dev_db;
++                _cleanup_(sd_device_unrefp) sd_device *dev_db = NULL;
++                const char *devnode, *id_filename;
++                int db_prio = 0;
+ 
+                 if (dent->d_name[0] == '\0')
+                         break;
+                 if (dent->d_name[0] == '.')
+                         continue;
+ 
+-                log_debug("found '%s' claiming '%s'", dent->d_name, stackdir);
++                log_debug("Found '%s' claiming '%s'", dent->d_name, stackdir);
++
++                if (device_get_id_filename(dev, &id_filename) < 0)
++                        continue;
+ 
+                 /* did we find ourself? */
+-                if (streq(dent->d_name, udev_device_get_id_filename(dev)))
++                if (streq(dent->d_name, id_filename))
+                         continue;
+ 
+-                dev_db = udev_device_new_from_device_id(udev, dent->d_name);
+-                if (dev_db != NULL) {
+-                        const char *devnode;
+-
+-                        devnode = udev_device_get_devnode(dev_db);
+-                        if (devnode != NULL) {
+-                                if (target == NULL || udev_device_get_devlink_priority(dev_db) > priority) {
+-                                        log_debug("'%s' claims priority %i for '%s'",
+-                                                  udev_device_get_syspath(dev_db), udev_device_get_devlink_priority(dev_db), stackdir);
+-                                        priority = udev_device_get_devlink_priority(dev_db);
+-                                        strscpy(buf, bufsize, devnode);
+-                                        target = buf;
+-                                }
+-                        }
+-                        udev_device_unref(dev_db);
++                if (sd_device_new_from_device_id(&dev_db, dent->d_name) < 0)
++                        continue;
++
++                if (sd_device_get_devname(dev_db, &devnode) < 0)
++                        continue;
++
++                if (device_get_devlink_priority(dev_db, &db_prio) < 0)
++                        continue;
++
++                if (target && db_prio <= priority)
++                        continue;
++
++                if (DEBUG_LOGGING) {
++                        const char *syspath = NULL;
++
++                        (void) sd_device_get_syspath(dev_db, &syspath);
++                        log_debug("Device '%s' claims priority %i for '%s'", strnull(syspath), db_prio, stackdir);
+                 }
++
++                r = free_and_strdup(&target, devnode);
++                if (r < 0)
++                        return r;
++                priority = db_prio;
+         }
+-        closedir(dir);
+-        return target;
++
++        if (!target)
++                return -ENOENT;
++
++        *ret = TAKE_PTR(target);
++        return 0;
+ }
+ 
++
+ /* manage "stack of names" with possibly specified device priorities */
+-static void link_update(struct udev_device *dev, const char *slink, bool add) {
+-        char name_enc[UTIL_PATH_SIZE];
+-        char filename[UTIL_PATH_SIZE * 2];
+-        char dirname[UTIL_PATH_SIZE];
+-        const char *target;
+-        char buf[UTIL_PATH_SIZE];
++static int link_update(sd_device *dev, const char *slink, bool add) {
++        _cleanup_free_ char *filename = NULL, *dirname = NULL;
++        char name_enc[PATH_MAX];
++        const char *id_filename;
++        int i, r, retries;
++
++        assert(dev);
++        assert(slink);
++
++        r = device_get_id_filename(dev, &id_filename);
++        if (r < 0)
++                return log_debug_errno(r, "Failed to get id_filename: %m");
+ 
+         util_path_encode(slink + STRLEN("/dev"), name_enc, sizeof(name_enc));
+-        strscpyl(dirname, sizeof(dirname), "/run/udev/links/", name_enc, NULL);
+-        strscpyl(filename, sizeof(filename), dirname, "/", udev_device_get_id_filename(dev), NULL);
++        dirname = path_join(NULL, "/run/udev/links/", name_enc);
++        if (!dirname)
++                return log_oom();
++        filename = path_join(NULL, dirname, id_filename);
++        if (!filename)
++                return log_oom();
++
++        if (!add) {
++                if (unlink(filename) == 0)
++                        (void) rmdir(dirname);
++        } else
++                for (;;) {
++                        _cleanup_close_ int fd = -1;
++
++                        r = mkdir_parents(filename, 0755);
++                        if (!IN_SET(r, 0, -ENOENT))
++                                return r;
+ 
+-        if (!add && unlink(filename) == 0)
+-                rmdir(dirname);
++                        fd = open(filename, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC|O_NOFOLLOW, 0444);
++                        if (fd >= 0)
++                                break;
++                        if (errno != ENOENT)
++                                return -errno;
++                }
+ 
+-        target = link_find_prioritized(dev, add, dirname, buf, sizeof(buf));
+-        if (target == NULL) {
+-                log_debug("no reference left, remove '%s'", slink);
+-                if (unlink(slink) == 0)
+-                        rmdir_parents(slink, "/");
+-        } else {
+-                log_debug("creating link '%s' to '%s'", slink, target);
+-                node_symlink(dev, target, slink);
+-        }
++        /* If the database entry is not written yet we will just do one iteration and possibly wrong symlink
++         * will be fixed in the second invocation. */
++        (void) sd_device_get_is_initialized(dev, &r);
++        retries = r > 0 ? LINK_UPDATE_MAX_RETRIES : 1;
+ 
+-        if (add) {
+-                int err;
++        for (i = 0; i < retries; i++) {
++                _cleanup_free_ char *target = NULL;
++                struct stat st1 = {}, st2 = {};
+ 
+-                do {
+-                        int fd;
++                r = stat(dirname, &st1);
++                if (r < 0 && errno != ENOENT)
++                        return -errno;
+ 
+-                        err = mkdir_parents(filename, 0755);
+-                        if (!IN_SET(err, 0, -ENOENT))
++                r = link_find_prioritized(dev, add, dirname, &target);
++                if (r == -ENOENT) {
++                        log_debug("No reference left, removing '%s'", slink);
++                        if (unlink(slink) == 0)
++                                (void) rmdir_parents(slink, "/");
++
++                        break;
++                } else if (r < 0)
++                        return log_error_errno(r, "Failed to determine highest priority symlink: %m");
++
++                r = node_symlink(dev, target, slink);
++                if (r < 0) {
++                        (void) unlink(filename);
++                        break;
++                } else if (r == 1)
++                        /* We have replaced already existing symlink, possibly there is some other device trying
++                         * to claim the same symlink. Let's do one more iteration to give us a chance to fix
++                         * the error if other device actually claims the symlink with higher priority. */
++                        continue;
++
++               /* Skip the second stat() if the first failed, stat_inode_unmodified() would return false regardless. */
++                if ((st1.st_mode & S_IFMT) != 0) {
++                        r = stat(dirname, &st2);
++                        if (r < 0 && errno != ENOENT)
++                                return -errno;
++
++                        if (stat_inode_unmodified(&st1, &st2))
+                                 break;
+-                        fd = open(filename, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC|O_NOFOLLOW, 0444);
+-                        if (fd >= 0)
+-                                close(fd);
+-                        else
+-                                err = -errno;
+-                } while (err == -ENOENT);
++                }
+         }
++
++        return i < LINK_UPDATE_MAX_RETRIES ? 0 : -ELOOP;
+ }
+ 
+ void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old) {
+@@ -233,7 +327,7 @@ void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev
+ 
+                 log_debug("update old name, '%s' no longer belonging to '%s'",
+                      name, udev_device_get_devpath(dev));
+-                link_update(dev, name, false);
++                link_update(dev->device, name, false);
+         }
+ }
+ 
+@@ -338,11 +432,16 @@ void udev_node_add(struct udev_device *dev, bool apply,
+         xsprintf_dev_num_path(filename,
+                               streq(udev_device_get_subsystem(dev), "block") ? "block" : "char",
+                               udev_device_get_devnum(dev));
+-        node_symlink(dev, udev_device_get_devnode(dev), filename);
++        node_symlink(dev->device, udev_device_get_devnode(dev), filename);
+ 
+         /* create/update symlinks, add symlinks to name index */
+-        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev))
+-                        link_update(dev, udev_list_entry_get_name(list_entry), true);
++        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) {
++                int r;
++
++                r = link_update(dev->device, udev_list_entry_get_name(list_entry), true);
++                if (r < 0)
++                        log_info_errno(r, "Failed to update device symlinks: %m");
++        }
+ }
+ 
+ void udev_node_remove(struct udev_device *dev) {
+@@ -350,8 +449,13 @@ void udev_node_remove(struct udev_device *dev) {
+         char filename[DEV_NUM_PATH_MAX];
+ 
+         /* remove/update symlinks, remove symlinks from name index */
+-        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev))
+-                link_update(dev, udev_list_entry_get_name(list_entry), false);
++        udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) {
++                int r;
++
++                r = link_update(dev->device, udev_list_entry_get_name(list_entry), false);
++                if (r < 0)
++                        log_info_errno(r, "Failed to update device symlinks: %m");
++        }
+ 
+         /* remove /dev/{block,char}/$major:$minor */
+         xsprintf_dev_num_path(filename,
diff --git a/SOURCES/0509-test-create-dev-null-in-test-udev.pl.patch b/SOURCES/0509-test-create-dev-null-in-test-udev.pl.patch
new file mode 100644
index 0000000..4dccdbc
--- /dev/null
+++ b/SOURCES/0509-test-create-dev-null-in-test-udev.pl.patch
@@ -0,0 +1,32 @@
+From 6a908a38135d050b7c271fdea9c061d7e7ad8ef7 Mon Sep 17 00:00:00 2001
+From: Yu Watanabe <watanabe.yu+github@gmail.com>
+Date: Tue, 23 Oct 2018 07:23:01 +0900
+Subject: [PATCH] test: create /dev/null in test-udev.pl
+
+(cherry picked from commit a41ff38b0999fb83464309a29b8f39450b8d4b85)
+
+Related: #1642728
+---
+ test/udev-test.pl | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 0433629c7c..a1c24f49b4 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1537,13 +1537,14 @@ sub udev_setup {
+         system("umount", $udev_tmpfs);
+         rmdir($udev_tmpfs);
+         mkdir($udev_tmpfs) || die "unable to create udev_tmpfs: $udev_tmpfs\n";
+-        system("mount", "-o", "rw,mode=755,nosuid,noexec,nodev", "-t", "tmpfs", "tmpfs", $udev_tmpfs) && die "unable to mount tmpfs";
++        system("mount", "-o", "rw,mode=755,nosuid,noexec", "-t", "tmpfs", "tmpfs", $udev_tmpfs) && die "unable to mount tmpfs";
+ 
+         mkdir($udev_dev) || die "unable to create udev_dev: $udev_dev\n";
+         # setting group and mode of udev_dev ensures the tests work
+         # even if the parent directory has setgid bit enabled.
+         chown (0, 0, $udev_dev) || die "unable to chown $udev_dev\n";
+         chmod (0755, $udev_dev) || die "unable to chmod $udev_dev\n";
++        system("mknod", $udev_dev . "/null", "c", "1", "3") && "unable to create $udev_dev/null";
+ 
+         system("cp", "-r", "test/sys/", $udev_sys) && die "unable to copy test/sys";
+ 
diff --git a/SOURCES/0510-test-missing-die.patch b/SOURCES/0510-test-missing-die.patch
new file mode 100644
index 0000000..959a689
--- /dev/null
+++ b/SOURCES/0510-test-missing-die.patch
@@ -0,0 +1,27 @@
+From 70bf708d5360372aa541e25ff512609230781dd6 Mon Sep 17 00:00:00 2001
+From: Yu Watanabe <watanabe.yu+github@gmail.com>
+Date: Wed, 7 Nov 2018 14:56:20 +0900
+Subject: [PATCH] test: missing "die"
+
+Follow-up for a41ff38b0999fb83464309a29b8f39450b8d4b85.
+
+(cherry picked from commit 11d93952ea806de2b6e9fb381153115cccc7b5e8)
+
+Related: #1642728
+---
+ test/udev-test.pl | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index a1c24f49b4..61bd3d703a 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1544,7 +1544,7 @@ sub udev_setup {
+         # even if the parent directory has setgid bit enabled.
+         chown (0, 0, $udev_dev) || die "unable to chown $udev_dev\n";
+         chmod (0755, $udev_dev) || die "unable to chmod $udev_dev\n";
+-        system("mknod", $udev_dev . "/null", "c", "1", "3") && "unable to create $udev_dev/null";
++        system("mknod", $udev_dev . "/null", "c", "1", "3") && die "unable to create $udev_dev/null";
+ 
+         system("cp", "-r", "test/sys/", $udev_sys) && die "unable to copy test/sys";
+ 
diff --git a/SOURCES/0511-udev-test-remove-a-check-for-whether-the-test-is-run.patch b/SOURCES/0511-udev-test-remove-a-check-for-whether-the-test-is-run.patch
new file mode 100644
index 0000000..eeadbda
--- /dev/null
+++ b/SOURCES/0511-udev-test-remove-a-check-for-whether-the-test-is-run.patch
@@ -0,0 +1,33 @@
+From 1b133f2ca15f0a15b05407b2c04521d7de88dfa2 Mon Sep 17 00:00:00 2001
+From: Evgeny Vereshchagin <evvers@ya.ru>
+Date: Fri, 9 Nov 2018 03:14:04 +0100
+Subject: [PATCH] udev-test: remove a check for whether the test is run in a
+ container
+
+It's too broad a check that prevents the test from running on Travis CI.
+
+(cherry picked from commit 881886ef08d50951159633248b0f73977c5d6924)
+
+Related: #1642728
+---
+ test/udev-test.pl | 7 -------
+ 1 file changed, 7 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 61bd3d703a..05b3e17188 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1646,13 +1646,6 @@ if ($? >> 8 == 0) {
+         exit($EXIT_TEST_SKIP);
+ }
+ 
+-# skip the test when running in a container
+-system("systemd-detect-virt", "-c", "-q");
+-if ($? >> 8 == 0) {
+-        print "Running in a container, skipping the test.\n";
+-        exit($EXIT_TEST_SKIP);
+-}
+-
+ udev_setup();
+ 
+ my $test_num = 1;
diff --git a/SOURCES/0512-udev-test-skip-the-test-only-if-it-can-t-setup-its-e.patch b/SOURCES/0512-udev-test-skip-the-test-only-if-it-can-t-setup-its-e.patch
new file mode 100644
index 0000000..37970ab
--- /dev/null
+++ b/SOURCES/0512-udev-test-skip-the-test-only-if-it-can-t-setup-its-e.patch
@@ -0,0 +1,94 @@
+From 8c82f3a4aa2d029dcc303cbf95a71194aa5ac9c3 Mon Sep 17 00:00:00 2001
+From: Evgeny Vereshchagin <evvers@ya.ru>
+Date: Fri, 9 Nov 2018 04:01:15 +0100
+Subject: [PATCH] udev-test: skip the test only if it can't setup its
+ environment
+
+This is basically a replacement for 0eb3cc88504b5d8f74.
+
+(cherry picked from commit 110a13202eab6d92678abcde08372d4afac1cc45)
+
+Related: #1642728
+---
+ src/test/test-udev.c |  8 ++++++++
+ test/udev-test.pl    | 24 +++++++++++++++++++++---
+ 2 files changed, 29 insertions(+), 3 deletions(-)
+
+diff --git a/src/test/test-udev.c b/src/test/test-udev.c
+index bed51c1270..f098fab721 100644
+--- a/src/test/test-udev.c
++++ b/src/test/test-udev.c
+@@ -65,6 +65,11 @@ int main(int argc, char *argv[]) {
+         log_parse_environment();
+         log_open();
+ 
++        if (!IN_SET(argc, 2, 3)) {
++                log_error("This program needs one or two arguments, %d given", argc - 1);
++                return EXIT_FAILURE;
++        }
++
+         err = fake_filesystems();
+         if (err < 0)
+                 return EXIT_FAILURE;
+@@ -73,6 +78,9 @@ int main(int argc, char *argv[]) {
+         if (udev == NULL)
+                 return EXIT_FAILURE;
+ 
++        if (argc == 2)
++                return EXIT_SUCCESS;
++
+         log_debug("version %s", PACKAGE_VERSION);
+         mac_selinux_init();
+ 
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 05b3e17188..aa38bae0b1 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1537,18 +1537,28 @@ sub udev_setup {
+         system("umount", $udev_tmpfs);
+         rmdir($udev_tmpfs);
+         mkdir($udev_tmpfs) || die "unable to create udev_tmpfs: $udev_tmpfs\n";
+-        system("mount", "-o", "rw,mode=755,nosuid,noexec", "-t", "tmpfs", "tmpfs", $udev_tmpfs) && die "unable to mount tmpfs";
++
++        if (system("mount", "-o", "rw,mode=755,nosuid,noexec", "-t", "tmpfs", "tmpfs", $udev_tmpfs)) {
++                warn "unable to mount tmpfs";
++                return 0;
++        }
+ 
+         mkdir($udev_dev) || die "unable to create udev_dev: $udev_dev\n";
+         # setting group and mode of udev_dev ensures the tests work
+         # even if the parent directory has setgid bit enabled.
+         chown (0, 0, $udev_dev) || die "unable to chown $udev_dev\n";
+         chmod (0755, $udev_dev) || die "unable to chmod $udev_dev\n";
+-        system("mknod", $udev_dev . "/null", "c", "1", "3") && die "unable to create $udev_dev/null";
++
++        if (system("mknod", $udev_dev . "/null", "c", "1", "3")) {
++                warn "unable to create $udev_dev/null";
++                return 0;
++        }
+ 
+         system("cp", "-r", "test/sys/", $udev_sys) && die "unable to copy test/sys";
+ 
+         system("rm", "-rf", "$udev_run");
++
++        return 1;
+ }
+ 
+ sub run_test {
+@@ -1646,7 +1656,15 @@ if ($? >> 8 == 0) {
+         exit($EXIT_TEST_SKIP);
+ }
+ 
+-udev_setup();
++if (!udev_setup()) {
++        warn "Failed to set up the environment, skipping the test";
++        exit($EXIT_TEST_SKIP);
++}
++
++if (!system($udev_bin, "check")) {
++        warn "$udev_bin failed to set up the environment, skipping the test";
++        exit($EXIT_TEST_SKIP);
++}
+ 
+ my $test_num = 1;
+ my @list;
diff --git a/SOURCES/0513-udev-test-fix-test-skip-condition.patch b/SOURCES/0513-udev-test-fix-test-skip-condition.patch
new file mode 100644
index 0000000..165aadb
--- /dev/null
+++ b/SOURCES/0513-udev-test-fix-test-skip-condition.patch
@@ -0,0 +1,33 @@
+From f44fcdde656036f0388fc8244b8960c1873a3a08 Mon Sep 17 00:00:00 2001
+From: Alexey Bogdanenko <alexey@bogdanenko.com>
+Date: Sat, 8 Dec 2018 11:02:30 +0300
+Subject: [PATCH] udev-test: fix test skip condition
+
+When there is a failure to setup the environment, the following happens:
+
+1. Command "./test-udev check" exits with non-zero code.
+2. Perl function "system" returns the code.
+3. The code is evaluated as true by Perl.
+
+Then we stop the test.
+
+(cherry picked from commit 7935dae547caf164d807237f1009a9e9fa510337)
+
+Related: #1642728
+---
+ test/udev-test.pl | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index aa38bae0b1..3517feab15 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1661,7 +1661,7 @@ if (!udev_setup()) {
+         exit($EXIT_TEST_SKIP);
+ }
+ 
+-if (!system($udev_bin, "check")) {
++if (system($udev_bin, "check")) {
+         warn "$udev_bin failed to set up the environment, skipping the test";
+         exit($EXIT_TEST_SKIP);
+ }
diff --git a/SOURCES/0514-udev-test-fix-missing-directory-test-run.patch b/SOURCES/0514-udev-test-fix-missing-directory-test-run.patch
new file mode 100644
index 0000000..5714a16
--- /dev/null
+++ b/SOURCES/0514-udev-test-fix-missing-directory-test-run.patch
@@ -0,0 +1,35 @@
+From 974431a70775d5127cd973c4b4705d2cf8884011 Mon Sep 17 00:00:00 2001
+From: Alexey Bogdanenko <alexey@bogdanenko.com>
+Date: Sat, 8 Dec 2018 15:35:30 +0300
+Subject: [PATCH] udev-test: fix missing directory test/run
+
+Fixes the following error:
+
+    Failed to mount test /run: No such file or directory
+
+By the time command "./test-udev check" calls function "fake_filesystems",
+directory "test/run" must be present.
+
+(cherry picked from commit 1e5548c0e0962424b6ca5fdfd35c866b70760c8f)
+
+Related: #1642728
+---
+ test/udev-test.pl | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 3517feab15..eb76ebd72e 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1558,6 +1558,11 @@ sub udev_setup {
+ 
+         system("rm", "-rf", "$udev_run");
+ 
++        if (!mkdir($udev_run)) {
++                warn "unable to create directory $udev_run";
++                return 0;
++        }
++
+         return 1;
+ }
+ 
diff --git a/SOURCES/0515-udev-test-check-if-permitted-to-create-block-device-.patch b/SOURCES/0515-udev-test-check-if-permitted-to-create-block-device-.patch
new file mode 100644
index 0000000..67037a2
--- /dev/null
+++ b/SOURCES/0515-udev-test-check-if-permitted-to-create-block-device-.patch
@@ -0,0 +1,31 @@
+From 57e9ee0f19098d56995955f6692437affdf94041 Mon Sep 17 00:00:00 2001
+From: Alexey Bogdanenko <alexey@bogdanenko.com>
+Date: Tue, 11 Dec 2018 16:55:34 +0300
+Subject: [PATCH] udev-test: check if permitted to create block device nodes
+
+(cherry picked from commit dbfbc6c4e34366033cb340e8b0c3cbca683ff6f5)
+
+Related: #1642728
+---
+ test/udev-test.pl | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index eb76ebd72e..957cda541c 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1554,6 +1554,14 @@ sub udev_setup {
+                 return 0;
+         }
+ 
++        # check if we are permitted to create block device nodes
++        my $block_device_filename = $udev_dev . "/sda";
++        if (system("mknod", $block_device_filename, "b", "8", "0")) {
++                warn "unable to create $block_device_filename";
++                return 0;
++        }
++        unlink $block_device_filename;
++
+         system("cp", "-r", "test/sys/", $udev_sys) && die "unable to copy test/sys";
+ 
+         system("rm", "-rf", "$udev_run");
diff --git a/SOURCES/0516-test-udev-add-a-testcase-of-too-long-line.patch b/SOURCES/0516-test-udev-add-a-testcase-of-too-long-line.patch
new file mode 100644
index 0000000..7f712d8
--- /dev/null
+++ b/SOURCES/0516-test-udev-add-a-testcase-of-too-long-line.patch
@@ -0,0 +1,45 @@
+From 527d43064a93fae9a4490e5d152b120e91f5eade Mon Sep 17 00:00:00 2001
+From: Yu Watanabe <watanabe.yu+github@gmail.com>
+Date: Mon, 18 Feb 2019 10:38:29 +0900
+Subject: [PATCH] test-udev: add a testcase of too long line
+
+(cherry picked from commit 1e797cf596df50a6bdd8cbf8e9b2467a3a934171)
+
+Related: #1642728
+---
+ test/udev-test.pl | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 957cda541c..3a50694fa9 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -39,6 +39,11 @@ for (my $i = 1; $i <= 10000; ++$i) {
+         $rules_10k_tags .= 'KERNEL=="sda", TAG+="test' . $i . "\"\n";
+ }
+ 
++my $rules_10k_tags_continuation = "";
++for (my $i = 1; $i <= 10000; ++$i) {
++        $rules_10k_tags_continuation .= 'KERNEL=="sda", TAG+="test' . $i . "\"\\\n";
++}
++
+ my @tests = (
+         {
+                 desc            => "no rules",
+@@ -1444,6 +1449,16 @@ EOF
+                 exp_name        => "found",
+                 rules           => $rules_10k_tags . <<EOF
+ TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="found"
++EOF
++        },
++        {
++                desc            => "don't crash with lots of tags with continuation",
++                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                exp_name        => "found",
++                not_exp_name    => "bad" ,
++                rules           => $rules_10k_tags_continuation . <<EOF
++TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="bad"
++KERNEL=="sda", SYMLINK+="found"
+ EOF
+         },
+ );
diff --git a/SOURCES/0517-test-udev-use-proper-semantics-for-too-long-line-wit.patch b/SOURCES/0517-test-udev-use-proper-semantics-for-too-long-line-wit.patch
new file mode 100644
index 0000000..493d6b9
--- /dev/null
+++ b/SOURCES/0517-test-udev-use-proper-semantics-for-too-long-line-wit.patch
@@ -0,0 +1,34 @@
+From 4fb6b699b3d69341093830e92838336c0dbd7ea9 Mon Sep 17 00:00:00 2001
+From: Yu Watanabe <watanabe.yu+github@gmail.com>
+Date: Tue, 19 Feb 2019 09:21:42 +0900
+Subject: [PATCH] test-udev: use proper semantics for too long line with
+ continuation
+
+Follow-up for 1e797cf596df50a6bdd8cbf8e9b2467a3a934171.
+
+(cherry picked from commit e37a5d90b0c624b95f8d0c3400288fec60417ec4)
+
+Related: #1642728
+---
+ test/udev-test.pl | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 3a50694fa9..58b5dc85e1 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -39,10 +39,11 @@ for (my $i = 1; $i <= 10000; ++$i) {
+         $rules_10k_tags .= 'KERNEL=="sda", TAG+="test' . $i . "\"\n";
+ }
+ 
+-my $rules_10k_tags_continuation = "";
+-for (my $i = 1; $i <= 10000; ++$i) {
+-        $rules_10k_tags_continuation .= 'KERNEL=="sda", TAG+="test' . $i . "\"\\\n";
++my $rules_10k_tags_continuation = "KERNEL==\"sda\", \\\n";
++for (my $i = 1; $i < 10000; ++$i) {
++        $rules_10k_tags_continuation .= 'TAG+="test' . $i . "\",\\\n";
+ }
++$rules_10k_tags_continuation .= "TAG+=\"test10000\"\\n";
+ 
+ my @tests = (
+         {
diff --git a/SOURCES/0518-test-udev-add-more-tests-for-line-continuations-and-.patch b/SOURCES/0518-test-udev-add-more-tests-for-line-continuations-and-.patch
new file mode 100644
index 0000000..9689d81
--- /dev/null
+++ b/SOURCES/0518-test-udev-add-more-tests-for-line-continuations-and-.patch
@@ -0,0 +1,40 @@
+From 66c41fbbeb472563993724352b1984aa3e7e47db Mon Sep 17 00:00:00 2001
+From: Yu Watanabe <watanabe.yu+github@gmail.com>
+Date: Tue, 19 Feb 2019 09:22:45 +0900
+Subject: [PATCH] test-udev: add more tests for line continuations and comments
+
+(cherry picked from commit d35976c670b0e5c2d4081b781e5af88c0689ff00)
+
+Related: #1642728
+---
+ test/udev-test.pl | 12 ++++++++++--
+ 1 file changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 58b5dc85e1..a5e1f8cda3 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1453,13 +1453,21 @@ TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test1
+ EOF
+         },
+         {
+-                desc            => "don't crash with lots of tags with continuation",
++                desc            => "continuations",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                 exp_name        => "found",
+                 not_exp_name    => "bad" ,
+                 rules           => $rules_10k_tags_continuation . <<EOF
+ TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="bad"
+-KERNEL=="sda", SYMLINK+="found"
++KERNEL=="sda",\\
++# comment in continuation
++TAG+="hoge1",\\
++  # space before comment
++TAG+="hoge2",\\
++# spaces before and after token are dropped
++  TAG+="hoge3",   \\
++TAG+="hoge4"
++TAGS=="hoge1", TAGS=="hoge2", TAGS=="hoge3", TAGS=="hoge4", SYMLINK+="found"
+ EOF
+         },
+ );
diff --git a/SOURCES/0519-test-udev-add-more-tests-for-line-continuation.patch b/SOURCES/0519-test-udev-add-more-tests-for-line-continuation.patch
new file mode 100644
index 0000000..82482c3
--- /dev/null
+++ b/SOURCES/0519-test-udev-add-more-tests-for-line-continuation.patch
@@ -0,0 +1,59 @@
+From ac0def8fb2b51a17b7ef256c5c0edf786fffff2a Mon Sep 17 00:00:00 2001
+From: Yu Watanabe <watanabe.yu+github@gmail.com>
+Date: Thu, 21 Feb 2019 18:03:32 +0900
+Subject: [PATCH] test-udev: add more tests for line continuation
+
+(cherry picked from commit 84a0819c9d89a2ddb195a5d975ae1fd5c62fde3c)
+
+Related: #1642728
+---
+ test/udev-test.pl | 34 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 34 insertions(+)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index a5e1f8cda3..002fabd9fd 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1466,8 +1466,42 @@ TAG+="hoge1",\\
+ TAG+="hoge2",\\
+ # spaces before and after token are dropped
+   TAG+="hoge3",   \\
++\\
++ \\
+ TAG+="hoge4"
+ TAGS=="hoge1", TAGS=="hoge2", TAGS=="hoge3", TAGS=="hoge4", SYMLINK+="found"
++EOF
++        },
++        {
++                desc            => "continuations with empty line",
++                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                exp_name        => "found",
++                not_exp_name    => "bad",
++                rules           => <<EOF
++# empty line finishes continuation
++KERNEL=="sda", TAG+="foo" \\
++
++KERNEL=="sdb", TAG+="hoge"
++KERNEL=="sda", TAG+="aaa" \\
++KERNEL=="sdb", TAG+="bbb"
++TAGS=="foo", SYMLINK+="found"
++TAGS=="aaa", SYMLINK+="bad"
++EOF
++        },
++        {
++                desc            => "continuations with white only line",
++                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                exp_name        => "found",
++                not_exp_name    => "bad",
++                rules           => <<EOF
++# space only line finishes continuation
++KERNEL=="sda", TAG+="foo" \\
++   \t
++KERNEL=="sdb", TAG+="hoge"
++KERNEL=="sda", TAG+="aaa" \\
++KERNEL=="sdb", TAG+="bbb"
++TAGS=="foo", SYMLINK+="found"
++TAGS=="aaa", SYMLINK+="bad"
+ EOF
+         },
+ );
diff --git a/SOURCES/0520-test-udev-fix-alignment-and-drop-unnecessary-white-s.patch b/SOURCES/0520-test-udev-fix-alignment-and-drop-unnecessary-white-s.patch
new file mode 100644
index 0000000..e17bb97
--- /dev/null
+++ b/SOURCES/0520-test-udev-fix-alignment-and-drop-unnecessary-white-s.patch
@@ -0,0 +1,501 @@
+From 7898cd7e75f40627651cec134e3ac3a80176759a Mon Sep 17 00:00:00 2001
+From: Yu Watanabe <watanabe.yu+github@gmail.com>
+Date: Thu, 21 Feb 2019 18:04:12 +0900
+Subject: [PATCH] test-udev: fix alignment and drop unnecessary white spaces
+
+(cherry picked from commit 3dd2d524141d09d57443ae339e1a77d7ce40f847)
+
+Related: #1642728
+---
+ test/udev-test.pl | 114 +++++++++++++++++++++++-----------------------
+ 1 file changed, 57 insertions(+), 57 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 002fabd9fd..122359e377 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -49,7 +49,7 @@ my @tests = (
+         {
+                 desc            => "no rules",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "sda" ,
++                exp_name        => "sda",
+                 exp_rem_error   => "yes",
+                 rules           => <<EOF
+ #
+@@ -58,7 +58,7 @@ EOF
+         {
+                 desc            => "label test of scsi disc",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "boot_disk" ,
++                exp_name        => "boot_disk",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -67,7 +67,7 @@ EOF
+         {
+                 desc            => "label test of scsi disc",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "boot_disk" ,
++                exp_name        => "boot_disk",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -76,7 +76,7 @@ EOF
+         {
+                 desc            => "label test of scsi disc",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "boot_disk" ,
++                exp_name        => "boot_disk",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -85,7 +85,7 @@ EOF
+         {
+                 desc            => "label test of scsi partition",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "boot_disk1" ,
++                exp_name        => "boot_disk1",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+ EOF
+@@ -93,7 +93,7 @@ EOF
+         {
+                 desc            => "label test of pattern match",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "boot_disk1" ,
++                exp_name        => "boot_disk1",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="?ATA", SYMLINK+="boot_disk%n-1"
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA?", SYMLINK+="boot_disk%n-2"
+@@ -104,7 +104,7 @@ EOF
+         {
+                 desc            => "label test of multiple sysfs files",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "boot_disk1" ,
++                exp_name        => "boot_disk1",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS X ", SYMLINK+="boot_diskX%n"
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="boot_disk%n"
+@@ -113,7 +113,7 @@ EOF
+         {
+                 desc            => "label test of max sysfs files (skip invalid rule)",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "boot_disk1" ,
++                exp_name        => "boot_disk1",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="32", SYMLINK+="boot_diskXX%n"
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n"
+@@ -122,7 +122,7 @@ EOF
+         {
+                 desc            => "catch device by *",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem/0" ,
++                exp_name        => "modem/0",
+                 rules           => <<EOF
+ KERNEL=="ttyACM*", SYMLINK+="modem/%n"
+ EOF
+@@ -130,7 +130,7 @@ EOF
+         {
+                 desc            => "catch device by * - take 2",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem/0" ,
++                exp_name        => "modem/0",
+                 rules           => <<EOF
+ KERNEL=="*ACM1", SYMLINK+="bad"
+ KERNEL=="*ACM0", SYMLINK+="modem/%n"
+@@ -139,7 +139,7 @@ EOF
+         {
+                 desc            => "catch device by ?",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem/0" ,
++                exp_name        => "modem/0",
+                 rules           => <<EOF
+ KERNEL=="ttyACM??*", SYMLINK+="modem/%n-1"
+ KERNEL=="ttyACM??", SYMLINK+="modem/%n-2"
+@@ -149,7 +149,7 @@ EOF
+         {
+                 desc            => "catch device by character class",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem/0" ,
++                exp_name        => "modem/0",
+                 rules           => <<EOF
+ KERNEL=="ttyACM[A-Z]*", SYMLINK+="modem/%n-1"
+ KERNEL=="ttyACM?[0-9]", SYMLINK+="modem/%n-2"
+@@ -159,7 +159,7 @@ EOF
+         {
+                 desc            => "replace kernel name",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem" ,
++                exp_name        => "modem",
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+ EOF
+@@ -167,7 +167,7 @@ EOF
+         {
+                 desc            => "Handle comment lines in config file (and replace kernel name)",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem" ,
++                exp_name        => "modem",
+                 rules           => <<EOF
+ # this is a comment
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -177,7 +177,7 @@ EOF
+         {
+                 desc            => "Handle comment lines in config file with whitespace (and replace kernel name)",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem" ,
++                exp_name        => "modem",
+                 rules           => <<EOF
+  # this is a comment with whitespace before the comment
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -187,7 +187,7 @@ EOF
+         {
+                 desc            => "Handle whitespace only lines (and replace kernel name)",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "whitespace" ,
++                exp_name        => "whitespace",
+                 rules           => <<EOF
+ 
+ 
+@@ -202,7 +202,7 @@ EOF
+         {
+                 desc            => "Handle empty lines in config file (and replace kernel name)",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem" ,
++                exp_name        => "modem",
+                 rules           => <<EOF
+ 
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -212,7 +212,7 @@ EOF
+         {
+                 desc            => "Handle backslashed multi lines in config file (and replace kernel name)",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem" ,
++                exp_name        => "modem",
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", \\
+ SYMLINK+="modem"
+@@ -230,7 +230,7 @@ EOF
+         {
+                 desc            => "Handle stupid backslashed multi lines in config file (and replace kernel name)",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem" ,
++                exp_name        => "modem",
+                 rules           => <<EOF
+ 
+ #
+@@ -248,7 +248,7 @@ EOF
+         {
+                 desc            => "subdirectory handling",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "sub/direct/ory/modem" ,
++                exp_name        => "sub/direct/ory/modem",
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", SYMLINK+="sub/direct/ory/modem"
+ EOF
+@@ -256,7 +256,7 @@ EOF
+         {
+                 desc            => "parent device name match of scsi partition",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "first_disk5" ,
++                exp_name        => "first_disk5",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="first_disk%n"
+ EOF
+@@ -264,7 +264,7 @@ EOF
+         {
+                 desc            => "test substitution chars",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "Major:8:minor:5:kernelnumber:5:id:0:0:0:0" ,
++                exp_name        => "Major:8:minor:5:kernelnumber:5:id:0:0:0:0",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:%M:minor:%m:kernelnumber:%n:id:%b"
+ EOF
+@@ -281,7 +281,7 @@ EOF
+         {
+                 desc            => "sustitution of sysfs value (%s{file})",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "disk-ATA-sda" ,
++                exp_name        => "disk-ATA-sda",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="disk-%s{vendor}-%k"
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -290,8 +290,8 @@ EOF
+         {
+                 desc            => "program result substitution",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "special-device-5" ,
+-                not_exp_name    => "not" ,
++                exp_name        => "special-device-5",
++                not_exp_name    => "not",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="-special-*", SYMLINK+="not"
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="special-*", SYMLINK+="%c-%n"
+@@ -300,7 +300,7 @@ EOF
+         {
+                 desc            => "program result substitution (newline removal)",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "newline_removed" ,
++                exp_name        => "newline_removed",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo test", RESULT=="test", SYMLINK+="newline_removed"
+ EOF
+@@ -308,7 +308,7 @@ EOF
+         {
+                 desc            => "program result substitution",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "test-0:0:0:0" ,
++                exp_name        => "test-0:0:0:0",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n test-%b", RESULT=="test-0:0*", SYMLINK+="%c"
+ EOF
+@@ -316,7 +316,7 @@ EOF
+         {
+                 desc            => "program with lots of arguments",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "foo9" ,
++                exp_name        => "foo9",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="%c{7}"
+ EOF
+@@ -324,7 +324,7 @@ EOF
+         {
+                 desc            => "program with subshell",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "bar9" ,
++                exp_name        => "bar9",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'echo foo3 foo4 foo5 foo6 foo7 foo8 foo9 | sed  s/foo9/bar9/'", KERNEL=="sda5", SYMLINK+="%c{7}"
+ EOF
+@@ -332,7 +332,7 @@ EOF
+         {
+                 desc            => "program arguments combined with apostrophes",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "foo7" ,
++                exp_name        => "foo7",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n 'foo3 foo4'   'foo5   foo6   foo7 foo8'", KERNEL=="sda5", SYMLINK+="%c{5}"
+ EOF
+@@ -340,7 +340,7 @@ EOF
+         {
+                 desc            => "program arguments combined with escaped double quotes, part 1",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "foo2" ,
++                exp_name        => "foo2",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'printf %%s \\\"foo1 foo2\\\" | grep \\\"foo1 foo2\\\"'", KERNEL=="sda5", SYMLINK+="%c{2}"
+ EOF
+@@ -348,7 +348,7 @@ EOF
+         {
+                 desc            => "program arguments combined with escaped double quotes, part 2",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "foo2" ,
++                exp_name        => "foo2",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c \\\"printf %%s 'foo1 foo2' | grep 'foo1 foo2'\\\"", KERNEL=="sda5", SYMLINK+="%c{2}"
+ EOF
+@@ -356,7 +356,7 @@ EOF
+         {
+                 desc            => "program arguments combined with escaped double quotes, part 3",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "foo2" ,
++                exp_name        => "foo2",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'printf \\\"%%s %%s\\\" \\\"foo1 foo2\\\" \\\"foo3\\\"| grep \\\"foo1 foo2\\\"'", KERNEL=="sda5", SYMLINK+="%c{2}"
+ EOF
+@@ -364,7 +364,7 @@ EOF
+         {
+                 desc            => "characters before the %c{N} substitution",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "my-foo9" ,
++                exp_name        => "my-foo9",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{7}"
+ EOF
+@@ -372,7 +372,7 @@ EOF
+         {
+                 desc            => "substitute the second to last argument",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "my-foo8" ,
++                exp_name        => "my-foo8",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{6}"
+ EOF
+@@ -396,7 +396,7 @@ EOF
+         {
+                 desc            => "test substitution by variable name 3",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "850:0:0:05" ,
++                exp_name        => "850:0:0:05",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="%M%m%b%n"
+ EOF
+@@ -404,7 +404,7 @@ EOF
+         {
+                 desc            => "test substitution by variable name 4",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "855" ,
++                exp_name        => "855",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="\$major\$minor\$number"
+ EOF
+@@ -412,7 +412,7 @@ EOF
+         {
+                 desc            => "test substitution by variable name 5",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "8550:0:0:0" ,
++                exp_name        => "8550:0:0:0",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="\$major%m%n\$id"
+ EOF
+@@ -429,7 +429,7 @@ EOF
+         {
+                 desc            => "non matching SUBSYSTEMS",
+                 devpath         => "/devices/virtual/tty/console",
+-                exp_name        => "TTY" ,
++                exp_name        => "TTY",
+                 rules                => <<EOF
+ SUBSYSTEMS=="foo", ATTRS{dev}=="5:1", SYMLINK+="foo"
+ KERNEL=="console", SYMLINK+="TTY"
+@@ -438,7 +438,7 @@ EOF
+         {
+                 desc            => "ATTRS match",
+                 devpath         => "/devices/virtual/tty/console",
+-                exp_name        => "foo" ,
++                exp_name        => "foo",
+                 rules           => <<EOF
+ KERNEL=="console", SYMLINK+="TTY"
+ ATTRS{dev}=="5:1", SYMLINK+="foo"
+@@ -447,7 +447,7 @@ EOF
+         {
+                 desc            => "ATTR (empty file)",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "empty" ,
++                exp_name        => "empty",
+                 rules           => <<EOF
+ KERNEL=="sda", ATTR{test_empty_file}=="?*", SYMLINK+="something"
+ KERNEL=="sda", ATTR{test_empty_file}!="", SYMLINK+="not-empty"
+@@ -458,7 +458,7 @@ EOF
+         {
+                 desc            => "ATTR (non-existent file)",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "non-existent" ,
++                exp_name        => "non-existent",
+                 rules           => <<EOF
+ KERNEL=="sda", ATTR{nofile}=="?*", SYMLINK+="something"
+ KERNEL=="sda", ATTR{nofile}!="", SYMLINK+="not-empty"
+@@ -471,7 +471,7 @@ EOF
+         {
+                 desc            => "program and bus type match",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "scsi-0:0:0:0" ,
++                exp_name        => "scsi-0:0:0:0",
+                 rules           => <<EOF
+ SUBSYSTEMS=="usb", PROGRAM=="/bin/echo -n usb-%b", SYMLINK+="%c"
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n scsi-%b", SYMLINK+="%c"
+@@ -481,7 +481,7 @@ EOF
+         {
+                 desc            => "sysfs parent hierarchy",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem" ,
++                exp_name        => "modem",
+                 rules           => <<EOF
+ ATTRS{idProduct}=="007b", SYMLINK+="modem"
+ EOF
+@@ -489,7 +489,7 @@ EOF
+         {
+                 desc            => "name test with ! in the name",
+                 devpath         => "/devices/virtual/block/fake!blockdev0",
+-                exp_name        => "is/a/fake/blockdev0" ,
++                exp_name        => "is/a/fake/blockdev0",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", SYMLINK+="is/not/a/%k"
+ SUBSYSTEM=="block", SYMLINK+="is/a/%k"
+@@ -499,7 +499,7 @@ EOF
+         {
+                 desc            => "name test with ! in the name, but no matching rule",
+                 devpath         => "/devices/virtual/block/fake!blockdev0",
+-                exp_name        => "fake/blockdev0" ,
++                exp_name        => "fake/blockdev0",
+                 exp_rem_error   => "yes",
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -542,7 +542,7 @@ EOF
+                 desc            => "KERNELS wildcard partial 2",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                 exp_name        => "scsi-0:0:0:0",
+-                rules                => <<EOF
++                rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before"
+ SUBSYSTEMS=="scsi", KERNELS=="*:0:0:0", SYMLINK+="scsi-0:0:0:0"
+ EOF
+@@ -739,7 +739,7 @@ EOF
+                 devpath         => "/devices/virtual/misc/misc-fake1",
+                 exp_name        => "node",
+                 exp_majorminor  => "4095:1",
+-                rules                => <<EOF
++                rules           => <<EOF
+ KERNEL=="misc-fake1", SYMLINK+="node"
+ EOF
+         },
+@@ -764,7 +764,7 @@ EOF
+                 desc            => "multiple symlinks with a lot of s p a c e s",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+                 exp_name        => "one",
+-                not_exp_name        => " ",
++                not_exp_name    => " ",
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK="  one     two        "
+ EOF
+@@ -860,7 +860,7 @@ EOF
+         {
+                 desc            => "multiple symlinks",
+                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "second-0" ,
++                exp_name        => "second-0",
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", SYMLINK="first-%n second-%n third-%n"
+ EOF
+@@ -869,8 +869,8 @@ EOF
+                 desc            => "symlink name '.'",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                 exp_name        => ".",
+-                exp_add_error        => "yes",
+-                exp_rem_error        => "yes",
++                exp_add_error   => "yes",
++                exp_rem_error   => "yes",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="."
+ EOF
+@@ -879,9 +879,9 @@ EOF
+                 desc            => "symlink node to itself",
+                 devpath         => "/devices/virtual/tty/tty0",
+                 exp_name        => "link",
+-                exp_add_error        => "yes",
+-                exp_rem_error        => "yes",
+-                option                => "clean",
++                exp_add_error   => "yes",
++                exp_rem_error   => "yes",
++                option          => "clean",
+                 rules           => <<EOF
+ KERNEL=="tty0", SYMLINK+="tty0"
+ EOF
+@@ -1437,7 +1437,7 @@ EOF
+                 desc            => "add and match tag",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                 exp_name        => "found",
+-                not_exp_name    => "bad" ,
++                not_exp_name    => "bad",
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", TAG+="green"
+ TAGS=="green", SYMLINK+="found"
+@@ -1456,7 +1456,7 @@ EOF
+                 desc            => "continuations",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                 exp_name        => "found",
+-                not_exp_name    => "bad" ,
++                not_exp_name    => "bad",
+                 rules           => $rules_10k_tags_continuation . <<EOF
+ TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="bad"
+ KERNEL=="sda",\\
diff --git a/SOURCES/0521-test-udev-test.pl-cleanup-if-skipping-test.patch b/SOURCES/0521-test-udev-test.pl-cleanup-if-skipping-test.patch
new file mode 100644
index 0000000..1500d54
--- /dev/null
+++ b/SOURCES/0521-test-udev-test.pl-cleanup-if-skipping-test.patch
@@ -0,0 +1,73 @@
+From dc50d1cc5bf445f1a26dbc646ff52421563e677f Mon Sep 17 00:00:00 2001
+From: Dan Streetman <ddstreet@canonical.com>
+Date: Fri, 5 Jul 2019 11:24:55 -0400
+Subject: [PATCH] test/udev-test.pl: cleanup if skipping test
+
+In Ubuntu CI, udev-test.pl is run from the debian/test/udev script,
+in a test dir created for it; but udev-test.pl setup mounts a
+dir, so if it doesn't cleanup/unmount before exiting, the test dir
+autopkgtest created for it can't be removed, and autopkgtest
+aborts the entire test suite, for example this output (from a
+test run inside an armhf container):
+
+autopkgtest [12:45:36]: test udev: [-----------------------
+umount: test/tmpfs: no mount point specified.
+mknod: test/tmpfs/dev/null: Operation not permitted
+unable to create test/tmpfs/dev/null at ./udev-test.pl line 1611.
+Failed to set up the environment, skipping the test at ./udev-test.pl line 1731.
+autopkgtest [12:45:41]: test udev: -----------------------]
+autopkgtest [12:45:44]: test udev:  - - - - - - - - - - results - - - - - - - - - -
+udev                 FAIL non-zero exit status 77
+rm: cannot remove '/tmp/autopkgtest.ocPFA6/autopkgtest_tmp/test/tmpfs': Device or resource busy
+autopkgtest [12:46:22]: ERROR: "rm -rf /tmp/autopkgtest.ocPFA6/udev-artifacts /tmp/autopkgtest.ocPFA6/autopkgtest_tmp" failed with stderr "rm:
+
+(cherry picked from commit abb9cc50afb3949c442849f43301fb33578f3888)
+
+Related: #1642728
+---
+ test/udev-test.pl | 13 +++++++++----
+ 1 file changed, 9 insertions(+), 4 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 122359e377..2fea72875b 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1713,6 +1713,12 @@ sub run_test {
+ 
+ }
+ 
++sub cleanup {
++        system("rm", "-rf", "$udev_run");
++        system("umount", "$udev_tmpfs");
++        rmdir($udev_tmpfs);
++}
++
+ # only run if we have root permissions
+ # due to mknod restrictions
+ if (!($<==0)) {
+@@ -1729,11 +1735,13 @@ if ($? >> 8 == 0) {
+ 
+ if (!udev_setup()) {
+         warn "Failed to set up the environment, skipping the test";
++        cleanup();
+         exit($EXIT_TEST_SKIP);
+ }
+ 
+ if (system($udev_bin, "check")) {
+         warn "$udev_bin failed to set up the environment, skipping the test";
++        cleanup();
+         exit($EXIT_TEST_SKIP);
+ }
+ 
+@@ -1776,10 +1784,7 @@ if ($list[0]) {
+ 
+ print "$error errors occurred\n\n";
+ 
+-# cleanup
+-system("rm", "-rf", "$udev_run");
+-system("umount", "$udev_tmpfs");
+-rmdir($udev_tmpfs);
++cleanup();
+ 
+ if ($error > 0) {
+         exit(1);
diff --git a/SOURCES/0522-test-add-test-cases-for-empty-string-match.patch b/SOURCES/0522-test-add-test-cases-for-empty-string-match.patch
new file mode 100644
index 0000000..acf7479
--- /dev/null
+++ b/SOURCES/0522-test-add-test-cases-for-empty-string-match.patch
@@ -0,0 +1,89 @@
+From 03bc565e6e3249385c4e1ca0ae27670ca2ad9a41 Mon Sep 17 00:00:00 2001
+From: Yu Watanabe <watanabe.yu+github@gmail.com>
+Date: Wed, 11 Sep 2019 09:06:15 +0900
+Subject: [PATCH] test: add test cases for empty string match
+
+(cherry picked from commit 48d26c90852c22ec94be961f5fbdcf462bb9a6e8)
+
+Related: #1642728
+---
+ test/udev-test.pl | 66 +++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 66 insertions(+)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 2fea72875b..50d978391b 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1256,6 +1256,72 @@ KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1"
+ KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2"
+ KERNEL=="all|dontknow|ttyACM0", SYMLINK+="right"
+ KERNEL=="ttyACM0a|nothing", SYMLINK+="wrong3"
++EOF
++        },
++        {
++                desc            => "test multi matches 5",
++                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                exp_name        => "found",
++                not_exp_name    => "bad",
++                rules           => <<EOF
++KERNEL=="sda", TAG="foo"
++TAGS=="|foo", SYMLINK+="found"
++TAGS=="|aaa", SYMLINK+="bad"
++EOF
++        },
++        {
++                desc            => "test multi matches 6",
++                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                exp_name        => "found",
++                not_exp_name    => "bad",
++                rules           => <<EOF
++KERNEL=="sda", TAG=""
++TAGS=="|foo", SYMLINK+="found"
++TAGS=="aaa|bbb", SYMLINK+="bad"
++EOF
++        },
++        {
++                desc            => "test multi matches 7",
++                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                exp_name        => "found",
++                not_exp_name    => "bad",
++                rules           => <<EOF
++KERNEL=="sda", TAG="foo"
++TAGS=="foo||bar", SYMLINK+="found"
++TAGS=="aaa||bbb", SYMLINK+="bad"
++EOF
++        },
++        {
++                desc            => "test multi matches 8",
++                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                exp_name        => "found",
++                not_exp_name    => "bad",
++                rules           => <<EOF
++KERNEL=="sda", TAG=""
++TAGS=="foo||bar", SYMLINK+="found"
++TAGS=="aaa|bbb", SYMLINK+="bad"
++EOF
++        },
++        {
++                desc            => "test multi matches 9",
++                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                exp_name        => "found",
++                not_exp_name    => "bad",
++                rules           => <<EOF
++KERNEL=="sda", TAG="foo"
++TAGS=="foo|", SYMLINK+="found"
++TAGS=="aaa|", SYMLINK+="bad"
++EOF
++        },
++        {
++                desc            => "test multi matches 10",
++                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                exp_name        => "found",
++                not_exp_name    => "bad",
++                rules           => <<EOF
++KERNEL=="sda", TAG=""
++TAGS=="foo|", SYMLINK+="found"
++TAGS=="aaa|bbb", SYMLINK+="bad"
+ EOF
+         },
+         {
diff --git a/SOURCES/0523-test-add-test-case-for-multi-matches-when-use.patch b/SOURCES/0523-test-add-test-case-for-multi-matches-when-use.patch
new file mode 100644
index 0000000..d7d60b3
--- /dev/null
+++ b/SOURCES/0523-test-add-test-case-for-multi-matches-when-use.patch
@@ -0,0 +1,35 @@
+From 03b766cc937ffa4dcb7cfb25b2ac20d8a00cb6db Mon Sep 17 00:00:00 2001
+From: gaoyi <ymuemc@163.com>
+Date: Sun, 12 Jul 2020 03:27:45 -0400
+Subject: [PATCH] test: add test case for multi matches when use "||"
+
+Signed-off-by: gaoyi <ymuemc@163.com>
+(cherry picked from commit 0d3a8bc7ebd76591e14f7098b4266fd2065ac4db)
+
+Related: #1642728
+---
+ test/udev-test.pl | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 50d978391b..4bf97d82bb 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1322,6 +1322,17 @@ EOF
+ KERNEL=="sda", TAG=""
+ TAGS=="foo|", SYMLINK+="found"
+ TAGS=="aaa|bbb", SYMLINK+="bad"
++EOF
++        },
++        {
++                desc            => "test multi matches 11",
++                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                exp_name        => "found",
++                not_exp_name    => "bad",
++                rules           => <<EOF
++KERNEL=="sda", TAG="c"
++TAGS=="foo||bar||c", SYMLINK+="found"
++TAGS=="aaa||bbb||ccc", SYMLINK+="bad"
+ EOF
+         },
+         {
diff --git a/SOURCES/0524-udev-test-do-not-rely-on-mail-group-being-defined.patch b/SOURCES/0524-udev-test-do-not-rely-on-mail-group-being-defined.patch
new file mode 100644
index 0000000..a048469
--- /dev/null
+++ b/SOURCES/0524-udev-test-do-not-rely-on-mail-group-being-defined.patch
@@ -0,0 +1,33 @@
+From c68da72231d5c502acd4e79791d0810790f3231b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Fri, 4 Sep 2020 18:09:20 +0200
+Subject: [PATCH] udev-test: do not rely on "mail" group being defined
+
+"audio" should be there, at least we declare it. "mail" nowadays is less
+likely to exist than in the past.
+
+Fixes one of the items in #16942.
+
+(cherry picked from commit a9030b81c154c3ec92227d04cad6b13cc1125608)
+
+Related: #1642728
+---
+ test/udev-test.pl | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 4bf97d82bb..a4deffacb9 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -629,9 +629,9 @@ EOF
+                 desc            => "textual user/group id",
+                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                 exp_name        => "node",
+-                exp_perms       => "root:mail:0660",
++                exp_perms       => "root:audio:0660",
+                 rules           => <<EOF
+-SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="root", GROUP="mail"
++SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="root", GROUP="audio"
+ EOF
+         },
+         {
diff --git a/SOURCES/0525-test-udev-test.pl-allow-multiple-devices-per-test.patch b/SOURCES/0525-test-udev-test.pl-allow-multiple-devices-per-test.patch
new file mode 100644
index 0000000..25887e2
--- /dev/null
+++ b/SOURCES/0525-test-udev-test.pl-allow-multiple-devices-per-test.patch
@@ -0,0 +1,2452 @@
+From 3f90eeef6a8469de6ad490efb62a5be80188f41f Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Fri, 20 Apr 2018 22:38:30 +0200
+Subject: [PATCH] test/udev-test.pl: allow multiple devices per test
+
+Allow testing cases where multiple devices are added and removed.
+This implies a change of the data structure: every test allows
+for multiple devices to be added, and "exp_name" etc. are now properties
+of the device, not of the test.
+
+(cherry picked from commit 255c05b72455dcad1b5552d12a813b31f68201a7)
+
+Related: #1642728
+---
+ test/udev-test.pl | 1352 +++++++++++++++++++++++++++++++--------------
+ 1 file changed, 929 insertions(+), 423 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index a4deffacb9..bd5401da75 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -48,17 +48,28 @@ $rules_10k_tags_continuation .= "TAG+=\"test10000\"\\n";
+ my @tests = (
+         {
+                 desc            => "no rules",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "sda",
+-                exp_rem_error   => "yes",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "sda" ,
++                                exp_rem_error   => "yes",
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "sda1" ,
++                                exp_rem_error   => "yes",
++                        }],
+                 rules           => <<EOF
+ #
+ EOF
+         },
+         {
+                 desc            => "label test of scsi disc",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "boot_disk",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "boot_disk" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -66,8 +77,11 @@ EOF
+         },
+         {
+                 desc            => "label test of scsi disc",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "boot_disk",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "boot_disk" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -75,8 +89,11 @@ EOF
+         },
+         {
+                 desc            => "label test of scsi disc",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "boot_disk",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "boot_disk" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -84,16 +101,22 @@ EOF
+         },
+         {
+                 desc            => "label test of scsi partition",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "boot_disk1",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "boot_disk1" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+ EOF
+         },
+         {
+                 desc            => "label test of pattern match",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "boot_disk1",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "boot_disk1" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="?ATA", SYMLINK+="boot_disk%n-1"
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA?", SYMLINK+="boot_disk%n-2"
+@@ -103,8 +126,11 @@ EOF
+         },
+         {
+                 desc            => "label test of multiple sysfs files",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "boot_disk1",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "boot_disk1" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS X ", SYMLINK+="boot_diskX%n"
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="boot_disk%n"
+@@ -112,8 +138,11 @@ EOF
+         },
+         {
+                 desc            => "label test of max sysfs files (skip invalid rule)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "boot_disk1",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "boot_disk1" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="32", SYMLINK+="boot_diskXX%n"
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n"
+@@ -121,16 +150,22 @@ EOF
+         },
+         {
+                 desc            => "catch device by *",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem/0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "modem/0" ,
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM*", SYMLINK+="modem/%n"
+ EOF
+         },
+         {
+                 desc            => "catch device by * - take 2",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem/0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "modem/0" ,
++                        }],
+                 rules           => <<EOF
+ KERNEL=="*ACM1", SYMLINK+="bad"
+ KERNEL=="*ACM0", SYMLINK+="modem/%n"
+@@ -138,8 +173,11 @@ EOF
+         },
+         {
+                 desc            => "catch device by ?",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem/0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "modem/0" ,
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM??*", SYMLINK+="modem/%n-1"
+ KERNEL=="ttyACM??", SYMLINK+="modem/%n-2"
+@@ -148,8 +186,11 @@ EOF
+         },
+         {
+                 desc            => "catch device by character class",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem/0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "modem/0" ,
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[A-Z]*", SYMLINK+="modem/%n-1"
+ KERNEL=="ttyACM?[0-9]", SYMLINK+="modem/%n-2"
+@@ -158,16 +199,22 @@ EOF
+         },
+         {
+                 desc            => "replace kernel name",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "modem" ,
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+ EOF
+         },
+         {
+                 desc            => "Handle comment lines in config file (and replace kernel name)",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "modem" ,
++                        }],
+                 rules           => <<EOF
+ # this is a comment
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -176,8 +223,11 @@ EOF
+         },
+         {
+                 desc            => "Handle comment lines in config file with whitespace (and replace kernel name)",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "modem" ,
++                        }],
+                 rules           => <<EOF
+  # this is a comment with whitespace before the comment
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -186,8 +236,11 @@ EOF
+         },
+         {
+                 desc            => "Handle whitespace only lines (and replace kernel name)",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "whitespace",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "whitespace" ,
++                        }],
+                 rules           => <<EOF
+ 
+ 
+@@ -201,8 +254,11 @@ EOF
+         },
+         {
+                 desc            => "Handle empty lines in config file (and replace kernel name)",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "modem" ,
++                        }],
+                 rules           => <<EOF
+ 
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -211,8 +267,11 @@ EOF
+         },
+         {
+                 desc            => "Handle backslashed multi lines in config file (and replace kernel name)",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "modem" ,
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", \\
+ SYMLINK+="modem"
+@@ -221,16 +280,22 @@ EOF
+         },
+         {
+                 desc            => "preserve backslashes, if they are not for a newline",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "aaa",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "aaa",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", PROGRAM=="/bin/echo -e \\101", RESULT=="A", SYMLINK+="aaa"
+ EOF
+         },
+         {
+                 desc            => "Handle stupid backslashed multi lines in config file (and replace kernel name)",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "modem" ,
++                        }],
+                 rules           => <<EOF
+ 
+ #
+@@ -247,41 +312,56 @@ EOF
+         },
+         {
+                 desc            => "subdirectory handling",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "sub/direct/ory/modem",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "sub/direct/ory/modem" ,
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", SYMLINK+="sub/direct/ory/modem"
+ EOF
+         },
+         {
+                 desc            => "parent device name match of scsi partition",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "first_disk5",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "first_disk5" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="first_disk%n"
+ EOF
+         },
+         {
+                 desc            => "test substitution chars",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "Major:8:minor:5:kernelnumber:5:id:0:0:0:0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "Major:8:minor:5:kernelnumber:5:id:0:0:0:0" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:%M:minor:%m:kernelnumber:%n:id:%b"
+ EOF
+         },
+         {
+                 desc            => "import of shell-value returned from program",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "node12345678",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "node12345678",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", IMPORT{program}="/bin/echo -e \' TEST_KEY=12345678\\n  TEST_key2=98765\'", SYMLINK+="node\$env{TEST_KEY}"
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+ EOF
+         },
+         {
+-                desc            => "sustitution of sysfs value (%s{file})",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "disk-ATA-sda",
++                desc            => "substitution of sysfs value (%s{file})",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "disk-ATA-sda" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="disk-%s{vendor}-%k"
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -289,9 +369,12 @@ EOF
+         },
+         {
+                 desc            => "program result substitution",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "special-device-5",
+-                not_exp_name    => "not",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "special-device-5" ,
++                                not_exp_name    => "not" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="-special-*", SYMLINK+="not"
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="special-*", SYMLINK+="%c-%n"
+@@ -299,128 +382,176 @@ EOF
+         },
+         {
+                 desc            => "program result substitution (newline removal)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "newline_removed",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "newline_removed" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo test", RESULT=="test", SYMLINK+="newline_removed"
+ EOF
+         },
+         {
+                 desc            => "program result substitution",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "test-0:0:0:0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "test-0:0:0:0" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n test-%b", RESULT=="test-0:0*", SYMLINK+="%c"
+ EOF
+         },
+         {
+                 desc            => "program with lots of arguments",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "foo9",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "foo9" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="%c{7}"
+ EOF
+         },
+         {
+                 desc            => "program with subshell",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "bar9",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "bar9" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'echo foo3 foo4 foo5 foo6 foo7 foo8 foo9 | sed  s/foo9/bar9/'", KERNEL=="sda5", SYMLINK+="%c{7}"
+ EOF
+         },
+         {
+                 desc            => "program arguments combined with apostrophes",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "foo7",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "foo7" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n 'foo3 foo4'   'foo5   foo6   foo7 foo8'", KERNEL=="sda5", SYMLINK+="%c{5}"
+ EOF
+         },
+         {
+                 desc            => "program arguments combined with escaped double quotes, part 1",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "foo2",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "foo2" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'printf %%s \\\"foo1 foo2\\\" | grep \\\"foo1 foo2\\\"'", KERNEL=="sda5", SYMLINK+="%c{2}"
+ EOF
+         },
+         {
+                 desc            => "program arguments combined with escaped double quotes, part 2",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "foo2",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "foo2" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c \\\"printf %%s 'foo1 foo2' | grep 'foo1 foo2'\\\"", KERNEL=="sda5", SYMLINK+="%c{2}"
+ EOF
+         },
+         {
+                 desc            => "program arguments combined with escaped double quotes, part 3",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "foo2",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "foo2" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'printf \\\"%%s %%s\\\" \\\"foo1 foo2\\\" \\\"foo3\\\"| grep \\\"foo1 foo2\\\"'", KERNEL=="sda5", SYMLINK+="%c{2}"
+ EOF
+         },
+         {
+                 desc            => "characters before the %c{N} substitution",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "my-foo9",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "my-foo9" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{7}"
+ EOF
+         },
+         {
+                 desc            => "substitute the second to last argument",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "my-foo8",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "my-foo8" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{6}"
+ EOF
+         },
+         {
+                 desc            => "test substitution by variable name",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "Major:8-minor:5-kernelnumber:5-id:0:0:0:0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "Major:8-minor:5-kernelnumber:5-id:0:0:0:0",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:\$major-minor:\$minor-kernelnumber:\$number-id:\$id"
+ EOF
+         },
+         {
+                 desc            => "test substitution by variable name 2",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "Major:8-minor:5-kernelnumber:5-id:0:0:0:0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "Major:8-minor:5-kernelnumber:5-id:0:0:0:0",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="Major:\$major-minor:%m-kernelnumber:\$number-id:\$id"
+ EOF
+         },
+         {
+                 desc            => "test substitution by variable name 3",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "850:0:0:05",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "850:0:0:05" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="%M%m%b%n"
+ EOF
+         },
+         {
+                 desc            => "test substitution by variable name 4",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "855",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "855" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="\$major\$minor\$number"
+ EOF
+         },
+         {
+                 desc            => "test substitution by variable name 5",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "8550:0:0:0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "8550:0:0:0" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="\$major%m%n\$id"
+ EOF
+         },
+         {
+                 desc            => "non matching SUBSYSTEMS for device with no parent",
+-                devpath         => "/devices/virtual/tty/console",
+-                exp_name        => "TTY",
++                devices => [
++                        {
++                                devpath         => "/devices/virtual/tty/console",
++                                exp_name        => "TTY",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo", RESULT=="foo", SYMLINK+="foo"
+ KERNEL=="console", SYMLINK+="TTY"
+@@ -428,8 +559,11 @@ EOF
+         },
+         {
+                 desc            => "non matching SUBSYSTEMS",
+-                devpath         => "/devices/virtual/tty/console",
+-                exp_name        => "TTY",
++                devices => [
++                        {
++                                devpath         => "/devices/virtual/tty/console",
++                                exp_name        => "TTY" ,
++                        }],
+                 rules                => <<EOF
+ SUBSYSTEMS=="foo", ATTRS{dev}=="5:1", SYMLINK+="foo"
+ KERNEL=="console", SYMLINK+="TTY"
+@@ -437,8 +571,11 @@ EOF
+         },
+         {
+                 desc            => "ATTRS match",
+-                devpath         => "/devices/virtual/tty/console",
+-                exp_name        => "foo",
++                devices => [
++                        {
++                                devpath         => "/devices/virtual/tty/console",
++                                exp_name        => "foo" ,
++                        }],
+                 rules           => <<EOF
+ KERNEL=="console", SYMLINK+="TTY"
+ ATTRS{dev}=="5:1", SYMLINK+="foo"
+@@ -446,8 +583,11 @@ EOF
+         },
+         {
+                 desc            => "ATTR (empty file)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "empty",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "empty" ,
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", ATTR{test_empty_file}=="?*", SYMLINK+="something"
+ KERNEL=="sda", ATTR{test_empty_file}!="", SYMLINK+="not-empty"
+@@ -457,8 +597,11 @@ EOF
+         },
+         {
+                 desc            => "ATTR (non-existent file)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "non-existent",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "non-existent" ,
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", ATTR{nofile}=="?*", SYMLINK+="something"
+ KERNEL=="sda", ATTR{nofile}!="", SYMLINK+="not-empty"
+@@ -470,8 +613,11 @@ EOF
+         },
+         {
+                 desc            => "program and bus type match",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "scsi-0:0:0:0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "scsi-0:0:0:0" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="usb", PROGRAM=="/bin/echo -n usb-%b", SYMLINK+="%c"
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n scsi-%b", SYMLINK+="%c"
+@@ -480,16 +626,22 @@ EOF
+         },
+         {
+                 desc            => "sysfs parent hierarchy",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "modem" ,
++                        }],
+                 rules           => <<EOF
+ ATTRS{idProduct}=="007b", SYMLINK+="modem"
+ EOF
+         },
+         {
+                 desc            => "name test with ! in the name",
+-                devpath         => "/devices/virtual/block/fake!blockdev0",
+-                exp_name        => "is/a/fake/blockdev0",
++                devices => [
++                        {
++                                devpath         => "/devices/virtual/block/fake!blockdev0",
++                                exp_name        => "is/a/fake/blockdev0" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", SYMLINK+="is/not/a/%k"
+ SUBSYSTEM=="block", SYMLINK+="is/a/%k"
+@@ -498,17 +650,23 @@ EOF
+         },
+         {
+                 desc            => "name test with ! in the name, but no matching rule",
+-                devpath         => "/devices/virtual/block/fake!blockdev0",
+-                exp_name        => "fake/blockdev0",
+-                exp_rem_error   => "yes",
++                devices => [
++                        {
++                                devpath         => "/devices/virtual/block/fake!blockdev0",
++                                exp_name        => "fake/blockdev0" ,
++                                exp_rem_error   => "yes",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+ EOF
+         },
+         {
+                 desc            => "KERNELS rule",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "scsi-0:0:0:0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "scsi-0:0:0:0",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="usb", KERNELS=="0:0:0:0", SYMLINK+="not-scsi"
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:1", SYMLINK+="no-match"
+@@ -519,8 +677,11 @@ EOF
+         },
+         {
+                 desc            => "KERNELS wildcard all",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "scsi-0:0:0:0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "scsi-0:0:0:0",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="*:1", SYMLINK+="no-match"
+ SUBSYSTEMS=="scsi", KERNELS=="*:0:1", SYMLINK+="no-match"
+@@ -531,8 +692,11 @@ EOF
+         },
+         {
+                 desc            => "KERNELS wildcard partial",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "scsi-0:0:0:0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "scsi-0:0:0:0",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before"
+ SUBSYSTEMS=="scsi", KERNELS=="*:0", SYMLINK+="scsi-0:0:0:0"
+@@ -540,41 +704,56 @@ EOF
+         },
+         {
+                 desc            => "KERNELS wildcard partial 2",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "scsi-0:0:0:0",
+-                rules           => <<EOF
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "scsi-0:0:0:0",
++                        }],
++                rules                => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before"
+ SUBSYSTEMS=="scsi", KERNELS=="*:0:0:0", SYMLINK+="scsi-0:0:0:0"
+ EOF
+         },
+         {
+                 desc            => "substitute attr with link target value (first match)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "driver-is-sd",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "driver-is-sd",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", SYMLINK+="driver-is-\$attr{driver}"
+ EOF
+         },
+         {
+                 desc            => "substitute attr with link target value (currently selected device)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "driver-is-ahci",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "driver-is-ahci",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="pci", SYMLINK+="driver-is-\$attr{driver}"
+ EOF
+         },
+         {
+                 desc            => "ignore ATTRS attribute whitespace",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "ignored",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "ignored",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE  SPACE", SYMLINK+="ignored"
+ EOF
+         },
+         {
+                 desc            => "do not ignore ATTRS attribute whitespace",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "matched-with-space",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "matched-with-space",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE  SPACE ", SYMLINK+="wrong-to-ignore"
+ SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE  SPACE   ", SYMLINK+="matched-with-space"
+@@ -582,117 +761,156 @@ EOF
+         },
+         {
+                 desc            => "permissions USER=bad GROUP=name",
+-                devpath         => "/devices/virtual/tty/tty33",
+-                exp_name        => "tty33",
+-                exp_perms       => "0:0:0600",
++                devices => [
++                        {
++                                devpath         => "/devices/virtual/tty/tty33",
++                                exp_name        => "tty33",
++                                exp_perms       => "0:0:0600",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="tty33", OWNER="bad", GROUP="name"
+ EOF
+         },
+         {
+                 desc            => "permissions OWNER=1",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "node",
+-                exp_perms       => "1::0600",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "node",
++                                exp_perms       => "1::0600",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1"
+ EOF
+         },
+         {
+                 desc            => "permissions GROUP=1",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "node",
+-                exp_perms       => ":1:0660",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "node",
++                                exp_perms       => ":1:0660",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="1"
+ EOF
+         },
+         {
+                 desc            => "textual user id",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "node",
+-                exp_perms       => "daemon::0600",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "node",
++                                exp_perms       => "daemon::0600",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="daemon"
+ EOF
+         },
+         {
+                 desc            => "textual group id",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "node",
+-                exp_perms       => ":daemon:0660",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "node",
++                                exp_perms       => ":daemon:0660",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="daemon"
+ EOF
+         },
+         {
+                 desc            => "textual user/group id",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "node",
+-                exp_perms       => "root:audio:0660",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "node",
++                                exp_perms       => "root:audio:0660",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="root", GROUP="audio"
+ EOF
+         },
+         {
+                 desc            => "permissions MODE=0777",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "node",
+-                exp_perms       => "::0777",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "node",
++                                exp_perms       => "::0777",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", MODE="0777"
+ EOF
+         },
+         {
+                 desc            => "permissions OWNER=1 GROUP=1 MODE=0777",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "node",
+-                exp_perms       => "1:1:0777",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "node",
++                                exp_perms       => "1:1:0777",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1", GROUP="1", MODE="0777"
+ EOF
+         },
+         {
+                 desc            => "permissions OWNER to 1",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "ttyACM0",
+-                exp_perms       => "1::",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "ttyACM0",
++                                exp_perms       => "1::",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1"
+ EOF
+         },
+         {
+                 desc            => "permissions GROUP to 1",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "ttyACM0",
+-                exp_perms       => ":1:0660",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "ttyACM0",
++                                exp_perms       => ":1:0660",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="1"
+ EOF
+         },
+         {
+                 desc            => "permissions MODE to 0060",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "ttyACM0",
+-                exp_perms       => "::0060",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "ttyACM0",
++                                exp_perms       => "::0060",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", MODE="0060"
+ EOF
+         },
+         {
+                 desc            => "permissions OWNER, GROUP, MODE",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "ttyACM0",
+-                exp_perms       => "1:1:0777",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "ttyACM0",
++                                exp_perms       => "1:1:0777",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1", GROUP="1", MODE="0777"
+ EOF
+         },
+         {
+                 desc            => "permissions only rule",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "ttyACM0",
+-                exp_perms       => "1:1:0777",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "ttyACM0",
++                                exp_perms       => "1:1:0777",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", OWNER="1", GROUP="1", MODE="0777"
+ KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444"
+@@ -701,9 +919,12 @@ EOF
+         },
+         {
+                 desc            => "multiple permissions only rule",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "ttyACM0",
+-                exp_perms       => "1:1:0777",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "ttyACM0",
++                                exp_perms       => "1:1:0777",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEM=="tty", OWNER="1"
+ SUBSYSTEM=="tty", GROUP="1"
+@@ -714,9 +935,12 @@ EOF
+         },
+         {
+                 desc            => "permissions only rule with override at SYMLINK+ rule",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "ttyACM0",
+-                exp_perms       => "1:2:0777",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "ttyACM0",
++                                exp_perms       => "1:2:0777",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEM=="tty", OWNER="1"
+ SUBSYSTEM=="tty", GROUP="1"
+@@ -727,53 +951,71 @@ EOF
+         },
+         {
+                 desc            => "major/minor number test",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "node",
+-                exp_majorminor  => "8:0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "node",
++                                exp_majorminor  => "8:0",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node"
+ EOF
+         },
+         {
+                 desc            => "big major number test",
+-                devpath         => "/devices/virtual/misc/misc-fake1",
+-                exp_name        => "node",
+-                exp_majorminor  => "4095:1",
+-                rules           => <<EOF
++                devices => [
++                        {
++                                devpath         => "/devices/virtual/misc/misc-fake1",
++                                exp_name        => "node",
++                                exp_majorminor  => "4095:1",
++                        }],
++                rules                => <<EOF
+ KERNEL=="misc-fake1", SYMLINK+="node"
+ EOF
+         },
+         {
+                 desc            => "big major and big minor number test",
+-                devpath         => "/devices/virtual/misc/misc-fake89999",
+-                exp_name        => "node",
+-                exp_majorminor  => "4095:89999",
++                devices => [
++                        {
++                                devpath         => "/devices/virtual/misc/misc-fake89999",
++                                exp_name        => "node",
++                                exp_majorminor  => "4095:89999",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="misc-fake89999", SYMLINK+="node"
+ EOF
+         },
+         {
+                 desc            => "multiple symlinks with format char",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "symlink2-ttyACM0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "symlink2-ttyACM0",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK="symlink1-%n symlink2-%k symlink3-%b"
+ EOF
+         },
+         {
+                 desc            => "multiple symlinks with a lot of s p a c e s",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "one",
+-                not_exp_name    => " ",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "one",
++                                not_exp_name        => " ",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK="  one     two        "
+ EOF
+         },
+         {
+                 desc            => "symlink with spaces in substituted variable",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "name-one_two_three-end",
+-                not_exp_name    => " ",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "name-one_two_three-end",
++                                not_exp_name    => " ",
++                        }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="one two three"
+ SYMLINK="name-\$env{WITH_WS}-end"
+@@ -781,9 +1023,12 @@ EOF
+         },
+         {
+                 desc            => "symlink with leading space in substituted variable",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "name-one_two_three-end",
+-                not_exp_name    => " ",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "name-one_two_three-end",
++                                not_exp_name    => " ",
++                        }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="   one two three"
+ SYMLINK="name-\$env{WITH_WS}-end"
+@@ -791,9 +1036,12 @@ EOF
+         },
+         {
+                 desc            => "symlink with trailing space in substituted variable",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "name-one_two_three-end",
+-                not_exp_name    => " ",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "name-one_two_three-end",
++                                not_exp_name    => " ",
++                        }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="one two three   "
+ SYMLINK="name-\$env{WITH_WS}-end"
+@@ -801,9 +1049,12 @@ EOF
+         },
+         {
+                 desc            => "symlink with lots of space in substituted variable",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "name-one_two_three-end",
+-                not_exp_name    => " ",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "name-one_two_three-end",
++                                not_exp_name    => " ",
++                        }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="   one two three   "
+ SYMLINK="name-\$env{WITH_WS}-end"
+@@ -811,9 +1062,12 @@ EOF
+         },
+         {
+                 desc            => "symlink with multiple spaces in substituted variable",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "name-one_two_three-end",
+-                not_exp_name    => " ",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "name-one_two_three-end",
++                                not_exp_name    => " ",
++                        }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="   one  two  three   "
+ SYMLINK="name-\$env{WITH_WS}-end"
+@@ -821,9 +1075,12 @@ EOF
+         },
+         {
+                 desc            => "symlink with space and var with space, part 1",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "first",
+-                not_exp_name    => " ",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "first",
++                                not_exp_name    => " ",
++                        }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="   one  two  three   "
+ SYMLINK="  first  name-\$env{WITH_WS}-end another_symlink a b c "
+@@ -831,9 +1088,12 @@ EOF
+         },
+         {
+                 desc            => "symlink with space and var with space, part 2",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "name-one_two_three-end",
+-                not_exp_name    => " ",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "name-one_two_three-end",
++                                not_exp_name    => " ",
++                        }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="   one  two  three   "
+ SYMLINK="  first  name-\$env{WITH_WS}-end another_symlink a b c "
+@@ -841,9 +1101,12 @@ EOF
+         },
+         {
+                 desc            => "symlink with space and var with space, part 3",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "another_symlink",
+-                not_exp_name    => " ",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "another_symlink",
++                                not_exp_name    => " ",
++                        }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="   one  two  three   "
+ SYMLINK="  first  name-\$env{WITH_WS}-end another_symlink a b c "
+@@ -851,133 +1114,181 @@ EOF
+         },
+         {
+                 desc            => "symlink creation (same directory)",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "modem0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "modem0",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK="modem%n"
+ EOF
+         },
+         {
+                 desc            => "multiple symlinks",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "second-0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "second-0" ,
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", SYMLINK="first-%n second-%n third-%n"
+ EOF
+         },
+         {
+                 desc            => "symlink name '.'",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => ".",
+-                exp_add_error   => "yes",
+-                exp_rem_error   => "yes",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => ".",
++                                exp_add_error        => "yes",
++                                exp_rem_error        => "yes",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="."
+ EOF
+         },
+         {
+                 desc            => "symlink node to itself",
+-                devpath         => "/devices/virtual/tty/tty0",
+-                exp_name        => "link",
+-                exp_add_error   => "yes",
+-                exp_rem_error   => "yes",
+-                option          => "clean",
++                devices => [
++                        {
++                                devpath         => "/devices/virtual/tty/tty0",
++                                exp_name        => "link",
++                                exp_add_error        => "yes",
++                                exp_rem_error        => "yes",
++                        }],
++                option                => "clean",
+                 rules           => <<EOF
+ KERNEL=="tty0", SYMLINK+="tty0"
+ EOF
+         },
+         {
+                 desc            => "symlink %n substitution",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "symlink0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "symlink0",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink%n"
+ EOF
+         },
+         {
+                 desc            => "symlink %k substitution",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "symlink-ttyACM0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "symlink-ttyACM0",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink-%k"
+ EOF
+         },
+         {
+                 desc            => "symlink %M:%m substitution",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "major-166:0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "major-166:0",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="major-%M:%m"
+ EOF
+         },
+         {
+                 desc            => "symlink %b substitution",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "symlink-0:0:0:0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "symlink-0:0:0:0",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="symlink-%b"
+ EOF
+         },
+         {
+                 desc            => "symlink %c substitution",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "test",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "test",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo test", SYMLINK+="%c"
+ EOF
+         },
+         {
+                 desc            => "symlink %c{N} substitution",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "test",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "test",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2}"
+ EOF
+         },
+         {
+                 desc            => "symlink %c{N+} substitution",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "this",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "this",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2+}"
+ EOF
+         },
+         {
+                 desc            => "symlink only rule with %c{N+}",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "test",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "test",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/bin/echo link test this" SYMLINK+="%c{2+}"
+ EOF
+         },
+         {
+                 desc            => "symlink %s{filename} substitution",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "166:0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "166:0",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="%s{dev}"
+ EOF
+         },
+         {
+                 desc            => "program result substitution (numbered part of)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "link1",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "link1",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2", RESULT=="node *", SYMLINK+="%c{2} %c{3}"
+ EOF
+         },
+         {
+                 desc            => "program result substitution (numbered part of+)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                exp_name        => "link4",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_name        => "link4",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2 link3 link4", RESULT=="node *", SYMLINK+="%c{2+}"
+ EOF
+         },
+         {
+                 desc            => "SUBSYSTEM match test",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "node",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "node",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", SUBSYSTEM=="vc"
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", SUBSYSTEM=="block"
+@@ -986,8 +1297,11 @@ EOF
+         },
+         {
+                 desc            => "DRIVERS match test",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "node",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "node",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", DRIVERS=="sd-wrong"
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", DRIVERS=="sd"
+@@ -995,32 +1309,44 @@ EOF
+         },
+         {
+                 desc            => "devnode substitution test",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "node",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "node",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/usr/bin/test -b %N" SYMLINK+="node"
+ EOF
+         },
+         {
+                 desc            => "parent node name substitution test",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "sda-part-1",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "sda-part-1",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="%P-part-1"
+ EOF
+         },
+         {
+                 desc            => "udev_root substitution",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "start-/dev-end",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "start-/dev-end",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="start-%r-end"
+ EOF
+         },
+         {
+                 desc            => "last_rule option",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "last",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "last",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="last", OPTIONS="last_rule"
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="very-last"
+@@ -1028,8 +1354,11 @@ EOF
+         },
+         {
+                 desc            => "negation KERNEL!=",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "match",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "match",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL!="sda1", SYMLINK+="matches-but-is-negated"
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
+@@ -1038,8 +1367,11 @@ EOF
+         },
+         {
+                 desc            => "negation SUBSYSTEM!=",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "not-anything",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "not-anything",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", SUBSYSTEM=="block", KERNEL!="sda1", SYMLINK+="matches-but-is-negated"
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
+@@ -1048,8 +1380,11 @@ EOF
+         },
+         {
+                 desc            => "negation PROGRAM!= exit code",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "nonzero-program",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "nonzero-program",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
+ KERNEL=="sda1", PROGRAM!="/bin/false", SYMLINK+="nonzero-program"
+@@ -1057,8 +1392,11 @@ EOF
+         },
+         {
+                 desc            => "ENV{} test",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "true",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "true",
++                        }],
+                 rules           => <<EOF
+ ENV{ENV_KEY_TEST}="test"
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong"
+@@ -1068,8 +1406,11 @@ EOF
+         },
+         {
+                 desc            => "ENV{} test",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "true",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "true",
++                        }],
+                 rules           => <<EOF
+ ENV{ENV_KEY_TEST}="test"
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong"
+@@ -1080,8 +1421,11 @@ EOF
+         },
+         {
+                 desc            => "ENV{} test (assign)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "true",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "true",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true"
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no"
+@@ -1091,8 +1435,11 @@ EOF
+         },
+         {
+                 desc            => "ENV{} test (assign 2 times)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "true",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "true",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true"
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="absolutely-\$env{ASSIGN}"
+@@ -1103,8 +1450,11 @@ EOF
+         },
+         {
+                 desc            => "ENV{} test (assign2)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "part",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "part",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEM=="block", KERNEL=="*[0-9]", ENV{PARTITION}="true", ENV{MAINDEVICE}="false"
+ SUBSYSTEM=="block", KERNEL=="*[!0-9]", ENV{PARTITION}="false", ENV{MAINDEVICE}="true"
+@@ -1115,40 +1465,55 @@ EOF
+         },
+         {
+                 desc            => "untrusted string sanitize",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "sane",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "sane",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e name; (/usr/bin/badprogram)", RESULT=="name_ _/usr/bin/badprogram_", SYMLINK+="sane"
+ EOF
+         },
+         {
+                 desc            => "untrusted string sanitize (don't replace utf8)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "uber",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "uber",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \\xc3\\xbcber" RESULT=="\xc3\xbcber", SYMLINK+="uber"
+ EOF
+         },
+         {
+                 desc            => "untrusted string sanitize (replace invalid utf8)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "replaced",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "replaced",
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \\xef\\xe8garbage", RESULT=="__garbage", SYMLINK+="replaced"
+ EOF
+         },
+         {
+                 desc            => "read sysfs value from parent device",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "serial-354172020305000",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "serial-354172020305000",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM*", ATTRS{serial}=="?*", SYMLINK+="serial-%s{serial}"
+ EOF
+         },
+         {
+                 desc            => "match against empty key string",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "ok",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "ok",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", ATTRS{nothing}!="", SYMLINK+="not-1-ok"
+ KERNEL=="sda", ATTRS{nothing}=="", SYMLINK+="not-2-ok"
+@@ -1158,8 +1523,11 @@ EOF
+         },
+         {
+                 desc            => "check ACTION value",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "ok",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "ok",
++                        }],
+                 rules           => <<EOF
+ ACTION=="unknown", KERNEL=="sda", SYMLINK+="unknown-not-ok"
+ ACTION=="add", KERNEL=="sda", SYMLINK+="ok"
+@@ -1167,9 +1535,12 @@ EOF
+         },
+         {
+                 desc            => "final assignment",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "ok",
+-                exp_perms       => "root:tty:0640",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "ok",
++                                exp_perms       => "root:tty:0640",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", GROUP:="tty"
+ KERNEL=="sda", GROUP="not-ok", MODE="0640", SYMLINK+="ok"
+@@ -1177,9 +1548,12 @@ EOF
+         },
+         {
+                 desc            => "final assignment 2",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "ok",
+-                exp_perms       => "root:tty:0640",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "ok",
++                                exp_perms       => "root:tty:0640",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", GROUP:="tty"
+ SUBSYSTEM=="block", MODE:="640"
+@@ -1188,17 +1562,23 @@ EOF
+         },
+         {
+                 desc            => "env substitution",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "node-add-me",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "node-add-me",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", MODE="0666", SYMLINK+="node-\$env{ACTION}-me"
+ EOF
+         },
+         {
+                 desc            => "reset list to current value",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "three",
+-                not_exp_name    => "two",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "three",
++                                not_exp_name    => "two",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="one"
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="two"
+@@ -1207,9 +1587,12 @@ EOF
+         },
+         {
+                 desc            => "test empty SYMLINK+ (empty override)",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "right",
+-                not_exp_name    => "wrong",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "right",
++                                not_exp_name    => "wrong",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="wrong"
+ KERNEL=="ttyACM[0-9]*", SYMLINK=""
+@@ -1218,8 +1601,11 @@ EOF
+         },
+         {
+                 desc            => "test multi matches",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "right",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "right",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM*", SYMLINK+="before"
+ KERNEL=="ttyACM*|nothing", SYMLINK+="right"
+@@ -1227,8 +1613,11 @@ EOF
+         },
+         {
+                 desc            => "test multi matches 2",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "right",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "right",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="dontknow*|*nothing", SYMLINK+="nomatch"
+ KERNEL=="ttyACM*", SYMLINK+="before"
+@@ -1237,8 +1626,11 @@ EOF
+         },
+         {
+                 desc            => "test multi matches 3",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "right",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "right",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="dontknow|nothing", SYMLINK+="nomatch"
+ KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1"
+@@ -1248,8 +1640,11 @@ EOF
+         },
+         {
+                 desc            => "test multi matches 4",
+-                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                exp_name        => "right",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
++                                exp_name        => "right",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="dontknow|nothing", SYMLINK+="nomatch"
+ KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1"
+@@ -1259,10 +1654,13 @@ KERNEL=="ttyACM0a|nothing", SYMLINK+="wrong3"
+ EOF
+         },
+         {
+-                desc            => "test multi matches 5",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "found",
+-                not_exp_name    => "bad",
++               desc            => "test multi matches 5",
++               devices => [
++                       {
++                               devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                               exp_name        => "found",
++                               not_exp_name    => "bad",
++                       }],
+                 rules           => <<EOF
+ KERNEL=="sda", TAG="foo"
+ TAGS=="|foo", SYMLINK+="found"
+@@ -1271,9 +1669,12 @@ EOF
+         },
+         {
+                 desc            => "test multi matches 6",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "found",
+-                not_exp_name    => "bad",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "found",
++                                not_exp_name    => "bad",
++                       }],
+                 rules           => <<EOF
+ KERNEL=="sda", TAG=""
+ TAGS=="|foo", SYMLINK+="found"
+@@ -1282,9 +1683,12 @@ EOF
+         },
+         {
+                 desc            => "test multi matches 7",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "found",
+-                not_exp_name    => "bad",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "found",
++                                not_exp_name    => "bad",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", TAG="foo"
+ TAGS=="foo||bar", SYMLINK+="found"
+@@ -1293,9 +1697,12 @@ EOF
+         },
+         {
+                 desc            => "test multi matches 8",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "found",
+-                not_exp_name    => "bad",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "found",
++                                not_exp_name    => "bad",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", TAG=""
+ TAGS=="foo||bar", SYMLINK+="found"
+@@ -1304,9 +1711,12 @@ EOF
+         },
+         {
+                 desc            => "test multi matches 9",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "found",
+-                not_exp_name    => "bad",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "found",
++                                not_exp_name    => "bad",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", TAG="foo"
+ TAGS=="foo|", SYMLINK+="found"
+@@ -1315,9 +1725,12 @@ EOF
+         },
+         {
+                 desc            => "test multi matches 10",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "found",
+-                not_exp_name    => "bad",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "found",
++                                not_exp_name    => "bad",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", TAG=""
+ TAGS=="foo|", SYMLINK+="found"
+@@ -1326,9 +1739,12 @@ EOF
+         },
+         {
+                 desc            => "test multi matches 11",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "found",
+-                not_exp_name    => "bad",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "found",
++                                not_exp_name    => "bad",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", TAG="c"
+ TAGS=="foo||bar||c", SYMLINK+="found"
+@@ -1337,8 +1753,11 @@ EOF
+         },
+         {
+                 desc            => "IMPORT parent test sequence 1/2 (keep)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "parent",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "parent",
++                        }],
+                 option          => "keep",
+                 rules           => <<EOF
+ KERNEL=="sda", IMPORT{program}="/bin/echo -e \'PARENT_KEY=parent_right\\nWRONG_PARENT_KEY=parent_wrong'"
+@@ -1347,8 +1766,11 @@ EOF
+         },
+         {
+                 desc            => "IMPORT parent test sequence 2/2 (keep)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "parentenv-parent_right",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "parentenv-parent_right",
++                        }],
+                 option          => "clean",
+                 rules           => <<EOF
+ KERNEL=="sda1", IMPORT{parent}="PARENT*", SYMLINK+="parentenv-\$env{PARENT_KEY}\$env{WRONG_PARENT_KEY}"
+@@ -1356,8 +1778,11 @@ EOF
+         },
+         {
+                 desc            => "GOTO test",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "right",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "right",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda1", GOTO="TEST"
+ KERNEL=="sda1", SYMLINK+="wrong"
+@@ -1370,8 +1795,11 @@ EOF
+         },
+         {
+                 desc            => "GOTO label does not exist",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "right",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "right",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda1", GOTO="does-not-exist"
+ KERNEL=="sda1", SYMLINK+="right",
+@@ -1380,9 +1808,12 @@ EOF
+         },
+         {
+                 desc            => "SYMLINK+ compare test",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "right",
+-                not_exp_name    => "wrong",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "right",
++                                not_exp_name    => "wrong",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda1", SYMLINK+="link"
+ KERNEL=="sda1", SYMLINK=="link*", SYMLINK+="right"
+@@ -1391,8 +1822,11 @@ EOF
+         },
+         {
+                 desc            => "invalid key operation",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "yes",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "yes",
++                        }],
+                 rules           => <<EOF
+ KERNEL="sda1", SYMLINK+="no"
+ KERNEL=="sda1", SYMLINK+="yes"
+@@ -1400,16 +1834,22 @@ EOF
+         },
+         {
+                 desc            => "operator chars in attribute",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "yes",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "yes",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", ATTR{test:colon+plus}=="?*", SYMLINK+="yes"
+ EOF
+         },
+         {
+                 desc            => "overlong comment line",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                exp_name        => "yes",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_name        => "yes",
++                        }],
+                 rules           => <<EOF
+ # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+    # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+@@ -1419,16 +1859,22 @@ EOF
+         },
+         {
+                 desc            => "magic subsys/kernel lookup",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "00:16:41:e2:8d:ff",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "00:16:41:e2:8d:ff",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", SYMLINK+="\$attr{[net/eth0]address}"
+ EOF
+         },
+         {
+                 desc            => "TEST absolute path",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "there",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "there",
++                        }],
+                 rules           => <<EOF
+ TEST=="/etc/machine-id", SYMLINK+="there"
+ TEST!="/etc/machine-id", SYMLINK+="notthere"
+@@ -1436,44 +1882,59 @@ EOF
+         },
+         {
+                 desc            => "TEST subsys/kernel lookup",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "yes",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "yes",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", TEST=="[net/eth0]", SYMLINK+="yes"
+ EOF
+         },
+         {
+                 desc            => "TEST relative path",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "relative",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "relative",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", TEST=="size", SYMLINK+="relative"
+ EOF
+         },
+         {
+                 desc            => "TEST wildcard substitution (find queue/nr_requests)",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "found-subdir",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "found-subdir",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", TEST=="*/nr_requests", SYMLINK+="found-subdir"
+ EOF
+         },
+         {
+                 desc            => "TEST MODE=0000",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "sda",
+-                exp_perms       => "0:0:0000",
+-                exp_rem_error   => "yes",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "sda",
++                                exp_perms       => "0:0:0000",
++                                exp_rem_error   => "yes",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", MODE="0000"
+ EOF
+         },
+         {
+                 desc            => "TEST PROGRAM feeds OWNER, GROUP, MODE",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "sda",
+-                exp_perms       => "1:1:0400",
+-                exp_rem_error   => "yes",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "sda",
++                                exp_perms       => "1:1:0400",
++                                exp_rem_error   => "yes",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", MODE="666"
+ KERNEL=="sda", PROGRAM=="/bin/echo 1 1 0400", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}"
+@@ -1481,10 +1942,13 @@ EOF
+         },
+         {
+                 desc            => "TEST PROGRAM feeds MODE with overflow",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "sda",
+-                exp_perms       => "0:0:0440",
+-                exp_rem_error   => "yes",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "sda",
++                                exp_perms       => "0:0:0440",
++                                exp_rem_error   => "yes",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", MODE="440"
+ KERNEL=="sda", PROGRAM=="/bin/echo 0 0 0400letsdoabuffferoverflow0123456789012345789012345678901234567890", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}"
+@@ -1492,9 +1956,12 @@ EOF
+         },
+         {
+                 desc            => "magic [subsys/sysname] attribute substitution",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "sda-8741C4G-end",
+-                exp_perms       => "0:0:0600",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "sda-8741C4G-end",
++                                exp_perms       => "0:0:0600",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", PROGRAM="/bin/true create-envp"
+ KERNEL=="sda", ENV{TESTENV}="change-envp"
+@@ -1503,8 +1970,11 @@ EOF
+         },
+         {
+                 desc            => "builtin path_id",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0",
++                        }],
+                 rules           => <<EOF
+ KERNEL=="sda", IMPORT{builtin}="path_id"
+ KERNEL=="sda", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/\$env{ID_PATH}"
+@@ -1512,9 +1982,12 @@ EOF
+         },
+         {
+                 desc            => "add and match tag",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "found",
+-                not_exp_name    => "bad",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "found",
++                                not_exp_name    => "bad" ,
++                        }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", TAG+="green"
+ TAGS=="green", SYMLINK+="found"
+@@ -1523,17 +1996,23 @@ EOF
+         },
+         {
+                 desc            => "don't crash with lots of tags",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "found",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "found",
++                        }],
+                 rules           => $rules_10k_tags . <<EOF
+ TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="found"
+ EOF
+         },
+         {
+                 desc            => "continuations",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "found",
+-                not_exp_name    => "bad",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "found",
++                                not_exp_name    => "bad",
++                        }],
+                 rules           => $rules_10k_tags_continuation . <<EOF
+ TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="bad"
+ KERNEL=="sda",\\
+@@ -1551,9 +2030,13 @@ EOF
+         },
+         {
+                 desc            => "continuations with empty line",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "found",
+-                not_exp_name    => "bad",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "found",
++                                not_exp_name    => "bad",
++
++                        }],
+                 rules           => <<EOF
+ # empty line finishes continuation
+ KERNEL=="sda", TAG+="foo" \\
+@@ -1563,13 +2046,16 @@ KERNEL=="sda", TAG+="aaa" \\
+ KERNEL=="sdb", TAG+="bbb"
+ TAGS=="foo", SYMLINK+="found"
+ TAGS=="aaa", SYMLINK+="bad"
+-EOF
++                    EOF
+         },
+         {
+                 desc            => "continuations with white only line",
+-                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                exp_name        => "found",
+-                not_exp_name    => "bad",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_name        => "found",
++                                not_exp_name    => "bad",
++                        }],
+                 rules           => <<EOF
+ # space only line finishes continuation
+ KERNEL=="sda", TAG+="foo" \\
+@@ -1709,43 +2195,33 @@ sub udev_setup {
+         return 1;
+ }
+ 
+-sub run_test {
+-        my ($rules, $number) = @_;
+-        my $rc;
+-
+-        print "TEST $number: $rules->{desc}\n";
+-        print "device \'$rules->{devpath}\' expecting node/link \'$rules->{exp_name}\'\n";
++sub check_add {
++        my ($device) = @_;
+ 
+-        $rc = udev("add", $rules->{devpath}, \$rules->{rules});
+-        if ($rc != 0) {
+-                print "$udev_bin add failed with code $rc\n";
+-                $error++;
+-        }
+-        if (defined($rules->{not_exp_name})) {
+-                if ((-e "$udev_dev/$rules->{not_exp_name}") ||
+-                    (-l "$udev_dev/$rules->{not_exp_name}")) {
+-                        print "nonexistent: error \'$rules->{not_exp_name}\' not expected to be there\n";
++        if (defined($device->{not_exp_name})) {
++                if ((-e "$udev_dev/$device->{not_exp_name}") ||
++                    (-l "$udev_dev/$device->{not_exp_name}")) {
++                        print "nonexistent: error \'$device->{not_exp_name}\' not expected to be there\n";
+                         $error++;
+                         sleep(1);
+                 }
+         }
+-
+-        if ((-e "$udev_dev/$rules->{exp_name}") ||
+-            (-l "$udev_dev/$rules->{exp_name}")) {
++        if ((-e "$udev_dev/$device->{exp_name}") ||
++            (-l "$udev_dev/$device->{exp_name}")) {
+ 
+                 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size,
+-                    $atime, $mtime, $ctime, $blksize, $blocks) = stat("$udev_dev/$rules->{exp_name}");
++                    $atime, $mtime, $ctime, $blksize, $blocks) = stat("$udev_dev/$device->{exp_name}");
+ 
+-                if (defined($rules->{exp_perms})) {
+-                        permissions_test($rules, $uid, $gid, $mode);
++                if (defined($device->{exp_perms})) {
++                        permissions_test($device, $uid, $gid, $mode);
+                 }
+-                if (defined($rules->{exp_majorminor})) {
+-                        major_minor_test($rules, $rdev);
++                if (defined($device->{exp_majorminor})) {
++                        major_minor_test($device, $rdev);
+                 }
+-                print "add:         ok\n";
++                print "add $device->{devpath}:         ok\n";
+         } else {
+-                print "add:         error";
+-                if ($rules->{exp_add_error}) {
++                print "add  $device->{devpath}:         error";
++                if ($device->{exp_add_error}) {
+                         print " as expected\n";
+                 } else {
+                         print "\n";
+@@ -1755,21 +2231,15 @@ sub run_test {
+                         sleep(1);
+                 }
+         }
++}
+ 
+-        if (defined($rules->{option}) && $rules->{option} eq "keep") {
+-                print "\n\n";
+-                return;
+-        }
++sub check_remove {
++        my ($device) = @_;
+ 
+-        $rc = udev("remove", $rules->{devpath}, \$rules->{rules});
+-        if ($rc != 0) {
+-                print "$udev_bin remove failed with code $rc\n";
+-                $error++;
+-        }
+-        if ((-e "$udev_dev/$rules->{exp_name}") ||
+-            (-l "$udev_dev/$rules->{exp_name}")) {
+-                print "remove:      error";
+-                if ($rules->{exp_rem_error}) {
++        if ((-e "$udev_dev/$device->{exp_name}") ||
++            (-l "$udev_dev/$device->{exp_name}")) {
++                print "remove  $device->{devpath}:      error";
++                if ($device->{exp_rem_error}) {
+                         print " as expected\n";
+                 } else {
+                         print "\n";
+@@ -1779,7 +2249,43 @@ sub run_test {
+                         sleep(1);
+                 }
+         } else {
+-                print "remove:      ok\n";
++                print "remove  $device->{devpath}:      ok\n";
++        }
++}
++
++sub run_test {
++        my ($rules, $number) = @_;
++        my $rc;
++        my @devices = @{$rules->{devices}};
++
++        print "TEST $number: $rules->{desc}\n";
++        foreach my $dev (@devices) {
++                print "device \'$dev->{devpath}\' expecting node/link \'$dev->{exp_name}\'\n";
++                $rc = udev("add", $dev->{devpath}, \$rules->{rules});
++                if ($rc != 0) {
++                        print "$udev_bin add failed with code $rc\n";
++                        $error++;
++                }
++        }
++
++        foreach my $dev (@devices) {
++                check_add($dev);
++        }
++
++        if (defined($rules->{option}) && $rules->{option} eq "keep") {
++                print "\n\n";
++                return;
++        }
++
++        foreach my $dev (@devices) {
++                $rc = udev("remove", $dev->{devpath}, \$rules->{rules});
++                if ($rc != 0) {
++                        print "$udev_bin remove failed with code $rc\n";
++                        $error++;
++                }
++        }
++        foreach my $dev (@devices) {
++                check_remove($dev);
+         }
+ 
+         print "\n";
diff --git a/SOURCES/0526-test-udev-test.pl-create-rules-only-once.patch b/SOURCES/0526-test-udev-test.pl-create-rules-only-once.patch
new file mode 100644
index 0000000..b394455
--- /dev/null
+++ b/SOURCES/0526-test-udev-test.pl-create-rules-only-once.patch
@@ -0,0 +1,61 @@
+From 9aa12f2f564c208c4c1eaef613d18d1c0b481a16 Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Mon, 23 Apr 2018 21:58:12 +0200
+Subject: [PATCH] test/udev-test.pl: create rules only once
+
+It's not necessary to write the rules for every udev run, as we
+now may have many (rather than just 2) per test.
+
+(cherry picked from commit af7ee3eae689f9c31b49ea13758ad9c901918ce3)
+
+Related: #1642728
+---
+ test/udev-test.pl | 13 +++++++++----
+ 1 file changed, 9 insertions(+), 4 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index bd5401da75..8b5a97ad61 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -2069,14 +2069,18 @@ EOF
+         },
+ );
+ 
+-sub udev {
+-        my ($action, $devpath, $rules) = @_;
++sub create_rules {
++        my ($rules) = @_;
+ 
+         # create temporary rules
+         system("mkdir", "-p", "$udev_rules_dir");
+         open CONF, ">$udev_rules" || die "unable to create rules file: $udev_rules";
+         print CONF $$rules;
+         close CONF;
++}
++
++sub udev {
++        my ($action, $devpath) = @_;
+ 
+         if ($valgrind > 0) {
+                 return system("$udev_bin_valgrind $action $devpath");
+@@ -2259,9 +2263,10 @@ sub run_test {
+         my @devices = @{$rules->{devices}};
+ 
+         print "TEST $number: $rules->{desc}\n";
++        create_rules(\$rules->{rules});
+         foreach my $dev (@devices) {
+                 print "device \'$dev->{devpath}\' expecting node/link \'$dev->{exp_name}\'\n";
+-                $rc = udev("add", $dev->{devpath}, \$rules->{rules});
++                $rc = udev("add", $dev->{devpath});
+                 if ($rc != 0) {
+                         print "$udev_bin add failed with code $rc\n";
+                         $error++;
+@@ -2278,7 +2283,7 @@ sub run_test {
+         }
+ 
+         foreach my $dev (@devices) {
+-                $rc = udev("remove", $dev->{devpath}, \$rules->{rules});
++                $rc = udev("remove", $dev->{devpath});
+                 if ($rc != 0) {
+                         print "$udev_bin remove failed with code $rc\n";
+                         $error++;
diff --git a/SOURCES/0527-test-udev-test.pl-allow-concurrent-additions-and-rem.patch b/SOURCES/0527-test-udev-test.pl-allow-concurrent-additions-and-rem.patch
new file mode 100644
index 0000000..f87307f
--- /dev/null
+++ b/SOURCES/0527-test-udev-test.pl-allow-concurrent-additions-and-rem.patch
@@ -0,0 +1,169 @@
+From 618d56c7ac8bd8cd701344a0eaca8373a78dea95 Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Mon, 23 Apr 2018 21:59:05 +0200
+Subject: [PATCH] test/udev-test.pl: allow concurrent additions and removals
+
+Allow testing cases where multiple devices are added and removed
+simultaneously. Tests are started as synchronously as possible using a
+semaphore, in order to test possible race conditions. If this isn't desired,
+the test parameter "sleep_us" can be set to the number of microseconds to wait
+between udev invocations.
+
+(cherry picked from commit 09a4062d70b3a10d022e40066e2adf09df05bbbc)
+
+Related: #1642728
+---
+ test/udev-test.pl | 90 +++++++++++++++++++++++++++++++++++++----------
+ 1 file changed, 72 insertions(+), 18 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 8b5a97ad61..db25ef13c1 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -18,6 +18,10 @@
+ 
+ use warnings;
+ use strict;
++use POSIX qw(WIFEXITED WEXITSTATUS);
++use IPC::SysV qw(IPC_PRIVATE S_IRUSR S_IWUSR IPC_CREAT);
++use IPC::Semaphore;
++use Time::HiRes qw(usleep);
+ 
+ my $udev_bin            = "./test-udev";
+ my $valgrind            = 0;
+@@ -2210,6 +2214,8 @@ sub check_add {
+                         sleep(1);
+                 }
+         }
++
++        print "device \'$device->{devpath}\' expecting node/link \'$device->{exp_name}\'\n";
+         if ((-e "$udev_dev/$device->{exp_name}") ||
+             (-l "$udev_dev/$device->{exp_name}")) {
+ 
+@@ -2257,21 +2263,72 @@ sub check_remove {
+         }
+ }
+ 
++sub run_udev {
++        my ($action, $dev, $sleep_us, $sema) = @_;
++
++        # Notify main process that this worker has started
++        $sema->op(0, 1, 0);
++
++        # Wait for start
++        $sema->op(0, 0, 0);
++        usleep($sleep_us) if defined ($sleep_us);
++        my $rc = udev($action, $dev->{devpath});
++        exit $rc;
++}
++
++sub fork_and_run_udev {
++        my ($action, $rules, $sema) = @_;
++        my @devices = @{$rules->{devices}};
++        my $dev;
++        my $k = 0;
++
++        $sema->setval(0, 1);
++        foreach $dev (@devices) {
++                my $pid = fork();
++
++                if (!$pid) {
++                        run_udev($action, $dev,
++                                 defined($rules->{sleep_us}) ? $k * $rules->{sleep_us} : undef,
++                                 $sema);
++                } else {
++                        $dev->{pid} = $pid;
++                }
++                $k++;
++        }
++
++        # This operation waits for all workers to become ready, and
++        # starts them off when that's the case.
++        $sema->op(0, -($#devices + 2), 0);
++
++        foreach $dev (@devices) {
++                my $rc;
++                my $pid;
++
++                $pid = waitpid($dev->{pid}, 0);
++                if ($pid == -1) {
++                        print "error waiting for pid dev->{pid}\n";
++                        $error += 1;
++                }
++                if (WIFEXITED($?)) {
++                        $rc = WEXITSTATUS($?);
++
++                        if ($rc) {
++                                print "$udev_bin $action for $dev->{devpath} failed with code $rc\n";
++                                $error += 1;
++                        }
++                }
++        }
++}
++
+ sub run_test {
+-        my ($rules, $number) = @_;
++        my ($rules, $number, $sema) = @_;
+         my $rc;
+         my @devices = @{$rules->{devices}};
+ 
+         print "TEST $number: $rules->{desc}\n";
+         create_rules(\$rules->{rules});
+-        foreach my $dev (@devices) {
+-                print "device \'$dev->{devpath}\' expecting node/link \'$dev->{exp_name}\'\n";
+-                $rc = udev("add", $dev->{devpath});
+-                if ($rc != 0) {
+-                        print "$udev_bin add failed with code $rc\n";
+-                        $error++;
+-                }
+-        }
++
++        fork_and_run_udev("add", $rules, $sema);
+ 
+         foreach my $dev (@devices) {
+                 check_add($dev);
+@@ -2282,13 +2339,8 @@ sub run_test {
+                 return;
+         }
+ 
+-        foreach my $dev (@devices) {
+-                $rc = udev("remove", $dev->{devpath});
+-                if ($rc != 0) {
+-                        print "$udev_bin remove failed with code $rc\n";
+-                        $error++;
+-                }
+-        }
++        fork_and_run_udev("remove", $rules, $sema);
++
+         foreach my $dev (@devices) {
+                 check_remove($dev);
+         }
+@@ -2350,12 +2402,13 @@ foreach my $arg (@ARGV) {
+                 push(@list, $arg);
+         }
+ }
++my $sema = IPC::Semaphore->new(IPC_PRIVATE, 1, S_IRUSR | S_IWUSR | IPC_CREAT);
+ 
+ if ($list[0]) {
+         foreach my $arg (@list) {
+                 if (defined($tests[$arg-1]->{desc})) {
+                         print "udev-test will run test number $arg:\n\n";
+-                        run_test($tests[$arg-1], $arg);
++                        run_test($tests[$arg-1], $arg, $sema);
+                 } else {
+                         print "test does not exist.\n";
+                 }
+@@ -2365,11 +2418,12 @@ if ($list[0]) {
+         print "\nudev-test will run ".($#tests + 1)." tests:\n\n";
+ 
+         foreach my $rules (@tests) {
+-                run_test($rules, $test_num);
++                run_test($rules, $test_num, $sema);
+                 $test_num++;
+         }
+ }
+ 
++$sema->remove;
+ print "$error errors occurred\n\n";
+ 
+ cleanup();
diff --git a/SOURCES/0528-test-udev-test.pl-use-computed-devnode-name.patch b/SOURCES/0528-test-udev-test.pl-use-computed-devnode-name.patch
new file mode 100644
index 0000000..a20ebbd
--- /dev/null
+++ b/SOURCES/0528-test-udev-test.pl-use-computed-devnode-name.patch
@@ -0,0 +1,260 @@
+From 5f34ea55a8c6723240eb1641a655db7df3c428a2 Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Tue, 24 Apr 2018 09:38:26 +0200
+Subject: [PATCH] test/udev-test.pl: use computed devnode name
+
+More often than not, the created devnode is the basename of the
+sysfs entry. The "devnode" device may be used to override the
+auto-detected node name.
+
+Permissions and major/minor number are now verified on the devnode
+itself, not on symlinks.
+
+For those tests where exp_name is set to the computed devnode name,
+the explicit "exp_name" can be removed. "exp_name" is only required for
+symlinks.
+
+This allows separate testing for devnodes and symlinks an a follow-up
+patch.
+
+(cherry picked from commit f0dccf01a7b4e72278e14effd74782ea83d0a73b)
+
+Related: #1642728
+---
+ test/udev-test.pl | 92 +++++++++++++++++++++++++++++++++--------------
+ 1 file changed, 66 insertions(+), 26 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index db25ef13c1..aa9a8dc2ff 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -55,12 +55,10 @@ my @tests = (
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "sda" ,
+                                 exp_rem_error   => "yes",
+                         },
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "sda1" ,
+                                 exp_rem_error   => "yes",
+                         }],
+                 rules           => <<EOF
+@@ -644,6 +642,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/virtual/block/fake!blockdev0",
++                                devnode         => "fake/blockdev0",
+                                 exp_name        => "is/a/fake/blockdev0" ,
+                         }],
+                 rules           => <<EOF
+@@ -657,7 +656,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/virtual/block/fake!blockdev0",
+-                                exp_name        => "fake/blockdev0" ,
++                                devnode         => "fake/blockdev0",
+                                 exp_rem_error   => "yes",
+                         }],
+                 rules           => <<EOF
+@@ -768,7 +767,6 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/virtual/tty/tty33",
+-                                exp_name        => "tty33",
+                                 exp_perms       => "0:0:0600",
+                         }],
+                 rules           => <<EOF
+@@ -864,7 +862,6 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "ttyACM0",
+                                 exp_perms       => "1::",
+                         }],
+                 rules           => <<EOF
+@@ -876,7 +873,6 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "ttyACM0",
+                                 exp_perms       => ":1:0660",
+                         }],
+                 rules           => <<EOF
+@@ -888,7 +884,6 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "ttyACM0",
+                                 exp_perms       => "::0060",
+                         }],
+                 rules           => <<EOF
+@@ -900,7 +895,6 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "ttyACM0",
+                                 exp_perms       => "1:1:0777",
+                         }],
+                 rules           => <<EOF
+@@ -912,7 +906,6 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "ttyACM0",
+                                 exp_perms       => "1:1:0777",
+                         }],
+                 rules           => <<EOF
+@@ -926,7 +919,6 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "ttyACM0",
+                                 exp_perms       => "1:1:0777",
+                         }],
+                 rules           => <<EOF
+@@ -942,7 +934,6 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "ttyACM0",
+                                 exp_perms       => "1:2:0777",
+                         }],
+                 rules           => <<EOF
+@@ -1922,7 +1913,6 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "sda",
+                                 exp_perms       => "0:0:0000",
+                                 exp_rem_error   => "yes",
+                         }],
+@@ -1935,7 +1925,6 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "sda",
+                                 exp_perms       => "1:1:0400",
+                                 exp_rem_error   => "yes",
+                         }],
+@@ -1949,7 +1938,6 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "sda",
+                                 exp_perms       => "0:0:0440",
+                                 exp_rem_error   => "yes",
+                         }],
+@@ -2203,6 +2191,44 @@ sub udev_setup {
+         return 1;
+ }
+ 
++sub get_devnode {
++        my ($device) = @_;
++        my $devnode;
++
++        if (defined($device->{devnode})) {
++                $devnode = "$udev_dev/$device->{devnode}";
++        } else {
++                $devnode = "$device->{devpath}";
++                $devnode =~ s!.*/!$udev_dev/!;
++        }
++        return $devnode;
++}
++
++sub check_devnode {
++        my ($device) = @_;
++        my $devnode = get_devnode($device);
++
++        my @st = lstat("$devnode");
++        if (! (-b _  || -c _)) {
++                print "add $devnode:         error\n";
++                system("tree", "$udev_dev");
++                $error++;
++                return undef;
++        }
++
++        my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size,
++            $atime, $mtime, $ctime, $blksize, $blocks) = @st;
++
++        if (defined($device->{exp_perms})) {
++                permissions_test($device, $uid, $gid, $mode);
++        }
++        if (defined($device->{exp_majorminor})) {
++                major_minor_test($device, $rdev);
++        }
++        print "add $devnode:         ok\n";
++        return $devnode;
++}
++
+ sub check_add {
+         my ($device) = @_;
+ 
+@@ -2215,19 +2241,13 @@ sub check_add {
+                 }
+         }
+ 
++        my $devnode = check_devnode($device);
++
+         print "device \'$device->{devpath}\' expecting node/link \'$device->{exp_name}\'\n";
++        return if (!defined($device->{exp_name}));
++
+         if ((-e "$udev_dev/$device->{exp_name}") ||
+             (-l "$udev_dev/$device->{exp_name}")) {
+-
+-                my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size,
+-                    $atime, $mtime, $ctime, $blksize, $blocks) = stat("$udev_dev/$device->{exp_name}");
+-
+-                if (defined($device->{exp_perms})) {
+-                        permissions_test($device, $uid, $gid, $mode);
+-                }
+-                if (defined($device->{exp_majorminor})) {
+-                        major_minor_test($device, $rdev);
+-                }
+                 print "add $device->{devpath}:         ok\n";
+         } else {
+                 print "add  $device->{devpath}:         error";
+@@ -2243,12 +2263,32 @@ sub check_add {
+         }
+ }
+ 
++sub check_remove_devnode {
++        my ($device) = @_;
++        my $devnode = get_devnode($device);
++
++        if (-e "$devnode") {
++                print "remove  $devnode:      error";
++                print "\n";
++                system("tree", "$udev_dev");
++                print "\n";
++                $error++;
++                sleep(1);
++        } else {
++                print "remove $devnode:         ok\n";
++        }
++}
++
+ sub check_remove {
+         my ($device) = @_;
+ 
++        check_remove_devnode($device);
++
++        return if (!defined($device->{exp_name}));
++
+         if ((-e "$udev_dev/$device->{exp_name}") ||
+             (-l "$udev_dev/$device->{exp_name}")) {
+-                print "remove  $device->{devpath}:      error";
++                print "remove  $device->{exp_name}:      error";
+                 if ($device->{exp_rem_error}) {
+                         print " as expected\n";
+                 } else {
+@@ -2259,7 +2299,7 @@ sub check_remove {
+                         sleep(1);
+                 }
+         } else {
+-                print "remove  $device->{devpath}:      ok\n";
++                print "remove  $device->{exp_name}:      ok\n";
+         }
+ }
+ 
diff --git a/SOURCES/0529-test-udev-test.pl-test-correctness-of-symlink-target.patch b/SOURCES/0529-test-udev-test.pl-test-correctness-of-symlink-target.patch
new file mode 100644
index 0000000..3c6e624
--- /dev/null
+++ b/SOURCES/0529-test-udev-test.pl-test-correctness-of-symlink-target.patch
@@ -0,0 +1,61 @@
+From 8ee1cc626f616a2022d641a464fbde9108dd8ad9 Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Tue, 24 Apr 2018 10:50:24 +0200
+Subject: [PATCH] test/udev-test.pl: test correctness of symlink targets
+
+Test if symlinks are created correctly by comparing the symlink
+targets to the devnode path. This implies (for the symlink) that
+major/minor numbers and permissions are correct, as we have tested
+that on the devnode already.
+
+(cherry picked from commit 997683c8f152e1c139a7ce537de81a0aeae4627f)
+
+Related: #1642728
+---
+ test/udev-test.pl | 23 ++++++++++++++++++-----
+ 1 file changed, 18 insertions(+), 5 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index aa9a8dc2ff..2e3089c5e0 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -22,6 +22,7 @@ use POSIX qw(WIFEXITED WEXITSTATUS);
+ use IPC::SysV qw(IPC_PRIVATE S_IRUSR S_IWUSR IPC_CREAT);
+ use IPC::Semaphore;
+ use Time::HiRes qw(usleep);
++use Cwd qw(getcwd abs_path);
+ 
+ my $udev_bin            = "./test-udev";
+ my $valgrind            = 0;
+@@ -2243,14 +2244,26 @@ sub check_add {
+ 
+         my $devnode = check_devnode($device);
+ 
+-        print "device \'$device->{devpath}\' expecting node/link \'$device->{exp_name}\'\n";
+         return if (!defined($device->{exp_name}));
+ 
+-        if ((-e "$udev_dev/$device->{exp_name}") ||
+-            (-l "$udev_dev/$device->{exp_name}")) {
+-                print "add $device->{devpath}:         ok\n";
++        my @st = lstat("$udev_dev/$device->{exp_name}");
++        if (-l _) {
++                my $cwd = getcwd();
++                my $dir = "$udev_dev/$device->{exp_name}";
++                $dir =~ s!/[^/]*$!!;
++                my $tgt = readlink("$udev_dev/$device->{exp_name}");
++                $tgt = abs_path("$dir/$tgt");
++                $tgt =~ s!^$cwd/!!;
++
++                if ($tgt ne $devnode) {
++                        print "symlink $device->{exp_name}:         error, found -> $tgt\n";
++                        $error++;
++                        system("tree", "$udev_dev");
++                } else {
++                        print "symlink $device->{exp_name}:         ok\n";
++                }
+         } else {
+-                print "add  $device->{devpath}:         error";
++                print "symlink $device->{exp_name}:         error";
+                 if ($device->{exp_add_error}) {
+                         print " as expected\n";
+                 } else {
diff --git a/SOURCES/0530-test-udev-test.pl-allow-checking-multiple-symlinks.patch b/SOURCES/0530-test-udev-test.pl-allow-checking-multiple-symlinks.patch
new file mode 100644
index 0000000..f004054
--- /dev/null
+++ b/SOURCES/0530-test-udev-test.pl-allow-checking-multiple-symlinks.patch
@@ -0,0 +1,1607 @@
+From fb8d10456d7d5a085e1adb5bfd45f1cda813ac22 Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Tue, 24 Apr 2018 17:15:58 +0200
+Subject: [PATCH] test/udev-test.pl: allow checking multiple symlinks
+
+Instead of testing the existence or non-exisitence of just a single
+symlink, allow testing of several links per device.
+
+Change the test definitions accordingly.
+
+(cherry picked from commit e62acc3159935781f05fa59c48e5a74e85c61ce2)
+
+Related: #1642728
+---
+ test/udev-test.pl | 495 +++++++++++++++++++++++++++-------------------
+ 1 file changed, 296 insertions(+), 199 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 2e3089c5e0..f5edecefd0 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -71,7 +71,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "boot_disk" ,
++                                exp_links       => ["boot_disk"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+@@ -83,7 +83,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "boot_disk" ,
++                                exp_links       => ["boot_disk"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+@@ -95,7 +95,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "boot_disk" ,
++                                exp_links       => ["boot_disk"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+@@ -107,7 +107,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "boot_disk1" ,
++                                exp_links       => ["boot_disk1"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
+@@ -118,13 +118,16 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "boot_disk1" ,
++                                exp_links       => ["boot_disk1", "boot_disk1-4", "boot_disk1-5"],
++                                not_exp_links   => ["boot_disk1-1", "boot_disk1-2", "boot_disk1-3"]
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="?ATA", SYMLINK+="boot_disk%n-1"
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA?", SYMLINK+="boot_disk%n-2"
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="A??", SYMLINK+="boot_disk%n"
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATAS", SYMLINK+="boot_disk%n-3"
++SUBSYSTEMS=="scsi", ATTRS{vendor}=="AT?", SYMLINK+="boot_disk%n-4"
++SUBSYSTEMS=="scsi", ATTRS{vendor}=="??A", SYMLINK+="boot_disk%n-5"
+ EOF
+         },
+         {
+@@ -132,7 +135,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "boot_disk1" ,
++                                exp_links       => ["boot_disk1"],
++                                not_exp_links   => ["boot_diskX1"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS X ", SYMLINK+="boot_diskX%n"
+@@ -144,10 +148,12 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "boot_disk1" ,
++                                exp_links       => ["boot_disk1", "boot_diskXY1"],
++                                not_exp_links   => ["boot_diskXX1"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="32", SYMLINK+="boot_diskXX%n"
++SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="1", SYMLINK+="boot_diskXY%n"
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n"
+ EOF
+         },
+@@ -156,18 +162,21 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "modem/0" ,
++                                exp_links       => ["modem/0", "catch-all"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM*", SYMLINK+="modem/%n"
++KERNEL=="*", SYMLINK+="catch-all"
+ EOF
+         },
++        # 10
+         {
+                 desc            => "catch device by * - take 2",
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "modem/0" ,
++                                exp_links       => ["modem/0"],
++                                not_exp_links   => ["bad"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="*ACM1", SYMLINK+="bad"
+@@ -179,7 +188,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "modem/0" ,
++                                exp_links       => ["modem/0"],
++                                not_exp_links   => ["modem/0-1", "modem/0-2"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM??*", SYMLINK+="modem/%n-1"
+@@ -192,7 +202,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "modem/0" ,
++                                exp_links       => ["modem/0"],
++                                not_exp_links   => ["modem/0-1", "modem/0-2"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[A-Z]*", SYMLINK+="modem/%n-1"
+@@ -205,7 +216,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "modem" ,
++                                exp_links       => ["modem"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -216,7 +227,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "modem" ,
++                                exp_links       => ["modem"],
+                         }],
+                 rules           => <<EOF
+ # this is a comment
+@@ -229,7 +240,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "modem" ,
++                                exp_links       => ["modem"],
+                         }],
+                 rules           => <<EOF
+  # this is a comment with whitespace before the comment
+@@ -242,7 +253,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "whitespace" ,
++                                exp_links       => ["whitespace"],
+                         }],
+                 rules           => <<EOF
+ 
+@@ -260,7 +271,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "modem" ,
++                                exp_links       => ["modem"],
+                         }],
+                 rules           => <<EOF
+ 
+@@ -273,7 +284,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "modem" ,
++                                exp_links       => ["modem"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", \\
+@@ -286,7 +297,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "aaa",
++                                exp_links       => ["aaa"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", PROGRAM=="/bin/echo -e \\101", RESULT=="A", SYMLINK+="aaa"
+@@ -297,7 +308,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "modem" ,
++                                exp_links       => ["modem"],
+                         }],
+                 rules           => <<EOF
+ 
+@@ -318,7 +329,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "sub/direct/ory/modem" ,
++                                exp_links       => ["sub/direct/ory/modem"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", SYMLINK+="sub/direct/ory/modem"
+@@ -329,7 +340,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "first_disk5" ,
++                                exp_links       => ["first_disk5"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="first_disk%n"
+@@ -340,7 +351,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "Major:8:minor:5:kernelnumber:5:id:0:0:0:0" ,
++                                exp_links       => ["Major:8:minor:5:kernelnumber:5:id:0:0:0:0"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:%M:minor:%m:kernelnumber:%n:id:%b"
+@@ -351,7 +362,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "node12345678",
++                                exp_links       => ["node12345678"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", IMPORT{program}="/bin/echo -e \' TEST_KEY=12345678\\n  TEST_key2=98765\'", SYMLINK+="node\$env{TEST_KEY}"
+@@ -363,7 +374,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "disk-ATA-sda" ,
++                                exp_links       => ["disk-ATA-sda"],
++                                not_exp_links   => ["modem"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="disk-%s{vendor}-%k"
+@@ -375,8 +387,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "special-device-5" ,
+-                                not_exp_name    => "not" ,
++                                exp_links       => ["special-device-5"],
++                                not_exp_links   => ["not"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="-special-*", SYMLINK+="not"
+@@ -388,7 +400,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "newline_removed" ,
++                                exp_links       => ["newline_removed"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo test", RESULT=="test", SYMLINK+="newline_removed"
+@@ -399,7 +411,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "test-0:0:0:0" ,
++                                exp_links       => ["test-0:0:0:0"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n test-%b", RESULT=="test-0:0*", SYMLINK+="%c"
+@@ -410,7 +422,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "foo9" ,
++                                exp_links       => ["foo9"],
++                                not_exp_links   => ["foo3", "foo4", "foo5", "foo6", "foo7", "foo8"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="%c{7}"
+@@ -421,7 +434,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "bar9" ,
++                                exp_links       => ["bar9"],
++                                not_exp_links   => ["foo3", "foo4", "foo5", "foo6", "foo7", "foo8"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'echo foo3 foo4 foo5 foo6 foo7 foo8 foo9 | sed  s/foo9/bar9/'", KERNEL=="sda5", SYMLINK+="%c{7}"
+@@ -432,7 +446,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "foo7" ,
++                                exp_links       => ["foo7"],
++                                not_exp_links   => ["foo3", "foo4", "foo5", "foo6", "foo8"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n 'foo3 foo4'   'foo5   foo6   foo7 foo8'", KERNEL=="sda5", SYMLINK+="%c{5}"
+@@ -443,7 +458,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "foo2" ,
++                                exp_links       => ["foo2"],
++                                not_exp_links   => ["foo1"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'printf %%s \\\"foo1 foo2\\\" | grep \\\"foo1 foo2\\\"'", KERNEL=="sda5", SYMLINK+="%c{2}"
+@@ -454,7 +470,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "foo2" ,
++                                exp_links       => ["foo2"],
++                                not_exp_links   => ["foo1"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c \\\"printf %%s 'foo1 foo2' | grep 'foo1 foo2'\\\"", KERNEL=="sda5", SYMLINK+="%c{2}"
+@@ -465,7 +482,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "foo2" ,
++                                exp_links       => ["foo2"],
++                                not_exp_links   => ["foo1", "foo3"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'printf \\\"%%s %%s\\\" \\\"foo1 foo2\\\" \\\"foo3\\\"| grep \\\"foo1 foo2\\\"'", KERNEL=="sda5", SYMLINK+="%c{2}"
+@@ -476,7 +494,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "my-foo9" ,
++                                exp_links       => ["my-foo9"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{7}"
+@@ -487,7 +505,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "my-foo8" ,
++                                exp_links       => ["my-foo8"],
++                                not_exp_links   => ["my-foo3", "my-foo4", "my-foo5", "my-foo6", "my-foo7", "my-foo9"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{6}"
+@@ -498,7 +517,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "Major:8-minor:5-kernelnumber:5-id:0:0:0:0",
++                                exp_links       => ["Major:8-minor:5-kernelnumber:5-id:0:0:0:0"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:\$major-minor:\$minor-kernelnumber:\$number-id:\$id"
+@@ -509,7 +528,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "Major:8-minor:5-kernelnumber:5-id:0:0:0:0",
++                                exp_links       => ["Major:8-minor:5-kernelnumber:5-id:0:0:0:0"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="Major:\$major-minor:%m-kernelnumber:\$number-id:\$id"
+@@ -520,7 +539,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "850:0:0:05" ,
++                                exp_links       => ["850:0:0:05"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="%M%m%b%n"
+@@ -531,7 +550,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "855" ,
++                                exp_links       => ["855"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="\$major\$minor\$number"
+@@ -542,7 +561,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "8550:0:0:0" ,
++                                exp_links       => ["8550:0:0:0"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="\$major%m%n\$id"
+@@ -553,7 +572,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/virtual/tty/console",
+-                                exp_name        => "TTY",
++                                exp_links       => ["TTY"],
++                                not_exp_links   => ["foo"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo", RESULT=="foo", SYMLINK+="foo"
+@@ -565,7 +585,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/virtual/tty/console",
+-                                exp_name        => "TTY" ,
++                                exp_links       => ["TTY"],
++                                not_exp_links   => ["foo"],
+                         }],
+                 rules                => <<EOF
+ SUBSYSTEMS=="foo", ATTRS{dev}=="5:1", SYMLINK+="foo"
+@@ -577,7 +598,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/virtual/tty/console",
+-                                exp_name        => "foo" ,
++                                exp_links       => ["foo", "TTY"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="console", SYMLINK+="TTY"
+@@ -589,7 +610,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "empty" ,
++                                exp_links       => ["empty", "not-something"],
++                                not_exp_links   => ["something", "not-empty"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda", ATTR{test_empty_file}=="?*", SYMLINK+="something"
+@@ -603,7 +625,9 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "non-existent" ,
++                                exp_links       => ["non-existent", "wrong"],
++                                not_exp_links   => ["something", "empty", "not-empty",
++                                                    "not-something", "something"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda", ATTR{nofile}=="?*", SYMLINK+="something"
+@@ -619,7 +643,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "scsi-0:0:0:0" ,
++                                exp_links       => ["scsi-0:0:0:0"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="usb", PROGRAM=="/bin/echo -n usb-%b", SYMLINK+="%c"
+@@ -632,7 +656,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "modem" ,
++                                exp_links       => ["modem"],
+                         }],
+                 rules           => <<EOF
+ ATTRS{idProduct}=="007b", SYMLINK+="modem"
+@@ -644,7 +668,8 @@ EOF
+                         {
+                                 devpath         => "/devices/virtual/block/fake!blockdev0",
+                                 devnode         => "fake/blockdev0",
+-                                exp_name        => "is/a/fake/blockdev0" ,
++                                exp_links       => ["is/a/fake/blockdev0"],
++                                not_exp_links       => ["is/not/a/fake/blockdev0", "modem"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", SYMLINK+="is/not/a/%k"
+@@ -658,7 +683,7 @@ EOF
+                         {
+                                 devpath         => "/devices/virtual/block/fake!blockdev0",
+                                 devnode         => "fake/blockdev0",
+-                                exp_rem_error   => "yes",
++                                not_exp_links       => ["modem"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", SYMLINK+="modem"
+@@ -669,7 +694,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "scsi-0:0:0:0",
++                                exp_links       => ["scsi-0:0:0:0"],
++                                not_exp_links       => ["no-match", "short-id", "not-scsi"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="usb", KERNELS=="0:0:0:0", SYMLINK+="not-scsi"
+@@ -684,7 +710,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "scsi-0:0:0:0",
++                                exp_links       => ["scsi-0:0:0:0"],
++                                not_exp_links   => ["no-match", "before"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="*:1", SYMLINK+="no-match"
+@@ -699,7 +726,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "scsi-0:0:0:0",
++                                exp_links       => ["scsi-0:0:0:0", "before"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before"
+@@ -711,7 +738,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "scsi-0:0:0:0",
++                                exp_links       => ["scsi-0:0:0:0", "before"],
+                         }],
+                 rules                => <<EOF
+ SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before"
+@@ -723,7 +750,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "driver-is-sd",
++                                exp_links       => ["driver-is-sd"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", SYMLINK+="driver-is-\$attr{driver}"
+@@ -734,7 +761,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "driver-is-ahci",
++                                exp_links       => ["driver-is-ahci"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="pci", SYMLINK+="driver-is-\$attr{driver}"
+@@ -745,7 +772,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "ignored",
++                                exp_links       => ["ignored"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE  SPACE", SYMLINK+="ignored"
+@@ -756,7 +783,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "matched-with-space",
++                                exp_links       => ["matched-with-space"],
++                                not_exp_links   => ["wrong-to-ignore"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE  SPACE ", SYMLINK+="wrong-to-ignore"
+@@ -779,7 +807,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "node",
++                                exp_links       => ["node"],
+                                 exp_perms       => "1::0600",
+                         }],
+                 rules           => <<EOF
+@@ -791,7 +819,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "node",
++                                exp_links       => ["node"],
+                                 exp_perms       => ":1:0660",
+                         }],
+                 rules           => <<EOF
+@@ -803,7 +831,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "node",
++                                exp_links       => ["node"],
+                                 exp_perms       => "daemon::0600",
+                         }],
+                 rules           => <<EOF
+@@ -815,7 +843,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "node",
++                                exp_links       => ["node"],
+                                 exp_perms       => ":daemon:0660",
+                         }],
+                 rules           => <<EOF
+@@ -827,7 +855,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "node",
++                                exp_links       => ["node"],
+                                 exp_perms       => "root:audio:0660",
+                         }],
+                 rules           => <<EOF
+@@ -839,7 +867,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "node",
++                                exp_links       => ["node"],
+                                 exp_perms       => "::0777",
+                         }],
+                 rules           => <<EOF
+@@ -851,7 +879,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "node",
++                                exp_links       => ["node"],
+                                 exp_perms       => "1:1:0777",
+                         }],
+                 rules           => <<EOF
+@@ -950,7 +978,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "node",
++                                exp_links       => ["node"],
+                                 exp_majorminor  => "8:0",
+                         }],
+                 rules           => <<EOF
+@@ -962,7 +990,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/virtual/misc/misc-fake1",
+-                                exp_name        => "node",
++                                exp_links       => ["node"],
+                                 exp_majorminor  => "4095:1",
+                         }],
+                 rules                => <<EOF
+@@ -974,7 +1002,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/virtual/misc/misc-fake89999",
+-                                exp_name        => "node",
++                                exp_links       => ["node"],
+                                 exp_majorminor  => "4095:89999",
+                         }],
+                 rules           => <<EOF
+@@ -986,7 +1014,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "symlink2-ttyACM0",
++                                exp_links       => ["symlink1-0", "symlink2-ttyACM0", "symlink3-"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK="symlink1-%n symlink2-%k symlink3-%b"
+@@ -997,8 +1025,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "one",
+-                                not_exp_name        => " ",
++                                exp_links       => ["one", "two"],
++                                not_exp_links       => [" "],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK="  one     two        "
+@@ -1009,8 +1037,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "name-one_two_three-end",
+-                                not_exp_name    => " ",
++                                exp_links       => ["name-one_two_three-end"],
++                                not_exp_links   => [" "],
+                         }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="one two three"
+@@ -1022,8 +1050,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "name-one_two_three-end",
+-                                not_exp_name    => " ",
++                                exp_links       => ["name-one_two_three-end"],
++                                not_exp_links   => [" "],
+                         }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="   one two three"
+@@ -1035,8 +1063,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "name-one_two_three-end",
+-                                not_exp_name    => " ",
++                                exp_links       => ["name-one_two_three-end"],
++                                not_exp_links   => [" "],
+                         }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="one two three   "
+@@ -1048,8 +1076,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "name-one_two_three-end",
+-                                not_exp_name    => " ",
++                                exp_links       => ["name-one_two_three-end"],
++                                not_exp_links   => [" "],
+                         }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="   one two three   "
+@@ -1061,8 +1089,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "name-one_two_three-end",
+-                                not_exp_name    => " ",
++                                exp_links       => ["name-one_two_three-end"],
++                                not_exp_links   => [" "],
+                         }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="   one  two  three   "
+@@ -1070,12 +1098,12 @@ SYMLINK="name-\$env{WITH_WS}-end"
+ EOF
+         },
+         {
+-                desc            => "symlink with space and var with space, part 1",
++                desc            => "symlink with space and var with space",
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "first",
+-                                not_exp_name    => " ",
++                                exp_links        => ["first"],
++                                not_exp_links    => [" "],
+                         }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="   one  two  three   "
+@@ -1087,8 +1115,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "name-one_two_three-end",
+-                                not_exp_name    => " ",
++                                exp_links        => ["name-one_two_three-end"],
++                                not_exp_links    => [" "],
+                         }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="   one  two  three   "
+@@ -1100,8 +1128,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "another_symlink",
+-                                not_exp_name    => " ",
++                                exp_links        => ["another_symlink"],
++                                not_exp_links    => [" "],
+                         }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="   one  two  three   "
+@@ -1113,7 +1141,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "modem0",
++                                exp_links       => ["modem0"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK="modem%n"
+@@ -1124,7 +1152,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "second-0" ,
++                                exp_links       => ["first-0", "second-0", "third-0"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM0", SYMLINK="first-%n second-%n third-%n"
+@@ -1135,7 +1163,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => ".",
++                                exp_links       => ["."],
+                                 exp_add_error        => "yes",
+                                 exp_rem_error        => "yes",
+                         }],
+@@ -1148,7 +1176,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/virtual/tty/tty0",
+-                                exp_name        => "link",
++                                exp_links       => ["link"],
+                                 exp_add_error        => "yes",
+                                 exp_rem_error        => "yes",
+                         }],
+@@ -1162,7 +1190,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "symlink0",
++                                exp_links       => ["symlink0"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink%n"
+@@ -1173,7 +1201,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "symlink-ttyACM0",
++                                exp_links       => ["symlink-ttyACM0"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink-%k"
+@@ -1184,7 +1212,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "major-166:0",
++                                exp_links       => ["major-166:0"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="major-%M:%m"
+@@ -1195,7 +1223,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "symlink-0:0:0:0",
++                                exp_links       => ["symlink-0:0:0:0"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="symlink-%b"
+@@ -1206,7 +1234,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "test",
++                                exp_links       => ["test"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo test", SYMLINK+="%c"
+@@ -1217,7 +1245,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "test",
++                                exp_links       => ["test"],
++                                not_exp_links   => ["symlink", "this"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2}"
+@@ -1228,7 +1257,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "this",
++                                exp_links       => ["test", "this"],
++                                not_exp_links   => ["symlink"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2+}"
+@@ -1239,7 +1269,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "test",
++                                exp_links       => ["test", "this"],
++                                not_exp_links   => ["symlink"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/bin/echo link test this" SYMLINK+="%c{2+}"
+@@ -1250,7 +1281,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "166:0",
++                                exp_links       => ["166:0"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="%s{dev}"
+@@ -1261,7 +1292,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "link1",
++                                exp_links       => ["link1", "link2"],
++                                not_exp_links   => ["node"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2", RESULT=="node *", SYMLINK+="%c{2} %c{3}"
+@@ -1272,7 +1304,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
+-                                exp_name        => "link4",
++                                exp_links       => ["link1", "link2", "link3", "link4"],
++                                not_exp_links   => ["node"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2 link3 link4", RESULT=="node *", SYMLINK+="%c{2+}"
+@@ -1283,7 +1316,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "node",
++                                exp_links       => ["node"],
++                                not_exp_links   => ["should_not_match", "should_not_match2"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", SUBSYSTEM=="vc"
+@@ -1296,7 +1330,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "node",
++                                exp_links       => ["node"],
++                                not_exp_links   => ["should_not_match"]
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", DRIVERS=="sd-wrong"
+@@ -1308,7 +1343,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "node",
++                                exp_links       => ["node"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/usr/bin/test -b %N" SYMLINK+="node"
+@@ -1319,7 +1354,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "sda-part-1",
++                                exp_links       => ["sda-part-1"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="%P-part-1"
+@@ -1330,7 +1365,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "start-/dev-end",
++                                exp_links       => ["start-/dev-end"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="start-%r-end"
+@@ -1341,7 +1376,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "last",
++                                exp_links       => ["last"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="last", OPTIONS="last_rule"
+@@ -1353,7 +1388,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "match",
++                                exp_links       => ["match", "before"],
++                                not_exp_links   => ["matches-but-is-negated"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL!="sda1", SYMLINK+="matches-but-is-negated"
+@@ -1366,7 +1402,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "not-anything",
++                                exp_links       => ["before", "not-anything"],
++                                not_exp_links   => ["matches-but-is-negated"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", SUBSYSTEM=="block", KERNEL!="sda1", SYMLINK+="matches-but-is-negated"
+@@ -1379,7 +1416,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "nonzero-program",
++                                exp_links       => ["before", "nonzero-program"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
+@@ -1391,7 +1428,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "true",
++                                exp_links       => ["true"],
++                                not_exp_links   => ["bad", "wrong"],
+                         }],
+                 rules           => <<EOF
+ ENV{ENV_KEY_TEST}="test"
+@@ -1405,7 +1443,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "true",
++                                exp_links       => ["true"],
++                                not_exp_links   => ["bad", "wrong", "no"],
+                         }],
+                 rules           => <<EOF
+ ENV{ENV_KEY_TEST}="test"
+@@ -1420,7 +1459,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "true",
++                                exp_links       => ["true", "before"],
++                                not_exp_links   => ["no"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true"
+@@ -1434,7 +1474,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "true",
++                                exp_links       => ["true", "before"],
++                                not_exp_links   => ["no", "bad"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true"
+@@ -1449,8 +1490,10 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "part",
+-                        }],
++                                exp_links       => ["part"],
++                                not_exp_links   => ["disk"],
++                        },
++                    ],
+                 rules           => <<EOF
+ SUBSYSTEM=="block", KERNEL=="*[0-9]", ENV{PARTITION}="true", ENV{MAINDEVICE}="false"
+ SUBSYSTEM=="block", KERNEL=="*[!0-9]", ENV{PARTITION}="false", ENV{MAINDEVICE}="true"
+@@ -1464,7 +1507,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "sane",
++                                exp_links       => ["sane"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e name; (/usr/bin/badprogram)", RESULT=="name_ _/usr/bin/badprogram_", SYMLINK+="sane"
+@@ -1475,7 +1518,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "uber",
++                                exp_links       => ["uber"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \\xc3\\xbcber" RESULT=="\xc3\xbcber", SYMLINK+="uber"
+@@ -1486,7 +1529,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "replaced",
++                                exp_links       => ["replaced"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \\xef\\xe8garbage", RESULT=="__garbage", SYMLINK+="replaced"
+@@ -1497,7 +1540,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "serial-354172020305000",
++                                exp_links       => ["serial-354172020305000"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM*", ATTRS{serial}=="?*", SYMLINK+="serial-%s{serial}"
+@@ -1508,7 +1551,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "ok",
++                                exp_links       => ["ok"],
++                                not_exp_links   => ["not-1-ok", "not-2-ok", "not-3-ok"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda", ATTRS{nothing}!="", SYMLINK+="not-1-ok"
+@@ -1522,7 +1566,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "ok",
++                                exp_links       => ["ok"],
++                                not_exp_links   => ["unknown-not-ok"],
+                         }],
+                 rules           => <<EOF
+ ACTION=="unknown", KERNEL=="sda", SYMLINK+="unknown-not-ok"
+@@ -1534,7 +1579,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "ok",
++                                exp_links       => ["ok"],
+                                 exp_perms       => "root:tty:0640",
+                         }],
+                 rules           => <<EOF
+@@ -1547,7 +1592,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "ok",
++                                exp_links       => ["ok"],
+                                 exp_perms       => "root:tty:0640",
+                         }],
+                 rules           => <<EOF
+@@ -1561,7 +1606,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "node-add-me",
++                                exp_links       => ["node-add-me"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda", MODE="0666", SYMLINK+="node-\$env{ACTION}-me"
+@@ -1572,8 +1617,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "three",
+-                                not_exp_name    => "two",
++                                exp_links       => ["three"],
++                                not_exp_links   => ["two", "one"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="one"
+@@ -1586,8 +1631,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "right",
+-                                not_exp_name    => "wrong",
++                                exp_links       => ["right"],
++                                not_exp_links   => ["wrong"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM[0-9]*", SYMLINK+="wrong"
+@@ -1600,7 +1645,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "right",
++                                exp_links       => ["right", "before"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="ttyACM*", SYMLINK+="before"
+@@ -1612,7 +1657,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "right",
++                                exp_links       => ["right", "before"],
++                                not_exp_links   => ["nomatch"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="dontknow*|*nothing", SYMLINK+="nomatch"
+@@ -1625,7 +1671,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "right",
++                                exp_links       => ["right"],
++                                not_exp_links   => ["nomatch", "wrong1", "wrong2"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="dontknow|nothing", SYMLINK+="nomatch"
+@@ -1639,7 +1686,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_name        => "right",
++                                exp_links       => ["right"],
++                                not_exp_links   => ["nomatch", "wrong1", "wrong2", "wrong3"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="dontknow|nothing", SYMLINK+="nomatch"
+@@ -1654,7 +1702,7 @@ EOF
+                devices => [
+                        {
+                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                               exp_name        => "found",
++                               exp_links       => ["found"],
+                                not_exp_name    => "bad",
+                        }],
+                 rules           => <<EOF
+@@ -1668,7 +1716,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "found",
++                                exp_links       => ["found"],
+                                 not_exp_name    => "bad",
+                        }],
+                 rules           => <<EOF
+@@ -1682,7 +1730,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "found",
++                                exp_links       => ["found"],
+                                 not_exp_name    => "bad",
+                         }],
+                 rules           => <<EOF
+@@ -1696,7 +1744,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "found",
++                                exp_links       => ["found"],
+                                 not_exp_name    => "bad",
+                         }],
+                 rules           => <<EOF
+@@ -1710,7 +1758,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "found",
++                                exp_links       => ["found"],
+                                 not_exp_name    => "bad",
+                         }],
+                 rules           => <<EOF
+@@ -1724,7 +1772,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "found",
++                                exp_links       => ["found"],
+                                 not_exp_name    => "bad",
+                         }],
+                 rules           => <<EOF
+@@ -1738,7 +1786,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "found",
++                                exp_links       => ["found"],
+                                 not_exp_name    => "bad",
+                         }],
+                 rules           => <<EOF
+@@ -1752,7 +1800,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "parent",
++                                exp_links       => ["parent"],
+                         }],
+                 option          => "keep",
+                 rules           => <<EOF
+@@ -1765,7 +1813,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "parentenv-parent_right",
++                                exp_links       => ["parentenv-parent_right"],
+                         }],
+                 option          => "clean",
+                 rules           => <<EOF
+@@ -1777,7 +1825,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "right",
++                                exp_links       => ["right"],
++                                not_exp_test    => ["wrong", "wrong2"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda1", GOTO="TEST"
+@@ -1794,7 +1843,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "right",
++                                exp_links       => ["right"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda1", GOTO="does-not-exist"
+@@ -1807,8 +1856,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "right",
+-                                not_exp_name    => "wrong",
++                                exp_links       => ["right", "link"],
++                                not_exp_links   => ["wrong"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda1", SYMLINK+="link"
+@@ -1821,7 +1870,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "yes",
++                                exp_links       => ["yes"],
++                                not_exp_links   => ["no"],
+                         }],
+                 rules           => <<EOF
+ KERNEL="sda1", SYMLINK+="no"
+@@ -1833,7 +1883,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "yes",
++                                exp_links       => ["yes"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda", ATTR{test:colon+plus}=="?*", SYMLINK+="yes"
+@@ -1844,7 +1894,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+-                                exp_name        => "yes",
++                                exp_links       => ["yes"],
++                                not_exp_links   => ["no"],
+                         }],
+                 rules           => <<EOF
+ # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+@@ -1858,7 +1909,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "00:16:41:e2:8d:ff",
++                                exp_links       => ["00:16:41:e2:8d:ff"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda", SYMLINK+="\$attr{[net/eth0]address}"
+@@ -1869,7 +1920,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "there",
++                                exp_links       => ["there"],
++                                not_exp_links   => ["notthere"],
+                         }],
+                 rules           => <<EOF
+ TEST=="/etc/machine-id", SYMLINK+="there"
+@@ -1881,7 +1933,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "yes",
++                                exp_links       => ["yes"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda", TEST=="[net/eth0]", SYMLINK+="yes"
+@@ -1892,7 +1944,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "relative",
++                                exp_links       => ["relative"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda", TEST=="size", SYMLINK+="relative"
+@@ -1903,7 +1955,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "found-subdir",
++                                exp_links       => ["found-subdir"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda", TEST=="*/nr_requests", SYMLINK+="found-subdir"
+@@ -1952,7 +2004,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "sda-8741C4G-end",
++                                exp_links       => ["sda-8741C4G-end"],
+                                 exp_perms       => "0:0:0600",
+                         }],
+                 rules           => <<EOF
+@@ -1966,7 +2018,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0",
++                                exp_links       => ["disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0"],
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda", IMPORT{builtin}="path_id"
+@@ -1978,8 +2030,8 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "found",
+-                                not_exp_name    => "bad" ,
++                                exp_links       => ["found"],
++                                not_exp_links   => ["bad"],
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", TAG+="green"
+@@ -1992,7 +2044,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "found",
++                                exp_links       => ["found"],
+                         }],
+                 rules           => $rules_10k_tags . <<EOF
+ TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="found"
+@@ -2003,7 +2055,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "found",
++                                exp_links       => ["found"],
+                                 not_exp_name    => "bad",
+                         }],
+                 rules           => $rules_10k_tags_continuation . <<EOF
+@@ -2026,7 +2078,7 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "found",
++                                exp_links       => ["found"],
+                                 not_exp_name    => "bad",
+ 
+                         }],
+@@ -2046,7 +2098,7 @@ TAGS=="aaa", SYMLINK+="bad"
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+-                                exp_name        => "found",
++                                exp_links       => ["found"],
+                                 not_exp_name    => "bad",
+                         }],
+                 rules           => <<EOF
+@@ -2230,41 +2282,35 @@ sub check_devnode {
+         return $devnode;
+ }
+ 
+-sub check_add {
+-        my ($device) = @_;
+-
+-        if (defined($device->{not_exp_name})) {
+-                if ((-e "$udev_dev/$device->{not_exp_name}") ||
+-                    (-l "$udev_dev/$device->{not_exp_name}")) {
+-                        print "nonexistent: error \'$device->{not_exp_name}\' not expected to be there\n";
+-                        $error++;
+-                        sleep(1);
+-                }
+-        }
++sub get_link_target {
++        my ($link) = @_;
+ 
+-        my $devnode = check_devnode($device);
++        my $cwd = getcwd();
++        my $dir = "$udev_dev/$link";
++        my $tgt = readlink("$udev_dev/$link");
++        $dir =~ s!/[^/]*$!!;
++        $tgt = abs_path("$dir/$tgt");
++        $tgt =~ s!^$cwd/!!;
++        return $tgt;
++}
+ 
+-        return if (!defined($device->{exp_name}));
++sub check_link_add {
++        my ($link, $devnode, $err_expected) = @_;
+ 
+-        my @st = lstat("$udev_dev/$device->{exp_name}");
++        my @st = lstat("$udev_dev/$link");
+         if (-l _) {
+-                my $cwd = getcwd();
+-                my $dir = "$udev_dev/$device->{exp_name}";
+-                $dir =~ s!/[^/]*$!!;
+-                my $tgt = readlink("$udev_dev/$device->{exp_name}");
+-                $tgt = abs_path("$dir/$tgt");
+-                $tgt =~ s!^$cwd/!!;
++                my $tgt = get_link_target($link);
+ 
+                 if ($tgt ne $devnode) {
+-                        print "symlink $device->{exp_name}:         error, found -> $tgt\n";
++                        print "symlink $link:         error, found -> $tgt\n";
+                         $error++;
+                         system("tree", "$udev_dev");
+                 } else {
+-                        print "symlink $device->{exp_name}:         ok\n";
++                        print "symlink $link:         ok\n";
+                 }
+         } else {
+-                print "symlink $device->{exp_name}:         error";
+-                if ($device->{exp_add_error}) {
++                print "symlink $link:         error";
++                if ($err_expected) {
+                         print " as expected\n";
+                 } else {
+                         print "\n";
+@@ -2276,6 +2322,49 @@ sub check_add {
+         }
+ }
+ 
++sub check_link_nonexistent {
++        my ($link, $devnode, $err_expected) = @_;
++
++        if ((-e "$udev_dev/$link") || (-l "$udev_dev/$link")) {
++                my $tgt = get_link_target($link);
++
++                if ($tgt ne $devnode) {
++                        print "nonexistent: '$link' points to other device (ok)\n";
++                } else {
++                        print "nonexistent: error \'$link\' should not be there";
++                        if ($err_expected) {
++                                print " (as expected)\n";
++                        } else {
++                                print "\n";
++                                system("tree", "$udev_dev");
++                                print "\n";
++                                $error++;
++                                sleep(1);
++                        }
++                }
++        } else {
++                print "nonexistent $link:         ok\n";
++        }
++}
++
++sub check_add {
++        my ($device) = @_;
++        my $devnode = check_devnode($device);
++
++        if (defined($device->{exp_links})) {
++                foreach my $link (@{$device->{exp_links}}) {
++                        check_link_add($link, $devnode,
++                                       $device->{exp_add_error});
++                }
++        }
++        if (defined $device->{not_exp_links}) {
++                foreach my $link (@{$device->{not_exp_links}}) {
++                        check_link_nonexistent($link, $devnode,
++                                               $device->{exp_nodev_error});
++                }
++        }
++}
++
+ sub check_remove_devnode {
+         my ($device) = @_;
+         my $devnode = get_devnode($device);
+@@ -2292,17 +2381,13 @@ sub check_remove_devnode {
+         }
+ }
+ 
+-sub check_remove {
+-        my ($device) = @_;
++sub check_link_remove {
++        my ($link, $err_expected) = @_;
+ 
+-        check_remove_devnode($device);
+-
+-        return if (!defined($device->{exp_name}));
+-
+-        if ((-e "$udev_dev/$device->{exp_name}") ||
+-            (-l "$udev_dev/$device->{exp_name}")) {
+-                print "remove  $device->{exp_name}:      error";
+-                if ($device->{exp_rem_error}) {
++        if ((-e "$udev_dev/$link") ||
++            (-l "$udev_dev/$link")) {
++                print "remove  $link:      error";
++                if ($err_expected) {
+                         print " as expected\n";
+                 } else {
+                         print "\n";
+@@ -2312,7 +2397,19 @@ sub check_remove {
+                         sleep(1);
+                 }
+         } else {
+-                print "remove  $device->{exp_name}:      ok\n";
++                print "remove  $link:      ok\n";
++        }
++}
++
++sub check_remove {
++        my ($device) = @_;
++
++        check_remove_devnode($device);
++
++        return if (!defined($device->{exp_links}));
++
++        foreach my $link (@{$device->{exp_links}}) {
++                check_link_remove($link, $device->{exp_rem_error});
+         }
+ }
+ 
diff --git a/SOURCES/0531-test-udev-test.pl-fix-wrong-test-descriptions.patch b/SOURCES/0531-test-udev-test.pl-fix-wrong-test-descriptions.patch
new file mode 100644
index 0000000..9c2ee94
--- /dev/null
+++ b/SOURCES/0531-test-udev-test.pl-fix-wrong-test-descriptions.patch
@@ -0,0 +1,83 @@
+From 0e0b90ffcf0731865846bfa2754a809cc2b8c53e Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Tue, 24 Apr 2018 17:57:47 +0200
+Subject: [PATCH] test/udev-test.pl: fix wrong test descriptions
+
+udev hasn't supported renaming device nodes for some time.
+
+(cherry picked from commit 46bc71b2b73f8a1e27dc5e142730e9877dd05e3e)
+
+Related: #1642728
+---
+ test/udev-test.pl | 15 ++++++++-------
+ 1 file changed, 8 insertions(+), 7 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index f5edecefd0..d5d0e130e3 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -212,7 +212,7 @@ KERNEL=="ttyACM[0-9]*", SYMLINK+="modem/%n"
+ EOF
+         },
+         {
+-                desc            => "replace kernel name",
++                desc            => "don't replace kernel name",
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+@@ -223,7 +223,7 @@ KERNEL=="ttyACM0", SYMLINK+="modem"
+ EOF
+         },
+         {
+-                desc            => "Handle comment lines in config file (and replace kernel name)",
++                desc            => "Handle comment lines in config file (and don't replace kernel name)",
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+@@ -236,7 +236,7 @@ KERNEL=="ttyACM0", SYMLINK+="modem"
+ EOF
+         },
+         {
+-                desc            => "Handle comment lines in config file with whitespace (and replace kernel name)",
++                desc            => "Handle comment lines in config file with whitespace (and don't replace kernel name)",
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+@@ -249,7 +249,7 @@ KERNEL=="ttyACM0", SYMLINK+="modem"
+ EOF
+         },
+         {
+-                desc            => "Handle whitespace only lines (and replace kernel name)",
++                desc            => "Handle whitespace only lines (and don't replace kernel name)",
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+@@ -267,7 +267,7 @@ KERNEL=="ttyACM0", SYMLINK+="whitespace"
+ EOF
+         },
+         {
+-                desc            => "Handle empty lines in config file (and replace kernel name)",
++                desc            => "Handle empty lines in config file (and don't replace kernel name)",
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+@@ -280,7 +280,7 @@ KERNEL=="ttyACM0", SYMLINK+="modem"
+ EOF
+         },
+         {
+-                desc            => "Handle backslashed multi lines in config file (and replace kernel name)",
++                desc            => "Handle backslashed multi lines in config file (and don't replace kernel name)",
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+@@ -303,8 +303,9 @@ EOF
+ KERNEL=="ttyACM0", PROGRAM=="/bin/echo -e \\101", RESULT=="A", SYMLINK+="aaa"
+ EOF
+         },
++        # 20
+         {
+-                desc            => "Handle stupid backslashed multi lines in config file (and replace kernel name)",
++                desc            => "Handle stupid backslashed multi lines in config file (and don't replace kernel name)",
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
diff --git a/SOURCES/0532-test-udev-test.pl-last_rule-is-unsupported.patch b/SOURCES/0532-test-udev-test.pl-last_rule-is-unsupported.patch
new file mode 100644
index 0000000..72021fb
--- /dev/null
+++ b/SOURCES/0532-test-udev-test.pl-last_rule-is-unsupported.patch
@@ -0,0 +1,35 @@
+From 2d0b828715e67f7accda6f73481deb74febebcb6 Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Tue, 24 Apr 2018 18:08:18 +0200
+Subject: [PATCH] test/udev-test.pl: last_rule is unsupported
+
+the "last_rule" option hasn't been supported for some time.
+Therefore this test fails if a "not_exp_links" attribute is added,
+as it should be. Mark it appropriately.
+
+(cherry picked from commit 17cce031531a5d3f38a27374c99d1bdba5959dbd)
+
+Related: #1642728
+---
+ test/udev-test.pl | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index d5d0e130e3..a9c2dd95f1 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1373,11 +1373,14 @@ SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="start-%r-end"
+ EOF
+         },
+         {
++                # This is not supported any more
+                 desc            => "last_rule option",
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                                 exp_links       => ["last"],
++                                not_exp_links   => ["very-last"],
++                                exp_nodev_error => "yes",
+                         }],
+                 rules           => <<EOF
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="last", OPTIONS="last_rule"
diff --git a/SOURCES/0533-test-udev-test.pl-Make-some-tests-a-little-harder.patch b/SOURCES/0533-test-udev-test.pl-Make-some-tests-a-little-harder.patch
new file mode 100644
index 0000000..5206096
--- /dev/null
+++ b/SOURCES/0533-test-udev-test.pl-Make-some-tests-a-little-harder.patch
@@ -0,0 +1,74 @@
+From 134a415c2d690e57d0f1add23e900e60bcef4627 Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Tue, 24 Apr 2018 18:09:50 +0200
+Subject: [PATCH] test/udev-test.pl: Make some tests a little harder
+
+Add some rules that make it a bit harder to pass, mainly the
+non-existence checks.
+
+(cherry picked from commit 06d4d4e24e7d0b51120b165e540d278842e8b1a3)
+
+Related: #1642728
+---
+ test/udev-test.pl | 13 +++++++++----
+ 1 file changed, 9 insertions(+), 4 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index a9c2dd95f1..7465b5859e 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1358,7 +1358,7 @@ EOF
+                                 exp_links       => ["sda-part-1"],
+                         }],
+                 rules           => <<EOF
+-SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="%P-part-1"
++SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="%P-part-%n"
+ EOF
+         },
+         {
+@@ -1486,6 +1486,7 @@ SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true"
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="absolutely-\$env{ASSIGN}"
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no"
++SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="true", SYMLINK+="bad"
+ SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="absolutely-true", SYMLINK+="true"
+ EOF
+         },
+@@ -1497,6 +1498,11 @@ EOF
+                                 exp_links       => ["part"],
+                                 not_exp_links   => ["disk"],
+                         },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
++                                exp_links       => ["disk"],
++                                not_exp_links   => ["part"],
++                        },
+                     ],
+                 rules           => <<EOF
+ SUBSYSTEM=="block", KERNEL=="*[0-9]", ENV{PARTITION}="true", ENV{MAINDEVICE}="false"
+@@ -1588,7 +1594,7 @@ EOF
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda", GROUP:="tty"
+-KERNEL=="sda", GROUP="not-ok", MODE="0640", SYMLINK+="ok"
++KERNEL=="sda", GROUP="root", MODE="0640", SYMLINK+="ok"
+ EOF
+         },
+         {
+@@ -1602,7 +1608,7 @@ EOF
+                 rules           => <<EOF
+ KERNEL=="sda", GROUP:="tty"
+ SUBSYSTEM=="block", MODE:="640"
+-KERNEL=="sda", GROUP="not-ok", MODE="0666", SYMLINK+="ok"
++KERNEL=="sda", GROUP="root", MODE="0666", SYMLINK+="ok"
+ EOF
+         },
+         {
+@@ -1983,7 +1989,6 @@ EOF
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                                 exp_perms       => "1:1:0400",
+-                                exp_rem_error   => "yes",
+                         }],
+                 rules           => <<EOF
+ KERNEL=="sda", MODE="666"
diff --git a/SOURCES/0534-test-udev-test.pl-remove-bogus-rules-from-magic-subs.patch b/SOURCES/0534-test-udev-test.pl-remove-bogus-rules-from-magic-subs.patch
new file mode 100644
index 0000000..2694d31
--- /dev/null
+++ b/SOURCES/0534-test-udev-test.pl-remove-bogus-rules-from-magic-subs.patch
@@ -0,0 +1,28 @@
+From c63273de445789ffeea961448fda51d21c924f8c Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Tue, 24 Apr 2018 18:16:59 +0200
+Subject: [PATCH] test/udev-test.pl: remove bogus rules from magic subsys test
+
+These rules have survived from an ancient version of the code
+and save no purpose any more.
+
+(cherry picked from commit 86634df43b715f3f77c7de73a3ef6566e5cdf571)
+
+Related: #1642728
+---
+ test/udev-test.pl | 2 --
+ 1 file changed, 2 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 7465b5859e..6928439d14 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -2017,8 +2017,6 @@ EOF
+                                 exp_perms       => "0:0:0600",
+                         }],
+                 rules           => <<EOF
+-KERNEL=="sda", PROGRAM="/bin/true create-envp"
+-KERNEL=="sda", ENV{TESTENV}="change-envp"
+ KERNEL=="sda", SYMLINK+="%k-%s{[dmi/id]product_name}-end"
+ EOF
+         },
diff --git a/SOURCES/0535-test-udev-test.pl-merge-space-and-var-with-space-tes.patch b/SOURCES/0535-test-udev-test.pl-merge-space-and-var-with-space-tes.patch
new file mode 100644
index 0000000..bc81f1c
--- /dev/null
+++ b/SOURCES/0535-test-udev-test.pl-merge-space-and-var-with-space-tes.patch
@@ -0,0 +1,57 @@
+From 7821ecc0b35c422bc8ed26e0e44c841d067f88d7 Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Tue, 24 Apr 2018 18:27:25 +0200
+Subject: [PATCH] test/udev-test.pl: merge "space and var with space" tests
+
+As we can check multiple links in a single test now, these 3
+tests can be merged into one.
+
+(cherry picked from commit 2084fe0d3290c525ecb9faa07d07c3abc2488e59)
+
+Related: #1642728
+---
+ test/udev-test.pl | 31 +++----------------------------
+ 1 file changed, 3 insertions(+), 28 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 6928439d14..880a73b10b 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1103,34 +1103,9 @@ EOF
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_links        => ["first"],
+-                                not_exp_links    => [" "],
+-                        }],
+-                rules           => <<EOF
+-ENV{WITH_WS}="   one  two  three   "
+-SYMLINK="  first  name-\$env{WITH_WS}-end another_symlink a b c "
+-EOF
+-        },
+-        {
+-                desc            => "symlink with space and var with space, part 2",
+-                devices => [
+-                        {
+-                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_links        => ["name-one_two_three-end"],
+-                                not_exp_links    => [" "],
+-                        }],
+-                rules           => <<EOF
+-ENV{WITH_WS}="   one  two  three   "
+-SYMLINK="  first  name-\$env{WITH_WS}-end another_symlink a b c "
+-EOF
+-        },
+-        {
+-                desc            => "symlink with space and var with space, part 3",
+-                devices => [
+-                        {
+-                                devpath         => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
+-                                exp_links        => ["another_symlink"],
+-                                not_exp_links    => [" "],
++                                exp_links       => ["first", "name-one_two_three-end",
++                                                    "another_symlink", "a", "b", "c"],
++                                not_exp_links   => [" "],
+                         }],
+                 rules           => <<EOF
+ ENV{WITH_WS}="   one  two  three   "
diff --git a/SOURCES/0536-test-udev-test.pl-merge-import-parent-tests-into-one.patch b/SOURCES/0536-test-udev-test.pl-merge-import-parent-tests-into-one.patch
new file mode 100644
index 0000000..9713034
--- /dev/null
+++ b/SOURCES/0536-test-udev-test.pl-merge-import-parent-tests-into-one.patch
@@ -0,0 +1,53 @@
+From 06e937177bf23d8ea8e5060a856cce02d5436eb4 Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Tue, 24 Apr 2018 18:30:09 +0200
+Subject: [PATCH] test/udev-test.pl: merge import parent tests into one
+
+As we can test multiple devices and multiple links per device
+in one test now, these two tests can be merged into one.
+
+(cherry picked from commit a96cd21d31cb7af211862768e133b50b085634e7)
+
+Related: #1642728
+---
+ test/udev-test.pl | 17 +++++------------
+ 1 file changed, 5 insertions(+), 12 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 880a73b10b..0344d2e89c 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -1781,28 +1781,21 @@ TAGS=="aaa||bbb||ccc", SYMLINK+="bad"
+ EOF
+         },
+         {
+-                desc            => "IMPORT parent test sequence 1/2 (keep)",
++                desc            => "IMPORT parent test",
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                                 exp_links       => ["parent"],
+-                        }],
+-                option          => "keep",
+-                rules           => <<EOF
+-KERNEL=="sda", IMPORT{program}="/bin/echo -e \'PARENT_KEY=parent_right\\nWRONG_PARENT_KEY=parent_wrong'"
+-KERNEL=="sda", SYMLINK+="parent"
+-EOF
+-        },
+-        {
+-                desc            => "IMPORT parent test sequence 2/2 (keep)",
+-                devices => [
++                        },
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                                 exp_links       => ["parentenv-parent_right"],
+                         }],
+-                option          => "clean",
++                sleep_us        => 500000,  # Serialized! We need to sleep here after adding sda
+                 rules           => <<EOF
+ KERNEL=="sda1", IMPORT{parent}="PARENT*", SYMLINK+="parentenv-\$env{PARENT_KEY}\$env{WRONG_PARENT_KEY}"
++KERNEL=="sda", IMPORT{program}="/bin/echo -e \'PARENT_KEY=parent_right\\nWRONG_PARENT_KEY=parent_wrong'"
++KERNEL=="sda", SYMLINK+="parent"
+ EOF
+         },
+         {
diff --git a/SOURCES/0537-test-udev-test.pl-count-good-results.patch b/SOURCES/0537-test-udev-test.pl-count-good-results.patch
new file mode 100644
index 0000000..7a8a727
--- /dev/null
+++ b/SOURCES/0537-test-udev-test.pl-count-good-results.patch
@@ -0,0 +1,136 @@
+From 7913cdbdfe6ca3fa0a1221c60702806eb51aa707 Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Tue, 24 Apr 2018 20:55:01 +0200
+Subject: [PATCH] test/udev-test.pl: count "good" results
+
+This is helpful to catch possible regressions in the test.
+Also, don't count wait() errors, they are likely not udev errors.
+
+(cherry picked from commit b95c43982ab7d0253b552ad56cffb3d68fcbb4f6)
+
+Related: #1642728
+---
+ test/udev-test.pl | 17 +++++++++++++++--
+ 1 file changed, 15 insertions(+), 2 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 0344d2e89c..813be70739 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -2114,6 +2114,7 @@ sub udev {
+ }
+ 
+ my $error = 0;
++my $good = 0;
+ 
+ sub permissions_test {
+         my($rules, $uid, $gid, $mode) = @_;
+@@ -2144,6 +2145,7 @@ sub permissions_test {
+         }
+         if ($wrong == 0) {
+                 print "permissions: ok\n";
++                $good++;
+         } else {
+                 printf "  expected permissions are: %s:%s:%#o\n", $1, $2, oct($3);
+                 printf "  created permissions are : %i:%i:%#o\n", $uid, $gid, $mode & 07777;
+@@ -2169,6 +2171,7 @@ sub major_minor_test {
+         }
+         if ($wrong == 0) {
+                 print "major:minor: ok\n";
++                $good++;
+         } else {
+                 printf "  expected major:minor is: %i:%i\n", $1, $2;
+                 printf "  created major:minor is : %i:%i\n", $major, $minor;
+@@ -2254,6 +2257,7 @@ sub check_devnode {
+                 major_minor_test($device, $rdev);
+         }
+         print "add $devnode:         ok\n";
++        $good++;
+         return $devnode;
+ }
+ 
+@@ -2282,11 +2286,13 @@ sub check_link_add {
+                         system("tree", "$udev_dev");
+                 } else {
+                         print "symlink $link:         ok\n";
++                        $good++;
+                 }
+         } else {
+                 print "symlink $link:         error";
+                 if ($err_expected) {
+                         print " as expected\n";
++                        $good++;
+                 } else {
+                         print "\n";
+                         system("tree", "$udev_dev");
+@@ -2305,10 +2311,12 @@ sub check_link_nonexistent {
+ 
+                 if ($tgt ne $devnode) {
+                         print "nonexistent: '$link' points to other device (ok)\n";
++                        $good++;
+                 } else {
+                         print "nonexistent: error \'$link\' should not be there";
+                         if ($err_expected) {
+                                 print " (as expected)\n";
++                                $good++;
+                         } else {
+                                 print "\n";
+                                 system("tree", "$udev_dev");
+@@ -2319,6 +2327,7 @@ sub check_link_nonexistent {
+                 }
+         } else {
+                 print "nonexistent $link:         ok\n";
++                $good++;
+         }
+ }
+ 
+@@ -2353,6 +2362,7 @@ sub check_remove_devnode {
+                 sleep(1);
+         } else {
+                 print "remove $devnode:         ok\n";
++                $good++;
+         }
+ }
+ 
+@@ -2364,6 +2374,7 @@ sub check_link_remove {
+                 print "remove  $link:      error";
+                 if ($err_expected) {
+                         print " as expected\n";
++                        $good++;
+                 } else {
+                         print "\n";
+                         system("tree", "$udev_dev");
+@@ -2373,6 +2384,7 @@ sub check_link_remove {
+                 }
+         } else {
+                 print "remove  $link:      ok\n";
++                $good++;
+         }
+ }
+ 
+@@ -2432,7 +2444,6 @@ sub fork_and_run_udev {
+                 $pid = waitpid($dev->{pid}, 0);
+                 if ($pid == -1) {
+                         print "error waiting for pid dev->{pid}\n";
+-                        $error += 1;
+                 }
+                 if (WIFEXITED($?)) {
+                         $rc = WEXITSTATUS($?);
+@@ -2440,6 +2451,8 @@ sub fork_and_run_udev {
+                         if ($rc) {
+                                 print "$udev_bin $action for $dev->{devpath} failed with code $rc\n";
+                                 $error += 1;
++                        } else {
++                                $good++;
+                         }
+                 }
+         }
+@@ -2549,7 +2562,7 @@ if ($list[0]) {
+ }
+ 
+ $sema->remove;
+-print "$error errors occurred\n\n";
++print "$error errors occurred. $good good results.\n\n";
+ 
+ cleanup();
+ 
diff --git a/SOURCES/0538-tests-udev-test.pl-add-multiple-device-test.patch b/SOURCES/0538-tests-udev-test.pl-add-multiple-device-test.patch
new file mode 100644
index 0000000..e01add4
--- /dev/null
+++ b/SOURCES/0538-tests-udev-test.pl-add-multiple-device-test.patch
@@ -0,0 +1,199 @@
+From 8ab9d11b925e7f39b350ce69a1e28752de411b35 Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Tue, 24 Apr 2018 22:04:55 +0200
+Subject: [PATCH] tests/udev-test.pl: add multiple device test
+
+Add 4 new tests using multiple devices. Number 2-4 use many
+devices claiming the same symlink, where only one device has
+a higher priority thatn the others. They fail sporadically with
+the current code, if a race condition causes the symlink to point
+to the wrong device. Test 4 is like test 2 with sleeps in between,
+it's much less likely to fail.
+
+(cherry picked from commit 4a0ec82daf32446519e1d86329bb802325b82104)
+
+Related: #1642728
+---
+ test/udev-test.pl | 169 ++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 169 insertions(+)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 813be70739..d964c664b6 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -2085,6 +2085,175 @@ KERNEL=="sda", TAG+="aaa" \\
+ KERNEL=="sdb", TAG+="bbb"
+ TAGS=="foo", SYMLINK+="found"
+ TAGS=="aaa", SYMLINK+="bad"
++EOF
++        },
++        {
++                desc            => "multiple devices",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_links       => ["part-1"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_links       => ["part-5"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6",
++                                exp_links       => ["part-6"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7",
++                                exp_links       => ["part-7"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8",
++                                exp_links       => ["part-8"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9",
++                                exp_links       => ["part-9"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10",
++                                exp_links       => ["part-10"],
++                        },
++                    ],
++                rules          => <<EOF
++SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n"
++EOF
++        },
++        {
++                desc            => "multiple devices, same link name, positive prio",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_links       => ["part-1"],
++                                not_exp_links   => ["partition"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_links       => ["part-5"],
++                                not_exp_links   => ["partition"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6",
++                                not_exp_links   => ["partition"],
++                                exp_links       => ["part-6"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7",
++                                exp_links       => ["part-7", "partition"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8",
++                                not_exp_links   => ["partition"],
++                                exp_links       => ["part-8"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9",
++                                not_exp_links   => ["partition"],
++                                exp_links       => ["part-9"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10",
++                                not_exp_links   => ["partition"],
++                                exp_links       => ["part-10"],
++                        },
++                    ],
++                rules          => <<EOF
++SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n"
++SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition"
++KERNEL=="*7", OPTIONS+="link_priority=10"
++EOF
++        },
++        {
++                desc            => "multiple devices, same link name, negative prio",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_links       => ["part-1"],
++                                not_exp_links   => ["partition"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_links       => ["part-5"],
++                                not_exp_links   => ["partition"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6",
++                                not_exp_links   => ["partition"],
++                                exp_links       => ["part-6"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7",
++                                exp_links       => ["part-7", "partition"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8",
++                                not_exp_links   => ["partition"],
++                                exp_links       => ["part-8"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9",
++                                not_exp_links   => ["partition"],
++                                exp_links       => ["part-9"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10",
++                                not_exp_links   => ["partition"],
++                                exp_links       => ["part-10"],
++                        },
++                    ],
++                rules          => <<EOF
++SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n"
++SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition"
++KERNEL!="*7", OPTIONS+="link_priority=-10"
++EOF
++        },
++        {
++                desc            => "multiple devices, same link name, positive prio, sleep",
++                devices => [
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
++                                exp_links       => ["part-1"],
++                                not_exp_links   => ["partition"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
++                                exp_links       => ["part-5"],
++                                not_exp_links   => ["partition"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6",
++                                not_exp_links   => ["partition"],
++                                exp_links       => ["part-6"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7",
++                                exp_links       => ["part-7", "partition"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8",
++                                not_exp_links   => ["partition"],
++                                exp_links       => ["part-8"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9",
++                                not_exp_links   => ["partition"],
++                                exp_links       => ["part-9"],
++                        },
++                        {
++                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10",
++                                not_exp_links   => ["partition"],
++                                exp_links       => ["part-10"],
++                        },
++                    ],
++                sleep_us       => 10000,
++                rules          => <<EOF
++SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n"
++SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition"
++KERNEL=="*7", OPTIONS+="link_priority=10"
+ EOF
+         },
+ );
diff --git a/SOURCES/0539-test-udev-test.pl-add-repeat-count.patch b/SOURCES/0539-test-udev-test.pl-add-repeat-count.patch
new file mode 100644
index 0000000..725f7b2
--- /dev/null
+++ b/SOURCES/0539-test-udev-test.pl-add-repeat-count.patch
@@ -0,0 +1,44 @@
+From 6fba8e9a94026ee7b8791844ed1e7c6d464f7666 Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Tue, 24 Apr 2018 22:24:43 +0200
+Subject: [PATCH] test/udev-test.pl: add repeat count
+
+for easier reproduction of sporadic test failures.
+
+(cherry picked from commit 2ab0a8d00bc48d3531e953d938db889d8a932d65)
+
+Related: #1642728
+---
+ test/udev-test.pl | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index d964c664b6..8b1ab3c06c 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -2125,6 +2125,7 @@ EOF
+         },
+         {
+                 desc            => "multiple devices, same link name, positive prio",
++                repeat          => 100,
+                 devices => [
+                         {
+                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+@@ -2635,6 +2636,7 @@ sub run_test {
+         print "TEST $number: $rules->{desc}\n";
+         create_rules(\$rules->{rules});
+ 
++      REPEAT:
+         fork_and_run_udev("add", $rules, $sema);
+ 
+         foreach my $dev (@devices) {
+@@ -2653,6 +2655,9 @@ sub run_test {
+         }
+ 
+         print "\n";
++        if (defined($rules->{repeat}) && --($rules->{repeat}) > 0) {
++                goto REPEAT;
++        }
+ 
+         if (defined($rules->{option}) && $rules->{option} eq "clean") {
+                 udev_setup();
diff --git a/SOURCES/0540-test-udev-test.pl-generator-for-large-list-of-block-.patch b/SOURCES/0540-test-udev-test.pl-generator-for-large-list-of-block-.patch
new file mode 100644
index 0000000..02e407d
--- /dev/null
+++ b/SOURCES/0540-test-udev-test.pl-generator-for-large-list-of-block-.patch
@@ -0,0 +1,101 @@
+From 6c3191e979165700f98903b76621c214186a110c Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Wed, 25 Apr 2018 09:54:26 +0200
+Subject: [PATCH] test/udev-test.pl: generator for large list of block devices
+
+Manually listing all devices in the test definition becomes cumbersome with
+lots of devices. Add a function that scans on all block devices in
+the test sysfs and generates a list of devices to test.
+
+(cherry picked from commit eb44d715ebee2fe11288433b99f8e1dc5fdac84a)
+
+Related: #1642728
+---
+ test/udev-test.pl | 60 ++++++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 59 insertions(+), 1 deletion(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 8b1ab3c06c..2866fdb77a 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -50,6 +50,50 @@ for (my $i = 1; $i < 10000; ++$i) {
+ }
+ $rules_10k_tags_continuation .= "TAG+=\"test10000\"\\n";
+ 
++# Create a device list with all block devices under /sys
++# (except virtual devices and cd-roms)
++# the optional argument exp_func returns expected and non-expected
++# symlinks for the device.
++sub all_block_devs {
++        my ($exp_func) = @_;
++        my @devices;
++
++        foreach my $bd (glob "$udev_sys/dev/block/*") {
++                my $tgt = readlink($bd);
++                my ($exp, $notexp) = (undef, undef);
++
++                next if ($tgt =~ m!/virtual/! || $tgt =~ m!/sr[0-9]*$!);
++
++                $tgt =~ s!^\.\./\.\.!!;
++                ($exp, $notexp) = $exp_func->($tgt) if defined($exp_func);
++                my $device = {
++                        devpath => $tgt,
++                        exp_links => $exp,
++                        not_exp_links => $notexp,
++                };
++                push(@devices, $device);
++        }
++        return \@devices;
++}
++
++# This generator returns a suitable exp_func for use with
++# all_block_devs().
++sub expect_for_some {
++        my ($pattern, $links, $donot) = @_;
++        my $_expect = sub {
++                my ($name) = @_;
++
++                if ($name =~ /$pattern/) {
++                        return ($links, undef);
++                } elsif ($donot) {
++                        return (undef, $links);
++                } else {
++                        return (undef, undef);
++                }
++        };
++        return $_expect;
++}
++
+ my @tests = (
+         {
+                 desc            => "no rules",
+@@ -2257,6 +2301,15 @@ SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partitio
+ KERNEL=="*7", OPTIONS+="link_priority=10"
+ EOF
+         },
++        {
++                desc           => 'all_block_devs',
++                generator      => expect_for_some("\\/sda6\$", ["blockdev"]),
++                repeat         => 10,
++                rules          => <<EOF
++SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sd*", SYMLINK+="blockdev"
++KERNEL=="sda6", OPTIONS+="link_priority=10"
++EOF
++        }
+ );
+ 
+ sub create_rules {
+@@ -2631,7 +2684,12 @@ sub fork_and_run_udev {
+ sub run_test {
+         my ($rules, $number, $sema) = @_;
+         my $rc;
+-        my @devices = @{$rules->{devices}};
++        my @devices;
++
++        if (!defined $rules->{devices}) {
++                $rules->{devices} = all_block_devs($rules->{generator});
++        }
++        @devices = @{$rules->{devices}};
+ 
+         print "TEST $number: $rules->{desc}\n";
+         create_rules(\$rules->{rules});
diff --git a/SOURCES/0541-test-udev-test.pl-suppress-umount-error-message-at-s.patch b/SOURCES/0541-test-udev-test.pl-suppress-umount-error-message-at-s.patch
new file mode 100644
index 0000000..fbe9d4b
--- /dev/null
+++ b/SOURCES/0541-test-udev-test.pl-suppress-umount-error-message-at-s.patch
@@ -0,0 +1,29 @@
+From 453df9eb2bbfa34f3e4b78e917812f0ac6958010 Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Thu, 26 Apr 2018 13:25:11 +0200
+Subject: [PATCH] test/udev-test.pl: suppress umount error message at startup
+
+umount emits an error message "no mount point specified" if the
+tmpfs isn't mounted yet, which is the normal case.
+Suppress that by redirecting stderr.
+
+(cherry picked from commit f1cb0860549e775be5f91237b5a3b97698dd14dd)
+
+Related: #1642728
+---
+ test/udev-test.pl | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 2866fdb77a..33a76ad292 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -2405,7 +2405,7 @@ sub major_minor_test {
+ }
+ 
+ sub udev_setup {
+-        system("umount", $udev_tmpfs);
++        system("umount \"$udev_tmpfs\" 2>/dev/null");
+         rmdir($udev_tmpfs);
+         mkdir($udev_tmpfs) || die "unable to create udev_tmpfs: $udev_tmpfs\n";
+ 
diff --git a/SOURCES/0542-test-udev_test.pl-add-expected-good-count.patch b/SOURCES/0542-test-udev_test.pl-add-expected-good-count.patch
new file mode 100644
index 0000000..d1f61ca
--- /dev/null
+++ b/SOURCES/0542-test-udev_test.pl-add-expected-good-count.patch
@@ -0,0 +1,78 @@
+From e0cee95e0cc401ce120a1b56cdb7a8b9afbd6bcf Mon Sep 17 00:00:00 2001
+From: Martin Wilck <mwilck@suse.com>
+Date: Thu, 26 Apr 2018 14:07:27 +0200
+Subject: [PATCH] test/udev_test.pl: add "expected good" count
+
+Since 'test/udev-test.pl: count "good" results', we know how many
+checks succeeded. Add an "expected good" count to make that number
+more meaningful.
+
+(cherry picked from commit cbeb23d863d540408cd1fb274d78213f59639df2)
+
+Related: #1642728
+---
+ test/udev-test.pl | 21 +++++++++++++++++++--
+ 1 file changed, 19 insertions(+), 2 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index 33a76ad292..cf6ca6b80c 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -2338,6 +2338,7 @@ sub udev {
+ 
+ my $error = 0;
+ my $good = 0;
++my $exp_good = 0;
+ 
+ sub permissions_test {
+         my($rules, $uid, $gid, $mode) = @_;
+@@ -2685,12 +2686,27 @@ sub run_test {
+         my ($rules, $number, $sema) = @_;
+         my $rc;
+         my @devices;
++        my $ntests;
++        my $cur_good = $good;
++        my $cur_error = $error;
+ 
+         if (!defined $rules->{devices}) {
+                 $rules->{devices} = all_block_devs($rules->{generator});
+         }
+         @devices = @{$rules->{devices}};
++        # For each device: exit status and devnode test for add & remove
++        $ntests += 4 * ($#devices + 1);
+ 
++        foreach my $dev (@devices) {
++                $ntests += 2 * ($#{$dev->{exp_links}} + 1)
++                    + ($#{$dev->{not_exp_links}} + 1)
++                    + (defined $dev->{exp_perms} ? 1 : 0)
++                    + (defined $dev->{exp_majorminor} ? 1 : 0);
++        }
++        if (defined $rules->{repeat}) {
++                $ntests *= $rules->{repeat};
++        }
++        $exp_good += $ntests;
+         print "TEST $number: $rules->{desc}\n";
+         create_rules(\$rules->{rules});
+ 
+@@ -2712,10 +2728,11 @@ sub run_test {
+                 check_remove($dev);
+         }
+ 
+-        print "\n";
+         if (defined($rules->{repeat}) && --($rules->{repeat}) > 0) {
+                 goto REPEAT;
+         }
++        printf "TEST $number: errors: %d good: %d/%d\n\n", $error-$cur_error,
++            $good-$cur_good, $ntests;
+ 
+         if (defined($rules->{option}) && $rules->{option} eq "clean") {
+                 udev_setup();
+@@ -2794,7 +2811,7 @@ if ($list[0]) {
+ }
+ 
+ $sema->remove;
+-print "$error errors occurred. $good good results.\n\n";
++print "$error errors occurred. $good/$exp_good good results.\n\n";
+ 
+ cleanup();
+ 
diff --git a/SOURCES/0543-test-udev-test-gracefully-exit-when-imports-fail.patch b/SOURCES/0543-test-udev-test-gracefully-exit-when-imports-fail.patch
new file mode 100644
index 0000000..3e5c348
--- /dev/null
+++ b/SOURCES/0543-test-udev-test-gracefully-exit-when-imports-fail.patch
@@ -0,0 +1,48 @@
+From 2e50a00f6930f1c65ca804b78f4a853e2ae2d2c0 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Tue, 17 Nov 2020 17:13:31 +0100
+Subject: [PATCH] test/udev-test: gracefully exit when imports fail
+
+In Fedora rawhide various perl modules are now available as separate
+packages that are not pulled in by dependencies. If we don't have some
+package, skip the tests.
+
+This ugly code is apparently the way to do conditional imports:
+https://www.cs.ait.ac.th/~on/O/oreilly/perl/cookbook/ch12_03.htm.
+
+(cherry picked from commit d40763838278246e2073d15ca927ee700e583afc)
+
+Related: #1642728
+---
+ test/udev-test.pl | 18 +++++++++++++-----
+ 1 file changed, 13 insertions(+), 5 deletions(-)
+
+diff --git a/test/udev-test.pl b/test/udev-test.pl
+index cf6ca6b80c..5b1e33504e 100755
+--- a/test/udev-test.pl
++++ b/test/udev-test.pl
+@@ -18,11 +18,19 @@
+ 
+ use warnings;
+ use strict;
+-use POSIX qw(WIFEXITED WEXITSTATUS);
+-use IPC::SysV qw(IPC_PRIVATE S_IRUSR S_IWUSR IPC_CREAT);
+-use IPC::Semaphore;
+-use Time::HiRes qw(usleep);
+-use Cwd qw(getcwd abs_path);
++
++BEGIN {
++    my $EXIT_TEST_SKIP = 77;
++
++    unless (eval "use POSIX qw(WIFEXITED WEXITSTATUS);
++                  use Cwd qw(getcwd abs_path);
++                  use IPC::Semaphore;
++                  use IPC::SysV qw(IPC_PRIVATE S_IRUSR S_IWUSR IPC_CREAT);
++                  use Time::HiRes qw(usleep); 1") {
++        warn "Failed to import dependencies, skipping the test: $@";
++        exit($EXIT_TEST_SKIP);
++    }
++}
+ 
+ my $udev_bin            = "./test-udev";
+ my $valgrind            = 0;
diff --git a/SPECS/systemd.spec b/SPECS/systemd.spec
index 8faead7..a3c4fe1 100644
--- a/SPECS/systemd.spec
+++ b/SPECS/systemd.spec
@@ -13,7 +13,7 @@
 Name:           systemd
 Url:            http://www.freedesktop.org/wiki/Software/systemd
 Version:        239
-Release:        36%{?dist}
+Release:        44%{?dist}
 # For a breakdown of the licensing, see README
 License:        LGPLv2+ and MIT and GPLv2+
 Summary:        System and Service Manager
@@ -479,6 +479,120 @@ Patch0426: 0426-core-fix-the-return-value-in-order-to-make-sure-we-d.patch
 Patch0427: 0427-test-add-test-for-cgroup-v2-freezer-support.patch
 Patch0428: 0428-fix-mis-merge.patch
 Patch0429: 0429-tests-sleep-a-bit-and-give-kernel-time-to-perform-th.patch
+Patch0430: 0430-device-make-sure-we-emit-PropertiesChanged-signal-on.patch
+Patch0431: 0431-device-don-t-emit-PropetiesChanged-needlessly.patch
+Patch0432: 0432-units-add-generic-boot-complete.target.patch
+Patch0433: 0433-man-document-new-boot-complete.target-unit.patch
+Patch0434: 0434-core-make-sure-to-restore-the-control-command-id-too.patch
+Patch0435: 0435-cgroup-freezer-action-must-be-NOP-when-cgroup-v2-fre.patch
+Patch0436: 0436-logind-don-t-print-warning-when-user-.service-templa.patch
+Patch0437: 0437-build-use-simple-project-version-in-pkgconfig-files.patch
+Patch0438: 0438-basic-virt-try-the-proc-1-sched-hack-also-for-PID1.patch
+Patch0439: 0439-seccomp-rework-how-the-S-UG-ID-filter-is-installed.patch
+Patch0440: 0440-vconsole-setup-downgrade-log-message-when-setting-fo.patch
+Patch0441: 0441-units-fix-systemd.special-man-page-reference-in-syst.patch
+Patch0442: 0442-units-drop-reference-to-sushell-man-page.patch
+Patch0443: 0443-sd-bus-break-the-loop-in-bus_ensure_running-if-the-b.patch
+Patch0444: 0444-core-add-new-API-for-enqueing-a-job-with-returning-t.patch
+Patch0445: 0445-systemctl-replace-switch-statement-by-table-of-struc.patch
+Patch0446: 0446-systemctl-reindent-table.patch
+Patch0447: 0447-systemctl-Only-wait-when-there-s-something-to-wait-f.patch
+Patch0448: 0448-systemctl-clean-up-start_unit_one-error-handling.patch
+Patch0449: 0449-systemctl-split-out-extra-args-generation-into-helpe.patch
+Patch0450: 0450-systemctl-add-new-show-transaction-switch.patch
+Patch0451: 0451-test-add-some-basic-testing-that-systemctl-start-T-d.patch
+Patch0452: 0452-man-document-the-new-systemctl-show-transaction-opti.patch
+Patch0453: 0453-socket-New-option-FlushPending-boolean-to-flush-sock.patch
+Patch0454: 0454-core-remove-support-for-API-bus-started-outside-our-.patch
+Patch0455: 0455-mount-setup-fix-segfault-in-mount_cgroup_controllers.patch
+Patch0456: 0456-dbus-execute-make-transfer-of-CPUAffinity-endian-saf.patch
+Patch0457: 0457-core-add-support-for-setting-CPUAffinity-to-special-.patch
+Patch0458: 0458-basic-user-util-always-use-base-10-for-user-group-nu.patch
+Patch0459: 0459-parse-util-sometimes-it-is-useful-to-check-if-a-stri.patch
+Patch0460: 0460-basic-parse-util-add-safe_atoux64.patch
+Patch0461: 0461-parse-util-allow-tweaking-how-to-parse-integers.patch
+Patch0462: 0462-parse-util-allow-0-as-alternative-to-0-and-0.patch
+Patch0463: 0463-parse-util-make-return-parameter-optional-in-safe_at.patch
+Patch0464: 0464-parse-util-rewrite-parse_mode-on-top-of-safe_atou_fu.patch
+Patch0465: 0465-user-util-be-stricter-in-parse_uid.patch
+Patch0466: 0466-strv-add-new-macro-STARTSWITH_SET.patch
+Patch0467: 0467-parse-util-also-parse-integers-prefixed-with-0b-and-.patch
+Patch0468: 0468-tests-beef-up-integer-parsing-tests.patch
+Patch0469: 0469-shared-user-util-add-compat-forms-of-user-name-check.patch
+Patch0470: 0470-shared-user-util-emit-a-warning-on-names-with-dots.patch
+Patch0471: 0471-user-util-Allow-names-starting-with-a-digit.patch
+Patch0472: 0472-shared-user-util-allow-usernames-with-dots-in-specif.patch
+Patch0473: 0473-user-util-switch-order-of-checks-in-valid_user_group.patch
+Patch0474: 0474-user-util-rework-how-we-validate-user-names.patch
+Patch0475: 0475-man-mention-System-Administrator-s-Guide-in-systemct.patch
+Patch0476: 0476-udev-introduce-udev-net_id-naming-schemes.patch
+Patch0477: 0477-meson-make-net.naming-scheme-default-configurable.patch
+Patch0478: 0478-man-describe-naming-schemes-in-a-new-man-page.patch
+Patch0479: 0479-udev-net_id-parse-_SUN-ACPI-index-as-a-signed-intege.patch
+Patch0480: 0480-udev-net_id-don-t-generate-slot-based-names-if-multi.patch
+Patch0481: 0481-fix-typo-in-ProtectSystem-option.patch
+Patch0482: 0482-remove-references-of-non-existent-man-pages.patch
+Patch0483: 0483-log-Prefer-logging-to-CLI-unless-JOURNAL_STREAM-is-s.patch
+Patch0484: 0484-locale-util-add-new-helper-locale_is_installed.patch
+Patch0485: 0485-test-add-test-case-for-locale_is_installed.patch
+Patch0486: 0486-tree-wide-port-various-bits-over-to-locale_is_instal.patch
+Patch0487: 0487-install-allow-instantiated-units-to-be-enabled-via-p.patch
+Patch0488: 0488-install-small-refactor-to-combine-two-function-calls.patch
+Patch0489: 0489-test-fix-a-memleak.patch
+Patch0490: 0490-docs-Add-syntax-for-templated-units-to-systemd.prese.patch
+Patch0491: 0491-shared-install-fix-preset-operations-for-non-service.patch
+Patch0492: 0492-introduce-setsockopt_int-helper.patch
+Patch0493: 0493-socket-util-add-generic-socket_pass_pktinfo-helper.patch
+Patch0494: 0494-core-add-new-PassPacketInfo-socket-unit-property.patch
+Patch0495: 0495-resolved-tweak-cmsg-calculation.patch
+Patch0496: 0496-ci-PowerTools-repo-was-renamed-to-powertools-in-RHEL.patch
+Patch0497: 0497-ci-use-quay.io-instead-of-Docker-Hub-to-avoid-rate-l.patch
+Patch0498: 0498-ci-move-jobs-from-Travis-CI-to-GH-Actions.patch
+Patch0499: 0499-unit-make-UNIT-cast-function-deal-with-NULL-pointers.patch
+Patch0500: 0500-use-link-to-RHEL-8-docs.patch
+Patch0501: 0501-cgroup-Also-set-blkio.bfq.weight.patch
+Patch0502: 0502-units-make-sure-initrd-cleanup.service-terminates-be.patch
+Patch0503: 0503-core-reload-SELinux-label-cache-on-daemon-reload.patch
+Patch0504: 0504-selinux-introduce-mac_selinux_create_file_prepare_at.patch
+Patch0505: 0505-selinux-add-trigger-for-policy-reload-to-refresh-int.patch
+Patch0506: 0506-udev-net_id-give-RHEL-8.4-naming-scheme-a-name.patch
+Patch0507: 0507-basic-stat-util-make-mtime-check-stricter-and-use-en.patch
+Patch0508: 0508-udev-make-algorithm-that-selects-highest-priority-de.patch
+Patch0509: 0509-test-create-dev-null-in-test-udev.pl.patch
+Patch0510: 0510-test-missing-die.patch
+Patch0511: 0511-udev-test-remove-a-check-for-whether-the-test-is-run.patch
+Patch0512: 0512-udev-test-skip-the-test-only-if-it-can-t-setup-its-e.patch
+Patch0513: 0513-udev-test-fix-test-skip-condition.patch
+Patch0514: 0514-udev-test-fix-missing-directory-test-run.patch
+Patch0515: 0515-udev-test-check-if-permitted-to-create-block-device-.patch
+Patch0516: 0516-test-udev-add-a-testcase-of-too-long-line.patch
+Patch0517: 0517-test-udev-use-proper-semantics-for-too-long-line-wit.patch
+Patch0518: 0518-test-udev-add-more-tests-for-line-continuations-and-.patch
+Patch0519: 0519-test-udev-add-more-tests-for-line-continuation.patch
+Patch0520: 0520-test-udev-fix-alignment-and-drop-unnecessary-white-s.patch
+Patch0521: 0521-test-udev-test.pl-cleanup-if-skipping-test.patch
+Patch0522: 0522-test-add-test-cases-for-empty-string-match.patch
+Patch0523: 0523-test-add-test-case-for-multi-matches-when-use.patch
+Patch0524: 0524-udev-test-do-not-rely-on-mail-group-being-defined.patch
+Patch0525: 0525-test-udev-test.pl-allow-multiple-devices-per-test.patch
+Patch0526: 0526-test-udev-test.pl-create-rules-only-once.patch
+Patch0527: 0527-test-udev-test.pl-allow-concurrent-additions-and-rem.patch
+Patch0528: 0528-test-udev-test.pl-use-computed-devnode-name.patch
+Patch0529: 0529-test-udev-test.pl-test-correctness-of-symlink-target.patch
+Patch0530: 0530-test-udev-test.pl-allow-checking-multiple-symlinks.patch
+Patch0531: 0531-test-udev-test.pl-fix-wrong-test-descriptions.patch
+Patch0532: 0532-test-udev-test.pl-last_rule-is-unsupported.patch
+Patch0533: 0533-test-udev-test.pl-Make-some-tests-a-little-harder.patch
+Patch0534: 0534-test-udev-test.pl-remove-bogus-rules-from-magic-subs.patch
+Patch0535: 0535-test-udev-test.pl-merge-space-and-var-with-space-tes.patch
+Patch0536: 0536-test-udev-test.pl-merge-import-parent-tests-into-one.patch
+Patch0537: 0537-test-udev-test.pl-count-good-results.patch
+Patch0538: 0538-tests-udev-test.pl-add-multiple-device-test.patch
+Patch0539: 0539-test-udev-test.pl-add-repeat-count.patch
+Patch0540: 0540-test-udev-test.pl-generator-for-large-list-of-block-.patch
+Patch0541: 0541-test-udev-test.pl-suppress-umount-error-message-at-s.patch
+Patch0542: 0542-test-udev_test.pl-add-expected-good-count.patch
+Patch0543: 0543-test-udev-test-gracefully-exit-when-imports-fail.patch
 
 
 %ifarch %{ix86} x86_64 aarch64
@@ -849,6 +963,9 @@ install -D -t %{buildroot}/usr/lib/systemd/ %{SOURCE3}
 # No tmp-on-tmpfs by default in RHEL. bz#876122 bz#1578772
 rm -f %{buildroot}%{_prefix}/lib/systemd/system/local-fs.target.wants/tmp.mount
 
+# bz#1844465
+rm -f %{buildroot}/etc/systemd/system/dbus-org.freedesktop.resolve1.service
+
 %find_lang %{name}
 
 # Split files in build root into rpms. See split-files.py for the
@@ -856,7 +973,7 @@ rm -f %{buildroot}%{_prefix}/lib/systemd/system/local-fs.target.wants/tmp.mount
 # here.
 python3 %{SOURCE2} %buildroot <<EOF
 %ghost %config(noreplace) /etc/crypttab
-%ghost /etc/udev/hwdb.bin
+%ghost %verify (not mode) /etc/udev/hwdb.bin
 /etc/inittab
 /etc/yum/protected.d/systemd.conf
 /usr/lib/systemd/purge-nobody-user
@@ -870,7 +987,7 @@ python3 %{SOURCE2} %buildroot <<EOF
 %ghost %config(noreplace) /etc/locale.conf
 %ghost %config(noreplace) %attr(0444,root,root) /etc/machine-id
 %ghost %config(noreplace) /etc/machine-info
-%config(noreplace) %{_sysconfdir}/rc.d/rc.local
+%verify(owner group) %config(noreplace) %{_sysconfdir}/rc.d/rc.local
 %{_sysconfdir}/rc.local
 %ghost %dir %attr(0700,root,root) /var/cache/private
 %ghost %dir %attr(0700,root,root) /var/lib/private
@@ -1104,6 +1221,138 @@ fi
 %files tests -f .file-list-tests
 
 %changelog
+* Thu Jan 28 2021 systemd maintenance team <systemd-maint@redhat.com> - 239-44
+- ci: PowerTools repo was renamed to powertools in RHEL 8.3 (#1871827)
+- ci: use quay.io instead of Docker Hub to avoid rate limits (#1871827)
+- ci: move jobs from Travis CI to GH Actions (#1871827)
+- unit: make UNIT() cast function deal with NULL pointers (#1871827)
+- use link to RHEL-8 docs (#1623116)
+- cgroup: Also set blkio.bfq.weight (#1657810)
+- units: make sure initrd-cleanup.service terminates before switching to rootfs (#1657810)
+- core: reload SELinux label cache on daemon-reload (#1888912)
+- selinux: introduce mac_selinux_create_file_prepare_at() (#1888912)
+- selinux: add trigger for policy reload to refresh internal selabel cache (#1888912)
+- udev/net_id: give RHEL-8.4 naming scheme a name (#1827462)
+- basic/stat-util: make mtime check stricter and use entire timestamp (#1642728)
+- udev: make algorithm that selects highest priority devlink less susceptible to race conditions (#1642728)
+- test: create /dev/null in test-udev.pl (#1642728)
+- test: missing "die" (#1642728)
+- udev-test: remove a check for whether the test is run in a container (#1642728)
+- udev-test: skip the test only if it can't setup its environment (#1642728)
+- udev-test: fix test skip condition (#1642728)
+- udev-test: fix missing directory test/run (#1642728)
+- udev-test: check if permitted to create block device nodes (#1642728)
+- test-udev: add a testcase of too long line (#1642728)
+- test-udev: use proper semantics for too long line with continuation (#1642728)
+- test-udev: add more tests for line continuations and comments (#1642728)
+- test-udev: add more tests for line continuation (#1642728)
+- test-udev: fix alignment and drop unnecessary white spaces (#1642728)
+- test/udev-test.pl: cleanup if skipping test (#1642728)
+- test: add test cases for empty string match (#1642728)
+- test: add test case for multi matches when use "||" (#1642728)
+- udev-test: do not rely on "mail" group being defined (#1642728)
+- test/udev-test.pl: allow multiple devices per test (#1642728)
+- test/udev-test.pl: create rules only once (#1642728)
+- test/udev-test.pl: allow concurrent additions and removals (#1642728)
+- test/udev-test.pl: use computed devnode name (#1642728)
+- test/udev-test.pl: test correctness of symlink targets (#1642728)
+- test/udev-test.pl: allow checking multiple symlinks (#1642728)
+- test/udev-test.pl: fix wrong test descriptions (#1642728)
+- test/udev-test.pl: last_rule is unsupported (#1642728)
+- test/udev-test.pl: Make some tests a little harder (#1642728)
+- test/udev-test.pl: remove bogus rules from magic subsys test (#1642728)
+- test/udev-test.pl: merge "space and var with space" tests (#1642728)
+- test/udev-test.pl: merge import parent tests into one (#1642728)
+- test/udev-test.pl: count "good" results (#1642728)
+- tests/udev-test.pl: add multiple device test (#1642728)
+- test/udev-test.pl: add repeat count (#1642728)
+- test/udev-test.pl: generator for large list of block devices (#1642728)
+- test/udev-test.pl: suppress umount error message at startup (#1642728)
+- test/udev_test.pl: add "expected good" count (#1642728)
+- test/udev-test: gracefully exit when imports fail (#1642728)
+
+* Thu Nov 26 2020 systemd maintenance team <systemd-maint@redhat.com> - 239-43
+- man: mention System Administrator's Guide in systemctl manpage (#1623116)
+- udev: introduce udev net_id "naming schemes" (#1827462)
+- meson: make net.naming-scheme= default configurable (#1827462)
+- man: describe naming schemes in a new man page (#1827462)
+- udev/net_id: parse _SUN ACPI index as a signed integer (#1827462)
+- udev/net_id: don't generate slot based names if multiple devices might claim the same slot (#1827462)
+- fix typo in ProtectSystem= option (#1871139)
+- remove references of non-existent man pages (#1876807)
+- log: Prefer logging to CLI unless JOURNAL_STREAM is set (#1865840)
+- locale-util: add new helper locale_is_installed() (#1755287)
+- test: add test case for locale_is_installed() (#1755287)
+- tree-wide: port various bits over to locale_is_installed() (#1755287)
+- install: allow instantiated units to be enabled via presets (#1812972)
+- install: small refactor to combine two function calls into one function (#1812972)
+- test: fix a memleak (#1812972)
+- docs: Add syntax for templated units to systemd.preset man page (#1812972)
+- shared/install: fix preset operations for non-service instantiated units (#1812972)
+- introduce setsockopt_int() helper (#1887181)
+- socket-util: add generic socket_pass_pktinfo() helper (#1887181)
+- core: add new PassPacketInfo= socket unit property (#1887181)
+- resolved: tweak cmsg calculation (#1887181)
+
+* Tue Nov 03 2020 systemd maintenance team <systemd-maint@redhat.com> - 239-42
+- logind: don't print warning when user@.service template is masked (#1880270)
+- build: use simple project version in pkgconfig files (#1862714)
+- basic/virt: try the /proc/1/sched hack also for PID1 (#1868877)
+- seccomp: rework how the S[UG]ID filter is installed (#1860374)
+- vconsole-setup: downgrade log message when setting font fails on dummy console (#1889996)
+- units: fix systemd.special man page reference in system-update-cleanup.service (#1871827)
+- units: drop reference to sushell man page (#1871827)
+- sd-bus: break the loop in bus_ensure_running() if the bus is not connecting (#1885553)
+- core: add new API for enqueing a job with returning the transaction data (#846319)
+- systemctl: replace switch statement by table of structures (#846319)
+- systemctl: reindent table (#846319)
+- systemctl: Only wait when there's something to wait for. (#846319)
+- systemctl: clean up start_unit_one() error handling (#846319)
+- systemctl: split out extra args generation into helper function of its own (#846319)
+- systemctl: add new --show-transaction switch (#846319)
+- test: add some basic testing that "systemctl start -T" does something (#846319)
+- man: document the new systemctl --show-transaction option (#846319)
+- socket: New option 'FlushPending' (boolean) to flush socket before entering listening state (#1870638)
+- core: remove support for API bus "started outside our own logic" (#1764282)
+- mount-setup: fix segfault in mount_cgroup_controllers when using gcc9 compiler (#1868877)
+- dbus-execute: make transfer of CPUAffinity endian safe (#12711) (#1740657)
+- core: add support for setting CPUAffinity= to special "numa" value (#1740657)
+- basic/user-util: always use base 10 for user/group numbers (#1848373)
+- parse-util: sometimes it is useful to check if a string is a valid integer, but not actually parse it (#1848373)
+- basic/parse-util: add safe_atoux64() (#1848373)
+- parse-util: allow tweaking how to parse integers (#1848373)
+- parse-util: allow '-0' as alternative to '0' and '+0' (#1848373)
+- parse-util: make return parameter optional in safe_atou16_full() (#1848373)
+- parse-util: rewrite parse_mode() on top of safe_atou_full() (#1848373)
+- user-util: be stricter in parse_uid() (#1848373)
+- strv: add new macro STARTSWITH_SET() (#1848373)
+- parse-util: also parse integers prefixed with 0b and 0o (#1848373)
+- tests: beef up integer parsing tests (#1848373)
+- shared/user-util: add compat forms of user name checking functions (#1848373)
+- shared/user-util: emit a warning on names with dots (#1848373)
+- user-util: Allow names starting with a digit (#1848373)
+- shared/user-util: allow usernames with dots in specific fields (#1848373)
+- user-util: switch order of checks in valid_user_group_name_or_id_full() (#1848373)
+- user-util: rework how we validate user names (#1848373)
+
+* Wed Oct 07 2020 systemd maintenance team <systemd-maint@redhat.com> - 239-41
+- cgroup: freezer action must be NOP when cgroup v2 freezer is not available (#1868831)
+
+* Fri Aug 28 2020 systemd maintenance team <systemd-maint@redhat.com> - 239-40
+- units: add generic boot-complete.target (#1872243)
+- man: document new "boot-complete.target" unit (#1872243)
+- core: make sure to restore the control command id, too (#1829867)
+
+* Thu Aug 06 2020 systemd maintenance team <systemd-maint@redhat.com> - 239-39
+- device: make sure we emit PropertiesChanged signal once we set sysfs (#1793533)
+- device: don't emit PropetiesChanged needlessly (#1793533)
+
+* Tue Aug 04 2020 systemd maintenance team <systemd-maint@redhat.com> - 239-38
+- spec: fix rpm verification (#1702300)
+
+* Wed Jul 08 2020 systemd maintenance team <systemd-maint@redhat.com> - 239-37
+- spec: don't package /etc/systemd/system/dbus-org.freedesktop.resolve1.service (#1844465)
+
 * Fri Jun 26 2020 systemd maintenance team <systemd-maint@redhat.com> - 239-36
 - core: don't consider SERVICE_SKIP_CONDITION for abnormal or failure restarts (#1737283)
 - selinux: do preprocessor check only in selinux-access.c (#1830861)