Merge branch 'master' into core-updates
[jackhill/guix/guix.git] / nix / libstore / gc.cc
CommitLineData
36457566
LC
1#include "globals.hh"
2#include "misc.hh"
3#include "local-store.hh"
4
5#include <functional>
6#include <queue>
7#include <algorithm>
8
9#include <sys/types.h>
10#include <sys/stat.h>
11#include <errno.h>
12#include <fcntl.h>
13#include <unistd.h>
14
15
16namespace nix {
17
18
19static string gcLockName = "gc.lock";
20static string tempRootsDir = "temproots";
21static string gcRootsDir = "gcroots";
22
23
24/* Acquire the global GC lock. This is used to prevent new Nix
25 processes from starting after the temporary root files have been
26 read. To be precise: when they try to create a new temporary root
27 file, they will block until the garbage collector has finished /
28 yielded the GC lock. */
29int LocalStore::openGCLock(LockType lockType)
30{
31 Path fnGCLock = (format("%1%/%2%")
32 % settings.nixStateDir % gcLockName).str();
33
34 debug(format("acquiring global GC lock `%1%'") % fnGCLock);
35
36 AutoCloseFD fdGCLock = open(fnGCLock.c_str(), O_RDWR | O_CREAT, 0600);
37 if (fdGCLock == -1)
38 throw SysError(format("opening global GC lock `%1%'") % fnGCLock);
39 closeOnExec(fdGCLock);
40
41 if (!lockFile(fdGCLock, lockType, false)) {
42 printMsg(lvlError, format("waiting for the big garbage collector lock..."));
43 lockFile(fdGCLock, lockType, true);
44 }
45
46 /* !!! Restrict read permission on the GC root. Otherwise any
47 process that can open the file for reading can DoS the
48 collector. */
49
50 return fdGCLock.borrow();
51}
52
53
54static void makeSymlink(const Path & link, const Path & target)
55{
56 /* Create directories up to `gcRoot'. */
57 createDirs(dirOf(link));
58
59 /* Create the new symlink. */
60 Path tempLink = (format("%1%.tmp-%2%-%3%")
61 % link % getpid() % rand()).str();
62 createSymlink(target, tempLink);
63
64 /* Atomically replace the old one. */
65 if (rename(tempLink.c_str(), link.c_str()) == -1)
66 throw SysError(format("cannot rename `%1%' to `%2%'")
67 % tempLink % link);
68}
69
70
71void LocalStore::syncWithGC()
72{
73 AutoCloseFD fdGCLock = openGCLock(ltRead);
74}
75
76
77void LocalStore::addIndirectRoot(const Path & path)
78{
79 string hash = printHash32(hashString(htSHA1, path));
80 Path realRoot = canonPath((format("%1%/%2%/auto/%3%")
81 % settings.nixStateDir % gcRootsDir % hash).str());
82 makeSymlink(realRoot, path);
83}
84
85
86Path addPermRoot(StoreAPI & store, const Path & _storePath,
87 const Path & _gcRoot, bool indirect, bool allowOutsideRootsDir)
88{
89 Path storePath(canonPath(_storePath));
90 Path gcRoot(canonPath(_gcRoot));
91 assertStorePath(storePath);
92
93 if (isInStore(gcRoot))
94 throw Error(format(
95 "creating a garbage collector root (%1%) in the Nix store is forbidden "
96 "(are you running nix-build inside the store?)") % gcRoot);
97
98 if (indirect) {
99 /* Don't clobber the the link if it already exists and doesn't
100 point to the Nix store. */
101 if (pathExists(gcRoot) && (!isLink(gcRoot) || !isInStore(readLink(gcRoot))))
102 throw Error(format("cannot create symlink `%1%'; already exists") % gcRoot);
103 makeSymlink(gcRoot, storePath);
104 store.addIndirectRoot(gcRoot);
105 }
106
107 else {
108 if (!allowOutsideRootsDir) {
109 Path rootsDir = canonPath((format("%1%/%2%") % settings.nixStateDir % gcRootsDir).str());
110
111 if (string(gcRoot, 0, rootsDir.size() + 1) != rootsDir + "/")
112 throw Error(format(
113 "path `%1%' is not a valid garbage collector root; "
114 "it's not in the directory `%2%'")
115 % gcRoot % rootsDir);
116 }
117
118 makeSymlink(gcRoot, storePath);
119 }
120
121 /* Check that the root can be found by the garbage collector.
122 !!! This can be very slow on machines that have many roots.
123 Instead of reading all the roots, it would be more efficient to
124 check if the root is in a directory in or linked from the
125 gcroots directory. */
126 if (settings.checkRootReachability) {
127 Roots roots = store.findRoots();
128 if (roots.find(gcRoot) == roots.end())
129 printMsg(lvlError,
130 format(
131 "warning: `%1%' is not in a directory where the garbage collector looks for roots; "
132 "therefore, `%2%' might be removed by the garbage collector")
133 % gcRoot % storePath);
134 }
135
136 /* Grab the global GC root, causing us to block while a GC is in
137 progress. This prevents the set of permanent roots from
138 increasing while a GC is in progress. */
139 store.syncWithGC();
140
141 return gcRoot;
142}
143
144
145/* The file to which we write our temporary roots. */
146static Path fnTempRoots;
147static AutoCloseFD fdTempRoots;
148
149
150void LocalStore::addTempRoot(const Path & path)
151{
152 /* Create the temporary roots file for this process. */
153 if (fdTempRoots == -1) {
154
155 while (1) {
156 Path dir = (format("%1%/%2%") % settings.nixStateDir % tempRootsDir).str();
157 createDirs(dir);
158
159 fnTempRoots = (format("%1%/%2%")
160 % dir % getpid()).str();
161
162 AutoCloseFD fdGCLock = openGCLock(ltRead);
163
164 if (pathExists(fnTempRoots))
165 /* It *must* be stale, since there can be no two
166 processes with the same pid. */
167 unlink(fnTempRoots.c_str());
168
169 fdTempRoots = openLockFile(fnTempRoots, true);
170
171 fdGCLock.close();
172
173 debug(format("acquiring read lock on `%1%'") % fnTempRoots);
174 lockFile(fdTempRoots, ltRead, true);
175
176 /* Check whether the garbage collector didn't get in our
177 way. */
178 struct stat st;
179 if (fstat(fdTempRoots, &st) == -1)
180 throw SysError(format("statting `%1%'") % fnTempRoots);
181 if (st.st_size == 0) break;
182
183 /* The garbage collector deleted this file before we could
184 get a lock. (It won't delete the file after we get a
185 lock.) Try again. */
186 }
187
188 }
189
190 /* Upgrade the lock to a write lock. This will cause us to block
191 if the garbage collector is holding our lock. */
192 debug(format("acquiring write lock on `%1%'") % fnTempRoots);
193 lockFile(fdTempRoots, ltWrite, true);
194
195 string s = path + '\0';
196 writeFull(fdTempRoots, (const unsigned char *) s.data(), s.size());
197
198 /* Downgrade to a read lock. */
199 debug(format("downgrading to read lock on `%1%'") % fnTempRoots);
200 lockFile(fdTempRoots, ltRead, true);
201}
202
203
204void removeTempRoots()
205{
206 if (fdTempRoots != -1) {
207 fdTempRoots.close();
208 unlink(fnTempRoots.c_str());
209 }
210}
211
212
213/* Automatically clean up the temporary roots file when we exit. */
214struct RemoveTempRoots
215{
216 ~RemoveTempRoots()
217 {
218 removeTempRoots();
219 }
220};
221
222static RemoveTempRoots autoRemoveTempRoots __attribute__((unused));
223
224
225typedef std::shared_ptr<AutoCloseFD> FDPtr;
226typedef list<FDPtr> FDs;
227
228
229static void readTempRoots(PathSet & tempRoots, FDs & fds)
230{
231 /* Read the `temproots' directory for per-process temporary root
232 files. */
233 Strings tempRootFiles = readDirectory(
234 (format("%1%/%2%") % settings.nixStateDir % tempRootsDir).str());
235
236 foreach (Strings::iterator, i, tempRootFiles) {
237 Path path = (format("%1%/%2%/%3%") % settings.nixStateDir % tempRootsDir % *i).str();
238
239 debug(format("reading temporary root file `%1%'") % path);
240 FDPtr fd(new AutoCloseFD(open(path.c_str(), O_RDWR, 0666)));
241 if (*fd == -1) {
242 /* It's okay if the file has disappeared. */
243 if (errno == ENOENT) continue;
244 throw SysError(format("opening temporary roots file `%1%'") % path);
245 }
246
247 /* This should work, but doesn't, for some reason. */
248 //FDPtr fd(new AutoCloseFD(openLockFile(path, false)));
249 //if (*fd == -1) continue;
250
251 /* Try to acquire a write lock without blocking. This can
252 only succeed if the owning process has died. In that case
253 we don't care about its temporary roots. */
254 if (lockFile(*fd, ltWrite, false)) {
255 printMsg(lvlError, format("removing stale temporary roots file `%1%'") % path);
256 unlink(path.c_str());
257 writeFull(*fd, (const unsigned char *) "d", 1);
258 continue;
259 }
260
261 /* Acquire a read lock. This will prevent the owning process
262 from upgrading to a write lock, therefore it will block in
263 addTempRoot(). */
264 debug(format("waiting for read lock on `%1%'") % path);
265 lockFile(*fd, ltRead, true);
266
267 /* Read the entire file. */
268 string contents = readFile(*fd);
269
270 /* Extract the roots. */
271 string::size_type pos = 0, end;
272
273 while ((end = contents.find((char) 0, pos)) != string::npos) {
274 Path root(contents, pos, end - pos);
275 debug(format("got temporary root `%1%'") % root);
276 assertStorePath(root);
277 tempRoots.insert(root);
278 pos = end + 1;
279 }
280
281 fds.push_back(fd); /* keep open */
282 }
283}
284
285
286static void foundRoot(StoreAPI & store,
287 const Path & path, const Path & target, Roots & roots)
288{
289 Path storePath = toStorePath(target);
290 if (store.isValidPath(storePath))
291 roots[path] = storePath;
292 else
293 printMsg(lvlInfo, format("skipping invalid root from `%1%' to `%2%'") % path % storePath);
294}
295
296
297static void findRoots(StoreAPI & store, const Path & path, Roots & roots)
298{
299 try {
300
301 struct stat st = lstat(path);
302
303 if (S_ISDIR(st.st_mode)) {
304 Strings names = readDirectory(path);
305 foreach (Strings::iterator, i, names)
306 findRoots(store, path + "/" + *i, roots);
307 }
308
309 else if (S_ISLNK(st.st_mode)) {
310 Path target = readLink(path);
311 if (isInStore(target))
312 foundRoot(store, path, target, roots);
313
314 /* Handle indirect roots. */
315 else {
316 target = absPath(target, dirOf(path));
317 if (!pathExists(target)) {
318 if (isInDir(path, settings.nixStateDir + "/" + gcRootsDir + "/auto")) {
319 printMsg(lvlInfo, format("removing stale link from `%1%' to `%2%'") % path % target);
320 unlink(path.c_str());
321 }
322 } else {
323 struct stat st2 = lstat(target);
324 if (!S_ISLNK(st2.st_mode)) return;
325 Path target2 = readLink(target);
326 if (isInStore(target2)) foundRoot(store, target, target2, roots);
327 }
328 }
329 }
330
331 }
332
333 catch (SysError & e) {
334 /* We only ignore permanent failures. */
335 if (e.errNo == EACCES || e.errNo == ENOENT || e.errNo == ENOTDIR)
336 printMsg(lvlInfo, format("cannot read potential root `%1%'") % path);
337 else
338 throw;
339 }
340}
341
342
343Roots LocalStore::findRoots()
344{
345 Roots roots;
346
347 /* Process direct roots in {gcroots,manifests,profiles}. */
348 nix::findRoots(*this, settings.nixStateDir + "/" + gcRootsDir, roots);
349 nix::findRoots(*this, settings.nixStateDir + "/manifests", roots);
350 nix::findRoots(*this, settings.nixStateDir + "/profiles", roots);
351
352 return roots;
353}
354
355
356static void addAdditionalRoots(StoreAPI & store, PathSet & roots)
357{
358 Path rootFinder = getEnv("NIX_ROOT_FINDER",
359 settings.nixLibexecDir + "/guix/list-runtime-roots");
360
361 if (rootFinder.empty()) return;
362
363 debug(format("executing `%1%' to find additional roots") % rootFinder);
364
365 string result = runProgram(rootFinder);
366
367 StringSet paths = tokenizeString<StringSet>(result, "\n");
368
369 foreach (StringSet::iterator, i, paths) {
370 if (isInStore(*i)) {
371 Path path = toStorePath(*i);
372 if (roots.find(path) == roots.end() && store.isValidPath(path)) {
373 debug(format("got additional root `%1%'") % path);
374 roots.insert(path);
375 }
376 }
377 }
378}
379
380
381struct GCLimitReached { };
382
383
384struct LocalStore::GCState
385{
386 GCOptions options;
387 GCResults & results;
388 PathSet roots;
389 PathSet tempRoots;
390 PathSet dead;
391 PathSet alive;
392 bool gcKeepOutputs;
393 bool gcKeepDerivations;
394 unsigned long long bytesInvalidated;
395 Path trashDir;
396 bool shouldDelete;
397 GCState(GCResults & results_) : results(results_), bytesInvalidated(0) { }
398};
399
400
401bool LocalStore::isActiveTempFile(const GCState & state,
402 const Path & path, const string & suffix)
403{
404 return hasSuffix(path, suffix)
405 && state.tempRoots.find(string(path, 0, path.size() - suffix.size())) != state.tempRoots.end();
406}
407
408
409void LocalStore::deleteGarbage(GCState & state, const Path & path)
410{
411 unsigned long long bytesFreed;
412 deletePath(path, bytesFreed);
413 state.results.bytesFreed += bytesFreed;
414}
415
416
417void LocalStore::deletePathRecursive(GCState & state, const Path & path)
418{
419 checkInterrupt();
420
421 unsigned long long size = 0;
422
423 if (isValidPath(path)) {
424 PathSet referrers;
425 queryReferrers(path, referrers);
426 foreach (PathSet::iterator, i, referrers)
427 if (*i != path) deletePathRecursive(state, *i);
428 size = queryPathInfo(path).narSize;
429 invalidatePathChecked(path);
430 }
431
432 struct stat st;
433 if (lstat(path.c_str(), &st)) {
434 if (errno == ENOENT) return;
435 throw SysError(format("getting status of %1%") % path);
436 }
437
438 printMsg(lvlInfo, format("deleting `%1%'") % path);
439
440 state.results.paths.insert(path);
441
442 /* If the path is not a regular file or symlink, move it to the
443 trash directory. The move is to ensure that later (when we're
444 not holding the global GC lock) we can delete the path without
445 being afraid that the path has become alive again. Otherwise
446 delete it right away. */
447 if (S_ISDIR(st.st_mode)) {
448 // Estimate the amount freed using the narSize field. FIXME:
449 // if the path was not valid, need to determine the actual
450 // size.
451 state.bytesInvalidated += size;
452 // Mac OS X cannot rename directories if they are read-only.
453 if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1)
454 throw SysError(format("making `%1%' writable") % path);
455 Path tmp = state.trashDir + "/" + baseNameOf(path);
456 if (rename(path.c_str(), tmp.c_str()))
457 throw SysError(format("unable to rename `%1%' to `%2%'") % path % tmp);
458 } else
459 deleteGarbage(state, path);
460
461 if (state.results.bytesFreed + state.bytesInvalidated > state.options.maxFreed) {
462 printMsg(lvlInfo, format("deleted or invalidated more than %1% bytes; stopping") % state.options.maxFreed);
463 throw GCLimitReached();
464 }
465}
466
467
468bool LocalStore::canReachRoot(GCState & state, PathSet & visited, const Path & path)
469{
470 if (visited.find(path) != visited.end()) return false;
471
472 if (state.alive.find(path) != state.alive.end()) {
473 return true;
474 }
475
476 if (state.dead.find(path) != state.dead.end()) {
477 return false;
478 }
479
480 if (state.roots.find(path) != state.roots.end()) {
481 printMsg(lvlDebug, format("cannot delete `%1%' because it's a root") % path);
482 state.alive.insert(path);
483 return true;
484 }
485
486 visited.insert(path);
487
488 if (!isValidPath(path)) return false;
489
490 PathSet incoming;
491
492 /* Don't delete this path if any of its referrers are alive. */
493 queryReferrers(path, incoming);
494
495 /* If gc-keep-derivations is set and this is a derivation, then
496 don't delete the derivation if any of the outputs are alive. */
497 if (state.gcKeepDerivations && isDerivation(path)) {
498 PathSet outputs = queryDerivationOutputs(path);
499 foreach (PathSet::iterator, i, outputs)
500 if (isValidPath(*i) && queryDeriver(*i) == path)
501 incoming.insert(*i);
502 }
503
504 /* If gc-keep-outputs is set, then don't delete this path if there
505 are derivers of this path that are not garbage. */
506 if (state.gcKeepOutputs) {
507 PathSet derivers = queryValidDerivers(path);
508 foreach (PathSet::iterator, i, derivers)
509 incoming.insert(*i);
510 }
511
512 foreach (PathSet::iterator, i, incoming)
513 if (*i != path)
514 if (canReachRoot(state, visited, *i)) {
515 state.alive.insert(path);
516 return true;
517 }
518
519 return false;
520}
521
522
523void LocalStore::tryToDelete(GCState & state, const Path & path)
524{
525 checkInterrupt();
526
527 if (path == linksDir || path == state.trashDir) return;
528
529 startNest(nest, lvlDebug, format("considering whether to delete `%1%'") % path);
530
531 if (!isValidPath(path)) {
532 /* A lock file belonging to a path that we're building right
533 now isn't garbage. */
534 if (isActiveTempFile(state, path, ".lock")) return;
535
536 /* Don't delete .chroot directories for derivations that are
537 currently being built. */
538 if (isActiveTempFile(state, path, ".chroot")) return;
539 }
540
541 PathSet visited;
542
543 if (canReachRoot(state, visited, path)) {
544 printMsg(lvlDebug, format("cannot delete `%1%' because it's still reachable") % path);
545 } else {
546 /* No path we visited was a root, so everything is garbage.
547 But we only delete ‘path’ and its referrers here so that
548 ‘nix-store --delete’ doesn't have the unexpected effect of
549 recursing into derivations and outputs. */
550 state.dead.insert(visited.begin(), visited.end());
551 if (state.shouldDelete)
552 deletePathRecursive(state, path);
553 }
554}
555
556
557/* Unlink all files in /nix/store/.links that have a link count of 1,
558 which indicates that there are no other links and so they can be
559 safely deleted. FIXME: race condition with optimisePath(): we
560 might see a link count of 1 just before optimisePath() increases
561 the link count. */
562void LocalStore::removeUnusedLinks(const GCState & state)
563{
564 AutoCloseDir dir = opendir(linksDir.c_str());
565 if (!dir) throw SysError(format("opening directory `%1%'") % linksDir);
566
567 long long actualSize = 0, unsharedSize = 0;
568
569 struct dirent * dirent;
570 while (errno = 0, dirent = readdir(dir)) {
571 checkInterrupt();
572 string name = dirent->d_name;
573 if (name == "." || name == "..") continue;
574 Path path = linksDir + "/" + name;
575
576 struct stat st;
577 if (lstat(path.c_str(), &st) == -1)
578 throw SysError(format("statting `%1%'") % path);
579
580 if (st.st_nlink != 1) {
581 unsigned long long size = st.st_blocks * 512ULL;
582 actualSize += size;
583 unsharedSize += (st.st_nlink - 1) * size;
584 continue;
585 }
586
587 printMsg(lvlTalkative, format("deleting unused link `%1%'") % path);
588
589 if (unlink(path.c_str()) == -1)
590 throw SysError(format("deleting `%1%'") % path);
591
592 state.results.bytesFreed += st.st_blocks * 512;
593 }
594
595 struct stat st;
596 if (stat(linksDir.c_str(), &st) == -1)
597 throw SysError(format("statting `%1%'") % linksDir);
598 long long overhead = st.st_blocks * 512ULL;
599
600 printMsg(lvlInfo, format("note: currently hard linking saves %.2f MiB")
601 % ((unsharedSize - actualSize - overhead) / (1024.0 * 1024.0)));
602}
603
604
605void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
606{
607 GCState state(results);
608 state.options = options;
609 state.trashDir = settings.nixStore + "/trash";
610 state.gcKeepOutputs = settings.gcKeepOutputs;
611 state.gcKeepDerivations = settings.gcKeepDerivations;
612
613 /* Using `--ignore-liveness' with `--delete' can have unintended
614 consequences if `gc-keep-outputs' or `gc-keep-derivations' are
615 true (the garbage collector will recurse into deleting the
616 outputs or derivers, respectively). So disable them. */
617 if (options.action == GCOptions::gcDeleteSpecific && options.ignoreLiveness) {
618 state.gcKeepOutputs = false;
619 state.gcKeepDerivations = false;
620 }
621
622 state.shouldDelete = options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific;
623
624 /* Acquire the global GC root. This prevents
625 a) New roots from being added.
626 b) Processes from creating new temporary root files. */
627 AutoCloseFD fdGCLock = openGCLock(ltWrite);
628
629 /* Find the roots. Since we've grabbed the GC lock, the set of
630 permanent roots cannot increase now. */
631 printMsg(lvlError, format("finding garbage collector roots..."));
632 Roots rootMap = options.ignoreLiveness ? Roots() : findRoots();
633
634 foreach (Roots::iterator, i, rootMap) state.roots.insert(i->second);
635
636 /* Add additional roots returned by the program specified by the
637 NIX_ROOT_FINDER environment variable. This is typically used
638 to add running programs to the set of roots (to prevent them
639 from being garbage collected). */
640 if (!options.ignoreLiveness)
641 addAdditionalRoots(*this, state.roots);
642
643 /* Read the temporary roots. This acquires read locks on all
644 per-process temporary root files. So after this point no paths
645 can be added to the set of temporary roots. */
646 FDs fds;
647 readTempRoots(state.tempRoots, fds);
648 state.roots.insert(state.tempRoots.begin(), state.tempRoots.end());
649
650 /* After this point the set of roots or temporary roots cannot
651 increase, since we hold locks on everything. So everything
652 that is not reachable from `roots'. */
653
654 if (state.shouldDelete) {
655 if (pathExists(state.trashDir)) deleteGarbage(state, state.trashDir);
656 createDirs(state.trashDir);
657 }
658
659 /* Now either delete all garbage paths, or just the specified
660 paths (for gcDeleteSpecific). */
661
662 if (options.action == GCOptions::gcDeleteSpecific) {
663
664 foreach (PathSet::iterator, i, options.pathsToDelete) {
665 assertStorePath(*i);
666 tryToDelete(state, *i);
667 if (state.dead.find(*i) == state.dead.end())
668 throw Error(format("cannot delete path `%1%' since it is still alive") % *i);
669 }
670
671 } else if (options.maxFreed > 0) {
672
673 if (state.shouldDelete)
674 printMsg(lvlError, format("deleting garbage..."));
675 else
676 printMsg(lvlError, format("determining live/dead paths..."));
677
678 try {
679
680 AutoCloseDir dir = opendir(settings.nixStore.c_str());
681 if (!dir) throw SysError(format("opening directory `%1%'") % settings.nixStore);
682
683 /* Read the store and immediately delete all paths that
684 aren't valid. When using --max-freed etc., deleting
685 invalid paths is preferred over deleting unreachable
686 paths, since unreachable paths could become reachable
687 again. We don't use readDirectory() here so that GCing
688 can start faster. */
689 Paths entries;
690 struct dirent * dirent;
691 while (errno = 0, dirent = readdir(dir)) {
692 checkInterrupt();
693 string name = dirent->d_name;
694 if (name == "." || name == "..") continue;
695 Path path = settings.nixStore + "/" + name;
696 if (isValidPath(path))
697 entries.push_back(path);
698 else
699 tryToDelete(state, path);
700 }
701
702 dir.close();
703
704 /* Now delete the unreachable valid paths. Randomise the
705 order in which we delete entries to make the collector
706 less biased towards deleting paths that come
707 alphabetically first (e.g. /nix/store/000...). This
708 matters when using --max-freed etc. */
709 vector<Path> entries_(entries.begin(), entries.end());
710 random_shuffle(entries_.begin(), entries_.end());
711
712 foreach (vector<Path>::iterator, i, entries_)
713 tryToDelete(state, *i);
714
715 } catch (GCLimitReached & e) {
716 }
717 }
718
719 if (state.options.action == GCOptions::gcReturnLive) {
720 state.results.paths = state.alive;
721 return;
722 }
723
724 if (state.options.action == GCOptions::gcReturnDead) {
725 state.results.paths = state.dead;
726 return;
727 }
728
729 /* Allow other processes to add to the store from here on. */
730 fdGCLock.close();
731 fds.clear();
732
733 /* Delete the trash directory. */
734 printMsg(lvlInfo, format("deleting `%1%'") % state.trashDir);
735 deleteGarbage(state, state.trashDir);
736
737 /* Clean up the links directory. */
738 if (options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific) {
739 printMsg(lvlError, format("deleting unused links..."));
740 removeUnusedLinks(state);
741 }
742
743 /* While we're at it, vacuum the database. */
744 if (options.action == GCOptions::gcDeleteDead) vacuumDB();
745}
746
747
748}