diff options
author | Colin Walters <walters@verbum.org> | 2013-09-07 19:01:27 -0400 |
---|---|---|
committer | Colin Walters <walters@verbum.org> | 2013-09-08 14:40:09 -0400 |
commit | 9846fb27fd008ac08dcc4b304fcd314eb75bba09 (patch) | |
tree | 402bdd37aa3efb064f58197a93bca9ef21cc72b0 | |
parent | db148cc9ba6b5f8cd54dae6e6d81927c235e955c (diff) | |
download | ostree-9846fb27fd008ac08dcc4b304fcd314eb75bba09.tar.gz |
core: Use linkat() for hardlink checkouts too
Clean up how we deal with the uncompressed object cache; we now use
openat()/linkat() and such just like we do for the main objects/.
Use linkat() between the objects and the destination, if possible.
https://bugzilla.gnome.org/show_bug.cgi?id=707733
-rw-r--r-- | src/libostree/ostree-core-private.h | 6 | ||||
-rw-r--r-- | src/libostree/ostree-core.c | 37 | ||||
-rw-r--r-- | src/libostree/ostree-repo-checkout.c | 266 | ||||
-rw-r--r-- | src/libostree/ostree-repo-commit.c | 47 | ||||
-rw-r--r-- | src/libostree/ostree-repo-private.h | 9 | ||||
-rw-r--r-- | src/libostree/ostree-repo.c | 18 |
6 files changed, 224 insertions, 159 deletions
diff --git a/src/libostree/ostree-core-private.h b/src/libostree/ostree-core-private.h index 49d5cf81..3fe67f9c 100644 --- a/src/libostree/ostree-core-private.h +++ b/src/libostree/ostree-core-private.h @@ -74,6 +74,12 @@ gboolean _ostree_write_variant_with_size (GOutputStream *output, GCancellable *cancellable, GError **error); +gboolean +_ostree_set_xattrs_fd (int fd, + GVariant *xattrs, + GCancellable *cancellable, + GError **error); + /* XX + / + checksum-2 + . + extension, but let's just use 256 for a * bit of overkill. */ diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c index d88c15d7..185aa82d 100644 --- a/src/libostree/ostree-core.c +++ b/src/libostree/ostree-core.c @@ -1036,6 +1036,43 @@ ostree_create_directory_metadata (GFileInfo *dir_info, return ret_metadata; } +gboolean +_ostree_set_xattrs_fd (int fd, + GVariant *xattrs, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + int i, n; + + n = g_variant_n_children (xattrs); + for (i = 0; i < n; i++) + { + const guint8* name; + gs_unref_variant GVariant *value = NULL; + const guint8* value_data; + gsize value_len; + int res; + + g_variant_get_child (xattrs, i, "(^&ay@ay)", + &name, &value); + value_data = g_variant_get_fixed_array (value, &value_len, 1); + + do + res = fsetxattr (fd, (char*)name, (char*)value_data, value_len, 0); + while (G_UNLIKELY (res == -1 && errno == EINTR)); + if (G_UNLIKELY (res == -1)) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + + ret = TRUE; + out: + return ret; +} + /** * ostree_set_xattrs: * @f: a file diff --git a/src/libostree/ostree-repo-checkout.c b/src/libostree/ostree-repo-checkout.c index ffd1c27d..83ed9110 100644 --- a/src/libostree/ostree-repo-checkout.c +++ b/src/libostree/ostree-repo-checkout.c @@ -23,11 +23,81 @@ #include "config.h" #include <glib-unix.h> +#include <attr/xattr.h> +#include <gio/gfiledescriptorbased.h> #include "otutil.h" #include "ostree-repo-file.h" +#include "ostree-core-private.h" #include "ostree-repo-private.h" +static gboolean +checkout_object_for_uncompressed_cache (OstreeRepo *self, + const char *loose_path, + GFileInfo *src_info, + GInputStream *content, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gs_free char *temp_filename = NULL; + gs_unref_object GOutputStream *temp_out = NULL; + int fd; + int res; + guint32 file_mode; + + /* Don't make setuid files in uncompressed cache */ + file_mode = g_file_info_get_attribute_uint32 (src_info, "unix::mode"); + file_mode &= ~(S_ISUID|S_ISGID); + + if (!gs_file_open_in_tmpdir_at (self->tmp_dir_fd, file_mode, + &temp_filename, &temp_out, + cancellable, error)) + goto out; + + if (g_output_stream_splice (temp_out, content, 0, cancellable, error) < 0) + goto out; + + if (!g_output_stream_flush (temp_out, cancellable, error)) + goto out; + + fd = g_file_descriptor_based_get_fd ((GFileDescriptorBased*)temp_out); + + do + res = fsync (fd); + while (G_UNLIKELY (res == -1 && errno == EINTR)); + if (G_UNLIKELY (res == -1)) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + + if (!g_output_stream_close (temp_out, cancellable, error)) + goto out; + + if (!_ostree_repo_ensure_loose_objdir_at (self->uncompressed_objects_dir_fd, + loose_path, + cancellable, error)) + goto out; + + if (G_UNLIKELY (renameat (self->tmp_dir_fd, temp_filename, + self->uncompressed_objects_dir_fd, loose_path) == -1)) + { + if (errno != EEXIST) + { + ot_util_set_error_from_errno (error, errno); + g_prefix_error (error, "Storing file '%s': ", temp_filename); + goto out; + } + else + (void) unlinkat (self->tmp_dir_fd, temp_filename, 0); + } + + ret = TRUE; + out: + return ret; +} + /* * create_file_from_input: * @dest_file: Destination; must not exist @@ -323,21 +393,24 @@ checkout_file_from_input (GFile *file, } static gboolean -checkout_file_hardlink (OstreeRepo *self, - OstreeRepoCheckoutMode mode, - OstreeRepoCheckoutOverwriteMode overwrite_mode, - GFile *source, - int dirfd, - const char *name, - gboolean *out_was_supported, - GCancellable *cancellable, - GError **error) +checkout_file_hardlink (OstreeRepo *self, + OstreeRepoCheckoutMode mode, + OstreeRepoCheckoutOverwriteMode overwrite_mode, + const char *loose_path, + int destination_dfd, + const char *destination_name, + gboolean allow_noent, + gboolean *out_was_supported, + GCancellable *cancellable, + GError **error) { gboolean ret = FALSE; gboolean ret_was_supported = FALSE; + int srcfd = self->mode == OSTREE_REPO_MODE_BARE ? + self->objects_dir_fd : self->uncompressed_objects_dir_fd; again: - if (linkat (-1, gs_file_get_path_cached (source), dirfd, name, 0) != -1) + if (linkat (srcfd, loose_path, destination_dfd, destination_name, 0) != -1) ret_was_supported = TRUE; else if (errno == EMLINK || errno == EXDEV || errno == EPERM) { @@ -346,6 +419,10 @@ checkout_file_hardlink (OstreeRepo *self, */ ret_was_supported = FALSE; } + else if (allow_noent && errno == ENOENT) + { + ret_was_supported = FALSE; + } else if (errno == EEXIST && overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES) { /* Idiocy, from man rename(2) @@ -356,9 +433,8 @@ checkout_file_hardlink (OstreeRepo *self, * * So we can't make this atomic. */ - (void) unlinkat (dirfd, name, 0); + (void) unlinkat (destination_dfd, destination_name, 0); goto again; - ret_was_supported = TRUE; } else { @@ -374,72 +450,6 @@ checkout_file_hardlink (OstreeRepo *self, } 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_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; -} - -static gboolean checkout_one_file (OstreeRepo *repo, GFile *source, GFileInfo *source_info, @@ -454,61 +464,68 @@ checkout_one_file (OstreeRepo *repo, gboolean ret = FALSE; const char *checksum; gboolean is_symlink; - gboolean hardlink_supported; - gs_unref_object GFile *loose_path = NULL; + gboolean did_hardlink = FALSE; + char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; gs_unref_object GInputStream *input = NULL; gs_unref_variant GVariant *xattrs = NULL; - /* Hack to avoid trying to create device files as a user */ - if (mode == OSTREE_REPO_CHECKOUT_MODE_USER - && g_file_info_get_file_type (source_info) == G_FILE_TYPE_SPECIAL) - goto out; - is_symlink = g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK; checksum = ostree_repo_file_get_checksum ((OstreeRepoFile*)source); - /* We can only do hardlinks in these scenarios */ - if (!is_symlink && - ((repo->mode == OSTREE_REPO_MODE_BARE && mode == OSTREE_REPO_CHECKOUT_MODE_NONE) - || (repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2 && mode == OSTREE_REPO_CHECKOUT_MODE_USER))) + /* Try to do a hardlink first, if it's a regular file. This also + * traverses all parent repos. + */ + if (!is_symlink) { - if (!find_loose_for_checkout (repo, checksum, &loose_path, - cancellable, error)) - goto out; + OstreeRepo *current_repo = repo; + + while (current_repo) + { + gboolean is_bare = (current_repo->mode == OSTREE_REPO_MODE_BARE + && mode == OSTREE_REPO_CHECKOUT_MODE_NONE); + gboolean is_archive_z2_with_cache = (current_repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2 + && mode == OSTREE_REPO_CHECKOUT_MODE_USER); + + /* But only under these conditions */ + if (is_bare || is_archive_z2_with_cache) + { + /* Override repo mode; for archive-z2 we're looking in + the cache, which is in "bare" form */ + _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE); + if (!checkout_file_hardlink (current_repo, + mode, overwrite_mode, loose_path_buf, + destination_dfd, destination_name, + TRUE, &did_hardlink, + cancellable, error)) + goto out; + if (did_hardlink) + break; + } + current_repo = current_repo->parent_repo; + } } - /* 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. + + + /* Ok, if we're archive-z2 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 + && !did_hardlink && repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2 && 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; - } + /* Overwrite any parent repo from earlier */ + _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE); - /* 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, - source_info, xattrs, - input, cancellable, error)) + if (!checkout_object_for_uncompressed_cache (repo, loose_path_buf, + source_info, input, + cancellable, error)) { g_prefix_error (error, "Unpacking loose object %s: ", checksum); goto out; @@ -538,24 +555,16 @@ checkout_one_file (OstreeRepo *repo, 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 (repo, mode, - overwrite_mode, loose_path, + if (!checkout_file_hardlink (repo, mode, overwrite_mode, loose_path_buf, destination_dfd, destination_name, - &hardlink_supported, cancellable, error)) - { - g_prefix_error (error, "Hardlinking loose object %s to %s: ", checksum, - gs_file_get_path_cached (destination)); - goto out; - } + FALSE, &did_hardlink, + cancellable, error)) + goto out; } - /* Fall back to copy if there's no loose object, or we couldn't hardlink */ - if (loose_path == NULL || !hardlink_supported) + /* Fall back to copy if we couldn't hardlink */ + if (!did_hardlink) { if (!ostree_repo_load_file (repo, checksum, &input, NULL, &xattrs, cancellable, error)) @@ -708,8 +717,7 @@ ostree_repo_checkout_gc (OstreeRepo *self, 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); + objdir = g_file_get_child (self->uncompressed_objects_dir, objdir_name); enumerator = g_file_enumerate_children (objdir, "standard::name,standard::type,unix::inode,unix::nlink", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index 5d200a87..454cfa97 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -32,28 +32,41 @@ #include "ostree-checksum-input-stream.h" #include "ostree-mutable-tree.h" -static gboolean -commit_loose_object_trusted (OstreeRepo *self, - const char *loose_path, - const char *tempfile_name, - GCancellable *cancellable, - GError **error) +gboolean +_ostree_repo_ensure_loose_objdir_at (int dfd, + const char *loose_path, + GCancellable *cancellable, + GError **error) { - gboolean ret = FALSE; char loose_prefix[3]; loose_prefix[0] = loose_path[0]; loose_prefix[1] = loose_path[1]; loose_prefix[2] = '\0'; - if (G_UNLIKELY (mkdirat (self->objects_dir_fd, loose_prefix, 0777) == -1)) + if (mkdirat (dfd, loose_prefix, 0777) == -1) { int errsv = errno; - if (errsv != EEXIST) + if (G_UNLIKELY (errsv != EEXIST)) { ot_util_set_error_from_errno (error, errsv); - goto out; + return FALSE; } } + return TRUE; +} + +static gboolean +commit_loose_object_trusted (OstreeRepo *self, + const char *loose_path, + const char *tempfile_name, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + if (!_ostree_repo_ensure_loose_objdir_at (self->objects_dir_fd, loose_path, + cancellable, error)) + goto out; if (G_UNLIKELY (renameat (self->tmp_dir_fd, tempfile_name, self->objects_dir_fd, loose_path) == -1)) @@ -1000,20 +1013,6 @@ _ostree_repo_get_object_path (OstreeRepo *self, 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_write_content_trusted: * @self: Repo diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index 0060ca48..77336e51 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -36,6 +36,7 @@ struct OstreeRepo { GFile *objects_dir; int objects_dir_fd; GFile *uncompressed_objects_dir; + int uncompressed_objects_dir_fd; GFile *remote_cache_dir; GFile *config_file; @@ -60,9 +61,11 @@ struct OstreeRepo { OstreeRepo *parent_repo; }; -GFile * -_ostree_repo_get_uncompressed_object_cache_path (OstreeRepo *self, - const char *checksum); +gboolean +_ostree_repo_ensure_loose_objdir_at (int dfd, + const char *loose_path, + GCancellable *cancellable, + GError **error); GFile * _ostree_repo_get_file_object_path (OstreeRepo *self, diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 1a7dbce1..02c518c3 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -92,6 +92,8 @@ ostree_repo_finalize (GObject *object) if (self->objects_dir_fd != -1) (void) close (self->objects_dir_fd); g_clear_object (&self->uncompressed_objects_dir); + if (self->uncompressed_objects_dir_fd != -1) + (void) close (self->uncompressed_objects_dir_fd); g_clear_object (&self->remote_cache_dir); g_clear_object (&self->config_file); @@ -164,7 +166,7 @@ ostree_repo_constructed (GObject *object) self->remote_heads_dir = g_file_resolve_relative_path (self->repodir, "refs/remotes"); self->objects_dir = g_file_get_child (self->repodir, "objects"); - self->uncompressed_objects_dir = g_file_get_child (self->repodir, "uncompressed-objects-cache"); + self->uncompressed_objects_dir = g_file_resolve_relative_path (self->repodir, "uncompressed-objects-cache/objects"); self->remote_cache_dir = g_file_get_child (self->repodir, "remote-cache"); self->config_file = g_file_get_child (self->repodir, "config"); @@ -196,6 +198,7 @@ ostree_repo_init (OstreeRepo *self) g_mutex_init (&self->cache_lock); g_mutex_init (&self->txn_stats_lock); self->objects_dir_fd = -1; + self->uncompressed_objects_dir_fd = -1; } /** @@ -526,6 +529,16 @@ ostree_repo_open (OstreeRepo *self, if (!gs_file_open_dir_fd (self->tmp_dir, &self->tmp_dir_fd, cancellable, error)) goto out; + if (self->mode == OSTREE_REPO_MODE_ARCHIVE_Z2) + { + if (!gs_file_ensure_directory (self->uncompressed_objects_dir, TRUE, cancellable, error)) + goto out; + if (!gs_file_open_dir_fd (self->uncompressed_objects_dir, + &self->uncompressed_objects_dir_fd, + cancellable, error)) + goto out; + } + self->inited = TRUE; ret = TRUE; @@ -644,8 +657,7 @@ _ostree_repo_get_loose_object_dirs (OstreeRepo *self, if (ostree_repo_get_mode (self) == OSTREE_REPO_MODE_ARCHIVE_Z2) { - gs_unref_object GFile *cachedir = g_file_get_child (self->uncompressed_objects_dir, "objects"); - if (!append_object_dirs_from (self, cachedir, ret_object_dirs, + if (!append_object_dirs_from (self, self->uncompressed_objects_dir, ret_object_dirs, cancellable, error)) goto out; } |