Skip to content

Commit

Permalink
PosixSourceAccessor: Cache lstat() calls
Browse files Browse the repository at this point in the history
Since we're doing a lot of them in assertNoSymlinks().
  • Loading branch information
edolstra committed Dec 6, 2023
1 parent 504e4fc commit 57246c4
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 18 deletions.
56 changes: 38 additions & 18 deletions src/libutil/posix-source-accessor.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include "posix-source-accessor.hh"
#include "signals.hh"
#include "sync.hh"

#include <unordered_map>

namespace nix {

Expand Down Expand Up @@ -46,23 +49,45 @@ bool PosixSourceAccessor::pathExists(const CanonPath & path)
return nix::pathExists(path.abs());
}

std::optional<struct stat> PosixSourceAccessor::cachedLstat(const CanonPath & path)
{
static Sync<std::unordered_map<CanonPath, std::optional<struct stat>>> _cache;

{
auto cache(_cache.lock());
auto i = cache->find(path);
if (i != cache->end()) return i->second;
}

std::optional<struct stat> st{std::in_place};
if (::lstat(path.c_str(), &*st)) {
if (errno == ENOENT || errno == ENOTDIR)
st.reset();
else
throw SysError("getting status of '%s'", showPath(path));
}

auto cache(_cache.lock());
if (cache->size() >= 16384) cache->clear();
cache->emplace(path, st);

return st;
}

std::optional<SourceAccessor::Stat> PosixSourceAccessor::maybeLstat(const CanonPath & path)
{
if (auto parent = path.parent()) assertNoSymlinks(*parent);
struct stat st;
if (::lstat(path.c_str(), &st)) {
if (errno == ENOENT || errno == ENOTDIR) return std::nullopt;
throw SysError("getting status of '%s'", showPath(path));
}
mtime = std::max(mtime, st.st_mtime);
auto st = cachedLstat(path);
if (!st) return std::nullopt;
mtime = std::max(mtime, st->st_mtime);
return Stat {
.type =
S_ISREG(st.st_mode) ? tRegular :
S_ISDIR(st.st_mode) ? tDirectory :
S_ISLNK(st.st_mode) ? tSymlink :
S_ISREG(st->st_mode) ? tRegular :
S_ISDIR(st->st_mode) ? tDirectory :
S_ISLNK(st->st_mode) ? tSymlink :
tMisc,
.fileSize = S_ISREG(st.st_mode) ? std::optional<uint64_t>(st.st_size) : std::nullopt,
.isExecutable = S_ISREG(st.st_mode) && st.st_mode & S_IXUSR,
.fileSize = S_ISREG(st->st_mode) ? std::optional<uint64_t>(st->st_size) : std::nullopt,
.isExecutable = S_ISREG(st->st_mode) && st->st_mode & S_IXUSR,
};
}

Expand Down Expand Up @@ -95,14 +120,9 @@ std::optional<CanonPath> PosixSourceAccessor::getPhysicalPath(const CanonPath &

void PosixSourceAccessor::assertNoSymlinks(CanonPath path)
{
// FIXME: cache this since it potentially causes a lot of lstat calls.
while (!path.isRoot()) {
struct stat st;
if (::lstat(path.c_str(), &st)) {
if (errno != ENOENT)
throw SysError("getting status of '%s'", showPath(path));
}
if (S_ISLNK(st.st_mode))
auto st = cachedLstat(path);
if (st && S_ISLNK(st->st_mode))
throw Error("path '%s' is a symlink", showPath(path));
path.pop();
}
Expand Down
4 changes: 4 additions & 0 deletions src/libutil/posix-source-accessor.hh
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@ struct PosixSourceAccessor : virtual SourceAccessor

std::optional<CanonPath> getPhysicalPath(const CanonPath & path) override;

private:

/**
* Throw an error if `path` or any of its ancestors are symlinks.
*/
void assertNoSymlinks(CanonPath path);

std::optional<struct stat> cachedLstat(const CanonPath & path);
};

}

0 comments on commit 57246c4

Please sign in to comment.