diff --git a/doc/ostree-sections.txt b/doc/ostree-sections.txt index 3452ce95..f8fca738 100644 --- a/doc/ostree-sections.txt +++ b/doc/ostree-sections.txt @@ -277,6 +277,7 @@ ostree_repo_write_commit_detached_metadata OstreeRepoCheckoutMode OstreeRepoCheckoutOverwriteMode ostree_repo_checkout_tree +ostree_repo_checkout_tree_at ostree_repo_checkout_gc ostree_repo_read_commit OstreeRepoListObjectsFlags diff --git a/src/libostree/ostree-repo-checkout.c b/src/libostree/ostree-repo-checkout.c index 22aaf564..e6c321b0 100644 --- a/src/libostree/ostree-repo-checkout.c +++ b/src/libostree/ostree-repo-checkout.c @@ -173,7 +173,7 @@ write_regular_file_content (OstreeRepo *self, static gboolean checkout_file_from_input_at (OstreeRepo *self, - OstreeRepoCheckoutMode mode, + OstreeRepoCheckoutOptions *options, GFileInfo *file_info, GVariant *xattrs, GInputStream *input, @@ -197,7 +197,7 @@ checkout_file_from_input_at (OstreeRepo *self, goto out; } - if (mode != OSTREE_REPO_CHECKOUT_MODE_USER) + if (options->mode != OSTREE_REPO_CHECKOUT_MODE_USER) { if (G_UNLIKELY (fchownat (destination_dfd, destination_name, g_file_info_get_attribute_uint32 (file_info, "unix::uid"), @@ -224,7 +224,7 @@ checkout_file_from_input_at (OstreeRepo *self, file_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); /* Don't make setuid files on checkout when we're doing --user */ - if (mode == OSTREE_REPO_CHECKOUT_MODE_USER) + if (options->mode == OSTREE_REPO_CHECKOUT_MODE_USER) file_mode &= ~(S_ISUID|S_ISGID); do @@ -238,7 +238,7 @@ checkout_file_from_input_at (OstreeRepo *self, temp_out = g_unix_output_stream_new (fd, TRUE); fd = -1; /* Transfer ownership */ - if (!write_regular_file_content (self, mode, temp_out, file_info, xattrs, input, + if (!write_regular_file_content (self, options->mode, temp_out, file_info, xattrs, input, cancellable, error)) goto out; } @@ -256,7 +256,7 @@ checkout_file_from_input_at (OstreeRepo *self, */ static gboolean checkout_file_unioning_from_input_at (OstreeRepo *repo, - OstreeRepoCheckoutMode mode, + OstreeRepoCheckoutOptions *options, GFileInfo *file_info, GVariant *xattrs, GInputStream *input, @@ -290,7 +290,7 @@ checkout_file_unioning_from_input_at (OstreeRepo *repo, file_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); /* Don't make setuid files on checkout when we're doing --user */ - if (mode == OSTREE_REPO_CHECKOUT_MODE_USER) + if (options->mode == OSTREE_REPO_CHECKOUT_MODE_USER) file_mode &= ~(S_ISUID|S_ISGID); if (!gs_file_open_in_tmpdir_at (destination_dfd, file_mode, @@ -298,7 +298,7 @@ checkout_file_unioning_from_input_at (OstreeRepo *repo, cancellable, error)) goto out; - if (!write_regular_file_content (repo, mode, temp_out, file_info, xattrs, input, + if (!write_regular_file_content (repo, options->mode, temp_out, file_info, xattrs, input, cancellable, error)) goto out; } @@ -319,8 +319,7 @@ checkout_file_unioning_from_input_at (OstreeRepo *repo, static gboolean checkout_file_hardlink (OstreeRepo *self, - OstreeRepoCheckoutMode mode, - OstreeRepoCheckoutOverwriteMode overwrite_mode, + OstreeRepoCheckoutOptions *options, const char *loose_path, int destination_dfd, const char *destination_name, @@ -348,7 +347,7 @@ checkout_file_hardlink (OstreeRepo *self, { ret_was_supported = FALSE; } - else if (errno == EEXIST && overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES) + else if (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES) { /* Idiocy, from man rename(2) * @@ -377,18 +376,18 @@ checkout_file_hardlink (OstreeRepo *self, static gboolean checkout_one_file_at (OstreeRepo *repo, + OstreeRepoCheckoutOptions *options, GFile *source, GFileInfo *source_info, int destination_dfd, const char *destination_name, - OstreeRepoCheckoutMode mode, - OstreeRepoCheckoutOverwriteMode overwrite_mode, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; const char *checksum; gboolean is_symlink; + gboolean can_cache; gboolean did_hardlink = FALSE; char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; gs_unref_object GInputStream *input = NULL; @@ -408,12 +407,14 @@ checkout_one_file_at (OstreeRepo *repo, while (current_repo) { gboolean is_bare = ((current_repo->mode == OSTREE_REPO_MODE_BARE - && mode == OSTREE_REPO_CHECKOUT_MODE_NONE) || + && options->mode == OSTREE_REPO_CHECKOUT_MODE_NONE) || (current_repo->mode == OSTREE_REPO_MODE_BARE_USER - && mode == OSTREE_REPO_CHECKOUT_MODE_USER)); + && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER)); + gboolean current_can_cache = (options->enable_uncompressed_cache + && current_repo->enable_uncompressed_cache); gboolean is_archive_z2_with_cache = (current_repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2 - && mode == OSTREE_REPO_CHECKOUT_MODE_USER - && current_repo->enable_uncompressed_cache); + && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER + && current_can_cache); /* But only under these conditions */ if (is_bare || is_archive_z2_with_cache) @@ -422,7 +423,8 @@ checkout_one_file_at (OstreeRepo *repo, the cache, which is in "bare" form */ _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE); if (!checkout_file_hardlink (current_repo, - mode, overwrite_mode, loose_path_buf, + options, + loose_path_buf, destination_dfd, destination_name, TRUE, &did_hardlink, cancellable, error)) @@ -434,14 +436,17 @@ checkout_one_file_at (OstreeRepo *repo, } } + can_cache = (options->enable_uncompressed_cache + && repo->enable_uncompressed_cache); + /* Ok, if we're archive-z2 and we didn't find an object, uncompress * it now, stick it in the cache, and then hardlink to that. */ - if (!is_symlink + if (can_cache + && !is_symlink && !did_hardlink && repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2 - && mode == OSTREE_REPO_CHECKOUT_MODE_USER - && repo->enable_uncompressed_cache) + && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER) { if (!ostree_repo_load_file (repo, checksum, &input, NULL, NULL, cancellable, error)) @@ -485,7 +490,7 @@ checkout_one_file_at (OstreeRepo *repo, } g_mutex_unlock (&repo->cache_lock); - if (!checkout_file_hardlink (repo, mode, overwrite_mode, loose_path_buf, + if (!checkout_file_hardlink (repo, options, loose_path_buf, destination_dfd, destination_name, FALSE, &did_hardlink, cancellable, error)) @@ -502,9 +507,9 @@ checkout_one_file_at (OstreeRepo *repo, cancellable, error)) goto out; - if (overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES) + if (options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES) { - if (!checkout_file_unioning_from_input_at (repo, mode, source_info, xattrs, input, + if (!checkout_file_unioning_from_input_at (repo, options, source_info, xattrs, input, destination_dfd, destination_name, cancellable, error)) @@ -515,7 +520,7 @@ checkout_one_file_at (OstreeRepo *repo, } else { - if (!checkout_file_from_input_at (repo, mode, source_info, xattrs, input, + if (!checkout_file_from_input_at (repo, options, source_info, xattrs, input, destination_dfd, destination_name, cancellable, error)) @@ -554,8 +559,7 @@ checkout_one_file_at (OstreeRepo *repo, */ static gboolean checkout_tree_at (OstreeRepo *self, - OstreeRepoCheckoutMode mode, - OstreeRepoCheckoutOverwriteMode overwrite_mode, + OstreeRepoCheckoutOptions *options, int destination_parent_fd, const char *destination_name, OstreeRepoFile *source, @@ -579,7 +583,7 @@ checkout_tree_at (OstreeRepo *self, while (G_UNLIKELY (res == -1 && errno == EINTR)); if (res == -1) { - if (errno == EEXIST && overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES) + if (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES) did_exist = TRUE; else { @@ -594,7 +598,7 @@ checkout_tree_at (OstreeRepo *self, goto out; /* Set the xattrs now, so any derived labeling works */ - if (!did_exist && mode != OSTREE_REPO_CHECKOUT_MODE_USER) + if (!did_exist && options->mode != OSTREE_REPO_CHECKOUT_MODE_USER) { if (!ostree_repo_file_get_xattrs (source, &xattrs, NULL, error)) goto out; @@ -608,11 +612,11 @@ checkout_tree_at (OstreeRepo *self, if (g_file_info_get_file_type (source_info) != G_FILE_TYPE_DIRECTORY) { - ret = checkout_one_file_at (self, (GFile *) source, + ret = checkout_one_file_at (self, options, + (GFile *) source, source_info, destination_dfd, g_file_info_get_name (source_info), - mode, TRUE, cancellable, error); goto out; } @@ -640,7 +644,7 @@ checkout_tree_at (OstreeRepo *self, if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) { - if (!checkout_tree_at (self, mode, overwrite_mode, + if (!checkout_tree_at (self, options, destination_dfd, name, (OstreeRepoFile*)src_child, file_info, cancellable, error)) @@ -648,9 +652,9 @@ checkout_tree_at (OstreeRepo *self, } else { - if (!checkout_one_file_at (self, src_child, file_info, + if (!checkout_one_file_at (self, options, + src_child, file_info, destination_dfd, name, - mode, overwrite_mode, cancellable, error)) goto out; } @@ -672,7 +676,7 @@ checkout_tree_at (OstreeRepo *self, } } - if (!did_exist && mode != OSTREE_REPO_CHECKOUT_MODE_USER) + if (!did_exist && options->mode != OSTREE_REPO_CHECKOUT_MODE_USER) { do res = fchown (destination_dfd, @@ -748,13 +752,89 @@ ostree_repo_checkout_tree (OstreeRepo *self, GCancellable *cancellable, GError **error) { - return checkout_tree_at (self, mode, overwrite_mode, - AT_FDCWD, - gs_file_get_path_cached (destination), + OstreeRepoCheckoutOptions options = { 0, }; + + options.mode = mode; + options.overwrite_mode = overwrite_mode; + /* Backwards compatibility */ + options.enable_uncompressed_cache = TRUE; + + return checkout_tree_at (self, &options, + AT_FDCWD, gs_file_get_path_cached (destination), source, source_info, cancellable, error); } +/** + * ostree_repo_checkout_tree_at: + * @self: Repo + * @options: (allow-none): Options + * @destination_dfd: Directory FD for destination + * @destination_path: Directory for destination + * @commit: Checksum for commit + * @subpath: (allow-none): Subdirectory path + * @cancellable: Cancellable + * @error: Error + * + * Similar to ostree_repo_checkout_tree(), but uses directory-relative + * paths for the destination, uses a new `OstreeRepoCheckoutOptions`, + * and takes a commit checksum and optional subpath pair, rather than + * requiring use of `GFile` APIs for the caller. + * + * Note in addition that unlike ostree_repo_checkout_tree(), the + * default is not to use the repository-internal uncompressed objects + * cache. + */ +gboolean +ostree_repo_checkout_tree_at (OstreeRepo *self, + OstreeRepoCheckoutOptions *options, + int destination_dfd, + const char *destination_path, + const char *commit, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gs_unref_object GFile* commit_root = NULL; + gs_unref_object GFile* target_dir = NULL; + gs_unref_object GFileInfo* target_info = NULL; + OstreeRepoCheckoutOptions default_options = { 0, }; + + if (!options) + { + default_options.subpath = NULL; + options = &default_options; + } + + commit_root = (GFile*) _ostree_repo_file_new_for_commit (self, commit, error); + if (!commit_root) + goto out; + + if (!ostree_repo_file_ensure_resolved ((OstreeRepoFile*)commit_root, error)) + goto out; + + if (options->subpath && strcmp (options->subpath, "/") != 0) + target_dir = g_file_get_child (commit_root, options->subpath); + else + target_dir = g_object_ref (commit_root); + target_info = g_file_query_info (target_dir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + if (!target_info) + goto out; + + if (!checkout_tree_at (self, options, + destination_dfd, + destination_path, + (OstreeRepoFile*)target_dir, target_info, + cancellable, error)) + goto out; + + ret = TRUE; + out: + return ret; +} + /** * ostree_repo_checkout_gc: * @self: Repo diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index e775b54d..7f0c85da 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -449,6 +449,36 @@ ostree_repo_checkout_tree (OstreeRepo *self, GCancellable *cancellable, GError **error); +/** + * OstreeRepoCheckoutOptions: + * + * An extensible options structure controlling checkout. Ensure that + * you have entirely zeroed the structure, then set just the desired + * options. This is used by ostree_repo_checkout_tree_at() which + * supercedes previous separate enumeration usage in + * ostree_repo_checkout_tree(). + */ +typedef struct { + OstreeRepoCheckoutMode mode; + OstreeRepoCheckoutOverwriteMode overwrite_mode; + + guint enable_uncompressed_cache : 1; + guint unused : 31; + + const char *subpath; + + guint unused_uints[6]; + gpointer unused_ptrs[8]; +} OstreeRepoCheckoutOptions; + +gboolean ostree_repo_checkout_tree_at (OstreeRepo *self, + OstreeRepoCheckoutOptions *options, + int destination_dfd, + const char *destination_path, + const char *commit, + GCancellable *cancellable, + GError **error); + gboolean ostree_repo_checkout_gc (OstreeRepo *self, GCancellable *cancellable, GError **error); diff --git a/src/ostree/ot-builtin-checkout.c b/src/ostree/ot-builtin-checkout.c index 39d9afce..8a3e1c62 100644 --- a/src/ostree/ot-builtin-checkout.c +++ b/src/ostree/ot-builtin-checkout.c @@ -33,6 +33,7 @@ static gboolean opt_user_mode; static gboolean opt_allow_noent; +static gboolean opt_disable_cache; static char *opt_subpath; static gboolean opt_union; static gboolean opt_from_stdin; @@ -57,6 +58,7 @@ parse_fsync_cb (const char *option_name, static GOptionEntry options[] = { { "user-mode", 'U', 0, G_OPTION_ARG_NONE, &opt_user_mode, "Do not change file ownership or initialize extended attributes", NULL }, + { "disable-cache", 0, 0, G_OPTION_ARG_NONE, &opt_disable_cache, "Do not update or use the internal repository uncompressed object cache", NULL }, { "subpath", 0, 0, G_OPTION_ARG_STRING, &opt_subpath, "Checkout sub-directory PATH", "PATH" }, { "union", 0, 0, G_OPTION_ARG_NONE, &opt_union, "Keep existing directories, overwrite existing files", NULL }, { "allow-noent", 0, 0, G_OPTION_ARG_NONE, &opt_allow_noent, "Do nothing if specified path does not exist", NULL }, @@ -70,46 +72,76 @@ static gboolean process_one_checkout (OstreeRepo *repo, const char *resolved_commit, const char *subpath, - GFile *target, + const char *destination, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; - GError *tmp_error = NULL; - gs_unref_object GFile *root = NULL; - gs_unref_object GFile *subtree = NULL; - gs_unref_object GFileInfo *file_info = NULL; - if (!ostree_repo_read_commit (repo, resolved_commit, &root, NULL, cancellable, error)) - goto out; - - if (subpath) - subtree = g_file_resolve_relative_path (root, subpath); - else - subtree = g_object_ref (root); - - file_info = g_file_query_info (subtree, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, &tmp_error); - if (!file_info) + /* This strange code structure is to preserve testing + * coverage of both `ostree_repo_checkout_tree` and + * `ostree_repo_checkout_tree_at` until such time as we have a more + * convenient infrastructure for testing C APIs with data. + */ + if (opt_disable_cache) { - if (opt_allow_noent - && g_error_matches (tmp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - { - g_clear_error (&tmp_error); - ret = TRUE; - } - else - { - g_propagate_error (error, tmp_error); - } - goto out; - } + OstreeRepoCheckoutOptions options = { 0, }; + + if (opt_user_mode) + options.mode = OSTREE_REPO_CHECKOUT_MODE_USER; + if (opt_union) + options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES; + if (subpath) + options.subpath = subpath; - if (!ostree_repo_checkout_tree (repo, opt_user_mode ? OSTREE_REPO_CHECKOUT_MODE_USER : 0, - opt_union ? OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES : 0, - target, OSTREE_REPO_FILE (subtree), file_info, cancellable, error)) - goto out; + + if (!ostree_repo_checkout_tree_at (repo, &options, + AT_FDCWD, destination, + resolved_commit, + cancellable, error)) + goto out; + } + else + { + GError *tmp_error = NULL; + gs_unref_object GFile *root = NULL; + gs_unref_object GFile *subtree = NULL; + gs_unref_object GFileInfo *file_info = NULL; + gs_unref_object GFile *destination_file = g_file_new_for_path (destination); + + if (!ostree_repo_read_commit (repo, resolved_commit, &root, NULL, cancellable, error)) + goto out; + + if (subpath) + subtree = g_file_resolve_relative_path (root, subpath); + else + subtree = g_object_ref (root); + + file_info = g_file_query_info (subtree, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, &tmp_error); + if (!file_info) + { + if (opt_allow_noent + && g_error_matches (tmp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_clear_error (&tmp_error); + ret = TRUE; + } + else + { + g_propagate_error (error, tmp_error); + } + goto out; + } + + if (!ostree_repo_checkout_tree (repo, opt_user_mode ? OSTREE_REPO_CHECKOUT_MODE_USER : 0, + opt_union ? OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES : 0, + destination_file, + OSTREE_REPO_FILE (subtree), file_info, + cancellable, error)) + goto out; + } ret = TRUE; out: @@ -118,7 +150,7 @@ process_one_checkout (OstreeRepo *repo, static gboolean process_many_checkouts (OstreeRepo *repo, - GFile *target, + const char *target, GCancellable *cancellable, GError **error) { @@ -198,7 +230,6 @@ ostree_builtin_checkout (int argc, char **argv, GCancellable *cancellable, GErro const char *commit; const char *destination; gs_free char *resolved_commit = NULL; - gs_unref_object GFile *checkout_target = NULL; context = g_option_context_new ("COMMIT [DESTINATION] - Check out a commit into a filesystem tree"); @@ -221,9 +252,8 @@ ostree_builtin_checkout (int argc, char **argv, GCancellable *cancellable, GErro if (opt_from_stdin || opt_from_file) { destination = argv[1]; - checkout_target = g_file_new_for_path (destination); - if (!process_many_checkouts (repo, checkout_target, cancellable, error)) + if (!process_many_checkouts (repo, destination, cancellable, error)) goto out; } else @@ -237,10 +267,8 @@ ostree_builtin_checkout (int argc, char **argv, GCancellable *cancellable, GErro if (!ostree_repo_resolve_rev (repo, commit, FALSE, &resolved_commit, error)) goto out; - checkout_target = g_file_new_for_path (destination); - if (!process_one_checkout (repo, resolved_commit, opt_subpath, - checkout_target, + destination, cancellable, error)) goto out; } diff --git a/tests/basic-test.sh b/tests/basic-test.sh index 65e2229e..753f5004 100755 --- a/tests/basic-test.sh +++ b/tests/basic-test.sh @@ -19,7 +19,7 @@ set -e -echo "1..47" +echo "1..48" $OSTREE checkout test2 checkout-test2 echo "ok checkout" @@ -362,6 +362,25 @@ if $OSTREE commit -b test2 -s "Attempt to commit a FIFO" 2>../errmsg; then fi echo "ok commit of fifo was rejected" +cd ${test_tmpdir} +rm repo2 -rf +mkdir repo2 +${CMD_PREFIX} ostree --repo=repo2 init --mode=archive-z2 +${CMD_PREFIX} ostree --repo=repo2 pull-local repo +rm -rf test2-checkout +${CMD_PREFIX} ostree --repo=repo2 checkout -U --disable-cache test2 test2-checkout +if test -d repo2/uncompressed-objects-cache; then + ls repo2/uncompressed-objects-cache > ls.txt + if test -s ls.txt; then + assert_not_reached "repo has uncompressed objects" + fi +fi +rm test2-checkout -rf +${CMD_PREFIX} ostree --repo=repo2 checkout -U test2 test2-checkout +assert_file_has_content test2-checkout/baz/cow moo +assert_has_dir repo2/uncompressed-objects-cache +echo "ok disable cache checkout" + cd ${test_tmpdir} rm -rf test2-checkout mkdir -p test2-checkout