Compare commits
1 Commits
local-bina
...
cloneable-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ded675e56 |
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@01619ebc9a6e3f6820274221b9956b3e7365000a # v4.1.0
|
||||
uses: korthout/backport-action@c656f5d5851037b2b38fb5db2691a03fa229e3b2 # v4.0.1
|
||||
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@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@4e002c8ec80594ecd40e759629461e26c8abed15 # v31.9.0
|
||||
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@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@4e002c8ec80594ecd40e759629461e26c8abed15 # v31.9.0
|
||||
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) }}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
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: 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,6 +358,7 @@ dockerTools.buildLayeredImageWithNixDb {
|
||||
|
||||
extraCommands = ''
|
||||
rm -rf nix-support
|
||||
ln -s /nix/var/nix/profiles nix/var/nix/gcroots/profiles
|
||||
'';
|
||||
fakeRootCommands = ''
|
||||
chmod 1777 tmp
|
||||
|
||||
@@ -9,3 +9,9 @@ endif
|
||||
if 'address' in get_option('b_sanitize')
|
||||
deps_other += declare_dependency(sources : 'asan-options.cc')
|
||||
endif
|
||||
|
||||
if 'undefined' in get_option('b_sanitize')
|
||||
add_project_arguments('-DNIX_UBSAN_ENABLED=1', language : 'cpp')
|
||||
else
|
||||
add_project_arguments('-DNIX_UBSAN_ENABLED=0', language : 'cpp')
|
||||
endif
|
||||
|
||||
@@ -710,7 +710,7 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
|
||||
try {
|
||||
cwd = std::filesystem::current_path();
|
||||
} catch (std::filesystem::filesystem_error & e) {
|
||||
throw SystemError(e.code(), "cannot determine current working directory");
|
||||
throw SysError("cannot determine current working directory");
|
||||
}
|
||||
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, flakeRefS, cwd.string(), true);
|
||||
|
||||
@@ -185,9 +185,7 @@ 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),
|
||||
.statePtr = std::make_shared<nix::EvalState>(
|
||||
builder->lookupPath, builder->store, self->fetchSettings, self->settings),
|
||||
.state = *self->statePtr,
|
||||
.state = nix::EvalState(builder->lookupPath, builder->store, self->fetchSettings, self->settings),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@ struct EvalState
|
||||
{
|
||||
nix::fetchers::Settings fetchSettings;
|
||||
nix::EvalSettings settings;
|
||||
std::shared_ptr<nix::EvalState> statePtr;
|
||||
nix::EvalState & state;
|
||||
nix::EvalState state;
|
||||
};
|
||||
|
||||
struct BindingsBuilder
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#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"
|
||||
@@ -108,13 +107,8 @@ static void nix_c_primop_wrapper(
|
||||
f(userdata, &ctx, (EvalState *) &state, external_args.data(), vTmpPtr);
|
||||
|
||||
if (ctx.last_err_code != NIX_OK) {
|
||||
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();
|
||||
}
|
||||
/* 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 (!vTmp.isValid()) {
|
||||
@@ -200,8 +194,6 @@ 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,8 +100,7 @@ typedef enum {
|
||||
/** @brief External value from C++ plugins or C API
|
||||
* @see Externals
|
||||
*/
|
||||
NIX_TYPE_EXTERNAL,
|
||||
NIX_TYPE_FAILED,
|
||||
NIX_TYPE_EXTERNAL
|
||||
} ValueType;
|
||||
|
||||
// forward declarations
|
||||
|
||||
@@ -37,8 +37,7 @@ static void BM_EvalDynamicAttrs(benchmark::State & state)
|
||||
EvalSettings evalSettings{readOnlyMode};
|
||||
evalSettings.nixPath = {};
|
||||
|
||||
auto stPtr = std::make_shared<EvalState>(LookupPath{}, store, fetchSettings, evalSettings, nullptr);
|
||||
auto & st = *stPtr;
|
||||
EvalState st({}, store, fetchSettings, evalSettings, nullptr);
|
||||
Expr * expr = st.parseExprFromString(exprStr, st.rootPath(CanonPath::root));
|
||||
|
||||
Value v;
|
||||
|
||||
@@ -16,8 +16,7 @@ struct GetDerivationsEnv
|
||||
fetchers::Settings fetchSettings{};
|
||||
bool readOnlyMode = true;
|
||||
EvalSettings evalSettings{readOnlyMode};
|
||||
std::shared_ptr<EvalState> statePtr;
|
||||
EvalState & state;
|
||||
EvalState state;
|
||||
|
||||
Bindings * autoArgs = nullptr;
|
||||
Value attrsValue;
|
||||
@@ -28,8 +27,7 @@ struct GetDerivationsEnv
|
||||
settings.nixPath = {};
|
||||
return settings;
|
||||
}())
|
||||
, statePtr(std::make_shared<EvalState>(LookupPath{}, store, fetchSettings, evalSettings, nullptr))
|
||||
, state(*statePtr)
|
||||
, state({}, store, fetchSettings, evalSettings, nullptr)
|
||||
{
|
||||
autoArgs = state.buildBindings(0).finish();
|
||||
|
||||
|
||||
@@ -517,106 +517,4 @@ 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,8 +27,7 @@ static void BM_EvalManyBuiltinsMatchSameRegex(benchmark::State & state)
|
||||
EvalSettings evalSettings{readOnlyMode};
|
||||
evalSettings.nixPath = {};
|
||||
|
||||
auto stPtr = std::make_shared<EvalState>(LookupPath{}, store, fetchSettings, evalSettings, nullptr);
|
||||
auto & st = *stPtr;
|
||||
EvalState st({}, store, fetchSettings, evalSettings, nullptr);
|
||||
Expr * expr = st.parseExprFromString(std::string(exprStr), st.rootPath(CanonPath::root));
|
||||
|
||||
Value v;
|
||||
|
||||
@@ -188,22 +188,6 @@ 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;
|
||||
|
||||
@@ -70,7 +70,7 @@ struct AttrDb
|
||||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
auto cacheDir = getCacheDir() / "eval-cache-v6";
|
||||
auto cacheDir = std::filesystem::path(getCacheDir()) / "eval-cache-v6";
|
||||
createDirs(cacheDir);
|
||||
|
||||
auto dbPath = cacheDir / (fingerprint.to_string(HashFormat::Base16, false) + ".sqlite");
|
||||
|
||||
@@ -114,6 +114,5 @@ 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(getNixDefExpr() / "channels");
|
||||
add(std::filesystem::path{getNixDefExpr()} / "channels");
|
||||
auto profilesDirOpts = settings.getProfileDirsOptions();
|
||||
add(rootChannelsDir(profilesDirOpts) / "nixpkgs", "nixpkgs");
|
||||
add(rootChannelsDir(profilesDirOpts));
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#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"
|
||||
@@ -32,7 +31,6 @@
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
@@ -157,8 +155,6 @@ 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();
|
||||
}
|
||||
@@ -1991,15 +1987,11 @@ void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
|
||||
UpdateQueue q;
|
||||
evalForUpdate(state, env, q);
|
||||
|
||||
Value vTmp;
|
||||
vTmp.mkAttrs(&Bindings::emptyBindings);
|
||||
|
||||
v.mkAttrs(&Bindings::emptyBindings);
|
||||
for (auto & rhs : std::views::reverse(q)) {
|
||||
/* Remember that queue is sorted rightmost attrset first. */
|
||||
eval(state, /*v=*/vTmp, /*v1=*/vTmp, /*v2=*/rhs);
|
||||
eval(state, /*v=*/v, /*v1=*/v, /*v2=*/rhs);
|
||||
}
|
||||
|
||||
v = vTmp;
|
||||
}
|
||||
|
||||
void Expr::evalForUpdate(EvalState & state, Env & env, UpdateQueue & q, std::string_view errorCtx)
|
||||
@@ -2182,54 +2174,6 @@ 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())
|
||||
@@ -2238,8 +2182,7 @@ void EvalState::tryFixupBlackHolePos(Value & v, PosIdx pos)
|
||||
try {
|
||||
std::rethrow_exception(e);
|
||||
} catch (InfiniteRecursionError & e) {
|
||||
if (!e.hasPos())
|
||||
e.atPos(positions[pos]);
|
||||
e.atPos(positions[pos]);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
@@ -2878,11 +2821,8 @@ void EvalState::assertEqValues(Value & v1, Value & v2, const PosIdx pos, std::st
|
||||
}
|
||||
return;
|
||||
|
||||
// Cannot be returned by forceValue().
|
||||
case nThunk:
|
||||
case nFailed:
|
||||
unreachable();
|
||||
|
||||
case nThunk: // Must not be left by forceValue
|
||||
assert(false);
|
||||
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),
|
||||
@@ -2976,11 +2916,8 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
|
||||
// !!!
|
||||
return v1.fpoint() == v2.fpoint();
|
||||
|
||||
// Cannot be returned by forceValue().
|
||||
case nThunk:
|
||||
case nFailed:
|
||||
unreachable();
|
||||
|
||||
case nThunk: // Must not be left by forceValue
|
||||
assert(false);
|
||||
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)
|
||||
|
||||
@@ -70,14 +70,6 @@ struct StackOverflowError : public CloneableError<StackOverflowError, EvalBaseEr
|
||||
|
||||
MakeError(IFDError, EvalBaseError);
|
||||
|
||||
/**
|
||||
* 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:
|
||||
|
||||
@@ -33,9 +33,6 @@ using gc_allocator = std::allocator<T>;
|
||||
struct gc
|
||||
{};
|
||||
|
||||
struct gc_cleanup
|
||||
{};
|
||||
|
||||
#endif
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "nix/expr/eval.hh"
|
||||
#include "nix/expr/eval-error.hh"
|
||||
#include "nix/expr/eval-settings.hh"
|
||||
#include <exception>
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -92,25 +91,18 @@ 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 (...) {
|
||||
handleEvalExceptionForThunk(env, expr, v, pos);
|
||||
v.mkThunk(env, expr);
|
||||
tryFixupBlackHolePos(v, pos);
|
||||
throw;
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
} else if (v.isApp())
|
||||
callFunction(*v.app().left, *v.app().right, v, pos);
|
||||
}
|
||||
|
||||
[[gnu::always_inline]]
|
||||
|
||||
@@ -652,28 +652,8 @@ 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,7 +7,6 @@
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <memory_resource>
|
||||
#include <exception>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
@@ -43,7 +42,6 @@ enum InternalType {
|
||||
tBool,
|
||||
tNull,
|
||||
tFloat,
|
||||
tFailed,
|
||||
tExternal,
|
||||
tPrimOp,
|
||||
tAttrs,
|
||||
@@ -70,7 +68,6 @@ enum InternalType {
|
||||
*/
|
||||
typedef enum {
|
||||
nThunk,
|
||||
nFailed,
|
||||
nInt,
|
||||
nFloat,
|
||||
nBool,
|
||||
@@ -427,36 +424,6 @@ 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>
|
||||
@@ -483,7 +450,6 @@ 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) \
|
||||
@@ -788,11 +754,6 @@ 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);
|
||||
@@ -842,11 +803,6 @@ protected:
|
||||
{
|
||||
setUntaggablePayload<pdPath>(path.accessor, path.path);
|
||||
}
|
||||
|
||||
void setStorage(Failed * failed) noexcept
|
||||
{
|
||||
setSingleDWordPayload<tFailed>(std::bit_cast<PackedPointer>(failed));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1098,12 +1054,12 @@ public:
|
||||
inline bool isThunk() const
|
||||
{
|
||||
return isa<tThunk>();
|
||||
}
|
||||
};
|
||||
|
||||
inline bool isApp() const
|
||||
{
|
||||
return isa<tApp>();
|
||||
}
|
||||
};
|
||||
|
||||
inline bool isBlackhole() const;
|
||||
|
||||
@@ -1111,22 +1067,17 @@ 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
|
||||
@@ -1147,7 +1098,6 @@ public:
|
||||
t[tBool] = nBool;
|
||||
t[tNull] = nNull;
|
||||
t[tFloat] = nFloat;
|
||||
t[tFailed] = nFailed;
|
||||
t[tExternal] = nExternal;
|
||||
t[tAttrs] = nAttrs;
|
||||
t[tPrimOp] = nFunction;
|
||||
@@ -1285,11 +1235,6 @@ 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>();
|
||||
@@ -1404,13 +1349,6 @@ public:
|
||||
{
|
||||
return getStorage<Path>().accessor;
|
||||
}
|
||||
|
||||
Failed & failed() const noexcept
|
||||
{
|
||||
auto p = getStorage<Failed *>();
|
||||
assert(p);
|
||||
return *p;
|
||||
}
|
||||
};
|
||||
|
||||
extern ExprBlackHole eBlackHole;
|
||||
|
||||
@@ -562,7 +562,6 @@ static void prim_typeOf(EvalState & state, const PosIdx pos, Value ** args, Valu
|
||||
v.mkStringNoCopy("float"_sds);
|
||||
break;
|
||||
case nThunk:
|
||||
case nFailed:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,11 +73,6 @@ 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,17 +509,6 @@ 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);
|
||||
@@ -601,10 +590,6 @@ private:
|
||||
printFunction(v);
|
||||
break;
|
||||
|
||||
case nFailed:
|
||||
printFailed();
|
||||
break;
|
||||
|
||||
case nThunk:
|
||||
printThunk(v);
|
||||
break;
|
||||
|
||||
@@ -98,7 +98,6 @@ 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,11 +171,7 @@ 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";
|
||||
createDirs(dbPath.parent_path());
|
||||
auto dbPath = (getCacheDir() / "fetcher-cache-v4.sqlite").string();
|
||||
createDirs(dirOf(dbPath));
|
||||
|
||||
state->db = SQLite(dbPath, {.useWAL = nix::settings.useSQLiteWAL});
|
||||
state->db.isCache();
|
||||
|
||||
@@ -268,10 +268,10 @@ void Fetch::fetch(
|
||||
return;
|
||||
}
|
||||
|
||||
auto cacheDir = getCacheDir() / "git-lfs";
|
||||
std::filesystem::path cacheDir = getCacheDir() / "git-lfs";
|
||||
std::string key = hashString(HashAlgorithm::SHA256, pointerFilePath.rel()).to_string(HashFormat::Base16, false)
|
||||
+ "/" + pointer->oid;
|
||||
auto cachePath = cacheDir / key;
|
||||
std::filesystem::path cachePath = cacheDir / key;
|
||||
if (pathExists(cachePath)) {
|
||||
debug("using cache entry %s -> %s", key, PathFmt(cachePath));
|
||||
sink(readFile(cachePath));
|
||||
|
||||
@@ -247,8 +247,7 @@ static void initRepoAtomically(std::filesystem::path & path, GitRepo::Options op
|
||||
|| e.code() == std::errc::directory_not_empty) {
|
||||
return;
|
||||
} else
|
||||
throw SystemError(
|
||||
e.code(), "moving temporary git repository from %s to %s", PathFmt(tmpDir), PathFmt(path));
|
||||
throw SysError("moving temporary git repository from %s to %s", PathFmt(tmpDir), PathFmt(path));
|
||||
}
|
||||
// we successfully moved the repository, so the temporary directory no longer exists.
|
||||
delTmpDir.cancel();
|
||||
|
||||
@@ -44,9 +44,8 @@ static bool isCacheFileWithinTtl(const Settings & settings, time_t now, const Po
|
||||
|
||||
std::filesystem::path getCachePath(std::string_view key, bool shallow)
|
||||
{
|
||||
auto name =
|
||||
hashString(HashAlgorithm::SHA256, key).to_string(HashFormat::Nix32, false) + (shallow ? "-shallow" : "");
|
||||
return getCacheDir() / "gitv3" / std::move(name);
|
||||
return getCacheDir() / "gitv3"
|
||||
/ (hashString(HashAlgorithm::SHA256, key).to_string(HashFormat::Nix32, false) + (shallow ? "-shallow" : ""));
|
||||
}
|
||||
|
||||
// Returns the name of the HEAD branch.
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace nix::flake {
|
||||
// setting name -> setting value -> allow or ignore.
|
||||
typedef std::map<std::string, std::map<std::string, bool>> TrustedList;
|
||||
|
||||
static std::filesystem::path trustedListPath()
|
||||
std::filesystem::path trustedListPath()
|
||||
{
|
||||
return getDataDir() / "trusted-settings.json";
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
C:\foo\bar\baz?trusted=true
|
||||
@@ -13,6 +13,10 @@ using testing::Eq;
|
||||
using testing::Field;
|
||||
using testing::SizeIs;
|
||||
|
||||
namespace nix::fs {
|
||||
using namespace std::filesystem;
|
||||
}
|
||||
|
||||
using namespace nix;
|
||||
|
||||
TEST(machines, getMachinesWithEmptyBuilders)
|
||||
|
||||
@@ -85,20 +85,6 @@ 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{
|
||||
@@ -122,11 +108,7 @@ URI_TEST_READ(local_3_no_percent, localExample_3)
|
||||
|
||||
URI_TEST_READ(local_shorthand_1, localExample_1)
|
||||
|
||||
#ifndef _WIN32
|
||||
URI_TEST_READ(local_shorthand_path_unix, localExample_2)
|
||||
#else
|
||||
URI_TEST_READ(local_shorthand_path_windows, localExample_windows)
|
||||
#endif
|
||||
URI_TEST_READ(local_shorthand_2, localExample_2)
|
||||
|
||||
URI_TEST(
|
||||
local_shorthand_3,
|
||||
|
||||
@@ -20,9 +20,6 @@ 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())
|
||||
@@ -30,10 +27,6 @@ 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,8 +27,6 @@ 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());
|
||||
|
||||
@@ -38,8 +36,8 @@ static void builtinUnpackChannel(const BuiltinBuilderContext & ctx)
|
||||
auto target = out / channelName;
|
||||
try {
|
||||
std::filesystem::rename(fileName, target);
|
||||
} catch (std::filesystem::filesystem_error & e) {
|
||||
throw SystemError(e.code(), "failed to rename %1% to %2%", fileName, target.string());
|
||||
} catch (std::filesystem::filesystem_error &) {
|
||||
throw SysError("failed to rename %1% to %2%", fileName, target.string());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ void LocalStore::findRoots(const Path & path, std::filesystem::file_type type, R
|
||||
|| e.code() == std::errc::not_a_directory)
|
||||
printInfo("cannot read potential root '%1%'", path);
|
||||
else
|
||||
throw SystemError(e.code(), "finding GC roots in '%1%'", path);
|
||||
throw;
|
||||
}
|
||||
|
||||
catch (SystemError & e) {
|
||||
|
||||
@@ -86,17 +86,6 @@ 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);
|
||||
|
||||
std::filesystem::path binaryCacheDir;
|
||||
Path binaryCacheDir;
|
||||
|
||||
static const std::string name()
|
||||
{
|
||||
|
||||
@@ -3,32 +3,11 @@
|
||||
#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}
|
||||
@@ -50,7 +29,7 @@ StoreReference LocalBinaryCacheStoreConfig::getReference() const
|
||||
.variant =
|
||||
StoreReference::Specified{
|
||||
.scheme = "file",
|
||||
.authority = encodeUrlPath(pathToUrlPath(binaryCacheDir)),
|
||||
.authority = binaryCacheDir,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -77,9 +56,7 @@ protected:
|
||||
void upsertFile(
|
||||
const std::string & path, RestartableSource & source, const std::string & mimeType, uint64_t sizeHint) override
|
||||
{
|
||||
/* 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);
|
||||
auto path2 = std::filesystem::path{config->binaryCacheDir} / path;
|
||||
static std::atomic<int> counter{0};
|
||||
createDirs(path2.parent_path());
|
||||
auto tmp = path2;
|
||||
@@ -93,7 +70,7 @@ protected:
|
||||
void getFile(const std::string & path, Sink & sink) override
|
||||
{
|
||||
try {
|
||||
readFile(checkBinaryCachePath(config->binaryCacheDir, path), sink);
|
||||
readFile(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);
|
||||
@@ -124,17 +101,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(checkBinaryCachePath(config->binaryCacheDir, path));
|
||||
return pathExists(config->binaryCacheDir + "/" + path);
|
||||
}
|
||||
|
||||
StringSet LocalBinaryCacheStoreConfig::uriSchemes()
|
||||
|
||||
@@ -35,7 +35,7 @@ static void readProcLink(const std::filesystem::path & file, UncheckedRoots & ro
|
||||
if (e.code() == std::errc::no_such_file_or_directory || e.code() == std::errc::permission_denied
|
||||
|| e.code() == std::errc::no_such_process)
|
||||
return;
|
||||
throw SystemError(e.code(), "reading symlink '%s'", PathFmt(file));
|
||||
throw;
|
||||
}
|
||||
if (buf.is_absolute())
|
||||
roots[buf.string()].emplace(file.string());
|
||||
|
||||
@@ -144,7 +144,10 @@ LocalStore::LocalStore(ref<const Config> config)
|
||||
Path gcRootsDir = config->stateDir + "/gcroots";
|
||||
const auto & localSettings = config->getLocalSettings();
|
||||
const auto & gcSettings = localSettings.getGCSettings();
|
||||
createDirs(gcRootsDir);
|
||||
if (!pathExists(gcRootsDir)) {
|
||||
createDirs(gcRootsDir);
|
||||
replaceSymlink(profilesDir, gcRootsDir + "/profiles");
|
||||
}
|
||||
|
||||
for (auto & perUserDir : {profilesDir + "/per-user", gcRootsDir + "/per-user"}) {
|
||||
createDirs(perUserDir);
|
||||
|
||||
@@ -111,8 +111,8 @@ static std::vector<std::string> expandBuilderLines(const std::string & builders)
|
||||
std::string text;
|
||||
try {
|
||||
text = readFile(path);
|
||||
} catch (const SystemError & e) {
|
||||
if (!e.is(std::errc::no_such_file_or_directory))
|
||||
} catch (const SysError & e) {
|
||||
if (e.errNo != ENOENT)
|
||||
throw;
|
||||
debug("cannot find machines file '%s'", path);
|
||||
continue;
|
||||
|
||||
@@ -63,6 +63,9 @@ 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;
|
||||
@@ -181,7 +184,7 @@ private:
|
||||
{
|
||||
auto i = state.caches.find(uri);
|
||||
if (i == state.caches.end()) {
|
||||
auto queryCache(state.queryCache.use()(uri)(time(0) - settings.ttlMeta));
|
||||
auto queryCache(state.queryCache.use()(uri)(time(0) - cacheInfoTtl));
|
||||
if (!queryCache.next())
|
||||
return std::nullopt;
|
||||
auto cache = Cache{
|
||||
|
||||
@@ -202,13 +202,14 @@ void LocalStore::optimisePath_(
|
||||
full. When that happens, it's fine to ignore it: we
|
||||
just effectively disable deduplication of this
|
||||
file.
|
||||
TODO: Get rid of errno, use error code.
|
||||
*/
|
||||
printInfo("cannot link %s to '%s': %s", PathFmt(linkPath), path, e.code().message());
|
||||
printInfo("cannot link %s to '%s': %s", PathFmt(linkPath), path, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
else
|
||||
throw SystemError(e.code(), "creating hard link from %1% to %2%", PathFmt(linkPath), path);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +250,7 @@ void LocalStore::optimisePath_(
|
||||
printInfo("%1% has maximum number of links", PathFmt(linkPath));
|
||||
return;
|
||||
}
|
||||
throw SystemError(e.code(), "creating hard link from %1% to %2%", PathFmt(linkPath), PathFmt(tempLink));
|
||||
throw;
|
||||
}
|
||||
|
||||
/* Atomically replace the old file with the new hard link. */
|
||||
@@ -270,7 +271,7 @@ void LocalStore::optimisePath_(
|
||||
debug("%s has reached maximum number of links", PathFmt(linkPath));
|
||||
return;
|
||||
}
|
||||
throw SystemError(e.code(), "renaming %1% to %2%", PathFmt(tempLink), path);
|
||||
throw;
|
||||
}
|
||||
|
||||
stats.filesLinked++;
|
||||
|
||||
@@ -103,7 +103,7 @@ static void removeFile(const std::filesystem::path & path)
|
||||
try {
|
||||
std::filesystem::remove(path);
|
||||
} catch (std::filesystem::filesystem_error & e) {
|
||||
throw SystemError(e.code(), "removing file %1%", PathFmt(path));
|
||||
throw SysError("removing file %1%", PathFmt(path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +293,8 @@ std::string optimisticLockProfile(const std::filesystem::path & profile)
|
||||
|
||||
std::filesystem::path profilesDir(ProfileDirsOptions settings)
|
||||
{
|
||||
auto profileRoot = isRootUser() ? rootProfilesDir(settings) : createNixStateDir() / "profiles";
|
||||
auto profileRoot =
|
||||
isRootUser() ? rootProfilesDir(settings) : std::filesystem::path{createNixStateDir()} / "profiles";
|
||||
createDirs(profileRoot);
|
||||
return profileRoot;
|
||||
}
|
||||
@@ -305,8 +306,9 @@ std::filesystem::path rootProfilesDir(ProfileDirsOptions settings)
|
||||
|
||||
std::filesystem::path getDefaultProfile(ProfileDirsOptions settings)
|
||||
{
|
||||
std::filesystem::path profileLink =
|
||||
settings.useXDGBaseDirectories ? createNixStateDir() / "profile" : getHome() / ".nix-profile";
|
||||
std::filesystem::path profileLink = settings.useXDGBaseDirectories
|
||||
? std::filesystem::path{createNixStateDir()} / "profile"
|
||||
: std::filesystem::path{getHome()} / ".nix-profile";
|
||||
try {
|
||||
auto profile = profilesDir(settings) / "profile";
|
||||
if (!pathExists(profileLink)) {
|
||||
@@ -322,6 +324,8 @@ std::filesystem::path getDefaultProfile(ProfileDirsOptions settings)
|
||||
return absPath(readLink(profileLink), &linkDir);
|
||||
} catch (Error &) {
|
||||
return profileLink;
|
||||
} catch (std::filesystem::filesystem_error &) {
|
||||
return profileLink;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ void SSHMaster::addCommonSSHOpts(Strings & args)
|
||||
args.insert(args.end(), {"-i", keyFile});
|
||||
if (!sshPublicHostKey.empty()) {
|
||||
std::filesystem::path fileName = tmpDir->path() / "host-key";
|
||||
writeFile(fileName, authority.host + " " + sshPublicHostKey + "\n");
|
||||
writeFile(fileName.string(), authority.host + " " + sshPublicHostKey + "\n");
|
||||
args.insert(args.end(), {"-oUserKnownHostsFile=" + fileName.string()});
|
||||
}
|
||||
if (compress)
|
||||
|
||||
@@ -1190,14 +1190,8 @@ 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 {
|
||||
/* 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));
|
||||
return parseDerivation(store, accessor->readFile(CanonPath::root), Derivation::nameFromPath(drvPath));
|
||||
} catch (FormatError & e) {
|
||||
throw Error("error parsing derivation '%s': %s", store.printStorePath(drvPath), e.msg());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#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"
|
||||
@@ -18,7 +17,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"
|
||||
&& OsPathTrait<char>::findPathSep(spec) != std::string::npos;
|
||||
&& spec.find("/") != std::string::npos;
|
||||
}
|
||||
|
||||
std::string StoreReference::render(bool withParams) const
|
||||
@@ -112,7 +111,7 @@ StoreReference StoreReference::parse(const std::string & uri, const StoreReferen
|
||||
.variant =
|
||||
Specified{
|
||||
.scheme = "local",
|
||||
.authority = encodeUrlPath(pathToUrlPath(absPath(std::filesystem::path{baseURI}))),
|
||||
.authority = absPath(baseURI),
|
||||
},
|
||||
.params = std::move(params),
|
||||
};
|
||||
|
||||
@@ -42,18 +42,17 @@ ref<StoreConfig> resolveStoreConfig(StoreReference && storeURI)
|
||||
/* If /nix doesn't exist, there is no daemon socket, and
|
||||
we're not root, then automatically set up a chroot
|
||||
store in ~/.local/share/nix/root. */
|
||||
auto chrootStore = getDataDir() / "root";
|
||||
auto chrootStore = getDataDir() + "/root";
|
||||
if (!pathExists(chrootStore)) {
|
||||
try {
|
||||
createDirs(chrootStore);
|
||||
} catch (SystemError & e) {
|
||||
return make_ref<LocalStore::Config>(params);
|
||||
}
|
||||
warn("%s does not exist, so Nix will use %s as a chroot store", stateDir, PathFmt(chrootStore));
|
||||
warn("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore);
|
||||
} else
|
||||
debug(
|
||||
"%s does not exist, so Nix will use %s as a chroot store", stateDir, PathFmt(chrootStore));
|
||||
return make_ref<LocalStore::Config>("local", chrootStore.string(), params);
|
||||
debug("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore);
|
||||
return make_ref<LocalStore::Config>("local", chrootStore, params);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
|
||||
@@ -1269,7 +1269,7 @@ void DerivationBuilderImpl::writeBuilderFile(const std::string & name, std::stri
|
||||
openat(tmpDirFd.get(), name.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC | O_EXCL | O_NOFOLLOW, 0666)};
|
||||
if (!fd)
|
||||
throw SysError("creating file %s", PathFmt(path));
|
||||
writeFile(fd.get(), contents);
|
||||
writeFile(fd, path, contents);
|
||||
chownToBuilder(fd.get(), path);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#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>
|
||||
@@ -53,43 +51,31 @@ 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))
|
||||
return warnOrThrowWine(GetLastError(), "Failed to unlock file %s", PathFmt(descriptorToPath(desc)));
|
||||
if (!UnlockFileEx(desc, 0, 2, 0, &ov)) {
|
||||
WinError winError("Failed to unlock file desc %s", desc);
|
||||
throw winError;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case ltRead: {
|
||||
OVERLAPPED ov = {0};
|
||||
if (!LockFileEx(desc, wait ? 0 : LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &ov)) {
|
||||
auto lastError = GetLastError();
|
||||
if (lastError == ERROR_LOCK_VIOLATION)
|
||||
WinError winError("Failed to lock file desc %s", desc);
|
||||
if (winError.lastError == ERROR_LOCK_VIOLATION)
|
||||
return false;
|
||||
return warnOrThrowWine(lastError, "Failed to lock file %s", PathFmt(descriptorToPath(desc)));
|
||||
throw winError;
|
||||
}
|
||||
|
||||
ov.Offset = 1;
|
||||
if (!UnlockFileEx(desc, 0, 1, 0, &ov)) {
|
||||
auto lastError = GetLastError();
|
||||
if (lastError != ERROR_NOT_LOCKED)
|
||||
return warnOrThrowWine(lastError, "Failed to unlock file %s", PathFmt(descriptorToPath(desc)));
|
||||
WinError winError("Failed to unlock file desc %s", desc);
|
||||
if (winError.lastError != ERROR_NOT_LOCKED)
|
||||
throw winError;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -97,17 +83,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)) {
|
||||
auto lastError = GetLastError();
|
||||
if (lastError == ERROR_LOCK_VIOLATION)
|
||||
WinError winError("Failed to lock file desc %s", desc);
|
||||
if (winError.lastError == ERROR_LOCK_VIOLATION)
|
||||
return false;
|
||||
return warnOrThrowWine(lastError, "Failed to lock file %s", PathFmt(descriptorToPath(desc)));
|
||||
throw winError;
|
||||
}
|
||||
|
||||
ov.Offset = 0;
|
||||
if (!UnlockFileEx(desc, 0, 1, 0, &ov)) {
|
||||
auto lastError = GetLastError();
|
||||
if (lastError != ERROR_NOT_LOCKED)
|
||||
return warnOrThrowWine(lastError, "Failed to unlock file %s", PathFmt(descriptorToPath(desc)));
|
||||
WinError winError("Failed to unlock file desc %s", desc);
|
||||
if (winError.lastError != ERROR_NOT_LOCKED)
|
||||
throw winError;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -109,13 +109,6 @@ 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,15 +3,9 @@
|
||||
|
||||
#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.
|
||||
@@ -119,55 +113,6 @@ 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;
|
||||
@@ -298,25 +243,4 @@ 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,18 +84,14 @@ 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
|
||||
EXPECT_THROW(
|
||||
ASSERT_THROW(
|
||||
sink.createDirectory(
|
||||
CanonPath("a/b/c/f"), [](FileSystemObjectSink & dirSink, const CanonPath & relPath) {}),
|
||||
SymlinkNotAllowed);
|
||||
EXPECT_THROW(
|
||||
ASSERT_THROW(
|
||||
sink.createRegularFile(
|
||||
CanonPath("a/b/c/regular"), [](CreateRegularFileSink & crf) { crf("some contents"); }),
|
||||
SymlinkNotAllowed);
|
||||
@@ -104,7 +100,7 @@ TEST(openFileEnsureBeneathNoSymlinks, works)
|
||||
AutoCloseFD dirFd = openDirectory(tmpDir);
|
||||
|
||||
// Helper to open files with platform-specific arguments
|
||||
auto openRead = [&](std::string_view path) -> AutoCloseFD {
|
||||
auto openRead = [&](std::string_view path) -> Descriptor {
|
||||
return openFileEnsureBeneathNoSymlinks(
|
||||
dirFd.get(),
|
||||
CanonPath(path),
|
||||
@@ -118,7 +114,7 @@ TEST(openFileEnsureBeneathNoSymlinks, works)
|
||||
);
|
||||
};
|
||||
|
||||
auto openReadDir = [&](std::string_view path) -> AutoCloseFD {
|
||||
auto openReadDir = [&](std::string_view path) -> Descriptor {
|
||||
return openFileEnsureBeneathNoSymlinks(
|
||||
dirFd.get(),
|
||||
CanonPath(path),
|
||||
@@ -132,7 +128,7 @@ TEST(openFileEnsureBeneathNoSymlinks, works)
|
||||
);
|
||||
};
|
||||
|
||||
auto openCreateExclusive = [&](std::string_view path) -> AutoCloseFD {
|
||||
auto openCreateExclusive = [&](std::string_view path) -> Descriptor {
|
||||
return openFileEnsureBeneathNoSymlinks(
|
||||
dirFd.get(),
|
||||
CanonPath(path),
|
||||
@@ -158,19 +154,19 @@ TEST(openFileEnsureBeneathNoSymlinks, works)
|
||||
|
||||
#if !defined(_WIN32) && !defined(__CYGWIN__)
|
||||
// This returns ELOOP on cygwin when O_NOFOLLOW is used
|
||||
EXPECT_FALSE(openCreateExclusive("a/broken_symlink"));
|
||||
EXPECT_EQ(openCreateExclusive("a/broken_symlink"), INVALID_DESCRIPTOR);
|
||||
/* Sanity check, no symlink shenanigans and behaves the same as regular openat with O_EXCL | O_CREAT. */
|
||||
EXPECT_EQ(errno, EEXIST);
|
||||
#endif
|
||||
EXPECT_THROW(openCreateExclusive("a/absolute_symlink/broken_symlink"), SymlinkNotAllowed);
|
||||
|
||||
// Test invalid paths
|
||||
EXPECT_FALSE(openRead("c/d/regular/a"));
|
||||
EXPECT_FALSE(openReadDir("c/d/regular"));
|
||||
EXPECT_EQ(openRead("c/d/regular/a"), INVALID_DESCRIPTOR);
|
||||
EXPECT_EQ(openReadDir("c/d/regular"), INVALID_DESCRIPTOR);
|
||||
|
||||
// Test valid paths work
|
||||
EXPECT_TRUE(openRead("c/d/regular"));
|
||||
EXPECT_TRUE(openCreateExclusive("a/regular"));
|
||||
EXPECT_TRUE(AutoCloseFD{openRead("c/d/regular")});
|
||||
EXPECT_TRUE(AutoCloseFD{openCreateExclusive("a/regular")});
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -16,12 +16,10 @@ using namespace std::string_view_literals;
|
||||
|
||||
#ifdef _WIN32
|
||||
# define FS_SEP L"\\"
|
||||
# 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
|
||||
# define FS_ROOT L"C:" FS_SEP // Need a mounted one, C drive is likely
|
||||
#else
|
||||
# define FS_SEP "/"
|
||||
# define FS_ROOT_NO_TRAILING_SLASH FS_SEP
|
||||
# define FS_ROOT FS_ROOT_NO_TRAILING_SLASH
|
||||
# define FS_ROOT FS_SEP
|
||||
#endif
|
||||
|
||||
#ifndef PATH_MAX
|
||||
@@ -46,7 +44,7 @@ TEST(absPath, doesntChangeRoot)
|
||||
{
|
||||
auto p = absPath(std::filesystem::path{FS_ROOT});
|
||||
|
||||
ASSERT_EQ(p, FS_ROOT_NO_TRAILING_SLASH);
|
||||
ASSERT_EQ(p, FS_ROOT);
|
||||
}
|
||||
|
||||
TEST(absPath, turnsEmptyPathIntoCWD)
|
||||
@@ -198,42 +196,42 @@ TEST(baseNameOf, absoluteNothingSlashNothing)
|
||||
|
||||
TEST(isInDir, trivialCase)
|
||||
{
|
||||
EXPECT_TRUE(isInDir(FS_ROOT "foo" FS_SEP "bar", FS_ROOT "foo"));
|
||||
EXPECT_TRUE(isInDir("/foo/bar", "/foo"));
|
||||
}
|
||||
|
||||
TEST(isInDir, notInDir)
|
||||
{
|
||||
EXPECT_FALSE(isInDir(FS_ROOT "zes" FS_SEP "foo" FS_SEP "bar", FS_ROOT "foo"));
|
||||
EXPECT_FALSE(isInDir("/zes/foo/bar", "/foo"));
|
||||
}
|
||||
|
||||
TEST(isInDir, emptyDir)
|
||||
{
|
||||
EXPECT_FALSE(isInDir(FS_ROOT "zes" FS_SEP "foo" FS_SEP "bar", ""));
|
||||
EXPECT_FALSE(isInDir("/zes/foo/bar", ""));
|
||||
}
|
||||
|
||||
TEST(isInDir, hiddenSubdirectory)
|
||||
{
|
||||
EXPECT_TRUE(isInDir(FS_ROOT "foo" FS_SEP ".ssh", FS_ROOT "foo"));
|
||||
EXPECT_TRUE(isInDir("/foo/.ssh", "/foo"));
|
||||
}
|
||||
|
||||
TEST(isInDir, ellipsisEntry)
|
||||
{
|
||||
EXPECT_TRUE(isInDir(FS_ROOT "foo" FS_SEP "...", FS_ROOT "foo"));
|
||||
EXPECT_TRUE(isInDir("/foo/...", "/foo"));
|
||||
}
|
||||
|
||||
TEST(isInDir, sameDir)
|
||||
{
|
||||
EXPECT_FALSE(isInDir(FS_ROOT "foo", FS_ROOT "foo"));
|
||||
EXPECT_FALSE(isInDir("/foo", "/foo"));
|
||||
}
|
||||
|
||||
TEST(isInDir, sameDirDot)
|
||||
{
|
||||
EXPECT_FALSE(isInDir(FS_ROOT "foo" FS_SEP ".", FS_ROOT "foo"));
|
||||
EXPECT_FALSE(isInDir("/foo/.", "/foo"));
|
||||
}
|
||||
|
||||
TEST(isInDir, dotDotPrefix)
|
||||
{
|
||||
EXPECT_FALSE(isInDir(FS_ROOT "foo" FS_SEP ".." FS_SEP "bar", FS_ROOT "foo"));
|
||||
EXPECT_FALSE(isInDir("/foo/../bar", "/foo"));
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
@@ -242,8 +240,8 @@ TEST(isInDir, dotDotPrefix)
|
||||
|
||||
TEST(isDirOrInDir, trueForSameDirectory)
|
||||
{
|
||||
ASSERT_EQ(isDirOrInDir(FS_ROOT "nix", FS_ROOT "nix"), true);
|
||||
ASSERT_EQ(isDirOrInDir(FS_ROOT, FS_ROOT), true);
|
||||
ASSERT_EQ(isDirOrInDir("/nix", "/nix"), true);
|
||||
ASSERT_EQ(isDirOrInDir("/", "/"), true);
|
||||
}
|
||||
|
||||
TEST(isDirOrInDir, trueForEmptyPaths)
|
||||
@@ -253,17 +251,17 @@ TEST(isDirOrInDir, trueForEmptyPaths)
|
||||
|
||||
TEST(isDirOrInDir, falseForDisjunctPaths)
|
||||
{
|
||||
ASSERT_EQ(isDirOrInDir(FS_ROOT "foo", FS_ROOT "bar"), false);
|
||||
ASSERT_EQ(isDirOrInDir("/foo", "/bar"), false);
|
||||
}
|
||||
|
||||
TEST(isDirOrInDir, relativePaths)
|
||||
{
|
||||
ASSERT_EQ(isDirOrInDir(FS_ROOT "foo" FS_SEP "..", FS_ROOT "foo"), false);
|
||||
ASSERT_EQ(isDirOrInDir("/foo/..", "/foo"), false);
|
||||
}
|
||||
|
||||
TEST(isDirOrInDir, relativePathsTwice)
|
||||
{
|
||||
ASSERT_EQ(isDirOrInDir(FS_ROOT "foo" FS_SEP "..", FS_ROOT "foo" FS_SEP "."), false);
|
||||
ASSERT_EQ(isDirOrInDir("/foo/..", "/foo/."), false);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
@@ -296,16 +294,13 @@ TEST(makeParentCanonical, noParent)
|
||||
|
||||
TEST(makeParentCanonical, root)
|
||||
{
|
||||
ASSERT_EQ(makeParentCanonical(FS_ROOT), FS_ROOT_NO_TRAILING_SLASH);
|
||||
ASSERT_EQ(makeParentCanonical("/"), "/");
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* 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();
|
||||
@@ -321,11 +316,10 @@ TEST(chmodIfNeeded, works)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(chmodIfNeeded, nonexistent)
|
||||
{
|
||||
ASSERT_THROW(chmodIfNeeded("/schnitzel/darmstadt/pommes", 0755), SystemError);
|
||||
ASSERT_THROW(chmodIfNeeded("/schnitzel/darmstadt/pommes", 0755), SysError);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
@@ -346,7 +340,7 @@ TEST(DirectoryIterator, works)
|
||||
|
||||
TEST(DirectoryIterator, nonexistent)
|
||||
{
|
||||
ASSERT_THROW(DirectoryIterator("/schnitzel/darmstadt/pommes"), SystemError);
|
||||
ASSERT_THROW(DirectoryIterator("/schnitzel/darmstadt/pommes"), SysError);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
|
||||
@@ -137,34 +137,4 @@ TEST_F(FSSourceAccessorTest, works)
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* RestoreSink non-directory at root (no dirFd)
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST_F(FSSourceAccessorTest, RestoreSinkRegularFileAtRoot)
|
||||
{
|
||||
auto filePath = tmpDir / "rootfile";
|
||||
{
|
||||
RestoreSink sink(false);
|
||||
sink.dstPath = filePath;
|
||||
// No dirFd set - this tests the !dirFd path
|
||||
sink.createRegularFile(CanonPath::root, [](CreateRegularFileSink & crf) { crf("root content"); });
|
||||
}
|
||||
|
||||
EXPECT_THAT(makeFSSourceAccessor(filePath), HasContents(CanonPath::root, "root content"));
|
||||
}
|
||||
|
||||
TEST_F(FSSourceAccessorTest, RestoreSinkSymlinkAtRoot)
|
||||
{
|
||||
auto linkPath = tmpDir / "rootlink";
|
||||
{
|
||||
RestoreSink sink(false);
|
||||
sink.dstPath = linkPath;
|
||||
// No dirFd set - this tests the !dirFd path
|
||||
sink.createSymlink(CanonPath::root, "symlink_target");
|
||||
}
|
||||
|
||||
EXPECT_THAT(makeFSSourceAccessor(linkPath), HasSymlink(CanonPath::root, "symlink_target"));
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -956,108 +956,4 @@ 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,11 +41,6 @@ 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)
|
||||
@@ -427,20 +422,13 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
|
||||
*/
|
||||
static void writeErr(std::string_view buf)
|
||||
{
|
||||
Descriptor fd = getStandardError();
|
||||
while (!buf.empty()) {
|
||||
#ifdef _WIN32
|
||||
DWORD n;
|
||||
if (!WriteFile(fd, buf.data(), buf.size(), &n, NULL))
|
||||
abort();
|
||||
#else
|
||||
auto n = ::write(fd, buf.data(), buf.size());
|
||||
auto n = write(STDERR_FILENO, buf.data(), buf.size());
|
||||
if (n < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
buf = buf.substr(n);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,23 +20,17 @@ ExecutablePath ExecutablePath::load()
|
||||
}
|
||||
|
||||
ExecutablePath ExecutablePath::parse(const OsString & path)
|
||||
{
|
||||
ExecutablePath ret;
|
||||
ret.parseAppend(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ExecutablePath::parseAppend(const OsString & path)
|
||||
{
|
||||
auto strings = path.empty() ? (std::list<OsString>{})
|
||||
: basicSplitString<std::list<OsString>, OsChar>(path, path_var_separator);
|
||||
|
||||
directories.reserve(directories.size() + strings.size());
|
||||
std::vector<std::filesystem::path> ret;
|
||||
ret.reserve(strings.size());
|
||||
|
||||
std::transform(
|
||||
std::make_move_iterator(strings.begin()),
|
||||
std::make_move_iterator(strings.end()),
|
||||
std::back_inserter(directories),
|
||||
std::back_inserter(ret),
|
||||
[](OsString && str) {
|
||||
return std::filesystem::path{
|
||||
str.empty()
|
||||
@@ -51,6 +45,8 @@ void ExecutablePath::parseAppend(const OsString & path)
|
||||
: std::move(str),
|
||||
};
|
||||
});
|
||||
|
||||
return {ret};
|
||||
}
|
||||
|
||||
OsString ExecutablePath::render() const
|
||||
|
||||
@@ -8,112 +8,10 @@
|
||||
#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';
|
||||
@@ -169,6 +67,8 @@ 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;
|
||||
}
|
||||
|
||||
@@ -205,8 +105,18 @@ 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());
|
||||
auto n = readOffset(fd, offset, std::span(buf.data(), limit));
|
||||
/* 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;
|
||||
}
|
||||
if (n == 0)
|
||||
throw EndOfFile("unexpected end-of-file");
|
||||
assert(n <= left);
|
||||
@@ -274,6 +184,24 @@ void AutoCloseFD::close()
|
||||
}
|
||||
}
|
||||
|
||||
void AutoCloseFD::fsync() const
|
||||
{
|
||||
if (fd != INVALID_DESCRIPTOR) {
|
||||
int result;
|
||||
result =
|
||||
#ifdef _WIN32
|
||||
::FlushFileBuffers(fd)
|
||||
#elif defined(__APPLE__)
|
||||
::fcntl(fd, F_FULLFSYNC)
|
||||
#else
|
||||
::fsync(fd)
|
||||
#endif
|
||||
;
|
||||
if (result == -1)
|
||||
throw NativeSysError("fsync file descriptor %1%", fd);
|
||||
}
|
||||
}
|
||||
|
||||
void AutoCloseFD::startFsync() const
|
||||
{
|
||||
#ifdef __linux__
|
||||
|
||||
@@ -21,9 +21,7 @@
|
||||
#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>
|
||||
@@ -42,9 +40,9 @@ DirectoryIterator::DirectoryIterator(const std::filesystem::path & p)
|
||||
// **Attempt to create the underlying directory_iterator**
|
||||
it_ = std::filesystem::directory_iterator(p);
|
||||
} catch (const std::filesystem::filesystem_error & e) {
|
||||
// **Catch filesystem_error and throw SystemError**
|
||||
// Adapt the error message as needed for SystemError
|
||||
throw SystemError(e.code(), "cannot read directory %s", PathFmt(p));
|
||||
// **Catch filesystem_error and throw SysError**
|
||||
// Adapt the error message as needed for SysError
|
||||
throw SysError("cannot read directory %s", PathFmt(p));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,13 +262,13 @@ std::optional<PosixStat> maybeLstat(const std::filesystem::path & path)
|
||||
|
||||
bool pathExists(const std::filesystem::path & path)
|
||||
{
|
||||
return maybeLstat(path).has_value();
|
||||
return maybeLstat(path.string()).has_value();
|
||||
}
|
||||
|
||||
bool pathAccessible(const std::filesystem::path & path)
|
||||
{
|
||||
try {
|
||||
return pathExists(path);
|
||||
return pathExists(path.string());
|
||||
} catch (SystemError & e) {
|
||||
// swallow EPERM
|
||||
if (e.is(std::errc::operation_not_permitted))
|
||||
@@ -282,11 +280,7 @@ bool pathAccessible(const std::filesystem::path & path)
|
||||
std::filesystem::path readLink(const std::filesystem::path & path)
|
||||
{
|
||||
checkInterrupt();
|
||||
try {
|
||||
return std::filesystem::read_symlink(path);
|
||||
} catch (std::filesystem::filesystem_error & e) {
|
||||
throw SystemError(e.code(), "reading symbolic link '%s'", PathFmt(path));
|
||||
}
|
||||
return std::filesystem::read_symlink(path);
|
||||
}
|
||||
|
||||
Path readLink(const Path & path)
|
||||
@@ -307,27 +301,25 @@ std::string readFile(const std::filesystem::path & path)
|
||||
return readFile(fd.get());
|
||||
}
|
||||
|
||||
void readFile(const std::filesystem::path & path, Sink & sink, bool memory_map)
|
||||
void readFile(const Path & path, Sink & sink, bool memory_map)
|
||||
{
|
||||
// Memory-map the file for faster processing where possible.
|
||||
if (memory_map) {
|
||||
try {
|
||||
// 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()});
|
||||
boost::iostreams::mapped_file_source mmap(path);
|
||||
if (mmap.is_open()) {
|
||||
sink({mmap.data(), mmap.size()});
|
||||
return;
|
||||
}
|
||||
} catch (const boost::exception & e) {
|
||||
debug("memory-mapping failed for path: %s: %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(path);
|
||||
AutoCloseFD fd = openFileReadonly(std::filesystem::path(path));
|
||||
if (!fd)
|
||||
throw NativeSysError("opening file %s", PathFmt(path));
|
||||
throw NativeSysError("opening file %s", path);
|
||||
drainFD(fd.get(), sink);
|
||||
}
|
||||
|
||||
@@ -344,23 +336,23 @@ void writeFile(const Path & path, std::string_view s, mode_t mode, FsSync sync)
|
||||
if (!fd)
|
||||
throw SysError("opening file '%1%'", path);
|
||||
|
||||
writeFile(fd.get(), s, sync, &path);
|
||||
writeFile(fd, path, s, mode, sync);
|
||||
|
||||
/* Close explicitly to propagate the exceptions. */
|
||||
fd.close();
|
||||
}
|
||||
|
||||
void writeFile(Descriptor fd, std::string_view s, FsSync sync, const Path * origPath)
|
||||
void writeFile(AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode, FsSync sync)
|
||||
{
|
||||
assert(fd != INVALID_DESCRIPTOR);
|
||||
assert(fd);
|
||||
try {
|
||||
writeFull(fd, s);
|
||||
writeFull(fd.get(), s);
|
||||
|
||||
if (sync == FsSync::Yes)
|
||||
syncDescriptor(fd);
|
||||
fd.fsync();
|
||||
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "writing file '%1%'", origPath ? *origPath : descriptorToPath(fd).string());
|
||||
e.addTrace({}, "writing file '%1%'", origPath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -471,7 +463,7 @@ void createDirs(const std::filesystem::path & path)
|
||||
try {
|
||||
std::filesystem::create_directories(path);
|
||||
} catch (std::filesystem::filesystem_error & e) {
|
||||
throw SystemError(e.code(), "creating directory '%1%'", path.string());
|
||||
throw SysError("creating directory '%1%'", path.string());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,7 +664,7 @@ void replaceSymlink(const std::filesystem::path & target, const std::filesystem:
|
||||
} catch (std::filesystem::filesystem_error & e) {
|
||||
if (e.code() == std::errc::file_exists)
|
||||
continue;
|
||||
throw SystemError(e.code(), "creating symlink %1% -> %2%", PathFmt(tmp), PathFmt(target));
|
||||
throw SysError("creating symlink %1% -> %2%", PathFmt(tmp), PathFmt(target));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -680,7 +672,7 @@ void replaceSymlink(const std::filesystem::path & target, const std::filesystem:
|
||||
} catch (std::filesystem::filesystem_error & e) {
|
||||
if (e.code() == std::errc::file_exists)
|
||||
continue;
|
||||
throw SystemError(e.code(), "renaming %1% to %2%", PathFmt(tmp), PathFmt(link));
|
||||
throw SysError("renaming %1% to %2%", PathFmt(tmp), PathFmt(link));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -781,7 +773,7 @@ std::filesystem::path makeParentCanonical(const std::filesystem::path & rawPath)
|
||||
}
|
||||
return std::filesystem::canonical(parent) / path.filename();
|
||||
} catch (std::filesystem::filesystem_error & e) {
|
||||
throw SystemError(e.code(), "canonicalising parent path of %1%", PathFmt(path));
|
||||
throw SysError("canonicalising parent path of %1%", PathFmt(path));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 AutoCloseFD{::open(p.c_str(), flags, 0666)};
|
||||
return ::open(p.c_str(), flags, 0666);
|
||||
return openFileEnsureBeneathNoSymlinks(dirFd.get(), path, flags, 0666);
|
||||
}()
|
||||
#endif
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
#include "nix/util/suggestions.hh"
|
||||
#include "nix/util/fmt.hh"
|
||||
#include "nix/util/config.hh"
|
||||
|
||||
#include <cstring>
|
||||
#include <list>
|
||||
@@ -127,20 +126,20 @@ public:
|
||||
BaseError & operator=(BaseError &&) = default;
|
||||
|
||||
template<typename... Args>
|
||||
BaseError(unsigned int status, Args &&... args)
|
||||
: err{.level = lvlError, .msg = HintFmt(std::forward<Args>(args)...), .pos = {}, .status = status}
|
||||
BaseError(unsigned int status, const Args &... args)
|
||||
: err{.level = lvlError, .msg = HintFmt(args...), .pos = {}, .status = status}
|
||||
{
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
explicit BaseError(const std::string & fs, Args &&... args)
|
||||
: err{.level = lvlError, .msg = HintFmt(fs, std::forward<Args>(args)...), .pos = {}}
|
||||
explicit BaseError(const std::string & fs, const Args &... args)
|
||||
: err{.level = lvlError, .msg = HintFmt(fs, args...), .pos = {}}
|
||||
{
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
BaseError(const Suggestions & sug, Args &&... args)
|
||||
: err{.level = lvlError, .msg = HintFmt(std::forward<Args>(args)...), .pos = {}, .suggestions = sug}
|
||||
BaseError(const Suggestions & sug, const Args &... args)
|
||||
: err{.level = lvlError, .msg = HintFmt(args...), .pos = {}, .suggestions = sug}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -191,8 +190,6 @@ public:
|
||||
err.pos = pos;
|
||||
}
|
||||
|
||||
bool hasPos() const;
|
||||
|
||||
void pushTrace(Trace trace)
|
||||
{
|
||||
err.traces.push_front(trace);
|
||||
@@ -206,9 +203,9 @@ public:
|
||||
* @param args... Format string arguments.
|
||||
*/
|
||||
template<typename... Args>
|
||||
void addTrace(std::shared_ptr<const Pos> && pos, std::string_view fs, Args &&... args)
|
||||
void addTrace(std::shared_ptr<const Pos> && pos, std::string_view fs, const Args &... args)
|
||||
{
|
||||
addTrace(std::move(pos), HintFmt(std::string(fs), std::forward<Args>(args)...));
|
||||
addTrace(std::move(pos), HintFmt(std::string(fs), args...));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,39 +265,19 @@ MakeError(UnimplementedError, Error);
|
||||
class SystemError : public CloneableError<SystemError, Error>
|
||||
{
|
||||
std::error_code errorCode;
|
||||
std::string errorDetails;
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* Just here to allow derived classes to use the right constructor
|
||||
* (the protected one).
|
||||
*/
|
||||
struct Disambig
|
||||
{};
|
||||
|
||||
/**
|
||||
* Protected constructor for subclasses that provide their own error message.
|
||||
* The error message is appended to the formatted hint.
|
||||
*/
|
||||
template<typename... Args>
|
||||
SystemError(Disambig, std::error_code errorCode, std::string_view errorDetails, Args &&... args)
|
||||
: CloneableError("")
|
||||
, errorCode(errorCode)
|
||||
, errorDetails(errorDetails)
|
||||
{
|
||||
auto hf = HintFmt(std::forward<Args>(args)...);
|
||||
err.msg = HintFmt("%s: %s", Uncolored(hf.str()), errorDetails);
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* Construct with an error code. The error code's message is automatically
|
||||
* appended to the error message.
|
||||
*/
|
||||
template<typename... Args>
|
||||
SystemError(std::errc posixErrNo, Args &&... args)
|
||||
: CloneableError(std::forward<Args>(args)...)
|
||||
, errorCode(std::make_error_code(posixErrNo))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
SystemError(std::error_code errorCode, Args &&... args)
|
||||
: SystemError(Disambig{}, errorCode, errorCode.message(), std::forward<Args>(args)...)
|
||||
: CloneableError(std::forward<Args>(args)...)
|
||||
, errorCode(errorCode)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -341,14 +318,12 @@ public:
|
||||
* will be used to try to add additional information to the message.
|
||||
*/
|
||||
template<typename... Args>
|
||||
SysError(int errNo, Args &&... args)
|
||||
: CloneableError(
|
||||
Disambig{},
|
||||
std::make_error_code(static_cast<std::errc>(errNo)),
|
||||
strerror(errNo),
|
||||
std::forward<Args>(args)...)
|
||||
SysError(int errNo, const Args &... args)
|
||||
: CloneableError(static_cast<std::errc>(errNo), "")
|
||||
, errNo(errNo)
|
||||
{
|
||||
auto hf = HintFmt(args...);
|
||||
err.msg = HintFmt("%1%: %2%", Uncolored(hf.str()), strerror(errNo));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -358,8 +333,8 @@ public:
|
||||
* calling this constructor!
|
||||
*/
|
||||
template<typename... Args>
|
||||
SysError(Args &&... args)
|
||||
: SysError(errno, std::forward<Args>(args)...)
|
||||
SysError(const Args &... args)
|
||||
: SysError(errno, args...)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -393,7 +368,7 @@ int handleExceptions(const std::string & programName, std::function<void()> fun)
|
||||
*/
|
||||
[[gnu::noinline, gnu::cold, noreturn]] void unreachable(std::source_location loc = std::source_location::current());
|
||||
|
||||
#if NIX_UBSAN_ENABLED
|
||||
#if NIX_UBSAN_ENABLED == 1
|
||||
/* When building with sanitizers, also enable expensive unreachable checks. In
|
||||
optimised builds this explicitly invokes UB with std::unreachable for better
|
||||
optimisations. */
|
||||
@@ -423,14 +398,12 @@ public:
|
||||
* information to the message.
|
||||
*/
|
||||
template<typename... Args>
|
||||
WinError(DWORD lastError, Args &&... args)
|
||||
: CloneableError(
|
||||
Disambig{},
|
||||
std::error_code(lastError, std::system_category()),
|
||||
renderError(lastError),
|
||||
std::forward<Args>(args)...)
|
||||
WinError(DWORD lastError, const Args &... args)
|
||||
: CloneableError(std::error_code(lastError, std::system_category()), "")
|
||||
, lastError(lastError)
|
||||
{
|
||||
auto hf = HintFmt(args...);
|
||||
err.msg = HintFmt("%1%: %2%", Uncolored(hf.str()), renderError(lastError));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -440,14 +413,14 @@ public:
|
||||
* before calling this constructor!
|
||||
*/
|
||||
template<typename... Args>
|
||||
WinError(Args &&... args)
|
||||
: WinError(GetLastError(), std::forward<Args>(args)...)
|
||||
WinError(const Args &... args)
|
||||
: WinError(GetLastError(), args...)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static std::string renderError(DWORD lastError);
|
||||
std::string renderError(DWORD lastError);
|
||||
};
|
||||
|
||||
} // namespace windows
|
||||
|
||||
@@ -33,12 +33,6 @@ struct ExecutablePath
|
||||
*/
|
||||
static ExecutablePath parse(const OsString & path);
|
||||
|
||||
/**
|
||||
* Like `parse` but appends new entries to the end of an existing
|
||||
* `ExecutablePath`.
|
||||
*/
|
||||
void parseAppend(const OsString & path);
|
||||
|
||||
/**
|
||||
* Load the `PATH` environment variable and `parse` it.
|
||||
*/
|
||||
|
||||
@@ -65,7 +65,7 @@ std::string readFile(Descriptor fd);
|
||||
* Platform-specific read into a buffer.
|
||||
*
|
||||
* Thin wrapper around ::read (Unix) or ReadFile (Windows).
|
||||
* Handles EINTR on Unix. Treats ERROR_BROKEN_PIPE as EOF on Windows.
|
||||
* Does NOT handle EINTR on Unix - caller must catch and retry if needed.
|
||||
*
|
||||
* @param fd The file descriptor to read from
|
||||
* @param buffer The buffer to read into
|
||||
@@ -74,19 +74,6 @@ 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.
|
||||
*
|
||||
@@ -146,11 +133,6 @@ std::string readLine(Descriptor fd, bool eofOk = false, char terminator = '\n');
|
||||
*/
|
||||
void writeLine(Descriptor fd, std::string s);
|
||||
|
||||
/**
|
||||
* Perform a blocking fsync operation on a file descriptor.
|
||||
*/
|
||||
void syncDescriptor(Descriptor fd);
|
||||
|
||||
/**
|
||||
* Options for draining a file descriptor to a sink.
|
||||
*/
|
||||
@@ -271,11 +253,7 @@ public:
|
||||
/**
|
||||
* Perform a blocking fsync operation.
|
||||
*/
|
||||
void fsync() const
|
||||
{
|
||||
if (fd != INVALID_DESCRIPTOR)
|
||||
nix::syncDescriptor(fd);
|
||||
}
|
||||
void fsync() const;
|
||||
|
||||
/**
|
||||
* Asynchronously flush to disk without blocking, if available on
|
||||
|
||||
@@ -54,7 +54,7 @@ OsString readLinkAt(Descriptor dirFd, const CanonPath & path);
|
||||
* @throws SymlinkNotAllowed if any path components are symlinks
|
||||
* @throws SystemError on other errors
|
||||
*/
|
||||
AutoCloseFD openFileEnsureBeneathNoSymlinks(
|
||||
Descriptor openFileEnsureBeneathNoSymlinks(
|
||||
Descriptor dirFd,
|
||||
const CanonPath & path,
|
||||
#ifdef _WIN32
|
||||
@@ -75,7 +75,7 @@ namespace linux {
|
||||
*
|
||||
* @see https://man7.org/linux/man-pages/man2/openat2.2.html
|
||||
* @see https://man7.org/linux/man-pages/man2/open_how.2type.html
|
||||
*
|
||||
v*
|
||||
* @param flags O_* flags
|
||||
* @param mode Mode for O_{CREAT,TMPFILE}
|
||||
* @param resolve RESOLVE_* flags
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#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>
|
||||
@@ -190,19 +189,21 @@ Path readLink(const Path & path);
|
||||
*/
|
||||
std::filesystem::path readLink(const std::filesystem::path & path);
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace windows {
|
||||
|
||||
/**
|
||||
* Get the path associated with a file descriptor.
|
||||
* Get the path associated with a file handle.
|
||||
*
|
||||
* @note One MUST only use this for error handling, because it creates
|
||||
* TOCTOU issues. We don't mind if error messages point to out of date
|
||||
* paths (that is a rather trivial TOCTOU --- the error message is best
|
||||
* effort) but for anything else we do.
|
||||
*
|
||||
* @note this function will clobber `errno` (Unix) / "last error"
|
||||
* (Windows), so care must be used to get those error codes, then call
|
||||
* this, then build a `SysError` / `WinError` with the saved error code.
|
||||
*/
|
||||
std::filesystem::path descriptorToPath(Descriptor fd);
|
||||
std::filesystem::path handleToPath(Descriptor handle);
|
||||
|
||||
} // namespace windows
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Open a `Descriptor` with read-only access to the given directory.
|
||||
@@ -244,7 +245,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 std::filesystem::path & path, Sink & sink, bool memory_map = true);
|
||||
void readFile(const Path & path, Sink & sink, bool memory_map = true);
|
||||
|
||||
enum struct FsSync { Yes, No };
|
||||
|
||||
@@ -267,7 +268,8 @@ writeFile(const std::filesystem::path & path, Source & source, mode_t mode = 066
|
||||
return writeFile(path.string(), source, mode, sync);
|
||||
}
|
||||
|
||||
void writeFile(Descriptor fd, std::string_view s, FsSync sync = FsSync::No, const Path * origPath = nullptr);
|
||||
void writeFile(
|
||||
AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode = 0666, FsSync sync = FsSync::No);
|
||||
|
||||
/**
|
||||
* Flush a path's parent directory to disk.
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
include_dirs = [ include_directories('../..') ]
|
||||
|
||||
config_pub_h = configure_file(
|
||||
configuration : configdata_pub,
|
||||
output : 'config.hh',
|
||||
)
|
||||
|
||||
headers = [ config_pub_h ] + files(
|
||||
headers = files(
|
||||
'abstract-setting-to-json.hh',
|
||||
'alignment.hh',
|
||||
'ansicolor.hh',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include <filesystem>
|
||||
#include <ranges>
|
||||
#include <span>
|
||||
|
||||
@@ -266,13 +265,7 @@ std::string percentEncode(std::string_view s, std::string_view keep = "");
|
||||
* paths have no escape sequences --- file names cannot contain a
|
||||
* `/`.
|
||||
*/
|
||||
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);
|
||||
Path renderUrlPathEnsureLegal(const std::vector<std::string> & urlPath);
|
||||
|
||||
/**
|
||||
* Percent encode path. `%2F` for "interior slashes" is the most
|
||||
@@ -354,22 +347,6 @@ 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,12 +2,13 @@
|
||||
///@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();
|
||||
|
||||
@@ -16,8 +16,7 @@ cxx = meson.get_compiler('cpp')
|
||||
|
||||
subdir('nix-meson-build-support/deps-lists')
|
||||
|
||||
configdata_pub = configuration_data()
|
||||
configdata_priv = configuration_data()
|
||||
configdata = configuration_data()
|
||||
|
||||
deps_private_maybe_subproject = []
|
||||
deps_public_maybe_subproject = []
|
||||
@@ -35,15 +34,9 @@ check_funcs = [
|
||||
foreach funcspec : check_funcs
|
||||
define_name = 'HAVE_' + funcspec[0].underscorify().to_upper()
|
||||
define_value = cxx.has_function(funcspec[0]).to_int()
|
||||
configdata_priv.set(define_name, define_value, description : funcspec[1])
|
||||
configdata.set(define_name, define_value, description : funcspec[1])
|
||||
endforeach
|
||||
|
||||
configdata_pub.set(
|
||||
'NIX_UBSAN_ENABLED',
|
||||
('undefined' in get_option('b_sanitize')).to_int(),
|
||||
description : 'Whether nix has been built with UBSan enabled',
|
||||
)
|
||||
|
||||
subdir('nix-meson-build-support/libatomic')
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
@@ -111,7 +104,7 @@ cpuid = dependency(
|
||||
version : '>= 0.7.0',
|
||||
required : cpuid_required,
|
||||
)
|
||||
configdata_priv.set('HAVE_LIBCPUID', cpuid.found().to_int())
|
||||
configdata.set('HAVE_LIBCPUID', cpuid.found().to_int())
|
||||
deps_private += cpuid
|
||||
|
||||
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
|
||||
@@ -120,7 +113,7 @@ deps_public += nlohmann_json
|
||||
cxx = meson.get_compiler('cpp')
|
||||
|
||||
config_priv_h = configure_file(
|
||||
configuration : configdata_priv,
|
||||
configuration : configdata,
|
||||
output : 'util-config-private.hh',
|
||||
)
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ SourceAccessor::DirEntries PosixSourceAccessor::readDirectory(const CanonPath &
|
||||
if (e.code() == std::errc::permission_denied || e.code() == std::errc::operation_not_permitted)
|
||||
return std::nullopt;
|
||||
else
|
||||
throw SystemError(e.code(), "getting status of '%s'", PathFmt(entry.path()));
|
||||
throw;
|
||||
}
|
||||
}();
|
||||
res.emplace(entry.path().filename().string(), type);
|
||||
|
||||
@@ -190,7 +190,24 @@ bool BufferedSource::hasData()
|
||||
|
||||
size_t FdSource::readUnbuffered(char * data, size_t len)
|
||||
{
|
||||
auto n = nix::read(fd, {reinterpret_cast<std::byte *>(data), 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
|
||||
if (n == 0) {
|
||||
_good = false;
|
||||
throw EndOfFile(std::string(*endOfFileError));
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <span>
|
||||
|
||||
#include "util-config-private.hh"
|
||||
@@ -12,19 +13,107 @@
|
||||
|
||||
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;
|
||||
}
|
||||
throw SysError("reading from file");
|
||||
}
|
||||
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;
|
||||
}
|
||||
throw SysError("writing to file");
|
||||
}
|
||||
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:
|
||||
throw SysError("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)
|
||||
{
|
||||
ssize_t n;
|
||||
do {
|
||||
checkInterrupt();
|
||||
n = ::read(fd, buffer.data(), buffer.size());
|
||||
} while (n == -1 && errno == EINTR);
|
||||
ssize_t n = ::read(fd, buffer.data(), buffer.size());
|
||||
if (n == -1)
|
||||
throw SysError("read of %1% bytes", buffer.size());
|
||||
return static_cast<size_t>(n);
|
||||
@@ -32,29 +121,12 @@ 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;
|
||||
do {
|
||||
checkInterrupt();
|
||||
n = pread(fd, buffer.data(), buffer.size(), offset);
|
||||
} while (n == -1 && errno == EINTR);
|
||||
ssize_t n = pread(fd, buffer.data(), buffer.size(), offset);
|
||||
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()
|
||||
@@ -131,17 +203,4 @@ void unix::closeOnExec(int fd)
|
||||
throw SysError("setting close-on-exec flag");
|
||||
}
|
||||
|
||||
void syncDescriptor(Descriptor fd)
|
||||
{
|
||||
int result =
|
||||
#if defined(__APPLE__)
|
||||
::fcntl(fd, F_FULLFSYNC)
|
||||
#else
|
||||
::fsync(fd)
|
||||
#endif
|
||||
;
|
||||
if (result == -1)
|
||||
throw NativeSysError("fsync file descriptor %1%", fd);
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -71,10 +71,8 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
|
||||
if (res < 0) {
|
||||
if (errno == ENOSYS)
|
||||
fchmodat2Unsupported.test_and_set();
|
||||
else {
|
||||
auto savedErrno = errno;
|
||||
throw SysError(savedErrno, "fchmodat2 %s", PathFmt(descriptorToPath(dirFd) / path.rel()));
|
||||
}
|
||||
else
|
||||
throw SysError("fchmodat2 '%s' relative to parent directory", path.rel());
|
||||
} else
|
||||
return;
|
||||
}
|
||||
@@ -82,13 +80,10 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
|
||||
|
||||
#ifdef __linux__
|
||||
AutoCloseFD pathFd = ::openat(dirFd, path.rel_c_str(), O_PATH | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (!pathFd) {
|
||||
auto savedErrno = errno;
|
||||
if (!pathFd)
|
||||
throw SysError(
|
||||
savedErrno,
|
||||
"opening %s to get an O_PATH file descriptor (fchmodat2 is unsupported)",
|
||||
PathFmt(descriptorToPath(dirFd) / path.rel()));
|
||||
}
|
||||
"opening '%s' relative to parent directory to get an O_PATH file descriptor (fchmodat2 is unsupported)",
|
||||
path.rel());
|
||||
|
||||
struct ::stat st;
|
||||
/* Possible since https://github.com/torvalds/linux/commit/55815f70147dcfa3ead5738fd56d3574e2e3c1c2 (3.6) */
|
||||
@@ -96,7 +91,7 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
|
||||
throw SysError("statting '%s' relative to parent directory via O_PATH file descriptor", path.rel());
|
||||
|
||||
if (S_ISLNK(st.st_mode))
|
||||
throw SysError(EOPNOTSUPP, "can't change mode of symlink %s", PathFmt(descriptorToPath(dirFd) / path.rel()));
|
||||
throw SysError(EOPNOTSUPP, "can't change mode of symlink '%s' relative to parent directory", path.rel());
|
||||
|
||||
static std::atomic_flag dontHaveProc{};
|
||||
if (!dontHaveProc.test()) {
|
||||
@@ -106,11 +101,8 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
|
||||
if (int res = ::chmod(selfProcFdPath.c_str(), mode); res == -1) {
|
||||
if (errno == ENOENT)
|
||||
dontHaveProc.test_and_set();
|
||||
else {
|
||||
auto savedErrno = errno;
|
||||
throw SysError(
|
||||
savedErrno, "chmod %s (%s)", selfProcFdPath, PathFmt(descriptorToPath(dirFd) / path.rel()));
|
||||
}
|
||||
else
|
||||
throw SysError("chmod '%s' ('%s' relative to parent directory)", selfProcFdPath, path);
|
||||
} else
|
||||
return;
|
||||
}
|
||||
@@ -130,13 +122,11 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
|
||||
#endif
|
||||
);
|
||||
|
||||
if (res == -1) {
|
||||
auto savedErrno = errno;
|
||||
throw SysError(savedErrno, "fchmodat %s", PathFmt(descriptorToPath(dirFd) / path.rel()));
|
||||
}
|
||||
if (res == -1)
|
||||
throw SysError("fchmodat '%s' relative to parent directory", path.rel());
|
||||
}
|
||||
|
||||
static AutoCloseFD
|
||||
static Descriptor
|
||||
openFileEnsureBeneathNoSymlinksIterative(Descriptor dirFd, const CanonPath & path, int flags, mode_t mode)
|
||||
{
|
||||
AutoCloseFD parentFd;
|
||||
@@ -179,19 +169,19 @@ openFileEnsureBeneathNoSymlinksIterative(Descriptor dirFd, const CanonPath & pat
|
||||
throw SymlinkNotAllowed(path2);
|
||||
}
|
||||
|
||||
return AutoCloseFD{};
|
||||
return INVALID_DESCRIPTOR;
|
||||
}
|
||||
|
||||
parentFd = std::move(parentFd2);
|
||||
}
|
||||
|
||||
AutoCloseFD res = ::openat(getParentFd(), std::string(path.baseName().value()).c_str(), flags | O_NOFOLLOW, mode);
|
||||
if (!res && errno == ELOOP)
|
||||
auto res = ::openat(getParentFd(), std::string(path.baseName().value()).c_str(), flags | O_NOFOLLOW, mode);
|
||||
if (res < 0 && errno == ELOOP)
|
||||
throw SymlinkNotAllowed(path);
|
||||
return res;
|
||||
}
|
||||
|
||||
AutoCloseFD openFileEnsureBeneathNoSymlinks(Descriptor dirFd, const CanonPath & path, int flags, mode_t mode)
|
||||
Descriptor 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 +191,7 @@ AutoCloseFD openFileEnsureBeneathNoSymlinks(Descriptor dirFd, const CanonPath &
|
||||
if (maybeFd) {
|
||||
if (*maybeFd < 0 && errno == ELOOP)
|
||||
throw SymlinkNotAllowed(path);
|
||||
return AutoCloseFD{*maybeFd};
|
||||
return *maybeFd;
|
||||
}
|
||||
#endif
|
||||
return openFileEnsureBeneathNoSymlinksIterative(dirFd, path, flags, mode);
|
||||
@@ -216,10 +206,9 @@ OsString readLinkAt(Descriptor dirFd, const CanonPath & path)
|
||||
checkInterrupt();
|
||||
buf.resize(bufSize);
|
||||
ssize_t rlSize = ::readlinkat(dirFd, path.rel_c_str(), buf.data(), bufSize);
|
||||
if (rlSize == -1) {
|
||||
auto savedErrno = errno;
|
||||
throw SysError(savedErrno, "reading symbolic link %1%", PathFmt(descriptorToPath(dirFd) / path.rel()));
|
||||
} else if (rlSize < bufSize)
|
||||
if (rlSize == -1)
|
||||
throw SysError("reading symbolic link '%1%' relative to parent directory", path.rel());
|
||||
else if (rlSize < bufSize)
|
||||
return {buf.data(), static_cast<std::size_t>(rlSize)};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,31 +46,6 @@ Descriptor openNewFileForWrite(const std::filesystem::path & path, mode_t mode,
|
||||
return open(path.c_str(), flags, mode);
|
||||
}
|
||||
|
||||
std::filesystem::path descriptorToPath(Descriptor fd)
|
||||
{
|
||||
if (fd == STDIN_FILENO)
|
||||
return "<stdin>";
|
||||
if (fd == STDOUT_FILENO)
|
||||
return "<stdout>";
|
||||
if (fd == STDERR_FILENO)
|
||||
return "<stderr>";
|
||||
|
||||
#if defined(__linux__)
|
||||
try {
|
||||
return readLink("/proc/self/fd/" + std::to_string(fd));
|
||||
} catch (SystemError &) {
|
||||
}
|
||||
#elif HAVE_F_GETPATH
|
||||
/* F_GETPATH requires PATH_MAX buffer per POSIX */
|
||||
char buf[PATH_MAX];
|
||||
if (fcntl(fd, F_GETPATH, buf) != -1)
|
||||
return buf;
|
||||
#endif
|
||||
|
||||
/* Fallback for unknown fd or unsupported platform */
|
||||
return "<fd " + std::to_string(fd) + ">";
|
||||
}
|
||||
|
||||
std::filesystem::path defaultTempDir()
|
||||
{
|
||||
return getEnvNonEmpty("TMPDIR").value_or("/tmp");
|
||||
|
||||
@@ -8,12 +8,6 @@ configdata_unix.set(
|
||||
description : 'Optionally used for changing the files and symlinks.',
|
||||
)
|
||||
|
||||
configdata_unix.set(
|
||||
'HAVE_F_GETPATH',
|
||||
cxx.has_header_symbol('fcntl.h', 'F_GETPATH').to_int(),
|
||||
description : 'Optionally used for getting the path of a file descriptor (macOS).',
|
||||
)
|
||||
|
||||
# Check for each of these functions, and create a define like `#define
|
||||
# HAVE_CLOSE_RANGE 1`.
|
||||
check_funcs_unix = [
|
||||
|
||||
@@ -321,7 +321,7 @@ std::string encodeQuery(const StringMap & ss)
|
||||
return res;
|
||||
}
|
||||
|
||||
Path renderUrlPathEnsureLegal(std::span<const std::string> urlPath)
|
||||
Path renderUrlPathEnsureLegal(const std::vector<std::string> & urlPath)
|
||||
{
|
||||
for (const auto & comp : urlPath) {
|
||||
/* This is only really valid for UNIX. Windows has more restrictions. */
|
||||
@@ -334,11 +334,6 @@ Path renderUrlPathEnsureLegal(std::span<const std::string> urlPath)
|
||||
}
|
||||
}
|
||||
|
||||
return renderUrlPathNoPctEncoding(urlPath);
|
||||
}
|
||||
|
||||
std::string renderUrlPathNoPctEncoding(std::span<const std::string> urlPath)
|
||||
{
|
||||
return concatStringsSep("/", urlPath);
|
||||
}
|
||||
|
||||
@@ -346,7 +341,7 @@ std::string ParsedURL::renderPath(bool encode) const
|
||||
{
|
||||
if (encode)
|
||||
return encodeUrlPath(path);
|
||||
return renderUrlPathNoPctEncoding(path);
|
||||
return concatStringsSep("/", path);
|
||||
}
|
||||
|
||||
std::string ParsedURL::renderAuthorityAndPath() const
|
||||
@@ -457,61 +452,6 @@ 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,7 +1,6 @@
|
||||
#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
|
||||
@@ -14,7 +13,7 @@ namespace nix {
|
||||
|
||||
std::filesystem::path getCacheDir()
|
||||
{
|
||||
auto dir = getEnvOs(OS_STR("NIX_CACHE_HOME"));
|
||||
auto dir = getEnv("NIX_CACHE_HOME");
|
||||
if (dir)
|
||||
return *dir;
|
||||
#ifndef _WIN32
|
||||
@@ -26,7 +25,7 @@ std::filesystem::path getCacheDir()
|
||||
|
||||
std::filesystem::path getConfigDir()
|
||||
{
|
||||
auto dir = getEnvOs(OS_STR("NIX_CONFIG_HOME"));
|
||||
auto dir = getEnv("NIX_CONFIG_HOME");
|
||||
if (dir)
|
||||
return *dir;
|
||||
#ifndef _WIN32
|
||||
@@ -52,7 +51,7 @@ std::vector<std::filesystem::path> getConfigDirs()
|
||||
|
||||
std::filesystem::path getDataDir()
|
||||
{
|
||||
auto dir = getEnvOs(OS_STR("NIX_DATA_HOME"));
|
||||
auto dir = getEnv("NIX_DATA_HOME");
|
||||
if (dir)
|
||||
return *dir;
|
||||
#ifndef _WIN32
|
||||
@@ -64,7 +63,7 @@ std::filesystem::path getDataDir()
|
||||
|
||||
std::filesystem::path getStateDir()
|
||||
{
|
||||
auto dir = getEnvOs(OS_STR("NIX_STATE_HOME"));
|
||||
auto dir = getEnv("NIX_STATE_HOME");
|
||||
if (dir)
|
||||
return *dir;
|
||||
#ifndef _WIN32
|
||||
@@ -85,10 +84,9 @@ std::string expandTilde(std::string_view path)
|
||||
{
|
||||
// TODO: expand ~user ?
|
||||
auto tilde = path.substr(0, 2);
|
||||
if (tilde == "~/" || tilde == "~") {
|
||||
auto suffix = path.size() >= 2 ? std::string(path.substr(2)) : std::string{};
|
||||
return (getHome() / suffix).string();
|
||||
} else
|
||||
if (tilde == "~/" || tilde == "~")
|
||||
return getHome().string() + std::string(path.substr(1));
|
||||
else
|
||||
return std::string(path);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,56 +19,82 @@ using namespace nix::windows;
|
||||
std::make_unsigned_t<off_t> getFileSize(Descriptor fd)
|
||||
{
|
||||
LARGE_INTEGER li;
|
||||
if (!GetFileSizeEx(fd, &li)) {
|
||||
auto lastError = GetLastError();
|
||||
throw WinError(lastError, "getting size of file %s", PathFmt(descriptorToPath(fd)));
|
||||
}
|
||||
if (!GetFileSizeEx(fd, &li))
|
||||
throw WinError("GetFileSizeEx");
|
||||
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 `handleToPath` will overwrite the last error.
|
||||
auto lastError = GetLastError();
|
||||
auto path = handleToPath(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)) {
|
||||
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)));
|
||||
}
|
||||
if (!ReadFile(fd, buffer.data(), static_cast<DWORD>(buffer.size()), &n, NULL))
|
||||
throw WinError("ReadFile of %1% bytes", buffer.size());
|
||||
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)) {
|
||||
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)));
|
||||
}
|
||||
if (!ReadFile(fd, buffer.data(), static_cast<DWORD>(buffer.size()), &n, &ov))
|
||||
throw WinError("ReadFile of %1% bytes at offset %2%", buffer.size(), offset);
|
||||
return static_cast<size_t>(n);
|
||||
}
|
||||
|
||||
@@ -122,12 +148,4 @@ 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)
|
||||
*/
|
||||
AutoCloseFD ntOpenAt(
|
||||
HANDLE ntOpenAt(
|
||||
Descriptor dirFd,
|
||||
std::wstring_view pathComponent,
|
||||
ACCESS_MASK desiredAccess,
|
||||
@@ -73,7 +73,7 @@ AutoCloseFD ntOpenAt(
|
||||
throw WinError(
|
||||
RtlNtStatusToDosError(status), "opening %s relative to directory handle", PathFmt(pathComponent));
|
||||
|
||||
return AutoCloseFD{h};
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,9 +81,9 @@ AutoCloseFD ntOpenAt(
|
||||
*
|
||||
* @param dirFd Directory handle to open relative to
|
||||
* @param path Relative path to the symlink
|
||||
* @return Handle to the symlink
|
||||
* @return Handle to the symlink (caller must close)
|
||||
*/
|
||||
AutoCloseFD openSymlinkAt(Descriptor dirFd, const CanonPath & path)
|
||||
HANDLE openSymlinkAt(Descriptor dirFd, const CanonPath & path)
|
||||
{
|
||||
assert(!path.isRoot());
|
||||
assert(!path.rel().starts_with('/')); /* Just in case the invariant is somehow broken. */
|
||||
@@ -156,13 +156,13 @@ OsString readSymlinkTarget(HANDLE linkHandle)
|
||||
size_t path_buf_offset = offsetof(ReparseDataBuffer, SymbolicLinkReparseBuffer.PathBuffer[0]);
|
||||
|
||||
if (out < path_buf_offset) {
|
||||
auto fullPath = descriptorToPath(linkHandle);
|
||||
auto fullPath = handleToPath(linkHandle);
|
||||
throw WinError(
|
||||
DWORD{ERROR_REPARSE_TAG_INVALID}, "invalid reparse data for %d:%s", linkHandle, PathFmt(fullPath));
|
||||
}
|
||||
|
||||
if (reparse->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
|
||||
auto fullPath = descriptorToPath(linkHandle);
|
||||
auto fullPath = handleToPath(linkHandle);
|
||||
throw WinError(DWORD{ERROR_REPARSE_TAG_INVALID}, "not a symlink: %d:%s", linkHandle, PathFmt(fullPath));
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ OsString readSymlinkTarget(HANDLE linkHandle)
|
||||
}
|
||||
|
||||
if (path_buf_offset + name_offset + name_length > out) {
|
||||
auto fullPath = descriptorToPath(linkHandle);
|
||||
auto fullPath = handleToPath(linkHandle);
|
||||
throw WinError(
|
||||
DWORD{ERROR_REPARSE_TAG_INVALID}, "invalid symlink data for %d:%s", linkHandle, PathFmt(fullPath));
|
||||
}
|
||||
@@ -210,7 +210,7 @@ bool isReparsePoint(HANDLE handle)
|
||||
|
||||
} // namespace windows
|
||||
|
||||
AutoCloseFD openFileEnsureBeneathNoSymlinks(
|
||||
Descriptor openFileEnsureBeneathNoSymlinks(
|
||||
Descriptor dirFd, const CanonPath & path, ACCESS_MASK desiredAccess, ULONG createOptions, ULONG createDisposition)
|
||||
{
|
||||
assert(!path.isRoot());
|
||||
@@ -233,8 +233,9 @@ AutoCloseFD 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 testHandle =
|
||||
auto testFd =
|
||||
ntOpenAt(getParentFd(), component, FILE_READ_ATTRIBUTES | SYNCHRONIZE, FILE_OPEN_REPARSE_POINT);
|
||||
AutoCloseFD testHandle(testFd);
|
||||
if (isReparsePoint(testHandle.get()))
|
||||
throw SymlinkNotAllowed(pathForError);
|
||||
} catch (SymlinkNotAllowed &) {
|
||||
@@ -277,7 +278,7 @@ AutoCloseFD openFileEnsureBeneathNoSymlinks(
|
||||
/* Now open the final component with requested flags */
|
||||
std::wstring finalComponent = string_to_os_string(std::string(path.baseName().value()));
|
||||
|
||||
AutoCloseFD finalHandle;
|
||||
HANDLE finalHandle;
|
||||
try {
|
||||
finalHandle = ntOpenAt(
|
||||
getParentFd(),
|
||||
@@ -294,7 +295,7 @@ AutoCloseFD openFileEnsureBeneathNoSymlinks(
|
||||
}
|
||||
|
||||
/* Final check: did we accidentally open a symlink? */
|
||||
if (isReparsePoint(finalHandle.get()))
|
||||
if (isReparsePoint(finalHandle))
|
||||
throw SymlinkNotAllowed(path);
|
||||
|
||||
return finalHandle;
|
||||
|
||||
@@ -83,7 +83,7 @@ void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed)
|
||||
deletePath(path);
|
||||
}
|
||||
|
||||
std::filesystem::path descriptorToPath(Descriptor handle)
|
||||
std::filesystem::path windows::handleToPath(HANDLE handle)
|
||||
{
|
||||
std::vector<wchar_t> buf(0x100);
|
||||
DWORD dw = GetFinalPathNameByHandleW(handle, buf.data(), buf.size(), FILE_NAME_OPENED);
|
||||
|
||||
@@ -5,6 +5,5 @@ include_dirs += include_directories('../..')
|
||||
headers += files(
|
||||
'signals-impl.hh',
|
||||
'windows-async-pipe.hh',
|
||||
'windows-environment.hh',
|
||||
'windows-known-folders.hh',
|
||||
)
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
///@file
|
||||
|
||||
namespace nix::windows {
|
||||
|
||||
/**
|
||||
* Check if the current process is running under Wine.
|
||||
*/
|
||||
bool isWine();
|
||||
|
||||
} // namespace nix::windows
|
||||
@@ -11,7 +11,6 @@ sources += files(
|
||||
'processes.cc',
|
||||
'users.cc',
|
||||
'windows-async-pipe.cc',
|
||||
'windows-environment.cc',
|
||||
'windows-error.cc',
|
||||
)
|
||||
|
||||
|
||||
@@ -37,9 +37,9 @@ std::string getUserName()
|
||||
std::filesystem::path getHome()
|
||||
{
|
||||
static std::filesystem::path homeDir = []() {
|
||||
std::filesystem::path homeDir = getEnvOs(L"USERPROFILE").value_or(L"C:\\Users\\Default");
|
||||
std::filesystem::path homeDir = getEnv("USERPROFILE").value_or("C:\\Users\\Default");
|
||||
assert(!homeDir.empty());
|
||||
return std::filesystem::path{canonPath(homeDir.string())};
|
||||
return canonPath(homeDir.string());
|
||||
}();
|
||||
return homeDir;
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
#include "nix/util/windows-environment.hh"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
namespace nix::windows {
|
||||
|
||||
bool isWine()
|
||||
{
|
||||
HMODULE hntdll = GetModuleHandle("ntdll.dll");
|
||||
return GetProcAddress(hntdll, "wine_get_version") != nullptr;
|
||||
}
|
||||
|
||||
} // namespace nix::windows
|
||||
@@ -7,6 +7,10 @@
|
||||
#include "nix/expr/eval-inline.hh"
|
||||
#include "nix/store/globals.hh"
|
||||
|
||||
namespace nix::fs {
|
||||
using namespace std::filesystem;
|
||||
}
|
||||
|
||||
using namespace nix;
|
||||
|
||||
struct CmdBundle : InstallableValueCommand
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
#include "nix/util/executable-path.hh"
|
||||
#include "nix/store/globals.hh"
|
||||
|
||||
namespace nix::fs {
|
||||
using namespace std::filesystem;
|
||||
}
|
||||
|
||||
using namespace nix;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
|
||||
#include "nix/util/strings.hh"
|
||||
|
||||
namespace nix::fs {
|
||||
using namespace std::filesystem;
|
||||
}
|
||||
|
||||
using namespace nix;
|
||||
|
||||
struct DevelopSettings : Config
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
|
||||
using namespace nix;
|
||||
|
||||
namespace nix::fs {
|
||||
using namespace std::filesystem;
|
||||
}
|
||||
|
||||
struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption
|
||||
{
|
||||
bool raw = false;
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
// FIXME is this supposed to be private or not?
|
||||
#include "flake-command.hh"
|
||||
|
||||
namespace nix::fs {
|
||||
using namespace std::filesystem;
|
||||
}
|
||||
|
||||
using namespace nix;
|
||||
using namespace nix::flake;
|
||||
using json = nlohmann::json;
|
||||
|
||||
@@ -247,12 +247,11 @@ static void showHelp(std::vector<std::string> subcommand, NixArgs & toplevel)
|
||||
|
||||
evalSettings.restrictEval = true;
|
||||
evalSettings.pureEval = true;
|
||||
auto statePtr = std::make_shared<EvalState>(
|
||||
LookupPath{},
|
||||
EvalState state(
|
||||
{},
|
||||
openStore(StoreReference{.variant = StoreReference::Specified{.scheme = "dummy"}}),
|
||||
fetchSettings,
|
||||
evalSettings);
|
||||
auto & state = *statePtr;
|
||||
|
||||
auto vGenerateManpage = state.allocValue();
|
||||
state.eval(
|
||||
@@ -455,12 +454,11 @@ void mainWrapped(int argc, char ** argv)
|
||||
Xp::FetchTree,
|
||||
};
|
||||
evalSettings.pureEval = false;
|
||||
auto statePtr = std::make_shared<EvalState>(
|
||||
LookupPath{},
|
||||
EvalState state(
|
||||
{},
|
||||
openStore(StoreReference{.variant = StoreReference::Specified{.scheme = "dummy"}}),
|
||||
fetchSettings,
|
||||
evalSettings);
|
||||
auto & state = *statePtr;
|
||||
auto builtinsJson = nlohmann::json::object();
|
||||
for (auto & builtinPtr : state.getBuiltins().attrs()->lexicographicOrder(state.symbols)) {
|
||||
auto & builtin = *builtinPtr;
|
||||
@@ -566,16 +564,12 @@ void mainWrapped(int argc, char ** argv)
|
||||
fileTransferSettings.tries = 0;
|
||||
if (!fileTransferSettings.connectTimeout.overridden)
|
||||
fileTransferSettings.connectTimeout = 1;
|
||||
auto & ttlMeta = settings.getNarInfoDiskCacheSettings().ttlMeta;
|
||||
if (!ttlMeta.overridden)
|
||||
ttlMeta = std::numeric_limits<unsigned int>::max();
|
||||
}
|
||||
|
||||
if (args.refresh) {
|
||||
fetchSettings.tarballTtl = 0;
|
||||
settings.getNarInfoDiskCacheSettings().ttlNegative = 0;
|
||||
settings.getNarInfoDiskCacheSettings().ttlPositive = 0;
|
||||
settings.getNarInfoDiskCacheSettings().ttlMeta = 0;
|
||||
}
|
||||
|
||||
if (args.command->second->forceImpureByDefault() && !evalSettings.pureEval.overridden) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user