mirror of
https://github.com/NixOS/nix.git
synced 2025-11-16 07:22:43 +01:00
The short answer for why we need to do this is so we can consistently do `#include "nix/..."`. Without this change, there are ways to still make that work, but they are hacky, and they have downsides such as making it harder to make sure headers from the wrong Nix library (e..g. `libnixexpr` headers in `libnixutil`) aren't being used. The C API alraedy used `nix_api_*`, so its headers are *not* put in subdirectories accordingly. Progress on #7876 We resisted doing this for a while because it would be annoying to not have the header source file pairs close by / easy to change file path/name from one to the other. But I am ameliorating that with symlinks in the next commit.
212 lines
6.8 KiB
C++
212 lines
6.8 KiB
C++
#include "nix/posix-source-accessor.hh"
|
|
#include "nix/source-path.hh"
|
|
#include "nix/signals.hh"
|
|
#include "nix/sync.hh"
|
|
|
|
#include <unordered_map>
|
|
|
|
namespace nix {
|
|
|
|
PosixSourceAccessor::PosixSourceAccessor(std::filesystem::path && argRoot)
|
|
: root(std::move(argRoot))
|
|
{
|
|
assert(root.empty() || root.is_absolute());
|
|
displayPrefix = root.string();
|
|
}
|
|
|
|
PosixSourceAccessor::PosixSourceAccessor()
|
|
: PosixSourceAccessor(std::filesystem::path {})
|
|
{ }
|
|
|
|
SourcePath PosixSourceAccessor::createAtRoot(const std::filesystem::path & path)
|
|
{
|
|
std::filesystem::path path2 = absPath(path);
|
|
return {
|
|
make_ref<PosixSourceAccessor>(path2.root_path()),
|
|
CanonPath { path2.relative_path().string() },
|
|
};
|
|
}
|
|
|
|
std::filesystem::path PosixSourceAccessor::makeAbsPath(const CanonPath & path)
|
|
{
|
|
return root.empty()
|
|
? (std::filesystem::path { path.abs() })
|
|
: path.isRoot()
|
|
? /* Don't append a slash for the root of the accessor, since
|
|
it can be a non-directory (e.g. in the case of `fetchTree
|
|
{ type = "file" }`). */
|
|
root
|
|
: root / path.rel();
|
|
}
|
|
|
|
void PosixSourceAccessor::readFile(
|
|
const CanonPath & path,
|
|
Sink & sink,
|
|
std::function<void(uint64_t)> sizeCallback)
|
|
{
|
|
assertNoSymlinks(path);
|
|
|
|
auto ap = makeAbsPath(path);
|
|
|
|
AutoCloseFD fd = toDescriptor(open(ap.string().c_str(), O_RDONLY
|
|
#ifndef _WIN32
|
|
| O_NOFOLLOW | O_CLOEXEC
|
|
#endif
|
|
));
|
|
if (!fd)
|
|
throw SysError("opening file '%1%'", ap.string());
|
|
|
|
struct stat st;
|
|
if (fstat(fromDescriptorReadOnly(fd.get()), &st) == -1)
|
|
throw SysError("statting file");
|
|
|
|
sizeCallback(st.st_size);
|
|
|
|
off_t left = st.st_size;
|
|
|
|
std::array<unsigned char, 64 * 1024> buf;
|
|
while (left) {
|
|
checkInterrupt();
|
|
ssize_t rd = read(fromDescriptorReadOnly(fd.get()), buf.data(), (size_t) std::min(left, (off_t) buf.size()));
|
|
if (rd == -1) {
|
|
if (errno != EINTR)
|
|
throw SysError("reading from file '%s'", showPath(path));
|
|
}
|
|
else if (rd == 0)
|
|
throw SysError("unexpected end-of-file reading '%s'", showPath(path));
|
|
else {
|
|
assert(rd <= left);
|
|
sink({(char *) buf.data(), (size_t) rd});
|
|
left -= rd;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PosixSourceAccessor::pathExists(const CanonPath & path)
|
|
{
|
|
if (auto parent = path.parent()) assertNoSymlinks(*parent);
|
|
return nix::pathExists(makeAbsPath(path).string());
|
|
}
|
|
|
|
std::optional<struct stat> PosixSourceAccessor::cachedLstat(const CanonPath & path)
|
|
{
|
|
static SharedSync<std::unordered_map<Path, std::optional<struct stat>>> _cache;
|
|
|
|
// Note: we convert std::filesystem::path to Path because the
|
|
// former is not hashable on libc++.
|
|
Path absPath = makeAbsPath(path).string();
|
|
|
|
{
|
|
auto cache(_cache.readLock());
|
|
auto i = cache->find(absPath);
|
|
if (i != cache->end()) return i->second;
|
|
}
|
|
|
|
auto st = nix::maybeLstat(absPath.c_str());
|
|
|
|
auto cache(_cache.lock());
|
|
if (cache->size() >= 16384) cache->clear();
|
|
cache->emplace(absPath, st);
|
|
|
|
return st;
|
|
}
|
|
|
|
std::optional<SourceAccessor::Stat> PosixSourceAccessor::maybeLstat(const CanonPath & path)
|
|
{
|
|
if (auto parent = path.parent()) assertNoSymlinks(*parent);
|
|
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_ISCHR(st->st_mode) ? tChar :
|
|
S_ISBLK(st->st_mode) ? tBlock :
|
|
#ifdef S_ISSOCK
|
|
S_ISSOCK(st->st_mode) ? tSocket :
|
|
#endif
|
|
S_ISFIFO(st->st_mode) ? tFifo :
|
|
tUnknown,
|
|
.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,
|
|
};
|
|
}
|
|
|
|
SourceAccessor::DirEntries PosixSourceAccessor::readDirectory(const CanonPath & path)
|
|
{
|
|
assertNoSymlinks(path);
|
|
DirEntries res;
|
|
try {
|
|
for (auto & entry : std::filesystem::directory_iterator{makeAbsPath(path)}) {
|
|
checkInterrupt();
|
|
auto type = [&]() -> std::optional<Type> {
|
|
std::filesystem::file_type nativeType;
|
|
try {
|
|
nativeType = entry.symlink_status().type();
|
|
} catch (std::filesystem::filesystem_error & e) {
|
|
// We cannot always stat the child. (Ideally there is no
|
|
// stat because the native directory entry has the type
|
|
// already, but this isn't always the case.)
|
|
if (e.code() == std::errc::permission_denied || e.code() == std::errc::operation_not_permitted)
|
|
return std::nullopt;
|
|
else throw;
|
|
}
|
|
|
|
// cannot exhaustively enumerate because implementation-specific
|
|
// additional file types are allowed.
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
|
switch (nativeType) {
|
|
case std::filesystem::file_type::regular: return Type::tRegular; break;
|
|
case std::filesystem::file_type::symlink: return Type::tSymlink; break;
|
|
case std::filesystem::file_type::directory: return Type::tDirectory; break;
|
|
case std::filesystem::file_type::character: return Type::tChar; break;
|
|
case std::filesystem::file_type::block: return Type::tBlock; break;
|
|
case std::filesystem::file_type::fifo: return Type::tFifo; break;
|
|
case std::filesystem::file_type::socket: return Type::tSocket; break;
|
|
default: return tUnknown;
|
|
}
|
|
#pragma GCC diagnostic pop
|
|
}();
|
|
res.emplace(entry.path().filename().string(), type);
|
|
}
|
|
} catch (std::filesystem::filesystem_error & e) {
|
|
throw SysError("reading directory %1%", showPath(path));
|
|
}
|
|
return res;
|
|
}
|
|
|
|
std::string PosixSourceAccessor::readLink(const CanonPath & path)
|
|
{
|
|
if (auto parent = path.parent()) assertNoSymlinks(*parent);
|
|
return nix::readLink(makeAbsPath(path).string());
|
|
}
|
|
|
|
std::optional<std::filesystem::path> PosixSourceAccessor::getPhysicalPath(const CanonPath & path)
|
|
{
|
|
return makeAbsPath(path);
|
|
}
|
|
|
|
void PosixSourceAccessor::assertNoSymlinks(CanonPath path)
|
|
{
|
|
while (!path.isRoot()) {
|
|
auto st = cachedLstat(path);
|
|
if (st && S_ISLNK(st->st_mode))
|
|
throw Error("path '%s' is a symlink", showPath(path));
|
|
path.pop();
|
|
}
|
|
}
|
|
|
|
ref<SourceAccessor> getFSSourceAccessor()
|
|
{
|
|
static auto rootFS = make_ref<PosixSourceAccessor>();
|
|
return rootFS;
|
|
}
|
|
|
|
ref<SourceAccessor> makeFSSourceAccessor(std::filesystem::path root)
|
|
{
|
|
return make_ref<PosixSourceAccessor>(std::move(root));
|
|
}
|
|
}
|