static gboolean delete_commit (OstreeRepo *repo, const char *commit_to_delete, GCancellable *cancellable, GError **error) { g_autoptr(GHashTable) refs = NULL; GHashTableIter hashiter; gpointer hashkey, hashvalue; gboolean ret = FALSE; if (!ostree_repo_list_refs (repo, NULL, &refs, cancellable, error)) goto out; g_hash_table_iter_init (&hashiter, refs); while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue)) { const char *ref = hashkey; const char *commit = hashvalue; if (g_strcmp0 (commit_to_delete, commit) == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Commit '%s' is referenced by '%s'", commit_to_delete, ref); goto out; } } if (!ot_enable_tombstone_commits (repo, error)) goto out; if (!ostree_repo_delete_object (repo, OSTREE_OBJECT_TYPE_COMMIT, commit_to_delete, cancellable, error)) goto out; ret = TRUE; out: return ret; }
/** * ostree_repo_traverse_reachable_refs: * @self: Repo * @depth: Depth of traversal * @reachable: (element-type GVariant GVariant): Set of reachable objects (will be modified) * @cancellable: Cancellable * @error: Error * * Add all commit objects directly reachable via a ref to @reachable. * * Locking: shared * Since: 2018.6 */ gboolean ostree_repo_traverse_reachable_refs (OstreeRepo *self, guint depth, GHashTable *reachable, GCancellable *cancellable, GError **error) { g_autoptr(OstreeRepoAutoLock) lock = _ostree_repo_auto_lock_push (self, OSTREE_REPO_LOCK_SHARED, cancellable, error); if (!lock) return FALSE; /* Ignoring collections. */ g_autoptr(GHashTable) all_refs = NULL; /* (element-type utf8 utf8) */ if (!ostree_repo_list_refs (self, NULL, &all_refs, cancellable, error)) return FALSE; GLNX_HASH_TABLE_FOREACH_V (all_refs, const char*, checksum) { g_debug ("Finding objects to keep for commit %s", checksum); if (!ostree_repo_traverse_commit_union (self, checksum, depth, reachable, cancellable, error)) return FALSE; }
gboolean ostree_builtin_refs (int argc, char **argv, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; GOptionContext *context; gs_unref_object OstreeRepo *repo = NULL; const char *refspec_prefix = NULL; gs_unref_hashtable GHashTable *refs = NULL; GHashTableIter hashiter; gpointer hashkey, hashvalue; context = g_option_context_new ("[PREFIX] - List refs"); if (!ostree_option_context_parse (context, options, &argc, &argv, OSTREE_BUILTIN_FLAG_NONE, &repo, cancellable, error)) goto out; if (argc >= 2) refspec_prefix = argv[1]; if (!ostree_repo_list_refs (repo, refspec_prefix, &refs, cancellable, error)) goto out; if (!opt_delete) { g_hash_table_iter_init (&hashiter, refs); while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue)) { const char *ref = hashkey; g_print ("%s\n", ref); } } else { g_hash_table_iter_init (&hashiter, refs); while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue)) { const char *refspec = hashkey; gs_free char *remote = NULL; gs_free char *ref = NULL; if (!ostree_parse_refspec (refspec, &remote, &ref, error)) goto out; if (!ostree_repo_set_ref_immediate (repo, remote, ref, NULL, cancellable, error)) goto out; } } ret = TRUE; out: if (context) g_option_context_free (context); if (repo) ostree_repo_abort_transaction (repo, cancellable, NULL); return ret; }
gboolean ostree_builtin_reset (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = NULL; g_autoptr(OstreeRepo) repo = NULL; g_autoptr(GHashTable) known_refs = NULL; gboolean ret = FALSE; const char *ref; const char *target = NULL; g_autofree char *checksum = NULL; /* FIXME: Add support for collection–refs. */ context = g_option_context_new ("REF COMMIT"); if (!ostree_option_context_parse (context, options, &argc, &argv, invocation, &repo, cancellable, error)) goto out; if (!ostree_ensure_repo_writable (repo, error)) goto out; if (argc <= 2) { ot_util_usage_error (context, "A REF and COMMIT argument is required", error); goto out; } ref = argv[1]; target = argv[2]; if (!ostree_repo_list_refs (repo, NULL, &known_refs, cancellable, error)) goto out; if (!g_hash_table_contains (known_refs, ref)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR, "Invalid ref '%s'", ref); goto out; } if (!ostree_repo_resolve_rev (repo, target, FALSE, &checksum, error)) goto out; if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) goto out; ostree_repo_transaction_set_ref (repo, NULL, ref, checksum); if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) goto out; ret = TRUE; out: if (repo) ostree_repo_abort_transaction (repo, cancellable, NULL); return ret; }
static gboolean cleanup_ref_prefix (OstreeRepo *repo, int bootversion, int subbootversion, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gs_free char *prefix = NULL; gs_unref_hashtable GHashTable *refs = NULL; GHashTableIter hashiter; gpointer hashkey, hashvalue; prefix = g_strdup_printf ("ostree/%d/%d", bootversion, subbootversion); if (!ostree_repo_list_refs (repo, prefix, &refs, cancellable, error)) goto out; if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) goto out; g_hash_table_iter_init (&hashiter, refs); while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue)) { const char *suffix = hashkey; gs_free char *ref = g_strconcat (prefix, "/", suffix, NULL); ostree_repo_transaction_set_refspec (repo, ref, NULL); } if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) goto out; ret = TRUE; out: ostree_repo_abort_transaction (repo, cancellable, NULL); return ret; }
gboolean ostree_builtin_fsck (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr(OstreeRepo) repo = NULL; gboolean found_corruption = FALSE; g_autoptr(GOptionContext) context = g_option_context_new (""); if (!ostree_option_context_parse (context, options, &argc, &argv, invocation, &repo, cancellable, error)) return FALSE; if (!opt_quiet) g_print ("Validating refs...\n"); /* Validate that the commit for each ref is available */ g_autoptr(GHashTable) all_refs = NULL; if (!ostree_repo_list_refs (repo, NULL, &all_refs, cancellable, error)) return FALSE; GHashTableIter hash_iter; gpointer key, value; g_hash_table_iter_init (&hash_iter, all_refs); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { const char *refspec = key; const char *checksum = value; g_autofree char *ref_name = NULL; if (!ostree_parse_refspec (refspec, NULL, &ref_name, error)) return FALSE; if (!fsck_commit_for_ref (repo, checksum, NULL, ref_name, &found_corruption, cancellable, error)) return FALSE; } if (!opt_quiet) g_print ("Validating refs in collections...\n"); g_autoptr(GHashTable) all_collection_refs = NULL; /* (element-type OstreeCollectionRef utf8) */ if (!ostree_repo_list_collection_refs (repo, NULL, &all_collection_refs, OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES, cancellable, error)) return FALSE; g_hash_table_iter_init (&hash_iter, all_collection_refs); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { const OstreeCollectionRef *ref = key; if (!fsck_commit_for_ref (repo, value, ref->collection_id, ref->ref_name, &found_corruption, cancellable, error)) return FALSE; } if (!opt_quiet) g_print ("Enumerating objects...\n"); g_autoptr(GHashTable) objects = NULL; if (!ostree_repo_list_objects (repo, OSTREE_REPO_LIST_OBJECTS_ALL, &objects, cancellable, error)) return FALSE; g_autoptr(GHashTable) commits = g_hash_table_new_full (ostree_hash_object_name, g_variant_equal, (GDestroyNotify)g_variant_unref, NULL); g_autoptr(GPtrArray) tombstones = NULL; if (opt_add_tombstones) tombstones = g_ptr_array_new_with_free_func (g_free); if (opt_verify_back_refs) opt_verify_bindings = TRUE; guint n_partial = 0; g_hash_table_iter_init (&hash_iter, objects); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { GVariant *serialized_key = key; const char *checksum; OstreeObjectType objtype; OstreeRepoCommitState commitstate = 0; g_autoptr(GVariant) commit = NULL; ostree_object_name_deserialize (serialized_key, &checksum, &objtype); if (objtype == OSTREE_OBJECT_TYPE_COMMIT) { if (!ostree_repo_load_commit (repo, checksum, &commit, &commitstate, error)) return FALSE; /* If requested, check that all the refs listed in the ref-bindings * for this commit resolve back to this commit. */ if (opt_verify_back_refs) { g_autoptr(GVariant) metadata = g_variant_get_child_value (commit, 0); const char *collection_id = NULL; if (!g_variant_lookup (metadata, OSTREE_COMMIT_META_KEY_COLLECTION_BINDING, "&s", &collection_id)) collection_id = NULL; g_autofree const char **refs = NULL; if (g_variant_lookup (metadata, OSTREE_COMMIT_META_KEY_REF_BINDING, "^a&s", &refs)) { for (const char **iter = refs; *iter != NULL; ++iter) { g_autofree char *checksum_for_ref = NULL; if (collection_id != NULL) { const OstreeCollectionRef collection_ref = { (char *) collection_id, (char *) *iter }; if (!ostree_repo_resolve_collection_ref (repo, &collection_ref, TRUE, OSTREE_REPO_RESOLVE_REV_EXT_NONE, &checksum_for_ref, cancellable, error)) return FALSE; } else { if (!ostree_repo_resolve_rev (repo, *iter, TRUE, &checksum_for_ref, error)) return FALSE; } if (checksum_for_ref == NULL) { if (collection_id != NULL) return glnx_throw (error, "Collection–ref (%s, %s) in bindings for commit %s does not exist", collection_id, *iter, checksum); else return glnx_throw (error, "Ref ‘%s’ in bindings for commit %s does not exist", *iter, checksum); } else if (g_strcmp0 (checksum_for_ref, checksum) != 0) { if (collection_id != NULL) return glnx_throw (error, "Collection–ref (%s, %s) in bindings for commit %s does not resolve to that commit", collection_id, *iter, checksum); else return glnx_throw (error, "Ref ‘%s’ in bindings for commit %s does not resolve to that commit", *iter, checksum); } } } } if (opt_add_tombstones) { GError *local_error = NULL; g_autofree char *parent = ostree_commit_get_parent (commit); if (parent) { g_autoptr(GVariant) parent_commit = NULL; if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, parent, &parent_commit, &local_error)) { if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_ptr_array_add (tombstones, g_strdup (checksum)); g_clear_error (&local_error); } else { g_propagate_error (error, local_error); return FALSE; } } } } if (commitstate & OSTREE_REPO_COMMIT_STATE_PARTIAL) n_partial++; else g_hash_table_add (commits, g_variant_ref (serialized_key)); } } g_clear_pointer (&objects, (GDestroyNotify) g_hash_table_unref); if (!opt_quiet) g_print ("Verifying content integrity of %u commit objects...\n", (guint)g_hash_table_size (commits)); if (!fsck_reachable_objects_from_commits (repo, commits, &found_corruption, cancellable, error)) return FALSE; if (opt_add_tombstones) { guint i; if (tombstones->len) { if (!ot_enable_tombstone_commits (repo, error)) return FALSE; } for (i = 0; i < tombstones->len; i++) { const char *checksum = tombstones->pdata[i]; g_print ("Adding tombstone for commit %s\n", checksum); if (!ostree_repo_delete_object (repo, OSTREE_OBJECT_TYPE_COMMIT, checksum, cancellable, error)) return FALSE; } } else if (n_partial > 0) { g_print ("%u partial commits not verified\n", n_partial); } if (found_corruption) return glnx_throw (error, "Repository corruption encountered"); return TRUE; }
static gboolean prune_commits_keep_younger_than_date (OstreeRepo *repo, const char *date, GCancellable *cancellable, GError **error) { g_autoptr(GHashTable) refs = NULL; g_autoptr(GHashTable) ref_heads = g_hash_table_new (g_str_hash, g_str_equal); g_autoptr(GHashTable) objects = NULL; GHashTableIter hash_iter; gpointer key, value; struct timespec ts; gboolean ret = FALSE; if (!parse_datetime (&ts, date, NULL)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Could not parse '%s'", date); goto out; } if (!ot_enable_tombstone_commits (repo, error)) goto out; if (!ostree_repo_list_refs (repo, NULL, &refs, cancellable, error)) goto out; /* We used to prune the HEAD of a given ref by default, but that's * broken for a few reasons. One is that people may use branches as * tags. Second is that if we do it, we should be deleting the ref * too, otherwise e.g. `summary -u` breaks trying to load it, etc. */ g_hash_table_iter_init (&hash_iter, refs); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { /* Value is lifecycle bound to refs */ g_hash_table_add (ref_heads, (char*)value); } if (!ostree_repo_list_objects (repo, OSTREE_REPO_LIST_OBJECTS_ALL, &objects, cancellable, error)) goto out; g_hash_table_iter_init (&hash_iter, objects); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { GVariant *serialized_key = key; const char *checksum; OstreeObjectType objtype; guint64 commit_timestamp; g_autoptr(GVariant) commit = NULL; ostree_object_name_deserialize (serialized_key, &checksum, &objtype); if (objtype != OSTREE_OBJECT_TYPE_COMMIT) continue; if (g_hash_table_contains (ref_heads, checksum)) continue; if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, checksum, &commit, error)) goto out; commit_timestamp = ostree_commit_get_timestamp (commit); if (commit_timestamp < ts.tv_sec) { if (opt_static_deltas_only) { if(!ostree_repo_prune_static_deltas (repo, checksum, cancellable, error)) goto out; } else { if (!ostree_repo_delete_object (repo, OSTREE_OBJECT_TYPE_COMMIT, checksum, cancellable, error)) goto out; } } } ret = TRUE; out: return ret; }
gboolean flatpak_builtin_build_commit_from (int argc, char **argv, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = NULL; g_autoptr(GFile) dst_repofile = NULL; g_autoptr(OstreeRepo) dst_repo = NULL; g_autoptr(GFile) src_repofile = NULL; g_autoptr(OstreeRepo) src_repo = NULL; g_autofree char *src_repo_uri = NULL; const char *dst_repo_arg; const char **dst_refs; int n_dst_refs = 0; g_autoptr(FlatpakRepoTransaction) transaction = NULL; g_autoptr(GPtrArray) src_refs = NULL; g_autoptr(GPtrArray) resolved_src_refs = NULL; OstreeRepoCommitState src_commit_state; struct timespec ts; guint64 timestamp; int i; context = g_option_context_new (_("DST-REPO [DST-REF…] - Make a new commit from existing commits")); g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); if (!flatpak_option_context_parse (context, options, &argc, &argv, FLATPAK_BUILTIN_FLAG_NO_DIR, NULL, cancellable, error)) return FALSE; if (argc < 2) return usage_error (context, _("DST-REPO must be specified"), error); dst_repo_arg = argv[1]; dst_refs = (const char **) argv + 2; n_dst_refs = argc - 2; if (opt_src_repo == NULL && n_dst_refs != 1) return usage_error (context, _("If --src-repo is not specified, exactly one destination ref must be specified"), error); if (opt_src_ref != NULL && n_dst_refs != 1) return usage_error (context, _("If --src-ref is specified, exactly one destination ref must be specified"), error); if (opt_src_repo == NULL && opt_src_ref == NULL) return flatpak_fail (error, _("Either --src-repo or --src-ref must be specified.")); /* Always create a commit if we're eol:ing, even though the app is the same */ if (opt_endoflife != NULL || opt_endoflife_rebase != NULL) opt_force = TRUE; if (opt_endoflife_rebase) { opt_endoflife_rebase_new = g_new0 (char *, g_strv_length (opt_endoflife_rebase)); for (i = 0; opt_endoflife_rebase[i] != NULL; i++) { char *rebase_old = opt_endoflife_rebase[i]; char *rebase_new = strchr (rebase_old, '='); if (rebase_new == NULL) { return usage_error (context, _("Invalid argument format of use --end-of-life-rebase=OLDID=NEWID"), error); } *rebase_new = 0; rebase_new++; if (!flatpak_is_valid_name (rebase_old, error)) return glnx_prefix_error (error, _("Invalid name %s in --end-of-life-rebase"), rebase_old); if (!flatpak_is_valid_name (rebase_new, error)) return glnx_prefix_error (error, _("Invalid name %s in --end-of-life-rebase"), rebase_new); opt_endoflife_rebase_new[i] = rebase_new; } } if (opt_timestamp) { if (!parse_datetime (&ts, opt_timestamp, NULL)) return flatpak_fail (error, _("Could not parse '%s'"), opt_timestamp); } dst_repofile = g_file_new_for_commandline_arg (dst_repo_arg); if (!g_file_query_exists (dst_repofile, cancellable)) return flatpak_fail (error, _("'%s' is not a valid repository"), dst_repo_arg); dst_repo = ostree_repo_new (dst_repofile); if (!ostree_repo_open (dst_repo, cancellable, error)) return FALSE; if (opt_disable_fsync) ostree_repo_set_disable_fsync (dst_repo, TRUE); if (opt_src_repo) { src_repofile = g_file_new_for_commandline_arg (opt_src_repo); if (!g_file_query_exists (src_repofile, cancellable)) return flatpak_fail (error, _("'%s' is not a valid repository"), opt_src_repo); src_repo_uri = g_file_get_uri (src_repofile); src_repo = ostree_repo_new (src_repofile); if (!ostree_repo_open (src_repo, cancellable, error)) return FALSE; } else { src_repo = g_object_ref (dst_repo); } src_refs = g_ptr_array_new_with_free_func (g_free); if (opt_src_ref) { g_assert (n_dst_refs == 1); g_ptr_array_add (src_refs, g_strdup (opt_src_ref)); } else { g_assert (opt_src_repo != NULL); if (n_dst_refs == 0) { g_autofree const char **keys = NULL; g_autoptr(GHashTable) all_src_refs = NULL; if (!ostree_repo_list_refs (src_repo, NULL, &all_src_refs, cancellable, error)) return FALSE; keys = (const char **) g_hash_table_get_keys_as_array (all_src_refs, NULL); for (i = 0; keys[i] != NULL; i++) { if (g_str_has_prefix (keys[i], "runtime/") || g_str_has_prefix (keys[i], "app/")) g_ptr_array_add (src_refs, g_strdup (keys[i])); } n_dst_refs = src_refs->len; dst_refs = (const char **) src_refs->pdata; } else { for (i = 0; i < n_dst_refs; i++) g_ptr_array_add (src_refs, g_strdup (dst_refs[i])); } } resolved_src_refs = g_ptr_array_new_with_free_func (g_free); for (i = 0; i < src_refs->len; i++) { const char *src_ref = g_ptr_array_index (src_refs, i); char *resolved_ref; if (!ostree_repo_resolve_rev (src_repo, src_ref, FALSE, &resolved_ref, error)) return FALSE; g_ptr_array_add (resolved_src_refs, resolved_ref); } if (src_repo_uri != NULL) { OstreeRepoPullFlags pullflags = 0; GVariantBuilder builder; g_autoptr(OstreeAsyncProgress) progress = NULL; g_auto(GLnxConsoleRef) console = { 0, }; g_autoptr(GVariant) options = NULL; gboolean res; if (opt_untrusted) pullflags |= OSTREE_REPO_PULL_FLAGS_UNTRUSTED; glnx_console_lock (&console); if (console.is_tty) progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, &console); g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (&builder, "{s@v}", "flags", g_variant_new_variant (g_variant_new_int32 (pullflags))); g_variant_builder_add (&builder, "{s@v}", "refs", g_variant_new_variant (g_variant_new_strv ((const char * const *) resolved_src_refs->pdata, resolved_src_refs->len))); g_variant_builder_add (&builder, "{s@v}", "depth", g_variant_new_variant (g_variant_new_int32 (0))); options = g_variant_ref_sink (g_variant_builder_end (&builder)); res = ostree_repo_pull_with_options (dst_repo, src_repo_uri, options, progress, cancellable, error); if (progress) ostree_async_progress_finish (progress); if (!res) return FALSE; } /* By now we have the commit with commit_id==resolved_ref and dependencies in dst_repo. We now create a new * commit based on the toplevel tree ref from that commit. * This is equivalent to: * ostree commit --skip-if-unchanged --repo=${destrepo} --tree=ref=${resolved_ref} */ transaction = flatpak_repo_transaction_start (dst_repo, cancellable, error); if (transaction == NULL) return FALSE; for (i = 0; i < resolved_src_refs->len; i++) { const char *dst_ref = dst_refs[i]; const char *resolved_ref = g_ptr_array_index (resolved_src_refs, i); g_autofree char *dst_parent = NULL; g_autoptr(GFile) dst_parent_root = NULL; g_autoptr(GFile) src_ref_root = NULL; g_autoptr(GVariant) src_commitv = NULL; g_autoptr(GVariant) dst_commitv = NULL; g_autoptr(OstreeMutableTree) mtree = NULL; g_autoptr(GFile) dst_root = NULL; g_autoptr(GVariant) commitv_metadata = NULL; g_autoptr(GVariant) metadata = NULL; const char *subject; const char *body; g_autofree char *commit_checksum = NULL; GVariantBuilder metadata_builder; gint j; const char *dst_collection_id = NULL; const char *main_collection_id = NULL; g_autoptr(GPtrArray) collection_ids = NULL; if (!ostree_repo_resolve_rev (dst_repo, dst_ref, TRUE, &dst_parent, error)) return FALSE; if (dst_parent != NULL && !ostree_repo_read_commit (dst_repo, dst_parent, &dst_parent_root, NULL, cancellable, error)) return FALSE; if (!ostree_repo_read_commit (dst_repo, resolved_ref, &src_ref_root, NULL, cancellable, error)) return FALSE; if (!ostree_repo_load_commit (dst_repo, resolved_ref, &src_commitv, &src_commit_state, error)) return FALSE; if (src_commit_state & OSTREE_REPO_COMMIT_STATE_PARTIAL) return flatpak_fail (error, _("Can't commit from partial source commit.")); /* Don't create a new commit if this is the same tree */ if (!opt_force && dst_parent_root != NULL && g_file_equal (dst_parent_root, src_ref_root)) { g_print (_("%s: no change\n"), dst_ref); continue; } mtree = ostree_mutable_tree_new (); if (!ostree_repo_write_directory_to_mtree (dst_repo, src_ref_root, mtree, NULL, cancellable, error)) return FALSE; if (!ostree_repo_write_mtree (dst_repo, mtree, &dst_root, cancellable, error)) return FALSE; commitv_metadata = g_variant_get_child_value (src_commitv, 0); g_variant_get_child (src_commitv, 3, "&s", &subject); if (opt_subject) subject = (const char *) opt_subject; g_variant_get_child (src_commitv, 4, "&s", &body); if (opt_body) body = (const char *) opt_body; dst_collection_id = ostree_repo_get_collection_id (dst_repo); collection_ids = g_ptr_array_new_with_free_func (g_free); if (dst_collection_id) { main_collection_id = dst_collection_id; g_ptr_array_add (collection_ids, g_strdup (dst_collection_id)); } if (opt_extra_collection_ids != NULL) { for (j = 0; opt_extra_collection_ids[j] != NULL; j++) { const char *cid = opt_extra_collection_ids[j]; if (main_collection_id == NULL) main_collection_id = cid; /* Fall back to first arg */ if (g_strcmp0 (cid, dst_collection_id) != 0) g_ptr_array_add (collection_ids, g_strdup (cid)); } } g_ptr_array_sort (collection_ids, (GCompareFunc) flatpak_strcmp0_ptr); /* Copy old metadata */ g_variant_builder_init (&metadata_builder, G_VARIANT_TYPE ("a{sv}")); /* Bindings. xa.ref is deprecated but added anyway for backwards compatibility. */ g_variant_builder_add (&metadata_builder, "{sv}", "ostree.collection-binding", g_variant_new_string (main_collection_id ? main_collection_id : "")); if (collection_ids->len > 0) { g_autoptr(GVariantBuilder) cr_builder = g_variant_builder_new (G_VARIANT_TYPE ("a(ss)")); for (j = 0; j < collection_ids->len; j++) g_variant_builder_add (cr_builder, "(ss)", g_ptr_array_index (collection_ids, j), dst_ref); g_variant_builder_add (&metadata_builder, "{sv}", "ostree.collection-refs-binding", g_variant_builder_end (cr_builder)); } g_variant_builder_add (&metadata_builder, "{sv}", "ostree.ref-binding", g_variant_new_strv (&dst_ref, 1)); g_variant_builder_add (&metadata_builder, "{sv}", "xa.ref", g_variant_new_string (dst_ref)); /* Record the source commit. This is nice to have, but it also means the commit-from gets a different commit id, which avoids problems with e.g. sharing .commitmeta files (signatures) */ g_variant_builder_add (&metadata_builder, "{sv}", "xa.from_commit", g_variant_new_string (resolved_ref)); for (j = 0; j < g_variant_n_children (commitv_metadata); j++) { g_autoptr(GVariant) child = g_variant_get_child_value (commitv_metadata, j); g_autoptr(GVariant) keyv = g_variant_get_child_value (child, 0); const char *key = g_variant_get_string (keyv, NULL); if (strcmp (key, "xa.ref") == 0 || strcmp (key, "xa.from_commit") == 0 || strcmp (key, "ostree.collection-binding") == 0 || strcmp (key, "ostree.collection-refs-binding") == 0 || strcmp (key, "ostree.ref-binding") == 0) continue; if (opt_endoflife && strcmp (key, OSTREE_COMMIT_META_KEY_ENDOFLIFE) == 0) continue; if (opt_endoflife_rebase && strcmp (key, OSTREE_COMMIT_META_KEY_ENDOFLIFE_REBASE) == 0) continue; g_variant_builder_add_value (&metadata_builder, child); } if (opt_endoflife && *opt_endoflife) g_variant_builder_add (&metadata_builder, "{sv}", OSTREE_COMMIT_META_KEY_ENDOFLIFE, g_variant_new_string (opt_endoflife)); if (opt_endoflife_rebase) { g_auto(GStrv) dst_ref_parts = g_strsplit (dst_ref, "/", 0); for (j = 0; opt_endoflife_rebase[j] != NULL; j++) { const char *old_prefix = opt_endoflife_rebase[j]; if (flatpak_has_name_prefix (dst_ref_parts[1], old_prefix)) { g_autofree char *new_id = g_strconcat (opt_endoflife_rebase_new[j], dst_ref_parts[1] + strlen(old_prefix), NULL); g_autofree char *rebased_ref = g_build_filename (dst_ref_parts[0], new_id, dst_ref_parts[2], dst_ref_parts[3], NULL); g_variant_builder_add (&metadata_builder, "{sv}", OSTREE_COMMIT_META_KEY_ENDOFLIFE_REBASE, g_variant_new_string (rebased_ref)); break; } } } timestamp = ostree_commit_get_timestamp (src_commitv); if (opt_timestamp) timestamp = ts.tv_sec; metadata = g_variant_ref_sink (g_variant_builder_end (&metadata_builder)); if (!ostree_repo_write_commit_with_time (dst_repo, dst_parent, subject, body, metadata, OSTREE_REPO_FILE (dst_root), timestamp, &commit_checksum, cancellable, error)) return FALSE; g_print ("%s: %s\n", dst_ref, commit_checksum); if (!ostree_repo_load_commit (dst_repo, commit_checksum, &dst_commitv, NULL, error)) return FALSE; /* This doesn't copy the detached metadata. I'm not sure if this is a problem. * The main thing there is commit signatures, and we can't copy those, as the commit hash changes. */ if (opt_gpg_key_ids) { char **iter; for (iter = opt_gpg_key_ids; iter && *iter; iter++) { const char *keyid = *iter; g_autoptr(GError) my_error = NULL; if (!ostree_repo_sign_commit (dst_repo, commit_checksum, keyid, opt_gpg_homedir, cancellable, &my_error) && !g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_EXISTS)) { g_propagate_error (error, g_steal_pointer (&my_error)); return FALSE; } } } if (dst_collection_id != NULL) { OstreeCollectionRef ref = { (char *) dst_collection_id, (char *) dst_ref }; ostree_repo_transaction_set_collection_ref (dst_repo, &ref, commit_checksum); } else { ostree_repo_transaction_set_ref (dst_repo, NULL, dst_ref, commit_checksum); } if (opt_extra_collection_ids) { for (j = 0; opt_extra_collection_ids[j] != NULL; j++) { OstreeCollectionRef ref = { (char *) opt_extra_collection_ids[j], (char *) dst_ref }; ostree_repo_transaction_set_collection_ref (dst_repo, &ref, commit_checksum); } } /* Copy + Rewrite any deltas */ { const char *from[2]; gsize j, n_from = 0; if (dst_parent != NULL) from[n_from++] = dst_parent; from[n_from++] = NULL; for (j = 0; j < n_from; j++) { g_autoptr(GError) local_error = NULL; if (!rewrite_delta (src_repo, resolved_ref, dst_repo, commit_checksum, dst_commitv, from[j], &local_error)) g_debug ("Failed to copy delta: %s", local_error->message); } } } if (!ostree_repo_commit_transaction (dst_repo, NULL, cancellable, error)) return FALSE; if (opt_update_appstream && !flatpak_repo_generate_appstream (dst_repo, (const char **) opt_gpg_key_ids, opt_gpg_homedir, 0, cancellable, error)) return FALSE; if (!opt_no_update_summary && !flatpak_repo_update (dst_repo, (const char **) opt_gpg_key_ids, opt_gpg_homedir, cancellable, error)) return FALSE; return TRUE; }
/** * ostree_repo_prune: * @self: Repo * @flags: Options controlling prune process * @depth: Stop traversal after this many iterations (-1 for unlimited) * @out_objects_total: (out): Number of objects found * @out_objects_pruned: (out): Number of objects deleted * @out_pruned_object_size_total: (out): Storage size in bytes of objects deleted * @cancellable: Cancellable * @error: Error * * Delete content from the repository. By default, this function will * only delete "orphaned" objects not referred to by any commit. This * can happen during a local commit operation, when we have written * content objects but not saved the commit referencing them. * * However, if %OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY is provided, instead * of traversing all commits, only refs will be used. Particularly * when combined with @depth, this is a convenient way to delete * history from the repository. * * Use the %OSTREE_REPO_PRUNE_FLAGS_NO_PRUNE to just determine * statistics on objects that would be deleted, without actually * deleting them. */ gboolean ostree_repo_prune (OstreeRepo *self, OstreeRepoPruneFlags flags, gint depth, gint *out_objects_total, gint *out_objects_pruned, guint64 *out_pruned_object_size_total, GCancellable *cancellable, GError **error) { GHashTableIter hash_iter; gpointer key, value; g_autoptr(GHashTable) objects = NULL; g_autoptr(GHashTable) all_refs = NULL; g_autoptr(GHashTable) reachable = NULL; gboolean refs_only = flags & OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY; reachable = ostree_repo_traverse_new_reachable (); /* This original prune API has fixed logic for traversing refs or all commits * combined with actually deleting content. The newer backend API just does * the deletion. */ if (refs_only) { if (!ostree_repo_list_refs (self, NULL, &all_refs, cancellable, error)) return FALSE; g_hash_table_iter_init (&hash_iter, all_refs); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { const char *checksum = value; g_debug ("Finding objects to keep for commit %s", checksum); if (!ostree_repo_traverse_commit_union (self, checksum, depth, reachable, cancellable, error)) return FALSE; } } if (!ostree_repo_list_objects (self, OSTREE_REPO_LIST_OBJECTS_ALL | OSTREE_REPO_LIST_OBJECTS_NO_PARENTS, &objects, cancellable, error)) return FALSE; if (!refs_only) { g_hash_table_iter_init (&hash_iter, objects); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { GVariant *serialized_key = key; const char *checksum; OstreeObjectType objtype; ostree_object_name_deserialize (serialized_key, &checksum, &objtype); if (objtype != OSTREE_OBJECT_TYPE_COMMIT) continue; g_debug ("Finding objects to keep for commit %s", checksum); if (!ostree_repo_traverse_commit_union (self, checksum, depth, reachable, cancellable, error)) return FALSE; } } { OstreeRepoPruneOptions opts = { flags, reachable }; return repo_prune_internal (self, objects, &opts, out_objects_total, out_objects_pruned, out_pruned_object_size_total, cancellable, error); } }
gboolean ostree_builtin_pull_local (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; g_autoptr(GOptionContext) context = NULL; g_autoptr(OstreeRepo) repo = NULL; int i; const char *src_repo_arg; g_autofree char *src_repo_uri = NULL; g_autoptr(OstreeAsyncProgress) progress = NULL; g_autoptr(GPtrArray) refs_to_fetch = NULL; OstreeRepoPullFlags pullflags = 0; context = g_option_context_new ("SRC_REPO [REFS...]"); if (!ostree_option_context_parse (context, options, &argc, &argv, invocation, &repo, cancellable, error)) goto out; if (!ostree_ensure_repo_writable (repo, error)) goto out; if (argc < 2) { gchar *help = g_option_context_get_help (context, TRUE, NULL); g_printerr ("%s\n", help); g_free (help); g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "DESTINATION must be specified"); goto out; } src_repo_arg = argv[1]; if (src_repo_arg[0] == '/') src_repo_uri = g_strconcat ("file://", src_repo_arg, NULL); else { g_autofree char *cwd = g_get_current_dir (); src_repo_uri = g_strconcat ("file://", cwd, "/", src_repo_arg, NULL); } if (opt_untrusted) pullflags |= OSTREE_REPO_PULL_FLAGS_UNTRUSTED; if (opt_bareuseronly_files) pullflags |= OSTREE_REPO_PULL_FLAGS_BAREUSERONLY_FILES; if (opt_disable_fsync) ostree_repo_set_disable_fsync (repo, TRUE); if (argc == 2) { g_autoptr(GFile) src_repo_path = g_file_new_for_path (src_repo_arg); g_autoptr(OstreeRepo) src_repo = ostree_repo_new (src_repo_path); g_autoptr(GHashTable) refs_to_clone = NULL; refs_to_fetch = g_ptr_array_new_with_free_func (g_free); if (!ostree_repo_open (src_repo, cancellable, error)) goto out; /* FIXME: This should grow support for pulling refs from refs/mirrors on * a local repository, using ostree_repo_list_collection_refs(). */ if (!ostree_repo_list_refs (src_repo, NULL, &refs_to_clone, cancellable, error)) goto out; { GHashTableIter hashiter; gpointer hkey, hvalue; g_hash_table_iter_init (&hashiter, refs_to_clone); while (g_hash_table_iter_next (&hashiter, &hkey, &hvalue)) g_ptr_array_add (refs_to_fetch, g_strdup (hkey)); } g_ptr_array_add (refs_to_fetch, NULL); } else { refs_to_fetch = g_ptr_array_new (); for (i = 2; i < argc; i++) { const char *ref = argv[i]; g_ptr_array_add (refs_to_fetch, (char*)ref); } g_ptr_array_add (refs_to_fetch, NULL); } { GVariantBuilder builder; g_autoptr(GVariant) opts = NULL; g_auto(GLnxConsoleRef) console = { 0, }; glnx_console_lock (&console); g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (&builder, "{s@v}", "flags", g_variant_new_variant (g_variant_new_int32 (pullflags))); g_variant_builder_add (&builder, "{s@v}", "refs", g_variant_new_variant (g_variant_new_strv ((const char *const*) refs_to_fetch->pdata, -1))); if (opt_remote) g_variant_builder_add (&builder, "{s@v}", "override-remote-name", g_variant_new_variant (g_variant_new_string (opt_remote))); g_variant_builder_add (&builder, "{s@v}", "require-static-deltas", g_variant_new_variant (g_variant_new_boolean (opt_require_static_deltas))); if (opt_gpg_verify) g_variant_builder_add (&builder, "{s@v}", "gpg-verify", g_variant_new_variant (g_variant_new_boolean (TRUE))); if (opt_gpg_verify_summary) g_variant_builder_add (&builder, "{s@v}", "gpg-verify-summary", g_variant_new_variant (g_variant_new_boolean (TRUE))); g_variant_builder_add (&builder, "{s@v}", "depth", g_variant_new_variant (g_variant_new_int32 (opt_depth))); if (console.is_tty) progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, &console); else progress = ostree_async_progress_new_and_connect (noninteractive_console_progress_changed, &console); opts = g_variant_ref_sink (g_variant_builder_end (&builder)); if (!ostree_repo_pull_with_options (repo, src_repo_uri, opts, progress, cancellable, error)) goto out; if (!console.is_tty) { g_assert (progress); const char *status = ostree_async_progress_get_status (progress); if (status) g_print ("%s\n", status); } ostree_async_progress_finish (progress); } ret = TRUE; out: if (repo) ostree_repo_abort_transaction (repo, cancellable, NULL); return ret; }
static gboolean generate_all_deltas (OstreeRepo *repo, GPtrArray **unwanted_deltas, GCancellable *cancellable, GError **error) { g_autoptr(GHashTable) all_refs = NULL; g_autoptr(GHashTable) all_deltas_hash = NULL; g_autoptr(GHashTable) wanted_deltas_hash = NULL; g_autoptr(GPtrArray) all_deltas = NULL; int i; GHashTableIter iter; gpointer key, value; g_autoptr(GVariantBuilder) parambuilder = NULL; g_autoptr(GVariant) params = NULL; int n_spawned_delta_generate = 0; g_autoptr(GMainContextPopDefault) context = NULL; g_autoptr(GPtrArray) ignore_patterns = g_ptr_array_new_with_free_func ((GDestroyNotify)g_pattern_spec_free); g_print ("Generating static deltas\n"); parambuilder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); /* Fall back for 1 meg files */ g_variant_builder_add (parambuilder, "{sv}", "min-fallback-size", g_variant_new_uint32 (1)); params = g_variant_ref_sink (g_variant_builder_end (parambuilder)); if (!ostree_repo_list_static_delta_names (repo, &all_deltas, cancellable, error)) return FALSE; wanted_deltas_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); all_deltas_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); for (i = 0; i < all_deltas->len; i++) g_hash_table_insert (all_deltas_hash, g_strdup (g_ptr_array_index (all_deltas, i)), NULL); if (!ostree_repo_list_refs (repo, NULL, &all_refs, cancellable, error)) return FALSE; context = flatpak_main_context_new_default (); if (opt_static_delta_ignore_refs != NULL) { for (i = 0; opt_static_delta_ignore_refs[i] != NULL; i++) g_ptr_array_add (ignore_patterns, g_pattern_spec_new (opt_static_delta_ignore_refs[i])); } g_hash_table_iter_init (&iter, all_refs); while (g_hash_table_iter_next (&iter, &key, &value)) { const char *ref = key; const char *commit = value; g_autoptr(GVariant) variant = NULL; g_autoptr(GVariant) parent_variant = NULL; g_autofree char *parent_commit = NULL; g_autofree char *grandparent_commit = NULL; gboolean ignore_ref = FALSE; if (g_str_has_prefix (ref, "app/") || g_str_has_prefix (ref, "runtime/")) { g_auto(GStrv) parts = g_strsplit (ref, "/", 4); for (i = 0; i < ignore_patterns->len; i++) { GPatternSpec *pattern = g_ptr_array_index(ignore_patterns, i); if (g_pattern_match_string (pattern, parts[1])) { ignore_ref = TRUE; break; } } } else if (g_str_has_prefix (ref, "appstream/")) { /* Old appstream branch deltas poorly, and most users handle the new format */ ignore_ref = TRUE; } else if (g_str_has_prefix (ref, "appstream2/")) { /* Always delta this */ ignore_ref = FALSE; } else { /* Ignore unknown ref types */ ignore_ref = FALSE; } if (ignore_ref) { g_debug ("Ignoring deltas for ref %s", ref); continue; } if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, commit, &variant, NULL)) { g_warning ("Couldn't load commit %s", commit); continue; } /* From empty */ if (!g_hash_table_contains (all_deltas_hash, commit)) { if (!spawn_delta_generation (context, &n_spawned_delta_generate, repo, params, ref, NULL, commit, error)) return FALSE; } /* Mark this one as wanted */ g_hash_table_insert (wanted_deltas_hash, g_strdup (commit), GINT_TO_POINTER (1)); parent_commit = ostree_commit_get_parent (variant); if (parent_commit != NULL && !ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, parent_commit, &parent_variant, NULL)) { g_warning ("Couldn't load parent commit %s", parent_commit); continue; } /* From parent */ if (parent_variant != NULL) { g_autofree char *from_parent = g_strdup_printf ("%s-%s", parent_commit, commit); if (!g_hash_table_contains (all_deltas_hash, from_parent)) { if (!spawn_delta_generation (context, &n_spawned_delta_generate, repo, params, ref, parent_commit, commit, error)) return FALSE; } /* Mark parent-to-current as wanted */ g_hash_table_insert (wanted_deltas_hash, g_strdup (from_parent), GINT_TO_POINTER (1)); /* We also want to keep around the parent and the grandparent-to-parent deltas * because otherwise these will be deleted immediately which may cause a race if * someone is currently downloading them. * However, there is no need to generate these if they don't exist. */ g_hash_table_insert (wanted_deltas_hash, g_strdup (parent_commit), GINT_TO_POINTER (1)); grandparent_commit = ostree_commit_get_parent (parent_variant); if (grandparent_commit != NULL) g_hash_table_insert (wanted_deltas_hash, g_strdup_printf ("%s-%s", grandparent_commit, parent_commit), GINT_TO_POINTER (1)); } } while (n_spawned_delta_generate > 0) g_main_context_iteration (context, TRUE); *unwanted_deltas = g_ptr_array_new_with_free_func (g_free); for (i = 0; i < all_deltas->len; i++) { const char *delta = g_ptr_array_index (all_deltas, i); if (!g_hash_table_contains (wanted_deltas_hash, delta)) g_ptr_array_add (*unwanted_deltas, g_strdup (delta)); } return TRUE; }
static gboolean write_checksum_file_at (OstreeRepo *self, int dfd, const char *name, const char *sha256, GCancellable *cancellable, GError **error) { if (!ostree_validate_checksum_string (sha256, error)) return FALSE; if (ostree_validate_checksum_string (name, NULL)) return glnx_throw (error, "Rev name '%s' looks like a checksum", name); if (!*name) return glnx_throw (error, "Invalid empty ref name"); const char *lastslash = strrchr (name, '/'); if (lastslash) { char *parent = strdupa (name); parent[lastslash - name] = '\0'; if (!glnx_shutil_mkdir_p_at (dfd, parent, 0777, cancellable, error)) return FALSE; } { size_t l = strlen (sha256); char *bufnl = alloca (l + 2); g_autoptr(GError) temp_error = NULL; memcpy (bufnl, sha256, l); bufnl[l] = '\n'; bufnl[l+1] = '\0'; if (!_ostree_repo_file_replace_contents (self, dfd, name, (guint8*)bufnl, l + 1, cancellable, &temp_error)) { if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY)) { g_autoptr(GHashTable) refs = NULL; GHashTableIter hashiter; gpointer hashkey, hashvalue; g_clear_error (&temp_error); if (!ostree_repo_list_refs (self, name, &refs, cancellable, error)) return FALSE; g_hash_table_iter_init (&hashiter, refs); while ((g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue))) { if (strcmp (name, (char *)hashkey) != 0) return glnx_throw (error, "Conflict: %s exists under %s when attempting write", (char*)hashkey, name); } if (!glnx_shutil_rm_rf_at (dfd, name, cancellable, error)) return FALSE; if (!_ostree_repo_file_replace_contents (self, dfd, name, (guint8*)bufnl, l + 1, cancellable, error)) return FALSE; } else { g_propagate_error (error, g_steal_pointer (&temp_error)); return FALSE; } } } return TRUE; }