diff --git a/.gitignore b/.gitignore index 04d96ca2c..647798491 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,9 @@ perl/Makefile.config /src/libexpr/nix.tbl /src/libexpr/tests/libnixexpr-tests +# /src/libfetchers +/src/libfetchers/tests/libnixfetchers-tests + # /src/libstore/ *.gen.* /src/libstore/tests/libnixstore-tests diff --git a/Makefile b/Makefile index 4f4ac0c6e..e6f3d63e7 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ UNIT_TEST_ENV = _NIX_TEST_UNIT_DATA=unit-test-data makefiles += \ src/libutil/tests/local.mk \ src/libstore/tests/local.mk \ + src/libfetchers/tests/local.mk \ src/libexpr/tests/local.mk endif diff --git a/src/libfetchers/input-accessor.cc b/src/libfetchers/input-accessor.cc index e5e3c1d72..bf24a5298 100644 --- a/src/libfetchers/input-accessor.cc +++ b/src/libfetchers/input-accessor.cc @@ -70,7 +70,7 @@ SourcePath SourcePath::followSymlinks() const { while (true) { // Basic cycle/depth limit to avoid infinite loops. if (++followCount >= maxFollow) - throw Error("too many symbolic links encountered while traversing the path '%s'", path); + throw Error("too many levels of symbolic links while traversing the path '%s'; assuming it leads to a cycle after following %d indirections", this->to_string(), maxFollow); if (path.lstat().type != InputAccessor::tSymlink) break; path = {path.accessor, CanonPath(path.readLink(), path.path.parent().value_or(CanonPath::root))}; } diff --git a/src/libfetchers/memory-input-accessor.cc b/src/libfetchers/memory-input-accessor.cc index 057f3e37f..2c35ff8b4 100644 --- a/src/libfetchers/memory-input-accessor.cc +++ b/src/libfetchers/memory-input-accessor.cc @@ -12,6 +12,14 @@ struct MemoryInputAccessorImpl : MemoryInputAccessor, MemorySourceAccessor MemorySourceAccessor::addFile(path, std::move(contents)) }; } + + SourcePath addSymlink(CanonPath path, std::string && contents) override + { + return { + ref(shared_from_this()), + MemorySourceAccessor::addSymlink(path, std::move(contents)) + }; + } }; ref makeMemoryInputAccessor() diff --git a/src/libfetchers/memory-input-accessor.hh b/src/libfetchers/memory-input-accessor.hh index b75b02bfd..5fb90466d 100644 --- a/src/libfetchers/memory-input-accessor.hh +++ b/src/libfetchers/memory-input-accessor.hh @@ -8,6 +8,7 @@ namespace nix { struct MemoryInputAccessor : InputAccessor { virtual SourcePath addFile(CanonPath path, std::string && contents) = 0; + virtual SourcePath addSymlink(CanonPath path, std::string && contents) = 0; }; ref makeMemoryInputAccessor(); diff --git a/src/libfetchers/tests/input-accessor.cc b/src/libfetchers/tests/input-accessor.cc new file mode 100644 index 000000000..30106c117 --- /dev/null +++ b/src/libfetchers/tests/input-accessor.cc @@ -0,0 +1,29 @@ +#include "../input-accessor.hh" +#include "../memory-input-accessor.hh" +#include "gmock/gmock.h" +#include +#include "terminal.hh" + +namespace nix { + +TEST(SourcePath, followSymlinks_cycle) { + auto fs = makeMemoryInputAccessor(); + fs->addSymlink({"origin", CanonPath::root}, "a"); + fs->addSymlink({"a", CanonPath::root}, "b"); + fs->addSymlink({"b", CanonPath::root}, "a"); + + ASSERT_TRUE(fs->pathExists({"a", CanonPath::root})); + SourcePath origin (fs->root() + "origin"); + try { + origin.followSymlinks(); + ASSERT_TRUE(false); + } catch (const Error &e) { + auto msg = filterANSIEscapes(e.what(), true); + // EXPECT_THAT(msg, ("too many levels of symbolic links")); + EXPECT_THAT(msg, testing::HasSubstr("too many levels of symbolic links")); + EXPECT_THAT(msg, testing::HasSubstr("'/origin'")); + EXPECT_THAT(msg, testing::HasSubstr("assuming it leads to a cycle after following 1000 indirections")); + } +} + +} \ No newline at end of file diff --git a/src/libfetchers/tests/local.mk b/src/libfetchers/tests/local.mk new file mode 100644 index 000000000..f372f8466 --- /dev/null +++ b/src/libfetchers/tests/local.mk @@ -0,0 +1,39 @@ +check: libfetchers-tests-exe_RUN + +programs += libfetchers-tests-exe + +libfetchers-tests-exe_NAME = libnixfetchers-tests + +libfetchers-tests-exe_DIR := $(d) + +ifeq ($(INSTALL_UNIT_TESTS), yes) + libfetchers-tests-exe_INSTALL_DIR := $(checkbindir) +else + libfetchers-tests-exe_INSTALL_DIR := +endif + + +libfetchers-tests-exe_LIBS = libfetchers-tests libstore + + +libfetchers-tests-exe_LDFLAGS := $(GTEST_LIBS) + +libraries += libfetchers-tests + +libfetchers-tests_NAME = libnixfetchers-tests + +libfetchers-tests_DIR := $(d) + +ifeq ($(INSTALL_UNIT_TESTS), yes) + libfetchers-tests_INSTALL_DIR := $(checklibdir) +else + libfetchers-tests_INSTALL_DIR := +endif + +libfetchers-tests_SOURCES := $(wildcard $(d)/*.cc) + +libfetchers-tests_CXXFLAGS += -I src/libfetchers -I src/libutil -I src/libstore + +libfetchers-tests_LIBS = libutil-tests libstore-tests libutil libstore libfetchers + +libfetchers-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS) diff --git a/src/libutil/memory-source-accessor.cc b/src/libutil/memory-source-accessor.cc index 78a4dd298..ef892a369 100644 --- a/src/libutil/memory-source-accessor.cc +++ b/src/libutil/memory-source-accessor.cc @@ -121,6 +121,19 @@ CanonPath MemorySourceAccessor::addFile(CanonPath path, std::string && contents) return path; } +CanonPath MemorySourceAccessor::addSymlink(CanonPath path, std::string &&contents) +{ + auto * f = open(path, File { File::Symlink {} }); + if (!f) + throw Error("file '%s' cannot be made because some parent file is not a directory", path); + if (auto * s = std::get_if(&f->raw)) + s->target = std::move(contents); + else + throw Error("file '%s' is not a symbolic link", path); + + return path; +} + using File = MemorySourceAccessor::File; diff --git a/src/libutil/memory-source-accessor.hh b/src/libutil/memory-source-accessor.hh index b908f3713..42fa9bea3 100644 --- a/src/libutil/memory-source-accessor.hh +++ b/src/libutil/memory-source-accessor.hh @@ -70,6 +70,7 @@ struct MemorySourceAccessor : virtual SourceAccessor File * open(const CanonPath & path, std::optional create); CanonPath addFile(CanonPath path, std::string && contents); + CanonPath addSymlink(CanonPath path, std::string && contents); }; /**