diff options
Diffstat (limited to 'src/libstore/gc.cc')
-rw-r--r-- | src/libstore/gc.cc | 115 |
1 files changed, 76 insertions, 39 deletions
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index 95c7154110b3..f6ed7dd2264e 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -1,6 +1,7 @@ #include "globals.hh" #include "misc.hh" #include "local-store.hh" +#include "immutable.hh" #include <boost/shared_ptr.hpp> @@ -55,24 +56,22 @@ int LocalStore::openGCLock(LockType lockType) } -void createSymlink(const Path & link, const Path & target, bool careful) +void createSymlink(const Path & link, const Path & target) { /* Create directories up to `gcRoot'. */ createDirs(dirOf(link)); - /* !!! shouldn't removing and creating the symlink be atomic? */ - - /* Remove the old symlink. */ - if (pathExists(link)) { - if (careful && (!isLink(link) || !isInStore(readLink(link)))) - throw Error(format("cannot create symlink `%1%'; already exists") % link); - unlink(link.c_str()); - } - - /* And create the new one. */ - if (symlink(target.c_str(), link.c_str()) == -1) + /* Create the new symlink. */ + Path tempLink = (format("%1%.tmp-%2%-%3%") + % link % getpid() % rand()).str(); + if (symlink(target.c_str(), tempLink.c_str()) == -1) throw SysError(format("symlinking `%1%' to `%2%'") - % link % target); + % tempLink % target); + + /* Atomically replace the old one. */ + if (rename(tempLink.c_str(), link.c_str()) == -1) + throw SysError(format("cannot rename `%1%' to `%2%'") + % tempLink % link); } @@ -87,7 +86,7 @@ void LocalStore::addIndirectRoot(const Path & path) string hash = printHash32(hashString(htSHA1, path)); Path realRoot = canonPath((format("%1%/%2%/auto/%3%") % nixStateDir % gcRootsDir % hash).str()); - createSymlink(realRoot, path, false); + createSymlink(realRoot, path); } @@ -104,7 +103,11 @@ Path addPermRoot(StoreAPI & store, const Path & _storePath, "(are you running nix-build inside the store?)") % gcRoot); if (indirect) { - createSymlink(gcRoot, storePath, true); + /* Don't clobber the the link if it already exists and doesn't + point to the Nix store. */ + if (pathExists(gcRoot) && (!isLink(gcRoot) || !isInStore(readLink(gcRoot)))) + throw Error(format("cannot create symlink `%1%'; already exists") % gcRoot); + createSymlink(gcRoot, storePath); store.addIndirectRoot(gcRoot); } @@ -119,7 +122,7 @@ Path addPermRoot(StoreAPI & store, const Path & _storePath, % gcRoot % rootsDir); } - createSymlink(gcRoot, storePath, false); + createSymlink(gcRoot, storePath); } /* Check that the root can be found by the garbage collector. @@ -396,11 +399,11 @@ struct LocalStore::GCState PathSet deleted; PathSet live; PathSet busy; + PathSet invalidated; bool gcKeepOutputs; bool gcKeepDerivations; - GCState(GCResults & results_) : results(results_) - { - } + unsigned long long bytesInvalidated; + GCState(GCResults & results_) : results(results_), bytesInvalidated(0) { } }; @@ -418,12 +421,27 @@ bool LocalStore::isActiveTempFile(const GCState & state, && state.tempRoots.find(string(path, 0, path.size() - suffix.size())) != state.tempRoots.end(); } - + +void LocalStore::deleteGarbage(GCState & state, const Path & path) +{ + printMsg(lvlInfo, format("deleting `%1%'") % path); + unsigned long long bytesFreed, blocksFreed; + deletePathWrapped(path, bytesFreed, blocksFreed); + state.results.bytesFreed += bytesFreed; + state.results.blocksFreed += blocksFreed; +} + + bool LocalStore::tryToDelete(GCState & state, const Path & path) { checkInterrupt(); - - if (!pathExists(path)) return true; + + struct stat st; + if (lstat(path.c_str(), &st)) { + if (errno == ENOENT) return true; + throw SysError(format("getting status of %1%") % path); + } + if (state.deleted.find(path) != state.deleted.end()) return true; if (state.live.find(path) != state.live.end()) return false; @@ -502,28 +520,39 @@ bool LocalStore::tryToDelete(GCState & state, const Path & path) /* The path is garbage, so delete it. */ if (shouldDelete(state.options.action)) { - printMsg(lvlInfo, format("deleting `%1%'") % path); - unsigned long long bytesFreed, blocksFreed; - deleteFromStore(path, bytesFreed, blocksFreed); - state.results.bytesFreed += bytesFreed; - state.results.blocksFreed += blocksFreed; + /* If it's a valid path that's not a regular file or symlink, + invalidate it, rename it, and schedule it for deletion. + The renaming is to ensure that later (when we're not + holding the global GC lock) we can delete the path without + being afraid that the path has become alive again. + Otherwise delete it right away. */ + if (isValidPath(path)) { + if (S_ISDIR(st.st_mode)) { + printMsg(lvlInfo, format("invalidating `%1%'") % path); + // Estimate the amount freed using the narSize field. + state.bytesInvalidated += queryPathInfo(path).narSize; + invalidatePathChecked(path); + makeMutable(path.c_str()); + // Mac OS X cannot rename directories if they are read-only. + if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1) + throw SysError(format("making `%1%' writable") % path); + Path tmp = (format("%1%-gc-%2%") % path % getpid()).str(); + if (rename(path.c_str(), tmp.c_str())) + throw SysError(format("unable to rename `%1%' to `%2%'") % path % tmp); + state.invalidated.insert(tmp); + } else { + invalidatePathChecked(path); + deleteGarbage(state, path); + } + } else + deleteGarbage(state, path); - if (state.options.maxFreed && state.results.bytesFreed > state.options.maxFreed) { - printMsg(lvlInfo, format("deleted more than %1% bytes; stopping") % state.options.maxFreed); + if (state.options.maxFreed && state.results.bytesFreed + state.bytesInvalidated > state.options.maxFreed) { + printMsg(lvlInfo, format("deleted or invalidated more than %1% bytes; stopping") % state.options.maxFreed); throw GCLimitReached(); } - if (state.options.maxLinks) { - struct stat st; - if (stat(nixStore.c_str(), &st) == -1) - throw SysError(format("statting `%1%'") % nixStore); - if (st.st_nlink < state.options.maxLinks) { - printMsg(lvlInfo, format("link count on the store has dropped below %1%; stopping") % state.options.maxLinks); - throw GCLimitReached(); - } - } - } else printMsg(lvlTalkative, format("would delete `%1%'") % path); @@ -645,6 +674,14 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) } catch (GCLimitReached & e) { } } + + /* Allow other processes to add to the store from here on. */ + fdGCLock.close(); + + /* Delete the invalidated paths now that the lock has been + released. */ + foreach (PathSet::iterator, i, state.invalidated) + deleteGarbage(state, *i); } |