Compare commits

...

8 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
John Ericson
bbcf2041e1 File system error improvements
- Make `descriptorToPath` cross-platform (renamed from
  `windows::handleToPath`). Uses `/proc/self/fd` on Linux and
  `F_GETPATH` on macOS. Add `HAVE_F_GETPATH` meson check.

  This is based on 7226a116a0, which was
  removed in 479c356510, but is now
  introduced more judiciously.

- Unix error messages in `readFull`, `writeFull`, `readLine` now include
  file paths via `descriptorToPath`.

- Convert `std::filesystem::filesystem_error` to `SystemError`

  Wrappers like `readLink`, `createDirs`, `DirectoryIterator`, etc. now
  catch `std::filesystem::filesystem_error` and rethrow as `SystemError`
  with the error code preserved. This ensures consistent exception types
  throughout the codebase.

  Call sites that previously caught `filesystem_error` and rethrew with
  `throw;` now throw `SystemError(e.code(), ...)` instead.

  Some call sites can stop catching `filesystem_error` at all,
  because they only call the wrapped functions.

- Rework `SystemError` constructors to auto-append error message

  The public `SystemError(std::error_code, ...)` constructor now
  automatically appends `errorCode.message()` to the error message.
  A protected constructor takes an explicit error message string for
  subclasses.

  `SysError` delegates to the protected constructor with `strerror(errNo)`.
  `WinError` delegates with `renderError(lastError)` (now static).

  This removes the need to manually append `e.code().message()` at call
  sites when converting `filesystem_error` to `SystemError`.

- Use perfect forwarding (`Args &&...` with `std::forward`) consistently
  in `BaseError`, `SystemError`, `SysError`, and `WinError` constructors.

Co-authored-by: Sergei Zimmerman <sergei@zimmerman.foo>
2026-02-18 12:29:11 -05:00
John Ericson
96bcf5928f Merge pull request #15273 from NixOS/more-robust-ubsan-macro
libutil: More robust check for NIX_UBSAN_ENABLED
2026-02-18 16:15:26 +00:00
Sergei Zimmerman
db853cf4fb libutil: More robust check for NIX_UBSAN_ENABLED
In 3df91bea62 I forgot that the header
might get included out-of-tree with -Wundef. Let's make this a public
config option for libutil as it can affect function bodies in headers.
2026-02-18 17:33:51 +03:00
John Ericson
663db5b48b Merge pull request #15278 from puffnfresh/windows/bar-log-format
Windows: don't use bar log format
2026-02-18 05:14:27 +00:00
Brian McKenna
c486e78235 Windows: don't use bar log format
Relies on terminal features that don't always work on Windows.
2026-02-18 14:35:35 +11:00
John Ericson
4fff871383 Merge pull request #15274 from obsidiansystems/tryToBuild-raii
libstore: refactor `tryToBuild` with coroutine lambdas and RAII
2026-02-17 22:10:42 +00:00
Amaan Qureshi
b9acea908e libstore: refactor tryToBuild with coroutine lambdas and RAII
`tryToBuild` threaded a single `PathLocks outputLocks` by reference
across all build phases and managed a `std::unique_ptr<Activity> actLock`
with explicit `if (!actLock)` guards and `.reset()` calls around the hook
retry loop. This commit introduces coroutine lambdas for the three phases:
`tryHookLoop` owns a `PathLocks` in a scoped block for the first attempt
and per-iteration in the retry loop, `tryBuildLocally` acquires its own
`PathLocks`, and the hook-wait `Activity` is a stack variable scoped to
the postpone block.
2026-02-17 16:23:44 -05:00
39 changed files with 455 additions and 311 deletions

View File

@@ -9,9 +9,3 @@ 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 SysError("cannot determine current working directory");
throw SystemError(e.code(), "cannot determine current working directory");
}
auto flakeRef = parseFlakeRef(fetchSettings, flakeRefS, cwd.string(), true);

View File

