[ostree] libostree: Start splitting up the monstrous ostree-repo.c
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [ostree] libostree: Start splitting up the monstrous ostree-repo.c
- Date: Wed, 10 Jul 2013 00:55:17 +0000 (UTC)
commit dbffd3101719cad640ecc8e533e75cc9d0948896
Author: Colin Walters <walters verbum org>
Date: Tue Jul 9 19:02:38 2013 -0400
libostree: Start splitting up the monstrous ostree-repo.c
Checkout now lives in its own file.
Makefile-libostree.am | 2 +
src/libostree/ostree-repo-checkout.c | 826 +++++++++++++++++++++++++++++++++
src/libostree/ostree-repo-private.h | 63 +++
src/libostree/ostree-repo.c | 841 +---------------------------------
4 files changed, 897 insertions(+), 835 deletions(-)
---
diff --git a/Makefile-libostree.am b/Makefile-libostree.am
index 5641ae9..3719b5a 100644
--- a/Makefile-libostree.am
+++ b/Makefile-libostree.am
@@ -31,7 +31,9 @@ libostree_la_SOURCES = src/libostree/ostree.h \
src/libostree/ostree-mutable-tree.c \
src/libostree/ostree-mutable-tree.h \
src/libostree/ostree-repo.c \
+ src/libostree/ostree-repo-checkout.c \
src/libostree/ostree-repo.h \
+ src/libostree/ostree-repo-private.h \
src/libostree/ostree-repo-file.c \
src/libostree/ostree-repo-file.h \
src/libostree/ostree-repo-file-enumerator.c \
diff --git a/src/libostree/ostree-repo-checkout.c b/src/libostree/ostree-repo-checkout.c
new file mode 100644
index 0000000..e40c8d5
--- /dev/null
+++ b/src/libostree/ostree-repo-checkout.c
@@ -0,0 +1,826 @@
+/* -*- 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 "ostree-repo-file.h"
+#include "ostree-repo-private.h"
+
+static gboolean
+checkout_file_from_input (GFile *file,
+ OstreeRepoCheckoutMode mode,
+ OstreeRepoCheckoutOverwriteMode overwrite_mode,
+ GFileInfo *finfo,
+ GVariant *xattrs,
+ GInputStream *input,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GError *temp_error = NULL;
+ gs_unref_object GFile *dir = NULL;
+ gs_unref_object GFile *temp_file = NULL;
+ gs_unref_object GFileInfo *temp_info = NULL;
+
+ if (mode == OSTREE_REPO_CHECKOUT_MODE_USER)
+ {
+ temp_info = g_file_info_dup (finfo);
+
+ g_file_info_set_attribute_uint32 (temp_info, "unix::uid", geteuid ());
+ g_file_info_set_attribute_uint32 (temp_info, "unix::gid", getegid ());
+
+ xattrs = NULL;
+ }
+ else
+ temp_info = g_object_ref (finfo);
+
+ if (overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES)
+ {
+ if (g_file_info_get_file_type (temp_info) == G_FILE_TYPE_DIRECTORY)
+ {
+ if (!ostree_create_file_from_input (file, temp_info,
+ xattrs, input,
+ cancellable, &temp_error))
+ {
+ if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ {
+ g_clear_error (&temp_error);
+ }
+ else
+ {
+ g_propagate_error (error, temp_error);
+ goto out;
+ }
+ }
+ }
+ else
+ {
+ dir = g_file_get_parent (file);
+ if (!ostree_create_temp_file_from_input (dir, NULL, "checkout",
+ temp_info, xattrs, input, &temp_file,
+ cancellable, error))
+ goto out;
+
+ if (g_file_info_get_file_type (temp_info) == G_FILE_TYPE_REGULAR)
+ {
+ if (!gs_file_sync_data (temp_file, cancellable, error))
+ goto out;
+ }
+
+ if (rename (gs_file_get_path_cached (temp_file), gs_file_get_path_cached (file)) < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+ }
+ }
+ else
+ {
+ if (!ostree_create_file_from_input (file, temp_info,
+ xattrs, input, cancellable, error))
+ goto out;
+
+ if (g_file_info_get_file_type (temp_info) == G_FILE_TYPE_REGULAR)
+ {
+ if (!gs_file_sync_data (file, cancellable, error))
+ goto out;
+ }
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+checkout_file_hardlink (OstreeRepo *self,
+ OstreeRepoCheckoutMode mode,
+ OstreeRepoCheckoutOverwriteMode overwrite_mode,
+ GFile *source,
+ GFile *destination,
+ int dirfd,
+ gboolean *out_was_supported,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gboolean ret_was_supported = FALSE;
+ gs_unref_object GFile *dir = NULL;
+
+ again:
+ if (dirfd != -1 &&
+ linkat (-1, gs_file_get_path_cached (source),
+ dirfd, gs_file_get_basename_cached (destination), 0) != -1)
+ ret_was_supported = TRUE;
+ else if (link (gs_file_get_path_cached (source), gs_file_get_path_cached (destination)) != -1)
+ ret_was_supported = TRUE;
+ else if (errno == EMLINK || errno == EXDEV || errno == EPERM)
+ {
+ /* EMLINK, EXDEV and EPERM shouldn't be fatal; we just can't do the
+ * optimization of hardlinking instead of copying.
+ */
+ ret_was_supported = FALSE;
+ }
+ else if (errno == EEXIST && overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES)
+ {
+ /* Idiocy, from man rename(2)
+ *
+ * "If oldpath and newpath are existing hard links referring to
+ * the same file, then rename() does nothing, and returns a
+ * success status."
+ *
+ * So we can't make this atomic.
+ */
+ (void) unlink (gs_file_get_path_cached (destination));
+ goto again;
+ ret_was_supported = TRUE;
+ }
+ else
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+
+ ret = TRUE;
+ if (out_was_supported)
+ *out_was_supported = ret_was_supported;
+ out:
+ return ret;
+}
+
+static gboolean
+find_loose_for_checkout (OstreeRepo *self,
+ const char *checksum,
+ GFile **out_loose_path,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_object GFile *path = NULL;
+ struct stat stbuf;
+
+ do
+ {
+ switch (self->mode)
+ {
+ case OSTREE_REPO_MODE_BARE:
+ path = ostree_repo_get_object_path (self, checksum, OSTREE_OBJECT_TYPE_FILE);
+ break;
+ case OSTREE_REPO_MODE_ARCHIVE:
+ path = ostree_repo_get_archive_content_path (self, checksum);
+ break;
+ case OSTREE_REPO_MODE_ARCHIVE_Z2:
+ {
+ if (self->enable_uncompressed_cache)
+ path = _ostree_repo_get_uncompressed_object_cache_path (self, checksum);
+ else
+ path = NULL;
+ }
+ break;
+ }
+
+ if (!path)
+ {
+ self = self->parent_repo;
+ continue;
+ }
+
+ if (lstat (gs_file_get_path_cached (path), &stbuf) < 0)
+ {
+ if (errno != ENOENT)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+ self = self->parent_repo;
+ }
+ else if (S_ISLNK (stbuf.st_mode))
+ {
+ /* Don't check out symbolic links via hardlink; it's very easy
+ * to hit the maximum number of hardlinks to an inode this way,
+ * especially since right now we have a lot of symbolic links to
+ * busybox.
+ *
+ * fs/ext4/ext4.h:#define EXT4_LINK_MAX 65000
+ */
+ self = self->parent_repo;
+ }
+ else
+ break;
+
+ g_clear_object (&path);
+ } while (self != NULL);
+
+ ret = TRUE;
+ ot_transfer_out_value (out_loose_path, &path);
+ out:
+ return ret;
+}
+
+typedef struct {
+ OstreeRepo *repo;
+ OstreeRepoCheckoutMode mode;
+ OstreeRepoCheckoutOverwriteMode overwrite_mode;
+ GFile *destination;
+ int dirfd;
+ OstreeRepoFile *source;
+ GFileInfo *source_info;
+ GCancellable *cancellable;
+
+ gboolean caught_error;
+ GError *error;
+
+ GSimpleAsyncResult *result;
+} CheckoutOneFileAsyncData;
+
+static void
+checkout_file_async_data_free (gpointer data)
+{
+ CheckoutOneFileAsyncData *checkout_data = data;
+
+ g_clear_object (&checkout_data->repo);
+ g_clear_object (&checkout_data->destination);
+ g_clear_object (&checkout_data->source);
+ g_clear_object (&checkout_data->source_info);
+ g_clear_object (&checkout_data->cancellable);
+ g_free (checkout_data);
+}
+
+static void
+checkout_file_thread (GSimpleAsyncResult *result,
+ GObject *src,
+ GCancellable *cancellable)
+{
+ const char *checksum;
+ OstreeRepo *repo;
+ gboolean is_symlink;
+ gboolean hardlink_supported;
+ GError *local_error = NULL;
+ GError **error = &local_error;
+ gs_unref_object GFile *loose_path = NULL;
+ gs_unref_object GInputStream *input = NULL;
+ gs_unref_variant GVariant *xattrs = NULL;
+ CheckoutOneFileAsyncData *checkout_data;
+
+ checkout_data = g_simple_async_result_get_op_res_gpointer (result);
+ repo = checkout_data->repo;
+
+ /* Hack to avoid trying to create device files as a user */
+ if (checkout_data->mode == OSTREE_REPO_CHECKOUT_MODE_USER
+ && g_file_info_get_file_type (checkout_data->source_info) == G_FILE_TYPE_SPECIAL)
+ goto out;
+
+ is_symlink = g_file_info_get_file_type (checkout_data->source_info) == G_FILE_TYPE_SYMBOLIC_LINK;
+
+ checksum = ostree_repo_file_get_checksum ((OstreeRepoFile*)checkout_data->source);
+
+ /* We can only do hardlinks in these scenarios */
+ if (!is_symlink &&
+ ((checkout_data->repo->mode == OSTREE_REPO_MODE_BARE && checkout_data->mode ==
OSTREE_REPO_CHECKOUT_MODE_NONE)
+ || (checkout_data->repo->mode == OSTREE_REPO_MODE_ARCHIVE && checkout_data->mode ==
OSTREE_REPO_CHECKOUT_MODE_USER)
+ || (checkout_data->repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2 && checkout_data->mode ==
OSTREE_REPO_CHECKOUT_MODE_USER)))
+ {
+ if (!find_loose_for_checkout (checkout_data->repo, checksum, &loose_path,
+ cancellable, error))
+ goto out;
+ }
+ /* Also, if we're archive-z and we didn't find an object, uncompress it now,
+ * stick it in the cache, and then hardlink to that.
+ */
+ if (!is_symlink
+ && loose_path == NULL
+ && repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2
+ && checkout_data->mode == OSTREE_REPO_CHECKOUT_MODE_USER
+ && repo->enable_uncompressed_cache)
+ {
+ gs_unref_object GFile *objdir = NULL;
+
+ loose_path = _ostree_repo_get_uncompressed_object_cache_path (repo, checksum);
+ if (!ostree_repo_load_file (repo, checksum, &input, NULL, &xattrs,
+ cancellable, error))
+ goto out;
+
+ objdir = g_file_get_parent (loose_path);
+ if (!gs_file_ensure_directory (objdir, TRUE, cancellable, error))
+ {
+ g_prefix_error (error, "Creating cache directory %s: ",
+ gs_file_get_path_cached (objdir));
+ goto out;
+ }
+
+ /* Use UNION_FILES to make this last-one-wins thread behavior
+ * for now; we lose deduplication potentially, but oh well
+ */
+ if (!checkout_file_from_input (loose_path,
+ OSTREE_REPO_CHECKOUT_MODE_USER,
+ OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES,
+ checkout_data->source_info, xattrs,
+ input, cancellable, error))
+ {
+ g_prefix_error (error, "Unpacking loose object %s: ", checksum);
+ goto out;
+ }
+
+ /* Store the 2-byte objdir prefix (e.g. e3) in a set. The basic
+ * idea here is that if we had to unpack an object, it's very
+ * likely we're replacing some other object, so we may need a GC.
+ *
+ * This model ensures that we do work roughly proportional to
+ * the size of the changes. For example, we don't scan any
+ * directories if we didn't modify anything, meaning you can
+ * checkout the same tree multiple times very quickly.
+ *
+ * This is also scale independent; we don't hardcode e.g. looking
+ * at 1000 objects.
+ *
+ * The downside is that if we're unlucky, we may not free
+ * an object for quite some time.
+ */
+ g_mutex_lock (&repo->cache_lock);
+ {
+ gpointer key = GUINT_TO_POINTER ((g_ascii_xdigit_value (checksum[0]) << 4) +
+ g_ascii_xdigit_value (checksum[1]));
+ if (repo->updated_uncompressed_dirs == NULL)
+ repo->updated_uncompressed_dirs = g_hash_table_new (NULL, NULL);
+ g_hash_table_insert (repo->updated_uncompressed_dirs, key, key);
+ }
+ g_mutex_unlock (&repo->cache_lock);
+ }
+
+ if (loose_path)
+ {
+ /* If we found one, try hardlinking */
+ if (!checkout_file_hardlink (checkout_data->repo, checkout_data->mode,
+ checkout_data->overwrite_mode, loose_path,
+ checkout_data->destination, checkout_data->dirfd,
+ &hardlink_supported, cancellable, error))
+ {
+ g_prefix_error (error, "Hardlinking loose object %s to %s: ", checksum,
+ gs_file_get_path_cached (checkout_data->destination));
+ goto out;
+ }
+ }
+
+ /* Fall back to copy if there's no loose object, or we couldn't hardlink */
+ if (loose_path == NULL || !hardlink_supported)
+ {
+ if (!ostree_repo_load_file (checkout_data->repo, checksum, &input, NULL, &xattrs,
+ cancellable, error))
+ goto out;
+
+ if (!checkout_file_from_input (checkout_data->destination,
+ checkout_data->mode,
+ checkout_data->overwrite_mode,
+ checkout_data->source_info, xattrs,
+ input, cancellable, error))
+ {
+ g_prefix_error (error, "Copying object %s to %s: ", checksum,
+ gs_file_get_path_cached (checkout_data->destination));
+ goto out;
+ }
+ }
+
+ out:
+ if (local_error)
+ g_simple_async_result_take_error (result, local_error);
+}
+
+static void
+checkout_one_file_async (OstreeRepo *self,
+ OstreeRepoCheckoutMode mode,
+ OstreeRepoCheckoutOverwriteMode overwrite_mode,
+ OstreeRepoFile *source,
+ GFileInfo *source_info,
+ GFile *destination,
+ int dirfd,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ CheckoutOneFileAsyncData *checkout_data;
+
+ checkout_data = g_new0 (CheckoutOneFileAsyncData, 1);
+ checkout_data->repo = g_object_ref (self);
+ checkout_data->mode = mode;
+ checkout_data->overwrite_mode = overwrite_mode;
+ checkout_data->destination = g_object_ref (destination);
+ checkout_data->dirfd = dirfd;
+ checkout_data->source = g_object_ref (source);
+ checkout_data->source_info = g_object_ref (source_info);
+ checkout_data->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+
+ checkout_data->result = g_simple_async_result_new ((GObject*) self,
+ callback, user_data,
+ checkout_one_file_async);
+
+ g_simple_async_result_set_op_res_gpointer (checkout_data->result, checkout_data,
+ checkout_file_async_data_free);
+
+ g_simple_async_result_run_in_thread (checkout_data->result,
+ checkout_file_thread, G_PRIORITY_DEFAULT,
+ cancellable);
+ g_object_unref (checkout_data->result);
+}
+
+static gboolean
+checkout_one_file_finish (OstreeRepo *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result, (GObject*)self, checkout_one_file_async),
FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+ return TRUE;
+}
+
+typedef struct {
+ OstreeRepo *repo;
+ OstreeRepoCheckoutMode mode;
+ OstreeRepoCheckoutOverwriteMode overwrite_mode;
+ GFile *destination;
+ OstreeRepoFile *source;
+ GFileInfo *source_info;
+ GCancellable *cancellable;
+
+ gboolean caught_error;
+ GError *error;
+
+ DIR *dir_handle;
+
+ gboolean dir_enumeration_complete;
+ guint pending_ops;
+ guint pending_file_ops;
+ GPtrArray *pending_dirs;
+ GMainLoop *loop;
+ GSimpleAsyncResult *result;
+} CheckoutTreeAsyncData;
+
+static void
+checkout_tree_async_data_free (gpointer data)
+{
+ CheckoutTreeAsyncData *checkout_data = data;
+
+ g_clear_object (&checkout_data->repo);
+ g_clear_object (&checkout_data->destination);
+ g_clear_object (&checkout_data->source);
+ g_clear_object (&checkout_data->source_info);
+ g_clear_object (&checkout_data->cancellable);
+ if (checkout_data->pending_dirs)
+ g_ptr_array_unref (checkout_data->pending_dirs);
+ if (checkout_data->dir_handle)
+ (void) closedir (checkout_data->dir_handle);
+ g_free (checkout_data);
+}
+
+static void
+on_tree_async_child_op_complete (CheckoutTreeAsyncData *data,
+ GError *local_error)
+{
+ data->pending_ops--;
+
+ if (local_error)
+ {
+ if (!data->caught_error)
+ {
+ data->caught_error = TRUE;
+ g_propagate_error (&data->error, local_error);
+ }
+ else
+ g_clear_error (&local_error);
+ }
+
+ if (data->pending_ops != 0)
+ return;
+
+ if (data->caught_error)
+ g_simple_async_result_take_error (data->result, data->error);
+ g_simple_async_result_complete_in_idle (data->result);
+ g_object_unref (data->result);
+}
+
+static void
+on_one_subdir_checked_out (GObject *src,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CheckoutTreeAsyncData *data = user_data;
+ GError *local_error = NULL;
+
+ if (!ostree_repo_checkout_tree_finish ((OstreeRepo*) src, result, &local_error))
+ goto out;
+
+ out:
+ on_tree_async_child_op_complete (data, local_error);
+}
+
+static void
+process_pending_dirs (CheckoutTreeAsyncData *data)
+{
+ guint i;
+
+ g_assert (data->dir_enumeration_complete);
+ g_assert (data->pending_file_ops == 0);
+
+ /* Don't hold a FD open while we're processing
+ * recursive calls, otherwise we can pretty easily
+ * hit the max of 1024 fds =(
+ */
+ if (data->dir_handle)
+ {
+ (void) closedir (data->dir_handle);
+ data->dir_handle = NULL;
+ }
+
+ if (data->pending_dirs != NULL)
+ {
+ for (i = 0; i < data->pending_dirs->len; i++)
+ {
+ GFileInfo *file_info = data->pending_dirs->pdata[i];
+ const char *name;
+ gs_unref_object GFile *dest_path = NULL;
+ gs_unref_object GFile *src_child = NULL;
+
+ name = g_file_info_get_attribute_byte_string (file_info, "standard::name");
+
+ dest_path = g_file_get_child (data->destination, name);
+ src_child = g_file_get_child ((GFile*)data->source, name);
+
+ ostree_repo_checkout_tree_async (data->repo,
+ data->mode,
+ data->overwrite_mode,
+ dest_path, (OstreeRepoFile*)src_child, file_info,
+ data->cancellable,
+ on_one_subdir_checked_out,
+ data);
+ data->pending_ops++;
+ }
+ g_ptr_array_set_size (data->pending_dirs, 0);
+ on_tree_async_child_op_complete (data, NULL);
+ }
+}
+
+static void
+on_one_file_checked_out (GObject *src,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CheckoutTreeAsyncData *data = user_data;
+ GError *local_error = NULL;
+
+ if (!checkout_one_file_finish ((OstreeRepo*) src, result, &local_error))
+ goto out;
+
+ out:
+ data->pending_file_ops--;
+ if (data->dir_enumeration_complete && data->pending_file_ops == 0)
+ process_pending_dirs (data);
+ on_tree_async_child_op_complete (data, local_error);
+}
+
+static void
+on_got_next_files (GObject *src,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CheckoutTreeAsyncData *data = user_data;
+ GError *local_error = NULL;
+ GList *files = NULL;
+ GList *iter = NULL;
+
+ files = g_file_enumerator_next_files_finish ((GFileEnumerator*) src, result, &local_error);
+ if (local_error)
+ goto out;
+
+ if (!files)
+ data->dir_enumeration_complete = TRUE;
+ else
+ {
+ g_file_enumerator_next_files_async ((GFileEnumerator*)src, 50, G_PRIORITY_DEFAULT,
+ data->cancellable,
+ on_got_next_files, data);
+ data->pending_ops++;
+ }
+
+ if (data->dir_enumeration_complete && data->pending_file_ops == 0)
+ process_pending_dirs (data);
+
+ for (iter = files; iter; iter = iter->next)
+ {
+ GFileInfo *file_info = iter->data;
+ const char *name;
+ guint32 type;
+
+ name = g_file_info_get_attribute_byte_string (file_info, "standard::name");
+ type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
+
+ if (type != G_FILE_TYPE_DIRECTORY)
+ {
+ gs_unref_object GFile *dest_path = NULL;
+ gs_unref_object GFile *src_child = NULL;
+
+ dest_path = g_file_get_child (data->destination, name);
+ src_child = g_file_get_child ((GFile*)data->source, name);
+
+ checkout_one_file_async (data->repo, data->mode,
+ data->overwrite_mode,
+ (OstreeRepoFile*)src_child, file_info,
+ dest_path, dirfd(data->dir_handle),
+ data->cancellable, on_one_file_checked_out,
+ data);
+ data->pending_file_ops++;
+ data->pending_ops++;
+ }
+ else
+ {
+ if (data->pending_dirs == NULL)
+ {
+ data->pending_dirs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+ data->pending_ops++;
+ }
+ g_ptr_array_add (data->pending_dirs, g_object_ref (file_info));
+ }
+ g_object_unref (file_info);
+ }
+
+ g_list_free (files);
+
+ out:
+ on_tree_async_child_op_complete (data, local_error);
+}
+
+void
+ostree_repo_checkout_tree_async (OstreeRepo *self,
+ OstreeRepoCheckoutMode mode,
+ OstreeRepoCheckoutOverwriteMode overwrite_mode,
+ GFile *destination,
+ OstreeRepoFile *source,
+ GFileInfo *source_info,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ CheckoutTreeAsyncData *checkout_data;
+ gs_unref_object GFileInfo *file_info = NULL;
+ gs_unref_variant GVariant *xattrs = NULL;
+ gs_unref_object GFileEnumerator *dir_enum = NULL;
+ GError *local_error = NULL;
+ GError **error = &local_error;
+
+ checkout_data = g_new0 (CheckoutTreeAsyncData, 1);
+ checkout_data->repo = g_object_ref (self);
+ checkout_data->mode = mode;
+ checkout_data->overwrite_mode = overwrite_mode;
+ checkout_data->destination = g_object_ref (destination);
+ checkout_data->source = g_object_ref (source);
+ checkout_data->source_info = g_object_ref (source_info);
+ checkout_data->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+ checkout_data->pending_ops++; /* Count this function */
+
+ checkout_data->result = g_simple_async_result_new ((GObject*) self,
+ callback, user_data,
+ ostree_repo_checkout_tree_async);
+
+ g_simple_async_result_set_op_res_gpointer (checkout_data->result, checkout_data,
+ checkout_tree_async_data_free);
+
+ if (!ostree_repo_file_get_xattrs (checkout_data->source, &xattrs, NULL, error))
+ goto out;
+
+ if (!checkout_file_from_input (checkout_data->destination,
+ checkout_data->mode,
+ checkout_data->overwrite_mode,
+ checkout_data->source_info,
+ xattrs, NULL,
+ cancellable, error))
+ goto out;
+
+ checkout_data->dir_handle = opendir (gs_file_get_path_cached (checkout_data->destination));
+ if (!checkout_data->dir_handle)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+
+ g_clear_pointer (&xattrs, (GDestroyNotify) g_variant_unref);
+
+ dir_enum = g_file_enumerate_children ((GFile*)checkout_data->source,
+ OSTREE_GIO_FAST_QUERYINFO,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable,
+ error);
+ if (!dir_enum)
+ goto out;
+
+ g_file_enumerator_next_files_async (dir_enum, 50, G_PRIORITY_DEFAULT, cancellable,
+ on_got_next_files, checkout_data);
+ checkout_data->pending_ops++;
+
+ out:
+ on_tree_async_child_op_complete (checkout_data, local_error);
+}
+
+gboolean
+ostree_repo_checkout_tree_finish (OstreeRepo *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result, (GObject*)self,
ostree_repo_checkout_tree_async), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+ return TRUE;
+}
+
+/**
+ * ostree_repo_checkout_gc:
+ *
+ * Call this after finishing a succession of checkout operations; it
+ * will delete any currently-unused uncompressed objects from the
+ * cache.
+ */
+gboolean
+ostree_repo_checkout_gc (OstreeRepo *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_hashtable GHashTable *to_clean_dirs = NULL;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_mutex_lock (&self->cache_lock);
+ to_clean_dirs = self->updated_uncompressed_dirs;
+ self->updated_uncompressed_dirs = g_hash_table_new (NULL, NULL);
+ g_mutex_unlock (&self->cache_lock);
+
+ if (to_clean_dirs)
+ g_hash_table_iter_init (&iter, to_clean_dirs);
+ while (to_clean_dirs && g_hash_table_iter_next (&iter, &key, &value))
+ {
+ gs_unref_object GFile *objdir = NULL;
+ gs_unref_object GFileEnumerator *enumerator = NULL;
+ gs_free char *objdir_name = NULL;
+
+ objdir_name = g_strdup_printf ("%02x", GPOINTER_TO_UINT (key));
+ objdir = ot_gfile_get_child_build_path (self->uncompressed_objects_dir, "objects",
+ objdir_name, NULL);
+
+ enumerator = g_file_enumerate_children (objdir,
"standard::name,standard::type,unix::inode,unix::nlink",
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable,
+ error);
+ if (!enumerator)
+ goto out;
+
+ while (TRUE)
+ {
+ GFileInfo *file_info;
+ guint32 nlinks;
+
+ if (!gs_file_enumerator_iterate (enumerator, &file_info, NULL,
+ cancellable, error))
+ goto out;
+ if (file_info == NULL)
+ break;
+
+ nlinks = g_file_info_get_attribute_uint32 (file_info, "unix::nlink");
+ if (nlinks == 1)
+ {
+ gs_unref_object GFile *objpath = NULL;
+ objpath = g_file_get_child (objdir, g_file_info_get_name (file_info));
+ if (!gs_file_unlink (objpath, cancellable, error))
+ goto out;
+ }
+ }
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h
new file mode 100644
index 0000000..3e88692
--- /dev/null
+++ b/src/libostree/ostree-repo-private.h
@@ -0,0 +1,63 @@
+/* -*- 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.
+ */
+
+#pragma once
+
+#include "ostree-repo.h"
+
+G_BEGIN_DECLS
+
+struct OstreeRepo {
+ GObject parent;
+
+ GFile *repodir;
+ GFile *tmp_dir;
+ GFile *pending_dir;
+ GFile *local_heads_dir;
+ GFile *remote_heads_dir;
+ GFile *objects_dir;
+ GFile *uncompressed_objects_dir;
+ GFile *remote_cache_dir;
+ GFile *config_file;
+
+ GFile *transaction_lock_path;
+
+ GMutex cache_lock;
+ GPtrArray *cached_meta_indexes;
+ GPtrArray *cached_content_indexes;
+
+ gboolean inited;
+ gboolean in_transaction;
+ GHashTable *loose_object_devino_hash;
+ GHashTable *updated_uncompressed_dirs;
+
+ GKeyFile *config;
+ OstreeRepoMode mode;
+ gboolean enable_uncompressed_cache;
+
+ OstreeRepo *parent_repo;
+};
+
+GFile *
+_ostree_repo_get_uncompressed_object_cache_path (OstreeRepo *self,
+ const char *checksum);
+
+G_END_DECLS
+
diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c
index 016d361..3b94951 100644
--- a/src/libostree/ostree-repo.c
+++ b/src/libostree/ostree-repo.c
@@ -24,7 +24,9 @@
#include "config.h"
-#include "ostree.h"
+#include "ostree-repo-private.h"
+#include "ostree-mutable-tree.h"
+#include "ostree-checksum-input-stream.h"
#include "otutil.h"
#include "ostree-repo-file-enumerator.h"
@@ -41,37 +43,6 @@
#include "ostree-libarchive-input-stream.h"
#endif
-struct OstreeRepo {
- GObject parent;
-
- GFile *repodir;
- GFile *tmp_dir;
- GFile *pending_dir;
- GFile *local_heads_dir;
- GFile *remote_heads_dir;
- GFile *objects_dir;
- GFile *uncompressed_objects_dir;
- GFile *remote_cache_dir;
- GFile *config_file;
-
- GFile *transaction_lock_path;
-
- GMutex cache_lock;
- GPtrArray *cached_meta_indexes;
- GPtrArray *cached_content_indexes;
-
- gboolean inited;
- gboolean in_transaction;
- GHashTable *loose_object_devino_hash;
- GHashTable *updated_uncompressed_dirs;
-
- GKeyFile *config;
- OstreeRepoMode mode;
- gboolean enable_uncompressed_cache;
-
- OstreeRepo *parent_repo;
-};
-
typedef struct {
GObjectClass parent_class;
} OstreeRepoClass;
@@ -1627,9 +1598,9 @@ ostree_repo_get_object_path (OstreeRepo *self,
return ret;
}
-static GFile *
-get_uncompressed_object_cache_path (OstreeRepo *self,
- const char *checksum)
+GFile *
+_ostree_repo_get_uncompressed_object_cache_path (OstreeRepo *self,
+ const char *checksum)
{
char *relpath;
GFile *ret;
@@ -3309,806 +3280,6 @@ ostree_repo_list_objects (OstreeRepo *self,
return ret;
}
-static gboolean
-checkout_file_from_input (GFile *file,
- OstreeRepoCheckoutMode mode,
- OstreeRepoCheckoutOverwriteMode overwrite_mode,
- GFileInfo *finfo,
- GVariant *xattrs,
- GInputStream *input,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- GError *temp_error = NULL;
- gs_unref_object GFile *dir = NULL;
- gs_unref_object GFile *temp_file = NULL;
- gs_unref_object GFileInfo *temp_info = NULL;
-
- if (mode == OSTREE_REPO_CHECKOUT_MODE_USER)
- {
- temp_info = g_file_info_dup (finfo);
-
- g_file_info_set_attribute_uint32 (temp_info, "unix::uid", geteuid ());
- g_file_info_set_attribute_uint32 (temp_info, "unix::gid", getegid ());
-
- xattrs = NULL;
- }
- else
- temp_info = g_object_ref (finfo);
-
- if (overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES)
- {
- if (g_file_info_get_file_type (temp_info) == G_FILE_TYPE_DIRECTORY)
- {
- if (!ostree_create_file_from_input (file, temp_info,
- xattrs, input,
- cancellable, &temp_error))
- {
- if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
- {
- g_clear_error (&temp_error);
- }
- else
- {
- g_propagate_error (error, temp_error);
- goto out;
- }
- }
- }
- else
- {
- dir = g_file_get_parent (file);
- if (!ostree_create_temp_file_from_input (dir, NULL, "checkout",
- temp_info, xattrs, input, &temp_file,
- cancellable, error))
- goto out;
-
- if (g_file_info_get_file_type (temp_info) == G_FILE_TYPE_REGULAR)
- {
- if (!gs_file_sync_data (temp_file, cancellable, error))
- goto out;
- }
-
- if (rename (gs_file_get_path_cached (temp_file), gs_file_get_path_cached (file)) < 0)
- {
- ot_util_set_error_from_errno (error, errno);
- goto out;
- }
- }
- }
- else
- {
- if (!ostree_create_file_from_input (file, temp_info,
- xattrs, input, cancellable, error))
- goto out;
-
- if (g_file_info_get_file_type (temp_info) == G_FILE_TYPE_REGULAR)
- {
- if (!gs_file_sync_data (file, cancellable, error))
- goto out;
- }
- }
-
- ret = TRUE;
- out:
- return ret;
-}
-
-static gboolean
-checkout_file_hardlink (OstreeRepo *self,
- OstreeRepoCheckoutMode mode,
- OstreeRepoCheckoutOverwriteMode overwrite_mode,
- GFile *source,
- GFile *destination,
- int dirfd,
- gboolean *out_was_supported,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- gboolean ret_was_supported = FALSE;
- gs_unref_object GFile *dir = NULL;
-
- again:
- if (dirfd != -1 &&
- linkat (-1, gs_file_get_path_cached (source),
- dirfd, gs_file_get_basename_cached (destination), 0) != -1)
- ret_was_supported = TRUE;
- else if (link (gs_file_get_path_cached (source), gs_file_get_path_cached (destination)) != -1)
- ret_was_supported = TRUE;
- else if (errno == EMLINK || errno == EXDEV || errno == EPERM)
- {
- /* EMLINK, EXDEV and EPERM shouldn't be fatal; we just can't do the
- * optimization of hardlinking instead of copying.
- */
- ret_was_supported = FALSE;
- }
- else if (errno == EEXIST && overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES)
- {
- /* Idiocy, from man rename(2)
- *
- * "If oldpath and newpath are existing hard links referring to
- * the same file, then rename() does nothing, and returns a
- * success status."
- *
- * So we can't make this atomic.
- */
- (void) unlink (gs_file_get_path_cached (destination));
- goto again;
- ret_was_supported = TRUE;
- }
- else
- {
- ot_util_set_error_from_errno (error, errno);
- goto out;
- }
-
- ret = TRUE;
- if (out_was_supported)
- *out_was_supported = ret_was_supported;
- out:
- return ret;
-}
-
-static gboolean
-find_loose_for_checkout (OstreeRepo *self,
- const char *checksum,
- GFile **out_loose_path,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- gs_unref_object GFile *path = NULL;
- struct stat stbuf;
-
- do
- {
- switch (self->mode)
- {
- case OSTREE_REPO_MODE_BARE:
- path = ostree_repo_get_object_path (self, checksum, OSTREE_OBJECT_TYPE_FILE);
- break;
- case OSTREE_REPO_MODE_ARCHIVE:
- path = ostree_repo_get_archive_content_path (self, checksum);
- break;
- case OSTREE_REPO_MODE_ARCHIVE_Z2:
- {
- if (self->enable_uncompressed_cache)
- path = get_uncompressed_object_cache_path (self, checksum);
- else
- path = NULL;
- }
- break;
- }
-
- if (!path)
- {
- self = self->parent_repo;
- continue;
- }
-
- if (lstat (gs_file_get_path_cached (path), &stbuf) < 0)
- {
- if (errno != ENOENT)
- {
- ot_util_set_error_from_errno (error, errno);
- goto out;
- }
- self = self->parent_repo;
- }
- else if (S_ISLNK (stbuf.st_mode))
- {
- /* Don't check out symbolic links via hardlink; it's very easy
- * to hit the maximum number of hardlinks to an inode this way,
- * especially since right now we have a lot of symbolic links to
- * busybox.
- *
- * fs/ext4/ext4.h:#define EXT4_LINK_MAX 65000
- */
- self = self->parent_repo;
- }
- else
- break;
-
- g_clear_object (&path);
- } while (self != NULL);
-
- ret = TRUE;
- ot_transfer_out_value (out_loose_path, &path);
- out:
- return ret;
-}
-
-typedef struct {
- OstreeRepo *repo;
- OstreeRepoCheckoutMode mode;
- OstreeRepoCheckoutOverwriteMode overwrite_mode;
- GFile *destination;
- int dirfd;
- OstreeRepoFile *source;
- GFileInfo *source_info;
- GCancellable *cancellable;
-
- gboolean caught_error;
- GError *error;
-
- GSimpleAsyncResult *result;
-} CheckoutOneFileAsyncData;
-
-static void
-checkout_file_async_data_free (gpointer data)
-{
- CheckoutOneFileAsyncData *checkout_data = data;
-
- g_clear_object (&checkout_data->repo);
- g_clear_object (&checkout_data->destination);
- g_clear_object (&checkout_data->source);
- g_clear_object (&checkout_data->source_info);
- g_clear_object (&checkout_data->cancellable);
- g_free (checkout_data);
-}
-
-static void
-checkout_file_thread (GSimpleAsyncResult *result,
- GObject *src,
- GCancellable *cancellable)
-{
- const char *checksum;
- OstreeRepo *repo;
- gboolean is_symlink;
- gboolean hardlink_supported;
- GError *local_error = NULL;
- GError **error = &local_error;
- gs_unref_object GFile *loose_path = NULL;
- gs_unref_object GInputStream *input = NULL;
- gs_unref_variant GVariant *xattrs = NULL;
- CheckoutOneFileAsyncData *checkout_data;
-
- checkout_data = g_simple_async_result_get_op_res_gpointer (result);
- repo = checkout_data->repo;
-
- /* Hack to avoid trying to create device files as a user */
- if (checkout_data->mode == OSTREE_REPO_CHECKOUT_MODE_USER
- && g_file_info_get_file_type (checkout_data->source_info) == G_FILE_TYPE_SPECIAL)
- goto out;
-
- is_symlink = g_file_info_get_file_type (checkout_data->source_info) == G_FILE_TYPE_SYMBOLIC_LINK;
-
- checksum = ostree_repo_file_get_checksum ((OstreeRepoFile*)checkout_data->source);
-
- /* We can only do hardlinks in these scenarios */
- if (!is_symlink &&
- ((checkout_data->repo->mode == OSTREE_REPO_MODE_BARE && checkout_data->mode ==
OSTREE_REPO_CHECKOUT_MODE_NONE)
- || (checkout_data->repo->mode == OSTREE_REPO_MODE_ARCHIVE && checkout_data->mode ==
OSTREE_REPO_CHECKOUT_MODE_USER)
- || (checkout_data->repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2 && checkout_data->mode ==
OSTREE_REPO_CHECKOUT_MODE_USER)))
- {
- if (!find_loose_for_checkout (checkout_data->repo, checksum, &loose_path,
- cancellable, error))
- goto out;
- }
- /* Also, if we're archive-z and we didn't find an object, uncompress it now,
- * stick it in the cache, and then hardlink to that.
- */
- if (!is_symlink
- && loose_path == NULL
- && repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2
- && checkout_data->mode == OSTREE_REPO_CHECKOUT_MODE_USER
- && repo->enable_uncompressed_cache)
- {
- gs_unref_object GFile *objdir = NULL;
-
- loose_path = get_uncompressed_object_cache_path (repo, checksum);
- if (!ostree_repo_load_file (repo, checksum, &input, NULL, &xattrs,
- cancellable, error))
- goto out;
-
- objdir = g_file_get_parent (loose_path);
- if (!gs_file_ensure_directory (objdir, TRUE, cancellable, error))
- {
- g_prefix_error (error, "Creating cache directory %s: ",
- gs_file_get_path_cached (objdir));
- goto out;
- }
-
- /* Use UNION_FILES to make this last-one-wins thread behavior
- * for now; we lose deduplication potentially, but oh well
- */
- if (!checkout_file_from_input (loose_path,
- OSTREE_REPO_CHECKOUT_MODE_USER,
- OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES,
- checkout_data->source_info, xattrs,
- input, cancellable, error))
- {
- g_prefix_error (error, "Unpacking loose object %s: ", checksum);
- goto out;
- }
-
- /* Store the 2-byte objdir prefix (e.g. e3) in a set. The basic
- * idea here is that if we had to unpack an object, it's very
- * likely we're replacing some other object, so we may need a GC.
- *
- * This model ensures that we do work roughly proportional to
- * the size of the changes. For example, we don't scan any
- * directories if we didn't modify anything, meaning you can
- * checkout the same tree multiple times very quickly.
- *
- * This is also scale independent; we don't hardcode e.g. looking
- * at 1000 objects.
- *
- * The downside is that if we're unlucky, we may not free
- * an object for quite some time.
- */
- g_mutex_lock (&repo->cache_lock);
- {
- gpointer key = GUINT_TO_POINTER ((g_ascii_xdigit_value (checksum[0]) << 4) +
- g_ascii_xdigit_value (checksum[1]));
- if (repo->updated_uncompressed_dirs == NULL)
- repo->updated_uncompressed_dirs = g_hash_table_new (NULL, NULL);
- g_hash_table_insert (repo->updated_uncompressed_dirs, key, key);
- }
- g_mutex_unlock (&repo->cache_lock);
- }
-
- if (loose_path)
- {
- /* If we found one, try hardlinking */
- if (!checkout_file_hardlink (checkout_data->repo, checkout_data->mode,
- checkout_data->overwrite_mode, loose_path,
- checkout_data->destination, checkout_data->dirfd,
- &hardlink_supported, cancellable, error))
- {
- g_prefix_error (error, "Hardlinking loose object %s to %s: ", checksum,
- gs_file_get_path_cached (checkout_data->destination));
- goto out;
- }
- }
-
- /* Fall back to copy if there's no loose object, or we couldn't hardlink */
- if (loose_path == NULL || !hardlink_supported)
- {
- if (!ostree_repo_load_file (checkout_data->repo, checksum, &input, NULL, &xattrs,
- cancellable, error))
- goto out;
-
- if (!checkout_file_from_input (checkout_data->destination,
- checkout_data->mode,
- checkout_data->overwrite_mode,
- checkout_data->source_info, xattrs,
- input, cancellable, error))
- {
- g_prefix_error (error, "Copying object %s to %s: ", checksum,
- gs_file_get_path_cached (checkout_data->destination));
- goto out;
- }
- }
-
- out:
- if (local_error)
- g_simple_async_result_take_error (result, local_error);
-}
-
-static void
-checkout_one_file_async (OstreeRepo *self,
- OstreeRepoCheckoutMode mode,
- OstreeRepoCheckoutOverwriteMode overwrite_mode,
- OstreeRepoFile *source,
- GFileInfo *source_info,
- GFile *destination,
- int dirfd,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- CheckoutOneFileAsyncData *checkout_data;
-
- checkout_data = g_new0 (CheckoutOneFileAsyncData, 1);
- checkout_data->repo = g_object_ref (self);
- checkout_data->mode = mode;
- checkout_data->overwrite_mode = overwrite_mode;
- checkout_data->destination = g_object_ref (destination);
- checkout_data->dirfd = dirfd;
- checkout_data->source = g_object_ref (source);
- checkout_data->source_info = g_object_ref (source_info);
- checkout_data->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
-
- checkout_data->result = g_simple_async_result_new ((GObject*) self,
- callback, user_data,
- checkout_one_file_async);
-
- g_simple_async_result_set_op_res_gpointer (checkout_data->result, checkout_data,
- checkout_file_async_data_free);
-
- g_simple_async_result_run_in_thread (checkout_data->result,
- checkout_file_thread, G_PRIORITY_DEFAULT,
- cancellable);
- g_object_unref (checkout_data->result);
-}
-
-static gboolean
-checkout_one_file_finish (OstreeRepo *self,
- GAsyncResult *result,
- GError **error)
-{
- GSimpleAsyncResult *simple;
-
- g_return_val_if_fail (g_simple_async_result_is_valid (result, (GObject*)self, checkout_one_file_async),
FALSE);
-
- simple = G_SIMPLE_ASYNC_RESULT (result);
- if (g_simple_async_result_propagate_error (simple, error))
- return FALSE;
- return TRUE;
-}
-
-typedef struct {
- OstreeRepo *repo;
- OstreeRepoCheckoutMode mode;
- OstreeRepoCheckoutOverwriteMode overwrite_mode;
- GFile *destination;
- OstreeRepoFile *source;
- GFileInfo *source_info;
- GCancellable *cancellable;
-
- gboolean caught_error;
- GError *error;
-
- DIR *dir_handle;
-
- gboolean dir_enumeration_complete;
- guint pending_ops;
- guint pending_file_ops;
- GPtrArray *pending_dirs;
- GMainLoop *loop;
- GSimpleAsyncResult *result;
-} CheckoutTreeAsyncData;
-
-static void
-checkout_tree_async_data_free (gpointer data)
-{
- CheckoutTreeAsyncData *checkout_data = data;
-
- g_clear_object (&checkout_data->repo);
- g_clear_object (&checkout_data->destination);
- g_clear_object (&checkout_data->source);
- g_clear_object (&checkout_data->source_info);
- g_clear_object (&checkout_data->cancellable);
- if (checkout_data->pending_dirs)
- g_ptr_array_unref (checkout_data->pending_dirs);
- if (checkout_data->dir_handle)
- (void) closedir (checkout_data->dir_handle);
- g_free (checkout_data);
-}
-
-static void
-on_tree_async_child_op_complete (CheckoutTreeAsyncData *data,
- GError *local_error)
-{
- data->pending_ops--;
-
- if (local_error)
- {
- if (!data->caught_error)
- {
- data->caught_error = TRUE;
- g_propagate_error (&data->error, local_error);
- }
- else
- g_clear_error (&local_error);
- }
-
- if (data->pending_ops != 0)
- return;
-
- if (data->caught_error)
- g_simple_async_result_take_error (data->result, data->error);
- g_simple_async_result_complete_in_idle (data->result);
- g_object_unref (data->result);
-}
-
-static void
-on_one_subdir_checked_out (GObject *src,
- GAsyncResult *result,
- gpointer user_data)
-{
- CheckoutTreeAsyncData *data = user_data;
- GError *local_error = NULL;
-
- if (!ostree_repo_checkout_tree_finish ((OstreeRepo*) src, result, &local_error))
- goto out;
-
- out:
- on_tree_async_child_op_complete (data, local_error);
-}
-
-static void
-process_pending_dirs (CheckoutTreeAsyncData *data)
-{
- guint i;
-
- g_assert (data->dir_enumeration_complete);
- g_assert (data->pending_file_ops == 0);
-
- /* Don't hold a FD open while we're processing
- * recursive calls, otherwise we can pretty easily
- * hit the max of 1024 fds =(
- */
- if (data->dir_handle)
- {
- (void) closedir (data->dir_handle);
- data->dir_handle = NULL;
- }
-
- if (data->pending_dirs != NULL)
- {
- for (i = 0; i < data->pending_dirs->len; i++)
- {
- GFileInfo *file_info = data->pending_dirs->pdata[i];
- const char *name;
- gs_unref_object GFile *dest_path = NULL;
- gs_unref_object GFile *src_child = NULL;
-
- name = g_file_info_get_attribute_byte_string (file_info, "standard::name");
-
- dest_path = g_file_get_child (data->destination, name);
- src_child = g_file_get_child ((GFile*)data->source, name);
-
- ostree_repo_checkout_tree_async (data->repo,
- data->mode,
- data->overwrite_mode,
- dest_path, (OstreeRepoFile*)src_child, file_info,
- data->cancellable,
- on_one_subdir_checked_out,
- data);
- data->pending_ops++;
- }
- g_ptr_array_set_size (data->pending_dirs, 0);
- on_tree_async_child_op_complete (data, NULL);
- }
-}
-
-static void
-on_one_file_checked_out (GObject *src,
- GAsyncResult *result,
- gpointer user_data)
-{
- CheckoutTreeAsyncData *data = user_data;
- GError *local_error = NULL;
-
- if (!checkout_one_file_finish ((OstreeRepo*) src, result, &local_error))
- goto out;
-
- out:
- data->pending_file_ops--;
- if (data->dir_enumeration_complete && data->pending_file_ops == 0)
- process_pending_dirs (data);
- on_tree_async_child_op_complete (data, local_error);
-}
-
-static void
-on_got_next_files (GObject *src,
- GAsyncResult *result,
- gpointer user_data)
-{
- CheckoutTreeAsyncData *data = user_data;
- GError *local_error = NULL;
- GList *files = NULL;
- GList *iter = NULL;
-
- files = g_file_enumerator_next_files_finish ((GFileEnumerator*) src, result, &local_error);
- if (local_error)
- goto out;
-
- if (!files)
- data->dir_enumeration_complete = TRUE;
- else
- {
- g_file_enumerator_next_files_async ((GFileEnumerator*)src, 50, G_PRIORITY_DEFAULT,
- data->cancellable,
- on_got_next_files, data);
- data->pending_ops++;
- }
-
- if (data->dir_enumeration_complete && data->pending_file_ops == 0)
- process_pending_dirs (data);
-
- for (iter = files; iter; iter = iter->next)
- {
- GFileInfo *file_info = iter->data;
- const char *name;
- guint32 type;
-
- name = g_file_info_get_attribute_byte_string (file_info, "standard::name");
- type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
-
- if (type != G_FILE_TYPE_DIRECTORY)
- {
- gs_unref_object GFile *dest_path = NULL;
- gs_unref_object GFile *src_child = NULL;
-
- dest_path = g_file_get_child (data->destination, name);
- src_child = g_file_get_child ((GFile*)data->source, name);
-
- checkout_one_file_async (data->repo, data->mode,
- data->overwrite_mode,
- (OstreeRepoFile*)src_child, file_info,
- dest_path, dirfd(data->dir_handle),
- data->cancellable, on_one_file_checked_out,
- data);
- data->pending_file_ops++;
- data->pending_ops++;
- }
- else
- {
- if (data->pending_dirs == NULL)
- {
- data->pending_dirs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
- data->pending_ops++;
- }
- g_ptr_array_add (data->pending_dirs, g_object_ref (file_info));
- }
- g_object_unref (file_info);
- }
-
- g_list_free (files);
-
- out:
- on_tree_async_child_op_complete (data, local_error);
-}
-
-void
-ostree_repo_checkout_tree_async (OstreeRepo *self,
- OstreeRepoCheckoutMode mode,
- OstreeRepoCheckoutOverwriteMode overwrite_mode,
- GFile *destination,
- OstreeRepoFile *source,
- GFileInfo *source_info,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- CheckoutTreeAsyncData *checkout_data;
- gs_unref_object GFileInfo *file_info = NULL;
- gs_unref_variant GVariant *xattrs = NULL;
- gs_unref_object GFileEnumerator *dir_enum = NULL;
- GError *local_error = NULL;
- GError **error = &local_error;
-
- checkout_data = g_new0 (CheckoutTreeAsyncData, 1);
- checkout_data->repo = g_object_ref (self);
- checkout_data->mode = mode;
- checkout_data->overwrite_mode = overwrite_mode;
- checkout_data->destination = g_object_ref (destination);
- checkout_data->source = g_object_ref (source);
- checkout_data->source_info = g_object_ref (source_info);
- checkout_data->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
- checkout_data->pending_ops++; /* Count this function */
-
- checkout_data->result = g_simple_async_result_new ((GObject*) self,
- callback, user_data,
- ostree_repo_checkout_tree_async);
-
- g_simple_async_result_set_op_res_gpointer (checkout_data->result, checkout_data,
- checkout_tree_async_data_free);
-
- if (!ostree_repo_file_get_xattrs (checkout_data->source, &xattrs, NULL, error))
- goto out;
-
- if (!checkout_file_from_input (checkout_data->destination,
- checkout_data->mode,
- checkout_data->overwrite_mode,
- checkout_data->source_info,
- xattrs, NULL,
- cancellable, error))
- goto out;
-
- checkout_data->dir_handle = opendir (gs_file_get_path_cached (checkout_data->destination));
- if (!checkout_data->dir_handle)
- {
- ot_util_set_error_from_errno (error, errno);
- goto out;
- }
-
- g_clear_pointer (&xattrs, (GDestroyNotify) g_variant_unref);
-
- dir_enum = g_file_enumerate_children ((GFile*)checkout_data->source,
- OSTREE_GIO_FAST_QUERYINFO,
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- cancellable,
- error);
- if (!dir_enum)
- goto out;
-
- g_file_enumerator_next_files_async (dir_enum, 50, G_PRIORITY_DEFAULT, cancellable,
- on_got_next_files, checkout_data);
- checkout_data->pending_ops++;
-
- out:
- on_tree_async_child_op_complete (checkout_data, local_error);
-}
-
-gboolean
-ostree_repo_checkout_tree_finish (OstreeRepo *self,
- GAsyncResult *result,
- GError **error)
-{
- GSimpleAsyncResult *simple;
-
- g_return_val_if_fail (g_simple_async_result_is_valid (result, (GObject*)self,
ostree_repo_checkout_tree_async), FALSE);
-
- simple = G_SIMPLE_ASYNC_RESULT (result);
- if (g_simple_async_result_propagate_error (simple, error))
- return FALSE;
- return TRUE;
-}
-
-/**
- * ostree_repo_checkout_gc:
- *
- * Call this after finishing a succession of checkout operations; it
- * will delete any currently-unused uncompressed objects from the
- * cache.
- */
-gboolean
-ostree_repo_checkout_gc (OstreeRepo *self,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- gs_unref_hashtable GHashTable *to_clean_dirs = NULL;
- GHashTableIter iter;
- gpointer key, value;
-
- g_mutex_lock (&self->cache_lock);
- to_clean_dirs = self->updated_uncompressed_dirs;
- self->updated_uncompressed_dirs = g_hash_table_new (NULL, NULL);
- g_mutex_unlock (&self->cache_lock);
-
- if (to_clean_dirs)
- g_hash_table_iter_init (&iter, to_clean_dirs);
- while (to_clean_dirs && g_hash_table_iter_next (&iter, &key, &value))
- {
- gs_unref_object GFile *objdir = NULL;
- gs_unref_object GFileEnumerator *enumerator = NULL;
- gs_free char *objdir_name = NULL;
-
- objdir_name = g_strdup_printf ("%02x", GPOINTER_TO_UINT (key));
- objdir = ot_gfile_get_child_build_path (self->uncompressed_objects_dir, "objects",
- objdir_name, NULL);
-
- enumerator = g_file_enumerate_children (objdir,
"standard::name,standard::type,unix::inode,unix::nlink",
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- cancellable,
- error);
- if (!enumerator)
- goto out;
-
- while (TRUE)
- {
- GFileInfo *file_info;
- guint32 nlinks;
-
- if (!gs_file_enumerator_iterate (enumerator, &file_info, NULL,
- cancellable, error))
- goto out;
- if (file_info == NULL)
- break;
-
- nlinks = g_file_info_get_attribute_uint32 (file_info, "unix::nlink");
- if (nlinks == 1)
- {
- gs_unref_object GFile *objpath = NULL;
- objpath = g_file_get_child (objdir, g_file_info_get_name (file_info));
- if (!gs_file_unlink (objpath, cancellable, error))
- goto out;
- }
- }
- }
-
- ret = TRUE;
- out:
- return ret;
-}
-
gboolean
ostree_repo_read_commit (OstreeRepo *self,
const char *rev,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]