From 19c42b914e957688e7a6a5bba6ab31ecf3bc6fba Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 9 Jun 2022 17:15:29 +0200 Subject: [PATCH] lib: Add API for checking out commits into an composefs image This supports checking out one or more commits into a combined tree which is then converted into a composefs image containing fs-verity digests for all the regular files, and payloads that are relative to a the `repo/objects` directory of a bare ostree repo. --- Makefile-libostree.am | 4 +- src/libostree/libostree-devel.sym | 6 + src/libostree/ostree-autocleanups.h | 1 + src/libostree/ostree-repo-checkout.c | 589 +++++++++++++++++++++++++++ src/libostree/ostree-repo.h | 52 +++ src/libostree/ostree-types.h | 1 + 6 files changed, 651 insertions(+), 2 deletions(-) diff --git a/Makefile-libostree.am b/Makefile-libostree.am index dd7ed8df7e..34541d9f0c 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -185,12 +185,12 @@ EXTRA_DIST += \ $(top_srcdir)/src/libostree/libostree-released.sym \ $(NULL) -libostree_1_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/bsdiff -I$(srcdir)/libglnx -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -I$(builddir)/src/libostree \ +libostree_1_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/bsdiff -I$(srcdir)/libglnx -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -I$(builddir)/src/libostree -I$(srcdir)/composefs \ $(OT_INTERNAL_GIO_UNIX_CFLAGS) $(OT_INTERNAL_GPGME_CFLAGS) $(OT_DEP_LZMA_CFLAGS) $(OT_DEP_ZLIB_CFLAGS) $(OT_DEP_CRYPTO_CFLAGS) \ -fvisibility=hidden '-D_OSTREE_PUBLIC=__attribute__((visibility("default"))) extern' \ -DPKGLIBEXECDIR=\"$(pkglibexecdir)\" libostree_1_la_LDFLAGS = -version-number 1:0:0 -Bsymbolic-functions $(addprefix $(wl_versionscript_arg),$(symbol_files)) -libostree_1_la_LIBADD = libotutil.la libglnx.la libbsdiff.la $(OT_INTERNAL_GIO_UNIX_LIBS) $(OT_INTERNAL_GPGME_LIBS) \ +libostree_1_la_LIBADD = libcomposefs.la libotutil.la libglnx.la libbsdiff.la $(OT_INTERNAL_GIO_UNIX_LIBS) $(OT_INTERNAL_GPGME_LIBS) \ $(OT_DEP_LZMA_LIBS) $(OT_DEP_ZLIB_LIBS) $(OT_DEP_CRYPTO_LIBS) # Some change between rust-1.21.0-1.fc27 and rust-1.22.1-1.fc27.x86_64 libostree_1_la_LIBADD += $(bupsplitpath) diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index 54945ecaf6..c9c6ceb448 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -23,6 +23,12 @@ LIBOSTREE_2022.5 { global: ostree_kernel_args_append_if_missing; + ostree_composefs_target_get_type; + ostree_composefs_target_new; + ostree_composefs_target_ref; + ostree_composefs_target_unref; + ostree_composefs_target_write_at; + ostree_repo_checkout_composefs; } LIBOSTREE_2022.4; /* Stub section for the stable release *after* this development one; don't diff --git a/src/libostree/ostree-autocleanups.h b/src/libostree/ostree-autocleanups.h index 56274949ab..08e4843382 100644 --- a/src/libostree/ostree-autocleanups.h +++ b/src/libostree/ostree-autocleanups.h @@ -70,6 +70,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderMount, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderOverride, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderResult, ostree_repo_finder_result_free) G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeRepoFinderResultv, ostree_repo_finder_result_freev, NULL) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeComposefsTarget, ostree_composefs_target_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeSign, g_object_unref) #endif diff --git a/src/libostree/ostree-repo-checkout.c b/src/libostree/ostree-repo-checkout.c index 189358efcf..3ce74b3dbd 100644 --- a/src/libostree/ostree-repo-checkout.c +++ b/src/libostree/ostree-repo-checkout.c @@ -32,6 +32,8 @@ #include "ostree-core-private.h" #include "ostree-repo-private.h" +#include "libcomposefs/lcfs-writer.h" + #define WHITEOUT_PREFIX ".wh." #define OPAQUE_WHITEOUT_NAME ".wh..wh..opq" @@ -1521,3 +1523,590 @@ ostree_repo_checkout_gc (OstreeRepo *self, return TRUE; } + +static ssize_t _composefs_read_cb(void *_file, void *buf, size_t count) +{ + GInputStream *in = _file; + + if (!g_input_stream_read_all (in, buf, count, NULL, NULL, NULL)) + { + errno = EIO; + return -1; + } + + return count; +} + +static ssize_t _composefs_write_cb(void *_file, void *buf, size_t count) +{ + GOutputStream *out = _file; + + if (!g_output_stream_write_all (out, buf, count, NULL, NULL, NULL)) + { + errno = EIO; + return -1; + } + + return count; +} + +struct OstreeComposefsTarget { + struct lcfs_node_s *dest; + int ref_count; +}; + +/** + * ostree_composefs_target_new: + * + * Creates a #OstreeComposefsTarget which can be used with + * ostree_repo_checkout_composefs() to create a composefs image based + * on a set of checkouts. + * + * Returns: (transfer full): a new of #OstreeComposefsTarget + */ +OstreeComposefsTarget * +ostree_composefs_target_new (void) +{ + OstreeComposefsTarget *target; + + target = g_slice_new0 (OstreeComposefsTarget); + + target->dest = lcfs_node_new (); + lcfs_node_set_mode (target->dest, 0755 | S_IFDIR); + + target->ref_count = 1; + + return target; +} + +/** + * ostree_composefs_target_ref: + * @target: an #OstreeComposefsTarget + * + * Increase the reference count on the given @target. + * + * Returns: (transfer full): a copy of @target, for convenience + * Since: 2022.5 + */ +OstreeComposefsTarget * +ostree_composefs_target_ref (OstreeComposefsTarget *target) +{ + gint refcount; + g_return_val_if_fail (target != NULL, NULL); + refcount = g_atomic_int_add (&target->ref_count, 1); + g_assert (refcount > 0); + return target; +} + +/** + * ostree_composefs_target_unref: + * @target: (transfer full): an #OstreeComposefsTarget + * + * Decrease the reference count on the given @target and free it if the + * reference count reaches 0. + * + * Since: 2022.5 + */ +void +ostree_composefs_target_unref (OstreeComposefsTarget *target) +{ + g_return_if_fail (target != NULL); + g_return_if_fail (target->ref_count > 0); + + if (g_atomic_int_dec_and_test (&target->ref_count)) + { + g_clear_pointer (&target->dest, lcfs_node_unref); + g_slice_free (OstreeComposefsTarget, target); + } +} + +G_DEFINE_BOXED_TYPE(OstreeComposefsTarget, ostree_composefs_target, + ostree_composefs_target_ref, + ostree_composefs_target_unref); + +/** + * ostree_composefs_target_write_at: + * @target: an #OstreeComposefsTarget + * @destination_dfd: Place image here + * @destination_path: (nullable): Use this name for image, or %NULL to not write the image + * @out_fsverity_digest: (out) (array fixed-size=32) (nullable): Return location for the fsverity binary digest, or %NULL to not compute it + * @cancellable: Cancellable + * @error: Error + * + * Writes a composefs image file to the filesystem at the + * path specified by @destination_dfd and destination_path (if not %NULL) + * and (optionally) computes the fsverity digest of the image. + * + * Returns: %TRUE on success, %FALSE on failure + * Since: 2022.5 + */ +gboolean +ostree_composefs_target_write_at (OstreeComposefsTarget *target, + int destination_dfd, + const char *destination_path, + guchar **out_fsverity_digest, + GCancellable *cancellable, + GError **error) +{ + g_auto(GLnxTmpfile) tmpf = { 0, }; + g_autoptr(GOutputStream) tmp_out = NULL; + g_autoptr(GOutputStream) out = NULL; + struct lcfs_node_s *root; + g_autofree guchar *fsverity_digest = NULL; + + if (!glnx_open_tmpfile_linkable_at (destination_dfd, ".", O_WRONLY | O_CLOEXEC, + &tmpf, error)) + return FALSE; + + tmp_out = g_unix_output_stream_new (tmpf.fd, FALSE); + out = g_buffered_output_stream_new (tmp_out); + + root = lcfs_node_lookup_child (target->dest, "root"); + if (root == NULL) + root = target->dest; /* Nothing was checked out, use an empty dir */ + + if (out_fsverity_digest) + fsverity_digest = g_malloc (OSTREE_SHA256_DIGEST_LEN); + + if (lcfs_write_to (root, out, _composefs_write_cb, fsverity_digest) != 0) + return glnx_throw_errno (error); + + if (!g_output_stream_flush (out, cancellable, error)) + return FALSE; + + if (!glnx_fchmod (tmpf.fd, 0644, error)) + return FALSE; + + if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_REPLACE, + destination_dfd, destination_path, + error)) + return FALSE; + + if (out_fsverity_digest) + *out_fsverity_digest = g_steal_pointer (&fsverity_digest); + + return TRUE; +} + +static gboolean +_ostree_composefs_set_xattrs (struct lcfs_node_s *node, + GVariant *xattrs, + GCancellable *cancellable, + GError **error) +{ + const guint n = g_variant_n_children (xattrs); + for (guint i = 0; i < n; i++) + { + const guint8* name; + g_autoptr(GVariant) value = NULL; + g_variant_get_child (xattrs, i, "(^&ay@ay)", + &name, &value); + + gsize value_len; + const guint8* value_data = g_variant_get_fixed_array (value, &value_len, 1); + + if (lcfs_node_set_xattr (node, (char *)name, (char*)value_data, value_len) != 0) + return glnx_throw_errno_prefix (error, "Setting composefs xattrs for %s", name); + } + + return TRUE; +} + +static gboolean +checkout_one_composefs_file_at (OstreeRepo *repo, + OstreeRepoCheckoutComposefsOptions *options, + CheckoutState *state, + const char *checksum, + struct lcfs_node_s *parent, + const char *destination_name, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GInputStream) input = NULL; + g_autoptr(GVariant) xattrs = NULL; + struct lcfs_node_s *existing; + + /* Validate this up front to prevent path traversal attacks */ + if (!ot_util_filename_validate (destination_name, error)) + return FALSE; + + existing = lcfs_node_lookup_child (parent, destination_name); + if (existing != NULL) + { + switch (options->overwrite_mode) + { + case OSTREE_REPO_CHECKOUT_OVERWRITE_NONE: + return glnx_throw (error, "Target checkout file already exist"); + break; + case OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES: + if (lcfs_node_remove_child (parent, destination_name) != 0 && errno != ENOENT) + return glnx_throw_errno (error); + break; + case OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES: + return TRUE; + case OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_IDENTICAL: + /* TODO: Handle this */ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_IDENTICAL checkout is not supported"); + return FALSE; + } + } + + g_autoptr(GFileInfo) source_info = NULL; + if (!ostree_repo_load_file (repo, checksum, &input, &source_info, &xattrs, + cancellable, error)) + return FALSE; + + if (options->filter) + { + /* use struct stat for when we can get rid of GFileInfo; though for now, we end up + * packing and unpacking in the non-archive case; blehh */ + struct stat stbuf = {0,}; + _ostree_gfileinfo_to_stbuf (source_info, &stbuf); + if (options->filter (repo, state->path_buf->str, &stbuf, options->filter_user_data) == + OSTREE_REPO_CHECKOUT_FILTER_SKIP) + return TRUE; /* Note early return */ + } + + const guint32 source_mode = g_file_info_get_attribute_uint32 (source_info, "unix::mode"); + const guint32 source_uid = g_file_info_get_attribute_uint32 (source_info, "unix::uid"); + const guint32 source_gid = g_file_info_get_attribute_uint32 (source_info, "unix::gid"); + const guint64 source_size = g_file_info_get_size (source_info); + const char *source_symlink_target = g_file_info_get_symlink_target (source_info); + const gboolean is_symlink = (g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK); + const gboolean is_whiteout = (!is_symlink && options->process_whiteouts && + g_str_has_prefix (destination_name, WHITEOUT_PREFIX)); + + /* First, see if it's a Docker whiteout, + * https://github.com/docker/docker/blob/1a714e76a2cb9008cd19609059e9988ff1660b78/pkg/archive/whiteouts.go + */ + if (is_whiteout) + { + const char *name = destination_name + (sizeof (WHITEOUT_PREFIX) - 1); + + if (!name[0]) + return glnx_throw (error, "Invalid empty whiteout '%s'", name); + + g_assert (name[0] != '/'); /* Sanity */ + + if (lcfs_node_remove_child (parent, name) != 0 && errno != ENOENT) + return glnx_throw_errno (error); + } + + struct lcfs_node_s *node = lcfs_node_new (); + if (node == NULL) + return glnx_throw (error, "Out of memory"); + + /* Takes ownership on success */ + if (lcfs_node_add_child (parent, node, destination_name) != 0) + { + lcfs_node_unref (node); + return glnx_throw_errno (error); + } + + lcfs_node_set_mode (node, source_mode); + lcfs_node_set_uid (node, source_uid); + lcfs_node_set_gid (node, source_gid); + lcfs_node_set_size (node, source_size); + if (is_symlink) + { + if (lcfs_node_set_payload (node, source_symlink_target) != 0) + return glnx_throw_errno (error); + } + else + { + char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; + _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE); + if (lcfs_node_set_payload (node, loose_path_buf) != 0) + return glnx_throw_errno (error); + + if (source_size != 0 && options->enable_fsverity) + { + if (lcfs_node_set_fsverity_from_content (node, input, _composefs_read_cb) != 0) + return glnx_throw_errno (error); + } + } + + if (xattrs) + { + if (!_ostree_composefs_set_xattrs (node, xattrs, cancellable, error)) + return FALSE; + } + + g_clear_object (&input); + + return TRUE; +} + +static gboolean +checkout_composefs_recurse (OstreeRepo *self, + OstreeRepoCheckoutComposefsOptions *options, + CheckoutState *state, + const char *dirtree_checksum, + const char *dirmeta_checksum, + struct lcfs_node_s *parent, + const char *name, + GCancellable *cancellable, + GError **error) +{ + gboolean did_exist = FALSE; + gboolean is_opaque_whiteout = FALSE; + g_autoptr(GVariant) dirtree = NULL; + g_autoptr(GVariant) dirmeta = NULL; + g_autoptr(GVariant) xattrs = NULL; + struct lcfs_node_s *directory; + + if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_DIR_TREE, + dirtree_checksum, &dirtree, error)) + return FALSE; + if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_DIR_META, + dirmeta_checksum, &dirmeta, error)) + return FALSE; + + /* Parse OSTREE_OBJECT_TYPE_DIR_META */ + guint32 uid, gid, mode; + g_variant_get (dirmeta, "(uuu@a(ayay))", + &uid, &gid, &mode, + &xattrs); + uid = GUINT32_FROM_BE (uid); + gid = GUINT32_FROM_BE (gid); + mode = GUINT32_FROM_BE (mode); + + if (options->filter) + { + struct stat stbuf = { 0, }; + stbuf.st_mode = mode; + stbuf.st_uid = uid; + stbuf.st_gid = gid; + if (options->filter (self, state->path_buf->str, &stbuf, options->filter_user_data) + == OSTREE_REPO_CHECKOUT_FILTER_SKIP) + return TRUE; /* Note early return */ + } + + if (options->process_whiteouts) + { + g_autoptr(GVariant) dir_file_contents = g_variant_get_child_value (dirtree, 0); + GVariantIter viter; + const char *fname; + g_autoptr(GVariant) contents_csum_v = NULL; + g_variant_iter_init (&viter, dir_file_contents); + while (g_variant_iter_loop (&viter, "(&s@ay)", &fname, &contents_csum_v)) + { + is_opaque_whiteout = (g_str_equal (fname, OPAQUE_WHITEOUT_NAME)); + if (is_opaque_whiteout) + break; + } + contents_csum_v = NULL; /* iter_loop freed it */ + } + + /* If it is an opaque whiteout, ensure the destination is empty first. */ + if (is_opaque_whiteout) + { + if (lcfs_node_remove_child (parent, name) != 0 && errno != ENOENT) + return glnx_throw_errno (error); + } + + directory = lcfs_node_lookup_child (parent, name); + if (directory != NULL && lcfs_node_get_mode (directory) != 0) + { + switch (options->overwrite_mode) + { + case OSTREE_REPO_CHECKOUT_OVERWRITE_NONE: + return glnx_throw (error, "Target checkout directory already exist"); + /* All of these cases are the same for directories */ + case OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES: + case OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES: + case OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_IDENTICAL: + did_exist = TRUE; + break; + } + } + else + { + directory = lcfs_node_new (); + if (directory == NULL) + return glnx_throw (error, "Out of memory"); + + /* Takes ownership on success */ + if (lcfs_node_add_child(parent, directory, name) != 0) + { + lcfs_node_unref(directory); + return glnx_throw_errno (error); + } + } + + if (!did_exist) + { + lcfs_node_set_mode (directory, mode); + lcfs_node_set_uid (directory, uid); + lcfs_node_set_gid (directory, gid); + + /* Set the xattrs if we created the dir */ + if (xattrs) + { + if (!_ostree_composefs_set_xattrs (directory, xattrs, cancellable, error)) + return FALSE; + } + } + + /* Process files in this subdir */ + { g_autoptr(GVariant) dir_file_contents = g_variant_get_child_value (dirtree, 0); + GVariantIter viter; + g_variant_iter_init (&viter, dir_file_contents); + const char *fname; + g_autoptr(GVariant) contents_csum_v = NULL; + while (g_variant_iter_loop (&viter, "(&s@ay)", &fname, &contents_csum_v)) + { + push_path_element (state, fname, FALSE); + + char tmp_checksum[OSTREE_SHA256_STRING_LEN+1]; + _ostree_checksum_inplace_from_bytes_v (contents_csum_v, tmp_checksum); + + if (!checkout_one_composefs_file_at (self, options, state, + tmp_checksum, + directory, fname, + cancellable, error)) + return FALSE; + + pop_path_element (state, fname, FALSE); + } + contents_csum_v = NULL; /* iter_loop freed it */ + } + + /* Process subdirectories */ + { g_autoptr(GVariant) dir_subdirs = g_variant_get_child_value (dirtree, 1); + const char *dname; + g_autoptr(GVariant) subdirtree_csum_v = NULL; + g_autoptr(GVariant) subdirmeta_csum_v = NULL; + GVariantIter viter; + g_variant_iter_init (&viter, dir_subdirs); + while (g_variant_iter_loop (&viter, "(&s@ay@ay)", &dname, + &subdirtree_csum_v, &subdirmeta_csum_v)) + { + /* Validate this up front to prevent path traversal attacks. Note that + * we don't validate at the top of this function like we do for + * checkout_one_file_at() becuase I believe in some cases this function + * can be called *initially* with user-specified paths for the root + * directory. + */ + if (!ot_util_filename_validate (dname, error)) + return FALSE; + + push_path_element (state, dname, TRUE); + + char subdirtree_checksum[OSTREE_SHA256_STRING_LEN+1]; + _ostree_checksum_inplace_from_bytes_v (subdirtree_csum_v, subdirtree_checksum); + char subdirmeta_checksum[OSTREE_SHA256_STRING_LEN+1]; + _ostree_checksum_inplace_from_bytes_v (subdirmeta_csum_v, subdirmeta_checksum); + if (!checkout_composefs_recurse (self, options, state, + subdirtree_checksum, subdirmeta_checksum, + directory, dname, + cancellable, error)) + return FALSE; + + pop_path_element (state, dname, TRUE); + } + } + + + return TRUE; +} + +/* Begin a checkout process */ +static gboolean +checkout_composefs_tree (OstreeRepo *self, + OstreeRepoCheckoutComposefsOptions *options, + OstreeComposefsTarget *target, + OstreeRepoFile *source, + GFileInfo *source_info, + GCancellable *cancellable, + GError **error) +{ + g_auto(CheckoutState) state = { 0, }; + + if (options->filter) + state.path_buf = g_string_new ("/"); + + if (g_file_info_get_file_type (source_info) != G_FILE_TYPE_DIRECTORY) + return glnx_throw (error, "Root checkout of composefs must be directory"); + + /* Cache any directory metadata we read during this operation; + * see commit b7afe91e21143d7abb0adde440683a52712aa246 + */ + g_auto(OstreeRepoMemoryCacheRef) memcache_ref; + _ostree_repo_memory_cache_ref_init (&memcache_ref, self); + + g_assert_cmpint (g_file_info_get_file_type (source_info), ==, G_FILE_TYPE_DIRECTORY); + + const char *dirtree_checksum = ostree_repo_file_tree_get_contents_checksum (source); + const char *dirmeta_checksum = ostree_repo_file_tree_get_metadata_checksum (source); + return checkout_composefs_recurse (self, options, &state, + dirtree_checksum, dirmeta_checksum, target->dest, "root", + cancellable, error); +} + +/** + * ostree_repo_checkout_composefs: + * @self: Repo + * @options: (allow-none): Options + * @target: A target for the checkout + * @commit: Checksum for commit + * @cancellable: Cancellable + * @error: Error + * + * Check out @source into @target, which is an in-memory + * representation of a composefs image. The @target can be reused + * multiple times to layer multiple checkouts before writing out the + * image to disk using ostree_composefs_target_write_at(). + * + * There are various options specified by @options that affect + * how the image is created. + * + * Returns: %TRUE on success, %FALSE on failure + * Since: 2022.5 + */ +gboolean +ostree_repo_checkout_composefs (OstreeRepo *self, + OstreeRepoCheckoutComposefsOptions *options, + OstreeComposefsTarget *target, + const char *commit, + GCancellable *cancellable, + GError **error) +{ + OstreeRepoCheckoutComposefsOptions default_options = { 0 }; + + if (!options) + { + default_options.subpath = NULL; + options = &default_options; + } + + g_autoptr(GFile) commit_root = (GFile*) _ostree_repo_file_new_for_commit (self, commit, error); + if (!commit_root) + return FALSE; + + if (!ostree_repo_file_ensure_resolved ((OstreeRepoFile*)commit_root, error)) + return FALSE; + + g_autoptr(GFile) target_dir = NULL; + + if (options->subpath != NULL && strcmp (options->subpath, "/") != 0) + target_dir = g_file_resolve_relative_path (commit_root, options->subpath); + else + target_dir = g_object_ref (commit_root); + + g_autoptr(GFileInfo) target_info = + g_file_query_info (target_dir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + if (!target_info) + return FALSE; + + if (!checkout_composefs_tree (self, options, target, + (OstreeRepoFile*)target_dir, target_info, + cancellable, error)) + return FALSE; + + + return TRUE; +} diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index 985711702b..83a29f6853 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -1030,6 +1030,58 @@ gboolean ostree_repo_checkout_gc (OstreeRepo *self, GCancellable *cancellable, GError **error); + +_OSTREE_PUBLIC +GType ostree_composefs_target_get_type (void) G_GNUC_CONST; +_OSTREE_PUBLIC +OstreeComposefsTarget *ostree_composefs_target_new (void); +_OSTREE_PUBLIC +OstreeComposefsTarget *ostree_composefs_target_ref (OstreeComposefsTarget *target); +_OSTREE_PUBLIC +void ostree_composefs_target_unref (OstreeComposefsTarget *target); +_OSTREE_PUBLIC +gboolean ostree_composefs_target_write_at (OstreeComposefsTarget *target, + int destination_dfd, + const char *destination_path, + guchar **out_fsverity_digest, + GCancellable *cancellable, + GError **error); + + +/** + * OstreeRepoCheckoutComposefsOptions: + * + * An extensible options structure controlling checkout to composfs, + * similar to #OstreeRepoCheckoutAtOptions. This is used by + * ostree_repo_checkout_composefs(). + * + * Ensure that you have entirely zeroed the structure, then set just + * the desired options. + */ +typedef struct { + OstreeRepoCheckoutOverwriteMode overwrite_mode; + + gboolean enable_fsverity; + gboolean process_whiteouts; + gboolean unused_bools[6]; + + const char *subpath; + gpointer unused_ptrs[3]; + + int unused_ints[8]; + + OstreeRepoCheckoutFilter filter; + gpointer filter_user_data; +} OstreeRepoCheckoutComposefsOptions; + +_OSTREE_PUBLIC +gboolean ostree_repo_checkout_composefs (OstreeRepo *self, + OstreeRepoCheckoutComposefsOptions *options, + OstreeComposefsTarget *target, + const char *commit, + GCancellable *cancellable, + GError **error); + _OSTREE_PUBLIC gboolean ostree_repo_read_commit (OstreeRepo *self, const char *ref, diff --git a/src/libostree/ostree-types.h b/src/libostree/ostree-types.h index f3ba829c17..ff27362682 100644 --- a/src/libostree/ostree-types.h +++ b/src/libostree/ostree-types.h @@ -38,5 +38,6 @@ typedef struct OstreeMutableTree OstreeMutableTree; typedef struct OstreeRepoFile OstreeRepoFile; typedef struct _OstreeContentWriter OstreeContentWriter; typedef struct OstreeRemote OstreeRemote; +typedef struct OstreeComposefsTarget OstreeComposefsTarget; G_END_DECLS