mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-29 17:16:15 +02:00
Merge branch 'errors-phase-2' of https://github.com/bburdette/nix
This commit is contained in:
commit
7a77762961
81 changed files with 1857 additions and 1010 deletions
|
@ -200,9 +200,12 @@ static int _main(int argc, char * * argv)
|
||||||
|
|
||||||
} catch (std::exception & e) {
|
} catch (std::exception & e) {
|
||||||
auto msg = chomp(drainFD(5, false));
|
auto msg = chomp(drainFD(5, false));
|
||||||
printError("cannot build on '%s': %s%s",
|
logError({
|
||||||
|
.name = "Remote build",
|
||||||
|
.hint = hintfmt("cannot build on '%s': %s%s",
|
||||||
bestMachine->storeUri, e.what(),
|
bestMachine->storeUri, e.what(),
|
||||||
(msg.empty() ? "" : ": " + msg));
|
(msg.empty() ? "" : ": " + msg))
|
||||||
|
});
|
||||||
bestMachine->enabled = false;
|
bestMachine->enabled = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
#include "error.hh"
|
|
||||||
#include "nixexpr.hh"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
using namespace nix;
|
|
||||||
|
|
||||||
// In each program where errors occur, this has to be set.
|
|
||||||
ErrorInfo::programName = std::optional("error-demo");
|
|
||||||
|
|
||||||
// Error in a program; no hint and no nix code.
|
|
||||||
printErrorInfo(
|
|
||||||
ErrorInfo { .level = elError,
|
|
||||||
.name = "name",
|
|
||||||
.description = "error description",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Warning with name, description, and hint.
|
|
||||||
// The hintfmt function makes all the substituted text yellow.
|
|
||||||
printErrorInfo(
|
|
||||||
ErrorInfo { .level = elWarning,
|
|
||||||
.name = "name",
|
|
||||||
.description = "error description",
|
|
||||||
.hint = std::optional(
|
|
||||||
hintfmt("there was a %1%", "warning")),
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Warning with nix file, line number, column, and the lines of
|
|
||||||
// code where a warning occurred.
|
|
||||||
SymbolTable testTable;
|
|
||||||
auto problem_file = testTable.create("myfile.nix");
|
|
||||||
|
|
||||||
printErrorInfo(
|
|
||||||
ErrorInfo{
|
|
||||||
.level = elWarning,
|
|
||||||
.name = "warning name",
|
|
||||||
.description = "warning description",
|
|
||||||
.hint = hintfmt("this hint has %1% templated %2%!!", "yellow", "values"),
|
|
||||||
.nixCode = NixCode {
|
|
||||||
.errPos = Pos(problem_file, 40, 13),
|
|
||||||
.prevLineOfCode = std::nullopt,
|
|
||||||
.errLineOfCode = "this is the problem line of code",
|
|
||||||
.nextLineOfCode = std::nullopt
|
|
||||||
}});
|
|
||||||
|
|
||||||
// Error with previous and next lines of code.
|
|
||||||
printErrorInfo(
|
|
||||||
ErrorInfo{
|
|
||||||
.level = elError,
|
|
||||||
.name = "error name",
|
|
||||||
.description = "error description",
|
|
||||||
.hint = hintfmt("this hint has %1% templated %2%!!", "yellow", "values"),
|
|
||||||
.nixCode = NixCode {
|
|
||||||
.errPos = Pos(problem_file, 40, 13),
|
|
||||||
.prevLineOfCode = std::optional("previous line of code"),
|
|
||||||
.errLineOfCode = "this is the problem line of code",
|
|
||||||
.nextLineOfCode = std::optional("next line of code"),
|
|
||||||
}});
|
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
programs += error-demo
|
|
||||||
|
|
||||||
error-demo_DIR := $(d)
|
|
||||||
|
|
||||||
error-demo_SOURCES := \
|
|
||||||
$(wildcard $(d)/*.cc) \
|
|
||||||
|
|
||||||
error-demo_CXXFLAGS += -I src/libutil -I src/libexpr
|
|
||||||
|
|
||||||
error-demo_LIBS = libutil libexpr
|
|
||||||
|
|
||||||
error-demo_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) $(BOOST_LDFLAGS) -lboost_context -lboost_thread -lboost_system
|
|
|
@ -19,7 +19,7 @@ static Strings parseAttrPath(const string & s)
|
||||||
++i;
|
++i;
|
||||||
while (1) {
|
while (1) {
|
||||||
if (i == s.end())
|
if (i == s.end())
|
||||||
throw Error(format("missing closing quote in selection path '%1%'") % s);
|
throw Error("missing closing quote in selection path '%1%'", s);
|
||||||
if (*i == '"') break;
|
if (*i == '"') break;
|
||||||
cur.push_back(*i++);
|
cur.push_back(*i++);
|
||||||
}
|
}
|
||||||
|
@ -60,11 +60,11 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attr
|
||||||
|
|
||||||
if (v->type != tAttrs)
|
if (v->type != tAttrs)
|
||||||
throw TypeError(
|
throw TypeError(
|
||||||
format("the expression selected by the selection path '%1%' should be a set but is %2%")
|
"the expression selected by the selection path '%1%' should be a set but is %2%",
|
||||||
% attrPath % showType(*v));
|
attrPath,
|
||||||
|
showType(*v));
|
||||||
if (attr.empty())
|
if (attr.empty())
|
||||||
throw Error(format("empty attribute name in selection path '%1%'") % attrPath);
|
throw Error("empty attribute name in selection path '%1%'", attrPath);
|
||||||
|
|
||||||
Bindings::iterator a = v->attrs->find(state.symbols.create(attr));
|
Bindings::iterator a = v->attrs->find(state.symbols.create(attr));
|
||||||
if (a == v->attrs->end())
|
if (a == v->attrs->end())
|
||||||
|
@ -77,9 +77,9 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attr
|
||||||
|
|
||||||
if (!v->isList())
|
if (!v->isList())
|
||||||
throw TypeError(
|
throw TypeError(
|
||||||
format("the expression selected by the selection path '%1%' should be a list but is %2%")
|
"the expression selected by the selection path '%1%' should be a list but is %2%",
|
||||||
% attrPath % showType(*v));
|
attrPath,
|
||||||
|
showType(*v));
|
||||||
if (attrIndex >= v->listSize())
|
if (attrIndex >= v->listSize())
|
||||||
throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", attrIndex, attrPath);
|
throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", attrIndex, attrPath);
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,12 @@ public:
|
||||||
{
|
{
|
||||||
auto a = get(name);
|
auto a = get(name);
|
||||||
if (!a)
|
if (!a)
|
||||||
throw Error("attribute '%s' missing, at %s", name, pos);
|
throw Error(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("attribute '%s' missing", name),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
return *a;
|
return *a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,20 +7,28 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const Pos & pos))
|
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s))
|
||||||
{
|
{
|
||||||
throw EvalError(format(s) % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt(s),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v))
|
LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v))
|
||||||
{
|
{
|
||||||
throw TypeError(format(s) % showType(v));
|
throw TypeError(s, showType(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v, const Pos & pos))
|
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v))
|
||||||
{
|
{
|
||||||
throw TypeError(format(s) % showType(v) % pos);
|
throw TypeError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt(s, showType(v)),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,7 +51,7 @@ void EvalState::forceValue(Value & v, const Pos & pos)
|
||||||
else if (v.type == tApp)
|
else if (v.type == tApp)
|
||||||
callFunction(*v.app.left, *v.app.right, v, noPos);
|
callFunction(*v.app.left, *v.app.right, v, noPos);
|
||||||
else if (v.type == tBlackhole)
|
else if (v.type == tBlackhole)
|
||||||
throwEvalError("infinite recursion encountered, at %1%", pos);
|
throwEvalError(pos, "infinite recursion encountered");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,7 +67,7 @@ inline void EvalState::forceAttrs(Value & v, const Pos & pos)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type != tAttrs)
|
if (v.type != tAttrs)
|
||||||
throwTypeError("value is %1% while a set was expected, at %2%", v, pos);
|
throwTypeError(pos, "value is %1% while a set was expected", v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,7 +83,7 @@ inline void EvalState::forceList(Value & v, const Pos & pos)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (!v.isList())
|
if (!v.isList())
|
||||||
throwTypeError("value is %1% while a list was expected, at %2%", v, pos);
|
throwTypeError(pos, "value is %1% while a list was expected", v);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Note: Various places expect the allocated memory to be zeroed. */
|
/* Note: Various places expect the allocated memory to be zeroed. */
|
||||||
|
|
|
@ -501,52 +501,81 @@ Value & EvalState::getBuiltin(const string & name)
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2))
|
LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2))
|
||||||
{
|
{
|
||||||
throw EvalError(format(s) % s2);
|
throw EvalError(s, s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const Pos & pos))
|
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2))
|
||||||
{
|
{
|
||||||
throw EvalError(format(s) % s2 % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt(s, s2),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const string & s3))
|
LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const string & s3))
|
||||||
{
|
{
|
||||||
throw EvalError(format(s) % s2 % s3);
|
throw EvalError(s, s2, s3);
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const string & s3, const Pos & pos))
|
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2, const string & s3))
|
||||||
{
|
{
|
||||||
throw EvalError(format(s) % s2 % s3 % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt(s, s2, s3),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const Symbol & sym, const Pos & p1, const Pos & p2))
|
LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2))
|
||||||
{
|
{
|
||||||
throw EvalError(format(s) % sym % p1 % p2);
|
// p1 is where the error occurred; p2 is a position mentioned in the message.
|
||||||
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt(s, sym, p2),
|
||||||
|
.nixCode = NixCode { .errPos = p1 }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwTypeError(const char * s, const Pos & pos))
|
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s))
|
||||||
{
|
{
|
||||||
throw TypeError(format(s) % pos);
|
throw TypeError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt(s),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwTypeError(const char * s, const string & s1))
|
LocalNoInlineNoReturn(void throwTypeError(const char * s, const string & s1))
|
||||||
{
|
{
|
||||||
throw TypeError(format(s) % s1);
|
throw TypeError(s, s1);
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwTypeError(const char * s, const ExprLambda & fun, const Symbol & s2, const Pos & pos))
|
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2))
|
||||||
{
|
{
|
||||||
throw TypeError(format(s) % fun.showNamePos() % s2 % pos);
|
throw TypeError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt(s, fun.showNamePos(), s2),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwAssertionError(const char * s, const string & s1, const Pos & pos))
|
LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const string & s1))
|
||||||
{
|
{
|
||||||
throw AssertionError(format(s) % s1 % pos);
|
throw AssertionError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt(s, s1),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwUndefinedVarError(const char * s, const string & s1, const Pos & pos))
|
LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const string & s1))
|
||||||
{
|
{
|
||||||
throw UndefinedVarError(format(s) % s1 % pos);
|
throw UndefinedVarError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt(s, s1),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInline(void addErrorPrefix(Error & e, const char * s, const string & s2))
|
LocalNoInline(void addErrorPrefix(Error & e, const char * s, const string & s2))
|
||||||
|
@ -614,7 +643,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
||||||
return j->value;
|
return j->value;
|
||||||
}
|
}
|
||||||
if (!env->prevWith)
|
if (!env->prevWith)
|
||||||
throwUndefinedVarError("undefined variable '%1%' at %2%", var.name, var.pos);
|
throwUndefinedVarError(var.pos, "undefined variable '%1%'", var.name);
|
||||||
for (size_t l = env->prevWith; l; --l, env = env->up) ;
|
for (size_t l = env->prevWith; l; --l, env = env->up) ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -812,7 +841,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos)
|
||||||
Value v;
|
Value v;
|
||||||
e->eval(*this, env, v);
|
e->eval(*this, env, v);
|
||||||
if (v.type != tBool)
|
if (v.type != tBool)
|
||||||
throwTypeError("value is %1% while a Boolean was expected, at %2%", v, pos);
|
throwTypeError(pos, "value is %1% while a Boolean was expected", v);
|
||||||
return v.boolean;
|
return v.boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -926,7 +955,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||||
Symbol nameSym = state.symbols.create(nameVal.string.s);
|
Symbol nameSym = state.symbols.create(nameVal.string.s);
|
||||||
Bindings::iterator j = v.attrs->find(nameSym);
|
Bindings::iterator j = v.attrs->find(nameSym);
|
||||||
if (j != v.attrs->end())
|
if (j != v.attrs->end())
|
||||||
throwEvalError("dynamic attribute '%1%' at %2% already defined at %3%", nameSym, i.pos, *j->pos);
|
throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, *j->pos);
|
||||||
|
|
||||||
i.valueExpr->setName(nameSym);
|
i.valueExpr->setName(nameSym);
|
||||||
/* Keep sorted order so find can catch duplicates */
|
/* Keep sorted order so find can catch duplicates */
|
||||||
|
@ -1014,7 +1043,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
||||||
} else {
|
} else {
|
||||||
state.forceAttrs(*vAttrs, pos);
|
state.forceAttrs(*vAttrs, pos);
|
||||||
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
|
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
|
||||||
throwEvalError("attribute '%1%' missing, at %2%", name, pos);
|
throwEvalError(pos, "attribute '%1%' missing", name);
|
||||||
}
|
}
|
||||||
vAttrs = j->value;
|
vAttrs = j->value;
|
||||||
pos2 = j->pos;
|
pos2 = j->pos;
|
||||||
|
@ -1140,7 +1169,7 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fun.type != tLambda)
|
if (fun.type != tLambda)
|
||||||
throwTypeError("attempt to call something which is not a function but %1%, at %2%", fun, pos);
|
throwTypeError(pos, "attempt to call something which is not a function but %1%", fun);
|
||||||
|
|
||||||
ExprLambda & lambda(*fun.lambda.fun);
|
ExprLambda & lambda(*fun.lambda.fun);
|
||||||
|
|
||||||
|
@ -1168,8 +1197,8 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po
|
||||||
for (auto & i : lambda.formals->formals) {
|
for (auto & i : lambda.formals->formals) {
|
||||||
Bindings::iterator j = arg.attrs->find(i.name);
|
Bindings::iterator j = arg.attrs->find(i.name);
|
||||||
if (j == arg.attrs->end()) {
|
if (j == arg.attrs->end()) {
|
||||||
if (!i.def) throwTypeError("%1% called without required argument '%2%', at %3%",
|
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
|
||||||
lambda, i.name, pos);
|
lambda, i.name);
|
||||||
env2.values[displ++] = i.def->maybeThunk(*this, env2);
|
env2.values[displ++] = i.def->maybeThunk(*this, env2);
|
||||||
} else {
|
} else {
|
||||||
attrsUsed++;
|
attrsUsed++;
|
||||||
|
@ -1184,7 +1213,7 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po
|
||||||
user. */
|
user. */
|
||||||
for (auto & i : *arg.attrs)
|
for (auto & i : *arg.attrs)
|
||||||
if (lambda.formals->argNames.find(i.name) == lambda.formals->argNames.end())
|
if (lambda.formals->argNames.find(i.name) == lambda.formals->argNames.end())
|
||||||
throwTypeError("%1% called with unexpected argument '%2%', at %3%", lambda, i.name, pos);
|
throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name);
|
||||||
abort(); // can't happen
|
abort(); // can't happen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1273,7 +1302,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
|
||||||
if (!state.evalBool(env, cond, pos)) {
|
if (!state.evalBool(env, cond, pos)) {
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
cond->show(out);
|
cond->show(out);
|
||||||
throwAssertionError("assertion '%1%' failed at %2%", out.str(), pos);
|
throwAssertionError(pos, "assertion '%1%' failed at %2%", out.str());
|
||||||
}
|
}
|
||||||
body->eval(state, env, v);
|
body->eval(state, env, v);
|
||||||
}
|
}
|
||||||
|
@ -1425,14 +1454,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||||
nf = n;
|
nf = n;
|
||||||
nf += vTmp.fpoint;
|
nf += vTmp.fpoint;
|
||||||
} else
|
} else
|
||||||
throwEvalError("cannot add %1% to an integer, at %2%", showType(vTmp), pos);
|
throwEvalError(pos, "cannot add %1% to an integer", showType(vTmp));
|
||||||
} else if (firstType == tFloat) {
|
} else if (firstType == tFloat) {
|
||||||
if (vTmp.type == tInt) {
|
if (vTmp.type == tInt) {
|
||||||
nf += vTmp.integer;
|
nf += vTmp.integer;
|
||||||
} else if (vTmp.type == tFloat) {
|
} else if (vTmp.type == tFloat) {
|
||||||
nf += vTmp.fpoint;
|
nf += vTmp.fpoint;
|
||||||
} else
|
} else
|
||||||
throwEvalError("cannot add %1% to a float, at %2%", showType(vTmp), pos);
|
throwEvalError(pos, "cannot add %1% to a float", showType(vTmp));
|
||||||
} else
|
} else
|
||||||
s << state.coerceToString(pos, vTmp, context, false, firstType == tString);
|
s << state.coerceToString(pos, vTmp, context, false, firstType == tString);
|
||||||
}
|
}
|
||||||
|
@ -1443,7 +1472,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||||
mkFloat(v, nf);
|
mkFloat(v, nf);
|
||||||
else if (firstType == tPath) {
|
else if (firstType == tPath) {
|
||||||
if (!context.empty())
|
if (!context.empty())
|
||||||
throwEvalError("a string that refers to a store path cannot be appended to a path, at %1%", pos);
|
throwEvalError(pos, "a string that refers to a store path cannot be appended to a path");
|
||||||
auto path = canonPath(s.str());
|
auto path = canonPath(s.str());
|
||||||
mkPath(v, path.c_str());
|
mkPath(v, path.c_str());
|
||||||
} else
|
} else
|
||||||
|
@ -1492,7 +1521,7 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type != tInt)
|
if (v.type != tInt)
|
||||||
throwTypeError("value is %1% while an integer was expected, at %2%", v, pos);
|
throwTypeError(pos, "value is %1% while an integer was expected", v);
|
||||||
return v.integer;
|
return v.integer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1503,7 +1532,7 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos)
|
||||||
if (v.type == tInt)
|
if (v.type == tInt)
|
||||||
return v.integer;
|
return v.integer;
|
||||||
else if (v.type != tFloat)
|
else if (v.type != tFloat)
|
||||||
throwTypeError("value is %1% while a float was expected, at %2%", v, pos);
|
throwTypeError(pos, "value is %1% while a float was expected", v);
|
||||||
return v.fpoint;
|
return v.fpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1512,7 +1541,7 @@ bool EvalState::forceBool(Value & v, const Pos & pos)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type != tBool)
|
if (v.type != tBool)
|
||||||
throwTypeError("value is %1% while a Boolean was expected, at %2%", v, pos);
|
throwTypeError(pos, "value is %1% while a Boolean was expected", v);
|
||||||
return v.boolean;
|
return v.boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1527,7 +1556,7 @@ void EvalState::forceFunction(Value & v, const Pos & pos)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type != tLambda && v.type != tPrimOp && v.type != tPrimOpApp && !isFunctor(v))
|
if (v.type != tLambda && v.type != tPrimOp && v.type != tPrimOpApp && !isFunctor(v))
|
||||||
throwTypeError("value is %1% while a function was expected, at %2%", v, pos);
|
throwTypeError(pos, "value is %1% while a function was expected", v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1536,7 +1565,7 @@ string EvalState::forceString(Value & v, const Pos & pos)
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type != tString) {
|
if (v.type != tString) {
|
||||||
if (pos)
|
if (pos)
|
||||||
throwTypeError("value is %1% while a string was expected, at %2%", v, pos);
|
throwTypeError(pos, "value is %1% while a string was expected", v);
|
||||||
else
|
else
|
||||||
throwTypeError("value is %1% while a string was expected", v);
|
throwTypeError("value is %1% while a string was expected", v);
|
||||||
}
|
}
|
||||||
|
@ -1565,8 +1594,8 @@ string EvalState::forceStringNoCtx(Value & v, const Pos & pos)
|
||||||
string s = forceString(v, pos);
|
string s = forceString(v, pos);
|
||||||
if (v.string.context) {
|
if (v.string.context) {
|
||||||
if (pos)
|
if (pos)
|
||||||
throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%'), at %3%",
|
throwEvalError(pos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')",
|
||||||
v.string.s, v.string.context[0], pos);
|
v.string.s, v.string.context[0]);
|
||||||
else
|
else
|
||||||
throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')",
|
throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')",
|
||||||
v.string.s, v.string.context[0]);
|
v.string.s, v.string.context[0]);
|
||||||
|
@ -1622,7 +1651,7 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
|
||||||
return *maybeString;
|
return *maybeString;
|
||||||
}
|
}
|
||||||
auto i = v.attrs->find(sOutPath);
|
auto i = v.attrs->find(sOutPath);
|
||||||
if (i == v.attrs->end()) throwTypeError("cannot coerce a set to a string, at %1%", pos);
|
if (i == v.attrs->end()) throwTypeError(pos, "cannot coerce a set to a string");
|
||||||
return coerceToString(pos, *i->value, context, coerceMore, copyToStore);
|
return coerceToString(pos, *i->value, context, coerceMore, copyToStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1653,7 +1682,7 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throwTypeError("cannot coerce %1% to a string, at %2%", v, pos);
|
throwTypeError(pos, "cannot coerce %1% to a string", v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1684,7 +1713,7 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context)
|
||||||
{
|
{
|
||||||
string path = coerceToString(pos, v, context, false, false);
|
string path = coerceToString(pos, v, context, false, false);
|
||||||
if (path == "" || path[0] != '/')
|
if (path == "" || path[0] != '/')
|
||||||
throwEvalError("string '%1%' doesn't represent an absolute path, at %2%", path, pos);
|
throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path);
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1891,8 +1920,11 @@ void EvalState::printStats()
|
||||||
|
|
||||||
string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const
|
string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const
|
||||||
{
|
{
|
||||||
throw TypeError(format("cannot coerce %1% to a string, at %2%") %
|
throw TypeError(
|
||||||
showType() % pos);
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("cannot coerce %1% to a string", showType()),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -127,14 +127,14 @@ or { return OR_KW; }
|
||||||
try {
|
try {
|
||||||
yylval->n = boost::lexical_cast<int64_t>(yytext);
|
yylval->n = boost::lexical_cast<int64_t>(yytext);
|
||||||
} catch (const boost::bad_lexical_cast &) {
|
} catch (const boost::bad_lexical_cast &) {
|
||||||
throw ParseError(format("invalid integer '%1%'") % yytext);
|
throw ParseError("invalid integer '%1%'", yytext);
|
||||||
}
|
}
|
||||||
return INT;
|
return INT;
|
||||||
}
|
}
|
||||||
{FLOAT} { errno = 0;
|
{FLOAT} { errno = 0;
|
||||||
yylval->nf = strtod(yytext, 0);
|
yylval->nf = strtod(yytext, 0);
|
||||||
if (errno != 0)
|
if (errno != 0)
|
||||||
throw ParseError(format("invalid float '%1%'") % yytext);
|
throw ParseError("invalid float '%1%'", yytext);
|
||||||
return FLOAT;
|
return FLOAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -267,8 +267,12 @@ void ExprVar::bindVars(const StaticEnv & env)
|
||||||
/* Otherwise, the variable must be obtained from the nearest
|
/* Otherwise, the variable must be obtained from the nearest
|
||||||
enclosing `with'. If there is no `with', then we can issue an
|
enclosing `with'. If there is no `with', then we can issue an
|
||||||
"undefined variable" error now. */
|
"undefined variable" error now. */
|
||||||
if (withLevel == -1) throw UndefinedVarError(format("undefined variable '%1%' at %2%") % name % pos);
|
if (withLevel == -1)
|
||||||
|
throw UndefinedVarError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("undefined variable '%1%'", name),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
fromWith = true;
|
fromWith = true;
|
||||||
this->level = withLevel;
|
this->level = withLevel;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "value.hh"
|
#include "value.hh"
|
||||||
#include "symbol-table.hh"
|
#include "symbol-table.hh"
|
||||||
|
#include "error.hh"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
@ -235,8 +236,11 @@ struct ExprLambda : Expr
|
||||||
: pos(pos), arg(arg), matchAttrs(matchAttrs), formals(formals), body(body)
|
: pos(pos), arg(arg), matchAttrs(matchAttrs), formals(formals), body(body)
|
||||||
{
|
{
|
||||||
if (!arg.empty() && formals && formals->argNames.find(arg) != formals->argNames.end())
|
if (!arg.empty() && formals && formals->argNames.find(arg) != formals->argNames.end())
|
||||||
throw ParseError(format("duplicate formal function argument '%1%' at %2%")
|
throw ParseError(
|
||||||
% arg % pos);
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("duplicate formal function argument '%1%'", arg),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
};
|
};
|
||||||
void setName(Symbol & name);
|
void setName(Symbol & name);
|
||||||
string showNamePos() const;
|
string showNamePos() const;
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace nix {
|
||||||
Expr * result;
|
Expr * result;
|
||||||
Path basePath;
|
Path basePath;
|
||||||
Symbol path;
|
Symbol path;
|
||||||
string error;
|
ErrorInfo error;
|
||||||
Symbol sLetBody;
|
Symbol sLetBody;
|
||||||
ParseData(EvalState & state)
|
ParseData(EvalState & state)
|
||||||
: state(state)
|
: state(state)
|
||||||
|
@ -64,15 +64,23 @@ namespace nix {
|
||||||
|
|
||||||
static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prevPos)
|
static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prevPos)
|
||||||
{
|
{
|
||||||
throw ParseError(format("attribute '%1%' at %2% already defined at %3%")
|
throw ParseError(
|
||||||
% showAttrPath(attrPath) % pos % prevPos);
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("attribute '%1%' already defined at %2%",
|
||||||
|
showAttrPath(attrPath), prevPos),
|
||||||
|
.nixCode = NixCode { .errPos = pos },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos)
|
static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos)
|
||||||
{
|
{
|
||||||
throw ParseError(format("attribute '%1%' at %2% already defined at %3%")
|
throw ParseError(
|
||||||
% attr % pos % prevPos);
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("attribute '%1%' already defined at %2%",
|
||||||
|
attr, prevPos),
|
||||||
|
.nixCode = NixCode { .errPos = pos },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -140,8 +148,12 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
|
||||||
static void addFormal(const Pos & pos, Formals * formals, const Formal & formal)
|
static void addFormal(const Pos & pos, Formals * formals, const Formal & formal)
|
||||||
{
|
{
|
||||||
if (!formals->argNames.insert(formal.name).second)
|
if (!formals->argNames.insert(formal.name).second)
|
||||||
throw ParseError(format("duplicate formal function argument '%1%' at %2%")
|
throw ParseError(
|
||||||
% formal.name % pos);
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("duplicate formal function argument '%1%'",
|
||||||
|
formal.name),
|
||||||
|
.nixCode = NixCode { .errPos = pos },
|
||||||
|
});
|
||||||
formals->formals.push_front(formal);
|
formals->formals.push_front(formal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,8 +261,10 @@ static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data)
|
||||||
|
|
||||||
void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * error)
|
void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * error)
|
||||||
{
|
{
|
||||||
data->error = (format("%1%, at %2%")
|
data->error = ErrorInfo {
|
||||||
% error % makeCurPos(*loc, data)).str();
|
.hint = hintfmt(error),
|
||||||
|
.nixCode = NixCode { .errPos = makeCurPos(*loc, data) }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -327,8 +341,11 @@ expr_function
|
||||||
{ $$ = new ExprWith(CUR_POS, $2, $4); }
|
{ $$ = new ExprWith(CUR_POS, $2, $4); }
|
||||||
| LET binds IN expr_function
|
| LET binds IN expr_function
|
||||||
{ if (!$2->dynamicAttrs.empty())
|
{ if (!$2->dynamicAttrs.empty())
|
||||||
throw ParseError(format("dynamic attributes not allowed in let at %1%")
|
throw ParseError(
|
||||||
% CUR_POS);
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("dynamic attributes not allowed in let"),
|
||||||
|
.nixCode = NixCode { .errPos = CUR_POS },
|
||||||
|
});
|
||||||
$$ = new ExprLet($2, $4);
|
$$ = new ExprLet($2, $4);
|
||||||
}
|
}
|
||||||
| expr_if
|
| expr_if
|
||||||
|
@ -405,7 +422,11 @@ expr_simple
|
||||||
| URI {
|
| URI {
|
||||||
static bool noURLLiterals = settings.isExperimentalFeatureEnabled("no-url-literals");
|
static bool noURLLiterals = settings.isExperimentalFeatureEnabled("no-url-literals");
|
||||||
if (noURLLiterals)
|
if (noURLLiterals)
|
||||||
throw ParseError("URL literals are disabled, at %s", CUR_POS);
|
throw ParseError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("URL literals are disabled"),
|
||||||
|
.nixCode = NixCode { .errPos = CUR_POS }
|
||||||
|
});
|
||||||
$$ = new ExprString(data->symbols.create($1));
|
$$ = new ExprString(data->symbols.create($1));
|
||||||
}
|
}
|
||||||
| '(' expr ')' { $$ = $2; }
|
| '(' expr ')' { $$ = $2; }
|
||||||
|
@ -475,8 +496,11 @@ attrs
|
||||||
$$->push_back(AttrName(str->s));
|
$$->push_back(AttrName(str->s));
|
||||||
delete str;
|
delete str;
|
||||||
} else
|
} else
|
||||||
throw ParseError(format("dynamic attributes not allowed in inherit at %1%")
|
throw ParseError(
|
||||||
% makeCurPos(@2, data));
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("dynamic attributes not allowed in inherit"),
|
||||||
|
.nixCode = NixCode { .errPos = makeCurPos(@2, data) },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
| { $$ = new AttrPath; }
|
| { $$ = new AttrPath; }
|
||||||
;
|
;
|
||||||
|
@ -671,11 +695,11 @@ Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos
|
||||||
Path res = r.second + suffix;
|
Path res = r.second + suffix;
|
||||||
if (pathExists(res)) return canonPath(res);
|
if (pathExists(res)) return canonPath(res);
|
||||||
}
|
}
|
||||||
format f = format(
|
throw ThrownError(
|
||||||
"file '%1%' was not found in the Nix search path (add it using $NIX_PATH or -I)"
|
ErrorInfo {
|
||||||
+ string(pos ? ", at %2%" : ""));
|
.hint = hintfmt("file '%1%' was not found in the Nix search path (add it using $NIX_PATH or -I)", path),
|
||||||
f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
|
.nixCode = NixCode { .errPos = pos }
|
||||||
throw ThrownError(f % path % pos);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -691,7 +715,11 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl
|
||||||
res = { true, store->toRealPath(fetchers::downloadTarball(
|
res = { true, store->toRealPath(fetchers::downloadTarball(
|
||||||
store, resolveUri(elem.second), "source", false).storePath) };
|
store, resolveUri(elem.second), "source", false).storePath) };
|
||||||
} catch (FileTransferError & e) {
|
} catch (FileTransferError & e) {
|
||||||
printError(format("warning: Nix search path entry '%1%' cannot be downloaded, ignoring") % elem.second);
|
logWarning(
|
||||||
|
ErrorInfo {
|
||||||
|
.name = "Entry download",
|
||||||
|
.hint = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second)
|
||||||
|
});
|
||||||
res = { false, "" };
|
res = { false, "" };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -699,7 +727,11 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl
|
||||||
if (pathExists(path))
|
if (pathExists(path))
|
||||||
res = { true, path };
|
res = { true, path };
|
||||||
else {
|
else {
|
||||||
printError(format("warning: Nix search path entry '%1%' does not exist, ignoring") % elem.second);
|
logWarning(
|
||||||
|
ErrorInfo {
|
||||||
|
.name = "Entry not found",
|
||||||
|
.hint = hintfmt("warning: Nix search path entry '%1%' does not exist, ignoring", elem.second)
|
||||||
|
});
|
||||||
res = { false, "" };
|
res = { false, "" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,8 +93,12 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
|
||||||
try {
|
try {
|
||||||
state.realiseContext(context);
|
state.realiseContext(context);
|
||||||
} catch (InvalidPathError & e) {
|
} catch (InvalidPathError & e) {
|
||||||
throw EvalError(format("cannot import '%1%', since path '%2%' is not valid, at %3%")
|
throw EvalError(
|
||||||
% path % e.path % pos);
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("cannot import '%1%', since path '%2%' is not valid",
|
||||||
|
path, e.path),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Path realPath = state.checkSourcePath(state.toRealPath(path, context));
|
Path realPath = state.checkSourcePath(state.toRealPath(path, context));
|
||||||
|
@ -170,8 +174,13 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value
|
||||||
try {
|
try {
|
||||||
state.realiseContext(context);
|
state.realiseContext(context);
|
||||||
} catch (InvalidPathError & e) {
|
} catch (InvalidPathError & e) {
|
||||||
throw EvalError(format("cannot import '%1%', since path '%2%' is not valid, at %3%")
|
throw EvalError(
|
||||||
% path % e.path % pos);
|
ErrorInfo {
|
||||||
|
.hint = hintfmt(
|
||||||
|
"cannot import '%1%', since path '%2%' is not valid",
|
||||||
|
path, e.path),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
path = state.checkSourcePath(path);
|
path = state.checkSourcePath(path);
|
||||||
|
@ -180,17 +189,17 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value
|
||||||
|
|
||||||
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||||
if (!handle)
|
if (!handle)
|
||||||
throw EvalError(format("could not open '%1%': %2%") % path % dlerror());
|
throw EvalError("could not open '%1%': %2%", path, dlerror());
|
||||||
|
|
||||||
dlerror();
|
dlerror();
|
||||||
ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str());
|
ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str());
|
||||||
if(!func) {
|
if(!func) {
|
||||||
char *message = dlerror();
|
char *message = dlerror();
|
||||||
if (message)
|
if (message)
|
||||||
throw EvalError(format("could not load symbol '%1%' from '%2%': %3%") % sym % path % message);
|
throw EvalError("could not load symbol '%1%' from '%2%': %3%", sym, path, message);
|
||||||
else
|
else
|
||||||
throw EvalError(format("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected")
|
throw EvalError("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected",
|
||||||
% sym % path);
|
sym, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
(func)(state, v);
|
(func)(state, v);
|
||||||
|
@ -206,7 +215,11 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
auto elems = args[0]->listElems();
|
auto elems = args[0]->listElems();
|
||||||
auto count = args[0]->listSize();
|
auto count = args[0]->listSize();
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
throw EvalError(format("at least one argument to 'exec' required, at %1%") % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("at least one argument to 'exec' required"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
PathSet context;
|
PathSet context;
|
||||||
auto program = state.coerceToString(pos, *elems[0], context, false, false);
|
auto program = state.coerceToString(pos, *elems[0], context, false, false);
|
||||||
|
@ -217,22 +230,25 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
try {
|
try {
|
||||||
state.realiseContext(context);
|
state.realiseContext(context);
|
||||||
} catch (InvalidPathError & e) {
|
} catch (InvalidPathError & e) {
|
||||||
throw EvalError(format("cannot execute '%1%', since path '%2%' is not valid, at %3%")
|
throw EvalError(
|
||||||
% program % e.path % pos);
|
ErrorInfo {
|
||||||
}
|
.hint = hintfmt("cannot execute '%1%', since path '%2%' is not valid",
|
||||||
|
program, e.path),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});}
|
||||||
|
|
||||||
auto output = runProgram(program, true, commandArgs);
|
auto output = runProgram(program, true, commandArgs);
|
||||||
Expr * parsed;
|
Expr * parsed;
|
||||||
try {
|
try {
|
||||||
parsed = state.parseExprFromString(output, pos.file);
|
parsed = state.parseExprFromString(output, pos.file);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addPrefix(format("While parsing the output from '%1%', at %2%\n") % program % pos);
|
e.addPrefix(fmt("While parsing the output from '%1%', at %2%\n", program, pos));
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
state.eval(parsed, v);
|
state.eval(parsed, v);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addPrefix(format("While evaluating the output from '%1%', at %2%\n") % program % pos);
|
e.addPrefix(fmt("While evaluating the output from '%1%', at %2%\n", program, pos));
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -338,7 +354,7 @@ struct CompareValues
|
||||||
if (v1->type == tInt && v2->type == tFloat)
|
if (v1->type == tInt && v2->type == tFloat)
|
||||||
return v1->integer < v2->fpoint;
|
return v1->integer < v2->fpoint;
|
||||||
if (v1->type != v2->type)
|
if (v1->type != v2->type)
|
||||||
throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % showType(*v2));
|
throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2));
|
||||||
switch (v1->type) {
|
switch (v1->type) {
|
||||||
case tInt:
|
case tInt:
|
||||||
return v1->integer < v2->integer;
|
return v1->integer < v2->integer;
|
||||||
|
@ -349,7 +365,7 @@ struct CompareValues
|
||||||
case tPath:
|
case tPath:
|
||||||
return strcmp(v1->path, v2->path) < 0;
|
return strcmp(v1->path, v2->path) < 0;
|
||||||
default:
|
default:
|
||||||
throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % showType(*v2));
|
throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -370,7 +386,11 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
|
||||||
Bindings::iterator startSet =
|
Bindings::iterator startSet =
|
||||||
args[0]->attrs->find(state.symbols.create("startSet"));
|
args[0]->attrs->find(state.symbols.create("startSet"));
|
||||||
if (startSet == args[0]->attrs->end())
|
if (startSet == args[0]->attrs->end())
|
||||||
throw EvalError(format("attribute 'startSet' required, at %1%") % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("attribute 'startSet' required"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
state.forceList(*startSet->value, pos);
|
state.forceList(*startSet->value, pos);
|
||||||
|
|
||||||
ValueList workSet;
|
ValueList workSet;
|
||||||
|
@ -381,7 +401,11 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
|
||||||
Bindings::iterator op =
|
Bindings::iterator op =
|
||||||
args[0]->attrs->find(state.symbols.create("operator"));
|
args[0]->attrs->find(state.symbols.create("operator"));
|
||||||
if (op == args[0]->attrs->end())
|
if (op == args[0]->attrs->end())
|
||||||
throw EvalError(format("attribute 'operator' required, at %1%") % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("attribute 'operator' required"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
state.forceValue(*op->value, pos);
|
state.forceValue(*op->value, pos);
|
||||||
|
|
||||||
/* Construct the closure by applying the operator to element of
|
/* Construct the closure by applying the operator to element of
|
||||||
|
@ -400,7 +424,11 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
|
||||||
Bindings::iterator key =
|
Bindings::iterator key =
|
||||||
e->attrs->find(state.symbols.create("key"));
|
e->attrs->find(state.symbols.create("key"));
|
||||||
if (key == e->attrs->end())
|
if (key == e->attrs->end())
|
||||||
throw EvalError(format("attribute 'key' required, at %1%") % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("attribute 'key' required"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
state.forceValue(*key->value, pos);
|
state.forceValue(*key->value, pos);
|
||||||
|
|
||||||
if (!doneKeys.insert(key->value).second) continue;
|
if (!doneKeys.insert(key->value).second) continue;
|
||||||
|
@ -430,7 +458,7 @@ static void prim_abort(EvalState & state, const Pos & pos, Value * * args, Value
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
string s = state.coerceToString(pos, *args[0], context);
|
string s = state.coerceToString(pos, *args[0], context);
|
||||||
throw Abort(format("evaluation aborted with the following error message: '%1%'") % s);
|
throw Abort("evaluation aborted with the following error message: '%1%'", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -505,9 +533,9 @@ static void prim_trace(EvalState & state, const Pos & pos, Value * * args, Value
|
||||||
{
|
{
|
||||||
state.forceValue(*args[0], pos);
|
state.forceValue(*args[0], pos);
|
||||||
if (args[0]->type == tString)
|
if (args[0]->type == tString)
|
||||||
printError(format("trace: %1%") % args[0]->string.s);
|
printError("trace: %1%", args[0]->string.s);
|
||||||
else
|
else
|
||||||
printError(format("trace: %1%") % *args[0]);
|
printError("trace: %1%", *args[0]);
|
||||||
state.forceValue(*args[1], pos);
|
state.forceValue(*args[1], pos);
|
||||||
v = *args[1];
|
v = *args[1];
|
||||||
}
|
}
|
||||||
|
@ -532,13 +560,17 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||||
/* Figure out the name first (for stack backtraces). */
|
/* Figure out the name first (for stack backtraces). */
|
||||||
Bindings::iterator attr = args[0]->attrs->find(state.sName);
|
Bindings::iterator attr = args[0]->attrs->find(state.sName);
|
||||||
if (attr == args[0]->attrs->end())
|
if (attr == args[0]->attrs->end())
|
||||||
throw EvalError(format("required attribute 'name' missing, at %1%") % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("required attribute 'name' missing"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
string drvName;
|
string drvName;
|
||||||
Pos & posDrvName(*attr->pos);
|
Pos & posDrvName(*attr->pos);
|
||||||
try {
|
try {
|
||||||
drvName = state.forceStringNoCtx(*attr->value, pos);
|
drvName = state.forceStringNoCtx(*attr->value, pos);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addPrefix(format("while evaluating the derivation attribute 'name' at %1%:\n") % posDrvName);
|
e.addPrefix(fmt("while evaluating the derivation attribute 'name' at %1%:\n", posDrvName));
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -575,25 +607,42 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||||
auto handleHashMode = [&](const std::string & s) {
|
auto handleHashMode = [&](const std::string & s) {
|
||||||
if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive;
|
if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive;
|
||||||
else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat;
|
else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat;
|
||||||
else throw EvalError("invalid value '%s' for 'outputHashMode' attribute, at %s", s, posDrvName);
|
else
|
||||||
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
|
||||||
|
.nixCode = NixCode { .errPos = posDrvName }
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
auto handleOutputs = [&](const Strings & ss) {
|
auto handleOutputs = [&](const Strings & ss) {
|
||||||
outputs.clear();
|
outputs.clear();
|
||||||
for (auto & j : ss) {
|
for (auto & j : ss) {
|
||||||
if (outputs.find(j) != outputs.end())
|
if (outputs.find(j) != outputs.end())
|
||||||
throw EvalError(format("duplicate derivation output '%1%', at %2%") % j % posDrvName);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("duplicate derivation output '%1%'", j),
|
||||||
|
.nixCode = NixCode { .errPos = posDrvName }
|
||||||
|
});
|
||||||
/* !!! Check whether j is a valid attribute
|
/* !!! Check whether j is a valid attribute
|
||||||
name. */
|
name. */
|
||||||
/* Derivations cannot be named ‘drv’, because
|
/* Derivations cannot be named ‘drv’, because
|
||||||
then we'd have an attribute ‘drvPath’ in
|
then we'd have an attribute ‘drvPath’ in
|
||||||
the resulting set. */
|
the resulting set. */
|
||||||
if (j == "drv")
|
if (j == "drv")
|
||||||
throw EvalError(format("invalid derivation output name 'drv', at %1%") % posDrvName);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("invalid derivation output name 'drv'" ),
|
||||||
|
.nixCode = NixCode { .errPos = posDrvName }
|
||||||
|
});
|
||||||
outputs.insert(j);
|
outputs.insert(j);
|
||||||
}
|
}
|
||||||
if (outputs.empty())
|
if (outputs.empty())
|
||||||
throw EvalError(format("derivation cannot have an empty set of outputs, at %1%") % posDrvName);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("derivation cannot have an empty set of outputs"),
|
||||||
|
.nixCode = NixCode { .errPos = posDrvName }
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -705,18 +754,35 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||||
|
|
||||||
/* Do we have all required attributes? */
|
/* Do we have all required attributes? */
|
||||||
if (drv.builder == "")
|
if (drv.builder == "")
|
||||||
throw EvalError(format("required attribute 'builder' missing, at %1%") % posDrvName);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("required attribute 'builder' missing"),
|
||||||
|
.nixCode = NixCode { .errPos = posDrvName }
|
||||||
|
});
|
||||||
|
|
||||||
if (drv.platform == "")
|
if (drv.platform == "")
|
||||||
throw EvalError(format("required attribute 'system' missing, at %1%") % posDrvName);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("required attribute 'system' missing"),
|
||||||
|
.nixCode = NixCode { .errPos = posDrvName }
|
||||||
|
});
|
||||||
|
|
||||||
/* Check whether the derivation name is valid. */
|
/* Check whether the derivation name is valid. */
|
||||||
if (isDerivation(drvName))
|
if (isDerivation(drvName))
|
||||||
throw EvalError("derivation names are not allowed to end in '%s', at %s", drvExtension, posDrvName);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("derivation names are not allowed to end in '%s'", drvExtension),
|
||||||
|
.nixCode = NixCode { .errPos = posDrvName }
|
||||||
|
});
|
||||||
|
|
||||||
if (outputHash) {
|
if (outputHash) {
|
||||||
/* Handle fixed-output derivations. */
|
/* Handle fixed-output derivations. */
|
||||||
if (outputs.size() != 1 || *(outputs.begin()) != "out")
|
if (outputs.size() != 1 || *(outputs.begin()) != "out")
|
||||||
throw Error(format("multiple outputs are not supported in fixed-output derivations, at %1%") % posDrvName);
|
throw Error(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("multiple outputs are not supported in fixed-output derivations"),
|
||||||
|
.nixCode = NixCode { .errPos = posDrvName }
|
||||||
|
});
|
||||||
|
|
||||||
HashType ht = outputHashAlgo.empty() ? htUnknown : parseHashType(outputHashAlgo);
|
HashType ht = outputHashAlgo.empty() ? htUnknown : parseHashType(outputHashAlgo);
|
||||||
|
|
||||||
|
@ -821,7 +887,11 @@ static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, V
|
||||||
e.g. nix-push does the right thing. */
|
e.g. nix-push does the right thing. */
|
||||||
if (!state.store->isStorePath(path)) path = canonPath(path, true);
|
if (!state.store->isStorePath(path)) path = canonPath(path, true);
|
||||||
if (!state.store->isInStore(path))
|
if (!state.store->isInStore(path))
|
||||||
throw EvalError(format("path '%1%' is not in the Nix store, at %2%") % path % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("path '%1%' is not in the Nix store", path),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
Path path2 = state.store->toStorePath(path);
|
Path path2 = state.store->toStorePath(path);
|
||||||
if (!settings.readOnlyMode)
|
if (!settings.readOnlyMode)
|
||||||
state.store->ensurePath(state.store->parseStorePath(path2));
|
state.store->ensurePath(state.store->parseStorePath(path2));
|
||||||
|
@ -837,9 +907,13 @@ static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args,
|
||||||
try {
|
try {
|
||||||
state.realiseContext(context);
|
state.realiseContext(context);
|
||||||
} catch (InvalidPathError & e) {
|
} catch (InvalidPathError & e) {
|
||||||
throw EvalError(format(
|
throw EvalError(
|
||||||
"cannot check the existence of '%1%', since path '%2%' is not valid, at %3%")
|
ErrorInfo {
|
||||||
% path % e.path % pos);
|
.hint = hintfmt(
|
||||||
|
"cannot check the existence of '%1%', since path '%2%' is not valid",
|
||||||
|
path, e.path),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -882,12 +956,16 @@ static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Va
|
||||||
try {
|
try {
|
||||||
state.realiseContext(context);
|
state.realiseContext(context);
|
||||||
} catch (InvalidPathError & e) {
|
} catch (InvalidPathError & e) {
|
||||||
throw EvalError(format("cannot read '%1%', since path '%2%' is not valid, at %3%")
|
throw EvalError(
|
||||||
% path % e.path % pos);
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("cannot read '%1%', since path '%2%' is not valid"
|
||||||
|
, path, e.path),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
string s = readFile(state.checkSourcePath(state.toRealPath(path, context)));
|
string s = readFile(state.checkSourcePath(state.toRealPath(path, context)));
|
||||||
if (s.find((char) 0) != string::npos)
|
if (s.find((char) 0) != string::npos)
|
||||||
throw Error(format("the contents of the file '%1%' cannot be represented as a Nix string") % path);
|
throw Error("the contents of the file '%1%' cannot be represented as a Nix string", path);
|
||||||
mkString(v, s.c_str());
|
mkString(v, s.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -911,7 +989,11 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
|
||||||
|
|
||||||
i = v2.attrs->find(state.symbols.create("path"));
|
i = v2.attrs->find(state.symbols.create("path"));
|
||||||
if (i == v2.attrs->end())
|
if (i == v2.attrs->end())
|
||||||
throw EvalError(format("attribute 'path' missing, at %1%") % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("attribute 'path' missing"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
PathSet context;
|
PathSet context;
|
||||||
string path = state.coerceToString(pos, *i->value, context, false, false);
|
string path = state.coerceToString(pos, *i->value, context, false, false);
|
||||||
|
@ -919,8 +1001,12 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
|
||||||
try {
|
try {
|
||||||
state.realiseContext(context);
|
state.realiseContext(context);
|
||||||
} catch (InvalidPathError & e) {
|
} catch (InvalidPathError & e) {
|
||||||
throw EvalError(format("cannot find '%1%', since path '%2%' is not valid, at %3%")
|
throw EvalError(
|
||||||
% path % e.path % pos);
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("cannot find '%1%', since path '%2%' is not valid",
|
||||||
|
path, e.path),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
searchPath.emplace_back(prefix, path);
|
searchPath.emplace_back(prefix, path);
|
||||||
|
@ -937,7 +1023,11 @@ static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Va
|
||||||
string type = state.forceStringNoCtx(*args[0], pos);
|
string type = state.forceStringNoCtx(*args[0], pos);
|
||||||
HashType ht = parseHashType(type);
|
HashType ht = parseHashType(type);
|
||||||
if (ht == htUnknown)
|
if (ht == htUnknown)
|
||||||
throw Error(format("unknown hash type '%1%', at %2%") % type % pos);
|
throw Error(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("unknown hash type '%1%'", type),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
PathSet context; // discarded
|
PathSet context; // discarded
|
||||||
Path p = state.coerceToPath(pos, *args[1], context);
|
Path p = state.coerceToPath(pos, *args[1], context);
|
||||||
|
@ -953,8 +1043,12 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val
|
||||||
try {
|
try {
|
||||||
state.realiseContext(ctx);
|
state.realiseContext(ctx);
|
||||||
} catch (InvalidPathError & e) {
|
} catch (InvalidPathError & e) {
|
||||||
throw EvalError(format("cannot read '%1%', since path '%2%' is not valid, at %3%")
|
throw EvalError(
|
||||||
% path % e.path % pos);
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("cannot read '%1%', since path '%2%' is not valid",
|
||||||
|
path, e.path),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
DirEntries entries = readDirectory(state.checkSourcePath(path));
|
DirEntries entries = readDirectory(state.checkSourcePath(path));
|
||||||
|
@ -1024,9 +1118,15 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu
|
||||||
|
|
||||||
for (auto path : context) {
|
for (auto path : context) {
|
||||||
if (path.at(0) != '/')
|
if (path.at(0) != '/')
|
||||||
throw EvalError(format(
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt(
|
||||||
"in 'toFile': the file named '%1%' must not contain a reference "
|
"in 'toFile': the file named '%1%' must not contain a reference "
|
||||||
"to a derivation but contains (%2%), at %3%") % name % path % pos);
|
"to a derivation but contains (%2%)",
|
||||||
|
name,
|
||||||
|
path),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
refs.insert(state.store->parseStorePath(path));
|
refs.insert(state.store->parseStorePath(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1094,11 +1194,21 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args
|
||||||
PathSet context;
|
PathSet context;
|
||||||
Path path = state.coerceToPath(pos, *args[1], context);
|
Path path = state.coerceToPath(pos, *args[1], context);
|
||||||
if (!context.empty())
|
if (!context.empty())
|
||||||
throw EvalError(format("string '%1%' cannot refer to other paths, at %2%") % path % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("string '%1%' cannot refer to other paths", path),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
state.forceValue(*args[0], pos);
|
state.forceValue(*args[0], pos);
|
||||||
if (args[0]->type != tLambda)
|
if (args[0]->type != tLambda)
|
||||||
throw TypeError(format("first argument in call to 'filterSource' is not a function but %1%, at %2%") % showType(*args[0]) % pos);
|
throw TypeError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt(
|
||||||
|
"first argument in call to 'filterSource' is not a function but %1%",
|
||||||
|
showType(*args[0])),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, Hash(), v);
|
addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, Hash(), v);
|
||||||
}
|
}
|
||||||
|
@ -1118,7 +1228,12 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value
|
||||||
PathSet context;
|
PathSet context;
|
||||||
path = state.coerceToPath(*attr.pos, *attr.value, context);
|
path = state.coerceToPath(*attr.pos, *attr.value, context);
|
||||||
if (!context.empty())
|
if (!context.empty())
|
||||||
throw EvalError(format("string '%1%' cannot refer to other paths, at %2%") % path % *attr.pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("string '%1%' cannot refer to other paths",
|
||||||
|
path),
|
||||||
|
.nixCode = NixCode { .errPos = *attr.pos }
|
||||||
|
});
|
||||||
} else if (attr.name == state.sName)
|
} else if (attr.name == state.sName)
|
||||||
name = state.forceStringNoCtx(*attr.value, *attr.pos);
|
name = state.forceStringNoCtx(*attr.value, *attr.pos);
|
||||||
else if (n == "filter") {
|
else if (n == "filter") {
|
||||||
|
@ -1129,10 +1244,19 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value
|
||||||
else if (n == "sha256")
|
else if (n == "sha256")
|
||||||
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
|
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
|
||||||
else
|
else
|
||||||
throw EvalError(format("unsupported argument '%1%' to 'addPath', at %2%") % attr.name % *attr.pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("unsupported argument '%1%' to 'addPath'",
|
||||||
|
attr.name),
|
||||||
|
.nixCode = NixCode { .errPos = *attr.pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (path.empty())
|
if (path.empty())
|
||||||
throw EvalError(format("'path' required, at %1%") % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("'path' required"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
if (name.empty())
|
if (name.empty())
|
||||||
name = baseNameOf(path);
|
name = baseNameOf(path);
|
||||||
|
|
||||||
|
@ -1190,7 +1314,11 @@ void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
// !!! Should we create a symbol here or just do a lookup?
|
// !!! Should we create a symbol here or just do a lookup?
|
||||||
Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr));
|
Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr));
|
||||||
if (i == args[1]->attrs->end())
|
if (i == args[1]->attrs->end())
|
||||||
throw EvalError(format("attribute '%1%' missing, at %2%") % attr % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("attribute '%1%' missing", attr),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
// !!! add to stack trace?
|
// !!! add to stack trace?
|
||||||
if (state.countCalls && i->pos) state.attrSelects[*i->pos]++;
|
if (state.countCalls && i->pos) state.attrSelects[*i->pos]++;
|
||||||
state.forceValue(*i->value, pos);
|
state.forceValue(*i->value, pos);
|
||||||
|
@ -1270,15 +1398,22 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args,
|
||||||
|
|
||||||
Bindings::iterator j = v2.attrs->find(state.sName);
|
Bindings::iterator j = v2.attrs->find(state.sName);
|
||||||
if (j == v2.attrs->end())
|
if (j == v2.attrs->end())
|
||||||
throw TypeError(format("'name' attribute missing in a call to 'listToAttrs', at %1%") % pos);
|
throw TypeError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("'name' attribute missing in a call to 'listToAttrs'"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
string name = state.forceStringNoCtx(*j->value, pos);
|
string name = state.forceStringNoCtx(*j->value, pos);
|
||||||
|
|
||||||
Symbol sym = state.symbols.create(name);
|
Symbol sym = state.symbols.create(name);
|
||||||
if (seen.insert(sym).second) {
|
if (seen.insert(sym).second) {
|
||||||
Bindings::iterator j2 = v2.attrs->find(state.symbols.create(state.sValue));
|
Bindings::iterator j2 = v2.attrs->find(state.symbols.create(state.sValue));
|
||||||
if (j2 == v2.attrs->end())
|
if (j2 == v2.attrs->end())
|
||||||
throw TypeError(format("'value' attribute missing in a call to 'listToAttrs', at %1%") % pos);
|
throw TypeError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("'value' attribute missing in a call to 'listToAttrs'"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
v.attrs->push_back(Attr(sym, j2->value, j2->pos));
|
v.attrs->push_back(Attr(sym, j2->value, j2->pos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1351,7 +1486,11 @@ static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args
|
||||||
{
|
{
|
||||||
state.forceValue(*args[0], pos);
|
state.forceValue(*args[0], pos);
|
||||||
if (args[0]->type != tLambda)
|
if (args[0]->type != tLambda)
|
||||||
throw TypeError(format("'functionArgs' requires a function, at %1%") % pos);
|
throw TypeError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("'functionArgs' requires a function"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
if (!args[0]->lambda.fun->matchAttrs) {
|
if (!args[0]->lambda.fun->matchAttrs) {
|
||||||
state.mkAttrs(v, 0);
|
state.mkAttrs(v, 0);
|
||||||
|
@ -1404,7 +1543,11 @@ static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Valu
|
||||||
{
|
{
|
||||||
state.forceList(list, pos);
|
state.forceList(list, pos);
|
||||||
if (n < 0 || (unsigned int) n >= list.listSize())
|
if (n < 0 || (unsigned int) n >= list.listSize())
|
||||||
throw Error(format("list index %1% is out of bounds, at %2%") % n % pos);
|
throw Error(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("list index %1% is out of bounds", n),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
state.forceValue(*list.listElems()[n], pos);
|
state.forceValue(*list.listElems()[n], pos);
|
||||||
v = *list.listElems()[n];
|
v = *list.listElems()[n];
|
||||||
}
|
}
|
||||||
|
@ -1431,7 +1574,12 @@ static void prim_tail(EvalState & state, const Pos & pos, Value * * args, Value
|
||||||
{
|
{
|
||||||
state.forceList(*args[0], pos);
|
state.forceList(*args[0], pos);
|
||||||
if (args[0]->listSize() == 0)
|
if (args[0]->listSize() == 0)
|
||||||
throw Error(format("'tail' called on an empty list, at %1%") % pos);
|
throw Error(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("'tail' called on an empty list"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
state.mkList(v, args[0]->listSize() - 1);
|
state.mkList(v, args[0]->listSize() - 1);
|
||||||
for (unsigned int n = 0; n < v.listSize(); ++n)
|
for (unsigned int n = 0; n < v.listSize(); ++n)
|
||||||
v.listElems()[n] = args[0]->listElems()[n + 1];
|
v.listElems()[n] = args[0]->listElems()[n + 1];
|
||||||
|
@ -1572,7 +1720,12 @@ static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Val
|
||||||
auto len = state.forceInt(*args[1], pos);
|
auto len = state.forceInt(*args[1], pos);
|
||||||
|
|
||||||
if (len < 0)
|
if (len < 0)
|
||||||
throw EvalError(format("cannot create list of size %1%, at %2%") % len % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("cannot create list of size %1%", len),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
state.mkList(v, len);
|
state.mkList(v, len);
|
||||||
|
|
||||||
|
@ -1730,7 +1883,12 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value &
|
||||||
state.forceValue(*args[1], pos);
|
state.forceValue(*args[1], pos);
|
||||||
|
|
||||||
NixFloat f2 = state.forceFloat(*args[1], pos);
|
NixFloat f2 = state.forceFloat(*args[1], pos);
|
||||||
if (f2 == 0) throw EvalError(format("division by zero, at %1%") % pos);
|
if (f2 == 0)
|
||||||
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("division by zero"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
if (args[0]->type == tFloat || args[1]->type == tFloat) {
|
if (args[0]->type == tFloat || args[1]->type == tFloat) {
|
||||||
mkFloat(v, state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos));
|
mkFloat(v, state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos));
|
||||||
|
@ -1739,7 +1897,12 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value &
|
||||||
NixInt i2 = state.forceInt(*args[1], pos);
|
NixInt i2 = state.forceInt(*args[1], pos);
|
||||||
/* Avoid division overflow as it might raise SIGFPE. */
|
/* Avoid division overflow as it might raise SIGFPE. */
|
||||||
if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1)
|
if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1)
|
||||||
throw EvalError(format("overflow in integer division, at %1%") % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("overflow in integer division"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
mkInt(v, i1 / i2);
|
mkInt(v, i1 / i2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1795,7 +1958,12 @@ static void prim_substring(EvalState & state, const Pos & pos, Value * * args, V
|
||||||
PathSet context;
|
PathSet context;
|
||||||
string s = state.coerceToString(pos, *args[2], context);
|
string s = state.coerceToString(pos, *args[2], context);
|
||||||
|
|
||||||
if (start < 0) throw EvalError(format("negative start position in 'substring', at %1%") % pos);
|
if (start < 0)
|
||||||
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("negative start position in 'substring'"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
mkString(v, (unsigned int) start >= s.size() ? "" : string(s, start, len), context);
|
mkString(v, (unsigned int) start >= s.size() ? "" : string(s, start, len), context);
|
||||||
}
|
}
|
||||||
|
@ -1815,7 +1983,11 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args,
|
||||||
string type = state.forceStringNoCtx(*args[0], pos);
|
string type = state.forceStringNoCtx(*args[0], pos);
|
||||||
HashType ht = parseHashType(type);
|
HashType ht = parseHashType(type);
|
||||||
if (ht == htUnknown)
|
if (ht == htUnknown)
|
||||||
throw Error(format("unknown hash type '%1%', at %2%") % type % pos);
|
throw Error(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("unknown hash type '%1%'", type),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
PathSet context; // discarded
|
PathSet context; // discarded
|
||||||
string s = state.forceString(*args[1], context, pos);
|
string s = state.forceString(*args[1], context, pos);
|
||||||
|
@ -1858,9 +2030,17 @@ void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
} catch (std::regex_error &e) {
|
} catch (std::regex_error &e) {
|
||||||
if (e.code() == std::regex_constants::error_space) {
|
if (e.code() == std::regex_constants::error_space) {
|
||||||
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
||||||
throw EvalError("memory limit exceeded by regular expression '%s', at %s", re, pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("memory limit exceeded by regular expression '%s'", re),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
throw EvalError("invalid regular expression '%s', at %s", re, pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("invalid regular expression '%s'", re),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1925,9 +2105,17 @@ static void prim_split(EvalState & state, const Pos & pos, Value * * args, Value
|
||||||
} catch (std::regex_error &e) {
|
} catch (std::regex_error &e) {
|
||||||
if (e.code() == std::regex_constants::error_space) {
|
if (e.code() == std::regex_constants::error_space) {
|
||||||
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
||||||
throw EvalError("memory limit exceeded by regular expression '%s', at %s", re, pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("memory limit exceeded by regular expression '%s'", re),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
throw EvalError("invalid regular expression '%s', at %s", re, pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("invalid regular expression '%s'", re),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1958,7 +2146,11 @@ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * ar
|
||||||
state.forceList(*args[0], pos);
|
state.forceList(*args[0], pos);
|
||||||
state.forceList(*args[1], pos);
|
state.forceList(*args[1], pos);
|
||||||
if (args[0]->listSize() != args[1]->listSize())
|
if (args[0]->listSize() != args[1]->listSize())
|
||||||
throw EvalError(format("'from' and 'to' arguments to 'replaceStrings' have different lengths, at %1%") % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
vector<string> from;
|
vector<string> from;
|
||||||
from.reserve(args[0]->listSize());
|
from.reserve(args[0]->listSize());
|
||||||
|
|
|
@ -146,7 +146,11 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
|
||||||
auto sAllOutputs = state.symbols.create("allOutputs");
|
auto sAllOutputs = state.symbols.create("allOutputs");
|
||||||
for (auto & i : *args[1]->attrs) {
|
for (auto & i : *args[1]->attrs) {
|
||||||
if (!state.store->isStorePath(i.name))
|
if (!state.store->isStorePath(i.name))
|
||||||
throw EvalError("Context key '%s' is not a store path, at %s", i.name, i.pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("Context key '%s' is not a store path", i.name),
|
||||||
|
.nixCode = NixCode { .errPos = *i.pos }
|
||||||
|
});
|
||||||
if (!settings.readOnlyMode)
|
if (!settings.readOnlyMode)
|
||||||
state.store->ensurePath(state.store->parseStorePath(i.name));
|
state.store->ensurePath(state.store->parseStorePath(i.name));
|
||||||
state.forceAttrs(*i.value, *i.pos);
|
state.forceAttrs(*i.value, *i.pos);
|
||||||
|
@ -160,7 +164,11 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
|
||||||
if (iter != i.value->attrs->end()) {
|
if (iter != i.value->attrs->end()) {
|
||||||
if (state.forceBool(*iter->value, *iter->pos)) {
|
if (state.forceBool(*iter->value, *iter->pos)) {
|
||||||
if (!isDerivation(i.name)) {
|
if (!isDerivation(i.name)) {
|
||||||
throw EvalError("Tried to add all-outputs context of %s, which is not a derivation, to a string, at %s", i.name, i.pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name),
|
||||||
|
.nixCode = NixCode { .errPos = *i.pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
context.insert("=" + string(i.name));
|
context.insert("=" + string(i.name));
|
||||||
}
|
}
|
||||||
|
@ -170,7 +178,11 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
|
||||||
if (iter != i.value->attrs->end()) {
|
if (iter != i.value->attrs->end()) {
|
||||||
state.forceList(*iter->value, *iter->pos);
|
state.forceList(*iter->value, *iter->pos);
|
||||||
if (iter->value->listSize() && !isDerivation(i.name)) {
|
if (iter->value->listSize() && !isDerivation(i.name)) {
|
||||||
throw EvalError("Tried to add derivation output context of %s, which is not a derivation, to a string, at %s", i.name, i.pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name),
|
||||||
|
.nixCode = NixCode { .errPos = *i.pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
for (unsigned int n = 0; n < iter->value->listSize(); ++n) {
|
for (unsigned int n = 0; n < iter->value->listSize(); ++n) {
|
||||||
auto name = state.forceStringNoCtx(*iter->value->listElems()[n], *iter->pos);
|
auto name = state.forceStringNoCtx(*iter->value->listElems()[n], *iter->pos);
|
||||||
|
|
|
@ -35,11 +35,19 @@ static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Va
|
||||||
else if (n == "submodules")
|
else if (n == "submodules")
|
||||||
fetchSubmodules = state.forceBool(*attr.value, *attr.pos);
|
fetchSubmodules = state.forceBool(*attr.value, *attr.pos);
|
||||||
else
|
else
|
||||||
throw EvalError("unsupported argument '%s' to 'fetchGit', at %s", attr.name, *attr.pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("unsupported argument '%s' to 'fetchGit'", attr.name),
|
||||||
|
.nixCode = NixCode { .errPos = *attr.pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.empty())
|
if (url.empty())
|
||||||
throw EvalError(format("'url' argument required, at %1%") % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("'url' argument required"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
} else
|
} else
|
||||||
url = state.coerceToString(pos, *args[0], context, false, false);
|
url = state.coerceToString(pos, *args[0], context, false, false);
|
||||||
|
|
|
@ -38,11 +38,19 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
|
||||||
else if (n == "name")
|
else if (n == "name")
|
||||||
name = state.forceStringNoCtx(*attr.value, *attr.pos);
|
name = state.forceStringNoCtx(*attr.value, *attr.pos);
|
||||||
else
|
else
|
||||||
throw EvalError("unsupported argument '%s' to 'fetchMercurial', at %s", attr.name, *attr.pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name),
|
||||||
|
.nixCode = NixCode { .errPos = *attr.pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.empty())
|
if (url.empty())
|
||||||
throw EvalError(format("'url' argument required, at %1%") % pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("'url' argument required"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
} else
|
} else
|
||||||
url = state.coerceToString(pos, *args[0], context, false, false);
|
url = state.coerceToString(pos, *args[0], context, false, false);
|
||||||
|
|
|
@ -66,7 +66,11 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!attrs.count("type"))
|
if (!attrs.count("type"))
|
||||||
throw Error("attribute 'type' is missing in call to 'fetchTree', at %s", pos);
|
throw Error(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
|
|
||||||
input = fetchers::inputFromAttrs(attrs);
|
input = fetchers::inputFromAttrs(attrs);
|
||||||
} else
|
} else
|
||||||
|
@ -107,13 +111,20 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
|
||||||
else if (n == "name")
|
else if (n == "name")
|
||||||
name = state.forceStringNoCtx(*attr.value, *attr.pos);
|
name = state.forceStringNoCtx(*attr.value, *attr.pos);
|
||||||
else
|
else
|
||||||
throw EvalError("unsupported argument '%s' to '%s', at %s",
|
throw EvalError(
|
||||||
attr.name, who, *attr.pos);
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("unsupported argument '%s' to '%s'",
|
||||||
|
attr.name, who),
|
||||||
|
.nixCode = NixCode { .errPos = *attr.pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!url)
|
if (!url)
|
||||||
throw EvalError("'url' argument required, at %s", pos);
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("'url' argument required"),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
} else
|
} else
|
||||||
url = state.forceStringNoCtx(*args[0], pos);
|
url = state.forceStringNoCtx(*args[0], pos);
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,11 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va
|
||||||
try {
|
try {
|
||||||
visit(v, parser(tomlStream).parse());
|
visit(v, parser(tomlStream).parse());
|
||||||
} catch (std::runtime_error & e) {
|
} catch (std::runtime_error & e) {
|
||||||
throw EvalError("while parsing a TOML string at %s: %s", pos, e.what());
|
throw EvalError(
|
||||||
|
ErrorInfo {
|
||||||
|
.hint = hintfmt("while parsing a TOML string: %s", e.what()),
|
||||||
|
.nixCode = NixCode { .errPos = pos }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ void printValueAsJSON(EvalState & state, bool strict,
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw TypeError(format("cannot convert %1% to JSON") % showType(v));
|
throw TypeError("cannot convert %1% to JSON", showType(v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ void printValueAsJSON(EvalState & state, bool strict,
|
||||||
void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
|
void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
|
||||||
JSONPlaceholder & out, PathSet & context) const
|
JSONPlaceholder & out, PathSet & context) const
|
||||||
{
|
{
|
||||||
throw TypeError(format("cannot convert %1% to JSON") % showType());
|
throw TypeError("cannot convert %1% to JSON", showType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,16 @@ public:
|
||||||
log(*state, lvl, fs.s);
|
log(*state, lvl, fs.s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void logEI(const ErrorInfo &ei) override
|
||||||
|
{
|
||||||
|
auto state(state_.lock());
|
||||||
|
|
||||||
|
std::stringstream oss;
|
||||||
|
oss << ei;
|
||||||
|
|
||||||
|
log(*state, ei.level, oss.str());
|
||||||
|
}
|
||||||
|
|
||||||
void log(State & state, Verbosity lvl, const std::string & s)
|
void log(State & state, Verbosity lvl, const std::string & s)
|
||||||
{
|
{
|
||||||
if (state.active) {
|
if (state.active) {
|
||||||
|
|
|
@ -76,7 +76,7 @@ string getArg(const string & opt,
|
||||||
Strings::iterator & i, const Strings::iterator & end)
|
Strings::iterator & i, const Strings::iterator & end)
|
||||||
{
|
{
|
||||||
++i;
|
++i;
|
||||||
if (i == end) throw UsageError(format("'%1%' requires an argument") % opt);
|
if (i == end) throw UsageError("'%1%' requires an argument", opt);
|
||||||
return *i;
|
return *i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +235,7 @@ bool LegacyArgs::processArgs(const Strings & args, bool finish)
|
||||||
Strings ss(args);
|
Strings ss(args);
|
||||||
auto pos = ss.begin();
|
auto pos = ss.begin();
|
||||||
if (!parseArg(pos, ss.end()))
|
if (!parseArg(pos, ss.end()))
|
||||||
throw UsageError(format("unexpected argument '%1%'") % args.front());
|
throw UsageError("unexpected argument '%1%'", args.front());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,7 +282,7 @@ void showManPage(const string & name)
|
||||||
restoreSignals();
|
restoreSignals();
|
||||||
setenv("MANPATH", settings.nixManDir.c_str(), 1);
|
setenv("MANPATH", settings.nixManDir.c_str(), 1);
|
||||||
execlp("man", "man", name.c_str(), nullptr);
|
execlp("man", "man", name.c_str(), nullptr);
|
||||||
throw SysError(format("command 'man %1%' failed") % name.c_str());
|
throw SysError("command 'man %1%' failed", name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -290,6 +290,8 @@ int handleExceptions(const string & programName, std::function<void()> fun)
|
||||||
{
|
{
|
||||||
ReceiveInterrupts receiveInterrupts; // FIXME: need better place for this
|
ReceiveInterrupts receiveInterrupts; // FIXME: need better place for this
|
||||||
|
|
||||||
|
ErrorInfo::programName = programName;
|
||||||
|
|
||||||
string error = ANSI_RED "error:" ANSI_NORMAL " ";
|
string error = ANSI_RED "error:" ANSI_NORMAL " ";
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
|
@ -305,12 +307,13 @@ int handleExceptions(const string & programName, std::function<void()> fun)
|
||||||
} catch (Exit & e) {
|
} catch (Exit & e) {
|
||||||
return e.status;
|
return e.status;
|
||||||
} catch (UsageError & e) {
|
} catch (UsageError & e) {
|
||||||
printError(
|
logError(e.info());
|
||||||
format(error + "%1%\nTry '%2% --help' for more information.")
|
printError("Try '%1% --help' for more information.", programName);
|
||||||
% e.what() % programName);
|
|
||||||
return 1;
|
return 1;
|
||||||
} catch (BaseError & e) {
|
} catch (BaseError & e) {
|
||||||
printError(format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
|
if (settings.showTrace && e.prefix() != "")
|
||||||
|
printError(e.prefix());
|
||||||
|
logError(e.info());
|
||||||
if (e.prefix() != "" && !settings.showTrace)
|
if (e.prefix() != "" && !settings.showTrace)
|
||||||
printError("(use '--show-trace' to show detailed location information)");
|
printError("(use '--show-trace' to show detailed location information)");
|
||||||
return e.status;
|
return e.status;
|
||||||
|
@ -347,7 +350,7 @@ RunPager::RunPager()
|
||||||
execlp("pager", "pager", nullptr);
|
execlp("pager", "pager", nullptr);
|
||||||
execlp("less", "less", nullptr);
|
execlp("less", "less", nullptr);
|
||||||
execlp("more", "more", nullptr);
|
execlp("more", "more", nullptr);
|
||||||
throw SysError(format("executing '%1%'") % pager);
|
throw SysError("executing '%1%'", pager);
|
||||||
});
|
});
|
||||||
|
|
||||||
pid.setKillSignal(SIGINT);
|
pid.setKillSignal(SIGINT);
|
||||||
|
|
|
@ -56,7 +56,7 @@ template<class N> N getIntArg(const string & opt,
|
||||||
Strings::iterator & i, const Strings::iterator & end, bool allowUnit)
|
Strings::iterator & i, const Strings::iterator & end, bool allowUnit)
|
||||||
{
|
{
|
||||||
++i;
|
++i;
|
||||||
if (i == end) throw UsageError(format("'%1%' requires an argument") % opt);
|
if (i == end) throw UsageError("'%1%' requires an argument", opt);
|
||||||
string s = *i;
|
string s = *i;
|
||||||
N multiplier = 1;
|
N multiplier = 1;
|
||||||
if (allowUnit && !s.empty()) {
|
if (allowUnit && !s.empty()) {
|
||||||
|
@ -66,13 +66,13 @@ template<class N> N getIntArg(const string & opt,
|
||||||
else if (u == 'M') multiplier = 1ULL << 20;
|
else if (u == 'M') multiplier = 1ULL << 20;
|
||||||
else if (u == 'G') multiplier = 1ULL << 30;
|
else if (u == 'G') multiplier = 1ULL << 30;
|
||||||
else if (u == 'T') multiplier = 1ULL << 40;
|
else if (u == 'T') multiplier = 1ULL << 40;
|
||||||
else throw UsageError(format("invalid unit specifier '%1%'") % u);
|
else throw UsageError("invalid unit specifier '%1%'", u);
|
||||||
s.resize(s.size() - 1);
|
s.resize(s.size() - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
N n;
|
N n;
|
||||||
if (!string2Int(s, n))
|
if (!string2Int(s, n))
|
||||||
throw UsageError(format("'%1%' requires an integer argument") % opt);
|
throw UsageError("'%1%' requires an integer argument", opt);
|
||||||
return n * multiplier;
|
return n * multiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include "types.hh"
|
#include "error.hh"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
|
@ -40,14 +40,14 @@ void BinaryCacheStore::init()
|
||||||
upsertFile(cacheInfoFile, "StoreDir: " + storeDir + "\n", "text/x-nix-cache-info");
|
upsertFile(cacheInfoFile, "StoreDir: " + storeDir + "\n", "text/x-nix-cache-info");
|
||||||
} else {
|
} else {
|
||||||
for (auto & line : tokenizeString<Strings>(*cacheInfo, "\n")) {
|
for (auto & line : tokenizeString<Strings>(*cacheInfo, "\n")) {
|
||||||
size_t colon = line.find(':');
|
size_t colon= line.find(':');
|
||||||
if (colon == std::string::npos) continue;
|
if (colon ==std::string::npos) continue;
|
||||||
auto name = line.substr(0, colon);
|
auto name = line.substr(0, colon);
|
||||||
auto value = trim(line.substr(colon + 1, std::string::npos));
|
auto value = trim(line.substr(colon + 1, std::string::npos));
|
||||||
if (name == "StoreDir") {
|
if (name == "StoreDir") {
|
||||||
if (value != storeDir)
|
if (value != storeDir)
|
||||||
throw Error(format("binary cache '%s' is for Nix stores with prefix '%s', not '%s'")
|
throw Error("binary cache '%s' is for Nix stores with prefix '%s', not '%s'",
|
||||||
% getUri() % value % storeDir);
|
getUri(), value, storeDir);
|
||||||
} else if (name == "WantMassQuery") {
|
} else if (name == "WantMassQuery") {
|
||||||
wantMassQuery.setDefault(value == "1" ? "true" : "false");
|
wantMassQuery.setDefault(value == "1" ? "true" : "false");
|
||||||
} else if (name == "Priority") {
|
} else if (name == "Priority") {
|
||||||
|
@ -287,7 +287,7 @@ void BinaryCacheStore::narFromPath(const StorePath & storePath, Sink & sink)
|
||||||
try {
|
try {
|
||||||
getFile(info->url, *decompressor);
|
getFile(info->url, *decompressor);
|
||||||
} catch (NoSuchBinaryCacheFile & e) {
|
} catch (NoSuchBinaryCacheFile & e) {
|
||||||
throw SubstituteGone(e.what());
|
throw SubstituteGone(e.info());
|
||||||
}
|
}
|
||||||
|
|
||||||
decompressor->finish();
|
decompressor->finish();
|
||||||
|
|
|
@ -453,7 +453,7 @@ static void commonChildInit(Pipe & logPipe)
|
||||||
that e.g. ssh cannot open /dev/tty) and it doesn't receive
|
that e.g. ssh cannot open /dev/tty) and it doesn't receive
|
||||||
terminal signals. */
|
terminal signals. */
|
||||||
if (setsid() == -1)
|
if (setsid() == -1)
|
||||||
throw SysError(format("creating a new session"));
|
throw SysError("creating a new session");
|
||||||
|
|
||||||
/* Dup the write side of the logger pipe into stderr. */
|
/* Dup the write side of the logger pipe into stderr. */
|
||||||
if (dup2(logPipe.writeSide.get(), STDERR_FILENO) == -1)
|
if (dup2(logPipe.writeSide.get(), STDERR_FILENO) == -1)
|
||||||
|
@ -466,7 +466,7 @@ static void commonChildInit(Pipe & logPipe)
|
||||||
/* Reroute stdin to /dev/null. */
|
/* Reroute stdin to /dev/null. */
|
||||||
int fdDevNull = open(pathNullDevice.c_str(), O_RDWR);
|
int fdDevNull = open(pathNullDevice.c_str(), O_RDWR);
|
||||||
if (fdDevNull == -1)
|
if (fdDevNull == -1)
|
||||||
throw SysError(format("cannot open '%1%'") % pathNullDevice);
|
throw SysError("cannot open '%1%'", pathNullDevice);
|
||||||
if (dup2(fdDevNull, STDIN_FILENO) == -1)
|
if (dup2(fdDevNull, STDIN_FILENO) == -1)
|
||||||
throw SysError("cannot dup null device into stdin");
|
throw SysError("cannot dup null device into stdin");
|
||||||
close(fdDevNull);
|
close(fdDevNull);
|
||||||
|
@ -488,12 +488,18 @@ void handleDiffHook(
|
||||||
|
|
||||||
auto diffRes = runProgram(diffHookOptions);
|
auto diffRes = runProgram(diffHookOptions);
|
||||||
if (!statusOk(diffRes.first))
|
if (!statusOk(diffRes.first))
|
||||||
throw ExecError(diffRes.first, fmt("diff-hook program '%1%' %2%", diffHook, statusToString(diffRes.first)));
|
throw ExecError(diffRes.first,
|
||||||
|
"diff-hook program '%1%' %2%",
|
||||||
|
diffHook,
|
||||||
|
statusToString(diffRes.first));
|
||||||
|
|
||||||
if (diffRes.second != "")
|
if (diffRes.second != "")
|
||||||
printError(chomp(diffRes.second));
|
printError(chomp(diffRes.second));
|
||||||
} catch (Error & error) {
|
} catch (Error & error) {
|
||||||
printError("diff hook execution failed: %s", error.what());
|
ErrorInfo ei = error.info();
|
||||||
|
ei.hint = hintfmt("diff hook execution failed: %s",
|
||||||
|
(error.info().hint.has_value() ? error.info().hint->str() : ""));
|
||||||
|
logError(ei);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -542,37 +548,37 @@ bool UserLock::findFreeUser() {
|
||||||
/* Get the members of the build-users-group. */
|
/* Get the members of the build-users-group. */
|
||||||
struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
|
struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
|
||||||
if (!gr)
|
if (!gr)
|
||||||
throw Error(format("the group '%1%' specified in 'build-users-group' does not exist")
|
throw Error("the group '%1%' specified in 'build-users-group' does not exist",
|
||||||
% settings.buildUsersGroup);
|
settings.buildUsersGroup);
|
||||||
gid = gr->gr_gid;
|
gid = gr->gr_gid;
|
||||||
|
|
||||||
/* Copy the result of getgrnam. */
|
/* Copy the result of getgrnam. */
|
||||||
Strings users;
|
Strings users;
|
||||||
for (char * * p = gr->gr_mem; *p; ++p) {
|
for (char * * p = gr->gr_mem; *p; ++p) {
|
||||||
debug(format("found build user '%1%'") % *p);
|
debug("found build user '%1%'", *p);
|
||||||
users.push_back(*p);
|
users.push_back(*p);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (users.empty())
|
if (users.empty())
|
||||||
throw Error(format("the build users group '%1%' has no members")
|
throw Error("the build users group '%1%' has no members",
|
||||||
% settings.buildUsersGroup);
|
settings.buildUsersGroup);
|
||||||
|
|
||||||
/* Find a user account that isn't currently in use for another
|
/* Find a user account that isn't currently in use for another
|
||||||
build. */
|
build. */
|
||||||
for (auto & i : users) {
|
for (auto & i : users) {
|
||||||
debug(format("trying user '%1%'") % i);
|
debug("trying user '%1%'", i);
|
||||||
|
|
||||||
struct passwd * pw = getpwnam(i.c_str());
|
struct passwd * pw = getpwnam(i.c_str());
|
||||||
if (!pw)
|
if (!pw)
|
||||||
throw Error(format("the user '%1%' in the group '%2%' does not exist")
|
throw Error("the user '%1%' in the group '%2%' does not exist",
|
||||||
% i % settings.buildUsersGroup);
|
i, settings.buildUsersGroup);
|
||||||
|
|
||||||
|
|
||||||
fnUserLock = (format("%1%/userpool/%2%") % settings.nixStateDir % pw->pw_uid).str();
|
fnUserLock = (format("%1%/userpool/%2%") % settings.nixStateDir % pw->pw_uid).str();
|
||||||
|
|
||||||
AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);
|
AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);
|
||||||
if (!fd)
|
if (!fd)
|
||||||
throw SysError(format("opening user lock '%1%'") % fnUserLock);
|
throw SysError("opening user lock '%1%'", fnUserLock);
|
||||||
|
|
||||||
if (lockFile(fd.get(), ltWrite, false)) {
|
if (lockFile(fd.get(), ltWrite, false)) {
|
||||||
fdUserLock = std::move(fd);
|
fdUserLock = std::move(fd);
|
||||||
|
@ -581,8 +587,8 @@ bool UserLock::findFreeUser() {
|
||||||
|
|
||||||
/* Sanity check... */
|
/* Sanity check... */
|
||||||
if (uid == getuid() || uid == geteuid())
|
if (uid == getuid() || uid == geteuid())
|
||||||
throw Error(format("the Nix user should not be a member of '%1%'")
|
throw Error("the Nix user should not be a member of '%1%'",
|
||||||
% settings.buildUsersGroup);
|
settings.buildUsersGroup);
|
||||||
|
|
||||||
#if __linux__
|
#if __linux__
|
||||||
/* Get the list of supplementary groups of this build user. This
|
/* Get the list of supplementary groups of this build user. This
|
||||||
|
@ -592,7 +598,7 @@ bool UserLock::findFreeUser() {
|
||||||
int err = getgrouplist(pw->pw_name, pw->pw_gid,
|
int err = getgrouplist(pw->pw_name, pw->pw_gid,
|
||||||
supplementaryGIDs.data(), &ngroups);
|
supplementaryGIDs.data(), &ngroups);
|
||||||
if (err == -1)
|
if (err == -1)
|
||||||
throw Error(format("failed to get list of supplementary groups for '%1%'") % pw->pw_name);
|
throw Error("failed to get list of supplementary groups for '%1%'", pw->pw_name);
|
||||||
|
|
||||||
supplementaryGIDs.resize(ngroups);
|
supplementaryGIDs.resize(ngroups);
|
||||||
#endif
|
#endif
|
||||||
|
@ -601,6 +607,7 @@ bool UserLock::findFreeUser() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1151,7 +1158,10 @@ void DerivationGoal::loadDerivation()
|
||||||
trace("loading derivation");
|
trace("loading derivation");
|
||||||
|
|
||||||
if (nrFailed != 0) {
|
if (nrFailed != 0) {
|
||||||
printError("cannot build missing derivation '%s'", worker.store.printStorePath(drvPath));
|
logError({
|
||||||
|
.name = "missing derivation during build",
|
||||||
|
.hint = hintfmt("cannot build missing derivation '%s'", worker.store.printStorePath(drvPath))
|
||||||
|
});
|
||||||
done(BuildResult::MiscFailure);
|
done(BuildResult::MiscFailure);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1302,8 +1312,12 @@ void DerivationGoal::repairClosure()
|
||||||
/* Check each path (slow!). */
|
/* Check each path (slow!). */
|
||||||
for (auto & i : outputClosure) {
|
for (auto & i : outputClosure) {
|
||||||
if (worker.pathContentsGood(i)) continue;
|
if (worker.pathContentsGood(i)) continue;
|
||||||
printError("found corrupted or missing path '%s' in the output closure of '%s'",
|
logError({
|
||||||
worker.store.printStorePath(i), worker.store.printStorePath(drvPath));
|
.name = "Corrupt path in closure",
|
||||||
|
.hint = hintfmt(
|
||||||
|
"found corrupted or missing path '%s' in the output closure of '%s'",
|
||||||
|
worker.store.printStorePath(i), worker.store.printStorePath(drvPath))
|
||||||
|
});
|
||||||
auto drvPath2 = outputsToDrv.find(i);
|
auto drvPath2 = outputsToDrv.find(i);
|
||||||
if (drvPath2 == outputsToDrv.end())
|
if (drvPath2 == outputsToDrv.end())
|
||||||
addWaitee(worker.makeSubstitutionGoal(i, Repair));
|
addWaitee(worker.makeSubstitutionGoal(i, Repair));
|
||||||
|
@ -1337,8 +1351,12 @@ void DerivationGoal::inputsRealised()
|
||||||
if (nrFailed != 0) {
|
if (nrFailed != 0) {
|
||||||
if (!useDerivation)
|
if (!useDerivation)
|
||||||
throw Error("some dependencies of '%s' are missing", worker.store.printStorePath(drvPath));
|
throw Error("some dependencies of '%s' are missing", worker.store.printStorePath(drvPath));
|
||||||
printError("cannot build derivation '%s': %s dependencies couldn't be built",
|
logError({
|
||||||
worker.store.printStorePath(drvPath), nrFailed);
|
.name = "Dependencies could not be built",
|
||||||
|
.hint = hintfmt(
|
||||||
|
"cannot build derivation '%s': %s dependencies couldn't be built",
|
||||||
|
worker.store.printStorePath(drvPath), nrFailed)
|
||||||
|
});
|
||||||
done(BuildResult::DependencyFailed);
|
done(BuildResult::DependencyFailed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1523,7 +1541,7 @@ void DerivationGoal::tryLocalBuild() {
|
||||||
startBuilder();
|
startBuilder();
|
||||||
|
|
||||||
} catch (BuildError & e) {
|
} catch (BuildError & e) {
|
||||||
printError(e.msg());
|
logError(e.info());
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
buildUser.reset();
|
buildUser.reset();
|
||||||
worker.permanentFailure = true;
|
worker.permanentFailure = true;
|
||||||
|
@ -1740,7 +1758,7 @@ void DerivationGoal::buildDone()
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
|
|
||||||
} catch (BuildError & e) {
|
} catch (BuildError & e) {
|
||||||
printError(e.msg());
|
logError(e.info());
|
||||||
|
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
|
|
||||||
|
@ -1803,7 +1821,7 @@ HookReply DerivationGoal::tryBuildHook()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug(format("hook reply is '%1%'") % reply);
|
debug("hook reply is '%1%'", reply);
|
||||||
|
|
||||||
if (reply == "decline")
|
if (reply == "decline")
|
||||||
return rpDecline;
|
return rpDecline;
|
||||||
|
@ -1819,8 +1837,12 @@ HookReply DerivationGoal::tryBuildHook()
|
||||||
|
|
||||||
} catch (SysError & e) {
|
} catch (SysError & e) {
|
||||||
if (e.errNo == EPIPE) {
|
if (e.errNo == EPIPE) {
|
||||||
printError("build hook died unexpectedly: %s",
|
logError({
|
||||||
chomp(drainFD(worker.hook->fromHook.readSide.get())));
|
.name = "Build hook died",
|
||||||
|
.hint = hintfmt(
|
||||||
|
"build hook died unexpectedly: %s",
|
||||||
|
chomp(drainFD(worker.hook->fromHook.readSide.get())))
|
||||||
|
});
|
||||||
worker.hook = 0;
|
worker.hook = 0;
|
||||||
return rpDecline;
|
return rpDecline;
|
||||||
} else
|
} else
|
||||||
|
@ -2000,7 +2022,7 @@ void DerivationGoal::startBuilder()
|
||||||
string s = get(drv->env, "exportReferencesGraph").value_or("");
|
string s = get(drv->env, "exportReferencesGraph").value_or("");
|
||||||
Strings ss = tokenizeString<Strings>(s);
|
Strings ss = tokenizeString<Strings>(s);
|
||||||
if (ss.size() % 2 != 0)
|
if (ss.size() % 2 != 0)
|
||||||
throw BuildError(format("odd number of tokens in 'exportReferencesGraph': '%1%'") % s);
|
throw BuildError("odd number of tokens in 'exportReferencesGraph': '%1%'", s);
|
||||||
for (Strings::iterator i = ss.begin(); i != ss.end(); ) {
|
for (Strings::iterator i = ss.begin(); i != ss.end(); ) {
|
||||||
string fileName = *i++;
|
string fileName = *i++;
|
||||||
static std::regex regex("[A-Za-z_][A-Za-z0-9_.-]*");
|
static std::regex regex("[A-Za-z_][A-Za-z0-9_.-]*");
|
||||||
|
@ -2049,7 +2071,7 @@ void DerivationGoal::startBuilder()
|
||||||
worker.store.computeFSClosure(worker.store.parseStorePath(worker.store.toStorePath(i.second.source)), closure);
|
worker.store.computeFSClosure(worker.store.parseStorePath(worker.store.toStorePath(i.second.source)), closure);
|
||||||
} catch (InvalidPath & e) {
|
} catch (InvalidPath & e) {
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
throw Error(format("while processing 'sandbox-paths': %s") % e.what());
|
throw Error("while processing 'sandbox-paths': %s", e.what());
|
||||||
}
|
}
|
||||||
for (auto & i : closure) {
|
for (auto & i : closure) {
|
||||||
auto p = worker.store.printStorePath(i);
|
auto p = worker.store.printStorePath(i);
|
||||||
|
@ -2096,10 +2118,10 @@ void DerivationGoal::startBuilder()
|
||||||
printMsg(lvlChatty, format("setting up chroot environment in '%1%'") % chrootRootDir);
|
printMsg(lvlChatty, format("setting up chroot environment in '%1%'") % chrootRootDir);
|
||||||
|
|
||||||
if (mkdir(chrootRootDir.c_str(), 0750) == -1)
|
if (mkdir(chrootRootDir.c_str(), 0750) == -1)
|
||||||
throw SysError(format("cannot create '%1%'") % chrootRootDir);
|
throw SysError("cannot create '%1%'", chrootRootDir);
|
||||||
|
|
||||||
if (buildUser && chown(chrootRootDir.c_str(), 0, buildUser->getGID()) == -1)
|
if (buildUser && chown(chrootRootDir.c_str(), 0, buildUser->getGID()) == -1)
|
||||||
throw SysError(format("cannot change ownership of '%1%'") % chrootRootDir);
|
throw SysError("cannot change ownership of '%1%'", chrootRootDir);
|
||||||
|
|
||||||
/* Create a writable /tmp in the chroot. Many builders need
|
/* Create a writable /tmp in the chroot. Many builders need
|
||||||
this. (Of course they should really respect $TMPDIR
|
this. (Of course they should really respect $TMPDIR
|
||||||
|
@ -2143,7 +2165,7 @@ void DerivationGoal::startBuilder()
|
||||||
chmod_(chrootStoreDir, 01775);
|
chmod_(chrootStoreDir, 01775);
|
||||||
|
|
||||||
if (buildUser && chown(chrootStoreDir.c_str(), 0, buildUser->getGID()) == -1)
|
if (buildUser && chown(chrootStoreDir.c_str(), 0, buildUser->getGID()) == -1)
|
||||||
throw SysError(format("cannot change ownership of '%1%'") % chrootStoreDir);
|
throw SysError("cannot change ownership of '%1%'", chrootStoreDir);
|
||||||
|
|
||||||
for (auto & i : inputPaths) {
|
for (auto & i : inputPaths) {
|
||||||
auto p = worker.store.printStorePath(i);
|
auto p = worker.store.printStorePath(i);
|
||||||
|
@ -2176,7 +2198,7 @@ void DerivationGoal::startBuilder()
|
||||||
if (needsHashRewrite()) {
|
if (needsHashRewrite()) {
|
||||||
|
|
||||||
if (pathExists(homeDir))
|
if (pathExists(homeDir))
|
||||||
throw Error(format("home directory '%1%' exists; please remove it to assure purity of builds without sandboxing") % homeDir);
|
throw Error("home directory '%1%' exists; please remove it to assure purity of builds without sandboxing", homeDir);
|
||||||
|
|
||||||
/* We're not doing a chroot build, but we have some valid
|
/* We're not doing a chroot build, but we have some valid
|
||||||
output paths. Since we can't just overwrite or delete
|
output paths. Since we can't just overwrite or delete
|
||||||
|
@ -2221,8 +2243,7 @@ void DerivationGoal::startBuilder()
|
||||||
if (line == "extra-sandbox-paths" || line == "extra-chroot-dirs") {
|
if (line == "extra-sandbox-paths" || line == "extra-chroot-dirs") {
|
||||||
state = stExtraChrootDirs;
|
state = stExtraChrootDirs;
|
||||||
} else {
|
} else {
|
||||||
throw Error(format("unknown pre-build hook command '%1%'")
|
throw Error("unknown pre-build hook command '%1%'", line);
|
||||||
% line);
|
|
||||||
}
|
}
|
||||||
} else if (state == stExtraChrootDirs) {
|
} else if (state == stExtraChrootDirs) {
|
||||||
if (line == "") {
|
if (line == "") {
|
||||||
|
@ -2244,7 +2265,7 @@ void DerivationGoal::startBuilder()
|
||||||
startDaemon();
|
startDaemon();
|
||||||
|
|
||||||
/* Run the builder. */
|
/* Run the builder. */
|
||||||
printMsg(lvlChatty, format("executing builder '%1%'") % drv->builder);
|
printMsg(lvlChatty, "executing builder '%1%'", drv->builder);
|
||||||
|
|
||||||
/* Create the log file. */
|
/* Create the log file. */
|
||||||
Path logFile = openLogFile();
|
Path logFile = openLogFile();
|
||||||
|
@ -2982,7 +3003,7 @@ void DerivationGoal::chownToBuilder(const Path & path)
|
||||||
{
|
{
|
||||||
if (!buildUser) return;
|
if (!buildUser) return;
|
||||||
if (chown(path.c_str(), buildUser->getUID(), buildUser->getGID()) == -1)
|
if (chown(path.c_str(), buildUser->getUID(), buildUser->getGID()) == -1)
|
||||||
throw SysError(format("cannot change ownership of '%1%'") % path);
|
throw SysError("cannot change ownership of '%1%'", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -3119,7 +3140,7 @@ void DerivationGoal::runChild()
|
||||||
/* Bind-mount chroot directory to itself, to treat it as a
|
/* Bind-mount chroot directory to itself, to treat it as a
|
||||||
different filesystem from /, as needed for pivot_root. */
|
different filesystem from /, as needed for pivot_root. */
|
||||||
if (mount(chrootRootDir.c_str(), chrootRootDir.c_str(), 0, MS_BIND, 0) == -1)
|
if (mount(chrootRootDir.c_str(), chrootRootDir.c_str(), 0, MS_BIND, 0) == -1)
|
||||||
throw SysError(format("unable to bind mount '%1%'") % chrootRootDir);
|
throw SysError("unable to bind mount '%1%'", chrootRootDir);
|
||||||
|
|
||||||
/* Bind-mount the sandbox's Nix store onto itself so that
|
/* Bind-mount the sandbox's Nix store onto itself so that
|
||||||
we can mark it as a "shared" subtree, allowing bind
|
we can mark it as a "shared" subtree, allowing bind
|
||||||
|
@ -3181,7 +3202,7 @@ void DerivationGoal::runChild()
|
||||||
filesystem that we want in the chroot
|
filesystem that we want in the chroot
|
||||||
environment. */
|
environment. */
|
||||||
auto doBind = [&](const Path & source, const Path & target, bool optional = false) {
|
auto doBind = [&](const Path & source, const Path & target, bool optional = false) {
|
||||||
debug(format("bind mounting '%1%' to '%2%'") % source % target);
|
debug("bind mounting '%1%' to '%2%'", source, target);
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (stat(source.c_str(), &st) == -1) {
|
if (stat(source.c_str(), &st) == -1) {
|
||||||
if (optional && errno == ENOENT)
|
if (optional && errno == ENOENT)
|
||||||
|
@ -3253,16 +3274,16 @@ void DerivationGoal::runChild()
|
||||||
|
|
||||||
/* Do the chroot(). */
|
/* Do the chroot(). */
|
||||||
if (chdir(chrootRootDir.c_str()) == -1)
|
if (chdir(chrootRootDir.c_str()) == -1)
|
||||||
throw SysError(format("cannot change directory to '%1%'") % chrootRootDir);
|
throw SysError("cannot change directory to '%1%'", chrootRootDir);
|
||||||
|
|
||||||
if (mkdir("real-root", 0) == -1)
|
if (mkdir("real-root", 0) == -1)
|
||||||
throw SysError("cannot create real-root directory");
|
throw SysError("cannot create real-root directory");
|
||||||
|
|
||||||
if (pivot_root(".", "real-root") == -1)
|
if (pivot_root(".", "real-root") == -1)
|
||||||
throw SysError(format("cannot pivot old root directory onto '%1%'") % (chrootRootDir + "/real-root"));
|
throw SysError("cannot pivot old root directory onto '%1%'", (chrootRootDir + "/real-root"));
|
||||||
|
|
||||||
if (chroot(".") == -1)
|
if (chroot(".") == -1)
|
||||||
throw SysError(format("cannot change root directory to '%1%'") % chrootRootDir);
|
throw SysError("cannot change root directory to '%1%'", chrootRootDir);
|
||||||
|
|
||||||
if (umount2("real-root", MNT_DETACH) == -1)
|
if (umount2("real-root", MNT_DETACH) == -1)
|
||||||
throw SysError("cannot unmount real root filesystem");
|
throw SysError("cannot unmount real root filesystem");
|
||||||
|
@ -3283,7 +3304,7 @@ void DerivationGoal::runChild()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (chdir(tmpDirInSandbox.c_str()) == -1)
|
if (chdir(tmpDirInSandbox.c_str()) == -1)
|
||||||
throw SysError(format("changing into '%1%'") % tmpDir);
|
throw SysError("changing into '%1%'", tmpDir);
|
||||||
|
|
||||||
/* Close all other file descriptors. */
|
/* Close all other file descriptors. */
|
||||||
closeMostFDs({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO});
|
closeMostFDs({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO});
|
||||||
|
@ -3422,9 +3443,9 @@ void DerivationGoal::runChild()
|
||||||
sandboxProfile += "(allow file-read* file-write* process-exec\n";
|
sandboxProfile += "(allow file-read* file-write* process-exec\n";
|
||||||
for (auto & i : dirsInChroot) {
|
for (auto & i : dirsInChroot) {
|
||||||
if (i.first != i.second.source)
|
if (i.first != i.second.source)
|
||||||
throw Error(format(
|
throw Error(
|
||||||
"can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin")
|
"can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin",
|
||||||
% i.first % i.second.source);
|
i.first, i.second.source);
|
||||||
|
|
||||||
string path = i.first;
|
string path = i.first;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
@ -3515,7 +3536,7 @@ void DerivationGoal::runChild()
|
||||||
else if (drv->builder == "builtin:unpack-channel")
|
else if (drv->builder == "builtin:unpack-channel")
|
||||||
builtinUnpackChannel(drv2);
|
builtinUnpackChannel(drv2);
|
||||||
else
|
else
|
||||||
throw Error(format("unsupported builtin function '%1%'") % string(drv->builder, 8));
|
throw Error("unsupported builtin function '%1%'", string(drv->builder, 8));
|
||||||
_exit(0);
|
_exit(0);
|
||||||
} catch (std::exception & e) {
|
} catch (std::exception & e) {
|
||||||
writeFull(STDERR_FILENO, "error: " + string(e.what()) + "\n");
|
writeFull(STDERR_FILENO, "error: " + string(e.what()) + "\n");
|
||||||
|
@ -3525,7 +3546,7 @@ void DerivationGoal::runChild()
|
||||||
|
|
||||||
execve(builder, stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
|
execve(builder, stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
|
||||||
|
|
||||||
throw SysError(format("executing '%1%'") % drv->builder);
|
throw SysError("executing '%1%'", drv->builder);
|
||||||
|
|
||||||
} catch (std::exception & e) {
|
} catch (std::exception & e) {
|
||||||
writeFull(STDERR_FILENO, "\1while setting up the build environment: " + string(e.what()) + "\n");
|
writeFull(STDERR_FILENO, "\1while setting up the build environment: " + string(e.what()) + "\n");
|
||||||
|
@ -3558,7 +3579,7 @@ static void moveCheckToStore(const Path & src, const Path & dst)
|
||||||
directory's parent link ".."). */
|
directory's parent link ".."). */
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(src.c_str(), &st) == -1) {
|
if (lstat(src.c_str(), &st) == -1) {
|
||||||
throw SysError(format("getting attributes of path '%1%'") % src);
|
throw SysError("getting attributes of path '%1%'", src);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool changePerm = (geteuid() && S_ISDIR(st.st_mode) && !(st.st_mode & S_IWUSR));
|
bool changePerm = (geteuid() && S_ISDIR(st.st_mode) && !(st.st_mode & S_IWUSR));
|
||||||
|
@ -3567,7 +3588,7 @@ static void moveCheckToStore(const Path & src, const Path & dst)
|
||||||
chmod_(src, st.st_mode | S_IWUSR);
|
chmod_(src, st.st_mode | S_IWUSR);
|
||||||
|
|
||||||
if (rename(src.c_str(), dst.c_str()))
|
if (rename(src.c_str(), dst.c_str()))
|
||||||
throw SysError(format("renaming '%1%' to '%2%'") % src % dst);
|
throw SysError("renaming '%1%' to '%2%'", src, dst);
|
||||||
|
|
||||||
if (changePerm)
|
if (changePerm)
|
||||||
chmod_(dst, st.st_mode);
|
chmod_(dst, st.st_mode);
|
||||||
|
@ -3633,7 +3654,7 @@ void DerivationGoal::registerOutputs()
|
||||||
replaceValidPath(path, actualPath);
|
replaceValidPath(path, actualPath);
|
||||||
else
|
else
|
||||||
if (buildMode != bmCheck && rename(actualPath.c_str(), worker.store.toRealPath(path).c_str()) == -1)
|
if (buildMode != bmCheck && rename(actualPath.c_str(), worker.store.toRealPath(path).c_str()) == -1)
|
||||||
throw SysError(format("moving build output '%1%' from the sandbox to the Nix store") % path);
|
throw SysError("moving build output '%1%' from the sandbox to the Nix store", path);
|
||||||
}
|
}
|
||||||
if (buildMode != bmCheck) actualPath = worker.store.toRealPath(path);
|
if (buildMode != bmCheck) actualPath = worker.store.toRealPath(path);
|
||||||
}
|
}
|
||||||
|
@ -3654,13 +3675,16 @@ void DerivationGoal::registerOutputs()
|
||||||
user. */
|
user. */
|
||||||
if ((!S_ISLNK(st.st_mode) && (st.st_mode & (S_IWGRP | S_IWOTH))) ||
|
if ((!S_ISLNK(st.st_mode) && (st.st_mode & (S_IWGRP | S_IWOTH))) ||
|
||||||
(buildUser && st.st_uid != buildUser->getUID()))
|
(buildUser && st.st_uid != buildUser->getUID()))
|
||||||
throw BuildError(format("suspicious ownership or permission on '%1%'; rejecting this build output") % path);
|
throw BuildError("suspicious ownership or permission on '%1%'; rejecting this build output", path);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Apply hash rewriting if necessary. */
|
/* Apply hash rewriting if necessary. */
|
||||||
bool rewritten = false;
|
bool rewritten = false;
|
||||||
if (!outputRewrites.empty()) {
|
if (!outputRewrites.empty()) {
|
||||||
printError(format("warning: rewriting hashes in '%1%'; cross fingers") % path);
|
logWarning({
|
||||||
|
.name = "Rewriting hashes",
|
||||||
|
.hint = hintfmt("rewriting hashes in '%1%'; cross fingers", path)
|
||||||
|
});
|
||||||
|
|
||||||
/* Canonicalise first. This ensures that the path we're
|
/* Canonicalise first. This ensures that the path we're
|
||||||
rewriting doesn't contain a hard link to /etc/shadow or
|
rewriting doesn't contain a hard link to /etc/shadow or
|
||||||
|
@ -3692,8 +3716,9 @@ void DerivationGoal::registerOutputs()
|
||||||
/* The output path should be a regular file without execute permission. */
|
/* The output path should be a regular file without execute permission. */
|
||||||
if (!S_ISREG(st.st_mode) || (st.st_mode & S_IXUSR) != 0)
|
if (!S_ISREG(st.st_mode) || (st.st_mode & S_IXUSR) != 0)
|
||||||
throw BuildError(
|
throw BuildError(
|
||||||
format("output path '%1%' should be a non-executable regular file "
|
"output path '%1%' should be a non-executable regular file "
|
||||||
"since recursive hashing is not enabled (outputHashMode=flat)") % path);
|
"since recursive hashing is not enabled (outputHashMode=flat)",
|
||||||
|
path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check the hash. In hash mode, move the path produced by
|
/* Check the hash. In hash mode, move the path produced by
|
||||||
|
@ -3823,10 +3848,10 @@ void DerivationGoal::registerOutputs()
|
||||||
result.isNonDeterministic = true;
|
result.isNonDeterministic = true;
|
||||||
Path prev = worker.store.printStorePath(i->second.path) + checkSuffix;
|
Path prev = worker.store.printStorePath(i->second.path) + checkSuffix;
|
||||||
bool prevExists = keepPreviousRound && pathExists(prev);
|
bool prevExists = keepPreviousRound && pathExists(prev);
|
||||||
auto msg = prevExists
|
hintformat hint = prevExists
|
||||||
? fmt("output '%s' of '%s' differs from '%s' from previous round",
|
? hintfmt("output '%s' of '%s' differs from '%s' from previous round",
|
||||||
worker.store.printStorePath(i->second.path), worker.store.printStorePath(drvPath), prev)
|
worker.store.printStorePath(i->second.path), worker.store.printStorePath(drvPath), prev)
|
||||||
: fmt("output '%s' of '%s' differs from previous round",
|
: hintfmt("output '%s' of '%s' differs from previous round",
|
||||||
worker.store.printStorePath(i->second.path), worker.store.printStorePath(drvPath));
|
worker.store.printStorePath(i->second.path), worker.store.printStorePath(drvPath));
|
||||||
|
|
||||||
handleDiffHook(
|
handleDiffHook(
|
||||||
|
@ -3836,9 +3861,14 @@ void DerivationGoal::registerOutputs()
|
||||||
worker.store.printStorePath(drvPath), tmpDir);
|
worker.store.printStorePath(drvPath), tmpDir);
|
||||||
|
|
||||||
if (settings.enforceDeterminism)
|
if (settings.enforceDeterminism)
|
||||||
throw NotDeterministic(msg);
|
throw NotDeterministic(hint);
|
||||||
|
|
||||||
|
logError({
|
||||||
|
.name = "Output determinism error",
|
||||||
|
.hint = hint
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
printError(msg);
|
|
||||||
curRound = nrRounds; // we know enough, bail out early
|
curRound = nrRounds; // we know enough, bail out early
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4056,7 +4086,7 @@ Path DerivationGoal::openLogFile()
|
||||||
settings.compressLog ? ".bz2" : "");
|
settings.compressLog ? ".bz2" : "");
|
||||||
|
|
||||||
fdLogFile = open(logFileName.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0666);
|
fdLogFile = open(logFileName.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0666);
|
||||||
if (!fdLogFile) throw SysError(format("creating log file '%1%'") % logFileName);
|
if (!fdLogFile) throw SysError("creating log file '%1%'", logFileName);
|
||||||
|
|
||||||
logFileSink = std::make_shared<FdSink>(fdLogFile.get());
|
logFileSink = std::make_shared<FdSink>(fdLogFile.get());
|
||||||
|
|
||||||
|
@ -4102,9 +4132,12 @@ void DerivationGoal::handleChildOutput(int fd, const string & data)
|
||||||
{
|
{
|
||||||
logSize += data.size();
|
logSize += data.size();
|
||||||
if (settings.maxLogSize && logSize > settings.maxLogSize) {
|
if (settings.maxLogSize && logSize > settings.maxLogSize) {
|
||||||
printError(
|
logError({
|
||||||
format("%1% killed after writing more than %2% bytes of log output")
|
.name = "Max log size exceeded",
|
||||||
% getName() % settings.maxLogSize);
|
.hint = hintfmt(
|
||||||
|
"%1% killed after writing more than %2% bytes of log output",
|
||||||
|
getName(), settings.maxLogSize)
|
||||||
|
});
|
||||||
killChild();
|
killChild();
|
||||||
done(BuildResult::LogLimitExceeded);
|
done(BuildResult::LogLimitExceeded);
|
||||||
return;
|
return;
|
||||||
|
@ -4389,7 +4422,7 @@ void SubstitutionGoal::tryNext()
|
||||||
throw;
|
throw;
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
if (settings.tryFallback) {
|
if (settings.tryFallback) {
|
||||||
printError(e.what());
|
logError(e.info());
|
||||||
tryNext();
|
tryNext();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -4415,8 +4448,11 @@ void SubstitutionGoal::tryNext()
|
||||||
&& !sub->isTrusted
|
&& !sub->isTrusted
|
||||||
&& !info->checkSignatures(worker.store, worker.store.getPublicKeys()))
|
&& !info->checkSignatures(worker.store, worker.store.getPublicKeys()))
|
||||||
{
|
{
|
||||||
printError("warning: substituter '%s' does not have a valid signature for path '%s'",
|
logWarning({
|
||||||
sub->getUri(), worker.store.printStorePath(storePath));
|
.name = "Invalid path signature",
|
||||||
|
.hint = hintfmt("substituter '%s' does not have a valid signature for path '%s'",
|
||||||
|
sub->getUri(), worker.store.printStorePath(storePath))
|
||||||
|
});
|
||||||
tryNext();
|
tryNext();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -4559,7 +4595,6 @@ void SubstitutionGoal::handleEOF(int fd)
|
||||||
if (fd == outPipe.readSide.get()) worker.wakeUp(shared_from_this());
|
if (fd == outPipe.readSide.get()) worker.wakeUp(shared_from_this());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
@ -4776,8 +4811,8 @@ void Worker::run(const Goals & _topGoals)
|
||||||
if (!children.empty() || !waitingForAWhile.empty())
|
if (!children.empty() || !waitingForAWhile.empty())
|
||||||
waitForInput();
|
waitForInput();
|
||||||
else {
|
else {
|
||||||
if (awake.empty() && 0 == settings.maxBuildJobs) throw Error(
|
if (awake.empty() && 0 == settings.maxBuildJobs)
|
||||||
"unable to start any build; either increase '--max-jobs' "
|
throw Error("unable to start any build; either increase '--max-jobs' "
|
||||||
"or enable remote builds");
|
"or enable remote builds");
|
||||||
assert(!awake.empty());
|
assert(!awake.empty());
|
||||||
}
|
}
|
||||||
|
@ -4791,7 +4826,6 @@ void Worker::run(const Goals & _topGoals)
|
||||||
assert(!settings.keepGoing || children.empty());
|
assert(!settings.keepGoing || children.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Worker::waitForInput()
|
void Worker::waitForInput()
|
||||||
{
|
{
|
||||||
printMsg(lvlVomit, "waiting for children");
|
printMsg(lvlVomit, "waiting for children");
|
||||||
|
@ -4830,7 +4864,7 @@ void Worker::waitForInput()
|
||||||
if (!waitingForAWhile.empty()) {
|
if (!waitingForAWhile.empty()) {
|
||||||
useTimeout = true;
|
useTimeout = true;
|
||||||
if (lastWokenUp == steady_time_point::min())
|
if (lastWokenUp == steady_time_point::min())
|
||||||
printError("waiting for locks, build slots or build users...");
|
printInfo("waiting for locks, build slots or build users...");
|
||||||
if (lastWokenUp == steady_time_point::min() || lastWokenUp > before) lastWokenUp = before;
|
if (lastWokenUp == steady_time_point::min() || lastWokenUp > before) lastWokenUp = before;
|
||||||
timeout = std::max(1L,
|
timeout = std::max(1L,
|
||||||
(long) std::chrono::duration_cast<std::chrono::seconds>(
|
(long) std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
@ -4879,15 +4913,15 @@ void Worker::waitForInput()
|
||||||
// FIXME: is there a cleaner way to handle pt close
|
// FIXME: is there a cleaner way to handle pt close
|
||||||
// than EIO? Is this even standard?
|
// than EIO? Is this even standard?
|
||||||
if (rd == 0 || (rd == -1 && errno == EIO)) {
|
if (rd == 0 || (rd == -1 && errno == EIO)) {
|
||||||
debug(format("%1%: got EOF") % goal->getName());
|
debug("%1%: got EOF", goal->getName());
|
||||||
goal->handleEOF(k);
|
goal->handleEOF(k);
|
||||||
j->fds.erase(k);
|
j->fds.erase(k);
|
||||||
} else if (rd == -1) {
|
} else if (rd == -1) {
|
||||||
if (errno != EINTR)
|
if (errno != EINTR)
|
||||||
throw SysError("%s: read failed", goal->getName());
|
throw SysError("%s: read failed", goal->getName());
|
||||||
} else {
|
} else {
|
||||||
printMsg(lvlVomit, format("%1%: read %2% bytes")
|
printMsg(lvlVomit, "%1%: read %2% bytes",
|
||||||
% goal->getName() % rd);
|
goal->getName(), rd);
|
||||||
string data((char *) buffer.data(), rd);
|
string data((char *) buffer.data(), rd);
|
||||||
j->lastOutput = after;
|
j->lastOutput = after;
|
||||||
goal->handleChildOutput(k, data);
|
goal->handleChildOutput(k, data);
|
||||||
|
@ -4900,9 +4934,12 @@ void Worker::waitForInput()
|
||||||
j->respectTimeouts &&
|
j->respectTimeouts &&
|
||||||
after - j->lastOutput >= std::chrono::seconds(settings.maxSilentTime))
|
after - j->lastOutput >= std::chrono::seconds(settings.maxSilentTime))
|
||||||
{
|
{
|
||||||
printError(
|
logError({
|
||||||
format("%1% timed out after %2% seconds of silence")
|
.name = "Silent build timeout",
|
||||||
% goal->getName() % settings.maxSilentTime);
|
.hint = hintfmt(
|
||||||
|
"%1% timed out after %2% seconds of silence",
|
||||||
|
goal->getName(), settings.maxSilentTime)
|
||||||
|
});
|
||||||
goal->timedOut();
|
goal->timedOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4911,9 +4948,12 @@ void Worker::waitForInput()
|
||||||
j->respectTimeouts &&
|
j->respectTimeouts &&
|
||||||
after - j->timeStarted >= std::chrono::seconds(settings.buildTimeout))
|
after - j->timeStarted >= std::chrono::seconds(settings.buildTimeout))
|
||||||
{
|
{
|
||||||
printError(
|
logError({
|
||||||
format("%1% timed out after %2% seconds")
|
.name = "Build timeout",
|
||||||
% goal->getName() % settings.buildTimeout);
|
.hint = hintfmt(
|
||||||
|
"%1% timed out after %2% seconds",
|
||||||
|
goal->getName(), settings.buildTimeout)
|
||||||
|
});
|
||||||
goal->timedOut();
|
goal->timedOut();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4972,7 +5012,11 @@ bool Worker::pathContentsGood(const StorePath & path)
|
||||||
res = info->narHash == nullHash || info->narHash == current.first;
|
res = info->narHash == nullHash || info->narHash == current.first;
|
||||||
}
|
}
|
||||||
pathContentsGoodCache.insert_or_assign(path.clone(), res);
|
pathContentsGoodCache.insert_or_assign(path.clone(), res);
|
||||||
if (!res) printError("path '%s' is corrupted or missing!", store.printStorePath(path));
|
if (!res)
|
||||||
|
logError({
|
||||||
|
.name = "Corrupted path",
|
||||||
|
.hint = hintfmt("path '%s' is corrupted or missing!", store.printStorePath(path))
|
||||||
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5028,7 +5072,6 @@ void LocalStore::buildPaths(const std::vector<StorePathWithOutputs> & drvPaths,
|
||||||
throw Error(worker.exitStatus(), "build of %s failed", showPaths(failed));
|
throw Error(worker.exitStatus(), "build of %s failed", showPaths(failed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
BuildResult LocalStore::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
BuildResult LocalStore::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||||
BuildMode buildMode)
|
BuildMode buildMode)
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,7 +22,11 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
|
||||||
srcFiles = readDirectory(srcDir);
|
srcFiles = readDirectory(srcDir);
|
||||||
} catch (SysError & e) {
|
} catch (SysError & e) {
|
||||||
if (e.errNo == ENOTDIR) {
|
if (e.errNo == ENOTDIR) {
|
||||||
printError("warning: not including '%s' in the user environment because it's not a directory", srcDir);
|
logWarning(
|
||||||
|
ErrorInfo {
|
||||||
|
.name = "Create links - directory",
|
||||||
|
.hint = hintfmt("not including '%s' in the user environment because it's not a directory", srcDir)
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw;
|
throw;
|
||||||
|
@ -41,7 +45,11 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
|
||||||
throw SysError("getting status of '%1%'", srcFile);
|
throw SysError("getting status of '%1%'", srcFile);
|
||||||
} catch (SysError & e) {
|
} catch (SysError & e) {
|
||||||
if (e.errNo == ENOENT || e.errNo == ENOTDIR) {
|
if (e.errNo == ENOENT || e.errNo == ENOTDIR) {
|
||||||
printError("warning: skipping dangling symlink '%s'", dstFile);
|
logWarning(
|
||||||
|
ErrorInfo {
|
||||||
|
.name = "Create links - skipping symlink",
|
||||||
|
.hint = hintfmt("skipping dangling symlink '%s'", dstFile)
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw;
|
throw;
|
||||||
|
@ -72,15 +80,15 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
|
||||||
if (!S_ISDIR(lstat(target).st_mode))
|
if (!S_ISDIR(lstat(target).st_mode))
|
||||||
throw Error("collision between '%1%' and non-directory '%2%'", srcFile, target);
|
throw Error("collision between '%1%' and non-directory '%2%'", srcFile, target);
|
||||||
if (unlink(dstFile.c_str()) == -1)
|
if (unlink(dstFile.c_str()) == -1)
|
||||||
throw SysError(format("unlinking '%1%'") % dstFile);
|
throw SysError("unlinking '%1%'", dstFile);
|
||||||
if (mkdir(dstFile.c_str(), 0755) == -1)
|
if (mkdir(dstFile.c_str(), 0755) == -1)
|
||||||
throw SysError(format("creating directory '%1%'"));
|
throw SysError("creating directory '%1%'", dstFile);
|
||||||
createLinks(state, target, dstFile, state.priorities[dstFile]);
|
createLinks(state, target, dstFile, state.priorities[dstFile]);
|
||||||
createLinks(state, srcFile, dstFile, priority);
|
createLinks(state, srcFile, dstFile, priority);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else if (errno != ENOENT)
|
} else if (errno != ENOENT)
|
||||||
throw SysError(format("getting status of '%1%'") % dstFile);
|
throw SysError("getting status of '%1%'", dstFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
|
@ -99,11 +107,11 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
|
||||||
if (prevPriority < priority)
|
if (prevPriority < priority)
|
||||||
continue;
|
continue;
|
||||||
if (unlink(dstFile.c_str()) == -1)
|
if (unlink(dstFile.c_str()) == -1)
|
||||||
throw SysError(format("unlinking '%1%'") % dstFile);
|
throw SysError("unlinking '%1%'", dstFile);
|
||||||
} else if (S_ISDIR(dstSt.st_mode))
|
} else if (S_ISDIR(dstSt.st_mode))
|
||||||
throw Error("collision between non-directory '%1%' and directory '%2%'", srcFile, dstFile);
|
throw Error("collision between non-directory '%1%' and directory '%2%'", srcFile, dstFile);
|
||||||
} else if (errno != ENOENT)
|
} else if (errno != ENOENT)
|
||||||
throw SysError(format("getting status of '%1%'") % dstFile);
|
throw SysError("getting status of '%1%'", dstFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
createSymlink(srcFile, dstFile);
|
createSymlink(srcFile, dstFile);
|
||||||
|
|
|
@ -18,7 +18,7 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData)
|
||||||
|
|
||||||
auto getAttr = [&](const string & name) {
|
auto getAttr = [&](const string & name) {
|
||||||
auto i = drv.env.find(name);
|
auto i = drv.env.find(name);
|
||||||
if (i == drv.env.end()) throw Error(format("attribute '%s' missing") % name);
|
if (i == drv.env.end()) throw Error("attribute '%s' missing", name);
|
||||||
return i->second;
|
return i->second;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData)
|
||||||
auto executable = drv.env.find("executable");
|
auto executable = drv.env.find("executable");
|
||||||
if (executable != drv.env.end() && executable->second == "1") {
|
if (executable != drv.env.end() && executable->second == "1") {
|
||||||
if (chmod(storePath.c_str(), 0755) == -1)
|
if (chmod(storePath.c_str(), 0755) == -1)
|
||||||
throw SysError(format("making '%1%' executable") % storePath);
|
throw SysError("making '%1%' executable", storePath);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,18 @@ struct TunnelLogger : public Logger
|
||||||
enqueueMsg(*buf.s);
|
enqueueMsg(*buf.s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void logEI(const ErrorInfo & ei) override
|
||||||
|
{
|
||||||
|
if (ei.level > verbosity) return;
|
||||||
|
|
||||||
|
std::stringstream oss;
|
||||||
|
oss << ei;
|
||||||
|
|
||||||
|
StringSink buf;
|
||||||
|
buf << STDERR_NEXT << oss.str() << "\n"; // (fs.s + "\n");
|
||||||
|
enqueueMsg(*buf.s);
|
||||||
|
}
|
||||||
|
|
||||||
/* startWork() means that we're starting an operation for which we
|
/* startWork() means that we're starting an operation for which we
|
||||||
want to send out stderr to the client. */
|
want to send out stderr to the client. */
|
||||||
void startWork()
|
void startWork()
|
||||||
|
@ -744,7 +756,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw Error(format("invalid operation %1%") % op);
|
throw Error("invalid operation %1%", op);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ static void expect(std::istream & str, const string & s)
|
||||||
char s2[s.size()];
|
char s2[s.size()];
|
||||||
str.read(s2, s.size());
|
str.read(s2, s.size());
|
||||||
if (string(s2, s.size()) != s)
|
if (string(s2, s.size()) != s)
|
||||||
throw FormatError(format("expected string '%1%'") % s);
|
throw FormatError("expected string '%1%'", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ static Path parsePath(std::istream & str)
|
||||||
{
|
{
|
||||||
string s = parseString(str);
|
string s = parseString(str);
|
||||||
if (s.size() == 0 || s[0] != '/')
|
if (s.size() == 0 || s[0] != '/')
|
||||||
throw FormatError(format("bad path '%1%' in derivation") % s);
|
throw FormatError("bad path '%1%' in derivation", s);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ Derivation readDerivation(const Store & store, const Path & drvPath)
|
||||||
try {
|
try {
|
||||||
return parseDerivation(store, readFile(drvPath));
|
return parseDerivation(store, readFile(drvPath));
|
||||||
} catch (FormatError & e) {
|
} catch (FormatError & e) {
|
||||||
throw Error(format("error parsing derivation '%1%': %2%") % drvPath % e.msg());
|
throw Error("error parsing derivation '%1%': %2%", drvPath, e.msg());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ struct curlFileTransfer : public FileTransfer
|
||||||
if (requestHeaders) curl_slist_free_all(requestHeaders);
|
if (requestHeaders) curl_slist_free_all(requestHeaders);
|
||||||
try {
|
try {
|
||||||
if (!done)
|
if (!done)
|
||||||
fail(FileTransferError(Interrupted, format("download of '%s' was interrupted") % request.uri));
|
fail(FileTransferError(Interrupted, "download of '%s' was interrupted", request.uri));
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ignoreException();
|
ignoreException();
|
||||||
}
|
}
|
||||||
|
@ -517,7 +517,7 @@ struct curlFileTransfer : public FileTransfer
|
||||||
int running;
|
int running;
|
||||||
CURLMcode mc = curl_multi_perform(curlm, &running);
|
CURLMcode mc = curl_multi_perform(curlm, &running);
|
||||||
if (mc != CURLM_OK)
|
if (mc != CURLM_OK)
|
||||||
throw nix::Error(format("unexpected error from curl_multi_perform(): %s") % curl_multi_strerror(mc));
|
throw nix::Error("unexpected error from curl_multi_perform(): %s", curl_multi_strerror(mc));
|
||||||
|
|
||||||
/* Set the promises of any finished requests. */
|
/* Set the promises of any finished requests. */
|
||||||
CURLMsg * msg;
|
CURLMsg * msg;
|
||||||
|
@ -547,7 +547,7 @@ struct curlFileTransfer : public FileTransfer
|
||||||
vomit("download thread waiting for %d ms", sleepTimeMs);
|
vomit("download thread waiting for %d ms", sleepTimeMs);
|
||||||
mc = curl_multi_wait(curlm, extraFDs, 1, sleepTimeMs, &numfds);
|
mc = curl_multi_wait(curlm, extraFDs, 1, sleepTimeMs, &numfds);
|
||||||
if (mc != CURLM_OK)
|
if (mc != CURLM_OK)
|
||||||
throw nix::Error(format("unexpected error from curl_multi_wait(): %s") % curl_multi_strerror(mc));
|
throw nix::Error("unexpected error from curl_multi_wait(): %s", curl_multi_strerror(mc));
|
||||||
|
|
||||||
nextWakeup = std::chrono::steady_clock::time_point();
|
nextWakeup = std::chrono::steady_clock::time_point();
|
||||||
|
|
||||||
|
@ -599,7 +599,11 @@ struct curlFileTransfer : public FileTransfer
|
||||||
workerThreadMain();
|
workerThreadMain();
|
||||||
} catch (nix::Interrupted & e) {
|
} catch (nix::Interrupted & e) {
|
||||||
} catch (std::exception & e) {
|
} catch (std::exception & e) {
|
||||||
printError("unexpected error in download thread: %s", e.what());
|
logError({
|
||||||
|
.name = "File transfer",
|
||||||
|
.hint = hintfmt("unexpected error in download thread: %s",
|
||||||
|
e.what())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -103,8 +103,9 @@ class FileTransferError : public Error
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FileTransfer::Error error;
|
FileTransfer::Error error;
|
||||||
FileTransferError(FileTransfer::Error error, const FormatOrString & fs)
|
template<typename... Args>
|
||||||
: Error(fs), error(error)
|
FileTransferError(FileTransfer::Error error, const Args & ... args)
|
||||||
|
: Error(args...), error(error)
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -38,10 +38,10 @@ AutoCloseFD LocalStore::openGCLock(LockType lockType)
|
||||||
|
|
||||||
AutoCloseFD fdGCLock = open(fnGCLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);
|
AutoCloseFD fdGCLock = open(fnGCLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);
|
||||||
if (!fdGCLock)
|
if (!fdGCLock)
|
||||||
throw SysError(format("opening global GC lock '%1%'") % fnGCLock);
|
throw SysError("opening global GC lock '%1%'", fnGCLock);
|
||||||
|
|
||||||
if (!lockFile(fdGCLock.get(), lockType, false)) {
|
if (!lockFile(fdGCLock.get(), lockType, false)) {
|
||||||
printError(format("waiting for the big garbage collector lock..."));
|
printInfo("waiting for the big garbage collector lock...");
|
||||||
lockFile(fdGCLock.get(), lockType, true);
|
lockFile(fdGCLock.get(), lockType, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,8 +65,8 @@ static void makeSymlink(const Path & link, const Path & target)
|
||||||
|
|
||||||
/* Atomically replace the old one. */
|
/* Atomically replace the old one. */
|
||||||
if (rename(tempLink.c_str(), link.c_str()) == -1)
|
if (rename(tempLink.c_str(), link.c_str()) == -1)
|
||||||
throw SysError(format("cannot rename '%1%' to '%2%'")
|
throw SysError("cannot rename '%1%' to '%2%'",
|
||||||
% tempLink % link);
|
tempLink , link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -91,15 +91,15 @@ Path LocalFSStore::addPermRoot(const StorePath & storePath,
|
||||||
Path gcRoot(canonPath(_gcRoot));
|
Path gcRoot(canonPath(_gcRoot));
|
||||||
|
|
||||||
if (isInStore(gcRoot))
|
if (isInStore(gcRoot))
|
||||||
throw Error(format(
|
throw Error(
|
||||||
"creating a garbage collector root (%1%) in the Nix store is forbidden "
|
"creating a garbage collector root (%1%) in the Nix store is forbidden "
|
||||||
"(are you running nix-build inside the store?)") % gcRoot);
|
"(are you running nix-build inside the store?)", gcRoot);
|
||||||
|
|
||||||
if (indirect) {
|
if (indirect) {
|
||||||
/* Don't clobber the link if it already exists and doesn't
|
/* Don't clobber the link if it already exists and doesn't
|
||||||
point to the Nix store. */
|
point to the Nix store. */
|
||||||
if (pathExists(gcRoot) && (!isLink(gcRoot) || !isInStore(readLink(gcRoot))))
|
if (pathExists(gcRoot) && (!isLink(gcRoot) || !isInStore(readLink(gcRoot))))
|
||||||
throw Error(format("cannot create symlink '%1%'; already exists") % gcRoot);
|
throw Error("cannot create symlink '%1%'; already exists", gcRoot);
|
||||||
makeSymlink(gcRoot, printStorePath(storePath));
|
makeSymlink(gcRoot, printStorePath(storePath));
|
||||||
addIndirectRoot(gcRoot);
|
addIndirectRoot(gcRoot);
|
||||||
}
|
}
|
||||||
|
@ -109,10 +109,10 @@ Path LocalFSStore::addPermRoot(const StorePath & storePath,
|
||||||
Path rootsDir = canonPath((format("%1%/%2%") % stateDir % gcRootsDir).str());
|
Path rootsDir = canonPath((format("%1%/%2%") % stateDir % gcRootsDir).str());
|
||||||
|
|
||||||
if (string(gcRoot, 0, rootsDir.size() + 1) != rootsDir + "/")
|
if (string(gcRoot, 0, rootsDir.size() + 1) != rootsDir + "/")
|
||||||
throw Error(format(
|
throw Error(
|
||||||
"path '%1%' is not a valid garbage collector root; "
|
"path '%1%' is not a valid garbage collector root; "
|
||||||
"it's not in the directory '%2%'")
|
"it's not in the directory '%2%'",
|
||||||
% gcRoot % rootsDir);
|
gcRoot, rootsDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseNameOf(gcRoot) == std::string(storePath.to_string()))
|
if (baseNameOf(gcRoot) == std::string(storePath.to_string()))
|
||||||
|
@ -129,10 +129,13 @@ Path LocalFSStore::addPermRoot(const StorePath & storePath,
|
||||||
if (settings.checkRootReachability) {
|
if (settings.checkRootReachability) {
|
||||||
auto roots = findRoots(false);
|
auto roots = findRoots(false);
|
||||||
if (roots[storePath.clone()].count(gcRoot) == 0)
|
if (roots[storePath.clone()].count(gcRoot) == 0)
|
||||||
printError(
|
logWarning(
|
||||||
"warning: '%1%' is not in a directory where the garbage collector looks for roots; "
|
ErrorInfo {
|
||||||
|
.name = "GC root",
|
||||||
|
.hint = hintfmt("warning: '%1%' is not in a directory where the garbage collector looks for roots; "
|
||||||
"therefore, '%2%' might be removed by the garbage collector",
|
"therefore, '%2%' might be removed by the garbage collector",
|
||||||
gcRoot, printStorePath(storePath));
|
gcRoot, printStorePath(storePath))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Grab the global GC root, causing us to block while a GC is in
|
/* Grab the global GC root, causing us to block while a GC is in
|
||||||
|
@ -170,7 +173,7 @@ void LocalStore::addTempRoot(const StorePath & path)
|
||||||
way. */
|
way. */
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (fstat(state->fdTempRoots.get(), &st) == -1)
|
if (fstat(state->fdTempRoots.get(), &st) == -1)
|
||||||
throw SysError(format("statting '%1%'") % fnTempRoots);
|
throw SysError("statting '%1%'", fnTempRoots);
|
||||||
if (st.st_size == 0) break;
|
if (st.st_size == 0) break;
|
||||||
|
|
||||||
/* The garbage collector deleted this file before we could
|
/* The garbage collector deleted this file before we could
|
||||||
|
@ -216,7 +219,7 @@ void LocalStore::findTempRoots(FDs & fds, Roots & tempRoots, bool censor)
|
||||||
if (!*fd) {
|
if (!*fd) {
|
||||||
/* It's okay if the file has disappeared. */
|
/* It's okay if the file has disappeared. */
|
||||||
if (errno == ENOENT) continue;
|
if (errno == ENOENT) continue;
|
||||||
throw SysError(format("opening temporary roots file '%1%'") % path);
|
throw SysError("opening temporary roots file '%1%'", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This should work, but doesn't, for some reason. */
|
/* This should work, but doesn't, for some reason. */
|
||||||
|
@ -227,7 +230,7 @@ void LocalStore::findTempRoots(FDs & fds, Roots & tempRoots, bool censor)
|
||||||
only succeed if the owning process has died. In that case
|
only succeed if the owning process has died. In that case
|
||||||
we don't care about its temporary roots. */
|
we don't care about its temporary roots. */
|
||||||
if (lockFile(fd->get(), ltWrite, false)) {
|
if (lockFile(fd->get(), ltWrite, false)) {
|
||||||
printError(format("removing stale temporary roots file '%1%'") % path);
|
printInfo("removing stale temporary roots file '%1%'", path);
|
||||||
unlink(path.c_str());
|
unlink(path.c_str());
|
||||||
writeFull(fd->get(), "d");
|
writeFull(fd->get(), "d");
|
||||||
continue;
|
continue;
|
||||||
|
@ -403,7 +406,7 @@ void LocalStore::findRuntimeRoots(Roots & roots, bool censor)
|
||||||
if (!fdDir) {
|
if (!fdDir) {
|
||||||
if (errno == ENOENT || errno == EACCES)
|
if (errno == ENOENT || errno == EACCES)
|
||||||
continue;
|
continue;
|
||||||
throw SysError(format("opening %1%") % fdStr);
|
throw SysError("opening %1%", fdStr);
|
||||||
}
|
}
|
||||||
struct dirent * fd_ent;
|
struct dirent * fd_ent;
|
||||||
while (errno = 0, fd_ent = readdir(fdDir.get())) {
|
while (errno = 0, fd_ent = readdir(fdDir.get())) {
|
||||||
|
@ -413,7 +416,7 @@ void LocalStore::findRuntimeRoots(Roots & roots, bool censor)
|
||||||
if (errno) {
|
if (errno) {
|
||||||
if (errno == ESRCH)
|
if (errno == ESRCH)
|
||||||
continue;
|
continue;
|
||||||
throw SysError(format("iterating /proc/%1%/fd") % ent->d_name);
|
throw SysError("iterating /proc/%1%/fd", ent->d_name);
|
||||||
}
|
}
|
||||||
fdDir.reset();
|
fdDir.reset();
|
||||||
|
|
||||||
|
@ -541,7 +544,7 @@ void LocalStore::deletePathRecursive(GCState & state, const Path & path)
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(realPath.c_str(), &st)) {
|
if (lstat(realPath.c_str(), &st)) {
|
||||||
if (errno == ENOENT) return;
|
if (errno == ENOENT) return;
|
||||||
throw SysError(format("getting status of %1%") % realPath);
|
throw SysError("getting status of %1%", realPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
printInfo(format("deleting '%1%'") % path);
|
printInfo(format("deleting '%1%'") % path);
|
||||||
|
@ -559,10 +562,10 @@ void LocalStore::deletePathRecursive(GCState & state, const Path & path)
|
||||||
// size.
|
// size.
|
||||||
try {
|
try {
|
||||||
if (chmod(realPath.c_str(), st.st_mode | S_IWUSR) == -1)
|
if (chmod(realPath.c_str(), st.st_mode | S_IWUSR) == -1)
|
||||||
throw SysError(format("making '%1%' writable") % realPath);
|
throw SysError("making '%1%' writable", realPath);
|
||||||
Path tmp = trashDir + "/" + std::string(baseNameOf(path));
|
Path tmp = trashDir + "/" + std::string(baseNameOf(path));
|
||||||
if (rename(realPath.c_str(), tmp.c_str()))
|
if (rename(realPath.c_str(), tmp.c_str()))
|
||||||
throw SysError(format("unable to rename '%1%' to '%2%'") % realPath % tmp);
|
throw SysError("unable to rename '%1%' to '%2%'", realPath, tmp);
|
||||||
state.bytesInvalidated += size;
|
state.bytesInvalidated += size;
|
||||||
} catch (SysError & e) {
|
} catch (SysError & e) {
|
||||||
if (e.errNo == ENOSPC) {
|
if (e.errNo == ENOSPC) {
|
||||||
|
@ -681,7 +684,7 @@ void LocalStore::tryToDelete(GCState & state, const Path & path)
|
||||||
void LocalStore::removeUnusedLinks(const GCState & state)
|
void LocalStore::removeUnusedLinks(const GCState & state)
|
||||||
{
|
{
|
||||||
AutoCloseDir dir(opendir(linksDir.c_str()));
|
AutoCloseDir dir(opendir(linksDir.c_str()));
|
||||||
if (!dir) throw SysError(format("opening directory '%1%'") % linksDir);
|
if (!dir) throw SysError("opening directory '%1%'", linksDir);
|
||||||
|
|
||||||
long long actualSize = 0, unsharedSize = 0;
|
long long actualSize = 0, unsharedSize = 0;
|
||||||
|
|
||||||
|
@ -694,7 +697,7 @@ void LocalStore::removeUnusedLinks(const GCState & state)
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(path.c_str(), &st) == -1)
|
if (lstat(path.c_str(), &st) == -1)
|
||||||
throw SysError(format("statting '%1%'") % path);
|
throw SysError("statting '%1%'", path);
|
||||||
|
|
||||||
if (st.st_nlink != 1) {
|
if (st.st_nlink != 1) {
|
||||||
actualSize += st.st_size;
|
actualSize += st.st_size;
|
||||||
|
@ -705,14 +708,14 @@ void LocalStore::removeUnusedLinks(const GCState & state)
|
||||||
printMsg(lvlTalkative, format("deleting unused link '%1%'") % path);
|
printMsg(lvlTalkative, format("deleting unused link '%1%'") % path);
|
||||||
|
|
||||||
if (unlink(path.c_str()) == -1)
|
if (unlink(path.c_str()) == -1)
|
||||||
throw SysError(format("deleting '%1%'") % path);
|
throw SysError("deleting '%1%'", path);
|
||||||
|
|
||||||
state.results.bytesFreed += st.st_size;
|
state.results.bytesFreed += st.st_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (stat(linksDir.c_str(), &st) == -1)
|
if (stat(linksDir.c_str(), &st) == -1)
|
||||||
throw SysError(format("statting '%1%'") % linksDir);
|
throw SysError("statting '%1%'", linksDir);
|
||||||
long long overhead = st.st_blocks * 512ULL;
|
long long overhead = st.st_blocks * 512ULL;
|
||||||
|
|
||||||
printInfo(format("note: currently hard linking saves %.2f MiB")
|
printInfo(format("note: currently hard linking saves %.2f MiB")
|
||||||
|
@ -747,7 +750,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
|
|
||||||
/* Find the roots. Since we've grabbed the GC lock, the set of
|
/* Find the roots. Since we've grabbed the GC lock, the set of
|
||||||
permanent roots cannot increase now. */
|
permanent roots cannot increase now. */
|
||||||
printError("finding garbage collector roots...");
|
printInfo("finding garbage collector roots...");
|
||||||
Roots rootMap;
|
Roots rootMap;
|
||||||
if (!options.ignoreLiveness)
|
if (!options.ignoreLiveness)
|
||||||
findRootsNoTemp(rootMap, true);
|
findRootsNoTemp(rootMap, true);
|
||||||
|
@ -799,14 +802,14 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
} else if (options.maxFreed > 0) {
|
} else if (options.maxFreed > 0) {
|
||||||
|
|
||||||
if (state.shouldDelete)
|
if (state.shouldDelete)
|
||||||
printError("deleting garbage...");
|
printInfo("deleting garbage...");
|
||||||
else
|
else
|
||||||
printError("determining live/dead paths...");
|
printInfo("determining live/dead paths...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
AutoCloseDir dir(opendir(realStoreDir.c_str()));
|
AutoCloseDir dir(opendir(realStoreDir.c_str()));
|
||||||
if (!dir) throw SysError(format("opening directory '%1%'") % realStoreDir);
|
if (!dir) throw SysError("opening directory '%1%'", realStoreDir);
|
||||||
|
|
||||||
/* Read the store and immediately delete all paths that
|
/* Read the store and immediately delete all paths that
|
||||||
aren't valid. When using --max-freed etc., deleting
|
aren't valid. When using --max-freed etc., deleting
|
||||||
|
@ -868,7 +871,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
|
|
||||||
/* Clean up the links directory. */
|
/* Clean up the links directory. */
|
||||||
if (options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific) {
|
if (options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific) {
|
||||||
printError("deleting unused links...");
|
printInfo("deleting unused links...");
|
||||||
removeUnusedLinks(state);
|
removeUnusedLinks(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ static void atomicWrite(const Path & path, const std::string & s)
|
||||||
AutoDelete del(tmp, false);
|
AutoDelete del(tmp, false);
|
||||||
writeFile(tmp, s);
|
writeFile(tmp, s);
|
||||||
if (rename(tmp.c_str(), path.c_str()))
|
if (rename(tmp.c_str(), path.c_str()))
|
||||||
throw SysError(format("renaming '%1%' to '%2%'") % tmp % path);
|
throw SysError("renaming '%1%' to '%2%'", tmp, path);
|
||||||
del.cancel();
|
del.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ struct LocalStoreAccessor : public FSAccessor
|
||||||
{
|
{
|
||||||
Path storePath = store->toStorePath(path);
|
Path storePath = store->toStorePath(path);
|
||||||
if (!store->isValidPath(store->parseStorePath(storePath)))
|
if (!store->isValidPath(store->parseStorePath(storePath)))
|
||||||
throw InvalidPath(format("path '%1%' is not a valid store path") % storePath);
|
throw InvalidPath("path '%1%' is not a valid store path", storePath);
|
||||||
return store->getRealStoreDir() + std::string(path, store->storeDir.size());
|
return store->getRealStoreDir() + std::string(path, store->storeDir.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,11 +33,11 @@ struct LocalStoreAccessor : public FSAccessor
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(realPath.c_str(), &st)) {
|
if (lstat(realPath.c_str(), &st)) {
|
||||||
if (errno == ENOENT || errno == ENOTDIR) return {Type::tMissing, 0, false};
|
if (errno == ENOENT || errno == ENOTDIR) return {Type::tMissing, 0, false};
|
||||||
throw SysError(format("getting status of '%1%'") % path);
|
throw SysError("getting status of '%1%'", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode))
|
if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode))
|
||||||
throw Error(format("file '%1%' has unsupported type") % path);
|
throw Error("file '%1%' has unsupported type", path);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
S_ISREG(st.st_mode) ? Type::tRegular :
|
S_ISREG(st.st_mode) ? Type::tRegular :
|
||||||
|
|
|
@ -87,18 +87,22 @@ LocalStore::LocalStore(const Params & params)
|
||||||
|
|
||||||
struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
|
struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
|
||||||
if (!gr)
|
if (!gr)
|
||||||
printError(format("warning: the group '%1%' specified in 'build-users-group' does not exist")
|
logError({
|
||||||
% settings.buildUsersGroup);
|
.name = "'build-users-group' not found",
|
||||||
|
.hint = hintfmt(
|
||||||
|
"warning: the group '%1%' specified in 'build-users-group' does not exist",
|
||||||
|
settings.buildUsersGroup)
|
||||||
|
});
|
||||||
else {
|
else {
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (stat(realStoreDir.c_str(), &st))
|
if (stat(realStoreDir.c_str(), &st))
|
||||||
throw SysError(format("getting attributes of path '%1%'") % realStoreDir);
|
throw SysError("getting attributes of path '%1%'", realStoreDir);
|
||||||
|
|
||||||
if (st.st_uid != 0 || st.st_gid != gr->gr_gid || (st.st_mode & ~S_IFMT) != perm) {
|
if (st.st_uid != 0 || st.st_gid != gr->gr_gid || (st.st_mode & ~S_IFMT) != perm) {
|
||||||
if (chown(realStoreDir.c_str(), 0, gr->gr_gid) == -1)
|
if (chown(realStoreDir.c_str(), 0, gr->gr_gid) == -1)
|
||||||
throw SysError(format("changing ownership of path '%1%'") % realStoreDir);
|
throw SysError("changing ownership of path '%1%'", realStoreDir);
|
||||||
if (chmod(realStoreDir.c_str(), perm) == -1)
|
if (chmod(realStoreDir.c_str(), perm) == -1)
|
||||||
throw SysError(format("changing permissions on path '%1%'") % realStoreDir);
|
throw SysError("changing permissions on path '%1%'", realStoreDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,12 +113,12 @@ LocalStore::LocalStore(const Params & params)
|
||||||
struct stat st;
|
struct stat st;
|
||||||
while (path != "/") {
|
while (path != "/") {
|
||||||
if (lstat(path.c_str(), &st))
|
if (lstat(path.c_str(), &st))
|
||||||
throw SysError(format("getting status of '%1%'") % path);
|
throw SysError("getting status of '%1%'", path);
|
||||||
if (S_ISLNK(st.st_mode))
|
if (S_ISLNK(st.st_mode))
|
||||||
throw Error(format(
|
throw Error(
|
||||||
"the path '%1%' is a symlink; "
|
"the path '%1%' is a symlink; "
|
||||||
"this is not allowed for the Nix store and its parent directories")
|
"this is not allowed for the Nix store and its parent directories",
|
||||||
% path);
|
path);
|
||||||
path = dirOf(path);
|
path = dirOf(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,7 +151,7 @@ LocalStore::LocalStore(const Params & params)
|
||||||
globalLock = openLockFile(globalLockPath.c_str(), true);
|
globalLock = openLockFile(globalLockPath.c_str(), true);
|
||||||
|
|
||||||
if (!lockFile(globalLock.get(), ltRead, false)) {
|
if (!lockFile(globalLock.get(), ltRead, false)) {
|
||||||
printError("waiting for the big Nix store lock...");
|
printInfo("waiting for the big Nix store lock...");
|
||||||
lockFile(globalLock.get(), ltRead, true);
|
lockFile(globalLock.get(), ltRead, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,8 +159,8 @@ LocalStore::LocalStore(const Params & params)
|
||||||
upgrade. */
|
upgrade. */
|
||||||
int curSchema = getSchema();
|
int curSchema = getSchema();
|
||||||
if (curSchema > nixSchemaVersion)
|
if (curSchema > nixSchemaVersion)
|
||||||
throw Error(format("current Nix store schema is version %1%, but I only support %2%")
|
throw Error("current Nix store schema is version %1%, but I only support %2%",
|
||||||
% curSchema % nixSchemaVersion);
|
curSchema, nixSchemaVersion);
|
||||||
|
|
||||||
else if (curSchema == 0) { /* new store */
|
else if (curSchema == 0) { /* new store */
|
||||||
curSchema = nixSchemaVersion;
|
curSchema = nixSchemaVersion;
|
||||||
|
@ -178,7 +182,7 @@ LocalStore::LocalStore(const Params & params)
|
||||||
"please upgrade Nix to version 1.11 first.");
|
"please upgrade Nix to version 1.11 first.");
|
||||||
|
|
||||||
if (!lockFile(globalLock.get(), ltWrite, false)) {
|
if (!lockFile(globalLock.get(), ltWrite, false)) {
|
||||||
printError("waiting for exclusive access to the Nix store...");
|
printInfo("waiting for exclusive access to the Nix store...");
|
||||||
lockFile(globalLock.get(), ltWrite, true);
|
lockFile(globalLock.get(), ltWrite, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +260,7 @@ LocalStore::~LocalStore()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (future.valid()) {
|
if (future.valid()) {
|
||||||
printError("waiting for auto-GC to finish on exit...");
|
printInfo("waiting for auto-GC to finish on exit...");
|
||||||
future.get();
|
future.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,7 +288,7 @@ int LocalStore::getSchema()
|
||||||
if (pathExists(schemaPath)) {
|
if (pathExists(schemaPath)) {
|
||||||
string s = readFile(schemaPath);
|
string s = readFile(schemaPath);
|
||||||
if (!string2Int(s, curSchema))
|
if (!string2Int(s, curSchema))
|
||||||
throw Error(format("'%1%' is corrupt") % schemaPath);
|
throw Error("'%1%' is corrupt", schemaPath);
|
||||||
}
|
}
|
||||||
return curSchema;
|
return curSchema;
|
||||||
}
|
}
|
||||||
|
@ -293,7 +297,7 @@ int LocalStore::getSchema()
|
||||||
void LocalStore::openDB(State & state, bool create)
|
void LocalStore::openDB(State & state, bool create)
|
||||||
{
|
{
|
||||||
if (access(dbDir.c_str(), R_OK | W_OK))
|
if (access(dbDir.c_str(), R_OK | W_OK))
|
||||||
throw SysError(format("Nix database directory '%1%' is not writable") % dbDir);
|
throw SysError("Nix database directory '%1%' is not writable", dbDir);
|
||||||
|
|
||||||
/* Open the Nix database. */
|
/* Open the Nix database. */
|
||||||
string dbPath = dbDir + "/db.sqlite";
|
string dbPath = dbDir + "/db.sqlite";
|
||||||
|
@ -367,7 +371,7 @@ void LocalStore::makeStoreWritable()
|
||||||
throw SysError("setting up a private mount namespace");
|
throw SysError("setting up a private mount namespace");
|
||||||
|
|
||||||
if (mount(0, realStoreDir.c_str(), "none", MS_REMOUNT | MS_BIND, 0) == -1)
|
if (mount(0, realStoreDir.c_str(), "none", MS_REMOUNT | MS_BIND, 0) == -1)
|
||||||
throw SysError(format("remounting %1% writable") % realStoreDir);
|
throw SysError("remounting %1% writable", realStoreDir);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -388,7 +392,7 @@ static void canonicaliseTimestampAndPermissions(const Path & path, const struct
|
||||||
| 0444
|
| 0444
|
||||||
| (st.st_mode & S_IXUSR ? 0111 : 0);
|
| (st.st_mode & S_IXUSR ? 0111 : 0);
|
||||||
if (chmod(path.c_str(), mode) == -1)
|
if (chmod(path.c_str(), mode) == -1)
|
||||||
throw SysError(format("changing mode of '%1%' to %2$o") % path % mode);
|
throw SysError("changing mode of '%1%' to %2$o", path, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -406,7 +410,7 @@ static void canonicaliseTimestampAndPermissions(const Path & path, const struct
|
||||||
#else
|
#else
|
||||||
if (!S_ISLNK(st.st_mode) && utimes(path.c_str(), times) == -1)
|
if (!S_ISLNK(st.st_mode) && utimes(path.c_str(), times) == -1)
|
||||||
#endif
|
#endif
|
||||||
throw SysError(format("changing modification time of '%1%'") % path);
|
throw SysError("changing modification time of '%1%'", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,7 +419,7 @@ void canonicaliseTimestampAndPermissions(const Path & path)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(path.c_str(), &st))
|
if (lstat(path.c_str(), &st))
|
||||||
throw SysError(format("getting attributes of path '%1%'") % path);
|
throw SysError("getting attributes of path '%1%'", path);
|
||||||
canonicaliseTimestampAndPermissions(path, st);
|
canonicaliseTimestampAndPermissions(path, st);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,17 +434,17 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSe
|
||||||
setattrlist() to remove other attributes as well. */
|
setattrlist() to remove other attributes as well. */
|
||||||
if (lchflags(path.c_str(), 0)) {
|
if (lchflags(path.c_str(), 0)) {
|
||||||
if (errno != ENOTSUP)
|
if (errno != ENOTSUP)
|
||||||
throw SysError(format("clearing flags of path '%1%'") % path);
|
throw SysError("clearing flags of path '%1%'", path);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(path.c_str(), &st))
|
if (lstat(path.c_str(), &st))
|
||||||
throw SysError(format("getting attributes of path '%1%'") % path);
|
throw SysError("getting attributes of path '%1%'", path);
|
||||||
|
|
||||||
/* Really make sure that the path is of a supported type. */
|
/* Really make sure that the path is of a supported type. */
|
||||||
if (!(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)))
|
if (!(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)))
|
||||||
throw Error(format("file '%1%' has an unsupported type") % path);
|
throw Error("file '%1%' has an unsupported type", path);
|
||||||
|
|
||||||
#if __linux__
|
#if __linux__
|
||||||
/* Remove extended attributes / ACLs. */
|
/* Remove extended attributes / ACLs. */
|
||||||
|
@ -474,7 +478,7 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSe
|
||||||
if (fromUid != (uid_t) -1 && st.st_uid != fromUid) {
|
if (fromUid != (uid_t) -1 && st.st_uid != fromUid) {
|
||||||
assert(!S_ISDIR(st.st_mode));
|
assert(!S_ISDIR(st.st_mode));
|
||||||
if (inodesSeen.find(Inode(st.st_dev, st.st_ino)) == inodesSeen.end())
|
if (inodesSeen.find(Inode(st.st_dev, st.st_ino)) == inodesSeen.end())
|
||||||
throw BuildError(format("invalid ownership on file '%1%'") % path);
|
throw BuildError("invalid ownership on file '%1%'", path);
|
||||||
mode_t mode = st.st_mode & ~S_IFMT;
|
mode_t mode = st.st_mode & ~S_IFMT;
|
||||||
assert(S_ISLNK(st.st_mode) || (st.st_uid == geteuid() && (mode == 0444 || mode == 0555) && st.st_mtime == mtimeStore));
|
assert(S_ISLNK(st.st_mode) || (st.st_uid == geteuid() && (mode == 0444 || mode == 0555) && st.st_mtime == mtimeStore));
|
||||||
return;
|
return;
|
||||||
|
@ -498,8 +502,8 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSe
|
||||||
if (!S_ISLNK(st.st_mode) &&
|
if (!S_ISLNK(st.st_mode) &&
|
||||||
chown(path.c_str(), geteuid(), getegid()) == -1)
|
chown(path.c_str(), geteuid(), getegid()) == -1)
|
||||||
#endif
|
#endif
|
||||||
throw SysError(format("changing owner of '%1%' to %2%")
|
throw SysError("changing owner of '%1%' to %2%",
|
||||||
% path % geteuid());
|
path, geteuid());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (S_ISDIR(st.st_mode)) {
|
if (S_ISDIR(st.st_mode)) {
|
||||||
|
@ -518,11 +522,11 @@ void canonicalisePathMetaData(const Path & path, uid_t fromUid, InodesSeen & ino
|
||||||
be a symlink, since we can't change its ownership. */
|
be a symlink, since we can't change its ownership. */
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(path.c_str(), &st))
|
if (lstat(path.c_str(), &st))
|
||||||
throw SysError(format("getting attributes of path '%1%'") % path);
|
throw SysError("getting attributes of path '%1%'", path);
|
||||||
|
|
||||||
if (st.st_uid != geteuid()) {
|
if (st.st_uid != geteuid()) {
|
||||||
assert(S_ISLNK(st.st_mode));
|
assert(S_ISLNK(st.st_mode));
|
||||||
throw Error(format("wrong ownership of top-level store path '%1%'") % path);
|
throw Error("wrong ownership of top-level store path '%1%'", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -859,7 +863,7 @@ void LocalStore::querySubstitutablePathInfos(const StorePathSet & paths,
|
||||||
} catch (SubstituterDisabled &) {
|
} catch (SubstituterDisabled &) {
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
if (settings.tryFallback)
|
if (settings.tryFallback)
|
||||||
printError(e.what());
|
logError(e.info());
|
||||||
else
|
else
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
@ -1187,7 +1191,7 @@ void LocalStore::invalidatePathChecked(const StorePath & path)
|
||||||
|
|
||||||
bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
|
bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
|
||||||
{
|
{
|
||||||
printError(format("reading the Nix store..."));
|
printInfo(format("reading the Nix store..."));
|
||||||
|
|
||||||
bool errors = false;
|
bool errors = false;
|
||||||
|
|
||||||
|
@ -1219,12 +1223,15 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
|
||||||
Path linkPath = linksDir + "/" + link.name;
|
Path linkPath = linksDir + "/" + link.name;
|
||||||
string hash = hashPath(htSHA256, linkPath).first.to_string(Base32, false);
|
string hash = hashPath(htSHA256, linkPath).first.to_string(Base32, false);
|
||||||
if (hash != link.name) {
|
if (hash != link.name) {
|
||||||
printError(
|
logError({
|
||||||
|
.name = "Invalid hash",
|
||||||
|
.hint = hintfmt(
|
||||||
"link '%s' was modified! expected hash '%s', got '%s'",
|
"link '%s' was modified! expected hash '%s', got '%s'",
|
||||||
linkPath, link.name, hash);
|
linkPath, link.name, hash)
|
||||||
|
});
|
||||||
if (repair) {
|
if (repair) {
|
||||||
if (unlink(linkPath.c_str()) == 0)
|
if (unlink(linkPath.c_str()) == 0)
|
||||||
printError("removed link '%s'", linkPath);
|
printInfo("removed link '%s'", linkPath);
|
||||||
else
|
else
|
||||||
throw SysError("removing corrupt link '%s'", linkPath);
|
throw SysError("removing corrupt link '%s'", linkPath);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1254,8 +1261,11 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
|
||||||
auto current = hashSink->finish();
|
auto current = hashSink->finish();
|
||||||
|
|
||||||
if (info->narHash != nullHash && info->narHash != current.first) {
|
if (info->narHash != nullHash && info->narHash != current.first) {
|
||||||
printError("path '%s' was modified! expected hash '%s', got '%s'",
|
logError({
|
||||||
printStorePath(i), info->narHash.to_string(Base32, true), current.first.to_string(Base32, true));
|
.name = "Invalid hash - path modified",
|
||||||
|
.hint = hintfmt("path '%s' was modified! expected hash '%s', got '%s'",
|
||||||
|
printStorePath(i), info->narHash.to_string(Base32, true), current.first.to_string(Base32, true))
|
||||||
|
});
|
||||||
if (repair) repairPath(i); else errors = true;
|
if (repair) repairPath(i); else errors = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
@ -1263,14 +1273,14 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
|
||||||
|
|
||||||
/* Fill in missing hashes. */
|
/* Fill in missing hashes. */
|
||||||
if (info->narHash == nullHash) {
|
if (info->narHash == nullHash) {
|
||||||
printError("fixing missing hash on '%s'", printStorePath(i));
|
printInfo("fixing missing hash on '%s'", printStorePath(i));
|
||||||
info->narHash = current.first;
|
info->narHash = current.first;
|
||||||
update = true;
|
update = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fill in missing narSize fields (from old stores). */
|
/* Fill in missing narSize fields (from old stores). */
|
||||||
if (info->narSize == 0) {
|
if (info->narSize == 0) {
|
||||||
printError("updating size field on '%s' to %s", printStorePath(i), current.second);
|
printInfo("updating size field on '%s' to %s", printStorePath(i), current.second);
|
||||||
info->narSize = current.second;
|
info->narSize = current.second;
|
||||||
update = true;
|
update = true;
|
||||||
}
|
}
|
||||||
|
@ -1286,7 +1296,7 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
|
||||||
/* It's possible that the path got GC'ed, so ignore
|
/* It's possible that the path got GC'ed, so ignore
|
||||||
errors on invalid paths. */
|
errors on invalid paths. */
|
||||||
if (isValidPath(i))
|
if (isValidPath(i))
|
||||||
printError("error: %s", e.msg());
|
logError(e.info());
|
||||||
else
|
else
|
||||||
warn(e.msg());
|
warn(e.msg());
|
||||||
errors = true;
|
errors = true;
|
||||||
|
@ -1306,7 +1316,10 @@ void LocalStore::verifyPath(const Path & pathS, const StringSet & store,
|
||||||
if (!done.insert(pathS).second) return;
|
if (!done.insert(pathS).second) return;
|
||||||
|
|
||||||
if (!isStorePath(pathS)) {
|
if (!isStorePath(pathS)) {
|
||||||
printError("path '%s' is not in the Nix store", pathS);
|
logError({
|
||||||
|
.name = "Nix path not found",
|
||||||
|
.hint = hintfmt("path '%s' is not in the Nix store", pathS)
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1325,16 +1338,19 @@ void LocalStore::verifyPath(const Path & pathS, const StringSet & store,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canInvalidate) {
|
if (canInvalidate) {
|
||||||
printError("path '%s' disappeared, removing from database...", pathS);
|
printInfo("path '%s' disappeared, removing from database...", pathS);
|
||||||
auto state(_state.lock());
|
auto state(_state.lock());
|
||||||
invalidatePath(*state, path);
|
invalidatePath(*state, path);
|
||||||
} else {
|
} else {
|
||||||
printError("path '%s' disappeared, but it still has valid referrers!", pathS);
|
logError({
|
||||||
|
.name = "Missing path with referrers",
|
||||||
|
.hint = hintfmt("path '%s' disappeared, but it still has valid referrers!", pathS)
|
||||||
|
});
|
||||||
if (repair)
|
if (repair)
|
||||||
try {
|
try {
|
||||||
repairPath(path);
|
repairPath(path);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
warn(e.msg());
|
logWarning(e.info());
|
||||||
errors = true;
|
errors = true;
|
||||||
}
|
}
|
||||||
else errors = true;
|
else errors = true;
|
||||||
|
@ -1374,7 +1390,7 @@ static void makeMutable(const Path & path)
|
||||||
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
|
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
|
||||||
if (fd == -1) {
|
if (fd == -1) {
|
||||||
if (errno == ELOOP) return; // it's a symlink
|
if (errno == ELOOP) return; // it's a symlink
|
||||||
throw SysError(format("opening file '%1%'") % path);
|
throw SysError("opening file '%1%'", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int flags = 0, old;
|
unsigned int flags = 0, old;
|
||||||
|
@ -1392,7 +1408,7 @@ static void makeMutable(const Path & path)
|
||||||
void LocalStore::upgradeStore7()
|
void LocalStore::upgradeStore7()
|
||||||
{
|
{
|
||||||
if (getuid() != 0) return;
|
if (getuid() != 0) return;
|
||||||
printError("removing immutable bits from the Nix store (this may take a while)...");
|
printInfo("removing immutable bits from the Nix store (this may take a while)...");
|
||||||
makeMutable(realStoreDir);
|
makeMutable(realStoreDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -184,7 +184,7 @@ struct NarAccessor : public FSAccessor
|
||||||
auto i = get(path);
|
auto i = get(path);
|
||||||
|
|
||||||
if (i.type != FSAccessor::Type::tDirectory)
|
if (i.type != FSAccessor::Type::tDirectory)
|
||||||
throw Error(format("path '%1%' inside NAR file is not a directory") % path);
|
throw Error("path '%1%' inside NAR file is not a directory", path);
|
||||||
|
|
||||||
StringSet res;
|
StringSet res;
|
||||||
for (auto & child : i.children)
|
for (auto & child : i.children)
|
||||||
|
@ -197,7 +197,7 @@ struct NarAccessor : public FSAccessor
|
||||||
{
|
{
|
||||||
auto i = get(path);
|
auto i = get(path);
|
||||||
if (i.type != FSAccessor::Type::tRegular)
|
if (i.type != FSAccessor::Type::tRegular)
|
||||||
throw Error(format("path '%1%' inside NAR file is not a regular file") % path);
|
throw Error("path '%1%' inside NAR file is not a regular file", path);
|
||||||
|
|
||||||
if (getNarBytes) return getNarBytes(i.start, i.size);
|
if (getNarBytes) return getNarBytes(i.start, i.size);
|
||||||
|
|
||||||
|
@ -209,7 +209,7 @@ struct NarAccessor : public FSAccessor
|
||||||
{
|
{
|
||||||
auto i = get(path);
|
auto i = get(path);
|
||||||
if (i.type != FSAccessor::Type::tSymlink)
|
if (i.type != FSAccessor::Type::tSymlink)
|
||||||
throw Error(format("path '%1%' inside NAR file is not a symlink") % path);
|
throw Error("path '%1%' inside NAR file is not a symlink", path);
|
||||||
return i.target;
|
return i.target;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@ NarInfo::NarInfo(const Store & store, const std::string & s, const std::string &
|
||||||
: ValidPathInfo(StorePath::dummy.clone()) // FIXME: hack
|
: ValidPathInfo(StorePath::dummy.clone()) // FIXME: hack
|
||||||
{
|
{
|
||||||
auto corrupt = [&]() {
|
auto corrupt = [&]() {
|
||||||
throw Error(format("NAR info file '%1%' is corrupt") % whence);
|
throw Error("NAR info file '%1%' is corrupt", whence);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto parseHashField = [&](const string & s) {
|
auto parseHashField = [&](const string & s) {
|
||||||
|
|
|
@ -19,9 +19,9 @@ static void makeWritable(const Path & path)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(path.c_str(), &st))
|
if (lstat(path.c_str(), &st))
|
||||||
throw SysError(format("getting attributes of path '%1%'") % path);
|
throw SysError("getting attributes of path '%1%'", path);
|
||||||
if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1)
|
if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1)
|
||||||
throw SysError(format("changing writability of '%1%'") % path);
|
throw SysError("changing writability of '%1%'", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ LocalStore::InodeHash LocalStore::loadInodeHash()
|
||||||
InodeHash inodeHash;
|
InodeHash inodeHash;
|
||||||
|
|
||||||
AutoCloseDir dir(opendir(linksDir.c_str()));
|
AutoCloseDir dir(opendir(linksDir.c_str()));
|
||||||
if (!dir) throw SysError(format("opening directory '%1%'") % linksDir);
|
if (!dir) throw SysError("opening directory '%1%'", linksDir);
|
||||||
|
|
||||||
struct dirent * dirent;
|
struct dirent * dirent;
|
||||||
while (errno = 0, dirent = readdir(dir.get())) { /* sic */
|
while (errno = 0, dirent = readdir(dir.get())) { /* sic */
|
||||||
|
@ -55,7 +55,7 @@ LocalStore::InodeHash LocalStore::loadInodeHash()
|
||||||
// We don't care if we hit non-hash files, anything goes
|
// We don't care if we hit non-hash files, anything goes
|
||||||
inodeHash.insert(dirent->d_ino);
|
inodeHash.insert(dirent->d_ino);
|
||||||
}
|
}
|
||||||
if (errno) throw SysError(format("reading directory '%1%'") % linksDir);
|
if (errno) throw SysError("reading directory '%1%'", linksDir);
|
||||||
|
|
||||||
printMsg(lvlTalkative, format("loaded %1% hash inodes") % inodeHash.size());
|
printMsg(lvlTalkative, format("loaded %1% hash inodes") % inodeHash.size());
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ Strings LocalStore::readDirectoryIgnoringInodes(const Path & path, const InodeHa
|
||||||
Strings names;
|
Strings names;
|
||||||
|
|
||||||
AutoCloseDir dir(opendir(path.c_str()));
|
AutoCloseDir dir(opendir(path.c_str()));
|
||||||
if (!dir) throw SysError(format("opening directory '%1%'") % path);
|
if (!dir) throw SysError("opening directory '%1%'", path);
|
||||||
|
|
||||||
struct dirent * dirent;
|
struct dirent * dirent;
|
||||||
while (errno = 0, dirent = readdir(dir.get())) { /* sic */
|
while (errno = 0, dirent = readdir(dir.get())) { /* sic */
|
||||||
|
@ -83,7 +83,7 @@ Strings LocalStore::readDirectoryIgnoringInodes(const Path & path, const InodeHa
|
||||||
if (name == "." || name == "..") continue;
|
if (name == "." || name == "..") continue;
|
||||||
names.push_back(name);
|
names.push_back(name);
|
||||||
}
|
}
|
||||||
if (errno) throw SysError(format("reading directory '%1%'") % path);
|
if (errno) throw SysError("reading directory '%1%'", path);
|
||||||
|
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(path.c_str(), &st))
|
if (lstat(path.c_str(), &st))
|
||||||
throw SysError(format("getting attributes of path '%1%'") % path);
|
throw SysError("getting attributes of path '%1%'", path);
|
||||||
|
|
||||||
#if __APPLE__
|
#if __APPLE__
|
||||||
/* HFS/macOS has some undocumented security feature disabling hardlinking for
|
/* HFS/macOS has some undocumented security feature disabling hardlinking for
|
||||||
|
@ -130,7 +130,10 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
|
||||||
NixOS (example: $fontconfig/var/cache being modified). Skip
|
NixOS (example: $fontconfig/var/cache being modified). Skip
|
||||||
those files. FIXME: check the modification time. */
|
those files. FIXME: check the modification time. */
|
||||||
if (S_ISREG(st.st_mode) && (st.st_mode & S_IWUSR)) {
|
if (S_ISREG(st.st_mode) && (st.st_mode & S_IWUSR)) {
|
||||||
printError(format("skipping suspicious writable file '%1%'") % path);
|
logWarning({
|
||||||
|
.name = "Suspicious file",
|
||||||
|
.hint = hintfmt("skipping suspicious writable file '%1%'", path)
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,7 +189,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
|
||||||
current file with a hard link to that file. */
|
current file with a hard link to that file. */
|
||||||
struct stat stLink;
|
struct stat stLink;
|
||||||
if (lstat(linkPath.c_str(), &stLink))
|
if (lstat(linkPath.c_str(), &stLink))
|
||||||
throw SysError(format("getting attributes of path '%1%'") % linkPath);
|
throw SysError("getting attributes of path '%1%'", linkPath);
|
||||||
|
|
||||||
if (st.st_ino == stLink.st_ino) {
|
if (st.st_ino == stLink.st_ino) {
|
||||||
debug(format("'%1%' is already linked to '%2%'") % path % linkPath);
|
debug(format("'%1%' is already linked to '%2%'") % path % linkPath);
|
||||||
|
@ -194,7 +197,10 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st.st_size != stLink.st_size) {
|
if (st.st_size != stLink.st_size) {
|
||||||
printError(format("removing corrupted link '%1%'") % linkPath);
|
logWarning({
|
||||||
|
.name = "Corrupted link",
|
||||||
|
.hint = hintfmt("removing corrupted link '%1%'", linkPath)
|
||||||
|
});
|
||||||
unlink(linkPath.c_str());
|
unlink(linkPath.c_str());
|
||||||
goto retry;
|
goto retry;
|
||||||
}
|
}
|
||||||
|
@ -229,7 +235,10 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
|
||||||
/* Atomically replace the old file with the new hard link. */
|
/* Atomically replace the old file with the new hard link. */
|
||||||
if (rename(tempLink.c_str(), path.c_str()) == -1) {
|
if (rename(tempLink.c_str(), path.c_str()) == -1) {
|
||||||
if (unlink(tempLink.c_str()) == -1)
|
if (unlink(tempLink.c_str()) == -1)
|
||||||
printError(format("unable to unlink '%1%'") % tempLink);
|
logError({
|
||||||
|
.name = "Unlink error",
|
||||||
|
.hint = hintfmt("unable to unlink '%1%'", tempLink)
|
||||||
|
});
|
||||||
if (errno == EMLINK) {
|
if (errno == EMLINK) {
|
||||||
/* Some filesystems generate too many links on the rename,
|
/* Some filesystems generate too many links on the rename,
|
||||||
rather than on the original link. (Probably it
|
rather than on the original link. (Probably it
|
||||||
|
@ -238,7 +247,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
|
||||||
debug("'%s' has reached maximum number of links", linkPath);
|
debug("'%s' has reached maximum number of links", linkPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw SysError(format("cannot rename '%1%' to '%2%'") % tempLink % path);
|
throw SysError("cannot rename '%1%' to '%2%'", tempLink, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.filesLinked++;
|
stats.filesLinked++;
|
||||||
|
|
|
@ -20,7 +20,7 @@ AutoCloseFD openLockFile(const Path & path, bool create)
|
||||||
|
|
||||||
fd = open(path.c_str(), O_CLOEXEC | O_RDWR | (create ? O_CREAT : 0), 0600);
|
fd = open(path.c_str(), O_CLOEXEC | O_RDWR | (create ? O_CREAT : 0), 0600);
|
||||||
if (!fd && (create || errno != ENOENT))
|
if (!fd && (create || errno != ENOENT))
|
||||||
throw SysError(format("opening lock file '%1%'") % path);
|
throw SysError("opening lock file '%1%'", path);
|
||||||
|
|
||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ bool lockFile(int fd, LockType lockType, bool wait)
|
||||||
while (flock(fd, type) != 0) {
|
while (flock(fd, type) != 0) {
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
if (errno != EINTR)
|
if (errno != EINTR)
|
||||||
throw SysError(format("acquiring/releasing lock"));
|
throw SysError("acquiring/releasing lock");
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ bool lockFile(int fd, LockType lockType, bool wait)
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
if (errno == EWOULDBLOCK) return false;
|
if (errno == EWOULDBLOCK) return false;
|
||||||
if (errno != EINTR)
|
if (errno != EINTR)
|
||||||
throw SysError(format("acquiring/releasing lock"));
|
throw SysError("acquiring/releasing lock");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ bool PathLocks::lockPaths(const PathSet & paths,
|
||||||
hasn't been unlinked). */
|
hasn't been unlinked). */
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (fstat(fd.get(), &st) == -1)
|
if (fstat(fd.get(), &st) == -1)
|
||||||
throw SysError(format("statting lock file '%1%'") % lockPath);
|
throw SysError("statting lock file '%1%'", lockPath);
|
||||||
if (st.st_size != 0)
|
if (st.st_size != 0)
|
||||||
/* This lock file has been unlinked, so we're holding
|
/* This lock file has been unlinked, so we're holding
|
||||||
a lock on a deleted file. This means that other
|
a lock on a deleted file. This means that other
|
||||||
|
@ -160,7 +160,8 @@ void PathLocks::unlock()
|
||||||
|
|
||||||
if (close(i.first) == -1)
|
if (close(i.first) == -1)
|
||||||
printError(
|
printError(
|
||||||
format("error (ignored): cannot close lock file on '%1%'") % i.second);
|
"error (ignored): cannot close lock file on '%1%'",
|
||||||
|
i.second);
|
||||||
|
|
||||||
debug(format("lock released on '%1%'") % i.second);
|
debug(format("lock released on '%1%'") % i.second);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ Generations findGenerations(Path profile, int & curGen)
|
||||||
gen.number = n;
|
gen.number = n;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(gen.path.c_str(), &st) != 0)
|
if (lstat(gen.path.c_str(), &st) != 0)
|
||||||
throw SysError(format("statting '%1%'") % gen.path);
|
throw SysError("statting '%1%'", gen.path);
|
||||||
gen.creationTime = st.st_mtime;
|
gen.creationTime = st.st_mtime;
|
||||||
gens.push_back(gen);
|
gens.push_back(gen);
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ Path createGeneration(ref<LocalFSStore> store, Path profile, Path outPath)
|
||||||
static void removeFile(const Path & path)
|
static void removeFile(const Path & path)
|
||||||
{
|
{
|
||||||
if (remove(path.c_str()) == -1)
|
if (remove(path.c_str()) == -1)
|
||||||
throw SysError(format("cannot unlink '%1%'") % path);
|
throw SysError("cannot unlink '%1%'", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ void deleteGenerations(const Path & profile, const std::set<unsigned int> & gens
|
||||||
Generations gens = findGenerations(profile, curGen);
|
Generations gens = findGenerations(profile, curGen);
|
||||||
|
|
||||||
if (gensToDelete.find(curGen) != gensToDelete.end())
|
if (gensToDelete.find(curGen) != gensToDelete.end())
|
||||||
throw Error(format("cannot delete current generation of profile %1%'") % profile);
|
throw Error("cannot delete current generation of profile %1%'", profile);
|
||||||
|
|
||||||
for (auto & i : gens) {
|
for (auto & i : gens) {
|
||||||
if (gensToDelete.find(i.number) == gensToDelete.end()) continue;
|
if (gensToDelete.find(i.number) == gensToDelete.end()) continue;
|
||||||
|
@ -226,7 +226,7 @@ void deleteGenerationsOlderThan(const Path & profile, const string & timeSpec, b
|
||||||
int days;
|
int days;
|
||||||
|
|
||||||
if (!string2Int(strDays, days) || days < 1)
|
if (!string2Int(strDays, days) || days < 1)
|
||||||
throw Error(format("invalid number of days specifier '%1%'") % timeSpec);
|
throw Error("invalid number of days specifier '%1%'", timeSpec);
|
||||||
|
|
||||||
time_t oldTime = curTime - days * 24 * 3600;
|
time_t oldTime = curTime - days * 24 * 3600;
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ PathSet scanForReferences(const string & path,
|
||||||
auto baseName = std::string(baseNameOf(i));
|
auto baseName = std::string(baseNameOf(i));
|
||||||
string::size_type pos = baseName.find('-');
|
string::size_type pos = baseName.find('-');
|
||||||
if (pos == string::npos)
|
if (pos == string::npos)
|
||||||
throw Error(format("bad reference '%1%'") % i);
|
throw Error("bad reference '%1%'", i);
|
||||||
string s = string(baseName, 0, pos);
|
string s = string(baseName, 0, pos);
|
||||||
assert(s.size() == refLength);
|
assert(s.size() == refLength);
|
||||||
assert(backMap.find(s) == backMap.end());
|
assert(backMap.find(s) == backMap.end());
|
||||||
|
|
|
@ -51,7 +51,7 @@ std::pair<ref<FSAccessor>, Path> RemoteFSAccessor::fetch(const Path & path_)
|
||||||
std::string restPath = std::string(path, storePath.size());
|
std::string restPath = std::string(path, storePath.size());
|
||||||
|
|
||||||
if (!store->isValidPath(store->parseStorePath(storePath)))
|
if (!store->isValidPath(store->parseStorePath(storePath)))
|
||||||
throw InvalidPath(format("path '%1%' is not a valid store path") % storePath);
|
throw InvalidPath("path '%1%' is not a valid store path", storePath);
|
||||||
|
|
||||||
auto i = nars.find(storePath);
|
auto i = nars.find(storePath);
|
||||||
if (i != nars.end()) return {i->second, restPath};
|
if (i != nars.end()) return {i->second, restPath};
|
||||||
|
|
|
@ -116,11 +116,11 @@ ref<RemoteStore::Connection> UDSRemoteStore::openConnection()
|
||||||
struct sockaddr_un addr;
|
struct sockaddr_un addr;
|
||||||
addr.sun_family = AF_UNIX;
|
addr.sun_family = AF_UNIX;
|
||||||
if (socketPath.size() + 1 >= sizeof(addr.sun_path))
|
if (socketPath.size() + 1 >= sizeof(addr.sun_path))
|
||||||
throw Error(format("socket path '%1%' is too long") % socketPath);
|
throw Error("socket path '%1%' is too long", socketPath);
|
||||||
strcpy(addr.sun_path, socketPath.c_str());
|
strcpy(addr.sun_path, socketPath.c_str());
|
||||||
|
|
||||||
if (::connect(conn->fd.get(), (struct sockaddr *) &addr, sizeof(addr)) == -1)
|
if (::connect(conn->fd.get(), (struct sockaddr *) &addr, sizeof(addr)) == -1)
|
||||||
throw SysError(format("cannot connect to daemon at '%1%'") % socketPath);
|
throw SysError("cannot connect to daemon at '%1%'", socketPath);
|
||||||
|
|
||||||
conn->from.fd = conn->fd.get();
|
conn->from.fd = conn->fd.get();
|
||||||
conn->to.fd = conn->fd.get();
|
conn->to.fd = conn->fd.get();
|
||||||
|
@ -365,7 +365,7 @@ void RemoteStore::queryPathInfoUncached(const StorePath & path,
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
// Ugly backwards compatibility hack.
|
// Ugly backwards compatibility hack.
|
||||||
if (e.msg().find("is not valid") != std::string::npos)
|
if (e.msg().find("is not valid") != std::string::npos)
|
||||||
throw InvalidPath(e.what());
|
throw InvalidPath(e.info());
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 17) {
|
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 17) {
|
||||||
|
|
|
@ -32,8 +32,10 @@ namespace nix {
|
||||||
struct S3Error : public Error
|
struct S3Error : public Error
|
||||||
{
|
{
|
||||||
Aws::S3::S3Errors err;
|
Aws::S3::S3Errors err;
|
||||||
S3Error(Aws::S3::S3Errors err, const FormatOrString & fs)
|
|
||||||
: Error(fs), err(err) { };
|
template<typename... Args>
|
||||||
|
S3Error(Aws::S3::S3Errors err, const Args & ... args)
|
||||||
|
: Error(args...), err(err) { };
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Helper: given an Outcome<R, E>, return R in case of success, or
|
/* Helper: given an Outcome<R, E>, return R in case of success, or
|
||||||
|
@ -109,7 +111,9 @@ class RetryStrategy : public Aws::Client::DefaultRetryStrategy
|
||||||
auto retry = Aws::Client::DefaultRetryStrategy::ShouldRetry(error, attemptedRetries);
|
auto retry = Aws::Client::DefaultRetryStrategy::ShouldRetry(error, attemptedRetries);
|
||||||
if (retry)
|
if (retry)
|
||||||
printError("AWS error '%s' (%s), will retry in %d ms",
|
printError("AWS error '%s' (%s), will retry in %d ms",
|
||||||
error.GetExceptionName(), error.GetMessage(), CalculateDelayBeforeNextRetry(error, attemptedRetries));
|
error.GetExceptionName(),
|
||||||
|
error.GetMessage(),
|
||||||
|
CalculateDelayBeforeNextRetry(error, attemptedRetries));
|
||||||
return retry;
|
return retry;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -249,7 +253,7 @@ struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore
|
||||||
// If bucket listing is disabled, 404s turn into 403s
|
// If bucket listing is disabled, 404s turn into 403s
|
||||||
|| error.GetErrorType() == Aws::S3::S3Errors::ACCESS_DENIED)
|
|| error.GetErrorType() == Aws::S3::S3Errors::ACCESS_DENIED)
|
||||||
return false;
|
return false;
|
||||||
throw Error(format("AWS error fetching '%s': %s") % path % error.GetMessage());
|
throw Error("AWS error fetching '%s': %s", path, error.GetMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -29,7 +29,7 @@ SQLite::SQLite(const Path & path, bool create)
|
||||||
{
|
{
|
||||||
if (sqlite3_open_v2(path.c_str(), &db,
|
if (sqlite3_open_v2(path.c_str(), &db,
|
||||||
SQLITE_OPEN_READWRITE | (create ? SQLITE_OPEN_CREATE : 0), 0) != SQLITE_OK)
|
SQLITE_OPEN_READWRITE | (create ? SQLITE_OPEN_CREATE : 0), 0) != SQLITE_OK)
|
||||||
throw Error(format("cannot open SQLite database '%s'") % path);
|
throw Error("cannot open SQLite database '%s'", path);
|
||||||
|
|
||||||
if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
|
if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
|
||||||
throwSQLiteError(db, "setting timeout");
|
throwSQLiteError(db, "setting timeout");
|
||||||
|
@ -204,7 +204,10 @@ void handleSQLiteBusy(const SQLiteBusy & e)
|
||||||
|
|
||||||
if (now > lastWarned + 10) {
|
if (now > lastWarned + 10) {
|
||||||
lastWarned = now;
|
lastWarned = now;
|
||||||
printError("warning: %s", e.what());
|
logWarning(
|
||||||
|
ErrorInfo { .name = "Sqlite busy",
|
||||||
|
.hint = hintfmt(e.what())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sleep for a while since retrying the transaction right away
|
/* Sleep for a while since retrying the transaction right away
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "types.hh"
|
#include "error.hh"
|
||||||
|
|
||||||
struct sqlite3;
|
struct sqlite3;
|
||||||
struct sqlite3_stmt;
|
struct sqlite3_stmt;
|
||||||
|
|
|
@ -23,7 +23,7 @@ bool Store::isInStore(const Path & path) const
|
||||||
Path Store::toStorePath(const Path & path) const
|
Path Store::toStorePath(const Path & path) const
|
||||||
{
|
{
|
||||||
if (!isInStore(path))
|
if (!isInStore(path))
|
||||||
throw Error(format("path '%1%' is not in the Nix store") % path);
|
throw Error("path '%1%' is not in the Nix store", path);
|
||||||
Path::size_type slash = path.find('/', storeDir.size() + 1);
|
Path::size_type slash = path.find('/', storeDir.size() + 1);
|
||||||
if (slash == Path::npos)
|
if (slash == Path::npos)
|
||||||
return path;
|
return path;
|
||||||
|
@ -775,7 +775,11 @@ void ValidPathInfo::sign(const Store & store, const SecretKey & secretKey)
|
||||||
bool ValidPathInfo::isContentAddressed(const Store & store) const
|
bool ValidPathInfo::isContentAddressed(const Store & store) const
|
||||||
{
|
{
|
||||||
auto warn = [&]() {
|
auto warn = [&]() {
|
||||||
printError("warning: path '%s' claims to be content-addressed but isn't", store.printStorePath(path));
|
logWarning(
|
||||||
|
ErrorInfo{
|
||||||
|
.name = "Path not content-addressed",
|
||||||
|
.hint = hintfmt("path '%s' claims to be content-addressed but isn't", store.printStorePath(path))
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (hasPrefix(ca, "text:")) {
|
if (hasPrefix(ca, "text:")) {
|
||||||
|
@ -934,7 +938,7 @@ std::list<ref<Store>> getDefaultSubstituters()
|
||||||
try {
|
try {
|
||||||
stores.push_back(openStore(uri));
|
stores.push_back(openStore(uri));
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
printError("warning: %s", e.what());
|
logWarning(e.info());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,17 @@ namespace nix {
|
||||||
#if __linux__
|
#if __linux__
|
||||||
static bool didSaveAffinity = false;
|
static bool didSaveAffinity = false;
|
||||||
static cpu_set_t savedAffinity;
|
static cpu_set_t savedAffinity;
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream &os, const cpu_set_t &cset)
|
||||||
|
{
|
||||||
|
auto count = CPU_COUNT(&cset);
|
||||||
|
for (int i=0; i < count; ++i)
|
||||||
|
{
|
||||||
|
os << (CPU_ISSET(i,&cset) ? "1" : "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
return os;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,7 +36,7 @@ void setAffinityTo(int cpu)
|
||||||
CPU_ZERO(&newAffinity);
|
CPU_ZERO(&newAffinity);
|
||||||
CPU_SET(cpu, &newAffinity);
|
CPU_SET(cpu, &newAffinity);
|
||||||
if (sched_setaffinity(0, sizeof(cpu_set_t), &newAffinity) == -1)
|
if (sched_setaffinity(0, sizeof(cpu_set_t), &newAffinity) == -1)
|
||||||
printError(format("failed to lock thread to CPU %1%") % cpu);
|
printError("failed to lock thread to CPU %1%", cpu);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +58,11 @@ void restoreAffinity()
|
||||||
#if __linux__
|
#if __linux__
|
||||||
if (!didSaveAffinity) return;
|
if (!didSaveAffinity) return;
|
||||||
if (sched_setaffinity(0, sizeof(cpu_set_t), &savedAffinity) == -1)
|
if (sched_setaffinity(0, sizeof(cpu_set_t), &savedAffinity) == -1)
|
||||||
printError("failed to restore affinity %1%");
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << savedAffinity;
|
||||||
|
printError("failed to restore CPU affinity %1%", oss.str());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ static void dumpContents(const Path & path, size_t size,
|
||||||
sink << "contents" << size;
|
sink << "contents" << size;
|
||||||
|
|
||||||
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
||||||
if (!fd) throw SysError(format("opening file '%1%'") % path);
|
if (!fd) throw SysError("opening file '%1%'", path);
|
||||||
|
|
||||||
std::vector<unsigned char> buf(65536);
|
std::vector<unsigned char> buf(65536);
|
||||||
size_t left = size;
|
size_t left = size;
|
||||||
|
@ -68,7 +68,7 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(path.c_str(), &st))
|
if (lstat(path.c_str(), &st))
|
||||||
throw SysError(format("getting attributes of path '%1%'") % path);
|
throw SysError("getting attributes of path '%1%'", path);
|
||||||
|
|
||||||
sink << "(";
|
sink << "(";
|
||||||
|
|
||||||
|
@ -94,8 +94,9 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
|
||||||
name.erase(pos);
|
name.erase(pos);
|
||||||
}
|
}
|
||||||
if (unhacked.find(name) != unhacked.end())
|
if (unhacked.find(name) != unhacked.end())
|
||||||
throw Error(format("file name collision in between '%1%' and '%2%'")
|
throw Error("file name collision in between '%1%' and '%2%'",
|
||||||
% (path + "/" + unhacked[name]) % (path + "/" + i.name));
|
(path + "/" + unhacked[name]),
|
||||||
|
(path + "/" + i.name));
|
||||||
unhacked[name] = i.name;
|
unhacked[name] = i.name;
|
||||||
} else
|
} else
|
||||||
unhacked[i.name] = i.name;
|
unhacked[i.name] = i.name;
|
||||||
|
@ -111,7 +112,7 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
|
||||||
else if (S_ISLNK(st.st_mode))
|
else if (S_ISLNK(st.st_mode))
|
||||||
sink << "type" << "symlink" << "target" << readLink(path);
|
sink << "type" << "symlink" << "target" << readLink(path);
|
||||||
|
|
||||||
else throw Error(format("file '%1%' has an unsupported type") % path);
|
else throw Error("file '%1%' has an unsupported type", path);
|
||||||
|
|
||||||
sink << ")";
|
sink << ")";
|
||||||
}
|
}
|
||||||
|
@ -247,7 +248,7 @@ static void parse(ParseSink & sink, Source & source, const Path & path)
|
||||||
} else if (s == "name") {
|
} else if (s == "name") {
|
||||||
name = readString(source);
|
name = readString(source);
|
||||||
if (name.empty() || name == "." || name == ".." || name.find('/') != string::npos || name.find((char) 0) != string::npos)
|
if (name.empty() || name == "." || name == ".." || name.find('/') != string::npos || name.find((char) 0) != string::npos)
|
||||||
throw Error(format("NAR contains invalid file name '%1%'") % name);
|
throw Error("NAR contains invalid file name '%1%'", name);
|
||||||
if (name <= prevName)
|
if (name <= prevName)
|
||||||
throw Error("NAR directory is not sorted");
|
throw Error("NAR directory is not sorted");
|
||||||
prevName = name;
|
prevName = name;
|
||||||
|
@ -303,14 +304,14 @@ struct RestoreSink : ParseSink
|
||||||
{
|
{
|
||||||
Path p = dstPath + path;
|
Path p = dstPath + path;
|
||||||
if (mkdir(p.c_str(), 0777) == -1)
|
if (mkdir(p.c_str(), 0777) == -1)
|
||||||
throw SysError(format("creating directory '%1%'") % p);
|
throw SysError("creating directory '%1%'", p);
|
||||||
};
|
};
|
||||||
|
|
||||||
void createRegularFile(const Path & path)
|
void createRegularFile(const Path & path)
|
||||||
{
|
{
|
||||||
Path p = dstPath + path;
|
Path p = dstPath + path;
|
||||||
fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666);
|
fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666);
|
||||||
if (!fd) throw SysError(format("creating file '%1%'") % p);
|
if (!fd) throw SysError("creating file '%1%'", p);
|
||||||
}
|
}
|
||||||
|
|
||||||
void isExecutable()
|
void isExecutable()
|
||||||
|
@ -332,7 +333,7 @@ struct RestoreSink : ParseSink
|
||||||
OpenSolaris). Since preallocation is just an
|
OpenSolaris). Since preallocation is just an
|
||||||
optimisation, ignore it. */
|
optimisation, ignore it. */
|
||||||
if (errno && errno != EINVAL && errno != EOPNOTSUPP && errno != ENOSYS)
|
if (errno && errno != EINVAL && errno != EOPNOTSUPP && errno != ENOSYS)
|
||||||
throw SysError(format("preallocating file of %1% bytes") % len);
|
throw SysError("preallocating file of %1% bytes", len);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ void Args::parseCmdline(const Strings & _cmdline)
|
||||||
}
|
}
|
||||||
else if (!dashDash && std::string(arg, 0, 1) == "-") {
|
else if (!dashDash && std::string(arg, 0, 1) == "-") {
|
||||||
if (!processFlag(pos, cmdline.end()))
|
if (!processFlag(pos, cmdline.end()))
|
||||||
throw UsageError(format("unrecognised flag '%1%'") % arg);
|
throw UsageError("unrecognised flag '%1%'", arg);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
pendingArgs.push_back(*pos++);
|
pendingArgs.push_back(*pos++);
|
||||||
|
@ -130,7 +130,7 @@ bool Args::processArgs(const Strings & args, bool finish)
|
||||||
{
|
{
|
||||||
if (expectedArgs.empty()) {
|
if (expectedArgs.empty()) {
|
||||||
if (!args.empty())
|
if (!args.empty())
|
||||||
throw UsageError(format("unexpected argument '%1%'") % args.front());
|
throw UsageError("unexpected argument '%1%'", args.front());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -481,7 +481,7 @@ ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & next
|
||||||
else if (method == "br")
|
else if (method == "br")
|
||||||
return make_ref<BrotliCompressionSink>(nextSink);
|
return make_ref<BrotliCompressionSink>(nextSink);
|
||||||
else
|
else
|
||||||
throw UnknownCompressionMethod(format("unknown compression method '%s'") % method);
|
throw UnknownCompressionMethod("unknown compression method '%s'", method);
|
||||||
}
|
}
|
||||||
|
|
||||||
ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel)
|
ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel)
|
||||||
|
|
|
@ -2,9 +2,38 @@
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include "serialise.hh"
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
namespace nix
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
|
const std::string nativeSystem = SYSTEM;
|
||||||
|
|
||||||
|
// addPrefix is used for show-trace. Strings added with addPrefix
|
||||||
|
// will print ahead of the error itself.
|
||||||
|
BaseError & BaseError::addPrefix(const FormatOrString & fs)
|
||||||
{
|
{
|
||||||
|
prefix_ = fs.s + prefix_;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// c++ std::exception descendants must have a 'const char* what()' function.
|
||||||
|
// This stringifies the error and caches it for use by what(), or similarly by msg().
|
||||||
|
const string& BaseError::calcWhat() const
|
||||||
|
{
|
||||||
|
if (what_.has_value())
|
||||||
|
return *what_;
|
||||||
|
else {
|
||||||
|
err.name = sname();
|
||||||
|
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << err;
|
||||||
|
what_ = oss.str();
|
||||||
|
|
||||||
|
return *what_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<string> ErrorInfo::programName = std::nullopt;
|
std::optional<string> ErrorInfo::programName = std::nullopt;
|
||||||
|
|
||||||
|
@ -15,31 +44,37 @@ std::ostream& operator<<(std::ostream &os, const hintformat &hf)
|
||||||
|
|
||||||
string showErrPos(const ErrPos &errPos)
|
string showErrPos(const ErrPos &errPos)
|
||||||
{
|
{
|
||||||
|
if (errPos.line > 0) {
|
||||||
if (errPos.column > 0) {
|
if (errPos.column > 0) {
|
||||||
return fmt("(%1%:%2%)", errPos.lineNumber, errPos.column);
|
return fmt("(%1%:%2%)", errPos.line, errPos.column);
|
||||||
} else {
|
} else {
|
||||||
return fmt("(%1%)", errPos.lineNumber);
|
return fmt("(%1%)", errPos.line);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void printCodeLines(const string &prefix, const NixCode &nixCode)
|
// if nixCode contains lines of code, print them to the ostream, indicating the error column.
|
||||||
|
void printCodeLines(std::ostream &out, const string &prefix, const NixCode &nixCode)
|
||||||
{
|
{
|
||||||
// previous line of code.
|
// previous line of code.
|
||||||
if (nixCode.prevLineOfCode.has_value()) {
|
if (nixCode.prevLineOfCode.has_value()) {
|
||||||
std::cout << fmt("%1% %|2$5d|| %3%",
|
out << std::endl
|
||||||
|
<< fmt("%1% %|2$5d|| %3%",
|
||||||
prefix,
|
prefix,
|
||||||
(nixCode.errPos.lineNumber - 1),
|
(nixCode.errPos.line - 1),
|
||||||
*nixCode.prevLineOfCode)
|
*nixCode.prevLineOfCode);
|
||||||
<< std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// line of code containing the error.%2$+5d%
|
if (nixCode.errLineOfCode.has_value()) {
|
||||||
std::cout << fmt("%1% %|2$5d|| %3%",
|
// line of code containing the error.
|
||||||
|
out << std::endl
|
||||||
|
<< fmt("%1% %|2$5d|| %3%",
|
||||||
prefix,
|
prefix,
|
||||||
(nixCode.errPos.lineNumber),
|
(nixCode.errPos.line),
|
||||||
nixCode.errLineOfCode)
|
*nixCode.errLineOfCode);
|
||||||
<< std::endl;
|
|
||||||
|
|
||||||
// error arrows for the column range.
|
// error arrows for the column range.
|
||||||
if (nixCode.errPos.column > 0) {
|
if (nixCode.errPos.column > 0) {
|
||||||
int start = nixCode.errPos.column;
|
int start = nixCode.errPos.column;
|
||||||
|
@ -50,41 +85,73 @@ void printCodeLines(const string &prefix, const NixCode &nixCode)
|
||||||
|
|
||||||
std::string arrows("^");
|
std::string arrows("^");
|
||||||
|
|
||||||
std::cout << fmt("%1% |%2%" ANSI_RED "%3%" ANSI_NORMAL,
|
out << std::endl
|
||||||
|
<< fmt("%1% |%2%" ANSI_RED "%3%" ANSI_NORMAL,
|
||||||
prefix,
|
prefix,
|
||||||
spaces,
|
spaces,
|
||||||
arrows) << std::endl;
|
arrows);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// next line of code.
|
// next line of code.
|
||||||
if (nixCode.nextLineOfCode.has_value()) {
|
if (nixCode.nextLineOfCode.has_value()) {
|
||||||
std::cout << fmt("%1% %|2$5d|| %3%",
|
out << std::endl
|
||||||
|
<< fmt("%1% %|2$5d|| %3%",
|
||||||
prefix,
|
prefix,
|
||||||
(nixCode.errPos.lineNumber + 1),
|
(nixCode.errPos.line + 1),
|
||||||
*nixCode.nextLineOfCode)
|
*nixCode.nextLineOfCode);
|
||||||
<< std::endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void printErrorInfo(const ErrorInfo &einfo)
|
std::ostream& operator<<(std::ostream &out, const ErrorInfo &einfo)
|
||||||
{
|
{
|
||||||
int errwidth = 80;
|
int errwidth = 80;
|
||||||
string prefix = " ";
|
string prefix = "";
|
||||||
|
|
||||||
string levelString;
|
string levelString;
|
||||||
switch (einfo.level) {
|
switch (einfo.level) {
|
||||||
case ErrLevel::elError: {
|
case Verbosity::lvlError: {
|
||||||
levelString = ANSI_RED;
|
levelString = ANSI_RED;
|
||||||
levelString += "error:";
|
levelString += "error:";
|
||||||
levelString += ANSI_NORMAL;
|
levelString += ANSI_NORMAL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ErrLevel::elWarning: {
|
case Verbosity::lvlWarn: {
|
||||||
levelString = ANSI_YELLOW;
|
levelString = ANSI_YELLOW;
|
||||||
levelString += "warning:";
|
levelString += "warning:";
|
||||||
levelString += ANSI_NORMAL;
|
levelString += ANSI_NORMAL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Verbosity::lvlInfo: {
|
||||||
|
levelString = ANSI_GREEN;
|
||||||
|
levelString += "info:";
|
||||||
|
levelString += ANSI_NORMAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Verbosity::lvlTalkative: {
|
||||||
|
levelString = ANSI_GREEN;
|
||||||
|
levelString += "talk:";
|
||||||
|
levelString += ANSI_NORMAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Verbosity::lvlChatty: {
|
||||||
|
levelString = ANSI_GREEN;
|
||||||
|
levelString += "chat:";
|
||||||
|
levelString += ANSI_NORMAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Verbosity::lvlVomit: {
|
||||||
|
levelString = ANSI_GREEN;
|
||||||
|
levelString += "vomit:";
|
||||||
|
levelString += ANSI_NORMAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Verbosity::lvlDebug: {
|
||||||
|
levelString = ANSI_YELLOW;
|
||||||
|
levelString += "debug:";
|
||||||
|
levelString += ANSI_NORMAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
levelString = fmt("invalid error level: %1%", einfo.level);
|
levelString = fmt("invalid error level: %1%", einfo.level);
|
||||||
break;
|
break;
|
||||||
|
@ -99,48 +166,58 @@ void printErrorInfo(const ErrorInfo &einfo)
|
||||||
dashes.append("-");
|
dashes.append("-");
|
||||||
|
|
||||||
// divider.
|
// divider.
|
||||||
std::cout << fmt("%1%%2%" ANSI_BLUE " %3% %4% %5% %6%" ANSI_NORMAL,
|
if (einfo.name != "")
|
||||||
|
out << fmt("%1%%2%" ANSI_BLUE " --- %3% %4% %5%" ANSI_NORMAL,
|
||||||
prefix,
|
prefix,
|
||||||
levelString,
|
levelString,
|
||||||
"---",
|
|
||||||
einfo.name,
|
einfo.name,
|
||||||
dashes,
|
dashes,
|
||||||
einfo.programName.value_or(""))
|
einfo.programName.value_or(""));
|
||||||
<< std::endl;
|
else
|
||||||
|
out << fmt("%1%%2%" ANSI_BLUE " -----%3% %4%" ANSI_NORMAL,
|
||||||
// filename.
|
|
||||||
if (einfo.nixCode.has_value()) {
|
|
||||||
if (einfo.nixCode->errPos.nixFile != "") {
|
|
||||||
string eline = einfo.nixCode->errLineOfCode != ""
|
|
||||||
? string(" ") + showErrPos(einfo.nixCode->errPos)
|
|
||||||
: "";
|
|
||||||
|
|
||||||
std::cout << fmt("%1%in file: " ANSI_BLUE "%2%%3%" ANSI_NORMAL,
|
|
||||||
prefix,
|
prefix,
|
||||||
einfo.nixCode->errPos.nixFile,
|
levelString,
|
||||||
eline) << std::endl;
|
dashes,
|
||||||
std::cout << prefix << std::endl;
|
einfo.programName.value_or(""));
|
||||||
|
|
||||||
|
bool nl = false; // intersperse newline between sections.
|
||||||
|
if (einfo.nixCode.has_value()) {
|
||||||
|
if (einfo.nixCode->errPos.file != "") {
|
||||||
|
// filename, line, column.
|
||||||
|
out << std::endl << fmt("%1%in file: " ANSI_BLUE "%2% %3%" ANSI_NORMAL,
|
||||||
|
prefix,
|
||||||
|
einfo.nixCode->errPos.file,
|
||||||
|
showErrPos(einfo.nixCode->errPos));
|
||||||
} else {
|
} else {
|
||||||
std::cout << fmt("%1%from command line argument", prefix) << std::endl;
|
out << std::endl << fmt("%1%from command line argument", prefix);
|
||||||
std::cout << prefix << std::endl;
|
|
||||||
}
|
}
|
||||||
|
nl = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// description
|
// description
|
||||||
std::cout << prefix << einfo.description << std::endl;
|
if (einfo.description != "") {
|
||||||
std::cout << prefix << std::endl;
|
if (nl)
|
||||||
|
out << std::endl << prefix;
|
||||||
|
out << std::endl << prefix << einfo.description;
|
||||||
|
nl = true;
|
||||||
|
}
|
||||||
|
|
||||||
// lines of code.
|
// lines of code.
|
||||||
if (einfo.nixCode->errLineOfCode != "") {
|
if (einfo.nixCode.has_value() && einfo.nixCode->errLineOfCode.has_value()) {
|
||||||
printCodeLines(prefix, *einfo.nixCode);
|
if (nl)
|
||||||
std::cout << prefix << std::endl;
|
out << std::endl << prefix;
|
||||||
|
printCodeLines(out, prefix, *einfo.nixCode);
|
||||||
|
nl = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// hint
|
// hint
|
||||||
if (einfo.hint.has_value()) {
|
if (einfo.hint.has_value()) {
|
||||||
std::cout << prefix << *einfo.hint << std::endl;
|
if (nl)
|
||||||
std::cout << prefix << std::endl;
|
out << std::endl << prefix;
|
||||||
|
out << std::endl << prefix << *einfo.hint;
|
||||||
|
nl = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,72 @@
|
||||||
#ifndef error_hh
|
#pragma once
|
||||||
#define error_hh
|
|
||||||
|
|
||||||
#include "ansicolor.hh"
|
|
||||||
#include <string>
|
#include "ref.hh"
|
||||||
#include <optional>
|
|
||||||
#include <iostream>
|
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
|
|
||||||
namespace nix
|
#include <list>
|
||||||
{
|
#include <memory>
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "fmt.hh"
|
||||||
|
|
||||||
|
/* Before 4.7, gcc's std::exception uses empty throw() specifiers for
|
||||||
|
* its (virtual) destructor and what() in c++11 mode, in violation of spec
|
||||||
|
*/
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7)
|
||||||
|
#define EXCEPTION_NEEDS_THROW_SPEC
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
This file defines two main structs/classes used in nix error handling.
|
||||||
|
|
||||||
|
ErrorInfo provides a standard payload of error information, with conversion to string
|
||||||
|
happening in the logger rather than at the call site.
|
||||||
|
|
||||||
|
BaseError is the ancestor of nix specific exceptions (and Interrupted), and contains
|
||||||
|
an ErrorInfo.
|
||||||
|
|
||||||
|
ErrorInfo structs are sent to the logger as part of an exception, or directly with the
|
||||||
|
logError or logWarning macros.
|
||||||
|
|
||||||
|
See the error-demo.cc program for usage examples.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
elWarning,
|
lvlError = 0,
|
||||||
elError
|
lvlWarn,
|
||||||
} ErrLevel;
|
lvlInfo,
|
||||||
|
lvlTalkative,
|
||||||
|
lvlChatty,
|
||||||
|
lvlDebug,
|
||||||
|
lvlVomit
|
||||||
|
} Verbosity;
|
||||||
|
|
||||||
struct ErrPos
|
// ErrPos indicates the location of an error in a nix file.
|
||||||
{
|
struct ErrPos {
|
||||||
int lineNumber;
|
int line = 0;
|
||||||
int column;
|
int column = 0;
|
||||||
string nixFile;
|
string file;
|
||||||
|
|
||||||
|
operator bool() const
|
||||||
|
{
|
||||||
|
return line != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert from the Pos struct, found in libexpr.
|
||||||
template <class P>
|
template <class P>
|
||||||
ErrPos& operator=(const P &pos)
|
ErrPos& operator=(const P &pos)
|
||||||
{
|
{
|
||||||
lineNumber = pos.line;
|
line = pos.line;
|
||||||
column = pos.column;
|
column = pos.column;
|
||||||
nixFile = pos.file;
|
file = pos.file;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,71 +77,15 @@ struct ErrPos
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NixCode
|
struct NixCode {
|
||||||
{
|
|
||||||
ErrPos errPos;
|
ErrPos errPos;
|
||||||
std::optional<string> prevLineOfCode;
|
std::optional<string> prevLineOfCode;
|
||||||
string errLineOfCode;
|
std::optional<string> errLineOfCode;
|
||||||
std::optional<string> nextLineOfCode;
|
std::optional<string> nextLineOfCode;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
struct ErrorInfo {
|
||||||
// format function for hints. same as fmt, except templated values
|
Verbosity level;
|
||||||
// are always in yellow.
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
struct yellowify
|
|
||||||
{
|
|
||||||
yellowify(T &s) : value(s) {}
|
|
||||||
T &value;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
std::ostream& operator<<(std::ostream &out, const yellowify<T> &y)
|
|
||||||
{
|
|
||||||
return out << ANSI_YELLOW << y.value << ANSI_NORMAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
class hintformat
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
hintformat(string format) :fmt(format)
|
|
||||||
{
|
|
||||||
fmt.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
|
|
||||||
}
|
|
||||||
template<class T>
|
|
||||||
hintformat& operator%(const T &value)
|
|
||||||
{
|
|
||||||
fmt % yellowify(value);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string str() const
|
|
||||||
{
|
|
||||||
return fmt.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename U>
|
|
||||||
friend class AddHint;
|
|
||||||
private:
|
|
||||||
format fmt;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream &os, const hintformat &hf);
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
inline hintformat hintfmt(const std::string & fs, const Args & ... args)
|
|
||||||
{
|
|
||||||
hintformat f(fs);
|
|
||||||
formatHelper(f, args...);
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------
|
|
||||||
// ErrorInfo.
|
|
||||||
struct ErrorInfo
|
|
||||||
{
|
|
||||||
ErrLevel level;
|
|
||||||
string name;
|
string name;
|
||||||
string description;
|
string description;
|
||||||
std::optional<hintformat> hint;
|
std::optional<hintformat> hint;
|
||||||
|
@ -110,12 +94,88 @@ struct ErrorInfo
|
||||||
static std::optional<string> programName;
|
static std::optional<string> programName;
|
||||||
};
|
};
|
||||||
|
|
||||||
// --------------------------------------------------------
|
std::ostream& operator<<(std::ostream &out, const ErrorInfo &einfo);
|
||||||
// error printing
|
|
||||||
|
|
||||||
// just to cout for now.
|
/* BaseError should generally not be caught, as it has Interrupted as
|
||||||
void printErrorInfo(const ErrorInfo &einfo);
|
a subclass. Catch Error instead. */
|
||||||
|
class BaseError : public std::exception
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
string prefix_; // used for location traces etc.
|
||||||
|
mutable ErrorInfo err;
|
||||||
|
|
||||||
|
mutable std::optional<string> what_;
|
||||||
|
const string& calcWhat() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
unsigned int status = 1; // exit status
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
BaseError(unsigned int status, const Args & ... args)
|
||||||
|
: err { .level = lvlError,
|
||||||
|
.hint = hintfmt(args...)
|
||||||
|
}
|
||||||
|
, status(status)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
BaseError(const Args & ... args)
|
||||||
|
: err { .level = lvlError,
|
||||||
|
.hint = hintfmt(args...)
|
||||||
|
}
|
||||||
|
{ }
|
||||||
|
|
||||||
|
BaseError(hintformat hint)
|
||||||
|
: err { .level = lvlError,
|
||||||
|
.hint = hint
|
||||||
|
}
|
||||||
|
{ }
|
||||||
|
|
||||||
|
BaseError(ErrorInfo e)
|
||||||
|
: err(e)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
virtual const char* sname() const { return "BaseError"; }
|
||||||
|
|
||||||
|
#ifdef EXCEPTION_NEEDS_THROW_SPEC
|
||||||
|
~BaseError() throw () { };
|
||||||
|
const char * what() const throw () { return calcWhat().c_str(); }
|
||||||
|
#else
|
||||||
|
const char * what() const noexcept override { return calcWhat().c_str(); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const string & msg() const { return calcWhat(); }
|
||||||
|
const string & prefix() const { return prefix_; }
|
||||||
|
BaseError & addPrefix(const FormatOrString & fs);
|
||||||
|
|
||||||
|
const ErrorInfo & info() { calcWhat(); return err; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MakeError(newClass, superClass) \
|
||||||
|
class newClass : public superClass \
|
||||||
|
{ \
|
||||||
|
public: \
|
||||||
|
using superClass::superClass; \
|
||||||
|
virtual const char* sname() const override { return #newClass; } \
|
||||||
|
}
|
||||||
|
|
||||||
|
MakeError(Error, BaseError);
|
||||||
|
|
||||||
|
class SysError : public Error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int errNo;
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
SysError(const Args & ... args)
|
||||||
|
:Error("")
|
||||||
|
{
|
||||||
|
errNo = errno;
|
||||||
|
auto hf = hintfmt(args...);
|
||||||
|
err.hint = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo));
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual const char* sname() const override { return "SysError"; }
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
139
src/libutil/fmt.hh
Normal file
139
src/libutil/fmt.hh
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/format.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include "ansicolor.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
|
/* Inherit some names from other namespaces for convenience. */
|
||||||
|
using std::string;
|
||||||
|
using boost::format;
|
||||||
|
|
||||||
|
|
||||||
|
/* A variadic template that does nothing. Useful to call a function
|
||||||
|
for all variadic arguments but ignoring the result. */
|
||||||
|
struct nop { template<typename... T> nop(T...) {} };
|
||||||
|
|
||||||
|
|
||||||
|
struct FormatOrString
|
||||||
|
{
|
||||||
|
string s;
|
||||||
|
FormatOrString(const string & s) : s(s) { };
|
||||||
|
template<class F>
|
||||||
|
FormatOrString(const F & f) : s(f.str()) { };
|
||||||
|
FormatOrString(const char * s) : s(s) { };
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* A helper for formatting strings. ‘fmt(format, a_0, ..., a_n)’ is
|
||||||
|
equivalent to ‘boost::format(format) % a_0 % ... %
|
||||||
|
... a_n’. However, ‘fmt(s)’ is equivalent to ‘s’ (so no %-expansion
|
||||||
|
takes place). */
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
inline void formatHelper(F & f)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class F, typename T, typename... Args>
|
||||||
|
inline void formatHelper(F & f, const T & x, const Args & ... args)
|
||||||
|
{
|
||||||
|
formatHelper(f % x, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string fmt(const std::string & s)
|
||||||
|
{
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string fmt(const char * s)
|
||||||
|
{
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string fmt(const FormatOrString & fs)
|
||||||
|
{
|
||||||
|
return fs.s;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
inline std::string fmt(const std::string & fs, const Args & ... args)
|
||||||
|
{
|
||||||
|
boost::format f(fs);
|
||||||
|
f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
|
||||||
|
formatHelper(f, args...);
|
||||||
|
return f.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// format function for hints in errors. same as fmt, except templated values
|
||||||
|
// are always in yellow.
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct yellowtxt
|
||||||
|
{
|
||||||
|
yellowtxt(const T &s) : value(s) {}
|
||||||
|
const T &value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
std::ostream& operator<<(std::ostream &out, const yellowtxt<T> &y)
|
||||||
|
{
|
||||||
|
return out << ANSI_YELLOW << y.value << ANSI_NORMAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct normaltxt
|
||||||
|
{
|
||||||
|
normaltxt(const T &s) : value(s) {}
|
||||||
|
const T &value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
std::ostream& operator<<(std::ostream &out, const normaltxt<T> &y)
|
||||||
|
{
|
||||||
|
return out << ANSI_NORMAL << y.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
class hintformat
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
hintformat(const string &format) :fmt(format)
|
||||||
|
{
|
||||||
|
fmt.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
hintformat(const hintformat &hf)
|
||||||
|
: fmt(hf.fmt)
|
||||||
|
{}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
hintformat& operator%(const T &value)
|
||||||
|
{
|
||||||
|
fmt % yellowtxt(value);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string str() const
|
||||||
|
{
|
||||||
|
return fmt.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
format fmt;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream &os, const hintformat &hf);
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
inline hintformat hintfmt(const std::string & fs, const Args & ... args)
|
||||||
|
{
|
||||||
|
hintformat f(fs);
|
||||||
|
formatHelper(f, args...);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -69,6 +69,14 @@ public:
|
||||||
writeToStderr(prefix + filterANSIEscapes(fs.s, !tty) + "\n");
|
writeToStderr(prefix + filterANSIEscapes(fs.s, !tty) + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void logEI(const ErrorInfo & ei) override
|
||||||
|
{
|
||||||
|
std::stringstream oss;
|
||||||
|
oss << ei;
|
||||||
|
|
||||||
|
log(ei.level, oss.str());
|
||||||
|
}
|
||||||
|
|
||||||
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
|
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
|
||||||
const std::string & s, const Fields & fields, ActivityId parent)
|
const std::string & s, const Fields & fields, ActivityId parent)
|
||||||
override
|
override
|
||||||
|
@ -126,8 +134,7 @@ Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type,
|
||||||
logger.startActivity(id, lvl, type, s, fields, parent);
|
logger.startActivity(id, lvl, type, s, fields, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct JSONLogger : Logger
|
struct JSONLogger : Logger {
|
||||||
{
|
|
||||||
Logger & prevLogger;
|
Logger & prevLogger;
|
||||||
|
|
||||||
JSONLogger(Logger & prevLogger) : prevLogger(prevLogger) { }
|
JSONLogger(Logger & prevLogger) : prevLogger(prevLogger) { }
|
||||||
|
@ -163,6 +170,19 @@ struct JSONLogger : Logger
|
||||||
write(json);
|
write(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void logEI(const ErrorInfo & ei) override
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << ei;
|
||||||
|
|
||||||
|
nlohmann::json json;
|
||||||
|
json["action"] = "msg";
|
||||||
|
json["level"] = ei.level;
|
||||||
|
json["msg"] = oss.str();
|
||||||
|
|
||||||
|
write(json);
|
||||||
|
}
|
||||||
|
|
||||||
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
|
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
|
||||||
const std::string & s, const Fields & fields, ActivityId parent) override
|
const std::string & s, const Fields & fields, ActivityId parent) override
|
||||||
{
|
{
|
||||||
|
@ -253,13 +273,17 @@ bool handleJSONLogMessage(const std::string & msg,
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (std::exception & e) {
|
} catch (std::exception & e) {
|
||||||
printError("bad log message from builder: %s", e.what());
|
logError({
|
||||||
|
.name = "Json log message",
|
||||||
|
.hint = hintfmt("bad log message from builder: %s", e.what())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Activity::~Activity() {
|
Activity::~Activity()
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
logger.stopActivity(id);
|
logger.stopActivity(id);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
|
|
@ -1,19 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
|
#include "error.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
lvlError = 0,
|
|
||||||
lvlWarn,
|
|
||||||
lvlInfo,
|
|
||||||
lvlTalkative,
|
|
||||||
lvlChatty,
|
|
||||||
lvlDebug,
|
|
||||||
lvlVomit
|
|
||||||
} Verbosity;
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
actUnknown = 0,
|
actUnknown = 0,
|
||||||
actCopyPath = 100,
|
actCopyPath = 100,
|
||||||
|
@ -75,6 +66,14 @@ public:
|
||||||
log(lvlInfo, fs);
|
log(lvlInfo, fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void logEI(const ErrorInfo &ei) = 0;
|
||||||
|
|
||||||
|
void logEI(Verbosity lvl, ErrorInfo ei)
|
||||||
|
{
|
||||||
|
ei.level = lvl;
|
||||||
|
logEI(ei);
|
||||||
|
}
|
||||||
|
|
||||||
virtual void warn(const std::string & msg);
|
virtual void warn(const std::string & msg);
|
||||||
|
|
||||||
virtual void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
|
virtual void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
|
||||||
|
@ -156,9 +155,23 @@ bool handleJSONLogMessage(const std::string & msg,
|
||||||
|
|
||||||
extern Verbosity verbosity; /* suppress msgs > this */
|
extern Verbosity verbosity; /* suppress msgs > this */
|
||||||
|
|
||||||
/* Print a message if the current log level is at least the specified
|
/* Print a message with the standard ErrorInfo format.
|
||||||
level. Note that this has to be implemented as a macro to ensure
|
In general, use these 'log' macros for reporting problems that may require user
|
||||||
that the arguments are evaluated lazily. */
|
intervention or that need more explanation. Use the 'print' macros for more
|
||||||
|
lightweight status messages. */
|
||||||
|
#define logErrorInfo(level, errorInfo...) \
|
||||||
|
do { \
|
||||||
|
if (level <= nix::verbosity) { \
|
||||||
|
logger->logEI(level, errorInfo); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define logError(errorInfo...) logErrorInfo(lvlError, errorInfo)
|
||||||
|
#define logWarning(errorInfo...) logErrorInfo(lvlWarn, errorInfo)
|
||||||
|
|
||||||
|
/* Print a string message if the current log level is at least the specified
|
||||||
|
level. Note that this has to be implemented as a macro to ensure that the
|
||||||
|
arguments are evaluated lazily. */
|
||||||
#define printMsg(level, args...) \
|
#define printMsg(level, args...) \
|
||||||
do { \
|
do { \
|
||||||
if (level <= nix::verbosity) { \
|
if (level <= nix::verbosity) { \
|
||||||
|
@ -172,6 +185,7 @@ extern Verbosity verbosity; /* suppress msgs > this */
|
||||||
#define debug(args...) printMsg(lvlDebug, args)
|
#define debug(args...) printMsg(lvlDebug, args)
|
||||||
#define vomit(args...) printMsg(lvlVomit, args)
|
#define vomit(args...) printMsg(lvlVomit, args)
|
||||||
|
|
||||||
|
/* if verbosity >= lvlWarn, print a message with a yellow 'warning:' prefix. */
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
inline void warn(const std::string & fs, const Args & ... args)
|
inline void warn(const std::string & fs, const Args & ... args)
|
||||||
{
|
{
|
||||||
|
|
|
@ -52,7 +52,10 @@ size_t threshold = 256 * 1024 * 1024;
|
||||||
|
|
||||||
static void warnLargeDump()
|
static void warnLargeDump()
|
||||||
{
|
{
|
||||||
printError("warning: dumping very large path (> 256 MiB); this may run out of memory");
|
logWarning(ErrorInfo {
|
||||||
|
.name = "Large path",
|
||||||
|
.description = "dumping very large path (> 256 MiB); this may run out of memory"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ libutil-tests_INSTALL_DIR :=
|
||||||
|
|
||||||
libutil-tests_SOURCES := $(wildcard $(d)/*.cc)
|
libutil-tests_SOURCES := $(wildcard $(d)/*.cc)
|
||||||
|
|
||||||
libutil-tests_CXXFLAGS += -I src/libutil
|
libutil-tests_CXXFLAGS += -I src/libutil -I src/libexpr
|
||||||
|
|
||||||
libutil-tests_LIBS = libutil
|
libutil-tests_LIBS = libutil
|
||||||
|
|
||||||
|
|
255
src/libutil/tests/logging.cc
Normal file
255
src/libutil/tests/logging.cc
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
#include "logging.hh"
|
||||||
|
#include "nixexpr.hh"
|
||||||
|
#include "util.hh"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* logEI
|
||||||
|
* --------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
TEST(logEI, catpuresBasicProperties) {
|
||||||
|
|
||||||
|
MakeError(TestError, Error);
|
||||||
|
ErrorInfo::programName = std::optional("error-unit-test");
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw TestError("an error for testing purposes");
|
||||||
|
} catch (Error &e) {
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
logger->logEI(e.info());
|
||||||
|
auto str = testing::internal::GetCapturedStderr();
|
||||||
|
|
||||||
|
ASSERT_STREQ(str.c_str(),"\x1B[31;1merror:\x1B[0m\x1B[34;1m --- TestError ------------------------------------ error-unit-test\x1B[0m\nan error for testing purposes\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(logEI, appendingHintsToPreviousError) {
|
||||||
|
|
||||||
|
MakeError(TestError, Error);
|
||||||
|
ErrorInfo::programName = std::optional("error-unit-test");
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto e = Error("initial error");
|
||||||
|
throw TestError(e.info());
|
||||||
|
} catch (Error &e) {
|
||||||
|
ErrorInfo ei = e.info();
|
||||||
|
ei.hint = hintfmt("%s; subsequent error message.", normaltxt(e.info().hint ? e.info().hint->str() : ""));
|
||||||
|
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
logger->logEI(ei);
|
||||||
|
auto str = testing::internal::GetCapturedStderr();
|
||||||
|
|
||||||
|
ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- TestError ------------------------------------ error-unit-test\x1B[0m\n\x1B[33;1m\x1B[0minitial error\x1B[0m; subsequent error message.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(logEI, picksUpSysErrorExitCode) {
|
||||||
|
|
||||||
|
MakeError(TestError, Error);
|
||||||
|
ErrorInfo::programName = std::optional("error-unit-test");
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto x = readFile(-1);
|
||||||
|
}
|
||||||
|
catch (SysError &e) {
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
logError(e.info());
|
||||||
|
auto str = testing::internal::GetCapturedStderr();
|
||||||
|
|
||||||
|
ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- SysError ------------------------------------- error-unit-test\x1B[0m\n\x1B[33;1m\x1B[0mstatting file\x1B[0m: \x1B[33;1mBad file descriptor\x1B[0m\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(logEI, loggingErrorOnInfoLevel) {
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
|
||||||
|
logger->logEI({ .level = lvlInfo,
|
||||||
|
.name = "Info name",
|
||||||
|
.description = "Info description",
|
||||||
|
});
|
||||||
|
|
||||||
|
auto str = testing::internal::GetCapturedStderr();
|
||||||
|
ASSERT_STREQ(str.c_str(), "\x1B[32;1minfo:\x1B[0m\x1B[34;1m --- Info name ------------------------------------- error-unit-test\x1B[0m\nInfo description\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(logEI, loggingErrorOnTalkativeLevel) {
|
||||||
|
verbosity = lvlTalkative;
|
||||||
|
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
|
||||||
|
logger->logEI({ .level = lvlTalkative,
|
||||||
|
.name = "Talkative name",
|
||||||
|
.description = "Talkative description",
|
||||||
|
});
|
||||||
|
|
||||||
|
auto str = testing::internal::GetCapturedStderr();
|
||||||
|
ASSERT_STREQ(str.c_str(), "\x1B[32;1mtalk:\x1B[0m\x1B[34;1m --- Talkative name -------------------------------- error-unit-test\x1B[0m\nTalkative description\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(logEI, loggingErrorOnChattyLevel) {
|
||||||
|
verbosity = lvlChatty;
|
||||||
|
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
|
||||||
|
logger->logEI({ .level = lvlChatty,
|
||||||
|
.name = "Chatty name",
|
||||||
|
.description = "Talkative description",
|
||||||
|
});
|
||||||
|
|
||||||
|
auto str = testing::internal::GetCapturedStderr();
|
||||||
|
ASSERT_STREQ(str.c_str(), "\x1B[32;1mchat:\x1B[0m\x1B[34;1m --- Chatty name ----------------------------------- error-unit-test\x1B[0m\nTalkative description\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(logEI, loggingErrorOnDebugLevel) {
|
||||||
|
verbosity = lvlDebug;
|
||||||
|
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
|
||||||
|
logger->logEI({ .level = lvlDebug,
|
||||||
|
.name = "Debug name",
|
||||||
|
.description = "Debug description",
|
||||||
|
});
|
||||||
|
|
||||||
|
auto str = testing::internal::GetCapturedStderr();
|
||||||
|
ASSERT_STREQ(str.c_str(), "\x1B[33;1mdebug:\x1B[0m\x1B[34;1m --- Debug name ----------------------------------- error-unit-test\x1B[0m\nDebug description\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(logEI, loggingErrorOnVomitLevel) {
|
||||||
|
verbosity = lvlVomit;
|
||||||
|
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
|
||||||
|
logger->logEI({ .level = lvlVomit,
|
||||||
|
.name = "Vomit name",
|
||||||
|
.description = "Vomit description",
|
||||||
|
});
|
||||||
|
|
||||||
|
auto str = testing::internal::GetCapturedStderr();
|
||||||
|
ASSERT_STREQ(str.c_str(), "\x1B[32;1mvomit:\x1B[0m\x1B[34;1m --- Vomit name ----------------------------------- error-unit-test\x1B[0m\nVomit description\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* logError
|
||||||
|
* --------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
TEST(logError, logErrorWithoutHintOrCode) {
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
|
||||||
|
logError({
|
||||||
|
.name = "name",
|
||||||
|
.description = "error description",
|
||||||
|
});
|
||||||
|
|
||||||
|
auto str = testing::internal::GetCapturedStderr();
|
||||||
|
ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- name ----------------------------------------- error-unit-test\x1B[0m\nerror description\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(logError, logErrorWithPreviousAndNextLinesOfCode) {
|
||||||
|
SymbolTable testTable;
|
||||||
|
auto problem_file = testTable.create("myfile.nix");
|
||||||
|
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
|
||||||
|
logError({
|
||||||
|
.name = "error name",
|
||||||
|
.description = "error with code lines",
|
||||||
|
.hint = hintfmt("this hint has %1% templated %2%!!",
|
||||||
|
"yellow",
|
||||||
|
"values"),
|
||||||
|
.nixCode = NixCode {
|
||||||
|
.errPos = Pos(problem_file, 40, 13),
|
||||||
|
.prevLineOfCode = "previous line of code",
|
||||||
|
.errLineOfCode = "this is the problem line of code",
|
||||||
|
.nextLineOfCode = "next line of code",
|
||||||
|
}});
|
||||||
|
|
||||||
|
|
||||||
|
auto str = testing::internal::GetCapturedStderr();
|
||||||
|
ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- error name ----------------------------------- error-unit-test\x1B[0m\nin file: \x1B[34;1mmyfile.nix (40:13)\x1B[0m\n\nerror with code lines\n\n 39| previous line of code\n 40| this is the problem line of code\n | \x1B[31;1m^\x1B[0m\n 41| next line of code\n\nthis hint has \x1B[33;1myellow\x1B[0m templated \x1B[33;1mvalues\x1B[0m!!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(logError, logErrorWithoutLinesOfCode) {
|
||||||
|
SymbolTable testTable;
|
||||||
|
auto problem_file = testTable.create("myfile.nix");
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
|
||||||
|
logError({
|
||||||
|
.name = "error name",
|
||||||
|
.description = "error without any code lines.",
|
||||||
|
.hint = hintfmt("this hint has %1% templated %2%!!",
|
||||||
|
"yellow",
|
||||||
|
"values"),
|
||||||
|
.nixCode = NixCode {
|
||||||
|
.errPos = Pos(problem_file, 40, 13)
|
||||||
|
}});
|
||||||
|
|
||||||
|
auto str = testing::internal::GetCapturedStderr();
|
||||||
|
ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- error name ----------------------------------- error-unit-test\x1B[0m\nin file: \x1B[34;1mmyfile.nix (40:13)\x1B[0m\n\nerror without any code lines.\n\nthis hint has \x1B[33;1myellow\x1B[0m templated \x1B[33;1mvalues\x1B[0m!!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(logError, logErrorWithOnlyHintAndName) {
|
||||||
|
SymbolTable testTable;
|
||||||
|
auto problem_file = testTable.create("myfile.nix");
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
|
||||||
|
logError({
|
||||||
|
.name = "error name",
|
||||||
|
.hint = hintfmt("hint %1%", "only"),
|
||||||
|
.nixCode = NixCode {
|
||||||
|
.errPos = Pos(problem_file, 40, 13)
|
||||||
|
}});
|
||||||
|
|
||||||
|
auto str = testing::internal::GetCapturedStderr();
|
||||||
|
ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- error name ----------------------------------- error-unit-test\x1B[0m\nin file: \x1B[34;1mmyfile.nix (40:13)\x1B[0m\n\nhint \x1B[33;1monly\x1B[0m\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* logWarning
|
||||||
|
* --------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
TEST(logWarning, logWarningWithNameDescriptionAndHint) {
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
|
||||||
|
logWarning({
|
||||||
|
.name = "name",
|
||||||
|
.description = "error description",
|
||||||
|
.hint = hintfmt("there was a %1%", "warning"),
|
||||||
|
});
|
||||||
|
|
||||||
|
auto str = testing::internal::GetCapturedStderr();
|
||||||
|
ASSERT_STREQ(str.c_str(), "\x1B[33;1mwarning:\x1B[0m\x1B[34;1m --- name --------------------------------------- error-unit-test\x1B[0m\nerror description\n\nthere was a \x1B[33;1mwarning\x1B[0m\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(logWarning, logWarningWithFileLineNumAndCode) {
|
||||||
|
|
||||||
|
SymbolTable testTable;
|
||||||
|
auto problem_file = testTable.create("myfile.nix");
|
||||||
|
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
|
||||||
|
logWarning({
|
||||||
|
.name = "warning name",
|
||||||
|
.description = "warning description",
|
||||||
|
.hint = hintfmt("this hint has %1% templated %2%!!",
|
||||||
|
"yellow",
|
||||||
|
"values"),
|
||||||
|
.nixCode = NixCode {
|
||||||
|
.errPos = Pos(problem_file, 40, 13),
|
||||||
|
.prevLineOfCode = std::nullopt,
|
||||||
|
.errLineOfCode = "this is the problem line of code",
|
||||||
|
.nextLineOfCode = std::nullopt
|
||||||
|
}});
|
||||||
|
|
||||||
|
|
||||||
|
auto str = testing::internal::GetCapturedStderr();
|
||||||
|
ASSERT_STREQ(str.c_str(), "\x1B[33;1mwarning:\x1B[0m\x1B[34;1m --- warning name ------------------------------- error-unit-test\x1B[0m\nin file: \x1B[34;1mmyfile.nix (40:13)\x1B[0m\n\nwarning description\n\n 40| this is the problem line of code\n | \x1B[31;1m^\x1B[0m\n\nthis hint has \x1B[33;1myellow\x1B[0m templated \x1B[33;1mvalues\x1B[0m!!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,157 +3,24 @@
|
||||||
|
|
||||||
#include "ref.hh"
|
#include "ref.hh"
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <memory>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
#include <boost/format.hpp>
|
|
||||||
|
|
||||||
/* Before 4.7, gcc's std::exception uses empty throw() specifiers for
|
|
||||||
* its (virtual) destructor and what() in c++11 mode, in violation of spec
|
|
||||||
*/
|
|
||||||
#ifdef __GNUC__
|
|
||||||
#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7)
|
|
||||||
#define EXCEPTION_NEEDS_THROW_SPEC
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
/* Inherit some names from other namespaces for convenience. */
|
|
||||||
using std::string;
|
|
||||||
using std::list;
|
using std::list;
|
||||||
using std::set;
|
using std::set;
|
||||||
using std::vector;
|
using std::vector;
|
||||||
using boost::format;
|
using std::string;
|
||||||
|
|
||||||
|
|
||||||
/* A variadic template that does nothing. Useful to call a function
|
|
||||||
for all variadic arguments but ignoring the result. */
|
|
||||||
struct nop { template<typename... T> nop(T...) {} };
|
|
||||||
|
|
||||||
|
|
||||||
struct FormatOrString
|
|
||||||
{
|
|
||||||
string s;
|
|
||||||
FormatOrString(const string & s) : s(s) { };
|
|
||||||
template<class F>
|
|
||||||
FormatOrString(const F & f) : s(f.str()) { };
|
|
||||||
FormatOrString(const char * s) : s(s) { };
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/* A helper for formatting strings. ‘fmt(format, a_0, ..., a_n)’ is
|
|
||||||
equivalent to ‘boost::format(format) % a_0 % ... %
|
|
||||||
... a_n’. However, ‘fmt(s)’ is equivalent to ‘s’ (so no %-expansion
|
|
||||||
takes place). */
|
|
||||||
|
|
||||||
template<class F>
|
|
||||||
inline void formatHelper(F & f)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class F, typename T, typename... Args>
|
|
||||||
inline void formatHelper(F & f, const T & x, const Args & ... args)
|
|
||||||
{
|
|
||||||
formatHelper(f % x, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::string fmt(const std::string & s)
|
|
||||||
{
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::string fmt(const char * s)
|
|
||||||
{
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::string fmt(const FormatOrString & fs)
|
|
||||||
{
|
|
||||||
return fs.s;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
inline std::string fmt(const std::string & fs, const Args & ... args)
|
|
||||||
{
|
|
||||||
boost::format f(fs);
|
|
||||||
f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
|
|
||||||
formatHelper(f, args...);
|
|
||||||
return f.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* BaseError should generally not be caught, as it has Interrupted as
|
|
||||||
a subclass. Catch Error instead. */
|
|
||||||
class BaseError : public std::exception
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
string prefix_; // used for location traces etc.
|
|
||||||
string err;
|
|
||||||
public:
|
|
||||||
unsigned int status = 1; // exit status
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
BaseError(unsigned int status, const Args & ... args)
|
|
||||||
: err(fmt(args...))
|
|
||||||
, status(status)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
BaseError(const Args & ... args)
|
|
||||||
: err(fmt(args...))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef EXCEPTION_NEEDS_THROW_SPEC
|
|
||||||
~BaseError() throw () { };
|
|
||||||
const char * what() const throw () { return err.c_str(); }
|
|
||||||
#else
|
|
||||||
const char * what() const noexcept { return err.c_str(); }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const string & msg() const { return err; }
|
|
||||||
const string & prefix() const { return prefix_; }
|
|
||||||
BaseError & addPrefix(const FormatOrString & fs);
|
|
||||||
};
|
|
||||||
|
|
||||||
#define MakeError(newClass, superClass) \
|
|
||||||
class newClass : public superClass \
|
|
||||||
{ \
|
|
||||||
public: \
|
|
||||||
using superClass::superClass; \
|
|
||||||
}
|
|
||||||
|
|
||||||
MakeError(Error, BaseError);
|
|
||||||
|
|
||||||
class SysError : public Error
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
int errNo;
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
SysError(const Args & ... args)
|
|
||||||
: Error(addErrno(fmt(args...)))
|
|
||||||
{ }
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
std::string addErrno(const std::string & s);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
typedef list<string> Strings;
|
typedef list<string> Strings;
|
||||||
typedef set<string> StringSet;
|
typedef set<string> StringSet;
|
||||||
typedef std::map<std::string, std::string> StringMap;
|
typedef std::map<string, string> StringMap;
|
||||||
|
|
||||||
|
|
||||||
/* Paths are just strings. */
|
/* Paths are just strings. */
|
||||||
|
|
||||||
typedef string Path;
|
typedef string Path;
|
||||||
typedef list<Path> Paths;
|
typedef list<Path> Paths;
|
||||||
typedef set<Path> PathSet;
|
typedef set<Path> PathSet;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "types.hh"
|
#include "error.hh"
|
||||||
|
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
|
|
|
@ -40,24 +40,6 @@ extern char * * environ;
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
const std::string nativeSystem = SYSTEM;
|
|
||||||
|
|
||||||
|
|
||||||
BaseError & BaseError::addPrefix(const FormatOrString & fs)
|
|
||||||
{
|
|
||||||
prefix_ = fs.s + prefix_;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::string SysError::addErrno(const std::string & s)
|
|
||||||
{
|
|
||||||
errNo = errno;
|
|
||||||
return s + ": " + strerror(errNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::optional<std::string> getEnv(const std::string & key)
|
std::optional<std::string> getEnv(const std::string & key)
|
||||||
{
|
{
|
||||||
char * value = getenv(key.c_str());
|
char * value = getenv(key.c_str());
|
||||||
|
@ -129,7 +111,7 @@ Path canonPath(const Path & path, bool resolveSymlinks)
|
||||||
string s;
|
string s;
|
||||||
|
|
||||||
if (path[0] != '/')
|
if (path[0] != '/')
|
||||||
throw Error(format("not an absolute path: '%1%'") % path);
|
throw Error("not an absolute path: '%1%'", path);
|
||||||
|
|
||||||
string::const_iterator i = path.begin(), end = path.end();
|
string::const_iterator i = path.begin(), end = path.end();
|
||||||
string temp;
|
string temp;
|
||||||
|
@ -165,7 +147,7 @@ Path canonPath(const Path & path, bool resolveSymlinks)
|
||||||
the symlink target might contain new symlinks). */
|
the symlink target might contain new symlinks). */
|
||||||
if (resolveSymlinks && isLink(s)) {
|
if (resolveSymlinks && isLink(s)) {
|
||||||
if (++followCount >= maxFollow)
|
if (++followCount >= maxFollow)
|
||||||
throw Error(format("infinite symlink recursion in path '%1%'") % path);
|
throw Error("infinite symlink recursion in path '%1%'", path);
|
||||||
temp = absPath(readLink(s), dirOf(s))
|
temp = absPath(readLink(s), dirOf(s))
|
||||||
+ string(i, end);
|
+ string(i, end);
|
||||||
i = temp.begin(); /* restart */
|
i = temp.begin(); /* restart */
|
||||||
|
@ -226,7 +208,7 @@ struct stat lstat(const Path & path)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(path.c_str(), &st))
|
if (lstat(path.c_str(), &st))
|
||||||
throw SysError(format("getting status of '%1%'") % path);
|
throw SysError("getting status of '%1%'", path);
|
||||||
return st;
|
return st;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +220,7 @@ bool pathExists(const Path & path)
|
||||||
res = lstat(path.c_str(), &st);
|
res = lstat(path.c_str(), &st);
|
||||||
if (!res) return true;
|
if (!res) return true;
|
||||||
if (errno != ENOENT && errno != ENOTDIR)
|
if (errno != ENOENT && errno != ENOTDIR)
|
||||||
throw SysError(format("getting status of %1%") % path);
|
throw SysError("getting status of %1%", path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,7 +268,7 @@ DirEntries readDirectory(DIR *dir, const Path & path)
|
||||||
#endif
|
#endif
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (errno) throw SysError(format("reading directory '%1%'") % path);
|
if (errno) throw SysError("reading directory '%1%'", path);
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
@ -294,7 +276,7 @@ DirEntries readDirectory(DIR *dir, const Path & path)
|
||||||
DirEntries readDirectory(const Path & path)
|
DirEntries readDirectory(const Path & path)
|
||||||
{
|
{
|
||||||
AutoCloseDir dir(opendir(path.c_str()));
|
AutoCloseDir dir(opendir(path.c_str()));
|
||||||
if (!dir) throw SysError(format("opening directory '%1%'") % path);
|
if (!dir) throw SysError("opening directory '%1%'", path);
|
||||||
|
|
||||||
return readDirectory(dir.get(), path);
|
return readDirectory(dir.get(), path);
|
||||||
}
|
}
|
||||||
|
@ -324,7 +306,7 @@ string readFile(const Path & path)
|
||||||
{
|
{
|
||||||
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
||||||
if (!fd)
|
if (!fd)
|
||||||
throw SysError(format("opening file '%1%'") % path);
|
throw SysError("opening file '%1%'", path);
|
||||||
return readFile(fd.get());
|
return readFile(fd.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,7 +314,8 @@ string readFile(const Path & path)
|
||||||
void readFile(const Path & path, Sink & sink)
|
void readFile(const Path & path, Sink & sink)
|
||||||
{
|
{
|
||||||
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
||||||
if (!fd) throw SysError("opening file '%s'", path);
|
if (!fd)
|
||||||
|
throw SysError("opening file '%s'", path);
|
||||||
drainFD(fd.get(), sink);
|
drainFD(fd.get(), sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,7 +324,7 @@ void writeFile(const Path & path, const string & s, mode_t mode)
|
||||||
{
|
{
|
||||||
AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode);
|
AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode);
|
||||||
if (!fd)
|
if (!fd)
|
||||||
throw SysError(format("opening file '%1%'") % path);
|
throw SysError("opening file '%1%'", path);
|
||||||
writeFull(fd.get(), s);
|
writeFull(fd.get(), s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,7 +333,7 @@ void writeFile(const Path & path, Source & source, mode_t mode)
|
||||||
{
|
{
|
||||||
AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode);
|
AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode);
|
||||||
if (!fd)
|
if (!fd)
|
||||||
throw SysError(format("opening file '%1%'") % path);
|
throw SysError("opening file '%1%'", path);
|
||||||
|
|
||||||
std::vector<unsigned char> buf(64 * 1024);
|
std::vector<unsigned char> buf(64 * 1024);
|
||||||
|
|
||||||
|
@ -400,7 +383,7 @@ static void _deletePath(int parentfd, const Path & path, unsigned long long & by
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
|
if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
|
||||||
if (errno == ENOENT) return;
|
if (errno == ENOENT) return;
|
||||||
throw SysError(format("getting status of '%1%'") % path);
|
throw SysError("getting status of '%1%'", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!S_ISDIR(st.st_mode) && st.st_nlink == 1)
|
if (!S_ISDIR(st.st_mode) && st.st_nlink == 1)
|
||||||
|
@ -411,15 +394,15 @@ static void _deletePath(int parentfd, const Path & path, unsigned long long & by
|
||||||
const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR;
|
const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR;
|
||||||
if ((st.st_mode & PERM_MASK) != PERM_MASK) {
|
if ((st.st_mode & PERM_MASK) != PERM_MASK) {
|
||||||
if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1)
|
if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1)
|
||||||
throw SysError(format("chmod '%1%'") % path);
|
throw SysError("chmod '%1%'", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
int fd = openat(parentfd, path.c_str(), O_RDONLY);
|
int fd = openat(parentfd, path.c_str(), O_RDONLY);
|
||||||
if (!fd)
|
if (!fd)
|
||||||
throw SysError(format("opening directory '%1%'") % path);
|
throw SysError("opening directory '%1%'", path);
|
||||||
AutoCloseDir dir(fdopendir(fd));
|
AutoCloseDir dir(fdopendir(fd));
|
||||||
if (!dir)
|
if (!dir)
|
||||||
throw SysError(format("opening directory '%1%'") % path);
|
throw SysError("opening directory '%1%'", path);
|
||||||
for (auto & i : readDirectory(dir.get(), path))
|
for (auto & i : readDirectory(dir.get(), path))
|
||||||
_deletePath(dirfd(dir.get()), path + "/" + i.name, bytesFreed);
|
_deletePath(dirfd(dir.get()), path + "/" + i.name, bytesFreed);
|
||||||
}
|
}
|
||||||
|
@ -427,7 +410,7 @@ static void _deletePath(int parentfd, const Path & path, unsigned long long & by
|
||||||
int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0;
|
int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0;
|
||||||
if (unlinkat(parentfd, name.c_str(), flags) == -1) {
|
if (unlinkat(parentfd, name.c_str(), flags) == -1) {
|
||||||
if (errno == ENOENT) return;
|
if (errno == ENOENT) return;
|
||||||
throw SysError(format("cannot unlink '%1%'") % path);
|
throw SysError("cannot unlink '%1%'", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,7 +426,7 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed)
|
||||||
// for backwards compatibility.
|
// for backwards compatibility.
|
||||||
if (errno == ENOENT) return;
|
if (errno == ENOENT) return;
|
||||||
|
|
||||||
throw SysError(format("opening directory '%1%'") % path);
|
throw SysError("opening directory '%1%'", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
_deletePath(dirfd.get(), path, bytesFreed);
|
_deletePath(dirfd.get(), path, bytesFreed);
|
||||||
|
@ -497,12 +480,12 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix,
|
||||||
"wheel", then "tar" will fail to unpack archives that
|
"wheel", then "tar" will fail to unpack archives that
|
||||||
have the setgid bit set on directories. */
|
have the setgid bit set on directories. */
|
||||||
if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0)
|
if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0)
|
||||||
throw SysError(format("setting group of directory '%1%'") % tmpDir);
|
throw SysError("setting group of directory '%1%'", tmpDir);
|
||||||
#endif
|
#endif
|
||||||
return tmpDir;
|
return tmpDir;
|
||||||
}
|
}
|
||||||
if (errno != EEXIST)
|
if (errno != EEXIST)
|
||||||
throw SysError(format("creating directory '%1%'") % tmpDir);
|
throw SysError("creating directory '%1%'", tmpDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -584,15 +567,15 @@ Paths createDirs(const Path & path)
|
||||||
if (lstat(path.c_str(), &st) == -1) {
|
if (lstat(path.c_str(), &st) == -1) {
|
||||||
created = createDirs(dirOf(path));
|
created = createDirs(dirOf(path));
|
||||||
if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST)
|
if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST)
|
||||||
throw SysError(format("creating directory '%1%'") % path);
|
throw SysError("creating directory '%1%'", path);
|
||||||
st = lstat(path);
|
st = lstat(path);
|
||||||
created.push_back(path);
|
created.push_back(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1)
|
if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1)
|
||||||
throw SysError(format("statting symlink '%1%'") % path);
|
throw SysError("statting symlink '%1%'", path);
|
||||||
|
|
||||||
if (!S_ISDIR(st.st_mode)) throw Error(format("'%1%' is not a directory") % path);
|
if (!S_ISDIR(st.st_mode)) throw Error("'%1%' is not a directory", path);
|
||||||
|
|
||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
|
@ -601,7 +584,7 @@ Paths createDirs(const Path & path)
|
||||||
void createSymlink(const Path & target, const Path & link)
|
void createSymlink(const Path & target, const Path & link)
|
||||||
{
|
{
|
||||||
if (symlink(target.c_str(), link.c_str()))
|
if (symlink(target.c_str(), link.c_str()))
|
||||||
throw SysError(format("creating symlink from '%1%' to '%2%'") % link % target);
|
throw SysError("creating symlink from '%1%' to '%2%'", link, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -618,7 +601,7 @@ void replaceSymlink(const Path & target, const Path & link)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rename(tmp.c_str(), link.c_str()) != 0)
|
if (rename(tmp.c_str(), link.c_str()) != 0)
|
||||||
throw SysError(format("renaming '%1%' to '%2%'") % tmp % link);
|
throw SysError("renaming '%1%' to '%2%'", tmp, link);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -723,7 +706,7 @@ AutoDelete::~AutoDelete()
|
||||||
deletePath(path);
|
deletePath(path);
|
||||||
else {
|
else {
|
||||||
if (remove(path.c_str()) == -1)
|
if (remove(path.c_str()) == -1)
|
||||||
throw SysError(format("cannot unlink '%1%'") % path);
|
throw SysError("cannot unlink '%1%'", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
@ -789,7 +772,7 @@ void AutoCloseFD::close()
|
||||||
if (fd != -1) {
|
if (fd != -1) {
|
||||||
if (::close(fd) == -1)
|
if (::close(fd) == -1)
|
||||||
/* This should never happen. */
|
/* This should never happen. */
|
||||||
throw SysError(format("closing file descriptor %1%") % fd);
|
throw SysError("closing file descriptor %1%", fd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -862,7 +845,7 @@ int Pid::kill()
|
||||||
{
|
{
|
||||||
assert(pid != -1);
|
assert(pid != -1);
|
||||||
|
|
||||||
debug(format("killing process %1%") % pid);
|
debug("killing process %1%", pid);
|
||||||
|
|
||||||
/* Send the requested signal to the child. If it has its own
|
/* Send the requested signal to the child. If it has its own
|
||||||
process group, send the signal to every process in the child
|
process group, send the signal to every process in the child
|
||||||
|
@ -874,7 +857,7 @@ int Pid::kill()
|
||||||
#if __FreeBSD__ || __APPLE__
|
#if __FreeBSD__ || __APPLE__
|
||||||
if (errno != EPERM || ::kill(pid, 0) != 0)
|
if (errno != EPERM || ::kill(pid, 0) != 0)
|
||||||
#endif
|
#endif
|
||||||
printError((SysError("killing process %d", pid).msg()));
|
logError(SysError("killing process %d", pid).info());
|
||||||
}
|
}
|
||||||
|
|
||||||
return wait();
|
return wait();
|
||||||
|
@ -920,7 +903,7 @@ pid_t Pid::release()
|
||||||
|
|
||||||
void killUser(uid_t uid)
|
void killUser(uid_t uid)
|
||||||
{
|
{
|
||||||
debug(format("killing all processes running under uid '%1%'") % uid);
|
debug("killing all processes running under uid '%1%'", uid);
|
||||||
|
|
||||||
assert(uid != 0); /* just to be safe... */
|
assert(uid != 0); /* just to be safe... */
|
||||||
|
|
||||||
|
@ -949,7 +932,7 @@ void killUser(uid_t uid)
|
||||||
#endif
|
#endif
|
||||||
if (errno == ESRCH) break; /* no more processes */
|
if (errno == ESRCH) break; /* no more processes */
|
||||||
if (errno != EINTR)
|
if (errno != EINTR)
|
||||||
throw SysError(format("cannot kill processes for uid '%1%'") % uid);
|
throw SysError("cannot kill processes for uid '%1%'", uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
_exit(0);
|
_exit(0);
|
||||||
|
@ -957,7 +940,7 @@ void killUser(uid_t uid)
|
||||||
|
|
||||||
int status = pid.wait();
|
int status = pid.wait();
|
||||||
if (status != 0)
|
if (status != 0)
|
||||||
throw Error(format("cannot kill processes for uid '%1%': %2%") % uid % statusToString(status));
|
throw Error("cannot kill processes for uid '%1%': %2%", uid, statusToString(status));
|
||||||
|
|
||||||
/* !!! We should really do some check to make sure that there are
|
/* !!! We should really do some check to make sure that there are
|
||||||
no processes left running under `uid', but there is no portable
|
no processes left running under `uid', but there is no portable
|
||||||
|
@ -1351,7 +1334,7 @@ void ignoreException()
|
||||||
try {
|
try {
|
||||||
throw;
|
throw;
|
||||||
} catch (std::exception & e) {
|
} catch (std::exception & e) {
|
||||||
printError(format("error (ignored): %1%") % e.what());
|
printError("error (ignored): %1%", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1464,17 +1447,6 @@ string base64Decode(std::string_view s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void callFailure(const std::function<void(std::exception_ptr exc)> & failure, std::exception_ptr exc)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
failure(exc);
|
|
||||||
} catch (std::exception & e) {
|
|
||||||
printError(format("uncaught exception: %s") % e.what());
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}};
|
static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
|
#include "error.hh"
|
||||||
#include "logging.hh"
|
#include "logging.hh"
|
||||||
#include "ansicolor.hh"
|
#include "ansicolor.hh"
|
||||||
|
|
||||||
|
|
|
@ -368,7 +368,12 @@ static void _main(int argc, char * * argv)
|
||||||
shell = drv->queryOutPath() + "/bin/bash";
|
shell = drv->queryOutPath() + "/bin/bash";
|
||||||
|
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
printError("warning: %s; will use bash from your environment", e.what());
|
logWarning(
|
||||||
|
ErrorInfo {
|
||||||
|
.name = "bashInteractive",
|
||||||
|
.hint = hintfmt("%s; will use bash from your environment",
|
||||||
|
(e.info().hint ? e.info().hint->str() : ""))
|
||||||
|
});
|
||||||
shell = "bash";
|
shell = "bash";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ static void writeChannels()
|
||||||
{
|
{
|
||||||
auto channelsFD = AutoCloseFD{open(channelsList.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0644)};
|
auto channelsFD = AutoCloseFD{open(channelsList.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0644)};
|
||||||
if (!channelsFD)
|
if (!channelsFD)
|
||||||
throw SysError(format("opening '%1%' for writing") % channelsList);
|
throw SysError("opening '%1%' for writing", channelsList);
|
||||||
for (const auto & channel : channels)
|
for (const auto & channel : channels)
|
||||||
writeFull(channelsFD.get(), channel.second + " " + channel.first + "\n");
|
writeFull(channelsFD.get(), channel.second + " " + channel.first + "\n");
|
||||||
}
|
}
|
||||||
|
@ -47,9 +47,9 @@ static void writeChannels()
|
||||||
static void addChannel(const string & url, const string & name)
|
static void addChannel(const string & url, const string & name)
|
||||||
{
|
{
|
||||||
if (!regex_search(url, std::regex("^(file|http|https)://")))
|
if (!regex_search(url, std::regex("^(file|http|https)://")))
|
||||||
throw Error(format("invalid channel URL '%1%'") % url);
|
throw Error("invalid channel URL '%1%'", url);
|
||||||
if (!regex_search(name, std::regex("^[a-zA-Z0-9_][a-zA-Z0-9_\\.-]*$")))
|
if (!regex_search(name, std::regex("^[a-zA-Z0-9_][a-zA-Z0-9_\\.-]*$")))
|
||||||
throw Error(format("invalid channel identifier '%1%'") % name);
|
throw Error("invalid channel identifier '%1%'", name);
|
||||||
readChannels();
|
readChannels();
|
||||||
channels[name] = url;
|
channels[name] = url;
|
||||||
writeChannels();
|
writeChannels();
|
||||||
|
@ -137,9 +137,9 @@ static void update(const StringSet & channelNames)
|
||||||
if (S_ISLNK(st.st_mode))
|
if (S_ISLNK(st.st_mode))
|
||||||
// old-skool ~/.nix-defexpr
|
// old-skool ~/.nix-defexpr
|
||||||
if (unlink(nixDefExpr.c_str()) == -1)
|
if (unlink(nixDefExpr.c_str()) == -1)
|
||||||
throw SysError(format("unlinking %1%") % nixDefExpr);
|
throw SysError("unlinking %1%", nixDefExpr);
|
||||||
} else if (errno != ENOENT) {
|
} else if (errno != ENOENT) {
|
||||||
throw SysError(format("getting status of %1%") % nixDefExpr);
|
throw SysError("getting status of %1%", nixDefExpr);
|
||||||
}
|
}
|
||||||
createDirs(nixDefExpr);
|
createDirs(nixDefExpr);
|
||||||
auto channelLink = nixDefExpr + "/channels";
|
auto channelLink = nixDefExpr + "/channels";
|
||||||
|
|
|
@ -36,7 +36,7 @@ using namespace nix::daemon;
|
||||||
#define SPLICE_F_MOVE 0
|
#define SPLICE_F_MOVE 0
|
||||||
static ssize_t splice(int fd_in, void *off_in, int fd_out, void *off_out, size_t len, unsigned int flags)
|
static ssize_t splice(int fd_in, void *off_in, int fd_out, void *off_out, size_t len, unsigned int flags)
|
||||||
{
|
{
|
||||||
/* We ignore most parameters, we just have them for conformance with the linux syscall */
|
// We ignore most parameters, we just have them for conformance with the linux syscall
|
||||||
std::vector<char> buf(8192);
|
std::vector<char> buf(8192);
|
||||||
auto read_count = read(fd_in, buf.data(), buf.size());
|
auto read_count = read(fd_in, buf.data(), buf.size());
|
||||||
if (read_count == -1)
|
if (read_count == -1)
|
||||||
|
@ -57,7 +57,7 @@ static void sigChldHandler(int sigNo)
|
||||||
{
|
{
|
||||||
// Ensure we don't modify errno of whatever we've interrupted
|
// Ensure we don't modify errno of whatever we've interrupted
|
||||||
auto saved_errno = errno;
|
auto saved_errno = errno;
|
||||||
/* Reap all dead children. */
|
// Reap all dead children.
|
||||||
while (waitpid(-1, 0, WNOHANG) > 0) ;
|
while (waitpid(-1, 0, WNOHANG) > 0) ;
|
||||||
errno = saved_errno;
|
errno = saved_errno;
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ struct PeerInfo
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/* Get the identity of the caller, if possible. */
|
// Get the identity of the caller, if possible.
|
||||||
static PeerInfo getPeerInfo(int remote)
|
static PeerInfo getPeerInfo(int remote)
|
||||||
{
|
{
|
||||||
PeerInfo peer = { false, 0, false, 0, false, 0 };
|
PeerInfo peer = { false, 0, false, 0, false, 0 };
|
||||||
|
@ -154,13 +154,12 @@ static void daemonLoop(char * * argv)
|
||||||
if (chdir("/") == -1)
|
if (chdir("/") == -1)
|
||||||
throw SysError("cannot change current directory");
|
throw SysError("cannot change current directory");
|
||||||
|
|
||||||
/* Get rid of children automatically; don't let them become
|
// Get rid of children automatically; don't let them become zombies.
|
||||||
zombies. */
|
|
||||||
setSigChldAction(true);
|
setSigChldAction(true);
|
||||||
|
|
||||||
AutoCloseFD fdSocket;
|
AutoCloseFD fdSocket;
|
||||||
|
|
||||||
/* Handle socket-based activation by systemd. */
|
// Handle socket-based activation by systemd.
|
||||||
auto listenFds = getEnv("LISTEN_FDS");
|
auto listenFds = getEnv("LISTEN_FDS");
|
||||||
if (listenFds) {
|
if (listenFds) {
|
||||||
if (getEnv("LISTEN_PID") != std::to_string(getpid()) || listenFds != "1")
|
if (getEnv("LISTEN_PID") != std::to_string(getpid()) || listenFds != "1")
|
||||||
|
@ -169,17 +168,17 @@ static void daemonLoop(char * * argv)
|
||||||
closeOnExec(fdSocket.get());
|
closeOnExec(fdSocket.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Otherwise, create and bind to a Unix domain socket. */
|
// Otherwise, create and bind to a Unix domain socket.
|
||||||
else {
|
else {
|
||||||
createDirs(dirOf(settings.nixDaemonSocketFile));
|
createDirs(dirOf(settings.nixDaemonSocketFile));
|
||||||
fdSocket = createUnixDomainSocket(settings.nixDaemonSocketFile, 0666);
|
fdSocket = createUnixDomainSocket(settings.nixDaemonSocketFile, 0666);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Loop accepting connections. */
|
// Loop accepting connections.
|
||||||
while (1) {
|
while (1) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/* Accept a connection. */
|
// Accept a connection.
|
||||||
struct sockaddr_un remoteAddr;
|
struct sockaddr_un remoteAddr;
|
||||||
socklen_t remoteAddrLen = sizeof(remoteAddr);
|
socklen_t remoteAddrLen = sizeof(remoteAddr);
|
||||||
|
|
||||||
|
@ -209,13 +208,13 @@ static void daemonLoop(char * * argv)
|
||||||
trusted = Trusted;
|
trusted = Trusted;
|
||||||
|
|
||||||
if ((!trusted && !matchUser(user, group, allowedUsers)) || group == settings.buildUsersGroup)
|
if ((!trusted && !matchUser(user, group, allowedUsers)) || group == settings.buildUsersGroup)
|
||||||
throw Error(format("user '%1%' is not allowed to connect to the Nix daemon") % user);
|
throw Error("user '%1%' is not allowed to connect to the Nix daemon", user);
|
||||||
|
|
||||||
printInfo(format((string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : ""))
|
printInfo(format((string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : ""))
|
||||||
% (peer.pidKnown ? std::to_string(peer.pid) : "<unknown>")
|
% (peer.pidKnown ? std::to_string(peer.pid) : "<unknown>")
|
||||||
% (peer.uidKnown ? user : "<unknown>"));
|
% (peer.uidKnown ? user : "<unknown>"));
|
||||||
|
|
||||||
/* Fork a child to handle the connection. */
|
// Fork a child to handle the connection.
|
||||||
ProcessOptions options;
|
ProcessOptions options;
|
||||||
options.errorPrefix = "unexpected Nix daemon error: ";
|
options.errorPrefix = "unexpected Nix daemon error: ";
|
||||||
options.dieWithParent = false;
|
options.dieWithParent = false;
|
||||||
|
@ -224,20 +223,20 @@ static void daemonLoop(char * * argv)
|
||||||
startProcess([&]() {
|
startProcess([&]() {
|
||||||
fdSocket = -1;
|
fdSocket = -1;
|
||||||
|
|
||||||
/* Background the daemon. */
|
// Background the daemon.
|
||||||
if (setsid() == -1)
|
if (setsid() == -1)
|
||||||
throw SysError(format("creating a new session"));
|
throw SysError("creating a new session");
|
||||||
|
|
||||||
/* Restore normal handling of SIGCHLD. */
|
// Restore normal handling of SIGCHLD.
|
||||||
setSigChldAction(false);
|
setSigChldAction(false);
|
||||||
|
|
||||||
/* For debugging, stuff the pid into argv[1]. */
|
// For debugging, stuff the pid into argv[1].
|
||||||
if (peer.pidKnown && argv[1]) {
|
if (peer.pidKnown && argv[1]) {
|
||||||
string processName = std::to_string(peer.pid);
|
string processName = std::to_string(peer.pid);
|
||||||
strncpy(argv[1], processName.c_str(), strlen(argv[1]));
|
strncpy(argv[1], processName.c_str(), strlen(argv[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle the connection. */
|
// Handle the connection.
|
||||||
FdSource from(remote.get());
|
FdSource from(remote.get());
|
||||||
FdSink to(remote.get());
|
FdSink to(remote.get());
|
||||||
processConnection(openUncachedStore(), from, to, trusted, NotRecursive, user, peer.uid);
|
processConnection(openUncachedStore(), from, to, trusted, NotRecursive, user, peer.uid);
|
||||||
|
@ -247,8 +246,11 @@ static void daemonLoop(char * * argv)
|
||||||
|
|
||||||
} catch (Interrupted & e) {
|
} catch (Interrupted & e) {
|
||||||
return;
|
return;
|
||||||
} catch (Error & e) {
|
} catch (Error & error) {
|
||||||
printError(format("error processing connection: %1%") % e.msg());
|
ErrorInfo ei = error.info();
|
||||||
|
ei.hint = std::optional(hintfmt("error processing connection: %1%",
|
||||||
|
(error.info().hint.has_value() ? error.info().hint->str() : "")));
|
||||||
|
logError(ei);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,7 +263,7 @@ static int _main(int argc, char * * argv)
|
||||||
|
|
||||||
parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
|
parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
|
||||||
if (*arg == "--daemon")
|
if (*arg == "--daemon")
|
||||||
; /* ignored for backwards compatibility */
|
; // ignored for backwards compatibility
|
||||||
else if (*arg == "--help")
|
else if (*arg == "--help")
|
||||||
showManPage("nix-daemon");
|
showManPage("nix-daemon");
|
||||||
else if (*arg == "--version")
|
else if (*arg == "--version")
|
||||||
|
@ -276,7 +278,7 @@ static int _main(int argc, char * * argv)
|
||||||
|
|
||||||
if (stdio) {
|
if (stdio) {
|
||||||
if (getStoreType() == tDaemon) {
|
if (getStoreType() == tDaemon) {
|
||||||
/* Forward on this connection to the real daemon */
|
// Forward on this connection to the real daemon
|
||||||
auto socketPath = settings.nixDaemonSocketFile;
|
auto socketPath = settings.nixDaemonSocketFile;
|
||||||
auto s = socket(PF_UNIX, SOCK_STREAM, 0);
|
auto s = socket(PF_UNIX, SOCK_STREAM, 0);
|
||||||
if (s == -1)
|
if (s == -1)
|
||||||
|
@ -284,17 +286,17 @@ static int _main(int argc, char * * argv)
|
||||||
|
|
||||||
auto socketDir = dirOf(socketPath);
|
auto socketDir = dirOf(socketPath);
|
||||||
if (chdir(socketDir.c_str()) == -1)
|
if (chdir(socketDir.c_str()) == -1)
|
||||||
throw SysError(format("changing to socket directory '%1%'") % socketDir);
|
throw SysError("changing to socket directory '%1%'", socketDir);
|
||||||
|
|
||||||
auto socketName = std::string(baseNameOf(socketPath));
|
auto socketName = std::string(baseNameOf(socketPath));
|
||||||
auto addr = sockaddr_un{};
|
auto addr = sockaddr_un{};
|
||||||
addr.sun_family = AF_UNIX;
|
addr.sun_family = AF_UNIX;
|
||||||
if (socketName.size() + 1 >= sizeof(addr.sun_path))
|
if (socketName.size() + 1 >= sizeof(addr.sun_path))
|
||||||
throw Error(format("socket name %1% is too long") % socketName);
|
throw Error("socket name %1% is too long", socketName);
|
||||||
strcpy(addr.sun_path, socketName.c_str());
|
strcpy(addr.sun_path, socketName.c_str());
|
||||||
|
|
||||||
if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) == -1)
|
if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) == -1)
|
||||||
throw SysError(format("cannot connect to daemon at %1%") % socketPath);
|
throw SysError("cannot connect to daemon at %1%", socketPath);
|
||||||
|
|
||||||
auto nfds = (s > STDIN_FILENO ? s : STDIN_FILENO) + 1;
|
auto nfds = (s > STDIN_FILENO ? s : STDIN_FILENO) + 1;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
using std::cout;
|
using std::cout;
|
||||||
|
|
||||||
|
@ -70,8 +69,7 @@ typedef void (* Operation) (Globals & globals,
|
||||||
static string needArg(Strings::iterator & i,
|
static string needArg(Strings::iterator & i,
|
||||||
Strings & args, const string & arg)
|
Strings & args, const string & arg)
|
||||||
{
|
{
|
||||||
if (i == args.end()) throw UsageError(
|
if (i == args.end()) throw UsageError("'%1%' requires an argument", arg);
|
||||||
format("'%1%' requires an argument") % arg);
|
|
||||||
return *i++;
|
return *i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +123,10 @@ static void getAllExprs(EvalState & state,
|
||||||
if (hasSuffix(attrName, ".nix"))
|
if (hasSuffix(attrName, ".nix"))
|
||||||
attrName = string(attrName, 0, attrName.size() - 4);
|
attrName = string(attrName, 0, attrName.size() - 4);
|
||||||
if (!attrs.insert(attrName).second) {
|
if (!attrs.insert(attrName).second) {
|
||||||
printError(format("warning: name collision in input Nix expressions, skipping '%1%'") % path2);
|
logError({
|
||||||
|
.name = "Name collision",
|
||||||
|
.hint = hintfmt("warning: name collision in input Nix expressions, skipping '%1%'", path2)
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/* Load the expression on demand. */
|
/* Load the expression on demand. */
|
||||||
|
@ -133,7 +134,7 @@ static void getAllExprs(EvalState & state,
|
||||||
Value & vArg(*state.allocValue());
|
Value & vArg(*state.allocValue());
|
||||||
mkString(vArg, path2);
|
mkString(vArg, path2);
|
||||||
if (v.attrs->size() == v.attrs->capacity())
|
if (v.attrs->size() == v.attrs->capacity())
|
||||||
throw Error(format("too many Nix expressions in directory '%1%'") % path);
|
throw Error("too many Nix expressions in directory '%1%'", path);
|
||||||
mkApp(*state.allocAttr(v, state.symbols.create(attrName)), vFun, vArg);
|
mkApp(*state.allocAttr(v, state.symbols.create(attrName)), vFun, vArg);
|
||||||
}
|
}
|
||||||
else if (S_ISDIR(st.st_mode))
|
else if (S_ISDIR(st.st_mode))
|
||||||
|
@ -144,11 +145,12 @@ static void getAllExprs(EvalState & state,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static void loadSourceExpr(EvalState & state, const Path & path, Value & v)
|
static void loadSourceExpr(EvalState & state, const Path & path, Value & v)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (stat(path.c_str(), &st) == -1)
|
if (stat(path.c_str(), &st) == -1)
|
||||||
throw SysError(format("getting information about '%1%'") % path);
|
throw SysError("getting information about '%1%'", path);
|
||||||
|
|
||||||
if (isNixExpr(path, st))
|
if (isNixExpr(path, st))
|
||||||
state.evalFile(path, v);
|
state.evalFile(path, v);
|
||||||
|
@ -221,7 +223,7 @@ static void checkSelectorUse(DrvNames & selectors)
|
||||||
/* Check that all selectors have been used. */
|
/* Check that all selectors have been used. */
|
||||||
for (auto & i : selectors)
|
for (auto & i : selectors)
|
||||||
if (i.hits == 0 && i.fullName != "*")
|
if (i.hits == 0 && i.fullName != "*")
|
||||||
throw Error(format("selector '%1%' matches no derivations") % i.fullName);
|
throw Error("selector '%1%' matches no derivations", i.fullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -507,7 +509,7 @@ static void opInstall(Globals & globals, Strings opFlags, Strings opArgs)
|
||||||
globals.preserveInstalled = true;
|
globals.preserveInstalled = true;
|
||||||
else if (arg == "--remove-all" || arg == "-r")
|
else if (arg == "--remove-all" || arg == "-r")
|
||||||
globals.removeAll = true;
|
globals.removeAll = true;
|
||||||
else throw UsageError(format("unknown flag '%1%'") % arg);
|
else throw UsageError("unknown flag '%1%'", arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
installDerivations(globals, opArgs, globals.profile);
|
installDerivations(globals, opArgs, globals.profile);
|
||||||
|
@ -618,7 +620,7 @@ static void opUpgrade(Globals & globals, Strings opFlags, Strings opArgs)
|
||||||
else if (arg == "--leq") upgradeType = utLeq;
|
else if (arg == "--leq") upgradeType = utLeq;
|
||||||
else if (arg == "--eq") upgradeType = utEq;
|
else if (arg == "--eq") upgradeType = utEq;
|
||||||
else if (arg == "--always") upgradeType = utAlways;
|
else if (arg == "--always") upgradeType = utAlways;
|
||||||
else throw UsageError(format("unknown flag '%1%'") % arg);
|
else throw UsageError("unknown flag '%1%'", arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
upgradeDerivations(globals, opArgs, upgradeType);
|
upgradeDerivations(globals, opArgs, upgradeType);
|
||||||
|
@ -637,7 +639,7 @@ static void setMetaFlag(EvalState & state, DrvInfo & drv,
|
||||||
static void opSetFlag(Globals & globals, Strings opFlags, Strings opArgs)
|
static void opSetFlag(Globals & globals, Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
if (opFlags.size() > 0)
|
if (opFlags.size() > 0)
|
||||||
throw UsageError(format("unknown flag '%1%'") % opFlags.front());
|
throw UsageError("unknown flag '%1%'", opFlags.front());
|
||||||
if (opArgs.size() < 2)
|
if (opArgs.size() < 2)
|
||||||
throw UsageError("not enough arguments to '--set-flag'");
|
throw UsageError("not enough arguments to '--set-flag'");
|
||||||
|
|
||||||
|
@ -680,7 +682,7 @@ static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
|
||||||
for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
|
for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
|
||||||
string arg = *i++;
|
string arg = *i++;
|
||||||
if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
|
if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
|
||||||
else throw UsageError(format("unknown flag '%1%'") % arg);
|
else throw UsageError("unknown flag '%1%'", arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
DrvInfos elems;
|
DrvInfos elems;
|
||||||
|
@ -759,7 +761,7 @@ static void uninstallDerivations(Globals & globals, Strings & selectors,
|
||||||
static void opUninstall(Globals & globals, Strings opFlags, Strings opArgs)
|
static void opUninstall(Globals & globals, Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
if (opFlags.size() > 0)
|
if (opFlags.size() > 0)
|
||||||
throw UsageError(format("unknown flag '%1%'") % opFlags.front());
|
throw UsageError("unknown flag '%1%'", opFlags.front());
|
||||||
uninstallDerivations(globals, opArgs, globals.profile);
|
uninstallDerivations(globals, opArgs, globals.profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -872,7 +874,11 @@ static void queryJSON(Globals & globals, vector<DrvInfo> & elems)
|
||||||
auto placeholder = metaObj.placeholder(j);
|
auto placeholder = metaObj.placeholder(j);
|
||||||
Value * v = i.queryMeta(j);
|
Value * v = i.queryMeta(j);
|
||||||
if (!v) {
|
if (!v) {
|
||||||
printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j);
|
logError({
|
||||||
|
.name = "Invalid meta attribute",
|
||||||
|
.hint = hintfmt("derivation '%s' has invalid meta attribute '%s'",
|
||||||
|
i.queryName(), j)
|
||||||
|
});
|
||||||
placeholder.write(nullptr);
|
placeholder.write(nullptr);
|
||||||
} else {
|
} else {
|
||||||
PathSet context;
|
PathSet context;
|
||||||
|
@ -922,7 +928,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
|
||||||
else if (arg == "--attr" || arg == "-A")
|
else if (arg == "--attr" || arg == "-A")
|
||||||
attrPath = needArg(i, opFlags, arg);
|
attrPath = needArg(i, opFlags, arg);
|
||||||
else
|
else
|
||||||
throw UsageError(format("unknown flag '%1%'") % arg);
|
throw UsageError("unknown flag '%1%'", arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (printAttrPath && source != sAvailable)
|
if (printAttrPath && source != sAvailable)
|
||||||
|
@ -1123,7 +1129,12 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
|
||||||
attrs2["name"] = j;
|
attrs2["name"] = j;
|
||||||
Value * v = i.queryMeta(j);
|
Value * v = i.queryMeta(j);
|
||||||
if (!v)
|
if (!v)
|
||||||
printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j);
|
logError({
|
||||||
|
.name = "Invalid meta attribute",
|
||||||
|
.hint = hintfmt(
|
||||||
|
"derivation '%s' has invalid meta attribute '%s'",
|
||||||
|
i.queryName(), j)
|
||||||
|
});
|
||||||
else {
|
else {
|
||||||
if (v->type == tString) {
|
if (v->type == tString) {
|
||||||
attrs2["type"] = "string";
|
attrs2["type"] = "string";
|
||||||
|
@ -1188,9 +1199,9 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
|
||||||
static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
|
static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
if (opFlags.size() > 0)
|
if (opFlags.size() > 0)
|
||||||
throw UsageError(format("unknown flag '%1%'") % opFlags.front());
|
throw UsageError("unknown flag '%1%'", opFlags.front());
|
||||||
if (opArgs.size() != 1)
|
if (opArgs.size() != 1)
|
||||||
throw UsageError(format("exactly one argument expected"));
|
throw UsageError("exactly one argument expected");
|
||||||
|
|
||||||
Path profile = absPath(opArgs.front());
|
Path profile = absPath(opArgs.front());
|
||||||
Path profileLink = getHome() + "/.nix-profile";
|
Path profileLink = getHome() + "/.nix-profile";
|
||||||
|
@ -1218,10 +1229,10 @@ static void switchGeneration(Globals & globals, int dstGen)
|
||||||
|
|
||||||
if (!dst) {
|
if (!dst) {
|
||||||
if (dstGen == prevGen)
|
if (dstGen == prevGen)
|
||||||
throw Error(format("no generation older than the current (%1%) exists")
|
throw Error("no generation older than the current (%1%) exists",
|
||||||
% curGen);
|
curGen);
|
||||||
else
|
else
|
||||||
throw Error(format("generation %1% does not exist") % dstGen);
|
throw Error("generation %1% does not exist", dstGen);
|
||||||
}
|
}
|
||||||
|
|
||||||
printInfo(format("switching from generation %1% to %2%")
|
printInfo(format("switching from generation %1% to %2%")
|
||||||
|
@ -1236,13 +1247,13 @@ static void switchGeneration(Globals & globals, int dstGen)
|
||||||
static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArgs)
|
static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
if (opFlags.size() > 0)
|
if (opFlags.size() > 0)
|
||||||
throw UsageError(format("unknown flag '%1%'") % opFlags.front());
|
throw UsageError("unknown flag '%1%'", opFlags.front());
|
||||||
if (opArgs.size() != 1)
|
if (opArgs.size() != 1)
|
||||||
throw UsageError(format("exactly one argument expected"));
|
throw UsageError("exactly one argument expected");
|
||||||
|
|
||||||
int dstGen;
|
int dstGen;
|
||||||
if (!string2Int(opArgs.front(), dstGen))
|
if (!string2Int(opArgs.front(), dstGen))
|
||||||
throw UsageError(format("expected a generation number"));
|
throw UsageError("expected a generation number");
|
||||||
|
|
||||||
switchGeneration(globals, dstGen);
|
switchGeneration(globals, dstGen);
|
||||||
}
|
}
|
||||||
|
@ -1251,9 +1262,9 @@ static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArg
|
||||||
static void opRollback(Globals & globals, Strings opFlags, Strings opArgs)
|
static void opRollback(Globals & globals, Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
if (opFlags.size() > 0)
|
if (opFlags.size() > 0)
|
||||||
throw UsageError(format("unknown flag '%1%'") % opFlags.front());
|
throw UsageError("unknown flag '%1%'", opFlags.front());
|
||||||
if (opArgs.size() != 0)
|
if (opArgs.size() != 0)
|
||||||
throw UsageError(format("no arguments expected"));
|
throw UsageError("no arguments expected");
|
||||||
|
|
||||||
switchGeneration(globals, prevGen);
|
switchGeneration(globals, prevGen);
|
||||||
}
|
}
|
||||||
|
@ -1262,9 +1273,9 @@ static void opRollback(Globals & globals, Strings opFlags, Strings opArgs)
|
||||||
static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs)
|
static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
if (opFlags.size() > 0)
|
if (opFlags.size() > 0)
|
||||||
throw UsageError(format("unknown flag '%1%'") % opFlags.front());
|
throw UsageError("unknown flag '%1%'", opFlags.front());
|
||||||
if (opArgs.size() != 0)
|
if (opArgs.size() != 0)
|
||||||
throw UsageError(format("no arguments expected"));
|
throw UsageError("no arguments expected");
|
||||||
|
|
||||||
PathLocks lock;
|
PathLocks lock;
|
||||||
lockProfile(lock, globals.profile);
|
lockProfile(lock, globals.profile);
|
||||||
|
@ -1289,7 +1300,7 @@ static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs
|
||||||
static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opArgs)
|
static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
if (opFlags.size() > 0)
|
if (opFlags.size() > 0)
|
||||||
throw UsageError(format("unknown flag '%1%'") % opFlags.front());
|
throw UsageError("unknown flag '%1%'", opFlags.front());
|
||||||
|
|
||||||
if (opArgs.size() == 1 && opArgs.front() == "old") {
|
if (opArgs.size() == 1 && opArgs.front() == "old") {
|
||||||
deleteOldGenerations(globals.profile, globals.dryRun);
|
deleteOldGenerations(globals.profile, globals.dryRun);
|
||||||
|
@ -1297,18 +1308,18 @@ static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opAr
|
||||||
deleteGenerationsOlderThan(globals.profile, opArgs.front(), globals.dryRun);
|
deleteGenerationsOlderThan(globals.profile, opArgs.front(), globals.dryRun);
|
||||||
} else if (opArgs.size() == 1 && opArgs.front().find('+') != string::npos) {
|
} else if (opArgs.size() == 1 && opArgs.front().find('+') != string::npos) {
|
||||||
if(opArgs.front().size() < 2)
|
if(opArgs.front().size() < 2)
|
||||||
throw Error(format("invalid number of generations ‘%1%’") % opArgs.front());
|
throw Error("invalid number of generations ‘%1%’", opArgs.front());
|
||||||
string str_max = string(opArgs.front(), 1, opArgs.front().size());
|
string str_max = string(opArgs.front(), 1, opArgs.front().size());
|
||||||
int max;
|
int max;
|
||||||
if (!string2Int(str_max, max) || max == 0)
|
if (!string2Int(str_max, max) || max == 0)
|
||||||
throw Error(format("invalid number of generations to keep ‘%1%’") % opArgs.front());
|
throw Error("invalid number of generations to keep ‘%1%’", opArgs.front());
|
||||||
deleteGenerationsGreaterThan(globals.profile, max, globals.dryRun);
|
deleteGenerationsGreaterThan(globals.profile, max, globals.dryRun);
|
||||||
} else {
|
} else {
|
||||||
std::set<unsigned int> gens;
|
std::set<unsigned int> gens;
|
||||||
for (auto & i : opArgs) {
|
for (auto & i : opArgs) {
|
||||||
unsigned int n;
|
unsigned int n;
|
||||||
if (!string2Int(i, n))
|
if (!string2Int(i, n))
|
||||||
throw UsageError(format("invalid generation number '%1%'") % i);
|
throw UsageError("invalid generation number '%1%'", i);
|
||||||
gens.insert(n);
|
gens.insert(n);
|
||||||
}
|
}
|
||||||
deleteGenerations(globals.profile, gens, globals.dryRun);
|
deleteGenerations(globals.profile, gens, globals.dryRun);
|
||||||
|
|
|
@ -146,7 +146,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
||||||
|
|
||||||
Path lockTokenCur = optimisticLockProfile(profile);
|
Path lockTokenCur = optimisticLockProfile(profile);
|
||||||
if (lockToken != lockTokenCur) {
|
if (lockToken != lockTokenCur) {
|
||||||
printError(format("profile '%1%' changed while we were busy; restarting") % profile);
|
printInfo("profile '%1%' changed while we were busy; restarting", profile);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ void processExpr(EvalState & state, const Strings & attrPaths,
|
||||||
/* What output do we want? */
|
/* What output do we want? */
|
||||||
string outputName = i.queryOutputName();
|
string outputName = i.queryOutputName();
|
||||||
if (outputName == "")
|
if (outputName == "")
|
||||||
throw Error(format("derivation '%1%' lacks an 'outputName' attribute ") % drvPath);
|
throw Error("derivation '%1%' lacks an 'outputName' attribute ", drvPath);
|
||||||
|
|
||||||
if (gcRoot == "")
|
if (gcRoot == "")
|
||||||
printGCWarning();
|
printGCWarning();
|
||||||
|
@ -166,7 +166,7 @@ static int _main(int argc, char * * argv)
|
||||||
if (findFile) {
|
if (findFile) {
|
||||||
for (auto & i : files) {
|
for (auto & i : files) {
|
||||||
Path p = state->findFile(i);
|
Path p = state->findFile(i);
|
||||||
if (p == "") throw Error(format("unable to find '%1%'") % i);
|
if (p == "") throw Error("unable to find '%1%'", i);
|
||||||
std::cout << p << std::endl;
|
std::cout << p << std::endl;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -37,11 +37,11 @@ string resolveMirrorUri(EvalState & state, string uri)
|
||||||
|
|
||||||
auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
|
auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
|
||||||
if (mirrorList == vMirrors.attrs->end())
|
if (mirrorList == vMirrors.attrs->end())
|
||||||
throw Error(format("unknown mirror name '%1%'") % mirrorName);
|
throw Error("unknown mirror name '%1%'", mirrorName);
|
||||||
state.forceList(*mirrorList->value);
|
state.forceList(*mirrorList->value);
|
||||||
|
|
||||||
if (mirrorList->value->listSize() < 1)
|
if (mirrorList->value->listSize() < 1)
|
||||||
throw Error(format("mirror URI '%1%' did not expand to anything") % uri);
|
throw Error("mirror URI '%1%' did not expand to anything", uri);
|
||||||
|
|
||||||
string mirror = state.forceString(*mirrorList->value->listElems()[0]);
|
string mirror = state.forceString(*mirrorList->value->listElems()[0]);
|
||||||
return mirror + (hasSuffix(mirror, "/") ? "" : "/") + string(s, p + 1);
|
return mirror + (hasSuffix(mirror, "/") ? "" : "/") + string(s, p + 1);
|
||||||
|
@ -73,7 +73,7 @@ static int _main(int argc, char * * argv)
|
||||||
string s = getArg(*arg, arg, end);
|
string s = getArg(*arg, arg, end);
|
||||||
ht = parseHashType(s);
|
ht = parseHashType(s);
|
||||||
if (ht == htUnknown)
|
if (ht == htUnknown)
|
||||||
throw UsageError(format("unknown hash type '%1%'") % s);
|
throw UsageError("unknown hash type '%1%'", s);
|
||||||
}
|
}
|
||||||
else if (*arg == "--print-path")
|
else if (*arg == "--print-path")
|
||||||
printPath = true;
|
printPath = true;
|
||||||
|
@ -151,7 +151,7 @@ static int _main(int argc, char * * argv)
|
||||||
if (name.empty())
|
if (name.empty())
|
||||||
name = baseNameOf(uri);
|
name = baseNameOf(uri);
|
||||||
if (name.empty())
|
if (name.empty())
|
||||||
throw Error(format("cannot figure out file name for '%1%'") % uri);
|
throw Error("cannot figure out file name for '%1%'", uri);
|
||||||
|
|
||||||
/* If an expected hash is given, the file may already exist in
|
/* If an expected hash is given, the file may already exist in
|
||||||
the store. */
|
the store. */
|
||||||
|
@ -207,7 +207,7 @@ static int _main(int argc, char * * argv)
|
||||||
hash = unpack ? hashPath(ht, tmpFile).first : hashFile(ht, tmpFile);
|
hash = unpack ? hashPath(ht, tmpFile).first : hashFile(ht, tmpFile);
|
||||||
|
|
||||||
if (expectedHash != Hash(ht) && expectedHash != hash)
|
if (expectedHash != Hash(ht) && expectedHash != hash)
|
||||||
throw Error(format("hash mismatch for '%1%'") % uri);
|
throw Error("hash mismatch for '%1%'", uri);
|
||||||
|
|
||||||
const auto recursive = unpack ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat;
|
const auto recursive = unpack ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat;
|
||||||
|
|
||||||
|
|
|
@ -124,7 +124,7 @@ static void opRealise(Strings opFlags, Strings opArgs)
|
||||||
else if (i == "--repair") buildMode = bmRepair;
|
else if (i == "--repair") buildMode = bmRepair;
|
||||||
else if (i == "--check") buildMode = bmCheck;
|
else if (i == "--check") buildMode = bmCheck;
|
||||||
else if (i == "--ignore-unknown") ignoreUnknown = true;
|
else if (i == "--ignore-unknown") ignoreUnknown = true;
|
||||||
else throw UsageError(format("unknown flag '%1%'") % i);
|
else throw UsageError("unknown flag '%1%'", i);
|
||||||
|
|
||||||
std::vector<StorePathWithOutputs> paths;
|
std::vector<StorePathWithOutputs> paths;
|
||||||
for (auto & i : opArgs)
|
for (auto & i : opArgs)
|
||||||
|
@ -178,7 +178,7 @@ static void opAddFixed(Strings opFlags, Strings opArgs)
|
||||||
|
|
||||||
for (auto & i : opFlags)
|
for (auto & i : opFlags)
|
||||||
if (i == "--recursive") recursive = FileIngestionMethod::Recursive;
|
if (i == "--recursive") recursive = FileIngestionMethod::Recursive;
|
||||||
else throw UsageError(format("unknown flag '%1%'") % i);
|
else throw UsageError("unknown flag '%1%'", i);
|
||||||
|
|
||||||
if (opArgs.empty())
|
if (opArgs.empty())
|
||||||
throw UsageError("first argument must be hash algorithm");
|
throw UsageError("first argument must be hash algorithm");
|
||||||
|
@ -198,10 +198,10 @@ static void opPrintFixedPath(Strings opFlags, Strings opArgs)
|
||||||
|
|
||||||
for (auto i : opFlags)
|
for (auto i : opFlags)
|
||||||
if (i == "--recursive") recursive = FileIngestionMethod::Recursive;
|
if (i == "--recursive") recursive = FileIngestionMethod::Recursive;
|
||||||
else throw UsageError(format("unknown flag '%1%'") % i);
|
else throw UsageError("unknown flag '%1%'", i);
|
||||||
|
|
||||||
if (opArgs.size() != 3)
|
if (opArgs.size() != 3)
|
||||||
throw UsageError(format("'--print-fixed-path' requires three arguments"));
|
throw UsageError("'--print-fixed-path' requires three arguments");
|
||||||
|
|
||||||
Strings::iterator i = opArgs.begin();
|
Strings::iterator i = opArgs.begin();
|
||||||
HashType hashAlgo = parseHashType(*i++);
|
HashType hashAlgo = parseHashType(*i++);
|
||||||
|
@ -296,9 +296,9 @@ static void opQuery(Strings opFlags, Strings opArgs)
|
||||||
else if (i == "--use-output" || i == "-u") useOutput = true;
|
else if (i == "--use-output" || i == "-u") useOutput = true;
|
||||||
else if (i == "--force-realise" || i == "--force-realize" || i == "-f") forceRealise = true;
|
else if (i == "--force-realise" || i == "--force-realize" || i == "-f") forceRealise = true;
|
||||||
else if (i == "--include-outputs") includeOutputs = true;
|
else if (i == "--include-outputs") includeOutputs = true;
|
||||||
else throw UsageError(format("unknown flag '%1%'") % i);
|
else throw UsageError("unknown flag '%1%'", i);
|
||||||
if (prev != qDefault && prev != query)
|
if (prev != qDefault && prev != query)
|
||||||
throw UsageError(format("query type '%1%' conflicts with earlier flag") % i);
|
throw UsageError("query type '%1%' conflicts with earlier flag", i);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query == qDefault) query = qOutputs;
|
if (query == qDefault) query = qOutputs;
|
||||||
|
@ -444,7 +444,7 @@ static void opPrintEnv(Strings opFlags, Strings opArgs)
|
||||||
Derivation drv = store->derivationFromPath(store->parseStorePath(drvPath));
|
Derivation drv = store->derivationFromPath(store->parseStorePath(drvPath));
|
||||||
|
|
||||||
/* Print each environment variable in the derivation in a format
|
/* Print each environment variable in the derivation in a format
|
||||||
that can be sourced by the shell. */
|
* that can be sourced by the shell. */
|
||||||
for (auto & i : drv.env)
|
for (auto & i : drv.env)
|
||||||
cout << format("export %1%; %1%=%2%\n") % i.first % shellEscape(i.second);
|
cout << format("export %1%; %1%=%2%\n") % i.first % shellEscape(i.second);
|
||||||
|
|
||||||
|
@ -531,7 +531,7 @@ static void opRegisterValidity(Strings opFlags, Strings opArgs)
|
||||||
for (auto & i : opFlags)
|
for (auto & i : opFlags)
|
||||||
if (i == "--reregister") reregister = true;
|
if (i == "--reregister") reregister = true;
|
||||||
else if (i == "--hash-given") hashGiven = true;
|
else if (i == "--hash-given") hashGiven = true;
|
||||||
else throw UsageError(format("unknown flag '%1%'") % i);
|
else throw UsageError("unknown flag '%1%'", i);
|
||||||
|
|
||||||
if (!opArgs.empty()) throw UsageError("no arguments expected");
|
if (!opArgs.empty()) throw UsageError("no arguments expected");
|
||||||
|
|
||||||
|
@ -545,7 +545,7 @@ static void opCheckValidity(Strings opFlags, Strings opArgs)
|
||||||
|
|
||||||
for (auto & i : opFlags)
|
for (auto & i : opFlags)
|
||||||
if (i == "--print-invalid") printInvalid = true;
|
if (i == "--print-invalid") printInvalid = true;
|
||||||
else throw UsageError(format("unknown flag '%1%'") % i);
|
else throw UsageError("unknown flag '%1%'", i);
|
||||||
|
|
||||||
for (auto & i : opArgs) {
|
for (auto & i : opArgs) {
|
||||||
auto path = store->followLinksToStorePath(i);
|
auto path = store->followLinksToStorePath(i);
|
||||||
|
@ -576,7 +576,7 @@ static void opGC(Strings opFlags, Strings opArgs)
|
||||||
long long maxFreed = getIntArg<long long>(*i, i, opFlags.end(), true);
|
long long maxFreed = getIntArg<long long>(*i, i, opFlags.end(), true);
|
||||||
options.maxFreed = maxFreed >= 0 ? maxFreed : 0;
|
options.maxFreed = maxFreed >= 0 ? maxFreed : 0;
|
||||||
}
|
}
|
||||||
else throw UsageError(format("bad sub-operation '%1%' in GC") % *i);
|
else throw UsageError("bad sub-operation '%1%' in GC", *i);
|
||||||
|
|
||||||
if (!opArgs.empty()) throw UsageError("no arguments expected");
|
if (!opArgs.empty()) throw UsageError("no arguments expected");
|
||||||
|
|
||||||
|
@ -612,7 +612,7 @@ static void opDelete(Strings opFlags, Strings opArgs)
|
||||||
|
|
||||||
for (auto & i : opFlags)
|
for (auto & i : opFlags)
|
||||||
if (i == "--ignore-liveness") options.ignoreLiveness = true;
|
if (i == "--ignore-liveness") options.ignoreLiveness = true;
|
||||||
else throw UsageError(format("unknown flag '%1%'") % i);
|
else throw UsageError("unknown flag '%1%'", i);
|
||||||
|
|
||||||
for (auto & i : opArgs)
|
for (auto & i : opArgs)
|
||||||
options.pathsToDelete.insert(store->followLinksToStorePath(i));
|
options.pathsToDelete.insert(store->followLinksToStorePath(i));
|
||||||
|
@ -650,7 +650,7 @@ static void opRestore(Strings opFlags, Strings opArgs)
|
||||||
static void opExport(Strings opFlags, Strings opArgs)
|
static void opExport(Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
for (auto & i : opFlags)
|
for (auto & i : opFlags)
|
||||||
throw UsageError(format("unknown flag '%1%'") % i);
|
throw UsageError("unknown flag '%1%'", i);
|
||||||
|
|
||||||
StorePathSet paths;
|
StorePathSet paths;
|
||||||
|
|
||||||
|
@ -666,7 +666,7 @@ static void opExport(Strings opFlags, Strings opArgs)
|
||||||
static void opImport(Strings opFlags, Strings opArgs)
|
static void opImport(Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
for (auto & i : opFlags)
|
for (auto & i : opFlags)
|
||||||
throw UsageError(format("unknown flag '%1%'") % i);
|
throw UsageError("unknown flag '%1%'", i);
|
||||||
|
|
||||||
if (!opArgs.empty()) throw UsageError("no arguments expected");
|
if (!opArgs.empty()) throw UsageError("no arguments expected");
|
||||||
|
|
||||||
|
@ -701,10 +701,13 @@ static void opVerify(Strings opFlags, Strings opArgs)
|
||||||
for (auto & i : opFlags)
|
for (auto & i : opFlags)
|
||||||
if (i == "--check-contents") checkContents = true;
|
if (i == "--check-contents") checkContents = true;
|
||||||
else if (i == "--repair") repair = Repair;
|
else if (i == "--repair") repair = Repair;
|
||||||
else throw UsageError(format("unknown flag '%1%'") % i);
|
else throw UsageError("unknown flag '%1%'", i);
|
||||||
|
|
||||||
if (store->verifyStore(checkContents, repair)) {
|
if (store->verifyStore(checkContents, repair)) {
|
||||||
printError("warning: not all errors were fixed");
|
logWarning({
|
||||||
|
.name = "Store consistency",
|
||||||
|
.description = "not all errors were fixed"
|
||||||
|
});
|
||||||
throw Exit(1);
|
throw Exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -726,9 +729,14 @@ static void opVerifyPath(Strings opFlags, Strings opArgs)
|
||||||
store->narFromPath(path, sink);
|
store->narFromPath(path, sink);
|
||||||
auto current = sink.finish();
|
auto current = sink.finish();
|
||||||
if (current.first != info->narHash) {
|
if (current.first != info->narHash) {
|
||||||
printError(
|
logError({
|
||||||
|
.name = "Hash mismatch",
|
||||||
|
.hint = hintfmt(
|
||||||
"path '%s' was modified! expected hash '%s', got '%s'",
|
"path '%s' was modified! expected hash '%s', got '%s'",
|
||||||
store->printStorePath(path), info->narHash.to_string(Base32, true), current.first.to_string(Base32, true));
|
store->printStorePath(path),
|
||||||
|
info->narHash.to_string(Base32, true),
|
||||||
|
current.first.to_string(Base32, true))
|
||||||
|
});
|
||||||
status = 1;
|
status = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -764,7 +772,7 @@ static void opServe(Strings opFlags, Strings opArgs)
|
||||||
bool writeAllowed = false;
|
bool writeAllowed = false;
|
||||||
for (auto & i : opFlags)
|
for (auto & i : opFlags)
|
||||||
if (i == "--write") writeAllowed = true;
|
if (i == "--write") writeAllowed = true;
|
||||||
else throw UsageError(format("unknown flag '%1%'") % i);
|
else throw UsageError("unknown flag '%1%'", i);
|
||||||
|
|
||||||
if (!opArgs.empty()) throw UsageError("no arguments expected");
|
if (!opArgs.empty()) throw UsageError("no arguments expected");
|
||||||
|
|
||||||
|
@ -835,7 +843,7 @@ static void opServe(Strings opFlags, Strings opArgs)
|
||||||
for (auto & p : willSubstitute) subs.emplace_back(p.clone());
|
for (auto & p : willSubstitute) subs.emplace_back(p.clone());
|
||||||
store->buildPaths(subs);
|
store->buildPaths(subs);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
printError(format("warning: %1%") % e.msg());
|
logWarning(e.info());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -962,7 +970,7 @@ static void opServe(Strings opFlags, Strings opArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw Error(format("unknown serve command %1%") % cmd);
|
throw Error("unknown serve command %1%", cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
out.flush();
|
out.flush();
|
||||||
|
@ -973,7 +981,7 @@ static void opServe(Strings opFlags, Strings opArgs)
|
||||||
static void opGenerateBinaryCacheKey(Strings opFlags, Strings opArgs)
|
static void opGenerateBinaryCacheKey(Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
for (auto & i : opFlags)
|
for (auto & i : opFlags)
|
||||||
throw UsageError(format("unknown flag '%1%'") % i);
|
throw UsageError("unknown flag '%1%'", i);
|
||||||
|
|
||||||
if (opArgs.size() != 3) throw UsageError("three arguments expected");
|
if (opArgs.size() != 3) throw UsageError("three arguments expected");
|
||||||
auto i = opArgs.begin();
|
auto i = opArgs.begin();
|
||||||
|
|
|
@ -13,9 +13,9 @@ struct MixCat : virtual Args
|
||||||
{
|
{
|
||||||
auto st = accessor->stat(path);
|
auto st = accessor->stat(path);
|
||||||
if (st.type == FSAccessor::Type::tMissing)
|
if (st.type == FSAccessor::Type::tMissing)
|
||||||
throw Error(format("path '%1%' does not exist") % path);
|
throw Error("path '%1%' does not exist", path);
|
||||||
if (st.type != FSAccessor::Type::tRegular)
|
if (st.type != FSAccessor::Type::tRegular)
|
||||||
throw Error(format("path '%1%' is not a regular file") % path);
|
throw Error("path '%1%' is not a regular file", path);
|
||||||
|
|
||||||
std::cout << accessor->readFile(path);
|
std::cout << accessor->readFile(path);
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,7 +133,7 @@ static int compatNixHash(int argc, char * * argv)
|
||||||
string s = getArg(*arg, arg, end);
|
string s = getArg(*arg, arg, end);
|
||||||
ht = parseHashType(s);
|
ht = parseHashType(s);
|
||||||
if (ht == htUnknown)
|
if (ht == htUnknown)
|
||||||
throw UsageError(format("unknown hash type '%1%'") % s);
|
throw UsageError("unknown hash type '%1%'", s);
|
||||||
}
|
}
|
||||||
else if (*arg == "--to-base16") op = opTo16;
|
else if (*arg == "--to-base16") op = opTo16;
|
||||||
else if (*arg == "--to-base32") op = opTo32;
|
else if (*arg == "--to-base32") op = opTo32;
|
||||||
|
|
|
@ -63,7 +63,7 @@ struct MixLs : virtual Args, MixJSON
|
||||||
|
|
||||||
auto st = accessor->stat(path);
|
auto st = accessor->stat(path);
|
||||||
if (st.type == FSAccessor::Type::tMissing)
|
if (st.type == FSAccessor::Type::tMissing)
|
||||||
throw Error(format("path '%1%' does not exist") % path);
|
throw Error("path '%1%' does not exist", path);
|
||||||
doPath(st, path,
|
doPath(st, path,
|
||||||
st.type == FSAccessor::Type::tDirectory ? "." : std::string(baseNameOf(path)),
|
st.type == FSAccessor::Type::tDirectory ? "." : std::string(baseNameOf(path)),
|
||||||
showDirectory);
|
showDirectory);
|
||||||
|
|
|
@ -85,7 +85,7 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON
|
||||||
info.ca = makeFixedOutputCA(FileIngestionMethod::Recursive, info.narHash);
|
info.ca = makeFixedOutputCA(FileIngestionMethod::Recursive, info.narHash);
|
||||||
|
|
||||||
if (!json)
|
if (!json)
|
||||||
printError("rewrote '%s' to '%s'", pathS, store->printStorePath(info.path));
|
printInfo("rewrote '%s' to '%s'", pathS, store->printStorePath(info.path));
|
||||||
|
|
||||||
auto source = sinkToSource([&](Sink & nextSink) {
|
auto source = sinkToSource([&](Sink & nextSink) {
|
||||||
RewritingSink rsink2(oldHashPart, storePathToHash(store->printStorePath(info.path)), nextSink);
|
RewritingSink rsink2(oldHashPart, storePathToHash(store->printStorePath(info.path)), nextSink);
|
||||||
|
|
|
@ -218,12 +218,12 @@ void NixRepl::mainLoop(const std::vector<std::string> & files)
|
||||||
// input without clearing the input so far.
|
// input without clearing the input so far.
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
|
printMsg(lvlError, error + "%1%%2%", (settings.showTrace ? e.prefix() : ""), e.msg());
|
||||||
}
|
}
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
|
printMsg(lvlError, error + "%1%%2%", (settings.showTrace ? e.prefix() : ""), e.msg());
|
||||||
} catch (Interrupted & e) {
|
} catch (Interrupted & e) {
|
||||||
printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
|
printMsg(lvlError, error + "%1%%2%", (settings.showTrace ? e.prefix() : ""), e.msg());
|
||||||
}
|
}
|
||||||
|
|
||||||
// We handled the current input fully, so we should clear it
|
// We handled the current input fully, so we should clear it
|
||||||
|
@ -512,7 +512,7 @@ bool NixRepl::processLine(string line)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
else if (command != "")
|
else if (command != "")
|
||||||
throw Error(format("unknown command '%1%'") % command);
|
throw Error("unknown command '%1%'", command);
|
||||||
|
|
||||||
else {
|
else {
|
||||||
size_t p = line.find('=');
|
size_t p = line.find('=');
|
||||||
|
|
|
@ -197,10 +197,10 @@ void chrootHelper(int argc, char * * argv)
|
||||||
Finally freeCwd([&]() { free(cwd); });
|
Finally freeCwd([&]() { free(cwd); });
|
||||||
|
|
||||||
if (chroot(tmpDir.c_str()) == -1)
|
if (chroot(tmpDir.c_str()) == -1)
|
||||||
throw SysError(format("chrooting into '%s'") % tmpDir);
|
throw SysError("chrooting into '%s'", tmpDir);
|
||||||
|
|
||||||
if (chdir(cwd) == -1)
|
if (chdir(cwd) == -1)
|
||||||
throw SysError(format("chdir to '%s' in chroot") % cwd);
|
throw SysError("chdir to '%s' in chroot", cwd);
|
||||||
} else
|
} else
|
||||||
if (mount(realStoreDir.c_str(), storeDir.c_str(), "", MS_BIND, 0) == -1)
|
if (mount(realStoreDir.c_str(), storeDir.c_str(), "", MS_BIND, 0) == -1)
|
||||||
throw SysError("mounting '%s' on '%s'", realStoreDir, storeDir);
|
throw SysError("mounting '%s' on '%s'", realStoreDir, storeDir);
|
||||||
|
|
|
@ -68,7 +68,11 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
|
||||||
|
|
||||||
if (dryRun) {
|
if (dryRun) {
|
||||||
stopProgressBar();
|
stopProgressBar();
|
||||||
printError("would upgrade to version %s", version);
|
logWarning(
|
||||||
|
ErrorInfo {
|
||||||
|
.name = "Version update",
|
||||||
|
.hint = hintfmt("would upgrade to version %s", version)
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +98,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
|
||||||
{"--profile", profileDir, "-i", store->printStorePath(storePath), "--no-sandbox"});
|
{"--profile", profileDir, "-i", store->printStorePath(storePath), "--no-sandbox"});
|
||||||
}
|
}
|
||||||
|
|
||||||
printError(ANSI_GREEN "upgrade to version %s done" ANSI_NORMAL, version);
|
printInfo(ANSI_GREEN "upgrade to version %s done" ANSI_NORMAL, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return the profile in which Nix is installed. */
|
/* Return the profile in which Nix is installed. */
|
||||||
|
|
|
@ -99,11 +99,15 @@ struct CmdVerify : StorePathsCommand
|
||||||
if (hash.first != info->narHash) {
|
if (hash.first != info->narHash) {
|
||||||
corrupted++;
|
corrupted++;
|
||||||
act2.result(resCorruptedPath, store->printStorePath(info->path));
|
act2.result(resCorruptedPath, store->printStorePath(info->path));
|
||||||
printError(
|
logError({
|
||||||
|
.name = "Hash error - path modified",
|
||||||
|
.hint = hintfmt(
|
||||||
"path '%s' was modified! expected hash '%s', got '%s'",
|
"path '%s' was modified! expected hash '%s', got '%s'",
|
||||||
store->printStorePath(info->path), info->narHash.to_string(Base32, true), hash.first.to_string(Base32, true));
|
store->printStorePath(info->path),
|
||||||
|
info->narHash.to_string(Base32, true),
|
||||||
|
hash.first.to_string(Base32, true))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!noTrust) {
|
if (!noTrust) {
|
||||||
|
@ -139,7 +143,7 @@ struct CmdVerify : StorePathsCommand
|
||||||
doSigs(info2->sigs);
|
doSigs(info2->sigs);
|
||||||
} catch (InvalidPath &) {
|
} catch (InvalidPath &) {
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
printError(format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what());
|
logError(e.info());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +154,12 @@ struct CmdVerify : StorePathsCommand
|
||||||
if (!good) {
|
if (!good) {
|
||||||
untrusted++;
|
untrusted++;
|
||||||
act2.result(resUntrustedPath, store->printStorePath(info->path));
|
act2.result(resUntrustedPath, store->printStorePath(info->path));
|
||||||
printError("path '%s' is untrusted", store->printStorePath(info->path));
|
logError({
|
||||||
|
.name = "Untrusted path",
|
||||||
|
.hint = hintfmt("path '%s' is untrusted",
|
||||||
|
store->printStorePath(info->path))
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -158,7 +167,7 @@ struct CmdVerify : StorePathsCommand
|
||||||
done++;
|
done++;
|
||||||
|
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
printError(format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what());
|
logError(e.info());
|
||||||
failed++;
|
failed++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,12 +39,18 @@ std::set<std::string> runResolver(const Path & filename)
|
||||||
throw SysError("statting '%s'", filename);
|
throw SysError("statting '%s'", filename);
|
||||||
|
|
||||||
if (!S_ISREG(st.st_mode)) {
|
if (!S_ISREG(st.st_mode)) {
|
||||||
printError("file '%s' is not a regular file", filename);
|
logError({
|
||||||
|
.name = "Regular MACH file",
|
||||||
|
.hint = hintfmt("file '%s' is not a regular file", filename)
|
||||||
|
});
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st.st_size < sizeof(mach_header_64)) {
|
if (st.st_size < sizeof(mach_header_64)) {
|
||||||
printError("file '%s' is too short for a MACH binary", filename);
|
logError({
|
||||||
|
.name = "File too short",
|
||||||
|
.hint = hintfmt("file '%s' is too short for a MACH binary", filename)
|
||||||
|
});
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,13 +72,19 @@ std::set<std::string> runResolver(const Path & filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mach64_offset == 0) {
|
if (mach64_offset == 0) {
|
||||||
printError(format("Could not find any mach64 blobs in file '%1%', continuing...") % filename);
|
logError({
|
||||||
|
.name = "No mach64 blobs",
|
||||||
|
.hint = hintfmt("Could not find any mach64 blobs in file '%1%', continuing...", filename)
|
||||||
|
});
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
} else if (magic == MH_MAGIC_64 || magic == MH_CIGAM_64) {
|
} else if (magic == MH_MAGIC_64 || magic == MH_CIGAM_64) {
|
||||||
mach64_offset = 0;
|
mach64_offset = 0;
|
||||||
} else {
|
} else {
|
||||||
printError(format("Object file has unknown magic number '%1%', skipping it...") % magic);
|
logError({
|
||||||
|
.name = "Magic number",
|
||||||
|
.hint = hintfmt("Object file has unknown magic number '%1%', skipping it...", magic)
|
||||||
|
});
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ invalid_ref() {
|
||||||
else
|
else
|
||||||
(! git check-ref-format --branch "$1" >/dev/null 2>&1)
|
(! git check-ref-format --branch "$1" >/dev/null 2>&1)
|
||||||
fi
|
fi
|
||||||
nix --debug eval --raw "(builtins.fetchGit { url = $repo; ref = ''$1''; }).outPath" 2>&1 | grep 'error: invalid Git branch/tag name' >/dev/null
|
nix --debug eval --raw "(builtins.fetchGit { url = $repo; ref = ''$1''; }).outPath" 2>&1 | grep 'invalid Git branch/tag name' >/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,4 +16,6 @@ nix-env --foo 2>&1 | grep "no operation"
|
||||||
nix-env -q --foo 2>&1 | grep "unknown flag"
|
nix-env -q --foo 2>&1 | grep "unknown flag"
|
||||||
|
|
||||||
# Eval Errors.
|
# Eval Errors.
|
||||||
nix-instantiate --eval -E 'let a = {} // a; in a.foo' 2>&1 | grep "infinite recursion encountered, at .*(string).*:1:15$"
|
eval_res=$(nix-instantiate --eval -E 'let a = {} // a; in a.foo' 2>&1 || true)
|
||||||
|
echo $eval_res | grep "(string) (1:15)"
|
||||||
|
echo $eval_res | grep "infinite recursion encountered"
|
||||||
|
|
Loading…
Reference in a new issue