forked from lix-project/lix
* Move root finding from nix-collect-garbage' to
nix-store --gc'.
This was necessary becase root finding must be done after acquisition of the global GC lock. This makes `nix-collect-garbage' obsolete; it is now just a wrapper around `nix-store --gc'. * Automatically remove stale GC roots (i.e., indirect GC roots that point to non-existent paths).
This commit is contained in:
parent
630ae0c9d7
commit
65b6c8ab4c
|
@ -1,83 +1,2 @@
|
||||||
#! @perl@ -w
|
#! @shell@ -e
|
||||||
|
exec @bindir@/nix-store --gc "$@"
|
||||||
use strict;
|
|
||||||
use IPC::Open2;
|
|
||||||
|
|
||||||
my $rootsDir = "@localstatedir@/nix/gcroots";
|
|
||||||
my $storeDir = "@storedir@";
|
|
||||||
|
|
||||||
my %alive;
|
|
||||||
|
|
||||||
my $gcOper = "--delete";
|
|
||||||
my $extraArgs = "";
|
|
||||||
|
|
||||||
my @roots = ();
|
|
||||||
|
|
||||||
|
|
||||||
# Parse the command line.
|
|
||||||
for (my $i = 0; $i < scalar @ARGV; $i++) {
|
|
||||||
my $arg = $ARGV[$i];
|
|
||||||
if ($arg eq "--delete" || $arg eq "--print-live" || $arg eq "--print-dead") {
|
|
||||||
$gcOper = $arg;
|
|
||||||
}
|
|
||||||
elsif ($arg =~ /^-v+$/) {
|
|
||||||
$extraArgs = "$extraArgs $arg";
|
|
||||||
}
|
|
||||||
else { die "unknown argument `$arg'" };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Recursively finds all symlinks to the store in the given directory.
|
|
||||||
sub findRoots;
|
|
||||||
sub findRoots {
|
|
||||||
my $followSymlinks = shift;
|
|
||||||
my $dir = shift;
|
|
||||||
|
|
||||||
opendir(DIR, $dir) or die "cannot open directory `$dir': $!";
|
|
||||||
my @names = readdir DIR or die "cannot read directory `$dir': $!";
|
|
||||||
closedir DIR;
|
|
||||||
|
|
||||||
foreach my $name (@names) {
|
|
||||||
next if $name eq "." || $name eq "..";
|
|
||||||
my $path = $dir . "/" . $name;
|
|
||||||
|
|
||||||
if (-l $path) {
|
|
||||||
my $target = readlink $path
|
|
||||||
or die "cannot read symlink `$path': $!";
|
|
||||||
|
|
||||||
if (substr($target, 0, length $storeDir) eq $storeDir) {
|
|
||||||
# We're only interested in the store-level part.
|
|
||||||
$target = substr($target, length $storeDir);
|
|
||||||
$target = "$storeDir/$target";
|
|
||||||
push @roots, $target;
|
|
||||||
}
|
|
||||||
|
|
||||||
elsif ($followSymlinks && -d $path) {
|
|
||||||
findRoots 0, $path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
elsif (-d $path) {
|
|
||||||
findRoots $followSymlinks, $path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Find GC roots, starting at $rootsDir.
|
|
||||||
findRoots 1, $rootsDir;
|
|
||||||
|
|
||||||
|
|
||||||
# Run the collector with the roots we found.
|
|
||||||
my $pid = open2(">&1", \*WRITE, "@bindir@/nix-store --gc $gcOper $extraArgs")
|
|
||||||
or die "cannot run `nix-store --gc'";
|
|
||||||
|
|
||||||
foreach my $root (@roots) {
|
|
||||||
print WRITE "$root\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
close WRITE;
|
|
||||||
|
|
||||||
waitpid $pid, 0;
|
|
||||||
$? == 0 or die "`nix-store --gc' failed";
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ static int openGCLock(LockType lockType)
|
||||||
Path fnGCLock = (format("%1%/%2%")
|
Path fnGCLock = (format("%1%/%2%")
|
||||||
% nixStateDir % gcLockName).str();
|
% nixStateDir % gcLockName).str();
|
||||||
|
|
||||||
|
debug(format("acquiring global GC lock `%1%'") % fnGCLock);
|
||||||
|
|
||||||
AutoCloseFD fdGCLock = open(fnGCLock.c_str(), O_RDWR | O_CREAT, 0600);
|
AutoCloseFD fdGCLock = open(fnGCLock.c_str(), O_RDWR | O_CREAT, 0600);
|
||||||
if (fdGCLock == -1)
|
if (fdGCLock == -1)
|
||||||
throw SysError(format("opening global GC lock `%1%'") % fnGCLock);
|
throw SysError(format("opening global GC lock `%1%'") % fnGCLock);
|
||||||
|
@ -234,6 +236,46 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void findRoots(const Path & path, bool recurseSymlinks,
|
||||||
|
PathSet & roots)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
if (lstat(path.c_str(), &st) == -1)
|
||||||
|
throw SysError(format("statting `%1%'") % path);
|
||||||
|
|
||||||
|
printMsg(lvlVomit, format("looking at `%1%'") % path);
|
||||||
|
|
||||||
|
if (S_ISDIR(st.st_mode)) {
|
||||||
|
Strings names = readDirectory(path);
|
||||||
|
for (Strings::iterator i = names.begin(); i != names.end(); ++i)
|
||||||
|
findRoots(path + "/" + *i, recurseSymlinks, roots);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (S_ISLNK(st.st_mode)) {
|
||||||
|
string target = readLink(path);
|
||||||
|
Path target2 = absPath(target, dirOf(path));
|
||||||
|
|
||||||
|
if (isStorePath(target2)) {
|
||||||
|
debug(format("found root `%1%' in `%2%'")
|
||||||
|
% target2 % path);
|
||||||
|
roots.insert(target2);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (recurseSymlinks) {
|
||||||
|
if (pathExists(target2))
|
||||||
|
findRoots(target2, false, roots);
|
||||||
|
else {
|
||||||
|
printMsg(lvlInfo, format("removing stale link from `%1%' to `%2%'") % path % target2);
|
||||||
|
/* Note that we only delete when recursing, i.e., when
|
||||||
|
we are still in the `gcroots' tree. We never
|
||||||
|
delete stuff outside that tree. */
|
||||||
|
unlink(path.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void dfsVisit(const PathSet & paths, const Path & path,
|
static void dfsVisit(const PathSet & paths, const Path & path,
|
||||||
PathSet & visited, Paths & sorted)
|
PathSet & visited, Paths & sorted)
|
||||||
{
|
{
|
||||||
|
@ -265,8 +307,7 @@ static Paths topoSort(const PathSet & paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void collectGarbage(const PathSet & roots, GCAction action,
|
void collectGarbage(GCAction action, PathSet & result)
|
||||||
PathSet & result)
|
|
||||||
{
|
{
|
||||||
result.clear();
|
result.clear();
|
||||||
|
|
||||||
|
@ -275,8 +316,16 @@ void collectGarbage(const PathSet & roots, GCAction action,
|
||||||
b) Processes from creating new temporary root files. */
|
b) Processes from creating new temporary root files. */
|
||||||
AutoCloseFD fdGCLock = openGCLock(ltWrite);
|
AutoCloseFD fdGCLock = openGCLock(ltWrite);
|
||||||
|
|
||||||
/* !!! Find the roots here, after we've grabbed the GC lock, since
|
/* Find the roots. Since we've grabbed the GC lock, the set of
|
||||||
the set of permanent roots cannot increase now. */
|
permanent roots cannot increase now. */
|
||||||
|
Path rootsDir = canonPath((format("%1%/%2%") % nixStateDir % gcRootsDir).str());
|
||||||
|
PathSet roots;
|
||||||
|
findRoots(rootsDir, true, roots);
|
||||||
|
|
||||||
|
if (action == gcReturnRoots) {
|
||||||
|
result = roots;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Determine the live paths which is just the closure of the
|
/* Determine the live paths which is just the closure of the
|
||||||
roots under the `references' relation. */
|
roots under the `references' relation. */
|
||||||
|
|
|
@ -5,15 +5,21 @@
|
||||||
|
|
||||||
|
|
||||||
/* Garbage collector operation. */
|
/* Garbage collector operation. */
|
||||||
typedef enum { gcReturnLive, gcReturnDead, gcDeleteDead } GCAction;
|
typedef enum {
|
||||||
|
gcReturnRoots,
|
||||||
|
gcReturnLive,
|
||||||
|
gcReturnDead,
|
||||||
|
gcDeleteDead,
|
||||||
|
} GCAction;
|
||||||
|
|
||||||
/* If `action' is set to `soReturnLive', return the set of paths
|
/* If `action' is set to `gcReturnRoots', find and return the set of
|
||||||
reachable from (i.e. in the closure of) the specified roots. If
|
roots for the garbage collector. These are the store paths
|
||||||
`action' is `soReturnDead', return the set of paths not reachable
|
symlinked to in the `gcroots' directory. If `action' is
|
||||||
from the roots. If `action' is `soDeleteDead', actually delete the
|
`gcReturnLive', return the set of paths reachable from (i.e. in the
|
||||||
latter set. */
|
closure of) the roots. If `action' is `gcReturnDead', return the
|
||||||
void collectGarbage(const PathSet & roots, GCAction action,
|
set of paths not reachable from the roots. If `action' is
|
||||||
PathSet & result);
|
`gcDeleteDead', actually delete the latter set. */
|
||||||
|
void collectGarbage(GCAction action, PathSet & result);
|
||||||
|
|
||||||
/* Register a temporary GC root. This root will automatically
|
/* Register a temporary GC root. This root will automatically
|
||||||
disappear when this process exits. WARNING: this function should
|
disappear when this process exits. WARNING: this function should
|
||||||
|
|
|
@ -38,9 +38,7 @@ static Path followSymlinks(Path & path)
|
||||||
while (!isStorePath(path)) {
|
while (!isStorePath(path)) {
|
||||||
if (!isLink(path)) return path;
|
if (!isLink(path)) return path;
|
||||||
string target = readLink(path);
|
string target = readLink(path);
|
||||||
path = canonPath(string(target, 0, 1) == "/"
|
path = absPath(target, dirOf(path));
|
||||||
? target
|
|
||||||
: path + "/" + target);
|
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
@ -308,27 +306,19 @@ static void opIsValid(Strings opFlags, Strings opArgs)
|
||||||
|
|
||||||
static void opGC(Strings opFlags, Strings opArgs)
|
static void opGC(Strings opFlags, Strings opArgs)
|
||||||
{
|
{
|
||||||
GCAction action;
|
GCAction action = gcDeleteDead;
|
||||||
|
|
||||||
/* 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-live") action = gcReturnLive;
|
if (*i == "--print-roots") action = gcReturnRoots;
|
||||||
|
else if (*i == "--print-live") action = gcReturnLive;
|
||||||
else if (*i == "--print-dead") action = gcReturnDead;
|
else if (*i == "--print-dead") action = gcReturnDead;
|
||||||
else if (*i == "--delete") action = gcDeleteDead;
|
else if (*i == "--delete") action = gcDeleteDead;
|
||||||
else throw UsageError(format("bad sub-operation `%1%' in GC") % *i);
|
else throw UsageError(format("bad sub-operation `%1%' in GC") % *i);
|
||||||
|
|
||||||
/* Read the roots. */
|
|
||||||
PathSet roots;
|
|
||||||
while (1) {
|
|
||||||
Path root;
|
|
||||||
getline(cin, root);
|
|
||||||
if (cin.eof()) break;
|
|
||||||
roots.insert(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
PathSet result;
|
PathSet result;
|
||||||
collectGarbage(roots, action, result);
|
collectGarbage(action, result);
|
||||||
|
|
||||||
if (action != gcDeleteDead) {
|
if (action != gcDeleteDead) {
|
||||||
for (PathSet::iterator i = result.begin(); i != result.end(); ++i)
|
for (PathSet::iterator i = result.begin(); i != result.end(); ++i)
|
||||||
|
|
Loading…
Reference in a new issue