diff options
author | Colin Walters <walters@verbum.org> | 2014-09-13 10:36:59 -0400 |
---|---|---|
committer | Colin Walters <walters@verbum.org> | 2014-09-13 10:41:59 -0400 |
commit | 7b01bd2e4333d4346dd08e0b5caf672f56b1ccfd (patch) | |
tree | 30e06987822ebf41650b7be62846755ce49a9199 | |
parent | 34c336c1f3ad98918bb044533a43985413d05734 (diff) | |
download | ostree-7b01bd2e4333d4346dd08e0b5caf672f56b1ccfd.tar.gz |
deploy: Consistently use fd-relative API
While looking to fix a different bug here, I found the current
state of things where we had a mix of fd-relative API versus not
frustrating.
Change the code around to consistently use *at, and also add some more
tests.
-rw-r--r-- | src/libostree/ostree-sysroot-deploy.c | 178 | ||||
-rw-r--r-- | tests/test-admin-deploy-etcmerge-cornercases.sh | 27 |
2 files changed, 140 insertions, 65 deletions
diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index d5456c05..a6f1fc09 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -156,7 +156,7 @@ copy_one_file_fsync_at (int src_parent_dfd, } static gboolean -copy_dir_recurse_fsync (DIR *src_parent_dir, +copy_dir_recurse_fsync (int src_parent_dfd, int dest_parent_dfd, const char *name, GCancellable *cancellable, @@ -164,7 +164,6 @@ copy_dir_recurse_fsync (DIR *src_parent_dir, { gboolean ret = FALSE; struct stat src_stbuf; - int src_parent_dfd = dirfd (src_parent_dir); int src_dfd = -1; int dest_dfd = -1; DIR *srcd = NULL; @@ -229,7 +228,7 @@ copy_dir_recurse_fsync (DIR *src_parent_dir, if (S_ISDIR (child_stbuf.st_mode)) { - if (!copy_dir_recurse_fsync (srcd, dest_dfd, name, + if (!copy_dir_recurse_fsync (src_dfd, dest_dfd, name, cancellable, error)) goto out; } @@ -291,98 +290,125 @@ copy_dir_recurse_fsync (DIR *src_parent_dir, * needed by a modified file is deleted in a newer tree. */ static gboolean -copy_modified_config_file (GFile *orig_etc, - GFile *modified_etc, - GFile *new_etc, - GFile *src, +copy_modified_config_file (int orig_etc_fd, + int modified_etc_fd, + int new_etc_fd, + const char *path, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; - gs_unref_object GFile *src_parent = g_file_get_parent (src); - gs_unref_object GFileInfo *src_info = NULL; - gs_unref_object GFileInfo *parent_info = NULL; - gs_unref_object GFile *dest = NULL; - gs_unref_object GFile *dest_parent = NULL; - gs_free char *relative_path = NULL; - DIR *src_parent_dir = NULL; - int src_parent_dfd = -1; + struct stat modified_stbuf; + struct stat new_stbuf; + const char *parent_slash; + gs_free char *parent_path = NULL; int dest_parent_dfd = -1; - - relative_path = g_file_get_relative_path (modified_etc, src); - g_assert (relative_path); - dest = g_file_resolve_relative_path (new_etc, relative_path); - - src_info = g_file_query_info (src, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, error); - 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) + if (fstatat (modified_etc_fd, path, &modified_stbuf, AT_SYMLINK_NOFOLLOW) < 0) { - 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)); + ot_util_set_error_from_errno (error, errno); + g_prefix_error (error, "Failed to read modified config file '%s': ", path); 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) + parent_slash = strrchr (path, '/'); + if (parent_slash != NULL) { - src_parent_dfd = open (gs_file_get_path_cached (src_parent), - O_RDONLY | O_NOCTTY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC); - if (src_parent_dfd == -1) + parent_path = g_strndup (path, parent_slash - path); + dest_parent_dfd = openat (new_etc_fd, parent_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW | O_NOCTTY); + if (dest_parent_dfd == -1) { - ot_util_set_error_from_errno (error, errno); + if (errno == ENOENT) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "New tree removes parent directory '%s', cannot merge", + parent_path); + } + else + { + g_prefix_error (error, "openat: "); + ot_util_set_error_from_errno (error, errno); + } goto out; } - src_parent_dir = fdopendir (src_parent_dfd); - if (!src_parent_dir) + } + else + { + parent_path = NULL; + dest_parent_dfd = dup (new_etc_fd); + if (dest_parent_dfd == -1) { ot_util_set_error_from_errno (error, errno); goto out; } - dest_parent_dfd = open (gs_file_get_path_cached (dest_parent), - O_RDONLY | O_NOCTTY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC); - if (dest_parent_dfd == -1) + } + + if (fstatat (new_etc_fd, path, &new_stbuf, AT_SYMLINK_NOFOLLOW) < 0) + { + if (errno == ENOENT) + ; + else { ot_util_set_error_from_errno (error, errno); goto out; } - if (!copy_dir_recurse_fsync (src_parent_dir, dest_parent_dfd, - gs_file_get_basename_cached (src), - cancellable, error)) - goto out; + } + else if (S_ISDIR(new_stbuf.st_mode)) + { + if (!S_ISDIR(modified_stbuf.st_mode)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Modified config file newly defaults to directory '%s', cannot merge", + parent_path); + goto out; + } + else + { + /* Do nothing here - we assume that we've already + * recursively copied the parent directory. + */ + ret = TRUE; + goto out; + } } else { - 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; - if (g_file_info_get_file_type (src_info) == G_FILE_TYPE_REGULAR) + if (unlinkat (new_etc_fd, path, 0) < 0) { - if (!gs_file_sync_data (dest, cancellable, error)) - goto out; + ot_util_set_error_from_errno (error, errno); + goto out; } - if (!ot_util_fsync_directory (dest_parent, cancellable, error)) + } + + if (S_ISDIR (modified_stbuf.st_mode)) + { + if (!copy_dir_recurse_fsync (modified_etc_fd, new_etc_fd, path, + cancellable, error)) + goto out; + } + else if (S_ISLNK (modified_stbuf.st_mode) || S_ISREG (modified_stbuf.st_mode)) + { + if (!copy_one_file_fsync_at (modified_etc_fd, new_etc_fd, + &modified_stbuf, path, + cancellable, error)) goto out; } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unsupported non-regular/non-symlink file in /etc '%s'", + path); + goto out; + } - ret = TRUE; - out: - if (src_parent_dir) + if (fsync (dest_parent_dfd) != 0) { - (void) closedir (src_parent_dir); - src_parent_dfd = -1; + ot_util_set_error_from_errno (error, errno); + goto out; } - if (src_parent_dfd != -1) - (void) close (src_parent_dfd); + + ret = TRUE; + out: if (dest_parent_dfd != -1) (void) close (dest_parent_dfd); return ret; @@ -411,6 +437,9 @@ merge_etc_changes (GFile *orig_etc, gs_unref_ptrarray GPtrArray *removed = NULL; gs_unref_ptrarray GPtrArray *added = NULL; guint i; + int orig_etc_fd = -1; + int modified_etc_fd = -1; + int new_etc_fd = -1; modified = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_diff_item_unref); removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); @@ -438,6 +467,13 @@ merge_etc_changes (GFile *orig_etc, removed->len, added->len); + if (!gs_file_open_dir_fd (orig_etc, &orig_etc_fd, cancellable, error)) + goto out; + if (!gs_file_open_dir_fd (modified_etc, &modified_etc_fd, cancellable, error)) + goto out; + if (!gs_file_open_dir_fd (new_etc, &new_etc_fd, cancellable, error)) + goto out; + for (i = 0; i < removed->len; i++) { GFile *file = removed->pdata[i]; @@ -455,22 +491,34 @@ merge_etc_changes (GFile *orig_etc, for (i = 0; i < modified->len; i++) { OstreeDiffItem *diff = modified->pdata[i]; + gs_free char *path = g_file_get_relative_path (modified_etc, diff->target); - if (!copy_modified_config_file (orig_etc, modified_etc, new_etc, diff->target, + g_assert (path); + + if (!copy_modified_config_file (orig_etc_fd, modified_etc_fd, new_etc_fd, path, cancellable, error)) goto out; } for (i = 0; i < added->len; i++) { GFile *file = added->pdata[i]; + gs_free char *path = g_file_get_relative_path (modified_etc, file); + + g_assert (path); - if (!copy_modified_config_file (orig_etc, modified_etc, new_etc, file, + if (!copy_modified_config_file (orig_etc_fd, modified_etc_fd, new_etc_fd, path, cancellable, error)) goto out; } ret = TRUE; out: + if (orig_etc_fd != -1) + (void) close (orig_etc_fd); + if (modified_etc_fd != -1) + (void) close (modified_etc_fd); + if (new_etc_fd != -1) + (void) close (new_etc_fd); return ret; } diff --git a/tests/test-admin-deploy-etcmerge-cornercases.sh b/tests/test-admin-deploy-etcmerge-cornercases.sh index 09386aa2..17900941 100644 --- a/tests/test-admin-deploy-etcmerge-cornercases.sh +++ b/tests/test-admin-deploy-etcmerge-cornercases.sh @@ -95,4 +95,31 @@ rev=$(ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runt assert_has_file sysroot/ostree/deploy/testos/deploy/$rev.0/etc/initially-empty/afile assert_has_file sysroot/ostree/deploy/testos/deploy/$rev.0/etc/initially-empty/bfile +# Replace config file with default directory +cd "${test_tmpdir}/osdata" +mkdir usr/etc/somenewdir +ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Add default dir" +cd ${test_tmpdir} +rev=$(ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime) +newconfpath=sysroot/ostree/deploy/testos/deploy/${rev}.0/etc/somenewdir +echo "some content blah" > ${newconfpath} +if ostree admin --sysroot=sysroot upgrade --os=testos 2>err.txt; then + assert_not_reached "upgrade should have failed" +fi +assert_file_has_content err.txt "Modified config file newly defaults to directory" +rm ${newconfpath} + +# Remove parent directory of modified config file +cd "${test_tmpdir}/osdata" +rm -rf usr/etc/initially-empty +ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Remove default dir" +cd ${test_tmpdir} +newconfpath=sysroot/ostree/deploy/testos/deploy/${rev}.0/etc/initially-empty/mynewfile +touch ${newconfpath} +if ostree admin --sysroot=sysroot upgrade --os=testos 2>err.txt; then + assert_not_reached "upgrade should have failed" +fi +assert_file_has_content err.txt "New tree removes parent directory" +rm ${newconfpath} + echo "ok" |