diff --git a/Makefile-tests.am b/Makefile-tests.am index b634bc43..c51b1144 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -34,6 +34,7 @@ testfiles = test-basic \ test-gpg-signed-commit \ test-admin-deploy-1 \ test-admin-deploy-2 \ + test-admin-deploy-etcmerge-cornercases \ test-admin-deploy-uboot \ test-setuid \ test-xattrs \ diff --git a/Makefile.am b/Makefile.am index 131a4583..c7ca36e9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -91,7 +91,7 @@ OT_INTERNAL_SOUP_LIBS = $(OT_DEP_SOUP_LIBS) endif libgsystem_srcpath := src/libgsystem -libgsystem_cflags = $(OT_INTERNAL_GIO_UNIX_CFLAGS) -I$(srcdir)/src/libgsystem +libgsystem_cflags = $(OT_INTERNAL_GIO_UNIX_CFLAGS) -I$(srcdir)/src/libgsystem -DGSYSTEM_CONFIG_XATTRS libgsystem_libs = $(OT_INTERNAL_GIO_UNIX_LIBS) include src/libgsystem/Makefile-libgsystem.am noinst_LTLIBRARIES += libgsystem.la diff --git a/src/libgsystem b/src/libgsystem index f861ba48..3a59dab5 160000 --- a/src/libgsystem +++ b/src/libgsystem @@ -1 +1 @@ -Subproject commit f861ba48955b6c3a3a05cdadae510695db3b5a99 +Subproject commit 3a59dab5ed8b574d27c58d967df203825afd0954 diff --git a/src/libostree/ostree-core-private.h b/src/libostree/ostree-core-private.h index 8145cc33..84356e73 100644 --- a/src/libostree/ostree-core-private.h +++ b/src/libostree/ostree-core-private.h @@ -74,15 +74,6 @@ 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); - -gboolean _ostree_set_xattrs (GFile *f, GVariant *xattrs, - GCancellable *cancellable, GError **error); - gboolean _ostree_make_temporary_symlink_at (int tmp_dirfd, const char *target, diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c index bc796bb1..fc3d2741 100644 --- a/src/libostree/ostree-core.c +++ b/src/libostree/ostree-core.c @@ -26,7 +26,6 @@ #include #include #include -#include #include "ostree.h" #include "ostree-core-private.h" #include "ostree-chain-input-stream.h" @@ -201,151 +200,6 @@ ostree_validate_rev (const char *rev, return ret; } -static char * -canonicalize_xattrs (char *xattr_string, size_t len) -{ - char *p; - GSList *xattrs = NULL; - GSList *iter; - GString *result; - - result = g_string_new (0); - - p = xattr_string; - while (p < xattr_string+len) - { - xattrs = g_slist_prepend (xattrs, p); - p += strlen (p) + 1; - } - - xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp); - for (iter = xattrs; iter; iter = iter->next) { - g_string_append (result, iter->data); - g_string_append_c (result, '\0'); - } - - g_slist_free (xattrs); - return g_string_free (result, FALSE); -} - -static gboolean -read_xattr_name_array (const char *path, - const char *xattrs, - size_t len, - GVariantBuilder *builder, - GError **error) -{ - gboolean ret = FALSE; - const char *p; - - p = xattrs; - while (p < xattrs+len) - { - ssize_t bytes_read; - char *buf; - gs_unref_bytes GBytes *bytes = NULL; - - bytes_read = lgetxattr (path, p, NULL, 0); - if (bytes_read < 0) - { - ot_util_set_error_from_errno (error, errno); - g_prefix_error (error, "lgetxattr (%s, %s) failed: ", path, p); - goto out; - } - if (bytes_read == 0) - continue; - - buf = g_malloc (bytes_read); - bytes = g_bytes_new_take (buf, bytes_read); - if (lgetxattr (path, p, buf, bytes_read) < 0) - { - ot_util_set_error_from_errno (error, errno); - g_prefix_error (error, "lgetxattr (%s, %s) failed: ", path, p); - goto out; - } - - g_variant_builder_add (builder, "(@ay@ay)", - g_variant_new_bytestring (p), - ot_gvariant_new_ay_bytes (bytes)); - - p = p + strlen (p) + 1; - } - - ret = TRUE; - out: - return ret; -} - -/** - * ostree_get_xattrs_for_file: - * @f: a #GFile - * @out_xattrs: (out): A new #GVariant containing the extended attributes - * @cancellable: Cancellable - * @error: Error - * - * Read all extended attributes of @f in a canonical sorted order, and - * set @out_xattrs with the result. - * - * If the filesystem does not support extended attributes, @out_xattrs - * will have 0 elements, and this function will return successfully. - */ -gboolean -ostree_get_xattrs_for_file (GFile *f, - GVariant **out_xattrs, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - const char *path; - ssize_t bytes_read; - gs_unref_variant GVariant *ret_xattrs = NULL; - gs_free char *xattr_names = NULL; - gs_free char *xattr_names_canonical = NULL; - GVariantBuilder builder; - gboolean builder_initialized = FALSE; - - path = gs_file_get_path_cached (f); - - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)")); - builder_initialized = TRUE; - - bytes_read = llistxattr (path, NULL, 0); - - if (bytes_read < 0) - { - if (errno != ENOTSUP) - { - ot_util_set_error_from_errno (error, errno); - g_prefix_error (error, "llistxattr (%s) failed: ", path); - goto out; - } - } - else if (bytes_read > 0) - { - xattr_names = g_malloc (bytes_read); - if (llistxattr (path, xattr_names, bytes_read) < 0) - { - ot_util_set_error_from_errno (error, errno); - g_prefix_error (error, "llistxattr (%s) failed: ", path); - goto out; - } - xattr_names_canonical = canonicalize_xattrs (xattr_names, bytes_read); - - if (!read_xattr_name_array (path, xattr_names_canonical, bytes_read, &builder, error)) - goto out; - } - - ret_xattrs = g_variant_builder_end (&builder); - g_variant_ref_sink (ret_xattrs); - - ret = TRUE; - ot_transfer_out_value (out_xattrs, &ret_xattrs); - out: - if (!builder_initialized) - g_variant_builder_clear (&builder); - return ret; -} - static GVariant * file_header_new (GFileInfo *file_info, GVariant *xattrs) @@ -902,7 +756,7 @@ ostree_checksum_file (GFile *f, if (objtype == OSTREE_OBJECT_TYPE_FILE) { - if (!ostree_get_xattrs_for_file (f, &xattrs, cancellable, error)) + if (!gs_file_get_all_xattrs (f, &xattrs, cancellable, error)) goto out; } @@ -1036,94 +890,6 @@ 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 - * @xattrs: Extended attribute list - * @cancellable: Cancellable - * @error: Error - * - * For each attribute in @xattrs, replace the value (if any) of @f for - * that attribute. This function does not clear other existing - * attributes. - */ -gboolean -_ostree_set_xattrs (GFile *f, - GVariant *xattrs, - GCancellable *cancellable, - GError **error) -{ - const char *path; - gboolean ret = FALSE; - int i, n; - - path = gs_file_get_path_cached (f); - - n = g_variant_n_children (xattrs); - for (i = 0; i < n; i++) - { - const guint8* name; - GVariant *value; - const guint8* value_data; - gsize value_len; - gboolean loop_err; - - g_variant_get_child (xattrs, i, "(^&ay@ay)", - &name, &value); - value_data = g_variant_get_fixed_array (value, &value_len, 1); - - loop_err = lsetxattr (path, (char*)name, (char*)value_data, value_len, 0) < 0; - g_clear_pointer (&value, (GDestroyNotify) g_variant_unref); - if (loop_err) - { - ot_util_set_error_from_errno (error, errno); - g_prefix_error (error, "lsetxattr (%s, %s) failed: ", path, name); - 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. * diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h index 539909a8..5c76a4a6 100644 --- a/src/libostree/ostree-core.h +++ b/src/libostree/ostree-core.h @@ -167,11 +167,6 @@ void ostree_object_from_string (const char *str, gchar **out_checksum, OstreeObjectType *out_objtype); -gboolean ostree_get_xattrs_for_file (GFile *f, - GVariant **out_xattrs, - GCancellable *cancellable, - GError **error); - gboolean ostree_content_stream_parse (gboolean compressed, GInputStream *input, diff --git a/src/libostree/ostree-repo-checkout.c b/src/libostree/ostree-repo-checkout.c index f91b1c18..6bd393ab 100644 --- a/src/libostree/ostree-repo-checkout.c +++ b/src/libostree/ostree-repo-checkout.c @@ -145,7 +145,7 @@ write_regular_file_content (OstreeRepoCheckoutMode mode, if (xattrs) { - if (!_ostree_set_xattrs_fd (fd, xattrs, cancellable, error)) + if (!gs_fd_set_all_xattrs (fd, xattrs, cancellable, error)) goto out; } } @@ -194,7 +194,7 @@ checkout_file_from_input_at (OstreeRepoCheckoutMode mode, if (xattrs) { gs_unref_object GFile *path = g_file_get_child (destination_parent, destination_name); - if (!_ostree_set_xattrs (path, xattrs, cancellable, error)) + if (!gs_file_set_all_xattrs (path, xattrs, cancellable, error)) goto out; } } @@ -261,7 +261,7 @@ checkout_file_unioning_from_input_at (OstreeRepoCheckoutMode mode, if (xattrs) { gs_unref_object GFile *temp_path = g_file_get_child (destination_parent, temp_filename); - if (!_ostree_set_xattrs (temp_path, xattrs, cancellable, error)) + if (!gs_file_set_all_xattrs (temp_path, xattrs, cancellable, error)) goto out; } } @@ -588,7 +588,7 @@ checkout_tree_at (OstreeRepo *self, if (xattrs) { - if (!_ostree_set_xattrs_fd (destination_dfd, xattrs, cancellable, error)) + if (!gs_fd_set_all_xattrs (destination_dfd, xattrs, cancellable, error)) goto out; } } diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index 9c399387..4fc46c3e 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -92,7 +92,7 @@ commit_loose_object_trusted (OstreeRepo *self, */ if (xattrs != NULL) { - if (!_ostree_set_xattrs (temp_file, xattrs, cancellable, error)) + if (!gs_file_set_all_xattrs (temp_file, xattrs, cancellable, error)) goto out; } } @@ -131,7 +131,7 @@ commit_loose_object_trusted (OstreeRepo *self, if (xattrs) { - if (!_ostree_set_xattrs_fd (fd, xattrs, cancellable, error)) + if (!gs_fd_set_all_xattrs (fd, xattrs, cancellable, error)) goto out; } } @@ -1722,7 +1722,7 @@ write_directory_to_mtree_internal (OstreeRepo *self, 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)) + if (!gs_file_get_all_xattrs (dir, &xattrs, cancellable, error)) goto out; } @@ -1818,7 +1818,7 @@ write_directory_to_mtree_internal (OstreeRepo *self, 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)) + if (!gs_file_get_all_xattrs (child, &xattrs, cancellable, error)) goto out; } diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index eb5bc537..956b31a7 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -1039,8 +1039,8 @@ ostree_repo_load_file (OstreeRepo *self, gs_unref_object GFile *full_path = _ostree_repo_get_object_path (self, checksum, OSTREE_OBJECT_TYPE_FILE); - if (!ostree_get_xattrs_for_file (full_path, &ret_xattrs, - cancellable, error)) + if (!gs_file_get_all_xattrs (full_path, &ret_xattrs, + cancellable, error)) goto out; } diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index e36a6193..d908cd87 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -23,27 +23,33 @@ #include "config.h" #include "ostree-sysroot-private.h" +#include "ostree-core-private.h" #include "otutil.h" #include "libgsystem.h" /** - * copy_one_config_file: + * copy_modified_config_file: * * Copy @file from @modified_etc to @new_etc, overwriting any existing - * file there. + * file there. The @file may refer to a regular file, a symbolic + * link, or a directory. Directories will be copied recursively. + * + * Note this function does not (yet) handle the case where a directory + * needed by a modified file is deleted in a newer tree. */ static gboolean -copy_one_config_file (GFile *orig_etc, - GFile *modified_etc, - GFile *new_etc, - GFile *src, - GCancellable *cancellable, - GError **error) +copy_modified_config_file (GFile *orig_etc, + GFile *modified_etc, + GFile *new_etc, + GFile *src, + GCancellable *cancellable, + GError **error) { gboolean ret = FALSE; gs_unref_object GFileInfo *src_info = NULL; + gs_unref_object GFileInfo *parent_info = NULL; gs_unref_object GFile *dest = NULL; - gs_unref_object GFile *parent = NULL; + gs_unref_object GFile *dest_parent = NULL; gs_free char *relative_path = NULL; relative_path = g_file_get_relative_path (modified_etc, src); @@ -55,49 +61,28 @@ copy_one_config_file (GFile *orig_etc, if (!src_info) goto out; + dest_parent = g_file_get_parent (dest); + if (!ot_gfile_query_info_allow_noent (dest_parent, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + &parent_info, cancellable, error)) + goto out; + if (!parent_info) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "New tree removes parent directory '%s', cannot merge", + gs_file_get_path_cached (dest_parent)); + goto out; + } + + if (!gs_shutil_rm_rf (dest, cancellable, error)) + goto out; + if (g_file_info_get_file_type (src_info) == G_FILE_TYPE_DIRECTORY) { - gs_unref_object GFileEnumerator *src_enum = NULL; - gs_unref_object GFileInfo *child_info = NULL; - GError *temp_error = NULL; - - /* FIXME actually we need to copy permissions and xattrs */ - if (!gs_file_ensure_directory (dest, TRUE, cancellable, error)) + if (!gs_shutil_cp_a (src, dest, cancellable, error)) goto out; - - src_enum = g_file_enumerate_children (src, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, error); - - while ((child_info = g_file_enumerator_next_file (src_enum, cancellable, error)) != NULL) - { - gs_unref_object GFile *child = g_file_get_child (src, g_file_info_get_name (child_info)); - - if (!copy_one_config_file (orig_etc, modified_etc, new_etc, child, - cancellable, error)) - goto out; - } - g_clear_object (&child_info); - if (temp_error != NULL) - { - g_propagate_error (error, temp_error); - goto out; - } } else { - parent = g_file_get_parent (dest); - - /* FIXME actually we need to copy permissions and xattrs */ - if (!gs_file_ensure_directory (parent, TRUE, cancellable, error)) - goto out; - - /* We unlink here because otherwise gio throws an error on - * dangling symlinks. - */ - if (!ot_gfile_ensure_unlinked (dest, cancellable, error)) - goto out; - if (!g_file_copy (src, dest, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA, cancellable, NULL, NULL, error)) goto out; @@ -169,16 +154,16 @@ merge_etc_changes (GFile *orig_etc, { OstreeDiffItem *diff = modified->pdata[i]; - if (!copy_one_config_file (orig_etc, modified_etc, new_etc, diff->target, - cancellable, error)) + if (!copy_modified_config_file (orig_etc, modified_etc, new_etc, diff->target, + cancellable, error)) goto out; } for (i = 0; i < added->len; i++) { GFile *file = added->pdata[i]; - if (!copy_one_config_file (orig_etc, modified_etc, new_etc, file, - cancellable, error)) + if (!copy_modified_config_file (orig_etc, modified_etc, new_etc, file, + cancellable, error)) goto out; } diff --git a/tests/test-admin-deploy-etcmerge-cornercases.sh b/tests/test-admin-deploy-etcmerge-cornercases.sh new file mode 100644 index 00000000..033e5599 --- /dev/null +++ b/tests/test-admin-deploy-etcmerge-cornercases.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# +# Copyright (C) 2013 Colin Walters +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +set -e + +. $(dirname $0)/libtest.sh + +echo "1..1" + +setup_os_repository "archive-z2" "syslinux" + +echo "ok setup" + +echo "1..1" + +ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmaster/x86_64-runtime +rev=$(ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime) +export rev +echo "rev=${rev}" +# This initial deployment gets kicked off with some kernel arguments +ostree admin --sysroot=sysroot deploy --karg=root=LABEL=MOO --karg=quiet --os=testos testos:testos/buildmaster/x86_64-runtime +assert_has_dir sysroot/boot/ostree/testos-${bootcsum} + +# Ok, let's create a long directory chain with custom permissions +etc=sysroot/ostree/deploy/testos/deploy/${rev}.0/etc +mkdir -p ${etc}/a/long/dir/chain +mkdir -p ${etc}/a/long/dir/forking +chmod 700 ${etc}/a +chmod 770 ${etc}/a/long +chmod 777 ${etc}/a/long/dir +chmod 707 ${etc}/a/long/dir/chain +chmod 700 ${etc}/a/long/dir/forking + +# Now deploy a new commit +os_repository_new_commit +ostree --repo=sysroot/ostree/repo remote add --set=gpg-verify=false testos file://$(pwd)/testos-repo testos/buildmaster/x86_64-runtime +ostree admin --sysroot=sysroot upgrade --os=testos +newrev=$(ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime) +echo "newrev=${newrev}" + +newetc=sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc +assert_file_has_mode() { + stat -c '%a' $1 > mode.txt + if ! grep -q -e "$2" mode.txt; then + echo 1>&2 "file $1 has mode $(cat mode.txt); expected $2"; + exit 1 + fi + rm -f mode.txt +} +assert_file_has_mode ${newetc}/a 700 +assert_file_has_mode ${newetc}/a/long 770 +assert_file_has_mode ${newetc}/a/long/dir 777 +assert_file_has_mode ${newetc}/a/long/dir/chain 707 +assert_file_has_mode ${newetc}/a/long/dir/forking 700 +assert_file_has_mode ${newetc}/a/long/dir 777 + +echo "ok"