From 18f0b537a45f12852e4ec6b174440cbfe7702e4d Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 14 Nov 2011 15:39:38 -0500 Subject: [PATCH] build: Move sources into src/ again This is necessary if we want to build when srcdir == builddir, otherwise we blow up because "ostree" is a source directory and a binary. --- Makefile-libostree.am | 24 +- Makefile-osbuild.am | 6 +- Makefile-ostree.am | 30 +- Makefile-otutil.am | 24 +- src/libostree/ostree-checkout.c | 362 ++++ src/libostree/ostree-checkout.h | 60 + src/libostree/ostree-core.c | 856 ++++++++ src/libostree/ostree-core.h | 155 ++ src/libostree/ostree-repo-file-enumerator.c | 143 ++ src/libostree/ostree-repo-file-enumerator.h | 55 + src/libostree/ostree-repo-file.c | 1428 +++++++++++++ src/libostree/ostree-repo-file.h | 115 + src/libostree/ostree-repo.c | 1877 +++++++++++++++++ src/libostree/ostree-repo.h | 162 ++ src/libostree/ostree.h | 29 + {libotutil => src/libotutil}/ot-gio-utils.c | 0 {libotutil => src/libotutil}/ot-gio-utils.h | 0 {libotutil => src/libotutil}/ot-glib-compat.c | 0 {libotutil => src/libotutil}/ot-glib-compat.h | 0 {libotutil => src/libotutil}/ot-opt-utils.c | 0 {libotutil => src/libotutil}/ot-opt-utils.h | 0 {libotutil => src/libotutil}/ot-unix-utils.c | 0 {libotutil => src/libotutil}/ot-unix-utils.h | 0 .../libotutil}/ot-variant-utils.c | 0 .../libotutil}/ot-variant-utils.h | 0 {libotutil => src/libotutil}/otutil.h | 0 {osbuild => src/osbuild}/main.c | 0 .../osbuild}/ob-builtin-buildone.c | 0 {osbuild => src/osbuild}/ob-builtins.h | 0 .../osbuild}/osbuild-raw-makeinstall.c | 0 {osbuild => src/osbuild}/ostree-buildone | 0 {osbuild => src/osbuild}/ostree-buildone-make | 0 ...stree-buildone-makeinstall-split-artifacts | 0 {ostree => src/ostree}/main.c | 0 {ostree => src/ostree}/ot-builtin-checkout.c | 0 {ostree => src/ostree}/ot-builtin-commit.c | 0 {ostree => src/ostree}/ot-builtin-compose.c | 0 {ostree => src/ostree}/ot-builtin-diff.c | 0 {ostree => src/ostree}/ot-builtin-fsck.c | 0 {ostree => src/ostree}/ot-builtin-init.c | 0 {ostree => src/ostree}/ot-builtin-log.c | 0 {ostree => src/ostree}/ot-builtin-pull.c | 0 {ostree => src/ostree}/ot-builtin-remote.c | 0 {ostree => src/ostree}/ot-builtin-rev-parse.c | 0 .../ostree}/ot-builtin-run-triggers.c | 0 {ostree => src/ostree}/ot-builtin-show.c | 0 {ostree => src/ostree}/ot-builtins.h | 0 47 files changed, 5284 insertions(+), 42 deletions(-) create mode 100644 src/libostree/ostree-checkout.c create mode 100644 src/libostree/ostree-checkout.h create mode 100644 src/libostree/ostree-core.c create mode 100644 src/libostree/ostree-core.h create mode 100644 src/libostree/ostree-repo-file-enumerator.c create mode 100644 src/libostree/ostree-repo-file-enumerator.h create mode 100644 src/libostree/ostree-repo-file.c create mode 100644 src/libostree/ostree-repo-file.h create mode 100644 src/libostree/ostree-repo.c create mode 100644 src/libostree/ostree-repo.h create mode 100644 src/libostree/ostree.h rename {libotutil => src/libotutil}/ot-gio-utils.c (100%) rename {libotutil => src/libotutil}/ot-gio-utils.h (100%) rename {libotutil => src/libotutil}/ot-glib-compat.c (100%) rename {libotutil => src/libotutil}/ot-glib-compat.h (100%) rename {libotutil => src/libotutil}/ot-opt-utils.c (100%) rename {libotutil => src/libotutil}/ot-opt-utils.h (100%) rename {libotutil => src/libotutil}/ot-unix-utils.c (100%) rename {libotutil => src/libotutil}/ot-unix-utils.h (100%) rename {libotutil => src/libotutil}/ot-variant-utils.c (100%) rename {libotutil => src/libotutil}/ot-variant-utils.h (100%) rename {libotutil => src/libotutil}/otutil.h (100%) rename {osbuild => src/osbuild}/main.c (100%) rename {osbuild => src/osbuild}/ob-builtin-buildone.c (100%) rename {osbuild => src/osbuild}/ob-builtins.h (100%) rename {osbuild => src/osbuild}/osbuild-raw-makeinstall.c (100%) rename {osbuild => src/osbuild}/ostree-buildone (100%) rename {osbuild => src/osbuild}/ostree-buildone-make (100%) rename {osbuild => src/osbuild}/ostree-buildone-makeinstall-split-artifacts (100%) rename {ostree => src/ostree}/main.c (100%) rename {ostree => src/ostree}/ot-builtin-checkout.c (100%) rename {ostree => src/ostree}/ot-builtin-commit.c (100%) rename {ostree => src/ostree}/ot-builtin-compose.c (100%) rename {ostree => src/ostree}/ot-builtin-diff.c (100%) rename {ostree => src/ostree}/ot-builtin-fsck.c (100%) rename {ostree => src/ostree}/ot-builtin-init.c (100%) rename {ostree => src/ostree}/ot-builtin-log.c (100%) rename {ostree => src/ostree}/ot-builtin-pull.c (100%) rename {ostree => src/ostree}/ot-builtin-remote.c (100%) rename {ostree => src/ostree}/ot-builtin-rev-parse.c (100%) rename {ostree => src/ostree}/ot-builtin-run-triggers.c (100%) rename {ostree => src/ostree}/ot-builtin-show.c (100%) rename {ostree => src/ostree}/ot-builtins.h (100%) diff --git a/Makefile-libostree.am b/Makefile-libostree.am index 84737a78..091d152a 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -19,17 +19,17 @@ noinst_LTLIBRARIES += libostree.la -libostree_la_SOURCES = libostree/ostree.h \ - libostree/ostree-core.c \ - 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 \ +libostree_la_SOURCES = src/libostree/ostree.h \ + src/libostree/ostree-core.c \ + src/libostree/ostree-core.h \ + src/libostree/ostree-repo.c \ + src/libostree/ostree-repo.h \ + src/libostree/ostree-repo-file.c \ + src/libostree/ostree-repo-file.h \ + src/libostree/ostree-repo-file-enumerator.c \ + src/libostree/ostree-repo-file-enumerator.h \ + src/libostree/ostree-checkout.c \ + src/libostree/ostree-checkout.h \ $(NULL) -libostree_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/libotutil -I$(srcdir)/libostree -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS) +libostree_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS) libostree_la_LIBADD = libotutil.la $(OT_COREBIN_DEP_LIBS) diff --git a/Makefile-osbuild.am b/Makefile-osbuild.am index 3f2e56b5..8c2b1bd3 100644 --- a/Makefile-osbuild.am +++ b/Makefile-osbuild.am @@ -15,7 +15,7 @@ # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. -bin_SCRIPTS += osbuild/ostree-buildone \ - osbuild/ostree-buildone-make \ - osbuild/ostree-buildone-makeinstall-split-artifacts \ +bin_SCRIPTS += src/osbuild/ostree-buildone \ + src/osbuild/ostree-buildone-make \ + src/osbuild/ostree-buildone-makeinstall-split-artifacts \ $(NULL) diff --git a/Makefile-ostree.am b/Makefile-ostree.am index b39f9bd8..0bb7cdec 100644 --- a/Makefile-ostree.am +++ b/Makefile-ostree.am @@ -19,23 +19,23 @@ bin_PROGRAMS += ostree -ostree_SOURCES = ostree/main.c \ - ostree/ot-builtins.h \ - ostree/ot-builtin-checkout.c \ - ostree/ot-builtin-commit.c \ - ostree/ot-builtin-compose.c \ - ostree/ot-builtin-diff.c \ - ostree/ot-builtin-fsck.c \ - ostree/ot-builtin-init.c \ - ostree/ot-builtin-log.c \ - ostree/ot-builtin-run-triggers.c \ - ostree/ot-builtin-remote.c \ - ostree/ot-builtin-rev-parse.c \ - ostree/ot-builtin-show.c \ +ostree_SOURCES = src/ostree/main.c \ + src/ostree/ot-builtins.h \ + src/ostree/ot-builtin-checkout.c \ + src/ostree/ot-builtin-commit.c \ + src/ostree/ot-builtin-compose.c \ + src/ostree/ot-builtin-diff.c \ + src/ostree/ot-builtin-fsck.c \ + src/ostree/ot-builtin-init.c \ + src/ostree/ot-builtin-log.c \ + src/ostree/ot-builtin-run-triggers.c \ + src/ostree/ot-builtin-remote.c \ + src/ostree/ot-builtin-rev-parse.c \ + src/ostree/ot-builtin-show.c \ $(NULL) if USE_LIBSOUP_GNOME -ostree_SOURCES += ostree/ot-builtin-pull.c +ostree_SOURCES += src/ostree/ot-builtin-pull.c endif -ostree_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/libotutil -I$(srcdir)/libostree -I$(srcdir)/ostree -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS) +ostree_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -I$(srcdir)/src/ostree -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS) ostree_LDADD = libotutil.la libostree.la $(OT_COREBIN_DEP_LIBS) diff --git a/Makefile-otutil.am b/Makefile-otutil.am index ff729bdc..6d4e492c 100644 --- a/Makefile-otutil.am +++ b/Makefile-otutil.am @@ -20,17 +20,17 @@ noinst_LTLIBRARIES += libotutil.la libotutil_la_SOURCES = \ - libotutil/ot-opt-utils.c \ - libotutil/ot-opt-utils.h \ - libotutil/ot-unix-utils.c \ - libotutil/ot-unix-utils.h \ - libotutil/ot-variant-utils.c \ - libotutil/ot-variant-utils.h \ - libotutil/ot-gio-utils.c \ - libotutil/ot-gio-utils.h \ - libotutil/ot-glib-compat.c \ - libotutil/ot-glib-compat.h \ - libotutil/otutil.h \ + src/libotutil/ot-opt-utils.c \ + src/libotutil/ot-opt-utils.h \ + src/libotutil/ot-unix-utils.c \ + src/libotutil/ot-unix-utils.h \ + src/libotutil/ot-variant-utils.c \ + src/libotutil/ot-variant-utils.h \ + src/libotutil/ot-gio-utils.c \ + src/libotutil/ot-gio-utils.h \ + src/libotutil/ot-glib-compat.c \ + src/libotutil/ot-glib-compat.h \ + src/libotutil/otutil.h \ $(NULL) -libotutil_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS) +libotutil_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS) libotutil_la_LIBADD = $(GIO_UNIX_LIBS) diff --git a/src/libostree/ostree-checkout.c b/src/libostree/ostree-checkout.c new file mode 100644 index 00000000..0d584986 --- /dev/null +++ b/src/libostree/ostree-checkout.c @@ -0,0 +1,362 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; 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.h" +#include "otutil.h" + +enum { + PROP_0, + + PROP_REPO, + PROP_PATH +}; + +G_DEFINE_TYPE (OstreeCheckout, ostree_checkout, G_TYPE_OBJECT) + +#define GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), OSTREE_TYPE_CHECKOUT, OstreeCheckoutPrivate)) + +typedef struct _OstreeCheckoutPrivate OstreeCheckoutPrivate; + +struct _OstreeCheckoutPrivate { + OstreeRepo *repo; + char *path; +}; + +static void +ostree_checkout_finalize (GObject *object) +{ + OstreeCheckout *self = OSTREE_CHECKOUT (object); + OstreeCheckoutPrivate *priv = GET_PRIVATE (self); + + g_free (priv->path); + g_clear_object (&priv->repo); + + G_OBJECT_CLASS (ostree_checkout_parent_class)->finalize (object); +} + +static void +ostree_checkout_set_property(GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + OstreeCheckout *self = OSTREE_CHECKOUT (object); + OstreeCheckoutPrivate *priv = GET_PRIVATE (self); + + switch (prop_id) + { + case PROP_PATH: + priv->path = g_value_dup_string (value); + break; + case PROP_REPO: + priv->repo = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ostree_checkout_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + OstreeCheckout *self = OSTREE_CHECKOUT (object); + OstreeCheckoutPrivate *priv = GET_PRIVATE (self); + + switch (prop_id) + { + case PROP_PATH: + g_value_set_string (value, priv->path); + break; + case PROP_REPO: + g_value_set_object (value, priv->repo); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +ostree_checkout_constructor (GType gtype, + guint n_properties, + GObjectConstructParam *properties) +{ + GObject *object; + GObjectClass *parent_class; + OstreeCheckoutPrivate *priv; + + parent_class = G_OBJECT_CLASS (ostree_checkout_parent_class); + object = parent_class->constructor (gtype, n_properties, properties); + + priv = GET_PRIVATE (object); + + g_assert (priv->path != NULL); + + return object; +} + +static void +ostree_checkout_class_init (OstreeCheckoutClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (OstreeCheckoutPrivate)); + + object_class->constructor = ostree_checkout_constructor; + object_class->get_property = ostree_checkout_get_property; + object_class->set_property = ostree_checkout_set_property; + object_class->finalize = ostree_checkout_finalize; + + g_object_class_install_property (object_class, + PROP_PATH, + g_param_spec_string ("path", "", "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, + PROP_REPO, + g_param_spec_object ("repo", "", "", + OSTREE_TYPE_REPO, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +static void +ostree_checkout_init (OstreeCheckout *self) +{ +} + +OstreeCheckout* +ostree_checkout_new (OstreeRepo *repo, + const char *path) +{ + return g_object_new (OSTREE_TYPE_CHECKOUT, "repo", repo, "path", path, NULL); +} + +static gboolean +executable_exists_in_checkout (const char *path, + const char *executable) +{ + int i; + const char *subdirs[] = {"bin", "sbin", "usr/bin", "usr/sbin"}; + + for (i = 0; i < G_N_ELEMENTS (subdirs); i++) + { + char *possible_path = g_build_filename (path, subdirs[i], executable, NULL); + gboolean exists; + + exists = g_file_test (possible_path, G_FILE_TEST_EXISTS); + g_free (possible_path); + + if (exists) + return TRUE; + } + + return FALSE; +} + +static gboolean +run_trigger (OstreeCheckout *self, + GFile *trigger, + gboolean requires_chroot, + GError **error) +{ + OstreeCheckoutPrivate *priv = GET_PRIVATE (self); + gboolean ret = FALSE; + char *path = NULL; + char *temp_path = NULL; + char *rel_temp_path = NULL; + GFile *temp_copy = NULL; + char *basename = NULL; + GPtrArray *args = NULL; + int estatus; + + path = g_file_get_path (trigger); + basename = g_path_get_basename (path); + + args = g_ptr_array_new (); + + if (requires_chroot) + { + temp_path = g_build_filename (priv->path, basename, NULL); + rel_temp_path = g_strconcat ("./", basename, NULL); + temp_copy = ot_util_new_file_for_path (temp_path); + + if (!g_file_copy (trigger, temp_copy, 0, NULL, NULL, NULL, error)) + goto out; + + g_ptr_array_add (args, "chroot"); + g_ptr_array_add (args, "."); + g_ptr_array_add (args, rel_temp_path); + g_ptr_array_add (args, NULL); + } + else + { + g_ptr_array_add (args, path); + g_ptr_array_add (args, NULL); + } + + g_print ("Running trigger: %s\n", path); + if (!g_spawn_sync (priv->path, + (char**)args->pdata, + NULL, + G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, NULL, + &estatus, + error)) + { + g_prefix_error (error, "Failed to run trigger %s: ", basename); + goto out; + } + + ret = TRUE; + out: + if (requires_chroot && temp_path) + (void)unlink (temp_path); + + g_free (path); + g_free (basename); + g_free (temp_path); + g_free (rel_temp_path); + g_clear_object (&temp_copy); + if (args) + g_ptr_array_free (args, TRUE); + return ret; +} + +static gboolean +check_trigger (OstreeCheckout *self, + GFile *trigger, + GError **error) +{ + OstreeCheckoutPrivate *priv = GET_PRIVATE (self); + gboolean ret = FALSE; + GInputStream *instream = NULL; + GDataInputStream *datain = NULL; + GError *temp_error = NULL; + char *line; + gsize len; + gboolean requires_chroot = TRUE; + gboolean matches = FALSE; + + instream = (GInputStream*)g_file_read (trigger, NULL, error); + if (!instream) + goto out; + datain = g_data_input_stream_new (instream); + + while ((line = g_data_input_stream_read_line (datain, &len, NULL, &temp_error)) != NULL) + { + if (g_str_has_prefix (line, "# IfExecutable: ")) + { + char *executable = g_strdup (line + strlen ("# IfExecutable: ")); + g_strchomp (executable); + matches = executable_exists_in_checkout (priv->path, executable); + g_free (executable); + } + + g_free (line); + } + if (line == NULL && temp_error != NULL) + { + g_propagate_error (error, temp_error); + goto out; + } + if (matches) + { + if (!run_trigger (self, trigger, requires_chroot, error)) + goto out; + } + + ret = TRUE; + out: + g_clear_object (&instream); + g_clear_object (&datain); + return ret; +} + +gboolean +ostree_checkout_run_triggers (OstreeCheckout *self, + GError **error) +{ + gboolean ret = FALSE; + GError *temp_error = NULL; + char *triggerdir_path = NULL; + GFile *triggerdir = NULL; + GFileInfo *file_info = NULL; + GFileEnumerator *enumerator = NULL; + + triggerdir_path = g_build_filename (LIBEXECDIR, "ostree", "triggers.d", NULL); + triggerdir = ot_util_new_file_for_path (triggerdir_path); + + enumerator = g_file_enumerate_children (triggerdir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, + error); + if (!enumerator) + goto out; + + while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL) + { + const char *name; + guint32 type; + char *child_path = NULL; + GFile *child = NULL; + gboolean success; + + name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); + type = g_file_info_get_attribute_uint32 (file_info, "standard::type"); + + if (type == G_FILE_TYPE_REGULAR && g_str_has_suffix (name, ".trigger")) + { + child_path = g_build_filename (triggerdir_path, name, NULL); + child = ot_util_new_file_for_path (child_path); + + success = check_trigger (self, child, error); + } + else + success = TRUE; + + g_object_unref (file_info); + g_free (child_path); + g_clear_object (&child); + if (!success) + goto out; + } + if (file_info == NULL && temp_error != NULL) + { + g_propagate_error (error, temp_error); + goto out; + } + + ret = TRUE; + out: + g_free (triggerdir_path); + g_clear_object (&triggerdir); + g_clear_object (&enumerator); + return ret; +} diff --git a/src/libostree/ostree-checkout.h b/src/libostree/ostree-checkout.h new file mode 100644 index 00000000..042cdc8b --- /dev/null +++ b/src/libostree/ostree-checkout.h @@ -0,0 +1,60 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Colin Walters + */ + +#ifndef _OSTREE_CHECKOUT +#define _OSTREE_CHECKOUT + +#include + +G_BEGIN_DECLS + +#define OSTREE_TYPE_CHECKOUT ostree_checkout_get_type() +#define OSTREE_CHECKOUT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), OSTREE_TYPE_CHECKOUT, OstreeCheckout)) +#define OSTREE_CHECKOUT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), OSTREE_TYPE_CHECKOUT, OstreeCheckoutClass)) +#define OSTREE_IS_CHECKOUT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OSTREE_TYPE_CHECKOUT)) +#define OSTREE_IS_CHECKOUT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), OSTREE_TYPE_CHECKOUT)) +#define OSTREE_CHECKOUT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), OSTREE_TYPE_CHECKOUT, OstreeCheckoutClass)) + +typedef struct { + GObject parent; +} OstreeCheckout; + +typedef struct { + GObjectClass parent_class; +} OstreeCheckoutClass; + +GType ostree_checkout_get_type (void); + +OstreeCheckout* ostree_checkout_new (OstreeRepo *repo, + const char *path); + +gboolean ostree_checkout_run_triggers (OstreeCheckout *checkout, + GError **error); + +G_END_DECLS + +#endif /* _OSTREE_CHECKOUT */ diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c new file mode 100644 index 00000000..ca0fb1f5 --- /dev/null +++ b/src/libostree/ostree-core.c @@ -0,0 +1,856 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; 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.h" +#include "otutil.h" + +#include +#include + +gboolean +ostree_validate_checksum_string (const char *sha256, + GError **error) +{ + if (strlen (sha256) != 64) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid rev '%s'", sha256); + return FALSE; + } + return TRUE; +} + + +void +ostree_checksum_update_stat (GChecksum *checksum, guint32 uid, guint32 gid, guint32 mode) +{ + guint32 perms = (mode & ~S_IFMT); + g_checksum_update (checksum, (guint8*) &uid, 4); + g_checksum_update (checksum, (guint8*) &gid, 4); + g_checksum_update (checksum, (guint8*) &perms, 4); +} + +static char * +canonicalize_xattrs (char *xattr_string, size_t len) +{ + char *p; + GSList *xattrs = NULL; + GSList *iter; + GString *result; + + result = g_string_new (0); + + p = xattr_string; + while (p < xattr_string+len) + { + xattrs = g_slist_prepend (xattrs, p); + p += strlen (p) + 1; + } + + xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp); + for (iter = xattrs; iter; iter = iter->next) + g_string_append (result, iter->data); + + g_slist_free (xattrs); + return g_string_free (result, FALSE); +} + +static gboolean +read_xattr_name_array (const char *path, + const char *xattrs, + size_t len, + GVariantBuilder *builder, + GError **error) +{ + gboolean ret = FALSE; + const char *p; + + p = xattrs; + while (p < xattrs+len) + { + ssize_t bytes_read; + char *buf; + + bytes_read = lgetxattr (path, p, NULL, 0); + if (bytes_read < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + if (bytes_read == 0) + continue; + + buf = g_malloc (bytes_read); + if (lgetxattr (path, p, buf, bytes_read) < 0) + { + ot_util_set_error_from_errno (error, errno); + g_free (buf); + goto out; + } + + g_variant_builder_add (builder, "(@ay@ay)", + g_variant_new_bytestring (p), + g_variant_new_from_data (G_VARIANT_TYPE ("ay"), + buf, bytes_read, FALSE, g_free, buf)); + + p = p + strlen (p) + 1; + } + + ret = TRUE; + out: + return ret; +} + +GVariant * +ostree_get_xattrs_for_path (const char *path, + GError **error) +{ + GVariant *ret = NULL; + GVariantBuilder builder; + char *xattr_names = NULL; + char *xattr_names_canonical = NULL; + ssize_t bytes_read; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)")); + + bytes_read = llistxattr (path, NULL, 0); + + if (bytes_read < 0) + { + if (errno != ENOTSUP) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + else if (bytes_read > 0) + { + xattr_names = g_malloc (bytes_read); + if (llistxattr (path, xattr_names, bytes_read) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + xattr_names_canonical = canonicalize_xattrs (xattr_names, bytes_read); + + if (!read_xattr_name_array (path, xattr_names_canonical, bytes_read, &builder, error)) + goto out; + } + + ret = g_variant_builder_end (&builder); + g_variant_ref_sink (ret); + out: + if (!ret) + g_variant_builder_clear (&builder); + g_free (xattr_names); + g_free (xattr_names_canonical); + return ret; +} + +gboolean +ostree_stat_and_checksum_file (int dir_fd, const char *path, + OstreeObjectType objtype, + GChecksum **out_checksum, + struct stat *out_stbuf, + GError **error) +{ + GChecksum *content_sha256 = NULL; + GChecksum *content_and_meta_sha256 = NULL; + char *stat_string = NULL; + ssize_t bytes_read; + GVariant *xattrs = NULL; + int fd = -1; + DIR *temp_dir = NULL; + char *basename = NULL; + gboolean ret = FALSE; + char *symlink_target = NULL; + char *device_id = NULL; + struct stat stbuf; + + basename = g_path_get_basename (path); + + if (dir_fd == -1) + { + char *dirname = g_path_get_dirname (path); + temp_dir = opendir (dirname); + if (temp_dir == NULL) + { + ot_util_set_error_from_errno (error, errno); + g_free (dirname); + } + g_free (dirname); + dir_fd = dirfd (temp_dir); + } + + if (fstatat (dir_fd, basename, &stbuf, AT_SYMLINK_NOFOLLOW) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + + if (S_ISREG(stbuf.st_mode)) + { + fd = ot_util_open_file_read_at (dir_fd, basename, error); + if (fd < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + + if (objtype == OSTREE_OBJECT_TYPE_FILE) + { + xattrs = ostree_get_xattrs_for_path (path, error); + if (!xattrs) + goto out; + } + + content_sha256 = g_checksum_new (G_CHECKSUM_SHA256); + + if (S_ISREG(stbuf.st_mode)) + { + guint8 buf[8192]; + + while ((bytes_read = read (fd, buf, sizeof (buf))) > 0) + g_checksum_update (content_sha256, buf, bytes_read); + if (bytes_read < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + else if (S_ISLNK(stbuf.st_mode)) + { + symlink_target = g_malloc (PATH_MAX); + + g_assert (objtype == OSTREE_OBJECT_TYPE_FILE); + + bytes_read = readlinkat (dir_fd, basename, symlink_target, PATH_MAX); + if (bytes_read < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + g_checksum_update (content_sha256, (guint8*)symlink_target, bytes_read); + } + else if (S_ISCHR(stbuf.st_mode) || S_ISBLK(stbuf.st_mode)) + { + g_assert (objtype == OSTREE_OBJECT_TYPE_FILE); + device_id = g_strdup_printf ("%u", (guint)stbuf.st_rdev); + g_checksum_update (content_sha256, (guint8*)device_id, strlen (device_id)); + } + else if (S_ISFIFO(stbuf.st_mode)) + { + g_assert (objtype == OSTREE_OBJECT_TYPE_FILE); + } + else + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_FAILED, + "Unsupported file '%s' (must be regular, symbolic link, fifo, or character/block device)", + path); + goto out; + } + + content_and_meta_sha256 = g_checksum_copy (content_sha256); + + if (objtype == OSTREE_OBJECT_TYPE_FILE) + { + ostree_checksum_update_stat (content_and_meta_sha256, stbuf.st_uid, + stbuf.st_gid, stbuf.st_mode); + g_checksum_update (content_and_meta_sha256, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs)); + } + + *out_stbuf = stbuf; + *out_checksum = content_and_meta_sha256; + ret = TRUE; + out: + if (fd >= 0) + close (fd); + if (temp_dir != NULL) + closedir (temp_dir); + g_free (symlink_target); + g_free (basename); + g_free (stat_string); + if (xattrs) + g_variant_unref (xattrs); + if (content_sha256) + g_checksum_free (content_sha256); + return ret; +} + +gboolean +ostree_set_xattrs (const char *path, GVariant *xattrs, GCancellable *cancellable, GError **error) +{ + gboolean ret = FALSE; + int i, n; + + n = g_variant_n_children (xattrs); + for (i = 0; i < n; i++) + { + const guint8* name; + GVariant *value; + const guint8* value_data; + gsize value_len; + gboolean loop_err; + + g_variant_get_child (xattrs, i, "(^&ay@ay)", + &name, &value); + value_data = g_variant_get_fixed_array (value, &value_len, 1); + + loop_err = lsetxattr (path, (char*)name, (char*)value_data, value_len, XATTR_REPLACE) < 0; + + g_variant_unref (value); + if (loop_err) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + + ret = TRUE; + out: + return ret; +} + +gboolean +ostree_parse_metadata_file (const char *path, + OstreeSerializedVariantType *out_type, + GVariant **out_variant, + GError **error) +{ + GFile *pathf = NULL; + gboolean ret = FALSE; + GVariant *ret_variant = NULL; + GVariant *container = NULL; + guint32 ret_type; + + pathf = ot_util_new_file_for_path (path); + if (!ot_util_variant_map (pathf, G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT), + &container, error)) + goto out; + + g_variant_get (container, "(uv)", + &ret_type, &ret_variant); + ret_type = GUINT32_FROM_BE (ret_type); + if (ret_type <= 0 || ret_type > OSTREE_SERIALIZED_VARIANT_LAST) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted metadata object '%s'; invalid type %d", path, ret_type); + goto out; + } + + ret = TRUE; + *out_type = ret_type; + *out_variant = ot_util_variant_take_ref (ret_variant); + ret_variant = NULL; + out: + if (ret_variant) + g_variant_unref (ret_variant); + if (container != NULL) + g_variant_unref (container); + g_clear_object (&pathf); + return ret; +} + +char * +ostree_get_relative_object_path (const char *checksum, + OstreeObjectType type, + gboolean archive) +{ + GString *path; + const char *type_string; + + g_assert (strlen (checksum) == 64); + + path = g_string_new ("objects/"); + + g_string_append_len (path, checksum, 2); + g_string_append_c (path, '/'); + g_string_append (path, checksum + 2); + switch (type) + { + case OSTREE_OBJECT_TYPE_FILE: + if (archive) + type_string = ".packfile"; + else + type_string = ".file"; + break; + case OSTREE_OBJECT_TYPE_META: + type_string = ".meta"; + break; + default: + g_assert_not_reached (); + } + g_string_append (path, type_string); + return g_string_free (path, FALSE); +} + +gboolean +ostree_pack_object (GOutputStream *output, + GFile *file, + OstreeObjectType objtype, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + char *path = NULL; + GFileInfo *finfo = NULL; + GFileInputStream *instream = NULL; + gboolean pack_builder_initialized = FALSE; + GVariantBuilder pack_builder; + GVariant *pack_variant = NULL; + GVariant *xattrs = NULL; + gsize bytes_written; + + path = g_file_get_path (file); + + finfo = g_file_query_info (file, "standard::type,standard::size,standard::is-symlink,standard::symlink-target,unix::*", + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); + if (!finfo) + goto out; + + if (objtype == OSTREE_OBJECT_TYPE_META) + { + guint64 object_size_be = GUINT64_TO_BE ((guint64)g_file_info_get_size (finfo)); + if (!g_output_stream_write_all (output, &object_size_be, 8, &bytes_written, cancellable, error)) + goto out; + + instream = g_file_read (file, NULL, error); + if (!instream) + goto out; + + if (g_output_stream_splice (output, (GInputStream*)instream, 0, cancellable, error) < 0) + goto out; + } + else + { + guint32 uid, gid, mode; + guint32 device = 0; + guint32 metadata_size_be; + const char *target = NULL; + guint64 object_size; + + uid = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_UID); + gid = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_GID); + mode = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_MODE); + + g_variant_builder_init (&pack_builder, G_VARIANT_TYPE (OSTREE_PACK_FILE_VARIANT_FORMAT)); + pack_builder_initialized = TRUE; + g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (0)); + g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (uid)); + g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (gid)); + g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (mode)); + + xattrs = ostree_get_xattrs_for_path (path, error); + if (!xattrs) + goto out; + g_variant_builder_add (&pack_builder, "@a(ayay)", xattrs); + + if (S_ISREG (mode)) + { + object_size = (guint64)g_file_info_get_size (finfo); + } + else if (S_ISLNK (mode)) + { + target = g_file_info_get_attribute_byte_string (finfo, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET); + object_size = strlen (target); + } + else if (S_ISBLK (mode) || S_ISCHR (mode)) + { + device = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_DEVICE); + object_size = 4; + } + else if (S_ISFIFO (mode)) + { + object_size = 0; + } + else + g_assert_not_reached (); + + g_variant_builder_add (&pack_builder, "t", GUINT64_TO_BE (object_size)); + pack_variant = g_variant_builder_end (&pack_builder); + pack_builder_initialized = FALSE; + + metadata_size_be = GUINT32_TO_BE (g_variant_get_size (pack_variant)); + + if (!g_output_stream_write_all (output, &metadata_size_be, 4, + &bytes_written, cancellable, error)) + goto out; + g_assert (bytes_written == 4); + + if (!g_output_stream_write_all (output, g_variant_get_data (pack_variant), g_variant_get_size (pack_variant), + &bytes_written, cancellable, error)) + goto out; + + if (S_ISREG (mode)) + { + instream = g_file_read (file, NULL, error); + if (!instream) + goto out; + bytes_written = g_output_stream_splice (output, (GInputStream*)instream, 0, cancellable, error); + if (bytes_written < 0) + goto out; + if (bytes_written != object_size) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "File size changed unexpectedly"); + goto out; + } + } + else if (S_ISLNK (mode)) + { + if (!g_output_stream_write_all (output, target, object_size, + &bytes_written, cancellable, error)) + goto out; + } + else if (S_ISBLK (mode) || S_ISCHR (mode)) + { + guint32 device_be = GUINT32_TO_BE (device); + g_assert (object_size == 4); + if (!g_output_stream_write_all (output, &device_be, object_size, + &bytes_written, cancellable, error)) + goto out; + g_assert (bytes_written == 4); + } + else if (S_ISFIFO (mode)) + { + } + else + g_assert_not_reached (); + } + + ret = TRUE; + out: + g_free (path); + g_clear_object (&finfo); + g_clear_object (&instream); + if (xattrs) + g_variant_unref (xattrs); + if (pack_builder_initialized) + g_variant_builder_clear (&pack_builder); + if (pack_variant) + g_variant_unref (pack_variant); + return ret; +} + +static gboolean +splice_and_checksum (GOutputStream *out, + GInputStream *in, + GChecksum *checksum, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + if (checksum != NULL) + { + gsize bytes_read, bytes_written; + char buf[4096]; + do + { + if (!g_input_stream_read_all (in, buf, sizeof(buf), &bytes_read, cancellable, error)) + goto out; + if (checksum) + g_checksum_update (checksum, (guint8*)buf, bytes_read); + if (!g_output_stream_write_all (out, buf, bytes_read, &bytes_written, cancellable, error)) + goto out; + } + while (bytes_read > 0); + } + else + { + if (g_output_stream_splice (out, in, 0, cancellable, error) < 0) + goto out; + } + + ret = TRUE; + out: + return ret; +} + +static gboolean +unpack_meta (const char *path, + const char *dest_path, + GChecksum **out_checksum, + GError **error) +{ + gboolean ret = FALSE; + GFile *file = NULL; + GFile *dest_file = NULL; + GFileInputStream *in = NULL; + GChecksum *ret_checksum = NULL; + GFileOutputStream *out = NULL; + + file = ot_util_new_file_for_path (path); + dest_file = ot_util_new_file_for_path (dest_path); + + if (out_checksum) + ret_checksum = g_checksum_new (G_CHECKSUM_SHA256); + + in = g_file_read (file, NULL, error); + if (!in) + goto out; + + out = g_file_replace (dest_file, NULL, FALSE, 0, NULL, error); + if (!out) + goto out; + + if (!splice_and_checksum ((GOutputStream*)out, (GInputStream*)in, ret_checksum, NULL, error)) + goto out; + + if (!g_output_stream_close ((GOutputStream*)out, NULL, error)) + goto out; + + ret = TRUE; + if (out_checksum) + *out_checksum = ret_checksum; + ret_checksum = NULL; + out: + if (!ret) + (void) unlink (dest_path); + if (ret_checksum) + g_checksum_free (ret_checksum); + g_clear_object (&file); + g_clear_object (&dest_file); + g_clear_object (&in); + return ret; +} + +gboolean +ostree_parse_packed_file (GFile *file, + GVariant **out_metadata, + GInputStream **out_content, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + char *metadata_buf = NULL; + GVariant *ret_metadata = NULL; + GFileInputStream *in = NULL; + guint32 metadata_len; + gsize bytes_read; + + in = g_file_read (file, NULL, error); + if (!in) + goto out; + + if (!g_input_stream_read_all ((GInputStream*)in, &metadata_len, 4, &bytes_read, NULL, error)) + goto out; + if (bytes_read != 4) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted packfile; too short while reading metadata length"); + goto out; + } + + metadata_len = GUINT32_FROM_BE (metadata_len); + if (metadata_len > OSTREE_MAX_METADATA_SIZE) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted packfile; metadata length %u is larger than maximum %u", + metadata_len, OSTREE_MAX_METADATA_SIZE); + goto out; + } + metadata_buf = g_malloc (metadata_len); + + if (!g_input_stream_read_all ((GInputStream*)in, metadata_buf, metadata_len, &bytes_read, NULL, error)) + goto out; + if (bytes_read != metadata_len) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted packfile; too short while reading metadata"); + goto out; + } + + ret_metadata = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_PACK_FILE_VARIANT_FORMAT), + metadata_buf, metadata_len, FALSE, + (GDestroyNotify)g_free, + metadata_buf); + metadata_buf = NULL; + + ret = TRUE; + *out_metadata = ret_metadata; + ret_metadata = NULL; + *out_content = (GInputStream*)in; + in = NULL; + out: + g_clear_object (&in); + if (ret_metadata) + g_variant_unref (ret_metadata); + return ret; +} + +static gboolean +unpack_file (const char *path, + const char *dest_path, + GChecksum **out_checksum, + GError **error) +{ + gboolean ret = FALSE; + GFile *file = NULL; + GFile *dest_file = NULL; + GVariant *metadata = NULL; + GVariant *xattrs = NULL; + GInputStream *in = NULL; + GFileOutputStream *out = NULL; + GChecksum *ret_checksum = NULL; + guint32 version, uid, gid, mode; + guint64 content_len; + gsize bytes_read; + + file = ot_util_new_file_for_path (path); + + if (!ostree_parse_packed_file (file, &metadata, &in, NULL, error)) + goto out; + + g_variant_get (metadata, "(uuuu@a(ayay)t)", + &version, &uid, &gid, &mode, + &xattrs, &content_len); + uid = GUINT32_FROM_BE (uid); + gid = GUINT32_FROM_BE (gid); + mode = GUINT32_FROM_BE (mode); + content_len = GUINT64_FROM_BE (content_len); + + dest_file = ot_util_new_file_for_path (dest_path); + + if (out_checksum) + ret_checksum = g_checksum_new (G_CHECKSUM_SHA256); + + if (S_ISREG (mode)) + { + out = g_file_replace (dest_file, NULL, FALSE, 0, NULL, error); + if (!out) + goto out; + + if (!splice_and_checksum ((GOutputStream*)out, in, ret_checksum, NULL, error)) + goto out; + + if (!g_output_stream_close ((GOutputStream*)out, NULL, error)) + goto out; + } + else if (S_ISLNK (mode)) + { + char target[PATH_MAX+1]; + + if (!g_input_stream_read_all (in, target, sizeof(target)-1, &bytes_read, NULL, error)) + goto out; + target[bytes_read] = '\0'; + if (ret_checksum) + g_checksum_update (ret_checksum, (guint8*)target, bytes_read); + if (symlink (target, dest_path) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + else if (S_ISCHR (mode) || S_ISBLK (mode)) + { + guint32 dev; + + if (!g_input_stream_read_all (in, &dev, 4, &bytes_read, NULL, error)) + goto out; + if (bytes_read != 4) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted packfile; too short while reading device id"); + goto out; + } + dev = GUINT32_FROM_BE (dev); + if (ret_checksum) + g_checksum_update (ret_checksum, (guint8*)&dev, 4); + if (mknod (dest_path, mode, dev) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + else if (S_ISFIFO (mode)) + { + if (mkfifo (dest_path, mode) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted packfile; invalid mode %u", mode); + goto out; + } + + if (!S_ISLNK (mode)) + { + if (chmod (dest_path, mode) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + + if (!ostree_set_xattrs (dest_path, xattrs, NULL, error)) + goto out; + + if (ret_checksum) + { + ostree_checksum_update_stat (ret_checksum, uid, gid, mode); + g_checksum_update (ret_checksum, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs)); + } + + ret = TRUE; + if (out_checksum) + *out_checksum = ret_checksum; + ret_checksum = NULL; + out: + if (!ret) + (void) unlink (dest_path); + if (ret_checksum) + g_checksum_free (ret_checksum); + g_clear_object (&file); + g_clear_object (&dest_file); + g_clear_object (&in); + g_clear_object (&out); + if (metadata) + g_variant_unref (metadata); + if (xattrs) + g_variant_unref (xattrs); + return ret; +} + +gboolean +ostree_unpack_object (const char *path, + OstreeObjectType objtype, + const char *dest_path, + GChecksum **out_checksum, + GError **error) +{ + if (objtype == OSTREE_OBJECT_TYPE_META) + return unpack_meta (path, dest_path, out_checksum, error); + else + return unpack_file (path, dest_path, out_checksum, error); +} + + + diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h new file mode 100644 index 00000000..d67f42f9 --- /dev/null +++ b/src/libostree/ostree-core.h @@ -0,0 +1,155 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Colin Walters + */ + +#ifndef _OSTREE_CORE +#define _OSTREE_CORE + +#include + +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,standard::is-hidden,unix::*" + +#define OSTREE_EMPTY_STRING_SHA256 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + +typedef enum { + OSTREE_OBJECT_TYPE_FILE = 1, + OSTREE_OBJECT_TYPE_META = 2, +} OstreeObjectType; + +typedef enum { + OSTREE_SERIALIZED_TREE_VARIANT = 1, + OSTREE_SERIALIZED_COMMIT_VARIANT = 2, + OSTREE_SERIALIZED_DIRMETA_VARIANT = 3, + OSTREE_SERIALIZED_XATTR_VARIANT = 4 +} OstreeSerializedVariantType; +#define OSTREE_SERIALIZED_VARIANT_LAST 4 + +#define OSTREE_SERIALIZED_VARIANT_FORMAT "(uv)" + +/* + * xattr objects: + * a(ayay) - array of (name, value) pairs, both binary data, though name is a bytestring + */ +#define OSTREE_XATTR_GVARIANT_FORMAT "a(ayay)" + +#define OSTREE_DIR_META_VERSION 0 +/* + * dirmeta objects: + * u - Version + * u - uid + * u - gid + * u - mode + * a(ayay) - xattrs + */ +#define OSTREE_DIRMETA_GVARIANT_FORMAT "(uuuua(ayay))" + +#define OSTREE_TREE_VERSION 0 +/* + * Tree objects: + * u - Version + * a{sv} - Metadata + * a(ss) - array of (filename, checksum) for files + * a(sss) - array of (dirname, tree_checksum, meta_checksum) for directories + */ +#define OSTREE_TREE_GVARIANT_FORMAT "(ua{sv}a(ss)a(sss)" + +#define OSTREE_COMMIT_VERSION 0 +/* + * Commit objects: + * u - Version + * a{sv} - Metadata + * s - parent checksum (empty string for initial) + * s - subject + * s - body + * t - Timestamp in seconds since the epoch (UTC) + * s - Root tree contents + * s - Root tree metadata + */ +#define OSTREE_COMMIT_GVARIANT_FORMAT "(ua{sv}ssstss)" + +gboolean ostree_validate_checksum_string (const char *sha256, + GError **error); + +char *ostree_get_relative_object_path (const char *checksum, + OstreeObjectType type, + gboolean archive); + +GVariant *ostree_get_xattrs_for_path (const char *path, + 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, + GVariant **out_variant, + GError **error); + +gboolean ostree_stat_and_checksum_file (int dirfd, const char *path, + OstreeObjectType type, + GChecksum **out_checksum, + struct stat *out_stbuf, + GError **error); + +/* Packed files: + * + * guint32 metadata_length [metadata gvariant] [content] + * + * metadata variant: + * u - Version + * u - uid + * u - gid + * u - mode + * a(ayay) - xattrs + * t - content length + * + * And then following the end of the variant is the content. If + * symlink, then this is the target; if device, then device ID as + * network byte order uint32. + */ +#define OSTREE_PACK_FILE_VARIANT_FORMAT "(uuuua(ayay)t)" + +gboolean ostree_pack_object (GOutputStream *output, + GFile *file, + OstreeObjectType objtype, + GCancellable *cancellable, + GError **error); + +gboolean ostree_parse_packed_file (GFile *file, + GVariant **out_metadata, + GInputStream **out_content, + GCancellable *cancellable, + GError **error); + +gboolean ostree_unpack_object (const char *path, + OstreeObjectType objtype, + const char *dest_path, + GChecksum **out_checksum, + GError **error); + +void ostree_checksum_update_stat (GChecksum *checksum, guint32 uid, guint32 gid, guint32 mode); + + +#endif /* _OSTREE_REPO */ diff --git a/src/libostree/ostree-repo-file-enumerator.c b/src/libostree/ostree-repo-file-enumerator.c new file mode 100644 index 00000000..7858bde9 --- /dev/null +++ b/src/libostree/ostree-repo-file-enumerator.c @@ -0,0 +1,143 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; 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/src/libostree/ostree-repo-file-enumerator.h b/src/libostree/ostree-repo-file-enumerator.h new file mode 100644 index 00000000..499a01be --- /dev/null +++ b/src/libostree/ostree-repo-file-enumerator.h @@ -0,0 +1,55 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; 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/src/libostree/ostree-repo-file.c b/src/libostree/ostree-repo-file.c new file mode 100644 index 00000000..a3d5e79c --- /dev/null +++ b/src/libostree/ostree-repo-file.c @@ -0,0 +1,1428 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; 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) || S_ISFIFO(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/src/libostree/ostree-repo-file.h b/src/libostree/ostree-repo-file.h new file mode 100644 index 00000000..20749655 --- /dev/null +++ b/src/libostree/ostree-repo-file.h @@ -0,0 +1,115 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; 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/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c new file mode 100644 index 00000000..2bdb56dc --- /dev/null +++ b/src/libostree/ostree-repo.c @@ -0,0 +1,1877 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Colin Walters + */ + +#define _GNU_SOURCE + +#include "config.h" + +#include "ostree.h" +#include "otutil.h" +#include "ostree-repo-file-enumerator.h" + +#include +#include + +enum { + PROP_0, + + PROP_PATH +}; + +G_DEFINE_TYPE (OstreeRepo, ostree_repo, G_TYPE_OBJECT) + +#define GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), OSTREE_TYPE_REPO, OstreeRepoPrivate)) + +typedef struct _OstreeRepoPrivate OstreeRepoPrivate; + +struct _OstreeRepoPrivate { + char *path; + GFile *repo_file; + GFile *tmp_dir; + GFile *local_heads_dir; + GFile *remote_heads_dir; + char *objects_path; + char *config_path; + + gboolean inited; + + GKeyFile *config; + gboolean archive; +}; + +static void +ostree_repo_finalize (GObject *object) +{ + OstreeRepo *self = OSTREE_REPO (object); + OstreeRepoPrivate *priv = GET_PRIVATE (self); + + g_free (priv->path); + g_clear_object (&priv->repo_file); + g_clear_object (&priv->tmp_dir); + g_clear_object (&priv->local_heads_dir); + g_clear_object (&priv->remote_heads_dir); + g_free (priv->objects_path); + g_free (priv->config_path); + if (priv->config) + g_key_file_free (priv->config); + + G_OBJECT_CLASS (ostree_repo_parent_class)->finalize (object); +} + +static void +ostree_repo_set_property(GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + OstreeRepo *self = OSTREE_REPO (object); + OstreeRepoPrivate *priv = GET_PRIVATE (self); + + switch (prop_id) + { + case PROP_PATH: + priv->path = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ostree_repo_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + OstreeRepo *self = OSTREE_REPO (object); + OstreeRepoPrivate *priv = GET_PRIVATE (self); + + switch (prop_id) + { + case PROP_PATH: + g_value_set_string (value, priv->path); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +ostree_repo_constructor (GType gtype, + guint n_properties, + GObjectConstructParam *properties) +{ + GObject *object; + GObjectClass *parent_class; + OstreeRepoPrivate *priv; + + parent_class = G_OBJECT_CLASS (ostree_repo_parent_class); + object = parent_class->constructor (gtype, n_properties, properties); + + priv = GET_PRIVATE (object); + + g_assert (priv->path != NULL); + + priv->repo_file = ot_util_new_file_for_path (priv->path); + priv->tmp_dir = g_file_resolve_relative_path (priv->repo_file, "tmp"); + priv->local_heads_dir = g_file_resolve_relative_path (priv->repo_file, "refs/heads"); + priv->remote_heads_dir = g_file_resolve_relative_path (priv->repo_file, "refs/remotes"); + + priv->objects_path = g_build_filename (priv->path, "objects", NULL); + priv->config_path = g_build_filename (priv->path, "config", NULL); + + return object; +} + +static void +ostree_repo_class_init (OstreeRepoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (OstreeRepoPrivate)); + + object_class->constructor = ostree_repo_constructor; + object_class->get_property = ostree_repo_get_property; + object_class->set_property = ostree_repo_set_property; + object_class->finalize = ostree_repo_finalize; + + g_object_class_install_property (object_class, + PROP_PATH, + g_param_spec_string ("path", + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +static void +ostree_repo_init (OstreeRepo *self) +{ +} + +OstreeRepo* +ostree_repo_new (const char *path) +{ + return g_object_new (OSTREE_TYPE_REPO, "path", path, NULL); +} + +static gboolean +parse_rev_file (OstreeRepo *self, + const char *path, + char **sha256, + GError **error) G_GNUC_UNUSED; + +static gboolean +parse_rev_file (OstreeRepo *self, + const char *path, + char **sha256, + GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + GError *temp_error = NULL; + gboolean ret = FALSE; + char *rev = NULL; + + rev = ot_util_get_file_contents_utf8 (path, &temp_error); + if (rev == NULL) + { + if (g_error_matches (temp_error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + { + g_clear_error (&temp_error); + } + else + { + g_propagate_error (error, temp_error); + goto out; + } + } + else + { + g_strchomp (rev); + } + + if (g_str_has_prefix (rev, "ref: ")) + { + GFile *ref; + char *ref_path; + char *ref_sha256; + gboolean subret; + + ref = g_file_resolve_relative_path (priv->local_heads_dir, rev + 5); + ref_path = g_file_get_path (ref); + + subret = parse_rev_file (self, ref_path, &ref_sha256, error); + g_clear_object (&ref); + g_free (ref_path); + + if (!subret) + { + g_free (ref_sha256); + goto out; + } + + g_free (rev); + rev = ref_sha256; + } + else + { + if (!ostree_validate_checksum_string (rev, error)) + goto out; + } + + *sha256 = rev; + rev = NULL; + ret = TRUE; + out: + g_free (rev); + return ret; +} + +static gboolean +resolve_rev (OstreeRepo *self, + const char *rev, + gboolean allow_noent, + char **sha256, + GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + gboolean ret = FALSE; + char *tmp = NULL; + char *tmp2 = NULL; + char *ret_rev = NULL; + GFile *child = NULL; + char *child_path = NULL; + GError *temp_error = NULL; + GVariant *commit = NULL; + + if (strlen (rev) == 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid empty rev"); + goto out; + } + else if (strlen (rev) == 64) + { + ret_rev = g_strdup (rev); + } + else if (g_str_has_suffix (rev, "^")) + { + tmp = g_strdup (rev); + tmp[strlen(tmp) - 1] = '\0'; + + if (!resolve_rev (self, tmp, allow_noent, &tmp2, error)) + goto out; + + if (!ostree_repo_load_variant_checked (self, OSTREE_SERIALIZED_COMMIT_VARIANT, tmp2, &commit, error)) + goto out; + + g_variant_get_child (commit, 2, "s", &ret_rev); + if (strlen (ret_rev) == 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Commit %s has no parent", tmp2); + goto out; + + } + } + else + { + child = g_file_get_child (priv->local_heads_dir, rev); + child_path = g_file_get_path (child); + if (!ot_util_gfile_load_contents_utf8 (child, NULL, &ret_rev, NULL, &temp_error)) + { + if (allow_noent && g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_free (ret_rev); + ret_rev = NULL; + } + else + { + g_propagate_error (error, temp_error); + g_prefix_error (error, "Couldn't open ref '%s': ", child_path); + goto out; + } + } + else + { + g_strchomp (ret_rev); + + if (!ostree_validate_checksum_string (ret_rev, error)) + goto out; + } + } + + *sha256 = ret_rev; + ret_rev = NULL; + ret = TRUE; + out: + if (commit) + g_variant_unref (commit); + g_free (tmp); + g_free (tmp2); + g_clear_object (&child); + g_free (child_path); + g_free (ret_rev); + return ret; +} + +gboolean +ostree_repo_resolve_rev (OstreeRepo *self, + const char *rev, + char **sha256, + GError **error) +{ + g_return_val_if_fail (rev != NULL, FALSE); + return resolve_rev (self, rev, FALSE, sha256, error); +} + +static gboolean +write_checksum_file (GFile *parentdir, + const char *name, + const char *sha256, + GError **error) +{ + gboolean ret = FALSE; + GFile *child = NULL; + GOutputStream *out = NULL; + gsize bytes_written; + + child = g_file_get_child (parentdir, name); + + if ((out = (GOutputStream*)g_file_replace (child, NULL, FALSE, 0, NULL, error)) == NULL) + goto out; + if (!g_output_stream_write_all (out, sha256, strlen (sha256), &bytes_written, NULL, error)) + goto out; + if (!g_output_stream_write_all (out, "\n", 1, &bytes_written, NULL, error)) + goto out; + if (!g_output_stream_close (out, NULL, error)) + goto out; + + ret = TRUE; + out: + g_clear_object (&child); + g_clear_object (&out); + return ret; +} + +/** + * ostree_repo_get_config: + * @self: + * + * Returns: (transfer none): The repository configuration; do not modify + */ +GKeyFile * +ostree_repo_get_config (OstreeRepo *self) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + + g_return_val_if_fail (priv->inited, NULL); + + return priv->config; +} + +/** + * ostree_repo_copy_config: + * @self: + * + * Returns: (transfer full): A newly-allocated copy of the repository config + */ +GKeyFile * +ostree_repo_copy_config (OstreeRepo *self) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + GKeyFile *copy; + char *data; + gsize len; + + g_return_val_if_fail (priv->inited, NULL); + + copy = g_key_file_new (); + data = g_key_file_to_data (priv->config, &len, NULL); + if (!g_key_file_load_from_data (copy, data, len, 0, NULL)) + g_assert_not_reached (); + g_free (data); + return copy; +} + +/** + * ostree_repo_write_config: + * @self: + * @new_config: Overwrite the config file with this data. Do not change later! + * @error: a #GError + * + * Save @new_config in place of this repository's config file. Note + * that @new_config should not be modified after - this function + * simply adds a reference. + */ +gboolean +ostree_repo_write_config (OstreeRepo *self, + GKeyFile *new_config, + GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + char *data = NULL; + gsize len; + gboolean ret = FALSE; + + g_return_val_if_fail (priv->inited, FALSE); + + data = g_key_file_to_data (new_config, &len, error); + if (!g_file_set_contents (priv->config_path, data, len, error)) + goto out; + + g_key_file_free (priv->config); + priv->config = g_key_file_new (); + if (!g_key_file_load_from_data (priv->config, data, len, 0, error)) + goto out; + + ret = TRUE; + out: + g_free (data); + return ret; +} + +gboolean +ostree_repo_check (OstreeRepo *self, GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + gboolean ret = FALSE; + char *version = NULL;; + GError *temp_error = NULL; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (priv->inited) + return TRUE; + + if (!g_file_test (priv->objects_path, G_FILE_TEST_IS_DIR)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Couldn't find objects directory '%s'", priv->objects_path); + goto out; + } + + priv->config = g_key_file_new (); + if (!g_key_file_load_from_file (priv->config, priv->config_path, 0, error)) + { + g_prefix_error (error, "Couldn't parse config file: "); + goto out; + } + + version = g_key_file_get_value (priv->config, "core", "repo_version", &temp_error); + if (temp_error) + { + g_propagate_error (error, temp_error); + goto out; + } + + if (strcmp (version, "0") != 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid repository version '%s'", version); + goto out; + } + + priv->archive = g_key_file_get_boolean (priv->config, "core", "archive", &temp_error); + if (temp_error) + { + if (g_error_matches (temp_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)) + { + g_clear_error (&temp_error); + } + else + { + g_propagate_error (error, temp_error); + goto out; + } + } + + priv->inited = TRUE; + + ret = TRUE; + out: + g_free (version); + return ret; +} + +const char * +ostree_repo_get_path (OstreeRepo *self) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + return priv->path; +} + +gboolean +ostree_repo_is_archive (OstreeRepo *self) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + + g_return_val_if_fail (priv->inited, FALSE); + + return priv->archive; +} + +static gboolean +write_gvariant_to_tmp (OstreeRepo *self, + OstreeSerializedVariantType type, + GVariant *variant, + GChecksum **out_checksum, + GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + GVariant *serialized = NULL; + gboolean ret = FALSE; + gsize bytes_written; + char *tmp_name = NULL; + char *dest_name = NULL; + int fd = -1; + GUnixOutputStream *stream = NULL; + GChecksum *checksum = NULL; + + serialized = g_variant_new ("(uv)", GUINT32_TO_BE ((guint32)type), variant); + + tmp_name = g_build_filename (ot_gfile_get_path_cached (priv->tmp_dir), "variant-tmp-XXXXXX", NULL); + fd = g_mkstemp (tmp_name); + if (fd < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + + checksum = g_checksum_new (G_CHECKSUM_SHA256); + + stream = (GUnixOutputStream*)g_unix_output_stream_new (fd, FALSE); + if (!g_output_stream_write_all ((GOutputStream*)stream, + g_variant_get_data (serialized), + g_variant_get_size (serialized), + &bytes_written, + NULL, + error)) + goto out; + + g_checksum_update (checksum, (guint8*)g_variant_get_data (serialized), g_variant_get_size (serialized)); + + if (!g_output_stream_close ((GOutputStream*)stream, + NULL, error)) + goto out; + + dest_name = g_build_filename (ot_gfile_get_path_cached (priv->tmp_dir), g_checksum_get_string (checksum), NULL); + if (rename (tmp_name, dest_name) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + + ret = TRUE; + *out_checksum = checksum; + checksum = NULL; + out: + /* Unconditionally unlink; if we suceeded, there's a new link, if not, clean up. */ + (void) unlink (tmp_name); + if (fd != -1) + close (fd); + if (checksum) + g_checksum_free (checksum); + if (serialized != NULL) + g_variant_unref (serialized); + g_free (tmp_name); + g_free (dest_name); + g_clear_object (&stream); + return ret; +} + +static gboolean +import_gvariant_object (OstreeRepo *self, + OstreeSerializedVariantType type, + GVariant *variant, + GChecksum **out_checksum, + GError **error) +{ + gboolean ret = FALSE; + OstreeRepoPrivate *priv = GET_PRIVATE (self); + char *tmp_name = NULL; + GChecksum *ret_checksum = NULL; + gboolean did_exist; + + if (!write_gvariant_to_tmp (self, type, variant, &ret_checksum, error)) + goto out; + + tmp_name = g_build_filename (ot_gfile_get_path_cached (priv->tmp_dir), + g_checksum_get_string (ret_checksum), NULL); + + if (!ostree_repo_store_object_trusted (self, tmp_name, + g_checksum_get_string (ret_checksum), + OSTREE_OBJECT_TYPE_META, + TRUE, FALSE, &did_exist, error)) + goto out; + + ret = TRUE; + *out_checksum = ret_checksum; + ret_checksum = NULL; + out: + (void) unlink (tmp_name); + g_free (tmp_name); + if (ret_checksum) + g_checksum_free (ret_checksum); + return ret; +} + +gboolean +ostree_repo_load_variant_checked (OstreeRepo *self, + OstreeSerializedVariantType expected_type, + const char *sha256, + GVariant **out_variant, + GError **error) +{ + gboolean ret = FALSE; + OstreeSerializedVariantType type; + GVariant *ret_variant = NULL; + + if (!ostree_repo_load_variant (self, sha256, &type, &ret_variant, error)) + goto out; + + if (type != expected_type) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted metadata object '%s'; found type %u, expected %u", sha256, + type, (guint32)expected_type); + goto out; + } + + ret = TRUE; + *out_variant = ret_variant; + ret_variant = NULL; + out: + if (ret_variant) + g_variant_unref (ret_variant); + return ret; +} + +static gboolean +import_directory_meta (OstreeRepo *self, + const char *path, + GVariant **out_variant, + GChecksum **out_checksum, + GError **error) +{ + gboolean ret = FALSE; + struct stat stbuf; + GChecksum *ret_checksum = NULL; + GVariant *dirmeta = NULL; + GVariant *xattrs = NULL; + + if (lstat (path, &stbuf) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + + if (!S_ISDIR(stbuf.st_mode)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Not a directory: '%s'", path); + goto out; + } + + xattrs = ostree_get_xattrs_for_path (path, error); + if (!xattrs) + goto out; + + dirmeta = g_variant_new ("(uuuu@a(ayay))", + OSTREE_DIR_META_VERSION, + GUINT32_TO_BE ((guint32)stbuf.st_uid), + GUINT32_TO_BE ((guint32)stbuf.st_gid), + GUINT32_TO_BE ((guint32)stbuf.st_mode), + xattrs); + g_variant_ref_sink (dirmeta); + + if (!import_gvariant_object (self, OSTREE_SERIALIZED_DIRMETA_VARIANT, + dirmeta, &ret_checksum, error)) + goto out; + + ret = TRUE; + out: + if (!ret) + { + if (ret_checksum) + g_checksum_free (ret_checksum); + if (dirmeta != NULL) + g_variant_unref (dirmeta); + } + else + { + *out_checksum = ret_checksum; + *out_variant = dirmeta; + } + if (xattrs) + g_variant_unref (xattrs); + return ret; +} + +char * +ostree_repo_get_object_path (OstreeRepo *self, + const char *checksum, + OstreeObjectType type) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + char *ret; + char *relpath; + + relpath = ostree_get_relative_object_path (checksum, type, priv->archive); + ret = g_build_filename (priv->path, relpath, NULL); + g_free (relpath); + + return ret; +} + +static char * +prepare_dir_for_checksum_get_object_path (OstreeRepo *self, + const char *checksum, + OstreeObjectType type, + GError **error) +{ + char *checksum_dir = NULL; + char *object_path = NULL; + + object_path = ostree_repo_get_object_path (self, checksum, type); + checksum_dir = g_path_get_dirname (object_path); + + if (!ot_util_ensure_directory (checksum_dir, FALSE, error)) + goto out; + + out: + g_free (checksum_dir); + return object_path; +} + +static gboolean +link_object_trusted (OstreeRepo *self, + const char *path, + const char *checksum, + OstreeObjectType objtype, + gboolean ignore_exists, + gboolean force, + gboolean *did_exist, + GError **error) +{ + char *src_basename = NULL; + char *src_dirname = NULL; + char *dest_basename = NULL; + char *tmp_dest_basename = NULL; + char *dest_dirname = NULL; + DIR *src_dir = NULL; + DIR *dest_dir = NULL; + gboolean ret = FALSE; + char *dest_path = NULL; + + src_basename = g_path_get_basename (path); + src_dirname = g_path_get_dirname (path); + + src_dir = opendir (src_dirname); + if (src_dir == NULL) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + + dest_path = prepare_dir_for_checksum_get_object_path (self, checksum, objtype, error); + if (!dest_path) + goto out; + + dest_basename = g_path_get_basename (dest_path); + dest_dirname = g_path_get_dirname (dest_path); + dest_dir = opendir (dest_dirname); + if (dest_dir == NULL) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + + if (force) + { + tmp_dest_basename = g_strconcat (dest_basename, ".tmp", NULL); + (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0); + } + else + tmp_dest_basename = g_strdup (dest_basename); + + if (linkat (dirfd (src_dir), src_basename, dirfd (dest_dir), tmp_dest_basename, 0) < 0) + { + if (errno != EEXIST || !ignore_exists) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + else + *did_exist = TRUE; + } + else + *did_exist = FALSE; + + if (force) + { + if (renameat (dirfd (dest_dir), tmp_dest_basename, + dirfd (dest_dir), dest_basename) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0); + } + + ret = TRUE; + out: + if (src_dir != NULL) + closedir (src_dir); + if (dest_dir != NULL) + closedir (dest_dir); + g_free (src_basename); + g_free (src_dirname); + g_free (dest_basename); + g_free (tmp_dest_basename); + g_free (dest_dirname); + g_free (dest_path); + return ret; +} + +static gboolean +archive_file_trusted (OstreeRepo *self, + const char *path, + const char *checksum, + OstreeObjectType objtype, + gboolean ignore_exists, + gboolean force, + gboolean *did_exist, + GError **error) +{ + GFile *infile = NULL; + GFile *outfile = NULL; + GFileOutputStream *out = NULL; + gboolean ret = FALSE; + char *dest_path = NULL; + char *dest_tmp_path = NULL; + + infile = ot_util_new_file_for_path (path); + + dest_path = prepare_dir_for_checksum_get_object_path (self, checksum, objtype, error); + if (!dest_path) + goto out; + + dest_tmp_path = g_strconcat (dest_path, ".tmp", NULL); + + outfile = ot_util_new_file_for_path (dest_tmp_path); + out = g_file_replace (outfile, NULL, FALSE, 0, NULL, error); + if (!out) + goto out; + + if (!ostree_pack_object ((GOutputStream*)out, infile, objtype, NULL, error)) + goto out; + + if (!g_output_stream_close ((GOutputStream*)out, NULL, error)) + goto out; + + if (rename (dest_tmp_path, dest_path) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + + ret = TRUE; + out: + g_free (dest_path); + g_free (dest_tmp_path); + g_clear_object (&infile); + g_clear_object (&outfile); + g_clear_object (&out); + return ret; +} + +gboolean +ostree_repo_store_object_trusted (OstreeRepo *self, + const char *path, + const char *checksum, + OstreeObjectType objtype, + gboolean ignore_exists, + gboolean force, + gboolean *did_exist, + GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + if (priv->archive && objtype == OSTREE_OBJECT_TYPE_FILE) + return archive_file_trusted (self, path, checksum, objtype, ignore_exists, force, did_exist, error); + else + return link_object_trusted (self, path, checksum, objtype, ignore_exists, force, did_exist, error); +} + +gboolean +ostree_repo_store_packfile (OstreeRepo *self, + const char *expected_checksum, + const char *path, + OstreeObjectType objtype, + GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + gboolean ret = FALSE; + GString *tempfile_path = NULL; + GChecksum *checksum = NULL; + gboolean did_exist; + + tempfile_path = g_string_new (priv->path); + g_string_append_printf (tempfile_path, "/tmp-unpack-%s", expected_checksum); + + if (!ostree_unpack_object (path, objtype, tempfile_path->str, &checksum, error)) + goto out; + + if (strcmp (g_checksum_get_string (checksum), expected_checksum) != 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted object %s (actual checksum is %s)", + expected_checksum, g_checksum_get_string (checksum)); + goto out; + } + + if (!ostree_repo_store_object_trusted (self, tempfile_path ? tempfile_path->str : path, + expected_checksum, + objtype, + TRUE, FALSE, &did_exist, error)) + goto out; + + ret = TRUE; + out: + if (tempfile_path) + { + (void) unlink (tempfile_path->str); + g_string_free (tempfile_path, TRUE); + } + if (checksum) + g_checksum_free (checksum); + return ret; +} + +typedef struct _ParsedTreeData ParsedTreeData; +typedef struct _ParsedDirectoryData ParsedDirectoryData; + +static void parsed_tree_data_free (ParsedTreeData *pdata); + +struct _ParsedDirectoryData { + ParsedTreeData *tree_data; + char *metadata_sha256; + GVariant *meta_data; +}; + +static void +parsed_directory_data_free (ParsedDirectoryData *pdata) +{ + if (pdata == NULL) + return; + parsed_tree_data_free (pdata->tree_data); + g_free (pdata->metadata_sha256); + g_variant_unref (pdata->meta_data); + g_free (pdata); +} + +struct _ParsedTreeData { + GHashTable *files; /* char* filename -> char* checksum */ + GHashTable *directories; /* char* dirname -> ParsedDirectoryData* */ +}; + +static ParsedTreeData * +parsed_tree_data_new (void) +{ + ParsedTreeData *ret = g_new0 (ParsedTreeData, 1); + ret->files = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify)g_free, + (GDestroyNotify)g_free); + ret->directories = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify)g_free, + (GDestroyNotify)parsed_directory_data_free); + return ret; +} + +static void +parsed_tree_data_free (ParsedTreeData *pdata) +{ + if (pdata == NULL) + return; + g_hash_table_destroy (pdata->files); + g_hash_table_destroy (pdata->directories); + g_free (pdata); +} + +static GVariant * +create_empty_gvariant_dict (void) +{ + GVariantBuilder builder; + g_variant_builder_init (&builder, G_VARIANT_TYPE("a{sv}")); + return g_variant_builder_end (&builder); +} + +static gboolean +import_parsed_tree (OstreeRepo *self, + ParsedTreeData *tree, + GChecksum **out_checksum, + GError **error) +{ + gboolean ret = FALSE; + GVariant *serialized_tree = NULL; + gboolean builders_initialized = FALSE; + GVariantBuilder files_builder; + GVariantBuilder dirs_builder; + GHashTableIter hash_iter; + GSList *sorted_filenames = NULL; + GSList *iter; + gpointer key, value; + + g_variant_builder_init (&files_builder, G_VARIANT_TYPE ("a(ss)")); + g_variant_builder_init (&dirs_builder, G_VARIANT_TYPE ("a(sss)")); + builders_initialized = TRUE; + + g_hash_table_iter_init (&hash_iter, tree->files); + while (g_hash_table_iter_next (&hash_iter, &key, &value)) + { + const char *name = key; + sorted_filenames = g_slist_prepend (sorted_filenames, (char*)name); + } + + sorted_filenames = g_slist_sort (sorted_filenames, (GCompareFunc)strcmp); + + for (iter = sorted_filenames; iter; iter = iter->next) + { + const char *name = iter->data; + const char *value; + + value = g_hash_table_lookup (tree->files, name); + g_variant_builder_add (&files_builder, "(ss)", name, value); + } + + g_slist_free (sorted_filenames); + sorted_filenames = NULL; + + g_hash_table_iter_init (&hash_iter, tree->directories); + while (g_hash_table_iter_next (&hash_iter, &key, &value)) + { + const char *name = key; + sorted_filenames = g_slist_prepend (sorted_filenames, (char*)name); + } + + sorted_filenames = g_slist_sort (sorted_filenames, (GCompareFunc)strcmp); + + for (iter = sorted_filenames; iter; iter = iter->next) + { + const char *name = iter->data; + GChecksum *dir_checksum = NULL; + ParsedDirectoryData *dir; + + dir = g_hash_table_lookup (tree->directories, name); + + if (!import_parsed_tree (self, dir->tree_data, &dir_checksum, error)) + goto out; + + g_variant_builder_add (&dirs_builder, "(sss)", + name, g_checksum_get_string (dir_checksum), dir->metadata_sha256); + g_checksum_free (dir_checksum); + } + + g_slist_free (sorted_filenames); + sorted_filenames = NULL; + + serialized_tree = g_variant_new ("(u@a{sv}@a(ss)@a(sss))", + GUINT32_TO_BE (0), + create_empty_gvariant_dict (), + g_variant_builder_end (&files_builder), + g_variant_builder_end (&dirs_builder)); + builders_initialized = FALSE; + g_variant_ref_sink (serialized_tree); + if (!import_gvariant_object (self, OSTREE_SERIALIZED_TREE_VARIANT, serialized_tree, out_checksum, error)) + goto out; + + ret = TRUE; + out: + g_slist_free (sorted_filenames); + if (builders_initialized) + { + g_variant_builder_clear (&files_builder); + g_variant_builder_clear (&dirs_builder); + } + if (serialized_tree) + g_variant_unref (serialized_tree); + return ret; +} + +static gboolean +check_path (const char *filename, + GError **error) +{ + gboolean ret = FALSE; + + if (!*filename) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid empty filename"); + goto out; + } + + if (strcmp (filename, ".") == 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Self-reference '.' in filename '%s' not allowed (yet)", filename); + goto out; + } + + if (ot_util_filename_has_dotdot (filename)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Path uplink '..' in filename '%s' not allowed (yet)", filename); + goto out; + } + + if (g_path_is_absolute (filename)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Absolute filename '%s' not allowed (yet)", filename); + goto out; + } + + ret = TRUE; + out: + return ret; +} + +static gboolean +add_one_directory_to_tree_and_import (OstreeRepo *self, + const char *basename, + const char *abspath, + ParsedTreeData *tree, + ParsedDirectoryData **dir, /*inout*/ + GError **error) +{ + gboolean ret = FALSE; + GVariant *dirmeta = NULL; + GChecksum *dir_meta_checksum = NULL; + ParsedDirectoryData *dir_value = *dir; + + g_assert (tree != NULL); + + if (!import_directory_meta (self, abspath, &dirmeta, &dir_meta_checksum, error)) + goto out; + + if (dir_value) + { + g_variant_unref (dir_value->meta_data); + dir_value->meta_data = dirmeta; + } + else + { + dir_value = g_new0 (ParsedDirectoryData, 1); + dir_value->tree_data = parsed_tree_data_new (); + dir_value->metadata_sha256 = g_strdup (g_checksum_get_string (dir_meta_checksum)); + dir_value->meta_data = dirmeta; + g_hash_table_insert (tree->directories, g_strdup (basename), dir_value); + } + + ret = TRUE; + *dir = dir_value; + out: + if (dir_meta_checksum) + g_checksum_free (dir_meta_checksum); + return ret; +} + +static gboolean +add_one_file_to_tree_and_import (OstreeRepo *self, + const char *basename, + const char *abspath, + ParsedTreeData *tree, + GError **error) +{ + gboolean ret = FALSE; + GChecksum *checksum = NULL; + struct stat stbuf; + gboolean did_exist; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_assert (tree != NULL); + + if (!ostree_stat_and_checksum_file (-1, abspath, OSTREE_OBJECT_TYPE_FILE, &checksum, &stbuf, error)) + goto out; + + if (!ostree_repo_store_object_trusted (self, abspath, g_checksum_get_string (checksum), + OSTREE_OBJECT_TYPE_FILE, TRUE, FALSE, &did_exist, error)) + goto out; + + g_hash_table_replace (tree->files, g_strdup (basename), + g_strdup (g_checksum_get_string (checksum))); + + ret = TRUE; + out: + if (checksum) + g_checksum_free (checksum); + return ret; +} + +static gboolean +add_one_path_to_tree_and_import (OstreeRepo *self, + const char *base, + const char *filename, + ParsedTreeData *tree, + GError **error) +{ + gboolean ret = FALSE; + GPtrArray *components = NULL; + struct stat stbuf; + char *component_abspath = NULL; + ParsedTreeData *current_tree = tree; + const char *component = NULL; + const char *file_sha1; + char *abspath = NULL; + ParsedDirectoryData *dir; + int i; + gboolean is_directory; + + if (!check_path (filename, error)) + goto out; + + abspath = g_build_filename (base, filename, NULL); + + if (lstat (abspath, &stbuf) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + is_directory = S_ISDIR(stbuf.st_mode); + + if (components) + g_ptr_array_free (components, TRUE); + components = ot_util_path_split (filename); + g_assert (components->len > 0); + + current_tree = tree; + for (i = 0; i < components->len; i++) + { + component = components->pdata[i]; + g_free (component_abspath); + component_abspath = ot_util_path_join_n (base, components, i); + file_sha1 = g_hash_table_lookup (current_tree->files, component); + dir = g_hash_table_lookup (current_tree->directories, component); + + g_assert_cmpstr (component, !=, "."); + + if (i < components->len - 1) + { + if (file_sha1 != NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Encountered non-directory '%s' in '%s'", + component, + filename); + goto out; + } + /* Implicitly add intermediate directories */ + if (!add_one_directory_to_tree_and_import (self, component, + component_abspath, current_tree, &dir, + error)) + goto out; + g_assert (dir != NULL); + current_tree = dir->tree_data; + } + else if (is_directory) + { + if (file_sha1 != NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "File '%s' can't be overwritten by directory", + filename); + goto out; + } + if (!add_one_directory_to_tree_and_import (self, component, + abspath, current_tree, &dir, + error)) + goto out; + } + else + { + g_assert (!is_directory); + if (dir != NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "File '%s' can't be overwritten by directory", + filename); + goto out; + } + if (!add_one_file_to_tree_and_import (self, component, abspath, + current_tree, error)) + goto out; + } + } + + ret = TRUE; + out: + if (components) + g_ptr_array_unref (components); + g_free (component_abspath); + g_free (abspath); + return ret; +} + +gboolean +ostree_repo_write_ref (OstreeRepo *self, + gboolean is_local, + const char *name, + const char *rev, + GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + return write_checksum_file (is_local ? priv->local_heads_dir : priv->remote_heads_dir, + name, rev, error); +} + +static gboolean +commit_parsed_tree (OstreeRepo *self, + const char *branch, + const char *parent, + const char *subject, + const char *body, + GVariant *metadata, + ParsedDirectoryData *root, + GChecksum **out_commit, + GError **error) +{ + gboolean ret = FALSE; + GChecksum *root_checksum = NULL; + GChecksum *ret_commit = NULL; + GVariant *commit = NULL; + GDateTime *now = NULL; + + g_assert (branch != NULL); + g_assert (subject != NULL); + + if (!import_parsed_tree (self, root->tree_data, &root_checksum, error)) + goto out; + + now = g_date_time_new_now_utc (); + commit = g_variant_new ("(u@a{sv}ssstss)", + GUINT32_TO_BE (OSTREE_COMMIT_VERSION), + metadata ? metadata : create_empty_gvariant_dict (), + parent ? parent : "", + subject, body ? body : "", + GUINT64_TO_BE (g_date_time_to_unix (now)), + g_checksum_get_string (root_checksum), + root->metadata_sha256); + g_variant_ref_sink (commit); + if (!import_gvariant_object (self, OSTREE_SERIALIZED_COMMIT_VARIANT, + commit, &ret_commit, error)) + goto out; + + if (!ostree_repo_write_ref (self, TRUE, branch, g_checksum_get_string (ret_commit), error)) + goto out; + + ret = TRUE; + *out_commit = ret_commit; + out: + if (root_checksum) + g_checksum_free (root_checksum); + if (commit) + g_variant_unref (commit); + if (now) + g_date_time_unref (now); + return ret; +} + +static gboolean +import_root (OstreeRepo *self, + const char *base, + ParsedDirectoryData **out_root, + GError **error) +{ + gboolean ret = FALSE; + ParsedDirectoryData *ret_root = NULL; + GVariant *root_metadata = NULL; + GChecksum *root_meta_checksum = NULL; + + if (!import_directory_meta (self, base, &root_metadata, &root_meta_checksum, error)) + goto out; + + ret_root = g_new0 (ParsedDirectoryData, 1); + ret_root->tree_data = parsed_tree_data_new (); + ret_root->meta_data = root_metadata; + root_metadata = NULL; + ret_root->metadata_sha256 = g_strdup (g_checksum_get_string (root_meta_checksum)); + + ret = TRUE; + *out_root = ret_root; + ret_root = NULL; + out: + if (root_metadata) + g_variant_unref (root_metadata); + if (root_meta_checksum) + g_checksum_free (root_meta_checksum); + parsed_directory_data_free (ret_root); + return ret; +} + +gboolean +ostree_repo_commit_from_filelist_fd (OstreeRepo *self, + const char *branch, + const char *parent, + const char *subject, + const char *body, + GVariant *metadata, + const char *base, + int fd, + char separator, + GChecksum **out_commit, + GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + gboolean ret = FALSE; + ParsedDirectoryData *root = NULL; + GChecksum *ret_commit_checksum = NULL; + GUnixInputStream *in = NULL; + GDataInputStream *datain = NULL; + char *filename = NULL; + gsize filename_len; + GError *temp_error = NULL; + GVariant *root_metadata = NULL; + GChecksum *root_meta_checksum = NULL; + char *current_head = NULL; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (priv->inited, FALSE); + g_return_val_if_fail (branch != NULL, FALSE); + g_return_val_if_fail (subject != NULL, FALSE); + g_return_val_if_fail (metadata == NULL || g_variant_is_of_type (metadata, G_VARIANT_TYPE ("a{sv}")), FALSE); + + if (parent == NULL) + parent = branch; + + /* We're overwriting the tree */ + if (!import_root (self, base, &root, error)) + goto out; + + if (!resolve_rev (self, parent, TRUE, ¤t_head, error)) + goto out; + + in = (GUnixInputStream*)g_unix_input_stream_new (fd, FALSE); + datain = g_data_input_stream_new ((GInputStream*)in); + + while ((filename = g_data_input_stream_read_upto (datain, &separator, 1, + &filename_len, NULL, &temp_error)) != NULL) + { + if (!g_data_input_stream_read_byte (datain, NULL, &temp_error)) + { + if (temp_error != NULL) + { + g_propagate_prefixed_error (error, temp_error, "%s", "While reading filelist: "); + goto out; + } + } + if (!add_one_path_to_tree_and_import (self, base, filename, root->tree_data, error)) + goto out; + g_free (filename); + filename = NULL; + } + if (filename == NULL && temp_error != NULL) + { + g_propagate_prefixed_error (error, temp_error, "%s", "While reading filelist: "); + goto out; + } + if (!commit_parsed_tree (self, branch, current_head, subject, body, metadata, + root, &ret_commit_checksum, error)) + goto out; + + ret = TRUE; + *out_commit = ret_commit_checksum; + ret_commit_checksum = NULL; + out: + if (ret_commit_checksum) + g_checksum_free (ret_commit_checksum); + g_free (current_head); + if (root_metadata) + g_variant_unref (root_metadata); + if (root_meta_checksum) + g_checksum_free (root_meta_checksum); + g_clear_object (&datain); + g_clear_object (&in); + g_free (filename); + parsed_directory_data_free (root); + return ret; + +} + +static gboolean +iter_object_dir (OstreeRepo *self, + GFile *dir, + OstreeRepoObjectIter callback, + gpointer user_data, + GError **error) +{ + gboolean ret = FALSE; + GError *temp_error = NULL; + GFileEnumerator *enumerator = NULL; + GFileInfo *file_info = NULL; + char *dirpath = NULL; + + dirpath = g_file_get_path (dir); + + enumerator = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, + error); + if (!enumerator) + goto out; + + while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL) + { + const char *name; + guint32 type; + name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); + type = g_file_info_get_attribute_uint32 (file_info, "standard::type"); + + if (type != G_FILE_TYPE_DIRECTORY + && (g_str_has_suffix (name, ".meta") + || g_str_has_suffix (name, ".file") + || g_str_has_suffix (name, ".packfile"))) + { + char *dot; + char *path; + + dot = strrchr (name, '.'); + g_assert (dot); + + if ((dot - name) == 62) + { + path = g_build_filename (dirpath, name, NULL); + callback (self, path, file_info, user_data); + g_free (path); + } + } + + g_object_unref (file_info); + } + if (file_info == NULL && temp_error != NULL) + { + g_propagate_error (error, temp_error); + goto out; + } + if (!g_file_enumerator_close (enumerator, NULL, error)) + goto out; + + ret = TRUE; + out: + g_free (dirpath); + return ret; +} + +gboolean +ostree_repo_iter_objects (OstreeRepo *self, + OstreeRepoObjectIter callback, + gpointer user_data, + GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + GFile *objectdir = NULL; + GFileEnumerator *enumerator = NULL; + gboolean ret = FALSE; + GFileInfo *file_info = NULL; + GError *temp_error = NULL; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (priv->inited, FALSE); + + objectdir = ot_util_new_file_for_path (priv->objects_path); + enumerator = g_file_enumerate_children (objectdir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, + error); + if (!enumerator) + goto out; + + while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL) + { + const char *name; + guint32 type; + + name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); + type = g_file_info_get_attribute_uint32 (file_info, "standard::type"); + + if (strlen (name) == 2 && type == G_FILE_TYPE_DIRECTORY) + { + GFile *objdir = g_file_get_child (objectdir, name); + if (!iter_object_dir (self, objdir, callback, user_data, error)) + { + g_object_unref (objdir); + goto out; + } + g_object_unref (objdir); + } + g_object_unref (file_info); + } + if (file_info == NULL && temp_error != NULL) + { + g_propagate_error (error, temp_error); + goto out; + } + if (!g_file_enumerator_close (enumerator, NULL, error)) + goto out; + + ret = TRUE; + out: + g_clear_object (&file_info); + g_clear_object (&enumerator); + g_clear_object (&objectdir); + return ret; +} + +gboolean +ostree_repo_load_variant (OstreeRepo *self, + const char *sha256, + OstreeSerializedVariantType *out_type, + GVariant **out_variant, + GError **error) +{ + gboolean ret = FALSE; + OstreeSerializedVariantType ret_type; + GVariant *ret_variant = NULL; + char *path = NULL; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + path = ostree_repo_get_object_path (self, sha256, OSTREE_OBJECT_TYPE_META); + if (!ostree_parse_metadata_file (path, &ret_type, &ret_variant, error)) + goto out; + + ret = TRUE; + *out_type = ret_type; + *out_variant = ret_variant; + ret_variant = NULL; + out: + if (ret_variant) + g_variant_unref (ret_variant); + g_free (path); + return ret; +} + +static gboolean +checkout_tree (OstreeRepo *self, + OstreeRepoFile *dir, + const char *destination, + GCancellable *cancellable, + GError **error); + +static gboolean +checkout_one_directory (OstreeRepo *self, + const char *destination, + const char *dirname, + OstreeRepoFile *dir, + GFileInfo *dir_info, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + char *dest_path = NULL; + GVariant *xattr_variant = NULL; + + dest_path = g_build_filename (destination, dirname, NULL); + + 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 (!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); + if (xattr_variant) + g_variant_unref (xattr_variant); + return ret; +} + +static gboolean +checkout_tree (OstreeRepo *self, + OstreeRepoFile *dir, + const char *destination, + GCancellable *cancellable, + GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + gboolean ret = FALSE; + GError *temp_error = NULL; + GFileInfo *file_info = NULL; + GFileEnumerator *dir_enum = NULL; + GFile *child = NULL; + char *object_path = NULL; + char *dest_path = NULL; + + 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 *name; + guint32 type; + + 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 (!checkout_one_directory (self, destination, name, (OstreeRepoFile*)child, file_info, cancellable, error)) + goto out; + } + else + { + const char *checksum = _ostree_repo_file_nontree_get_checksum ((OstreeRepoFile*)child); + + 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) + { + 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; +} + +gboolean +ostree_repo_checkout (OstreeRepo *self, + const char *rev, + const char *destination, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + char *resolved = NULL; + OstreeRepoFile *root = NULL; + GFileInfo *root_info = NULL; + + if (g_file_test (destination, G_FILE_TEST_EXISTS)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Destination path '%s' already exists", + destination); + goto out; + } + + if (!resolve_rev (self, rev, FALSE, &resolved, error)) + goto out; + + root = (OstreeRepoFile*)_ostree_repo_file_new_root (self, resolved); + if (!_ostree_repo_file_ensure_resolved (root, error)) + goto out; + + 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); + g_clear_object (&root); + g_clear_object (&root_info); + return ret; +} + +gboolean +ostree_repo_diff (OstreeRepo *self, + const char *ref, + GFile *target, + GPtrArray **out_modified, + GPtrArray **out_removed, + GPtrArray **out_added, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GPtrArray *ret_modified = NULL; + GPtrArray *ret_removed = NULL; + GPtrArray *ret_added = NULL; + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Not implemented yet"); + goto out; + + ret = TRUE; + out: + if (ret_modified) + g_ptr_array_free (ret_modified, TRUE); + if (ret_removed) + g_ptr_array_free (ret_removed, TRUE); + if (ret_added) + g_ptr_array_free (ret_added, TRUE); + return ret; +} diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h new file mode 100644 index 00000000..47a896e1 --- /dev/null +++ b/src/libostree/ostree-repo.h @@ -0,0 +1,162 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; 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 +#define _OSTREE_REPO + +#include "ostree-core.h" + +G_BEGIN_DECLS + +#define OSTREE_TYPE_REPO ostree_repo_get_type() +#define OSTREE_REPO(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), OSTREE_TYPE_REPO, OstreeRepo)) +#define OSTREE_REPO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), OSTREE_TYPE_REPO, OstreeRepoClass)) +#define OSTREE_IS_REPO(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OSTREE_TYPE_REPO)) +#define OSTREE_IS_REPO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), OSTREE_TYPE_REPO)) +#define OSTREE_REPO_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), OSTREE_TYPE_REPO, OstreeRepoClass)) + +typedef struct { + GObject parent; +} OstreeRepo; + +typedef struct { + GObjectClass parent_class; +} OstreeRepoClass; + +GType ostree_repo_get_type (void); + +OstreeRepo* ostree_repo_new (const char *path); + +gboolean ostree_repo_check (OstreeRepo *self, GError **error); + +const char * ostree_repo_get_path (OstreeRepo *self); + +gboolean ostree_repo_is_archive (OstreeRepo *self); + +GKeyFile * ostree_repo_get_config (OstreeRepo *self); + +GKeyFile * ostree_repo_copy_config (OstreeRepo *self); + +gboolean ostree_repo_write_config (OstreeRepo *self, + GKeyFile *new_config, + GError **error); + +char * ostree_repo_get_object_path (OstreeRepo *self, + const char *object, + OstreeObjectType type); + +gboolean ostree_repo_store_packfile (OstreeRepo *self, + const char *expected_checksum, + const char *path, + OstreeObjectType objtype, + GError **error); + +gboolean ostree_repo_store_object_trusted (OstreeRepo *self, + const char *path, + const char *checksum, + OstreeObjectType objtype, + gboolean ignore_exists, + gboolean force, + gboolean *did_exist, + GError **error); + +gboolean ostree_repo_resolve_rev (OstreeRepo *self, + const char *rev, + char **out_resolved, + GError **error); + +gboolean ostree_repo_write_ref (OstreeRepo *self, + gboolean is_local, + const char *name, + const char *rev, + GError **error); + +gboolean ostree_repo_load_variant (OstreeRepo *self, + const char *sha256, + OstreeSerializedVariantType *out_type, + GVariant **out_variant, + GError **error); + +gboolean ostree_repo_load_variant_checked (OstreeRepo *self, + OstreeSerializedVariantType expected_type, + const char *sha256, + GVariant **out_variant, + GError **error); + +gboolean ostree_repo_commit_from_filelist_fd (OstreeRepo *self, + const char *branch, + const char *parent, + const char *subject, + const char *body, + GVariant *metadata, + const char *base, + int fd, + char separator, + GChecksum **out_commit, + GError **error); + +gboolean ostree_repo_checkout (OstreeRepo *self, + const char *ref, + const char *destination, + GCancellable *cancellable, + GError **error); + +typedef struct { + guint content_differs : 1; + guint xattrs_differs : 1; + guint unused : 30; + + GFileInfo *src_info; + GFileInfo *target_info; + + char *src_file_checksum; + char *target_file_checksum; + + GVariant *src_xattrs; + GVariant *target_xattrs; +} OstreeRepoDiffItem; + +gboolean ostree_repo_diff (OstreeRepo *self, + const char *ref, + GFile *target, + GPtrArray **out_modified, /* OstreeRepoDiffItem */ + GPtrArray **out_removed, /* OstreeRepoDiffItem */ + GPtrArray **out_added, /* OstreeRepoDiffItem */ + GCancellable *cancellable, + GError **error); + +typedef void (*OstreeRepoObjectIter) (OstreeRepo *self, const char *path, + GFileInfo *fileinfo, gpointer user_data); + +gboolean ostree_repo_iter_objects (OstreeRepo *self, + OstreeRepoObjectIter callback, + gpointer user_data, + GError **error); + +G_END_DECLS + +#endif /* _OSTREE_REPO */ diff --git a/src/libostree/ostree.h b/src/libostree/ostree.h new file mode 100644 index 00000000..d683080b --- /dev/null +++ b/src/libostree/ostree.h @@ -0,0 +1,29 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Colin Walters + */ + +#ifndef __OSTREE_H__ + +#include +#include +#include + +#endif diff --git a/libotutil/ot-gio-utils.c b/src/libotutil/ot-gio-utils.c similarity index 100% rename from libotutil/ot-gio-utils.c rename to src/libotutil/ot-gio-utils.c diff --git a/libotutil/ot-gio-utils.h b/src/libotutil/ot-gio-utils.h similarity index 100% rename from libotutil/ot-gio-utils.h rename to src/libotutil/ot-gio-utils.h diff --git a/libotutil/ot-glib-compat.c b/src/libotutil/ot-glib-compat.c similarity index 100% rename from libotutil/ot-glib-compat.c rename to src/libotutil/ot-glib-compat.c diff --git a/libotutil/ot-glib-compat.h b/src/libotutil/ot-glib-compat.h similarity index 100% rename from libotutil/ot-glib-compat.h rename to src/libotutil/ot-glib-compat.h diff --git a/libotutil/ot-opt-utils.c b/src/libotutil/ot-opt-utils.c similarity index 100% rename from libotutil/ot-opt-utils.c rename to src/libotutil/ot-opt-utils.c diff --git a/libotutil/ot-opt-utils.h b/src/libotutil/ot-opt-utils.h similarity index 100% rename from libotutil/ot-opt-utils.h rename to src/libotutil/ot-opt-utils.h diff --git a/libotutil/ot-unix-utils.c b/src/libotutil/ot-unix-utils.c similarity index 100% rename from libotutil/ot-unix-utils.c rename to src/libotutil/ot-unix-utils.c diff --git a/libotutil/ot-unix-utils.h b/src/libotutil/ot-unix-utils.h similarity index 100% rename from libotutil/ot-unix-utils.h rename to src/libotutil/ot-unix-utils.h diff --git a/libotutil/ot-variant-utils.c b/src/libotutil/ot-variant-utils.c similarity index 100% rename from libotutil/ot-variant-utils.c rename to src/libotutil/ot-variant-utils.c diff --git a/libotutil/ot-variant-utils.h b/src/libotutil/ot-variant-utils.h similarity index 100% rename from libotutil/ot-variant-utils.h rename to src/libotutil/ot-variant-utils.h diff --git a/libotutil/otutil.h b/src/libotutil/otutil.h similarity index 100% rename from libotutil/otutil.h rename to src/libotutil/otutil.h diff --git a/osbuild/main.c b/src/osbuild/main.c similarity index 100% rename from osbuild/main.c rename to src/osbuild/main.c diff --git a/osbuild/ob-builtin-buildone.c b/src/osbuild/ob-builtin-buildone.c similarity index 100% rename from osbuild/ob-builtin-buildone.c rename to src/osbuild/ob-builtin-buildone.c diff --git a/osbuild/ob-builtins.h b/src/osbuild/ob-builtins.h similarity index 100% rename from osbuild/ob-builtins.h rename to src/osbuild/ob-builtins.h diff --git a/osbuild/osbuild-raw-makeinstall.c b/src/osbuild/osbuild-raw-makeinstall.c similarity index 100% rename from osbuild/osbuild-raw-makeinstall.c rename to src/osbuild/osbuild-raw-makeinstall.c diff --git a/osbuild/ostree-buildone b/src/osbuild/ostree-buildone similarity index 100% rename from osbuild/ostree-buildone rename to src/osbuild/ostree-buildone diff --git a/osbuild/ostree-buildone-make b/src/osbuild/ostree-buildone-make similarity index 100% rename from osbuild/ostree-buildone-make rename to src/osbuild/ostree-buildone-make diff --git a/osbuild/ostree-buildone-makeinstall-split-artifacts b/src/osbuild/ostree-buildone-makeinstall-split-artifacts similarity index 100% rename from osbuild/ostree-buildone-makeinstall-split-artifacts rename to src/osbuild/ostree-buildone-makeinstall-split-artifacts diff --git a/ostree/main.c b/src/ostree/main.c similarity index 100% rename from ostree/main.c rename to src/ostree/main.c diff --git a/ostree/ot-builtin-checkout.c b/src/ostree/ot-builtin-checkout.c similarity index 100% rename from ostree/ot-builtin-checkout.c rename to src/ostree/ot-builtin-checkout.c diff --git a/ostree/ot-builtin-commit.c b/src/ostree/ot-builtin-commit.c similarity index 100% rename from ostree/ot-builtin-commit.c rename to src/ostree/ot-builtin-commit.c diff --git a/ostree/ot-builtin-compose.c b/src/ostree/ot-builtin-compose.c similarity index 100% rename from ostree/ot-builtin-compose.c rename to src/ostree/ot-builtin-compose.c diff --git a/ostree/ot-builtin-diff.c b/src/ostree/ot-builtin-diff.c similarity index 100% rename from ostree/ot-builtin-diff.c rename to src/ostree/ot-builtin-diff.c diff --git a/ostree/ot-builtin-fsck.c b/src/ostree/ot-builtin-fsck.c similarity index 100% rename from ostree/ot-builtin-fsck.c rename to src/ostree/ot-builtin-fsck.c diff --git a/ostree/ot-builtin-init.c b/src/ostree/ot-builtin-init.c similarity index 100% rename from ostree/ot-builtin-init.c rename to src/ostree/ot-builtin-init.c diff --git a/ostree/ot-builtin-log.c b/src/ostree/ot-builtin-log.c similarity index 100% rename from ostree/ot-builtin-log.c rename to src/ostree/ot-builtin-log.c diff --git a/ostree/ot-builtin-pull.c b/src/ostree/ot-builtin-pull.c similarity index 100% rename from ostree/ot-builtin-pull.c rename to src/ostree/ot-builtin-pull.c diff --git a/ostree/ot-builtin-remote.c b/src/ostree/ot-builtin-remote.c similarity index 100% rename from ostree/ot-builtin-remote.c rename to src/ostree/ot-builtin-remote.c diff --git a/ostree/ot-builtin-rev-parse.c b/src/ostree/ot-builtin-rev-parse.c similarity index 100% rename from ostree/ot-builtin-rev-parse.c rename to src/ostree/ot-builtin-rev-parse.c diff --git a/ostree/ot-builtin-run-triggers.c b/src/ostree/ot-builtin-run-triggers.c similarity index 100% rename from ostree/ot-builtin-run-triggers.c rename to src/ostree/ot-builtin-run-triggers.c diff --git a/ostree/ot-builtin-show.c b/src/ostree/ot-builtin-show.c similarity index 100% rename from ostree/ot-builtin-show.c rename to src/ostree/ot-builtin-show.c diff --git a/ostree/ot-builtins.h b/src/ostree/ot-builtins.h similarity index 100% rename from ostree/ot-builtins.h rename to src/ostree/ot-builtins.h