Skip to content

Commit

Permalink
lib/deploy: Add support for overlay initrds
Browse files Browse the repository at this point in the history
In FCOS and RHCOS, the need to configure software in the initramfs has
come up multiple times. Sometimes, using kernel arguments suffices.
Other times, it really must be a configuration file. Rebuilding the
initramfs on the client-side however is a costly operation. Not only
does it add complexity to the update workflow, it also erodes a lot of
the value obtained from using the baked "blessed" initramfs from the
tree itself.

One elegant way to address this is to allow specifying multiple
initramfses. This is supported by most bootloaders (notably GRUB) and
results in each initrd being overlayed on top of each other.

This patch allows libostree clients to leverage this so that they can
avoid regenerating the initramfs entirely. libostree itself is agnostic
as to what kind and how much data overlay initrds contain. It's up to
the clients to enforce such boundaries.

To implement this, we add a new ostree_sysroot_stage_overlay_initrd
which takes a file descriptor and returns a checksum. Then users can
pass these checksums when calling the deploy APIs via the new array
option `overlay_initrds`. We copy these files into `/boot` and add them
to the BLS as another `initrd` entry.
  • Loading branch information
jlebon committed Sep 28, 2020
1 parent 390e315 commit 83c8505
Show file tree
Hide file tree
Showing 11 changed files with 390 additions and 8 deletions.
1 change: 1 addition & 0 deletions apidoc/ostree-sections.txt
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@ ostree_sysroot_write_deployments_with_options
ostree_sysroot_write_origin_file
ostree_sysroot_stage_tree
ostree_sysroot_stage_tree_with_options
ostree_sysroot_stage_overlay_initrd
ostree_sysroot_deploy_tree
ostree_sysroot_deploy_tree_with_options
ostree_sysroot_get_merge_deployment
Expand Down
1 change: 1 addition & 0 deletions src/libostree/libostree-devel.sym
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ global:
ostree_bootconfig_parser_set_overlay_initrds;
ostree_sysroot_deploy_tree_with_options;
ostree_sysroot_stage_tree_with_options;
ostree_sysroot_stage_overlay_initrd;
} LIBOSTREE_2020.4;

/* Stub section for the stable release *after* this development one; don't
Expand Down
9 changes: 9 additions & 0 deletions src/libostree/ostree-deployment-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ G_BEGIN_DECLS
* @origin: How to construct an upgraded version of this tree
* @unlocked: The unlocked state
* @staged: TRUE iff this deployment is staged
* @overlay_initrds: Checksums of staged additional initrds for this deployment
* @overlay_initrds_id: Unique ID generated from initrd checksums; used to compare deployments
*/
struct _OstreeDeployment
{
Expand All @@ -52,8 +54,15 @@ struct _OstreeDeployment
GKeyFile *origin;
OstreeDeploymentUnlockedState unlocked;
gboolean staged;
char **overlay_initrds;
char *overlay_initrds_id;
};

void _ostree_deployment_set_bootcsum (OstreeDeployment *self, const char *bootcsum);

void _ostree_deployment_set_overlay_initrds (OstreeDeployment *self,
char **overlay_initrds);

char** _ostree_deployment_get_overlay_initrds (OstreeDeployment *self);

G_END_DECLS
32 changes: 32 additions & 0 deletions src/libostree/ostree-deployment.c
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,34 @@ _ostree_deployment_set_bootcsum (OstreeDeployment *self,
self->bootcsum = g_strdup (bootcsum);
}

void
_ostree_deployment_set_overlay_initrds (OstreeDeployment *self,
char **overlay_initrds)
{
g_clear_pointer (&self->overlay_initrds, g_strfreev);
g_clear_pointer (&self->overlay_initrds_id, g_free);

if (!overlay_initrds || g_strv_length (overlay_initrds) == 0)
return;

/* Generate a unique ID representing this combination of overlay initrds. This is so that
* ostree_sysroot_write_deployments_with_options() can easily compare initrds when
* comparing deployments for whether a bootswap is necessary. We could be fancier here but
* meh... this works. */
g_autoptr(GString) id = g_string_new (NULL);
for (char **it = overlay_initrds; it && *it; it++)
g_string_append (id, *it);

self->overlay_initrds = g_strdupv (overlay_initrds);
self->overlay_initrds_id = g_string_free (g_steal_pointer (&id), FALSE);
}

char**
_ostree_deployment_get_overlay_initrds (OstreeDeployment *self)
{
return self->overlay_initrds;
}

