diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 33ca0ae8..49251284 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -3073,6 +3073,20 @@ copy_detached_metadata (OstreeRepo *self, return TRUE; } +/* Special case between bare-user and bare-user-only, + * mostly for https://github.com/flatpak/flatpak/issues/845 + * see below for any more comments. + */ +static gboolean +import_is_bareuser_only_conversion (OstreeRepo *src_repo, + OstreeRepo *dest_repo, + OstreeObjectType objtype) +{ + return src_repo->mode == OSTREE_REPO_MODE_BARE_USER + && dest_repo->mode == OSTREE_REPO_MODE_BARE_USER_ONLY + && objtype == OSTREE_OBJECT_TYPE_FILE; +} + static gboolean import_one_object_link (OstreeRepo *self, OstreeRepo *source, @@ -3085,6 +3099,33 @@ import_one_object_link (OstreeRepo *self, char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; _ostree_loose_path (loose_path_buf, checksum, objtype, self->mode); + /* Hardlinking between bare-user → bare-user-only is only possible for regular + * files, *not* symlinks, which in bare-user are stored as regular files. At + * this point we need to parse the file to see the difference. + */ + if (import_is_bareuser_only_conversion (source, self, objtype)) + { + g_autoptr(GFileInfo) finfo = NULL; + + if (!ostree_repo_load_file (source, checksum, NULL, &finfo, NULL, + cancellable, error)) + return FALSE; + + switch (g_file_info_get_file_type (finfo)) + { + case G_FILE_TYPE_REGULAR: + /* This is OK, we'll drop through and try a hardlink */ + break; + case G_FILE_TYPE_SYMBOLIC_LINK: + /* NOTE early return */ + *out_was_supported = FALSE; + return TRUE; + default: + g_assert_not_reached (); + break; + } + } + if (!_ostree_repo_ensure_loose_objdir_at (self->objects_dir_fd, loose_path_buf, cancellable, error)) return FALSE; @@ -3157,6 +3198,11 @@ import_via_hardlink_is_possible (OstreeRepo *src_repo, /* Metadata is identical between all modes */ if (OSTREE_OBJECT_TYPE_IS_META (objtype)) return TRUE; + /* And now a special case between bare-user and bare-user-only, + * mostly for https://github.com/flatpak/flatpak/issues/845 + */ + if (import_is_bareuser_only_conversion (src_repo, dest_repo, objtype)) + return TRUE; return FALSE; } diff --git a/tests/libtest.sh b/tests/libtest.sh index 1774a7b6..15802dfd 100755 --- a/tests/libtest.sh +++ b/tests/libtest.sh @@ -116,10 +116,16 @@ else fi fi +files_are_hardlinked() { + f1=$(stat -c %i $1) + f2=$(stat -c %i $2) + [ "$f1" == "$f2" ] +} + assert_files_hardlinked() { f1=$(stat -c %i $1) f2=$(stat -c %i $2) - if [ "$f1" != "$f2" ]; then + if ! files_are_hardlinked "$f1" "$f2"; then fatal "Files '$1' and '$2' are not hardlinked" fi } @@ -512,12 +518,22 @@ ostree_file_path_to_checksum() { $CMD_PREFIX ostree --repo=$repo ls -C $ref $path | awk '{ print $5 }' } +# Given a path to a file in a repo for a ref, print the (relative) path to its +# object +ostree_file_path_to_relative_object_path() { + repo=$1 + ref=$2 + path=$3 + checksum=$(ostree_file_path_to_checksum $repo $ref $path) + test -n "${checksum}" + echo objects/${checksum:0:2}/${checksum:2}.file +} + # Given a path to a file in a repo for a ref, print the path to its object ostree_file_path_to_object_path() { - repo=$1 - ref=$2 - path=$3 - checksum=$(ostree_file_path_to_checksum $repo $ref $path) - test -n "${checksum}" - echo ${repo}/objects/${checksum:0:2}/${checksum:2}.file + repo=$1 + ref=$2 + path=$3 + relpath=$(ostree_file_path_to_relative_object_path $repo $ref $path) + echo ${repo}/${relpath} } diff --git a/tests/test-basic-user-only.sh b/tests/test-basic-user-only.sh index 29fbbdd3..fb071fe4 100755 --- a/tests/test-basic-user-only.sh +++ b/tests/test-basic-user-only.sh @@ -22,7 +22,7 @@ set -euo pipefail . $(dirname $0)/libtest.sh setup_test_repository "bare-user-only" -extra_basic_tests=3 +extra_basic_tests=4 . $(dirname $0)/basic-test.sh # Reset things so we don't inherit a lot of state from earlier tests @@ -71,3 +71,22 @@ $CMD_PREFIX ostree pull-local --repo=repo repo-input $CMD_PREFIX ostree --repo=repo checkout -U -H content-with-dir-world-writable dir-co assert_file_has_mode dir-co/worldwritable-dir 775 echo "ok didn't make world-writable dir" + +cd ${test_tmpdir} +rm repo-input -rf +rm repo -rf +ostree_repo_init repo init --mode=bare-user-only +ostree_repo_init repo-input init --mode=bare-user +rm files -rf && mkdir files +echo afile > files/afile +ln -s afile files/afile-link +$CMD_PREFIX ostree --repo=repo-input commit --canonical-permissions -b testtree --tree=dir=files +afile_relobjpath=$(ostree_file_path_to_relative_object_path repo-input testtree /afile) +afile_link_relobjpath=$(ostree_file_path_to_relative_object_path repo-input testtree /afile-link) +$CMD_PREFIX ostree pull-local --repo=repo repo-input +assert_files_hardlinked repo/${afile_relobjpath} repo-input/${afile_relobjpath} +if files_are_hardlinked repo/${afile_link_relobjpath} repo-input/${afile_link_relobjpath}; then + assert_not_reached "symlinks hardlinked across bare-user?" +fi +$OSTREE fsck -q +echo "ok hardlink pull from bare-user"