about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2005-02-01T15·05+0000
committerEelco Dolstra <e.dolstra@tudelft.nl>2005-02-01T15·05+0000
commit65b6c8ab4c7832abdad46a29ce2ef18d289b2471 (patch)
tree3038d7ed1f60efdf2e2dad43cfec93023d7c699f
parent630ae0c9d7f65a2d6bef85a5194b4d704e54eded (diff)
* 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).

-rw-r--r--scripts/nix-collect-garbage.in85
-rw-r--r--src/libstore/gc.cc59
-rw-r--r--src/libstore/gc.hh24
-rw-r--r--src/nix-store/main.cc20
4 files changed, 76 insertions, 112 deletions
diff --git a/scripts/nix-collect-garbage.in b/scripts/nix-collect-garbage.in
index c92737f25c13..7ec9f349b18e 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 8385e31b1c36..323acf2651dc 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 e3da4505d5dd..b6a367c4b330 100644
--- a/src/libstore/gc.hh
+++ b/src/libstore/gc.hh
@@ -5,15 +5,21 @@
 
 
 /* Garbage collector operation. */
-typedef enum { 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);
+typedef enum {
+    gcReturnRoots,
+    gcReturnLive,
+    gcReturnDead,
+    gcDeleteDead,
+} GCAction;
+
+/* 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 3edcff7eeef0..d473475b821a 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)