Compare commits
58 Commits
getflake-p
...
local-bina
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dfeff04c1 | ||
|
|
3d9174cec5 | ||
|
|
5ce241cbfd | ||
|
|
3bfd64c3cd | ||
|
|
3f419cfa4e | ||
|
|
994137dcf7 | ||
|
|
9242d74bc1 | ||
|
|
6cddf03b5a | ||
|
|
afccf1d2d3 | ||
|
|
de16ef8be6 | ||
|
|
7cd7930344 | ||
|
|
658c775f01 | ||
|
|
b1ad42e6d5 | ||
|
|
761139f31c | ||
|
|
c6d93828bd | ||
|
|
614072adcb | ||
|
|
42f6e9933d | ||
|
|
6808bfab92 | ||
|
|
92942071c7 | ||
|
|
8899af09c1 | ||
|
|
6363c1bf00 | ||
|
|
5adb6a36b6 | ||
|
|
b10cb6596e | ||
|
|
c0e849b696 | ||
|
|
2470b7981a | ||
|
|
5c1939e315 | ||
|
|
9799023545 | ||
|
|
ae4e229c9f | ||
|
|
b63f5d1914 | ||
|
|
31d87afc5a | ||
|
|
204618c9d8 | ||
|
|
f0d90d3bdb | ||
|
|
0730dcb4a8 | ||
|
|
9b363e1e5c | ||
|
|
3df67a8347 | ||
|
|
dd4b73a44d | ||
|
|
fd4eee9d62 | ||
|
|
100e9a4436 | ||
|
|
17f344cdda | ||
|
|
c33d9e31cc | ||
|
|
89158eedb5 | ||
|
|
c33c82f345 | ||
|
|
40abcebbe1 | ||
|
|
3bf690a407 | ||
|
|
360ff05e73 | ||
|
|
100e7cc337 | ||
|
|
247cc7013a | ||
|
|
9e865ae4ff | ||
|
|
fef83c9f9c | ||
|
|
8491e26cd4 | ||
|
|
e5fa203d7f | ||
|
|
987ecca24a | ||
|
|
a98b43b994 | ||
|
|
6733f2e5ce | ||
|
|
6429f2fd6c | ||
|
|
1a2d73dc2b | ||
|
|
0e39aa2068 | ||
|
|
4f91e9599f |
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
# required to find all branches
|
||||
fetch-depth: 0
|
||||
- name: Create backport PRs
|
||||
uses: korthout/backport-action@c656f5d5851037b2b38fb5db2691a03fa229e3b2 # v4.0.1
|
||||
uses: korthout/backport-action@01619ebc9a6e3f6820274221b9956b3e7365000a # v4.1.0
|
||||
id: backport
|
||||
with:
|
||||
# Config README: https://github.com/korthout/backport-action#backport-action
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -174,7 +174,7 @@ jobs:
|
||||
echo "installer-url=file://$GITHUB_WORKSPACE/out" >> "$GITHUB_OUTPUT"
|
||||
TARBALL_PATH="$(find "$GITHUB_WORKSPACE/out" -name 'nix*.tar.xz' -print | head -n 1)"
|
||||
echo "tarball-path=file://$TARBALL_PATH" >> "$GITHUB_OUTPUT"
|
||||
- uses: cachix/install-nix-action@4e002c8ec80594ecd40e759629461e26c8abed15 # v31.9.0
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
if: ${{ !matrix.experimental-installer }}
|
||||
with:
|
||||
install_url: ${{ format('{0}/install', steps.installer-tarball-url.outputs.installer-url) }}
|
||||
@@ -222,7 +222,7 @@ jobs:
|
||||
id: installer-tarball-url
|
||||
run: |
|
||||
echo "installer-url=file://$GITHUB_WORKSPACE/out" >> "$GITHUB_OUTPUT"
|
||||
- uses: cachix/install-nix-action@4e002c8ec80594ecd40e759629461e26c8abed15 # v31.9.0
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
with:
|
||||
install_url: ${{ format('{0}/install', steps.installer-tarball-url.outputs.installer-url) }}
|
||||
install_options: ${{ format('--tarball-url-prefix {0}', steps.installer-tarball-url.outputs.installer-url) }}
|
||||
|
||||
23
doc/manual/rl-next/c-api-recoverable-errors.md
Normal file
23
doc/manual/rl-next/c-api-recoverable-errors.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
synopsis: "C API: Errors returned from your primops are not treated as recoverable by default"
|
||||
prs: [15286, 13930]
|
||||
---
|
||||
|
||||
Nix 2.34 by default remembers the error in the thunk that triggered it.
|
||||
|
||||
Previously the following sequence of events worked:
|
||||
|
||||
1. Have a thunk that invokes a primop that's defined through the C API
|
||||
2. The primop returns an error
|
||||
3. Force the thunk again
|
||||
4. The primop returns a value
|
||||
5. The thunk evaluated successfully
|
||||
|
||||
**Resolution**
|
||||
|
||||
C API consumers that rely on this must change their recoverable error calls:
|
||||
|
||||
```diff
|
||||
-nix_set_err_msg(context, NIX_ERR_*, msg);
|
||||
+nix_set_err_msg(context, NIX_ERR_RECOVERABLE, msg);
|
||||
```
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
synopsis: "`builtins.getFlake` now supports path values"
|
||||
prs: [15290]
|
||||
---
|
||||
|
||||
`builtins.getFlake` now accepts path values in addition to flakerefs, allowing you to write `builtins.getFlake ./subflake` instead of having to use ugly workarounds to construct a pure flakeref.
|
||||
6
doc/manual/rl-next/narinfo-cache-meta-ttl.md
Normal file
6
doc/manual/rl-next/narinfo-cache-meta-ttl.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
synopsis: New setting `narinfo-cache-meta-ttl`
|
||||
prs: [15287]
|
||||
---
|
||||
|
||||
The new setting `narinfo-cache-meta-ttl` controls how long binary cache metadata (i.e. `/nix-cache-info`) is cached locally, in seconds. This was previously hard-coded to 7 days, which is still the default. As a result, you can now use `nix store info --refresh` to check whether a binary cache is still valid.
|
||||
@@ -358,7 +358,6 @@ dockerTools.buildLayeredImageWithNixDb {
|
||||
|
||||
extraCommands = ''
|
||||
rm -rf nix-support
|
||||
ln -s /nix/var/nix/profiles nix/var/nix/gcroots/profiles
|
||||
'';
|
||||
fakeRootCommands = ''
|
||||
chmod 1777 tmp
|
||||
|
||||
@@ -185,7 +185,9 @@ EvalState * nix_eval_state_build(nix_c_context * context, nix_eval_state_builder
|
||||
return EvalState{
|
||||
.fetchSettings = std::move(builder->fetchSettings),
|
||||
.settings = std::move(builder->settings),
|
||||
.state = nix::EvalState(builder->lookupPath, builder->store, self->fetchSettings, self->settings),
|
||||
.statePtr = std::make_shared<nix::EvalState>(
|
||||
builder->lookupPath, builder->store, self->fetchSettings, self->settings),
|
||||
.state = *self->statePtr,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ struct EvalState
|
||||
{
|
||||
nix::fetchers::Settings fetchSettings;
|
||||
nix::EvalSettings settings;
|
||||
nix::EvalState state;
|
||||
std::shared_ptr<nix::EvalState> statePtr;
|
||||
nix::EvalState & state;
|
||||
};
|
||||
|
||||
struct BindingsBuilder
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "nix/expr/attr-set.hh"
|
||||
#include "nix/expr/eval-error.hh"
|
||||
#include "nix/util/configuration.hh"
|
||||
#include "nix/expr/eval.hh"
|
||||
#include "nix/store/globals.hh"
|
||||
@@ -107,8 +108,13 @@ static void nix_c_primop_wrapper(
|
||||
f(userdata, &ctx, (EvalState *) &state, external_args.data(), vTmpPtr);
|
||||
|
||||
if (ctx.last_err_code != NIX_OK) {
|
||||
/* TODO: Throw different errors depending on the error code */
|
||||
state.error<nix::EvalError>("Error from custom function: %s", *ctx.last_err).atPos(pos).debugThrow();
|
||||
if (ctx.last_err_code == NIX_ERR_RECOVERABLE) {
|
||||
state.error<nix::RecoverableEvalError>("Recoverable error from custom function: %s", *ctx.last_err)
|
||||
.atPos(pos)
|
||||
.debugThrow();
|
||||
} else {
|
||||
state.error<nix::EvalError>("Error from custom function: %s", *ctx.last_err).atPos(pos).debugThrow();
|
||||
}
|
||||
}
|
||||
|
||||
if (!vTmp.isValid()) {
|
||||
@@ -194,6 +200,8 @@ ValueType nix_get_type(nix_c_context * context, const nix_value * value)
|
||||
switch (v.type()) {
|
||||
case nThunk:
|
||||
return NIX_TYPE_THUNK;
|
||||
case nFailed:
|
||||
return NIX_TYPE_FAILED;
|
||||
case nInt:
|
||||
return NIX_TYPE_INT;
|
||||
case nFloat:
|
||||
|
||||
@@ -100,7 +100,8 @@ typedef enum {
|
||||
/** @brief External value from C++ plugins or C API
|
||||
* @see Externals
|
||||
*/
|
||||
NIX_TYPE_EXTERNAL
|
||||
NIX_TYPE_EXTERNAL,
|
||||
NIX_TYPE_FAILED,
|
||||
} ValueType;
|
||||
|
||||
// forward declarations
|
||||
|
||||
@@ -37,7 +37,8 @@ static void BM_EvalDynamicAttrs(benchmark::State & state)
|
||||
EvalSettings evalSettings{readOnlyMode};
|
||||
evalSettings.nixPath = {};
|
||||
|
||||
EvalState st({}, store, fetchSettings, evalSettings, nullptr);
|
||||
auto stPtr = std::make_shared<EvalState>(LookupPath{}, store, fetchSettings, evalSettings, nullptr);
|
||||
auto & st = *stPtr;
|
||||
Expr * expr = st.parseExprFromString(exprStr, st.rootPath(CanonPath::root));
|
||||
|
||||
Value v;
|
||||
|
||||
@@ -16,7 +16,8 @@ struct GetDerivationsEnv
|
||||
fetchers::Settings fetchSettings{};
|
||||
bool readOnlyMode = true;
|
||||
EvalSettings evalSettings{readOnlyMode};
|
||||
EvalState state;
|
||||
std::shared_ptr<EvalState> statePtr;
|
||||
EvalState & state;
|
||||
|
||||
Bindings * autoArgs = nullptr;
|
||||
Value attrsValue;
|
||||
@@ -27,7 +28,8 @@ struct GetDerivationsEnv
|
||||
settings.nixPath = {};
|
||||
return settings;
|
||||
}())
|
||||
, state({}, store, fetchSettings, evalSettings, nullptr)
|
||||
, statePtr(std::make_shared<EvalState>(LookupPath{}, store, fetchSettings, evalSettings, nullptr))
|
||||
, state(*statePtr)
|
||||
{
|
||||
autoArgs = state.buildBindings(0).finish();
|
||||
|
||||
|
||||
@@ -517,4 +517,106 @@ TEST_F(nix_api_expr_test, nix_expr_attrset_update)
|
||||
assert_ctx_ok();
|
||||
}
|
||||
|
||||
// The following is a test case for retryable thunks. This is a requirement
|
||||
// for the current way in which NixOps4 evaluates its deployment expressions.
|
||||
// An alternative strategy could be implemented, but unwinding the stack may
|
||||
// be a more efficient way to deal with many suspensions/resumptions, compared
|
||||
// to e.g. using a thread or coroutine stack for each suspended dependency.
|
||||
// This test models the essential bits of a deployment tool that uses such
|
||||
// a strategy.
|
||||
|
||||
// State for the retryable primop - simulates deployment resource availability
|
||||
struct DeploymentResourceState
|
||||
{
|
||||
bool vm_created = false;
|
||||
};
|
||||
|
||||
static void primop_load_resource_input(
|
||||
void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
|
||||
{
|
||||
assert(context);
|
||||
assert(state);
|
||||
auto * resource_state = static_cast<DeploymentResourceState *>(user_data);
|
||||
|
||||
// Get the resource input name argument
|
||||
std::string input_name;
|
||||
if (nix_get_string(context, args[0], OBSERVE_STRING(input_name)) != NIX_OK)
|
||||
return;
|
||||
|
||||
// Only handle "vm_id" input - throw for anything else
|
||||
if (input_name != "vm_id") {
|
||||
std::string error_msg = "unknown resource input: " + input_name;
|
||||
nix_set_err_msg(context, NIX_ERR_NIX_ERROR, error_msg.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (resource_state->vm_created) {
|
||||
// VM has been created, return the ID
|
||||
nix_init_string(context, ret, "vm-12345");
|
||||
} else {
|
||||
// VM not created yet, fail with dependency error
|
||||
nix_set_err_msg(context, NIX_ERR_RECOVERABLE, "VM not yet created");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_thunk_re_evaluation_after_deployment)
|
||||
{
|
||||
// This test demonstrates NixOps4's requirement: a thunk calling a primop should be
|
||||
// re-evaluable when deployment resources become available that were not available initially.
|
||||
|
||||
DeploymentResourceState resource_state;
|
||||
|
||||
PrimOp * primop = nix_alloc_primop(
|
||||
ctx,
|
||||
primop_load_resource_input,
|
||||
1,
|
||||
"loadResourceInput",
|
||||
nullptr,
|
||||
"load a deployment resource input",
|
||||
&resource_state);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * primopValue = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_primop(ctx, primopValue, primop);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * inputName = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_string(ctx, inputName, "vm_id");
|
||||
assert_ctx_ok();
|
||||
|
||||
// Create a single thunk by using nix_init_apply instead of nix_value_call
|
||||
// This creates a lazy application that can be forced multiple times
|
||||
nix_value * thunk = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_apply(ctx, thunk, primopValue, inputName);
|
||||
assert_ctx_ok();
|
||||
|
||||
// First force: VM not created yet, should fail
|
||||
nix_value_force(ctx, state, thunk);
|
||||
ASSERT_EQ(NIX_ERR_NIX_ERROR, nix_err_code(ctx));
|
||||
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("VM not yet created"));
|
||||
|
||||
// Clear the error context for the next attempt
|
||||
nix_c_context_free(ctx);
|
||||
ctx = nix_c_context_create();
|
||||
|
||||
// Simulate deployment process: VM gets created
|
||||
resource_state.vm_created = true;
|
||||
|
||||
// Second force of the SAME thunk: this is where the "failed" value issue appears
|
||||
// With failed value caching, this should fail because the thunk is marked as permanently failed
|
||||
// Without failed value caching (or with retryable failures), this should succeed
|
||||
nix_value_force(ctx, state, thunk);
|
||||
|
||||
// If we get here without error, the thunk was successfully re-evaluated
|
||||
assert_ctx_ok();
|
||||
|
||||
std::string result;
|
||||
nix_get_string(ctx, thunk, OBSERVE_STRING(result));
|
||||
assert_ctx_ok();
|
||||
ASSERT_STREQ("vm-12345", result.c_str());
|
||||
}
|
||||
|
||||
} // namespace nixC
|
||||
|
||||
@@ -27,7 +27,8 @@ static void BM_EvalManyBuiltinsMatchSameRegex(benchmark::State & state)
|
||||
EvalSettings evalSettings{readOnlyMode};
|
||||
evalSettings.nixPath = {};
|
||||
|
||||
EvalState st({}, store, fetchSettings, evalSettings, nullptr);
|
||||
auto stPtr = std::make_shared<EvalState>(LookupPath{}, store, fetchSettings, evalSettings, nullptr);
|
||||
auto & st = *stPtr;
|
||||
Expr * expr = st.parseExprFromString(std::string(exprStr), st.rootPath(CanonPath::root));
|
||||
|
||||
Value v;
|
||||
|
||||
@@ -188,6 +188,22 @@ TEST_F(ValuePrintingTests, vBlackhole)
|
||||
test(vBlackhole, "«potential infinite recursion»");
|
||||
}
|
||||
|
||||
TEST_F(ValuePrintingTests, vFailed)
|
||||
{
|
||||
Value v;
|
||||
try {
|
||||
throw Error("nope");
|
||||
} catch (...) {
|
||||
v.mkFailed(std::current_exception(), nullptr);
|
||||
}
|
||||
|
||||
// Historically, a tried and then ignored value (e.g. through tryEval) was
|
||||
// reverted to the original thunk.
|
||||
|
||||
test(v, "«thunk»");
|
||||
test(v, ANSI_MAGENTA "«thunk»" ANSI_NORMAL, PrintOptions{.ansiColors = true});
|
||||
}
|
||||
|
||||
TEST_F(ValuePrintingTests, depthAttrs)
|
||||
{
|
||||
Value vOne;
|
||||
|
||||
@@ -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 = std::filesystem::path(getCacheDir()) / "eval-cache-v6";
|
||||
auto cacheDir = getCacheDir() / "eval-cache-v6";
|
||||
createDirs(cacheDir);
|
||||
|
||||
auto dbPath = cacheDir / (fingerprint.to_string(HashFormat::Base16, false) + ".sqlite");
|
||||
|
||||
@@ -114,5 +114,6 @@ template class EvalErrorBuilder<InfiniteRecursionError>;
|
||||
template class EvalErrorBuilder<StackOverflowError>;
|
||||
template class EvalErrorBuilder<InvalidPathError>;
|
||||
template class EvalErrorBuilder<IFDError>;
|
||||
template class EvalErrorBuilder<RecoverableEvalError>;
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -70,7 +70,7 @@ Strings EvalSettings::getDefaultNixPath()
|
||||
}
|
||||
};
|
||||
|
||||
add(std::filesystem::path{getNixDefExpr()} / "channels");
|
||||
add(getNixDefExpr() / "channels");
|
||||
auto profilesDirOpts = settings.getProfileDirsOptions();
|
||||
add(rootChannelsDir(profilesDirOpts) / "nixpkgs", "nixpkgs");
|
||||
add(rootChannelsDir(profilesDirOpts));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "nix/expr/eval.hh"
|
||||
#include "nix/expr/eval-error.hh"
|
||||
#include "nix/expr/eval-settings.hh"
|
||||
#include "nix/expr/primops.hh"
|
||||
#include "nix/expr/print-options.hh"
|
||||
@@ -31,6 +32,7 @@
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
@@ -155,6 +157,8 @@ std::string_view showType(ValueType type, bool withArticle)
|
||||
return WA("a", "float");
|
||||
case nThunk:
|
||||
return WA("a", "thunk");
|
||||
case nFailed:
|
||||
return WA("an", "error");
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
@@ -1987,11 +1991,15 @@ void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
|
||||
UpdateQueue q;
|
||||
evalForUpdate(state, env, q);
|
||||
|
||||
v.mkAttrs(&Bindings::emptyBindings);
|
||||
Value vTmp;
|
||||
vTmp.mkAttrs(&Bindings::emptyBindings);
|
||||
|
||||
for (auto & rhs : std::views::reverse(q)) {
|
||||
/* Remember that queue is sorted rightmost attrset first. */
|
||||
eval(state, /*v=*/v, /*v1=*/v, /*v2=*/rhs);
|
||||
eval(state, /*v=*/vTmp, /*v1=*/vTmp, /*v2=*/rhs);
|
||||
}
|
||||
|
||||
v = vTmp;
|
||||
}
|
||||
|
||||
void Expr::evalForUpdate(EvalState & state, Env & env, UpdateQueue & q, std::string_view errorCtx)
|
||||
@@ -2174,6 +2182,54 @@ void ExprBlackHole::eval(EvalState & state, [[maybe_unused]] Env & env, Value &
|
||||
// always force this to be separate, otherwise forceValue may inline it and take
|
||||
// a massive perf hit
|
||||
[[gnu::noinline]]
|
||||
void EvalState::handleEvalExceptionForThunk(Env * env, Expr * expr, Value & v, const PosIdx pos)
|
||||
{
|
||||
if (!env)
|
||||
tryFixupBlackHolePos(v, pos);
|
||||
|
||||
auto e = std::current_exception();
|
||||
Value * recovery = nullptr;
|
||||
try {
|
||||
std::rethrow_exception(e);
|
||||
} catch (const RecoverableEvalError & e) {
|
||||
recovery = allocValue();
|
||||
} catch (...) {
|
||||
}
|
||||
if (recovery) {
|
||||
recovery->mkThunk(env, expr);
|
||||
}
|
||||
v.mkFailed(e, recovery);
|
||||
}
|
||||
|
||||
[[gnu::noinline]]
|
||||
void EvalState::handleEvalExceptionForApp(Value & v, const Value & savedApp)
|
||||
{
|
||||
auto e = std::current_exception();
|
||||
Value * recovery = nullptr;
|
||||
try {
|
||||
std::rethrow_exception(e);
|
||||
} catch (const RecoverableEvalError & e) {
|
||||
recovery = allocValue();
|
||||
} catch (...) {
|
||||
}
|
||||
if (recovery) {
|
||||
*recovery = savedApp;
|
||||
}
|
||||
v.mkFailed(e, recovery);
|
||||
}
|
||||
|
||||
[[gnu::noinline]]
|
||||
void EvalState::handleEvalFailed(Value & v, const PosIdx pos)
|
||||
{
|
||||
assert(v.isFailed());
|
||||
if (auto recoveryValue = v.failed().recoveryValue) {
|
||||
v = *recoveryValue;
|
||||
forceValue(v, pos);
|
||||
} else {
|
||||
v.failed().rethrow();
|
||||
}
|
||||
}
|
||||
|
||||
void EvalState::tryFixupBlackHolePos(Value & v, PosIdx pos)
|
||||
{
|
||||
if (!v.isBlackhole())
|
||||
@@ -2182,7 +2238,8 @@ void EvalState::tryFixupBlackHolePos(Value & v, PosIdx pos)
|
||||
try {
|
||||
std::rethrow_exception(e);
|
||||
} catch (InfiniteRecursionError & e) {
|
||||
e.atPos(positions[pos]);
|
||||
if (!e.hasPos())
|
||||
e.atPos(positions[pos]);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
@@ -2821,8 +2878,11 @@ void EvalState::assertEqValues(Value & v1, Value & v2, const PosIdx pos, std::st
|
||||
}
|
||||
return;
|
||||
|
||||
case nThunk: // Must not be left by forceValue
|
||||
assert(false);
|
||||
// Cannot be returned by forceValue().
|
||||
case nThunk:
|
||||
case nFailed:
|
||||
unreachable();
|
||||
|
||||
default: // Note that we pass compiler flags that should make `default:` unreachable.
|
||||
// Also note that this probably ran after `eqValues`, which implements
|
||||
// the same logic more efficiently (without having to unwind stacks),
|
||||
@@ -2916,8 +2976,11 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
|
||||
// !!!
|
||||
return v1.fpoint() == v2.fpoint();
|
||||
|
||||
case nThunk: // Must not be left by forceValue
|
||||
assert(false);
|
||||
// Cannot be returned by forceValue().
|
||||
case nThunk:
|
||||
case nFailed:
|
||||
unreachable();
|
||||
|
||||
default: // Note that we pass compiler flags that should make `default:` unreachable.
|
||||
error<EvalError>("eqValues: cannot compare %1% with %2%", showType(v1), showType(v2))
|
||||
.withTrace(pos, errorCtx)
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace nix::eval_cache {
|
||||
struct AttrDb;
|
||||
class AttrCursor;
|
||||
|
||||
struct CachedEvalError : EvalError
|
||||
struct CachedEvalError : CloneableError<CachedEvalError, EvalError>
|
||||
{
|
||||
const ref<AttrCursor> cursor;
|
||||
const Symbol attr;
|
||||
|
||||
@@ -18,7 +18,7 @@ class EvalErrorBuilder;
|
||||
*
|
||||
* Most subclasses should inherit from `EvalError` instead of this class.
|
||||
*/
|
||||
class EvalBaseError : public Error
|
||||
class EvalBaseError : public CloneableError<EvalBaseError, Error>
|
||||
{
|
||||
template<class T>
|
||||
friend class EvalErrorBuilder;
|
||||
@@ -26,14 +26,14 @@ public:
|
||||
EvalState & state;
|
||||
|
||||
EvalBaseError(EvalState & state, ErrorInfo && errorInfo)
|
||||
: Error(errorInfo)
|
||||
: CloneableError(errorInfo)
|
||||
, state(state)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
explicit EvalBaseError(EvalState & state, const std::string & formatString, const Args &... formatArgs)
|
||||
: Error(formatString, formatArgs...)
|
||||
: CloneableError(formatString, formatArgs...)
|
||||
, state(state)
|
||||
{
|
||||
}
|
||||
@@ -60,23 +60,31 @@ 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
|
||||
/**
|
||||
* An evaluation error which should be retried instead of rethrown.
|
||||
*
|
||||
* A RecoverableEvalError is not an EvalError, because we shouldn't cache it in
|
||||
* the eval cache, as it should be retried anyway.
|
||||
*/
|
||||
MakeError(RecoverableEvalError, EvalBaseError);
|
||||
|
||||
struct InvalidPathError : public CloneableError<InvalidPathError, EvalError>
|
||||
{
|
||||
public:
|
||||
Path path;
|
||||
|
||||
InvalidPathError(EvalState & state, const Path & path)
|
||||
: EvalError(state, "path '%s' is not valid", path)
|
||||
: CloneableError(state, "path '%s' is not valid", path)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
@@ -33,6 +33,9 @@ using gc_allocator = std::allocator<T>;
|
||||
struct gc
|
||||
{};
|
||||
|
||||
struct gc_cleanup
|
||||
{};
|
||||
|
||||
#endif
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "nix/expr/eval.hh"
|
||||
#include "nix/expr/eval-error.hh"
|
||||
#include "nix/expr/eval-settings.hh"
|
||||
#include <exception>
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -91,18 +92,25 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
|
||||
Expr * expr = v.thunk().expr;
|
||||
try {
|
||||
v.mkBlackhole();
|
||||
// checkInterrupt();
|
||||
if (env) [[likely]]
|
||||
expr->eval(*this, *env, v);
|
||||
else
|
||||
ExprBlackHole::throwInfiniteRecursionError(*this, v);
|
||||
} catch (...) {
|
||||
v.mkThunk(env, expr);
|
||||
tryFixupBlackHolePos(v, pos);
|
||||
handleEvalExceptionForThunk(env, expr, v, pos);
|
||||
throw;
|
||||
}
|
||||
} else if (v.isApp())
|
||||
callFunction(*v.app().left, *v.app().right, v, pos);
|
||||
} else if (v.isApp()) {
|
||||
Value savedApp = v;
|
||||
try {
|
||||
callFunction(*v.app().left, *v.app().right, v, pos);
|
||||
} catch (...) {
|
||||
handleEvalExceptionForApp(v, savedApp);
|
||||
throw;
|
||||
}
|
||||
} else if (v.isFailed()) {
|
||||
handleEvalFailed(v, pos);
|
||||
}
|
||||
}
|
||||
|
||||
[[gnu::always_inline]]
|
||||
|
||||
@@ -652,8 +652,28 @@ public:
|
||||
*/
|
||||
inline void forceValue(Value & v, const PosIdx pos);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Internal support function for forceValue
|
||||
*
|
||||
* This code is factored out so that it's not in the heavily inlined hot path.
|
||||
*/
|
||||
void handleEvalExceptionForThunk(Env * env, Expr * expr, Value & v, const PosIdx pos);
|
||||
|
||||
/**
|
||||
* Internal support function for forceValue
|
||||
*
|
||||
* This code is factored out so that it's not in the heavily inlined hot path.
|
||||
*/
|
||||
void handleEvalExceptionForApp(Value & v, const Value & savedApp);
|
||||
|
||||
void handleEvalFailed(Value & v, PosIdx pos);
|
||||
|
||||
void tryFixupBlackHolePos(Value & v, PosIdx pos);
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Force a value, then recursively force list elements and
|
||||
* attributes.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <memory_resource>
|
||||
#include <exception>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
@@ -42,6 +43,7 @@ enum InternalType {
|
||||
tBool,
|
||||
tNull,
|
||||
tFloat,
|
||||
tFailed,
|
||||
tExternal,
|
||||
tPrimOp,
|
||||
tAttrs,
|
||||
@@ -68,6 +70,7 @@ enum InternalType {
|
||||
*/
|
||||
typedef enum {
|
||||
nThunk,
|
||||
nFailed,
|
||||
nInt,
|
||||
nFloat,
|
||||
nBool,
|
||||
@@ -424,6 +427,36 @@ struct ValueBase
|
||||
size_t size;
|
||||
Value * const * elems;
|
||||
};
|
||||
|
||||
struct Failed : gc_cleanup
|
||||
{
|
||||
std::exception_ptr ex;
|
||||
/**
|
||||
* Optional value for recovering `RecoverableEvalError`
|
||||
* Must be set iff `ex` is an instance of `RecoverableEvalError`.
|
||||
*/
|
||||
Value * recoveryValue;
|
||||
|
||||
Failed(std::exception_ptr ex, Value * recoveryValue)
|
||||
: ex(ex)
|
||||
, recoveryValue(recoveryValue)
|
||||
{
|
||||
}
|
||||
|
||||
[[noreturn]] void rethrow() const
|
||||
{
|
||||
try {
|
||||
std::rethrow_exception(ex);
|
||||
} catch (BaseError & e) {
|
||||
/* Rethrow the copy of the exception - not the original one.
|
||||
Stack tracing mechanisms rely on being able to modify the exceptions
|
||||
they catch by reference. */
|
||||
e.throwClone();
|
||||
} catch (...) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
@@ -450,6 +483,7 @@ struct PayloadTypeToInternalType
|
||||
MACRO(PrimOp *, primOp, tPrimOp) \
|
||||
MACRO(ValueBase::PrimOpApplicationThunk, primOpApp, tPrimOpApp) \
|
||||
MACRO(ExternalValueBase *, external, tExternal) \
|
||||
MACRO(ValueBase::Failed *, failed, tFailed) \
|
||||
MACRO(NixFloat, fpoint, tFloat)
|
||||
|
||||
#define NIX_VALUE_PAYLOAD_TYPE(T, FIELD_NAME, DISCRIMINATOR) \
|
||||
@@ -754,6 +788,11 @@ protected:
|
||||
path.path = std::bit_cast<const StringData *>(payload[1]);
|
||||
}
|
||||
|
||||
void getStorage(Failed *& failed) const noexcept
|
||||
{
|
||||
failed = std::bit_cast<Failed *>(payload[1]);
|
||||
}
|
||||
|
||||
void setStorage(NixInt integer) noexcept
|
||||
{
|
||||
setSingleDWordPayload<tInt>(integer.value);
|
||||
@@ -803,6 +842,11 @@ protected:
|
||||
{
|
||||
setUntaggablePayload<pdPath>(path.accessor, path.path);
|
||||
}
|
||||
|
||||
void setStorage(Failed * failed) noexcept
|
||||
{
|
||||
setSingleDWordPayload<tFailed>(std::bit_cast<PackedPointer>(failed));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1054,12 +1098,12 @@ public:
|
||||
inline bool isThunk() const
|
||||
{
|
||||
return isa<tThunk>();
|
||||
};
|
||||
}
|
||||
|
||||
inline bool isApp() const
|
||||
{
|
||||
return isa<tApp>();
|
||||
};
|
||||
}
|
||||
|
||||
inline bool isBlackhole() const;
|
||||
|
||||
@@ -1067,17 +1111,22 @@ public:
|
||||
inline bool isLambda() const
|
||||
{
|
||||
return isa<tLambda>();
|
||||
};
|
||||
}
|
||||
|
||||
inline bool isPrimOp() const
|
||||
{
|
||||
return isa<tPrimOp>();
|
||||
};
|
||||
}
|
||||
|
||||
inline bool isPrimOpApp() const
|
||||
{
|
||||
return isa<tPrimOpApp>();
|
||||
};
|
||||
}
|
||||
|
||||
inline bool isFailed() const
|
||||
{
|
||||
return isa<tFailed>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the normal type of a Value. This only returns nThunk if
|
||||
@@ -1098,6 +1147,7 @@ public:
|
||||
t[tBool] = nBool;
|
||||
t[tNull] = nNull;
|
||||
t[tFloat] = nFloat;
|
||||
t[tFailed] = nFailed;
|
||||
t[tExternal] = nExternal;
|
||||
t[tAttrs] = nAttrs;
|
||||
t[tPrimOp] = nFunction;
|
||||
@@ -1235,6 +1285,11 @@ public:
|
||||
setStorage(n);
|
||||
}
|
||||
|
||||
inline void mkFailed(std::exception_ptr e, Value * recovery) noexcept
|
||||
{
|
||||
setStorage(new Value::Failed(e, recovery));
|
||||
}
|
||||
|
||||
bool isList() const noexcept
|
||||
{
|
||||
return isa<tListSmall, tListN>();
|
||||
@@ -1349,6 +1404,13 @@ public:
|
||||
{
|
||||
return getStorage<Path>().accessor;
|
||||
}
|
||||
|
||||
Failed & failed() const noexcept
|
||||
{
|
||||
auto p = getStorage<Failed *>();
|
||||
assert(p);
|
||||
return *p;
|
||||
}
|
||||
};
|
||||
|
||||
extern ExprBlackHole eBlackHole;
|
||||
|
||||
@@ -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...);
|
||||
|
||||
@@ -562,6 +562,7 @@ static void prim_typeOf(EvalState & state, const PosIdx pos, Value ** args, Valu
|
||||
v.mkStringNoCopy("float"_sds);
|
||||
break;
|
||||
case nThunk:
|
||||
case nFailed:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
@@ -1312,11 +1313,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,11 @@ void printAmbiguous(EvalState & state, Value & v, std::ostream & str, std::set<c
|
||||
str << "«potential infinite recursion»";
|
||||
}
|
||||
break;
|
||||
case nFailed:
|
||||
// Historically, a tried and then ignored value (e.g. through tryEval) was
|
||||
// reverted to the original thunk.
|
||||
str << "<CODE>";
|
||||
break;
|
||||
case nFunction:
|
||||
if (v.isLambda()) {
|
||||
str << "<LAMBDA>";
|
||||
|
||||
@@ -509,6 +509,17 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void printFailed()
|
||||
{
|
||||
if (options.ansiColors)
|
||||
output << ANSI_MAGENTA;
|
||||
// Historically, a tried and then ignored value (e.g. through tryEval) was
|
||||
// reverted to the original thunk.
|
||||
output << "«thunk»";
|
||||
if (options.ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
}
|
||||
|
||||
void printExternal(Value & v)
|
||||
{
|
||||
v.external()->print(output);
|
||||
@@ -590,6 +601,10 @@ private:
|
||||
printFunction(v);
|
||||
break;
|
||||
|
||||
case nFailed:
|
||||
printFailed();
|
||||
break;
|
||||
|
||||
case nThunk:
|
||||
printThunk(v);
|
||||
break;
|
||||
|
||||
@@ -98,6 +98,7 @@ json printValueAsJSON(
|
||||
break;
|
||||
|
||||
case nThunk:
|
||||
case nFailed:
|
||||
case nFunction:
|
||||
state.error<TypeError>("cannot convert %1% to JSON", showType(v)).atPos(v.determinePos(pos)).debugThrow();
|
||||
}
|
||||
|
||||
@@ -171,7 +171,11 @@ static void printValueAsXML(
|
||||
break;
|
||||
|
||||
case nThunk:
|
||||
// Historically, a tried and then ignored value (e.g. through tryEval) was
|
||||
// reverted to the original thunk.
|
||||
case nFailed:
|
||||
doc.writeEmptyElement("unevaluated");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,8 @@ struct CacheImpl : Cache
|
||||
{
|
||||
auto state(_state.lock());
|
||||
|
||||
auto dbPath = (getCacheDir() / "fetcher-cache-v4.sqlite").string();
|
||||
createDirs(dirOf(dbPath));
|
||||
auto dbPath = getCacheDir() / "fetcher-cache-v4.sqlite";
|
||||
createDirs(dbPath.parent_path());
|
||||
|
||||
state->db = SQLite(dbPath, {.useWAL = nix::settings.useSQLiteWAL});
|
||||
state->db.isCache();
|
||||
|
||||
@@ -268,10 +268,10 @@ void Fetch::fetch(
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path cacheDir = getCacheDir() / "git-lfs";
|
||||
auto cacheDir = getCacheDir() / "git-lfs";
|
||||
std::string key = hashString(HashAlgorithm::SHA256, pointerFilePath.rel()).to_string(HashFormat::Base16, false)
|
||||
+ "/" + pointer->oid;
|
||||
std::filesystem::path cachePath = cacheDir / key;
|
||||
auto cachePath = cacheDir / key;
|
||||
if (pathExists(cachePath)) {
|
||||
debug("using cache entry %s -> %s", key, PathFmt(cachePath));
|
||||
sink(readFile(cachePath));
|
||||
|
||||
@@ -74,11 +74,11 @@ namespace nix {
|
||||
|
||||
struct GitSourceAccessor;
|
||||
|
||||
struct GitError : public Error
|
||||
struct GitError final : public CloneableError<GitError, Error>
|
||||
{
|
||||
template<typename... Ts>
|
||||
GitError(const git_error & error, Ts &&... args)
|
||||
: Error("")
|
||||
: CloneableError("")
|
||||
{
|
||||
auto hf = HintFmt(std::forward<Ts>(args)...);
|
||||
err.msg = HintFmt("%1%: %2% (libgit2 error code = %3%)", Uncolored(hf.str()), error.message, error.klass);
|
||||
|
||||
@@ -44,8 +44,9 @@ static bool isCacheFileWithinTtl(const Settings & settings, time_t now, const Po
|
||||
|
||||
std::filesystem::path getCachePath(std::string_view key, bool shallow)
|
||||
{
|
||||
return getCacheDir() / "gitv3"
|
||||
/ (hashString(HashAlgorithm::SHA256, key).to_string(HashFormat::Nix32, false) + (shallow ? "-shallow" : ""));
|
||||
auto name =
|
||||
hashString(HashAlgorithm::SHA256, key).to_string(HashFormat::Nix32, false) + (shallow ? "-shallow" : "");
|
||||
return getCacheDir() / "gitv3" / std::move(name);
|
||||
}
|
||||
|
||||
// Returns the name of the HEAD branch.
|
||||
|
||||
@@ -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;
|
||||
|
||||
std::filesystem::path trustedListPath()
|
||||
static std::filesystem::path trustedListPath()
|
||||
{
|
||||
return getDataDir() / "trusted-settings.json";
|
||||
}
|
||||
|
||||
@@ -35,39 +35,35 @@ namespace nix::flake::primops {
|
||||
PrimOp getFlake(const Settings & settings)
|
||||
{
|
||||
auto prim_getFlake = [&settings](EvalState & state, const PosIdx pos, Value ** args, Value & v) {
|
||||
state.forceValue(*args[0], pos);
|
||||
std::string flakeRefS(
|
||||
state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
|
||||
auto flakeRef = nix::parseFlakeRef(state.fetchSettings, flakeRefS, {}, true);
|
||||
if (state.settings.pureEval && !flakeRef.input.isLocked(state.fetchSettings))
|
||||
throw Error(
|
||||
"cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)",
|
||||
flakeRefS,
|
||||
state.positions[pos]);
|
||||
|
||||
LockFlags lockFlags{
|
||||
.updateLockFile = false,
|
||||
.writeLockFile = false,
|
||||
.useRegistries = !state.settings.pureEval && settings.useRegistries,
|
||||
.allowUnlocked = !state.settings.pureEval,
|
||||
};
|
||||
|
||||
if (args[0]->type() == nPath) {
|
||||
auto path = state.realisePath(pos, *args[0]);
|
||||
callFlake(state, lockFlake(settings, state, path, lockFlags), v);
|
||||
} else {
|
||||
NixStringContext context;
|
||||
std::string flakeRefS(
|
||||
state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
|
||||
|
||||
auto flakeRef = nix::parseFlakeRef(state.fetchSettings, flakeRefS, {}, true);
|
||||
if (state.settings.pureEval && !flakeRef.input.isLocked(state.fetchSettings))
|
||||
throw Error(
|
||||
"cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)",
|
||||
flakeRefS,
|
||||
state.positions[pos]);
|
||||
|
||||
callFlake(state, lockFlake(settings, state, flakeRef, lockFlags), v);
|
||||
}
|
||||
callFlake(
|
||||
state,
|
||||
lockFlake(
|
||||
settings,
|
||||
state,
|
||||
flakeRef,
|
||||
LockFlags{
|
||||
.updateLockFile = false,
|
||||
.writeLockFile = false,
|
||||
.useRegistries = !state.settings.pureEval && settings.useRegistries,
|
||||
.allowUnlocked = !state.settings.pureEval,
|
||||
}),
|
||||
v);
|
||||
};
|
||||
|
||||
return PrimOp{
|
||||
.name = "__getFlake",
|
||||
.args = {"args"},
|
||||
.doc = R"(
|
||||
Fetch a flake from a flake reference or a path, and return its output attributes and some metadata. For example:
|
||||
Fetch a flake from a flake reference, and return its output attributes and some metadata. For example:
|
||||
|
||||
```nix
|
||||
(builtins.getFlake "nix/55bc52401966fbffa525c574c14f67b00bc4fb3a").packages.x86_64-linux.nix
|
||||
|
||||
@@ -416,8 +416,10 @@ static LockFile readLockFile(const fetchers::Settings & fetchSettings, const Sou
|
||||
: LockFile();
|
||||
}
|
||||
|
||||
LockedFlake lockFlake(
|
||||
const Settings & settings, EvalState & state, const FlakeRef & topRef, const LockFlags & lockFlags, Flake flake)
|
||||
/* Compute an in-memory lock file for the specified top-level flake,
|
||||
and optionally write it to file, if the flake is writable. */
|
||||
LockedFlake
|
||||
lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef, const LockFlags & lockFlags)
|
||||
{
|
||||
experimentalFeatureSettings.require(Xp::Flakes);
|
||||
|
||||
@@ -425,6 +427,8 @@ LockedFlake lockFlake(
|
||||
auto useRegistriesTop = useRegistries ? fetchers::UseRegistries::All : fetchers::UseRegistries::No;
|
||||
auto useRegistriesInputs = useRegistries ? fetchers::UseRegistries::Limited : fetchers::UseRegistries::No;
|
||||
|
||||
auto flake = getFlake(state, topRef, useRegistriesTop, {});
|
||||
|
||||
if (lockFlags.applyNixConfig) {
|
||||
flake.config.apply(settings);
|
||||
state.store->setOptions();
|
||||
@@ -904,22 +908,6 @@ LockedFlake lockFlake(
|
||||
}
|
||||
}
|
||||
|
||||
LockedFlake
|
||||
lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef, const LockFlags & lockFlags)
|
||||
{
|
||||
auto useRegistries = lockFlags.useRegistries.value_or(settings.useRegistries);
|
||||
auto useRegistriesTop = useRegistries ? fetchers::UseRegistries::All : fetchers::UseRegistries::No;
|
||||
return lockFlake(settings, state, topRef, lockFlags, getFlake(state, topRef, useRegistriesTop, {}));
|
||||
}
|
||||
|
||||
LockedFlake
|
||||
lockFlake(const Settings & settings, EvalState & state, const SourcePath & flakeDir, const LockFlags & lockFlags)
|
||||
{
|
||||
/* We need a fake flakeref to put in the `Flake` struct, but it's not used for anything. */
|
||||
auto fakeRef = parseFlakeRef(state.fetchSettings, "flake:get-flake");
|
||||
return lockFlake(settings, state, fakeRef, lockFlags, readFlake(state, fakeRef, fakeRef, fakeRef, flakeDir, {}));
|
||||
}
|
||||
|
||||
static ref<SourceAccessor> makeInternalFS()
|
||||
{
|
||||
auto internalFS = make_ref<MemorySourceAccessor>(MemorySourceAccessor{});
|
||||
|
||||
@@ -214,16 +214,9 @@ struct LockFlags
|
||||
std::set<NonEmptyInputAttrPath> inputUpdates;
|
||||
};
|
||||
|
||||
/*
|
||||
* Compute an in-memory lock file for the specified top-level flake, and optionally write it to file, if the flake is
|
||||
* writable.
|
||||
*/
|
||||
LockedFlake
|
||||
lockFlake(const Settings & settings, EvalState & state, const FlakeRef & flakeRef, const LockFlags & lockFlags);
|
||||
|
||||
LockedFlake
|
||||
lockFlake(const Settings & settings, EvalState & state, const SourcePath & flakeDir, const LockFlags & lockFlags);
|
||||
|
||||
void callFlake(EvalState & state, const LockedFlake & lockedFlake, Value & v);
|
||||
|
||||
/**
|
||||
|
||||
@@ -73,7 +73,7 @@ std::function<void(siginfo_t * info, void * ctx)> stackOverflowHandler(defaultSt
|
||||
void defaultStackOverflowHandler(siginfo_t * info, void * ctx)
|
||||
{
|
||||
char msg[] = "error: stack overflow (possible infinite recursion)\n";
|
||||
[[gnu::unused]] auto res = write(2, msg, strlen(msg));
|
||||
[[gnu::unused]] auto res = ::write(2, msg, strlen(msg));
|
||||
_exit(1); // maybe abort instead?
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
C:\foo\bar\baz?trusted=true
|
||||
@@ -13,10 +13,6 @@ using testing::Eq;
|
||||
using testing::Field;
|
||||
using testing::SizeIs;
|
||||
|
||||
namespace nix::fs {
|
||||
using namespace std::filesystem;
|
||||
}
|
||||
|
||||
using namespace nix;
|
||||
|
||||
TEST(machines, getMachinesWithEmptyBuilders)
|
||||
|
||||
@@ -85,6 +85,20 @@ static StoreReference localExample_2{
|
||||
},
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
static StoreReference localExample_windows{
|
||||
.variant =
|
||||
StoreReference::Specified{
|
||||
.scheme = "local",
|
||||
.authority = "/C:/foo/bar/baz",
|
||||
},
|
||||
.params =
|
||||
{
|
||||
{"trusted", "true"},
|
||||
},
|
||||
};
|
||||
#endif
|
||||
|
||||
static StoreReference localExample_3{
|
||||
.variant =
|
||||
StoreReference::Specified{
|
||||
@@ -108,7 +122,11 @@ URI_TEST_READ(local_3_no_percent, localExample_3)
|
||||
|
||||
URI_TEST_READ(local_shorthand_1, localExample_1)
|
||||
|
||||
URI_TEST_READ(local_shorthand_2, localExample_2)
|
||||
#ifndef _WIN32
|
||||
URI_TEST_READ(local_shorthand_path_unix, localExample_2)
|
||||
#else
|
||||
URI_TEST_READ(local_shorthand_path_windows, localExample_windows)
|
||||
#endif
|
||||
|
||||
URI_TEST(
|
||||
local_shorthand_3,
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
namespace nix {
|
||||
|
||||
AwsAuthError::AwsAuthError(int errorCode)
|
||||
: Error("AWS authentication error: '%s' (%d)", aws_error_str(errorCode), errorCode)
|
||||
: CloneableError("AWS authentication error: '%s' (%d)", aws_error_str(errorCode), errorCode)
|
||||
, errorCode(errorCode)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "nix/store/build/goal.hh"
|
||||
#include "nix/store/build/worker.hh"
|
||||
#include "nix/store/globals.hh"
|
||||
#include "nix/store/worker-settings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
TimedOut::TimedOut(time_t maxDuration)
|
||||
: BuildError(BuildResult::Failure::TimedOut, "timed out after %1% seconds", maxDuration)
|
||||
: CloneableError(BuildResult::Failure::TimedOut, "timed out after %1% seconds", maxDuration)
|
||||
, maxDuration(maxDuration)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ Worker::Worker(Store & store, Store & evalStore)
|
||||
: act(*logger, actRealise)
|
||||
, actDerivations(*logger, actBuilds)
|
||||
, actSubstitutions(*logger, actCopyPaths)
|
||||
#ifdef _WIN32
|
||||
, ioport{CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)}
|
||||
#endif
|
||||
, store(store)
|
||||
, evalStore(evalStore)
|
||||
, settings(nix::settings.getWorkerSettings())
|
||||
@@ -27,6 +30,10 @@ Worker::Worker(Store & store, Store & evalStore)
|
||||
return nix::settings.getWorkerSettings().useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>{};
|
||||
}}
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!ioport)
|
||||
throw windows::WinError("CreateIoCompletionPort");
|
||||
#endif
|
||||
nrLocalBuilds = 0;
|
||||
nrSubstitutions = 0;
|
||||
lastWokenUp = steady_time_point::min();
|
||||
|
||||
@@ -27,6 +27,8 @@ static void builtinUnpackChannel(const BuiltinBuilderContext & ctx)
|
||||
size_t fileCount;
|
||||
std::string fileName;
|
||||
auto entries = DirectoryIterator{out};
|
||||
if (entries == DirectoryIterator{})
|
||||
throw Error("channel tarball '%s' is empty", src);
|
||||
fileName = entries->path().string();
|
||||
fileCount = std::distance(entries.begin(), entries.end());
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -34,12 +34,13 @@ struct AwsCredentials
|
||||
}
|
||||
};
|
||||
|
||||
class AwsAuthError : public Error
|
||||
class AwsAuthError final : public CloneableError<AwsAuthError, Error>
|
||||
{
|
||||
std::optional<int> errorCode;
|
||||
|
||||
public:
|
||||
using Error::Error;
|
||||
using CloneableError::CloneableError;
|
||||
|
||||
AwsAuthError(int errorCode);
|
||||
|
||||
std::optional<int> getErrorCode() const
|
||||
|
||||
@@ -58,7 +58,7 @@ enum struct BuildResultFailureStatus : uint8_t {
|
||||
* This is both an exception type (inherits from Error) and serves as
|
||||
* the failure variant in BuildResult::inner.
|
||||
*/
|
||||
struct BuildError : public Error
|
||||
struct BuildError : public CloneableError<BuildError, Error>
|
||||
{
|
||||
using Status = BuildResultFailureStatus;
|
||||
using enum Status;
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
*/
|
||||
template<typename... Args>
|
||||
BuildError(Status status, const Args &... args)
|
||||
: Error(args...)
|
||||
: CloneableError(args...)
|
||||
, status{status}
|
||||
{
|
||||
}
|
||||
@@ -97,7 +97,7 @@ public:
|
||||
* Also used for deserialization.
|
||||
*/
|
||||
BuildError(Args args)
|
||||
: Error(std::move(args.msg))
|
||||
: CloneableError(std::move(args.msg))
|
||||
, status{args.status}
|
||||
, isNonDeterministic{args.isNonDeterministic}
|
||||
|
||||
@@ -108,7 +108,7 @@ public:
|
||||
* Default constructor for deserialization.
|
||||
*/
|
||||
BuildError()
|
||||
: Error("")
|
||||
: CloneableError("")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -20,14 +20,14 @@ namespace nix {
|
||||
* Denotes a build failure that stemmed from the builder exiting with a
|
||||
* failing exist status.
|
||||
*/
|
||||
struct BuilderFailureError : BuildError
|
||||
struct BuilderFailureError final : CloneableError<BuilderFailureError, BuildError>
|
||||
{
|
||||
int builderStatus;
|
||||
|
||||
std::string extraMsgAfter;
|
||||
|
||||
BuilderFailureError(BuildResult::Failure::Status status, int builderStatus, std::string extraMsgAfter)
|
||||
: BuildError{
|
||||
: CloneableError{
|
||||
status,
|
||||
/* No message for now, because the caller will make for
|
||||
us, with extra context */
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct TimedOut : BuildError
|
||||
struct TimedOut final : CloneableError<TimedOut, BuildError>
|
||||
{
|
||||
time_t maxDuration;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ struct Package
|
||||
}
|
||||
};
|
||||
|
||||
class BuildEnvFileConflictError : public Error
|
||||
class BuildEnvFileConflictError final : public CloneableError<BuildEnvFileConflictError, Error>
|
||||
{
|
||||
public:
|
||||
const Path fileA;
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
int priority;
|
||||
|
||||
BuildEnvFileConflictError(const Path fileA, const Path fileB, int priority)
|
||||
: Error(
|
||||
: CloneableError(
|
||||
"Unable to build profile. There is a conflict for the following files:\n"
|
||||
"\n"
|
||||
" %1%\n"
|
||||
|
||||
@@ -403,7 +403,7 @@ ref<FileTransfer> getFileTransfer();
|
||||
*/
|
||||
ref<FileTransfer> makeFileTransfer(const FileTransferSettings & settings = fileTransferSettings);
|
||||
|
||||
class FileTransferError : public Error
|
||||
class FileTransferError final : public CloneableError<FileTransferError, Error>
|
||||
{
|
||||
public:
|
||||
FileTransfer::Error error;
|
||||
|
||||
@@ -86,6 +86,17 @@ struct NarInfoDiskCacheSettings : public virtual Config
|
||||
would prevent trying to pull the path again and failing with a hash
|
||||
mismatch if the build isn't reproducible.
|
||||
)"};
|
||||
|
||||
Setting<unsigned int> ttlMeta{
|
||||
this,
|
||||
7 * 24 * 3600,
|
||||
"narinfo-cache-meta-ttl",
|
||||
R"(
|
||||
The TTL in seconds for caching binary cache metadata (i.e.
|
||||
`/nix-cache-info`). This determines how long information about a
|
||||
binary cache (such as its store directory, priority, and whether it
|
||||
wants mass queries) is considered valid before being refreshed.
|
||||
)"};
|
||||
};
|
||||
|
||||
class Settings : public virtual Config,
|
||||
|
||||
@@ -14,7 +14,7 @@ struct LocalBinaryCacheStoreConfig : std::enable_shared_from_this<LocalBinaryCac
|
||||
*/
|
||||
LocalBinaryCacheStoreConfig(std::string_view scheme, PathView binaryCacheDir, const Params & params);
|
||||
|
||||
Path binaryCacheDir;
|
||||
std::filesystem::path binaryCacheDir;
|
||||
|
||||
static const std::string name()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,11 +3,32 @@
|
||||
#include "nix/store/nar-info-disk-cache.hh"
|
||||
#include "nix/util/signals.hh"
|
||||
#include "nix/store/store-registration.hh"
|
||||
#include "nix/util/url.hh"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace nix {
|
||||
|
||||
static std::filesystem::path checkBinaryCachePath(const std::filesystem::path & root, std::string_view path)
|
||||
{
|
||||
/* Note: these checks aren't complete and don't guard against symlink shenanigans. */
|
||||
auto p = std::filesystem::path(path);
|
||||
if (p.is_absolute())
|
||||
/* Never happens unless the caller is messed up. */
|
||||
throw Error("binary cache path '%s' must be relative", path);
|
||||
|
||||
auto result = (root / p).lexically_normal();
|
||||
|
||||
/* NB: lexically_normal() only does textual normalization and does
|
||||
not resolve symlinks. This is acceptable because store/substituter
|
||||
paths are already trusted, and this check is defense-in-depth
|
||||
against ".." traversal. */
|
||||
if (!isInDir(result, root.lexically_normal()))
|
||||
throw Error("binary cache path '%s' escapes cache directory", path);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
LocalBinaryCacheStoreConfig::LocalBinaryCacheStoreConfig(
|
||||
std::string_view scheme, PathView binaryCacheDir, const StoreReference::Params & params)
|
||||
: Store::Config{params}
|
||||
@@ -29,7 +50,7 @@ StoreReference LocalBinaryCacheStoreConfig::getReference() const
|
||||
.variant =
|
||||
StoreReference::Specified{
|
||||
.scheme = "file",
|
||||
.authority = binaryCacheDir,
|
||||
.authority = encodeUrlPath(pathToUrlPath(binaryCacheDir)),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -56,7 +77,9 @@ protected:
|
||||
void upsertFile(
|
||||
const std::string & path, RestartableSource & source, const std::string & mimeType, uint64_t sizeHint) override
|
||||
{
|
||||
auto path2 = std::filesystem::path{config->binaryCacheDir} / path;
|
||||
/* TODO: Maybe use RestoreSink for writing stuff? It would have to gain the ability to write files
|
||||
atomically (maybe with O_TMPFILE + linkat + AT_EMPTY_PATH when available or fallback to rename). */
|
||||
auto path2 = checkBinaryCachePath(config->binaryCacheDir, path);
|
||||
static std::atomic<int> counter{0};
|
||||
createDirs(path2.parent_path());
|
||||
auto tmp = path2;
|
||||
@@ -70,7 +93,7 @@ protected:
|
||||
void getFile(const std::string & path, Sink & sink) override
|
||||
{
|
||||
try {
|
||||
readFile(config->binaryCacheDir + "/" + path, sink);
|
||||
readFile(checkBinaryCachePath(config->binaryCacheDir, path), sink);
|
||||
} catch (SystemError & e) {
|
||||
if (e.is(std::errc::no_such_file_or_directory))
|
||||
throw NoSuchBinaryCacheFile("file '%s' does not exist in binary cache", path);
|
||||
@@ -101,17 +124,17 @@ protected:
|
||||
|
||||
void LocalBinaryCacheStore::init()
|
||||
{
|
||||
createDirs(config->binaryCacheDir + "/nar");
|
||||
createDirs(config->binaryCacheDir + "/" + realisationsPrefix);
|
||||
createDirs(config->binaryCacheDir / "nar");
|
||||
createDirs(config->binaryCacheDir / realisationsPrefix);
|
||||
if (config->writeDebugInfo)
|
||||
createDirs(config->binaryCacheDir + "/debuginfo");
|
||||
createDirs(config->binaryCacheDir + "/log");
|
||||
createDirs(config->binaryCacheDir / "debuginfo");
|
||||
createDirs(config->binaryCacheDir / "log");
|
||||
BinaryCacheStore::init();
|
||||
}
|
||||
|
||||
bool LocalBinaryCacheStore::fileExists(const std::string & path)
|
||||
{
|
||||
return pathExists(config->binaryCacheDir + "/" + path);
|
||||
return pathExists(checkBinaryCachePath(config->binaryCacheDir, path));
|
||||
}
|
||||
|
||||
StringSet LocalBinaryCacheStoreConfig::uriSchemes()
|
||||
|
||||
@@ -144,10 +144,7 @@ LocalStore::LocalStore(ref<const Config> config)
|
||||
Path gcRootsDir = config->stateDir + "/gcroots";
|
||||
const auto & localSettings = config->getLocalSettings();
|
||||
const auto & gcSettings = localSettings.getGCSettings();
|
||||
if (!pathExists(gcRootsDir)) {
|
||||
createDirs(gcRootsDir);
|
||||
replaceSymlink(profilesDir, gcRootsDir + "/profiles");
|
||||
}
|
||||
createDirs(gcRootsDir);
|
||||
|
||||
for (auto & perUserDir : {profilesDir + "/per-user", gcRootsDir + "/per-user"}) {
|
||||
createDirs(perUserDir);
|
||||
|
||||
@@ -63,9 +63,6 @@ struct NarInfoDiskCacheImpl : NarInfoDiskCache
|
||||
/* How often to purge expired entries from the cache. */
|
||||
const int purgeInterval = 24 * 3600;
|
||||
|
||||
/* How long to cache binary cache info (i.e. /nix-cache-info) */
|
||||
const int cacheInfoTtl = 7 * 24 * 3600;
|
||||
|
||||
struct Cache
|
||||
{
|
||||
int id;
|
||||
@@ -184,7 +181,7 @@ private:
|
||||
{
|
||||
auto i = state.caches.find(uri);
|
||||
if (i == state.caches.end()) {
|
||||
auto queryCache(state.queryCache.use()(uri)(time(0) - cacheInfoTtl));
|
||||
auto queryCache(state.queryCache.use()(uri)(time(0) - settings.ttlMeta));
|
||||
if (!queryCache.next())
|
||||
return std::nullopt;
|
||||
auto cache = Cache{
|
||||
|
||||
@@ -293,8 +293,7 @@ std::string optimisticLockProfile(const std::filesystem::path & profile)
|
||||
|
||||
std::filesystem::path profilesDir(ProfileDirsOptions settings)
|
||||
{
|
||||
auto profileRoot =
|
||||
isRootUser() ? rootProfilesDir(settings) : std::filesystem::path{createNixStateDir()} / "profiles";
|
||||
auto profileRoot = isRootUser() ? rootProfilesDir(settings) : createNixStateDir() / "profiles";
|
||||
createDirs(profileRoot);
|
||||
return profileRoot;
|
||||
}
|
||||
@@ -306,9 +305,8 @@ std::filesystem::path rootProfilesDir(ProfileDirsOptions settings)
|
||||
|
||||
std::filesystem::path getDefaultProfile(ProfileDirsOptions settings)
|
||||
{
|
||||
std::filesystem::path profileLink = settings.useXDGBaseDirectories
|
||||
? std::filesystem::path{createNixStateDir()} / "profile"
|
||||
: std::filesystem::path{getHome()} / ".nix-profile";
|
||||
std::filesystem::path profileLink =
|
||||
settings.useXDGBaseDirectories ? createNixStateDir() / "profile" : getHome() / ".nix-profile";
|
||||
try {
|
||||
auto profile = profilesDir(settings) / "profile";
|
||||
if (!pathExists(profileLink)) {
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace nix {
|
||||
|
||||
SQLiteError::SQLiteError(
|
||||
const char * path, const char * errMsg, int errNo, int extendedErrNo, int offset, HintFmt && hf)
|
||||
: Error("")
|
||||
: CloneableError("")
|
||||
, path(path)
|
||||
, errMsg(errMsg)
|
||||
, errNo(errNo)
|
||||
|
||||
@@ -18,11 +18,11 @@ static std::string parsePublicHostKey(std::string_view host, std::string_view ss
|
||||
}
|
||||
}
|
||||
|
||||
class InvalidSSHAuthority : public Error
|
||||
class InvalidSSHAuthority final : public CloneableError<InvalidSSHAuthority, Error>
|
||||
{
|
||||
public:
|
||||
InvalidSSHAuthority(const ParsedURL::Authority & authority, std::string_view reason)
|
||||
: Error("invalid SSH authority: '%s': %s", authority.to_string(), reason)
|
||||
: CloneableError("invalid SSH authority: '%s': %s", authority.to_string(), reason)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -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.string(), authority.host + " " + sshPublicHostKey + "\n");
|
||||
writeFile(fileName, authority.host + " " + sshPublicHostKey + "\n");
|
||||
args.insert(args.end(), {"-oUserKnownHostsFile=" + fileName.string()});
|
||||
}
|
||||
if (compress)
|
||||
|
||||
@@ -1190,8 +1190,14 @@ Derivation Store::derivationFromPath(const StorePath & drvPath)
|
||||
static Derivation readDerivationCommon(Store & store, const StorePath & drvPath, bool requireValidPath)
|
||||
{
|
||||
auto accessor = store.requireStoreObjectAccessor(drvPath, requireValidPath);
|
||||
auto contents = accessor->readFile(CanonPath::root);
|
||||
|
||||
try {
|
||||
return parseDerivation(store, accessor->readFile(CanonPath::root), Derivation::nameFromPath(drvPath));
|
||||
/* Special case for an empty file to show the user a better message */
|
||||
if (contents.empty())
|
||||
throw FormatError("file is empty (possible filesystem corruption)");
|
||||
|
||||
return parseDerivation(store, std::move(contents), Derivation::nameFromPath(drvPath));
|
||||
} catch (FormatError & e) {
|
||||
throw Error("error parsing derivation '%s': %s", store.printStorePath(drvPath), e.msg());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "nix/util/error.hh"
|
||||
#include "nix/util/file-path-impl.hh"
|
||||
#include "nix/util/split.hh"
|
||||
#include "nix/util/url.hh"
|
||||
#include "nix/store/store-reference.hh"
|
||||
@@ -17,7 +18,7 @@ static bool isNonUriPath(const std::string & spec)
|
||||
spec.find("://") == std::string::npos
|
||||
// Has at least one path separator, and so isn't a single word that
|
||||
// might be special like "auto"
|
||||
&& spec.find("/") != std::string::npos;
|
||||
&& OsPathTrait<char>::findPathSep(spec) != std::string::npos;
|
||||
}
|
||||
|
||||
std::string StoreReference::render(bool withParams) const
|
||||
@@ -111,7 +112,7 @@ StoreReference StoreReference::parse(const std::string & uri, const StoreReferen
|
||||
.variant =
|
||||
Specified{
|
||||
.scheme = "local",
|
||||
.authority = absPath(baseURI),
|
||||
.authority = encodeUrlPath(pathToUrlPath(absPath(std::filesystem::path{baseURI}))),
|
||||
},
|
||||
.params = std::move(params),
|
||||
};
|
||||
|
||||
@@ -42,17 +42,18 @@ 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, chrootStore);
|
||||
warn("%s does not exist, so Nix will use %s as a chroot store", stateDir, PathFmt(chrootStore));
|
||||
} else
|
||||
debug("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore);
|
||||
return make_ref<LocalStore::Config>("local", chrootStore, params);
|
||||
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);
|
||||
}
|
||||
#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;
|
||||
}
|
||||
@@ -1269,7 +1269,7 @@ void DerivationBuilderImpl::writeBuilderFile(const std::string & name, std::stri
|
||||
openat(tmpDirFd.get(), name.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC | O_EXCL | O_NOFOLLOW, 0666)};
|
||||
if (!fd)
|
||||
throw SysError("creating file %s", PathFmt(path));
|
||||
writeFile(fd, path, contents);
|
||||
writeFile(fd.get(), contents);
|
||||
chownToBuilder(fd.get(), path);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "nix/util/file-system.hh"
|
||||
#include "nix/util/logging.hh"
|
||||
#include "nix/store/pathlocks.hh"
|
||||
#include "nix/util/signals.hh"
|
||||
#include "nix/util/util.hh"
|
||||
#include "nix/util/windows-environment.hh"
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <errhandlingapi.h>
|
||||
@@ -51,31 +53,43 @@ AutoCloseFD openLockFile(const std::filesystem::path & path, bool create)
|
||||
return desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw a WinError, or if running under Wine, just warn and return true.
|
||||
* Wine has incomplete file locking support, so we degrade gracefully.
|
||||
*/
|
||||
template<typename... Args>
|
||||
static bool warnOrThrowWine(DWORD lastError, const std::string & fs, const Args &... args)
|
||||
{
|
||||
if (isWine()) {
|
||||
warn(fs + ": %s (ignored under Wine)", args..., lastError);
|
||||
return true;
|
||||
}
|
||||
throw WinError(lastError, fs, args...);
|
||||
}
|
||||
|
||||
bool lockFile(Descriptor desc, LockType lockType, bool wait)
|
||||
{
|
||||
switch (lockType) {
|
||||
case ltNone: {
|
||||
OVERLAPPED ov = {0};
|
||||
if (!UnlockFileEx(desc, 0, 2, 0, &ov)) {
|
||||
WinError winError("Failed to unlock file desc %s", desc);
|
||||
throw winError;
|
||||
}
|
||||
if (!UnlockFileEx(desc, 0, 2, 0, &ov))
|
||||
return warnOrThrowWine(GetLastError(), "Failed to unlock file %s", PathFmt(descriptorToPath(desc)));
|
||||
return true;
|
||||
}
|
||||
case ltRead: {
|
||||
OVERLAPPED ov = {0};
|
||||
if (!LockFileEx(desc, wait ? 0 : LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &ov)) {
|
||||
WinError winError("Failed to lock file desc %s", desc);
|
||||
if (winError.lastError == ERROR_LOCK_VIOLATION)
|
||||
auto lastError = GetLastError();
|
||||
if (lastError == ERROR_LOCK_VIOLATION)
|
||||
return false;
|
||||
throw winError;
|
||||
return warnOrThrowWine(lastError, "Failed to lock file %s", PathFmt(descriptorToPath(desc)));
|
||||
}
|
||||
|
||||
ov.Offset = 1;
|
||||
if (!UnlockFileEx(desc, 0, 1, 0, &ov)) {
|
||||
WinError winError("Failed to unlock file desc %s", desc);
|
||||
if (winError.lastError != ERROR_NOT_LOCKED)
|
||||
throw winError;
|
||||
auto lastError = GetLastError();
|
||||
if (lastError != ERROR_NOT_LOCKED)
|
||||
return warnOrThrowWine(lastError, "Failed to unlock file %s", PathFmt(descriptorToPath(desc)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -83,17 +97,17 @@ bool lockFile(Descriptor desc, LockType lockType, bool wait)
|
||||
OVERLAPPED ov = {0};
|
||||
ov.Offset = 1;
|
||||
if (!LockFileEx(desc, LOCKFILE_EXCLUSIVE_LOCK | (wait ? 0 : LOCKFILE_FAIL_IMMEDIATELY), 0, 1, 0, &ov)) {
|
||||
WinError winError("Failed to lock file desc %s", desc);
|
||||
if (winError.lastError == ERROR_LOCK_VIOLATION)
|
||||
auto lastError = GetLastError();
|
||||
if (lastError == ERROR_LOCK_VIOLATION)
|
||||
return false;
|
||||
throw winError;
|
||||
return warnOrThrowWine(lastError, "Failed to lock file %s", PathFmt(descriptorToPath(desc)));
|
||||
}
|
||||
|
||||
ov.Offset = 0;
|
||||
if (!UnlockFileEx(desc, 0, 1, 0, &ov)) {
|
||||
WinError winError("Failed to unlock file desc %s", desc);
|
||||
if (winError.lastError != ERROR_NOT_LOCKED)
|
||||
throw winError;
|
||||
auto lastError = GetLastError();
|
||||
if (lastError != ERROR_NOT_LOCKED)
|
||||
return warnOrThrowWine(lastError, "Failed to unlock file %s", PathFmt(descriptorToPath(desc)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -109,6 +109,13 @@ enum nix_err {
|
||||
*/
|
||||
NIX_ERR_NIX_ERROR = -4,
|
||||
|
||||
/**
|
||||
* @brief A recoverable error occurred.
|
||||
*
|
||||
* This is used primarily by C API *consumers* to communicate that a failed
|
||||
* primop call should be retried on the next evaluation attempt.
|
||||
*/
|
||||
NIX_ERR_RECOVERABLE = -5,
|
||||
};
|
||||
|
||||
typedef enum nix_err nix_err;
|
||||
|
||||
@@ -3,9 +3,15 @@
|
||||
|
||||
#include "nix/util/file-descriptor.hh"
|
||||
#include "nix/util/serialise.hh"
|
||||
#include "nix/util/signals.hh"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <fcntl.h>
|
||||
# include <stdlib.h>
|
||||
#endif
|
||||
|
||||
namespace nix {
|
||||
|
||||
// BufferedSource with configurable small buffer for precise boundary testing.
|
||||
@@ -113,6 +119,55 @@ TEST(ReadLine, LineWithNullBytes)
|
||||
3));
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
TEST(ReadLine, TreatsEioAsEof)
|
||||
{
|
||||
// Open a pty master. When the slave side is closed (or never opened),
|
||||
// reading from the master returns EIO, which readLine should treat as EOF.
|
||||
int master = posix_openpt(O_RDWR | O_NOCTTY);
|
||||
ASSERT_NE(master, -1);
|
||||
ASSERT_EQ(grantpt(master), 0);
|
||||
ASSERT_EQ(unlockpt(master), 0);
|
||||
|
||||
// Open and immediately close the slave to trigger EIO on the master.
|
||||
int slave = open(ptsname(master), O_RDWR | O_NOCTTY);
|
||||
ASSERT_NE(slave, -1);
|
||||
close(slave);
|
||||
|
||||
// With eofOk=true, readLine should return empty string (treating EIO as EOF).
|
||||
EXPECT_EQ(readLine(master, /*eofOk=*/true), "");
|
||||
|
||||
// With eofOk=false, readLine should throw EndOfFile.
|
||||
EXPECT_THROW(readLine(master), EndOfFile);
|
||||
|
||||
close(master);
|
||||
}
|
||||
|
||||
// macOS (BSD) discards buffered pty data on slave close and returns normal
|
||||
// EOF (0) instead of EIO, so partial data never reaches the master.
|
||||
# ifdef __linux__
|
||||
TEST(ReadLine, PartialLineBeforeEio)
|
||||
{
|
||||
int master = posix_openpt(O_RDWR | O_NOCTTY);
|
||||
ASSERT_NE(master, -1);
|
||||
ASSERT_EQ(grantpt(master), 0);
|
||||
ASSERT_EQ(unlockpt(master), 0);
|
||||
|
||||
int slave = open(ptsname(master), O_RDWR | O_NOCTTY);
|
||||
ASSERT_NE(slave, -1);
|
||||
|
||||
// Write a partial line (no terminator) from the slave, then close it.
|
||||
ASSERT_EQ(::write(slave, "partial", 7), 7);
|
||||
close(slave);
|
||||
|
||||
// readLine should return the partial data when eofOk=true.
|
||||
EXPECT_EQ(readLine(master, /*eofOk=*/true), "partial");
|
||||
|
||||
close(master);
|
||||
}
|
||||
# endif
|
||||
#endif
|
||||
|
||||
TEST(BufferedSourceReadLine, ReadsLinesFromPipe)
|
||||
{
|
||||
Pipe pipe;
|
||||
@@ -243,4 +298,25 @@ TEST(BufferedSourceReadLine, BufferExhaustedThenEof)
|
||||
EXPECT_EQ(source.readLine(/*eofOk=*/true), "");
|
||||
}
|
||||
|
||||
TEST(WriteFull, RespectsAllowInterrupts)
|
||||
{
|
||||
Pipe pipe;
|
||||
pipe.create();
|
||||
|
||||
setInterrupted(true);
|
||||
|
||||
// Must not throw Interrupted even though the interrupt flag is set.
|
||||
EXPECT_NO_THROW(writeFull(pipe.writeSide.get(), "hello", /*allowInterrupts=*/false));
|
||||
|
||||
// Must throw Interrupted when allowInterrupts is true.
|
||||
EXPECT_THROW(writeFull(pipe.writeSide.get(), "hello", /*allowInterrupts=*/true), Interrupted);
|
||||
|
||||
setInterrupted(false);
|
||||
pipe.writeSide.close();
|
||||
|
||||
// Verify the data from the first write was actually written.
|
||||
FdSource source(pipe.readSide.get());
|
||||
EXPECT_EQ(source.readLine(/*eofOk=*/true), "hello");
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -84,14 +84,18 @@ TEST(openFileEnsureBeneathNoSymlinks, works)
|
||||
dirSink.createDirectory(CanonPath("d"));
|
||||
dirSink.createSymlink(CanonPath("c"), "./d");
|
||||
});
|
||||
#ifdef _WIN32
|
||||
EXPECT_THROW(sink.createDirectory(CanonPath("a/b/c/e")), SymlinkNotAllowed);
|
||||
#else
|
||||
// FIXME: This still follows symlinks on Unix (incorrectly succeeds)
|
||||
sink.createDirectory(CanonPath("a/b/c/e"));
|
||||
#endif
|
||||
// Test that symlinks in intermediate path are detected during nested operations
|
||||
ASSERT_THROW(
|
||||
EXPECT_THROW(
|
||||
sink.createDirectory(
|
||||
CanonPath("a/b/c/f"), [](FileSystemObjectSink & dirSink, const CanonPath & relPath) {}),
|
||||
SymlinkNotAllowed);
|
||||
ASSERT_THROW(
|
||||
EXPECT_THROW(
|
||||
sink.createRegularFile(
|
||||
CanonPath("a/b/c/regular"), [](CreateRegularFileSink & crf) { crf("some contents"); }),
|
||||
SymlinkNotAllowed);
|
||||
@@ -100,7 +104,7 @@ TEST(openFileEnsureBeneathNoSymlinks, works)
|
||||
AutoCloseFD dirFd = openDirectory(tmpDir);
|
||||
|
||||
// Helper to open files with platform-specific arguments
|
||||
auto openRead = [&](std::string_view path) -> Descriptor {
|
||||
auto openRead = [&](std::string_view path) -> AutoCloseFD {
|
||||
return openFileEnsureBeneathNoSymlinks(
|
||||
dirFd.get(),
|
||||
CanonPath(path),
|
||||
@@ -114,7 +118,7 @@ TEST(openFileEnsureBeneathNoSymlinks, works)
|
||||
);
|
||||
};
|
||||
|
||||
auto openReadDir = [&](std::string_view path) -> Descriptor {
|
||||
auto openReadDir = [&](std::string_view path) -> AutoCloseFD {
|
||||
return openFileEnsureBeneathNoSymlinks(
|
||||
dirFd.get(),
|
||||
CanonPath(path),
|
||||
@@ -128,7 +132,7 @@ TEST(openFileEnsureBeneathNoSymlinks, works)
|
||||
);
|
||||
};
|
||||
|
||||
auto openCreateExclusive = [&](std::string_view path) -> Descriptor {
|
||||
auto openCreateExclusive = [&](std::string_view path) -> AutoCloseFD {
|
||||
return openFileEnsureBeneathNoSymlinks(
|
||||
dirFd.get(),
|
||||
CanonPath(path),
|
||||
@@ -154,19 +158,19 @@ TEST(openFileEnsureBeneathNoSymlinks, works)
|
||||
|
||||
#if !defined(_WIN32) && !defined(__CYGWIN__)
|
||||
// This returns ELOOP on cygwin when O_NOFOLLOW is used
|
||||
EXPECT_EQ(openCreateExclusive("a/broken_symlink"), INVALID_DESCRIPTOR);
|
||||
EXPECT_FALSE(openCreateExclusive("a/broken_symlink"));
|
||||
/* 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);
|
||||
EXPECT_FALSE(openRead("c/d/regular/a"));
|
||||
EXPECT_FALSE(openReadDir("c/d/regular"));
|
||||
|
||||
// Test valid paths work
|
||||
EXPECT_TRUE(AutoCloseFD{openRead("c/d/regular")});
|
||||
EXPECT_TRUE(AutoCloseFD{openCreateExclusive("a/regular")});
|
||||
EXPECT_TRUE(openRead("c/d/regular"));
|
||||
EXPECT_TRUE(openCreateExclusive("a/regular"));
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -16,10 +16,12 @@ using namespace std::string_view_literals;
|
||||
|
||||
#ifdef _WIN32
|
||||
# define FS_SEP L"\\"
|
||||
# define FS_ROOT L"C:" FS_SEP // Need a mounted one, C drive is likely
|
||||
# define FS_ROOT_NO_TRAILING_SLASH L"C:" // Need a mounted one, C drive is likely
|
||||
# define FS_ROOT FS_ROOT_NO_TRAILING_SLASH FS_SEP
|
||||
#else
|
||||
# define FS_SEP "/"
|
||||
# define FS_ROOT FS_SEP
|
||||
# define FS_ROOT_NO_TRAILING_SLASH FS_SEP
|
||||
# define FS_ROOT FS_ROOT_NO_TRAILING_SLASH
|
||||
#endif
|
||||
|
||||
#ifndef PATH_MAX
|
||||
@@ -44,7 +46,7 @@ TEST(absPath, doesntChangeRoot)
|
||||
{
|
||||
auto p = absPath(std::filesystem::path{FS_ROOT});
|
||||
|
||||
ASSERT_EQ(p, FS_ROOT);
|
||||
ASSERT_EQ(p, FS_ROOT_NO_TRAILING_SLASH);
|
||||
}
|
||||
|
||||
TEST(absPath, turnsEmptyPathIntoCWD)
|
||||
@@ -196,42 +198,42 @@ TEST(baseNameOf, absoluteNothingSlashNothing)
|
||||
|
||||
TEST(isInDir, trivialCase)
|
||||
{
|
||||
EXPECT_TRUE(isInDir("/foo/bar", "/foo"));
|
||||
EXPECT_TRUE(isInDir(FS_ROOT "foo" FS_SEP "bar", FS_ROOT "foo"));
|
||||
}
|
||||
|
||||
TEST(isInDir, notInDir)
|
||||
{
|
||||
EXPECT_FALSE(isInDir("/zes/foo/bar", "/foo"));
|
||||
EXPECT_FALSE(isInDir(FS_ROOT "zes" FS_SEP "foo" FS_SEP "bar", FS_ROOT "foo"));
|
||||
}
|
||||
|
||||
TEST(isInDir, emptyDir)
|
||||
{
|
||||
EXPECT_FALSE(isInDir("/zes/foo/bar", ""));
|
||||
EXPECT_FALSE(isInDir(FS_ROOT "zes" FS_SEP "foo" FS_SEP "bar", ""));
|
||||
}
|
||||
|
||||
TEST(isInDir, hiddenSubdirectory)
|
||||
{
|
||||
EXPECT_TRUE(isInDir("/foo/.ssh", "/foo"));
|
||||
EXPECT_TRUE(isInDir(FS_ROOT "foo" FS_SEP ".ssh", FS_ROOT "foo"));
|
||||
}
|
||||
|
||||
TEST(isInDir, ellipsisEntry)
|
||||
{
|
||||
EXPECT_TRUE(isInDir("/foo/...", "/foo"));
|
||||
EXPECT_TRUE(isInDir(FS_ROOT "foo" FS_SEP "...", FS_ROOT "foo"));
|
||||
}
|
||||
|
||||
TEST(isInDir, sameDir)
|
||||
{
|
||||
EXPECT_FALSE(isInDir("/foo", "/foo"));
|
||||
EXPECT_FALSE(isInDir(FS_ROOT "foo", FS_ROOT "foo"));
|
||||
}
|
||||
|
||||
TEST(isInDir, sameDirDot)
|
||||
{
|
||||
EXPECT_FALSE(isInDir("/foo/.", "/foo"));
|
||||
EXPECT_FALSE(isInDir(FS_ROOT "foo" FS_SEP ".", FS_ROOT "foo"));
|
||||
}
|
||||
|
||||
TEST(isInDir, dotDotPrefix)
|
||||
{
|
||||
EXPECT_FALSE(isInDir("/foo/../bar", "/foo"));
|
||||
EXPECT_FALSE(isInDir(FS_ROOT "foo" FS_SEP ".." FS_SEP "bar", FS_ROOT "foo"));
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
@@ -240,8 +242,8 @@ TEST(isInDir, dotDotPrefix)
|
||||
|
||||
TEST(isDirOrInDir, trueForSameDirectory)
|
||||
{
|
||||
ASSERT_EQ(isDirOrInDir("/nix", "/nix"), true);
|
||||
ASSERT_EQ(isDirOrInDir("/", "/"), true);
|
||||
ASSERT_EQ(isDirOrInDir(FS_ROOT "nix", FS_ROOT "nix"), true);
|
||||
ASSERT_EQ(isDirOrInDir(FS_ROOT, FS_ROOT), true);
|
||||
}
|
||||
|
||||
TEST(isDirOrInDir, trueForEmptyPaths)
|
||||
@@ -251,17 +253,17 @@ TEST(isDirOrInDir, trueForEmptyPaths)
|
||||
|
||||
TEST(isDirOrInDir, falseForDisjunctPaths)
|
||||
{
|
||||
ASSERT_EQ(isDirOrInDir("/foo", "/bar"), false);
|
||||
ASSERT_EQ(isDirOrInDir(FS_ROOT "foo", FS_ROOT "bar"), false);
|
||||
}
|
||||
|
||||
TEST(isDirOrInDir, relativePaths)
|
||||
{
|
||||
ASSERT_EQ(isDirOrInDir("/foo/..", "/foo"), false);
|
||||
ASSERT_EQ(isDirOrInDir(FS_ROOT "foo" FS_SEP "..", FS_ROOT "foo"), false);
|
||||
}
|
||||
|
||||
TEST(isDirOrInDir, relativePathsTwice)
|
||||
{
|
||||
ASSERT_EQ(isDirOrInDir("/foo/..", "/foo/."), false);
|
||||
ASSERT_EQ(isDirOrInDir(FS_ROOT "foo" FS_SEP "..", FS_ROOT "foo" FS_SEP "."), false);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
@@ -294,13 +296,16 @@ TEST(makeParentCanonical, noParent)
|
||||
|
||||
TEST(makeParentCanonical, root)
|
||||
{
|
||||
ASSERT_EQ(makeParentCanonical("/"), "/");
|
||||
ASSERT_EQ(makeParentCanonical(FS_ROOT), FS_ROOT_NO_TRAILING_SLASH);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* chmodIfNeeded
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef _WIN32
|
||||
// Windows doesn't support Unix-style permission bits - lstat always
|
||||
// returns the same mode regardless of what chmod sets.
|
||||
TEST(chmodIfNeeded, works)
|
||||
{
|
||||
auto [autoClose_, tmpFile] = nix::createTempFile();
|
||||
@@ -316,6 +321,7 @@ TEST(chmodIfNeeded, works)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(chmodIfNeeded, nonexistent)
|
||||
{
|
||||
|
||||
@@ -137,4 +137,34 @@ TEST_F(FSSourceAccessorTest, works)
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* RestoreSink non-directory at root (no dirFd)
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST_F(FSSourceAccessorTest, RestoreSinkRegularFileAtRoot)
|
||||
{
|
||||
auto filePath = tmpDir / "rootfile";
|
||||
{
|
||||
RestoreSink sink(false);
|
||||
sink.dstPath = filePath;
|
||||
// No dirFd set - this tests the !dirFd path
|
||||
sink.createRegularFile(CanonPath::root, [](CreateRegularFileSink & crf) { crf("root content"); });
|
||||
}
|
||||
|
||||
EXPECT_THAT(makeFSSourceAccessor(filePath), HasContents(CanonPath::root, "root content"));
|
||||
}
|
||||
|
||||
TEST_F(FSSourceAccessorTest, RestoreSinkSymlinkAtRoot)
|
||||
{
|
||||
auto linkPath = tmpDir / "rootlink";
|
||||
{
|
||||
RestoreSink sink(false);
|
||||
sink.dstPath = linkPath;
|
||||
// No dirFd set - this tests the !dirFd path
|
||||
sink.createSymlink(CanonPath::root, "symlink_target");
|
||||
}
|
||||
|
||||
EXPECT_THAT(makeFSSourceAccessor(linkPath), HasSymlink(CanonPath::root, "symlink_target"));
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -956,4 +956,108 @@ TEST(nix, isValidSchemeName)
|
||||
ASSERT_FALSE(isValidSchemeName("http "));
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* pathToUrlPath / urlPathToPath
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
struct UrlPathTestCase
|
||||
{
|
||||
std::string_view urlString;
|
||||
ParsedURL urlParsed;
|
||||
std::filesystem::path path;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
class UrlPathTest : public ::testing::TestWithParam<UrlPathTestCase>
|
||||
{};
|
||||
|
||||
TEST_P(UrlPathTest, pathToUrlPath)
|
||||
{
|
||||
const auto & testCase = GetParam();
|
||||
auto urlPath = pathToUrlPath(testCase.path);
|
||||
EXPECT_EQ(urlPath, testCase.urlParsed.path);
|
||||
}
|
||||
|
||||
TEST_P(UrlPathTest, urlPathToPath)
|
||||
{
|
||||
const auto & testCase = GetParam();
|
||||
auto path = urlPathToPath(testCase.urlParsed.path);
|
||||
EXPECT_EQ(path, testCase.path);
|
||||
}
|
||||
|
||||
TEST_P(UrlPathTest, urlToString)
|
||||
{
|
||||
const auto & testCase = GetParam();
|
||||
EXPECT_EQ(testCase.urlParsed.to_string(), testCase.urlString);
|
||||
}
|
||||
|
||||
TEST_P(UrlPathTest, stringToUrl)
|
||||
{
|
||||
const auto & testCase = GetParam();
|
||||
auto parsed = parseURL(std::string{testCase.urlString});
|
||||
EXPECT_EQ(parsed, testCase.urlParsed);
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Unix,
|
||||
UrlPathTest,
|
||||
::testing::Values(
|
||||
UrlPathTestCase{
|
||||
.urlString = "file:///foo/bar/baz",
|
||||
.urlParsed =
|
||||
ParsedURL{
|
||||
.scheme = "file",
|
||||
.authority = ParsedURL::Authority{},
|
||||
.path = {"", "foo", "bar", "baz"},
|
||||
},
|
||||
.path = "/foo/bar/baz",
|
||||
.description = "absolute_path",
|
||||
},
|
||||
UrlPathTestCase{
|
||||
.urlString = "file:///",
|
||||
.urlParsed =
|
||||
ParsedURL{
|
||||
.scheme = "file",
|
||||
.authority = ParsedURL::Authority{},
|
||||
.path = {"", ""},
|
||||
},
|
||||
.path = "/",
|
||||
.description = "root_path",
|
||||
}),
|
||||
[](const auto & info) { return info.param.description; });
|
||||
|
||||
#else // _WIN32
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Windows,
|
||||
UrlPathTest,
|
||||
::testing::Values(
|
||||
UrlPathTestCase{
|
||||
.urlString = "file:///C:/foo/bar/baz",
|
||||
.urlParsed =
|
||||
ParsedURL{
|
||||
.scheme = "file",
|
||||
.authority = ParsedURL::Authority{},
|
||||
.path = {"", "C:", "foo", "bar", "baz"},
|
||||
},
|
||||
.path = L"C:\\foo\\bar\\baz",
|
||||
.description = "absolute_path",
|
||||
},
|
||||
UrlPathTestCase{
|
||||
.urlString = "file:///C:/",
|
||||
.urlParsed =
|
||||
ParsedURL{
|
||||
.scheme = "file",
|
||||
.authority = ParsedURL::Authority{},
|
||||
.path = {"", "C:", ""},
|
||||
},
|
||||
.path = L"C:\\",
|
||||
.description = "drive_root",
|
||||
}),
|
||||
[](const auto & info) { return info.param.description; });
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -41,6 +41,11 @@ const std::string & BaseError::calcWhat() const
|
||||
}
|
||||
}
|
||||
|
||||
bool BaseError::hasPos() const
|
||||
{
|
||||
return err.pos.get() && *err.pos.get();
|
||||
}
|
||||
|
||||
std::optional<std::string> ErrorInfo::programName = std::nullopt;
|
||||
|
||||
std::ostream & operator<<(std::ostream & os, const HintFmt & hf)
|
||||
@@ -422,13 +427,20 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
|
||||
*/
|
||||
static void writeErr(std::string_view buf)
|
||||
{
|
||||
Descriptor fd = getStandardError();
|
||||
while (!buf.empty()) {
|
||||
auto n = write(STDERR_FILENO, buf.data(), buf.size());
|
||||
#ifdef _WIN32
|
||||
DWORD n;
|
||||
if (!WriteFile(fd, buf.data(), buf.size(), &n, NULL))
|
||||
abort();
|
||||
#else
|
||||
auto n = ::write(fd, buf.data(), buf.size());
|
||||
if (n < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
buf = buf.substr(n);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,17 +20,23 @@ 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);
|
||||
|
||||
std::vector<std::filesystem::path> ret;
|
||||
ret.reserve(strings.size());
|
||||
directories.reserve(directories.size() + strings.size());
|
||||
|
||||
std::transform(
|
||||
std::make_move_iterator(strings.begin()),
|
||||
std::make_move_iterator(strings.end()),
|
||||
std::back_inserter(ret),
|
||||
std::back_inserter(directories),
|
||||
[](OsString && str) {
|
||||
return std::filesystem::path{
|
||||
str.empty()
|
||||
@@ -45,8 +51,6 @@ ExecutablePath ExecutablePath::parse(const OsString & path)
|
||||
: std::move(str),
|
||||
};
|
||||
});
|
||||
|
||||
return {ret};
|
||||
}
|
||||
|
||||
OsString ExecutablePath::render() const
|
||||
|
||||
@@ -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, ")")))
|
||||
|
||||
@@ -8,10 +8,112 @@
|
||||
#ifdef _WIN32
|
||||
# include <winnt.h>
|
||||
# include <fileapi.h>
|
||||
#else
|
||||
# include <poll.h>
|
||||
#endif
|
||||
|
||||
namespace nix {
|
||||
|
||||
namespace {
|
||||
|
||||
enum class PollDirection { In, Out };
|
||||
|
||||
/**
|
||||
* Retry an I/O operation if it fails with EAGAIN/EWOULDBLOCK.
|
||||
*
|
||||
* On Unix, polls the fd and retries. On Windows, just calls `f` once.
|
||||
*
|
||||
* This retry logic is needed to handle non-blocking reads/writes. This
|
||||
* is needed in the buildhook, because somehow the json logger file
|
||||
* descriptor ends up being non-blocking and breaks remote-building.
|
||||
*
|
||||
* @todo Get rid of buildhook and remove this logic again
|
||||
* (https://github.com/NixOS/nix/issues/12688)
|
||||
*/
|
||||
template<typename F>
|
||||
auto retryOnBlock([[maybe_unused]] Descriptor fd, [[maybe_unused]] PollDirection dir, F && f) -> decltype(f())
|
||||
{
|
||||
#ifndef _WIN32
|
||||
while (true) {
|
||||
try {
|
||||
return std::forward<F>(f)();
|
||||
} catch (SystemError & e) {
|
||||
if (e.is(std::errc::resource_unavailable_try_again) || e.is(std::errc::operation_would_block)) {
|
||||
struct pollfd pfd;
|
||||
pfd.fd = fd;
|
||||
pfd.events = dir == PollDirection::In ? POLLIN : POLLOUT;
|
||||
if (poll(&pfd, 1, -1) == -1)
|
||||
throw SysError("poll on file descriptor failed");
|
||||
continue;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
#else
|
||||
return std::forward<F>(f)();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void readFull(Descriptor fd, char * buf, size_t count)
|
||||
{
|
||||
while (count) {
|
||||
checkInterrupt();
|
||||
auto res = retryOnBlock(
|
||||
fd, PollDirection::In, [&]() { return read(fd, {reinterpret_cast<std::byte *>(buf), count}); });
|
||||
if (res == 0)
|
||||
throw EndOfFile("unexpected end-of-file");
|
||||
count -= res;
|
||||
buf += res;
|
||||
}
|
||||
}
|
||||
|
||||
std::string readLine(Descriptor fd, bool eofOk, char terminator)
|
||||
{
|
||||
std::string s;
|
||||
while (1) {
|
||||
checkInterrupt();
|
||||
char ch;
|
||||
// FIXME: inefficient
|
||||
auto rd = retryOnBlock(fd, PollDirection::In, [&]() -> size_t {
|
||||
try {
|
||||
return read(fd, {reinterpret_cast<std::byte *>(&ch), 1});
|
||||
} catch (SystemError & e) {
|
||||
// On pty masters, EIO signals that the slave side closed,
|
||||
// which is semantically EOF. Map it to a zero-length read
|
||||
// so the existing EOF path handles it.
|
||||
if (e.is(std::errc::io_error))
|
||||
return 0;
|
||||
throw;
|
||||
}
|
||||
});
|
||||
if (rd == 0) {
|
||||
if (eofOk)
|
||||
return s;
|
||||
else
|
||||
throw EndOfFile("unexpected EOF reading a line");
|
||||
} else {
|
||||
if (ch == terminator)
|
||||
return s;
|
||||
s += ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeFull(Descriptor fd, std::string_view s, bool allowInterrupts)
|
||||
{
|
||||
while (!s.empty()) {
|
||||
if (allowInterrupts)
|
||||
checkInterrupt();
|
||||
auto res = retryOnBlock(fd, PollDirection::Out, [&]() {
|
||||
return write(fd, {reinterpret_cast<const std::byte *>(s.data()), s.size()}, allowInterrupts);
|
||||
});
|
||||
if (res > 0)
|
||||
s.remove_prefix(res);
|
||||
}
|
||||
}
|
||||
|
||||
void writeLine(Descriptor fd, std::string s)
|
||||
{
|
||||
s += '\n';
|
||||
@@ -67,8 +169,6 @@ void drainFD(Descriptor fd, Sink & sink, DrainFdSinkOpts opts)
|
||||
&& (e.is(std::errc::resource_unavailable_try_again) || e.is(std::errc::operation_would_block)))
|
||||
break;
|
||||
#endif
|
||||
if (e.is(std::errc::interrupted))
|
||||
continue;
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -105,18 +205,8 @@ void copyFdRange(Descriptor fd, off_t offset, size_t nbytes, Sink & sink)
|
||||
std::array<std::byte, 64 * 1024> buf;
|
||||
|
||||
while (left) {
|
||||
checkInterrupt();
|
||||
auto limit = std::min<size_t>(left, buf.size());
|
||||
/* Should be initialized before we read, because the `catch`
|
||||
block either throws or continues. */
|
||||
size_t n;
|
||||
try {
|
||||
n = readOffset(fd, offset, std::span(buf.data(), limit));
|
||||
} catch (SystemError & e) {
|
||||
if (e.is(std::errc::interrupted))
|
||||
continue;
|
||||
throw;
|
||||
}
|
||||
auto n = readOffset(fd, offset, std::span(buf.data(), limit));
|
||||
if (n == 0)
|
||||
throw EndOfFile("unexpected end-of-file");
|
||||
assert(n <= left);
|
||||
@@ -184,24 +274,6 @@ void AutoCloseFD::close()
|
||||
}
|
||||
}
|
||||
|
||||
void AutoCloseFD::fsync() const
|
||||
{
|
||||
if (fd != INVALID_DESCRIPTOR) {
|
||||
int result;
|
||||
result =
|
||||
#ifdef _WIN32
|
||||
::FlushFileBuffers(fd)
|
||||
#elif defined(__APPLE__)
|
||||
::fcntl(fd, F_FULLFSYNC)
|
||||
#else
|
||||
::fsync(fd)
|
||||
#endif
|
||||
;
|
||||
if (result == -1)
|
||||
throw NativeSysError("fsync file descriptor %1%", fd);
|
||||
}
|
||||
}
|
||||
|
||||
void AutoCloseFD::startFsync() const
|
||||
{
|
||||
#ifdef __linux__
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <boost/exception/diagnostic_information.hpp>
|
||||
#include <boost/iostreams/device/mapped_file.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
# include <sys/param.h>
|
||||
@@ -262,13 +264,13 @@ std::optional<PosixStat> maybeLstat(const std::filesystem::path & path)
|
||||
|
||||
bool pathExists(const std::filesystem::path & path)
|
||||
{
|
||||
return maybeLstat(path.string()).has_value();
|
||||
return maybeLstat(path).has_value();
|
||||
}
|
||||
|
||||
bool pathAccessible(const std::filesystem::path & path)
|
||||
{
|
||||
try {
|
||||
return pathExists(path.string());
|
||||
return pathExists(path);
|
||||
} catch (SystemError & e) {
|
||||
// swallow EPERM
|
||||
if (e.is(std::errc::operation_not_permitted))
|
||||
@@ -305,25 +307,27 @@ std::string readFile(const std::filesystem::path & path)
|
||||
return readFile(fd.get());
|
||||
}
|
||||
|
||||
void readFile(const Path & path, Sink & sink, bool memory_map)
|
||||
void readFile(const std::filesystem::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);
|
||||
// mapped_file_source can't be constructed from std::filesystem::path with wide paths. Go
|
||||
// through boost::filesystem::path.
|
||||
boost::iostreams::mapped_file_source mmap(boost::filesystem::path{path.native()});
|
||||
if (mmap.is_open()) {
|
||||
sink({mmap.data(), mmap.size()});
|
||||
return;
|
||||
}
|
||||
} catch (const boost::exception & e) {
|
||||
debug("memory-mapping failed for path: %s: %s", PathFmt(path), boost::diagnostic_information(e));
|
||||
}
|
||||
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));
|
||||
AutoCloseFD fd = openFileReadonly(path);
|
||||
if (!fd)
|
||||
throw NativeSysError("opening file %s", path);
|
||||
throw NativeSysError("opening file %s", PathFmt(path));
|
||||
drainFD(fd.get(), sink);
|
||||
}
|
||||
|
||||
@@ -340,23 +344,23 @@ void writeFile(const Path & path, std::string_view s, mode_t mode, FsSync sync)
|
||||
if (!fd)
|
||||
throw SysError("opening file '%1%'", path);
|
||||
|
||||
writeFile(fd, path, s, mode, sync);
|
||||
writeFile(fd.get(), s, sync, &path);
|
||||
|
||||
/* Close explicitly to propagate the exceptions. */
|
||||
fd.close();
|
||||
}
|
||||
|
||||
void writeFile(AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode, FsSync sync)
|
||||
void writeFile(Descriptor fd, std::string_view s, FsSync sync, const Path * origPath)
|
||||
{
|
||||
assert(fd);
|
||||
assert(fd != INVALID_DESCRIPTOR);
|
||||
try {
|
||||
writeFull(fd.get(), s);
|
||||
writeFull(fd, s);
|
||||
|
||||
if (sync == FsSync::Yes)
|
||||
fd.fsync();
|
||||
syncDescriptor(fd);
|
||||
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "writing file '%1%'", origPath);
|
||||
e.addTrace({}, "writing file '%1%'", origPath ? *origPath : descriptorToPath(fd).string());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ void RestoreSink::createRegularFile(const CanonPath & path, std::function<void(C
|
||||
component are not followed. */
|
||||
constexpr int flags = O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC;
|
||||
if (!dirFd)
|
||||
return ::open(p.c_str(), flags, 0666);
|
||||
return AutoCloseFD{::open(p.c_str(), flags, 0666)};
|
||||
return openFileEnsureBeneathNoSymlinks(dirFd.get(), path, flags, 0666);
|
||||
}()
|
||||
#endif
|
||||
|
||||
@@ -191,6 +191,8 @@ public:
|
||||
err.pos = pos;
|
||||
}
|
||||
|
||||
bool hasPos() const;
|
||||
|
||||
void pushTrace(Trace trace)
|
||||
{
|
||||
err.traces.push_front(trace);
|
||||
@@ -227,13 +229,31 @@ public:
|
||||
{
|
||||
return err;
|
||||
};
|
||||
|
||||
[[noreturn]] virtual void throwClone() const = 0;
|
||||
};
|
||||
|
||||
#define MakeError(newClass, superClass) \
|
||||
class newClass : public superClass \
|
||||
{ \
|
||||
public: \
|
||||
using superClass::superClass; \
|
||||
template<typename Derived, typename Base>
|
||||
class CloneableError : public Base
|
||||
{
|
||||
public:
|
||||
using Base::Base;
|
||||
|
||||
/**
|
||||
* Rethrow a copy of this exception. Useful when the exception can get
|
||||
* modified when appending traces.
|
||||
*/
|
||||
[[noreturn]] void throwClone() const override
|
||||
{
|
||||
throw Derived(static_cast<const Derived &>(*this));
|
||||
}
|
||||
};
|
||||
|
||||
#define MakeError(newClass, superClass) \
|
||||
class newClass : public CloneableError<newClass, superClass> \
|
||||
{ \
|
||||
public: \
|
||||
using CloneableError<newClass, superClass>::CloneableError; \
|
||||
}
|
||||
|
||||
MakeError(Error, BaseError);
|
||||
@@ -245,7 +265,7 @@ MakeError(UnimplementedError, Error);
|
||||
* std::error_code. Use when you want to catch and check an error condition like
|
||||
* no_such_file_or_directory (ENOENT) without ifdefs.
|
||||
*/
|
||||
class SystemError : public Error
|
||||
class SystemError : public CloneableError<SystemError, Error>
|
||||
{
|
||||
std::error_code errorCode;
|
||||
std::string errorDetails;
|
||||
@@ -265,7 +285,7 @@ protected:
|
||||
*/
|
||||
template<typename... Args>
|
||||
SystemError(Disambig, std::error_code errorCode, std::string_view errorDetails, Args &&... args)
|
||||
: Error("")
|
||||
: CloneableError("")
|
||||
, errorCode(errorCode)
|
||||
, errorDetails(errorDetails)
|
||||
{
|
||||
@@ -311,7 +331,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;
|
||||
@@ -322,7 +342,7 @@ public:
|
||||
*/
|
||||
template<typename... Args>
|
||||
SysError(int errNo, Args &&... args)
|
||||
: SystemError(
|
||||
: CloneableError(
|
||||
Disambig{},
|
||||
std::make_error_code(static_cast<std::errc>(errNo)),
|
||||
strerror(errNo),
|
||||
@@ -392,7 +412,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;
|
||||
@@ -404,7 +424,7 @@ public:
|
||||
*/
|
||||
template<typename... Args>
|
||||
WinError(DWORD lastError, Args &&... args)
|
||||
: SystemError(
|
||||
: CloneableError(
|
||||
Disambig{},
|
||||
std::error_code(lastError, std::system_category()),
|
||||
renderError(lastError),
|
||||
|
||||
@@ -33,6 +33,12 @@ 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:
|
||||
/**
|
||||
|
||||
@@ -65,7 +65,7 @@ std::string readFile(Descriptor fd);
|
||||
* Platform-specific read into a buffer.
|
||||
*
|
||||
* Thin wrapper around ::read (Unix) or ReadFile (Windows).
|
||||
* Does NOT handle EINTR on Unix - caller must catch and retry if needed.
|
||||
* Handles EINTR on Unix. Treats ERROR_BROKEN_PIPE as EOF on Windows.
|
||||
*
|
||||
* @param fd The file descriptor to read from
|
||||
* @param buffer The buffer to read into
|
||||
@@ -74,6 +74,19 @@ std::string readFile(Descriptor fd);
|
||||
*/
|
||||
size_t read(Descriptor fd, std::span<std::byte> buffer);
|
||||
|
||||
/**
|
||||
* Platform-specific write from a buffer.
|
||||
*
|
||||
* Thin wrapper around ::write (Unix) or WriteFile (Windows).
|
||||
* Handles EINTR on Unix.
|
||||
*
|
||||
* @param fd The file descriptor to write to
|
||||
* @param buffer The buffer to write from
|
||||
* @return The number of bytes actually written
|
||||
* @throws SystemError on failure
|
||||
*/
|
||||
size_t write(Descriptor fd, std::span<const std::byte> buffer, bool allowInterrupts);
|
||||
|
||||
/**
|
||||
* Get the size of a file.
|
||||
*
|
||||
@@ -133,6 +146,11 @@ std::string readLine(Descriptor fd, bool eofOk = false, char terminator = '\n');
|
||||
*/
|
||||
void writeLine(Descriptor fd, std::string s);
|
||||
|
||||
/**
|
||||
* Perform a blocking fsync operation on a file descriptor.
|
||||
*/
|
||||
void syncDescriptor(Descriptor fd);
|
||||
|
||||
/**
|
||||
* Options for draining a file descriptor to a sink.
|
||||
*/
|
||||
@@ -253,7 +271,11 @@ public:
|
||||
/**
|
||||
* Perform a blocking fsync operation.
|
||||
*/
|
||||
void fsync() const;
|
||||
void fsync() const
|
||||
{
|
||||
if (fd != INVALID_DESCRIPTOR)
|
||||
nix::syncDescriptor(fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously flush to disk without blocking, if available on
|
||||
|
||||
@@ -54,7 +54,7 @@ OsString readLinkAt(Descriptor dirFd, const CanonPath & path);
|
||||
* @throws SymlinkNotAllowed if any path components are symlinks
|
||||
* @throws SystemError on other errors
|
||||
*/
|
||||
Descriptor openFileEnsureBeneathNoSymlinks(
|
||||
AutoCloseFD openFileEnsureBeneathNoSymlinks(
|
||||
Descriptor dirFd,
|
||||
const CanonPath & path,
|
||||
#ifdef _WIN32
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "nix/util/file-descriptor.hh"
|
||||
#include "nix/util/file-path.hh"
|
||||
|
||||
#include <filesystem>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
@@ -243,7 +244,7 @@ Descriptor openNewFileForWrite(const std::filesystem::path & path, mode_t mode,
|
||||
*/
|
||||
std::string readFile(const Path & path);
|
||||
std::string readFile(const std::filesystem::path & path);
|
||||
void readFile(const Path & path, Sink & sink, bool memory_map = true);
|
||||
void readFile(const std::filesystem::path & path, Sink & sink, bool memory_map = true);
|
||||
|
||||
enum struct FsSync { Yes, No };
|
||||
|
||||
@@ -266,8 +267,7 @@ writeFile(const std::filesystem::path & path, Source & source, mode_t mode = 066
|
||||
return writeFile(path.string(), source, mode, sync);
|
||||
}
|
||||
|
||||
void writeFile(
|
||||
AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode = 0666, FsSync sync = FsSync::No);
|
||||
void writeFile(Descriptor fd, std::string_view s, FsSync sync = FsSync::No, const Path * origPath = nullptr);
|
||||
|
||||
/**
|
||||
* Flush a path's parent directory to disk.
|
||||
|
||||
@@ -133,14 +133,14 @@ std::pair<int, std::string> runProgram(RunOptions && options);
|
||||
|
||||
void runProgram2(const RunOptions & options);
|
||||
|
||||
class ExecError : public Error
|
||||
class ExecError final : public CloneableError<ExecError, Error>
|
||||
{
|
||||
public:
|
||||
int status;
|
||||
|
||||
template<typename... Args>
|
||||
ExecError(int status, const Args &... args)
|
||||
: Error(args...)
|
||||
: CloneableError(args...)
|
||||
, status(status)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -231,19 +231,19 @@ ref<SourceAccessor> makeEmptySourceAccessor();
|
||||
*/
|
||||
MakeError(RestrictedPathError, Error);
|
||||
|
||||
struct SymlinkNotAllowed : public Error
|
||||
struct SymlinkNotAllowed final : public CloneableError<SymlinkNotAllowed, Error>
|
||||
{
|
||||
CanonPath path;
|
||||
|
||||
SymlinkNotAllowed(CanonPath path)
|
||||
: Error("relative path '%s' points to a symlink, which is not allowed", path.rel())
|
||||
: CloneableError("relative path '%s' points to a symlink, which is not allowed", path.rel())
|
||||
, path(std::move(path))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
SymlinkNotAllowed(CanonPath path, const std::string & fs, Args &&... args)
|
||||
: Error(fs, std::forward<Args>(args)...)
|
||||
: CloneableError(fs, std::forward<Args>(args)...)
|
||||
, path(std::move(path))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include <filesystem>
|
||||
#include <ranges>
|
||||
#include <span>
|
||||
|
||||
@@ -265,7 +266,13 @@ std::string percentEncode(std::string_view s, std::string_view keep = "");
|
||||
* paths have no escape sequences --- file names cannot contain a
|
||||
* `/`.
|
||||
*/
|
||||
Path renderUrlPathEnsureLegal(const std::vector<std::string> & urlPath);
|
||||
Path renderUrlPathEnsureLegal(std::span<const std::string> urlPath);
|
||||
|
||||
/**
|
||||
* Render URL path segments to a string by joining with `/`.
|
||||
* Does not percent-encode the segments.
|
||||
*/
|
||||
std::string renderUrlPathNoPctEncoding(std::span<const std::string> urlPath);
|
||||
|
||||
/**
|
||||
* Percent encode path. `%2F` for "interior slashes" is the most
|
||||
@@ -347,6 +354,22 @@ ParsedURL fixGitURL(std::string url);
|
||||
*/
|
||||
bool isValidSchemeName(std::string_view scheme);
|
||||
|
||||
/**
|
||||
* Convert a filesystem path to a URL path vector.
|
||||
*
|
||||
* On Windows, converts backslashes to forward slashes and prepends a `/`
|
||||
* before the drive letter (e.g., `C:\foo\bar` becomes `/C:/foo/bar`).
|
||||
*/
|
||||
std::vector<std::string> pathToUrlPath(const std::filesystem::path & path);
|
||||
|
||||
/**
|
||||
* Convert a URL path vector to a native filesystem path.
|
||||
*
|
||||
* On Windows, strips the leading `/` before the drive letter and converts
|
||||
* to native format (e.g., `/C:/foo/bar` becomes `C:\foo\bar`).
|
||||
*/
|
||||
std::filesystem::path urlPathToPath(std::span<const std::string> urlPath);
|
||||
|
||||
/**
|
||||
* Either a ParsedURL or a verbatim string. This is necessary because in certain cases URI must be passed
|
||||
* verbatim (e.g. in builtin fetchers), since those are specified by the user.
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
///@file
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "nix/util/types.hh"
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#include "nix/util/types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::string getUserName();
|
||||
|
||||
@@ -190,24 +190,7 @@ bool BufferedSource::hasData()
|
||||
|
||||
size_t FdSource::readUnbuffered(char * data, size_t len)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD n;
|
||||
checkInterrupt();
|
||||
if (!::ReadFile(fd, data, len, &n, NULL)) {
|
||||
_good = false;
|
||||
throw windows::WinError("ReadFile when FdSource::readUnbuffered");
|
||||
}
|
||||
#else
|
||||
ssize_t n;
|
||||
do {
|
||||
checkInterrupt();
|
||||
n = ::read(fd, data, len);
|
||||
} while (n == -1 && errno == EINTR);
|
||||
if (n == -1) {
|
||||
_good = false;
|
||||
throw SysError("reading from file");
|
||||
}
|
||||
#endif
|
||||
auto n = nix::read(fd, {reinterpret_cast<std::byte *>(data), len});
|
||||
if (n == 0) {
|
||||
_good = false;
|
||||
throw EndOfFile(std::string(*endOfFileError));
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <span>
|
||||
|
||||
#include "util-config-private.hh"
|
||||
@@ -13,111 +12,19 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
namespace {
|
||||
|
||||
// This function is needed to handle non-blocking reads/writes. This is needed in the buildhook, because
|
||||
// somehow the json logger file descriptor ends up being non-blocking and breaks remote-building.
|
||||
// TODO: get rid of buildhook and remove this function again (https://github.com/NixOS/nix/issues/12688)
|
||||
void pollFD(int fd, int events)
|
||||
{
|
||||
struct pollfd pfd;
|
||||
pfd.fd = fd;
|
||||
pfd.events = events;
|
||||
int ret = poll(&pfd, 1, -1);
|
||||
if (ret == -1) {
|
||||
throw SysError("poll on file descriptor failed");
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::make_unsigned_t<off_t> getFileSize(Descriptor fd)
|
||||
{
|
||||
auto st = nix::fstat(fd);
|
||||
return st.st_size;
|
||||
}
|
||||
|
||||
void readFull(int fd, char * buf, size_t count)
|
||||
{
|
||||
while (count) {
|
||||
checkInterrupt();
|
||||
ssize_t res = ::read(fd, buf, count);
|
||||
if (res == -1) {
|
||||
switch (errno) {
|
||||
case EINTR:
|
||||
continue;
|
||||
case EAGAIN:
|
||||
pollFD(fd, POLLIN);
|
||||
continue;
|
||||
}
|
||||
auto savedErrno = errno;
|
||||
throw SysError(savedErrno, "reading from file %s", PathFmt(descriptorToPath(fd)));
|
||||
}
|
||||
if (res == 0)
|
||||
throw EndOfFile("unexpected end-of-file");
|
||||
count -= res;
|
||||
buf += res;
|
||||
}
|
||||
}
|
||||
|
||||
void writeFull(int fd, std::string_view s, bool allowInterrupts)
|
||||
{
|
||||
while (!s.empty()) {
|
||||
if (allowInterrupts)
|
||||
checkInterrupt();
|
||||
ssize_t res = write(fd, s.data(), s.size());
|
||||
if (res == -1) {
|
||||
switch (errno) {
|
||||
case EINTR:
|
||||
continue;
|
||||
case EAGAIN:
|
||||
pollFD(fd, POLLOUT);
|
||||
continue;
|
||||
}
|
||||
auto savedErrno = errno;
|
||||
throw SysError(savedErrno, "writing to file %s", PathFmt(descriptorToPath(fd)));
|
||||
}
|
||||
if (res > 0)
|
||||
s.remove_prefix(res);
|
||||
}
|
||||
}
|
||||
|
||||
std::string readLine(int fd, bool eofOk, char terminator)
|
||||
{
|
||||
std::string s;
|
||||
while (1) {
|
||||
checkInterrupt();
|
||||
char ch;
|
||||
// FIXME: inefficient
|
||||
ssize_t rd = ::read(fd, &ch, 1);
|
||||
if (rd == -1) {
|
||||
switch (errno) {
|
||||
case EINTR:
|
||||
continue;
|
||||
case EAGAIN: {
|
||||
pollFD(fd, POLLIN);
|
||||
continue;
|
||||
}
|
||||
default: {
|
||||
auto savedErrno = errno;
|
||||
throw SysError(savedErrno, "reading a line from %s", PathFmt(descriptorToPath(fd)));
|
||||
}
|
||||
}
|
||||
} else if (rd == 0) {
|
||||
if (eofOk)
|
||||
return s;
|
||||
else
|
||||
throw EndOfFile("unexpected EOF reading a line");
|
||||
} else {
|
||||
if (ch == terminator)
|
||||
return s;
|
||||
s += ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t read(Descriptor fd, std::span<std::byte> buffer)
|
||||
{
|
||||
ssize_t n = ::read(fd, buffer.data(), buffer.size());
|
||||
ssize_t n;
|
||||
do {
|
||||
checkInterrupt();
|
||||
n = ::read(fd, buffer.data(), buffer.size());
|
||||
} while (n == -1 && errno == EINTR);
|
||||
if (n == -1)
|
||||
throw SysError("read of %1% bytes", buffer.size());
|
||||
return static_cast<size_t>(n);
|
||||
@@ -125,12 +32,29 @@ size_t read(Descriptor fd, std::span<std::byte> buffer)
|
||||
|
||||
size_t readOffset(Descriptor fd, off_t offset, std::span<std::byte> buffer)
|
||||
{
|
||||
ssize_t n = pread(fd, buffer.data(), buffer.size(), offset);
|
||||
ssize_t n;
|
||||
do {
|
||||
checkInterrupt();
|
||||
n = pread(fd, buffer.data(), buffer.size(), offset);
|
||||
} while (n == -1 && errno == EINTR);
|
||||
if (n == -1)
|
||||
throw SysError("pread of %1% bytes at offset %2%", buffer.size(), offset);
|
||||
return static_cast<size_t>(n);
|
||||
}
|
||||
|
||||
size_t write(Descriptor fd, std::span<const std::byte> buffer, bool allowInterrupts)
|
||||
{
|
||||
ssize_t n;
|
||||
do {
|
||||
if (allowInterrupts)
|
||||
checkInterrupt();
|
||||
n = ::write(fd, buffer.data(), buffer.size());
|
||||
} while (n == -1 && errno == EINTR);
|
||||
if (n == -1)
|
||||
throw SysError("write of %1% bytes", buffer.size());
|
||||
return static_cast<size_t>(n);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Pipe::create()
|
||||
@@ -207,4 +131,17 @@ void unix::closeOnExec(int fd)
|
||||
throw SysError("setting close-on-exec flag");
|
||||
}
|
||||
|
||||
void syncDescriptor(Descriptor fd)
|
||||
{
|
||||
int result =
|
||||
#if defined(__APPLE__)
|
||||
::fcntl(fd, F_FULLFSYNC)
|
||||
#else
|
||||
::fsync(fd)
|
||||
#endif
|
||||
;
|
||||
if (result == -1)
|
||||
throw NativeSysError("fsync file descriptor %1%", fd);
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -136,7 +136,7 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
|
||||
}
|
||||
}
|
||||
|
||||
static Descriptor
|
||||
static AutoCloseFD
|
||||
openFileEnsureBeneathNoSymlinksIterative(Descriptor dirFd, const CanonPath & path, int flags, mode_t mode)
|
||||
{
|
||||
AutoCloseFD parentFd;
|
||||
@@ -179,19 +179,19 @@ openFileEnsureBeneathNoSymlinksIterative(Descriptor dirFd, const CanonPath & pat
|
||||
throw SymlinkNotAllowed(path2);
|
||||
}
|
||||
|
||||
return INVALID_DESCRIPTOR;
|
||||
return AutoCloseFD{};
|
||||
}
|
||||
|
||||
parentFd = std::move(parentFd2);
|
||||
}
|
||||
|
||||
auto res = ::openat(getParentFd(), std::string(path.baseName().value()).c_str(), flags | O_NOFOLLOW, mode);
|
||||
if (res < 0 && errno == ELOOP)
|
||||
AutoCloseFD res = ::openat(getParentFd(), std::string(path.baseName().value()).c_str(), flags | O_NOFOLLOW, mode);
|
||||
if (!res && errno == ELOOP)
|
||||
throw SymlinkNotAllowed(path);
|
||||
return res;
|
||||
}
|
||||
|
||||
Descriptor openFileEnsureBeneathNoSymlinks(Descriptor dirFd, const CanonPath & path, int flags, mode_t mode)
|
||||
AutoCloseFD openFileEnsureBeneathNoSymlinks(Descriptor dirFd, const CanonPath & path, int flags, mode_t mode)
|
||||
{
|
||||
assert(!path.rel().starts_with('/')); /* Just in case the invariant is somehow broken. */
|
||||
assert(!path.isRoot());
|
||||
@@ -201,7 +201,7 @@ Descriptor openFileEnsureBeneathNoSymlinks(Descriptor dirFd, const CanonPath & p
|
||||
if (maybeFd) {
|
||||
if (*maybeFd < 0 && errno == ELOOP)
|
||||
throw SymlinkNotAllowed(path);
|
||||
return *maybeFd;
|
||||
return AutoCloseFD{*maybeFd};
|
||||
}
|
||||
#endif
|
||||
return openFileEnsureBeneathNoSymlinksIterative(dirFd, path, flags, mode);
|
||||
|
||||
@@ -321,7 +321,7 @@ std::string encodeQuery(const StringMap & ss)
|
||||
return res;
|
||||
}
|
||||
|
||||
Path renderUrlPathEnsureLegal(const std::vector<std::string> & urlPath)
|
||||
Path renderUrlPathEnsureLegal(std::span<const std::string> urlPath)
|
||||
{
|
||||
for (const auto & comp : urlPath) {
|
||||
/* This is only really valid for UNIX. Windows has more restrictions. */
|
||||
@@ -334,6 +334,11 @@ Path renderUrlPathEnsureLegal(const std::vector<std::string> & urlPath)
|
||||
}
|
||||
}
|
||||
|
||||
return renderUrlPathNoPctEncoding(urlPath);
|
||||
}
|
||||
|
||||
std::string renderUrlPathNoPctEncoding(std::span<const std::string> urlPath)
|
||||
{
|
||||
return concatStringsSep("/", urlPath);
|
||||
}
|
||||
|
||||
@@ -341,7 +346,7 @@ std::string ParsedURL::renderPath(bool encode) const
|
||||
{
|
||||
if (encode)
|
||||
return encodeUrlPath(path);
|
||||
return concatStringsSep("/", path);
|
||||
return renderUrlPathNoPctEncoding(path);
|
||||
}
|
||||
|
||||
std::string ParsedURL::renderAuthorityAndPath() const
|
||||
@@ -452,6 +457,61 @@ bool isValidSchemeName(std::string_view s)
|
||||
return std::regex_match(s.begin(), s.end(), regex, std::regex_constants::match_default);
|
||||
}
|
||||
|
||||
std::vector<std::string> pathToUrlPath(const std::filesystem::path & path)
|
||||
{
|
||||
std::vector<std::string> urlPath;
|
||||
|
||||
// Prepend empty segment for absolute paths (those with a root directory)
|
||||
if (path.has_root_directory())
|
||||
urlPath.push_back("");
|
||||
|
||||
// Handle Windows drive letter (root_name like "C:")
|
||||
if (path.has_root_name())
|
||||
urlPath.push_back(path.root_name().generic_string());
|
||||
|
||||
// Iterate only over the relative path portion
|
||||
for (const auto & component : path.relative_path())
|
||||
urlPath.push_back(component.generic_string());
|
||||
|
||||
// Add trailing empty segment for paths ending with separator (including root-only paths)
|
||||
if (path.filename().empty())
|
||||
urlPath.push_back("");
|
||||
|
||||
return urlPath;
|
||||
}
|
||||
|
||||
std::filesystem::path urlPathToPath(std::span<const std::string> urlPath)
|
||||
{
|
||||
std::filesystem::path result;
|
||||
auto it = urlPath.begin();
|
||||
|
||||
// URL path must start with empty segment (representing absolute path "/")
|
||||
if (it == urlPath.end() || !it->empty())
|
||||
throw Error("only absolute URL paths can be converted to filesystem paths");
|
||||
|
||||
++it;
|
||||
result = "/";
|
||||
#ifdef _WIN32
|
||||
// On Windows, check if next segment is a drive letter (e.g., "C:").
|
||||
// If it isn't then this is something like a UNC path rather than a
|
||||
// DOS path.
|
||||
if (it != urlPath.end()) {
|
||||
std::filesystem::path segment{*it};
|
||||
if (segment.has_root_name()) {
|
||||
segment /= "/";
|
||||
result = std::move(segment);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Append remaining segments
|
||||
for (; it != urlPath.end(); ++it)
|
||||
result /= *it;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::ostream & operator<<(std::ostream & os, const VerbatimURL & url)
|
||||
{
|
||||
os << url.to_string();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "nix/util/util.hh"
|
||||
#include "nix/util/users.hh"
|
||||
#include "nix/util/environment-variables.hh"
|
||||
#include "nix/util/executable-path.hh"
|
||||
#include "nix/util/file-system.hh"
|
||||
|
||||
#ifndef _WIN32
|
||||
@@ -13,7 +14,7 @@ namespace nix {
|
||||
|
||||
std::filesystem::path getCacheDir()
|
||||
{
|
||||
auto dir = getEnv("NIX_CACHE_HOME");
|
||||
auto dir = getEnvOs(OS_STR("NIX_CACHE_HOME"));
|
||||
if (dir)
|
||||
return *dir;
|
||||
#ifndef _WIN32
|
||||
@@ -25,7 +26,7 @@ std::filesystem::path getCacheDir()
|
||||
|
||||
std::filesystem::path getConfigDir()
|
||||
{
|
||||
auto dir = getEnv("NIX_CONFIG_HOME");
|
||||
auto dir = getEnvOs(OS_STR("NIX_CONFIG_HOME"));
|
||||
if (dir)
|
||||
return *dir;
|
||||
#ifndef _WIN32
|
||||
@@ -51,7 +52,7 @@ std::vector<std::filesystem::path> getConfigDirs()
|
||||
|
||||
std::filesystem::path getDataDir()
|
||||
{
|
||||
auto dir = getEnv("NIX_DATA_HOME");
|
||||
auto dir = getEnvOs(OS_STR("NIX_DATA_HOME"));
|
||||
if (dir)
|
||||
return *dir;
|
||||
#ifndef _WIN32
|
||||
@@ -63,7 +64,7 @@ std::filesystem::path getDataDir()
|
||||
|
||||
std::filesystem::path getStateDir()
|
||||
{
|
||||
auto dir = getEnv("NIX_STATE_HOME");
|
||||
auto dir = getEnvOs(OS_STR("NIX_STATE_HOME"));
|
||||
if (dir)
|
||||
return *dir;
|
||||
#ifndef _WIN32
|
||||
@@ -84,9 +85,10 @@ std::string expandTilde(std::string_view path)
|
||||
{
|
||||
// TODO: expand ~user ?
|
||||
auto tilde = path.substr(0, 2);
|
||||
if (tilde == "~/" || tilde == "~")
|
||||
return getHome().string() + std::string(path.substr(1));
|
||||
else
|
||||
if (tilde == "~/" || tilde == "~") {
|
||||
auto suffix = path.size() >= 2 ? std::string(path.substr(2)) : std::string{};
|
||||
return (getHome() / suffix).string();
|
||||
} else
|
||||
return std::string(path);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,82 +19,56 @@ using namespace nix::windows;
|
||||
std::make_unsigned_t<off_t> getFileSize(Descriptor fd)
|
||||
{
|
||||
LARGE_INTEGER li;
|
||||
if (!GetFileSizeEx(fd, &li))
|
||||
throw WinError("GetFileSizeEx");
|
||||
if (!GetFileSizeEx(fd, &li)) {
|
||||
auto lastError = GetLastError();
|
||||
throw WinError(lastError, "getting size of file %s", PathFmt(descriptorToPath(fd)));
|
||||
}
|
||||
return li.QuadPart;
|
||||
}
|
||||
|
||||
void readFull(HANDLE handle, char * buf, size_t count)
|
||||
{
|
||||
while (count) {
|
||||
checkInterrupt();
|
||||
DWORD res;
|
||||
if (!ReadFile(handle, (char *) buf, count, &res, NULL))
|
||||
throw WinError("%s:%d reading from file", __FILE__, __LINE__);
|
||||
if (res == 0)
|
||||
throw EndOfFile("unexpected end-of-file");
|
||||
count -= res;
|
||||
buf += res;
|
||||
}
|
||||
}
|
||||
|
||||
void writeFull(HANDLE handle, std::string_view s, bool allowInterrupts)
|
||||
{
|
||||
while (!s.empty()) {
|
||||
if (allowInterrupts)
|
||||
checkInterrupt();
|
||||
DWORD res;
|
||||
if (!WriteFile(handle, s.data(), s.size(), &res, NULL)) {
|
||||
// Do this because `descriptorToPath` will overwrite the last error.
|
||||
auto lastError = GetLastError();
|
||||
auto path = descriptorToPath(handle);
|
||||
throw WinError(lastError, "writing to file %d:%s", handle, PathFmt(path));
|
||||
}
|
||||
if (res > 0)
|
||||
s.remove_prefix(res);
|
||||
}
|
||||
}
|
||||
|
||||
std::string readLine(HANDLE handle, bool eofOk, char terminator)
|
||||
{
|
||||
std::string s;
|
||||
while (1) {
|
||||
checkInterrupt();
|
||||
char ch;
|
||||
// FIXME: inefficient
|
||||
DWORD rd;
|
||||
if (!ReadFile(handle, &ch, 1, &rd, NULL)) {
|
||||
throw WinError("reading a line");
|
||||
} else if (rd == 0) {
|
||||
if (eofOk)
|
||||
return s;
|
||||
else
|
||||
throw EndOfFile("unexpected EOF reading a line");
|
||||
} else {
|
||||
if (ch == terminator)
|
||||
return s;
|
||||
s += ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t read(Descriptor fd, std::span<std::byte> buffer)
|
||||
{
|
||||
checkInterrupt(); // For consistency with unix, and its EINTR loop
|
||||
DWORD n;
|
||||
if (!ReadFile(fd, buffer.data(), static_cast<DWORD>(buffer.size()), &n, NULL))
|
||||
throw WinError("ReadFile of %1% bytes", buffer.size());
|
||||
if (!ReadFile(fd, buffer.data(), static_cast<DWORD>(buffer.size()), &n, NULL)) {
|
||||
auto lastError = GetLastError();
|
||||
if (lastError == ERROR_BROKEN_PIPE)
|
||||
n = 0; // Treat as EOF
|
||||
else
|
||||
throw WinError(lastError, "reading %1% bytes from %2%", buffer.size(), PathFmt(descriptorToPath(fd)));
|
||||
}
|
||||
return static_cast<size_t>(n);
|
||||
}
|
||||
|
||||
size_t readOffset(Descriptor fd, off_t offset, std::span<std::byte> buffer)
|
||||
{
|
||||
checkInterrupt(); // For consistency with unix, and its EINTR loop
|
||||
OVERLAPPED ov = {};
|
||||
ov.Offset = static_cast<DWORD>(offset);
|
||||
if constexpr (sizeof(offset) > 4) /* We don't build with 32 bit off_t, but let's be safe. */
|
||||
ov.OffsetHigh = static_cast<DWORD>(offset >> 32);
|
||||
DWORD n;
|
||||
if (!ReadFile(fd, buffer.data(), static_cast<DWORD>(buffer.size()), &n, &ov))
|
||||
throw WinError("ReadFile of %1% bytes at offset %2%", buffer.size(), offset);
|
||||
if (!ReadFile(fd, buffer.data(), static_cast<DWORD>(buffer.size()), &n, &ov)) {
|
||||
auto lastError = GetLastError();
|
||||
throw WinError(
|
||||
lastError,
|
||||
"reading %1% bytes at offset %2% from %3%",
|
||||
buffer.size(),
|
||||
offset,
|
||||
PathFmt(descriptorToPath(fd)));
|
||||
}
|
||||
return static_cast<size_t>(n);
|
||||
}
|
||||
|
||||
size_t write(Descriptor fd, std::span<const std::byte> buffer, bool allowInterrupts)
|
||||
{
|
||||
if (allowInterrupts)
|
||||
checkInterrupt(); // For consistency with unix
|
||||
DWORD n;
|
||||
if (!WriteFile(fd, buffer.data(), static_cast<DWORD>(buffer.size()), &n, NULL)) {
|
||||
auto lastError = GetLastError();
|
||||
throw WinError(lastError, "writing %1% bytes to %2%", buffer.size(), PathFmt(descriptorToPath(fd)));
|
||||
}
|
||||
return static_cast<size_t>(n);
|
||||
}
|
||||
|
||||
@@ -148,4 +122,12 @@ off_t lseek(HANDLE h, off_t offset, int whence)
|
||||
return newPos.QuadPart;
|
||||
}
|
||||
|
||||
void syncDescriptor(Descriptor fd)
|
||||
{
|
||||
if (!::FlushFileBuffers(fd)) {
|
||||
auto lastError = GetLastError();
|
||||
throw WinError(lastError, "flushing file %s", PathFmt(descriptorToPath(fd)));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace {
|
||||
* @param createDisposition FILE_OPEN, FILE_CREATE, etc.
|
||||
* @return Handle to the opened file/directory (caller must close)
|
||||
*/
|
||||
HANDLE ntOpenAt(
|
||||
AutoCloseFD ntOpenAt(
|
||||
Descriptor dirFd,
|
||||
std::wstring_view pathComponent,
|
||||
ACCESS_MASK desiredAccess,
|
||||
@@ -73,7 +73,7 @@ HANDLE ntOpenAt(
|
||||
throw WinError(
|
||||
RtlNtStatusToDosError(status), "opening %s relative to directory handle", PathFmt(pathComponent));
|
||||
|
||||
return h;
|
||||
return AutoCloseFD{h};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,9 +81,9 @@ HANDLE ntOpenAt(
|
||||
*
|
||||
* @param dirFd Directory handle to open relative to
|
||||
* @param path Relative path to the symlink
|
||||
* @return Handle to the symlink (caller must close)
|
||||
* @return Handle to the symlink
|
||||
*/
|
||||
HANDLE openSymlinkAt(Descriptor dirFd, const CanonPath & path)
|
||||
AutoCloseFD openSymlinkAt(Descriptor dirFd, const CanonPath & path)
|
||||
{
|
||||
assert(!path.isRoot());
|
||||
assert(!path.rel().starts_with('/')); /* Just in case the invariant is somehow broken. */
|
||||
@@ -210,7 +210,7 @@ bool isReparsePoint(HANDLE handle)
|
||||
|
||||
} // namespace windows
|
||||
|
||||
Descriptor openFileEnsureBeneathNoSymlinks(
|
||||
AutoCloseFD openFileEnsureBeneathNoSymlinks(
|
||||
Descriptor dirFd, const CanonPath & path, ACCESS_MASK desiredAccess, ULONG createOptions, ULONG createDisposition)
|
||||
{
|
||||
assert(!path.isRoot());
|
||||
@@ -233,9 +233,8 @@ Descriptor openFileEnsureBeneathNoSymlinks(
|
||||
/* Helper to check if a component is a symlink and throw SymlinkNotAllowed if so */
|
||||
auto throwIfSymlink = [&](std::wstring_view component, const CanonPath & pathForError) {
|
||||
try {
|
||||
auto testFd =
|
||||
auto testHandle =
|
||||
ntOpenAt(getParentFd(), component, FILE_READ_ATTRIBUTES | SYNCHRONIZE, FILE_OPEN_REPARSE_POINT);
|
||||
AutoCloseFD testHandle(testFd);
|
||||
if (isReparsePoint(testHandle.get()))
|
||||
throw SymlinkNotAllowed(pathForError);
|
||||
} catch (SymlinkNotAllowed &) {
|
||||
@@ -278,7 +277,7 @@ Descriptor openFileEnsureBeneathNoSymlinks(
|
||||
/* Now open the final component with requested flags */
|
||||
std::wstring finalComponent = string_to_os_string(std::string(path.baseName().value()));
|
||||
|
||||
HANDLE finalHandle;
|
||||
AutoCloseFD finalHandle;
|
||||
try {
|
||||
finalHandle = ntOpenAt(
|
||||
getParentFd(),
|
||||
@@ -295,7 +294,7 @@ Descriptor openFileEnsureBeneathNoSymlinks(
|
||||
}
|
||||
|
||||
/* Final check: did we accidentally open a symlink? */
|
||||
if (isReparsePoint(finalHandle))
|
||||
if (isReparsePoint(finalHandle.get()))
|
||||
throw SymlinkNotAllowed(path);
|
||||
|
||||
return finalHandle;
|
||||
|
||||
@@ -5,5 +5,6 @@ include_dirs += include_directories('../..')
|
||||
headers += files(
|
||||
'signals-impl.hh',
|
||||
'windows-async-pipe.hh',
|
||||
'windows-environment.hh',
|
||||
'windows-known-folders.hh',
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user