mirror of
https://github.com/NixOS/nix
synced 2025-07-02 05:11:47 +02:00
Merge branch 'best-effort-supplementary-groups' into overlayfs-store
This commit is contained in:
commit
04d5aa02e6
99 changed files with 1416 additions and 213 deletions
|
@ -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({
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1502,6 +1502,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,
|
||||
});
|
||||
|
@ -1656,9 +1658,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 },
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -4319,12 +4321,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, {
|
||||
|
|
|
@ -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,
|
||||
|
|
56
src/libexpr/search-path.cc
Normal file
56
src/libexpr/search-path.cc
Normal file
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
108
src/libexpr/search-path.hh
Normal file
108
src/libexpr/search-path.hh
Normal file
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
90
src/libexpr/tests/search-path.cc
Normal file
90
src/libexpr/tests/search-path.cc
Normal file
|
@ -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" });
|
||||
}
|
||||
|
||||
}
|
|
@ -909,9 +909,12 @@ void LocalDerivationGoal::startBuilder()
|
|||
|
||||
/* Drop additional groups here because we can't do it
|
||||
after we've created the new user namespace. */
|
||||
if (settings.dropSupplementaryGroups)
|
||||
if (setgroups(0, 0) == -1)
|
||||
throw SysError("setgroups failed. Set the drop-supplementary-groups option to false to skip this step.");
|
||||
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;
|
||||
|
|
|
@ -524,23 +524,22 @@ public:
|
|||
Setting<bool> sandboxFallback{this, true, "sandbox-fallback",
|
||||
"Whether to disable sandboxing when the kernel doesn't allow it."};
|
||||
|
||||
Setting<bool> dropSupplementaryGroups{this, getuid() == 0, "drop-supplementary-groups",
|
||||
Setting<bool> requireDropSupplementaryGroups{this, getuid() == 0, "require-drop-supplementary-groups",
|
||||
R"(
|
||||
Whether to drop supplementary groups when building with sandboxing.
|
||||
This is normally a good idea if we are root and have the capability to
|
||||
do so.
|
||||
Following the principle of least privilege,
|
||||
Nix will attempt to drop supplementary groups when building with sandboxing.
|
||||
|
||||
But if this "root" is mapped from a non-root user in a larger
|
||||
namespace, we won't be able drop additional groups; they will be
|
||||
mapped to nogroup in the child namespace. There does not seem to be a
|
||||
workaround for this.
|
||||
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.
|
||||
|
||||
(But who can tell from reading user_namespaces(7)? See also https://lwn.net/Articles/621612/.)
|
||||
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.
|
||||
|
||||
TODO: It might be good to create a middle ground option that allows
|
||||
`setgroups` to fail if all additional groups are "nogroup" / the value
|
||||
of `/proc/sys/fs/overflowuid`. This would handle the common
|
||||
nested-sandboxing case identified above.
|
||||
This option defaults to `true` when the user is root
|
||||
(since `root` usually has permissions to call setgroups)
|
||||
and `false` otherwise.
|
||||
)"};
|
||||
|
||||
#if __linux__
|
||||
|
|
|
@ -80,4 +80,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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue