From 35bdb9cee7dbb7f8733cab1b1fe327f525496773 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 11 Jun 2024 16:05:57 +0200 Subject: [PATCH 1/8] Support hard links in tarballs Fixes #10395. --- src/libfetchers/git-utils.cc | 48 +++++++++++++++++++++++++++++++++-- src/libfetchers/git-utils.hh | 2 +- src/libutil/fs-sink.hh | 13 ++++++++++ src/libutil/tarfile.cc | 11 +++++--- src/libutil/tarfile.hh | 2 +- tests/functional/tarball.sh | 9 +++++++ tests/functional/tree.tar.gz | Bin 0 -> 298 bytes 7 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 tests/functional/tree.tar.gz diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index 2ea1e15ed..32b35931a 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -115,10 +115,10 @@ git_oid hashToOID(const Hash & hash) return oid; } -Object lookupObject(git_repository * repo, const git_oid & oid) +Object lookupObject(git_repository * repo, const git_oid & oid, git_object_t type = GIT_OBJECT_ANY) { Object obj; - if (git_object_lookup(Setter(obj), repo, &oid, GIT_OBJECT_ANY)) { + if (git_object_lookup(Setter(obj), repo, &oid, type)) { auto err = git_error_last(); throw Error("getting Git object '%s': %s", oid, err->message); } @@ -909,6 +909,50 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink addToTree(*pathComponents.rbegin(), oid, GIT_FILEMODE_LINK); } + void createHardlink(const Path & path, const CanonPath & target) override + { + auto pathComponents = tokenizeString>(path, "/"); + if (!prepareDirs(pathComponents, false)) return; + + auto relTarget = CanonPath(path).parent()->makeRelative(target); + + auto dir = pendingDirs.rbegin(); + + // For each ../ component at the start, go up one directory. + std::string_view relTargetLeft(relTarget); + while (hasPrefix(relTargetLeft, "../")) { + if (dir == pendingDirs.rend()) + throw Error("invalid hard link target '%s'", target); + ++dir; + relTargetLeft = relTargetLeft.substr(3); + } + + // Look up the remainder of the target, starting at the + // top-most `git_treebuilder`. + std::variant curDir{dir->builder.get()}; + Object tree; // needed to keep `entry` alive + const git_tree_entry * entry = nullptr; + + for (auto & c : CanonPath(relTargetLeft)) { + if (auto builder = std::get_if(&curDir)) { + if (!(entry = git_treebuilder_get(*builder, std::string(c).c_str()))) + throw Error("cannot find hard link target '%s'", target); + curDir = *git_tree_entry_id(entry); + } else if (auto oid = std::get_if(&curDir)) { + tree = lookupObject(*repo, *oid, GIT_OBJECT_TREE); + if (!(entry = git_tree_entry_byname((const git_tree *) &*tree, std::string(c).c_str()))) + throw Error("cannot find hard link target '%s'", target); + curDir = *git_tree_entry_id(entry); + } + } + + assert(entry); + + addToTree(*pathComponents.rbegin(), + *git_tree_entry_id(entry), + git_tree_entry_filemode(entry)); + } + Hash sync() override { updateBuilders({}); diff --git a/src/libfetchers/git-utils.hh b/src/libfetchers/git-utils.hh index 29d799554..495916f62 100644 --- a/src/libfetchers/git-utils.hh +++ b/src/libfetchers/git-utils.hh @@ -7,7 +7,7 @@ namespace nix { namespace fetchers { struct PublicKey; } -struct GitFileSystemObjectSink : FileSystemObjectSink +struct GitFileSystemObjectSink : ExtendedFileSystemObjectSink { /** * Flush builder and return a final Git hash. diff --git a/src/libutil/fs-sink.hh b/src/libutil/fs-sink.hh index ae577819a..994f19960 100644 --- a/src/libutil/fs-sink.hh +++ b/src/libutil/fs-sink.hh @@ -41,6 +41,19 @@ struct FileSystemObjectSink virtual void createSymlink(const Path & path, const std::string & target) = 0; }; +/** + * An extension of `FileSystemObjectSink` that supports file types + * that are not supported by Nix's FSO model. + */ +struct ExtendedFileSystemObjectSink : FileSystemObjectSink +{ + /** + * Create a hard link. The target must be the path of a previously + * encountered file relative to the root of the FSO. + */ + virtual void createHardlink(const Path & path, const CanonPath & target) = 0; +}; + /** * Recursively copy file system objects from the source into the sink. */ diff --git a/src/libutil/tarfile.cc b/src/libutil/tarfile.cc index 6bb2bd2f3..e45cfaf85 100644 --- a/src/libutil/tarfile.cc +++ b/src/libutil/tarfile.cc @@ -163,7 +163,7 @@ void unpackTarfile(const Path & tarFile, const Path & destDir) extract_archive(archive, destDir); } -time_t unpackTarfileToSink(TarArchive & archive, FileSystemObjectSink & parseSink) +time_t unpackTarfileToSink(TarArchive & archive, ExtendedFileSystemObjectSink & parseSink) { time_t lastModified = 0; @@ -183,7 +183,12 @@ time_t unpackTarfileToSink(TarArchive & archive, FileSystemObjectSink & parseSin lastModified = std::max(lastModified, archive_entry_mtime(entry)); - switch (archive_entry_filetype(entry)) { + if (auto target = archive_entry_hardlink(entry)) { + parseSink.createHardlink(path, CanonPath(target)); + continue; + } + + switch (auto type = archive_entry_filetype(entry)) { case AE_IFDIR: parseSink.createDirectory(path); @@ -220,7 +225,7 @@ time_t unpackTarfileToSink(TarArchive & archive, FileSystemObjectSink & parseSin } default: - throw Error("file '%s' in tarball has unsupported file type", path); + throw Error("file '%s' in tarball has unsupported file type %d", path, type); } } diff --git a/src/libutil/tarfile.hh b/src/libutil/tarfile.hh index 705d211e4..0517177db 100644 --- a/src/libutil/tarfile.hh +++ b/src/libutil/tarfile.hh @@ -41,6 +41,6 @@ void unpackTarfile(Source & source, const Path & destDir); void unpackTarfile(const Path & tarFile, const Path & destDir); -time_t unpackTarfileToSink(TarArchive & archive, FileSystemObjectSink & parseSink); +time_t unpackTarfileToSink(TarArchive & archive, ExtendedFileSystemObjectSink & parseSink); } diff --git a/tests/functional/tarball.sh b/tests/functional/tarball.sh index ce162ddce..5d4749eb2 100755 --- a/tests/functional/tarball.sh +++ b/tests/functional/tarball.sh @@ -59,3 +59,12 @@ test_tarball() { test_tarball '' cat test_tarball .xz xz test_tarball .gz gzip + +# Test hard links. +path="$(nix flake prefetch --json "tarball+file://$(pwd)/tree.tar.gz" | jq -r .storePath)" +[[ $(cat "$path/a/b/foo") = bar ]] +[[ $(cat "$path/a/b/xyzzy") = bar ]] +[[ $(cat "$path/a/yyy") = bar ]] +[[ $(cat "$path/a/zzz") = bar ]] +[[ $(cat "$path/c/aap") = bar ]] +[[ $(cat "$path/fnord") = bar ]] diff --git a/tests/functional/tree.tar.gz b/tests/functional/tree.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..f1f1d996d8494c930ede3d0b66574c8e6efb014c GIT binary patch literal 298 zcmV+_0oDE=iwFP!000001MQdHYQ!KAMtzjLLC2rb=W)00RcVTwLX)R&bY%;LZb_DL z3;oWG1caG*bVjF~(vy;fRswSwbzrLB+POM5ly=@4Vr!jOq%~Qs1{Th%@_wFT9tM@t z%W=FpFXeNOg!(cS|50`aZ1K;Ui+|$+{P&>wUzSBKMiJ~UzJKswt0k+hC6HKZ9E)eQ}53c@Cj`>+G#*Y3^#PAOQ00000000000KmO`0`*Phd;ll_0G_ItkN^Mx literal 0 HcmV?d00001 From bd37a70d8f0ed43c21ce3cf746e9a20bede221ca Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 11 Jun 2024 19:39:42 +0200 Subject: [PATCH 2/8] Update tests/functional/tarball.sh Co-authored-by: Robert Hensing --- tests/functional/tarball.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/functional/tarball.sh b/tests/functional/tarball.sh index 5d4749eb2..86b8ef2f5 100755 --- a/tests/functional/tarball.sh +++ b/tests/functional/tarball.sh @@ -61,6 +61,9 @@ test_tarball .xz xz test_tarball .gz gzip # Test hard links. +# All entries in tree.tar.gz refer to the same file, and all have the same inode when unpacked by GNU tar. +# We don't preserve the hard links, because that's an optimization we think is not worth the complexity, +# so we only make sure that the contents are copied correctly. path="$(nix flake prefetch --json "tarball+file://$(pwd)/tree.tar.gz" | jq -r .storePath)" [[ $(cat "$path/a/b/foo") = bar ]] [[ $(cat "$path/a/b/xyzzy") = bar ]] From efd4bf653325ce2a3f243923f39092e77af29fe3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 12 Jun 2024 14:41:35 +0200 Subject: [PATCH 3/8] Update src/libfetchers/git-utils.cc Co-authored-by: Robert Hensing --- src/libfetchers/git-utils.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index 32b35931a..ca02fbc89 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -914,11 +914,16 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink auto pathComponents = tokenizeString>(path, "/"); if (!prepareDirs(pathComponents, false)) return; + // We can't just look up the path from the start of the root, since + // some parent directories may not have finished yet, so we compute + // a relative path that helps us find the right git_tree_builder or object. auto relTarget = CanonPath(path).parent()->makeRelative(target); auto dir = pendingDirs.rbegin(); // For each ../ component at the start, go up one directory. + // CanonPath::makeRelative() always puts all .. elements at the start, + // so they're all handled by this loop: std::string_view relTargetLeft(relTarget); while (hasPrefix(relTargetLeft, "../")) { if (dir == pendingDirs.rend()) From 992912f3b4a0eb8aaa7f27e5cb67e351b6ab07ac Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 1 Jul 2024 17:12:34 +0200 Subject: [PATCH 4/8] test-support: Add TracingFileSystemObjectSink --- src/libutil/fs-sink.hh | 2 +- .../tests/tracing-file-system-object-sink.cc | 33 +++++++++++++++ .../tests/tracing-file-system-object-sink.hh | 41 +++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 tests/unit/libutil-support/tests/tracing-file-system-object-sink.cc create mode 100644 tests/unit/libutil-support/tests/tracing-file-system-object-sink.hh diff --git a/src/libutil/fs-sink.hh b/src/libutil/fs-sink.hh index 994f19960..32dcb2f01 100644 --- a/src/libutil/fs-sink.hh +++ b/src/libutil/fs-sink.hh @@ -45,7 +45,7 @@ struct FileSystemObjectSink * An extension of `FileSystemObjectSink` that supports file types * that are not supported by Nix's FSO model. */ -struct ExtendedFileSystemObjectSink : FileSystemObjectSink +struct ExtendedFileSystemObjectSink : virtual FileSystemObjectSink { /** * Create a hard link. The target must be the path of a previously diff --git a/tests/unit/libutil-support/tests/tracing-file-system-object-sink.cc b/tests/unit/libutil-support/tests/tracing-file-system-object-sink.cc new file mode 100644 index 000000000..737e02213 --- /dev/null +++ b/tests/unit/libutil-support/tests/tracing-file-system-object-sink.cc @@ -0,0 +1,33 @@ +#include +#include "tracing-file-system-object-sink.hh" + +namespace nix::test { + +void TracingFileSystemObjectSink::createDirectory(const Path & path) +{ + std::cerr << "createDirectory(" << path << ")\n"; + sink.createDirectory(path); +} + +void TracingFileSystemObjectSink::createRegularFile(const Path & path, std::function fn) +{ + std::cerr << "createRegularFile(" << path << ")\n"; + sink.createRegularFile(path, [&](CreateRegularFileSink & crf) { + // We could wrap this and trace about the chunks of data and such + fn(crf); + }); +} + +void TracingFileSystemObjectSink::createSymlink(const Path & path, const std::string & target) +{ + std::cerr << "createSymlink(" << path << ", target: " << target << ")\n"; + sink.createSymlink(path, target); +} + +void TracingExtendedFileSystemObjectSink::createHardlink(const Path & path, const CanonPath & target) +{ + std::cerr << "createHardlink(" << path << ", target: " << target << ")\n"; + sink.createHardlink(path, target); +} + +} // namespace nix::test diff --git a/tests/unit/libutil-support/tests/tracing-file-system-object-sink.hh b/tests/unit/libutil-support/tests/tracing-file-system-object-sink.hh new file mode 100644 index 000000000..9527b0be3 --- /dev/null +++ b/tests/unit/libutil-support/tests/tracing-file-system-object-sink.hh @@ -0,0 +1,41 @@ +#pragma once +#include "fs-sink.hh" + +namespace nix::test { + +/** + * A `FileSystemObjectSink` that traces calls, writing to stderr. + */ +class TracingFileSystemObjectSink : public virtual FileSystemObjectSink +{ + FileSystemObjectSink & sink; +public: + TracingFileSystemObjectSink(FileSystemObjectSink & sink) + : sink(sink) + { + } + + void createDirectory(const Path & path) override; + + void createRegularFile(const Path & path, std::function fn); + + void createSymlink(const Path & path, const std::string & target); +}; + +/** + * A `ExtendedFileSystemObjectSink` that traces calls, writing to stderr. + */ +class TracingExtendedFileSystemObjectSink : public TracingFileSystemObjectSink, public ExtendedFileSystemObjectSink +{ + ExtendedFileSystemObjectSink & sink; +public: + TracingExtendedFileSystemObjectSink(ExtendedFileSystemObjectSink & sink) + : TracingFileSystemObjectSink(sink) + , sink(sink) + { + } + + void createHardlink(const Path & path, const CanonPath & target); +}; + +} From 1fac22b16e216e3ab8850d542be587eb379605b6 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 1 Jul 2024 17:13:48 +0200 Subject: [PATCH 5/8] GitFileSystemObjectSink: Add path context to some messages --- src/libfetchers/git-utils.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index ca02fbc89..c41cfe683 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -927,7 +927,7 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink std::string_view relTargetLeft(relTarget); while (hasPrefix(relTargetLeft, "../")) { if (dir == pendingDirs.rend()) - throw Error("invalid hard link target '%s'", target); + throw Error("invalid hard link target '%s' for path '%s'", target, path); ++dir; relTargetLeft = relTargetLeft.substr(3); } @@ -940,13 +940,14 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink for (auto & c : CanonPath(relTargetLeft)) { if (auto builder = std::get_if(&curDir)) { + assert(*builder); if (!(entry = git_treebuilder_get(*builder, std::string(c).c_str()))) - throw Error("cannot find hard link target '%s'", target); + throw Error("cannot find hard link target '%s' for path '%s'", target, path); curDir = *git_tree_entry_id(entry); } else if (auto oid = std::get_if(&curDir)) { tree = lookupObject(*repo, *oid, GIT_OBJECT_TREE); if (!(entry = git_tree_entry_byname((const git_tree *) &*tree, std::string(c).c_str()))) - throw Error("cannot find hard link target '%s'", target); + throw Error("cannot find hard link target '%s' for path '%s'", target, path); curDir = *git_tree_entry_id(entry); } } From a409c1a882e65878476f58d033321eb05e163ab5 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 1 Jul 2024 17:22:26 +0200 Subject: [PATCH 6/8] Start unit testing GitFileSystemObjectSink --- tests/unit/libfetchers/git-utils.cc | 90 +++++++++++++++++++++++++++++ tests/unit/libfetchers/local.mk | 2 +- 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 tests/unit/libfetchers/git-utils.cc diff --git a/tests/unit/libfetchers/git-utils.cc b/tests/unit/libfetchers/git-utils.cc new file mode 100644 index 000000000..c007d1c3c --- /dev/null +++ b/tests/unit/libfetchers/git-utils.cc @@ -0,0 +1,90 @@ +#include "git-utils.hh" +#include "file-system.hh" +#include "gmock/gmock.h" +#include +#include +#include +#include +#include "fs-sink.hh" +#include "serialise.hh" + +namespace nix { + +class GitUtilsTest : public ::testing::Test +{ + // We use a single repository for all tests. + Path tmpDir; + std::unique_ptr delTmpDir; + +public: + void SetUp() override + { + tmpDir = createTempDir(); + delTmpDir = std::make_unique(tmpDir, true); + + // Create the repo with libgit2 + git_libgit2_init(); + git_repository * repo = nullptr; + auto r = git_repository_init(&repo, tmpDir.c_str(), 0); + ASSERT_EQ(r, 0); + git_repository_free(repo); + } + + void TearDown() override + { + // Destroy the AutoDelete, triggering removal + // not AutoDelete::reset(), which would cancel the deletion. + delTmpDir.reset(); + } + + ref openRepo() + { + return GitRepo::openRepo(tmpDir, true, false); + } +}; + +void writeString(CreateRegularFileSink & fileSink, std::string contents, bool executable) +{ + if (executable) + fileSink.isExecutable(); + fileSink.preallocateContents(contents.size()); + fileSink(contents); +} + +TEST_F(GitUtilsTest, sink_basic) +{ + auto repo = openRepo(); + auto sink = repo->getFileSystemObjectSink(); + + // TODO/Question: It seems a little odd that we use the tarball-like convention of requiring a top-level directory + // here + // The sync method does not document this behavior, should probably renamed because it's not very + // general, and I can't imagine that "non-conventional" archives or any other source to be handled by + // this sink. + + sink->createDirectory("foo-1.1"); + + sink->createRegularFile( + "foo-1.1/hello", [](CreateRegularFileSink & fileSink) { writeString(fileSink, "hello world", false); }); + sink->createRegularFile("foo-1.1/bye", [](CreateRegularFileSink & fileSink) { + writeString(fileSink, "thanks for all the fish", false); + }); + sink->createSymlink("foo-1.1/bye-link", "bye"); + sink->createDirectory("foo-1.1/empty"); + sink->createDirectory("foo-1.1/links"); + sink->createHardlink("foo-1.1/links/foo", CanonPath("foo-1.1/hello")); + + // sink->createHardlink("foo-1.1/links/foo-2", CanonPath("foo-1.1/hello")); + + auto result = sink->sync(); + auto accessor = repo->getAccessor(result, false); + auto entries = accessor->readDirectory(CanonPath::root); + ASSERT_EQ(entries.size(), 5); + ASSERT_EQ(accessor->readFile(CanonPath("hello")), "hello world"); + ASSERT_EQ(accessor->readFile(CanonPath("bye")), "thanks for all the fish"); + ASSERT_EQ(accessor->readLink(CanonPath("bye-link")), "bye"); + ASSERT_EQ(accessor->readDirectory(CanonPath("empty")).size(), 0); + ASSERT_EQ(accessor->readFile(CanonPath("links/foo")), "hello world"); +}; + +} // namespace nix diff --git a/tests/unit/libfetchers/local.mk b/tests/unit/libfetchers/local.mk index d576d28f3..f4e56a501 100644 --- a/tests/unit/libfetchers/local.mk +++ b/tests/unit/libfetchers/local.mk @@ -29,7 +29,7 @@ libfetchers-tests_LIBS = \ libstore-test-support libutil-test-support \ libfetchers libstore libutil -libfetchers-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS) +libfetchers-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS) $(LIBGIT2_LIBS) ifdef HOST_WINDOWS # Increase the default reserved stack size to 65 MB so Nix doesn't run out of space From f0329568b5afeddd03db4b969489e19a1bd2ee97 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 1 Jul 2024 17:14:27 +0200 Subject: [PATCH 7/8] GitFileSystemObjectSink: catch an overflow --- src/libfetchers/git-utils.cc | 2 ++ tests/unit/libfetchers/git-utils.cc | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index c41cfe683..64fd39aed 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -931,6 +931,8 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink ++dir; relTargetLeft = relTargetLeft.substr(3); } + if (dir == pendingDirs.rend()) + throw Error("invalid hard link target '%s' for path '%s'", target, path); // Look up the remainder of the target, starting at the // top-most `git_treebuilder`. diff --git a/tests/unit/libfetchers/git-utils.cc b/tests/unit/libfetchers/git-utils.cc index c007d1c3c..3c06b593a 100644 --- a/tests/unit/libfetchers/git-utils.cc +++ b/tests/unit/libfetchers/git-utils.cc @@ -87,4 +87,24 @@ TEST_F(GitUtilsTest, sink_basic) ASSERT_EQ(accessor->readFile(CanonPath("links/foo")), "hello world"); }; +TEST_F(GitUtilsTest, sink_hardlink) +{ + auto repo = openRepo(); + auto sink = repo->getFileSystemObjectSink(); + + sink->createDirectory("foo-1.1"); + + sink->createRegularFile( + "foo-1.1/hello", [](CreateRegularFileSink & fileSink) { writeString(fileSink, "hello world", false); }); + + try { + sink->createHardlink("foo-1.1/link", CanonPath("hello")); + FAIL() << "Expected an exception"; + } catch (const nix::Error & e) { + ASSERT_THAT(e.msg(), testing::HasSubstr("invalid hard link target")); + ASSERT_THAT(e.msg(), testing::HasSubstr("/hello")); + ASSERT_THAT(e.msg(), testing::HasSubstr("foo-1.1/link")); + } +}; + } // namespace nix From 4fd8f19ecfd8ced21c0f43bb3f3e3567d1d38bcd Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 11 Jul 2024 12:14:48 +0200 Subject: [PATCH 8/8] Fix build to use CanonPath in new FSO sinks --- src/libfetchers/git-utils.cc | 7 +++-- src/libutil/fs-sink.hh | 2 +- src/libutil/tarfile.cc | 2 +- tests/unit/libfetchers/git-utils.cc | 26 ++++++++++--------- .../tests/tracing-file-system-object-sink.cc | 9 ++++--- .../tests/tracing-file-system-object-sink.hh | 8 +++--- 6 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index a88bdc8b6..ecc71ae47 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -909,9 +909,12 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink addToTree(*pathComponents.rbegin(), oid, GIT_FILEMODE_LINK); } - void createHardlink(const Path & path, const CanonPath & target) override + void createHardlink(const CanonPath & path, const CanonPath & target) override { - auto pathComponents = tokenizeString>(path, "/"); + std::vector pathComponents; + for (auto & c : path) + pathComponents.emplace_back(c); + if (!prepareDirs(pathComponents, false)) return; // We can't just look up the path from the start of the root, since diff --git a/src/libutil/fs-sink.hh b/src/libutil/fs-sink.hh index e5e240073..774c0d942 100644 --- a/src/libutil/fs-sink.hh +++ b/src/libutil/fs-sink.hh @@ -51,7 +51,7 @@ struct ExtendedFileSystemObjectSink : virtual FileSystemObjectSink * Create a hard link. The target must be the path of a previously * encountered file relative to the root of the FSO. */ - virtual void createHardlink(const Path & path, const CanonPath & target) = 0; + virtual void createHardlink(const CanonPath & path, const CanonPath & target) = 0; }; /** diff --git a/src/libutil/tarfile.cc b/src/libutil/tarfile.cc index f3b2f55b5..2e3236295 100644 --- a/src/libutil/tarfile.cc +++ b/src/libutil/tarfile.cc @@ -196,7 +196,7 @@ time_t unpackTarfileToSink(TarArchive & archive, ExtendedFileSystemObjectSink & lastModified = std::max(lastModified, archive_entry_mtime(entry)); if (auto target = archive_entry_hardlink(entry)) { - parseSink.createHardlink(path, CanonPath(target)); + parseSink.createHardlink(cpath, CanonPath(target)); continue; } diff --git a/tests/unit/libfetchers/git-utils.cc b/tests/unit/libfetchers/git-utils.cc index 3c06b593a..d3547ec6a 100644 --- a/tests/unit/libfetchers/git-utils.cc +++ b/tests/unit/libfetchers/git-utils.cc @@ -62,17 +62,18 @@ TEST_F(GitUtilsTest, sink_basic) // general, and I can't imagine that "non-conventional" archives or any other source to be handled by // this sink. - sink->createDirectory("foo-1.1"); + sink->createDirectory(CanonPath("foo-1.1")); - sink->createRegularFile( - "foo-1.1/hello", [](CreateRegularFileSink & fileSink) { writeString(fileSink, "hello world", false); }); - sink->createRegularFile("foo-1.1/bye", [](CreateRegularFileSink & fileSink) { + sink->createRegularFile(CanonPath("foo-1.1/hello"), [](CreateRegularFileSink & fileSink) { + writeString(fileSink, "hello world", false); + }); + sink->createRegularFile(CanonPath("foo-1.1/bye"), [](CreateRegularFileSink & fileSink) { writeString(fileSink, "thanks for all the fish", false); }); - sink->createSymlink("foo-1.1/bye-link", "bye"); - sink->createDirectory("foo-1.1/empty"); - sink->createDirectory("foo-1.1/links"); - sink->createHardlink("foo-1.1/links/foo", CanonPath("foo-1.1/hello")); + sink->createSymlink(CanonPath("foo-1.1/bye-link"), "bye"); + sink->createDirectory(CanonPath("foo-1.1/empty")); + sink->createDirectory(CanonPath("foo-1.1/links")); + sink->createHardlink(CanonPath("foo-1.1/links/foo"), CanonPath("foo-1.1/hello")); // sink->createHardlink("foo-1.1/links/foo-2", CanonPath("foo-1.1/hello")); @@ -92,13 +93,14 @@ TEST_F(GitUtilsTest, sink_hardlink) auto repo = openRepo(); auto sink = repo->getFileSystemObjectSink(); - sink->createDirectory("foo-1.1"); + sink->createDirectory(CanonPath("foo-1.1")); - sink->createRegularFile( - "foo-1.1/hello", [](CreateRegularFileSink & fileSink) { writeString(fileSink, "hello world", false); }); + sink->createRegularFile(CanonPath("foo-1.1/hello"), [](CreateRegularFileSink & fileSink) { + writeString(fileSink, "hello world", false); + }); try { - sink->createHardlink("foo-1.1/link", CanonPath("hello")); + sink->createHardlink(CanonPath("foo-1.1/link"), CanonPath("hello")); FAIL() << "Expected an exception"; } catch (const nix::Error & e) { ASSERT_THAT(e.msg(), testing::HasSubstr("invalid hard link target")); diff --git a/tests/unit/libutil-support/tests/tracing-file-system-object-sink.cc b/tests/unit/libutil-support/tests/tracing-file-system-object-sink.cc index 737e02213..122a09dcb 100644 --- a/tests/unit/libutil-support/tests/tracing-file-system-object-sink.cc +++ b/tests/unit/libutil-support/tests/tracing-file-system-object-sink.cc @@ -3,13 +3,14 @@ namespace nix::test { -void TracingFileSystemObjectSink::createDirectory(const Path & path) +void TracingFileSystemObjectSink::createDirectory(const CanonPath & path) { std::cerr << "createDirectory(" << path << ")\n"; sink.createDirectory(path); } -void TracingFileSystemObjectSink::createRegularFile(const Path & path, std::function fn) +void TracingFileSystemObjectSink::createRegularFile( + const CanonPath & path, std::function fn) { std::cerr << "createRegularFile(" << path << ")\n"; sink.createRegularFile(path, [&](CreateRegularFileSink & crf) { @@ -18,13 +19,13 @@ void TracingFileSystemObjectSink::createRegularFile(const Path & path, std::func }); } -void TracingFileSystemObjectSink::createSymlink(const Path & path, const std::string & target) +void TracingFileSystemObjectSink::createSymlink(const CanonPath & path, const std::string & target) { std::cerr << "createSymlink(" << path << ", target: " << target << ")\n"; sink.createSymlink(path, target); } -void TracingExtendedFileSystemObjectSink::createHardlink(const Path & path, const CanonPath & target) +void TracingExtendedFileSystemObjectSink::createHardlink(const CanonPath & path, const CanonPath & target) { std::cerr << "createHardlink(" << path << ", target: " << target << ")\n"; sink.createHardlink(path, target); diff --git a/tests/unit/libutil-support/tests/tracing-file-system-object-sink.hh b/tests/unit/libutil-support/tests/tracing-file-system-object-sink.hh index 9527b0be3..895ac3664 100644 --- a/tests/unit/libutil-support/tests/tracing-file-system-object-sink.hh +++ b/tests/unit/libutil-support/tests/tracing-file-system-object-sink.hh @@ -15,11 +15,11 @@ public: { } - void createDirectory(const Path & path) override; + void createDirectory(const CanonPath & path) override; - void createRegularFile(const Path & path, std::function fn); + void createRegularFile(const CanonPath & path, std::function fn) override; - void createSymlink(const Path & path, const std::string & target); + void createSymlink(const CanonPath & path, const std::string & target) override; }; /** @@ -35,7 +35,7 @@ public: { } - void createHardlink(const Path & path, const CanonPath & target); + void createHardlink(const CanonPath & path, const CanonPath & target) override; }; }