* 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:
Eelco Dolstra 2005-02-01 15:05:32 +00:00
parent 630ae0c9d7
commit 65b6c8ab4c
4 changed files with 75 additions and 111 deletions

View file

@ -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";

View file

@ -26,7 +26,9 @@ 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. */

View file

@ -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

View file

@ -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)