From 4cad4cf9c56c123c9334bbcc8847e6d514d26c61 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Jul 14 2020 02:00:46 +0000 Subject: import systemd-239-36.el8 --- diff --git a/SOURCES/0331-pid1-fix-DefaultTasksMax-initialization.patch b/SOURCES/0331-pid1-fix-DefaultTasksMax-initialization.patch index 9c89a62..0a146a3 100644 --- a/SOURCES/0331-pid1-fix-DefaultTasksMax-initialization.patch +++ b/SOURCES/0331-pid1-fix-DefaultTasksMax-initialization.patch @@ -1,7 +1,7 @@ From e809564cfa5af01a26075682d49f81a987c41dd8 Mon Sep 17 00:00:00 2001 From: Franck Bui Date: Wed, 2 Oct 2019 11:58:16 +0200 -Subject: [PATCH 331/341] pid1: fix DefaultTasksMax initialization +Subject: [PATCH] pid1: fix DefaultTasksMax initialization Otherwise DefaultTasksMax is always set to "inifinity". @@ -36,6 +36,3 @@ index d6550ea161..45d09b1e11 100644 /* Assign configuration defaults */ reset_arguments(); --- -2.21.1 - diff --git a/SOURCES/0332-cgroup-make-sure-that-cpuset-is-supported-on-cgroup-.patch b/SOURCES/0332-cgroup-make-sure-that-cpuset-is-supported-on-cgroup-.patch index 2596a8e..d2c0d2e 100644 --- a/SOURCES/0332-cgroup-make-sure-that-cpuset-is-supported-on-cgroup-.patch +++ b/SOURCES/0332-cgroup-make-sure-that-cpuset-is-supported-on-cgroup-.patch @@ -1,8 +1,8 @@ From 5fc2d94fbf8271bb340e834f832af5d890c267bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= Date: Tue, 3 Mar 2020 11:45:00 +0100 -Subject: [PATCH 332/341] cgroup: make sure that cpuset is supported on cgroup - v2 and disabled with v1 +Subject: [PATCH] cgroup: make sure that cpuset is supported on cgroup v2 and + disabled with v1 Resolves: #1808940 @@ -38,6 +38,3 @@ index 6f47c3aacb..92bc1f2543 100644 n = cgroup_controller_to_string(c); if (controller_is_accessible(n) >= 0) mask |= CGROUP_CONTROLLER_TO_MASK(c); --- -2.21.1 - diff --git a/SOURCES/0333-test-introduce-TEST-36-NUMAPOLICY.patch b/SOURCES/0333-test-introduce-TEST-36-NUMAPOLICY.patch index 2e1b42a..811b91e 100644 --- a/SOURCES/0333-test-introduce-TEST-36-NUMAPOLICY.patch +++ b/SOURCES/0333-test-introduce-TEST-36-NUMAPOLICY.patch @@ -1,7 +1,7 @@ From 90dda340e4adeb1126639a849d4f31ae327fdc4b Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Tue, 25 Jun 2019 23:01:40 +0200 -Subject: [PATCH 333/341] test: introduce TEST-36-NUMAPOLICY +Subject: [PATCH] test: introduce TEST-36-NUMAPOLICY (cherry picked from commit 8f65e26508969610ac934d1aadbade8223bfcaac) @@ -378,6 +378,3 @@ index 0000000000..e15087b137 +echo OK > /testok + +exit 0 --- -2.21.1 - diff --git a/SOURCES/0334-test-replace-tail-f-with-journal-cursor-which-should.patch b/SOURCES/0334-test-replace-tail-f-with-journal-cursor-which-should.patch index 8254d16..afe81ee 100644 --- a/SOURCES/0334-test-replace-tail-f-with-journal-cursor-which-should.patch +++ b/SOURCES/0334-test-replace-tail-f-with-journal-cursor-which-should.patch @@ -1,8 +1,8 @@ From b93a2617d49d9636801130d974995cabe6335b71 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Mon, 1 Jul 2019 09:27:59 +0200 -Subject: [PATCH 334/341] test: replace `tail -f` with journal cursor which - should be... +Subject: [PATCH] test: replace `tail -f` with journal cursor which should + be... more reliable @@ -50,6 +50,3 @@ index e15087b137..306a96b517 100755 } checkNUMA() { --- -2.21.1 - diff --git a/SOURCES/0335-test-support-MPOL_LOCAL-matching-in-unpatched-strace.patch b/SOURCES/0335-test-support-MPOL_LOCAL-matching-in-unpatched-strace.patch index 8d293d9..88a2e32 100644 --- a/SOURCES/0335-test-support-MPOL_LOCAL-matching-in-unpatched-strace.patch +++ b/SOURCES/0335-test-support-MPOL_LOCAL-matching-in-unpatched-strace.patch @@ -1,7 +1,7 @@ From d6d43b81df76d571d57f83ceb050c8b4ac4701b8 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Mon, 1 Jul 2019 13:08:26 +0200 -Subject: [PATCH 335/341] test: support MPOL_LOCAL matching in unpatched strace +Subject: [PATCH] test: support MPOL_LOCAL matching in unpatched strace versions The MPOL_LOCAL constant is not recognized in current strace versions. @@ -56,6 +56,3 @@ index 306a96b517..a4134bdeca 100755 echo "systemd-run NUMAPolicy support" runUnit='numa-systemd-run-test.service' --- -2.21.1 - diff --git a/SOURCES/0336-test-make-sure-the-strace-process-is-indeed-dead.patch b/SOURCES/0336-test-make-sure-the-strace-process-is-indeed-dead.patch index 55d9b4f..24d38b3 100644 --- a/SOURCES/0336-test-make-sure-the-strace-process-is-indeed-dead.patch +++ b/SOURCES/0336-test-make-sure-the-strace-process-is-indeed-dead.patch @@ -1,7 +1,7 @@ From 60813b55f9b5b44b14f38bbc1b8c0d2b30e3f6c7 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Mon, 1 Jul 2019 19:53:45 +0200 -Subject: [PATCH 336/341] test: make sure the strace process is indeed dead +Subject: [PATCH] test: make sure the strace process is indeed dead It may take a few moments for the strace process to properly terminate and write all logs to the backing storage @@ -48,6 +48,3 @@ index a4134bdeca..daed8fcc1c 100755 stopStrace } --- -2.21.1 - diff --git a/SOURCES/0337-test-skip-the-test-on-systems-without-NUMA-support.patch b/SOURCES/0337-test-skip-the-test-on-systems-without-NUMA-support.patch index 2680709..eef7e87 100644 --- a/SOURCES/0337-test-skip-the-test-on-systems-without-NUMA-support.patch +++ b/SOURCES/0337-test-skip-the-test-on-systems-without-NUMA-support.patch @@ -1,7 +1,7 @@ From ad3e4a0f010c9497b01d89de54213af982f8afd2 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Tue, 2 Jul 2019 09:52:45 +0200 -Subject: [PATCH 337/341] test: skip the test on systems without NUMA support +Subject: [PATCH] test: skip the test on systems without NUMA support (cherry picked from commit b030847163e9bd63d3dd6eec6ac7f336411faba6) @@ -34,6 +34,3 @@ index daed8fcc1c..4b715d305a 100755 writeTestUnit # Create systemd config drop-in directory --- -2.21.1 - diff --git a/SOURCES/0338-test-give-strace-some-time-to-initialize.patch b/SOURCES/0338-test-give-strace-some-time-to-initialize.patch index 5d68666..6cd3426 100644 --- a/SOURCES/0338-test-give-strace-some-time-to-initialize.patch +++ b/SOURCES/0338-test-give-strace-some-time-to-initialize.patch @@ -1,7 +1,7 @@ From 66f6f6304d87b2fe0c4f91373c7d1b836de1b054 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Tue, 23 Jul 2019 00:56:04 +0200 -Subject: [PATCH 338/341] test: give strace some time to initialize +Subject: [PATCH] test: give strace some time to initialize The `coproc` implementation seems to be a little bit different in older bash versions, so the `strace` is sometimes started AFTER `systemctl @@ -28,6 +28,3 @@ index 4b715d305a..1c8cf7e6b6 100755 } stopStrace() { --- -2.21.1 - diff --git a/SOURCES/0339-test-add-a-simple-sanity-check-for-systems-without-N.patch b/SOURCES/0339-test-add-a-simple-sanity-check-for-systems-without-N.patch index 10d52aa..2bb77a3 100644 --- a/SOURCES/0339-test-add-a-simple-sanity-check-for-systems-without-N.patch +++ b/SOURCES/0339-test-add-a-simple-sanity-check-for-systems-without-N.patch @@ -1,8 +1,8 @@ From 8239ecf0b4b8bbe5b3c17964d230d13cee4d900a Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Mon, 5 Aug 2019 14:38:45 +0200 -Subject: [PATCH 339/341] test: add a simple sanity check for systems without - NUMA support +Subject: [PATCH] test: add a simple sanity check for systems without NUMA + support (cherry picked from commit 92f8e978923f962a57d744c5f358520ac06f7892) @@ -389,6 +389,3 @@ index 1c8cf7e6b6..a5ac788178 100755 # Cleanup rm -rf $testDir --- -2.21.1 - diff --git a/SOURCES/0340-test-drop-the-missed-exit-1-expression.patch b/SOURCES/0340-test-drop-the-missed-exit-1-expression.patch index d0df520..716dcf2 100644 --- a/SOURCES/0340-test-drop-the-missed-exit-1-expression.patch +++ b/SOURCES/0340-test-drop-the-missed-exit-1-expression.patch @@ -1,7 +1,7 @@ From 772f08f8255d7ab921c344ab4243249cbd1c37fc Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Sat, 10 Aug 2019 16:05:07 +0200 -Subject: [PATCH 340/341] test: drop the missed || exit 1 expression +Subject: [PATCH] test: drop the missed || exit 1 expression ...as we've already done in the rest of the testsuite, see cc469c3dfc398210f38f819d367e68646c71d8da @@ -33,6 +33,3 @@ index f0a321e7a1..3b3b120423 100755 setup_nspawn_root ddebug "umount $TESTDIR/root" --- -2.21.1 - diff --git a/SOURCES/0341-test-replace-cursor-file-with-a-plain-cursor.patch b/SOURCES/0341-test-replace-cursor-file-with-a-plain-cursor.patch index f3b1f25..83a7f55 100644 --- a/SOURCES/0341-test-replace-cursor-file-with-a-plain-cursor.patch +++ b/SOURCES/0341-test-replace-cursor-file-with-a-plain-cursor.patch @@ -1,7 +1,7 @@ From 0bef8805c81eecfe3960bf00b6022837e4979198 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Tue, 3 Mar 2020 15:54:29 +0100 -Subject: [PATCH 341/341] test: replace cursor file with a plain cursor +Subject: [PATCH] test: replace cursor file with a plain cursor systemd in RHEL 8 doesn't support the --cursor-file option, so let's fall back to a plain cursor string @@ -57,6 +57,3 @@ index a5ac788178..bffac4ffe6 100755 } checkNUMA() { --- -2.21.1 - diff --git a/SOURCES/0343-swap-finish-the-secondary-swap-units-jobs-if-deactiv.patch b/SOURCES/0343-swap-finish-the-secondary-swap-units-jobs-if-deactiv.patch new file mode 100644 index 0000000..b196d0e --- /dev/null +++ b/SOURCES/0343-swap-finish-the-secondary-swap-units-jobs-if-deactiv.patch @@ -0,0 +1,79 @@ +From b89a1a9d19aa806feb984c8dba25116b5b5a52bc Mon Sep 17 00:00:00 2001 +From: HATAYAMA Daisuke +Date: Wed, 24 Jul 2019 23:54:48 -0400 +Subject: [PATCH] swap: finish the secondary swap units' jobs if deactivation + of the primary swap unit fails + +Currently, if deactivation of the primary swap unit fails: + + # LANG=C systemctl --no-pager stop dev-mapper-fedora\\x2dswap.swap + Job for dev-mapper-fedora\x2dswap.swap failed. + See "systemctl status "dev-mapper-fedora\\x2dswap.swap"" and "journalctl -xe" for details. + +then there are still the running stop jobs for all the secondary swap units +that follow the primary one: + + # systemctl list-jobs + JOB UNIT TYPE STATE + 3233 dev-disk-by\x2duuid-2dc8b9b1\x2da0a5\x2d44d8\x2d89c4\x2d6cdd26cd5ce0.swap stop running + 3232 dev-dm\x2d1.swap stop running + 3231 dev-disk-by\x2did-dm\x2duuid\x2dLVM\x2dyuXWpCCIurGzz2nkGCVnUFSi7GH6E3ZcQjkKLnF0Fil0RJmhoLN8fcOnDybWCMTj.swap stop running + 3230 dev-disk-by\x2did-dm\x2dname\x2dfedora\x2dswap.swap stop running + 3234 dev-fedora-swap.swap stop running + + 5 jobs listed. + +This remains endlessly because their JobTimeoutUSec is infinity: + + # LANG=C systemctl show -p JobTimeoutUSec dev-fedora-swap.swap + JobTimeoutUSec=infinity + +If this issue happens during system shutdown, the system shutdown appears to +get hang and the system will be forcibly shutdown or rebooted 30 minutes later +by the following configuration: + + # grep -E "^JobTimeout" /usr/lib/systemd/system/reboot.target + JobTimeoutSec=30min + JobTimeoutAction=reboot-force + +The scenario in the real world seems that there is some service unit with +KillMode=none, processes whose memory is being swapped out are not killed +during stop operation in the service unit and then swapoff command fails. + +On the other hand, it works well in successful case of swapoff command because +the secondary jobs monitor /proc/swaps file and can detect deletion of the +corresponding swap file. + +This commit fixes the issue by finishing the secondary swap units' jobs if +deactivation of the primary swap unit fails. + +Fixes: #11577 +(cherry picked from commit 9c1f969d40f84d5cc98d810bab8b24148b2d8928) + +Resolves: #1749622 +--- + src/core/swap.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/src/core/swap.c b/src/core/swap.c +index e717dbb54a..66a62d8a37 100644 +--- a/src/core/swap.c ++++ b/src/core/swap.c +@@ -682,9 +682,15 @@ static void swap_enter_active(Swap *s, SwapResult f) { + static void swap_enter_dead_or_active(Swap *s, SwapResult f) { + assert(s); + +- if (s->from_proc_swaps) ++ if (s->from_proc_swaps) { ++ Swap *other; ++ + swap_enter_active(s, f); +- else ++ ++ LIST_FOREACH_OTHERS(same_devnode, other, s) ++ if (UNIT(other)->job) ++ swap_enter_dead_or_active(other, f); ++ } else + swap_enter_dead(s, f); + } + diff --git a/SOURCES/0344-resolved-Recover-missing-PrivateTmp-yes-and-ProtectS.patch b/SOURCES/0344-resolved-Recover-missing-PrivateTmp-yes-and-ProtectS.patch new file mode 100644 index 0000000..9c59bdc --- /dev/null +++ b/SOURCES/0344-resolved-Recover-missing-PrivateTmp-yes-and-ProtectS.patch @@ -0,0 +1,41 @@ +From d9ae3222cfbd5d2a48e6dbade6617085cc76f1c1 Mon Sep 17 00:00:00 2001 +From: HATAYAMA Daisuke +Date: Tue, 25 Feb 2020 13:35:50 -0500 +Subject: [PATCH] resolved: Recover missing PrivateTmp=yes and + ProtectSystem=strict + +Since the commit b61e8046ebcb28225423fc0073183d68d4c577c4, +systemd-resolved.service often fails to start with the following message: + + Failed at step NAMESPACE spawning /usr/bin/mount: Read-only file system + +This is because dropping DynamicUser=yes dropped implicit PrivateTmp=yes and +also implicit After=systemd-tmpfiles-setup.service, and thus +systemd-resolved.service can start before systemd-remount-fs.service. As a +result, mount operations associated with PrivateDevices= can be performed to +still read-only filesystems. + +To fix this issue, it's better to recover PrivateTmp=yes and +ProtectSystem=strict just as the upstream commit +62fb7e80fcc45a1530ed58a84980be8cfafa9b3e (Revert "resolve: enable DynamicUser= +for systemd-resolved.service"). + +Resolves: #1810869 +--- + units/systemd-resolved.service.in | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/units/systemd-resolved.service.in b/units/systemd-resolved.service.in +index 6c2ad5ca86..aad1a53a5f 100644 +--- a/units/systemd-resolved.service.in ++++ b/units/systemd-resolved.service.in +@@ -28,7 +28,9 @@ WatchdogSec=3min + User=systemd-resolve + 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 + ProtectHome=yes + ProtectControlGroups=yes + ProtectKernelTunables=yes diff --git a/SOURCES/0345-bus_open-leak-sd_event_source-when-udevadm-trigger.patch b/SOURCES/0345-bus_open-leak-sd_event_source-when-udevadm-trigger.patch new file mode 100644 index 0000000..b5536da --- /dev/null +++ b/SOURCES/0345-bus_open-leak-sd_event_source-when-udevadm-trigger.patch @@ -0,0 +1,30 @@ +From 448b34284c09469eaa2168291ccb34afc3e4cc1d Mon Sep 17 00:00:00 2001 +From: ven <2988994+hexiaowen@users.noreply.github.com> +Date: Wed, 22 May 2019 14:24:28 +0800 +Subject: [PATCH] =?UTF-8?q?bus=5Fopen=20leak=20sd=5Fevent=5Fsource=20when?= + =?UTF-8?q?=20udevadm=20trigger=E3=80=82?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +On my host, when executing the udevadm trigger, I only receive the change event, which causes memleak + +(cherry picked from commit b2774a3ae692113e1f47a336a6c09bac9cfb49ad) + +Resolves: #1798504 +--- + src/login/logind-button.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/login/logind-button.c b/src/login/logind-button.c +index 0defa6b9ba..9944eb2316 100644 +--- a/src/login/logind-button.c ++++ b/src/login/logind-button.c +@@ -341,6 +341,7 @@ int button_open(Button *b) { + + (void) button_set_mask(b); + ++ b->io_event_source = sd_event_source_unref(b->io_event_source); + r = sd_event_add_io(b->manager->event, &b->io_event_source, b->fd, EPOLLIN, button_dispatch, b); + if (r < 0) { + log_error_errno(r, "Failed to add button event: %m"); diff --git a/SOURCES/0346-core-rework-StopWhenUnneeded-logic.patch b/SOURCES/0346-core-rework-StopWhenUnneeded-logic.patch new file mode 100644 index 0000000..fcddae3 --- /dev/null +++ b/SOURCES/0346-core-rework-StopWhenUnneeded-logic.patch @@ -0,0 +1,339 @@ +From 5458256264c97eee521caf07c705f549a0f0bd55 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Thu, 9 Aug 2018 16:26:27 +0200 +Subject: [PATCH] core: rework StopWhenUnneeded= logic + +Previously, we'd act immediately on StopWhenUnneeded= when a unit state +changes. With this rework we'll maintain a queue instead: whenever +there's the chance that StopWhenUneeded= might have an effect we enqueue +the unit, and process it later when we have nothing better to do. + +This should make the implementation a bit more reliable, as the unit notify event +cannot immediately enqueue tons of side-effect jobs that might +contradict each other, but we do so only in a strictly ordered fashion, +from the main event loop. + +This slightly changes the check when to consider a unit "unneeded". +Previously, we'd assume that a unit in "deactivating" state could also +be cleaned up. With this new logic we'll only consider units unneeded +that are fully up and have no job queued. This means that whenever +there's something pending for a unit we won't clean it up. + +(cherry picked from commit a3c1168ac293f16d9343d248795bb4c246aaff4a) + +Resolves: #1798046 +--- + src/core/manager.c | 43 ++++++++++++++++ + src/core/manager.h | 3 ++ + src/core/unit.c | 122 +++++++++++++++++++++++++-------------------- + src/core/unit.h | 7 +++ + 4 files changed, 120 insertions(+), 55 deletions(-) + +diff --git a/src/core/manager.c b/src/core/manager.c +index 0eae7d46fb..4c04896aaa 100644 +--- a/src/core/manager.c ++++ b/src/core/manager.c +@@ -1211,6 +1211,45 @@ static unsigned manager_dispatch_gc_job_queue(Manager *m) { + return n; + } + ++static unsigned manager_dispatch_stop_when_unneeded_queue(Manager *m) { ++ unsigned n = 0; ++ Unit *u; ++ int r; ++ ++ assert(m); ++ ++ while ((u = m->stop_when_unneeded_queue)) { ++ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; ++ assert(m->stop_when_unneeded_queue); ++ ++ assert(u->in_stop_when_unneeded_queue); ++ LIST_REMOVE(stop_when_unneeded_queue, m->stop_when_unneeded_queue, u); ++ u->in_stop_when_unneeded_queue = false; ++ ++ n++; ++ ++ if (!unit_is_unneeded(u)) ++ continue; ++ ++ log_unit_debug(u, "Unit is not needed anymore."); ++ ++ /* If stopping a unit fails continuously we might enter a stop loop here, hence stop acting on the ++ * service being unnecessary after a while. */ ++ ++ if (!ratelimit_below(&u->auto_stop_ratelimit)) { ++ log_unit_warning(u, "Unit not needed anymore, but not stopping since we tried this too often recently."); ++ continue; ++ } ++ ++ /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */ ++ r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL); ++ if (r < 0) ++ log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r)); ++ } ++ ++ return n; ++} ++ + static void manager_clear_jobs_and_units(Manager *m) { + Unit *u; + +@@ -1228,6 +1267,7 @@ static void manager_clear_jobs_and_units(Manager *m) { + assert(!m->cleanup_queue); + assert(!m->gc_unit_queue); + assert(!m->gc_job_queue); ++ assert(!m->stop_when_unneeded_queue); + + assert(hashmap_isempty(m->jobs)); + assert(hashmap_isempty(m->units)); +@@ -2824,6 +2864,9 @@ int manager_loop(Manager *m) { + if (manager_dispatch_cgroup_realize_queue(m) > 0) + continue; + ++ if (manager_dispatch_stop_when_unneeded_queue(m) > 0) ++ continue; ++ + if (manager_dispatch_dbus_queue(m) > 0) + continue; + +diff --git a/src/core/manager.h b/src/core/manager.h +index fa47952d24..40568d3c8b 100644 +--- a/src/core/manager.h ++++ b/src/core/manager.h +@@ -130,6 +130,9 @@ struct Manager { + /* Target units whose default target dependencies haven't been set yet */ + LIST_HEAD(Unit, target_deps_queue); + ++ /* Units that might be subject to StopWhenUnneeded= clean-up */ ++ LIST_HEAD(Unit, stop_when_unneeded_queue); ++ + sd_event *event; + + /* This maps PIDs we care about to units that are interested in. We allow multiple units to he interested in +diff --git a/src/core/unit.c b/src/core/unit.c +index e1f5e6f7bd..40f138d25c 100644 +--- a/src/core/unit.c ++++ b/src/core/unit.c +@@ -438,6 +438,22 @@ void unit_add_to_dbus_queue(Unit *u) { + u->in_dbus_queue = true; + } + ++void unit_add_to_stop_when_unneeded_queue(Unit *u) { ++ assert(u); ++ ++ if (u->in_stop_when_unneeded_queue) ++ return; ++ ++ if (!u->stop_when_unneeded) ++ return; ++ ++ if (!UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) ++ return; ++ ++ LIST_PREPEND(stop_when_unneeded_queue, u->manager->stop_when_unneeded_queue, u); ++ u->in_stop_when_unneeded_queue = true; ++} ++ + static void bidi_set_free(Unit *u, Hashmap *h) { + Unit *other; + Iterator i; +@@ -634,6 +650,9 @@ void unit_free(Unit *u) { + if (u->in_target_deps_queue) + LIST_REMOVE(target_deps_queue, u->manager->target_deps_queue, u); + ++ if (u->in_stop_when_unneeded_queue) ++ LIST_REMOVE(stop_when_unneeded_queue, u->manager->stop_when_unneeded_queue, u); ++ + safe_close(u->ip_accounting_ingress_map_fd); + safe_close(u->ip_accounting_egress_map_fd); + +@@ -1950,55 +1969,71 @@ bool unit_can_reload(Unit *u) { + return UNIT_VTABLE(u)->reload; + } + +-static void unit_check_unneeded(Unit *u) { +- +- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; +- +- static const UnitDependency needed_dependencies[] = { ++bool unit_is_unneeded(Unit *u) { ++ static const UnitDependency deps[] = { + UNIT_REQUIRED_BY, + UNIT_REQUISITE_OF, + UNIT_WANTED_BY, + UNIT_BOUND_BY, + }; +- +- unsigned j; +- int r; ++ size_t j; + + assert(u); + +- /* If this service shall be shut down when unneeded then do +- * so. */ +- + if (!u->stop_when_unneeded) +- return; ++ return false; + +- if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) +- return; ++ /* Don't clean up while the unit is transitioning or is even inactive. */ ++ if (!UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) ++ return false; ++ if (u->job) ++ return false; + +- for (j = 0; j < ELEMENTSOF(needed_dependencies); j++) { ++ for (j = 0; j < ELEMENTSOF(deps); j++) { + Unit *other; + Iterator i; + void *v; + +- HASHMAP_FOREACH_KEY(v, other, u->dependencies[needed_dependencies[j]], i) +- if (unit_active_or_pending(other) || unit_will_restart(other)) +- return; +- } ++ /* If a dependending unit has a job queued, or is active (or in transitioning), or is marked for ++ * restart, then don't clean this one up. */ + +- /* If stopping a unit fails continuously we might enter a stop +- * loop here, hence stop acting on the service being +- * unnecessary after a while. */ +- if (!ratelimit_below(&u->auto_stop_ratelimit)) { +- log_unit_warning(u, "Unit not needed anymore, but not stopping since we tried this too often recently."); +- return; ++ HASHMAP_FOREACH_KEY(v, other, u->dependencies[deps[j]], i) { ++ if (u->job) ++ return false; ++ ++ if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) ++ return false; ++ ++ if (unit_will_restart(other)) ++ return false; ++ } + } + +- log_unit_info(u, "Unit not needed anymore. Stopping."); ++ return true; ++} + +- /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */ +- r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL); +- if (r < 0) +- log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r)); ++static void check_unneeded_dependencies(Unit *u) { ++ ++ static const UnitDependency deps[] = { ++ UNIT_REQUIRES, ++ UNIT_REQUISITE, ++ UNIT_WANTS, ++ UNIT_BINDS_TO, ++ }; ++ size_t j; ++ ++ assert(u); ++ ++ /* Add all units this unit depends on to the queue that processes StopWhenUnneeded= behaviour. */ ++ ++ for (j = 0; j < ELEMENTSOF(deps); j++) { ++ Unit *other; ++ Iterator i; ++ void *v; ++ ++ HASHMAP_FOREACH_KEY(v, other, u->dependencies[deps[j]], i) ++ unit_add_to_stop_when_unneeded_queue(other); ++ } + } + + static void unit_check_binds_to(Unit *u) { +@@ -2098,29 +2133,6 @@ static void retroactively_stop_dependencies(Unit *u) { + manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); + } + +-static void check_unneeded_dependencies(Unit *u) { +- Unit *other; +- Iterator i; +- void *v; +- +- assert(u); +- assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))); +- +- /* Garbage collect services that might not be needed anymore, if enabled */ +- HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REQUIRES], i) +- if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) +- unit_check_unneeded(other); +- HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_WANTS], i) +- if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) +- unit_check_unneeded(other); +- HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REQUISITE], i) +- if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) +- unit_check_unneeded(other); +- HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BINDS_TO], i) +- if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) +- unit_check_unneeded(other); +-} +- + void unit_start_on_failure(Unit *u) { + Unit *other; + Iterator i; +@@ -2423,7 +2435,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag + } + + /* stop unneeded units regardless if going down was expected or not */ +- if (UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) ++ if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + check_unneeded_dependencies(u); + + if (ns != os && ns == UNIT_FAILED) { +@@ -2483,7 +2495,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag + + if (!MANAGER_IS_RELOADING(u->manager)) { + /* Maybe we finished startup and are now ready for being stopped because unneeded? */ +- unit_check_unneeded(u); ++ unit_add_to_stop_when_unneeded_queue(u); + + /* Maybe we finished startup, but something we needed has vanished? Let's die then. (This happens when + * something BindsTo= to a Type=oneshot unit, as these units go directly from starting to inactive, +diff --git a/src/core/unit.h b/src/core/unit.h +index 99755823eb..595ee88d43 100644 +--- a/src/core/unit.h ++++ b/src/core/unit.h +@@ -212,6 +212,9 @@ typedef struct Unit { + /* Target dependencies queue */ + LIST_FIELDS(Unit, target_deps_queue); + ++ /* Queue of units with StopWhenUnneeded set that shell be checked for clean-up. */ ++ LIST_FIELDS(Unit, stop_when_unneeded_queue); ++ + /* PIDs we keep an eye on. Note that a unit might have many + * more, but these are the ones we care enough about to + * process SIGCHLD for */ +@@ -322,6 +325,7 @@ typedef struct Unit { + bool in_cgroup_realize_queue:1; + bool in_cgroup_empty_queue:1; + bool in_target_deps_queue:1; ++ bool in_stop_when_unneeded_queue:1; + + bool sent_dbus_new_signal:1; + +@@ -615,6 +619,7 @@ void unit_add_to_dbus_queue(Unit *u); + void unit_add_to_cleanup_queue(Unit *u); + void unit_add_to_gc_queue(Unit *u); + void unit_add_to_target_deps_queue(Unit *u); ++void unit_add_to_stop_when_unneeded_queue(Unit *u); + + int unit_merge(Unit *u, Unit *other); + int unit_merge_by_name(Unit *u, const char *other); +@@ -751,6 +756,8 @@ bool unit_type_supported(UnitType t); + + bool unit_is_pristine(Unit *u); + ++bool unit_is_unneeded(Unit *u); ++ + pid_t unit_control_pid(Unit *u); + pid_t unit_main_pid(Unit *u); + diff --git a/SOURCES/0347-pid1-fix-the-names-of-AllowedCPUs-and-AllowedMemoryN.patch b/SOURCES/0347-pid1-fix-the-names-of-AllowedCPUs-and-AllowedMemoryN.patch new file mode 100644 index 0000000..0431571 --- /dev/null +++ b/SOURCES/0347-pid1-fix-the-names-of-AllowedCPUs-and-AllowedMemoryN.patch @@ -0,0 +1,81 @@ +From 6ecf0a945d6d4187995de7e79e3ed75f4827289a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Sun, 24 Nov 2019 14:14:43 +0100 +Subject: [PATCH] pid1: fix the names of AllowedCPUs= and AllowedMemoryNodes= + +The original PR was submitted with CPUSetCpus and CPUSetMems, which was later +changed to AllowedCPUs and AllowedMemmoryNodes everywhere (including the parser +used by systemd-run), but not in the parser for unit files. + +Since we already released -rc1, let's keep support for the old names. I think +we can remove it in a release or two if anyone remembers to do that. + +Fixes #14126. Follow-up for 047f5d63d7a1ab75073f8485e2f9b550d25b0772. + +(cherry picked from commit 0b8d3075872a05e0449906d24421ce192f50c29f) + +Related: #1818054 +--- + src/core/load-fragment-gperf.gperf.m4 | 4 ++-- + src/core/load-fragment.c | 4 ++-- + src/core/load-fragment.h | 4 ++-- + 3 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 +index ebb44df487..161c5a2c82 100644 +--- a/src/core/load-fragment-gperf.gperf.m4 ++++ b/src/core/load-fragment-gperf.gperf.m4 +@@ -161,14 +161,14 @@ $1.KillSignal, config_parse_signal, 0, + )m4_dnl + m4_define(`CGROUP_CONTEXT_CONFIG_ITEMS', + `$1.Slice, config_parse_unit_slice, 0, 0 ++$1.AllowedCPUs, config_parse_allowed_cpus, 0, offsetof($1, cgroup_context) ++$1.AllowedMemoryNodes, config_parse_allowed_mems, 0, offsetof($1, cgroup_context) + $1.CPUAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.cpu_accounting) + $1.CPUWeight, config_parse_cg_weight, 0, offsetof($1, cgroup_context.cpu_weight) + $1.StartupCPUWeight, config_parse_cg_weight, 0, offsetof($1, cgroup_context.startup_cpu_weight) + $1.CPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.cpu_shares) + $1.StartupCPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.startup_cpu_shares) + $1.CPUQuota, config_parse_cpu_quota, 0, offsetof($1, cgroup_context) +-$1.CPUSetCpus, config_parse_cpuset_cpus, 0, offsetof($1, cgroup_context) +-$1.CPUSetMems, config_parse_cpuset_mems, 0, offsetof($1, cgroup_context) + $1.MemoryAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.memory_accounting) + $1.MemoryLow, config_parse_memory_limit, 0, offsetof($1, cgroup_context) + $1.MemoryHigh, config_parse_memory_limit, 0, offsetof($1, cgroup_context) +diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c +index 6debf82401..2082166afb 100644 +--- a/src/core/load-fragment.c ++++ b/src/core/load-fragment.c +@@ -3011,7 +3011,7 @@ int config_parse_cpu_quota( + return 0; + } + +-int config_parse_cpuset_cpus( ++int config_parse_allowed_cpus( + const char *unit, + const char *filename, + unsigned line, +@@ -3030,7 +3030,7 @@ int config_parse_cpuset_cpus( + return 0; + } + +-int config_parse_cpuset_mems( ++int config_parse_allowed_mems( + const char *unit, + const char *filename, + unsigned line, +diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h +index 6612e1fb32..424fa478a7 100644 +--- a/src/core/load-fragment.h ++++ b/src/core/load-fragment.h +@@ -86,8 +86,8 @@ CONFIG_PARSER_PROTOTYPE(config_parse_set_status); + CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv); + CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems); + CONFIG_PARSER_PROTOTYPE(config_parse_cpu_quota); +-CONFIG_PARSER_PROTOTYPE(config_parse_cpuset_cpus); +-CONFIG_PARSER_PROTOTYPE(config_parse_cpuset_mems); ++CONFIG_PARSER_PROTOTYPE(config_parse_allowed_cpus); ++CONFIG_PARSER_PROTOTYPE(config_parse_allowed_mems); + CONFIG_PARSER_PROTOTYPE(config_parse_protect_home); + CONFIG_PARSER_PROTOTYPE(config_parse_protect_system); + CONFIG_PARSER_PROTOTYPE(config_parse_bus_name); diff --git a/SOURCES/0348-core-fix-re-realization-of-cgroup-siblings.patch b/SOURCES/0348-core-fix-re-realization-of-cgroup-siblings.patch new file mode 100644 index 0000000..173b2e3 --- /dev/null +++ b/SOURCES/0348-core-fix-re-realization-of-cgroup-siblings.patch @@ -0,0 +1,62 @@ +From ca843e40587fb87fe20bd0561b6ccb42aaafc4ab Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Thu, 9 Jan 2020 17:30:31 +0100 +Subject: [PATCH] core: fix re-realization of cgroup siblings + +This is a fix-up for eef85c4a3f8054d29383a176f6cebd1ef3a15b9a which +broke this. + +Tracked down by @w-simon + +Fixes: #14453 +(cherry picked from commit 65f6b6bdcb500c576674b5838e4cc4c35e18bfde) + +Related: #1818054 +--- + src/core/cgroup.c | 21 +++++++-------------- + 1 file changed, 7 insertions(+), 14 deletions(-) + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index 664d269483..3f7665b755 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -1796,32 +1796,25 @@ unsigned manager_dispatch_cgroup_realize_queue(Manager *m) { + static void unit_add_siblings_to_cgroup_realize_queue(Unit *u) { + Unit *slice; + +- /* This adds the siblings of the specified unit and the +- * siblings of all parent units to the cgroup queue. (But +- * neither the specified unit itself nor the parents.) */ ++ /* This adds the siblings of the specified unit and the siblings of all parent units to the cgroup ++ * queue. (But neither the specified unit itself nor the parents.) */ + + while ((slice = UNIT_DEREF(u->slice))) { + Iterator i; + Unit *m; + void *v; + +- HASHMAP_FOREACH_KEY(v, m, u->dependencies[UNIT_BEFORE], i) { +- if (m == u) +- continue; +- +- /* Skip units that have a dependency on the slice +- * but aren't actually in it. */ ++ HASHMAP_FOREACH_KEY(v, m, slice->dependencies[UNIT_BEFORE], i) { ++ /* Skip units that have a dependency on the slice but aren't actually in it. */ + if (UNIT_DEREF(m->slice) != slice) + continue; + +- /* No point in doing cgroup application for units +- * without active processes. */ ++ /* No point in doing cgroup application for units without active processes. */ + if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(m))) + continue; + +- /* If the unit doesn't need any new controllers +- * and has current ones realized, it doesn't need +- * any changes. */ ++ /* If the unit doesn't need any new controllers and has current ones realized, it ++ * doesn't need any changes. */ + if (unit_has_mask_realized(m, + unit_get_target_mask(m), + unit_get_enable_mask(m), diff --git a/SOURCES/0349-basic-use-comma-as-separator-in-cpuset-cgroup-cpu-ra.patch b/SOURCES/0349-basic-use-comma-as-separator-in-cpuset-cgroup-cpu-ra.patch new file mode 100644 index 0000000..8d42493 --- /dev/null +++ b/SOURCES/0349-basic-use-comma-as-separator-in-cpuset-cgroup-cpu-ra.patch @@ -0,0 +1,99 @@ +From 9fe3b9c7165afeedcf9f31959c436bcec233bb4d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= +Date: Tue, 14 Apr 2020 16:16:45 +0200 +Subject: [PATCH] basic: use comma as separator in cpuset cgroup cpu ranges + +This is a workaround for +https://bugzilla.redhat.com/show_bug.cgi?id=1819152 and should be +reverted in RHEL-8.3. + +RHEL-only + +Related: #1818054 +--- + src/basic/cpu-set-util.c | 45 ++++++++++++++++++++++++++++++++++++++++ + src/basic/cpu-set-util.h | 1 + + src/core/cgroup.c | 2 +- + 3 files changed, 47 insertions(+), 1 deletion(-) + +diff --git a/src/basic/cpu-set-util.c b/src/basic/cpu-set-util.c +index 36cb017ae7..51752ad1a6 100644 +--- a/src/basic/cpu-set-util.c ++++ b/src/basic/cpu-set-util.c +@@ -86,6 +86,51 @@ char *cpu_set_to_range_string(const CPUSet *set) { + return TAKE_PTR(str) ?: strdup(""); + } + ++/* XXX(msekleta): this is the workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1819152, remove in 8.3 */ ++char *cpu_set_to_range_string_kernel(const CPUSet *set) { ++ unsigned range_start = 0, range_end; ++ _cleanup_free_ char *str = NULL; ++ size_t allocated = 0, len = 0; ++ bool in_range = false; ++ int r; ++ ++ for (unsigned i = 0; i < set->allocated * 8; i++) ++ if (CPU_ISSET_S(i, set->allocated, set->set)) { ++ if (in_range) ++ range_end++; ++ else { ++ range_start = range_end = i; ++ in_range = true; ++ } ++ } else if (in_range) { ++ in_range = false; ++ ++ if (!GREEDY_REALLOC(str, allocated, len + 2 + 2 * DECIMAL_STR_MAX(unsigned))) ++ return NULL; ++ ++ if (range_end > range_start) ++ r = sprintf(str + len, len > 0 ? ",%d-%d" : "%d-%d", range_start, range_end); ++ else ++ r = sprintf(str + len, len > 0 ? ",%d" : "%d", range_start); ++ assert_se(r > 0); ++ len += r; ++ } ++ ++ if (in_range) { ++ if (!GREEDY_REALLOC(str, allocated, len + 2 + 2 * DECIMAL_STR_MAX(int))) ++ return NULL; ++ ++ if (range_end > range_start) ++ r = sprintf(str + len, len > 0 ? ",%d-%d" : "%d-%d", range_start, range_end); ++ else ++ r = sprintf(str + len, len > 0 ? ",%d" : "%d", range_start); ++ assert_se(r > 0); ++ } ++ ++ return TAKE_PTR(str) ?: strdup(""); ++} ++ ++ + int cpu_set_realloc(CPUSet *cpu_set, unsigned ncpus) { + size_t need; + +diff --git a/src/basic/cpu-set-util.h b/src/basic/cpu-set-util.h +index 295028cb54..8519a9b6c8 100644 +--- a/src/basic/cpu-set-util.h ++++ b/src/basic/cpu-set-util.h +@@ -27,6 +27,7 @@ int cpu_set_add_all(CPUSet *a, const CPUSet *b); + + char* cpu_set_to_string(const CPUSet *a); + char *cpu_set_to_range_string(const CPUSet *a); ++char *cpu_set_to_range_string_kernel(const CPUSet *a); + int cpu_set_realloc(CPUSet *cpu_set, unsigned ncpus); + + int parse_cpu_set_full( +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index 3f7665b755..9e4c3c7dac 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -557,7 +557,7 @@ static void cgroup_apply_unified_cpuset(Unit *u, CPUSet cpus, const char *name) + _cleanup_free_ char *buf = NULL; + int r; + +- buf = cpu_set_to_range_string(&cpus); ++ buf = cpu_set_to_range_string_kernel(&cpus); + if (!buf) + return; + diff --git a/SOURCES/0350-core-transition-to-FINAL_SIGTERM-state-after-ExecSto.patch b/SOURCES/0350-core-transition-to-FINAL_SIGTERM-state-after-ExecSto.patch new file mode 100644 index 0000000..e9ae6e2 --- /dev/null +++ b/SOURCES/0350-core-transition-to-FINAL_SIGTERM-state-after-ExecSto.patch @@ -0,0 +1,159 @@ +From 6e732b6e44ad8eb3e94c47459c64f0bc6ef2fcb0 Mon Sep 17 00:00:00 2001 +From: Anita Zhang +Date: Sat, 25 Jan 2020 16:46:16 +0100 +Subject: [PATCH] core: transition to FINAL_SIGTERM state after ExecStopPost= + +Fixes #14566 + +(cherry picked from commit c1566ef0d22ed786b9ecf4c476e53b8a91e67578) + +Resolves: #1766479 +--- + src/core/service.c | 10 +++++ + test/TEST-47-ISSUE-14566/Makefile | 1 + + test/TEST-47-ISSUE-14566/repro.sh | 5 +++ + test/TEST-47-ISSUE-14566/test.sh | 55 +++++++++++++++++++++++++++ + test/TEST-47-ISSUE-14566/testsuite.sh | 23 +++++++++++ + 5 files changed, 94 insertions(+) + create mode 120000 test/TEST-47-ISSUE-14566/Makefile + create mode 100755 test/TEST-47-ISSUE-14566/repro.sh + create mode 100755 test/TEST-47-ISSUE-14566/test.sh + create mode 100755 test/TEST-47-ISSUE-14566/testsuite.sh + +diff --git a/src/core/service.c b/src/core/service.c +index b1ec52d220..5035dcacac 100644 +--- a/src/core/service.c ++++ b/src/core/service.c +@@ -3280,6 +3280,12 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { + break; + + case SERVICE_STOP_POST: ++ ++ if (control_pid_good(s) <= 0) ++ service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); ++ ++ break; ++ + case SERVICE_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + +@@ -3415,6 +3421,10 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { + break; + + case SERVICE_STOP_POST: ++ if (main_pid_good(s) <= 0) ++ service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); ++ break; ++ + case SERVICE_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + if (main_pid_good(s) <= 0) +diff --git a/test/TEST-47-ISSUE-14566/Makefile b/test/TEST-47-ISSUE-14566/Makefile +new file mode 120000 +index 0000000000..e9f93b1104 +--- /dev/null ++++ b/test/TEST-47-ISSUE-14566/Makefile +@@ -0,0 +1 @@ ++../TEST-01-BASIC/Makefile +\ No newline at end of file +diff --git a/test/TEST-47-ISSUE-14566/repro.sh b/test/TEST-47-ISSUE-14566/repro.sh +new file mode 100755 +index 0000000000..5217602257 +--- /dev/null ++++ b/test/TEST-47-ISSUE-14566/repro.sh +@@ -0,0 +1,5 @@ ++#!/bin/bash ++ ++sleep infinity & ++echo $! > /leakedtestpid ++wait $! +diff --git a/test/TEST-47-ISSUE-14566/test.sh b/test/TEST-47-ISSUE-14566/test.sh +new file mode 100755 +index 0000000000..0ce772164a +--- /dev/null ++++ b/test/TEST-47-ISSUE-14566/test.sh +@@ -0,0 +1,55 @@ ++#!/bin/bash ++set -e ++TEST_DESCRIPTION="Test that KillMode=mixed does not leave left over proccesses with ExecStopPost=" ++. $TEST_BASE_DIR/test-functions ++ ++test_setup() { ++ create_empty_image ++ mkdir -p $TESTDIR/root ++ mount ${LOOPDEV}p1 $TESTDIR/root ++ ++ ( ++ LOG_LEVEL=5 ++ eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) ++ ++ setup_basic_environment ++ ++ # mask some services that we do not want to run in these tests ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.service ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.socket ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-resolved.service ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-machined.service ++ ++ # setup the testsuite service ++ cat >$initdir/etc/systemd/system/testsuite.service < $initdir/etc/systemd/system/issue_14566_test.service << EOF ++[Unit] ++Description=Issue 14566 Repro ++ ++[Service] ++ExecStart=/repro.sh ++ExecStopPost=/bin/true ++KillMode=mixed ++EOF ++ ++ cp testsuite.sh $initdir/ ++ cp repro.sh $initdir/ ++ ++ setup_testsuite ++ ) ++ setup_nspawn_root ++ ++ ddebug "umount $TESTDIR/root" ++ umount $TESTDIR/root ++} ++ ++do_test "$@" +diff --git a/test/TEST-47-ISSUE-14566/testsuite.sh b/test/TEST-47-ISSUE-14566/testsuite.sh +new file mode 100755 +index 0000000000..d917cf52ff +--- /dev/null ++++ b/test/TEST-47-ISSUE-14566/testsuite.sh +@@ -0,0 +1,23 @@ ++#!/bin/bash ++set -ex ++set -o pipefail ++ ++systemd-analyze log-level debug ++systemd-analyze log-target console ++ ++systemctl start issue_14566_test ++systemctl status issue_14566_test ++ ++leaked_pid=$(cat /leakedtestpid) ++ ++systemctl stop issue_14566_test ++ ++# Leaked PID will still be around if we're buggy. ++# I personally prefer to see 42. ++ps -p "$leaked_pid" && exit 42 ++ ++systemd-analyze log-level info ++ ++echo OK > /testok ++ ++exit 0 diff --git a/SOURCES/0351-sd-journal-close-journal-files-that-were-deleted-by-.patch b/SOURCES/0351-sd-journal-close-journal-files-that-were-deleted-by-.patch new file mode 100644 index 0000000..ce44405 --- /dev/null +++ b/SOURCES/0351-sd-journal-close-journal-files-that-were-deleted-by-.patch @@ -0,0 +1,75 @@ +From 45275461f4a5293f15191ec5cb3bb80219ef2474 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= +Date: Tue, 4 Feb 2020 14:23:14 +0100 +Subject: [PATCH] sd-journal: close journal files that were deleted by journald + before we've setup inotify watch + +Fixes #14695 + +(cherry picked from commit 28ca867abdb20d0e4ac1901e2ed669cdb41ea3f6) + +Related: #1796128 +--- + src/journal/journal-file.c | 2 +- + src/journal/journal-file.h | 1 + + src/journal/sd-journal.c | 15 +++++++++++++++ + 3 files changed, 17 insertions(+), 1 deletion(-) + +diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c +index 8249b11b23..7ab3c47fc9 100644 +--- a/src/journal/journal-file.c ++++ b/src/journal/journal-file.c +@@ -597,7 +597,7 @@ static int journal_file_verify_header(JournalFile *f) { + return 0; + } + +-static int journal_file_fstat(JournalFile *f) { ++int journal_file_fstat(JournalFile *f) { + int r; + + assert(f); +diff --git a/src/journal/journal-file.h b/src/journal/journal-file.h +index 6a44fd39d2..6069b35234 100644 +--- a/src/journal/journal-file.h ++++ b/src/journal/journal-file.h +@@ -144,6 +144,7 @@ int journal_file_open( + int journal_file_set_offline(JournalFile *f, bool wait); + bool journal_file_is_offlining(JournalFile *f); + JournalFile* journal_file_close(JournalFile *j); ++int journal_file_fstat(JournalFile *f); + DEFINE_TRIVIAL_CLEANUP_FUNC(JournalFile*, journal_file_close); + + int journal_file_open_reliably( +diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c +index 323300baec..c06255e273 100644 +--- a/src/journal/sd-journal.c ++++ b/src/journal/sd-journal.c +@@ -2584,6 +2584,8 @@ _public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) { + assert_return(!journal_pid_changed(j), -ECHILD); + + if (j->inotify_fd < 0) { ++ Iterator i; ++ JournalFile *f; + + /* This is the first invocation, hence create the + * inotify watch */ +@@ -2591,6 +2593,19 @@ _public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) { + if (r < 0) + return r; + ++ /* Server might have done some vacuuming while we weren't watching. ++ Get rid of the deleted files now so they don't stay around indefinitely. */ ++ ORDERED_HASHMAP_FOREACH(f, j->files, i) { ++ r = journal_file_fstat(f); ++ if (r < 0) { ++ log_debug_errno(r,"Failed to fstat() journal file '%s' : %m", f->path); ++ continue; ++ } ++ ++ if (f->last_stat.st_nlink <= 0) ++ remove_file_real(j, f); ++ } ++ + /* The journal might have changed since the context + * object was created and we weren't watching before, + * hence don't wait for anything, and return diff --git a/SOURCES/0352-sd-journal-remove-the-dead-code-and-actually-fix-146.patch b/SOURCES/0352-sd-journal-remove-the-dead-code-and-actually-fix-146.patch new file mode 100644 index 0000000..40e4870 --- /dev/null +++ b/SOURCES/0352-sd-journal-remove-the-dead-code-and-actually-fix-146.patch @@ -0,0 +1,42 @@ +From 0f7ee0007b8267cc66b638a44da6ddd984ece412 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= +Date: Fri, 27 Mar 2020 17:01:59 +0100 +Subject: [PATCH] sd-journal: remove the dead code and actually fix #14695 + +journal_file_fstat() returns an error if we call it on already unlinked +journal file and hence we never reach remove_file_real() which is the +entire point. + +I must have made some mistake while testing the fix that got me thinking +the issue is gone while opposite was true. + +Fixes #14695 + +(cherry picked from commit 8581b9f9732d4c158bb5f773230a65ce77f2c292) + +Resolves: #1796128 +--- + src/journal/sd-journal.c | 7 +++---- + 1 file changed, 3 insertions(+), 4 deletions(-) + +diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c +index c06255e273..4c502978de 100644 +--- a/src/journal/sd-journal.c ++++ b/src/journal/sd-journal.c +@@ -2597,13 +2597,12 @@ _public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) { + Get rid of the deleted files now so they don't stay around indefinitely. */ + ORDERED_HASHMAP_FOREACH(f, j->files, i) { + r = journal_file_fstat(f); +- if (r < 0) { ++ if (r == -EIDRM) ++ remove_file_real(j, f); ++ else if (r < 0) { + log_debug_errno(r,"Failed to fstat() journal file '%s' : %m", f->path); + continue; + } +- +- if (f->last_stat.st_nlink <= 0) +- remove_file_real(j, f); + } + + /* The journal might have changed since the context diff --git a/SOURCES/0353-udev-downgrade-message-when-we-fail-to-set-inotify-w.patch b/SOURCES/0353-udev-downgrade-message-when-we-fail-to-set-inotify-w.patch new file mode 100644 index 0000000..0df7140 --- /dev/null +++ b/SOURCES/0353-udev-downgrade-message-when-we-fail-to-set-inotify-w.patch @@ -0,0 +1,43 @@ +From 38532765172a4e60624b9c24b8d081b34d9f7b86 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Tue, 13 Nov 2018 14:53:04 +0100 +Subject: [PATCH] udev: downgrade message when we fail to set inotify watch up + +My logs are full of: + +systemd-udevd[6586]: seq 13515 queued, 'add' 'block' +systemd-udevd[6586]: seq 13516 queued, 'change' 'block' +systemd-udevd[6586]: seq 13517 queued, 'change' 'block' +systemd-udevd[6586]: seq 13518 queued, 'remove' 'bdi' +systemd-udevd[6586]: seq 13519 queued, 'remove' 'block' +systemd-udevd[9865]: seq 13514 processed +systemd-udevd[9865]: seq 13515 running +systemd-udevd[9865]: GROUP 6 /usr/lib/udev/rules.d/50-udev-default.rules:59 +systemd-udevd[9865]: IMPORT builtin 'blkid' /usr/lib/udev/rules.d/60-persistent-storage.rules:95 +systemd-udevd[9865]: IMPORT builtin 'blkid' fails: No such file or directory +systemd-udevd[9865]: loop4: Failed to add device '/dev/loop4' to watch: No such file or directory +(the last line is at error level). +If we are too slow to set up a watch and the device is already gone by the time +we try, this is not an error. + +(cherry picked from commit 7fe0d0d5c0ad5aa3f069bb282868938d414d7ad1) + +Resolves: #1808051 +--- + src/udev/udev-watch.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/udev/udev-watch.c b/src/udev/udev-watch.c +index 7864f57aa5..9c82196add 100644 +--- a/src/udev/udev-watch.c ++++ b/src/udev/udev-watch.c +@@ -87,7 +87,8 @@ void udev_watch_begin(struct udev *udev, struct udev_device *dev) { + log_debug("adding watch on '%s'", udev_device_get_devnode(dev)); + wd = inotify_add_watch(inotify_fd, udev_device_get_devnode(dev), IN_CLOSE_WRITE); + if (wd < 0) { +- log_error_errno(errno, "inotify_add_watch(%d, %s, %o) failed: %m", ++ log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, ++ errno, "inotify_add_watch(%d, %s, %o) failed: %m", + inotify_fd, udev_device_get_devnode(dev), IN_CLOSE_WRITE); + return; + } diff --git a/SOURCES/0354-logind-check-PolicyKit-before-allowing-VT-switch.patch b/SOURCES/0354-logind-check-PolicyKit-before-allowing-VT-switch.patch new file mode 100644 index 0000000..6eacf48 --- /dev/null +++ b/SOURCES/0354-logind-check-PolicyKit-before-allowing-VT-switch.patch @@ -0,0 +1,195 @@ +From af20a66874296f71618819ebce9d4335b195728c Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Wed, 22 Jan 2020 12:04:38 +0100 +Subject: [PATCH] logind: check PolicyKit before allowing VT switch + +Let's lock this down a bit. Effectively nothing much changes, since the +default PK policy will allow users on the VT to change VT. Only users +with no local VT session won't be able to switch VTs. + +(cherry picked from commit 4acf0cfd2f92edb94ad48d04f1ce6c9ab4e19d55) + +Resolves: #1797679 +--- + src/login/logind-dbus.c | 16 +++++++ + src/login/logind-seat-dbus.c | 58 ++++++++++++++++++++++++- + src/login/logind-session-dbus.c | 15 +++++++ + src/login/org.freedesktop.login1.policy | 10 +++++ + 4 files changed, 98 insertions(+), 1 deletion(-) + +diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c +index dca7f4a30f..3f122fcbd9 100644 +--- a/src/login/logind-dbus.c ++++ b/src/login/logind-dbus.c +@@ -913,6 +913,8 @@ static int method_activate_session(sd_bus_message *message, void *userdata, sd_b + if (r < 0) + return r; + ++ /* PolicyKit is done by bus_session_method_activate() */ ++ + return bus_session_method_activate(message, session, error); + } + +@@ -944,6 +946,20 @@ static int method_activate_session_on_seat(sd_bus_message *message, void *userda + if (session->seat != seat) + return sd_bus_error_setf(error, BUS_ERROR_SESSION_NOT_ON_SEAT, "Session %s not on seat %s", session_name, seat_name); + ++ r = bus_verify_polkit_async( ++ message, ++ CAP_SYS_ADMIN, ++ "org.freedesktop.login1.chvt", ++ NULL, ++ false, ++ UID_INVALID, ++ &m->polkit_registry, ++ error); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ return 1; /* Will call us back */ ++ + r = session_activate(session); + if (r < 0) + return r; +diff --git a/src/login/logind-seat-dbus.c b/src/login/logind-seat-dbus.c +index c4d9b067c6..2e590a8f21 100644 +--- a/src/login/logind-seat-dbus.c ++++ b/src/login/logind-seat-dbus.c +@@ -174,6 +174,20 @@ static int method_activate_session(sd_bus_message *message, void *userdata, sd_b + if (session->seat != s) + return sd_bus_error_setf(error, BUS_ERROR_SESSION_NOT_ON_SEAT, "Session %s not on seat %s", name, s->id); + ++ r = bus_verify_polkit_async( ++ message, ++ CAP_SYS_ADMIN, ++ "org.freedesktop.login1.chvt", ++ NULL, ++ false, ++ UID_INVALID, ++ &s->manager->polkit_registry, ++ error); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ return 1; /* Will call us back */ ++ + r = session_activate(session); + if (r < 0) + return r; +@@ -194,7 +208,21 @@ static int method_switch_to(sd_bus_message *message, void *userdata, sd_bus_erro + return r; + + if (to <= 0) +- return -EINVAL; ++ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid virtual terminal"); ++ ++ r = bus_verify_polkit_async( ++ message, ++ CAP_SYS_ADMIN, ++ "org.freedesktop.login1.chvt", ++ NULL, ++ false, ++ UID_INVALID, ++ &s->manager->polkit_registry, ++ error); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ return 1; /* Will call us back */ + + r = seat_switch_to(s, to); + if (r < 0) +@@ -210,6 +238,20 @@ static int method_switch_to_next(sd_bus_message *message, void *userdata, sd_bus + assert(message); + assert(s); + ++ r = bus_verify_polkit_async( ++ message, ++ CAP_SYS_ADMIN, ++ "org.freedesktop.login1.chvt", ++ NULL, ++ false, ++ UID_INVALID, ++ &s->manager->polkit_registry, ++ error); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ return 1; /* Will call us back */ ++ + r = seat_switch_to_next(s); + if (r < 0) + return r; +@@ -224,6 +266,20 @@ static int method_switch_to_previous(sd_bus_message *message, void *userdata, sd + assert(message); + assert(s); + ++ r = bus_verify_polkit_async( ++ message, ++ CAP_SYS_ADMIN, ++ "org.freedesktop.login1.chvt", ++ NULL, ++ false, ++ UID_INVALID, ++ &s->manager->polkit_registry, ++ error); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ return 1; /* Will call us back */ ++ + r = seat_switch_to_previous(s); + if (r < 0) + return r; +diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c +index 25c4981dc0..88a2d33dc8 100644 +--- a/src/login/logind-session-dbus.c ++++ b/src/login/logind-session-dbus.c +@@ -13,6 +13,7 @@ + #include "logind.h" + #include "signal-util.h" + #include "strv.h" ++#include "user-util.h" + #include "util.h" + + static int property_get_user( +@@ -182,6 +183,20 @@ int bus_session_method_activate(sd_bus_message *message, void *userdata, sd_bus_ + assert(message); + assert(s); + ++ r = bus_verify_polkit_async( ++ message, ++ CAP_SYS_ADMIN, ++ "org.freedesktop.login1.chvt", ++ NULL, ++ false, ++ UID_INVALID, ++ &s->manager->polkit_registry, ++ error); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ return 1; /* Will call us back */ ++ + r = session_activate(s); + if (r < 0) + return r; +diff --git a/src/login/org.freedesktop.login1.policy b/src/login/org.freedesktop.login1.policy +index f1d1f956d3..83760e1580 100644 +--- a/src/login/org.freedesktop.login1.policy ++++ b/src/login/org.freedesktop.login1.policy +@@ -357,4 +357,14 @@ + + + ++ ++ Change Session ++ Authentication is required for changing the virtual terminal. ++ ++ auth_admin_keep ++ auth_admin_keep ++ yes ++ ++ ++ + diff --git a/SOURCES/0355-test-do-not-use-global-variable-to-pass-error.patch b/SOURCES/0355-test-do-not-use-global-variable-to-pass-error.patch new file mode 100644 index 0000000..89e07ba --- /dev/null +++ b/SOURCES/0355-test-do-not-use-global-variable-to-pass-error.patch @@ -0,0 +1,58 @@ +From 35e9e01b2879854a2adb8d571d0204990cad38d9 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Fri, 14 Sep 2018 13:25:02 +0900 +Subject: [PATCH] test: do not use global variable to pass error + +(cherry picked from commit 0013fac248a15be3acce84c17a65e3ae0377294b) + +Resolves: #1823767 +--- + test/TEST-21-SYSUSERS/test.sh | 1 + + test/test-functions | 8 +++++--- + 2 files changed, 6 insertions(+), 3 deletions(-) + +diff --git a/test/TEST-21-SYSUSERS/test.sh b/test/TEST-21-SYSUSERS/test.sh +index 73cbe10b69..b1049e720d 100755 +--- a/test/TEST-21-SYSUSERS/test.sh ++++ b/test/TEST-21-SYSUSERS/test.sh +@@ -15,6 +15,7 @@ prepare_testdir() { + for i in $1.initial-{passwd,group,shadow}; do + test -f $i && cp $i $TESTDIR/etc/${i#*.initial-} + done ++ return 0 + } + + preprocess() { +diff --git a/test/test-functions b/test/test-functions +index fe25a501da..da83891f46 100644 +--- a/test/test-functions ++++ b/test/test-functions +@@ -449,7 +449,7 @@ EOF + } + + check_result_nspawn() { +- ret=1 ++ local ret=1 + [[ -e $TESTDIR/$1/testok ]] && ret=0 + [[ -f $TESTDIR/$1/failed ]] && cp -a $TESTDIR/$1/failed $TESTDIR + cp -a $TESTDIR/$1/var/log/journal $TESTDIR +@@ -462,7 +462,7 @@ check_result_nspawn() { + + # can be overridden in specific test + check_result_qemu() { +- ret=1 ++ local ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 +@@ -1527,7 +1527,9 @@ do_test() { + case $1 in + --run) + echo "TEST RUN: $TEST_DESCRIPTION" +- if test_run; then ++ test_run ++ ret=$? ++ if (( $ret == 0 )); then + echo "TEST RUN: $TEST_DESCRIPTION [OK]" + else + echo "TEST RUN: $TEST_DESCRIPTION [FAILED]" diff --git a/SOURCES/0356-test-install-libraries-required-by-tests.patch b/SOURCES/0356-test-install-libraries-required-by-tests.patch new file mode 100644 index 0000000..e14e742 --- /dev/null +++ b/SOURCES/0356-test-install-libraries-required-by-tests.patch @@ -0,0 +1,25 @@ +From 98e4c0d39a83a325038d0b39715fc534c7613bef Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Wed, 12 Sep 2018 18:19:45 +0900 +Subject: [PATCH] test: install libraries required by tests + +(cherry picked from commit e3d3dada248c5f30e2978840ca1f0a03a4675b53) + +Resolves: #1823767 +--- + test/test-functions | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/test/test-functions b/test/test-functions +index da83891f46..f7ca3ad975 100644 +--- a/test/test-functions ++++ b/test/test-functions +@@ -420,7 +420,7 @@ get_ldpath() { + + install_missing_libraries() { + # install possible missing libraries +- for i in $initdir{,/usr}/{sbin,bin}/* $initdir{,/usr}/lib/systemd/*; do ++ for i in $initdir{,/usr}/{sbin,bin}/* $initdir{,/usr}/lib/systemd/{,tests/{,manual/,unsafe/}}*; do + LD_LIBRARY_PATH=$(get_ldpath $i) inst_libs $i + done + } diff --git a/SOURCES/0357-test-introduce-install_zoneinfo.patch b/SOURCES/0357-test-introduce-install_zoneinfo.patch new file mode 100644 index 0000000..ba58935 --- /dev/null +++ b/SOURCES/0357-test-introduce-install_zoneinfo.patch @@ -0,0 +1,32 @@ +From 73781e52f331f816924232f96c7bfc92ee763e70 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Wed, 12 Sep 2018 18:20:31 +0900 +Subject: [PATCH] test: introduce install_zoneinfo() + +But it is not called by default. + +(cherry picked from commit 7d10ec1cda8fed20c36b16d2387f529583645cda) + +Resolves: #1823767 +--- + test/test-functions | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/test/test-functions b/test/test-functions +index f7ca3ad975..4d76ed1f82 100644 +--- a/test/test-functions ++++ b/test/test-functions +@@ -628,6 +628,13 @@ install_keymaps() { + done + } + ++install_zoneinfo() { ++ for i in /usr/share/zoneinfo/{,*/,*/*/}*; do ++ [[ -f $i ]] || continue ++ inst $i ++ done ++} ++ + install_fonts() { + for i in \ + /usr/lib/kbd/consolefonts/eurlatgr* \ diff --git a/SOURCES/0358-test-replace-duplicated-Makefile-by-symbolic-link.patch b/SOURCES/0358-test-replace-duplicated-Makefile-by-symbolic-link.patch new file mode 100644 index 0000000..11032a9 --- /dev/null +++ b/SOURCES/0358-test-replace-duplicated-Makefile-by-symbolic-link.patch @@ -0,0 +1,151 @@ +From e39c4e6aee6c2ece5d9b51cc0e7a772016546f5a Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Thu, 13 Sep 2018 03:01:42 +0900 +Subject: [PATCH] test: replace duplicated Makefile by symbolic link + +(cherry picked from commit dd75c133d81f07c56c82ee4e7a80f391ffebd9ce) + +Resolves: #1823767 +--- + test/TEST-17-UDEV-WANTS/Makefile | 5 +---- + test/TEST-18-FAILUREACTION/Makefile | 5 +---- + test/TEST-19-DELEGATE/Makefile | 5 +---- + test/TEST-20-MAINPIDGAMES/Makefile | 5 +---- + test/TEST-21-SYSUSERS/Makefile | 5 +---- + test/TEST-22-TMPFILES/Makefile | 5 +---- + test/TEST-23-TYPE-EXEC/Makefile | 5 +---- + 7 files changed, 7 insertions(+), 28 deletions(-) + mode change 100644 => 120000 test/TEST-17-UDEV-WANTS/Makefile + mode change 100644 => 120000 test/TEST-18-FAILUREACTION/Makefile + mode change 100644 => 120000 test/TEST-19-DELEGATE/Makefile + mode change 100644 => 120000 test/TEST-20-MAINPIDGAMES/Makefile + mode change 100644 => 120000 test/TEST-21-SYSUSERS/Makefile + mode change 100644 => 120000 test/TEST-22-TMPFILES/Makefile + mode change 100644 => 120000 test/TEST-23-TYPE-EXEC/Makefile + +diff --git a/test/TEST-17-UDEV-WANTS/Makefile b/test/TEST-17-UDEV-WANTS/Makefile +deleted file mode 100644 +index 34d7cc6cdf..0000000000 +--- a/test/TEST-17-UDEV-WANTS/Makefile ++++ /dev/null +@@ -1,4 +0,0 @@ +-BUILD_DIR=$(shell ../../tools/find-build-dir.sh) +- +-all setup clean run: +- @basedir=../.. TEST_BASE_DIR=../ BUILD_DIR=$(BUILD_DIR) ./test.sh --$@ +diff --git a/test/TEST-17-UDEV-WANTS/Makefile b/test/TEST-17-UDEV-WANTS/Makefile +new file mode 120000 +index 0000000000..e9f93b1104 +--- /dev/null ++++ b/test/TEST-17-UDEV-WANTS/Makefile +@@ -0,0 +1 @@ ++../TEST-01-BASIC/Makefile +\ No newline at end of file +diff --git a/test/TEST-18-FAILUREACTION/Makefile b/test/TEST-18-FAILUREACTION/Makefile +deleted file mode 100644 +index 34d7cc6cdf..0000000000 +--- a/test/TEST-18-FAILUREACTION/Makefile ++++ /dev/null +@@ -1,4 +0,0 @@ +-BUILD_DIR=$(shell ../../tools/find-build-dir.sh) +- +-all setup clean run: +- @basedir=../.. TEST_BASE_DIR=../ BUILD_DIR=$(BUILD_DIR) ./test.sh --$@ +diff --git a/test/TEST-18-FAILUREACTION/Makefile b/test/TEST-18-FAILUREACTION/Makefile +new file mode 120000 +index 0000000000..e9f93b1104 +--- /dev/null ++++ b/test/TEST-18-FAILUREACTION/Makefile +@@ -0,0 +1 @@ ++../TEST-01-BASIC/Makefile +\ No newline at end of file +diff --git a/test/TEST-19-DELEGATE/Makefile b/test/TEST-19-DELEGATE/Makefile +deleted file mode 100644 +index 34d7cc6cdf..0000000000 +--- a/test/TEST-19-DELEGATE/Makefile ++++ /dev/null +@@ -1,4 +0,0 @@ +-BUILD_DIR=$(shell ../../tools/find-build-dir.sh) +- +-all setup clean run: +- @basedir=../.. TEST_BASE_DIR=../ BUILD_DIR=$(BUILD_DIR) ./test.sh --$@ +diff --git a/test/TEST-19-DELEGATE/Makefile b/test/TEST-19-DELEGATE/Makefile +new file mode 120000 +index 0000000000..e9f93b1104 +--- /dev/null ++++ b/test/TEST-19-DELEGATE/Makefile +@@ -0,0 +1 @@ ++../TEST-01-BASIC/Makefile +\ No newline at end of file +diff --git a/test/TEST-20-MAINPIDGAMES/Makefile b/test/TEST-20-MAINPIDGAMES/Makefile +deleted file mode 100644 +index 34d7cc6cdf..0000000000 +--- a/test/TEST-20-MAINPIDGAMES/Makefile ++++ /dev/null +@@ -1,4 +0,0 @@ +-BUILD_DIR=$(shell ../../tools/find-build-dir.sh) +- +-all setup clean run: +- @basedir=../.. TEST_BASE_DIR=../ BUILD_DIR=$(BUILD_DIR) ./test.sh --$@ +diff --git a/test/TEST-20-MAINPIDGAMES/Makefile b/test/TEST-20-MAINPIDGAMES/Makefile +new file mode 120000 +index 0000000000..e9f93b1104 +--- /dev/null ++++ b/test/TEST-20-MAINPIDGAMES/Makefile +@@ -0,0 +1 @@ ++../TEST-01-BASIC/Makefile +\ No newline at end of file +diff --git a/test/TEST-21-SYSUSERS/Makefile b/test/TEST-21-SYSUSERS/Makefile +deleted file mode 100644 +index 34d7cc6cdf..0000000000 +--- a/test/TEST-21-SYSUSERS/Makefile ++++ /dev/null +@@ -1,4 +0,0 @@ +-BUILD_DIR=$(shell ../../tools/find-build-dir.sh) +- +-all setup clean run: +- @basedir=../.. TEST_BASE_DIR=../ BUILD_DIR=$(BUILD_DIR) ./test.sh --$@ +diff --git a/test/TEST-21-SYSUSERS/Makefile b/test/TEST-21-SYSUSERS/Makefile +new file mode 120000 +index 0000000000..e9f93b1104 +--- /dev/null ++++ b/test/TEST-21-SYSUSERS/Makefile +@@ -0,0 +1 @@ ++../TEST-01-BASIC/Makefile +\ No newline at end of file +diff --git a/test/TEST-22-TMPFILES/Makefile b/test/TEST-22-TMPFILES/Makefile +deleted file mode 100644 +index 34d7cc6cdf..0000000000 +--- a/test/TEST-22-TMPFILES/Makefile ++++ /dev/null +@@ -1,4 +0,0 @@ +-BUILD_DIR=$(shell ../../tools/find-build-dir.sh) +- +-all setup clean run: +- @basedir=../.. TEST_BASE_DIR=../ BUILD_DIR=$(BUILD_DIR) ./test.sh --$@ +diff --git a/test/TEST-22-TMPFILES/Makefile b/test/TEST-22-TMPFILES/Makefile +new file mode 120000 +index 0000000000..e9f93b1104 +--- /dev/null ++++ b/test/TEST-22-TMPFILES/Makefile +@@ -0,0 +1 @@ ++../TEST-01-BASIC/Makefile +\ No newline at end of file +diff --git a/test/TEST-23-TYPE-EXEC/Makefile b/test/TEST-23-TYPE-EXEC/Makefile +deleted file mode 100644 +index 34d7cc6cdf..0000000000 +--- a/test/TEST-23-TYPE-EXEC/Makefile ++++ /dev/null +@@ -1,4 +0,0 @@ +-BUILD_DIR=$(shell ../../tools/find-build-dir.sh) +- +-all setup clean run: +- @basedir=../.. TEST_BASE_DIR=../ BUILD_DIR=$(BUILD_DIR) ./test.sh --$@ +diff --git a/test/TEST-23-TYPE-EXEC/Makefile b/test/TEST-23-TYPE-EXEC/Makefile +new file mode 120000 +index 0000000000..e9f93b1104 +--- /dev/null ++++ b/test/TEST-23-TYPE-EXEC/Makefile +@@ -0,0 +1 @@ ++../TEST-01-BASIC/Makefile +\ No newline at end of file diff --git a/SOURCES/0359-test-add-paths-of-keymaps-in-install_keymaps.patch b/SOURCES/0359-test-add-paths-of-keymaps-in-install_keymaps.patch new file mode 100644 index 0000000..3ef2cdf --- /dev/null +++ b/SOURCES/0359-test-add-paths-of-keymaps-in-install_keymaps.patch @@ -0,0 +1,34 @@ +From 1599be6d3625700ded28108c0747b000448fa5dc Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Wed, 19 Sep 2018 10:54:16 +0900 +Subject: [PATCH] test: add paths of keymaps in install_keymaps() + +It seems that the paths of directories storing keymaps are changed. + +(cherry picked from commit 83a7051ee1edbfe8cd2278477d23083beb385409) + +Resolves: #1823767 +--- + test/test-functions | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/test/test-functions b/test/test-functions +index 4d76ed1f82..546928c516 100644 +--- a/test/test-functions ++++ b/test/test-functions +@@ -619,10 +619,14 @@ install_pam() { + } + + install_keymaps() { ++ # The first three paths may be deprecated. ++ # It seems now the last two paths are used by many distributions. + for i in \ + /usr/lib/kbd/keymaps/include/* \ + /usr/lib/kbd/keymaps/i386/include/* \ +- /usr/lib/kbd/keymaps/i386/qwerty/us.*; do ++ /usr/lib/kbd/keymaps/i386/qwerty/us.* \ ++ /usr/lib/kbd/keymaps/legacy/include/* \ ++ /usr/lib/kbd/keymaps/legacy/i386/qwerty/us.*; do + [[ -f $i ]] || continue + inst $i + done diff --git a/SOURCES/0360-test-make-install_keymaps-optionally-install-more-ke.patch b/SOURCES/0360-test-make-install_keymaps-optionally-install-more-ke.patch new file mode 100644 index 0000000..52dff88 --- /dev/null +++ b/SOURCES/0360-test-make-install_keymaps-optionally-install-more-ke.patch @@ -0,0 +1,33 @@ +From 8dbd01f7018947fd15d00c25b1aa0ffc72278cb6 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Wed, 19 Sep 2018 10:54:28 +0900 +Subject: [PATCH] test: make install_keymaps() optionally install more keymaps + +(cherry picked from commit ad931fee506e1313e8a520ae0ecc1c8e275d9941) + +Resolves: #1823767 +--- + test/test-functions | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/test/test-functions b/test/test-functions +index 546928c516..0938e6e826 100644 +--- a/test/test-functions ++++ b/test/test-functions +@@ -630,6 +630,16 @@ install_keymaps() { + [[ -f $i ]] || continue + inst $i + done ++ ++ # When it takes any argument, then install more keymaps. ++ if [[ -n $1 ]]; then ++ for i in \ ++ /usr/lib/kbd/keymaps/i386/*/* \ ++ /usr/lib/kbd/keymaps/legacy/i386/*/*; do ++ [[ -f $i ]] || continue ++ inst $i ++ done ++ fi + } + + install_zoneinfo() { diff --git a/SOURCES/0361-test-fs-util-skip-some-tests-when-running-in-unprivi.patch b/SOURCES/0361-test-fs-util-skip-some-tests-when-running-in-unprivi.patch new file mode 100644 index 0000000..d461fa5 --- /dev/null +++ b/SOURCES/0361-test-fs-util-skip-some-tests-when-running-in-unprivi.patch @@ -0,0 +1,47 @@ +From 15ab55eca3d1f7feb86e55bdc147069f36d198eb Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Fri, 14 Sep 2018 15:51:04 +0900 +Subject: [PATCH] test-fs-util: skip some tests when running in unprivileged + container + +(cherry picked from commit 9590065f37be040996f1c2b9a246b9952fdc0c0b) + +Resolves: #1823767 +--- + src/test/test-fs-util.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c +index 7b7990bb70..e3338ea440 100644 +--- a/src/test/test-fs-util.c ++++ b/src/test/test-fs-util.c +@@ -17,6 +17,7 @@ + #include "strv.h" + #include "user-util.h" + #include "util.h" ++#include "virt.h" + + static void test_chase_symlinks(void) { + _cleanup_free_ char *result = NULL; +@@ -468,6 +469,7 @@ static void test_touch_file(void) { + struct stat st; + const char *a; + usec_t test_mtime; ++ int r; + + test_uid = geteuid() == 0 ? 65534 : getuid(); + test_gid = geteuid() == 0 ? 65534 : getgid(); +@@ -517,7 +519,12 @@ static void test_touch_file(void) { + + if (geteuid() == 0) { + a = strjoina(p, "/cdev"); +- assert_se(mknod(a, 0775 | S_IFCHR, makedev(0, 0)) >= 0); ++ r = mknod(a, 0775 | S_IFCHR, makedev(0, 0)); ++ if (r < 0 && errno == EPERM && detect_container() > 0) { ++ log_notice("Running in unprivileged container? Skipping remaining tests in %s", __func__); ++ return; ++ } ++ assert_se(r >= 0); + assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0); + assert_se(lstat(a, &st) >= 0); + assert_se(st.st_uid == test_uid); diff --git a/SOURCES/0362-test-process-util-skip-several-verifications-when-ru.patch b/SOURCES/0362-test-process-util-skip-several-verifications-when-ru.patch new file mode 100644 index 0000000..c3edf03 --- /dev/null +++ b/SOURCES/0362-test-process-util-skip-several-verifications-when-ru.patch @@ -0,0 +1,39 @@ +From b550cc33e762fa209b0740f1874f75ef780b8d90 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Thu, 20 Sep 2018 16:08:38 +0900 +Subject: [PATCH] test-process-util: skip several verifications when running in + unprivileged container + +(cherry picked from commit 767eab47501b06327a0e6030e5c54860a3fc427f) + +Resolves: #1823767 +--- + src/test/test-process-util.c | 13 +++++++++---- + 1 file changed, 9 insertions(+), 4 deletions(-) + +diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c +index fd4d17408d..26e3247993 100644 +--- a/src/test/test-process-util.c ++++ b/src/test/test-process-util.c +@@ -394,12 +394,17 @@ static void test_rename_process_now(const char *p, int ret) { + log_info("comm = <%s>", comm); + assert_se(strneq(comm, p, TASK_COMM_LEN-1)); + +- assert_se(get_process_cmdline(0, 0, false, &cmdline) >= 0); ++ r = get_process_cmdline(0, 0, false, &cmdline); ++ assert_se(r >= 0); + /* we cannot expect cmdline to be renamed properly without privileges */ + if (geteuid() == 0) { +- log_info("cmdline = <%s>", cmdline); +- assert_se(strneq(p, cmdline, STRLEN("test-process-util"))); +- assert_se(startswith(p, cmdline)); ++ if (r == 0 && detect_container() > 0) ++ log_info("cmdline = <%s> (not verified, Running in unprivileged container?)", cmdline); ++ else { ++ log_info("cmdline = <%s>", cmdline); ++ assert_se(strneq(p, cmdline, STRLEN("test-process-util"))); ++ assert_se(startswith(p, cmdline)); ++ } + } else + log_info("cmdline = <%s> (not verified)", cmdline); + } diff --git a/SOURCES/0363-test-execute-also-check-python3-is-installed-or-not.patch b/SOURCES/0363-test-execute-also-check-python3-is-installed-or-not.patch new file mode 100644 index 0000000..7bfe287 --- /dev/null +++ b/SOURCES/0363-test-execute-also-check-python3-is-installed-or-not.patch @@ -0,0 +1,59 @@ +From a3a3d861496b8c0d061c6ba21278d0326c50f37d Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Wed, 12 Sep 2018 18:18:33 +0900 +Subject: [PATCH] test-execute: also check python3 is installed or not + +(cherry picked from commit 738c74d7b163ea18e3c68115c3ed8ceed166cbf7) + +Resolves: #1823767 +--- + src/test/test-execute.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/src/test/test-execute.c b/src/test/test-execute.c +index 6c22995b1e..af64427bc7 100644 +--- a/src/test/test-execute.c ++++ b/src/test/test-execute.c +@@ -317,6 +317,8 @@ static void test_exec_temporaryfilesystem(Manager *m) { + + static void test_exec_systemcallfilter(Manager *m) { + #if HAVE_SECCOMP ++ int r; ++ + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; +@@ -326,6 +328,13 @@ static void test_exec_systemcallfilter(Manager *m) { + test(m, "exec-systemcallfilter-not-failing2.service", 0, CLD_EXITED); + test(m, "exec-systemcallfilter-failing.service", SIGSYS, CLD_KILLED); + test(m, "exec-systemcallfilter-failing2.service", SIGSYS, CLD_KILLED); ++ ++ r = find_binary("python3", NULL); ++ if (r < 0) { ++ log_notice_errno(r, "Skipping remaining tests in %s, could not find python3 binary: %m", __func__); ++ return; ++ } ++ + test(m, "exec-systemcallfilter-with-errno-name.service", errno_from_name("EILSEQ"), CLD_EXITED); + test(m, "exec-systemcallfilter-with-errno-number.service", 255, CLD_EXITED); + #endif +@@ -333,11 +342,19 @@ static void test_exec_systemcallfilter(Manager *m) { + + static void test_exec_systemcallerrornumber(Manager *m) { + #if HAVE_SECCOMP ++ int r; ++ + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + ++ r = find_binary("python3", NULL); ++ if (r < 0) { ++ log_notice_errno(r, "Skipping %s, could not find python3 binary: %m", __func__); ++ return; ++ } ++ + test(m, "exec-systemcallerrornumber-name.service", errno_from_name("EACCES"), CLD_EXITED); + test(m, "exec-systemcallerrornumber-number.service", 255, CLD_EXITED); + #endif diff --git a/SOURCES/0364-test-execute-skip-several-tests-when-running-in-cont.patch b/SOURCES/0364-test-execute-skip-several-tests-when-running-in-cont.patch new file mode 100644 index 0000000..472d8ff --- /dev/null +++ b/SOURCES/0364-test-execute-skip-several-tests-when-running-in-cont.patch @@ -0,0 +1,42 @@ +From fb66f2dbf1d228adc6f15edbbdf0ce53eb3be982 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Fri, 14 Sep 2018 15:47:42 +0900 +Subject: [PATCH] test-execute: skip several tests when running in container + +(cherry picked from commit 642d1a6d6e98204ade25816bcc429cb67df92a29) + +Resolves: #1823767 +--- + src/test/test-execute.c | 12 +++++++++++- + 1 file changed, 11 insertions(+), 1 deletion(-) + +diff --git a/src/test/test-execute.c b/src/test/test-execute.c +index af64427bc7..637ffe96bb 100644 +--- a/src/test/test-execute.c ++++ b/src/test/test-execute.c +@@ -616,14 +616,24 @@ static void test_exec_privatenetwork(Manager *m) { + + static void test_exec_oomscoreadjust(Manager *m) { + test(m, "exec-oomscoreadjust-positive.service", 0, CLD_EXITED); ++ ++ if (detect_container() > 0) { ++ log_notice("Testing in container, skipping remaining tests in %s", __func__); ++ return; ++ } + test(m, "exec-oomscoreadjust-negative.service", 0, CLD_EXITED); + } + + static void test_exec_ioschedulingclass(Manager *m) { + test(m, "exec-ioschedulingclass-none.service", 0, CLD_EXITED); + test(m, "exec-ioschedulingclass-idle.service", 0, CLD_EXITED); +- test(m, "exec-ioschedulingclass-realtime.service", 0, CLD_EXITED); + test(m, "exec-ioschedulingclass-best-effort.service", 0, CLD_EXITED); ++ ++ if (detect_container() > 0) { ++ log_notice("Testing in container, skipping remaining tests in %s", __func__); ++ return; ++ } ++ test(m, "exec-ioschedulingclass-realtime.service", 0, CLD_EXITED); + } + + static void test_exec_unsetenvironment(Manager *m) { diff --git a/SOURCES/0365-test-introduce-test_is_running_from_builddir.patch b/SOURCES/0365-test-introduce-test_is_running_from_builddir.patch new file mode 100644 index 0000000..c572849 --- /dev/null +++ b/SOURCES/0365-test-introduce-test_is_running_from_builddir.patch @@ -0,0 +1,70 @@ +From b69552ccc0e33f713ae3a2baf1b0173cf221507d Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 11 Sep 2018 09:17:22 +0900 +Subject: [PATCH] test: introduce test_is_running_from_builddir() + +(cherry picked from commit 8cb10a4f4dabc508a04f76ea55f23ef517881b61) + +Resolves: #1823767 +--- + src/shared/tests.c | 23 ++++++++++++++++++++--- + src/shared/tests.h | 1 + + 2 files changed, 21 insertions(+), 3 deletions(-) + +diff --git a/src/shared/tests.c b/src/shared/tests.c +index b10343650f..c77eb00924 100644 +--- a/src/shared/tests.c ++++ b/src/shared/tests.c +@@ -19,6 +19,24 @@ char* setup_fake_runtime_dir(void) { + return p; + } + ++bool test_is_running_from_builddir(char **exedir) { ++ _cleanup_free_ char *s = NULL; ++ bool r; ++ ++ /* Check if we're running from the builddir. Optionally, this returns ++ * the path to the directory where the binary is located. */ ++ ++ assert_se(readlink_and_make_absolute("/proc/self/exe", &s) >= 0); ++ r = path_startswith(s, ABS_BUILD_DIR); ++ ++ if (exedir) { ++ dirname(s); ++ *exedir = TAKE_PTR(s); ++ } ++ ++ return r; ++} ++ + const char* get_testdata_dir(const char *suffix) { + const char *env; + /* convenience: caller does not need to free result */ +@@ -35,14 +53,13 @@ const char* get_testdata_dir(const char *suffix) { + strncpy(testdir, env, sizeof(testdir) - 1); + } else { + _cleanup_free_ char *exedir = NULL; +- assert_se(readlink_and_make_absolute("/proc/self/exe", &exedir) >= 0); + + /* Check if we're running from the builddir. If so, use the compiled in path. */ +- if (path_startswith(exedir, ABS_BUILD_DIR)) ++ if (test_is_running_from_builddir(&exedir)) + assert_se(snprintf(testdir, sizeof(testdir), "%s/test", ABS_SRC_DIR) > 0); + else + /* Try relative path, according to the install-test layout */ +- assert_se(snprintf(testdir, sizeof(testdir), "%s/testdata", dirname(exedir)) > 0); ++ assert_se(snprintf(testdir, sizeof(testdir), "%s/testdata", exedir) > 0); + + /* test this without the suffix, as it may contain a glob */ + if (access(testdir, F_OK) < 0) { +diff --git a/src/shared/tests.h b/src/shared/tests.h +index cad21169f8..7f45c32d32 100644 +--- a/src/shared/tests.h ++++ b/src/shared/tests.h +@@ -2,5 +2,6 @@ + #pragma once + + char* setup_fake_runtime_dir(void); ++bool test_is_running_from_builddir(char **exedir); + const char* get_testdata_dir(const char *suffix); + void test_setup_logging(int level); diff --git a/SOURCES/0366-test-make-test-catalog-relocatable.patch b/SOURCES/0366-test-make-test-catalog-relocatable.patch new file mode 100644 index 0000000..7af676f --- /dev/null +++ b/SOURCES/0366-test-make-test-catalog-relocatable.patch @@ -0,0 +1,103 @@ +From ac476ab0ce970e4a269fee34a15e24f6b20962b7 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 11 Sep 2018 09:18:33 +0900 +Subject: [PATCH] test: make test-catalog relocatable + +Fixes #10045. + +(cherry picked from commit d9b6baa69968132d33e4ad8627c7fe0bd527c859) + +Resolves: #1823767 +--- + catalog/meson.build | 1 - + src/journal/test-catalog.c | 27 +++++++++++++++++++-------- + src/test/meson.build | 2 +- + 3 files changed, 20 insertions(+), 10 deletions(-) + +diff --git a/catalog/meson.build b/catalog/meson.build +index 1b13150894..3db8e390f2 100644 +--- a/catalog/meson.build ++++ b/catalog/meson.build +@@ -17,7 +17,6 @@ in_files = ''' + + support_url = get_option('support-url') + support_sed = 's~%SUPPORT_URL%~@0@~'.format(support_url) +-build_catalog_dir = meson.current_build_dir() + + foreach file : in_files + custom_target( +diff --git a/src/journal/test-catalog.c b/src/journal/test-catalog.c +index 8eae993780..0c4da29f31 100644 +--- a/src/journal/test-catalog.c ++++ b/src/journal/test-catalog.c +@@ -14,14 +14,13 @@ + #include "fileio.h" + #include "log.h" + #include "macro.h" ++#include "path-util.h" + #include "string-util.h" ++#include "strv.h" ++#include "tests.h" + #include "util.h" + +-static const char *catalog_dirs[] = { +- CATALOG_DIR, +- NULL, +-}; +- ++static char** catalog_dirs = NULL; + static const char *no_catalog_dirs[] = { + "/bin/hopefully/with/no/catalog", + NULL +@@ -167,8 +166,8 @@ static void test_catalog_update(const char *database) { + assert_se(r == 0); + + /* Make sure that we at least have some files loaded or the +- catalog_list below will fail. */ +- r = catalog_update(database, NULL, catalog_dirs); ++ * catalog_list below will fail. */ ++ r = catalog_update(database, NULL, (const char * const *) catalog_dirs); + assert_se(r == 0); + } + +@@ -202,14 +201,26 @@ static void test_catalog_file_lang(void) { + + int main(int argc, char *argv[]) { + _cleanup_(unlink_tempfilep) char database[] = "/tmp/test-catalog.XXXXXX"; +- _cleanup_free_ char *text = NULL; ++ _cleanup_free_ char *text = NULL, *catalog_dir = NULL; + int r; + + setlocale(LC_ALL, "de_DE.UTF-8"); + ++ log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + ++ /* If test-catalog is located at the build directory, then use catalogs in that. ++ * If it is not, e.g. installed by systemd-tests package, then use installed catalogs. */ ++ if (test_is_running_from_builddir(NULL)) { ++ assert_se(catalog_dir = path_join(NULL, ABS_BUILD_DIR, "catalog")); ++ catalog_dirs = STRV_MAKE(catalog_dir); ++ } else ++ catalog_dirs = STRV_MAKE(CATALOG_DIR); ++ ++ assert_se(access(catalog_dirs[0], F_OK) >= 0); ++ log_notice("Using catalog directory '%s'", catalog_dirs[0]); ++ + test_catalog_file_lang(); + + test_catalog_import_invalid(); +diff --git a/src/test/meson.build b/src/test/meson.build +index 4259421f98..ead000e30c 100644 +--- a/src/test/meson.build ++++ b/src/test/meson.build +@@ -766,7 +766,7 @@ tests += [ + [threads, + libxz, + liblz4], +- '', '', '-DCATALOG_DIR="@0@"'.format(build_catalog_dir)], ++ '', '', '-DCATALOG_DIR="@0@"'.format(catalogdir)], + + [['src/journal/test-compress.c'], + [libjournal_core, diff --git a/SOURCES/0367-test-parallelize-tasks-in-TEST-24-UNIT-TESTS.patch b/SOURCES/0367-test-parallelize-tasks-in-TEST-24-UNIT-TESTS.patch new file mode 100644 index 0000000..fff67d8 --- /dev/null +++ b/SOURCES/0367-test-parallelize-tasks-in-TEST-24-UNIT-TESTS.patch @@ -0,0 +1,137 @@ +From 7ba4a6f5a02f6142d0e28fd475fd008532fb1083 Mon Sep 17 00:00:00 2001 +From: Frantisek Sumsal +Date: Tue, 5 Mar 2019 13:50:28 +0100 +Subject: [PATCH] test: parallelize tasks in TEST-24-UNIT-TESTS + +(cherry picked from commit 2f2a0454efd07644a4e0ccb3f00f1db2d7043391) + +Related: #1823767 +--- + test/TEST-24-UNIT-TESTS/test.sh | 2 + + test/TEST-24-UNIT-TESTS/testsuite.sh | 97 +++++++++++++++++++++------- + 2 files changed, 77 insertions(+), 22 deletions(-) + +diff --git a/test/TEST-24-UNIT-TESTS/test.sh b/test/TEST-24-UNIT-TESTS/test.sh +index 014ee52277..fc8c89fe0a 100755 +--- a/test/TEST-24-UNIT-TESTS/test.sh ++++ b/test/TEST-24-UNIT-TESTS/test.sh +@@ -78,6 +78,8 @@ test_setup() { + setup_basic_environment + install_keymaps yes + install_zoneinfo ++ # Install nproc to determine # of CPUs for correct parallelization ++ inst_binary nproc + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service < /$NAME.log 2>&1 +- ret=$? +- if (( $ret && $ret != 77 )); then +- echo "$NAME failed with $ret" +- echo $NAME >> /failed-tests +- echo "--- $NAME begin ---" >> /failed +- cat /$NAME.log >> /failed +- echo "--- $NAME end ---" >> /failed +- elif (( $ret == 77 )); then +- echo "$NAME skipped" +- echo $NAME >> /skipped-tests +- echo "--- $NAME begin ---" >> /skipped +- cat /$NAME.log >> /skipped +- echo "--- $NAME end ---" >> /skipped ++NPROC=$(nproc) ++MAX_QUEUE_SIZE=${NPROC:-2} ++IFS=$'\n' TEST_LIST=($(ls /usr/lib/systemd/tests/test-*)) ++ ++# Check & report test results ++# Arguments: ++# $1: test path ++# $2: test exit code ++function report_result() { ++ if [[ $# -ne 2 ]]; then ++ echo >&2 "check_result: missing arguments" ++ exit 1 ++ fi ++ ++ local name="${1##*/}" ++ local ret=$2 ++ ++ if [[ $ret -ne 0 && $ret != 77 ]]; then ++ echo "$name failed with $ret" ++ echo "$name" >> /failed-tests ++ { ++ echo "--- $name begin ---" ++ cat "/$name.log" ++ echo "--- $name end ---" ++ } >> /failed ++ elif [[ $ret == 77 ]]; then ++ echo "$name skipped" ++ echo "$name" >> /skipped-tests ++ { ++ echo "--- $name begin ---" ++ cat "/$name.log" ++ echo "--- $name end ---" ++ } >> /skipped + else +- echo "$NAME OK" +- echo $NAME >> /testok ++ echo "$name OK" ++ echo "$name" >> /testok ++ fi ++ ++ systemd-cat echo "--- $name ---" ++ systemd-cat cat "/$name.log" ++} ++ ++# Associative array for running tasks, where running[test-path]=PID ++declare -A running=() ++for task in "${TEST_LIST[@]}"; do ++ # If there's MAX_QUEUE_SIZE running tasks, keep checking the running queue ++ # until one of the tasks finishes, so we can replace it. ++ while [[ ${#running[@]} -ge $MAX_QUEUE_SIZE ]]; do ++ for key in "${!running[@]}"; do ++ if ! kill -0 ${running[$key]} &>/dev/null; then ++ # Task has finished, report its result and drop it from the queue ++ wait ${running[$key]} ++ ec=$? ++ report_result "$key" $ec ++ unset running["$key"] ++ # Break from inner for loop and outer while loop to skip ++ # the sleep below when we find a free slot in the queue ++ break 2 ++ fi ++ done ++ ++ # Precisely* calculated constant to keep the spinlock from burning the CPU(s) ++ sleep 0.01 ++ done ++ ++ if [[ -x $task ]]; then ++ log_file="/${task##*/}.log" ++ $task &> "$log_file" & ++ running[$task]=$! + fi ++done + +- systemd-cat echo "--- $NAME ---" +- systemd-cat cat /$NAME.log ++# Wait for remaining running tasks ++for key in "${!running[@]}"; do ++ wait ${running[$key]} ++ ec=$? ++ report_result "$key" $ec ++ unset running["$key"] + done + + exit 0 diff --git a/SOURCES/0368-test-try-to-determine-QEMU_SMP-dynamically.patch b/SOURCES/0368-test-try-to-determine-QEMU_SMP-dynamically.patch new file mode 100644 index 0000000..a53b7b2 --- /dev/null +++ b/SOURCES/0368-test-try-to-determine-QEMU_SMP-dynamically.patch @@ -0,0 +1,41 @@ +From fe6e09aa0931112e7e3750801858c66129c7a3a8 Mon Sep 17 00:00:00 2001 +From: Frantisek Sumsal +Date: Tue, 5 Mar 2019 16:08:00 +0100 +Subject: [PATCH] test: try to determine QEMU_SMP dynamically + +If the QEMU_SMP value has not been explicitly set, try to determine it +from the number of online CPUs using the nproc utility. If this approach +fails, fall back to the default value QEMU_SMP=1. + +This change should significantly help when running integration tests +under QEMU on multicore systems. + +(cherry picked from commit 5bfb2a93a4a36bba0d24199553dcda6e560cbb75) + +Related: #1823767 +--- + test/test-functions | 11 ++++++++++- + 1 file changed, 10 insertions(+), 1 deletion(-) + +diff --git a/test/test-functions b/test/test-functions +index 0938e6e826..3f1c327f3c 100644 +--- a/test/test-functions ++++ b/test/test-functions +@@ -120,7 +120,16 @@ run_qemu() { + fi + fi + +- [ "$QEMU_SMP" ] || QEMU_SMP=1 ++ # If QEMU_SMP was not explicitly set, try to determine the value 'dynamically' ++ # i.e. use the number of online CPUs on the host machine. If the nproc utility ++ # is not installed or there's some other error when calling it, fall back ++ # to the original value (QEMU_SMP=1). ++ if ! [ "$QEMU_SMP" ]; then ++ if ! QEMU_SMP=$(nproc); then ++ dwarn "nproc utility is not installed, falling back to QEMU_SMP=1" ++ QEMU_SMP=1 ++ fi ++ fi + + find_qemu_bin || return 1 + diff --git a/SOURCES/0369-test-store-coredumps-in-journal.patch b/SOURCES/0369-test-store-coredumps-in-journal.patch new file mode 100644 index 0000000..ca9f771 --- /dev/null +++ b/SOURCES/0369-test-store-coredumps-in-journal.patch @@ -0,0 +1,31 @@ +From 8df2d39a562416e1218e3ff191f3f3af1f9d4844 Mon Sep 17 00:00:00 2001 +From: Frantisek Sumsal +Date: Tue, 13 Aug 2019 00:14:54 +0200 +Subject: [PATCH] test: store coredumps in journal + +To make debugging much easier, especially for crashes in tests under +QEMU, let's store the entire coredump bundle in the systemd journal, +which is usually kept around by various CIs. Right now, we usually end +up with a journal, but without the coredump itself, which is pretty +useless. + +(cherry picked from commit 215bffe1b8d7cb72fe9f72ed53682d52d5c2a9c5) + +Related: #1823767 +--- + test/test-functions | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/test/test-functions b/test/test-functions +index 3f1c327f3c..7c4230b078 100644 +--- a/test/test-functions ++++ b/test/test-functions +@@ -420,6 +420,8 @@ install_systemd() { + + # enable debug logging in PID1 + echo LogLevel=debug >> $initdir/etc/systemd/system.conf ++ # store coredumps in journal ++ echo Storage=journal >> $initdir/etc/systemd/coredump.conf + } + + get_ldpath() { diff --git a/SOURCES/0370-pid1-add-new-kernel-cmdline-arg-systemd.cpu_affinity.patch b/SOURCES/0370-pid1-add-new-kernel-cmdline-arg-systemd.cpu_affinity.patch new file mode 100644 index 0000000..340e056 --- /dev/null +++ b/SOURCES/0370-pid1-add-new-kernel-cmdline-arg-systemd.cpu_affinity.patch @@ -0,0 +1,62 @@ +From 82156850f6642a363aa2ff06677ad089a460104e Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 26 Nov 2019 09:46:00 +0100 +Subject: [PATCH] pid1: add new kernel cmdline arg systemd.cpu_affinity= + +Let's allow configuration of the CPU affinity via the kernel cmdline, +overriding CPUAffinity= in /etc/systemd/system.conf + +Prompted by: + +https://lists.freedesktop.org/archives/systemd-devel/2019-November/043754.html + +(cherry picked from commit 68d58f38693e586b5ce5785274f8e42a79625196) + +Resolves: #1812894 +--- + man/kernel-command-line.xml | 11 +++++++++++ + src/core/main.c | 9 +++++++++ + 2 files changed, 20 insertions(+) + +diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml +index 0545f9d84b..4d8cb4e50e 100644 +--- a/man/kernel-command-line.xml ++++ b/man/kernel-command-line.xml +@@ -366,6 +366,17 @@ + + + ++ ++ systemd.cpu_affinity= ++ ++ ++ Overrides the CPU affinity mask for the service manager and the default for all child ++ processes it forks. This takes precedence over CPUAffinity=, see ++ systemd-system.conf5 ++ for details. ++ ++ ++ + + modules_load= + rd.modules_load= +diff --git a/src/core/main.c b/src/core/main.c +index 45d09b1e11..9f238a8430 100644 +--- a/src/core/main.c ++++ b/src/core/main.c +@@ -472,6 +472,15 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat + if (arg_default_timeout_start_usec <= 0) + arg_default_timeout_start_usec = USEC_INFINITY; + ++ } else if (proc_cmdline_key_streq(key, "systemd.cpu_affinity")) { ++ ++ if (proc_cmdline_value_missing(key, value)) ++ return 0; ++ ++ r = parse_cpu_set(value, &arg_cpu_affinity); ++ if (r < 0) ++ log_warning_errno(r, "Failed to parse CPU affinity mask '%s', ignoring: %m", value); ++ + } else if (proc_cmdline_key_streq(key, "systemd.watchdog_device")) { + + if (proc_cmdline_value_missing(key, value)) diff --git a/SOURCES/0371-udev-rules-make-tape-changers-also-apprear-in-dev-ta.patch b/SOURCES/0371-udev-rules-make-tape-changers-also-apprear-in-dev-ta.patch new file mode 100644 index 0000000..e267fe5 --- /dev/null +++ b/SOURCES/0371-udev-rules-make-tape-changers-also-apprear-in-dev-ta.patch @@ -0,0 +1,57 @@ +From f60e89ea4c38c11a9d0c1e642c0a78faa32aca56 Mon Sep 17 00:00:00 2001 +From: Joerg Steffens +Date: Tue, 21 Nov 2017 12:21:49 +0100 +Subject: [PATCH] udev-rules: make tape-changers also apprear in + /dev/tape/by-path/ + +It is important to be able to access tape changer ("Medium Changers") by +persistant name. +While tape devices can be accessed via /dev/tape/by-id/ and +/dev/tape/by-path/, tape-changers could only be accessed by +/dev/tape/by-id/. +However, in some cases, especially when accessing Amazon Webservice +Storage Gateway VTLs (or accessing iSCSI VTLs in general?) this does not +work, as all tape devices and the tape changer have the same ENV{ID_SERIAL}. +The results is, that only the last device is available in +/dev/tape/by-id/, as the former devices have been overwritten. + +As this behavior is hard to change without breaking consistentcy, +this additional device in /dev/tape/by-path/ can be used to access the medium changes. +The tape devices can also be accessed by this path. + +The content of the directory will now look like: + + # SCSI tape device, rewind (unchanged) + /dev/tape/by-path/$env{ID_PATH} -> ../../st* + + # SCSI tape device, no-rewind (unchanged) + /dev/tape/by-path/$env{ID_PATH}-nst -> ../../nst* + + # SCSI tape changer device (newly added) + /dev/tape/by-path/$env{ID_PATH}-changer -> ../../sg* + +Tape devices and tape changer have different ID_PATHs. +SCSI tape changer get the suffix "-changer" +to make them better distinguishable from tape devices. + +(cherry picked from commit 7f8ddf96a25162f06bd94a684cf700c128d18142) + +Resolves: #1820112 +--- + rules/60-persistent-storage-tape.rules | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/rules/60-persistent-storage-tape.rules b/rules/60-persistent-storage-tape.rules +index b604864ee8..0575f308df 100644 +--- a/rules/60-persistent-storage-tape.rules ++++ b/rules/60-persistent-storage-tape.rules +@@ -9,6 +9,9 @@ ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_t + SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $devnode", \ + SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL}" + ++SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{builtin}="path_id", \ ++ SYMLINK+="tape/by-path/$env{ID_PATH}-changer" ++ + SUBSYSTEM!="scsi_tape", GOTO="persistent_storage_tape_end" + + KERNEL=="st*[0-9]|nst*[0-9]", ATTRS{ieee1394_id}=="?*", ENV{ID_SERIAL}="$attr{ieee1394_id}", ENV{ID_BUS}="ieee1394" diff --git a/SOURCES/0372-man-be-clearer-that-.timer-time-expressions-need-to-.patch b/SOURCES/0372-man-be-clearer-that-.timer-time-expressions-need-to-.patch new file mode 100644 index 0000000..8e72683 --- /dev/null +++ b/SOURCES/0372-man-be-clearer-that-.timer-time-expressions-need-to-.patch @@ -0,0 +1,75 @@ +From b8af9fd65b697e9bb77a32d1a6a70367814aaed5 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 1 Apr 2019 17:30:45 +0200 +Subject: [PATCH] man: be clearer that .timer time expressions need to be reset + to override them + +let's be clearer about the overriding concept for OnCalendar= settings. + +Prompted by this thread: + +https://lists.freedesktop.org/archives/systemd-devel/2019-March/042351.html +(cherry picked from commit 58031d99c6320855b86f4890baa9165597e3d841) + +Resolves: #1816908 +--- + man/systemd.timer.xml | 31 ++++++++++++++++++------------- + 1 file changed, 18 insertions(+), 13 deletions(-) + +diff --git a/man/systemd.timer.xml b/man/systemd.timer.xml +index 44b257c745..ebc1df89f1 100644 +--- a/man/systemd.timer.xml ++++ b/man/systemd.timer.xml +@@ -125,12 +125,12 @@ + to when the unit the timer is activating was last + deactivated. + +- Multiple directives may be combined of the same and of +- different types. For example, by combining +- OnBootSec= and +- OnUnitActiveSec=, it is possible to define +- a timer that elapses in regular intervals and activates a +- specific service each time. ++ Multiple directives may be combined of the same and of different types, in which case the timer ++ unit will trigger whenever any of the specified timer expressions elapse. For example, by combining ++ OnBootSec= and OnUnitActiveSec=, it is possible to define a ++ timer that elapses in regular intervals and activates a specific service each time. Moreover, both ++ monotonic time expressions and OnCalendar= calendar expressions may be combined in ++ the same timer unit. + + The arguments to the directives are time spans + configured in seconds. Example: "OnBootSec=50" means 50s after +@@ -145,13 +145,12 @@ + and the configured unit is started. This is not the case for + timers defined in the other directives. + +- These are monotonic timers, independent of wall-clock +- time and timezones. If the computer is temporarily suspended, +- the monotonic clock stops too. ++ These are monotonic timers, independent of wall-clock time and timezones. If the computer is ++ temporarily suspended, the monotonic clock pauses, too. + +- If the empty string is assigned to any of these options, +- the list of timers is reset, and all prior assignments will +- have no effect. ++ If the empty string is assigned to any of these options, the list of timers is reset (both ++ monotonic timers and OnCalendar= timers, see below), and all prior assignments ++ will have no effect. + + Note that timers do not necessarily expire at the + precise time configured with these settings, as they are +@@ -175,7 +174,13 @@ + the AccuracySec= setting + below. + +- May be specified more than once. ++ May be specified more than once, in which case the timer unit will trigger whenever any of the ++ specified expressions elapse. Moreover calendar timers and monotonic timers (see above) may be ++ combined within the same timer unit. ++ ++ If the empty string is assigned to any of these options, the list of timers is reset (both ++ OnCalendar= timers and monotonic timers, see above), and all prior assignments ++ will have no effect. + + + diff --git a/SOURCES/0373-Add-support-for-opening-files-for-appending.patch b/SOURCES/0373-Add-support-for-opening-files-for-appending.patch new file mode 100644 index 0000000..fbfa0e8 --- /dev/null +++ b/SOURCES/0373-Add-support-for-opening-files-for-appending.patch @@ -0,0 +1,312 @@ +From 2808e53f785e9ca7fdab286678e784b661b4c185 Mon Sep 17 00:00:00 2001 +From: Zsolt Dollenstein +Date: Tue, 3 Jul 2018 12:22:29 -0700 +Subject: [PATCH] Add support for opening files for appending + +Addresses part of #8983 + +(cherry picked from commit 566b7d23eb747e9c5a74e5647693077b52395fc5) + +Resolves: #1809175 +--- + man/systemd.exec.xml | 16 ++++++---- + src/core/dbus-execute.c | 30 ++++++++++++++----- + src/core/execute.c | 20 ++++++++++--- + src/core/execute.h | 1 + + src/core/load-fragment.c | 11 +++++++ + src/core/main.c | 4 +-- + src/test/test-execute.c | 10 +++++++ + test/meson.build | 2 ++ + .../exec-standardoutput-append.service | 13 ++++++++ + .../exec-standardoutput-file.service | 13 ++++++++ + 10 files changed, 101 insertions(+), 19 deletions(-) + create mode 100644 test/test-execute/exec-standardoutput-append.service + create mode 100644 test/test-execute/exec-standardoutput-file.service + +diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml +index bdaed68162..e2a5ede968 100644 +--- a/man/systemd.exec.xml ++++ b/man/systemd.exec.xml +@@ -1792,8 +1792,8 @@ SystemCallErrorNumber=EPERM + of , , , , + , , , + , , +- , or +- . ++ , , ++ or. + + duplicates the file descriptor of standard input for standard output. + +@@ -1824,11 +1824,17 @@ SystemCallErrorNumber=EPERM + + The option may be used to connect a specific file + system object to standard output. The semantics are similar to the same option of +- StandardInput=, see above. If standard input and output are directed to the same file path, +- it is opened only once, for reading as well as writing and duplicated. This is particular useful when the +- specified path refers to an AF_UNIX socket in the file system, as in that case only a ++ StandardInput=, see above. If path refers to a regular file ++ on the filesystem, it is opened (created if it doesn't exist yet) for writing at the beginning of the file, ++ but without truncating it. ++ If standard input and output are directed to the same file path, it is opened only once, for reading as well ++ as writing and duplicated. This is particularly useful when the specified path refers to an ++ AF_UNIX socket in the file system, as in that case only a + single stream connection is created for both input and output. + ++ is similar to above, but it opens the file in append mode. ++ + connects standard output to a socket acquired via socket activation. The + semantics are similar to the same option of StandardInput=, see above. + +diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c +index e7c0b893d1..f9527e56b2 100644 +--- a/src/core/dbus-execute.c ++++ b/src/core/dbus-execute.c +@@ -1772,7 +1772,10 @@ int bus_exec_context_set_transient_property( + + return 1; + +- } else if (STR_IN_SET(name, "StandardInputFile", "StandardOutputFile", "StandardErrorFile")) { ++ } else if (STR_IN_SET(name, ++ "StandardInputFile", ++ "StandardOutputFile", "StandardOutputFileToCreate", "StandardOutputFileToAppend", ++ "StandardErrorFile", "StandardErrorFileToCreate", "StandardErrorFileToAppend")) { + const char *s; + + r = sd_bus_message_read(message, "s", &s); +@@ -1796,23 +1799,34 @@ int bus_exec_context_set_transient_property( + c->std_input = EXEC_INPUT_FILE; + unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardInput=file:%s", s); + +- } else if (streq(name, "StandardOutputFile")) { ++ } else if (STR_IN_SET(name, "StandardOutputFile", "StandardOutputFileToAppend")) { + r = free_and_strdup(&c->stdio_file[STDOUT_FILENO], empty_to_null(s)); + if (r < 0) + return r; + +- c->std_output = EXEC_OUTPUT_FILE; +- unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardOutput=file:%s", s); +- ++ if (streq(name, "StandardOutputFile")) { ++ c->std_output = EXEC_OUTPUT_FILE; ++ unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardOutput=file:%s", s); ++ } else { ++ assert(streq(name, "StandardOutputFileToAppend")); ++ c->std_output = EXEC_OUTPUT_FILE_APPEND; ++ unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardOutput=append:%s", s); ++ } + } else { +- assert(streq(name, "StandardErrorFile")); ++ assert(STR_IN_SET(name, "StandardErrorFile", "StandardErrorFileToAppend")); + + r = free_and_strdup(&c->stdio_file[STDERR_FILENO], empty_to_null(s)); + if (r < 0) + return r; + +- c->std_error = EXEC_OUTPUT_FILE; +- unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardError=file:%s", s); ++ if (streq(name, "StandardErrorFile")) { ++ c->std_error = EXEC_OUTPUT_FILE; ++ unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardOutput=file:%s", s); ++ } else { ++ assert(streq(name, "StandardErrorFileToAppend")); ++ c->std_error = EXEC_OUTPUT_FILE_APPEND; ++ unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "StandardOutput=append:%s", s); ++ } + } + } + +diff --git a/src/core/execute.c b/src/core/execute.c +index f012023224..3c54ac1110 100644 +--- a/src/core/execute.c ++++ b/src/core/execute.c +@@ -89,6 +89,7 @@ + #include "strv.h" + #include "syslog-util.h" + #include "terminal-util.h" ++#include "umask-util.h" + #include "unit.h" + #include "user-util.h" + #include "util.h" +@@ -675,9 +676,10 @@ static int setup_output( + (void) fd_nonblock(named_iofds[fileno], false); + return dup2(named_iofds[fileno], fileno) < 0 ? -errno : fileno; + +- case EXEC_OUTPUT_FILE: { ++ case EXEC_OUTPUT_FILE: ++ case EXEC_OUTPUT_FILE_APPEND: { + bool rw; +- int fd; ++ int fd, flags; + + assert(context->stdio_file[fileno]); + +@@ -687,11 +689,16 @@ static int setup_output( + if (rw) + return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno; + +- fd = acquire_path(context->stdio_file[fileno], O_WRONLY, 0666 & ~context->umask); ++ flags = O_WRONLY; ++ if (o == EXEC_OUTPUT_FILE_APPEND) ++ flags |= O_APPEND; ++ ++ fd = acquire_path(context->stdio_file[fileno], flags, 0666 & ~context->umask); ++ + if (fd < 0) + return fd; + +- return move_fd(fd, fileno, false); ++ return move_fd(fd, fileno, 0); + } + + default: +@@ -4168,8 +4175,12 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { + fprintf(f, "%sStandardInputFile: %s\n", prefix, c->stdio_file[STDIN_FILENO]); + if (c->std_output == EXEC_OUTPUT_FILE) + fprintf(f, "%sStandardOutputFile: %s\n", prefix, c->stdio_file[STDOUT_FILENO]); ++ if (c->std_output == EXEC_OUTPUT_FILE_APPEND) ++ fprintf(f, "%sStandardOutputFileToAppend: %s\n", prefix, c->stdio_file[STDOUT_FILENO]); + if (c->std_error == EXEC_OUTPUT_FILE) + fprintf(f, "%sStandardErrorFile: %s\n", prefix, c->stdio_file[STDERR_FILENO]); ++ if (c->std_error == EXEC_OUTPUT_FILE_APPEND) ++ fprintf(f, "%sStandardErrorFileToAppend: %s\n", prefix, c->stdio_file[STDERR_FILENO]); + + if (c->tty_path) + fprintf(f, +@@ -5111,6 +5122,7 @@ static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = { + [EXEC_OUTPUT_SOCKET] = "socket", + [EXEC_OUTPUT_NAMED_FD] = "fd", + [EXEC_OUTPUT_FILE] = "file", ++ [EXEC_OUTPUT_FILE_APPEND] = "append", + }; + + DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput); +diff --git a/src/core/execute.h b/src/core/execute.h +index 2266355962..86c1cee84c 100644 +--- a/src/core/execute.h ++++ b/src/core/execute.h +@@ -57,6 +57,7 @@ typedef enum ExecOutput { + EXEC_OUTPUT_SOCKET, + EXEC_OUTPUT_NAMED_FD, + EXEC_OUTPUT_FILE, ++ EXEC_OUTPUT_FILE_APPEND, + _EXEC_OUTPUT_MAX, + _EXEC_OUTPUT_INVALID = -1 + } ExecOutput; +diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c +index 2082166afb..9b2724307d 100644 +--- a/src/core/load-fragment.c ++++ b/src/core/load-fragment.c +@@ -1016,6 +1016,17 @@ int config_parse_exec_output( + + eo = EXEC_OUTPUT_FILE; + ++ } else if ((n = startswith(rvalue, "append:"))) { ++ ++ r = unit_full_printf(u, n, &resolved); ++ if (r < 0) ++ return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s: %m", n); ++ ++ r = path_simplify_and_warn(resolved, PATH_CHECK_ABSOLUTE | PATH_CHECK_FATAL, unit, filename, line, lvalue); ++ if (r < 0) ++ return -ENOEXEC; ++ ++ eo = EXEC_OUTPUT_FILE_APPEND; + } else { + eo = exec_output_from_string(rvalue); + if (eo < 0) { +diff --git a/src/core/main.c b/src/core/main.c +index 9f238a8430..25536054b3 100644 +--- a/src/core/main.c ++++ b/src/core/main.c +@@ -620,8 +620,8 @@ static int config_parse_output_restricted( + return 0; + } + +- if (IN_SET(t, EXEC_OUTPUT_SOCKET, EXEC_OUTPUT_NAMED_FD, EXEC_OUTPUT_FILE)) { +- log_syntax(unit, LOG_ERR, filename, line, 0, "Standard output types socket, fd:, file: are not supported as defaults, ignoring: %s", rvalue); ++ if (IN_SET(t, EXEC_OUTPUT_SOCKET, EXEC_OUTPUT_NAMED_FD, EXEC_OUTPUT_FILE, EXEC_OUTPUT_FILE_APPEND)) { ++ log_syntax(unit, LOG_ERR, filename, line, 0, "Standard output types socket, fd:, file:, append: are not supported as defaults, ignoring: %s", rvalue); + return 0; + } + +diff --git a/src/test/test-execute.c b/src/test/test-execute.c +index 637ffe96bb..0f8dc883b1 100644 +--- a/src/test/test-execute.c ++++ b/src/test/test-execute.c +@@ -651,6 +651,14 @@ static void test_exec_standardinput(Manager *m) { + test(m, "exec-standardinput-file.service", 0, CLD_EXITED); + } + ++static void test_exec_standardoutput(Manager *m) { ++ test(m, "exec-standardoutput-file.service", 0, CLD_EXITED); ++} ++ ++static void test_exec_standardoutput_append(Manager *m) { ++ test(m, "exec-standardoutput-append.service", 0, CLD_EXITED); ++} ++ + static int run_tests(UnitFileScope scope, const test_function_t *tests) { + const test_function_t *test = NULL; + _cleanup_(manager_freep) Manager *m = NULL; +@@ -698,6 +706,8 @@ int main(int argc, char *argv[]) { + test_exec_restrictnamespaces, + test_exec_runtimedirectory, + test_exec_standardinput, ++ test_exec_standardoutput, ++ test_exec_standardoutput_append, + test_exec_supplementarygroups, + test_exec_systemcallerrornumber, + test_exec_systemcallfilter, +diff --git a/test/meson.build b/test/meson.build +index fb9f2cdb9b..4d1c51048c 100644 +--- a/test/meson.build ++++ b/test/meson.build +@@ -115,6 +115,8 @@ test_data_files = ''' + test-execute/exec-specifier@.service + test-execute/exec-standardinput-data.service + test-execute/exec-standardinput-file.service ++ test-execute/exec-standardoutput-file.service ++ test-execute/exec-standardoutput-append.service + test-execute/exec-supplementarygroups-multiple-groups-default-group-user.service + test-execute/exec-supplementarygroups-multiple-groups-withgid.service + test-execute/exec-supplementarygroups-multiple-groups-withuid.service +diff --git a/test/test-execute/exec-standardoutput-append.service b/test/test-execute/exec-standardoutput-append.service +new file mode 100644 +index 0000000000..8983bb056b +--- /dev/null ++++ b/test/test-execute/exec-standardoutput-append.service +@@ -0,0 +1,13 @@ ++[Unit] ++Description=Test for StandardOutput=append: ++ ++[Service] ++ExecStartPre=sh -c 'printf "hello\n" > /tmp/test-exec-standardoutput-output' ++ExecStartPre=sh -c 'printf "hello\nhello\n" > /tmp/test-exec-standardoutput-expected' ++StandardInput=data ++StandardInputText=hello ++StandardOutput=append:/tmp/test-exec-standardoutput-output ++StandardError=null ++ExecStart=cat ++ExecStart=cmp /tmp/test-exec-standardoutput-output /tmp/test-exec-standardoutput-expected ++Type=oneshot +diff --git a/test/test-execute/exec-standardoutput-file.service b/test/test-execute/exec-standardoutput-file.service +new file mode 100644 +index 0000000000..71e2604b94 +--- /dev/null ++++ b/test/test-execute/exec-standardoutput-file.service +@@ -0,0 +1,13 @@ ++[Unit] ++Description=Test for StandardOutput=file: ++ ++[Service] ++ExecStartPre=sh -c 'printf "nooo\nhello\n" > /tmp/test-exec-standardoutput-output' ++ExecStartPre=sh -c 'printf "hello\nello\n" > /tmp/test-exec-standardoutput-expected' ++StandardInput=data ++StandardInputText=hello ++StandardOutput=file:/tmp/test-exec-standardoutput-output ++StandardError=null ++ExecStart=cat ++ExecStart=cmp /tmp/test-exec-standardoutput-expected /tmp/test-exec-standardoutput-output ++Type=oneshot diff --git a/SOURCES/0374-nspawn-move-payload-to-sub-cgroup-first-then-sync-cg.patch b/SOURCES/0374-nspawn-move-payload-to-sub-cgroup-first-then-sync-cg.patch new file mode 100644 index 0000000..52971de --- /dev/null +++ b/SOURCES/0374-nspawn-move-payload-to-sub-cgroup-first-then-sync-cg.patch @@ -0,0 +1,36 @@ +From 8ee1465520ad49892a0a378626ef93abc03f4d4e Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 5 Mar 2019 18:57:53 +0100 +Subject: [PATCH] nspawn: move payload to sub-cgroup first, then sync cgroup + trees + +if we sync the legacy and unified trees before moving to the right +subcgroup then ultimately the cgroup paths in the hierarchies will be +out-of-sync... Hence, let's move the payload first, and sync then. + +Addresses: https://github.com/systemd/systemd/pull/9762#issuecomment-441187979 +(cherry picked from commit 27da7ef0d09e00eae821f3ef26e1a666fe7aa087) + +Resolves: #1837094 +--- + src/nspawn/nspawn.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c +index 08255b5724..8cb7591f0e 100644 +--- a/src/nspawn/nspawn.c ++++ b/src/nspawn/nspawn.c +@@ -3960,11 +3960,11 @@ static int run(int master, + } else if (arg_slice || arg_property) + log_notice("Machine and scope registration turned off, --slice= and --property= settings will have no effect."); + +- r = sync_cgroup(*pid, arg_unified_cgroup_hierarchy, arg_uid_shift); ++ r = create_subcgroup(*pid, arg_keep_unit, arg_unified_cgroup_hierarchy); + if (r < 0) + return r; + +- r = create_subcgroup(*pid, arg_keep_unit, arg_unified_cgroup_hierarchy); ++ r = sync_cgroup(*pid, arg_unified_cgroup_hierarchy, arg_uid_shift); + if (r < 0) + return r; + diff --git a/SOURCES/0375-nspawn-chown-the-legacy-hierarchy-when-it-s-used-in-.patch b/SOURCES/0375-nspawn-chown-the-legacy-hierarchy-when-it-s-used-in-.patch new file mode 100644 index 0000000..1914ad5 --- /dev/null +++ b/SOURCES/0375-nspawn-chown-the-legacy-hierarchy-when-it-s-used-in-.patch @@ -0,0 +1,31 @@ +From f4a34d97bd7e1564a51f590df591cb31a1a3f333 Mon Sep 17 00:00:00 2001 +From: Evgeny Vereshchagin +Date: Mon, 17 Sep 2018 07:12:38 +0000 +Subject: [PATCH] nspawn: chown() the legacy hierarchy when it's used in a + container + +This is a follow-up to 720f0a2f3c928cc9379501a52146be9fbb4d9be2. + +Closes https://github.com/systemd/systemd/issues/10026 +Closes https://github.com/systemd/systemd/issues/9563 + +(cherry picked from commit 89f180201cd8c0f3ce5cb6e8dd7e2b3cbcf71527) + +Resolves: 1837094 +--- + src/nspawn/nspawn-cgroup.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/nspawn/nspawn-cgroup.c b/src/nspawn/nspawn-cgroup.c +index d8a39a6959..a231622e29 100644 +--- a/src/nspawn/nspawn-cgroup.c ++++ b/src/nspawn/nspawn-cgroup.c +@@ -55,7 +55,7 @@ int chown_cgroup(pid_t pid, CGroupUnified unified_requested, uid_t uid_shift) { + if (r < 0) + return log_error_errno(r, "Failed to chown() cgroup %s: %m", fs); + +- if (unified_requested == CGROUP_UNIFIED_SYSTEMD) { ++ if (unified_requested == CGROUP_UNIFIED_SYSTEMD || (unified_requested == CGROUP_UNIFIED_NONE && cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) > 0)) { + _cleanup_free_ char *lfs = NULL; + /* Always propagate access rights from unified to legacy controller */ + diff --git a/SOURCES/0376-core-move-unit_status_emit_starting_stopping_reloadi.patch b/SOURCES/0376-core-move-unit_status_emit_starting_stopping_reloadi.patch new file mode 100644 index 0000000..2badda5 --- /dev/null +++ b/SOURCES/0376-core-move-unit_status_emit_starting_stopping_reloadi.patch @@ -0,0 +1,354 @@ +From 883dfbff6e3e9763d21f9d029a824c63e016cfd9 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 13 Nov 2018 19:57:43 +0100 +Subject: [PATCH] core: move unit_status_emit_starting_stopping_reloading() and + related calls to job.c +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This call is only used by job.c and very specific to job handling. +Moreover the very similar logic of job_emit_status_message() is already +in job.c. + +Hence, let's clean this up, and move both sets of functions to job.c, +and rename them a bit so that they express precisely what they do: + +1. unit_status_emit_starting_stopping_reloading() → + job_emit_begin_status_message() +2. job_emit_status_message() → job_emit_done_status_message() + +The first call is after all what we call when we begin with the +execution of a job, and the second call what we call when we are done +wiht it. + +Just some moving and renaming, not other changes, and hence no change in +behaviour. + +(cherry picked from commit 33a3fdd9781329379f74e11a7a2707816aad8c61) + +Related: #1737283 +--- + src/core/job.c | 119 +++++++++++++++++++++++++++++++++++++++++------- + src/core/unit.c | 86 ---------------------------------- + src/core/unit.h | 1 - + 3 files changed, 103 insertions(+), 103 deletions(-) + +diff --git a/src/core/job.c b/src/core/job.c +index 769ed6d603..561ea14682 100644 +--- a/src/core/job.c ++++ b/src/core/job.c +@@ -507,6 +507,93 @@ static void job_change_type(Job *j, JobType newtype) { + j->type = newtype; + } + ++_pure_ static const char* job_get_begin_status_message_format(Unit *u, JobType t) { ++ const char *format; ++ const UnitStatusMessageFormats *format_table; ++ ++ assert(u); ++ assert(IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)); ++ ++ if (t != JOB_RELOAD) { ++ format_table = &UNIT_VTABLE(u)->status_message_formats; ++ if (format_table) { ++ format = format_table->starting_stopping[t == JOB_STOP]; ++ if (format) ++ return format; ++ } ++ } ++ ++ /* Return generic strings */ ++ if (t == JOB_START) ++ return "Starting %s."; ++ else if (t == JOB_STOP) ++ return "Stopping %s."; ++ else ++ return "Reloading %s."; ++} ++ ++static void job_print_begin_status_message(Unit *u, JobType t) { ++ const char *format; ++ ++ assert(u); ++ ++ /* Reload status messages have traditionally not been printed to console. */ ++ if (!IN_SET(t, JOB_START, JOB_STOP)) ++ return; ++ ++ format = job_get_begin_status_message_format(u, t); ++ ++ DISABLE_WARNING_FORMAT_NONLITERAL; ++ unit_status_printf(u, "", format); ++ REENABLE_WARNING; ++} ++ ++static void job_log_begin_status_message(Unit *u, JobType t) { ++ const char *format, *mid; ++ char buf[LINE_MAX]; ++ ++ assert(u); ++ ++ if (!IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)) ++ return; ++ ++ if (log_on_console()) ++ return; ++ ++ /* We log status messages for all units and all operations. */ ++ ++ format = job_get_begin_status_message_format(u, t); ++ ++ DISABLE_WARNING_FORMAT_NONLITERAL; ++ (void) snprintf(buf, sizeof buf, format, unit_description(u)); ++ REENABLE_WARNING; ++ ++ mid = t == JOB_START ? "MESSAGE_ID=" SD_MESSAGE_UNIT_STARTING_STR : ++ t == JOB_STOP ? "MESSAGE_ID=" SD_MESSAGE_UNIT_STOPPING_STR : ++ "MESSAGE_ID=" SD_MESSAGE_UNIT_RELOADING_STR; ++ ++ /* Note that we deliberately use LOG_MESSAGE() instead of ++ * LOG_UNIT_MESSAGE() here, since this is supposed to mimic ++ * closely what is written to screen using the status output, ++ * which is supposed the highest level, friendliest output ++ * possible, which means we should avoid the low-level unit ++ * name. */ ++ log_struct(LOG_INFO, ++ LOG_MESSAGE("%s", buf), ++ LOG_UNIT_ID(u), ++ LOG_UNIT_INVOCATION_ID(u), ++ mid); ++} ++ ++static void job_emit_begin_status_message(Unit *u, JobType t) { ++ assert(u); ++ assert(t >= 0); ++ assert(t < _JOB_TYPE_MAX); ++ ++ job_log_begin_status_message(u, t); ++ job_print_begin_status_message(u, t); ++} ++ + static int job_perform_on_unit(Job **j) { + uint32_t id; + Manager *m; +@@ -552,7 +639,7 @@ static int job_perform_on_unit(Job **j) { + * actually did something. */ + *j = manager_get_job(m, id); + if (*j && r > 0) +- unit_status_emit_starting_stopping_reloading(u, t); ++ job_emit_begin_status_message(u, t); + + return r; + } +@@ -638,7 +725,7 @@ int job_run_and_invalidate(Job *j) { + return r; + } + +-_pure_ static const char *job_get_status_message_format(Unit *u, JobType t, JobResult result) { ++_pure_ static const char *job_get_done_status_message_format(Unit *u, JobType t, JobResult result) { + + static const char *const generic_finished_start_job[_JOB_RESULT_MAX] = { + [JOB_DONE] = "Started %s.", +@@ -699,7 +786,7 @@ _pure_ static const char *job_get_status_message_format(Unit *u, JobType t, JobR + + static const struct { + const char *color, *word; +-} job_print_status_messages [_JOB_RESULT_MAX] = { ++} job_print_done_status_messages[_JOB_RESULT_MAX] = { + [JOB_DONE] = { ANSI_OK_COLOR, " OK " }, + [JOB_TIMEOUT] = { ANSI_HIGHLIGHT_RED, " TIME " }, + [JOB_FAILED] = { ANSI_HIGHLIGHT_RED, "FAILED" }, +@@ -711,7 +798,7 @@ static const struct { + [JOB_ONCE] = { ANSI_HIGHLIGHT_RED, " ONCE " }, + }; + +-static void job_print_status_message(Unit *u, JobType t, JobResult result) { ++static void job_print_done_status_message(Unit *u, JobType t, JobResult result) { + const char *format; + const char *status; + +@@ -723,19 +810,19 @@ static void job_print_status_message(Unit *u, JobType t, JobResult result) { + if (t == JOB_RELOAD) + return; + +- if (!job_print_status_messages[result].word) ++ if (!job_print_done_status_messages[result].word) + return; + +- format = job_get_status_message_format(u, t, result); ++ format = job_get_done_status_message_format(u, t, result); + if (!format) + return; + + if (log_get_show_color()) +- status = strjoina(job_print_status_messages[result].color, +- job_print_status_messages[result].word, ++ status = strjoina(job_print_done_status_messages[result].color, ++ job_print_done_status_messages[result].word, + ANSI_NORMAL); + else +- status = job_print_status_messages[result].word; ++ status = job_print_done_status_messages[result].word; + + if (result != JOB_DONE) + manager_flip_auto_status(u->manager, true); +@@ -752,7 +839,7 @@ static void job_print_status_message(Unit *u, JobType t, JobResult result) { + } + } + +-static void job_log_status_message(Unit *u, JobType t, JobResult result) { ++static void job_log_done_status_message(Unit *u, uint32_t job_id, JobType t, JobResult result) { + const char *format, *mid; + char buf[LINE_MAX]; + static const int job_result_log_level[_JOB_RESULT_MAX] = { +@@ -775,10 +862,10 @@ static void job_log_status_message(Unit *u, JobType t, JobResult result) { + + /* Skip printing if output goes to the console, and job_print_status_message() + will actually print something to the console. */ +- if (log_on_console() && job_print_status_messages[result].word) ++ if (log_on_console() && job_print_done_status_messages[result].word) + return; + +- format = job_get_status_message_format(u, t, result); ++ format = job_get_done_status_message_format(u, t, result); + if (!format) + return; + +@@ -827,15 +914,15 @@ static void job_log_status_message(Unit *u, JobType t, JobResult result) { + mid); + } + +-static void job_emit_status_message(Unit *u, JobType t, JobResult result) { ++static void job_emit_done_status_message(Unit *u, uint32_t job_id, JobType t, JobResult result) { + assert(u); + + /* No message if the job did not actually do anything due to failed condition. */ + if (t == JOB_START && result == JOB_DONE && !u->condition_result) + return; + +- job_log_status_message(u, t, result); +- job_print_status_message(u, t, result); ++ job_log_done_status_message(u, job_id, t, result); ++ job_print_done_status_message(u, t, result); + } + + static void job_fail_dependencies(Unit *u, UnitDependency d) { +@@ -890,7 +977,7 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool alr + + /* If this job did nothing to respective unit we don't log the status message */ + if (!already) +- job_emit_status_message(u, t, result); ++ job_emit_done_status_message(u, j->id, t, result); + + /* Patch restart jobs so that they become normal start jobs */ + if (result == JOB_DONE && t == JOB_RESTART) { +diff --git a/src/core/unit.c b/src/core/unit.c +index 40f138d25c..f5e251123d 100644 +--- a/src/core/unit.c ++++ b/src/core/unit.c +@@ -1624,92 +1624,6 @@ void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg + REENABLE_WARNING; + } + +-_pure_ static const char* unit_get_status_message_format(Unit *u, JobType t) { +- const char *format; +- const UnitStatusMessageFormats *format_table; +- +- assert(u); +- assert(IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)); +- +- if (t != JOB_RELOAD) { +- format_table = &UNIT_VTABLE(u)->status_message_formats; +- if (format_table) { +- format = format_table->starting_stopping[t == JOB_STOP]; +- if (format) +- return format; +- } +- } +- +- /* Return generic strings */ +- if (t == JOB_START) +- return "Starting %s."; +- else if (t == JOB_STOP) +- return "Stopping %s."; +- else +- return "Reloading %s."; +-} +- +-static void unit_status_print_starting_stopping(Unit *u, JobType t) { +- const char *format; +- +- assert(u); +- +- /* Reload status messages have traditionally not been printed to console. */ +- if (!IN_SET(t, JOB_START, JOB_STOP)) +- return; +- +- format = unit_get_status_message_format(u, t); +- +- DISABLE_WARNING_FORMAT_NONLITERAL; +- unit_status_printf(u, "", format); +- REENABLE_WARNING; +-} +- +-static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { +- const char *format, *mid; +- char buf[LINE_MAX]; +- +- assert(u); +- +- if (!IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)) +- return; +- +- if (log_on_console()) +- return; +- +- /* We log status messages for all units and all operations. */ +- +- format = unit_get_status_message_format(u, t); +- +- DISABLE_WARNING_FORMAT_NONLITERAL; +- (void) snprintf(buf, sizeof buf, format, unit_description(u)); +- REENABLE_WARNING; +- +- mid = t == JOB_START ? "MESSAGE_ID=" SD_MESSAGE_UNIT_STARTING_STR : +- t == JOB_STOP ? "MESSAGE_ID=" SD_MESSAGE_UNIT_STOPPING_STR : +- "MESSAGE_ID=" SD_MESSAGE_UNIT_RELOADING_STR; +- +- /* Note that we deliberately use LOG_MESSAGE() instead of +- * LOG_UNIT_MESSAGE() here, since this is supposed to mimic +- * closely what is written to screen using the status output, +- * which is supposed the highest level, friendliest output +- * possible, which means we should avoid the low-level unit +- * name. */ +- log_struct(LOG_INFO, +- LOG_MESSAGE("%s", buf), +- LOG_UNIT_ID(u), +- LOG_UNIT_INVOCATION_ID(u), +- mid); +-} +- +-void unit_status_emit_starting_stopping_reloading(Unit *u, JobType t) { +- assert(u); +- assert(t >= 0); +- assert(t < _JOB_TYPE_MAX); +- +- unit_status_log_starting_stopping_reloading(u, t); +- unit_status_print_starting_stopping(u, t); +-} + + int unit_start_limit_test(Unit *u) { + assert(u); +diff --git a/src/core/unit.h b/src/core/unit.h +index 595ee88d43..4d9539790a 100644 +--- a/src/core/unit.h ++++ b/src/core/unit.h +@@ -699,7 +699,6 @@ int unit_coldplug(Unit *u); + void unit_catchup(Unit *u); + + void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) _printf_(3, 0); +-void unit_status_emit_starting_stopping_reloading(Unit *u, JobType t); + + bool unit_need_daemon_reload(Unit *u); + diff --git a/SOURCES/0377-job-when-a-job-was-skipped-due-to-a-failed-condition.patch b/SOURCES/0377-job-when-a-job-was-skipped-due-to-a-failed-condition.patch new file mode 100644 index 0000000..c7f1a68 --- /dev/null +++ b/SOURCES/0377-job-when-a-job-was-skipped-due-to-a-failed-condition.patch @@ -0,0 +1,63 @@ +From 9c543783dbe560f4dafa4c2f276e03a4bce0389e Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Wed, 14 Nov 2018 11:08:16 +0100 +Subject: [PATCH] job: when a job was skipped due to a failed condition, log + about it + +Previously we'd neither show console status output nor log output. Let's +fix that, and still log something. + +(cherry picked from commit 9a80f2f4533883d272e6a436512aa7e88cedc549) + +Related: #1737283 +--- + src/core/job.c | 22 ++++++++++++++++++---- + 1 file changed, 18 insertions(+), 4 deletions(-) + +diff --git a/src/core/job.c b/src/core/job.c +index 561ea14682..b9eee91cf3 100644 +--- a/src/core/job.c ++++ b/src/core/job.c +@@ -810,6 +810,10 @@ static void job_print_done_status_message(Unit *u, JobType t, JobResult result) + if (t == JOB_RELOAD) + return; + ++ /* No message if the job did not actually do anything due to failed condition. */ ++ if (t == JOB_START && result == JOB_DONE && !u->condition_result) ++ return; ++ + if (!job_print_done_status_messages[result].word) + return; + +@@ -865,6 +869,20 @@ static void job_log_done_status_message(Unit *u, uint32_t job_id, JobType t, Job + if (log_on_console() && job_print_done_status_messages[result].word) + return; + ++ /* Show condition check message if the job did not actually do anything due to failed condition. */ ++ if (t == JOB_START && result == JOB_DONE && !u->condition_result) { ++ log_struct(LOG_INFO, ++ "MESSAGE=Condition check resulted in %s being skipped.", unit_description(u), ++ "JOB_ID=%" PRIu32, job_id, ++ "JOB_TYPE=%s", job_type_to_string(t), ++ "JOB_RESULT=%s", job_result_to_string(result), ++ LOG_UNIT_ID(u), ++ LOG_UNIT_INVOCATION_ID(u), ++ "MESSAGE_ID=" SD_MESSAGE_UNIT_STARTED_STR); ++ ++ return; ++ } ++ + format = job_get_done_status_message_format(u, t, result); + if (!format) + return; +@@ -917,10 +935,6 @@ static void job_log_done_status_message(Unit *u, uint32_t job_id, JobType t, Job + static void job_emit_done_status_message(Unit *u, uint32_t job_id, JobType t, JobResult result) { + assert(u); + +- /* No message if the job did not actually do anything due to failed condition. */ +- if (t == JOB_START && result == JOB_DONE && !u->condition_result) +- return; +- + job_log_done_status_message(u, job_id, t, result); + job_print_done_status_message(u, t, result); + } diff --git a/SOURCES/0378-core-split-out-all-logic-that-updates-a-Job-on-a-uni.patch b/SOURCES/0378-core-split-out-all-logic-that-updates-a-Job-on-a-uni.patch new file mode 100644 index 0000000..eb29a28 --- /dev/null +++ b/SOURCES/0378-core-split-out-all-logic-that-updates-a-Job-on-a-uni.patch @@ -0,0 +1,169 @@ +From 81c3f90d41c973a18e157c1106926711815adc0e Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 10 Dec 2018 20:56:57 +0100 +Subject: [PATCH] core: split out all logic that updates a Job on a unit's + unit_notify() invocation + +Just some refactoring, no change in behaviour. + +(cherry picked from commit 16c74914d233ec93012d77e5f93cf90e42939669) + +Related: #1737283 +--- + src/core/unit.c | 136 +++++++++++++++++++++++++----------------------- + 1 file changed, 71 insertions(+), 65 deletions(-) + +diff --git a/src/core/unit.c b/src/core/unit.c +index f5e251123d..a4865c1da5 100644 +--- a/src/core/unit.c ++++ b/src/core/unit.c +@@ -2225,6 +2225,73 @@ static void unit_update_on_console(Unit *u) { + manager_unref_console(u->manager); + } + ++static bool unit_process_job(Job *j, UnitActiveState ns, UnitNotifyFlags flags) { ++ bool unexpected = false; ++ ++ assert(j); ++ ++ if (j->state == JOB_WAITING) ++ ++ /* So we reached a different state for this job. Let's see if we can run it now if it failed previously ++ * due to EAGAIN. */ ++ job_add_to_run_queue(j); ++ ++ /* Let's check whether the unit's new state constitutes a finished job, or maybe contradicts a running job and ++ * hence needs to invalidate jobs. */ ++ ++ switch (j->type) { ++ ++ case JOB_START: ++ case JOB_VERIFY_ACTIVE: ++ ++ if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) ++ job_finish_and_invalidate(j, JOB_DONE, true, false); ++ else if (j->state == JOB_RUNNING && ns != UNIT_ACTIVATING) { ++ unexpected = true; ++ ++ if (UNIT_IS_INACTIVE_OR_FAILED(ns)) ++ job_finish_and_invalidate(j, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true, false); ++ } ++ ++ break; ++ ++ case JOB_RELOAD: ++ case JOB_RELOAD_OR_START: ++ case JOB_TRY_RELOAD: ++ ++ if (j->state == JOB_RUNNING) { ++ if (ns == UNIT_ACTIVE) ++ job_finish_and_invalidate(j, (flags & UNIT_NOTIFY_RELOAD_FAILURE) ? JOB_FAILED : JOB_DONE, true, false); ++ else if (!IN_SET(ns, UNIT_ACTIVATING, UNIT_RELOADING)) { ++ unexpected = true; ++ ++ if (UNIT_IS_INACTIVE_OR_FAILED(ns)) ++ job_finish_and_invalidate(j, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true, false); ++ } ++ } ++ ++ break; ++ ++ case JOB_STOP: ++ case JOB_RESTART: ++ case JOB_TRY_RESTART: ++ ++ if (UNIT_IS_INACTIVE_OR_FAILED(ns)) ++ job_finish_and_invalidate(j, JOB_DONE, true, false); ++ else if (j->state == JOB_RUNNING && ns != UNIT_DEACTIVATING) { ++ unexpected = true; ++ job_finish_and_invalidate(j, JOB_FAILED, true, false); ++ } ++ ++ break; ++ ++ default: ++ assert_not_reached("Job type unknown"); ++ } ++ ++ return unexpected; ++} ++ + void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlags flags) { + bool unexpected; + Manager *m; +@@ -2265,71 +2332,10 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag + + unit_update_on_console(u); + +- if (u->job) { +- unexpected = false; +- +- if (u->job->state == JOB_WAITING) +- +- /* So we reached a different state for this +- * job. Let's see if we can run it now if it +- * failed previously due to EAGAIN. */ +- job_add_to_run_queue(u->job); +- +- /* Let's check whether this state change constitutes a +- * finished job, or maybe contradicts a running job and +- * hence needs to invalidate jobs. */ +- +- switch (u->job->type) { +- +- case JOB_START: +- case JOB_VERIFY_ACTIVE: +- +- if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) +- job_finish_and_invalidate(u->job, JOB_DONE, true, false); +- else if (u->job->state == JOB_RUNNING && ns != UNIT_ACTIVATING) { +- unexpected = true; +- +- if (UNIT_IS_INACTIVE_OR_FAILED(ns)) +- job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true, false); +- } +- +- break; +- +- case JOB_RELOAD: +- case JOB_RELOAD_OR_START: +- case JOB_TRY_RELOAD: +- +- if (u->job->state == JOB_RUNNING) { +- if (ns == UNIT_ACTIVE) +- job_finish_and_invalidate(u->job, (flags & UNIT_NOTIFY_RELOAD_FAILURE) ? JOB_FAILED : JOB_DONE, true, false); +- else if (!IN_SET(ns, UNIT_ACTIVATING, UNIT_RELOADING)) { +- unexpected = true; +- +- if (UNIT_IS_INACTIVE_OR_FAILED(ns)) +- job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true, false); +- } +- } +- +- break; +- +- case JOB_STOP: +- case JOB_RESTART: +- case JOB_TRY_RESTART: +- +- if (UNIT_IS_INACTIVE_OR_FAILED(ns)) +- job_finish_and_invalidate(u->job, JOB_DONE, true, false); +- else if (u->job->state == JOB_RUNNING && ns != UNIT_DEACTIVATING) { +- unexpected = true; +- job_finish_and_invalidate(u->job, JOB_FAILED, true, false); +- } +- +- break; +- +- default: +- assert_not_reached("Job type unknown"); +- } +- +- } else ++ /* Let's propagate state changes to the job */ ++ if (u->job) ++ unexpected = unit_process_job(u->job, ns, flags); ++ else + unexpected = true; + + if (!MANAGER_IS_RELOADING(m)) { diff --git a/SOURCES/0379-core-make-log-messages-about-units-entering-a-failed.patch b/SOURCES/0379-core-make-log-messages-about-units-entering-a-failed.patch new file mode 100644 index 0000000..ff88dd4 --- /dev/null +++ b/SOURCES/0379-core-make-log-messages-about-units-entering-a-failed.patch @@ -0,0 +1,200 @@ +From d5245b46716cf53ce4d95966ea99499cf7aa209a Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 13 Nov 2018 21:25:22 +0100 +Subject: [PATCH] core: make log messages about units entering a 'failed' state + recognizable + +Let's make this recognizable, and carry result information in a +structure fashion. + +(cherry picked from commit 7c047d7443347c109daf67023a01c118b5f361eb) + +Related: #1737283 +--- + catalog/systemd.catalog.in | 7 +++++++ + src/core/automount.c | 2 +- + src/core/mount.c | 2 +- + src/core/path.c | 2 +- + src/core/scope.c | 2 +- + src/core/service.c | 2 +- + src/core/socket.c | 2 +- + src/core/swap.c | 2 +- + src/core/timer.c | 2 +- + src/core/unit.c | 12 ++++++++++++ + src/core/unit.h | 2 ++ + src/systemd/sd-messages.h | 4 ++++ + 12 files changed, 33 insertions(+), 8 deletions(-) + +diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in +index 49a45890f6..54a0f46921 100644 +--- a/catalog/systemd.catalog.in ++++ b/catalog/systemd.catalog.in +@@ -344,6 +344,13 @@ Support: %SUPPORT_URL% + + The unit @UNIT@ completed and consumed the indicated resources. + ++-- d9b373ed55a64feb8242e02dbe79a49c ++Subject: Unit failed ++Defined-By: systemd ++Support: %SUPPORT_URL% ++ ++The unit @UNIT@ has entered the 'failed' state with result '@UNIT_RESULT@'. ++ + -- 50876a9db00f4c40bde1a2ad381c3a1b + Subject: The system is configured in a way that might cause problems + Defined-By: systemd +diff --git a/src/core/automount.c b/src/core/automount.c +index 1b96a52c00..c78562c549 100644 +--- a/src/core/automount.c ++++ b/src/core/automount.c +@@ -315,7 +315,7 @@ static void automount_enter_dead(Automount *a, AutomountResult f) { + a->result = f; + + if (a->result != AUTOMOUNT_SUCCESS) +- log_unit_warning(UNIT(a), "Failed with result '%s'.", automount_result_to_string(a->result)); ++ unit_log_failure(UNIT(a), automount_result_to_string(a->result)); + + automount_set_state(a, a->result != AUTOMOUNT_SUCCESS ? AUTOMOUNT_FAILED : AUTOMOUNT_DEAD); + } +diff --git a/src/core/mount.c b/src/core/mount.c +index 5878814b1b..3cd0e479e9 100644 +--- a/src/core/mount.c ++++ b/src/core/mount.c +@@ -797,7 +797,7 @@ static void mount_enter_dead(Mount *m, MountResult f) { + m->result = f; + + if (m->result != MOUNT_SUCCESS) +- log_unit_warning(UNIT(m), "Failed with result '%s'.", mount_result_to_string(m->result)); ++ unit_log_failure(UNIT(m), mount_result_to_string(m->result)); + + mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD); + +diff --git a/src/core/path.c b/src/core/path.c +index 5ef178a46b..f8b69e7804 100644 +--- a/src/core/path.c ++++ b/src/core/path.c +@@ -449,7 +449,7 @@ static void path_enter_dead(Path *p, PathResult f) { + p->result = f; + + if (p->result != PATH_SUCCESS) +- log_unit_warning(UNIT(p), "Failed with result '%s'.", path_result_to_string(p->result)); ++ unit_log_failure(UNIT(p), path_result_to_string(p->result)); + + path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD); + } +diff --git a/src/core/scope.c b/src/core/scope.c +index 751556fecf..79ecfd992f 100644 +--- a/src/core/scope.c ++++ b/src/core/scope.c +@@ -240,7 +240,7 @@ static void scope_enter_dead(Scope *s, ScopeResult f) { + s->result = f; + + if (s->result != SCOPE_SUCCESS) +- log_unit_warning(UNIT(s), "Failed with result '%s'.", scope_result_to_string(s->result)); ++ unit_log_failure(UNIT(s), scope_result_to_string(s->result)); + + scope_set_state(s, s->result != SCOPE_SUCCESS ? SCOPE_FAILED : SCOPE_DEAD); + } +diff --git a/src/core/service.c b/src/core/service.c +index 5035dcacac..efceb0614c 100644 +--- a/src/core/service.c ++++ b/src/core/service.c +@@ -1680,7 +1680,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) + s->result = f; + + if (s->result != SERVICE_SUCCESS) +- log_unit_warning(UNIT(s), "Failed with result '%s'.", service_result_to_string(s->result)); ++ unit_log_failure(UNIT(s), service_result_to_string(s->result)); + + if (allow_restart && service_shall_restart(s)) + s->will_auto_restart = true; +diff --git a/src/core/socket.c b/src/core/socket.c +index b034549634..160f11765c 100644 +--- a/src/core/socket.c ++++ b/src/core/socket.c +@@ -1991,7 +1991,7 @@ static void socket_enter_dead(Socket *s, SocketResult f) { + s->result = f; + + if (s->result != SOCKET_SUCCESS) +- log_unit_warning(UNIT(s), "Failed with result '%s'.", socket_result_to_string(s->result)); ++ unit_log_failure(UNIT(s), socket_result_to_string(s->result)); + + socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD); + +diff --git a/src/core/swap.c b/src/core/swap.c +index 66a62d8a37..b5926d8644 100644 +--- a/src/core/swap.c ++++ b/src/core/swap.c +@@ -657,7 +657,7 @@ static void swap_enter_dead(Swap *s, SwapResult f) { + s->result = f; + + if (s->result != SWAP_SUCCESS) +- log_unit_warning(UNIT(s), "Failed with result '%s'.", swap_result_to_string(s->result)); ++ unit_log_failure(UNIT(s), swap_result_to_string(s->result)); + + swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD); + +diff --git a/src/core/timer.c b/src/core/timer.c +index db202971d3..6ac310cbe0 100644 +--- a/src/core/timer.c ++++ b/src/core/timer.c +@@ -288,7 +288,7 @@ static void timer_enter_dead(Timer *t, TimerResult f) { + t->result = f; + + if (t->result != TIMER_SUCCESS) +- log_unit_warning(UNIT(t), "Failed with result '%s'.", timer_result_to_string(t->result)); ++ unit_log_failure(UNIT(t), timer_result_to_string(t->result)); + + timer_set_state(t, t->result != TIMER_SUCCESS ? TIMER_FAILED : TIMER_DEAD); + } +diff --git a/src/core/unit.c b/src/core/unit.c +index a4865c1da5..f55bddc00f 100644 +--- a/src/core/unit.c ++++ b/src/core/unit.c +@@ -5462,6 +5462,18 @@ int unit_pid_attachable(Unit *u, pid_t pid, sd_bus_error *error) { + return 0; + } + ++void unit_log_failure(Unit *u, const char *result) { ++ assert(u); ++ assert(result); ++ ++ log_struct(LOG_WARNING, ++ "MESSAGE_ID=" SD_MESSAGE_UNIT_FAILURE_RESULT_STR, ++ LOG_UNIT_ID(u), ++ LOG_UNIT_INVOCATION_ID(u), ++ LOG_UNIT_MESSAGE(u, "Failed with result '%s'.", result), ++ "UNIT_RESULT=%s", result); ++} ++ + static const char* const collect_mode_table[_COLLECT_MODE_MAX] = { + [COLLECT_INACTIVE] = "inactive", + [COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed", +diff --git a/src/core/unit.h b/src/core/unit.h +index 4d9539790a..9d226fb3e0 100644 +--- a/src/core/unit.h ++++ b/src/core/unit.h +@@ -804,6 +804,8 @@ const char *unit_label_path(Unit *u); + + int unit_pid_attachable(Unit *unit, pid_t pid, sd_bus_error *error); + ++void unit_log_failure(Unit *u, const char *result); ++ + /* Macros which append UNIT= or USER_UNIT= to the message */ + + #define log_unit_full(unit, level, error, ...) \ +diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h +index 2adfe16062..846b28fc2b 100644 +--- a/src/systemd/sd-messages.h ++++ b/src/systemd/sd-messages.h +@@ -106,6 +106,10 @@ _SD_BEGIN_DECLARATIONS; + #define SD_MESSAGE_UNIT_RESOURCES SD_ID128_MAKE(ae,8f,7b,86,6b,03,47,b9,af,31,fe,1c,80,b1,27,c0) + #define SD_MESSAGE_UNIT_RESOURCES_STR SD_ID128_MAKE_STR(ae,8f,7b,86,6b,03,47,b9,af,31,fe,1c,80,b1,27,c0) + ++#define SD_MESSAGE_UNIT_FAILURE_RESULT SD_ID128_MAKE(d9,b3,73,ed,55,a6,4f,eb,82,42,e0,2d,be,79,a4,9c) ++#define SD_MESSAGE_UNIT_FAILURE_RESULT_STR \ ++ SD_ID128_MAKE_STR(d9,b3,73,ed,55,a6,4f,eb,82,42,e0,2d,be,79,a4,9c) ++ + #define SD_MESSAGE_SPAWN_FAILED SD_ID128_MAKE(64,12,57,65,1c,1b,4e,c9,a8,62,4d,7a,40,a9,e1,e7) + #define SD_MESSAGE_SPAWN_FAILED_STR SD_ID128_MAKE_STR(64,12,57,65,1c,1b,4e,c9,a8,62,4d,7a,40,a9,e1,e7) + diff --git a/SOURCES/0380-core-log-a-recognizable-message-when-a-unit-succeeds.patch b/SOURCES/0380-core-log-a-recognizable-message-when-a-unit-succeeds.patch new file mode 100644 index 0000000..a526810 --- /dev/null +++ b/SOURCES/0380-core-log-a-recognizable-message-when-a-unit-succeeds.patch @@ -0,0 +1,210 @@ +From 40c2b0a20ff133f2050642dc7230424ddcb2987b Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 13 Nov 2018 23:28:09 +0100 +Subject: [PATCH] core: log a recognizable message when a unit succeeds, too + +We already are doing it on failure, let's do it on success, too. + +Fixes: #10265 +(cherry picked from commit 523ee2d41471bfb738f52d59de9b469301842644) + +Related: #1737283 +--- + catalog/systemd.catalog.in | 7 +++++++ + src/core/automount.c | 4 +++- + src/core/mount.c | 4 +++- + src/core/path.c | 4 +++- + src/core/scope.c | 4 +++- + src/core/service.c | 4 +++- + src/core/socket.c | 4 +++- + src/core/swap.c | 4 +++- + src/core/timer.c | 4 +++- + src/core/unit.c | 10 ++++++++++ + src/core/unit.h | 1 + + src/systemd/sd-messages.h | 2 ++ + 12 files changed, 44 insertions(+), 8 deletions(-) + +diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in +index 54a0f46921..2492ad2028 100644 +--- a/catalog/systemd.catalog.in ++++ b/catalog/systemd.catalog.in +@@ -344,6 +344,13 @@ Support: %SUPPORT_URL% + + The unit @UNIT@ completed and consumed the indicated resources. + ++-- 7ad2d189f7e94e70a38c781354912448 ++Subject: Unit succeeded ++Defined-By: systemd ++Support: %SUPPORT_URL% ++ ++The unit @UNIT@ has successfully entered the 'dead' state. ++ + -- d9b373ed55a64feb8242e02dbe79a49c + Subject: Unit failed + Defined-By: systemd +diff --git a/src/core/automount.c b/src/core/automount.c +index c78562c549..b1a155d8d4 100644 +--- a/src/core/automount.c ++++ b/src/core/automount.c +@@ -314,7 +314,9 @@ static void automount_enter_dead(Automount *a, AutomountResult f) { + if (a->result == AUTOMOUNT_SUCCESS) + a->result = f; + +- if (a->result != AUTOMOUNT_SUCCESS) ++ if (a->result == AUTOMOUNT_SUCCESS) ++ unit_log_success(UNIT(a)); ++ else + unit_log_failure(UNIT(a), automount_result_to_string(a->result)); + + automount_set_state(a, a->result != AUTOMOUNT_SUCCESS ? AUTOMOUNT_FAILED : AUTOMOUNT_DEAD); +diff --git a/src/core/mount.c b/src/core/mount.c +index 3cd0e479e9..30aaf5ae55 100644 +--- a/src/core/mount.c ++++ b/src/core/mount.c +@@ -796,7 +796,9 @@ static void mount_enter_dead(Mount *m, MountResult f) { + if (m->result == MOUNT_SUCCESS) + m->result = f; + +- if (m->result != MOUNT_SUCCESS) ++ if (m->result == MOUNT_SUCCESS) ++ unit_log_success(UNIT(m)); ++ else + unit_log_failure(UNIT(m), mount_result_to_string(m->result)); + + mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD); +diff --git a/src/core/path.c b/src/core/path.c +index f8b69e7804..dda4a3036b 100644 +--- a/src/core/path.c ++++ b/src/core/path.c +@@ -448,7 +448,9 @@ static void path_enter_dead(Path *p, PathResult f) { + if (p->result == PATH_SUCCESS) + p->result = f; + +- if (p->result != PATH_SUCCESS) ++ if (p->result == PATH_SUCCESS) ++ unit_log_success(UNIT(p)); ++ else + unit_log_failure(UNIT(p), path_result_to_string(p->result)); + + path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD); +diff --git a/src/core/scope.c b/src/core/scope.c +index 79ecfd992f..a1a5363244 100644 +--- a/src/core/scope.c ++++ b/src/core/scope.c +@@ -239,7 +239,9 @@ static void scope_enter_dead(Scope *s, ScopeResult f) { + if (s->result == SCOPE_SUCCESS) + s->result = f; + +- if (s->result != SCOPE_SUCCESS) ++ if (s->result == SCOPE_SUCCESS) ++ unit_log_success(UNIT(s)); ++ else + unit_log_failure(UNIT(s), scope_result_to_string(s->result)); + + scope_set_state(s, s->result != SCOPE_SUCCESS ? SCOPE_FAILED : SCOPE_DEAD); +diff --git a/src/core/service.c b/src/core/service.c +index efceb0614c..2c31e70ef6 100644 +--- a/src/core/service.c ++++ b/src/core/service.c +@@ -1679,7 +1679,9 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) + if (s->result == SERVICE_SUCCESS) + s->result = f; + +- if (s->result != SERVICE_SUCCESS) ++ if (s->result == SERVICE_SUCCESS) ++ unit_log_success(UNIT(s)); ++ else + unit_log_failure(UNIT(s), service_result_to_string(s->result)); + + if (allow_restart && service_shall_restart(s)) +diff --git a/src/core/socket.c b/src/core/socket.c +index 160f11765c..7c6d3dfad1 100644 +--- a/src/core/socket.c ++++ b/src/core/socket.c +@@ -1990,7 +1990,9 @@ static void socket_enter_dead(Socket *s, SocketResult f) { + if (s->result == SOCKET_SUCCESS) + s->result = f; + +- if (s->result != SOCKET_SUCCESS) ++ if (s->result == SOCKET_SUCCESS) ++ unit_log_success(UNIT(s)); ++ else + unit_log_failure(UNIT(s), socket_result_to_string(s->result)); + + socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD); +diff --git a/src/core/swap.c b/src/core/swap.c +index b5926d8644..a8f127f660 100644 +--- a/src/core/swap.c ++++ b/src/core/swap.c +@@ -656,7 +656,9 @@ static void swap_enter_dead(Swap *s, SwapResult f) { + if (s->result == SWAP_SUCCESS) + s->result = f; + +- if (s->result != SWAP_SUCCESS) ++ if (s->result == SWAP_SUCCESS) ++ unit_log_success(UNIT(s)); ++ else + unit_log_failure(UNIT(s), swap_result_to_string(s->result)); + + swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD); +diff --git a/src/core/timer.c b/src/core/timer.c +index 6ac310cbe0..2876d54a59 100644 +--- a/src/core/timer.c ++++ b/src/core/timer.c +@@ -287,7 +287,9 @@ static void timer_enter_dead(Timer *t, TimerResult f) { + if (t->result == TIMER_SUCCESS) + t->result = f; + +- if (t->result != TIMER_SUCCESS) ++ if (t->result == TIMER_SUCCESS) ++ unit_log_success(UNIT(t)); ++ else + unit_log_failure(UNIT(t), timer_result_to_string(t->result)); + + timer_set_state(t, t->result != TIMER_SUCCESS ? TIMER_FAILED : TIMER_DEAD); +diff --git a/src/core/unit.c b/src/core/unit.c +index f55bddc00f..ccb0106719 100644 +--- a/src/core/unit.c ++++ b/src/core/unit.c +@@ -5462,6 +5462,16 @@ int unit_pid_attachable(Unit *u, pid_t pid, sd_bus_error *error) { + return 0; + } + ++void unit_log_success(Unit *u) { ++ assert(u); ++ ++ log_struct(LOG_INFO, ++ "MESSAGE_ID=" SD_MESSAGE_UNIT_SUCCESS_STR, ++ LOG_UNIT_ID(u), ++ LOG_UNIT_INVOCATION_ID(u), ++ LOG_UNIT_MESSAGE(u, "Succeeded.")); ++} ++ + void unit_log_failure(Unit *u, const char *result) { + assert(u); + assert(result); +diff --git a/src/core/unit.h b/src/core/unit.h +index 9d226fb3e0..4ae1b38624 100644 +--- a/src/core/unit.h ++++ b/src/core/unit.h +@@ -804,6 +804,7 @@ const char *unit_label_path(Unit *u); + + int unit_pid_attachable(Unit *unit, pid_t pid, sd_bus_error *error); + ++void unit_log_success(Unit *u); + void unit_log_failure(Unit *u, const char *result); + + /* Macros which append UNIT= or USER_UNIT= to the message */ +diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h +index 846b28fc2b..e7ef81b597 100644 +--- a/src/systemd/sd-messages.h ++++ b/src/systemd/sd-messages.h +@@ -106,6 +106,8 @@ _SD_BEGIN_DECLARATIONS; + #define SD_MESSAGE_UNIT_RESOURCES SD_ID128_MAKE(ae,8f,7b,86,6b,03,47,b9,af,31,fe,1c,80,b1,27,c0) + #define SD_MESSAGE_UNIT_RESOURCES_STR SD_ID128_MAKE_STR(ae,8f,7b,86,6b,03,47,b9,af,31,fe,1c,80,b1,27,c0) + ++#define SD_MESSAGE_UNIT_SUCCESS SD_ID128_MAKE(7a,d2,d1,89,f7,e9,4e,70,a3,8c,78,13,54,91,24,48) ++#define SD_MESSAGE_UNIT_SUCCESS_STR SD_ID128_MAKE_STR(7a,d2,d1,89,f7,e9,4e,70,a3,8c,78,13,54,91,24,48) + #define SD_MESSAGE_UNIT_FAILURE_RESULT SD_ID128_MAKE(d9,b3,73,ed,55,a6,4f,eb,82,42,e0,2d,be,79,a4,9c) + #define SD_MESSAGE_UNIT_FAILURE_RESULT_STR \ + SD_ID128_MAKE_STR(d9,b3,73,ed,55,a6,4f,eb,82,42,e0,2d,be,79,a4,9c) diff --git a/SOURCES/0381-tests-always-use-the-right-vtable-wrapper-calls.patch b/SOURCES/0381-tests-always-use-the-right-vtable-wrapper-calls.patch new file mode 100644 index 0000000..d10b974 --- /dev/null +++ b/SOURCES/0381-tests-always-use-the-right-vtable-wrapper-calls.patch @@ -0,0 +1,113 @@ +From 9a6fa8659e7c6b18c95754e6fa9417f03b6341df Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 19 Nov 2018 14:48:28 +0100 +Subject: [PATCH] tests: always use the right vtable wrapper calls + +Prompted by https://github.com/systemd/systemd/pull/10836#discussion_r234598868 + +(cherry picked from commit bd7989a3d90e5d97e09f1eef33d09b2469a79f4d) + +Related: #1737283 +--- + src/test/test-execute.c | 2 +- + src/test/test-path.c | 18 +++++++++--------- + 2 files changed, 10 insertions(+), 10 deletions(-) + +diff --git a/src/test/test-execute.c b/src/test/test-execute.c +index 0f8dc883b1..d88de52d2a 100644 +--- a/src/test/test-execute.c ++++ b/src/test/test-execute.c +@@ -129,7 +129,7 @@ static void test(Manager *m, const char *unit_name, int status_expected, int cod + assert_se(unit_name); + + assert_se(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit) >= 0); +- assert_se(UNIT_VTABLE(unit)->start(unit) >= 0); ++ assert_se(unit_start(unit) >= 0); + check(m, unit, status_expected, code_expected); + } + +diff --git a/src/test/test-path.c b/src/test/test-path.c +index 209eb2e366..3a1469ae02 100644 +--- a/src/test/test-path.c ++++ b/src/test/test-path.c +@@ -106,7 +106,7 @@ static void check_stop_unlink(Manager *m, Unit *unit, const char *test_path, con + } + } + +- assert_se(UNIT_VTABLE(unit)->stop(unit) >= 0); ++ assert_se(unit_stop(unit) >= 0); + (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); + } + +@@ -117,7 +117,7 @@ static void test_path_exists(Manager *m) { + assert_se(m); + + assert_se(manager_load_startable_unit_or_warn(m, "path-exists.path", NULL, &unit) >= 0); +- assert_se(UNIT_VTABLE(unit)->start(unit) >= 0); ++ assert_se(unit_start(unit) >= 0); + + assert_se(touch(test_path) >= 0); + +@@ -130,7 +130,7 @@ static void test_path_existsglob(Manager *m) { + + assert_se(m); + assert_se(manager_load_startable_unit_or_warn(m, "path-existsglob.path", NULL, &unit) >= 0); +- assert_se(UNIT_VTABLE(unit)->start(unit) >= 0); ++ assert_se(unit_start(unit) >= 0); + + assert_se(touch(test_path) >= 0); + +@@ -147,7 +147,7 @@ static void test_path_changed(Manager *m) { + assert_se(touch(test_path) >= 0); + + assert_se(manager_load_startable_unit_or_warn(m, "path-changed.path", NULL, &unit) >= 0); +- assert_se(UNIT_VTABLE(unit)->start(unit) >= 0); ++ assert_se(unit_start(unit) >= 0); + + f = fopen(test_path, "w"); + assert_se(f); +@@ -166,7 +166,7 @@ static void test_path_modified(Manager *m) { + assert_se(touch(test_path) >= 0); + + assert_se(manager_load_startable_unit_or_warn(m, "path-modified.path", NULL, &unit) >= 0); +- assert_se(UNIT_VTABLE(unit)->start(unit) >= 0); ++ assert_se(unit_start(unit) >= 0); + + f = fopen(test_path, "w"); + assert_se(f); +@@ -182,7 +182,7 @@ static void test_path_unit(Manager *m) { + assert_se(m); + + assert_se(manager_load_startable_unit_or_warn(m, "path-unit.path", NULL, &unit) >= 0); +- assert_se(UNIT_VTABLE(unit)->start(unit) >= 0); ++ assert_se(unit_start(unit) >= 0); + + assert_se(touch(test_path) >= 0); + +@@ -198,7 +198,7 @@ static void test_path_directorynotempty(Manager *m) { + assert_se(access(test_path, F_OK) < 0); + + assert_se(manager_load_startable_unit_or_warn(m, "path-directorynotempty.path", NULL, &unit) >= 0); +- assert_se(UNIT_VTABLE(unit)->start(unit) >= 0); ++ assert_se(unit_start(unit) >= 0); + + /* MakeDirectory default to no */ + assert_se(access(test_path, F_OK) < 0); +@@ -219,7 +219,7 @@ static void test_path_makedirectory_directorymode(Manager *m) { + assert_se(access(test_path, F_OK) < 0); + + assert_se(manager_load_startable_unit_or_warn(m, "path-makedirectory.path", NULL, &unit) >= 0); +- assert_se(UNIT_VTABLE(unit)->start(unit) >= 0); ++ assert_se(unit_start(unit) >= 0); + + /* Check if the directory has been created */ + assert_se(access(test_path, F_OK) >= 0); +@@ -230,7 +230,7 @@ static void test_path_makedirectory_directorymode(Manager *m) { + assert_se((s.st_mode & S_IRWXG) == 0040); + assert_se((s.st_mode & S_IRWXO) == 0004); + +- assert_se(UNIT_VTABLE(unit)->stop(unit) >= 0); ++ assert_se(unit_stop(unit) >= 0); + (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); + } + diff --git a/SOURCES/0382-test-execute-allow-filtering-test-cases-by-pattern.patch b/SOURCES/0382-test-execute-allow-filtering-test-cases-by-pattern.patch new file mode 100644 index 0000000..da50009 --- /dev/null +++ b/SOURCES/0382-test-execute-allow-filtering-test-cases-by-pattern.patch @@ -0,0 +1,153 @@ +From 9beae637a919326ddc072c4dd53cb66e80273c8f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Fri, 15 Mar 2019 13:42:55 +0100 +Subject: [PATCH] test-execute: allow filtering test cases by pattern + +When debugging failure in one of the cases, it's annoying to have to wade +through the output from all the other cases. Let's allow picking select +cases. + +(cherry picked from commit 9efb96315ae502dabeb94ab35816ea8955563b7a) + +Related: #1737283 +--- + src/test/test-execute.c | 104 ++++++++++++++++++++++------------------ + 1 file changed, 58 insertions(+), 46 deletions(-) + +diff --git a/src/test/test-execute.c b/src/test/test-execute.c +index d88de52d2a..d077674efc 100644 +--- a/src/test/test-execute.c ++++ b/src/test/test-execute.c +@@ -659,8 +659,15 @@ static void test_exec_standardoutput_append(Manager *m) { + test(m, "exec-standardoutput-append.service", 0, CLD_EXITED); + } + +-static int run_tests(UnitFileScope scope, const test_function_t *tests) { +- const test_function_t *test = NULL; ++typedef struct test_entry { ++ test_function_t f; ++ const char *name; ++} test_entry; ++ ++#define entry(x) {x, #x} ++ ++static int run_tests(UnitFileScope scope, const test_entry tests[], char **patterns) { ++ const test_entry *test = NULL; + _cleanup_(manager_freep) Manager *m = NULL; + int r; + +@@ -674,55 +681,60 @@ static int run_tests(UnitFileScope scope, const test_function_t *tests) { + assert_se(r >= 0); + assert_se(manager_startup(m, NULL, NULL) >= 0); + +- for (test = tests; test && *test; test++) +- (*test)(m); ++ for (test = tests; test && test->f; test++) ++ if (strv_fnmatch_or_empty(patterns, test->name, FNM_NOESCAPE)) ++ test->f(m); ++ else ++ log_info("Skipping %s because it does not match any pattern.", test->name); + + return 0; + } + ++ + int main(int argc, char *argv[]) { + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; +- static const test_function_t user_tests[] = { +- test_exec_basic, +- test_exec_ambientcapabilities, +- test_exec_bindpaths, +- test_exec_capabilityboundingset, +- test_exec_cpuaffinity, +- test_exec_environment, +- test_exec_environmentfile, +- test_exec_group, +- test_exec_ignoresigpipe, +- test_exec_inaccessiblepaths, +- test_exec_ioschedulingclass, +- test_exec_oomscoreadjust, +- test_exec_passenvironment, +- test_exec_personality, +- test_exec_privatedevices, +- test_exec_privatenetwork, +- test_exec_privatetmp, +- test_exec_protectkernelmodules, +- test_exec_readonlypaths, +- test_exec_readwritepaths, +- test_exec_restrictnamespaces, +- test_exec_runtimedirectory, +- test_exec_standardinput, +- test_exec_standardoutput, +- test_exec_standardoutput_append, +- test_exec_supplementarygroups, +- test_exec_systemcallerrornumber, +- test_exec_systemcallfilter, +- test_exec_temporaryfilesystem, +- test_exec_umask, +- test_exec_unsetenvironment, +- test_exec_user, +- test_exec_workingdirectory, +- NULL, ++ _cleanup_free_ char *test_execute_path = NULL; ++ static const test_entry user_tests[] = { ++ entry(test_exec_basic), ++ entry(test_exec_ambientcapabilities), ++ entry(test_exec_bindpaths), ++ entry(test_exec_capabilityboundingset), ++ entry(test_exec_cpuaffinity), ++ entry(test_exec_environment), ++ entry(test_exec_environmentfile), ++ entry(test_exec_group), ++ entry(test_exec_ignoresigpipe), ++ entry(test_exec_inaccessiblepaths), ++ entry(test_exec_ioschedulingclass), ++ entry(test_exec_oomscoreadjust), ++ entry(test_exec_passenvironment), ++ entry(test_exec_personality), ++ entry(test_exec_privatedevices), ++ entry(test_exec_privatenetwork), ++ entry(test_exec_privatetmp), ++ entry(test_exec_protectkernelmodules), ++ entry(test_exec_readonlypaths), ++ entry(test_exec_readwritepaths), ++ entry(test_exec_restrictnamespaces), ++ entry(test_exec_runtimedirectory), ++ entry(test_exec_standardinput), ++ entry(test_exec_standardoutput), ++ entry(test_exec_standardoutput_append), ++ entry(test_exec_supplementarygroups), ++ entry(test_exec_systemcallerrornumber), ++ entry(test_exec_systemcallfilter), ++ entry(test_exec_temporaryfilesystem), ++ entry(test_exec_umask), ++ entry(test_exec_unsetenvironment), ++ entry(test_exec_user), ++ entry(test_exec_workingdirectory), ++ {}, + }; +- static const test_function_t system_tests[] = { +- test_exec_dynamicuser, +- test_exec_specifier, +- test_exec_systemcallfilter_system, +- NULL, ++ static const test_entry system_tests[] = { ++ entry(test_exec_dynamicuser), ++ entry(test_exec_specifier), ++ entry(test_exec_systemcallfilter_system), ++ {}, + }; + int r; + +@@ -759,9 +771,9 @@ int main(int argc, char *argv[]) { + assert_se(unsetenv("VAR2") == 0); + assert_se(unsetenv("VAR3") == 0); + +- r = run_tests(UNIT_FILE_USER, user_tests); ++ r = run_tests(UNIT_FILE_USER, user_tests, argv + 1); + if (r != 0) + return r; + +- return run_tests(UNIT_FILE_SYSTEM, system_tests); ++ return run_tests(UNIT_FILE_SYSTEM, system_tests, argv + 1); + } diff --git a/SOURCES/0383-test-execute-provide-custom-failure-message.patch b/SOURCES/0383-test-execute-provide-custom-failure-message.patch new file mode 100644 index 0000000..3299943 --- /dev/null +++ b/SOURCES/0383-test-execute-provide-custom-failure-message.patch @@ -0,0 +1,568 @@ +From ad8f0b480799aa7e312dacbcb0c01a3a9e6aa6db Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Tue, 26 Mar 2019 11:38:55 +0100 +Subject: [PATCH] test-execute: provide custom failure message + +test_exec_ambientcapabilities: exec-ambientcapabilities-nobody.service: exit status 0, expected 1 + +Sometimes we get just the last line, for example from the failure summary, +so make it as useful as possible. + +(cherry picked from commit 6aed6a11577b108b9a39f26aeae5e45d98f20c90) + +Related: #1737283 +--- + src/test/test-execute.c | 236 +++++++++++++++++++++------------------- + 1 file changed, 123 insertions(+), 113 deletions(-) + +diff --git a/src/test/test-execute.c b/src/test/test-execute.c +index d077674efc..e42d0d30a8 100644 +--- a/src/test/test-execute.c ++++ b/src/test/test-execute.c +@@ -30,7 +30,7 @@ + + typedef void (*test_function_t)(Manager *m); + +-static void check(Manager *m, Unit *unit, int status_expected, int code_expected) { ++static void check(const char *func, Manager *m, Unit *unit, int status_expected, int code_expected) { + Service *service = NULL; + usec_t ts; + usec_t timeout = 2 * USEC_PER_MINUTE; +@@ -56,8 +56,18 @@ static void check(Manager *m, Unit *unit, int status_expected, int code_expected + } + } + exec_status_dump(&service->main_exec_status, stdout, "\t"); +- assert_se(service->main_exec_status.status == status_expected); +- assert_se(service->main_exec_status.code == code_expected); ++ if (service->main_exec_status.status != status_expected) { ++ log_error("%s: %s: exit status %d, expected %d", ++ func, unit->id, ++ service->main_exec_status.status, status_expected); ++ abort(); ++ } ++ if (service->main_exec_status.code != code_expected) { ++ log_error("%s: %s: exit code %d, expected %d", ++ func, unit->id, ++ service->main_exec_status.code, code_expected); ++ abort(); ++ } + } + + static bool check_nobody_user_and_group(void) { +@@ -123,21 +133,21 @@ static bool is_inaccessible_available(void) { + return true; + } + +-static void test(Manager *m, const char *unit_name, int status_expected, int code_expected) { ++static void test(const char *func, Manager *m, const char *unit_name, int status_expected, int code_expected) { + Unit *unit; + + assert_se(unit_name); + + assert_se(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit) >= 0); + assert_se(unit_start(unit) >= 0); +- check(m, unit, status_expected, code_expected); ++ check(func, m, unit, status_expected, code_expected); + } + + static void test_exec_bindpaths(Manager *m) { + assert_se(mkdir_p("/tmp/test-exec-bindpaths", 0755) >= 0); + assert_se(mkdir_p("/tmp/test-exec-bindreadonlypaths", 0755) >= 0); + +- test(m, "exec-bindpaths.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-bindpaths.service", 0, CLD_EXITED); + + (void) rm_rf("/tmp/test-exec-bindpaths", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/tmp/test-exec-bindreadonlypaths", REMOVE_ROOT|REMOVE_PHYSICAL); +@@ -154,8 +164,8 @@ static void test_exec_cpuaffinity(Manager *m) { + return; + } + +- test(m, "exec-cpuaffinity1.service", 0, CLD_EXITED); +- test(m, "exec-cpuaffinity2.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-cpuaffinity1.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-cpuaffinity2.service", 0, CLD_EXITED); + + if (!CPU_ISSET_S(1, c.allocated, c.set) || + !CPU_ISSET_S(2, c.allocated, c.set)) { +@@ -163,52 +173,52 @@ static void test_exec_cpuaffinity(Manager *m) { + return; + } + +- test(m, "exec-cpuaffinity3.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-cpuaffinity3.service", 0, CLD_EXITED); + } + + static void test_exec_workingdirectory(Manager *m) { + assert_se(mkdir_p("/tmp/test-exec_workingdirectory", 0755) >= 0); + +- test(m, "exec-workingdirectory.service", 0, CLD_EXITED); +- test(m, "exec-workingdirectory-trailing-dot.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-workingdirectory.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-workingdirectory-trailing-dot.service", 0, CLD_EXITED); + + (void) rm_rf("/tmp/test-exec_workingdirectory", REMOVE_ROOT|REMOVE_PHYSICAL); + } + + static void test_exec_personality(Manager *m) { + #if defined(__x86_64__) +- test(m, "exec-personality-x86-64.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-personality-x86-64.service", 0, CLD_EXITED); + + #elif defined(__s390__) +- test(m, "exec-personality-s390.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-personality-s390.service", 0, CLD_EXITED); + + #elif defined(__powerpc64__) + # if __BYTE_ORDER == __BIG_ENDIAN +- test(m, "exec-personality-ppc64.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-personality-ppc64.service", 0, CLD_EXITED); + # else +- test(m, "exec-personality-ppc64le.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-personality-ppc64le.service", 0, CLD_EXITED); + # endif + + #elif defined(__aarch64__) +- test(m, "exec-personality-aarch64.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-personality-aarch64.service", 0, CLD_EXITED); + + #elif defined(__i386__) +- test(m, "exec-personality-x86.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-personality-x86.service", 0, CLD_EXITED); + #else + log_notice("Unknown personality, skipping %s", __func__); + #endif + } + + static void test_exec_ignoresigpipe(Manager *m) { +- test(m, "exec-ignoresigpipe-yes.service", 0, CLD_EXITED); +- test(m, "exec-ignoresigpipe-no.service", SIGPIPE, CLD_KILLED); ++ test(__func__, m, "exec-ignoresigpipe-yes.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-ignoresigpipe-no.service", SIGPIPE, CLD_KILLED); + } + + static void test_exec_privatetmp(Manager *m) { + assert_se(touch("/tmp/test-exec_privatetmp") >= 0); + +- test(m, "exec-privatetmp-yes.service", 0, CLD_EXITED); +- test(m, "exec-privatetmp-no.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-privatetmp-yes.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-privatetmp-no.service", 0, CLD_EXITED); + + unlink("/tmp/test-exec_privatetmp"); + } +@@ -225,9 +235,9 @@ static void test_exec_privatedevices(Manager *m) { + return; + } + +- test(m, "exec-privatedevices-yes.service", 0, CLD_EXITED); +- test(m, "exec-privatedevices-no.service", 0, CLD_EXITED); +- test(m, "exec-privatedevices-disabled-by-prefix.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-privatedevices-yes.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-privatedevices-no.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-privatedevices-disabled-by-prefix.service", 0, CLD_EXITED); + + /* We use capsh to test if the capabilities are + * properly set, so be sure that it exists */ +@@ -237,10 +247,10 @@ static void test_exec_privatedevices(Manager *m) { + return; + } + +- test(m, "exec-privatedevices-yes-capability-mknod.service", 0, CLD_EXITED); +- test(m, "exec-privatedevices-no-capability-mknod.service", 0, CLD_EXITED); +- test(m, "exec-privatedevices-yes-capability-sys-rawio.service", 0, CLD_EXITED); +- test(m, "exec-privatedevices-no-capability-sys-rawio.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-privatedevices-yes-capability-mknod.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-privatedevices-no-capability-mknod.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-privatedevices-yes-capability-sys-rawio.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-privatedevices-no-capability-sys-rawio.service", 0, CLD_EXITED); + } + + static void test_exec_protectkernelmodules(Manager *m) { +@@ -261,23 +271,23 @@ static void test_exec_protectkernelmodules(Manager *m) { + return; + } + +- test(m, "exec-protectkernelmodules-no-capabilities.service", 0, CLD_EXITED); +- test(m, "exec-protectkernelmodules-yes-capabilities.service", 0, CLD_EXITED); +- test(m, "exec-protectkernelmodules-yes-mount-propagation.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-protectkernelmodules-no-capabilities.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-protectkernelmodules-yes-capabilities.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-protectkernelmodules-yes-mount-propagation.service", 0, CLD_EXITED); + } + + static void test_exec_readonlypaths(Manager *m) { + +- test(m, "exec-readonlypaths-simple.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-readonlypaths-simple.service", 0, CLD_EXITED); + + if (path_is_read_only_fs("/var") > 0) { + log_notice("Directory /var is readonly, skipping remaining tests in %s", __func__); + return; + } + +- test(m, "exec-readonlypaths.service", 0, CLD_EXITED); +- test(m, "exec-readonlypaths-mount-propagation.service", 0, CLD_EXITED); +- test(m, "exec-readonlypaths-with-bindpaths.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-readonlypaths.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-readonlypaths-mount-propagation.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-readonlypaths-with-bindpaths.service", 0, CLD_EXITED); + } + + static void test_exec_readwritepaths(Manager *m) { +@@ -287,7 +297,7 @@ static void test_exec_readwritepaths(Manager *m) { + return; + } + +- test(m, "exec-readwritepaths-mount-propagation.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-readwritepaths-mount-propagation.service", 0, CLD_EXITED); + } + + static void test_exec_inaccessiblepaths(Manager *m) { +@@ -297,22 +307,22 @@ static void test_exec_inaccessiblepaths(Manager *m) { + return; + } + +- test(m, "exec-inaccessiblepaths-proc.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-inaccessiblepaths-proc.service", 0, CLD_EXITED); + + if (path_is_read_only_fs("/") > 0) { + log_notice("Root directory is readonly, skipping remaining tests in %s", __func__); + return; + } + +- test(m, "exec-inaccessiblepaths-mount-propagation.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-inaccessiblepaths-mount-propagation.service", 0, CLD_EXITED); + } + + static void test_exec_temporaryfilesystem(Manager *m) { + +- test(m, "exec-temporaryfilesystem-options.service", 0, CLD_EXITED); +- test(m, "exec-temporaryfilesystem-ro.service", 0, CLD_EXITED); +- test(m, "exec-temporaryfilesystem-rw.service", 0, CLD_EXITED); +- test(m, "exec-temporaryfilesystem-usr.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-temporaryfilesystem-options.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-temporaryfilesystem-ro.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-temporaryfilesystem-rw.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-temporaryfilesystem-usr.service", 0, CLD_EXITED); + } + + static void test_exec_systemcallfilter(Manager *m) { +@@ -324,10 +334,10 @@ static void test_exec_systemcallfilter(Manager *m) { + return; + } + +- test(m, "exec-systemcallfilter-not-failing.service", 0, CLD_EXITED); +- test(m, "exec-systemcallfilter-not-failing2.service", 0, CLD_EXITED); +- test(m, "exec-systemcallfilter-failing.service", SIGSYS, CLD_KILLED); +- test(m, "exec-systemcallfilter-failing2.service", SIGSYS, CLD_KILLED); ++ test(__func__, m, "exec-systemcallfilter-not-failing.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-systemcallfilter-not-failing2.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-systemcallfilter-failing.service", SIGSYS, CLD_KILLED); ++ test(__func__, m, "exec-systemcallfilter-failing2.service", SIGSYS, CLD_KILLED); + + r = find_binary("python3", NULL); + if (r < 0) { +@@ -335,8 +345,8 @@ static void test_exec_systemcallfilter(Manager *m) { + return; + } + +- test(m, "exec-systemcallfilter-with-errno-name.service", errno_from_name("EILSEQ"), CLD_EXITED); +- test(m, "exec-systemcallfilter-with-errno-number.service", 255, CLD_EXITED); ++ test(__func__, m, "exec-systemcallfilter-with-errno-name.service", errno_from_name("EILSEQ"), CLD_EXITED); ++ test(__func__, m, "exec-systemcallfilter-with-errno-number.service", 255, CLD_EXITED); + #endif + } + +@@ -355,8 +365,8 @@ static void test_exec_systemcallerrornumber(Manager *m) { + return; + } + +- test(m, "exec-systemcallerrornumber-name.service", errno_from_name("EACCES"), CLD_EXITED); +- test(m, "exec-systemcallerrornumber-number.service", 255, CLD_EXITED); ++ test(__func__, m, "exec-systemcallerrornumber-name.service", errno_from_name("EACCES"), CLD_EXITED); ++ test(__func__, m, "exec-systemcallerrornumber-number.service", 255, CLD_EXITED); + #endif + } + +@@ -367,13 +377,13 @@ static void test_exec_restrictnamespaces(Manager *m) { + return; + } + +- test(m, "exec-restrictnamespaces-no.service", 0, CLD_EXITED); +- test(m, "exec-restrictnamespaces-yes.service", 1, CLD_EXITED); +- test(m, "exec-restrictnamespaces-mnt.service", 0, CLD_EXITED); +- test(m, "exec-restrictnamespaces-mnt-blacklist.service", 1, CLD_EXITED); +- test(m, "exec-restrictnamespaces-merge-and.service", 0, CLD_EXITED); +- test(m, "exec-restrictnamespaces-merge-or.service", 0, CLD_EXITED); +- test(m, "exec-restrictnamespaces-merge-all.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-restrictnamespaces-no.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-restrictnamespaces-yes.service", 1, CLD_EXITED); ++ test(__func__, m, "exec-restrictnamespaces-mnt.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-restrictnamespaces-mnt-blacklist.service", 1, CLD_EXITED); ++ test(__func__, m, "exec-restrictnamespaces-merge-and.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-restrictnamespaces-merge-or.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-restrictnamespaces-merge-all.service", 0, CLD_EXITED); + #endif + } + +@@ -384,7 +394,7 @@ static void test_exec_systemcallfilter_system(Manager *m) { + return; + } + +- test(m, "exec-systemcallfilter-system-user.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-systemcallfilter-system-user.service", 0, CLD_EXITED); + + if (!check_nobody_user_and_group()) { + log_notice("nobody user/group is not synthesized or may conflict to other entries, skipping remaining tests in %s", __func__); +@@ -396,12 +406,12 @@ static void test_exec_systemcallfilter_system(Manager *m) { + return; + } + +- test(m, "exec-systemcallfilter-system-user-" NOBODY_USER_NAME ".service", 0, CLD_EXITED); ++ test(__func__, m, "exec-systemcallfilter-system-user-" NOBODY_USER_NAME ".service", 0, CLD_EXITED); + #endif + } + + static void test_exec_user(Manager *m) { +- test(m, "exec-user.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-user.service", 0, CLD_EXITED); + + if (!check_nobody_user_and_group()) { + log_notice("nobody user/group is not synthesized or may conflict to other entries, skipping remaining tests in %s", __func__); +@@ -413,11 +423,11 @@ static void test_exec_user(Manager *m) { + return; + } + +- test(m, "exec-user-" NOBODY_USER_NAME ".service", 0, CLD_EXITED); ++ test(__func__, m, "exec-user-" NOBODY_USER_NAME ".service", 0, CLD_EXITED); + } + + static void test_exec_group(Manager *m) { +- test(m, "exec-group.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-group.service", 0, CLD_EXITED); + + if (!check_nobody_user_and_group()) { + log_notice("nobody user/group is not synthesized or may conflict to other entries, skipping remaining tests in %s", __func__); +@@ -429,31 +439,31 @@ static void test_exec_group(Manager *m) { + return; + } + +- test(m, "exec-group-" NOBODY_GROUP_NAME ".service", 0, CLD_EXITED); ++ test(__func__, m, "exec-group-" NOBODY_GROUP_NAME ".service", 0, CLD_EXITED); + } + + static void test_exec_supplementarygroups(Manager *m) { +- test(m, "exec-supplementarygroups.service", 0, CLD_EXITED); +- test(m, "exec-supplementarygroups-single-group.service", 0, CLD_EXITED); +- test(m, "exec-supplementarygroups-single-group-user.service", 0, CLD_EXITED); +- test(m, "exec-supplementarygroups-multiple-groups-default-group-user.service", 0, CLD_EXITED); +- test(m, "exec-supplementarygroups-multiple-groups-withgid.service", 0, CLD_EXITED); +- test(m, "exec-supplementarygroups-multiple-groups-withuid.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-supplementarygroups.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-supplementarygroups-single-group.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-supplementarygroups-single-group-user.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-supplementarygroups-multiple-groups-default-group-user.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-supplementarygroups-multiple-groups-withgid.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-supplementarygroups-multiple-groups-withuid.service", 0, CLD_EXITED); + } + + static void test_exec_dynamicuser(Manager *m) { +- test(m, "exec-dynamicuser-fixeduser.service", 0, CLD_EXITED); +- test(m, "exec-dynamicuser-fixeduser-one-supplementarygroup.service", 0, CLD_EXITED); +- test(m, "exec-dynamicuser-supplementarygroups.service", 0, CLD_EXITED); +- test(m, "exec-dynamicuser-statedir.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-dynamicuser-fixeduser.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-dynamicuser-fixeduser-one-supplementarygroup.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-dynamicuser-supplementarygroups.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-dynamicuser-statedir.service", 0, CLD_EXITED); + + (void) rm_rf("/var/lib/test-dynamicuser-migrate", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/var/lib/test-dynamicuser-migrate2", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/var/lib/private/test-dynamicuser-migrate", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/var/lib/private/test-dynamicuser-migrate2", REMOVE_ROOT|REMOVE_PHYSICAL); + +- test(m, "exec-dynamicuser-statedir-migrate-step1.service", 0, CLD_EXITED); +- test(m, "exec-dynamicuser-statedir-migrate-step2.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-dynamicuser-statedir-migrate-step1.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-dynamicuser-statedir-migrate-step2.service", 0, CLD_EXITED); + + (void) rm_rf("/var/lib/test-dynamicuser-migrate", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/var/lib/test-dynamicuser-migrate2", REMOVE_ROOT|REMOVE_PHYSICAL); +@@ -462,9 +472,9 @@ static void test_exec_dynamicuser(Manager *m) { + } + + static void test_exec_environment(Manager *m) { +- test(m, "exec-environment.service", 0, CLD_EXITED); +- test(m, "exec-environment-multiple.service", 0, CLD_EXITED); +- test(m, "exec-environment-empty.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-environment.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-environment-multiple.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-environment-empty.service", 0, CLD_EXITED); + } + + static void test_exec_environmentfile(Manager *m) { +@@ -484,7 +494,7 @@ static void test_exec_environmentfile(Manager *m) { + r = write_string_file("/tmp/test-exec_environmentfile.conf", e, WRITE_STRING_FILE_CREATE); + assert_se(r == 0); + +- test(m, "exec-environmentfile.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-environmentfile.service", 0, CLD_EXITED); + + (void) unlink("/tmp/test-exec_environmentfile.conf"); + } +@@ -506,26 +516,26 @@ static void test_exec_passenvironment(Manager *m) { + assert_se(setenv("VAR3", "$word 5 6", 1) == 0); + assert_se(setenv("VAR4", "new\nline", 1) == 0); + assert_se(setenv("VAR5", "passwordwithbackslashes", 1) == 0); +- test(m, "exec-passenvironment.service", 0, CLD_EXITED); +- test(m, "exec-passenvironment-repeated.service", 0, CLD_EXITED); +- test(m, "exec-passenvironment-empty.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-passenvironment.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-passenvironment-repeated.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-passenvironment-empty.service", 0, CLD_EXITED); + assert_se(unsetenv("VAR1") == 0); + assert_se(unsetenv("VAR2") == 0); + assert_se(unsetenv("VAR3") == 0); + assert_se(unsetenv("VAR4") == 0); + assert_se(unsetenv("VAR5") == 0); +- test(m, "exec-passenvironment-absent.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-passenvironment-absent.service", 0, CLD_EXITED); + } + + static void test_exec_umask(Manager *m) { +- test(m, "exec-umask-default.service", 0, CLD_EXITED); +- test(m, "exec-umask-0177.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-umask-default.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-umask-0177.service", 0, CLD_EXITED); + } + + static void test_exec_runtimedirectory(Manager *m) { +- test(m, "exec-runtimedirectory.service", 0, CLD_EXITED); +- test(m, "exec-runtimedirectory-mode.service", 0, CLD_EXITED); +- test(m, "exec-runtimedirectory-owner.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-runtimedirectory.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-runtimedirectory-mode.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-runtimedirectory-owner.service", 0, CLD_EXITED); + + if (!check_nobody_user_and_group()) { + log_notice("nobody user/group is not synthesized or may conflict to other entries, skipping remaining tests in %s", __func__); +@@ -537,7 +547,7 @@ static void test_exec_runtimedirectory(Manager *m) { + return; + } + +- test(m, "exec-runtimedirectory-owner-" NOBODY_GROUP_NAME ".service", 0, CLD_EXITED); ++ test(__func__, m, "exec-runtimedirectory-owner-" NOBODY_GROUP_NAME ".service", 0, CLD_EXITED); + } + + static void test_exec_capabilityboundingset(Manager *m) { +@@ -556,14 +566,14 @@ static void test_exec_capabilityboundingset(Manager *m) { + return; + } + +- test(m, "exec-capabilityboundingset-simple.service", 0, CLD_EXITED); +- test(m, "exec-capabilityboundingset-reset.service", 0, CLD_EXITED); +- test(m, "exec-capabilityboundingset-merge.service", 0, CLD_EXITED); +- test(m, "exec-capabilityboundingset-invert.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-capabilityboundingset-simple.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-capabilityboundingset-reset.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-capabilityboundingset-merge.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-capabilityboundingset-invert.service", 0, CLD_EXITED); + } + + static void test_exec_basic(Manager *m) { +- test(m, "exec-basic.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-basic.service", 0, CLD_EXITED); + } + + static void test_exec_ambientcapabilities(Manager *m) { +@@ -585,8 +595,8 @@ static void test_exec_ambientcapabilities(Manager *m) { + return; + } + +- test(m, "exec-ambientcapabilities.service", 0, CLD_EXITED); +- test(m, "exec-ambientcapabilities-merge.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-ambientcapabilities.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-ambientcapabilities-merge.service", 0, CLD_EXITED); + + if (!check_nobody_user_and_group()) { + log_notice("nobody user/group is not synthesized or may conflict to other entries, skipping remaining tests in %s", __func__); +@@ -598,8 +608,8 @@ static void test_exec_ambientcapabilities(Manager *m) { + return; + } + +- test(m, "exec-ambientcapabilities-" NOBODY_USER_NAME ".service", 0, CLD_EXITED); +- test(m, "exec-ambientcapabilities-merge-" NOBODY_USER_NAME ".service", 0, CLD_EXITED); ++ test(__func__, m, "exec-ambientcapabilities-" NOBODY_USER_NAME ".service", 0, CLD_EXITED); ++ test(__func__, m, "exec-ambientcapabilities-merge-" NOBODY_USER_NAME ".service", 0, CLD_EXITED); + } + + static void test_exec_privatenetwork(Manager *m) { +@@ -611,52 +621,52 @@ static void test_exec_privatenetwork(Manager *m) { + return; + } + +- test(m, "exec-privatenetwork-yes.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-privatenetwork-yes.service", 0, CLD_EXITED); + } + + static void test_exec_oomscoreadjust(Manager *m) { +- test(m, "exec-oomscoreadjust-positive.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-oomscoreadjust-positive.service", 0, CLD_EXITED); + + if (detect_container() > 0) { + log_notice("Testing in container, skipping remaining tests in %s", __func__); + return; + } +- test(m, "exec-oomscoreadjust-negative.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-oomscoreadjust-negative.service", 0, CLD_EXITED); + } + + static void test_exec_ioschedulingclass(Manager *m) { +- test(m, "exec-ioschedulingclass-none.service", 0, CLD_EXITED); +- test(m, "exec-ioschedulingclass-idle.service", 0, CLD_EXITED); +- test(m, "exec-ioschedulingclass-best-effort.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-ioschedulingclass-none.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-ioschedulingclass-idle.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-ioschedulingclass-best-effort.service", 0, CLD_EXITED); + + if (detect_container() > 0) { + log_notice("Testing in container, skipping remaining tests in %s", __func__); + return; + } +- test(m, "exec-ioschedulingclass-realtime.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-ioschedulingclass-realtime.service", 0, CLD_EXITED); + } + + static void test_exec_unsetenvironment(Manager *m) { +- test(m, "exec-unsetenvironment.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-unsetenvironment.service", 0, CLD_EXITED); + } + + static void test_exec_specifier(Manager *m) { +- test(m, "exec-specifier.service", 0, CLD_EXITED); +- test(m, "exec-specifier@foo-bar.service", 0, CLD_EXITED); +- test(m, "exec-specifier-interpolation.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-specifier.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-specifier@foo-bar.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-specifier-interpolation.service", 0, CLD_EXITED); + } + + static void test_exec_standardinput(Manager *m) { +- test(m, "exec-standardinput-data.service", 0, CLD_EXITED); +- test(m, "exec-standardinput-file.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-standardinput-data.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-standardinput-file.service", 0, CLD_EXITED); + } + + static void test_exec_standardoutput(Manager *m) { +- test(m, "exec-standardoutput-file.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-standardoutput-file.service", 0, CLD_EXITED); + } + + static void test_exec_standardoutput_append(Manager *m) { +- test(m, "exec-standardoutput-append.service", 0, CLD_EXITED); ++ test(__func__, m, "exec-standardoutput-append.service", 0, CLD_EXITED); + } + + typedef struct test_entry { diff --git a/SOURCES/0384-core-ExecCondition-for-services.patch b/SOURCES/0384-core-ExecCondition-for-services.patch new file mode 100644 index 0000000..1d1e10f --- /dev/null +++ b/SOURCES/0384-core-ExecCondition-for-services.patch @@ -0,0 +1,738 @@ +From ab9c835796a27f0fbaee75a90f0311ec456941d8 Mon Sep 17 00:00:00 2001 +From: Anita Zhang +Date: Fri, 28 Jun 2019 17:02:30 -0700 +Subject: [PATCH] core: ExecCondition= for services + +Closes #10596 + +(cherry picked from commit 31cd5f63ce86a0784c4ef869c4d323a11ff14adc) + +Resolves: #1737283 +--- + TODO | 2 - + catalog/systemd.catalog.in | 7 ++ + doc/TRANSIENT-SETTINGS.md | 1 + + man/systemd.service.xml | 20 ++++ + src/basic/unit-def.c | 1 + + src/basic/unit-def.h | 1 + + src/core/dbus-service.c | 1 + + src/core/job.c | 3 +- + src/core/job.h | 2 +- + src/core/load-fragment-gperf.gperf.m4 | 1 + + src/core/service.c | 93 ++++++++++++++++--- + src/core/service.h | 2 + + src/core/unit.c | 25 ++++- + src/core/unit.h | 4 + + src/shared/bus-unit-util.c | 2 +- + src/systemd/sd-messages.h | 2 + + src/test/test-execute.c | 50 +++++++++- + test/fuzz/fuzz-unit-file/directives.service | 1 + + test/meson.build | 2 + + .../exec-condition-failed.service | 11 +++ + test/test-execute/exec-condition-skip.service | 15 +++ + 21 files changed, 222 insertions(+), 24 deletions(-) + create mode 100644 test/test-execute/exec-condition-failed.service + create mode 100644 test/test-execute/exec-condition-skip.service + +diff --git a/TODO b/TODO +index ff1008accf..8f78000089 100644 +--- a/TODO ++++ b/TODO +@@ -626,8 +626,6 @@ Features: + + * merge unit_kill_common() and unit_kill_context() + +-* introduce ExecCondition= in services +- + * EFI: + - honor language efi variables for default language selection (if there are any?) + - honor timezone efi variables for default timezone selection (if there are any?) +diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in +index 2492ad2028..dc44414f9d 100644 +--- a/catalog/systemd.catalog.in ++++ b/catalog/systemd.catalog.in +@@ -358,6 +358,13 @@ Support: %SUPPORT_URL% + + The unit @UNIT@ has entered the 'failed' state with result '@UNIT_RESULT@'. + ++-- 0e4284a0caca4bfc81c0bb6786972673 ++Subject: Unit skipped ++Defined-By: systemd ++Support: %SUPPORT_URL% ++ ++The unit @UNIT@ was skipped and has entered the 'dead' state with result '@UNIT_RESULT@'. ++ + -- 50876a9db00f4c40bde1a2ad381c3a1b + Subject: The system is configured in a way that might cause problems + Defined-By: systemd +diff --git a/doc/TRANSIENT-SETTINGS.md b/doc/TRANSIENT-SETTINGS.md +index 0b2ad66dcb..23fe84e4d1 100644 +--- a/doc/TRANSIENT-SETTINGS.md ++++ b/doc/TRANSIENT-SETTINGS.md +@@ -267,6 +267,7 @@ Most service unit settings are available for transient units. + + ``` + ✓ PIDFile= ++✓ ExecCondition= + ✓ ExecStartPre= + ✓ ExecStart= + ✓ ExecStartPost= +diff --git a/man/systemd.service.xml b/man/systemd.service.xml +index 315b80e704..54586d1948 100644 +--- a/man/systemd.service.xml ++++ b/man/systemd.service.xml +@@ -414,6 +414,26 @@ + + + ++ ++ ExecCondition= ++ Optional commands that are executed before the command(s) in ExecStartPre=. ++ Syntax is the same as for ExecStart=, except that multiple command lines are allowed and the ++ commands are executed one after the other, serially. ++ ++ The behavior is like an ExecStartPre= and condition check hybrid: when an ++ ExecCondition= command exits with exit code 1 through 254 (inclusive), the remaining ++ commands are skipped and the unit is not marked as failed. However, if an ++ ExecCondition= command exits with 255 or abnormally (e.g. timeout, killed by a ++ signal, etc.), the unit will be considered failed (and remaining commands will be skipped). Exit code of 0 or ++ those matching SuccessExitStatus= will continue execution to the next command(s). ++ ++ The same recommendations about not running long-running processes in ExecStartPre= ++ also applies to ExecCondition=. ExecCondition= will also run the commands ++ in ExecStopPost=, as part of stopping the service, in the case of any non-zero or abnormal ++ exits, like the ones described above. ++ ++ ++ + + ExecReload= + Commands to execute to trigger a configuration +diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c +index ac6a9b37e8..46593f6e65 100644 +--- a/src/basic/unit-def.c ++++ b/src/basic/unit-def.c +@@ -162,6 +162,7 @@ DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState); + + static const char* const service_state_table[_SERVICE_STATE_MAX] = { + [SERVICE_DEAD] = "dead", ++ [SERVICE_CONDITION] = "condition", + [SERVICE_START_PRE] = "start-pre", + [SERVICE_START] = "start", + [SERVICE_START_POST] = "start-post", +diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h +index d7e2d74669..db397a31ed 100644 +--- a/src/basic/unit-def.h ++++ b/src/basic/unit-def.h +@@ -101,6 +101,7 @@ typedef enum ScopeState { + + typedef enum ServiceState { + SERVICE_DEAD, ++ SERVICE_CONDITION, + SERVICE_START_PRE, + SERVICE_START, + SERVICE_START_POST, +diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c +index 1b4c98c7d2..5f768a77c8 100644 +--- a/src/core/dbus-service.c ++++ b/src/core/dbus-service.c +@@ -127,6 +127,7 @@ const sd_bus_vtable bus_service_vtable[] = { + SD_BUS_PROPERTY("NRestarts", "u", bus_property_get_unsigned, offsetof(Service, n_restarts), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + + BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), ++ BUS_EXEC_COMMAND_LIST_VTABLE("ExecCondition", offsetof(Service, exec_command[SERVICE_EXEC_CONDITION]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Service, exec_command[SERVICE_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStart", offsetof(Service, exec_command[SERVICE_EXEC_START]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Service, exec_command[SERVICE_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), +diff --git a/src/core/job.c b/src/core/job.c +index b9eee91cf3..870ec0a387 100644 +--- a/src/core/job.c ++++ b/src/core/job.c +@@ -870,7 +870,8 @@ static void job_log_done_status_message(Unit *u, uint32_t job_id, JobType t, Job + return; + + /* Show condition check message if the job did not actually do anything due to failed condition. */ +- if (t == JOB_START && result == JOB_DONE && !u->condition_result) { ++ if ((t == JOB_START && result == JOB_DONE && !u->condition_result) || ++ (t == JOB_START && result == JOB_SKIPPED)) { + log_struct(LOG_INFO, + "MESSAGE=Condition check resulted in %s being skipped.", unit_description(u), + "JOB_ID=%" PRIu32, job_id, +diff --git a/src/core/job.h b/src/core/job.h +index 2f5f3f3989..189fea20ca 100644 +--- a/src/core/job.h ++++ b/src/core/job.h +@@ -85,7 +85,7 @@ enum JobResult { + JOB_TIMEOUT, /* Job timeout elapsed */ + JOB_FAILED, /* Job failed */ + JOB_DEPENDENCY, /* A required dependency job did not result in JOB_DONE */ +- JOB_SKIPPED, /* Negative result of JOB_VERIFY_ACTIVE */ ++ JOB_SKIPPED, /* Negative result of JOB_VERIFY_ACTIVE or skip due to ExecCondition= */ + JOB_INVALID, /* JOB_RELOAD of inactive unit */ + JOB_ASSERT, /* Couldn't start a unit, because an assert didn't hold */ + JOB_UNSUPPORTED, /* Couldn't start a unit, because the unit type is not supported on the system */ +diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 +index 161c5a2c82..8883818ff2 100644 +--- a/src/core/load-fragment-gperf.gperf.m4 ++++ b/src/core/load-fragment-gperf.gperf.m4 +@@ -291,6 +291,7 @@ Unit.AssertNull, config_parse_unit_condition_null, 0, + Unit.CollectMode, config_parse_collect_mode, 0, offsetof(Unit, collect_mode) + m4_dnl + Service.PIDFile, config_parse_unit_path_printf, 0, offsetof(Service, pid_file) ++Service.ExecCondition, config_parse_exec, SERVICE_EXEC_CONDITION, offsetof(Service, exec_command) + Service.ExecStartPre, config_parse_exec, SERVICE_EXEC_START_PRE, offsetof(Service, exec_command) + Service.ExecStart, config_parse_exec, SERVICE_EXEC_START, offsetof(Service, exec_command) + Service.ExecStartPost, config_parse_exec, SERVICE_EXEC_START_POST, offsetof(Service, exec_command) +diff --git a/src/core/service.c b/src/core/service.c +index 2c31e70ef6..92be4280f6 100644 +--- a/src/core/service.c ++++ b/src/core/service.c +@@ -41,6 +41,7 @@ + + static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { + [SERVICE_DEAD] = UNIT_INACTIVE, ++ [SERVICE_CONDITION] = UNIT_ACTIVATING, + [SERVICE_START_PRE] = UNIT_ACTIVATING, + [SERVICE_START] = UNIT_ACTIVATING, + [SERVICE_START_POST] = UNIT_ACTIVATING, +@@ -62,6 +63,7 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { + * consider idle jobs active as soon as we start working on them */ + static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = { + [SERVICE_DEAD] = UNIT_INACTIVE, ++ [SERVICE_CONDITION] = UNIT_ACTIVE, + [SERVICE_START_PRE] = UNIT_ACTIVE, + [SERVICE_START] = UNIT_ACTIVE, + [SERVICE_START_POST] = UNIT_ACTIVE, +@@ -1024,7 +1026,7 @@ static void service_set_state(Service *s, ServiceState state) { + service_unwatch_pid_file(s); + + if (!IN_SET(state, +- SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, ++ SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + SERVICE_RUNNING, + SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, +@@ -1042,7 +1044,7 @@ static void service_set_state(Service *s, ServiceState state) { + } + + if (!IN_SET(state, +- SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, ++ SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { +@@ -1057,7 +1059,7 @@ static void service_set_state(Service *s, ServiceState state) { + } + + if (!IN_SET(state, +- SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, ++ SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + SERVICE_RUNNING, SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL) && +@@ -1080,7 +1082,8 @@ static void service_set_state(Service *s, ServiceState state) { + + unit_notify(UNIT(s), table[old_state], table[state], + (s->reload_result == SERVICE_SUCCESS ? 0 : UNIT_NOTIFY_RELOAD_FAILURE) | +- (s->will_auto_restart ? UNIT_NOTIFY_WILL_AUTO_RESTART : 0)); ++ (s->will_auto_restart ? UNIT_NOTIFY_WILL_AUTO_RESTART : 0) | ++ (s->result == SERVICE_SKIP_CONDITION ? UNIT_NOTIFY_SKIP_CONDITION : 0)); + } + + static usec_t service_coldplug_timeout(Service *s) { +@@ -1088,6 +1091,7 @@ static usec_t service_coldplug_timeout(Service *s) { + + switch (s->deserialized_state) { + ++ case SERVICE_CONDITION: + case SERVICE_START_PRE: + case SERVICE_START: + case SERVICE_START_POST: +@@ -1143,7 +1147,7 @@ static int service_coldplug(Unit *u) { + if (s->control_pid > 0 && + pid_is_unwaited(s->control_pid) && + IN_SET(s->deserialized_state, +- SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, ++ SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { +@@ -1667,6 +1671,7 @@ static bool service_will_restart(Unit *u) { + } + + static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) { ++ ServiceState end_state; + int r; + + assert(s); +@@ -1679,10 +1684,16 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) + if (s->result == SERVICE_SUCCESS) + s->result = f; + +- if (s->result == SERVICE_SUCCESS) ++ if (s->result == SERVICE_SUCCESS) { + unit_log_success(UNIT(s)); +- else ++ end_state = SERVICE_DEAD; ++ } else if (s->result == SERVICE_SKIP_CONDITION) { ++ unit_log_skip(UNIT(s), service_result_to_string(s->result)); ++ end_state = SERVICE_DEAD; ++ } else { + unit_log_failure(UNIT(s), service_result_to_string(s->result)); ++ end_state = SERVICE_FAILED; ++ } + + if (allow_restart && service_shall_restart(s)) + s->will_auto_restart = true; +@@ -1691,7 +1702,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) + * SERVICE_FAILED/SERVICE_DEAD before entering into SERVICE_AUTO_RESTART. */ + s->n_keep_fd_store ++; + +- service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD); ++ service_set_state(s, end_state); + + if (s->will_auto_restart) { + s->will_auto_restart = false; +@@ -2110,6 +2121,40 @@ fail: + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); + } + ++static void service_enter_condition(Service *s) { ++ int r; ++ ++ assert(s); ++ ++ service_unwatch_control_pid(s); ++ ++ s->control_command = s->exec_command[SERVICE_EXEC_CONDITION]; ++ if (s->control_command) { ++ ++ unit_warn_leftover_processes(UNIT(s)); ++ ++ s->control_command_id = SERVICE_EXEC_CONDITION; ++ ++ r = service_spawn(s, ++ s->control_command, ++ s->timeout_start_usec, ++ EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_APPLY_TTY_STDIN, ++ &s->control_pid); ++ ++ if (r < 0) ++ goto fail; ++ ++ service_set_state(s, SERVICE_CONDITION); ++ } else ++ service_enter_start_pre(s); ++ ++ return; ++ ++fail: ++ log_unit_warning_errno(UNIT(s), r, "Failed to run 'exec-condition' task: %m"); ++ service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); ++} ++ + static void service_enter_restart(Service *s) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; +@@ -2222,7 +2267,7 @@ static void service_run_next_control(Service *s) { + s->control_command = s->control_command->command_next; + service_unwatch_control_pid(s); + +- if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) ++ if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) + timeout = s->timeout_start_usec; + else + timeout = s->timeout_stop_usec; +@@ -2231,7 +2276,7 @@ static void service_run_next_control(Service *s) { + s->control_command, + timeout, + EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL| +- (IN_SET(s->control_command_id, SERVICE_EXEC_START_PRE, SERVICE_EXEC_STOP_POST) ? EXEC_APPLY_TTY_STDIN : 0)| ++ (IN_SET(s->control_command_id, SERVICE_EXEC_CONDITION, SERVICE_EXEC_START_PRE, SERVICE_EXEC_STOP_POST) ? EXEC_APPLY_TTY_STDIN : 0)| + (IN_SET(s->control_command_id, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST) ? EXEC_SETENV_RESULT : 0), + &s->control_pid); + if (r < 0) +@@ -2242,7 +2287,7 @@ static void service_run_next_control(Service *s) { + fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run next control task: %m"); + +- if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START_POST, SERVICE_STOP)) ++ if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START_POST, SERVICE_STOP)) + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); + else if (s->state == SERVICE_STOP_POST) + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); +@@ -2296,7 +2341,7 @@ static int service_start(Unit *u) { + return -EAGAIN; + + /* Already on it! */ +- if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST)) ++ if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST)) + return 0; + + /* A service that will be restarted must be stopped first to +@@ -2344,7 +2389,9 @@ static int service_start(Unit *u) { + s->flush_n_restarts = false; + } + +- service_enter_start_pre(s); ++ u->reset_accounting = true; ++ ++ service_enter_condition(s); + return 1; + } + +@@ -2370,7 +2417,7 @@ static int service_stop(Unit *u) { + + /* If there's already something running we go directly into + * kill mode. */ +- if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD)) { ++ if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD)) { + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); + return 0; + } +@@ -3303,6 +3350,10 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { + } else if (s->control_pid == pid) { + s->control_pid = 0; + ++ /* ExecCondition= calls that exit with (0, 254] should invoke skip-like behavior instead of failing */ ++ if (f == SERVICE_FAILURE_EXIT_CODE && s->state == SERVICE_CONDITION && status < 255) ++ f = SERVICE_SKIP_CONDITION; ++ + if (s->control_command) { + exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); + +@@ -3338,6 +3389,13 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { + + switch (s->state) { + ++ case SERVICE_CONDITION: ++ if (f == SERVICE_SUCCESS) ++ service_enter_start_pre(s); ++ else ++ service_enter_signal(s, SERVICE_STOP_SIGTERM, f); ++ break; ++ + case SERVICE_START_PRE: + if (f == SERVICE_SUCCESS) + service_enter_start(s); +@@ -3462,9 +3520,10 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us + + switch (s->state) { + ++ case SERVICE_CONDITION: + case SERVICE_START_PRE: + case SERVICE_START: +- log_unit_warning(UNIT(s), "%s operation timed out. Terminating.", s->state == SERVICE_START ? "Start" : "Start-pre"); ++ log_unit_warning(UNIT(s), "%s operation timed out. Terminating.", service_state_to_string(s->state)); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + +@@ -3975,6 +4034,7 @@ static bool service_needs_console(Unit *u) { + return false; + + return IN_SET(s->state, ++ SERVICE_CONDITION, + SERVICE_START_PRE, + SERVICE_START, + SERVICE_START_POST, +@@ -4014,6 +4074,7 @@ static const char* const service_type_table[_SERVICE_TYPE_MAX] = { + DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType); + + static const char* const service_exec_command_table[_SERVICE_EXEC_COMMAND_MAX] = { ++ [SERVICE_EXEC_CONDITION] = "ExecCondition", + [SERVICE_EXEC_START_PRE] = "ExecStartPre", + [SERVICE_EXEC_START] = "ExecStart", + [SERVICE_EXEC_START_POST] = "ExecStartPost", +@@ -4043,6 +4104,7 @@ static const char* const service_result_table[_SERVICE_RESULT_MAX] = { + [SERVICE_FAILURE_CORE_DUMP] = "core-dump", + [SERVICE_FAILURE_WATCHDOG] = "watchdog", + [SERVICE_FAILURE_START_LIMIT_HIT] = "start-limit-hit", ++ [SERVICE_SKIP_CONDITION] = "exec-condition", + }; + + DEFINE_STRING_TABLE_LOOKUP(service_result, ServiceResult); +@@ -4118,6 +4180,7 @@ const UnitVTable service_vtable = { + .finished_start_job = { + [JOB_DONE] = "Started %s.", + [JOB_FAILED] = "Failed to start %s.", ++ [JOB_SKIPPED] = "Skipped %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Stopped %s.", +diff --git a/src/core/service.h b/src/core/service.h +index 1206e3cdda..62b78cadf1 100644 +--- a/src/core/service.h ++++ b/src/core/service.h +@@ -36,6 +36,7 @@ typedef enum ServiceType { + } ServiceType; + + typedef enum ServiceExecCommand { ++ SERVICE_EXEC_CONDITION, + SERVICE_EXEC_START_PRE, + SERVICE_EXEC_START, + SERVICE_EXEC_START_POST, +@@ -67,6 +68,7 @@ typedef enum ServiceResult { + SERVICE_FAILURE_CORE_DUMP, + SERVICE_FAILURE_WATCHDOG, + SERVICE_FAILURE_START_LIMIT_HIT, ++ SERVICE_SKIP_CONDITION, + _SERVICE_RESULT_MAX, + _SERVICE_RESULT_INVALID = -1 + } ServiceResult; +diff --git a/src/core/unit.c b/src/core/unit.c +index ccb0106719..61799bf9e3 100644 +--- a/src/core/unit.c ++++ b/src/core/unit.c +@@ -2227,6 +2227,7 @@ static void unit_update_on_console(Unit *u) { + + static bool unit_process_job(Job *j, UnitActiveState ns, UnitNotifyFlags flags) { + bool unexpected = false; ++ JobResult result; + + assert(j); + +@@ -2249,8 +2250,16 @@ static bool unit_process_job(Job *j, UnitActiveState ns, UnitNotifyFlags flags) + else if (j->state == JOB_RUNNING && ns != UNIT_ACTIVATING) { + unexpected = true; + +- if (UNIT_IS_INACTIVE_OR_FAILED(ns)) +- job_finish_and_invalidate(j, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true, false); ++ if (UNIT_IS_INACTIVE_OR_FAILED(ns)) { ++ if (ns == UNIT_FAILED) ++ result = JOB_FAILED; ++ else if (FLAGS_SET(flags, UNIT_NOTIFY_SKIP_CONDITION)) ++ result = JOB_SKIPPED; ++ else ++ result = JOB_DONE; ++ ++ job_finish_and_invalidate(j, result, true, false); ++ } + } + + break; +@@ -5484,6 +5493,18 @@ void unit_log_failure(Unit *u, const char *result) { + "UNIT_RESULT=%s", result); + } + ++void unit_log_skip(Unit *u, const char *result) { ++ assert(u); ++ assert(result); ++ ++ log_struct(LOG_INFO, ++ "MESSAGE_ID=" SD_MESSAGE_UNIT_SKIPPED_STR, ++ LOG_UNIT_ID(u), ++ LOG_UNIT_INVOCATION_ID(u), ++ LOG_UNIT_MESSAGE(u, "Skipped due to '%s'.", result), ++ "UNIT_RESULT=%s", result); ++} ++ + static const char* const collect_mode_table[_COLLECT_MODE_MAX] = { + [COLLECT_INACTIVE] = "inactive", + [COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed", +diff --git a/src/core/unit.h b/src/core/unit.h +index 4ae1b38624..39179f5fd4 100644 +--- a/src/core/unit.h ++++ b/src/core/unit.h +@@ -658,6 +658,7 @@ int unit_kill_common(Unit *u, KillWho who, int signo, pid_t main_pid, pid_t cont + typedef enum UnitNotifyFlags { + UNIT_NOTIFY_RELOAD_FAILURE = 1 << 0, + UNIT_NOTIFY_WILL_AUTO_RESTART = 1 << 1, ++ UNIT_NOTIFY_SKIP_CONDITION = 1 << 2, + } UnitNotifyFlags; + + void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlags flags); +@@ -806,6 +807,9 @@ int unit_pid_attachable(Unit *unit, pid_t pid, sd_bus_error *error); + + void unit_log_success(Unit *u); + void unit_log_failure(Unit *u, const char *result); ++/* unit_log_skip is for cases like ExecCondition= where a unit is considered "done" ++ * after some execution, rather than succeeded or failed. */ ++void unit_log_skip(Unit *u, const char *result); + + /* Macros which append UNIT= or USER_UNIT= to the message */ + +diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c +index 8f3b463c6b..e0b2cfb170 100644 +--- a/src/shared/bus-unit-util.c ++++ b/src/shared/bus-unit-util.c +@@ -1334,7 +1334,7 @@ static int bus_append_service_property(sd_bus_message *m, const char *field, con + return bus_append_safe_atou(m, field, eq); + + if (STR_IN_SET(field, +- "ExecStartPre", "ExecStart", "ExecStartPost", ++ "ExecCondition", "ExecStartPre", "ExecStart", "ExecStartPost", + "ExecReload", "ExecStop", "ExecStopPost")) + + return bus_append_exec_command(m, field, eq); +diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h +index e7ef81b597..bdd4fd3974 100644 +--- a/src/systemd/sd-messages.h ++++ b/src/systemd/sd-messages.h +@@ -111,6 +111,8 @@ _SD_BEGIN_DECLARATIONS; + #define SD_MESSAGE_UNIT_FAILURE_RESULT SD_ID128_MAKE(d9,b3,73,ed,55,a6,4f,eb,82,42,e0,2d,be,79,a4,9c) + #define SD_MESSAGE_UNIT_FAILURE_RESULT_STR \ + SD_ID128_MAKE_STR(d9,b3,73,ed,55,a6,4f,eb,82,42,e0,2d,be,79,a4,9c) ++#define SD_MESSAGE_UNIT_SKIPPED SD_ID128_MAKE(0e,42,84,a0,ca,ca,4b,fc,81,c0,bb,67,86,97,26,73) ++#define SD_MESSAGE_UNIT_SKIPPED_STR SD_ID128_MAKE_STR(0e,42,84,a0,ca,ca,4b,fc,81,c0,bb,67,86,97,26,73) + + #define SD_MESSAGE_SPAWN_FAILED SD_ID128_MAKE(64,12,57,65,1c,1b,4e,c9,a8,62,4d,7a,40,a9,e1,e7) + #define SD_MESSAGE_SPAWN_FAILED_STR SD_ID128_MAKE_STR(64,12,57,65,1c,1b,4e,c9,a8,62,4d,7a,40,a9,e1,e7) +diff --git a/src/test/test-execute.c b/src/test/test-execute.c +index e42d0d30a8..882e866ea9 100644 +--- a/src/test/test-execute.c ++++ b/src/test/test-execute.c +@@ -30,7 +30,7 @@ + + typedef void (*test_function_t)(Manager *m); + +-static void check(const char *func, Manager *m, Unit *unit, int status_expected, int code_expected) { ++static void wait_for_service_finish(Manager *m, Unit *unit) { + Service *service = NULL; + usec_t ts; + usec_t timeout = 2 * USEC_PER_MINUTE; +@@ -55,6 +55,17 @@ static void check(const char *func, Manager *m, Unit *unit, int status_expected, + exit(EXIT_FAILURE); + } + } ++} ++ ++static void check_main_result(const char *func, Manager *m, Unit *unit, int status_expected, int code_expected) { ++ Service *service = NULL; ++ ++ assert_se(m); ++ assert_se(unit); ++ ++ wait_for_service_finish(m, unit); ++ ++ service = SERVICE(unit); + exec_status_dump(&service->main_exec_status, stdout, "\t"); + if (service->main_exec_status.status != status_expected) { + log_error("%s: %s: exit status %d, expected %d", +@@ -70,6 +81,25 @@ static void check(const char *func, Manager *m, Unit *unit, int status_expected, + } + } + ++static void check_service_result(const char *func, Manager *m, Unit *unit, ServiceResult result_expected) { ++ Service *service = NULL; ++ ++ assert_se(m); ++ assert_se(unit); ++ ++ wait_for_service_finish(m, unit); ++ ++ service = SERVICE(unit); ++ ++ if (service->result != result_expected) { ++ log_error("%s: %s: service end result %s, expected %s", ++ func, unit->id, ++ service_result_to_string(service->result), ++ service_result_to_string(result_expected)); ++ abort(); ++ } ++} ++ + static bool check_nobody_user_and_group(void) { + static int cache = -1; + struct passwd *p; +@@ -140,7 +170,17 @@ static void test(const char *func, Manager *m, const char *unit_name, int status + + assert_se(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit) >= 0); + assert_se(unit_start(unit) >= 0); +- check(func, m, unit, status_expected, code_expected); ++ check_main_result(func, m, unit, status_expected, code_expected); ++} ++ ++static void test_service(const char *func, Manager *m, const char *unit_name, ServiceResult result_expected) { ++ Unit *unit; ++ ++ assert_se(unit_name); ++ ++ assert_se(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit) >= 0); ++ assert_se(unit_start(unit) >= 0); ++ check_service_result(func, m, unit, result_expected); + } + + static void test_exec_bindpaths(Manager *m) { +@@ -669,6 +709,11 @@ static void test_exec_standardoutput_append(Manager *m) { + test(__func__, m, "exec-standardoutput-append.service", 0, CLD_EXITED); + } + ++static void test_exec_condition(Manager *m) { ++ test_service(__func__, m, "exec-condition-failed.service", SERVICE_FAILURE_EXIT_CODE); ++ test_service(__func__, m, "exec-condition-skip.service", SERVICE_SKIP_CONDITION); ++} ++ + typedef struct test_entry { + test_function_t f; + const char *name; +@@ -709,6 +754,7 @@ int main(int argc, char *argv[]) { + entry(test_exec_ambientcapabilities), + entry(test_exec_bindpaths), + entry(test_exec_capabilityboundingset), ++ entry(test_exec_condition), + entry(test_exec_cpuaffinity), + entry(test_exec_environment), + entry(test_exec_environmentfile), +diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service +index eab1820e20..9d0530df72 100644 +--- a/test/fuzz/fuzz-unit-file/directives.service ++++ b/test/fuzz/fuzz-unit-file/directives.service +@@ -83,6 +83,7 @@ DirectoryNotEmpty= + Documentation= + DynamicUser= + ExecReload= ++ExecCondition= + ExecStart= + ExecStartPost= + ExecStartPre= +diff --git a/test/meson.build b/test/meson.build +index 4d1c51048c..070731c4a9 100644 +--- a/test/meson.build ++++ b/test/meson.build +@@ -42,6 +42,8 @@ test_data_files = ''' + test-execute/exec-capabilityboundingset-merge.service + test-execute/exec-capabilityboundingset-reset.service + test-execute/exec-capabilityboundingset-simple.service ++ test-execute/exec-condition-failed.service ++ test-execute/exec-condition-skip.service + test-execute/exec-cpuaffinity1.service + test-execute/exec-cpuaffinity2.service + test-execute/exec-cpuaffinity3.service +diff --git a/test/test-execute/exec-condition-failed.service b/test/test-execute/exec-condition-failed.service +new file mode 100644 +index 0000000000..4a406dc17f +--- /dev/null ++++ b/test/test-execute/exec-condition-failed.service +@@ -0,0 +1,11 @@ ++[Unit] ++Description=Test for exec condition that fails the unit ++ ++[Service] ++Type=oneshot ++ ++# exit 255 will fail the unit ++ExecCondition=/bin/sh -c 'exit 255' ++ ++# This should not get run ++ExecStart=/bin/sh -c 'true' +diff --git a/test/test-execute/exec-condition-skip.service b/test/test-execute/exec-condition-skip.service +new file mode 100644 +index 0000000000..9450e8442a +--- /dev/null ++++ b/test/test-execute/exec-condition-skip.service +@@ -0,0 +1,15 @@ ++[Unit] ++Description=Test for exec condition that triggers skipping ++ ++[Service] ++Type=oneshot ++ ++# exit codes [1, 254] will result in skipping the rest of execution ++ExecCondition=/bin/sh -c 'exit 0' ++ExecCondition=/bin/sh -c 'exit 254' ++ ++# This would normally fail the unit but will not get run due to the skip above ++ExecCondition=/bin/sh -c 'exit 255' ++ ++# This should not get run ++ExecStart=/bin/sh -c 'true' diff --git a/SOURCES/0385-Drop-support-for-lz4-1.3.0.patch b/SOURCES/0385-Drop-support-for-lz4-1.3.0.patch new file mode 100644 index 0000000..0f14b3d --- /dev/null +++ b/SOURCES/0385-Drop-support-for-lz4-1.3.0.patch @@ -0,0 +1,75 @@ +From 09c96d5ef3f2b0bc4e5f1cf69e9b66248e325509 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Mon, 29 Oct 2018 18:32:51 +0100 +Subject: [PATCH] Drop support for lz4 < 1.3.0 + +lz4-r130 was released on May 29th, 2015. Let's drop the work-around for older +versions. In particular, we won't test any new code against those ancient +releases, so we shouldn't pretend they are supported. + +(cherry picked from commit e0a1d4b049e6991919a0eacd5d96f7f39dc6ddd1) +Resolves: #1843871 +--- + README | 2 +- + meson.build | 1 + + src/journal/compress.c | 4 ---- + src/journal/test-compress.c | 4 ---- + 4 files changed, 2 insertions(+), 9 deletions(-) + +diff --git a/README b/README +index 7d06e04800..859152fbde 100644 +--- a/README ++++ b/README +@@ -148,7 +148,7 @@ REQUIREMENTS: + libacl (optional) + libselinux (optional) + liblzma (optional) +- liblz4 >= 119 (optional) ++ liblz4 >= 1.3.0 / 130 (optional) + libgcrypt (optional) + libqrencode (optional) + libmicrohttpd (optional) +diff --git a/meson.build b/meson.build +index 70811c29cf..c8ae1e15bd 100644 +--- a/meson.build ++++ b/meson.build +@@ -1076,6 +1076,7 @@ conf.set10('HAVE_XZ', have) + want_lz4 = get_option('lz4') + if want_lz4 != 'false' and not fuzzer_build + liblz4 = dependency('liblz4', ++ version : '>= 1.3.0', + required : want_lz4 == 'true') + have = liblz4.found() + else +diff --git a/src/journal/compress.c b/src/journal/compress.c +index 6baf15c8ff..a4a5e63840 100644 +--- a/src/journal/compress.c ++++ b/src/journal/compress.c +@@ -95,11 +95,7 @@ int compress_blob_lz4(const void *src, uint64_t src_size, + if (src_size < 9) + return -ENOBUFS; + +-#if LZ4_VERSION_NUMBER >= 10700 + r = LZ4_compress_default(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8); +-#else +- r = LZ4_compress_limitedOutput(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8); +-#endif + if (r <= 0) + return -ENOBUFS; + +diff --git a/src/journal/test-compress.c b/src/journal/test-compress.c +index 791c6fdffb..eb3dc3eb6b 100644 +--- a/src/journal/test-compress.c ++++ b/src/journal/test-compress.c +@@ -207,11 +207,7 @@ static void test_lz4_decompress_partial(void) { + memset(huge, 'x', HUGE_SIZE); + memcpy(huge, "HUGE=", 5); + +-#if LZ4_VERSION_NUMBER >= 10700 + r = LZ4_compress_default(huge, buf, HUGE_SIZE, buf_size); +-#else +- r = LZ4_compress_limitedOutput(huge, buf, HUGE_SIZE, buf_size); +-#endif + assert_se(r >= 0); + compressed = r; + log_info("Compressed %i → %zu", HUGE_SIZE, compressed); diff --git a/SOURCES/0386-test-compress-add-test-for-short-decompress_startswi.patch b/SOURCES/0386-test-compress-add-test-for-short-decompress_startswi.patch new file mode 100644 index 0000000..2931a78 --- /dev/null +++ b/SOURCES/0386-test-compress-add-test-for-short-decompress_startswi.patch @@ -0,0 +1,72 @@ +From fc1e6209f622ff96c24259a50d98ca6f57a55426 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Mon, 29 Oct 2018 22:21:28 +0100 +Subject: [PATCH] test-compress: add test for short decompress_startswith calls + +I thought this might fail with lz4 < 1.8.3, but it seems that because of +greedy_realloc, we always use a buffer that is large enough, and it always +passes. + +(cherry picked from commit ba17efce44e6a1e139c1671205e9a6ed3824af1b) +Resolves: #1843871 +--- + src/journal/test-compress.c | 32 ++++++++++++++++++++++++++++++++ + 1 file changed, 32 insertions(+) + +diff --git a/src/journal/test-compress.c b/src/journal/test-compress.c +index eb3dc3eb6b..65cd3fbfeb 100644 +--- a/src/journal/test-compress.c ++++ b/src/journal/test-compress.c +@@ -131,6 +131,32 @@ static void test_decompress_startswith(int compression, + assert_se(r > 0); + } + ++static void test_decompress_startswith_short(int compression, ++ compress_blob_t compress, ++ decompress_sw_t decompress_sw) { ++ ++#define TEXT "HUGE=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ++ ++ char buf[1024]; ++ size_t i, csize; ++ int r; ++ ++ log_info("/* %s with %s */", __func__, object_compressed_to_string(compression)); ++ ++ r = compress(TEXT, sizeof TEXT, buf, sizeof buf, &csize); ++ assert_se(r == 0); ++ ++ for (i = 1; i < strlen(TEXT); i++) { ++ size_t alloc_size = i; ++ _cleanup_free_ void *buf2 = NULL; ++ ++ assert_se(buf2 = malloc(i)); ++ ++ assert_se(decompress_sw(buf, csize, &buf2, &alloc_size, TEXT, i, TEXT[i]) == 1); ++ assert_se(decompress_sw(buf, csize, &buf2, &alloc_size, TEXT, i, 'y') == 0); ++ } ++} ++ + static void test_compress_stream(int compression, + const char* cat, + compress_stream_t compress, +@@ -271,6 +297,9 @@ int main(int argc, char *argv[]) { + + test_compress_stream(OBJECT_COMPRESSED_XZ, "xzcat", + compress_stream_xz, decompress_stream_xz, srcfile); ++ ++ test_decompress_startswith_short(OBJECT_COMPRESSED_XZ, compress_blob_xz, decompress_startswith_xz); ++ + #else + log_info("/* XZ test skipped */"); + #endif +@@ -295,6 +324,9 @@ int main(int argc, char *argv[]) { + compress_stream_lz4, decompress_stream_lz4, srcfile); + + test_lz4_decompress_partial(); ++ ++ test_decompress_startswith_short(OBJECT_COMPRESSED_LZ4, compress_blob_lz4, decompress_startswith_lz4); ++ + #else + log_info("/* LZ4 test skipped */"); + #endif diff --git a/SOURCES/0387-journal-adapt-for-new-improved-LZ4_decompress_safe_p.patch b/SOURCES/0387-journal-adapt-for-new-improved-LZ4_decompress_safe_p.patch new file mode 100644 index 0000000..2b61c2c --- /dev/null +++ b/SOURCES/0387-journal-adapt-for-new-improved-LZ4_decompress_safe_p.patch @@ -0,0 +1,180 @@ +From 696d56fc75e72f47e4d3232a2140fac10b6b44de Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Mon, 29 Oct 2018 14:55:33 +0100 +Subject: [PATCH] journal: adapt for new improved LZ4_decompress_safe_partial() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +With lz4 1.8.3, this function can now decompress partial results into a smaller +buffer. The release news don't say anything interesting, but the test case that +was previously failing now works OK. + +Fixes #10259. + +A test is added. It shows that with *older* lz4, a partial decompression can +occur with the returned size smaller then the requested number of bytes _and_ +smaller then the size of the compressed data: + +(lz4-libs-1.8.2-1.fc29.x86_64) +Compressed 4194304 → 16464 +Decompressed → 4194304 +Decompressed partial 12/4194304 → 4194304 +Decompressed partial 1/1 → -2 (bad) +Decompressed partial 2/2 → -2 (bad) +Decompressed partial 3/3 → -2 (bad) +Decompressed partial 4/4 → -2 (bad) +Decompressed partial 5/5 → -2 (bad) +Decompressed partial 6/6 → 6 (good) +Decompressed partial 7/7 → 6 (good) +Decompressed partial 8/8 → 6 (good) +Decompressed partial 9/9 → 6 (good) +Decompressed partial 10/10 → 6 (good) +Decompressed partial 11/11 → 6 (good) +Decompressed partial 12/12 → 6 (good) +Decompressed partial 13/13 → 6 (good) +Decompressed partial 14/14 → 6 (good) +Decompressed partial 15/15 → 6 (good) +Decompressed partial 16/16 → 6 (good) +Decompressed partial 17/17 → 6 (good) +Decompressed partial 18/18 → -16459 (bad) + +(lz4-libs-1.8.3-1.fc29.x86_64) +Compressed 4194304 → 16464 +Decompressed → 4194304 +Decompressed partial 12/4194304 → 12 +Decompressed partial 1/1 → 1 (good) +Decompressed partial 2/2 → 2 (good) +Decompressed partial 3/3 → 3 (good) +Decompressed partial 4/4 → 4 (good) +... + +If we got such a short "successful" decompression in decompress_startswith() as +implemented before this patch, we could be confused and return a false negative +result. But it turns out that this only occurs with small output buffer +sizes. We use greedy_realloc() to manager the buffer, so it is always at least +64 bytes. I couldn't hit a case where decompress_startswith() would actually +return a bogus result. But since the lack of proof is not conclusive, the code +for *older* lz4 is changed too, just to be safe. We cannot rule out that on a +different architecture or with some unlucky compressed string we could hit this +corner case. + +The fallback code is guarded by a version check. The check uses a function not +the compile-time define, because there was no soversion bump in lz4 or new +symbols, and we could be compiled against a newer lz4 and linked at runtime +with an older one. (This happens routinely e.g. when somebody upgrades a subset +of distro packages.) + +(cherry picked from commit e41ef6fd0027d3619dc1cf062100b2d224d0ee7e) +Resolves: #1843871 +--- + src/journal/compress.c | 39 ++++++++++++++++++++++++------------- + src/journal/test-compress.c | 21 ++++++++++---------- + 2 files changed, 37 insertions(+), 23 deletions(-) + +diff --git a/src/journal/compress.c b/src/journal/compress.c +index a4a5e63840..e95ce2bcaa 100644 +--- a/src/journal/compress.c ++++ b/src/journal/compress.c +@@ -290,7 +290,6 @@ int decompress_startswith_lz4(const void *src, uint64_t src_size, + * prefix */ + + int r; +- size_t size; + + assert(src); + assert(src_size > 0); +@@ -307,23 +306,37 @@ int decompress_startswith_lz4(const void *src, uint64_t src_size, + + r = LZ4_decompress_safe_partial((char*)src + 8, *buffer, src_size - 8, + prefix_len + 1, *buffer_size); +- if (r >= 0) +- size = (unsigned) r; +- else { +- /* lz4 always tries to decode full "sequence", so in +- * pathological cases might need to decompress the +- * full field. */ ++ /* One lz4 < 1.8.3, we might get "failure" (r < 0), or "success" where ++ * just a part of the buffer is decompressed. But if we get a smaller ++ * amount of bytes than requested, we don't know whether there isn't enough ++ * data to fill the requested size or whether we just got a partial answer. ++ */ ++ if (r < 0 || (size_t) r < prefix_len + 1) { ++ size_t size; ++ ++ if (LZ4_versionNumber() >= 10803) ++ /* We trust that the newer lz4 decompresses the number of bytes we ++ * requested if available in the compressed string. */ ++ return 0; ++ ++ if (r > 0) ++ /* Compare what we have first, in case of mismatch we can ++ * shortcut the full comparison. */ ++ if (memcmp(*buffer, prefix, r) != 0) ++ return 0; ++ ++ /* Before version 1.8.3, lz4 always tries to decode full a "sequence", ++ * so in pathological cases might need to decompress the full field. */ + r = decompress_blob_lz4(src, src_size, buffer, buffer_size, &size, 0); + if (r < 0) + return r; +- } + +- if (size >= prefix_len + 1) +- return memcmp(*buffer, prefix, prefix_len) == 0 && +- ((const uint8_t*) *buffer)[prefix_len] == extra; +- else +- return 0; ++ if (size < prefix_len + 1) ++ return 0; ++ } + ++ return memcmp(*buffer, prefix, prefix_len) == 0 && ++ ((const uint8_t*) *buffer)[prefix_len] == extra; + #else + return -EPROTONOSUPPORT; + #endif +diff --git a/src/journal/test-compress.c b/src/journal/test-compress.c +index 65cd3fbfeb..f60c4ae3d7 100644 +--- a/src/journal/test-compress.c ++++ b/src/journal/test-compress.c +@@ -223,13 +223,13 @@ static void test_compress_stream(int compression, + + #if HAVE_LZ4 + static void test_lz4_decompress_partial(void) { +- char buf[20000]; ++ char buf[20000], buf2[100]; + size_t buf_size = sizeof(buf), compressed; + int r; + _cleanup_free_ char *huge = NULL; + + #define HUGE_SIZE (4096*1024) +- huge = malloc(HUGE_SIZE); ++ assert_se(huge = malloc(HUGE_SIZE)); + memset(huge, 'x', HUGE_SIZE); + memcpy(huge, "HUGE=", 5); + +@@ -248,14 +248,15 @@ static void test_lz4_decompress_partial(void) { + assert_se(r >= 0); + log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE, r); + +- /* We expect this to fail, because that's how current lz4 works. If this +- * call succeeds, then lz4 has been fixed, and we need to change our code. +- */ +- r = LZ4_decompress_safe_partial(buf, huge, +- compressed, +- 12, HUGE_SIZE-1); +- assert_se(r < 0); +- log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE-1, r); ++ for (size_t size = 1; size < sizeof(buf2); size++) { ++ /* This failed in older lz4s but works in newer ones. */ ++ r = LZ4_decompress_safe_partial(buf, buf2, compressed, size, size); ++ log_info("Decompressed partial %zu/%zu → %i (%s)", size, size, r, ++ r < 0 ? "bad" : "good"); ++ if (r >= 0 && LZ4_versionNumber() >= 10803) ++ /* lz4 <= 1.8.2 should fail that test, let's only check for newer ones */ ++ assert_se(memcmp(buf2, huge, r) == 0); ++ } + } + #endif + diff --git a/SOURCES/0388-fuzz-compress-add-fuzzer-for-compression-and-decompr.patch b/SOURCES/0388-fuzz-compress-add-fuzzer-for-compression-and-decompr.patch new file mode 100644 index 0000000..cccc17a --- /dev/null +++ b/SOURCES/0388-fuzz-compress-add-fuzzer-for-compression-and-decompr.patch @@ -0,0 +1,118 @@ +From 242273e1afd456e86ebc48d7d601cb28297f8efb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Tue, 30 Oct 2018 09:02:26 +0100 +Subject: [PATCH] fuzz-compress: add fuzzer for compression and decompression + +(cherry picked from commit 029427043b2e0523a21f54374f872b23cf744350) +Resolves: #1843871 +--- + src/fuzz/fuzz-compress.c | 80 ++++++++++++++++++++++++++++++++++++++++ + src/fuzz/meson.build | 7 +++- + 2 files changed, 86 insertions(+), 1 deletion(-) + create mode 100644 src/fuzz/fuzz-compress.c + +diff --git a/src/fuzz/fuzz-compress.c b/src/fuzz/fuzz-compress.c +new file mode 100644 +index 0000000000..9c5dfc92c0 +--- /dev/null ++++ b/src/fuzz/fuzz-compress.c +@@ -0,0 +1,80 @@ ++/* SPDX-License-Identifier: LGPL-2.1+ */ ++ ++#include ++ ++#include "alloc-util.h" ++#include "compress.h" ++#include "fuzz.h" ++ ++static int compress(int alg, ++ const void *src, uint64_t src_size, ++ void *dst, size_t dst_alloc_size, size_t *dst_size) { ++ ++ if (alg == OBJECT_COMPRESSED_LZ4) ++ return compress_blob_lz4(src, src_size, dst, dst_alloc_size, dst_size); ++ if (alg == OBJECT_COMPRESSED_XZ) ++ return compress_blob_xz(src, src_size, dst, dst_alloc_size, dst_size); ++ return -EOPNOTSUPP; ++} ++ ++typedef struct header { ++ uint32_t alg:2; /* We have only two compression algorithms so far, but we might add ++ * more in the future. Let's make this a bit wider so our fuzzer ++ * cases remain stable in the future. */ ++ uint32_t sw_len; ++ uint32_t sw_alloc; ++ uint32_t reserved[3]; /* Extra space to keep fuzz cases stable in case we need to ++ * add stuff in the future. */ ++ uint8_t data[]; ++} header; ++ ++int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { ++ _cleanup_free_ void *buf = NULL, *buf2 = NULL; ++ int r; ++ ++ if (size < offsetof(header, data) + 1) ++ return 0; ++ ++ const header *h = (struct header*) data; ++ const size_t data_len = size - offsetof(header, data); ++ ++ int alg = h->alg; ++ ++ /* We don't want to fill the logs with messages about parse errors. ++ * Disable most logging if not running standalone */ ++ if (!getenv("SYSTEMD_LOG_LEVEL")) ++ log_set_max_level(LOG_CRIT); ++ ++ log_info("Using compression %s, data size=%zu", ++ object_compressed_to_string(alg) ?: "(none)", ++ data_len); ++ ++ buf = malloc(MAX(size, 128u)); /* Make the buffer a bit larger for very small data */ ++ if (!buf) { ++ log_oom(); ++ return 0; ++ } ++ ++ size_t csize; ++ r = compress(alg, h->data, data_len, buf, size, &csize); ++ if (r < 0) { ++ log_error_errno(r, "Compression failed: %m"); ++ return 0; ++ } ++ ++ log_debug("Compressed %zu bytes to → %zu bytes", data_len, csize); ++ ++ size_t sw_alloc = MAX(h->sw_alloc, 1u); ++ buf2 = malloc(sw_alloc); ++ if (!buf) { ++ log_oom(); ++ return 0; ++ } ++ ++ size_t sw_len = MIN(data_len - 1, h->sw_len); ++ ++ r = decompress_startswith(alg, buf, csize, &buf2, &sw_alloc, h->data, sw_len, h->data[sw_len]); ++ assert_se(r > 0); ++ ++ return 0; ++} +diff --git a/src/fuzz/meson.build b/src/fuzz/meson.build +index 5315d2771c..b8d5979d3c 100644 +--- a/src/fuzz/meson.build ++++ b/src/fuzz/meson.build +@@ -73,8 +73,13 @@ fuzzers += [ + [libsystemd_journal_remote, + libshared], + []], ++ + [['src/fuzz/fuzz-fido-id-desc.c', + 'src/udev/fido_id/fido_id_desc.c'], + [], +- []] ++ []], ++ ++ [['src/fuzz/fuzz-compress.c'], ++ [libshared], ++ []], + ] diff --git a/SOURCES/0389-seccomp-fix-__NR__sysctl-usage.patch b/SOURCES/0389-seccomp-fix-__NR__sysctl-usage.patch new file mode 100644 index 0000000..7d67d8f --- /dev/null +++ b/SOURCES/0389-seccomp-fix-__NR__sysctl-usage.patch @@ -0,0 +1,35 @@ +From 65a066aae68744e889c114cee56dff5b48d872df Mon Sep 17 00:00:00 2001 +From: Jan Synacek +Date: Thu, 4 Jun 2020 16:55:52 +0200 +Subject: [PATCH] seccomp: fix __NR__sysctl usage + +Loosely based on +https://github.com/systemd/systemd/pull/14032 and +https://github.com/systemd/systemd/pull/14268. + +Related: #1843871 +--- + src/test/test-seccomp.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/test/test-seccomp.c b/src/test/test-seccomp.c +index 4021a06e0e..009a2e1922 100644 +--- a/src/test/test-seccomp.c ++++ b/src/test/test-seccomp.c +@@ -237,14 +237,14 @@ static void test_protect_sysctl(void) { + assert_se(pid >= 0); + + if (pid == 0) { +-#if __NR__sysctl > 0 ++#if defined __NR__sysctl && __NR__sysctl >= 0 + assert_se(syscall(__NR__sysctl, NULL) < 0); + assert_se(errno == EFAULT); + #endif + + assert_se(seccomp_protect_sysctl() >= 0); + +-#if __NR__sysctl > 0 ++#if defined __NR__sysctl && __NR__sysctl >= 0 + assert_se(syscall(__NR__sysctl, 0, 0, 0) < 0); + assert_se(errno == EPERM); + #endif diff --git a/SOURCES/0390-tmpfiles-fix-crash-with-NULL-in-arg_root-and-other-f.patch b/SOURCES/0390-tmpfiles-fix-crash-with-NULL-in-arg_root-and-other-f.patch new file mode 100644 index 0000000..8ae24a5 --- /dev/null +++ b/SOURCES/0390-tmpfiles-fix-crash-with-NULL-in-arg_root-and-other-f.patch @@ -0,0 +1,174 @@ +From 3569b29eb8b082229dd97b8aae60bbe4d2f96ef5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Wed, 19 Dec 2018 23:05:48 +0100 +Subject: [PATCH] tmpfiles: fix crash with NULL in arg_root and other fixes and + tests + +The function to replacement paths into the configuration file list was borked. +Apart from the crash with empty root prefix, it would incorrectly handle the +case where root *was* set, and the replacement file was supposed to override +an existing file. + +prefix_root is used instead of path_join because prefix_root removes duplicate +slashes (when --root=dir/ is used). + +A test is added. + +Fixes #11124. + +(cherry picked from commit 082bb1c59bd4300bcdc08488c94109680cfadf57) + +Resolves: #1836024 +--- + src/basic/conf-files.c | 21 ++++++++----- + src/test/test-conf-files.c | 61 +++++++++++++++++++++++++++++++++++++- + 2 files changed, 73 insertions(+), 9 deletions(-) + +diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c +index d6ef0e941e..5ca83091c9 100644 +--- a/src/basic/conf-files.c ++++ b/src/basic/conf-files.c +@@ -204,14 +204,17 @@ int conf_files_insert(char ***strv, const char *root, char **dirs, const char *p + if (c == 0) { + char **dir; + +- /* Oh, we found our spot and it already contains something. */ ++ /* Oh, there already is an entry with a matching name (the last component). */ ++ + STRV_FOREACH(dir, dirs) { ++ _cleanup_free_ char *rdir = NULL; + char *p1, *p2; + +- p1 = path_startswith((*strv)[i], root); +- if (p1) +- /* Skip "/" in *dir, because p1 is without "/" too */ +- p1 = path_startswith(p1, *dir + 1); ++ rdir = prefix_root(root, *dir); ++ if (!rdir) ++ return -ENOMEM; ++ ++ p1 = path_startswith((*strv)[i], rdir); + if (p1) + /* Existing entry with higher priority + * or same priority, no need to do anything. */ +@@ -220,7 +223,8 @@ int conf_files_insert(char ***strv, const char *root, char **dirs, const char *p + p2 = path_startswith(path, *dir); + if (p2) { + /* Our new entry has higher priority */ +- t = path_join(root, path, NULL); ++ ++ t = prefix_root(root, path); + if (!t) + return log_oom(); + +@@ -236,7 +240,8 @@ int conf_files_insert(char ***strv, const char *root, char **dirs, const char *p + /* … we are not there yet, let's continue */ + } + +- t = path_join(root, path, NULL); ++ /* The new file has lower priority than all the existing entries */ ++ t = prefix_root(root, path); + if (!t) + return log_oom(); + +@@ -322,7 +327,7 @@ int conf_files_list_with_replacement( + if (r < 0) + return log_error_errno(r, "Failed to extend config file list: %m"); + +- p = path_join(root, replacement, NULL); ++ p = prefix_root(root, replacement); + if (!p) + return log_oom(); + } +diff --git a/src/test/test-conf-files.c b/src/test/test-conf-files.c +index 2ec2dfc261..5789767161 100644 +--- a/src/test/test-conf-files.c ++++ b/src/test/test-conf-files.c +@@ -13,6 +13,7 @@ + #include "macro.h" + #include "mkdir.h" + #include "parse-util.h" ++#include "path-util.h" + #include "rm-rf.h" + #include "string-util.h" + #include "strv.h" +@@ -42,7 +43,7 @@ static void test_conf_files_list(bool use_root) { + _cleanup_strv_free_ char **found_files = NULL, **found_files2 = NULL; + const char *root_dir, *search_1, *search_2, *expect_a, *expect_b, *expect_c, *mask; + +- log_debug("/* %s */", __func__); ++ log_debug("/* %s(%s) */", __func__, yes_no(use_root)); + + setup_test_dir(tmp_dir, + "/dir1/a.conf", +@@ -92,6 +93,60 @@ static void test_conf_files_list(bool use_root) { + assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); + } + ++static void test_conf_files_insert(const char *root) { ++ _cleanup_strv_free_ char **s = NULL; ++ ++ log_info("/* %s root=%s */", __func__, strempty(root)); ++ ++ char **dirs = STRV_MAKE("/dir1", "/dir2", "/dir3"); ++ ++ _cleanup_free_ const char ++ *foo1 = prefix_root(root, "/dir1/foo.conf"), ++ *foo2 = prefix_root(root, "/dir2/foo.conf"), ++ *bar2 = prefix_root(root, "/dir2/bar.conf"), ++ *zzz3 = prefix_root(root, "/dir3/zzz.conf"), ++ *whatever = prefix_root(root, "/whatever.conf"); ++ ++ assert_se(conf_files_insert(&s, root, dirs, "/dir2/foo.conf") == 0); ++ assert_se(strv_equal(s, STRV_MAKE(foo2))); ++ ++ /* The same file again, https://github.com/systemd/systemd/issues/11124 */ ++ assert_se(conf_files_insert(&s, root, dirs, "/dir2/foo.conf") == 0); ++ assert_se(strv_equal(s, STRV_MAKE(foo2))); ++ ++ /* Lower priority → new entry is ignored */ ++ assert_se(conf_files_insert(&s, root, dirs, "/dir3/foo.conf") == 0); ++ assert_se(strv_equal(s, STRV_MAKE(foo2))); ++ ++ /* Higher priority → new entry replaces */ ++ assert_se(conf_files_insert(&s, root, dirs, "/dir1/foo.conf") == 0); ++ assert_se(strv_equal(s, STRV_MAKE(foo1))); ++ ++ /* Earlier basename */ ++ assert_se(conf_files_insert(&s, root, dirs, "/dir2/bar.conf") == 0); ++ assert_se(strv_equal(s, STRV_MAKE(bar2, foo1))); ++ ++ /* Later basename */ ++ assert_se(conf_files_insert(&s, root, dirs, "/dir3/zzz.conf") == 0); ++ assert_se(strv_equal(s, STRV_MAKE(bar2, foo1, zzz3))); ++ ++ /* All lower priority → all ignored */ ++ assert_se(conf_files_insert(&s, root, dirs, "/dir3/zzz.conf") == 0); ++ assert_se(conf_files_insert(&s, root, dirs, "/dir2/bar.conf") == 0); ++ assert_se(conf_files_insert(&s, root, dirs, "/dir3/bar.conf") == 0); ++ assert_se(conf_files_insert(&s, root, dirs, "/dir2/foo.conf") == 0); ++ assert_se(strv_equal(s, STRV_MAKE(bar2, foo1, zzz3))); ++ ++ /* Two entries that don't match any of the directories, but match basename */ ++ assert_se(conf_files_insert(&s, root, dirs, "/dir4/zzz.conf") == 0); ++ assert_se(conf_files_insert(&s, root, dirs, "/zzz.conf") == 0); ++ assert_se(strv_equal(s, STRV_MAKE(bar2, foo1, zzz3))); ++ ++ /* An entry that doesn't match any of the directories, no match at all */ ++ assert_se(conf_files_insert(&s, root, dirs, "/whatever.conf") == 0); ++ assert_se(strv_equal(s, STRV_MAKE(bar2, foo1, whatever, zzz3))); ++} ++ + int main(int argc, char **argv) { + log_set_max_level(LOG_DEBUG); + log_parse_environment(); +@@ -99,5 +154,9 @@ int main(int argc, char **argv) { + + test_conf_files_list(false); + test_conf_files_list(true); ++ test_conf_files_insert(NULL); ++ test_conf_files_insert("/root"); ++ test_conf_files_insert("/root/"); ++ + return 0; + } diff --git a/SOURCES/0391-sulogin-shell-Use-force-if-SYSTEMD_SULOGIN_FORCE-set.patch b/SOURCES/0391-sulogin-shell-Use-force-if-SYSTEMD_SULOGIN_FORCE-set.patch new file mode 100644 index 0000000..31783e0 --- /dev/null +++ b/SOURCES/0391-sulogin-shell-Use-force-if-SYSTEMD_SULOGIN_FORCE-set.patch @@ -0,0 +1,85 @@ +From d8ae33a302f01601e9e98b4aca3516e93c634a54 Mon Sep 17 00:00:00 2001 +From: Andreas Henriksson +Date: Sun, 14 Oct 2018 14:53:09 +0200 +Subject: [PATCH] sulogin-shell: Use force if SYSTEMD_SULOGIN_FORCE set + +When the root account is locked sulogin will either inform you of +this and not allow you in or if --force is used it will hand +you passwordless root (if using a recent enough version of util-linux). + +Not being allowed a shell is ofcourse inconvenient, but at the same +time handing out passwordless root unconditionally is probably not +a good idea everywhere. + +This patch thus allows to control which behaviour you want by +setting the SYSTEMD_SULOGIN_FORCE environment variable to true +or false to control the behaviour, eg. via adding this to +'systemctl edit rescue.service' (or emergency.service): + +[Service] +Environment=SYSTEMD_SULOGIN_FORCE=1 + +Distributions who used locked root accounts and want the passwordless +behaviour could thus simply drop in the override file in +/etc/systemd/system/rescue.service.d/override.conf + +Fixes: #7115 +Addresses: https://bugs.debian.org/802211 +(cherry picked from commit 33eb44fe4a8d7971b5614bc4c2d90f8d91cce66c) + +Resolves: #1625929 +--- + doc/ENVIRONMENT.md | 6 ++++++ + src/sulogin-shell/sulogin-shell.c | 11 ++++++++++- + 2 files changed, 16 insertions(+), 1 deletion(-) + +diff --git a/doc/ENVIRONMENT.md b/doc/ENVIRONMENT.md +index 1e648be640..39a36a52cc 100644 +--- a/doc/ENVIRONMENT.md ++++ b/doc/ENVIRONMENT.md +@@ -101,3 +101,9 @@ systemd-timedated: + NTP client services. If set, `timedatectl set-ntp on` enables and starts the + first existing unit listed in the environment variable, and + `timedatectl set-ntp off` disables and stops all listed units. ++ ++systemd-sulogin-shell: ++ ++* `$SYSTEMD_SULOGIN_FORCE=1` — This skips asking for the root password if the ++ root password is not available (such as when the root account is locked). ++ See `sulogin(8)` for more details. +diff --git a/src/sulogin-shell/sulogin-shell.c b/src/sulogin-shell/sulogin-shell.c +index 5db3592d6f..a1ea2333de 100644 +--- a/src/sulogin-shell/sulogin-shell.c ++++ b/src/sulogin-shell/sulogin-shell.c +@@ -9,6 +9,7 @@ + #include "bus-util.h" + #include "bus-error.h" + #include "def.h" ++#include "env-util.h" + #include "log.h" + #include "process-util.h" + #include "sd-bus.h" +@@ -89,7 +90,11 @@ static void print_mode(const char* mode) { + } + + int main(int argc, char *argv[]) { +- static const char* const sulogin_cmdline[] = {SULOGIN, NULL}; ++ const char* sulogin_cmdline[] = { ++ SULOGIN, ++ NULL, /* --force */ ++ NULL ++ }; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + +@@ -99,6 +104,10 @@ int main(int argc, char *argv[]) { + + print_mode(argc > 1 ? argv[1] : ""); + ++ if (getenv_bool("SYSTEMD_SULOGIN_FORCE") > 0) ++ /* allows passwordless logins if root account is locked. */ ++ sulogin_cmdline[1] = "--force"; ++ + (void) fork_wait(sulogin_cmdline); + + r = bus_connect_system_systemd(&bus); diff --git a/SOURCES/0392-resolvconf-fixes-for-the-compatibility-interface.patch b/SOURCES/0392-resolvconf-fixes-for-the-compatibility-interface.patch new file mode 100644 index 0000000..636b585 --- /dev/null +++ b/SOURCES/0392-resolvconf-fixes-for-the-compatibility-interface.patch @@ -0,0 +1,62 @@ +From 9b7aa39e7db5a6446d3c034741e64cda1a9dd200 Mon Sep 17 00:00:00 2001 +From: Filipe Brandenburger +Date: Mon, 25 Jun 2018 18:07:48 -0700 +Subject: [PATCH] resolvconf: fixes for the compatibility interface + +Also use compat_main() when called as `resolvconf`, since the interface +is closer to that of `systemd-resolve`. + +Use a heap allocated string to set arg_ifname, since a stack allocated +one would be lost after the function returns. (This last one broke the +case where an interface name was suffixed with a dot, such as in +`resolvconf -a tap0.dhcp`.) + +Tested: + $ build/resolvconf -a nonexistent.abc +Date: Tue, 17 Mar 2020 10:49:44 +0100 +Subject: [PATCH] mount: don't add Requires for tmp.mount + +This is a follow-up to #1619292. + +rhel-only +Resolves: #1748840 +--- + src/core/mount.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/core/mount.c b/src/core/mount.c +index 30aaf5ae55..2746372db2 100644 +--- a/src/core/mount.c ++++ b/src/core/mount.c +@@ -305,7 +305,7 @@ static int mount_add_mount_dependencies(Mount *m) { + if (r < 0) + return r; + +- if (UNIT(m)->fragment_path) { ++ if (UNIT(m)->fragment_path && !streq(UNIT(m)->id, "tmp.mount")) { + /* If we have fragment configuration, then make this dependency required */ + r = unit_add_dependency(other, UNIT_REQUIRES, UNIT(m), true, UNIT_DEPENDENCY_PATH); + if (r < 0) diff --git a/SOURCES/0394-core-coldplug-possible-nop_job.patch b/SOURCES/0394-core-coldplug-possible-nop_job.patch new file mode 100644 index 0000000..f133544 --- /dev/null +++ b/SOURCES/0394-core-coldplug-possible-nop_job.patch @@ -0,0 +1,55 @@ +From 4a1405998671caaaad5b24d4cef309c05175b1c1 Mon Sep 17 00:00:00 2001 +From: ypf791 +Date: Fri, 19 Jul 2019 18:28:04 +0800 +Subject: [PATCH] core: coldplug possible nop_job + +When a unit in a state INACTIVE or DEACTIVATING, JobType JOB_TRY_RESTART or +JOB_TRY_RELOAD will be collapsed to JOB_NOP. And use u->nop_job instead +of u->job. + +If a JOB_NOP job is going on with a waiting state, a parallel daemon-reload +just install it during deserialization. Without a coldplug, the job will +not be in m->run_queue, which results in a hung try-restart or +try-reload process. + +Reproduce: + + run systemctl try-restart test.servcie (inactive) repeatly in a terminal. + run systemctl daemon-reload repeatly in other terminals. + +After successful reproduce, systemctl list-jobs will list the hang job. + +Upsteam: +systemd/systemd#13124 + +(cherry picked from commit b49e14d5f3081dfcd363d8199a14c0924ae9152f) + +Resolves: #1829798 +--- + src/core/unit.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/src/core/unit.c b/src/core/unit.c +index 61799bf9e3..f57260727f 100644 +--- a/src/core/unit.c ++++ b/src/core/unit.c +@@ -3696,6 +3696,7 @@ int unit_add_node_dependency(Unit *u, const char *what, bool wants, UnitDependen + int unit_coldplug(Unit *u) { + int r = 0, q; + char **i; ++ Job *uj; + + assert(u); + +@@ -3718,8 +3719,9 @@ int unit_coldplug(Unit *u) { + r = q; + } + +- if (u->job) { +- q = job_coldplug(u->job); ++ uj = u->job ?: u->nop_job; ++ if (uj) { ++ q = job_coldplug(uj); + if (q < 0 && r >= 0) + r = q; + } diff --git a/SOURCES/0395-core-add-IODeviceLatencyTargetSec.patch b/SOURCES/0395-core-add-IODeviceLatencyTargetSec.patch new file mode 100644 index 0000000..974a1d9 --- /dev/null +++ b/SOURCES/0395-core-add-IODeviceLatencyTargetSec.patch @@ -0,0 +1,584 @@ +From 2240e7955d64260e94dd52a3ab9855d267c2af89 Mon Sep 17 00:00:00 2001 +From: Tejun Heo +Date: Wed, 13 Jun 2018 14:16:35 -0700 +Subject: [PATCH] core: add IODeviceLatencyTargetSec + +This adds support for the following proposed latency based IO control +mechanism. + + https://lkml.org/lkml/2018/6/5/428 + +(cherry picked from commit 6ae4283cb14c4e4a895f4bbba703804e4128c86c) + +Resolves: #1831519 +--- + man/systemd.resource-control.xml | 29 +++++-- + src/core/cgroup.c | 56 +++++++++++-- + src/core/cgroup.h | 9 +++ + src/core/dbus-cgroup.c | 111 ++++++++++++++++++++++++++ + src/core/load-fragment-gperf.gperf.m4 | 1 + + src/core/load-fragment.c | 72 +++++++++++++++++ + src/core/load-fragment.h | 1 + + src/shared/bus-unit-util.c | 31 +++++++ + src/systemctl/systemctl.c | 22 +++++ + 9 files changed, 320 insertions(+), 12 deletions(-) + +diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml +index 4329742e94..b0064bf98f 100644 +--- a/man/systemd.resource-control.xml ++++ b/man/systemd.resource-control.xml +@@ -417,11 +417,11 @@ + + Set the per-device overall block I/O weight for the executed processes, if the unified control group + hierarchy is used on the system. Takes a space-separated pair of a file path and a weight value to specify +- the device specific weight value, between 1 and 10000. (Example: "/dev/sda 1000"). The file path may be +- specified as path to a block device node or as any other file, in which case the backing block device of the +- file system of the file is determined. This controls the io.weight control group +- attribute, which defaults to 100. Use this option multiple times to set weights for multiple devices. For +- details about this control group attribute, see /dev/sda 1000). The file ++ path may be specified as path to a block device node or as any other file, in which case the backing block ++ device of the file system of the file is determined. This controls the io.weight control ++ group attribute, which defaults to 100. Use this option multiple times to set weights for multiple devices. ++ For details about this control group attribute, see cgroup-v2.txt. + + Implies IOAccounting=true. +@@ -482,6 +482,25 @@ + + + ++ ++ IODeviceLatencyTargetSec=device target ++ ++ ++ Set the per-device average target I/O latency for the executed processes, if the unified control group ++ hierarchy is used on the system. Takes a file path and a timespan separated by a space to specify ++ the device specific latency target. (Example: "/dev/sda 25ms"). The file path may be specified ++ as path to a block device node or as any other file, in which case the backing block device of the file ++ system of the file is determined. This controls the io.latency control group ++ attribute. Use this option multiple times to set latency target for multiple devices. For details about this ++ control group attribute, see cgroup-v2.txt. ++ ++ Implies IOAccounting=true. ++ ++ These settings are supported only if the unified control group hierarchy is used. ++ ++ ++ + + IPAccounting= + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index 9e4c3c7dac..ad8219bd79 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -114,6 +114,15 @@ void cgroup_context_free_io_device_weight(CGroupContext *c, CGroupIODeviceWeight + free(w); + } + ++void cgroup_context_free_io_device_latency(CGroupContext *c, CGroupIODeviceLatency *l) { ++ assert(c); ++ assert(l); ++ ++ LIST_REMOVE(device_latencies, c->io_device_latencies, l); ++ free(l->path); ++ free(l); ++} ++ + void cgroup_context_free_io_device_limit(CGroupContext *c, CGroupIODeviceLimit *l) { + assert(c); + assert(l); +@@ -147,6 +156,9 @@ void cgroup_context_done(CGroupContext *c) { + while (c->io_device_weights) + cgroup_context_free_io_device_weight(c, c->io_device_weights); + ++ while (c->io_device_latencies) ++ cgroup_context_free_io_device_latency(c, c->io_device_latencies); ++ + while (c->io_device_limits) + cgroup_context_free_io_device_limit(c, c->io_device_limits); + +@@ -171,6 +183,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + _cleanup_free_ char *cpuset_mems = NULL; + CGroupIODeviceLimit *il; + CGroupIODeviceWeight *iw; ++ CGroupIODeviceLatency *l; + CGroupBlockIODeviceBandwidth *b; + CGroupBlockIODeviceWeight *w; + CGroupDeviceAllow *a; +@@ -256,11 +269,18 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + + LIST_FOREACH(device_weights, iw, c->io_device_weights) + fprintf(f, +- "%sIODeviceWeight=%s %" PRIu64, ++ "%sIODeviceWeight=%s %" PRIu64 "\n", + prefix, + iw->path, + iw->weight); + ++ LIST_FOREACH(device_latencies, l, c->io_device_latencies) ++ fprintf(f, ++ "%sIODeviceLatencyTargetSec=%s %s\n", ++ prefix, ++ l->path, ++ format_timespan(u, sizeof(u), l->target_usec, 1)); ++ + LIST_FOREACH(device_limits, il, c->io_device_limits) { + char buf[FORMAT_BYTES_MAX]; + CGroupIOLimitType type; +@@ -573,6 +593,7 @@ static bool cgroup_context_has_io_config(CGroupContext *c) { + c->io_weight != CGROUP_WEIGHT_INVALID || + c->startup_io_weight != CGROUP_WEIGHT_INVALID || + c->io_device_weights || ++ c->io_device_latencies || + c->io_device_limits; + } + +@@ -646,6 +667,26 @@ static void cgroup_apply_blkio_device_weight(Unit *u, const char *dev_path, uint + "Failed to set blkio.weight_device: %m"); + } + ++static void cgroup_apply_io_device_latency(Unit *u, const char *dev_path, usec_t target) { ++ char buf[DECIMAL_STR_MAX(dev_t)*2+2+7+DECIMAL_STR_MAX(uint64_t)+1]; ++ dev_t dev; ++ int r; ++ ++ r = lookup_block_device(dev_path, &dev); ++ if (r < 0) ++ return; ++ ++ if (target != USEC_INFINITY) ++ xsprintf(buf, "%u:%u target=%" PRIu64 "\n", major(dev), minor(dev), target); ++ else ++ xsprintf(buf, "%u:%u target=max\n", major(dev), minor(dev)); ++ ++ r = cg_set_attribute("io", u->cgroup_path, "io.latency", buf); ++ if (r < 0) ++ log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, ++ "Failed to set io.latency on cgroup %s: %m", u->cgroup_path); ++} ++ + static void cgroup_apply_io_device_limit(Unit *u, const char *dev_path, uint64_t *limits) { + char limit_bufs[_CGROUP_IO_LIMIT_TYPE_MAX][DECIMAL_STR_MAX(uint64_t)]; + char buf[DECIMAL_STR_MAX(dev_t)*2+2+(6+DECIMAL_STR_MAX(uint64_t)+1)*4]; +@@ -827,13 +868,11 @@ static void cgroup_context_apply( + if (has_io) { + CGroupIODeviceWeight *w; + +- /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->io_device_weights) + cgroup_apply_io_device_weight(u, w->path, w->weight); + } else if (has_blockio) { + CGroupBlockIODeviceWeight *w; + +- /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->blockio_device_weights) { + weight = cgroup_weight_blkio_to_io(w->weight); + +@@ -843,9 +882,15 @@ static void cgroup_context_apply( + cgroup_apply_io_device_weight(u, w->path, weight); + } + } ++ ++ if (has_io) { ++ CGroupIODeviceLatency *l; ++ ++ LIST_FOREACH(device_latencies, l, c->io_device_latencies) ++ cgroup_apply_io_device_latency(u, l->path, l->target_usec); ++ } + } + +- /* Apply limits and free ones without config. */ + if (has_io) { + CGroupIODeviceLimit *l; + +@@ -902,7 +947,6 @@ static void cgroup_context_apply( + if (has_io) { + CGroupIODeviceWeight *w; + +- /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->io_device_weights) { + weight = cgroup_weight_io_to_blkio(w->weight); + +@@ -914,13 +958,11 @@ static void cgroup_context_apply( + } else if (has_blockio) { + CGroupBlockIODeviceWeight *w; + +- /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->blockio_device_weights) + cgroup_apply_blkio_device_weight(u, w->path, w->weight); + } + } + +- /* Apply limits and free ones without config. */ + if (has_io) { + CGroupIODeviceLimit *l; + +diff --git a/src/core/cgroup.h b/src/core/cgroup.h +index da10575394..f7365b4c46 100644 +--- a/src/core/cgroup.h ++++ b/src/core/cgroup.h +@@ -13,6 +13,7 @@ typedef struct CGroupContext CGroupContext; + typedef struct CGroupDeviceAllow CGroupDeviceAllow; + typedef struct CGroupIODeviceWeight CGroupIODeviceWeight; + typedef struct CGroupIODeviceLimit CGroupIODeviceLimit; ++typedef struct CGroupIODeviceLatency CGroupIODeviceLatency; + typedef struct CGroupBlockIODeviceWeight CGroupBlockIODeviceWeight; + typedef struct CGroupBlockIODeviceBandwidth CGroupBlockIODeviceBandwidth; + +@@ -52,6 +53,12 @@ struct CGroupIODeviceLimit { + uint64_t limits[_CGROUP_IO_LIMIT_TYPE_MAX]; + }; + ++struct CGroupIODeviceLatency { ++ LIST_FIELDS(CGroupIODeviceLatency, device_latencies); ++ char *path; ++ usec_t target_usec; ++}; ++ + struct CGroupBlockIODeviceWeight { + LIST_FIELDS(CGroupBlockIODeviceWeight, device_weights); + char *path; +@@ -85,6 +92,7 @@ struct CGroupContext { + uint64_t startup_io_weight; + LIST_HEAD(CGroupIODeviceWeight, io_device_weights); + LIST_HEAD(CGroupIODeviceLimit, io_device_limits); ++ LIST_HEAD(CGroupIODeviceLatency, io_device_latencies); + + uint64_t memory_low; + uint64_t memory_high; +@@ -137,6 +145,7 @@ CGroupMask cgroup_context_get_mask(CGroupContext *c); + void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a); + void cgroup_context_free_io_device_weight(CGroupContext *c, CGroupIODeviceWeight *w); + void cgroup_context_free_io_device_limit(CGroupContext *c, CGroupIODeviceLimit *l); ++void cgroup_context_free_io_device_latency(CGroupContext *c, CGroupIODeviceLatency *l); + void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w); + void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b); + +diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c +index 30d4e83932..a1d3014d61 100644 +--- a/src/core/dbus-cgroup.c ++++ b/src/core/dbus-cgroup.c +@@ -140,6 +140,36 @@ static int property_get_io_device_limits( + return sd_bus_message_close_container(reply); + } + ++static int property_get_io_device_latency( ++ sd_bus *bus, ++ const char *path, ++ const char *interface, ++ const char *property, ++ sd_bus_message *reply, ++ void *userdata, ++ sd_bus_error *error) { ++ ++ CGroupContext *c = userdata; ++ CGroupIODeviceLatency *l; ++ int r; ++ ++ assert(bus); ++ assert(reply); ++ assert(c); ++ ++ r = sd_bus_message_open_container(reply, 'a', "(st)"); ++ if (r < 0) ++ return r; ++ ++ LIST_FOREACH(device_latencies, l, c->io_device_latencies) { ++ r = sd_bus_message_append(reply, "(st)", l->path, l->target_usec); ++ if (r < 0) ++ return r; ++ } ++ ++ return sd_bus_message_close_container(reply); ++} ++ + static int property_get_blockio_device_weight( + sd_bus *bus, + const char *path, +@@ -314,6 +344,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = { + SD_BUS_PROPERTY("IOWriteBandwidthMax", "a(st)", property_get_io_device_limits, 0, 0), + SD_BUS_PROPERTY("IOReadIOPSMax", "a(st)", property_get_io_device_limits, 0, 0), + SD_BUS_PROPERTY("IOWriteIOPSMax", "a(st)", property_get_io_device_limits, 0, 0), ++ SD_BUS_PROPERTY("IODeviceLatencyTargetUSec", "a(st)", property_get_io_device_latency, 0, 0), + SD_BUS_PROPERTY("BlockIOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, blockio_accounting), 0), + SD_BUS_PROPERTY("BlockIOWeight", "t", NULL, offsetof(CGroupContext, blockio_weight), 0), + SD_BUS_PROPERTY("StartupBlockIOWeight", "t", NULL, offsetof(CGroupContext, startup_blockio_weight), 0), +@@ -898,6 +929,86 @@ int bus_cgroup_set_property( + + return 1; + ++ } else if (streq(name, "IODeviceLatencyTargetUSec")) { ++ const char *path; ++ uint64_t target; ++ unsigned n = 0; ++ ++ r = sd_bus_message_enter_container(message, 'a', "(st)"); ++ if (r < 0) ++ return r; ++ ++ while ((r = sd_bus_message_read(message, "(st)", &path, &target)) > 0) { ++ ++ if (!path_is_normalized(path)) ++ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path '%s' specified in %s= is not normalized.", name, path); ++ ++ if (!UNIT_WRITE_FLAGS_NOOP(flags)) { ++ CGroupIODeviceLatency *a = NULL, *b; ++ ++ LIST_FOREACH(device_latencies, b, c->io_device_latencies) { ++ if (path_equal(b->path, path)) { ++ a = b; ++ break; ++ } ++ } ++ ++ if (!a) { ++ a = new0(CGroupIODeviceLatency, 1); ++ if (!a) ++ return -ENOMEM; ++ ++ a->path = strdup(path); ++ if (!a->path) { ++ free(a); ++ return -ENOMEM; ++ } ++ LIST_PREPEND(device_latencies, c->io_device_latencies, a); ++ } ++ ++ a->target_usec = target; ++ } ++ ++ n++; ++ } ++ ++ r = sd_bus_message_exit_container(message); ++ if (r < 0) ++ return r; ++ ++ if (!UNIT_WRITE_FLAGS_NOOP(flags)) { ++ _cleanup_free_ char *buf = NULL; ++ _cleanup_fclose_ FILE *f = NULL; ++ char ts[FORMAT_TIMESPAN_MAX]; ++ CGroupIODeviceLatency *a; ++ size_t size = 0; ++ ++ if (n == 0) { ++ while (c->io_device_latencies) ++ cgroup_context_free_io_device_latency(c, c->io_device_latencies); ++ } ++ ++ unit_invalidate_cgroup(u, CGROUP_MASK_IO); ++ ++ f = open_memstream(&buf, &size); ++ if (!f) ++ return -ENOMEM; ++ ++ (void) __fsetlocking(f, FSETLOCKING_BYCALLER); ++ ++ fputs("IODeviceLatencyTargetSec=\n", f); ++ LIST_FOREACH(device_latencies, a, c->io_device_latencies) ++ fprintf(f, "IODeviceLatencyTargetSec=%s %s\n", ++ a->path, format_timespan(ts, sizeof(ts), a->target_usec, 1)); ++ ++ r = fflush_and_check(f); ++ if (r < 0) ++ return r; ++ unit_write_setting(u, flags, name, buf); ++ } ++ ++ return 1; ++ + } else if (STR_IN_SET(name, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) { + const char *path; + bool read = true; +diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 +index 8883818ff2..23879c001f 100644 +--- a/src/core/load-fragment-gperf.gperf.m4 ++++ b/src/core/load-fragment-gperf.gperf.m4 +@@ -185,6 +185,7 @@ $1.IOReadBandwidthMax, config_parse_io_limit, 0, + $1.IOWriteBandwidthMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) + $1.IOReadIOPSMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) + $1.IOWriteIOPSMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) ++$1.IODeviceLatencyTargetSec, config_parse_io_device_latency, 0, offsetof($1, cgroup_context) + $1.BlockIOAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.blockio_accounting) + $1.BlockIOWeight, config_parse_blockio_weight, 0, offsetof($1, cgroup_context.blockio_weight) + $1.StartupBlockIOWeight, config_parse_blockio_weight, 0, offsetof($1, cgroup_context.startup_blockio_weight) +diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c +index 9b2724307d..1e22013b75 100644 +--- a/src/core/load-fragment.c ++++ b/src/core/load-fragment.c +@@ -3383,6 +3383,77 @@ int config_parse_io_device_weight( + return 0; + } + ++int config_parse_io_device_latency( ++ const char *unit, ++ const char *filename, ++ unsigned line, ++ const char *section, ++ unsigned section_line, ++ const char *lvalue, ++ int ltype, ++ const char *rvalue, ++ void *data, ++ void *userdata) { ++ ++ _cleanup_free_ char *path = NULL, *resolved = NULL; ++ CGroupIODeviceLatency *l; ++ CGroupContext *c = data; ++ const char *p = rvalue; ++ usec_t usec; ++ int r; ++ ++ assert(filename); ++ assert(lvalue); ++ assert(rvalue); ++ ++ if (isempty(rvalue)) { ++ while (c->io_device_latencies) ++ cgroup_context_free_io_device_latency(c, c->io_device_latencies); ++ ++ return 0; ++ } ++ ++ r = extract_first_word(&p, &path, NULL, EXTRACT_QUOTES); ++ if (r == -ENOMEM) ++ return log_oom(); ++ if (r < 0) { ++ log_syntax(unit, LOG_WARNING, filename, line, r, ++ "Invalid syntax, ignoring: %s", rvalue); ++ return 0; ++ } ++ if (r == 0 || isempty(p)) { ++ log_syntax(unit, LOG_WARNING, filename, line, 0, ++ "Failed to extract device path and latency from '%s', ignoring.", rvalue); ++ return 0; ++ } ++ ++ r = unit_full_printf(userdata, path, &resolved); ++ if (r < 0) { ++ log_syntax(unit, LOG_WARNING, filename, line, r, ++ "Failed to resolve unit specifiers in '%s', ignoring: %m", path); ++ return 0; ++ } ++ ++ r = path_simplify_and_warn(resolved, 0, unit, filename, line, lvalue); ++ if (r < 0) ++ return 0; ++ ++ if (parse_sec(p, &usec) < 0) { ++ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse timer value, ignoring: %s", p); ++ return 0; ++ } ++ ++ l = new0(CGroupIODeviceLatency, 1); ++ if (!l) ++ return log_oom(); ++ ++ l->path = TAKE_PTR(resolved); ++ l->target_usec = usec; ++ ++ LIST_PREPEND(device_latencies, c->io_device_latencies, l); ++ return 0; ++} ++ + int config_parse_io_limit( + const char *unit, + const char *filename, +@@ -4572,6 +4643,7 @@ void unit_dump_config_items(FILE *f) { + { config_parse_device_policy, "POLICY" }, + { config_parse_io_limit, "LIMIT" }, + { config_parse_io_device_weight, "DEVICEWEIGHT" }, ++ { config_parse_io_device_latency, "DEVICELATENCY" }, + { config_parse_blockio_bandwidth, "BANDWIDTH" }, + { config_parse_blockio_weight, "WEIGHT" }, + { config_parse_blockio_device_weight, "DEVICEWEIGHT" }, +diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h +index 424fa478a7..65a94d53cc 100644 +--- a/src/core/load-fragment.h ++++ b/src/core/load-fragment.h +@@ -68,6 +68,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_tasks_max); + CONFIG_PARSER_PROTOTYPE(config_parse_delegate); + CONFIG_PARSER_PROTOTYPE(config_parse_device_policy); + CONFIG_PARSER_PROTOTYPE(config_parse_device_allow); ++CONFIG_PARSER_PROTOTYPE(config_parse_io_device_latency); + CONFIG_PARSER_PROTOTYPE(config_parse_io_device_weight); + CONFIG_PARSER_PROTOTYPE(config_parse_io_limit); + CONFIG_PARSER_PROTOTYPE(config_parse_blockio_weight); +diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c +index e0b2cfb170..3c1ecf2027 100644 +--- a/src/shared/bus-unit-util.c ++++ b/src/shared/bus-unit-util.c +@@ -566,6 +566,37 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons + return 1; + } + ++ if (streq(field, "IODeviceLatencyTargetSec")) { ++ const char *field_usec = "IODeviceLatencyTargetUSec"; ++ ++ if (isempty(eq)) ++ r = sd_bus_message_append(m, "(sv)", field_usec, "a(st)", USEC_INFINITY); ++ else { ++ const char *path, *target, *e; ++ usec_t usec; ++ ++ e = strchr(eq, ' '); ++ if (!e) { ++ log_error("Failed to parse %s value %s.", field, eq); ++ return -EINVAL; ++ } ++ ++ path = strndupa(eq, e - eq); ++ target = e+1; ++ ++ r = parse_sec(target, &usec); ++ if (r < 0) ++ return log_error_errno(r, "Failed to parse %s value %s: %m", field, target); ++ ++ r = sd_bus_message_append(m, "(sv)", field_usec, "a(st)", 1, path, usec); ++ } ++ ++ if (r < 0) ++ return bus_log_create_error(r); ++ ++ return 1; ++ } ++ + if (STR_IN_SET(field, "IPAddressAllow", "IPAddressDeny")) { + unsigned char prefixlen; + union in_addr_union prefix = {}; +diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c +index a3074bc5e3..559e49f104 100644 +--- a/src/systemctl/systemctl.c ++++ b/src/systemctl/systemctl.c +@@ -4875,6 +4875,28 @@ static int print_property(const char *name, sd_bus_message *m, bool value, bool + + return 1; + ++ } else if (contents[0] == SD_BUS_TYPE_STRUCT_BEGIN && ++ streq(name, "IODeviceLatencyTargetUSec")) { ++ char ts[FORMAT_TIMESPAN_MAX]; ++ const char *path; ++ uint64_t target; ++ ++ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(st)"); ++ if (r < 0) ++ return bus_log_parse_error(r); ++ ++ while ((r = sd_bus_message_read(m, "(st)", &path, &target)) > 0) ++ print_prop(name, "%s %s", strna(path), ++ format_timespan(ts, sizeof(ts), target, 1)); ++ if (r < 0) ++ return bus_log_parse_error(r); ++ ++ r = sd_bus_message_exit_container(m); ++ if (r < 0) ++ return bus_log_parse_error(r); ++ ++ return 1; ++ + } else if (contents[0] == SD_BUS_TYPE_BYTE && streq(name, "StandardInputData")) { + _cleanup_free_ char *h = NULL; + const void *p; diff --git a/SOURCES/0396-time-util-Introduce-parse_sec_def_infinity.patch b/SOURCES/0396-time-util-Introduce-parse_sec_def_infinity.patch new file mode 100644 index 0000000..8af140b --- /dev/null +++ b/SOURCES/0396-time-util-Introduce-parse_sec_def_infinity.patch @@ -0,0 +1,122 @@ +From 9692477a59c47b5fb6bd6d4702302859296db070 Mon Sep 17 00:00:00 2001 +From: Filipe Brandenburger +Date: Wed, 23 Jan 2019 19:48:54 -0800 +Subject: [PATCH] time-util: Introduce parse_sec_def_infinity + +This works like parse_sec() but defaults to USEC_INFINITY when passed an +empty string or only whitespace. + +Also introduce config_parse_sec_def_infinity, which can be used to parse +config options using this function. + +This is useful for time options that use "infinity" for default and that +can be reset by unsetting them. + +Introduce a test case to ensure it works as expected. + +(cherry picked from commit 7b61ce3c44ef5908e817009ce4f9d2a7a37722be) + +Related: #1770379 +--- + src/basic/time-util.c | 9 +++++++++ + src/basic/time-util.h | 1 + + src/shared/conf-parser.c | 1 + + src/shared/conf-parser.h | 1 + + src/test/test-time-util.c | 21 +++++++++++++++++++++ + 5 files changed, 33 insertions(+) + +diff --git a/src/basic/time-util.c b/src/basic/time-util.c +index fe201c398d..c36e462193 100644 +--- a/src/basic/time-util.c ++++ b/src/basic/time-util.c +@@ -1072,6 +1072,15 @@ int parse_sec_fix_0(const char *t, usec_t *usec) { + return parse_sec(t, usec); + } + ++int parse_sec_def_infinity(const char *t, usec_t *ret) { ++ t += strspn(t, WHITESPACE); ++ if (isempty(t)) { ++ *ret = USEC_INFINITY; ++ return 0; ++ } ++ return parse_sec(t, ret); ++} ++ + int parse_nsec(const char *t, nsec_t *nsec) { + static const struct { + const char *suffix; +diff --git a/src/basic/time-util.h b/src/basic/time-util.h +index 344f2dc52e..f5c9ea6327 100644 +--- a/src/basic/time-util.h ++++ b/src/basic/time-util.h +@@ -116,6 +116,7 @@ int parse_timestamp(const char *t, usec_t *usec); + + int parse_sec(const char *t, usec_t *usec); + int parse_sec_fix_0(const char *t, usec_t *usec); ++int parse_sec_def_infinity(const char *t, usec_t *usec); + int parse_time(const char *t, usec_t *usec, usec_t default_unit); + int parse_nsec(const char *t, nsec_t *nsec); + +diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c +index 2d62fdf05d..246b7431e4 100644 +--- a/src/shared/conf-parser.c ++++ b/src/shared/conf-parser.c +@@ -509,6 +509,7 @@ DEFINE_PARSER(unsigned, unsigned, safe_atou); + DEFINE_PARSER(double, double, safe_atod); + DEFINE_PARSER(nsec, nsec_t, parse_nsec); + DEFINE_PARSER(sec, usec_t, parse_sec); ++DEFINE_PARSER(sec_def_infinity, usec_t, parse_sec_def_infinity); + DEFINE_PARSER(mode, mode_t, parse_mode); + + int config_parse_iec_size(const char* unit, +diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h +index 16f042d894..a0a5c89c27 100644 +--- a/src/shared/conf-parser.h ++++ b/src/shared/conf-parser.h +@@ -127,6 +127,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_string); + CONFIG_PARSER_PROTOTYPE(config_parse_path); + CONFIG_PARSER_PROTOTYPE(config_parse_strv); + CONFIG_PARSER_PROTOTYPE(config_parse_sec); ++CONFIG_PARSER_PROTOTYPE(config_parse_sec_def_infinity); + CONFIG_PARSER_PROTOTYPE(config_parse_nsec); + CONFIG_PARSER_PROTOTYPE(config_parse_mode); + CONFIG_PARSER_PROTOTYPE(config_parse_warn_compat); +diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c +index 87de8d172c..354a01dd1a 100644 +--- a/src/test/test-time-util.c ++++ b/src/test/test-time-util.c +@@ -61,6 +61,26 @@ static void test_parse_sec_fix_0(void) { + assert_se(u == USEC_INFINITY); + } + ++static void test_parse_sec_def_infinity(void) { ++ usec_t u; ++ ++ log_info("/* %s */", __func__); ++ ++ assert_se(parse_sec_def_infinity("5s", &u) >= 0); ++ assert_se(u == 5 * USEC_PER_SEC); ++ assert_se(parse_sec_def_infinity("", &u) >= 0); ++ assert_se(u == USEC_INFINITY); ++ assert_se(parse_sec_def_infinity(" ", &u) >= 0); ++ assert_se(u == USEC_INFINITY); ++ assert_se(parse_sec_def_infinity("0s", &u) >= 0); ++ assert_se(u == 0); ++ assert_se(parse_sec_def_infinity("0", &u) >= 0); ++ assert_se(u == 0); ++ assert_se(parse_sec_def_infinity(" 0", &u) >= 0); ++ assert_se(u == 0); ++ assert_se(parse_sec_def_infinity("-5s", &u) < 0); ++} ++ + static void test_parse_time(void) { + usec_t u; + +@@ -420,6 +440,7 @@ int main(int argc, char *argv[]) { + + test_parse_sec(); + test_parse_sec_fix_0(); ++ test_parse_sec_def_infinity(); + test_parse_time(); + test_parse_nsec(); + test_format_timespan(1); diff --git a/SOURCES/0397-cgroup-use-structured-initialization.patch b/SOURCES/0397-cgroup-use-structured-initialization.patch new file mode 100644 index 0000000..7b9e695 --- /dev/null +++ b/SOURCES/0397-cgroup-use-structured-initialization.patch @@ -0,0 +1,63 @@ +From f7b462bacb3c0ed1f7bbe63193e9e349aafd21d3 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 20 Nov 2018 19:45:02 +0100 +Subject: [PATCH] cgroup: use structured initialization + +(cherry picked from commit de8a711a5849f9239c93aefa5554a62986dfce42) + +Related: #1770379 +--- + src/core/cgroup.c | 33 +++++++++++++++++---------------- + 1 file changed, 17 insertions(+), 16 deletions(-) + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index ad8219bd79..7aa7db9261 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -71,29 +71,30 @@ static void cgroup_compat_warn(void) { + void cgroup_context_init(CGroupContext *c) { + assert(c); + +- /* Initialize everything to the kernel defaults, assuming the +- * structure is preinitialized to 0 */ ++ /* Initialize everything to the kernel defaults. */ + +- c->cpu_weight = CGROUP_WEIGHT_INVALID; +- c->startup_cpu_weight = CGROUP_WEIGHT_INVALID; +- c->cpu_quota_per_sec_usec = USEC_INFINITY; ++ *c = (CGroupContext) { ++ .cpu_weight = CGROUP_WEIGHT_INVALID, ++ .startup_cpu_weight = CGROUP_WEIGHT_INVALID, ++ .cpu_quota_per_sec_usec = USEC_INFINITY, + +- c->cpu_shares = CGROUP_CPU_SHARES_INVALID; +- c->startup_cpu_shares = CGROUP_CPU_SHARES_INVALID; ++ .cpu_shares = CGROUP_CPU_SHARES_INVALID, ++ .startup_cpu_shares = CGROUP_CPU_SHARES_INVALID, + +- c->memory_high = CGROUP_LIMIT_MAX; +- c->memory_max = CGROUP_LIMIT_MAX; +- c->memory_swap_max = CGROUP_LIMIT_MAX; ++ .memory_high = CGROUP_LIMIT_MAX, ++ .memory_max = CGROUP_LIMIT_MAX, ++ .memory_swap_max = CGROUP_LIMIT_MAX, + +- c->memory_limit = CGROUP_LIMIT_MAX; ++ .memory_limit = CGROUP_LIMIT_MAX, + +- c->io_weight = CGROUP_WEIGHT_INVALID; +- c->startup_io_weight = CGROUP_WEIGHT_INVALID; ++ .io_weight = CGROUP_WEIGHT_INVALID, ++ .startup_io_weight = CGROUP_WEIGHT_INVALID, + +- c->blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID; +- c->startup_blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID; ++ .blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID, ++ .startup_blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID, + +- c->tasks_max = (uint64_t) -1; ++ .tasks_max = CGROUP_LIMIT_MAX, ++ }; + } + + void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a) { diff --git a/SOURCES/0398-core-add-CPUQuotaPeriodSec.patch b/SOURCES/0398-core-add-CPUQuotaPeriodSec.patch new file mode 100644 index 0000000..b411152 --- /dev/null +++ b/SOURCES/0398-core-add-CPUQuotaPeriodSec.patch @@ -0,0 +1,403 @@ +From 11df25536319a4c0f3276c2218054243d9ee213c Mon Sep 17 00:00:00 2001 +From: Filipe Brandenburger +Date: Fri, 2 Nov 2018 09:21:57 -0700 +Subject: [PATCH] core: add CPUQuotaPeriodSec= + +This new setting allows configuration of CFS period on the CPU cgroup, instead +of using a hardcoded default of 100ms. + +Tested: +- Legacy cgroup + Unified cgroup +- systemctl set-property +- systemctl show +- Confirmed that the cgroup settings (such as cpu.cfs_period_ns) were set + appropriately, including updating the CPU quota (cpu.cfs_quota_ns) when + CPUQuotaPeriodSec= is updated. +- Checked that clamping works properly when either period or (quota * period) + are below the resolution of 1ms, or if period is above the max of 1s. + +(cherry picked from commit 10f28641115733c61754342d5dcbe70b083bea4b) + +Resolves: #1770379 +--- + doc/TRANSIENT-SETTINGS.md | 1 + + man/systemd.resource-control.xml | 19 +++++++++ + src/core/cgroup.c | 61 ++++++++++++++++++++++----- + src/core/cgroup.h | 3 ++ + src/core/dbus-cgroup.c | 23 ++++++++++ + src/core/load-fragment-gperf.gperf.m4 | 1 + + src/core/load-fragment.c | 1 + + src/shared/bus-unit-util.c | 14 ++++++ + src/test/meson.build | 5 +++ + src/test/test-cgroup-cpu.c | 38 +++++++++++++++++ + 10 files changed, 155 insertions(+), 11 deletions(-) + create mode 100644 src/test/test-cgroup-cpu.c + +diff --git a/doc/TRANSIENT-SETTINGS.md b/doc/TRANSIENT-SETTINGS.md +index 23fe84e4d1..0d2d3e9065 100644 +--- a/doc/TRANSIENT-SETTINGS.md ++++ b/doc/TRANSIENT-SETTINGS.md +@@ -218,6 +218,7 @@ All cgroup/resource control settings are available for transient units + ✓ CPUShares= + ✓ StartupCPUShares= + ✓ CPUQuota= ++✓ CPUQuotaPeriodSec= + ✓ AllowedCPUs= + ✓ AllowedMemoryNodes= + ✓ MemoryAccounting= +diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml +index b0064bf98f..cfe19a6574 100644 +--- a/man/systemd.resource-control.xml ++++ b/man/systemd.resource-control.xml +@@ -231,6 +231,25 @@ + + + ++ ++ CPUQuotaPeriodSec= ++ ++ ++ Assign the duration over which the CPU time quota specified by CPUQuota= is measured. ++ Takes a time duration value in seconds, with an optional suffix such as "ms" for milliseconds (or "s" for seconds.) ++ The default setting is 100ms. The period is clamped to the range supported by the kernel, which is [1ms, 1000ms]. ++ Additionally, the period is adjusted up so that the quota interval is also at least 1ms. ++ Setting CPUQuotaPeriodSec= to an empty value resets it to the default. ++ ++ This controls the second field of cpu.max attribute on the unified control group hierarchy ++ and cpu.cfs_period_us on legacy. For details about these control group attributes, see ++ cgroup-v2.txt and ++ sched-design-CFS.txt. ++ ++ Example: CPUQuotaPeriodSec=10ms to request that the CPU quota is measured in periods of 10ms. ++ ++ ++ + + MemoryAccounting= + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index 7aa7db9261..45fd64a394 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -23,7 +23,7 @@ + #include "string-util.h" + #include "virt.h" + +-#define CGROUP_CPU_QUOTA_PERIOD_USEC ((usec_t) 100 * USEC_PER_MSEC) ++#define CGROUP_CPU_QUOTA_DEFAULT_PERIOD_USEC ((usec_t) 100 * USEC_PER_MSEC) + + bool manager_owns_root_cgroup(Manager *m) { + assert(m); +@@ -77,6 +77,7 @@ void cgroup_context_init(CGroupContext *c) { + .cpu_weight = CGROUP_WEIGHT_INVALID, + .startup_cpu_weight = CGROUP_WEIGHT_INVALID, + .cpu_quota_per_sec_usec = USEC_INFINITY, ++ .cpu_quota_period_usec = USEC_INFINITY, + + .cpu_shares = CGROUP_CPU_SHARES_INVALID, + .startup_cpu_shares = CGROUP_CPU_SHARES_INVALID, +@@ -190,6 +191,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + CGroupDeviceAllow *a; + IPAddressAccessItem *iaai; + char u[FORMAT_TIMESPAN_MAX]; ++ char v[FORMAT_TIMESPAN_MAX]; + + assert(c); + assert(f); +@@ -211,6 +213,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + "%sCPUShares=%" PRIu64 "\n" + "%sStartupCPUShares=%" PRIu64 "\n" + "%sCPUQuotaPerSecSec=%s\n" ++ "%sCPUQuotaPeriodSec=%s\n" + "%sAllowedCPUs=%s\n" + "%sAllowedMemoryNodes=%s\n" + "%sIOWeight=%" PRIu64 "\n" +@@ -236,6 +239,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + prefix, c->cpu_shares, + prefix, c->startup_cpu_shares, + prefix, format_timespan(u, sizeof(u), c->cpu_quota_per_sec_usec, 1), ++ prefix, format_timespan(v, sizeof(v), c->cpu_quota_period_usec, 1), + prefix, cpuset_cpus, + prefix, cpuset_mems, + prefix, c->io_weight, +@@ -515,7 +519,40 @@ static uint64_t cgroup_context_cpu_shares(CGroupContext *c, ManagerState state) + return CGROUP_CPU_SHARES_DEFAULT; + } + +-static void cgroup_apply_unified_cpu_config(Unit *u, uint64_t weight, uint64_t quota) { ++usec_t cgroup_cpu_adjust_period(usec_t period, usec_t quota, usec_t resolution, usec_t max_period) { ++ /* kernel uses a minimum resolution of 1ms, so both period and (quota * period) ++ * need to be higher than that boundary. quota is specified in USecPerSec. ++ * Additionally, period must be at most max_period. */ ++ assert(quota > 0); ++ ++ return MIN(MAX3(period, resolution, resolution * USEC_PER_SEC / quota), max_period); ++} ++ ++static usec_t cgroup_cpu_adjust_period_and_log(Unit *u, usec_t period, usec_t quota) { ++ usec_t new_period; ++ ++ if (quota == USEC_INFINITY) ++ /* Always use default period for infinity quota. */ ++ return CGROUP_CPU_QUOTA_DEFAULT_PERIOD_USEC; ++ ++ if (period == USEC_INFINITY) ++ /* Default period was requested. */ ++ period = CGROUP_CPU_QUOTA_DEFAULT_PERIOD_USEC; ++ ++ /* Clamp to interval [1ms, 1s] */ ++ new_period = cgroup_cpu_adjust_period(period, quota, USEC_PER_MSEC, USEC_PER_SEC); ++ ++ if (new_period != period) { ++ char v[FORMAT_TIMESPAN_MAX]; ++ log_unit_full(u, LOG_WARNING, 0, ++ "Clamping CPU interval for cpu.max: period is now %s", ++ format_timespan(v, sizeof(v), new_period, 1)); ++ } ++ ++ return new_period; ++} ++ ++static void cgroup_apply_unified_cpu_config(Unit *u, uint64_t weight, uint64_t quota, usec_t period) { + char buf[MAX(DECIMAL_STR_MAX(uint64_t) + 1, (DECIMAL_STR_MAX(usec_t) + 1) * 2)]; + int r; + +@@ -525,11 +562,12 @@ static void cgroup_apply_unified_cpu_config(Unit *u, uint64_t weight, uint64_t q + log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.weight: %m"); + ++ period = cgroup_cpu_adjust_period_and_log(u, period, quota); + if (quota != USEC_INFINITY) + xsprintf(buf, USEC_FMT " " USEC_FMT "\n", +- quota * CGROUP_CPU_QUOTA_PERIOD_USEC / USEC_PER_SEC, CGROUP_CPU_QUOTA_PERIOD_USEC); ++ MAX(quota * period / USEC_PER_SEC, USEC_PER_MSEC), period); + else +- xsprintf(buf, "max " USEC_FMT "\n", CGROUP_CPU_QUOTA_PERIOD_USEC); ++ xsprintf(buf, "max " USEC_FMT "\n", period); + + r = cg_set_attribute("cpu", u->cgroup_path, "cpu.max", buf); + +@@ -538,7 +576,7 @@ static void cgroup_apply_unified_cpu_config(Unit *u, uint64_t weight, uint64_t q + "Failed to set cpu.max: %m"); + } + +-static void cgroup_apply_legacy_cpu_config(Unit *u, uint64_t shares, uint64_t quota) { ++static void cgroup_apply_legacy_cpu_config(Unit *u, uint64_t shares, uint64_t quota, usec_t period) { + char buf[MAX(DECIMAL_STR_MAX(uint64_t), DECIMAL_STR_MAX(usec_t)) + 1]; + int r; + +@@ -548,20 +586,21 @@ static void cgroup_apply_legacy_cpu_config(Unit *u, uint64_t shares, uint64_t qu + log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.shares: %m"); + +- xsprintf(buf, USEC_FMT "\n", CGROUP_CPU_QUOTA_PERIOD_USEC); ++ period = cgroup_cpu_adjust_period_and_log(u, period, quota); ++ ++ xsprintf(buf, USEC_FMT "\n", period); + r = cg_set_attribute("cpu", u->cgroup_path, "cpu.cfs_period_us", buf); + if (r < 0) + log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.cfs_period_us: %m"); + + if (quota != USEC_INFINITY) { +- xsprintf(buf, USEC_FMT "\n", quota * CGROUP_CPU_QUOTA_PERIOD_USEC / USEC_PER_SEC); +- r = cg_set_attribute("cpu", u->cgroup_path, "cpu.cfs_quota_us", buf); +- } else ++ xsprintf(buf, USEC_FMT "\n", MAX(quota * period / USEC_PER_SEC, USEC_PER_MSEC)); + r = cg_set_attribute("cpu", u->cgroup_path, "cpu.cfs_quota_us", "-1"); + if (r < 0) + log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.cfs_quota_us: %m"); ++ } + } + + static uint64_t cgroup_cpu_shares_to_weight(uint64_t shares) { +@@ -815,7 +854,7 @@ static void cgroup_context_apply( + } else + weight = CGROUP_WEIGHT_DEFAULT; + +- cgroup_apply_unified_cpu_config(u, weight, c->cpu_quota_per_sec_usec); ++ cgroup_apply_unified_cpu_config(u, weight, c->cpu_quota_per_sec_usec, c->cpu_quota_period_usec); + } else { + uint64_t shares; + +@@ -831,7 +870,7 @@ static void cgroup_context_apply( + else + shares = CGROUP_CPU_SHARES_DEFAULT; + +- cgroup_apply_legacy_cpu_config(u, shares, c->cpu_quota_per_sec_usec); ++ cgroup_apply_legacy_cpu_config(u, shares, c->cpu_quota_per_sec_usec, c->cpu_quota_period_usec); + } + } + +diff --git a/src/core/cgroup.h b/src/core/cgroup.h +index f7365b4c46..2ba57d3ded 100644 +--- a/src/core/cgroup.h ++++ b/src/core/cgroup.h +@@ -84,6 +84,7 @@ struct CGroupContext { + uint64_t cpu_weight; + uint64_t startup_cpu_weight; + usec_t cpu_quota_per_sec_usec; ++ usec_t cpu_quota_period_usec; + + CPUSet cpuset_cpus; + CPUSet cpuset_mems; +@@ -136,6 +137,8 @@ typedef enum CGroupIPAccountingMetric { + typedef struct Unit Unit; + typedef struct Manager Manager; + ++usec_t cgroup_cpu_adjust_period(usec_t period, usec_t quota, usec_t resolution, usec_t max_period); ++ + void cgroup_context_init(CGroupContext *c); + void cgroup_context_done(CGroupContext *c); + void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix); +diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c +index a1d3014d61..c8b918e45d 100644 +--- a/src/core/dbus-cgroup.c ++++ b/src/core/dbus-cgroup.c +@@ -334,6 +334,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = { + SD_BUS_PROPERTY("CPUShares", "t", NULL, offsetof(CGroupContext, cpu_shares), 0), + SD_BUS_PROPERTY("StartupCPUShares", "t", NULL, offsetof(CGroupContext, startup_cpu_shares), 0), + SD_BUS_PROPERTY("CPUQuotaPerSecUSec", "t", bus_property_get_usec, offsetof(CGroupContext, cpu_quota_per_sec_usec), 0), ++ SD_BUS_PROPERTY("CPUQuotaPeriodUSec", "t", bus_property_get_usec, offsetof(CGroupContext, cpu_quota_period_usec), 0), + SD_BUS_PROPERTY("AllowedCPUs", "ay", property_get_cpuset, offsetof(CGroupContext, cpuset_cpus), 0), + SD_BUS_PROPERTY("AllowedMemoryNodes", "ay", property_get_cpuset, offsetof(CGroupContext, cpuset_mems), 0), + SD_BUS_PROPERTY("IOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, io_accounting), 0), +@@ -725,6 +726,28 @@ int bus_cgroup_set_property( + + return 1; + ++ } else if (streq(name, "CPUQuotaPeriodUSec")) { ++ uint64_t u64; ++ ++ r = sd_bus_message_read(message, "t", &u64); ++ if (r < 0) ++ return r; ++ ++ if (!UNIT_WRITE_FLAGS_NOOP(flags)) { ++ c->cpu_quota_period_usec = u64; ++ unit_invalidate_cgroup(u, CGROUP_MASK_CPU); ++ if (c->cpu_quota_period_usec == USEC_INFINITY) ++ unit_write_setting(u, flags, "CPUQuotaPeriodSec", "CPUQuotaPeriodSec="); ++ else { ++ char v[FORMAT_TIMESPAN_MAX]; ++ unit_write_settingf(u, flags, "CPUQuotaPeriodSec", ++ "CPUQuotaPeriodSec=%s", ++ format_timespan(v, sizeof(v), c->cpu_quota_period_usec, 1)); ++ } ++ } ++ ++ return 1; ++ + } else if (STR_IN_SET(name, "AllowedCPUs", "AllowedMemoryNodes")) { + const void *a; + size_t n; +diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 +index 23879c001f..4defa82ac1 100644 +--- a/src/core/load-fragment-gperf.gperf.m4 ++++ b/src/core/load-fragment-gperf.gperf.m4 +@@ -169,6 +169,7 @@ $1.StartupCPUWeight, config_parse_cg_weight, 0, + $1.CPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.cpu_shares) + $1.StartupCPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.startup_cpu_shares) + $1.CPUQuota, config_parse_cpu_quota, 0, offsetof($1, cgroup_context) ++$1.CPUQuotaPeriodSec, config_parse_sec_def_infinity, 0, offsetof($1, cgroup_context.cpu_quota_period_usec) + $1.MemoryAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.memory_accounting) + $1.MemoryLow, config_parse_memory_limit, 0, offsetof($1, cgroup_context) + $1.MemoryHigh, config_parse_memory_limit, 0, offsetof($1, cgroup_context) +diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c +index 1e22013b75..762b106007 100644 +--- a/src/core/load-fragment.c ++++ b/src/core/load-fragment.c +@@ -55,6 +55,7 @@ + #include "unit-name.h" + #include "unit-printf.h" + #include "user-util.h" ++#include "time-util.h" + #include "web-util.h" + + static int supported_socket_protocol_from_string(const char *s) { +diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c +index 3c1ecf2027..ec45d6f86d 100644 +--- a/src/shared/bus-unit-util.c ++++ b/src/shared/bus-unit-util.c +@@ -480,6 +480,20 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons + return 1; + } + ++ if (streq(field, "CPUQuotaPeriodSec")) { ++ usec_t u = USEC_INFINITY; ++ ++ r = parse_sec_def_infinity(eq, &u); ++ if (r < 0) ++ return log_error_errno(r, "CPU quota period '%s' invalid.", eq); ++ ++ r = sd_bus_message_append(m, "(sv)", "CPUQuotaPeriodUSec", "t", u); ++ if (r < 0) ++ return bus_log_create_error(r); ++ ++ return 1; ++ } ++ + if (streq(field, "DeviceAllow")) { + + if (isempty(eq)) +diff --git a/src/test/meson.build b/src/test/meson.build +index ead000e30c..22264d034c 100644 +--- a/src/test/meson.build ++++ b/src/test/meson.build +@@ -513,6 +513,11 @@ tests += [ + [], + '', 'manual'], + ++ [['src/test/test-cgroup-cpu.c'], ++ [libcore, ++ libshared], ++ []], ++ + [['src/test/test-cgroup-mask.c', + 'src/test/test-helper.c'], + [libcore, +diff --git a/src/test/test-cgroup-cpu.c b/src/test/test-cgroup-cpu.c +new file mode 100644 +index 0000000000..a445acc955 +--- /dev/null ++++ b/src/test/test-cgroup-cpu.c +@@ -0,0 +1,38 @@ ++/* SPDX-License-Identifier: LGPL-2.1+ */ ++ ++#include "cgroup.h" ++#include "log.h" ++ ++static void test_cgroup_cpu_adjust_period(void) { ++ log_info("/* %s */", __func__); ++ ++ /* Period 1ms, quota 40% -> Period 2.5ms */ ++ assert_se(2500 == cgroup_cpu_adjust_period(USEC_PER_MSEC, 400 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); ++ /* Period 10ms, quota 10% -> keep. */ ++ assert_se(10 * USEC_PER_MSEC == cgroup_cpu_adjust_period(10 * USEC_PER_MSEC, 100 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); ++ /* Period 1ms, quota 1000% -> keep. */ ++ assert_se(USEC_PER_MSEC == cgroup_cpu_adjust_period(USEC_PER_MSEC, 10000 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); ++ /* Period 100ms, quota 30% -> keep. */ ++ assert_se(100 * USEC_PER_MSEC == cgroup_cpu_adjust_period(100 * USEC_PER_MSEC, 300 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); ++ /* Period 5s, quota 40% -> adjust to 1s. */ ++ assert_se(USEC_PER_SEC == cgroup_cpu_adjust_period(5 * USEC_PER_SEC, 400 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); ++ /* Period 2s, quota 250% -> adjust to 1s. */ ++ assert_se(USEC_PER_SEC == cgroup_cpu_adjust_period(2 * USEC_PER_SEC, 2500 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); ++ /* Period 10us, quota 5,000,000% -> adjust to 1ms. */ ++ assert_se(USEC_PER_MSEC == cgroup_cpu_adjust_period(10, 50000000 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); ++ /* Period 10ms, quota 50,000% -> keep. */ ++ assert_se(10 * USEC_PER_MSEC == cgroup_cpu_adjust_period(10 * USEC_PER_MSEC, 500000 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); ++ /* Period 10ms, quota 1% -> adjust to 100ms. */ ++ assert_se(100 * USEC_PER_MSEC == cgroup_cpu_adjust_period(10 * USEC_PER_MSEC, 10 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); ++ /* Period 10ms, quota .001% -> adjust to 1s. */ ++ assert_se(1 * USEC_PER_SEC == cgroup_cpu_adjust_period(10 * USEC_PER_MSEC, 10, USEC_PER_MSEC, USEC_PER_SEC)); ++ /* Period 0ms, quota 200% -> adjust to 1ms. */ ++ assert_se(1 * USEC_PER_MSEC == cgroup_cpu_adjust_period(0, 2 * USEC_PER_SEC, USEC_PER_MSEC, USEC_PER_SEC)); ++ /* Period 0ms, quota 40% -> adjust to 2.5ms. */ ++ assert_se(2500 == cgroup_cpu_adjust_period(0, 400 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); ++} ++ ++int main(int argc, char *argv[]) { ++ test_cgroup_cpu_adjust_period(); ++ return 0; ++} diff --git a/SOURCES/0399-core-downgrade-CPUQuotaPeriodSec-clamping-logs-to-de.patch b/SOURCES/0399-core-downgrade-CPUQuotaPeriodSec-clamping-logs-to-de.patch new file mode 100644 index 0000000..e584cb5 --- /dev/null +++ b/SOURCES/0399-core-downgrade-CPUQuotaPeriodSec-clamping-logs-to-de.patch @@ -0,0 +1,66 @@ +From 9d58db234dc9f22a4f551b1b06bd9fc60d085f4d Mon Sep 17 00:00:00 2001 +From: Filipe Brandenburger +Date: Wed, 23 Jan 2019 20:19:44 -0800 +Subject: [PATCH] core: downgrade CPUQuotaPeriodSec= clamping logs to debug + +After the first warning log, further messages are downgraded to LOG_DEBUG. + +(cherry picked from commit 527ede0c638b47b62a87900438a8a09dea42889e) + +Related: #1770379 +--- + src/core/cgroup.c | 3 ++- + src/core/dbus-cgroup.c | 2 ++ + src/core/unit.h | 3 +++ + 3 files changed, 7 insertions(+), 1 deletion(-) + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index 45fd64a394..f8b351a65d 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -544,9 +544,10 @@ static usec_t cgroup_cpu_adjust_period_and_log(Unit *u, usec_t period, usec_t qu + + if (new_period != period) { + char v[FORMAT_TIMESPAN_MAX]; +- log_unit_full(u, LOG_WARNING, 0, ++ log_unit_full(u, u->warned_clamping_cpu_quota_period ? LOG_DEBUG : LOG_WARNING, 0, + "Clamping CPU interval for cpu.max: period is now %s", + format_timespan(v, sizeof(v), new_period, 1)); ++ u->warned_clamping_cpu_quota_period = true; + } + + return new_period; +diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c +index c8b918e45d..4555b33b1f 100644 +--- a/src/core/dbus-cgroup.c ++++ b/src/core/dbus-cgroup.c +@@ -712,6 +712,7 @@ int bus_cgroup_set_property( + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + c->cpu_quota_per_sec_usec = u64; ++ u->warned_clamping_cpu_quota_period = false; + unit_invalidate_cgroup(u, CGROUP_MASK_CPU); + + if (c->cpu_quota_per_sec_usec == USEC_INFINITY) +@@ -735,6 +736,7 @@ int bus_cgroup_set_property( + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + c->cpu_quota_period_usec = u64; ++ u->warned_clamping_cpu_quota_period = false; + unit_invalidate_cgroup(u, CGROUP_MASK_CPU); + if (c->cpu_quota_period_usec == USEC_INFINITY) + unit_write_setting(u, flags, "CPUQuotaPeriodSec", "CPUQuotaPeriodSec="); +diff --git a/src/core/unit.h b/src/core/unit.h +index 39179f5fd4..b40ff9b961 100644 +--- a/src/core/unit.h ++++ b/src/core/unit.h +@@ -356,6 +356,9 @@ typedef struct Unit { + bool exported_log_rate_limit_interval:1; + bool exported_log_rate_limit_burst:1; + ++ /* Whether we warned about clamping the CPU quota period */ ++ bool warned_clamping_cpu_quota_period:1; ++ + /* When writing transient unit files, stores which section we stored last. If < 0, we didn't write any yet. If + * == 0 we are in the [Unit] section, if > 0 we are in the unit type-specific section. */ + int last_section_private:2; diff --git a/SOURCES/0400-sd-bus-avoid-magic-number-in-SASL-length-calculation.patch b/SOURCES/0400-sd-bus-avoid-magic-number-in-SASL-length-calculation.patch new file mode 100644 index 0000000..f0bf8b7 --- /dev/null +++ b/SOURCES/0400-sd-bus-avoid-magic-number-in-SASL-length-calculation.patch @@ -0,0 +1,41 @@ +From d524f9f9089b71d83e991ed42d25a4616ec575a2 Mon Sep 17 00:00:00 2001 +From: David Rheinsberg +Date: Thu, 14 Mar 2019 13:26:50 +0100 +Subject: [PATCH] sd-bus: avoid magic number in SASL length calculation + +Lets avoid magic numbers and use a constant `strlen()` instead. + +Signed-off-by: David Rheinsberg +(cherry picked from commit 3cacdab925c40a5d9b7cf3f67719201bbaa17f67) + +Related: #1838081 +--- + src/libsystemd/sd-bus/bus-socket.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c +index 4a72795d2b..1c8b331b48 100644 +--- a/src/libsystemd/sd-bus/bus-socket.c ++++ b/src/libsystemd/sd-bus/bus-socket.c +@@ -390,7 +390,9 @@ static int bus_socket_auth_verify_server(sd_bus *b) { + + if (line_begins(line, l, "AUTH ANONYMOUS")) { + +- r = verify_anonymous_token(b, line + 14, l - 14); ++ r = verify_anonymous_token(b, ++ line + strlen("AUTH ANONYMOUS"), ++ l - strlen("AUTH ANONYMOUS")); + if (r < 0) + return r; + if (r == 0) +@@ -402,7 +404,9 @@ static int bus_socket_auth_verify_server(sd_bus *b) { + + } else if (line_begins(line, l, "AUTH EXTERNAL")) { + +- r = verify_external_token(b, line + 13, l - 13); ++ r = verify_external_token(b, ++ line + strlen("AUTH EXTERNAL"), ++ l - strlen("AUTH EXTERNAL")); + if (r < 0) + return r; + if (r == 0) diff --git a/SOURCES/0401-sd-bus-fix-SASL-reply-to-empty-AUTH.patch b/SOURCES/0401-sd-bus-fix-SASL-reply-to-empty-AUTH.patch new file mode 100644 index 0000000..636fad0 --- /dev/null +++ b/SOURCES/0401-sd-bus-fix-SASL-reply-to-empty-AUTH.patch @@ -0,0 +1,55 @@ +From badb16c481cf592a1761ad20dd0a84614d2bbd5b Mon Sep 17 00:00:00 2001 +From: David Rheinsberg +Date: Thu, 14 Mar 2019 13:33:28 +0100 +Subject: [PATCH] sd-bus: fix SASL reply to empty AUTH + +The correct way to reply to "AUTH " without any payload is to +send "DATA" rather than "OK". The "DATA" reply triggers the client to +respond with the requested payload. + +In fact, adding the data as hex-encoded argument like +"AUTH " is an optimization that skips the "DATA" +roundtrip. The standard way to perform an authentication is to send the +"DATA" line. + +This commit fixes sd-bus to properly send the "DATA" line. Surprisingly +no existing implementation depends on this, as they all pass the data +directly as argument to "AUTH". This will not work if we want to pass +an empty argument, though. + +Signed-off-by: David Rheinsberg +(cherry picked from commit 2010873b4b49b223e0cc07d28205b09c693ef005) + +Related: #1838081 +--- + src/libsystemd/sd-bus/bus-socket.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c +index 1c8b331b48..e505d43c6b 100644 +--- a/src/libsystemd/sd-bus/bus-socket.c ++++ b/src/libsystemd/sd-bus/bus-socket.c +@@ -399,7 +399,10 @@ static int bus_socket_auth_verify_server(sd_bus *b) { + r = bus_socket_auth_write(b, "REJECTED\r\n"); + else { + b->auth = BUS_AUTH_ANONYMOUS; +- r = bus_socket_auth_write_ok(b); ++ if (l <= strlen("AUTH ANONYMOUS")) ++ r = bus_socket_auth_write(b, "DATA\r\n"); ++ else ++ r = bus_socket_auth_write_ok(b); + } + + } else if (line_begins(line, l, "AUTH EXTERNAL")) { +@@ -413,7 +416,10 @@ static int bus_socket_auth_verify_server(sd_bus *b) { + r = bus_socket_auth_write(b, "REJECTED\r\n"); + else { + b->auth = BUS_AUTH_EXTERNAL; +- r = bus_socket_auth_write_ok(b); ++ if (l <= strlen("AUTH EXTERNAL")) ++ r = bus_socket_auth_write(b, "DATA\r\n"); ++ else ++ r = bus_socket_auth_write_ok(b); + } + + } else if (line_begins(line, l, "AUTH")) diff --git a/SOURCES/0402-sd-bus-skip-sending-formatted-UIDs-via-SASL.patch b/SOURCES/0402-sd-bus-skip-sending-formatted-UIDs-via-SASL.patch new file mode 100644 index 0000000..063be89 --- /dev/null +++ b/SOURCES/0402-sd-bus-skip-sending-formatted-UIDs-via-SASL.patch @@ -0,0 +1,210 @@ +From ccdb8883c6ff0e72d5e221768af0718e25cbf177 Mon Sep 17 00:00:00 2001 +From: David Rheinsberg +Date: Thu, 14 Mar 2019 13:34:13 +0100 +Subject: [PATCH] sd-bus: skip sending formatted UIDs via SASL + +The dbus external authentication takes as optional argument the UID the +sender wants to authenticate as. This uid is purely optional. The +AF_UNIX socket already conveys the same information through the +auxiliary socket data, so we really don't have to provide that +information. + +Unfortunately, there is no way to send empty arguments, since they are +interpreted as "missing argument", which has a different meaning. The +SASL negotiation thus changes from: + + AUTH EXTERNAL + NEGOTIATE_UNIX_FD (optional) + BEGIN + +to: + + AUTH EXTERNAL + DATA + NEGOTIATE_UNIX_FD (optional) + BEGIN + +And thus the replies we expect as a client change from: + + OK + AGREE_UNIX_FD (optional) + +to: + + DATA + OK + AGREE_UNIX_FD (optional) + +Since the old sd-bus server implementation used the wrong reply for +"AUTH" requests that do not carry the arguments inlined, we decided to +make sd-bus clients accept this as well. Hence, sd-bus now allows +"OK \r\n" replies instead of "DATA\r\n" replies. + +Signed-off-by: David Rheinsberg +(cherry picked from commit 1ed4723d38cd0d1423c8fe650f90fa86007ddf55) + +Resolves: #1838081 +--- + src/libsystemd/sd-bus/bus-socket.c | 106 +++++++++++++++++------------ + 1 file changed, 64 insertions(+), 42 deletions(-) + +diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c +index e505d43c6b..8813dd5efd 100644 +--- a/src/libsystemd/sd-bus/bus-socket.c ++++ b/src/libsystemd/sd-bus/bus-socket.c +@@ -162,17 +162,25 @@ static int bus_socket_write_auth(sd_bus *b) { + } + + static int bus_socket_auth_verify_client(sd_bus *b) { +- char *e, *f, *start; ++ char *d, *e, *f, *start; + sd_id128_t peer; + unsigned i; + int r; + + assert(b); + +- /* We expect two response lines: "OK" and possibly +- * "AGREE_UNIX_FD" */ ++ /* ++ * We expect three response lines: ++ * "DATA\r\n" ++ * "OK \r\n" ++ * "AGREE_UNIX_FD\r\n" (optional) ++ */ + +- e = memmem_safe(b->rbuffer, b->rbuffer_size, "\r\n", 2); ++ d = memmem_safe(b->rbuffer, b->rbuffer_size, "\r\n", 2); ++ if (!d) ++ return 0; ++ ++ e = memmem(d + 2, b->rbuffer_size - (d - (char*) b->rbuffer) - 2, "\r\n", 2); + if (!e) + return 0; + +@@ -187,13 +195,30 @@ static int bus_socket_auth_verify_client(sd_bus *b) { + start = e + 2; + } + +- /* Nice! We got all the lines we need. First check the OK +- * line */ ++ /* Nice! We got all the lines we need. First check the DATA line. */ ++ ++ if (d - (char*) b->rbuffer == 4) { ++ if (memcmp(b->rbuffer, "DATA", 4)) ++ return -EPERM; ++ } else if (d - (char*) b->rbuffer == 3 + 32) { ++ /* ++ * Old versions of the server-side implementation of `sd-bus` replied with "OK " to ++ * "AUTH" requests from a client, even if the "AUTH" line did not contain inlined ++ * arguments. Therefore, we also accept "OK " here, even though it is technically the ++ * wrong reply. We ignore the "" parameter, though, since it has no real value. ++ */ ++ if (memcmp(b->rbuffer, "OK ", 3)) ++ return -EPERM; ++ } else { ++ return -EPERM; ++ } ++ ++ /* Now check the OK line. */ + +- if (e - (char*) b->rbuffer != 3 + 32) ++ if (e - d != 2 + 3 + 32) + return -EPERM; + +- if (memcmp(b->rbuffer, "OK ", 3)) ++ if (memcmp(d + 2, "OK ", 3)) + return -EPERM; + + b->auth = b->anonymous_auth ? BUS_AUTH_ANONYMOUS : BUS_AUTH_EXTERNAL; +@@ -201,8 +226,8 @@ static int bus_socket_auth_verify_client(sd_bus *b) { + for (i = 0; i < 32; i += 2) { + int x, y; + +- x = unhexchar(((char*) b->rbuffer)[3 + i]); +- y = unhexchar(((char*) b->rbuffer)[3 + i + 1]); ++ x = unhexchar(d[2 + 3 + i]); ++ y = unhexchar(d[2 + 3 + i + 1]); + + if (x < 0 || y < 0) + return -EINVAL; +@@ -216,7 +241,7 @@ static int bus_socket_auth_verify_client(sd_bus *b) { + + b->server_id = peer; + +- /* And possibly check the second line, too */ ++ /* And possibly check the third line, too */ + + if (f) + b->can_fds = +@@ -616,42 +641,39 @@ static void bus_get_peercred(sd_bus *b) { + } + + static int bus_socket_start_auth_client(sd_bus *b) { +- size_t l; +- const char *auth_suffix, *auth_prefix; ++ static const char sasl_auth_anonymous[] = { ++ /* ++ * We use an arbitrary trace-string for the ANONYMOUS authentication. It can be used by the ++ * message broker to aid debugging of clients. We fully anonymize the connection and use a ++ * static default. ++ */ ++ "\0AUTH ANONYMOUS\r\n" ++ /* HEX a n o n y m o u s */ ++ "DATA 616e6f6e796d6f7573\r\n" ++ }; ++ static const char sasl_auth_external[] = { ++ "\0AUTH EXTERNAL\r\n" ++ "DATA\r\n" ++ }; ++ static const char sasl_negotiate_unix_fd[] = { ++ "NEGOTIATE_UNIX_FD\r\n" ++ }; ++ static const char sasl_begin[] = { ++ "BEGIN\r\n" ++ }; ++ size_t i = 0; + + assert(b); + +- if (b->anonymous_auth) { +- auth_prefix = "\0AUTH ANONYMOUS "; +- +- /* For ANONYMOUS auth we send some arbitrary "trace" string */ +- l = 9; +- b->auth_buffer = hexmem("anonymous", l); +- } else { +- char text[DECIMAL_STR_MAX(uid_t) + 1]; +- +- auth_prefix = "\0AUTH EXTERNAL "; +- +- xsprintf(text, UID_FMT, geteuid()); +- +- l = strlen(text); +- b->auth_buffer = hexmem(text, l); +- } +- +- if (!b->auth_buffer) +- return -ENOMEM; ++ if (b->anonymous_auth) ++ b->auth_iovec[i++] = IOVEC_MAKE((char*) sasl_auth_anonymous, sizeof(sasl_auth_anonymous) - 1); ++ else ++ b->auth_iovec[i++] = IOVEC_MAKE((char*) sasl_auth_external, sizeof(sasl_auth_external) - 1); + + if (b->accept_fd) +- auth_suffix = "\r\nNEGOTIATE_UNIX_FD\r\nBEGIN\r\n"; +- else +- auth_suffix = "\r\nBEGIN\r\n"; +- +- b->auth_iovec[0].iov_base = (void*) auth_prefix; +- b->auth_iovec[0].iov_len = 1 + strlen(auth_prefix + 1); +- b->auth_iovec[1].iov_base = (void*) b->auth_buffer; +- b->auth_iovec[1].iov_len = l * 2; +- b->auth_iovec[2].iov_base = (void*) auth_suffix; +- b->auth_iovec[2].iov_len = strlen(auth_suffix); ++ b->auth_iovec[i++] = IOVEC_MAKE_STRING(sasl_negotiate_unix_fd); ++ ++ b->auth_iovec[i++] = IOVEC_MAKE_STRING(sasl_begin); + + return bus_socket_write_auth(b); + } diff --git a/SOURCES/0403-core-add-MemoryMin.patch b/SOURCES/0403-core-add-MemoryMin.patch new file mode 100644 index 0000000..eadeaed --- /dev/null +++ b/SOURCES/0403-core-add-MemoryMin.patch @@ -0,0 +1,231 @@ +From 04f21709ff081a5f1a2b5ca746582a9c5c03db7f Mon Sep 17 00:00:00 2001 +From: Tejun Heo +Date: Fri, 8 Jun 2018 17:33:14 -0700 +Subject: [PATCH] core: add MemoryMin + +The kernel added support for a new cgroup memory controller knob memory.min in +bf8d5d52ffe8 ("memcg: introduce memory.min") which was merged during v4.18 +merge window. + +Add MemoryMin to support memory.min. + +(cherry picked from commit 484226357789991de0b3363beb69258be06b4c92) + +Resolves: #1763435 +--- + doc/TRANSIENT-SETTINGS.md | 1 + + man/systemd.resource-control.xml | 21 +++++++++++++++++++++ + src/core/cgroup.c | 5 ++++- + src/core/cgroup.h | 1 + + src/core/dbus-cgroup.c | 7 +++++++ + src/core/load-fragment-gperf.gperf.m4 | 1 + + src/core/load-fragment.c | 4 +++- + src/shared/bus-unit-util.c | 2 +- + src/systemctl/systemctl.c | 11 +++++++++-- + 9 files changed, 48 insertions(+), 5 deletions(-) + +diff --git a/doc/TRANSIENT-SETTINGS.md b/doc/TRANSIENT-SETTINGS.md +index 0d2d3e9065..93865c0333 100644 +--- a/doc/TRANSIENT-SETTINGS.md ++++ b/doc/TRANSIENT-SETTINGS.md +@@ -222,6 +222,7 @@ All cgroup/resource control settings are available for transient units + ✓ AllowedCPUs= + ✓ AllowedMemoryNodes= + ✓ MemoryAccounting= ++✓ MemoryMin= + ✓ MemoryLow= + ✓ MemoryHigh= + ✓ MemoryMax= +diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml +index cfe19a6574..63a0c87565 100644 +--- a/man/systemd.resource-control.xml ++++ b/man/systemd.resource-control.xml +@@ -265,6 +265,27 @@ + + + ++ ++ MemoryMin=bytes ++ ++ ++ Specify the memory usage protection of the executed processes in this unit. If the memory usages of ++ this unit and all its ancestors are below their minimum boundaries, this unit's memory won't be reclaimed. ++ ++ Takes a memory size in bytes. If the value is suffixed with K, M, G or T, the specified memory size is ++ parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. Alternatively, a ++ percentage value may be specified, which is taken relative to the installed physical memory on the ++ system. This controls the memory.min control group attribute. For details about this ++ control group attribute, see cgroup-v2.txt. ++ ++ Implies MemoryAccounting=true. ++ ++ This setting is supported only if the unified control group hierarchy is used and disables ++ MemoryLimit=. ++ ++ ++ + + MemoryLow=bytes + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index f8b351a65d..9d09c65453 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -220,6 +220,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + "%sStartupIOWeight=%" PRIu64 "\n" + "%sBlockIOWeight=%" PRIu64 "\n" + "%sStartupBlockIOWeight=%" PRIu64 "\n" ++ "%sMemoryMin=%" PRIu64 "\n" + "%sMemoryLow=%" PRIu64 "\n" + "%sMemoryHigh=%" PRIu64 "\n" + "%sMemoryMax=%" PRIu64 "\n" +@@ -246,6 +247,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + prefix, c->startup_io_weight, + prefix, c->blockio_weight, + prefix, c->startup_blockio_weight, ++ prefix, c->memory_min, + prefix, c->memory_low, + prefix, c->memory_high, + prefix, c->memory_max, +@@ -777,7 +779,7 @@ static void cgroup_apply_blkio_device_limit(Unit *u, const char *dev_path, uint6 + } + + static bool cgroup_context_has_unified_memory_config(CGroupContext *c) { +- return c->memory_low > 0 || c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX || c->memory_swap_max != CGROUP_LIMIT_MAX; ++ return c->memory_min > 0 || c->memory_low > 0 || c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX || c->memory_swap_max != CGROUP_LIMIT_MAX; + } + + static void cgroup_apply_unified_memory_limit(Unit *u, const char *file, uint64_t v) { +@@ -1035,6 +1037,7 @@ static void cgroup_context_apply( + log_cgroup_compat(u, "Applying MemoryLimit %" PRIu64 " as MemoryMax", max); + } + ++ cgroup_apply_unified_memory_limit(u, "memory.min", c->memory_min); + cgroup_apply_unified_memory_limit(u, "memory.low", c->memory_low); + cgroup_apply_unified_memory_limit(u, "memory.high", c->memory_high); + cgroup_apply_unified_memory_limit(u, "memory.max", max); +diff --git a/src/core/cgroup.h b/src/core/cgroup.h +index 2ba57d3ded..5e1be87b20 100644 +--- a/src/core/cgroup.h ++++ b/src/core/cgroup.h +@@ -95,6 +95,7 @@ struct CGroupContext { + LIST_HEAD(CGroupIODeviceLimit, io_device_limits); + LIST_HEAD(CGroupIODeviceLatency, io_device_latencies); + ++ uint64_t memory_min; + uint64_t memory_low; + uint64_t memory_high; + uint64_t memory_max; +diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c +index 4555b33b1f..6ce5984a02 100644 +--- a/src/core/dbus-cgroup.c ++++ b/src/core/dbus-cgroup.c +@@ -353,6 +353,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = { + SD_BUS_PROPERTY("BlockIOReadBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0), + SD_BUS_PROPERTY("BlockIOWriteBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0), + SD_BUS_PROPERTY("MemoryAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, memory_accounting), 0), ++ SD_BUS_PROPERTY("MemoryMin", "t", NULL, offsetof(CGroupContext, memory_min), 0), + SD_BUS_PROPERTY("MemoryLow", "t", NULL, offsetof(CGroupContext, memory_low), 0), + SD_BUS_PROPERTY("MemoryHigh", "t", NULL, offsetof(CGroupContext, memory_high), 0), + SD_BUS_PROPERTY("MemoryMax", "t", NULL, offsetof(CGroupContext, memory_max), 0), +@@ -661,6 +662,9 @@ int bus_cgroup_set_property( + if (streq(name, "MemoryAccounting")) + return bus_cgroup_set_boolean(u, name, &c->memory_accounting, CGROUP_MASK_MEMORY, message, flags, error); + ++ if (streq(name, "MemoryMin")) ++ return bus_cgroup_set_memory(u, name, &c->memory_min, message, flags, error); ++ + if (streq(name, "MemoryLow")) + return bus_cgroup_set_memory(u, name, &c->memory_low, message, flags, error); + +@@ -676,6 +680,9 @@ int bus_cgroup_set_property( + if (streq(name, "MemoryLimit")) + return bus_cgroup_set_memory(u, name, &c->memory_limit, message, flags, error); + ++ if (streq(name, "MemoryMinScale")) ++ return bus_cgroup_set_memory_scale(u, name, &c->memory_min, message, flags, error); ++ + if (streq(name, "MemoryLowScale")) + return bus_cgroup_set_memory_scale(u, name, &c->memory_low, message, flags, error); + +diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 +index 4defa82ac1..1c6e539f30 100644 +--- a/src/core/load-fragment-gperf.gperf.m4 ++++ b/src/core/load-fragment-gperf.gperf.m4 +@@ -171,6 +171,7 @@ $1.StartupCPUShares, config_parse_cpu_shares, 0, + $1.CPUQuota, config_parse_cpu_quota, 0, offsetof($1, cgroup_context) + $1.CPUQuotaPeriodSec, config_parse_sec_def_infinity, 0, offsetof($1, cgroup_context.cpu_quota_period_usec) + $1.MemoryAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.memory_accounting) ++$1.MemoryMin, config_parse_memory_limit, 0, offsetof($1, cgroup_context) + $1.MemoryLow, config_parse_memory_limit, 0, offsetof($1, cgroup_context) + $1.MemoryHigh, config_parse_memory_limit, 0, offsetof($1, cgroup_context) + $1.MemoryMax, config_parse_memory_limit, 0, offsetof($1, cgroup_context) +diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c +index 762b106007..d43b0f08f9 100644 +--- a/src/core/load-fragment.c ++++ b/src/core/load-fragment.c +@@ -3096,7 +3096,9 @@ int config_parse_memory_limit( + } + } + +- if (streq(lvalue, "MemoryLow")) ++ if (streq(lvalue, "MemoryMin")) ++ c->memory_min = bytes; ++ else if (streq(lvalue, "MemoryLow")) + c->memory_low = bytes; + else if (streq(lvalue, "MemoryHigh")) + c->memory_high = bytes; +diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c +index ec45d6f86d..203c068d02 100644 +--- a/src/shared/bus-unit-util.c ++++ b/src/shared/bus-unit-util.c +@@ -429,7 +429,7 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons + return 1; + } + +- if (STR_IN_SET(field, "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit", "TasksMax")) { ++ if (STR_IN_SET(field, "MemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit", "TasksMax")) { + + if (isempty(eq) || streq(eq, "infinity")) { + r = sd_bus_message_append(m, "(sv)", field, "t", CGROUP_LIMIT_MAX); +diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c +index 559e49f104..35ad20f510 100644 +--- a/src/systemctl/systemctl.c ++++ b/src/systemctl/systemctl.c +@@ -3905,6 +3905,7 @@ typedef struct UnitStatusInfo { + + /* CGroup */ + uint64_t memory_current; ++ uint64_t memory_min; + uint64_t memory_low; + uint64_t memory_high; + uint64_t memory_max; +@@ -4284,12 +4285,17 @@ static void print_status_info( + + printf(" Memory: %s", format_bytes(buf, sizeof(buf), i->memory_current)); + +- if (i->memory_low > 0 || i->memory_high != CGROUP_LIMIT_MAX || +- i->memory_max != CGROUP_LIMIT_MAX || i->memory_swap_max != CGROUP_LIMIT_MAX || ++ if (i->memory_min > 0 || i->memory_low > 0 || ++ i->memory_high != CGROUP_LIMIT_MAX || i->memory_max != CGROUP_LIMIT_MAX || ++ i->memory_swap_max != CGROUP_LIMIT_MAX || + i->memory_limit != CGROUP_LIMIT_MAX) { + const char *prefix = ""; + + printf(" ("); ++ if (i->memory_min > 0) { ++ printf("%smin: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_min)); ++ prefix = " "; ++ } + if (i->memory_low > 0) { + printf("%slow: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_low)); + prefix = " "; +@@ -5022,6 +5028,7 @@ static int show_one( + { "Where", "s", NULL, offsetof(UnitStatusInfo, where) }, + { "What", "s", NULL, offsetof(UnitStatusInfo, what) }, + { "MemoryCurrent", "t", NULL, offsetof(UnitStatusInfo, memory_current) }, ++ { "MemoryMin", "t", NULL, offsetof(UnitStatusInfo, memory_min) }, + { "MemoryLow", "t", NULL, offsetof(UnitStatusInfo, memory_low) }, + { "MemoryHigh", "t", NULL, offsetof(UnitStatusInfo, memory_high) }, + { "MemoryMax", "t", NULL, offsetof(UnitStatusInfo, memory_max) }, diff --git a/SOURCES/0404-core-introduce-cgroup_add_device_allow.patch b/SOURCES/0404-core-introduce-cgroup_add_device_allow.patch new file mode 100644 index 0000000..351e10a --- /dev/null +++ b/SOURCES/0404-core-introduce-cgroup_add_device_allow.patch @@ -0,0 +1,98 @@ +From d8024b3de8ce376cdea48ffa59a44b050f215470 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 6 Aug 2018 13:42:14 +0900 +Subject: [PATCH] core: introduce cgroup_add_device_allow() + +(cherry picked from commit fd870bac25c2dd36affaed0251b5a7023f635306) + +Related: #1763435 +--- + src/core/cgroup.c | 29 +++++++++++++++++++++++++++++ + src/core/cgroup.h | 2 ++ + src/core/load-fragment.c | 13 +------------ + 3 files changed, 32 insertions(+), 12 deletions(-) + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index 9d09c65453..a17b38f914 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -341,6 +341,35 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + } + } + ++int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode) { ++ _cleanup_free_ CGroupDeviceAllow *a = NULL; ++ _cleanup_free_ char *d = NULL; ++ ++ assert(c); ++ assert(dev); ++ assert(isempty(mode) || in_charset(mode, "rwm")); ++ ++ a = new(CGroupDeviceAllow, 1); ++ if (!a) ++ return -ENOMEM; ++ ++ d = strdup(dev); ++ if (!d) ++ return -ENOMEM; ++ ++ *a = (CGroupDeviceAllow) { ++ .path = TAKE_PTR(d), ++ .r = isempty(mode) || !!strchr(mode, 'r'), ++ .w = isempty(mode) || !!strchr(mode, 'w'), ++ .m = isempty(mode) || !!strchr(mode, 'm'), ++ }; ++ ++ LIST_PREPEND(device_allow, c->device_allow, a); ++ TAKE_PTR(a); ++ ++ return 0; ++} ++ + static int lookup_block_device(const char *p, dev_t *ret) { + struct stat st; + int r; +diff --git a/src/core/cgroup.h b/src/core/cgroup.h +index 5e1be87b20..8102b442b8 100644 +--- a/src/core/cgroup.h ++++ b/src/core/cgroup.h +@@ -153,6 +153,8 @@ void cgroup_context_free_io_device_latency(CGroupContext *c, CGroupIODeviceLaten + void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w); + void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b); + ++int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode); ++ + CGroupMask unit_get_own_mask(Unit *u); + CGroupMask unit_get_delegate_mask(Unit *u); + CGroupMask unit_get_members_mask(Unit *u); +diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c +index d43b0f08f9..89a3457acc 100644 +--- a/src/core/load-fragment.c ++++ b/src/core/load-fragment.c +@@ -3250,7 +3250,6 @@ int config_parse_device_allow( + + _cleanup_free_ char *path = NULL, *resolved = NULL; + CGroupContext *c = data; +- CGroupDeviceAllow *a; + const char *p = rvalue; + int r; + +@@ -3299,17 +3298,7 @@ int config_parse_device_allow( + return 0; + } + +- a = new0(CGroupDeviceAllow, 1); +- if (!a) +- return log_oom(); +- +- a->path = TAKE_PTR(resolved); +- a->r = isempty(p) || !!strchr(p, 'r'); +- a->w = isempty(p) || !!strchr(p, 'w'); +- a->m = isempty(p) || !!strchr(p, 'm'); +- +- LIST_PREPEND(device_allow, c->device_allow, a); +- return 0; ++ return cgroup_add_device_allow(c, resolved, p); + } + + int config_parse_io_device_weight( diff --git a/SOURCES/0405-test-remove-support-for-suffix-in-get_testdata_dir.patch b/SOURCES/0405-test-remove-support-for-suffix-in-get_testdata_dir.patch new file mode 100644 index 0000000..811da17 --- /dev/null +++ b/SOURCES/0405-test-remove-support-for-suffix-in-get_testdata_dir.patch @@ -0,0 +1,302 @@ +From e73aa709bff2eb5c4649ed7c7055f29ca42f52aa Mon Sep 17 00:00:00 2001 +From: Filipe Brandenburger +Date: Tue, 11 Sep 2018 23:15:09 -0700 +Subject: [PATCH] test: remove support for suffix in get_testdata_dir() + +Instead, use path_join() in callers wherever needed. + +(cherry picked from commit 55890a40c3ec0c061c04d1395a38c26313132d12) + +Related: #1763435 +--- + src/resolve/test-dns-packet.c | 5 ++++- + src/shared/tests.c | 4 +--- + src/shared/tests.h | 2 +- + src/test/test-bpf.c | 2 +- + src/test/test-cgroup-mask.c | 2 +- + src/test/test-engine.c | 2 +- + src/test/test-execute.c | 3 ++- + src/test/test-journal-importer.c | 10 ++++++++-- + src/test/test-path.c | 5 ++++- + src/test/test-sched-prio.c | 2 +- + src/test/test-umount.c | 18 ++++++++++++++---- + src/test/test-watch-pid.c | 2 +- + 12 files changed, 39 insertions(+), 18 deletions(-) + +diff --git a/src/resolve/test-dns-packet.c b/src/resolve/test-dns-packet.c +index 905f000dc2..0dac05e7be 100644 +--- a/src/resolve/test-dns-packet.c ++++ b/src/resolve/test-dns-packet.c +@@ -12,6 +12,7 @@ + #include "macro.h" + #include "resolved-dns-packet.h" + #include "resolved-dns-rr.h" ++#include "path-util.h" + #include "string-util.h" + #include "strv.h" + #include "tests.h" +@@ -92,6 +93,7 @@ static void test_packet_from_file(const char* filename, bool canonical) { + + int main(int argc, char **argv) { + int i, N; ++ _cleanup_free_ char *pkts_glob = NULL; + _cleanup_globfree_ glob_t g = {}; + char **fnames; + +@@ -101,7 +103,8 @@ int main(int argc, char **argv) { + N = argc - 1; + fnames = argv + 1; + } else { +- assert_se(glob(get_testdata_dir("/test-resolve/*.pkts"), GLOB_NOSORT, NULL, &g) == 0); ++ pkts_glob = path_join(NULL, get_testdata_dir(), "test-resolve/*.pkts"); ++ assert_se(glob(pkts_glob, GLOB_NOSORT, NULL, &g) == 0); + N = g.gl_pathc; + fnames = g.gl_pathv; + } +diff --git a/src/shared/tests.c b/src/shared/tests.c +index c77eb00924..100b62b9b0 100644 +--- a/src/shared/tests.c ++++ b/src/shared/tests.c +@@ -37,7 +37,7 @@ bool test_is_running_from_builddir(char **exedir) { + return r; + } + +-const char* get_testdata_dir(const char *suffix) { ++const char* get_testdata_dir(void) { + const char *env; + /* convenience: caller does not need to free result */ + static char testdir[PATH_MAX]; +@@ -61,14 +61,12 @@ const char* get_testdata_dir(const char *suffix) { + /* Try relative path, according to the install-test layout */ + assert_se(snprintf(testdir, sizeof(testdir), "%s/testdata", exedir) > 0); + +- /* test this without the suffix, as it may contain a glob */ + if (access(testdir, F_OK) < 0) { + fputs("ERROR: Cannot find testdata directory, set $SYSTEMD_TEST_DATA\n", stderr); + exit(EXIT_FAILURE); + } + } + +- strncpy(testdir + strlen(testdir), suffix, sizeof(testdir) - strlen(testdir) - 1); + return testdir; + } + +diff --git a/src/shared/tests.h b/src/shared/tests.h +index 7f45c32d32..3d696d02fd 100644 +--- a/src/shared/tests.h ++++ b/src/shared/tests.h +@@ -3,5 +3,5 @@ + + char* setup_fake_runtime_dir(void); + bool test_is_running_from_builddir(char **exedir); +-const char* get_testdata_dir(const char *suffix); ++const char* get_testdata_dir(void); + void test_setup_logging(int level); +diff --git a/src/test/test-bpf.c b/src/test/test-bpf.c +index 4d89bd46d3..6f4a22a1cc 100644 +--- a/src/test/test-bpf.c ++++ b/src/test/test-bpf.c +@@ -38,7 +38,7 @@ int main(int argc, char *argv[]) { + return EXIT_TEST_SKIP; + } + +- assert_se(set_unit_path(get_testdata_dir("")) >= 0); ++ assert_se(set_unit_path(get_testdata_dir()) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + + r = bpf_program_new(BPF_PROG_TYPE_CGROUP_SKB, &p); +diff --git a/src/test/test-cgroup-mask.c b/src/test/test-cgroup-mask.c +index 93c3f5d856..ed2d810dd6 100644 +--- a/src/test/test-cgroup-mask.c ++++ b/src/test/test-cgroup-mask.c +@@ -26,7 +26,7 @@ static int test_cgroup_mask(void) { + } + + /* Prepare the manager. */ +- assert_se(set_unit_path(get_testdata_dir("")) >= 0); ++ assert_se(set_unit_path(get_testdata_dir()) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + r = manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_BASIC, &m); + if (IN_SET(r, -EPERM, -EACCES)) { +diff --git a/src/test/test-engine.c b/src/test/test-engine.c +index d072a15cb1..0f3e244dc1 100644 +--- a/src/test/test-engine.c ++++ b/src/test/test-engine.c +@@ -29,7 +29,7 @@ int main(int argc, char *argv[]) { + } + + /* prepare the test */ +- assert_se(set_unit_path(get_testdata_dir("")) >= 0); ++ assert_se(set_unit_path(get_testdata_dir()) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + r = manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_BASIC, &m); + if (MANAGER_SKIP_TEST(r)) { +diff --git a/src/test/test-execute.c b/src/test/test-execute.c +index 882e866ea9..294f8fe7dd 100644 +--- a/src/test/test-execute.c ++++ b/src/test/test-execute.c +@@ -815,7 +815,8 @@ int main(int argc, char *argv[]) { + } + + assert_se(runtime_dir = setup_fake_runtime_dir()); +- assert_se(set_unit_path(get_testdata_dir("/test-execute")) >= 0); ++ test_execute_path = path_join(NULL, get_testdata_dir(), "test-execute"); ++ assert_se(set_unit_path(test_execute_path) >= 0); + + /* Unset VAR1, VAR2 and VAR3 which are used in the PassEnvironment test + * cases, otherwise (and if they are present in the environment), +diff --git a/src/test/test-journal-importer.c b/src/test/test-journal-importer.c +index 56bf6a1296..8f09d5ad2f 100644 +--- a/src/test/test-journal-importer.c ++++ b/src/test/test-journal-importer.c +@@ -4,8 +4,10 @@ + #include + #include + ++#include "alloc-util.h" + #include "log.h" + #include "journal-importer.h" ++#include "path-util.h" + #include "string-util.h" + #include "tests.h" + +@@ -20,9 +22,11 @@ static void assert_iovec_entry(const struct iovec *iovec, const char* content) { + + static void test_basic_parsing(void) { + _cleanup_(journal_importer_cleanup) JournalImporter imp = {}; ++ _cleanup_free_ char *journal_data_path = NULL; + int r; + +- imp.fd = open(get_testdata_dir("/journal-data/journal-1.txt"), O_RDONLY|O_CLOEXEC); ++ journal_data_path = path_join(NULL, get_testdata_dir(), "journal-data/journal-1.txt"); ++ imp.fd = open(journal_data_path, O_RDONLY|O_CLOEXEC); + assert_se(imp.fd >= 0); + + do +@@ -49,9 +53,11 @@ static void test_basic_parsing(void) { + + static void test_bad_input(void) { + _cleanup_(journal_importer_cleanup) JournalImporter imp = {}; ++ _cleanup_free_ char *journal_data_path = NULL; + int r; + +- imp.fd = open(get_testdata_dir("/journal-data/journal-2.txt"), O_RDONLY|O_CLOEXEC); ++ journal_data_path = path_join(NULL, get_testdata_dir(), "journal-data/journal-2.txt"); ++ imp.fd = open(journal_data_path, O_RDONLY|O_CLOEXEC); + assert_se(imp.fd >= 0); + + do +diff --git a/src/test/test-path.c b/src/test/test-path.c +index 3a1469ae02..faae142696 100644 +--- a/src/test/test-path.c ++++ b/src/test/test-path.c +@@ -12,6 +12,7 @@ + #include "macro.h" + #include "manager.h" + #include "mkdir.h" ++#include "path-util.h" + #include "rm-rf.h" + #include "string-util.h" + #include "strv.h" +@@ -247,6 +248,7 @@ int main(int argc, char *argv[]) { + }; + + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; ++ _cleanup_free_ char *test_path = NULL; + const test_function_t *test = NULL; + Manager *m = NULL; + +@@ -255,7 +257,8 @@ int main(int argc, char *argv[]) { + log_parse_environment(); + log_open(); + +- assert_se(set_unit_path(get_testdata_dir("/test-path")) >= 0); ++ test_path = path_join(NULL, get_testdata_dir(), "test-path"); ++ assert_se(set_unit_path(test_path) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + + for (test = tests; test && *test; test++) { +diff --git a/src/test/test-sched-prio.c b/src/test/test-sched-prio.c +index c986284155..60012e47d2 100644 +--- a/src/test/test-sched-prio.c ++++ b/src/test/test-sched-prio.c +@@ -26,7 +26,7 @@ int main(int argc, char *argv[]) { + } + + /* prepare the test */ +- assert_se(set_unit_path(get_testdata_dir("")) >= 0); ++ assert_se(set_unit_path(get_testdata_dir()) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + r = manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_BASIC, &m); + if (MANAGER_SKIP_TEST(r)) { +diff --git a/src/test/test-umount.c b/src/test/test-umount.c +index 770d1a73c8..c068f7a0f0 100644 +--- a/src/test/test-umount.c ++++ b/src/test/test-umount.c +@@ -1,6 +1,8 @@ + /* SPDX-License-Identifier: LGPL-2.1+ */ + ++#include "alloc-util.h" + #include "log.h" ++#include "path-util.h" + #include "string-util.h" + #include "tests.h" + #include "umount.h" +@@ -8,10 +10,14 @@ + + static void test_mount_points_list(const char *fname) { + _cleanup_(mount_points_list_free) LIST_HEAD(MountPoint, mp_list_head); ++ _cleanup_free_ char *testdata_fname = NULL; + MountPoint *m; + + log_info("/* %s(\"%s\") */", __func__, fname ?: "/proc/self/mountinfo"); + ++ if (fname) ++ fname = testdata_fname = path_join(NULL, get_testdata_dir(), fname); ++ + LIST_HEAD_INIT(mp_list_head); + assert_se(mount_points_list_get(fname, &mp_list_head) >= 0); + +@@ -26,10 +32,14 @@ static void test_mount_points_list(const char *fname) { + + static void test_swap_list(const char *fname) { + _cleanup_(mount_points_list_free) LIST_HEAD(MountPoint, mp_list_head); ++ _cleanup_free_ char *testdata_fname = NULL; + MountPoint *m; + + log_info("/* %s(\"%s\") */", __func__, fname ?: "/proc/swaps"); + ++ if (fname) ++ fname = testdata_fname = path_join(NULL, get_testdata_dir(), fname); ++ + LIST_HEAD_INIT(mp_list_head); + assert_se(swap_list_get(fname, &mp_list_head) >= 0); + +@@ -48,10 +58,10 @@ int main(int argc, char **argv) { + log_open(); + + test_mount_points_list(NULL); +- test_mount_points_list(get_testdata_dir("/test-umount/empty.mountinfo")); +- test_mount_points_list(get_testdata_dir("/test-umount/garbled.mountinfo")); +- test_mount_points_list(get_testdata_dir("/test-umount/rhbug-1554943.mountinfo")); ++ test_mount_points_list("/test-umount/empty.mountinfo"); ++ test_mount_points_list("/test-umount/garbled.mountinfo"); ++ test_mount_points_list("/test-umount/rhbug-1554943.mountinfo"); + + test_swap_list(NULL); +- test_swap_list(get_testdata_dir("/test-umount/example.swaps")); ++ test_swap_list("/test-umount/example.swaps"); + } +diff --git a/src/test/test-watch-pid.c b/src/test/test-watch-pid.c +index 8c70175aed..d6e2886dde 100644 +--- a/src/test/test-watch-pid.c ++++ b/src/test/test-watch-pid.c +@@ -28,7 +28,7 @@ int main(int argc, char *argv[]) { + return EXIT_TEST_SKIP; + } + +- assert_se(set_unit_path(get_testdata_dir("")) >= 0); ++ assert_se(set_unit_path(get_testdata_dir()) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + + assert_se(manager_new(UNIT_FILE_USER, true, &m) >= 0); diff --git a/SOURCES/0406-cgroup-Implement-default-propagation-of-MemoryLow-wi.patch b/SOURCES/0406-cgroup-Implement-default-propagation-of-MemoryLow-wi.patch new file mode 100644 index 0000000..cbd2d37 --- /dev/null +++ b/SOURCES/0406-cgroup-Implement-default-propagation-of-MemoryLow-wi.patch @@ -0,0 +1,695 @@ +From f45cb6d6a2c247c7594d9965cbb745303adb61d6 Mon Sep 17 00:00:00 2001 +From: Chris Down +Date: Thu, 28 Mar 2019 12:50:50 +0000 +Subject: [PATCH] cgroup: Implement default propagation of MemoryLow with + DefaultMemoryLow + +In cgroup v2 we have protection tunables -- currently MemoryLow and +MemoryMin (there will be more in future for other resources, too). The +design of these protection tunables requires not only intermediate +cgroups to propagate protections, but also the units at the leaf of that +resource's operation to accept it (by setting MemoryLow or MemoryMin). + +This makes sense from an low-level API design perspective, but it's a +good idea to also have a higher-level abstraction that can, by default, +propagate these resources to children recursively. In this patch, this +happens by having descendants set memory.low to N if their ancestor has +DefaultMemoryLow=N -- assuming they don't set a separate MemoryLow +value. + +Any affected unit can opt out of this propagation by manually setting +`MemoryLow` to some value in its unit configuration. A unit can also +stop further propagation by setting `DefaultMemoryLow=` with no +argument. This removes further propagation in the subtree, but has no +effect on the unit itself (for that, use `MemoryLow=0`). + +Our use case in production is simplifying the configuration of machines +which heavily rely on memory protection tunables, but currently require +tweaking a huge number of unit files to make that a reality. This +directive makes that significantly less fragile, and decreases the risk +of misconfiguration. + +After this patch is merged, I will implement DefaultMemoryMin= using the +same principles. + +(cherry picked from commit c52db42b78f6fbeb7792cc4eca27e2767a48b6ca) + +Related: #1763435 +--- + doc/TRANSIENT-SETTINGS.md | 1 + + man/systemd.resource-control.xml | 4 + + src/core/cgroup.c | 58 +++++++++-- + src/core/cgroup.h | 6 ++ + src/core/dbus-cgroup.c | 7 ++ + src/core/load-fragment-gperf.gperf.m4 | 1 + + src/core/load-fragment.c | 13 ++- + src/shared/bus-unit-util.c | 2 +- + src/shared/bus-util.c | 2 +- + src/systemctl/systemctl.c | 3 + + src/test/meson.build | 6 ++ + src/test/test-cgroup-unit-default.c | 145 ++++++++++++++++++++++++++ + test/dml-discard-empty.service | 7 ++ + test/dml-discard-set-ml.service | 8 ++ + test/dml-discard.slice | 5 + + test/dml-override-empty.service | 7 ++ + test/dml-override.slice | 5 + + test/dml-passthrough-empty.service | 7 ++ + test/dml-passthrough-set-dml.service | 8 ++ + test/dml-passthrough-set-ml.service | 8 ++ + test/dml-passthrough.slice | 5 + + test/dml.slice | 5 + + test/meson.build | 10 ++ + 23 files changed, 310 insertions(+), 13 deletions(-) + create mode 100644 src/test/test-cgroup-unit-default.c + create mode 100644 test/dml-discard-empty.service + create mode 100644 test/dml-discard-set-ml.service + create mode 100644 test/dml-discard.slice + create mode 100644 test/dml-override-empty.service + create mode 100644 test/dml-override.slice + create mode 100644 test/dml-passthrough-empty.service + create mode 100644 test/dml-passthrough-set-dml.service + create mode 100644 test/dml-passthrough-set-ml.service + create mode 100644 test/dml-passthrough.slice + create mode 100644 test/dml.slice + +diff --git a/doc/TRANSIENT-SETTINGS.md b/doc/TRANSIENT-SETTINGS.md +index 93865c0333..5a8fa0727e 100644 +--- a/doc/TRANSIENT-SETTINGS.md ++++ b/doc/TRANSIENT-SETTINGS.md +@@ -223,6 +223,7 @@ All cgroup/resource control settings are available for transient units + ✓ AllowedMemoryNodes= + ✓ MemoryAccounting= + ✓ MemoryMin= ++✓ DefaultMemoryLow= + ✓ MemoryLow= + ✓ MemoryHigh= + ✓ MemoryMax= +diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml +index 63a0c87565..27f16001dd 100644 +--- a/man/systemd.resource-control.xml ++++ b/man/systemd.resource-control.xml +@@ -305,6 +305,10 @@ + + This setting is supported only if the unified control group hierarchy is used and disables + MemoryLimit=. ++ ++ Units may can have their children use a default memory.low value by specifying ++ DefaultMemoryLow=, which has the same usage as MemoryLow=. This setting ++ does not affect memory.low in the unit itself. + + + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index a17b38f914..f804bf4727 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -220,6 +220,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + "%sStartupIOWeight=%" PRIu64 "\n" + "%sBlockIOWeight=%" PRIu64 "\n" + "%sStartupBlockIOWeight=%" PRIu64 "\n" ++ "%sDefaultMemoryLow=%" PRIu64 "\n" + "%sMemoryMin=%" PRIu64 "\n" + "%sMemoryLow=%" PRIu64 "\n" + "%sMemoryHigh=%" PRIu64 "\n" +@@ -247,6 +248,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + prefix, c->startup_io_weight, + prefix, c->blockio_weight, + prefix, c->startup_blockio_weight, ++ prefix, c->default_memory_low, + prefix, c->memory_min, + prefix, c->memory_low, + prefix, c->memory_high, +@@ -370,6 +372,32 @@ int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode) + return 0; + } + ++uint64_t unit_get_ancestor_memory_low(Unit *u) { ++ CGroupContext *c; ++ ++ /* 1. Is MemoryLow set in this unit? If so, use that. ++ * 2. Is DefaultMemoryLow set in any ancestor? If so, use that. ++ * 3. Otherwise, return CGROUP_LIMIT_MIN. */ ++ ++ assert(u); ++ ++ c = unit_get_cgroup_context(u); ++ ++ if (c->memory_low_set) ++ return c->memory_low; ++ ++ while (UNIT_ISSET(u->slice)) { ++ u = UNIT_DEREF(u->slice); ++ c = unit_get_cgroup_context(u); ++ ++ if (c->default_memory_low_set) ++ return c->default_memory_low; ++ } ++ ++ /* We've reached the root, but nobody had DefaultMemoryLow set, so set it to the kernel default. */ ++ return CGROUP_LIMIT_MIN; ++} ++ + static int lookup_block_device(const char *p, dev_t *ret) { + struct stat st; + int r; +@@ -807,8 +835,17 @@ static void cgroup_apply_blkio_device_limit(Unit *u, const char *dev_path, uint6 + "Failed to set blkio.throttle.write_bps_device: %m"); + } + +-static bool cgroup_context_has_unified_memory_config(CGroupContext *c) { +- return c->memory_min > 0 || c->memory_low > 0 || c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX || c->memory_swap_max != CGROUP_LIMIT_MAX; ++static bool unit_has_unified_memory_config(Unit *u) { ++ CGroupContext *c; ++ ++ assert(u); ++ ++ c = unit_get_cgroup_context(u); ++ assert(c); ++ ++ return c->memory_min > 0 || unit_get_ancestor_memory_low(u) > 0 || ++ c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX || ++ c->memory_swap_max != CGROUP_LIMIT_MAX; + } + + static void cgroup_apply_unified_memory_limit(Unit *u, const char *file, uint64_t v) { +@@ -1056,7 +1093,7 @@ static void cgroup_context_apply( + if (cg_all_unified() > 0) { + uint64_t max, swap_max = CGROUP_LIMIT_MAX; + +- if (cgroup_context_has_unified_memory_config(c)) { ++ if (unit_has_unified_memory_config(u)) { + max = c->memory_max; + swap_max = c->memory_swap_max; + } else { +@@ -1067,7 +1104,7 @@ static void cgroup_context_apply( + } + + cgroup_apply_unified_memory_limit(u, "memory.min", c->memory_min); +- cgroup_apply_unified_memory_limit(u, "memory.low", c->memory_low); ++ cgroup_apply_unified_memory_limit(u, "memory.low", unit_get_ancestor_memory_low(u)); + cgroup_apply_unified_memory_limit(u, "memory.high", c->memory_high); + cgroup_apply_unified_memory_limit(u, "memory.max", max); + cgroup_apply_unified_memory_limit(u, "memory.swap.max", swap_max); +@@ -1075,7 +1112,7 @@ static void cgroup_context_apply( + char buf[DECIMAL_STR_MAX(uint64_t) + 1]; + uint64_t val; + +- if (cgroup_context_has_unified_memory_config(c)) { ++ if (unit_has_unified_memory_config(u)) { + val = c->memory_max; + log_cgroup_compat(u, "Applying MemoryMax %" PRIi64 " as MemoryLimit", val); + } else +@@ -1204,8 +1241,13 @@ static void cgroup_context_apply( + cgroup_apply_firewall(u); + } + +-CGroupMask cgroup_context_get_mask(CGroupContext *c) { ++static CGroupMask unit_get_cgroup_mask(Unit *u) { + CGroupMask mask = 0; ++ CGroupContext *c; ++ ++ assert(u); ++ ++ c = unit_get_cgroup_context(u); + + /* Figure out which controllers we need */ + +@@ -1223,7 +1265,7 @@ CGroupMask cgroup_context_get_mask(CGroupContext *c) { + + if (c->memory_accounting || + c->memory_limit != CGROUP_LIMIT_MAX || +- cgroup_context_has_unified_memory_config(c)) ++ unit_has_unified_memory_config(u)) + mask |= CGROUP_MASK_MEMORY; + + if (c->device_allow || +@@ -1246,7 +1288,7 @@ CGroupMask unit_get_own_mask(Unit *u) { + if (!c) + return 0; + +- return cgroup_context_get_mask(c) | unit_get_delegate_mask(u); ++ return unit_get_cgroup_mask(u) | unit_get_delegate_mask(u); + } + + CGroupMask unit_get_delegate_mask(Unit *u) { +diff --git a/src/core/cgroup.h b/src/core/cgroup.h +index 8102b442b8..a263d6a169 100644 +--- a/src/core/cgroup.h ++++ b/src/core/cgroup.h +@@ -95,12 +95,16 @@ struct CGroupContext { + LIST_HEAD(CGroupIODeviceLimit, io_device_limits); + LIST_HEAD(CGroupIODeviceLatency, io_device_latencies); + ++ uint64_t default_memory_low; + uint64_t memory_min; + uint64_t memory_low; + uint64_t memory_high; + uint64_t memory_max; + uint64_t memory_swap_max; + ++ bool default_memory_low_set; ++ bool memory_low_set; ++ + LIST_HEAD(IPAddressAccessItem, ip_address_allow); + LIST_HEAD(IPAddressAccessItem, ip_address_deny); + +@@ -191,6 +195,8 @@ Unit *manager_get_unit_by_cgroup(Manager *m, const char *cgroup); + Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid); + Unit* manager_get_unit_by_pid(Manager *m, pid_t pid); + ++uint64_t unit_get_ancestor_memory_low(Unit *u); ++ + int unit_search_main_pid(Unit *u, pid_t *ret); + int unit_watch_all_pids(Unit *u); + +diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c +index 6ce5984a02..2115d43b0c 100644 +--- a/src/core/dbus-cgroup.c ++++ b/src/core/dbus-cgroup.c +@@ -353,6 +353,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = { + SD_BUS_PROPERTY("BlockIOReadBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0), + SD_BUS_PROPERTY("BlockIOWriteBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0), + SD_BUS_PROPERTY("MemoryAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, memory_accounting), 0), ++ SD_BUS_PROPERTY("DefaultMemoryLow", "t", NULL, offsetof(CGroupContext, default_memory_low), 0), + SD_BUS_PROPERTY("MemoryMin", "t", NULL, offsetof(CGroupContext, memory_min), 0), + SD_BUS_PROPERTY("MemoryLow", "t", NULL, offsetof(CGroupContext, memory_low), 0), + SD_BUS_PROPERTY("MemoryHigh", "t", NULL, offsetof(CGroupContext, memory_high), 0), +@@ -668,6 +669,9 @@ int bus_cgroup_set_property( + if (streq(name, "MemoryLow")) + return bus_cgroup_set_memory(u, name, &c->memory_low, message, flags, error); + ++ if (streq(name, "DefaultMemoryLow")) ++ return bus_cgroup_set_memory(u, name, &c->default_memory_low, message, flags, error); ++ + if (streq(name, "MemoryHigh")) + return bus_cgroup_set_memory(u, name, &c->memory_high, message, flags, error); + +@@ -686,6 +690,9 @@ int bus_cgroup_set_property( + if (streq(name, "MemoryLowScale")) + return bus_cgroup_set_memory_scale(u, name, &c->memory_low, message, flags, error); + ++ if (streq(name, "DefaultMemoryLowScale")) ++ return bus_cgroup_set_memory_scale(u, name, &c->default_memory_low, message, flags, error); ++ + if (streq(name, "MemoryHighScale")) + return bus_cgroup_set_memory_scale(u, name, &c->memory_high, message, flags, error); + +diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 +index 1c6e539f30..43cc78fdea 100644 +--- a/src/core/load-fragment-gperf.gperf.m4 ++++ b/src/core/load-fragment-gperf.gperf.m4 +@@ -172,6 +172,7 @@ $1.CPUQuota, config_parse_cpu_quota, 0, + $1.CPUQuotaPeriodSec, config_parse_sec_def_infinity, 0, offsetof($1, cgroup_context.cpu_quota_period_usec) + $1.MemoryAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.memory_accounting) + $1.MemoryMin, config_parse_memory_limit, 0, offsetof($1, cgroup_context) ++$1.DefaultMemoryLow, config_parse_memory_limit, 0, offsetof($1, cgroup_context) + $1.MemoryLow, config_parse_memory_limit, 0, offsetof($1, cgroup_context) + $1.MemoryHigh, config_parse_memory_limit, 0, offsetof($1, cgroup_context) + $1.MemoryMax, config_parse_memory_limit, 0, offsetof($1, cgroup_context) +diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c +index 89a3457acc..20faed02ad 100644 +--- a/src/core/load-fragment.c ++++ b/src/core/load-fragment.c +@@ -3096,11 +3096,18 @@ int config_parse_memory_limit( + } + } + +- if (streq(lvalue, "MemoryMin")) ++ if (streq(lvalue, "DefaultMemoryLow")) { ++ c->default_memory_low_set = true; ++ if (isempty(rvalue)) ++ c->default_memory_low = CGROUP_LIMIT_MIN; ++ else ++ c->default_memory_low = bytes; ++ } else if (streq(lvalue, "MemoryMin")) + c->memory_min = bytes; +- else if (streq(lvalue, "MemoryLow")) ++ else if (streq(lvalue, "MemoryLow")) { + c->memory_low = bytes; +- else if (streq(lvalue, "MemoryHigh")) ++ c->memory_low_set = true; ++ } else if (streq(lvalue, "MemoryHigh")) + c->memory_high = bytes; + else if (streq(lvalue, "MemoryMax")) + c->memory_max = bytes; +diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c +index 203c068d02..f88730a85d 100644 +--- a/src/shared/bus-unit-util.c ++++ b/src/shared/bus-unit-util.c +@@ -429,7 +429,7 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons + return 1; + } + +- if (STR_IN_SET(field, "MemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit", "TasksMax")) { ++ if (STR_IN_SET(field, "MemoryMin", "DefaultMemoryLow", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit", "TasksMax")) { + + if (isempty(eq) || streq(eq, "infinity")) { + r = sd_bus_message_append(m, "(sv)", field, "t", CGROUP_LIMIT_MAX); +diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c +index 5ed68429be..0ba2712deb 100644 +--- a/src/shared/bus-util.c ++++ b/src/shared/bus-util.c +@@ -774,7 +774,7 @@ int bus_print_property(const char *name, sd_bus_message *m, bool value, bool all + + print_property(name, "%s", "[not set]"); + +- else if ((STR_IN_SET(name, "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit") && u == CGROUP_LIMIT_MAX) || ++ else if ((STR_IN_SET(name, "DefaultMemoryLow", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit") && u == CGROUP_LIMIT_MAX) || + (STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == (uint64_t) -1) || + (startswith(name, "Limit") && u == (uint64_t) -1) || + (startswith(name, "DefaultLimit") && u == (uint64_t) -1)) +diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c +index 35ad20f510..763ca0c6b7 100644 +--- a/src/systemctl/systemctl.c ++++ b/src/systemctl/systemctl.c +@@ -3918,6 +3918,8 @@ typedef struct UnitStatusInfo { + uint64_t ip_ingress_bytes; + uint64_t ip_egress_bytes; + ++ uint64_t default_memory_low; ++ + LIST_HEAD(ExecStatusInfo, exec); + } UnitStatusInfo; + +@@ -5028,6 +5030,7 @@ static int show_one( + { "Where", "s", NULL, offsetof(UnitStatusInfo, where) }, + { "What", "s", NULL, offsetof(UnitStatusInfo, what) }, + { "MemoryCurrent", "t", NULL, offsetof(UnitStatusInfo, memory_current) }, ++ { "DefaultMemoryLow", "t", NULL, offsetof(UnitStatusInfo, default_memory_low) }, + { "MemoryMin", "t", NULL, offsetof(UnitStatusInfo, memory_min) }, + { "MemoryLow", "t", NULL, offsetof(UnitStatusInfo, memory_low) }, + { "MemoryHigh", "t", NULL, offsetof(UnitStatusInfo, memory_high) }, +diff --git a/src/test/meson.build b/src/test/meson.build +index 22264d034c..7b310d4ec7 100644 +--- a/src/test/meson.build ++++ b/src/test/meson.build +@@ -518,6 +518,12 @@ tests += [ + libshared], + []], + ++ [['src/test/test-cgroup-unit-default.c', ++ 'src/test/test-helper.c'], ++ [libcore, ++ libshared], ++ []], ++ + [['src/test/test-cgroup-mask.c', + 'src/test/test-helper.c'], + [libcore, +diff --git a/src/test/test-cgroup-unit-default.c b/src/test/test-cgroup-unit-default.c +new file mode 100644 +index 0000000000..54f7d50c45 +--- /dev/null ++++ b/src/test/test-cgroup-unit-default.c +@@ -0,0 +1,145 @@ ++/* SPDX-License-Identifier: LGPL-2.1+ */ ++ ++#include ++ ++#include "cgroup.h" ++#include "manager.h" ++#include "rm-rf.h" ++#include "test-helper.h" ++#include "tests.h" ++#include "unit.h" ++ ++static int test_default_memory_low(void) { ++ _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; ++ _cleanup_(manager_freep) Manager *m = NULL; ++ Unit *root, *dml, ++ *dml_passthrough, *dml_passthrough_empty, *dml_passthrough_set_dml, *dml_passthrough_set_ml, ++ *dml_override, *dml_override_empty, ++ *dml_discard, *dml_discard_empty, *dml_discard_set_ml; ++ uint64_t dml_tree_default; ++ int r; ++ ++ r = enter_cgroup_subroot(); ++ if (r == -ENOMEDIUM) ++ return EXIT_TEST_SKIP; ++ ++ assert_se(set_unit_path(get_testdata_dir()) >= 0); ++ assert_se(runtime_dir = setup_fake_runtime_dir()); ++ r = manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_BASIC, &m); ++ if (IN_SET(r, -EPERM, -EACCES)) { ++ log_error_errno(r, "manager_new: %m"); ++ return EXIT_TEST_SKIP; ++ } ++ ++ assert_se(r >= 0); ++ assert_se(manager_startup(m, NULL, NULL) >= 0); ++ ++ /* dml.slice has DefaultMemoryLow=50. Beyond that, individual subhierarchies look like this: ++ * ++ * 1. dml-passthrough.slice sets MemoryLow=100. This should not affect its children, as only ++ * DefaultMemoryLow is propagated, not MemoryLow. As such, all leaf services should end up with ++ * memory.low as 50, inherited from dml.slice, *except* for dml-passthrough-set-ml.service, which ++ * should have the value of 25, as it has MemoryLow explicitly set. ++ * ++ * ┌───────────┐ ++ * │ dml.slice │ ++ * └─────┬─────┘ ++ * MemoryLow=100 ++ * ┌───────────┴───────────┐ ++ * │ dml-passthrough.slice │ ++ * └───────────┬───────────┘ ++ * ┌───────────────────────────────────┼───────────────────────────────────┐ ++ * no new settings DefaultMemoryLow=15 MemoryLow=25 ++ * ┌───────────────┴───────────────┐ ┌────────────────┴────────────────┐ ┌───────────────┴────────────────┐ ++ * │ dml-passthrough-empty.service │ │ dml-passthrough-set-dml.service │ │ dml-passthrough-set-ml.service │ ++ * └───────────────────────────────┘ └─────────────────────────────────┘ └────────────────────────────────┘ ++ * ++ * 2. dml-override.slice sets DefaultMemoryLow=10. As such, dml-override-empty.service should also ++ * end up with a memory.low of 10. dml-override.slice should still have a memory.low of 50. ++ * ++ * ┌───────────┐ ++ * │ dml.slice │ ++ * └─────┬─────┘ ++ * DefaultMemoryLow=10 ++ * ┌─────────┴──────────┐ ++ * │ dml-override.slice │ ++ * └─────────┬──────────┘ ++ * no new settings ++ * ┌─────────────┴──────────────┐ ++ * │ dml-override-empty.service │ ++ * └────────────────────────────┘ ++ * ++ * 3. dml-discard.slice sets DefaultMemoryLow= with no rvalue. As such, ++ * dml-discard-empty.service should end up with a value of 0. ++ * dml-discard-explicit-ml.service sets MemoryLow=70, and as such should have that override the ++ * reset DefaultMemoryLow value. dml-discard.slice should still have an eventual memory.low of 50. ++ * ++ * ┌───────────┐ ++ * │ dml.slice │ ++ * └─────┬─────┘ ++ * DefaultMemoryLow= ++ * ┌─────────┴─────────┐ ++ * │ dml-discard.slice │ ++ * └─────────┬─────────┘ ++ * ┌──────────────┴───────────────┐ ++ * no new settings MemoryLow=15 ++ * ┌─────────────┴─────────────┐ ┌─────────────┴──────────────┐ ++ * │ dml-discard-empty.service │ │ dml-discard-set-ml.service │ ++ * └───────────────────────────┘ └────────────────────────────┘ ++ */ ++ assert_se(manager_load_startable_unit_or_warn(m, "dml.slice", NULL, &dml) >= 0); ++ ++ assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough.slice", NULL, &dml_passthrough) >= 0); ++ assert_se(UNIT_DEREF(dml_passthrough->slice) == dml); ++ assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-empty.service", NULL, &dml_passthrough_empty) >= 0); ++ assert_se(UNIT_DEREF(dml_passthrough_empty->slice) == dml_passthrough); ++ assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-set-dml.service", NULL, &dml_passthrough_set_dml) >= 0); ++ assert_se(UNIT_DEREF(dml_passthrough_set_dml->slice) == dml_passthrough); ++ assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-set-ml.service", NULL, &dml_passthrough_set_ml) >= 0); ++ assert_se(UNIT_DEREF(dml_passthrough_set_ml->slice) == dml_passthrough); ++ ++ assert_se(manager_load_startable_unit_or_warn(m, "dml-override.slice", NULL, &dml_override) >= 0); ++ assert_se(UNIT_DEREF(dml_override->slice) == dml); ++ assert_se(manager_load_startable_unit_or_warn(m, "dml-override-empty.service", NULL, &dml_override_empty) >= 0); ++ assert_se(UNIT_DEREF(dml_override_empty->slice) == dml_override); ++ ++ assert_se(manager_load_startable_unit_or_warn(m, "dml-discard.slice", NULL, &dml_discard) >= 0); ++ assert_se(UNIT_DEREF(dml_discard->slice) == dml); ++ assert_se(manager_load_startable_unit_or_warn(m, "dml-discard-empty.service", NULL, &dml_discard_empty) >= 0); ++ assert_se(UNIT_DEREF(dml_discard_empty->slice) == dml_discard); ++ assert_se(manager_load_startable_unit_or_warn(m, "dml-discard-set-ml.service", NULL, &dml_discard_set_ml) >= 0); ++ assert_se(UNIT_DEREF(dml_discard_set_ml->slice) == dml_discard); ++ ++ root = UNIT_DEREF(dml->slice); ++ assert_se(!UNIT_ISSET(root->slice)); ++ ++ assert_se(unit_get_ancestor_memory_low(root) == CGROUP_LIMIT_MIN); ++ ++ assert_se(unit_get_ancestor_memory_low(dml) == CGROUP_LIMIT_MIN); ++ dml_tree_default = unit_get_cgroup_context(dml)->default_memory_low; ++ assert_se(dml_tree_default == 50); ++ ++ assert_se(unit_get_ancestor_memory_low(dml_passthrough) == 100); ++ assert_se(unit_get_ancestor_memory_low(dml_passthrough_empty) == dml_tree_default); ++ assert_se(unit_get_ancestor_memory_low(dml_passthrough_set_dml) == 50); ++ assert_se(unit_get_ancestor_memory_low(dml_passthrough_set_ml) == 25); ++ ++ assert_se(unit_get_ancestor_memory_low(dml_override) == dml_tree_default); ++ assert_se(unit_get_ancestor_memory_low(dml_override_empty) == 10); ++ ++ assert_se(unit_get_ancestor_memory_low(dml_discard) == dml_tree_default); ++ assert_se(unit_get_ancestor_memory_low(dml_discard_empty) == CGROUP_LIMIT_MIN); ++ assert_se(unit_get_ancestor_memory_low(dml_discard_set_ml) == 15); ++ ++ return 0; ++} ++ ++int main(int argc, char* argv[]) { ++ int rc = EXIT_SUCCESS; ++ ++ test_setup_logging(LOG_DEBUG); ++ ++ TEST_REQ_RUNNING_SYSTEMD(rc = test_default_memory_low()); ++ ++ return rc; ++} +diff --git a/test/dml-discard-empty.service b/test/dml-discard-empty.service +new file mode 100644 +index 0000000000..75228f6470 +--- /dev/null ++++ b/test/dml-discard-empty.service +@@ -0,0 +1,7 @@ ++[Unit] ++Description=DML discard empty service ++ ++[Service] ++Slice=dml-discard.slice ++Type=oneshot ++ExecStart=/bin/true +diff --git a/test/dml-discard-set-ml.service b/test/dml-discard-set-ml.service +new file mode 100644 +index 0000000000..591c99270c +--- /dev/null ++++ b/test/dml-discard-set-ml.service +@@ -0,0 +1,8 @@ ++[Unit] ++Description=DML discard set ml service ++ ++[Service] ++Slice=dml-discard.slice ++Type=oneshot ++ExecStart=/bin/true ++MemoryLow=15 +diff --git a/test/dml-discard.slice b/test/dml-discard.slice +new file mode 100644 +index 0000000000..e26d86846c +--- /dev/null ++++ b/test/dml-discard.slice +@@ -0,0 +1,5 @@ ++[Unit] ++Description=DML discard slice ++ ++[Slice] ++DefaultMemoryLow= +diff --git a/test/dml-override-empty.service b/test/dml-override-empty.service +new file mode 100644 +index 0000000000..142c98720c +--- /dev/null ++++ b/test/dml-override-empty.service +@@ -0,0 +1,7 @@ ++[Unit] ++Description=DML override empty service ++ ++[Service] ++Slice=dml-override.slice ++Type=oneshot ++ExecStart=/bin/true +diff --git a/test/dml-override.slice b/test/dml-override.slice +new file mode 100644 +index 0000000000..feb6773e39 +--- /dev/null ++++ b/test/dml-override.slice +@@ -0,0 +1,5 @@ ++[Unit] ++Description=DML override slice ++ ++[Slice] ++DefaultMemoryLow=10 +diff --git a/test/dml-passthrough-empty.service b/test/dml-passthrough-empty.service +new file mode 100644 +index 0000000000..34832de491 +--- /dev/null ++++ b/test/dml-passthrough-empty.service +@@ -0,0 +1,7 @@ ++[Unit] ++Description=DML passthrough empty service ++ ++[Service] ++Slice=dml-passthrough.slice ++Type=oneshot ++ExecStart=/bin/true +diff --git a/test/dml-passthrough-set-dml.service b/test/dml-passthrough-set-dml.service +new file mode 100644 +index 0000000000..5bdf4ed4b7 +--- /dev/null ++++ b/test/dml-passthrough-set-dml.service +@@ -0,0 +1,8 @@ ++[Unit] ++Description=DML passthrough set DML service ++ ++[Service] ++Slice=dml-passthrough.slice ++Type=oneshot ++ExecStart=/bin/true ++DefaultMemoryLow=15 +diff --git a/test/dml-passthrough-set-ml.service b/test/dml-passthrough-set-ml.service +new file mode 100644 +index 0000000000..2abd591389 +--- /dev/null ++++ b/test/dml-passthrough-set-ml.service +@@ -0,0 +1,8 @@ ++[Unit] ++Description=DML passthrough set ML service ++ ++[Service] ++Slice=dml-passthrough.slice ++Type=oneshot ++ExecStart=/bin/true ++MemoryLow=25 +diff --git a/test/dml-passthrough.slice b/test/dml-passthrough.slice +new file mode 100644 +index 0000000000..1b1a848edb +--- /dev/null ++++ b/test/dml-passthrough.slice +@@ -0,0 +1,5 @@ ++[Unit] ++Description=DML passthrough slice ++ ++[Slice] ++MemoryLow=100 +diff --git a/test/dml.slice b/test/dml.slice +new file mode 100644 +index 0000000000..84e333ef04 +--- /dev/null ++++ b/test/dml.slice +@@ -0,0 +1,5 @@ ++[Unit] ++Description=DML slice ++ ++[Slice] ++DefaultMemoryLow=50 +diff --git a/test/meson.build b/test/meson.build +index 070731c4a9..52e4fa2e3c 100644 +--- a/test/meson.build ++++ b/test/meson.build +@@ -7,6 +7,16 @@ test_data_files = ''' + c.service + d.service + daughter.service ++ dml.slice ++ dml-passthrough.slice ++ dml-passthrough-empty.service ++ dml-passthrough-set-dml.service ++ dml-passthrough-set-ml.service ++ dml-override.slice ++ dml-override-empty.service ++ dml-discard.slice ++ dml-discard-empty.service ++ dml-discard-set-ml.service + e.service + end.service + f.service diff --git a/SOURCES/0407-cgroup-Create-UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP.patch b/SOURCES/0407-cgroup-Create-UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP.patch new file mode 100644 index 0000000..5c43842 --- /dev/null +++ b/SOURCES/0407-cgroup-Create-UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP.patch @@ -0,0 +1,80 @@ +From a016ef4ab29ed62da547db008866624f75ed6407 Mon Sep 17 00:00:00 2001 +From: Chris Down +Date: Tue, 16 Apr 2019 18:14:09 +0100 +Subject: [PATCH] cgroup: Create UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP + +This is in preparation for creating unit_get_ancestor_memory_min. + +(cherry picked from commit 6264b85e92aeddb74b8d8808a08c9eae8390a6a5) + +Related: #1763435 +--- + src/core/cgroup.c | 55 ++++++++++++++++++++++++++--------------------- + 1 file changed, 30 insertions(+), 25 deletions(-) + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index f804bf4727..46a89ff5e1 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -372,31 +372,36 @@ int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode) + return 0; + } + +-uint64_t unit_get_ancestor_memory_low(Unit *u) { +- CGroupContext *c; +- +- /* 1. Is MemoryLow set in this unit? If so, use that. +- * 2. Is DefaultMemoryLow set in any ancestor? If so, use that. +- * 3. Otherwise, return CGROUP_LIMIT_MIN. */ +- +- assert(u); +- +- c = unit_get_cgroup_context(u); +- +- if (c->memory_low_set) +- return c->memory_low; +- +- while (UNIT_ISSET(u->slice)) { +- u = UNIT_DEREF(u->slice); +- c = unit_get_cgroup_context(u); +- +- if (c->default_memory_low_set) +- return c->default_memory_low; +- } +- +- /* We've reached the root, but nobody had DefaultMemoryLow set, so set it to the kernel default. */ +- return CGROUP_LIMIT_MIN; +-} ++#define UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP(entry) \ ++ uint64_t unit_get_ancestor_##entry(Unit *u) { \ ++ CGroupContext *c; \ ++ \ ++ /* 1. Is entry set in this unit? If so, use that. \ ++ * 2. Is the default for this entry set in any \ ++ * ancestor? If so, use that. \ ++ * 3. Otherwise, return CGROUP_LIMIT_MIN. */ \ ++ \ ++ assert(u); \ ++ \ ++ c = unit_get_cgroup_context(u); \ ++ \ ++ if (c->entry##_set) \ ++ return c->entry; \ ++ \ ++ while (UNIT_ISSET(u->slice)) { \ ++ u = UNIT_DEREF(u->slice); \ ++ c = unit_get_cgroup_context(u); \ ++ \ ++ if (c->default_##entry##_set) \ ++ return c->default_##entry; \ ++ } \ ++ \ ++ /* We've reached the root, but nobody had default for \ ++ * this entry set, so set it to the kernel default. */ \ ++ return CGROUP_LIMIT_MIN; \ ++} ++ ++UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP(memory_low); + + static int lookup_block_device(const char *p, dev_t *ret) { + struct stat st; diff --git a/SOURCES/0408-unit-Add-DefaultMemoryMin.patch b/SOURCES/0408-unit-Add-DefaultMemoryMin.patch new file mode 100644 index 0000000..bdbac28 --- /dev/null +++ b/SOURCES/0408-unit-Add-DefaultMemoryMin.patch @@ -0,0 +1,141 @@ +From 69dbbc29f26569fd09f0109e6fbebde98c0c8567 Mon Sep 17 00:00:00 2001 +From: Chris Down +Date: Tue, 16 Apr 2019 18:44:05 +0100 +Subject: [PATCH] unit: Add DefaultMemoryMin + +(cherry picked from commit 7ad5439e0663e39e36619957fa37eefe8026bcab) + +Related: #1763435 +--- + src/core/cgroup.c | 3 +++ + src/core/cgroup.h | 4 ++++ + src/core/dbus-cgroup.c | 6 ++++++ + src/core/load-fragment.c | 11 +++++++++-- + src/systemctl/systemctl.c | 2 ++ + 5 files changed, 24 insertions(+), 2 deletions(-) + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index 46a89ff5e1..d40f9cbc2a 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -220,6 +220,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + "%sStartupIOWeight=%" PRIu64 "\n" + "%sBlockIOWeight=%" PRIu64 "\n" + "%sStartupBlockIOWeight=%" PRIu64 "\n" ++ "%sDefaultMemoryMin=%" PRIu64 "\n" + "%sDefaultMemoryLow=%" PRIu64 "\n" + "%sMemoryMin=%" PRIu64 "\n" + "%sMemoryLow=%" PRIu64 "\n" +@@ -248,6 +249,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + prefix, c->startup_io_weight, + prefix, c->blockio_weight, + prefix, c->startup_blockio_weight, ++ prefix, c->default_memory_min, + prefix, c->default_memory_low, + prefix, c->memory_min, + prefix, c->memory_low, +@@ -402,6 +404,7 @@ int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode) + } + + UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP(memory_low); ++UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP(memory_min); + + static int lookup_block_device(const char *p, dev_t *ret) { + struct stat st; +diff --git a/src/core/cgroup.h b/src/core/cgroup.h +index a263d6a169..976224336d 100644 +--- a/src/core/cgroup.h ++++ b/src/core/cgroup.h +@@ -95,6 +95,7 @@ struct CGroupContext { + LIST_HEAD(CGroupIODeviceLimit, io_device_limits); + LIST_HEAD(CGroupIODeviceLatency, io_device_latencies); + ++ uint64_t default_memory_min; + uint64_t default_memory_low; + uint64_t memory_min; + uint64_t memory_low; +@@ -102,7 +103,9 @@ struct CGroupContext { + uint64_t memory_max; + uint64_t memory_swap_max; + ++ bool default_memory_min_set; + bool default_memory_low_set; ++ bool memory_min_set; + bool memory_low_set; + + LIST_HEAD(IPAddressAccessItem, ip_address_allow); +@@ -195,6 +198,7 @@ Unit *manager_get_unit_by_cgroup(Manager *m, const char *cgroup); + Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid); + Unit* manager_get_unit_by_pid(Manager *m, pid_t pid); + ++uint64_t unit_get_ancestor_memory_min(Unit *u); + uint64_t unit_get_ancestor_memory_low(Unit *u); + + int unit_search_main_pid(Unit *u, pid_t *ret); +diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c +index 2115d43b0c..e1278c317a 100644 +--- a/src/core/dbus-cgroup.c ++++ b/src/core/dbus-cgroup.c +@@ -669,6 +669,9 @@ int bus_cgroup_set_property( + if (streq(name, "MemoryLow")) + return bus_cgroup_set_memory(u, name, &c->memory_low, message, flags, error); + ++ if (streq(name, "DefaultMemoryMin")) ++ return bus_cgroup_set_memory(u, name, &c->default_memory_min, message, flags, error); ++ + if (streq(name, "DefaultMemoryLow")) + return bus_cgroup_set_memory(u, name, &c->default_memory_low, message, flags, error); + +@@ -690,6 +693,9 @@ int bus_cgroup_set_property( + if (streq(name, "MemoryLowScale")) + return bus_cgroup_set_memory_scale(u, name, &c->memory_low, message, flags, error); + ++ if (streq(name, "DefaultMemoryMinScale")) ++ return bus_cgroup_set_memory_scale(u, name, &c->default_memory_min, message, flags, error); ++ + if (streq(name, "DefaultMemoryLowScale")) + return bus_cgroup_set_memory_scale(u, name, &c->default_memory_low, message, flags, error); + +diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c +index 20faed02ad..3b8ee6b124 100644 +--- a/src/core/load-fragment.c ++++ b/src/core/load-fragment.c +@@ -3102,9 +3102,16 @@ int config_parse_memory_limit( + c->default_memory_low = CGROUP_LIMIT_MIN; + else + c->default_memory_low = bytes; +- } else if (streq(lvalue, "MemoryMin")) ++ } else if (streq(lvalue, "DefaultMemoryMin")) { ++ c->default_memory_min_set = true; ++ if (isempty(rvalue)) ++ c->default_memory_min = CGROUP_LIMIT_MIN; ++ else ++ c->default_memory_min = bytes; ++ } else if (streq(lvalue, "MemoryMin")) { + c->memory_min = bytes; +- else if (streq(lvalue, "MemoryLow")) { ++ c->memory_min_set = true; ++ } else if (streq(lvalue, "MemoryLow")) { + c->memory_low = bytes; + c->memory_low_set = true; + } else if (streq(lvalue, "MemoryHigh")) +diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c +index 763ca0c6b7..e0db97e339 100644 +--- a/src/systemctl/systemctl.c ++++ b/src/systemctl/systemctl.c +@@ -3918,6 +3918,7 @@ typedef struct UnitStatusInfo { + uint64_t ip_ingress_bytes; + uint64_t ip_egress_bytes; + ++ uint64_t default_memory_min; + uint64_t default_memory_low; + + LIST_HEAD(ExecStatusInfo, exec); +@@ -5030,6 +5031,7 @@ static int show_one( + { "Where", "s", NULL, offsetof(UnitStatusInfo, where) }, + { "What", "s", NULL, offsetof(UnitStatusInfo, what) }, + { "MemoryCurrent", "t", NULL, offsetof(UnitStatusInfo, memory_current) }, ++ { "DefaultMemoryMin", "t", NULL, offsetof(UnitStatusInfo, default_memory_min) }, + { "DefaultMemoryLow", "t", NULL, offsetof(UnitStatusInfo, default_memory_low) }, + { "MemoryMin", "t", NULL, offsetof(UnitStatusInfo, memory_min) }, + { "MemoryLow", "t", NULL, offsetof(UnitStatusInfo, memory_low) }, diff --git a/SOURCES/0409-cgroup-Polish-hierarchically-aware-protection-docs-a.patch b/SOURCES/0409-cgroup-Polish-hierarchically-aware-protection-docs-a.patch new file mode 100644 index 0000000..d2399a0 --- /dev/null +++ b/SOURCES/0409-cgroup-Polish-hierarchically-aware-protection-docs-a.patch @@ -0,0 +1,56 @@ +From 556375ae1d8d0b460d691888f2fb7ea520fe9a6b Mon Sep 17 00:00:00 2001 +From: Chris Down +Date: Tue, 30 Apr 2019 14:22:04 -0400 +Subject: [PATCH] cgroup: Polish hierarchically aware protection docs a bit + +I missed adding a section in `systemd.resource-control` about +DefaultMemoryMin in #12332. + +Also, add a NEWS entry going over the general concept. + +(cherry picked from commit acdb4b5236f38bbefbcc4a47fdbb9cd558b4b5c5) + +Related: #1763435 +--- + doc/TRANSIENT-SETTINGS.md | 1 + + man/systemd.resource-control.xml | 8 ++++++-- + 2 files changed, 7 insertions(+), 2 deletions(-) + +diff --git a/doc/TRANSIENT-SETTINGS.md b/doc/TRANSIENT-SETTINGS.md +index 5a8fa0727e..1a4e79190a 100644 +--- a/doc/TRANSIENT-SETTINGS.md ++++ b/doc/TRANSIENT-SETTINGS.md +@@ -222,6 +222,7 @@ All cgroup/resource control settings are available for transient units + ✓ AllowedCPUs= + ✓ AllowedMemoryNodes= + ✓ MemoryAccounting= ++✓ DefaultMemoryMin= + ✓ MemoryMin= + ✓ DefaultMemoryLow= + ✓ MemoryLow= +diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml +index 27f16001dd..d3bff29169 100644 +--- a/man/systemd.resource-control.xml ++++ b/man/systemd.resource-control.xml +@@ -283,6 +283,10 @@ + + This setting is supported only if the unified control group hierarchy is used and disables + MemoryLimit=. ++ ++ Units may have their children use a default memory.min value by specifying ++ DefaultMemoryMin=, which has the same semantics as MemoryMin=. This setting ++ does not affect memory.min in the unit itself. + + + +@@ -306,8 +310,8 @@ + This setting is supported only if the unified control group hierarchy is used and disables + MemoryLimit=. + +- Units may can have their children use a default memory.low value by specifying +- DefaultMemoryLow=, which has the same usage as MemoryLow=. This setting ++ Units may have their children use a default memory.low value by specifying ++ DefaultMemoryLow=, which has the same semantics as MemoryLow=. This setting + does not affect memory.low in the unit itself. + + diff --git a/SOURCES/0410-cgroup-Readd-some-plumbing-for-DefaultMemoryMin.patch b/SOURCES/0410-cgroup-Readd-some-plumbing-for-DefaultMemoryMin.patch new file mode 100644 index 0000000..13f3fec --- /dev/null +++ b/SOURCES/0410-cgroup-Readd-some-plumbing-for-DefaultMemoryMin.patch @@ -0,0 +1,68 @@ +From dbdba4f2998313949e7c5f5a47f4ef938e759a86 Mon Sep 17 00:00:00 2001 +From: Chris Down +Date: Fri, 3 May 2019 08:19:05 -0400 +Subject: [PATCH] cgroup: Readd some plumbing for DefaultMemoryMin + +Somehow these got lost in the previous PR, rendering DefaultMemoryMin +not very useful. + +(cherry picked from commit 7e7223b3d57c950b399352a92e1d817f7c463602) + +Related: #1763435 +--- + src/core/dbus-cgroup.c | 1 + + src/core/load-fragment-gperf.gperf.m4 | 1 + + src/shared/bus-unit-util.c | 2 +- + src/shared/bus-util.c | 2 +- + 4 files changed, 4 insertions(+), 2 deletions(-) + +diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c +index e1278c317a..e34ff3a016 100644 +--- a/src/core/dbus-cgroup.c ++++ b/src/core/dbus-cgroup.c +@@ -354,6 +354,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = { + SD_BUS_PROPERTY("BlockIOWriteBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0), + SD_BUS_PROPERTY("MemoryAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, memory_accounting), 0), + SD_BUS_PROPERTY("DefaultMemoryLow", "t", NULL, offsetof(CGroupContext, default_memory_low), 0), ++ SD_BUS_PROPERTY("DefaultMemoryMin", "t", NULL, offsetof(CGroupContext, default_memory_min), 0), + SD_BUS_PROPERTY("MemoryMin", "t", NULL, offsetof(CGroupContext, memory_min), 0), + SD_BUS_PROPERTY("MemoryLow", "t", NULL, offsetof(CGroupContext, memory_low), 0), + SD_BUS_PROPERTY("MemoryHigh", "t", NULL, offsetof(CGroupContext, memory_high), 0), +diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 +index 43cc78fdea..6d21b2e433 100644 +--- a/src/core/load-fragment-gperf.gperf.m4 ++++ b/src/core/load-fragment-gperf.gperf.m4 +@@ -172,6 +172,7 @@ $1.CPUQuota, config_parse_cpu_quota, 0, + $1.CPUQuotaPeriodSec, config_parse_sec_def_infinity, 0, offsetof($1, cgroup_context.cpu_quota_period_usec) + $1.MemoryAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.memory_accounting) + $1.MemoryMin, config_parse_memory_limit, 0, offsetof($1, cgroup_context) ++$1.DefaultMemoryMin, config_parse_memory_limit, 0, offsetof($1, cgroup_context) + $1.DefaultMemoryLow, config_parse_memory_limit, 0, offsetof($1, cgroup_context) + $1.MemoryLow, config_parse_memory_limit, 0, offsetof($1, cgroup_context) + $1.MemoryHigh, config_parse_memory_limit, 0, offsetof($1, cgroup_context) +diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c +index f88730a85d..77788f0fe2 100644 +--- a/src/shared/bus-unit-util.c ++++ b/src/shared/bus-unit-util.c +@@ -429,7 +429,7 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons + return 1; + } + +- if (STR_IN_SET(field, "MemoryMin", "DefaultMemoryLow", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit", "TasksMax")) { ++ if (STR_IN_SET(field, "MemoryMin", "DefaultMemoryLow", "DefaultMemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit", "TasksMax")) { + + if (isempty(eq) || streq(eq, "infinity")) { + r = sd_bus_message_append(m, "(sv)", field, "t", CGROUP_LIMIT_MAX); +diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c +index 0ba2712deb..ff0e800347 100644 +--- a/src/shared/bus-util.c ++++ b/src/shared/bus-util.c +@@ -774,7 +774,7 @@ int bus_print_property(const char *name, sd_bus_message *m, bool value, bool all + + print_property(name, "%s", "[not set]"); + +- else if ((STR_IN_SET(name, "DefaultMemoryLow", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit") && u == CGROUP_LIMIT_MAX) || ++ else if ((STR_IN_SET(name, "DefaultMemoryLow", "DefaultMemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit") && u == CGROUP_LIMIT_MAX) || + (STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == (uint64_t) -1) || + (startswith(name, "Limit") && u == (uint64_t) -1) || + (startswith(name, "DefaultLimit") && u == (uint64_t) -1)) diff --git a/SOURCES/0411-cgroup-Support-0-value-for-memory-protection-directi.patch b/SOURCES/0411-cgroup-Support-0-value-for-memory-protection-directi.patch new file mode 100644 index 0000000..5e1e1f2 --- /dev/null +++ b/SOURCES/0411-cgroup-Support-0-value-for-memory-protection-directi.patch @@ -0,0 +1,86 @@ +From 614b43fcad3a16dfde5ad606b43c3aa1adacc30a Mon Sep 17 00:00:00 2001 +From: Chris Down +Date: Fri, 3 May 2019 08:32:41 -0400 +Subject: [PATCH] cgroup: Support 0-value for memory protection directives + +These make sense to be explicitly set at 0 (which has a different effect +than the default, since it can affect processing of `DefaultMemoryXXX`). + +Without this, it's not easily possible to relinquish memory protection +for a subtree, which is not great. + +(cherry picked from commit 22bf131be278b95a4a204514d37a4344cf6365c6) + +Related: #1763435 +--- + src/core/dbus-cgroup.c | 17 +++++++++-------- + src/core/load-fragment.c | 2 +- + 2 files changed, 10 insertions(+), 9 deletions(-) + +diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c +index e34ff3a016..39778a8dd4 100644 +--- a/src/core/dbus-cgroup.c ++++ b/src/core/dbus-cgroup.c +@@ -606,6 +606,7 @@ BUS_DEFINE_SET_CGROUP_WEIGHT(cpu_shares, CGROUP_MASK_CPU, CGROUP_CPU_SHARES_IS_O + BUS_DEFINE_SET_CGROUP_WEIGHT(io_weight, CGROUP_MASK_IO, CGROUP_WEIGHT_IS_OK, CGROUP_WEIGHT_INVALID); + BUS_DEFINE_SET_CGROUP_WEIGHT(blockio_weight, CGROUP_MASK_BLKIO, CGROUP_BLKIO_WEIGHT_IS_OK, CGROUP_BLKIO_WEIGHT_INVALID); + BUS_DEFINE_SET_CGROUP_LIMIT(memory, CGROUP_MASK_MEMORY, physical_memory_scale, 1); ++BUS_DEFINE_SET_CGROUP_LIMIT(memory_protection, CGROUP_MASK_MEMORY, physical_memory_scale, 0); + BUS_DEFINE_SET_CGROUP_LIMIT(swap, CGROUP_MASK_MEMORY, physical_memory_scale, 0); + BUS_DEFINE_SET_CGROUP_LIMIT(tasks_max, CGROUP_MASK_PIDS, system_tasks_max_scale, 1); + #pragma GCC diagnostic pop +@@ -665,16 +666,16 @@ int bus_cgroup_set_property( + return bus_cgroup_set_boolean(u, name, &c->memory_accounting, CGROUP_MASK_MEMORY, message, flags, error); + + if (streq(name, "MemoryMin")) +- return bus_cgroup_set_memory(u, name, &c->memory_min, message, flags, error); ++ return bus_cgroup_set_memory_protection(u, name, &c->memory_min, message, flags, error); + + if (streq(name, "MemoryLow")) +- return bus_cgroup_set_memory(u, name, &c->memory_low, message, flags, error); ++ return bus_cgroup_set_memory_protection(u, name, &c->memory_low, message, flags, error); + + if (streq(name, "DefaultMemoryMin")) +- return bus_cgroup_set_memory(u, name, &c->default_memory_min, message, flags, error); ++ return bus_cgroup_set_memory_protection(u, name, &c->default_memory_min, message, flags, error); + + if (streq(name, "DefaultMemoryLow")) +- return bus_cgroup_set_memory(u, name, &c->default_memory_low, message, flags, error); ++ return bus_cgroup_set_memory_protection(u, name, &c->default_memory_low, message, flags, error); + + if (streq(name, "MemoryHigh")) + return bus_cgroup_set_memory(u, name, &c->memory_high, message, flags, error); +@@ -689,16 +690,16 @@ int bus_cgroup_set_property( + return bus_cgroup_set_memory(u, name, &c->memory_limit, message, flags, error); + + if (streq(name, "MemoryMinScale")) +- return bus_cgroup_set_memory_scale(u, name, &c->memory_min, message, flags, error); ++ return bus_cgroup_set_memory_protection_scale(u, name, &c->memory_min, message, flags, error); + + if (streq(name, "MemoryLowScale")) +- return bus_cgroup_set_memory_scale(u, name, &c->memory_low, message, flags, error); ++ return bus_cgroup_set_memory_protection_scale(u, name, &c->memory_low, message, flags, error); + + if (streq(name, "DefaultMemoryMinScale")) +- return bus_cgroup_set_memory_scale(u, name, &c->default_memory_min, message, flags, error); ++ return bus_cgroup_set_memory_protection_scale(u, name, &c->default_memory_min, message, flags, error); + + if (streq(name, "DefaultMemoryLowScale")) +- return bus_cgroup_set_memory_scale(u, name, &c->default_memory_low, message, flags, error); ++ return bus_cgroup_set_memory_protection_scale(u, name, &c->default_memory_low, message, flags, error); + + if (streq(name, "MemoryHighScale")) + return bus_cgroup_set_memory_scale(u, name, &c->memory_high, message, flags, error); +diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c +index 3b8ee6b124..33fdb82754 100644 +--- a/src/core/load-fragment.c ++++ b/src/core/load-fragment.c +@@ -3090,7 +3090,7 @@ int config_parse_memory_limit( + bytes = physical_memory_scale(r, 100U); + + if (bytes >= UINT64_MAX || +- (bytes <= 0 && !streq(lvalue, "MemorySwapMax"))) { ++ (bytes <= 0 && !STR_IN_SET(lvalue, "MemorySwapMax", "MemoryLow", "MemoryMin", "DefaultMemoryLow", "DefaultMemoryMin"))) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Memory limit '%s' out of range, ignoring.", rvalue); + return 0; + } diff --git a/SOURCES/0412-cgroup-Test-that-it-s-possible-to-set-memory-protect.patch b/SOURCES/0412-cgroup-Test-that-it-s-possible-to-set-memory-protect.patch new file mode 100644 index 0000000..bb8f9c9 --- /dev/null +++ b/SOURCES/0412-cgroup-Test-that-it-s-possible-to-set-memory-protect.patch @@ -0,0 +1,58 @@ +From ef4157dab3d267c33ec2a06ae9bb5e4c87f785a6 Mon Sep 17 00:00:00 2001 +From: Chris Down +Date: Fri, 3 May 2019 08:40:11 -0400 +Subject: [PATCH] cgroup: Test that it's possible to set memory protection to 0 + again + +The previous commit fixes this up, and this should prevent it +regressing. + +(cherry picked from commit 465ace74d9820824968ab5e82c81e42c2f1894b0) + +Related: #1763435 +--- + src/test/test-cgroup-unit-default.c | 6 +++--- + test/dml-passthrough-set-ml.service | 2 +- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/test/test-cgroup-unit-default.c b/src/test/test-cgroup-unit-default.c +index 54f7d50c45..1938609cee 100644 +--- a/src/test/test-cgroup-unit-default.c ++++ b/src/test/test-cgroup-unit-default.c +@@ -39,7 +39,7 @@ static int test_default_memory_low(void) { + * 1. dml-passthrough.slice sets MemoryLow=100. This should not affect its children, as only + * DefaultMemoryLow is propagated, not MemoryLow. As such, all leaf services should end up with + * memory.low as 50, inherited from dml.slice, *except* for dml-passthrough-set-ml.service, which +- * should have the value of 25, as it has MemoryLow explicitly set. ++ * should have the value of 0, as it has MemoryLow explicitly set. + * + * ┌───────────┐ + * │ dml.slice │ +@@ -49,7 +49,7 @@ static int test_default_memory_low(void) { + * │ dml-passthrough.slice │ + * └───────────┬───────────┘ + * ┌───────────────────────────────────┼───────────────────────────────────┐ +- * no new settings DefaultMemoryLow=15 MemoryLow=25 ++ * no new settings DefaultMemoryLow=15 MemoryLow=0 + * ┌───────────────┴───────────────┐ ┌────────────────┴────────────────┐ ┌───────────────┴────────────────┐ + * │ dml-passthrough-empty.service │ │ dml-passthrough-set-dml.service │ │ dml-passthrough-set-ml.service │ + * └───────────────────────────────┘ └─────────────────────────────────┘ └────────────────────────────────┘ +@@ -122,7 +122,7 @@ static int test_default_memory_low(void) { + assert_se(unit_get_ancestor_memory_low(dml_passthrough) == 100); + assert_se(unit_get_ancestor_memory_low(dml_passthrough_empty) == dml_tree_default); + assert_se(unit_get_ancestor_memory_low(dml_passthrough_set_dml) == 50); +- assert_se(unit_get_ancestor_memory_low(dml_passthrough_set_ml) == 25); ++ assert_se(unit_get_ancestor_memory_low(dml_passthrough_set_ml) == 0); + + assert_se(unit_get_ancestor_memory_low(dml_override) == dml_tree_default); + assert_se(unit_get_ancestor_memory_low(dml_override_empty) == 10); +diff --git a/test/dml-passthrough-set-ml.service b/test/dml-passthrough-set-ml.service +index 2abd591389..2e568b5deb 100644 +--- a/test/dml-passthrough-set-ml.service ++++ b/test/dml-passthrough-set-ml.service +@@ -5,4 +5,4 @@ Description=DML passthrough set ML service + Slice=dml-passthrough.slice + Type=oneshot + ExecStart=/bin/true +-MemoryLow=25 ++MemoryLow=0 diff --git a/SOURCES/0413-cgroup-Check-ancestor-memory-min-for-unified-memory-.patch b/SOURCES/0413-cgroup-Check-ancestor-memory-min-for-unified-memory-.patch new file mode 100644 index 0000000..7761e7f --- /dev/null +++ b/SOURCES/0413-cgroup-Check-ancestor-memory-min-for-unified-memory-.patch @@ -0,0 +1,28 @@ +From 49c990010f48b429b52f73f54d70d529f0d2c7fe Mon Sep 17 00:00:00 2001 +From: Chris Down +Date: Mon, 30 Sep 2019 18:24:26 +0100 +Subject: [PATCH] cgroup: Check ancestor memory min for unified memory config + +Otherwise we might not enable it when we should, ie. DefaultMemoryMin is +set in a parent, but not MemoryMin in the current unit. + +(cherry picked from commit 7c9d2b79935d413389a603918a711df75acd3f48) + +Related: #1763435 +--- + src/core/cgroup.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index d40f9cbc2a..4299923754 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -851,7 +851,7 @@ static bool unit_has_unified_memory_config(Unit *u) { + c = unit_get_cgroup_context(u); + assert(c); + +- return c->memory_min > 0 || unit_get_ancestor_memory_low(u) > 0 || ++ return unit_get_ancestor_memory_min(u) > 0 || unit_get_ancestor_memory_low(u) > 0 || + c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX || + c->memory_swap_max != CGROUP_LIMIT_MAX; + } diff --git a/SOURCES/0414-cgroup-Respect-DefaultMemoryMin-when-setting-memory..patch b/SOURCES/0414-cgroup-Respect-DefaultMemoryMin-when-setting-memory..patch new file mode 100644 index 0000000..4ded71a --- /dev/null +++ b/SOURCES/0414-cgroup-Respect-DefaultMemoryMin-when-setting-memory..patch @@ -0,0 +1,31 @@ +From 9c04746fb35b32b592fe14cab43782db9e0dee5c Mon Sep 17 00:00:00 2001 +From: Chris Down +Date: Mon, 30 Sep 2019 18:25:09 +0100 +Subject: [PATCH] cgroup: Respect DefaultMemoryMin when setting memory.min + +This is an oversight from https://github.com/systemd/systemd/pull/12332. + +Sadly the tests didn't catch it since it requires a real cgroup +hierarchy to see, and it wasn't seen in prod since we're only currently +using DefaultMemoryLow, not DefaultMemoryMin. :-( + +(cherry picked from commit 64fe532e90b3e99bf7821ded8a1107c239099e40) + +Related: #1763435 +--- + src/core/cgroup.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index 4299923754..7a9857adad 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -1111,7 +1111,7 @@ static void cgroup_context_apply( + log_cgroup_compat(u, "Applying MemoryLimit %" PRIu64 " as MemoryMax", max); + } + +- cgroup_apply_unified_memory_limit(u, "memory.min", c->memory_min); ++ cgroup_apply_unified_memory_limit(u, "memory.min", unit_get_ancestor_memory_min(u)); + cgroup_apply_unified_memory_limit(u, "memory.low", unit_get_ancestor_memory_low(u)); + cgroup_apply_unified_memory_limit(u, "memory.high", c->memory_high); + cgroup_apply_unified_memory_limit(u, "memory.max", max); diff --git a/SOURCES/0415-cgroup-Mark-memory-protections-as-explicitly-set-in-.patch b/SOURCES/0415-cgroup-Mark-memory-protections-as-explicitly-set-in-.patch new file mode 100644 index 0000000..c38a93e --- /dev/null +++ b/SOURCES/0415-cgroup-Mark-memory-protections-as-explicitly-set-in-.patch @@ -0,0 +1,108 @@ +From 4e7a3af028dfe6b5a4a85c31d670df73c08a0719 Mon Sep 17 00:00:00 2001 +From: Chris Down +Date: Mon, 30 Sep 2019 18:36:13 +0100 +Subject: [PATCH] cgroup: Mark memory protections as explicitly set in + transient units + +A later version of the DefaultMemory{Low,Min} patch changed these to +require explicitly setting memory_foo_set, but we only set that in +load-fragment, not dbus-cgroup. + +Without these, we may fall back to either DefaultMemoryFoo or +CGROUP_LIMIT_MIN when we really shouldn't. + +(cherry picked from commit 184e989d7da4648bd36511ffa28a9f2b469589d1) + +Related: #1763435 +--- + src/core/dbus-cgroup.c | 64 +++++++++++++++++++++++++++++++----------- + 1 file changed, 48 insertions(+), 16 deletions(-) + +diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c +index 39778a8dd4..de85a4851b 100644 +--- a/src/core/dbus-cgroup.c ++++ b/src/core/dbus-cgroup.c +@@ -665,17 +665,33 @@ int bus_cgroup_set_property( + if (streq(name, "MemoryAccounting")) + return bus_cgroup_set_boolean(u, name, &c->memory_accounting, CGROUP_MASK_MEMORY, message, flags, error); + +- if (streq(name, "MemoryMin")) +- return bus_cgroup_set_memory_protection(u, name, &c->memory_min, message, flags, error); ++ if (streq(name, "MemoryMin")) { ++ r = bus_cgroup_set_memory_protection(u, name, &c->memory_min, message, flags, error); ++ if (r > 0) ++ c->memory_min_set = true; ++ return r; ++ } + +- if (streq(name, "MemoryLow")) +- return bus_cgroup_set_memory_protection(u, name, &c->memory_low, message, flags, error); ++ if (streq(name, "MemoryLow")) { ++ r = bus_cgroup_set_memory_protection(u, name, &c->memory_low, message, flags, error); ++ if (r > 0) ++ c->memory_low_set = true; ++ return r; ++ } + +- if (streq(name, "DefaultMemoryMin")) +- return bus_cgroup_set_memory_protection(u, name, &c->default_memory_min, message, flags, error); ++ if (streq(name, "DefaultMemoryMin")) { ++ r = bus_cgroup_set_memory_protection(u, name, &c->default_memory_min, message, flags, error); ++ if (r > 0) ++ c->default_memory_min_set = true; ++ return r; ++ } + +- if (streq(name, "DefaultMemoryLow")) +- return bus_cgroup_set_memory_protection(u, name, &c->default_memory_low, message, flags, error); ++ if (streq(name, "DefaultMemoryLow")) { ++ r = bus_cgroup_set_memory_protection(u, name, &c->default_memory_low, message, flags, error); ++ if (r > 0) ++ c->default_memory_low_set = true; ++ return r; ++ } + + if (streq(name, "MemoryHigh")) + return bus_cgroup_set_memory(u, name, &c->memory_high, message, flags, error); +@@ -689,17 +705,33 @@ int bus_cgroup_set_property( + if (streq(name, "MemoryLimit")) + return bus_cgroup_set_memory(u, name, &c->memory_limit, message, flags, error); + +- if (streq(name, "MemoryMinScale")) +- return bus_cgroup_set_memory_protection_scale(u, name, &c->memory_min, message, flags, error); ++ if (streq(name, "MemoryMinScale")) { ++ r = bus_cgroup_set_memory_protection_scale(u, name, &c->memory_min, message, flags, error); ++ if (r > 0) ++ c->memory_min_set = true; ++ return r; ++ } + +- if (streq(name, "MemoryLowScale")) +- return bus_cgroup_set_memory_protection_scale(u, name, &c->memory_low, message, flags, error); ++ if (streq(name, "MemoryLowScale")) { ++ r = bus_cgroup_set_memory_protection_scale(u, name, &c->memory_low, message, flags, error); ++ if (r > 0) ++ c->memory_low_set = true; ++ return r; ++ } + +- if (streq(name, "DefaultMemoryMinScale")) +- return bus_cgroup_set_memory_protection_scale(u, name, &c->default_memory_min, message, flags, error); ++ if (streq(name, "DefaultMemoryMinScale")) { ++ r = bus_cgroup_set_memory_protection_scale(u, name, &c->default_memory_min, message, flags, error); ++ if (r > 0) ++ c->default_memory_min_set = true; ++ return r; ++ } + +- if (streq(name, "DefaultMemoryLowScale")) +- return bus_cgroup_set_memory_protection_scale(u, name, &c->default_memory_low, message, flags, error); ++ if (streq(name, "DefaultMemoryLowScale")) { ++ r = bus_cgroup_set_memory_protection_scale(u, name, &c->default_memory_low, message, flags, error); ++ if (r > 0) ++ c->default_memory_low_set = true; ++ return r; ++ } + + if (streq(name, "MemoryHighScale")) + return bus_cgroup_set_memory_scale(u, name, &c->memory_high, message, flags, error); diff --git a/SOURCES/0416-meson-allow-setting-the-version-string-during-config.patch b/SOURCES/0416-meson-allow-setting-the-version-string-during-config.patch new file mode 100644 index 0000000..9e082d2 --- /dev/null +++ b/SOURCES/0416-meson-allow-setting-the-version-string-during-config.patch @@ -0,0 +1,86 @@ +From 0065f2bf838dd0c24ec7be41439b4c0ba650029c Mon Sep 17 00:00:00 2001 +From: Jan Synacek +Date: Wed, 19 Feb 2020 15:36:13 +0100 +Subject: [PATCH] meson: allow setting the version string during configuration + +Very loosely based on upstream commits e1ca734edd17a90a325d5b566a4ea96e66c206e5 +and 681bd2c524ed71ac04045c90884ba8d55eee7b66. + +Resolves: #1804252 +--- + meson.build | 15 +++++++++++---- + meson_options.txt | 3 +++ + src/udev/udev-ctrl.c | 5 ++++- + 3 files changed, 18 insertions(+), 5 deletions(-) + +diff --git a/meson.build b/meson.build +index c8ae1e15bd..0ba3f924ea 100644 +--- a/meson.build ++++ b/meson.build +@@ -15,17 +15,24 @@ project('systemd', 'c', + libsystemd_version = '0.23.0' + libudev_version = '1.6.11' + ++dist_version = get_option('version-tag') ++if dist_version == '' ++ dist_version = meson.project_version() ++else ++ dist_version = meson.project_version() + ' (' + dist_version + ')' ++endif ++ + # We need the same data in two different formats, ugh! + # Also, for hysterical reasons, we use different variable + # names, sometimes. Not all variables are included in every + # set. Ugh, ugh, ugh! + conf = configuration_data() +-conf.set_quoted('PACKAGE_STRING', meson.project_name() + ' ' + meson.project_version()) +-conf.set_quoted('PACKAGE_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', meson.project_version()) ++substs.set('PACKAGE_VERSION', dist_version) + + ##################################################################### + +@@ -2871,7 +2878,7 @@ run_target( + ############################################################ + + status = [ +- '@0@ @1@'.format(meson.project_name(), meson.project_version()), ++ '@0@ @1@'.format(meson.project_name(), dist_version), + + 'split /usr: @0@'.format(split_usr), + 'split bin-sbin: @0@'.format(split_bin), +diff --git a/meson_options.txt b/meson_options.txt +index 563b11f0a2..0996891177 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -1,6 +1,9 @@ + # -*- mode: meson -*- + # SPDX-License-Identifier: LGPL-2.1+ + ++option('version-tag', type : 'string', ++ description : 'override the version string') ++ + option('split-usr', type : 'combo', choices : ['auto', 'true', 'false'], + description : '''/bin, /sbin aren't symlinks into /usr''') + option('split-bin', type : 'combo', choices : ['auto', 'true', 'false'], +diff --git a/src/udev/udev-ctrl.c b/src/udev/udev-ctrl.c +index efe7297f04..5382ce0d26 100644 +--- a/src/udev/udev-ctrl.c ++++ b/src/udev/udev-ctrl.c +@@ -239,7 +239,10 @@ static int ctrl_send(struct udev_ctrl *uctrl, enum udev_ctrl_msg_type type, int + int err = 0; + + memzero(&ctrl_msg_wire, sizeof(struct udev_ctrl_msg_wire)); +- strcpy(ctrl_msg_wire.version, "udev-" PACKAGE_VERSION); ++ /* jsynacek: As PACKAGE_VERSION is populated from the spec file with %{version}-%{release}, ++ * it might not fit entirely into the version field. */ ++ strncpy(ctrl_msg_wire.version, "udev-" PACKAGE_VERSION, 16-5); ++ ctrl_msg_wire.version[15] = '\0'; + ctrl_msg_wire.magic = UDEV_CTRL_MAGIC; + ctrl_msg_wire.type = type; + diff --git a/SOURCES/0417-core-don-t-consider-SERVICE_SKIP_CONDITION-for-abnor.patch b/SOURCES/0417-core-don-t-consider-SERVICE_SKIP_CONDITION-for-abnor.patch new file mode 100644 index 0000000..3ef1273 --- /dev/null +++ b/SOURCES/0417-core-don-t-consider-SERVICE_SKIP_CONDITION-for-abnor.patch @@ -0,0 +1,170 @@ +From 41346615264e01c6ff6118e09cf3ac4b4c71e89d Mon Sep 17 00:00:00 2001 +From: Anita Zhang +Date: Wed, 10 Jun 2020 01:18:00 -0700 +Subject: [PATCH] core: don't consider SERVICE_SKIP_CONDITION for abnormal or + failure restarts + +Fixes: #16115 +(cherry picked from commit bb9244781c6fc7608f7cac910269f8987b8adc01) + +Related: #1737283 +--- + src/core/service.c | 4 +-- + test/TEST-51-ISSUE-16115/Makefile | 1 + + test/TEST-51-ISSUE-16115/repro-1.service | 9 +++++ + test/TEST-51-ISSUE-16115/repro-2.service | 9 +++++ + test/TEST-51-ISSUE-16115/test.sh | 46 ++++++++++++++++++++++++ + test/TEST-51-ISSUE-16115/testsuite.sh | 19 ++++++++++ + test/test-functions | 2 +- + 7 files changed, 87 insertions(+), 3 deletions(-) + create mode 120000 test/TEST-51-ISSUE-16115/Makefile + create mode 100644 test/TEST-51-ISSUE-16115/repro-1.service + create mode 100644 test/TEST-51-ISSUE-16115/repro-2.service + create mode 100755 test/TEST-51-ISSUE-16115/test.sh + create mode 100755 test/TEST-51-ISSUE-16115/testsuite.sh + +diff --git a/src/core/service.c b/src/core/service.c +index 92be4280f6..1d98ee37fd 100644 +--- a/src/core/service.c ++++ b/src/core/service.c +@@ -1637,10 +1637,10 @@ static bool service_shall_restart(Service *s) { + return s->result == SERVICE_SUCCESS; + + case SERVICE_RESTART_ON_FAILURE: +- return s->result != SERVICE_SUCCESS; ++ return !IN_SET(s->result, SERVICE_SUCCESS, SERVICE_SKIP_CONDITION); + + case SERVICE_RESTART_ON_ABNORMAL: +- return !IN_SET(s->result, SERVICE_SUCCESS, SERVICE_FAILURE_EXIT_CODE); ++ return !IN_SET(s->result, SERVICE_SUCCESS, SERVICE_FAILURE_EXIT_CODE, SERVICE_SKIP_CONDITION); + + case SERVICE_RESTART_ON_WATCHDOG: + return s->result == SERVICE_FAILURE_WATCHDOG; +diff --git a/test/TEST-51-ISSUE-16115/Makefile b/test/TEST-51-ISSUE-16115/Makefile +new file mode 120000 +index 0000000000..e9f93b1104 +--- /dev/null ++++ b/test/TEST-51-ISSUE-16115/Makefile +@@ -0,0 +1 @@ ++../TEST-01-BASIC/Makefile +\ No newline at end of file +diff --git a/test/TEST-51-ISSUE-16115/repro-1.service b/test/TEST-51-ISSUE-16115/repro-1.service +new file mode 100644 +index 0000000000..96ecabe234 +--- /dev/null ++++ b/test/TEST-51-ISSUE-16115/repro-1.service +@@ -0,0 +1,9 @@ ++[Unit] ++Description=Issue 16115 Repro with on-abnormal ++ ++[Service] ++Type=simple ++Restart=on-abnormal ++ExecCondition=/bin/false ++ExecStart=sleep 100 ++RestartSec=1 +diff --git a/test/TEST-51-ISSUE-16115/repro-2.service b/test/TEST-51-ISSUE-16115/repro-2.service +new file mode 100644 +index 0000000000..6015ad8080 +--- /dev/null ++++ b/test/TEST-51-ISSUE-16115/repro-2.service +@@ -0,0 +1,9 @@ ++[Unit] ++Description=Issue 16115 Repro with on-failure ++ ++[Service] ++Type=simple ++Restart=on-failure ++ExecCondition=/bin/false ++ExecStart=sleep 100 ++RestartSec=1 +diff --git a/test/TEST-51-ISSUE-16115/test.sh b/test/TEST-51-ISSUE-16115/test.sh +new file mode 100755 +index 0000000000..09ac96ffce +--- /dev/null ++++ b/test/TEST-51-ISSUE-16115/test.sh +@@ -0,0 +1,46 @@ ++#!/usr/bin/env bash ++set -e ++TEST_DESCRIPTION="Test ExecCondition= does not restart on abnormal or failure" ++. $TEST_BASE_DIR/test-functions ++ ++test_setup() { ++ create_empty_image ++ mkdir -p $TESTDIR/root ++ mount ${LOOPDEV}p1 $TESTDIR/root ++ ++ ( ++ LOG_LEVEL=5 ++ eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) ++ ++ setup_basic_environment ++ ++ # mask some services that we do not want to run in these tests ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.service ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.socket ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-resolved.service ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-machined.service ++ ++ # setup the testsuite service ++ cat >$initdir/etc/systemd/system/testsuite.service < /testok ++ ++exit 0 +diff --git a/test/test-functions b/test/test-functions +index 7c4230b078..4d7832b1fb 100644 +--- a/test/test-functions ++++ b/test/test-functions +@@ -23,7 +23,7 @@ fi + + PATH_TO_INIT=$ROOTLIBDIR/systemd + +-BASICTOOLS="test sh bash setsid loadkeys setfont login sulogin gzip sleep echo mount umount cryptsetup date dmsetup modprobe sed cmp tee rm true false chmod chown ln xargs" ++BASICTOOLS="test sh bash setsid loadkeys setfont login sulogin gzip sleep echo mount umount cryptsetup date dmsetup modprobe sed cmp tee rm true false chmod chown ln xargs env" + DEBUGTOOLS="df free ls stty cat ps ln ip route dmesg dhclient mkdir cp ping dhclient strace less grep id tty touch du sort hostname find" + + STATEDIR="${BUILD_DIR:-.}/test/$(basename $(dirname $(realpath $0)))" diff --git a/SOURCES/0418-selinux-do-preprocessor-check-only-in-selinux-access.patch b/SOURCES/0418-selinux-do-preprocessor-check-only-in-selinux-access.patch new file mode 100644 index 0000000..d6b7c1a --- /dev/null +++ b/SOURCES/0418-selinux-do-preprocessor-check-only-in-selinux-access.patch @@ -0,0 +1,39 @@ +From 7301b170b266225f091e95ff52b3a95ff9776d13 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= +Date: Fri, 3 Apr 2020 09:13:59 +0200 +Subject: [PATCH] selinux: do preprocessor check only in selinux-access.c + +This has the advantage that mac_selinux_access_check() can be used as a +function in all contexts. For example, parameters passed to it won't be +reported as unused if the "function" call is replaced with 0 on SELinux +disabled builds. + +(cherry picked from commit 08deac6e3e9119aeb966375f94695e4aa14ffb1c) + +Related: #1830861 +--- + src/core/selinux-access.h | 9 --------- + 1 file changed, 9 deletions(-) + +diff --git a/src/core/selinux-access.h b/src/core/selinux-access.h +index 59f2e60c77..46a657a4b4 100644 +--- a/src/core/selinux-access.h ++++ b/src/core/selinux-access.h +@@ -12,17 +12,8 @@ + + int mac_selinux_generic_access_check(sd_bus_message *message, const char *path, const char *permission, sd_bus_error *error); + +-#if HAVE_SELINUX +- + #define mac_selinux_access_check(message, permission, error) \ + mac_selinux_generic_access_check((message), NULL, (permission), (error)) + + #define mac_selinux_unit_access_check(unit, message, permission, error) \ + mac_selinux_generic_access_check((message), unit_label_path(unit), (permission), (error)) +- +-#else +- +-#define mac_selinux_access_check(message, permission, error) 0 +-#define mac_selinux_unit_access_check(unit, message, permission, error) 0 +- +-#endif diff --git a/SOURCES/0419-basic-cgroup-util-introduce-cg_get_keyed_attribute_f.patch b/SOURCES/0419-basic-cgroup-util-introduce-cg_get_keyed_attribute_f.patch new file mode 100644 index 0000000..adcc7a4 --- /dev/null +++ b/SOURCES/0419-basic-cgroup-util-introduce-cg_get_keyed_attribute_f.patch @@ -0,0 +1,144 @@ +From 5aefc153b25b42a80942e5c367ce143817551870 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= +Date: Wed, 29 Apr 2020 17:40:22 +0200 +Subject: [PATCH] basic/cgroup-util: introduce cg_get_keyed_attribute_full() + +Callers of cg_get_keyed_attribute_full() can now specify via the flag whether the +missing keyes in cgroup attribute file are OK or not. Also the wrappers for both +strict and graceful version are provided. + +(cherry picked from commit 25a1f04c682260bb9b96e25bdf33665d6172db98) + +Related: #1830861 +--- + src/basic/cgroup-util.c | 13 ++++++++++--- + src/basic/cgroup-util.h | 24 +++++++++++++++++++++++- + src/test/test-cgroup-util.c | 20 ++++++++++++++++++++ + 3 files changed, 53 insertions(+), 4 deletions(-) + +diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c +index 92bc1f2543..4c0b73850d 100644 +--- a/src/basic/cgroup-util.c ++++ b/src/basic/cgroup-util.c +@@ -2021,12 +2021,13 @@ int cg_get_attribute(const char *controller, const char *path, const char *attri + return read_one_line_file(p, ret); + } + +-int cg_get_keyed_attribute( ++int cg_get_keyed_attribute_full( + const char *controller, + const char *path, + const char *attribute, + char **keys, +- char **ret_values) { ++ char **ret_values, ++ CGroupKeyMode mode) { + + _cleanup_free_ char *filename = NULL, *contents = NULL; + const char *p; +@@ -2086,7 +2087,10 @@ int cg_get_keyed_attribute( + p += strspn(p, NEWLINE); + } + +- r = -ENXIO; ++ if (mode & CG_KEY_MODE_GRACEFUL) ++ goto done; ++ else ++ r = -ENXIO; + + fail: + for (i = 0; i < n; i++) +@@ -2096,6 +2100,9 @@ fail: + + done: + memcpy(ret_values, v, sizeof(char*) * n); ++ if (mode & CG_KEY_MODE_GRACEFUL) ++ return n_done; ++ + return 0; + + } +diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h +index b414600dca..0673f4b4ce 100644 +--- a/src/basic/cgroup-util.h ++++ b/src/basic/cgroup-util.h +@@ -167,9 +167,31 @@ int cg_attach(const char *controller, const char *path, pid_t pid); + int cg_attach_fallback(const char *controller, const char *path, pid_t pid); + int cg_create_and_attach(const char *controller, const char *path, pid_t pid); + ++typedef enum { ++ CG_KEY_MODE_GRACEFUL = 1 << 0, ++} CGroupKeyMode; ++ + int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value); + int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret); +-int cg_get_keyed_attribute(const char *controller, const char *path, const char *attribute, char **keys, char **values); ++int cg_get_keyed_attribute_full(const char *controller, const char *path, const char *attribute, char **keys, char **values, CGroupKeyMode mode); ++ ++static inline int cg_get_keyed_attribute( ++ const char *controller, ++ const char *path, ++ const char *attribute, ++ char **keys, ++ char **ret_values) { ++ return cg_get_keyed_attribute_full(controller, path, attribute, keys, ret_values, 0); ++} ++ ++static inline int cg_get_keyed_attribute_graceful( ++ const char *controller, ++ const char *path, ++ const char *attribute, ++ char **keys, ++ char **ret_values) { ++ return cg_get_keyed_attribute_full(controller, path, attribute, keys, ret_values, CG_KEY_MODE_GRACEFUL); ++} + + int cg_set_access(const char *controller, const char *path, uid_t uid, gid_t gid); + +diff --git a/src/test/test-cgroup-util.c b/src/test/test-cgroup-util.c +index d49356315e..60d7bb19cb 100644 +--- a/src/test/test-cgroup-util.c ++++ b/src/test/test-cgroup-util.c +@@ -421,22 +421,42 @@ static void test_cg_get_keyed_attribute(void) { + } + + assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("no_such_attr"), &val) == -ENXIO); ++ assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", STRV_MAKE("no_such_attr"), &val) == 0); + assert_se(val == NULL); + + assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec"), &val) == 0); ++ free(val); ++ ++ assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec"), &val) == 1); + log_info("cpu /init.scope cpu.stat [usage_usec] → \"%s\"", val); + + assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "no_such_attr"), vals3) == -ENXIO); ++ assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "no_such_attr"), vals3) == 1); ++ assert(vals3[0] && !vals3[1]); ++ free(vals3[0]); + + assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "usage_usec"), vals3) == -ENXIO); ++ assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "usage_usec"), vals3) == 1); ++ assert(vals3[0] && !vals3[1]); ++ free(vals3[0]); + + assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", + STRV_MAKE("usage_usec", "user_usec", "system_usec"), vals3) == 0); ++ for (i = 0; i < 3; i++) ++ free(vals3[i]); ++ ++ assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", ++ STRV_MAKE("usage_usec", "user_usec", "system_usec"), vals3) == 3); + log_info("cpu /init.scope cpu.stat [usage_usec user_usec system_usec] → \"%s\", \"%s\", \"%s\"", + vals3[0], vals3[1], vals3[2]); + + assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", + STRV_MAKE("system_usec", "user_usec", "usage_usec"), vals3a) == 0); ++ for (i = 0; i < 3; i++) ++ free(vals3a[i]); ++ ++ assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", ++ STRV_MAKE("system_usec", "user_usec", "usage_usec"), vals3a) == 3); + log_info("cpu /init.scope cpu.stat [system_usec user_usec usage_usec] → \"%s\", \"%s\", \"%s\"", + vals3a[0], vals3a[1], vals3a[2]); + diff --git a/SOURCES/0420-shared-add-generic-logic-for-waiting-for-a-unit-to-e.patch b/SOURCES/0420-shared-add-generic-logic-for-waiting-for-a-unit-to-e.patch new file mode 100644 index 0000000..39ba3d7 --- /dev/null +++ b/SOURCES/0420-shared-add-generic-logic-for-waiting-for-a-unit-to-e.patch @@ -0,0 +1,526 @@ +From 5e422a9cea38bd5c7ce54c7bbac612c04418dc41 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 1 Apr 2019 18:54:59 +0200 +Subject: [PATCH] shared: add generic logic for waiting for a unit to enter + some state + +This is a generic implementation of a client-side logic of waiting until +a unit enters or leaves some state. + +This is a more generic implementation of the WaitContext logic currently +in systemctl.c, and is supposed to replace it (a later commit does +this). It's similar to bus-wait-for-jobs.c and we probably should fold +that one into it later on. + +This code is more powerful and cleaner than the WaitContext logic +however. In addition to waiting for a unit to exit this also allows us +to wait for a unit to leave the "maintainance" state. + +This commit only implements the generic logic, and adds no users of it +yet. + +(cherry picked from commit 3572d3df8f822d4cf1601428401a837f723771cf) + +Related: #1830861 +--- + src/shared/bus-wait-for-units.c | 434 ++++++++++++++++++++++++++++++++ + src/shared/bus-wait-for-units.h | 35 +++ + src/shared/meson.build | 2 + + 3 files changed, 471 insertions(+) + create mode 100644 src/shared/bus-wait-for-units.c + create mode 100644 src/shared/bus-wait-for-units.h + +diff --git a/src/shared/bus-wait-for-units.c b/src/shared/bus-wait-for-units.c +new file mode 100644 +index 0000000000..d07f491e93 +--- /dev/null ++++ b/src/shared/bus-wait-for-units.c +@@ -0,0 +1,434 @@ ++/* SPDX-License-Identifier: LGPL-2.1+ */ ++ ++#include "bus-util.h" ++#include "bus-wait-for-units.h" ++#include "hashmap.h" ++#include "string-util.h" ++#include "strv.h" ++#include "unit-def.h" ++ ++typedef struct WaitForItem { ++ BusWaitForUnits *parent; ++ ++ BusWaitForUnitsFlags flags; ++ ++ char *bus_path; ++ ++ sd_bus_slot *slot_get_all; ++ sd_bus_slot *slot_properties_changed; ++ ++ bus_wait_for_units_unit_callback unit_callback; ++ void *userdata; ++ ++ char *active_state; ++ uint32_t job_id; ++ char *clean_result; ++} WaitForItem; ++ ++typedef struct BusWaitForUnits { ++ sd_bus *bus; ++ sd_bus_slot *slot_disconnected; ++ ++ Hashmap *items; ++ ++ bus_wait_for_units_ready_callback ready_callback; ++ void *userdata; ++ ++ WaitForItem *current; ++ ++ BusWaitForUnitsState state; ++ bool has_failed:1; ++} BusWaitForUnits; ++ ++static WaitForItem *wait_for_item_free(WaitForItem *item) { ++ int r; ++ ++ if (!item) ++ return NULL; ++ ++ if (item->parent) { ++ if (FLAGS_SET(item->flags, BUS_WAIT_REFFED) && item->bus_path && item->parent->bus) { ++ r = sd_bus_call_method_async( ++ item->parent->bus, ++ NULL, ++ "org.freedesktop.systemd1", ++ item->bus_path, ++ "org.freedesktop.systemd1.Unit", ++ "Unref", ++ NULL, ++ NULL, ++ NULL); ++ if (r < 0) ++ log_debug_errno(r, "Failed to drop reference to unit %s, ignoring: %m", item->bus_path); ++ } ++ ++ assert_se(hashmap_remove(item->parent->items, item->bus_path) == item); ++ ++ if (item->parent->current == item) ++ item->parent->current = NULL; ++ } ++ ++ sd_bus_slot_unref(item->slot_properties_changed); ++ sd_bus_slot_unref(item->slot_get_all); ++ ++ free(item->bus_path); ++ free(item->active_state); ++ free(item->clean_result); ++ ++ return mfree(item); ++} ++ ++DEFINE_TRIVIAL_CLEANUP_FUNC(WaitForItem*, wait_for_item_free); ++ ++static void bus_wait_for_units_clear(BusWaitForUnits *d) { ++ WaitForItem *item; ++ ++ assert(d); ++ ++ d->slot_disconnected = sd_bus_slot_unref(d->slot_disconnected); ++ d->bus = sd_bus_unref(d->bus); ++ ++ while ((item = hashmap_first(d->items))) { ++ d->current = item; ++ ++ item->unit_callback(d, item->bus_path, false, item->userdata); ++ wait_for_item_free(item); ++ } ++ ++ d->items = hashmap_free(d->items); ++} ++ ++static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) { ++ BusWaitForUnits *d = userdata; ++ ++ assert(m); ++ assert(d); ++ ++ log_error("Warning! D-Bus connection terminated."); ++ ++ bus_wait_for_units_clear(d); ++ ++ if (d->ready_callback) ++ d->ready_callback(d, false, d->userdata); ++ else /* If no ready callback is specified close the connection so that the event loop exits */ ++ sd_bus_close(sd_bus_message_get_bus(m)); ++ ++ return 0; ++} ++ ++int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret) { ++ _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *d = NULL; ++ int r; ++ ++ assert(bus); ++ assert(ret); ++ ++ d = new(BusWaitForUnits, 1); ++ if (!d) ++ return -ENOMEM; ++ ++ *d = (BusWaitForUnits) { ++ .state = BUS_WAIT_SUCCESS, ++ .bus = sd_bus_ref(bus), ++ }; ++ ++ r = sd_bus_match_signal_async( ++ bus, ++ &d->slot_disconnected, ++ "org.freedesktop.DBus.Local", ++ NULL, ++ "org.freedesktop.DBus.Local", ++ "Disconnected", ++ match_disconnected, NULL, d); ++ if (r < 0) ++ return r; ++ ++ *ret = TAKE_PTR(d); ++ return 0; ++} ++ ++BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d) { ++ if (!d) ++ return NULL; ++ ++ bus_wait_for_units_clear(d); ++ sd_bus_slot_unref(d->slot_disconnected); ++ sd_bus_unref(d->bus); ++ ++ return mfree(d); ++} ++ ++static bool bus_wait_for_units_is_ready(BusWaitForUnits *d) { ++ assert(d); ++ ++ if (!d->bus) /* Disconnected? */ ++ return true; ++ ++ return hashmap_isempty(d->items); ++} ++ ++void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata) { ++ assert(d); ++ ++ d->ready_callback = callback; ++ d->userdata = userdata; ++} ++ ++static void bus_wait_for_units_check_ready(BusWaitForUnits *d) { ++ assert(d); ++ ++ if (!bus_wait_for_units_is_ready(d)) ++ return; ++ ++ d->state = d->has_failed ? BUS_WAIT_FAILURE : BUS_WAIT_SUCCESS; ++ ++ if (d->ready_callback) ++ d->ready_callback(d, d->state, d->userdata); ++} ++ ++static void wait_for_item_check_ready(WaitForItem *item) { ++ BusWaitForUnits *d; ++ ++ assert(item); ++ assert(d = item->parent); ++ ++ if (FLAGS_SET(item->flags, BUS_WAIT_FOR_MAINTENANCE_END)) { ++ ++ if (item->clean_result && !streq(item->clean_result, "success")) ++ d->has_failed = true; ++ ++ if (!item->active_state || streq(item->active_state, "maintenance")) ++ return; ++ } ++ ++ if (FLAGS_SET(item->flags, BUS_WAIT_NO_JOB) && item->job_id != 0) ++ return; ++ ++ if (FLAGS_SET(item->flags, BUS_WAIT_FOR_INACTIVE)) { ++ ++ if (streq_ptr(item->active_state, "failed")) ++ d->has_failed = true; ++ else if (!streq_ptr(item->active_state, "inactive")) ++ return; ++ } ++ ++ if (item->unit_callback) { ++ d->current = item; ++ item->unit_callback(d, item->bus_path, true, item->userdata); ++ } ++ ++ wait_for_item_free(item); ++ ++ bus_wait_for_units_check_ready(d); ++} ++ ++static int property_map_job( ++ sd_bus *bus, ++ const char *member, ++ sd_bus_message *m, ++ sd_bus_error *error, ++ void *userdata) { ++ ++ WaitForItem *item = userdata; ++ const char *path; ++ uint32_t id; ++ int r; ++ ++ assert(item); ++ ++ r = sd_bus_message_read(m, "(uo)", &id, &path); ++ if (r < 0) ++ return r; ++ ++ item->job_id = id; ++ return 0; ++} ++ ++static int wait_for_item_parse_properties(WaitForItem *item, sd_bus_message *m) { ++ ++ static const struct bus_properties_map map[] = { ++ { "ActiveState", "s", NULL, offsetof(WaitForItem, active_state) }, ++ { "Job", "(uo)", property_map_job, 0 }, ++ { "CleanResult", "s", NULL, offsetof(WaitForItem, clean_result) }, ++ {} ++ }; ++ ++ int r; ++ ++ assert(item); ++ assert(m); ++ ++ r = bus_message_map_all_properties(m, map, BUS_MAP_STRDUP, NULL, item); ++ if (r < 0) ++ return r; ++ ++ wait_for_item_check_ready(item); ++ return 0; ++} ++ ++static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) { ++ WaitForItem *item = userdata; ++ const char *interface; ++ int r; ++ ++ assert(item); ++ ++ r = sd_bus_message_read(m, "s", &interface); ++ if (r < 0) { ++ log_debug_errno(r, "Failed to parse PropertiesChanged signal: %m"); ++ return 0; ++ } ++ ++ if (!streq(interface, "org.freedesktop.systemd1.Unit")) ++ return 0; ++ ++ r = wait_for_item_parse_properties(item, m); ++ if (r < 0) ++ log_debug_errno(r, "Failed to process PropertiesChanged signal: %m"); ++ ++ return 0; ++} ++ ++static int on_get_all_properties(sd_bus_message *m, void *userdata, sd_bus_error *error) { ++ WaitForItem *item = userdata; ++ int r; ++ ++ assert(item); ++ ++ if (sd_bus_error_is_set(error)) { ++ BusWaitForUnits *d = item->parent; ++ ++ d->has_failed = true; ++ ++ log_debug_errno(sd_bus_error_get_errno(error), "GetAll() failed for %s: %s", ++ item->bus_path, error->message); ++ ++ d->current = item; ++ item->unit_callback(d, item->bus_path, false, item->userdata); ++ wait_for_item_free(item); ++ ++ bus_wait_for_units_check_ready(d); ++ return 0; ++ } ++ ++ r = wait_for_item_parse_properties(item, m); ++ if (r < 0) ++ log_debug_errno(r, "Failed to process GetAll method reply: %m"); ++ ++ return 0; ++} ++ ++int bus_wait_for_units_add_unit( ++ BusWaitForUnits *d, ++ const char *unit, ++ BusWaitForUnitsFlags flags, ++ bus_wait_for_units_unit_callback callback, ++ void *userdata) { ++ ++ _cleanup_(wait_for_item_freep) WaitForItem *item = NULL; ++ int r; ++ ++ assert(d); ++ assert(unit); ++ ++ assert(flags != 0); ++ ++ r = hashmap_ensure_allocated(&d->items, &string_hash_ops); ++ if (r < 0) ++ return r; ++ ++ item = new(WaitForItem, 1); ++ if (!item) ++ return -ENOMEM; ++ ++ *item = (WaitForItem) { ++ .flags = flags, ++ .bus_path = unit_dbus_path_from_name(unit), ++ .unit_callback = callback, ++ .userdata = userdata, ++ .job_id = UINT32_MAX, ++ }; ++ ++ if (!item->bus_path) ++ return -ENOMEM; ++ ++ if (!FLAGS_SET(item->flags, BUS_WAIT_REFFED)) { ++ r = sd_bus_call_method_async( ++ d->bus, ++ NULL, ++ "org.freedesktop.systemd1", ++ item->bus_path, ++ "org.freedesktop.systemd1.Unit", ++ "Ref", ++ NULL, ++ NULL, ++ NULL); ++ if (r < 0) ++ return log_debug_errno(r, "Failed to add reference to unit %s: %m", unit); ++ ++ ++ item->flags |= BUS_WAIT_REFFED; ++ } ++ ++ r = sd_bus_match_signal_async( ++ d->bus, ++ &item->slot_properties_changed, ++ "org.freedesktop.systemd1", ++ item->bus_path, ++ "org.freedesktop.DBus.Properties", ++ "PropertiesChanged", ++ on_properties_changed, ++ NULL, ++ item); ++ if (r < 0) ++ return log_debug_errno(r, "Failed to request match for PropertiesChanged signal: %m"); ++ ++ r = sd_bus_call_method_async( ++ d->bus, ++ &item->slot_get_all, ++ "org.freedesktop.systemd1", ++ item->bus_path, ++ "org.freedesktop.DBus.Properties", ++ "GetAll", ++ on_get_all_properties, ++ item, ++ "s", FLAGS_SET(item->flags, BUS_WAIT_FOR_MAINTENANCE_END) ? NULL : "org.freedesktop.systemd1.Unit"); ++ if (r < 0) ++ return log_debug_errno(r, "Failed to request properties of unit %s: %m", unit); ++ ++ r = hashmap_put(d->items, item->bus_path, item); ++ if (r < 0) ++ return r; ++ ++ d->state = BUS_WAIT_RUNNING; ++ item->parent = d; ++ TAKE_PTR(item); ++ return 0; ++} ++ ++int bus_wait_for_units_run(BusWaitForUnits *d) { ++ int r; ++ ++ assert(d); ++ ++ while (d->state == BUS_WAIT_RUNNING) { ++ ++ r = sd_bus_process(d->bus, NULL); ++ if (r < 0) ++ return r; ++ if (r > 0) ++ continue; ++ ++ r = sd_bus_wait(d->bus, (uint64_t) -1); ++ if (r < 0) ++ return r; ++ } ++ ++ return d->state; ++} ++ ++BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d) { ++ assert(d); ++ ++ return d->state; ++} +diff --git a/src/shared/bus-wait-for-units.h b/src/shared/bus-wait-for-units.h +new file mode 100644 +index 0000000000..a20f3d8fd7 +--- /dev/null ++++ b/src/shared/bus-wait-for-units.h +@@ -0,0 +1,35 @@ ++/* SPDX-License-Identifier: LGPL-2.1+ */ ++#pragma once ++ ++#include "macro.h" ++#include "sd-bus.h" ++ ++typedef struct BusWaitForUnits BusWaitForUnits; ++ ++typedef enum BusWaitForUnitsState { ++ BUS_WAIT_SUCCESS, /* Nothing to wait for anymore and nothing failed */ ++ BUS_WAIT_FAILURE, /* dito, but something failed */ ++ BUS_WAIT_RUNNING, /* Still something to wait for */ ++ _BUS_WAIT_FOR_UNITS_STATE_MAX, ++ _BUS_WAIT_FOR_UNITS_STATE_INVALID = -1, ++} BusWaitForUnitsState; ++ ++typedef enum BusWaitForUnitsFlags { ++ BUS_WAIT_FOR_MAINTENANCE_END = 1 << 0, /* Wait until the unit is no longer in maintenance state */ ++ BUS_WAIT_FOR_INACTIVE = 1 << 1, /* Wait until the unit is back in inactive or dead state */ ++ BUS_WAIT_NO_JOB = 1 << 2, /* Wait until there's no more job pending */ ++ BUS_WAIT_REFFED = 1 << 3, /* The unit is already reffed with RefUnit() */ ++} BusWaitForUnitsFlags; ++ ++typedef void (*bus_wait_for_units_ready_callback)(BusWaitForUnits *d, BusWaitForUnitsState state, void *userdata); ++typedef void (*bus_wait_for_units_unit_callback)(BusWaitForUnits *d, const char *unit_path, bool good, void *userdata); ++ ++int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret); ++BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d); ++ ++BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d); ++void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata); ++int bus_wait_for_units_add_unit(BusWaitForUnits *d, const char *unit, BusWaitForUnitsFlags flags, bus_wait_for_units_unit_callback callback, void *userdata); ++int bus_wait_for_units_run(BusWaitForUnits *d); ++ ++DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForUnits*, bus_wait_for_units_free); +diff --git a/src/shared/meson.build b/src/shared/meson.build +index 54e77e9af6..d0a1bba4c6 100644 +--- a/src/shared/meson.build ++++ b/src/shared/meson.build +@@ -18,6 +18,8 @@ shared_sources = files(''' + bus-unit-util.h + bus-util.c + bus-util.h ++ bus-wait-for-units.c ++ bus-wait-for-units.h + cgroup-show.c + cgroup-show.h + clean-ipc.c diff --git a/SOURCES/0421-shared-fix-assert-call.patch b/SOURCES/0421-shared-fix-assert-call.patch new file mode 100644 index 0000000..5650d80 --- /dev/null +++ b/SOURCES/0421-shared-fix-assert-call.patch @@ -0,0 +1,27 @@ +From 63b5df7c9fda4f7d44674076da5fc5cef4564f3a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Wed, 17 Jul 2019 09:39:39 +0200 +Subject: [PATCH] shared: fix assert call + +Fixup for 3572d3df8f8. Coverity CID#1403013. + +(cherry picked from commit 60b17d6fcd988c9995b7d1476d3aba1c4cbbfddd) + +Related: #1830861 +--- + src/shared/bus-wait-for-units.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shared/bus-wait-for-units.c b/src/shared/bus-wait-for-units.c +index d07f491e93..de15da1620 100644 +--- a/src/shared/bus-wait-for-units.c ++++ b/src/shared/bus-wait-for-units.c +@@ -190,7 +190,7 @@ static void wait_for_item_check_ready(WaitForItem *item) { + BusWaitForUnits *d; + + assert(item); +- assert(d = item->parent); ++ assert_se(d = item->parent); + + if (FLAGS_SET(item->flags, BUS_WAIT_FOR_MAINTENANCE_END)) { + diff --git a/SOURCES/0422-shared-Don-t-try-calling-NULL-callback-in-bus_wait_f.patch b/SOURCES/0422-shared-Don-t-try-calling-NULL-callback-in-bus_wait_f.patch new file mode 100644 index 0000000..43aaa66 --- /dev/null +++ b/SOURCES/0422-shared-Don-t-try-calling-NULL-callback-in-bus_wait_f.patch @@ -0,0 +1,28 @@ +From e607286e070675498fcd5a7ab73bc3da533f9eea Mon Sep 17 00:00:00 2001 +From: Balint Reczey +Date: Wed, 22 Apr 2020 09:51:53 +0200 +Subject: [PATCH] shared: Don't try calling NULL callback in + bus_wait_for_units_clear + +BugLink: https://bugs.launchpad.net/bugs/1870930 +(cherry picked from commit 9f656373082cb13542b877b4f5cb917ef5ff329c) + +Related: #1830861 +--- + src/shared/bus-wait-for-units.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/shared/bus-wait-for-units.c b/src/shared/bus-wait-for-units.c +index de15da1620..63ba3fd422 100644 +--- a/src/shared/bus-wait-for-units.c ++++ b/src/shared/bus-wait-for-units.c +@@ -91,7 +91,8 @@ static void bus_wait_for_units_clear(BusWaitForUnits *d) { + while ((item = hashmap_first(d->items))) { + d->current = item; + +- item->unit_callback(d, item->bus_path, false, item->userdata); ++ if (item->unit_callback) ++ item->unit_callback(d, item->bus_path, false, item->userdata); + wait_for_item_free(item); + } + diff --git a/SOURCES/0423-shared-add-NULL-callback-check-in-one-more-place.patch b/SOURCES/0423-shared-add-NULL-callback-check-in-one-more-place.patch new file mode 100644 index 0000000..f0d4a5d --- /dev/null +++ b/SOURCES/0423-shared-add-NULL-callback-check-in-one-more-place.patch @@ -0,0 +1,77 @@ +From 48ab0db62ab23307e9f35a862a9c9b33ba3ef261 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Thu, 23 Apr 2020 14:53:54 +0200 +Subject: [PATCH] shared: add NULL callback check in one more place + +Follow-up for 9f65637308. + +(cherry picked from commit d3d53e5cd143bf96d1eb0e254f16fa8d458d38ce) + +Related: #1830861 +--- + src/shared/bus-wait-for-units.c | 31 +++++++++++++------------------ + 1 file changed, 13 insertions(+), 18 deletions(-) + +diff --git a/src/shared/bus-wait-for-units.c b/src/shared/bus-wait-for-units.c +index 63ba3fd422..ba97922764 100644 +--- a/src/shared/bus-wait-for-units.c ++++ b/src/shared/bus-wait-for-units.c +@@ -80,6 +80,15 @@ static WaitForItem *wait_for_item_free(WaitForItem *item) { + + DEFINE_TRIVIAL_CLEANUP_FUNC(WaitForItem*, wait_for_item_free); + ++static void call_unit_callback_and_wait(BusWaitForUnits *d, WaitForItem *item, bool good) { ++ d->current = item; ++ ++ if (item->unit_callback) ++ item->unit_callback(d, item->bus_path, good, item->userdata); ++ ++ wait_for_item_free(item); ++} ++ + static void bus_wait_for_units_clear(BusWaitForUnits *d) { + WaitForItem *item; + +@@ -88,13 +97,8 @@ static void bus_wait_for_units_clear(BusWaitForUnits *d) { + d->slot_disconnected = sd_bus_slot_unref(d->slot_disconnected); + d->bus = sd_bus_unref(d->bus); + +- while ((item = hashmap_first(d->items))) { +- d->current = item; +- +- if (item->unit_callback) +- item->unit_callback(d, item->bus_path, false, item->userdata); +- wait_for_item_free(item); +- } ++ while ((item = hashmap_first(d->items))) ++ call_unit_callback_and_wait(d, item, false); + + d->items = hashmap_free(d->items); + } +@@ -213,13 +217,7 @@ static void wait_for_item_check_ready(WaitForItem *item) { + return; + } + +- if (item->unit_callback) { +- d->current = item; +- item->unit_callback(d, item->bus_path, true, item->userdata); +- } +- +- wait_for_item_free(item); +- ++ call_unit_callback_and_wait(d, item, true); + bus_wait_for_units_check_ready(d); + } + +@@ -304,10 +302,7 @@ static int on_get_all_properties(sd_bus_message *m, void *userdata, sd_bus_error + log_debug_errno(sd_bus_error_get_errno(error), "GetAll() failed for %s: %s", + item->bus_path, error->message); + +- d->current = item; +- item->unit_callback(d, item->bus_path, false, item->userdata); +- wait_for_item_free(item); +- ++ call_unit_callback_and_wait(d, item, false); + bus_wait_for_units_check_ready(d); + return 0; + } diff --git a/SOURCES/0424-core-introduce-support-for-cgroup-freezer.patch b/SOURCES/0424-core-introduce-support-for-cgroup-freezer.patch new file mode 100644 index 0000000..58cd2e7 --- /dev/null +++ b/SOURCES/0424-core-introduce-support-for-cgroup-freezer.patch @@ -0,0 +1,1146 @@ +From 046ea98539eb8d7cef93d8062035fa6d9f58efea Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= +Date: Wed, 29 Apr 2020 17:53:43 +0200 +Subject: [PATCH] core: introduce support for cgroup freezer + +With cgroup v2 the cgroup freezer is implemented as a cgroup +attribute called cgroup.freeze. cgroup can be frozen by writing "1" +to the file and kernel will send us a notification through +"cgroup.events" after the operation is finished and processes in the +cgroup entered quiescent state, i.e. they are not scheduled to +run. Writing "0" to the attribute file does the inverse and process +execution is resumed. + +This commit exposes above low-level functionality through systemd's DBus +API. Each unit type must provide specialized implementation for these +methods, otherwise, we return an error. So far only service, scope, and +slice unit types provide the support. It is possible to check if a +given unit has the support using CanFreeze() DBus property. + +Note that DBus API has a synchronous behavior and we dispatch the reply +to freeze/thaw requests only after the kernel has notified us that +requested operation was completed. + +(cherry picked from commit d9e45bc3abb8adf5a1cb20816ba8f2d2aa65b17e) + +Resolves: #1830861 +--- + man/systemctl.xml | 24 +++++ + src/basic/cgroup-util.c | 18 +++- + src/basic/cgroup-util.h | 1 + + src/basic/unit-def.c | 9 ++ + src/basic/unit-def.h | 12 +++ + src/core/cgroup.c | 105 ++++++++++++++++-- + src/core/cgroup.h | 12 +++ + src/core/dbus-manager.c | 50 +++++++++ + src/core/dbus-unit.c | 96 +++++++++++++++++ + src/core/dbus-unit.h | 3 + + src/core/dbus.c | 7 +- + src/core/scope.c | 3 + + src/core/service.c | 3 + + src/core/slice.c | 80 ++++++++++++++ + src/core/unit.c | 123 ++++++++++++++++++++++ + src/core/unit.h | 20 ++++ + src/libsystemd/sd-bus/bus-common-errors.h | 2 + + src/systemctl/systemctl.c | 105 +++++++++++++++++- + 18 files changed, 660 insertions(+), 13 deletions(-) + +diff --git a/man/systemctl.xml b/man/systemctl.xml +index d95d3726af..6145486123 100644 +--- a/man/systemctl.xml ++++ b/man/systemctl.xml +@@ -873,6 +873,30 @@ Sun 2017-02-26 20:57:49 EST 2h 3min left Sun 2017-02-26 11:56:36 EST 6h ago + the signal to send. + + ++ ++ freeze PATTERN ++ ++ ++ Freeze one or more units specified on the ++ command line using cgroup freezer ++ ++ Freezing the unit will cause all processes contained within the cgroup corresponding to the unit ++ to be suspended. Being suspended means that unit's processes won't be scheduled to run on CPU until thawed. ++ Note that this command is supported only on systems that use unified cgroup hierarchy. Unit is automatically ++ thawed just before we execute a job against the unit, e.g. before the unit is stopped. ++ ++ ++ ++ thaw PATTERN ++ ++ ++ Thaw (unfreeze) one or more units specified on the ++ command line. ++ ++ This is the inverse operation to the freeze command and resumes the execution of ++ processes in the unit's cgroup. ++ ++ + + is-active PATTERN + +diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c +index 4c0b73850d..992b12811a 100644 +--- a/src/basic/cgroup-util.c ++++ b/src/basic/cgroup-util.c +@@ -137,6 +137,17 @@ bool cg_ns_supported(void) { + return enabled; + } + ++bool cg_freezer_supported(void) { ++ static thread_local int supported = -1; ++ ++ if (supported >= 0) ++ return supported; ++ ++ supported = cg_all_unified() > 0 && access("/sys/fs/cgroup/init.scope/cgroup.freeze", F_OK) == 0; ++ ++ return supported; ++} ++ + int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) { + _cleanup_free_ char *fs = NULL; + int r; +@@ -2039,7 +2050,8 @@ int cg_get_keyed_attribute_full( + * all keys to retrieve. The 'ret_values' parameter should be passed as string size with the same number of + * entries as 'keys'. On success each entry will be set to the value of the matching key. + * +- * If the attribute file doesn't exist at all returns ENOENT, if any key is not found returns ENXIO. */ ++ * If the attribute file doesn't exist at all returns ENOENT, if any key is not found returns ENXIO. If mode ++ * is set to GG_KEY_MODE_GRACEFUL we ignore missing keys and return those that were parsed successfully. */ + + r = cg_get_path(controller, path, attribute, &filename); + if (r < 0) +@@ -2089,8 +2101,8 @@ int cg_get_keyed_attribute_full( + + if (mode & CG_KEY_MODE_GRACEFUL) + goto done; +- else +- r = -ENXIO; ++ ++ r = -ENXIO; + + fail: + for (i = 0; i < n; i++) +diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h +index 0673f4b4ce..1210b38a83 100644 +--- a/src/basic/cgroup-util.h ++++ b/src/basic/cgroup-util.h +@@ -250,6 +250,7 @@ int cg_mask_to_string(CGroupMask mask, char **ret); + int cg_kernel_controllers(Set **controllers); + + bool cg_ns_supported(void); ++bool cg_freezer_supported(void); + + int cg_all_unified(void); + int cg_hybrid_unified(void); +diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c +index 46593f6e65..e79cc73dd3 100644 +--- a/src/basic/unit-def.c ++++ b/src/basic/unit-def.c +@@ -107,6 +107,15 @@ static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { + + DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); + ++static const char* const freezer_state_table[_FREEZER_STATE_MAX] = { ++ [FREEZER_RUNNING] = "running", ++ [FREEZER_FREEZING] = "freezing", ++ [FREEZER_FROZEN] = "frozen", ++ [FREEZER_THAWING] = "thawing", ++}; ++ ++DEFINE_STRING_TABLE_LOOKUP(freezer_state, FreezerState); ++ + static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = { + [AUTOMOUNT_DEAD] = "dead", + [AUTOMOUNT_WAITING] = "waiting", +diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h +index db397a31ed..8eea379a6d 100644 +--- a/src/basic/unit-def.h ++++ b/src/basic/unit-def.h +@@ -44,6 +44,15 @@ typedef enum UnitActiveState { + _UNIT_ACTIVE_STATE_INVALID = -1 + } UnitActiveState; + ++typedef enum FreezerState { ++ FREEZER_RUNNING, ++ FREEZER_FREEZING, ++ FREEZER_FROZEN, ++ FREEZER_THAWING, ++ _FREEZER_STATE_MAX, ++ _FREEZER_STATE_INVALID = -1 ++} FreezerState; ++ + typedef enum AutomountState { + AUTOMOUNT_DEAD, + AUTOMOUNT_WAITING, +@@ -245,6 +254,9 @@ UnitLoadState unit_load_state_from_string(const char *s) _pure_; + const char *unit_active_state_to_string(UnitActiveState i) _const_; + UnitActiveState unit_active_state_from_string(const char *s) _pure_; + ++const char *freezer_state_to_string(FreezerState i) _const_; ++FreezerState freezer_state_from_string(const char *s) _pure_; ++ + const char* automount_state_to_string(AutomountState i) _const_; + AutomountState automount_state_from_string(const char *s) _pure_; + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index 7a9857adad..e7ae9273a6 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -2279,6 +2279,51 @@ void unit_add_to_cgroup_empty_queue(Unit *u) { + log_debug_errno(r, "Failed to enable cgroup empty event source: %m"); + } + ++static void unit_remove_from_cgroup_empty_queue(Unit *u) { ++ assert(u); ++ ++ if (!u->in_cgroup_empty_queue) ++ return; ++ ++ LIST_REMOVE(cgroup_empty_queue, u->manager->cgroup_empty_queue, u); ++ u->in_cgroup_empty_queue = false; ++} ++ ++static int unit_check_cgroup_events(Unit *u) { ++ char *values[2] = {}; ++ int r; ++ ++ assert(u); ++ ++ r = cg_get_keyed_attribute_graceful(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events", ++ STRV_MAKE("populated", "frozen"), values); ++ if (r < 0) ++ return r; ++ ++ /* The cgroup.events notifications can be merged together so act as we saw the given state for the ++ * first time. The functions we call to handle given state are idempotent, which makes them ++ * effectively remember the previous state. */ ++ if (values[0]) { ++ if (streq(values[0], "1")) ++ unit_remove_from_cgroup_empty_queue(u); ++ else ++ unit_add_to_cgroup_empty_queue(u); ++ } ++ ++ /* Disregard freezer state changes due to operations not initiated by us */ ++ if (values[1] && IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_THAWING)) { ++ if (streq(values[1], "0")) ++ unit_thawed(u); ++ else ++ unit_frozen(u); ++ } ++ ++ free(values[0]); ++ free(values[1]); ++ ++ return 0; ++} ++ + static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + +@@ -2310,15 +2355,12 @@ static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents, + /* The watch was just removed */ + continue; + +- u = hashmap_get(m->cgroup_inotify_wd_unit, INT_TO_PTR(e->wd)); +- if (!u) /* Not that inotify might deliver +- * events for a watch even after it +- * was removed, because it was queued +- * before the removal. Let's ignore +- * this here safely. */ +- continue; ++ /* Note that inotify might deliver events for a watch even after it was removed, ++ * because it was queued before the removal. Let's ignore this here safely. */ + +- unit_add_to_cgroup_empty_queue(u); ++ u = hashmap_get(m->cgroup_inotify_wd_unit, INT_TO_PTR(e->wd)); ++ if (u) ++ unit_check_cgroup_events(u); + } + } + } +@@ -2884,6 +2926,46 @@ void manager_invalidate_startup_units(Manager *m) { + unit_invalidate_cgroup(u, CGROUP_MASK_CPU|CGROUP_MASK_IO|CGROUP_MASK_BLKIO); + } + ++int unit_cgroup_freezer_action(Unit *u, FreezerAction action) { ++ _cleanup_free_ char *path = NULL; ++ FreezerState target, kernel = _FREEZER_STATE_INVALID; ++ int r; ++ ++ assert(u); ++ assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW)); ++ ++ if (!u->cgroup_realized) ++ return -EBUSY; ++ ++ target = action == FREEZER_FREEZE ? FREEZER_FROZEN : FREEZER_RUNNING; ++ ++ r = unit_freezer_state_kernel(u, &kernel); ++ if (r < 0) ++ log_unit_debug_errno(u, r, "Failed to obtain cgroup freezer state: %m"); ++ ++ if (target == kernel) { ++ u->freezer_state = target; ++ return 0; ++ } ++ ++ r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.freeze", &path); ++ if (r < 0) ++ return r; ++ ++ log_unit_debug(u, "%s unit.", action == FREEZER_FREEZE ? "Freezing" : "Thawing"); ++ ++ if (action == FREEZER_FREEZE) ++ u->freezer_state = FREEZER_FREEZING; ++ else ++ u->freezer_state = FREEZER_THAWING; ++ ++ r = write_string_file(path, one_zero(action == FREEZER_FREEZE), WRITE_STRING_FILE_DISABLE_BUFFER); ++ if (r < 0) ++ return r; ++ ++ return 0; ++} ++ + static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = { + [CGROUP_AUTO] = "auto", + [CGROUP_CLOSED] = "closed", +@@ -2919,3 +3001,10 @@ int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name) { + } + + DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy); ++ ++static const char* const freezer_action_table[_FREEZER_ACTION_MAX] = { ++ [FREEZER_FREEZE] = "freeze", ++ [FREEZER_THAW] = "thaw", ++}; ++ ++DEFINE_STRING_TABLE_LOOKUP(freezer_action, FreezerAction); +diff --git a/src/core/cgroup.h b/src/core/cgroup.h +index 976224336d..36ea77fdc5 100644 +--- a/src/core/cgroup.h ++++ b/src/core/cgroup.h +@@ -33,6 +33,14 @@ typedef enum CGroupDevicePolicy { + _CGROUP_DEVICE_POLICY_INVALID = -1 + } CGroupDevicePolicy; + ++typedef enum FreezerAction { ++ FREEZER_FREEZE, ++ FREEZER_THAW, ++ ++ _FREEZER_ACTION_MAX, ++ _FREEZER_ACTION_INVALID = -1, ++} FreezerAction; ++ + struct CGroupDeviceAllow { + LIST_FIELDS(CGroupDeviceAllow, device_allow); + char *path; +@@ -235,3 +243,7 @@ CGroupDevicePolicy cgroup_device_policy_from_string(const char *s) _pure_; + + bool unit_cgroup_delegate(Unit *u); + int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name); ++int unit_cgroup_freezer_action(Unit *u, FreezerAction action); ++ ++const char* freezer_action_to_string(FreezerAction a) _const_; ++FreezerAction freezer_action_from_string(const char *s) _pure_; +diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c +index b3c011b0df..a0777f63d5 100644 +--- a/src/core/dbus-manager.c ++++ b/src/core/dbus-manager.c +@@ -683,6 +683,54 @@ static int method_unref_unit(sd_bus_message *message, void *userdata, sd_bus_err + return bus_unit_method_unref(message, u, error); + } + ++static int method_freeze_unit(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 = bus_load_unit_by_name(m, message, name, &u, error); ++ if (r < 0) ++ return r; ++ ++ r = bus_unit_validate_load_state(u, error); ++ if (r < 0) ++ return r; ++ ++ return bus_unit_method_freeze(message, u, error); ++} ++ ++static int method_thaw_unit(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 = bus_load_unit_by_name(m, message, name, &u, error); ++ if (r < 0) ++ return r; ++ ++ r = bus_unit_validate_load_state(u, error); ++ if (r < 0) ++ return r; ++ ++ return bus_unit_method_thaw(message, u, error); ++} ++ + static int reply_unit_info(sd_bus_message *reply, Unit *u) { + _cleanup_free_ char *unit_path = NULL, *job_path = NULL; + Unit *following; +@@ -2500,6 +2548,8 @@ const sd_bus_vtable bus_manager_vtable[] = { + 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("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), + SD_BUS_METHOD("ResetFailedUnit", "s", NULL, method_reset_failed_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetUnitProperties", "sba(sv)", NULL, method_set_unit_properties, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RefUnit", "s", NULL, method_ref_unit, SD_BUS_VTABLE_UNPRIVILEGED), +diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c +index aa15e47754..ce81103e92 100644 +--- a/src/core/dbus-unit.c ++++ b/src/core/dbus-unit.c +@@ -42,12 +42,14 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_job_mode, job_mode, JobMode); + static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_emergency_action, emergency_action, EmergencyAction); + static BUS_DEFINE_PROPERTY_GET(property_get_description, "s", Unit, unit_description); + static BUS_DEFINE_PROPERTY_GET2(property_get_active_state, "s", Unit, unit_active_state, unit_active_state_to_string); ++static BUS_DEFINE_PROPERTY_GET2(property_get_freezer_state, "s", Unit, unit_freezer_state, freezer_state_to_string); + static BUS_DEFINE_PROPERTY_GET(property_get_sub_state, "s", Unit, unit_sub_state_to_string); + static BUS_DEFINE_PROPERTY_GET2(property_get_unit_file_state, "s", Unit, unit_get_unit_file_state, unit_file_state_to_string); + static BUS_DEFINE_PROPERTY_GET(property_get_can_reload, "b", Unit, unit_can_reload); + static BUS_DEFINE_PROPERTY_GET(property_get_can_start, "b", Unit, unit_can_start_refuse_manual); + static BUS_DEFINE_PROPERTY_GET(property_get_can_stop, "b", Unit, unit_can_stop_refuse_manual); + static BUS_DEFINE_PROPERTY_GET(property_get_can_isolate, "b", Unit, unit_can_isolate_refuse_manual); ++static BUS_DEFINE_PROPERTY_GET(property_get_can_freeze, "b", Unit, unit_can_freeze); + static BUS_DEFINE_PROPERTY_GET(property_get_need_daemon_reload, "b", Unit, unit_need_daemon_reload); + static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_empty_strv, "as", 0); + +@@ -561,6 +563,79 @@ int bus_unit_method_unref(sd_bus_message *message, void *userdata, sd_bus_error + return sd_bus_reply_method_return(message, NULL); + } + ++static int bus_unit_method_freezer_generic(sd_bus_message *message, void *userdata, sd_bus_error *error, FreezerAction action) { ++ const char* perm; ++ int (*method)(Unit*); ++ Unit *u = userdata; ++ bool reply_no_delay = false; ++ int r; ++ ++ assert(message); ++ assert(u); ++ assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW)); ++ ++ if (action == FREEZER_FREEZE) { ++ perm = "stop"; ++ method = unit_freeze; ++ } else { ++ perm = "start"; ++ method = unit_thaw; ++ } ++ ++ r = mac_selinux_unit_access_check(u, message, perm, error); ++ if (r < 0) ++ return r; ++ ++ r = bus_verify_manage_units_async_full( ++ u, ++ perm, ++ CAP_SYS_ADMIN, ++ N_("Authentication is required to freeze or thaw the processes of '$(unit)' unit."), ++ 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 */ ++ ++ r = method(u); ++ if (r == -EOPNOTSUPP) ++ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Unit '%s' does not support freezing.", u->id); ++ if (r == -EBUSY) ++ return sd_bus_error_setf(error, BUS_ERROR_UNIT_BUSY, "Unit has a pending job."); ++ if (r == -EHOSTDOWN) ++ return sd_bus_error_setf(error, BUS_ERROR_UNIT_INACTIVE, "Unit is inactive."); ++ if (r == -EALREADY) ++ return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Previously requested freezer operation for unit '%s' is still in progress.", u->id); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ reply_no_delay = true; ++ ++ assert(!u->pending_freezer_message); ++ ++ r = sd_bus_message_new_method_return(message, &u->pending_freezer_message); ++ if (r < 0) ++ return r; ++ ++ if (reply_no_delay) { ++ r = bus_unit_send_pending_freezer_message(u); ++ if (r < 0) ++ return r; ++ } ++ ++ return 1; ++} ++ ++int bus_unit_method_thaw(sd_bus_message *message, void *userdata, sd_bus_error *error) { ++ return bus_unit_method_freezer_generic(message, userdata, error, FREEZER_THAW); ++} ++ ++int bus_unit_method_freeze(sd_bus_message *message, void *userdata, sd_bus_error *error) { ++ return bus_unit_method_freezer_generic(message, userdata, error, FREEZER_FREEZE); ++} ++ + const sd_bus_vtable bus_unit_vtable[] = { + SD_BUS_VTABLE_START(0), + +@@ -592,6 +667,7 @@ const sd_bus_vtable bus_unit_vtable[] = { + SD_BUS_PROPERTY("Description", "s", property_get_description, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LoadState", "s", property_get_load_state, offsetof(Unit, load_state), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ActiveState", "s", property_get_active_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), ++ SD_BUS_PROPERTY("FreezerState", "s", property_get_freezer_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("SubState", "s", property_get_sub_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("FragmentPath", "s", NULL, offsetof(Unit, fragment_path), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SourcePath", "s", NULL, offsetof(Unit, source_path), SD_BUS_VTABLE_PROPERTY_CONST), +@@ -607,6 +683,7 @@ const sd_bus_vtable bus_unit_vtable[] = { + SD_BUS_PROPERTY("CanStop", "b", property_get_can_stop, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CanReload", "b", property_get_can_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CanIsolate", "b", property_get_can_isolate, 0, SD_BUS_VTABLE_PROPERTY_CONST), ++ SD_BUS_PROPERTY("CanFreeze", "b", property_get_can_freeze, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Job", "(uo)", property_get_job, offsetof(Unit, job), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("StopWhenUnneeded", "b", bus_property_get_bool, offsetof(Unit, stop_when_unneeded), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RefuseManualStart", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_start), SD_BUS_VTABLE_PROPERTY_CONST), +@@ -650,6 +727,8 @@ const sd_bus_vtable bus_unit_vtable[] = { + SD_BUS_METHOD("SetProperties", "ba(sv)", NULL, bus_unit_method_set_properties, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Ref", NULL, NULL, bus_unit_method_ref, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Unref", NULL, NULL, bus_unit_method_unref, SD_BUS_VTABLE_UNPRIVILEGED), ++ SD_BUS_METHOD("Freeze", NULL, NULL, bus_unit_method_freeze, SD_BUS_VTABLE_UNPRIVILEGED), ++ SD_BUS_METHOD("Thaw", NULL, NULL, bus_unit_method_thaw, SD_BUS_VTABLE_UNPRIVILEGED), + + /* For dependency types we don't support anymore always return an empty array */ + SD_BUS_PROPERTY("RequiresOverridable", "as", property_get_empty_strv, 0, SD_BUS_VTABLE_HIDDEN), +@@ -1209,6 +1288,23 @@ void bus_unit_send_change_signal(Unit *u) { + u->sent_dbus_new_signal = true; + } + ++int bus_unit_send_pending_freezer_message(Unit *u) { ++ int r; ++ ++ assert(u); ++ ++ if (!u->pending_freezer_message) ++ return 0; ++ ++ r = sd_bus_send(NULL, u->pending_freezer_message, NULL); ++ if (r < 0) ++ log_warning_errno(r, "Failed to send queued message, ignoring: %m"); ++ ++ u->pending_freezer_message = sd_bus_message_unref(u->pending_freezer_message); ++ ++ return 0; ++} ++ + static int send_removed_signal(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *p = NULL; +diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h +index 68eb621836..39aa1bb53c 100644 +--- a/src/core/dbus-unit.h ++++ b/src/core/dbus-unit.h +@@ -11,6 +11,7 @@ extern const sd_bus_vtable bus_unit_vtable[]; + extern const sd_bus_vtable bus_unit_cgroup_vtable[]; + + void bus_unit_send_change_signal(Unit *u); ++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); +@@ -23,6 +24,8 @@ int bus_unit_method_get_processes(sd_bus_message *message, void *userdata, sd_bu + int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd_bus_error *error); + int bus_unit_method_ref(sd_bus_message *message, void *userdata, sd_bus_error *error); + int bus_unit_method_unref(sd_bus_message *message, void *userdata, sd_bus_error *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); + int bus_unit_validate_load_state(Unit *u, sd_bus_error *error); +diff --git a/src/core/dbus.c b/src/core/dbus.c +index 346a440c5d..b69c11c519 100644 +--- a/src/core/dbus.c ++++ b/src/core/dbus.c +@@ -1073,10 +1073,15 @@ static void destroy_bus(Manager *m, sd_bus **bus) { + if (j->bus_track && sd_bus_track_get_bus(j->bus_track) == *bus) + j->bus_track = sd_bus_track_unref(j->bus_track); + +- HASHMAP_FOREACH(u, m->units, i) ++ HASHMAP_FOREACH(u, m->units, i) { + if (u->bus_track && sd_bus_track_get_bus(u->bus_track) == *bus) + u->bus_track = sd_bus_track_unref(u->bus_track); + ++ /* Get rid of pending freezer messages on this bus */ ++ if (u->pending_freezer_message && sd_bus_message_get_bus(u->pending_freezer_message) == *bus) ++ u->pending_freezer_message = sd_bus_message_unref(u->pending_freezer_message); ++ } ++ + /* Get rid of queued message on this bus */ + if (m->pending_reload_message && sd_bus_message_get_bus(m->pending_reload_message) == *bus) + m->pending_reload_message = sd_bus_message_unref(m->pending_reload_message); +diff --git a/src/core/scope.c b/src/core/scope.c +index a1a5363244..5a595c65a6 100644 +--- a/src/core/scope.c ++++ b/src/core/scope.c +@@ -601,6 +601,9 @@ const UnitVTable scope_vtable = { + + .kill = scope_kill, + ++ .freeze = unit_freeze_vtable_common, ++ .thaw = unit_thaw_vtable_common, ++ + .get_timeout = scope_get_timeout, + + .serialize = scope_serialize, +diff --git a/src/core/service.c b/src/core/service.c +index 1d98ee37fd..89b41f6783 100644 +--- a/src/core/service.c ++++ b/src/core/service.c +@@ -4143,6 +4143,9 @@ const UnitVTable service_vtable = { + + .kill = service_kill, + ++ .freeze = unit_freeze_vtable_common, ++ .thaw = unit_thaw_vtable_common, ++ + .serialize = service_serialize, + .deserialize_item = service_deserialize_item, + +diff --git a/src/core/slice.c b/src/core/slice.c +index 58f18a4dad..b5eb2f5c01 100644 +--- a/src/core/slice.c ++++ b/src/core/slice.c +@@ -344,6 +344,82 @@ static void slice_enumerate_perpetual(Manager *m) { + (void) slice_make_perpetual(m, SPECIAL_SYSTEM_SLICE, NULL); + } + ++static bool slice_freezer_action_supported_by_children(Unit *s) { ++ Unit *member; ++ void *v; ++ Iterator i; ++ ++ assert(s); ++ ++ HASHMAP_FOREACH_KEY(v, member, s->dependencies[UNIT_BEFORE], i) { ++ int r; ++ ++ if (UNIT_DEREF(member->slice) != s) ++ continue; ++ ++ if (member->type == UNIT_SLICE) { ++ r = slice_freezer_action_supported_by_children(member); ++ if (!r) ++ return r; ++ } ++ ++ if (!UNIT_VTABLE(member)->freeze) ++ return false; ++ } ++ ++ return true; ++} ++ ++static int slice_freezer_action(Unit *s, FreezerAction action) { ++ Unit *member; ++ void *v; ++ Iterator i; ++ int r; ++ ++ assert(s); ++ assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW)); ++ ++ if (!slice_freezer_action_supported_by_children(s)) ++ return log_unit_warning(s, "Requested freezer operation is not supported by all children of the slice"); ++ ++ HASHMAP_FOREACH_KEY(v, member, s->dependencies[UNIT_BEFORE], i) { ++ if (UNIT_DEREF(member->slice) != s) ++ continue; ++ ++ if (action == FREEZER_FREEZE) ++ r = UNIT_VTABLE(member)->freeze(member); ++ else ++ r = UNIT_VTABLE(member)->thaw(member); ++ ++ if (r < 0) ++ return r; ++ } ++ ++ r = unit_cgroup_freezer_action(s, action); ++ if (r < 0) ++ return r; ++ ++ return 0; ++} ++ ++static int slice_freeze(Unit *s) { ++ assert(s); ++ ++ return slice_freezer_action(s, FREEZER_FREEZE); ++} ++ ++static int slice_thaw(Unit *s) { ++ assert(s); ++ ++ return slice_freezer_action(s, FREEZER_THAW); ++} ++ ++static bool slice_can_freeze(Unit *s) { ++ assert(s); ++ ++ return slice_freezer_action_supported_by_children(s); ++} ++ + const UnitVTable slice_vtable = { + .object_size = sizeof(Slice), + .cgroup_context_offset = offsetof(Slice, cgroup_context), +@@ -368,6 +444,10 @@ const UnitVTable slice_vtable = { + + .kill = slice_kill, + ++ .freeze = slice_freeze, ++ .thaw = slice_thaw, ++ .can_freeze = slice_can_freeze, ++ + .serialize = slice_serialize, + .deserialize_item = slice_deserialize_item, + +diff --git a/src/core/unit.c b/src/core/unit.c +index f57260727f..29ce6c1fb7 100644 +--- a/src/core/unit.c ++++ b/src/core/unit.c +@@ -583,6 +583,7 @@ void unit_free(Unit *u) { + sd_bus_slot_unref(u->match_bus_slot); + sd_bus_track_unref(u->bus_track); + u->deserialized_refs = strv_free(u->deserialized_refs); ++ u->pending_freezer_message = sd_bus_message_unref(u->pending_freezer_message); + + unit_free_requires_mounts_for(u); + +@@ -685,6 +686,38 @@ void unit_free(Unit *u) { + free(u); + } + ++FreezerState unit_freezer_state(Unit *u) { ++ assert(u); ++ ++ return u->freezer_state; ++} ++ ++int unit_freezer_state_kernel(Unit *u, FreezerState *ret) { ++ char *values[1] = {}; ++ int r; ++ ++ assert(u); ++ ++ r = cg_get_keyed_attribute(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events", ++ STRV_MAKE("frozen"), values); ++ if (r < 0) ++ return r; ++ ++ r = _FREEZER_STATE_INVALID; ++ ++ if (values[0]) { ++ if (streq(values[0], "0")) ++ r = FREEZER_RUNNING; ++ else if (streq(values[0], "1")) ++ r = FREEZER_FROZEN; ++ } ++ ++ free(values[0]); ++ *ret = r; ++ ++ return 0; ++} ++ + UnitActiveState unit_active_state(Unit *u) { + assert(u); + +@@ -1760,6 +1793,7 @@ int unit_start(Unit *u) { + * before it will start again. */ + + unit_add_to_dbus_queue(u); ++ unit_cgroup_freezer_action(u, FREEZER_THAW); + + return UNIT_VTABLE(u)->start(u); + } +@@ -1812,6 +1846,7 @@ int unit_stop(Unit *u) { + return -EBADR; + + unit_add_to_dbus_queue(u); ++ unit_cgroup_freezer_action(u, FREEZER_THAW); + + return UNIT_VTABLE(u)->stop(u); + } +@@ -1868,6 +1903,8 @@ int unit_reload(Unit *u) { + return 0; + } + ++ unit_cgroup_freezer_action(u, FREEZER_THAW); ++ + return UNIT_VTABLE(u)->reload(u); + } + +@@ -3208,6 +3245,8 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { + if (!sd_id128_is_null(u->invocation_id)) + unit_serialize_item_format(u, f, "invocation-id", SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(u->invocation_id)); + ++ (void) unit_serialize_item_format(u, f, "freezer-state", "%s", freezer_state_to_string(unit_freezer_state(u))); ++ + bus_track_serialize(u->bus_track, f, "ref"); + + for (m = 0; m < _CGROUP_IP_ACCOUNTING_METRIC_MAX; m++) { +@@ -3574,6 +3613,16 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { + log_unit_warning_errno(u, r, "Failed to set invocation ID for unit: %m"); + } + ++ continue; ++ } else if (streq(l, "freezer-state")) { ++ FreezerState s; ++ ++ s = freezer_state_from_string(v); ++ if (s < 0) ++ log_unit_debug(u, "Failed to deserialize freezer-state '%s', ignoring.", v); ++ else ++ u->freezer_state = s; ++ + continue; + } + +@@ -5507,6 +5556,80 @@ void unit_log_skip(Unit *u, const char *result) { + "UNIT_RESULT=%s", result); + } + ++bool unit_can_freeze(Unit *u) { ++ assert(u); ++ ++ if (UNIT_VTABLE(u)->can_freeze) ++ return UNIT_VTABLE(u)->can_freeze(u); ++ ++ return UNIT_VTABLE(u)->freeze; ++} ++ ++void unit_frozen(Unit *u) { ++ assert(u); ++ ++ u->freezer_state = FREEZER_FROZEN; ++ ++ bus_unit_send_pending_freezer_message(u); ++} ++ ++void unit_thawed(Unit *u) { ++ assert(u); ++ ++ u->freezer_state = FREEZER_RUNNING; ++ ++ bus_unit_send_pending_freezer_message(u); ++} ++ ++static int unit_freezer_action(Unit *u, FreezerAction action) { ++ UnitActiveState s; ++ int (*method)(Unit*); ++ int r; ++ ++ assert(u); ++ assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW)); ++ ++ method = action == FREEZER_FREEZE ? UNIT_VTABLE(u)->freeze : UNIT_VTABLE(u)->thaw; ++ if (!method || !cg_freezer_supported()) ++ return -EOPNOTSUPP; ++ ++ if (u->job) ++ return -EBUSY; ++ ++ if (u->load_state != UNIT_LOADED) ++ return -EHOSTDOWN; ++ ++ s = unit_active_state(u); ++ if (s != UNIT_ACTIVE) ++ return -EHOSTDOWN; ++ ++ if (IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_THAWING)) ++ return -EALREADY; ++ ++ r = method(u); ++ if (r <= 0) ++ return r; ++ ++ return 1; ++} ++ ++int unit_freeze(Unit *u) { ++ return unit_freezer_action(u, FREEZER_FREEZE); ++} ++ ++int unit_thaw(Unit *u) { ++ return unit_freezer_action(u, FREEZER_THAW); ++} ++ ++/* Wrappers around low-level cgroup freezer operations common for service and scope units */ ++int unit_freeze_vtable_common(Unit *u) { ++ return unit_cgroup_freezer_action(u, FREEZER_FREEZE); ++} ++ ++int unit_thaw_vtable_common(Unit *u) { ++ return unit_cgroup_freezer_action(u, FREEZER_THAW); ++} ++ + static const char* const collect_mode_table[_COLLECT_MODE_MAX] = { + [COLLECT_INACTIVE] = "inactive", + [COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed", +diff --git a/src/core/unit.h b/src/core/unit.h +index b40ff9b961..6e37fd6f5a 100644 +--- a/src/core/unit.h ++++ b/src/core/unit.h +@@ -118,6 +118,9 @@ typedef struct Unit { + UnitLoadState load_state; + Unit *merged_into; + ++ FreezerState freezer_state; ++ sd_bus_message *pending_freezer_message; ++ + char *id; /* One name is special because we use it for identification. Points to an entry in the names set */ + char *instance; + +@@ -456,6 +459,11 @@ typedef struct UnitVTable { + + int (*kill)(Unit *u, KillWho w, int signo, sd_bus_error *error); + ++ /* Freeze the unit */ ++ int (*freeze)(Unit *u); ++ int (*thaw)(Unit *u); ++ bool (*can_freeze)(Unit *u); ++ + bool (*can_reload)(Unit *u); + + /* Write all data that cannot be restored from other sources +@@ -641,6 +649,8 @@ const char *unit_description(Unit *u) _pure_; + bool unit_has_name(Unit *u, const char *name); + + UnitActiveState unit_active_state(Unit *u); ++FreezerState unit_freezer_state(Unit *u); ++int unit_freezer_state_kernel(Unit *u, FreezerState *ret); + + const char* unit_sub_state_to_string(Unit *u); + +@@ -814,6 +824,16 @@ void unit_log_failure(Unit *u, const char *result); + * after some execution, rather than succeeded or failed. */ + void unit_log_skip(Unit *u, const char *result); + ++bool unit_can_freeze(Unit *u); ++int unit_freeze(Unit *u); ++void unit_frozen(Unit *u); ++ ++int unit_thaw(Unit *u); ++void unit_thawed(Unit *u); ++ ++int unit_freeze_vtable_common(Unit *u); ++int unit_thaw_vtable_common(Unit *u); ++ + /* Macros which append UNIT= or USER_UNIT= to the message */ + + #define log_unit_full(unit, level, error, ...) \ +diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h +index 3945c7f6ac..77da78d4d4 100644 +--- a/src/libsystemd/sd-bus/bus-common-errors.h ++++ b/src/libsystemd/sd-bus/bus-common-errors.h +@@ -30,6 +30,8 @@ + #define BUS_ERROR_NO_SUCH_DYNAMIC_USER "org.freedesktop.systemd1.NoSuchDynamicUser" + #define BUS_ERROR_NOT_REFERENCED "org.freedesktop.systemd1.NotReferenced" + #define BUS_ERROR_DISK_FULL "org.freedesktop.systemd1.DiskFull" ++#define BUS_ERROR_UNIT_BUSY "org.freedesktop.systemd1.UnitBusy" ++#define BUS_ERROR_UNIT_INACTIVE "org.freedesktop.systemd1.UnitInactive" + + #define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine" + #define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage" +diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c +index e0db97e339..e963f19b0a 100644 +--- a/src/systemctl/systemctl.c ++++ b/src/systemctl/systemctl.c +@@ -27,6 +27,7 @@ + #include "bus-message.h" + #include "bus-unit-util.h" + #include "bus-util.h" ++#include "bus-wait-for-units.h" + #include "cgroup-show.h" + #include "cgroup-util.h" + #include "copy.h" +@@ -3728,6 +3729,98 @@ static int kill_unit(int argc, char *argv[], void *userdata) { + + return r; + } ++static int freeze_or_thaw_unit(int argc, char *argv[], void *userdata) { ++ _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *w = NULL; ++ _cleanup_strv_free_ char **names = NULL; ++ int r, ret = EXIT_SUCCESS; ++ char **name; ++ const char *method; ++ sd_bus *bus; ++ ++ r = acquire_bus(BUS_FULL, &bus); ++ if (r < 0) ++ return r; ++ ++ polkit_agent_open_maybe(); ++ ++ r = expand_names(bus, strv_skip(argv, 1), NULL, &names); ++ if (r < 0) ++ return log_error_errno(r, "Failed to expand names: %m"); ++ ++ if (!arg_no_block) { ++ r = bus_wait_for_units_new(bus, &w); ++ if (r < 0) ++ return log_error_errno(r, "Failed to allocate unit waiter: %m"); ++ } ++ ++ if (streq(argv[0], "freeze")) ++ method = "FreezeUnit"; ++ else if (streq(argv[0], "thaw")) ++ method = "ThawUnit"; ++ else ++ assert_not_reached("Unhandled method"); ++ ++ STRV_FOREACH(name, names) { ++ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; ++ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; ++ ++ if (w) { ++ /* If we shall wait for the cleaning to complete, let's add a ref on the unit first */ ++ r = sd_bus_call_method( ++ bus, ++ "org.freedesktop.systemd1", ++ "/org/freedesktop/systemd1", ++ "org.freedesktop.systemd1.Manager", ++ "RefUnit", ++ &error, ++ NULL, ++ "s", *name); ++ if (r < 0) { ++ log_error_errno(r, "Failed to add reference to unit %s: %s", *name, bus_error_message(&error, r)); ++ if (ret == EXIT_SUCCESS) ++ ret = r; ++ continue; ++ } ++ } ++ ++ r = sd_bus_message_new_method_call( ++ bus, ++ &m, ++ "org.freedesktop.systemd1", ++ "/org/freedesktop/systemd1", ++ "org.freedesktop.systemd1.Manager", ++ method); ++ if (r < 0) ++ return bus_log_create_error(r); ++ ++ r = sd_bus_message_append(m, "s", *name); ++ if (r < 0) ++ return bus_log_create_error(r); ++ ++ r = sd_bus_call(bus, m, 0, &error, NULL); ++ if (r < 0) { ++ log_error_errno(r, "Failed to %s unit %s: %s", argv[0], *name, bus_error_message(&error, r)); ++ if (ret == EXIT_SUCCESS) { ++ ret = r; ++ continue; ++ } ++ } ++ ++ if (w) { ++ r = bus_wait_for_units_add_unit(w, *name, BUS_WAIT_REFFED|BUS_WAIT_FOR_MAINTENANCE_END, NULL, NULL); ++ if (r < 0) ++ return log_error_errno(r, "Failed to watch unit %s: %m", *name); ++ } ++ } ++ ++ r = bus_wait_for_units_run(w); ++ if (r < 0) ++ return log_error_errno(r, "Failed to wait for units: %m"); ++ if (r == BUS_WAIT_FAILURE) ++ ret = EXIT_FAILURE; ++ ++ return ret; ++} + + typedef struct ExecStatusInfo { + char *name; +@@ -3832,6 +3925,7 @@ typedef struct UnitStatusInfo { + const char *id; + const char *load_state; + const char *active_state; ++ const char *freezer_state; + const char *sub_state; + const char *unit_file_state; + const char *unit_file_preset; +@@ -3949,7 +4043,7 @@ static void print_status_info( + bool *ellipsized) { + + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], since2[FORMAT_TIMESTAMP_MAX]; +- const char *s1, *s2, *active_on, *active_off, *on, *off, *ss; ++ const char *s1, *s2, *active_on, *active_off, *on, *off, *ss, *fs; + _cleanup_free_ char *formatted_path = NULL; + ExecStatusInfo *p; + usec_t timestamp; +@@ -4056,6 +4150,10 @@ static void print_status_info( + printf(" Active: %s%s%s", + active_on, strna(i->active_state), active_off); + ++ fs = !isempty(i->freezer_state) && !streq(i->freezer_state, "running") ? i->freezer_state : NULL; ++ if (fs) ++ printf(" %s(%s)%s", ansi_highlight_yellow(), fs, active_off); ++ + if (!isempty(i->result) && !streq(i->result, "success")) + printf(" (Result: %s)", i->result); + +@@ -4985,6 +5083,7 @@ static int show_one( + { "Id", "s", NULL, offsetof(UnitStatusInfo, id) }, + { "LoadState", "s", NULL, offsetof(UnitStatusInfo, load_state) }, + { "ActiveState", "s", NULL, offsetof(UnitStatusInfo, active_state) }, ++ { "FreezerState", "s", NULL, offsetof(UnitStatusInfo, freezer_state) }, + { "SubState", "s", NULL, offsetof(UnitStatusInfo, sub_state) }, + { "UnitFileState", "s", NULL, offsetof(UnitStatusInfo, unit_file_state) }, + { "UnitFilePreset", "s", NULL, offsetof(UnitStatusInfo, unit_file_preset) }, +@@ -7139,6 +7238,8 @@ static void systemctl_help(void) { + " if supported, otherwise restart\n" + " isolate UNIT Start one unit and stop all others\n" + " kill UNIT... Send signal to processes of a unit\n" ++ " freeze PATTERN... Freeze execution of unit processes\n" ++ " thaw PATTERN... Resume execution of a frozen unit\n" + " is-active PATTERN... Check whether units are active\n" + " is-failed PATTERN... Check whether units are failed\n" + " status [PATTERN...|PID...] Show runtime status of one or more units\n" +@@ -8280,6 +8381,8 @@ static int systemctl_main(int argc, char *argv[]) { + { "condrestart", 2, VERB_ANY, VERB_ONLINE_ONLY, start_unit }, /* For compatibility with RH */ + { "isolate", 2, 2, VERB_ONLINE_ONLY, start_unit }, + { "kill", 2, VERB_ANY, VERB_ONLINE_ONLY, kill_unit }, ++ { "freeze", 2, VERB_ANY, VERB_ONLINE_ONLY, freeze_or_thaw_unit }, ++ { "thaw", 2, VERB_ANY, VERB_ONLINE_ONLY, freeze_or_thaw_unit }, + { "is-active", 2, VERB_ANY, VERB_ONLINE_ONLY, check_unit_active }, + { "check", 2, VERB_ANY, VERB_ONLINE_ONLY, check_unit_active }, /* deprecated alias of is-active */ + { "is-failed", 2, VERB_ANY, VERB_ONLINE_ONLY, check_unit_failed }, diff --git a/SOURCES/0425-core-cgroup-fix-return-value-of-unit_cgorup_freezer_.patch b/SOURCES/0425-core-cgroup-fix-return-value-of-unit_cgorup_freezer_.patch new file mode 100644 index 0000000..f78f7a7 --- /dev/null +++ b/SOURCES/0425-core-cgroup-fix-return-value-of-unit_cgorup_freezer_.patch @@ -0,0 +1,31 @@ +From abf2fb67dc3d7da8db030ea8b8db73a20acc08a9 Mon Sep 17 00:00:00 2001 +From: Michal Sekletar +Date: Thu, 7 May 2020 17:23:30 +0200 +Subject: [PATCH] core/cgroup: fix return value of unit_cgorup_freezer_action() + +We should return 0 only if current freezer state, as reported by the +kernel, is already the desired state. Otherwise, we would dispatch +return dbus message prematurely in bus_unit_method_freezer_generic(). + +Thanks to Frantisek Sumsal for reporting the issue. + +(cherry picked from commit d910f4c2b2542544d7b187a09605da7a0f220837) + +Related: #1830861 +--- + src/core/cgroup.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index e7ae9273a6..2d819b8ebb 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -2963,7 +2963,7 @@ int unit_cgroup_freezer_action(Unit *u, FreezerAction action) { + if (r < 0) + return r; + +- return 0; ++ return 1; + } + + static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = { diff --git a/SOURCES/0426-core-fix-the-return-value-in-order-to-make-sure-we-d.patch b/SOURCES/0426-core-fix-the-return-value-in-order-to-make-sure-we-d.patch new file mode 100644 index 0000000..94cccdd --- /dev/null +++ b/SOURCES/0426-core-fix-the-return-value-in-order-to-make-sure-we-d.patch @@ -0,0 +1,31 @@ +From 4da9dfaec0d7d232d8bfed0d7f65afd65369bc8c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= +Date: Fri, 5 Jun 2020 15:23:12 +0200 +Subject: [PATCH] core: fix the return value in order to make sure we don't + dipatch method return too early + +Actually, it is the same kind of problem as in d910f4c . Basically, we +need to return 1 on success code path in slice_freezer_action(). +Otherwise we dispatch DBus return message too soon. + +Fixes: #16050 +(cherry picked from commit 2884836e3c26fa76718319cdc6d13136bbc1354d) + +Related: #1830861 +--- + src/core/slice.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/core/slice.c b/src/core/slice.c +index b5eb2f5c01..c10e830917 100644 +--- a/src/core/slice.c ++++ b/src/core/slice.c +@@ -399,7 +399,7 @@ static int slice_freezer_action(Unit *s, FreezerAction action) { + if (r < 0) + return r; + +- return 0; ++ return 1; + } + + static int slice_freeze(Unit *s) { diff --git a/SOURCES/0427-test-add-test-for-cgroup-v2-freezer-support.patch b/SOURCES/0427-test-add-test-for-cgroup-v2-freezer-support.patch new file mode 100644 index 0000000..4d68414 --- /dev/null +++ b/SOURCES/0427-test-add-test-for-cgroup-v2-freezer-support.patch @@ -0,0 +1,397 @@ +From 762959047c1e1a5d4b6a6fe85cfa8a14c622e4da Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= +Date: Fri, 3 Apr 2020 09:13:51 +0200 +Subject: [PATCH] test: add test for cgroup v2 freezer support + +(cherry picked from commit d446ae89c0168f17eed7135ac06df3b294b3fcc6) + +Related: #1830861 +--- + ci/travis-centos-rhel8.sh | 2 +- + test/TEST-38-FREEZER/Makefile | 1 + + test/TEST-38-FREEZER/test.sh | 53 ++++++ + test/TEST-38-FREEZER/testsuite.sh | 293 ++++++++++++++++++++++++++++++ + 4 files changed, 348 insertions(+), 1 deletion(-) + create mode 120000 test/TEST-38-FREEZER/Makefile + create mode 100755 test/TEST-38-FREEZER/test.sh + create mode 100755 test/TEST-38-FREEZER/testsuite.sh + +diff --git a/ci/travis-centos-rhel8.sh b/ci/travis-centos-rhel8.sh +index a1502e15ee..cd0857fd29 100755 +--- a/ci/travis-centos-rhel8.sh ++++ b/ci/travis-centos-rhel8.sh +@@ -15,7 +15,7 @@ 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=(libasan libubsan net-tools strace nc e2fsprogs quota dnsmasq) ++ADDITIONAL_DEPS=(libasan libubsan net-tools strace nc e2fsprogs quota dnsmasq diffutils) + # RHEL8 options + CONFIGURE_OPTS=( + -Dsysvinit-path=/etc/rc.d/init.d +diff --git a/test/TEST-38-FREEZER/Makefile b/test/TEST-38-FREEZER/Makefile +new file mode 120000 +index 0000000000..e9f93b1104 +--- /dev/null ++++ b/test/TEST-38-FREEZER/Makefile +@@ -0,0 +1 @@ ++../TEST-01-BASIC/Makefile +\ No newline at end of file +diff --git a/test/TEST-38-FREEZER/test.sh b/test/TEST-38-FREEZER/test.sh +new file mode 100755 +index 0000000000..0d00754b5c +--- /dev/null ++++ b/test/TEST-38-FREEZER/test.sh +@@ -0,0 +1,53 @@ ++#!/bin/bash ++set -e ++TEST_DESCRIPTION="test unit freezing and thawing via DBus and systemctl" ++TEST_NO_NSPAWN=1 ++. $TEST_BASE_DIR/test-functions ++ ++test_setup() { ++ create_empty_image ++ mkdir -p $TESTDIR/root ++ mount ${LOOPDEV}p1 $TESTDIR/root ++ ++ ( ++ LOG_LEVEL=5 ++ eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) ++ ++ setup_basic_environment ++ dracut_install tr cut timeout ++ ++ # mask some services that we do not want to run in these tests ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.service ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.socket ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-resolved.service ++ ln -fs /dev/null $initdir/etc/systemd/system/systemd-machined.service ++ ++ # setup the testsuite service ++ cat >$initdir/etc/systemd/system/testsuite.service < $initdir/etc/systemd/system/testsuite-38-sleep.service << EOF ++[Service] ++ExecStart=/bin/sleep 3600 ++EOF ++ ++ cp testsuite.sh $initdir/ ++ setup_testsuite ++ ) ++ setup_nspawn_root ++ ++ ddebug "umount $TESTDIR/root" ++ umount $TESTDIR/root ++} ++ ++ ++do_test "$@" +diff --git a/test/TEST-38-FREEZER/testsuite.sh b/test/TEST-38-FREEZER/testsuite.sh +new file mode 100755 +index 0000000000..6fcadb8f8e +--- /dev/null ++++ b/test/TEST-38-FREEZER/testsuite.sh +@@ -0,0 +1,293 @@ ++#!/usr/bin/env bash ++ ++set -ex ++set -o pipefail ++ ++systemd-analyze log-level debug ++systemd-analyze log-target console ++ ++unit=testsuite-38-sleep.service ++ ++start_test_service() { ++ systemctl daemon-reload ++ systemctl start "${unit}" ++} ++ ++dbus_freeze() { ++ local suffix= ++ suffix="${1##*.}" ++ ++ local name="$(echo ${1%.$suffix} | sed s/-/_2d/g)" ++ local object_path="/org/freedesktop/systemd1/unit/${name}_2e${suffix}" ++ ++ busctl call \ ++ org.freedesktop.systemd1 \ ++ "${object_path}" \ ++ org.freedesktop.systemd1.Unit \ ++ Freeze ++} ++ ++dbus_thaw() { ++ local suffix= ++ suffix="${1##*.}" ++ ++ local name="$(echo ${1%.$suffix} | sed s/-/_2d/g)" ++ local object_path="/org/freedesktop/systemd1/unit/${name}_2e${suffix}" ++ ++ busctl call \ ++ org.freedesktop.systemd1 \ ++ "${object_path}" \ ++ org.freedesktop.systemd1.Unit \ ++ Thaw ++} ++ ++dbus_freeze_unit() { ++ busctl call \ ++ org.freedesktop.systemd1 \ ++ /org/freedesktop/systemd1 \ ++ org.freedesktop.systemd1.Manager \ ++ FreezeUnit \ ++ s \ ++ "$1" ++} ++ ++dbus_thaw_unit() { ++ busctl call \ ++ org.freedesktop.systemd1 \ ++ /org/freedesktop/systemd1 \ ++ org.freedesktop.systemd1.Manager \ ++ ThawUnit \ ++ s \ ++ "$1" ++} ++ ++dbus_can_freeze() { ++ local suffix= ++ suffix="${1##*.}" ++ ++ local name="$(echo ${1%.$suffix} | sed s/-/_2d/g)" ++ local object_path="/org/freedesktop/systemd1/unit/${name}_2e${suffix}" ++ ++ busctl get-property \ ++ org.freedesktop.systemd1 \ ++ "${object_path}" \ ++ org.freedesktop.systemd1.Unit \ ++ CanFreeze ++} ++ ++check_freezer_state() { ++ local suffix= ++ suffix="${1##*.}" ++ ++ local name="$(echo ${1%.$suffix} | sed s/-/_2d/g)" ++ local object_path="/org/freedesktop/systemd1/unit/${name}_2e${suffix}" ++ ++ state=$(busctl get-property \ ++ org.freedesktop.systemd1 \ ++ "${object_path}" \ ++ org.freedesktop.systemd1.Unit \ ++ FreezerState | cut -d " " -f2 | tr -d '"') ++ ++ [ "$state" = "$2" ] || { ++ echo "error: unexpected freezer state, expected: $2, actual: $state" >&2 ++ exit 1 ++ } ++} ++ ++check_cgroup_state() { ++ grep -q "frozen $2" /sys/fs/cgroup/system.slice/"$1"/cgroup.events ++} ++ ++test_dbus_api() { ++ echo "Test that DBus API works:" ++ echo -n " - Freeze(): " ++ dbus_freeze "${unit}" ++ check_freezer_state "${unit}" "frozen" ++ check_cgroup_state "$unit" 1 ++ echo "[ OK ]" ++ ++ echo -n " - Thaw(): " ++ dbus_thaw "${unit}" ++ check_freezer_state "${unit}" "running" ++ check_cgroup_state "$unit" 0 ++ echo "[ OK ]" ++ ++ echo -n " - FreezeUnit(): " ++ dbus_freeze_unit "${unit}" ++ check_freezer_state "${unit}" "frozen" ++ check_cgroup_state "$unit" 1 ++ echo "[ OK ]" ++ ++ echo -n " - ThawUnit(): " ++ dbus_thaw_unit "${unit}" ++ check_freezer_state "${unit}" "running" ++ check_cgroup_state "$unit" 0 ++ echo "[ OK ]" ++ ++ echo -n " - CanFreeze(): " ++ output=$(dbus_can_freeze "${unit}") ++ [ "$output" = "b true" ] ++ echo "[ OK ]" ++ ++ echo ++} ++ ++test_jobs() { ++ local pid_before= ++ local pid_after= ++ echo "Test that it is possible to apply jobs on frozen units:" ++ ++ systemctl start "${unit}" ++ dbus_freeze "${unit}" ++ check_freezer_state "${unit}" "frozen" ++ ++ echo -n " - restart: " ++ pid_before=$(systemctl show -p MainPID "${unit}" --value) ++ systemctl restart "${unit}" ++ pid_after=$(systemctl show -p MainPID "${unit}" --value) ++ [ "$pid_before" != "$pid_after" ] && echo "[ OK ]" ++ ++ dbus_freeze "${unit}" ++ check_freezer_state "${unit}" "frozen" ++ ++ echo -n " - stop: " ++ timeout 5s systemctl stop "${unit}" ++ echo "[ OK ]" ++ ++ echo ++} ++ ++test_systemctl() { ++ echo "Test that systemctl freeze/thaw verbs:" ++ ++ systemctl start "$unit" ++ ++ echo -n " - freeze: " ++ systemctl freeze "$unit" ++ check_freezer_state "${unit}" "frozen" ++ check_cgroup_state "$unit" 1 ++ # Freezing already frozen unit should be NOP and return quickly ++ timeout 3s systemctl freeze "$unit" ++ echo "[ OK ]" ++ ++ echo -n " - thaw: " ++ systemctl thaw "$unit" ++ check_freezer_state "${unit}" "running" ++ check_cgroup_state "$unit" 0 ++ # Likewise thawing already running unit shouldn't block ++ timeout 3s systemctl thaw "$unit" ++ echo "[ OK ]" ++ ++ systemctl stop "$unit" ++ ++ echo ++} ++ ++test_systemctl_show() { ++ echo "Test systemctl show integration:" ++ ++ systemctl start "$unit" ++ ++ echo -n " - FreezerState property: " ++ state=$(systemctl show -p FreezerState --value "$unit") ++ [ "$state" = "running" ] ++ systemctl freeze "$unit" ++ state=$(systemctl show -p FreezerState --value "$unit") ++ [ "$state" = "frozen" ] ++ systemctl thaw "$unit" ++ echo "[ OK ]" ++ ++ echo -n " - CanFreeze property: " ++ state=$(systemctl show -p CanFreeze --value "$unit") ++ [ "$state" = "yes" ] ++ echo "[ OK ]" ++ ++ systemctl stop "$unit" ++ echo ++} ++ ++test_recursive() { ++ local slice="bar.slice" ++ local unit="baz.service" ++ ++ systemd-run --unit "$unit" --slice "$slice" sleep 3600 >/dev/null 2>&1 ++ ++ echo "Test recursive freezing:" ++ ++ echo -n " - freeze: " ++ systemctl freeze "$slice" ++ check_freezer_state "${slice}" "frozen" ++ check_freezer_state "${unit}" "frozen" ++ grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/cgroup.events ++ grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events ++ echo "[ OK ]" ++ ++ echo -n " - thaw: " ++ systemctl thaw "$slice" ++ check_freezer_state "${unit}" "running" ++ check_freezer_state "${slice}" "running" ++ grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/cgroup.events ++ grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events ++ echo "[ OK ]" ++ ++ systemctl stop "$unit" ++ systemctl stop "$slice" ++ ++ echo ++} ++ ++test_preserve_state() { ++ local slice="bar.slice" ++ local unit="baz.service" ++ ++ systemd-run --unit "$unit" --slice "$slice" sleep 3600 >/dev/null 2>&1 ++ ++ echo "Test that freezer state is preserved when recursive freezing is initiated from outside (e.g. by manager up the tree):" ++ ++ echo -n " - freeze from outside: " ++ echo 1 > /sys/fs/cgroup/"${slice}"/cgroup.freeze ++ ++ # Our state should not be affected ++ check_freezer_state "${slice}" "running" ++ check_freezer_state "${unit}" "running" ++ ++ # However actual kernel state should be frozen ++ grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/cgroup.events ++ grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events ++ echo "[ OK ]" ++ ++ echo -n " - thaw from outside: " ++ echo 0 > /sys/fs/cgroup/"${slice}"/cgroup.freeze ++ check_freezer_state "${unit}" "running" ++ check_freezer_state "${slice}" "running" ++ grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/cgroup.events ++ grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events ++ echo "[ OK ]" ++ ++ echo -n " - thaw from outside while inner service is frozen: " ++ systemctl freeze "$unit" ++ check_freezer_state "${unit}" "frozen" ++ echo 1 > /sys/fs/cgroup/"${slice}"/cgroup.freeze ++ echo 0 > /sys/fs/cgroup/"${slice}"/cgroup.freeze ++ check_freezer_state "${slice}" "running" ++ check_freezer_state "${unit}" "frozen" ++ echo "[ OK ]" ++ ++ systemctl stop "$unit" ++ systemctl stop "$slice" ++ ++ echo ++} ++ ++test -e /sys/fs/cgroup/system.slice/cgroup.freeze && { ++ start_test_service ++ test_dbus_api ++ test_systemctl ++ test_systemctl_show ++ test_jobs ++ test_recursive ++ test_preserve_state ++} ++ ++echo OK > /testok ++exit 0 diff --git a/SOURCES/0428-fix-mis-merge.patch b/SOURCES/0428-fix-mis-merge.patch new file mode 100644 index 0000000..544299a --- /dev/null +++ b/SOURCES/0428-fix-mis-merge.patch @@ -0,0 +1,29 @@ +From 8e1cc941607263a4f8454d0d6d5939aec3be1fcb Mon Sep 17 00:00:00 2001 +From: David Tardon +Date: Tue, 23 Jun 2020 13:58:21 +0200 +Subject: [PATCH] fix mis-merge + +Resolves: #1848421 +--- + src/core/cgroup.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index 2d819b8ebb..e0eb184fd2 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -664,11 +664,13 @@ static void cgroup_apply_legacy_cpu_config(Unit *u, uint64_t shares, uint64_t qu + + if (quota != USEC_INFINITY) { + xsprintf(buf, USEC_FMT "\n", MAX(quota * period / USEC_PER_SEC, USEC_PER_MSEC)); ++ r = cg_set_attribute("cpu", u->cgroup_path, "cpu.cfs_quota_us", buf); ++ } ++ else + r = cg_set_attribute("cpu", u->cgroup_path, "cpu.cfs_quota_us", "-1"); + if (r < 0) + log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.cfs_quota_us: %m"); +- } + } + + static uint64_t cgroup_cpu_shares_to_weight(uint64_t shares) { diff --git a/SOURCES/0429-tests-sleep-a-bit-and-give-kernel-time-to-perform-th.patch b/SOURCES/0429-tests-sleep-a-bit-and-give-kernel-time-to-perform-th.patch new file mode 100644 index 0000000..82805da --- /dev/null +++ b/SOURCES/0429-tests-sleep-a-bit-and-give-kernel-time-to-perform-th.patch @@ -0,0 +1,36 @@ +From 9028873ee3bffe37b50fa7ada123fcf270de4658 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Michal=20Sekleta=CC=81r?= +Date: Fri, 5 Jun 2020 11:35:01 +0200 +Subject: [PATCH] tests: sleep a bit and give kernel time to perform the action + after manual freeze/thaw + +Fixes: #16050 +(cherry picked from commit a0d79df8e59c6bb6dc0382d71e835dec869a7df4) + +Related: #1848421 +--- + test/TEST-38-FREEZER/testsuite.sh | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/test/TEST-38-FREEZER/testsuite.sh b/test/TEST-38-FREEZER/testsuite.sh +index 6fcadb8f8e..18b7bd6dce 100755 +--- a/test/TEST-38-FREEZER/testsuite.sh ++++ b/test/TEST-38-FREEZER/testsuite.sh +@@ -246,6 +246,8 @@ test_preserve_state() { + + echo -n " - freeze from outside: " + echo 1 > /sys/fs/cgroup/"${slice}"/cgroup.freeze ++ # Give kernel some time to freeze the slice ++ sleep 1 + + # Our state should not be affected + check_freezer_state "${slice}" "running" +@@ -258,6 +260,8 @@ test_preserve_state() { + + echo -n " - thaw from outside: " + echo 0 > /sys/fs/cgroup/"${slice}"/cgroup.freeze ++ sleep 1 ++ + check_freezer_state "${unit}" "running" + check_freezer_state "${slice}" "running" + grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/cgroup.events diff --git a/SPECS/systemd.spec b/SPECS/systemd.spec index 1c03e7a..8faead7 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: 29%{?dist} +Release: 36%{?dist} # For a breakdown of the licensing, see README License: LGPLv2+ and MIT and GPLv2+ Summary: System and Service Manager @@ -392,6 +392,93 @@ Patch0339: 0339-test-add-a-simple-sanity-check-for-systems-without-N.patch Patch0340: 0340-test-drop-the-missed-exit-1-expression.patch Patch0341: 0341-test-replace-cursor-file-with-a-plain-cursor.patch Patch0342: 0342-cryptsetup-Treat-key-file-errors-as-a-failed-passwor.patch +Patch0343: 0343-swap-finish-the-secondary-swap-units-jobs-if-deactiv.patch +Patch0344: 0344-resolved-Recover-missing-PrivateTmp-yes-and-ProtectS.patch +Patch0345: 0345-bus_open-leak-sd_event_source-when-udevadm-trigger.patch +Patch0346: 0346-core-rework-StopWhenUnneeded-logic.patch +Patch0347: 0347-pid1-fix-the-names-of-AllowedCPUs-and-AllowedMemoryN.patch +Patch0348: 0348-core-fix-re-realization-of-cgroup-siblings.patch +Patch0349: 0349-basic-use-comma-as-separator-in-cpuset-cgroup-cpu-ra.patch +Patch0350: 0350-core-transition-to-FINAL_SIGTERM-state-after-ExecSto.patch +Patch0351: 0351-sd-journal-close-journal-files-that-were-deleted-by-.patch +Patch0352: 0352-sd-journal-remove-the-dead-code-and-actually-fix-146.patch +Patch0353: 0353-udev-downgrade-message-when-we-fail-to-set-inotify-w.patch +Patch0354: 0354-logind-check-PolicyKit-before-allowing-VT-switch.patch +Patch0355: 0355-test-do-not-use-global-variable-to-pass-error.patch +Patch0356: 0356-test-install-libraries-required-by-tests.patch +Patch0357: 0357-test-introduce-install_zoneinfo.patch +Patch0358: 0358-test-replace-duplicated-Makefile-by-symbolic-link.patch +Patch0359: 0359-test-add-paths-of-keymaps-in-install_keymaps.patch +Patch0360: 0360-test-make-install_keymaps-optionally-install-more-ke.patch +Patch0361: 0361-test-fs-util-skip-some-tests-when-running-in-unprivi.patch +Patch0362: 0362-test-process-util-skip-several-verifications-when-ru.patch +Patch0363: 0363-test-execute-also-check-python3-is-installed-or-not.patch +Patch0364: 0364-test-execute-skip-several-tests-when-running-in-cont.patch +Patch0365: 0365-test-introduce-test_is_running_from_builddir.patch +Patch0366: 0366-test-make-test-catalog-relocatable.patch +Patch0367: 0367-test-parallelize-tasks-in-TEST-24-UNIT-TESTS.patch +Patch0368: 0368-test-try-to-determine-QEMU_SMP-dynamically.patch +Patch0369: 0369-test-store-coredumps-in-journal.patch +Patch0370: 0370-pid1-add-new-kernel-cmdline-arg-systemd.cpu_affinity.patch +Patch0371: 0371-udev-rules-make-tape-changers-also-apprear-in-dev-ta.patch +Patch0372: 0372-man-be-clearer-that-.timer-time-expressions-need-to-.patch +Patch0373: 0373-Add-support-for-opening-files-for-appending.patch +Patch0374: 0374-nspawn-move-payload-to-sub-cgroup-first-then-sync-cg.patch +Patch0375: 0375-nspawn-chown-the-legacy-hierarchy-when-it-s-used-in-.patch +Patch0376: 0376-core-move-unit_status_emit_starting_stopping_reloadi.patch +Patch0377: 0377-job-when-a-job-was-skipped-due-to-a-failed-condition.patch +Patch0378: 0378-core-split-out-all-logic-that-updates-a-Job-on-a-uni.patch +Patch0379: 0379-core-make-log-messages-about-units-entering-a-failed.patch +Patch0380: 0380-core-log-a-recognizable-message-when-a-unit-succeeds.patch +Patch0381: 0381-tests-always-use-the-right-vtable-wrapper-calls.patch +Patch0382: 0382-test-execute-allow-filtering-test-cases-by-pattern.patch +Patch0383: 0383-test-execute-provide-custom-failure-message.patch +Patch0384: 0384-core-ExecCondition-for-services.patch +Patch0385: 0385-Drop-support-for-lz4-1.3.0.patch +Patch0386: 0386-test-compress-add-test-for-short-decompress_startswi.patch +Patch0387: 0387-journal-adapt-for-new-improved-LZ4_decompress_safe_p.patch +Patch0388: 0388-fuzz-compress-add-fuzzer-for-compression-and-decompr.patch +Patch0389: 0389-seccomp-fix-__NR__sysctl-usage.patch +Patch0390: 0390-tmpfiles-fix-crash-with-NULL-in-arg_root-and-other-f.patch +Patch0391: 0391-sulogin-shell-Use-force-if-SYSTEMD_SULOGIN_FORCE-set.patch +Patch0392: 0392-resolvconf-fixes-for-the-compatibility-interface.patch +Patch0393: 0393-mount-don-t-add-Requires-for-tmp.mount.patch +Patch0394: 0394-core-coldplug-possible-nop_job.patch +Patch0395: 0395-core-add-IODeviceLatencyTargetSec.patch +Patch0396: 0396-time-util-Introduce-parse_sec_def_infinity.patch +Patch0397: 0397-cgroup-use-structured-initialization.patch +Patch0398: 0398-core-add-CPUQuotaPeriodSec.patch +Patch0399: 0399-core-downgrade-CPUQuotaPeriodSec-clamping-logs-to-de.patch +Patch0400: 0400-sd-bus-avoid-magic-number-in-SASL-length-calculation.patch +Patch0401: 0401-sd-bus-fix-SASL-reply-to-empty-AUTH.patch +Patch0402: 0402-sd-bus-skip-sending-formatted-UIDs-via-SASL.patch +Patch0403: 0403-core-add-MemoryMin.patch +Patch0404: 0404-core-introduce-cgroup_add_device_allow.patch +Patch0405: 0405-test-remove-support-for-suffix-in-get_testdata_dir.patch +Patch0406: 0406-cgroup-Implement-default-propagation-of-MemoryLow-wi.patch +Patch0407: 0407-cgroup-Create-UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP.patch +Patch0408: 0408-unit-Add-DefaultMemoryMin.patch +Patch0409: 0409-cgroup-Polish-hierarchically-aware-protection-docs-a.patch +Patch0410: 0410-cgroup-Readd-some-plumbing-for-DefaultMemoryMin.patch +Patch0411: 0411-cgroup-Support-0-value-for-memory-protection-directi.patch +Patch0412: 0412-cgroup-Test-that-it-s-possible-to-set-memory-protect.patch +Patch0413: 0413-cgroup-Check-ancestor-memory-min-for-unified-memory-.patch +Patch0414: 0414-cgroup-Respect-DefaultMemoryMin-when-setting-memory..patch +Patch0415: 0415-cgroup-Mark-memory-protections-as-explicitly-set-in-.patch +Patch0416: 0416-meson-allow-setting-the-version-string-during-config.patch +Patch0417: 0417-core-don-t-consider-SERVICE_SKIP_CONDITION-for-abnor.patch +Patch0418: 0418-selinux-do-preprocessor-check-only-in-selinux-access.patch +Patch0419: 0419-basic-cgroup-util-introduce-cg_get_keyed_attribute_f.patch +Patch0420: 0420-shared-add-generic-logic-for-waiting-for-a-unit-to-e.patch +Patch0421: 0421-shared-fix-assert-call.patch +Patch0422: 0422-shared-Don-t-try-calling-NULL-callback-in-bus_wait_f.patch +Patch0423: 0423-shared-add-NULL-callback-check-in-one-more-place.patch +Patch0424: 0424-core-introduce-support-for-cgroup-freezer.patch +Patch0425: 0425-core-cgroup-fix-return-value-of-unit_cgorup_freezer_.patch +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 %ifarch %{ix86} x86_64 aarch64 @@ -652,6 +739,7 @@ CONFIGURE_OPTS=( -Dnetworkd=false -Dtimesyncd=false -Ddefault-hierarchy=legacy + -Dversion-tag=%{version}-%{release} ) # Don't ship /var/log/README. The relationship between journal and syslog should be documented @@ -776,16 +864,16 @@ python3 %{SOURCE2} %buildroot < - 239-29 -- cryptsetup: Treat key file errors as a failed password attempt (#1763155) - -* Wed Mar 11 2020 systemd maintenance team - 239-28 +* Fri Jun 26 2020 systemd maintenance team - 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) +- basic/cgroup-util: introduce cg_get_keyed_attribute_full() (#1830861) +- shared: add generic logic for waiting for a unit to enter some state (#1830861) +- shared: fix assert call (#1830861) +- shared: Don't try calling NULL callback in bus_wait_for_units_clear (#1830861) +- shared: add NULL callback check in one more place (#1830861) +- core: introduce support for cgroup freezer (#1830861) +- core/cgroup: fix return value of unit_cgorup_freezer_action() (#1830861) +- core: fix the return value in order to make sure we don't dipatch method return too early (#1830861) +- test: add test for cgroup v2 freezer support (#1830861) +- fix mis-merge (#1848421) +- tests: sleep a bit and give kernel time to perform the action after manual freeze/thaw (#1848421) + +* Fri Jun 26 2020 systemd maintenance team - 239-35 +- spec: fix rpm verification (#1702300) + +* Thu Jun 18 2020 systemd maintenance team - 239-34 +- spec: fix rpm verification (#1702300) + +* Tue Jun 09 2020 systemd maintenance team - 239-33 +- tmpfiles: fix crash with NULL in arg_root and other fixes and tests (#1836024) +- sulogin-shell: Use force if SYSTEMD_SULOGIN_FORCE set (#1625929) +- resolvconf: fixes for the compatibility interface (#1835594) +- mount: don't add Requires for tmp.mount (#1748840) +- core: coldplug possible nop_job (#1829798) +- core: add IODeviceLatencyTargetSec (#1831519) +- time-util: Introduce parse_sec_def_infinity (#1770379) +- cgroup: use structured initialization (#1770379) +- core: add CPUQuotaPeriodSec= (#1770379) +- core: downgrade CPUQuotaPeriodSec= clamping logs to debug (#1770379) +- sd-bus: avoid magic number in SASL length calculation (#1838081) +- sd-bus: fix SASL reply to empty AUTH (#1838081) +- sd-bus: skip sending formatted UIDs via SASL (#1838081) +- core: add MemoryMin (#1763435) +- core: introduce cgroup_add_device_allow() (#1763435) +- test: remove support for suffix in get_testdata_dir() (#1763435) +- cgroup: Implement default propagation of MemoryLow with DefaultMemoryLow (#1763435) +- cgroup: Create UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP (#1763435) +- unit: Add DefaultMemoryMin (#1763435) +- cgroup: Polish hierarchically aware protection docs a bit (#1763435) +- cgroup: Readd some plumbing for DefaultMemoryMin (#1763435) +- cgroup: Support 0-value for memory protection directives (#1763435) +- cgroup: Test that it's possible to set memory protection to 0 again (#1763435) +- cgroup: Check ancestor memory min for unified memory config (#1763435) +- cgroup: Respect DefaultMemoryMin when setting memory.min (#1763435) +- cgroup: Mark memory protections as explicitly set in transient units (#1763435) +- meson: allow setting the version string during configuration (#1804252) + +* Thu Jun 04 2020 systemd maintenance team - 239-32 - pid1: fix DefaultTasksMax initialization (#1809037) -- cgroup: make sure that cpuset is supported on cgroup v2 and disabled with v1 (#1808940) +- cgroup: make sure that cpuset is supported on cgroup v2 and disabled with v1 (#1808940) - test: introduce TEST-36-NUMAPOLICY (#1808940) -- test: replace `tail -f` with journal cursor which should be more reliable (#1808940) -- test: support MPOL_LOCAL matching in unpatched strace versions (#1808940) +- test: replace `tail -f` with journal cursor which should be... (#1808940) +- test: support MPOL_LOCAL matching in unpatched strace versions (#1808940) - test: make sure the strace process is indeed dead (#1808940) - test: skip the test on systems without NUMA support (#1808940) - test: give strace some time to initialize (#1808940) -- test: add a simple sanity check for systems without NUMA support (#1808940) +- test: add a simple sanity check for systems without NUMA support (#1808940) - test: drop the missed || exit 1 expression (#1808940) - test: replace cursor file with a plain cursor (#1808940) +- cryptsetup: Treat key file errors as a failed password attempt (#1763155) +- swap: finish the secondary swap units' jobs if deactivation of the primary swap unit fails (#1749622) +- resolved: Recover missing PrivateTmp=yes and ProtectSystem=strict (#1810869) +- bus_open leak sd_event_source when udevadm trigger。 (#1798504) +- core: rework StopWhenUnneeded= logic (#1798046) +- pid1: fix the names of AllowedCPUs= and AllowedMemoryNodes= (#1818054) +- core: fix re-realization of cgroup siblings (#1818054) +- basic: use comma as separator in cpuset cgroup cpu ranges (#1818054) +- core: transition to FINAL_SIGTERM state after ExecStopPost= (#1766479) +- sd-journal: close journal files that were deleted by journald before we've setup inotify watch (#1796128) +- sd-journal: remove the dead code and actually fix #14695 (#1796128) +- udev: downgrade message when we fail to set inotify watch up (#1808051) +- logind: check PolicyKit before allowing VT switch (#1797679) +- test: do not use global variable to pass error (#1823767) +- test: install libraries required by tests (#1823767) +- test: introduce install_zoneinfo() (#1823767) +- test: replace duplicated Makefile by symbolic link (#1823767) +- test: add paths of keymaps in install_keymaps() (#1823767) +- test: make install_keymaps() optionally install more keymaps (#1823767) +- test-fs-util: skip some tests when running in unprivileged container (#1823767) +- test-process-util: skip several verifications when running in unprivileged container (#1823767) +- test-execute: also check python3 is installed or not (#1823767) +- test-execute: skip several tests when running in container (#1823767) +- test: introduce test_is_running_from_builddir() (#1823767) +- test: make test-catalog relocatable (#1823767) +- test: parallelize tasks in TEST-24-UNIT-TESTS (#1823767) +- test: try to determine QEMU_SMP dynamically (#1823767) +- test: store coredumps in journal (#1823767) +- pid1: add new kernel cmdline arg systemd.cpu_affinity= (#1812894) +- udev-rules: make tape-changers also apprear in /dev/tape/by-path/ (#1820112) +- man: be clearer that .timer time expressions need to be reset to override them (#1816908) +- Add support for opening files for appending (#1809175) +- nspawn: move payload to sub-cgroup first, then sync cgroup trees (#1837094) +- core: move unit_status_emit_starting_stopping_reloading() and related calls to job.c (#1737283) +- job: when a job was skipped due to a failed condition, log about it (#1737283) +- core: split out all logic that updates a Job on a unit's unit_notify() invocation (#1737283) +- core: make log messages about units entering a 'failed' state recognizable (#1737283) +- core: log a recognizable message when a unit succeeds, too (#1737283) +- tests: always use the right vtable wrapper calls (#1737283) +- test-execute: allow filtering test cases by pattern (#1737283) +- test-execute: provide custom failure message (#1737283) +- core: ExecCondition= for services (#1737283) +- Drop support for lz4 < 1.3.0 (#1843871) +- test-compress: add test for short decompress_startswith calls (#1843871) +- journal: adapt for new improved LZ4_decompress_safe_partial() (#1843871) +- fuzz-compress: add fuzzer for compression and decompression (#1843871) +- seccomp: fix __NR__sysctl usage (#1843871) * Fri Feb 21 2020 systemd maintenance team - 239-27 - cgroup: introduce support for cgroup v2 CPUSET controller (#1724617)