@@ -247,7 +247,8 @@ static void initRepoAtomically(std::filesystem::path & path, GitRepo::Options op
|| e.code() == std::errc::directory_not_empty) {
return;
} else
throw SysError("moving temporary git repository from %s to %s", PathFmt(tmpDir), PathFmt(path));
throw SystemError(
e.code(), "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

@@ -362,25 +362,17 @@ Goal::Co DerivationBuildingGoal::tryToBuild(StorePathSet inputPaths)
return LocalBuildCapability{*localStoreP, ext};
}();
/**
* Activity that denotes waiting for a lock.
*/
std::unique_ptr<Activity> actLock;
/**
* Locks on (fixed) output paths.
*/
PathLocks outputLocks;
while (true) {
auto acquireResources = [&](bool & done, PathLocks & outputLocks) -> Goal::Co {
trace("trying to build");
/* Obtain locks on all output paths, if the paths are known a priori.
The locks are automatically released when we exit this function or Nix
crashes. If we can't acquire the lock, then continue; hopefully some
other goal can start a build, and if not, the main loop will sleep a few
seconds and then retry this goal. */
/**
* Output paths to acquire locks on, if known a priori.
*
* The locks are automatically released when the caller's `PathLocks` goes
* out of scope, including on exception unwinding. If we can't acquire the lock, then
* continue; hopefully some other goal can start a build, and if not, the
* main loop will sleep a few seconds and then retry this goal.
*/
std::set<std::filesystem::path> lockFiles;
/* FIXME: Should lock something like the drv itself so we don't build same
CA drv concurrently */
@@ -424,7 +416,8 @@ Goal::Co DerivationBuildingGoal::tryToBuild(StorePathSet inputPaths)
debug("skipping build of derivation '%s', someone beat us to it", worker.store.printStorePath(drvPath));
outputLocks.setDeletion(true);
outputLocks.unlock();
co_return doneSuccess(BuildResult::Success::AlreadyValid, std::move(validOutputs));
done = true;
co_return Return{};
}
/* If any of the outputs already exist but are not valid, delete
@@ -439,71 +432,133 @@ Goal::Co DerivationBuildingGoal::tryToBuild(StorePathSet inputPaths)
}
}
// Check and repair modes are only supported for local builds.
if (buildMode != bmNormal) {
// can try hook: false
// can try local: true
// preference: doesn't matter because it's only one option
break;
} else {
// can try hook: true
// can try local: true
// preference: unknown
co_return Return{};
};
if (drvOptions.preferLocalBuild) {
// preference: local
if (std::holds_alternative<LocalBuildCapability>(localBuildResult)) {
// Can build locally as preferred, so do it.
break;
}
// We cannot build locally, but we can maybe do the hook.
}
auto tryHookLoop = [&](bool & valid) -> Goal::Co {
{
PathLocks outputLocks;
co_await acquireResources(valid, outputLocks);
if (valid)
co_return doneSuccess(BuildResult::Success::AlreadyValid, checkPathValidity(initialOutputs).second);
// If we don't prefer a local build, OR we do but the local build was rejected, then we try the hook if
// possible.
switch (tryBuildHook(drvOptions)) {
case rpAccept:
/* Yes, it has started doing so. Wait until we get
EOF from the hook. */
actLock.reset();
valid = true;
co_return buildWithHook(
std::move(inputPaths), std::move(initialOutputs), std::move(drvOptions), std::move(outputLocks));
break;
case rpDecline:
// We should do it ourselves.
co_return Return{};
case rpPostpone:
/* Not now; wait until at least one child finishes or
the wake-up timeout expires. */
if (!actLock)
actLock = std::make_unique<Activity>(
*logger,
lvlWarn,
actBuildWaiting,
fmt("waiting for a machine to build '%s'", Magenta(worker.store.printStorePath(drvPath))));
outputLocks.unlock();
co_await waitForAWhile();
continue;
case rpDecline:
// We should do it ourselves.
break;
}
}
break;
}
PathLocks outputLocks;
{
// First attempt was postponed. Retry in a loop with an activity
// that lives until accept or decline.
Activity act(
*logger,
lvlWarn,
actBuildWaiting,
fmt("waiting for a machine to build '%s'", Magenta(worker.store.printStorePath(drvPath))));
// can do hook: false (either we can't try, or we tried and were declined)
// can try local: true
// preference: doesn't matter
while (true) {
co_await waitForAWhile();
co_await acquireResources(valid, outputLocks);
if (valid)
break;
actLock.reset();
switch (tryBuildHook(drvOptions)) {
case rpAccept:
/* Yes, it has started doing so. Wait until we get
EOF from the hook. */
break;
case rpPostpone:
/* Not now; wait until at least one child finishes or
the wake-up timeout expires. */
outputLocks.unlock();
continue;
case rpDecline:
// We should do it ourselves.
co_return Return{};
}
if (auto * cap = std::get_if<LocalBuildCapability>(&localBuildResult)) {
co_return buildLocally(
*cap, std::move(inputPaths), std::move(initialOutputs), std::move(drvOptions), std::move(outputLocks));
break;
}
}
if (valid) {
co_return doneSuccess(BuildResult::Success::AlreadyValid, checkPathValidity(initialOutputs).second);
} else {
co_return buildWithHook(
std::move(inputPaths), std::move(initialOutputs), std::move(drvOptions), std::move(outputLocks));
}
};
auto tryBuildLocally = [&](bool & valid) -> Goal::Co {
if (auto * cap = std::get_if<LocalBuildCapability>(&localBuildResult)) {
PathLocks outputLocks;
co_await acquireResources(valid, outputLocks);
if (valid)
co_return doneSuccess(BuildResult::Success::AlreadyValid, checkPathValidity(initialOutputs).second);
valid = true;
co_return buildLocally(
*cap, std::move(inputPaths), std::move(initialOutputs), std::move(drvOptions), std::move(outputLocks));
}
co_return Return{};
};
if (buildMode != bmNormal) {
// Check and repair modes operate on the state of this store specifically,
// so they must always build locally.
bool valid = false;
co_await tryBuildLocally(valid);
if (valid)
co_return Return{};
} else if (drvOptions.preferLocalBuild) {
// Local is preferred, so try it first. If it's not available, fall back to the hook.
{
bool valid = false;
co_await tryBuildLocally(valid);
if (valid)
co_return Return{};
}
{
bool valid = false;
co_await tryHookLoop(valid);
if (valid)
co_return Return{};
}
} else {
outputLocks.unlock();
std::string storePath = worker.store.printStorePath(drvPath);
co_return doneFailure(reject(std::get<LocalBuildRejection>(localBuildResult), storePath));
// Default preference is a remote build: they tend to be faster and preserve local
// resources for other tasks. Fall back to local if no remote is available.
{
bool valid = false;
co_await tryHookLoop(valid);
if (valid)
co_return Return{};
}
{
bool valid = false;
co_await tryBuildLocally(valid);
if (valid)
co_return Return{};
}
}
std::string storePath = worker.store.printStorePath(drvPath);
auto * rejection = std::get_if<LocalBuildRejection>(&localBuildResult);
assert(rejection);
co_return doneFailure(reject(*rejection, storePath));
}
Goal::Co DerivationBuildingGoal::buildWithHook(

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 &) {
throw SysError("failed to rename %1% to %2%", fileName, target.string());
} catch (std::filesystem::filesystem_error & e) {
throw SystemError(e.code(), "failed to rename %1% to %2%", fileName, target.string());
}
}

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;
throw SystemError(e.code(), "finding GC roots in '%1%'", path);
}
catch (SystemError & e) {

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;
throw SystemError(e.code(), "reading symlink '%s'", PathFmt(file));
}
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 SysError & e) {
if (e.errNo != ENOENT)
} catch (const SystemError & e) {
if (!e.is(std::errc::no_such_file_or_directory))
throw;
debug("cannot find machines file '%s'", path);
continue;

View File

@@ -202,14 +202,13 @@ 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, strerror(errno));
printInfo("cannot link %s to '%s': %s", PathFmt(linkPath), path, e.code().message());
return;
}
else
throw;
throw SystemError(e.code(), "creating hard link from %1% to %2%", PathFmt(linkPath), path);
}
}
@@ -250,7 +249,7 @@ void LocalStore::optimisePath_(
printInfo("%1% has maximum number of links", PathFmt(linkPath));
return;
}
throw;
throw SystemError(e.code(), "creating hard link from %1% to %2%", PathFmt(linkPath), PathFmt(tempLink));
}
/* Atomically replace the old file with the new hard link. */
@@ -271,7 +270,7 @@ void LocalStore::optimisePath_(
debug("%s has reached maximum number of links", PathFmt(linkPath));
return;
}
throw;
throw SystemError(e.code(), "renaming %1% to %2%", PathFmt(tempLink), path);
}
stats.filesLinked++;

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 SysError("removing file %1%", PathFmt(path));
throw SystemError(e.code(), "removing file %1%", PathFmt(path));
}
}
@@ -324,8 +324,6 @@ std::filesystem::path getDefaultProfile(ProfileDirsOptions settings)
return absPath(readLink(profileLink), &linkDir);
} catch (Error &) {
return profileLink;
} catch (std::filesystem::filesystem_error &) {
return profileLink;
}
}

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

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

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

