From 0c45183ae266ccfd5416fc7843f432be317e3f73 Mon Sep 17 00:00:00 2001
From: Colin Walters <walters@verbum.org>
Date: Wed, 22 Jan 2020 19:52:07 +0000
Subject: [PATCH] sysroot: Detect "live" system on /run/ostree-live

Support read-only queries, but deny any attempts to make
changes with a clear error.

Now...in the future, we probably do want to support making
"transient" overlays.  See:
https://github.com/coreos/fedora-coreos-tracker/issues/354#issuecomment-591417441

Closes: https://github.com/ostreedev/ostree/issues/1921
---
 src/libostree/ostree-sysroot-private.h |   1 +
 src/libostree/ostree-sysroot.c         | 153 ++++++++++++++++++++-----
 src/libostree/ostree-sysroot.h         |  10 ++
 3 files changed, 133 insertions(+), 31 deletions(-)

diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h
index 96670b137b..eb021e80e0 100644
--- a/src/libostree/ostree-sysroot-private.h
+++ b/src/libostree/ostree-sysroot-private.h
@@ -60,6 +60,7 @@ struct OstreeSysroot {
   OstreeSysrootLoadState loadstate;
   gboolean mount_namespace_in_use; /* TRUE if caller has told us they used CLONE_NEWNS */
   gboolean root_is_ostree_booted; /* TRUE if sysroot is / and we are booted via ostree */
+  gboolean root_is_ostree_live; /* TRUE if sysroot is / and we are running fully in RAM */
   /* The device/inode for /, used to detect booted deployment */
   dev_t root_device;
   ino_t root_inode;
diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c
index 54b8990097..4129b88963 100644
--- a/src/libostree/ostree-sysroot.c
+++ b/src/libostree/ostree-sysroot.c
@@ -284,6 +284,9 @@ gboolean
 _ostree_sysroot_ensure_writable (OstreeSysroot      *self,
                                  GError            **error)
 {
+  if (self->root_is_ostree_live)
+    return glnx_throw (error, "sysroot does not support persistent changes");
+
   /* Do nothing if no mount namespace is in use */
   if (!self->mount_namespace_in_use)
     return TRUE;
@@ -908,6 +911,9 @@ ostree_sysroot_initialize (OstreeSysroot  *self,
       if (!glnx_fstatat_allow_noent (AT_FDCWD, "/run/ostree-booted", NULL, 0, error))
         return FALSE;
       const gboolean ostree_booted = (errno == 0);
+      if (!glnx_fstatat_allow_noent (AT_FDCWD, OSTREE_SYSROOT_LIVE_PATH, NULL, 0, error))
+        return FALSE;
+      const gboolean ostree_live = (errno == 0);
 
       { struct stat root_stbuf;
         if (!glnx_fstatat (AT_FDCWD, "/", &root_stbuf, 0, error))
@@ -925,6 +931,7 @@ ostree_sysroot_initialize (OstreeSysroot  *self,
          self->root_inode == self_stbuf.st_ino);
 
       self->root_is_ostree_booted = (ostree_booted && root_is_sysroot);
+      self->root_is_ostree_live = self->root_is_ostree_booted && ostree_live;
       self->loadstate = OSTREE_SYSROOT_LOAD_STATE_INIT;
     }
 
@@ -993,8 +1000,6 @@ sysroot_load_from_bootloader_configs (OstreeSysroot  *self,
                                       GCancellable   *cancellable,
                                       GError       **error)
 {
-  struct stat stbuf;
-
   int bootversion = 0;
   if (!read_current_bootversion (self, &bootversion, cancellable, error))
     return FALSE;
@@ -1049,39 +1054,86 @@ sysroot_load_from_bootloader_configs (OstreeSysroot  *self,
   if (self->staged_deployment)
     g_ptr_array_insert (deployments, 0, g_object_ref (self->staged_deployment));
 
-  /* And then set their index variables */
-  for (guint i = 0; i < deployments->len; i++)
-    {
-      OstreeDeployment *deployment = deployments->pdata[i];
-      ostree_deployment_set_index (deployment, i);
-    }
+  self->bootversion = bootversion;
+  self->subbootversion = subbootversion;
+  self->deployments = g_steal_pointer (&deployments);
 
-  /* Determine whether we're "physical" or not, the first time we load deployments */
-  if (self->loadstate < OSTREE_SYSROOT_LOAD_STATE_LOADED)
+  return TRUE;
+}
+
+static gboolean
+find_first_ent_at (int                dirfd,
+                   const char        *path,
+                   int                dtype,
+                   char             **ret_name,
+                   GCancellable      *cancellable,
+                   GError           **error)
+{
+  g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
+
+  if (!glnx_dirfd_iterator_init_at (dirfd, path, TRUE,
+                                    &dfd_iter, error))
+    return FALSE;
+
+  while (TRUE)
     {
-      /* If we have a booted deployment, the sysroot is / and we're definitely
-       * not physical.
-       */
-      if (self->booted_deployment)
-        self->is_physical = FALSE;  /* (the default, but explicit for clarity) */
-      /* Otherwise - check for /sysroot which should only exist in a deployment,
-       * not in ${sysroot} (a metavariable for the real physical root).
-       */
-      else
-        {
-          if (!glnx_fstatat_allow_noent (self->sysroot_fd, "sysroot", &stbuf, 0, error))
-            return FALSE;
-          if (errno == ENOENT)
-            self->is_physical = TRUE;
-        }
-      /* Otherwise, the default is FALSE */
+      struct dirent *dent;
 
-      self->loadstate = OSTREE_SYSROOT_LOAD_STATE_LOADED;
+      if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent,
+                                                       cancellable, error))
+        return FALSE;
+      if (dent == NULL)
+        break;
+
+      if (dent->d_type != dtype)
+        continue;
+
+      *ret_name = g_strdup (dent->d_name);
+      break;
     }
 
-  self->bootversion = bootversion;
-  self->subbootversion = subbootversion;
+  return TRUE;
+}
+
+static gboolean
+sysroot_load_live (OstreeSysroot  *self,
+                   GCancellable   *cancellable,
+                   GError       **error)
+{
+  g_autoptr(GString) bootlink = g_string_new ("/ostree/boot.1");
+
+  g_autofree char *osname = NULL;
+  if (!find_first_ent_at (AT_FDCWD, bootlink->str, DT_DIR, &osname, cancellable, error))
+    return FALSE;
+  if (!osname)
+    return glnx_throw (error, "failed to find /ostree/boot subdirectory");
+  g_string_append_c (bootlink, '/');
+  g_string_append (bootlink, osname);
+
+  g_autofree char *checksum = NULL;
+  if (!find_first_ent_at (AT_FDCWD, bootlink->str, DT_DIR, &checksum, cancellable, error))
+    return FALSE;
+  if (!checksum)
+    return glnx_throw (error, "failed to find /ostree/boot statedir");
+  g_string_append_c (bootlink, '/');
+  g_string_append (bootlink, checksum);
+
+  g_autofree char *treeserial = NULL;
+  if (!find_first_ent_at (AT_FDCWD, bootlink->str, DT_LNK, &treeserial, cancellable, error))
+    return FALSE;
+  if (!treeserial)
+    return glnx_throw (error, "failed to find /ostree/boot treeserial");
+  g_string_append_c (bootlink, '/');
+  g_string_append (bootlink, treeserial);
+
+  g_autoptr(OstreeDeployment) deployment = NULL;
+  if (!parse_deployment (self, bootlink->str, &deployment,
+                         cancellable, error))
+    return FALSE;
+  g_autoptr(GPtrArray) deployments = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
+  g_ptr_array_add (deployments, g_object_ref (deployment));
   self->deployments = g_steal_pointer (&deployments);
+  self->booted_deployment = g_steal_pointer (&deployment);
 
   return TRUE;
 }
@@ -1130,8 +1182,47 @@ ostree_sysroot_load_if_changed (OstreeSysroot  *self,
   self->bootversion = -1;
   self->subbootversion = -1;
 
-  if (!sysroot_load_from_bootloader_configs (self, cancellable, error))
-    return FALSE;
+  if (self->root_is_ostree_live)
+    {
+      if (!sysroot_load_live (self, cancellable, error))
+        return FALSE;
+    }
+  else
+    {
+      if (!sysroot_load_from_bootloader_configs (self, cancellable, error))
+        return FALSE;
+    }
+
+  /* Assign the index variables */
+  g_assert (self->deployments);
+  for (guint i = 0; i < self->deployments->len; i++)
+    {
+      OstreeDeployment *deployment = self->deployments->pdata[i];
+      ostree_deployment_set_index (deployment, i);
+    }
+
+  /* Determine whether we're "physical" or not, the first time we load deployments */
+  if (self->loadstate < OSTREE_SYSROOT_LOAD_STATE_LOADED)
+    {
+      /* If we have a booted deployment, the sysroot is / and we're definitely
+       * not physical.
+       */
+      if (self->booted_deployment)
+        self->is_physical = FALSE;  /* (the default, but explicit for clarity) */
+      /* Otherwise - check for /sysroot which should only exist in a deployment,
+       * not in ${sysroot} (a metavariable for the real physical root).
+       */
+      else
+        {
+          if (!glnx_fstatat_allow_noent (self->sysroot_fd, "sysroot", &stbuf, 0, error))
+            return FALSE;
+          if (errno == ENOENT)
+            self->is_physical = TRUE;
+        }
+      /* Otherwise, the default is FALSE */
+
+      self->loadstate = OSTREE_SYSROOT_LOAD_STATE_LOADED;
+    }
 
   self->loaded_ts = stbuf.st_mtim;
 
diff --git a/src/libostree/ostree-sysroot.h b/src/libostree/ostree-sysroot.h
index af8192fc40..5962d4bcca 100644
--- a/src/libostree/ostree-sysroot.h
+++ b/src/libostree/ostree-sysroot.h
@@ -32,6 +32,16 @@ G_BEGIN_DECLS
 #define OSTREE_IS_SYSROOT(obj) \
   (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OSTREE_TYPE_SYSROOT))
 
+/**
+ * OSTREE_SYSROOT_LIVE_PATH:
+ *
+ * The existence of this file declares that the system
+ * is booted "live" (fully in RAM).
+ *
+ * Since: 2020.1
+ **/
+#define OSTREE_SYSROOT_LIVE_PATH "/run/ostree-live"
+
 _OSTREE_PUBLIC
 GType ostree_sysroot_get_type (void);