Track deployment root/inode from prepare root
authorColin Walters <walters@verbum.org>
Thu, 8 Feb 2024 15:13:57 +0000 (10:13 -0500)
committerColin Walters <walters@verbum.org>
Thu, 8 Feb 2024 17:57:53 +0000 (12:57 -0500)
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.

Signed-off-by: Colin Walters <walters@verbum.org>
src/libostree/ostree-sysroot-private.h
src/libostree/ostree-sysroot.c
src/libotcore/otcore.h
src/switchroot/ostree-prepare-root.c

index 5cbeae0ecb5bd63c33456c4808d8b7bdfe20418d..8b9cadea080b929e3df92cdf03d3ffb4c83dd860 100644 (file)
@@ -74,8 +74,9 @@ struct OstreeSysroot
   /* The device/inode for / and /etc, used to detect booted deployment */
   dev_t root_device;
   ino_t root_inode;
-  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;
index 5bcae1c79a0c7e7be60a649aa15f1cbffc456746..dca69339cacfae6e04d0446e257305b94a24943b 100644 (file)
@@ -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;
     }
 
index 1fe4be015e8e12f268e7fc19fd9ef6e1c3c25151..752c0758e417876094b23d7760bfec5c02cec5fe 100644 (file)
@@ -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"
index bde698b00e5db568a7f0c67afc5718256115662c..28e422f430414ff9deb7a1b223153716843695ef 100644 (file)
@@ -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;