Compare commits

..

3 Commits

Author SHA1 Message Date
Eelco Dolstra
490cb842cc Add release note 2026-02-18 22:50:37 +01:00
Eelco Dolstra
6992698ac5 builtins.getFlake: Support path values
This allows doing `builtins.getFlake ./subflake` instead of ugly hacks.
2026-02-18 22:12:09 +01:00
Eelco Dolstra
9868310d6f Add test for builtins.getFlake 2026-02-18 21:58:36 +01:00
129 changed files with 570 additions and 1378 deletions

View File

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

View File

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

View File

@@ -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);
```

View File

@@ -0,0 +1,6 @@
---
synopsis: "`builtins.getFlake` now supports path values"
prs: [15290]
---
`builtins.getFlake` now accepts path values in addition to flakerefs, allowing you to write `builtins.getFlake ./subflake` instead of having to use ugly workarounds to construct a pure flakeref.

View File

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

View File

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

View File

@@ -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),
};
});
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();

View File

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

View File

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

View File

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

View File

@@ -11,7 +11,7 @@
namespace nix::eval_cache {
CachedEvalError::CachedEvalError(ref<AttrCursor> cursor, Symbol attr)
: CloneableError(cursor->root->state, "cached failure of attribute '%s'", cursor->getAttrPathStr(attr))
: EvalError(cursor->root->state, "cached failure of attribute '%s'", cursor->getAttrPathStr(attr))
, cursor(cursor)
, attr(attr)
{
@@ -70,7 +70,7 @@ struct AttrDb
{
auto state(_state->lock());
auto cacheDir = getCacheDir() / "eval-cache-v6";
auto cacheDir = std::filesystem::path(getCacheDir()) / "eval-cache-v6";
createDirs(cacheDir);
auto dbPath = cacheDir / (fingerprint.to_string(HashFormat::Base16, false) + ".sqlite");

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ namespace nix::eval_cache {
struct AttrDb;
class AttrCursor;
struct CachedEvalError : CloneableError<CachedEvalError, EvalError>
struct CachedEvalError : EvalError
{
const ref<AttrCursor> cursor;
const Symbol attr;

View File

@@ -18,7 +18,7 @@ class EvalErrorBuilder;
*
* Most subclasses should inherit from `EvalError` instead of this class.
*/
class EvalBaseError : public CloneableError<EvalBaseError, Error>
class EvalBaseError : public Error
{
template<class T>
friend class EvalErrorBuilder;
@@ -26,14 +26,14 @@ public:
EvalState & state;
EvalBaseError(EvalState & state, ErrorInfo && errorInfo)
: CloneableError(errorInfo)
: Error(errorInfo)
, state(state)
{
}
template<typename... Args>
explicit EvalBaseError(EvalState & state, const std::string & formatString, const Args &... formatArgs)
: CloneableError(formatString, formatArgs...)
: Error(formatString, formatArgs...)
, state(state)
{
}
@@ -60,31 +60,23 @@ MakeError(InfiniteRecursionError, EvalError);
* Inherits from EvalBaseError (not EvalError) because resource exhaustion
* should not be cached.
*/
struct StackOverflowError : public CloneableError<StackOverflowError, EvalBaseError>
struct StackOverflowError : public EvalBaseError
{
StackOverflowError(EvalState & state)
: CloneableError(state, "stack overflow; max-call-depth exceeded")
: EvalBaseError(state, "stack overflow; max-call-depth exceeded")
{
}
};
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>
struct InvalidPathError : public EvalError
{
public:
Path path;
InvalidPathError(EvalState & state, const Path & path)
: CloneableError(state, "path '%s' is not valid", path)
: EvalError(state, "path '%s' is not valid", path)
{
}
};

View File

@@ -33,9 +33,6 @@ using gc_allocator = std::allocator<T>;
struct gc
{};
struct gc_cleanup
{};
#endif
namespace nix {

View File

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

View File

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

View File

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

View File

@@ -9,14 +9,14 @@
namespace nix {
class BadNixStringContextElem final : public CloneableError<BadNixStringContextElem, Error>
class BadNixStringContextElem : public Error
{
public:
std::string_view raw;
template<typename... Args>
BadNixStringContextElem(std::string_view raw_, const Args &... args)
: CloneableError("")
: Error("")
{
raw = raw_;
auto hf = HintFmt(args...);

View File

@@ -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();
}
}
@@ -1313,12 +1312,11 @@ static void prim_warn(EvalState & state, const PosIdx pos, Value ** args, Value
state.forceString(*args[0], pos, "while evaluating the first argument; the message passed to builtins.warn");
{
ErrorInfo info{
.level = lvlWarn,
.msg = HintFmt(std::string(msgStr)),
.pos = state.positions[pos],
.isFromExpr = true,
};
BaseError msg(std::string{msgStr});
msg.atPos(state.positions[pos]);
auto info = msg.info();
info.level = lvlWarn;
info.isFromExpr = true;
logWarning(info);
}

View File

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

View File

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

View File

@@ -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();
}

View File

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

View File

@@ -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();

View File

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

View File

@@ -74,11 +74,11 @@ namespace nix {
struct GitSourceAccessor;
struct GitError final : public CloneableError<GitError, Error>
struct GitError : public Error
{
template<typename... Ts>
GitError(const git_error & error, Ts &&... args)
: CloneableError("")
: Error("")
{
auto hf = HintFmt(std::forward<Ts>(args)...);
err.msg = HintFmt("%1%: %2% (libgit2 error code = %3%)", Uncolored(hf.str()), error.message, error.klass);

View File

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

View File

@@ -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";
}

View File

@@ -35,35 +35,39 @@ namespace nix::flake::primops {
PrimOp getFlake(const Settings & settings)
{
auto prim_getFlake = [&settings](EvalState & state, const PosIdx pos, Value ** args, Value & v) {
std::string flakeRefS(
state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
auto flakeRef = nix::parseFlakeRef(state.fetchSettings, flakeRefS, {}, true);
if (state.settings.pureEval && !flakeRef.input.isLocked(state.fetchSettings))
throw Error(
"cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)",
flakeRefS,
state.positions[pos]);
state.forceValue(*args[0], pos);
callFlake(
state,
lockFlake(
settings,
state,
flakeRef,
LockFlags{
.updateLockFile = false,
.writeLockFile = false,
.useRegistries = !state.settings.pureEval && settings.useRegistries,
.allowUnlocked = !state.settings.pureEval,
}),
v);
LockFlags lockFlags{
.updateLockFile = false,
.writeLockFile = false,
.useRegistries = !state.settings.pureEval && settings.useRegistries,
.allowUnlocked = !state.settings.pureEval,
};
if (args[0]->type() == nPath) {
auto path = state.realisePath(pos, *args[0]);
callFlake(state, lockFlake(settings, state, path, lockFlags), v);
} else {
NixStringContext context;
std::string flakeRefS(
state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
auto flakeRef = nix::parseFlakeRef(state.fetchSettings, flakeRefS, {}, true);
if (state.settings.pureEval && !flakeRef.input.isLocked(state.fetchSettings))
throw Error(
"cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)",
flakeRefS,
state.positions[pos]);
callFlake(state, lockFlake(settings, state, flakeRef, lockFlags), v);
}
};
return PrimOp{
.name = "__getFlake",
.args = {"args"},
.doc = R"(
Fetch a flake from a flake reference, and return its output attributes and some metadata. For example:
Fetch a flake from a flake reference or a path, and return its output attributes and some metadata. For example:
```nix
(builtins.getFlake "nix/55bc52401966fbffa525c574c14f67b00bc4fb3a").packages.x86_64-linux.nix

