Merge branch 'nix'.

This is a squashed commit of the following:

commit 0dccab9f41
Author: Eelco Dolstra <eelco.dolstra@logicblox.com>
Date:   Thu Jul 2 00:30:16 2015 +0200

    Typo

commit 2cd28517b1
Author: Ludovic Courtès <ludo@gnu.org>
Date:   Wed Jul 1 14:56:34 2015 +0200

    Preserve supplementary groups of build users

    The following patch is an attempt to address this bug (see
    <http://bugs.gnu.org/18994>) by preserving the supplementary groups of
    build users in the build environment.

    In practice, I would expect that supplementary groups would contain only
    one or two groups: the build users group, and possibly the “kvm” group.

    [Changed &at(0) to data() and removed tabs - Eelco]

commit 6e38685ef6
Author: Eelco Dolstra <eelco.dolstra@logicblox.com>
Date:   Tue Jun 30 21:41:26 2015 +0200

    GC: Handle ENOSPC creating/moving to the trash directory

    Issue #564.

commit 5e0a9ae2e2
Author: Eelco Dolstra <eelco.dolstra@logicblox.com>
Date:   Mon Jun 22 15:54:55 2015 +0200

    Use posix_fallocate to create /nix/var/nix/db/reserved

commit 4e5ab98d6d
Author: Eelco Dolstra <eelco.dolstra@logicblox.com>
Date:   Mon Jun 22 15:47:40 2015 +0200

    Make /nix/var/nix/db/reserved bigger

    Issue #564.

commit 60bda60fc0
Author: Eelco Dolstra <eelco.dolstra@logicblox.com>
Date:   Wed Jun 10 16:17:06 2015 +0200

    Export outputPaths function

    This is useful for the new hydra-queue-runner.

commit 5dfea34048
Author: Eelco Dolstra <eelco.dolstra@logicblox.com>
Date:   Thu Jul 2 22:51:33 2015 +0200

    Use std::vector::data()

commit 2459458bc8
Author: Eelco Dolstra <eelco.dolstra@logicblox.com>
Date:   Thu Jun 4 16:04:41 2015 +0200

    Allow substitutes for builds that have preferLocalBuild set

    Not substituting builds with "preferLocalBuild = true" was a bad idea,
    because it didn't take the cost of dependencies into account. For
    instance, if we can't substitute a fetchgit call, then we have to
    download/build git and all its dependencies.

    Partially reverts 5558652709 and adds a
    new derivation attribute "allowSubstitutes" to specify whether a
    derivation may be substituted.
This commit is contained in:
Ludovic Courtès 2015-07-02 23:37:29 +02:00
parent d2cef629fd
commit 322eeb87d0
11 changed files with 94 additions and 55 deletions

View File

@ -447,6 +447,7 @@ private:
string user; string user;
uid_t uid; uid_t uid;
gid_t gid; gid_t gid;
std::vector<gid_t> supplementaryGIDs;
public: public:
UserLock(); UserLock();
@ -460,6 +461,7 @@ public:
string getUser() { return user; } string getUser() { return user; }
uid_t getUID() { return uid; } uid_t getUID() { return uid; }
uid_t getGID() { return gid; } uid_t getGID() { return gid; }
std::vector<gid_t> getSupplementaryGIDs() { return supplementaryGIDs; }
bool enabled() { return uid != 0; } bool enabled() { return uid != 0; }
@ -539,6 +541,17 @@ void UserLock::acquire()
throw Error(format("the Nix user should not be a member of `%1%'") throw Error(format("the Nix user should not be a member of `%1%'")
% settings.buildUsersGroup); % settings.buildUsersGroup);
/* Get the list of supplementary groups of this build user. This
is usually either empty or contains a group such as "kvm". */
supplementaryGIDs.resize(10);
int ngroups = supplementaryGIDs.size();
int err = getgrouplist(pw->pw_name, pw->pw_gid,
supplementaryGIDs.data(), &ngroups);
if (err == -1)
throw Error(format("failed to get list of supplementary groups for %1%") % pw->pw_name);
supplementaryGIDs.resize(ngroups);
return; return;
} }
} }
@ -1000,7 +1013,7 @@ void DerivationGoal::haveDerivation()
/* We are first going to try to create the invalid output paths /* We are first going to try to create the invalid output paths
through substitutes. If that doesn't work, we'll build through substitutes. If that doesn't work, we'll build
them. */ them. */
if (settings.useSubstitutes && !willBuildLocally(drv)) if (settings.useSubstitutes && substitutesAllowed(drv))
foreach (PathSet::iterator, i, invalidOutputs) foreach (PathSet::iterator, i, invalidOutputs)
addWaitee(worker.makeSubstitutionGoal(*i, buildMode == bmRepair)); addWaitee(worker.makeSubstitutionGoal(*i, buildMode == bmRepair));
@ -1188,22 +1201,6 @@ void DerivationGoal::inputsRealised()
} }
PathSet outputPaths(const DerivationOutputs & outputs)
{
PathSet paths;
foreach (DerivationOutputs::const_iterator, i, outputs)
paths.insert(i->second.path);
return paths;
}
static string get(const StringPairs & map, const string & key)
{
StringPairs::const_iterator i = map.find(key);
return i == map.end() ? (string) "" : i->second;
}
static bool canBuildLocally(const string & platform) static bool canBuildLocally(const string & platform)
{ {
return platform == settings.thisSystem return platform == settings.thisSystem
@ -1214,12 +1211,25 @@ static bool canBuildLocally(const string & platform)
} }
static string get(const StringPairs & map, const string & key, const string & def = "")
{
StringPairs::const_iterator i = map.find(key);
return i == map.end() ? def : i->second;
}
bool willBuildLocally(const Derivation & drv) bool willBuildLocally(const Derivation & drv)
{ {
return get(drv.env, "preferLocalBuild") == "1" && canBuildLocally(drv.platform); return get(drv.env, "preferLocalBuild") == "1" && canBuildLocally(drv.platform);
} }
bool substitutesAllowed(const Derivation & drv)
{
return get(drv.env, "allowSubstitutes", "1") == "1";
}
void DerivationGoal::tryToBuild() void DerivationGoal::tryToBuild()
{ {
trace("trying to build"); trace("trying to build");
@ -1242,7 +1252,7 @@ void DerivationGoal::tryToBuild()
can't acquire the lock, then continue; hopefully some other can't acquire the lock, then continue; hopefully some other
goal can start a build, and if not, the main loop will sleep a goal can start a build, and if not, the main loop will sleep a
few seconds and then retry this goal. */ few seconds and then retry this goal. */
if (!outputLocks.lockPaths(outputPaths(drv.outputs), "", false)) { if (!outputLocks.lockPaths(outputPaths(drv), "", false)) {
worker.waitForAWhile(shared_from_this()); worker.waitForAWhile(shared_from_this());
return; return;
} }
@ -1263,7 +1273,7 @@ void DerivationGoal::tryToBuild()
return; return;
} }
missingPaths = outputPaths(drv.outputs); missingPaths = outputPaths(drv);
if (buildMode != bmCheck) if (buildMode != bmCheck)
foreach (PathSet::iterator, i, validPaths) missingPaths.erase(*i); foreach (PathSet::iterator, i, validPaths) missingPaths.erase(*i);
@ -2168,7 +2178,6 @@ void DerivationGoal::runChild()
Strings envStrs; Strings envStrs;
foreach (Environment::const_iterator, i, env) foreach (Environment::const_iterator, i, env)
envStrs.push_back(rewriteHashes(i->first + "=" + i->second, rewritesToTmp)); envStrs.push_back(rewriteHashes(i->first + "=" + i->second, rewritesToTmp));
auto envArr = stringsToCharPtrs(envStrs);
/* If we are running in `build-users' mode, then switch to the /* If we are running in `build-users' mode, then switch to the
user we allocated above. Make sure that we drop all root user we allocated above. Make sure that we drop all root
@ -2177,10 +2186,11 @@ void DerivationGoal::runChild()
setuid() when run as root sets the real, effective and setuid() when run as root sets the real, effective and
saved UIDs. */ saved UIDs. */
if (buildUser.enabled()) { if (buildUser.enabled()) {
printMsg(lvlChatty, format("switching to user `%1%'") % buildUser.getUser()); /* Preserve supplementary groups of the build user, to allow
admins to specify groups such as "kvm". */
if (setgroups(0, 0) == -1) if (setgroups(buildUser.getSupplementaryGIDs().size(),
throw SysError("cannot clear the set of supplementary groups"); buildUser.getSupplementaryGIDs().data()) == -1)
throw SysError("cannot set supplementary groups of build user");
if (setgid(buildUser.getGID()) == -1 || if (setgid(buildUser.getGID()) == -1 ||
getgid() != buildUser.getGID() || getgid() != buildUser.getGID() ||
@ -2199,7 +2209,6 @@ void DerivationGoal::runChild()
args.push_back(builderBasename); args.push_back(builderBasename);
foreach (Strings::iterator, i, drv.args) foreach (Strings::iterator, i, drv.args)
args.push_back(rewriteHashes(*i, rewritesToTmp)); args.push_back(rewriteHashes(*i, rewritesToTmp));
auto argArr = stringsToCharPtrs(args);
restoreSIGPIPE(); restoreSIGPIPE();
@ -2207,7 +2216,7 @@ void DerivationGoal::runChild()
writeFull(STDERR_FILENO, "\n"); writeFull(STDERR_FILENO, "\n");
/* Execute the program. This should not return. */ /* Execute the program. This should not return. */
execve(drv.builder.c_str(), (char * *) &argArr[0], (char * *) &envArr[0]); execve(drv.builder.c_str(), stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
throw SysError(format("executing `%1%'") % drv.builder); throw SysError(format("executing `%1%'") % drv.builder);
@ -2837,7 +2846,6 @@ void SubstitutionGoal::tryToRun()
args.push_back("--substitute"); args.push_back("--substitute");
args.push_back(storePath); args.push_back(storePath);
args.push_back(destPath); args.push_back(destPath);
auto argArr = stringsToCharPtrs(args);
/* Fork the substitute program. */ /* Fork the substitute program. */
pid = startProcess([&]() { pid = startProcess([&]() {
@ -2847,7 +2855,7 @@ void SubstitutionGoal::tryToRun()
if (dup2(outPipe.writeSide, STDOUT_FILENO) == -1) if (dup2(outPipe.writeSide, STDOUT_FILENO) == -1)
throw SysError("cannot dup output pipe into stdout"); throw SysError("cannot dup output pipe into stdout");
execv(sub.c_str(), (char * *) &argArr[0]); execv(sub.c_str(), stringsToCharPtrs(args).data());
throw SysError(format("executing `%1%'") % sub); throw SysError(format("executing `%1%'") % sub);
}); });

View File

@ -285,4 +285,13 @@ bool wantOutput(const string & output, const std::set<string> & wanted)
} }
PathSet outputPaths(const Derivation & drv)
{
PathSet paths;
for (auto & i : drv.outputs)
paths.insert(i.second.path);
return paths;
}
} }

View File

@ -89,5 +89,6 @@ Path makeDrvPathWithOutputs(const Path & drvPath, const std::set<string> & outpu
bool wantOutput(const string & output, const std::set<string> & wanted); bool wantOutput(const string & output, const std::set<string> & wanted);
PathSet outputPaths(const Derivation & drv);
} }

