diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index bea4cbce..c60724da 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -621,6 +621,7 @@ ostree_sysroot_upgrader_get_origin ostree_sysroot_upgrader_dup_origin ostree_sysroot_upgrader_set_origin ostree_sysroot_upgrader_get_origin_description +ostree_sysroot_upgrader_set_parent_mountns ostree_sysroot_upgrader_check_timestamps OstreeSysrootUpgraderPullFlags ostree_sysroot_upgrader_pull diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index 85169b5c..4c605619 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -23,6 +23,7 @@ LIBOSTREE_2025.2 { global: ostree_sepolicy_set_null_log; + ostree_sysroot_upgrader_set_parent_mountns; } LIBOSTREE_2025.1; /* Stub section for the stable release *after* this development one; don't diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 37e7774b..8a7876db 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -4259,6 +4259,102 @@ ostree_sysroot_deployment_set_mutable (OstreeSysroot *self, OstreeDeployment *de return TRUE; } +/** + * ostree_sysroot_deployment_prepare_next_root + * @self: Sysroot + * @deployment: Deployment to prepare /run/nextroot for + * @cancellable: Cancellable + * @error: Error + * + * Prepare the specified deployment for a systemd soft-reboot by creating a new + * root with it at /run/nextroot + * + * Since: TODO + */ +gboolean +ostree_sysroot_deployment_prepare_next_root (OstreeSysroot *self, OstreeDeployment *deployment, + GCancellable *cancellable, GError **error) +{ + GLNX_AUTO_PREFIX_ERROR ("Preparing /run/nextroot for a soft-reboot", error); + OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment); + const char *kargs_const = ostree_bootconfig_parser_get (bootconfig, "options"); + g_autofree gchar *kargs = g_strdup (kargs_const); + gint exit_status; + g_autoptr (GPtrArray) args = g_ptr_array_new (); + + /* We cannot just make a bind mount, because when the soft reboot happens, our + current root will be unmounted, and the bind mount will break. Therefore, + we have to recreate the mount at a different location. */ + + /* Read in mounted FSes */ + g_autofree gchar *mounts_contents = NULL; + if (!g_file_get_contents ("/proc/self/mounts", &mounts_contents, NULL, error)) + return FALSE; + g_auto (GStrv) mounts = g_strsplit (mounts_contents, "\n", -1); + for (char **iter = mounts; iter && *iter; iter++) + { + const gchar *mount_text = *iter; + g_auto (GStrv) mount_fields = g_strsplit (mount_text, " ", 6); + /* Validate all 6 tokens are present */ + for (int i = 0; i < 6; ++i) + { + if (mount_fields[i] == NULL) + { + ot_journal_print (LOG_WARNING, "Mount %s is missing a field at %d!", mount_text, i); + continue; + } + } + + /* Only care about /sysroot */ + if (!g_str_equal (mount_fields[1], "/sysroot")) + continue; + + if (!glnx_shutil_mkdir_p_at (AT_FDCWD, "/run/nextroot", 0755, cancellable, error)) + return FALSE; + + if (mount (mount_fields[0], "/run/nextroot", mount_fields[2], MS_SILENT, NULL)) + { + glnx_prefix_error (error, "failed to mount /run/nextroot"); + goto err_unlink; + } + + /* Found it, and mounted it! */ + goto end_loop; + } + + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Couldn't find mountpoint for /sysroot!"); + return FALSE; + +end_loop: + + /* At this point, /sysroot is mounted at /run/nextroot, do the pivot from there: */ + g_ptr_array_add (args, "/usr/lib/ostree/ostree-prepare-root"); + g_ptr_array_add (args, "/run/nextroot"); + g_ptr_array_add (args, kargs); + g_ptr_array_add (args, NULL); + + if (!g_spawn_sync (NULL, (char **)args->pdata, NULL, 0, NULL, NULL, NULL, NULL, &exit_status, + error)) + goto err_unlink; + + if (!g_spawn_check_exit_status (exit_status, error)) + { + glnx_prefix_error (error, "failed to prepare next root"); + goto err_unlink; + } + + return TRUE; + +err_unlink: + /* We're already handling an error, not much we can do if this fails too... */ + if (unlinkat (AT_FDCWD, "/run/nextroot", AT_REMOVEDIR) < 0) + ot_journal_print (LOG_WARNING, + "Couldn't remove /run/nextroot while cleaning up from failed next root"); + + return FALSE; +} + /** * ostree_sysroot_deployment_kexec_load * @self: Sysroot diff --git a/src/libostree/ostree-sysroot-upgrader.c b/src/libostree/ostree-sysroot-upgrader.c index c991f338..bf98cbda 100644 --- a/src/libostree/ostree-sysroot-upgrader.c +++ b/src/libostree/ostree-sysroot-upgrader.c @@ -25,6 +25,8 @@ #include "ostree-sysroot-upgrader.h" #include "ostree.h" +#include + /** * SECTION:ostree-sysroot-upgrader * @title: Simple upgrade class @@ -53,6 +55,8 @@ struct OstreeSysrootUpgrader char *override_csum; char *new_revision; + + int parent_mountns; }; enum @@ -139,6 +143,8 @@ ostree_sysroot_upgrader_initable_init (GInitable *initable, GCancellable *cancel if (!parse_refspec (self, cancellable, error)) return FALSE; + self->parent_mountns = -1; + return TRUE; } @@ -381,6 +387,21 @@ ostree_sysroot_upgrader_get_origin_description (OstreeSysrootUpgrader *self) return g_key_file_get_string (self->origin, "origin", "refspec", NULL); } +/** + * ostree_sysroot_upgrader_set_parent_mountns: + * @self: Sysroot Upgrader + * @mountns: FD of the parent mount NS + * + * Replace FD of the parent mountns + */ +gboolean +ostree_sysroot_upgrader_set_parent_mountns (OstreeSysrootUpgrader *self, int mountns) +{ + self->parent_mountns = mountns; + + return TRUE; +} + /** * ostree_sysroot_upgrader_check_timestamps: * @repo: Repo @@ -628,6 +649,36 @@ ostree_sysroot_upgrader_deploy (OstreeSysrootUpgrader *self, GCancellable *cance error)) return FALSE; } + + if ((self->flags & OSTREE_SYSROOT_UPGRADER_FLAGS_SOFT_REBOOT) > 0) + { + /* Need to fork because setns will fail if we have threads (GIO loop) */ + pid_t child_pid = fork (); + if (child_pid < 0) + { + return glnx_throw_errno_prefix (error, "fork() for parent mount ns"); + } + + /* Hop back into the main system's mount NS so we can mount /run/nextroot */ + if (child_pid == 0) + { + if (self->parent_mountns >= 0 && setns (self->parent_mountns, CLONE_NEWNS)) + { + return glnx_throw_errno_prefix (error, "Couldn't recover parent mount NS!"); + } + if (!ostree_sysroot_deployment_prepare_next_root (self->sysroot, new_deployment, + cancellable, error)) + return FALSE; + + exit (0); + } + + gint status; + if (waitpid (child_pid, &status, 0) < 0) + { + return glnx_throw_errno_prefix (error, "waitpid() for mount ns child"); + } + } } return TRUE; @@ -645,6 +696,8 @@ ostree_sysroot_upgrader_flags_get_type (void) "OSTREE_SYSROOT_UPGRADER_FLAGS_IGNORE_UNCONFIGURED", "ignore-unconfigured" }, { OSTREE_SYSROOT_UPGRADER_FLAGS_STAGE, "OSTREE_SYSROOT_UPGRADER_FLAGS_STAGE", "stage" }, { OSTREE_SYSROOT_UPGRADER_FLAGS_KEXEC, "OSTREE_SYSROOT_UPGRADER_FLAGS_KEXEC", "kexec" }, + { OSTREE_SYSROOT_UPGRADER_FLAGS_SOFT_REBOOT, "OSTREE_SYSROOT_UPGRADER_FLAGS_SOFT_REBOOT", + "soft_reboot" }, { 0, NULL, NULL } }; GType g_define_type_id diff --git a/src/libostree/ostree-sysroot-upgrader.h b/src/libostree/ostree-sysroot-upgrader.h index 5e2f5ed8..5e69df69 100644 --- a/src/libostree/ostree-sysroot-upgrader.h +++ b/src/libostree/ostree-sysroot-upgrader.h @@ -45,6 +45,7 @@ typedef enum OSTREE_SYSROOT_UPGRADER_FLAGS_IGNORE_UNCONFIGURED = (1 << 1), OSTREE_SYSROOT_UPGRADER_FLAGS_STAGE = (1 << 2), OSTREE_SYSROOT_UPGRADER_FLAGS_KEXEC = (1 << 3), + OSTREE_SYSROOT_UPGRADER_FLAGS_SOFT_REBOOT = (1 << 4), } OstreeSysrootUpgraderFlags; _OSTREE_PUBLIC @@ -80,6 +81,9 @@ gboolean ostree_sysroot_upgrader_set_origin (OstreeSysrootUpgrader *self, GKeyFi _OSTREE_PUBLIC char *ostree_sysroot_upgrader_get_origin_description (OstreeSysrootUpgrader *self); +_OSTREE_PUBLIC +gboolean ostree_sysroot_upgrader_set_parent_mountns (OstreeSysrootUpgrader *self, int mountns); + _OSTREE_PUBLIC gboolean ostree_sysroot_upgrader_check_timestamps (OstreeRepo *repo, const char *from_rev, const char *to_rev, GError **error); diff --git a/src/libostree/ostree-sysroot.h b/src/libostree/ostree-sysroot.h index 64a1207c..819b12ab 100644 --- a/src/libostree/ostree-sysroot.h +++ b/src/libostree/ostree-sysroot.h @@ -268,6 +268,11 @@ gboolean ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const c OstreeSysrootSimpleWriteDeploymentFlags flags, GCancellable *cancellable, GError **error); +_OSTREE_PUBLIC +gboolean ostree_sysroot_deployment_prepare_next_root (OstreeSysroot *self, + OstreeDeployment *deployment, + GCancellable *cancellable, GError **error); + _OSTREE_PUBLIC gboolean ostree_sysroot_deployment_kexec_load (OstreeSysroot *self, OstreeDeployment *deployment, GCancellable *cancellable, GError **error); diff --git a/src/ostree/ot-admin-builtin-upgrade.c b/src/ostree/ot-admin-builtin-upgrade.c index 4c9dbbed..3b49169f 100644 --- a/src/ostree/ot-admin-builtin-upgrade.c +++ b/src/ostree/ot-admin-builtin-upgrade.c @@ -32,6 +32,7 @@ static gboolean opt_reboot; static gboolean opt_kexec; +static gboolean opt_soft_reboot; static gboolean opt_allow_downgrade; static gboolean opt_pull_only; static gboolean opt_deploy_only; @@ -44,6 +45,8 @@ static GOptionEntry options[] = { "Use a different operating system root than the current one", "OSNAME" }, { "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Reboot after a successful upgrade", NULL }, { "kexec", 'k', 0, G_OPTION_ARG_NONE, &opt_kexec, "Stage new kernel in kexec", NULL }, + { "soft-reboot", 'x', 0, G_OPTION_ARG_NONE, &opt_soft_reboot, + "Prepare nextroot for a systemd soft reboot", NULL }, { "allow-downgrade", 0, 0, G_OPTION_ARG_NONE, &opt_allow_downgrade, "Permit deployment of chronologically older trees", NULL }, { "override-commit", 0, 0, G_OPTION_ARG_STRING, &opt_override_commit, @@ -86,12 +89,17 @@ ot_admin_builtin_upgrade (int argc, char **argv, OstreeCommandInvocation *invoca flags |= OSTREE_SYSROOT_UPGRADER_FLAGS_STAGE; if (opt_kexec) flags |= OSTREE_SYSROOT_UPGRADER_FLAGS_KEXEC; + if (opt_soft_reboot) + flags |= OSTREE_SYSROOT_UPGRADER_FLAGS_SOFT_REBOOT; g_autoptr (OstreeSysrootUpgrader) upgrader = ostree_sysroot_upgrader_new_for_os_with_flags ( sysroot, opt_osname, flags, cancellable, error); if (!upgrader) return FALSE; + if (!ostree_sysroot_upgrader_set_parent_mountns (upgrader, invocation->parent_mountns)) + return FALSE; + g_autoptr (GKeyFile) origin = ostree_sysroot_upgrader_dup_origin (upgrader); if (origin != NULL) { diff --git a/src/ostree/ot-builtin-admin.c b/src/ostree/ot-builtin-admin.c index 53face6a..345d4230 100644 --- a/src/ostree/ot-builtin-admin.c +++ b/src/ostree/ot-builtin-admin.c @@ -174,6 +174,7 @@ ostree_builtin_admin (int argc, char **argv, OstreeCommandInvocation *invocation g_set_prgname (prgname); } - OstreeCommandInvocation sub_invocation = { .command = subcommand }; + OstreeCommandInvocation sub_invocation = *invocation; + sub_invocation.command = subcommand; return subcommand->fn (argc, argv, &sub_invocation, cancellable, error); } diff --git a/src/ostree/ot-main.c b/src/ostree/ot-main.c index 794af31d..557f57a5 100644 --- a/src/ostree/ot-main.c +++ b/src/ostree/ot-main.c @@ -288,6 +288,13 @@ ostree_run (int argc, char **argv, OstreeCommand *commands, GError **res_error) command++; } + int parent_mountns = open ("/proc/self/ns/mnt", O_RDONLY | O_NOCTTY | O_CLOEXEC); + if (parent_mountns < 0) + { + glnx_throw_errno_prefix (&error, "open(mountns)"); + goto out; + } + if (!command->fn) { g_autoptr (GOptionContext) context = ostree_option_context_new_with_commands (commands); @@ -315,7 +322,8 @@ ostree_run (int argc, char **argv, OstreeCommand *commands, GError **res_error) prgname = g_strdup_printf ("%s %s", g_get_prgname (), command_name); g_set_prgname (prgname); #endif - OstreeCommandInvocation invocation = { .command = command }; + OstreeCommandInvocation invocation = { .command = command, .parent_mountns = parent_mountns }; + if (!command->fn (argc, argv, &invocation, cancellable, &error)) goto out; diff --git a/src/ostree/ot-main.h b/src/ostree/ot-main.h index 8df1ca8e..5347882b 100644 --- a/src/ostree/ot-main.h +++ b/src/ostree/ot-main.h @@ -61,6 +61,7 @@ typedef struct struct OstreeCommandInvocation { OstreeCommand *command; + int parent_mountns; }; int ostree_main (int argc, char **argv, OstreeCommand *commands);