about summary refs log tree commit diff
path: root/src/libstore/build.cc
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2012-10-02T18·08-0400
committerEelco Dolstra <eelco.dolstra@logicblox.com>2012-10-02T18·08-0400
commit9958bd6992e2b3e7bacb493a372d17d5a5b95d90 (patch)
tree48371e99e9ca79b8caa6152c1efed6174dd4e0c5 /src/libstore/build.cc
parente666e1156fba936dce93ccfa2486f67369a97129 (diff)
Add operation ‘nix-store --repair-path’
This operation allows fixing corrupted or accidentally deleted store
paths by redownloading them using substituters, if available.

Since the corrupted path cannot be replaced atomically, there is a
very small time window (one system call) during which neither the old
(corrupted) nor the new (repaired) contents are available.  So
repairing should be used with some care on critical packages like
Glibc.
Diffstat (limited to 'src/libstore/build.cc')
-rw-r--r--src/libstore/build.cc95
1 files changed, 67 insertions, 28 deletions
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index fecee04d54b1..3097b55ef544 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -242,7 +242,7 @@ public:
 
     /* Make a goal (with caching). */
     GoalPtr makeDerivationGoal(const Path & drvPath);
-    GoalPtr makeSubstitutionGoal(const Path & storePath);
+    GoalPtr makeSubstitutionGoal(const Path & storePath, bool repair = false);
 
     /* Remove a dead goal. */
     void removeGoal(GoalPtr goal);
@@ -2344,11 +2344,18 @@ private:
     /* Lock on the store path. */
     boost::shared_ptr<PathLocks> outputLock;
 
+    /* Whether to try to repair a valid path. */
+    bool repair;
+
+    /* Location where we're downloading the substitute.  Differs from
+       storePath when doing a repair. */
+    Path destPath;
+
     typedef void (SubstitutionGoal::*GoalState)();
     GoalState state;
 
 public:
-    SubstitutionGoal(const Path & storePath, Worker & worker);
+    SubstitutionGoal(const Path & storePath, Worker & worker, bool repair = false);
     ~SubstitutionGoal();
 
     void cancel();
@@ -2371,9 +2378,10 @@ public:
 };
 
 
-SubstitutionGoal::SubstitutionGoal(const Path & storePath, Worker & worker)
+SubstitutionGoal::SubstitutionGoal(const Path & storePath, Worker & worker, bool repair)
     : Goal(worker)
     , hasSubstitute(false)
