[ostree] deploy: Handle a read-only /boot



commit 8894bb39498267f4ae06badc7aa54c4eb4bb7f73
Author: Colin Walters <walters verbum org>
Date:   Mon Mar 21 10:37:38 2016 -0400

    deploy: Handle a read-only /boot
    
    I'd like to encourage people to make OSTree-managed systems more
    strictly read-only in multiple places.  Ideally everywhere is
    read-only normally besides `/var/`, `/tmp/`, and `/run`.
    
    `/boot` is a good example of something to make readonly.  Particularly
    now that there's work on the `admin unlock` verb, we need to protect
    the system better against things like `rpm -Uvh kernel.rpm` because
    the RPM-packaged kernel won't understand how to do OSTree right.
    
    In order to make this work of course, we *do* need to remount `/boot`
    as writable when we're doing an upgrade that changes the kernel
    configuration.  So the strategy is to detect whether it's read-only,
    and if so, temporarily mount read-write, then remount read-only when
    the upgrade is done.
    
    We can generalize this in the future to also do `/etc` (and possibly
    `/sysroot/ostree/` although that gets tricky).
    
    One detail: In order to detect "is this path a mountpoint" is
    nontrivial - I looked at copying the systemd code, but the right place
    is to use `libmount` anyways.

 Makefile-libostree.am                 |    5 ++
 configure.ac                          |   26 +++++++++++
 docs/manual/atomic-upgrades.md        |    5 ++-
 src/libostree/ostree-sysroot-deploy.c |   74 +++++++++++++++++++++++++++++++++
 4 files changed, 109 insertions(+), 1 deletions(-)
---
diff --git a/Makefile-libostree.am b/Makefile-libostree.am
index 5dbe774..a50b2b9 100644
--- a/Makefile-libostree.am
+++ b/Makefile-libostree.am
@@ -159,6 +159,11 @@ libostree_1_la_CFLAGS += $(OT_INTERNAL_SOUP_CFLAGS)
 libostree_1_la_LIBADD += $(OT_INTERNAL_SOUP_LIBS)
 endif
 
+if USE_LIBMOUNT
+libostree_1_la_CFLAGS += $(OT_DEP_LIBMOUNT_CFLAGS)
+libostree_1_la_LIBADD += $(OT_DEP_LIBMOUNT_LIBS)
+endif
+
 if USE_SELINUX
 libostree_1_la_CFLAGS += $(OT_DEP_SELINUX_CFLAGS)
 libostree_1_la_LIBADD += $(OT_DEP_SELINUX_LIBS)
