diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index d7ea7acc..c4db502a 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -154,6 +154,15 @@ _ostree_repo_file_new_root (OstreeRepo *repo, const char *contents_checksum, const char *metadata_checksum); +gboolean +_ostree_repo_traverse_dirtree_internal (OstreeRepo *repo, + const char *dirtree_checksum, + int recursion_depth, + GHashTable *inout_reachable, + GHashTable *inout_content_names, + GCancellable *cancellable, + GError **error); + OstreeRepoCommitFilterResult _ostree_repo_commit_modifier_apply (OstreeRepo *self, OstreeRepoCommitModifier *modifier, diff --git a/src/libostree/ostree-repo-traverse.c b/src/libostree/ostree-repo-traverse.c index 122fccec..6638b6b4 100644 --- a/src/libostree/ostree-repo-traverse.c +++ b/src/libostree/ostree-repo-traverse.c @@ -26,6 +26,261 @@ #include "otutil.h" #include "libgsystem.h" +struct _OstreeRepoRealCommitTraverseIter { + gboolean initialized; + OstreeRepo *repo; + GVariant *commit; + GVariant *current_dir; + const char *name; + OstreeRepoCommitIterResult state; + guint idx; + char checksum_content[65]; + char checksum_meta[65]; +}; + +/** + * ostree_repo_commit_traverse_iter_init_commit: + * @iter: An iter + * @repo: A repo + * @commit: Variant of type %OSTREE_OBJECT_TYPE_COMMIT + * @flags: Flags + * @error: Error + * + * Initialize (in place) an iterator over the root of a commit object. + */ +gboolean +ostree_repo_commit_traverse_iter_init_commit (OstreeRepoCommitTraverseIter *iter, + OstreeRepo *repo, + GVariant *commit, + OstreeRepoCommitTraverseFlags flags, + GError **error) +{ + struct _OstreeRepoRealCommitTraverseIter *real = + (struct _OstreeRepoRealCommitTraverseIter*)iter; + gboolean ret = FALSE; + const guchar *csum; + gs_unref_variant GVariant *meta_csum_bytes = NULL; + gs_unref_variant GVariant *content_csum_bytes = NULL; + + memset (real, 0, sizeof (*real)); + real->initialized = TRUE; + real->repo = g_object_ref (repo); + real->commit = g_variant_ref (commit); + real->current_dir = NULL; + real->idx = 0; + + g_variant_get_child (commit, 6, "@ay", &content_csum_bytes); + csum = ostree_checksum_bytes_peek_validate (content_csum_bytes, error); + if (!csum) + goto out; + ostree_checksum_inplace_from_bytes (csum, real->checksum_content); + + g_variant_get_child (commit, 7, "@ay", &meta_csum_bytes); + csum = ostree_checksum_bytes_peek_validate (meta_csum_bytes, error); + if (!csum) + goto out; + ostree_checksum_inplace_from_bytes (csum, real->checksum_meta); + + ret = TRUE; + out: + return ret; +} + +/** + * ostree_repo_commit_traverse_iter_init_dirtree: + * @iter: An iter + * @repo: A repo + * @commit: Variant of type %OSTREE_OBJECT_TYPE_DIR_TREE + * @flags: Flags + * @error: Error + * + * Initialize (in place) an iterator over a directory tree. + */ +gboolean +ostree_repo_commit_traverse_iter_init_dirtree (OstreeRepoCommitTraverseIter *iter, + OstreeRepo *repo, + GVariant *dirtree, + OstreeRepoCommitTraverseFlags flags, + GError **error) +{ + struct _OstreeRepoRealCommitTraverseIter *real = + (struct _OstreeRepoRealCommitTraverseIter*)iter; + + memset (real, 0, sizeof (*real)); + real->initialized = TRUE; + real->repo = g_object_ref (repo); + real->current_dir = g_variant_ref (dirtree); + real->idx = 0; + + return TRUE; +} + +/** + * ostree_repo_commit_traverse_iter_next: + * @iter: An iter + * @cancellable: Cancellable + * @error: Error + * + * Step the interator to the next item. Files will be returned first, + * then subdirectories. Call this in a loop; upon encountering + * %OSTREE_REPO_COMMIT_ITER_RESULT_END, there will be no more files or + * directories. If %OSTREE_REPO_COMMIT_ITER_RESULT_DIR is returned, + * then call ostree_repo_commit_traverse_iter_get_dir() to retrieve + * data for that directory. Similarly, if + * %OSTREE_REPO_COMMIT_ITER_RESULT_FILE is returned, call + * ostree_repo_commit_traverse_iter_get_file(). + * + * If %OSTREE_REPO_COMMIT_ITER_RESULT_ERROR is returned, it is a + * program error to call any further API on @iter except for + * ostree_repo_commit_traverse_iter_clear(). + */ +OstreeRepoCommitIterResult +ostree_repo_commit_traverse_iter_next (OstreeRepoCommitTraverseIter *iter, + GCancellable *cancellable, + GError **error) +{ + struct _OstreeRepoRealCommitTraverseIter *real = + (struct _OstreeRepoRealCommitTraverseIter*)iter; + OstreeRepoCommitIterResult res = OSTREE_REPO_COMMIT_ITER_RESULT_ERROR; + + if (!real->current_dir) + { + if (!ostree_repo_load_variant (real->repo, OSTREE_OBJECT_TYPE_DIR_TREE, + real->checksum_content, + &real->current_dir, + error)) + goto out; + res = OSTREE_REPO_COMMIT_ITER_RESULT_DIR; + } + else + { + guint nfiles; + guint ndirs; + guint idx; + const guchar *csum; + gs_unref_variant GVariant *content_csum_v = NULL; + gs_unref_variant GVariant *meta_csum_v = NULL; + gs_unref_variant GVariant *files_variant = NULL; + gs_unref_variant GVariant *dirs_variant = NULL; + + files_variant = g_variant_get_child_value (real->current_dir, 0); + dirs_variant = g_variant_get_child_value (real->current_dir, 1); + + nfiles = g_variant_n_children (files_variant); + ndirs = g_variant_n_children (dirs_variant); + if (real->idx < nfiles) + { + idx = real->idx; + g_variant_get_child (files_variant, idx, "(&s@ay)", + &real->name, + &content_csum_v); + + csum = ostree_checksum_bytes_peek_validate (content_csum_v, error); + if (!csum) + goto out; + ostree_checksum_inplace_from_bytes (csum, real->checksum_content); + + res = OSTREE_REPO_COMMIT_ITER_RESULT_FILE; + + real->idx++; + } + else if (real->idx < nfiles + ndirs) + { + idx = real->idx - nfiles; + + g_variant_get_child (dirs_variant, idx, "(&s@ay@ay)", + &real->name, &content_csum_v, &meta_csum_v); + + csum = ostree_checksum_bytes_peek_validate (content_csum_v, error); + if (!csum) + goto out; + ostree_checksum_inplace_from_bytes (csum, real->checksum_content); + + csum = ostree_checksum_bytes_peek_validate (meta_csum_v, error); + if (!csum) + goto out; + ostree_checksum_inplace_from_bytes (csum, real->checksum_meta); + + res = OSTREE_REPO_COMMIT_ITER_RESULT_DIR; + + real->idx++; + } + else + res = OSTREE_REPO_COMMIT_ITER_RESULT_END; + } + + real->state = res; + out: + return res; +} + +/** + * ostree_repo_commit_traverse_iter_get_file: + * @iter: An iter + * @out_name: (out) (transfer none): Name of current file + * @out_checksum: (out) (transfer none): Checksum of current file + * + * Return information on the current file. This function may only be + * called if %OSTREE_REPO_COMMIT_ITER_RESULT_FILE was returned from + * ostree_repo_commit_traverse_iter_next(). + */ +void +ostree_repo_commit_traverse_iter_get_file (OstreeRepoCommitTraverseIter *iter, + char **out_name, + char **out_checksum) +{ + struct _OstreeRepoRealCommitTraverseIter *real = + (struct _OstreeRepoRealCommitTraverseIter*)iter; + *out_name = (char*)real->name; + *out_checksum = (char*)real->checksum_content; +} + +/** + * ostree_repo_commit_traverse_iter_get_dir: + * @iter: An iter + * @out_name: (out) (transfer none): Name of current dir + * @out_content_checksum: (out) (transfer none): Checksum of current content + * @out_meta_checksum: (out) (transfer none): Checksum of current metadata + * + * Return information on the current directory. This function may + * only be called if %OSTREE_REPO_COMMIT_ITER_RESULT_DIR was returned + * from ostree_repo_commit_traverse_iter_next(). + */ +void +ostree_repo_commit_traverse_iter_get_dir (OstreeRepoCommitTraverseIter *iter, + char **out_name, + char **out_content_checksum, + char **out_meta_checksum) +{ + struct _OstreeRepoRealCommitTraverseIter *real = + (struct _OstreeRepoRealCommitTraverseIter*)iter; + *out_name = (char*)real->name; + *out_content_checksum = (char*)real->checksum_content; + *out_meta_checksum = (char*)real->checksum_meta; +} + +void +ostree_repo_commit_traverse_iter_clear (OstreeRepoCommitTraverseIter *iter) +{ + struct _OstreeRepoRealCommitTraverseIter *real = + (struct _OstreeRepoRealCommitTraverseIter*)iter; + g_clear_pointer (&real->commit, g_variant_unref); + g_clear_pointer (&real->current_dir, g_variant_unref); +} + +void +ostree_repo_commit_traverse_iter_cleanup (void *p) +{ + OstreeRepoCommitTraverseIter *iter = p; + struct _OstreeRepoRealCommitTraverseIter *real = + (struct _OstreeRepoRealCommitTraverseIter*)iter; + if (real->initialized) + { + ostree_repo_commit_traverse_iter_clear (iter); + real->initialized = FALSE; + } +} + /** * ostree_repo_traverse_new_reachable: * @@ -42,83 +297,99 @@ ostree_repo_traverse_new_reachable (void) } static gboolean -traverse_dirtree_internal (OstreeRepo *repo, - const char *dirtree_checksum, - int recursion_depth, - GHashTable *inout_reachable, - GCancellable *cancellable, - GError **error) +traverse_dirtree (OstreeRepo *repo, + const char *checksum, + GHashTable *inout_reachable, + GCancellable *cancellable, + GError **error); + +static gboolean +traverse_iter (OstreeRepo *repo, + OstreeRepoCommitTraverseIter *iter, + GHashTable *inout_reachable, + GCancellable *cancellable, + GError **error) { gboolean ret = FALSE; - int n, i; - gs_unref_variant GVariant *key = NULL; - gs_unref_variant GVariant *tree = NULL; - gs_unref_variant GVariant *files_variant = NULL; - gs_unref_variant GVariant *dirs_variant = NULL; - gs_unref_variant GVariant *csum_v = NULL; - gs_unref_variant GVariant *content_csum_v = NULL; - gs_unref_variant GVariant *metadata_csum_v = NULL; - gs_free char *tmp_checksum = NULL; - if (recursion_depth > OSTREE_MAX_RECURSION) + while (TRUE) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Maximum recursion limit reached during traversal"); - goto out; + gs_unref_variant GVariant *key = NULL; + OstreeRepoCommitIterResult iterres = + ostree_repo_commit_traverse_iter_next (iter, cancellable, error); + + if (iterres == OSTREE_REPO_COMMIT_ITER_RESULT_ERROR) + goto out; + else if (iterres == OSTREE_REPO_COMMIT_ITER_RESULT_END) + break; + else if (iterres == OSTREE_REPO_COMMIT_ITER_RESULT_FILE) + { + char *name; + char *checksum; + + ostree_repo_commit_traverse_iter_get_file (iter, &name, &checksum); + + key = ostree_object_name_serialize (checksum, OSTREE_OBJECT_TYPE_FILE); + g_hash_table_replace (inout_reachable, key, key); + key = NULL; + } + else if (iterres == OSTREE_REPO_COMMIT_ITER_RESULT_DIR) + { + char *name; + char *content_checksum; + char *meta_checksum; + + ostree_repo_commit_traverse_iter_get_dir (iter, &name, &content_checksum, + &meta_checksum); + + key = ostree_object_name_serialize (meta_checksum, OSTREE_OBJECT_TYPE_DIR_META); + g_hash_table_replace (inout_reachable, key, key); + key = NULL; + + key = ostree_object_name_serialize (content_checksum, OSTREE_OBJECT_TYPE_DIR_TREE); + if (!g_hash_table_lookup (inout_reachable, key)) + { + g_hash_table_replace (inout_reachable, key, key); + key = NULL; + + if (!traverse_dirtree (repo, content_checksum, inout_reachable, + cancellable, error)) + goto out; + } + } + else + g_assert_not_reached (); } - if (!ostree_repo_load_variant_if_exists (repo, OSTREE_OBJECT_TYPE_DIR_TREE, dirtree_checksum, &tree, error)) + ret = TRUE; + out: + return ret; +} + +static gboolean +traverse_dirtree (OstreeRepo *repo, + const char *checksum, + GHashTable *inout_reachable, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gs_unref_variant GVariant *dirtree = NULL; + ostree_cleanup_repo_commit_traverse_iter + OstreeRepoCommitTraverseIter iter = { 0, }; + + if (!ostree_repo_load_variant_if_exists (repo, OSTREE_OBJECT_TYPE_DIR_TREE, + checksum, &dirtree, + error)) goto out; - if (!tree) - return TRUE; + if (!ostree_repo_commit_traverse_iter_init_dirtree (&iter, repo, dirtree, + OSTREE_REPO_COMMIT_TRAVERSE_FLAG_NONE, + error)) + goto out; - key = ostree_object_name_serialize (dirtree_checksum, OSTREE_OBJECT_TYPE_DIR_TREE); - if (!g_hash_table_lookup (inout_reachable, key)) - { - g_hash_table_insert (inout_reachable, key, key); - key = NULL; - - /* PARSE OSTREE_SERIALIZED_TREE_VARIANT */ - files_variant = g_variant_get_child_value (tree, 0); - n = g_variant_n_children (files_variant); - for (i = 0; i < n; i++) - { - const char *filename; - - g_clear_pointer (&csum_v, (GDestroyNotify) g_variant_unref); - g_variant_get_child (files_variant, i, "(&s@ay)", &filename, &csum_v); - g_free (tmp_checksum); - tmp_checksum = ostree_checksum_from_bytes_v (csum_v); - key = ostree_object_name_serialize (tmp_checksum, OSTREE_OBJECT_TYPE_FILE); - g_hash_table_replace (inout_reachable, key, key); - key = NULL; - } - - dirs_variant = g_variant_get_child_value (tree, 1); - n = g_variant_n_children (dirs_variant); - for (i = 0; i < n; i++) - { - const char *dirname; - - g_clear_pointer (&content_csum_v, (GDestroyNotify) g_variant_unref); - g_clear_pointer (&metadata_csum_v, (GDestroyNotify) g_variant_unref); - g_variant_get_child (dirs_variant, i, "(&s@ay@ay)", - &dirname, &content_csum_v, &metadata_csum_v); - - g_free (tmp_checksum); - tmp_checksum = ostree_checksum_from_bytes_v (content_csum_v); - if (!traverse_dirtree_internal (repo, tmp_checksum, recursion_depth + 1, - inout_reachable, cancellable, error)) - goto out; - - g_free (tmp_checksum); - tmp_checksum = ostree_checksum_from_bytes_v (metadata_csum_v); - key = ostree_object_name_serialize (tmp_checksum, OSTREE_OBJECT_TYPE_DIR_META); - g_hash_table_replace (inout_reachable, key, key); - key = NULL; - } - } + if (!traverse_iter (repo, &iter, inout_reachable, cancellable, error)) + goto out; ret = TRUE; out: @@ -151,58 +422,38 @@ ostree_repo_traverse_commit_union (OstreeRepo *repo, while (TRUE) { gboolean recurse = FALSE; - gs_unref_variant GVariant *meta_csum_bytes = NULL; - gs_unref_variant GVariant *content_csum_bytes = NULL; gs_unref_variant GVariant *key = NULL; gs_unref_variant GVariant *commit = NULL; + ostree_cleanup_repo_commit_traverse_iter + OstreeRepoCommitTraverseIter iter = { 0, }; key = ostree_object_name_serialize (commit_checksum, OSTREE_OBJECT_TYPE_COMMIT); if (g_hash_table_contains (inout_reachable, key)) break; - /* PARSE OSTREE_SERIALIZED_COMMIT_VARIANT */ - if (!ostree_repo_load_variant_if_exists (repo, OSTREE_OBJECT_TYPE_COMMIT, commit_checksum, &commit, error)) + if (!ostree_repo_load_variant_if_exists (repo, OSTREE_OBJECT_TYPE_COMMIT, + commit_checksum, &commit, + error)) goto out; - + /* Just return if the parent isn't found; we do expect most * people to have partial repositories. */ if (!commit) break; - + g_hash_table_add (inout_reachable, key); key = NULL; - g_variant_get_child (commit, 7, "@ay", &meta_csum_bytes); - g_free (tmp_checksum); - if (G_UNLIKELY (g_variant_n_children (meta_csum_bytes) == 0)) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Corrupted commit '%s'; invalid tree metadata", - commit_checksum); - goto out; - } - - tmp_checksum = ostree_checksum_from_bytes_v (meta_csum_bytes); - key = ostree_object_name_serialize (tmp_checksum, OSTREE_OBJECT_TYPE_DIR_META); - g_hash_table_replace (inout_reachable, key, key); - key = NULL; - - g_variant_get_child (commit, 6, "@ay", &content_csum_bytes); - g_free (tmp_checksum); - if (G_UNLIKELY (g_variant_n_children (content_csum_bytes) == 0)) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Corrupted commit '%s'; invalid tree content", - commit_checksum); - goto out; - } - - tmp_checksum = ostree_checksum_from_bytes_v (content_csum_bytes); - if (!traverse_dirtree_internal (repo, tmp_checksum, 0, inout_reachable, cancellable, error)) + if (!ostree_repo_commit_traverse_iter_init_commit (&iter, repo, commit, + OSTREE_REPO_COMMIT_TRAVERSE_FLAG_NONE, + error)) goto out; + if (!traverse_iter (repo, &iter, inout_reachable, cancellable, error)) + goto out; + if (maxdepth == -1 || maxdepth > 0) { g_free (tmp_checksum); diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index 0ecf9bde..c59364a7 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -536,6 +536,59 @@ gboolean ostree_repo_traverse_commit_union (OstreeRepo *repo, GCancellable *cancellable, GError **error); +struct _OstreeRepoCommitTraverseIter { + gboolean initialized; + gpointer dummy[10]; + char dummy_checksum_data[65*2]; +}; + +typedef struct _OstreeRepoCommitTraverseIter OstreeRepoCommitTraverseIter; + +typedef enum { + OSTREE_REPO_COMMIT_TRAVERSE_FLAG_NONE = (1 << 0) +} OstreeRepoCommitTraverseFlags; + +gboolean +ostree_repo_commit_traverse_iter_init_commit (OstreeRepoCommitTraverseIter *iter, + OstreeRepo *repo, + GVariant *commit, + OstreeRepoCommitTraverseFlags flags, + GError **error); + +gboolean +ostree_repo_commit_traverse_iter_init_dirtree (OstreeRepoCommitTraverseIter *iter, + OstreeRepo *repo, + GVariant *dirtree, + OstreeRepoCommitTraverseFlags flags, + GError **error); + +typedef enum { + OSTREE_REPO_COMMIT_ITER_RESULT_ERROR, + OSTREE_REPO_COMMIT_ITER_RESULT_END, + OSTREE_REPO_COMMIT_ITER_RESULT_FILE, + OSTREE_REPO_COMMIT_ITER_RESULT_DIR +} OstreeRepoCommitIterResult; + +OstreeRepoCommitIterResult ostree_repo_commit_traverse_iter_next (OstreeRepoCommitTraverseIter *iter, + GCancellable *cancellable, + GError **error); + +void ostree_repo_commit_traverse_iter_get_file (OstreeRepoCommitTraverseIter *iter, + char **out_name, + char **out_checksum); + +void ostree_repo_commit_traverse_iter_get_dir (OstreeRepoCommitTraverseIter *iter, + char **out_name, + char **out_content_checksum, + char **out_meta_checksum); + +void +ostree_repo_commit_traverse_iter_clear (OstreeRepoCommitTraverseIter *iter); + +void ostree_repo_commit_traverse_iter_cleanup (void *p); + +#define ostree_cleanup_repo_commit_traverse_iter __attribute__ ((cleanup(ostree_repo_commit_traverse_iter_cleanup))) + /** * OstreeRepoPruneFlags: * @OSTREE_REPO_PRUNE_FLAGS_NONE: No special options for pruning