Merge branch 'master' into no-manifests
This commit is contained in:
		
						commit
						e94806d030
					
				
					 20 changed files with 325 additions and 139 deletions
				
			
		|  | @ -144,6 +144,8 @@ EvalState::EvalState() | ||||||
| { | { | ||||||
|     nrEnvs = nrValuesInEnvs = nrValues = nrListElems = 0; |     nrEnvs = nrValuesInEnvs = nrValues = nrListElems = 0; | ||||||
|     nrAttrsets = nrOpUpdates = nrOpUpdateValuesCopied = 0; |     nrAttrsets = nrOpUpdates = nrOpUpdateValuesCopied = 0; | ||||||
|  |     nrListConcats = nrPrimOpCalls = nrFunctionCalls = 0; | ||||||
|  |     countCalls = getEnv("NIX_COUNT_CALLS", "0") != "0"; | ||||||
| 
 | 
 | ||||||
| #if HAVE_BOEHMGC | #if HAVE_BOEHMGC | ||||||
|     static bool gcInitialised = true; |     static bool gcInitialised = true; | ||||||
|  | @ -300,8 +302,10 @@ inline Value * EvalState::lookupVar(Env * env, const VarRef & var) | ||||||
|     if (var.fromWith) { |     if (var.fromWith) { | ||||||
|         while (1) { |         while (1) { | ||||||
|             Bindings::iterator j = env->values[0]->attrs->find(var.name); |             Bindings::iterator j = env->values[0]->attrs->find(var.name); | ||||||
|             if (j != env->values[0]->attrs->end()) |             if (j != env->values[0]->attrs->end()) { | ||||||
|  |                 if (countCalls && j->pos) attrSelects[*j->pos]++; | ||||||
|                 return j->value; |                 return j->value; | ||||||
|  |             } | ||||||
|             if (env->prevWith == 0) |             if (env->prevWith == 0) | ||||||
|                 throwEvalError("undefined variable `%1%'", var.name); |                 throwEvalError("undefined variable `%1%'", var.name); | ||||||
|             for (unsigned int l = env->prevWith; l; --l, env = env->up) ; |             for (unsigned int l = env->prevWith; l; --l, env = env->up) ; | ||||||
|  | @ -344,7 +348,7 @@ void EvalState::mkList(Value & v, unsigned int length) | ||||||
| { | { | ||||||
|     v.type = tList; |     v.type = tList; | ||||||
|     v.list.length = length; |     v.list.length = length; | ||||||
|     v.list.elems = (Value * *) GC_MALLOC(length * sizeof(Value *)); |     v.list.elems = length ? (Value * *) GC_MALLOC(length * sizeof(Value *)) : 0; | ||||||
|     nrListElems += length; |     nrListElems += length; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -619,8 +623,10 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) | ||||||
|             } |             } | ||||||
|             vAttrs = j->value; |             vAttrs = j->value; | ||||||
|             pos = j->pos; |             pos = j->pos; | ||||||
|  |             if (state.countCalls && pos) state.attrSelects[*pos]++; | ||||||
|         } |         } | ||||||
|      |      | ||||||
|  | 
 | ||||||
|         state.forceValue(*vAttrs); |         state.forceValue(*vAttrs); | ||||||
|          |          | ||||||
|     } catch (Error & e) { |     } catch (Error & e) { | ||||||
|  | @ -700,6 +706,8 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v) | ||||||
|                 vArgs[n--] = arg->primOpApp.right; |                 vArgs[n--] = arg->primOpApp.right; | ||||||
| 
 | 
 | ||||||
|             /* And call the primop. */ |             /* And call the primop. */ | ||||||
|  |             nrPrimOpCalls++; | ||||||
|  |             if (countCalls) primOpCalls[primOp->primOp->name]++; | ||||||
|             try { |             try { | ||||||
|                 primOp->primOp->fun(*this, vArgs, v); |                 primOp->primOp->fun(*this, vArgs, v); | ||||||
|             } catch (Error & e) { |             } catch (Error & e) { | ||||||
|  | @ -716,7 +724,7 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v) | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     if (fun.type != tLambda) |     if (fun.type != tLambda) | ||||||
|         throwTypeError("attempt to call something which is neither a function nor a primop (built-in operation) but %1%", |         throwTypeError("attempt to call something which is not a function but %1%", | ||||||
|             showType(fun)); |             showType(fun)); | ||||||
| 
 | 
 | ||||||
|     unsigned int size = |     unsigned int size = | ||||||
|  | @ -760,6 +768,9 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v) | ||||||
|             throwTypeError("function at %1% called with unexpected argument", fun.lambda.fun->pos); |             throwTypeError("function at %1% called with unexpected argument", fun.lambda.fun->pos); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     nrFunctionCalls++; | ||||||
|  |     if (countCalls) functionCalls[fun.lambda.fun->pos]++; | ||||||
|  | 
 | ||||||
|     try { |     try { | ||||||
|         fun.lambda.fun->body->eval(*this, env2, v); |         fun.lambda.fun->body->eval(*this, env2, v); | ||||||
|     } catch (Error & e) { |     } catch (Error & e) { | ||||||
|  | @ -902,14 +913,36 @@ void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v) | ||||||
| void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v) | void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v) | ||||||
| { | { | ||||||
|     Value v1; e1->eval(state, env, v1); |     Value v1; e1->eval(state, env, v1); | ||||||
|     state.forceList(v1); |  | ||||||
|     Value v2; e2->eval(state, env, v2); |     Value v2; e2->eval(state, env, v2); | ||||||
|     state.forceList(v2); |     Value * lists[2] = { &v1, &v2 }; | ||||||
|     state.mkList(v, v1.list.length + v2.list.length); |     state.concatLists(v, 2, lists); | ||||||
|     for (unsigned int n = 0; n < v1.list.length; ++n) | } | ||||||
|         v.list.elems[n] = v1.list.elems[n]; | 
 | ||||||
|     for (unsigned int n = 0; n < v2.list.length; ++n) | 
 | ||||||
|         v.list.elems[n + v1.list.length] = v2.list.elems[n]; | void EvalState::concatLists(Value & v, unsigned int nrLists, Value * * lists) | ||||||
|  | { | ||||||
|  |     nrListConcats++; | ||||||
|  | 
 | ||||||
|  |     Value * nonEmpty = 0; | ||||||
|  |     unsigned int len = 0; | ||||||
|  |     for (unsigned int n = 0; n < nrLists; ++n) { | ||||||
|  |         forceList(*lists[n]); | ||||||
|  |         unsigned int l = lists[n]->list.length; | ||||||
|  |         len += l; | ||||||
|  |         if (l) nonEmpty = lists[n]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (nonEmpty && len == nonEmpty->list.length) { | ||||||
|  |         v = *nonEmpty; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     mkList(v, len); | ||||||
|  |     for (unsigned int n = 0, pos = 0; n < nrLists; ++n) { | ||||||
|  |         unsigned int l = lists[n]->list.length; | ||||||
|  |         memcpy(v.list.elems + pos, lists[n]->list.elems, l * sizeof(Value *)); | ||||||
|  |         pos += l; | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -1207,6 +1240,7 @@ void EvalState::printStats() | ||||||
|         % nrEnvs % (nrEnvs * sizeof(Env) + nrValuesInEnvs * sizeof(Value *))); |         % nrEnvs % (nrEnvs * sizeof(Env) + nrValuesInEnvs * sizeof(Value *))); | ||||||
|     printMsg(v, format("  list elements: %1% (%2% bytes)") |     printMsg(v, format("  list elements: %1% (%2% bytes)") | ||||||
|         % nrListElems % (nrListElems * sizeof(Value *))); |         % nrListElems % (nrListElems * sizeof(Value *))); | ||||||
|  |     printMsg(v, format("  list concatenations: %1%") % nrListConcats); | ||||||
|     printMsg(v, format("  values allocated: %1% (%2% bytes)") |     printMsg(v, format("  values allocated: %1% (%2% bytes)") | ||||||
|         % nrValues % (nrValues * sizeof(Value))); |         % nrValues % (nrValues * sizeof(Value))); | ||||||
|     printMsg(v, format("  attribute sets allocated: %1%") % nrAttrsets); |     printMsg(v, format("  attribute sets allocated: %1%") % nrAttrsets); | ||||||
|  | @ -1216,6 +1250,36 @@ void EvalState::printStats() | ||||||
|     printMsg(v, format("  number of thunks: %1%") % nrThunks); |     printMsg(v, format("  number of thunks: %1%") % nrThunks); | ||||||
|     printMsg(v, format("  number of thunks avoided: %1%") % nrAvoided); |     printMsg(v, format("  number of thunks avoided: %1%") % nrAvoided); | ||||||
|     printMsg(v, format("  number of attr lookups: %1%") % nrLookups); |     printMsg(v, format("  number of attr lookups: %1%") % nrLookups); | ||||||
|  |     printMsg(v, format("  number of primop calls: %1%") % nrPrimOpCalls); | ||||||
|  |     printMsg(v, format("  number of function calls: %1%") % nrFunctionCalls); | ||||||
|  | 
 | ||||||
|  |     if (countCalls) { | ||||||
|  | 
 | ||||||
|  |         printMsg(v, format("calls to %1% primops:") % primOpCalls.size()); | ||||||
|  |         typedef std::multimap<unsigned int, Symbol> PrimOpCalls_; | ||||||
|  |         std::multimap<unsigned int, Symbol> primOpCalls_; | ||||||
|  |         foreach (PrimOpCalls::iterator, i, primOpCalls) | ||||||
|  |             primOpCalls_.insert(std::pair<unsigned int, Symbol>(i->second, i->first)); | ||||||
|  |         foreach_reverse (PrimOpCalls_::reverse_iterator, i, primOpCalls_) | ||||||
|  |             printMsg(v, format("%1$10d %2%") % i->first % i->second); | ||||||
|  | 
 | ||||||
|  |         printMsg(v, format("calls to %1% functions:") % functionCalls.size()); | ||||||
|  |         typedef std::multimap<unsigned int, Pos> FunctionCalls_; | ||||||
|  |         std::multimap<unsigned int, Pos> functionCalls_; | ||||||
|  |         foreach (FunctionCalls::iterator, i, functionCalls) | ||||||
|  |             functionCalls_.insert(std::pair<unsigned int, Pos>(i->second, i->first)); | ||||||
|  |         foreach_reverse (FunctionCalls_::reverse_iterator, i, functionCalls_) | ||||||
|  |             printMsg(v, format("%1$10d %2%") % i->first % i->second); | ||||||
|  | 
 | ||||||
|  |         printMsg(v, format("evaluations of %1% attributes:") % attrSelects.size()); | ||||||
|  |         typedef std::multimap<unsigned int, Pos> AttrSelects_; | ||||||
|  |         std::multimap<unsigned int, Pos> attrSelects_; | ||||||
|  |         foreach (AttrSelects::iterator, i, attrSelects) | ||||||
|  |             attrSelects_.insert(std::pair<unsigned int, Pos>(i->second, i->first)); | ||||||
|  |         foreach_reverse (AttrSelects_::reverse_iterator, i, attrSelects_) | ||||||
|  |             printMsg(v, format("%1$10d %2%") % i->first % i->second); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -232,6 +232,8 @@ public: | ||||||
|     void mkAttrs(Value & v, unsigned int expected); |     void mkAttrs(Value & v, unsigned int expected); | ||||||
|     void mkThunk_(Value & v, Expr * expr); |     void mkThunk_(Value & v, Expr * expr); | ||||||
| 
 | 
 | ||||||
|  |     void concatLists(Value & v, unsigned int nrLists, Value * * lists); | ||||||
|  | 
 | ||||||
|     /* Print statistics. */ |     /* Print statistics. */ | ||||||
|     void printStats(); |     void printStats(); | ||||||
| 
 | 
 | ||||||
|  | @ -244,9 +246,25 @@ private: | ||||||
|     unsigned long nrAttrsets; |     unsigned long nrAttrsets; | ||||||
|     unsigned long nrOpUpdates; |     unsigned long nrOpUpdates; | ||||||
|     unsigned long nrOpUpdateValuesCopied; |     unsigned long nrOpUpdateValuesCopied; | ||||||
|  |     unsigned long nrListConcats; | ||||||
|  |     unsigned long nrPrimOpCalls; | ||||||
|  |     unsigned long nrFunctionCalls; | ||||||
|  | 
 | ||||||
|  |     bool countCalls; | ||||||
|  | 
 | ||||||
|  |     typedef std::map<Symbol, unsigned int> PrimOpCalls; | ||||||
|  |     PrimOpCalls primOpCalls; | ||||||
|  | 
 | ||||||
|  |     typedef std::map<Pos, unsigned int> FunctionCalls; | ||||||
|  |     FunctionCalls functionCalls; | ||||||
|  | 
 | ||||||
|  |     typedef std::map<Pos, unsigned int> AttrSelects; | ||||||
|  |     AttrSelects attrSelects; | ||||||
| 
 | 
 | ||||||
|     friend class RecursionCounter; |  | ||||||
|     friend class ExprOpUpdate; |     friend class ExprOpUpdate; | ||||||
|  |     friend class ExprOpConcatLists; | ||||||
|  |     friend class ExprSelect; | ||||||
|  |     friend void prim_getAttr(EvalState & state, Value * * args, Value & v); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -27,6 +27,15 @@ struct Pos | ||||||
|     Pos() : line(0), column(0) { }; |     Pos() : line(0), column(0) { }; | ||||||
|     Pos(const string & file, unsigned int line, unsigned int column) |     Pos(const string & file, unsigned int line, unsigned int column) | ||||||
|         : file(file), line(line), column(column) { }; |         : file(file), line(line), column(column) { }; | ||||||
|  |     bool operator < (const Pos & p2) const | ||||||
|  |     { | ||||||
|  |         int d = file.compare(p2.file); | ||||||
|  |         if (d < 0) return true; | ||||||
|  |         if (d > 0) return false; | ||||||
|  |         if (line < p2.line) return true; | ||||||
|  |         if (line > p2.line) return false; | ||||||
|  |         return column < p2.column; | ||||||
|  |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| extern Pos noPos; | extern Pos noPos; | ||||||
|  |  | ||||||
|  | @ -203,7 +203,7 @@ static Expr * stripIndentation(SymbolTable & symbols, vector<Expr *> & es) | ||||||
|         es2->push_back(new ExprString(symbols.create(s2))); |         es2->push_back(new ExprString(symbols.create(s2))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return new ExprConcatStrings(es2); |     return es2->size() == 1 ? (*es2)[0] : new ExprConcatStrings(es2); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -719,7 +719,7 @@ static void prim_attrNames(EvalState & state, Value * * args, Value & v) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /* Dynamic version of the `.' operator. */ | /* Dynamic version of the `.' operator. */ | ||||||
| static void prim_getAttr(EvalState & state, Value * * args, Value & v) | void prim_getAttr(EvalState & state, Value * * args, Value & v) | ||||||
| { | { | ||||||
|     string attr = state.forceStringNoCtx(*args[0]); |     string attr = state.forceStringNoCtx(*args[0]); | ||||||
|     state.forceAttrs(*args[1]); |     state.forceAttrs(*args[1]); | ||||||
|  | @ -728,6 +728,7 @@ static void prim_getAttr(EvalState & state, Value * * args, Value & v) | ||||||
|     if (i == args[1]->attrs->end()) |     if (i == args[1]->attrs->end()) | ||||||
|         throw EvalError(format("attribute `%1%' missing") % attr); |         throw EvalError(format("attribute `%1%' missing") % attr); | ||||||
|     // !!! add to stack trace?
 |     // !!! add to stack trace?
 | ||||||
|  |     if (state.countCalls && i->pos) state.attrSelects[*i->pos]++; | ||||||
|     state.forceValue(*i->value); |     state.forceValue(*i->value); | ||||||
|     v = *i->value; |     v = *i->value; | ||||||
| } | } | ||||||
|  | @ -873,19 +874,33 @@ static void prim_isList(EvalState & state, Value * * args, Value & v) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | static void elemAt(EvalState & state, Value & list, int n, Value & v) | ||||||
|  | { | ||||||
|  |     state.forceList(list); | ||||||
|  |     if (n < 0 || n >= list.list.length) | ||||||
|  |         throw Error(format("list index %1% is out of bounds") % n); | ||||||
|  |     state.forceValue(*list.list.elems[n]); | ||||||
|  |     v = *list.list.elems[n]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /* Return the n-1'th element of a list. */ | ||||||
|  | static void prim_elemAt(EvalState & state, Value * * args, Value & v) | ||||||
|  | { | ||||||
|  |     elemAt(state, *args[0], state.forceInt(*args[1]), v); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| /* Return the first element of a list. */ | /* Return the first element of a list. */ | ||||||
| static void prim_head(EvalState & state, Value * * args, Value & v) | static void prim_head(EvalState & state, Value * * args, Value & v) | ||||||
| { | { | ||||||
|     state.forceList(*args[0]); |     elemAt(state, *args[0], 0, v); | ||||||
|     if (args[0]->list.length == 0) |  | ||||||
|         throw Error("`head' called on an empty list"); |  | ||||||
|     state.forceValue(*args[0]->list.elems[0]); |  | ||||||
|     v = *args[0]->list.elems[0]; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /* Return a list consisting of everything but the the first element of
 | /* Return a list consisting of everything but the the first element of
 | ||||||
|    a list. */ |    a list.  Warning: this function takes O(n) time, so you probably | ||||||
|  |    don't want to use it!  */ | ||||||
| static void prim_tail(EvalState & state, Value * * args, Value & v) | static void prim_tail(EvalState & state, Value * * args, Value & v) | ||||||
| { | { | ||||||
|     state.forceList(*args[0]); |     state.forceList(*args[0]); | ||||||
|  | @ -911,6 +926,52 @@ static void prim_map(EvalState & state, Value * * args, Value & v) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | /* Filter a list using a predicate; that is, return a list containing
 | ||||||
|  |    every element from the list for which the predicate function | ||||||
|  |    returns true. */ | ||||||
|  | static void prim_filter(EvalState & state, Value * * args, Value & v) | ||||||
|  | { | ||||||
|  |     state.forceFunction(*args[0]); | ||||||
|  |     state.forceList(*args[1]); | ||||||
|  | 
 | ||||||
|  |     // FIXME: putting this on the stack is risky.
 | ||||||
|  |     Value * vs[args[1]->list.length]; | ||||||
|  |     unsigned int k = 0; | ||||||
|  | 
 | ||||||
|  |     for (unsigned int n = 0; n < args[1]->list.length; ++n) { | ||||||
|  |         Value res; | ||||||
|  |         state.callFunction(*args[0], *args[1]->list.elems[n], res); | ||||||
|  |         if (state.forceBool(res)) | ||||||
|  |             vs[k++] = args[1]->list.elems[n]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     state.mkList(v, k); | ||||||
|  |     for (unsigned int n = 0; n < k; ++n) v.list.elems[n] = vs[n]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /* Return true if a list contains a given element. */ | ||||||
|  | static void prim_elem(EvalState & state, Value * * args, Value & v) | ||||||
|  | { | ||||||
|  |     bool res = false; | ||||||
|  |     state.forceList(*args[1]); | ||||||
|  |     for (unsigned int n = 0; n < args[1]->list.length; ++n) | ||||||
|  |         if (state.eqValues(*args[0], *args[1]->list.elems[n])) { | ||||||
|  |             res = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     mkBool(v, res); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /* Concatenate a list of lists. */ | ||||||
|  | static void prim_concatLists(EvalState & state, Value * * args, Value & v) | ||||||
|  | { | ||||||
|  |     state.forceList(*args[0]); | ||||||
|  |     state.concatLists(v, args[0]->list.length, args[0]->list.elems); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| /* Return the length of a list.  This is an O(1) time operation. */ | /* Return the length of a list.  This is an O(1) time operation. */ | ||||||
| static void prim_length(EvalState & state, Value * * args, Value & v) | static void prim_length(EvalState & state, Value * * args, Value & v) | ||||||
| { | { | ||||||
|  | @ -1122,9 +1183,13 @@ void EvalState::createBaseEnv() | ||||||
| 
 | 
 | ||||||
|     // Lists
 |     // Lists
 | ||||||
|     addPrimOp("__isList", 1, prim_isList); |     addPrimOp("__isList", 1, prim_isList); | ||||||
|  |     addPrimOp("__elemAt", 2, prim_elemAt); | ||||||
|     addPrimOp("__head", 1, prim_head); |     addPrimOp("__head", 1, prim_head); | ||||||
|     addPrimOp("__tail", 1, prim_tail); |     addPrimOp("__tail", 1, prim_tail); | ||||||
|     addPrimOp("map", 2, prim_map); |     addPrimOp("map", 2, prim_map); | ||||||
|  |     addPrimOp("__filter", 2, prim_filter); | ||||||
|  |     addPrimOp("__elem", 2, prim_elem); | ||||||
|  |     addPrimOp("__concatLists", 1, prim_concatLists); | ||||||
|     addPrimOp("__length", 1, prim_length); |     addPrimOp("__length", 1, prim_length); | ||||||
| 
 | 
 | ||||||
|     // Integer arithmetic
 |     // Integer arithmetic
 | ||||||
|  |  | ||||||
|  | @ -33,16 +33,6 @@ static void sigintHandler(int signo) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Path makeRootName(const Path & gcRoot, int & counter) |  | ||||||
| { |  | ||||||
|     counter++; |  | ||||||
|     if (counter == 1) |  | ||||||
|         return gcRoot; |  | ||||||
|     else |  | ||||||
|         return (format("%1%-%2%") % gcRoot % counter).str(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| void printGCWarning() | void printGCWarning() | ||||||
| { | { | ||||||
|     static bool haveWarned = false; |     static bool haveWarned = false; | ||||||
|  |  | ||||||
|  | @ -26,7 +26,6 @@ MakeError(UsageError, nix::Error); | ||||||
| class StoreAPI; | class StoreAPI; | ||||||
| 
 | 
 | ||||||
| /* Ugh.  No better place to put this. */ | /* Ugh.  No better place to put this. */ | ||||||
| Path makeRootName(const Path & gcRoot, int & counter); |  | ||||||
| void printGCWarning(); | void printGCWarning(); | ||||||
| 
 | 
 | ||||||
| void printMissing(StoreAPI & store, const PathSet & paths); | void printMissing(StoreAPI & store, const PathSet & paths); | ||||||
|  |  | ||||||
|  | @ -45,7 +45,7 @@ | ||||||
| #include <sched.h> | #include <sched.h> | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #define CHROOT_ENABLED HAVE_CHROOT && HAVE_UNSHARE && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(CLONE_NEWNS) | #define CHROOT_ENABLED HAVE_CHROOT && HAVE_UNSHARE && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_PRIVATE) && defined(CLONE_NEWNS) | ||||||
| 
 | 
 | ||||||
| #if CHROOT_ENABLED | #if CHROOT_ENABLED | ||||||
| #include <sys/socket.h> | #include <sys/socket.h> | ||||||
|  | @ -604,18 +604,17 @@ void getOwnership(const Path & path) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| void deletePathWrapped(const Path & path, | void deletePathWrapped(const Path & path, unsigned long long & bytesFreed) | ||||||
|     unsigned long long & bytesFreed, unsigned long long & blocksFreed) |  | ||||||
| { | { | ||||||
|     try { |     try { | ||||||
|         /* First try to delete it ourselves. */ |         /* First try to delete it ourselves. */ | ||||||
|         deletePath(path, bytesFreed, blocksFreed); |         deletePath(path, bytesFreed); | ||||||
|     } catch (SysError & e) { |     } catch (SysError & e) { | ||||||
|         /* If this failed due to a permission error, then try it with
 |         /* If this failed due to a permission error, then try it with
 | ||||||
|            the setuid helper. */ |            the setuid helper. */ | ||||||
|         if (settings.buildUsersGroup != "" && !amPrivileged()) { |         if (settings.buildUsersGroup != "" && !amPrivileged()) { | ||||||
|             getOwnership(path); |             getOwnership(path); | ||||||
|             deletePath(path, bytesFreed, blocksFreed); |             deletePath(path, bytesFreed); | ||||||
|         } else |         } else | ||||||
|             throw; |             throw; | ||||||
|     } |     } | ||||||
|  | @ -624,8 +623,8 @@ void deletePathWrapped(const Path & path, | ||||||
| 
 | 
 | ||||||
| void deletePathWrapped(const Path & path) | void deletePathWrapped(const Path & path) | ||||||
| { | { | ||||||
|     unsigned long long dummy1, dummy2; |     unsigned long long dummy1; | ||||||
|     deletePathWrapped(path, dummy1, dummy2); |     deletePathWrapped(path, dummy1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -1470,9 +1469,9 @@ HookReply DerivationGoal::tryBuildHook() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| void chmod(const Path & path, mode_t mode) | void chmod_(const Path & path, mode_t mode) | ||||||
| { | { | ||||||
|     if (::chmod(path.c_str(), 01777) == -1) |     if (chmod(path.c_str(), mode) == -1) | ||||||
|         throw SysError(format("setting permissions on `%1%'") % path); |         throw SysError(format("setting permissions on `%1%'") % path); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -1674,7 +1673,7 @@ void DerivationGoal::startBuilder() | ||||||
|            instead.) */ |            instead.) */ | ||||||
|         Path chrootTmpDir = chrootRootDir + "/tmp"; |         Path chrootTmpDir = chrootRootDir + "/tmp"; | ||||||
|         createDirs(chrootTmpDir); |         createDirs(chrootTmpDir); | ||||||
|         chmod(chrootTmpDir, 01777); |         chmod_(chrootTmpDir, 01777); | ||||||
| 
 | 
 | ||||||
|         /* Create a /etc/passwd with entries for the build user and the
 |         /* Create a /etc/passwd with entries for the build user and the
 | ||||||
|            nobody account.  The latter is kind of a hack to support |            nobody account.  The latter is kind of a hack to support | ||||||
|  | @ -1710,7 +1709,7 @@ void DerivationGoal::startBuilder() | ||||||
|            precaution, make the fake Nix store only writable by the |            precaution, make the fake Nix store only writable by the | ||||||
|            build user. */ |            build user. */ | ||||||
|         createDirs(chrootRootDir + settings.nixStore); |         createDirs(chrootRootDir + settings.nixStore); | ||||||
|         chmod(chrootRootDir + settings.nixStore, 01777); |         chmod_(chrootRootDir + settings.nixStore, 01777); | ||||||
| 
 | 
 | ||||||
|         foreach (PathSet::iterator, i, inputPaths) { |         foreach (PathSet::iterator, i, inputPaths) { | ||||||
|             struct stat st; |             struct stat st; | ||||||
|  | @ -1844,22 +1843,40 @@ void DerivationGoal::initChild() | ||||||
|             char domainname[] = "(none)"; // kernel default
 |             char domainname[] = "(none)"; // kernel default
 | ||||||
|             setdomainname(domainname, sizeof(domainname)); |             setdomainname(domainname, sizeof(domainname)); | ||||||
| 
 | 
 | ||||||
|  |             /* Make all filesystems private.  This is necessary
 | ||||||
|  |                because subtrees may have been mounted as "shared" | ||||||
|  |                (MS_SHARED).  (Systemd does this, for instance.)  Even | ||||||
|  |                though we have a private mount namespace, mounting | ||||||
|  |                filesystems on top of a shared subtree still propagates | ||||||
|  |                outside of the namespace.  Making a subtree private is | ||||||
|  |                local to the namespace, though, so setting MS_PRIVATE | ||||||
|  |                does not affect the outside world. */ | ||||||
|  |             Strings mounts = tokenizeString(readFile("/proc/self/mountinfo", true), "\n"); | ||||||
|  |             foreach (Strings::iterator, i, mounts) { | ||||||
|  |                 Strings fields = tokenizeString(*i, " "); | ||||||
|  |                 assert(fields.size() >= 5); | ||||||
|  |                 Strings::iterator j = fields.begin(); | ||||||
|  |                 std::advance(j, 4); | ||||||
|  |                 if (mount(0, j->c_str(), 0, MS_PRIVATE, 0) == -1) | ||||||
|  |                     throw SysError(format("unable to make filesystem `%1%' private") % *j); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             /* Bind-mount all the directories from the "host"
 |             /* Bind-mount all the directories from the "host"
 | ||||||
|                filesystem that we want in the chroot |                filesystem that we want in the chroot | ||||||
|                environment. */ |                environment. */ | ||||||
|             foreach (PathSet::iterator, i, dirsInChroot) { |             foreach (PathSet::iterator, i, dirsInChroot) { | ||||||
|                 Path source = *i; |                 Path source = *i; | ||||||
|                 Path target = chrootRootDir + source; |                 Path target = chrootRootDir + source; | ||||||
|  |                 if (source == "/proc") continue; // backwards compatibility
 | ||||||
|                 debug(format("bind mounting `%1%' to `%2%'") % source % target); |                 debug(format("bind mounting `%1%' to `%2%'") % source % target); | ||||||
| 
 |  | ||||||
|                 createDirs(target); |                 createDirs(target); | ||||||
| 
 |  | ||||||
|                 if (mount(source.c_str(), target.c_str(), "", MS_BIND, 0) == -1) |                 if (mount(source.c_str(), target.c_str(), "", MS_BIND, 0) == -1) | ||||||
|                     throw SysError(format("bind mount from `%1%' to `%2%' failed") % source % target); |                     throw SysError(format("bind mount from `%1%' to `%2%' failed") % source % target); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             /* Bind a new instance of procfs on /proc to reflect our
 |             /* Bind a new instance of procfs on /proc to reflect our
 | ||||||
|                private PID namespace. */ |                private PID namespace. */ | ||||||
|  |             createDirs(chrootRootDir + "/proc"); | ||||||
|             if (mount("none", (chrootRootDir + "/proc").c_str(), "proc", 0, 0) == -1) |             if (mount("none", (chrootRootDir + "/proc").c_str(), "proc", 0, 0) == -1) | ||||||
|                 throw SysError("mounting /proc"); |                 throw SysError("mounting /proc"); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -425,10 +425,9 @@ bool LocalStore::isActiveTempFile(const GCState & state, | ||||||
| void LocalStore::deleteGarbage(GCState & state, const Path & path) | void LocalStore::deleteGarbage(GCState & state, const Path & path) | ||||||
| { | { | ||||||
|     printMsg(lvlInfo, format("deleting `%1%'") % path); |     printMsg(lvlInfo, format("deleting `%1%'") % path); | ||||||
|     unsigned long long bytesFreed, blocksFreed; |     unsigned long long bytesFreed; | ||||||
|     deletePathWrapped(path, bytesFreed, blocksFreed); |     deletePathWrapped(path, bytesFreed); | ||||||
|     state.results.bytesFreed += bytesFreed; |     state.results.bytesFreed += bytesFreed; | ||||||
|     state.results.blocksFreed += blocksFreed; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -550,7 +549,7 @@ bool LocalStore::tryToDelete(GCState & state, const Path & path) | ||||||
|         } else |         } else | ||||||
|             deleteGarbage(state, path); |             deleteGarbage(state, path); | ||||||
| 
 | 
 | ||||||
|         if (state.options.maxFreed && state.results.bytesFreed + state.bytesInvalidated > state.options.maxFreed) { |         if (state.results.bytesFreed + state.bytesInvalidated > state.options.maxFreed) { | ||||||
|             printMsg(lvlInfo, format("deleted or invalidated more than %1% bytes; stopping") % state.options.maxFreed); |             printMsg(lvlInfo, format("deleted or invalidated more than %1% bytes; stopping") % state.options.maxFreed); | ||||||
|             throw GCLimitReached(); |             throw GCLimitReached(); | ||||||
|         } |         } | ||||||
|  | @ -576,11 +575,13 @@ bool LocalStore::tryToDelete(GCState & state, const Path & path) | ||||||
|    safely deleted.  FIXME: race condition with optimisePath(): we |    safely deleted.  FIXME: race condition with optimisePath(): we | ||||||
|    might see a link count of 1 just before optimisePath() increases |    might see a link count of 1 just before optimisePath() increases | ||||||
|    the link count. */ |    the link count. */ | ||||||
| void LocalStore::removeUnusedLinks() | 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(format("opening directory `%1%'") % linksDir); | ||||||
| 
 | 
 | ||||||
|  |     long long actualSize = 0, unsharedSize = 0; | ||||||
|  | 
 | ||||||
|     struct dirent * dirent; |     struct dirent * dirent; | ||||||
|     while (errno = 0, dirent = readdir(dir)) { |     while (errno = 0, dirent = readdir(dir)) { | ||||||
|         checkInterrupt(); |         checkInterrupt(); | ||||||
|  | @ -592,13 +593,28 @@ void LocalStore::removeUnusedLinks() | ||||||
|         if (lstat(path.c_str(), &st) == -1) |         if (lstat(path.c_str(), &st) == -1) | ||||||
|             throw SysError(format("statting `%1%'") % path); |             throw SysError(format("statting `%1%'") % path); | ||||||
| 
 | 
 | ||||||
|         if (st.st_nlink != 1) continue; |         if (st.st_nlink != 1) { | ||||||
|  |             unsigned long long size = st.st_blocks * 512ULL; | ||||||
|  |             actualSize += size; | ||||||
|  |             unsharedSize += (st.st_nlink - 1) * size; | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         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(format("deleting `%1%'") % path); | ||||||
|  | 
 | ||||||
|  |         state.results.bytesFreed += st.st_blocks * 512; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     struct stat st; | ||||||
|  |     if (stat(linksDir.c_str(), &st) == -1) | ||||||
|  |         throw SysError(format("statting `%1%'") % linksDir); | ||||||
|  |     long long overhead = st.st_blocks * 512ULL; | ||||||
|  | 
 | ||||||
|  |     printMsg(lvlInfo, format("note: currently hard linking saves %.2f MiB") | ||||||
|  |         % ((unsharedSize - actualSize - overhead) / (1024.0 * 1024.0))); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -660,7 +676,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) | ||||||
|                 throw Error(format("cannot delete path `%1%' since it is still alive") % *i); |                 throw Error(format("cannot delete path `%1%' since it is still alive") % *i); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     } else { |     } else if (options.maxFreed > 0) { | ||||||
| 
 | 
 | ||||||
|         if (shouldDelete(state.options.action)) |         if (shouldDelete(state.options.action)) | ||||||
|             printMsg(lvlError, format("deleting garbage...")); |             printMsg(lvlError, format("deleting garbage...")); | ||||||
|  | @ -718,7 +734,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) | ||||||
| 
 | 
 | ||||||
|     /* Clean up the links directory. */ |     /* Clean up the links directory. */ | ||||||
|     printMsg(lvlError, format("deleting unused links...")); |     printMsg(lvlError, format("deleting unused links...")); | ||||||
|     removeUnusedLinks(); |     removeUnusedLinks(state); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -261,7 +261,7 @@ private: | ||||||
| 
 | 
 | ||||||
|     int openGCLock(LockType lockType); |     int openGCLock(LockType lockType); | ||||||
| 
 | 
 | ||||||
|     void removeUnusedLinks(); |     void removeUnusedLinks(const GCState & state); | ||||||
| 
 | 
 | ||||||
|     void startSubstituter(const Path & substituter, |     void startSubstituter(const Path & substituter, | ||||||
|         RunningSubstituter & runningSubstituter); |         RunningSubstituter & runningSubstituter); | ||||||
|  | @ -298,8 +298,7 @@ void getOwnership(const Path & path); | ||||||
| 
 | 
 | ||||||
| /* Like deletePath(), but changes the ownership of `path' using the
 | /* Like deletePath(), but changes the ownership of `path' using the
 | ||||||
|    setuid wrapper if necessary (and possible). */ |    setuid wrapper if necessary (and possible). */ | ||||||
| void deletePathWrapped(const Path & path, | void deletePathWrapped(const Path & path, unsigned long long & bytesFreed); | ||||||
|     unsigned long long & bytesFreed, unsigned long long & blocksFreed); |  | ||||||
| 
 | 
 | ||||||
| void deletePathWrapped(const Path & path); | void deletePathWrapped(const Path & path); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -102,11 +102,11 @@ void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path) | ||||||
|         /* Nope, create a hard link in the links directory. */ |         /* Nope, create a hard link in the links directory. */ | ||||||
|         makeMutable(path); |         makeMutable(path); | ||||||
|         MakeImmutable mk1(path); |         MakeImmutable mk1(path); | ||||||
| 
 |         if (link(path.c_str(), linkPath.c_str()) == 0) return; | ||||||
|         if (link(path.c_str(), linkPath.c_str()) == -1) |         if (errno != EEXIST) | ||||||
|             throw SysError(format("cannot link `%1%' to `%2%'") % linkPath % path); |             throw SysError(format("cannot link `%1%' to `%2%'") % linkPath % path); | ||||||
| 
 |         /* Fall through if another process created ‘linkPath’ before
 | ||||||
|         return; |            we did. */ | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /* Yes!  We've seen a file with the same contents.  Replace the
 |     /* Yes!  We've seen a file with the same contents.  Replace the
 | ||||||
|  | @ -123,9 +123,6 @@ void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path) | ||||||
| 
 | 
 | ||||||
|     printMsg(lvlTalkative, format("linking `%1%' to `%2%'") % path % linkPath); |     printMsg(lvlTalkative, format("linking `%1%' to `%2%'") % path % linkPath); | ||||||
| 
 | 
 | ||||||
|     Path tempLink = (format("%1%/.tmp-link-%2%-%3%") |  | ||||||
|         % settings.nixStore % getpid() % rand()).str(); |  | ||||||
| 
 |  | ||||||
|     /* Make the containing directory writable, but only if it's not
 |     /* Make the containing directory writable, but only if it's not
 | ||||||
|        the store itself (we don't want or need to mess with its |        the store itself (we don't want or need to mess with its | ||||||
|        permissions). */ |        permissions). */ | ||||||
|  | @ -140,40 +137,55 @@ void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path) | ||||||
|        so make it mutable first (and make it immutable again when |        so make it mutable first (and make it immutable again when | ||||||
|        we're done).  We also have to make ‘path’ mutable, otherwise |        we're done).  We also have to make ‘path’ mutable, otherwise | ||||||
|        rename() will fail to delete it. */ |        rename() will fail to delete it. */ | ||||||
|     makeMutable(linkPath); |  | ||||||
|     MakeImmutable mk1(linkPath); |  | ||||||
| 
 |  | ||||||
|     makeMutable(path); |     makeMutable(path); | ||||||
|     MakeImmutable mk2(path); |     MakeImmutable mk2(path); | ||||||
| 
 | 
 | ||||||
|  |     /* Another process might be doing the same thing (creating a new
 | ||||||
|  |        link to ‘linkPath’) and make ‘linkPath’ immutable before we're | ||||||
|  |        done.  In that case, just retry. */ | ||||||
|  |     unsigned int retries = 1024; | ||||||
|  |     while (--retries > 0) { | ||||||
|  |         makeMutable(linkPath); | ||||||
|  |         MakeImmutable mk1(linkPath); | ||||||
|  | 
 | ||||||
|  |         Path tempLink = (format("%1%/.tmp-link-%2%-%3%") | ||||||
|  |             % settings.nixStore % getpid() % rand()).str(); | ||||||
|  | 
 | ||||||
|         if (link(linkPath.c_str(), tempLink.c_str()) == -1) { |         if (link(linkPath.c_str(), tempLink.c_str()) == -1) { | ||||||
|             if (errno == EMLINK) { |             if (errno == EMLINK) { | ||||||
|             /* Too many links to the same file (>= 32000 on most file
 |                 /* Too many links to the same file (>= 32000 on most
 | ||||||
|                systems).  This is likely to happen with empty files. |                    file systems).  This is likely to happen with empty | ||||||
|                Just shrug and ignore. */ |                    files.  Just shrug and ignore. */ | ||||||
|  |                 if (st.st_size) | ||||||
|                     printMsg(lvlInfo, format("`%1%' has maximum number of links") % linkPath); |                     printMsg(lvlInfo, format("`%1%' has maximum number of links") % linkPath); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |             if (errno == EPERM) continue; | ||||||
|             throw SysError(format("cannot link `%1%' to `%2%'") % tempLink % linkPath); |             throw SysError(format("cannot link `%1%' to `%2%'") % tempLink % linkPath); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /* 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 (errno == EMLINK) { |  | ||||||
|             /* Some filesystems generate too many links on the rename,
 |  | ||||||
|                rather than on the original link.  (Probably it |  | ||||||
|                temporarily increases the st_nlink field before |  | ||||||
|                decreasing it again.) */ |  | ||||||
|             printMsg(lvlInfo, format("`%1%' has maximum number of links") % linkPath); |  | ||||||
| 
 |  | ||||||
|             /* Unlink the temp link. */ |  | ||||||
|             if (unlink(tempLink.c_str()) == -1) |             if (unlink(tempLink.c_str()) == -1) | ||||||
|                 printMsg(lvlError, format("unable to unlink `%1%'") % tempLink); |                 printMsg(lvlError, format("unable to unlink `%1%'") % tempLink); | ||||||
|  |             if (errno == EMLINK) { | ||||||
|  |                 /* Some filesystems generate too many links on the
 | ||||||
|  |                    rename, rather than on the original link. | ||||||
|  |                    (Probably it temporarily increases the st_nlink | ||||||
|  |                    field before decreasing it again.) */ | ||||||
|  |                 if (st.st_size) | ||||||
|  |                     printMsg(lvlInfo, format("`%1%' has maximum number of links") % linkPath); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |             if (errno == EPERM) continue; | ||||||
|             throw SysError(format("cannot rename `%1%' to `%2%'") % tempLink % path); |             throw SysError(format("cannot rename `%1%' to `%2%'") % tempLink % path); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (retries == 0) throw Error(format("cannot link `%1%' to `%2%'") % path % linkPath); | ||||||
|  | 
 | ||||||
|     stats.filesLinked++; |     stats.filesLinked++; | ||||||
|     stats.bytesFreed += st.st_size; |     stats.bytesFreed += st.st_size; | ||||||
|     stats.blocksFreed += st.st_blocks; |     stats.blocksFreed += st.st_blocks; | ||||||
|  |  | ||||||
|  | @ -558,7 +558,7 @@ void RemoteStore::collectGarbage(const GCOptions & options, GCResults & results) | ||||||
| 
 | 
 | ||||||
|     results.paths = readStrings<PathSet>(from); |     results.paths = readStrings<PathSet>(from); | ||||||
|     results.bytesFreed = readLongLong(from); |     results.bytesFreed = readLongLong(from); | ||||||
|     results.blocksFreed = readLongLong(from); |     readLongLong(from); // obsolete
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| #include "globals.hh" | #include "globals.hh" | ||||||
| #include "util.hh" | #include "util.hh" | ||||||
| 
 | 
 | ||||||
| #include <limits.h> | #include <climits> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| namespace nix { | namespace nix { | ||||||
|  | @ -12,7 +12,7 @@ GCOptions::GCOptions() | ||||||
| { | { | ||||||
|     action = gcDeleteDead; |     action = gcDeleteDead; | ||||||
|     ignoreLiveness = false; |     ignoreLiveness = false; | ||||||
|     maxFreed = 0; |     maxFreed = ULLONG_MAX; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -48,8 +48,7 @@ struct GCOptions | ||||||
|     /* For `gcDeleteSpecific', the paths to delete. */ |     /* For `gcDeleteSpecific', the paths to delete. */ | ||||||
|     PathSet pathsToDelete; |     PathSet pathsToDelete; | ||||||
| 
 | 
 | ||||||
|     /* Stop after at least `maxFreed' bytes have been freed.  0 means
 |     /* Stop after at least `maxFreed' bytes have been freed. */ | ||||||
|        no limit. */ |  | ||||||
|     unsigned long long maxFreed; |     unsigned long long maxFreed; | ||||||
| 
 | 
 | ||||||
|     GCOptions(); |     GCOptions(); | ||||||
|  | @ -66,13 +65,9 @@ struct GCResults | ||||||
|        number of bytes that would be or was freed. */ |        number of bytes that would be or was freed. */ | ||||||
|     unsigned long long bytesFreed; |     unsigned long long bytesFreed; | ||||||
| 
 | 
 | ||||||
|     /* The number of file system blocks that would be or was freed. */ |  | ||||||
|     unsigned long long blocksFreed; |  | ||||||
| 
 |  | ||||||
|     GCResults() |     GCResults() | ||||||
|     { |     { | ||||||
|         bytesFreed = 0; |         bytesFreed = 0; | ||||||
|         blocksFreed = 0; |  | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -224,12 +224,12 @@ string readFile(int fd) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| string readFile(const Path & path) | string readFile(const Path & path, bool drain) | ||||||
| { | { | ||||||
|     AutoCloseFD fd = open(path.c_str(), O_RDONLY); |     AutoCloseFD fd = open(path.c_str(), O_RDONLY); | ||||||
|     if (fd == -1) |     if (fd == -1) | ||||||
|         throw SysError(format("opening file `%1%'") % path); |         throw SysError(format("opening file `%1%'") % path); | ||||||
|     return readFile(fd); |     return drain ? drainFD(fd) : readFile(fd); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -297,8 +297,7 @@ void computePathSize(const Path & path, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| static void _deletePath(const Path & path, unsigned long long & bytesFreed, | static void _deletePath(const Path & path, unsigned long long & bytesFreed) | ||||||
|     unsigned long long & blocksFreed) |  | ||||||
| { | { | ||||||
|     checkInterrupt(); |     checkInterrupt(); | ||||||
| 
 | 
 | ||||||
|  | @ -308,10 +307,8 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed, | ||||||
| 
 | 
 | ||||||
|     if (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode)) makeMutable(path); |     if (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode)) makeMutable(path); | ||||||
| 
 | 
 | ||||||
|     if (!S_ISDIR(st.st_mode) && st.st_nlink == 1) { |     if (!S_ISDIR(st.st_mode) && st.st_nlink == 1) | ||||||
|         bytesFreed += st.st_size; |         bytesFreed += st.st_blocks * 512; | ||||||
|         blocksFreed += st.st_blocks; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     if (S_ISDIR(st.st_mode)) { |     if (S_ISDIR(st.st_mode)) { | ||||||
| 	Strings names = readDirectory(path); | 	Strings names = readDirectory(path); | ||||||
|  | @ -323,7 +320,7 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for (Strings::iterator i = names.begin(); i != names.end(); ++i) | 	for (Strings::iterator i = names.begin(); i != names.end(); ++i) | ||||||
|             _deletePath(path + "/" + *i, bytesFreed, blocksFreed); |             _deletePath(path + "/" + *i, bytesFreed); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (remove(path.c_str()) == -1) |     if (remove(path.c_str()) == -1) | ||||||
|  | @ -333,19 +330,17 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed, | ||||||
| 
 | 
 | ||||||
| void deletePath(const Path & path) | void deletePath(const Path & path) | ||||||
| { | { | ||||||
|     unsigned long long dummy1, dummy2; |     unsigned long long dummy; | ||||||
|     deletePath(path, dummy1, dummy2); |     deletePath(path, dummy); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| void deletePath(const Path & path, unsigned long long & bytesFreed, | void deletePath(const Path & path, unsigned long long & bytesFreed) | ||||||
|     unsigned long long & blocksFreed) |  | ||||||
| { | { | ||||||
|     startNest(nest, lvlDebug, |     startNest(nest, lvlDebug, | ||||||
|         format("recursively deleting path `%1%'") % path); |         format("recursively deleting path `%1%'") % path); | ||||||
|     bytesFreed = 0; |     bytesFreed = 0; | ||||||
|     blocksFreed = 0; |     _deletePath(path, bytesFreed); | ||||||
|     _deletePath(path, bytesFreed, blocksFreed); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -17,6 +17,9 @@ namespace nix { | ||||||
| #define foreach(it_type, it, collection)                                \ | #define foreach(it_type, it, collection)                                \ | ||||||
|     for (it_type it = (collection).begin(); it != (collection).end(); ++it) |     for (it_type it = (collection).begin(); it != (collection).end(); ++it) | ||||||
| 
 | 
 | ||||||
|  | #define foreach_reverse(it_type, it, collection)                                \ | ||||||
|  |     for (it_type it = (collection).rbegin(); it != (collection).rend(); ++it) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| /* Return an environment variable. */ | /* Return an environment variable. */ | ||||||
| string getEnv(const string & key, const string & def = ""); | string getEnv(const string & key, const string & def = ""); | ||||||
|  | @ -60,7 +63,7 @@ Strings readDirectory(const Path & path); | ||||||
| 
 | 
 | ||||||
| /* Read the contents of a file into a string. */ | /* Read the contents of a file into a string. */ | ||||||
| string readFile(int fd); | string readFile(int fd); | ||||||
| string readFile(const Path & path); | string readFile(const Path & path, bool drain = false); | ||||||
| 
 | 
 | ||||||
| /* Write a string to a file. */ | /* Write a string to a file. */ | ||||||
| void writeFile(const Path & path, const string & s); | void writeFile(const Path & path, const string & s); | ||||||
|  | @ -80,8 +83,7 @@ void computePathSize(const Path & path, | ||||||
|    returns the number of bytes and blocks freed. */ |    returns the number of bytes and blocks freed. */ | ||||||
| void deletePath(const Path & path); | void deletePath(const Path & path); | ||||||
| 
 | 
 | ||||||
| void deletePath(const Path & path, unsigned long long & bytesFreed, | void deletePath(const Path & path, unsigned long long & bytesFreed); | ||||||
|     unsigned long long & blocksFreed); |  | ||||||
| 
 | 
 | ||||||
| /* Make a path read-only recursively. */ | /* Make a path read-only recursively. */ | ||||||
| void makePathReadOnly(const Path & path); | void makePathReadOnly(const Path & path); | ||||||
|  |  | ||||||
|  | @ -64,9 +64,11 @@ void processExpr(EvalState & state, const Strings & attrPaths, | ||||||
|                     Path drvPath = i->queryDrvPath(state); |                     Path drvPath = i->queryDrvPath(state); | ||||||
|                     if (gcRoot == "") |                     if (gcRoot == "") | ||||||
|                         printGCWarning(); |                         printGCWarning(); | ||||||
|                     else |                     else { | ||||||
|                         drvPath = addPermRoot(*store, drvPath, |                         Path rootName = gcRoot; | ||||||
|                             makeRootName(gcRoot, rootNr), indirectRoot); |                         if (++rootNr > 1) rootName += "-" + int2String(rootNr); | ||||||
|  |                         drvPath = addPermRoot(*store, drvPath, rootName, indirectRoot); | ||||||
|  |                     } | ||||||
|                     std::cout << format("%1%\n") % drvPath; |                     std::cout << format("%1%\n") % drvPath; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -64,15 +64,19 @@ static PathSet realisePath(const Path & path) | ||||||
|     if (isDerivation(path)) { |     if (isDerivation(path)) { | ||||||
|         store->buildPaths(singleton<PathSet>(path)); |         store->buildPaths(singleton<PathSet>(path)); | ||||||
|         Derivation drv = derivationFromPath(*store, path); |         Derivation drv = derivationFromPath(*store, path); | ||||||
|  |         rootNr++; | ||||||
| 
 | 
 | ||||||
|         PathSet outputs; |         PathSet outputs; | ||||||
|         foreach (DerivationOutputs::iterator, i, drv.outputs) { |         foreach (DerivationOutputs::iterator, i, drv.outputs) { | ||||||
|             Path outPath = i->second.path; |             Path outPath = i->second.path; | ||||||
|             if (gcRoot == "") |             if (gcRoot == "") | ||||||
|                 printGCWarning(); |                 printGCWarning(); | ||||||
|             else |             else { | ||||||
|                 outPath = addPermRoot(*store, outPath, |                 Path rootName = gcRoot; | ||||||
|                     makeRootName(gcRoot, rootNr), indirectRoot); |                 if (rootNr > 1) rootName += "-" + int2String(rootNr); | ||||||
|  |                 if (i->first != "out") rootName += "-" + i->first; | ||||||
|  |                 outPath = addPermRoot(*store, outPath, rootName, indirectRoot); | ||||||
|  |             } | ||||||
|             outputs.insert(outPath); |             outputs.insert(outPath); | ||||||
|         } |         } | ||||||
|         return outputs; |         return outputs; | ||||||
|  | @ -544,10 +548,9 @@ static void opCheckValidity(Strings opFlags, Strings opArgs) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| static string showBytes(unsigned long long bytes, unsigned long long blocks) | static string showBytes(unsigned long long bytes) | ||||||
| { | { | ||||||
|     return (format("%d bytes (%.2f MiB, %d blocks)") |     return (format("%.2f MiB") % (bytes / (1024.0 * 1024.0))).str(); | ||||||
|         % bytes % (bytes / (1024.0 * 1024.0)) % blocks).str(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -562,7 +565,7 @@ struct PrintFreed | ||||||
|         if (show) |         if (show) | ||||||
|             cout << format("%1% store paths deleted, %2% freed\n") |             cout << format("%1% store paths deleted, %2% freed\n") | ||||||
|                 % results.paths.size() |                 % results.paths.size() | ||||||
|                 % showBytes(results.bytesFreed, results.blocksFreed); |                 % showBytes(results.bytesFreed); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -583,7 +586,7 @@ static void opGC(Strings opFlags, Strings opArgs) | ||||||
|         else if (*i == "--delete") options.action = GCOptions::gcDeleteDead; |         else if (*i == "--delete") options.action = GCOptions::gcDeleteDead; | ||||||
|         else if (*i == "--max-freed") { |         else if (*i == "--max-freed") { | ||||||
|             long long maxFreed = getIntArg<long long>(*i, i, opFlags.end()); |             long long maxFreed = getIntArg<long long>(*i, i, opFlags.end()); | ||||||
|             options.maxFreed = maxFreed >= 1 ? maxFreed : 1; |             options.maxFreed = maxFreed >= 0 ? maxFreed : 0; | ||||||
|         } |         } | ||||||
|         else throw UsageError(format("bad sub-operation `%1%' in GC") % *i); |         else throw UsageError(format("bad sub-operation `%1%' in GC") % *i); | ||||||
| 
 | 
 | ||||||
|  | @ -735,7 +738,7 @@ static void showOptimiseStats(OptimiseStats & stats) | ||||||
| { | { | ||||||
|     printMsg(lvlError, |     printMsg(lvlError, | ||||||
|         format("%1% freed by hard-linking %2% files; there are %3% files with equal contents out of %4% files in total") |         format("%1% freed by hard-linking %2% files; there are %3% files with equal contents out of %4% files in total") | ||||||
|         % showBytes(stats.bytesFreed, stats.blocksFreed) |         % showBytes(stats.bytesFreed) | ||||||
|         % stats.filesLinked |         % stats.filesLinked | ||||||
|         % stats.sameContents |         % stats.sameContents | ||||||
|         % stats.totalFiles); |         % stats.totalFiles); | ||||||
|  |  | ||||||
|  | @ -521,7 +521,7 @@ static void performOp(unsigned int clientVersion, | ||||||
| 
 | 
 | ||||||
|         writeStrings(results.paths, to); |         writeStrings(results.paths, to); | ||||||
|         writeLongLong(results.bytesFreed, to); |         writeLongLong(results.bytesFreed, to); | ||||||
|         writeLongLong(results.blocksFreed, to); |         writeLongLong(0, to); // obsolete
 | ||||||
| 
 | 
 | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|  | @ -661,6 +661,10 @@ static void processConnection() | ||||||
|     to.flush(); |     to.flush(); | ||||||
|     unsigned int clientVersion = readInt(from); |     unsigned int clientVersion = readInt(from); | ||||||
| 
 | 
 | ||||||
|  |     bool reserveSpace = true; | ||||||
|  |     if (GET_PROTOCOL_MINOR(clientVersion) >= 11) | ||||||
|  |         reserveSpace = readInt(from) != 0; | ||||||
|  | 
 | ||||||
|     /* Send startup error messages to the client. */ |     /* Send startup error messages to the client. */ | ||||||
|     startWork(); |     startWork(); | ||||||
| 
 | 
 | ||||||
|  | @ -676,10 +680,6 @@ static void processConnection() | ||||||
|             throw Error("if you run `nix-worker' as root, then you MUST set `build-users-group'!"); |             throw Error("if you run `nix-worker' as root, then you MUST set `build-users-group'!"); | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|         bool reserveSpace = true; |  | ||||||
|         if (GET_PROTOCOL_MINOR(clientVersion) >= 11) |  | ||||||
|             reserveSpace = readInt(from) != 0; |  | ||||||
| 
 |  | ||||||
|         /* Open the store. */ |         /* Open the store. */ | ||||||
|         store = boost::shared_ptr<StoreAPI>(new LocalStore(reserveSpace)); |         store = boost::shared_ptr<StoreAPI>(new LocalStore(reserveSpace)); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue