aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--boehmgc-coroutine-sp-fallback.diff77
-rw-r--r--configure.ac2
-rw-r--r--doc/manual/book.toml5
-rw-r--r--doc/manual/src/glossary.md12
-rw-r--r--doc/manual/src/release-notes/rl-next.md6
-rw-r--r--flake.lock8
-rw-r--r--flake.nix14
-rwxr-xr-xmaintainers/upload-release.pl6
-rw-r--r--src/libcmd/repl.cc28
-rw-r--r--src/libexpr/eval.cc31
-rw-r--r--src/libexpr/eval.hh10
-rw-r--r--src/libexpr/flake/config.cc2
-rw-r--r--src/libexpr/flake/flake.cc2
-rw-r--r--src/libexpr/nixexpr.cc72
-rw-r--r--src/libexpr/nixexpr.hh29
-rw-r--r--src/libexpr/parser.y45
-rw-r--r--src/libexpr/primops.cc7
-rw-r--r--src/libexpr/tests/libexprtests.hh2
-rw-r--r--src/libexpr/tests/primops.cc17
-rw-r--r--src/libexpr/value-to-xml.cc3
-rw-r--r--src/libstore/build/derivation-goal.cc1
-rw-r--r--src/libstore/build/local-derivation-goal.cc31
-rw-r--r--src/libstore/build/personality.cc44
-rw-r--r--src/libstore/build/personality.hh11
-rw-r--r--src/libstore/builtins/buildenv.cc2
-rw-r--r--src/libstore/globals.hh5
-rw-r--r--src/libstore/nar-info-disk-cache.cc42
-rw-r--r--src/libstore/sqlite.cc15
-rw-r--r--src/libstore/sqlite.hh11
-rw-r--r--src/libutil/canon-path.cc103
-rw-r--r--src/libutil/canon-path.hh173
-rw-r--r--src/libutil/error.cc144
-rw-r--r--src/libutil/error.hh58
-rw-r--r--src/libutil/logging.cc32
-rw-r--r--src/libutil/tests/canon-path.cc155
-rw-r--r--src/nix-env/nix-env.cc6
-rw-r--r--src/nix/develop.cc12
-rw-r--r--src/nix/run.cc13
-rw-r--r--src/nix/run.hh3
-rw-r--r--tests/fetchGitSubmodules.sh9
-rwxr-xr-xtests/function-trace.sh28
41 files changed, 829 insertions, 447 deletions
diff --git a/boehmgc-coroutine-sp-fallback.diff b/boehmgc-coroutine-sp-fallback.diff
deleted file mode 100644
index 8fdafbecb..000000000
--- a/boehmgc-coroutine-sp-fallback.diff
+++ /dev/null
@@ -1,77 +0,0 @@
-diff --git a/darwin_stop_world.c b/darwin_stop_world.c
-index 3dbaa3fb..36a1d1f7 100644
---- a/darwin_stop_world.c
-+++ b/darwin_stop_world.c
-@@ -352,6 +352,7 @@ GC_INNER void GC_push_all_stacks(void)
- int nthreads = 0;
- word total_size = 0;
- mach_msg_type_number_t listcount = (mach_msg_type_number_t)THREAD_TABLE_SZ;
-+ size_t stack_limit;
- if (!EXPECT(GC_thr_initialized, TRUE))
- GC_thr_init();
-
-@@ -407,6 +408,19 @@ GC_INNER void GC_push_all_stacks(void)
- GC_push_all_stack_sections(lo, hi, p->traced_stack_sect);
- }
- if (altstack_lo) {
-+ // When a thread goes into a coroutine, we lose its original sp until
-+ // control flow returns to the thread.
-+ // While in the coroutine, the sp points outside the thread stack,
-+ // so we can detect this and push the entire thread stack instead,
-+ // as an approximation.
-+ // We assume that the coroutine has similarly added its entire stack.
-+ // This could be made accurate by cooperating with the application
-+ // via new functions and/or callbacks.
-+ stack_limit = pthread_get_stacksize_np(p->id);
-+ if (altstack_lo >= altstack_hi || altstack_lo < altstack_hi - stack_limit) { // sp outside stack
-+ altstack_lo = altstack_hi - stack_limit;
-+ }
-+
- total_size += altstack_hi - altstack_lo;
- GC_push_all_stack(altstack_lo, altstack_hi);
- }
-diff --git a/pthread_stop_world.c b/pthread_stop_world.c
-index 4b2c429..1fb4c52 100644
---- a/pthread_stop_world.c
-+++ b/pthread_stop_world.c
-@@ -673,6 +673,8 @@ GC_INNER void GC_push_all_stacks(void)
- struct GC_traced_stack_sect_s *traced_stack_sect;
- pthread_t self = pthread_self();
- word total_size = 0;
-+ size_t stack_limit;
-+ pthread_attr_t pattr;
-
- if (!EXPECT(GC_thr_initialized, TRUE))
- GC_thr_init();
-@@ -722,6 +724,31 @@ GC_INNER void GC_push_all_stacks(void)
- hi = p->altstack + p->altstack_size;
- /* FIXME: Need to scan the normal stack too, but how ? */
- /* FIXME: Assume stack grows down */
-+ } else {
-+ if (pthread_getattr_np(p->id, &pattr)) {
-+ ABORT("GC_push_all_stacks: pthread_getattr_np failed!");
-+ }
-+ if (pthread_attr_getstacksize(&pattr, &stack_limit)) {
-+ ABORT("GC_push_all_stacks: pthread_attr_getstacksize failed!");
-+ }
-+ if (pthread_attr_destroy(&pattr)) {
-+ ABORT("GC_push_all_stacks: pthread_attr_destroy failed!");
-+ }
-+ // When a thread goes into a coroutine, we lose its original sp until
-+ // control flow returns to the thread.
-+ // While in the coroutine, the sp points outside the thread stack,
-+ // so we can detect this and push the entire thread stack instead,
-+ // as an approximation.
-+ // We assume that the coroutine has similarly added its entire stack.
-+ // This could be made accurate by cooperating with the application
-+ // via new functions and/or callbacks.
-+ #ifndef STACK_GROWS_UP
-+ if (lo >= hi || lo < hi - stack_limit) { // sp outside stack
-+ lo = hi - stack_limit;
-+ }
-+ #else
-+ #error "STACK_GROWS_UP not supported in boost_coroutine2 (as of june 2021), so we don't support it in Nix."
-+ #endif
- }
- GC_push_all_stack_sections(lo, hi, traced_stack_sect);
- # ifdef STACK_GROWS_UP
diff --git a/configure.ac b/configure.ac
index c0e989d85..1b0d6fd27 100644
--- a/configure.ac
+++ b/configure.ac
@@ -41,8 +41,6 @@ AC_DEFINE_UNQUOTED(SYSTEM, ["$system"], [platform identifier ('cpu-os')])
test "$localstatedir" = '${prefix}/var' && localstatedir=/nix/var
-CFLAGS=
-CXXFLAGS=
AC_PROG_CC
AC_PROG_CXX
AC_PROG_CPP
diff --git a/doc/manual/book.toml b/doc/manual/book.toml
index 5f78a7614..46ced7ff7 100644
--- a/doc/manual/book.toml
+++ b/doc/manual/book.toml
@@ -1,6 +1,11 @@
+[book]
+title = "Nix Reference Manual"
+
[output.html]
additional-css = ["custom.css"]
additional-js = ["redirects.js"]
+edit-url-template = "https://github.com/NixOS/nix/tree/master/doc/manual/{path}"
+git-repository-url = "https://github.com/NixOS/nix"
[preprocessor.anchors]
renderers = ["html"]
diff --git a/doc/manual/src/glossary.md b/doc/manual/src/glossary.md
index b13709f8a..aa775b163 100644
--- a/doc/manual/src/glossary.md
+++ b/doc/manual/src/glossary.md
@@ -3,18 +3,18 @@
- [derivation]{#gloss-derivation}\
A description of a build task. The result of a derivation is a
store object. Derivations are typically specified in Nix expressions
- using the [`derivation` primitive](language/derivations.md). These are
+ using the [`derivation` primitive](./language/derivations.md). These are
translated into low-level *store derivations* (implicitly by
`nix-env` and `nix-build`, or explicitly by `nix-instantiate`).
- [content-addressed derivation]{#gloss-content-addressed-derivation}\
A derivation which has the
- [`__contentAddressed`](language/advanced-attributes.md#adv-attr-__contentAddressed)
+ [`__contentAddressed`](./language/advanced-attributes.md#adv-attr-__contentAddressed)
attribute set to `true`.
- [fixed-output derivation]{#gloss-fixed-output-derivation}\
A derivation which includes the
- [`outputHash`](language/advanced-attributes.md#adv-attr-outputHash) attribute.
+ [`outputHash`](./language/advanced-attributes.md#adv-attr-outputHash) attribute.
- [store]{#gloss-store}\
The location in the file system where store objects live. Typically
@@ -79,7 +79,7 @@
- [substituter]{#gloss-substituter}\
A *substituter* is an additional store from which Nix will
copy store objects it doesn't have. For details, see the
- [`substituters` option](command-ref/conf-file.html#conf-substituters).
+ [`substituters` option](./command-ref/conf-file.md#conf-substituters).
- [purity]{#gloss-purity}\
The assumption that equal Nix derivations when run always produce
@@ -139,7 +139,7 @@
An automatically generated store object that consists of a set of
symlinks to “active” applications, i.e., other store paths. These
are generated automatically by
- [`nix-env`](command-ref/nix-env.md). See *profiles*.
+ [`nix-env`](./command-ref/nix-env.md). See *profiles*.
- [profile]{#gloss-profile}\
A symlink to the current *user environment* of a user, e.g.,
@@ -150,7 +150,9 @@
store. It can contain regular files, directories and symbolic
links. NARs are generated and unpacked using `nix-store --dump`
and `nix-store --restore`.
+
- [`∅`]{#gloss-emtpy-set}\
The empty set symbol. In the context of profile history, this denotes a package is not present in a particular version of the profile.
+
- [`ε`]{#gloss-epsilon}\
The epsilon symbol. In the context of a package, this means the version is empty. More precisely, the derivation does not have a version attribute.
diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md
index 6c169bd09..906b048f1 100644
--- a/doc/manual/src/release-notes/rl-next.md
+++ b/doc/manual/src/release-notes/rl-next.md
@@ -25,3 +25,9 @@
$ nix-build glibc^dev`
```
does already.
+
+* On Linux, `nix develop` now sets the
+ [*personality*](https://man7.org/linux/man-pages/man2/personality.2.html)
+ for the development shell in the same way as the actual build of the
+ derivation. This makes shells for `i686-linux` derivations work
+ correctly on `x86_64-linux`.
diff --git a/flake.lock b/flake.lock
index a66c9cb1b..4490b5ead 100644
--- a/flake.lock
+++ b/flake.lock
@@ -18,16 +18,16 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1657693803,
- "narHash": "sha256-G++2CJ9u0E7NNTAi9n5G8TdDmGJXcIjkJ3NF8cetQB8=",
+ "lastModified": 1670461440,
+ "narHash": "sha256-jy1LB8HOMKGJEGXgzFRLDU1CBGL0/LlkolgnqIsF0D8=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "365e1b3a859281cf11b94f87231adeabbdd878a2",
+ "rev": "04a75b2eecc0acf6239acf9dd04485ff8d14f425",
"type": "github"
},
"original": {
"owner": "NixOS",
- "ref": "nixos-22.05-small",
+ "ref": "nixos-22.11-small",
"repo": "nixpkgs",
"type": "github"
}
diff --git a/flake.nix b/flake.nix
index 4ba3f04b0..652695989 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,7 +1,7 @@
{
description = "The purely functional package manager";
- inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.05-small";
+ inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.11-small";
inputs.nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2";
inputs.lowdown-src = { url = "github:kristapsdz/lowdown"; flake = false; };
@@ -108,7 +108,7 @@
++ lib.optionals stdenv.hostPlatform.isLinux [(buildPackages.util-linuxMinimal or buildPackages.utillinuxMinimal)];
buildDeps =
- [ (curl.override { patchNetrcRegression = true; })
+ [ curl
bzip2 xz brotli editline
openssl sqlite
libarchive
@@ -127,13 +127,9 @@
});
propagatedDeps =
- [ ((boehmgc.override {
+ [ (boehmgc.override {
enableLargeConfig = true;
- }).overrideAttrs(o: {
- patches = (o.patches or []) ++ [
- ./boehmgc-coroutine-sp-fallback.diff
- ];
- }))
+ })
nlohmann_json
];
};
@@ -364,7 +360,7 @@
buildInputs =
[ nix
- (curl.override { patchNetrcRegression = true; })
+ curl
bzip2
xz
pkgs.perl
diff --git a/maintainers/upload-release.pl b/maintainers/upload-release.pl
index d3ef63db8..77469148a 100755
--- a/maintainers/upload-release.pl
+++ b/maintainers/upload-release.pl
@@ -115,10 +115,6 @@ sub downloadFile {
write_file("$tmpFile.sha256", $sha256_actual);
- if (! -e "$tmpFile.asc") {
- system("gpg2 --detach-sign --armor $tmpFile") == 0 or die "unable to sign $tmpFile\n";
- }
-
return $sha256_expected;
}
@@ -194,7 +190,7 @@ for my $fn (glob "$tmpDir/*") {
my $configuration = ();
$configuration->{content_type} = "application/octet-stream";
- if ($fn =~ /.sha256|.asc|install/) {
+ if ($fn =~ /.sha256|install/) {
# Text files
$configuration->{content_type} = "text/plain";
}
diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
index c704fcfb1..5400fcd69 100644
--- a/src/libcmd/repl.cc
+++ b/src/libcmd/repl.cc
@@ -215,17 +215,15 @@ static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positi
out << dt.hint.str() << "\n";
// prefer direct pos, but if noPos then try the expr.
- auto pos = *dt.pos
- ? *dt.pos
- : positions[dt.expr.getPos() ? dt.expr.getPos() : noPos];
+ auto pos = dt.pos
+ ? dt.pos
+ : static_cast<std::shared_ptr<AbstractPos>>(positions[dt.expr.getPos() ? dt.expr.getPos() : noPos]);
if (pos) {
- printAtPos(pos, out);
-
- auto loc = getCodeLines(pos);
- if (loc.has_value()) {
+ out << pos;
+ if (auto loc = pos->getCodeLines()) {
out << "\n";
- printCodeLines(out, "", pos, *loc);
+ printCodeLines(out, "", *pos, *loc);
out << "\n";
}
}
@@ -589,15 +587,17 @@ bool NixRepl::processLine(std::string line)
Value v;
evalString(arg, v);
- const auto [file, line] = [&] () -> std::pair<std::string, uint32_t> {
+ const auto [path, line] = [&] () -> std::pair<Path, uint32_t> {
if (v.type() == nPath || v.type() == nString) {
PathSet context;
- auto filename = state->coerceToString(noPos, v, context).toOwned();
- state->symbols.create(filename);
- return {filename, 0};
+ auto path = state->coerceToPath(noPos, v, context);
+ return {path, 0};
} else if (v.isLambda()) {
auto pos = state->positions[v.lambda.fun->pos];
- return {pos.file, pos.line};
+ if (auto path = std::get_if<Path>(&pos.origin))
+ return {*path, pos.line};
+ else
+ throw Error("'%s' cannot be shown in an editor", pos);
} else {
// assume it's a derivation
return findPackageFilename(*state, v, arg);
@@ -605,7 +605,7 @@ bool NixRepl::processLine(std::string line)
}();
// Open in EDITOR
- auto args = editorFor(file, line);
+ auto args = editorFor(path, line);
auto editor = args.front();
args.pop_front();
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index aee0636b0..084ccbee2 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -820,7 +820,7 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
? std::make_unique<DebugTraceStacker>(
*this,
DebugTrace {
- .pos = error->info().errPos ? *error->info().errPos : positions[expr.getPos()],
+ .pos = error->info().errPos ? error->info().errPos : static_cast<std::shared_ptr<AbstractPos>>(positions[expr.getPos()]),
.expr = expr,
.env = env,
.hint = error->info().msg,
@@ -1009,7 +1009,7 @@ void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, cons
void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const
{
- e.addTrace(std::nullopt, s, s2);
+ e.addTrace(nullptr, s, s2);
}
void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const
@@ -1021,13 +1021,13 @@ static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker(
EvalState & state,
Expr & expr,
Env & env,
- std::optional<ErrPos> pos,
+ std::shared_ptr<AbstractPos> && pos,
const char * s,
const std::string & s2)
{
return std::make_unique<DebugTraceStacker>(state,
DebugTrace {
- .pos = pos,
+ .pos = std::move(pos),
.expr = expr,
.env = env,
.hint = hintfmt(s, s2),
@@ -1133,9 +1133,9 @@ void EvalState::mkThunk_(Value & v, Expr * expr)
void EvalState::mkPos(Value & v, PosIdx p)
{
auto pos = positions[p];
- if (!pos.file.empty()) {
+ if (auto path = std::get_if<Path>(&pos.origin)) {
auto attrs = buildBindings(3);
- attrs.alloc(sFile).mkString(pos.file);
+ attrs.alloc(sFile).mkString(*path);
attrs.alloc(sLine).mkInt(pos.line);
attrs.alloc(sColumn).mkInt(pos.column);
v.mkAttrs(attrs);
@@ -1243,7 +1243,7 @@ void EvalState::cacheFile(
*this,
*e,
this->baseEnv,
- e->getPos() ? std::optional(ErrPos(positions[e->getPos()])) : std::nullopt,
+ e->getPos() ? static_cast<std::shared_ptr<AbstractPos>>(positions[e->getPos()]) : nullptr,
"while evaluating the file '%1%':", resolvedPath)
: nullptr;
@@ -1514,10 +1514,13 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
state.forceValue(*vAttrs, (pos2 ? pos2 : this->pos ) );
} catch (Error & e) {
- auto pos2r = state.positions[pos2];
- if (pos2 && pos2r.file != state.derivationNixPath)
- state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'",
- showAttrPath(state, env, attrPath));
+ if (pos2) {
+ auto pos2r = state.positions[pos2];
+ auto origin = std::get_if<Path>(&pos2r.origin);
+ if (!(origin && *origin == state.derivationNixPath))
+ state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'",
+ showAttrPath(state, env, attrPath));
+ }
throw;
}
@@ -2494,7 +2497,8 @@ void EvalState::printStats()
else
obj["name"] = nullptr;
if (auto pos = positions[fun->pos]) {
- obj["file"] = (std::string_view) pos.file;
+ if (auto path = std::get_if<Path>(&pos.origin))
+ obj["file"] = *path;
obj["line"] = pos.line;
obj["column"] = pos.column;
}
@@ -2508,7 +2512,8 @@ void EvalState::printStats()
for (auto & i : attrSelects) {
json obj = json::object();
if (auto pos = positions[i.first]) {
- obj["file"] = (const std::string &) pos.file;
+ if (auto path = std::get_if<Path>(&pos.origin))
+ obj["file"] = *path;
obj["line"] = pos.line;
obj["column"] = pos.column;
}
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index cf307d820..21666339b 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -78,7 +78,7 @@ struct RegexCache;
std::shared_ptr<RegexCache> makeRegexCache();
struct DebugTrace {
- std::optional<ErrPos> pos;
+ std::shared_ptr<AbstractPos> pos;
const Expr & expr;
const Env & env;
hintformat hint;
@@ -457,8 +457,12 @@ private:
friend struct ExprAttrs;
friend struct ExprLet;
- Expr * parse(char * text, size_t length, FileOrigin origin, const PathView path,
- const PathView basePath, std::shared_ptr<StaticEnv> & staticEnv);
+ Expr * parse(
+ char * text,
+ size_t length,
+ Pos::Origin origin,
+ Path basePath,
+ std::shared_ptr<StaticEnv> & staticEnv);
public:
diff --git a/src/libexpr/flake/config.cc b/src/libexpr/flake/config.cc
index 6df95f1f0..89ddbde7e 100644
--- a/src/libexpr/flake/config.cc
+++ b/src/libexpr/flake/config.cc
@@ -56,7 +56,7 @@ void ConfigFile::apply()
auto tlname = get(trustedList, name);
if (auto saved = tlname ? get(*tlname, valueS) : nullptr) {
trusted = *saved;
- warn("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name,valueS);
+ printInfo("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name, valueS);
} else {
// FIXME: filter ANSI escapes, newlines, \r, etc.
if (std::tolower(logger->ask(fmt("do you want to allow configuration setting '%s' to be set to '" ANSI_RED "%s" ANSI_NORMAL "' (y/N)?", name, valueS)).value_or('n')) == 'y') {
diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc
index 6344fb253..105d32467 100644
--- a/src/libexpr/flake/flake.cc
+++ b/src/libexpr/flake/flake.cc
@@ -220,7 +220,7 @@ static Flake getFlake(
Value vInfo;
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
- expectType(state, nAttrs, vInfo, state.positions.add({flakeFile, foFile}, 0, 0));
+ expectType(state, nAttrs, vInfo, state.positions.add({flakeFile}, 1, 1));
if (auto description = vInfo.attrs->get(state.sDescription)) {
expectType(state, nString, *description->value, description->pos);
diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc
index 2be560d76..eb6f062b4 100644
--- a/src/libexpr/nixexpr.cc
+++ b/src/libexpr/nixexpr.cc
@@ -8,6 +8,58 @@
namespace nix {
+struct PosAdapter : AbstractPos
+{
+ Pos::Origin origin;
+
+ PosAdapter(Pos::Origin origin)
+ : origin(std::move(origin))
+ {
+ }
+
+ std::optional<std::string> getSource() const override
+ {
+ return std::visit(overloaded {
+ [](const Pos::none_tag &) -> std::optional<std::string> {
+ return std::nullopt;
+ },
+ [](const Pos::Stdin & s) -> std::optional<std::string> {
+ // Get rid of the null terminators added by the parser.
+ return std::string(s.source->c_str());
+ },
+ [](const Pos::String & s) -> std::optional<std::string> {
+ // Get rid of the null terminators added by the parser.
+ return std::string(s.source->c_str());
+ },
+ [](const Path & path) -> std::optional<std::string> {
+ try {
+ return readFile(path);
+ } catch (Error &) {
+ return std::nullopt;
+ }
+ }
+ }, origin);
+ }
+
+ void print(std::ostream & out) const override
+ {
+ std::visit(overloaded {
+ [&](const Pos::none_tag &) { out << "«none»"; },
+ [&](const Pos::Stdin &) { out << "«stdin»"; },
+ [&](const Pos::String & s) { out << "«string»"; },
+ [&](const Path & path) { out << path; }
+ }, origin);
+ }
+};
+
+Pos::operator std::shared_ptr<AbstractPos>() const
+{
+ auto pos = std::make_shared<PosAdapter>(origin);
+ pos->line = line;
+ pos->column = column;
+ return pos;
+}
+
/* Displaying abstract syntax trees. */
static void showString(std::ostream & str, std::string_view s)
@@ -248,24 +300,10 @@ void ExprPos::show(const SymbolTable & symbols, std::ostream & str) const
std::ostream & operator << (std::ostream & str, const Pos & pos)
{
- if (!pos)
+ if (auto pos2 = (std::shared_ptr<AbstractPos>) pos) {
+ str << *pos2;
+ } else
str << "undefined position";
- else
- {
- auto f = format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%");
- switch (pos.origin) {
- case foFile:
- f % (const std::string &) pos.file;
- break;
- case foStdin:
- case foString:
- f % "(string)";
- break;
- default:
- throw Error("unhandled Pos origin!");
- }
- str << (f % pos.line % pos.column).str();
- }
return str;
}
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index 5eb022770..ac7ce021e 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -23,15 +23,22 @@ MakeError(MissingArgumentError, EvalError);
MakeError(RestrictedPathError, Error);
/* Position objects. */
-
struct Pos
{
- std::string file;
- FileOrigin origin;
uint32_t line;
uint32_t column;
+ struct none_tag { };
+ struct Stdin { ref<std::string> source; };
+ struct String { ref<std::string> source; };
+
+ typedef std::variant<none_tag, Stdin, String, Path> Origin;
+
+ Origin origin;
+
explicit operator bool() const { return line > 0; }
+
+ operator std::shared_ptr<AbstractPos>() const;
};
class PosIdx {
@@ -47,7 +54,11 @@ public:
explicit operator bool() const { return id > 0; }
- bool operator<(const PosIdx other) const { return id < other.id; }
+ bool operator <(const PosIdx other) const { return id < other.id; }
+
+ bool operator ==(const PosIdx other) const { return id == other.id; }
+
+ bool operator !=(const PosIdx other) const { return id != other.id; }
};
class PosTable
@@ -61,13 +72,13 @@ public:
// current origins.back() can be reused or not.
mutable uint32_t idx = std::numeric_limits<uint32_t>::max();
- explicit Origin(uint32_t idx): idx(idx), file{}, origin{} {}
+ // Used for searching in PosTable::[].
+ explicit Origin(uint32_t idx): idx(idx), origin{Pos::none_tag()} {}
public:
- const std::string file;
- const FileOrigin origin;
+ const Pos::Origin origin;
- Origin(std::string file, FileOrigin origin): file(std::move(file)), origin(origin) {}
+ Origin(Pos::Origin origin): origin(origin) {}
};
struct Offset {
@@ -107,7 +118,7 @@ public:
[] (const auto & a, const auto & b) { return a.idx < b.idx; });
const auto origin = *std::prev(pastOrigin);
const auto offset = offsets[idx];
- return {origin.file, origin.origin, offset.line, offset.column};
+ return {offset.line, offset.column, origin.origin};
}
};
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index fbf865719..e07909f8e 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -34,11 +34,6 @@ namespace nix {
Path basePath;
PosTable::Origin origin;
std::optional<ErrorInfo> error;
- ParseData(EvalState & state, PosTable::Origin origin)
- : state(state)
- , symbols(state.symbols)
- , origin(std::move(origin))
- { };
};
struct ParserFormals {
@@ -649,24 +644,20 @@ formal
namespace nix {
-Expr * EvalState::parse(char * text, size_t length, FileOrigin origin,
- const PathView path, const PathView basePath, std::shared_ptr<StaticEnv> & staticEnv)
+Expr * EvalState::parse(
+ char * text,
+ size_t length,
+ Pos::Origin origin,
+ Path basePath,
+ std::shared_ptr<StaticEnv> & staticEnv)
{
yyscan_t scanner;
- std::string file;
- switch (origin) {
- case foFile:
- file = path;
- break;
- case foStdin:
- case foString:
- file = text;
- break;
- default:
- assert(false);
- }
- ParseData data(*this, {file, origin});
- data.basePath = basePath;
+ ParseData data {
+ .state = *this,
+ .symbols = symbols,
+ .basePath = std::move(basePath),
+ .origin = {origin},
+ };
yylex_init(&scanner);
yy_scan_buffer(text, length, scanner);
@@ -718,14 +709,15 @@ Expr * EvalState::parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv
auto buffer = readFile(path);
// readFile should have left some extra space for terminators
buffer.append("\0\0", 2);
- return parse(buffer.data(), buffer.size(), foFile, path, dirOf(path), staticEnv);
+ return parse(buffer.data(), buffer.size(), path, dirOf(path), staticEnv);
}
-Expr * EvalState::parseExprFromString(std::string s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv)
+Expr * EvalState::parseExprFromString(std::string s_, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv)
{
- s.append("\0\0", 2);
- return parse(s.data(), s.size(), foString, "", basePath, staticEnv);
+ auto s = make_ref<std::string>(std::move(s_));
+ s->append("\0\0", 2);
+ return parse(s->data(), s->size(), Pos::String{.source = s}, basePath, staticEnv);
}
@@ -741,7 +733,8 @@ Expr * EvalState::parseStdin()
auto buffer = drainFD(0);
// drainFD should have left some extra space for terminators
buffer.append("\0\0", 2);
- return parse(buffer.data(), buffer.size(), foStdin, "", absPath("."), staticBaseEnv);
+ auto s = make_ref<std::string>(std::move(buffer));
+ return parse(s->data(), s->size(), Pos::Stdin{.source = s}, absPath("."), staticBaseEnv);
}
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 283d2746b..7efe50324 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -368,8 +368,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto output = runProgram(program, true, commandArgs);
Expr * parsed;
try {
- auto base = state.positions[pos];
- parsed = state.parseExprFromString(std::move(output), base.file);
+ parsed = state.parseExprFromString(std::move(output), "/");
} catch (Error & e) {
e.addTrace(state.positions[pos], "While parsing the output from '%1%'", program);
throw;
@@ -798,7 +797,7 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
v = *args[1];
} catch (Error & e) {
PathSet context;
- e.addTrace(std::nullopt, state.coerceToString(pos, *args[0], context).toOwned());
+ e.addTrace(nullptr, state.coerceToString(pos, *args[0], context).toOwned());
throw;
}
}
@@ -4018,7 +4017,7 @@ void EvalState::createBaseEnv()
// the parser needs two NUL bytes as terminators; one of them
// is implied by being a C string.
"\0";
- eval(parse(code, sizeof(code), foFile, derivationNixPath, "/", staticBaseEnv), *vDerivation);
+ eval(parse(code, sizeof(code), derivationNixPath, "/", staticBaseEnv), *vDerivation);
}
diff --git a/src/libexpr/tests/libexprtests.hh b/src/libexpr/tests/libexprtests.hh
index 4f6915882..5bb5e66d3 100644
--- a/src/libexpr/tests/libexprtests.hh
+++ b/src/libexpr/tests/libexprtests.hh
@@ -123,7 +123,7 @@ namespace nix {
MATCHER_P(IsAttrsOfSize, n, fmt("Is a set of size [%1%]", n)) {
if (arg.type() != nAttrs) {
- *result_listener << "Expexted set got " << arg.type();
+ *result_listener << "Expected set got " << arg.type();
return false;
} else if (arg.attrs->size() != (size_t)n) {
*result_listener << "Expected a set with " << n << " attributes but got " << arg.attrs->size();
diff --git a/src/libexpr/tests/primops.cc b/src/libexpr/tests/primops.cc
index 16cf66d2c..bcdc7086b 100644
--- a/src/libexpr/tests/primops.cc
+++ b/src/libexpr/tests/primops.cc
@@ -151,20 +151,7 @@ namespace nix {
// The `y` attribute is at position
const char* expr = "builtins.unsafeGetAttrPos \"y\" { y = \"x\"; }";
auto v = eval(expr);
- ASSERT_THAT(v, IsAttrsOfSize(3));
-
- auto file = v.attrs->find(createSymbol("file"));
- ASSERT_NE(file, nullptr);
- // FIXME: The file when running these tests is the input string?!?
- ASSERT_THAT(*file->value, IsStringEq(expr));
-
- auto line = v.attrs->find(createSymbol("line"));
- ASSERT_NE(line, nullptr);
- ASSERT_THAT(*line->value, IsIntEq(1));
-
- auto column = v.attrs->find(createSymbol("column"));
- ASSERT_NE(column, nullptr);
- ASSERT_THAT(*column->value, IsIntEq(33));
+ ASSERT_THAT(v, IsNull());
}
TEST_F(PrimOpTest, hasAttr) {
@@ -617,7 +604,7 @@ namespace nix {
TEST_F(PrimOpTest, storeDir) {
auto v = eval("builtins.storeDir");
- ASSERT_THAT(v, IsStringEq("/nix/store"));
+ ASSERT_THAT(v, IsStringEq(settings.nixStore));
}
TEST_F(PrimOpTest, nixVersion) {
diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc
index 7c3bf9492..3f6222768 100644
--- a/src/libexpr/value-to-xml.cc
+++ b/src/libexpr/value-to-xml.cc
@@ -24,7 +24,8 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos)
{
- xmlAttrs["path"] = pos.file;
+ if (auto path = std::get_if<Path>(&pos.origin))
+ xmlAttrs["path"] = *path;
xmlAttrs["line"] = (format("%1%") % pos.line).str();
xmlAttrs["column"] = (format("%1%") % pos.column).str();
}
diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc
index d3b995a4f..173058d1b 100644
--- a/src/libstore/build/derivation-goal.cc
+++ b/src/libstore/build/derivation-goal.cc
@@ -39,7 +39,6 @@
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/ip.h>
-#include <sys/personality.h>
#include <sys/mman.h>
#include <sched.h>
#include <sys/param.h>
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index dccd096ec..9d869d513 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -15,6 +15,7 @@
#include "callback.hh"
#include "json-utils.hh"
#include "cgroup.hh"
+#include "personality.hh"
#include <regex>
#include <queue>
@@ -24,7 +25,6 @@
#include <termios.h>
#include <unistd.h>
#include <sys/mman.h>
-#include <sys/utsname.h>
#include <sys/resource.h>
#include <sys/socket.h>
@@ -37,7 +37,6 @@
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/ip.h>
-#include <sys/personality.h>
#include <sys/mman.h>
#include <sched.h>
#include <sys/param.h>
@@ -1964,33 +1963,7 @@ void LocalDerivationGoal::runChild()
/* Close all other file descriptors. */
closeMostFDs({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO});
-#if __linux__
- /* Change the personality to 32-bit if we're doing an
- i686-linux build on an x86_64-linux machine. */
- struct utsname utsbuf;
- uname(&utsbuf);
- if ((drv->platform == "i686-linux"
- && (settings.thisSystem == "x86_64-linux"
- || (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64"))))
- || drv->platform == "armv7l-linux"
- || drv->platform == "armv6l-linux")
- {
- if (personality(PER_LINUX32) == -1)
- throw SysError("cannot set 32-bit personality");
- }
-
- /* Impersonate a Linux 2.6 machine to get some determinism in
- builds that depend on the kernel version. */
- if ((drv->platform == "i686-linux" || drv->platform == "x86_64-linux") && settings.impersonateLinux26) {
- int cur = personality(0xffffffff);
- if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */);
- }
-
- /* Disable address space randomization for improved
- determinism. */
- int cur = personality(0xffffffff);
- if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE);
-#endif
+ setPersonality(drv->platform);
/* Disable core dumps by default. */
struct rlimit limit = { 0, RLIM_INFINITY };
diff --git a/src/libstore/build/personality.cc b/src/libstore/build/personality.cc
new file mode 100644
index 000000000..4ad477869
--- /dev/null
+++ b/src/libstore/build/personality.cc
@@ -0,0 +1,44 @@
+#include "personality.hh"
+#include "globals.hh"
+
+#if __linux__
+#include <sys/utsname.h>
+#include <sys/personality.h>
+#endif
+
+#include <cstring>
+
+namespace nix {
+
+void setPersonality(std::string_view system)
+{
+#if __linux__
+ /* Change the personality to 32-bit if we're doing an
+ i686-linux build on an x86_64-linux machine. */
+ struct utsname utsbuf;
+ uname(&utsbuf);
+ if ((system == "i686-linux"
+ && (std::string_view(SYSTEM) == "x86_64-linux"
+ || (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64"))))
+ || system == "armv7l-linux"
+ || system == "armv6l-linux")
+ {
+ if (personality(PER_LINUX32) == -1)
+ throw SysError("cannot set 32-bit personality");
+ }
+
+ /* Impersonate a Linux 2.6 machine to get some determinism in
+ builds that depend on the kernel version. */
+ if ((system == "i686-linux" || system == "x86_64-linux") && settings.impersonateLinux26) {
+ int cur = personality(0xffffffff);
+ if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */);
+ }
+
+ /* Disable address space randomization for improved
+ determinism. */
+ int cur = personality(0xffffffff);
+ if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE);
+#endif
+}
+
+}
diff --git a/src/libstore/build/personality.hh b/src/libstore/build/personality.hh
new file mode 100644
index 000000000..30e4f4062
--- /dev/null
+++ b/src/libstore/build/personality.hh
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <string>
+
+namespace nix {
+
+void setPersonality(std::string_view system);
+
+}
+
+
diff --git a/src/libstore/builtins/buildenv.cc b/src/libstore/builtins/buildenv.cc
index 47458a388..b1fbda13d 100644
--- a/src/libstore/builtins/buildenv.cc
+++ b/src/libstore/builtins/buildenv.cc
@@ -95,7 +95,7 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
throw Error(
"files '%1%' and '%2%' have the same priority %3%; "
"use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' "
- "or type 'nix profile install --help' if using 'nix profile' to find out how"
+ "or type 'nix profile install --help' if using 'nix profile' to find out how "
"to change the priority of one of the conflicting packages"
" (0 being the highest priority)",
srcFile, readLink(dstFile), priority);
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index 274a15dd7..f4d53757c 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -329,7 +329,7 @@ public:
Whether to execute builds inside cgroups.
This is only supported on Linux.
- Cgroups are required and enabled automatically for derivations
+ Cgroups are required and enabled automatically for derivations
that require the `uid-range` system feature.
> **Warning**
@@ -491,6 +491,9 @@ public:
for example, `/dev/nvidiactl?` specifies that `/dev/nvidiactl` will
only be mounted in the sandbox if it exists in the host filesystem.
+ If the source is in the Nix store, then its closure will be added to
+ the sandbox as well.
+
Depending on how Nix was built, the default value for this option
may be empty or provide `/bin/sh` as a bind-mount of `bash`.
)",
diff --git a/src/libstore/nar-info-disk-cache.cc b/src/libstore/nar-info-disk-cache.cc
index f4ea739b0..3e0689534 100644
--- a/src/libstore/nar-info-disk-cache.cc
+++ b/src/libstore/nar-info-disk-cache.cc
@@ -166,16 +166,37 @@ public:
return i->second;
}
+ std::optional<Cache> queryCacheRaw(State & state, const std::string & uri)
+ {
+ auto i = state.caches.find(uri);
+ if (i == state.caches.end()) {
+ auto queryCache(state.queryCache.use()(uri)(time(0) - cacheInfoTtl));
+ if (!queryCache.next())
+ return std::nullopt;
+ state.caches.emplace(uri,
+ Cache{(int) queryCache.getInt(0), queryCache.getStr(1), queryCache.getInt(2) != 0, (int) queryCache.getInt(3)});
+ }
+ return getCache(state, uri);
+ }
+
void createCache(const std::string & uri, const Path & storeDir, bool wantMassQuery, int priority) override
{
retrySQLite<void>([&]() {
auto state(_state.lock());
+ SQLiteTxn txn(state->db);
+
+ // To avoid the race, we have to check if maybe someone hasn't yet created
+ // the cache for this URI in the meantime.
+ auto cache(queryCacheRaw(*state, uri));
- // FIXME: race
+ if (cache)
+ return;
state->insertCache.use()(uri)(time(0))(storeDir)(wantMassQuery)(priority).exec();
assert(sqlite3_changes(state->db) == 1);
state->caches[uri] = Cache{(int) sqlite3_last_insert_rowid(state->db), storeDir, wantMassQuery, priority};
+
+ txn.commit();
});
}
@@ -183,21 +204,12 @@ public:
{
return retrySQLite<std::optional<CacheInfo>>([&]() -> std::optional<CacheInfo> {
auto state(_state.lock());
-
- auto i = state->caches.find(uri);
- if (i == state->caches.end()) {
- auto queryCache(state->queryCache.use()(uri)(time(0) - cacheInfoTtl));
- if (!queryCache.next())
- return std::nullopt;
- state->caches.emplace(uri,
- Cache{(int) queryCache.getInt(0), queryCache.getStr(1), queryCache.getInt(2) != 0, (int) queryCache.getInt(3)});
- }
-
- auto & cache(getCache(*state, uri));
-
+ auto cache(queryCacheRaw(*state, uri));
+ if (!cache)
+ return std::nullopt;
return CacheInfo {
- .wantMassQuery = cache.wantMassQuery,
- .priority = cache.priority
+ .wantMassQuery = cache->wantMassQuery,
+ .priority = cache->priority
};
});
}
diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc
index 2090beabd..6c350888f 100644
--- a/src/libstore/sqlite.cc
+++ b/src/libstore/sqlite.cc
@@ -8,12 +8,15 @@
namespace nix {
-SQLiteError::SQLiteError(const char *path, int errNo, int extendedErrNo, hintformat && hf)
- : Error(""), path(path), errNo(errNo), extendedErrNo(extendedErrNo)
+SQLiteError::SQLiteError(const char *path, const char *errMsg, int errNo, int extendedErrNo, int offset, hintformat && hf)
+ : Error(""), path(path), errMsg(errMsg), errNo(errNo), extendedErrNo(extendedErrNo), offset(offset)
{
- err.msg = hintfmt("%s: %s (in '%s')",
+ auto offsetStr = (offset == -1) ? "" : "at offset " + std::to_string(offset) + ": ";
+ err.msg = hintfmt("%s: %s%s, %s (in '%s')",
normaltxt(hf.str()),
+ offsetStr,
sqlite3_errstr(extendedErrNo),
+ errMsg,
path ? path : "(in-memory)");
}
@@ -21,11 +24,13 @@ SQLiteError::SQLiteError(const char *path, int errNo, int extendedErrNo, hintfor
{
int err = sqlite3_errcode(db);
int exterr = sqlite3_extended_errcode(db);
+ int offset = sqlite3_error_offset(db);
auto path = sqlite3_db_filename(db, nullptr);
+ auto errMsg = sqlite3_errmsg(db);
if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) {
- auto exp = SQLiteBusy(path, err, exterr, std::move(hf));
+ auto exp = SQLiteBusy(path, errMsg, err, exterr, offset, std::move(hf));
exp.err.msg = hintfmt(
err == SQLITE_PROTOCOL
? "SQLite database '%s' is busy (SQLITE_PROTOCOL)"
@@ -33,7 +38,7 @@ SQLiteError::SQLiteError(const char *path, int errNo, int extendedErrNo, hintfor
path ? path : "(in-memory)");
throw exp;
} else
- throw SQLiteError(path, err, exterr, std::move(hf));
+ throw SQLiteError(path, errMsg, err, exterr, offset, std::move(hf));
}
SQLite::SQLite(const Path & path, bool create)
diff --git a/src/libstore/sqlite.hh b/src/libstore/sqlite.hh
index 1d1c553ea..1853731a2 100644
--- a/src/libstore/sqlite.hh
+++ b/src/libstore/sqlite.hh
@@ -98,21 +98,22 @@ struct SQLiteTxn
struct SQLiteError : Error
{
- const char *path;
- int errNo, extendedErrNo;
+ std::string path;
+ std::string errMsg;
+ int errNo, extendedErrNo, offset;
template<typename... Args>
[[noreturn]] static void throw_(sqlite3 * db, const std::string & fs, const Args & ... args) {
throw_(db, hintfmt(fs, args...));
}
- SQLiteError(const char *path, int errNo, int extendedErrNo, hintformat && hf);
+ SQLiteError(const char *path, const char *errMsg, int errNo, int extendedErrNo, int offset, hintformat && hf);
protected:
template<typename... Args>
- SQLiteError(const char *path, int errNo, int extendedErrNo, const std::string & fs, const Args & ... args)
- : SQLiteError(path, errNo, extendedErrNo, hintfmt(fs, args...))
+ SQLiteError(const char *path, const char *errMsg, int errNo, int extendedErrNo, int offset, const std::string & fs, const Args & ... args)
+ : SQLiteError(path, errNo, extendedErrNo, offset, hintfmt(fs, args...))
{ }
[[noreturn]] static void throw_(sqlite3 * db, hintformat && hf);
diff --git a/src/libutil/canon-path.cc b/src/libutil/canon-path.cc
new file mode 100644
index 000000000..b132b4262
--- /dev/null
+++ b/src/libutil/canon-path.cc
@@ -0,0 +1,103 @@
+#include "canon-path.hh"
+#include "util.hh"
+
+namespace nix {
+
+CanonPath CanonPath::root = CanonPath("/");
+
+CanonPath::CanonPath(std::string_view raw)
+ : path(absPath((Path) raw, "/"))
+{ }
+
+CanonPath::CanonPath(std::string_view raw, const CanonPath & root)
+ : path(absPath((Path) raw, root.abs()))
+{ }
+
+std::optional<CanonPath> CanonPath::parent() const
+{
+ if (isRoot()) return std::nullopt;
+ return CanonPath(unchecked_t(), path.substr(0, std::max((size_t) 1, path.rfind('/'))));
+}
+
+void CanonPath::pop()
+{
+ assert(!isRoot());
+ path.resize(std::max((size_t) 1, path.rfind('/')));
+}
+
+bool CanonPath::isWithin(const CanonPath & parent) const
+{
+ return !(
+ path.size() < parent.path.size()
+ || path.substr(0, parent.path.size()) != parent.path
+ || (parent.path.size() > 1 && path.size() > parent.path.size()
+ && path[parent.path.size()] != '/'));
+}
+
+CanonPath CanonPath::removePrefix(const CanonPath & prefix) const
+{
+ assert(isWithin(prefix));
+ if (prefix.isRoot()) return *this;
+ if (path.size() == prefix.path.size()) return root;
+ return CanonPath(unchecked_t(), path.substr(prefix.path.size()));
+}
+
+void CanonPath::extend(const CanonPath & x)
+{
+ if (x.isRoot()) return;
+ if (isRoot())
+ path += x.rel();
+ else
+ path += x.abs();
+}
+
+CanonPath CanonPath::operator + (const CanonPath & x) const
+{
+ auto res = *this;
+ res.extend(x);
+ return res;
+}
+
+void CanonPath::push(std::string_view c)
+{
+ assert(c.find('/') == c.npos);
+ assert(c != "." && c != "..");
+ if (!isRoot()) path += '/';
+ path += c;
+}
+
+CanonPath CanonPath::operator + (std::string_view c) const
+{
+ auto res = *this;
+ res.push(c);
+ return res;
+}
+
+bool CanonPath::isAllowed(const std::set<CanonPath> & allowed) const
+{
+ /* Check if `this` is an exact match or the parent of an
+ allowed path. */
+ auto lb = allowed.lower_bound(*this);
+ if (lb != allowed.end()) {
+ if (lb->isWithin(*this))
+ return true;
+ }
+
+ /* Check if a parent of `this` is allowed. */
+ auto path = *this;
+ while (!path.isRoot()) {
+ path.pop();
+ if (allowed.count(path))
+ return true;
+ }
+
+ return false;
+}
+
+std::ostream & operator << (std::ostream & stream, const CanonPath & path)
+{
+ stream << path.abs();
+ return stream;
+}
+
+}
diff --git a/src/libutil/canon-path.hh b/src/libutil/canon-path.hh
new file mode 100644
index 000000000..9d5984584
--- /dev/null
+++ b/src/libutil/canon-path.hh
@@ -0,0 +1,173 @@
+#pragma once
+
+#include <string>
+#include <optional>
+#include <cassert>
+#include <iostream>
+#include <set>
+
+namespace nix {
+
+/* A canonical representation of a path. It ensures the following:
+
+ - It always starts with a slash.
+
+ - It never ends with a slash, except if the path is "/".
+
+ - A slash is never followed by a slash (i.e. no empty components).
+
+ - There are no components equal to '.' or '..'.
+
+ Note that the path does not need to correspond to an actually
+ existing path, and there is no guarantee that symlinks are
+ resolved.
+*/
+class CanonPath
+{
+ std::string path;
+
+public:
+
+ /* Construct a canon path from a non-canonical path. Any '.', '..'
+ or empty components are removed. */
+ CanonPath(std::string_view raw);
+
+ explicit CanonPath(const char * raw)
+ : CanonPath(std::string_view(raw))
+ { }
+
+ struct unchecked_t { };
+
+ CanonPath(unchecked_t _, std::string path)
+ : path(std::move(path))
+ { }
+
+ static CanonPath root;
+
+ /* If `raw` starts with a slash, return
+ `CanonPath(raw)`. Otherwise return a `CanonPath` representing
+ `root + "/" + raw`. */
+ CanonPath(std::string_view raw, const CanonPath & root);
+
+ bool isRoot() const
+ { return path.size() <= 1; }
+
+ explicit operator std::string_view() const
+ { return path; }
+
+ const std::string & abs() const
+ { return path; }
+
+ /* Like abs(), but return an empty string if this path is
+ '/'. Thus the returned string never ends in a slash. */
+ const std::string & absOrEmpty() const
+ {
+ const static std::string epsilon;
+ return isRoot() ? epsilon : path;
+ }
+
+ const char * c_str() const
+ { return path.c_str(); }
+
+ std::string_view rel() const
+ { return ((std::string_view) path).substr(1); }
+
+ struct Iterator
+ {
+ std::string_view remaining;
+ size_t slash;
+
+ Iterator(std::string_view remaining)
+ : remaining(remaining)
+ , slash(remaining.find('/'))
+ { }
+
+ bool operator != (const Iterator & x) const
+ { return remaining.data() != x.remaining.data(); }
+
+ const std::string_view operator * () const
+ { return remaining.substr(0, slash); }
+
+ void operator ++ ()
+ {
+ if (slash == remaining.npos)
+ remaining = remaining.substr(remaining.size());
+ else {
+ remaining = remaining.substr(slash + 1);
+ slash = remaining.find('/');
+ }
+ }
+ };
+
+ Iterator begin() const { return Iterator(rel()); }
+ Iterator end() const { return Iterator(rel().substr(path.size() - 1)); }
+
+ std::optional<CanonPath> parent() const;
+
+ /* Remove the last component. Panics if this path is the root. */
+ void pop();
+
+ std::optional<std::string_view> dirOf() const
+ {
+ if (isRoot()) return std::nullopt;
+ return ((std::string_view) path).substr(0, path.rfind('/'));
+ }
+
+ std::optional<std::string_view> baseName() const
+ {
+ if (isRoot()) return std::nullopt;
+ return ((std::string_view) path).substr(path.rfind('/') + 1);
+ }
+
+ bool operator == (const CanonPath & x) const
+ { return path == x.path; }
+
+ bool operator != (const CanonPath & x) const
+ { return path != x.path; }
+
+ /* Compare paths lexicographically except that path separators
+ are sorted before any other character. That is, in the sorted order
+ a directory is always followed directly by its children. For
+ instance, 'foo' < 'foo/bar' < 'foo!'. */
+ bool operator < (const CanonPath & x) const
+ {
+ auto i = path.begin();
+ auto j = x.path.begin();
+ for ( ; i != path.end() && j != x.path.end(); ++i, ++j) {
+ auto c_i = *i;
+ if (c_i == '/') c_i = 0;
+ auto c_j = *j;
+ if (c_j == '/') c_j = 0;
+ if (c_i < c_j) return true;
+ if (c_i > c_j) return false;
+ }
+ return i == path.end() && j != x.path.end();
+ }
+
+ /* Return true if `this` is equal to `parent` or a child of
+ `parent`. */
+ bool isWithin(const CanonPath & parent) const;
+
+ CanonPath removePrefix(const CanonPath & prefix) const;
+
+ /* Append another path to this one. */
+ void extend(const CanonPath & x);
+
+ /* Concatenate two paths. */
+ CanonPath operator + (const CanonPath & x) const;
+
+ /* Add a path component to this one. It must not contain any slashes. */
+ void push(std::string_view c);
+
+ CanonPath operator + (std::string_view c) const;
+
+ /* Check whether access to this path is allowed, which is the case
+ if 1) `this` is within any of the `allowed` paths; or 2) any of
+ the `allowed` paths are within `this`. (The latter condition
+ ensures access to the parents of allowed paths.) */
+ bool isAllowed(const std::set<CanonPath> & allowed) const;
+};
+
+std::ostream & operator << (std::ostream & stream, const CanonPath & path);
+
+}
diff --git a/src/libutil/error.cc b/src/libutil/error.cc
index 3bb3efb0e..1a1aecea5 100644
--- a/src/libutil/error.cc
+++ b/src/libutil/error.cc
@@ -9,9 +9,9 @@ namespace nix {
const std::string nativeSystem = SYSTEM;
-void BaseError::addTrace(std::optional<ErrPos> e, hintformat hint)
+void BaseError::addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint)
{
- err.traces.push_front(Trace { .pos = e, .hint = hint });
+ err.traces.push_front(Trace { .pos = std::move(e), .hint = hint });
}
// c++ std::exception descendants must have a 'const char* what()' function.
@@ -30,91 +30,46 @@ const std::string & BaseError::calcWhat() const
std::optional<std::string> ErrorInfo::programName = std::nullopt;
-std::ostream & operator<<(std::ostream & os, const hintformat & hf)
+std::ostream & operator <<(std::ostream & os, const hintformat & hf)
{
return os << hf.str();
}
-std::string showErrPos(const ErrPos & errPos)
+std::ostream & operator <<(std::ostream & str, const AbstractPos & pos)
{
- if (errPos.line > 0) {
- if (errPos.column > 0) {
- return fmt("%d:%d", errPos.line, errPos.column);
- } else {
- return fmt("%d", errPos.line);
- }
- }
- else {
- return "";
- }
+ pos.print(str);
+ str << ":" << pos.line;
+ if (pos.column > 0)
+ str << ":" << pos.column;
+ return str;
}
-std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos)
+std::optional<LinesOfCode> AbstractPos::getCodeLines() const
{
- if (errPos.line <= 0)
+ if (line == 0)
return std::nullopt;
- if (errPos.origin == foFile) {
- LinesOfCode loc;
- try {
- // FIXME: when running as the daemon, make sure we don't
- // open a file to which the client doesn't have access.
- AutoCloseFD fd = open(errPos.file.c_str(), O_RDONLY | O_CLOEXEC);
- if (!fd) return {};
-
- // count the newlines.
- int count = 0;
- std::string line;
- int pl = errPos.line - 1;
- do
- {
- line = readLine(fd.get());
- ++count;
- if (count < pl)
- ;
- else if (count == pl)
- loc.prevLineOfCode = line;
- else if (count == pl + 1)
- loc.errLineOfCode = line;
- else if (count == pl + 2) {
- loc.nextLineOfCode = line;
- break;
- }
- } while (true);
- return loc;
- }
- catch (EndOfFile & eof) {
- if (loc.errLineOfCode.has_value())
- return loc;
- else
- return std::nullopt;
- }
- catch (std::exception & e) {
- return std::nullopt;
- }
- } else {
- std::istringstream iss(errPos.file);
+ if (auto source = getSource()) {
+
+ std::istringstream iss(*source);
// count the newlines.
int count = 0;
- std::string line;
- int pl = errPos.line - 1;
+ std::string curLine;
+ int pl = line - 1;
LinesOfCode loc;
- do
- {
- std::getline(iss, line);
+ do {
+ std::getline(iss, curLine);
++count;
if (count < pl)
- {
;
- }
else if (count == pl) {
- loc.prevLineOfCode = line;
+ loc.prevLineOfCode = curLine;
} else if (count == pl + 1) {
- loc.errLineOfCode = line;
+ loc.errLineOfCode = curLine;
} else if (count == pl + 2) {
- loc.nextLineOfCode = line;
+ loc.nextLineOfCode = curLine;
break;
}
@@ -124,12 +79,14 @@ std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos)
return loc;
}
+
+ return std::nullopt;
}
// print lines of code to the ostream, indicating the error column.
void printCodeLines(std::ostream & out,
const std::string & prefix,
- const ErrPos & errPos,
+ const AbstractPos & errPos,
const LinesOfCode & loc)
{
// previous line of code.
@@ -176,28 +133,6 @@ void printCodeLines(std::ostream & out,
}
}
-void printAtPos(const ErrPos & pos, std::ostream & out)
-{
- if (pos) {
- switch (pos.origin) {
- case foFile: {
- out << fmt(ANSI_BLUE "at " ANSI_WARNING "%s:%s" ANSI_NORMAL ":", pos.file, showErrPos(pos));
- break;
- }
- case foString: {
- out << fmt(ANSI_BLUE "at " ANSI_WARNING "«string»:%s" ANSI_NORMAL ":", showErrPos(pos));
- break;
- }
- case foStdin: {
- out << fmt(ANSI_BLUE "at " ANSI_WARNING "«stdin»:%s" ANSI_NORMAL ":", showErrPos(pos));
- break;
- }
- default:
- throw Error("invalid FileOrigin in errPos");
- }
- }
-}
-
static std::string indent(std::string_view indentFirst, std::string_view indentRest, std::string_view s)
{
std::string res;
@@ -263,22 +198,22 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
std::ostringstream oss;
+ auto noSource = ANSI_ITALIC " (source not available)" ANSI_NORMAL "\n";
+
// traces
if (showTrace && !einfo.traces.empty()) {
for (const auto & trace : einfo.traces) {
oss << "\n" << "… " << trace.hint.str() << "\n";
- if (trace.pos.has_value() && (*trace.pos)) {
- auto pos = trace.pos.value();
- oss << "\n";
- printAtPos(pos, oss);
+ if (trace.pos) {
+ oss << "\n" << ANSI_BLUE << "at " ANSI_WARNING << *trace.pos << ANSI_NORMAL << ":";
- auto loc = getCodeLines(pos);
- if (loc.has_value()) {
+ if (auto loc = trace.pos->getCodeLines()) {
oss << "\n";
- printCodeLines(oss, "", pos, *loc);
+ printCodeLines(oss, "", *trace.pos, *loc);
oss << "\n";
- }
+ } else
+ oss << noSource;
}
}
oss << "\n" << prefix;
@@ -286,22 +221,19 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
oss << einfo.msg << "\n";
- if (einfo.errPos.has_value() && *einfo.errPos) {
- oss << "\n";
- printAtPos(*einfo.errPos, oss);
-
- auto loc = getCodeLines(*einfo.errPos);
+ if (einfo.errPos) {
+ oss << "\n" << ANSI_BLUE << "at " ANSI_WARNING << *einfo.errPos << ANSI_NORMAL << ":";
- // lines of code.
- if (loc.has_value()) {
+ if (auto loc = einfo.errPos->getCodeLines()) {
oss << "\n";
printCodeLines(oss, "", *einfo.errPos, *loc);
oss << "\n";
- }
+ } else
+ oss << noSource;
}
auto suggestions = einfo.suggestions.trim();
- if (! suggestions.suggestions.empty()){
+ if (!suggestions.suggestions.empty()) {
oss << "Did you mean " <<
suggestions.trim() <<
"?" << std::endl;
diff --git a/src/libutil/error.hh b/src/libutil/error.hh
index 3d1479c54..c3bb8c0df 100644
--- a/src/libutil/error.hh
+++ b/src/libutil/error.hh
@@ -54,13 +54,6 @@ typedef enum {
lvlVomit
} Verbosity;
-/* adjust Pos::origin bit width when adding stuff here */
-typedef enum {
- foFile,
- foStdin,
- foString
-} FileOrigin;
-
// the lines of code surrounding an error.
struct LinesOfCode {
std::optional<std::string> prevLineOfCode;
@@ -68,54 +61,37 @@ struct LinesOfCode {
std::optional<std::string> nextLineOfCode;
};
-// ErrPos indicates the location of an error in a nix file.
-struct ErrPos {
- int line = 0;
- int column = 0;
- std::string file;
- FileOrigin origin;
+/* An abstract type that represents a location in a source file. */
+struct AbstractPos
+{
+ uint32_t line = 0;
+ uint32_t column = 0;
- operator bool() const
- {
- return line != 0;
- }
+ /* Return the contents of the source file. */
+ virtual std::optional<std::string> getSource() const
+ { return std::nullopt; };
- // convert from the Pos struct, found in libexpr.
- template <class P>
- ErrPos & operator=(const P & pos)
- {
- origin = pos.origin;
- line = pos.line;
- column = pos.column;
- file = pos.file;
- return *this;
- }
+ virtual void print(std::ostream & out) const = 0;
- template <class P>
- ErrPos(const P & p)
- {
- *this = p;
- }
+ std::optional<LinesOfCode> getCodeLines() const;
};
-std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos);
+std::ostream & operator << (std::ostream & str, const AbstractPos & pos);
void printCodeLines(std::ostream & out,
const std::string & prefix,
- const ErrPos & errPos,
+ const AbstractPos & errPos,
const LinesOfCode & loc);
-void printAtPos(const ErrPos & pos, std::ostream & out);
-
struct Trace {
- std::optional<ErrPos> pos;
+ std::shared_ptr<AbstractPos> pos;
hintformat hint;
};
struct ErrorInfo {
Verbosity level;
hintformat msg;
- std::optional<ErrPos> errPos;
+ std::shared_ptr<AbstractPos> errPos;
std::list<Trace> traces;
Suggestions suggestions;
@@ -177,12 +153,12 @@ public:
const ErrorInfo & info() const { calcWhat(); return err; }
template<typename... Args>
- void addTrace(std::optional<ErrPos> e, const std::string & fs, const Args & ... args)
+ void addTrace(std::shared_ptr<AbstractPos> && e, const std::string & fs, const Args & ... args)
{
- addTrace(e, hintfmt(fs, args...));
+ addTrace(std::move(e), hintfmt(fs, args...));
}
- void addTrace(std::optional<ErrPos> e, hintformat hint);
+ void addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint);
bool hasTrace() const { return !err.traces.empty(); }
};
diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc
index ac86d8ac2..904ba6ebe 100644
--- a/src/libutil/logging.cc
+++ b/src/libutil/logging.cc
@@ -131,6 +131,21 @@ Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type,
logger.startActivity(id, lvl, type, s, fields, parent);
}
+void to_json(nlohmann::json & json, std::shared_ptr<AbstractPos> pos)
+{
+ if (pos) {
+ json["line"] = pos->line;
+ json["column"] = pos->column;
+ std::ostringstream str;
+ pos->print(str);
+ json["file"] = str.str();
+ } else {
+ json["line"] = nullptr;
+ json["column"] = nullptr;
+ json["file"] = nullptr;
+ }
+}
+
struct JSONLogger : Logger {
Logger & prevLogger;
@@ -177,27 +192,14 @@ struct JSONLogger : Logger {
json["level"] = ei.level;
json["msg"] = oss.str();
json["raw_msg"] = ei.msg.str();
-
- if (ei.errPos.has_value() && (*ei.errPos)) {
- json["line"] = ei.errPos->line;
- json["column"] = ei.errPos->column;
- json["file"] = ei.errPos->file;
- } else {
- json["line"] = nullptr;
- json["column"] = nullptr;
- json["file"] = nullptr;
- }
+ to_json(json, ei.errPos);
if (loggerSettings.showTrace.get() && !ei.traces.empty()) {
nlohmann::json traces = nlohmann::json::array();
for (auto iter = ei.traces.rbegin(); iter != ei.traces.rend(); ++iter) {
nlohmann::json stackFrame;
stackFrame["raw_msg"] = iter->hint.str();
- if (iter->pos.has_value() && (*iter->pos)) {
- stackFrame["line"] = iter->pos->line;
- stackFrame["column"] = iter->pos->column;
- stackFrame["file"] = iter->pos->file;
- }
+ to_json(stackFrame, iter->pos);
traces.push_back(stackFrame);
}
diff --git a/src/libutil/tests/canon-path.cc b/src/libutil/tests/canon-path.cc
new file mode 100644
index 000000000..c1c5adadf
--- /dev/null
+++ b/src/libutil/tests/canon-path.cc
@@ -0,0 +1,155 @@
+#include "canon-path.hh"
+
+#include <gtest/gtest.h>
+
+namespace nix {
+
+ TEST(CanonPath, basic) {
+ {
+ CanonPath p("/");
+ ASSERT_EQ(p.abs(), "/");
+ ASSERT_EQ(p.rel(), "");
+ ASSERT_EQ(p.baseName(), std::nullopt);
+ ASSERT_EQ(p.dirOf(), std::nullopt);
+ ASSERT_FALSE(p.parent());
+ }
+
+ {
+ CanonPath p("/foo//");
+ ASSERT_EQ(p.abs(), "/foo");
+ ASSERT_EQ(p.rel(), "foo");
+ ASSERT_EQ(*p.baseName(), "foo");
+ ASSERT_EQ(*p.dirOf(), ""); // FIXME: do we want this?
+ ASSERT_EQ(p.parent()->abs(), "/");
+ }
+
+ {
+ CanonPath p("foo/bar");
+ ASSERT_EQ(p.abs(), "/foo/bar");
+ ASSERT_EQ(p.rel(), "foo/bar");
+ ASSERT_EQ(*p.baseName(), "bar");
+ ASSERT_EQ(*p.dirOf(), "/foo");
+ ASSERT_EQ(p.parent()->abs(), "/foo");
+ }
+
+ {
+ CanonPath p("foo//bar/");
+ ASSERT_EQ(p.abs(), "/foo/bar");
+ ASSERT_EQ(p.rel(), "foo/bar");
+ ASSERT_EQ(*p.baseName(), "bar");
+ ASSERT_EQ(*p.dirOf(), "/foo");
+ }
+ }
+
+ TEST(CanonPath, pop) {
+ CanonPath p("foo/bar/x");
+ ASSERT_EQ(p.abs(), "/foo/bar/x");
+ p.pop();
+ ASSERT_EQ(p.abs(), "/foo/bar");
+ p.pop();
+ ASSERT_EQ(p.abs(), "/foo");
+ p.pop();
+ ASSERT_EQ(p.abs(), "/");
+ }
+
+ TEST(CanonPath, removePrefix) {
+ CanonPath p1("foo/bar");
+ CanonPath p2("foo/bar/a/b/c");
+ ASSERT_EQ(p2.removePrefix(p1).abs(), "/a/b/c");
+ ASSERT_EQ(p1.removePrefix(p1).abs(), "/");
+ ASSERT_EQ(p1.removePrefix(CanonPath("/")).abs(), "/foo/bar");
+ }
+
+ TEST(CanonPath, iter) {
+ {
+ CanonPath p("a//foo/bar//");
+ std::vector<std::string_view> ss;
+ for (auto & c : p) ss.push_back(c);
+ ASSERT_EQ(ss, std::vector<std::string_view>({"a", "foo", "bar"}));
+ }
+
+ {
+ CanonPath p("/");
+ std::vector<std::string_view> ss;
+ for (auto & c : p) ss.push_back(c);
+ ASSERT_EQ(ss, std::vector<std::string_view>());
+ }
+ }
+
+ TEST(CanonPath, concat) {
+ {
+ CanonPath p1("a//foo/bar//");
+ CanonPath p2("xyzzy/bla");
+ ASSERT_EQ((p1 + p2).abs(), "/a/foo/bar/xyzzy/bla");
+ }
+
+ {
+ CanonPath p1("/");
+ CanonPath p2("/a/b");
+ ASSERT_EQ((p1 + p2).abs(), "/a/b");
+ }
+
+ {
+ CanonPath p1("/a/b");
+ CanonPath p2("/");
+ ASSERT_EQ((p1 + p2).abs(), "/a/b");
+ }
+
+ {
+ CanonPath p("/foo/bar");
+ ASSERT_EQ((p + "x").abs(), "/foo/bar/x");
+ }
+
+ {
+ CanonPath p("/");
+ ASSERT_EQ((p + "foo" + "bar").abs(), "/foo/bar");
+ }
+ }
+
+ TEST(CanonPath, within) {
+ {
+ ASSERT_TRUE(CanonPath("foo").isWithin(CanonPath("foo")));
+ ASSERT_FALSE(CanonPath("foo").isWithin(CanonPath("bar")));
+ ASSERT_FALSE(CanonPath("foo").isWithin(CanonPath("fo")));
+ ASSERT_TRUE(CanonPath("foo/bar").isWithin(CanonPath("foo")));
+ ASSERT_FALSE(CanonPath("foo").isWithin(CanonPath("foo/bar")));
+ ASSERT_TRUE(CanonPath("/foo/bar/default.nix").isWithin(CanonPath("/")));
+ ASSERT_TRUE(CanonPath("/").isWithin(CanonPath("/")));
+ }
+ }
+
+ TEST(CanonPath, sort) {
+ ASSERT_FALSE(CanonPath("foo") < CanonPath("foo"));
+ ASSERT_TRUE (CanonPath("foo") < CanonPath("foo/bar"));
+ ASSERT_TRUE (CanonPath("foo/bar") < CanonPath("foo!"));
+ ASSERT_FALSE(CanonPath("foo!") < CanonPath("foo"));
+ ASSERT_TRUE (CanonPath("foo") < CanonPath("foo!"));
+ }
+
+ TEST(CanonPath, allowed) {
+ {
+ std::set<CanonPath> allowed {
+ CanonPath("foo/bar"),
+ CanonPath("foo!"),
+ CanonPath("xyzzy"),
+ CanonPath("a/b/c"),
+ };
+
+ ASSERT_TRUE (CanonPath("foo/bar").isAllowed(allowed));
+ ASSERT_TRUE (CanonPath("foo/bar/bla").isAllowed(allowed));
+ ASSERT_TRUE (CanonPath("foo").isAllowed(allowed));
+ ASSERT_FALSE(CanonPath("bar").isAllowed(allowed));
+ ASSERT_FALSE(CanonPath("bar/a").isAllowed(allowed));
+ ASSERT_TRUE (CanonPath("a").isAllowed(allowed));
+ ASSERT_TRUE (CanonPath("a/b").isAllowed(allowed));
+ ASSERT_TRUE (CanonPath("a/b/c").isAllowed(allowed));
+ ASSERT_TRUE (CanonPath("a/b/c/d").isAllowed(allowed));
+ ASSERT_TRUE (CanonPath("a/b/c/d/e").isAllowed(allowed));
+ ASSERT_FALSE(CanonPath("a/b/a").isAllowed(allowed));
+ ASSERT_FALSE(CanonPath("a/b/d").isAllowed(allowed));
+ ASSERT_FALSE(CanonPath("aaa").isAllowed(allowed));
+ ASSERT_FALSE(CanonPath("zzz").isAllowed(allowed));
+ ASSERT_TRUE (CanonPath("/").isAllowed(allowed));
+ }
+ }
+}
diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc
index 776c5f6db..31823a966 100644
--- a/src/nix-env/nix-env.cc
+++ b/src/nix-env/nix-env.cc
@@ -647,7 +647,7 @@ static void upgradeDerivations(Globals & globals,
} else newElems.push_back(i);
} catch (Error & e) {
- e.addTrace(std::nullopt, "while trying to find an upgrade for '%s'", i.queryName());
+ e.addTrace(nullptr, "while trying to find an upgrade for '%s'", i.queryName());
throw;
}
}
@@ -958,7 +958,7 @@ static void queryJSON(Globals & globals, std::vector<DrvInfo> & elems, bool prin
} catch (AssertionError & e) {
printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName());
} catch (Error & e) {
- e.addTrace(std::nullopt, "while querying the derivation named '%1%'", i.queryName());
+ e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName());
throw;
}
}
@@ -1262,7 +1262,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
} catch (AssertionError & e) {
printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName());
} catch (Error & e) {
- e.addTrace(std::nullopt, "while querying the derivation named '%1%'", i.queryName());
+ e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName());
throw;
}
}
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index 4de109754..1d90d1dac 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -164,6 +164,14 @@ struct BuildEnvironment
{
return vars == other.vars && bashFunctions == other.bashFunctions;
}
+
+ std::string getSystem() const
+ {
+ if (auto v = get(vars, "system"))
+ return getString(*v);
+ else
+ return settings.thisSystem;
+ }
};
const static std::string getEnvSh =
@@ -192,10 +200,12 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore
drv.env.erase("allowedRequisites");
drv.env.erase("disallowedReferences");
drv.env.erase("disallowedRequisites");
+ drv.env.erase("name");
/* Rehash and write the derivation. FIXME: would be nice to use
'buildDerivation', but that's privileged. */
drv.name += "-env";
+ drv.env.emplace("name", drv.name);
drv.inputSrcs.insert(std::move(getEnvShPath));
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
for (auto & output : drv.outputs) {
@@ -568,7 +578,7 @@ struct CmdDevelop : Common, MixEnvironment
}
}
- runProgramInStore(store, shell, args);
+ runProgramInStore(store, shell, args, buildEnvironment.getSystem());
}
};
diff --git a/src/nix/run.cc b/src/nix/run.cc
index 45d2dfd0d..6fca68047 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -9,6 +9,7 @@
#include "fs-accessor.hh"
#include "progress-bar.hh"
#include "eval.hh"
+#include "build/personality.hh"
#if __linux__
#include <sys/mount.h>
@@ -24,7 +25,8 @@ namespace nix {
void runProgramInStore(ref<Store> store,
const std::string & program,
- const Strings & args)
+ const Strings & args,
+ std::optional<std::string_view> system)
{
stopProgressBar();
@@ -44,7 +46,7 @@ void runProgramInStore(ref<Store> store,
throw Error("store '%s' is not a local store so it does not support command execution", store->getUri());
if (store->storeDir != store2->getRealStoreDir()) {
- Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), program };
+ Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), std::string(system.value_or("")), program };
for (auto & arg : args) helperArgs.push_back(arg);
execv(getSelfExe().value_or("nix").c_str(), stringsToCharPtrs(helperArgs).data());
@@ -52,6 +54,9 @@ void runProgramInStore(ref<Store> store,
throw SysError("could not execute chroot helper");
}
+ if (system)
+ setPersonality(*system);
+
execvp(program.c_str(), stringsToCharPtrs(args).data());
throw SysError("unable to execute '%s'", program);
@@ -199,6 +204,7 @@ void chrootHelper(int argc, char * * argv)
int p = 1;
std::string storeDir = argv[p++];
std::string realStoreDir = argv[p++];
+ std::string system = argv[p++];
std::string cmd = argv[p++];
Strings args;
while (p < argc)
@@ -262,6 +268,9 @@ void chrootHelper(int argc, char * * argv)
writeFile("/proc/self/uid_map", fmt("%d %d %d", uid, uid, 1));
writeFile("/proc/self/gid_map", fmt("%d %d %d", gid, gid, 1));
+ if (system != "")
+ setPersonality(system);
+
execvp(cmd.c_str(), stringsToCharPtrs(args).data());
throw SysError("unable to exec '%s'", cmd);
diff --git a/src/nix/run.hh b/src/nix/run.hh
index 6180a87dd..fed360158 100644
--- a/src/nix/run.hh
+++ b/src/nix/run.hh
@@ -6,6 +6,7 @@ namespace nix {
void runProgramInStore(ref<Store> store,
const std::string & program,
- const Strings & args);
+ const Strings & args,
+ std::optional<std::string_view> system = std::nullopt);
}
diff --git a/tests/fetchGitSubmodules.sh b/tests/fetchGitSubmodules.sh
index 5f104355f..50da4cb97 100644
--- a/tests/fetchGitSubmodules.sh
+++ b/tests/fetchGitSubmodules.sh
@@ -14,6 +14,15 @@ subRepo=$TEST_ROOT/gitSubmodulesSub
rm -rf ${rootRepo} ${subRepo} $TEST_HOME/.cache/nix
+# Submodules can't be fetched locally by default, which can cause
+# information leakage vulnerabilities, but for these tests our
+# submodule is intentionally local and it's all trusted, so we
+# disable this restriction. Setting it per repo is not sufficient, as
+# the repo-local config does not apply to the commands run from
+# outside the repos by Nix.
+export XDG_CONFIG_HOME=$TEST_HOME/.config
+git config --global protocol.file.allow always
+
initGitRepo() {
git init $1
git -C $1 config user.email "foobar@example.com"
diff --git a/tests/function-trace.sh b/tests/function-trace.sh
index d68e10df5..b0d6c9d59 100755
--- a/tests/function-trace.sh
+++ b/tests/function-trace.sh
@@ -32,40 +32,40 @@ expect_trace() {
# failure inside a tryEval
expect_trace 'builtins.tryEval (throw "example")' "
-function-trace entered (string):1:1 at
-function-trace entered (string):1:19 at
-function-trace exited (string):1:19 at
-function-trace exited (string):1:1 at
+function-trace entered «string»:1:1 at
+function-trace entered «string»:1:19 at
+function-trace exited «string»:1:19 at
+function-trace exited «string»:1:1 at
"
# Missing argument to a formal function
expect_trace '({ x }: x) { }' "
-function-trace entered (string):1:1 at
-function-trace exited (string):1:1 at
+function-trace entered «string»:1:1 at
+function-trace exited «string»:1:1 at
"
# Too many arguments to a formal function
expect_trace '({ x }: x) { x = "x"; y = "y"; }' "
-function-trace entered (string):1:1 at
-function-trace exited (string):1:1 at
+function-trace entered «string»:1:1 at
+function-trace exited «string»:1:1 at
"
# Not enough arguments to a lambda
expect_trace '(x: y: x + y) 1' "
-function-trace entered (string):1:1 at
-function-trace exited (string):1:1 at
+function-trace entered «string»:1:1 at
+function-trace exited «string»:1:1 at
"
# Too many arguments to a lambda
expect_trace '(x: x) 1 2' "
-function-trace entered (string):1:1 at
-function-trace exited (string):1:1 at
+function-trace entered «string»:1:1 at
+function-trace exited «string»:1:1 at
"
# Not a function
expect_trace '1 2' "
-function-trace entered (string):1:1 at
-function-trace exited (string):1:1 at
+function-trace entered «string»:1:1 at
+function-trace exited «string»:1:1 at
"
set -e