From 65b6c8ab4c7832abdad46a29ce2ef18d289b2471 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 1 Feb 2005 15:05:32 +0000 Subject: [PATCH] * 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). --- scripts/nix-collect-garbage.in | 85 +--------------------------------- src/libstore/gc.cc | 59 +++++++++++++++++++++-- src/libstore/gc.hh | 22 +++++---- src/nix-store/main.cc | 20 ++------ 4 files changed, 75 insertions(+), 111 deletions(-) diff --git a/scripts/nix-collect-garbage.in b/scripts/nix-collect-garbage.in index c92737f25c..7ec9f349b1 100644 --- a/scripts/nix-collect-garbage.in +++ b/scripts/nix-collect-garbage.in @@ -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 "$@" diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index 8385e31b1c..323acf2651 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -26,7 +26,9 @@ 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. */ diff --git a/src/libstore/gc.hh b/src/libstore/gc.hh index e3da4505d5..b6a367c4b3 100644 --- a/src/libstore/gc.hh +++ b/src/libstore/gc.hh @@ -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 diff --git a/src/nix-store/main.cc b/src/nix-store/main.cc index 3edcff7eee..d473475b82 100644 --- a/src/nix-store/main.cc +++ b/src/nix-store/main.cc @@ -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)