From 0b6e19d3dbe6ee35a2e047cdd35cdeb755c71f8c Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 8 Feb 2024 10:13:57 -0500 Subject: [PATCH] Track deployment root/inode from prepare root When we added composefs, it broke the logic for detecting the booted deployment which was previously a direct (device, inode) comparison. So the code there started looking at `etc`. However, that in turns breaks with `etc.transient = true` enabled. Fix all of this by tracking the real deployment directory's (device,inode) that we found in `ostree-prepare-root`, and inject it into the extensible metadata we have in `/run/ostree-booted` which is designed exactly to pass state between the initramfs and the real root. --- src/libostree/ostree-sysroot-private.h | 3 ++ src/libostree/ostree-sysroot.c | 57 +++++++++++++++----------- src/libotcore/otcore.h | 2 + src/switchroot/ostree-prepare-root.c | 9 ++++ 4 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h index 5cbeae0ecb..f111efaa9c 100644 --- a/src/libostree/ostree-sysroot-private.h +++ b/src/libostree/ostree-sysroot-private.h @@ -77,6 +77,9 @@ struct OstreeSysroot dev_t etc_device; ino_t etc_inode; + // The parsed data from /run/ostree + GVariantDict *run_ostree_metadata; + gboolean is_physical; /* TRUE if we're pointed at physical storage root and not a deployment */ GPtrArray *deployments; int bootversion; diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c index 5bcae1c79a..dca69339ca 100644 --- a/src/libostree/ostree-sysroot.c +++ b/src/libostree/ostree-sysroot.c @@ -83,6 +83,7 @@ ostree_sysroot_finalize (GObject *object) g_clear_object (&self->path); g_clear_object (&self->repo); + g_clear_pointer (&self->run_ostree_metadata, g_variant_dict_unref); g_clear_pointer (&self->deployments, g_ptr_array_unref); g_clear_object (&self->booted_deployment); g_clear_object (&self->staged_deployment); @@ -808,26 +809,35 @@ parse_deployment (OstreeSysroot *self, const char *boot_link, OstreeDeployment * if (looking_for_booted_deployment) { struct stat stbuf; - struct stat etc_stbuf = {}; if (!glnx_fstat (deployment_dfd, &stbuf, error)) return FALSE; - /* We look for either the root or the etc subdir of the - * deployment. We need to do this, because when using composefs, - * the root is not a bind mount of the deploy dir, but the etc - * dir is. + /* ostree-prepare-root records the (device, inode) pair of the underlying real deployment + * directory (before we might have mounted a composefs or overlayfs on top). + * + * Because this parser is operating outside the mounted namespace, we compare against + * that backing directory. */ - - if (!glnx_fstatat_allow_noent (deployment_dfd, "etc", &etc_stbuf, 0, error)) - return FALSE; + g_assert (self->run_ostree_metadata); + guint64 expected_root_dev = 0; + guint64 expected_root_inode = 0; + if (!g_variant_dict_lookup (self->run_ostree_metadata, + OTCORE_RUN_BOOTED_KEY_BACKING_ROOTDEVINO, "(tt)", + &expected_root_dev, &expected_root_inode)) + { + g_debug ("Missing %s", OTCORE_RUN_BOOTED_KEY_BACKING_ROOTDEVINO); + expected_root_dev = (guint64)self->root_device; + expected_root_inode = (guint64)self->root_inode; + } + else + g_debug ("Target rootdev key %s found", OTCORE_RUN_BOOTED_KEY_BACKING_ROOTDEVINO); /* A bit ugly, we're assigning to a sysroot-owned variable from deep in * this parsing code. But eh, if something fails the sysroot state can't * be relied on anyways. */ is_booted_deployment - = (stbuf.st_dev == self->root_device && stbuf.st_ino == self->root_inode) - || (etc_stbuf.st_dev == self->etc_device && etc_stbuf.st_ino == self->etc_inode); + = stbuf.st_dev == expected_root_dev && stbuf.st_ino == expected_root_inode; } g_autoptr (OstreeDeployment) ret_deployment @@ -1016,10 +1026,21 @@ ostree_sysroot_initialize (OstreeSysroot *self, GError **error) * we'll use it to sanity check that we found a booted deployment for example. * Second, we also find out whether sysroot == /. */ - if (!glnx_fstatat_allow_noent (AT_FDCWD, OSTREE_PATH_BOOTED, NULL, 0, error)) + glnx_autofd int booted_state_fd = -1; + if (!ot_openat_ignore_enoent (AT_FDCWD, OSTREE_PATH_BOOTED, &booted_state_fd, error)) return FALSE; - const gboolean ostree_booted = (errno == 0); + const gboolean ostree_booted = booted_state_fd != -1; + + if (booted_state_fd != -1) + { + g_autoptr (GVariant) ostree_run_metadata_v = NULL; + if (!ot_variant_read_fd (booted_state_fd, 0, G_VARIANT_TYPE_VARDICT, TRUE, + &ostree_run_metadata_v, error)) + return glnx_prefix_error (error, "failed to read %s", OTCORE_RUN_BOOTED); + self->run_ostree_metadata = g_variant_dict_new (ostree_run_metadata_v); + } + // Gather the root device/inode { struct stat root_stbuf; if (!glnx_fstatat (AT_FDCWD, "/", &root_stbuf, 0, error)) @@ -1028,17 +1049,6 @@ ostree_sysroot_initialize (OstreeSysroot *self, GError **error) self->root_inode = root_stbuf.st_ino; } - { - struct stat etc_stbuf; - if (!glnx_fstatat_allow_noent (AT_FDCWD, "/etc", &etc_stbuf, 0, error)) - return FALSE; - if (errno != ENOENT) - { - self->etc_device = etc_stbuf.st_dev; - self->etc_inode = etc_stbuf.st_ino; - } - } - struct stat self_stbuf; if (!glnx_fstatat (AT_FDCWD, gs_file_get_path_cached (self->path), &self_stbuf, 0, error)) return FALSE; @@ -1047,6 +1057,7 @@ ostree_sysroot_initialize (OstreeSysroot *self, GError **error) = (self->root_device == self_stbuf.st_dev && self->root_inode == self_stbuf.st_ino); self->root_is_ostree_booted = (ostree_booted && root_is_sysroot); + g_debug ("root_is_ostree_booted: %d", self->root_is_ostree_booted); self->loadstate = OSTREE_SYSROOT_LOAD_STATE_INIT; } diff --git a/src/libotcore/otcore.h b/src/libotcore/otcore.h index 1fe4be015e..752c0758e4 100644 --- a/src/libotcore/otcore.h +++ b/src/libotcore/otcore.h @@ -82,5 +82,7 @@ GKeyFile *otcore_load_config (int rootfs, const char *filename, GError **error); #define OTCORE_RUN_BOOTED_KEY_ROOT_TRANSIENT "root.transient" // This key will be present if the sysroot-ro flag was found #define OTCORE_RUN_BOOTED_KEY_SYSROOT_RO "sysroot-ro" +// Always holds the (device, inode) pair of the booted deployment +#define OTCORE_RUN_BOOTED_KEY_BACKING_ROOTDEVINO "backing-root-device-inode" #define OTCORE_RUN_BOOTED_KEY_TRANSIENT_ETC "transient-etc" diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c index bde698b00e..28e422f430 100644 --- a/src/switchroot/ostree-prepare-root.c +++ b/src/switchroot/ostree-prepare-root.c @@ -430,6 +430,15 @@ main (int argc, char *argv[]) GVariantBuilder metadata_builder; g_variant_builder_init (&metadata_builder, G_VARIANT_TYPE ("a{sv}")); + /* Record the underlying plain deployment directory (device,inode) pair + * so that it can be later checked by the sysroot code to figure out + * which deployment was booted. + */ + if (lstat (".", &stbuf) < 0) + err (EXIT_FAILURE, "lstat deploy_root"); + g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_BACKING_ROOTDEVINO, + g_variant_new ("(tt)", (guint64)stbuf.st_dev, (guint64)stbuf.st_ino)); + // Tracks if we did successfully enable it at runtime bool using_composefs = false;