Compare commits
3 Commits
master
...
directory-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd1001a752 | ||
|
|
30008ac474 | ||
|
|
a649b0dab7 |
@@ -1,5 +1,6 @@
|
||||
#include "nix/util/archive.hh"
|
||||
#include "nix/util/posix-source-accessor.hh"
|
||||
#include "nix/util/posix-source-accessor.hh"
|
||||
#include "nix/store/store-api.hh"
|
||||
#include "nix/store/local-fs-store.hh"
|
||||
#include "nix/store/globals.hh"
|
||||
@@ -109,7 +110,7 @@ std::shared_ptr<SourceAccessor> LocalFSStore::getFSAccessor(const StorePath & pa
|
||||
if (!pathExists(absPath))
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_shared<PosixSourceAccessor>(std::move(absPath));
|
||||
return makeFSSourceAccessor(std::move(absPath));
|
||||
}
|
||||
|
||||
const std::string LocalFSStore::drvsLogDir = "drvs";
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "nix/util/util.hh"
|
||||
#include "nix/store/store-api.hh"
|
||||
#include "nix/store/globals.hh"
|
||||
#include "nix/util/file-system-at.hh"
|
||||
#include "store-config-private.hh"
|
||||
|
||||
#if NIX_SUPPORT_ACL
|
||||
|
||||
@@ -758,7 +758,7 @@ std::optional<Descriptor> DerivationBuilderImpl::startBuild()
|
||||
|
||||
/* The TOCTOU between the previous mkdir call and this open call is unavoidable due to
|
||||
POSIX semantics.*/
|
||||
tmpDirFd = AutoCloseFD{open(tmpDir.c_str(), O_RDONLY | O_NOFOLLOW | O_DIRECTORY)};
|
||||
tmpDirFd = openDirectory(tmpDir, /*followFinalSymlink=*/false);
|
||||
if (!tmpDirFd)
|
||||
throw SysError("failed to open the build temporary directory descriptor %1%", PathFmt(tmpDir));
|
||||
|
||||
@@ -1269,7 +1269,7 @@ void DerivationBuilderImpl::writeBuilderFile(const std::string & name, std::stri
|
||||
openat(tmpDirFd.get(), name.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC | O_EXCL | O_NOFOLLOW, 0666)};
|
||||
if (!fd)
|
||||
throw SysError("creating file %s", PathFmt(path));
|
||||
writeFile(fd, path, contents);
|
||||
writeFile(fd.get(), contents);
|
||||
chownToBuilder(fd.get(), path);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "nix/store/pathlocks.hh"
|
||||
#include "nix/util/file-system.hh"
|
||||
#include "nix/util/file-system-at.hh"
|
||||
#include "nix/util/util.hh"
|
||||
#include "nix/util/sync.hh"
|
||||
#include "nix/util/signals.hh"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "nix/util/file-system-at.hh"
|
||||
#include "nix/util/file-system.hh"
|
||||
#include "nix/util/fs-sink.hh"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
|
||||
@@ -91,9 +91,7 @@ TEST_F(FSSourceAccessorTest, works)
|
||||
{
|
||||
RestoreSink sink(false);
|
||||
sink.dstPath = tmpDir;
|
||||
#ifndef _WIN32
|
||||
sink.dirFd = openDirectory(tmpDir);
|
||||
#endif
|
||||
sink.createDirectory(CanonPath("subdir"));
|
||||
sink.createRegularFile(CanonPath("file1"), [](CreateRegularFileSink & crf) { crf("content1"); });
|
||||
sink.createRegularFile(CanonPath("subdir/file2"), [](CreateRegularFileSink & crf) { crf("content2"); });
|
||||
@@ -137,4 +135,34 @@ TEST_F(FSSourceAccessorTest, works)
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* RestoreSink non-directory at root (no dirFd)
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST_F(FSSourceAccessorTest, RestoreSinkRegularFileAtRoot)
|
||||
{
|
||||
auto filePath = tmpDir / "rootfile";
|
||||
{
|
||||
RestoreSink sink(false);
|
||||
sink.dstPath = filePath;
|
||||
// No dirFd set - this tests the !dirFd path
|
||||
sink.createRegularFile(CanonPath::root, [](CreateRegularFileSink & crf) { crf("root content"); });
|
||||
}
|
||||
|
||||
EXPECT_THAT(makeFSSourceAccessor(filePath), HasContents(CanonPath::root, "root content"));
|
||||
}
|
||||
|
||||
TEST_F(FSSourceAccessorTest, RestoreSinkSymlinkAtRoot)
|
||||
{
|
||||
auto linkPath = tmpDir / "rootlink";
|
||||
{
|
||||
RestoreSink sink(false);
|
||||
sink.dstPath = linkPath;
|
||||
// No dirFd set - this tests the !dirFd path
|
||||
sink.createSymlink(CanonPath::root, "symlink_target");
|
||||
}
|
||||
|
||||
EXPECT_THAT(makeFSSourceAccessor(linkPath), HasSymlink(CanonPath::root, "symlink_target"));
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
285
src/libutil/directory-source-accessor.cc
Normal file
285
src/libutil/directory-source-accessor.cc
Normal file
@@ -0,0 +1,285 @@
|
||||
// FIXME: detect in meson
|
||||
#define HAVE_OPENAT2 1
|
||||
#define HAVE_OPENAT 1
|
||||
|
||||
#include "util-config-private.hh"
|
||||
#include "nix/util/signals.hh"
|
||||
#include "nix/util/directory-source-accessor.hh"
|
||||
#include "nix/util/posix-source-accessor.hh"
|
||||
|
||||
#if defined(__linux__) && HAVE_OPENAT2
|
||||
# include <sys/syscall.h>
|
||||
# include <linux/openat2.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <ranges>
|
||||
|
||||
namespace nix {
|
||||
|
||||
#if HAVE_OPENAT
|
||||
|
||||
namespace {
|
||||
|
||||
class DirFdSourceAccessor : public SourceAccessor
|
||||
{
|
||||
/**
|
||||
* File descriptor of the root directory.
|
||||
*/
|
||||
AutoCloseFD dirFd;
|
||||
|
||||
/**
|
||||
* Path corresponding to the accessor.
|
||||
* @warning Do not use for any file operations!
|
||||
*/
|
||||
std::filesystem::path root;
|
||||
|
||||
/**
|
||||
* The most recent mtime seen by lstat(). This is a hack to
|
||||
* support dumpPathAndGetMtime(). Should remove this eventually.
|
||||
*/
|
||||
time_t mtime = 0;
|
||||
|
||||
bool trackLastModified = false;
|
||||
|
||||
AutoCloseFD openFile(const CanonPath & path, int flags)
|
||||
{
|
||||
if (path.isRoot())
|
||||
return ::openat(dirFd.get(), ".", flags);
|
||||
|
||||
# if defined(__linux__) && HAVE_OPENAT2
|
||||
/* Cache the result of whether openat2 is not supported. */
|
||||
static std::atomic_flag openat2Unsupported = ATOMIC_FLAG_INIT;
|
||||
|
||||
if (!openat2Unsupported.test()) {
|
||||
/* No glibc wrapper yet, but there's a patch:
|
||||
* https://patchwork.sourceware.org/project/glibc/patch/20251029200519.3203914-1-adhemerval.zanella@linaro.org/
|
||||
*/
|
||||
auto how = ::open_how{
|
||||
.flags = static_cast<decltype(::open_how::flags)>(flags),
|
||||
/* Symlinks are disallowed. RESOLVE_BENEATH is a bit overkill, since
|
||||
CanonPath has the invariant of not having any `..` components, but
|
||||
that's good practice anyway. */
|
||||
.resolve = RESOLVE_NO_SYMLINKS | RESOLVE_BENEATH,
|
||||
};
|
||||
|
||||
auto res = ::syscall(__NR_openat2, dirFd.get(), path.rel_c_str(), &how, sizeof(how));
|
||||
if (res < 0 && errno == ENOSYS) {
|
||||
/* Cache that the syscall is not supported and fall through to openat. */
|
||||
openat2Unsupported.test_and_set();
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
AutoCloseFD parentFd;
|
||||
auto nrComponents = std::ranges::distance(path);
|
||||
auto components = std::views::take(path, nrComponents - 1); /* Everything but last component */
|
||||
auto getParentFd = [&]() { return parentFd ? parentFd.get() : dirFd.get(); };
|
||||
|
||||
/* This rather convoluted loop is necessary to avoid TOCTOU when validating that
|
||||
no inner path component is a symlink. */
|
||||
for (auto it = components.begin(); it != components.end(); ++it) {
|
||||
std::string_view component = *it;
|
||||
parentFd = ::openat(
|
||||
getParentFd(), /* First iteration uses dirFd. */
|
||||
std::string(component).c_str(), /* Copy into a string to make NUL terminated. */
|
||||
O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC |
|
||||
# ifdef __linux__
|
||||
O_PATH /* Linux-specific optimization. Files are open only for path resolution purposes. */
|
||||
# endif
|
||||
);
|
||||
|
||||
if (!parentFd) {
|
||||
/* Construct the CanonPath for error message. */
|
||||
auto path2 = std::ranges::fold_left(components.begin(), ++it, CanonPath::root, [](auto lhs, auto rhs) {
|
||||
lhs.push(rhs);
|
||||
return lhs;
|
||||
});
|
||||
|
||||
if (errno == ELOOP)
|
||||
/* Do not allow any simlinks in internal path components.
|
||||
This is what is necessary to emulate it without RESOLVE_NO_SYMLINKS. */
|
||||
throw Error("path '%s' is a symlink", showPath(path2));
|
||||
|
||||
return AutoCloseFD();
|
||||
}
|
||||
}
|
||||
|
||||
return ::openat(getParentFd(), std::string(path.baseName().value()).c_str(), flags);
|
||||
}
|
||||
|
||||
std::optional<struct ::stat> maybeLstatImpl(const CanonPath & path)
|
||||
{
|
||||
std::optional<struct ::stat> st{std::in_place};
|
||||
|
||||
if (path.isRoot()) {
|
||||
if (::fstat(dirFd.get(), &*st)) /* Is already open. */
|
||||
throw SysError("getting status of '%s'", showPath(path));
|
||||
return st;
|
||||
}
|
||||
|
||||
auto parentPath = path.parent().value();
|
||||
auto fd = openFile(parentPath, O_PATH | O_NOFOLLOW | O_CLOEXEC | O_DIRECTORY);
|
||||
if (!fd) {
|
||||
if (errno == ENOENT || errno == ENOTDIR)
|
||||
return std::nullopt;
|
||||
throw SysError("opening parent path of '%s'", showPath(path));
|
||||
}
|
||||
|
||||
/* Should have the same semantics as lstat on a path. */
|
||||
if (::fstatat(fd.get(), std::string(path.baseName().value()).c_str(), &*st, AT_SYMLINK_NOFOLLOW)) {
|
||||
if (errno == ENOENT || errno == ENOTDIR)
|
||||
st.reset();
|
||||
else
|
||||
throw SysError("getting status of '%s'", showPath(path));
|
||||
}
|
||||
|
||||
/* The contract is that trackLastModified implies that the caller uses the accessor
|
||||
from a single thread. Thus this is not a CAS loop. */
|
||||
if (trackLastModified)
|
||||
mtime = std::max(mtime, st->st_mtime);
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
public:
|
||||
DirFdSourceAccessor(AutoCloseFD rootFd_, std::filesystem::path root_, bool trackLastModified)
|
||||
: dirFd(std::move(rootFd_))
|
||||
, root(std::move(root_))
|
||||
, trackLastModified(trackLastModified)
|
||||
{
|
||||
if (root != root.root_path()) /* Don't prefix root directory. */
|
||||
displayPrefix = root.string();
|
||||
}
|
||||
|
||||
void readFile(const CanonPath & path, Sink & sink, std::function<void(uint64_t)> sizeCallback) override
|
||||
{
|
||||
auto fd = openFile(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
|
||||
|
||||
auto size = getFileSize(fd.get());
|
||||
sizeCallback(size);
|
||||
drainFD(fd.get(), sink, {.expectedSize = size});
|
||||
}
|
||||
|
||||
bool pathExists(const CanonPath & path) override
|
||||
{
|
||||
return maybeLstatImpl(path).has_value();
|
||||
}
|
||||
|
||||
std::optional<Stat> maybeLstat(const CanonPath & path) override
|
||||
{
|
||||
// TODO: Deduplicate with PosixSourceAccessor.
|
||||
|
||||
auto st = maybeLstatImpl(path);
|
||||
if (!st)
|
||||
return std::nullopt;
|
||||
|
||||
/* The contract is that trackLastModified implies that the caller uses the accessor
|
||||
from a single thread. Thus this is not a CAS loop. */
|
||||
if (trackLastModified)
|
||||
mtime = std::max(mtime, st->st_mtime);
|
||||
|
||||
return PosixSourceAccessor::makeStat(*st);
|
||||
}
|
||||
|
||||
DirEntries readDirectory(const CanonPath & path) override
|
||||
{
|
||||
auto fd = openFile(path, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (!fd)
|
||||
throw SysError("opening directory '%1%'", path);
|
||||
|
||||
auto dir = AutoCloseDir{::fdopendir(fd.get())};
|
||||
if (!dir)
|
||||
throw SysError("opening directory '%1%'", path);
|
||||
|
||||
fd.release();
|
||||
|
||||
DirEntries entries;
|
||||
struct dirent * dirent;
|
||||
|
||||
while (errno = 0, dirent = ::readdir(dir.get())) {
|
||||
checkInterrupt();
|
||||
std::string name = dirent->d_name;
|
||||
if (name == "." || name == "..")
|
||||
continue;
|
||||
|
||||
auto type = [&]() -> std::optional<Type> {
|
||||
switch (dirent->d_type) {
|
||||
case DT_REG:
|
||||
return tRegular;
|
||||
case DT_DIR:
|
||||
return tDirectory;
|
||||
case DT_LNK:
|
||||
return tSymlink;
|
||||
case DT_BLK:
|
||||
return tBlock;
|
||||
case DT_CHR:
|
||||
return tChar;
|
||||
case DT_FIFO:
|
||||
return tFifo;
|
||||
case DT_SOCK:
|
||||
return tSocket;
|
||||
case DT_UNKNOWN:
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}();
|
||||
|
||||
entries.emplace(std::move(name), type);
|
||||
}
|
||||
|
||||
if (errno)
|
||||
throw SysError("reading directory '%1%'", showPath(path));
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::string readLink(const CanonPath & path) override
|
||||
{
|
||||
if (path.isRoot())
|
||||
throw Error("file '%s' is not a symbolic link", path);
|
||||
|
||||
auto parentPath = path.parent().value();
|
||||
auto basename = std::string(path.baseName().value());
|
||||
|
||||
auto parentFd = openFile(
|
||||
parentPath,
|
||||
O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC |
|
||||
# ifdef __linux__
|
||||
O_PATH
|
||||
# endif
|
||||
);
|
||||
if (!parentFd)
|
||||
throw SysError("opening path '%1%'", showPath(parentPath));
|
||||
|
||||
std::string target;
|
||||
target.resize(PATH_MAX);
|
||||
auto len = ::readlinkat(parentFd.get(), basename.c_str(), target.data(), target.size());
|
||||
if (len < 0)
|
||||
throw SysError("reading link '%1%'", showPath(path));
|
||||
target.resize(len);
|
||||
return target;
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> getPhysicalPath(const CanonPath & path) override
|
||||
{
|
||||
return root / path.rel();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
||||
|
||||
ref<SourceAccessor> makeDirectorySourceAccessor(AutoCloseFD fd, std::filesystem::path root, bool trackLastModified)
|
||||
{
|
||||
#if HAVE_OPENAT
|
||||
return make_ref<DirFdSourceAccessor>(std::move(fd), std::move(root), trackLastModified);
|
||||
#else
|
||||
return make_ref<PosixSourceAccessor>(std::move(root), trackLastModified);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
@@ -184,24 +184,6 @@ void AutoCloseFD::close()
|
||||
}
|
||||
}
|
||||
|
||||
void AutoCloseFD::fsync() const
|
||||
{
|
||||
if (fd != INVALID_DESCRIPTOR) {
|
||||
int result;
|
||||
result =
|
||||
#ifdef _WIN32
|
||||
::FlushFileBuffers(fd)
|
||||
#elif defined(__APPLE__)
|
||||
::fcntl(fd, F_FULLFSYNC)
|
||||
#else
|
||||
::fsync(fd)
|
||||
#endif
|
||||
;
|
||||
if (result == -1)
|
||||
throw NativeSysError("fsync file descriptor %1%", fd);
|
||||
}
|
||||
}
|
||||
|
||||
void AutoCloseFD::startFsync() const
|
||||
{
|
||||
#ifdef __linux__
|
||||
|
||||
16
src/libutil/file-system-at.cc
Normal file
16
src/libutil/file-system-at.cc
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "nix/util/file-system-at.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::optional<PosixStat> maybeFstatat(Descriptor dirFd, const CanonPath & path)
|
||||
{
|
||||
try {
|
||||
return fstatat(dirFd, path);
|
||||
} catch (SystemError & e) {
|
||||
if (e.is(std::errc::no_such_file_or_directory) || e.is(std::errc::not_a_directory))
|
||||
return std::nullopt;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
@@ -195,71 +195,42 @@ bool isDirOrInDir(const std::filesystem::path & path, const std::filesystem::pat
|
||||
return path == dir || isInDir(path, dir);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
# define STAT _wstat64
|
||||
# define LSTAT _wstat64
|
||||
#else
|
||||
# define STAT stat
|
||||
# define LSTAT lstat
|
||||
#endif
|
||||
|
||||
PosixStat stat(const std::filesystem::path & path)
|
||||
{
|
||||
PosixStat st;
|
||||
if (STAT(path.c_str(), &st))
|
||||
throw SysError("getting status of %s", PathFmt(path));
|
||||
return st;
|
||||
}
|
||||
|
||||
PosixStat lstat(const std::filesystem::path & path)
|
||||
{
|
||||
PosixStat st;
|
||||
if (LSTAT(path.c_str(), &st))
|
||||
throw SysError("getting status of %s", PathFmt(path));
|
||||
return st;
|
||||
}
|
||||
|
||||
PosixStat fstat(int fd)
|
||||
{
|
||||
PosixStat st;
|
||||
if (
|
||||
#ifdef _WIN32
|
||||
_fstat64
|
||||
_wstat64
|
||||
#else
|
||||
::fstat
|
||||
::stat
|
||||
#endif
|
||||
(fd, &st))
|
||||
throw SysError("getting status of fd %d", fd);
|
||||
(path.c_str(), &st))
|
||||
throw SysError("getting status of %s", PathFmt(path));
|
||||
return st;
|
||||
}
|
||||
|
||||
std::optional<PosixStat> maybeStat(const std::filesystem::path & path)
|
||||
{
|
||||
std::optional<PosixStat> st{std::in_place};
|
||||
if (STAT(path.c_str(), &*st)) {
|
||||
if (errno == ENOENT || errno == ENOTDIR)
|
||||
st.reset();
|
||||
else
|
||||
throw SysError("getting status of %s", PathFmt(path));
|
||||
try {
|
||||
return stat(path);
|
||||
} catch (SystemError & e) {
|
||||
if (e.is(std::errc::no_such_file_or_directory) || e.is(std::errc::not_a_directory))
|
||||
return std::nullopt;
|
||||
throw;
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
std::optional<PosixStat> maybeLstat(const std::filesystem::path & path)
|
||||
{
|
||||
std::optional<PosixStat> st{std::in_place};
|
||||
if (LSTAT(path.c_str(), &*st)) {
|
||||
if (errno == ENOENT || errno == ENOTDIR)
|
||||
st.reset();
|
||||
else
|
||||
throw SysError("getting status of %s", PathFmt(path));
|
||||
try {
|
||||
return lstat(path);
|
||||
} catch (SystemError & e) {
|
||||
if (e.is(std::errc::no_such_file_or_directory) || e.is(std::errc::not_a_directory))
|
||||
return std::nullopt;
|
||||
throw;
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
#undef STAT
|
||||
#undef LSTAT
|
||||
|
||||
bool pathExists(const std::filesystem::path & path)
|
||||
{
|
||||
return maybeLstat(path.string()).has_value();
|
||||
@@ -340,23 +311,23 @@ void writeFile(const Path & path, std::string_view s, mode_t mode, FsSync sync)
|
||||
if (!fd)
|
||||
throw SysError("opening file '%1%'", path);
|
||||
|
||||
writeFile(fd, path, s, mode, sync);
|
||||
writeFile(fd.get(), s, sync, &path);
|
||||
|
||||
/* Close explicitly to propagate the exceptions. */
|
||||
fd.close();
|
||||
}
|
||||
|
||||
void writeFile(AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode, FsSync sync)
|
||||
void writeFile(Descriptor fd, std::string_view s, FsSync sync, const Path * origPath)
|
||||
{
|
||||
assert(fd);
|
||||
assert(fd != INVALID_DESCRIPTOR);
|
||||
try {
|
||||
writeFull(fd.get(), s);
|
||||
writeFull(fd, s);
|
||||
|
||||
if (sync == FsSync::Yes)
|
||||
fd.fsync();
|
||||
syncDescriptor(fd);
|
||||
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "writing file '%1%'", origPath);
|
||||
e.addTrace({}, "writing file '%1%'", origPath ? *origPath : descriptorToPath(fd).string());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,34 +105,26 @@ void RestoreSink::createDirectory(const CanonPath & path)
|
||||
{
|
||||
auto p = append(dstPath, path);
|
||||
|
||||
#ifndef _WIN32
|
||||
if (dirFd) {
|
||||
if (path.isRoot())
|
||||
/* Trying to create a directory that we already have a file descriptor for. */
|
||||
throw Error("path %s already exists", PathFmt(p));
|
||||
|
||||
if (::mkdirat(dirFd.get(), path.rel_c_str(), 0777) == -1)
|
||||
throw SysError("creating directory %s", PathFmt(p));
|
||||
|
||||
createDirectoryAt(dirFd.get(), path);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!std::filesystem::create_directory(p))
|
||||
throw Error("path '%s' already exists", p.string());
|
||||
|
||||
#ifndef _WIN32
|
||||
if (path.isRoot()) {
|
||||
assert(!dirFd); // Handled above
|
||||
|
||||
/* Open directory for further *at operations relative to the sink root
|
||||
directory. */
|
||||
dirFd = open(p.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (!dirFd)
|
||||
throw SysError("creating directory %1%", PathFmt(p));
|
||||
dirFd = openDirectory(p, /*followFinalSymlink=*/false);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
struct RestoreRegularFile : CreateRegularFileSink, FdSink
|
||||
{
|
||||
@@ -164,28 +156,26 @@ void RestoreSink::createRegularFile(const CanonPath & path, std::function<void(C
|
||||
{
|
||||
auto p = append(dstPath, path);
|
||||
|
||||
auto crf = RestoreRegularFile(
|
||||
startFsync,
|
||||
auto crf = RestoreRegularFile(startFsync, [&]() -> Descriptor {
|
||||
AutoCloseFD parentFd;
|
||||
if (!dirFd) {
|
||||
assert(path.isRoot());
|
||||
assert(p.has_parent_path());
|
||||
parentFd = openDirectory(p.parent_path());
|
||||
}
|
||||
return openFileEnsureBeneathNoSymlinks(
|
||||
dirFd ? dirFd.get() : parentFd.get(),
|
||||
dirFd ? path : CanonPath::root / p.filename().string(),
|
||||
#ifdef _WIN32
|
||||
CreateFileW(
|
||||
p.c_str(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL,
|
||||
CREATE_NEW,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL)
|
||||
FILE_NON_DIRECTORY_FILE,
|
||||
FILE_CREATE
|
||||
#else
|
||||
[&]() {
|
||||
/* O_EXCL together with O_CREAT ensures symbolic links in the last
|
||||
component are not followed. */
|
||||
constexpr int flags = O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC;
|
||||
if (!dirFd)
|
||||
return ::open(p.c_str(), flags, 0666);
|
||||
return openFileEnsureBeneathNoSymlinks(dirFd.get(), path, flags, 0666);
|
||||
}()
|
||||
O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC,
|
||||
0666
|
||||
#endif
|
||||
);
|
||||
);
|
||||
}());
|
||||
if (!crf.fd)
|
||||
throw NativeSysError("creating file %1%", PathFmt(p));
|
||||
func(crf);
|
||||
@@ -224,14 +214,16 @@ void RestoreRegularFile::preallocateContents(uint64_t len)
|
||||
void RestoreSink::createSymlink(const CanonPath & path, const std::string & target)
|
||||
{
|
||||
auto p = append(dstPath, path);
|
||||
#ifndef _WIN32
|
||||
if (dirFd) {
|
||||
if (::symlinkat(requireCString(target), dirFd.get(), path.rel_c_str()) == -1)
|
||||
throw SysError("creating symlink from %1% -> '%2%'", PathFmt(p), target);
|
||||
return;
|
||||
AutoCloseFD parentFd;
|
||||
if (!dirFd) {
|
||||
assert(path.isRoot());
|
||||
assert(p.has_parent_path());
|
||||
parentFd = openDirectory(p.parent_path());
|
||||
}
|
||||
#endif
|
||||
nix::createSymlink(target, p.string());
|
||||
createUnknownSymlinkAt(
|
||||
dirFd ? dirFd.get() : parentFd.get(),
|
||||
dirFd ? path : CanonPath::root / p.filename().string(),
|
||||
string_to_os_string(target));
|
||||
}
|
||||
|
||||
void RegularFileSink::createRegularFile(const CanonPath & path, std::function<void(CreateRegularFileSink &)> func)
|
||||
|
||||
25
src/libutil/include/nix/util/directory-source-accessor.hh
Normal file
25
src/libutil/include/nix/util/directory-source-accessor.hh
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
/// @file
|
||||
|
||||
#include "nix/util/source-accessor.hh"
|
||||
#include "nix/util/ref.hh"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* Create a directory accessor rooted at @param root.
|
||||
*
|
||||
* On unix accesses are performed with openat. Linux uses openat2 if supported
|
||||
* by the kernel (>= 5.6).
|
||||
*
|
||||
* On Windows returns a PosixSourceAccessor.
|
||||
*
|
||||
* @param fd Descriptor of the directory.
|
||||
* @param root Filesystem path corresponding to fd. Must correspond to a directory.
|
||||
*/
|
||||
ref<SourceAccessor>
|
||||
makeDirectorySourceAccessor(AutoCloseFD fd, std::filesystem::path root, bool trackLastModified = false);
|
||||
|
||||
} // namespace nix
|
||||
@@ -133,6 +133,11 @@ std::string readLine(Descriptor fd, bool eofOk = false, char terminator = '\n');
|
||||
*/
|
||||
void writeLine(Descriptor fd, std::string s);
|
||||
|
||||
/**
|
||||
* Perform a blocking fsync operation on a file descriptor.
|
||||
*/
|
||||
void syncDescriptor(Descriptor fd);
|
||||
|
||||
/**
|
||||
* Options for draining a file descriptor to a sink.
|
||||
*/
|
||||
@@ -253,7 +258,11 @@ public:
|
||||
/**
|
||||
* Perform a blocking fsync operation.
|
||||
*/
|
||||
void fsync() const;
|
||||
void fsync() const
|
||||
{
|
||||
if (fd != INVALID_DESCRIPTOR)
|
||||
nix::syncDescriptor(fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously flush to disk without blocking, if available on
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "nix/util/file-descriptor.hh"
|
||||
#include "nix/util/file-system.hh"
|
||||
|
||||
#include <optional>
|
||||
|
||||
@@ -33,6 +34,90 @@ namespace nix {
|
||||
*/
|
||||
OsString readLinkAt(Descriptor dirFd, const CanonPath & path);
|
||||
|
||||
/**
|
||||
* Create a symlink to a file relative to a directory file descriptor.
|
||||
*
|
||||
* On Windows, creates a file symlink. On Unix, equivalent to symlinkat.
|
||||
*
|
||||
* @param dirFd Directory file descriptor
|
||||
* @param path Relative path for the new symlink
|
||||
* @param target The symlink target (what it points to)
|
||||
*
|
||||
* @throws SystemError on any I/O errors.
|
||||
*/
|
||||
void createFileSymlinkAt(Descriptor dirFd, const CanonPath & path, const OsString & target);
|
||||
|
||||
/**
|
||||
* Create a symlink to a directory relative to a directory file descriptor.
|
||||
*
|
||||
* On Windows, creates a directory symlink. On Unix, equivalent to symlinkat.
|
||||
*
|
||||
* @param dirFd Directory file descriptor
|
||||
* @param path Relative path for the new symlink
|
||||
* @param target The symlink target (what it points to)
|
||||
*
|
||||
* @throws SystemError on any I/O errors.
|
||||
*/
|
||||
void createDirectorySymlinkAt(Descriptor dirFd, const CanonPath & path, const OsString & target);
|
||||
|
||||
/**
|
||||
* Create a symlink relative to a directory file descriptor, detecting target type.
|
||||
*
|
||||
* On Windows, stats the target to determine whether to create a file or
|
||||
* directory symlink. Falls back to file symlink if the target does not exist.
|
||||
* On Unix, equivalent to symlinkat.
|
||||
*
|
||||
* @param dirFd Directory file descriptor
|
||||
* @param path Relative path for the new symlink
|
||||
* @param target The symlink target (what it points to)
|
||||
*
|
||||
* @throws SystemError on any I/O errors.
|
||||
*/
|
||||
void createUnknownSymlinkAt(Descriptor dirFd, const CanonPath & path, const OsString & target);
|
||||
|
||||
/**
|
||||
* Create a directory relative to a directory file descriptor.
|
||||
*
|
||||
* @param dirFd Directory file descriptor
|
||||
* @param path Relative path for the new directory
|
||||
*
|
||||
* @throws SystemError on any I/O errors.
|
||||
*/
|
||||
void createDirectoryAt(Descriptor dirFd, const CanonPath & path);
|
||||
|
||||
/**
|
||||
* Get status of an open file/directory handle.
|
||||
*
|
||||
* @param fd File descriptor/handle
|
||||
* @throws SystemError on I/O errors.
|
||||
*/
|
||||
PosixStat fstat(Descriptor fd);
|
||||
|
||||
/**
|
||||
* Get status of a file relative to a directory file descriptor.
|
||||
*
|
||||
* @param dirFd Directory file descriptor
|
||||
* @param path Relative path to stat
|
||||
*
|
||||
* @return nullopt if the path does not exist.
|
||||
* @throws SystemError on other I/O errors.
|
||||
*
|
||||
* @pre path.isRoot() is false
|
||||
*/
|
||||
std::optional<PosixStat> maybeFstatat(Descriptor dirFd, const CanonPath & path);
|
||||
|
||||
/**
|
||||
* Get status of a file relative to a directory file descriptor.
|
||||
*
|
||||
* @param dirFd Directory file descriptor
|
||||
* @param path Relative path to stat
|
||||
*
|
||||
* @throws SystemError if the path does not exist or on other I/O errors.
|
||||
*
|
||||
* @pre path.isRoot() is false
|
||||
*/
|
||||
PosixStat fstatat(Descriptor dirFd, const CanonPath & path);
|
||||
|
||||
/**
|
||||
* Safe(r) function to open a file relative to dirFd, while
|
||||
* disallowing escaping from a directory and any symlinks in the process.
|
||||
|
||||
@@ -28,11 +28,20 @@
|
||||
* Polyfill for MinGW
|
||||
*
|
||||
* Windows does in fact support symlinks, but the C runtime interfaces predate this.
|
||||
*
|
||||
* @todo get rid of this, and stop using `stat` when we want `lstat` too.
|
||||
* We define S_IFLNK and S_ISLNK so that our lstat implementation can properly
|
||||
* indicate symlinks by setting these mode bits when it detects a reparse point.
|
||||
*/
|
||||
#ifndef S_IFLNK
|
||||
# ifndef _WIN32
|
||||
# error "S_IFLNK should be defined on non-Windows platforms"
|
||||
# endif
|
||||
# define S_IFLNK 0120000
|
||||
#endif
|
||||
#ifndef S_ISLNK
|
||||
# define S_ISLNK(m) false
|
||||
# ifndef _WIN32
|
||||
# error "S_ISLNK should be defined on non-Windows platforms"
|
||||
# endif
|
||||
# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
|
||||
#endif
|
||||
|
||||
namespace nix {
|
||||
@@ -125,6 +134,38 @@ using PosixStat =
|
||||
#endif
|
||||
;
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace windows {
|
||||
|
||||
/**
|
||||
* Convert Windows FILETIME to Unix time_t.
|
||||
*/
|
||||
time_t fileTimeToUnixTime(const FILETIME & ft);
|
||||
|
||||
/**
|
||||
* Fill a PosixStat structure from file attributes and timestamps.
|
||||
*
|
||||
* @param dwFileAttributes File attributes (FILE_ATTRIBUTE_*)
|
||||
* @param ftCreationTime Creation time
|
||||
* @param ftLastAccessTime Last access time
|
||||
* @param ftLastWriteTime Last write time
|
||||
* @param nFileSizeHigh High 32 bits of file size
|
||||
* @param nFileSizeLow Low 32 bits of file size
|
||||
* @param nNumberOfLinks Number of hard links (default 1)
|
||||
*/
|
||||
void statFromFileInfo(
|
||||
PosixStat & st,
|
||||
DWORD dwFileAttributes,
|
||||
const FILETIME & ftCreationTime,
|
||||
const FILETIME & ftLastAccessTime,
|
||||
const FILETIME & ftLastWriteTime,
|
||||
DWORD nFileSizeHigh,
|
||||
DWORD nFileSizeLow,
|
||||
DWORD nNumberOfLinks = 1);
|
||||
|
||||
} // namespace windows
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Get status of `path`.
|
||||
*/
|
||||
@@ -133,10 +174,6 @@ PosixStat lstat(const std::filesystem::path & path);
|
||||
* Get status of `path` following symlinks.
|
||||
*/
|
||||
PosixStat stat(const std::filesystem::path & path);
|
||||
/**
|
||||
* Get status of an open file descriptor.
|
||||
*/
|
||||
PosixStat fstat(int fd);
|
||||
/**
|
||||
* `lstat` the given path if it exists.
|
||||
* @return std::nullopt if the path doesn't exist, or an optional containing the result of `lstat` otherwise
|
||||
@@ -205,8 +242,10 @@ std::filesystem::path descriptorToPath(Descriptor fd);
|
||||
|
||||
/**
|
||||
* Open a `Descriptor` with read-only access to the given directory.
|
||||
*
|
||||
* @param followSymlinks If false, fail if the path is a symlink.
|
||||
*/
|
||||
Descriptor openDirectory(const std::filesystem::path & path);
|
||||
Descriptor openDirectory(const std::filesystem::path & path, bool followFinalSymlink = true);
|
||||
|
||||
/**
|
||||
* Open a `Descriptor` with read-only access to the given file.
|
||||
@@ -266,8 +305,7 @@ writeFile(const std::filesystem::path & path, Source & source, mode_t mode = 066
|
||||
return writeFile(path.string(), source, mode, sync);
|
||||
}
|
||||
|
||||
void writeFile(
|
||||
AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode = 0666, FsSync sync = FsSync::No);
|
||||
void writeFile(Descriptor fd, std::string_view s, FsSync sync = FsSync::No, const Path * origPath = nullptr);
|
||||
|
||||
/**
|
||||
* Flush a path's parent directory to disk.
|
||||
|
||||
@@ -32,6 +32,7 @@ headers = [ config_pub_h ] + files(
|
||||
'configuration.hh',
|
||||
'current-process.hh',
|
||||
'demangle.hh',
|
||||
'directory-source-accessor.hh',
|
||||
'english.hh',
|
||||
'environment-variables.hh',
|
||||
'error.hh',
|
||||
|
||||
@@ -82,6 +82,8 @@ public:
|
||||
|
||||
void invalidateCache(const CanonPath & path) override;
|
||||
|
||||
static Stat makeStat(const struct ::stat st);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
|
||||
@@ -139,6 +139,7 @@ sources = [ config_priv_h ] + files(
|
||||
'config-global.cc',
|
||||
'configuration.cc',
|
||||
'current-process.cc',
|
||||
'directory-source-accessor.cc',
|
||||
'english.cc',
|
||||
'environment-variables.cc',
|
||||
'error.cc',
|
||||
@@ -147,6 +148,7 @@ sources = [ config_priv_h ] + files(
|
||||
'experimental-features.cc',
|
||||
'file-content-address.cc',
|
||||
'file-descriptor.cc',
|
||||
'file-system-at.cc',
|
||||
'file-system.cc',
|
||||
'fs-sink.cc',
|
||||
'git.cc',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "nix/util/posix-source-accessor.hh"
|
||||
#include "nix/util/directory-source-accessor.hh"
|
||||
#include "nix/util/source-path.hh"
|
||||
#include "nix/util/signals.hh"
|
||||
#include "nix/util/sync.hh"
|
||||
@@ -108,22 +109,7 @@ std::optional<SourceAccessor::Stat> PosixSourceAccessor::maybeLstat(const CanonP
|
||||
if (trackLastModified)
|
||||
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,
|
||||
};
|
||||
return makeStat(*st);
|
||||
}
|
||||
|
||||
SourceAccessor::DirEntries PosixSourceAccessor::readDirectory(const CanonPath & path)
|
||||
@@ -200,6 +186,26 @@ void PosixSourceAccessor::assertNoSymlinks(CanonPath path)
|
||||
}
|
||||
}
|
||||
|
||||
PosixSourceAccessor::Stat PosixSourceAccessor::makeStat(const struct ::stat st)
|
||||
{
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
ref<SourceAccessor> getFSSourceAccessor()
|
||||
{
|
||||
static auto rootFS = make_ref<PosixSourceAccessor>();
|
||||
@@ -208,6 +214,30 @@ ref<SourceAccessor> getFSSourceAccessor()
|
||||
|
||||
ref<SourceAccessor> makeFSSourceAccessor(std::filesystem::path root, bool trackLastModified)
|
||||
{
|
||||
AutoCloseFD fd = toDescriptor(
|
||||
::open(
|
||||
root.c_str(),
|
||||
O_RDONLY |
|
||||
#ifndef _WIN32
|
||||
O_NOFOLLOW | O_CLOEXEC
|
||||
#endif
|
||||
));
|
||||
|
||||
if (!fd) {
|
||||
if (errno == ELOOP)
|
||||
/* Use a plain old PosixSourceAccessor for symlinks. Should probably just
|
||||
read the link here into a MemorySourceAccessor. */
|
||||
return make_ref<PosixSourceAccessor>(std::move(root), trackLastModified);
|
||||
throw SysError("opening file %s", PathFmt(root));
|
||||
}
|
||||
|
||||
struct ::stat st;
|
||||
if (::fstat(fd.get(), &st))
|
||||
throw SysError("getting status of %s", PathFmt(root));
|
||||
|
||||
if (S_ISDIR(st.st_mode))
|
||||
return makeDirectorySourceAccessor(std::move(fd), std::move(root), trackLastModified);
|
||||
|
||||
return make_ref<PosixSourceAccessor>(std::move(root), trackLastModified);
|
||||
}
|
||||
} // namespace nix
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "nix/util/file-system.hh"
|
||||
#include "nix/util/file-system-at.hh"
|
||||
#include "nix/util/signals.hh"
|
||||
#include "nix/util/finally.hh"
|
||||
#include "nix/util/serialise.hh"
|
||||
@@ -207,4 +208,17 @@ void unix::closeOnExec(int fd)
|
||||
throw SysError("setting close-on-exec flag");
|
||||
}
|
||||
|
||||
void syncDescriptor(Descriptor fd)
|
||||
{
|
||||
int result =
|
||||
#if defined(__APPLE__)
|
||||
::fcntl(fd, F_FULLFSYNC)
|
||||
#else
|
||||
::fsync(fd)
|
||||
#endif
|
||||
;
|
||||
if (result == -1)
|
||||
throw NativeSysError("fsync file descriptor %1%", fd);
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
# define HAVE_FCHMODAT2 0
|
||||
#endif
|
||||
|
||||
#include "util-unix-config-private.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
#ifdef __linux__
|
||||
@@ -90,10 +92,9 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
|
||||
PathFmt(descriptorToPath(dirFd) / path.rel()));
|
||||
}
|
||||
|
||||
struct ::stat st;
|
||||
/* Possible since https://github.com/torvalds/linux/commit/55815f70147dcfa3ead5738fd56d3574e2e3c1c2 (3.6) */
|
||||
if (::fstat(pathFd.get(), &st) == -1)
|
||||
throw SysError("statting '%s' relative to parent directory via O_PATH file descriptor", path.rel());
|
||||
/* Possible to use with O_PATH fd since
|
||||
* https://github.com/torvalds/linux/commit/55815f70147dcfa3ead5738fd56d3574e2e3c1c2 (3.6) */
|
||||
auto st = fstat(pathFd.get());
|
||||
|
||||
if (S_ISLNK(st.st_mode))
|
||||
throw SysError(EOPNOTSUPP, "can't change mode of symlink %s", PathFmt(descriptorToPath(dirFd) / path.rel()));
|
||||
@@ -171,8 +172,7 @@ openFileEnsureBeneathNoSymlinksIterative(Descriptor dirFd, const CanonPath & pat
|
||||
});
|
||||
|
||||
if (errno == ENOTDIR) /* Path component might be a symlink. */ {
|
||||
struct ::stat st;
|
||||
if (::fstatat(getParentFd(), component.c_str(), &st, AT_SYMLINK_NOFOLLOW) == 0 && S_ISLNK(st.st_mode))
|
||||
if (auto st = maybeFstatat(getParentFd(), CanonPath(component)); st && S_ISLNK(st->st_mode))
|
||||
throw SymlinkNotAllowed(path2);
|
||||
errno = ENOTDIR; /* Restore the errno. */
|
||||
} else if (errno == ELOOP) {
|
||||
@@ -224,4 +224,61 @@ OsString readLinkAt(Descriptor dirFd, const CanonPath & path)
|
||||
}
|
||||
}
|
||||
|
||||
static void symlinkAt(Descriptor dirFd, const CanonPath & path, const OsString & target)
|
||||
{
|
||||
assert(!path.isRoot());
|
||||
assert(!path.rel().starts_with('/')); /* Just in case the invariant is somehow broken. */
|
||||
if (::symlinkat(target.c_str(), dirFd, path.rel_c_str()) == -1) {
|
||||
auto savedErrno = errno;
|
||||
throw SysError(
|
||||
savedErrno, "creating symlink %1% -> %2%", PathFmt(descriptorToPath(dirFd) / path.rel()), target);
|
||||
}
|
||||
}
|
||||
|
||||
void createFileSymlinkAt(Descriptor dirFd, const CanonPath & path, const OsString & target)
|
||||
{
|
||||
symlinkAt(dirFd, path, target);
|
||||
}
|
||||
|
||||
void createDirectorySymlinkAt(Descriptor dirFd, const CanonPath & path, const OsString & target)
|
||||
{
|
||||
symlinkAt(dirFd, path, target);
|
||||
}
|
||||
|
||||
void createUnknownSymlinkAt(Descriptor dirFd, const CanonPath & path, const OsString & target)
|
||||
{
|
||||
symlinkAt(dirFd, path, target);
|
||||
}
|
||||
|
||||
void createDirectoryAt(Descriptor dirFd, const CanonPath & path)
|
||||
{
|
||||
assert(!path.isRoot());
|
||||
assert(!path.rel().starts_with('/')); /* Just in case the invariant is somehow broken. */
|
||||
if (::mkdirat(dirFd, path.rel_c_str(), 0777) == -1) {
|
||||
auto savedErrno = errno;
|
||||
throw SysError(savedErrno, "creating directory %1%", PathFmt(descriptorToPath(dirFd) / path.rel()));
|
||||
}
|
||||
}
|
||||
|
||||
PosixStat fstat(Descriptor fd)
|
||||
{
|
||||
PosixStat st;
|
||||
if (::fstat(fd, &st)) {
|
||||
auto savedErrno = errno;
|
||||
throw SysError(savedErrno, "getting status of %s", PathFmt(descriptorToPath(fd)));
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
PosixStat fstatat(Descriptor dirFd, const CanonPath & path)
|
||||
{
|
||||
assert(!path.isRoot());
|
||||
PosixStat st;
|
||||
if (::fstatat(dirFd, path.rel_c_str(), &st, AT_SYMLINK_NOFOLLOW)) {
|
||||
auto savedErrno = errno;
|
||||
throw SysError(savedErrno, "getting status of %s", PathFmt(descriptorToPath(dirFd) / path.rel()));
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
Descriptor openDirectory(const std::filesystem::path & path)
|
||||
Descriptor openDirectory(const std::filesystem::path & path, bool followFinalSymlink)
|
||||
{
|
||||
return open(path.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||
return open(path.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC | (followFinalSymlink ? 0 : O_NOFOLLOW));
|
||||
}
|
||||
|
||||
Descriptor openFileReadonly(const std::filesystem::path & path)
|
||||
@@ -76,6 +76,14 @@ std::filesystem::path defaultTempDir()
|
||||
return getEnvNonEmpty("TMPDIR").value_or("/tmp");
|
||||
}
|
||||
|
||||
PosixStat lstat(const std::filesystem::path & path)
|
||||
{
|
||||
PosixStat st;
|
||||
if (::lstat(path.c_str(), &st))
|
||||
throw SysError("getting status of %s", PathFmt(path));
|
||||
return st;
|
||||
}
|
||||
|
||||
void setWriteTime(
|
||||
const std::filesystem::path & path, time_t accessedTime, time_t modificationTime, std::optional<bool> optIsSymlink)
|
||||
{
|
||||
|
||||
@@ -148,4 +148,10 @@ off_t lseek(HANDLE h, off_t offset, int whence)
|
||||
return newPos.QuadPart;
|
||||
}
|
||||
|
||||
void syncDescriptor(Descriptor fd)
|
||||
{
|
||||
if (!::FlushFileBuffers(fd))
|
||||
throw WinError("FlushFileBuffers file descriptor %1%", fd);
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
#include "nix/util/file-path.hh"
|
||||
#include "nix/util/source-accessor.hh"
|
||||
|
||||
#include <span>
|
||||
|
||||
#include <fileapi.h>
|
||||
#include <error.h>
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <winioctl.h>
|
||||
@@ -88,8 +89,8 @@ HANDLE openSymlinkAt(Descriptor dirFd, const CanonPath & path)
|
||||
assert(!path.isRoot());
|
||||
assert(!path.rel().starts_with('/')); /* Just in case the invariant is somehow broken. */
|
||||
|
||||
std::wstring wpath = string_to_os_string(path.rel());
|
||||
return ntOpenAt(dirFd, wpath, FILE_READ_ATTRIBUTES | SYNCHRONIZE, FILE_OPEN_REPARSE_POINT);
|
||||
auto wpath = std::filesystem::path(path.rel()).make_preferred();
|
||||
return ntOpenAt(dirFd, wpath.c_str(), FILE_READ_ATTRIBUTES | SYNCHRONIZE, FILE_OPEN_REPARSE_POINT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,6 +192,54 @@ OsString readSymlinkTarget(HANDLE linkHandle)
|
||||
return {target_start, target_len};
|
||||
}
|
||||
|
||||
/**
|
||||
* Write symlink target to an open file handle using reparse point.
|
||||
*
|
||||
* @param handle Open file handle (must have GENERIC_WRITE access)
|
||||
* @param target The symlink target (what it points to)
|
||||
*/
|
||||
void writeSymlinkTarget(HANDLE handle, const std::filesystem::path & target)
|
||||
{
|
||||
/* Build the reparse data buffer for a symbolic link.
|
||||
Layout: SubstituteName and PrintName stored consecutively in PathBuffer.
|
||||
We use the same string for both. */
|
||||
size_t targetBytes = target.native().size() * sizeof(wchar_t);
|
||||
size_t bufSize = offsetof(ReparseDataBuffer, SymbolicLinkReparseBuffer.PathBuffer) + targetBytes * 2;
|
||||
std::vector<uint8_t> buf(bufSize, 0);
|
||||
|
||||
auto * reparse = reinterpret_cast<ReparseDataBuffer *>(buf.data());
|
||||
reparse->ReparseTag = IO_REPARSE_TAG_SYMLINK;
|
||||
reparse->ReparseDataLength =
|
||||
static_cast<unsigned short>(bufSize - offsetof(ReparseDataBuffer, SymbolicLinkReparseBuffer));
|
||||
reparse->Reserved = 0;
|
||||
|
||||
auto & symlink = reparse->SymbolicLinkReparseBuffer;
|
||||
/* SubstituteName comes first */
|
||||
symlink.SubstituteNameOffset = 0;
|
||||
symlink.SubstituteNameLength = static_cast<unsigned short>(targetBytes);
|
||||
/* PrintName follows SubstituteName */
|
||||
symlink.PrintNameOffset = static_cast<unsigned short>(targetBytes);
|
||||
symlink.PrintNameLength = static_cast<unsigned short>(targetBytes);
|
||||
/* SYMLINK_FLAG_RELATIVE = 1 for relative symlinks, 0 for absolute */
|
||||
symlink.Flags = target.is_relative() ? 1 : 0;
|
||||
|
||||
/* Copy target into PathBuffer twice (SubstituteName and PrintName) */
|
||||
memcpy(symlink.PathBuffer, target.c_str(), targetBytes);
|
||||
memcpy(reinterpret_cast<char *>(symlink.PathBuffer) + targetBytes, target.c_str(), targetBytes);
|
||||
|
||||
DWORD bytesReturned;
|
||||
if (!DeviceIoControl(
|
||||
handle,
|
||||
FSCTL_SET_REPARSE_POINT,
|
||||
buf.data(),
|
||||
static_cast<DWORD>(bufSize),
|
||||
nullptr,
|
||||
0,
|
||||
&bytesReturned,
|
||||
nullptr))
|
||||
throw WinError("setting reparse point for symlink");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a handle refers to a reparse point (e.g., symlink).
|
||||
*
|
||||
@@ -210,6 +259,115 @@ bool isReparsePoint(HANDLE handle)
|
||||
|
||||
} // namespace windows
|
||||
|
||||
OsString readLinkAt(Descriptor dirFd, const CanonPath & path)
|
||||
{
|
||||
AutoCloseFD linkHandle(openSymlinkAt(dirFd, path));
|
||||
return readSymlinkTarget(linkHandle.get());
|
||||
}
|
||||
|
||||
void createFileSymlinkAt(Descriptor dirFd, const CanonPath & path, const OsString & target)
|
||||
{
|
||||
assert(!path.isRoot());
|
||||
assert(!path.rel().starts_with('/')); /* Just in case the invariant is somehow broken. */
|
||||
|
||||
auto wpath = std::filesystem::path(path.rel()).make_preferred();
|
||||
|
||||
/* Create the file that will become the symlink */
|
||||
AutoCloseFD handle(ntOpenAt(
|
||||
dirFd, wpath.c_str(), GENERIC_WRITE | DELETE, FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT, FILE_CREATE));
|
||||
|
||||
writeSymlinkTarget(handle.get(), target);
|
||||
}
|
||||
|
||||
void createDirectorySymlinkAt(Descriptor dirFd, const CanonPath & path, const OsString & target)
|
||||
{
|
||||
assert(!path.isRoot());
|
||||
assert(!path.rel().starts_with('/')); /* Just in case the invariant is somehow broken. */
|
||||
|
||||
auto wpath = std::filesystem::path(path.rel()).make_preferred();
|
||||
|
||||
/* Create the directory that will become the symlink */
|
||||
AutoCloseFD handle(ntOpenAt(
|
||||
dirFd, wpath.c_str(), GENERIC_WRITE | DELETE, FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT, FILE_CREATE));
|
||||
|
||||
writeSymlinkTarget(handle.get(), target);
|
||||
}
|
||||
|
||||
void createUnknownSymlinkAt(Descriptor dirFd, const CanonPath & path, const OsString & target)
|
||||
{
|
||||
assert(!path.isRoot());
|
||||
assert(!path.rel().starts_with('/')); /* Just in case the invariant is somehow broken. */
|
||||
|
||||
/* Resolve target path to determine if it's a directory.
|
||||
Target is relative to the symlink's parent directory. */
|
||||
std::filesystem::path targetPath(target);
|
||||
std::filesystem::path resolvedTarget;
|
||||
|
||||
if (targetPath.is_absolute()) {
|
||||
resolvedTarget = targetPath;
|
||||
} else {
|
||||
/* Get the symlink's parent directory path and resolve target relative to it */
|
||||
auto symlinkDir = descriptorToPath(dirFd);
|
||||
if (auto parent = path.parent())
|
||||
symlinkDir /= parent->rel();
|
||||
resolvedTarget = symlinkDir / targetPath;
|
||||
}
|
||||
|
||||
/* Check if target is a directory. If we can't stat it (e.g., dangling symlink),
|
||||
fall back to file symlink. */
|
||||
std::error_code ec;
|
||||
auto status = std::filesystem::status(resolvedTarget, ec);
|
||||
bool isDirectory = !ec && std::filesystem::is_directory(status);
|
||||
|
||||
if (isDirectory)
|
||||
createDirectorySymlinkAt(dirFd, path, target);
|
||||
else
|
||||
createFileSymlinkAt(dirFd, path, target);
|
||||
}
|
||||
|
||||
void createDirectoryAt(Descriptor dirFd, const CanonPath & path)
|
||||
{
|
||||
assert(!path.isRoot());
|
||||
assert(!path.rel().starts_with('/')); /* Just in case the invariant is somehow broken. */
|
||||
|
||||
auto wpath = std::filesystem::path(path.rel()).make_preferred();
|
||||
|
||||
/* Create the directory. The handle is immediately closed. */
|
||||
AutoCloseFD handle(ntOpenAt(dirFd, wpath.c_str(), FILE_TRAVERSE | SYNCHRONIZE, FILE_DIRECTORY_FILE, FILE_CREATE));
|
||||
}
|
||||
|
||||
PosixStat fstat(Descriptor fd)
|
||||
{
|
||||
BY_HANDLE_FILE_INFORMATION info;
|
||||
if (!GetFileInformationByHandle(fd, &info))
|
||||
throw WinError("getting file information for %s", PathFmt(descriptorToPath(fd)));
|
||||
|
||||
PosixStat st;
|
||||
windows::statFromFileInfo(
|
||||
st,
|
||||
info.dwFileAttributes,
|
||||
info.ftCreationTime,
|
||||
info.ftLastAccessTime,
|
||||
info.ftLastWriteTime,
|
||||
info.nFileSizeHigh,
|
||||
info.nFileSizeLow,
|
||||
info.nNumberOfLinks);
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
PosixStat fstatat(Descriptor dirFd, const CanonPath & path)
|
||||
{
|
||||
assert(!path.isRoot());
|
||||
|
||||
auto wpath = std::filesystem::path(path.rel()).make_preferred();
|
||||
|
||||
/* Open the file without following symlinks */
|
||||
AutoCloseFD handle(ntOpenAt(dirFd, wpath.c_str(), FILE_READ_ATTRIBUTES | SYNCHRONIZE, FILE_OPEN_REPARSE_POINT));
|
||||
|
||||
return fstat(handle.get());
|
||||
}
|
||||
|
||||
Descriptor openFileEnsureBeneathNoSymlinks(
|
||||
Descriptor dirFd, const CanonPath & path, ACCESS_MASK desiredAccess, ULONG createOptions, ULONG createDisposition)
|
||||
{
|
||||
@@ -301,10 +459,4 @@ Descriptor openFileEnsureBeneathNoSymlinks(
|
||||
return finalHandle;
|
||||
}
|
||||
|
||||
OsString readLinkAt(Descriptor dirFd, const CanonPath & path)
|
||||
{
|
||||
AutoCloseFD linkHandle(windows::openSymlinkAt(dirFd, path));
|
||||
return windows::readSymlinkTarget(linkHandle.get());
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -23,7 +23,7 @@ void setWriteTime(
|
||||
warn("Changing file times is not yet implemented on Windows, path is %s", PathFmt(path));
|
||||
}
|
||||
|
||||
Descriptor openDirectory(const std::filesystem::path & path)
|
||||
Descriptor openDirectory(const std::filesystem::path & path, bool followFinalSymlink)
|
||||
{
|
||||
return CreateFileW(
|
||||
path.c_str(),
|
||||
@@ -31,7 +31,7 @@ Descriptor openDirectory(const std::filesystem::path & path)
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
/*lpSecurityAttributes=*/nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | (followFinalSymlink ? 0 : FILE_FLAG_OPEN_REPARSE_POINT),
|
||||
/*hTemplateFile=*/nullptr);
|
||||
}
|
||||
|
||||
@@ -105,4 +105,67 @@ std::filesystem::path descriptorToPath(Descriptor handle)
|
||||
return std::filesystem::path{std::wstring{buf.data(), dw}};
|
||||
}
|
||||
|
||||
time_t windows::fileTimeToUnixTime(const FILETIME & ft)
|
||||
{
|
||||
/* FILETIME is 100-nanosecond intervals since January 1, 1601 UTC.
|
||||
Unix time is seconds since January 1, 1970 UTC.
|
||||
Difference is 11644473600 seconds. */
|
||||
ULARGE_INTEGER ull;
|
||||
ull.LowPart = ft.dwLowDateTime;
|
||||
ull.HighPart = ft.dwHighDateTime;
|
||||
return static_cast<time_t>(ull.QuadPart / 10000000ULL - 11644473600ULL);
|
||||
}
|
||||
|
||||
void windows::statFromFileInfo(
|
||||
PosixStat & st,
|
||||
DWORD dwFileAttributes,
|
||||
const FILETIME & ftCreationTime,
|
||||
const FILETIME & ftLastAccessTime,
|
||||
const FILETIME & ftLastWriteTime,
|
||||
DWORD nFileSizeHigh,
|
||||
DWORD nFileSizeLow,
|
||||
DWORD nNumberOfLinks)
|
||||
{
|
||||
memset(&st, 0, sizeof(st));
|
||||
|
||||
/* Determine file type */
|
||||
if (dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||
st.st_mode = S_IFLNK | 0777;
|
||||
} else if (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
st.st_mode = S_IFDIR | 0755;
|
||||
} else {
|
||||
st.st_mode = S_IFREG | 0644;
|
||||
}
|
||||
|
||||
/* File size (only meaningful for regular files) */
|
||||
st.st_size = (static_cast<int64_t>(nFileSizeHigh) << 32) | nFileSizeLow;
|
||||
|
||||
/* Timestamps */
|
||||
st.st_atime = fileTimeToUnixTime(ftLastAccessTime);
|
||||
st.st_mtime = fileTimeToUnixTime(ftLastWriteTime);
|
||||
st.st_ctime = fileTimeToUnixTime(ftCreationTime);
|
||||
|
||||
st.st_nlink = nNumberOfLinks;
|
||||
st.st_uid = 0;
|
||||
st.st_gid = 0;
|
||||
}
|
||||
|
||||
PosixStat lstat(const std::filesystem::path & path)
|
||||
{
|
||||
WIN32_FILE_ATTRIBUTE_DATA attrData;
|
||||
if (!GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &attrData))
|
||||
throw WinError("getting status of %s", PathFmt(path));
|
||||
|
||||
PosixStat st;
|
||||
windows::statFromFileInfo(
|
||||
st,
|
||||
attrData.dwFileAttributes,
|
||||
attrData.ftCreationTime,
|
||||
attrData.ftLastAccessTime,
|
||||
attrData.ftLastWriteTime,
|
||||
attrData.nFileSizeHigh,
|
||||
attrData.nFileSizeLow);
|
||||
return st;
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
Reference in New Issue
Block a user