@@ -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 SysError**
// Adapt the error message as needed for SysError
throw SysError("cannot read directory %s", PathFmt(p));
// **Catch filesystem_error and throw SystemError**
// Adapt the error message as needed for SystemError
throw SystemError(e.code(), "cannot read directory %s", PathFmt(p));
}
}
@@ -280,7 +280,11 @@ bool pathAccessible(const std::filesystem::path & path)
std::filesystem::path readLink(const std::filesystem::path & path)
{
checkInterrupt();
return std::filesystem::read_symlink(path);
try {
return std::filesystem::read_symlink(path);
} catch (std::filesystem::filesystem_error & e) {
throw SystemError(e.code(), "reading symbolic link '%s'", PathFmt(path));
}
}
Path readLink(const Path & path)
@@ -463,7 +467,7 @@ void createDirs(const std::filesystem::path & path)
try {
std::filesystem::create_directories(path);
} catch (std::filesystem::filesystem_error & e) {
throw SysError("creating directory '%1%'", path.string());
throw SystemError(e.code(), "creating directory '%1%'", path.string());
}
}
@@ -664,7 +668,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 SysError("creating symlink %1% -> %2%", PathFmt(tmp), PathFmt(target));
throw SystemError(e.code(), "creating symlink %1% -> %2%", PathFmt(tmp), PathFmt(target));
}
try {
@@ -672,7 +676,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 SysError("renaming %1% to %2%", PathFmt(tmp), PathFmt(link));
throw SystemError(e.code(), "renaming %1% to %2%", PathFmt(tmp), PathFmt(link));
}
break;
@@ -773,7 +777,7 @@ std::filesystem::path makeParentCanonical(const std::filesystem::path & rawPath)
}
return std::filesystem::canonical(parent) / path.filename();
} catch (std::filesystem::filesystem_error & e) {
throw SysError("canonicalising parent path of %1%", PathFmt(path));
throw SystemError(e.code(), "canonicalising parent path of %1%", PathFmt(path));
}
}

