mirror of
https://github.com/ostreedev/ostree.git
synced 2025-03-11 20:58:45 +03:00
Support mounting /sysroot (and /boot) read-only
We want to support extending the read-only state to cover `/sysroot` and `/boot`, since conceptually all of the data there should only be written via libostree. Or at least for `/boot` should *mostly* just be written by ostree. This change needs to be opt-in though to avoid breaking anyone. Add a `sysroot/readonly` key to the repository config which instructs `ostree-remount.service` to ensure `/sysroot` is read-only. This requires a bit of a dance because `/sysroot` is actually the same filesystem as `/`; so we make `/etc` a writable bind mount in this case. We also need to handle `/var` in the "OSTree default" case of a bind mount; the systemd generator now looks at the writability state of `/sysroot` and uses that to determine whether it should have the `var.mount` unit happen before or after `ostree-remount.service.` Also add an API to instruct the libostree shared library that the caller has created a new mount namespace. This way we can freely remount read-write. This approach extends upon in a much better way previous work we did to support remounting `/boot` read-write. Closes: https://github.com/ostreedev/ostree/issues/1265
This commit is contained in:
parent
20daf96880
commit
5af403be0c
@ -55,7 +55,8 @@ ostree_remount_SOURCES = \
|
||||
src/switchroot/ostree-mount-util.h \
|
||||
src/switchroot/ostree-remount.c \
|
||||
$(NULL)
|
||||
ostree_remount_CPPFLAGS = $(AM_CPPFLAGS) -Isrc/switchroot
|
||||
ostree_remount_CPPFLAGS = $(AM_CPPFLAGS) $(OT_INTERNAL_GIO_UNIX_CFLAGS) -Isrc/switchroot -I$(srcdir)/libglnx
|
||||
ostree_remount_LDADD = $(AM_LDFLAGS) $(OT_INTERNAL_GIO_UNIX_LIBS) libglnx.la
|
||||
|
||||
if BUILDOPT_SYSTEMD
|
||||
ostree_prepare_root_CPPFLAGS += -DHAVE_SYSTEMD=1
|
||||
|
@ -499,6 +499,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
|
||||
@ -508,6 +509,8 @@ ostree_sysroot_lock_async
|
||||
ostree_sysroot_lock_finish
|
||||
ostree_sysroot_unlock
|
||||
ostree_sysroot_unload
|
||||
ostree_sysroot_set_mount_namespace_in_use
|
||||
ostree_sysroot_is_booted
|
||||
ostree_sysroot_get_fd
|
||||
ostree_sysroot_ensure_initialized
|
||||
ostree_sysroot_get_bootversion
|
||||
|
@ -22,12 +22,12 @@ DefaultDependencies=no
|
||||
ConditionKernelCommandLine=ostree
|
||||
OnFailure=emergency.target
|
||||
Conflicts=umount.target
|
||||
After=-.mount
|
||||
# Run after core mounts
|
||||
After=-.mount var.mount
|
||||
After=systemd-remount-fs.service
|
||||
# But we run *before* most other core bootup services that need write access to /etc and /var
|
||||
Before=local-fs.target umount.target
|
||||
# Other early boot units that need to write to /var
|
||||
Before=systemd-random-seed.service plymouth-read-write.service systemd-journal-flush.service
|
||||
# tmpfiles.d usually needs write access to a few places
|
||||
Before=systemd-tmpfiles-setup.service
|
||||
|
||||
[Service]
|
||||
|
@ -19,6 +19,9 @@
|
||||
|
||||
/* Add new symbols here. Release commits should copy this section into -released.sym. */
|
||||
LIBOSTREE_2019.7 {
|
||||
ostree_sysroot_initialize;
|
||||
ostree_sysroot_is_booted;
|
||||
ostree_sysroot_set_mount_namespace_in_use;
|
||||
} LIBOSTREE_2019.6;
|
||||
|
||||
/* Stub section for the stable release *after* this development one; don't
|
||||
|
@ -28,6 +28,7 @@
|
||||
#ifdef HAVE_LIBMOUNT
|
||||
#include <libmount.h>
|
||||
#endif
|
||||
#include <sys/statvfs.h>
|
||||
#include <stdbool.h>
|
||||
#include "otutil.h"
|
||||
|
||||
@ -189,8 +190,6 @@ _ostree_impl_system_generator (const char *ostree_cmdline,
|
||||
"[Unit]\n"
|
||||
"Documentation=man:ostree(1)\n"
|
||||
"ConditionKernelCommandLine=!systemd.volatile\n"
|
||||
/* We need /sysroot mounted writable first */
|
||||
"After=ostree-remount.service\n"
|
||||
"Before=local-fs.target\n"
|
||||
"\n"
|
||||
"[Mount]\n"
|
||||
|
@ -455,6 +455,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.
|
||||
*/
|
||||
@ -535,7 +538,10 @@ _ostree_sysroot_cleanup_internal (OstreeSysroot *self,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SYSROOT (self), FALSE);
|
||||
g_return_val_if_fail (self->loaded, FALSE);
|
||||
g_return_val_if_fail (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_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");
|
||||
|
@ -56,6 +56,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.
|
||||
@ -806,6 +809,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);
|
||||
@ -2217,7 +2223,10 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_assert (self->loaded);
|
||||
g_assert (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_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
|
||||
@ -2374,7 +2383,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");
|
||||
}
|
||||
@ -2408,8 +2416,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)
|
||||
{
|
||||
@ -2716,6 +2726,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))
|
||||
@ -2817,6 +2830,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");
|
||||
@ -3043,6 +3059,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));
|
||||
|
||||
@ -3090,6 +3109,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;
|
||||
|
@ -40,6 +40,12 @@ typedef enum {
|
||||
OSTREE_SYSROOT_DEBUG_TEST_STAGED_PATH = 1 << 3,
|
||||
} OstreeSysrootDebugFlags;
|
||||
|
||||
typedef enum {
|
||||
OSTREE_SYSROOT_LOAD_STATE_NONE, /* ostree_sysroot_new() was called */
|
||||
OSTREE_SYSROOT_LOAD_STATE_INIT, /* We've loaded basic sysroot state and have an fd */
|
||||
OSTREE_SYSROOT_LOAD_STATE_LOADED, /* We've loaded all of the deployments */
|
||||
} OstreeSysrootLoadState;
|
||||
|
||||
/**
|
||||
* OstreeSysroot:
|
||||
* Internal struct
|
||||
@ -51,7 +57,8 @@ struct OstreeSysroot {
|
||||
int sysroot_fd;
|
||||
GLnxLockFile lock;
|
||||
|
||||
gboolean loaded;
|
||||
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 */
|
||||
/* The device/inode for /, used to detect booted deployment */
|
||||
dev_t root_device;
|
||||
@ -79,6 +86,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);
|
||||
|
@ -226,6 +226,33 @@ 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 when a function which requires
|
||||
* writable access is invoked, libostree will automatically remount as writable
|
||||
* any mount points on which it operates. This currently is just `/sysroot` and
|
||||
* `/boot`.
|
||||
*
|
||||
* If you invoke this function, it must be before ostree_sysroot_load(); it may
|
||||
* be invoked before or after ostree_sysroot_initialize().
|
||||
*
|
||||
* Since: 2019.7
|
||||
*/
|
||||
void
|
||||
ostree_sysroot_set_mount_namespace_in_use (OstreeSysroot *self)
|
||||
{
|
||||
/* Must be before we're loaded, as otherwise we'd have to close/reopen all our
|
||||
fds, e.g. the repo */
|
||||
g_return_if_fail (self->loadstate < OSTREE_SYSROOT_LOAD_STATE_LOADED);
|
||||
self->mount_namespace_in_use = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_get_path:
|
||||
* @self:
|
||||
@ -238,6 +265,7 @@ ostree_sysroot_get_path (OstreeSysroot *self)
|
||||
return self->path;
|
||||
}
|
||||
|
||||
/* Open a directory file descriptor for the sysroot if we haven't yet */
|
||||
static gboolean
|
||||
ensure_sysroot_fd (OstreeSysroot *self,
|
||||
GError **error)
|
||||
@ -251,13 +279,51 @@ ensure_sysroot_fd (OstreeSysroot *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Remount /sysroot read-write if necessary */
|
||||
gboolean
|
||||
_ostree_sysroot_ensure_writable (OstreeSysroot *self,
|
||||
GError **error)
|
||||
{
|
||||
/* Do nothing if no mount namespace is in use */
|
||||
if (!self->mount_namespace_in_use)
|
||||
return TRUE;
|
||||
|
||||
/* If a mount namespace is in use, ensure we're initialized */
|
||||
if (!ostree_sysroot_initialize (self, error))
|
||||
return FALSE;
|
||||
|
||||
/* If we aren't operating on a booted system, then we don't
|
||||
* do anything with mounts.
|
||||
*/
|
||||
if (!self->root_is_ostree_booted)
|
||||
return TRUE;
|
||||
|
||||
/* 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_RELATIME, "") < 0)
|
||||
return glnx_throw_errno_prefix (error, "Remounting /sysroot read-write");
|
||||
|
||||
/* Reopen our fd */
|
||||
glnx_close_fd (&self->sysroot_fd);
|
||||
if (!ensure_sysroot_fd (self, error))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_get_fd:
|
||||
* @self: Sysroot
|
||||
*
|
||||
* Access a file descriptor that refers to the root directory of this
|
||||
* sysroot. ostree_sysroot_load() must have been invoked prior to
|
||||
* calling this function.
|
||||
* Access a file descriptor that refers to the root directory of this sysroot.
|
||||
* ostree_sysroot_initialize() (or ostree_sysroot_load()) must have been invoked
|
||||
* prior to calling this function.
|
||||
*
|
||||
* Returns: A file descriptor valid for the lifetime of @self
|
||||
*/
|
||||
@ -268,6 +334,22 @@ ostree_sysroot_get_fd (OstreeSysroot *self)
|
||||
return self->sysroot_fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_is_booted:
|
||||
* @self: Sysroot
|
||||
*
|
||||
* Can only be invoked after `ostree_sysroot_initialize()`.
|
||||
*
|
||||
* Returns: %TRUE iff the sysroot points to a booted deployment
|
||||
* Since: 2019.7
|
||||
*/
|
||||
gboolean
|
||||
ostree_sysroot_is_booted (OstreeSysroot *self)
|
||||
{
|
||||
g_return_val_if_fail (self->loadstate >= OSTREE_SYSROOT_LOAD_STATE_INIT, FALSE);
|
||||
return self->root_is_ostree_booted;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_ostree_sysroot_bump_mtime (OstreeSysroot *self,
|
||||
GError **error)
|
||||
@ -798,6 +880,57 @@ ensure_repo (OstreeSysroot *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Since: 2019.7
|
||||
*/
|
||||
gboolean
|
||||
ostree_sysroot_initialize (OstreeSysroot *self,
|
||||
GError **error)
|
||||
{
|
||||
if (!ensure_sysroot_fd (self, error))
|
||||
return FALSE;
|
||||
|
||||
if (self->loadstate < OSTREE_SYSROOT_LOAD_STATE_INIT)
|
||||
{
|
||||
/* Gather some global state; first if we have the global ostree-booted flag;
|
||||
* we'll use it to sanity check that we found a booted deployment for example.
|
||||
* Second, we also find out whether sysroot == /.
|
||||
*/
|
||||
if (!glnx_fstatat_allow_noent (AT_FDCWD, "/run/ostree-booted", NULL, 0, error))
|
||||
return FALSE;
|
||||
const gboolean ostree_booted = (errno == 0);
|
||||
|
||||
{ struct stat root_stbuf;
|
||||
if (!glnx_fstatat (AT_FDCWD, "/", &root_stbuf, 0, error))
|
||||
return FALSE;
|
||||
self->root_device = root_stbuf.st_dev;
|
||||
self->root_inode = root_stbuf.st_ino;
|
||||
}
|
||||
|
||||
struct stat self_stbuf;
|
||||
if (!glnx_fstatat (AT_FDCWD, gs_file_get_path_cached (self->path), &self_stbuf, 0, error))
|
||||
return FALSE;
|
||||
|
||||
const gboolean root_is_sysroot =
|
||||
(self->root_device == self_stbuf.st_dev &&
|
||||
self->root_inode == self_stbuf.st_ino);
|
||||
|
||||
self->root_is_ostree_booted = (ostree_booted && root_is_sysroot);
|
||||
self->loadstate = OSTREE_SYSROOT_LOAD_STATE_INIT;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Reload the staged deployment from the file in /run */
|
||||
gboolean
|
||||
_ostree_sysroot_reload_staged (OstreeSysroot *self,
|
||||
@ -870,7 +1003,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
|
||||
@ -880,34 +1013,6 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self,
|
||||
if (!ensure_repo (self, error))
|
||||
return FALSE;
|
||||
|
||||
/* Gather some global state; first if we have the global ostree-booted flag;
|
||||
* we'll use it to sanity check that we found a booted deployment for example.
|
||||
* Second, we also find out whether sysroot == /.
|
||||
*/
|
||||
if (!self->loaded)
|
||||
{
|
||||
if (!glnx_fstatat_allow_noent (AT_FDCWD, "/run/ostree-booted", NULL, 0, error))
|
||||
return FALSE;
|
||||
const gboolean ostree_booted = (errno == 0);
|
||||
|
||||
{ struct stat root_stbuf;
|
||||
if (!glnx_fstatat (AT_FDCWD, "/", &root_stbuf, 0, error))
|
||||
return FALSE;
|
||||
self->root_device = root_stbuf.st_dev;
|
||||
self->root_inode = root_stbuf.st_ino;
|
||||
}
|
||||
|
||||
struct stat self_stbuf;
|
||||
if (!glnx_fstat (self->sysroot_fd, &self_stbuf, error))
|
||||
return FALSE;
|
||||
|
||||
const gboolean root_is_sysroot =
|
||||
(self->root_device == self_stbuf.st_dev &&
|
||||
self->root_inode == self_stbuf.st_ino);
|
||||
|
||||
self->root_is_ostree_booted = (ostree_booted && root_is_sysroot);
|
||||
}
|
||||
|
||||
int bootversion = 0;
|
||||
if (!read_current_bootversion (self, &bootversion, cancellable, error))
|
||||
return FALSE;
|
||||
@ -990,8 +1095,8 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self,
|
||||
ostree_deployment_set_index (deployment, i);
|
||||
}
|
||||
|
||||
/* Determine whether we're "physical" or not, the first time we initialize */
|
||||
if (!self->loaded)
|
||||
/* 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.
|
||||
@ -1009,13 +1114,14 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self,
|
||||
self->is_physical = TRUE;
|
||||
}
|
||||
/* Otherwise, the default is FALSE */
|
||||
|
||||
self->loadstate = OSTREE_SYSROOT_LOAD_STATE_LOADED;
|
||||
}
|
||||
|
||||
self->bootversion = bootversion;
|
||||
self->subbootversion = subbootversion;
|
||||
self->deployments = deployments;
|
||||
deployments = NULL; /* Transfer ownership */
|
||||
self->loaded = TRUE;
|
||||
self->loaded_ts = stbuf.st_mtim;
|
||||
|
||||
if (out_changed)
|
||||
@ -1044,7 +1150,7 @@ ostree_sysroot_get_subbootversion (OstreeSysroot *self)
|
||||
OstreeDeployment *
|
||||
ostree_sysroot_get_booted_deployment (OstreeSysroot *self)
|
||||
{
|
||||
g_return_val_if_fail (self->loaded, NULL);
|
||||
g_return_val_if_fail (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_LOADED, NULL);
|
||||
|
||||
return self->booted_deployment;
|
||||
}
|
||||
@ -1060,7 +1166,7 @@ ostree_sysroot_get_booted_deployment (OstreeSysroot *self)
|
||||
OstreeDeployment *
|
||||
ostree_sysroot_get_staged_deployment (OstreeSysroot *self)
|
||||
{
|
||||
g_return_val_if_fail (self->loaded, NULL);
|
||||
g_return_val_if_fail (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_LOADED, NULL);
|
||||
|
||||
return self->staged_deployment;
|
||||
}
|
||||
@ -1074,7 +1180,7 @@ ostree_sysroot_get_staged_deployment (OstreeSysroot *self)
|
||||
GPtrArray *
|
||||
ostree_sysroot_get_deployments (OstreeSysroot *self)
|
||||
{
|
||||
g_return_val_if_fail (self->loaded, NULL);
|
||||
g_return_val_if_fail (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_LOADED, NULL);
|
||||
|
||||
GPtrArray *copy = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
|
||||
for (guint i = 0; i < self->deployments->len; i++)
|
||||
@ -1163,8 +1269,8 @@ ostree_sysroot_get_repo (OstreeSysroot *self,
|
||||
* @self: Sysroot
|
||||
*
|
||||
* This function is a variant of ostree_sysroot_get_repo() that cannot fail, and
|
||||
* returns a cached repository. Can only be called after ostree_sysroot_load()
|
||||
* has been invoked successfully.
|
||||
* returns a cached repository. Can only be called after ostree_sysroot_initialize()
|
||||
* or ostree_sysroot_load() has been invoked successfully.
|
||||
*
|
||||
* Returns: (transfer none): The OSTree repository in sysroot @self.
|
||||
*
|
||||
@ -1173,7 +1279,7 @@ ostree_sysroot_get_repo (OstreeSysroot *self,
|
||||
OstreeRepo *
|
||||
ostree_sysroot_repo (OstreeSysroot *self)
|
||||
{
|
||||
g_return_val_if_fail (self->loaded, NULL);
|
||||
g_return_val_if_fail (self->loadstate >= OSTREE_SYSROOT_LOAD_STATE_LOADED, NULL);
|
||||
g_assert (self->repo);
|
||||
return self->repo;
|
||||
}
|
||||
@ -1368,6 +1474,10 @@ ostree_sysroot_lock (OstreeSysroot *self,
|
||||
{
|
||||
if (!ensure_sysroot_fd (self, error))
|
||||
return FALSE;
|
||||
|
||||
if (!_ostree_sysroot_ensure_writable (self, error))
|
||||
return FALSE;
|
||||
|
||||
return glnx_make_lock_file (self->sysroot_fd, OSTREE_SYSROOT_LOCKFILE,
|
||||
LOCK_EX, &self->lock, error);
|
||||
}
|
||||
@ -1391,12 +1501,14 @@ ostree_sysroot_try_lock (OstreeSysroot *self,
|
||||
gboolean *out_acquired,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(GError) local_error = NULL;
|
||||
|
||||
if (!ensure_sysroot_fd (self, error))
|
||||
return FALSE;
|
||||
|
||||
if (!_ostree_sysroot_ensure_writable (self, error))
|
||||
return FALSE;
|
||||
|
||||
/* Note use of LOCK_NB */
|
||||
g_autoptr(GError) local_error = NULL;
|
||||
if (!glnx_make_lock_file (self->sysroot_fd, OSTREE_SYSROOT_LOCKFILE,
|
||||
LOCK_EX | LOCK_NB, &self->lock, &local_error))
|
||||
{
|
||||
@ -1509,7 +1621,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);
|
||||
|
@ -41,12 +41,22 @@ 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
|
||||
gboolean ostree_sysroot_is_booted (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,
|
||||
@ -90,6 +100,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,
|
||||
|
@ -27,6 +27,7 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/statvfs.h>
|
||||
|
||||
#include "ot-main.h"
|
||||
#include "ostree.h"
|
||||
@ -434,10 +435,46 @@ 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 check if we're operating
|
||||
* on a booted (not physical) sysroot. Then find out if the /sysroot
|
||||
* subdir is 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().
|
||||
*
|
||||
* This is a conservative approach; we could just always
|
||||
* unshare() too.
|
||||
*/
|
||||
if (ostree_sysroot_is_booted (sysroot))
|
||||
{
|
||||
int sysroot_fd = ostree_sysroot_get_fd (sysroot);
|
||||
g_assert_cmpint (sysroot_fd, !=, -1);
|
||||
|
||||
glnx_autofd int sysroot_subdir_fd = glnx_opendirat_with_errno (sysroot_fd, "sysroot", TRUE);
|
||||
if (sysroot_subdir_fd < 0)
|
||||
{
|
||||
if (errno != ENOENT)
|
||||
return glnx_throw_errno_prefix (error, "opendirat");
|
||||
}
|
||||
else if (getuid () == 0)
|
||||
{
|
||||
struct statvfs stvfs;
|
||||
if (fstatvfs (sysroot_subdir_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;
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/mount.h>
|
||||
@ -37,10 +38,14 @@
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include "ostree-mount-util.h"
|
||||
#include "glnx-backport-autocleanups.h"
|
||||
|
||||
static void
|
||||
do_remount (const char *target)
|
||||
do_remount (const char *target,
|
||||
bool writable)
|
||||
{
|
||||
struct stat stbuf;
|
||||
if (lstat (target, &stbuf) < 0)
|
||||
@ -54,20 +59,41 @@ do_remount (const char *target)
|
||||
struct statvfs stvfsbuf;
|
||||
if (statvfs (target, &stvfsbuf) == -1)
|
||||
return;
|
||||
/* If no read-only flag, skip it */
|
||||
if ((stvfsbuf.f_flag & ST_RDONLY) == 0)
|
||||
|
||||
const bool currently_writable = ((stvfsbuf.f_flag & ST_RDONLY) == 0);
|
||||
if (writable == currently_writable)
|
||||
return;
|
||||
/* It's a mounted, read-only fs; remount it */
|
||||
if (mount (target, target, NULL, MS_REMOUNT | MS_SILENT, NULL) < 0)
|
||||
|
||||
int mnt_flags = MS_REMOUNT | MS_SILENT;
|
||||
if (!writable)
|
||||
mnt_flags |= MS_RDONLY;
|
||||
if (mount (target, target, NULL, mnt_flags, NULL) < 0)
|
||||
{
|
||||
/* Also ignore EINVAL - if the target isn't a mountpoint
|
||||
* already, then assume things are OK.
|
||||
*/
|
||||
if (errno != EINVAL)
|
||||
err (EXIT_FAILURE, "failed to remount %s", target);
|
||||
if (errno != EINVAL)
|
||||
err (EXIT_FAILURE, "failed to remount(%s) %s", writable ? "rw" : "ro", target);
|
||||
else
|
||||
return;
|
||||
}
|
||||
else
|
||||
printf ("Remounted: %s\n", target);
|
||||
|
||||
printf ("Remounted %s: %s\n", writable ? "rw" : "ro", target);
|
||||
}
|
||||
|
||||
static bool
|
||||
sysroot_is_configured_ro (void)
|
||||
{
|
||||
struct stat stbuf;
|
||||
static const char config_path[] = "/ostree/repo/config";
|
||||
if (stat (config_path, &stbuf) != 0)
|
||||
return false;
|
||||
|
||||
g_autoptr(GKeyFile) keyfile = g_key_file_new ();
|
||||
if (!g_key_file_load_from_file (keyfile, config_path, 0, NULL))
|
||||
return false;
|
||||
|
||||
return g_key_file_get_boolean (keyfile, "sysroot", "readonly", NULL);
|
||||
}
|
||||
|
||||
int
|
||||
@ -95,8 +121,38 @@ main(int argc, char *argv[])
|
||||
exit (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
do_remount ("/sysroot");
|
||||
do_remount ("/var");
|
||||
/* Query the repository configuration - this is an operating system builder
|
||||
* choice.
|
||||
* */
|
||||
const bool sysroot_readonly = sysroot_is_configured_ro ();
|
||||
|
||||
/* Mount the sysroot read-only if we're configured to do so.
|
||||
* Note we only get here if / is already writable.
|
||||
*/
|
||||
do_remount ("/sysroot", !sysroot_readonly);
|
||||
|
||||
if (sysroot_readonly)
|
||||
{
|
||||
/* Now, /etc is not normally a bind mount, but remounting the
|
||||
* sysroot above made it read-only since it's on the same filesystem.
|
||||
* Make it a self-bind mount, so we can then mount it read-write.
|
||||
*/
|
||||
if (mount ("/etc", "/etc", NULL, MS_BIND, NULL) < 0)
|
||||
err (EXIT_FAILURE, "failed to make /etc a bind mount");
|
||||
do_remount ("/etc", true);
|
||||
}
|
||||
|
||||
/* If /var was created as as an OSTree default bind mount (instead of being a separate filesystem)
|
||||
* then remounting the root mount read-only also remounted it.
|
||||
* So just like /etc, we need to make it read-write by default.
|
||||
* If it was a separate filesystem, we expect it to be writable anyways,
|
||||
* so it doesn't hurt to remount it if so.
|
||||
*
|
||||
* And if we started out with a writable system root, then we need
|
||||
* to ensure that the /var bind mount created by the systemd generator
|
||||
* is writable too.
|
||||
*/
|
||||
do_remount ("/var", true);
|
||||
|
||||
exit (EXIT_SUCCESS);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user