aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libcmd/built-path.cc67
-rw-r--r--src/libcmd/built-path.hh47
-rw-r--r--src/libcmd/common-eval-args.cc4
-rw-r--r--src/libcmd/common-eval-args.hh3
-rw-r--r--src/libcmd/installables.hh1
-rw-r--r--src/libcmd/repl.cc8
-rw-r--r--src/libcmd/repl.hh2
-rw-r--r--src/libexpr/eval.cc24
-rw-r--r--src/libexpr/eval.hh26
-rw-r--r--src/libexpr/parser.y86
-rw-r--r--src/libexpr/primops.cc16
-rw-r--r--src/libexpr/primops/fetchClosure.cc250
-rw-r--r--src/libexpr/search-path.cc56
-rw-r--r--src/libexpr/search-path.hh108
-rw-r--r--src/libexpr/tests/search-path.cc90
-rw-r--r--src/libstore/build/local-derivation-goal.cc20
-rw-r--r--src/libstore/derived-path.cc56
-rw-r--r--src/libstore/derived-path.hh41
-rw-r--r--src/libstore/globals.hh76
-rw-r--r--src/libstore/make-content-addressed.cc11
-rw-r--r--src/libstore/make-content-addressed.hh13
-rw-r--r--src/libstore/ssh.cc5
-rw-r--r--src/nix/nix.md2
-rw-r--r--src/nix/upgrade-nix.cc2
24 files changed, 727 insertions, 287 deletions
diff --git a/src/libcmd/built-path.cc b/src/libcmd/built-path.cc
new file mode 100644
index 000000000..db9c440e3
--- /dev/null
+++ b/src/libcmd/built-path.cc
@@ -0,0 +1,67 @@
+#include "built-path.hh"
+#include "derivations.hh"
+#include "store-api.hh"
+
+#include <nlohmann/json.hpp>
+
+#include <optional>
+
+namespace nix {
+
+nlohmann::json BuiltPath::Built::toJSON(ref<Store> store) const {
+ nlohmann::json res;
+ res["drvPath"] = store->printStorePath(drvPath);
+ for (const auto& [output, path] : outputs) {
+ res["outputs"][output] = store->printStorePath(path);
+ }
+ return res;
+}
+
+StorePathSet BuiltPath::outPaths() const
+{
+ return std::visit(
+ overloaded{
+ [](const BuiltPath::Opaque & p) { return StorePathSet{p.path}; },
+ [](const BuiltPath::Built & b) {
+ StorePathSet res;
+ for (auto & [_, path] : b.outputs)
+ res.insert(path);
+ return res;
+ },
+ }, raw()
+ );
+}
+
+RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
+{
+ RealisedPath::Set res;
+ std::visit(
+ overloaded{
+ [&](const BuiltPath::Opaque & p) { res.insert(p.path); },
+ [&](const BuiltPath::Built & p) {
+ auto drvHashes =
+ staticOutputHashes(store, store.readDerivation(p.drvPath));
+ for (auto& [outputName, outputPath] : p.outputs) {
+ if (experimentalFeatureSettings.isEnabled(
+ Xp::CaDerivations)) {
+ auto drvOutput = get(drvHashes, outputName);
+ if (!drvOutput)
+ throw Error(
+ "the derivation '%s' has unrealised output '%s' (derived-path.cc/toRealisedPaths)",
+ store.printStorePath(p.drvPath), outputName);
+ auto thisRealisation = store.queryRealisation(
+ DrvOutput{*drvOutput, outputName});
+ assert(thisRealisation); // We’ve built it, so we must
+ // have the realisation
+ res.insert(*thisRealisation);
+ } else {
+ res.insert(outputPath);
+ }
+ }
+ },
+ },
+ raw());
+ return res;
+}
+
+}
diff --git a/src/libcmd/built-path.hh b/src/libcmd/built-path.hh
new file mode 100644
index 000000000..c563a46e9
--- /dev/null
+++ b/src/libcmd/built-path.hh
@@ -0,0 +1,47 @@
+#include "derived-path.hh"
+
+namespace nix {
+
+/**
+ * A built derived path with hints in the form of optional concrete output paths.
+ *
+ * See 'BuiltPath' for more an explanation.
+ */
+struct BuiltPathBuilt {
+ StorePath drvPath;
+ std::map<std::string, StorePath> outputs;
+
+ nlohmann::json toJSON(ref<Store> store) const;
+ static BuiltPathBuilt parse(const Store & store, std::string_view);
+
+ GENERATE_CMP(BuiltPathBuilt, me->drvPath, me->outputs);
+};
+
+using _BuiltPathRaw = std::variant<
+ DerivedPath::Opaque,
+ BuiltPathBuilt
+>;
+
+/**
+ * A built path. Similar to a DerivedPath, but enriched with the corresponding
+ * output path(s).
+ */
+struct BuiltPath : _BuiltPathRaw {
+ using Raw = _BuiltPathRaw;
+ using Raw::Raw;
+
+ using Opaque = DerivedPathOpaque;
+ using Built = BuiltPathBuilt;
+
+ inline const Raw & raw() const {
+ return static_cast<const Raw &>(*this);
+ }
+
+ StorePathSet outPaths() const;
+ RealisedPath::Set toRealisedPaths(Store & store) const;
+
+};
+
+typedef std::vector<BuiltPath> BuiltPaths;
+
+}
diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc
index 7f97364a1..3df2c71a5 100644
--- a/src/libcmd/common-eval-args.cc
+++ b/src/libcmd/common-eval-args.cc
@@ -105,7 +105,9 @@ MixEvalArgs::MixEvalArgs()
)",
.category = category,
.labels = {"path"},
- .handler = {[&](std::string s) { searchPath.push_back(s); }}
+ .handler = {[&](std::string s) {
+ searchPath.elements.emplace_back(SearchPath::Elem::parse(s));
+ }}
});
addFlag({
diff --git a/src/libcmd/common-eval-args.hh b/src/libcmd/common-eval-args.hh
index b65cb5b20..6359b2579 100644
--- a/src/libcmd/common-eval-args.hh
+++ b/src/libcmd/common-eval-args.hh
@@ -3,6 +3,7 @@
#include "args.hh"
#include "common-args.hh"
+#include "search-path.hh"
namespace nix {
@@ -19,7 +20,7 @@ struct MixEvalArgs : virtual Args, virtual MixRepair
Bindings * getAutoArgs(EvalState & state);
- Strings searchPath;
+ SearchPath searchPath;
std::optional<std::string> evalStoreUrl;
diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh
index 42d6c7c7c..b0dc0dc02 100644
--- a/src/libcmd/installables.hh
+++ b/src/libcmd/installables.hh
@@ -5,6 +5,7 @@
#include "path.hh"
#include "outputs-spec.hh"
#include "derived-path.hh"
+#include "built-path.hh"
#include "store-api.hh"
#include "build-result.hh"
diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
index 4b160a100..f9e9c2bf8 100644
--- a/src/libcmd/repl.cc
+++ b/src/libcmd/repl.cc
@@ -68,7 +68,7 @@ struct NixRepl
const Path historyFile;
- NixRepl(const Strings & searchPath, nix::ref<Store> store,ref<EvalState> state,
+ NixRepl(const SearchPath & searchPath, nix::ref<Store> store,ref<EvalState> state,
std::function<AnnotatedValues()> getValues);
virtual ~NixRepl();
@@ -104,7 +104,7 @@ std::string removeWhitespace(std::string s)
}
-NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store, ref<EvalState> state,
+NixRepl::NixRepl(const SearchPath & searchPath, nix::ref<Store> store, ref<EvalState> state,
std::function<NixRepl::AnnotatedValues()> getValues)
: AbstractNixRepl(state)
, debugTraceIndex(0)
@@ -1024,7 +1024,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
std::unique_ptr<AbstractNixRepl> AbstractNixRepl::create(
- const Strings & searchPath, nix::ref<Store> store, ref<EvalState> state,
+ const SearchPath & searchPath, nix::ref<Store> store, ref<EvalState> state,
std::function<AnnotatedValues()> getValues)
{
return std::make_unique<NixRepl>(
@@ -1044,7 +1044,7 @@ void AbstractNixRepl::runSimple(
NixRepl::AnnotatedValues values;
return values;
};
- const Strings & searchPath = {};
+ SearchPath searchPath = {};
auto repl = std::make_unique<NixRepl>(
searchPath,
openStore(),
diff --git a/src/libcmd/repl.hh b/src/libcmd/repl.hh
index 731c8e6db..6d88883fe 100644
--- a/src/libcmd/repl.hh
+++ b/src/libcmd/repl.hh
@@ -25,7 +25,7 @@ struct AbstractNixRepl
typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues;
static std::unique_ptr<AbstractNixRepl> create(
- const Strings & searchPath, nix::ref<Store> store, ref<EvalState> state,
+ const SearchPath & searchPath, nix::ref<Store> store, ref<EvalState> state,
std::function<AnnotatedValues()> getValues);
static void runSimple(
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 706a19024..be1bdb806 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -498,7 +498,7 @@ ErrorBuilder & ErrorBuilder::withFrame(const Env & env, const Expr & expr)
EvalState::EvalState(
- const Strings & _searchPath,
+ const SearchPath & _searchPath,
ref<Store> store,
std::shared_ptr<Store> buildStore)
: sWith(symbols.create("<with>"))
@@ -563,30 +563,32 @@ EvalState::EvalState(
/* Initialise the Nix expression search path. */
if (!evalSettings.pureEval) {
- for (auto & i : _searchPath) addToSearchPath(i);
- for (auto & i : evalSettings.nixPath.get()) addToSearchPath(i);
+ for (auto & i : _searchPath.elements)
+ addToSearchPath(SearchPath::Elem {i});
+ for (auto & i : evalSettings.nixPath.get())
+ addToSearchPath(SearchPath::Elem::parse(i));
}
if (evalSettings.restrictEval || evalSettings.pureEval) {
allowedPaths = PathSet();
- for (auto & i : searchPath) {
- auto r = resolveSearchPathElem(i);
- if (!r.first) continue;
+ for (auto & i : searchPath.elements) {
+ auto r = resolveSearchPathPath(i.path);
+ if (!r) continue;
- auto path = r.second;
+ auto path = *std::move(r);
- if (store->isInStore(r.second)) {
+ if (store->isInStore(path)) {
try {
StorePathSet closure;
- store->computeFSClosure(store->toStorePath(r.second).first, closure);
+ store->computeFSClosure(store->toStorePath(path).first, closure);
for (auto & path : closure)
allowPath(path);
} catch (InvalidPath &) {
- allowPath(r.second);
+ allowPath(path);
}
} else
- allowPath(r.second);
+ allowPath(path);
}
}
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index e3676c1b7..46fa96d05 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -9,6 +9,7 @@
#include "config.hh"
#include "experimental-features.hh"
#include "input-accessor.hh"
+#include "search-path.hh"
#include <map>
#include <optional>
@@ -122,15 +123,6 @@ std::string printValue(const EvalState & state, const Value & v);
std::ostream & operator << (std::ostream & os, const ValueType t);
-struct SearchPathElem
-{
- std::string prefix;
- // FIXME: maybe change this to an std::variant<SourcePath, URL>.
- std::string path;
-};
-typedef std::list<SearchPathElem> SearchPath;
-
-
/**
* Initialise the Boehm GC, if applicable.
*/
@@ -317,7 +309,7 @@ private:
SearchPath searchPath;
- std::map<std::string, std::pair<bool, std::string>> searchPathResolved;
+ std::map<std::string, std::optional<std::string>> searchPathResolved;
/**
* Cache used by checkSourcePath().
@@ -344,12 +336,12 @@ private:
public:
EvalState(
- const Strings & _searchPath,
+ const SearchPath & _searchPath,
ref<Store> store,
std::shared_ptr<Store> buildStore = nullptr);
~EvalState();
- void addToSearchPath(const std::string & s);
+ void addToSearchPath(SearchPath::Elem && elem);
SearchPath getSearchPath() { return searchPath; }
@@ -431,12 +423,16 @@ public:
* Look up a file in the search path.
*/
SourcePath findFile(const std::string_view path);
- SourcePath findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos);
+ SourcePath findFile(const SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos);
/**
+ * Try to resolve a search path value (not the optinal key part)
+ *
* If the specified search path element is a URI, download it.
+ *
+ * If it is not found, return `std::nullopt`
*/
- std::pair<bool, std::string> resolveSearchPathElem(const SearchPathElem & elem);
+ std::optional<std::string> resolveSearchPathPath(const SearchPath::Path & path);
/**
* Evaluate an expression to normal form
@@ -810,7 +806,7 @@ struct EvalSettings : Config
List of directories to be searched for `<...>` file references
In particular, outside of [pure evaluation mode](#conf-pure-evaluation), this determines the value of
- [`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtin-constants-nixPath).
+ [`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath).
)"};
Setting<bool> restrictEval{
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index 0d0004f9f..0a1ad9967 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -663,7 +663,7 @@ Expr * EvalState::parse(
ParseData data {
.state = *this,
.symbols = symbols,
- .basePath = std::move(basePath),
+ .basePath = basePath,
.origin = {origin},
};
@@ -734,22 +734,9 @@ Expr * EvalState::parseStdin()
}
-void EvalState::addToSearchPath(const std::string & s)
+void EvalState::addToSearchPath(SearchPath::Elem && elem)
{
- size_t pos = s.find('=');
- std::string prefix;
- Path path;
- if (pos == std::string::npos) {
- path = s;
- } else {
- prefix = std::string(s, 0, pos);
- path = std::string(s, pos + 1);
- }
-
- searchPath.emplace_back(SearchPathElem {
- .prefix = prefix,
- .path = path,
- });
+ searchPath.elements.emplace_back(std::move(elem));
}
@@ -759,22 +746,19 @@ SourcePath EvalState::findFile(const std::string_view path)
}
-SourcePath EvalState::findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos)
+SourcePath EvalState::findFile(const SearchPath & searchPath, const std::string_view path, const PosIdx pos)
{
- for (auto & i : searchPath) {
- std::string suffix;
- if (i.prefix.empty())
- suffix = concatStrings("/", path);
- else {
- auto s = i.prefix.size();
- if (path.compare(0, s, i.prefix) != 0 ||
- (path.size() > s && path[s] != '/'))
- continue;
- suffix = path.size() == s ? "" : concatStrings("/", path.substr(s));
- }
- auto r = resolveSearchPathElem(i);
- if (!r.first) continue;
- Path res = r.second + suffix;
+ for (auto & i : searchPath.elements) {
+ auto suffixOpt = i.prefix.suffixIfPotentialMatch(path);
+
+ if (!suffixOpt) continue;
+ auto suffix = *suffixOpt;
+
+ auto rOpt = resolveSearchPathPath(i.path);
+ if (!rOpt) continue;
+ auto r = *rOpt;
+
+ Path res = suffix == "" ? r : concatStrings(r, "/", suffix);
if (pathExists(res)) return CanonPath(canonPath(res));
}
@@ -791,49 +775,53 @@ SourcePath EvalState::findFile(SearchPath & searchPath, const std::string_view p
}
-std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathElem & elem)
+std::optional<std::string> EvalState::resolveSearchPathPath(const SearchPath::Path & value0)
{
- auto i = searchPathResolved.find(elem.path);
+ auto & value = value0.s;
+ auto i = searchPathResolved.find(value);
if (i != searchPathResolved.end()) return i->second;
- std::pair<bool, std::string> res;
+ std::optional<std::string> res;
- if (EvalSettings::isPseudoUrl(elem.path)) {
+ if (EvalSettings::isPseudoUrl(value)) {
try {
auto storePath = fetchers::downloadTarball(
- store, EvalSettings::resolvePseudoUrl(elem.path), "source", false).tree.storePath;
- res = { true, store->toRealPath(storePath) };
+ store, EvalSettings::resolvePseudoUrl(value), "source", false).tree.storePath;
+ res = { store->toRealPath(storePath) };
} catch (FileTransferError & e) {
logWarning({
- .msg = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.path)
+ .msg = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", value)
});
- res = { false, "" };
+ res = std::nullopt;
}
}
- else if (hasPrefix(elem.path, "flake:")) {
+ else if (hasPrefix(value, "flake:")) {
experimentalFeatureSettings.require(Xp::Flakes);
- auto flakeRef = parseFlakeRef(elem.path.substr(6), {}, true, false);
- debug("fetching flake search path element '%s''", elem.path);
+ auto flakeRef = parseFlakeRef(value.substr(6), {}, true, false);
+ debug("fetching flake search path element '%s''", value);
auto storePath = flakeRef.resolve(store).fetchTree(store).first.storePath;
- res = { true, store->toRealPath(storePath) };
+ res = { store->toRealPath(storePath) };
}
else {
- auto path = absPath(elem.path);
+ auto path = absPath(value);
if (pathExists(path))
- res = { true, path };
+ res = { path };
else {
logWarning({
- .msg = hintfmt("Nix search path entry '%1%' does not exist, ignoring", elem.path)
+ .msg = hintfmt("Nix search path entry '%1%' does not exist, ignoring", value)
});
- res = { false, "" };
+ res = std::nullopt;
}
}
- debug("resolved search path element '%s' to '%s'", elem.path, res.second);
+ if (res)
+ debug("resolved search path element '%s' to '%s'", value, *res);
+ else
+ debug("failed to resolve search path element '%s'", value);
- searchPathResolved[elem.path] = res;
+ searchPathResolved[value] = res;
return res;
}
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index a8b4a069f..7ff17b6ee 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -1503,6 +1503,8 @@ static RegisterPrimOp primop_storePath({
in a new path (e.g. `/nix/store/ld01dnzc…-source-source`).
Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
+
+ See also [`builtins.fetchClosure`](#builtins-fetchClosure).
)",
.fun = prim_storePath,
});
@@ -1657,9 +1659,9 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
}));
}
- searchPath.emplace_back(SearchPathElem {
- .prefix = prefix,
- .path = path,
+ searchPath.elements.emplace_back(SearchPath::Elem {
+ .prefix = SearchPath::Prefix { .s = prefix },
+ .path = SearchPath::Path { .s = path },
});
}
@@ -4318,12 +4320,12 @@ void EvalState::createBaseEnv()
});
/* Add a value containing the current Nix expression search path. */
- mkList(v, searchPath.size());
+ mkList(v, searchPath.elements.size());
int n = 0;
- for (auto & i : searchPath) {
+ for (auto & i : searchPath.elements) {
auto attrs = buildBindings(2);
- attrs.alloc("path").mkString(i.path);
- attrs.alloc("prefix").mkString(i.prefix);
+ attrs.alloc("path").mkString(i.path.s);
+ attrs.alloc("prefix").mkString(i.prefix.s);
(v.listElems()[n++] = allocValue())->mkAttrs(attrs);
}
addConstant("__nixPath", v, {
diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc
index bae849f61..7fe8203f4 100644
--- a/src/libexpr/primops/fetchClosure.cc
+++ b/src/libexpr/primops/fetchClosure.cc
@@ -5,37 +5,150 @@
namespace nix {
+/**
+ * Handler for the content addressed case.
+ *
+ * @param state Evaluator state and store to write to.
+ * @param fromStore Store containing the path to rewrite.
+ * @param fromPath Source path to be rewritten.
+ * @param toPathMaybe Path to write the rewritten path to. If empty, the error shows the actual path.
+ * @param v Return `Value`
+ */
+static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Store & fromStore, const StorePath & fromPath, const std::optional<StorePath> & toPathMaybe, Value &v) {
+
+ // establish toPath or throw
+
+ if (!toPathMaybe || !state.store->isValidPath(*toPathMaybe)) {
+ auto rewrittenPath = makeContentAddressed(fromStore, *state.store, fromPath);
+ if (toPathMaybe && *toPathMaybe != rewrittenPath)
+ throw Error({
+ .msg = hintfmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected",
+ state.store->printStorePath(fromPath),
+ state.store->printStorePath(rewrittenPath),
+ state.store->printStorePath(*toPathMaybe)),
+ .errPos = state.positions[pos]
+ });
+ if (!toPathMaybe)
+ throw Error({
+ .msg = hintfmt(
+ "rewriting '%s' to content-addressed form yielded '%s'\n"
+ "Use this value for the 'toPath' attribute passed to 'fetchClosure'",
+ state.store->printStorePath(fromPath),
+ state.store->printStorePath(rewrittenPath)),
+ .errPos = state.positions[pos]
+ });
+ }
+
+ auto toPath = *toPathMaybe;
+
+ // check and return
+
+ auto resultInfo = state.store->queryPathInfo(toPath);
+
+ if (!resultInfo->isContentAddressed(*state.store)) {
+ // We don't perform the rewriting when outPath already exists, as an optimisation.
+ // However, we can quickly detect a mistake if the toPath is input addressed.
+ throw Error({
+ .msg = hintfmt(
+ "The 'toPath' value '%s' is input-addressed, so it can't possibly be the result of rewriting to a content-addressed path.\n\n"
+ "Set 'toPath' to an empty string to make Nix report the correct content-addressed path.",
+ state.store->printStorePath(toPath)),
+ .errPos = state.positions[pos]
+ });
+ }
+
+ state.mkStorePathString(toPath, v);
+}
+
+/**
+ * Fetch the closure and make sure it's content addressed.
+ */
+static void runFetchClosureWithContentAddressedPath(EvalState & state, const PosIdx pos, Store & fromStore, const StorePath & fromPath, Value & v) {
+
+ if (!state.store->isValidPath(fromPath))
+ copyClosure(fromStore, *state.store, RealisedPath::Set { fromPath });
+
+ auto info = state.store->queryPathInfo(fromPath);
+
+ if (!info->isContentAddressed(*state.store)) {
+ throw Error({
+ .msg = hintfmt(
+ "The 'fromPath' value '%s' is input-addressed, but 'inputAddressed' is set to 'false' (default).\n\n"
+ "If you do intend to fetch an input-addressed store path, add\n\n"
+ " inputAddressed = true;\n\n"
+ "to the 'fetchClosure' arguments.\n\n"
+ "Note that to ensure authenticity input-addressed store paths, users must configure a trusted binary cache public key on their systems. This is not needed for content-addressed paths.",
+ state.store->printStorePath(fromPath)),
+ .errPos = state.positions[pos]
+ });
+ }
+
+ state.mkStorePathString(fromPath, v);
+}
+
+/**
+ * Fetch the closure and make sure it's input addressed.
+ */
+static void runFetchClosureWithInputAddressedPath(EvalState & state, const PosIdx pos, Store & fromStore, const StorePath & fromPath, Value & v) {
+
+ if (!state.store->isValidPath(fromPath))
+ copyClosure(fromStore, *state.store, RealisedPath::Set { fromPath });
+
+ auto info = state.store->queryPathInfo(fromPath);
+
+ if (info->isContentAddressed(*state.store)) {
+ throw Error({
+ .msg = hintfmt(
+ "The store object referred to by 'fromPath' at '%s' is not input-addressed, but 'inputAddressed' is set to 'true'.\n\n"
+ "Remove the 'inputAddressed' attribute (it defaults to 'false') to expect 'fromPath' to be content-addressed",
+ state.store->printStorePath(fromPath)),
+ .errPos = state.positions[pos]
+ });
+ }
+
+ state.mkStorePathString(fromPath, v);
+}
+
+typedef std::optional<StorePath> StorePathOrGap;
+
static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchClosure");
std::optional<std::string> fromStoreUrl;
std::optional<StorePath> fromPath;
- bool toCA = false;
- std::optional<StorePath> toPath;
+ std::optional<StorePathOrGap> toPath;
+ std::optional<bool> inputAddressedMaybe;
for (auto & attr : *args[0]->attrs) {
const auto & attrName = state.symbols[attr.name];
+ auto attrHint = [&]() -> std::string {
+ return "while evaluating the '" + attrName + "' attribute passed to builtins.fetchClosure";
+ };
if (attrName == "fromPath") {
NixStringContext context;
- fromPath = state.coerceToStorePath(attr.pos, *attr.value, context,
- "while evaluating the 'fromPath' attribute passed to builtins.fetchClosure");
+ fromPath = state.coerceToStorePath(attr.pos, *attr.value, context, attrHint());
}
else if (attrName == "toPath") {
state.forceValue(*attr.value, attr.pos);
- toCA = true;
- if (attr.value->type() != nString || attr.value->string.s != std::string("")) {
+ bool isEmptyString = attr.value->type() == nString && attr.value->string.s == std::string("");
+ if (isEmptyString) {
+ toPath = StorePathOrGap {};
+ }
+ else {
NixStringContext context;
- toPath = state.coerceToStorePath(attr.pos, *attr.value, context,
- "while evaluating the 'toPath' attribute passed to builtins.fetchClosure");
+ toPath = state.coerceToStorePath(attr.pos, *attr.value, context, attrHint());
}
}
else if (attrName == "fromStore")
fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos,
- "while evaluating the 'fromStore' attribute passed to builtins.fetchClosure");
+ attrHint());
+
+ else if (attrName == "inputAddressed")
+ inputAddressedMaybe = state.forceBool(*attr.value, attr.pos, attrHint());
else
throw Error({
@@ -50,6 +163,18 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
.errPos = state.positions[pos]
});
+ bool inputAddressed = inputAddressedMaybe.value_or(false);
+
+ if (inputAddressed) {
+ if (toPath)
+ throw Error({
+ .msg = hintfmt("attribute '%s' is set to true, but '%s' is also set. Please remove one of them",
+ "inputAddressed",
+ "toPath"),
+ .errPos = state.positions[pos]
+ });
+ }
+
if (!fromStoreUrl)
throw Error({
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
@@ -74,55 +199,40 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
auto fromStore = openStore(parsedURL.to_string());
- if (toCA) {
- if (!toPath || !state.store->isValidPath(*toPath)) {
- auto remappings = makeContentAddressed(*fromStore, *state.store, { *fromPath });
- auto i = remappings.find(*fromPath);
- assert(i != remappings.end());
- if (toPath && *toPath != i->second)
- throw Error({
- .msg = hintfmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected",
- state.store->printStorePath(*fromPath),
- state.store->printStorePath(i->second),
- state.store->printStorePath(*toPath)),
- .errPos = state.positions[pos]
- });
- if (!toPath)
- throw Error({
- .msg = hintfmt(
- "rewriting '%s' to content-addressed form yielded '%s'; "
- "please set this in the 'toPath' attribute passed to 'fetchClosure'",
- state.store->printStorePath(*fromPath),
- state.store->printStorePath(i->second)),
- .errPos = state.positions[pos]
- });
- }
- } else {
- if (!state.store->isValidPath(*fromPath))
- copyClosure(*fromStore, *state.store, RealisedPath::Set { *fromPath });
- toPath = fromPath;
- }
-
- /* In pure mode, require a CA path. */
- if (evalSettings.pureEval) {
- auto info = state.store->queryPathInfo(*toPath);
- if (!info->isContentAddressed(*state.store))
- throw Error({
- .msg = hintfmt("in pure mode, 'fetchClosure' requires a content-addressed path, which '%s' isn't",
- state.store->printStorePath(*toPath)),
- .errPos = state.positions[pos]
- });
- }
-
- state.mkStorePathString(*toPath, v);
+ if (toPath)
+ runFetchClosureWithRewrite(state, pos, *fromStore, *fromPath, *toPath, v);
+ else if (inputAddressed)
+ runFetchClosureWithInputAddressedPath(state, pos, *fromStore, *fromPath, v);
+ else
+ runFetchClosureWithContentAddressedPath(state, pos, *fromStore, *fromPath, v);
}
static RegisterPrimOp primop_fetchClosure({
.name = "__fetchClosure",
.args = {"args"},
.doc = R"(
- Fetch a Nix store closure from a binary cache, rewriting it into
- content-addressed form. For example,
+ Fetch a store path [closure](@docroot@/glossary.md#gloss-closure) from a binary cache, and return the store path as a string with context.
+
+ This function can be invoked in three ways, that we will discuss in order of preference.
+
+ **Fetch a content-addressed store path**
+
+ Example:
+
+ ```nix
+ builtins.fetchClosure {
+ fromStore = "https://cache.nixos.org";
+ fromPath = /nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1;
+ }
+ ```
+
+ This is the simplest invocation, and it does not require the user of the expression to configure [`trusted-public-keys`](@docroot@/command-ref/conf-file.md#conf-trusted-public-keys) to ensure their authenticity.
+
+ If your store path is [input addressed](@docroot@/glossary.md#gloss-input-addressed-store-object) instead of content addressed, consider the other two invocations.
+
+ **Fetch any store path and rewrite it to a fully content-addressed store path**
+
+ Example:
```nix
builtins.fetchClosure {
@@ -132,28 +242,42 @@ static RegisterPrimOp primop_fetchClosure({
}
```
- fetches `/nix/store/r2jd...` from the specified binary cache,
+ This example fetches `/nix/store/r2jd...` from the specified binary cache,
and rewrites it into the content-addressed store path
`/nix/store/ldbh...`.
- If `fromPath` is already content-addressed, or if you are
- allowing impure evaluation (`--impure`), then `toPath` may be
- omitted.
+ Like the previous example, no extra configuration or privileges are required.
To find out the correct value for `toPath` given a `fromPath`,
- you can use `nix store make-content-addressed`:
+ use [`nix store make-content-addressed`](@docroot@/command-ref/new-cli/nix3-store-make-content-addressed.md):
```console
# nix store make-content-addressed --from https://cache.nixos.org /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1
rewrote '/nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1' to '/nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1'
```
- This function is similar to `builtins.storePath` in that it
- allows you to use a previously built store path in a Nix
- expression. However, it is more reproducible because it requires
- specifying a binary cache from which the path can be fetched.
- Also, requiring a content-addressed final store path avoids the
- need for users to configure binary cache public keys.
+ Alternatively, set `toPath = ""` and find the correct `toPath` in the error message.
+
+ **Fetch an input-addressed store path as is**
+
+ Example:
+
+ ```nix
+ builtins.fetchClosure {
+ fromStore = "https://cache.nixos.org";
+ fromPath = /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1;
+ inputAddressed = true;
+ }
+ ```
+
+ It is possible to fetch an [input-addressed store path](@docroot@/glossary.md#gloss-input-addressed-store-object) and return it as is.
+ However, this is the least preferred way of invoking `fetchClosure`, because it requires that the input-addressed paths are trusted by the Nix configuration.
+
+ **`builtins.storePath`**
+
+ `fetchClosure` is similar to [`builtins.storePath`](#builtins-storePath) in that it allows you to use a previously built store path in a Nix expression.
+ However, `fetchClosure` is more reproducible because it specifies a binary cache from which the path can be fetched.
+ Also, using content-addressed store paths does not require users to configure [`trusted-public-keys`](@docroot@/command-ref/conf-file.md#conf-trusted-public-keys) to ensure their authenticity.
)",
.fun = prim_fetchClosure,
.experimentalFeature = Xp::FetchClosure,
diff --git a/src/libexpr/search-path.cc b/src/libexpr/search-path.cc
new file mode 100644
index 000000000..36bb4c3a5
--- /dev/null
+++ b/src/libexpr/search-path.cc
@@ -0,0 +1,56 @@
+#include "search-path.hh"
+#include "util.hh"
+
+namespace nix {
+
+std::optional<std::string_view> SearchPath::Prefix::suffixIfPotentialMatch(
+ std::string_view path) const
+{
+ auto n = s.size();
+
+ /* Non-empty prefix and suffix must be separated by a /, or the
+ prefix is not a valid path prefix. */
+ bool needSeparator = n > 0 && (path.size() - n) > 0;
+
+ if (needSeparator && path[n] != '/') {
+ return std::nullopt;
+ }
+
+ /* Prefix must be prefix of this path. */
+ if (path.compare(0, n, s) != 0) {
+ return std::nullopt;
+ }
+
+ /* Skip next path separator. */
+ return {
+ path.substr(needSeparator ? n + 1 : n)
+ };
+}
+
+
+SearchPath::Elem SearchPath::Elem::parse(std::string_view rawElem)
+{
+ size_t pos = rawElem.find('=');
+
+ return SearchPath::Elem {
+ .prefix = Prefix {
+ .s = pos == std::string::npos
+ ? std::string { "" }
+ : std::string { rawElem.substr(0, pos) },
+ },
+ .path = Path {
+ .s = std::string { rawElem.substr(pos + 1) },
+ },
+ };
+}
+
+
+SearchPath parseSearchPath(const Strings & rawElems)
+{
+ SearchPath res;
+ for (auto & rawElem : rawElems)
+ res.elements.emplace_back(SearchPath::Elem::parse(rawElem));
+ return res;
+}
+
+}
diff --git a/src/libexpr/search-path.hh b/src/libexpr/search-path.hh
new file mode 100644
index 000000000..ce78135b5
--- /dev/null
+++ b/src/libexpr/search-path.hh
@@ -0,0 +1,108 @@
+#pragma once
+///@file
+
+#include <optional>
+
+#include "types.hh"
+#include "comparator.hh"
+
+namespace nix {
+
+/**
+ * A "search path" is a list of ways look for something, used with
+ * `builtins.findFile` and `< >` lookup expressions.
+ */
+struct SearchPath
+{
+ /**
+ * A single element of a `SearchPath`.
+ *
+ * Each element is tried in succession when looking up a path. The first
+ * element to completely match wins.
+ */
+ struct Elem;
+
+ /**
+ * The first part of a `SearchPath::Elem` pair.
+ *
+ * Called a "prefix" because it takes the form of a prefix of a file
+ * path (first `n` path components). When looking up a path, to use
+ * a `SearchPath::Elem`, its `Prefix` must match the path.
+ */
+ struct Prefix;
+
+ /**
+ * The second part of a `SearchPath::Elem` pair.
+ *
+ * It is either a path or a URL (with certain restrictions / extra
+ * structure).
+ *
+ * If the prefix of the path we are looking up matches, we then
+ * check if the rest of the path points to something that exists
+ * within the directory denoted by this. If so, the
+ * `SearchPath::Elem` as a whole matches, and that *something* being
+ * pointed to by the rest of the path we are looking up is the
+ * result.
+ */
+ struct Path;
+
+ /**
+ * The list of search path elements. Each one is checked for a path
+ * when looking up. (The actual lookup entry point is in `EvalState`
+ * not in this class.)
+ */
+ std::list<SearchPath::Elem> elements;
+
+ /**
+ * Parse a string into a `SearchPath`
+ */
+ static SearchPath parse(const Strings & rawElems);
+};
+
+struct SearchPath::Prefix
+{
+ /**
+ * Underlying string
+ *
+ * @todo Should we normalize this when constructing a `SearchPath::Prefix`?
+ */
+ std::string s;
+
+ GENERATE_CMP(SearchPath::Prefix, me->s);
+
+ /**
+ * If the path possibly matches this search path element, return the
+ * suffix that we should look for inside the resolved value of the
+ * element
+ * Note the double optionality in the name. While we might have a matching prefix, the suffix may not exist.
+ */
+ std::optional<std::string_view> suffixIfPotentialMatch(std::string_view path) const;
+};
+
+struct SearchPath::Path
+{
+ /**
+ * The location of a search path item, as a path or URL.
+ *
+ * @todo Maybe change this to `std::variant<SourcePath, URL>`.
+ */
+ std::string s;
+
+ GENERATE_CMP(SearchPath::Path, me->s);
+};
+
+struct SearchPath::Elem
+{
+
+ Prefix prefix;
+ Path path;
+
+ GENERATE_CMP(SearchPath::Elem, me->prefix, me->path);
+
+ /**
+ * Parse a string into a `SearchPath::Elem`
+ */
+ static SearchPath::Elem parse(std::string_view rawElem);
+};
+
+}
diff --git a/src/libexpr/tests/search-path.cc b/src/libexpr/tests/search-path.cc
new file mode 100644
index 000000000..dbe7ab95f
--- /dev/null
+++ b/src/libexpr/tests/search-path.cc
@@ -0,0 +1,90 @@
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+#include "search-path.hh"
+
+namespace nix {
+
+TEST(SearchPathElem, parse_justPath) {
+ ASSERT_EQ(
+ SearchPath::Elem::parse("foo"),
+ (SearchPath::Elem {
+ .prefix = SearchPath::Prefix { .s = "" },
+ .path = SearchPath::Path { .s = "foo" },
+ }));
+}
+
+TEST(SearchPathElem, parse_emptyPrefix) {
+ ASSERT_EQ(
+ SearchPath::Elem::parse("=foo"),
+ (SearchPath::Elem {
+ .prefix = SearchPath::Prefix { .s = "" },
+ .path = SearchPath::Path { .s = "foo" },
+ }));
+}
+
+TEST(SearchPathElem, parse_oneEq) {
+ ASSERT_EQ(
+ SearchPath::Elem::parse("foo=bar"),
+ (SearchPath::Elem {
+ .prefix = SearchPath::Prefix { .s = "foo" },
+ .path = SearchPath::Path { .s = "bar" },
+ }));
+}
+
+TEST(SearchPathElem, parse_twoEqs) {
+ ASSERT_EQ(
+ SearchPath::Elem::parse("foo=bar=baz"),
+ (SearchPath::Elem {
+ .prefix = SearchPath::Prefix { .s = "foo" },
+ .path = SearchPath::Path { .s = "bar=baz" },
+ }));
+}
+
+
+TEST(SearchPathElem, suffixIfPotentialMatch_justPath) {
+ SearchPath::Prefix prefix { .s = "" };
+ ASSERT_EQ(prefix.suffixIfPotentialMatch("any/thing"), std::optional { "any/thing" });
+}
+
+TEST(SearchPathElem, suffixIfPotentialMatch_misleadingPrefix1) {
+ SearchPath::Prefix prefix { .s = "foo" };
+ ASSERT_EQ(prefix.suffixIfPotentialMatch("fooX"), std::nullopt);
+}
+
+TEST(SearchPathElem, suffixIfPotentialMatch_misleadingPrefix2) {
+ SearchPath::Prefix prefix { .s = "foo" };
+ ASSERT_EQ(prefix.suffixIfPotentialMatch("fooX/bar"), std::nullopt);
+}
+
+TEST(SearchPathElem, suffixIfPotentialMatch_partialPrefix) {
+ SearchPath::Prefix prefix { .s = "fooX" };
+ ASSERT_EQ(prefix.suffixIfPotentialMatch("foo"), std::nullopt);
+}
+
+TEST(SearchPathElem, suffixIfPotentialMatch_exactPrefix) {
+ SearchPath::Prefix prefix { .s = "foo" };
+ ASSERT_EQ(prefix.suffixIfPotentialMatch("foo"), std::optional { "" });
+}
+
+TEST(SearchPathElem, suffixIfPotentialMatch_multiKey) {
+ SearchPath::Prefix prefix { .s = "foo/bar" };
+ ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/bar/baz"), std::optional { "baz" });
+}
+
+TEST(SearchPathElem, suffixIfPotentialMatch_trailingSlash) {
+ SearchPath::Prefix prefix { .s = "foo" };
+ ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/"), std::optional { "" });
+}
+
+TEST(SearchPathElem, suffixIfPotentialMatch_trailingDoubleSlash) {
+ SearchPath::Prefix prefix { .s = "foo" };
+ ASSERT_EQ(prefix.suffixIfPotentialMatch("foo//"), std::optional { "/" });
+}
+
+TEST(SearchPathElem, suffixIfPotentialMatch_trailingPath) {
+ SearchPath::Prefix prefix { .s = "foo" };
+ ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/bar/baz"), std::optional { "bar/baz" });
+}
+
+}
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index 7b98a8601..7e9f6c100 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -594,6 +594,10 @@ void LocalDerivationGoal::startBuilder()
else
dirsInChroot[i.substr(0, p)] = {i.substr(p + 1), optional};
}
+ if (hasPrefix(worker.store.storeDir, tmpDirInSandbox))
+ {
+ throw Error("`sandbox-build-dir` must not contain the storeDir");
+ }
dirsInChroot[tmpDirInSandbox] = tmpDir;
/* Add the closure of store paths to the chroot. */
@@ -908,15 +912,13 @@ void LocalDerivationGoal::startBuilder()
openSlave();
/* Drop additional groups here because we can't do it
- after we've created the new user namespace. FIXME:
- this means that if we're not root in the parent
- namespace, we can't drop additional groups; they will
- be mapped to nogroup in the child namespace. There does
- not seem to be a workaround for this. (But who can tell
- from reading user_namespaces(7)?)
- See also https://lwn.net/Articles/621612/. */
- if (getuid() == 0 && setgroups(0, 0) == -1)
- throw SysError("setgroups failed");
+ after we've created the new user namespace. */
+ if (setgroups(0, 0) == -1) {
+ if (errno != EPERM)
+ throw SysError("setgroups failed");
+ if (settings.requireDropSupplementaryGroups)
+ throw Error("setgroups failed. Set the require-drop-supplementary-groups option to false to skip this step.");
+ }
ProcessOptions options;
options.cloneFlags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc
index 9a2ffda39..52d073f81 100644
--- a/src/libstore/derived-path.cc
+++ b/src/libstore/derived-path.cc
@@ -1,5 +1,4 @@
#include "derived-path.hh"
-#include "derivations.hh"
#include "store-api.hh"
#include <nlohmann/json.hpp>
@@ -30,30 +29,6 @@ nlohmann::json DerivedPath::Built::toJSON(ref<Store> store) const {
return res;
}
-nlohmann::json BuiltPath::Built::toJSON(ref<Store> store) const {
- nlohmann::json res;
- res["drvPath"] = store->printStorePath(drvPath);
- for (const auto& [output, path] : outputs) {
- res["outputs"][output] = store->printStorePath(path);
- }
- return res;
-}
-
-StorePathSet BuiltPath::outPaths() const
-{
- return std::visit(
- overloaded{
- [](const BuiltPath::Opaque & p) { return StorePathSet{p.path}; },
- [](const BuiltPath::Built & b) {
- StorePathSet res;
- for (auto & [_, path] : b.outputs)
- res.insert(path);
- return res;
- },
- }, raw()
- );
-}
-
std::string DerivedPath::Opaque::to_string(const Store & store) const
{
return store.printStorePath(path);
@@ -121,35 +96,4 @@ DerivedPath DerivedPath::parseLegacy(const Store & store, std::string_view s)
return parseWith(store, s, "!");
}
-RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
-{
- RealisedPath::Set res;
- std::visit(
- overloaded{
- [&](const BuiltPath::Opaque & p) { res.insert(p.path); },
- [&](const BuiltPath::Built & p) {
- auto drvHashes =
- staticOutputHashes(store, store.readDerivation(p.drvPath));
- for (auto& [outputName, outputPath] : p.outputs) {
- if (experimentalFeatureSettings.isEnabled(
- Xp::CaDerivations)) {
- auto drvOutput = get(drvHashes, outputName);
- if (!drvOutput)
- throw Error(
- "the derivation '%s' has unrealised output '%s' (derived-path.cc/toRealisedPaths)",
- store.printStorePath(p.drvPath), outputName);
- auto thisRealisation = store.queryRealisation(
- DrvOutput{*drvOutput, outputName});
- assert(thisRealisation); // We’ve built it, so we must
- // have the realisation
- res.insert(*thisRealisation);
- } else {
- res.insert(outputPath);
- }
- }
- },
- },
- raw());
- return res;
-}
}
diff --git a/src/libstore/derived-path.hh b/src/libstore/derived-path.hh
index 5f7acbebc..6ea80c92e 100644
--- a/src/libstore/derived-path.hh
+++ b/src/libstore/derived-path.hh
@@ -109,47 +109,6 @@ struct DerivedPath : _DerivedPathRaw {
static DerivedPath parseLegacy(const Store & store, std::string_view);
};
-/**
- * A built derived path with hints in the form of optional concrete output paths.
- *
- * See 'BuiltPath' for more an explanation.
- */
-struct BuiltPathBuilt {
- StorePath drvPath;
- std::map<std::string, StorePath> outputs;
-
- nlohmann::json toJSON(ref<Store> store) const;
- static BuiltPathBuilt parse(const Store & store, std::string_view);
-
- GENERATE_CMP(BuiltPathBuilt, me->drvPath, me->outputs);
-};
-
-using _BuiltPathRaw = std::variant<
- DerivedPath::Opaque,
- BuiltPathBuilt
->;
-
-/**
- * A built path. Similar to a DerivedPath, but enriched with the corresponding
- * output path(s).
- */
-struct BuiltPath : _BuiltPathRaw {
- using Raw = _BuiltPathRaw;
- using Raw::Raw;
-
- using Opaque = DerivedPathOpaque;
- using Built = BuiltPathBuilt;
-
- inline const Raw & raw() const {
- return static_cast<const Raw &>(*this);
- }
-
- StorePathSet outPaths() const;
- RealisedPath::Set toRealisedPaths(Store & store) const;
-
-};
-
typedef std::vector<DerivedPath> DerivedPaths;
-typedef std::vector<BuiltPath> BuiltPaths;
}
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index ec8625020..7009f6bb8 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -193,18 +193,24 @@ public:
Setting<std::string> thisSystem{
this, SYSTEM, "system",
R"(
- This option specifies the canonical Nix system name of the current
- installation, such as `i686-linux` or `x86_64-darwin`. Nix can only
- build derivations whose `system` attribute equals the value
- specified here. In general, it never makes sense to modify this
- value from its default, since you can use it to ‘lie’ about the
- platform you are building on (e.g., perform a Mac OS build on a
- Linux machine; the result would obviously be wrong). It only makes
- sense if the Nix binaries can run on multiple platforms, e.g.,
- ‘universal binaries’ that run on `x86_64-linux` and `i686-linux`.
-
- It defaults to the canonical Nix system name detected by `configure`
- at build time.
+ The system type of the current Nix installation.
+ Nix will only build a given [derivation](@docroot@/language/derivations.md) locally when its `system` attribute equals any of the values specified here or in [`extra-platforms`](#conf-extra-platforms).
+
+ The default value is set when Nix itself is compiled for the system it will run on.
+ The following system types are widely used, as [Nix is actively supported on these platforms](@docroot@/contributing/hacking.md#platforms):
+
+ - `x86_64-linux`
+ - `x86_64-darwin`
+ - `i686-linux`
+ - `aarch64-linux`
+ - `aarch64-darwin`
+ - `armv6l-linux`
+ - `armv7l-linux`
+
+ In general, you do not have to modify this setting.
+ While you can force Nix to run a Darwin-specific `builder` executable on a Linux machine, the result would obviously be wrong.
+
+ This value is available in the Nix language as [`builtins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem).
)"};
Setting<time_t> maxSilentTime{
@@ -524,6 +530,24 @@ public:
Setting<bool> sandboxFallback{this, true, "sandbox-fallback",
"Whether to disable sandboxing when the kernel doesn't allow it."};
+ Setting<bool> requireDropSupplementaryGroups{this, getuid() == 0, "require-drop-supplementary-groups",
+ R"(
+ Following the principle of least privilege,
+ Nix will attempt to drop supplementary groups when building with sandboxing.
+
+ However this can fail under some circumstances.
+ For example, if the user lacks the `CAP_SETGID` capability.
+ Search `setgroups(2)` for `EPERM` to find more detailed information on this.
+
+ If you encounter such a failure, setting this option to `false` will let you ignore it and continue.
+ But before doing so, you should consider the security implications carefully.
+ Not dropping supplementary groups means the build sandbox will be less restricted than intended.
+
+ This option defaults to `true` when the user is root
+ (since `root` usually has permissions to call setgroups)
+ and `false` otherwise.
+ )"};
+
#if __linux__
Setting<std::string> sandboxShmSize{
this, "50%", "sandbox-dev-shm-size",
@@ -652,18 +676,20 @@ public:
getDefaultExtraPlatforms(),
"extra-platforms",
R"(
- Platforms other than the native one which this machine is capable of
- building for. This can be useful for supporting additional
- architectures on compatible machines: i686-linux can be built on
- x86\_64-linux machines (and the default for this setting reflects
- this); armv7 is backwards-compatible with armv6 and armv5tel; some
- aarch64 machines can also natively run 32-bit ARM code; and
- qemu-user may be used to support non-native platforms (though this
- may be slow and buggy). Most values for this are not enabled by
- default because build systems will often misdetect the target
- platform and generate incompatible code, so you may wish to
- cross-check the results of using this option against proper
- natively-built versions of your derivations.
+ System types of executables that can be run on this machine.
+
+ Nix will only build a given [derivation](@docroot@/language/derivations.md) locally when its `system` attribute equals any of the values specified here or in the [`system` option](#conf-system).
+
+ Setting this can be useful to build derivations locally on compatible machines:
+ - `i686-linux` executables can be run on `x86_64-linux` machines (set by default)
+ - `x86_64-darwin` executables can be run on macOS `aarch64-darwin` with Rosetta 2 (set by default where applicable)
+ - `armv6` and `armv5tel` executables can be run on `armv7`
+ - some `aarch64` machines can also natively run 32-bit ARM code
+ - `qemu-user` may be used to support non-native platforms (though this
+ may be slow and buggy)
+
+ Build systems will usually detect the target platform to be the current physical system and therefore produce machine code incompatible with what may be intended in the derivation.
+ You should design your derivation's `builder` accordingly and cross-check the results when using this option against natively-built versions of your derivation.
)", {}, false};
Setting<StringSet> systemFeatures{
@@ -992,7 +1018,7 @@ public:
| `~/.nix-defexpr` | `$XDG_STATE_HOME/nix/defexpr` |
| `~/.nix-channels` | `$XDG_STATE_HOME/nix/channels` |
- If you already have Nix installed and are using [profiles](@docroot@/package-management/profiles.md) or [channels](@docroot@/package-management/channels.md), you should migrate manually when you enable this option.
+ If you already have Nix installed and are using [profiles](@docroot@/package-management/profiles.md) or [channels](@docroot@/command-ref/nix-channel.md), you should migrate manually when you enable this option.
If `$XDG_STATE_HOME` is not set, use `$HOME/.local/state/nix` instead of `$XDG_STATE_HOME/nix`.
This can be achieved with the following shell commands:
diff --git a/src/libstore/make-content-addressed.cc b/src/libstore/make-content-addressed.cc
index 349c2f187..253609ed2 100644
--- a/src/libstore/make-content-addressed.cc
+++ b/src/libstore/make-content-addressed.cc
@@ -78,4 +78,15 @@ std::map<StorePath, StorePath> makeContentAddressed(
return remappings;
}
+StorePath makeContentAddressed(
+ Store & srcStore,
+ Store & dstStore,
+ const StorePath & fromPath)
+{
+ auto remappings = makeContentAddressed(srcStore, dstStore, StorePathSet { fromPath });
+ auto i = remappings.find(fromPath);
+ assert(i != remappings.end());
+ return i->second;
+}
+
}
diff --git a/src/libstore/make-content-addressed.hh b/src/libstore/make-content-addressed.hh
index 2ce6ec7bc..60bb2b477 100644
--- a/src/libstore/make-content-addressed.hh
+++ b/src/libstore/make-content-addressed.hh
@@ -5,9 +5,20 @@
namespace nix {
+/** Rewrite a closure of store paths to be completely content addressed.
+ */
std::map<StorePath, StorePath> makeContentAddressed(
Store & srcStore,
Store & dstStore,
- const StorePathSet & storePaths);
+ const StorePathSet & rootPaths);
+
+/** Rewrite a closure of a store path to be completely content addressed.
+ *
+ * This is a convenience function for the case where you only have one root path.
+ */
+StorePath makeContentAddressed(
+ Store & srcStore,
+ Store & dstStore,
+ const StorePath & rootPath);
}
diff --git a/src/libstore/ssh.cc b/src/libstore/ssh.cc
index fae99d75b..da32f1b79 100644
--- a/src/libstore/ssh.cc
+++ b/src/libstore/ssh.cc
@@ -42,7 +42,10 @@ void SSHMaster::addCommonSSHOpts(Strings & args)
}
bool SSHMaster::isMasterRunning() {
- auto res = runProgram(RunOptions {.program = "ssh", .args = {"-O", "check", host}, .mergeStderrToStdout = true});
+ Strings args = {"-O", "check", host};
+ addCommonSSHOpts(args);
+
+ auto res = runProgram(RunOptions {.program = "ssh", .args = args, .mergeStderrToStdout = true});
return res.first == 0;
}
diff --git a/src/nix/nix.md b/src/nix/nix.md
index 6d9e40dbc..e0f459d6b 100644
--- a/src/nix/nix.md
+++ b/src/nix/nix.md
@@ -63,7 +63,7 @@ The following types of installable are supported by most commands:
- [Nix file](#nix-file), optionally qualified by an attribute path
- [Nix expression](#nix-expression), optionally qualified by an attribute path
-For most commands, if no installable is specified, `.` as assumed.
+For most commands, if no installable is specified, `.` is assumed.
That is, Nix will operate on the default flake output attribute of the flake in the current directory.
### Flake output attribute
diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc
index 3997c98bf..d05c23fb7 100644
--- a/src/nix/upgrade-nix.cc
+++ b/src/nix/upgrade-nix.cc
@@ -146,7 +146,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
auto req = FileTransferRequest(storePathsUrl);
auto res = getFileTransfer()->download(req);
- auto state = std::make_unique<EvalState>(Strings(), store);
+ auto state = std::make_unique<EvalState>(SearchPath{}, store);
auto v = state->allocValue();
state->eval(state->parseExprFromString(res.data, state->rootPath(CanonPath("/no-such-path"))), *v);
Bindings & bindings(*state->allocBindings(0));