Skip to content

Commit

Permalink
checkout: Add API to directly checkout composefs
Browse files Browse the repository at this point in the history
We were missing the simple, obvious API and CLI to go
from ostree commit -> composefs.

Internally, we had `ostree_repo_checkout_composefs`
with the right "shape" mostly, except it had more code
in the deploy path to turn that into a composefs.

Add a straightforward public API that does what
the deploy code did before, and then the old
API becomes an explicitly internal helper with an `_`
prefix.

Goals:

- Lead towards a composefs-oriented future
- This makes the composefs logic more testable directly

Signed-off-by: Colin Walters <[email protected]>
  • Loading branch information
cgwalters committed May 23, 2024
1 parent 64a09da commit c0a715f
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 99 deletions.
2 changes: 1 addition & 1 deletion Makefile-libostree.am
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ symbol_files = $(top_srcdir)/src/libostree/libostree-released.sym

# Uncomment this include when adding new development symbols.
if BUILDOPT_IS_DEVEL_BUILD
#symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym
symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym
endif

# http://blog.jgc.org/2007/06/escaping-comma-and-space-in-gnu-make.html
Expand Down
1 change: 1 addition & 0 deletions apidoc/ostree-sections.txt
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ OstreeRepoCheckoutOverwriteMode
ostree_repo_checkout_tree
ostree_repo_checkout_tree_at
ostree_repo_checkout_at
ostree_repo_checkout_composefs
ostree_repo_checkout_gc
ostree_repo_read_commit
OstreeRepoListObjectsFlags
Expand Down
5 changes: 5 additions & 0 deletions src/libostree/libostree-devel.sym
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
- uncomment the include in Makefile-libostree.am
*/

LIBOSTREE_2024.7 {
global:
ostree_repo_checkout_composefs;
} LIBOSTREE_2023.8;

/* Stub section for the stable release *after* this development one; don't
* edit this other than to update the year. This is just a copy/paste
* source. Replace $LASTSTABLE with the last stable version, and $NEWVERSION
Expand Down
100 changes: 100 additions & 0 deletions src/libostree/ostree-repo-checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,106 @@ checkout_tree_at_recurse (OstreeRepo *self, OstreeRepoCheckoutAtOptions *options
return TRUE;
}

#ifdef HAVE_COMPOSEFS
static gboolean
compare_verity_digests (GVariant *metadata_composefs, const guchar *fsverity_digest, GError **error)
{
const guchar *expected_digest;

if (metadata_composefs == NULL)
return TRUE;

if (g_variant_n_children (metadata_composefs) != OSTREE_SHA256_DIGEST_LEN)
return glnx_throw (error, "Expected composefs fs-verity in metadata has the wrong size");

expected_digest = g_variant_get_data (metadata_composefs);
if (memcmp (fsverity_digest, expected_digest, OSTREE_SHA256_DIGEST_LEN) != 0)
{
char actual_checksum[OSTREE_SHA256_STRING_LEN + 1];
char expected_checksum[OSTREE_SHA256_STRING_LEN + 1];

ostree_checksum_inplace_from_bytes (fsverity_digest, actual_checksum);
ostree_checksum_inplace_from_bytes (expected_digest, expected_checksum);

return glnx_throw (error,
"Generated composefs image digest (%s) doesn't match expected digest (%s)",
actual_checksum, expected_checksum);
}

return TRUE;
}

#endif

/**
* ostree_repo_checkout_composefs:
* @self: A repo
* @options: (nullable): Future expansion space; must currently be %NULL
* @destination_dfd: Parent directory fd
* @destination_path: Filename
* @checksum: OStree commit digest
* @cancellable: Cancellable
* @error: Error
*
* Create a composefs filesystem metadata blob from an OSTree commit.
*/
gboolean
ostree_repo_checkout_composefs (OstreeRepo *self, GVariant *options, int destination_dfd,
const char *destination_path, const char *checksum,
GCancellable *cancellable, GError **error)
{
#ifdef HAVE_COMPOSEFS
/* Force this for now */
g_assert (options == NULL);

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

g_autoptr (GVariant) commit_variant = NULL;
if (!ostree_repo_load_commit (self, checksum, &commit_variant, NULL, error))
return FALSE;

g_autoptr (GVariant) metadata = g_variant_get_child_value (commit_variant, 0);
g_autoptr (GVariant) metadata_composefs = g_variant_lookup_value (
metadata, OSTREE_COMPOSEFS_DIGEST_KEY_V0, G_VARIANT_TYPE_BYTESTRING);

g_autoptr (GFile) commit_root = NULL;
if (!ostree_repo_read_commit (self, checksum, &commit_root, NULL, cancellable, error))
return FALSE;

g_autoptr (OstreeComposefsTarget) target = ostree_composefs_target_new ();

if (!_ostree_repo_checkout_composefs (self, target, (OstreeRepoFile *)commit_root, cancellable,
error))
return FALSE;

g_autofree guchar *fsverity_digest = NULL;
if (!ostree_composefs_target_write (target, tmpf.fd, &fsverity_digest, cancellable, error))
return FALSE;

/* If the commit specified a composefs digest, verify it */
if (!compare_verity_digests (metadata_composefs, fsverity_digest, error))
return FALSE;

if (!glnx_fchmod (tmpf.fd, 0644, error))
return FALSE;

if (!_ostree_tmpf_fsverity (self, &tmpf, NULL, error))
return FALSE;

if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_REPLACE, destination_dfd, destination_path,
error))
return FALSE;

