diff --git a/scripts/nix-build.in b/scripts/nix-build.in index aa3f4661ae..c46b270ff8 100755 --- a/scripts/nix-build.in +++ b/scripts/nix-build.in @@ -46,7 +46,7 @@ Flags: --no-out-link: do not create the `result' symlink --out-link / -o NAME: create symlink NAME instead of `result' --attr / -A ATTR: select a specific attribute from the Nix expression - + --run-env: build dependencies of the specified derivation, then start a shell with the environment of the derivation --command: command to run with `--run-env' @@ -119,7 +119,7 @@ EOF push @buildArgs, "--dry-run"; $dryRun = 1; } - + elsif ($arg eq "--show-trace") { push @instArgs, $arg; } @@ -127,22 +127,22 @@ EOF elsif ($arg eq "-") { @exprs = ("-"); } - + elsif ($arg eq "--verbose" or substr($arg, 0, 2) eq "-v") { push @buildArgs, $arg; push @instArgs, $arg; $verbose = 1; } - + elsif ($arg eq "--quiet") { push @buildArgs, $arg; push @instArgs, $arg; } - + elsif ($arg eq "--run-env") { $runEnv = 1; } - + elsif ($arg eq "--command") { $n++; die "$0: `$arg' requires an argument\n" unless $n < scalar @ARGV; diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 74f7560fe0..c10177223e 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -144,6 +144,8 @@ EvalState::EvalState() { nrEnvs = nrValuesInEnvs = nrValues = nrListElems = 0; nrAttrsets = nrOpUpdates = nrOpUpdateValuesCopied = 0; + nrListConcats = nrPrimOpCalls = nrFunctionCalls = 0; + countCalls = getEnv("NIX_COUNT_CALLS", "0") != "0"; #if HAVE_BOEHMGC static bool gcInitialised = true; @@ -300,8 +302,10 @@ inline Value * EvalState::lookupVar(Env * env, const VarRef & var) if (var.fromWith) { while (1) { 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; + } if (env->prevWith == 0) throwEvalError("undefined variable `%1%'", var.name); 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.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; } @@ -619,8 +623,10 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) } vAttrs = j->value; pos = j->pos; + if (state.countCalls && pos) state.attrSelects[*pos]++; } + state.forceValue(*vAttrs); } catch (Error & e) { @@ -700,6 +706,8 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v) vArgs[n--] = arg->primOpApp.right; /* And call the primop. */ + nrPrimOpCalls++; + if (countCalls) primOpCalls[primOp->primOp->name]++; try { primOp->primOp->fun(*this, vArgs, v); } catch (Error & e) { @@ -716,7 +724,7 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v) } 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)); 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); } + nrFunctionCalls++; + if (countCalls) functionCalls[fun.lambda.fun->pos]++; + try { fun.lambda.fun->body->eval(*this, env2, v); } 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) { Value v1; e1->eval(state, env, v1); - state.forceList(v1); Value v2; e2->eval(state, env, v2); - state.forceList(v2); - state.mkList(v, v1.list.length + v2.list.length); - 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]; + Value * lists[2] = { &v1, &v2 }; + state.concatLists(v, 2, lists); +} + + +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; + } } @@ -932,7 +965,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) isPath = vStr.type == tPath; first = false; } - + s << state.coerceToString(vStr, context, false, !isPath); } @@ -1207,6 +1240,7 @@ void EvalState::printStats() % nrEnvs % (nrEnvs * sizeof(Env) + nrValuesInEnvs * sizeof(Value *))); printMsg(v, format(" list elements: %1% (%2% bytes)") % nrListElems % (nrListElems * sizeof(Value *))); + printMsg(v, format(" list concatenations: %1%") % nrListConcats); printMsg(v, format(" values allocated: %1% (%2% bytes)") % nrValues % (nrValues * sizeof(Value))); 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 avoided: %1%") % nrAvoided); 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 PrimOpCalls_; + std::multimap primOpCalls_; + foreach (PrimOpCalls::iterator, i, primOpCalls) + primOpCalls_.insert(std::pair(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 FunctionCalls_; + std::multimap functionCalls_; + foreach (FunctionCalls::iterator, i, functionCalls) + functionCalls_.insert(std::pair(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 AttrSelects_; + std::multimap attrSelects_; + foreach (AttrSelects::iterator, i, attrSelects) + attrSelects_.insert(std::pair(i->second, i->first)); + foreach_reverse (AttrSelects_::reverse_iterator, i, attrSelects_) + printMsg(v, format("%1$10d %2%") % i->first % i->second); + + } } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 5103ae8cef..a1f26a0566 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -232,11 +232,13 @@ public: void mkAttrs(Value & v, unsigned int expected); void mkThunk_(Value & v, Expr * expr); + void concatLists(Value & v, unsigned int nrLists, Value * * lists); + /* Print statistics. */ void printStats(); private: - + unsigned long nrEnvs; unsigned long nrValuesInEnvs; unsigned long nrValues; @@ -244,9 +246,25 @@ private: unsigned long nrAttrsets; unsigned long nrOpUpdates; unsigned long nrOpUpdateValuesCopied; - - friend class RecursionCounter; + unsigned long nrListConcats; + unsigned long nrPrimOpCalls; + unsigned long nrFunctionCalls; + + bool countCalls; + + typedef std::map PrimOpCalls; + PrimOpCalls primOpCalls; + + typedef std::map FunctionCalls; + FunctionCalls functionCalls; + + typedef std::map AttrSelects; + AttrSelects attrSelects; + friend class ExprOpUpdate; + friend class ExprOpConcatLists; + friend class ExprSelect; + friend void prim_getAttr(EvalState & state, Value * * args, Value & v); }; diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 4c1a0bb2d5..bc6c3287c7 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -27,6 +27,15 @@ struct Pos Pos() : line(0), column(0) { }; Pos(const string & file, unsigned int line, unsigned int 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; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 095e288430..1819da5e1c 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -203,7 +203,7 @@ static Expr * stripIndentation(SymbolTable & symbols, vector & es) es2->push_back(new ExprString(symbols.create(s2))); } - return new ExprConcatStrings(es2); + return es2->size() == 1 ? (*es2)[0] : new ExprConcatStrings(es2); } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 7258c4cc0f..d3809e6984 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -719,7 +719,7 @@ static void prim_attrNames(EvalState & state, Value * * args, Value & v) /* 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]); state.forceAttrs(*args[1]); @@ -728,6 +728,7 @@ static void prim_getAttr(EvalState & state, Value * * args, Value & v) if (i == args[1]->attrs->end()) throw EvalError(format("attribute `%1%' missing") % attr); // !!! add to stack trace? + if (state.countCalls && i->pos) state.attrSelects[*i->pos]++; state.forceValue(*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. */ static void prim_head(EvalState & state, Value * * args, Value & v) { - state.forceList(*args[0]); - 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]; + elemAt(state, *args[0], 0, v); } /* 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) { 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. */ static void prim_length(EvalState & state, Value * * args, Value & v) { @@ -1122,11 +1183,15 @@ void EvalState::createBaseEnv() // Lists addPrimOp("__isList", 1, prim_isList); + addPrimOp("__elemAt", 2, prim_elemAt); addPrimOp("__head", 1, prim_head); addPrimOp("__tail", 1, prim_tail); 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); - + // Integer arithmetic addPrimOp("__add", 2, prim_add); addPrimOp("__sub", 2, prim_sub); diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index e1280911b3..5f92c3df34 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -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() { static bool haveWarned = false; diff --git a/src/libmain/shared.hh b/src/libmain/shared.hh index 7849e10e36..c69879a123 100644 --- a/src/libmain/shared.hh +++ b/src/libmain/shared.hh @@ -26,7 +26,6 @@ MakeError(UsageError, nix::Error); class StoreAPI; /* Ugh. No better place to put this. */ -Path makeRootName(const Path & gcRoot, int & counter); void printGCWarning(); void printMissing(StoreAPI & store, const PathSet & paths); diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 0972d6e193..1840fb7b21 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -45,7 +45,7 @@ #include #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 #include @@ -604,18 +604,17 @@ void getOwnership(const Path & path) } -void deletePathWrapped(const Path & path, - unsigned long long & bytesFreed, unsigned long long & blocksFreed) +void deletePathWrapped(const Path & path, unsigned long long & bytesFreed) { try { /* First try to delete it ourselves. */ - deletePath(path, bytesFreed, blocksFreed); + deletePath(path, bytesFreed); } catch (SysError & e) { /* If this failed due to a permission error, then try it with the setuid helper. */ if (settings.buildUsersGroup != "" && !amPrivileged()) { getOwnership(path); - deletePath(path, bytesFreed, blocksFreed); + deletePath(path, bytesFreed); } else throw; } @@ -624,8 +623,8 @@ void deletePathWrapped(const Path & path, void deletePathWrapped(const Path & path) { - unsigned long long dummy1, dummy2; - deletePathWrapped(path, dummy1, dummy2); + unsigned long long dummy1; + 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); } @@ -1674,7 +1673,7 @@ void DerivationGoal::startBuilder() instead.) */ Path chrootTmpDir = chrootRootDir + "/tmp"; createDirs(chrootTmpDir); - chmod(chrootTmpDir, 01777); + chmod_(chrootTmpDir, 01777); /* Create a /etc/passwd with entries for the build user and the 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 build user. */ createDirs(chrootRootDir + settings.nixStore); - chmod(chrootRootDir + settings.nixStore, 01777); + chmod_(chrootRootDir + settings.nixStore, 01777); foreach (PathSet::iterator, i, inputPaths) { struct stat st; @@ -1844,22 +1843,40 @@ void DerivationGoal::initChild() char domainname[] = "(none)"; // kernel default 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" filesystem that we want in the chroot environment. */ foreach (PathSet::iterator, i, dirsInChroot) { Path source = *i; Path target = chrootRootDir + source; + if (source == "/proc") continue; // backwards compatibility debug(format("bind mounting `%1%' to `%2%'") % source % target); - createDirs(target); - if (mount(source.c_str(), target.c_str(), "", MS_BIND, 0) == -1) 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. */ + createDirs(chrootRootDir + "/proc"); if (mount("none", (chrootRootDir + "/proc").c_str(), "proc", 0, 0) == -1) throw SysError("mounting /proc"); diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index 1355702f87..88b7bec326 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -425,10 +425,9 @@ bool LocalStore::isActiveTempFile(const GCState & state, void LocalStore::deleteGarbage(GCState & state, const Path & path) { printMsg(lvlInfo, format("deleting `%1%'") % path); - unsigned long long bytesFreed, blocksFreed; - deletePathWrapped(path, bytesFreed, blocksFreed); + unsigned long long bytesFreed; + deletePathWrapped(path, bytesFreed); state.results.bytesFreed += bytesFreed; - state.results.blocksFreed += blocksFreed; } @@ -550,7 +549,7 @@ bool LocalStore::tryToDelete(GCState & state, const Path & path) } else 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); throw GCLimitReached(); } @@ -576,11 +575,13 @@ bool LocalStore::tryToDelete(GCState & state, const Path & path) safely deleted. FIXME: race condition with optimisePath(): we might see a link count of 1 just before optimisePath() increases the link count. */ -void LocalStore::removeUnusedLinks() +void LocalStore::removeUnusedLinks(const GCState & state) { AutoCloseDir dir = opendir(linksDir.c_str()); if (!dir) throw SysError(format("opening directory `%1%'") % linksDir); + long long actualSize = 0, unsharedSize = 0; + struct dirent * dirent; while (errno = 0, dirent = readdir(dir)) { checkInterrupt(); @@ -592,13 +593,28 @@ void LocalStore::removeUnusedLinks() if (lstat(path.c_str(), &st) == -1) 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); if (unlink(path.c_str()) == -1) 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); } - } else { + } else if (options.maxFreed > 0) { if (shouldDelete(state.options.action)) printMsg(lvlError, format("deleting garbage...")); @@ -718,7 +734,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) /* Clean up the links directory. */ printMsg(lvlError, format("deleting unused links...")); - removeUnusedLinks(); + removeUnusedLinks(state); } diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh index 3cb016e9ca..ba05829221 100644 --- a/src/libstore/local-store.hh +++ b/src/libstore/local-store.hh @@ -261,7 +261,7 @@ private: int openGCLock(LockType lockType); - void removeUnusedLinks(); + void removeUnusedLinks(const GCState & state); void startSubstituter(const Path & substituter, RunningSubstituter & runningSubstituter); @@ -298,8 +298,7 @@ void getOwnership(const Path & path); /* Like deletePath(), but changes the ownership of `path' using the setuid wrapper if necessary (and possible). */ -void deletePathWrapped(const Path & path, - unsigned long long & bytesFreed, unsigned long long & blocksFreed); +void deletePathWrapped(const Path & path, unsigned long long & bytesFreed); void deletePathWrapped(const Path & path); diff --git a/src/libstore/optimise-store.cc b/src/libstore/optimise-store.cc index 334f4f355f..9d0242bbc8 100644 --- a/src/libstore/optimise-store.cc +++ b/src/libstore/optimise-store.cc @@ -102,11 +102,11 @@ void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path) /* Nope, create a hard link in the links directory. */ makeMutable(path); MakeImmutable mk1(path); - - if (link(path.c_str(), linkPath.c_str()) == -1) + if (link(path.c_str(), linkPath.c_str()) == 0) return; + if (errno != EEXIST) throw SysError(format("cannot link `%1%' to `%2%'") % linkPath % path); - - return; + /* Fall through if another process created ‘linkPath’ before + we did. */ } /* 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); - Path tempLink = (format("%1%/.tmp-link-%2%-%3%") - % settings.nixStore % getpid() % rand()).str(); - /* Make the containing directory writable, but only if it's not the store itself (we don't want or need to mess with its permissions). */ @@ -140,40 +137,55 @@ void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path) so make it mutable first (and make it immutable again when we're done). We also have to make ‘path’ mutable, otherwise rename() will fail to delete it. */ - makeMutable(linkPath); - MakeImmutable mk1(linkPath); - makeMutable(path); MakeImmutable mk2(path); - if (link(linkPath.c_str(), tempLink.c_str()) == -1) { - if (errno == EMLINK) { - /* Too many links to the same file (>= 32000 on most file - systems). This is likely to happen with empty files. - Just shrug and ignore. */ - printMsg(lvlInfo, format("`%1%' has maximum number of links") % linkPath); - return; + /* 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 (errno == EMLINK) { + /* Too many links to the same file (>= 32000 on most + file systems). This is likely to happen with empty + files. Just shrug and ignore. */ + if (st.st_size) + printMsg(lvlInfo, format("`%1%' has maximum number of links") % linkPath); + 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. */ - 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. */ + /* Atomically replace the old file with the new hard link. */ + if (rename(tempLink.c_str(), path.c_str()) == -1) { if (unlink(tempLink.c_str()) == -1) printMsg(lvlError, format("unable to unlink `%1%'") % tempLink); - return; + 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; + } + 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.bytesFreed += st.st_size; stats.blocksFreed += st.st_blocks; diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index 56396541ad..d3c05f0df4 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -558,7 +558,7 @@ void RemoteStore::collectGarbage(const GCOptions & options, GCResults & results) results.paths = readStrings(from); results.bytesFreed = readLongLong(from); - results.blocksFreed = readLongLong(from); + readLongLong(from); // obsolete } diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 6f81a9aab0..32aaca6be0 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -2,7 +2,7 @@ #include "globals.hh" #include "util.hh" -#include +#include namespace nix { @@ -12,7 +12,7 @@ GCOptions::GCOptions() { action = gcDeleteDead; ignoreLiveness = false; - maxFreed = 0; + maxFreed = ULLONG_MAX; } diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index 324d802dc4..a562360ce3 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -48,8 +48,7 @@ struct GCOptions /* For `gcDeleteSpecific', the paths to delete. */ PathSet pathsToDelete; - /* Stop after at least `maxFreed' bytes have been freed. 0 means - no limit. */ + /* Stop after at least `maxFreed' bytes have been freed. */ unsigned long long maxFreed; GCOptions(); @@ -66,13 +65,9 @@ struct GCResults number of bytes that would be or was freed. */ unsigned long long bytesFreed; - /* The number of file system blocks that would be or was freed. */ - unsigned long long blocksFreed; - GCResults() { bytesFreed = 0; - blocksFreed = 0; } }; diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 3790e2fa3b..56bf5875de 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -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); if (fd == -1) 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, - unsigned long long & blocksFreed) +static void _deletePath(const Path & path, unsigned long long & bytesFreed) { 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) && st.st_nlink == 1) { - bytesFreed += st.st_size; - blocksFreed += st.st_blocks; - } + if (!S_ISDIR(st.st_mode) && st.st_nlink == 1) + bytesFreed += st.st_blocks * 512; if (S_ISDIR(st.st_mode)) { 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) - _deletePath(path + "/" + *i, bytesFreed, blocksFreed); + _deletePath(path + "/" + *i, bytesFreed); } 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) { - unsigned long long dummy1, dummy2; - deletePath(path, dummy1, dummy2); + unsigned long long dummy; + deletePath(path, dummy); } -void deletePath(const Path & path, unsigned long long & bytesFreed, - unsigned long long & blocksFreed) +void deletePath(const Path & path, unsigned long long & bytesFreed) { startNest(nest, lvlDebug, format("recursively deleting path `%1%'") % path); bytesFreed = 0; - blocksFreed = 0; - _deletePath(path, bytesFreed, blocksFreed); + _deletePath(path, bytesFreed); } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 16633a0835..0616288cd5 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -17,6 +17,9 @@ namespace nix { #define foreach(it_type, it, collection) \ 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. */ 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. */ string readFile(int fd); -string readFile(const Path & path); +string readFile(const Path & path, bool drain = false); /* Write a string to a file. */ 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. */ void deletePath(const Path & path); -void deletePath(const Path & path, unsigned long long & bytesFreed, - unsigned long long & blocksFreed); +void deletePath(const Path & path, unsigned long long & bytesFreed); /* Make a path read-only recursively. */ void makePathReadOnly(const Path & path); diff --git a/src/nix-instantiate/nix-instantiate.cc b/src/nix-instantiate/nix-instantiate.cc index 270b4ddc2e..beb7fa0249 100644 --- a/src/nix-instantiate/nix-instantiate.cc +++ b/src/nix-instantiate/nix-instantiate.cc @@ -64,9 +64,11 @@ void processExpr(EvalState & state, const Strings & attrPaths, Path drvPath = i->queryDrvPath(state); if (gcRoot == "") printGCWarning(); - else - drvPath = addPermRoot(*store, drvPath, - makeRootName(gcRoot, rootNr), indirectRoot); + else { + Path rootName = gcRoot; + if (++rootNr > 1) rootName += "-" + int2String(rootNr); + drvPath = addPermRoot(*store, drvPath, rootName, indirectRoot); + } std::cout << format("%1%\n") % drvPath; } } diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index 5ada797136..d3a707f0d8 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -64,15 +64,19 @@ static PathSet realisePath(const Path & path) if (isDerivation(path)) { store->buildPaths(singleton(path)); Derivation drv = derivationFromPath(*store, path); + rootNr++; PathSet outputs; foreach (DerivationOutputs::iterator, i, drv.outputs) { Path outPath = i->second.path; if (gcRoot == "") printGCWarning(); - else - outPath = addPermRoot(*store, outPath, - makeRootName(gcRoot, rootNr), indirectRoot); + else { + Path rootName = gcRoot; + if (rootNr > 1) rootName += "-" + int2String(rootNr); + if (i->first != "out") rootName += "-" + i->first; + outPath = addPermRoot(*store, outPath, rootName, indirectRoot); + } outputs.insert(outPath); } 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)") - % bytes % (bytes / (1024.0 * 1024.0)) % blocks).str(); + return (format("%.2f MiB") % (bytes / (1024.0 * 1024.0))).str(); } @@ -562,7 +565,7 @@ struct PrintFreed if (show) cout << format("%1% store paths deleted, %2% freed\n") % 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 == "--max-freed") { long long maxFreed = getIntArg(*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); @@ -735,7 +738,7 @@ static void showOptimiseStats(OptimiseStats & stats) { printMsg(lvlError, 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.sameContents % stats.totalFiles); diff --git a/src/nix-worker/nix-worker.cc b/src/nix-worker/nix-worker.cc index 8ccafca29d..dadde9cc51 100644 --- a/src/nix-worker/nix-worker.cc +++ b/src/nix-worker/nix-worker.cc @@ -521,7 +521,7 @@ static void performOp(unsigned int clientVersion, writeStrings(results.paths, to); writeLongLong(results.bytesFreed, to); - writeLongLong(results.blocksFreed, to); + writeLongLong(0, to); // obsolete break; } @@ -661,6 +661,10 @@ static void processConnection() to.flush(); 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. */ startWork(); @@ -676,10 +680,6 @@ static void processConnection() throw Error("if you run `nix-worker' as root, then you MUST set `build-users-group'!"); #endif - bool reserveSpace = true; - if (GET_PROTOCOL_MINOR(clientVersion) >= 11) - reserveSpace = readInt(from) != 0; - /* Open the store. */ store = boost::shared_ptr(new LocalStore(reserveSpace));