* 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
					
				
					 4 changed files with 75 additions and 111 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue