* Some refactoring: put the GC options / results in separate structs.
* The garbage collector now also prints the number of blocks freed.
This commit is contained in:
		
							parent
							
								
									934c58aa38
								
							
						
					
					
						commit
						a72709afd8
					
				
					 15 changed files with 252 additions and 166 deletions
				
			
		|  | @ -578,17 +578,17 @@ void getOwnership(const Path & path) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| void deletePathWrapped(const Path & path, | void deletePathWrapped(const Path & path, | ||||||
|     unsigned long long & bytesFreed) |     unsigned long long & bytesFreed, unsigned long long & blocksFreed) | ||||||
| { | { | ||||||
|     try { |     try { | ||||||
|         /* First try to delete it ourselves. */ |         /* First try to delete it ourselves. */ | ||||||
|         deletePath(path, bytesFreed); |         deletePath(path, bytesFreed, blocksFreed); | ||||||
|     } catch (SysError & e) { |     } catch (SysError & e) { | ||||||
|         /* If this failed due to a permission error, then try it with
 |         /* If this failed due to a permission error, then try it with
 | ||||||
|            the setuid helper. */ |            the setuid helper. */ | ||||||
|         if (haveBuildUsers() && !amPrivileged()) { |         if (haveBuildUsers() && !amPrivileged()) { | ||||||
|             getOwnership(path); |             getOwnership(path); | ||||||
|             deletePath(path, bytesFreed); |             deletePath(path, bytesFreed, blocksFreed); | ||||||
|         } else |         } else | ||||||
|             throw; |             throw; | ||||||
|     } |     } | ||||||
|  | @ -597,8 +597,8 @@ void deletePathWrapped(const Path & path, | ||||||
| 
 | 
 | ||||||
| void deletePathWrapped(const Path & path) | void deletePathWrapped(const Path & path) | ||||||
| { | { | ||||||
|     unsigned long long dummy; |     unsigned long long dummy1, dummy2; | ||||||
|     deletePathWrapped(path, dummy); |     deletePathWrapped(path, dummy1, dummy2); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -439,9 +439,9 @@ Paths topoSortPaths(const PathSet & paths) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths, | void LocalStore::tryToDelete(const GCOptions & options, GCResults & results,  | ||||||
|     const PathSet & tempRootsClosed, PathSet & done, PathSet & deleted, |     const PathSet & livePaths, const PathSet & tempRootsClosed, PathSet & done,  | ||||||
|     const Path & path, unsigned long long & bytesFreed) |     const Path & path) | ||||||
| { | { | ||||||
|     if (done.find(path) != done.end()) return; |     if (done.find(path) != done.end()) return; | ||||||
|     done.insert(path); |     done.insert(path); | ||||||
|  | @ -449,7 +449,7 @@ void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths, | ||||||
|     debug(format("considering deletion of `%1%'") % path); |     debug(format("considering deletion of `%1%'") % path); | ||||||
|          |          | ||||||
|     if (livePaths.find(path) != livePaths.end()) { |     if (livePaths.find(path) != livePaths.end()) { | ||||||
|         if (action == gcDeleteSpecific) |         if (options.action == GCOptions::gcDeleteSpecific) | ||||||
|             throw Error(format("cannot delete path `%1%' since it is still alive") % path); |             throw Error(format("cannot delete path `%1%' since it is still alive") % path); | ||||||
|         debug(format("live path `%1%'") % path); |         debug(format("live path `%1%'") % path); | ||||||
|         return; |         return; | ||||||
|  | @ -470,15 +470,18 @@ void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths, | ||||||
|         queryReferrers(path, referrers); |         queryReferrers(path, referrers); | ||||||
|     foreach (PathSet::iterator, i, referrers) |     foreach (PathSet::iterator, i, referrers) | ||||||
|         if (*i != path) |         if (*i != path) | ||||||
|             tryToDelete(action, livePaths, tempRootsClosed, done, deleted, *i, bytesFreed); |             tryToDelete(options, results, livePaths, tempRootsClosed, done, *i); | ||||||
| 
 | 
 | ||||||
|     debug(format("dead path `%1%'") % path); |     debug(format("dead path `%1%'") % path); | ||||||
|     deleted.insert(path); |     results.paths.insert(path); | ||||||
| 
 | 
 | ||||||
|     /* If just returning the set of dead paths, we also return the
 |     /* If just returning the set of dead paths, we also return the
 | ||||||
|        space that would be freed if we deleted them. */ |        space that would be freed if we deleted them. */ | ||||||
|     if (action == gcReturnDead) { |     if (options.action == GCOptions::gcReturnDead) { | ||||||
|         bytesFreed += computePathSize(path); |         unsigned long long bytesFreed, blocksFreed; | ||||||
|  |         computePathSize(path, bytesFreed, blocksFreed); | ||||||
|  |         results.bytesFreed += bytesFreed; | ||||||
|  |         results.blocksFreed += blocksFreed; | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -504,9 +507,10 @@ void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths, | ||||||
|     printMsg(lvlInfo, format("deleting `%1%'") % path); |     printMsg(lvlInfo, format("deleting `%1%'") % path); | ||||||
|              |              | ||||||
|     /* Okay, it's safe to delete. */ |     /* Okay, it's safe to delete. */ | ||||||
|     unsigned long long freed; |     unsigned long long bytesFreed, blocksFreed; | ||||||
|     deleteFromStore(path, freed); |     deleteFromStore(path, bytesFreed, blocksFreed); | ||||||
|     bytesFreed += freed; |     results.bytesFreed += bytesFreed; | ||||||
|  |     results.blocksFreed += blocksFreed; | ||||||
| 
 | 
 | ||||||
| #ifndef __CYGWIN__ | #ifndef __CYGWIN__ | ||||||
|     if (fdLock != -1) |     if (fdLock != -1) | ||||||
|  | @ -516,12 +520,8 @@ void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete, | void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) | ||||||
|     bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed) |  | ||||||
| { | { | ||||||
|     result.clear(); |  | ||||||
|     bytesFreed = 0; |  | ||||||
| 
 |  | ||||||
|     bool gcKeepOutputs = |     bool gcKeepOutputs = | ||||||
|         queryBoolSetting("gc-keep-outputs", false); |         queryBoolSetting("gc-keep-outputs", false); | ||||||
|     bool gcKeepDerivations = |     bool gcKeepDerivations = | ||||||
|  | @ -537,7 +537,7 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete, | ||||||
|     /* Find the roots.  Since we've grabbed the GC lock, the set of
 |     /* Find the roots.  Since we've grabbed the GC lock, the set of
 | ||||||
|        permanent roots cannot increase now. */ |        permanent roots cannot increase now. */ | ||||||
|     printMsg(lvlError, format("finding garbage collector roots...")); |     printMsg(lvlError, format("finding garbage collector roots...")); | ||||||
|     Roots rootMap = ignoreLiveness ? Roots() : nix::findRoots(true); |     Roots rootMap = options.ignoreLiveness ? Roots() : nix::findRoots(true); | ||||||
| 
 | 
 | ||||||
|     PathSet roots; |     PathSet roots; | ||||||
|     for (Roots::iterator i = rootMap.begin(); i != rootMap.end(); ++i) |     for (Roots::iterator i = rootMap.begin(); i != rootMap.end(); ++i) | ||||||
|  | @ -547,11 +547,11 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete, | ||||||
|        NIX_ROOT_FINDER environment variable.  This is typically used |        NIX_ROOT_FINDER environment variable.  This is typically used | ||||||
|        to add running programs to the set of roots (to prevent them |        to add running programs to the set of roots (to prevent them | ||||||
|        from being garbage collected). */ |        from being garbage collected). */ | ||||||
|     if (!ignoreLiveness) |     if (!options.ignoreLiveness) | ||||||
|         addAdditionalRoots(roots); |         addAdditionalRoots(roots); | ||||||
| 
 | 
 | ||||||
|     if (action == gcReturnRoots) { |     if (options.action == GCOptions::gcReturnRoots) { | ||||||
|         result = roots; |         results.paths = roots; | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -595,8 +595,8 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete, | ||||||
|             } |             } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (action == gcReturnLive) { |     if (options.action == GCOptions::gcReturnLive) { | ||||||
|         result = livePaths; |         results.paths = livePaths; | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -633,27 +633,25 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete, | ||||||
|        paths. */ |        paths. */ | ||||||
|     printMsg(lvlError, format("reading the Nix store...")); |     printMsg(lvlError, format("reading the Nix store...")); | ||||||
|     PathSet storePaths; |     PathSet storePaths; | ||||||
|     if (action != gcDeleteSpecific) { |     if (options.action != GCOptions::gcDeleteSpecific) { | ||||||
|         Paths entries = readDirectory(nixStore); |         Paths entries = readDirectory(nixStore); | ||||||
|         for (Paths::iterator i = entries.begin(); i != entries.end(); ++i) |         for (Paths::iterator i = entries.begin(); i != entries.end(); ++i) | ||||||
|             storePaths.insert(canonPath(nixStore + "/" + *i)); |             storePaths.insert(canonPath(nixStore + "/" + *i)); | ||||||
|     } else { |     } else { | ||||||
|         for (PathSet::iterator i = pathsToDelete.begin(); |         foreach (PathSet::iterator, i, options.pathsToDelete) { | ||||||
|              i != pathsToDelete.end(); ++i) |  | ||||||
|         { |  | ||||||
|             assertStorePath(*i); |             assertStorePath(*i); | ||||||
|             storePaths.insert(*i); |             storePaths.insert(*i); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /* Try to delete store paths in the topologically sorted order. */ |     /* Try to delete store paths in the topologically sorted order. */ | ||||||
|     printMsg(lvlError, action == gcReturnDead |     printMsg(lvlError, options.action == GCOptions::gcReturnDead | ||||||
|         ? format("looking for garbage...") |         ? format("looking for garbage...") | ||||||
|         : format("deleting garbage...")); |         : format("deleting garbage...")); | ||||||
| 
 | 
 | ||||||
|     PathSet done; |     PathSet done; | ||||||
|     foreach (PathSet::iterator, i, storePaths) |     foreach (PathSet::iterator, i, storePaths) | ||||||
|         tryToDelete(action, livePaths, tempRootsClosed, done, result, *i, bytesFreed); |         tryToDelete(options, results, livePaths, tempRootsClosed, done, *i); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|   |   | ||||||
|  |  | ||||||
|  | @ -851,7 +851,8 @@ Path LocalStore::importPath(bool requireSignature, Source & source) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFreed) | void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFreed, | ||||||
|  |     unsigned long long & blocksFreed) | ||||||
| { | { | ||||||
|     bytesFreed = 0; |     bytesFreed = 0; | ||||||
| 
 | 
 | ||||||
|  | @ -871,7 +872,7 @@ void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFr | ||||||
|         invalidatePath(path); |         invalidatePath(path); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     deletePathWrapped(path, bytesFreed); |     deletePathWrapped(path, bytesFreed, blocksFreed); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -25,10 +25,11 @@ struct OptimiseStats | ||||||
|     unsigned long sameContents; |     unsigned long sameContents; | ||||||
|     unsigned long filesLinked; |     unsigned long filesLinked; | ||||||
|     unsigned long long bytesFreed; |     unsigned long long bytesFreed; | ||||||
|  |     unsigned long long blocksFreed; | ||||||
|     OptimiseStats() |     OptimiseStats() | ||||||
|     { |     { | ||||||
|         totalFiles = sameContents = filesLinked = 0; |         totalFiles = sameContents = filesLinked = 0; | ||||||
|         bytesFreed = 0; |         bytesFreed = blocksFreed = 0; | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -89,11 +90,11 @@ public: | ||||||
| 
 | 
 | ||||||
|     Roots findRoots(); |     Roots findRoots(); | ||||||
| 
 | 
 | ||||||
|     void collectGarbage(GCAction action, const PathSet & pathsToDelete, |     void collectGarbage(const GCOptions & options, GCResults & results); | ||||||
|         bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed); |  | ||||||
| 
 | 
 | ||||||
|     /* Delete a path from the Nix store. */ |     /* Delete a path from the Nix store. */ | ||||||
|     void deleteFromStore(const Path & path, unsigned long long & bytesFreed); |     void deleteFromStore(const Path & path, unsigned long long & bytesFreed, | ||||||
|  |         unsigned long long & blocksFreed); | ||||||
|      |      | ||||||
|     /* Optimise the disk space usage of the Nix store by hard-linking
 |     /* Optimise the disk space usage of the Nix store by hard-linking
 | ||||||
|        files with the same contents. */ |        files with the same contents. */ | ||||||
|  | @ -143,10 +144,9 @@ private: | ||||||
|      |      | ||||||
|     void upgradeStore12(); |     void upgradeStore12(); | ||||||
| 
 | 
 | ||||||
|     void tryToDelete(GCAction action, const PathSet & livePaths, |     void tryToDelete(const GCOptions & options, GCResults & results, | ||||||
|         const PathSet & tempRootsClosed, PathSet & done, PathSet & deleted, |         const PathSet & livePaths, const PathSet & tempRootsClosed, PathSet & done,  | ||||||
|         const Path & path, unsigned long long & bytesFreed); |         const Path & path); | ||||||
|      |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -179,7 +179,7 @@ void getOwnership(const Path & path); | ||||||
| /* Like deletePath(), but changes the ownership of `path' using the
 | /* Like deletePath(), but changes the ownership of `path' using the
 | ||||||
|    setuid wrapper if necessary (and possible). */ |    setuid wrapper if necessary (and possible). */ | ||||||
| void deletePathWrapped(const Path & path, | void deletePathWrapped(const Path & path, | ||||||
|     unsigned long long & bytesFreed); |     unsigned long long & bytesFreed, unsigned long long & blocksFreed); | ||||||
| 
 | 
 | ||||||
| void deletePathWrapped(const Path & path); | void deletePathWrapped(const Path & path); | ||||||
|   |   | ||||||
|  |  | ||||||
|  | @ -101,6 +101,7 @@ static void hashAndLink(bool dryRun, HashToPath & hashToPath, | ||||||
|          |          | ||||||
|         stats.filesLinked++; |         stats.filesLinked++; | ||||||
|         stats.bytesFreed += st.st_size; |         stats.bytesFreed += st.st_size; | ||||||
|  |         stats.blocksFreed += st.st_blocks; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (S_ISDIR(st.st_mode)) { |     if (S_ISDIR(st.st_mode)) { | ||||||
|  |  | ||||||
|  | @ -372,24 +372,20 @@ Roots RemoteStore::findRoots() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| void RemoteStore::collectGarbage(GCAction action, const PathSet & pathsToDelete, | void RemoteStore::collectGarbage(const GCOptions & options, GCResults & results) | ||||||
|     bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed) |  | ||||||
| { | { | ||||||
|     result.clear(); |  | ||||||
|     bytesFreed = 0; |  | ||||||
|     writeInt(wopCollectGarbage, to); |     writeInt(wopCollectGarbage, to); | ||||||
|     writeInt(action, to); |     writeInt(options.action, to); | ||||||
|     writeStringSet(pathsToDelete, to); |     writeStringSet(options.pathsToDelete, to); | ||||||
|     writeInt(ignoreLiveness, to); |     writeInt(options.ignoreLiveness, to); | ||||||
|  |     writeLongLong(options.maxFreed, to); | ||||||
|  |     writeInt(options.maxLinks, to); | ||||||
|      |      | ||||||
|     processStderr(); |     processStderr(); | ||||||
|      |      | ||||||
|     result = readStringSet(from); |     results.paths = readStringSet(from); | ||||||
| 
 |     results.bytesFreed = readLongLong(from); | ||||||
|     /* Ugh - NAR integers are 64 bits, but read/writeInt() aren't. */ |     results.blocksFreed = readLongLong(from); | ||||||
|     unsigned int lo = readInt(from); |  | ||||||
|     unsigned int hi = readInt(from); |  | ||||||
|     bytesFreed = (((unsigned long long) hi) << 32) | lo; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -65,8 +65,7 @@ public: | ||||||
|      |      | ||||||
|     Roots findRoots(); |     Roots findRoots(); | ||||||
| 
 | 
 | ||||||
|     void collectGarbage(GCAction action, const PathSet & pathsToDelete, |     void collectGarbage(const GCOptions & options, GCResults & results); | ||||||
|         bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed); |  | ||||||
|      |      | ||||||
| private: | private: | ||||||
|     AutoCloseFD fdSocket; |     AutoCloseFD fdSocket; | ||||||
|  |  | ||||||
|  | @ -16,14 +16,82 @@ namespace nix { | ||||||
| typedef std::map<Path, Path> Roots; | typedef std::map<Path, Path> Roots; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /* Garbage collector operation. */ | 
 | ||||||
| typedef enum { | 
 | ||||||
|  | struct GCOptions | ||||||
|  | { | ||||||
|  |     /* Garbage collector operation:
 | ||||||
|  | 
 | ||||||
|  |        - `gcReturnRoots': find and return the set of roots for the | ||||||
|  |          garbage collector.  These are the store paths symlinked to in | ||||||
|  |          the `gcroots' directory. | ||||||
|  | 
 | ||||||
|  |        - `gcReturnLive': return the set of paths reachable from | ||||||
|  |          (i.e. in the closure of) the roots. | ||||||
|  | 
 | ||||||
|  |        - `gcReturnDead': return the set of paths not reachable from | ||||||
|  |          the roots. | ||||||
|  | 
 | ||||||
|  |        - `gcDeleteDead': actually delete the latter set. | ||||||
|  | 
 | ||||||
|  |        - `gcDeleteSpecific': delete the paths listed in | ||||||
|  |           `pathsToDelete', insofar as they are not reachable. | ||||||
|  |     */ | ||||||
|  |     typedef enum { | ||||||
|         gcReturnRoots, |         gcReturnRoots, | ||||||
|         gcReturnLive, |         gcReturnLive, | ||||||
|         gcReturnDead, |         gcReturnDead, | ||||||
|         gcDeleteDead, |         gcDeleteDead, | ||||||
|         gcDeleteSpecific, |         gcDeleteSpecific, | ||||||
| } GCAction; |     } GCAction; | ||||||
|  | 
 | ||||||
|  |     GCAction action; | ||||||
|  | 
 | ||||||
|  |     /* If `ignoreLiveness' is set, then reachability from the roots is
 | ||||||
|  |        ignored (dangerous!).  However, the paths must still be | ||||||
|  |        unreferenced *within* the store (i.e., there can be no other | ||||||
|  |        store paths that depend on them). */ | ||||||
|  |     bool ignoreLiveness; | ||||||
|  | 
 | ||||||
|  |     /* For `gcDeleteSpecific', the paths to delete. */ | ||||||
|  |     PathSet pathsToDelete; | ||||||
|  | 
 | ||||||
|  |     /* Stop after at least `maxFreed' bytes have been freed. */ | ||||||
|  |     unsigned long long maxFreed; | ||||||
|  | 
 | ||||||
|  |     /* Stop after the number of hard links to the Nix store directory
 | ||||||
|  |        has dropped to at least `maxLinks'. */ | ||||||
|  |     unsigned int maxLinks; | ||||||
|  | 
 | ||||||
|  |     GCOptions()  | ||||||
|  |     { | ||||||
|  |         action = gcDeleteDead; | ||||||
|  |         ignoreLiveness = false; | ||||||
|  |         maxFreed = ULLONG_MAX; | ||||||
|  |         maxLinks = UINT_MAX; | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | struct GCResults  | ||||||
|  | { | ||||||
|  |     /* Depending on the action, the GC roots, or the paths that would
 | ||||||
|  |        be or have been deleted. */ | ||||||
|  |     PathSet paths; | ||||||
|  | 
 | ||||||
|  |     /* For `gcReturnDead', `gcDeleteDead' and `gcDeleteSpecific', the
 | ||||||
|  |        number of bytes that would be or was freed. */ | ||||||
|  |     unsigned long long bytesFreed; | ||||||
|  | 
 | ||||||
|  |     /* The number of file system blocks that would be or was freed. */ | ||||||
|  |     unsigned long long blocksFreed; | ||||||
|  | 
 | ||||||
|  |     GCResults() | ||||||
|  |     { | ||||||
|  |         bytesFreed = 0; | ||||||
|  |         blocksFreed = 0; | ||||||
|  |     } | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class StoreAPI  | class StoreAPI  | ||||||
|  | @ -137,33 +205,8 @@ public: | ||||||
|        outside of the Nix store that point to `storePath'.  */ |        outside of the Nix store that point to `storePath'.  */ | ||||||
|     virtual Roots findRoots() = 0; |     virtual Roots findRoots() = 0; | ||||||
| 
 | 
 | ||||||
|     /* Depending on `action', this function does the following:
 |     /* Perform a garbage collection. */ | ||||||
| 
 |     virtual void collectGarbage(const GCOptions & options, GCResults & results) = 0; | ||||||
|        - `gcReturnRoots': find and return the set of roots for the |  | ||||||
|          garbage collector.  These are the store paths symlinked to in |  | ||||||
|          the `gcroots' directory. |  | ||||||
| 
 |  | ||||||
|        - `gcReturnLive': return the set of paths reachable from |  | ||||||
|          (i.e. in the closure of) the roots. |  | ||||||
| 
 |  | ||||||
|        - `gcReturnDead': return the set of paths not reachable from |  | ||||||
|          the roots. |  | ||||||
| 
 |  | ||||||
|        - `gcDeleteDead': actually delete the latter set. |  | ||||||
| 
 |  | ||||||
|        - `gcDeleteSpecific': delete the paths listed in |  | ||||||
|          `pathsToDelete', insofar as they are not reachable. |  | ||||||
| 
 |  | ||||||
|        If `ignoreLiveness' is set, then reachability from the roots is |  | ||||||
|        ignored (dangerous!).  However, the paths must still be |  | ||||||
|        unreferenced *within* the store (i.e., there can be no other |  | ||||||
|        store paths that depend on them). |  | ||||||
| 
 |  | ||||||
|        For `gcReturnDead', `gcDeleteDead' and `gcDeleteSpecific', the |  | ||||||
|        number of bytes that would be or was freed is returned in |  | ||||||
|        `bytesFreed'. */ |  | ||||||
|     virtual void collectGarbage(GCAction action, const PathSet & pathsToDelete, |  | ||||||
|         bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed) = 0; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,24 +15,24 @@ namespace nix { | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     wopQuit = 0, |     wopQuit = 0, | ||||||
|     wopIsValidPath, |     wopIsValidPath = 1, | ||||||
|     wopHasSubstitutes = 3, |     wopHasSubstitutes = 3, | ||||||
|     wopQueryPathHash, |     wopQueryPathHash = 4, | ||||||
|     wopQueryReferences, |     wopQueryReferences = 5, | ||||||
|     wopQueryReferrers, |     wopQueryReferrers = 6, | ||||||
|     wopAddToStore, |     wopAddToStore = 7, | ||||||
|     wopAddTextToStore, |     wopAddTextToStore = 8, | ||||||
|     wopBuildDerivations, |     wopBuildDerivations = 9, | ||||||
|     wopEnsurePath, |     wopEnsurePath = 10, | ||||||
|     wopAddTempRoot, |     wopAddTempRoot = 11, | ||||||
|     wopAddIndirectRoot, |     wopAddIndirectRoot = 12, | ||||||
|     wopSyncWithGC, |     wopSyncWithGC = 13, | ||||||
|     wopFindRoots, |     wopFindRoots = 14, | ||||||
|     wopCollectGarbage, |     wopExportPath = 16, | ||||||
|     wopExportPath, |     wopImportPath = 17, | ||||||
|     wopImportPath, |     wopQueryDeriver = 18, | ||||||
|     wopQueryDeriver, |     wopSetOptions = 19, | ||||||
|     wopSetOptions, |     wopCollectGarbage = 20, | ||||||
| } WorkerOp; | } WorkerOp; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -41,6 +41,21 @@ void writeInt(unsigned int n, Sink & sink) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | void writeLongLong(unsigned long long n, Sink & sink) | ||||||
|  | { | ||||||
|  |     unsigned char buf[8]; | ||||||
|  |     buf[0] = n & 0xff; | ||||||
|  |     buf[1] = (n >> 8) & 0xff; | ||||||
|  |     buf[2] = (n >> 16) & 0xff; | ||||||
|  |     buf[3] = (n >> 24) & 0xff; | ||||||
|  |     buf[4] = (n >> 32) & 0xff; | ||||||
|  |     buf[5] = (n >> 40) & 0xff; | ||||||
|  |     buf[6] = (n >> 48) & 0xff; | ||||||
|  |     buf[7] = (n >> 56) & 0xff; | ||||||
|  |     sink(buf, sizeof(buf)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| void writeString(const string & s, Sink & sink) | void writeString(const string & s, Sink & sink) | ||||||
| { | { | ||||||
|     unsigned int len = s.length(); |     unsigned int len = s.length(); | ||||||
|  | @ -84,6 +99,22 @@ unsigned int readInt(Source & source) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | unsigned long long readLongLong(Source & source) | ||||||
|  | { | ||||||
|  |     unsigned char buf[8]; | ||||||
|  |     source(buf, sizeof(buf)); | ||||||
|  |     return | ||||||
|  |         ((unsigned long long) buf[0]) | | ||||||
|  |         ((unsigned long long) buf[1] << 8) | | ||||||
|  |         ((unsigned long long) buf[2] << 16) | | ||||||
|  |         ((unsigned long long) buf[3] << 24) | | ||||||
|  |         ((unsigned long long) buf[4] << 32) | | ||||||
|  |         ((unsigned long long) buf[5] << 40) | | ||||||
|  |         ((unsigned long long) buf[6] << 48) | | ||||||
|  |         ((unsigned long long) buf[7] << 56); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| string readString(Source & source) | string readString(Source & source) | ||||||
| { | { | ||||||
|     unsigned int len = readInt(source); |     unsigned int len = readInt(source); | ||||||
|  |  | ||||||
|  | @ -95,11 +95,13 @@ struct StringSource : Source | ||||||
| 
 | 
 | ||||||
| void writePadding(unsigned int len, Sink & sink); | void writePadding(unsigned int len, Sink & sink); | ||||||
| void writeInt(unsigned int n, Sink & sink); | void writeInt(unsigned int n, Sink & sink); | ||||||
|  | void writeLongLong(unsigned long long n, Sink & sink); | ||||||
| void writeString(const string & s, Sink & sink); | void writeString(const string & s, Sink & sink); | ||||||
| void writeStringSet(const StringSet & ss, Sink & sink); | void writeStringSet(const StringSet & ss, Sink & sink); | ||||||
| 
 | 
 | ||||||
| void readPadding(unsigned int len, Source & source); | void readPadding(unsigned int len, Source & source); | ||||||
| unsigned int readInt(Source & source); | unsigned int readInt(Source & source); | ||||||
|  | unsigned long long readLongLong(Source & source); | ||||||
| string readString(Source & source); | string readString(Source & source); | ||||||
| StringSet readStringSet(Source & source); | StringSet readStringSet(Source & source); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -229,30 +229,38 @@ void writeFile(const Path & path, const string & s) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| unsigned long long computePathSize(const Path & path) | static void _computePathSize(const Path & path, | ||||||
|  |     unsigned long long & bytes, unsigned long long & blocks) | ||||||
| { | { | ||||||
|     unsigned long long size = 0; |  | ||||||
|      |  | ||||||
|     checkInterrupt(); |     checkInterrupt(); | ||||||
| 
 | 
 | ||||||
|     struct stat st; |     struct stat st; | ||||||
|     if (lstat(path.c_str(), &st)) |     if (lstat(path.c_str(), &st)) | ||||||
| 	throw SysError(format("getting attributes of path `%1%'") % path); | 	throw SysError(format("getting attributes of path `%1%'") % path); | ||||||
| 
 | 
 | ||||||
|     size += st.st_size; |     bytes += st.st_size; | ||||||
|  |     blocks += st.st_blocks; | ||||||
| 
 | 
 | ||||||
|     if (S_ISDIR(st.st_mode)) { |     if (S_ISDIR(st.st_mode)) { | ||||||
| 	Strings names = readDirectory(path); | 	Strings names = readDirectory(path); | ||||||
| 
 | 
 | ||||||
| 	for (Strings::iterator i = names.begin(); i != names.end(); ++i) | 	for (Strings::iterator i = names.begin(); i != names.end(); ++i) | ||||||
|             size += computePathSize(path + "/" + *i); |             _computePathSize(path + "/" + *i, bytes, blocks); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     return size; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| static void _deletePath(const Path & path, unsigned long long & bytesFreed) | void computePathSize(const Path & path, | ||||||
|  |     unsigned long long & bytes, unsigned long long & blocks) | ||||||
|  | { | ||||||
|  |     bytes = 0; | ||||||
|  |     blocks = 0; | ||||||
|  |     _computePathSize(path, bytes, blocks); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | static void _deletePath(const Path & path, unsigned long long & bytesFreed, | ||||||
|  |     unsigned long long & blocksFreed) | ||||||
| { | { | ||||||
|     checkInterrupt(); |     checkInterrupt(); | ||||||
| 
 | 
 | ||||||
|  | @ -263,6 +271,7 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed) | ||||||
| 	throw SysError(format("getting attributes of path `%1%'") % path); | 	throw SysError(format("getting attributes of path `%1%'") % path); | ||||||
| 
 | 
 | ||||||
|     bytesFreed += st.st_size; |     bytesFreed += st.st_size; | ||||||
|  |     blocksFreed += st.st_blocks; | ||||||
| 
 | 
 | ||||||
|     if (S_ISDIR(st.st_mode)) { |     if (S_ISDIR(st.st_mode)) { | ||||||
| 	Strings names = readDirectory(path); | 	Strings names = readDirectory(path); | ||||||
|  | @ -274,7 +283,7 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for (Strings::iterator i = names.begin(); i != names.end(); ++i) | 	for (Strings::iterator i = names.begin(); i != names.end(); ++i) | ||||||
|             _deletePath(path + "/" + *i, bytesFreed); |             _deletePath(path + "/" + *i, bytesFreed, blocksFreed); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (remove(path.c_str()) == -1) |     if (remove(path.c_str()) == -1) | ||||||
|  | @ -284,17 +293,19 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed) | ||||||
| 
 | 
 | ||||||
| void deletePath(const Path & path) | void deletePath(const Path & path) | ||||||
| { | { | ||||||
|     unsigned long long dummy; |     unsigned long long dummy1, dummy2; | ||||||
|     deletePath(path, dummy); |     deletePath(path, dummy1, dummy2); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| void deletePath(const Path & path, unsigned long long & bytesFreed) | void deletePath(const Path & path, unsigned long long & bytesFreed, | ||||||
|  |     unsigned long long & blocksFreed) | ||||||
| { | { | ||||||
|     startNest(nest, lvlDebug, |     startNest(nest, lvlDebug, | ||||||
|         format("recursively deleting path `%1%'") % path); |         format("recursively deleting path `%1%'") % path); | ||||||
|     bytesFreed = 0; |     bytesFreed = 0; | ||||||
|     _deletePath(path, bytesFreed); |     blocksFreed = 0; | ||||||
|  |     _deletePath(path, bytesFreed, blocksFreed); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -61,14 +61,16 @@ string readFile(const Path & path); | ||||||
| void writeFile(const Path & path, const string & s); | void writeFile(const Path & path, const string & s); | ||||||
| 
 | 
 | ||||||
| /* Compute the sum of the sizes of all files in `path'. */ | /* Compute the sum of the sizes of all files in `path'. */ | ||||||
| unsigned long long computePathSize(const Path & path); | void computePathSize(const Path & path, | ||||||
|  |     unsigned long long & bytes, unsigned long long & blocks); | ||||||
| 
 | 
 | ||||||
| /* Delete a path; i.e., in the case of a directory, it is deleted
 | /* Delete a path; i.e., in the case of a directory, it is deleted
 | ||||||
|    recursively.  Don't use this at home, kids.  The second variant |    recursively.  Don't use this at home, kids.  The second variant | ||||||
|    returns the number of bytes freed. */ |    returns the number of bytes and blocks freed. */ | ||||||
| void deletePath(const Path & path); | void deletePath(const Path & path); | ||||||
| 
 | 
 | ||||||
| void deletePath(const Path & path, unsigned long long & bytesFreed); | void deletePath(const Path & path, unsigned long long & bytesFreed, | ||||||
|  |     unsigned long long & blocksFreed); | ||||||
| 
 | 
 | ||||||
| /* Make a path read-only recursively. */ | /* Make a path read-only recursively. */ | ||||||
| void makePathReadOnly(const Path & path); | void makePathReadOnly(const Path & path); | ||||||
|  |  | ||||||
|  | @ -489,19 +489,19 @@ static void opCheckValidity(Strings opFlags, Strings opArgs) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| static string showBytes(unsigned long long bytes) | static string showBytes(unsigned long long bytes, unsigned long long blocks) | ||||||
| { | { | ||||||
|     return (format("%d bytes (%.2f MiB)") |     return (format("%d bytes (%.2f MiB, %d blocks)") | ||||||
|         % bytes % (bytes / (1024.0 * 1024.0))).str(); |         % bytes % (bytes / (1024.0 * 1024.0)) % blocks).str(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| struct PrintFreed  | struct PrintFreed  | ||||||
| { | { | ||||||
|     bool show, dryRun; |     bool show, dryRun; | ||||||
|     unsigned long long bytesFreed; |     const GCResults & results; | ||||||
|     PrintFreed(bool show, bool dryRun) |     PrintFreed(bool show, bool dryRun, const GCResults & results) | ||||||
|         : show(show), dryRun(dryRun), bytesFreed(0) { } |         : show(show), dryRun(dryRun), results(results) { } | ||||||
|     ~PrintFreed()  |     ~PrintFreed()  | ||||||
|     { |     { | ||||||
|         if (show) |         if (show) | ||||||
|  | @ -509,33 +509,35 @@ struct PrintFreed | ||||||
|                 (dryRun |                 (dryRun | ||||||
|                     ? "%1% would be freed\n" |                     ? "%1% would be freed\n" | ||||||
|                     : "%1% freed\n")) |                     : "%1% freed\n")) | ||||||
|                 % showBytes(bytesFreed); |                 % showBytes(results.bytesFreed, results.blocksFreed); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| static void opGC(Strings opFlags, Strings opArgs) | static void opGC(Strings opFlags, Strings opArgs) | ||||||
| { | { | ||||||
|     GCAction action = gcDeleteDead; |     GCOptions options; | ||||||
|  |     options.action = GCOptions::gcDeleteDead; | ||||||
|  |      | ||||||
|  |     GCResults results; | ||||||
|      |      | ||||||
|     /* Do what? */ |     /* Do what? */ | ||||||
|     for (Strings::iterator i = opFlags.begin(); |     for (Strings::iterator i = opFlags.begin(); | ||||||
|          i != opFlags.end(); ++i) |          i != opFlags.end(); ++i) | ||||||
|         if (*i == "--print-roots") action = gcReturnRoots; |         if (*i == "--print-roots") options.action = GCOptions::gcReturnRoots; | ||||||
|         else if (*i == "--print-live") action = gcReturnLive; |         else if (*i == "--print-live") options.action = GCOptions::gcReturnLive; | ||||||
|         else if (*i == "--print-dead") action = gcReturnDead; |         else if (*i == "--print-dead") options.action = GCOptions::gcReturnDead; | ||||||
|         else if (*i == "--delete") action = gcDeleteDead; |         else if (*i == "--delete") options.action = GCOptions::gcDeleteDead; | ||||||
|         else throw UsageError(format("bad sub-operation `%1%' in GC") % *i); |         else throw UsageError(format("bad sub-operation `%1%' in GC") % *i); | ||||||
| 
 | 
 | ||||||
|     PathSet result; |     PrintFreed freed( | ||||||
|     PrintFreed freed(action == gcDeleteDead || action == gcReturnDead, |         options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcReturnDead, | ||||||
|         action == gcReturnDead); |         options.action == GCOptions::gcReturnDead, results); | ||||||
|     store->collectGarbage(action, PathSet(), false, result, freed.bytesFreed); |     store->collectGarbage(options, results); | ||||||
| 
 | 
 | ||||||
|     if (action != gcDeleteDead) { |     if (options.action != GCOptions::gcDeleteDead) | ||||||
|         for (PathSet::iterator i = result.begin(); i != result.end(); ++i) |         foreach (PathSet::iterator, i, results.paths) | ||||||
|             cout << *i << std::endl; |             cout << *i << std::endl; | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -544,22 +546,21 @@ static void opGC(Strings opFlags, Strings opArgs) | ||||||
|    roots). */ |    roots). */ | ||||||
| static void opDelete(Strings opFlags, Strings opArgs) | static void opDelete(Strings opFlags, Strings opArgs) | ||||||
| { | { | ||||||
|     bool ignoreLiveness = false; |     GCOptions options; | ||||||
|  |     options.action = GCOptions::gcDeleteSpecific; | ||||||
|      |      | ||||||
|     for (Strings::iterator i = opFlags.begin(); |     for (Strings::iterator i = opFlags.begin(); | ||||||
|          i != opFlags.end(); ++i) |          i != opFlags.end(); ++i) | ||||||
|         if (*i == "--ignore-liveness") ignoreLiveness = true; |         if (*i == "--ignore-liveness") options.ignoreLiveness = true; | ||||||
|         else throw UsageError(format("unknown flag `%1%'") % *i); |         else throw UsageError(format("unknown flag `%1%'") % *i); | ||||||
| 
 | 
 | ||||||
|     PathSet pathsToDelete; |  | ||||||
|     for (Strings::iterator i = opArgs.begin(); |     for (Strings::iterator i = opArgs.begin(); | ||||||
|          i != opArgs.end(); ++i) |          i != opArgs.end(); ++i) | ||||||
|         pathsToDelete.insert(followLinksToStorePath(*i)); |         options.pathsToDelete.insert(followLinksToStorePath(*i)); | ||||||
|      |      | ||||||
|     PathSet dummy; |     GCResults results; | ||||||
|     PrintFreed freed(true, false); |     PrintFreed freed(true, false, results); | ||||||
|     store->collectGarbage(gcDeleteSpecific, pathsToDelete, ignoreLiveness, |     store->collectGarbage(options, results); | ||||||
|         dummy, freed.bytesFreed); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -653,7 +654,7 @@ static void showOptimiseStats(OptimiseStats & stats) | ||||||
| { | { | ||||||
|     printMsg(lvlError, |     printMsg(lvlError, | ||||||
|         format("%1% freed by hard-linking %2% files; there are %3% files with equal contents out of %4% files in total") |         format("%1% freed by hard-linking %2% files; there are %3% files with equal contents out of %4% files in total") | ||||||
|         % showBytes(stats.bytesFreed) |         % showBytes(stats.bytesFreed, stats.blocksFreed) | ||||||
|         % stats.filesLinked |         % stats.filesLinked | ||||||
|         % stats.sameContents |         % stats.sameContents | ||||||
|         % stats.totalFiles); |         % stats.totalFiles); | ||||||
|  |  | ||||||
|  | @ -395,23 +395,24 @@ static void performOp(unsigned int clientVersion, | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     case wopCollectGarbage: { |     case wopCollectGarbage: { | ||||||
|         GCAction action = (GCAction) readInt(from); |         GCOptions options; | ||||||
|         PathSet pathsToDelete = readStorePaths(from); |         options.action = (GCOptions::GCAction) readInt(from); | ||||||
|         bool ignoreLiveness = readInt(from); |         options.pathsToDelete = readStorePaths(from); | ||||||
|  |         options.ignoreLiveness = readInt(from); | ||||||
|  |         options.maxFreed = readLongLong(from); | ||||||
|  |         options.maxLinks = readInt(from); | ||||||
| 
 | 
 | ||||||
|         PathSet result; |         GCResults results; | ||||||
|         unsigned long long bytesFreed; |  | ||||||
|          |          | ||||||
|         startWork(); |         startWork(); | ||||||
|         if (ignoreLiveness) |         if (options.ignoreLiveness) | ||||||
|             throw Error("you are not allowed to ignore liveness"); |             throw Error("you are not allowed to ignore liveness"); | ||||||
|         store->collectGarbage(action, pathsToDelete, ignoreLiveness, |         store->collectGarbage(options, results); | ||||||
|             result, bytesFreed); |  | ||||||
|         stopWork(); |         stopWork(); | ||||||
|          |          | ||||||
|         writeStringSet(result, to); |         writeStringSet(results.paths, to); | ||||||
|         writeInt(bytesFreed & 0xffffffff, to); |         writeLongLong(results.bytesFreed, to); | ||||||
|         writeInt(bytesFreed >> 32, to); |         writeLongLong(results.blocksFreed, to); | ||||||
|          |          | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue