Skip to content

Commit

Permalink
prepare-root: Add support for root.transient
Browse files Browse the repository at this point in the history
Closes: ostreedev#3113

It'd greatly improve compatibility with things like RPMs that install
in `/opt` if we supported a full "original docker" style model where
`/` is a transient overlayfs.  We'd still keep our semantics for `/etc`
and `/var` by default, but e.g. we'd stop recommending
`/opt` ➡️ `/var/opt`, in this model,
so `/opt` would be on the overlayfs.

Note this all aligns with composefs, where we'd actually be making
`/` a *read-only* overlayfs by default; it'd be really nice of course
to *implement* this by just making the composefs overlayfs writable,
but I am not sure we can hard require composefs for this right now.

So this change adds support for `root.transient = true`
in `/usr/lib/ostree/prepare-root.conf`.

The major downside is that people could be surprised if files they
write to e.g. `/opt` don't persist across upgrades.  But, that's
already again how it works since Docker started.

Note as part of the implementation of this, we need to add a whole
new "backing" directory distinct from the deployment directories.

(Tangentially related to this, it's tempting to switch to always
 using a *read-only* overlay mount by default.
  • Loading branch information
cgwalters committed Dec 7, 2023
1 parent 9887658 commit 57f0703
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 34 deletions.
2 changes: 1 addition & 1 deletion src/libostree/ostree-sysroot-cleanup.c
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ gboolean
_ostree_sysroot_rmrf_deployment (OstreeSysroot *self, OstreeDeployment *deployment,
GCancellable *cancellable, GError **error)
{
g_autofree char *backing_relpath =_ostree_sysroot_get_deployment_backing_relpath (deployment);
g_autofree char *backing_relpath = _ostree_sysroot_get_deployment_backing_relpath (deployment);
g_autofree char *origin_relpath = ostree_deployment_get_origin_relpath (deployment);
g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (self, deployment);
struct stat stbuf;
Expand Down
50 changes: 50 additions & 0 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -3078,6 +3078,10 @@ sysroot_initialize_deployment (OstreeSysroot *self, const char *osname, const ch
if (!require_stateroot (self, osname, error))
return FALSE;

g_autofree char *stateroot_backing = g_strdup_printf ("ostree/deploy/%s/backing", osname);
if (!glnx_shutil_mkdir_p_at (self->sysroot_fd, stateroot_backing, 0700, cancellable, error))
return glnx_prefix_error (error, "Creating backing directory");

OstreeRepo *repo = ostree_sysroot_repo (self);

gint new_deployserial;
Expand Down Expand Up @@ -3295,6 +3299,49 @@ sysroot_finalize_selinux_policy (int deployment_dfd, GError **error)
}
#endif /* HAVE_SELINUX */

static gboolean
sysroot_initialize_deployment_backing (OstreeSysroot *self, OstreeDeployment *deployment,
OstreeSePolicy *sepolicy, GError **error)
{
GLNX_AUTO_PREFIX_ERROR ("Preparing deployment backing dir", error);
g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (self, deployment);
g_autofree char *backing_relpath = _ostree_sysroot_get_deployment_backing_relpath (deployment);
struct stat stbuf;

if (!glnx_fstatat (self->sysroot_fd, deployment_path, &stbuf, AT_SYMLINK_NOFOLLOW, error))
return FALSE;

// Create the "backing" directory with additional data */
if (!glnx_ensure_dir (self->sysroot_fd, backing_relpath, 0700, error))
return glnx_prefix_error (error, "Creating backing dir");

// The root-transient holds overlayfs directories for the root
g_autofree char *rootovldir
= g_build_filename (backing_relpath, OSTREE_DEPLOYMENT_ROOT_TRANSIENT_DIR, NULL);
if (!glnx_ensure_dir (self->sysroot_fd, rootovldir, 0700, error))
return glnx_prefix_error (error, "Creating root ovldir");

// The overlayfs work (subdirectory of root-transient)
g_autofree char *workdir = g_build_filename (rootovldir, "work", NULL);
if (!glnx_ensure_dir (self->sysroot_fd, workdir, 0700, error))
return glnx_prefix_error (error, "Creating work dir");

// Create the overlayfs upper; this needs to have the same mode and SELinux label as the root
{
g_auto (OstreeSepolicyFsCreatecon) con = {
0,
};

if (!_ostree_sepolicy_preparefscreatecon (&con, sepolicy, "/", stbuf.st_mode, error))
return glnx_prefix_error (error, "Looking up SELinux label for /");
g_autofree char *upperdir = g_build_filename (rootovldir, "upper", NULL);
if (!glnx_ensure_dir (self->sysroot_fd, upperdir, stbuf.st_mode, error))
return glnx_prefix_error (error, "Creating upper dir");
}

return TRUE;
}

static gboolean
sysroot_finalize_deployment (OstreeSysroot *self, OstreeDeployment *deployment,
OstreeDeployment *merge_deployment, GCancellable *cancellable,
Expand Down Expand Up @@ -3360,6 +3407,9 @@ sysroot_finalize_deployment (OstreeSysroot *self, OstreeDeployment *deployment,
if (!selinux_relabel_var_if_needed (self, sepolicy, os_deploy_dfd, cancellable, error))
return FALSE;

if (!sysroot_initialize_deployment_backing (self, deployment, sepolicy, error))
return FALSE;

/* Rewrite the origin using the final merged selinux config, just to be
* conservative about getting the right labels.
*/
Expand Down
8 changes: 3 additions & 5 deletions src/libostree/ostree-sysroot.c
Original file line number Diff line number Diff line change
Expand Up @@ -1971,13 +1971,11 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osna
char *
_ostree_sysroot_get_deployment_backing_relpath (OstreeDeployment *deployment)
{
return g_strdup_printf ("ostree/deploy/%s/deploy/%s.%d%s",
ostree_deployment_get_osname (deployment), ostree_deployment_get_csum (deployment),
ostree_deployment_get_deployserial (deployment),
OSTREE_DEPLOYMENT_BACKING_EXT);
return g_strdup_printf (
"ostree/deploy/%s/backing/%s.%d", ostree_deployment_get_osname (deployment),
ostree_deployment_get_csum (deployment), ostree_deployment_get_deployserial (deployment));
}


/* Deploy a copy of @target_deployment */
static gboolean
clone_deployment (OstreeSysroot *sysroot, OstreeDeployment *target_deployment,
Expand Down
6 changes: 5 additions & 1 deletion src/libotcore/otcore.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ GKeyFile *otcore_load_config (int rootfs, const char *filename, GError **error);
#define OTCORE_RUN_OSTREE_PRIVATE "/run/ostree/.private"

// The directory holding extra/backing data for a deployment, such as overlayfs workdirs
#define OSTREE_DEPLOYMENT_BACKING_EXT ".backing"
#define OSTREE_DEPLOYMENT_BACKING_DIR "backing"
// The directory holding the root overlayfs
#define OSTREE_DEPLOYMENT_ROOT_TRANSIENT_DIR "root-transient"

// The name of the composefs metadata root
#define OSTREE_COMPOSEFS_NAME ".ostree.cfs"
Expand All @@ -73,6 +75,8 @@ GKeyFile *otcore_load_config (int rootfs, const char *filename, GError **error);
// This key if present contains the public key successfully used
// to verify the signature.
#define OTCORE_RUN_BOOTED_KEY_COMPOSEFS_SIGNATURE "composefs.signed"
// This key will be present if the root is transient
#define OTCORE_RUN_BOOTED_KEY_ROOT_TRANSIENT "root.transient"
// This key will be present if the sysroot-ro flag was found
#define OTCORE_RUN_BOOTED_KEY_SYSROOT_RO "sysroot-ro"

Expand Down
53 changes: 26 additions & 27 deletions src/switchroot/ostree-prepare-root.c
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,9 @@ main (int argc, char *argv[])
gboolean sysroot_readonly = FALSE;
gboolean root_transient = FALSE;

if (!ot_keyfile_get_boolean_with_default (config, ROOT_KEY, TRANSIENT_KEY,
FALSE, &root_transient, &error))
return FALSE;
if (!ot_keyfile_get_boolean_with_default (config, ROOT_KEY, TRANSIENT_KEY, FALSE, &root_transient,
&error))
return FALSE;

// We always parse the composefs config, because we want to detect and error
// out if it's enabled, but not supported at compile time.
Expand Down Expand Up @@ -522,36 +522,35 @@ main (int argc, char *argv[])
errx (EXIT_FAILURE, "composefs: enabled at runtime, but support is not compiled in");
#endif

if (!using_composefs)
if (root_transient)
{
/* if (using_composefs)
* TODO: Add support to libcomposefs to mount writably; for now we end up with two overlayfs
* which is a bit silly.
*/

g_autofree char *backingdir = g_strdup_printf ("../../backing/%s", deploy_directory_name);
g_autofree char *workdir
= g_build_filename (backingdir, OSTREE_DEPLOYMENT_ROOT_TRANSIENT_DIR, "work", NULL);
g_autofree char *upperdir
= g_build_filename (backingdir, OSTREE_DEPLOYMENT_ROOT_TRANSIENT_DIR, "upper", NULL);
g_autofree char *ovl_options
= g_strdup_printf ("lowerdir=.,upperdir=%s,workdir=%s", upperdir, workdir);
if (mount ("overlay", TMP_SYSROOT, "overlay", MS_SILENT, ovl_options) < 0)
err (EXIT_FAILURE, "failed to mount transient root overlayfs");
g_print ("Enabled transient /\n");
}
else if (!using_composefs)
{
g_print ("Using legacy ostree bind mount for /\n");
/* The deploy root starts out bind mounted to sysroot.tmp */
if (mount (deploy_path, TMP_SYSROOT, NULL, MS_BIND | MS_SILENT, NULL) < 0)
err (EXIT_FAILURE, "failed to make initial bind mount %s", deploy_path);
}

if (root_transient)
{
const char *root_transient_dirname = "root-transient";
// Create the "backing" directory with additional data */
g_autofree char *backingdir = g_strdup_printf ("../%s%s", deploy_directory_name, OSTREE_DEPLOYMENT_BACKING_EXT);
if (!glnx_ensure_dir (AT_FDCWD, backingdir, 0700, &error))
err (EXIT_FAILURE, "%s", error->message);
// The root-transient holds overlayfs directories for the root
g_autofree char *rootovldir = g_build_filename (backingdir, root_transient_dirname, NULL);
if (!glnx_ensure_dir (AT_FDCWD, rootovldir, 0700, &error))
err (EXIT_FAILURE, "%s", error->message);
// Create the overlayfs upper
g_autofree char *upperdir = g_build_filename (backingdir, root_transient_dirname, "upper", NULL);
if (!glnx_ensure_dir (AT_FDCWD, upperdir, 0700, &error))
err (EXIT_FAILURE, "%s", error->message);
// And the overlayfs work
g_autofree char *workdir = g_build_filename (backingdir, root_transient_dirname, "work", NULL);
if (!glnx_ensure_dir (AT_FDCWD, workdir, 0700, &error))
err (EXIT_FAILURE, "%s", error->message);
g_autofree char *ovl_options = g_strdup_printf ("lowerdir=" TMP_SYSROOT ",upperdir=%s,workdir=%s", upperdir, workdir);
if (mount ("overlay", ".", "overlay", MS_SILENT, ovl_options) < 0)
err (EXIT_FAILURE, "failed to mount transient root overlayfs");
}
/* Pass on the state */
g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_ROOT_TRANSIENT,
g_variant_new_boolean (root_transient));

/* This will result in a system with /sysroot read-only. Thus, two additional
* writable bind-mounts (for /etc and /var) are required later on. */
Expand Down

0 comments on commit 57f0703

Please sign in to comment.