repo: Add a new iterator traversal API for commits

This is a more optimized version of the GFile * APIs, and is now used
internally by the previous ostree_repo_traverse().
This commit is contained in:
Colin Walters 2015-02-11 03:25:17 -05:00
parent 08476ce254
commit 0f74ed62b7
3 changed files with 414 additions and 101 deletions

View File

@ -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,

View File

@ -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);

View File

@ -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