return TRUE;
#else
return composefs_not_supported (error);
#endif
}

/* Begin a checkout process */
static gboolean
checkout_tree_at (OstreeRepo *self, OstreeRepoCheckoutAtOptions *options, int destination_parent_fd,
Expand Down
16 changes: 4 additions & 12 deletions src/libostree/ostree-repo-composefs.c
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,6 @@ _composefs_write_cb (void *file, void *buf, size_t len)

#else /* HAVE_COMPOSEFS */

static gboolean
composefs_not_supported (GError **error)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"composefs is not supported in this ostree build");
return FALSE;
}

#endif

/**
Expand Down Expand Up @@ -520,7 +512,7 @@ ensure_lcfs_dir (struct lcfs_node_s *parent, const char *name, GError **error)
#endif /* HAVE_COMPOSEFS */

/**
* ostree_repo_checkout_composefs:
* _ostree_repo_checkout_composefs:
* @self: Repo
* @target: A target for the checkout
* @source: Source tree
Expand All @@ -538,8 +530,8 @@ ensure_lcfs_dir (struct lcfs_node_s *parent, const char *name, GError **error)
* Returns: %TRUE on success, %FALSE on failure
*/
gboolean
ostree_repo_checkout_composefs (OstreeRepo *self, OstreeComposefsTarget *target,
OstreeRepoFile *source, GCancellable *cancellable, GError **error)
_ostree_repo_checkout_composefs (OstreeRepo *self, OstreeComposefsTarget *target,
OstreeRepoFile *source, GCancellable *cancellable, GError **error)
{
#ifdef HAVE_COMPOSEFS
GLNX_AUTO_PREFIX_ERROR ("Checking out composefs", error);
Expand Down Expand Up @@ -601,7 +593,7 @@ ostree_repo_commit_add_composefs_metadata (OstreeRepo *self, guint format_versio

g_autoptr (OstreeComposefsTarget) target = ostree_composefs_target_new ();

if (!ostree_repo_checkout_composefs (self, target, repo_root, cancellable, error))
if (!_ostree_repo_checkout_composefs (self, target, repo_root, cancellable, error))
return FALSE;

g_autofree guchar *fsverity_digest = NULL;
Expand Down
13 changes: 10 additions & 3 deletions src/libostree/ostree-repo-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -473,9 +473,16 @@ gboolean ostree_composefs_target_write (OstreeComposefsTarget *target, int fd,
guchar **out_fsverity_digest, GCancellable *cancellable,
GError **error);

gboolean ostree_repo_checkout_composefs (OstreeRepo *self, OstreeComposefsTarget *target,
OstreeRepoFile *source, GCancellable *cancellable,
GError **error);
gboolean _ostree_repo_checkout_composefs (OstreeRepo *self, OstreeComposefsTarget *target,
OstreeRepoFile *source, GCancellable *cancellable,
GError **error);
static inline gboolean
composefs_not_supported (GError **error)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"composefs is not supported in this ostree build");
return FALSE;
}

