Compare commits
1 Commits
directory-
...
cloneable-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ded675e56 |
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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...);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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("")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct TimedOut : BuildError
|
||||
struct TimedOut final : CloneableError<TimedOut, BuildError>
|
||||
{
|
||||
time_t maxDuration;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -166,7 +166,7 @@ struct SQLiteTxn
|
||||
~SQLiteTxn();
|
||||
};
|
||||
|
||||
struct SQLiteError : Error
|
||||
struct SQLiteError : CloneableError<SQLiteError, Error>
|
||||
{
|
||||
std::string path;
|
||||
std::string errMsg;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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, ")")))
|
||||
|
||||
@@ -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__
|
||||
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -82,8 +82,6 @@ public:
|
||||
|
||||
void invalidateCache(const CanonPath & path) override;
|
||||
|
||||
static Stat makeStat(const struct ::stat st);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 &) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user