Compare commits

..

1 Commits

Author SHA1 Message Date
Sergei Zimmerman
2ded675e56 treewide: Make exceptions cloneable
This is needed to make it possible to store exceptions in failed values
with each new rethrow getting a fresh copy of the exception object.
2026-02-18 19:45:49 +03:00
61 changed files with 341 additions and 1166 deletions

View File

@@ -9,3 +9,9 @@ endif
if 'address' in get_option('b_sanitize')
deps_other += declare_dependency(sources : 'asan-options.cc')
endif
if 'undefined' in get_option('b_sanitize')
add_project_arguments('-DNIX_UBSAN_ENABLED=1', language : 'cpp')
else
add_project_arguments('-DNIX_UBSAN_ENABLED=0', language : 'cpp')
endif

View File

@@ -710,7 +710,7 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
try {
cwd = std::filesystem::current_path();
} catch (std::filesystem::filesystem_error & e) {
throw SystemError(e.code(), "cannot determine current working directory");
throw SysError("cannot determine current working directory");
}
auto flakeRef = parseFlakeRef(fetchSettings, flakeRefS, cwd.string(), true);

View File

@@ -11,7 +11,7 @@
namespace nix::eval_cache {
CachedEvalError::CachedEvalError(ref<AttrCursor> cursor, Symbol attr)
: EvalError(cursor->root->state, "cached failure of attribute '%s'", cursor->getAttrPathStr(attr))
: CloneableError(cursor->root->state, "cached failure of attribute '%s'", cursor->getAttrPathStr(attr))
, cursor(cursor)
, attr(attr)
{

View File

@@ -14,7 +14,7 @@ namespace nix::eval_cache {
struct AttrDb;
class AttrCursor;
struct CachedEvalError : EvalError
struct CachedEvalError : CloneableError<CachedEvalError, EvalError>
{
const ref<AttrCursor> cursor;
const Symbol attr;

View File

@@ -18,7 +18,7 @@ class EvalErrorBuilder;
*
* Most subclasses should inherit from `EvalError` instead of this class.
*/
class EvalBaseError : public Error
class EvalBaseError : public CloneableError<EvalBaseError, Error>
{
template<class T>
friend class EvalErrorBuilder;
@@ -26,14 +26,14 @@ public:
EvalState & state;
EvalBaseError(EvalState & state, ErrorInfo && errorInfo)
: Error(errorInfo)
: CloneableError(errorInfo)
, state(state)
{
}
template<typename... Args>
explicit EvalBaseError(EvalState & state, const std::string & formatString, const Args &... formatArgs)
: Error(formatString, formatArgs...)
: CloneableError(formatString, formatArgs...)
, state(state)
{
}
@@ -60,23 +60,23 @@ MakeError(InfiniteRecursionError, EvalError);
* Inherits from EvalBaseError (not EvalError) because resource exhaustion
* should not be cached.
*/
struct StackOverflowError : public EvalBaseError
struct StackOverflowError : public CloneableError<StackOverflowError, EvalBaseError>
{
StackOverflowError(EvalState & state)
: EvalBaseError(state, "stack overflow; max-call-depth exceeded")
: CloneableError(state, "stack overflow; max-call-depth exceeded")
{
}
};
MakeError(IFDError, EvalBaseError);
struct InvalidPathError : public EvalError
struct InvalidPathError : public CloneableError<InvalidPathError, EvalError>
{
public:
Path path;
InvalidPathError(EvalState & state, const Path & path)
: EvalError(state, "path '%s' is not valid", path)
: CloneableError(state, "path '%s' is not valid", path)
{
}
};

View File

@@ -9,14 +9,14 @@
namespace nix {
class BadNixStringContextElem : public Error
class BadNixStringContextElem final : public CloneableError<BadNixStringContextElem, Error>
{
public:
std::string_view raw;
template<typename... Args>
BadNixStringContextElem(std::string_view raw_, const Args &... args)
: Error("")
: CloneableError("")
{
raw = raw_;
auto hf = HintFmt(args...);

View File

@@ -1312,11 +1312,12 @@ static void prim_warn(EvalState & state, const PosIdx pos, Value ** args, Value
state.forceString(*args[0], pos, "while evaluating the first argument; the message passed to builtins.warn");
{
BaseError msg(std::string{msgStr});
msg.atPos(state.positions[pos]);
auto info = msg.info();
info.level = lvlWarn;
info.isFromExpr = true;
ErrorInfo info{
.level = lvlWarn,
.msg = HintFmt(std::string(msgStr)),
.pos = state.positions[pos],
.isFromExpr = true,
};
logWarning(info);
}

View File

@@ -74,11 +74,11 @@ namespace nix {
struct GitSourceAccessor;
struct GitError : public Error
struct GitError final : public CloneableError<GitError, Error>
{
template<typename... Ts>
GitError(const git_error & error, Ts &&... args)
: Error("")
: CloneableError("")
{
auto hf = HintFmt(std::forward<Ts>(args)...);
err.msg = HintFmt("%1%: %2% (libgit2 error code = %3%)", Uncolored(hf.str()), error.message, error.klass);
@@ -247,8 +247,7 @@ static void initRepoAtomically(std::filesystem::path & path, GitRepo::Options op
|| e.code() == std::errc::directory_not_empty) {
return;
} else
throw SystemError(
e.code(), "moving temporary git repository from %s to %s", PathFmt(tmpDir), PathFmt(path));
throw SysError("moving temporary git repository from %s to %s", PathFmt(tmpDir), PathFmt(path));
}
// we successfully moved the repository, so the temporary directory no longer exists.
delTmpDir.cancel();

View File

@@ -28,7 +28,7 @@
namespace nix {
AwsAuthError::AwsAuthError(int errorCode)
: Error("AWS authentication error: '%s' (%d)", aws_error_str(errorCode), errorCode)
: CloneableError("AWS authentication error: '%s' (%d)", aws_error_str(errorCode), errorCode)
, errorCode(errorCode)
{
}

View File

@@ -1,11 +1,11 @@
#include "nix/store/build/goal.hh"
#include "nix/store/build/worker.hh"
#include "nix/store/globals.hh"
#include "nix/store/worker-settings.hh"
namespace nix {
TimedOut::TimedOut(time_t maxDuration)
: BuildError(BuildResult::Failure::TimedOut, "timed out after %1% seconds", maxDuration)
: CloneableError(BuildResult::Failure::TimedOut, "timed out after %1% seconds", maxDuration)
, maxDuration(maxDuration)
{
}

View File

@@ -36,8 +36,8 @@ static void builtinUnpackChannel(const BuiltinBuilderContext & ctx)
auto target = out / channelName;
try {
std::filesystem::rename(fileName, target);
} catch (std::filesystem::filesystem_error & e) {
throw SystemError(e.code(), "failed to rename %1% to %2%", fileName, target.string());
} catch (std::filesystem::filesystem_error &) {
throw SysError("failed to rename %1% to %2%", fileName, target.string());
}
}

View File

@@ -60,12 +60,12 @@ namespace {
using curlSList = std::unique_ptr<::curl_slist, decltype([](::curl_slist * list) { ::curl_slist_free_all(list); })>;
using curlMulti = std::unique_ptr<::CURLM, decltype([](::CURLM * multi) { ::curl_multi_cleanup(multi); })>;
struct curlMultiError : Error
struct curlMultiError final : CloneableError<curlMultiError, Error>
{
::CURLMcode code;
curlMultiError(::CURLMcode code)
: Error{"unexpected curl multi error: %s", ::curl_multi_strerror(code)}
: CloneableError{"unexpected curl multi error: %s", ::curl_multi_strerror(code)}
{
assert(code != CURLM_OK);
}
@@ -1212,7 +1212,7 @@ void FileTransfer::download(
template<typename... Args>
FileTransferError::FileTransferError(
FileTransfer::Error error, std::optional<std::string> response, const Args &... args)
: Error(args...)
: CloneableError(args...)
, error(error)
, response(response)
{

View File

@@ -272,7 +272,7 @@ void LocalStore::findRoots(const Path & path, std::filesystem::file_type type, R
|| e.code() == std::errc::not_a_directory)
printInfo("cannot read potential root '%1%'", path);
else
throw SystemError(e.code(), "finding GC roots in '%1%'", path);
throw;
}
catch (SystemError & e) {

View File

@@ -34,12 +34,13 @@ struct AwsCredentials
}
};
class AwsAuthError : public Error
class AwsAuthError final : public CloneableError<AwsAuthError, Error>
{
std::optional<int> errorCode;
public:
using Error::Error;
using CloneableError::CloneableError;
AwsAuthError(int errorCode);
std::optional<int> getErrorCode() const

View File

@@ -58,7 +58,7 @@ enum struct BuildResultFailureStatus : uint8_t {
* This is both an exception type (inherits from Error) and serves as
* the failure variant in BuildResult::inner.
*/
struct BuildError : public Error
struct BuildError : public CloneableError<BuildError, Error>
{
using Status = BuildResultFailureStatus;
using enum Status;
@@ -80,7 +80,7 @@ public:
*/
template<typename... Args>
BuildError(Status status, const Args &... args)
: Error(args...)
: CloneableError(args...)
, status{status}
{
}
@@ -97,7 +97,7 @@ public:
* Also used for deserialization.
*/
BuildError(Args args)
: Error(std::move(args.msg))
: CloneableError(std::move(args.msg))
, status{args.status}
, isNonDeterministic{args.isNonDeterministic}
@@ -108,7 +108,7 @@ public:
* Default constructor for deserialization.
*/
BuildError()
: Error("")
: CloneableError("")
{
}

View File

@@ -20,14 +20,14 @@ namespace nix {
* Denotes a build failure that stemmed from the builder exiting with a
* failing exist status.
*/
struct BuilderFailureError : BuildError
struct BuilderFailureError final : CloneableError<BuilderFailureError, BuildError>
{
int builderStatus;
std::string extraMsgAfter;
BuilderFailureError(BuildResult::Failure::Status status, int builderStatus, std::string extraMsgAfter)
: BuildError{
: CloneableError{
status,
/* No message for now, because the caller will make for
us, with extra context */

View File

@@ -10,7 +10,7 @@
namespace nix {
struct TimedOut : BuildError
struct TimedOut final : CloneableError<TimedOut, BuildError>
{
time_t maxDuration;

View File

@@ -22,7 +22,7 @@ struct Package
}
};
class BuildEnvFileConflictError : public Error
class BuildEnvFileConflictError final : public CloneableError<BuildEnvFileConflictError, Error>
{
public:
const Path fileA;
@@ -30,7 +30,7 @@ public:
int priority;
BuildEnvFileConflictError(const Path fileA, const Path fileB, int priority)
: Error(
: CloneableError(
"Unable to build profile. There is a conflict for the following files:\n"
"\n"
" %1%\n"

View File

@@ -403,7 +403,7 @@ ref<FileTransfer> getFileTransfer();
*/
ref<FileTransfer> makeFileTransfer(const FileTransferSettings & settings = fileTransferSettings);
class FileTransferError : public Error
class FileTransferError final : public CloneableError<FileTransferError, Error>
{
public:
FileTransfer::Error error;

View File

@@ -146,7 +146,7 @@ struct RealisedPath
auto operator<=>(const RealisedPath &) const = default;
};
class MissingRealisation : public Error
class MissingRealisation final : public CloneableError<MissingRealisation, Error>
{
public:
MissingRealisation(DrvOutput & outputId)
@@ -155,7 +155,7 @@ public:
}
MissingRealisation(std::string_view drv, OutputName outputName)
: Error(
: CloneableError(
"cannot operate on output '%s' of the "
"unbuilt derivation '%s'",
outputName,

View File

@@ -166,7 +166,7 @@ struct SQLiteTxn
~SQLiteTxn();
};
struct SQLiteError : Error
struct SQLiteError : CloneableError<SQLiteError, Error>
{
std::string path;
std::string errMsg;

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

@@ -35,7 +35,7 @@ static void readProcLink(const std::filesystem::path & file, UncheckedRoots & ro
if (e.code() == std::errc::no_such_file_or_directory || e.code() == std::errc::permission_denied
|| e.code() == std::errc::no_such_process)
return;
throw SystemError(e.code(), "reading symlink '%s'", PathFmt(file));
throw;
}
if (buf.is_absolute())
roots[buf.string()].emplace(file.string());

View File

@@ -111,8 +111,8 @@ static std::vector<std::string> expandBuilderLines(const std::string & builders)
std::string text;
try {
text = readFile(path);
} catch (const SystemError & e) {
if (!e.is(std::errc::no_such_file_or_directory))
} catch (const SysError & e) {
if (e.errNo != ENOENT)
throw;
debug("cannot find machines file '%s'", path);
continue;

View File

@@ -202,13 +202,14 @@ void LocalStore::optimisePath_(
full. When that happens, it's fine to ignore it: we
just effectively disable deduplication of this
file.
TODO: Get rid of errno, use error code.
*/
printInfo("cannot link %s to '%s': %s", PathFmt(linkPath), path, e.code().message());
printInfo("cannot link %s to '%s': %s", PathFmt(linkPath), path, strerror(errno));
return;
}
else
throw SystemError(e.code(), "creating hard link from %1% to %2%", PathFmt(linkPath), path);
throw;
}
}
@@ -249,7 +250,7 @@ void LocalStore::optimisePath_(
printInfo("%1% has maximum number of links", PathFmt(linkPath));
return;
}
throw SystemError(e.code(), "creating hard link from %1% to %2%", PathFmt(linkPath), PathFmt(tempLink));
throw;
}
/* Atomically replace the old file with the new hard link. */
@@ -270,7 +271,7 @@ void LocalStore::optimisePath_(
debug("%s has reached maximum number of links", PathFmt(linkPath));
return;
}
throw SystemError(e.code(), "renaming %1% to %2%", PathFmt(tempLink), path);
throw;
}
stats.filesLinked++;

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

@@ -103,7 +103,7 @@ static void removeFile(const std::filesystem::path & path)
try {
std::filesystem::remove(path);
} catch (std::filesystem::filesystem_error & e) {
throw SystemError(e.code(), "removing file %1%", PathFmt(path));
throw SysError("removing file %1%", PathFmt(path));
}
}
@@ -324,6 +324,8 @@ std::filesystem::path getDefaultProfile(ProfileDirsOptions settings)
return absPath(readLink(profileLink), &linkDir);
} catch (Error &) {
return profileLink;
} catch (std::filesystem::filesystem_error &) {
return profileLink;
}
}

View File

@@ -17,7 +17,7 @@ namespace nix {
SQLiteError::SQLiteError(
const char * path, const char * errMsg, int errNo, int extendedErrNo, int offset, HintFmt && hf)
: Error("")
: CloneableError("")
, path(path)
, errMsg(errMsg)
, errNo(errNo)

View File

@@ -18,11 +18,11 @@ static std::string parsePublicHostKey(std::string_view host, std::string_view ss
}
}
class InvalidSSHAuthority : public Error
class InvalidSSHAuthority final : public CloneableError<InvalidSSHAuthority, Error>
{
public:
InvalidSSHAuthority(const ParsedURL::Authority & authority, std::string_view reason)
: Error("invalid SSH authority: '%s': %s", authority.to_string(), reason)
: CloneableError("invalid SSH authority: '%s': %s", authority.to_string(), reason)
{
}
};

View File

@@ -55,10 +55,10 @@
namespace nix {
struct NotDeterministic : BuildError
struct NotDeterministic final : CloneableError<NotDeterministic, BuildError>
{
NotDeterministic(auto &&... args)
: BuildError(BuildResult::Failure::NotDeterministic, args...)
: CloneableError(BuildResult::Failure::NotDeterministic, args...)
{
isNonDeterministic = true;
}
@@ -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

@@ -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

@@ -319,7 +319,7 @@ TEST(chmodIfNeeded, works)
TEST(chmodIfNeeded, nonexistent)
{
ASSERT_THROW(chmodIfNeeded("/schnitzel/darmstadt/pommes", 0755), SystemError);
ASSERT_THROW(chmodIfNeeded("/schnitzel/darmstadt/pommes", 0755), SysError);
}
/* ----------------------------------------------------------------------------
@@ -340,7 +340,7 @@ TEST(DirectoryIterator, works)
TEST(DirectoryIterator, nonexistent)
{
ASSERT_THROW(DirectoryIterator("/schnitzel/darmstadt/pommes"), SystemError);
ASSERT_THROW(DirectoryIterator("/schnitzel/darmstadt/pommes"), SysError);
}
/* ----------------------------------------------------------------------------

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

@@ -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

@@ -378,7 +378,7 @@ std::set<ExperimentalFeature> parseFeatures(const StringSet & rawFeatures)
}
MissingExperimentalFeature::MissingExperimentalFeature(ExperimentalFeature feature, std::string reason)
: Error(
: CloneableError(
"experimental Nix feature '%1%' is disabled%2%; add '--extra-experimental-features %1%' to enable it",
showExperimentalFeature(feature),
Uncolored(optionalBracket(" (", reason, ")")))

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

@@ -40,9 +40,9 @@ DirectoryIterator::DirectoryIterator(const std::filesystem::path & p)
// **Attempt to create the underlying directory_iterator**
it_ = std::filesystem::directory_iterator(p);
} catch (const std::filesystem::filesystem_error & e) {
// **Catch filesystem_error and throw SystemError**
// Adapt the error message as needed for SystemError
throw SystemError(e.code(), "cannot read directory %s", PathFmt(p));
// **Catch filesystem_error and throw SysError**
// Adapt the error message as needed for SysError
throw SysError("cannot read directory %s", PathFmt(p));
}
}
@@ -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();
@@ -251,11 +280,7 @@ bool pathAccessible(const std::filesystem::path & path)
std::filesystem::path readLink(const std::filesystem::path & path)
{
checkInterrupt();
try {
return std::filesystem::read_symlink(path);
} catch (std::filesystem::filesystem_error & e) {
throw SystemError(e.code(), "reading symbolic link '%s'", PathFmt(path));
}
return std::filesystem::read_symlink(path);
}
Path readLink(const Path & path)
@@ -311,23 +336,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;
}
}
@@ -438,7 +463,7 @@ void createDirs(const std::filesystem::path & path)
try {
std::filesystem::create_directories(path);
} catch (std::filesystem::filesystem_error & e) {
throw SystemError(e.code(), "creating directory '%1%'", path.string());
throw SysError("creating directory '%1%'", path.string());
}
}
@@ -639,7 +664,7 @@ void replaceSymlink(const std::filesystem::path & target, const std::filesystem:
} catch (std::filesystem::filesystem_error & e) {
if (e.code() == std::errc::file_exists)
continue;
throw SystemError(e.code(), "creating symlink %1% -> %2%", PathFmt(tmp), PathFmt(target));
throw SysError("creating symlink %1% -> %2%", PathFmt(tmp), PathFmt(target));
}
try {
@@ -647,7 +672,7 @@ void replaceSymlink(const std::filesystem::path & target, const std::filesystem:
} catch (std::filesystem::filesystem_error & e) {
if (e.code() == std::errc::file_exists)
continue;
throw SystemError(e.code(), "renaming %1% to %2%", PathFmt(tmp), PathFmt(link));
throw SysError("renaming %1% to %2%", PathFmt(tmp), PathFmt(link));
}
break;
@@ -748,7 +773,7 @@ std::filesystem::path makeParentCanonical(const std::filesystem::path & rawPath)
}
return std::filesystem::canonical(parent) / path.filename();
} catch (std::filesystem::filesystem_error & e) {
throw SystemError(e.code(), "canonicalising parent path of %1%", PathFmt(path));
throw SysError("canonicalising parent path of %1%", PathFmt(path));
}
}

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

@@ -17,7 +17,6 @@
#include "nix/util/suggestions.hh"
#include "nix/util/fmt.hh"
#include "nix/util/config.hh"
#include <cstring>
#include <list>
@@ -127,20 +126,20 @@ public:
BaseError & operator=(BaseError &&) = default;
template<typename... Args>
BaseError(unsigned int status, Args &&... args)
: err{.level = lvlError, .msg = HintFmt(std::forward<Args>(args)...), .pos = {}, .status = status}
BaseError(unsigned int status, const Args &... args)
: err{.level = lvlError, .msg = HintFmt(args...), .pos = {}, .status = status}
{
}
template<typename... Args>
explicit BaseError(const std::string & fs, Args &&... args)
: err{.level = lvlError, .msg = HintFmt(fs, std::forward<Args>(args)...), .pos = {}}
explicit BaseError(const std::string & fs, const Args &... args)
: err{.level = lvlError, .msg = HintFmt(fs, args...), .pos = {}}
{
}
template<typename... Args>
BaseError(const Suggestions & sug, Args &&... args)
: err{.level = lvlError, .msg = HintFmt(std::forward<Args>(args)...), .pos = {}, .suggestions = sug}
BaseError(const Suggestions & sug, const Args &... args)
: err{.level = lvlError, .msg = HintFmt(args...), .pos = {}, .suggestions = sug}
{
}
@@ -204,9 +203,9 @@ public:
* @param args... Format string arguments.
*/
template<typename... Args>
void addTrace(std::shared_ptr<const Pos> && pos, std::string_view fs, Args &&... args)
void addTrace(std::shared_ptr<const Pos> && pos, std::string_view fs, const Args &... args)
{
addTrace(std::move(pos), HintFmt(std::string(fs), std::forward<Args>(args)...));
addTrace(std::move(pos), HintFmt(std::string(fs), args...));
}
/**
@@ -227,13 +226,31 @@ public:
{
return err;
};
[[noreturn]] virtual void throwClone() const = 0;
};
#define MakeError(newClass, superClass) \
class newClass : public superClass \
{ \
public: \
using superClass::superClass; \
template<typename Derived, typename Base>
class CloneableError : public Base
{
public:
using Base::Base;
/**
* Rethrow a copy of this exception. Useful when the exception can get
* modified when appending traces.
*/
[[noreturn]] void throwClone() const override
{
throw Derived(static_cast<const Derived &>(*this));
}
};
#define MakeError(newClass, superClass) \
class newClass : public CloneableError<newClass, superClass> \
{ \
public: \
using CloneableError<newClass, superClass>::CloneableError; \
}
MakeError(Error, BaseError);
@@ -245,42 +262,22 @@ MakeError(UnimplementedError, Error);
* std::error_code. Use when you want to catch and check an error condition like
* no_such_file_or_directory (ENOENT) without ifdefs.
*/
class SystemError : public Error
class SystemError : public CloneableError<SystemError, Error>
{
std::error_code errorCode;
std::string errorDetails;
protected:
/**
* Just here to allow derived classes to use the right constructor
* (the protected one).
*/
struct Disambig
{};
/**
* Protected constructor for subclasses that provide their own error message.
* The error message is appended to the formatted hint.
*/
template<typename... Args>
SystemError(Disambig, std::error_code errorCode, std::string_view errorDetails, Args &&... args)
: Error("")
, errorCode(errorCode)
, errorDetails(errorDetails)
{
auto hf = HintFmt(std::forward<Args>(args)...);
err.msg = HintFmt("%s: %s", Uncolored(hf.str()), errorDetails);
}
public:
/**
* Construct with an error code. The error code's message is automatically
* appended to the error message.
*/
template<typename... Args>
SystemError(std::errc posixErrNo, Args &&... args)
: CloneableError(std::forward<Args>(args)...)
, errorCode(std::make_error_code(posixErrNo))
{
}
template<typename... Args>
SystemError(std::error_code errorCode, Args &&... args)
: SystemError(Disambig{}, errorCode, errorCode.message(), std::forward<Args>(args)...)
: CloneableError(std::forward<Args>(args)...)
, errorCode(errorCode)
{
}
@@ -311,7 +308,7 @@ public:
* support is too WIP to justify the code churn, but if it is finished
* then a better identifier becomes moe worth it.
*/
class SysError : public SystemError
class SysError final : public CloneableError<SysError, SystemError>
{
public:
int errNo;
@@ -321,14 +318,12 @@ public:
* 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)...)
SysError(int errNo, const Args &... args)
: CloneableError(static_cast<std::errc>(errNo), "")
, errNo(errNo)
{
auto hf = HintFmt(args...);
err.msg = HintFmt("%1%: %2%", Uncolored(hf.str()), strerror(errNo));
}
/**
@@ -338,8 +333,8 @@ public:
* calling this constructor!
*/
template<typename... Args>
SysError(Args &&... args)
: SysError(errno, std::forward<Args>(args)...)
SysError(const Args &... args)
: SysError(errno, args...)
{
}
};
@@ -373,7 +368,7 @@ int handleExceptions(const std::string & programName, std::function<void()> fun)
*/
[[gnu::noinline, gnu::cold, noreturn]] void unreachable(std::source_location loc = std::source_location::current());
#if NIX_UBSAN_ENABLED
#if NIX_UBSAN_ENABLED == 1
/* When building with sanitizers, also enable expensive unreachable checks. In
optimised builds this explicitly invokes UB with std::unreachable for better
optimisations. */
@@ -392,7 +387,7 @@ namespace windows {
* Unless you need to catch a specific error number, don't catch this in
* portable code. Catch `SystemError` instead.
*/
class WinError : public SystemError
class WinError : public CloneableError<WinError, SystemError>
{
public:
DWORD lastError;
@@ -403,14 +398,12 @@ public:
* 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)...)
WinError(DWORD lastError, const Args &... args)
: CloneableError(std::error_code(lastError, std::system_category()), "")
, lastError(lastError)
{
auto hf = HintFmt(args...);
err.msg = HintFmt("%1%: %2%", Uncolored(hf.str()), renderError(lastError));
}
/**
@@ -420,14 +413,14 @@ public:
* before calling this constructor!
*/
template<typename... Args>
WinError(Args &&... args)
: WinError(GetLastError(), std::forward<Args>(args)...)
WinError(const Args &... args)
: WinError(GetLastError(), args...)
{
}
private:
static std::string renderError(DWORD lastError);
std::string renderError(DWORD lastError);
};
} // namespace windows

View File

@@ -80,7 +80,7 @@ std::set<ExperimentalFeature> parseFeatures(const StringSet &);
* An experimental feature was required for some (experimental)
* operation, but was not enabled.
*/
class MissingExperimentalFeature : public Error
class MissingExperimentalFeature final : public CloneableError<MissingExperimentalFeature, Error>
{
public:
/**

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.
@@ -160,7 +75,7 @@ namespace linux {
*
* @see https://man7.org/linux/man-pages/man2/openat2.2.html
* @see https://man7.org/linux/man-pages/man2/open_how.2type.html
*
v*
* @param flags O_* flags
* @param mode Mode for O_{CREAT,TMPFILE}
* @param resolve RESOLVE_* flags

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
@@ -226,26 +189,26 @@ Path readLink(const Path & path);
*/
std::filesystem::path readLink(const std::filesystem::path & path);
#ifdef _WIN32
namespace windows {
/**
* Get the path associated with a file descriptor.
* Get the path associated with a file handle.
*
* @note One MUST only use this for error handling, because it creates
* TOCTOU issues. We don't mind if error messages point to out of date
* paths (that is a rather trivial TOCTOU --- the error message is best
* effort) but for anything else we do.
*
* @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.
*/
std::filesystem::path descriptorToPath(Descriptor fd);
std::filesystem::path handleToPath(Descriptor handle);
} // namespace windows
#endif
/**
* 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 +268,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

@@ -2,12 +2,7 @@
include_dirs = [ include_directories('../..') ]
config_pub_h = configure_file(
configuration : configdata_pub,
output : 'config.hh',
)
headers = [ config_pub_h ] + files(
headers = files(
'abstract-setting-to-json.hh',
'alignment.hh',
'ansicolor.hh',
@@ -32,7 +27,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

@@ -133,14 +133,14 @@ std::pair<int, std::string> runProgram(RunOptions && options);
void runProgram2(const RunOptions & options);
class ExecError : public Error
class ExecError final : public CloneableError<ExecError, Error>
{
public:
int status;
template<typename... Args>
ExecError(int status, const Args &... args)
: Error(args...)
: CloneableError(args...)
, status(status)
{
}

View File

@@ -231,19 +231,19 @@ ref<SourceAccessor> makeEmptySourceAccessor();
*/
MakeError(RestrictedPathError, Error);
struct SymlinkNotAllowed : public Error
struct SymlinkNotAllowed final : public CloneableError<SymlinkNotAllowed, Error>
{
CanonPath path;
SymlinkNotAllowed(CanonPath path)
: Error("relative path '%s' points to a symlink, which is not allowed", path.rel())
: CloneableError("relative path '%s' points to a symlink, which is not allowed", path.rel())
, path(std::move(path))
{
}
template<typename... Args>
SymlinkNotAllowed(CanonPath path, const std::string & fs, Args &&... args)
: Error(fs, std::forward<Args>(args)...)
: CloneableError(fs, std::forward<Args>(args)...)
, path(std::move(path))
{
}

View File

@@ -16,8 +16,7 @@ cxx = meson.get_compiler('cpp')
subdir('nix-meson-build-support/deps-lists')
configdata_pub = configuration_data()
configdata_priv = configuration_data()
configdata = configuration_data()
deps_private_maybe_subproject = []
deps_public_maybe_subproject = []
@@ -35,15 +34,9 @@ check_funcs = [
foreach funcspec : check_funcs
define_name = 'HAVE_' + funcspec[0].underscorify().to_upper()
define_value = cxx.has_function(funcspec[0]).to_int()
configdata_priv.set(define_name, define_value, description : funcspec[1])
configdata.set(define_name, define_value, description : funcspec[1])
endforeach
configdata_pub.set(
'NIX_UBSAN_ENABLED',
('undefined' in get_option('b_sanitize')).to_int(),
description : 'Whether nix has been built with UBSan enabled',
)
subdir('nix-meson-build-support/libatomic')
if host_machine.system() == 'windows'
@@ -111,7 +104,7 @@ cpuid = dependency(
version : '>= 0.7.0',
required : cpuid_required,
)
configdata_priv.set('HAVE_LIBCPUID', cpuid.found().to_int())
configdata.set('HAVE_LIBCPUID', cpuid.found().to_int())
deps_private += cpuid
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
@@ -120,7 +113,7 @@ deps_public += nlohmann_json
cxx = meson.get_compiler('cpp')
config_priv_h = configure_file(
configuration : configdata_priv,
configuration : configdata,
output : 'util-config-private.hh',
)
@@ -139,7 +132,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 +140,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)
@@ -156,7 +170,7 @@ SourceAccessor::DirEntries PosixSourceAccessor::readDirectory(const CanonPath &
if (e.code() == std::errc::permission_denied || e.code() == std::errc::operation_not_permitted)
return std::nullopt;
else
throw SystemError(e.code(), "getting status of '%s'", PathFmt(entry.path()));
throw;
}
}();
res.emplace(entry.path().filename().string(), type);
@@ -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

@@ -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"
@@ -50,8 +49,7 @@ void readFull(int fd, char * buf, size_t count)
pollFD(fd, POLLIN);
continue;
}
auto savedErrno = errno;
throw SysError(savedErrno, "reading from file %s", PathFmt(descriptorToPath(fd)));
throw SysError("reading from file");
}
if (res == 0)
throw EndOfFile("unexpected end-of-file");
@@ -74,8 +72,7 @@ void writeFull(int fd, std::string_view s, bool allowInterrupts)
pollFD(fd, POLLOUT);
continue;
}
auto savedErrno = errno;
throw SysError(savedErrno, "writing to file %s", PathFmt(descriptorToPath(fd)));
throw SysError("writing to file");
}
if (res > 0)
s.remove_prefix(res);
@@ -98,10 +95,8 @@ std::string readLine(int fd, bool eofOk, char terminator)
pollFD(fd, POLLIN);
continue;
}
default: {
auto savedErrno = errno;
throw SysError(savedErrno, "reading a line from %s", PathFmt(descriptorToPath(fd)));
}
default:
throw SysError("reading a line");
}
} else if (rd == 0) {
if (eofOk)
@@ -208,17 +203,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__
@@ -73,10 +71,8 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
if (res < 0) {
if (errno == ENOSYS)
fchmodat2Unsupported.test_and_set();
else {
auto savedErrno = errno;
throw SysError(savedErrno, "fchmodat2 %s", PathFmt(descriptorToPath(dirFd) / path.rel()));
}
else
throw SysError("fchmodat2 '%s' relative to parent directory", path.rel());
} else
return;
}
@@ -84,20 +80,18 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
#ifdef __linux__
AutoCloseFD pathFd = ::openat(dirFd, path.rel_c_str(), O_PATH | O_NOFOLLOW | O_CLOEXEC);
if (!pathFd) {
auto savedErrno = errno;
if (!pathFd)
throw SysError(
savedErrno,
"opening %s to get an O_PATH file descriptor (fchmodat2 is unsupported)",
PathFmt(descriptorToPath(dirFd) / path.rel()));
}
"opening '%s' relative to parent directory to get an O_PATH file descriptor (fchmodat2 is unsupported)",
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()));
throw SysError(EOPNOTSUPP, "can't change mode of symlink '%s' relative to parent directory", path.rel());
static std::atomic_flag dontHaveProc{};
if (!dontHaveProc.test()) {
@@ -107,11 +101,8 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
if (int res = ::chmod(selfProcFdPath.c_str(), mode); res == -1) {
if (errno == ENOENT)
dontHaveProc.test_and_set();
else {
auto savedErrno = errno;
throw SysError(
savedErrno, "chmod %s (%s)", selfProcFdPath, PathFmt(descriptorToPath(dirFd) / path.rel()));
}
else
throw SysError("chmod '%s' ('%s' relative to parent directory)", selfProcFdPath, path);
} else
return;
}
@@ -131,10 +122,8 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
#endif
);
if (res == -1) {
auto savedErrno = errno;
throw SysError(savedErrno, "fchmodat %s", PathFmt(descriptorToPath(dirFd) / path.rel()));
}
if (res == -1)
throw SysError("fchmodat '%s' relative to parent directory", path.rel());
}
static Descriptor
@@ -172,7 +161,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) {
@@ -216,69 +206,11 @@ OsString readLinkAt(Descriptor dirFd, const CanonPath & path)
checkInterrupt();
buf.resize(bufSize);
ssize_t rlSize = ::readlinkat(dirFd, path.rel_c_str(), buf.data(), bufSize);
if (rlSize == -1) {
auto savedErrno = errno;
throw SysError(savedErrno, "reading symbolic link %1%", PathFmt(descriptorToPath(dirFd) / path.rel()));
} else if (rlSize < bufSize)
if (rlSize == -1)
throw SysError("reading symbolic link '%1%' relative to parent directory", path.rel());
else if (rlSize < bufSize)
return {buf.data(), static_cast<std::size_t>(rlSize)};
}
}
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)
@@ -46,44 +46,11 @@ Descriptor openNewFileForWrite(const std::filesystem::path & path, mode_t mode,
return open(path.c_str(), flags, mode);
}
std::filesystem::path descriptorToPath(Descriptor fd)
{
if (fd == STDIN_FILENO)
return "<stdin>";
if (fd == STDOUT_FILENO)
return "<stdout>";
if (fd == STDERR_FILENO)
return "<stderr>";
#if defined(__linux__)
try {
return readLink("/proc/self/fd/" + std::to_string(fd));
} catch (SystemError &) {
}
#elif HAVE_F_GETPATH
/* F_GETPATH requires PATH_MAX buffer per POSIX */
char buf[PATH_MAX];
if (fcntl(fd, F_GETPATH, buf) != -1)
return buf;
#endif
/* Fallback for unknown fd or unsupported platform */
return "<fd " + std::to_string(fd) + ">";
}
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)
{

View File

@@ -8,12 +8,6 @@ configdata_unix.set(
description : 'Optionally used for changing the files and symlinks.',
)
configdata_unix.set(
'HAVE_F_GETPATH',
cxx.has_header_symbol('fcntl.h', 'F_GETPATH').to_int(),
description : 'Optionally used for getting the path of a file descriptor (macOS).',
)
# Check for each of these functions, and create a define like `#define
# HAVE_CLOSE_RANGE 1`.
check_funcs_unix = [

View File

@@ -45,9 +45,9 @@ void writeFull(HANDLE handle, std::string_view s, bool allowInterrupts)
checkInterrupt();
DWORD res;
if (!WriteFile(handle, s.data(), s.size(), &res, NULL)) {
// Do this because `descriptorToPath` will overwrite the last error.
// Do this because `handleToPath` will overwrite the last error.
auto lastError = GetLastError();
auto path = descriptorToPath(handle);
auto path = handleToPath(handle);
throw WinError(lastError, "writing to file %d:%s", handle, PathFmt(path));
}
if (res > 0)
@@ -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>
@@ -89,8 +88,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);
}
/**
@@ -157,13 +156,13 @@ OsString readSymlinkTarget(HANDLE linkHandle)
size_t path_buf_offset = offsetof(ReparseDataBuffer, SymbolicLinkReparseBuffer.PathBuffer[0]);
if (out < path_buf_offset) {
auto fullPath = descriptorToPath(linkHandle);
auto fullPath = handleToPath(linkHandle);
throw WinError(
DWORD{ERROR_REPARSE_TAG_INVALID}, "invalid reparse data for %d:%s", linkHandle, PathFmt(fullPath));
}
if (reparse->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
auto fullPath = descriptorToPath(linkHandle);
auto fullPath = handleToPath(linkHandle);
throw WinError(DWORD{ERROR_REPARSE_TAG_INVALID}, "not a symlink: %d:%s", linkHandle, PathFmt(fullPath));
}
@@ -180,7 +179,7 @@ OsString readSymlinkTarget(HANDLE linkHandle)
}
if (path_buf_offset + name_offset + name_length > out) {
auto fullPath = descriptorToPath(linkHandle);
auto fullPath = handleToPath(linkHandle);
throw WinError(
DWORD{ERROR_REPARSE_TAG_INVALID}, "invalid symlink data for %d:%s", linkHandle, PathFmt(fullPath));
}
@@ -192,54 +191,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,115 +210,6 @@ 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)
{
@@ -459,4 +301,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);
}
@@ -83,7 +83,7 @@ void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed)
deletePath(path);
}
std::filesystem::path descriptorToPath(Descriptor handle)
std::filesystem::path windows::handleToPath(HANDLE handle)
{
std::vector<wchar_t> buf(0x100);
DWORD dw = GetFinalPathNameByHandleW(handle, buf.data(), buf.size(), FILE_NAME_OPENED);
@@ -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

@@ -43,8 +43,8 @@ void removeOldGenerations(std::filesystem::path dir)
std::string link;
try {
link = readLink(path);
} catch (SystemError & e) {
if (e.is(std::errc::no_such_file_or_directory))
} catch (std::filesystem::filesystem_error & e) {
if (e.code() == std::errc::no_such_file_or_directory)
continue;
throw;
}

View File

@@ -1419,6 +1419,7 @@ static int main_nix_env(int argc, char ** argv)
replaceSymlink(defaultChannelsDir(profilesDirOpts), nixExprPath / "channels");
if (!isRootUser())
replaceSymlink(rootChannelsDir(profilesDirOpts), nixExprPath / "channels_root");
} catch (std::filesystem::filesystem_error &) {
} catch (Error &) {
}
}