[ostree/wip/transaction: 5/9] repo: Move commit code to another file
- From: Jasper St. Pierre <jstpierre src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [ostree/wip/transaction: 5/9] repo: Move commit code to another file
- Date: Thu, 5 Sep 2013 21:33:24 +0000 (UTC)
commit feaf380ca6a6158616c4446c96140161c047c9c0
Author: Jasper St. Pierre <jstpierre mecheye net>
Date: Thu Sep 5 15:43:07 2013 -0400
repo: Move commit code to another file
Makefile-libostree.am | 1 +
src/libostree/ostree-repo-commit.c | 1621 ++++++++++++++++++++++++++++++++
src/libostree/ostree-repo-private.h | 14 +
src/libostree/ostree-repo.c | 1763 ++---------------------------------
4 files changed, 1716 insertions(+), 1683 deletions(-)
---
diff --git a/Makefile-libostree.am b/Makefile-libostree.am
index aec27f7..af74b75 100644
--- a/Makefile-libostree.am
+++ b/Makefile-libostree.am
@@ -34,6 +34,7 @@ libostree_1_la_SOURCES = \
src/libostree/ostree-mutable-tree.c \
src/libostree/ostree-repo.c \
src/libostree/ostree-repo-checkout.c \
+ src/libostree/ostree-repo-commit.c \
src/libostree/ostree-repo-libarchive.c \
src/libostree/ostree-repo-prune.c \
src/libostree/ostree-repo-refs.c \
diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c
new file mode 100644
index 0000000..9bacfcd
--- /dev/null
+++ b/src/libostree/ostree-repo-commit.c
@@ -0,0 +1,1621 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011,2013 Colin Walters <walters verbum org>
+ *
+ * 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 <walters verbum org>
+ */
+
+#include "config.h"
+
+#include <glib-unix.h>
+#include "otutil.h"
+#include "libgsystem.h"
+
+#include "ostree-core-private.h"
+#include "ostree-repo-private.h"
+#include "ostree-repo-file-enumerator.h"
+#include "ostree-checksum-input-stream.h"
+#include "ostree-mutable-tree.h"
+
+static gboolean
+commit_loose_object_impl (OstreeRepo *self,
+ GFile *tempfile_path,
+ GFile *dest,
+ gboolean is_regular,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_object GFile *parent = NULL;
+
+ parent = g_file_get_parent (dest);
+ if (!gs_file_ensure_directory (parent, FALSE, cancellable, error))
+ goto out;
+
+ if (is_regular)
+ {
+ /* Ensure that in case of a power cut, these files have the data we
+ * want. See http://lwn.net/Articles/322823/
+ */
+ if (!gs_file_sync_data (tempfile_path, cancellable, error))
+ goto out;
+ }
+
+ if (rename (gs_file_get_path_cached (tempfile_path), gs_file_get_path_cached (dest)) < 0)
+ {
+ if (errno != EEXIST)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ g_prefix_error (error, "Storing file '%s': ",
+ gs_file_get_path_cached (dest));
+ goto out;
+ }
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+commit_loose_object_trusted (OstreeRepo *self,
+ const char *checksum,
+ OstreeObjectType objtype,
+ GFile *tempfile_path,
+ gboolean is_regular,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_object GFile *dest_file = NULL;
+
+ dest_file = _ostree_repo_get_object_path (self, checksum, objtype);
+
+ if (!commit_loose_object_impl (self, tempfile_path, dest_file, is_regular,
+ cancellable, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+/* Create a randomly-named symbolic link in @tempdir which points to
+ * @target. The filename will be returned in @out_file.
+ *
+ * The reason this odd function exists is that the repo should only
+ * contain objects in their final state. For bare repositories, we
+ * need to first create the symlink, then chown it, and apply all
+ * extended attributes, before finally rename()ing it into place.
+ */
+static gboolean
+make_temporary_symlink (GFile *tmpdir,
+ const char *target,
+ GFile **out_file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_free char *tmpname = NULL;
+ DIR *d = NULL;
+ int dfd = -1;
+ guint i;
+ const int max_attempts = 128;
+
+ d = opendir (gs_file_get_path_cached (tmpdir));
+ if (!d)
+ {
+ int errsv = errno;
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+ g_strerror (errsv));
+ goto out;
+ }
+ dfd = dirfd (d);
+
+ for (i = 0; i < max_attempts; i++)
+ {
+ g_free (tmpname);
+ tmpname = gsystem_fileutil_gen_tmp_name (NULL, NULL);
+ if (symlinkat (target, dfd, tmpname) < 0)
+ {
+ if (errno == EEXIST)
+ continue;
+ else
+ {
+ int errsv = errno;
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+ g_strerror (errsv));
+ goto out;
+ }
+ }
+ else
+ break;
+ }
+ if (i == max_attempts)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Exhausted attempts to open temporary file");
+ goto out;
+ }
+
+ ret = TRUE;
+ *out_file = g_file_get_child (tmpdir, tmpname);
+ out:
+ if (d) (void) closedir (d);
+ return ret;
+}
+
+static gboolean
+stage_object (OstreeRepo *self,
+ OstreeObjectType objtype,
+ const char *expected_checksum,
+ GInputStream *input,
+ guint64 file_object_length,
+ guchar **out_csum,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ const char *actual_checksum;
+ gboolean do_commit;
+ OstreeRepoMode repo_mode;
+ gs_unref_object GFile *temp_file = NULL;
+ gs_unref_object GFile *raw_temp_file = NULL;
+ gs_unref_object GFile *stored_path = NULL;
+ gs_free guchar *ret_csum = NULL;
+ gs_unref_object OstreeChecksumInputStream *checksum_input = NULL;
+ gs_unref_object GInputStream *file_input = NULL;
+ gs_unref_object GFileInfo *file_info = NULL;
+ gs_unref_variant GVariant *xattrs = NULL;
+ gboolean have_obj;
+ GChecksum *checksum = NULL;
+ gboolean temp_file_is_regular;
+ gboolean is_symlink = FALSE;
+
+ g_return_val_if_fail (self->in_transaction, FALSE);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ g_assert (expected_checksum || out_csum);
+
+ if (expected_checksum)
+ {
+ if (!_ostree_repo_find_object (self, objtype, expected_checksum, &stored_path,
+ cancellable, error))
+ goto out;
+ }
+
+ repo_mode = ostree_repo_get_mode (self);
+
+ if (out_csum)
+ {
+ checksum = g_checksum_new (G_CHECKSUM_SHA256);
+ if (input)
+ checksum_input = ostree_checksum_input_stream_new (input, checksum);
+ }
+
+ if (objtype == OSTREE_OBJECT_TYPE_FILE)
+ {
+ if (!ostree_content_stream_parse (FALSE, checksum_input ? (GInputStream*)checksum_input : input,
+ file_object_length, FALSE,
+ &file_input, &file_info, &xattrs,
+ cancellable, error))
+ goto out;
+
+ temp_file_is_regular = g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR;
+ is_symlink = g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK;
+
+ if (!(temp_file_is_regular || is_symlink))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Unsupported file type %u", g_file_info_get_file_type (file_info));
+ goto out;
+ }
+
+ /* For regular files, we create them with default mode, and only
+ * later apply any xattrs and setuid bits. The rationale here
+ * is that an attacker on the network with the ability to MITM
+ * could potentially cause the system to make a temporary setuid
+ * binary with trailing garbage, creating a window on the local
+ * system where a malicious setuid binary exists.
+ */
+ if (repo_mode == OSTREE_REPO_MODE_BARE && temp_file_is_regular)
+ {
+ gs_unref_object GOutputStream *temp_out = NULL;
+ if (!gs_file_open_in_tmpdir (self->tmp_dir, 0644, &temp_file, &temp_out,
+ cancellable, error))
+ goto out;
+ if (g_output_stream_splice (temp_out, file_input, G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ cancellable, error) < 0)
+ goto out;
+ }
+ else if (repo_mode == OSTREE_REPO_MODE_BARE && is_symlink)
+ {
+ if (!make_temporary_symlink (self->tmp_dir,
+ g_file_info_get_symlink_target (file_info),
+ &temp_file,
+ cancellable, error))
+ goto out;
+ }
+ else if (repo_mode == OSTREE_REPO_MODE_ARCHIVE_Z2)
+ {
+ gs_unref_variant GVariant *file_meta = NULL;
+ gs_unref_object GOutputStream *temp_out = NULL;
+ gs_unref_object GConverter *zlib_compressor = NULL;
+ gs_unref_object GOutputStream *compressed_out_stream = NULL;
+
+ if (!gs_file_open_in_tmpdir (self->tmp_dir, 0644,
+ &temp_file, &temp_out,
+ cancellable, error))
+ goto out;
+ temp_file_is_regular = TRUE;
+
+ file_meta = _ostree_zlib_file_header_new (file_info, xattrs);
+
+ if (!_ostree_write_variant_with_size (temp_out, file_meta, 0, NULL, NULL,
+ cancellable, error))
+ goto out;
+
+ if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
+ {
+ zlib_compressor = (GConverter*)g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW, 9);
+ compressed_out_stream = g_converter_output_stream_new (temp_out, zlib_compressor);
+
+ if (g_output_stream_splice (compressed_out_stream, file_input,
+ G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ cancellable, error) < 0)
+ goto out;
+ }
+
+ if (!g_output_stream_close (temp_out, cancellable, error))
+ goto out;
+ }
+ else
+ g_assert_not_reached ();
+ }
+ else
+ {
+ gs_unref_object GOutputStream *temp_out = NULL;
+ if (!gs_file_open_in_tmpdir (self->tmp_dir, 0644, &temp_file, &temp_out,
+ cancellable, error))
+ goto out;
+ if (g_output_stream_splice (temp_out, checksum_input ? (GInputStream*)checksum_input : input,
+ G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ cancellable, error) < 0)
+ goto out;
+ temp_file_is_regular = TRUE;
+ }
+
+ if (!checksum)
+ actual_checksum = expected_checksum;
+ else
+ {
+ actual_checksum = g_checksum_get_string (checksum);
+ if (expected_checksum && strcmp (actual_checksum, expected_checksum) != 0)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Corrupted %s object %s (actual checksum is %s)",
+ ostree_object_type_to_string (objtype),
+ expected_checksum, actual_checksum);
+ goto out;
+ }
+ }
+
+ if (!ostree_repo_has_object (self, objtype, actual_checksum, &have_obj,
+ cancellable, error))
+ goto out;
+
+ do_commit = !have_obj;
+
+ if (do_commit)
+ {
+ if (objtype == OSTREE_OBJECT_TYPE_FILE && repo_mode == OSTREE_REPO_MODE_BARE)
+ {
+ g_assert (file_info != NULL);
+ /* Now that we know the checksum is valid, apply uid/gid, mode bits,
+ * and extended attributes.
+ */
+ if (!gs_file_lchown (temp_file,
+ g_file_info_get_attribute_uint32 (file_info, "unix::uid"),
+ g_file_info_get_attribute_uint32 (file_info, "unix::gid"),
+ cancellable, error))
+ goto out;
+ /* symlinks are always 777, there's no lchmod(). Calling
+ * chmod() on them would apply to their target, which we
+ * definitely don't want.
+ */
+ if (!is_symlink)
+ {
+ if (!gs_file_chmod (temp_file, g_file_info_get_attribute_uint32 (file_info, "unix::mode"),
+ cancellable, error))
+ goto out;
+ }
+ if (xattrs != NULL)
+ {
+ if (!ostree_set_xattrs (temp_file, xattrs, cancellable, error))
+ goto out;
+ }
+ }
+ if (!commit_loose_object_trusted (self, actual_checksum, objtype, temp_file, temp_file_is_regular,
+ cancellable, error))
+ goto out;
+ g_clear_object (&temp_file);
+ }
+
+ g_mutex_lock (&self->txn_stats_lock);
+ if (do_commit)
+ {
+ if (OSTREE_OBJECT_TYPE_IS_META (objtype))
+ {
+ self->txn_metadata_objects_written++;
+ }
+ else
+ {
+ self->txn_content_objects_written++;
+ self->txn_content_bytes_written += file_object_length;
+ }
+ }
+ if (OSTREE_OBJECT_TYPE_IS_META (objtype))
+ self->txn_metadata_objects_total++;
+ else
+ self->txn_content_objects_total++;
+ g_mutex_unlock (&self->txn_stats_lock);
+
+ if (checksum)
+ ret_csum = ot_csum_from_gchecksum (checksum);
+
+ ret = TRUE;
+ ot_transfer_out_value(out_csum, &ret_csum);
+ out:
+ if (temp_file)
+ (void) unlink (gs_file_get_path_cached (temp_file));
+ if (raw_temp_file)
+ (void) unlink (gs_file_get_path_cached (raw_temp_file));
+ g_clear_pointer (&checksum, (GDestroyNotify) g_checksum_free);
+ return ret;
+}
+
+typedef struct {
+ dev_t dev;
+ ino_t ino;
+} OstreeDevIno;
+
+static guint
+devino_hash (gconstpointer a)
+{
+ OstreeDevIno *a_i = (gpointer)a;
+ return (guint) (a_i->dev + a_i->ino);
+}
+
+static int
+devino_equal (gconstpointer a,
+ gconstpointer b)
+{
+ OstreeDevIno *a_i = (gpointer)a;
+ OstreeDevIno *b_i = (gpointer)b;
+ return a_i->dev == b_i->dev
+ && a_i->ino == b_i->ino;
+}
+
+static gboolean
+scan_loose_devino (OstreeRepo *self,
+ GHashTable *devino_cache,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ guint i;
+ OstreeRepoMode repo_mode;
+ gs_unref_ptrarray GPtrArray *object_dirs = NULL;
+
+ if (self->parent_repo)
+ {
+ if (!scan_loose_devino (self->parent_repo, devino_cache, cancellable, error))
+ goto out;
+ }
+
+ repo_mode = ostree_repo_get_mode (self);
+
+ if (!_ostree_repo_get_loose_object_dirs (self, &object_dirs, cancellable, error))
+ goto out;
+
+ for (i = 0; i < object_dirs->len; i++)
+ {
+ GFile *objdir = object_dirs->pdata[i];
+ gs_unref_object GFileEnumerator *enumerator = NULL;
+ gs_unref_object GFileInfo *file_info = NULL;
+ const char *dirname;
+
+ enumerator = g_file_enumerate_children (objdir, OSTREE_GIO_FAST_QUERYINFO,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable,
+ error);
+ if (!enumerator)
+ goto out;
+
+ dirname = gs_file_get_basename_cached (objdir);
+
+ while (TRUE)
+ {
+ const char *name;
+ const char *dot;
+ guint32 type;
+ OstreeDevIno *key;
+ GString *checksum;
+ gboolean skip;
+
+ if (!gs_file_enumerator_iterate (enumerator, &file_info, NULL,
+ NULL, error))
+ goto out;
+ if (file_info == NULL)
+ break;
+
+ 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)
+ continue;
+
+ switch (repo_mode)
+ {
+ case OSTREE_REPO_MODE_ARCHIVE_Z2:
+ case OSTREE_REPO_MODE_BARE:
+ skip = !g_str_has_suffix (name, ".file");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ if (skip)
+ continue;
+
+ dot = strrchr (name, '.');
+ g_assert (dot);
+
+ if ((dot - name) != 62)
+ continue;
+
+ checksum = g_string_new (dirname);
+ g_string_append_len (checksum, name, 62);
+
+ key = g_new (OstreeDevIno, 1);
+ key->dev = g_file_info_get_attribute_uint32 (file_info, "unix::device");
+ key->ino = g_file_info_get_attribute_uint64 (file_info, "unix::inode");
+
+ g_hash_table_replace (devino_cache, key, g_string_free (checksum, FALSE));
+ }
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static const char *
+devino_cache_lookup (OstreeRepo *self,
+ GFileInfo *finfo)
+{
+ OstreeDevIno dev_ino;
+
+ if (!self->loose_object_devino_hash)
+ return NULL;
+
+ dev_ino.dev = g_file_info_get_attribute_uint32 (finfo, "unix::device");
+ dev_ino.ino = g_file_info_get_attribute_uint64 (finfo, "unix::inode");
+ return g_hash_table_lookup (self->loose_object_devino_hash, &dev_ino);
+}
+
+/**
+ * ostree_repo_prepare_transaction:
+ * @self: An #OstreeRepo
+ * @enable_commit_hardlink_scan:
+ * @out_transaction_resume: (allow-none) (out): Whether this transaction
+ * is resuming from a previous one.
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Starts or resumes a transaction. In order to write to a repo, you
+ * need to start a transaction. You can complete the transaction with
+ * ostree_repo_commit_transaction(), or abort the transaction with
+ * ostree_repo_abort_transaction().
+ *
+ * Currently, transactions are not atomic, and aborting a transaction
+ * will not erase any data you write during the transaction.
+ */
+gboolean
+ostree_repo_prepare_transaction (OstreeRepo *self,
+ gboolean enable_commit_hardlink_scan,
+ gboolean *out_transaction_resume,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gboolean ret_transaction_resume = FALSE;
+ gs_free char *transaction_str = NULL;
+
+ g_return_val_if_fail (self->in_transaction == FALSE, FALSE);
+
+ if (self->transaction_lock_path == NULL)
+ self->transaction_lock_path = g_file_resolve_relative_path (self->repodir, "transaction");
+
+ if (g_file_query_file_type (self->transaction_lock_path, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) ==
G_FILE_TYPE_SYMBOLIC_LINK)
+ ret_transaction_resume = TRUE;
+ else
+ ret_transaction_resume = FALSE;
+
+ self->txn_metadata_objects_total =
+ self->txn_metadata_objects_written =
+ self->txn_content_objects_total =
+ self->txn_content_objects_written =
+ self->txn_content_bytes_written = 0;
+
+ self->in_transaction = TRUE;
+ if (ret_transaction_resume)
+ {
+ if (!ot_gfile_ensure_unlinked (self->transaction_lock_path, cancellable, error))
+ goto out;
+ }
+ transaction_str = g_strdup_printf ("pid=%llu", (unsigned long long) getpid ());
+ if (!g_file_make_symbolic_link (self->transaction_lock_path, transaction_str,
+ cancellable, error))
+ goto out;
+
+ if (enable_commit_hardlink_scan)
+ {
+ if (!self->loose_object_devino_hash)
+ self->loose_object_devino_hash = g_hash_table_new_full (devino_hash, devino_equal, g_free, g_free);
+ g_hash_table_remove_all (self->loose_object_devino_hash);
+ if (!scan_loose_devino (self, self->loose_object_devino_hash, cancellable, error))
+ goto out;
+ }
+
+ ret = TRUE;
+ if (out_transaction_resume)
+ *out_transaction_resume = ret_transaction_resume;
+ out:
+ return ret;
+}
+
+static gboolean
+cleanup_tmpdir (OstreeRepo *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_object GFileEnumerator *enumerator = NULL;
+
+ enumerator = g_file_enumerate_children (self->tmp_dir, "standard::name",
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable,
+ error);
+ if (!enumerator)
+ goto out;
+
+ while (TRUE)
+ {
+ GFileInfo *file_info;
+ GFile *path;
+
+ if (!gs_file_enumerator_iterate (enumerator, &file_info, &path,
+ cancellable, error))
+ goto out;
+ if (file_info == NULL)
+ break;
+
+ if (!gs_shutil_rm_rf (path, cancellable, error))
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+gboolean
+ostree_repo_commit_transaction_with_stats (OstreeRepo *self,
+ guint *out_metadata_objects_total,
+ guint *out_metadata_objects_written,
+ guint *out_content_objects_total,
+ guint *out_content_objects_written,
+ guint64 *out_content_bytes_written,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (self->in_transaction == TRUE, FALSE);
+
+ if (!cleanup_tmpdir (self, cancellable, error))
+ goto out;
+
+ if (self->loose_object_devino_hash)
+ g_hash_table_remove_all (self->loose_object_devino_hash);
+
+ self->in_transaction = FALSE;
+
+ if (!ot_gfile_ensure_unlinked (self->transaction_lock_path, cancellable, error))
+ goto out;
+
+ if (out_metadata_objects_total) *out_metadata_objects_total = self->txn_metadata_objects_total;
+ if (out_metadata_objects_written) *out_metadata_objects_written = self->txn_metadata_objects_written;
+ if (out_content_objects_total) *out_content_objects_total = self->txn_content_objects_total;
+ if (out_content_objects_written) *out_content_objects_written = self->txn_content_objects_written;
+ if (out_content_bytes_written) *out_content_bytes_written = self->txn_content_bytes_written;
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+gboolean
+ostree_repo_commit_transaction (OstreeRepo *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return ostree_repo_commit_transaction_with_stats (self, NULL, NULL, NULL, NULL, NULL,
+ cancellable, error);
+}
+
+gboolean
+ostree_repo_abort_transaction (OstreeRepo *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (self->in_transaction == TRUE, FALSE);
+
+ if (!cleanup_tmpdir (self, cancellable, error))
+ goto out;
+
+ if (self->loose_object_devino_hash)
+ g_hash_table_remove_all (self->loose_object_devino_hash);
+
+ self->in_transaction = FALSE;
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+/**
+ * ostree_repo_stage_metadata:
+ * @self: Repo
+ * @objtype: Object type
+ * @expected_checksum: (allow-none): If provided, validate content against this checksum
+ * @object: Metadata
+ * @out_csum: (out) (array fixed-size=32) (allow-none): Binary checksum
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Store the metadata object @variant. Return the checksum
+ * as @out_csum.
+ *
+ * If @expected_checksum is not %NULL, verify it against the
+ * computed checksum.
+ */
+gboolean
+ostree_repo_stage_metadata (OstreeRepo *self,
+ OstreeObjectType objtype,
+ const char *expected_checksum,
+ GVariant *object,
+ guchar **out_csum,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gs_unref_object GInputStream *input = NULL;
+ gs_unref_variant GVariant *normalized = NULL;
+
+ normalized = g_variant_get_normal_form (object);
+ input = ot_variant_read (normalized);
+
+ return stage_object (self, objtype, expected_checksum, input, 0, out_csum,
+ cancellable, error);
+}
+
+/**
+ * ostree_repo_stage_metadata_trusted:
+ * @self: Repo
+ * @objtype: Object type
+ * @checksum: Store object with this ASCII SHA256 checksum
+ * @variant: Metadata object
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Store the metadata object @variant; the provided @checksum is
+ * trusted.
+ */
+gboolean
+ostree_repo_stage_metadata_trusted (OstreeRepo *self,
+ OstreeObjectType type,
+ const char *checksum,
+ GVariant *variant,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gs_unref_object GInputStream *input = NULL;
+ gs_unref_variant GVariant *normalized = NULL;
+
+ normalized = g_variant_get_normal_form (variant);
+ input = ot_variant_read (normalized);
+
+ return stage_object (self, type, checksum, input, 0, NULL,
+ cancellable, error);
+}
+
+typedef struct {
+ OstreeRepo *repo;
+ OstreeObjectType objtype;
+ char *expected_checksum;
+ GVariant *object;
+ GCancellable *cancellable;
+ GSimpleAsyncResult *result;
+
+ guchar *result_csum;
+} StageMetadataAsyncData;
+
+static void
+stage_metadata_async_data_free (gpointer user_data)
+{
+ StageMetadataAsyncData *data = user_data;
+
+ g_clear_object (&data->repo);
+ g_clear_object (&data->cancellable);
+ g_variant_unref (data->object);
+ g_free (data->result_csum);
+ g_free (data->expected_checksum);
+ g_free (data);
+}
+
+static void
+stage_metadata_thread (GSimpleAsyncResult *res,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ GError *error = NULL;
+ StageMetadataAsyncData *data;
+
+ data = g_simple_async_result_get_op_res_gpointer (res);
+ if (!ostree_repo_stage_metadata (data->repo, data->objtype, data->expected_checksum,
+ data->object,
+ &data->result_csum,
+ cancellable, &error))
+ g_simple_async_result_take_error (res, error);
+}
+
+/**
+ * ostree_repo_stage_metadata_async:
+ * @self: Repo
+ * @objtype: Object type
+ * @expected_checksum: (allow-none): If provided, validate content against this checksum
+ * @object: Metadata
+ * @cancellable: Cancellable
+ * @callback: Invoked when metadata is staged
+ * @user_data: Data for @callback
+ *
+ * Asynchronously store the metadata object @variant. If provided,
+ * the checksum @expected_checksum will be verified.
+ */
+void
+ostree_repo_stage_metadata_async (OstreeRepo *self,
+ OstreeObjectType objtype,
+ const char *expected_checksum,
+ GVariant *object,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ StageMetadataAsyncData *asyncdata;
+
+ asyncdata = g_new0 (StageMetadataAsyncData, 1);
+ asyncdata->repo = g_object_ref (self);
+ asyncdata->objtype = objtype;
+ asyncdata->expected_checksum = g_strdup (expected_checksum);
+ asyncdata->object = g_variant_ref (object);
+ asyncdata->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+
+ asyncdata->result = g_simple_async_result_new ((GObject*) self,
+ callback, user_data,
+ ostree_repo_stage_metadata_async);
+
+ g_simple_async_result_set_op_res_gpointer (asyncdata->result, asyncdata,
+ stage_metadata_async_data_free);
+ g_simple_async_result_run_in_thread (asyncdata->result, stage_metadata_thread, G_PRIORITY_DEFAULT,
cancellable);
+ g_object_unref (asyncdata->result);
+}
+
+gboolean
+ostree_repo_stage_metadata_finish (OstreeRepo *self,
+ GAsyncResult *result,
+ guchar **out_csum,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+ StageMetadataAsyncData *data;
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == ostree_repo_stage_metadata_async);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ data = g_simple_async_result_get_op_res_gpointer (simple);
+ /* Transfer ownership */
+ *out_csum = data->result_csum;
+ data->result_csum = NULL;
+ return TRUE;
+}
+
+gboolean
+_ostree_repo_stage_directory_meta (OstreeRepo *self,
+ GFileInfo *file_info,
+ GVariant *xattrs,
+ guchar **out_csum,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gs_unref_variant GVariant *dirmeta = NULL;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ dirmeta = ostree_create_directory_metadata (file_info, xattrs);
+
+ return ostree_repo_stage_metadata (self, OSTREE_OBJECT_TYPE_DIR_META, NULL,
+ dirmeta, out_csum, cancellable, error);
+}
+
+GFile *
+_ostree_repo_get_object_path (OstreeRepo *self,
+ const char *checksum,
+ OstreeObjectType type)
+{
+ char *relpath;
+ GFile *ret;
+ gboolean compressed;
+
+ compressed = (type == OSTREE_OBJECT_TYPE_FILE
+ && ostree_repo_get_mode (self) == OSTREE_REPO_MODE_ARCHIVE_Z2);
+ relpath = ostree_get_relative_object_path (checksum, type, compressed);
+ ret = g_file_resolve_relative_path (self->repodir, relpath);
+ g_free (relpath);
+
+ return ret;
+}
+
+GFile *
+_ostree_repo_get_uncompressed_object_cache_path (OstreeRepo *self,
+ const char *checksum)
+{
+ char *relpath;
+ GFile *ret;
+
+ relpath = ostree_get_relative_object_path (checksum, OSTREE_OBJECT_TYPE_FILE, FALSE);
+ ret = g_file_resolve_relative_path (self->uncompressed_objects_dir, relpath);
+ g_free (relpath);
+
+ return ret;
+}
+
+/**
+ * ostree_repo_stage_content_trusted:
+ * @self: Repo
+ * @checksum: Store content using this ASCII SHA256 checksum
+ * @object_input: Content stream
+ * @length: Length of @object_input
+ * @cancellable: Cancellable
+ * @error: Data for @callback
+ *
+ * Store the content object streamed as @object_input, with total
+ * length @length. The given @checksum will be treated as trusted.
+ *
+ * This function should be used when importing file objects from local
+ * disk, for example.
+ */
+gboolean
+ostree_repo_stage_content_trusted (OstreeRepo *self,
+ const char *checksum,
+ GInputStream *object_input,
+ guint64 length,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return stage_object (self, OSTREE_OBJECT_TYPE_FILE, checksum,
+ object_input, length, NULL,
+ cancellable, error);
+}
+
+/**
+ * ostree_repo_stage_content:
+ * @self: Repo
+ * @expected_checksum: (allow-none): If provided, validate content against this checksum
+ * @object_input: Content object stream
+ * @length: Length of @object_input
+ * @out_csum: (out) (array fixed-size=32) (allow-none): Binary checksum
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Store the content object streamed as @object_input,
+ * with total length @length. The actual checksum will
+ * be returned as @out_csum.
+ */
+gboolean
+ostree_repo_stage_content (OstreeRepo *self,
+ const char *expected_checksum,
+ GInputStream *object_input,
+ guint64 length,
+ guchar **out_csum,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return stage_object (self, OSTREE_OBJECT_TYPE_FILE, expected_checksum,
+ object_input, length, out_csum,
+ cancellable, error);
+}
+
+typedef struct {
+ OstreeRepo *repo;
+ char *expected_checksum;
+ GInputStream *object;
+ guint64 file_object_length;
+ GCancellable *cancellable;
+ GSimpleAsyncResult *result;
+
+ guchar *result_csum;
+} StageContentAsyncData;
+
+static void
+stage_content_async_data_free (gpointer user_data)
+{
+ StageContentAsyncData *data = user_data;
+
+ g_clear_object (&data->repo);
+ g_clear_object (&data->cancellable);
+ g_clear_object (&data->object);
+ g_free (data->result_csum);
+ g_free (data->expected_checksum);
+ g_free (data);
+}
+
+static void
+stage_content_thread (GSimpleAsyncResult *res,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ GError *error = NULL;
+ StageContentAsyncData *data;
+
+ data = g_simple_async_result_get_op_res_gpointer (res);
+ if (!ostree_repo_stage_content (data->repo, data->expected_checksum,
+ data->object, data->file_object_length,
+ &data->result_csum,
+ cancellable, &error))
+ g_simple_async_result_take_error (res, error);
+}
+
+/**
+ * ostree_repo_stage_content_async:
+ * @self: Repo
+ * @expected_checksum: (allow-none): If provided, validate content against this checksum
+ * @object: Input
+ * @length: Length of @object
+ * @cancellable: Cancellable
+ * @callback: Invoked when content is staged
+ * @user_data: User data for @callback
+ *
+ * Asynchronously store the content object @object. If provided, the
+ * checksum @expected_checksum will be verified.
+ */
+void
+ostree_repo_stage_content_async (OstreeRepo *self,
+ const char *expected_checksum,
+ GInputStream *object,
+ guint64 length,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ StageContentAsyncData *asyncdata;
+
+ asyncdata = g_new0 (StageContentAsyncData, 1);
+ asyncdata->repo = g_object_ref (self);
+ asyncdata->expected_checksum = g_strdup (expected_checksum);
+ asyncdata->object = g_object_ref (object);
+ asyncdata->file_object_length = length;
+ asyncdata->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+
+ asyncdata->result = g_simple_async_result_new ((GObject*) self,
+ callback, user_data,
+ ostree_repo_stage_content_async);
+
+ g_simple_async_result_set_op_res_gpointer (asyncdata->result, asyncdata,
+ stage_content_async_data_free);
+ g_simple_async_result_run_in_thread (asyncdata->result, stage_content_thread, G_PRIORITY_DEFAULT,
cancellable);
+ g_object_unref (asyncdata->result);
+}
+
+/**
+ * ostree_repo_stage_content_finish:
+ * @self: a #OstreeRepo
+ * @result: a #GAsyncResult
+ * @out_csum: (out) (transfer full): A binary SHA256 checksum of the content object
+ * @error: a #GError
+ *
+ * Completes an invocation of ostree_repo_stage_content_async().
+ */
+gboolean
+ostree_repo_stage_content_finish (OstreeRepo *self,
+ GAsyncResult *result,
+ guchar **out_csum,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+ StageContentAsyncData *data;
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == ostree_repo_stage_content_async);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ data = g_simple_async_result_get_op_res_gpointer (simple);
+ ot_transfer_out_value (out_csum, &data->result_csum);
+ return TRUE;
+}
+
+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);
+}
+
+/**
+ * ostree_repo_stage_commit:
+ * @self: Repo
+ * @branch: Name of ref
+ * @parent: (allow-none): ASCII SHA256 checksum for parent, or %NULL for none
+ * @subject: Subject
+ * @body: Body
+ * @root_contents_checksum: ASCII SHA256 checksum for %OSTREE_OBJECT_TYPE_DIR_TREE
+ * @root_metadata_checksum: ASCII SHA256 checksum for %OSTREE_OBJECT_TYPE_DIR_META
+ * @out_commit: (out): Resulting ASCII SHA256 checksum for commit
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Write a commit metadata object, referencing @root_contents_checksum
+ * and @root_metadata_checksum.
+ */
+gboolean
+ostree_repo_stage_commit (OstreeRepo *self,
+ const char *branch,
+ const char *parent,
+ const char *subject,
+ const char *body,
+ const char *root_contents_checksum,
+ const char *root_metadata_checksum,
+ char **out_commit,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_free char *ret_commit = NULL;
+ gs_unref_variant GVariant *commit = NULL;
+ gs_free guchar *commit_csum = NULL;
+ GDateTime *now = NULL;
+
+ g_return_val_if_fail (branch != NULL, FALSE);
+ g_return_val_if_fail (subject != NULL, FALSE);
+ g_return_val_if_fail (root_contents_checksum != NULL, FALSE);
+ g_return_val_if_fail (root_metadata_checksum != NULL, FALSE);
+
+ now = g_date_time_new_now_utc ();
+ commit = g_variant_new ("(@a{sv} ay@a(say)sst ay@ay)",
+ create_empty_gvariant_dict (),
+ parent ? ostree_checksum_to_bytes_v (parent) : ot_gvariant_new_bytearray (NULL, 0),
+ g_variant_new_array (G_VARIANT_TYPE ("(say)"), NULL, 0),
+ subject, body ? body : "",
+ GUINT64_TO_BE (g_date_time_to_unix (now)),
+ ostree_checksum_to_bytes_v (root_contents_checksum),
+ ostree_checksum_to_bytes_v (root_metadata_checksum));
+ g_variant_ref_sink (commit);
+ if (!ostree_repo_stage_metadata (self, OSTREE_OBJECT_TYPE_COMMIT, NULL,
+ commit, &commit_csum,
+ cancellable, error))
+ goto out;
+
+ ret_commit = ostree_checksum_from_bytes (commit_csum);
+
+ ret = TRUE;
+ ot_transfer_out_value(out_commit, &ret_commit);
+ out:
+ if (now)
+ g_date_time_unref (now);
+ return ret;
+}
+
+static GVariant *
+create_tree_variant_from_hashes (GHashTable *file_checksums,
+ GHashTable *dir_contents_checksums,
+ GHashTable *dir_metadata_checksums)
+{
+ GHashTableIter hash_iter;
+ gpointer key, value;
+ GVariantBuilder files_builder;
+ GVariantBuilder dirs_builder;
+ GSList *sorted_filenames = NULL;
+ GSList *iter;
+ GVariant *serialized_tree;
+
+ g_variant_builder_init (&files_builder, G_VARIANT_TYPE ("a(say)"));
+ g_variant_builder_init (&dirs_builder, G_VARIANT_TYPE ("a(sayay)"));
+
+ g_hash_table_iter_init (&hash_iter, file_checksums);
+ 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 (file_checksums, name);
+ g_variant_builder_add (&files_builder, "(s ay)", name,
+ ostree_checksum_to_bytes_v (value));
+ }
+
+ g_slist_free (sorted_filenames);
+ sorted_filenames = NULL;
+
+ g_hash_table_iter_init (&hash_iter, dir_metadata_checksums);
+ 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 *content_checksum;
+ const char *meta_checksum;
+
+ content_checksum = g_hash_table_lookup (dir_contents_checksums, name);
+ meta_checksum = g_hash_table_lookup (dir_metadata_checksums, name);
+
+ g_variant_builder_add (&dirs_builder, "(s ay@ay)",
+ name,
+ ostree_checksum_to_bytes_v (content_checksum),
+ ostree_checksum_to_bytes_v (meta_checksum));
+ }
+
+ g_slist_free (sorted_filenames);
+ sorted_filenames = NULL;
+
+ serialized_tree = g_variant_new ("(@a(say)@a(sayay))",
+ g_variant_builder_end (&files_builder),
+ g_variant_builder_end (&dirs_builder));
+ g_variant_ref_sink (serialized_tree);
+
+ return serialized_tree;
+}
+
+struct OstreeRepoCommitModifier {
+ volatile gint refcount;
+
+ OstreeRepoCommitModifierFlags flags;
+ OstreeRepoCommitFilter filter;
+ gpointer user_data;
+ GDestroyNotify destroy_notify;
+};
+
+static OstreeRepoCommitFilterResult
+apply_commit_filter (OstreeRepo *self,
+ OstreeRepoCommitModifier *modifier,
+ GPtrArray *path,
+ GFileInfo *file_info,
+ GFileInfo **out_modified_info)
+{
+ GString *path_buf;
+ guint i;
+ OstreeRepoCommitFilterResult result;
+ GFileInfo *modified_info;
+
+ if (modifier == NULL || modifier->filter == NULL)
+ {
+ *out_modified_info = g_object_ref (file_info);
+ return OSTREE_REPO_COMMIT_FILTER_ALLOW;
+ }
+
+ path_buf = g_string_new ("");
+
+ if (path->len == 0)
+ g_string_append_c (path_buf, '/');
+ else
+ {
+ for (i = 0; i < path->len; i++)
+ {
+ const char *elt = path->pdata[i];
+
+ g_string_append_c (path_buf, '/');
+ g_string_append (path_buf, elt);
+ }
+ }
+
+ modified_info = g_file_info_dup (file_info);
+ result = modifier->filter (self, path_buf->str, modified_info, modifier->user_data);
+ *out_modified_info = modified_info;
+
+ g_string_free (path_buf, TRUE);
+ return result;
+}
+
+static gboolean
+stage_directory_to_mtree_internal (OstreeRepo *self,
+ GFile *dir,
+ OstreeMutableTree *mtree,
+ OstreeRepoCommitModifier *modifier,
+ GPtrArray *path,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gboolean repo_dir_was_empty = FALSE;
+ OstreeRepoCommitFilterResult filter_result;
+ gs_unref_object OstreeRepoFile *repo_dir = NULL;
+ gs_unref_object GFileEnumerator *dir_enum = NULL;
+ gs_unref_object GFileInfo *child_info = NULL;
+
+ g_debug ("Examining: %s", gs_file_get_path_cached (dir));
+
+ /* We can only reuse checksums directly if there's no modifier */
+ if (OSTREE_IS_REPO_FILE (dir) && modifier == NULL)
+ repo_dir = (OstreeRepoFile*)g_object_ref (dir);
+
+ if (repo_dir)
+ {
+ if (!ostree_repo_file_ensure_resolved (repo_dir, error))
+ goto out;
+
+ ostree_mutable_tree_set_metadata_checksum (mtree, ostree_repo_file_get_checksum (repo_dir));
+ repo_dir_was_empty =
+ g_hash_table_size (ostree_mutable_tree_get_files (mtree)) == 0
+ && g_hash_table_size (ostree_mutable_tree_get_subdirs (mtree)) == 0;
+
+ filter_result = OSTREE_REPO_COMMIT_FILTER_ALLOW;
+ }
+ else
+ {
+ gs_unref_object GFileInfo *modified_info = NULL;
+ gs_unref_variant GVariant *xattrs = NULL;
+ gs_free guchar *child_file_csum = NULL;
+ gs_free char *tmp_checksum = NULL;
+
+ child_info = g_file_query_info (dir, OSTREE_GIO_FAST_QUERYINFO,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable, error);
+ if (!child_info)
+ goto out;
+
+ filter_result = apply_commit_filter (self, modifier, path, child_info, &modified_info);
+
+ if (filter_result == OSTREE_REPO_COMMIT_FILTER_ALLOW)
+ {
+ g_debug ("Adding: %s", gs_file_get_path_cached (dir));
+ if (!(modifier && (modifier->flags & OSTREE_REPO_COMMIT_MODIFIER_FLAGS_SKIP_XATTRS) > 0))
+ {
+ if (!ostree_get_xattrs_for_file (dir, &xattrs, cancellable, error))
+ goto out;
+ }
+
+ if (!_ostree_repo_stage_directory_meta (self, modified_info, xattrs, &child_file_csum,
+ cancellable, error))
+ goto out;
+
+ g_free (tmp_checksum);
+ tmp_checksum = ostree_checksum_from_bytes (child_file_csum);
+ ostree_mutable_tree_set_metadata_checksum (mtree, tmp_checksum);
+ }
+
+ g_clear_object (&child_info);
+ }
+
+ if (filter_result == OSTREE_REPO_COMMIT_FILTER_ALLOW)
+ {
+ 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 (TRUE)
+ {
+ GFileInfo *child_info;
+ gs_unref_object GFile *child = NULL;
+ gs_unref_object GFileInfo *modified_info = NULL;
+ gs_unref_object OstreeMutableTree *child_mtree = NULL;
+ const char *name;
+
+ if (!gs_file_enumerator_iterate (dir_enum, &child_info, NULL,
+ cancellable, error))
+ goto out;
+ if (child_info == NULL)
+ break;
+
+ name = g_file_info_get_name (child_info);
+ g_ptr_array_add (path, (char*)name);
+ filter_result = apply_commit_filter (self, modifier, path, child_info, &modified_info);
+
+ if (filter_result == OSTREE_REPO_COMMIT_FILTER_ALLOW)
+ {
+ child = g_file_get_child (dir, name);
+
+ if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY)
+ {
+ if (!ostree_mutable_tree_ensure_dir (mtree, name, &child_mtree, error))
+ goto out;
+
+ if (!stage_directory_to_mtree_internal (self, child, child_mtree,
+ modifier, path,
+ cancellable, error))
+ goto out;
+ }
+ else if (repo_dir)
+ {
+ g_debug ("Adding: %s", gs_file_get_path_cached (child));
+ if (!ostree_mutable_tree_replace_file (mtree, name,
+ ostree_repo_file_get_checksum ((OstreeRepoFile*)
child),
+ error))
+ goto out;
+ }
+ else
+ {
+ guint64 file_obj_length;
+ const char *loose_checksum;
+ gs_unref_object GInputStream *file_input = NULL;
+ gs_unref_variant GVariant *xattrs = NULL;
+ gs_unref_object GInputStream *file_object_input = NULL;
+ gs_free guchar *child_file_csum = NULL;
+ gs_free char *tmp_checksum = NULL;
+
+ g_debug ("Adding: %s", gs_file_get_path_cached (child));
+ loose_checksum = devino_cache_lookup (self, child_info);
+
+ if (loose_checksum)
+ {
+ if (!ostree_mutable_tree_replace_file (mtree, name, loose_checksum,
+ error))
+ goto out;
+ }
+ else
+ {
+ if (g_file_info_get_file_type (modified_info) == G_FILE_TYPE_REGULAR)
+ {
+ file_input = (GInputStream*)g_file_read (child, cancellable, error);
+ if (!file_input)
+ goto out;
+ }
+
+ if (!(modifier && (modifier->flags & OSTREE_REPO_COMMIT_MODIFIER_FLAGS_SKIP_XATTRS) >
0))
+ {
+ g_clear_pointer (&xattrs, (GDestroyNotify) g_variant_unref);
+ if (!ostree_get_xattrs_for_file (child, &xattrs, cancellable, error))
+ goto out;
+ }
+
+ if (!ostree_raw_file_to_content_stream (file_input,
+ modified_info, xattrs,
+ &file_object_input, &file_obj_length,
+ cancellable, error))
+ goto out;
+ if (!ostree_repo_stage_content (self, NULL, file_object_input, file_obj_length,
+ &child_file_csum, cancellable, error))
+ goto out;
+
+ g_free (tmp_checksum);
+ tmp_checksum = ostree_checksum_from_bytes (child_file_csum);
+ if (!ostree_mutable_tree_replace_file (mtree, name, tmp_checksum,
+ error))
+ goto out;
+ }
+ }
+
+ g_ptr_array_remove_index (path, path->len - 1);
+ }
+ }
+ }
+
+ if (repo_dir && repo_dir_was_empty)
+ ostree_mutable_tree_set_contents_checksum (mtree, ostree_repo_file_tree_get_content_checksum (repo_dir));
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+/**
+ * ostree_repo_stage_directory_to_mtree:
+ * @self: Repo
+ * @dir: Path to a directory
+ * @mtree: Overlay directory contents into this tree
+ * @modifier: (allow-none): Optional modifier
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Store objects for @dir and all children into the repository @self,
+ * overlaying the resulting filesystem hierarchy into @mtree.
+ */
+gboolean
+ostree_repo_stage_directory_to_mtree (OstreeRepo *self,
+ GFile *dir,
+ OstreeMutableTree *mtree,
+ OstreeRepoCommitModifier *modifier,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GPtrArray *path = NULL;
+
+ path = g_ptr_array_new ();
+ if (!stage_directory_to_mtree_internal (self, dir, mtree, modifier, path,
+ cancellable, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ if (path)
+ g_ptr_array_free (path, TRUE);
+ return ret;
+}
+
+/**
+ * ostree_repo_stage_mtree:
+ * @self: Repo
+ * @mtree: Mutable tree
+ * @out_contents_checksum: (out): Return location for ASCII checksum
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Write all metadata objects for @mtree to repo; the resulting
+ * @out_contents_checksum contains the checksum for the
+ * %OSTREE_OBJECT_TYPE_DIR_TREE object.
+ */
+gboolean
+ostree_repo_stage_mtree (OstreeRepo *self,
+ OstreeMutableTree *mtree,
+ char **out_contents_checksum,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GHashTableIter hash_iter;
+ gpointer key, value;
+ const char *existing_checksum;
+ gs_free char *ret_contents_checksum = NULL;
+ gs_unref_hashtable GHashTable *dir_metadata_checksums = NULL;
+ gs_unref_hashtable GHashTable *dir_contents_checksums = NULL;
+ gs_unref_variant GVariant *serialized_tree = NULL;
+ gs_free guchar *contents_csum = NULL;
+
+ existing_checksum = ostree_mutable_tree_get_contents_checksum (mtree);
+ if (existing_checksum)
+ {
+ ret_contents_checksum = g_strdup (existing_checksum);
+ }
+ else
+ {
+ dir_contents_checksums = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify)g_free, (GDestroyNotify)g_free);
+ dir_metadata_checksums = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify)g_free, (GDestroyNotify)g_free);
+
+ g_hash_table_iter_init (&hash_iter, ostree_mutable_tree_get_subdirs (mtree));
+ while (g_hash_table_iter_next (&hash_iter, &key, &value))
+ {
+ const char *name = key;
+ const char *metadata_checksum;
+ OstreeMutableTree *child_dir = value;
+ char *child_dir_contents_checksum;
+
+ if (!ostree_repo_stage_mtree (self, child_dir, &child_dir_contents_checksum,
+ cancellable, error))
+ goto out;
+
+ g_assert (child_dir_contents_checksum);
+ g_hash_table_replace (dir_contents_checksums, g_strdup (name),
+ child_dir_contents_checksum); /* Transfer ownership */
+ metadata_checksum = ostree_mutable_tree_get_metadata_checksum (child_dir);
+ g_assert (metadata_checksum);
+ g_hash_table_replace (dir_metadata_checksums, g_strdup (name),
+ g_strdup (metadata_checksum));
+ }
+
+ serialized_tree = create_tree_variant_from_hashes (ostree_mutable_tree_get_files (mtree),
+ dir_contents_checksums,
+ dir_metadata_checksums);
+
+ if (!ostree_repo_stage_metadata (self, OSTREE_OBJECT_TYPE_DIR_TREE, NULL,
+ serialized_tree, &contents_csum,
+ cancellable, error))
+ goto out;
+ ret_contents_checksum = ostree_checksum_from_bytes (contents_csum);
+ }
+
+ ret = TRUE;
+ ot_transfer_out_value(out_contents_checksum, &ret_contents_checksum);
+ out:
+ return ret;
+}
+
+/**
+ * ostree_repo_commit_modifier_new:
+ * @flags: Control options for filter
+ * @commit_filter: (allow-none): Function that can inspect individual files
+ * @user_data: (allow-none): User data
+ * @destroy_notify: A #GDestroyNotify
+ *
+ * Returns: (transfer full): A new commit modifier.
+ */
+OstreeRepoCommitModifier *
+ostree_repo_commit_modifier_new (OstreeRepoCommitModifierFlags flags,
+ OstreeRepoCommitFilter commit_filter,
+ gpointer user_data,
+ GDestroyNotify destroy_notify)
+{
+ OstreeRepoCommitModifier *modifier = g_new0 (OstreeRepoCommitModifier, 1);
+
+ modifier->refcount = 1;
+ modifier->flags = flags;
+ modifier->filter = commit_filter;
+ modifier->user_data = user_data;
+ modifier->destroy_notify = destroy_notify;
+
+ return modifier;
+}
+
+OstreeRepoCommitModifier *
+ostree_repo_commit_modifier_ref (OstreeRepoCommitModifier *modifier)
+{
+ g_atomic_int_inc (&modifier->refcount);
+ return modifier;
+}
+
+void
+ostree_repo_commit_modifier_unref (OstreeRepoCommitModifier *modifier)
+{
+ if (!modifier)
+ return;
+ if (!g_atomic_int_dec_and_test (&modifier->refcount))
+ return;
+
+ modifier->destroy_notify (modifier->user_data);
+
+ g_free (modifier);
+ return;
+}
+
+G_DEFINE_BOXED_TYPE(OstreeRepoCommitModifier, ostree_repo_commit_modifier,
+ ostree_repo_commit_modifier_ref,
+ ostree_repo_commit_modifier_unref);
diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h
index e03656d..705f844 100644
--- a/src/libostree/ostree-repo-private.h
+++ b/src/libostree/ostree-repo-private.h
@@ -69,6 +69,20 @@ GFile *
_ostree_repo_get_file_object_path (OstreeRepo *self,
const char *checksum);
+gboolean
+_ostree_repo_find_object (OstreeRepo *self,
+ OstreeObjectType objtype,
+ const char *checksum,
+ GFile **out_stored_path,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean
+_ostree_repo_get_loose_object_dirs (OstreeRepo *self,
+ GPtrArray **out_object_dirs,
+ GCancellable *cancellable,
+ GError **error);
+
GFile *
_ostree_repo_get_object_path (OstreeRepo *self,
const char *checksum,
diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c
index 95d42b1..3c5dad6 100644
--- a/src/libostree/ostree-repo.c
+++ b/src/libostree/ostree-repo.c
@@ -22,20 +22,13 @@
#include "config.h"
-#include <gio/gunixoutputstream.h>
-#include <gio/gunixinputstream.h>
-#include <glib/gstdio.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "ostree-repo-private.h"
-#include "ostree-core-private.h"
-#include "ostree-mutable-tree.h"
-#include "ostree-checksum-input-stream.h"
+#include <glib-unix.h>
#include "otutil.h"
#include "libgsystem.h"
-#include "ostree-repo-file-enumerator.h"
+
+#include "ostree-core-private.h"
+#include "ostree-repo-private.h"
+#include "ostree-repo-file.h"
/**
* SECTION:libostree-repo
@@ -72,14 +65,6 @@ typedef struct {
GObjectClass parent_class;
} OstreeRepoClass;
-static gboolean
-repo_find_object (OstreeRepo *self,
- OstreeObjectType objtype,
- const char *checksum,
- GFile **out_stored_path,
- GCancellable *cancellable,
- GError **error);
-
enum {
PROP_0,
@@ -578,361 +563,84 @@ _ostree_repo_get_file_object_path (OstreeRepo *self,
}
static gboolean
-commit_loose_object_impl (OstreeRepo *self,
- GFile *tempfile_path,
- GFile *dest,
- gboolean is_regular,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- gs_unref_object GFile *parent = NULL;
-
- parent = g_file_get_parent (dest);
- if (!gs_file_ensure_directory (parent, FALSE, cancellable, error))
- goto out;
-
- if (is_regular)
- {
- /* Ensure that in case of a power cut, these files have the data we
- * want. See http://lwn.net/Articles/322823/
- */
- if (!gs_file_sync_data (tempfile_path, cancellable, error))
- goto out;
- }
-
- if (rename (gs_file_get_path_cached (tempfile_path), gs_file_get_path_cached (dest)) < 0)
- {
- if (errno != EEXIST)
- {
- ot_util_set_error_from_errno (error, errno);
- g_prefix_error (error, "Storing file '%s': ",
- gs_file_get_path_cached (dest));
- goto out;
- }
- }
-
- ret = TRUE;
- out:
- return ret;
-}
-
-static gboolean
-commit_loose_object_trusted (OstreeRepo *self,
- const char *checksum,
- OstreeObjectType objtype,
- GFile *tempfile_path,
- gboolean is_regular,
- GCancellable *cancellable,
- GError **error)
+list_loose_object_dir (OstreeRepo *self,
+ GFile *dir,
+ GHashTable *inout_objects,
+ GCancellable *cancellable,
+ GError **error)
{
gboolean ret = FALSE;
- gs_unref_object GFile *dest_file = NULL;
+ const char *dirname = NULL;
+ const char *dot = NULL;
+ gs_unref_object GFileEnumerator *enumerator = NULL;
+ GString *checksum = NULL;
- dest_file = _ostree_repo_get_object_path (self, checksum, objtype);
+ dirname = gs_file_get_basename_cached (dir);
- if (!commit_loose_object_impl (self, tempfile_path, dest_file, is_regular,
- cancellable, error))
+ /* We're only querying name */
+ enumerator = g_file_enumerate_children (dir, "standard::name,standard::type",
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable,
+ error);
+ if (!enumerator)
goto out;
-
- ret = TRUE;
- out:
- return ret;
-}
-
-/* Create a randomly-named symbolic link in @tempdir which points to
- * @target. The filename will be returned in @out_file.
- *
- * The reason this odd function exists is that the repo should only
- * contain objects in their final state. For bare repositories, we
- * need to first create the symlink, then chown it, and apply all
- * extended attributes, before finally rename()ing it into place.
- */
-static gboolean
-make_temporary_symlink (GFile *tmpdir,
- const char *target,
- GFile **out_file,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- gs_free char *tmpname = NULL;
- DIR *d = NULL;
- int dfd = -1;
- guint i;
- const int max_attempts = 128;
-
- d = opendir (gs_file_get_path_cached (tmpdir));
- if (!d)
- {
- int errsv = errno;
- g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
- g_strerror (errsv));
- goto out;
- }
- dfd = dirfd (d);
-
- for (i = 0; i < max_attempts; i++)
- {
- g_free (tmpname);
- tmpname = gsystem_fileutil_gen_tmp_name (NULL, NULL);
- if (symlinkat (target, dfd, tmpname) < 0)
- {
- if (errno == EEXIST)
- continue;
- else
- {
- int errsv = errno;
- g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
- g_strerror (errsv));
- goto out;
- }
- }
- else
- break;
- }
- if (i == max_attempts)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Exhausted attempts to open temporary file");
- goto out;
- }
-
- ret = TRUE;
- *out_file = g_file_get_child (tmpdir, tmpname);
- out:
- if (d) (void) closedir (d);
- return ret;
-}
-
-static gboolean
-stage_object (OstreeRepo *self,
- OstreeObjectType objtype,
- const char *expected_checksum,
- GInputStream *input,
- guint64 file_object_length,
- guchar **out_csum,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- const char *actual_checksum;
- gboolean do_commit;
- OstreeRepoMode repo_mode;
- gs_unref_object GFile *temp_file = NULL;
- gs_unref_object GFile *raw_temp_file = NULL;
- gs_unref_object GFile *stored_path = NULL;
- gs_free guchar *ret_csum = NULL;
- gs_unref_object OstreeChecksumInputStream *checksum_input = NULL;
- gs_unref_object GInputStream *file_input = NULL;
- gs_unref_object GFileInfo *file_info = NULL;
- gs_unref_variant GVariant *xattrs = NULL;
- gboolean have_obj;
- GChecksum *checksum = NULL;
- gboolean temp_file_is_regular;
- gboolean is_symlink = FALSE;
-
- g_return_val_if_fail (self->in_transaction, FALSE);
- if (g_cancellable_set_error_if_cancelled (cancellable, error))
- return FALSE;
-
- g_assert (expected_checksum || out_csum);
-
- if (expected_checksum)
- {
- if (!repo_find_object (self, objtype, expected_checksum, &stored_path,
- cancellable, error))
- goto out;
- }
-
- repo_mode = ostree_repo_get_mode (self);
-
- if (out_csum)
+ while (TRUE)
{
- checksum = g_checksum_new (G_CHECKSUM_SHA256);
- if (input)
- checksum_input = ostree_checksum_input_stream_new (input, checksum);
- }
+ GFileInfo *file_info;
+ const char *name;
+ guint32 type;
+ OstreeObjectType objtype;
- if (objtype == OSTREE_OBJECT_TYPE_FILE)
- {
- if (!ostree_content_stream_parse (FALSE, checksum_input ? (GInputStream*)checksum_input : input,
- file_object_length, FALSE,
- &file_input, &file_info, &xattrs,
- cancellable, error))
+ if (!gs_file_enumerator_iterate (enumerator, &file_info, NULL,
+ NULL, error))
goto out;
+ if (file_info == NULL)
+ break;
- temp_file_is_regular = g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR;
- is_symlink = g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK;
-
- if (!(temp_file_is_regular || is_symlink))
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
- "Unsupported file type %u", g_file_info_get_file_type (file_info));
- goto out;
- }
-
- /* For regular files, we create them with default mode, and only
- * later apply any xattrs and setuid bits. The rationale here
- * is that an attacker on the network with the ability to MITM
- * could potentially cause the system to make a temporary setuid
- * binary with trailing garbage, creating a window on the local
- * system where a malicious setuid binary exists.
- */
- if (repo_mode == OSTREE_REPO_MODE_BARE && temp_file_is_regular)
- {
- gs_unref_object GOutputStream *temp_out = NULL;
- if (!gs_file_open_in_tmpdir (self->tmp_dir, 0644, &temp_file, &temp_out,
- cancellable, error))
- goto out;
- if (g_output_stream_splice (temp_out, file_input, G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
- cancellable, error) < 0)
- goto out;
- }
- else if (repo_mode == OSTREE_REPO_MODE_BARE && is_symlink)
- {
- if (!make_temporary_symlink (self->tmp_dir,
- g_file_info_get_symlink_target (file_info),
- &temp_file,
- cancellable, error))
- goto out;
- }
- else if (repo_mode == OSTREE_REPO_MODE_ARCHIVE_Z2)
- {
- gs_unref_variant GVariant *file_meta = NULL;
- gs_unref_object GOutputStream *temp_out = NULL;
- gs_unref_object GConverter *zlib_compressor = NULL;
- gs_unref_object GOutputStream *compressed_out_stream = NULL;
-
- if (!gs_file_open_in_tmpdir (self->tmp_dir, 0644,
- &temp_file, &temp_out,
- cancellable, error))
- goto out;
- temp_file_is_regular = TRUE;
-
- file_meta = _ostree_zlib_file_header_new (file_info, xattrs);
-
- if (!_ostree_write_variant_with_size (temp_out, file_meta, 0, NULL, NULL,
- cancellable, error))
- goto out;
+ type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
- if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
- {
- zlib_compressor = (GConverter*)g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW, 9);
- compressed_out_stream = g_converter_output_stream_new (temp_out, zlib_compressor);
-
- if (g_output_stream_splice (compressed_out_stream, file_input,
- G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
- cancellable, error) < 0)
- goto out;
- }
+ if (type == G_FILE_TYPE_DIRECTORY)
+ continue;
- if (!g_output_stream_close (temp_out, cancellable, error))
- goto out;
- }
+ name = g_file_info_get_attribute_byte_string (file_info, "standard::name");
+
+ if (g_str_has_suffix (name, ".file"))
+ objtype = OSTREE_OBJECT_TYPE_FILE;
+ else if (g_str_has_suffix (name, ".dirtree"))
+ objtype = OSTREE_OBJECT_TYPE_DIR_TREE;
+ else if (g_str_has_suffix (name, ".dirmeta"))
+ objtype = OSTREE_OBJECT_TYPE_DIR_META;
+ else if (g_str_has_suffix (name, ".commit"))
+ objtype = OSTREE_OBJECT_TYPE_COMMIT;
else
- g_assert_not_reached ();
- }
- else
- {
- gs_unref_object GOutputStream *temp_out = NULL;
- if (!gs_file_open_in_tmpdir (self->tmp_dir, 0644, &temp_file, &temp_out,
- cancellable, error))
- goto out;
- if (g_output_stream_splice (temp_out, checksum_input ? (GInputStream*)checksum_input : input,
- G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
- cancellable, error) < 0)
- goto out;
- temp_file_is_regular = TRUE;
- }
-
- if (!checksum)
- actual_checksum = expected_checksum;
- else
- {
- actual_checksum = g_checksum_get_string (checksum);
- if (expected_checksum && strcmp (actual_checksum, expected_checksum) != 0)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Corrupted %s object %s (actual checksum is %s)",
- ostree_object_type_to_string (objtype),
- expected_checksum, actual_checksum);
- goto out;
- }
- }
-
- if (!ostree_repo_has_object (self, objtype, actual_checksum, &have_obj,
- cancellable, error))
- goto out;
+ continue;
- do_commit = !have_obj;
+ dot = strrchr (name, '.');
+ g_assert (dot);
- if (do_commit)
- {
- if (objtype == OSTREE_OBJECT_TYPE_FILE && repo_mode == OSTREE_REPO_MODE_BARE)
+ if ((dot - name) == 62)
{
- g_assert (file_info != NULL);
- /* Now that we know the checksum is valid, apply uid/gid, mode bits,
- * and extended attributes.
- */
- if (!gs_file_lchown (temp_file,
- g_file_info_get_attribute_uint32 (file_info, "unix::uid"),
- g_file_info_get_attribute_uint32 (file_info, "unix::gid"),
- cancellable, error))
- goto out;
- /* symlinks are always 777, there's no lchmod(). Calling
- * chmod() on them would apply to their target, which we
- * definitely don't want.
- */
- if (!is_symlink)
- {
- if (!gs_file_chmod (temp_file, g_file_info_get_attribute_uint32 (file_info, "unix::mode"),
- cancellable, error))
- goto out;
- }
- if (xattrs != NULL)
- {
- if (!ostree_set_xattrs (temp_file, xattrs, cancellable, error))
- goto out;
- }
- }
- if (!commit_loose_object_trusted (self, actual_checksum, objtype, temp_file, temp_file_is_regular,
- cancellable, error))
- goto out;
- g_clear_object (&temp_file);
- }
+ GVariant *key, *value;
- g_mutex_lock (&self->txn_stats_lock);
- if (do_commit)
- {
- if (OSTREE_OBJECT_TYPE_IS_META (objtype))
- {
- self->txn_metadata_objects_written++;
- }
- else
- {
- self->txn_content_objects_written++;
- self->txn_content_bytes_written += file_object_length;
+ if (checksum)
+ g_string_free (checksum, TRUE);
+ checksum = g_string_new (dirname);
+ g_string_append_len (checksum, name, 62);
+
+ key = ostree_object_name_serialize (checksum->str, objtype);
+ value = g_variant_new ("(b as)",
+ TRUE, g_variant_new_strv (NULL, 0));
+ /* transfer ownership */
+ g_hash_table_replace (inout_objects, key,
+ g_variant_ref_sink (value));
}
}
- if (OSTREE_OBJECT_TYPE_IS_META (objtype))
- self->txn_metadata_objects_total++;
- else
- self->txn_content_objects_total++;
- g_mutex_unlock (&self->txn_stats_lock);
-
- if (checksum)
- ret_csum = ot_csum_from_gchecksum (checksum);
ret = TRUE;
- ot_transfer_out_value(out_csum, &ret_csum);
out:
- if (temp_file)
- (void) unlink (gs_file_get_path_cached (temp_file));
- if (raw_temp_file)
- (void) unlink (gs_file_get_path_cached (raw_temp_file));
- g_clear_pointer (&checksum, (GDestroyNotify) g_checksum_free);
+ if (checksum)
+ g_string_free (checksum, TRUE);
return ret;
}
@@ -991,11 +699,11 @@ append_object_dirs_from (OstreeRepo *self,
return ret;
}
-static gboolean
-get_loose_object_dirs (OstreeRepo *self,
- GPtrArray **out_object_dirs,
- GCancellable *cancellable,
- GError **error)
+gboolean
+_ostree_repo_get_loose_object_dirs (OstreeRepo *self,
+ GPtrArray **out_object_dirs,
+ GCancellable *cancellable,
+ GError **error)
{
gboolean ret = FALSE;
gs_unref_ptrarray GPtrArray *ret_object_dirs = NULL;
@@ -1020,1317 +728,6 @@ get_loose_object_dirs (OstreeRepo *self,
return ret;
}
-typedef struct {
- dev_t dev;
- ino_t ino;
-} OstreeDevIno;
-
-static guint
-devino_hash (gconstpointer a)
-{
- OstreeDevIno *a_i = (gpointer)a;
- return (guint) (a_i->dev + a_i->ino);
-}
-
-static int
-devino_equal (gconstpointer a,
- gconstpointer b)
-{
- OstreeDevIno *a_i = (gpointer)a;
- OstreeDevIno *b_i = (gpointer)b;
- return a_i->dev == b_i->dev
- && a_i->ino == b_i->ino;
-}
-
-static gboolean
-scan_loose_devino (OstreeRepo *self,
- GHashTable *devino_cache,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- guint i;
- OstreeRepoMode repo_mode;
- gs_unref_ptrarray GPtrArray *object_dirs = NULL;
-
- if (self->parent_repo)
- {
- if (!scan_loose_devino (self->parent_repo, devino_cache, cancellable, error))
- goto out;
- }
-
- repo_mode = ostree_repo_get_mode (self);
-
- if (!get_loose_object_dirs (self, &object_dirs, cancellable, error))
- goto out;
-
- for (i = 0; i < object_dirs->len; i++)
- {
- GFile *objdir = object_dirs->pdata[i];
- gs_unref_object GFileEnumerator *enumerator = NULL;
- gs_unref_object GFileInfo *file_info = NULL;
- const char *dirname;
-
- enumerator = g_file_enumerate_children (objdir, OSTREE_GIO_FAST_QUERYINFO,
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- cancellable,
- error);
- if (!enumerator)
- goto out;
-
- dirname = gs_file_get_basename_cached (objdir);
-
- while (TRUE)
- {
- const char *name;
- const char *dot;
- guint32 type;
- OstreeDevIno *key;
- GString *checksum;
- gboolean skip;
-
- if (!gs_file_enumerator_iterate (enumerator, &file_info, NULL,
- NULL, error))
- goto out;
- if (file_info == NULL)
- break;
-
- 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)
- continue;
-
- switch (repo_mode)
- {
- case OSTREE_REPO_MODE_ARCHIVE_Z2:
- case OSTREE_REPO_MODE_BARE:
- skip = !g_str_has_suffix (name, ".file");
- break;
- default:
- g_assert_not_reached ();
- }
- if (skip)
- continue;
-
- dot = strrchr (name, '.');
- g_assert (dot);
-
- if ((dot - name) != 62)
- continue;
-
- checksum = g_string_new (dirname);
- g_string_append_len (checksum, name, 62);
-
- key = g_new (OstreeDevIno, 1);
- key->dev = g_file_info_get_attribute_uint32 (file_info, "unix::device");
- key->ino = g_file_info_get_attribute_uint64 (file_info, "unix::inode");
-
- g_hash_table_replace (devino_cache, key, g_string_free (checksum, FALSE));
- }
- }
-
- ret = TRUE;
- out:
- return ret;
-}
-
-static const char *
-devino_cache_lookup (OstreeRepo *self,
- GFileInfo *finfo)
-{
- OstreeDevIno dev_ino;
-
- if (!self->loose_object_devino_hash)
- return NULL;
-
- dev_ino.dev = g_file_info_get_attribute_uint32 (finfo, "unix::device");
- dev_ino.ino = g_file_info_get_attribute_uint64 (finfo, "unix::inode");
- return g_hash_table_lookup (self->loose_object_devino_hash, &dev_ino);
-}
-
-/**
- * ostree_repo_prepare_transaction:
- * @self: An #OstreeRepo
- * @enable_commit_hardlink_scan:
- * @out_transaction_resume: (allow-none) (out): Whether this transaction
- * is resuming from a previous one.
- * @cancellable: Cancellable
- * @error: Error
- *
- * Starts or resumes a transaction. In order to write to a repo, you
- * need to start a transaction. You can complete the transaction with
- * ostree_repo_commit_transaction(), or abort the transaction with
- * ostree_repo_abort_transaction().
- *
- * Currently, transactions are not atomic, and aborting a transaction
- * will not erase any data you write during the transaction.
- */
-gboolean
-ostree_repo_prepare_transaction (OstreeRepo *self,
- gboolean enable_commit_hardlink_scan,
- gboolean *out_transaction_resume,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- gboolean ret_transaction_resume = FALSE;
- gs_free char *transaction_str = NULL;
-
- g_return_val_if_fail (self->in_transaction == FALSE, FALSE);
-
- if (self->transaction_lock_path == NULL)
- self->transaction_lock_path = g_file_resolve_relative_path (self->repodir, "transaction");
-
- if (g_file_query_file_type (self->transaction_lock_path, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) ==
G_FILE_TYPE_SYMBOLIC_LINK)
- ret_transaction_resume = TRUE;
- else
- ret_transaction_resume = FALSE;
-
- self->txn_metadata_objects_total =
- self->txn_metadata_objects_written =
- self->txn_content_objects_total =
- self->txn_content_objects_written =
- self->txn_content_bytes_written = 0;
-
- self->in_transaction = TRUE;
- if (ret_transaction_resume)
- {
- if (!ot_gfile_ensure_unlinked (self->transaction_lock_path, cancellable, error))
- goto out;
- }
- transaction_str = g_strdup_printf ("pid=%llu", (unsigned long long) getpid ());
- if (!g_file_make_symbolic_link (self->transaction_lock_path, transaction_str,
- cancellable, error))
- goto out;
-
- if (enable_commit_hardlink_scan)
- {
- if (!self->loose_object_devino_hash)
- self->loose_object_devino_hash = g_hash_table_new_full (devino_hash, devino_equal, g_free, g_free);
- g_hash_table_remove_all (self->loose_object_devino_hash);
- if (!scan_loose_devino (self, self->loose_object_devino_hash, cancellable, error))
- goto out;
- }
-
- ret = TRUE;
- if (out_transaction_resume)
- *out_transaction_resume = ret_transaction_resume;
- out:
- return ret;
-}
-
-static gboolean
-cleanup_tmpdir (OstreeRepo *self,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- gs_unref_object GFileEnumerator *enumerator = NULL;
-
- enumerator = g_file_enumerate_children (self->tmp_dir, "standard::name",
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- cancellable,
- error);
- if (!enumerator)
- goto out;
-
- while (TRUE)
- {
- GFileInfo *file_info;
- GFile *path;
-
- if (!gs_file_enumerator_iterate (enumerator, &file_info, &path,
- cancellable, error))
- goto out;
- if (file_info == NULL)
- break;
-
- if (!gs_shutil_rm_rf (path, cancellable, error))
- goto out;
- }
-
- ret = TRUE;
- out:
- return ret;
-}
-
-gboolean
-ostree_repo_commit_transaction_with_stats (OstreeRepo *self,
- guint *out_metadata_objects_total,
- guint *out_metadata_objects_written,
- guint *out_content_objects_total,
- guint *out_content_objects_written,
- guint64 *out_content_bytes_written,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
-
- g_return_val_if_fail (self->in_transaction == TRUE, FALSE);
-
- if (!cleanup_tmpdir (self, cancellable, error))
- goto out;
-
- if (self->loose_object_devino_hash)
- g_hash_table_remove_all (self->loose_object_devino_hash);
-
- self->in_transaction = FALSE;
-
- if (!ot_gfile_ensure_unlinked (self->transaction_lock_path, cancellable, error))
- goto out;
-
- if (out_metadata_objects_total) *out_metadata_objects_total = self->txn_metadata_objects_total;
- if (out_metadata_objects_written) *out_metadata_objects_written = self->txn_metadata_objects_written;
- if (out_content_objects_total) *out_content_objects_total = self->txn_content_objects_total;
- if (out_content_objects_written) *out_content_objects_written = self->txn_content_objects_written;
- if (out_content_bytes_written) *out_content_bytes_written = self->txn_content_bytes_written;
-
- ret = TRUE;
- out:
- return ret;
-}
-
-gboolean
-ostree_repo_commit_transaction (OstreeRepo *self,
- GCancellable *cancellable,
- GError **error)
-{
- return ostree_repo_commit_transaction_with_stats (self, NULL, NULL, NULL, NULL, NULL,
- cancellable, error);
-}
-
-gboolean
-ostree_repo_abort_transaction (OstreeRepo *self,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
-
- g_return_val_if_fail (self->in_transaction == TRUE, FALSE);
-
- if (!cleanup_tmpdir (self, cancellable, error))
- goto out;
-
- if (self->loose_object_devino_hash)
- g_hash_table_remove_all (self->loose_object_devino_hash);
-
- self->in_transaction = FALSE;
-
- ret = TRUE;
- out:
- return ret;
-}
-
-/**
- * ostree_repo_stage_metadata:
- * @self: Repo
- * @objtype: Object type
- * @expected_checksum: (allow-none): If provided, validate content against this checksum
- * @object: Metadata
- * @out_csum: (out) (array fixed-size=32) (allow-none): Binary checksum
- * @cancellable: Cancellable
- * @error: Error
- *
- * Store the metadata object @variant. Return the checksum
- * as @out_csum.
- *
- * If @expected_checksum is not %NULL, verify it against the
- * computed checksum.
- */
-gboolean
-ostree_repo_stage_metadata (OstreeRepo *self,
- OstreeObjectType objtype,
- const char *expected_checksum,
- GVariant *object,
- guchar **out_csum,
- GCancellable *cancellable,
- GError **error)
-{
- gs_unref_object GInputStream *input = NULL;
- gs_unref_variant GVariant *normalized = NULL;
-
- normalized = g_variant_get_normal_form (object);
- input = ot_variant_read (normalized);
-
- return stage_object (self, objtype, expected_checksum, input, 0, out_csum,
- cancellable, error);
-}
-
-/**
- * ostree_repo_stage_metadata_trusted:
- * @self: Repo
- * @objtype: Object type
- * @checksum: Store object with this ASCII SHA256 checksum
- * @variant: Metadata object
- * @cancellable: Cancellable
- * @error: Error
- *
- * Store the metadata object @variant; the provided @checksum is
- * trusted.
- */
-gboolean
-ostree_repo_stage_metadata_trusted (OstreeRepo *self,
- OstreeObjectType type,
- const char *checksum,
- GVariant *variant,
- GCancellable *cancellable,
- GError **error)
-{
- gs_unref_object GInputStream *input = NULL;
- gs_unref_variant GVariant *normalized = NULL;
-
- normalized = g_variant_get_normal_form (variant);
- input = ot_variant_read (normalized);
-
- return stage_object (self, type, checksum, input, 0, NULL,
- cancellable, error);
-}
-
-typedef struct {
- OstreeRepo *repo;
- OstreeObjectType objtype;
- char *expected_checksum;
- GVariant *object;
- GCancellable *cancellable;
- GSimpleAsyncResult *result;
-
- guchar *result_csum;
-} StageMetadataAsyncData;
-
-static void
-stage_metadata_async_data_free (gpointer user_data)
-{
- StageMetadataAsyncData *data = user_data;
-
- g_clear_object (&data->repo);
- g_clear_object (&data->cancellable);
- g_variant_unref (data->object);
- g_free (data->result_csum);
- g_free (data->expected_checksum);
- g_free (data);
-}
-
-static void
-stage_metadata_thread (GSimpleAsyncResult *res,
- GObject *object,
- GCancellable *cancellable)
-{
- GError *error = NULL;
- StageMetadataAsyncData *data;
-
- data = g_simple_async_result_get_op_res_gpointer (res);
- if (!ostree_repo_stage_metadata (data->repo, data->objtype, data->expected_checksum,
- data->object,
- &data->result_csum,
- cancellable, &error))
- g_simple_async_result_take_error (res, error);
-}
-
-/**
- * ostree_repo_stage_metadata_async:
- * @self: Repo
- * @objtype: Object type
- * @expected_checksum: (allow-none): If provided, validate content against this checksum
- * @object: Metadata
- * @cancellable: Cancellable
- * @callback: Invoked when metadata is staged
- * @user_data: Data for @callback
- *
- * Asynchronously store the metadata object @variant. If provided,
- * the checksum @expected_checksum will be verified.
- */
-void
-ostree_repo_stage_metadata_async (OstreeRepo *self,
- OstreeObjectType objtype,
- const char *expected_checksum,
- GVariant *object,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- StageMetadataAsyncData *asyncdata;
-
- asyncdata = g_new0 (StageMetadataAsyncData, 1);
- asyncdata->repo = g_object_ref (self);
- asyncdata->objtype = objtype;
- asyncdata->expected_checksum = g_strdup (expected_checksum);
- asyncdata->object = g_variant_ref (object);
- asyncdata->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
-
- asyncdata->result = g_simple_async_result_new ((GObject*) self,
- callback, user_data,
- ostree_repo_stage_metadata_async);
-
- g_simple_async_result_set_op_res_gpointer (asyncdata->result, asyncdata,
- stage_metadata_async_data_free);
- g_simple_async_result_run_in_thread (asyncdata->result, stage_metadata_thread, G_PRIORITY_DEFAULT,
cancellable);
- g_object_unref (asyncdata->result);
-}
-
-gboolean
-ostree_repo_stage_metadata_finish (OstreeRepo *self,
- GAsyncResult *result,
- guchar **out_csum,
- GError **error)
-{
- GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
- StageMetadataAsyncData *data;
-
- g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == ostree_repo_stage_metadata_async);
-
- if (g_simple_async_result_propagate_error (simple, error))
- return FALSE;
-
- data = g_simple_async_result_get_op_res_gpointer (simple);
- /* Transfer ownership */
- *out_csum = data->result_csum;
- data->result_csum = NULL;
- return TRUE;
-}
-
-gboolean
-_ostree_repo_stage_directory_meta (OstreeRepo *self,
- GFileInfo *file_info,
- GVariant *xattrs,
- guchar **out_csum,
- GCancellable *cancellable,
- GError **error)
-{
- gs_unref_variant GVariant *dirmeta = NULL;
-
- if (g_cancellable_set_error_if_cancelled (cancellable, error))
- return FALSE;
-
- dirmeta = ostree_create_directory_metadata (file_info, xattrs);
-
- return ostree_repo_stage_metadata (self, OSTREE_OBJECT_TYPE_DIR_META, NULL,
- dirmeta, out_csum, cancellable, error);
-}
-
-GFile *
-_ostree_repo_get_object_path (OstreeRepo *self,
- const char *checksum,
- OstreeObjectType type)
-{
- char *relpath;
- GFile *ret;
- gboolean compressed;
-
- compressed = (type == OSTREE_OBJECT_TYPE_FILE
- && ostree_repo_get_mode (self) == OSTREE_REPO_MODE_ARCHIVE_Z2);
- relpath = ostree_get_relative_object_path (checksum, type, compressed);
- ret = g_file_resolve_relative_path (self->repodir, relpath);
- g_free (relpath);
-
- return ret;
-}
-
-GFile *
-_ostree_repo_get_uncompressed_object_cache_path (OstreeRepo *self,
- const char *checksum)
-{
- char *relpath;
- GFile *ret;
-
- relpath = ostree_get_relative_object_path (checksum, OSTREE_OBJECT_TYPE_FILE, FALSE);
- ret = g_file_resolve_relative_path (self->uncompressed_objects_dir, relpath);
- g_free (relpath);
-
- return ret;
-}
-
-/**
- * ostree_repo_stage_content_trusted:
- * @self: Repo
- * @checksum: Store content using this ASCII SHA256 checksum
- * @object_input: Content stream
- * @length: Length of @object_input
- * @cancellable: Cancellable
- * @error: Data for @callback
- *
- * Store the content object streamed as @object_input, with total
- * length @length. The given @checksum will be treated as trusted.
- *
- * This function should be used when importing file objects from local
- * disk, for example.
- */
-gboolean
-ostree_repo_stage_content_trusted (OstreeRepo *self,
- const char *checksum,
- GInputStream *object_input,
- guint64 length,
- GCancellable *cancellable,
- GError **error)
-{
- return stage_object (self, OSTREE_OBJECT_TYPE_FILE, checksum,
- object_input, length, NULL,
- cancellable, error);
-}
-
-/**
- * ostree_repo_stage_content:
- * @self: Repo
- * @expected_checksum: (allow-none): If provided, validate content against this checksum
- * @object_input: Content object stream
- * @length: Length of @object_input
- * @out_csum: (out) (array fixed-size=32) (allow-none): Binary checksum
- * @cancellable: Cancellable
- * @error: Error
- *
- * Store the content object streamed as @object_input,
- * with total length @length. The actual checksum will
- * be returned as @out_csum.
- */
-gboolean
-ostree_repo_stage_content (OstreeRepo *self,
- const char *expected_checksum,
- GInputStream *object_input,
- guint64 length,
- guchar **out_csum,
- GCancellable *cancellable,
- GError **error)
-{
- return stage_object (self, OSTREE_OBJECT_TYPE_FILE, expected_checksum,
- object_input, length, out_csum,
- cancellable, error);
-}
-
-typedef struct {
- OstreeRepo *repo;
- char *expected_checksum;
- GInputStream *object;
- guint64 file_object_length;
- GCancellable *cancellable;
- GSimpleAsyncResult *result;
-
- guchar *result_csum;
-} StageContentAsyncData;
-
-static void
-stage_content_async_data_free (gpointer user_data)
-{
- StageContentAsyncData *data = user_data;
-
- g_clear_object (&data->repo);
- g_clear_object (&data->cancellable);
- g_clear_object (&data->object);
- g_free (data->result_csum);
- g_free (data->expected_checksum);
- g_free (data);
-}
-
-static void
-stage_content_thread (GSimpleAsyncResult *res,
- GObject *object,
- GCancellable *cancellable)
-{
- GError *error = NULL;
- StageContentAsyncData *data;
-
- data = g_simple_async_result_get_op_res_gpointer (res);
- if (!ostree_repo_stage_content (data->repo, data->expected_checksum,
- data->object, data->file_object_length,
- &data->result_csum,
- cancellable, &error))
- g_simple_async_result_take_error (res, error);
-}
-
-/**
- * ostree_repo_stage_content_async:
- * @self: Repo
- * @expected_checksum: (allow-none): If provided, validate content against this checksum
- * @object: Input
- * @length: Length of @object
- * @cancellable: Cancellable
- * @callback: Invoked when content is staged
- * @user_data: User data for @callback
- *
- * Asynchronously store the content object @object. If provided, the
- * checksum @expected_checksum will be verified.
- */
-void
-ostree_repo_stage_content_async (OstreeRepo *self,
- const char *expected_checksum,
- GInputStream *object,
- guint64 length,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- StageContentAsyncData *asyncdata;
-
- asyncdata = g_new0 (StageContentAsyncData, 1);
- asyncdata->repo = g_object_ref (self);
- asyncdata->expected_checksum = g_strdup (expected_checksum);
- asyncdata->object = g_object_ref (object);
- asyncdata->file_object_length = length;
- asyncdata->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
-
- asyncdata->result = g_simple_async_result_new ((GObject*) self,
- callback, user_data,
- ostree_repo_stage_content_async);
-
- g_simple_async_result_set_op_res_gpointer (asyncdata->result, asyncdata,
- stage_content_async_data_free);
- g_simple_async_result_run_in_thread (asyncdata->result, stage_content_thread, G_PRIORITY_DEFAULT,
cancellable);
- g_object_unref (asyncdata->result);
-}
-
-/**
- * ostree_repo_stage_content_finish:
- * @self: a #OstreeRepo
- * @result: a #GAsyncResult
- * @out_csum: (out) (transfer full): A binary SHA256 checksum of the content object
- * @error: a #GError
- *
- * Completes an invocation of ostree_repo_stage_content_async().
- */
-gboolean
-ostree_repo_stage_content_finish (OstreeRepo *self,
- GAsyncResult *result,
- guchar **out_csum,
- GError **error)
-{
- GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
- StageContentAsyncData *data;
-
- g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == ostree_repo_stage_content_async);
-
- if (g_simple_async_result_propagate_error (simple, error))
- return FALSE;
-
- data = g_simple_async_result_get_op_res_gpointer (simple);
- ot_transfer_out_value (out_csum, &data->result_csum);
- return TRUE;
-}
-
-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);
-}
-
-/**
- * ostree_repo_stage_commit:
- * @self: Repo
- * @branch: Name of ref
- * @parent: (allow-none): ASCII SHA256 checksum for parent, or %NULL for none
- * @subject: Subject
- * @body: Body
- * @root_contents_checksum: ASCII SHA256 checksum for %OSTREE_OBJECT_TYPE_DIR_TREE
- * @root_metadata_checksum: ASCII SHA256 checksum for %OSTREE_OBJECT_TYPE_DIR_META
- * @out_commit: (out): Resulting ASCII SHA256 checksum for commit
- * @cancellable: Cancellable
- * @error: Error
- *
- * Write a commit metadata object, referencing @root_contents_checksum
- * and @root_metadata_checksum.
- */
-gboolean
-ostree_repo_stage_commit (OstreeRepo *self,
- const char *branch,
- const char *parent,
- const char *subject,
- const char *body,
- const char *root_contents_checksum,
- const char *root_metadata_checksum,
- char **out_commit,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- gs_free char *ret_commit = NULL;
- gs_unref_variant GVariant *commit = NULL;
- gs_free guchar *commit_csum = NULL;
- GDateTime *now = NULL;
-
- g_return_val_if_fail (branch != NULL, FALSE);
- g_return_val_if_fail (subject != NULL, FALSE);
- g_return_val_if_fail (root_contents_checksum != NULL, FALSE);
- g_return_val_if_fail (root_metadata_checksum != NULL, FALSE);
-
- now = g_date_time_new_now_utc ();
- commit = g_variant_new ("(@a{sv} ay@a(say)sst ay@ay)",
- create_empty_gvariant_dict (),
- parent ? ostree_checksum_to_bytes_v (parent) : ot_gvariant_new_bytearray (NULL, 0),
- g_variant_new_array (G_VARIANT_TYPE ("(say)"), NULL, 0),
- subject, body ? body : "",
- GUINT64_TO_BE (g_date_time_to_unix (now)),
- ostree_checksum_to_bytes_v (root_contents_checksum),
- ostree_checksum_to_bytes_v (root_metadata_checksum));
- g_variant_ref_sink (commit);
- if (!ostree_repo_stage_metadata (self, OSTREE_OBJECT_TYPE_COMMIT, NULL,
- commit, &commit_csum,
- cancellable, error))
- goto out;
-
- ret_commit = ostree_checksum_from_bytes (commit_csum);
-
- ret = TRUE;
- ot_transfer_out_value(out_commit, &ret_commit);
- out:
- if (now)
- g_date_time_unref (now);
- return ret;
-}
-
-static GVariant *
-create_tree_variant_from_hashes (GHashTable *file_checksums,
- GHashTable *dir_contents_checksums,
- GHashTable *dir_metadata_checksums)
-{
- GHashTableIter hash_iter;
- gpointer key, value;
- GVariantBuilder files_builder;
- GVariantBuilder dirs_builder;
- GSList *sorted_filenames = NULL;
- GSList *iter;
- GVariant *serialized_tree;
-
- g_variant_builder_init (&files_builder, G_VARIANT_TYPE ("a(say)"));
- g_variant_builder_init (&dirs_builder, G_VARIANT_TYPE ("a(sayay)"));
-
- g_hash_table_iter_init (&hash_iter, file_checksums);
- 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 (file_checksums, name);
- g_variant_builder_add (&files_builder, "(s ay)", name,
- ostree_checksum_to_bytes_v (value));
- }
-
- g_slist_free (sorted_filenames);
- sorted_filenames = NULL;
-
- g_hash_table_iter_init (&hash_iter, dir_metadata_checksums);
- 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 *content_checksum;
- const char *meta_checksum;
-
- content_checksum = g_hash_table_lookup (dir_contents_checksums, name);
- meta_checksum = g_hash_table_lookup (dir_metadata_checksums, name);
-
- g_variant_builder_add (&dirs_builder, "(s ay@ay)",
- name,
- ostree_checksum_to_bytes_v (content_checksum),
- ostree_checksum_to_bytes_v (meta_checksum));
- }
-
- g_slist_free (sorted_filenames);
- sorted_filenames = NULL;
-
- serialized_tree = g_variant_new ("(@a(say)@a(sayay))",
- g_variant_builder_end (&files_builder),
- g_variant_builder_end (&dirs_builder));
- g_variant_ref_sink (serialized_tree);
-
- return serialized_tree;
-}
-
-struct OstreeRepoCommitModifier {
- volatile gint refcount;
-
- OstreeRepoCommitModifierFlags flags;
- OstreeRepoCommitFilter filter;
- gpointer user_data;
- GDestroyNotify destroy_notify;
-};
-
-static OstreeRepoCommitFilterResult
-apply_commit_filter (OstreeRepo *self,
- OstreeRepoCommitModifier *modifier,
- GPtrArray *path,
- GFileInfo *file_info,
- GFileInfo **out_modified_info)
-{
- GString *path_buf;
- guint i;
- OstreeRepoCommitFilterResult result;
- GFileInfo *modified_info;
-
- if (modifier == NULL || modifier->filter == NULL)
- {
- *out_modified_info = g_object_ref (file_info);
- return OSTREE_REPO_COMMIT_FILTER_ALLOW;
- }
-
- path_buf = g_string_new ("");
-
- if (path->len == 0)
- g_string_append_c (path_buf, '/');
- else
- {
- for (i = 0; i < path->len; i++)
- {
- const char *elt = path->pdata[i];
-
- g_string_append_c (path_buf, '/');
- g_string_append (path_buf, elt);
- }
- }
-
- modified_info = g_file_info_dup (file_info);
- result = modifier->filter (self, path_buf->str, modified_info, modifier->user_data);
- *out_modified_info = modified_info;
-
- g_string_free (path_buf, TRUE);
- return result;
-}
-
-static gboolean
-stage_directory_to_mtree_internal (OstreeRepo *self,
- GFile *dir,
- OstreeMutableTree *mtree,
- OstreeRepoCommitModifier *modifier,
- GPtrArray *path,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- gboolean repo_dir_was_empty = FALSE;
- OstreeRepoCommitFilterResult filter_result;
- gs_unref_object OstreeRepoFile *repo_dir = NULL;
- gs_unref_object GFileEnumerator *dir_enum = NULL;
- gs_unref_object GFileInfo *child_info = NULL;
-
- g_debug ("Examining: %s", gs_file_get_path_cached (dir));
-
- /* We can only reuse checksums directly if there's no modifier */
- if (OSTREE_IS_REPO_FILE (dir) && modifier == NULL)
- repo_dir = (OstreeRepoFile*)g_object_ref (dir);
-
- if (repo_dir)
- {
- if (!ostree_repo_file_ensure_resolved (repo_dir, error))
- goto out;
-
- ostree_mutable_tree_set_metadata_checksum (mtree, ostree_repo_file_get_checksum (repo_dir));
- repo_dir_was_empty =
- g_hash_table_size (ostree_mutable_tree_get_files (mtree)) == 0
- && g_hash_table_size (ostree_mutable_tree_get_subdirs (mtree)) == 0;
-
- filter_result = OSTREE_REPO_COMMIT_FILTER_ALLOW;
- }
- else
- {
- gs_unref_object GFileInfo *modified_info = NULL;
- gs_unref_variant GVariant *xattrs = NULL;
- gs_free guchar *child_file_csum = NULL;
- gs_free char *tmp_checksum = NULL;
-
- child_info = g_file_query_info (dir, OSTREE_GIO_FAST_QUERYINFO,
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- cancellable, error);
- if (!child_info)
- goto out;
-
- filter_result = apply_commit_filter (self, modifier, path, child_info, &modified_info);
-
- if (filter_result == OSTREE_REPO_COMMIT_FILTER_ALLOW)
- {
- g_debug ("Adding: %s", gs_file_get_path_cached (dir));
- if (!(modifier && (modifier->flags & OSTREE_REPO_COMMIT_MODIFIER_FLAGS_SKIP_XATTRS) > 0))
- {
- if (!ostree_get_xattrs_for_file (dir, &xattrs, cancellable, error))
- goto out;
- }
-
- if (!_ostree_repo_stage_directory_meta (self, modified_info, xattrs, &child_file_csum,
- cancellable, error))
- goto out;
-
- g_free (tmp_checksum);
- tmp_checksum = ostree_checksum_from_bytes (child_file_csum);
- ostree_mutable_tree_set_metadata_checksum (mtree, tmp_checksum);
- }
-
- g_clear_object (&child_info);
- }
-
- if (filter_result == OSTREE_REPO_COMMIT_FILTER_ALLOW)
- {
- 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 (TRUE)
- {
- GFileInfo *child_info;
- gs_unref_object GFile *child = NULL;
- gs_unref_object GFileInfo *modified_info = NULL;
- gs_unref_object OstreeMutableTree *child_mtree = NULL;
- const char *name;
-
- if (!gs_file_enumerator_iterate (dir_enum, &child_info, NULL,
- cancellable, error))
- goto out;
- if (child_info == NULL)
- break;
-
- name = g_file_info_get_name (child_info);
- g_ptr_array_add (path, (char*)name);
- filter_result = apply_commit_filter (self, modifier, path, child_info, &modified_info);
-
- if (filter_result == OSTREE_REPO_COMMIT_FILTER_ALLOW)
- {
- child = g_file_get_child (dir, name);
-
- if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY)
- {
- if (!ostree_mutable_tree_ensure_dir (mtree, name, &child_mtree, error))
- goto out;
-
- if (!stage_directory_to_mtree_internal (self, child, child_mtree,
- modifier, path,
- cancellable, error))
- goto out;
- }
- else if (repo_dir)
- {
- g_debug ("Adding: %s", gs_file_get_path_cached (child));
- if (!ostree_mutable_tree_replace_file (mtree, name,
- ostree_repo_file_get_checksum ((OstreeRepoFile*)
child),
- error))
- goto out;
- }
- else
- {
- guint64 file_obj_length;
- const char *loose_checksum;
- gs_unref_object GInputStream *file_input = NULL;
- gs_unref_variant GVariant *xattrs = NULL;
- gs_unref_object GInputStream *file_object_input = NULL;
- gs_free guchar *child_file_csum = NULL;
- gs_free char *tmp_checksum = NULL;
-
- g_debug ("Adding: %s", gs_file_get_path_cached (child));
- loose_checksum = devino_cache_lookup (self, child_info);
-
- if (loose_checksum)
- {
- if (!ostree_mutable_tree_replace_file (mtree, name, loose_checksum,
- error))
- goto out;
- }
- else
- {
- if (g_file_info_get_file_type (modified_info) == G_FILE_TYPE_REGULAR)
- {
- file_input = (GInputStream*)g_file_read (child, cancellable, error);
- if (!file_input)
- goto out;
- }
-
- if (!(modifier && (modifier->flags & OSTREE_REPO_COMMIT_MODIFIER_FLAGS_SKIP_XATTRS) >
0))
- {
- g_clear_pointer (&xattrs, (GDestroyNotify) g_variant_unref);
- if (!ostree_get_xattrs_for_file (child, &xattrs, cancellable, error))
- goto out;
- }
-
- if (!ostree_raw_file_to_content_stream (file_input,
- modified_info, xattrs,
- &file_object_input, &file_obj_length,
- cancellable, error))
- goto out;
- if (!ostree_repo_stage_content (self, NULL, file_object_input, file_obj_length,
- &child_file_csum, cancellable, error))
- goto out;
-
- g_free (tmp_checksum);
- tmp_checksum = ostree_checksum_from_bytes (child_file_csum);
- if (!ostree_mutable_tree_replace_file (mtree, name, tmp_checksum,
- error))
- goto out;
- }
- }
-
- g_ptr_array_remove_index (path, path->len - 1);
- }
- }
- }
-
- if (repo_dir && repo_dir_was_empty)
- ostree_mutable_tree_set_contents_checksum (mtree, ostree_repo_file_tree_get_content_checksum (repo_dir));
-
- ret = TRUE;
- out:
- return ret;
-}
-
-/**
- * ostree_repo_stage_directory_to_mtree:
- * @self: Repo
- * @dir: Path to a directory
- * @mtree: Overlay directory contents into this tree
- * @modifier: (allow-none): Optional modifier
- * @cancellable: Cancellable
- * @error: Error
- *
- * Store objects for @dir and all children into the repository @self,
- * overlaying the resulting filesystem hierarchy into @mtree.
- */
-gboolean
-ostree_repo_stage_directory_to_mtree (OstreeRepo *self,
- GFile *dir,
- OstreeMutableTree *mtree,
- OstreeRepoCommitModifier *modifier,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- GPtrArray *path = NULL;
-
- path = g_ptr_array_new ();
- if (!stage_directory_to_mtree_internal (self, dir, mtree, modifier, path,
- cancellable, error))
- goto out;
-
- ret = TRUE;
- out:
- if (path)
- g_ptr_array_free (path, TRUE);
- return ret;
-}
-
-/**
- * ostree_repo_stage_mtree:
- * @self: Repo
- * @mtree: Mutable tree
- * @out_contents_checksum: (out): Return location for ASCII checksum
- * @cancellable: Cancellable
- * @error: Error
- *
- * Write all metadata objects for @mtree to repo; the resulting
- * @out_contents_checksum contains the checksum for the
- * %OSTREE_OBJECT_TYPE_DIR_TREE object.
- */
-gboolean
-ostree_repo_stage_mtree (OstreeRepo *self,
- OstreeMutableTree *mtree,
- char **out_contents_checksum,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- GHashTableIter hash_iter;
- gpointer key, value;
- const char *existing_checksum;
- gs_free char *ret_contents_checksum = NULL;
- gs_unref_hashtable GHashTable *dir_metadata_checksums = NULL;
- gs_unref_hashtable GHashTable *dir_contents_checksums = NULL;
- gs_unref_variant GVariant *serialized_tree = NULL;
- gs_free guchar *contents_csum = NULL;
-
- existing_checksum = ostree_mutable_tree_get_contents_checksum (mtree);
- if (existing_checksum)
- {
- ret_contents_checksum = g_strdup (existing_checksum);
- }
- else
- {
- dir_contents_checksums = g_hash_table_new_full (g_str_hash, g_str_equal,
- (GDestroyNotify)g_free, (GDestroyNotify)g_free);
- dir_metadata_checksums = g_hash_table_new_full (g_str_hash, g_str_equal,
- (GDestroyNotify)g_free, (GDestroyNotify)g_free);
-
- g_hash_table_iter_init (&hash_iter, ostree_mutable_tree_get_subdirs (mtree));
- while (g_hash_table_iter_next (&hash_iter, &key, &value))
- {
- const char *name = key;
- const char *metadata_checksum;
- OstreeMutableTree *child_dir = value;
- char *child_dir_contents_checksum;
-
- if (!ostree_repo_stage_mtree (self, child_dir, &child_dir_contents_checksum,
- cancellable, error))
- goto out;
-
- g_assert (child_dir_contents_checksum);
- g_hash_table_replace (dir_contents_checksums, g_strdup (name),
- child_dir_contents_checksum); /* Transfer ownership */
- metadata_checksum = ostree_mutable_tree_get_metadata_checksum (child_dir);
- g_assert (metadata_checksum);
- g_hash_table_replace (dir_metadata_checksums, g_strdup (name),
- g_strdup (metadata_checksum));
- }
-
- serialized_tree = create_tree_variant_from_hashes (ostree_mutable_tree_get_files (mtree),
- dir_contents_checksums,
- dir_metadata_checksums);
-
- if (!ostree_repo_stage_metadata (self, OSTREE_OBJECT_TYPE_DIR_TREE, NULL,
- serialized_tree, &contents_csum,
- cancellable, error))
- goto out;
- ret_contents_checksum = ostree_checksum_from_bytes (contents_csum);
- }
-
- ret = TRUE;
- ot_transfer_out_value(out_contents_checksum, &ret_contents_checksum);
- out:
- return ret;
-}
-
-/**
- * ostree_repo_commit_modifier_new:
- * @flags: Control options for filter
- * @commit_filter: (allow-none): Function that can inspect individual files
- * @user_data: (allow-none): User data
- * @destroy_notify: A #GDestroyNotify
- *
- * Returns: (transfer full): A new commit modifier.
- */
-OstreeRepoCommitModifier *
-ostree_repo_commit_modifier_new (OstreeRepoCommitModifierFlags flags,
- OstreeRepoCommitFilter commit_filter,
- gpointer user_data,
- GDestroyNotify destroy_notify)
-{
- OstreeRepoCommitModifier *modifier = g_new0 (OstreeRepoCommitModifier, 1);
-
- modifier->refcount = 1;
- modifier->flags = flags;
- modifier->filter = commit_filter;
- modifier->user_data = user_data;
- modifier->destroy_notify = destroy_notify;
-
- return modifier;
-}
-
-OstreeRepoCommitModifier *
-ostree_repo_commit_modifier_ref (OstreeRepoCommitModifier *modifier)
-{
- g_atomic_int_inc (&modifier->refcount);
- return modifier;
-}
-
-void
-ostree_repo_commit_modifier_unref (OstreeRepoCommitModifier *modifier)
-{
- if (!modifier)
- return;
- if (!g_atomic_int_dec_and_test (&modifier->refcount))
- return;
-
- modifier->destroy_notify (modifier->user_data);
-
- g_free (modifier);
- return;
-}
-
-G_DEFINE_BOXED_TYPE(OstreeRepoCommitModifier, ostree_repo_commit_modifier,
- ostree_repo_commit_modifier_ref,
- ostree_repo_commit_modifier_unref);
-
-static gboolean
-list_loose_object_dir (OstreeRepo *self,
- GFile *dir,
- GHashTable *inout_objects,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- const char *dirname = NULL;
- const char *dot = NULL;
- gs_unref_object GFileEnumerator *enumerator = NULL;
- GString *checksum = NULL;
-
- dirname = gs_file_get_basename_cached (dir);
-
- /* We're only querying name */
- enumerator = g_file_enumerate_children (dir, "standard::name,standard::type",
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- cancellable,
- error);
- if (!enumerator)
- goto out;
-
- while (TRUE)
- {
- GFileInfo *file_info;
- const char *name;
- guint32 type;
- OstreeObjectType objtype;
-
- if (!gs_file_enumerator_iterate (enumerator, &file_info, NULL,
- NULL, error))
- goto out;
- if (file_info == NULL)
- break;
-
- type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
-
- if (type == G_FILE_TYPE_DIRECTORY)
- continue;
-
- name = g_file_info_get_attribute_byte_string (file_info, "standard::name");
-
- if (g_str_has_suffix (name, ".file"))
- objtype = OSTREE_OBJECT_TYPE_FILE;
- else if (g_str_has_suffix (name, ".dirtree"))
- objtype = OSTREE_OBJECT_TYPE_DIR_TREE;
- else if (g_str_has_suffix (name, ".dirmeta"))
- objtype = OSTREE_OBJECT_TYPE_DIR_META;
- else if (g_str_has_suffix (name, ".commit"))
- objtype = OSTREE_OBJECT_TYPE_COMMIT;
- else
- continue;
-
- dot = strrchr (name, '.');
- g_assert (dot);
-
- if ((dot - name) == 62)
- {
- GVariant *key, *value;
-
- if (checksum)
- g_string_free (checksum, TRUE);
- checksum = g_string_new (dirname);
- g_string_append_len (checksum, name, 62);
-
- key = ostree_object_name_serialize (checksum->str, objtype);
- value = g_variant_new ("(b as)",
- TRUE, g_variant_new_strv (NULL, 0));
- /* transfer ownership */
- g_hash_table_replace (inout_objects, key,
- g_variant_ref_sink (value));
- }
- }
-
- ret = TRUE;
- out:
- if (checksum)
- g_string_free (checksum, TRUE);
- return ret;
-}
-
static gboolean
list_loose_objects (OstreeRepo *self,
GHashTable *inout_objects,
@@ -2341,7 +738,7 @@ list_loose_objects (OstreeRepo *self,
guint i;
gs_unref_ptrarray GPtrArray *object_dirs = NULL;
- if (!get_loose_object_dirs (self, &object_dirs, cancellable, error))
+ if (!_ostree_repo_get_loose_object_dirs (self, &object_dirs, cancellable, error))
goto out;
for (i = 0; i < object_dirs->len; i++)
@@ -2374,8 +771,8 @@ load_metadata_internal (OstreeRepo *self,
g_return_val_if_fail (OSTREE_OBJECT_TYPE_IS_META (objtype), FALSE);
- if (!repo_find_object (self, objtype, sha256, &object_path,
- cancellable, error))
+ if (!_ostree_repo_find_object (self, objtype, sha256, &object_path,
+ cancellable, error))
goto out;
if (object_path != NULL)
@@ -2452,8 +849,8 @@ ostree_repo_load_file (OstreeRepo *self,
gs_unref_object GFileInfo *ret_file_info = NULL;
gs_unref_variant GVariant *ret_xattrs = NULL;
- if (!repo_find_object (self, OSTREE_OBJECT_TYPE_FILE, checksum, &loose_path,
- cancellable, error))
+ if (!_ostree_repo_find_object (self, OSTREE_OBJECT_TYPE_FILE, checksum, &loose_path,
+ cancellable, error))
goto out;
repo_mode = ostree_repo_get_mode (self);
@@ -2579,13 +976,13 @@ ostree_repo_load_object_stream (OstreeRepo *self,
return ret;
}
-static gboolean
-repo_find_object (OstreeRepo *self,
- OstreeObjectType objtype,
- const char *checksum,
- GFile **out_stored_path,
- GCancellable *cancellable,
- GError **error)
+gboolean
+_ostree_repo_find_object (OstreeRepo *self,
+ OstreeObjectType objtype,
+ const char *checksum,
+ GFile **out_stored_path,
+ GCancellable *cancellable,
+ GError **error)
{
gboolean ret = FALSE;
struct stat stbuf;
@@ -2637,8 +1034,8 @@ ostree_repo_has_object (OstreeRepo *self,
gboolean ret_have_object;
gs_unref_object GFile *loose_path = NULL;
- if (!repo_find_object (self, objtype, checksum, &loose_path,
- cancellable, error))
+ if (!_ostree_repo_find_object (self, objtype, checksum, &loose_path,
+ cancellable, error))
goto out;
ret_have_object = (loose_path != NULL);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]