Add a --repair flag to ‘nix-store -r’ to repair derivation outputs

With this flag, if any valid derivation output is missing or corrupt,
it will be recreated by using a substitute if available, or by
rebuilding the derivation.  The latter may use hash rewriting if
chroots are not available.
This commit is contained in:
Eelco Dolstra 2012-10-02 17:13:46 -04:00
parent cf46f19444
commit 2001895f3d
7 changed files with 116 additions and 64 deletions

View file

@ -241,7 +241,7 @@ public:
~Worker();
/* Make a goal (with caching). */
GoalPtr makeDerivationGoal(const Path & drvPath);
GoalPtr makeDerivationGoal(const Path & drvPath, bool repair = false);
GoalPtr makeSubstitutionGoal(const Path & storePath, bool repair = false);
/* Remove a dead goal. */
@ -825,13 +825,18 @@ private:
HashRewrites rewritesToTmp, rewritesFromTmp;
PathSet redirectedOutputs;
/* Whether we're repairing. If set, a derivation will be rebuilt
if its outputs are valid but corrupt or missing. */
bool repair;
map<Path, Path> redirectedBadOutputs;
/* Magic exit code denoting that setting up the child environment
failed. (It's possible that the child actually returns the
exit code, but ah well.) */
const static int childSetupFailed = 189;
public:
DerivationGoal(const Path & drvPath, Worker & worker);
DerivationGoal(const Path & drvPath, Worker & worker, bool repair = false);
~DerivationGoal();
void cancel();
@ -882,21 +887,24 @@ private:
void handleEOF(int fd);
/* Return the set of (in)valid paths. */
PathSet checkPathValidity(bool returnValid);
PathSet checkPathValidity(bool returnValid, bool checkHash);
/* Abort the goal if `path' failed to build. */
bool pathFailed(const Path & path);
/* Forcibly kill the child process, if any. */
void killChild();
Path addHashRewrite(const Path & path);
};
DerivationGoal::DerivationGoal(const Path & drvPath, Worker & worker)
DerivationGoal::DerivationGoal(const Path & drvPath, Worker & worker, bool repair)
: Goal(worker)
, fLogFile(0)
, bzLogFile(0)
, useChroot(false)
, repair(repair)
{
this->drvPath = drvPath;
state = &DerivationGoal::init;
@ -1001,10 +1009,10 @@ void DerivationGoal::haveDerivation()
worker.store.addTempRoot(i->second.path);
/* Check what outputs paths are not already valid. */
PathSet invalidOutputs = checkPathValidity(false);
PathSet invalidOutputs = checkPathValidity(false, repair);
/* If they are all valid, then we're done. */
if (invalidOutputs.size() == 0) {
if (invalidOutputs.size() == 0 && !repair) {
amDone(ecSuccess);
return;
}
@ -1019,7 +1027,7 @@ void DerivationGoal::haveDerivation()
them. */
if (settings.useSubstitutes)
foreach (PathSet::iterator, i, invalidOutputs)
addWaitee(worker.makeSubstitutionGoal(*i));
addWaitee(worker.makeSubstitutionGoal(*i, repair));
if (waitees.empty()) /* to prevent hang (no wake-up event) */
outputsSubstituted();
@ -1037,7 +1045,7 @@ void DerivationGoal::outputsSubstituted()
nrFailed = nrNoSubstituters = 0;
if (checkPathValidity(false).size() == 0) {
if (checkPathValidity(false, repair).size() == 0) {
amDone(ecSuccess);
return;
}
@ -1173,7 +1181,7 @@ void DerivationGoal::tryToBuild()
omitted, but that would be less efficient.) Note that since we
now hold the locks on the output paths, no other process can
build this derivation, so no further checks are necessary. */
validPaths = checkPathValidity(true);
validPaths = checkPathValidity(true, repair);
if (validPaths.size() == drv.outputs.size()) {
debug(format("skipping build of derivation `%1%', someone beat us to it")
% drvPath);
@ -1256,6 +1264,24 @@ void DerivationGoal::tryToBuild()
}
void replaceValidPath(const Path & storePath, const Path tmpPath)
{
/* We can't atomically replace storePath (the original) with
tmpPath (the replacement), so we have to move it out of the
way first. We'd better not be interrupted here, because if
we're repairing (say) Glibc, we end up with a broken system. */
Path oldPath = (format("%1%.old-%2%-%3%") % storePath % getpid() % rand()).str();
if (pathExists(storePath)) {
makeMutable(storePath);
rename(storePath.c_str(), oldPath.c_str());
}
if (rename(tmpPath.c_str(), storePath.c_str()) == -1)
throw SysError(format("moving `%1%' to `%2%'") % tmpPath % storePath);
if (pathExists(oldPath))
deletePathWrapped(oldPath);
}
void DerivationGoal::buildDone()
{
trace("build done");
@ -1309,10 +1335,18 @@ void DerivationGoal::buildDone()
/* If the output was already valid, just skip (discard) it. */
if (validPaths.find(path) != validPaths.end()) continue;
if (useChroot && pathExists(chrootRootDir + path))
if (useChroot && pathExists(chrootRootDir + path)) {
/* Move output paths from the chroot to the Nix store. */
if (rename((chrootRootDir + path).c_str(), path.c_str()) == -1)
throw SysError(format("moving build output `%1%' from the chroot to the Nix store") % path);
if (repair)
replaceValidPath(path, chrootRootDir + path);
else
if (rename((chrootRootDir + path).c_str(), path.c_str()) == -1)
throw SysError(format("moving build output `%1%' from the chroot to the Nix store") % path);
}
Path redirected;
if (repair && (redirected = redirectedBadOutputs[path]) != "" && pathExists(redirected))
replaceValidPath(path, redirected);
if (!pathExists(path)) continue;
@ -1786,25 +1820,28 @@ void DerivationGoal::startBuilder()
#endif
}
/* We're not doing a chroot build, but we have some valid output
paths. Since we can't just overwrite or delete them, we have
to do hash rewriting: i.e. in the environment/arguments passed
to the build, we replace the hashes of the valid outputs with
unique dummy strings; after the build, we discard the
redirected outputs corresponding to the valid outputs, and
rewrite the contents of the new outputs to replace the dummy
strings with the actual hashes. */
else if (validPaths.size() > 0) {
//throw Error(format("derivation `%1%' is blocked by its output path(s) %2%") % drvPath % showPaths(validPaths));
foreach (PathSet::iterator, i, validPaths) {
string h1 = string(*i, settings.nixStore.size() + 1, 32);
string h2 = string(printHash32(hashString(htSHA256, "rewrite:" + drvPath + ":" + *i)), 0, 32);
Path p = settings.nixStore + "/" + h2 + string(*i, settings.nixStore.size() + 33);
assert(i->size() == p.size());
rewritesToTmp[h1] = h2;
rewritesFromTmp[h2] = h1;
redirectedOutputs.insert(p);
}
else {
/* We're not doing a chroot build, but we have some valid
output paths. Since we can't just overwrite or delete
them, we have to do hash rewriting: i.e. in the
environment/arguments passed to the build, we replace the
hashes of the valid outputs with unique dummy strings;
after the build, we discard the redirected outputs
corresponding to the valid outputs, and rewrite the
contents of the new outputs to replace the dummy strings
with the actual hashes. */
if (validPaths.size() > 0)
//throw Error(format("derivation `%1%' is blocked by its output path(s) %2%") % drvPath % showPaths(validPaths));
foreach (PathSet::iterator, i, validPaths)
redirectedOutputs.insert(addHashRewrite(*i));
/* If we're repairing, then we don't want to delete the
corrupt outputs in advance. So rewrite them as well. */
if (repair)
foreach (PathSet::iterator, i, missing)
if (worker.store.isValidPath(*i) && pathExists(*i))
redirectedBadOutputs[*i] = addHashRewrite(*i);
}
@ -2278,15 +2315,15 @@ void DerivationGoal::handleEOF(int fd)
}
PathSet DerivationGoal::checkPathValidity(bool returnValid)
PathSet DerivationGoal::checkPathValidity(bool returnValid, bool checkHash)
{
PathSet result;
foreach (DerivationOutputs::iterator, i, drv.outputs)
if (worker.store.isValidPath(i->second.path)) {
if (returnValid) result.insert(i->second.path);
} else {
if (!returnValid) result.insert(i->second.path);
}
foreach (DerivationOutputs::iterator, i, drv.outputs) {
bool good =
worker.store.isValidPath(i->second.path) &&
(!checkHash || worker.store.pathContentsGood(i->second.path));
if (good == returnValid) result.insert(i->second.path);
}
return result;
}
@ -2309,6 +2346,19 @@ bool DerivationGoal::pathFailed(const Path & path)
}
Path DerivationGoal::addHashRewrite(const Path & path)
{
string h1 = string(path, settings.nixStore.size() + 1, 32);
string h2 = string(printHash32(hashString(htSHA256, "rewrite:" + drvPath + ":" + path)), 0, 32);
Path p = settings.nixStore + "/" + h2 + string(path, settings.nixStore.size() + 33);
if (pathExists(p)) deletePathWrapped(p);
assert(path.size() == p.size());
rewritesToTmp[h1] = h2;
rewritesFromTmp[h2] = h1;
return p;
}
//////////////////////////////////////////////////////////////////////
@ -2667,22 +2717,7 @@ void SubstitutionGoal::finished()
worker.store.optimisePath(destPath); // FIXME: combine with hashPath()
if (repair) {
/* We can't atomically replace storePath (the original) with
destPath (the replacement), so we have to move it out of
the way first. We'd better not be interrupted here,
because if we're repairing (say) Glibc, we end up with a
broken system. */
Path oldPath = (format("%1%.old-%2%-%3%") % storePath % getpid() % rand()).str();
if (pathExists(storePath)) {
makeMutable(storePath);
rename(storePath.c_str(), oldPath.c_str());
}
if (rename(destPath.c_str(), storePath.c_str()) == -1)
throw SysError(format("moving `%1%' to `%2%'") % destPath % storePath);
if (pathExists(oldPath))
deletePathWrapped(oldPath);
}
if (repair) replaceValidPath(storePath, destPath);
ValidPathInfo info2;
info2.path = storePath;
@ -2752,11 +2787,11 @@ Worker::~Worker()
}
GoalPtr Worker::makeDerivationGoal(const Path & path)
GoalPtr Worker::makeDerivationGoal(const Path & path, bool repair)
{
GoalPtr goal = derivationGoals[path].lock();
if (!goal) {
goal = GoalPtr(new DerivationGoal(path, *this));
goal = GoalPtr(new DerivationGoal(path, *this, repair));
derivationGoals[path] = goal;
wakeUp(goal);
}
@ -3090,7 +3125,7 @@ unsigned int Worker::exitStatus()
//////////////////////////////////////////////////////////////////////
void LocalStore::buildPaths(const PathSet & drvPaths)
void LocalStore::buildPaths(const PathSet & drvPaths, bool repair)
{
startNest(nest, lvlDebug,
format("building %1%") % showPaths(drvPaths));
@ -3100,9 +3135,9 @@ void LocalStore::buildPaths(const PathSet & drvPaths)
Goals goals;
foreach (PathSet::const_iterator, i, drvPaths)
if (isDerivation(*i))
goals.insert(worker.makeDerivationGoal(*i));
goals.insert(worker.makeDerivationGoal(*i, repair));
else
goals.insert(worker.makeSubstitutionGoal(*i));
goals.insert(worker.makeSubstitutionGoal(*i, repair));
worker.run(goals);

View file

@ -1671,6 +1671,16 @@ void LocalStore::verifyPath(const Path & path, const PathSet & store,
}
bool LocalStore::pathContentsGood(const Path & path)
{
ValidPathInfo info = queryPathInfo(path);
if (!pathExists(path)) return false;
HashResult current = hashPath(info.hash.type, path);
Hash nullHash(htSHA256);
return info.hash == nullHash || info.hash == current.first;
}
/* Functions for upgrading from the pre-SQLite database. */
PathSet LocalStore::queryValidPathsOld()

View file

@ -150,7 +150,7 @@ public:
Paths importPaths(bool requireSignature, Source & source);
void buildPaths(const PathSet & paths);
void buildPaths(const PathSet & paths, bool repair = false);
void ensurePath(const Path & path);
@ -202,6 +202,10 @@ public:
a substituter (if available). */
void repairPath(const Path & path);
/* Check whether the given valid path exists and has the right
contents. */
bool pathContentsGood(const Path & path);
private:
Path schemaPath;

View file

@ -474,8 +474,9 @@ Paths RemoteStore::importPaths(bool requireSignature, Source & source)
}
void RemoteStore::buildPaths(const PathSet & drvPaths)
void RemoteStore::buildPaths(const PathSet & drvPaths, bool repair)
{
if (repair) throw Error("`--repair' is not supported when building through the Nix daemon");
openConnection();
writeInt(wopBuildPaths, to);
writeStrings(drvPaths, to);

View file

@ -63,7 +63,7 @@ public:
Paths importPaths(bool requireSignature, Source & source);
void buildPaths(const PathSet & paths);
void buildPaths(const PathSet & paths, bool repair = false);
void ensurePath(const Path & path);

View file

@ -184,7 +184,7 @@ public:
output paths can be created by running the builder, after
recursively building any sub-derivations. For inputs that are
not derivations, substitute them. */
virtual void buildPaths(const PathSet & paths) = 0;
virtual void buildPaths(const PathSet & paths, bool repair = false) = 0;
/* Ensure that a path is valid. If it is not currently valid, it
may be made valid by running a substitute (if defined for the

View file

@ -93,9 +93,11 @@ static PathSet realisePath(const Path & path, bool build = true)
static void opRealise(Strings opFlags, Strings opArgs)
{
bool dryRun = false;
bool repair = false;
foreach (Strings::iterator, i, opFlags)
if (*i == "--dry-run") dryRun = true;
else if (*i == "--repair") repair = true;
else throw UsageError(format("unknown flag `%1%'") % *i);
foreach (Strings::iterator, i, opArgs)
@ -107,7 +109,7 @@ static void opRealise(Strings opFlags, Strings opArgs)
/* Build all paths at the same time to exploit parallelism. */
PathSet paths(opArgs.begin(), opArgs.end());
store->buildPaths(paths);
store->buildPaths(paths, repair);
foreach (Paths::iterator, i, opArgs) {
PathSet paths = realisePath(*i, false);