G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeComposefsTarget, ostree_composefs_target_unref)

Expand Down
5 changes: 5 additions & 0 deletions src/libostree/ostree-repo.h
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,11 @@ gboolean ostree_repo_checkout_at (OstreeRepo *self, OstreeRepoCheckoutAtOptions
int destination_dfd, const char *destination_path,
const char *commit, GCancellable *cancellable, GError **error);

_OSTREE_PUBLIC
gboolean ostree_repo_checkout_composefs (OstreeRepo *self, GVariant *options, int destination_dfd,
const char *destination_path, const char *checksum,
GCancellable *cancellable, GError **error);

_OSTREE_PUBLIC
gboolean ostree_repo_checkout_gc (OstreeRepo *self, GCancellable *cancellable, GError **error);

Expand Down
81 changes: 2 additions & 79 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -600,37 +600,6 @@ merge_configuration_from (OstreeSysroot *sysroot, OstreeDeployment *merge_deploy
return TRUE;
}

#ifdef HAVE_COMPOSEFS
static gboolean
compare_verity_digests (GVariant *metadata_composefs, const guchar *fsverity_digest, GError **error)
{
const guchar *expected_digest;

if (metadata_composefs == NULL)
return TRUE;

if (g_variant_n_children (metadata_composefs) != OSTREE_SHA256_DIGEST_LEN)
return glnx_throw (error, "Expected composefs fs-verity in metadata has the wrong size");

expected_digest = g_variant_get_data (metadata_composefs);
if (memcmp (fsverity_digest, expected_digest, OSTREE_SHA256_DIGEST_LEN) != 0)
{
char actual_checksum[OSTREE_SHA256_STRING_LEN + 1];
char expected_checksum[OSTREE_SHA256_STRING_LEN + 1];

ostree_checksum_inplace_from_bytes (fsverity_digest, actual_checksum);
ostree_checksum_inplace_from_bytes (expected_digest, expected_checksum);

return glnx_throw (error,
"Generated composefs image digest (%s) doesn't match expected digest (%s)",
actual_checksum, expected_checksum);
}

return TRUE;
}

#endif

/* Look up @revision in the repository, and check it out in
* /ostree/deploy/OS/deploy/${treecsum}.${deployserial}.
* A dfd for the result is returned in @out_deployment_dfd.
Expand Down Expand Up @@ -696,54 +665,8 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy
composefs_enabled = repo->composefs_wanted;
if (composefs_enabled == OT_TRISTATE_YES)
{
g_autofree guchar *fsverity_digest = NULL;
g_auto (GLnxTmpfile) tmpf = {
0,
};
g_autoptr (GVariant) commit_variant = NULL;

if (!ostree_repo_load_commit (repo, revision, &commit_variant, NULL, error))
return FALSE;

g_autoptr (GVariant) metadata = g_variant_get_child_value (commit_variant, 0);
g_autoptr (GVariant) metadata_composefs = g_variant_lookup_value (
metadata, OSTREE_COMPOSEFS_DIGEST_KEY_V0, G_VARIANT_TYPE_BYTESTRING);

/* Create a composefs image and put in deploy dir */
g_autoptr (OstreeComposefsTarget) target = ostree_composefs_target_new ();

g_autoptr (GFile) commit_root = NULL;
if (!ostree_repo_read_commit (repo, csum, &commit_root, NULL, cancellable, error))
return FALSE;

if (!ostree_repo_checkout_composefs (repo, target, (OstreeRepoFile *)commit_root, cancellable,
error))
return FALSE;

g_autofree char *composefs_cfs_path
= g_strdup_printf ("%s/" OSTREE_COMPOSEFS_NAME, checkout_target_name);

g_debug ("writing %s", composefs_cfs_path);

