From 0bfeb40053a59a245ed862278927840f34cc1dc0 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Wed, 16 Feb 2022 15:58:58 -0700 Subject: [PATCH] finalize-staged: Ensure /boot automount doesn't expire If `/boot` is an automount, then the unit will be stopped as soon as the automount expires. That's would defeat the purpose of using systemd to delay finalizing the deployment until shutdown. This is not uncommon as `systemd-gpt-auto-generator` will create an automount unit for `/boot` when it's the EFI System Partition and there's no fstab entry. Instead of relying on systemd to run the command via `ExecStop` at the appropriate time, have `finalize-staged` open `/boot` and then block on `SIGTERM`. Having the directory open will prevent the automount from expiring, and then we presume that systemd will send `SIGTERM` when it's time for the service to stop. Finalizing the deployment still happens when the service is stopped. The difference is that the process is already running. In order to keep from blocking legitimate sysroot activity prior to shutdown, the sysroot lock is only taken after the signal has been received. Similarly, the sysroot is reloaded to ensure the state of the deployments is current. Fixes: #2543 --- src/boot/ostree-finalize-staged.service | 5 +- src/ostree/ot-admin-builtin-finalize-staged.c | 53 +++++++++++++++++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/boot/ostree-finalize-staged.service b/src/boot/ostree-finalize-staged.service index 2f28bbb706..997d7b71e5 100644 --- a/src/boot/ostree-finalize-staged.service +++ b/src/boot/ostree-finalize-staged.service @@ -30,9 +30,8 @@ After=systemd-journal-flush.service Conflicts=final.target [Service] -Type=oneshot -RemainAfterExit=yes -ExecStop=/usr/bin/ostree admin finalize-staged +Type=simple +ExecStart=/usr/bin/ostree admin finalize-staged # This is a quite long timeout intentionally; the failure mode # here is that people don't get an upgrade. We need to handle # cases with slow rotational media, etc. diff --git a/src/ostree/ot-admin-builtin-finalize-staged.c b/src/ostree/ot-admin-builtin-finalize-staged.c index 17b6a6255a..1acabfa320 100644 --- a/src/ostree/ot-admin-builtin-finalize-staged.c +++ b/src/ostree/ot-admin-builtin-finalize-staged.c @@ -19,8 +19,9 @@ #include "config.h" -#include "config.h" - +#include +#include +#include #include #include "ot-main.h" @@ -36,6 +37,16 @@ static GOptionEntry options[] = { { NULL } }; +static gboolean +sigterm_cb (gpointer user_data) +{ + gboolean *running = user_data; + g_print ("Received sigterm, finalizing deployment\n"); + *running = FALSE; + g_main_context_wakeup (NULL); + return G_SOURCE_REMOVE; +} + /* Called by ostree-finalize-staged.service, and in turn * invokes a cmdprivate function inside the shared library. */ @@ -51,10 +62,46 @@ ot_admin_builtin_finalize_staged (int argc, char **argv, OstreeCommandInvocation g_autoptr(GOptionContext) context = g_option_context_new (""); g_autoptr(OstreeSysroot) sysroot = NULL; if (!ostree_admin_option_context_parse (context, options, &argc, &argv, - OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER, + OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER | OSTREE_ADMIN_BUILTIN_FLAG_UNLOCKED, invocation, &sysroot, cancellable, error)) return FALSE; + /* In case it's an automount, open /boot so that the automount doesn't expire + * until before this process exits. If it did expire and got unmounted, the + * service would be stopped and the deployment would be finalized earlier + * than expected. + */ + int sysroot_fd = ostree_sysroot_get_fd (sysroot); + glnx_autofd int boot_fd = -1; + g_debug ("Opening /boot directory"); + if (!glnx_opendirat (sysroot_fd, "boot", TRUE, &boot_fd, error)) + return FALSE; + + /* We want to wait until the system is shut down to actually finalize, so + * block on SIGTERM under the assumption that it will be received when + * systemd stops the unit. + */ + gboolean running = TRUE; + g_unix_signal_add (SIGTERM, sigterm_cb, &running); + g_print ("Waiting for SIGTERM\n"); + while (running) + g_main_context_iteration (NULL, TRUE); + + /* Since the sysroot was initially started unlocked, setup a mount namespace + * to allow /boot and /sysroot to be writable, lock the sysroot, and reload + * it to ensure the state of the deployments is current. + * + * FIXME: This overlaps with the mount namespace handling in + * ostree_admin_option_context_parse. That should be factored out. + */ + if (unshare (CLONE_NEWNS) < 0) + return glnx_throw_errno_prefix (error, "setting up mount namespace: unshare(CLONE_NEWNS)"); + ostree_sysroot_set_mount_namespace_in_use (sysroot); + if (!ot_admin_sysroot_lock (sysroot, error)) + return FALSE; + if (!ostree_sysroot_load (sysroot, cancellable, error)) + return FALSE; + if (!ostree_cmd__private__()->ostree_finalize_staged (sysroot, cancellable, error)) return FALSE;