From 76bc35186eb19b1c55c891f8e6e513f2c19eb99a Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 6 Mar 2012 11:37:50 -0500 Subject: [PATCH] core: Add --union mode to checkout This is another step towards ostbuild using this instead of the "compose" builtin. --- src/libostree/ostree-core.c | 66 +++++++++++++++- src/libostree/ostree-core.h | 8 ++ src/libostree/ostree-repo.c | 132 +++++++++++++++++++++++++++---- src/libostree/ostree-repo.h | 10 ++- src/ostree/ot-builtin-checkout.c | 3 + tests/t0000-basic.sh | 12 ++- 6 files changed, 210 insertions(+), 21 deletions(-) diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c index 8a4cb1fd..5f938d11 100644 --- a/src/libostree/ostree-core.c +++ b/src/libostree/ostree-core.c @@ -915,6 +915,9 @@ ostree_create_temp_file_from_input (GFile *dir, /* 128 attempts seems reasonable... */ for (i = 0; i < 128; i++) { + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto out; + g_free (possible_name); possible_name = subst_xxxxxx (tmp_name->str); g_clear_object (&possible_file); @@ -941,7 +944,7 @@ ostree_create_temp_file_from_input (GFile *dir, break; } } - if (i == 128) + if (i >= 128) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Exhausted 128 attempts to create a temporary file"); @@ -990,3 +993,64 @@ ostree_create_temp_regular_file (GFile *dir, g_clear_object (&ret_stream); return ret; } + +gboolean +ostree_create_temp_hardlink (GFile *dir, + GFile *src, + const char *prefix, + const char *suffix, + GFile **out_file, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GString *tmp_name = NULL; + char *possible_name = NULL; + GFile *possible_file = NULL; + int i = 0; + + tmp_name = create_tmp_string (ot_gfile_get_path_cached (dir), + prefix, suffix); + + /* 128 attempts seems reasonable... */ + for (i = 0; i < 128; i++) + { + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto out; + + g_free (possible_name); + possible_name = subst_xxxxxx (tmp_name->str); + g_clear_object (&possible_file); + possible_file = g_file_get_child (dir, possible_name); + + if (link (ot_gfile_get_path_cached (src), ot_gfile_get_path_cached (possible_file)) < 0) + { + if (errno == EEXIST) + continue; + else + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + else + { + break; + } + } + if (i >= 128) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Exhausted 128 attempts to create a temporary file"); + goto out; + } + + ret = TRUE; + ot_transfer_out_value(out_file, &possible_file); + out: + if (tmp_name) + g_string_free (tmp_name, TRUE); + g_free (possible_name); + g_clear_object (&possible_file); + return ret; +} diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h index 24489ce3..e6893c92 100644 --- a/src/libostree/ostree-core.h +++ b/src/libostree/ostree-core.h @@ -182,6 +182,14 @@ gboolean ostree_create_temp_regular_file (GFile *dir, GCancellable *cancellable, GError **error); +gboolean ostree_create_temp_hardlink (GFile *dir, + GFile *src, + const char *prefix, + const char *suffix, + GFile **out_file, + GCancellable *cancellable, + GError **error); + GVariant *ostree_create_archive_file_metadata (GFileInfo *file_info, GVariant *xattrs); diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 8087f060..05ebbc4a 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -2453,6 +2453,7 @@ ostree_repo_iter_objects (OstreeRepo *self, static gboolean checkout_file_from_input (GFile *file, OstreeRepoCheckoutMode mode, + OstreeRepoCheckoutOverwriteMode overwrite_mode, GFileInfo *finfo, GVariant *xattrs, GInputStream *input, @@ -2460,6 +2461,9 @@ checkout_file_from_input (GFile *file, GError **error) { gboolean ret = FALSE; + GError *temp_error = NULL; + GFile *dir = NULL; + GFile *temp_file = NULL; GFileInfo *temp_info = NULL; if (mode == OSTREE_REPO_CHECKOUT_MODE_USER) @@ -2475,20 +2479,119 @@ checkout_file_from_input (GFile *file, xattrs = NULL; } - if (!ostree_create_file_from_input (file, temp_info ? temp_info : finfo, - xattrs, input, OSTREE_OBJECT_TYPE_RAW_FILE, - NULL, cancellable, error)) - goto out; + if (overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES) + { + if (g_file_info_get_file_type (temp_info ? temp_info : finfo) == G_FILE_TYPE_DIRECTORY) + { + if (!ostree_create_file_from_input (file, temp_info ? temp_info : finfo, + xattrs, input, OSTREE_OBJECT_TYPE_RAW_FILE, + NULL, 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 ? temp_info : finfo, + xattrs, input, OSTREE_OBJECT_TYPE_RAW_FILE, + &temp_file, NULL, + cancellable, error)) + goto out; + + if (rename (ot_gfile_get_path_cached (temp_file), ot_gfile_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 ? temp_info : finfo, + xattrs, input, OSTREE_OBJECT_TYPE_RAW_FILE, + NULL, cancellable, error)) + goto out; + } ret = TRUE; out: g_clear_object (&temp_info); + g_clear_object (&temp_file); + g_clear_object (&dir); + return ret; +} + +static gboolean +checkout_file_hardlink (OstreeRepo *self, + OstreeRepoCheckoutMode mode, + OstreeRepoCheckoutOverwriteMode overwrite_mode, + GFile *source, + GFile *destination, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GFile *dir = NULL; + GFile *temp_file = NULL; + + if (overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES) + { + dir = g_file_get_parent (destination); + if (!ostree_create_temp_hardlink (dir, (GFile*)source, NULL, "link", + &temp_file, cancellable, error)) + goto out; + + /* 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 (ot_gfile_get_path_cached (destination)); + + if (rename (ot_gfile_get_path_cached (temp_file), + ot_gfile_get_path_cached (destination)) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + g_clear_object (&temp_file); + } + else + { + if (link (ot_gfile_get_path_cached (source), ot_gfile_get_path_cached (destination)) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + + ret = TRUE; + out: + g_clear_object (&dir); + if (temp_file) + (void) unlink (ot_gfile_get_path_cached (temp_file)); + g_clear_object (&temp_file); return ret; } gboolean ostree_repo_checkout_tree (OstreeRepo *self, OstreeRepoCheckoutMode mode, + OstreeRepoCheckoutOverwriteMode overwrite_mode, GFile *destination, OstreeRepoFile *source, GFileInfo *source_info, @@ -2511,7 +2614,7 @@ ostree_repo_checkout_tree (OstreeRepo *self, if (!ostree_repo_file_get_xattrs (source, &xattrs, NULL, error)) goto out; - if (!checkout_file_from_input (destination, mode, source_info, + if (!checkout_file_from_input (destination, mode, overwrite_mode, source_info, xattrs, NULL, cancellable, error)) goto out; @@ -2541,7 +2644,8 @@ ostree_repo_checkout_tree (OstreeRepo *self, if (type == G_FILE_TYPE_DIRECTORY) { - if (!ostree_repo_checkout_tree (self, mode, dest_path, (OstreeRepoFile*)src_child, file_info, + if (!ostree_repo_checkout_tree (self, mode, overwrite_mode, + dest_path, (OstreeRepoFile*)src_child, file_info, cancellable, error)) goto out; } @@ -2554,11 +2658,8 @@ ostree_repo_checkout_tree (OstreeRepo *self, g_clear_object (&object_path); object_path = ostree_repo_get_object_path (self, checksum, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT); - if (link (ot_gfile_get_path_cached (object_path), ot_gfile_get_path_cached (dest_path)) < 0) - { - ot_util_set_error_from_errno (error, errno); - goto out; - } + if (!checkout_file_hardlink (self, mode, overwrite_mode, object_path, dest_path, cancellable, error) < 0) + goto out; } else if (priv->mode == OSTREE_REPO_MODE_ARCHIVE) { @@ -2581,7 +2682,7 @@ ostree_repo_checkout_tree (OstreeRepo *self, goto out; } - if (!checkout_file_from_input (dest_path, mode, file_info, xattrs, + if (!checkout_file_from_input (dest_path, mode, overwrite_mode, file_info, xattrs, content_input, cancellable, error)) goto out; } @@ -2590,11 +2691,8 @@ ostree_repo_checkout_tree (OstreeRepo *self, g_clear_object (&object_path); object_path = ostree_repo_get_object_path (self, checksum, OSTREE_OBJECT_TYPE_RAW_FILE); - if (link (ot_gfile_get_path_cached (object_path), ot_gfile_get_path_cached (dest_path)) < 0) - { - ot_util_set_error_from_errno (error, errno); - goto out; - } + if (!checkout_file_hardlink (self, mode, overwrite_mode, object_path, dest_path, cancellable, error) < 0) + goto out; } } diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index 5206f371..25ef09ee 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -201,13 +201,19 @@ gboolean ostree_repo_stage_commit (OstreeRepo *self, GError **error); typedef enum { - OSTREE_REPO_CHECKOUT_MODE_NONE, - OSTREE_REPO_CHECKOUT_MODE_USER + OSTREE_REPO_CHECKOUT_MODE_NONE = 0, + OSTREE_REPO_CHECKOUT_MODE_USER = 1 } OstreeRepoCheckoutMode; +typedef enum { + OSTREE_REPO_CHECKOUT_OVERWRITE_NONE = 0, + OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES = 1 +} OstreeRepoCheckoutOverwriteMode; + gboolean ostree_repo_checkout_tree (OstreeRepo *self, OstreeRepoCheckoutMode mode, + OstreeRepoCheckoutOverwriteMode overwrite_mode, GFile *destination, OstreeRepoFile *source, GFileInfo *source_info, diff --git a/src/ostree/ot-builtin-checkout.c b/src/ostree/ot-builtin-checkout.c index f4730725..fbdac135 100644 --- a/src/ostree/ot-builtin-checkout.c +++ b/src/ostree/ot-builtin-checkout.c @@ -29,10 +29,12 @@ static gboolean user_mode; static char *subpath; +static gboolean opt_union; static GOptionEntry options[] = { { "user-mode", 'U', 0, G_OPTION_ARG_NONE, &user_mode, "Do not change file ownership or initialze extended attributes", NULL }, { "subpath", 0, 0, G_OPTION_ARG_STRING, &subpath, "Checkout sub-directory PATH", "PATH" }, + { "union", 0, 0, G_OPTION_ARG_NONE, &opt_union, "Keep existing directories, overwrite existing files", NULL }, { NULL } }; @@ -99,6 +101,7 @@ ostree_builtin_checkout (int argc, char **argv, GFile *repo_path, GError **error goto out; if (!ostree_repo_checkout_tree (repo, user_mode ? OSTREE_REPO_CHECKOUT_MODE_USER : 0, + opt_union ? OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES : 0, destf, subtree, file_info, cancellable, error)) goto out; diff --git a/tests/t0000-basic.sh b/tests/t0000-basic.sh index 606ef38c..2d55e201 100755 --- a/tests/t0000-basic.sh +++ b/tests/t0000-basic.sh @@ -19,7 +19,7 @@ set -e -echo "1..27" +echo "1..28" . libtest.sh @@ -196,3 +196,13 @@ $OSTREE checkout --subpath /yet/another test2 checkout-test2-subpath cd checkout-test2-subpath assert_file_has_content tree/green "leaf" echo "ok checkout subpath" + +cd ${test_tmpdir} +$OSTREE checkout --union test2 checkout-test2-union +find checkout-test2-union | wc -l > union-files-count +$OSTREE checkout --union test2 checkout-test2-union +find checkout-test2-union | wc -l > union-files-count.new +cmp union-files-count{,.new} +cd checkout-test2-union +assert_file_has_content ./yet/another/tree/green "leaf" +echo "ok checkout union 1"