Compare commits

..

1 Commits

Author SHA1 Message Date
John Ericson
9f3de0f3e5 Get rid of SysError and WinError derived classes
All we need is `SystemError`, and the various ways to construct it can
be done with static methods that are more informative. Catching the
derived classes was a footgun that is now impossible, because one can
only has `SystemError` to catch.

I did however make `SysError` and `WinError` top-level function
wrappers in order to avoid churn in the vast majority of call sites.
2026-02-18 14:13:57 -05:00
37 changed files with 339 additions and 1097 deletions

View File

@@ -1,6 +1,5 @@
#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"
@@ -110,7 +109,7 @@ std::shared_ptr<SourceAccessor> LocalFSStore::getFSAccessor(const StorePath & pa
if (!pathExists(absPath))
return nullptr;
}
return makeFSSourceAccessor(std::move(absPath));
return std::make_shared<PosixSourceAccessor>(std::move(absPath));
}
const std::string LocalFSStore::drvsLogDir = "drvs";

View File

@@ -5,7 +5,6 @@
#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

View File

@@ -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 = openDirectory(tmpDir, /*followFinalSymlink=*/false);
tmpDirFd = AutoCloseFD{open(tmpDir.c_str(), O_RDONLY | O_NOFOLLOW | O_DIRECTORY)};
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.get(), contents);
writeFile(fd, path, contents);
chownToBuilder(fd.get(), path);
}

View File

@@ -1,5 +1,5 @@
#include "nix/store/pathlocks.hh"
#include "nix/util/file-system-at.hh"
#include "nix/util/file-system.hh"
#include "nix/util/util.hh"
#include "nix/util/sync.hh"
#include "nix/util/signals.hh"

View File

@@ -56,26 +56,24 @@ bool lockFile(Descriptor desc, LockType lockType, bool wait)
switch (lockType) {
case ltNone: {
OVERLAPPED ov = {0};
if (!UnlockFileEx(desc, 0, 2, 0, &ov)) {
WinError winError("Failed to unlock file desc %s", desc);
throw winError;
}
if (!UnlockFileEx(desc, 0, 2, 0, &ov))
throw WinError("Failed to unlock file desc %s", desc);
return true;
}
case ltRead: {
OVERLAPPED ov = {0};
if (!LockFileEx(desc, wait ? 0 : LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &ov)) {
WinError winError("Failed to lock file desc %s", desc);
if (winError.lastError == ERROR_LOCK_VIOLATION)
auto lastError = GetLastError();
if (lastError == ERROR_LOCK_VIOLATION)
return false;
throw winError;
throw WinError(lastError, "Failed to lock file desc %s", desc);
}
ov.Offset = 1;
if (!UnlockFileEx(desc, 0, 1, 0, &ov)) {
WinError winError("Failed to unlock file desc %s", desc);
if (winError.lastError != ERROR_NOT_LOCKED)
throw winError;
auto lastError = GetLastError();
if (lastError != ERROR_NOT_LOCKED)
throw WinError(lastError, "Failed to unlock file desc %s", desc);
}
return true;
}
@@ -83,17 +81,17 @@ bool lockFile(Descriptor desc, LockType lockType, bool wait)
OVERLAPPED ov = {0};
ov.Offset = 1;
if (!LockFileEx(desc, LOCKFILE_EXCLUSIVE_LOCK | (wait ? 0 : LOCKFILE_FAIL_IMMEDIATELY), 0, 1, 0, &ov)) {
WinError winError("Failed to lock file desc %s", desc);
if (winError.lastError == ERROR_LOCK_VIOLATION)
auto lastError = GetLastError();
if (lastError == ERROR_LOCK_VIOLATION)
return false;
throw winError;
throw WinError(lastError, "Failed to lock file desc %s", desc);
}
ov.Offset = 0;
if (!UnlockFileEx(desc, 0, 1, 0, &ov)) {
WinError winError("Failed to unlock file desc %s", desc);
if (winError.lastError != ERROR_NOT_LOCKED)
throw winError;
auto lastError = GetLastError();
if (lastError != ERROR_NOT_LOCKED)
throw WinError(lastError, "Failed to unlock file desc %s", desc);
}
return true;
}

