2009-11-24 10:53:18 +01:00
|
|
|
|
#include <algorithm>
|
2020-05-20 05:33:07 +02:00
|
|
|
|
#include <cerrno>
|
2016-07-20 20:00:36 +02:00
|
|
|
|
#include <climits>
|
2008-09-17 12:02:55 +02:00
|
|
|
|
#include <functional>
|
|
|
|
|
#include <queue>
|
2018-06-13 16:56:19 +02:00
|
|
|
|
#include <random>
|
2016-07-20 20:00:36 +02:00
|
|
|
|
#include <regex>
|
2020-05-19 16:54:39 +02:00
|
|
|
|
|
2020-05-25 03:19:01 +02:00
|
|
|
|
#include <absl/strings/match.h>
|
2020-05-25 16:54:14 +02:00
|
|
|
|
#include <absl/strings/str_split.h>
|
2005-01-31 11:27:25 +01:00
|
|
|
|
#include <fcntl.h>
|
2020-05-27 22:56:34 +02:00
|
|
|
|
#include <glog/logging.h>
|
2004-08-25 18:54:08 +02:00
|
|
|
|
#include <sys/stat.h>
|
2017-09-05 20:43:42 +02:00
|
|
|
|
#include <sys/statvfs.h>
|
2004-08-25 18:54:08 +02:00
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <unistd.h>
|
2020-05-19 16:54:39 +02:00
|
|
|
|
|
2020-05-27 22:56:34 +02:00
|
|
|
|
#include "libstore/derivations.hh"
|
|
|
|
|
#include "libstore/globals.hh"
|
|
|
|
|
#include "libstore/local-store.hh"
|
|
|
|
|
#include "libutil/finally.hh"
|
2004-08-25 18:54:08 +02:00
|
|
|
|
|
2006-09-04 23:06:23 +02:00
|
|
|
|
namespace nix {
|
|
|
|
|
|
2020-05-24 23:29:21 +02:00
|
|
|
|
static std::string gcLockName = "gc.lock";
|
|
|
|
|
static std::string gcRootsDir = "gcroots";
|
2005-01-31 23:23:49 +01:00
|
|
|
|
|
|
|
|
|
/* 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. */
|
2018-10-04 15:03:03 +02:00
|
|
|
|
AutoCloseFD LocalStore::openGCLock(LockType lockType) {
|
2005-01-31 23:23:49 +01:00
|
|
|
|
Path fnGCLock = (format("%1%/%2%") % stateDir % gcLockName).str();
|
2012-07-31 01:55:41 +02:00
|
|
|
|
|
2020-05-19 02:02:44 +02:00
|
|
|
|
DLOG(INFO) << "acquiring global GC lock " << fnGCLock;
|
2012-07-31 01:55:41 +02:00
|
|
|
|
|
2016-06-09 16:15:58 +02:00
|
|
|
|
AutoCloseFD fdGCLock =
|
|
|
|
|
open(fnGCLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);
|
2020-05-19 02:02:44 +02:00
|
|
|
|
if (!fdGCLock) {
|
2017-07-30 13:27:57 +02:00
|
|
|
|
throw SysError(format("opening global GC lock '%1%'") % fnGCLock);
|
2020-05-19 02:02:44 +02:00
|
|
|
|
}
|
2005-01-31 23:23:49 +01:00
|
|
|
|
|
2016-07-11 21:44:44 +02:00
|
|
|
|
if (!lockFile(fdGCLock.get(), lockType, false)) {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
LOG(ERROR) << "waiting for the big garbage collector lock...";
|
2016-07-11 21:44:44 +02:00
|
|
|
|
lockFile(fdGCLock.get(), lockType, true);
|
2006-09-15 00:30:33 +02:00
|
|
|
|
}
|
2005-01-31 23:23:49 +01:00
|
|
|
|
|
|
|
|
|
/* !!! Restrict read permission on the GC root. Otherwise any
|
|
|
|
|
process that can open the file for reading can DoS the
|
|
|
|
|
collector. */
|
2012-07-31 01:55:41 +02:00
|
|
|
|
|
2018-10-04 15:03:03 +02:00
|
|
|
|
return fdGCLock;
|
2005-01-31 23:01:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
2014-02-27 23:17:53 +01:00
|
|
|
|
static void makeSymlink(const Path& link, const Path& target) {
|
2005-02-01 14:48:46 +01:00
|
|
|
|
/* Create directories up to `gcRoot'. */
|
|
|
|
|
createDirs(dirOf(link));
|
2005-01-31 23:01:55 +01:00
|
|
|
|
|
2012-04-16 18:47:01 +02:00
|
|
|
|
/* Create the new symlink. */
|
|
|
|
|
Path tempLink =
|
2018-03-07 00:34:44 +01:00
|
|
|
|
(format("%1%.tmp-%2%-%3%") % link % getpid() % random()).str();
|
2014-02-27 23:17:53 +01:00
|
|
|
|
createSymlink(target, tempLink);
|
2005-02-01 14:48:46 +01:00
|
|
|
|
|
2006-12-02 17:41:36 +01:00
|
|
|
|
/* Atomically replace the old one. */
|
|
|
|
|
if (rename(tempLink.c_str(), link.c_str()) == -1) {
|
|
|
|
|
throw SysError(format("cannot rename '%1%' to '%2%'") % tempLink % link);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2006-12-02 17:41:36 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LocalStore::syncWithGC() { AutoCloseFD fdGCLock = openGCLock(ltRead); }
|
|
|
|
|
|
2006-12-05 00:29:16 +01:00
|
|
|
|
void LocalStore::addIndirectRoot(const Path& path) {
|
2020-05-24 23:29:21 +02:00
|
|
|
|
std::string hash = hashString(htSHA1, path).to_string(Base32, false);
|
2006-12-05 00:29:16 +01:00
|
|
|
|
Path realRoot = canonPath(
|
2016-06-02 13:33:49 +02:00
|
|
|
|
(format("%1%/%2%/auto/%3%") % stateDir % gcRootsDir % hash).str());
|
2014-02-27 23:17:53 +01:00
|
|
|
|
makeSymlink(realRoot, path);
|
2006-12-05 00:29:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-02 13:33:49 +02:00
|
|
|
|
Path LocalFSStore::addPermRoot(const Path& _storePath, const Path& _gcRoot,
|
2011-08-31 23:11:50 +02:00
|
|
|
|
bool indirect, bool allowOutsideRootsDir) {
|
2005-02-01 13:36:25 +01:00
|
|
|
|
Path storePath(canonPath(_storePath));
|
|
|
|
|
Path gcRoot(canonPath(_gcRoot));
|
2005-02-01 14:48:46 +01:00
|
|
|
|
assertStorePath(storePath);
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2007-06-11 13:36:22 +02:00
|
|
|
|
if (isInStore(gcRoot)) {
|
|
|
|
|
throw Error(format("creating a garbage collector root (%1%) in the Nix "
|
|
|
|
|
"store is forbidden "
|
|
|
|
|
"(are you running nix-build inside the store?)") %
|
|
|
|
|
gcRoot);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2005-02-01 14:48:46 +01:00
|
|
|
|
if (indirect) {
|
2015-03-06 16:39:48 +01:00
|
|
|
|
/* Don't clobber the link if it already exists and doesn't
|
2012-04-16 18:47:01 +02:00
|
|
|
|
point to the Nix store. */
|
|
|
|
|
if (pathExists(gcRoot) &&
|
|
|
|
|
(!isLink(gcRoot) || !isInStore(readLink(gcRoot)))) {
|
2017-07-30 13:27:57 +02:00
|
|
|
|
throw Error(format("cannot create symlink '%1%'; already exists") %
|
|
|
|
|
gcRoot);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2014-02-27 23:17:53 +01:00
|
|
|
|
makeSymlink(gcRoot, storePath);
|
2016-02-11 16:14:42 +01:00
|
|
|
|
addIndirectRoot(gcRoot);
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else {
|
2006-09-15 00:30:33 +02:00
|
|
|
|
if (!allowOutsideRootsDir) {
|
2016-06-02 13:33:49 +02:00
|
|
|
|
Path rootsDir =
|
|
|
|
|
canonPath((format("%1%/%2%") % stateDir % gcRootsDir).str());
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2020-05-24 23:29:21 +02:00
|
|
|
|
if (std::string(gcRoot, 0, rootsDir.size() + 1) != rootsDir + "/") {
|
2017-07-30 13:27:57 +02:00
|
|
|
|
throw Error(format("path '%1%' is not a valid garbage collector root; "
|
|
|
|
|
"it's not in the directory '%2%'") %
|
2006-09-15 00:30:33 +02:00
|
|
|
|
gcRoot % rootsDir);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2005-02-01 14:48:46 +01:00
|
|
|
|
}
|
2005-02-01 13:36:25 +01:00
|
|
|
|
|
2006-09-15 00:30:33 +02:00
|
|
|
|
if (baseNameOf(gcRoot) == baseNameOf(storePath)) {
|
2014-08-13 17:44:41 +02:00
|
|
|
|
writeFile(gcRoot, "");
|
|
|
|
|
} else {
|
|
|
|
|
makeSymlink(gcRoot, storePath);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2005-02-01 14:48:46 +01:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2008-06-14 18:02:31 +02:00
|
|
|
|
/* Check that the root can be found by the garbage collector.
|
|
|
|
|
!!! This can be very slow on machines that have many roots.
|
|
|
|
|
Instead of reading all the roots, it would be more efficient to
|
|
|
|
|
check if the root is in a directory in or linked from the
|
|
|
|
|
gcroots directory. */
|
2012-07-31 01:55:41 +02:00
|
|
|
|
if (settings.checkRootReachability) {
|
2019-03-14 13:50:07 +01:00
|
|
|
|
Roots roots = findRoots(false);
|
2020-05-19 02:02:44 +02:00
|
|
|
|
if (roots[storePath].count(gcRoot) == 0) {
|
|
|
|
|
LOG(ERROR) << "warning: '" << gcRoot
|
|
|
|
|
<< "' is not in a directory where the garbage "
|
|
|
|
|
<< "collector looks for roots; therefore, '" << storePath
|
|
|
|
|
<< "' might be removed by the garbage collector";
|
|
|
|
|
}
|
2007-03-19 10:16:47 +01:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2006-12-02 17:41:36 +01:00
|
|
|
|
/* 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 16:14:42 +01:00
|
|
|
|
syncWithGC();
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2005-02-01 13:36:25 +01:00
|
|
|
|
return gcRoot;
|
|
|
|
|
}
|
|
|
|
|
|
2006-12-02 17:41:36 +01:00
|
|
|
|
void LocalStore::addTempRoot(const Path& path) {
|
2016-04-08 18:07:13 +02:00
|
|
|
|
auto state(_state.lock());
|
2005-02-01 13:36:25 +01:00
|
|
|
|
|
2006-12-02 17:41:36 +01:00
|
|
|
|
/* Create the temporary roots file for this process. */
|
2016-04-08 18:07:13 +02:00
|
|
|
|
if (!state->fdTempRoots) {
|
2020-05-20 05:33:07 +02:00
|
|
|
|
while (true) {
|
2016-04-08 18:07:13 +02:00
|
|
|
|
AutoCloseFD fdGCLock = openGCLock(ltRead);
|
|
|
|
|
|
2005-01-31 11:27:25 +01:00
|
|
|
|
if (pathExists(fnTempRoots)) {
|
|
|
|
|
/* It *must* be stale, since there can be no two
|
|
|
|
|
processes with the same pid. */
|
2016-07-11 21:44:44 +02:00
|
|
|
|
unlink(fnTempRoots.c_str());
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2005-01-31 11:27:25 +01:00
|
|
|
|
|
2005-01-31 23:23:49 +01:00
|
|
|
|
state->fdTempRoots = openLockFile(fnTempRoots, true);
|
2012-07-31 01:55:41 +02:00
|
|
|
|
|
2006-06-20 19:48:10 +02:00
|
|
|
|
fdGCLock = -1;
|
|
|
|
|
|
2020-05-19 02:02:44 +02:00
|
|
|
|
DLOG(INFO) << "acquiring read lock on " << fnTempRoots;
|
2017-09-05 20:39:57 +02:00
|
|
|
|
lockFile(state->fdTempRoots.get(), ltRead, true);
|
2005-01-31 11:27:25 +01:00
|
|
|
|
|
|
|
|
|
/* Check whether the garbage collector didn't get in our
|
|
|
|
|
way. */
|
2020-08-02 00:32:00 +02:00
|
|
|
|
struct stat st;
|
2016-07-11 21:44:44 +02:00
|
|
|
|
if (fstat(state->fdTempRoots.get(), &st) == -1) {
|
2017-09-05 20:39:57 +02:00
|
|
|
|
throw SysError(format("statting '%1%'") % fnTempRoots);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2005-01-31 11:27:25 +01:00
|
|
|
|
if (st.st_size == 0) {
|
|
|
|
|
break;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2005-01-31 11:27:25 +01:00
|
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2005-01-31 11:27:25 +01:00
|
|
|
|
|
|
|
|
|
/* Upgrade the lock to a write lock. This will cause us to block
|
|
|
|
|
if the garbage collector is holding our lock. */
|
2020-05-19 02:02:44 +02:00
|
|
|
|
DLOG(INFO) << "acquiring write lock on " << fnTempRoots;
|
2016-07-11 21:44:44 +02:00
|
|
|
|
lockFile(state->fdTempRoots.get(), ltWrite, true);
|
2005-01-31 11:27:25 +01:00
|
|
|
|
|
2020-05-24 23:29:21 +02:00
|
|
|
|
std::string s = path + '\0';
|
2016-07-11 21:44:44 +02:00
|
|
|
|
writeFull(state->fdTempRoots.get(), s);
|
2005-01-31 11:27:25 +01:00
|
|
|
|
|
|
|
|
|
/* Downgrade to a read lock. */
|
2020-05-19 02:02:44 +02:00
|
|
|
|
DLOG(INFO) << "downgrading to read lock on " << fnTempRoots;
|
2016-07-11 21:44:44 +02:00
|
|
|
|
lockFile(state->fdTempRoots.get(), ltRead, true);
|
2005-01-31 11:27:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-14 13:50:07 +01:00
|
|
|
|
static std::string censored = "{censored}";
|
|
|
|
|
|
|
|
|
|
void LocalStore::findTempRoots(FDs& fds, Roots& tempRoots, bool censor) {
|
2005-01-31 11:27:25 +01:00
|
|
|
|
/* Read the `temproots' directory for per-process temporary root
|
|
|
|
|
files. */
|
2017-09-14 15:02:52 +02:00
|
|
|
|
for (auto& i : readDirectory(tempRootsDir)) {
|
2017-09-05 20:39:57 +02:00
|
|
|
|
Path path = tempRootsDir + "/" + i.name;
|
2019-03-14 13:50:07 +01:00
|
|
|
|
|
2019-08-02 18:37:55 +02:00
|
|
|
|
pid_t pid = std::stoi(i.name);
|
2005-01-31 11:27:25 +01:00
|
|
|
|
|
2020-05-19 02:02:44 +02:00
|
|
|
|
DLOG(INFO) << "reading temporary root file " << path;
|
2016-06-02 13:33:49 +02:00
|
|
|
|
FDPtr fd(new AutoCloseFD(open(path.c_str(), O_CLOEXEC | O_RDWR, 0666)));
|
2016-07-11 21:44:44 +02:00
|
|
|
|
if (!*fd) {
|
2016-06-02 13:33:49 +02:00
|
|
|
|
/* It's okay if the file has disappeared. */
|
|
|
|
|
if (errno == ENOENT) {
|
|
|
|
|
continue;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2019-03-14 13:50:07 +01:00
|
|
|
|
throw SysError(format("opening temporary roots file '%1%'") % path);
|
2005-01-31 11:27:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This should work, but doesn't, for some reason. */
|
|
|
|
|
// FDPtr fd(new AutoCloseFD(openLockFile(path, false)));
|
2006-06-20 19:48:10 +02:00
|
|
|
|
// if (*fd == -1) { continue; }
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2005-01-31 11:27:25 +01:00
|
|
|
|
/* 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-19 02:02:44 +02:00
|
|
|
|
LOG(ERROR) << "removing stale temporary roots file " << path;
|
2005-01-31 11:27:25 +01:00
|
|
|
|
unlink(path.c_str());
|
2019-08-02 18:37:55 +02:00
|
|
|
|
writeFull(fd->get(), "d");
|
2005-01-31 11:27:25 +01:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-02 18:37:55 +02:00
|
|
|
|
/* Acquire a read lock. This will prevent the owning process
|
|
|
|
|
from upgrading to a write lock, therefore it will block in
|
|
|
|
|
addTempRoot(). */
|
2020-05-19 02:02:44 +02:00
|
|
|
|
DLOG(INFO) << "waiting for read lock on " << path;
|
2019-08-02 18:37:55 +02:00
|
|
|
|
lockFile(fd->get(), ltRead, true);
|
2005-01-31 11:27:25 +01:00
|
|
|
|
|
Eliminate the "store" global variable
Also, move a few free-standing functions into StoreAPI and Derivation.
Also, introduce a non-nullable smart pointer, ref<T>, which is just a
wrapper around std::shared_ptr ensuring that the pointer is never
null. (For reference-counted values, this is better than passing a
"T&", because the latter doesn't maintain the refcount. Usually, the
caller will have a shared_ptr keeping the value alive, but that's not
always the case, e.g., when passing a reference to a std::thread via
std::bind.)
2016-02-04 14:28:26 +01:00
|
|
|
|
/* Read the entire file. */
|
2020-05-24 23:29:21 +02:00
|
|
|
|
std::string contents = readFile(fd->get());
|
2006-12-05 02:31:45 +01:00
|
|
|
|
|
2013-07-12 14:01:25 +02:00
|
|
|
|
/* Extract the roots. */
|
2020-05-24 23:29:21 +02:00
|
|
|
|
std::string::size_type pos = 0;
|
2020-08-02 00:32:00 +02:00
|
|
|
|
std::string::size_type end;
|
2013-07-12 14:01:25 +02:00
|
|
|
|
|
2020-08-02 02:17:44 +02:00
|
|
|
|
while ((end = contents.find(static_cast<char>(0), pos)) !=
|
|
|
|
|
std::string::npos) {
|
2005-01-31 11:27:25 +01:00
|
|
|
|
Path root(contents, pos, end - pos);
|
2020-05-19 02:02:44 +02:00
|
|
|
|
DLOG(INFO) << "got temporary root " << root;
|
2014-10-03 22:37:51 +02:00
|
|
|
|
assertStorePath(root);
|
|
|
|
|
tempRoots[root].emplace(censor ? censored : fmt("{temp:%d}", pid));
|
2005-01-31 11:27:25 +01:00
|
|
|
|
pos = end + 1;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2006-12-05 02:31:45 +01:00
|
|
|
|
|
Eliminate the "store" global variable
Also, move a few free-standing functions into StoreAPI and Derivation.
Also, introduce a non-nullable smart pointer, ref<T>, which is just a
wrapper around std::shared_ptr ensuring that the pointer is never
null. (For reference-counted values, this is better than passing a
"T&", because the latter doesn't maintain the refcount. Usually, the
caller will have a shared_ptr keeping the value alive, but that's not
always the case, e.g., when passing a reference to a std::thread via
std::bind.)
2016-02-04 14:28:26 +01:00
|
|
|
|
fds.push_back(fd); /* keep open */
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2005-02-01 16:05:32 +01:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-02 13:33:49 +02:00
|
|
|
|
void LocalStore::findRoots(const Path& path, unsigned char type, Roots& roots) {
|
|
|
|
|
auto foundRoot = [&](const Path& path, const Path& target) {
|
2017-07-30 13:27:57 +02:00
|
|
|
|
Path storePath = toStorePath(target);
|
2016-05-04 15:39:39 +02:00
|
|
|
|
if (isStorePath(storePath) && isValidPath(storePath)) {
|
2017-07-30 13:27:57 +02:00
|
|
|
|
roots[storePath].emplace(path);
|
|
|
|
|
} else {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
LOG(INFO) << "skipping invalid root from '" << path << "' to '"
|
|
|
|
|
<< storePath << "'";
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2013-07-12 14:01:25 +02:00
|
|
|
|
};
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2013-07-12 14:01:25 +02:00
|
|
|
|
try {
|
Eliminate the "store" global variable
Also, move a few free-standing functions into StoreAPI and Derivation.
Also, introduce a non-nullable smart pointer, ref<T>, which is just a
wrapper around std::shared_ptr ensuring that the pointer is never
null. (For reference-counted values, this is better than passing a
"T&", because the latter doesn't maintain the refcount. Usually, the
caller will have a shared_ptr keeping the value alive, but that's not
always the case, e.g., when passing a reference to a std::thread via
std::bind.)
2016-02-04 14:28:26 +01:00
|
|
|
|
if (type == DT_UNKNOWN) {
|
|
|
|
|
type = getFileType(path);
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
Eliminate the "store" global variable
Also, move a few free-standing functions into StoreAPI and Derivation.
Also, introduce a non-nullable smart pointer, ref<T>, which is just a
wrapper around std::shared_ptr ensuring that the pointer is never
null. (For reference-counted values, this is better than passing a
"T&", because the latter doesn't maintain the refcount. Usually, the
caller will have a shared_ptr keeping the value alive, but that's not
always the case, e.g., when passing a reference to a std::thread via
std::bind.)
2016-02-04 14:28:26 +01:00
|
|
|
|
if (type == DT_DIR) {
|
|
|
|
|
for (auto& i : readDirectory(path)) {
|
|
|
|
|
findRoots(path + "/" + i.name, i.type, roots);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2005-02-01 16:05:32 +01:00
|
|
|
|
}
|
2006-12-05 02:31:45 +01:00
|
|
|
|
|
2014-08-01 17:20:25 +02:00
|
|
|
|
else if (type == DT_LNK) {
|
2016-06-01 14:49:12 +02:00
|
|
|
|
Path target = readLink(path);
|
|
|
|
|
if (isInStore(target)) {
|
|
|
|
|
foundRoot(path, target);
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2013-07-12 14:01:25 +02:00
|
|
|
|
/* Handle indirect roots. */
|
2016-06-01 14:49:12 +02:00
|
|
|
|
else {
|
|
|
|
|
target = absPath(target, dirOf(path));
|
2013-07-12 14:01:25 +02:00
|
|
|
|
if (!pathExists(target)) {
|
2016-06-01 14:49:12 +02:00
|
|
|
|
if (isInDir(path, stateDir + "/" + gcRootsDir + "/auto")) {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
LOG(INFO) << "removing stale link from '" << path << "' to '"
|
|
|
|
|
<< target << "'";
|
2016-04-25 15:42:55 +02:00
|
|
|
|
unlink(path.c_str());
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2016-04-25 15:42:55 +02:00
|
|
|
|
struct stat st2 = lstat(target);
|
|
|
|
|
if (!S_ISLNK(st2.st_mode)) {
|
|
|
|
|
return;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2019-03-01 00:54:52 +01:00
|
|
|
|
Path target2 = readLink(target);
|
|
|
|
|
if (isInStore(target2)) {
|
|
|
|
|
foundRoot(target, target2);
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2014-08-01 16:46:01 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2006-12-05 02:31:45 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (type == DT_REG) {
|
|
|
|
|
Path storePath = storeDir + "/" + baseNameOf(path);
|
2017-07-30 13:27:57 +02:00
|
|
|
|
if (isStorePath(storePath) && isValidPath(storePath)) {
|
|
|
|
|
roots[storePath].emplace(path);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2005-02-01 16:05:32 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2006-12-05 02:31:45 +01:00
|
|
|
|
|
|
|
|
|
catch (SysError& e) {
|
2019-05-01 04:43:24 +02:00
|
|
|
|
/* We only ignore permanent failures. */
|
2020-05-19 02:02:44 +02:00
|
|
|
|
if (e.errNo == EACCES || e.errNo == ENOENT || e.errNo == ENOTDIR) {
|
|
|
|
|
LOG(INFO) << "cannot read potential root '" << path << "'";
|
|
|
|
|
} else {
|
2019-05-01 04:43:24 +02:00
|
|
|
|
throw;
|
2020-05-19 02:02:44 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2017-09-14 14:38:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-14 13:50:07 +01:00
|
|
|
|
void LocalStore::findRootsNoTemp(Roots& roots, bool censor) {
|
2018-02-01 10:39:16 +01:00
|
|
|
|
/* Process direct roots in {gcroots,profiles}. */
|
2016-06-02 13:33:49 +02:00
|
|
|
|
findRoots(stateDir + "/" + gcRootsDir, DT_UNKNOWN, roots);
|
|
|
|
|
findRoots(stateDir + "/profiles", DT_UNKNOWN, roots);
|
2017-09-14 14:38:36 +02:00
|
|
|
|
|
2019-03-14 13:50:07 +01:00
|
|
|
|
/* 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);
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2017-09-14 14:38:36 +02:00
|
|
|
|
|
2019-03-14 13:50:07 +01:00
|
|
|
|
Roots LocalStore::findRoots(bool censor) {
|
2017-09-14 14:38:36 +02:00
|
|
|
|
Roots roots;
|
2019-03-14 13:50:07 +01:00
|
|
|
|
findRootsNoTemp(roots, censor);
|
2017-09-14 14:38:36 +02:00
|
|
|
|
|
2020-05-17 17:31:57 +02:00
|
|
|
|
FDs fds;
|
2019-03-14 13:50:07 +01:00
|
|
|
|
findTempRoots(fds, roots, censor);
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2013-07-12 14:01:25 +02:00
|
|
|
|
return roots;
|
2006-12-05 01:34:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-24 23:29:21 +02:00
|
|
|
|
static void readProcLink(const std::string& file, Roots& roots) {
|
2016-07-20 20:00:36 +02: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) {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
if (errno == ENOENT || errno == EACCES || errno == ESRCH) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-07-20 20:00:36 +02:00
|
|
|
|
throw SysError("reading symlink");
|
|
|
|
|
}
|
|
|
|
|
if (res == bufsiz) {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
if (SSIZE_MAX / 2 < bufsiz) {
|
|
|
|
|
throw Error("stupidly long symlink");
|
|
|
|
|
}
|
2016-07-20 20:00:36 +02:00
|
|
|
|
bufsiz *= 2;
|
|
|
|
|
goto try_again;
|
|
|
|
|
}
|
2020-05-19 02:02:44 +02:00
|
|
|
|
if (res > 0 && buf[0] == '/') {
|
2019-03-01 00:54:52 +01:00
|
|
|
|
roots[std::string(static_cast<char*>(buf), res)].emplace(file);
|
2020-05-19 02:02:44 +02:00
|
|
|
|
}
|
2016-07-20 20:00:36 +02:00
|
|
|
|
}
|
2006-07-20 14:17:25 +02:00
|
|
|
|
|
2020-05-24 23:29:21 +02:00
|
|
|
|
static std::string quoteRegexChars(const std::string& raw) {
|
2016-07-20 20:00:36 +02:00
|
|
|
|
static auto specialRegex = std::regex(R"([.^$\\*+?()\[\]{}|])");
|
|
|
|
|
return std::regex_replace(raw, specialRegex, R"(\$&)");
|
|
|
|
|
}
|
2012-07-31 01:55:41 +02:00
|
|
|
|
|
2019-02-27 23:32:12 +01:00
|
|
|
|
static void readFileRoots(const char* path, Roots& roots) {
|
2016-07-20 20:00:36 +02:00
|
|
|
|
try {
|
2019-03-01 00:54:52 +01:00
|
|
|
|
roots[readFile(path)].emplace(path);
|
2016-07-20 20:00:36 +02:00
|
|
|
|
} catch (SysError& e) {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
if (e.errNo != ENOENT && e.errNo != EACCES) {
|
|
|
|
|
throw;
|
|
|
|
|
}
|
2016-07-20 20:00:36 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2006-07-20 14:17:25 +02:00
|
|
|
|
|
2019-03-14 13:50:07 +01:00
|
|
|
|
void LocalStore::findRuntimeRoots(Roots& roots, bool censor) {
|
2019-02-27 23:32:12 +01:00
|
|
|
|
Roots unchecked;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2016-07-20 20:00:36 +02:00
|
|
|
|
auto procDir = AutoCloseDir{opendir("/proc")};
|
|
|
|
|
if (procDir) {
|
2020-08-02 00:32:00 +02:00
|
|
|
|
struct dirent* ent;
|
2016-07-20 20:00:36 +02:00
|
|
|
|
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 22:39:27 +01:00
|
|
|
|
while (errno = 0, ent = readdir(procDir.get())) {
|
2016-07-20 20:00:36 +02:00
|
|
|
|
checkInterrupt();
|
2019-03-14 13:27:16 +01:00
|
|
|
|
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);
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2019-03-14 13:27:16 +01:00
|
|
|
|
auto fdStr = fmt("/proc/%s/fd", ent->d_name);
|
2016-07-20 20:00:36 +02:00
|
|
|
|
auto fdDir = AutoCloseDir(opendir(fdStr.c_str()));
|
|
|
|
|
if (!fdDir) {
|
2018-06-11 15:59:32 +02:00
|
|
|
|
if (errno == ENOENT || errno == EACCES) {
|
|
|
|
|
continue;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2016-07-20 20:00:36 +02:00
|
|
|
|
throw SysError(format("opening %1%") % fdStr);
|
|
|
|
|
}
|
2020-08-02 00:32:00 +02:00
|
|
|
|
struct dirent* fd_ent;
|
2017-01-16 22:39:27 +01:00
|
|
|
|
while (errno = 0, fd_ent = readdir(fdDir.get())) {
|
2016-07-20 20:00:36 +02:00
|
|
|
|
if (fd_ent->d_name[0] != '.') {
|
2019-03-14 13:27:16 +01:00
|
|
|
|
readProcLink(fmt("%s/%s", fdStr, fd_ent->d_name), unchecked);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2016-07-20 20:00:36 +02:00
|
|
|
|
if (errno) {
|
2005-01-31 11:27:25 +01:00
|
|
|
|
if (errno == ESRCH) {
|
|
|
|
|
continue;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2016-07-20 20:00:36 +02:00
|
|
|
|
throw SysError(format("iterating /proc/%1%/fd") % ent->d_name);
|
|
|
|
|
}
|
2018-06-11 15:59:32 +02:00
|
|
|
|
fdDir.reset();
|
2016-07-20 20:00:36 +02:00
|
|
|
|
|
2019-07-30 11:29:03 +02:00
|
|
|
|
try {
|
|
|
|
|
auto mapFile = fmt("/proc/%s/maps", ent->d_name);
|
2020-08-06 10:28:00 +02:00
|
|
|
|
std::vector<std::string> mapLines = absl::StrSplit(
|
|
|
|
|
readFile(mapFile, true), absl::ByChar('\n'), absl::SkipEmpty());
|
2019-07-30 11:29:03 +02:00
|
|
|
|
for (const auto& line : mapLines) {
|
|
|
|
|
auto match = std::smatch{};
|
|
|
|
|
if (std::regex_match(line, match, mapRegex)) {
|
|
|
|
|
unchecked[match[1]].emplace(mapFile);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2019-07-30 11:29:03 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2019-03-14 13:27:16 +01:00
|
|
|
|
auto envFile = fmt("/proc/%s/environ", ent->d_name);
|
2019-02-27 23:32:12 +01:00
|
|
|
|
auto envString = readFile(envFile, true);
|
2016-07-20 20:00:36 +02:00
|
|
|
|
auto env_end = std::sregex_iterator{};
|
2019-07-30 11:29:03 +02:00
|
|
|
|
for (auto i = std::sregex_iterator{envString.begin(), envString.end(),
|
2016-07-20 20:00:36 +02:00
|
|
|
|
storePathRegex};
|
|
|
|
|
i != env_end; ++i) {
|
2019-07-30 11:29:03 +02:00
|
|
|
|
unchecked[i->str()].emplace(envFile);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2019-07-30 11:29:03 +02:00
|
|
|
|
} catch (SysError& e) {
|
|
|
|
|
if (errno == ENOENT || errno == EACCES || errno == ESRCH) {
|
|
|
|
|
continue;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
throw;
|
2016-07-20 20:00:36 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-07-20 20:00:36 +02:00
|
|
|
|
if (errno) {
|
|
|
|
|
throw SysError("iterating /proc");
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-27 23:32:12 +01:00
|
|
|
|
readFileRoots("/proc/sys/kernel/modprobe", unchecked);
|
|
|
|
|
readFileRoots("/proc/sys/kernel/fbsplash", unchecked);
|
|
|
|
|
readFileRoots("/proc/sys/kernel/poweroff_cmd", unchecked);
|
2012-07-31 01:55:41 +02:00
|
|
|
|
|
2019-03-01 00:54:52 +01:00
|
|
|
|
for (auto& [target, links] : unchecked) {
|
|
|
|
|
if (isInStore(target)) {
|
|
|
|
|
Path path = toStorePath(target);
|
2019-02-27 23:32:12 +01:00
|
|
|
|
if (isStorePath(path) && isValidPath(path)) {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
DLOG(INFO) << "got additional root " << path;
|
2019-03-14 13:50:07 +01:00
|
|
|
|
if (censor) {
|
|
|
|
|
roots[path].insert(censored);
|
|
|
|
|
} else {
|
|
|
|
|
roots[path].insert(links.begin(), links.end());
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2006-07-20 14:17:25 +02:00
|
|
|
|
}
|
2019-02-27 23:32:12 +01:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2006-07-20 14:17:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
2008-06-18 16:20:16 +02:00
|
|
|
|
struct GCLimitReached {};
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2009-11-23 17:34:24 +01:00
|
|
|
|
struct LocalStore::GCState {
|
|
|
|
|
GCOptions options;
|
|
|
|
|
GCResults& results;
|
|
|
|
|
PathSet roots;
|
|
|
|
|
PathSet tempRoots;
|
2012-12-20 17:32:15 +01:00
|
|
|
|
PathSet dead;
|
|
|
|
|
PathSet alive;
|
2020-08-02 00:32:00 +02:00
|
|
|
|
bool gcKeepOutputs;
|
|
|
|
|
bool gcKeepDerivations;
|
2012-03-26 20:43:33 +02:00
|
|
|
|
unsigned long long bytesInvalidated;
|
2015-06-30 21:41:26 +02:00
|
|
|
|
bool moveToTrash = true;
|
2020-08-02 00:32:00 +02:00
|
|
|
|
bool shouldDelete;
|
2020-05-20 05:33:07 +02:00
|
|
|
|
explicit GCState(GCResults& results_)
|
|
|
|
|
: results(results_), bytesInvalidated(0) {}
|
2009-11-23 17:34:24 +01:00
|
|
|
|
};
|
2008-06-18 16:20:16 +02:00
|
|
|
|
|
2009-11-23 17:34:24 +01:00
|
|
|
|
bool LocalStore::isActiveTempFile(const GCState& state, const Path& path,
|
2020-05-24 23:29:21 +02:00
|
|
|
|
const std::string& suffix) {
|
2020-05-25 03:19:01 +02:00
|
|
|
|
return absl::EndsWith(path, suffix) &&
|
2020-05-24 23:29:21 +02:00
|
|
|
|
state.tempRoots.find(std::string(
|
|
|
|
|
path, 0, path.size() - suffix.size())) != state.tempRoots.end();
|
2009-11-23 17:34:24 +01:00
|
|
|
|
}
|
2008-09-17 14:54:07 +02:00
|
|
|
|
|
2012-03-26 20:43:33 +02:00
|
|
|
|
void LocalStore::deleteGarbage(GCState& state, const Path& path) {
|
2020-08-02 00:32:00 +02:00
|
|
|
|
unsigned long long bytesFreed;
|
2013-11-14 11:57:37 +01:00
|
|
|
|
deletePath(path, bytesFreed);
|
2012-03-26 20:43:33 +02:00
|
|
|
|
state.results.bytesFreed += bytesFreed;
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
void LocalStore::deletePathRecursive(GCState& state, const Path& path) {
|
2010-10-14 17:55:51 +02:00
|
|
|
|
checkInterrupt();
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
unsigned long long size = 0;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2016-05-04 15:39:39 +02:00
|
|
|
|
if (isStorePath(path) && isValidPath(path)) {
|
2012-12-20 17:32:15 +01:00
|
|
|
|
PathSet referrers;
|
|
|
|
|
queryReferrers(path, referrers);
|
2015-07-17 19:24:28 +02:00
|
|
|
|
for (auto& i : referrers) {
|
|
|
|
|
if (i != path) {
|
|
|
|
|
deletePathRecursive(state, i);
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2016-04-19 18:50:15 +02:00
|
|
|
|
size = queryPathInfo(path)->narSize;
|
2012-12-20 17:32:15 +01:00
|
|
|
|
invalidatePathChecked(path);
|
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2016-06-02 15:08:18 +02:00
|
|
|
|
Path realPath = realStoreDir + "/" + baseNameOf(path);
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2020-08-02 00:32:00 +02:00
|
|
|
|
struct stat st;
|
2020-05-20 23:27:37 +02:00
|
|
|
|
if (lstat(realPath.c_str(), &st) != 0) {
|
2012-12-20 17:32:15 +01:00
|
|
|
|
if (errno == ENOENT) {
|
|
|
|
|
return;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2016-06-02 15:08:18 +02:00
|
|
|
|
throw SysError(format("getting status of %1%") % realPath);
|
2012-03-26 20:56:30 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2020-05-19 02:02:44 +02:00
|
|
|
|
LOG(INFO) << "deleting '" << path << "'";
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2013-01-04 15:17:19 +01:00
|
|
|
|
state.results.paths.insert(path);
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
/* If the path is not a regular file or symlink, move it to the
|
|
|
|
|
trash directory. The move is to ensure that later (when we're
|
|
|
|
|
not holding the global GC lock) we can delete the path without
|
|
|
|
|
being afraid that the path has become alive again. Otherwise
|
|
|
|
|
delete it right away. */
|
2015-06-30 21:41:26 +02:00
|
|
|
|
if (state.moveToTrash && S_ISDIR(st.st_mode)) {
|
2012-12-20 17:32:15 +01:00
|
|
|
|
// Estimate the amount freed using the narSize field. FIXME:
|
|
|
|
|
// if the path was not valid, need to determine the actual
|
|
|
|
|
// size.
|
2015-06-30 21:41:26 +02:00
|
|
|
|
try {
|
2016-06-02 15:08:18 +02:00
|
|
|
|
if (chmod(realPath.c_str(), st.st_mode | S_IWUSR) == -1) {
|
2017-07-30 13:27:57 +02:00
|
|
|
|
throw SysError(format("making '%1%' writable") % realPath);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2016-06-02 15:08:18 +02:00
|
|
|
|
Path tmp = trashDir + "/" + baseNameOf(path);
|
2020-05-20 23:27:37 +02:00
|
|
|
|
if (rename(realPath.c_str(), tmp.c_str()) != 0) {
|
2017-07-30 13:27:57 +02:00
|
|
|
|
throw SysError(format("unable to rename '%1%' to '%2%'") % realPath %
|
|
|
|
|
tmp);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2015-06-30 21:41:26 +02:00
|
|
|
|
state.bytesInvalidated += size;
|
|
|
|
|
} catch (SysError& e) {
|
|
|
|
|
if (e.errNo == ENOSPC) {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
LOG(INFO) << "note: can't create move '" << realPath
|
|
|
|
|
<< "': " << e.msg();
|
2016-06-02 15:08:18 +02:00
|
|
|
|
deleteGarbage(state, realPath);
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2012-12-20 17:32:15 +01:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
} else {
|
2016-06-02 15:08:18 +02:00
|
|
|
|
deleteGarbage(state, realPath);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
if (state.results.bytesFreed + state.bytesInvalidated >
|
|
|
|
|
state.options.maxFreed) {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
LOG(INFO) << "deleted or invalidated more than " << state.options.maxFreed
|
|
|
|
|
<< " bytes; stopping";
|
2012-12-20 17:32:15 +01:00
|
|
|
|
throw GCLimitReached();
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2012-12-20 17:32:15 +01:00
|
|
|
|
}
|
2009-11-23 17:34:24 +01:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
bool LocalStore::canReachRoot(GCState& state, PathSet& visited,
|
Eliminate the "store" global variable
Also, move a few free-standing functions into StoreAPI and Derivation.
Also, introduce a non-nullable smart pointer, ref<T>, which is just a
wrapper around std::shared_ptr ensuring that the pointer is never
null. (For reference-counted values, this is better than passing a
"T&", because the latter doesn't maintain the refcount. Usually, the
caller will have a shared_ptr keeping the value alive, but that's not
always the case, e.g., when passing a reference to a std::thread via
std::bind.)
2016-02-04 14:28:26 +01:00
|
|
|
|
const Path& path) {
|
2020-05-20 23:27:37 +02:00
|
|
|
|
if (visited.count(path) != 0u) {
|
2017-09-14 14:38:36 +02:00
|
|
|
|
return false;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2009-11-23 17:34:24 +01:00
|
|
|
|
|
2020-05-20 23:27:37 +02:00
|
|
|
|
if (state.alive.count(path) != 0u) {
|
2017-09-14 14:38:36 +02:00
|
|
|
|
return true;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2009-11-23 17:34:24 +01:00
|
|
|
|
|
2020-05-20 23:27:37 +02:00
|
|
|
|
if (state.dead.count(path) != 0u) {
|
2017-09-14 14:38:36 +02:00
|
|
|
|
return false;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2008-09-17 14:54:07 +02:00
|
|
|
|
|
2020-05-20 23:27:37 +02:00
|
|
|
|
if (state.roots.count(path) != 0u) {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
DLOG(INFO) << "cannot delete '" << path << "' because it's a root";
|
2017-09-14 14:38:36 +02:00
|
|
|
|
state.alive.insert(path);
|
|
|
|
|
return true;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2009-11-23 17:34:24 +01:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
visited.insert(path);
|
2008-12-12 18:03:18 +01:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
if (!isStorePath(path) || !isValidPath(path)) {
|
|
|
|
|
return false;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2012-03-26 20:43:33 +02:00
|
|
|
|
|
2016-05-04 15:39:39 +02:00
|
|
|
|
PathSet incoming;
|
2012-09-13 00:49:35 +02:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
/* Don't delete this path if any of its referrers are alive. */
|
|
|
|
|
queryReferrers(path, incoming);
|
2009-11-23 17:34:24 +01:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
/* 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 && isDerivation(path)) {
|
|
|
|
|
PathSet outputs = queryDerivationOutputs(path);
|
2015-07-17 19:24:28 +02:00
|
|
|
|
for (auto& i : outputs) {
|
2016-04-19 18:50:15 +02:00
|
|
|
|
if (isValidPath(i) && queryPathInfo(i)->deriver == path) {
|
2012-12-20 17:32:15 +01:00
|
|
|
|
incoming.insert(i);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2012-09-13 00:49:35 +02:00
|
|
|
|
|
2017-08-31 14:28:25 +02:00
|
|
|
|
/* If keep-outputs is set, then don't delete this path if there
|
2012-12-20 17:32:15 +01:00
|
|
|
|
are derivers of this path that are not garbage. */
|
|
|
|
|
if (state.gcKeepOutputs) {
|
|
|
|
|
PathSet derivers = queryValidDerivers(path);
|
2015-07-17 19:24:28 +02:00
|
|
|
|
for (auto& i : derivers) {
|
|
|
|
|
incoming.insert(i);
|
2020-05-19 20:04:08 +02:00
|
|
|
|
}
|
2012-12-20 17:32:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
2015-07-17 19:24:28 +02:00
|
|
|
|
for (auto& i : incoming) {
|
|
|
|
|
if (i != path) {
|
|
|
|
|
if (canReachRoot(state, visited, i)) {
|
2012-12-20 17:32:15 +01:00
|
|
|
|
state.alive.insert(path);
|
|
|
|
|
return true;
|
2012-09-13 00:49:35 +02:00
|
|
|
|
}
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2012-09-13 00:49:35 +02:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2012-09-13 00:49:35 +02:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
void LocalStore::tryToDelete(GCState& state, const Path& path) {
|
|
|
|
|
checkInterrupt();
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2016-06-02 15:08:18 +02:00
|
|
|
|
auto realPath = realStoreDir + "/" + baseNameOf(path);
|
|
|
|
|
if (realPath == linksDir || realPath == trashDir) {
|
|
|
|
|
return;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2017-07-30 13:27:57 +02:00
|
|
|
|
// Activity act(*logger, lvlDebug, format("considering whether to delete
|
|
|
|
|
// '%1%'") % path);
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2016-05-04 15:39:39 +02:00
|
|
|
|
if (!isStorePath(path) || !isValidPath(path)) {
|
2012-12-20 17:32:15 +01:00
|
|
|
|
/* A lock file belonging to a path that we're building right
|
|
|
|
|
now isn't garbage. */
|
|
|
|
|
if (isActiveTempFile(state, path, ".lock")) {
|
|
|
|
|
return;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
/* Don't delete .chroot directories for derivations that are
|
|
|
|
|
currently being built. */
|
|
|
|
|
if (isActiveTempFile(state, path, ".chroot")) {
|
|
|
|
|
return;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2016-12-08 21:38:58 +01:00
|
|
|
|
/* 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;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2012-12-20 17:32:15 +01:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
PathSet visited;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
if (canReachRoot(state, visited, path)) {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
DLOG(INFO) << "cannot delete '" << path << "' because it's still reachable";
|
2012-12-20 17:32:15 +01:00
|
|
|
|
} else {
|
|
|
|
|
/* No path we visited was a root, so everything is garbage.
|
2016-11-26 00:37:43 +01:00
|
|
|
|
But we only delete ‘path’ and its referrers here so that
|
|
|
|
|
‘nix-store --delete’ doesn't have the unexpected effect of
|
2012-12-20 17:32:15 +01:00
|
|
|
|
recursing into derivations and outputs. */
|
|
|
|
|
state.dead.insert(visited.begin(), visited.end());
|
2020-05-19 18:38:04 +02:00
|
|
|
|
if (state.shouldDelete) {
|
|
|
|
|
deletePathRecursive(state, path);
|
|
|
|
|
}
|
2012-09-13 00:49:35 +02:00
|
|
|
|
}
|
2008-12-12 18:03:18 +01:00
|
|
|
|
}
|
2009-11-24 10:53:18 +01:00
|
|
|
|
|
2012-07-23 21:48:30 +02:00
|
|
|
|
/* 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. */
|
2012-08-02 04:43:03 +02:00
|
|
|
|
void LocalStore::removeUnusedLinks(const GCState& state) {
|
2017-01-16 22:39:27 +01:00
|
|
|
|
AutoCloseDir dir(opendir(linksDir.c_str()));
|
2020-05-19 02:02:44 +02:00
|
|
|
|
if (!dir) {
|
|
|
|
|
throw SysError(format("opening directory '%1%'") % linksDir);
|
|
|
|
|
}
|
2012-07-23 21:48:30 +02:00
|
|
|
|
|
2020-05-20 23:27:37 +02:00
|
|
|
|
long long actualSize = 0;
|
|
|
|
|
long long unsharedSize = 0;
|
2012-08-02 01:01:50 +02:00
|
|
|
|
|
2020-08-02 00:32:00 +02:00
|
|
|
|
struct dirent* dirent;
|
2017-01-16 22:39:27 +01:00
|
|
|
|
while (errno = 0, dirent = readdir(dir.get())) {
|
2012-07-23 21:48:30 +02:00
|
|
|
|
checkInterrupt();
|
2020-05-24 23:29:21 +02:00
|
|
|
|
std::string name = dirent->d_name;
|
2012-07-23 21:48:30 +02:00
|
|
|
|
if (name == "." || name == "..") {
|
|
|
|
|
continue;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2012-07-23 21:48:30 +02:00
|
|
|
|
Path path = linksDir + "/" + name;
|
|
|
|
|
|
2020-08-02 00:32:00 +02:00
|
|
|
|
struct stat st;
|
2012-07-23 21:48:30 +02:00
|
|
|
|
if (lstat(path.c_str(), &st) == -1) {
|
2017-07-30 13:27:57 +02:00
|
|
|
|
throw SysError(format("statting '%1%'") % path);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2012-07-23 21:48:30 +02:00
|
|
|
|
|
2012-08-02 01:01:50 +02:00
|
|
|
|
if (st.st_nlink != 1) {
|
2019-08-29 14:49:58 +02:00
|
|
|
|
actualSize += st.st_size;
|
|
|
|
|
unsharedSize += (st.st_nlink - 1) * st.st_size;
|
2012-08-02 01:01:50 +02:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2012-07-23 21:48:30 +02:00
|
|
|
|
|
2020-05-19 02:02:44 +02:00
|
|
|
|
LOG(INFO) << "deleting unused link " << path;
|
2012-07-23 21:48:30 +02:00
|
|
|
|
|
2020-05-19 02:02:44 +02:00
|
|
|
|
if (unlink(path.c_str()) == -1) {
|
2017-07-30 13:27:57 +02:00
|
|
|
|
throw SysError(format("deleting '%1%'") % path);
|
2020-05-19 02:02:44 +02:00
|
|
|
|
}
|
2012-08-02 04:43:03 +02:00
|
|
|
|
|
2019-08-29 14:49:58 +02:00
|
|
|
|
state.results.bytesFreed += st.st_size;
|
2012-07-23 21:48:30 +02:00
|
|
|
|
}
|
2012-08-02 01:01:50 +02:00
|
|
|
|
|
2020-08-02 00:32:00 +02:00
|
|
|
|
struct stat st;
|
2020-05-19 02:02:44 +02:00
|
|
|
|
if (stat(linksDir.c_str(), &st) == -1) {
|
2017-07-30 13:27:57 +02:00
|
|
|
|
throw SysError(format("statting '%1%'") % linksDir);
|
2020-05-19 02:02:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
2012-08-06 03:45:27 +02:00
|
|
|
|
long long overhead = st.st_blocks * 512ULL;
|
2012-08-02 01:01:50 +02:00
|
|
|
|
|
2020-05-19 02:02:44 +02:00
|
|
|
|
// TODO(tazjin): absl::StrFormat %.2f
|
|
|
|
|
LOG(INFO) << "note: currently hard linking saves "
|
|
|
|
|
<< ((unsharedSize - actualSize - overhead) / (1024.0 * 1024.0))
|
|
|
|
|
<< " MiB";
|
2012-07-23 21:48:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
2008-06-18 11:34:17 +02:00
|
|
|
|
void LocalStore::collectGarbage(const GCOptions& options, GCResults& results) {
|
2009-11-23 17:34:24 +01:00
|
|
|
|
GCState state(results);
|
2012-07-31 01:55:41 +02:00
|
|
|
|
state.options = options;
|
|
|
|
|
state.gcKeepOutputs = settings.gcKeepOutputs;
|
|
|
|
|
state.gcKeepDerivations = settings.gcKeepDerivations;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2010-03-08 22:31:42 +01:00
|
|
|
|
/* Using `--ignore-liveness' with `--delete' can have unintended
|
2017-08-31 14:28:25 +02:00
|
|
|
|
consequences if `keep-outputs' or `keep-derivations' are true
|
|
|
|
|
(the garbage collector will recurse into deleting the outputs
|
|
|
|
|
or derivers, respectively). So disable them. */
|
2010-03-08 22:31:42 +01:00
|
|
|
|
if (options.action == GCOptions::gcDeleteSpecific && options.ignoreLiveness) {
|
|
|
|
|
state.gcKeepOutputs = false;
|
2016-09-21 16:11:01 +02:00
|
|
|
|
state.gcKeepDerivations = false;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
2010-03-08 22:31:42 +01:00
|
|
|
|
state.shouldDelete = options.action == GCOptions::gcDeleteDead ||
|
2012-12-20 17:32:15 +01:00
|
|
|
|
options.action == GCOptions::gcDeleteSpecific;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2020-05-19 18:38:04 +02:00
|
|
|
|
if (state.shouldDelete) {
|
|
|
|
|
deletePath(reservedPath);
|
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2005-01-31 23:23:49 +01:00
|
|
|
|
/* Acquire the global GC root. This prevents
|
2005-01-31 11:27:25 +01:00
|
|
|
|
a) New roots from being added.
|
|
|
|
|
b) Processes from creating new temporary root files. */
|
2005-01-31 23:23:49 +01:00
|
|
|
|
AutoCloseFD fdGCLock = openGCLock(ltWrite);
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2005-02-01 16:05:32 +01:00
|
|
|
|
/* Find the roots. Since we've grabbed the GC lock, the set of
|
|
|
|
|
permanent roots cannot increase now. */
|
2020-05-19 02:02:44 +02:00
|
|
|
|
LOG(INFO) << "finding garbage collector roots...";
|
2019-03-10 00:37:52 +01:00
|
|
|
|
Roots rootMap;
|
2020-05-19 02:02:44 +02:00
|
|
|
|
if (!options.ignoreLiveness) {
|
|
|
|
|
findRootsNoTemp(rootMap, true);
|
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2020-05-19 02:02:44 +02:00
|
|
|
|
for (auto& i : rootMap) {
|
|
|
|
|
state.roots.insert(i.first);
|
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2005-01-31 11:27:25 +01:00
|
|
|
|
/* 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. */
|
2020-05-17 17:31:57 +02:00
|
|
|
|
FDs fds;
|
2019-03-10 00:37:52 +01:00
|
|
|
|
Roots tempRoots;
|
2019-03-14 13:50:07 +01:00
|
|
|
|
findTempRoots(fds, tempRoots, true);
|
2019-03-01 00:54:52 +01:00
|
|
|
|
for (auto& root : tempRoots) {
|
|
|
|
|
state.tempRoots.insert(root.first);
|
2020-05-19 20:04:08 +02:00
|
|
|
|
}
|
2009-11-23 17:34:24 +01:00
|
|
|
|
state.roots.insert(state.tempRoots.begin(), state.tempRoots.end());
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2005-01-31 15:00:43 +01:00
|
|
|
|
/* After this point the set of roots or temporary roots cannot
|
2010-03-08 22:31:42 +01:00
|
|
|
|
increase, since we hold locks on everything. So everything
|
2014-08-01 16:37:47 +02:00
|
|
|
|
that is not reachable from `roots' is garbage. */
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2013-03-08 01:24:59 +01:00
|
|
|
|
if (state.shouldDelete) {
|
2020-05-19 18:38:04 +02:00
|
|
|
|
if (pathExists(trashDir)) {
|
|
|
|
|
deleteGarbage(state, trashDir);
|
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
try {
|
2016-06-02 15:08:18 +02:00
|
|
|
|
createDirs(trashDir);
|
2015-06-30 21:41:26 +02:00
|
|
|
|
} catch (SysError& e) {
|
|
|
|
|
if (e.errNo == ENOSPC) {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
LOG(INFO) << "note: can't create trash directory: " << e.msg();
|
2010-03-08 22:31:42 +01:00
|
|
|
|
state.moveToTrash = false;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2010-03-08 22:31:42 +01:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
2005-02-01 16:05:32 +01:00
|
|
|
|
/* Now either delete all garbage paths, or just the specified
|
2016-09-21 16:11:01 +02:00
|
|
|
|
paths (for gcDeleteSpecific). */
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2019-03-10 00:37:52 +01:00
|
|
|
|
if (options.action == GCOptions::gcDeleteSpecific) {
|
2019-03-01 00:54:52 +01:00
|
|
|
|
for (auto& i : options.pathsToDelete) {
|
2019-03-10 00:37:52 +01:00
|
|
|
|
assertStorePath(i);
|
2016-06-02 15:08:18 +02:00
|
|
|
|
tryToDelete(state, i);
|
2015-06-30 21:41:26 +02:00
|
|
|
|
if (state.dead.find(i) == state.dead.end()) {
|
2016-09-21 16:11:01 +02:00
|
|
|
|
throw Error(format("cannot delete path '%1%' since it is still alive") %
|
|
|
|
|
i);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2013-03-08 01:24:59 +01:00
|
|
|
|
}
|
2012-12-20 17:32:15 +01:00
|
|
|
|
|
2009-11-23 17:34:24 +01:00
|
|
|
|
} else if (options.maxFreed > 0) {
|
|
|
|
|
if (state.shouldDelete) {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
LOG(INFO) << "deleting garbage...";
|
2017-07-30 13:27:57 +02:00
|
|
|
|
} else {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
LOG(ERROR) << "determining live/dead paths...";
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2012-07-31 01:55:41 +02:00
|
|
|
|
|
2020-05-17 17:31:57 +02:00
|
|
|
|
try {
|
2012-12-20 17:32:15 +01:00
|
|
|
|
AutoCloseDir dir(opendir(realStoreDir.c_str()));
|
2020-05-19 02:02:44 +02:00
|
|
|
|
if (!dir) {
|
2016-09-21 16:11:01 +02:00
|
|
|
|
throw SysError(format("opening directory '%1%'") % realStoreDir);
|
2020-05-19 02:02:44 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2016-09-21 16:11:01 +02:00
|
|
|
|
/* 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
|
2011-12-22 16:55:53 +01:00
|
|
|
|
paths, since unreachable paths could become reachable
|
2016-09-21 16:11:01 +02:00
|
|
|
|
again. We don't use readDirectory() here so that GCing
|
2011-12-22 16:55:53 +01:00
|
|
|
|
can start faster. */
|
2016-09-21 16:11:01 +02:00
|
|
|
|
Paths entries;
|
2020-08-02 00:32:00 +02:00
|
|
|
|
struct dirent* dirent;
|
2016-09-21 16:11:01 +02:00
|
|
|
|
while (errno = 0, dirent = readdir(dir.get())) {
|
2011-12-22 16:55:53 +01:00
|
|
|
|
checkInterrupt();
|
2020-05-24 23:29:21 +02:00
|
|
|
|
std::string name = dirent->d_name;
|
2016-09-21 16:11:01 +02:00
|
|
|
|
if (name == "." || name == "..") {
|
|
|
|
|
continue;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2016-06-01 14:49:12 +02:00
|
|
|
|
Path path = storeDir + "/" + name;
|
2016-04-21 18:21:25 +02:00
|
|
|
|
if (isStorePath(path) && isValidPath(path)) {
|
2016-09-21 16:11:01 +02:00
|
|
|
|
entries.push_back(path);
|
2009-11-23 17:34:24 +01:00
|
|
|
|
} else {
|
2016-09-21 16:11:01 +02:00
|
|
|
|
tryToDelete(state, path);
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2012-07-31 01:55:41 +02:00
|
|
|
|
|
2017-01-16 22:39:27 +01:00
|
|
|
|
dir.reset();
|
2011-12-22 16:55:53 +01:00
|
|
|
|
|
|
|
|
|
/* 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. */
|
2020-05-24 23:29:21 +02:00
|
|
|
|
std::vector<Path> entries_(entries.begin(), entries.end());
|
2018-06-13 16:56:19 +02:00
|
|
|
|
std::mt19937 gen(1);
|
|
|
|
|
std::shuffle(entries_.begin(), entries_.end(), gen);
|
2012-03-26 20:43:33 +02:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
for (auto& i : entries_) {
|
|
|
|
|
tryToDelete(state, i);
|
2020-05-19 20:04:08 +02:00
|
|
|
|
}
|
2012-12-20 17:32:15 +01:00
|
|
|
|
|
|
|
|
|
} catch (GCLimitReached& e) {
|
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
if (state.options.action == GCOptions::gcReturnLive) {
|
|
|
|
|
state.results.paths = state.alive;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
if (state.options.action == GCOptions::gcReturnDead) {
|
|
|
|
|
state.results.paths = state.dead;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-26 20:43:33 +02:00
|
|
|
|
/* Allow other processes to add to the store from here on. */
|
2016-07-11 21:44:44 +02:00
|
|
|
|
fdGCLock = -1;
|
2013-12-10 13:13:59 +01:00
|
|
|
|
fds.clear();
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2012-12-20 17:32:15 +01:00
|
|
|
|
/* Delete the trash directory. */
|
2020-05-19 02:02:44 +02:00
|
|
|
|
LOG(INFO) << "deleting " << trashDir;
|
2016-06-02 15:08:18 +02:00
|
|
|
|
deleteGarbage(state, trashDir);
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2012-07-23 21:48:30 +02:00
|
|
|
|
/* Clean up the links directory. */
|
2012-09-13 00:49:35 +02:00
|
|
|
|
if (options.action == GCOptions::gcDeleteDead ||
|
|
|
|
|
options.action == GCOptions::gcDeleteSpecific) {
|
2020-05-19 02:02:44 +02:00
|
|
|
|
LOG(INFO) << "deleting unused links...";
|
2012-09-13 00:49:35 +02:00
|
|
|
|
removeUnusedLinks(state);
|
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
2012-09-13 20:33:41 +02:00
|
|
|
|
/* While we're at it, vacuum the database. */
|
2014-11-19 18:02:39 +01:00
|
|
|
|
// if (options.action == GCOptions::gcDeleteDead) { vacuumDB(); }
|
2004-08-25 13:43:49 +02:00
|
|
|
|
}
|
2006-09-04 23:06:23 +02:00
|
|
|
|
|
2017-09-05 20:43:42 +02:00
|
|
|
|
void LocalStore::autoGC(bool sync) {
|
2019-08-02 17:07:33 +02:00
|
|
|
|
static auto fakeFreeSpaceFile = getEnv("_NIX_TEST_FREE_SPACE_FILE", "");
|
2008-09-17 12:02:55 +02:00
|
|
|
|
|
2019-08-02 17:07:33 +02:00
|
|
|
|
auto getAvail = [this]() -> uint64_t {
|
|
|
|
|
if (!fakeFreeSpaceFile.empty()) {
|
|
|
|
|
return std::stoll(readFile(fakeFreeSpaceFile));
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2019-08-02 17:07:33 +02:00
|
|
|
|
|
2020-08-02 00:32:00 +02:00
|
|
|
|
struct statvfs st;
|
2020-05-20 23:27:37 +02:00
|
|
|
|
if (statvfs(realStoreDir.c_str(), &st) != 0) {
|
2019-08-02 17:07:33 +02:00
|
|
|
|
throw SysError("getting filesystem info about '%s'", realStoreDir);
|
2020-05-19 02:02:44 +02:00
|
|
|
|
}
|
2019-08-02 17:07:33 +02:00
|
|
|
|
|
2020-08-02 02:17:44 +02:00
|
|
|
|
return static_cast<uint64_t>(st.f_bavail) * st.f_bsize;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
};
|
2017-09-05 20:43:42 +02:00
|
|
|
|
|
|
|
|
|
std::shared_future<void> future;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
auto state(_state.lock());
|
|
|
|
|
|
|
|
|
|
if (state->gcRunning) {
|
|
|
|
|
future = state->gcFuture;
|
2020-05-19 02:02:44 +02:00
|
|
|
|
DLOG(INFO) << "waiting for auto-GC to finish";
|
2017-09-05 20:43:42 +02:00
|
|
|
|
goto sync;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2017-09-05 20:43:42 +02:00
|
|
|
|
|
|
|
|
|
auto now = std::chrono::steady_clock::now();
|
|
|
|
|
|
|
|
|
|
if (now < state->lastGCCheck +
|
|
|
|
|
std::chrono::seconds(settings.minFreeCheckInterval)) {
|
|
|
|
|
return;
|
2020-05-19 21:47:23 +02:00
|
|
|
|
}
|
2017-09-05 20:43:42 +02:00
|
|
|
|
|
|
|
|
|
auto avail = getAvail();
|
|
|
|
|
|
|
|
|
|
state->lastGCCheck = now;
|
|
|
|
|
|
|
|
|
|
if (avail >= settings.minFree || avail >= settings.maxFree) {
|
|
|
|
|
return;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2017-09-05 20:43:42 +02:00
|
|
|
|
|
|
|
|
|
if (avail > state->availAfterGC * 0.97) {
|
|
|
|
|
return;
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2017-09-05 20:43:42 +02:00
|
|
|
|
|
2017-09-06 11:37:58 +02:00
|
|
|
|
state->gcRunning = true;
|
2017-09-05 20:43:42 +02:00
|
|
|
|
|
2017-09-06 11:37:58 +02:00
|
|
|
|
std::promise<void> promise;
|
|
|
|
|
future = state->gcFuture = promise.get_future().share();
|
2017-09-05 20:43:42 +02:00
|
|
|
|
|
2017-09-06 11:37:58 +02:00
|
|
|
|
std::thread([promise{std::move(promise)}, this, avail, getAvail]() mutable {
|
2020-05-17 17:31:57 +02:00
|
|
|
|
try {
|
2017-09-06 11:37:58 +02:00
|
|
|
|
/* 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();
|
2017-09-05 20:43:42 +02:00
|
|
|
|
promise.set_value();
|
2020-05-17 17:31:57 +02:00
|
|
|
|
});
|
2017-09-05 20:43:42 +02:00
|
|
|
|
|
2019-08-29 12:09:58 +02:00
|
|
|
|
GCOptions options;
|
|
|
|
|
options.maxFreed = settings.maxFree - avail;
|
|
|
|
|
|
2020-05-19 02:02:44 +02:00
|
|
|
|
LOG(INFO) << "running auto-GC to free " << options.maxFreed << " bytes";
|
2017-09-05 20:43:42 +02:00
|
|
|
|
|
2017-09-06 11:37:58 +02:00
|
|
|
|
GCResults results;
|
|
|
|
|
|
|
|
|
|
collectGarbage(options, results);
|
|
|
|
|
|
|
|
|
|
_state.lock()->availAfterGC = getAvail();
|
2017-09-05 20:43:42 +02:00
|
|
|
|
|
2017-09-06 11:37:58 +02:00
|
|
|
|
} catch (...) {
|
|
|
|
|
// FIXME: we could propagate the exception to the
|
|
|
|
|
// future, but we don't really care.
|
|
|
|
|
ignoreException();
|
2020-05-17 17:31:57 +02:00
|
|
|
|
}
|
2017-09-05 20:43:42 +02:00
|
|
|
|
}).detach();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sync:
|
|
|
|
|
// Wait for the future outside of the state lock.
|
|
|
|
|
if (sync) {
|
|
|
|
|
future.get();
|
2020-05-19 19:55:58 +02:00
|
|
|
|
}
|
2017-09-05 20:43:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
2006-09-04 23:06:23 +02:00
|
|
|
|
} // namespace nix
|