diff --git a/configure.ac b/configure.ac
index 5dfc0b2..3d080a6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -192,6 +192,31 @@ AS_IF([ test x$with_selinux != xno ], [
 if test x$with_selinux != xno; then OSTREE_FEATURES="$OSTREE_FEATURES +selinux"; fi
 AM_CONDITIONAL(USE_SELINUX, test $with_selinux != no)
 
+dnl This is what is in RHEL7.2 right now, picking it arbitrarily
+LIBMOUNT_DEPENDENCY="mount >= 2.23.0"
+
+AC_ARG_WITH(libmount,
+           AS_HELP_STRING([--without-libmount], [Do not use libmount]),
+           :, with_libmount=maybe)
+
+AS_IF([ test x$with_libmount != xno ], [
+    AC_MSG_CHECKING([for $LIBMOUNT_DEPENDENCY])
+    PKG_CHECK_EXISTS($LIBMOUNT_DEPENDENCY, have_libmount=yes, have_libmount=no)
+    AC_MSG_RESULT([$have_libmount])
+    AS_IF([ test x$have_libmount = xno && test x$with_libmount != xmaybe ], [
+       AC_MSG_ERROR([libmount is enabled but could not be found])
+    ])
+    AS_IF([ test x$have_libmount = xyes], [
+        AC_DEFINE([HAVE_LIBMOUNT], 1, [Define if we have libmount.pc])
+       PKG_CHECK_MODULES(OT_DEP_LIBMOUNT, $LIBMOUNT_DEPENDENCY)
+       with_libmount=yes
+    ], [
+       with_libmount=no
+    ])
+], [ with_libmount=no ])
+if test x$with_libmount != xno; then OSTREE_FEATURES="$OSTREE_FEATURES +libmount"; fi
+AM_CONDITIONAL(USE_LIBMOUNT, test $with_libmount != no)
+
 # Enabled by default because I think people should use it.
 AC_ARG_ENABLE(rofiles-fuse,
               [AS_HELP_STRING([--enable-rofiles-fuse],
@@ -260,6 +285,7 @@ echo "
     libsoup (retrieve remote HTTP repositories):  $with_soup
     libsoup TLS client certs:                     $have_libsoup_client_certs
     SELinux:                                      $with_selinux
+    libmount:                                     $with_libmount
     libarchive (parse tar files directly):        $with_libarchive
     static deltas:                                yes (always enabled now)
     man pages (xsltproc):                         $enable_man
diff --git a/docs/manual/atomic-upgrades.md b/docs/manual/atomic-upgrades.md
index 4285559..fa57673 100644
--- a/docs/manual/atomic-upgrades.md
+++ b/docs/manual/atomic-upgrades.md
@@ -100,7 +100,10 @@ deployment lists.  This happens when doing an upgrade that does not
 include the kernel; think of a simple translation update.  OSTree
 optimizes for this case because on some systems `/boot` may be on a
 separate medium such as flash storage not optimized for significant
-amounts of write traffic.
+amounts of write traffic.  Related to this, modern OSTree has support
+for having `/boot` be a read-only mount by default - it will
+automatically remount read-write just for the portion of time
+necessary to update the bootloader configuration.
 
 To implement this, OSTree also maintains the directory
 `/ostree/boot.<replaceable>bootversion</replaceable>`, which is a set
diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c
index 5f8e423..bbea3c0 100644
--- a/src/libostree/ostree-sysroot-deploy.c
+++ b/src/libostree/ostree-sysroot-deploy.c
@@ -22,6 +22,12 @@
 
 #include <gio/gunixinputstream.h>
 #include <gio/gunixoutputstream.h>
+#include <sys/mount.h>
+#include <sys/statvfs.h>
+
+#ifdef HAVE_LIBMOUNT
+#include <libmount.h>
+#endif
 
 #include "ostree-sysroot-private.h"
 #include "ostree-deployment-private.h"
@@ -1646,6 +1652,47 @@ cleanup_legacy_current_symlinks (OstreeSysroot         *self,
   return ret;
 }
 
+static gboolean
+is_ro_mount (const char *path)
+{
+#ifdef HAVE_LIBMOUNT
+  /* Dragging in all of this crud is apparently necessary just to determine
+   * whether something is a mount point.
+   *
+   * Systemd has a totally different implementation in
+   * src/basic/mount-util.c.
+   */
+  struct libmnt_table *tb = mnt_new_table_from_file ("/proc/self/mountinfo");
+  struct libmnt_fs *fs;
+  struct libmnt_cache *cache;
+  gboolean is_mount = FALSE;
+  struct statvfs stvfsbuf;
+
+  if (!tb)
+    return FALSE;
+
+  /* to canonicalize all necessary paths */
+  cache = mnt_new_cache ();
+  mnt_table_set_cache (tb, cache);
+
+  fs = mnt_table_find_target(tb, path, MNT_ITER_BACKWARD);
+  is_mount = fs && mnt_fs_get_target (fs);
+  mnt_free_cache (cache);
+  mnt_free_table (tb);
+
+  if (!is_mount)
+    return FALSE;
+
+  /* We *could* parse the options, but it seems more reliable to
+   * introspect the actual mount at runtime.
+   */
+  if (statvfs (path, &stvfsbuf) == 0)
+    return (stvfsbuf.f_flag & ST_RDONLY) != 0;
+
+#endif
+  return FALSE;
+}
+
 /**
  * ostree_sysroot_write_deployments:
  * @self: Sysroot
@@ -1667,6 +1714,7 @@ ostree_sysroot_write_deployments (OstreeSysroot     *self,
   gboolean requires_new_bootversion = FALSE;
   gboolean found_booted_deployment = FALSE;
   gboolean bootloader_is_atomic = FALSE;
+  gboolean boot_was_ro_mount = FALSE;
 
   g_assert (self->loaded);
 
@@ -1754,6 +1802,20 @@ ostree_sysroot_write_deployments (OstreeSysroot     *self,
       glnx_unref_object OstreeRepo *repo = NULL;
       gboolean show_osname = FALSE;
 
+      if (self->booted_deployment)
+        boot_was_ro_mount = is_ro_mount ("/boot");
+
+      g_debug ("boot is ro: %s", boot_was_ro_mount ? "yes" : "no");
+
+      if (boot_was_ro_mount)
+        {
+          if (mount ("/boot", "/boot", NULL, MS_REMOUNT | MS_SILENT, NULL) < 0)
+            {
+              glnx_set_prefix_error_from_errno (error, "%s", "Remounting /boot read-write");
+              goto out;
+            }
+        }
+
       if (!_ostree_sysroot_query_bootloader (self, &bootloader, cancellable, error))
         goto out;
 
@@ -1879,6 +1941,18 @@ ostree_sysroot_write_deployments (OstreeSysroot     *self,
 
   ret = TRUE;
  out:
+  if (boot_was_ro_mount)
+    {
+      if (mount ("/boot", "/boot", NULL, MS_REMOUNT | MS_RDONLY | MS_SILENT, NULL) < 0)
+        {
+          /* Only make this a warning because we don't want to
+           * completely bomb out if some other process happened to
+           * jump in and open a file there.
+           */
+          int errsv = errno;
+          g_printerr ("warning: Failed to remount /boot read-only: %s\n", strerror (errsv));
+        }
+    }
   return ret;
 }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]