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; }