View File

@ -376,6 +376,7 @@ struct LocalStore::GCState
bool gcKeepOutputs; bool gcKeepOutputs;
bool gcKeepDerivations; bool gcKeepDerivations;
unsigned long long bytesInvalidated; unsigned long long bytesInvalidated;
bool moveToTrash = true;
Path trashDir; Path trashDir;
bool shouldDelete; bool shouldDelete;
GCState(GCResults & results_) : results(results_), bytesInvalidated(0) { } GCState(GCResults & results_) : results(results_), bytesInvalidated(0) { }
@ -428,16 +429,23 @@ void LocalStore::deletePathRecursive(GCState & state, const Path & path)
not holding the global GC lock) we can delete the path without not holding the global GC lock) we can delete the path without
being afraid that the path has become alive again. Otherwise being afraid that the path has become alive again. Otherwise
delete it right away. */ delete it right away. */
if (S_ISDIR(st.st_mode)) { if (state.moveToTrash && S_ISDIR(st.st_mode)) {
// Estimate the amount freed using the narSize field. FIXME: // Estimate the amount freed using the narSize field. FIXME:
// if the path was not valid, need to determine the actual // if the path was not valid, need to determine the actual
// size. // size.
state.bytesInvalidated += size; try {
if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1) if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1)
throw SysError(format("making `%1%' writable") % path); throw SysError(format("making `%1%' writable") % path);
Path tmp = state.trashDir + "/" + baseNameOf(path); Path tmp = state.trashDir + "/" + baseNameOf(path);
if (rename(path.c_str(), tmp.c_str())) if (rename(path.c_str(), tmp.c_str()))
throw SysError(format("unable to rename `%1%' to `%2%'") % path % tmp); throw SysError(format("unable to rename `%1%' to `%2%'") % path % tmp);
state.bytesInvalidated += size;
} catch (SysError & e) {
if (e.errNo == ENOSPC) {
printMsg(lvlInfo, format("note: can't create move `%1%': %2%") % path % e.msg());
deleteGarbage(state, path);
}
}
} else } else
deleteGarbage(state, path); deleteGarbage(state, path);
@ -636,7 +644,14 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
if (state.shouldDelete) { if (state.shouldDelete) {
if (pathExists(state.trashDir)) deleteGarbage(state, state.trashDir); if (pathExists(state.trashDir)) deleteGarbage(state, state.trashDir);
createDirs(state.trashDir); try {
createDirs(state.trashDir);
} catch (SysError & e) {
if (e.errNo == ENOSPC) {
printMsg(lvlInfo, format("note: can't create trash directory: %1%") % e.msg());
state.moveToTrash = false;
}
}
} }
/* Now either delete all garbage paths, or just the specified /* Now either delete all garbage paths, or just the specified

View File

@ -36,7 +36,7 @@ Settings::Settings()
buildTimeout = 0; buildTimeout = 0;
useBuildHook = true; useBuildHook = true;
printBuildTrace = false; printBuildTrace = false;
reservedSize = 1024 * 1024; reservedSize = 8 * 1024 * 1024;
fsyncMetadata = true; fsyncMetadata = true;
useSQLiteWAL = true; useSQLiteWAL = true;
syncBeforeRegistering = false; syncBeforeRegistering = false;

View File

@ -288,7 +288,17 @@ LocalStore::LocalStore(bool reserveSpace)
struct stat st; struct stat st;
if (stat(reservedPath.c_str(), &st) == -1 || if (stat(reservedPath.c_str(), &st) == -1 ||
st.st_size != settings.reservedSize) st.st_size != settings.reservedSize)
writeFile(reservedPath, string(settings.reservedSize, 'X')); {
AutoCloseFD fd = open(reservedPath.c_str(), O_WRONLY | O_CREAT, 0600);
int res = -1;
#if HAVE_POSIX_FALLOCATE
res = posix_fallocate(fd, 0, settings.reservedSize);
#endif
if (res == -1) {
writeFull(fd, string(settings.reservedSize, 'X'));
ftruncate(fd, settings.reservedSize);
}
}
} }
else else
deletePath(reservedPath); deletePath(reservedPath);
@ -1166,10 +1176,8 @@ string LocalStore::getLineFromSubstituter(RunningSubstituter & run)
if (n == 0) throw EndOfFile(format("substituter `%1%' died unexpectedly") % run.program); if (n == 0) throw EndOfFile(format("substituter `%1%' died unexpectedly") % run.program);
err.append(buf, n); err.append(buf, n);
string::size_type p; string::size_type p;
while (((p = err.find('\n')) != string::npos) while ((p = err.find('\n')) != string::npos) {
|| ((p = err.find('\r')) != string::npos)) { printMsg(lvlError, run.program + ": " + string(err, 0, p));
string thing(err, 0, p + 1);
writeToStderr(run.program + ": " + thing);
err = string(err, p + 1); err = string(err, p + 1);
} }
} }

View File

@ -120,7 +120,7 @@ void queryMissing(StoreAPI & store, const PathSet & targets,
if (invalid.empty()) continue; if (invalid.empty()) continue;
todoDrv.insert(*i); todoDrv.insert(*i);
if (settings.useSubstitutes && !willBuildLocally(drv)) if (settings.useSubstitutes && substitutesAllowed(drv))
query.insert(invalid.begin(), invalid.end()); query.insert(invalid.begin(), invalid.end());
} }
@ -144,7 +144,7 @@ void queryMissing(StoreAPI & store, const PathSet & targets,
PathSet outputs; PathSet outputs;
bool mustBuild = false; bool mustBuild = false;
if (settings.useSubstitutes && !willBuildLocally(drv)) { if (settings.useSubstitutes && substitutesAllowed(drv)) {
foreach (DerivationOutputs::iterator, j, drv.outputs) { foreach (DerivationOutputs::iterator, j, drv.outputs) {
if (!wantOutput(j->first, i2.second)) continue; if (!wantOutput(j->first, i2.second)) continue;
if (!store.isValidPath(j->second.path)) { if (!store.isValidPath(j->second.path)) {

View File

@ -34,5 +34,7 @@ void queryMissing(StoreAPI & store, const PathSet & targets,
bool willBuildLocally(const Derivation & drv); bool willBuildLocally(const Derivation & drv);
bool substitutesAllowed(const Derivation & drv);
} }

View File

@ -897,10 +897,10 @@ pid_t startProcess(std::function<void()> fun,
} }
std::vector<const char *> stringsToCharPtrs(const Strings & ss) std::vector<char *> stringsToCharPtrs(const Strings & ss)
{ {
std::vector<const char *> res; std::vector<char *> res;
for (auto & s : ss) res.push_back(s.c_str()); for (auto & s : ss) res.push_back((char *) s.c_str());
res.push_back(0); res.push_back(0);
return res; return res;
} }
@ -921,12 +921,11 @@ string runProgram(Path program, bool searchPath, const Strings & args)
Strings args_(args); Strings args_(args);
args_.push_front(program); args_.push_front(program);
auto cargs = stringsToCharPtrs(args_);
if (searchPath) if (searchPath)
execvp(program.c_str(), (char * *) &cargs[0]); execvp(program.c_str(), stringsToCharPtrs(args_).data());
else else
execv(program.c_str(), (char * *) &cargs[0]); execv(program.c_str(), stringsToCharPtrs(args_).data());
throw SysError(format("executing `%1%'") % program); throw SysError(format("executing `%1%'") % program);
}); });

View File

@ -284,7 +284,7 @@ MakeError(ExecError, Error)
/* Convert a list of strings to a null-terminated vector of char /* Convert a list of strings to a null-terminated vector of char
*'s. The result must not be accessed beyond the lifetime of the *'s. The result must not be accessed beyond the lifetime of the
list of strings. */ list of strings. */
std::vector<const char *> stringsToCharPtrs(const Strings & ss); std::vector<char *> stringsToCharPtrs(const Strings & ss);
/* Close all file descriptors except stdin, stdout, stderr, and those /* Close all file descriptors except stdin, stdout, stderr, and those
listed in the given set. Good practice in child processes. */ listed in the given set. Good practice in child processes. */

View File

@ -440,10 +440,7 @@ static void performOp(bool trusted, unsigned int clientVersion,
case wopImportPaths: { case wopImportPaths: {
startWork(); startWork();
TunnelSource source(from); TunnelSource source(from);
Paths paths = store->importPaths(!trusted, source);
/* Unlike Nix, always require a signature, even for "trusted"
users. */
Paths paths = store->importPaths(true, source);
stopWork(); stopWork();
writeStrings(paths, to); writeStrings(paths, to);
break; break;