From 0446cdb0d38e1e05fc51202b580e28c44993b76c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 7 Oct 2016 16:28:57 +0200 Subject: [PATCH 01/12] pull: Support inherit-transaction If this is true, don't initiate, abort of commit a transaction, instead it is assumed that the caller initiated the transaction, and that it will eventually be commited. This allows you to do multiple pulls or a combination of pulls and commits in a single transaction. Closes: #525 Approved by: cgwalters --- src/libostree/ostree-repo-pull.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index 01561df2..f59d6d0a 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -2236,6 +2236,7 @@ repo_remote_fetch_summary (OstreeRepo *self, * * override-commit-ids (as): Array of specific commit IDs to fetch for refs * * dry-run (b): Only print information on what will be downloaded (requires static deltas) * * override-url (s): Fetch objects from this URL if remote specifies no metalink in options + * * inherit-transaction (b): Don't initiate, finish or abort a transaction, usefult to do mutliple pulls in one transaction. */ gboolean ostree_repo_pull_with_options (OstreeRepo *self, @@ -2273,6 +2274,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, g_autofree char *base_meta_url = NULL; g_autofree char *base_content_url = NULL; gboolean mirroring_into_archive; + gboolean inherit_transaction = FALSE; if (options) { @@ -2293,6 +2295,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, (void) g_variant_lookup (options, "override-commit-ids", "^a&s", &override_commit_ids); (void) g_variant_lookup (options, "dry-run", "b", &pull_data->dry_run); (void) g_variant_lookup (options, "override-url", "&s", &url_override); + (void) g_variant_lookup (options, "inherit-transaction", "b", &inherit_transaction); } g_return_val_if_fail (pull_data->maxdepth >= -1, FALSE); @@ -2822,7 +2825,9 @@ ostree_repo_pull_with_options (OstreeRepo *self, if (pull_data->fetcher == NULL) goto out; - if (!ostree_repo_prepare_transaction (pull_data->repo, &pull_data->legacy_transaction_resuming, + pull_data->legacy_transaction_resuming = FALSE; + if (!inherit_transaction && + !ostree_repo_prepare_transaction (pull_data->repo, &pull_data->legacy_transaction_resuming, cancellable, error)) goto out; @@ -2954,7 +2959,8 @@ ostree_repo_pull_with_options (OstreeRepo *self, } } - if (!ostree_repo_commit_transaction (pull_data->repo, NULL, cancellable, error)) + if (!inherit_transaction && + !ostree_repo_commit_transaction (pull_data->repo, NULL, cancellable, error)) goto out; end_time = g_get_monotonic_time (); @@ -3020,8 +3026,9 @@ ostree_repo_pull_with_options (OstreeRepo *self, g_propagate_error (error, pull_data->cached_async_error); else g_clear_error (&pull_data->cached_async_error); - - ostree_repo_abort_transaction (pull_data->repo, cancellable, NULL); + + if (!inherit_transaction) + ostree_repo_abort_transaction (pull_data->repo, cancellable, NULL); g_main_context_unref (pull_data->main_context); if (update_timeout) g_source_destroy (update_timeout); From ee484697cdf25a4185a1456d9cf3e80a4d490750 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 5 Oct 2016 21:26:38 +0200 Subject: [PATCH 02/12] pull: Support multiple specifications of --subpath I need this in flatpak to avoid doing multiple pulls when doing locale subsetting. Closes: #523 Approved by: cgwalters --- src/libostree/ostree-repo-pull.c | 203 +++++++++++++++++++++---------- src/ostree/ot-builtin-pull.c | 18 ++- tests/test-pull-subpath.sh | 5 +- 3 files changed, 158 insertions(+), 68 deletions(-) diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index f59d6d0a..52455ed4 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -99,7 +99,7 @@ typedef struct { gboolean is_commit_only; gboolean is_untrusted; - char *dir; + GPtrArray *dirs; gboolean commitpartial_exists; gboolean have_previous_bytes; @@ -117,6 +117,7 @@ typedef struct { typedef struct { OtPullData *pull_data; GVariant *object; + char *path; gboolean is_detached_meta; /* Only relevant when is_detached_meta is TRUE. Controls @@ -133,6 +134,7 @@ typedef struct { typedef struct { guchar csum[OSTREE_SHA256_DIGEST_LEN]; + char *path; OstreeObjectType objtype; guint recursion_depth; } ScanObjectQueueData; @@ -140,16 +142,19 @@ typedef struct { static void queue_scan_one_metadata_object (OtPullData *pull_data, const char *csum, OstreeObjectType objtype, + const char *path, guint recursion_depth); static void queue_scan_one_metadata_object_c (OtPullData *pull_data, const guchar *csum, OstreeObjectType objtype, + const char *path, guint recursion_depth); static gboolean scan_one_metadata_object_c (OtPullData *pull_data, const guchar *csum, OstreeObjectType objtype, + const char *path, guint recursion_depth, GCancellable *cancellable, GError **error); @@ -278,11 +283,13 @@ idle_worker (gpointer user_data) scan_one_metadata_object_c (pull_data, scan_data->csum, scan_data->objtype, + scan_data->path, scan_data->recursion_depth, pull_data->cancellable, &error); check_outstanding_requests_handle_error (pull_data, error); + g_free (scan_data->path); g_free (scan_data); return G_SOURCE_CONTINUE; } @@ -381,12 +388,80 @@ static void enqueue_one_object_request (OtPullData *pull_data, const char *checksum, OstreeObjectType objtype, + const char *path, gboolean is_detached_meta, gboolean object_is_stored); +static gboolean +matches_pull_dir (const char *current_file, + const char *pull_dir, + gboolean current_file_is_dir) +{ + const char *rest; + + if (g_str_has_prefix (pull_dir, current_file)) + { + rest = pull_dir + strlen (current_file); + if (*rest == 0) + { + /* The current file is exactly the same as the specified + pull dir. This matches always, even if the file is not a + directory. */ + return TRUE; + } + + if (*rest == '/') + { + /* The current file is a directory-prefix of the pull_dir. + Match only if this is supposed to be a directory */ + return current_file_is_dir; + } + + /* Matched a non-directory prefix such as /foo being a prefix of /fooo, + no match */ + return FALSE; + } + + if (g_str_has_prefix (current_file, pull_dir)) + { + rest = current_file + strlen (pull_dir); + /* Only match if the prefix match matched the entire directory + component */ + return *rest == '/'; + } + + return FALSE; +} + + +static gboolean +pull_matches_subdir (OtPullData *pull_data, + const char *path, + const char *basename, + gboolean basename_is_dir) +{ + int i; + g_autofree char *file = NULL; + + if (pull_data->dirs == NULL) + return TRUE; + + file = g_strconcat (path, basename, NULL); + + for (i = 0; i < pull_data->dirs->len; i++) + { + const char *pull_dir = g_ptr_array_index (pull_data->dirs, i); + if (matches_pull_dir (file, pull_dir, basename_is_dir)) + return TRUE; + } + + return FALSE; +} + static gboolean scan_dirtree_object (OtPullData *pull_data, const char *checksum, + const char *path, int recursion_depth, GCancellable *cancellable, GError **error) @@ -396,7 +471,6 @@ scan_dirtree_object (OtPullData *pull_data, g_autoptr(GVariant) tree = NULL; g_autoptr(GVariant) files_variant = NULL; g_autoptr(GVariant) dirs_variant = NULL; - char *subdir_target = NULL; const char *dirname = NULL; if (recursion_depth > OSTREE_MAX_RECURSION) @@ -429,10 +503,7 @@ scan_dirtree_object (OtPullData *pull_data, /* Skip files if we're traversing a request only directory, unless it exactly * matches the path */ - if (pull_data->dir && - /* Should always an initial slash, we assert it in scan_dirtree_object */ - pull_data->dir[0] == '/' && - strcmp (pull_data->dir+1, filename) != 0) + if (!pull_matches_subdir (pull_data, path, filename, FALSE)) continue; file_checksum = ostree_checksum_from_bytes_v (csum); @@ -451,32 +522,11 @@ scan_dirtree_object (OtPullData *pull_data, else if (!file_is_stored && !g_hash_table_lookup (pull_data->requested_content, file_checksum)) { g_hash_table_insert (pull_data->requested_content, file_checksum, file_checksum); - enqueue_one_object_request (pull_data, file_checksum, OSTREE_OBJECT_TYPE_FILE, FALSE, FALSE); + enqueue_one_object_request (pull_data, file_checksum, OSTREE_OBJECT_TYPE_FILE, path, FALSE, FALSE); file_checksum = NULL; /* Transfer ownership */ } } - if (pull_data->dir) - { - const char *subpath = NULL; - const char *nextslash = NULL; - g_autofree char *dir_data = NULL; - - g_assert (pull_data->dir[0] == '/'); // assert it starts with / like "/usr/share/rpm" - subpath = pull_data->dir + 1; // refers to name minus / like "usr/share/rpm" - nextslash = strchr (subpath, '/'); //refers to start of next slash like "/share/rpm" - dir_data = pull_data->dir; // keep the original pointer around since strchr() points into it - pull_data->dir = NULL; - - if (nextslash) - { - subdir_target = g_strndup (subpath, nextslash - subpath); // refers to first dir, like "usr" - pull_data->dir = g_strdup (nextslash); // sets dir to new deeper level like "/share/rpm" - } - else // we're as deep as it goes, i.e. subpath = "rpm" - subdir_target = g_strdup (subpath); - } - n = g_variant_n_children (dirs_variant); for (i = 0; i < n; i++) @@ -485,6 +535,7 @@ scan_dirtree_object (OtPullData *pull_data, g_autoptr(GVariant) meta_csum = NULL; const guchar *tree_csum_bytes; const guchar *meta_csum_bytes; + g_autofree char *subpath = NULL; g_variant_get_child (dirs_variant, i, "(&s@ay@ay)", &dirname, &tree_csum, &meta_csum); @@ -492,7 +543,7 @@ scan_dirtree_object (OtPullData *pull_data, if (!ot_util_filename_validate (dirname, error)) goto out; - if (subdir_target && strcmp (subdir_target, dirname) != 0) + if (!pull_matches_subdir (pull_data, path, dirname, TRUE)) continue; tree_csum_bytes = ostree_checksum_bytes_peek_validate (tree_csum, error); @@ -503,10 +554,12 @@ scan_dirtree_object (OtPullData *pull_data, if (meta_csum_bytes == NULL) goto out; + subpath = g_strconcat (path, dirname, "/", NULL); + queue_scan_one_metadata_object_c (pull_data, tree_csum_bytes, - OSTREE_OBJECT_TYPE_DIR_TREE, recursion_depth + 1); + OSTREE_OBJECT_TYPE_DIR_TREE, subpath, recursion_depth + 1); queue_scan_one_metadata_object_c (pull_data, meta_csum_bytes, - OSTREE_OBJECT_TYPE_DIR_META, recursion_depth + 1); + OSTREE_OBJECT_TYPE_DIR_META, subpath, recursion_depth + 1); } ret = TRUE; @@ -583,6 +636,14 @@ lookup_commit_checksum_from_summary (OtPullData *pull_data, return ret; } +static void +fetch_object_data_free (FetchObjectData *fetch_data) +{ + g_variant_unref (fetch_data->object); + g_free (fetch_data->path); + g_free (fetch_data); +} + static void content_fetch_on_write_complete (GObject *object, GAsyncResult *result, @@ -622,8 +683,7 @@ content_fetch_on_write_complete (GObject *object, out: pull_data->n_outstanding_content_write_requests--; check_outstanding_requests_handle_error (pull_data, local_error); - g_variant_unref (fetch_data->object); - g_free (fetch_data); + fetch_object_data_free (fetch_data); } static void @@ -712,10 +772,7 @@ content_fetch_on_complete (GObject *object, pull_data->n_outstanding_content_fetches--; check_outstanding_requests_handle_error (pull_data, local_error); if (free_fetch_data) - { - g_variant_unref (fetch_data->object); - g_free (fetch_data); - } + fetch_object_data_free (fetch_data); } static void @@ -753,12 +810,11 @@ on_metadata_written (GObject *object, goto out; } - queue_scan_one_metadata_object_c (pull_data, csum, objtype, 0); + queue_scan_one_metadata_object_c (pull_data, csum, objtype, fetch_data->path, 0); out: pull_data->n_outstanding_metadata_write_requests--; - g_variant_unref (fetch_data->object); - g_free (fetch_data); + fetch_object_data_free (fetch_data); check_outstanding_requests_handle_error (pull_data, local_error); } @@ -796,7 +852,7 @@ meta_fetch_on_complete (GObject *object, /* There isn't any detached metadata, just fetch the commit */ g_clear_error (&local_error); if (!fetch_data->object_is_stored) - enqueue_one_object_request (pull_data, checksum, objtype, FALSE, FALSE); + enqueue_one_object_request (pull_data, checksum, objtype, fetch_data->path, FALSE, FALSE); } /* When traversing parents, do not fail on a missing commit. @@ -811,7 +867,7 @@ meta_fetch_on_complete (GObject *object, if (pull_data->has_tombstone_commits) { enqueue_one_object_request (pull_data, checksum, OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT, - FALSE, FALSE); + fetch_data->path, FALSE, FALSE); } } } @@ -844,7 +900,7 @@ meta_fetch_on_complete (GObject *object, goto out; if (!fetch_data->object_is_stored) - enqueue_one_object_request (pull_data, checksum, objtype, FALSE, FALSE); + enqueue_one_object_request (pull_data, checksum, objtype, fetch_data->path, FALSE, FALSE); } else { @@ -874,10 +930,7 @@ meta_fetch_on_complete (GObject *object, pull_data->n_fetched_metadata++; check_outstanding_requests_handle_error (pull_data, local_error); if (free_fetch_data) - { - g_variant_unref (fetch_data->object); - g_free (fetch_data); - } + fetch_object_data_free (fetch_data); } static void @@ -1053,7 +1106,8 @@ scan_commit_object (OtPullData *pull_data, if (parent_csum_bytes != NULL && pull_data->maxdepth == -1) { queue_scan_one_metadata_object_c (pull_data, parent_csum_bytes, - OSTREE_OBJECT_TYPE_COMMIT, recursion_depth + 1); + OSTREE_OBJECT_TYPE_COMMIT, NULL, + recursion_depth + 1); } else if (parent_csum_bytes != NULL && depth > 0) { @@ -1078,7 +1132,9 @@ scan_commit_object (OtPullData *pull_data, g_hash_table_insert (pull_data->commit_to_depth, g_strdup (parent_checksum), GINT_TO_POINTER (parent_depth)); queue_scan_one_metadata_object_c (pull_data, parent_csum_bytes, - OSTREE_OBJECT_TYPE_COMMIT, recursion_depth + 1); + OSTREE_OBJECT_TYPE_COMMIT, + NULL, + recursion_depth + 1); } } @@ -1101,10 +1157,10 @@ scan_commit_object (OtPullData *pull_data, goto out; queue_scan_one_metadata_object_c (pull_data, tree_contents_csum_bytes, - OSTREE_OBJECT_TYPE_DIR_TREE, recursion_depth + 1); + OSTREE_OBJECT_TYPE_DIR_TREE, "/", recursion_depth + 1); queue_scan_one_metadata_object_c (pull_data, tree_meta_csum_bytes, - OSTREE_OBJECT_TYPE_DIR_META, recursion_depth + 1); + OSTREE_OBJECT_TYPE_DIR_META, NULL, recursion_depth + 1); } ret = TRUE; @@ -1116,23 +1172,26 @@ static void queue_scan_one_metadata_object (OtPullData *pull_data, const char *csum, OstreeObjectType objtype, + const char *path, guint recursion_depth) { guchar buf[OSTREE_SHA256_DIGEST_LEN]; ostree_checksum_inplace_to_bytes (csum, buf); - queue_scan_one_metadata_object_c (pull_data, buf, objtype, recursion_depth); + queue_scan_one_metadata_object_c (pull_data, buf, objtype, path, recursion_depth); } static void queue_scan_one_metadata_object_c (OtPullData *pull_data, const guchar *csum, OstreeObjectType objtype, + const char *path, guint recursion_depth) { ScanObjectQueueData *scan_data = g_new0 (ScanObjectQueueData, 1); memcpy (scan_data->csum, csum, sizeof (scan_data->csum)); scan_data->objtype = objtype; + scan_data->path = g_strdup (path); scan_data->recursion_depth = recursion_depth; g_queue_push_tail (&pull_data->scan_object_queue, scan_data); @@ -1143,6 +1202,7 @@ static gboolean scan_one_metadata_object_c (OtPullData *pull_data, const guchar *csum, OstreeObjectType objtype, + const char *path, guint recursion_depth, GCancellable *cancellable, GError **error) @@ -1190,7 +1250,7 @@ scan_one_metadata_object_c (OtPullData *pull_data, g_hash_table_insert (pull_data->requested_metadata, duped_checksum, duped_checksum); do_fetch_detached = (objtype == OSTREE_OBJECT_TYPE_COMMIT); - enqueue_one_object_request (pull_data, tmp_checksum, objtype, do_fetch_detached, FALSE); + enqueue_one_object_request (pull_data, tmp_checksum, objtype, path, do_fetch_detached, FALSE); } else if (objtype == OSTREE_OBJECT_TYPE_COMMIT && pull_data->is_commit_only) { @@ -1207,7 +1267,7 @@ scan_one_metadata_object_c (OtPullData *pull_data, /* For commits, always refetch detached metadata. */ if (objtype == OSTREE_OBJECT_TYPE_COMMIT) - enqueue_one_object_request (pull_data, tmp_checksum, objtype, TRUE, TRUE); + enqueue_one_object_request (pull_data, tmp_checksum, objtype, path, TRUE, TRUE); /* For commits, check whether we only had a partial fetch */ if (!do_scan && objtype == OSTREE_OBJECT_TYPE_COMMIT) @@ -1245,7 +1305,7 @@ scan_one_metadata_object_c (OtPullData *pull_data, case OSTREE_OBJECT_TYPE_DIR_META: break; case OSTREE_OBJECT_TYPE_DIR_TREE: - if (!scan_dirtree_object (pull_data, tmp_checksum, recursion_depth, + if (!scan_dirtree_object (pull_data, tmp_checksum, path, recursion_depth, pull_data->cancellable, error)) goto out; break; @@ -1267,6 +1327,7 @@ static void enqueue_one_object_request (OtPullData *pull_data, const char *checksum, OstreeObjectType objtype, + const char *path, gboolean is_detached_meta, gboolean object_is_stored) { @@ -1308,6 +1369,7 @@ enqueue_one_object_request (OtPullData *pull_data, fetch_data = g_new0 (FetchObjectData, 1); fetch_data->pull_data = pull_data; fetch_data->object = ostree_object_name_serialize (checksum, objtype); + fetch_data->path = g_strdup (path); fetch_data->is_detached_meta = is_detached_meta; fetch_data->object_is_stored = object_is_stored; @@ -1478,7 +1540,7 @@ process_one_static_delta_fallback (OtPullData *pull_data, g_hash_table_insert (pull_data->requested_metadata, checksum, checksum); do_fetch_detached = (objtype == OSTREE_OBJECT_TYPE_COMMIT); - enqueue_one_object_request (pull_data, checksum, objtype, do_fetch_detached, FALSE); + enqueue_one_object_request (pull_data, checksum, objtype, NULL, do_fetch_detached, FALSE); checksum = NULL; /* Transfer ownership */ } } @@ -1487,7 +1549,7 @@ process_one_static_delta_fallback (OtPullData *pull_data, if (!g_hash_table_lookup (pull_data->requested_content, checksum)) { g_hash_table_insert (pull_data->requested_content, checksum, checksum); - enqueue_one_object_request (pull_data, checksum, OSTREE_OBJECT_TYPE_FILE, FALSE, FALSE); + enqueue_one_object_request (pull_data, checksum, OSTREE_OBJECT_TYPE_FILE, NULL, FALSE, FALSE); checksum = NULL; /* Transfer ownership */ } } @@ -2227,6 +2289,7 @@ repo_remote_fetch_summary (OstreeRepo *self, * * refs (as): Array of string refs * * flags (i): An instance of #OstreeRepoPullFlags * * subdir (s): Pull just this subdirectory + * * subdirs (as): Pull just these subdirectories * * override-remote-name (s): If local, add this remote to refspec * * gpg-verify (b): GPG verify commits * * gpg-verify-summary (b): GPG verify summary @@ -2263,6 +2326,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, guint64 end_time; OstreeRepoPullFlags flags = 0; const char *dir_to_pull = NULL; + g_autofree char **dirs_to_pull = NULL; g_autofree char **refs_to_fetch = NULL; char **override_commit_ids = NULL; GSource *update_timeout = NULL; @@ -2275,6 +2339,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, g_autofree char *base_content_url = NULL; gboolean mirroring_into_archive; gboolean inherit_transaction = FALSE; + int i; if (options) { @@ -2284,6 +2349,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, /* Reduce risk of issues if enum happens to be 64 bit for some reason */ flags = flags_i; (void) g_variant_lookup (options, "subdir", "&s", &dir_to_pull); + (void) g_variant_lookup (options, "subdirs", "^a&s", &dirs_to_pull); (void) g_variant_lookup (options, "override-remote-name", "s", &pull_data->remote_name); opt_gpg_verify_set = g_variant_lookup (options, "gpg-verify", "b", &pull_data->gpg_verify); @@ -2305,6 +2371,9 @@ ostree_repo_pull_with_options (OstreeRepo *self, if (dir_to_pull) g_return_val_if_fail (dir_to_pull[0] == '/', FALSE); + for (i = 0; dirs_to_pull != NULL && dirs_to_pull[i] != NULL; i++) + g_return_val_if_fail (dirs_to_pull[i][0] == '/', FALSE); + g_return_val_if_fail (!(disable_static_deltas && require_static_deltas), FALSE); /* We only do dry runs with static deltas, because we don't really have any * in-advance information for bare fetches. @@ -2343,7 +2412,19 @@ ostree_repo_pull_with_options (OstreeRepo *self, (GDestroyNotify)g_free, NULL); pull_data->requested_metadata = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL); - pull_data->dir = g_strdup (dir_to_pull); + if (dir_to_pull != NULL || dirs_to_pull != NULL) + { + pull_data->dirs = g_ptr_array_new_with_free_func (g_free); + if (dir_to_pull != NULL) + g_ptr_array_add (pull_data->dirs, g_strdup (dir_to_pull)); + + if (dirs_to_pull != NULL) + { + for (i = 0; dirs_to_pull[i] != NULL; i++) + g_ptr_array_add (pull_data->dirs, g_strdup (dirs_to_pull[i])); + } + } + g_queue_init (&pull_data->scan_object_queue); pull_data->start_time = g_get_monotonic_time (); @@ -2838,7 +2919,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, while (g_hash_table_iter_next (&hash_iter, &key, &value)) { const char *commit = value; - queue_scan_one_metadata_object (pull_data, commit, OSTREE_OBJECT_TYPE_COMMIT, 0); + queue_scan_one_metadata_object (pull_data, commit, OSTREE_OBJECT_TYPE_COMMIT, NULL, 0); } g_hash_table_iter_init (&hash_iter, requested_refs_to_fetch); @@ -2871,7 +2952,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, goto out; } g_debug ("no delta superblock for %s-%s", from_revision ? from_revision : "empty", to_revision); - queue_scan_one_metadata_object (pull_data, to_revision, OSTREE_OBJECT_TYPE_COMMIT, 0); + queue_scan_one_metadata_object (pull_data, to_revision, OSTREE_OBJECT_TYPE_COMMIT, NULL, 0); } else { @@ -2994,7 +3075,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, } /* iterate over commits fetched and delete any commitpartial files */ - if (!dir_to_pull && !pull_data->is_commit_only) + if (pull_data->dirs == NULL && !pull_data->is_commit_only) { g_hash_table_iter_init (&hash_iter, requested_refs_to_fetch); while (g_hash_table_iter_next (&hash_iter, &key, &value)) diff --git a/src/ostree/ot-builtin-pull.c b/src/ostree/ot-builtin-pull.c index 99b25937..9f48c2e0 100644 --- a/src/ostree/ot-builtin-pull.c +++ b/src/ostree/ot-builtin-pull.c @@ -34,7 +34,7 @@ static gboolean opt_dry_run; static gboolean opt_disable_static_deltas; static gboolean opt_require_static_deltas; static gboolean opt_untrusted; -static char* opt_subpath; +static char** opt_subpaths; static char* opt_cache_dir; static int opt_depth = 0; static char* opt_url; @@ -46,7 +46,7 @@ static GOptionEntry options[] = { { "disable-static-deltas", 0, 0, G_OPTION_ARG_NONE, &opt_disable_static_deltas, "Do not use static deltas", NULL }, { "require-static-deltas", 0, 0, G_OPTION_ARG_NONE, &opt_require_static_deltas, "Require static deltas", NULL }, { "mirror", 0, 0, G_OPTION_ARG_NONE, &opt_mirror, "Write refs suitable for a mirror", NULL }, - { "subpath", 0, 0, G_OPTION_ARG_STRING, &opt_subpath, "Only pull the provided subpath", NULL }, + { "subpath", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_subpaths, "Only pull the provided subpath(s)", NULL }, { "untrusted", 0, 0, G_OPTION_ARG_NONE, &opt_untrusted, "Do not trust (local) sources", NULL }, { "dry-run", 0, 0, G_OPTION_ARG_NONE, &opt_dry_run, "Only print information on what will be downloaded (requires static deltas)", NULL }, { "depth", 0, 0, G_OPTION_ARG_INT, &opt_depth, "Traverse DEPTH parents (-1=infinite) (default: 0)", "DEPTH" }, @@ -216,9 +216,17 @@ ostree_builtin_pull (int argc, char **argv, GCancellable *cancellable, GError ** if (opt_url) g_variant_builder_add (&builder, "{s@v}", "override-url", g_variant_new_variant (g_variant_new_string (opt_url))); - if (opt_subpath) - g_variant_builder_add (&builder, "{s@v}", "subdir", - g_variant_new_variant (g_variant_new_string (opt_subpath))); + if (opt_subpaths && opt_subpaths[0] != NULL) + { + /* Special case the one-element case so that we excercise this + old single-argument version in the tests */ + if (opt_subpaths[1] == NULL) + g_variant_builder_add (&builder, "{s@v}", "subdir", + g_variant_new_variant (g_variant_new_string (opt_subpaths[0]))); + else + g_variant_builder_add (&builder, "{s@v}", "subdirs", + g_variant_new_variant (g_variant_new_strv ((const char *const*) opt_subpaths, -1))); + } g_variant_builder_add (&builder, "{s@v}", "flags", g_variant_new_variant (g_variant_new_int32 (pullflags))); if (refs_to_fetch) diff --git a/tests/test-pull-subpath.sh b/tests/test-pull-subpath.sh index 05f685b9..09145f09 100755 --- a/tests/test-pull-subpath.sh +++ b/tests/test-pull-subpath.sh @@ -36,9 +36,10 @@ mkdir repo ${CMD_PREFIX} ostree --repo=repo init ${CMD_PREFIX} ostree --repo=repo remote add --set=gpg-verify=false origin ${remoteurl} -${CMD_PREFIX} ostree --repo=repo pull --subpath=/baz origin main +${CMD_PREFIX} ostree --repo=repo pull --subpath=/baz/deeper --subpath=/baz/another origin main -${CMD_PREFIX} ostree --repo=repo ls origin:main /baz +${CMD_PREFIX} ostree --repo=repo ls origin:main /baz/deeper +${CMD_PREFIX} ostree --repo=repo ls origin:main /baz/another if ${CMD_PREFIX} ostree --repo=repo ls origin:main /firstfile 2>err.txt; then assert_not_reached fi From 3943284dadc9734cdc14b8ac7c1de0e4e253c913 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Thu, 13 Oct 2016 11:56:46 -0400 Subject: [PATCH 03/12] docs: amend vmlinuz & initramfs naming convention I was confused while reading the docs how this could work, since in at least the Fedora/CentOS/RHEL distros, they're named e.g. initramfs-`uname -r`-$checksum. Closes: #529 Approved by: cgwalters --- docs/manual/deployment.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/manual/deployment.md b/docs/manual/deployment.md index dc77809c..51554c4f 100644 --- a/docs/manual/deployment.md +++ b/docs/manual/deployment.md @@ -43,13 +43,14 @@ deployment. For short, we will call this the "tree", to distinguish it from the concept of a deployment. First, the tree must include a kernel stored as -`/boot/vmlinuz-$checksum`. The checksum should be a SHA256 hash of -the kernel contents; it must be pre-computed before storing the kernel -in the repository. Optionally, the tree can contain an initramfs, -stored as `/boot/initramfs-$checksum`. If this exists, the checksum -must include both the kernel and initramfs contents. OSTree will use -this to determine which kernels are shared. The rationale for this is -to avoid computing checksums on the client by default. +`vmlinuz(-.*)?-$checksum` in either `/boot` or `/usr/lib/ostree-boot`. +The checksum should be a SHA256 hash of the kernel contents; it must be +pre-computed before storing the kernel in the repository. Optionally, +the directory can also contain an initramfs, stored as +`initramfs(-.*)?-$checksum`. If this exists, the checksum must include +both the kernel and initramfs contents. OSTree will use this to +determine which kernels are shared. The rationale for this is to avoid +computing checksums on the client by default. The deployment should not have a traditional UNIX `/etc`; instead, it should include `/usr/etc`. This is the "default configuration". When From a0598cb494cb3b266c28696470eab7be6d29b3ed Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Thu, 13 Oct 2016 12:05:54 -0400 Subject: [PATCH 04/12] ostree-sysroot-deploy.c: delete redundant check Just noticed this while inspecting the code. The deployments retrieved by `_ostree_sysroot_list_deployment_dirs_for_os` will forcibly already have a matching osname since it indirectly uses that same variable to construct them. Having a check there makes it look like there may be subtle corner cases, when there aren't. Closes: #529 Approved by: cgwalters --- src/libostree/ostree-sysroot-deploy.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 65173ffe..a14f6005 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -2002,7 +2002,7 @@ allocate_deployserial (OstreeSysroot *self, g_ptr_array_new_with_free_func (g_object_unref); osdir = ot_gfile_get_child_build_path (self->path, "ostree/deploy", osname, NULL); - + if (!_ostree_sysroot_list_deployment_dirs_for_os (osdir, tmp_current_deployments, cancellable, error)) goto out; @@ -2010,9 +2010,7 @@ allocate_deployserial (OstreeSysroot *self, for (i = 0; i < tmp_current_deployments->len; i++) { OstreeDeployment *deployment = tmp_current_deployments->pdata[i]; - - if (strcmp (ostree_deployment_get_osname (deployment), osname) != 0) - continue; + if (strcmp (ostree_deployment_get_csum (deployment), revision) != 0) continue; From a660e5650d0f460810ea75c44d044b6924659f59 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Fri, 14 Oct 2016 09:25:01 -0400 Subject: [PATCH 05/12] OstreeFetcher: provide proxy credentials if needed There seems to be an issue in libsoup which causes basic auth credentials to not be passed to the proxy during requests. We thus have to handle PROXY_UNAUTHORIZED responses and provide the auth ourselves. Related: https://bugzilla.redhat.com/show_bug.cgi?id=1370558 Related: https://bugzilla.gnome.org/show_bug.cgi?id=772932 Closes: #529 Approved by: cgwalters --- src/libostree/ostree-fetcher.c | 60 +++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/src/libostree/ostree-fetcher.c b/src/libostree/ostree-fetcher.c index 3ddf2389..c2dc8ea4 100644 --- a/src/libostree/ostree-fetcher.c +++ b/src/libostree/ostree-fetcher.c @@ -65,6 +65,9 @@ typedef struct { /* Also protected by output_stream_set_lock. */ guint64 total_downloaded; + + GError *oob_error; + } ThreadClosure; static void @@ -159,6 +162,8 @@ thread_closure_unref (ThreadClosure *thread_closure) g_clear_pointer (&thread_closure->output_stream_set, g_hash_table_unref); g_mutex_clear (&thread_closure->output_stream_set_lock); + g_clear_pointer (&thread_closure->oob_error, g_error_free); + g_slice_free (ThreadClosure, thread_closure); } } @@ -276,6 +281,29 @@ session_thread_config_flags (ThreadClosure *thread_closure, } } +static void +on_authenticate (SoupSession *session, SoupMessage *msg, SoupAuth *auth, + gboolean retrying, gpointer user_data) +{ + ThreadClosure *thread_closure = user_data; + + if (msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED) + { + SoupURI *uri = NULL; + g_object_get (session, SOUP_SESSION_PROXY_URI, &uri, NULL); + if (retrying) + { + g_autofree char *s = soup_uri_to_string (uri, FALSE); + g_set_error (&thread_closure->oob_error, + G_IO_ERROR, G_IO_ERROR_PROXY_AUTH_FAILED, + "Invalid username or password for proxy '%s'", s); + } + else + soup_auth_authenticate (auth, soup_uri_get_user (uri), + soup_uri_get_password (uri)); + } +} + static void session_thread_set_proxy_cb (ThreadClosure *thread_closure, gpointer data) @@ -285,6 +313,17 @@ session_thread_set_proxy_cb (ThreadClosure *thread_closure, g_object_set (thread_closure->session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL); + + /* libsoup won't necessarily pass any embedded username and password to proxy + * requests, so we have to be ready to handle 407 and handle them ourselves. + * See also: https://bugzilla.gnome.org/show_bug.cgi?id=772932 + * */ + if (soup_uri_get_user (proxy_uri) && + soup_uri_get_password (proxy_uri)) + { + g_signal_connect (thread_closure->session, "authenticate", + G_CALLBACK (on_authenticate), thread_closure); + } } #ifdef HAVE_LIBSOUP_CLIENT_CERTS @@ -998,10 +1037,23 @@ on_request_sent (GObject *object, code = G_IO_ERROR_FAILED; } - local_error = g_error_new (G_IO_ERROR, code, - "Server returned status %u: %s", - msg->status_code, - soup_status_get_phrase (msg->status_code)); + { + g_autofree char *errmsg = + g_strdup_printf ("Server returned status %u: %s", + msg->status_code, + soup_status_get_phrase (msg->status_code)); + + /* Let's make OOB errors be the final one since they're probably + * the cause for the error here. */ + if (pending->thread_closure->oob_error) + { + local_error = + g_error_copy (pending->thread_closure->oob_error); + g_prefix_error (&local_error, "%s: ", errmsg); + } + else + local_error = g_error_new_literal (G_IO_ERROR, code, errmsg); + } if (pending->mirrorlist->len > 1) g_prefix_error (&local_error, From 1d4f1b8878addd28059c3a3928640491755cd615 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 4 Oct 2016 15:23:04 -0400 Subject: [PATCH 06/12] core: Do create hardlinks to symlinks for checkouts I was noticing a recent performance issue with checkouts which seemed to be mostly us going back to doing a `fsync()` on directories. Regardless, while looking at that, I saw we were spending time creating new symlinks. Even though symlinks are small, it's still better to hardlink them. Going way back in time, the reason we weren't doing this is because we were hitting `EMFILE` on ext4, but that was for gnome-continuous which creates *many* build roots. Even there though, they're just a cache, and we handle `EMFILE`. For ostree-for-host-system, we don't expect to have many roots (just 3 at most transiently), so hardlinking symlinks does make sense. Closes: #521 Approved by: jlebon --- src/libostree/ostree-repo-checkout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libostree/ostree-repo-checkout.c b/src/libostree/ostree-repo-checkout.c index 23258a47..298fcd15 100644 --- a/src/libostree/ostree-repo-checkout.c +++ b/src/libostree/ostree-repo-checkout.c @@ -437,7 +437,7 @@ checkout_one_file_at (OstreeRepo *repo, need_copy = FALSE; } - else if (!is_symlink) + else { gboolean did_hardlink = FALSE; /* Try to do a hardlink first, if it's a regular file. This also From e62defa1a4d3dd0caf02884f2f45a81301df51dc Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Wed, 19 Oct 2016 12:37:54 -0400 Subject: [PATCH 07/12] add .redhat-ci.yml and .redhat-ci.Dockerfile Add a YAML file for the new Red Hat CI framework. Rather than re-installing all the build deps everytime, which takes time, I added a Dockerfile that we can wire up to the Docker Hub. For now it lives at `jlebon/ostree-tester:rhci`, but we can move it under the `projectatomic` org (or a new `ostree` org). Closes: #535 Approved by: cgwalters --- .redhat-ci.Dockerfile | 21 +++++++++++++++++++++ .redhat-ci.yml | 25 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 .redhat-ci.Dockerfile create mode 100644 .redhat-ci.yml diff --git a/.redhat-ci.Dockerfile b/.redhat-ci.Dockerfile new file mode 100644 index 00000000..304563ef --- /dev/null +++ b/.redhat-ci.Dockerfile @@ -0,0 +1,21 @@ +FROM fedora:24 +MAINTAINER Jonathan Lebon + +RUN dnf install -y \ + gcc \ + sudo \ + which \ + attr \ + fuse \ + gjs \ + parallel \ + gnome-desktop-testing \ + redhat-rpm-config \ + elfutils \ + 'dnf-command(builddep)' \ + && dnf builddep -y \ + ostree \ + && dnf clean all + +# create an unprivileged user for testing +RUN adduser testuser diff --git a/.redhat-ci.yml b/.redhat-ci.yml new file mode 100644 index 00000000..288f82c7 --- /dev/null +++ b/.redhat-ci.yml @@ -0,0 +1,25 @@ +branches: + - master + - auto + - try + +container: + image: jlebon/ostree-tester:rhci + +tests: + - sh autogen.sh + --prefix=/usr + --libdir=/usr/lib64 + --enable-installed-tests + --enable-gtk-doc + - make -j2 + - make syntax-check + - make check + - make install + - gnome-desktop-testing-runner ostree + - sudo --user=testuser gnome-desktop-testing-runner ostree + +timeout: 30m + +artifacts: + - test-suite.log From 64568bc706bc7d2e4d9814e0daa837b9ad84f009 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Wed, 19 Oct 2016 16:49:56 -0400 Subject: [PATCH 08/12] .redhat-ci.yml: use projectatomic/ostree-tester Same Dockerfile, but automated to rebuild on pushes. Closes: #536 Approved by: giuseppe --- .redhat-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.redhat-ci.yml b/.redhat-ci.yml index 288f82c7..1956cb30 100644 --- a/.redhat-ci.yml +++ b/.redhat-ci.yml @@ -4,7 +4,7 @@ branches: - try container: - image: jlebon/ostree-tester:rhci + image: projectatomic/ostree-tester tests: - sh autogen.sh From cf6ec1bbbcfea78a85255a6cc4e997a6aca54e73 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 21 Oct 2016 11:16:42 +0200 Subject: [PATCH 09/12] Fix regression for symlinks in bare-user repos Commit 1d4f1b8878addd28059c3a3928640491755cd615 started using hardlinks checkouts of symlinks. However, symlinks are not stored as symlink in the repo for bare-user repos, so this breaks user-mode checkouts of such repos. We fix this by checking for !is_symlink in the bare-user case. This fixes: https://github.com/ostreedev/ostree/issues/537 Closes: #538 Approved by: giuseppe --- src/libostree/ostree-repo-checkout.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libostree/ostree-repo-checkout.c b/src/libostree/ostree-repo-checkout.c index 298fcd15..9b640648 100644 --- a/src/libostree/ostree-repo-checkout.c +++ b/src/libostree/ostree-repo-checkout.c @@ -450,7 +450,9 @@ checkout_one_file_at (OstreeRepo *repo, gboolean is_bare = ((current_repo->mode == OSTREE_REPO_MODE_BARE && options->mode == OSTREE_REPO_CHECKOUT_MODE_NONE) || (current_repo->mode == OSTREE_REPO_MODE_BARE_USER - && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER)); + && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER + /* NOTE: bare-user symlinks are not stored as symlinks */ + && !is_symlink)); 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 From d43c1216759564e974782695ecdade1fa827c343 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 21 Oct 2016 10:13:54 +0200 Subject: [PATCH 10/12] ostree_repo_read_commit_detached_metadata: Handle parent repo If the detached metadata is not in the repo, try in the parent repo if that is set. Without this a commit will not gpg validate in the child repo Closes: #539 Approved by: giuseppe --- src/libostree/ostree-repo-commit.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index 8dfe276f..3d8008f7 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -2044,6 +2044,12 @@ ostree_repo_read_commit_detached_metadata (OstreeRepo *self, goto out; } + if (ret_metadata == NULL && self->parent_repo) + return ostree_repo_read_commit_detached_metadata (self->parent_repo, + checksum, + out_metadata, + cancellable, + error); ret = TRUE; ot_transfer_out_value (out_metadata, &ret_metadata); out: From 67bddf76f7ed629624e409b824849f6e8dc503a3 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 21 Oct 2016 12:10:30 +0200 Subject: [PATCH 11/12] detached metadata: Put these in transaction If there is a transaction active, then we put writes to detached metadata into the staging dir, and when reading it we look there first. This allows transactions to be aborted half-way without writing the detached metadata into the repository (possibly overwriting any old metadata from there). This fixes https://github.com/ostreedev/ostree/issues/526 Closes: #539 Approved by: giuseppe --- src/libostree/ostree-repo-commit.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index 3d8008f7..42e06400 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -2035,8 +2035,18 @@ ostree_repo_read_commit_detached_metadata (OstreeRepo *self, g_autoptr(GVariant) ret_metadata = NULL; _ostree_loose_path (buf, checksum, OSTREE_OBJECT_TYPE_COMMIT_META, self->mode); - - if (!ot_util_variant_map_at (self->objects_dir_fd, buf, + + if (self->commit_stagedir_fd != -1 && + !ot_util_variant_map_at (self->commit_stagedir_fd, buf, + G_VARIANT_TYPE ("a{sv}"), + OT_VARIANT_MAP_ALLOW_NOENT | OT_VARIANT_MAP_TRUSTED, &ret_metadata, error)) + { + g_prefix_error (error, "Unable to read existing detached metadata: "); + goto out; + } + + if (ret_metadata == NULL && + !ot_util_variant_map_at (self->objects_dir_fd, buf, G_VARIANT_TYPE ("a{sv}"), OT_VARIANT_MAP_ALLOW_NOENT | OT_VARIANT_MAP_TRUSTED, &ret_metadata, error)) { @@ -2079,10 +2089,16 @@ ostree_repo_write_commit_detached_metadata (OstreeRepo *self, g_autoptr(GVariant) normalized = NULL; gsize normalized_size = 0; const guint8 *data = NULL; + int dest_dfd; + + if (self->in_transaction) + dest_dfd = self->commit_stagedir_fd; + else + dest_dfd = self->objects_dir_fd; _ostree_loose_path (pathbuf, checksum, OSTREE_OBJECT_TYPE_COMMIT_META, self->mode); - if (!_ostree_repo_ensure_loose_objdir_at (self->objects_dir_fd, checksum, + if (!_ostree_repo_ensure_loose_objdir_at (dest_dfd, checksum, cancellable, error)) return FALSE; @@ -2096,7 +2112,7 @@ ostree_repo_write_commit_detached_metadata (OstreeRepo *self, if (data == NULL) data = (guint8*)""; - if (!glnx_file_replace_contents_at (self->objects_dir_fd, pathbuf, + if (!glnx_file_replace_contents_at (dest_dfd, pathbuf, data, normalized_size, 0, cancellable, error)) { From d3f14f02e3d9f7259c7ec6b25980ae43f03c4906 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 21 Oct 2016 14:43:29 -0400 Subject: [PATCH 12/12] Release 2016.12 Closes: #540 Approved by: cgwalters --- configure.ac | 2 +- src/libostree/libostree.sym | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index e69d94e7..7a6fdf13 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ AC_PREREQ([2.63]) dnl If incrementing the version here, remember to update libostree.sym too -AC_INIT([ostree], [2016.11], [walters@verbum.org]) +AC_INIT([ostree], [2016.12], [walters@verbum.org]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([buildutil]) AC_CONFIG_AUX_DIR([build-aux]) diff --git a/src/libostree/libostree.sym b/src/libostree/libostree.sym index 4aa8d6cf..ddb87e7b 100644 --- a/src/libostree/libostree.sym +++ b/src/libostree/libostree.sym @@ -356,6 +356,7 @@ global: /* No new symbols in 2016.9 */ /* No new symbols in 2016.10 */ /* No new symbols in 2016.11 */ +/* No new symbols in 2016.12 */ /* NOTE NOTE NOTE * Versions above here are released. Only add symbols below this line.