View File

@@ -416,10 +416,8 @@ static LockFile readLockFile(const fetchers::Settings & fetchSettings, const Sou
: LockFile();
}
/* Compute an in-memory lock file for the specified top-level flake,
and optionally write it to file, if the flake is writable. */
LockedFlake
lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef, const LockFlags & lockFlags)
LockedFlake lockFlake(
const Settings & settings, EvalState & state, const FlakeRef & topRef, const LockFlags & lockFlags, Flake flake)
{
experimentalFeatureSettings.require(Xp::Flakes);
@@ -427,8 +425,6 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
auto useRegistriesTop = useRegistries ? fetchers::UseRegistries::All : fetchers::UseRegistries::No;
auto useRegistriesInputs = useRegistries ? fetchers::UseRegistries::Limited : fetchers::UseRegistries::No;
auto flake = getFlake(state, topRef, useRegistriesTop, {});
if (lockFlags.applyNixConfig) {
flake.config.apply(settings);
state.store->setOptions();
@@ -908,6 +904,22 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
}
}
LockedFlake
lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef, const LockFlags & lockFlags)
{
auto useRegistries = lockFlags.useRegistries.value_or(settings.useRegistries);
auto useRegistriesTop = useRegistries ? fetchers::UseRegistries::All : fetchers::UseRegistries::No;
return lockFlake(settings, state, topRef, lockFlags, getFlake(state, topRef, useRegistriesTop, {}));
}
LockedFlake
lockFlake(const Settings & settings, EvalState & state, const SourcePath & flakeDir, const LockFlags & lockFlags)
{
/* We need a fake flakeref to put in the `Flake` struct, but it's not used for anything. */
auto fakeRef = parseFlakeRef(state.fetchSettings, "flake:get-flake");
return lockFlake(settings, state, fakeRef, lockFlags, readFlake(state, fakeRef, fakeRef, fakeRef, flakeDir, {}));
}
static ref<SourceAccessor> makeInternalFS()
{
auto internalFS = make_ref<MemorySourceAccessor>(MemorySourceAccessor{});

View File

@@ -214,9 +214,16 @@ struct LockFlags
std::set<NonEmptyInputAttrPath> inputUpdates;
};
/*
* Compute an in-memory lock file for the specified top-level flake, and optionally write it to file, if the flake is
* writable.
*/
LockedFlake
lockFlake(const Settings & settings, EvalState & state, const FlakeRef & flakeRef, const LockFlags & lockFlags);
LockedFlake
lockFlake(const Settings & settings, EvalState & state, const SourcePath & flakeDir, const LockFlags & lockFlags);
void callFlake(EvalState & state, const LockedFlake & lockedFlake, Value & v);
/**

View File

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

View File

@@ -1 +0,0 @@
C:\foo\bar\baz?trusted=true

View File

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

View File

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

View File

@@ -28,7 +28,7 @@
namespace nix {
AwsAuthError::AwsAuthError(int errorCode)
: CloneableError("AWS authentication error: '%s' (%d)", aws_error_str(errorCode), errorCode)
: Error("AWS authentication error: '%s' (%d)", aws_error_str(errorCode), errorCode)
, errorCode(errorCode)
{
}

View File

@@ -1,11 +1,11 @@
#include "nix/store/build/goal.hh"
#include "nix/store/build/worker.hh"
#include "nix/store/worker-settings.hh"
#include "nix/store/globals.hh"
namespace nix {
TimedOut::TimedOut(time_t maxDuration)
: CloneableError(BuildResult::Failure::TimedOut, "timed out after %1% seconds", maxDuration)
: BuildError(BuildResult::Failure::TimedOut, "timed out after %1% seconds", maxDuration)
, maxDuration(maxDuration)
{
}

View File

@@ -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();

View File

@@ -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());

View File

@@ -60,12 +60,12 @@ namespace {
using curlSList = std::unique_ptr<::curl_slist, decltype([](::curl_slist * list) { ::curl_slist_free_all(list); })>;
using curlMulti = std::unique_ptr<::CURLM, decltype([](::CURLM * multi) { ::curl_multi_cleanup(multi); })>;
struct curlMultiError final : CloneableError<curlMultiError, Error>
struct curlMultiError : Error
{
::CURLMcode code;
curlMultiError(::CURLMcode code)
: CloneableError{"unexpected curl multi error: %s", ::curl_multi_strerror(code)}
: Error{"unexpected curl multi error: %s", ::curl_multi_strerror(code)}
{
assert(code != CURLM_OK);
}
@@ -1212,7 +1212,7 @@ void FileTransfer::download(
template<typename... Args>
FileTransferError::FileTransferError(
FileTransfer::Error error, std::optional<std::string> response, const Args &... args)
: CloneableError(args...)
: Error(args...)
, error(error)
, response(response)
{

View File

@@ -34,13 +34,12 @@ struct AwsCredentials
}
};
class AwsAuthError final : public CloneableError<AwsAuthError, Error>
class AwsAuthError : public Error
{
std::optional<int> errorCode;
public:
using CloneableError::CloneableError;
using Error::Error;
AwsAuthError(int errorCode);
std::optional<int> getErrorCode() const

View File

@@ -58,7 +58,7 @@ enum struct BuildResultFailureStatus : uint8_t {
* This is both an exception type (inherits from Error) and serves as
* the failure variant in BuildResult::inner.
*/
struct BuildError : public CloneableError<BuildError, Error>
struct BuildError : public Error
{
using Status = BuildResultFailureStatus;
using enum Status;
@@ -80,7 +80,7 @@ public:
*/
template<typename... Args>
BuildError(Status status, const Args &... args)
: CloneableError(args...)
: Error(args...)
, status{status}
{
}
@@ -97,7 +97,7 @@ public:
* Also used for deserialization.
*/
BuildError(Args args)
: CloneableError(std::move(args.msg))
: Error(std::move(args.msg))
, status{args.status}
, isNonDeterministic{args.isNonDeterministic}
@@ -108,7 +108,7 @@ public:
* Default constructor for deserialization.
*/
BuildError()
: CloneableError("")
: Error("")
{
}

View File

@@ -20,14 +20,14 @@ namespace nix {
* Denotes a build failure that stemmed from the builder exiting with a
* failing exist status.
*/
struct BuilderFailureError final : CloneableError<BuilderFailureError, BuildError>
struct BuilderFailureError : BuildError
{
int builderStatus;
std::string extraMsgAfter;
BuilderFailureError(BuildResult::Failure::Status status, int builderStatus, std::string extraMsgAfter)
: CloneableError{
: BuildError{
status,
/* No message for now, because the caller will make for
us, with extra context */

View File

@@ -10,7 +10,7 @@
namespace nix {
struct TimedOut final : CloneableError<TimedOut, BuildError>
struct TimedOut : BuildError
{
time_t maxDuration;

View File

@@ -22,7 +22,7 @@ struct Package
}
};
class BuildEnvFileConflictError final : public CloneableError<BuildEnvFileConflictError, Error>
class BuildEnvFileConflictError : public Error
{
public:
const Path fileA;
@@ -30,7 +30,7 @@ public:
int priority;
BuildEnvFileConflictError(const Path fileA, const Path fileB, int priority)
: CloneableError(
: Error(
"Unable to build profile. There is a conflict for the following files:\n"
"\n"
" %1%\n"

View File

@@ -403,7 +403,7 @@ ref<FileTransfer> getFileTransfer();
*/
ref<FileTransfer> makeFileTransfer(const FileTransferSettings & settings = fileTransferSettings);
class FileTransferError final : public CloneableError<FileTransferError, Error>
class FileTransferError : public Error
{
public:
FileTransfer::Error error;

View File

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

View File

@@ -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()
{

View File

@@ -146,7 +146,7 @@ struct RealisedPath
auto operator<=>(const RealisedPath &) const = default;
};
class MissingRealisation final : public CloneableError<MissingRealisation, Error>
class MissingRealisation : public Error
{
public:
MissingRealisation(DrvOutput & outputId)
@@ -155,7 +155,7 @@ public:
}
MissingRealisation(std::string_view drv, OutputName outputName)
: CloneableError(
: Error(
"cannot operate on output '%s' of the "
"unbuilt derivation '%s'",
outputName,

View File

@@ -166,7 +166,7 @@ struct SQLiteTxn
~SQLiteTxn();
};
struct SQLiteError : CloneableError<SQLiteError, Error>
struct SQLiteError : Error
{
std::string path;
std::string errMsg;

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ namespace nix {
SQLiteError::SQLiteError(
const char * path, const char * errMsg, int errNo, int extendedErrNo, int offset, HintFmt && hf)
: CloneableError("")
: Error("")
, path(path)
, errMsg(errMsg)
, errNo(errNo)

View File

@@ -18,11 +18,11 @@ static std::string parsePublicHostKey(std::string_view host, std::string_view ss
}
}
class InvalidSSHAuthority final : public CloneableError<InvalidSSHAuthority, Error>
class InvalidSSHAuthority : public Error
{
public:
InvalidSSHAuthority(const ParsedURL::Authority & authority, std::string_view reason)
: CloneableError("invalid SSH authority: '%s': %s", authority.to_string(), reason)
: Error("invalid SSH authority: '%s': %s", authority.to_string(), reason)
{
}
};
@@ -98,7 +98,7 @@ void SSHMaster::addCommonSSHOpts(Strings & args)
args.insert(args.end(), {"-i", keyFile});
if (!sshPublicHostKey.empty()) {
std::filesystem::path fileName = tmpDir->path() / "host-key";
writeFile(fileName, authority.host + " " + sshPublicHostKey + "\n");
writeFile(fileName.string(), authority.host + " " + sshPublicHostKey + "\n");
args.insert(args.end(), {"-oUserKnownHostsFile=" + fileName.string()});
}
if (compress)

View File

@@ -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());
}

View File

@@ -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),
};

View File

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

View File

@@ -55,10 +55,10 @@
namespace nix {
struct NotDeterministic final : CloneableError<NotDeterministic, BuildError>
struct NotDeterministic : BuildError
{
NotDeterministic(auto &&... args)
: CloneableError(BuildResult::Failure::NotDeterministic, args...)
: BuildError(BuildResult::Failure::NotDeterministic, args...)
{
isNonDeterministic = true;
}
@@ -1269,7 +1269,7 @@ void DerivationBuilderImpl::writeBuilderFile(const std::string & name, std::stri
openat(tmpDirFd.get(), name.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC | O_EXCL | O_NOFOLLOW, 0666)};
if (!fd)
throw SysError("creating file %s", PathFmt(path));
writeFile(fd.get(), contents);
writeFile(fd, path, contents);
chownToBuilder(fd.get(), path);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,7 +316,6 @@ TEST(chmodIfNeeded, works)
}
}
}
#endif
TEST(chmodIfNeeded, nonexistent)
{

View File

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

View File

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

View File

@@ -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);
}
}

View File

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

View File

@@ -378,7 +378,7 @@ std::set<ExperimentalFeature> parseFeatures(const StringSet & rawFeatures)
}
MissingExperimentalFeature::MissingExperimentalFeature(ExperimentalFeature feature, std::string reason)
: CloneableError(
: Error(
"experimental Nix feature '%1%' is disabled%2%; add '--extra-experimental-features %1%' to enable it",
showExperimentalFeature(feature),
Uncolored(optionalBracket(" (", reason, ")")))

View File

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

View File

@@ -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>
@@ -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))
@@ -307,27 +305,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 +340,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;
}
}

