| 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 |
|
| 17 | namespace nix {
|
| 18 |
|
| 19 |
|
| 20 | static string gcLockName = "gc.lock";
|
| 21 | static string tempRootsDir = "temproots";
|
| 22 | static string gcRootsDir = "gcroots";
|
| 23 |
|
| 24 |
|
| 25 | |
| 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.
|
| 30 | int 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 | |
| 47 | process that can open the file for reading can DoS the
|
| 48 | collector.
|
| 49 |
|
| 50 | return fdGCLock.borrow();
|
| 51 | }
|
| 52 |
|
| 53 |
|
| 54 | static void makeSymlink(const Path & link, const Path & target)
|
| 55 | {
|
| 56 |
|
| 57 | createDirs(dirOf(link));
|
| 58 |
|
| 59 |
|
| 60 | Path tempLink = std::format("{}.tmp-{}-{}", link, getpid(), rand());
|
| 61 | createSymlink(target, tempLink);
|
| 62 |
|
| 63 |
|
| 64 | if (rename(tempLink.c_str(), link.c_str()) == -1)
|
| 65 | throw SysError(std::format("cannot rename `{}' to `{}'", tempLink, link));
|
| 66 | }
|
| 67 |
|
| 68 |
|
| 69 | void LocalStore::syncWithGC()
|
| 70 | {
|
| 71 | AutoCloseFD fdGCLock = openGCLock(ltRead);
|
| 72 | }
|
| 73 |
|
| 74 |
|
| 75 | void 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 |
|
| 84 | Path 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 | |
| 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 | |
| 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 | |
| 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 |
|
| 146 | void LocalStore::addTempRoot(const Path & path)
|
| 147 | {
|
| 148 |
|
| 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 | |
| 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 | |
| 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 | |
| 179 | get a lock. (It won't delete the file after we get a
|
| 180 | lock.) Try again.
|
| 181 | }
|
| 182 |
|
| 183 | }
|
| 184 |
|
| 185 | |
| 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 |
|
| 194 | debug(std::format("downgrading to read lock on `{}'", fnTempRoots));
|
| 195 | lockFile(fdTempRoots, ltRead, true);
|
| 196 | }
|
| 197 |
|
| 198 |
|
| 199 | typedef std::shared_ptr<AutoCloseFD> FDPtr;
|
| 200 | typedef list<FDPtr> FDs;
|
| 201 |
|
| 202 |
|
| 203 | static void readTempRoots(PathSet & tempRoots, FDs & fds)
|
| 204 | {
|
| 205 | |
| 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 |
|
| 217 | if (errno == ENOENT) continue;
|
| 218 | throw SysError(std::format("opening temporary roots file `{}'", path));
|
| 219 | }
|
| 220 |
|
| 221 |
|
| 222 | |
| 223 | |
| 224 |
|
| 225 | |
| 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 | |
| 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 |
|
| 242 | string contents = readFile(*fd);
|
| 243 |
|
| 244 |
|
| 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);
|
| 256 | }
|
| 257 | }
|
| 258 |
|
| 259 |
|
| 260 | static 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 |
|
| 271 | static 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 |
|
| 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 |
|
| 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 |
|
| 324 | Roots LocalStore::findRoots()
|
| 325 | {
|
| 326 | Roots roots;
|
| 327 |
|
| 328 |
|
| 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 |
|
| 338 | static 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 |
|
| 360 | struct GCLimitReached { };
|
| 361 |
|
| 362 |
|
| 363 | struct 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 |
|
| 381 | bool 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 |
|
| 389 | void LocalStore::deleteGarbage(GCState & state, const Path & path)
|
| 390 | {
|
| 391 | unsigned long long bytesFreed;
|
| 392 |
|
| 393 | |
| 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 |
|
| 404 | void 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 | |
| 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 | |
| 444 | |
| 445 | |
| 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 | |
| 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 |
|
| 472 | bool 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 |
|
| 497 | queryReferrers(path, incoming);
|
| 498 |
|
| 499 | |
| 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 | |
| 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 |
|
| 527 | void 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 | |
| 537 | now isn't garbage.
|
| 538 | if (isActiveTempFile(state, path, ".lock")) return;
|
| 539 |
|
| 540 | |
| 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 | |
| 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 | |
| 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.
|
| 566 | void 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 | |
| 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 | |
| 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 |
|
| 634 | void 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 | |
| 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 | |
| 654 | a) New roots from being added.
|
| 655 | b) Processes from creating new temporary root files.
|
| 656 | AutoCloseFD fdGCLock = openGCLock(ltWrite);
|
| 657 |
|
| 658 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 |
|
| 767 | fdGCLock.close();
|
| 768 | fds.clear();
|
| 769 |
|
| 770 |
|
| 771 | printMsg(lvlInfo, std::format("deleting `{}'", state.trashDir));
|
| 772 | deleteGarbage(state, state.trashDir);
|
| 773 |
|
| 774 |
|
| 775 | if (options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific) {
|
| 776 | printMsg(lvlError, "deleting unused links...");
|
| 777 | removeUnusedLinks(state);
|
| 778 | }
|
| 779 |
|
| 780 |
|
| 781 | |
| 782 | }
|
| 783 |
|
| 784 |
|
| 785 | }
|