projects/guix·blob python-team · refs

785 lines · 26.2 KiB

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