View File

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

View File

@@ -191,8 +191,6 @@ public:
err.pos = pos;
}
bool hasPos() const;
void pushTrace(Trace trace)
{
err.traces.push_front(trace);
@@ -229,31 +227,13 @@ public:
{
return err;
};
[[noreturn]] virtual void throwClone() const = 0;
};
template<typename Derived, typename Base>
class CloneableError : public Base
{
public:
using Base::Base;
/**
* Rethrow a copy of this exception. Useful when the exception can get
* modified when appending traces.
*/
[[noreturn]] void throwClone() const override
{
throw Derived(static_cast<const Derived &>(*this));
}
};
#define MakeError(newClass, superClass) \
class newClass : public CloneableError<newClass, superClass> \
{ \
public: \
using CloneableError<newClass, superClass>::CloneableError; \
#define MakeError(newClass, superClass) \
class newClass : public superClass \
{ \
public: \
using superClass::superClass; \
}
MakeError(Error, BaseError);
@@ -265,7 +245,7 @@ MakeError(UnimplementedError, Error);
* std::error_code. Use when you want to catch and check an error condition like
* no_such_file_or_directory (ENOENT) without ifdefs.
*/
class SystemError : public CloneableError<SystemError, Error>
class SystemError : public Error
{
std::error_code errorCode;
std::string errorDetails;
@@ -285,7 +265,7 @@ protected:
*/
template<typename... Args>
SystemError(Disambig, std::error_code errorCode, std::string_view errorDetails, Args &&... args)
: CloneableError("")
: Error("")
, errorCode(errorCode)
, errorDetails(errorDetails)
{
@@ -331,7 +311,7 @@ public:
* support is too WIP to justify the code churn, but if it is finished
* then a better identifier becomes moe worth it.
*/
class SysError final : public CloneableError<SysError, SystemError>
class SysError : public SystemError
{
public:
int errNo;
@@ -342,7 +322,7 @@ public:
*/
template<typename... Args>
SysError(int errNo, Args &&... args)
: CloneableError(
: SystemError(
Disambig{},
std::make_error_code(static_cast<std::errc>(errNo)),
strerror(errNo),
@@ -412,7 +392,7 @@ namespace windows {
* Unless you need to catch a specific error number, don't catch this in
* portable code. Catch `SystemError` instead.
*/
class WinError : public CloneableError<WinError, SystemError>
class WinError : public SystemError
{
public:
DWORD lastError;
@@ -424,7 +404,7 @@ public:
*/
template<typename... Args>
WinError(DWORD lastError, Args &&... args)
: CloneableError(
: SystemError(
Disambig{},
std::error_code(lastError, std::system_category()),
renderError(lastError),

View File

@@ -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.
*/

View File

@@ -80,7 +80,7 @@ std::set<ExperimentalFeature> parseFeatures(const StringSet &);
* An experimental feature was required for some (experimental)
* operation, but was not enabled.
*/
class MissingExperimentalFeature final : public CloneableError<MissingExperimentalFeature, Error>
class MissingExperimentalFeature : public Error
{
public:
/**

View File

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

View File

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

View File

@@ -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>
@@ -244,7 +243,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 +266,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.

View File

@@ -133,14 +133,14 @@ std::pair<int, std::string> runProgram(RunOptions && options);
void runProgram2(const RunOptions & options);
class ExecError final : public CloneableError<ExecError, Error>
class ExecError : public Error
{
public:
int status;
template<typename... Args>
ExecError(int status, const Args &... args)
: CloneableError(args...)
: Error(args...)
, status(status)
{
}

View File

@@ -231,19 +231,19 @@ ref<SourceAccessor> makeEmptySourceAccessor();
*/
MakeError(RestrictedPathError, Error);
struct SymlinkNotAllowed final : public CloneableError<SymlinkNotAllowed, Error>
struct SymlinkNotAllowed : public Error
{
CanonPath path;
SymlinkNotAllowed(CanonPath path)
: CloneableError("relative path '%s' points to a symlink, which is not allowed", path.rel())
: Error("relative path '%s' points to a symlink, which is not allowed", path.rel())
, path(std::move(path))
{
}
template<typename... Args>
SymlinkNotAllowed(CanonPath path, const std::string & fs, Args &&... args)
: CloneableError(fs, std::forward<Args>(args)...)
: Error(fs, std::forward<Args>(args)...)
, path(std::move(path))
{
}

View File

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

View File

@@ -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();

View File

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

View File

@@ -5,6 +5,7 @@
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <span>
#include "util-config-private.hh"
@@ -12,19 +13,111 @@
namespace nix {
namespace {
// This function is needed to handle non-blocking reads/writes. This is needed in the buildhook, because
// somehow the json logger file descriptor ends up being non-blocking and breaks remote-building.
// TODO: get rid of buildhook and remove this function again (https://github.com/NixOS/nix/issues/12688)
void pollFD(int fd, int events)
{
struct pollfd pfd;
pfd.fd = fd;
pfd.events = events;
int ret = poll(&pfd, 1, -1);
if (ret == -1) {
throw SysError("poll on file descriptor failed");
}
}
} // namespace
std::make_unsigned_t<off_t> getFileSize(Descriptor fd)
{
auto st = nix::fstat(fd);
return st.st_size;
}
void readFull(int fd, char * buf, size_t count)
{
while (count) {
checkInterrupt();
ssize_t res = ::read(fd, buf, count);
if (res == -1) {
switch (errno) {
case EINTR:
continue;
case EAGAIN:
pollFD(fd, POLLIN);
continue;
}
auto savedErrno = errno;
throw SysError(savedErrno, "reading from file %s", PathFmt(descriptorToPath(fd)));
}
if (res == 0)
throw EndOfFile("unexpected end-of-file");
count -= res;
buf += res;
}
}
void writeFull(int fd, std::string_view s, bool allowInterrupts)
{
while (!s.empty()) {
if (allowInterrupts)
checkInterrupt();
ssize_t res = write(fd, s.data(), s.size());
if (res == -1) {
switch (errno) {
case EINTR:
continue;
case EAGAIN:
pollFD(fd, POLLOUT);
continue;
}
auto savedErrno = errno;
throw SysError(savedErrno, "writing to file %s", PathFmt(descriptorToPath(fd)));
}
if (res > 0)
s.remove_prefix(res);
}
}
std::string readLine(int fd, bool eofOk, char terminator)
{
std::string s;
while (1) {
checkInterrupt();
char ch;
// FIXME: inefficient
ssize_t rd = ::read(fd, &ch, 1);
if (rd == -1) {
switch (errno) {
case EINTR:
continue;
case EAGAIN: {
pollFD(fd, POLLIN);
continue;
}
default: {
auto savedErrno = errno;
throw SysError(savedErrno, "reading a line from %s", PathFmt(descriptorToPath(fd)));
}
}
} else if (rd == 0) {
if (eofOk)
return s;
else
throw EndOfFile("unexpected EOF reading a line");
} else {
if (ch == terminator)
return s;
s += ch;
}
}
}
size_t read(Descriptor fd, std::span<std::byte> buffer)
{
ssize_t n;
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 +125,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 +207,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

View File

@@ -136,7 +136,7 @@ void unix::fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t
}
}
static AutoCloseFD
static Descriptor
openFileEnsureBeneathNoSymlinksIterative(Descriptor dirFd, const CanonPath & path, int flags, mode_t mode)
{
AutoCloseFD parentFd;
@@ -179,19 +179,19 @@ openFileEnsureBeneathNoSymlinksIterative(Descriptor dirFd, const CanonPath & pat
throw SymlinkNotAllowed(path2);
}
return 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 +201,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);

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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 `descriptorToPath` will overwrite the last error.
auto lastError = GetLastError();
auto path = descriptorToPath(handle);
throw WinError(lastError, "writing to file %d:%s", handle, PathFmt(path));
}
if (res > 0)
s.remove_prefix(res);
}
}
std::string readLine(HANDLE handle, bool eofOk, char terminator)
{
std::string s;
while (1) {
checkInterrupt();
char ch;
// FIXME: inefficient
DWORD rd;
if (!ReadFile(handle, &ch, 1, &rd, NULL)) {
throw WinError("reading a line");
} else if (rd == 0) {
if (eofOk)
return s;
else
throw EndOfFile("unexpected EOF reading a line");
} else {
if (ch == terminator)
return s;
s += ch;
}
}
}
size_t read(Descriptor fd, std::span<std::byte> buffer)
{
checkInterrupt(); // For consistency with unix, and its EINTR loop
DWORD n;
if (!ReadFile(fd, buffer.data(), static_cast<DWORD>(buffer.size()), &n, NULL)) {
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

View File

@@ -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. */
@@ -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;

View File

@@ -5,6 +5,5 @@ include_dirs += include_directories('../..')
headers += files(
'signals-impl.hh',
'windows-async-pipe.hh',
'windows-environment.hh',
'windows-known-folders.hh',
)

Some files were not shown because too many files have changed in this diff Show More