Skip to content

Commit

Permalink
WIP: Support mounting /sysroot (and /boot) read-only
Browse files Browse the repository at this point in the history
Add a magic file and an API for consumers to tell us it's OK
for us to remount read-write (since it'll just affect our
mount namespace).
  • Loading branch information
cgwalters committed Dec 16, 2018
1 parent 30d7951 commit e3e9e90
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 21 deletions.
2 changes: 2 additions & 0 deletions apidoc/ostree-sections.txt
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ ostree_sepolicy_get_type
OstreeSysroot
ostree_sysroot_new
ostree_sysroot_new_default
ostree_sysroot_initialize
ostree_sysroot_get_path
ostree_sysroot_load
ostree_sysroot_load_if_changed
Expand All @@ -504,6 +505,7 @@ ostree_sysroot_lock_async
ostree_sysroot_lock_finish
ostree_sysroot_unlock
ostree_sysroot_unload
ostree_sysroot_set_mount_namespace_in_use
ostree_sysroot_get_fd
ostree_sysroot_ensure_initialized
ostree_sysroot_get_bootversion
Expand Down
2 changes: 2 additions & 0 deletions src/libostree/libostree-devel.sym
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

/* Add new symbols here. Release commits should copy this section into -released.sym. */
LIBOSTREE_2018.10 {
ostree_sysroot_initialize;
ostree_sysroot_set_mount_namespace_in_use;
} LIBOSTREE_2018.9;

/* Stub section for the stable release *after* this development one; don't
Expand Down
6 changes: 6 additions & 0 deletions src/libostree/ostree-sysroot-cleanup.c
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,9 @@ ostree_sysroot_cleanup_prune_repo (OstreeSysroot *sysroot,
OstreeRepo *repo = ostree_sysroot_repo (sysroot);
const guint depth = 0; /* Historical default */

if (!_ostree_sysroot_ensure_writable (sysroot, error))
return FALSE;

