diff options
Diffstat (limited to 'src')
30 files changed, 818 insertions, 369 deletions
diff --git a/src/download-via-ssh/download-via-ssh.cc b/src/download-via-ssh/download-via-ssh.cc index b64455eb1724..f71cf56507b8 100644 --- a/src/download-via-ssh/download-via-ssh.cc +++ b/src/download-via-ssh/download-via-ssh.cc @@ -9,6 +9,7 @@ #include "store-api.hh" #include <iostream> +#include <cstdlib> #include <unistd.h> using namespace nix; diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 5b9db6eeaa6e..298f6a3a60e3 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -104,6 +104,9 @@ static void printValue(std::ostream & str, std::set<const Value *> & active, con case tPrimOpApp: str << "<PRIMOP-APP>"; break; + case tExternal: + str << *v.external; + break; default: throw Error("invalid value"); } @@ -136,6 +139,7 @@ string showType(const Value & v) case tBlackhole: return "a black hole"; case tPrimOp: return "a built-in function"; case tPrimOpApp: return "a partially applied built-in function"; + case tExternal: return v.external->showType(); } abort(); } @@ -180,6 +184,7 @@ EvalState::EvalState(const Strings & _searchPath) , sFile(symbols.create("file")) , sLine(symbols.create("line")) , sColumn(symbols.create("column")) + , sFunctor(symbols.create("__functor")) , repair(false) , baseEnv(allocEnv(128)) , staticBaseEnv(false, 0) @@ -212,9 +217,9 @@ EvalState::EvalState(const Strings & _searchPath) allocated. This might be a problem on systems that don't overcommit. */ if (!getenv("GC_INITIAL_HEAP_SIZE")) { - size_t maxSize = 384 * 1024 * 1024; size_t size = 32 * 1024 * 1024; #if HAVE_SYSCONF && defined(_SC_PAGESIZE) && defined(_SC_PHYS_PAGES) + size_t maxSize = 384 * 1024 * 1024; long pageSize = sysconf(_SC_PAGESIZE); long pages = sysconf(_SC_PHYS_PAGES); if (pageSize != -1) @@ -242,7 +247,6 @@ EvalState::EvalState(const Strings & _searchPath) EvalState::~EvalState() { fileEvalCache.clear(); - printCanaries(); } @@ -312,11 +316,6 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const Symbol & sym, co throw EvalError(format(s) % sym % p1 % p2); } -LocalNoInlineNoReturn(void throwTypeError(const char * s)) -{ - throw TypeError(s); -} - LocalNoInlineNoReturn(void throwTypeError(const char * s, const Pos & pos)) { throw TypeError(format(s) % pos); @@ -327,11 +326,6 @@ LocalNoInlineNoReturn(void throwTypeError(const char * s, const string & s1)) throw TypeError(format(s) % s1); } -LocalNoInlineNoReturn(void throwTypeError(const char * s, const string & s1, const string & s2)) -{ - throw TypeError(format(s) % s1 % s2); -} - LocalNoInlineNoReturn(void throwTypeError(const char * s, const ExprLambda & fun, const Symbol & s2, const Pos & pos)) { throw TypeError(format(s) % fun.showNamePos() % s2 % pos); @@ -895,6 +889,17 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po return; } + if (fun.type == tAttrs) { + auto found = fun.attrs->find(sFunctor); + if (found != fun.attrs->end()) { + forceValue(*found->value); + Value * v2 = allocValue(); + callFunction(*found->value, fun, *v2, pos); + forceValue(*v2); + return callFunction(*v2, arg, v, pos); + } + } + if (fun.type != tLambda) throwTypeError("attempt to call something which is not a function but %1%, at %2%", fun, pos); @@ -1255,9 +1260,9 @@ void copyContext(const Value & v, PathSet & context) } -string EvalState::forceString(Value & v, PathSet & context) +string EvalState::forceString(Value & v, PathSet & context, const Pos & pos) { - string s = forceString(v); + string s = forceString(v, pos); copyContext(v, context); return s; } @@ -1312,6 +1317,9 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, return coerceToString(pos, *i->value, context, coerceMore, copyToStore); } + if (v.type == tExternal) + return v.external->coerceToString(pos, context, coerceMore, copyToStore); + if (coerceMore) { /* Note that `false' is represented as an empty string for @@ -1432,6 +1440,9 @@ bool EvalState::eqValues(Value & v1, Value & v2) case tPrimOpApp: return false; + case tExternal: + return *v1.external == *v2.external; + default: throwEvalError("cannot compare %1% with %2%", showType(v1), showType(v2)); } @@ -1502,26 +1513,6 @@ void EvalState::printStats() } -void EvalState::printCanaries() -{ -#if HAVE_BOEHMGC - if (!settings.get("debug-gc", false)) return; - - GC_gcollect(); - - if (gcCanaries.empty()) { - printMsg(lvlError, "all canaries have been garbage-collected"); - return; - } - - printMsg(lvlError, "the following canaries have not been garbage-collected:"); - - for (auto i : gcCanaries) - printMsg(lvlError, format(" %1%") % i->string.s); -#endif -} - - size_t valueSize(Value & v) { std::set<const void *> seen; @@ -1573,6 +1564,11 @@ size_t valueSize(Value & v) sz += doValue(*v.primOpApp.left); sz += doValue(*v.primOpApp.right); break; + case tExternal: + if (seen.find(v.external) != seen.end()) break; + seen.insert(v.external); + sz += v.external->valueSize(seen); + break; default: ; } @@ -1599,4 +1595,22 @@ size_t valueSize(Value & v) } +string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const +{ + throw TypeError(format("cannot coerce %1% to a string, at %2%") % + showType() % pos); +} + + +bool ExternalValueBase::operator==(const ExternalValueBase & b) const +{ + return false; +} + + +std::ostream & operator << (std::ostream & str, const ExternalValueBase & v) { + return v.print(str); +} + + } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index daf53846ffd5..f7415fb78dfd 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -39,10 +39,10 @@ public: typedef uint32_t size_t; private: - size_t size_, capacity; + size_t size_, capacity_; Attr attrs[0]; - Bindings(uint32_t capacity) : size_(0), capacity(capacity) { } + Bindings(size_t capacity) : size_(0), capacity_(capacity) { } Bindings(const Bindings & bindings) = delete; public: @@ -54,7 +54,7 @@ public: void push_back(const Attr & attr) { - assert(size_ < capacity); + assert(size_ < capacity_); attrs[size_++] = attr; } @@ -76,6 +76,8 @@ public: void sort(); + size_t capacity() { return capacity_; } + friend class EvalState; }; @@ -126,7 +128,7 @@ public: const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls, - sFile, sLine, sColumn; + sFile, sLine, sColumn, sFunctor; Symbol sDerivationNix; /* If set, force copying files to the Nix store even if they @@ -169,7 +171,7 @@ public: /* Look up a file in the search path. */ Path findFile(const string & path); - Path findFile(SearchPath & searchPath, const string & path); + Path findFile(SearchPath & searchPath, const string & path, const Pos & pos = noPos); /* Evaluate an expression to normal form, storing the result in value `v'. */ @@ -200,7 +202,7 @@ public: inline void forceList(Value & v, const Pos & pos); void forceFunction(Value & v, const Pos & pos); // either lambda or primop string forceString(Value & v, const Pos & pos = noPos); - string forceString(Value & v, PathSet & context); + string forceString(Value & v, PathSet & context, const Pos & pos = noPos); string forceStringNoCtx(Value & v, const Pos & pos = noPos); /* Return true iff the value `v' denotes a derivation (i.e. a @@ -287,8 +289,6 @@ public: /* Print statistics. */ void printStats(); - void printCanaries(); - private: unsigned long nrEnvs; @@ -320,12 +320,6 @@ private: friend struct ExprOpConcatLists; friend struct ExprSelect; friend void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v); - -#if HAVE_BOEHMGC - std::set<Value *> gcCanaries; - friend void canaryFinalizer(GC_PTR obj, GC_PTR client_data); - friend void prim_gcCanary(EvalState & state, const Pos & pos, Value * * args, Value & v); -#endif }; @@ -340,6 +334,9 @@ struct InvalidPathError : EvalError { Path path; InvalidPathError(const Path & path); +#ifdef EXCEPTION_NEEDS_THROW_SPEC + ~InvalidPathError() throw () { }; +#endif }; /* Realise all paths in `context' */ diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index c8521718b82a..50997e096fd1 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -16,6 +16,47 @@ std::ostream & operator << (std::ostream & str, Expr & e) return str; } +static void showString(std::ostream & str, const string & s) +{ + str << '"'; + for (auto c : (string) s) + if (c == '"' || c == '\\' || c == '$') str << "\\" << c; + else if (c == '\n') str << "\\n"; + else if (c == '\r') str << "\\r"; + else if (c == '\t') str << "\\t"; + else str << c; + str << '"'; +} + +static void showId(std::ostream & str, const string & s) +{ + assert(!s.empty()); + if (s == "if") + str << '"' << s << '"'; + else { + char c = s[0]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) { + showString(str, s); + return; + } + for (auto c : s) + if (!((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '_' || c == '\'' || c == '-')) { + showString(str, s); + return; + } + str << s; + } +} + +std::ostream & operator << (std::ostream & str, const Symbol & sym) +{ + showId(str, *sym.s); + return str; +} + void Expr::show(std::ostream & str) { abort(); @@ -28,7 +69,7 @@ void ExprInt::show(std::ostream & str) void ExprString::show(std::ostream & str) { - str << "\"" << s << "\""; // !!! escaping + showString(str, s); } void ExprPath::show(std::ostream & str) @@ -44,12 +85,12 @@ void ExprVar::show(std::ostream & str) void ExprSelect::show(std::ostream & str) { str << "(" << *e << ")." << showAttrPath(attrPath); - if (def) str << " or " << *def; + if (def) str << " or (" << *def << ")"; } void ExprOpHasAttr::show(std::ostream & str) { - str << "(" << *e << ") ? " << showAttrPath(attrPath); + str << "((" << *e << ") ? " << showAttrPath(attrPath) << ")"; } void ExprAttrs::show(std::ostream & str) @@ -85,6 +126,10 @@ void ExprLambda::show(std::ostream & str) str << i->name; if (i->def) str << " ? " << *i->def; } + if (formals->ellipsis) { + if (!first) str << ", "; + str << "..."; + } str << " }"; if (!arg.empty()) str << " @ "; } @@ -94,23 +139,24 @@ void ExprLambda::show(std::ostream & str) void ExprLet::show(std::ostream & str) { - str << "let "; + str << "(let "; foreach (ExprAttrs::AttrDefs::iterator, i, attrs->attrs) - if (i->second.inherited) + if (i->second.inherited) { str << "inherit " << i->first << "; "; + } else str << i->first << " = " << *i->second.e << "; "; - str << "in " << *body; + str << "in " << *body << ")"; } void ExprWith::show(std::ostream & str) { - str << "with " << *attrs << "; " << *body; + str << "(with " << *attrs << "; " << *body << ")"; } void ExprIf::show(std::ostream & str) { - str << "if " << *cond << " then " << *then << " else " << *else_; + str << "(if " << *cond << " then " << *then << " else " << *else_ << ")"; } void ExprAssert::show(std::ostream & str) @@ -120,16 +166,18 @@ void ExprAssert::show(std::ostream & str) void ExprOpNot::show(std::ostream & str) { - str << "! " << *e; + str << "(! " << *e << ")"; } void ExprConcatStrings::show(std::ostream & str) { bool first = true; + str << "("; foreach (vector<Expr *>::iterator, i, *es) { if (first) first = false; else str << " + "; str << **i; } + str << ")"; } void ExprPos::show(std::ostream & str) @@ -143,7 +191,7 @@ std::ostream & operator << (std::ostream & str, const Pos & pos) if (!pos) str << "undefined position"; else - str << (format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%") % pos.file % pos.line % pos.column).str(); + str << (format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%") % (string) pos.file % pos.line % pos.column).str(); return str; } diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 0eaa362fd68b..121dc58f25f7 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -280,7 +280,7 @@ struct ExprOpNot : Expr Expr##name(const Pos & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \ void show(std::ostream & str) \ { \ - str << *e1 << " " s " " << *e2; \ + str << "(" << *e1 << " " s " " << *e2 << ")"; \ } \ void bindVars(const StaticEnv & env) \ { \ diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index dcb270b862a3..7d877cd67862 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -53,10 +53,6 @@ namespace nix { #include "parser-tab.hh" #include "lexer-tab.hh" -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - YY_DECL; using namespace nix; @@ -630,7 +626,7 @@ Path EvalState::findFile(const string & path) } -Path EvalState::findFile(SearchPath & searchPath, const string & path) +Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos & pos) { foreach (SearchPath::iterator, i, searchPath) { Path res; @@ -645,7 +641,11 @@ Path EvalState::findFile(SearchPath & searchPath, const string & path) } if (pathExists(res)) return canonPath(res); } - throw ThrownError(format("file ‘%1%’ was not found in the Nix search path (add it using $NIX_PATH or -I)") % path); + format f = format( + "file ‘%1%’ was not found in the Nix search path (add it using $NIX_PATH or -I)" + + string(pos ? ", at %2%" : "")); + f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit); + throw ThrownError(f % path % pos); } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index a7884cb85521..6c30e6d549ec 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -35,7 +35,7 @@ std::pair<string, string> decodeContext(const string & s) size_t index = s.find("!", 1); return std::pair<string, string>(string(s, index + 1), string(s, 1, index - 1)); } else - return std::pair<string, string>(s, ""); + return std::pair<string, string>(s.at(0) == '/' ? s: string(s, 1), ""); } @@ -51,7 +51,7 @@ void realiseContext(const PathSet & context) assert(isStorePath(ctx)); if (!store->isValidPath(ctx)) throw InvalidPathError(ctx); - if (isDerivation(ctx)) + if (!decoded.second.empty() && isDerivation(ctx)) drvs.insert(decoded.first + "!" + decoded.second); } if (!drvs.empty()) { @@ -84,16 +84,19 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args Derivation drv = readDerivation(path); Value & w = *state.allocValue(); state.mkAttrs(w, 2 + drv.outputs.size()); - mkString(*state.allocAttr(w, state.sDrvPath), path, singleton<PathSet>("=" + path)); - state.mkList(*state.allocAttr(w, state.symbols.create("outputs")), drv.outputs.size()); + Value * v2 = state.allocAttr(w, state.sDrvPath); + mkString(*v2, path, singleton<PathSet>("=" + path)); + Value * outputsVal = + state.allocAttr(w, state.symbols.create("outputs")); + state.mkList(*outputsVal, drv.outputs.size()); unsigned int outputs_index = 0; - Value * outputsVal = w.attrs->find(state.symbols.create("outputs"))->value; - foreach (DerivationOutputs::iterator, i, drv.outputs) { - mkString(*state.allocAttr(w, state.symbols.create(i->first)), - i->second.path, singleton<PathSet>("!" + i->first + "!" + path)); - mkString(*(outputsVal->list.elems[outputs_index++] = state.allocValue()), - i->first); + for (const auto & o : drv.outputs) { + v2 = state.allocAttr(w, state.symbols.create(o.first)); + mkString(*v2, o.second.path, + singleton<PathSet>("!" + o.first + "!" + path)); + outputsVal->list.elems[outputs_index] = state.allocValue(); + mkString(*(outputsVal->list.elems[outputs_index++]), o.first); } w.attrs->sort(); Value fun; @@ -184,6 +187,9 @@ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Valu case tPrimOpApp: t = "lambda"; break; + case tExternal: + t = args[0]->external->typeOf(); + break; default: abort(); } mkString(v, state.symbols.create(t)); @@ -411,32 +417,6 @@ static void prim_trace(EvalState & state, const Pos & pos, Value * * args, Value } -#if HAVE_BOEHMGC -void canaryFinalizer(GC_PTR obj, GC_PTR client_data) -{ - Value * v = (Value *) obj; - EvalState & state(* (EvalState *) client_data); - printMsg(lvlError, format("canary ‘%1%’ garbage-collected") % v->string.s); - auto i = state.gcCanaries.find(v); - assert(i != state.gcCanaries.end()); - state.gcCanaries.erase(i); -} -#endif - - -void prim_gcCanary(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - string s = state.forceStringNoCtx(*args[0], pos); - state.mkList(v, 1); - Value * canary = v.list.elems[0] = state.allocValue(); -#if HAVE_BOEHMGC - state.gcCanaries.insert(canary); - GC_register_finalizer(canary, canaryFinalizer, &state, 0, 0); -#endif - mkString(*canary, s); -} - - void prim_valueSize(EvalState & state, const Pos & pos, Value * * args, Value & v) { /* We're not forcing the argument on purpose. */ @@ -750,8 +730,12 @@ static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Va { PathSet context; Path path = state.coerceToPath(pos, *args[0], context); - if (!context.empty()) - throw EvalError(format("string ‘%1%’ cannot refer to other paths, at %2%") % path % pos); + try { + realiseContext(context); + } catch (InvalidPathError & e) { + throw EvalError(format("cannot read ‘%1%’, since path ‘%2%’ is not valid, at %3%") + % path % e.path % pos); + } mkString(v, readFile(path).c_str()); } @@ -791,7 +775,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va % path % e.path % pos); } - mkPath(v, state.findFile(searchPath, path).c_str()); + mkPath(v, state.findFile(searchPath, path, pos).c_str()); } /* Read a directory (without . or ..) */ @@ -812,7 +796,7 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val for (auto & ent : entries) { Value * ent_val = state.allocAttr(v, state.symbols.create(ent.name)); if (ent.type == DT_UNKNOWN) - ent.type = getFileType(path); + ent.type = getFileType(path + "/" + ent.name); mkStringNoCopy(*ent_val, ent.type == DT_REG ? "regular" : ent.type == DT_DIR ? "directory" : @@ -867,7 +851,7 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu { PathSet context; string name = state.forceStringNoCtx(*args[0], pos); - string contents = state.forceString(*args[1], context); + string contents = state.forceString(*args[1], context, pos); PathSet refs; @@ -1424,10 +1408,37 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, throw Error(format("unknown hash type ‘%1%’, at %2%") % type % pos); PathSet context; // discarded - string s = state.forceString(*args[1], context); + string s = state.forceString(*args[1], context, pos); mkString(v, printHash(hashString(ht, s)), context); -}; +} + + +/* Match a regular expression against a string and return either + ‘null’ or a list containing substring matches. */ +static void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v) +{ + Regex regex(state.forceStringNoCtx(*args[0], pos), true); + + PathSet context; + string s = state.forceString(*args[1], context, pos); + + Regex::Subs subs; + if (!regex.matches(s, subs)) { + mkNull(v); + return; + } + + unsigned int len = subs.empty() ? 0 : subs.rbegin()->first + 1; + state.mkList(v, len); + for (unsigned int n = 0; n < len; ++n) { + auto i = subs.find(n); + if (i == subs.end()) + mkNull(*(v.list.elems[n] = state.allocValue())); + else + mkString(*(v.list.elems[n] = state.allocValue()), i->second); + } +} /************************************************************* @@ -1523,7 +1534,6 @@ void EvalState::createBaseEnv() // Debugging addPrimOp("__trace", 2, prim_trace); - addPrimOp("__gcCanary", 1, prim_gcCanary); addPrimOp("__valueSize", 1, prim_valueSize); // Paths @@ -1581,6 +1591,7 @@ void EvalState::createBaseEnv() addPrimOp("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext); addPrimOp("__unsafeDiscardOutputDependency", 1, prim_unsafeDiscardOutputDependency); addPrimOp("__hashString", 2, prim_hashString); + addPrimOp("__match", 2, prim_match); // Versions addPrimOp("__parseDrvName", 1, prim_parseDrvName); diff --git a/src/libexpr/symbol-table.hh b/src/libexpr/symbol-table.hh index 140662b51501..2fdf820211c8 100644 --- a/src/libexpr/symbol-table.hh +++ b/src/libexpr/symbol-table.hh @@ -58,12 +58,6 @@ public: friend std::ostream & operator << (std::ostream & str, const Symbol & sym); }; -inline std::ostream & operator << (std::ostream & str, const Symbol & sym) -{ - str << *sym.s; - return str; -} - class SymbolTable { private: diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc index d1ec9b566d66..cdb71341875a 100644 --- a/src/libexpr/value-to-json.cc +++ b/src/libexpr/value-to-json.cc @@ -80,10 +80,21 @@ void printValueAsJSON(EvalState & state, bool strict, break; } + case tExternal: + v.external->printValueAsJSON(state, strict, str, context); + break; + default: throw TypeError(format("cannot convert %1% to JSON") % showType(v)); } } +void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict, + std::ostream & str, PathSet & context) const +{ + throw TypeError(format("cannot convert %1% to JSON") % showType()); +} + + } diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc index 3934a83eec25..bbbb7039bf70 100644 --- a/src/libexpr/value-to-xml.cc +++ b/src/libexpr/value-to-xml.cc @@ -144,12 +144,23 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, break; } + case tExternal: + v.external->printValueAsXML(state, strict, location, doc, context, drvsSeen); + break; + default: doc.writeEmptyElement("unevaluated"); } } +void ExternalValueBase::printValueAsXML(EvalState & state, bool strict, + bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen) const +{ + doc.writeEmptyElement("unevaluated"); +} + + void printValueAsXML(EvalState & state, bool strict, bool location, Value & v, std::ostream & out, PathSet & context) { diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index 5f18f629e632..c06b5a6d1153 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -19,6 +19,7 @@ typedef enum { tBlackhole, tPrimOp, tPrimOpApp, + tExternal, } ValueType; @@ -29,10 +30,58 @@ struct ExprLambda; struct PrimOp; struct PrimOp; class Symbol; +struct Pos; +class EvalState; +class XMLWriter; typedef long NixInt; +/* External values must descend from ExternalValueBase, so that + * type-agnostic nix functions (e.g. showType) can be implemented + */ +class ExternalValueBase +{ + friend std::ostream & operator << (std::ostream & str, const ExternalValueBase & v); + protected: + /* Print out the value */ + virtual std::ostream & print(std::ostream & str) const = 0; + + public: + /* Return a simple string describing the type */ + virtual string showType() const = 0; + + /* Return a string to be used in builtins.typeOf */ + virtual string typeOf() const = 0; + + /* How much space does this value take up */ + virtual size_t valueSize(std::set<const void *> & seen) const = 0; + + /* Coerce the value to a string. Defaults to uncoercable, i.e. throws an + * error + */ + virtual string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const; + + /* Compare to another value of the same type. Defaults to uncomparable, + * i.e. always false. + */ + virtual bool operator==(const ExternalValueBase & b) const; + + /* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */ + virtual void printValueAsJSON(EvalState & state, bool strict, + std::ostream & str, PathSet & context) const; + + /* Print the value as XML. Defaults to unevaluated */ + virtual void printValueAsXML(EvalState & state, bool strict, bool location, + XMLWriter & doc, PathSet & context, PathSet & drvsSeen) const; + + virtual ~ExternalValueBase() + { + }; +}; + +std::ostream & operator << (std::ostream & str, const ExternalValueBase & v); + struct Value { @@ -88,6 +137,7 @@ struct Value struct { Value * left, * right; } primOpApp; + ExternalValueBase * external; }; }; diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index c4b5c210d5be..c83e997b2307 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -11,11 +11,14 @@ #include <exception> #include <algorithm> +#include <cstdlib> #include <sys/time.h> #include <sys/stat.h> #include <unistd.h> #include <signal.h> +extern char * * environ; + namespace nix { @@ -289,8 +292,10 @@ int handleExceptions(const string & programName, std::function<void()> fun) RunPager::RunPager() { - string pager = getEnv("PAGER"); - if (!isatty(STDOUT_FILENO) || pager.empty()) return; + if (!isatty(STDOUT_FILENO)) return; + char * pager = getenv("NIX_PAGER"); + if (!pager) pager = getenv("PAGER"); + if (pager && ((string) pager == "" || (string) pager == "cat")) return; /* Ignore SIGINT. The pager will handle it (and we'll get SIGPIPE). */ @@ -310,7 +315,11 @@ RunPager::RunPager() throw SysError("dupping stdin"); if (!getenv("LESS")) setenv("LESS", "FRSXMK", 1); - execl("/bin/sh", "sh", "-c", pager.c_str(), NULL); + if (pager) + execl("/bin/sh", "sh", "-c", pager, NULL); + execlp("pager", "pager", NULL); + execlp("less", "less", NULL); + execlp("more", "more", NULL); throw SysError(format("executing ‘%1%’") % pager); }); @@ -321,10 +330,14 @@ RunPager::RunPager() RunPager::~RunPager() { - if (pid != -1) { - std::cout.flush(); - close(STDOUT_FILENO); - pid.wait(true); + try { + if (pid != -1) { + std::cout.flush(); + close(STDOUT_FILENO); + pid.wait(true); + } + } catch (...) { + ignoreException(); } } diff --git a/src/libmain/stack.cc b/src/libmain/stack.cc index 64df95547e0b..41b617d98be2 100644 --- a/src/libmain/stack.cc +++ b/src/libmain/stack.cc @@ -18,7 +18,7 @@ static void sigsegvHandler(int signo, siginfo_t * info, void * ctx) the stack pointer. Unfortunately, getting the stack pointer is not portable. */ bool haveSP = true; - char * sp; + char * sp = 0; #if defined(__x86_64__) && defined(REG_RSP) sp = (char *) ((ucontext *) ctx)->uc_mcontext.gregs[REG_RSP]; #elif defined(REG_ESP) @@ -32,7 +32,7 @@ static void sigsegvHandler(int signo, siginfo_t * info, void * ctx) if (diff < 0) diff = -diff; if (diff < 4096) { char msg[] = "error: stack overflow (possible infinite recursion)\n"; - write(2, msg, strlen(msg)); + (void) write(2, msg, strlen(msg)); _exit(1); // maybe abort instead? } } diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 3c9db5f7a0e4..e0398e2fb4a3 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -50,6 +50,15 @@ #define CHROOT_ENABLED HAVE_CHROOT && HAVE_UNSHARE && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_PRIVATE) && defined(CLONE_NEWNS) +/* chroot-like behavior from Apple's sandbox */ +#if __APPLE__ + #define SANDBOX_ENABLED 1 + #define DEFAULT_ALLOWED_IMPURE_PREFIXES "/System/Library/Frameworks /usr/lib /dev /bin/sh" +#else + #define SANDBOX_ENABLED 0 + #define DEFAULT_ALLOWED_IMPURE_PREFIXES "/bin" "/usr/bin" +#endif + #if CHROOT_ENABLED #include <sys/socket.h> #include <sys/ioctl.h> @@ -84,8 +93,12 @@ class Goal; typedef std::shared_ptr<Goal> GoalPtr; typedef std::weak_ptr<Goal> WeakGoalPtr; +struct CompareGoalPtrs { + bool operator() (const GoalPtr & a, const GoalPtr & b); +}; + /* Set of goals. */ -typedef set<GoalPtr> Goals; +typedef set<GoalPtr, CompareGoalPtrs> Goals; typedef list<WeakGoalPtr> WeakGoals; /* A map of paths to goals (and the other way around). */ @@ -172,11 +185,20 @@ public: (important!), etc. */ virtual void cancel(bool timeout) = 0; + virtual string key() = 0; + protected: void amDone(ExitCode result); }; +bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) { + string s1 = a->key(); + string s2 = b->key(); + return s1 < s2; +} + + /* A mapping used to remember for each child process to what goal it belongs, and file descriptors for receiving log data and output path creation commands. */ @@ -303,6 +325,7 @@ public: void addToWeakGoals(WeakGoals & goals, GoalPtr p) { // FIXME: necessary? + // FIXME: O(n) foreach (WeakGoals::iterator, i, goals) if (i->lock() == p) return; goals.push_back(p); @@ -401,18 +424,6 @@ static void commonChildInit(Pipe & logPipe) } -/* Convert a string list to an array of char pointers. Careful: the - string list should outlive the array. */ -const char * * strings2CharPtrs(const Strings & ss) -{ - const char * * arr = new const char * [ss.size() + 1]; - const char * * p = arr; - foreach (Strings::const_iterator, i, ss) *p++ = i->c_str(); - *p = 0; - return arr; -} - - ////////////////////////////////////////////////////////////////////// @@ -767,6 +778,15 @@ public: void cancel(bool timeout); + string key() + { + /* Ensure that derivations get built in order of their name, + i.e. a derivation named "aardvark" always comes before + "baboon". And substitution goals always happen before + derivation goals (due to "b$"). */ + return "b$" + storePathToName(drvPath) + "$" + drvPath; + } + void work(); Path getDrvPath() @@ -793,8 +813,8 @@ private: /* Start building a derivation. */ void startBuilder(); - /* Initialise the builder's process. */ - void initChild(); + /* Run the builder's process. */ + void runChild(); friend int childEntry(void *); @@ -851,13 +871,9 @@ DerivationGoal::~DerivationGoal() { /* Careful: we should never ever throw an exception from a destructor. */ - try { - killChild(); - deleteTmpDir(false); - closeLogFile(); - } catch (...) { - ignoreException(); - } + try { killChild(); } catch (...) { ignoreException(); } + try { deleteTmpDir(false); } catch (...) { ignoreException(); } + try { closeLogFile(); } catch (...) { ignoreException(); } } @@ -928,6 +944,11 @@ void DerivationGoal::init() /* The first thing to do is to make sure that the derivation exists. If it doesn't, it may be created through a substitute. */ + if (buildMode == bmNormal && worker.store.isValidPath(drvPath)) { + haveDerivation(); + return; + } + addWaitee(worker.makeSubstitutionGoal(drvPath)); state = &DerivationGoal::haveDerivation; @@ -1574,6 +1595,13 @@ void chmod_(const Path & path, mode_t mode) } +int childEntry(void * arg) +{ + ((DerivationGoal *) arg)->runChild(); + return 1; +} + + void DerivationGoal::startBuilder() { startNest(nest, lvlInfo, format( @@ -1717,21 +1745,6 @@ void DerivationGoal::startBuilder() /* Change ownership of the temporary build directory. */ if (chown(tmpDir.c_str(), buildUser.getUID(), buildUser.getGID()) == -1) throw SysError(format("cannot change ownership of ‘%1%’") % tmpDir); - - /* Check that the Nix store has the appropriate permissions, - i.e., owned by root and mode 1775 (sticky bit on so that - the builder can create its output but not mess with the - outputs of other processes). */ - struct stat st; - if (stat(settings.nixStore.c_str(), &st) == -1) - throw SysError(format("cannot stat ‘%1%’") % settings.nixStore); - if (!(st.st_mode & S_ISVTX) || - ((st.st_mode & S_IRWXG) != S_IRWXG) || - (st.st_gid != buildUser.getGID())) - throw Error(format( - "builder does not have write permission to ‘%2%’; " - "try ‘chgrp %1% %2%; chmod 1775 %2%’") - % buildUser.getGID() % settings.nixStore); } @@ -1748,6 +1761,47 @@ void DerivationGoal::startBuilder() if (get(drv.env, "__noChroot") == "1") useChroot = false; if (useChroot) { + /* Allow a user-configurable set of directories from the + host file system. */ + PathSet dirs = tokenizeString<StringSet>(settings.get("build-chroot-dirs", string(DEFAULT_CHROOT_DIRS))); + PathSet dirs2 = tokenizeString<StringSet>(settings.get("build-extra-chroot-dirs", string(""))); + dirs.insert(dirs2.begin(), dirs2.end()); + + for (auto & i : dirs) { + size_t p = i.find('='); + if (p == string::npos) + dirsInChroot[i] = i; + else + dirsInChroot[string(i, 0, p)] = string(i, p + 1); + } + dirsInChroot[tmpDir] = tmpDir; + + string allowed = settings.get("allowed-impure-host-deps", string(DEFAULT_ALLOWED_IMPURE_PREFIXES)); + PathSet allowedPaths = tokenizeString<StringSet>(allowed); + + /* This works like the above, except on a per-derivation level */ + Strings impurePaths = tokenizeString<Strings>(get(drv.env, "__impureHostDeps")); + + for (auto & i : impurePaths) { + bool found = false; + /* Note: we're not resolving symlinks here to prevent + giving a non-root user info about inaccessible + files. */ + Path canonI = canonPath(i); + /* If only we had a trie to do this more efficiently :) luckily, these are generally going to be pretty small */ + for (auto & a : allowedPaths) { + Path canonA = canonPath(a); + if (canonI == canonA || isInDir(canonI, canonA)) { + found = true; + break; + } + } + if (!found) + throw Error(format("derivation '%1%' requested impure path ‘%2%’, but it was not in allowed-impure-host-deps (‘%3%’)") % drvPath % i % allowed); + + dirsInChroot[i] = i; + } + #if CHROOT_ENABLED /* Create a temporary directory in which we set up the chroot environment using bind-mounts. We put it in the Nix store @@ -1789,20 +1843,6 @@ void DerivationGoal::startBuilder() /* Create /etc/hosts with localhost entry. */ writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n"); - /* Bind-mount a user-configurable set of directories from the - host file system. */ - PathSet dirs = tokenizeString<StringSet>(settings.get("build-chroot-dirs", string(DEFAULT_CHROOT_DIRS))); - PathSet dirs2 = tokenizeString<StringSet>(settings.get("build-extra-chroot-dirs", string(""))); - dirs.insert(dirs2.begin(), dirs2.end()); - for (auto & i : dirs) { - size_t p = i.find('='); - if (p == string::npos) - dirsInChroot[i] = i; - else - dirsInChroot[string(i, 0, p)] = string(i, p + 1); - } - dirsInChroot[tmpDir] = tmpDir; - /* Make the closure of the inputs available in the chroot, rather than the whole Nix store. This prevents any access to undeclared dependencies. Directories are bind-mounted, @@ -1846,6 +1886,9 @@ void DerivationGoal::startBuilder() foreach (DerivationOutputs::iterator, i, drv.outputs) dirsInChroot.erase(i->second.path); +#elif SANDBOX_ENABLED + /* We don't really have any parent prep work to do (yet?) + All work happens in the child, instead. */ #else throw Error("chroot builds are not supported on this platform"); #endif @@ -1890,9 +1933,61 @@ void DerivationGoal::startBuilder() builderOut.create(); /* Fork a child to build the package. */ - pid = startProcess([&]() { - initChild(); - }); +#if CHROOT_ENABLED + if (useChroot) { + /* Set up private namespaces for the build: + + - The PID namespace causes the build to start as PID 1. + Processes outside of the chroot are not visible to those + on the inside, but processes inside the chroot are + visible from the outside (though with different PIDs). + + - The private mount namespace ensures that all the bind + mounts we do will only show up in this process and its + children, and will disappear automatically when we're + done. + + - The private network namespace ensures that the builder + cannot talk to the outside world (or vice versa). It + only has a private loopback interface. + + - The IPC namespace prevents the builder from communicating + with outside processes using SysV IPC mechanisms (shared + memory, message queues, semaphores). It also ensures + that all IPC objects are destroyed when the builder + exits. + + - The UTS namespace ensures that builders see a hostname of + localhost rather than the actual hostname. + + We use a helper process to do the clone() to work around + clone() being broken in multi-threaded programs due to + at-fork handlers not being run. Note that we use + CLONE_PARENT to ensure that the real builder is parented to + us. + */ + Pid helper = startProcess([&]() { + char stack[32 * 1024]; + pid_t child = clone(childEntry, stack + sizeof(stack) - 8, + CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD, this); + if (child == -1) throw SysError("cloning builder process"); + writeFull(builderOut.writeSide, int2String(child) + "\n"); + _exit(0); + }); + if (helper.wait(true) != 0) + throw Error("unable to start build process"); + pid_t tmp; + if (!string2Int<pid_t>(readLine(builderOut.readSide), tmp)) abort(); + pid = tmp; + } else +#endif + { + ProcessOptions options; + options.allowVfork = !buildUser.enabled(); + pid = startProcess([&]() { + runChild(); + }, options); + } /* parent */ pid.setSeparatePG(true); @@ -1908,11 +2003,10 @@ void DerivationGoal::startBuilder() printMsg(lvlError, format("@ build-started %1% - %2% %3%") % drvPath % drv.platform % logFile); } - } -void DerivationGoal::initChild() +void DerivationGoal::runChild() { /* Warning: in the child we should absolutely not make any SQLite calls! */ @@ -1924,36 +2018,6 @@ void DerivationGoal::initChild() #if CHROOT_ENABLED if (useChroot) { - /* Set up private namespaces for the build: - - - The PID namespace causes the build to start as PID 1. - Processes outside of the chroot are not visible to - those on the inside, but processes inside the chroot - are visible from the outside (though with different - PIDs). - - - The private mount namespace ensures that all the bind - mounts we do will only show up in this process and - its children, and will disappear automatically when - we're done. - - - The private network namespace ensures that the - builder cannot talk to the outside world (or vice - versa). It only has a private loopback interface. - - - The IPC namespace prevents the builder from - communicating with outside processes using SysV IPC - mechanisms (shared memory, message queues, - semaphores). It also ensures that all IPC objects - are destroyed when the builder exits. - - - The UTS namespace ensures that builders see a - hostname of localhost rather than the actual - hostname. - */ - if (unshare(CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC | CLONE_NEWUTS) == -1) - throw SysError("setting up private namespaces"); - /* Initialise the loopback interface. */ AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)); if (fd == -1) throw SysError("cannot open IP socket"); @@ -1968,9 +2032,11 @@ void DerivationGoal::initChild() /* Set the hostname etc. to fixed values. */ char hostname[] = "localhost"; - sethostname(hostname, sizeof(hostname)); + if (sethostname(hostname, sizeof(hostname)) == -1) + throw SysError("cannot set host name"); char domainname[] = "(none)"; // kernel default - setdomainname(domainname, sizeof(domainname)); + if (setdomainname(domainname, sizeof(domainname)) == -1) + throw SysError("cannot set domain name"); /* Make all filesystems private. This is necessary because subtrees may have been mounted as "shared" @@ -2032,8 +2098,7 @@ void DerivationGoal::initChild() throw SysError(format("bind mount from ‘%1%’ to ‘%2%’ failed") % source % target); } - /* Bind a new instance of procfs on /proc to reflect our - private PID namespace. */ + /* Bind a new instance of procfs on /proc. */ createDirs(chrootRootDir + "/proc"); if (mount("none", (chrootRootDir + "/proc").c_str(), "proc", 0, 0) == -1) throw SysError("mounting /proc"); @@ -2084,7 +2149,7 @@ void DerivationGoal::initChild() if (drv.platform == "i686-linux" && (settings.thisSystem == "x86_64-linux" || (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64")))) { - if (personality(PER_LINUX32_3GB) == -1) + if (personality(PER_LINUX32) == -1) throw SysError("cannot set i686-linux personality"); } @@ -2105,11 +2170,7 @@ void DerivationGoal::initChild() Strings envStrs; foreach (Environment::const_iterator, i, env) envStrs.push_back(rewriteHashes(i->first + "=" + i->second, rewritesToTmp)); - const char * * envArr = strings2CharPtrs(envStrs); - - Path program = drv.builder.c_str(); - std::vector<const char *> args; /* careful with c_str()! */ - string user; /* must be here for its c_str()! */ + auto envArr = stringsToCharPtrs(envStrs); /* If we are running in `build-users' mode, then switch to the user we allocated above. Make sure that we drop all root @@ -2118,8 +2179,6 @@ void DerivationGoal::initChild() setuid() when run as root sets the real, effective and saved UIDs. */ if (buildUser.enabled()) { - printMsg(lvlChatty, format("switching to user ‘%1%’") % buildUser.getUser()); - if (setgroups(0, 0) == -1) throw SysError("cannot clear the set of supplementary groups"); @@ -2135,29 +2194,146 @@ void DerivationGoal::initChild() } /* Fill in the arguments. */ - string builderBasename = baseNameOf(drv.builder); - args.push_back(builderBasename.c_str()); - foreach (Strings::iterator, i, drv.args) { - auto re = rewriteHashes(*i, rewritesToTmp); - auto cstr = new char[re.length()+1]; - std::strcpy(cstr, re.c_str()); - - args.push_back(cstr); + Strings args; + + const char *builder = "invalid"; + + string sandboxProfile; + if (useChroot && SANDBOX_ENABLED) { + /* Lots and lots and lots of file functions freak out if they can't stat their full ancestry */ + PathSet ancestry; + + /* We build the ancestry before adding all inputPaths to the store because we know they'll + all have the same parents (the store), and there might be lots of inputs. This isn't + particularly efficient... I doubt it'll be a bottleneck in practice */ + for (auto & i : dirsInChroot) { + Path cur = i.first; + while (cur.compare("/") != 0) { + cur = dirOf(cur); + ancestry.insert(cur); + } + } + + /* And we want the store in there regardless of how empty dirsInChroot. We include the innermost + path component this time, since it's typically /nix/store and we care about that. */ + Path cur = settings.nixStore; + while (cur.compare("/") != 0) { + ancestry.insert(cur); + cur = dirOf(cur); + } + + /* Add all our input paths to the chroot */ + for (auto & i : inputPaths) + dirsInChroot[i] = i; + + + /* TODO: we should factor out the policy cleanly, so we don't have to repeat the constants every time... */ + sandboxProfile += "(version 1)\n"; + + /* Violations will go to the syslog if you set this. Unfortunately the destination does not appear to be configurable */ + if (settings.get("darwin-log-sandbox-violations", false)) { + sandboxProfile += "(deny default)\n"; + } else { + sandboxProfile += "(deny default (with no-log))\n"; + } + + sandboxProfile += "(allow file-read* file-write-data (literal \"/dev/null\"))\n"; + + sandboxProfile += "(allow file-read-metadata\n" + "\t(literal \"/var\")\n" + "\t(literal \"/tmp\")\n" + "\t(literal \"/etc\")\n" + "\t(literal \"/etc/nix\")\n" + "\t(literal \"/etc/nix/nix.conf\"))\n"; + + /* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try different mechanisms + to find temporary directories, so we want to open up a broader place for them to dump their files, if needed. */ + Path globalTmpDir = canonPath(getEnv("TMPDIR", "/tmp"), true); + + /* They don't like trailing slashes on subpath directives */ + if (globalTmpDir.back() == '/') globalTmpDir.pop_back(); + + /* This is where our temp folders are and where most of the building will happen, so we want rwx on it. */ + sandboxProfile += (format("(allow file-read* file-write* process-exec (subpath \"%1%\") (subpath \"/private/tmp\"))\n") % globalTmpDir).str(); + + sandboxProfile += "(allow process-fork)\n"; + sandboxProfile += "(allow sysctl-read)\n"; + sandboxProfile += "(allow signal (target same-sandbox))\n"; + + /* Enables getpwuid (used by git and others) */ + sandboxProfile += "(allow mach-lookup (global-name \"com.apple.system.notification_center\") (global-name \"com.apple.system.opendirectoryd.libinfo\"))\n"; + + + /* Our rwx outputs */ + sandboxProfile += "(allow file-read* file-write* process-exec\n"; + for (auto & i : missingPaths) { + sandboxProfile += (format("\t(subpath \"%1%\")\n") % i.c_str()).str(); + } + sandboxProfile += ")\n"; + + /* Our inputs (transitive dependencies and any impurities computed above) + Note that the sandbox profile allows file-write* even though it isn't seemingly necessary. First of all, nix's standard user permissioning + mechanism still prevents builders from writing to input directories, so no security/purity is lost. The reason we allow file-write* is that + denying it means the `access` syscall will return EPERM instead of EACCESS, which confuses a few programs that assume (understandably, since + it appears to be a violation of the POSIX spec) that `access` won't do that, and don't deal with it nicely if it does. The most notable of + these is the entire GHC Haskell ecosystem. */ + sandboxProfile += "(allow file-read* file-write* process-exec\n"; + for (auto & i : dirsInChroot) { + if (i.first != i.second) + throw SysError(format("can't map '%1%' to '%2%': mismatched impure paths not supported on darwin")); + + string path = i.first; + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting attributes of path ‘%1%’") % path); + if (S_ISDIR(st.st_mode)) + sandboxProfile += (format("\t(subpath \"%1%\")\n") % path).str(); + else + sandboxProfile += (format("\t(literal \"%1%\")\n") % path).str(); + } + sandboxProfile += ")\n"; + + /* Our ancestry. N.B: this uses literal on folders, instead of subpath. Without that, + you open up the entire filesystem because you end up with (subpath "/") */ + sandboxProfile += "(allow file-read-metadata\n"; + for (auto & i : ancestry) { + sandboxProfile += (format("\t(literal \"%1%\")\n") % i.c_str()).str(); + } + sandboxProfile += ")\n"; + + builder = "/usr/bin/sandbox-exec"; + args.push_back("sandbox-exec"); + args.push_back("-p"); + args.push_back(sandboxProfile); + args.push_back(drv.builder); + } else { + builder = drv.builder.c_str(); + string builderBasename = baseNameOf(drv.builder); + args.push_back(builderBasename); } - args.push_back(0); + + foreach (Strings::iterator, i, drv.args) + args.push_back(rewriteHashes(*i, rewritesToTmp)); + auto argArr = stringsToCharPtrs(args); restoreSIGPIPE(); /* Indicate that we managed to set up the build environment. */ - writeToStderr("\n"); + writeFull(STDERR_FILENO, "\n"); + + /* This needs to be after that fateful '\n', and I didn't want to duplicate code */ + if (useChroot && SANDBOX_ENABLED) { + printMsg(lvlDebug, "Generated sandbox profile:"); + printMsg(lvlDebug, sandboxProfile); + } /* Execute the program. This should not return. */ - execve(program.c_str(), (char * *) &args[0], (char * *) envArr); + execve(builder, (char * *) &argArr[0], (char * *) &envArr[0]); throw SysError(format("executing ‘%1%’") % drv.builder); } catch (std::exception & e) { - writeToStderr("while setting up the build environment: " + string(e.what()) + "\n"); + writeFull(STDERR_FILENO, "while setting up the build environment: " + string(e.what()) + "\n"); _exit(1); } } @@ -2308,7 +2484,7 @@ void DerivationGoal::registerOutputs() if (buildMode == bmCheck) { ValidPathInfo info = worker.store.queryPathInfo(path); if (hash.first != info.hash) - throw Error(format("derivation ‘%2%’ may not be deterministic: hash mismatch in output ‘%1%’") % drvPath % path); + throw Error(format("derivation ‘%1%’ may not be deterministic: hash mismatch in output ‘%2%’") % drvPath % path); continue; } @@ -2470,7 +2646,7 @@ void DerivationGoal::handleChildOutput(int fd, const string & data) BZ2_bzWrite(&err, bzLogFile, (unsigned char *) data.data(), data.size()); if (err != BZ_OK) throw Error(format("cannot write to compressed log file (BZip2 error = %1%)") % err); } else if (fdLogFile != -1) - writeFull(fdLogFile, (unsigned char *) data.data(), data.size()); + writeFull(fdLogFile, data); } if (hook && fd == hook->fromHook.readSide) @@ -2581,6 +2757,13 @@ public: void cancel(bool timeout); + string key() + { + /* "a$" ensures substitution goals happen before derivation + goals. */ + return "a$" + storePathToName(storePath) + "$" + storePath; + } + void work(); /* The states. */ @@ -2773,7 +2956,7 @@ void SubstitutionGoal::tryToRun() args.push_back("--substitute"); args.push_back(storePath); args.push_back(destPath); - const char * * argArr = strings2CharPtrs(args); + auto argArr = stringsToCharPtrs(args); /* Fork the substitute program. */ pid = startProcess([&]() { @@ -2783,7 +2966,7 @@ void SubstitutionGoal::tryToRun() if (dup2(outPipe.writeSide, STDOUT_FILENO) == -1) throw SysError("cannot dup output pipe into stdout"); - execv(sub.c_str(), (char * *) argArr); + execv(sub.c_str(), (char * *) &argArr[0]); throw SysError(format("executing ‘%1%’") % sub); }); @@ -3091,15 +3274,19 @@ void Worker::run(const Goals & _topGoals) checkInterrupt(); - /* Call every wake goal. */ + /* Call every wake goal (in the ordering established by + CompareGoalPtrs). */ while (!awake.empty() && !topGoals.empty()) { - WeakGoals awake2(awake); + Goals awake2; + for (auto & i : awake) { + GoalPtr goal = i.lock(); + if (goal) awake2.insert(goal); + } awake.clear(); - foreach (WeakGoals::iterator, i, awake2) { + for (auto & goal : awake2) { checkInterrupt(); - GoalPtr goal = i->lock(); - if (goal) goal->work(); - if (topGoals.empty()) break; + goal->work(); + if (topGoals.empty()) break; // stuff may have been cancelled } } diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index e9db711e7ca0..7959a592d987 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -145,11 +145,6 @@ Path addPermRoot(StoreAPI & store, const Path & _storePath, } -/* The file to which we write our temporary roots. */ -static Path fnTempRoots; -static AutoCloseFD fdTempRoots; - - void LocalStore::addTempRoot(const Path & path) { /* Create the temporary roots file for this process. */ @@ -196,7 +191,7 @@ void LocalStore::addTempRoot(const Path & path) lockFile(fdTempRoots, ltWrite, true); string s = path + '\0'; - writeFull(fdTempRoots, (const unsigned char *) s.data(), s.size()); + writeFull(fdTempRoots, s); /* Downgrade to a read lock. */ debug(format("downgrading to read lock on ‘%1%’") % fnTempRoots); @@ -204,27 +199,6 @@ void LocalStore::addTempRoot(const Path & path) } -void removeTempRoots() -{ - if (fdTempRoots != -1) { - fdTempRoots.close(); - unlink(fnTempRoots.c_str()); - } -} - - -/* Automatically clean up the temporary roots file when we exit. */ -struct RemoveTempRoots -{ - ~RemoveTempRoots() - { - removeTempRoots(); - } -}; - -static RemoveTempRoots autoRemoveTempRoots __attribute__((unused)); - - typedef std::shared_ptr<AutoCloseFD> FDPtr; typedef list<FDPtr> FDs; @@ -257,7 +231,7 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds) if (lockFile(*fd, ltWrite, false)) { printMsg(lvlError, format("removing stale temporary roots file ‘%1%’") % path); unlink(path.c_str()); - writeFull(*fd, (const unsigned char *) "d", 1); + writeFull(*fd, "d"); continue; } @@ -355,7 +329,8 @@ Roots LocalStore::findRoots() /* Process direct roots in {gcroots,manifests,profiles}. */ nix::findRoots(*this, settings.nixStateDir + "/" + gcRootsDir, DT_UNKNOWN, roots); - nix::findRoots(*this, settings.nixStateDir + "/manifests", DT_UNKNOWN, roots); + if (pathExists(settings.nixStateDir + "/manifests")) + nix::findRoots(*this, settings.nixStateDir + "/manifests", DT_UNKNOWN, roots); nix::findRoots(*this, settings.nixStateDir + "/profiles", DT_UNKNOWN, roots); return roots; @@ -749,7 +724,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) } /* While we're at it, vacuum the database. */ - if (options.action == GCOptions::gcDeleteDead) vacuumDB(); + //if (options.action == GCOptions::gcDeleteDead) vacuumDB(); } diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index f08c877fe3d7..bc792baf296b 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -256,20 +256,23 @@ LocalStore::LocalStore(bool reserveSpace) if (chmod(perUserDir.c_str(), 01777) == -1) throw SysError(format("could not set permissions on ‘%1%’ to 1777") % perUserDir); + mode_t perm = 01735; + struct group * gr = getgrnam(settings.buildUsersGroup.c_str()); if (!gr) - throw Error(format("the group ‘%1%’ specified in ‘build-users-group’ does not exist") + printMsg(lvlError, format("warning: the group ‘%1%’ specified in ‘build-users-group’ does not exist") % settings.buildUsersGroup); - - struct stat st; - if (stat(settings.nixStore.c_str(), &st)) - throw SysError(format("getting attributes of path ‘%1%’") % settings.nixStore); - - if (st.st_uid != 0 || st.st_gid != gr->gr_gid || (st.st_mode & ~S_IFMT) != 01775) { - if (chown(settings.nixStore.c_str(), 0, gr->gr_gid) == -1) - throw SysError(format("changing ownership of path ‘%1%’") % settings.nixStore); - if (chmod(settings.nixStore.c_str(), 01775) == -1) - throw SysError(format("changing permissions on path ‘%1%’") % settings.nixStore); + else { + struct stat st; + if (stat(settings.nixStore.c_str(), &st)) + throw SysError(format("getting attributes of path ‘%1%’") % settings.nixStore); + + if (st.st_uid != 0 || st.st_gid != gr->gr_gid || (st.st_mode & ~S_IFMT) != perm) { + if (chown(settings.nixStore.c_str(), 0, gr->gr_gid) == -1) + throw SysError(format("changing ownership of path ‘%1%’") % settings.nixStore); + if (chmod(settings.nixStore.c_str(), perm) == -1) + throw SysError(format("changing permissions on path ‘%1%’") % settings.nixStore); + } } } @@ -358,7 +361,17 @@ LocalStore::~LocalStore() i->second.to.close(); i->second.from.close(); i->second.error.close(); - i->second.pid.wait(true); + if (i->second.pid != -1) + i->second.pid.wait(true); + } + } catch (...) { + ignoreException(); + } + + try { + if (fdTempRoots != -1) { + fdTempRoots.close(); + unlink(fnTempRoots.c_str()); } } catch (...) { ignoreException(); @@ -489,7 +502,7 @@ void LocalStore::makeStoreWritable() if (unshare(CLONE_NEWNS) == -1) throw SysError("setting up a private mount namespace"); - if (mount(0, settings.nixStore.c_str(), 0, MS_REMOUNT | MS_BIND, 0) == -1) + if (mount(0, settings.nixStore.c_str(), "none", MS_REMOUNT | MS_BIND, 0) == -1) throw SysError(format("remounting %1% writable") % settings.nixStore); } #endif diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh index dccdba533a0c..e0aabdba420d 100644 --- a/src/libstore/local-store.hh +++ b/src/libstore/local-store.hh @@ -244,6 +244,10 @@ private: bool didSetSubstituterEnv; + /* The file to which we write our temporary roots. */ + Path fnTempRoots; + AutoCloseFD fdTempRoots; + int getSchema(); void openDB(bool create); diff --git a/src/libstore/optimise-store.cc b/src/libstore/optimise-store.cc index dd18d66fa008..55c252b9b2e3 100644 --- a/src/libstore/optimise-store.cc +++ b/src/libstore/optimise-store.cc @@ -4,6 +4,7 @@ #include "local-store.hh" #include "globals.hh" +#include <cstdlib> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> diff --git a/src/libstore/pathlocks.cc b/src/libstore/pathlocks.cc index f26684afacb9..9db37e8f9aaa 100644 --- a/src/libstore/pathlocks.cc +++ b/src/libstore/pathlocks.cc @@ -33,7 +33,7 @@ void deleteLockFile(const Path & path, int fd) other processes waiting on this lock that the lock is stale (deleted). */ unlink(path.c_str()); - writeFull(fd, (const unsigned char *) "d", 1); + writeFull(fd, "d"); /* Note that the result of unlink() is ignored; removing the lock file is an optimisation, not a necessity. */ } diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index a0e9f22410f7..d08913246321 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -10,6 +10,7 @@ #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> +#include <errno.h> #include <fcntl.h> #include <iostream> @@ -87,8 +88,7 @@ void RemoteStore::openConnection(bool reserveSpace) processStderr(); } catch (Error & e) { - throw Error(format("cannot start worker (%1%)") - % e.msg()); + throw Error(format("cannot start daemon worker: %1%") % e.msg()); } setOptions(); @@ -110,7 +110,7 @@ void RemoteStore::connectToDaemon() applications... */ AutoCloseFD fdPrevDir = open(".", O_RDONLY); if (fdPrevDir == -1) throw SysError("couldn't open current directory"); - chdir(dirOf(socketPath).c_str()); + if (chdir(dirOf(socketPath).c_str()) == -1) throw SysError(format("couldn't change to directory of ‘%1%’") % socketPath); Path socketPathRel = "./" + baseNameOf(socketPath); struct sockaddr_un addr; diff --git a/src/libutil/monitor-fd.hh b/src/libutil/monitor-fd.hh index 72d23fb6934c..6f01ccd91a43 100644 --- a/src/libutil/monitor-fd.hh +++ b/src/libutil/monitor-fd.hh @@ -3,6 +3,7 @@ #include <thread> #include <atomic> +#include <cstdlib> #include <poll.h> #include <sys/types.h> #include <unistd.h> diff --git a/src/libutil/regex.cc b/src/libutil/regex.cc index 36c8458cee08..84274b3e1da9 100644 --- a/src/libutil/regex.cc +++ b/src/libutil/regex.cc @@ -1,13 +1,16 @@ #include "regex.hh" #include "types.hh" +#include <algorithm> + namespace nix { -Regex::Regex(const string & pattern) +Regex::Regex(const string & pattern, bool subs) { /* Patterns must match the entire string. */ - int err = regcomp(&preg, ("^(" + pattern + ")$").c_str(), REG_NOSUB | REG_EXTENDED); - if (err) throw Error(format("compiling pattern ‘%1%’: %2%") % pattern % showError(err)); + int err = regcomp(&preg, ("^(" + pattern + ")$").c_str(), (subs ? 0 : REG_NOSUB) | REG_EXTENDED); + if (err) throw RegexError(format("compiling pattern ‘%1%’: %2%") % pattern % showError(err)); + nrParens = subs ? std::count(pattern.begin(), pattern.end(), '(') : 0; } Regex::~Regex() @@ -23,6 +26,20 @@ bool Regex::matches(const string & s) throw Error(format("matching string ‘%1%’: %2%") % s % showError(err)); } +bool Regex::matches(const string & s, Subs & subs) +{ + regmatch_t pmatch[nrParens + 2]; + int err = regexec(&preg, s.c_str(), nrParens + 2, pmatch, 0); + if (err == 0) { + for (unsigned int n = 2; n < nrParens + 2; ++n) + if (pmatch[n].rm_eo != -1) + subs[n - 2] = string(s, pmatch[n].rm_so, pmatch[n].rm_eo - pmatch[n].rm_so); + return true; + } + else if (err == REG_NOMATCH) return false; + throw Error(format("matching string ‘%1%’: %2%") % s % showError(err)); +} + string Regex::showError(int err) { char buf[256]; diff --git a/src/libutil/regex.hh b/src/libutil/regex.hh index aa012b721cb7..53e31f4edc4a 100644 --- a/src/libutil/regex.hh +++ b/src/libutil/regex.hh @@ -5,16 +5,23 @@ #include <sys/types.h> #include <regex.h> +#include <map> + namespace nix { +MakeError(RegexError, Error) + class Regex { public: - Regex(const string & pattern); + Regex(const string & pattern, bool subs = false); ~Regex(); bool matches(const string & s); + typedef std::map<unsigned int, string> Subs; + bool matches(const string & s, Subs & subs); private: + unsigned nrParens; regex_t preg; string showError(int err); }; diff --git a/src/libutil/types.hh b/src/libutil/types.hh index 906a959e3079..160884ee1ad7 100644 --- a/src/libutil/types.hh +++ b/src/libutil/types.hh @@ -8,6 +8,15 @@ #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 { @@ -39,8 +48,12 @@ protected: public: unsigned int status; // exit status BaseError(const FormatOrString & fs, unsigned int status = 1); +#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); diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 99d2b1e0adce..0d903f2f0d43 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -193,8 +193,12 @@ Path readLink(const Path & path) if (!S_ISLNK(st.st_mode)) throw Error(format("‘%1%’ is not a symlink") % path); char buf[st.st_size]; - if (readlink(path.c_str(), buf, st.st_size) != st.st_size) + ssize_t rlsize = readlink(path.c_str(), buf, st.st_size); + if (rlsize == -1) throw SysError(format("reading symbolic link ‘%1%’") % path); + else if (rlsize > st.st_size) + throw Error(format("symbolic link ‘%1%’ size overflow %2% > %3%") + % path % rlsize % st.st_size); return string(buf, st.st_size); } @@ -265,7 +269,7 @@ void writeFile(const Path & path, const string & s) AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666); if (fd == -1) throw SysError(format("opening file ‘%1%’") % path); - writeFull(fd, (unsigned char *) s.data(), s.size()); + writeFull(fd, s); } @@ -292,7 +296,7 @@ string readLine(int fd) void writeLine(int fd, string s) { s += '\n'; - writeFull(fd, (const unsigned char *) s.data(), s.size()); + writeFull(fd, s); } @@ -483,18 +487,13 @@ void warnOnce(bool & haveWarned, const FormatOrString & fs) } -static void defaultWriteToStderr(const unsigned char * buf, size_t count) -{ - writeFull(STDERR_FILENO, buf, count); -} - - void writeToStderr(const string & s) { try { - auto p = _writeToStderr; - if (!p) p = defaultWriteToStderr; - p((const unsigned char *) s.data(), s.size()); + if (_writeToStderr) + _writeToStderr((const unsigned char *) s.data(), s.size()); + else + writeFull(STDERR_FILENO, s); } catch (SysError & e) { /* Ignore failing writes to stderr if we're in an exception handler, otherwise throw an exception. We need to ignore @@ -506,7 +505,7 @@ void writeToStderr(const string & s) } -void (*_writeToStderr) (const unsigned char * buf, size_t count) = defaultWriteToStderr; +void (*_writeToStderr) (const unsigned char * buf, size_t count) = 0; void readFull(int fd, unsigned char * buf, size_t count) @@ -540,6 +539,12 @@ void writeFull(int fd, const unsigned char * buf, size_t count) } +void writeFull(int fd, const string & s) +{ + writeFull(fd, (const unsigned char *) s.data(), s.size()); +} + + string drainFD(int fd) { string result; @@ -825,6 +830,9 @@ void killUser(uid_t uid) users to which the current process can send signals. So we fork a process, switch to uid, and send a mass kill. */ + ProcessOptions options; + options.allowVfork = false; + Pid pid = startProcess([&]() { if (setuid(uid) == -1) @@ -847,7 +855,7 @@ void killUser(uid_t uid) } _exit(0); - }); + }, options); int status = pid.wait(true); if (status != 0) @@ -863,43 +871,64 @@ void killUser(uid_t uid) ////////////////////////////////////////////////////////////////////// -pid_t startProcess(std::function<void()> fun, - bool dieWithParent, const string & errorPrefix) +/* Wrapper around vfork to prevent the child process from clobbering + the caller's stack frame in the parent. */ +static pid_t doFork(bool allowVfork, std::function<void()> fun) __attribute__((noinline)); +static pid_t doFork(bool allowVfork, std::function<void()> fun) { +#ifdef __linux__ + pid_t pid = allowVfork ? vfork() : fork(); +#else pid_t pid = fork(); - if (pid == -1) throw SysError("unable to fork"); +#endif + if (pid != 0) return pid; + fun(); + abort(); +} - if (pid == 0) { - _writeToStderr = 0; + +pid_t startProcess(std::function<void()> fun, const ProcessOptions & options) +{ + auto wrapper = [&]() { + if (!options.allowVfork) _writeToStderr = 0; try { #if __linux__ - if (dieWithParent && prctl(PR_SET_PDEATHSIG, SIGKILL) == -1) + if (options.dieWithParent && prctl(PR_SET_PDEATHSIG, SIGKILL) == -1) throw SysError("setting death signal"); #endif restoreAffinity(); fun(); } catch (std::exception & e) { try { - std::cerr << errorPrefix << e.what() << "\n"; + std::cerr << options.errorPrefix << e.what() << "\n"; } catch (...) { } } catch (...) { } - _exit(1); - } + if (options.runExitHandlers) + exit(1); + else + _exit(1); + }; + + pid_t pid = doFork(options.allowVfork, wrapper); + if (pid == -1) throw SysError("unable to fork"); return pid; } +std::vector<const char *> stringsToCharPtrs(const Strings & ss) +{ + std::vector<const char *> res; + for (auto & s : ss) res.push_back(s.c_str()); + res.push_back(0); + return res; +} + + string runProgram(Path program, bool searchPath, const Strings & args) { checkInterrupt(); - std::vector<const char *> cargs; /* careful with c_str()! */ - cargs.push_back(program.c_str()); - for (Strings::const_iterator i = args.begin(); i != args.end(); ++i) - cargs.push_back(i->c_str()); - cargs.push_back(0); - /* Create a pipe. */ Pipe pipe; pipe.create(); @@ -909,6 +938,10 @@ string runProgram(Path program, bool searchPath, const Strings & args) if (dup2(pipe.writeSide, STDOUT_FILENO) == -1) throw SysError("dupping stdout"); + Strings args_(args); + args_.push_front(program); + auto cargs = stringsToCharPtrs(args_); + if (searchPath) execvp(program.c_str(), (char * *) &cargs[0]); else diff --git a/src/libutil/util.hh b/src/libutil/util.hh index b35e02dceb60..186ee71f45d0 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -171,6 +171,7 @@ extern void (*_writeToStderr) (const unsigned char * buf, size_t count); requested number of bytes. */ void readFull(int fd, unsigned char * buf, size_t count); void writeFull(int fd, const unsigned char * buf, size_t count); +void writeFull(int fd, const string & s); MakeError(EndOfFile, Error) @@ -269,8 +270,16 @@ void killUser(uid_t uid); /* Fork a process that runs the given function, and return the child pid to the caller. */ -pid_t startProcess(std::function<void()> fun, bool dieWithParent = true, - const string & errorPrefix = "error: "); +struct ProcessOptions +{ + string errorPrefix; + bool dieWithParent; + bool runExitHandlers; + bool allowVfork; + ProcessOptions() : errorPrefix("error: "), dieWithParent(true), runExitHandlers(false), allowVfork(true) { }; +}; + +pid_t startProcess(std::function<void()> fun, const ProcessOptions & options = ProcessOptions()); /* Run a program and return its stdout in a string (i.e., like the @@ -280,6 +289,11 @@ string runProgram(Path program, bool searchPath = false, MakeError(ExecError, Error) +/* Convert a list of strings to a null-terminated vector of char + *'s. The result must not be accessed beyond the lifetime of the + list of strings. */ +std::vector<const char *> stringsToCharPtrs(const Strings & ss); + /* Close all file descriptors except stdin, stdout, stderr, and those listed in the given set. Good practice in child processes. */ void closeMostFDs(const set<int> & exceptions); diff --git a/src/nix-daemon/nix-daemon.cc b/src/nix-daemon/nix-daemon.cc index 8ec54e4580dc..bed7de0859a3 100644 --- a/src/nix-daemon/nix-daemon.cc +++ b/src/nix-daemon/nix-daemon.cc @@ -22,6 +22,10 @@ #include <pwd.h> #include <grp.h> +#if __APPLE__ || __FreeBSD__ +#include <sys/ucred.h> +#endif + using namespace nix; @@ -509,11 +513,11 @@ static void performOp(bool trusted, unsigned int clientVersion, } case wopOptimiseStore: - startWork(); - store->optimiseStore(); - stopWork(); - writeInt(1, to); - break; + startWork(); + store->optimiseStore(); + stopWork(); + writeInt(1, to); + break; default: throw Error(format("invalid operation %1%") % op); @@ -565,7 +569,7 @@ static void processConnection(bool trusted) to.flush(); } catch (Error & e) { - stopWork(false, e.msg()); + stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0); to.flush(); return; } @@ -606,6 +610,8 @@ static void processConnection(bool trusted) assert(!canSendStderr); }; + canSendStderr = false; + _isInterrupted = false; printMsg(lvlDebug, format("%1% operations") % opCount); } @@ -649,12 +655,51 @@ bool matchUser(const string & user, const string & group, const Strings & users) } +struct PeerInfo +{ + bool pidKnown; + pid_t pid; + bool uidKnown; + uid_t uid; + bool gidKnown; + gid_t gid; +}; + + +/* Get the identity of the caller, if possible. */ +static PeerInfo getPeerInfo(int remote) +{ + PeerInfo peer = { false, 0, false, 0, false, 0 }; + +#if defined(SO_PEERCRED) + + ucred cred; + socklen_t credLen = sizeof(cred); + if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1) + throw SysError("getting peer credentials"); + peer = { true, cred.pid, true, cred.uid, true, cred.gid }; + +#elif defined(LOCAL_PEERCRED) + + xucred cred; + socklen_t credLen = sizeof(cred); + if (getsockopt(remote, SOL_LOCAL, LOCAL_PEERCRED, &cred, &credLen) == -1) + throw SysError("getting peer credentials"); + peer = { false, 0, true, cred.cr_uid, false, 0 }; + +#endif + + return peer; +} + + #define SD_LISTEN_FDS_START 3 static void daemonLoop(char * * argv) { - chdir("/"); + if (chdir("/") == -1) + throw SysError("cannot change current directory"); /* Get rid of children automatically; don't let them become zombies. */ @@ -684,7 +729,8 @@ static void daemonLoop(char * * argv) /* Urgh, sockaddr_un allows path names of only 108 characters. So chdir to the socket directory so that we can pass a relative path name. */ - chdir(dirOf(socketPath).c_str()); + if (chdir(dirOf(socketPath).c_str()) == -1) + throw SysError("cannot change current directory"); Path socketPathRel = "./" + baseNameOf(socketPath); struct sockaddr_un addr; @@ -704,7 +750,8 @@ static void daemonLoop(char * * argv) if (res == -1) throw SysError(format("cannot bind to socket ‘%1%’") % socketPath); - chdir("/"); /* back to the root */ + if (chdir("/") == -1) /* back to the root */ + throw SysError("cannot change current directory"); if (listen(fdSocket, 5) == -1) throw SysError(format("cannot listen on socket ‘%1%’") % socketPath); @@ -735,22 +782,13 @@ static void daemonLoop(char * * argv) closeOnExec(remote); bool trusted = false; - pid_t clientPid = -1; - -#if defined(SO_PEERCRED) - /* Get the identity of the caller, if possible. */ - ucred cred; - socklen_t credLen = sizeof(cred); - if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1) - throw SysError("getting peer credentials"); + PeerInfo peer = getPeerInfo(remote); - clientPid = cred.pid; + struct passwd * pw = peer.uidKnown ? getpwuid(peer.uid) : 0; + string user = pw ? pw->pw_name : int2String(peer.uid); - struct passwd * pw = getpwuid(cred.uid); - string user = pw ? pw->pw_name : int2String(cred.uid); - - struct group * gr = getgrgid(cred.gid); - string group = gr ? gr->gr_name : int2String(cred.gid); + struct group * gr = peer.gidKnown ? getgrgid(peer.gid) : 0; + string group = gr ? gr->gr_name : int2String(peer.gid); Strings trustedUsers = settings.get("trusted-users", Strings({"root"})); Strings allowedUsers = settings.get("allowed-users", Strings({"*"})); @@ -761,11 +799,16 @@ static void daemonLoop(char * * argv) if (!trusted && !matchUser(user, group, allowedUsers)) throw Error(format("user ‘%1%’ is not allowed to connect to the Nix daemon") % user); - printMsg(lvlInfo, format((string) "accepted connection from pid %1%, user %2%" - + (trusted ? " (trusted)" : "")) % clientPid % user); -#endif + printMsg(lvlInfo, format((string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : "")) + % (peer.pidKnown ? int2String(peer.pid) : "<unknown>") + % (peer.uidKnown ? user : "<unknown>")); /* Fork a child to handle the connection. */ + ProcessOptions options; + options.errorPrefix = "unexpected Nix daemon error: "; + options.dieWithParent = false; + options.runExitHandlers = true; + options.allowVfork = false; startProcess([&]() { fdSocket.close(); @@ -777,8 +820,8 @@ static void daemonLoop(char * * argv) setSigChldAction(false); /* For debugging, stuff the pid into argv[1]. */ - if (clientPid != -1 && argv[1]) { - string processName = int2String(clientPid); + if (peer.pidKnown && argv[1]) { + string processName = int2String(peer.pid); strncpy(argv[1], processName.c_str(), strlen(argv[1])); } @@ -787,8 +830,8 @@ static void daemonLoop(char * * argv) to.fd = remote; processConnection(trusted); - _exit(0); - }, false, "unexpected Nix daemon error: "); + exit(0); + }, options); } catch (Interrupted & e) { throw; @@ -799,15 +842,6 @@ static void daemonLoop(char * * argv) } -void run(Strings args) -{ - for (Strings::iterator i = args.begin(); i != args.end(); ) { - string arg = *i++; - } - -} - - int main(int argc, char * * argv) { return handleExceptions(argv[0], [&]() { diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index f5e8ee08c42f..f3c8d3ba8953 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -132,6 +132,8 @@ static void getAllExprs(EvalState & state, Value & vArg(*state.allocValue()); state.getBuiltin("import", vFun); mkString(vArg, path2); + if (v.attrs->size() == v.attrs->capacity()) + throw Error(format("too many Nix expressions in directory ‘%1%’") % path); mkApp(*state.allocAttr(v, state.symbols.create(attrName)), vFun, vArg); } else if (S_ISDIR(st.st_mode)) @@ -160,7 +162,7 @@ static void loadSourceExpr(EvalState & state, const Path & path, Value & v) ~/.nix-defexpr directory that includes some system-wide directory). */ if (S_ISDIR(st.st_mode)) { - state.mkAttrs(v, 16); + state.mkAttrs(v, 1024); state.mkList(*state.allocAttr(v, state.symbols.create("_combineChannels")), 0); StringSet attrs; getAllExprs(state, path, attrs, v); diff --git a/src/nix-instantiate/nix-instantiate.cc b/src/nix-instantiate/nix-instantiate.cc index 9a6e178f514e..e7214e657bd5 100644 --- a/src/nix-instantiate/nix-instantiate.cc +++ b/src/nix-instantiate/nix-instantiate.cc @@ -86,8 +86,6 @@ void processExpr(EvalState & state, const Strings & attrPaths, } } } - - state.printCanaries(); } diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index c91bca97d534..87bc8c379de5 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -488,7 +488,7 @@ static void opReadLog(Strings opFlags, Strings opArgs) if (pathExists(logPath)) { /* !!! Make this run in O(1) memory. */ string log = readFile(logPath); - writeFull(STDOUT_FILENO, (const unsigned char *) log.data(), log.size()); + writeFull(STDOUT_FILENO, log); found = true; break; } |