lix/src/libstore/gc.cc

882 lines
28 KiB
C++
Raw Normal View History

#include "derivations.hh"
#include "globals.hh"
#include "local-store.hh"
#include "local-fs-store.hh"
#include "finally.hh"
#include <functional>
#include <queue>
#include <algorithm>
2016-07-20 18:00:36 +00:00
#include <regex>
#include <random>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
2016-07-20 18:00:36 +00:00
#include <climits>
namespace nix {
static string gcLockName = "gc.lock";
static string gcRootsDir = "gcroots";
/* Acquire the global GC lock. This is used to prevent new Nix
processes from starting after the temporary root files have been
read. To be precise: when they try to create a new temporary root
file, they will block until the garbage collector has finished /
yielded the GC lock. */
AutoCloseFD LocalStore::openGCLock(LockType lockType)
{
Path fnGCLock = (format("%1%/%2%")
% stateDir % gcLockName).str();
debug(format("acquiring global GC lock '%1%'") % fnGCLock);
2016-06-09 14:15:58 +00:00
AutoCloseFD fdGCLock = open(fnGCLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);
2016-07-11 19:44:44 +00:00
if (!fdGCLock)
throw SysError("opening global GC lock '%1%'", fnGCLock);
2016-07-11 19:44:44 +00:00
if (!lockFile(fdGCLock.get(), lockType, false)) {
2020-05-13 15:52:36 +00:00
printInfo("waiting for the big garbage collector lock...");
2016-07-11 19:44:44 +00:00
lockFile(fdGCLock.get(), lockType, true);
}
/* !!! Restrict read permission on the GC root. Otherwise any
process that can open the file for reading can DoS the
collector. */
return fdGCLock;
}
static void makeSymlink(const Path & link, const Path & target)
{
/* Create directories up to `gcRoot'. */
createDirs(dirOf(link));
/* Create the new symlink. */
Path tempLink = (format("%1%.tmp-%2%-%3%")
% link % getpid() % random()).str();
createSymlink(target, tempLink);
/* Atomically replace the old one. */
if (rename(tempLink.c_str(), link.c_str()) == -1)
throw SysError("cannot rename '%1%' to '%2%'",
tempLink , link);
}
void LocalStore::syncWithGC()
{
AutoCloseFD fdGCLock = openGCLock(ltRead);
}
void LocalStore::addIndirectRoot(const Path & path)
{
string hash = hashString(htSHA1, path).to_string(Base32, false);
Path realRoot = canonPath((format("%1%/%2%/auto/%3%")
% stateDir % gcRootsDir % hash).str());
makeSymlink(realRoot, path);
}
2020-09-03 09:26:36 +00:00
Path LocalFSStore::addPermRoot(const StorePath & storePath, const Path & _gcRoot)
{
Path gcRoot(canonPath(_gcRoot));
if (isInStore(gcRoot))
throw Error(
"creating a garbage collector root (%1%) in the Nix store is forbidden "
"(are you running nix-build inside the store?)", gcRoot);
2020-09-03 09:26:36 +00:00
/* Don't clobber the link if it already exists and doesn't
point to the Nix store. */
if (pathExists(gcRoot) && (!isLink(gcRoot) || !isInStore(readLink(gcRoot))))
throw Error("cannot create symlink '%1%'; already exists", gcRoot);
makeSymlink(gcRoot, printStorePath(storePath));
addIndirectRoot(gcRoot);
/* Grab the global GC root, causing us to block while a GC is in
progress. This prevents the set of permanent roots from
increasing while a GC is in progress. */
2016-02-11 15:14:42 +00:00
syncWithGC();
return gcRoot;
}
void LocalStore::addTempRoot(const StorePath & path)
{
auto state(_state.lock());
/* Create the temporary roots file for this process. */
2016-07-11 19:44:44 +00:00
if (!state->fdTempRoots) {
while (1) {
AutoCloseFD fdGCLock = openGCLock(ltRead);
if (pathExists(fnTempRoots))
2006-06-20 17:48:10 +00:00
/* It *must* be stale, since there can be no two
processes with the same pid. */
unlink(fnTempRoots.c_str());
2006-06-20 17:48:10 +00:00
state->fdTempRoots = openLockFile(fnTempRoots, true);
2016-07-11 19:44:44 +00:00
fdGCLock = -1;
debug(format("acquiring read lock on '%1%'") % fnTempRoots);
2016-07-11 19:44:44 +00:00
lockFile(state->fdTempRoots.get(), ltRead, true);
/* Check whether the garbage collector didn't get in our
way. */
struct stat st;
2016-07-11 19:44:44 +00:00
if (fstat(state->fdTempRoots.get(), &st) == -1)
throw SysError("statting '%1%'", fnTempRoots);
if (st.st_size == 0) break;
/* The garbage collector deleted this file before we could
get a lock. (It won't delete the file after we get a
lock.) Try again. */
}
}
/* Upgrade the lock to a write lock. This will cause us to block
if the garbage collector is holding our lock. */
debug(format("acquiring write lock on '%1%'") % fnTempRoots);
2016-07-11 19:44:44 +00:00
lockFile(state->fdTempRoots.get(), ltWrite, true);
string s = printStorePath(path) + '\0';
2016-07-11 19:44:44 +00:00
writeFull(state->fdTempRoots.get(), s);
/* Downgrade to a read lock. */
debug(format("downgrading to read lock on '%1%'") % fnTempRoots);
2016-07-11 19:44:44 +00:00
lockFile(state->fdTempRoots.get(), ltRead, true);
}
static std::string censored = "{censored}";
void LocalStore::findTempRoots(FDs & fds, Roots & tempRoots, bool censor)
{
/* Read the `temproots' directory for per-process temporary root
files. */
2017-09-14 13:02:52 +00:00
for (auto & i : readDirectory(tempRootsDir)) {
if (i.name[0] == '.') {
// Ignore hidden files. Some package managers (notably portage) create
// those to keep the directory alive.
continue;
}
Path path = tempRootsDir + "/" + i.name;
2017-09-14 13:02:52 +00:00
pid_t pid = std::stoi(i.name);
debug(format("reading temporary root file '%1%'") % path);
2016-06-09 14:15:58 +00:00
FDPtr fd(new AutoCloseFD(open(path.c_str(), O_CLOEXEC | O_RDWR, 0666)));
2016-07-11 19:44:44 +00:00
if (!*fd) {
/* It's okay if the file has disappeared. */
if (errno == ENOENT) continue;
throw SysError("opening temporary roots file '%1%'", path);
}
2006-06-20 17:48:10 +00:00
/* This should work, but doesn't, for some reason. */
//FDPtr fd(new AutoCloseFD(openLockFile(path, false)));
//if (*fd == -1) continue;
/* Try to acquire a write lock without blocking. This can
only succeed if the owning process has died. In that case
we don't care about its temporary roots. */
if (lockFile(fd->get(), ltWrite, false)) {
2020-05-13 15:52:36 +00:00
printInfo("removing stale temporary roots file '%1%'", path);
unlink(path.c_str());
writeFull(fd->get(), "d");
continue;
}
/* Acquire a read lock. This will prevent the owning process
from upgrading to a write lock, therefore it will block in
addTempRoot(). */
debug(format("waiting for read lock on '%1%'") % path);
lockFile(fd->get(), ltRead, true);
/* Read the entire file. */
2016-07-11 19:44:44 +00:00
string contents = readFile(fd->get());
/* Extract the roots. */
string::size_type pos = 0, end;
while ((end = contents.find((char) 0, pos)) != string::npos) {
Path root(contents, pos, end - pos);
2017-09-14 13:02:52 +00:00
debug("got temporary root '%s'", root);
tempRoots[parseStorePath(root)].emplace(censor ? censored : fmt("{temp:%d}", pid));
pos = end + 1;
}
fds.push_back(fd); /* keep open */
}
}
void LocalStore::findRoots(const Path & path, unsigned char type, Roots & roots)
{
auto foundRoot = [&](const Path & path, const Path & target) {
try {
auto storePath = toStorePath(target).first;
if (isValidPath(storePath))
roots[std::move(storePath)].emplace(path);
else
printInfo("skipping invalid root from '%1%' to '%2%'", path, target);
} catch (BadStorePath &) { }
};
try {
2014-10-03 20:37:51 +00:00
if (type == DT_UNKNOWN)
type = getFileType(path);
if (type == DT_DIR) {
for (auto & i : readDirectory(path))
findRoots(path + "/" + i.name, i.type, roots);
}
else if (type == DT_LNK) {
Path target = readLink(path);
if (isInStore(target))
foundRoot(path, target);
/* Handle indirect roots. */
else {
target = absPath(target, dirOf(path));
if (!pathExists(target)) {
if (isInDir(path, stateDir + "/" + gcRootsDir + "/auto")) {
printInfo(format("removing stale link from '%1%' to '%2%'") % path % target);
unlink(path.c_str());
}
} else {
struct stat st2 = lstat(target);
if (!S_ISLNK(st2.st_mode)) return;
Path target2 = readLink(target);
if (isInStore(target2)) foundRoot(target, target2);
}
}
}
else if (type == DT_REG) {
auto storePath = maybeParseStorePath(storeDir + "/" + std::string(baseNameOf(path)));
if (storePath && isValidPath(*storePath))
roots[std::move(*storePath)].emplace(path);
}
}
catch (SysError & e) {
/* We only ignore permanent failures. */
if (e.errNo == EACCES || e.errNo == ENOENT || e.errNo == ENOTDIR)
printInfo("cannot read potential root '%1%'", path);
else
throw;
}
}
void LocalStore::findRootsNoTemp(Roots & roots, bool censor)
{
/* Process direct roots in {gcroots,profiles}. */
findRoots(stateDir + "/" + gcRootsDir, DT_UNKNOWN, roots);
findRoots(stateDir + "/profiles", DT_UNKNOWN, roots);
/* Add additional roots returned by different platforms-specific
heuristics. This is typically used to add running programs to
the set of roots (to prevent them from being garbage collected). */
findRuntimeRoots(roots, censor);
}
Roots LocalStore::findRoots(bool censor)
{
2019-03-09 23:37:52 +00:00
Roots roots;
findRootsNoTemp(roots, censor);
FDs fds;
findTempRoots(fds, roots, censor);
return roots;
}
typedef std::unordered_map<Path, std::unordered_set<std::string>> UncheckedRoots;
static void readProcLink(const string & file, UncheckedRoots & roots)
{
2016-07-20 18:00:36 +00:00
/* 64 is the starting buffer size gnu readlink uses... */
auto bufsiz = ssize_t{64};
try_again:
char buf[bufsiz];
auto res = readlink(file.c_str(), buf, bufsiz);
if (res == -1) {
if (errno == ENOENT || errno == EACCES || errno == ESRCH)
2016-07-20 18:00:36 +00:00
return;
throw SysError("reading symlink");
}
if (res == bufsiz) {
if (SSIZE_MAX / 2 < bufsiz)
throw Error("stupidly long symlink");
bufsiz *= 2;
goto try_again;
}
if (res > 0 && buf[0] == '/')
roots[std::string(static_cast<char *>(buf), res)]
.emplace(file);
2016-07-20 18:00:36 +00:00
}
2016-07-20 18:00:36 +00:00
static string quoteRegexChars(const string & raw)
{
static auto specialRegex = std::regex(R"([.^$\\*+?()\[\]{}|])");
return std::regex_replace(raw, specialRegex, R"(\$&)");
}
static void readFileRoots(const char * path, UncheckedRoots & roots)
2016-07-20 18:00:36 +00:00
{
try {
roots[readFile(path)].emplace(path);
2016-07-20 18:00:36 +00:00
} catch (SysError & e) {
if (e.errNo != ENOENT && e.errNo != EACCES)
throw;
}
}
void LocalStore::findRuntimeRoots(Roots & roots, bool censor)
2016-07-20 18:00:36 +00:00
{
UncheckedRoots unchecked;
2019-02-27 22:32:12 +00:00
2016-07-20 18:00:36 +00:00
auto procDir = AutoCloseDir{opendir("/proc")};
if (procDir) {
struct dirent * ent;
auto digitsRegex = std::regex(R"(^\d+$)");
auto mapRegex = std::regex(R"(^\s*\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(/\S+)\s*$)");
auto storePathRegex = std::regex(quoteRegexChars(storeDir) + R"(/[0-9a-z]+[0-9a-zA-Z\+\-\._\?=]*)");
2017-01-16 21:39:27 +00:00
while (errno = 0, ent = readdir(procDir.get())) {
2016-07-20 18:00:36 +00:00
checkInterrupt();
if (std::regex_match(ent->d_name, digitsRegex)) {
readProcLink(fmt("/proc/%s/exe" ,ent->d_name), unchecked);
readProcLink(fmt("/proc/%s/cwd", ent->d_name), unchecked);
2016-07-20 18:00:36 +00:00
auto fdStr = fmt("/proc/%s/fd", ent->d_name);
2016-07-20 18:00:36 +00:00
auto fdDir = AutoCloseDir(opendir(fdStr.c_str()));
if (!fdDir) {
if (errno == ENOENT || errno == EACCES)
continue;
throw SysError("opening %1%", fdStr);
2016-07-20 18:00:36 +00:00
}
struct dirent * fd_ent;
2017-01-16 21:39:27 +00:00
while (errno = 0, fd_ent = readdir(fdDir.get())) {
if (fd_ent->d_name[0] != '.')
readProcLink(fmt("%s/%s", fdStr, fd_ent->d_name), unchecked);
2016-07-20 18:00:36 +00:00
}
if (errno) {
if (errno == ESRCH)
continue;
throw SysError("iterating /proc/%1%/fd", ent->d_name);
2016-07-20 18:00:36 +00:00
}
fdDir.reset();
2016-07-20 18:00:36 +00:00
try {
auto mapFile = fmt("/proc/%s/maps", ent->d_name);
auto mapLines = tokenizeString<std::vector<string>>(readFile(mapFile), "\n");
for (const auto & line : mapLines) {
auto match = std::smatch{};
if (std::regex_match(line, match, mapRegex))
unchecked[match[1]].emplace(mapFile);
}
auto envFile = fmt("/proc/%s/environ", ent->d_name);
auto envString = readFile(envFile);
2016-07-20 18:00:36 +00:00
auto env_end = std::sregex_iterator{};
for (auto i = std::sregex_iterator{envString.begin(), envString.end(), storePathRegex}; i != env_end; ++i)
unchecked[i->str()].emplace(envFile);
2016-07-20 18:00:36 +00:00
} catch (SysError & e) {
if (errno == ENOENT || errno == EACCES || errno == ESRCH)
2016-07-20 18:00:36 +00:00
continue;
throw;
}
}
}
if (errno)
throw SysError("iterating /proc");
}
#if !defined(__linux__)
// lsof is really slow on OS X. This actually causes the gc-concurrent.sh test to fail.
// See: https://github.com/NixOS/nix/issues/3011
// Because of this we disable lsof when running the tests.
if (getEnv("_NIX_TEST_NO_LSOF") != "1") {
try {
std::regex lsofRegex(R"(^n(/.*)$)");
auto lsofLines =
tokenizeString<std::vector<string>>(runProgram(LSOF, true, { "-n", "-w", "-F", "n" }), "\n");
for (const auto & line : lsofLines) {
std::smatch match;
if (std::regex_match(line, match, lsofRegex))
unchecked[match[1]].emplace("{lsof}");
}
} catch (ExecError & e) {
/* lsof not installed, lsof failed */
2016-07-20 18:00:36 +00:00
}
}
#endif
#if defined(__linux__)
2019-02-27 22:32:12 +00:00
readFileRoots("/proc/sys/kernel/modprobe", unchecked);
readFileRoots("/proc/sys/kernel/fbsplash", unchecked);
readFileRoots("/proc/sys/kernel/poweroff_cmd", unchecked);
#endif
for (auto & [target, links] : unchecked) {
if (!isInStore(target)) continue;
try {
auto path = toStorePath(target).first;
if (!isValidPath(path)) continue;
debug("got additional root '%1%'", printStorePath(path));
if (censor)
roots[path].insert(censored);
else
roots[path].insert(links.begin(), links.end());
} catch (BadStorePath &) { }
2019-02-27 22:32:12 +00:00
}
}
struct GCLimitReached { };
struct LocalStore::GCState
{
const GCOptions & options;
GCResults & results;
StorePathSet roots;
StorePathSet tempRoots;
StorePathSet dead;
StorePathSet alive;
bool gcKeepOutputs;
bool gcKeepDerivations;
bool shouldDelete;
GCState(const GCOptions & options, GCResults & results)
2021-08-16 11:52:19 +00:00
: options(options), results(results) { }
};
bool LocalStore::isActiveTempFile(const GCState & state,
const Path & path, const string & suffix)
{
return hasSuffix(path, suffix)
&& state.tempRoots.count(parseStorePath(string(path, 0, path.size() - suffix.size())));
}
void LocalStore::deleteGarbage(GCState & state, const Path & path)
{
2020-07-30 11:10:49 +00:00
uint64_t bytesFreed;
deletePath(path, bytesFreed);
state.results.bytesFreed += bytesFreed;
}
void LocalStore::deletePathRecursive(GCState & state, const Path & path)
{
checkInterrupt();
auto storePath = maybeParseStorePath(path);
if (storePath && isValidPath(*storePath)) {
StorePathSet referrers;
queryReferrers(*storePath, referrers);
2015-07-17 17:24:28 +00:00
for (auto & i : referrers)
if (printStorePath(i) != path) deletePathRecursive(state, printStorePath(i));
invalidatePathChecked(*storePath);
}
Path realPath = realStoreDir + "/" + std::string(baseNameOf(path));
struct stat st;
if (lstat(realPath.c_str(), &st)) {
if (errno == ENOENT) return;
throw SysError("getting status of %1%", realPath);
}
printInfo(format("deleting '%1%'") % path);
2013-01-04 14:17:19 +00:00
state.results.paths.insert(path);
2021-08-16 11:52:19 +00:00
deleteGarbage(state, realPath);
2021-08-16 11:52:19 +00:00
if (state.results.bytesFreed > state.options.maxFreed) {
printInfo("deleted more than %d bytes; stopping", state.options.maxFreed);
throw GCLimitReached();
}
}
bool LocalStore::canReachRoot(GCState & state, StorePathSet & visited, const StorePath & path)
{
if (visited.count(path)) return false;
if (state.alive.count(path)) return true;
if (state.dead.count(path)) return false;
if (state.roots.count(path)) {
debug("cannot delete '%1%' because it's a root", printStorePath(path));
state.alive.insert(path);
return true;
}
visited.insert(path);
if (!isValidPath(path)) return false;
StorePathSet incoming;
/* Don't delete this path if any of its referrers are alive. */
queryReferrers(path, incoming);
/* If keep-derivations is set and this is a derivation, then
don't delete the derivation if any of the outputs are alive. */
if (state.gcKeepDerivations && path.isDerivation()) {
for (auto & [name, maybeOutPath] : queryPartialDerivationOutputMap(path))
if (maybeOutPath &&
isValidPath(*maybeOutPath) &&
queryPathInfo(*maybeOutPath)->deriver == path
)
incoming.insert(*maybeOutPath);
}
/* If keep-outputs is set, then don't delete this path if there
are derivers of this path that are not garbage. */
if (state.gcKeepOutputs) {
auto derivers = queryValidDerivers(path);
2015-07-17 17:24:28 +00:00
for (auto & i : derivers)
incoming.insert(i);
}
2015-07-17 17:24:28 +00:00
for (auto & i : incoming)
if (i != path)
if (canReachRoot(state, visited, i)) {
state.alive.insert(path);
return true;
}
return false;
}
void LocalStore::tryToDelete(GCState & state, const Path & path)
{
checkInterrupt();
auto realPath = realStoreDir + "/" + std::string(baseNameOf(path));
2021-08-16 11:52:19 +00:00
if (realPath == linksDir) return;
//Activity act(*logger, lvlDebug, format("considering whether to delete '%1%'") % path);
auto storePath = maybeParseStorePath(path);
if (!storePath || !isValidPath(*storePath)) {
/* A lock file belonging to a path that we're building right
now isn't garbage. */
if (isActiveTempFile(state, path, ".lock")) return;
/* Don't delete .chroot directories for derivations that are
currently being built. */
if (isActiveTempFile(state, path, ".chroot")) return;
/* Don't delete .check directories for derivations that are
currently being built, because we may need to run
diff-hook. */
if (isActiveTempFile(state, path, ".check")) return;
}
StorePathSet visited;
if (storePath && canReachRoot(state, visited, *storePath)) {
debug("cannot delete '%s' because it's still reachable", path);
} else {
/* No path we visited was a root, so everything is garbage.
But we only delete path and its referrers here so that
nix-store --delete doesn't have the unexpected effect of
recursing into derivations and outputs. */
for (auto & i : visited)
state.dead.insert(i);
if (state.shouldDelete)
deletePathRecursive(state, path);
}
}
/* Unlink all files in /nix/store/.links that have a link count of 1,
which indicates that there are no other links and so they can be
safely deleted. FIXME: race condition with optimisePath(): we
might see a link count of 1 just before optimisePath() increases
the link count. */
void LocalStore::removeUnusedLinks(const GCState & state)
{
2017-01-16 21:39:27 +00:00
AutoCloseDir dir(opendir(linksDir.c_str()));
if (!dir) throw SysError("opening directory '%1%'", linksDir);
2020-07-30 11:10:49 +00:00
int64_t actualSize = 0, unsharedSize = 0;
struct dirent * dirent;
2017-01-16 21:39:27 +00:00
while (errno = 0, dirent = readdir(dir.get())) {
checkInterrupt();
string name = dirent->d_name;
if (name == "." || name == "..") continue;
Path path = linksDir + "/" + name;
2020-09-23 17:17:28 +00:00
auto st = lstat(path);
if (st.st_nlink != 1) {
actualSize += st.st_size;
unsharedSize += (st.st_nlink - 1) * st.st_size;
continue;
}
printMsg(lvlTalkative, format("deleting unused link '%1%'") % path);
if (unlink(path.c_str()) == -1)
throw SysError("deleting '%1%'", path);
state.results.bytesFreed += st.st_size;
}
struct stat st;
if (stat(linksDir.c_str(), &st) == -1)
throw SysError("statting '%1%'", linksDir);
int64_t overhead = st.st_blocks * 512ULL;
2020-07-30 11:10:49 +00:00
printInfo("note: currently hard linking saves %.2f MiB",
((unsharedSize - actualSize - overhead) / (1024.0 * 1024.0)));
}
void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
{
GCState state(options, results);
state.gcKeepOutputs = settings.gcKeepOutputs;
state.gcKeepDerivations = settings.gcKeepDerivations;
/* Using `--ignore-liveness' with `--delete' can have unintended
consequences if `keep-outputs' or `keep-derivations' are true
(the garbage collector will recurse into deleting the outputs
or derivers, respectively). So disable them. */
if (options.action == GCOptions::gcDeleteSpecific && options.ignoreLiveness) {
state.gcKeepOutputs = false;
state.gcKeepDerivations = false;
}
state.shouldDelete = options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific;
if (state.shouldDelete)
2016-02-24 16:33:53 +00:00
deletePath(reservedPath);
2021-08-16 11:52:19 +00:00
/* Acquire the global GC root. */
AutoCloseFD fdGCLock = openGCLock(ltWrite);
/* Find the roots. Since we've grabbed the GC lock, the set of
permanent roots cannot increase now. */
2020-05-13 15:52:36 +00:00
printInfo("finding garbage collector roots...");
2019-03-09 23:37:52 +00:00
Roots rootMap;
if (!options.ignoreLiveness)
findRootsNoTemp(rootMap, true);
for (auto & i : rootMap) state.roots.insert(i.first);
/* Read the temporary roots. This acquires read locks on all
per-process temporary root files. So after this point no paths
can be added to the set of temporary roots. */
FDs fds;
2019-03-09 23:37:52 +00:00
Roots tempRoots;
findTempRoots(fds, tempRoots, true);
for (auto & root : tempRoots) {
state.tempRoots.insert(root.first);
state.roots.insert(root.first);
}
/* After this point the set of roots or temporary roots cannot
increase, since we hold locks on everything. So everything
that is not reachable from `roots' is garbage. */
/* Now either delete all garbage paths, or just the specified
paths (for gcDeleteSpecific). */
if (options.action == GCOptions::gcDeleteSpecific) {
2015-07-17 17:24:28 +00:00
for (auto & i : options.pathsToDelete) {
tryToDelete(state, printStorePath(i));
2015-07-17 17:24:28 +00:00
if (state.dead.find(i) == state.dead.end())
throw Error(
"cannot delete path '%1%' since it is still alive. "
"To find out why use: "
"nix-store --query --roots",
printStorePath(i));
}
} else if (options.maxFreed > 0) {
if (state.shouldDelete)
2020-05-13 15:52:36 +00:00
printInfo("deleting garbage...");
else
2020-05-13 15:52:36 +00:00
printInfo("determining live/dead paths...");
try {
AutoCloseDir dir(opendir(realStoreDir.get().c_str()));
if (!dir) throw SysError("opening directory '%1%'", realStoreDir);
/* Read the store and immediately delete all paths that
aren't valid. When using --max-freed etc., deleting
invalid paths is preferred over deleting unreachable
paths, since unreachable paths could become reachable
again. We don't use readDirectory() here so that GCing
can start faster. */
Paths entries;
struct dirent * dirent;
2017-01-16 21:39:27 +00:00
while (errno = 0, dirent = readdir(dir.get())) {
checkInterrupt();
string name = dirent->d_name;
if (name == "." || name == "..") continue;
Path path = storeDir + "/" + name;
auto storePath = maybeParseStorePath(path);
if (storePath && isValidPath(*storePath))
entries.push_back(path);
else
tryToDelete(state, path);
}
2017-01-16 21:39:27 +00:00
dir.reset();
/* Now delete the unreachable valid paths. Randomise the
order in which we delete entries to make the collector
less biased towards deleting paths that come
alphabetically first (e.g. /nix/store/000...). This
matters when using --max-freed etc. */
vector<Path> entries_(entries.begin(), entries.end());
std::mt19937 gen(1);
std::shuffle(entries_.begin(), entries_.end(), gen);
2015-07-17 17:24:28 +00:00
for (auto & i : entries_)
tryToDelete(state, i);
} catch (GCLimitReached & e) {
}
}
if (state.options.action == GCOptions::gcReturnLive) {
for (auto & i : state.alive)
state.results.paths.insert(printStorePath(i));
return;
}
if (state.options.action == GCOptions::gcReturnDead) {
for (auto & i : state.dead)
state.results.paths.insert(printStorePath(i));
return;
}
/* Clean up the links directory. */
if (options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific) {
2020-05-13 15:52:36 +00:00
printInfo("deleting unused links...");
removeUnusedLinks(state);
}
/* While we're at it, vacuum the database. */
//if (options.action == GCOptions::gcDeleteDead) vacuumDB();
}
void LocalStore::autoGC(bool sync)
{
static auto fakeFreeSpaceFile = getEnv("_NIX_TEST_FREE_SPACE_FILE");
auto getAvail = [this]() -> uint64_t {
if (fakeFreeSpaceFile)
return std::stoll(readFile(*fakeFreeSpaceFile));
struct statvfs st;
if (statvfs(realStoreDir.get().c_str(), &st))
throw SysError("getting filesystem info about '%s'", realStoreDir);
2020-05-04 21:42:06 +00:00
return (uint64_t) st.f_bavail * st.f_frsize;
};
std::shared_future<void> future;
{
auto state(_state.lock());
if (state->gcRunning) {
future = state->gcFuture;
debug("waiting for auto-GC to finish");
goto sync;
}
auto now = std::chrono::steady_clock::now();
if (now < state->lastGCCheck + std::chrono::seconds(settings.minFreeCheckInterval)) return;
auto avail = getAvail();
state->lastGCCheck = now;
if (avail >= settings.minFree || avail >= settings.maxFree) return;
if (avail > state->availAfterGC * 0.97) return;
state->gcRunning = true;
std::promise<void> promise;
future = state->gcFuture = promise.get_future().share();
std::thread([promise{std::move(promise)}, this, avail, getAvail]() mutable {
try {
/* Wake up any threads waiting for the auto-GC to finish. */
Finally wakeup([&]() {
auto state(_state.lock());
state->gcRunning = false;
state->lastGCCheck = std::chrono::steady_clock::now();
promise.set_value();
});
GCOptions options;
options.maxFreed = settings.maxFree - avail;
2019-08-29 10:09:58 +00:00
printInfo("running auto-GC to free %d bytes", options.maxFreed);
GCResults results;
collectGarbage(options, results);
_state.lock()->availAfterGC = getAvail();
} catch (...) {
// FIXME: we could propagate the exception to the
// future, but we don't really care.
ignoreException();
}
}).detach();
}
sync:
// Wait for the future outside of the state lock.
if (sync) future.get();
}
}