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

checkout: Add API to directly checkout composefs #3252

Merged
merged 1 commit into from
May 23, 2024
Merged
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
2 changes: 1 addition & 1 deletion Makefile-libostree.am
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions apidoc/ostree-sections.txt
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions src/libostree/libostree-devel.sym
Original file line number Diff line number Diff line change
@@ -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
100 changes: 100 additions & 0 deletions src/libostree/ostree-repo-checkout.c
Original file line number Diff line number Diff line change
@@ -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,
16 changes: 4 additions & 12 deletions src/libostree/ostree-repo-composefs.c
Original file line number Diff line number Diff line change
@@ -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

/**
@@ -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
@@ -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);
@@ -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;
13 changes: 10 additions & 3 deletions src/libostree/ostree-repo-private.h
Original file line number Diff line number Diff line change
@@ -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)

5 changes: 5 additions & 0 deletions src/libostree/ostree-repo.h
Original file line number Diff line number Diff line change
@@ -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);

81 changes: 2 additions & 79 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
@@ -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.
@@ -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
22 changes: 18 additions & 4 deletions src/ostree/ot-builtin-checkout.c
Original file line number Diff line number Diff line change
@@ -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;
@@ -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 }
};

@@ -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,
10 changes: 10 additions & 0 deletions tests/test-composefs.sh
Original file line number Diff line number Diff line change
@@ -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