-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
treewide: Replace PosixSourceAccessor::createAtRoot with makeFSSourceAccessor #14809
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| #include "nix/util/fs-sink.hh" | ||
| #include "nix/util/file-system.hh" | ||
| #include "nix/util/processes.hh" | ||
|
|
||
| #include <gtest/gtest.h> | ||
| #include <gmock/gmock.h> | ||
| #include <rapidcheck/gtest.h> | ||
|
|
||
| namespace nix { | ||
|
|
||
| MATCHER_P2(HasContents, path, expected, "") | ||
| { | ||
| auto stat = arg->maybeLstat(path); | ||
| if (!stat) { | ||
| *result_listener << arg->showPath(path) << " does not exist"; | ||
| return false; | ||
| } | ||
| if (stat->type != SourceAccessor::tRegular) { | ||
| *result_listener << arg->showPath(path) << " is not a regular file"; | ||
| return false; | ||
| } | ||
| auto actual = arg->readFile(path); | ||
| if (actual != expected) { | ||
| *result_listener << arg->showPath(path) << " has contents " << ::testing::PrintToString(actual); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| MATCHER_P2(HasSymlink, path, target, "") | ||
| { | ||
| auto stat = arg->maybeLstat(path); | ||
| if (!stat) { | ||
| *result_listener << arg->showPath(path) << " does not exist"; | ||
| return false; | ||
| } | ||
| if (stat->type != SourceAccessor::tSymlink) { | ||
| *result_listener << arg->showPath(path) << " is not a symlink"; | ||
| return false; | ||
| } | ||
| auto actual = arg->readLink(path); | ||
| if (actual != target) { | ||
| *result_listener << arg->showPath(path) << " points to " << ::testing::PrintToString(actual); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| MATCHER_P2(HasDirectory, path, dirents, "") | ||
| { | ||
| auto stat = arg->maybeLstat(path); | ||
| if (!stat) { | ||
| *result_listener << arg->showPath(path) << " does not exist"; | ||
| return false; | ||
| } | ||
| if (stat->type != SourceAccessor::tDirectory) { | ||
| *result_listener << arg->showPath(path) << " is not a directory"; | ||
| return false; | ||
| } | ||
| auto actual = arg->readDirectory(path); | ||
| std::set<std::string> actualKeys, expectedKeys(dirents.begin(), dirents.end()); | ||
| for (auto & [k, _] : actual) | ||
| actualKeys.insert(k); | ||
| if (actualKeys != expectedKeys) { | ||
| *result_listener << arg->showPath(path) << " has entries " << ::testing::PrintToString(actualKeys); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| class FSSourceAccessorTest : public ::testing::Test | ||
| { | ||
| protected: | ||
| std::filesystem::path tmpDir; | ||
| std::unique_ptr<nix::AutoDelete> delTmpDir; | ||
|
|
||
| void SetUp() override | ||
| { | ||
| tmpDir = nix::createTempDir(); | ||
| delTmpDir = std::make_unique<nix::AutoDelete>(tmpDir, true); | ||
| } | ||
|
|
||
| void TearDown() override | ||
| { | ||
| delTmpDir.release(); | ||
| } | ||
| }; | ||
|
|
||
| TEST_F(FSSourceAccessorTest, works) | ||
| { | ||
| { | ||
| RestoreSink sink(false); | ||
| sink.dstPath = tmpDir; | ||
| sink.dirFd = openDirectory(tmpDir); | ||
| sink.createDirectory(CanonPath("subdir")); | ||
| sink.createRegularFile(CanonPath("file1"), [](CreateRegularFileSink & crf) { crf("content1"); }); | ||
| sink.createRegularFile(CanonPath("subdir/file2"), [](CreateRegularFileSink & crf) { crf("content2"); }); | ||
| sink.createSymlink(CanonPath("rootlink"), "target"); | ||
| sink.createDirectory(CanonPath("a")); | ||
| sink.createSymlink(CanonPath("a/dirlink"), "../subdir"); | ||
| } | ||
|
|
||
| EXPECT_THAT(makeFSSourceAccessor(tmpDir / "file1"), HasContents(CanonPath::root, "content1")); | ||
| EXPECT_THAT(makeFSSourceAccessor(tmpDir / "rootlink"), HasSymlink(CanonPath::root, "target")); | ||
| EXPECT_THAT( | ||
| makeFSSourceAccessor(tmpDir), | ||
| HasDirectory(CanonPath::root, std::set<std::string>{"file1", "subdir", "rootlink", "a"})); | ||
| EXPECT_THAT(makeFSSourceAccessor(tmpDir / "subdir"), HasDirectory(CanonPath::root, std::set<std::string>{"file2"})); | ||
|
|
||
| { | ||
| auto accessor = makeFSSourceAccessor(tmpDir); | ||
| EXPECT_THAT(accessor, HasContents(CanonPath("file1"), "content1")); | ||
| EXPECT_THAT(accessor, HasContents(CanonPath("subdir/file2"), "content2")); | ||
|
|
||
| EXPECT_TRUE(accessor->pathExists(CanonPath("file1"))); | ||
| EXPECT_FALSE(accessor->pathExists(CanonPath("nonexistent"))); | ||
|
|
||
| EXPECT_THROW(accessor->readFile(CanonPath("a/dirlink/file2")), SymlinkNotAllowed); | ||
| EXPECT_THROW(accessor->maybeLstat(CanonPath("a/dirlink/file2")), SymlinkNotAllowed); | ||
| EXPECT_THROW(accessor->readDirectory(CanonPath("a/dirlink")), SymlinkNotAllowed); | ||
| EXPECT_THROW(accessor->pathExists(CanonPath("a/dirlink/file2")), SymlinkNotAllowed); | ||
| } | ||
|
|
||
| { | ||
| auto accessor = makeFSSourceAccessor(tmpDir / "nonexistent"); | ||
| EXPECT_FALSE(accessor->maybeLstat(CanonPath::root)); | ||
| EXPECT_THROW(accessor->readFile(CanonPath::root), SystemError); | ||
| } | ||
|
|
||
| { | ||
| auto accessor = makeFSSourceAccessor(tmpDir, true); | ||
| EXPECT_EQ(accessor->getLastModified(), 0); | ||
| accessor->maybeLstat(CanonPath("file1")); | ||
| EXPECT_GT(accessor->getLastModified(), 0); | ||
| } | ||
| } | ||
|
|
||
| } // namespace nix |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,36 +43,6 @@ public: | |
|
|
||
| std::optional<std::filesystem::path> getPhysicalPath(const CanonPath & path) override; | ||
|
|
||
| /** | ||
| * Create a `PosixSourceAccessor` and `SourcePath` corresponding to | ||
| * some native path. | ||
| * | ||
| * @param Whether the accessor should return a non-null getLastModified. | ||
| * When true the accessor must be used only by a single thread. | ||
| * | ||
| * The `PosixSourceAccessor` is rooted as far up the tree as | ||
| * possible, (e.g. on Windows it could scoped to a drive like | ||
| * `C:\`). This allows more `..` parent accessing to work. | ||
| * | ||
| * @note When `path` is trusted user input, canonicalize it using | ||
| * `std::filesystem::canonical`, `makeParentCanonical`, `std::filesystem::weakly_canonical`, etc, | ||
| * as appropriate for the use case. At least weak canonicalization is | ||
| * required for the `SourcePath` to do anything useful at the location it | ||
| * points to. | ||
| * | ||
| * @note A canonicalizing behavior is not built in `createAtRoot` so that | ||
| * callers do not accidentally introduce symlink-related security vulnerabilities. | ||
|
Comment on lines
-63
to
-64
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also considering just how racy the PosixSourceAccessor is due to the lstat cache this is pretty much just a farce anyway. We'll be redoing this with fd-based accessor. The correct approach would be to use |
||
| * Furthermore, `createAtRoot` does not know whether the file pointed to by | ||
| * `path` should be resolved if it is itself a symlink. In other words, | ||
| * `createAtRoot` can not decide between aforementioned `canonical`, `makeParentCanonical`, etc. for its callers. | ||
| * | ||
| * See | ||
| * [`std::filesystem::path::root_path`](https://en.cppreference.com/w/cpp/filesystem/path/root_path) | ||
| * and | ||
| * [`std::filesystem::path::relative_path`](https://en.cppreference.com/w/cpp/filesystem/path/relative_path). | ||
| */ | ||
| static SourcePath createAtRoot(const std::filesystem::path & path, bool trackLastModified = false); | ||
|
|
||
| std::optional<std::time_t> getLastModified() override | ||
| { | ||
| return trackLastModified ? std::optional{mtime} : std::nullopt; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of the call-sites actually relied on the
..behaviour it looks like.dumpPathonly traverses down - not upwards.