]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
rpm: restart user services at the end of the transaction
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 7 Jul 2021 12:37:57 +0000 (14:37 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Sat, 24 Jul 2021 09:53:31 +0000 (11:53 +0200)
This closes an important gap: so far we would reexecute the system manager and
restart system services that were configured to do so, but we wouldn't do the
same for user managers or user services.

The scheme used for user managers is very similar to the system one, except
that there can be multiple user managers running, so we query the system
manager to get a list of them, and then tell each one to do the equivalent
operations: daemon-reload, disable --now, set-property Markers=+needs-restart,
reload-or-restart --marked.

The total time that can be spend on this is bounded: we execute the commands in
parallel over user managers and units, and additionally set SYSTEMD_BUS_TIMEOUT
to a lower value (15 s by default). User managers should not have too many
units running, and they should be able to do all those operations very
quickly (<< 1s). The final restart operation may take longer, but it's done
asynchronously, so we only wait for the queuing to happen.

The advantage of doing this synchronously is that we can wait for each step to
happen, and for example daemon-reloads can finish before we execute the service
restarts, etc. We can also order various steps wrt. to the phases in the rpm
transaction.

When this was initially proposed, we discussed a more relaxed scheme with bus
property notifications. Such an approach would be more complex because a bunch
of infrastructure would have to be added to system manager to propagate
appropriate notifications to the user managers, and then the user managers
would have to wait for them. Instead, now there is no new code in the managers,
all new functionality is contained in src/rpm/. The ability to call 'systemctl
--user user@' makes this approach very easy. Also, it would be very hard to
order the user manager steps and the rpm transaction steps.

Note: 'systemctl --user disable' is only called for a user managers that are
running. I don't see a nice way around this, and it shouldn't matter too much:
we'll just leave a dangling symlink in the case where the user enabled the
service manually.

A follow-up for https://bugzilla.redhat.com/show_bug.cgi?id=1792468 and
fa97d2fcf64e0558054bee673f734f523373b146.

meson.build
meson_options.txt
src/rpm/macros.systemd.in
src/rpm/systemd-update-helper.in
src/rpm/triggers.systemd.in
src/rpm/triggers.systemd.sh.in

index c6b3e72d237fc7c7ba13dd85b4afd91200662af9..cafce977c2978891367e037221fbc0bce252dbb2 100644 (file)
@@ -270,6 +270,7 @@ conf.set_quoted('TMPFILES_DIR',                               tmpfilesdir)
 conf.set_quoted('UDEVLIBEXECDIR',                             udevlibexecdir)
 conf.set_quoted('UDEV_HWDB_DIR',                              udevhwdbdir)
 conf.set_quoted('UDEV_RULES_DIR',                             udevrulesdir)
+conf.set_quoted('UPDATE_HELPER_USER_TIMEOUT',                 get_option('update-helper-user-timeout'))
 conf.set_quoted('USER_CONFIG_UNIT_DIR',                       join_paths(pkgsysconfdir, 'user'))
 conf.set_quoted('USER_DATA_UNIT_DIR',                         userunitdir)
 conf.set_quoted('USER_ENV_GENERATOR_DIR',                     userenvgeneratordir)
index b60261ac24e64154569d2e4295974d97636414a5..50f2b7b5e996dcb4fa97311468828f18df6869a7 100644 (file)
@@ -182,6 +182,8 @@ option('xinitrcdir', type : 'string', value : '',
        description : 'directory for xinitrc files')
 option('rpmmacrosdir', type : 'string', value : 'lib/rpm/macros.d',
        description : 'directory for rpm macros ["no" disables]')
+option('update-helper-user-timeout', type : 'string', value : '15s',
+       description : 'how long to wait for user manager operations')
 option('pamlibdir', type : 'string',
        description : 'directory for PAM modules')
 option('pamconfdir', type : 'string',
index bbdf036da7c40904c6301911749eb832c99cab56..caa2e45595058e0a24d825112d4885bb3ce74c1e 100644 (file)
@@ -93,7 +93,11 @@ fi \
 %{nil}
 
 %systemd_user_postun_with_restart() \
-%{expand:%%{?__systemd_someargs_%#:%%__systemd_someargs_%# systemd_postun_with_restart}} \
+%{expand:%%{?__systemd_someargs_%#:%%__systemd_someargs_%# systemd_user_postun_with_restart}} \
+if [ $1 -ge 1 ] && [ -x "{{SYSTEMD_UPDATE_HELPER_PATH}}" ]; then \
+    # Package upgrade, not uninstall \
+    {{SYSTEMD_UPDATE_HELPER_PATH}} mark-restart-user-units %{?*} || : \
+fi \
 %{nil}
 
 %udev_hwdb_update() %{nil}
index f3c75b75fa7f9e7dfa82caea5f2bf687ad6feabe..f3466ab3c05c577e706854ea279b3324ccccdf07 100755 (executable)
@@ -26,6 +26,15 @@ case "$command" in
 
     remove-user-units)
         systemctl --global disable "$@"
+
+        [ -d /run/systemd/system ] || exit 0
+
+        users=$(systemctl list-units 'user@*' --legend=no | sed -n -r 's/.*user@([0-9]+).service.*/\1/p')
+        for user in $users; do
+            SYSTEMD_BUS_TIMEOUT={{UPDATE_HELPER_USER_TIMEOUT}} \
+                    systemctl --user -M "$user@" disable --now "$@" &
+        done
+        wait
         ;;
 
     mark-restart-system-units)
@@ -37,6 +46,17 @@ case "$command" in
         wait
         ;;
 
+    mark-restart-user-units)
+        [ -d /run/systemd/system ] || exit 0
+
+        users=$(systemctl list-units 'user@*' --legend=no | sed -n -r 's/.*user@([0-9]+).service.*/\1/p')
+        for user in $users; do
+            SYSTEMD_BUS_TIMEOUT={{UPDATE_HELPER_USER_TIMEOUT}} \
+                    systemctl --user -M "$user@" set-property "$unit" Markers=+needs-restart &
+        done
+        wait
+        ;;
+
     system-reload-restart|system-reload|system-restart)
         if [ -n "$*" ]; then
             echo "Unexpected arguments for '$command': $*"
@@ -54,6 +74,33 @@ case "$command" in
         fi
         ;;
 
+    user-reload-restart|user-reload|user-restart)
+        if [ -n "$*" ]; then
+            echo "Unexpected arguments for '$command': $*"
+            exit 2
+        fi
+
+        [ -d /run/systemd/system ] || exit 0
+
+        users=$(systemctl list-units 'user@*' --legend=no | sed -n -r 's/.*user@([0-9]+).service.*/\1/p')
+
+        if [[ "$command" =~ reload ]]; then
+            for user in $users; do
+                SYSTEMD_BUS_TIMEOUT={{UPDATE_HELPER_USER_TIMEOUT}} \
+                        systemctl --user -M "$user@" daemon-reload &
+            done
+            wait
+        fi
+
+        if [[ "$command" =~ restart ]]; then
+            for user in $users; do
+                SYSTEMD_BUS_TIMEOUT={{UPDATE_HELPER_USER_TIMEOUT}} \
+                        systemctl --user -M "$user@" reload-or-restart --marked &
+            done
+            wait
+        fi
+        ;;
+
     *)
         echo "Unknown verb '$command'"
         exit 3
