Skip to content

Commit

Permalink
checkout: Merge union/add logic for copies during checkout
Browse files Browse the repository at this point in the history
We really have an astonishing variety of similar functions which write files and
symlinks. I was working on a different PR and the duplication between the
union-mode and add-mode/none-mode checkout functions bothered me.

I realized that the "handle EEXIST" tri-state maps directly to the
`GLnxLinkTmpfileReplaceMode`, so deduping things makes even more sense.

Closes: #801
Approved by: jlebon
  • Loading branch information
cgwalters authored and rh-atomic-bot committed Apr 25, 2017
1 parent b7afe91 commit 511b31c
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 118 deletions.
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 <[email protected]>
#
# 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"

0 comments on commit 511b31c

Please sign in to comment.