mirror of
https://github.com/ostreedev/ostree.git
synced 2024-12-22 17:35:55 +03:00
checkout: Merge union/add logic for copies during checkout
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
This commit is contained in:
parent
b7afe91e21
commit
511b31cfb5
@ -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 \
|
||||
|
@ -154,129 +154,66 @@ write_regular_file_content (OstreeRepo *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
int res;
|
||||
|
||||
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 (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES)
|
||||
{
|
||||
/* Note early return */
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
return glnx_throw_errno (error);
|
||||
}
|
||||
|
||||
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.
|
||||
* Create a copy of a file, supporting optional union/add 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)
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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 (symlinkat (g_file_info_get_symlink_target (file_info),
|
||||
destination_dfd, destination_name) < 0)
|
||||
{
|
||||
if (!glnx_dfd_name_set_all_xattrs (destination_dfd, temp_filename,
|
||||
xattrs, cancellable, error))
|
||||
/* Handle union/add behaviors if we get EEXIST */
|
||||
if (errno == EEXIST && union_mode)
|
||||
{
|
||||
/* 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 (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)
|
||||
{
|
||||
|
56
tests/test-basic-root.sh
Executable file
56
tests/test-basic-root.sh
Executable file
@ -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"
|
Loading…
Reference in New Issue
Block a user