index d29cc33dfd5d0eacab75b8f54dffa4e82824859c..8aeb2049c1d3f4f2ddf2e561a41a27b96c068155 100644 (file)
@@ -20,6 +20,14 @@ elseif pid > 0 then
     posix.wait(pid)
 end
 
+%transfiletriggerin -P 900899 -p <lua> -- {{USER_DATA_UNIT_DIR}} /etc/systemd/user
+pid = posix.fork()
+if pid == 0 then
+    assert(posix.exec("{{SYSTEMD_UPDATE_HELPER_PATH}}", "user-reload-restart"))
+elseif pid > 0 then
+    posix.wait(pid)
+end
+
 %transfiletriggerpostun -P 1000100 -p <lua> -- {{SYSTEM_DATA_UNIT_DIR}} /etc/systemd/system
 -- On removal, we need to run daemon-reload after any units have been
 -- removed.
@@ -33,8 +41,17 @@ elseif pid > 0 then
     posix.wait(pid)
 end
 
+%transfiletriggerpostun -P 1000100 -p <lua> -- {{SYSTEM_DATA_UNIT_DIR}} /etc/systemd/system
+-- Execute daemon-reload in user managers.
+pid = posix.fork()
+if pid == 0 then
+    assert(posix.exec("{{SYSTEMD_UPDATE_HELPER_PATH}}", "user-reload"))
+elseif pid > 0 then
+    posix.wait(pid)
+end
+
 %transfiletriggerpostun -P 10000 -p <lua> -- {{SYSTEM_DATA_UNIT_DIR}} /etc/systemd/system
