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:
parent
cf46f19444
commit
2001895f3d
7 changed files with 116 additions and 64 deletions
|
@ -241,7 +241,7 @@ public:
|
||||||
~Worker();
|
~Worker();
|
||||||
|
|
||||||
/* Make a goal (with caching). */
|
/* 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);
|
GoalPtr makeSubstitutionGoal(const Path & storePath, bool repair = false);
|
||||||
|
|
||||||
/* Remove a dead goal. */
|
/* Remove a dead goal. */
|
||||||
|
@ -825,13 +825,18 @@ private:
|
||||||
HashRewrites rewritesToTmp, rewritesFromTmp;
|
HashRewrites rewritesToTmp, rewritesFromTmp;
|
||||||
PathSet redirectedOutputs;
|
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
|
/* Magic exit code denoting that setting up the child environment
|
||||||
failed. (It's possible that the child actually returns the
|
failed. (It's possible that the child actually returns the
|
||||||
exit code, but ah well.) */
|
exit code, but ah well.) */
|
||||||
const static int childSetupFailed = 189;
|
const static int childSetupFailed = 189;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DerivationGoal(const Path & drvPath, Worker & worker);
|
DerivationGoal(const Path & drvPath, Worker & worker, bool repair = false);
|
||||||
~DerivationGoal();
|
~DerivationGoal();
|
||||||
|
|
||||||
void cancel();
|
void cancel();
|
||||||
|
@ -882,21 +887,24 @@ private:
|
||||||
void handleEOF(int fd);
|
void handleEOF(int fd);
|
||||||
|
|
||||||
/* Return the set of (in)valid paths. */
|
/* 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. */
|
/* Abort the goal if `path' failed to build. */
|
||||||
bool pathFailed(const Path & path);
|
bool pathFailed(const Path & path);
|
||||||
|
|
||||||
/* Forcibly kill the child process, if any. */
|
/* Forcibly kill the child process, if any. */
|
||||||
void killChild();
|
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)
|
: Goal(worker)
|
||||||
, fLogFile(0)
|
, fLogFile(0)
|
||||||
, bzLogFile(0)
|
, bzLogFile(0)
|
||||||
, useChroot(false)
|
, useChroot(false)
|
||||||
|
, repair(repair)
|
||||||
{
|
{
|
||||||
this->drvPath = drvPath;
|
this->drvPath = drvPath;
|
||||||
state = &DerivationGoal::init;
|
state = &DerivationGoal::init;
|
||||||
|
@ -1001,10 +1009,10 @@ void DerivationGoal::haveDerivation()
|
||||||
worker.store.addTempRoot(i->second.path);
|
worker.store.addTempRoot(i->second.path);
|
||||||
|
|
||||||
/* Check what outputs paths are not already valid. */
|
/* 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 they are all valid, then we're done. */
|
||||||
if (invalidOutputs.size() == 0) {
|
if (invalidOutputs.size() == 0 && !repair) {
|
||||||
amDone(ecSuccess);
|
amDone(ecSuccess);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1019,7 +1027,7 @@ void DerivationGoal::haveDerivation()
|
||||||
them. */
|
them. */
|
||||||
if (settings.useSubstitutes)
|
if (settings.useSubstitutes)
|
||||||
foreach (PathSet::iterator, i, invalidOutputs)
|
foreach (PathSet::iterator, i, invalidOutputs)
|
||||||
addWaitee(worker.makeSubstitutionGoal(*i));
|
addWaitee(worker.makeSubstitutionGoal(*i, repair));
|
||||||
|
|
||||||
if (waitees.empty()) /* to prevent hang (no wake-up event) */
|
if (waitees.empty()) /* to prevent hang (no wake-up event) */
|
||||||
outputsSubstituted();
|
outputsSubstituted();
|
||||||
|
@ -1037,7 +1045,7 @@ void DerivationGoal::outputsSubstituted()
|
||||||
|
|
||||||
nrFailed = nrNoSubstituters = 0;
|
nrFailed = nrNoSubstituters = 0;
|
||||||
|
|
||||||
if (checkPathValidity(false).size() == 0) {
|
if (checkPathValidity(false, repair).size() == 0) {
|
||||||
amDone(ecSuccess);
|
amDone(ecSuccess);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1173,7 +1181,7 @@ void DerivationGoal::tryToBuild()
|
||||||
omitted, but that would be less efficient.) Note that since we
|
omitted, but that would be less efficient.) Note that since we
|
||||||
now hold the locks on the output paths, no other process can
|
now hold the locks on the output paths, no other process can
|
||||||
build this derivation, so no further checks are necessary. */
|
build this derivation, so no further checks are necessary. */
|
||||||
validPaths = checkPathValidity(true);
|
validPaths = checkPathValidity(true, repair);
|
||||||
if (validPaths.size() == drv.outputs.size()) {
|
if (validPaths.size() == drv.outputs.size()) {
|
||||||
debug(format("skipping build of derivation `%1%', someone beat us to it")
|
debug(format("skipping build of derivation `%1%', someone beat us to it")
|
||||||
% drvPath);
|
% 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()
|
void DerivationGoal::buildDone()
|
||||||
{
|
{
|
||||||
trace("build done");
|
trace("build done");
|
||||||
|
@ -1309,10 +1335,18 @@ void DerivationGoal::buildDone()
|
||||||
/* If the output was already valid, just skip (discard) it. */
|
/* If the output was already valid, just skip (discard) it. */
|
||||||
if (validPaths.find(path) != validPaths.end()) continue;
|
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. */
|
/* Move output paths from the chroot to the Nix store. */
|
||||||
|
if (repair)
|
||||||
|
replaceValidPath(path, chrootRootDir + path);
|
||||||
|
else
|
||||||
if (rename((chrootRootDir + path).c_str(), path.c_str()) == -1)
|
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);
|
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;
|
if (!pathExists(path)) continue;
|
||||||
|
|
||||||
|
@ -1786,25 +1820,28 @@ void DerivationGoal::startBuilder()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We're not doing a chroot build, but we have some valid output
|
else {
|
||||||
paths. Since we can't just overwrite or delete them, we have
|
|
||||||
to do hash rewriting: i.e. in the environment/arguments passed
|
/* We're not doing a chroot build, but we have some valid
|
||||||
to the build, we replace the hashes of the valid outputs with
|
output paths. Since we can't just overwrite or delete
|
||||||
unique dummy strings; after the build, we discard the
|
them, we have to do hash rewriting: i.e. in the
|
||||||
redirected outputs corresponding to the valid outputs, and
|
environment/arguments passed to the build, we replace the
|
||||||
rewrite the contents of the new outputs to replace the dummy
|
hashes of the valid outputs with unique dummy strings;
|
||||||
strings with the actual hashes. */
|
after the build, we discard the redirected outputs
|
||||||
else if (validPaths.size() > 0) {
|
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));
|
//throw Error(format("derivation `%1%' is blocked by its output path(s) %2%") % drvPath % showPaths(validPaths));
|
||||||
foreach (PathSet::iterator, i, validPaths) {
|
foreach (PathSet::iterator, i, validPaths)
|
||||||
string h1 = string(*i, settings.nixStore.size() + 1, 32);
|
redirectedOutputs.insert(addHashRewrite(*i));
|
||||||
string h2 = string(printHash32(hashString(htSHA256, "rewrite:" + drvPath + ":" + *i)), 0, 32);
|
|
||||||
Path p = settings.nixStore + "/" + h2 + string(*i, settings.nixStore.size() + 33);
|
/* If we're repairing, then we don't want to delete the
|
||||||
assert(i->size() == p.size());
|
corrupt outputs in advance. So rewrite them as well. */
|
||||||
rewritesToTmp[h1] = h2;
|
if (repair)
|
||||||
rewritesFromTmp[h2] = h1;
|
foreach (PathSet::iterator, i, missing)
|
||||||
redirectedOutputs.insert(p);
|
if (worker.store.isValidPath(*i) && pathExists(*i))
|
||||||
}
|
redirectedBadOutputs[*i] = addHashRewrite(*i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2278,14 +2315,14 @@ void DerivationGoal::handleEOF(int fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PathSet DerivationGoal::checkPathValidity(bool returnValid)
|
PathSet DerivationGoal::checkPathValidity(bool returnValid, bool checkHash)
|
||||||
{
|
{
|
||||||
PathSet result;
|
PathSet result;
|
||||||
foreach (DerivationOutputs::iterator, i, drv.outputs)
|
foreach (DerivationOutputs::iterator, i, drv.outputs) {
|
||||||
if (worker.store.isValidPath(i->second.path)) {
|
bool good =
|
||||||
if (returnValid) result.insert(i->second.path);
|
worker.store.isValidPath(i->second.path) &&
|
||||||
} else {
|
(!checkHash || worker.store.pathContentsGood(i->second.path));
|
||||||
if (!returnValid) result.insert(i->second.path);
|
if (good == returnValid) result.insert(i->second.path);
|
||||||
}
|
}
|
||||||
return result;
|
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()
|
worker.store.optimisePath(destPath); // FIXME: combine with hashPath()
|
||||||
|
|
||||||
if (repair) {
|
if (repair) replaceValidPath(storePath, destPath);
|
||||||
/* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
ValidPathInfo info2;
|
ValidPathInfo info2;
|
||||||
info2.path = storePath;
|
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();
|
GoalPtr goal = derivationGoals[path].lock();
|
||||||
if (!goal) {
|
if (!goal) {
|
||||||
goal = GoalPtr(new DerivationGoal(path, *this));
|
goal = GoalPtr(new DerivationGoal(path, *this, repair));
|
||||||
derivationGoals[path] = goal;
|
derivationGoals[path] = goal;
|
||||||
wakeUp(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,
|
startNest(nest, lvlDebug,
|
||||||
format("building %1%") % showPaths(drvPaths));
|
format("building %1%") % showPaths(drvPaths));
|
||||||
|
@ -3100,9 +3135,9 @@ void LocalStore::buildPaths(const PathSet & drvPaths)
|
||||||
Goals goals;
|
Goals goals;
|
||||||
foreach (PathSet::const_iterator, i, drvPaths)
|
foreach (PathSet::const_iterator, i, drvPaths)
|
||||||
if (isDerivation(*i))
|
if (isDerivation(*i))
|
||||||
goals.insert(worker.makeDerivationGoal(*i));
|
goals.insert(worker.makeDerivationGoal(*i, repair));
|
||||||
else
|
else
|
||||||
goals.insert(worker.makeSubstitutionGoal(*i));
|
goals.insert(worker.makeSubstitutionGoal(*i, repair));
|
||||||
|
|
||||||
worker.run(goals);
|
worker.run(goals);
|
||||||
|
|
||||||
|
|
|
@ -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. */
|
/* Functions for upgrading from the pre-SQLite database. */
|
||||||
|
|
||||||
PathSet LocalStore::queryValidPathsOld()
|
PathSet LocalStore::queryValidPathsOld()
|
||||||
|
|
|
@ -150,7 +150,7 @@ public:
|
||||||
|
|
||||||
Paths importPaths(bool requireSignature, Source & source);
|
Paths importPaths(bool requireSignature, Source & source);
|
||||||
|
|
||||||
void buildPaths(const PathSet & paths);
|
void buildPaths(const PathSet & paths, bool repair = false);
|
||||||
|
|
||||||
void ensurePath(const Path & path);
|
void ensurePath(const Path & path);
|
||||||
|
|
||||||
|
@ -202,6 +202,10 @@ public:
|
||||||
a substituter (if available). */
|
a substituter (if available). */
|
||||||
void repairPath(const Path & path);
|
void repairPath(const Path & path);
|
||||||
|
|
||||||
|
/* Check whether the given valid path exists and has the right
|
||||||
|
contents. */
|
||||||
|
bool pathContentsGood(const Path & path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
Path schemaPath;
|
Path schemaPath;
|
||||||
|
|
|
@ -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();
|
openConnection();
|
||||||
writeInt(wopBuildPaths, to);
|
writeInt(wopBuildPaths, to);
|
||||||
writeStrings(drvPaths, to);
|
writeStrings(drvPaths, to);
|
||||||
|
|
|
@ -63,7 +63,7 @@ public:
|
||||||
|
|
||||||
Paths importPaths(bool requireSignature, Source & source);
|
Paths importPaths(bool requireSignature, Source & source);
|
||||||
|
|
||||||
void buildPaths(const PathSet & paths);
|
void buildPaths(const PathSet & paths, bool repair = false);
|
||||||
|
|
||||||
void ensurePath(const Path & path);
|
void ensurePath(const Path & path);
|
||||||
|
|
||||||
|
|
|
@ -184,7 +184,7 @@ public:
|
||||||
output paths can be created by running the builder, after
|
output paths can be created by running the builder, after
|
||||||
recursively building any sub-derivations. For inputs that are
|
recursively building any sub-derivations. For inputs that are
|
||||||
not derivations, substitute them. */
|
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
|
/* 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
|
may be made valid by running a substitute (if defined for the
|
||||||
|
|
|
@ -93,9 +93,11 @@ static PathSet realisePath(const Path & path, bool build = true)
|
||||||
static void opRealise(Strings opFlags, Strings opArgs)
|
static void opRealise(Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
bool dryRun = false;
|
bool dryRun = false;
|
||||||
|
bool repair = false;
|
||||||
|
|
||||||
foreach (Strings::iterator, i, opFlags)
|
foreach (Strings::iterator, i, opFlags)
|
||||||
if (*i == "--dry-run") dryRun = true;
|
if (*i == "--dry-run") dryRun = true;
|
||||||
|
else if (*i == "--repair") repair = true;
|
||||||
else throw UsageError(format("unknown flag `%1%'") % *i);
|
else throw UsageError(format("unknown flag `%1%'") % *i);
|
||||||
|
|
||||||
foreach (Strings::iterator, i, opArgs)
|
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. */
|
/* Build all paths at the same time to exploit parallelism. */
|
||||||
PathSet paths(opArgs.begin(), opArgs.end());
|
PathSet paths(opArgs.begin(), opArgs.end());
|
||||||
store->buildPaths(paths);
|
store->buildPaths(paths, repair);
|
||||||
|
|
||||||
foreach (Paths::iterator, i, opArgs) {
|
foreach (Paths::iterator, i, opArgs) {
|
||||||
PathSet paths = realisePath(*i, false);
|
PathSet paths = realisePath(*i, false);
|
||||||
|
|
Loading…
Reference in a new issue