diff --git a/Makefile-libostree.am b/Makefile-libostree.am index 7b8790b8..f1357cc1 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -25,6 +25,10 @@ libostree_la_SOURCES = libostree/ostree.h \ libostree/ostree-core.h \ libostree/ostree-repo.c \ libostree/ostree-repo.h \ + libostree/ostree-repo-file.c \ + libostree/ostree-repo-file.h \ + libostree/ostree-repo-file-enumerator.c \ + libostree/ostree-repo-file-enumerator.h \ libostree/ostree-checkout.c \ libostree/ostree-checkout.h \ $(NULL) diff --git a/libostree/ostree-core.c b/libostree/ostree-core.c index a3fc34ca..9a05e91e 100644 --- a/libostree/ostree-core.c +++ b/libostree/ostree-core.c @@ -296,7 +296,7 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path, } gboolean -ostree_set_xattrs (const char *path, GVariant *xattrs, GError **error) +ostree_set_xattrs (const char *path, GVariant *xattrs, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; int i, n; @@ -790,7 +790,7 @@ unpack_file (const char *path, } } - if (!ostree_set_xattrs (dest_path, xattrs, error)) + if (!ostree_set_xattrs (dest_path, xattrs, NULL, error)) goto out; if (ret_checksum) diff --git a/libostree/ostree-core.h b/libostree/ostree-core.h index 3dc70b48..2b39f3f9 100644 --- a/libostree/ostree-core.h +++ b/libostree/ostree-core.h @@ -28,7 +28,7 @@ G_BEGIN_DECLS #define OSTREE_MAX_METADATA_SIZE (1 << 26) -#define OSTREE_GIO_FAST_QUERYINFO "standard::name,standard::type,standard::is-symlink,standard::symlink-target,unix::*" +#define OSTREE_GIO_FAST_QUERYINFO "standard::name,standard::type,standard::is-symlink,standard::symlink-target,standard::is-hidden,unix::*" #define OSTREE_EMPTY_STRING_SHA256 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; @@ -98,7 +98,8 @@ char *ostree_get_relative_object_path (const char *checksum, GVariant *ostree_get_xattrs_for_path (const char *path, GError **error); -gboolean ostree_set_xattrs (const char *path, GVariant *xattrs, GError **error); +gboolean ostree_set_xattrs (const char *path, GVariant *xattrs, + GCancellable *cancellable, GError **error); gboolean ostree_parse_metadata_file (const char *path, OstreeSerializedVariantType *out_type, diff --git a/libostree/ostree-repo-file-enumerator.c b/libostree/ostree-repo-file-enumerator.c new file mode 100644 index 00000000..3a40281e --- /dev/null +++ b/libostree/ostree-repo-file-enumerator.c @@ -0,0 +1,142 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Colin Walters + */ + +#include "config.h" + +#include "ostree-repo-file-enumerator.h" +#include + +struct _OstreeRepoFileEnumerator +{ + GFileEnumerator parent; + + OstreeRepoFile *dir; + char *attributes; + GFileQueryInfoFlags flags; + + int index; +}; + +#define ostree_repo_file_enumerator_get_type _ostree_repo_file_enumerator_get_type +G_DEFINE_TYPE (OstreeRepoFileEnumerator, ostree_repo_file_enumerator, G_TYPE_FILE_ENUMERATOR); + +static GFileInfo *ostree_repo_file_enumerator_next_file (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error); +static gboolean ostree_repo_file_enumerator_close (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error); + + +static void +ostree_repo_file_enumerator_dispose (GObject *object) +{ + OstreeRepoFileEnumerator *self; + + self = OSTREE_REPO_FILE_ENUMERATOR (object); + + g_clear_object (&self->dir); + g_free (self->attributes); + + if (G_OBJECT_CLASS (ostree_repo_file_enumerator_parent_class)->dispose) + G_OBJECT_CLASS (ostree_repo_file_enumerator_parent_class)->dispose (object); +} + +static void +ostree_repo_file_enumerator_finalize (GObject *object) +{ + OstreeRepoFileEnumerator *self; + + self = OSTREE_REPO_FILE_ENUMERATOR (object); + (void)self; + + G_OBJECT_CLASS (ostree_repo_file_enumerator_parent_class)->finalize (object); +} + + +static void +ostree_repo_file_enumerator_class_init (OstreeRepoFileEnumeratorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GFileEnumeratorClass *enumerator_class = G_FILE_ENUMERATOR_CLASS (klass); + + gobject_class->finalize = ostree_repo_file_enumerator_finalize; + gobject_class->dispose = ostree_repo_file_enumerator_dispose; + + enumerator_class->next_file = ostree_repo_file_enumerator_next_file; + enumerator_class->close_fn = ostree_repo_file_enumerator_close; +} + +static void +ostree_repo_file_enumerator_init (OstreeRepoFileEnumerator *self) +{ +} + +GFileEnumerator * +_ostree_repo_file_enumerator_new (OstreeRepoFile *dir, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + OstreeRepoFileEnumerator *self; + + self = g_object_new (OSTREE_TYPE_REPO_FILE_ENUMERATOR, + "container", dir, + NULL); + + self->dir = g_object_ref (dir); + self->attributes = g_strdup (attributes); + self->flags = flags; + + return G_FILE_ENUMERATOR (self); +} + +static GFileInfo * +ostree_repo_file_enumerator_next_file (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error) +{ + OstreeRepoFileEnumerator *self = OSTREE_REPO_FILE_ENUMERATOR (enumerator); + gboolean ret = FALSE; + GFileInfo *info = NULL; + + if (!_ostree_repo_file_tree_query_child (self->dir, self->index, + self->attributes, self->flags, + &info, cancellable, error)) + goto out; + + self->index++; + + ret = TRUE; + out: + if (!ret) + g_clear_object (&info); + return info; +} + +static gboolean +ostree_repo_file_enumerator_close (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error) +{ + return TRUE; +} diff --git a/libostree/ostree-repo-file-enumerator.h b/libostree/ostree-repo-file-enumerator.h new file mode 100644 index 00000000..04c8e21e --- /dev/null +++ b/libostree/ostree-repo-file-enumerator.h @@ -0,0 +1,54 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Colin Walters + */ + +#ifndef _OSTREE_REPO_FILE_ENUMERATOR +#define _OSTREE_REPO_FILE_ENUMERATOR + +#include "ostree-repo-file.h" + +G_BEGIN_DECLS + +#define OSTREE_TYPE_REPO_FILE_ENUMERATOR (_ostree_repo_file_enumerator_get_type ()) +#define OSTREE_REPO_FILE_ENUMERATOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OSTREE_TYPE_REPO_FILE_ENUMERATOR, OstreeRepoFileEnumerator)) +#define OSTREE_REPO_FILE_ENUMERATOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), OSTREE_TYPE_REPO_FILE_ENUMERATOR, OstreeRepoFileEnumeratorClass)) +#define OSTREE_IS_REPO_FILE_ENUMERATOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSTREE_TYPE_REPO_FILE_ENUMERATOR)) +#define OSTREE_IS_REPO_FILE_ENUMERATOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OSTREE_TYPE_REPO_FILE_ENUMERATOR)) +#define OSTREE_REPO_FILE_ENUMERATOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OSTREE_TYPE_REPO_FILE_ENUMERATOR, OstreeRepoFileEnumeratorClass)) + +typedef struct _OstreeRepoFileEnumerator OstreeRepoFileEnumerator; +typedef struct _OstreeRepoFileEnumeratorClass OstreeRepoFileEnumeratorClass; + +struct _OstreeRepoFileEnumeratorClass +{ + GFileEnumeratorClass parent_class; +}; + +GType _ostree_repo_file_enumerator_get_type (void) G_GNUC_CONST; + +GFileEnumerator * _ostree_repo_file_enumerator_new (OstreeRepoFile *dir, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif diff --git a/libostree/ostree-repo-file.c b/libostree/ostree-repo-file.c new file mode 100644 index 00000000..66268723 --- /dev/null +++ b/libostree/ostree-repo-file.c @@ -0,0 +1,1427 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Colin Walters + */ + +#include "config.h" + +#include "ostree-repo-file-enumerator.h" + +static void ostree_repo_file_file_iface_init (GFileIface *iface); + +static void +tree_replace_contents (OstreeRepoFile *self, + GVariant *new_files, + GVariant *new_dirs); + +struct _OstreeRepoFile +{ + GObject parent_instance; + + OstreeRepo *repo; + + char *commit; + GError *commit_resolve_error; + + OstreeRepoFile *parent; + int index; + char *name; + + char *tree_contents_checksum; + GVariant *tree_contents; + char *tree_metadata_checksum; + GVariant *tree_metadata; +}; + +#define ostree_repo_file_get_type _ostree_repo_file_get_type +G_DEFINE_TYPE_WITH_CODE (OstreeRepoFile, ostree_repo_file, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_FILE, + ostree_repo_file_file_iface_init)) + +static void +ostree_repo_file_finalize (GObject *object) +{ + OstreeRepoFile *self; + + self = OSTREE_REPO_FILE (object); + + if (self->tree_contents) + g_variant_unref (self->tree_contents); + if (self->tree_metadata) + g_variant_unref (self->tree_metadata); + g_free (self->tree_contents_checksum); + g_free (self->tree_metadata_checksum); + g_free (self->commit); + g_free (self->name); + + G_OBJECT_CLASS (ostree_repo_file_parent_class)->finalize (object); +} + +static void +ostree_repo_file_dispose (GObject *object) +{ + OstreeRepoFile *self; + + self = OSTREE_REPO_FILE (object); + + g_clear_object (&self->repo); + g_clear_object (&self->parent); + + if (G_OBJECT_CLASS (ostree_repo_file_parent_class)->dispose) + G_OBJECT_CLASS (ostree_repo_file_parent_class)->dispose (object); +} + +static void +ostree_repo_file_class_init (OstreeRepoFileClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = ostree_repo_file_finalize; + gobject_class->dispose = ostree_repo_file_dispose; +} + +static void +ostree_repo_file_init (OstreeRepoFile *self) +{ + self->index = -1; +} + +static gboolean +set_error_noent (GFile *self, GError **error) +{ + char *path = g_file_get_path (self); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "No such file or directory: %s", path); + g_free (path); + return FALSE; +} + +GFile * +_ostree_repo_file_new_root (OstreeRepo *repo, + const char *commit) +{ + OstreeRepoFile *self; + + g_return_val_if_fail (repo != NULL, NULL); + g_return_val_if_fail (commit != NULL, NULL); + g_return_val_if_fail (strlen (commit) == 64, NULL); + + self = g_object_new (OSTREE_TYPE_REPO_FILE, NULL); + self->repo = g_object_ref (repo); + self->commit = g_strdup (commit); + + return G_FILE (self); +} + + +GFile * +_ostree_repo_file_new_child (OstreeRepoFile *parent, + const char *name) +{ + OstreeRepoFile *self; + + self = g_object_new (OSTREE_TYPE_REPO_FILE, NULL); + self->repo = g_object_ref (parent->repo); + self->parent = g_object_ref (parent); + self->name = g_strdup (name); + + return G_FILE (self); +} + +OstreeRepoFile * +_ostree_repo_file_new_empty_tree (OstreeRepo *repo) +{ + OstreeRepoFile *self; + + self = g_object_new (OSTREE_TYPE_REPO_FILE, NULL); + self->repo = g_object_ref (repo); + + tree_replace_contents (self, NULL, NULL); + + return self; +} + +static gboolean +do_resolve_commit (OstreeRepoFile *self, + GError **error) +{ + gboolean ret = FALSE; + GVariant *commit = NULL; + GVariant *root_contents = NULL; + GVariant *root_metadata = NULL; + const char *tree_contents_checksum; + const char *tree_meta_checksum; + + g_assert (self->parent == NULL); + + if (!ostree_repo_load_variant_checked (self->repo, OSTREE_SERIALIZED_COMMIT_VARIANT, + self->commit, &commit, error)) + goto out; + + /* PARSE OSTREE_SERIALIZED_COMMIT_VARIANT */ + g_variant_get_child (commit, 6, "&s", &tree_contents_checksum); + g_variant_get_child (commit, 7, "&s", &tree_meta_checksum); + + if (!ostree_repo_load_variant_checked (self->repo, OSTREE_SERIALIZED_TREE_VARIANT, + tree_contents_checksum, &root_contents, + error)) + goto out; + + if (!ostree_repo_load_variant_checked (self->repo, OSTREE_SERIALIZED_DIRMETA_VARIANT, + tree_meta_checksum, &root_metadata, + error)) + goto out; + + self->tree_metadata = root_metadata; + root_metadata = NULL; + self->tree_contents = root_contents; + root_contents = NULL; + + out: + if (commit) + g_variant_unref (commit); + if (root_metadata) + g_variant_unref (root_metadata); + if (root_contents) + g_variant_unref (root_contents); + return ret; +} + +static gboolean +do_resolve_nonroot (OstreeRepoFile *self, + GError **error) +{ + gboolean ret = FALSE; + GVariant *container = NULL; + GVariant *tree_contents = NULL; + GVariant *tree_metadata = NULL; + gboolean is_dir; + int i; + + i = _ostree_repo_file_tree_find_child (self->parent, self->name, &is_dir, &container); + + if (i < 0) + { + set_error_noent ((GFile*)self, error); + goto out; + } + + if (is_dir) + { + const char *name; + const char *content_checksum; + const char *metadata_checksum; + GVariant *files_variant; + + files_variant = g_variant_get_child_value (self->parent->tree_contents, 2); + self->index = g_variant_n_children (files_variant) + i; + g_variant_unref (files_variant); + + g_variant_get_child (container, i, "(&s&s&s)", + &name, &content_checksum, &metadata_checksum); + + if (!ostree_repo_load_variant_checked (self->repo, OSTREE_SERIALIZED_TREE_VARIANT, + content_checksum, &tree_contents, + error)) + goto out; + + if (!ostree_repo_load_variant_checked (self->repo, OSTREE_SERIALIZED_DIRMETA_VARIANT, + metadata_checksum, &tree_metadata, + error)) + goto out; + + self->tree_contents = tree_contents; + tree_contents = NULL; + self->tree_metadata = tree_metadata; + tree_metadata = NULL; + } + else + self->index = i; + + ret = TRUE; + out: + if (container) + g_variant_unref (container); + if (tree_metadata) + g_variant_unref (tree_metadata); + if (tree_contents) + g_variant_unref (tree_contents); + return ret; +} + +gboolean +_ostree_repo_file_ensure_resolved (OstreeRepoFile *self, + GError **error) +{ + if (self->commit_resolve_error != NULL) + goto out; + + if (self->parent == NULL) + { + if (self->tree_contents == NULL) + (void)do_resolve_commit (self, &(self->commit_resolve_error)); + } + else if (self->index == -1) + { + (void)do_resolve_nonroot (self, &(self->commit_resolve_error)); + } + + out: + if (self->commit_resolve_error) + { + if (error) + *error = g_error_copy (self->commit_resolve_error); + return FALSE; + } + else + return TRUE; +} + +gboolean +_ostree_repo_file_get_xattrs (OstreeRepoFile *self, + GVariant **out_xattrs, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GVariant *ret_xattrs = NULL; + GVariant *metadata = NULL; + GInputStream *input = NULL; + GFile *local_file = NULL; + + if (!_ostree_repo_file_ensure_resolved (self, error)) + goto out; + + if (self->tree_metadata) + ret_xattrs = g_variant_get_child_value (self->tree_metadata, 4); + else if (ostree_repo_is_archive (self->repo)) + { + local_file = _ostree_repo_file_nontree_get_local (self); + if (!ostree_parse_packed_file (local_file, &metadata, &input, cancellable, error)) + goto out; + ret_xattrs = g_variant_get_child_value (metadata, 4); + } + else + { + local_file = _ostree_repo_file_nontree_get_local (self); + ret_xattrs = ostree_get_xattrs_for_path (ot_gfile_get_path_cached (local_file), error); + } + + ret = TRUE; + *out_xattrs = ret_xattrs; + ret_xattrs = NULL; + out: + if (ret_xattrs) + g_variant_unref (ret_xattrs); + if (metadata) + g_variant_unref (metadata); + g_clear_object (&input); + g_clear_object (&local_file); + return ret; +} + +GVariant * +_ostree_repo_file_tree_get_contents (OstreeRepoFile *self) +{ + return self->tree_contents; +} + +GVariant * +_ostree_repo_file_tree_get_metadata (OstreeRepoFile *self) +{ + return self->tree_metadata; +} + +void +_ostree_repo_file_tree_set_metadata (OstreeRepoFile *self, + const char *checksum, + GVariant *metadata) +{ + if (self->tree_metadata) + g_variant_unref (self->tree_metadata); + self->tree_metadata = g_variant_ref (metadata); + g_free (self->tree_metadata_checksum); + self->tree_metadata_checksum = g_strdup (checksum); +} + +void +_ostree_repo_file_make_empty_tree (OstreeRepoFile *self) +{ + tree_replace_contents (self, NULL, NULL); +} + +void +_ostree_repo_file_tree_set_content_checksum (OstreeRepoFile *self, + const char *checksum) +{ + g_assert (self->parent == NULL); + g_free (self->tree_contents_checksum); + self->tree_contents_checksum = g_strdup (checksum); +} + +const char * +_ostree_repo_file_tree_get_content_checksum (OstreeRepoFile *self) +{ + g_assert (self->parent == NULL); + return self->tree_contents_checksum; +} + +GFile * +_ostree_repo_file_nontree_get_local (OstreeRepoFile *self) +{ + const char *checksum; + char *path; + GFile *ret; + + g_assert (!ostree_repo_is_archive (self->repo)); + + checksum = _ostree_repo_file_nontree_get_checksum (self); + path = ostree_repo_get_object_path (self->repo, checksum, OSTREE_OBJECT_TYPE_FILE); + ret = ot_util_new_file_for_path (path); + g_free (path); + + return ret; +} + +OstreeRepo * +_ostree_repo_file_get_repo (OstreeRepoFile *self) +{ + return self->repo; +} + +OstreeRepoFile * +_ostree_repo_file_get_root (OstreeRepoFile *self) +{ + OstreeRepoFile *parent = self; + + while (parent->parent) + parent = parent->parent; + return parent; +} + +const char * +_ostree_repo_file_nontree_get_checksum (OstreeRepoFile *self) +{ + int n; + gboolean is_dir; + + g_assert (self->parent); + + n = _ostree_repo_file_tree_find_child (self->parent, self->name, &is_dir, NULL); + g_assert (n >= 0 && !is_dir); + + return _ostree_repo_file_tree_get_child_checksum (self->parent, n); +} + +const char * +_ostree_repo_file_tree_get_child_checksum (OstreeRepoFile *self, + int n) +{ + GVariant *files_variant; + const char *checksum; + + g_assert (self->tree_contents); + + files_variant = g_variant_get_child_value (self->tree_contents, 2); + + g_variant_get_child (files_variant, n, "(@s&s)", NULL, &checksum); + + g_variant_unref (files_variant); + + return checksum; +} + +static gboolean +ostree_repo_file_is_native (GFile *file) +{ + return FALSE; +} + +static gboolean +ostree_repo_file_has_uri_scheme (GFile *file, + const char *uri_scheme) +{ + return g_ascii_strcasecmp (uri_scheme, "ostree") == 0; +} + +static char * +ostree_repo_file_get_uri_scheme (GFile *file) +{ + return g_strdup ("ostree"); +} + +static char * +ostree_repo_file_get_basename (GFile *file) +{ + OstreeRepoFile *self = OSTREE_REPO_FILE (file); + return g_strdup (self->name); +} + +static char * +ostree_repo_file_get_path (GFile *file) +{ + OstreeRepoFile *self = OSTREE_REPO_FILE (file); + OstreeRepoFile *parent; + GString *buf; + GSList *parents; + GSList *iter; + + buf = g_string_new (""); + parents = NULL; + + for (parent = self->parent; parent; parent = parent->parent) + parents = g_slist_prepend (parents, parent); + + if (parents->next) + { + for (iter = parents->next; iter; iter = iter->next) + { + parent = iter->data; + g_string_append_c (buf, '/'); + g_string_append (buf, parent->name); + } + } + g_string_append_c (buf, '/'); + g_string_append (buf, self->name); + + g_slist_free (parents); + + return g_string_free (buf, FALSE); +} + +static char * +ostree_repo_file_get_uri (GFile *file) +{ + OstreeRepoFile *self = OSTREE_REPO_FILE (file); + char *path; + char *uri_path; + char *ret; + + path = g_file_get_path (file); + uri_path = g_filename_to_uri (path, NULL, NULL); + g_free (path); + g_assert (g_str_has_prefix (uri_path, "file://")); + ret = g_strconcat ("ostree://", self->commit, uri_path+strlen("file://"), NULL); + g_free (uri_path); + + return ret; +} + +static char * +ostree_repo_file_get_parse_name (GFile *file) +{ + return ostree_repo_file_get_uri (file); +} + +static GFile * +ostree_repo_file_get_parent (GFile *file) +{ + OstreeRepoFile *self = OSTREE_REPO_FILE (file); + + return g_object_ref (self->parent); +} + +static GFile * +ostree_repo_file_dup (GFile *file) +{ + OstreeRepoFile *self = OSTREE_REPO_FILE (file); + + if (self->parent) + return _ostree_repo_file_new_child (self->parent, self->name); + else + return _ostree_repo_file_new_root (self->repo, self->commit); +} + +static guint +ostree_repo_file_hash (GFile *file) +{ + OstreeRepoFile *self = OSTREE_REPO_FILE (file); + + if (self->parent) + return g_file_hash (self->parent) + g_str_hash (self->name); + else + return g_str_hash (self->commit); +} + +static gboolean +ostree_repo_file_equal (GFile *file1, + GFile *file2) +{ + OstreeRepoFile *self1 = OSTREE_REPO_FILE (file1); + OstreeRepoFile *self2 = OSTREE_REPO_FILE (file2); + + if (self1->parent && self2->parent) + { + return g_str_equal (self1->name, self2->name) + && g_file_equal ((GFile*)self1->parent, (GFile*)self2->parent); + } + else if (!self1->parent && !self2->parent) + { + return g_str_equal (self1->commit, self2->commit); + } + else + return FALSE; +} + +static const char * +match_prefix (const char *path, + const char *prefix) +{ + int prefix_len; + + prefix_len = strlen (prefix); + if (strncmp (path, prefix, prefix_len) != 0) + return NULL; + + /* Handle the case where prefix is the root, so that + * the IS_DIR_SEPRARATOR check below works */ + if (prefix_len > 0 && + G_IS_DIR_SEPARATOR (prefix[prefix_len-1])) + prefix_len--; + + return path + prefix_len; +} + +static gboolean +ostree_repo_file_prefix_matches (GFile *parent, + GFile *descendant) +{ + const char *remainder; + char *parent_path; + char *descendant_path; + + parent_path = g_file_get_path (parent); + descendant_path = g_file_get_path (descendant); + remainder = match_prefix (descendant_path, parent_path); + g_free (parent_path); + g_free (descendant_path); + if (remainder != NULL && G_IS_DIR_SEPARATOR (*remainder)) + return TRUE; + return FALSE; +} + +static char * +ostree_repo_file_get_relative_path (GFile *parent, + GFile *descendant) +{ + const char *remainder; + char *parent_path; + char *descendant_path; + + parent_path = g_file_get_path (parent); + descendant_path = g_file_get_path (descendant); + remainder = match_prefix (descendant_path, parent_path); + g_free (parent_path); + g_free (descendant_path); + + if (remainder != NULL && G_IS_DIR_SEPARATOR (*remainder)) + return g_strdup (remainder + 1); + return NULL; +} + +static GFile * +ostree_repo_file_resolve_relative_path (GFile *file, + const char *relative_path) +{ + OstreeRepoFile *self = OSTREE_REPO_FILE (file); + OstreeRepoFile *parent; + char *filename; + const char *rest; + GFile *ret; + + if (g_path_is_absolute (relative_path) && self->parent) + { + g_assert (*relative_path == '/'); + return ostree_repo_file_resolve_relative_path ((GFile*)_ostree_repo_file_get_root (self), + relative_path+1); + } + + rest = strchr (relative_path, '/'); + if (rest) + { + rest += 1; + filename = g_strndup (relative_path, rest - relative_path); + } + else + filename = g_strdup (relative_path); + + parent = (OstreeRepoFile*)_ostree_repo_file_new_child (self, filename); + g_free (filename); + + if (!rest) + ret = (GFile*)parent; + else + { + ret = ostree_repo_file_resolve_relative_path ((GFile*)parent, rest); + g_clear_object (&parent); + } + return ret; +} + +static GFileEnumerator * +ostree_repo_file_enumerate_children (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + OstreeRepoFile *self = OSTREE_REPO_FILE (file); + return _ostree_repo_file_enumerator_new (self, + attributes, flags, + cancellable, error); +} + +static GFile * +ostree_repo_file_get_child_for_display_name (GFile *file, + const char *display_name, + GError **error) +{ + return g_file_get_child (file, display_name); +} + +static GFile * +get_child_local_file (OstreeRepo *repo, + const char *checksum) +{ + char *path; + GFile *ret; + + path = ostree_repo_get_object_path (repo, checksum, OSTREE_OBJECT_TYPE_FILE); + ret = ot_util_new_file_for_path (path); + g_free (path); + + return ret; +} + +static gboolean +query_child_info_file_nonarchive (OstreeRepo *repo, + const char *checksum, + GFileAttributeMatcher *matcher, + GFileInfo *info, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GFileInfo *local_info = NULL; + GFile *local_file = NULL; + int i ; + const char *mapped_boolean[] = { + "standard::is-symlink" + }; + const char *mapped_string[] = { + }; + const char *mapped_byte_string[] = { + "standard::symlink-target" + }; + const char *mapped_uint32[] = { + "standard::type", + "unix::device", + "unix::mode", + "unix::nlink", + "unix::uid", + "unix::gid", + "unix::rdev" + }; + const char *mapped_uint64[] = { + "standard::size", + "standard::allocated-size", + "unix::inode" + }; + + if (!(g_file_attribute_matcher_matches (matcher, "unix::mode") + || g_file_attribute_matcher_matches (matcher, "standard::type"))) + { + ret = TRUE; + goto out; + } + + local_file = get_child_local_file (repo, checksum); + local_info = g_file_query_info (local_file, + OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, + error); + if (!local_info) + goto out; + + for (i = 0; i < G_N_ELEMENTS (mapped_boolean); i++) + g_file_info_set_attribute_boolean (info, mapped_boolean[i], g_file_info_get_attribute_boolean (local_info, mapped_boolean[i])); + + for (i = 0; i < G_N_ELEMENTS (mapped_string); i++) + { + const char *string = g_file_info_get_attribute_string (local_info, mapped_string[i]); + if (string) + g_file_info_set_attribute_string (info, mapped_string[i], string); + } + + for (i = 0; i < G_N_ELEMENTS (mapped_byte_string); i++) + { + const char *byte_string = g_file_info_get_attribute_byte_string (local_info, mapped_byte_string[i]); + if (byte_string) + g_file_info_set_attribute_byte_string (info, mapped_byte_string[i], byte_string); + } + + for (i = 0; i < G_N_ELEMENTS (mapped_uint32); i++) + g_file_info_set_attribute_uint32 (info, mapped_uint32[i], g_file_info_get_attribute_uint32 (local_info, mapped_uint32[i])); + + for (i = 0; i < G_N_ELEMENTS (mapped_uint64); i++) + g_file_info_set_attribute_uint64 (info, mapped_uint64[i], g_file_info_get_attribute_uint64 (local_info, mapped_uint64[i])); + + ret = TRUE; + out: + g_clear_object (&local_info); + g_clear_object (&local_file); + return ret; +} + +static gboolean +query_child_info_file_archive (OstreeRepo *repo, + const char *checksum, + GFileAttributeMatcher *matcher, + GFileInfo *info, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GFile *local_file = NULL; + GVariant *metadata = NULL; + GInputStream *input = NULL; + guint32 version, uid, gid, mode; + guint64 content_len; + guint32 file_type; + gsize bytes_read; + char *buf = NULL; + + local_file = get_child_local_file (repo, checksum); + + if (!ostree_parse_packed_file (local_file, &metadata, &input, cancellable, error)) + goto out; + + g_variant_get (metadata, "(uuuu@a(ayay)t)", + &version, &uid, &gid, &mode, + NULL, &content_len); + uid = GUINT32_FROM_BE (uid); + gid = GUINT32_FROM_BE (gid); + mode = GUINT32_FROM_BE (mode); + content_len = GUINT64_FROM_BE (content_len); + + g_file_info_set_attribute_boolean (info, "standard::is-symlink", + S_ISLNK (mode)); + if (S_ISLNK (mode)) + file_type = G_FILE_TYPE_SYMBOLIC_LINK; + else if (S_ISREG (mode)) + file_type = G_FILE_TYPE_REGULAR; + else if (S_ISBLK (mode) || S_ISCHR(mode)) + file_type = G_FILE_TYPE_SPECIAL; + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted packfile %s: Invalid mode", checksum); + goto out; + } + g_file_info_set_attribute_uint32 (info, "standard::type", file_type); + + g_file_info_set_attribute_uint32 (info, "unix::uid", uid); + g_file_info_set_attribute_uint32 (info, "unix::gid", gid); + g_file_info_set_attribute_uint32 (info, "unix::mode", mode); + + if (file_type == G_FILE_TYPE_REGULAR) + { + g_file_info_set_attribute_uint64 (info, "standard::size", content_len); + } + else if (file_type == G_FILE_TYPE_SYMBOLIC_LINK) + { + gsize len = MIN (PATH_MAX, content_len) + 1; + buf = g_malloc (len); + + if (!g_input_stream_read_all (input, buf, len, &bytes_read, cancellable, error)) + goto out; + buf[bytes_read] = '\0'; + + g_file_info_set_attribute_byte_string (info, "standard::symlink-target", buf); + } + else if (file_type == G_FILE_TYPE_SPECIAL) + { + guint32 device; + + if (!g_input_stream_read_all (input, &device, 4, &bytes_read, cancellable, error)) + goto out; + + device = GUINT32_FROM_BE (device); + g_file_info_set_attribute_uint32 (info, "unix::device", device); + } + + ret = TRUE; + out: + g_free (buf); + if (metadata) + g_variant_unref (metadata); + g_clear_object (&local_file); + g_clear_object (&input); + return ret; +} + +static void +set_info_from_dirmeta (GFileInfo *info, + GVariant *metadata) +{ + guint32 version, uid, gid, mode; + + g_file_info_set_attribute_uint32 (info, "standard::type", G_FILE_TYPE_DIRECTORY); + + /* PARSE OSTREE_SERIALIZED_DIRMETA_VARIANT */ + g_variant_get (metadata, "(uuuu@a(ayay))", + &version, &uid, &gid, &mode, + NULL); + version = GUINT32_FROM_BE (version); + uid = GUINT32_FROM_BE (uid); + gid = GUINT32_FROM_BE (gid); + mode = GUINT32_FROM_BE (mode); + + g_file_info_set_attribute_uint32 (info, "unix::uid", uid); + g_file_info_set_attribute_uint32 (info, "unix::gid", gid); + g_file_info_set_attribute_uint32 (info, "unix::mode", mode); +} + +static gboolean +query_child_info_dir (OstreeRepo *repo, + const char *metadata_checksum, + GFileAttributeMatcher *matcher, + GFileQueryInfoFlags flags, + GFileInfo *info, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GVariant *metadata = NULL; + + if (!g_file_attribute_matcher_matches (matcher, "unix::mode")) + { + ret = TRUE; + goto out; + } + + if (!ostree_repo_load_variant_checked (repo, OSTREE_SERIALIZED_DIRMETA_VARIANT, + metadata_checksum, &metadata, error)) + goto out; + + set_info_from_dirmeta (info, metadata); + + ret = TRUE; + out: + if (metadata) + g_variant_unref (metadata); + return ret; +} + +static gboolean +bsearch_in_file_variant (GVariant *variant, + const char *name, + int *out_pos) +{ + int i, n; + int m; + + i = 0; + n = g_variant_n_children (variant) - 1; + m = 0; + + while (i <= n) + { + GVariant *child; + const char *cur; + int cmp; + + m = i + ((n - i) / 2); + + child = g_variant_get_child_value (variant, m); + g_variant_get_child (child, 0, "&s", &cur, NULL); + + cmp = strcmp (cur, name); + if (cmp < 0) + i = m + 1; + else if (cmp > 0) + n = m - 1; + else + { + g_variant_unref (child); + *out_pos = m; + return TRUE; + } + g_variant_unref (child); + } + + *out_pos = m; + return FALSE; +} + +static GVariant * +remove_variant_child (GVariant *variant, + int n) +{ + GVariantBuilder builder; + GVariantIter *iter; + int i; + GVariant *child; + + g_variant_builder_init (&builder, g_variant_get_type (variant)); + iter = g_variant_iter_new (variant); + + i = 0; + while ((child = g_variant_iter_next_value (iter)) != NULL) + { + if (i != n) + g_variant_builder_add_value (&builder, child); + g_variant_unref (child); + } + g_variant_iter_free (iter); + + return g_variant_builder_end (&builder); +} + +static GVariant * +insert_variant_child (GVariant *variant, + int n, + GVariant *item) +{ + GVariantBuilder builder; + GVariantIter *iter; + int i; + GVariant *child; + + g_variant_builder_init (&builder, g_variant_get_type (variant)); + iter = g_variant_iter_new (variant); + + i = 0; + while ((child = g_variant_iter_next_value (iter)) != NULL) + { + if (i == n) + g_variant_builder_add_value (&builder, item); + g_variant_builder_add_value (&builder, child); + g_variant_unref (child); + } + g_variant_iter_free (iter); + + return g_variant_builder_end (&builder); +} + +static void +tree_replace_contents (OstreeRepoFile *self, + GVariant *new_files, + GVariant *new_dirs) +{ + guint version; + GVariant *metadata; + GVariant *tmp_files = NULL; + GVariant *tmp_dirs = NULL; + + if (!(new_files || new_dirs) && self->tree_contents) + return; + else if (!self->tree_contents) + { + version = GUINT32_TO_BE (0); + metadata = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0); + tmp_dirs = g_variant_new_array (G_VARIANT_TYPE ("(ss)"), NULL, 0); + tmp_files = g_variant_new_array (G_VARIANT_TYPE ("(ss)"), NULL, 0); + } + else + { + g_variant_get_child (self->tree_contents, 0, "u", &version); + metadata = g_variant_get_child_value (self->tree_contents, 1); + if (!new_files) + tmp_files = g_variant_get_child_value (self->tree_contents, 2); + if (!new_dirs) + tmp_dirs = g_variant_get_child_value (self->tree_contents, 3); + } + + if (self->tree_contents) + g_variant_unref (self->tree_contents); + self->tree_contents = g_variant_new ("(u@a{sv}@a(ss)@a(sss))", version, metadata, + new_files ? new_files : tmp_files, + new_dirs ? new_dirs : tmp_dirs); + + g_variant_unref (metadata); + if (tmp_files) + g_variant_unref (tmp_files); + if (tmp_dirs) + g_variant_unref (tmp_dirs); +} + +void +_ostree_repo_file_tree_remove_child (OstreeRepoFile *self, + const char *name) +{ + int i; + GVariant *files_variant; + GVariant *new_files_variant = NULL; + GVariant *dirs_variant; + GVariant *new_dirs_variant = NULL; + + files_variant = g_variant_get_child_value (self->tree_contents, 2); + dirs_variant = g_variant_get_child_value (self->tree_contents, 3); + + if (bsearch_in_file_variant (files_variant, name, &i)) + { + new_files_variant = remove_variant_child (files_variant, i); + } + else + { + if (bsearch_in_file_variant (dirs_variant, name, &i)) + { + new_dirs_variant = remove_variant_child (dirs_variant, i); + } + } + + tree_replace_contents (self, new_files_variant, new_dirs_variant); + + g_variant_unref (files_variant); + g_variant_unref (dirs_variant); +} + +void +_ostree_repo_file_tree_add_file (OstreeRepoFile *self, + const char *name, + const char *checksum) +{ + int n; + GVariant *files_variant; + GVariant *new_files_variant; + + files_variant = g_variant_get_child_value (self->tree_contents, 2); + + if (!bsearch_in_file_variant (files_variant, name, &n)) + { + new_files_variant = insert_variant_child (files_variant, n, + g_variant_new ("(ss)", name, checksum)); + g_variant_ref_sink (new_files_variant); + tree_replace_contents (self, new_files_variant, NULL); + g_variant_unref (new_files_variant); + } + g_variant_unref (files_variant); +} + +void +_ostree_repo_file_tree_add_dir (OstreeRepoFile *self, + const char *name, + const char *content_checksum, + const char *metadata_checksum) +{ + int n; + GVariant *dirs_variant; + GVariant *new_dirs_variant; + + dirs_variant = g_variant_get_child_value (self->tree_contents, 3); + + if (!bsearch_in_file_variant (dirs_variant, name, &n)) + { + new_dirs_variant = insert_variant_child (dirs_variant, n, + g_variant_new ("(sss)", name, content_checksum, + metadata_checksum)); + g_variant_ref_sink (new_dirs_variant); + tree_replace_contents (self, NULL, new_dirs_variant); + g_variant_unref (new_dirs_variant); + } + g_variant_unref (dirs_variant); +} + +int +_ostree_repo_file_tree_find_child (OstreeRepoFile *self, + const char *name, + gboolean *is_dir, + GVariant **out_container) +{ + int i; + GVariant *files_variant = NULL; + GVariant *dirs_variant = NULL; + GVariant *ret_container = NULL; + + files_variant = g_variant_get_child_value (self->tree_contents, 2); + dirs_variant = g_variant_get_child_value (self->tree_contents, 3); + + i = -1; + if (bsearch_in_file_variant (files_variant, name, &i)) + { + *is_dir = FALSE; + ret_container = files_variant; + files_variant = NULL; + } + else + { + if (bsearch_in_file_variant (dirs_variant, name, &i)) + { + *is_dir = TRUE; + ret_container = dirs_variant; + dirs_variant = NULL; + } + else + { + i = -1; + } + } + if (ret_container && out_container) + { + *out_container = ret_container; + ret_container = NULL; + } + if (ret_container) + g_variant_unref (ret_container); + if (files_variant) + g_variant_unref (files_variant); + if (dirs_variant) + g_variant_unref (dirs_variant); + return i; +} + +gboolean +_ostree_repo_file_tree_query_child (OstreeRepoFile *self, + int n, + const char *attributes, + GFileQueryInfoFlags flags, + GFileInfo **out_info, + GCancellable *cancellable, + GError **error) +{ + const char *name = NULL; + gboolean ret = FALSE; + GFileInfo *ret_info = NULL; + GVariant *files_variant = NULL; + GVariant *dirs_variant = NULL; + GVariant *tree_child_metadata = NULL; + GFileAttributeMatcher *matcher = NULL; + int c; + + if (!_ostree_repo_file_ensure_resolved (self, error)) + goto out; + + matcher = g_file_attribute_matcher_new (attributes); + + ret_info = g_file_info_new (); + + g_assert (self->tree_contents); + + files_variant = g_variant_get_child_value (self->tree_contents, 2); + dirs_variant = g_variant_get_child_value (self->tree_contents, 3); + + c = g_variant_n_children (files_variant); + if (n < c) + { + const char *checksum; + + g_variant_get_child (files_variant, n, "(&s&s)", &name, &checksum); + + if (ostree_repo_is_archive (self->repo)) + { + if (!query_child_info_file_archive (self->repo, checksum, matcher, ret_info, + cancellable, error)) + goto out; + } + else + { + if (!query_child_info_file_nonarchive (self->repo, checksum, matcher, ret_info, + cancellable, error)) + goto out; + } + } + else + { + const char *tree_checksum; + const char *meta_checksum; + + n -= c; + + c = g_variant_n_children (dirs_variant); + + if (n < c) + { + g_variant_get_child (dirs_variant, n, "(&s&s&s)", + &name, &tree_checksum, &meta_checksum); + + if (!query_child_info_dir (self->repo, meta_checksum, + matcher, flags, ret_info, + cancellable, error)) + goto out; + } + else + n -= c; + } + + if (name) + { + g_file_info_set_attribute_byte_string (ret_info, "standard::name", + name); + g_file_info_set_attribute_string (ret_info, "standard::display-name", + name); + if (*name == '.') + g_file_info_set_is_hidden (ret_info, TRUE); + } + else + { + g_clear_object (&ret_info); + } + + ret = TRUE; + *out_info = ret_info; + ret_info = NULL; + out: + g_clear_object (&ret_info); + if (matcher) + g_file_attribute_matcher_unref (matcher); + if (tree_child_metadata) + g_variant_unref (tree_child_metadata); + g_variant_unref (files_variant); + g_variant_unref (dirs_variant); + return ret; +} + +static GFileInfo * +ostree_repo_file_query_info (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + OstreeRepoFile *self = OSTREE_REPO_FILE (file); + gboolean ret = FALSE; + GFileInfo *info = NULL; + + if (!_ostree_repo_file_ensure_resolved (self, error)) + goto out; + + if (!self->parent) + { + info = g_file_info_new (); + set_info_from_dirmeta (info, self->tree_metadata); + } + else + { + if (!_ostree_repo_file_tree_query_child (self->parent, self->index, + attributes, flags, + &info, cancellable, error)) + goto out; + } + + ret = TRUE; + out: + if (!ret) + g_clear_object (&info); + return info; +} + +static GFileAttributeInfoList * +ostree_repo_file_query_settable_attributes (GFile *file, + GCancellable *cancellable, + GError **error) +{ + return g_file_attribute_info_list_new (); +} + +static GFileAttributeInfoList * +ostree_repo_file_query_writable_namespaces (GFile *file, + GCancellable *cancellable, + GError **error) +{ + return g_file_attribute_info_list_new (); +} + +static GFileInputStream * +ostree_repo_file_read (GFile *file, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GFile *local_file = NULL; + GFileInputStream *ret_stream = NULL; + OstreeRepoFile *self = OSTREE_REPO_FILE (file); + + if (self->tree_contents) + { + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_IS_DIRECTORY, + "Can't open directory"); + goto out; + } + + if (ostree_repo_is_archive (self->repo)) + { + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Can't open archived file (yet)"); + goto out; + } + else + { + local_file = _ostree_repo_file_nontree_get_local (self); + ret_stream = g_file_read (local_file, cancellable, error); + if (!ret_stream) + goto out; + } + + ret = TRUE; + out: + g_clear_object (&local_file); + if (!ret) + g_clear_object (&ret_stream); + return ret_stream; +} + +static void +ostree_repo_file_file_iface_init (GFileIface *iface) +{ + iface->dup = ostree_repo_file_dup; + iface->hash = ostree_repo_file_hash; + iface->equal = ostree_repo_file_equal; + iface->is_native = ostree_repo_file_is_native; + iface->has_uri_scheme = ostree_repo_file_has_uri_scheme; + iface->get_uri_scheme = ostree_repo_file_get_uri_scheme; + iface->get_basename = ostree_repo_file_get_basename; + iface->get_path = ostree_repo_file_get_path; + iface->get_uri = ostree_repo_file_get_uri; + iface->get_parse_name = ostree_repo_file_get_parse_name; + iface->get_parent = ostree_repo_file_get_parent; + iface->prefix_matches = ostree_repo_file_prefix_matches; + iface->get_relative_path = ostree_repo_file_get_relative_path; + iface->resolve_relative_path = ostree_repo_file_resolve_relative_path; + iface->get_child_for_display_name = ostree_repo_file_get_child_for_display_name; + iface->set_display_name = NULL; + iface->enumerate_children = ostree_repo_file_enumerate_children; + iface->query_info = ostree_repo_file_query_info; + iface->query_filesystem_info = NULL; + iface->find_enclosing_mount = NULL; + iface->query_settable_attributes = ostree_repo_file_query_settable_attributes; + iface->query_writable_namespaces = ostree_repo_file_query_writable_namespaces; + iface->set_attribute = NULL; + iface->set_attributes_from_info = NULL; + iface->read_fn = ostree_repo_file_read; + iface->append_to = NULL; + iface->create = NULL; + iface->replace = NULL; + iface->open_readwrite = NULL; + iface->create_readwrite = NULL; + iface->replace_readwrite = NULL; + iface->delete_file = NULL; + iface->trash = NULL; + iface->make_directory = NULL; + iface->make_symbolic_link = NULL; + iface->copy = NULL; + iface->move = NULL; + iface->monitor_dir = NULL; + iface->monitor_file = NULL; + + iface->supports_thread_contexts = TRUE; +} diff --git a/libostree/ostree-repo-file.h b/libostree/ostree-repo-file.h new file mode 100644 index 00000000..4e16c716 --- /dev/null +++ b/libostree/ostree-repo-file.h @@ -0,0 +1,114 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Colin Walters + */ + +#ifndef _OSTREE_REPO_FILE +#define _OSTREE_REPO_FILE + +#include "ostree-repo.h" + +G_BEGIN_DECLS + +#define OSTREE_TYPE_REPO_FILE (_ostree_repo_file_get_type ()) +#define OSTREE_REPO_FILE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OSTREE_TYPE_REPO_FILE, OstreeRepoFile)) +#define OSTREE_REPO_FILE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), OSTREE_TYPE_REPO_FILE, OstreeRepoFileClass)) +#define OSTREE_IS_LOCAL_FILE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSTREE_TYPE_REPO_FILE)) +#define OSTREE_IS_LOCAL_FILE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OSTREE_TYPE_REPO_FILE)) +#define OSTREE_REPO_FILE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OSTREE_TYPE_REPO_FILE, OstreeRepoFileClass)) + +typedef struct _OstreeRepoFile OstreeRepoFile; +typedef struct _OstreeRepoFileClass OstreeRepoFileClass; + +struct _OstreeRepoFileClass +{ + GObjectClass parent_class; +}; + +GType _ostree_repo_file_get_type (void) G_GNUC_CONST; + +GFile * _ostree_repo_file_new_root (OstreeRepo *repo, + const char *commit); + +OstreeRepoFile * _ostree_repo_file_new_empty_tree (OstreeRepo *repo); + +GFile * _ostree_repo_file_new_child (OstreeRepoFile *parent, + const char *name); + +gboolean _ostree_repo_file_ensure_resolved (OstreeRepoFile *self, + GError **error); + +gboolean _ostree_repo_file_get_xattrs (OstreeRepoFile *self, + GVariant **out_xattrs, + GCancellable *cancellable, + GError **error); + +OstreeRepo * _ostree_repo_file_get_repo (OstreeRepoFile *self); +OstreeRepoFile * _ostree_repo_file_get_root (OstreeRepoFile *self); + +void _ostree_repo_file_make_empty_tree (OstreeRepoFile *self); + +void _ostree_repo_file_tree_set_metadata (OstreeRepoFile *self, + const char *checksum, + GVariant *metadata); + +void _ostree_repo_file_tree_set_content_checksum (OstreeRepoFile *self, + const char *checksum); +const char *_ostree_repo_file_tree_get_content_checksum (OstreeRepoFile *self); + +gboolean _ostree_repo_file_is_tree (OstreeRepoFile *self); + +const char * _ostree_repo_file_nontree_get_checksum (OstreeRepoFile *self); + +GFile *_ostree_repo_file_nontree_get_local (OstreeRepoFile *self); + +void _ostree_repo_file_tree_remove_child (OstreeRepoFile *self, + const char *name); + +void _ostree_repo_file_tree_add_file (OstreeRepoFile *self, + const char *name, + const char *checksum); + +void _ostree_repo_file_tree_add_dir (OstreeRepoFile *self, + const char *name, + const char *content_checksum, + const char *metadata_checksum); + +int _ostree_repo_file_tree_find_child (OstreeRepoFile *self, + const char *name, + gboolean *is_dir, + GVariant **out_container); + +const char *_ostree_repo_file_tree_get_child_checksum (OstreeRepoFile *self, + int n); + +gboolean _ostree_repo_file_tree_query_child (OstreeRepoFile *self, + int n, + const char *attributes, + GFileQueryInfoFlags flags, + GFileInfo **out_info, + GCancellable *cancellable, + GError **error); + +GVariant *_ostree_repo_file_tree_get_contents (OstreeRepoFile *self); +GVariant *_ostree_repo_file_tree_get_metadata (OstreeRepoFile *self); + +G_END_DECLS + +#endif diff --git a/libostree/ostree-repo.c b/libostree/ostree-repo.c index 0f5fb88f..1d9e8532 100644 --- a/libostree/ostree-repo.c +++ b/libostree/ostree-repo.c @@ -25,6 +25,7 @@ #include "ostree.h" #include "otutil.h" +#include "ostree-repo-file-enumerator.h" #include #include @@ -1016,142 +1017,6 @@ parsed_tree_data_free (ParsedTreeData *pdata) g_free (pdata); } -static gboolean -parse_tree (OstreeRepo *self, - const char *sha256, - ParsedTreeData **out_pdata, - GError **error) -{ - gboolean ret = FALSE; - ParsedTreeData *ret_pdata = NULL; - int i, n; - guint32 version; - GVariant *tree_variant = NULL; - GVariant *meta_variant = NULL; - GVariant *files_variant = NULL; - GVariant *dirs_variant = NULL; - - if (!ostree_repo_load_variant_checked (self, OSTREE_SERIALIZED_TREE_VARIANT, - sha256, &tree_variant, error)) - goto out; - - /* PARSE OSTREE_SERIALIZED_TREE_VARIANT */ - g_variant_get (tree_variant, "(u@a{sv}@a(ss)@a(sss))", - &version, &meta_variant, &files_variant, &dirs_variant); - version = GUINT32_FROM_BE (version); - - ret_pdata = parsed_tree_data_new (); - n = g_variant_n_children (files_variant); - for (i = 0; i < n; i++) - { - const char *filename; - const char *checksum; - - g_variant_get_child (files_variant, i, "(&s&s)", &filename, &checksum); - - g_hash_table_insert (ret_pdata->files, g_strdup (filename), g_strdup (checksum)); - } - - n = g_variant_n_children (dirs_variant); - for (i = 0; i < n; i++) - { - const char *dirname; - const char *tree_checksum; - const char *meta_checksum; - ParsedTreeData *child_tree = NULL; - GVariant *metadata = NULL; - ParsedDirectoryData *child_dir = NULL; - - g_variant_get_child (dirs_variant, i, "(&s&s&s)", - &dirname, &tree_checksum, &meta_checksum); - - if (!parse_tree (self, tree_checksum, &child_tree, error)) - goto out; - - if (!ostree_repo_load_variant_checked (self, OSTREE_SERIALIZED_DIRMETA_VARIANT, - meta_checksum, &metadata, error)) - { - parsed_tree_data_free (child_tree); - goto out; - } - - child_dir = g_new0 (ParsedDirectoryData, 1); - child_dir->tree_data = child_tree; - child_dir->metadata_sha256 = g_strdup (meta_checksum); - child_dir->meta_data = metadata; - - g_hash_table_insert (ret_pdata->directories, g_strdup (dirname), child_dir); - } - - ret = TRUE; - out: - if (!ret) - parsed_tree_data_free (ret_pdata); - else - *out_pdata = ret_pdata; - if (tree_variant) - g_variant_unref (tree_variant); - if (meta_variant) - g_variant_unref (meta_variant); - if (files_variant) - g_variant_unref (files_variant); - if (dirs_variant) - g_variant_unref (dirs_variant); - return ret; -} - -static gboolean -load_commit_and_trees (OstreeRepo *self, - const char *commit_sha256, - GVariant **out_commit, - ParsedDirectoryData **out_root_data, - GError **error) -{ - GVariant *ret_commit = NULL; - ParsedDirectoryData *ret_root_data = NULL; - ParsedTreeData *tree_data = NULL; - char *ret_metadata_checksum = NULL; - GVariant *root_metadata = NULL; - gboolean ret = FALSE; - const char *tree_contents_checksum; - const char *tree_meta_checksum; - - if (!ostree_repo_load_variant_checked (self, OSTREE_SERIALIZED_COMMIT_VARIANT, - commit_sha256, &ret_commit, error)) - goto out; - - /* PARSE OSTREE_SERIALIZED_COMMIT_VARIANT */ - g_variant_get_child (ret_commit, 6, "&s", &tree_contents_checksum); - g_variant_get_child (ret_commit, 7, "&s", &tree_meta_checksum); - - if (!ostree_repo_load_variant_checked (self, OSTREE_SERIALIZED_DIRMETA_VARIANT, - tree_meta_checksum, &root_metadata, error)) - goto out; - - if (!parse_tree (self, tree_contents_checksum, &tree_data, error)) - goto out; - - ret_root_data = g_new0 (ParsedDirectoryData, 1); - ret_root_data->tree_data = tree_data; - ret_root_data->metadata_sha256 = g_strdup (tree_meta_checksum); - ret_root_data->meta_data = root_metadata; - root_metadata = NULL; - - ret = TRUE; - *out_commit = ret_commit; - ret_commit = NULL; - *out_root_data = ret_root_data; - ret_root_data = NULL; - out: - if (ret_commit) - g_variant_unref (ret_commit); - parsed_directory_data_free (ret_root_data); - g_free (ret_metadata_checksum); - if (root_metadata) - g_variant_unref (root_metadata); - return ret; -} - static GVariant * create_empty_gvariant_dict (void) { @@ -1807,106 +1672,130 @@ ostree_repo_load_variant (OstreeRepo *self, static gboolean checkout_tree (OstreeRepo *self, - ParsedTreeData *tree, + OstreeRepoFile *dir, const char *destination, + GCancellable *cancellable, GError **error); static gboolean checkout_one_directory (OstreeRepo *self, const char *destination, const char *dirname, - ParsedDirectoryData *dir, + OstreeRepoFile *dir, + GFileInfo *dir_info, + GCancellable *cancellable, GError **error) { gboolean ret = FALSE; char *dest_path = NULL; - guint32 version, uid, gid, mode; GVariant *xattr_variant = NULL; dest_path = g_build_filename (destination, dirname, NULL); - - /* PARSE OSTREE_SERIALIZED_DIRMETA_VARIANT */ - g_variant_get (dir->meta_data, "(uuuu@a(ayay))", - &version, &uid, &gid, &mode, - &xattr_variant); - version = GUINT32_FROM_BE (version); - uid = GUINT32_FROM_BE (uid); - gid = GUINT32_FROM_BE (gid); - mode = GUINT32_FROM_BE (mode); - if (mkdir (dest_path, (mode_t)mode) < 0) + if (!_ostree_repo_file_get_xattrs (dir, &xattr_variant, NULL, error)) + goto out; + + if (mkdir (dest_path, (mode_t)g_file_info_get_attribute_uint32 (dir_info, "unix::mode")) < 0) { ot_util_set_error_from_errno (error, errno); g_prefix_error (error, "Failed to create directory '%s': ", dest_path); goto out; } - - if (!checkout_tree (self, dir->tree_data, dest_path, error)) - goto out; - if (!ostree_set_xattrs (dest_path, xattr_variant, error)) + if (!ostree_set_xattrs (dest_path, xattr_variant, cancellable, error)) + goto out; + + if (!checkout_tree (self, dir, dest_path, cancellable, error)) goto out; ret = TRUE; out: g_free (dest_path); - g_variant_unref (xattr_variant); + if (xattr_variant) + g_variant_unref (xattr_variant); return ret; } static gboolean checkout_tree (OstreeRepo *self, - ParsedTreeData *tree, + OstreeRepoFile *dir, const char *destination, + GCancellable *cancellable, GError **error) { OstreeRepoPrivate *priv = GET_PRIVATE (self); gboolean ret = FALSE; - GHashTableIter hash_iter; - gpointer key, value; + GError *temp_error = NULL; + GFileInfo *file_info = NULL; + GFileEnumerator *dir_enum = NULL; + GFile *child = NULL; + char *object_path = NULL; + char *dest_path = NULL; - g_hash_table_iter_init (&hash_iter, tree->files); - while (g_hash_table_iter_next (&hash_iter, &key, &value)) + dir_enum = g_file_enumerate_children ((GFile*)dir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, + error); + if (!dir_enum) + goto out; + + while ((file_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL) { - const char *filename = key; - const char *checksum = value; - char *object_path; - char *dest_path; + const char *name; + guint32 type; - object_path = ostree_repo_get_object_path (self, checksum, OSTREE_OBJECT_TYPE_FILE); - dest_path = g_build_filename (destination, filename, NULL); - - if (priv->archive) + name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); + type = g_file_info_get_attribute_uint32 (file_info, "standard::type"); + + child = g_file_get_child ((GFile*)dir, name); + + if (type == G_FILE_TYPE_DIRECTORY) { - if (!ostree_unpack_object (object_path, OSTREE_OBJECT_TYPE_FILE, dest_path, NULL, error)) + if (!checkout_one_directory (self, destination, name, (OstreeRepoFile*)child, file_info, cancellable, error)) goto out; } else { - if (link (object_path, dest_path) < 0) - { - ot_util_set_error_from_errno (error, errno); - g_free (object_path); - g_free (dest_path); - goto out; - } - g_free (object_path); - g_free (dest_path); - } - } + const char *checksum = _ostree_repo_file_nontree_get_checksum ((OstreeRepoFile*)child); - g_hash_table_iter_init (&hash_iter, tree->directories); - while (g_hash_table_iter_next (&hash_iter, &key, &value)) + dest_path = g_build_filename (destination, name, NULL); + object_path = ostree_repo_get_object_path (self, checksum, OSTREE_OBJECT_TYPE_FILE); + + if (priv->archive) + { + if (!ostree_unpack_object (object_path, OSTREE_OBJECT_TYPE_FILE, dest_path, NULL, error)) + goto out; + } + else + { + if (link (object_path, dest_path) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + } + + g_free (object_path); + object_path = NULL; + g_free (dest_path); + dest_path = NULL; + g_clear_object (&file_info); + g_clear_object (&child); + } + if (file_info == NULL && temp_error != NULL) { - const char *dirname = key; - ParsedDirectoryData *dir = value; - - if (!checkout_one_directory (self, destination, dirname, dir, error)) - goto out; + g_propagate_error (error, temp_error); + goto out; } ret = TRUE; out: + g_clear_object (&dir_enum); + g_clear_object (&file_info); + g_clear_object (&child); + g_free (object_path); + g_free (dest_path); return ret; } @@ -1914,13 +1803,13 @@ gboolean ostree_repo_checkout (OstreeRepo *self, const char *rev, const char *destination, + GCancellable *cancellable, GError **error) { gboolean ret = FALSE; - GVariant *commit = NULL; char *resolved = NULL; - char *root_meta_sha = NULL; - ParsedDirectoryData *root = NULL; + OstreeRepoFile *root = NULL; + GFileInfo *root_info = NULL; if (g_file_test (destination, G_FILE_TEST_EXISTS)) { @@ -1933,18 +1822,23 @@ ostree_repo_checkout (OstreeRepo *self, if (!resolve_rev (self, rev, FALSE, &resolved, error)) goto out; - if (!load_commit_and_trees (self, resolved, &commit, &root, error)) + root = (OstreeRepoFile*)_ostree_repo_file_new_root (self, resolved); + if (!_ostree_repo_file_ensure_resolved (root, error)) goto out; - if (!checkout_one_directory (self, destination, NULL, root, error)) + root_info = g_file_query_info ((GFile*)root, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, error); + if (!root_info) + goto out; + + if (!checkout_one_directory (self, destination, NULL, root, root_info, cancellable, error)) goto out; ret = TRUE; out: g_free (resolved); - if (commit) - g_variant_unref (commit); - parsed_directory_data_free (root); - g_free (root_meta_sha); + g_clear_object (&root); + g_clear_object (&root_info); return ret; } diff --git a/libostree/ostree-repo.h b/libostree/ostree-repo.h index 17ec48fe..5f46e46e 100644 --- a/libostree/ostree-repo.h +++ b/libostree/ostree-repo.h @@ -18,7 +18,6 @@ * * Author: Colin Walters */ -/* ostree-repo.h */ #ifndef _OSTREE_REPO #define _OSTREE_REPO @@ -120,9 +119,10 @@ gboolean ostree_repo_commit_from_filelist_fd (OstreeRepo *self, GError **error); gboolean ostree_repo_checkout (OstreeRepo *self, - const char *ref, - const char *destination, - GError **error); + const char *ref, + const char *destination, + GCancellable *cancellable, + GError **error); typedef void (*OstreeRepoObjectIter) (OstreeRepo *self, const char *path, GFileInfo *fileinfo, gpointer user_data); diff --git a/ostree/ot-builtin-checkout.c b/ostree/ot-builtin-checkout.c index 8a80270e..3662033a 100644 --- a/ostree/ot-builtin-checkout.c +++ b/ostree/ot-builtin-checkout.c @@ -64,7 +64,7 @@ ostree_builtin_checkout (int argc, char **argv, const char *repo_path, GError ** commit = argv[1]; destination = argv[2]; - if (!ostree_repo_checkout (repo, commit, destination, error)) + if (!ostree_repo_checkout (repo, commit, destination, NULL, error)) goto out; ret = TRUE; diff --git a/ostree/ot-builtin-compose.c b/ostree/ot-builtin-compose.c index 0f39af04..d43a6e5a 100644 --- a/ostree/ot-builtin-compose.c +++ b/ostree/ot-builtin-compose.c @@ -196,7 +196,7 @@ compose_branch_on_dir (OstreeRepo *repo, branchf = ot_util_new_file_for_path (branchpath); g_print ("Checking out %s (commit %s)...\n", branch, branchrev); - if (!ostree_repo_checkout (repo, branchrev, branchpath, error)) + if (!ostree_repo_checkout (repo, branchrev, branchpath, NULL, error)) goto out; g_print ("...done\n"); g_print ("Merging over destination...\n");