--- We restart remaining services that should be restarted here.
+-- We restart remaining system services that should be restarted here.
 pid = posix.fork()
 if pid == 0 then
     assert(posix.exec("{{SYSTEMD_UPDATE_HELPER_PATH}}", "system-restart"))
@@ -42,6 +59,15 @@ elseif pid > 0 then
     posix.wait(pid)
 end
 
+%transfiletriggerpostun -P 9999 -p <lua> -- {{USER_DATA_UNIT_DIR}} /etc/systemd/user
+-- We restart remaining user services that should be restarted here.
+pid = posix.fork()
+if pid == 0 then
+    assert(posix.exec("{{SYSTEMD_UPDATE_HELPER_PATH}}", "user-restart"))
+elseif pid > 0 then
+    posix.wait(pid)
+end
+
 %transfiletriggerin -P 100700 -p <lua> -- {{SYSUSERS_DIR}}
 -- This script will process files installed in {{SYSUSERS_DIR}} to create
 -- specified users automatically. The priority is set such that it
index 83cd7617f8250cc2f2c68f1b7deccab2d4898cb7..694cd94e8d8ea5bb87228cf3a613e926f39c9b07 100644 (file)
@@ -16,6 +16,9 @@
 # so sometimes we will reload needlessly.
 {{SYSTEMD_UPDATE_HELPER_PATH}} system-reload-restart || :
 
+%transfiletriggerin -P 900899 -- {{USER_DATA_UNIT_DIR}} /etc/systemd/user
+{{SYSTEMD_UPDATE_HELPER_PATH}} user-reload-restart || :
+
 %transfiletriggerpostun -P 1000100 -- {{SYSTEM_DATA_UNIT_DIR}} /etc/systemd/system
 # On removal, we need to run daemon-reload after any units have been
 # removed.
 # executed.
 {{SYSTEMD_UPDATE_HELPER_PATH}} system-reload || :
 
+%transfiletriggerpostun -P 1000099 -- {{USER_DATA_UNIT_DIR}} /etc/systemd/user
+# Execute daemon-reload in user managers.
+{{SYSTEMD_UPDATE_HELPER_PATH}} user-reload || :
+
 %transfiletriggerpostun -P 10000 -- {{SYSTEM_DATA_UNIT_DIR}} /etc/systemd/system
-# We restart remaining services that should be restarted here.
+# We restart remaining system services that should be restarted here.
 {{SYSTEMD_UPDATE_HELPER_PATH}} system-restart || :
 
+%transfiletriggerpostun -P  9999 -- {{USER_DATA_UNIT_DIR}} /etc/systemd/user
+# We restart remaining user services that should be restarted here.
+{{SYSTEMD_UPDATE_HELPER_PATH}} user-restart || :
+
 %transfiletriggerin -P 1000700 -- {{SYSUSERS_DIR}}
 # This script will process files installed in {{SYSUSERS_DIR}} to create
 # specified users automatically. The priority is set such that it