/* Hold an exclusive lock by default across gathering refs and doing
* the prune.
*/
Expand Down Expand Up @@ -536,6 +539,9 @@ _ostree_sysroot_cleanup_internal (OstreeSysroot *self,
g_return_val_if_fail (OSTREE_IS_SYSROOT (self), FALSE);
g_return_val_if_fail (self->loaded, FALSE);

if (!_ostree_sysroot_ensure_writable (self, error))
return FALSE;

if (!cleanup_other_bootversions (self, cancellable, error))
return glnx_prefix_error (error, "Cleaning bootversions");

Expand Down
27 changes: 24 additions & 3 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
#define OSTREE_DEPLOYMENT_FINALIZING_ID SD_ID128_MAKE(e8,64,6c,d6,3d,ff,46,25,b7,79,09,a8,e7,a4,09,94)
#endif

static gboolean
is_ro_mount (const char *path);

/*
* Like symlinkat() but overwrites (atomically) an existing
* symlink.
Expand Down Expand Up @@ -810,6 +813,9 @@ write_origin_file_internal (OstreeSysroot *sysroot,
GCancellable *cancellable,
GError **error)
{
if (!_ostree_sysroot_ensure_writable (sysroot, error))
return FALSE;

GLNX_AUTO_PREFIX_ERROR ("Writing out origin file", error);
GKeyFile *origin =
new_origin ? new_origin : ostree_deployment_get_origin (deployment);
Expand Down Expand Up @@ -2183,6 +2189,9 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self,
{
g_assert (self->loaded);

if (!_ostree_sysroot_ensure_writable (self, error))
return FALSE;

/* Dealing with the staged deployment is quite tricky here. This function is
* primarily concerned with writing out "finalized" deployments which have
* bootloader entries. Originally, we simply dropped the staged deployment
Expand Down Expand Up @@ -2337,7 +2346,6 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self,

if (boot_was_ro_mount)
{
/* TODO: Use new mount namespace. https://github.com/ostreedev/ostree/issues/1265 */
if (mount ("/boot", "/boot", NULL, MS_REMOUNT | MS_SILENT, NULL) < 0)
return glnx_throw_errno_prefix (error, "Remounting /boot read-write");
}
Expand All @@ -2349,8 +2357,10 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self,
/* Note equivalent of try/finally here */
gboolean success = write_deployments_bootswap (self, new_deployments, opts, bootloader,
&syncstats, cancellable, error);
/* Below here don't set GError until the if (!success) check */
if (boot_was_ro_mount)
/* Below here don't set GError until the if (!success) check.
* Note we only bother remounting if a mount namespace isn't in use.
* */
if (boot_was_ro_mount && !self->mount_namespace_in_use)
{
if (mount ("/boot", "/boot", NULL, MS_REMOUNT | MS_RDONLY | MS_SILENT, NULL) < 0)
{
Expand Down Expand Up @@ -2658,6 +2668,9 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self,
GCancellable *cancellable,
GError **error)
{
if (!_ostree_sysroot_ensure_writable (self, error))
return FALSE;

g_autoptr(OstreeDeployment) deployment = NULL;
if (!sysroot_initialize_deployment (self, osname, revision, origin, override_kernel_argv,
&deployment, cancellable, error))
Expand Down Expand Up @@ -2757,6 +2770,9 @@ ostree_sysroot_stage_tree (OstreeSysroot *self,
GCancellable *cancellable,
GError **error)
{
if (!_ostree_sysroot_ensure_writable (self, error))
return FALSE;

OstreeDeployment *booted_deployment = ostree_sysroot_get_booted_deployment (self);
if (booted_deployment == NULL)
return glnx_throw (error, "Cannot stage a deployment when not currently booted into an OSTree system");
Expand Down Expand Up @@ -2971,6 +2987,9 @@ ostree_sysroot_deployment_set_kargs (OstreeSysroot *self,
GCancellable *cancellable,
GError **error)
{
if (!_ostree_sysroot_ensure_writable (self, error))
return FALSE;

/* For now; instead of this do a redeployment */
g_assert (!ostree_deployment_is_staged (deployment));

Expand Down Expand Up @@ -3018,6 +3037,8 @@ ostree_sysroot_deployment_set_mutable (OstreeSysroot *self,
GCancellable *cancellable,
GError **error)
{
if (!_ostree_sysroot_ensure_writable (self, error))
return FALSE;

if (g_cancellable_set_error_if_cancelled (cancellable, error))
return FALSE;
Expand Down
5 changes: 5 additions & 0 deletions src/libostree/ostree-sysroot-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ struct OstreeSysroot {
GLnxLockFile lock;

gboolean loaded;
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 */
/* The device/inode for /, used to detect booted deployment */
dev_t root_device;
Expand All @@ -79,6 +80,10 @@ struct OstreeSysroot {
#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_DIR "/run/ostree/deployment-state/"
#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT "unlocked-development"

gboolean
_ostree_sysroot_ensure_writable (OstreeSysroot *self,
GError **error);

void
_ostree_sysroot_emit_journal_msg (OstreeSysroot *self,
const char *msg);
Expand Down
71 changes: 69 additions & 2 deletions src/libostree/ostree-sysroot.c
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,24 @@ ostree_sysroot_new_default (void)
return ostree_sysroot_new (NULL);
}

/**
* ostree_sysroot_set_mount_namespace_in_use:
*
* If this function is invoked, then libostree will assume that
* a private Linux mount namespace has been created by the process.
* The primary use case for this is to have e.g. /sysroot mounted
* read-only by default.
*
* If this function has been called, then libostree will automatically
* remount as writable any mount points on which it operates. This
* currently is just /sysroot.
*/
void
ostree_sysroot_set_mount_namespace_in_use (OstreeSysroot *self)
{
self->mount_namespace_in_use = TRUE;
}

/**
* ostree_sysroot_get_path:
* @self:
Expand All @@ -251,6 +269,37 @@ ensure_sysroot_fd (OstreeSysroot *self,
return TRUE;
}

/* Remount /sysroot read-write if necessary */
gboolean
_ostree_sysroot_ensure_writable (OstreeSysroot *self,
GError **error)
{
/* If we aren't operating on a booted system, then we don't
* do anything with mounts. Also, if the caller hasn't
* explicitly told us they made a mount namespace, we don't
* do any remounts.
*/
if (!self->root_is_ostree_booted ||
!self->mount_namespace_in_use)
return TRUE;

if (!ensure_sysroot_fd (self, error))
return FALSE;

/* Check if /sysroot is a read-only mountpoint */
struct statvfs stvfsbuf;
if (statvfs ("/sysroot", &stvfsbuf) <= 0)
return glnx_throw_errno_prefix (error, "fstatvfs(/sysroot)");
if ((stvfsbuf.f_flag & ST_RDONLY) != 0)
return TRUE;

/* OK, let's remount writable. */
if (mount ("/sysroot", "/sysroot", NULL, MS_REMOUNT | MS_SILENT, NULL) < 0)
return glnx_throw_errno_prefix (error, "Remounting /sysroot read-write");

return TRUE;
}

/**
* ostree_sysroot_get_fd:
* @self: Sysroot
Expand Down Expand Up @@ -771,6 +820,24 @@ ostree_sysroot_load (OstreeSysroot *self,
return ostree_sysroot_load_if_changed (self, NULL, cancellable, error);
}

/**
* ostree_sysroot_initialize:
* @self: sysroot
*
* Subset of ostree_sysroot_load(); performs basic initialization.
* Notably, one can invoke `ostree_sysroot_get_fd()` after calling
* this function.
*
* It is not necessary to call this function if ostree_sysroot_load()
* is invoked.
*/
gboolean
ostree_sysroot_initialize (OstreeSysroot *self,
GError **error)
{
return ensure_sysroot_fd (self, error);
}

static gboolean
ensure_repo (OstreeSysroot *self,
GError **error)
Expand Down Expand Up @@ -861,7 +928,7 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self,
GCancellable *cancellable,
GError **error)
{
if (!ensure_sysroot_fd (self, error))
if (!ostree_sysroot_initialize (self, error))
return FALSE;

/* Here we also lazily initialize the repository. We didn't do this
Expand Down Expand Up @@ -1482,7 +1549,7 @@ ostree_sysroot_init_osname (OstreeSysroot *self,
GCancellable *cancellable,
GError **error)
{
if (!ensure_sysroot_fd (self, error))
if (!_ostree_sysroot_ensure_writable (self, error))
return FALSE;

const char *deploydir = glnx_strjoina ("ostree/deploy/", osname);
Expand Down
11 changes: 11 additions & 0 deletions src/libostree/ostree-sysroot.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,19 @@ OstreeSysroot* ostree_sysroot_new (GFile *path);
_OSTREE_PUBLIC
OstreeSysroot* ostree_sysroot_new_default (void);

_OSTREE_PUBLIC
void ostree_sysroot_set_mount_namespace_in_use (OstreeSysroot *self);

_OSTREE_PUBLIC
GFile *ostree_sysroot_get_path (OstreeSysroot *self);

_OSTREE_PUBLIC
int ostree_sysroot_get_fd (OstreeSysroot *self);

_OSTREE_PUBLIC
gboolean ostree_sysroot_initialize (OstreeSysroot *self,
GError **error);

_OSTREE_PUBLIC
gboolean ostree_sysroot_load (OstreeSysroot *self,
GCancellable *cancellable,
Expand Down Expand Up @@ -90,6 +97,10 @@ GFile * ostree_sysroot_get_deployment_origin_path (GFile *deployment_path);

_OSTREE_PUBLIC
gboolean ostree_sysroot_lock (OstreeSysroot *self, GError **error);

_OSTREE_PUBLIC
gboolean ostree_sysroot_lock_with_mount_namespace (OstreeSysroot *self, GError **error);

_OSTREE_PUBLIC
gboolean ostree_sysroot_try_lock (OstreeSysroot *self,
gboolean *out_acquired,
Expand Down
30 changes: 30 additions & 0 deletions src/ostree/ot-main.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include <stdlib.h>
#include <string.h>
#include <sys/statvfs.h>

#include "ot-main.h"
#include "ostree.h"
Expand Down Expand Up @@ -423,10 +424,39 @@ ostree_admin_option_context_parse (GOptionContext *context,
sysroot_path = g_file_new_for_path (opt_sysroot);

g_autoptr(OstreeSysroot) sysroot = ostree_sysroot_new (sysroot_path);
if (!ostree_sysroot_initialize (sysroot, error))
return FALSE;
g_signal_connect (sysroot, "journal-msg", G_CALLBACK (on_sysroot_journal_msg), NULL);

if ((flags & OSTREE_ADMIN_BUILTIN_FLAG_UNLOCKED) == 0)
{
/* If we're requested to lock the sysroot, first find
* out if it's a read-only mount point, and if so,
* create a new mount namespace and tell the sysroot
* that we've done so. See the docs for
* ostree_sysroot_set_mount_namespace_in_use().
*/
int sysroot_fd = ostree_sysroot_get_fd (sysroot);
g_assert_cmpint (sysroot_fd, !=, -1);
struct stat stbuf;
if (!glnx_fstat (sysroot_fd, &stbuf, error))
return FALSE;
/* We really expect this to always be root but...maybe someone
* is running ostree as non-root on a guestmount or so.
*/
if (getuid () == stbuf.st_uid)
{
struct statvfs stvfs;
if (fstatvfs (sysroot_fd, &stvfs) < 0)
return glnx_throw_errno_prefix (error, "fstatvfs");
if (stvfs.f_flag & ST_RDONLY)
{
if (unshare (CLONE_NEWNS) < 0)
return glnx_throw_errno_prefix (error, "preparing writable sysroot: unshare (CLONE_NEWNS)");
ostree_sysroot_set_mount_namespace_in_use (sysroot);
}
}

/* Released when sysroot is finalized, or on process exit */
if (!ot_admin_sysroot_lock (sysroot, error))
return FALSE;
Expand Down
Loading

0 comments on commit e3e9e90

Please sign in to comment.