Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ded675e56 | ||
|
|
663db5b48b | ||
|
|
c486e78235 | ||
|
|
4fff871383 | ||
|
|
b9acea908e | ||
|
|
c3f0670b4e | ||
|
|
7cd4359a8b | ||
|
|
6e725093e6 | ||
|
|
96fef69755 | ||
|
|
16b0bb7548 | ||
|
|
ebcd31e434 | ||
|
|
f940ab5146 | ||
|
|
3df91bea62 | ||
|
|
aaabe82483 | ||
|
|
a81f83604b | ||
|
|
c1bfa30303 | ||
|
|
509694d5f0 | ||
|
|
0b7629da08 | ||
|
|
e7e5eaaa37 | ||
|
|
974545290e | ||
|
|
be6e72f11b | ||
|
|
27782fcc42 | ||
|
|
06d4d5779f | ||
|
|
a32cd16f64 | ||
|
|
46a4a554ca | ||
|
|
cc0b489967 | ||
|
|
af7e585009 | ||
|
|
2ccb8a9a56 | ||
|
|
fefa66880a | ||
|
|
a53391fd0e | ||
|
|
771421a34e | ||
|
|
5aaa0cc4a6 | ||
|
|
ac2dd58b6f |
No files matched your search
@@ -39,6 +39,17 @@ jobs:
|
||||
role-to-assume: "arn:aws:iam::080433136561:role/nix-release"
|
||||
role-session-name: nix-release-oidc-${{ github.run_id }}
|
||||
aws-region: eu-west-1
|
||||
- name: Disable containerd image store
|
||||
run: |
|
||||
# Docker 28+ defaults to the containerd image store, which
|
||||
# pushes layers uncompressed instead of gzip. OCI clients
|
||||
# that only support gzip (e.g. go-containerregistry) fail
|
||||
# with "gzip: invalid header". Disabling the containerd
|
||||
# snapshotter restores the classic storage driver, which
|
||||
# preserves gzip-compressed layers through the
|
||||
# `docker load` / `docker push` pipeline.
|
||||
echo '{"features":{"containerd-snapshotter":false}}' | sudo tee /etc/docker/daemon.json > /dev/null
|
||||
sudo systemctl restart docker
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
synopsis: New command `nix store roots-daemon` for serving GC roots
|
||||
prs: [15143]
|
||||
---
|
||||
|
||||
New command [`nix store roots-daemon`](@docroot@/command-ref/new-cli/nix3-store-roots-daemon.md) runs a daemon that serves garbage collector roots over a Unix domain socket.
|
||||
It enables the garbage collector to discover runtime roots when the main Nix daemon doesn't have `CAP_SYS_PTRACE` capability and therefore cannot scan `/proc`.
|
||||
|
||||
The garbage collector can be configured to use this daemon via the [`use-roots-daemon`](@docroot@/store/types/local-store.md#store-experimental-option-use-roots-daemon) store setting.
|
||||
|
||||
This feature requires the [`local-overlay-store` experimental feature](@docroot@/development/experimental-features.md#xp-feature-local-overlay-store).
|
||||
@@ -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
|
||||
@@ -407,7 +407,7 @@ void createOutLinks(const std::filesystem::path & outLink, const BuiltPaths & bu
|
||||
struct MixOutLinkBase : virtual Args
|
||||
{
|
||||
/** Prefix for any output symlinks. Empty means do not write an output symlink. */
|
||||
std::filesystem::path outLink;
|
||||
Path outLink;
|
||||
|
||||
MixOutLinkBase(const std::string & defaultOutLink)
|
||||
: outLink(defaultOutLink)
|
||||
@@ -435,7 +435,7 @@ struct MixOutLinkByDefault : MixOutLinkBase, virtual Args
|
||||
addFlag({
|
||||
.longName = "no-link",
|
||||
.description = "Do not create symlinks to the build results.",
|
||||
.handler = {&outLink, std::filesystem::path{}},
|
||||
.handler = {&outLink, Path("")},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -157,7 +157,7 @@ MixFlakeOptions::MixFlakeOptions()
|
||||
.category = category,
|
||||
.labels = {"flake-lock-path"},
|
||||
.handler = {[&](std::string lockFilePath) {
|
||||
lockFlags.referenceLockFilePath = {getFSSourceAccessor(), CanonPath(absPath(lockFilePath).string())};
|
||||
lockFlags.referenceLockFilePath = {getFSSourceAccessor(), CanonPath(absPath(lockFilePath))};
|
||||
}},
|
||||
.completer = completePath,
|
||||
});
|
||||
|
||||
@@ -40,8 +40,8 @@ void sigintHandler(int signo)
|
||||
static detail::ReplCompleterMixin * curRepl; // ugly
|
||||
|
||||
#if !USE_READLINE
|
||||
static char * completionCallback(char * s, int * match)
|
||||
{
|
||||
static char * completionCallback(char * s, int * match) noexcept
|
||||
try {
|
||||
auto possible = curRepl->completePrefix(s);
|
||||
if (possible.size() == 1) {
|
||||
*match = 1;
|
||||
@@ -73,10 +73,12 @@ static char * completionCallback(char * s, int * match)
|
||||
|
||||
*match = 0;
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static int listPossibleCallback(char * s, char *** avp)
|
||||
{
|
||||
static int listPossibleCallback(char * s, char *** avp) noexcept
|
||||
try {
|
||||
auto possible = curRepl->completePrefix(s);
|
||||
|
||||
if (possible.size() > (std::numeric_limits<int>::max() / sizeof(char *)))
|
||||
@@ -105,6 +107,9 @@ static int listPossibleCallback(char * s, char *** avp)
|
||||
*avp = vp;
|
||||
|
||||
return ac;
|
||||
} catch (...) {
|
||||
*avp = nullptr;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -113,7 +118,7 @@ ReadlineLikeInteracter::Guard ReadlineLikeInteracter::init(detail::ReplCompleter
|
||||
// Allow nix-repl specific settings in .inputrc
|
||||
rl_readline_name = "nix-repl";
|
||||
try {
|
||||
createDirs(std::filesystem::path(historyFile).parent_path());
|
||||
createDirs(dirOf(historyFile));
|
||||
} catch (SystemError & e) {
|
||||
logWarning(e.info());
|
||||
}
|
||||
|
||||
@@ -13,8 +13,7 @@ TEST_F(ValueTest, unsetValue)
|
||||
{
|
||||
Value unsetValue;
|
||||
ASSERT_EQ(false, unsetValue.isValid());
|
||||
ASSERT_EQ(nThunk, unsetValue.type(true));
|
||||
ASSERT_DEATH(unsetValue.type(), "");
|
||||
ASSERT_EQ(nThunk, unsetValue.type</*invalidIsThunk=*/true>());
|
||||
}
|
||||
|
||||
TEST_F(ValueTest, vInt)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -70,7 +70,7 @@ struct AttrDb
|
||||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
auto cacheDir = getCacheDir() / "eval-cache-v6";
|
||||
auto cacheDir = std::filesystem::path(getCacheDir()) / "eval-cache-v6";
|
||||
createDirs(cacheDir);
|
||||
|
||||
auto dbPath = cacheDir / (fingerprint.to_string(HashFormat::Base16, false) + ".sqlite");
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
#include "nix/expr/eval-error.hh"
|
||||
#include "nix/expr/eval.hh"
|
||||
#include "nix/expr/value.hh"
|
||||
#include "nix/store/store-api.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
InvalidPathError::InvalidPathError(EvalState & state, const StorePath & path)
|
||||
: EvalError(state, "path '%s' is not valid", state.store->printStorePath(path))
|
||||
, path{path}
|
||||
{
|
||||
}
|
||||
|
||||
template<class T>
|
||||
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withExitStatus(unsigned int exitStatus)
|
||||
{
|
||||
|
||||
@@ -70,7 +70,7 @@ Strings EvalSettings::getDefaultNixPath()
|
||||
}
|
||||
};
|
||||
|
||||
add(getNixDefExpr() / "channels");
|
||||
add(std::filesystem::path{getNixDefExpr()} / "channels");
|
||||
auto profilesDirOpts = settings.getProfileDirsOptions();
|
||||
add(rootChannelsDir(profilesDirOpts) / "nixpkgs", "nixpkgs");
|
||||
add(rootChannelsDir(profilesDirOpts));
|
||||
|
||||
+5
-5
@@ -432,7 +432,7 @@ void EvalState::checkURI(const std::string & uri)
|
||||
|
||||
/* If the URI is a path, then check it against allowedPaths as
|
||||
well. */
|
||||
if (std::filesystem::path(uri).is_absolute()) {
|
||||
if (isAbsolute(uri)) {
|
||||
if (auto rootFS2 = rootFS.dynamic_pointer_cast<AllowListSourceAccessor>())
|
||||
rootFS2->checkAccess(CanonPath(uri));
|
||||
return;
|
||||
@@ -466,7 +466,7 @@ void EvalState::addConstant(const std::string & name, Value * v, Constant info)
|
||||
|
||||
We might know the type of a thunk in advance, so be allowed
|
||||
to just write it down in that case. */
|
||||
if (auto gotType = v->type(true); gotType != nThunk)
|
||||
if (auto gotType = v->type</*invalidIsThunk=*/true>(); gotType != nThunk)
|
||||
assert(info.type == gotType);
|
||||
|
||||
/* Install value the base environment. */
|
||||
@@ -885,7 +885,7 @@ void Value::mkPath(const SourcePath & path, EvalMemory & mem)
|
||||
mkPath(&*path.accessor, StringData::make(mem, path.path.abs()));
|
||||
}
|
||||
|
||||
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
||||
[[gnu::always_inline]] inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
||||
{
|
||||
for (auto l = var.level; l; --l, env = env->up)
|
||||
;
|
||||
@@ -903,11 +903,11 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
||||
while (1) {
|
||||
forceAttrs(*env->values[0], fromWith->pos, "while evaluating the first subexpression of a with expression");
|
||||
if (auto j = env->values[0]->attrs()->get(var.name)) {
|
||||
if (countCalls)
|
||||
if (countCalls) [[unlikely]]
|
||||
attrSelects[j->pos]++;
|
||||
return j->value;
|
||||
}
|
||||
if (!fromWith->parentWith)
|
||||
if (!fromWith->parentWith) [[unlikely]]
|
||||
error<UndefinedVarError>("undefined variable '%1%'", symbols[var.name])
|
||||
.atPos(var.pos)
|
||||
.withFrame(*env, var)
|
||||
|
||||
@@ -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,22 +60,25 @@ 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:
|
||||
StorePath path;
|
||||
Path path;
|
||||
|
||||
InvalidPathError(EvalState & state, const StorePath & path);
|
||||
InvalidPathError(EvalState & state, const Path & path)
|
||||
: CloneableError(state, "path '%s' is not valid", path)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,7 +35,7 @@ class BindingsBuilder;
|
||||
* about how this is mapped into the alignment bits to save significant memory.
|
||||
* This also restricts the number of internal types represented with distinct memory layouts.
|
||||
*/
|
||||
typedef enum {
|
||||
enum InternalType {
|
||||
tUninitialized = 0,
|
||||
/* layout: Single/zero field payload */
|
||||
tInt = 1,
|
||||
@@ -46,16 +46,20 @@ typedef enum {
|
||||
tPrimOp,
|
||||
tAttrs,
|
||||
/* layout: Pair of pointers payload */
|
||||
tListSmall,
|
||||
tFirstPairOfPointers,
|
||||
tListSmall = tFirstPairOfPointers,
|
||||
tPrimOpApp,
|
||||
tApp,
|
||||
tThunk,
|
||||
tLambda,
|
||||
tLastPairOfPointers = tLambda,
|
||||
/* layout: Single untaggable field */
|
||||
tListN,
|
||||
tFirstSingleUntaggable,
|
||||
tListN = tFirstSingleUntaggable,
|
||||
tString,
|
||||
tPath,
|
||||
} InternalType;
|
||||
tNumberOfInternalTypes, // Must be last
|
||||
};
|
||||
|
||||
/**
|
||||
* This type abstracts over all actual value types in the language,
|
||||
@@ -633,7 +637,7 @@ class alignas(16)
|
||||
template<InternalType type, typename T, typename U>
|
||||
void setPairOfPointersPayload(T * firstPtrField, U * secondPtrField) noexcept
|
||||
{
|
||||
static_assert(type >= tListSmall && type <= tLambda);
|
||||
static_assert(type >= tFirstPairOfPointers && type <= tLastPairOfPointers);
|
||||
{
|
||||
auto firstFieldPayload = std::bit_cast<PackedPointer>(firstPtrField);
|
||||
assertAligned(firstFieldPayload);
|
||||
@@ -642,7 +646,7 @@ class alignas(16)
|
||||
{
|
||||
auto secondFieldPayload = std::bit_cast<PackedPointer>(secondPtrField);
|
||||
assertAligned(secondFieldPayload);
|
||||
payload[1] = (type - tListSmall) | secondFieldPayload;
|
||||
payload[1] = (type - tFirstPairOfPointers) | secondFieldPayload;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,11 +674,11 @@ protected:
|
||||
case pdListN:
|
||||
case pdString:
|
||||
case pdPath:
|
||||
return static_cast<InternalType>(tListN + (pd - pdListN));
|
||||
return static_cast<InternalType>(tFirstSingleUntaggable + (pd - pdListN));
|
||||
case pdPairOfPointers:
|
||||
return static_cast<InternalType>(tListSmall + (payload[1] & discriminatorMask));
|
||||
return static_cast<InternalType>(tFirstPairOfPointers + (payload[1] & discriminatorMask));
|
||||
[[unlikely]] default:
|
||||
unreachable();
|
||||
nixUnreachableWhenHardened();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1027,7 +1031,7 @@ private:
|
||||
T getStorage() const noexcept
|
||||
{
|
||||
if (getInternalType() != detail::payloadTypeToInternalType<T>) [[unlikely]]
|
||||
unreachable();
|
||||
nixUnreachableWhenHardened();
|
||||
T out;
|
||||
ValueStorage::getStorage(out);
|
||||
return out;
|
||||
@@ -1079,45 +1083,44 @@ public:
|
||||
* Returns the normal type of a Value. This only returns nThunk if
|
||||
* the Value hasn't been forceValue'd
|
||||
*
|
||||
* @param invalidIsThunk Instead of aborting an an invalid (probably
|
||||
* @param invalidIsThunk Instead of UB an an invalid (probably
|
||||
* 0, so uninitialized) internal type, return `nThunk`.
|
||||
*/
|
||||
inline ValueType type(bool invalidIsThunk = false) const
|
||||
template<bool invalidIsThunk = false>
|
||||
inline ValueType type() const
|
||||
{
|
||||
switch (getInternalType()) {
|
||||
case tUninitialized:
|
||||
break;
|
||||
case tInt:
|
||||
return nInt;
|
||||
case tBool:
|
||||
return nBool;
|
||||
case tString:
|
||||
return nString;
|
||||
case tPath:
|
||||
return nPath;
|
||||
case tNull:
|
||||
return nNull;
|
||||
case tAttrs:
|
||||
return nAttrs;
|
||||
case tListSmall:
|
||||
case tListN:
|
||||
return nList;
|
||||
case tLambda:
|
||||
case tPrimOp:
|
||||
case tPrimOpApp:
|
||||
return nFunction;
|
||||
case tExternal:
|
||||
return nExternal;
|
||||
case tFloat:
|
||||
return nFloat;
|
||||
case tThunk:
|
||||
case tApp:
|
||||
return nThunk;
|
||||
/* Explicit lookup table. switch() might compile down (and it does at least with GCC 14)
|
||||
to a jump table. Let's help the compiler a bit here. */
|
||||
static constexpr auto table = [] {
|
||||
std::array<ValueType, tNumberOfInternalTypes> t{};
|
||||
t[tUninitialized] = nThunk;
|
||||
t[tInt] = nInt;
|
||||
t[tBool] = nBool;
|
||||
t[tNull] = nNull;
|
||||
t[tFloat] = nFloat;
|
||||
t[tExternal] = nExternal;
|
||||
t[tAttrs] = nAttrs;
|
||||
t[tPrimOp] = nFunction;
|
||||
t[tLambda] = nFunction;
|
||||
t[tPrimOpApp] = nFunction;
|
||||
t[tApp] = nThunk;
|
||||
t[tThunk] = nThunk;
|
||||
t[tListSmall] = nList;
|
||||
t[tListN] = nList;
|
||||
t[tString] = nString;
|
||||
t[tPath] = nPath;
|
||||
return t;
|
||||
}();
|
||||
|
||||
auto it = getInternalType();
|
||||
if (it == tUninitialized || it >= tNumberOfInternalTypes) [[unlikely]] {
|
||||
if constexpr (invalidIsThunk)
|
||||
return nThunk;
|
||||
else
|
||||
nixUnreachableWhenHardened();
|
||||
}
|
||||
if (invalidIsThunk)
|
||||
return nThunk;
|
||||
else
|
||||
unreachable();
|
||||
|
||||
return table[it];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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...);
|
||||
|
||||
@@ -28,7 +28,7 @@ deps_public_maybe_subproject = [
|
||||
subdir('nix-meson-build-support/subprojects')
|
||||
subdir('nix-meson-build-support/big-objs')
|
||||
|
||||
# Check for each of these functions, and create a define like `#define HAVE_LCHOWN 1`.
|
||||
# Check for each of these functions, and create a define like `#define HAVE_SYSCONF 1`.
|
||||
check_funcs = [
|
||||
'sysconf',
|
||||
]
|
||||
|
||||
@@ -388,8 +388,7 @@ path_start
|
||||
});
|
||||
}
|
||||
|
||||
auto basePath = std::filesystem::path(state->basePath.path.abs());
|
||||
Path path(absPath(literal, &basePath).string());
|
||||
Path path(absPath(literal, state->basePath.path.abs()));
|
||||
/* add back in the trailing '/' to the first segment */
|
||||
if (literal.size() > 1 && literal.back() == '/')
|
||||
path += '/';
|
||||
|
||||
@@ -12,7 +12,7 @@ SourcePath EvalState::rootPath(CanonPath path)
|
||||
|
||||
SourcePath EvalState::rootPath(PathView path)
|
||||
{
|
||||
return {rootFS, CanonPath(absPath(std::filesystem::path{path}).string())};
|
||||
return {rootFS, CanonPath(absPath(path))};
|
||||
}
|
||||
|
||||
SourcePath EvalState::storePath(const StorePath & path)
|
||||
|
||||
+10
-9
@@ -74,7 +74,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context, StorePathS
|
||||
for (auto & c : context) {
|
||||
auto ensureValid = [&](const StorePath & p) {
|
||||
if (!store->isValidPath(p))
|
||||
error<InvalidPathError>(p).debugThrow();
|
||||
error<InvalidPathError>(store->printStorePath(p)).debugThrow();
|
||||
};
|
||||
std::visit(
|
||||
overloaded{
|
||||
@@ -503,7 +503,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
try {
|
||||
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
|
||||
} catch (InvalidPathError & e) {
|
||||
state.error<EvalError>("cannot execute '%1%', since path '%2%' is not valid", program, e.path.to_string())
|
||||
state.error<EvalError>("cannot execute '%1%', since path '%2%' is not valid", program, e.path)
|
||||
.atPos(pos)
|
||||
.debugThrow();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1931,7 +1932,7 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value ** args, V
|
||||
directly in the store. The latter condition is necessary so
|
||||
e.g. nix-push does the right thing. */
|
||||
if (!state.store->isStorePath(path.abs()))
|
||||
path = CanonPath(canonPath(path.abs(), true).string());
|
||||
path = CanonPath(canonPath(path.abs(), true));
|
||||
if (!state.store->isInStore(path.abs()))
|
||||
state.error<EvalError>("path '%1%' is not in the Nix store", path).atPos(pos).debugThrow();
|
||||
auto path2 = state.store->toStorePath(path.abs()).first;
|
||||
@@ -2152,7 +2153,7 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value ** args, Va
|
||||
auto rewrites = state.realiseContext(context);
|
||||
path = rewriteStrings(std::move(path), rewrites);
|
||||
} catch (InvalidPathError & e) {
|
||||
state.error<EvalError>("cannot find '%1%', since path '%2%' is not valid", path, state.store->printStorePath(e.path))
|
||||
state.error<EvalError>("cannot find '%1%', since path '%2%' is not valid", path, e.path)
|
||||
.atPos(pos)
|
||||
.debugThrow();
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ struct CacheImpl : Cache
|
||||
{
|
||||
auto state(_state.lock());
|
||||
|
||||
auto dbPath = getCacheDir() / "fetcher-cache-v4.sqlite";
|
||||
createDirs(dbPath.parent_path());
|
||||
auto dbPath = (getCacheDir() / "fetcher-cache-v4.sqlite").string();
|
||||
createDirs(dirOf(dbPath));
|
||||
|
||||
state->db = SQLite(dbPath, {.useWAL = nix::settings.useSQLiteWAL});
|
||||
state->db.isCache();
|
||||
|
||||
@@ -47,6 +47,12 @@ std::pair<StorePath, Hash> fetchToStore2(
|
||||
auto hash = Hash::parseSRI(fetchers::getStrAttr(*res, "hash"));
|
||||
auto storePath =
|
||||
store.makeFixedOutputPathFromCA(name, ContentAddressWithReferences::fromParts(method, hash, {}));
|
||||
|
||||
/* Add a temproot before the call to isValidPath to prevent accidental GC in case the
|
||||
input is cached. Note that this must be done before to avoid races. */
|
||||
if (mode != FetchMode::DryRun)
|
||||
store.addTempRoot(storePath);
|
||||
|
||||
if (mode == FetchMode::DryRun || store.isValidPath(storePath)) {
|
||||
debug(
|
||||
"source path '%s' cache hit in '%s' (hash '%s')",
|
||||
|
||||
@@ -268,10 +268,10 @@ void Fetch::fetch(
|
||||
return;
|
||||
}
|
||||
|
||||
auto cacheDir = getCacheDir() / "git-lfs";
|
||||
std::filesystem::path cacheDir = getCacheDir() / "git-lfs";
|
||||
std::string key = hashString(HashAlgorithm::SHA256, pointerFilePath.rel()).to_string(HashFormat::Base16, false)
|
||||
+ "/" + pointer->oid;
|
||||
auto cachePath = cacheDir / key;
|
||||
std::filesystem::path cachePath = cacheDir / key;
|
||||
if (pathExists(cachePath)) {
|
||||
debug("using cache entry %s -> %s", key, PathFmt(cachePath));
|
||||
sink(readFile(cachePath));
|
||||
|
||||
@@ -74,6 +74,29 @@ namespace nix {
|
||||
|
||||
struct GitSourceAccessor;
|
||||
|
||||
struct GitError final : public CloneableError<GitError, Error>
|
||||
{
|
||||
template<typename... Ts>
|
||||
GitError(const git_error & error, Ts &&... args)
|
||||
: CloneableError("")
|
||||
{
|
||||
auto hf = HintFmt(std::forward<Ts>(args)...);
|
||||
err.msg = HintFmt("%1%: %2% (libgit2 error code = %3%)", Uncolored(hf.str()), error.message, error.klass);
|
||||
}
|
||||
|
||||
template<typename... Ts>
|
||||
GitError(Ts &&... args)
|
||||
: GitError(
|
||||
[]() -> const git_error & {
|
||||
const git_error * p = git_error_last();
|
||||
assert(p && "git_error_last() is unexpectedly null");
|
||||
return *p;
|
||||
}(),
|
||||
std::forward<Ts>(args)...)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<git_repository, Deleter<git_repository_free>> Repository;
|
||||
typedef std::unique_ptr<git_tree_entry, Deleter<git_tree_entry_free>> TreeEntry;
|
||||
typedef std::unique_ptr<git_tree, Deleter<git_tree_free>> Tree;
|
||||
@@ -106,7 +129,7 @@ static void initLibGit2()
|
||||
static std::once_flag initialized;
|
||||
std::call_once(initialized, []() {
|
||||
if (git_libgit2_init() < 0)
|
||||
throw Error("initialising libgit2: %s", git_error_last()->message);
|
||||
throw GitError("initialising libgit2");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -114,7 +137,7 @@ static git_oid hashToOID(const Hash & hash)
|
||||
{
|
||||
git_oid oid;
|
||||
if (git_oid_fromstr(&oid, hash.gitRev().c_str()))
|
||||
throw Error("cannot convert '%s' to a Git OID", hash.gitRev());
|
||||
throw GitError("cannot convert '%s' to a Git OID", hash.gitRev());
|
||||
return oid;
|
||||
}
|
||||
|
||||
@@ -122,8 +145,7 @@ static Object lookupObject(git_repository * repo, const git_oid & oid, git_objec
|
||||
{
|
||||
Object obj;
|
||||
if (git_object_lookup(Setter(obj), repo, &oid, type)) {
|
||||
auto err = git_error_last();
|
||||
throw Error("getting Git object '%s': %s", oid, err->message);
|
||||
throw GitError("getting Git object '%s'", oid);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
@@ -133,8 +155,7 @@ static T peelObject(git_object * obj, git_object_t type)
|
||||
{
|
||||
T obj2;
|
||||
if (git_object_peel((git_object **) (typename T::pointer *) Setter(obj2), obj, type)) {
|
||||
auto err = git_error_last();
|
||||
throw Error("peeling Git object '%s': %s", *git_object_id(obj), err->message);
|
||||
throw Error("peeling Git object '%s'", *git_object_id(obj));
|
||||
}
|
||||
return obj2;
|
||||
}
|
||||
@@ -144,7 +165,7 @@ static T dupObject(typename T::pointer obj)
|
||||
{
|
||||
T obj2;
|
||||
if (git_object_dup((git_object **) (typename T::pointer *) Setter(obj2), (git_object *) obj))
|
||||
throw Error("duplicating object '%s': %s", *git_object_id((git_object *) obj), git_error_last()->message);
|
||||
throw GitError("duplicating object '%s'", *git_object_id((git_object *) obj));
|
||||
return obj2;
|
||||
}
|
||||
|
||||
@@ -216,7 +237,7 @@ static void initRepoAtomically(std::filesystem::path & path, GitRepo::Options op
|
||||
Repository tmpRepo;
|
||||
|
||||
if (git_repository_init(Setter(tmpRepo), tmpDir.string().c_str(), options.bare))
|
||||
throw Error("creating Git repository %s: %s", PathFmt(path), git_error_last()->message);
|
||||
throw GitError("creating Git repository %s", PathFmt(path));
|
||||
try {
|
||||
std::filesystem::rename(tmpDir, path);
|
||||
} catch (std::filesystem::filesystem_error & e) {
|
||||
@@ -266,7 +287,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
|
||||
initRepoAtomically(path, options);
|
||||
if (git_repository_open(Setter(repo), path.string().c_str()))
|
||||
throw Error("opening Git repository %s: %s", PathFmt(path), git_error_last()->message);
|
||||
throw GitError("opening Git repository %s", PathFmt(path));
|
||||
|
||||
ObjectDb odb;
|
||||
if (options.packfilesOnly) {
|
||||
@@ -279,28 +300,28 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
*/
|
||||
|
||||
if (git_odb_new(Setter(odb)))
|
||||
throw Error("creating Git object database: %s", git_error_last()->message);
|
||||
throw GitError("creating Git object database");
|
||||
|
||||
if (git_odb_backend_pack(&packBackend, (path / "objects").string().c_str()))
|
||||
throw Error("creating pack backend: %s", git_error_last()->message);
|
||||
throw GitError("creating pack backend");
|
||||
|
||||
if (git_odb_add_backend(odb.get(), packBackend, 1))
|
||||
throw Error("adding pack backend to Git object database: %s", git_error_last()->message);
|
||||
throw GitError("adding pack backend to Git object database");
|
||||
} else {
|
||||
if (git_repository_odb(Setter(odb), repo.get()))
|
||||
throw Error("getting Git object database: %s", git_error_last()->message);
|
||||
throw GitError("getting Git object database");
|
||||
}
|
||||
|
||||
// mempack_backend will be owned by the repository, so we are not expected to free it ourselves.
|
||||
if (git_mempack_new(&mempackBackend))
|
||||
throw Error("creating mempack backend: %s", git_error_last()->message);
|
||||
throw GitError("creating mempack backend");
|
||||
|
||||
if (git_odb_add_backend(odb.get(), mempackBackend, 999))
|
||||
throw Error("adding mempack backend to Git object database: %s", git_error_last()->message);
|
||||
throw GitError("adding mempack backend to Git object database");
|
||||
|
||||
if (options.packfilesOnly) {
|
||||
if (git_repository_set_odb(repo.get(), odb.get()))
|
||||
throw Error("setting Git object database: %s", git_error_last()->message);
|
||||
throw GitError("setting Git object database");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,7 +361,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
Indexer indexer;
|
||||
git_indexer_progress stats;
|
||||
if (git_indexer_new(Setter(indexer), pack_dir_path.c_str(), 0, nullptr, nullptr))
|
||||
throw Error("creating git packfile indexer: %s", git_error_last()->message);
|
||||
throw GitError("creating git packfile indexer");
|
||||
|
||||
// TODO: provide index callback for checkInterrupt() termination
|
||||
// though this is about an order of magnitude faster than the packbuilder
|
||||
@@ -348,15 +369,15 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
constexpr size_t chunkSize = 128 * 1024;
|
||||
for (size_t offset = 0; offset < buf.size; offset += chunkSize) {
|
||||
if (git_indexer_append(indexer.get(), buf.ptr + offset, std::min(chunkSize, buf.size - offset), &stats))
|
||||
throw Error("appending to git packfile index: %s", git_error_last()->message);
|
||||
throw GitError("appending to git packfile index");
|
||||
checkInterrupt();
|
||||
}
|
||||
|
||||
if (git_indexer_commit(indexer.get(), &stats))
|
||||
throw Error("committing git packfile index: %s", git_error_last()->message);
|
||||
throw GitError("committing git packfile index");
|
||||
|
||||
if (git_mempack_reset(mempackBackend))
|
||||
throw Error("resetting git mempack backend: %s", git_error_last()->message);
|
||||
throw GitError("resetting git mempack backend");
|
||||
|
||||
checkInterrupt();
|
||||
}
|
||||
@@ -449,7 +470,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
void setRemote(const std::string & name, const std::string & url) override
|
||||
{
|
||||
if (git_remote_set_url(*this, name.c_str(), url.c_str()))
|
||||
throw Error("setting remote '%s' URL to '%s': %s", name, url, git_error_last()->message);
|
||||
throw GitError("setting remote '%s' URL to '%s'", name, url);
|
||||
}
|
||||
|
||||
Hash resolveRef(std::string ref) override
|
||||
@@ -462,7 +483,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
// an object_id.
|
||||
std::string peeledRef = ref + "^{commit}";
|
||||
if (git_revparse_single(Setter(object), *this, peeledRef.c_str()))
|
||||
throw Error("resolving Git reference '%s': %s", ref, git_error_last()->message);
|
||||
throw GitError("resolving Git reference '%s'", ref);
|
||||
auto oid = git_object_id(object.get());
|
||||
return toHash(*oid);
|
||||
}
|
||||
@@ -471,11 +492,11 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
{
|
||||
GitConfig config;
|
||||
if (git_config_open_ondisk(Setter(config), configFile.string().c_str()))
|
||||
throw Error("parsing .gitmodules file: %s", git_error_last()->message);
|
||||
throw GitError("parsing .gitmodules file");
|
||||
|
||||
ConfigIterator it;
|
||||
if (git_config_iterator_glob_new(Setter(it), config.get(), "^submodule\\..*\\.(path|url|branch)$"))
|
||||
throw Error("iterating over .gitmodules: %s", git_error_last()->message);
|
||||
throw GitError("iterating over .gitmodules");
|
||||
|
||||
StringMap entries;
|
||||
|
||||
@@ -484,7 +505,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
if (auto err = git_config_next(&entry, it.get())) {
|
||||
if (err == GIT_ITEROVER)
|
||||
break;
|
||||
throw Error("iterating over .gitmodules: %s", git_error_last()->message);
|
||||
throw GitError("iterating over .gitmodules");
|
||||
}
|
||||
entries.emplace(entry->name + 10, entry->value);
|
||||
}
|
||||
@@ -521,7 +542,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
git_oid headRev;
|
||||
if (auto err = git_reference_name_to_id(&headRev, *this, "HEAD")) {
|
||||
if (err != GIT_ENOTFOUND)
|
||||
throw Error("resolving HEAD: %s", git_error_last()->message);
|
||||
throw GitError("resolving HEAD");
|
||||
} else
|
||||
info.headRev = toHash(headRev);
|
||||
|
||||
@@ -544,7 +565,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
options.flags |= GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
|
||||
options.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
|
||||
if (git_status_foreach_ext(*this, &options, &statusCallbackTrampoline, &statusCallback))
|
||||
throw Error("getting working directory status: %s", git_error_last()->message);
|
||||
throw GitError("getting working directory status");
|
||||
|
||||
/* Get submodule info. */
|
||||
auto modulesFile = path / ".gitmodules";
|
||||
@@ -587,8 +608,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
if (auto errCode = git_object_lookup(Setter(obj), *this, &oid, GIT_OBJECT_ANY)) {
|
||||
if (errCode == GIT_ENOTFOUND)
|
||||
return false;
|
||||
auto err = git_error_last();
|
||||
throw Error("getting Git object '%s': %s", oid, err->message);
|
||||
throw GitError("getting Git object '%s'", oid);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -666,7 +686,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
.program = "git",
|
||||
.args =
|
||||
{"-c",
|
||||
"gpg.ssh.allowedSignersFile=" + allowedSignersFile.string(),
|
||||
"gpg.ssh.allowedSignersFile=" + allowedSignersFile,
|
||||
"-C",
|
||||
path.string(),
|
||||
"verify-commit",
|
||||
@@ -918,7 +938,7 @@ struct GitSourceAccessor : SourceAccessor
|
||||
|
||||
TreeEntry copy;
|
||||
if (git_tree_entry_dup(Setter(copy), entry))
|
||||
throw Error("dupping tree entry: %s", git_error_last()->message);
|
||||
throw GitError("dupping tree entry");
|
||||
|
||||
auto entryName = std::string_view(git_tree_entry_name(entry));
|
||||
|
||||
@@ -948,7 +968,7 @@ struct GitSourceAccessor : SourceAccessor
|
||||
|
||||
Tree tree;
|
||||
if (git_tree_entry_to_object((git_object **) (git_tree **) Setter(tree), *state.repo, entry))
|
||||
throw Error("looking up directory '%s': %s", showPath(path), git_error_last()->message);
|
||||
throw GitError("looking up directory '%s'", showPath(path));
|
||||
|
||||
return tree;
|
||||
}
|
||||
@@ -983,7 +1003,7 @@ struct GitSourceAccessor : SourceAccessor
|
||||
|
||||
Tree tree;
|
||||
if (git_tree_entry_to_object((git_object **) (git_tree **) Setter(tree), *state.repo, entry))
|
||||
throw Error("looking up directory '%s': %s", showPath(path), git_error_last()->message);
|
||||
throw GitError("looking up directory '%s'", showPath(path));
|
||||
|
||||
return tree;
|
||||
}
|
||||
@@ -1016,7 +1036,7 @@ struct GitSourceAccessor : SourceAccessor
|
||||
|
||||
Blob blob;
|
||||
if (git_tree_entry_to_object((git_object **) (git_blob **) Setter(blob), *state.repo, entry))
|
||||
throw Error("looking up file '%s': %s", showPath(path), git_error_last()->message);
|
||||
throw GitError("looking up file '%s'", showPath(path));
|
||||
|
||||
return blob;
|
||||
}
|
||||
@@ -1068,7 +1088,7 @@ struct GitExportIgnoreSourceAccessor : CachingFilteringSourceAccessor
|
||||
if (git_error_last()->klass == GIT_ENOTFOUND)
|
||||
return false;
|
||||
else
|
||||
throw Error("looking up '%s': %s", showPath(path), git_error_last()->message);
|
||||
throw GitError("looking up '%s'", showPath(path));
|
||||
} else {
|
||||
// Official git will silently reject export-ignore lines that have
|
||||
// values. We do the same.
|
||||
@@ -1224,17 +1244,17 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
|
||||
repo.emplace(parent.repoPool.get());
|
||||
|
||||
if (git_blob_create_from_stream(Setter(stream), **repo, nullptr))
|
||||
throw Error("creating a blob stream object: %s", git_error_last()->message);
|
||||
throw GitError("creating a blob stream object");
|
||||
|
||||
if (stream->write(stream.get(), contents.data(), contents.size()))
|
||||
throw Error("writing a blob for tarball member '%s': %s", path, git_error_last()->message);
|
||||
throw GitError("writing a blob for tarball member '%s'", path);
|
||||
|
||||
parent.totalBufSize -= contents.size();
|
||||
contents.clear();
|
||||
}
|
||||
} else {
|
||||
if (stream->write(stream.get(), data.data(), data.size()))
|
||||
throw Error("writing a blob for tarball member '%s': %s", path, git_error_last()->message);
|
||||
throw GitError("writing a blob for tarball member '%s'", path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1256,7 +1276,7 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
|
||||
acquires ownership and frees the stream. */
|
||||
git_oid oid;
|
||||
if (git_blob_create_from_stream_commit(&oid, crf->stream.release()))
|
||||
throw Error("creating a blob object for '%s': %s", path, git_error_last()->message);
|
||||
throw GitError("creating a blob object for '%s'", path);
|
||||
addNode(
|
||||
*_state.lock(),
|
||||
crf->path,
|
||||
@@ -1270,8 +1290,7 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
|
||||
|
||||
git_oid oid;
|
||||
if (git_blob_create_from_buffer(&oid, *repo, crf->contents.data(), crf->contents.size()))
|
||||
throw Error(
|
||||
"creating a blob object for '%s' from in-memory buffer: %s", crf->path, git_error_last()->message);
|
||||
throw GitError("creating a blob object for '%s' from in-memory buffer", crf->path);
|
||||
|
||||
addNode(
|
||||
*_state.lock(),
|
||||
@@ -1295,8 +1314,7 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
|
||||
|
||||
git_oid oid;
|
||||
if (git_blob_create_from_buffer(&oid, *repo, target.c_str(), target.size()))
|
||||
throw Error(
|
||||
"creating a blob object for tarball symlink member '%s': %s", path, git_error_last()->message);
|
||||
throw GitError("creating a blob object for tarball symlink member '%s'", path);
|
||||
|
||||
auto state(_state.lock());
|
||||
addNode(*state, path, Child{GIT_FILEMODE_LINK, oid});
|
||||
@@ -1357,19 +1375,19 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
|
||||
// Write this directory.
|
||||
git_treebuilder * b;
|
||||
if (git_treebuilder_new(&b, *repo, nullptr))
|
||||
throw Error("creating a tree builder: %s", git_error_last()->message);
|
||||
throw GitError("creating a tree builder");
|
||||
TreeBuilder builder(b);
|
||||
|
||||
for (auto & [name, child] : node.children) {
|
||||
auto oid_p = std::get_if<git_oid>(&child.file);
|
||||
auto oid = oid_p ? *oid_p : std::get<Directory>(child.file).oid.value();
|
||||
if (git_treebuilder_insert(nullptr, builder.get(), name.c_str(), &oid, child.mode))
|
||||
throw Error("adding a file to a tree builder: %s", git_error_last()->message);
|
||||
throw GitError("adding a file to a tree builder");
|
||||
}
|
||||
|
||||
git_oid oid;
|
||||
if (git_treebuilder_write(&oid, builder.get()))
|
||||
throw Error("creating a tree object: %s", git_error_last()->message);
|
||||
throw GitError("creating a tree object");
|
||||
node.oid = oid;
|
||||
}(_state.lock()->root);
|
||||
|
||||
|
||||
@@ -44,9 +44,8 @@ static bool isCacheFileWithinTtl(const Settings & settings, time_t now, const Po
|
||||
|
||||
std::filesystem::path getCachePath(std::string_view key, bool shallow)
|
||||
{
|
||||
auto name =
|
||||
hashString(HashAlgorithm::SHA256, key).to_string(HashFormat::Nix32, false) + (shallow ? "-shallow" : "");
|
||||
return getCacheDir() / "gitv3" / std::move(name);
|
||||
return getCacheDir() / "gitv3"
|
||||
/ (hashString(HashAlgorithm::SHA256, key).to_string(HashFormat::Nix32, false) + (shallow ? "-shallow" : ""));
|
||||
}
|
||||
|
||||
// Returns the name of the HEAD branch.
|
||||
@@ -630,7 +629,7 @@ struct GitInputScheme : InputScheme
|
||||
if (url.scheme == "file" && !forceHttp && !isBareRepository(renderUrlPathEnsureLegal(url.path))) {
|
||||
auto path = renderUrlPathEnsureLegal(url.path);
|
||||
|
||||
if (!std::filesystem::path(path).is_absolute()) {
|
||||
if (!isAbsolute(path)) {
|
||||
warn(
|
||||
"Fetching Git repository '%s', which uses a path relative to the current directory. "
|
||||
"This is not supported and will stop working in a future release. "
|
||||
|
||||
@@ -302,7 +302,7 @@ struct MercurialInputScheme : InputScheme
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createDirs(cacheDir.parent_path());
|
||||
createDirs(dirOf(cacheDir.string()));
|
||||
runHg({"clone", "--noupdate", "--", actualUrl, cacheDir.string()});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ struct PathInputScheme : InputScheme
|
||||
std::optional<std::string> isRelative(const Input & input) const override
|
||||
{
|
||||
auto path = getStrAttr(input.attrs, "path");
|
||||
if (std::filesystem::path(path).is_absolute())
|
||||
if (isAbsolute(path))
|
||||
return std::nullopt;
|
||||
else
|
||||
return path;
|
||||
@@ -132,7 +132,7 @@ struct PathInputScheme : InputScheme
|
||||
{
|
||||
auto path = getStrAttr(input.attrs, "path");
|
||||
|
||||
if (std::filesystem::path(path).is_absolute())
|
||||
if (isAbsolute(path))
|
||||
return canonPath(path);
|
||||
|
||||
throw Error("cannot fetch input '%s' because it uses a relative path", input.to_string());
|
||||
|
||||
@@ -147,13 +147,13 @@ static std::shared_ptr<Registry> getGlobalRegistry(const Settings & settings, St
|
||||
return Registry::read(
|
||||
settings,
|
||||
[&] -> SourcePath {
|
||||
if (!std::filesystem::path(path).is_absolute()) {
|
||||
if (!isAbsolute(path)) {
|
||||
auto storePath = downloadFile(store, settings, path, "flake-registry.json").storePath;
|
||||
if (auto store2 = dynamic_cast<LocalFSStore *>(&store))
|
||||
store2->addPermRoot(storePath, (getCacheDir() / "flake-registry.json").string());
|
||||
return {store.requireStoreObjectAccessor(storePath)};
|
||||
} else {
|
||||
return SourcePath{makeFSSourceAccessor(path)}.resolveSymlinks();
|
||||
return SourcePath{getFSSourceAccessor(), CanonPath{path}}.resolveSymlinks();
|
||||
}
|
||||
}(),
|
||||
Registry::Global);
|
||||
|
||||
@@ -179,7 +179,7 @@ static DownloadTarballResult downloadTarball_(
|
||||
auto [fdTemp, path] = createTempFile("nix-zipfile");
|
||||
cleanupTemp.cancel();
|
||||
cleanupTemp = {path};
|
||||
debug("downloading '%s' into %s...", url, PathFmt(path));
|
||||
debug("downloading '%s' into '%s'...", url, path);
|
||||
{
|
||||
FdSink sink(fdTemp.get());
|
||||
source->drainInto(sink);
|
||||
|
||||
@@ -13,7 +13,7 @@ struct nix_flake_settings
|
||||
|
||||
struct nix_flake_reference_parse_flags
|
||||
{
|
||||
std::optional<std::filesystem::path> baseDirectory;
|
||||
std::optional<nix::Path> baseDirectory;
|
||||
};
|
||||
|
||||
struct nix_flake_reference
|
||||
|
||||
@@ -283,4 +283,18 @@ TEST(to_string, doesntReencodeUrl)
|
||||
ASSERT_EQ(unparsed, expected);
|
||||
}
|
||||
|
||||
TEST(parseFlakeRef, malformedGithubUrlDoesNotCrash)
|
||||
{
|
||||
experimentalFeatureSettings.experimentalFeatures.get().insert(Xp::Flakes);
|
||||
|
||||
fetchers::Settings fetchSettings;
|
||||
|
||||
// Using ref= instead of rev= with a github: URL should produce an
|
||||
// error, not an assertion failure in renderAuthorityAndPath
|
||||
// (https://github.com/NixOS/nix/issues/15196).
|
||||
EXPECT_THROW(
|
||||
parseFlakeRef(fetchSettings, "github:nixos/nixpkgs/nixpkgs.git?ref=aead170c1a49253ebfa5027010dfd89a77b73ca4"),
|
||||
Error);
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
@@ -31,7 +31,7 @@ namespace nix::flake {
|
||||
// setting name -> setting value -> allow or ignore.
|
||||
typedef std::map<std::string, std::map<std::string, bool>> TrustedList;
|
||||
|
||||
static std::filesystem::path trustedListPath()
|
||||
std::filesystem::path trustedListPath()
|
||||
{
|
||||
return getDataDir() / "trusted-settings.json";
|
||||
}
|
||||
|
||||
@@ -121,23 +121,19 @@ std::pair<FlakeRef, std::string> parsePathFlakeRefWithFragment(
|
||||
to 'baseDir'). If so, search upward to the root of the
|
||||
repo (i.e. the directory containing .git). */
|
||||
|
||||
{
|
||||
auto baseDirPath = *baseDir;
|
||||
path = absPath(path, &baseDirPath, true).string();
|
||||
}
|
||||
path = absPath(path, baseDir->string(), true);
|
||||
|
||||
if (isFlake) {
|
||||
|
||||
if (!S_ISDIR(lstat(path).st_mode)) {
|
||||
if (baseNameOf(path) == "flake.nix") {
|
||||
// Be gentle with people who accidentally write `/foo/bar/flake.nix` instead of `/foo/bar`
|
||||
auto parentPath = std::filesystem::path(path).parent_path().string();
|
||||
warn(
|
||||
"Path '%s' should point at the directory containing the 'flake.nix' file, not the file itself. "
|
||||
"Pretending that you meant '%s'",
|
||||
path,
|
||||
parentPath);
|
||||
path = parentPath;
|
||||
dirOf(path));
|
||||
path = dirOf(path);
|
||||
} else {
|
||||
throw BadURL("path '%s' is not a flake (because it's not a directory)", path);
|
||||
}
|
||||
@@ -161,7 +157,7 @@ std::pair<FlakeRef, std::string> parsePathFlakeRefWithFragment(
|
||||
if (lstat(path).st_dev != device)
|
||||
throw Error("unable to find a flake before encountering filesystem boundary at '%s'", path);
|
||||
}
|
||||
path = std::filesystem::path(path).parent_path().string();
|
||||
path = dirOf(path);
|
||||
}
|
||||
if (!found)
|
||||
throw BadURL("could not find a flake.nix file");
|
||||
@@ -196,12 +192,12 @@ std::pair<FlakeRef, std::string> parsePathFlakeRefWithFragment(
|
||||
}
|
||||
|
||||
subdir = std::string(baseNameOf(flakeRoot)) + (subdir.empty() ? "" : "/" + subdir);
|
||||
flakeRoot = std::filesystem::path(flakeRoot).parent_path().string();
|
||||
flakeRoot = dirOf(flakeRoot);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!preserveRelativePaths && !std::filesystem::path(path).is_absolute())
|
||||
if (!preserveRelativePaths && !isAbsolute(path))
|
||||
throw BadURL("flake reference '%s' is not an absolute path", url);
|
||||
}
|
||||
|
||||
@@ -209,7 +205,7 @@ std::pair<FlakeRef, std::string> parsePathFlakeRefWithFragment(
|
||||
fetchSettings,
|
||||
{
|
||||
.scheme = "path",
|
||||
.authority = ParsedURL::Authority{},
|
||||
.authority = isAbsolute(path) ? std::optional{ParsedURL::Authority{}} : std::nullopt,
|
||||
.path = splitString<std::vector<std::string>>(path, "/"),
|
||||
.query = query,
|
||||
.fragment = fragment,
|
||||
@@ -255,10 +251,8 @@ std::optional<std::pair<FlakeRef, std::string>> parseURLFlakeRef(
|
||||
if (baseDir && (parsed.scheme == "path" || parsed.scheme == "git+file")) {
|
||||
/* Here we know that the path must not contain encoded '/' or NUL bytes. */
|
||||
auto path = renderUrlPathEnsureLegal(parsed.path);
|
||||
if (!std::filesystem::path(path).is_absolute()) {
|
||||
auto baseDirPath = *baseDir;
|
||||
parsed.path = splitString<std::vector<std::string>>(absPath(path, &baseDirPath).string(), "/");
|
||||
}
|
||||
if (!isAbsolute(path))
|
||||
parsed.path = splitString<std::vector<std::string>>(absPath(path, baseDir->string()), "/");
|
||||
}
|
||||
return fromParsedURL(fetchSettings, std::move(parsed), isFlake);
|
||||
} catch (BadURL &) {
|
||||
|
||||
@@ -51,7 +51,7 @@ protected:
|
||||
// resolve any symlinks in i.e. on macOS /tmp -> /private/tmp
|
||||
// because this is not allowed for a nix store.
|
||||
auto tmpl =
|
||||
nix::absPath(std::filesystem::path(nix::defaultTempDir()) / "tests_nix-store.XXXXXX", nullptr, true);
|
||||
nix::absPath(std::filesystem::path(nix::defaultTempDir()) / "tests_nix-store.XXXXXX", std::nullopt, true);
|
||||
nixDir = mkdtemp((char *) tmpl.c_str());
|
||||
#endif
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ using testing::Eq;
|
||||
using testing::Field;
|
||||
using testing::SizeIs;
|
||||
|
||||
namespace nix::fs {
|
||||
using namespace std::filesystem;
|
||||
}
|
||||
|
||||
using namespace nix;
|
||||
|
||||
TEST(machines, getMachinesWithEmptyBuilders)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ BinaryCacheStore::BinaryCacheStore(Config & config)
|
||||
: config{config}
|
||||
{
|
||||
if (config.secretKeyFile != "")
|
||||
signers.push_back(std::make_unique<LocalSigner>(SecretKey{readFile(config.secretKeyFile.get())}));
|
||||
signers.push_back(std::make_unique<LocalSigner>(SecretKey{readFile(config.secretKeyFile)}));
|
||||
|
||||
if (config.secretKeyFiles != "") {
|
||||
std::stringstream ss(config.secretKeyFiles);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "nix/store/local-store.hh" // TODO remove, along with remaining downcasts
|
||||
#include "nix/store/globals.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
@@ -184,6 +185,82 @@ struct LogFile
|
||||
~LogFile();
|
||||
};
|
||||
|
||||
struct LocalBuildRejection
|
||||
{
|
||||
bool maxJobsZero = false;
|
||||
|
||||
struct NoLocalStore
|
||||
{};
|
||||
|
||||
/**
|
||||
* We have a local store, but we don't have an external derivation builder (which is fine), if we did, it'd be
|
||||
* fine because we would not care about platforms and features then. Since we don't, we either have the wrong
|
||||
* platform, or we are missing some system features.
|
||||
*/
|
||||
struct WrongLocalStore
|
||||
{
|
||||
template<typename T>
|
||||
struct Pair
|
||||
{
|
||||
T derivation;
|
||||
T localStore;
|
||||
};
|
||||
|
||||
std::optional<Pair<std::string>> badPlatform;
|
||||
std::optional<Pair<StringSet>> missingFeatures;
|
||||
};
|
||||
|
||||
std::variant<NoLocalStore, WrongLocalStore> rejection;
|
||||
};
|
||||
|
||||
static BuildError reject(const LocalBuildRejection & rejection, std::string_view thingCannotBuild)
|
||||
{
|
||||
if (std::get_if<LocalBuildRejection::NoLocalStore>(&rejection.rejection))
|
||||
return BuildError(
|
||||
BuildResult::Failure::InputRejected,
|
||||
"Unable to build with a primary store that isn't a local store; "
|
||||
"either pass a different '--store' or enable remote builds.\n\n"
|
||||
"For more information check 'man nix.conf' and search for '/machines'.");
|
||||
|
||||
auto & wrongStore = std::get<LocalBuildRejection::WrongLocalStore>(rejection.rejection);
|
||||
|
||||
std::string msg = fmt("Cannot build '%s'.", Magenta(thingCannotBuild));
|
||||
|
||||
if (rejection.maxJobsZero)
|
||||
msg += "\nReason: " ANSI_RED "local builds are disabled" ANSI_NORMAL
|
||||
" (max-jobs = 0)"
|
||||
"\nHint: set 'max-jobs' to a non-zero value to enable local builds, "
|
||||
"or configure remote builders via 'builders'";
|
||||
|
||||
if (wrongStore.badPlatform)
|
||||
msg +=
|
||||
fmt("\nReason: " ANSI_RED "platform mismatch" ANSI_NORMAL
|
||||
"\nRequired system: '%s'"
|
||||
"\nCurrent system: '%s'",
|
||||
Magenta(wrongStore.badPlatform->derivation),
|
||||
Magenta(wrongStore.badPlatform->localStore));
|
||||
|
||||
if (wrongStore.missingFeatures)
|
||||
msg +=
|
||||
fmt("\nReason: " ANSI_RED "missing system features" ANSI_NORMAL
|
||||
"\nRequired features: {%s}"
|
||||
"\nAvailable features: {%s}",
|
||||
concatStringsSep(", ", wrongStore.missingFeatures->derivation),
|
||||
concatStringsSep<StringSet>(", ", wrongStore.missingFeatures->localStore));
|
||||
|
||||
if (wrongStore.badPlatform || wrongStore.missingFeatures) {
|
||||
// since aarch64-darwin has Rosetta 2, this user can actually run x86_64-darwin on their
|
||||
// hardware - we should tell them to run the command to install Rosetta
|
||||
if (wrongStore.badPlatform && wrongStore.badPlatform->derivation == "x86_64-darwin"
|
||||
&& wrongStore.badPlatform->localStore == "aarch64-darwin")
|
||||
msg +=
|
||||
fmt("\nNote: run `%s` to run programs for x86_64-darwin",
|
||||
Magenta("/usr/sbin/softwareupdate --install-rosetta && launchctl stop org.nixos.nix-daemon"));
|
||||
}
|
||||
|
||||
return BuildError(BuildResult::Failure::InputRejected, std::move(msg));
|
||||
}
|
||||
|
||||
Goal::Co DerivationBuildingGoal::tryToBuild(StorePathSet inputPaths)
|
||||
{
|
||||
auto drvOptions = [&] {
|
||||
@@ -245,29 +322,57 @@ Goal::Co DerivationBuildingGoal::tryToBuild(StorePathSet inputPaths)
|
||||
}
|
||||
checkPathValidity(initialOutputs);
|
||||
|
||||
/**
|
||||
* Activity that denotes waiting for a lock.
|
||||
*/
|
||||
std::unique_ptr<Activity> actLock;
|
||||
auto localBuildResult = [&]() -> std::variant<LocalBuildCapability, LocalBuildRejection> {
|
||||
bool maxJobsZero = worker.settings.maxBuildJobs.get() == 0;
|
||||
|
||||
/**
|
||||
* Locks on (fixed) output paths.
|
||||
*/
|
||||
PathLocks outputLocks;
|
||||
auto * localStoreP = dynamic_cast<LocalStore *>(&worker.store);
|
||||
if (!localStoreP)
|
||||
return LocalBuildRejection{.maxJobsZero = maxJobsZero, .rejection = LocalBuildRejection::NoLocalStore{}};
|
||||
|
||||
bool useHook;
|
||||
/**
|
||||
* Now that we've decided we can't / won't do a remote build, check
|
||||
* that we can in fact build locally. First see if there is an
|
||||
* external builder for a "semi-local build". If there is, prefer to
|
||||
* use that. If there is not, then check if we can do a "true" local
|
||||
* build.
|
||||
*/
|
||||
auto * ext = settings.getLocalSettings().findExternalDerivationBuilderIfSupported(*drv);
|
||||
|
||||
const ExternalBuilder * externalBuilder = nullptr;
|
||||
if (ext)
|
||||
return LocalBuildCapability{*localStoreP, ext};
|
||||
|
||||
while (true) {
|
||||
using WrongLocalStore = LocalBuildRejection::WrongLocalStore;
|
||||
|
||||
WrongLocalStore wrongStore;
|
||||
|
||||
if (drv->platform != settings.thisSystem.get() && !settings.extraPlatforms.get().count(drv->platform)
|
||||
&& !drv->isBuiltin())
|
||||
wrongStore.badPlatform = WrongLocalStore::Pair<std::string>{drv->platform, settings.thisSystem.get()};
|
||||
|
||||
{
|
||||
auto required = drvOptions.getRequiredSystemFeatures(*drv);
|
||||
auto & available = worker.store.config.systemFeatures.get();
|
||||
if (std::ranges::any_of(required, [&](const std::string & f) { return !available.count(f); }))
|
||||
wrongStore.missingFeatures = WrongLocalStore::Pair<StringSet>{required, available};
|
||||
}
|
||||
|
||||
if (maxJobsZero || wrongStore.badPlatform || wrongStore.missingFeatures)
|
||||
return LocalBuildRejection{.maxJobsZero = maxJobsZero, .rejection = std::move(wrongStore)};
|
||||
|
||||
return LocalBuildCapability{*localStoreP, ext};
|
||||
}();
|
||||
|
||||
auto acquireResources = [&](bool & done, PathLocks & outputLocks) -> Goal::Co {
|
||||
trace("trying to build");
|
||||
|
||||
/* Obtain locks on all output paths, if the paths are known a priori.
|
||||
|
||||
The locks are automatically released when we exit this function or Nix
|
||||
crashes. If we can't acquire the lock, then continue; hopefully some
|
||||
other goal can start a build, and if not, the main loop will sleep a few
|
||||
seconds and then retry this goal. */
|
||||
/**
|
||||
* Output paths to acquire locks on, if known a priori.
|
||||
*
|
||||
* The locks are automatically released when the caller's `PathLocks` goes
|
||||
* out of scope, including on exception unwinding. If we can't acquire the lock, then
|
||||
* continue; hopefully some other goal can start a build, and if not, the
|
||||
* main loop will sleep a few seconds and then retry this goal.
|
||||
*/
|
||||
std::set<std::filesystem::path> lockFiles;
|
||||
/* FIXME: Should lock something like the drv itself so we don't build same
|
||||
CA drv concurrently */
|
||||
@@ -311,7 +416,8 @@ Goal::Co DerivationBuildingGoal::tryToBuild(StorePathSet inputPaths)
|
||||
debug("skipping build of derivation '%s', someone beat us to it", worker.store.printStorePath(drvPath));
|
||||
outputLocks.setDeletion(true);
|
||||
outputLocks.unlock();
|
||||
co_return doneSuccess(BuildResult::Success::AlreadyValid, std::move(validOutputs));
|
||||
done = true;
|
||||
co_return Return{};
|
||||
}
|
||||
|
||||
/* If any of the outputs already exist but are not valid, delete
|
||||
@@ -326,112 +432,133 @@ Goal::Co DerivationBuildingGoal::tryToBuild(StorePathSet inputPaths)
|
||||
}
|
||||
}
|
||||
|
||||
bool canBuildLocally = [&] {
|
||||
if (drv->platform != settings.thisSystem.get() && !settings.extraPlatforms.get().count(drv->platform)
|
||||
&& !drv->isBuiltin())
|
||||
return false;
|
||||
co_return Return{};
|
||||
};
|
||||
|
||||
if (worker.settings.maxBuildJobs.get() == 0 && !drv->isBuiltin())
|
||||
return false;
|
||||
auto tryHookLoop = [&](bool & valid) -> Goal::Co {
|
||||
{
|
||||
PathLocks outputLocks;
|
||||
co_await acquireResources(valid, outputLocks);
|
||||
if (valid)
|
||||
co_return doneSuccess(BuildResult::Success::AlreadyValid, checkPathValidity(initialOutputs).second);
|
||||
|
||||
for (auto & feature : drvOptions.getRequiredSystemFeatures(*drv))
|
||||
if (!worker.store.config.systemFeatures.get().count(feature))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}();
|
||||
|
||||
/* Don't do a remote build if the derivation has the attribute
|
||||
`preferLocalBuild' set. Also, check and repair modes are only
|
||||
supported for local builds. */
|
||||
bool buildLocally = (buildMode != bmNormal || (drvOptions.preferLocalBuild && canBuildLocally))
|
||||
&& worker.settings.maxBuildJobs.get() != 0;
|
||||
|
||||
if (buildLocally) {
|
||||
useHook = false;
|
||||
} else {
|
||||
switch (tryBuildHook(drvOptions)) {
|
||||
case rpAccept:
|
||||
/* Yes, it has started doing so. Wait until we get
|
||||
EOF from the hook. */
|
||||
useHook = true;
|
||||
break;
|
||||
valid = true;
|
||||
co_return buildWithHook(
|
||||
std::move(inputPaths), std::move(initialOutputs), std::move(drvOptions), std::move(outputLocks));
|
||||
case rpDecline:
|
||||
// We should do it ourselves.
|
||||
co_return Return{};
|
||||
case rpPostpone:
|
||||
/* Not now; wait until at least one child finishes or
|
||||
the wake-up timeout expires. */
|
||||
if (!actLock)
|
||||
actLock = std::make_unique<Activity>(
|
||||
*logger,
|
||||
lvlWarn,
|
||||
actBuildWaiting,
|
||||
fmt("waiting for a machine to build '%s'", Magenta(worker.store.printStorePath(drvPath))));
|
||||
outputLocks.unlock();
|
||||
co_await waitForAWhile();
|
||||
continue;
|
||||
case rpDecline:
|
||||
/* We should do it ourselves.
|
||||
|
||||
Now that we've decided we can't / won't do a remote build, check
|
||||
that we can in fact build locally. First see if there is an
|
||||
external builder for a "semi-local build". If there is, prefer to
|
||||
use that. If there is not, then check if we can do a "true" local
|
||||
build. */
|
||||
|
||||
externalBuilder = settings.findExternalDerivationBuilderIfSupported(*drv);
|
||||
|
||||
if (!externalBuilder && !canBuildLocally) {
|
||||
auto msg =
|
||||
fmt("Cannot build '%s'.\n"
|
||||
"Reason: " ANSI_RED "required system or feature not available" ANSI_NORMAL
|
||||
"\n"
|
||||
"Required system: '%s' with features {%s}\n"
|
||||
"Current system: '%s' with features {%s}",
|
||||
Magenta(worker.store.printStorePath(drvPath)),
|
||||
Magenta(drv->platform),
|
||||
concatStringsSep(", ", drvOptions.getRequiredSystemFeatures(*drv)),
|
||||
Magenta(settings.thisSystem),
|
||||
concatStringsSep<StringSet>(", ", worker.store.Store::config.systemFeatures));
|
||||
|
||||
// since aarch64-darwin has Rosetta 2, this user can actually run x86_64-darwin on their hardware -
|
||||
// we should tell them to run the command to install Darwin 2
|
||||
if (drv->platform == "x86_64-darwin" && settings.thisSystem == "aarch64-darwin")
|
||||
msg += fmt(
|
||||
"\nNote: run `%s` to run programs for x86_64-darwin",
|
||||
Magenta(
|
||||
"/usr/sbin/softwareupdate --install-rosetta && launchctl stop org.nixos.nix-daemon"));
|
||||
|
||||
outputLocks.unlock();
|
||||
co_return doneFailure({BuildResult::Failure::InputRejected, std::move(msg)});
|
||||
}
|
||||
useHook = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
actLock.reset();
|
||||
PathLocks outputLocks;
|
||||
{
|
||||
// First attempt was postponed. Retry in a loop with an activity
|
||||
// that lives until accept or decline.
|
||||
Activity act(
|
||||
*logger,
|
||||
lvlWarn,
|
||||
actBuildWaiting,
|
||||
fmt("waiting for a machine to build '%s'", Magenta(worker.store.printStorePath(drvPath))));
|
||||
|
||||
if (useHook) {
|
||||
co_return buildWithHook(
|
||||
std::move(inputPaths), std::move(initialOutputs), std::move(drvOptions), std::move(outputLocks));
|
||||
} else if (auto * localStoreP = dynamic_cast<LocalStore *>(&worker.store)) {
|
||||
co_return buildLocally(
|
||||
*localStoreP,
|
||||
std::move(inputPaths),
|
||||
std::move(initialOutputs),
|
||||
std::move(drvOptions),
|
||||
std::move(outputLocks),
|
||||
externalBuilder);
|
||||
while (true) {
|
||||
co_await waitForAWhile();
|
||||
co_await acquireResources(valid, outputLocks);
|
||||
if (valid)
|
||||
break;
|
||||
|
||||
switch (tryBuildHook(drvOptions)) {
|
||||
case rpAccept:
|
||||
/* Yes, it has started doing so. Wait until we get
|
||||
EOF from the hook. */
|
||||
break;
|
||||
case rpPostpone:
|
||||
/* Not now; wait until at least one child finishes or
|
||||
the wake-up timeout expires. */
|
||||
outputLocks.unlock();
|
||||
continue;
|
||||
case rpDecline:
|
||||
// We should do it ourselves.
|
||||
co_return Return{};
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
co_return doneSuccess(BuildResult::Success::AlreadyValid, checkPathValidity(initialOutputs).second);
|
||||
} else {
|
||||
co_return buildWithHook(
|
||||
std::move(inputPaths), std::move(initialOutputs), std::move(drvOptions), std::move(outputLocks));
|
||||
}
|
||||
};
|
||||
|
||||
auto tryBuildLocally = [&](bool & valid) -> Goal::Co {
|
||||
if (auto * cap = std::get_if<LocalBuildCapability>(&localBuildResult)) {
|
||||
PathLocks outputLocks;
|
||||
co_await acquireResources(valid, outputLocks);
|
||||
if (valid)
|
||||
co_return doneSuccess(BuildResult::Success::AlreadyValid, checkPathValidity(initialOutputs).second);
|
||||
|
||||
valid = true;
|
||||
co_return buildLocally(
|
||||
*cap, std::move(inputPaths), std::move(initialOutputs), std::move(drvOptions), std::move(outputLocks));
|
||||
}
|
||||
|
||||
co_return Return{};
|
||||
};
|
||||
|
||||
if (buildMode != bmNormal) {
|
||||
// Check and repair modes operate on the state of this store specifically,
|
||||
// so they must always build locally.
|
||||
bool valid = false;
|
||||
co_await tryBuildLocally(valid);
|
||||
if (valid)
|
||||
co_return Return{};
|
||||
} else if (drvOptions.preferLocalBuild) {
|
||||
// Local is preferred, so try it first. If it's not available, fall back to the hook.
|
||||
{
|
||||
bool valid = false;
|
||||
co_await tryBuildLocally(valid);
|
||||
if (valid)
|
||||
co_return Return{};
|
||||
}
|
||||
{
|
||||
bool valid = false;
|
||||
co_await tryHookLoop(valid);
|
||||
if (valid)
|
||||
co_return Return{};
|
||||
}
|
||||
} else {
|
||||
throw Error(
|
||||
R"(
|
||||
Unable to build with a primary store that isn't a local store;
|
||||
either pass a different '--store' or enable remote builds.
|
||||
|
||||
For more information check 'man nix.conf' and search for '/machines'.
|
||||
)");
|
||||
// Default preference is a remote build: they tend to be faster and preserve local
|
||||
// resources for other tasks. Fall back to local if no remote is available.
|
||||
{
|
||||
bool valid = false;
|
||||
co_await tryHookLoop(valid);
|
||||
if (valid)
|
||||
co_return Return{};
|
||||
}
|
||||
{
|
||||
bool valid = false;
|
||||
co_await tryBuildLocally(valid);
|
||||
if (valid)
|
||||
co_return Return{};
|
||||
}
|
||||
}
|
||||
|
||||
std::string storePath = worker.store.printStorePath(drvPath);
|
||||
auto * rejection = std::get_if<LocalBuildRejection>(&localBuildResult);
|
||||
assert(rejection);
|
||||
co_return doneFailure(reject(*rejection, storePath));
|
||||
}
|
||||
|
||||
Goal::Co DerivationBuildingGoal::buildWithHook(
|
||||
@@ -639,12 +766,11 @@ Goal::Co DerivationBuildingGoal::buildWithHook(
|
||||
}
|
||||
|
||||
Goal::Co DerivationBuildingGoal::buildLocally(
|
||||
LocalStore & localStore,
|
||||
LocalBuildCapability localBuildCap,
|
||||
StorePathSet inputPaths,
|
||||
std::map<std::string, InitialOutput> initialOutputs,
|
||||
DerivationOptions<StorePath> drvOptions,
|
||||
PathLocks outputLocks,
|
||||
const ExternalBuilder * externalBuilder)
|
||||
PathLocks outputLocks)
|
||||
{
|
||||
co_await yield();
|
||||
|
||||
@@ -728,7 +854,7 @@ Goal::Co DerivationBuildingGoal::buildLocally(
|
||||
};
|
||||
|
||||
decltype(DerivationBuilderParams::defaultPathsInChroot) defaultPathsInChroot =
|
||||
localStore.config->getLocalSettings().sandboxPaths.get();
|
||||
localBuildCap.localStore.config->getLocalSettings().sandboxPaths.get();
|
||||
DesugaredEnv desugaredEnv;
|
||||
|
||||
/* Add the closure of store paths to the chroot. */
|
||||
@@ -770,14 +896,14 @@ Goal::Co DerivationBuildingGoal::buildLocally(
|
||||
|
||||
/* If we have to wait and retry (see below), then `builder` will
|
||||
already be created, so we don't need to create it again. */
|
||||
builder = externalBuilder
|
||||
builder = localBuildCap.externalBuilder
|
||||
? makeExternalDerivationBuilder(
|
||||
localStore,
|
||||
localBuildCap.localStore,
|
||||
std::make_unique<DerivationBuildingGoalCallbacks>(*this, openLogFile, closeLogFile),
|
||||
std::move(params),
|
||||
*externalBuilder)
|
||||
*localBuildCap.externalBuilder)
|
||||
: makeDerivationBuilder(
|
||||
localStore,
|
||||
localBuildCap.localStore,
|
||||
std::make_unique<DerivationBuildingGoalCallbacks>(*this, openLogFile, closeLogFile),
|
||||
std::move(params));
|
||||
}
|
||||
@@ -1052,15 +1178,15 @@ LogFile::LogFile(Store & store, const StorePath & drvPath, const LogFileSettings
|
||||
|
||||
auto baseName = std::string(baseNameOf(store.printStorePath(drvPath)));
|
||||
|
||||
std::filesystem::path logDir;
|
||||
Path logDir;
|
||||
if (auto localStore = dynamic_cast<LocalStore *>(&store))
|
||||
logDir = localStore->config->logDir.get();
|
||||
logDir = localStore->config->logDir;
|
||||
else
|
||||
logDir = logSettings.nixLogDir;
|
||||
auto dir = logDir / LocalFSStore::drvsLogDir / baseName.substr(0, 2);
|
||||
logDir = logSettings.nixLogDir.string();
|
||||
Path dir = fmt("%s/%s/%s/", logDir, LocalFSStore::drvsLogDir, baseName.substr(0, 2));
|
||||
createDirs(dir);
|
||||
|
||||
auto logFileName = dir / (baseName.substr(2) + (logSettings.compressLog ? ".bz2" : ""));
|
||||
Path logFileName = fmt("%s/%s%s", dir, baseName.substr(2), logSettings.compressLog ? ".bz2" : "");
|
||||
|
||||
fd = openNewFileForWrite(
|
||||
logFileName,
|
||||
@@ -1070,7 +1196,7 @@ LogFile::LogFile(Store & store, const StorePath & drvPath, const LogFileSettings
|
||||
.followSymlinksOnTruncate = true, /* FIXME: Probably shouldn't follow symlinks. */
|
||||
});
|
||||
if (!fd)
|
||||
throw SysError("creating log file %1%", PathFmt(logFileName));
|
||||
throw SysError("creating log file '%1%'", logFileName);
|
||||
|
||||
fileSink = std::make_shared<FdSink>(fd.get());
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ void checkOutputs(
|
||||
const decltype(DerivationOptions<StorePath>::outputChecks) & outputChecks,
|
||||
const std::map<std::string, ValidPathInfo> & outputs)
|
||||
{
|
||||
std::map<std::filesystem::path, const ValidPathInfo &> outputsByPath;
|
||||
std::map<Path, const ValidPathInfo &> outputsByPath;
|
||||
for (auto & output : outputs)
|
||||
outputsByPath.emplace(store.printStorePath(output.second.path), output.second);
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -20,14 +20,14 @@ namespace {
|
||||
|
||||
struct State
|
||||
{
|
||||
std::map<std::filesystem::path, int> priorities;
|
||||
std::map<Path, int> priorities;
|
||||
unsigned long symlinks = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/* For each activated package, create symlinks */
|
||||
static void createLinks(State & state, const std::filesystem::path & srcDir, const std::filesystem::path & dstDir, int priority)
|
||||
static void createLinks(State & state, const Path & srcDir, const Path & dstDir, int priority)
|
||||
{
|
||||
DirectoryIterator srcFiles;
|
||||
|
||||
@@ -35,7 +35,7 @@ static void createLinks(State & state, const std::filesystem::path & srcDir, con
|
||||
srcFiles = DirectoryIterator{srcDir};
|
||||
} catch (SystemError & e) {
|
||||
if (e.is(std::errc::not_a_directory)) {
|
||||
warn("not including %s in the user environment because it's not a directory", PathFmt(srcDir));
|
||||
warn("not including '%s' in the user environment because it's not a directory", srcDir);
|
||||
return;
|
||||
}
|
||||
throw;
|
||||
@@ -78,9 +78,9 @@ static void createLinks(State & state, const std::filesystem::path & srcDir, con
|
||||
} else if (S_ISLNK(dstSt.st_mode)) {
|
||||
auto target = canonPath(dstFile, true);
|
||||
if (!S_ISDIR(lstat(target).st_mode))
|
||||
throw Error("collision between %1% and non-directory %2%", PathFmt(srcFile), PathFmt(target));
|
||||
throw Error("collision between '%1%' and non-directory '%2%'", srcFile, target);
|
||||
if (unlink(dstFile.c_str()) == -1)
|
||||
throw SysError("unlinking %1%", PathFmt(dstFile));
|
||||
throw SysError("unlinking '%1%'", dstFile);
|
||||
if (mkdir(
|
||||
dstFile.c_str()
|
||||
#ifndef _WIN32 // TODO abstract mkdir perms for Windows
|
||||
@@ -108,7 +108,7 @@ static void createLinks(State & state, const std::filesystem::path & srcDir, con
|
||||
if (prevPriority < priority)
|
||||
continue;
|
||||
if (unlink(dstFile.c_str()) == -1)
|
||||
throw SysError("unlinking %1%", PathFmt(dstFile));
|
||||
throw SysError("unlinking '%1%'", dstFile);
|
||||
} else if (S_ISDIR(dstSt.st_mode))
|
||||
throw Error("collision between non-directory '%1%' and directory '%2%'", srcFile, dstFile);
|
||||
}
|
||||
@@ -120,13 +120,13 @@ static void createLinks(State & state, const std::filesystem::path & srcDir, con
|
||||
}
|
||||
}
|
||||
|
||||
void buildProfile(const std::filesystem::path & out, Packages && pkgs)
|
||||
void buildProfile(const Path & out, Packages && pkgs)
|
||||
{
|
||||
State state;
|
||||
|
||||
PathSet done, postponed;
|
||||
|
||||
auto addPkg = [&](const std::filesystem::path & pkgDir, int priority) {
|
||||
auto addPkg = [&](const Path & pkgDir, int priority) {
|
||||
if (!done.insert(pkgDir).second)
|
||||
return;
|
||||
createLinks(state, pkgDir, out, priority);
|
||||
|
||||
@@ -680,21 +680,21 @@ static void performOp(
|
||||
"you are not privileged to create perm roots\n\n"
|
||||
"hint: you can just do this client-side without special privileges, and probably want to do that instead.");
|
||||
auto storePath = WorkerProto::Serialise<StorePath>::read(*store, rconn);
|
||||
std::filesystem::path gcRoot = absPath(readString(conn.from));
|
||||
Path gcRoot = absPath(readString(conn.from));
|
||||
logger->startWork();
|
||||
auto & localFSStore = require<LocalFSStore>(*store);
|
||||
localFSStore.addPermRoot(storePath, gcRoot.string());
|
||||
localFSStore.addPermRoot(storePath, gcRoot);
|
||||
logger->stopWork();
|
||||
conn.to << gcRoot.string();
|
||||
conn.to << gcRoot;
|
||||
break;
|
||||
}
|
||||
|
||||
case WorkerProto::Op::AddIndirectRoot: {
|
||||
std::filesystem::path path = absPath(readString(conn.from));
|
||||
Path path = absPath(readString(conn.from));
|
||||
|
||||
logger->startWork();
|
||||
auto & indirectRootStore = require<IndirectRootStore>(*store);
|
||||
indirectRootStore.addIndirectRoot(path.string());
|
||||
indirectRootStore.addIndirectRoot(path);
|
||||
logger->stopWork();
|
||||
|
||||
conn.to << 1;
|
||||
|
||||
@@ -136,6 +136,12 @@ StorePath Store::writeDerivation(const Derivation & drv, RepairFlag repair)
|
||||
{
|
||||
auto [suffix, contents, references, path] = infoForDerivation(*this, drv);
|
||||
|
||||
/* In case the derivation is already valid, we bail out early since that's
|
||||
faster. But we need to make sure that the derivation has a corresponding
|
||||
temproot. It is added by the remote in addToStoreFromDump, but we'd like
|
||||
to avoid sending a lot of drv contents to the daemon. */
|
||||
addTempRoot(path);
|
||||
|
||||
if (isValidPath(path) && !repair)
|
||||
return path;
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
+62
-38
@@ -3,6 +3,7 @@
|
||||
#include "nix/store/local-gc.hh"
|
||||
#include "nix/store/local-store.hh"
|
||||
#include "nix/store/path.hh"
|
||||
#include "nix/util/configuration.hh"
|
||||
#include "nix/util/finally.hh"
|
||||
#include "nix/util/unix-domain-socket.hh"
|
||||
#include "nix/util/signals.hh"
|
||||
@@ -40,8 +41,8 @@ static std::string gcRootsDir = "gcroots";
|
||||
void LocalStore::addIndirectRoot(const Path & path)
|
||||
{
|
||||
std::string hash = hashString(HashAlgorithm::SHA1, path).to_string(HashFormat::Nix32, false);
|
||||
auto realRoot = canonPath(config->stateDir.get() / gcRootsDir / "auto" / hash);
|
||||
makeSymlink(realRoot.string(), path);
|
||||
Path realRoot = canonPath(fmt("%1%/%2%/auto/%3%", config->stateDir, gcRootsDir, hash));
|
||||
makeSymlink(realRoot, path);
|
||||
}
|
||||
|
||||
void LocalStore::createTempRootsFile()
|
||||
@@ -60,7 +61,7 @@ void LocalStore::createTempRootsFile()
|
||||
|
||||
*fdTempRoots = openLockFile(fnTempRoots, true);
|
||||
|
||||
debug("acquiring write lock on %s", PathFmt(fnTempRoots));
|
||||
debug("acquiring write lock on '%s'", fnTempRoots);
|
||||
lockFile(fdTempRoots->get(), ltWrite, true);
|
||||
|
||||
/* Check whether the garbage collector didn't get in our
|
||||
@@ -165,11 +166,11 @@ void LocalStore::findTempRoots(Roots & tempRoots, bool censor)
|
||||
// those to keep the directory alive.
|
||||
continue;
|
||||
}
|
||||
auto path = i.path();
|
||||
Path path = i.path().string();
|
||||
|
||||
pid_t pid = std::stoi(name);
|
||||
|
||||
debug("reading temporary root file %1%", PathFmt(path));
|
||||
debug("reading temporary root file '%1%'", path);
|
||||
AutoCloseFD fd(toDescriptor(open(
|
||||
path.c_str(),
|
||||
#ifndef _WIN32
|
||||
@@ -181,14 +182,14 @@ void LocalStore::findTempRoots(Roots & tempRoots, bool censor)
|
||||
/* It's okay if the file has disappeared. */
|
||||
if (errno == ENOENT)
|
||||
continue;
|
||||
throw SysError("opening temporary roots file %1%", PathFmt(path));
|
||||
throw SysError("opening temporary roots file '%1%'", path);
|
||||
}
|
||||
|
||||
/* Try to acquire a write lock without blocking. This can
|
||||
only succeed if the owning process has died. In that case
|
||||
we don't care about its temporary roots. */
|
||||
if (lockFile(fd.get(), ltWrite, false)) {
|
||||
printInfo("removing stale temporary roots file %1%", PathFmt(path));
|
||||
printInfo("removing stale temporary roots file '%1%'", path);
|
||||
unlink(path.c_str());
|
||||
writeFull(fd.get(), "d");
|
||||
continue;
|
||||
@@ -201,7 +202,7 @@ void LocalStore::findTempRoots(Roots & tempRoots, bool censor)
|
||||
std::string::size_type pos = 0, end;
|
||||
|
||||
while ((end = contents.find((char) 0, pos)) != std::string::npos) {
|
||||
std::string root(contents, pos, end - pos);
|
||||
Path root(contents, pos, end - pos);
|
||||
debug("got temporary root '%s'", root);
|
||||
tempRoots[parseStorePath(root)].emplace(censor ? censored : fmt("{temp:%d}", pid));
|
||||
pos = end + 1;
|
||||
@@ -211,13 +212,13 @@ void LocalStore::findTempRoots(Roots & tempRoots, bool censor)
|
||||
|
||||
void LocalStore::findRoots(const Path & path, std::filesystem::file_type type, Roots & roots)
|
||||
{
|
||||
auto foundRoot = [&](const std::filesystem::path & path, const std::filesystem::path & target) {
|
||||
auto foundRoot = [&](const Path & path, const Path & target) {
|
||||
try {
|
||||
auto storePath = toStorePath(target.string()).first;
|
||||
auto storePath = toStorePath(target).first;
|
||||
if (isValidPath(storePath))
|
||||
roots[std::move(storePath)].emplace(path.string());
|
||||
roots[std::move(storePath)].emplace(path);
|
||||
else
|
||||
printInfo("skipping invalid root from %1% to %2%", PathFmt(path), PathFmt(target));
|
||||
printInfo("skipping invalid root from '%1%' to '%2%'", path, target);
|
||||
} catch (BadStorePath &) {
|
||||
}
|
||||
};
|
||||
@@ -235,24 +236,23 @@ void LocalStore::findRoots(const Path & path, std::filesystem::file_type type, R
|
||||
}
|
||||
|
||||
else if (type == std::filesystem::file_type::symlink) {
|
||||
auto target = readLink(path);
|
||||
if (isInStore(target.string()))
|
||||
Path target = readLink(path);
|
||||
if (isInStore(target))
|
||||
foundRoot(path, target);
|
||||
|
||||
/* Handle indirect roots. */
|
||||
else {
|
||||
auto parentPath = std::filesystem::path(path).parent_path();
|
||||
target = absPath(target, &parentPath);
|
||||
if (!pathExists(target.string())) {
|
||||
target = absPath(target, dirOf(path));
|
||||
if (!pathExists(target)) {
|
||||
if (isInDir(path, std::filesystem::path{config->stateDir.get()} / gcRootsDir / "auto")) {
|
||||
printInfo("removing stale link from %1% to %2%", PathFmt(path), PathFmt(target));
|
||||
printInfo("removing stale link from '%1%' to '%2%'", path, target);
|
||||
unlink(path.c_str());
|
||||
}
|
||||
} else {
|
||||
if (!std::filesystem::is_symlink(target))
|
||||
return;
|
||||
auto target2 = readLink(target);
|
||||
if (isInStore(target2.string()))
|
||||
Path target2 = readLink(target);
|
||||
if (isInStore(target2))
|
||||
foundRoot(target, target2);
|
||||
}
|
||||
}
|
||||
@@ -288,8 +288,8 @@ void LocalStore::findRoots(const Path & path, std::filesystem::file_type type, R
|
||||
void LocalStore::findRootsNoTemp(Roots & roots, bool censor)
|
||||
{
|
||||
/* Process direct roots in {gcroots,profiles}. */
|
||||
findRoots((config->stateDir.get() / gcRootsDir).string(), std::filesystem::file_type::unknown, roots);
|
||||
findRoots((config->stateDir.get() / "profiles").string(), std::filesystem::file_type::unknown, roots);
|
||||
findRoots(config->stateDir + "/" + gcRootsDir, std::filesystem::file_type::unknown, roots);
|
||||
findRoots(config->stateDir + "/profiles", std::filesystem::file_type::unknown, roots);
|
||||
|
||||
/* Add additional roots returned by different platforms-specific
|
||||
heuristics. This is typically used to add running programs to
|
||||
@@ -307,9 +307,33 @@ Roots LocalStore::findRoots(bool censor)
|
||||
return roots;
|
||||
}
|
||||
|
||||
static Roots requestRuntimeRoots(const LocalStoreConfig & config, const std::filesystem::path & socketPath)
|
||||
{
|
||||
Roots roots;
|
||||
|
||||
auto socket = connect(socketPath);
|
||||
auto socketSource = FdSource(socket.get());
|
||||
|
||||
while (1) {
|
||||
auto line = socketSource.readLine(true, '\0');
|
||||
if (line == "")
|
||||
break;
|
||||
roots[config.parseStorePath(line)].insert(censored);
|
||||
};
|
||||
|
||||
return roots;
|
||||
}
|
||||
|
||||
void LocalStore::findRuntimeRoots(Roots & roots, bool censor)
|
||||
{
|
||||
auto unchecked = findRuntimeRootsUnchecked(*config);
|
||||
Roots unchecked;
|
||||
|
||||
if (config->useRootsDaemon) {
|
||||
experimentalFeatureSettings.require(Xp::LocalOverlayStore);
|
||||
unchecked = requestRuntimeRoots(*config, config->getRootsSocketPath());
|
||||
} else {
|
||||
unchecked = findRuntimeRootsUnchecked(*config);
|
||||
}
|
||||
|
||||
for (auto & [path, links] : unchecked) {
|
||||
if (!isValidPath(path))
|
||||
@@ -375,7 +399,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||
|
||||
/* Start the server for receiving new roots. */
|
||||
auto socketPath = config->stateDir.get() + gcSocketPath;
|
||||
createDirs(std::filesystem::path(socketPath).parent_path());
|
||||
createDirs(dirOf(socketPath));
|
||||
auto fdServer = createUnixDomainSocket(socketPath, 0666);
|
||||
|
||||
// TODO nonblocking socket on windows?
|
||||
@@ -514,26 +538,26 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||
/* Helper function that deletes a path from the store and throws
|
||||
GCLimitReached if we've deleted enough garbage. */
|
||||
auto deleteFromStore = [&](std::string_view baseName, bool isKnownPath) {
|
||||
auto path = std::filesystem::path{storeDir} / std::string(baseName);
|
||||
auto realPath = config->realStoreDir.get() / std::string(baseName);
|
||||
Path path = storeDir + "/" + std::string(baseName);
|
||||
Path realPath = config->realStoreDir + "/" + std::string(baseName);
|
||||
|
||||
/* There may be temp directories in the store that are still in use
|
||||
by another process. We need to be sure that we can acquire an
|
||||
exclusive lock before deleting them. */
|
||||
if (baseName.find("tmp-", 0) == 0) {
|
||||
AutoCloseFD tmpDirFd = openDirectory(realPath.string());
|
||||
AutoCloseFD tmpDirFd = openDirectory(realPath);
|
||||
if (!tmpDirFd || !lockFile(tmpDirFd.get(), ltWrite, false)) {
|
||||
debug("skipping locked tempdir %s", PathFmt(realPath));
|
||||
debug("skipping locked tempdir '%s'", realPath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
printInfo("deleting %1%", PathFmt(path));
|
||||
printInfo("deleting '%1%'", path);
|
||||
|
||||
results.paths.insert(path.string());
|
||||
results.paths.insert(path);
|
||||
|
||||
uint64_t bytesFreed;
|
||||
deleteStorePath(realPath.string(), bytesFreed, isKnownPath);
|
||||
deleteStorePath(realPath, bytesFreed, isKnownPath);
|
||||
|
||||
results.bytesFreed += bytesFreed;
|
||||
|
||||
@@ -690,12 +714,12 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||
try {
|
||||
AutoCloseDir dir(opendir(config->realStoreDir.get().c_str()));
|
||||
if (!dir)
|
||||
throw SysError("opening directory %1%", PathFmt(config->realStoreDir.get()));
|
||||
throw SysError("opening directory '%1%'", config->realStoreDir);
|
||||
|
||||
/* Read the store and delete all paths that are invalid or
|
||||
unreachable. We don't use readDirectory() here so that
|
||||
GCing can start faster. */
|
||||
auto linksName = linksDir.filename().string();
|
||||
auto linksName = baseNameOf(linksDir);
|
||||
struct dirent * dirent;
|
||||
while (errno = 0, dirent = readdir(dir.get())) {
|
||||
checkInterrupt();
|
||||
@@ -734,7 +758,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||
|
||||
AutoCloseDir dir(opendir(linksDir.c_str()));
|
||||
if (!dir)
|
||||
throw SysError("opening directory %1%", PathFmt(linksDir));
|
||||
throw SysError("opening directory '%1%'", linksDir);
|
||||
|
||||
int64_t actualSize = 0, unsharedSize = 0;
|
||||
|
||||
@@ -744,9 +768,9 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||
std::string name = dirent->d_name;
|
||||
if (name == "." || name == "..")
|
||||
continue;
|
||||
auto path = linksDir / name;
|
||||
Path path = linksDir + "/" + name;
|
||||
|
||||
auto st = lstat(path.string());
|
||||
auto st = lstat(path);
|
||||
|
||||
if (st.st_nlink != 1) {
|
||||
actualSize += st.st_size;
|
||||
@@ -754,10 +778,10 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||
continue;
|
||||
}
|
||||
|
||||
printMsg(lvlTalkative, "deleting unused link %1%", PathFmt(path));
|
||||
printMsg(lvlTalkative, "deleting unused link '%1%'", path);
|
||||
|
||||
if (unlink(path.c_str()) == -1)
|
||||
throw SysError("deleting %1%", PathFmt(path));
|
||||
throw SysError("deleting '%1%'", path);
|
||||
|
||||
/* Do not account for deleted file here. Rely on deletePath()
|
||||
accounting. */
|
||||
|
||||
@@ -279,7 +279,7 @@ bool Settings::isWSL1()
|
||||
#endif
|
||||
}
|
||||
|
||||
const ExternalBuilder * Settings::findExternalDerivationBuilderIfSupported(const Derivation & drv)
|
||||
const ExternalBuilder * LocalSettings::findExternalDerivationBuilderIfSupported(const Derivation & drv)
|
||||
{
|
||||
if (auto it = std::ranges::find_if(
|
||||
externalBuilders.get(), [&](const auto & handler) { return handler.systems.contains(drv.platform); });
|
||||
@@ -429,17 +429,17 @@ unsigned int MaxBuildJobsSetting::parse(const std::string & str) const
|
||||
}
|
||||
|
||||
template<>
|
||||
Settings::ExternalBuilders BaseSetting<Settings::ExternalBuilders>::parse(const std::string & str) const
|
||||
LocalSettings::ExternalBuilders BaseSetting<LocalSettings::ExternalBuilders>::parse(const std::string & str) const
|
||||
{
|
||||
try {
|
||||
return nlohmann::json::parse(str).template get<Settings::ExternalBuilders>();
|
||||
return nlohmann::json::parse(str).template get<LocalSettings::ExternalBuilders>();
|
||||
} catch (std::exception & e) {
|
||||
throw UsageError("parsing setting '%s': %s", name, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string BaseSetting<Settings::ExternalBuilders>::to_string() const
|
||||
std::string BaseSetting<LocalSettings::ExternalBuilders>::to_string() const
|
||||
{
|
||||
return nlohmann::json(value).dump();
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -64,6 +64,12 @@ private:
|
||||
|
||||
std::string key() override;
|
||||
|
||||
struct LocalBuildCapability
|
||||
{
|
||||
LocalStore & localStore;
|
||||
const ExternalBuilder * externalBuilder;
|
||||
};
|
||||
|
||||
/**
|
||||
* The states.
|
||||
*/
|
||||
@@ -75,12 +81,11 @@ private:
|
||||
DerivationOptions<StorePath> drvOptions,
|
||||
PathLocks outputLocks);
|
||||
Co buildLocally(
|
||||
LocalStore & localStore,
|
||||
LocalBuildCapability localBuildCap,
|
||||
StorePathSet inputPaths,
|
||||
std::map<std::string, InitialOutput> initialOutputs,
|
||||
DerivationOptions<StorePath> drvOptions,
|
||||
PathLocks outputLocks,
|
||||
const ExternalBuilder * externalBuilder);
|
||||
PathLocks outputLocks);
|
||||
|
||||
/**
|
||||
* Is the build hook willing to perform the build?
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct TimedOut : BuildError
|
||||
struct TimedOut final : CloneableError<TimedOut, BuildError>
|
||||
{
|
||||
time_t maxDuration;
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
///@file
|
||||
|
||||
#include "nix/store/store-api.hh"
|
||||
#include "nix/util/fmt.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -11,11 +10,11 @@ namespace nix {
|
||||
*/
|
||||
struct Package
|
||||
{
|
||||
std::filesystem::path path;
|
||||
Path path;
|
||||
bool active;
|
||||
int priority;
|
||||
|
||||
Package(const std::filesystem::path & path, bool active, int priority)
|
||||
Package(const Path & path, bool active, int priority)
|
||||
: path{path}
|
||||
, active{active}
|
||||
, priority{priority}
|
||||
@@ -23,21 +22,21 @@ struct Package
|
||||
}
|
||||
};
|
||||
|
||||
class BuildEnvFileConflictError : public Error
|
||||
class BuildEnvFileConflictError final : public CloneableError<BuildEnvFileConflictError, Error>
|
||||
{
|
||||
public:
|
||||
const std::filesystem::path fileA;
|
||||
const std::filesystem::path fileB;
|
||||
const Path fileA;
|
||||
const Path fileB;
|
||||
int priority;
|
||||
|
||||
BuildEnvFileConflictError(const std::filesystem::path fileA, const std::filesystem::path fileB, int priority)
|
||||
: Error(
|
||||
BuildEnvFileConflictError(const Path fileA, const Path fileB, int priority)
|
||||
: CloneableError(
|
||||
"Unable to build profile. There is a conflict for the following files:\n"
|
||||
"\n"
|
||||
" %1%\n"
|
||||
" %2%",
|
||||
PathFmt(fileA),
|
||||
PathFmt(fileB))
|
||||
fileA,
|
||||
fileB)
|
||||
, fileA(fileA)
|
||||
, fileB(fileB)
|
||||
, priority(priority)
|
||||
@@ -47,6 +46,6 @@ public:
|
||||
|
||||
typedef std::vector<Package> Packages;
|
||||
|
||||
void buildProfile(const std::filesystem::path & out, Packages && pkgs);
|
||||
void buildProfile(const Path & out, Packages && pkgs);
|
||||
|
||||
} // namespace nix
|
||||
@@ -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;
|
||||
|
||||
@@ -104,8 +104,6 @@ public:
|
||||
|
||||
Settings();
|
||||
|
||||
using ExternalBuilders = std::vector<ExternalBuilder>;
|
||||
|
||||
/**
|
||||
* Get the local store settings.
|
||||
*/
|
||||
@@ -410,12 +408,6 @@ public:
|
||||
Set it to 1 to warn on all paths.
|
||||
)"};
|
||||
|
||||
/**
|
||||
* Finds the first external derivation builder that supports this
|
||||
* derivation, or else returns a null pointer.
|
||||
*/
|
||||
const ExternalBuilder * findExternalDerivationBuilderIfSupported(const Derivation & drv);
|
||||
|
||||
/**
|
||||
* Get the options needed for profile directory functions.
|
||||
*/
|
||||
|
||||
@@ -10,8 +10,7 @@ namespace nix {
|
||||
struct LocalFSStoreConfig : virtual StoreConfig
|
||||
{
|
||||
private:
|
||||
static Setting<std::optional<std::filesystem::path>>
|
||||
makeRootDirSetting(LocalFSStoreConfig & self, std::optional<Path> defaultValue)
|
||||
static OptionalPathSetting makeRootDirSetting(LocalFSStoreConfig & self, std::optional<Path> defaultValue)
|
||||
{
|
||||
return {
|
||||
&self,
|
||||
@@ -33,7 +32,7 @@ public:
|
||||
*/
|
||||
LocalFSStoreConfig(PathView path, const Params & params);
|
||||
|
||||
Setting<std::optional<std::filesystem::path>> rootDir = makeRootDirSetting(*this, std::nullopt);
|
||||
OptionalPathSetting rootDir = makeRootDirSetting(*this, std::nullopt);
|
||||
|
||||
private:
|
||||
|
||||
@@ -51,19 +50,19 @@ private:
|
||||
|
||||
public:
|
||||
|
||||
Setting<std::filesystem::path> stateDir{
|
||||
PathSetting stateDir{
|
||||
this,
|
||||
rootDir.get() ? *rootDir.get() + "/nix/var/nix" : getDefaultStateDir(),
|
||||
"state",
|
||||
"Directory where Nix stores state."};
|
||||
|
||||
Setting<std::filesystem::path> logDir{
|
||||
PathSetting logDir{
|
||||
this,
|
||||
rootDir.get() ? *rootDir.get() + "/nix/var/log/nix" : getDefaultLogDir(),
|
||||
"log",
|
||||
"directory where Nix stores log files."};
|
||||
|
||||
Setting<std::filesystem::path> realStoreDir{
|
||||
PathSetting realStoreDir{
|
||||
this, rootDir.get() ? *rootDir.get() + "/nix/store" : storeDir, "real", "Physical path of the Nix store."};
|
||||
};
|
||||
|
||||
@@ -78,7 +77,7 @@ struct alignas(8) /* Work around ASAN failures on i686-linux. */
|
||||
|
||||
inline static std::string operationName = "Local Filesystem Store";
|
||||
|
||||
const static std::filesystem::path drvsLogDir;
|
||||
const static std::string drvsLogDir;
|
||||
|
||||
LocalFSStore(const Config & params);
|
||||
|
||||
@@ -101,7 +100,7 @@ struct alignas(8) /* Work around ASAN failures on i686-linux. */
|
||||
*/
|
||||
virtual Path addPermRoot(const StorePath & storePath, const Path & gcRoot) = 0;
|
||||
|
||||
virtual std::filesystem::path getRealStoreDir()
|
||||
virtual Path getRealStoreDir()
|
||||
{
|
||||
return config.realStoreDir;
|
||||
}
|
||||
|
||||
@@ -707,6 +707,12 @@ public:
|
||||
// Current system: 'aarch64-darwin' with features {apple-virt, benchmark, big-parallel, nixos-test}
|
||||
// Xp::ExternalBuilders
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the first external derivation builder that supports this
|
||||
* derivation, or else returns a null pointer.
|
||||
*/
|
||||
const ExternalBuilder * findExternalDerivationBuilderIfSupported(const Derivation & drv);
|
||||
};
|
||||
|
||||
} // namespace nix
|
||||
@@ -133,6 +133,27 @@ public:
|
||||
Xp::LocalOverlayStore,
|
||||
};
|
||||
|
||||
Setting<bool> useRootsDaemon{
|
||||
this,
|
||||
false,
|
||||
"use-roots-daemon",
|
||||
R"(
|
||||
Whether to request garbage collector roots from an external daemon.
|
||||
|
||||
When enabled, the garbage collector connects to a Unix domain socket
|
||||
at [`<state-dir>`](@docroot@/store/types/local-store.md#store-option-state)`/gc-roots-socket/socket` to discover additional roots
|
||||
that should not be collected. This is useful when the Nix daemon runs
|
||||
without root privileges and cannot scan `/proc` for runtime roots.
|
||||
|
||||
The daemon can be started with [`nix store roots-daemon`](@docroot@/command-ref/new-cli/nix3-store-roots-daemon.md).
|
||||
)",
|
||||
{},
|
||||
true,
|
||||
Xp::LocalOverlayStore,
|
||||
};
|
||||
|
||||
std::filesystem::path getRootsSocketPath() const;
|
||||
|
||||
static const std::string name()
|
||||
{
|
||||
return "Local Store";
|
||||
@@ -207,12 +228,12 @@ private:
|
||||
|
||||
public:
|
||||
|
||||
const std::filesystem::path dbDir;
|
||||
const std::filesystem::path linksDir;
|
||||
const std::filesystem::path reservedPath;
|
||||
const std::filesystem::path schemaPath;
|
||||
const std::filesystem::path tempRootsDir;
|
||||
const std::filesystem::path fnTempRoots;
|
||||
const Path dbDir;
|
||||
const Path linksDir;
|
||||
const Path reservedPath;
|
||||
const Path schemaPath;
|
||||
const Path tempRootsDir;
|
||||
const Path fnTempRoots;
|
||||
|
||||
private:
|
||||
|
||||
@@ -223,7 +244,7 @@ public:
|
||||
/**
|
||||
* Hack for build-remote.cc.
|
||||
*/
|
||||
PathSetNG locksHeld;
|
||||
PathSet locksHeld;
|
||||
|
||||
/**
|
||||
* Initialise the local store, upgrading the schema if
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -34,7 +34,7 @@ private:
|
||||
#ifndef _WIN32 // TODO re-enable on Windows, once we can start processes.
|
||||
Pid sshMaster;
|
||||
#endif
|
||||
std::filesystem::path socketPath;
|
||||
Path socketPath;
|
||||
};
|
||||
|
||||
Sync<State> state_;
|
||||
@@ -43,7 +43,7 @@ private:
|
||||
bool isMasterRunning();
|
||||
|
||||
#ifndef _WIN32 // TODO re-enable on Windows, once we can start processes.
|
||||
std::filesystem::path startMaster();
|
||||
Path startMaster();
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
@@ -5,11 +5,11 @@ namespace nix {
|
||||
void IndirectRootStore::makeSymlink(const Path & link, const Path & target)
|
||||
{
|
||||
/* Create directories up to `gcRoot'. */
|
||||
createDirs(std::filesystem::path(link).parent_path());
|
||||
createDirs(dirOf(link));
|
||||
|
||||
/* Create the new symlink. */
|
||||
auto tempLink = std::filesystem::path(link).concat(fmt(".tmp-%1%-%2%", getpid(), rand()));
|
||||
createSymlink(target, tempLink.string());
|
||||
Path tempLink = fmt("%1%.tmp-%2%-%3%", link, getpid(), rand());
|
||||
createSymlink(target, tempLink);
|
||||
|
||||
/* Atomically replace the old one. */
|
||||
std::filesystem::rename(tempLink, link);
|
||||
@@ -17,13 +17,13 @@ void IndirectRootStore::makeSymlink(const Path & link, const Path & target)
|
||||
|
||||
Path IndirectRootStore::addPermRoot(const StorePath & storePath, const Path & _gcRoot)
|
||||
{
|
||||
auto gcRoot = canonPath(_gcRoot);
|
||||
Path gcRoot(canonPath(_gcRoot));
|
||||
|
||||
if (isInStore(gcRoot.string()))
|
||||
if (isInStore(gcRoot))
|
||||
throw Error(
|
||||
"creating a garbage collector root (%1%) in the Nix store is forbidden "
|
||||
"(are you running nix-build inside the store?)",
|
||||
PathFmt(gcRoot));
|
||||
gcRoot);
|
||||
|
||||
/* Register this root with the garbage collector, if it's
|
||||
running. This should be superfluous since the caller should
|
||||
@@ -33,13 +33,13 @@ Path IndirectRootStore::addPermRoot(const StorePath & storePath, const Path & _g
|
||||
|
||||
/* Don't clobber the link if it already exists and doesn't
|
||||
point to the Nix store. */
|
||||
if (pathExists(gcRoot.string()) && (!std::filesystem::is_symlink(gcRoot) || !isInStore(readLink(gcRoot).string())))
|
||||
throw Error("cannot create symlink %1%; already exists", PathFmt(gcRoot));
|
||||
if (pathExists(gcRoot) && (!std::filesystem::is_symlink(gcRoot) || !isInStore(readLink(gcRoot))))
|
||||
throw Error("cannot create symlink '%1%'; already exists", gcRoot);
|
||||
|
||||
makeSymlink(gcRoot.string(), printStorePath(storePath));
|
||||
addIndirectRoot(gcRoot.string());
|
||||
makeSymlink(gcRoot, printStorePath(storePath));
|
||||
addIndirectRoot(gcRoot);
|
||||
|
||||
return gcRoot.string();
|
||||
return gcRoot;
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
@@ -112,7 +112,7 @@ std::shared_ptr<SourceAccessor> LocalFSStore::getFSAccessor(const StorePath & pa
|
||||
return std::make_shared<PosixSourceAccessor>(std::move(absPath));
|
||||
}
|
||||
|
||||
const std::filesystem::path LocalFSStore::drvsLogDir = "drvs";
|
||||
const std::string LocalFSStore::drvsLogDir = "drvs";
|
||||
|
||||
std::optional<std::string> LocalFSStore::getBuildLogExact(const StorePath & path)
|
||||
{
|
||||
@@ -120,10 +120,10 @@ std::optional<std::string> LocalFSStore::getBuildLogExact(const StorePath & path
|
||||
|
||||
for (int j = 0; j < 2; j++) {
|
||||
|
||||
auto logPath = config.logDir.get()
|
||||
/ (j == 0 ? drvsLogDir / baseName.substr(0, 2) / baseName.substr(2) : drvsLogDir / baseName);
|
||||
auto logBz2Path = logPath;
|
||||
logBz2Path += ".bz2";
|
||||
Path logPath =
|
||||
j == 0 ? fmt("%s/%s/%s/%s", config.logDir.get(), drvsLogDir, baseName.substr(0, 2), baseName.substr(2))
|
||||
: fmt("%s/%s/%s", config.logDir.get(), drvsLogDir, baseName);
|
||||
Path logBz2Path = logPath + ".bz2";
|
||||
|
||||
if (pathExists(logPath))
|
||||
return readFile(logPath);
|
||||
|
||||
@@ -35,7 +35,7 @@ StoreReference LocalOverlayStoreConfig::getReference() const
|
||||
|
||||
Path LocalOverlayStoreConfig::toUpperPath(const StorePath & path) const
|
||||
{
|
||||
return (upperLayer.get() / path.to_string()).string();
|
||||
return upperLayer + "/" + path.to_string();
|
||||
}
|
||||
|
||||
LocalOverlayStore::LocalOverlayStore(ref<const Config> config)
|
||||
@@ -49,7 +49,7 @@ LocalOverlayStore::LocalOverlayStore(ref<const Config> config)
|
||||
std::smatch match;
|
||||
std::string mountInfo;
|
||||
auto mounts = readFile(std::filesystem::path{"/proc/self/mounts"});
|
||||
auto regex = std::regex(R"((^|\n)overlay )" + config->realStoreDir.get().string() + R"( .*(\n|$))");
|
||||
auto regex = std::regex(R"((^|\n)overlay )" + config->realStoreDir.get() + R"( .*(\n|$))");
|
||||
|
||||
// Mount points can be stacked, so there might be multiple matching entries.
|
||||
// Loop until the last match, which will be the current state of the mount point.
|
||||
@@ -58,16 +58,16 @@ LocalOverlayStore::LocalOverlayStore(ref<const Config> config)
|
||||
mounts = match.suffix();
|
||||
}
|
||||
|
||||
auto checkOption = [&](std::string_view option, const std::filesystem::path & value) {
|
||||
return std::regex_search(mountInfo, std::regex("\\b" + option + "=" + value.string() + "( |,)"));
|
||||
auto checkOption = [&](std::string option, std::string value) {
|
||||
return std::regex_search(mountInfo, std::regex("\\b" + option + "=" + value + "( |,)"));
|
||||
};
|
||||
|
||||
auto expectedLowerDir = lowerStore->config.realStoreDir.get();
|
||||
if (!checkOption("lowerdir", expectedLowerDir) || !checkOption("upperdir", config->upperLayer.get())) {
|
||||
debug("expected lowerdir: %s", PathFmt(lowerStore->config.realStoreDir.get()));
|
||||
debug("expected upperdir: %s", PathFmt(config->upperLayer.get()));
|
||||
if (!checkOption("lowerdir", expectedLowerDir) || !checkOption("upperdir", config->upperLayer)) {
|
||||
debug("expected lowerdir: %s", expectedLowerDir);
|
||||
debug("expected upperdir: %s", config->upperLayer);
|
||||
debug("actual mount: %s", mountInfo);
|
||||
throw Error("overlay filesystem %s mounted incorrectly", PathFmt(config->realStoreDir.get()));
|
||||
throw Error("overlay filesystem '%s' mounted incorrectly", config->realStoreDir.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,9 +281,9 @@ void LocalOverlayStore::remountIfNecessary()
|
||||
return;
|
||||
|
||||
if (config->remountHook.get().empty()) {
|
||||
warn("%s needs remounting, set remount-hook to do this automatically", PathFmt(config->realStoreDir.get()));
|
||||
warn("'%s' needs remounting, set remount-hook to do this automatically", config->realStoreDir.get());
|
||||
} else {
|
||||
runProgram(config->remountHook.get(), false, {config->realStoreDir.get().string()});
|
||||
runProgram(config->remountHook, false, {config->realStoreDir});
|
||||
}
|
||||
|
||||
_remountRequired = false;
|
||||
|
||||
+24
-20
@@ -119,12 +119,12 @@ LocalStore::LocalStore(ref<const Config> config)
|
||||
, LocalFSStore{*config}
|
||||
, config{config}
|
||||
, _state(make_ref<Sync<State>>())
|
||||
, dbDir(config->stateDir.get() / "db")
|
||||
, linksDir(config->realStoreDir.get() / ".links")
|
||||
, reservedPath(dbDir / "reserved")
|
||||
, schemaPath(dbDir / "schema")
|
||||
, tempRootsDir(config->stateDir.get() / "temproots")
|
||||
, fnTempRoots(tempRootsDir / std::to_string(getpid()))
|
||||
, dbDir(config->stateDir + "/db")
|
||||
, linksDir(config->realStoreDir + "/.links")
|
||||
, reservedPath(dbDir + "/reserved")
|
||||
, schemaPath(dbDir + "/schema")
|
||||
, tempRootsDir(config->stateDir + "/temproots")
|
||||
, fnTempRoots(fmt("%s/%d", tempRootsDir, getpid()))
|
||||
{
|
||||
auto state(_state->lock());
|
||||
state->stmts = std::make_unique<State::Stmts>();
|
||||
@@ -137,19 +137,19 @@ LocalStore::LocalStore(ref<const Config> config)
|
||||
makeStoreWritable();
|
||||
}
|
||||
createDirs(linksDir);
|
||||
auto profilesDir = config->stateDir.get() / "profiles";
|
||||
Path profilesDir = config->stateDir + "/profiles";
|
||||
createDirs(profilesDir);
|
||||
createDirs(tempRootsDir);
|
||||
createDirs(dbDir);
|
||||
auto gcRootsDir = config->stateDir.get() / "gcroots";
|
||||
Path gcRootsDir = config->stateDir + "/gcroots";
|
||||
const auto & localSettings = config->getLocalSettings();
|
||||
const auto & gcSettings = localSettings.getGCSettings();
|
||||
if (!pathExists(gcRootsDir)) {
|
||||
createDirs(gcRootsDir);
|
||||
replaceSymlink(profilesDir.string(), (gcRootsDir / "profiles").string());
|
||||
replaceSymlink(profilesDir, gcRootsDir + "/profiles");
|
||||
}
|
||||
|
||||
for (auto & perUserDir : {profilesDir / "per-user", gcRootsDir / "per-user"}) {
|
||||
for (auto & perUserDir : {profilesDir + "/per-user", gcRootsDir + "/per-user"}) {
|
||||
createDirs(perUserDir);
|
||||
if (!config->readOnly) {
|
||||
// Skip chmod call if the directory already has the correct permissions (0755).
|
||||
@@ -233,7 +233,7 @@ LocalStore::LocalStore(ref<const Config> config)
|
||||
/* Acquire the big fat lock in shared mode to make sure that no
|
||||
schema upgrade is in progress. */
|
||||
if (!config->readOnly) {
|
||||
auto globalLockPath = dbDir / "big-lock";
|
||||
Path globalLockPath = dbDir + "/big-lock";
|
||||
try {
|
||||
globalLock = openLockFile(globalLockPath.c_str(), true);
|
||||
} catch (SystemError & e) {
|
||||
@@ -395,7 +395,7 @@ LocalStore::LocalStore(ref<const Config> config)
|
||||
|
||||
AutoCloseFD LocalStore::openGCLock()
|
||||
{
|
||||
auto fnGCLock = config->stateDir.get() / "gc.lock";
|
||||
Path fnGCLock = config->stateDir + "/gc.lock";
|
||||
auto fdGCLock = open(
|
||||
fnGCLock.c_str(),
|
||||
O_RDWR | O_CREAT
|
||||
@@ -405,7 +405,7 @@ AutoCloseFD LocalStore::openGCLock()
|
||||
,
|
||||
0600);
|
||||
if (!fdGCLock)
|
||||
throw SysError("opening global GC lock %1%", PathFmt(fnGCLock));
|
||||
throw SysError("opening global GC lock '%1%'", fnGCLock);
|
||||
return toDescriptor(fdGCLock);
|
||||
}
|
||||
|
||||
@@ -457,6 +457,11 @@ LocalStore::~LocalStore()
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path LocalStoreConfig::getRootsSocketPath() const
|
||||
{
|
||||
return std::filesystem::path(stateDir.get()) / "gc-roots-socket" / "socket";
|
||||
}
|
||||
|
||||
StoreReference LocalStoreConfig::getReference() const
|
||||
{
|
||||
auto params = getQueryParams();
|
||||
@@ -485,7 +490,7 @@ int LocalStore::getSchema()
|
||||
auto s = readFile(schemaPath);
|
||||
auto n = string2Int<int>(s);
|
||||
if (!n)
|
||||
throw Error("%1% is corrupt", PathFmt(schemaPath));
|
||||
throw Error("'%1%' is corrupt", schemaPath);
|
||||
curSchema = *n;
|
||||
}
|
||||
return curSchema;
|
||||
@@ -498,14 +503,14 @@ void LocalStore::openDB(State & state, bool create)
|
||||
}
|
||||
|
||||
if (access(dbDir.c_str(), R_OK | (config->readOnly ? 0 : W_OK)))
|
||||
throw SysError("Nix database directory %1% is not writable", PathFmt(dbDir));
|
||||
throw SysError("Nix database directory '%1%' is not writable", dbDir);
|
||||
|
||||
/* Open the Nix database. */
|
||||
auto & db(state.db);
|
||||
auto openMode = config->readOnly ? SQLiteOpenMode::Immutable
|
||||
: create ? SQLiteOpenMode::Normal
|
||||
: SQLiteOpenMode::NoCreate;
|
||||
state.db = SQLite(dbDir / "db.sqlite", {.mode = openMode, .useWAL = settings.useSQLiteWAL});
|
||||
state.db = SQLite(std::filesystem::path(dbDir) / "db.sqlite", {.mode = openMode, .useWAL = settings.useSQLiteWAL});
|
||||
|
||||
#ifdef __CYGWIN__
|
||||
/* The cygwin version of sqlite3 has a patch which calls
|
||||
@@ -1585,15 +1590,14 @@ void LocalStore::addBuildLog(const StorePath & drvPath, std::string_view log)
|
||||
|
||||
auto baseName = drvPath.to_string();
|
||||
|
||||
auto logPath =
|
||||
config->logDir.get() / drvsLogDir / baseName.substr(0, 2) / (std::string(baseName.substr(2)) + ".bz2");
|
||||
auto logPath = fmt("%s/%s/%s/%s.bz2", config->logDir, drvsLogDir, baseName.substr(0, 2), baseName.substr(2));
|
||||
|
||||
if (pathExists(logPath))
|
||||
return;
|
||||
|
||||
createDirs(std::filesystem::path(logPath).parent_path());
|
||||
createDirs(dirOf(logPath));
|
||||
|
||||
auto tmpFile = fmt("%s.tmp.%d", logPath.string(), getpid());
|
||||
auto tmpFile = fmt("%s.tmp.%d", logPath, getpid());
|
||||
|
||||
writeFile(tmpFile, compress(CompressionAlgo::bzip2, log));
|
||||
|
||||
|
||||
@@ -68,10 +68,8 @@ endif
|
||||
summary('can hardlink to symlink', can_link_symlink, bool_yn : true)
|
||||
configdata_priv.set('CAN_LINK_SYMLINK', can_link_symlink.to_int())
|
||||
|
||||
# Check for each of these functions, and create a define like `#define HAVE_LCHOWN 1`.
|
||||
# Check for each of these functions, and create a define like `#define HAVE_POSIX_FALLOCATE 1`.
|
||||
check_funcs = [
|
||||
# Optionally used for canonicalising files from the build
|
||||
'lchown',
|
||||
'posix_fallocate',
|
||||
'statvfs',
|
||||
]
|
||||
|
||||
@@ -92,7 +92,7 @@ struct NarInfoDiskCacheImpl : NarInfoDiskCache
|
||||
{
|
||||
auto state(_state.lock());
|
||||
|
||||
createDirs(std::filesystem::path(dbPath).parent_path());
|
||||
createDirs(dirOf(dbPath));
|
||||
|
||||
state->db = SQLite(dbPath, SQLite::Settings{sqliteSettings});
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ LocalStore::InodeHash LocalStore::loadInodeHash()
|
||||
|
||||
AutoCloseDir dir(opendir(linksDir.c_str()));
|
||||
if (!dir)
|
||||
throw SysError("opening directory %1%", PathFmt(linksDir));
|
||||
throw SysError("opening directory '%1%'", linksDir);
|
||||
|
||||
struct dirent * dirent;
|
||||
while (errno = 0, dirent = readdir(dir.get())) { /* sic */
|
||||
@@ -61,7 +61,7 @@ LocalStore::InodeHash LocalStore::loadInodeHash()
|
||||
inodeHash.insert(dirent->d_ino);
|
||||
}
|
||||
if (errno)
|
||||
throw SysError("reading directory %1%", PathFmt(linksDir));
|
||||
throw SysError("reading directory '%1%'", linksDir);
|
||||
|
||||
printMsg(lvlTalkative, "loaded %1% hash inodes", inodeHash.size());
|
||||
|
||||
@@ -227,7 +227,7 @@ void LocalStore::optimisePath_(
|
||||
/* Make the containing directory writable, but only if it's not
|
||||
the store itself (we don't want or need to mess with its
|
||||
permissions). */
|
||||
const Path dirOfPath(std::filesystem::path(path).parent_path());
|
||||
const Path dirOfPath(dirOf(path));
|
||||
bool mustToggle = dirOfPath != config->realStoreDir.get();
|
||||
if (mustToggle)
|
||||
makeWritable(dirOfPath);
|
||||
@@ -305,7 +305,7 @@ void LocalStore::optimiseStore(OptimiseStats & stats)
|
||||
continue; /* path was GC'ed, probably */
|
||||
{
|
||||
Activity act(*logger, lvlTalkative, actUnknown, fmt("optimising path '%s'", printStorePath(i)));
|
||||
optimisePath_(&act, stats, (config->realStoreDir.get() / i.to_string()).string(), inodeHash, NoRepair);
|
||||
optimisePath_(&act, stats, config->realStoreDir + "/" + std::string(i.to_string()), inodeHash, NoRepair);
|
||||
}
|
||||
done++;
|
||||
act.progress(done, paths.size());
|
||||
|
||||
@@ -107,19 +107,9 @@ canonicalisePathMetaData_(const Path & path, CanonicalizePathMetadataOptions opt
|
||||
canonicaliseTimestampAndPermissions(path, st);
|
||||
|
||||
#ifndef _WIN32
|
||||
/* Change ownership to the current uid. If it's a symlink, use
|
||||
lchown if available, otherwise don't bother. Wrong ownership
|
||||
of a symlink doesn't matter, since the owning user can't change
|
||||
the symlink and can't delete it because the directory is not
|
||||
writable. The only exception is top-level paths in the Nix
|
||||
store (since that directory is group-writable for the Nix build
|
||||
users group); we check for this case below. */
|
||||
/* Change ownership to the current uid. */
|
||||
if (st.st_uid != geteuid()) {
|
||||
# if HAVE_LCHOWN
|
||||
if (lchown(path.c_str(), geteuid(), getegid()) == -1)
|
||||
# else
|
||||
if (!S_ISLNK(st.st_mode) && chown(path.c_str(), geteuid(), getegid()) == -1)
|
||||
# endif
|
||||
throw SysError("changing owner of '%1%' to %2%", path, geteuid());
|
||||
}
|
||||
#endif
|
||||
@@ -135,17 +125,6 @@ canonicalisePathMetaData_(const Path & path, CanonicalizePathMetadataOptions opt
|
||||
void canonicalisePathMetaData(const Path & path, CanonicalizePathMetadataOptions options, InodesSeen & inodesSeen)
|
||||
{
|
||||
canonicalisePathMetaData_(path, options, inodesSeen);
|
||||
|
||||
#ifndef _WIN32
|
||||
/* On platforms that don't have lchown(), the top-level path can't
|
||||
be a symlink, since we can't change its ownership. */
|
||||
auto st = lstat(path);
|
||||
|
||||
if (st.st_uid != geteuid()) {
|
||||
assert(S_ISLNK(st.st_mode));
|
||||
throw Error("wrong ownership of top-level store path '%1%'", path);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void canonicalisePathMetaData(const Path & path, CanonicalizePathMetadataOptions options)
|
||||
|
||||
@@ -293,7 +293,8 @@ std::string optimisticLockProfile(const std::filesystem::path & profile)
|
||||
|
||||
std::filesystem::path profilesDir(ProfileDirsOptions settings)
|
||||
{
|
||||
auto profileRoot = isRootUser() ? rootProfilesDir(settings) : createNixStateDir() / "profiles";
|
||||
auto profileRoot =
|
||||
isRootUser() ? rootProfilesDir(settings) : std::filesystem::path{createNixStateDir()} / "profiles";
|
||||
createDirs(profileRoot);
|
||||
return profileRoot;
|
||||
}
|
||||
@@ -305,8 +306,9 @@ std::filesystem::path rootProfilesDir(ProfileDirsOptions settings)
|
||||
|
||||
std::filesystem::path getDefaultProfile(ProfileDirsOptions settings)
|
||||
{
|
||||
std::filesystem::path profileLink =
|
||||
settings.useXDGBaseDirectories ? createNixStateDir() / "profile" : getHome() / ".nix-profile";
|
||||
std::filesystem::path profileLink = settings.useXDGBaseDirectories
|
||||
? std::filesystem::path{createNixStateDir()} / "profile"
|
||||
: std::filesystem::path{getHome()} / ".nix-profile";
|
||||
try {
|
||||
auto profile = profilesDir(settings) / "profile";
|
||||
if (!pathExists(profileLink)) {
|
||||
|
||||
@@ -53,7 +53,7 @@ struct RestrictedStore : public virtual IndirectRootStore, public virtual GcStor
|
||||
{
|
||||
}
|
||||
|
||||
std::filesystem::path getRealStoreDir() override
|
||||
Path getRealStoreDir() override
|
||||
{
|
||||
return next->config->realStoreDir;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+6
-6
@@ -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)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -98,7 +98,7 @@ void SSHMaster::addCommonSSHOpts(Strings & args)
|
||||
args.insert(args.end(), {"-i", keyFile});
|
||||
if (!sshPublicHostKey.empty()) {
|
||||
std::filesystem::path fileName = tmpDir->path() / "host-key";
|
||||
writeFile(fileName, authority.host + " " + sshPublicHostKey + "\n");
|
||||
writeFile(fileName.string(), authority.host + " " + sshPublicHostKey + "\n");
|
||||
args.insert(args.end(), {"-oUserKnownHostsFile=" + fileName.string()});
|
||||
}
|
||||
if (compress)
|
||||
@@ -150,7 +150,7 @@ std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(Strings && comman
|
||||
#ifdef _WIN32 // TODO re-enable on Windows, once we can start processes.
|
||||
throw UnimplementedError("cannot yet SSH on windows because spawning processes is not yet implemented");
|
||||
#else
|
||||
std::filesystem::path socketPath = startMaster();
|
||||
Path socketPath = startMaster();
|
||||
|
||||
Pipe in, out;
|
||||
in.create();
|
||||
@@ -228,7 +228,7 @@ std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(Strings && comman
|
||||
|
||||
#ifndef _WIN32 // TODO re-enable on Windows, once we can start processes.
|
||||
|
||||
std::filesystem::path SSHMaster::startMaster()
|
||||
Path SSHMaster::startMaster()
|
||||
{
|
||||
if (!useMaster)
|
||||
return "";
|
||||
@@ -238,7 +238,7 @@ std::filesystem::path SSHMaster::startMaster()
|
||||
if (state->sshMaster != INVALID_DESCRIPTOR)
|
||||
return state->socketPath;
|
||||
|
||||
state->socketPath = tmpDir->path() / "ssh.sock";
|
||||
state->socketPath = (Path) *tmpDir + "/ssh.sock";
|
||||
|
||||
Pipe out;
|
||||
out.create();
|
||||
|
||||
@@ -65,13 +65,13 @@ std::pair<StorePath, Path> StoreDirConfig::toStorePath(PathView path) const
|
||||
|
||||
Path Store::followLinksToStore(std::string_view _path) const
|
||||
{
|
||||
auto path = absPath(std::string(_path));
|
||||
Path path = absPath(std::string(_path));
|
||||
|
||||
// Limit symlink follows to prevent infinite loops
|
||||
unsigned int followCount = 0;
|
||||
const unsigned int maxFollow = 1024;
|
||||
|
||||
while (!isInStore(path.string())) {
|
||||
while (!isInStore(path)) {
|
||||
if (!std::filesystem::is_symlink(path))
|
||||
break;
|
||||
|
||||
@@ -79,13 +79,12 @@ Path Store::followLinksToStore(std::string_view _path) const
|
||||
throw Error("too many symbolic links encountered while resolving '%s'", _path);
|
||||
|
||||
auto target = readLink(path);
|
||||
auto parentPath = path.parent_path();
|
||||
path = absPath(target, &parentPath);
|
||||
path = absPath(target, dirOf(path));
|
||||
}
|
||||
|
||||
if (!isInStore(path.string()))
|
||||
throw BadStorePath("path %1% is not in the Nix store", PathFmt(path));
|
||||
return path.string();
|
||||
if (!isInStore(path))
|
||||
throw BadStorePath("path '%1%' is not in the Nix store", path);
|
||||
return path;
|
||||
}
|
||||
|
||||
StorePath Store::followLinksToStorePath(std::string_view path) const
|
||||
|
||||
@@ -20,9 +20,9 @@ StorePath StoreDirConfig::parseStorePath(std::string_view path) const
|
||||
canonPath(std::string(path))
|
||||
#endif
|
||||
;
|
||||
if (p.parent_path() != storeDir)
|
||||
throw BadStorePath("path %s is not in the Nix store", PathFmt(p));
|
||||
return StorePath(p.filename().string());
|
||||
if (dirOf(p) != storeDir)
|
||||
throw BadStorePath("path '%s' is not in the Nix store", p);
|
||||
return StorePath(baseNameOf(p));
|
||||
}
|
||||
|
||||
std::optional<StorePath> StoreDirConfig::maybeParseStorePath(std::string_view path) const
|
||||
|
||||
@@ -42,18 +42,17 @@ ref<StoreConfig> resolveStoreConfig(StoreReference && storeURI)
|
||||
/* If /nix doesn't exist, there is no daemon socket, and
|
||||
we're not root, then automatically set up a chroot
|
||||
store in ~/.local/share/nix/root. */
|
||||
auto chrootStore = getDataDir() / "root";
|
||||
auto chrootStore = getDataDir() + "/root";
|
||||
if (!pathExists(chrootStore)) {
|
||||
try {
|
||||
createDirs(chrootStore);
|
||||
} catch (SystemError & e) {
|
||||
return make_ref<LocalStore::Config>(params);
|
||||
}
|
||||
warn("%s does not exist, so Nix will use %s as a chroot store", stateDir, PathFmt(chrootStore));
|
||||
warn("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore);
|
||||
} else
|
||||
debug(
|
||||
"%s does not exist, so Nix will use %s as a chroot store", stateDir, PathFmt(chrootStore));
|
||||
return make_ref<LocalStore::Config>("local", chrootStore.string(), params);
|
||||
debug("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore);
|
||||
return make_ref<LocalStore::Config>("local", chrootStore, params);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -475,7 +475,7 @@ private:
|
||||
};
|
||||
|
||||
static void handleDiffHook(
|
||||
const std::filesystem::path & diffHook,
|
||||
const Path & diffHook,
|
||||
uid_t uid,
|
||||
uid_t gid,
|
||||
const std::filesystem::path & tryA,
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
#include "nix/util/file-descriptor.hh"
|
||||
#include "nix/util/serialise.hh"
|
||||
#include "nix/util/file-system.hh"
|
||||
#include "nix/util/fs-sink.hh"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -245,166 +243,4 @@ TEST(BufferedSourceReadLine, BufferExhaustedThenEof)
|
||||
EXPECT_EQ(source.readLine(/*eofOk=*/true), "");
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* readLinkAt
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST(readLinkAt, works)
|
||||
{
|
||||
std::filesystem::path tmpDir = nix::createTempDir();
|
||||
nix::AutoDelete delTmpDir(tmpDir, /*recursive=*/true);
|
||||
|
||||
constexpr size_t maxPathLength =
|
||||
#ifdef _WIN32
|
||||
260
|
||||
#else
|
||||
PATH_MAX
|
||||
#endif
|
||||
;
|
||||
std::string mediumTarget(maxPathLength / 2, 'x');
|
||||
std::string longTarget(maxPathLength - 1, 'y');
|
||||
|
||||
{
|
||||
RestoreSink sink(/*startFsync=*/false);
|
||||
sink.dstPath = tmpDir;
|
||||
sink.dirFd = openDirectory(tmpDir);
|
||||
sink.createSymlink(CanonPath("link"), "target");
|
||||
sink.createSymlink(CanonPath("relative"), "../relative/path");
|
||||
sink.createSymlink(CanonPath("absolute"), "/absolute/path");
|
||||
sink.createSymlink(CanonPath("medium"), mediumTarget);
|
||||
sink.createSymlink(CanonPath("long"), longTarget);
|
||||
sink.createDirectory(CanonPath("a"));
|
||||
sink.createDirectory(CanonPath("a/b"));
|
||||
sink.createSymlink(CanonPath("a/b/link"), "nested_target");
|
||||
sink.createRegularFile(CanonPath("regular"), [](CreateRegularFileSink &) {});
|
||||
sink.createDirectory(CanonPath("dir"));
|
||||
}
|
||||
|
||||
AutoCloseFD dirFd = openDirectory(tmpDir);
|
||||
|
||||
EXPECT_EQ(readLinkAt(dirFd.get(), CanonPath("link")), OS_STR("target"));
|
||||
EXPECT_EQ(readLinkAt(dirFd.get(), CanonPath("relative")), OS_STR("../relative/path"));
|
||||
EXPECT_EQ(readLinkAt(dirFd.get(), CanonPath("absolute")), OS_STR("/absolute/path"));
|
||||
EXPECT_EQ(readLinkAt(dirFd.get(), CanonPath("medium")), string_to_os_string(mediumTarget));
|
||||
EXPECT_EQ(readLinkAt(dirFd.get(), CanonPath("long")), string_to_os_string(longTarget));
|
||||
EXPECT_EQ(readLinkAt(dirFd.get(), CanonPath("a/b/link")), OS_STR("nested_target"));
|
||||
|
||||
AutoCloseFD subDirFd = openDirectory(tmpDir / "a");
|
||||
EXPECT_EQ(readLinkAt(subDirFd.get(), CanonPath("b/link")), OS_STR("nested_target"));
|
||||
|
||||
// Test error cases - expect SystemError on both platforms
|
||||
EXPECT_THROW(readLinkAt(dirFd.get(), CanonPath("regular")), SystemError);
|
||||
EXPECT_THROW(readLinkAt(dirFd.get(), CanonPath("dir")), SystemError);
|
||||
EXPECT_THROW(readLinkAt(dirFd.get(), CanonPath("nonexistent")), SystemError);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* openFileEnsureBeneathNoSymlinks
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST(openFileEnsureBeneathNoSymlinks, works)
|
||||
{
|
||||
std::filesystem::path tmpDir = nix::createTempDir();
|
||||
nix::AutoDelete delTmpDir(tmpDir, /*recursive=*/true);
|
||||
|
||||
{
|
||||
RestoreSink sink(/*startFsync=*/false);
|
||||
sink.dstPath = tmpDir;
|
||||
sink.dirFd = openDirectory(tmpDir);
|
||||
sink.createDirectory(CanonPath("a"));
|
||||
sink.createDirectory(CanonPath("c"));
|
||||
sink.createDirectory(CanonPath("c/d"));
|
||||
sink.createRegularFile(CanonPath("c/d/regular"), [](CreateRegularFileSink & crf) { crf("some contents"); });
|
||||
sink.createSymlink(CanonPath("a/absolute_symlink"), tmpDir.string());
|
||||
sink.createSymlink(CanonPath("a/relative_symlink"), "../.");
|
||||
sink.createSymlink(CanonPath("a/broken_symlink"), "./nonexistent");
|
||||
sink.createDirectory(CanonPath("a/b"), [](FileSystemObjectSink & dirSink, const CanonPath & relPath) {
|
||||
dirSink.createDirectory(CanonPath("d"));
|
||||
dirSink.createSymlink(CanonPath("c"), "./d");
|
||||
});
|
||||
// FIXME: This still follows symlinks on Unix (incorrectly succeeds)
|
||||
sink.createDirectory(CanonPath("a/b/c/e"));
|
||||
// Test that symlinks in intermediate path are detected during nested operations
|
||||
ASSERT_THROW(
|
||||
sink.createDirectory(
|
||||
CanonPath("a/b/c/f"), [](FileSystemObjectSink & dirSink, const CanonPath & relPath) {}),
|
||||
SymlinkNotAllowed);
|
||||
ASSERT_THROW(
|
||||
sink.createRegularFile(
|
||||
CanonPath("a/b/c/regular"), [](CreateRegularFileSink & crf) { crf("some contents"); }),
|
||||
SymlinkNotAllowed);
|
||||
}
|
||||
|
||||
AutoCloseFD dirFd = openDirectory(tmpDir);
|
||||
|
||||
// Helper to open files with platform-specific arguments
|
||||
auto openRead = [&](std::string_view path) -> Descriptor {
|
||||
return openFileEnsureBeneathNoSymlinks(
|
||||
dirFd.get(),
|
||||
CanonPath(path),
|
||||
#ifdef _WIN32
|
||||
FILE_READ_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
|
||||
0
|
||||
#else
|
||||
O_RDONLY,
|
||||
0
|
||||
#endif
|
||||
);
|
||||
};
|
||||
|
||||
auto openReadDir = [&](std::string_view path) -> Descriptor {
|
||||
return openFileEnsureBeneathNoSymlinks(
|
||||
dirFd.get(),
|
||||
CanonPath(path),
|
||||
#ifdef _WIN32
|
||||
FILE_READ_ATTRIBUTES | SYNCHRONIZE,
|
||||
FILE_DIRECTORY_FILE
|
||||
#else
|
||||
O_RDONLY | O_DIRECTORY,
|
||||
0
|
||||
#endif
|
||||
);
|
||||
};
|
||||
|
||||
auto openCreateExclusive = [&](std::string_view path) -> Descriptor {
|
||||
return openFileEnsureBeneathNoSymlinks(
|
||||
dirFd.get(),
|
||||
CanonPath(path),
|
||||
#ifdef _WIN32
|
||||
FILE_WRITE_DATA | SYNCHRONIZE,
|
||||
0,
|
||||
FILE_CREATE // Create new file, fail if exists (equivalent to O_CREAT | O_EXCL)
|
||||
#else
|
||||
O_CREAT | O_WRONLY | O_EXCL,
|
||||
0666
|
||||
#endif
|
||||
);
|
||||
};
|
||||
|
||||
// Test that symlinks are detected and rejected
|
||||
EXPECT_THROW(openRead("a/absolute_symlink"), SymlinkNotAllowed);
|
||||
EXPECT_THROW(openRead("a/relative_symlink"), SymlinkNotAllowed);
|
||||
EXPECT_THROW(openRead("a/absolute_symlink/a"), SymlinkNotAllowed);
|
||||
EXPECT_THROW(openRead("a/absolute_symlink/c/d"), SymlinkNotAllowed);
|
||||
EXPECT_THROW(openRead("a/relative_symlink/c"), SymlinkNotAllowed);
|
||||
EXPECT_THROW(openRead("a/b/c/d"), SymlinkNotAllowed);
|
||||
EXPECT_THROW(openRead("a/broken_symlink"), SymlinkNotAllowed);
|
||||
|
||||
#if !defined(_WIN32) && !defined(__CYGWIN__)
|
||||
// This returns ELOOP on cygwin when O_NOFOLLOW is used
|
||||
EXPECT_EQ(openCreateExclusive("a/broken_symlink"), INVALID_DESCRIPTOR);
|
||||
/* Sanity check, no symlink shenanigans and behaves the same as regular openat with O_EXCL | O_CREAT. */
|
||||
EXPECT_EQ(errno, EEXIST);
|
||||
#endif
|
||||
EXPECT_THROW(openCreateExclusive("a/absolute_symlink/broken_symlink"), SymlinkNotAllowed);
|
||||
|
||||
// Test invalid paths
|
||||
EXPECT_EQ(openRead("c/d/regular/a"), INVALID_DESCRIPTOR);
|
||||
EXPECT_EQ(openReadDir("c/d/regular"), INVALID_DESCRIPTOR);
|
||||
|
||||
// Test valid paths work
|
||||
EXPECT_TRUE(AutoCloseFD{openRead("c/d/regular")});
|
||||
EXPECT_TRUE(AutoCloseFD{openCreateExclusive("a/regular")});
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
@@ -0,0 +1,172 @@
|
||||
#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"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* readLinkAt
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST(readLinkAt, works)
|
||||
{
|
||||
std::filesystem::path tmpDir = nix::createTempDir();
|
||||
nix::AutoDelete delTmpDir(tmpDir, /*recursive=*/true);
|
||||
|
||||
constexpr size_t maxPathLength =
|
||||
#ifdef _WIN32
|
||||
260
|
||||
#else
|
||||
PATH_MAX
|
||||
#endif
|
||||
;
|
||||
std::string mediumTarget(maxPathLength / 2, 'x');
|
||||
std::string longTarget(maxPathLength - 1, 'y');
|
||||
|
||||
{
|
||||
RestoreSink sink(/*startFsync=*/false);
|
||||
sink.dstPath = tmpDir;
|
||||
sink.dirFd = openDirectory(tmpDir);
|
||||
sink.createSymlink(CanonPath("link"), "target");
|
||||
sink.createSymlink(CanonPath("relative"), "../relative/path");
|
||||
sink.createSymlink(CanonPath("absolute"), "/absolute/path");
|
||||
sink.createSymlink(CanonPath("medium"), mediumTarget);
|
||||
sink.createSymlink(CanonPath("long"), longTarget);
|
||||
sink.createDirectory(CanonPath("a"));
|
||||
sink.createDirectory(CanonPath("a/b"));
|
||||
sink.createSymlink(CanonPath("a/b/link"), "nested_target");
|
||||
sink.createRegularFile(CanonPath("regular"), [](CreateRegularFileSink &) {});
|
||||
sink.createDirectory(CanonPath("dir"));
|
||||
}
|
||||
|
||||
AutoCloseFD dirFd = openDirectory(tmpDir);
|
||||
|
||||
EXPECT_EQ(readLinkAt(dirFd.get(), CanonPath("link")), OS_STR("target"));
|
||||
EXPECT_EQ(readLinkAt(dirFd.get(), CanonPath("relative")), OS_STR("../relative/path"));
|
||||
EXPECT_EQ(readLinkAt(dirFd.get(), CanonPath("absolute")), OS_STR("/absolute/path"));
|
||||
EXPECT_EQ(readLinkAt(dirFd.get(), CanonPath("medium")), string_to_os_string(mediumTarget));
|
||||
EXPECT_EQ(readLinkAt(dirFd.get(), CanonPath("long")), string_to_os_string(longTarget));
|
||||
EXPECT_EQ(readLinkAt(dirFd.get(), CanonPath("a/b/link")), OS_STR("nested_target"));
|
||||
|
||||
AutoCloseFD subDirFd = openDirectory(tmpDir / "a");
|
||||
EXPECT_EQ(readLinkAt(subDirFd.get(), CanonPath("b/link")), OS_STR("nested_target"));
|
||||
|
||||
// Test error cases - expect SystemError on both platforms
|
||||
EXPECT_THROW(readLinkAt(dirFd.get(), CanonPath("regular")), SystemError);
|
||||
EXPECT_THROW(readLinkAt(dirFd.get(), CanonPath("dir")), SystemError);
|
||||
EXPECT_THROW(readLinkAt(dirFd.get(), CanonPath("nonexistent")), SystemError);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* openFileEnsureBeneathNoSymlinks
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST(openFileEnsureBeneathNoSymlinks, works)
|
||||
{
|
||||
std::filesystem::path tmpDir = nix::createTempDir();
|
||||
nix::AutoDelete delTmpDir(tmpDir, /*recursive=*/true);
|
||||
|
||||
{
|
||||
RestoreSink sink(/*startFsync=*/false);
|
||||
sink.dstPath = tmpDir;
|
||||
sink.dirFd = openDirectory(tmpDir);
|
||||
sink.createDirectory(CanonPath("a"));
|
||||
sink.createDirectory(CanonPath("c"));
|
||||
sink.createDirectory(CanonPath("c/d"));
|
||||
sink.createRegularFile(CanonPath("c/d/regular"), [](CreateRegularFileSink & crf) { crf("some contents"); });
|
||||
sink.createSymlink(CanonPath("a/absolute_symlink"), tmpDir.string());
|
||||
sink.createSymlink(CanonPath("a/relative_symlink"), "../.");
|
||||
sink.createSymlink(CanonPath("a/broken_symlink"), "./nonexistent");
|
||||
sink.createDirectory(CanonPath("a/b"), [](FileSystemObjectSink & dirSink, const CanonPath & relPath) {
|
||||
dirSink.createDirectory(CanonPath("d"));
|
||||
dirSink.createSymlink(CanonPath("c"), "./d");
|
||||
});
|
||||
// FIXME: This still follows symlinks on Unix (incorrectly succeeds)
|
||||
sink.createDirectory(CanonPath("a/b/c/e"));
|
||||
// Test that symlinks in intermediate path are detected during nested operations
|
||||
ASSERT_THROW(
|
||||
sink.createDirectory(
|
||||
CanonPath("a/b/c/f"), [](FileSystemObjectSink & dirSink, const CanonPath & relPath) {}),
|
||||
SymlinkNotAllowed);
|
||||
ASSERT_THROW(
|
||||
sink.createRegularFile(
|
||||
CanonPath("a/b/c/regular"), [](CreateRegularFileSink & crf) { crf("some contents"); }),
|
||||
SymlinkNotAllowed);
|
||||
}
|
||||
|
||||
AutoCloseFD dirFd = openDirectory(tmpDir);
|
||||
|
||||
// Helper to open files with platform-specific arguments
|
||||
auto openRead = [&](std::string_view path) -> Descriptor {
|
||||
return openFileEnsureBeneathNoSymlinks(
|
||||
dirFd.get(),
|
||||
CanonPath(path),
|
||||
#ifdef _WIN32
|
||||
FILE_READ_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
|
||||
0
|
||||
#else
|
||||
O_RDONLY,
|
||||
0
|
||||
#endif
|
||||
);
|
||||
};
|
||||
|
||||
auto openReadDir = [&](std::string_view path) -> Descriptor {
|
||||
return openFileEnsureBeneathNoSymlinks(
|
||||
dirFd.get(),
|
||||
CanonPath(path),
|
||||
#ifdef _WIN32
|
||||
FILE_READ_ATTRIBUTES | SYNCHRONIZE,
|
||||
FILE_DIRECTORY_FILE
|
||||
#else
|
||||
O_RDONLY | O_DIRECTORY,
|
||||
0
|
||||
#endif
|
||||
);
|
||||
};
|
||||
|
||||
auto openCreateExclusive = [&](std::string_view path) -> Descriptor {
|
||||
return openFileEnsureBeneathNoSymlinks(
|
||||
dirFd.get(),
|
||||
CanonPath(path),
|
||||
#ifdef _WIN32
|
||||
FILE_WRITE_DATA | SYNCHRONIZE,
|
||||
0,
|
||||
FILE_CREATE // Create new file, fail if exists (equivalent to O_CREAT | O_EXCL)
|
||||
#else
|
||||
O_CREAT | O_WRONLY | O_EXCL,
|
||||
0666
|
||||
#endif
|
||||
);
|
||||
};
|
||||
|
||||
// Test that symlinks are detected and rejected
|
||||
EXPECT_THROW(openRead("a/absolute_symlink"), SymlinkNotAllowed);
|
||||
EXPECT_THROW(openRead("a/relative_symlink"), SymlinkNotAllowed);
|
||||
EXPECT_THROW(openRead("a/absolute_symlink/a"), SymlinkNotAllowed);
|
||||
EXPECT_THROW(openRead("a/absolute_symlink/c/d"), SymlinkNotAllowed);
|
||||
EXPECT_THROW(openRead("a/relative_symlink/c"), SymlinkNotAllowed);
|
||||
EXPECT_THROW(openRead("a/b/c/d"), SymlinkNotAllowed);
|
||||
EXPECT_THROW(openRead("a/broken_symlink"), SymlinkNotAllowed);
|
||||
|
||||
#if !defined(_WIN32) && !defined(__CYGWIN__)
|
||||
// This returns ELOOP on cygwin when O_NOFOLLOW is used
|
||||
EXPECT_EQ(openCreateExclusive("a/broken_symlink"), INVALID_DESCRIPTOR);
|
||||
/* Sanity check, no symlink shenanigans and behaves the same as regular openat with O_EXCL | O_CREAT. */
|
||||
EXPECT_EQ(errno, EEXIST);
|
||||
#endif
|
||||
EXPECT_THROW(openCreateExclusive("a/absolute_symlink/broken_symlink"), SymlinkNotAllowed);
|
||||
|
||||
// Test invalid paths
|
||||
EXPECT_EQ(openRead("c/d/regular/a"), INVALID_DESCRIPTOR);
|
||||
EXPECT_EQ(openReadDir("c/d/regular"), INVALID_DESCRIPTOR);
|
||||
|
||||
// Test valid paths work
|
||||
EXPECT_TRUE(AutoCloseFD{openRead("c/d/regular")});
|
||||
EXPECT_TRUE(AutoCloseFD{openCreateExclusive("a/regular")});
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
@@ -60,10 +60,9 @@ TEST(absPath, usesOptionalBasePathWhenGiven)
|
||||
OsChar _cwd[PATH_MAX + 1];
|
||||
OsChar * cwd = GET_CWD((OsChar *) &_cwd, PATH_MAX);
|
||||
|
||||
auto cwdPath = std::filesystem::path{cwd};
|
||||
auto p = absPath(std::filesystem::path{""}.string(), &cwdPath);
|
||||
auto p = absPath(std::filesystem::path{""}.string(), std::filesystem::path{cwd}.string());
|
||||
|
||||
ASSERT_EQ(p, cwdPath.string());
|
||||
ASSERT_EQ(p, std::filesystem::path{cwd}.string());
|
||||
}
|
||||
|
||||
TEST(absPath, isIdempotent)
|
||||
@@ -122,6 +121,29 @@ TEST(canonPath, requiresAbsolutePath)
|
||||
ASSERT_DEATH({ canonPath(""sv); }, "path != \"\"");
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* dirOf
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST(dirOf, returnsEmptyStringForRoot)
|
||||
{
|
||||
auto p = dirOf("/");
|
||||
|
||||
ASSERT_EQ(p, "/");
|
||||
}
|
||||
|
||||
TEST(dirOf, returnsFirstPathComponent)
|
||||
{
|
||||
auto p1 = dirOf("/dir/");
|
||||
ASSERT_EQ(p1, "/dir");
|
||||
auto p2 = dirOf("/dir");
|
||||
ASSERT_EQ(p2, "/");
|
||||
auto p3 = dirOf("/dir/..");
|
||||
ASSERT_EQ(p3, "/dir");
|
||||
auto p4 = dirOf("/dir/../");
|
||||
ASSERT_EQ(p4, "/dir/..");
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* baseNameOf
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
@@ -60,6 +60,7 @@ sources = files(
|
||||
'executable-path.cc',
|
||||
'file-content-address.cc',
|
||||
'file-descriptor.cc',
|
||||
'file-system-at.cc',
|
||||
'file-system.cc',
|
||||
'git.cc',
|
||||
'hash.cc',
|
||||
@@ -75,6 +76,7 @@ sources = files(
|
||||
'pool.cc',
|
||||
'position.cc',
|
||||
'processes.cc',
|
||||
'ref.cc',
|
||||
'sort.cc',
|
||||
'source-accessor.cc',
|
||||
'spawn.cc',
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <type_traits>
|
||||
|
||||
#include "nix/util/demangle.hh"
|
||||
#include "nix/util/ref.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
// Test hierarchy for ref covariance tests
|
||||
struct Base
|
||||
{
|
||||
virtual ~Base() = default;
|
||||
};
|
||||
|
||||
struct Derived : Base
|
||||
{};
|
||||
|
||||
TEST(ref, upcast_is_implicit)
|
||||
{
|
||||
// ref<Derived> should be implicitly convertible to ref<Base>
|
||||
static_assert(std::is_convertible_v<ref<Derived>, ref<Base>>);
|
||||
|
||||
// Runtime test
|
||||
auto derived = make_ref<Derived>();
|
||||
ref<Base> base = derived; // implicit upcast
|
||||
EXPECT_NE(&*base, nullptr);
|
||||
}
|
||||
|
||||
TEST(ref, downcast_is_rejected)
|
||||
{
|
||||
// ref<Base> should NOT be implicitly convertible to ref<Derived>
|
||||
static_assert(!std::is_convertible_v<ref<Base>, ref<Derived>>);
|
||||
|
||||
// Uncomment to see error message:
|
||||
// auto base = make_ref<Base>();
|
||||
// ref<Derived> d = base;
|
||||
}
|
||||
|
||||
TEST(ref, same_type_conversion)
|
||||
{
|
||||
// ref<T> should be convertible to ref<T>
|
||||
static_assert(std::is_convertible_v<ref<Base>, ref<Base>>);
|
||||
static_assert(std::is_convertible_v<ref<Derived>, ref<Derived>>);
|
||||
}
|
||||
|
||||
TEST(ref, explicit_downcast_with_cast)
|
||||
{
|
||||
// .cast() should work for valid downcasts at runtime
|
||||
auto derived = make_ref<Derived>();
|
||||
ref<Base> base = derived;
|
||||
|
||||
// Downcast back to Derived using .cast()
|
||||
ref<Derived> backToDerived = base.cast<Derived>();
|
||||
EXPECT_NE(&*backToDerived, nullptr);
|
||||
}
|
||||
|
||||
TEST(ref, invalid_cast_throws)
|
||||
{
|
||||
// .cast() throws bad_ref_cast (a std::bad_cast subclass) with type info on invalid downcast
|
||||
// (unlike .dynamic_pointer_cast() which returns nullptr)
|
||||
auto base = make_ref<Base>();
|
||||
try {
|
||||
base.cast<Derived>();
|
||||
FAIL() << "Expected bad_ref_cast";
|
||||
} catch (const bad_ref_cast & e) {
|
||||
std::string expected = "ref<" + demangle(typeid(Base).name()) + "> cannot be cast to ref<"
|
||||
+ demangle(typeid(Derived).name()) + ">";
|
||||
EXPECT_EQ(e.what(), expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ref, explicit_downcast_with_dynamic_pointer_cast)
|
||||
{
|
||||
// .dynamic_pointer_cast() returns nullptr for invalid casts
|
||||
auto base = make_ref<Base>();
|
||||
|
||||
// Invalid downcast returns nullptr
|
||||
auto invalidCast = base.dynamic_pointer_cast<Derived>();
|
||||
EXPECT_EQ(invalidCast, nullptr);
|
||||
|
||||
// Valid downcast returns non-null
|
||||
auto derived = make_ref<Derived>();
|
||||
ref<Base> baseFromDerived = derived;
|
||||
auto validCast = baseFromDerived.dynamic_pointer_cast<Derived>();
|
||||
EXPECT_NE(validCast, nullptr);
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "nix/util/file-descriptor.hh"
|
||||
#include "nix/util/file-system-at.hh"
|
||||
#include "nix/util/file-system.hh"
|
||||
#include "nix/util/fs-sink.hh"
|
||||
#include "nix/util/processes.hh"
|
||||
@@ -1,3 +1,3 @@
|
||||
sources += files(
|
||||
'file-descriptor.cc',
|
||||
'file-system-at.cc',
|
||||
)
|
||||
@@ -32,7 +32,7 @@ static ArchiveSettings archiveSettings;
|
||||
|
||||
static GlobalConfig::Register rArchiveSettings(&archiveSettings);
|
||||
|
||||
PathFilter defaultPathFilter = [](const std::string &) { return true; };
|
||||
PathFilter defaultPathFilter = [](const Path &) { return true; };
|
||||
|
||||
void SourceAccessor::dumpPath(const CanonPath & path, Sink & sink, PathFilter & filter)
|
||||
{
|
||||
@@ -101,14 +101,14 @@ void SourceAccessor::dumpPath(const CanonPath & path, Sink & sink, PathFilter &
|
||||
}(path);
|
||||
}
|
||||
|
||||
time_t dumpPathAndGetMtime(const std::filesystem::path & path, Sink & sink, PathFilter & filter)
|
||||
time_t dumpPathAndGetMtime(const Path & path, Sink & sink, PathFilter & filter)
|
||||
{
|
||||
auto path2 = PosixSourceAccessor::createAtRoot(path, /*trackLastModified=*/true);
|
||||
path2.dumpPath(sink, filter);
|
||||
return path2.accessor->getLastModified().value();
|
||||
}
|
||||
|
||||
void dumpPath(const std::filesystem::path & path, Sink & sink, PathFilter & filter)
|
||||
void dumpPath(const Path & path, Sink & sink, PathFilter & filter)
|
||||
{
|
||||
dumpPathAndGetMtime(path, sink, filter);
|
||||
}
|
||||
@@ -201,7 +201,7 @@ static void parse(FileSystemObjectSink & sink, Source & source, const CanonPath
|
||||
|
||||
else if (type == "directory") {
|
||||
sink.createDirectory(path, [&](FileSystemObjectSink & dirSink, const CanonPath & relDirPath) {
|
||||
std::map<std::string, int, CaseInsensitiveCompare> names;
|
||||
std::map<Path, int, CaseInsensitiveCompare> names;
|
||||
|
||||
std::string prevName;
|
||||
|
||||
|
||||
+3
-3
@@ -284,7 +284,7 @@ void RootArgs::parseCmdline(const Strings & _cmdline, bool allowShebang)
|
||||
// executable file, and it starts with "#!".
|
||||
Strings savedArgs;
|
||||
if (allowShebang) {
|
||||
std::filesystem::path script = *cmdline.begin();
|
||||
auto script = *cmdline.begin();
|
||||
try {
|
||||
std::ifstream stream(script);
|
||||
char shebang[3] = {0, 0, 0};
|
||||
@@ -310,8 +310,8 @@ void RootArgs::parseCmdline(const Strings & _cmdline, bool allowShebang)
|
||||
for (const auto & word : parseShebangContent(shebangContent)) {
|
||||
cmdline.push_back(word);
|
||||
}
|
||||
cmdline.push_back(script.string());
|
||||
commandBaseDir = script.parent_path();
|
||||
cmdline.push_back(script);
|
||||
commandBaseDir = dirOf(script);
|
||||
for (auto pos = savedArgs.begin(); pos != savedArgs.end(); pos++)
|
||||
cmdline.push_back(*pos);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ void Config::getSettings(std::map<std::string, SettingInfo> & res, bool overridd
|
||||
*/
|
||||
static void parseConfigFiles(
|
||||
const std::string & contents,
|
||||
const std::filesystem::path & path,
|
||||
const std::string & path,
|
||||
std::vector<std::pair<std::string, std::string>> & parsedContents)
|
||||
{
|
||||
unsigned int pos = 0;
|
||||
@@ -122,7 +122,7 @@ static void parseConfigFiles(
|
||||
continue;
|
||||
|
||||
if (tokens.size() < 2)
|
||||
throw UsageError("syntax error in configuration line '%1%' in %s", line, PathFmt(path));
|
||||
throw UsageError("syntax error in configuration line '%1%' in '%2%'", line, path);
|
||||
|
||||
auto include = false;
|
||||
auto ignoreMissing = false;
|
||||
@@ -135,9 +135,8 @@ static void parseConfigFiles(
|
||||
|
||||
if (include) {
|
||||
if (tokens.size() != 2)
|
||||
throw UsageError("syntax error in configuration line '%1%' in %s", line, PathFmt(path));
|
||||
auto parent = path.parent_path();
|
||||
auto p = absPath(std::filesystem::path{tokens[1]}, &parent);
|
||||
throw UsageError("syntax error in configuration line '%1%' in '%2%'", line, path);
|
||||
auto p = absPath(tokens[1], dirOf(path));
|
||||
if (pathExists(p)) {
|
||||
try {
|
||||
std::string includedContents = readFile(p);
|
||||
@@ -146,13 +145,13 @@ static void parseConfigFiles(
|
||||
// TODO: Do we actually want to ignore this? Or is it better to fail?
|
||||
}
|
||||
} else if (!ignoreMissing) {
|
||||
throw Error("file %s included from %s not found", PathFmt(p), PathFmt(path));
|
||||
throw Error("file '%1%' included from '%2%' not found", p, path);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tokens[1] != "=")
|
||||
throw UsageError("syntax error in configuration line '%1%' in %s", line, PathFmt(path));
|
||||
throw UsageError("syntax error in configuration line '%1%' in '%2%'", line, path);
|
||||
|
||||
std::string name = std::move(tokens[0]);
|
||||
|
||||
@@ -457,7 +456,7 @@ std::string BaseSetting<StringMap>::to_string() const
|
||||
[](const auto & kvpair) { return kvpair.first + "=" + kvpair.second; });
|
||||
}
|
||||
|
||||
static std::filesystem::path parsePath(const AbstractSetting & s, const std::string & str)
|
||||
static Path parsePath(const AbstractSetting & s, const std::string & str)
|
||||
{
|
||||
if (str == "")
|
||||
throw UsageError("setting '%s' is a path and paths cannot be empty", s.name);
|
||||
@@ -523,7 +522,7 @@ PathSetting::PathSetting(
|
||||
|
||||
Path PathSetting::parse(const std::string & str) const
|
||||
{
|
||||
return parsePath(*this, str).string();
|
||||
return parsePath(*this, str);
|
||||
}
|
||||
|
||||
OptionalPathSetting::OptionalPathSetting(
|
||||
@@ -542,7 +541,7 @@ std::optional<Path> OptionalPathSetting::parse(const std::string & str) const
|
||||
if (str == "")
|
||||
return std::nullopt;
|
||||
else
|
||||
return parsePath(*this, str).string();
|
||||
return parsePath(*this, str);
|
||||
}
|
||||
|
||||
void OptionalPathSetting::operator=(const std::optional<Path> & v)
|
||||
|
||||
@@ -120,9 +120,9 @@ void restoreProcessContext(bool restoreMounts)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
std::optional<std::filesystem::path> getSelfExe()
|
||||
std::optional<Path> getSelfExe()
|
||||
{
|
||||
static auto cached = []() -> std::optional<std::filesystem::path> {
|
||||
static auto cached = []() -> std::optional<Path> {
|
||||
#if defined(__linux__) || defined(__GNU__)
|
||||
return readLink(std::filesystem::path{"/proc/self/exe"});
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
@@ -20,23 +20,17 @@ ExecutablePath ExecutablePath::load()
|
||||
}
|
||||
|
||||
ExecutablePath ExecutablePath::parse(const OsString & path)
|
||||
{
|
||||
ExecutablePath ret;
|
||||
ret.parseAppend(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ExecutablePath::parseAppend(const OsString & path)
|
||||
{
|
||||
auto strings = path.empty() ? (std::list<OsString>{})
|
||||
: basicSplitString<std::list<OsString>, OsChar>(path, path_var_separator);
|
||||
|
||||
directories.reserve(directories.size() + strings.size());
|
||||
std::vector<std::filesystem::path> ret;
|
||||
ret.reserve(strings.size());
|
||||
|
||||
std::transform(
|
||||
std::make_move_iterator(strings.begin()),
|
||||
std::make_move_iterator(strings.end()),
|
||||
std::back_inserter(directories),
|
||||
std::back_inserter(ret),
|
||||
[](OsString && str) {
|
||||
return std::filesystem::path{
|
||||
str.empty()
|
||||
@@ -51,11 +45,13 @@ void ExecutablePath::parseAppend(const OsString & path)
|
||||
: std::move(str),
|
||||
};
|
||||
});
|
||||
|
||||
return {ret};
|
||||
}
|
||||
|
||||
OsString ExecutablePath::render() const
|
||||
{
|
||||
std::vector<PathView> path2;
|
||||
std::vector<PathViewNG> path2;
|
||||
path2.reserve(directories.size());
|
||||
for (auto & p : directories)
|
||||
path2.push_back(p.native());
|
||||
|
||||
@@ -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, ")")))
|
||||
|
||||
@@ -75,7 +75,7 @@ void dumpPath(const SourcePath & path, Sink & sink, FileSerialisationMethod meth
|
||||
}
|
||||
}
|
||||
|
||||
void restorePath(const std::filesystem::path & path, Source & source, FileSerialisationMethod method, bool startFsync)
|
||||
void restorePath(const Path & path, Source & source, FileSerialisationMethod method, bool startFsync)
|
||||
{
|
||||
switch (method) {
|
||||
case FileSerialisationMethod::Flat:
|
||||
|
||||
+94
-63
@@ -64,14 +64,21 @@ DirectoryIterator & DirectoryIterator::operator++()
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::filesystem::path
|
||||
absPath(const std::filesystem::path & path0, const std::filesystem::path * dir, bool resolveSymlinks)
|
||||
bool isAbsolute(PathView path)
|
||||
{
|
||||
std::filesystem::path path = path0;
|
||||
return std::filesystem::path{path}.is_absolute();
|
||||
}
|
||||
|
||||
if (!path.is_absolute()) {
|
||||
Path absPath(PathView path, std::optional<PathView> dir, bool resolveSymlinks)
|
||||
{
|
||||
std::string scratch;
|
||||
|
||||
if (!isAbsolute(path)) {
|
||||
// In this case we need to call `canonPath` on a newly-created
|
||||
// string.
|
||||
// string. We set `scratch` to that string first, and then set
|
||||
// `path` to `scratch`. This ensures the newly-created string
|
||||
// lives long enough for the call to `canonPath`, and allows us
|
||||
// to just accept a `std::string_view`.
|
||||
if (!dir) {
|
||||
#ifdef __GNU__
|
||||
/* GNU (aka. GNU/Hurd) doesn't have any limitation on path
|
||||
@@ -83,22 +90,30 @@ absPath(const std::filesystem::path & path0, const std::filesystem::path * dir,
|
||||
if (!getcwd(buf, sizeof(buf)))
|
||||
#endif
|
||||
throw SysError("cannot get cwd");
|
||||
path = std::filesystem::path{buf} / path;
|
||||
scratch = concatStrings(buf, "/", path);
|
||||
#ifdef __GNU__
|
||||
free(buf);
|
||||
#endif
|
||||
} else
|
||||
path = *dir / path;
|
||||
scratch = concatStrings(*dir, "/", path);
|
||||
path = scratch;
|
||||
}
|
||||
return canonPath(path, resolveSymlinks);
|
||||
}
|
||||
|
||||
std::filesystem::path canonPath(const std::filesystem::path & path, bool resolveSymlinks)
|
||||
std::filesystem::path
|
||||
absPath(const std::filesystem::path & path, const std::filesystem::path * dir_, bool resolveSymlinks)
|
||||
{
|
||||
assert(!path.empty());
|
||||
std::optional<std::string> dir = dir_ ? std::optional<std::string>{dir_->string()} : std::nullopt;
|
||||
return absPath(PathView{path.string()}, dir.transform([](auto & p) { return PathView(p); }), resolveSymlinks);
|
||||
}
|
||||
|
||||
if (!path.is_absolute())
|
||||
throw Error("not an absolute path: %s", PathFmt(path));
|
||||
Path canonPath(PathView path, bool resolveSymlinks)
|
||||
{
|
||||
assert(path != "");
|
||||
|
||||
if (!isAbsolute(path))
|
||||
throw Error("not an absolute path: '%1%'", path);
|
||||
|
||||
/* This just exists because we cannot set the target of `remaining`
|
||||
(the callback parameter) directly to a newly-constructed string,
|
||||
@@ -110,19 +125,18 @@ std::filesystem::path canonPath(const std::filesystem::path & path, bool resolve
|
||||
unsigned int followCount = 0, maxFollow = 1024;
|
||||
|
||||
auto ret = canonPathInner<OsPathTrait<char>>(
|
||||
path.string(),
|
||||
[&followCount, &temp, maxFollow, resolveSymlinks](std::string & result, std::string_view & remaining) {
|
||||
path, [&followCount, &temp, maxFollow, resolveSymlinks](std::string & result, std::string_view & remaining) {
|
||||
if (resolveSymlinks && std::filesystem::is_symlink(result)) {
|
||||
if (++followCount >= maxFollow)
|
||||
throw Error("infinite symlink recursion in path '%1%'", remaining);
|
||||
remaining = (temp = concatStrings(readLink(result).string(), remaining));
|
||||
if (std::filesystem::path(remaining).is_absolute()) {
|
||||
remaining = (temp = concatStrings(readLink(result), remaining));
|
||||
if (isAbsolute(remaining)) {
|
||||
/* restart for symlinks pointing to absolute path */
|
||||
result.clear();
|
||||
} else {
|
||||
result = std::filesystem::path(result).parent_path().string();
|
||||
result = dirOf(result);
|
||||
if (result == "/") {
|
||||
/* we don't want trailing slashes here, which `dirOf`
|
||||
/* we don’t want trailing slashes here, which `dirOf`
|
||||
only produces if `result = /` */
|
||||
result.clear();
|
||||
}
|
||||
@@ -133,6 +147,19 @@ std::filesystem::path canonPath(const std::filesystem::path & path, bool resolve
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::filesystem::path canonPath(const std::filesystem::path & path, bool resolveSymlinks)
|
||||
{
|
||||
return {canonPath(path.string(), resolveSymlinks)};
|
||||
}
|
||||
|
||||
Path dirOf(const PathView path)
|
||||
{
|
||||
Path::size_type pos = OsPathTrait<char>::rfindPathSep(path);
|
||||
if (pos == path.npos)
|
||||
return ".";
|
||||
return std::filesystem::path{path}.parent_path().string();
|
||||
}
|
||||
|
||||
std::string_view baseNameOf(std::string_view path)
|
||||
{
|
||||
if (path.empty())
|
||||
@@ -235,13 +262,13 @@ std::optional<PosixStat> maybeLstat(const std::filesystem::path & path)
|
||||
|
||||
bool pathExists(const std::filesystem::path & path)
|
||||
{
|
||||
return maybeLstat(path).has_value();
|
||||
return maybeLstat(path.string()).has_value();
|
||||
}
|
||||
|
||||
bool pathAccessible(const std::filesystem::path & path)
|
||||
{
|
||||
try {
|
||||
return pathExists(path);
|
||||
return pathExists(path.string());
|
||||
} catch (SystemError & e) {
|
||||
// swallow EPERM
|
||||
if (e.is(std::errc::operation_not_permitted))
|
||||
@@ -256,6 +283,16 @@ std::filesystem::path readLink(const std::filesystem::path & path)
|
||||
return std::filesystem::read_symlink(path);
|
||||
}
|
||||
|
||||
Path readLink(const Path & path)
|
||||
{
|
||||
return readLink(std::filesystem::path{path}).string();
|
||||
}
|
||||
|
||||
std::string readFile(const Path & path)
|
||||
{
|
||||
return readFile(std::filesystem::path(path));
|
||||
}
|
||||
|
||||
std::string readFile(const std::filesystem::path & path)
|
||||
{
|
||||
AutoCloseFD fd = openFileReadonly(path);
|
||||
@@ -264,32 +301,32 @@ std::string readFile(const std::filesystem::path & path)
|
||||
return readFile(fd.get());
|
||||
}
|
||||
|
||||
void readFile(const std::filesystem::path & path, Sink & sink, bool memory_map)
|
||||
void readFile(const Path & path, Sink & sink, bool memory_map)
|
||||
{
|
||||
// Memory-map the file for faster processing where possible.
|
||||
if (memory_map) {
|
||||
try {
|
||||
boost::iostreams::mapped_file_source mmap(path.string());
|
||||
boost::iostreams::mapped_file_source mmap(path);
|
||||
if (mmap.is_open()) {
|
||||
sink({mmap.data(), mmap.size()});
|
||||
return;
|
||||
}
|
||||
} catch (const boost::exception & e) {
|
||||
}
|
||||
debug("memory-mapping failed for path: %s", PathFmt(path));
|
||||
debug("memory-mapping failed for path: %s", path);
|
||||
}
|
||||
|
||||
// Stream the file instead if memory-mapping fails or is disabled.
|
||||
AutoCloseFD fd = openFileReadonly(std::filesystem::path(path));
|
||||
if (!fd)
|
||||
throw NativeSysError("opening file %s", PathFmt(path));
|
||||
throw NativeSysError("opening file %s", path);
|
||||
drainFD(fd.get(), sink);
|
||||
}
|
||||
|
||||
void writeFile(const std::filesystem::path & path, std::string_view s, mode_t mode, FsSync sync)
|
||||
void writeFile(const Path & path, std::string_view s, mode_t mode, FsSync sync)
|
||||
{
|
||||
AutoCloseFD fd = toDescriptor(open(
|
||||
path.string().c_str(),
|
||||
path.c_str(),
|
||||
O_WRONLY | O_TRUNC | O_CREAT
|
||||
#ifdef O_CLOEXEC
|
||||
| O_CLOEXEC
|
||||
@@ -297,15 +334,15 @@ void writeFile(const std::filesystem::path & path, std::string_view s, mode_t mo
|
||||
,
|
||||
mode));
|
||||
if (!fd)
|
||||
throw SysError("opening file %s", PathFmt(path));
|
||||
throw SysError("opening file '%1%'", path);
|
||||
|
||||
writeFile(fd, path.string(), s, mode, sync);
|
||||
writeFile(fd, path, s, mode, sync);
|
||||
|
||||
/* Close explicitly to propagate the exceptions. */
|
||||
fd.close();
|
||||
}
|
||||
|
||||
void writeFile(AutoCloseFD & fd, const std::filesystem::path & origPath, std::string_view s, mode_t mode, FsSync sync)
|
||||
void writeFile(AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode, FsSync sync)
|
||||
{
|
||||
assert(fd);
|
||||
try {
|
||||
@@ -315,15 +352,15 @@ void writeFile(AutoCloseFD & fd, const std::filesystem::path & origPath, std::st
|
||||
fd.fsync();
|
||||
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "writing file %s", PathFmt(origPath));
|
||||
e.addTrace({}, "writing file '%1%'", origPath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void writeFile(const std::filesystem::path & path, Source & source, mode_t mode, FsSync sync)
|
||||
void writeFile(const Path & path, Source & source, mode_t mode, FsSync sync)
|
||||
{
|
||||
AutoCloseFD fd = toDescriptor(open(
|
||||
path.string().c_str(),
|
||||
path.c_str(),
|
||||
O_WRONLY | O_TRUNC | O_CREAT
|
||||
#ifdef O_CLOEXEC
|
||||
| O_CLOEXEC
|
||||
@@ -331,7 +368,7 @@ void writeFile(const std::filesystem::path & path, Source & source, mode_t mode,
|
||||
,
|
||||
mode));
|
||||
if (!fd)
|
||||
throw SysError("opening file %s", PathFmt(path));
|
||||
throw SysError("opening file '%1%'", path);
|
||||
|
||||
std::array<char, 64 * 1024> buf;
|
||||
|
||||
@@ -345,7 +382,7 @@ void writeFile(const std::filesystem::path & path, Source & source, mode_t mode,
|
||||
}
|
||||
}
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "writing file %s", PathFmt(path));
|
||||
e.addTrace({}, "writing file '%1%'", path);
|
||||
throw;
|
||||
}
|
||||
if (sync == FsSync::Yes)
|
||||
@@ -356,22 +393,22 @@ void writeFile(const std::filesystem::path & path, Source & source, mode_t mode,
|
||||
syncParent(path);
|
||||
}
|
||||
|
||||
void syncParent(const std::filesystem::path & path)
|
||||
void syncParent(const Path & path)
|
||||
{
|
||||
AutoCloseFD fd = toDescriptor(open(path.parent_path().c_str(), O_RDONLY, 0));
|
||||
AutoCloseFD fd = toDescriptor(open(dirOf(path).c_str(), O_RDONLY, 0));
|
||||
if (!fd)
|
||||
throw SysError("opening file %s", PathFmt(path));
|
||||
throw SysError("opening file '%1%'", path);
|
||||
fd.fsync();
|
||||
}
|
||||
|
||||
void recursiveSync(const std::filesystem::path & path)
|
||||
void recursiveSync(const Path & path)
|
||||
{
|
||||
/* If it's a file or symlink, just fsync and return. */
|
||||
auto st = lstat(path);
|
||||
if (S_ISREG(st.st_mode)) {
|
||||
AutoCloseFD fd = toDescriptor(open(path.string().c_str(), O_RDONLY, 0));
|
||||
AutoCloseFD fd = toDescriptor(open(path.c_str(), O_RDONLY, 0));
|
||||
if (!fd)
|
||||
throw SysError("opening file %s", PathFmt(path));
|
||||
throw SysError("opening file '%1%'", path);
|
||||
fd.fsync();
|
||||
return;
|
||||
} else if (S_ISLNK(st.st_mode))
|
||||
@@ -408,17 +445,17 @@ void recursiveSync(const std::filesystem::path & path)
|
||||
}
|
||||
}
|
||||
|
||||
void createDir(const std::filesystem::path & path, mode_t mode)
|
||||
void createDir(const Path & path, mode_t mode)
|
||||
{
|
||||
if (mkdir(
|
||||
path.string().c_str()
|
||||
path.c_str()
|
||||
#ifndef _WIN32
|
||||
,
|
||||
mode
|
||||
#endif
|
||||
)
|
||||
== -1)
|
||||
throw SysError("creating directory '%s'", PathFmt(path));
|
||||
throw SysError("creating directory '%1%'", path);
|
||||
}
|
||||
|
||||
void createDirs(const std::filesystem::path & path)
|
||||
@@ -578,7 +615,7 @@ AutoCloseFD createAnonymousTempFile()
|
||||
# endif
|
||||
auto [fd2, path] = createTempFile("nix-anonymous");
|
||||
if (!fd2)
|
||||
throw SysError("creating temporary file %s", PathFmt(path));
|
||||
throw SysError("creating temporary file '%s'", path);
|
||||
fd = std::move(fd2);
|
||||
unlink(requireCString(path)); /* We only care about the file descriptor. */
|
||||
#endif
|
||||
@@ -586,21 +623,14 @@ AutoCloseFD createAnonymousTempFile()
|
||||
return fd;
|
||||
}
|
||||
|
||||
std::pair<AutoCloseFD, std::filesystem::path> createTempFile(const std::filesystem::path & prefix)
|
||||
std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
|
||||
{
|
||||
std::filesystem::path tmpl(defaultTempDir() / (prefix.string() + ".XXXXXX"));
|
||||
Path tmpl(defaultTempDir().string() + "/" + prefix + ".XXXXXX");
|
||||
// Strictly speaking, this is UB, but who cares...
|
||||
// FIXME: use O_TMPFILE.
|
||||
AutoCloseFD fd = toDescriptor(
|
||||
#ifdef _WIN32
|
||||
_wmkstemp(tmpl.c_str())
|
||||
#else
|
||||
mkstemp(const_cast<char *>(tmpl.c_str()))
|
||||
#endif
|
||||
);
|
||||
|
||||
AutoCloseFD fd = toDescriptor(mkstemp((char *) tmpl.c_str()));
|
||||
if (!fd)
|
||||
throw SysError("creating temporary file %s", PathFmt(tmpl));
|
||||
throw SysError("creating temporary file '%s'", tmpl);
|
||||
#ifndef _WIN32
|
||||
unix::closeOnExec(fd.get());
|
||||
#endif
|
||||
@@ -612,15 +642,15 @@ std::filesystem::path makeTempPath(const std::filesystem::path & root, const std
|
||||
// start the counter at a random value to minimize issues with preexisting temp paths
|
||||
static std::atomic<uint32_t> counter(std::random_device{}());
|
||||
auto tmpRoot = canonPath(root.empty() ? defaultTempDir().string() : root.string(), true);
|
||||
return tmpRoot / fmt("%2%-%3%-%4%", suffix, getpid(), counter.fetch_add(1, std::memory_order_relaxed));
|
||||
return fmt("%1%/%2%-%3%-%4%", tmpRoot, suffix, getpid(), counter.fetch_add(1, std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
void createSymlink(const std::filesystem::path & target, const std::filesystem::path & link)
|
||||
void createSymlink(const Path & target, const Path & link)
|
||||
{
|
||||
std::error_code ec;
|
||||
std::filesystem::create_symlink(target, link, ec);
|
||||
if (ec)
|
||||
throw SysError(ec.value(), "creating symlink %s -> %s", PathFmt(link), PathFmt(target));
|
||||
throw SysError(ec.value(), "creating symlink '%1%' -> '%2%'", link, target);
|
||||
}
|
||||
|
||||
void replaceSymlink(const std::filesystem::path & target, const std::filesystem::path & link)
|
||||
@@ -678,7 +708,7 @@ void copyFile(const std::filesystem::path & from, const std::filesystem::path &
|
||||
throw Error("file %s has an unsupported type", PathFmt(from));
|
||||
}
|
||||
|
||||
setWriteTime(to, lstat(from));
|
||||
setWriteTime(to, lstat(from.string().c_str()));
|
||||
if (andDelete) {
|
||||
if (!std::filesystem::is_symlink(fromStatus))
|
||||
std::filesystem::permissions(
|
||||
@@ -689,13 +719,13 @@ void copyFile(const std::filesystem::path & from, const std::filesystem::path &
|
||||
}
|
||||
}
|
||||
|
||||
void moveFile(const std::filesystem::path & oldName, const std::filesystem::path & newName)
|
||||
void moveFile(const Path & oldName, const Path & newName)
|
||||
{
|
||||
try {
|
||||
std::filesystem::rename(oldName, newName);
|
||||
} catch (std::filesystem::filesystem_error & e) {
|
||||
auto oldPath = oldName;
|
||||
auto newPath = newName;
|
||||
auto oldPath = std::filesystem::path(oldName);
|
||||
auto newPath = std::filesystem::path(newName);
|
||||
// For the move to be as atomic as possible, copy to a temporary
|
||||
// directory
|
||||
std::filesystem::path temp =
|
||||
@@ -704,7 +734,7 @@ void moveFile(const std::filesystem::path & oldName, const std::filesystem::path
|
||||
auto tempCopyTarget = temp / "copy-target";
|
||||
if (e.code().value() == EXDEV) {
|
||||
std::filesystem::remove(newPath);
|
||||
warn("can't rename %s as %s, copying instead", PathFmt(oldName), PathFmt(newName));
|
||||
warn("can’t rename %s as %s, copying instead", oldName, newName);
|
||||
copyFile(oldPath, tempCopyTarget, true);
|
||||
std::filesystem::rename(
|
||||
os_string_to_string(PathViewNG{tempCopyTarget}), os_string_to_string(PathViewNG{newPath}));
|
||||
@@ -762,7 +792,8 @@ void chmod(const std::filesystem::path & path, mode_t mode)
|
||||
|
||||
bool chmodIfNeeded(const std::filesystem::path & path, mode_t mode, mode_t mask)
|
||||
{
|
||||
auto prevMode = lstat(path).st_mode;
|
||||
auto pathString = path.string();
|
||||
auto prevMode = lstat(pathString).st_mode;
|
||||
|
||||
if (((prevMode ^ mode) & mask) == 0)
|
||||
return false;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "nix/util/error.hh"
|
||||
#include "nix/util/config-global.hh"
|
||||
#include "nix/util/file-system-at.hh"
|
||||
#include "nix/util/fs-sink.hh"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
+1
-1
@@ -369,7 +369,7 @@ Hash hashString(HashAlgorithm ha, std::string_view s, const ExperimentalFeatureS
|
||||
return hash;
|
||||
}
|
||||
|
||||
Hash hashFile(HashAlgorithm ha, const std::filesystem::path & path)
|
||||
Hash hashFile(HashAlgorithm ha, const Path & path)
|
||||
{
|
||||
HashSink sink(ha);
|
||||
readFile(path, sink);
|
||||
|
||||
@@ -55,12 +55,12 @@ namespace nix {
|
||||
* `+` denotes string concatenation.
|
||||
* ```
|
||||
*/
|
||||
void dumpPath(const std::filesystem::path & path, Sink & sink, PathFilter & filter = defaultPathFilter);
|
||||
void dumpPath(const Path & path, Sink & sink, PathFilter & filter = defaultPathFilter);
|
||||
|
||||
/**
|
||||
* Same as dumpPath(), but returns the last modified date of the path.
|
||||
*/
|
||||
time_t dumpPathAndGetMtime(const std::filesystem::path & path, Sink & sink, PathFilter & filter = defaultPathFilter);
|
||||
time_t dumpPathAndGetMtime(const Path & path, Sink & sink, PathFilter & filter = defaultPathFilter);
|
||||
|
||||
/**
|
||||
* Dump an archive with a single file with these contents.
|
||||
|
||||
@@ -134,7 +134,6 @@ DECLARE_CONFIG_SERIALISER(bool)
|
||||
DECLARE_CONFIG_SERIALISER(Strings)
|
||||
DECLARE_CONFIG_SERIALISER(StringSet)
|
||||
DECLARE_CONFIG_SERIALISER(StringMap)
|
||||
DECLARE_CONFIG_SERIALISER(std::filesystem::path)
|
||||
DECLARE_CONFIG_SERIALISER(std::set<ExperimentalFeature>)
|
||||
DECLARE_CONFIG_SERIALISER(std::filesystem::path)
|
||||
DECLARE_CONFIG_SERIALISER(std::optional<std::filesystem::path>)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
///@file
|
||||
|
||||
#include <cassert>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
|
||||
@@ -42,6 +42,6 @@ void restoreProcessContext(bool restoreMounts = true);
|
||||
/**
|
||||
* @return the path of the current executable.
|
||||
*/
|
||||
std::optional<std::filesystem::path> getSelfExe();
|
||||
std::optional<Path> getSelfExe();
|
||||
|
||||
} // namespace nix
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cxxabi.h>
|
||||
#include <string>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* Demangle a C++ type name.
|
||||
* Returns the demangled name, or the original if demangling fails.
|
||||
*/
|
||||
inline std::string demangle(const char * name)
|
||||
{
|
||||
int status;
|
||||
char * demangled = abi::__cxa_demangle(name, nullptr, nullptr, &status);
|
||||
if (demangled) {
|
||||
std::string result(demangled);
|
||||
std::free(demangled);
|
||||
return result;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
@@ -226,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);
|
||||
@@ -244,21 +262,21 @@ 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;
|
||||
|
||||
public:
|
||||
template<typename... Args>
|
||||
SystemError(std::errc posixErrNo, Args &&... args)
|
||||
: Error(std::forward<Args>(args)...)
|
||||
: CloneableError(std::forward<Args>(args)...)
|
||||
, errorCode(std::make_error_code(posixErrNo))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
SystemError(std::error_code errorCode, Args &&... args)
|
||||
: Error(std::forward<Args>(args)...)
|
||||
: CloneableError(std::forward<Args>(args)...)
|
||||
, errorCode(errorCode)
|
||||
{
|
||||
}
|
||||
@@ -290,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;
|
||||
@@ -301,7 +319,7 @@ public:
|
||||
*/
|
||||
template<typename... Args>
|
||||
SysError(int errNo, const Args &... args)
|
||||
: SystemError(static_cast<std::errc>(errNo), "")
|
||||
: CloneableError(static_cast<std::errc>(errNo), "")
|
||||
, errNo(errNo)
|
||||
{
|
||||
auto hf = HintFmt(args...);
|
||||
@@ -350,6 +368,15 @@ int handleExceptions(const std::string & programName, std::function<void()> fun)
|
||||
*/
|
||||
[[gnu::noinline, gnu::cold, noreturn]] void unreachable(std::source_location loc = std::source_location::current());
|
||||
|
||||
#if NIX_UBSAN_ENABLED == 1
|
||||
/* When building with sanitizers, also enable expensive unreachable checks. In
|
||||
optimised builds this explicitly invokes UB with std::unreachable for better
|
||||
optimisations. */
|
||||
# define nixUnreachableWhenHardened ::nix::unreachable
|
||||
#else
|
||||
# define nixUnreachableWhenHardened std::unreachable
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
namespace windows {
|
||||
@@ -360,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;
|
||||
@@ -372,7 +399,7 @@ public:
|
||||
*/
|
||||
template<typename... Args>
|
||||
WinError(DWORD lastError, const Args &... args)
|
||||
: SystemError(std::error_code(lastError, std::system_category()), "")
|
||||
: CloneableError(std::error_code(lastError, std::system_category()), "")
|
||||
, lastError(lastError)
|
||||
{
|
||||
auto hf = HintFmt(args...);
|
||||
|
||||
@@ -33,12 +33,6 @@ struct ExecutablePath
|
||||
*/
|
||||
static ExecutablePath parse(const OsString & path);
|
||||
|
||||
/**
|
||||
* Like `parse` but appends new entries to the end of an existing
|
||||
* `ExecutablePath`.
|
||||
*/
|
||||
void parseAppend(const OsString & path);
|
||||
|
||||
/**
|
||||
* Load the `PATH` environment variable and `parse` it.
|
||||
*/
|
||||
|
||||
@@ -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:
|
||||
/**
|
||||
|
||||
Loaded 100 of 161 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user