/**
* ostree_deployment_clone:
* @self: Deployment
Expand All @@ -175,6 +203,8 @@ ostree_deployment_clone (OstreeDeployment *self)
new_bootconfig = ostree_bootconfig_parser_clone (self->bootconfig);
ostree_deployment_set_bootconfig (ret, new_bootconfig);

_ostree_deployment_set_overlay_initrds (ret, self->overlay_initrds);

if (self->origin)
{
g_autoptr(GKeyFile) new_origin = NULL;
Expand Down Expand Up @@ -238,6 +268,8 @@ ostree_deployment_finalize (GObject *object)
g_free (self->bootcsum);
g_clear_object (&self->bootconfig);
g_clear_pointer (&self->origin, g_key_file_unref);
g_strfreev (self->overlay_initrds);
g_free (self->overlay_initrds_id);

G_OBJECT_CLASS (ostree_deployment_parent_class)->finalize (object);
}
Expand Down
43 changes: 43 additions & 0 deletions src/libostree/ostree-sysroot-cleanup.c
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ cleanup_old_deployments (OstreeSysroot *self,
g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
g_autoptr(GHashTable) active_boot_checksums =
g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
g_autoptr(GHashTable) active_overlay_initrds =
g_hash_table_new (g_str_hash, g_str_equal); /* borrows from deployment's bootconfig */
for (guint i = 0; i < self->deployments->len; i++)
{
OstreeDeployment *deployment = self->deployments->pdata[i];
Expand All @@ -306,6 +308,11 @@ cleanup_old_deployments (OstreeSysroot *self,
/* Transfer ownership */
g_hash_table_replace (active_deployment_dirs, deployment_path, deployment_path);
g_hash_table_replace (active_boot_checksums, bootcsum, bootcsum);

OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment);
char **initrds = ostree_bootconfig_parser_get_overlay_initrds (bootconfig);
for (char **it = initrds; it && *it; it++)
g_hash_table_add (active_overlay_initrds, (char*)glnx_basename (*it));
}

/* Find all deployment directories, both active and inactive */
Expand Down Expand Up @@ -349,6 +356,42 @@ cleanup_old_deployments (OstreeSysroot *self,
return FALSE;
}

/* Clean up overlay initrds */
glnx_autofd int overlays_dfd =
glnx_opendirat_with_errno (self->sysroot_fd, _OSTREE_SYSROOT_INITRAMFS_OVERLAYS, FALSE);
if (overlays_dfd < 0)
{
if (errno != ENOENT)
return glnx_throw_errno_prefix (error, "open(initrd_overlays)");
}
else
{
g_autoptr(GPtrArray) initrds_to_delete = g_ptr_array_new_with_free_func (g_free);
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
if (!glnx_dirfd_iterator_init_at (overlays_dfd, ".", TRUE, &dfd_iter, error))
return FALSE;
while (TRUE)
{
struct dirent *dent;
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
return FALSE;
if (dent == NULL)
break;

/* there shouldn't be other file types there, but let's be conservative */
if (dent->d_type != DT_REG)
continue;

if (!g_hash_table_lookup (active_overlay_initrds, dent->d_name))
g_ptr_array_add (initrds_to_delete, g_strdup (dent->d_name));
}
for (guint i = 0; i < initrds_to_delete->len; i++)
{
if (!ot_ensure_unlinked_at (overlays_dfd, initrds_to_delete->pdata[i], error))
return FALSE;
}
}

return TRUE;
}

Expand Down
112 changes: 112 additions & 0 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -1859,6 +1859,47 @@ install_deployment_kernel (OstreeSysroot *sysroot,
}
}

g_autoptr(GPtrArray) overlay_initrds = NULL;
for (char **it = _ostree_deployment_get_overlay_initrds (deployment); it && *it; it++)
{
char *checksum = *it;

/* Overlay initrds are not part of the bootcsum dir; they're not part of the tree
* proper. Instead they're in /boot/ostree/initramfs-overlays/ named by their csum.
* Doing it this way allows sharing the same bootcsum dir for multiple deployments
* with the only change being in overlay initrds (or conversely, the same overlay
* across different boocsums). Eventually, it'd be nice to have an OSTree repo in
* /boot itself and drop the boocsum dir concept entirely. */

g_autofree char *destpath =
g_strdup_printf ("/" _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS "/%s.img", checksum);
const char *rel_destpath = destpath + 1;

/* lazily allocate array and create dir so we don't pollute /boot if not needed */
if (overlay_initrds == NULL)
{
overlay_initrds = g_ptr_array_new_with_free_func (g_free);

if (!glnx_shutil_mkdir_p_at (boot_dfd, _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS,
0755, cancellable, error))
return FALSE;
}

if (!glnx_fstatat_allow_noent (boot_dfd, rel_destpath, NULL, 0, error))
return FALSE;
if (errno == ENOENT)
{
g_autofree char *srcpath =
g_strdup_printf (_OSTREE_SYSROOT_RUNSTATE_STAGED_INITRDS_DIR "/%s", checksum);
if (!install_into_boot (repo, sepolicy, AT_FDCWD, srcpath, boot_dfd, rel_destpath,
cancellable, error))
return FALSE;
}

/* these are used lower down to populate the bootconfig */
g_ptr_array_add (overlay_initrds, g_steal_pointer (&destpath));
}