if (!glnx_open_tmpfile_linkable_at (osdeploy_dfd, checkout_target_name, O_WRONLY | O_CLOEXEC,
&tmpf, error))
return FALSE;

if (!ostree_composefs_target_write (target, tmpf.fd, &fsverity_digest, cancellable, error))
return FALSE;

/* If the commit specified a composefs digest, verify it */
if (!compare_verity_digests (metadata_composefs, fsverity_digest, error))
return FALSE;

if (!glnx_fchmod (tmpf.fd, 0644, error))
return FALSE;

if (!_ostree_tmpf_fsverity (repo, &tmpf, NULL, error))
return FALSE;

if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_REPLACE, osdeploy_dfd, composefs_cfs_path,
error))
if (!ostree_repo_checkout_composefs (repo, NULL, ret_deployment_dfd, OSTREE_COMPOSEFS_NAME,
csum, cancellable, error))
return FALSE;
}
else
Expand Down
22 changes: 18 additions & 4 deletions src/ostree/ot-builtin-checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "ot-builtins.h"
#include "otutil.h"

static gboolean opt_composefs;
static gboolean opt_user_mode;
static gboolean opt_allow_noent;
static gboolean opt_disable_cache;
Expand Down Expand Up @@ -107,6 +108,7 @@ static GOptionEntry options[] = {
"PATH" },
{ "selinux-prefix", 0, 0, G_OPTION_ARG_STRING, &opt_selinux_prefix,
"When setting SELinux labels, prefix all paths by PREFIX", "PREFIX" },
{ "composefs", 0, 0, G_OPTION_ARG_NONE, &opt_composefs, "Only create a composefs blob", NULL },
{ NULL }
};

Expand Down Expand Up @@ -136,10 +138,22 @@ process_one_checkout (OstreeRepo *repo, const char *resolved_commit, const char
* `ostree_repo_checkout_at` until such time as we have a more
* convenient infrastructure for testing C APIs with data.
*/
if (opt_disable_cache || opt_whiteouts || opt_require_hardlinks || opt_union_add || opt_force_copy
|| opt_force_copy_zerosized || opt_bareuseronly_dirs || opt_union_identical
|| opt_skiplist_file || opt_selinux_policy || opt_selinux_prefix
|| opt_process_passthrough_whiteouts)
gboolean new_options_set = opt_disable_cache || opt_whiteouts || opt_require_hardlinks
|| opt_union_add || opt_force_copy || opt_force_copy_zerosized
|| opt_bareuseronly_dirs || opt_union_identical || opt_skiplist_file
|| opt_selinux_policy || opt_selinux_prefix
|| opt_process_passthrough_whiteouts;

/* If we're doing composefs, then this is it */
if (opt_composefs)
{
if (new_options_set)
return glnx_throw (error, "Specified options are incompatible with --composefs");
return ostree_repo_checkout_composefs (repo, NULL, AT_FDCWD, destination, resolved_commit,
cancellable, error);
}

if (new_options_set)
{
OstreeRepoCheckoutAtOptions checkout_options = {
0,
Expand Down
10 changes: 10 additions & 0 deletions tests/test-composefs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,14 @@ assert_streq "${orig_composefs_digest}" "${new_composefs_digest}"
assert_streq "${new_composefs_digest}" "be956966c70970ea23b1a8043bca58cfb0d011d490a35a7817b36d04c0210954"
tap_ok "composefs metadata"

rm test2-co -rf
$OSTREE checkout --composefs test-composefs test2-co.cfs
digest=$(sha256sum < test2-co.cfs | cut -f 1 -d ' ')
# This file should be reproducible bit for bit across environments; per above
# we're operating on predictable data (fixed uid, gid, timestamps, xattrs, permissions).
assert_streq "${digest}" "031fab2c7f390b752a820146dc89f6880e5739cba7490f64024e0c7d11aad7c9"
# Verify it with composefs tooling
composefs-info dump test2-co.cfs >/dev/null
tap_ok "checkout composefs"

tap_end

0 comments on commit c0a715f

Please sign in to comment.