View File

@@ -17,6 +17,7 @@
#include "nix/util/suggestions.hh"
#include "nix/util/fmt.hh"
#include "nix/util/config.hh"
#include <cstring>
#include <list>
@@ -126,20 +127,20 @@ public:
BaseError & operator=(BaseError &&) = default;
template<typename... Args>
BaseError(unsigned int status, const Args &... args)
: err{.level = lvlError, .msg = HintFmt(args...), .pos = {}, .status = status}
BaseError(unsigned int status, Args &&... args)
: err{.level = lvlError, .msg = HintFmt(std::forward<Args>(args)...), .pos = {}, .status = status}
{
}
template<typename... Args>
explicit BaseError(const std::string & fs, const Args &... args)
: err{.level = lvlError, .msg = HintFmt(fs, args...), .pos = {}}
explicit BaseError(const std::string & fs, Args &&... args)
: err{.level = lvlError, .msg = HintFmt(fs, std::forward<Args>(args)...), .pos = {}}
{
}
template<typename... Args>
BaseError(const Suggestions & sug, const Args &... args)
: err{.level = lvlError, .msg = HintFmt(args...), .pos = {}, .suggestions = sug}
BaseError(const Suggestions & sug, Args &&... args)
: err{.level = lvlError, .msg = HintFmt(std::forward<Args>(args)...), .pos = {}, .suggestions = sug}
{
}
@@ -203,9 +204,9 @@ public:
* @param args... Format string arguments.
*/
template<typename... Args>
void addTrace(std::shared_ptr<const Pos> && pos, std::string_view fs, const Args &... args)
void addTrace(std::shared_ptr<const Pos> && pos, std::string_view fs, Args &&... args)
{
addTrace(std::move(pos), HintFmt(std::string(fs), args...));
addTrace(std::move(pos), HintFmt(std::string(fs), std::forward<Args>(args)...));
}
/**
@@ -247,20 +248,110 @@ MakeError(UnimplementedError, Error);
class SystemError : public Error
{
std::error_code errorCode;
std::string errorDetails;
/**
* Just here to allow the static methods to use the right constructor
* (the private one).
*/
struct Disambig
{};
/**
* Private constructor with explicit error message string.
*/
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)
: Error(std::forward<Args>(args)...)
, errorCode(std::make_error_code(posixErrNo))
SystemError(std::error_code errorCode, Args &&... args)
: SystemError(Disambig{}, errorCode, errorCode.message(), std::forward<Args>(args)...)
{
}
/**
* 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>
SystemError(std::error_code errorCode, Args &&... args)
: Error(std::forward<Args>(args)...)
, errorCode(errorCode)
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 &
@@ -275,51 +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, const Args &... args)
: SystemError(static_cast<std::errc>(errNo), "")
, errNo(errNo)
{
auto hf = HintFmt(args...);
err.msg = HintFmt("%1%: %2%", Uncolored(hf.str()), strerror(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(const Args &... args)
: SysError(errno, 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
@@ -350,7 +448,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 == 1
#if NIX_UBSAN_ENABLED
/* When building with sanitizers, also enable expensive unreachable checks. In
optimised builds this explicitly invokes UB with std::unreachable for better
optimisations. */
@@ -359,67 +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, const Args &... args)
: SystemError(std::error_code(lastError, std::system_category()), "")
, lastError(lastError)
{
auto hf = HintFmt(args...);
err.msg = HintFmt("%1%: %2%", Uncolored(hf.str()), renderError(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(const Args &... args)
: WinError(GetLastError(), args...)
{
}
private:
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

@@ -75,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
@@ -97,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

@@ -189,21 +189,19 @@ 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 handle.
* Get the path associated with a file descriptor.
*
* @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 `SystemError` with the saved error code.
*/
std::filesystem::path handleToPath(Descriptor handle);
} // namespace windows
#endif
std::filesystem::path descriptorToPath(Descriptor fd);
/**
* Open a `Descriptor` with read-only access to the given directory.

View File

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

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

@@ -16,7 +16,8 @@ cxx = meson.get_compiler('cpp')
subdir('nix-meson-build-support/deps-lists')
configdata = configuration_data()
configdata_pub = configuration_data()
configdata_priv = configuration_data()
deps_private_maybe_subproject = []
deps_public_maybe_subproject = []
@@ -34,9 +35,15 @@ check_funcs = [
foreach funcspec : check_funcs
define_name = 'HAVE_' + funcspec[0].underscorify().to_upper()
define_value = cxx.has_function(funcspec[0]).to_int()
configdata.set(define_name, define_value, description : funcspec[1])
configdata_priv.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'
@@ -104,7 +111,7 @@ cpuid = dependency(
version : '>= 0.7.0',
required : cpuid_required,
)
configdata.set('HAVE_LIBCPUID', cpuid.found().to_int())
configdata_priv.set('HAVE_LIBCPUID', cpuid.found().to_int())
deps_private += cpuid
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
@@ -113,7 +120,7 @@ deps_public += nlohmann_json
cxx = meson.get_compiler('cpp')
config_priv_h = configure_file(
configuration : configdata,
configuration : configdata_priv,
output : 'util-config-private.hh',
)

View File

@@ -170,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;
throw SystemError(e.code(), "getting status of '%s'", PathFmt(entry.path()));
}
}();
res.emplace(entry.path().filename().string(), type);

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

@@ -49,7 +49,8 @@ void readFull(int fd, char * buf, size_t count)
pollFD(fd, POLLIN);
continue;
}
throw SysError("reading from file");
auto savedErrno = errno;
throw SysError(savedErrno, "reading from file %s", PathFmt(descriptorToPath(fd)));
}
if (res == 0)
throw EndOfFile("unexpected end-of-file");
@@ -72,7 +73,8 @@ void writeFull(int fd, std::string_view s, bool allowInterrupts)
pollFD(fd, POLLOUT);
continue;
}
throw SysError("writing to file");
auto savedErrno = errno;
throw SysError(savedErrno, "writing to file %s", PathFmt(descriptorToPath(fd)));
}
if (res > 0)
s.remove_prefix(res);
@@ -95,8 +97,10 @@ std::string readLine(int fd, bool eofOk, char terminator)
pollFD(fd, POLLIN);
continue;
}
default:
throw SysError("reading a line");
default: {
auto savedErrno = errno;
throw SysError(savedErrno, "reading a line from %s", PathFmt(descriptorToPath(fd)));
}
}
} else if (rd == 0) {
if (eofOk)
@@ -184,7 +188,7 @@ void unix::closeExtraFDs()
}
}
return;
} catch (SysError &) {
} catch (SystemError &) {
}
#endif

View File

@@ -71,8 +71,10 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
if (res < 0) {
if (errno == ENOSYS)
fchmodat2Unsupported.test_and_set();
else
throw SysError("fchmodat2 '%s' relative to parent directory", path.rel());
else {
auto savedErrno = errno;
throw SysError(savedErrno, "fchmodat2 %s", PathFmt(descriptorToPath(dirFd) / path.rel()));
}
} else
return;
}
@@ -80,10 +82,13 @@ 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)
if (!pathFd) {
auto savedErrno = errno;
throw SysError(
"opening '%s' relative to parent directory to get an O_PATH file descriptor (fchmodat2 is unsupported)",
path.rel());
savedErrno,
"opening %s to get an O_PATH file descriptor (fchmodat2 is unsupported)",
PathFmt(descriptorToPath(dirFd) / path.rel()));
}
struct ::stat st;
/* Possible since https://github.com/torvalds/linux/commit/55815f70147dcfa3ead5738fd56d3574e2e3c1c2 (3.6) */
@@ -91,7 +96,7 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
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' relative to parent directory", path.rel());
throw SysError(EOPNOTSUPP, "can't change mode of symlink %s", PathFmt(descriptorToPath(dirFd) / path.rel()));
static std::atomic_flag dontHaveProc{};
if (!dontHaveProc.test()) {
@@ -101,8 +106,11 @@ 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
throw SysError("chmod '%s' ('%s' relative to parent directory)", selfProcFdPath, path);
else {
auto savedErrno = errno;
throw SysError(
savedErrno, "chmod %s (%s)", selfProcFdPath, PathFmt(descriptorToPath(dirFd) / path.rel()));
}
} else
return;
}
@@ -122,8 +130,10 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
#endif
);
if (res == -1)
throw SysError("fchmodat '%s' relative to parent directory", path.rel());
if (res == -1) {
auto savedErrno = errno;
throw SysError(savedErrno, "fchmodat %s", PathFmt(descriptorToPath(dirFd) / path.rel()));
}
}
static Descriptor
@@ -206,9 +216,10 @@ 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)
throw SysError("reading symbolic link '%1%' relative to parent directory", path.rel());
else if (rlSize < bufSize)
if (rlSize == -1) {
auto savedErrno = errno;
throw SysError(savedErrno, "reading symbolic link %1%", PathFmt(descriptorToPath(dirFd) / path.rel()));
} else if (rlSize < bufSize)
return {buf.data(), static_cast<std::size_t>(rlSize)};
}
}