g_autofree char *contents = NULL;
if (!glnx_fstatat_allow_noent (deployment_dfd, "usr/lib/os-release", &stbuf, 0, error))
return FALSE;
Expand Down Expand Up @@ -1938,6 +1979,12 @@ install_deployment_kernel (OstreeSysroot *sysroot,
g_autofree char * initrd_boot_relpath =
g_strconcat ("/", bootcsumdir, "/", kernel_layout->initramfs_namever, NULL);
ostree_bootconfig_parser_set (bootconfig, "initrd", initrd_boot_relpath);

if (overlay_initrds)
{
g_ptr_array_add (overlay_initrds, NULL);
ostree_bootconfig_parser_set_overlay_initrds (bootconfig, (char**)overlay_initrds->pdata);
}
}
else
{
Expand Down Expand Up @@ -2135,6 +2182,10 @@ deployment_bootconfigs_equal (OstreeRepo *repo,
if (strcmp (a_bootcsum, b_bootcsum) != 0)
return FALSE;

/* same initrd overlays? */
if (g_strcmp0 (a->overlay_initrds_id, b->overlay_initrds_id) != 0)
return FALSE;

/* same kargs? */
g_autofree char *a_boot_options_without_ostree = get_deployment_nonostree_kargs (a);
g_autofree char *b_boot_options_without_ostree = get_deployment_nonostree_kargs (b);
Expand Down Expand Up @@ -2722,6 +2773,7 @@ sysroot_initialize_deployment (OstreeSysroot *self,

_ostree_deployment_set_bootcsum (new_deployment, kernel_layout->bootcsum);
_ostree_deployment_set_bootconfig_from_kargs (new_deployment, opts ? opts->override_kernel_argv : NULL);
_ostree_deployment_set_overlay_initrds (new_deployment, opts ? opts->overlay_initrds : NULL);

if (!prepare_deployment_etc (self, repo, new_deployment, deployment_dfd,
cancellable, error))
Expand Down Expand Up @@ -2991,6 +3043,63 @@ _ostree_sysroot_deserialize_deployment_from_variant (GVariant *v,
}


/**
* ostree_sysroot_stage_overlay_initrd:
* @self: Sysroot
* @fd: (transfer none): File descriptor to overlay initrd
* @out_checksum: (out) (transfer full): Overlay initrd checksum
* @cancellable: Cancellable
* @error: Error
*
* Stage an overlay initrd to be used in an upcoming deployment. Returns a checksum which
* can be passed to ostree_sysroot_deploy_tree_with_options() or
* ostree_sysroot_stage_tree_with_options() via the `overlay_initrds` array option.
*
* Since: 2020.7
*/
gboolean
ostree_sysroot_stage_overlay_initrd (OstreeSysroot *self,
int fd,
char **out_checksum,
GCancellable *cancellable,
GError **error)
{
g_return_val_if_fail (fd != -1, FALSE);
g_return_val_if_fail (out_checksum != NULL, FALSE);

if (!glnx_shutil_mkdir_p_at (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED_INITRDS_DIR,
0755, cancellable, error))
return FALSE;

glnx_autofd int staged_initrds_dfd = -1;
if (!glnx_opendirat (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED_INITRDS_DIR, FALSE,
&staged_initrds_dfd, error))
return FALSE;

g_auto(GLnxTmpfile) overlay_initrd = { 0, };
if (!glnx_open_tmpfile_linkable_at (staged_initrds_dfd, ".", O_WRONLY | O_CLOEXEC,
&overlay_initrd, error))
return FALSE;

char checksum[_OSTREE_SHA256_STRING_LEN+1];
{
g_autoptr(GOutputStream) output = g_unix_output_stream_new (overlay_initrd.fd, FALSE);
g_autoptr(GInputStream) input = g_unix_input_stream_new (fd, FALSE);
g_autofree guchar *digest = NULL;
if (!ot_gio_splice_get_checksum (output, input, &digest, cancellable, error))
return FALSE;
ot_bin2hex (checksum, (guint8*)digest, _OSTREE_SHA256_DIGEST_LEN);
}

if (!glnx_link_tmpfile_at (&overlay_initrd, GLNX_LINK_TMPFILE_REPLACE,
staged_initrds_dfd, checksum, error))
return FALSE;

*out_checksum = g_strdup (checksum);
return TRUE;
}


/**
* ostree_sysroot_stage_tree:
* @self: Sysroot
Expand Down Expand Up @@ -3122,6 +3231,9 @@ ostree_sysroot_stage_tree_with_options (OstreeSysroot *self,
if (opts && opts->override_kernel_argv)
g_variant_builder_add (builder, "{sv}", "kargs",
g_variant_new_strv ((const char *const*)opts->override_kernel_argv, -1));
if (opts && opts->overlay_initrds)
g_variant_builder_add (builder, "{sv}", "overlay-initrds",
g_variant_new_strv ((const char *const*)opts->overlay_initrds, -1));

const char *parent = dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED));
if (!glnx_shutil_mkdir_p_at (AT_FDCWD, parent, 0755, cancellable, error))
Expand Down
4 changes: 4 additions & 0 deletions src/libostree/ostree-sysroot-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,14 @@ struct OstreeSysroot {
/* We keep some transient state in /run */
#define _OSTREE_SYSROOT_RUNSTATE_STAGED "/run/ostree/staged-deployment"
#define _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED "/run/ostree/staged-deployment-locked"
#define _OSTREE_SYSROOT_RUNSTATE_STAGED_INITRDS_DIR "/run/ostree/staged-initrds/"
#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_DIR "/run/ostree/deployment-state/"
#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT "unlocked-development"
#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_TRANSIENT "unlocked-transient"

#define _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS "ostree/initramfs-overlays"
#define _OSTREE_SYSROOT_INITRAMFS_OVERLAYS "boot/" _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS

gboolean
_ostree_sysroot_ensure_writable (OstreeSysroot *self,
GError **error);
Expand Down
22 changes: 22 additions & 0 deletions src/libostree/ostree-sysroot.c
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,24 @@ list_deployments_process_one_boot_entry (OstreeSysroot *self,
return FALSE;

ostree_deployment_set_bootconfig (deployment, config);
char **overlay_initrds = ostree_bootconfig_parser_get_overlay_initrds (config);
g_autoptr(GPtrArray) initrds_chksums = NULL;
for (char **it = overlay_initrds; it && *it; it++)
{
const char *basename = glnx_basename (*it);
if (strlen (basename) != (_OSTREE_SHA256_STRING_LEN + strlen (".img")))
return glnx_throw (error, "Malformed overlay initrd filename: %s", basename);

if (!initrds_chksums) /* lazy init */
initrds_chksums = g_ptr_array_new_full (g_strv_length (overlay_initrds), g_free);
g_ptr_array_add (initrds_chksums, g_strndup (basename, _OSTREE_SHA256_STRING_LEN));
}

if (initrds_chksums)
{
g_ptr_array_add (initrds_chksums, NULL);
_ostree_deployment_set_overlay_initrds (deployment, (char**)initrds_chksums->pdata);
}

g_ptr_array_add (inout_deployments, g_object_ref (deployment));
return TRUE;
Expand Down Expand Up @@ -967,8 +985,10 @@ _ostree_sysroot_reload_staged (OstreeSysroot *self,
/* Parse it */
g_autoptr(GVariant) target = NULL;
g_autofree char **kargs = NULL;
g_autofree char **overlay_initrds = NULL;
g_variant_dict_lookup (staged_deployment_dict, "target", "@a{sv}", &target);
g_variant_dict_lookup (staged_deployment_dict, "kargs", "^a&s", &kargs);
g_variant_dict_lookup (staged_deployment_dict, "overlay-initrds", "^a&s", &overlay_initrds);
if (target)
{
g_autoptr(OstreeDeployment) staged =
Expand All @@ -980,6 +1000,8 @@ _ostree_sysroot_reload_staged (OstreeSysroot *self,
if (!load_origin (self, staged, NULL, error))
return FALSE;

_ostree_deployment_set_overlay_initrds (staged, overlay_initrds);

self->staged_deployment = g_steal_pointer (&staged);
self->staged_deployment_data = g_steal_pointer (&staged_deployment_data);
/* We set this flag for ostree_deployment_is_staged() because that API
Expand Down
10 changes: 9 additions & 1 deletion src/libostree/ostree-sysroot.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,19 @@ gboolean ostree_sysroot_write_deployments_with_options (OstreeSysroot *self,
GCancellable *cancellable,
GError **error);

_OSTREE_PUBLIC
gboolean ostree_sysroot_stage_overlay_initrd (OstreeSysroot *self,
int fd,
char **out_checksum,
GCancellable *cancellable,
GError **error);

typedef struct {
gboolean unused_bools[8];
int unused_ints[8];
char **override_kernel_argv;
gpointer unused_ptrs[7];
char **overlay_initrds;
gpointer unused_ptrs[6];
} OstreeSysrootDeployTreeOpts;

_OSTREE_PUBLIC
Expand Down
Loading

0 comments on commit 83c8505

Please sign in to comment.