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
|
||||
|
||||
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";
|
||||
#! @shell@ -e
|
||||
exec @bindir@/nix-store --gc "$@"
|
||||
|
|
|
@ -27,6 +27,8 @@ static int openGCLock(LockType lockType)
|
|||
Path fnGCLock = (format("%1%/%2%")
|
||||
% nixStateDir % gcLockName).str();
|
||||
|
||||
debug(format("acquiring global GC lock `%1%'") % fnGCLock);
|
||||
|
||||
AutoCloseFD fdGCLock = open(fnGCLock.c_str(), O_RDWR | O_CREAT, 0600);
|
||||
if (fdGCLock == -1)
|
||||
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,
|
||||
PathSet & visited, Paths & sorted)
|
||||
{
|
||||
|
@ -265,8 +307,7 @@ static Paths topoSort(const PathSet & paths)
|
|||
}
|
||||
|
||||
|
||||
void collectGarbage(const PathSet & roots, GCAction action,
|
||||
PathSet & result)
|
||||
void collectGarbage(GCAction action, PathSet & result)
|
||||
{
|
||||
result.clear();
|
||||
|
||||
|
@ -275,8 +316,16 @@ void collectGarbage(const PathSet & roots, GCAction action,
|
|||
b) Processes from creating new temporary root files. */
|
||||
AutoCloseFD fdGCLock = openGCLock(ltWrite);
|
||||
|
||||
/* !!! Find the roots here, after we've grabbed the GC lock, since
|
||||
the set of permanent roots cannot increase now. */
|
||||
/* Find the roots. Since we've grabbed the GC lock, the set of
|
||||
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
|
||||
roots under the `references' relation. */
|
||||
|
|
|
@ -5,15 +5,21 @@
|
|||
|
||||
|
||||
/* 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
|
||||
reachable from (i.e. in the closure of) the specified roots. If
|
||||
`action' is `soReturnDead', return the set of paths not reachable
|
||||
from the roots. If `action' is `soDeleteDead', actually delete the
|
||||
latter set. */
|
||||
void collectGarbage(const PathSet & roots, GCAction action,
|
||||
PathSet & result);
|
||||
/* If `action' is set to `gcReturnRoots', find and return the set of
|
||||
roots for the garbage collector. These are the store paths
|
||||
symlinked to in the `gcroots' directory. If `action' is
|
||||
`gcReturnLive', return the set of paths reachable from (i.e. in the
|
||||
closure of) the roots. If `action' is `gcReturnDead', return the
|
||||
set of paths not reachable from the roots. If `action' is
|
||||
`gcDeleteDead', actually delete the latter set. */
|
||||
void collectGarbage(GCAction action, PathSet & result);
|
||||
|
||||
/* Register a temporary GC root. This root will automatically
|
||||
disappear when this process exits. WARNING: this function should
|
||||
|
|
|
@ -38,9 +38,7 @@ static Path followSymlinks(Path & path)
|
|||
while (!isStorePath(path)) {
|
||||
if (!isLink(path)) return path;
|
||||
string target = readLink(path);
|
||||
path = canonPath(string(target, 0, 1) == "/"
|
||||
? target
|
||||
: path + "/" + target);
|
||||
path = absPath(target, dirOf(path));
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
@ -308,27 +306,19 @@ static void opIsValid(Strings opFlags, Strings opArgs)
|
|||
|
||||
static void opGC(Strings opFlags, Strings opArgs)
|
||||
{
|
||||
GCAction action;
|
||||
GCAction action = gcDeleteDead;
|
||||
|
||||
/* Do what? */
|
||||
for (Strings::iterator i = opFlags.begin();
|
||||
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 == "--delete") action = gcDeleteDead;
|
||||
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;
|
||||
collectGarbage(roots, action, result);
|
||||
collectGarbage(action, result);
|
||||
|
||||
if (action != gcDeleteDead) {
|
||||
for (PathSet::iterator i = result.begin(); i != result.end(); ++i)
|
||||
|
|
Loading…
Reference in a new issue