+    , repair(repair)
 {
     this->storePath = storePath;
     state = &SubstitutionGoal::init;
@@ -2415,7 +2423,7 @@ void SubstitutionGoal::init()
     worker.store.addTempRoot(storePath);
 
     /* If the path already exists we're done. */
-    if (worker.store.isValidPath(storePath)) {
+    if (!repair && worker.store.isValidPath(storePath)) {
         amDone(ecSuccess);
         return;
     }
@@ -2519,7 +2527,7 @@ void SubstitutionGoal::tryToRun()
     }
 
     /* Check again whether the path is invalid. */
-    if (worker.store.isValidPath(storePath)) {
+    if (!repair && worker.store.isValidPath(storePath)) {
         debug(format("store path `%1%' has become valid") % storePath);
         outputLock->setDeletion(true);
         amDone(ecSuccess);
@@ -2531,9 +2539,11 @@ void SubstitutionGoal::tryToRun()
     outPipe.create();
     logPipe.create();
 
+    destPath = repair ? storePath + ".tmp" : storePath;
+
     /* Remove the (stale) output path if it exists. */
-    if (pathExists(storePath))
-        deletePathWrapped(storePath);
+    if (pathExists(destPath))
+        deletePathWrapped(destPath);
 
     /* Fork the substitute program. */
     pid = fork();
@@ -2561,6 +2571,7 @@ void SubstitutionGoal::tryToRun()
             args.push_back(baseNameOf(sub));
             args.push_back("--substitute");
             args.push_back(storePath);
+            args.push_back(destPath);
             const char * * argArr = strings2CharPtrs(args);
 
             execv(sub.c_str(), (char * *) argArr);
@@ -2617,10 +2628,10 @@ void SubstitutionGoal::finished()
             throw SubstError(format("fetching path `%1%' %2%")
                 % storePath % statusToString(status));
 
-        if (!pathExists(storePath))
-            throw SubstError(format("substitute did not produce path `%1%'") % storePath);
+        if (!pathExists(destPath))
+            throw SubstError(format("substitute did not produce path `%1%'") % destPath);
 
-        hash = hashPath(htSHA256, storePath);
+        hash = hashPath(htSHA256, destPath);
 
         /* Verify the expected hash we got from the substituer. */
         if (expectedHashStr != "") {
@@ -2631,7 +2642,7 @@ void SubstitutionGoal::finished()
             if (hashType == htUnknown)
                 throw Error(format("unknown hash algorithm in `%1%'") % expectedHashStr);
             Hash expectedHash = parseHash16or32(hashType, string(expectedHashStr, n + 1));
-            Hash actualHash = hashType == htSHA256 ? hash.first : hashPath(hashType, storePath).first;
+            Hash actualHash = hashType == htSHA256 ? hash.first : hashPath(hashType, destPath).first;
             if (expectedHash != actualHash)
                 throw SubstError(format("hash mismatch in downloaded path `%1%': expected %2%, got %3%")
                     % storePath % printHash(expectedHash) % printHash(actualHash));
@@ -2652,9 +2663,26 @@ void SubstitutionGoal::finished()
         return;
     }
 
-    canonicalisePathMetaData(storePath);
+    canonicalisePathMetaData(destPath);
 
-    worker.store.optimisePath(storePath); // FIXME: combine with hashPath()
+    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);
+    }
 
     ValidPathInfo info2;
     info2.path = storePath;
@@ -2724,29 +2752,27 @@ Worker::~Worker()
 }
 
 
-template<class T>
-static GoalPtr addGoal(const Path & path,
-    Worker & worker, WeakGoalMap & goalMap)
+GoalPtr Worker::makeDerivationGoal(const Path & path)
 {
-    GoalPtr goal = goalMap[path].lock();
+    GoalPtr goal = derivationGoals[path].lock();
     if (!goal) {
-        goal = GoalPtr(new T(path, worker));
-        goalMap[path] = goal;
-        worker.wakeUp(goal);
+        goal = GoalPtr(new DerivationGoal(path, *this));
+        derivationGoals[path] = goal;
+        wakeUp(goal);
     }
     return goal;
 }
 
 
-GoalPtr Worker::makeDerivationGoal(const Path & nePath)
+GoalPtr Worker::makeSubstitutionGoal(const Path & path, bool repair)
 {
-    return addGoal<DerivationGoal>(nePath, *this, derivationGoals);
-}
-
-
-GoalPtr Worker::makeSubstitutionGoal(const Path & storePath)
-{
-    return addGoal<SubstitutionGoal>(storePath, *this, substitutionGoals);
+    GoalPtr goal = substitutionGoals[path].lock();
+    if (!goal) {
+        goal = GoalPtr(new SubstitutionGoal(path, *this, repair));
+        substitutionGoals[path] = goal;
+        wakeUp(goal);
+    }
+    return goal;
 }
 
 
@@ -3109,4 +3135,17 @@ void LocalStore::ensurePath(const Path & path)
 }
 
 
+void LocalStore::repairPath(const Path & path)
+{
+    Worker worker(*this);
+    GoalPtr goal = worker.makeSubstitutionGoal(path, true);
+    Goals goals = singleton<Goals>(goal);
+
+    worker.run(goals);
+
+    if (goal->getExitCode() != Goal::ecSuccess)
+        throw Error(format("cannot repair path `%1%'") % path, worker.exitStatus());
+}
+
+
 }