diff --git a/doc/manual/nix-collect-garbage.xml b/doc/manual/nix-collect-garbage.xml index adc6c1e730..1de50408e0 100644 --- a/doc/manual/nix-collect-garbage.xml +++ b/doc/manual/nix-collect-garbage.xml @@ -11,6 +11,7 @@ + age @@ -56,6 +57,16 @@ + + age + + + This option corresponds to the + option in nix-store . + + + + diff --git a/doc/manual/nix-store.xml b/doc/manual/nix-store.xml index febe02fd4a..522de60d36 100644 --- a/doc/manual/nix-store.xml +++ b/doc/manual/nix-store.xml @@ -147,6 +147,7 @@ + age @@ -207,6 +208,17 @@ Each line should contain exactly one store path. + + The option specifies a minimum time + in hours that an unreachable store path must not have been + used before it is considered dead. The default is 0 (consider + all unreachable store paths dead). Whether a store path has + been used is determined by looking at its access time + (atime), so this does not work if the store + is located on a file system that has the + noatime option set. + + You generally will want to use the command diff --git a/scripts/nix-collect-garbage.in b/scripts/nix-collect-garbage.in index 539979cbb6..44bcc16bbc 100755 --- a/scripts/nix-collect-garbage.in +++ b/scripts/nix-collect-garbage.in @@ -9,16 +9,24 @@ my $storeDir = "@storedir@"; my %alive; my $gcOper = "--delete"; -my $keepSuccessors = 1; +my $minAge = 0; my @roots = (); # Parse the command line. -foreach my $arg (@ARGV) { +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; - } else { die "unknown argument `$arg'" }; + } + elsif ($arg eq "--min-age") { + $i++; + $minAge = undef; + $minAge = $ARGV[$i]; + die "invalid minimum age" unless defined $minAge && $minAge =~ /^\d*$/; + } + else { die "unknown argument `$arg'" }; } @@ -69,7 +77,7 @@ findRoots 1, $rootsDir; # Run the collector with the roots we found. -my $pid = open2(">&1", \*WRITE, "@bindir@/nix-store --gc $gcOper") +my $pid = open2(">&1", \*WRITE, "@bindir@/nix-store --gc $gcOper --min-age $minAge") or die "cannot run `nix-store --gc'"; foreach my $root (@roots) { diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index aed7c2294b..9af957693f 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -2,6 +2,11 @@ #include "globals.hh" +#include +#include +#include + + void followLivePaths(Path nePath, PathSet & live) { /* Just to be sure, canonicalise the path. It is important to do @@ -62,16 +67,26 @@ PathSet findLivePaths(const Paths & roots) } -PathSet findDeadPaths(const PathSet & live) +PathSet findDeadPaths(const PathSet & live, time_t minAge) { PathSet dead; startNest(nest, lvlDebug, "finding dead paths"); + time_t now = time(0); + Strings storeNames = readDirectory(nixStore); for (Strings::iterator i = storeNames.begin(); i != storeNames.end(); ++i) { Path p = canonPath(nixStore + "/" + *i); + + if (minAge > 0) { + struct stat st; + if (lstat(p.c_str(), &st) != 0) + throw SysError(format("obtaining information about `%1%'") % p); + if (st.st_atime + minAge >= now) continue; + } + if (live.find(p) == live.end()) { debug(format("dead path `%1%'") % p); dead.insert(p); diff --git a/src/libstore/gc.hh b/src/libstore/gc.hh index 997057ba9b..1ada419da4 100644 --- a/src/libstore/gc.hh +++ b/src/libstore/gc.hh @@ -17,8 +17,10 @@ PathSet findLivePaths(const Paths & roots); /* Given a set of "live" store paths, determine the set of "dead" store paths (which are simply all store paths that are not in the - live set). */ -PathSet findDeadPaths(const PathSet & live); + live set). The value `minAge' specifies the minimum age in seconds + for an unreachable file to be considered dead (0 meaning that any + unreachable file is dead). */ +PathSet findDeadPaths(const PathSet & live, time_t minAge); #endif /* !__GC_H */ diff --git a/src/nix-store/main.cc b/src/nix-store/main.cc index 7bc8565d29..e9948c7cf8 100644 --- a/src/nix-store/main.cc +++ b/src/nix-store/main.cc @@ -212,17 +212,22 @@ static void opIsValid(Strings opFlags, Strings opArgs) static void opGC(Strings opFlags, Strings opArgs) { - if (opFlags.size() != 1) throw UsageError("missing flag"); - if (!opArgs.empty()) - throw UsageError("no arguments expected"); - /* Do what? */ - string flag = opFlags.front(); enum { soPrintLive, soPrintDead, soDelete } subOp; - if (flag == "--print-live") subOp = soPrintLive; - else if (flag == "--print-dead") subOp = soPrintDead; - else if (flag == "--delete") subOp = soDelete; - else throw UsageError(format("bad sub-operation `%1%' in GC") % flag); + time_t minAge = 0; + for (Strings::iterator i = opFlags.begin(); + i != opFlags.end(); ++i) + if (*i == "--print-live") subOp = soPrintLive; + else if (*i == "--print-dead") subOp = soPrintDead; + else if (*i == "--delete") subOp = soDelete; + else if (*i == "--min-age") { + if (opArgs.size() == 0) + throw UsageError("`--min-age' requires an argument"); + istringstream st(opArgs.front()); + st >> minAge; + if (!st) throw Error("number expected"); + } + else throw UsageError(format("bad sub-operation `%1%' in GC") % *i); Paths roots; while (1) { @@ -240,7 +245,7 @@ static void opGC(Strings opFlags, Strings opArgs) return; } - PathSet dead = findDeadPaths(live); + PathSet dead = findDeadPaths(live, minAge * 3600); if (subOp == soPrintDead) { for (PathSet::iterator i = dead.begin(); i != dead.end(); ++i)