View File

@@ -46,6 +46,31 @@ 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");
@@ -161,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

@@ -8,6 +8,12 @@ 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

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

@@ -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 `handleToPath` will overwrite the last error.
// Do this because `descriptorToPath` will overwrite the last error.
auto lastError = GetLastError();
auto path = handleToPath(handle);
auto path = descriptorToPath(handle);
throw WinError(lastError, "writing to file %d:%s", handle, PathFmt(path));
}
if (res > 0)

View File

@@ -13,8 +13,6 @@
namespace nix {
using namespace nix::windows;
namespace windows {
namespace {
@@ -156,13 +154,13 @@ OsString readSymlinkTarget(HANDLE linkHandle)
size_t path_buf_offset = offsetof(ReparseDataBuffer, SymbolicLinkReparseBuffer.PathBuffer[0]);
if (out < path_buf_offset) {
auto fullPath = handleToPath(linkHandle);
auto fullPath = descriptorToPath(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 = handleToPath(linkHandle);
auto fullPath = descriptorToPath(linkHandle);
throw WinError(DWORD{ERROR_REPARSE_TAG_INVALID}, "not a symlink: %d:%s", linkHandle, PathFmt(fullPath));
}
@@ -179,7 +177,7 @@ OsString readSymlinkTarget(HANDLE linkHandle)
}
if (path_buf_offset + name_offset + name_length > out) {
auto fullPath = handleToPath(linkHandle);
auto fullPath = descriptorToPath(linkHandle);
throw WinError(
DWORD{ERROR_REPARSE_TAG_INVALID}, "invalid symlink data for %d:%s", linkHandle, PathFmt(fullPath));
}
@@ -210,6 +208,8 @@ bool isReparsePoint(HANDLE handle)
} // namespace windows
using namespace nix::windows;
Descriptor openFileEnsureBeneathNoSymlinks(
Descriptor dirFd, const CanonPath & path, ACCESS_MASK desiredAccess, ULONG createOptions, ULONG createDisposition)
{
@@ -259,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;
@@ -286,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;

View File

@@ -83,7 +83,7 @@ void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed)
deletePath(path);
}
std::filesystem::path windows::handleToPath(HANDLE handle)
std::filesystem::path descriptorToPath(Descriptor handle)
{
std::vector<wchar_t> buf(0x100);
DWORD dw = GetFinalPathNameByHandleW(handle, buf.data(), buf.size(), FILE_NAME_OPENED);

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

@@ -427,7 +427,9 @@ void mainWrapped(int argc, char ** argv)
evalSettings.pureEval = true;
#ifndef _WIN32
setLogFormat("bar");
#endif
settings.verboseBuild = false;
// If on a terminal, progress will be displayed via progress bars etc. (thus verbosity=notice)

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",

View File

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

View File

@@ -1419,7 +1419,6 @@ 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 &) {
}
}