Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

prepare-root: Add support for root.transient #3114

Merged
merged 3 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/libostree/ostree-sysroot-cleanup.c
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +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 *origin_relpath = ostree_deployment_get_origin_relpath (deployment);
g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (self, deployment);
struct stat stbuf;
Expand All @@ -238,6 +239,12 @@ _ostree_sysroot_rmrf_deployment (OstreeSysroot *self, OstreeDeployment *deployme
/* This deployment wasn't referenced, so delete it */
if (!_ostree_linuxfs_fd_alter_immutable_flag (deployment_fd, FALSE, cancellable, error))
return FALSE;
/* Note we must delete the origin and backing directories first, as the "source of truth"
* is the deployment path. We don't currently have code that detects "orphaned"
* origin files or work directories.
*/
if (!glnx_shutil_rm_rf_at (self->sysroot_fd, backing_relpath, cancellable, error))
return FALSE;
if (!glnx_shutil_rm_rf_at (self->sysroot_fd, origin_relpath, cancellable, error))
return FALSE;
if (!glnx_shutil_rm_rf_at (self->sysroot_fd, deployment_path, cancellable, error))
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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this too.

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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be gated on root.transient?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right to call this out, but this was semi-intentional because one thing I want to do is change usroverlay to write here instead of the /var/tmp it does today - because that would just be better; the content would be clearly "lifecycle bound" to the deployment and we wouldn't need that hacky tmpfiles.d snippet we carry:

# https://github.com/ostreedev/ostree/issues/393
R! /var/tmp/ostree-unlock-ovl.*

(That said to do that note we'd need to also change some logic to ensure the overlay content for it is actually removed on reboot by default)

This all said, yes we could still create it dynamically in either case. I think we actually should have logic here in ostree to do what is being replicated in ostree-rs-ext and rpm-ostree around querying the initramfs config. Then we could drop the ex-integrity.composefs repo config.

return FALSE;

/* Rewrite the origin using the final merged selinux config, just to be
* conservative about getting the right labels.
*/
Expand Down
3 changes: 1 addition & 2 deletions src/libostree/ostree-sysroot-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,7 @@ gboolean _ostree_sysroot_boot_complete (OstreeSysroot *self, GCancellable *cance

OstreeDeployment *_ostree_sysroot_deserialize_deployment_from_variant (GVariant *v, GError **error);

char *_ostree_sysroot_get_origin_relpath (GFile *path, guint32 *out_device, guint64 *out_inode,
GCancellable *cancellable, GError **error);
char *_ostree_sysroot_get_deployment_backing_relpath (OstreeDeployment *deployment);

gboolean _ostree_sysroot_rmrf_deployment (OstreeSysroot *sysroot, OstreeDeployment *deployment,
GCancellable *cancellable, GError **error);
Expand Down
13 changes: 12 additions & 1 deletion src/libostree/ostree-sysroot.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
#include "ostree-repo-private.h"
#include "ostree-sepolicy-private.h"
#include "ostree-sysroot-private.h"
#include "ostree.h"
#include "otcore.h"

/**
* SECTION:ostree-sysroot
Expand Down Expand Up @@ -1965,6 +1965,17 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osna
return TRUE;
}

/* Return the sysroot-relative path to the "backing" directory of a deployment
* which can hold additional data.
*/
char *
_ostree_sysroot_get_deployment_backing_relpath (OstreeDeployment *deployment)
{
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
7 changes: 7 additions & 0 deletions src/libotcore/otcore.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ GKeyFile *otcore_load_config (int rootfs, const char *filename, GError **error);
// we make it with mode 0 (which requires CAP_DAC_OVERRIDE to pass through).
#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_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"
// The temporary directory used for the EROFS mount; it's in the .private directory
Expand All @@ -70,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
41 changes: 38 additions & 3 deletions src/switchroot/ostree-prepare-root.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@
#define SYSROOT_KEY "sysroot"
#define READONLY_KEY "readonly"

/* This key configures the / mount in the deployment root */
#define ROOT_KEY "root"
#define ETC_KEY "etc"
#define TRANSIENT_KEY "transient"

Expand Down Expand Up @@ -352,6 +354,11 @@ main (int argc, char *argv[])
errx (EXIT_FAILURE, "Failed to parse config: %s", error->message);

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;

// 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 All @@ -374,7 +381,12 @@ main (int argc, char *argv[])
const char *root_mountpoint = realpath (root_arg, NULL);
if (root_mountpoint == NULL)
err (EXIT_FAILURE, "realpath(\"%s\")", root_arg);
char *deploy_path = resolve_deploy_path (root_mountpoint);
g_autofree char *deploy_path = resolve_deploy_path (root_mountpoint);
const char *deploy_directory_name = glnx_basename (deploy_path);
// Note that realpath() should have stripped any trailing `/` which shouldn't
// be in the karg to start with, but we assert here to be sure we have a non-empty
// filename.
g_assert (deploy_directory_name && *deploy_directory_name);

if (mkdirat (AT_FDCWD, OTCORE_RUN_OSTREE, 0755) < 0)
err (EXIT_FAILURE, "Failed to create %s", OTCORE_RUN_OSTREE);
Expand Down Expand Up @@ -510,13 +522,36 @@ 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);
}

/* 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. */
if (sysroot_readonly)
Expand Down Expand Up @@ -548,7 +583,7 @@ main (int argc, char *argv[])
/* Prepare /etc.
* No action required if sysroot is writable. Otherwise, a bind-mount for
* the deployment needs to be created and remounted as read/write. */
if (sysroot_readonly || using_composefs)
if (sysroot_readonly || using_composefs || root_transient)
{
gboolean etc_transient = FALSE;
if (!ot_keyfile_get_boolean_with_default (config, ETC_KEY, TRANSIENT_KEY, FALSE,
Expand Down
Loading