diff --git a/Makefile-tests.am b/Makefile-tests.am index 8389331d96..d2059e3acd 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -48,6 +48,7 @@ dist_test_scripts = \ tests/test-basic.sh \ tests/test-basic-user.sh \ tests/test-basic-user-only.sh \ + tests/test-basic-root.sh \ tests/test-pull-subpath.sh \ tests/test-archivez.sh \ tests/test-remote-add.sh \ diff --git a/src/libostree/ostree-repo-checkout.c b/src/libostree/ostree-repo-checkout.c index 4de6caf9d3..24bde4a4ad 100644 --- a/src/libostree/ostree-repo-checkout.c +++ b/src/libostree/ostree-repo-checkout.c @@ -154,129 +154,66 @@ write_regular_file_content (OstreeRepo *self, return TRUE; } +/* + * Create a copy of a file, supporting optional union/add behavior. + */ static gboolean -checkout_file_from_input_at (OstreeRepo *self, - OstreeRepoCheckoutAtOptions *options, - GFileInfo *file_info, - GVariant *xattrs, - GInputStream *input, - int destination_dfd, - const char *destination_name, - GCancellable *cancellable, - GError **error) +create_file_copy_from_input_at (OstreeRepo *repo, + OstreeRepoCheckoutAtOptions *options, + GFileInfo *file_info, + GVariant *xattrs, + GInputStream *input, + int destination_dfd, + const char *destination_name, + GCancellable *cancellable, + GError **error) { - int res; + g_autofree char *temp_filename = NULL; + const gboolean union_mode = options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES; + const gboolean add_mode = options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES; if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK) { - do - res = symlinkat (g_file_info_get_symlink_target (file_info), - destination_dfd, destination_name); - while (G_UNLIKELY (res == -1 && errno == EINTR)); - if (res == -1) + if (symlinkat (g_file_info_get_symlink_target (file_info), + destination_dfd, destination_name) < 0) { - if (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES) + /* Handle union/add behaviors if we get EEXIST */ + if (errno == EEXIST && union_mode) { - /* Note early return */ - return TRUE; + /* Unioning? Let's unlink and try again */ + (void) unlinkat (destination_dfd, destination_name, 0); + if (symlinkat (g_file_info_get_symlink_target (file_info), + destination_dfd, destination_name) < 0) + return glnx_throw_errno (error); } + else if (errno == EEXIST && add_mode) + /* Note early return - we don't want to set the xattrs below */ + return TRUE; else return glnx_throw_errno (error); } + /* Process ownership and xattrs now that we made the link */ if (options->mode != OSTREE_REPO_CHECKOUT_MODE_USER) { - if (G_UNLIKELY (fchownat (destination_dfd, destination_name, - g_file_info_get_attribute_uint32 (file_info, "unix::uid"), - g_file_info_get_attribute_uint32 (file_info, "unix::gid"), - AT_SYMLINK_NOFOLLOW) == -1)) - return glnx_throw_errno (error); - - if (xattrs) - { - if (!glnx_dfd_name_set_all_xattrs (destination_dfd, destination_name, - xattrs, cancellable, error)) - return FALSE; - } - } - } - else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR) - { - g_autoptr(GOutputStream) temp_out = NULL; - int fd; - guint32 file_mode; - - file_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); - /* Don't make setuid files on checkout when we're doing --user */ - if (options->mode == OSTREE_REPO_CHECKOUT_MODE_USER) - file_mode &= ~(S_ISUID|S_ISGID); - - do - fd = openat (destination_dfd, destination_name, O_WRONLY | O_CREAT | O_EXCL, file_mode); - while (G_UNLIKELY (fd == -1 && errno == EINTR)); - if (fd == -1) - { - if (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES) - { - /* Note early return */ - return TRUE; - } - else - return glnx_throw_errno (error); - } - temp_out = g_unix_output_stream_new (fd, TRUE); - fd = -1; /* Transfer ownership */ - - if (!write_regular_file_content (self, options, temp_out, file_info, xattrs, input, - cancellable, error)) - return FALSE; - } - else - g_assert_not_reached (); - - return TRUE; -} - -/* - * This function creates a file under a temporary name, then rename()s - * it into place. This implements union-like behavior. - */ -static gboolean -checkout_file_unioning_from_input_at (OstreeRepo *repo, - OstreeRepoCheckoutAtOptions *options, - GFileInfo *file_info, - GVariant *xattrs, - GInputStream *input, - int destination_dfd, - const char *destination_name, - GCancellable *cancellable, - GError **error) -{ - g_autofree char *temp_filename = NULL; - - if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK) - { - if (!_ostree_make_temporary_symlink_at (destination_dfd, - g_file_info_get_symlink_target (file_info), - &temp_filename, - cancellable, error)) - return FALSE; - - if (xattrs) - { - if (!glnx_dfd_name_set_all_xattrs (destination_dfd, temp_filename, - xattrs, cancellable, error)) + if (TEMP_FAILURE_RETRY (fchownat (destination_dfd, destination_name, + g_file_info_get_attribute_uint32 (file_info, "unix::uid"), + g_file_info_get_attribute_uint32 (file_info, "unix::gid"), + AT_SYMLINK_NOFOLLOW)) == -1) + return glnx_throw_errno_prefix (error, "fchownat"); + + if (xattrs != NULL && + !glnx_dfd_name_set_all_xattrs (destination_dfd, destination_name, + xattrs, cancellable, error)) return FALSE; } - if (G_UNLIKELY (renameat (destination_dfd, temp_filename, - destination_dfd, destination_name) == -1)) - return glnx_throw_errno (error); } else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR) { glnx_fd_close int temp_fd = -1; g_autoptr(GOutputStream) temp_out = NULL; guint32 file_mode; + GLnxLinkTmpfileReplaceMode replace_mode; file_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); /* Don't make setuid files on checkout when we're doing --user */ @@ -293,7 +230,15 @@ checkout_file_unioning_from_input_at (OstreeRepo *repo, cancellable, error)) return FALSE; - if (!glnx_link_tmpfile_at (destination_dfd, GLNX_LINK_TMPFILE_REPLACE, + /* The add/union/none behaviors map directly to GLnxLinkTmpfileReplaceMode */ + if (add_mode) + replace_mode = GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST; + else if (union_mode) + replace_mode = GLNX_LINK_TMPFILE_REPLACE; + else + replace_mode = GLNX_LINK_TMPFILE_NOREPLACE; + + if (!glnx_link_tmpfile_at (destination_dfd, replace_mode, temp_fd, temp_filename, destination_dfd, destination_name, error)) @@ -565,22 +510,11 @@ checkout_one_file_at (OstreeRepo *repo, cancellable, error)) return FALSE; - if (options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES) - { - if (!checkout_file_unioning_from_input_at (repo, options, source_info, xattrs, input, - destination_dfd, - destination_name, - cancellable, error)) - return g_prefix_error (error, "Union checkout of %s to %s: ", checksum, destination_name), FALSE; - } - else - { - if (!checkout_file_from_input_at (repo, options, source_info, xattrs, input, - destination_dfd, - destination_name, - cancellable, error)) - return g_prefix_error (error, "Checkout of %s to %s: ", checksum, destination_name), FALSE; - } + if (!create_file_copy_from_input_at (repo, options, source_info, xattrs, input, + destination_dfd, + destination_name, + cancellable, error)) + return g_prefix_error (error, "Copy checkout of %s to %s: ", checksum, destination_name), FALSE; if (input) { diff --git a/tests/test-basic-root.sh b/tests/test-basic-root.sh new file mode 100755 index 0000000000..a2fa880954 --- /dev/null +++ b/tests/test-basic-root.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# +# Copyright (C) 2011 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 -euo pipefail + +. $(dirname $0)/libtest.sh + +id=$(id -u) + +if test ${id} != 0; then + skip "continued basic tests must be run as root (possibly in a container)" +fi + +setup_test_repository "bare" + +echo "1..1" + +nextid=$(($id + 1)) + +rm checkout-test2 -rf +$OSTREE checkout test2 checkout-test2 +$OSTREE commit ${COMMIT_ARGS} -b test2 --tree=ref=test2 --owner-uid=$nextid +$OSTREE ls test2 baz/cow > ls.txt +assert_file_has_content ls.txt '-00644 '${nextid}' '${id} +# As bare and running as root (e.g. Docker container), do some ownership tests +# https://github.com/ostreedev/ostree/pull/801 +# Both hardlinks and copies should respect ownership, but we don't have -C yet; +# add it when we do. +for opt in -H; do + rm test2-co -rf + $OSTREE checkout ${opt} test2 test2-co + assert_streq "$(stat -c '%u' test2-co/baz/cow)" ${nextid} + assert_streq "$(stat -c '%u' test2-co/baz/alink)" ${nextid} +done +rm test2-co -rf +# But user mode doesn't +$OSTREE checkout -U test2 test2-co +assert_streq "$(stat -c '%u' test2-co/baz/cow)" ${id} +assert_streq "$(stat -c '%u' test2-co/baz/alink)" ${id} +echo "ok ownership"