Add exec primop behind allow-unsafe-native-code-during-evaluation.
Execute a given program with the (optional) given arguments as the user running the evaluation, parsing stdout as an expression to be evaluated. There are many use cases for nix that would benefit from being able to run arbitrary code during evaluation, including but not limited to: * Automatic git fetching to get a sha256 from a git revision * git rev-parse HEAD * Automatic extraction of information from build specifications from other tools, particularly language-specific package managers like cabal or npm * Secrets decryption (e.g. with nixops) * Private repository fetching Ideally, we would add this functionality in a more principled way to nix, but in the mean time 'builtins.exec' can be used to get these tasks done. The primop is only available when the 'allow-unsafe-native-code-during-evaluation' nix option is true. That flag also enables the 'importNative' primop, which is strictly more powerful but less convenient (since it requires compiling a plugin against the running version of nix).
This commit is contained in:
parent
c60715e937
commit
0bb8db257d
3 changed files with 59 additions and 5 deletions
|
@ -178,6 +178,58 @@ static void prim_importNative(EvalState & state, const Pos & pos, Value * * args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Execute a program and parse its output */
|
||||||
|
static void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceAttrs(*args[0], pos);
|
||||||
|
auto sProgram = state.symbols.create("program");
|
||||||
|
auto sArguments = state.symbols.create("arguments");
|
||||||
|
PathSet context;
|
||||||
|
string program;
|
||||||
|
bool programSet = false;
|
||||||
|
Strings commandArgs;
|
||||||
|
for (auto & attr : *args[0]->attrs) {
|
||||||
|
if (attr.name == sProgram) {
|
||||||
|
program = state.coerceToString(*attr.pos, *attr.value, context, false, false);
|
||||||
|
programSet = true;
|
||||||
|
} else if (attr.name == sArguments) {
|
||||||
|
state.forceList(*attr.value, *attr.pos);
|
||||||
|
auto elems = attr.value->listElems();
|
||||||
|
for (unsigned int i = 0; i < attr.value->listSize(); ++i) {
|
||||||
|
commandArgs.emplace_back(state.coerceToString(*attr.pos, *elems[i], context, false, false));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw EvalError(format("unexpected attribute ‘%1%’ in argument to builtins.exec, at %2%")
|
||||||
|
% attr.name % pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!programSet) {
|
||||||
|
throw EvalError(format("attribute ‘programSet’ required, at %1%") % pos);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
state.realiseContext(context);
|
||||||
|
} catch (InvalidPathError & e) {
|
||||||
|
throw EvalError(format("cannot execute ‘%1%’, since path ‘%2%’ is not valid, at %3%")
|
||||||
|
% program % e.path % pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto output = runProgram(program, true, commandArgs);
|
||||||
|
Expr * parsed;
|
||||||
|
try {
|
||||||
|
parsed = state.parseExprFromString(output, pos.file);
|
||||||
|
} catch (Error & e) {
|
||||||
|
e.addPrefix(format("While parsing the output from ‘%1%’, at %2%\n") % program % pos);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
state.eval(parsed, v);
|
||||||
|
} catch (Error & e) {
|
||||||
|
e.addPrefix(format("While evaluating the output from ‘%1%’, at %2%\n") % program % pos);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Return a string representing the type of the expression. */
|
/* Return a string representing the type of the expression. */
|
||||||
static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
|
@ -1903,8 +1955,10 @@ void EvalState::createBaseEnv()
|
||||||
mkApp(v, *baseEnv.values[baseEnvDispl - 1], *v2);
|
mkApp(v, *baseEnv.values[baseEnvDispl - 1], *v2);
|
||||||
forceValue(v);
|
forceValue(v);
|
||||||
addConstant("import", v);
|
addConstant("import", v);
|
||||||
if (settings.enableImportNative)
|
if (settings.enableNativeCode) {
|
||||||
addPrimOp("__importNative", 2, prim_importNative);
|
addPrimOp("__importNative", 2, prim_importNative);
|
||||||
|
addPrimOp("__exec", 1, prim_exec);
|
||||||
|
}
|
||||||
addPrimOp("__typeOf", 1, prim_typeOf);
|
addPrimOp("__typeOf", 1, prim_typeOf);
|
||||||
addPrimOp("isNull", 1, prim_isNull);
|
addPrimOp("isNull", 1, prim_isNull);
|
||||||
addPrimOp("__isFunction", 1, prim_isFunction);
|
addPrimOp("__isFunction", 1, prim_isFunction);
|
||||||
|
|
|
@ -67,7 +67,7 @@ Settings::Settings()
|
||||||
envKeepDerivations = false;
|
envKeepDerivations = false;
|
||||||
lockCPU = getEnv("NIX_AFFINITY_HACK", "1") == "1";
|
lockCPU = getEnv("NIX_AFFINITY_HACK", "1") == "1";
|
||||||
showTrace = false;
|
showTrace = false;
|
||||||
enableImportNative = false;
|
enableNativeCode = false;
|
||||||
netrcFile = fmt("%s/%s", nixConfDir, "netrc");
|
netrcFile = fmt("%s/%s", nixConfDir, "netrc");
|
||||||
caFile = getEnv("NIX_SSL_CERT_FILE", getEnv("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt"));
|
caFile = getEnv("NIX_SSL_CERT_FILE", getEnv("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt"));
|
||||||
enableImportFromDerivation = true;
|
enableImportFromDerivation = true;
|
||||||
|
@ -179,7 +179,7 @@ void Settings::update()
|
||||||
_get(envKeepDerivations, "env-keep-derivations");
|
_get(envKeepDerivations, "env-keep-derivations");
|
||||||
_get(sshSubstituterHosts, "ssh-substituter-hosts");
|
_get(sshSubstituterHosts, "ssh-substituter-hosts");
|
||||||
_get(useSshSubstituter, "use-ssh-substituter");
|
_get(useSshSubstituter, "use-ssh-substituter");
|
||||||
_get(enableImportNative, "allow-unsafe-native-code-during-evaluation");
|
_get(enableNativeCode, "allow-unsafe-native-code-during-evaluation");
|
||||||
_get(useCaseHack, "use-case-hack");
|
_get(useCaseHack, "use-case-hack");
|
||||||
_get(preBuildHook, "pre-build-hook");
|
_get(preBuildHook, "pre-build-hook");
|
||||||
_get(keepGoing, "keep-going");
|
_get(keepGoing, "keep-going");
|
||||||
|
|
|
@ -181,8 +181,8 @@ struct Settings {
|
||||||
/* Whether to show a stack trace if Nix evaluation fails. */
|
/* Whether to show a stack trace if Nix evaluation fails. */
|
||||||
bool showTrace;
|
bool showTrace;
|
||||||
|
|
||||||
/* Whether the importNative primop should be enabled */
|
/* Whether native-code enabling primops should be enabled */
|
||||||
bool enableImportNative;
|
bool enableNativeCode;
|
||||||
|
|
||||||
/* The hook to run just before a build to set derivation-specific
|
/* The hook to run just before a build to set derivation-specific
|
||||||
build settings */
|
build settings */
|
||||||
|
|
Loading…
Reference in a new issue