View File

@@ -1,11 +1,10 @@
#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 {
/* ----------------------------------------------------------------------------

View File

@@ -91,7 +91,9 @@ 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"); });
@@ -135,34 +137,4 @@ 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

View File

@@ -47,16 +47,16 @@ TEST(fchmodatTryNoFollow, works)
/* Check that symlinks are not followed and targets are not changed. */
EXPECT_NO_THROW(
try { fchmodatTryNoFollow(dirFd.get(), CanonPath("filelink"), 0777); } catch (SysError & e) {
if (e.errNo != EOPNOTSUPP)
try { fchmodatTryNoFollow(dirFd.get(), CanonPath("filelink"), 0777); } catch (SystemError & e) {
if (!e.is(std::errc::operation_not_supported))
throw;
});
ASSERT_EQ(stat((tmpDir / "file").c_str(), &st), 0);
EXPECT_EQ(st.st_mode & 0777, 0644);
EXPECT_NO_THROW(
try { fchmodatTryNoFollow(dirFd.get(), CanonPath("dirlink"), 0777); } catch (SysError & e) {
if (e.errNo != EOPNOTSUPP)
try { fchmodatTryNoFollow(dirFd.get(), CanonPath("dirlink"), 0777); } catch (SystemError & e) {
if (!e.is(std::errc::operation_not_supported))
throw;
});
ASSERT_EQ(stat((tmpDir / "dir").c_str(), &st), 0);
@@ -110,14 +110,14 @@ TEST(fchmodatTryNoFollow, fallbackWithoutProc)
try {
fchmodatTryNoFollow(dirFd.get(), CanonPath("file"), 0600);
} catch (SysError & e) {
} catch (SystemError & e) {
_exit(1);
}
try {
fchmodatTryNoFollow(dirFd.get(), CanonPath("link"), 0777);
} catch (SysError & e) {
if (e.errNo == EOPNOTSUPP)
} catch (SystemError & e) {
if (e.is(std::errc::operation_not_supported))
_exit(0); /* Success. */
}

View File

@@ -1,285 +0,0 @@
// 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

View File

@@ -184,6 +184,24 @@ 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__

View File

@@ -1,16 +0,0 @@
#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

View File

@@ -195,42 +195,71 @@ 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
_wstat64
_fstat64
#else
::stat
::fstat
#endif
(path.c_str(), &st))
throw SysError("getting status of %s", PathFmt(path));
(fd, &st))
throw SysError("getting status of fd %d", fd);
return st;
}
std::optional<PosixStat> maybeStat(const std::filesystem::path & 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;
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));
}
return st;
}
std::optional<PosixStat> maybeLstat(const std::filesystem::path & 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;
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));
}
return st;
}
#undef STAT
#undef LSTAT
bool pathExists(const std::filesystem::path & path)
{
return maybeLstat(path.string()).has_value();
@@ -311,23 +340,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.get(), s, sync, &path);
writeFile(fd, path, s, mode, sync);
/* Close explicitly to propagate the exceptions. */
fd.close();
}
void writeFile(Descriptor fd, std::string_view s, FsSync sync, const Path * origPath)
void writeFile(AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode, FsSync sync)
{
assert(fd != INVALID_DESCRIPTOR);
assert(fd);
try {
writeFull(fd, s);
writeFull(fd.get(), s);
if (sync == FsSync::Yes)
syncDescriptor(fd);
fd.fsync();
} catch (Error & e) {
e.addTrace({}, "writing file '%1%'", origPath ? *origPath : descriptorToPath(fd).string());
e.addTrace({}, "writing file '%1%'", origPath);
throw;
}
}

View File

