Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

checkout: Merge union/add logic for copies during checkout #801

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile-tests.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
170 changes: 52 additions & 118 deletions src/libostree/ostree-repo-checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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))
Expand Down Expand Up @@ -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)
{
Expand Down
56 changes: 56 additions & 0 deletions tests/test-basic-root.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/bash
#
# Copyright (C) 2011 Colin Walters <walters@verbum.org>
#
# 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"