forked from lix-project/lix
* 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
|
@ -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…
Reference in a new issue