@@ -105,26 +105,34 @@ 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));
createDirectoryAt(dirFd.get(), path);
if (::mkdirat(dirFd.get(), path.rel_c_str(), 0777) == -1)
throw SysError("creating directory %s", PathFmt(p));
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 = openDirectory(p, /*followFinalSymlink=*/false);
dirFd = open(p.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (!dirFd)
throw SysError("creating directory %1%", PathFmt(p));
}
}
#endif
};
struct RestoreRegularFile : CreateRegularFileSink, FdSink
{
@@ -156,26 +164,28 @@ void RestoreSink::createRegularFile(const CanonPath & path, std::function<void(C
{
auto p = append(dstPath, path);
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(),
auto crf = RestoreRegularFile(
startFsync,
#ifdef _WIN32
CreateFileW(
p.c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_NON_DIRECTORY_FILE,
FILE_CREATE
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL)
#else
O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC,
0666
[&]() {
/* 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);
}()
#endif
);
}());
);
if (!crf.fd)
throw NativeSysError("creating file %1%", PathFmt(p));
func(crf);
@@ -214,16 +224,14 @@ void RestoreRegularFile::preallocateContents(uint64_t len)
void RestoreSink::createSymlink(const CanonPath & path, const std::string & target)
{
auto p = append(dstPath, path);
AutoCloseFD parentFd;
if (!dirFd) {
assert(path.isRoot());
assert(p.has_parent_path());
parentFd = openDirectory(p.parent_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;
}
createUnknownSymlinkAt(
dirFd ? dirFd.get() : parentFd.get(),
dirFd ? path : CanonPath::root / p.filename().string(),
string_to_os_string(target));
#endif
nix::createSymlink(target, p.string());
}
void RegularFileSink::createRegularFile(const CanonPath & path, std::function<void(CreateRegularFileSink &)> func)

View File

@@ -1,25 +0,0 @@
#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

View File

@@ -250,18 +250,15 @@ class SystemError : public Error
std::error_code errorCode;
std::string errorDetails;
protected:
/**
* Just here to allow derived classes to use the right constructor
* (the protected one).
* Just here to allow the static methods to use the right constructor
* (the private one).
*/
struct Disambig
{};
/**
* Protected constructor for subclasses that provide their own error message.
* The error message is appended to the formatted hint.
* Private constructor with explicit error message string.
*/
template<typename... Args>
SystemError(Disambig, std::error_code errorCode, std::string_view errorDetails, Args &&... args)
@@ -284,6 +281,79 @@ public:
{
}
/**
* Construct a POSIX error using the explicitly-provided error number.
* `strerror` will be used to try to add additional information to the message.
*/
template<typename... Args>
static SystemError fromPosixExplicit(int errNo, Args &&... args)
{
return SystemError(
Disambig{},
std::make_error_code(static_cast<std::errc>(errNo)),
strerror(errNo),
std::forward<Args>(args)...);
}
/**
* Construct a POSIX error using the ambient `errno`.
*
* Be sure to not perform another `errno`-modifying operation before
* calling this!
*/
template<typename... Args>
static SystemError fromPosix(Args &&... args)
{
return fromPosixExplicit(errno, std::forward<Args>(args)...);
}
#ifdef _WIN32
/**
* Construct a Windows error using the explicitly-provided error number.
* `FormatMessageA` will be used to try to add additional information
* to the message.
*/
template<typename... Args>
static SystemError fromWindowsExplicit(DWORD lastError, Args &&... args)
{
return SystemError(
Disambig{},
std::error_code(lastError, std::system_category()),
renderWindowsError(lastError),
std::forward<Args>(args)...);
}
/**
* Construct a Windows error using `GetLastError()`.
*
* Be sure to not perform another last-error-modifying operation
* before calling this!
*/
template<typename... Args>
static SystemError fromWindows(Args &&... args)
{
return fromWindowsExplicit(GetLastError(), std::forward<Args>(args)...);
}
private:
static std::string renderWindowsError(DWORD lastError);
public:
#endif
/**
* Construct using the native error (errno on POSIX, GetLastError() on Windows).
*/
template<typename... Args>
static SystemError fromNative(Args &&... args)
{
#ifdef _WIN32
return fromWindows(std::forward<Args>(args)...);
#else
return fromPosix(std::forward<Args>(args)...);
#endif
}
const std::error_code ec() const &
{
return errorCode;
@@ -296,53 +366,58 @@ public:
};
/**
* POSIX system error, created using `errno`, `strerror` friends.
*
* Throw this, but prefer not to catch this, and catch `SystemError`
* instead. This allows implementations to freely switch between this
* and `windows::WinError` without breaking catch blocks.
*
* However, it is permissible to catch this and rethrow so long as
* certain conditions are not met (e.g. to catch only if `errNo =
* EFooBar`). In that case, try to also catch the equivalent `windows::WinError`
* code.
*
* @todo Rename this to `PosixError` or similar. At this point Windows
* support is too WIP to justify the code churn, but if it is finished
* then a better identifier becomes moe worth it.
* Wrapper to avoid churn
*/
class SysError : public SystemError
template<typename... Args>
SystemError SysError(int errNo, Args &&... args)
{
public:
int errNo;
return SystemError::fromPosixExplicit(errNo, std::forward<Args>(args)...);
}
/**
* Construct using the explicitly-provided error number. `strerror`
* will be used to try to add additional information to the message.
*/
template<typename... Args>
SysError(int errNo, Args &&... args)
: SystemError(
Disambig{},
std::make_error_code(static_cast<std::errc>(errNo)),
strerror(errNo),
std::forward<Args>(args)...)
, errNo(errNo)
{
}
/**
* Wrapper to avoid churn
*/
template<typename... Args>
SystemError SysError(Args &&... args)
{
return SystemError::fromPosix(std::forward<Args>(args)...);
}
/**
* Construct using the ambient `errno`.
*
* Be sure to not perform another `errno`-modifying operation before
* calling this constructor!
*/
template<typename... Args>
SysError(Args &&... args)
: SysError(errno, std::forward<Args>(args)...)
{
}
};
#ifdef _WIN32
namespace windows {
/**
* Wrapper to avoid churn
*/
template<typename... Args>
SystemError WinError(DWORD lastError, Args &&... args)
{
return SystemError::fromWindowsExplicit(lastError, std::forward<Args>(args)...);
}
/**
* Wrapper to avoid churn
*/
template<typename... Args>
SystemError WinError(Args &&... args)
{
return SystemError::fromWindows(std::forward<Args>(args)...);
}
} // namespace windows
#endif
/**
* Convenience wrapper for when we use `errno`-based error handling
* on Unix, and `GetLastError()`-based error handling on Windows.
*/
template<typename... Args>
SystemError NativeSysError(Args &&... args)
{
return SystemError::fromNative(std::forward<Args>(args)...);
}
/**
* Throw an exception for the purpose of checking that exception
@@ -382,69 +457,4 @@ int handleExceptions(const std::string & programName, std::function<void()> fun)
# define nixUnreachableWhenHardened std::unreachable
#endif
#ifdef _WIN32
namespace windows {
/**
* Windows Error type.
*
* Unless you need to catch a specific error number, don't catch this in
* portable code. Catch `SystemError` instead.
*/
class WinError : public SystemError
{
public:
DWORD lastError;
/**
* Construct using the explicitly-provided error number.
* `FormatMessageA` will be used to try to add additional
* information to the message.
*/
template<typename... Args>
WinError(DWORD lastError, Args &&... args)
: SystemError(
Disambig{},
std::error_code(lastError, std::system_category()),
renderError(lastError),
std::forward<Args>(args)...)
, lastError(lastError)
{
}
/**
* Construct using `GetLastError()` and the ambient "last error".
*
* Be sure to not perform another last-error-modifying operation
* before calling this constructor!
*/
template<typename... Args>
WinError(Args &&... args)
: WinError(GetLastError(), std::forward<Args>(args)...)
{
}
private:
static std::string renderError(DWORD lastError);
};
} // namespace windows
#endif
/**
* Convenience alias for when we use a `errno`-based error handling
* function on Unix, and `GetLastError()`-based error handling on on
* Windows.
*/
using NativeSysError =
#ifdef _WIN32
windows::WinError
#else
SysError
#endif
;
} // namespace nix

View File

@@ -133,11 +133,6 @@ 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.
*/
@@ -258,11 +253,7 @@ public:
/**
* Perform a blocking fsync operation.
*/
void fsync() const
{
if (fd != INVALID_DESCRIPTOR)
nix::syncDescriptor(fd);
}
void fsync() const;
/**
* Asynchronously flush to disk without blocking, if available on

View File

@@ -15,7 +15,6 @@
*/
#include "nix/util/file-descriptor.hh"
#include "nix/util/file-system.hh"
#include <optional>
@@ -34,90 +33,6 @@ 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.
@@ -182,7 +97,7 @@ namespace unix {
* AT_SYMLINK_NOFOLLOW, since it's the best we can do without failing.
*
* @pre path.isRoot() is false
* @throws SysError if any operation fails
* @throws SystemError if any operation fails
*/
void fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t mode);

View File

@@ -28,20 +28,11 @@
* Polyfill for MinGW
*
* Windows does in fact support symlinks, but the C runtime interfaces predate this.
* 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.
*
* @todo get rid of this, and stop using `stat` when we want `lstat` too.
*/
#ifndef S_IFLNK
# ifndef _WIN32
# error "S_IFLNK should be defined on non-Windows platforms"
# endif
# define S_IFLNK 0120000
#endif
#ifndef S_ISLNK
# ifndef _WIN32
# error "S_ISLNK should be defined on non-Windows platforms"
# endif
# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
# define S_ISLNK(m) false
#endif
namespace nix {
@@ -134,38 +125,6 @@ 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`.
*/
@@ -174,6 +133,10 @@ 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
@@ -236,16 +199,14 @@ std::filesystem::path readLink(const std::filesystem::path & path);
*
* @note this function will clobber `errno` (Unix) / "last error"
* (Windows), so care must be used to get those error codes, then call
* this, then build a `SysError` / `WinError` with the saved error code.
* this, then build a `SystemError` with the saved error code.
*/
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, bool followFinalSymlink = true);
Descriptor openDirectory(const std::filesystem::path & path);
/**
* Open a `Descriptor` with read-only access to the given file.
@@ -305,7 +266,8 @@ writeFile(const std::filesystem::path & path, Source & source, mode_t mode = 066
return writeFile(path.string(), source, mode, sync);
}
void writeFile(Descriptor fd, std::string_view s, FsSync sync = FsSync::No, const Path * origPath = nullptr);
void writeFile(
AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode = 0666, FsSync sync = FsSync::No);
/**
* Flush a path's parent directory to disk.

View File

@@ -32,7 +32,6 @@ headers = [ config_pub_h ] + files(
'configuration.hh',
'current-process.hh',
'demangle.hh',
'directory-source-accessor.hh',
'english.hh',
'environment-variables.hh',
'error.hh',

View File

@@ -82,8 +82,6 @@ public:
void invalidateCache(const CanonPath & path) override;
static Stat makeStat(const struct ::stat st);
private:
/**

View File

@@ -40,7 +40,7 @@ bool userNamespacesSupported()
auto r = pid.wait();
assert(!r);
} catch (SysError & e) {
} catch (SystemError & e) {
debug("user namespaces do not work on this system: %s", e.msg());
return false;
}
@@ -77,7 +77,7 @@ bool mountAndPidNamespacesSupported()
return false;
}
} catch (SysError & e) {
} catch (SystemError & e) {
debug("mount namespaces do not work on this system: %s", e.msg());
return false;
}

View File

@@ -139,7 +139,6 @@ sources = [ config_priv_h ] + files(
'config-global.cc',
'configuration.cc',
'current-process.cc',
'directory-source-accessor.cc',
'english.cc',
'environment-variables.cc',
'error.cc',
@@ -148,7 +147,6 @@ 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',

View File

@@ -1,5 +1,4 @@
#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"
@@ -109,7 +108,22 @@ std::optional<SourceAccessor::Stat> PosixSourceAccessor::maybeLstat(const CanonP
if (trackLastModified)
mtime = std::max(mtime, st->st_mtime);
return makeStat(*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,
};
}
SourceAccessor::DirEntries PosixSourceAccessor::readDirectory(const CanonPath & path)
@@ -186,26 +200,6 @@ 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>();
@@ -214,30 +208,6 @@ 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

View File

@@ -78,8 +78,8 @@ bindConnectProcHelper(std::string_view operationName, auto && operation, Socket
if (operation(fd, psaddr, sizeof(addr)) == -1)
throw SysError("cannot %s to socket at '%s'", operationName, path);
writeFull(pipe.writeSide.get(), "0\n");
} catch (SysError & e) {
writeFull(pipe.writeSide.get(), fmt("%d\n", e.errNo));
} catch (SystemError & e) {
writeFull(pipe.writeSide.get(), fmt("%d\n", e.ec().value()));
} catch (...) {
writeFull(pipe.writeSide.get(), "-1\n");
}

View File

@@ -1,5 +1,4 @@
#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"
@@ -189,7 +188,7 @@ void unix::closeExtraFDs()
}
}
return;
} catch (SysError &) {
} catch (SystemError &) {
}
#endif
@@ -208,17 +207,4 @@ 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

View File

@@ -23,8 +23,6 @@
# define HAVE_FCHMODAT2 0
#endif
#include "util-unix-config-private.hh"
namespace nix {
#ifdef __linux__
@@ -92,9 +90,10 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
PathFmt(descriptorToPath(dirFd) / path.rel()));
}
/* Possible to use with O_PATH fd since
* https://github.com/torvalds/linux/commit/55815f70147dcfa3ead5738fd56d3574e2e3c1c2 (3.6) */
auto st = fstat(pathFd.get());
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());
if (S_ISLNK(st.st_mode))
throw SysError(EOPNOTSUPP, "can't change mode of symlink %s", PathFmt(descriptorToPath(dirFd) / path.rel()));
@@ -172,7 +171,8 @@ openFileEnsureBeneathNoSymlinksIterative(Descriptor dirFd, const CanonPath & pat
});
if (errno == ENOTDIR) /* Path component might be a symlink. */ {
if (auto st = maybeFstatat(getParentFd(), CanonPath(component)); st && S_ISLNK(st->st_mode))
struct ::stat st;
if (::fstatat(getParentFd(), component.c_str(), &st, AT_SYMLINK_NOFOLLOW) == 0 && S_ISLNK(st.st_mode))
throw SymlinkNotAllowed(path2);
errno = ENOTDIR; /* Restore the errno. */
} else if (errno == ELOOP) {
@@ -224,61 +224,4 @@ 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

View File

@@ -23,9 +23,9 @@
namespace nix {
Descriptor openDirectory(const std::filesystem::path & path, bool followFinalSymlink)
Descriptor openDirectory(const std::filesystem::path & path)
{
return open(path.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC | (followFinalSymlink ? 0 : O_NOFOLLOW));
return open(path.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC);
}
Descriptor openFileReadonly(const std::filesystem::path & path)
@@ -76,14 +76,6 @@ 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)
{
@@ -194,9 +186,9 @@ static void _deletePath(
if ((st.st_mode & PERM_MASK) != PERM_MASK)
try {
unix::fchmodatTryNoFollow(parentfd, CanonPath(name), st.st_mode | PERM_MASK);
} catch (SysError & e) {
} catch (SystemError & e) {
e.addTrace({}, "while making directory %1% accessible for deletion", PathFmt(path));
if (e.errNo == EOPNOTSUPP)
if (e.is(std::errc::operation_not_supported))
e.addTrace({}, "%1% is now a symlink, expected directory", PathFmt(path));
throw;
}

View File

@@ -60,7 +60,7 @@ inline void MonitorFdHup::runThread(int watchFd, int notifyFd)
{
int kqResult = kqueue();
if (kqResult < 0) {
throw SysError("MonitorFdHup kqueue");
throw SystemError::fromPosix("MonitorFdHup kqueue");
}
AutoCloseFD kq{kqResult};
@@ -78,14 +78,14 @@ inline void MonitorFdHup::runThread(int watchFd, int notifyFd)
int result = kevent(kq.get(), kevs.data(), kevs.size(), nullptr, 0, nullptr);
if (result < 0) {
throw SysError("MonitorFdHup kevent add");
throw SystemError::fromPosix("MonitorFdHup kevent add");
}
while (true) {
struct kevent event;
int numEvents = kevent(kq.get(), nullptr, 0, &event, 1, nullptr);
if (numEvents < 0) {
throw SysError("MonitorFdHup kevent watch");
throw SystemError::fromPosix("MonitorFdHup kevent watch");
}
if (numEvents > 0 && (event.flags & EV_EOF)) {
@@ -112,7 +112,7 @@ inline void MonitorFdHup::runThread(int watchFd, int notifyFd)
if (errno == EINTR || errno == EAGAIN) {
continue;
} else {
throw SysError("in MonitorFdHup poll()");
throw SystemError::fromPosix("in MonitorFdHup poll()");
}
}

View File

@@ -39,7 +39,7 @@ std::filesystem::path getHome()
auto st = maybeStat(homeDir->c_str());
if (st && st->st_uid != geteuid())
unownedUserHomeDir.swap(homeDir);
} catch (SysError & e) {
} catch (SystemError & e) {
warn(
"couldn't stat $HOME ('%s') for reason other than not existing, falling back to the one defined in the 'passwd' file: %s",
*homeDir,

View File

@@ -8,6 +8,8 @@
namespace nix {
using namespace nix::windows;
std::chrono::microseconds getCpuUserTime()
{
FILETIME creationTime;
@@ -17,7 +19,7 @@ std::chrono::microseconds getCpuUserTime()
if (!GetProcessTimes(GetCurrentProcess(), &creationTime, &exitTime, &kernelTime, &userTime)) {
auto lastError = GetLastError();
throw windows::WinError(lastError, "failed to get CPU time");
throw WinError(lastError, "failed to get CPU time");
}
ULARGE_INTEGER uLargeInt;

View File

@@ -148,10 +148,4 @@ 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

View File

@@ -4,9 +4,8 @@
#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>
@@ -14,8 +13,6 @@
namespace nix {
using namespace nix::windows;
namespace windows {
namespace {
@@ -89,8 +86,8 @@ HANDLE openSymlinkAt(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();
return ntOpenAt(dirFd, wpath.c_str(), FILE_READ_ATTRIBUTES | SYNCHRONIZE, FILE_OPEN_REPARSE_POINT);
std::wstring wpath = string_to_os_string(path.rel());
return ntOpenAt(dirFd, wpath, FILE_READ_ATTRIBUTES | SYNCHRONIZE, FILE_OPEN_REPARSE_POINT);
}
/**
@@ -192,54 +189,6 @@ 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).
*
@@ -259,114 +208,7 @@ 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());
}
using namespace nix::windows;
Descriptor openFileEnsureBeneathNoSymlinks(
Descriptor dirFd, const CanonPath & path, ACCESS_MASK desiredAccess, ULONG createOptions, ULONG createDisposition)
@@ -417,9 +259,10 @@ Descriptor openFileEnsureBeneathNoSymlinks(
FILE_TRAVERSE | SYNCHRONIZE, // Just need traversal rights
FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT // Open directory, don't follow symlinks
);
} catch (WinError & e) {
} catch (SystemError & e) {
/* Check if this is because it's a symlink */
if (e.lastError == ERROR_CANT_ACCESS_FILE || e.lastError == ERROR_ACCESS_DENIED) {
auto err = e.ec().value();
if (err == ERROR_CANT_ACCESS_FILE || err == ERROR_ACCESS_DENIED) {
throwIfSymlink(wcomponent, pathUpTo(std::next(it)));
}
throw;
@@ -444,9 +287,9 @@ Descriptor openFileEnsureBeneathNoSymlinks(
desiredAccess,
createOptions | FILE_OPEN_REPARSE_POINT, // Don't follow symlinks on final component either
createDisposition);
} catch (WinError & e) {
} catch (SystemError & e) {
/* Check if final component is a symlink when we requested to not follow it */
if (e.lastError == ERROR_CANT_ACCESS_FILE) {
if (e.ec().value() == ERROR_CANT_ACCESS_FILE) {
throwIfSymlink(finalComponent, path);
}
throw;
@@ -459,4 +302,10 @@ Descriptor openFileEnsureBeneathNoSymlinks(
return finalHandle;
}
OsString readLinkAt(Descriptor dirFd, const CanonPath & path)
{
AutoCloseFD linkHandle(windows::openSymlinkAt(dirFd, path));
return windows::readSymlinkTarget(linkHandle.get());
}
} // namespace nix

View File

@@ -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, bool followFinalSymlink)
Descriptor openDirectory(const std::filesystem::path & path)
{
return CreateFileW(
path.c_str(),
@@ -31,7 +31,7 @@ Descriptor openDirectory(const std::filesystem::path & path, bool followFinalSym
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
/*lpSecurityAttributes=*/nullptr,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | (followFinalSymlink ? 0 : FILE_FLAG_OPEN_REPARSE_POINT),
FILE_FLAG_BACKUP_SEMANTICS,
/*hTemplateFile=*/nullptr);
}
@@ -105,67 +105,4 @@ 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

View File

@@ -8,8 +8,6 @@
namespace nix::windows::known_folders {
using namespace nix::windows;
static std::filesystem::path getKnownFolder(REFKNOWNFOLDERID rfid)
{
PWSTR str = nullptr;

View File

@@ -7,15 +7,17 @@
namespace nix {
using namespace nix::windows;
void MuxablePipePollState::poll(HANDLE ioport, std::optional<unsigned int> timeout)
{
/* We are on at least Windows Vista / Server 2008 and can get many
(countof(oentries)) statuses in one API call. */
if (!GetQueuedCompletionStatusEx(
ioport, oentries, sizeof(oentries) / sizeof(*oentries), &removed, timeout ? *timeout : INFINITE, false)) {
windows::WinError winError("GetQueuedCompletionStatusEx");
if (winError.lastError != WAIT_TIMEOUT)
throw winError;
auto lastError = GetLastError();
if (lastError != WAIT_TIMEOUT)
throw WinError(lastError, "GetQueuedCompletionStatusEx");
assert(removed == 0);
} else {
assert(0 < removed && removed <= sizeof(oentries) / sizeof(*oentries));
@@ -52,12 +54,12 @@ void MuxablePipePollState::iterate(
// here is possible (but not obligatory) to call
// `handleRead` and repeat ReadFile immediately
} else {
windows::WinError winError("ReadFile(%s, ..)", (*p)->readSide.get());
if (winError.lastError == ERROR_BROKEN_PIPE) {
auto lastError = GetLastError();
if (lastError == ERROR_BROKEN_PIPE) {
handleEOF((*p)->readSide.get());
nextp = channels.erase(p); // no need to maintain `channels` ?
} else if (winError.lastError != ERROR_IO_PENDING)
throw winError;
} else if (lastError != ERROR_IO_PENDING)
throw WinError(lastError, "ReadFile(%s, ..)", (*p)->readSide.get());
}
}
break;

View File

@@ -6,9 +6,9 @@
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
namespace nix::windows {
namespace nix {
std::string WinError::renderError(DWORD lastError)
std::string SystemError::renderWindowsError(DWORD lastError)
{
LPSTR errorText = NULL;
@@ -32,5 +32,5 @@ std::string WinError::renderError(DWORD lastError)
return fmt("CODE=%d", lastError);
}
} // namespace nix::windows
} // namespace nix
#endif

View File

@@ -17,7 +17,7 @@ void showManPage(const std::string & name)
setEnv("MANPATH", (getNixManDir().string() + ":").c_str());
execlp("man", "man", name.c_str(), nullptr);
if (errno == ENOENT) {
// Not SysError because we don't want to suffix the errno, aka No such file or directory.
// Not SystemError because we don't want to suffix the errno, aka No such file or directory.
throw Error(
"The '%1%' command was not found, but it is needed for '%2%' and some other '%3%' commands' help text. Perhaps you could install the '%1%' command?",
"man",