diff --git a/Makefile-libostree.am b/Makefile-libostree.am
index 5dbe7741..a50b2b9d 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 5dfc0b2b..3d080a65 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 42855593..fa576734 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.bootversion`, which is a set
diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c
index 5f8e4231..bbea3c05 100644
--- a/src/libostree/ostree-sysroot-deploy.c
+++ b/src/libostree/ostree-sysroot-deploy.c
@@ -22,6 +22,12 @@
#include
#include
+#include
+#include
+
+#ifdef HAVE_LIBMOUNT
+#include
+#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;
}