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 13:28:26 +00:00
|
|
|
|
#include "derivations.hh"
|
2006-09-04 21:06:23 +00:00
|
|
|
|
#include "globals.hh"
|
2006-11-30 17:43:04 +00:00
|
|
|
|
#include "local-store.hh"
|
2005-01-31 10:27:25 +00:00
|
|
|
|
|
2008-09-17 10:02:55 +00:00
|
|
|
|
#include <functional>
|
|
|
|
|
#include <queue>
|
2009-11-24 09:53:18 +00:00
|
|
|
|
#include <algorithm>
|
2008-09-17 10:02:55 +00:00
|
|
|
|
|
2004-08-25 16:54:08 +00:00
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/stat.h>
|
2005-01-31 10:27:25 +00:00
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <fcntl.h>
|
2004-08-25 16:54:08 +00:00
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
|
2006-09-04 21:06:23 +00:00
|
|
|
|
namespace nix {
|
|
|
|
|
|
|
|
|
|
|
2005-01-31 22:23:49 +00:00
|
|
|
|
static string gcLockName = "gc.lock";
|
2005-02-01 13:48:46 +00:00
|
|
|
|
static string tempRootsDir = "temproots";
|
|
|
|
|
static string gcRootsDir = "gcroots";
|
2005-01-31 22:23:49 +00: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. */
|
2010-08-31 11:47:31 +00:00
|
|
|
|
int LocalStore::openGCLock(LockType lockType)
|
2005-01-31 22:01:55 +00:00
|
|
|
|
{
|
2005-01-31 22:23:49 +00:00
|
|
|
|
Path fnGCLock = (format("%1%/%2%")
|
2012-07-30 23:55:41 +00:00
|
|
|
|
% settings.nixStateDir % gcLockName).str();
|
|
|
|
|
|
2014-08-20 15:00:17 +00:00
|
|
|
|
debug(format("acquiring global GC lock ‘%1%’") % fnGCLock);
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2005-01-31 22:23:49 +00:00
|
|
|
|
AutoCloseFD fdGCLock = open(fnGCLock.c_str(), O_RDWR | O_CREAT, 0600);
|
|
|
|
|
if (fdGCLock == -1)
|
2014-08-20 15:00:17 +00:00
|
|
|
|
throw SysError(format("opening global GC lock ‘%1%’") % fnGCLock);
|
2012-03-05 19:29:00 +00:00
|
|
|
|
closeOnExec(fdGCLock);
|
2005-01-31 22:23:49 +00:00
|
|
|
|
|
2006-09-14 22:30:33 +00:00
|
|
|
|
if (!lockFile(fdGCLock, lockType, false)) {
|
|
|
|
|
printMsg(lvlError, format("waiting for the big garbage collector lock..."));
|
|
|
|
|
lockFile(fdGCLock, lockType, true);
|
|
|
|
|
}
|
2005-01-31 22:23:49 +00:00
|
|
|
|
|
|
|
|
|
/* !!! Restrict read permission on the GC root. Otherwise any
|
|
|
|
|
process that can open the file for reading can DoS the
|
|
|
|
|
collector. */
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2005-01-31 22:23:49 +00:00
|
|
|
|
return fdGCLock.borrow();
|
2005-01-31 22:01:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-02-27 22:17:53 +00:00
|
|
|
|
static void makeSymlink(const Path & link, const Path & target)
|
2005-02-01 13:48:46 +00:00
|
|
|
|
{
|
|
|
|
|
/* Create directories up to `gcRoot'. */
|
|
|
|
|
createDirs(dirOf(link));
|
|
|
|
|
|
2012-04-16 16:47:01 +00:00
|
|
|
|
/* Create the new symlink. */
|
|
|
|
|
Path tempLink = (format("%1%.tmp-%2%-%3%")
|
|
|
|
|
% link % getpid() % rand()).str();
|
2014-02-27 22:17:53 +00:00
|
|
|
|
createSymlink(target, tempLink);
|
2012-04-16 16:47:01 +00:00
|
|
|
|
|
|
|
|
|
/* Atomically replace the old one. */
|
|
|
|
|
if (rename(tempLink.c_str(), link.c_str()) == -1)
|
2014-08-20 15:00:17 +00:00
|
|
|
|
throw SysError(format("cannot rename ‘%1%’ to ‘%2%’")
|
2012-04-16 16:47:01 +00:00
|
|
|
|
% tempLink % link);
|
2005-02-01 13:48:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2006-12-02 16:41:36 +00:00
|
|
|
|
void LocalStore::syncWithGC()
|
|
|
|
|
{
|
|
|
|
|
AutoCloseFD fdGCLock = openGCLock(ltRead);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2006-12-04 23:29:16 +00:00
|
|
|
|
void LocalStore::addIndirectRoot(const Path & path)
|
|
|
|
|
{
|
|
|
|
|
string hash = printHash32(hashString(htSHA1, path));
|
|
|
|
|
Path realRoot = canonPath((format("%1%/%2%/auto/%3%")
|
2012-07-30 23:55:41 +00:00
|
|
|
|
% settings.nixStateDir % gcRootsDir % hash).str());
|
2014-02-27 22:17:53 +00:00
|
|
|
|
makeSymlink(realRoot, path);
|
2006-12-04 23:29:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-02-11 15:14:42 +00:00
|
|
|
|
Path Store::addPermRoot(const Path & _storePath,
|
2011-08-31 21:11:50 +00:00
|
|
|
|
const Path & _gcRoot, bool indirect, bool allowOutsideRootsDir)
|
2005-02-01 12:36:25 +00:00
|
|
|
|
{
|
|
|
|
|
Path storePath(canonPath(_storePath));
|
|
|
|
|
Path gcRoot(canonPath(_gcRoot));
|
2005-02-01 13:48:46 +00:00
|
|
|
|
assertStorePath(storePath);
|
2005-02-01 12:36:25 +00:00
|
|
|
|
|
2007-06-11 11:36:22 +00: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);
|
|
|
|
|
|
2005-02-01 13:48:46 +00:00
|
|
|
|
if (indirect) {
|
2015-03-06 15:39:48 +00:00
|
|
|
|
/* Don't clobber the link if it already exists and doesn't
|
2012-04-16 16:47:01 +00:00
|
|
|
|
point to the Nix store. */
|
|
|
|
|
if (pathExists(gcRoot) && (!isLink(gcRoot) || !isInStore(readLink(gcRoot))))
|
2014-08-20 15:00:17 +00:00
|
|
|
|
throw Error(format("cannot create symlink ‘%1%’; already exists") % gcRoot);
|
2014-02-27 22:17:53 +00:00
|
|
|
|
makeSymlink(gcRoot, storePath);
|
2016-02-11 15:14:42 +00:00
|
|
|
|
addIndirectRoot(gcRoot);
|
2005-02-01 13:48:46 +00:00
|
|
|
|
}
|
2005-02-01 12:36:25 +00:00
|
|
|
|
|
2005-02-01 13:48:46 +00:00
|
|
|
|
else {
|
2006-09-14 22:30:33 +00:00
|
|
|
|
if (!allowOutsideRootsDir) {
|
2012-07-30 23:55:41 +00:00
|
|
|
|
Path rootsDir = canonPath((format("%1%/%2%") % settings.nixStateDir % gcRootsDir).str());
|
|
|
|
|
|
2006-09-14 22:30:33 +00:00
|
|
|
|
if (string(gcRoot, 0, rootsDir.size() + 1) != rootsDir + "/")
|
|
|
|
|
throw Error(format(
|
2014-08-20 15:00:17 +00:00
|
|
|
|
"path ‘%1%’ is not a valid garbage collector root; "
|
|
|
|
|
"it's not in the directory ‘%2%’")
|
2006-09-14 22:30:33 +00:00
|
|
|
|
% gcRoot % rootsDir);
|
|
|
|
|
}
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2014-08-13 15:44:41 +00:00
|
|
|
|
if (baseNameOf(gcRoot) == baseNameOf(storePath))
|
|
|
|
|
writeFile(gcRoot, "");
|
|
|
|
|
else
|
|
|
|
|
makeSymlink(gcRoot, storePath);
|
2005-02-01 13:48:46 +00:00
|
|
|
|
}
|
2005-02-01 12:36:25 +00:00
|
|
|
|
|
2008-06-14 16:02:31 +00: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-30 23:55:41 +00:00
|
|
|
|
if (settings.checkRootReachability) {
|
2016-02-11 15:14:42 +00:00
|
|
|
|
Roots roots = findRoots();
|
2007-03-19 09:16:47 +00:00
|
|
|
|
if (roots.find(gcRoot) == roots.end())
|
2012-07-30 23:55:41 +00:00
|
|
|
|
printMsg(lvlError,
|
2007-03-19 09:16:47 +00:00
|
|
|
|
format(
|
2014-08-20 15:00:17 +00:00
|
|
|
|
"warning: ‘%1%’ is not in a directory where the garbage collector looks for roots; "
|
|
|
|
|
"therefore, ‘%2%’ might be removed by the garbage collector")
|
2007-03-19 09:16:47 +00:00
|
|
|
|
% gcRoot % storePath);
|
|
|
|
|
}
|
2010-08-30 14:53:03 +00:00
|
|
|
|
|
2006-12-02 16:41:36 +00: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 15:14:42 +00:00
|
|
|
|
syncWithGC();
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2005-02-01 12:36:25 +00:00
|
|
|
|
return gcRoot;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2006-12-02 16:41:36 +00:00
|
|
|
|
void LocalStore::addTempRoot(const Path & path)
|
2005-01-31 10:27:25 +00:00
|
|
|
|
{
|
|
|
|
|
/* Create the temporary roots file for this process. */
|
|
|
|
|
if (fdTempRoots == -1) {
|
|
|
|
|
|
|
|
|
|
while (1) {
|
2012-07-30 23:55:41 +00:00
|
|
|
|
Path dir = (format("%1%/%2%") % settings.nixStateDir % tempRootsDir).str();
|
2005-03-24 17:46:38 +00:00
|
|
|
|
createDirs(dir);
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2005-03-24 17:46:38 +00:00
|
|
|
|
fnTempRoots = (format("%1%/%2%")
|
|
|
|
|
% dir % getpid()).str();
|
2005-01-31 22:23:49 +00:00
|
|
|
|
|
|
|
|
|
AutoCloseFD fdGCLock = openGCLock(ltRead);
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2006-06-20 17:48:10 +00:00
|
|
|
|
if (pathExists(fnTempRoots))
|
|
|
|
|
/* It *must* be stale, since there can be no two
|
|
|
|
|
processes with the same pid. */
|
2012-02-14 23:28:01 +00:00
|
|
|
|
unlink(fnTempRoots.c_str());
|
2006-06-20 17:48:10 +00:00
|
|
|
|
|
2012-07-30 23:55:41 +00:00
|
|
|
|
fdTempRoots = openLockFile(fnTempRoots, true);
|
2005-01-31 10:27:25 +00:00
|
|
|
|
|
2005-01-31 22:23:49 +00:00
|
|
|
|
fdGCLock.close();
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2014-08-20 15:00:17 +00:00
|
|
|
|
debug(format("acquiring read lock on ‘%1%’") % fnTempRoots);
|
2005-01-31 10:27:25 +00:00
|
|
|
|
lockFile(fdTempRoots, ltRead, true);
|
|
|
|
|
|
|
|
|
|
/* Check whether the garbage collector didn't get in our
|
|
|
|
|
way. */
|
|
|
|
|
struct stat st;
|
|
|
|
|
if (fstat(fdTempRoots, &st) == -1)
|
2014-08-20 15:00:17 +00:00
|
|
|
|
throw SysError(format("statting ‘%1%’") % fnTempRoots);
|
2005-01-31 10:27:25 +00:00
|
|
|
|
if (st.st_size == 0) break;
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2005-01-31 10:27:25 +00: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. */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Upgrade the lock to a write lock. This will cause us to block
|
|
|
|
|
if the garbage collector is holding our lock. */
|
2014-08-20 15:00:17 +00:00
|
|
|
|
debug(format("acquiring write lock on ‘%1%’") % fnTempRoots);
|
2005-01-31 10:27:25 +00:00
|
|
|
|
lockFile(fdTempRoots, ltWrite, true);
|
|
|
|
|
|
|
|
|
|
string s = path + '\0';
|
2014-12-12 13:35:44 +00:00
|
|
|
|
writeFull(fdTempRoots, s);
|
2005-01-31 10:27:25 +00:00
|
|
|
|
|
|
|
|
|
/* Downgrade to a read lock. */
|
2014-08-20 15:00:17 +00:00
|
|
|
|
debug(format("downgrading to read lock on ‘%1%’") % fnTempRoots);
|
2005-01-31 10:27:25 +00:00
|
|
|
|
lockFile(fdTempRoots, ltRead, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-03-29 23:49:23 +00:00
|
|
|
|
typedef std::shared_ptr<AutoCloseFD> FDPtr;
|
2005-01-31 10:27:25 +00:00
|
|
|
|
typedef list<FDPtr> FDs;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void readTempRoots(PathSet & tempRoots, FDs & fds)
|
|
|
|
|
{
|
|
|
|
|
/* Read the `temproots' directory for per-process temporary root
|
|
|
|
|
files. */
|
2014-08-01 14:37:47 +00:00
|
|
|
|
DirEntries tempRootFiles = readDirectory(
|
2012-07-30 23:55:41 +00:00
|
|
|
|
(format("%1%/%2%") % settings.nixStateDir % tempRootsDir).str());
|
2005-01-31 10:27:25 +00:00
|
|
|
|
|
2014-08-01 14:37:47 +00:00
|
|
|
|
for (auto & i : tempRootFiles) {
|
|
|
|
|
Path path = (format("%1%/%2%/%3%") % settings.nixStateDir % tempRootsDir % i.name).str();
|
2005-01-31 10:27:25 +00:00
|
|
|
|
|
2014-08-20 15:00:17 +00:00
|
|
|
|
debug(format("reading temporary root file ‘%1%’") % path);
|
2005-01-31 10:27:25 +00:00
|
|
|
|
FDPtr fd(new AutoCloseFD(open(path.c_str(), O_RDWR, 0666)));
|
|
|
|
|
if (*fd == -1) {
|
|
|
|
|
/* It's okay if the file has disappeared. */
|
|
|
|
|
if (errno == ENOENT) continue;
|
2014-08-20 15:00:17 +00:00
|
|
|
|
throw SysError(format("opening temporary roots file ‘%1%’") % path);
|
2005-01-31 10:27:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
|
2005-01-31 10:27:25 +00: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, ltWrite, false)) {
|
2014-08-20 15:00:17 +00:00
|
|
|
|
printMsg(lvlError, format("removing stale temporary roots file ‘%1%’") % path);
|
2005-01-31 21:20:59 +00:00
|
|
|
|
unlink(path.c_str());
|
2014-12-12 13:35:44 +00:00
|
|
|
|
writeFull(*fd, "d");
|
2005-01-31 10:27:25 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Acquire a read lock. This will prevent the owning process
|
|
|
|
|
from upgrading to a write lock, therefore it will block in
|
|
|
|
|
addTempRoot(). */
|
2014-08-20 15:00:17 +00:00
|
|
|
|
debug(format("waiting for read lock on ‘%1%’") % path);
|
2005-01-31 10:27:25 +00:00
|
|
|
|
lockFile(*fd, ltRead, true);
|
|
|
|
|
|
|
|
|
|
/* Read the entire file. */
|
2005-02-01 22:07:48 +00:00
|
|
|
|
string contents = readFile(*fd);
|
2005-01-31 10:27:25 +00:00
|
|
|
|
|
|
|
|
|
/* Extract the roots. */
|
2006-05-11 02:19:43 +00:00
|
|
|
|
string::size_type pos = 0, end;
|
2005-01-31 10:27:25 +00:00
|
|
|
|
|
|
|
|
|
while ((end = contents.find((char) 0, pos)) != string::npos) {
|
|
|
|
|
Path root(contents, pos, end - pos);
|
2014-08-20 15:00:17 +00:00
|
|
|
|
debug(format("got temporary root ‘%1%’") % root);
|
2005-01-31 10:27:25 +00:00
|
|
|
|
assertStorePath(root);
|
|
|
|
|
tempRoots.insert(root);
|
|
|
|
|
pos = end + 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fds.push_back(fd); /* keep open */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
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 13:28:26 +00:00
|
|
|
|
void LocalStore::findRoots(const Path & path, unsigned char type, Roots & roots)
|
2005-02-01 15:05:32 +00: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 13:28:26 +00:00
|
|
|
|
auto foundRoot = [&](const Path & path, const Path & target) {
|
|
|
|
|
Path storePath = toStorePath(target);
|
|
|
|
|
if (isValidPath(storePath))
|
|
|
|
|
roots[path] = storePath;
|
|
|
|
|
else
|
|
|
|
|
printMsg(lvlInfo, format("skipping invalid root from ‘%1%’ to ‘%2%’") % path % storePath);
|
|
|
|
|
};
|
2006-12-05 01:31:45 +00:00
|
|
|
|
|
2013-07-12 12:01:25 +00:00
|
|
|
|
try {
|
|
|
|
|
|
2014-10-03 20:37:51 +00:00
|
|
|
|
if (type == DT_UNKNOWN)
|
|
|
|
|
type = getFileType(path);
|
2006-12-05 01:31:45 +00:00
|
|
|
|
|
2014-08-01 15:20:25 +00:00
|
|
|
|
if (type == DT_DIR) {
|
2014-08-01 14:37:47 +00:00
|
|
|
|
for (auto & i : readDirectory(path))
|
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 13:28:26 +00:00
|
|
|
|
findRoots(path + "/" + i.name, i.type, roots);
|
2005-02-01 15:05:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-08-01 15:20:25 +00:00
|
|
|
|
else if (type == DT_LNK) {
|
2013-07-12 12:01:25 +00:00
|
|
|
|
Path target = readLink(path);
|
|
|
|
|
if (isInStore(target))
|
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 13:28:26 +00:00
|
|
|
|
foundRoot(path, target);
|
2013-07-12 12:01:25 +00:00
|
|
|
|
|
|
|
|
|
/* Handle indirect roots. */
|
|
|
|
|
else {
|
|
|
|
|
target = absPath(target, dirOf(path));
|
|
|
|
|
if (!pathExists(target)) {
|
|
|
|
|
if (isInDir(path, settings.nixStateDir + "/" + gcRootsDir + "/auto")) {
|
2014-08-20 15:00:17 +00:00
|
|
|
|
printMsg(lvlInfo, format("removing stale link from ‘%1%’ to ‘%2%’") % path % target);
|
2013-07-12 12:01:25 +00:00
|
|
|
|
unlink(path.c_str());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
struct stat st2 = lstat(target);
|
|
|
|
|
if (!S_ISLNK(st2.st_mode)) return;
|
|
|
|
|
Path target2 = readLink(target);
|
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 13:28:26 +00:00
|
|
|
|
if (isInStore(target2)) foundRoot(target, target2);
|
2006-12-05 01:31:45 +00:00
|
|
|
|
}
|
2005-02-01 15:05:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2006-12-05 01:31:45 +00:00
|
|
|
|
|
2014-08-01 15:20:25 +00:00
|
|
|
|
else if (type == DT_REG) {
|
2014-08-01 14:46:01 +00:00
|
|
|
|
Path storePath = settings.nixStore + "/" + baseNameOf(path);
|
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 13:28:26 +00:00
|
|
|
|
if (isValidPath(storePath))
|
2014-08-01 14:46:01 +00:00
|
|
|
|
roots[path] = storePath;
|
|
|
|
|
}
|
|
|
|
|
|
2006-12-05 01:31:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
catch (SysError & e) {
|
|
|
|
|
/* We only ignore permanent failures. */
|
|
|
|
|
if (e.errNo == EACCES || e.errNo == ENOENT || e.errNo == ENOTDIR)
|
2014-08-20 15:00:17 +00:00
|
|
|
|
printMsg(lvlInfo, format("cannot read potential root ‘%1%’") % path);
|
2006-12-05 01:31:45 +00:00
|
|
|
|
else
|
|
|
|
|
throw;
|
2005-02-01 15:05:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-07-12 12:01:25 +00:00
|
|
|
|
Roots LocalStore::findRoots()
|
2006-12-05 00:34:42 +00:00
|
|
|
|
{
|
2006-12-05 01:31:45 +00:00
|
|
|
|
Roots roots;
|
|
|
|
|
|
2013-07-12 12:01:25 +00:00
|
|
|
|
/* Process direct roots in {gcroots,manifests,profiles}. */
|
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 13:28:26 +00:00
|
|
|
|
findRoots(settings.nixStateDir + "/" + gcRootsDir, DT_UNKNOWN, roots);
|
2014-11-04 09:41:29 +00:00
|
|
|
|
if (pathExists(settings.nixStateDir + "/manifests"))
|
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 13:28:26 +00:00
|
|
|
|
findRoots(settings.nixStateDir + "/manifests", DT_UNKNOWN, roots);
|
|
|
|
|
findRoots(settings.nixStateDir + "/profiles", DT_UNKNOWN, roots);
|
2006-12-05 01:31:45 +00:00
|
|
|
|
|
2013-07-12 12:01:25 +00:00
|
|
|
|
return roots;
|
2006-12-05 00:34:42 +00: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 13:28:26 +00:00
|
|
|
|
void LocalStore::findRuntimeRoots(PathSet & roots)
|
2006-07-20 12:17:25 +00:00
|
|
|
|
{
|
|
|
|
|
Path rootFinder = getEnv("NIX_ROOT_FINDER",
|
2012-07-30 23:55:41 +00:00
|
|
|
|
settings.nixLibexecDir + "/nix/find-runtime-roots.pl");
|
2006-07-20 12:17:25 +00:00
|
|
|
|
|
|
|
|
|
if (rootFinder.empty()) return;
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2014-08-20 15:00:17 +00:00
|
|
|
|
debug(format("executing ‘%1%’ to find additional roots") % rootFinder);
|
2006-07-20 12:17:25 +00:00
|
|
|
|
|
|
|
|
|
string result = runProgram(rootFinder);
|
|
|
|
|
|
2013-04-26 10:07:25 +00:00
|
|
|
|
StringSet paths = tokenizeString<StringSet>(result, "\n");
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2015-07-17 17:24:28 +00:00
|
|
|
|
for (auto & i : paths)
|
|
|
|
|
if (isInStore(i)) {
|
|
|
|
|
Path path = toStorePath(i);
|
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 13:28:26 +00:00
|
|
|
|
if (roots.find(path) == roots.end() && isValidPath(path)) {
|
2014-08-20 15:00:17 +00:00
|
|
|
|
debug(format("got additional root ‘%1%’") % path);
|
2006-07-20 12:17:25 +00:00
|
|
|
|
roots.insert(path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2008-06-18 14:20:16 +00:00
|
|
|
|
struct GCLimitReached { };
|
|
|
|
|
|
|
|
|
|
|
2009-11-23 16:34:24 +00:00
|
|
|
|
struct LocalStore::GCState
|
2008-06-13 18:25:24 +00:00
|
|
|
|
{
|
2009-11-23 16:34:24 +00:00
|
|
|
|
GCOptions options;
|
|
|
|
|
GCResults & results;
|
|
|
|
|
PathSet roots;
|
|
|
|
|
PathSet tempRoots;
|
2012-12-20 16:32:15 +00:00
|
|
|
|
PathSet dead;
|
|
|
|
|
PathSet alive;
|
2009-11-23 16:34:24 +00:00
|
|
|
|
bool gcKeepOutputs;
|
|
|
|
|
bool gcKeepDerivations;
|
2012-03-26 18:43:33 +00:00
|
|
|
|
unsigned long long bytesInvalidated;
|
2015-06-30 19:41:26 +00:00
|
|
|
|
bool moveToTrash = true;
|
2012-12-20 16:32:15 +00:00
|
|
|
|
Path trashDir;
|
|
|
|
|
bool shouldDelete;
|
2012-03-26 18:43:33 +00:00
|
|
|
|
GCState(GCResults & results_) : results(results_), bytesInvalidated(0) { }
|
2009-11-23 16:34:24 +00:00
|
|
|
|
};
|
2008-06-18 14:20:16 +00:00
|
|
|
|
|
2009-11-23 16:34:24 +00:00
|
|
|
|
|
|
|
|
|
bool LocalStore::isActiveTempFile(const GCState & state,
|
|
|
|
|
const Path & path, const string & suffix)
|
2008-09-17 12:54:07 +00:00
|
|
|
|
{
|
2009-11-23 16:34:24 +00:00
|
|
|
|
return hasSuffix(path, suffix)
|
|
|
|
|
&& state.tempRoots.find(string(path, 0, path.size() - suffix.size())) != state.tempRoots.end();
|
|
|
|
|
}
|
2008-09-17 12:54:07 +00:00
|
|
|
|
|
2012-03-26 18:00:02 +00:00
|
|
|
|
|
2012-03-26 18:43:33 +00:00
|
|
|
|
void LocalStore::deleteGarbage(GCState & state, const Path & path)
|
|
|
|
|
{
|
2012-08-02 02:34:46 +00:00
|
|
|
|
unsigned long long bytesFreed;
|
2013-11-14 10:57:37 +00:00
|
|
|
|
deletePath(path, bytesFreed);
|
2012-03-26 18:43:33 +00:00
|
|
|
|
state.results.bytesFreed += bytesFreed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
void LocalStore::deletePathRecursive(GCState & state, const Path & path)
|
2009-11-23 16:34:24 +00:00
|
|
|
|
{
|
2010-10-14 15:55:51 +00:00
|
|
|
|
checkInterrupt();
|
2012-03-26 18:56:30 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
unsigned long long size = 0;
|
|
|
|
|
|
|
|
|
|
if (isValidPath(path)) {
|
|
|
|
|
PathSet referrers;
|
|
|
|
|
queryReferrers(path, referrers);
|
2015-07-17 17:24:28 +00:00
|
|
|
|
for (auto & i : referrers)
|
|
|
|
|
if (i != path) deletePathRecursive(state, i);
|
2012-12-20 16:32:15 +00:00
|
|
|
|
size = queryPathInfo(path).narSize;
|
|
|
|
|
invalidatePathChecked(path);
|
|
|
|
|
}
|
2012-07-23 19:48:30 +00:00
|
|
|
|
|
2012-03-26 18:56:30 +00:00
|
|
|
|
struct stat st;
|
|
|
|
|
if (lstat(path.c_str(), &st)) {
|
2012-12-20 16:32:15 +00:00
|
|
|
|
if (errno == ENOENT) return;
|
2012-03-26 18:56:30 +00:00
|
|
|
|
throw SysError(format("getting status of %1%") % path);
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-20 15:00:17 +00:00
|
|
|
|
printMsg(lvlInfo, format("deleting ‘%1%’") % path);
|
2009-11-23 16:34:24 +00:00
|
|
|
|
|
2013-01-04 14:17:19 +00:00
|
|
|
|
state.results.paths.insert(path);
|
|
|
|
|
|
2012-12-20 16:32:15 +00: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 19:41:26 +00:00
|
|
|
|
if (state.moveToTrash && S_ISDIR(st.st_mode)) {
|
2012-12-20 16:32:15 +00: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 19:41:26 +00:00
|
|
|
|
try {
|
|
|
|
|
if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1)
|
|
|
|
|
throw SysError(format("making ‘%1%’ writable") % path);
|
|
|
|
|
Path tmp = state.trashDir + "/" + baseNameOf(path);
|
|
|
|
|
if (rename(path.c_str(), tmp.c_str()))
|
|
|
|
|
throw SysError(format("unable to rename ‘%1%’ to ‘%2%’") % path % tmp);
|
|
|
|
|
state.bytesInvalidated += size;
|
|
|
|
|
} catch (SysError & e) {
|
|
|
|
|
if (e.errNo == ENOSPC) {
|
|
|
|
|
printMsg(lvlInfo, format("note: can't create move ‘%1%’: %2%") % path % e.msg());
|
|
|
|
|
deleteGarbage(state, path);
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-12-20 16:32:15 +00:00
|
|
|
|
} else
|
|
|
|
|
deleteGarbage(state, path);
|
|
|
|
|
|
|
|
|
|
if (state.results.bytesFreed + state.bytesInvalidated > state.options.maxFreed) {
|
|
|
|
|
printMsg(lvlInfo, format("deleted or invalidated more than %1% bytes; stopping") % state.options.maxFreed);
|
|
|
|
|
throw GCLimitReached();
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-11-23 16:34:24 +00:00
|
|
|
|
|
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
bool LocalStore::canReachRoot(GCState & state, PathSet & visited, const Path & path)
|
|
|
|
|
{
|
|
|
|
|
if (visited.find(path) != visited.end()) return false;
|
2009-11-23 16:34:24 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
if (state.alive.find(path) != state.alive.end()) {
|
|
|
|
|
return true;
|
2009-11-23 16:34:24 +00:00
|
|
|
|
}
|
2008-09-17 12:54:07 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
if (state.dead.find(path) != state.dead.end()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2009-11-23 16:34:24 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
if (state.roots.find(path) != state.roots.end()) {
|
2014-08-20 15:00:17 +00:00
|
|
|
|
printMsg(lvlDebug, format("cannot delete ‘%1%’ because it's a root") % path);
|
2012-12-20 16:32:15 +00:00
|
|
|
|
state.alive.insert(path);
|
|
|
|
|
return true;
|
2009-11-23 16:34:24 +00:00
|
|
|
|
}
|
2008-12-12 17:03:18 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
visited.insert(path);
|
2012-03-26 18:43:33 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
if (!isValidPath(path)) return false;
|
2012-09-12 22:49:35 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
PathSet incoming;
|
2009-11-23 16:34:24 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
/* Don't delete this path if any of its referrers are alive. */
|
|
|
|
|
queryReferrers(path, incoming);
|
2012-09-12 22:49:35 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
/* If gc-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 17:24:28 +00:00
|
|
|
|
for (auto & i : outputs)
|
|
|
|
|
if (isValidPath(i) && queryDeriver(i) == path)
|
|
|
|
|
incoming.insert(i);
|
2012-12-20 16:32:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If gc-keep-outputs is set, then don't delete this path if there
|
|
|
|
|
are derivers of this path that are not garbage. */
|
|
|
|
|
if (state.gcKeepOutputs) {
|
|
|
|
|
PathSet derivers = queryValidDerivers(path);
|
2015-07-17 17:24:28 +00:00
|
|
|
|
for (auto & i : derivers)
|
|
|
|
|
incoming.insert(i);
|
2012-12-20 16:32:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-07-17 17:24:28 +00:00
|
|
|
|
for (auto & i : incoming)
|
|
|
|
|
if (i != path)
|
|
|
|
|
if (canReachRoot(state, visited, i)) {
|
2012-12-20 16:32:15 +00:00
|
|
|
|
state.alive.insert(path);
|
|
|
|
|
return true;
|
2012-09-12 22:49:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2012-09-12 22:49:35 +00:00
|
|
|
|
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
void LocalStore::tryToDelete(GCState & state, const Path & path)
|
|
|
|
|
{
|
|
|
|
|
checkInterrupt();
|
2009-11-23 16:34:24 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
if (path == linksDir || path == state.trashDir) return;
|
|
|
|
|
|
2014-08-20 15:00:17 +00:00
|
|
|
|
startNest(nest, lvlDebug, format("considering whether to delete ‘%1%’") % path);
|
2012-12-20 16:32:15 +00:00
|
|
|
|
|
|
|
|
|
if (!isValidPath(path)) {
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PathSet visited;
|
|
|
|
|
|
|
|
|
|
if (canReachRoot(state, visited, path)) {
|
2014-08-20 15:00:17 +00:00
|
|
|
|
printMsg(lvlDebug, format("cannot delete ‘%1%’ because it's still reachable") % path);
|
2012-12-20 16:32:15 +00:00
|
|
|
|
} 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. */
|
|
|
|
|
state.dead.insert(visited.begin(), visited.end());
|
|
|
|
|
if (state.shouldDelete)
|
|
|
|
|
deletePathRecursive(state, path);
|
2012-09-12 22:49:35 +00:00
|
|
|
|
}
|
2008-12-12 17:03:18 +00:00
|
|
|
|
}
|
2009-11-24 09:53:18 +00:00
|
|
|
|
|
2008-12-12 17:03:18 +00:00
|
|
|
|
|
2012-07-23 19:48:30 +00: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 02:43:03 +00:00
|
|
|
|
void LocalStore::removeUnusedLinks(const GCState & state)
|
2012-07-23 19:48:30 +00:00
|
|
|
|
{
|
|
|
|
|
AutoCloseDir dir = opendir(linksDir.c_str());
|
2014-08-20 15:00:17 +00:00
|
|
|
|
if (!dir) throw SysError(format("opening directory ‘%1%’") % linksDir);
|
2012-07-23 19:48:30 +00:00
|
|
|
|
|
2012-08-06 01:45:27 +00:00
|
|
|
|
long long actualSize = 0, unsharedSize = 0;
|
2012-08-01 23:01:50 +00:00
|
|
|
|
|
2012-07-23 19:48:30 +00:00
|
|
|
|
struct dirent * dirent;
|
|
|
|
|
while (errno = 0, dirent = readdir(dir)) {
|
|
|
|
|
checkInterrupt();
|
|
|
|
|
string name = dirent->d_name;
|
|
|
|
|
if (name == "." || name == "..") continue;
|
|
|
|
|
Path path = linksDir + "/" + name;
|
|
|
|
|
|
|
|
|
|
struct stat st;
|
|
|
|
|
if (lstat(path.c_str(), &st) == -1)
|
2014-08-20 15:00:17 +00:00
|
|
|
|
throw SysError(format("statting ‘%1%’") % path);
|
2012-07-23 19:48:30 +00:00
|
|
|
|
|
2012-08-01 23:01:50 +00:00
|
|
|
|
if (st.st_nlink != 1) {
|
|
|
|
|
unsigned long long size = st.st_blocks * 512ULL;
|
|
|
|
|
actualSize += size;
|
|
|
|
|
unsharedSize += (st.st_nlink - 1) * size;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2012-07-23 19:48:30 +00:00
|
|
|
|
|
2014-08-20 15:00:17 +00:00
|
|
|
|
printMsg(lvlTalkative, format("deleting unused link ‘%1%’") % path);
|
2012-07-23 19:48:30 +00:00
|
|
|
|
|
|
|
|
|
if (unlink(path.c_str()) == -1)
|
2014-08-20 15:00:17 +00:00
|
|
|
|
throw SysError(format("deleting ‘%1%’") % path);
|
2012-08-02 02:43:03 +00:00
|
|
|
|
|
|
|
|
|
state.results.bytesFreed += st.st_blocks * 512;
|
2012-07-23 19:48:30 +00:00
|
|
|
|
}
|
2012-08-01 23:01:50 +00:00
|
|
|
|
|
|
|
|
|
struct stat st;
|
|
|
|
|
if (stat(linksDir.c_str(), &st) == -1)
|
2014-08-20 15:00:17 +00:00
|
|
|
|
throw SysError(format("statting ‘%1%’") % linksDir);
|
2012-08-06 01:45:27 +00:00
|
|
|
|
long long overhead = st.st_blocks * 512ULL;
|
2012-08-01 23:01:50 +00:00
|
|
|
|
|
|
|
|
|
printMsg(lvlInfo, format("note: currently hard linking saves %.2f MiB")
|
|
|
|
|
% ((unsharedSize - actualSize - overhead) / (1024.0 * 1024.0)));
|
2012-07-23 19:48:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2008-06-18 09:34:17 +00:00
|
|
|
|
void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
2005-01-27 15:21:29 +00:00
|
|
|
|
{
|
2009-11-23 16:34:24 +00:00
|
|
|
|
GCState state(results);
|
2012-07-30 23:55:41 +00:00
|
|
|
|
state.options = options;
|
2012-12-20 16:32:15 +00:00
|
|
|
|
state.trashDir = settings.nixStore + "/trash";
|
2012-07-30 23:55:41 +00:00
|
|
|
|
state.gcKeepOutputs = settings.gcKeepOutputs;
|
|
|
|
|
state.gcKeepDerivations = settings.gcKeepDerivations;
|
2010-03-08 21:31:42 +00:00
|
|
|
|
|
|
|
|
|
/* Using `--ignore-liveness' with `--delete' can have unintended
|
|
|
|
|
consequences if `gc-keep-outputs' or `gc-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;
|
|
|
|
|
}
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
state.shouldDelete = options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific;
|
|
|
|
|
|
2016-02-24 16:33:53 +00:00
|
|
|
|
if (state.shouldDelete && pathExists(reservedPath))
|
|
|
|
|
deletePath(reservedPath);
|
|
|
|
|
|
2005-01-31 22:23:49 +00:00
|
|
|
|
/* Acquire the global GC root. This prevents
|
2005-01-31 10:27:25 +00:00
|
|
|
|
a) New roots from being added.
|
|
|
|
|
b) Processes from creating new temporary root files. */
|
2005-01-31 22:23:49 +00:00
|
|
|
|
AutoCloseFD fdGCLock = openGCLock(ltWrite);
|
2005-01-31 10:27:25 +00:00
|
|
|
|
|
2005-02-01 15:05:32 +00:00
|
|
|
|
/* Find the roots. Since we've grabbed the GC lock, the set of
|
|
|
|
|
permanent roots cannot increase now. */
|
2008-06-13 17:21:20 +00:00
|
|
|
|
printMsg(lvlError, format("finding garbage collector roots..."));
|
2013-07-12 12:01:25 +00:00
|
|
|
|
Roots rootMap = options.ignoreLiveness ? Roots() : findRoots();
|
2006-12-05 00:48:36 +00:00
|
|
|
|
|
2015-07-17 17:24:28 +00:00
|
|
|
|
for (auto & i : rootMap) state.roots.insert(i.second);
|
2005-02-01 15:05:32 +00:00
|
|
|
|
|
2006-07-20 12:17:25 +00:00
|
|
|
|
/* Add additional roots returned by the program specified by the
|
|
|
|
|
NIX_ROOT_FINDER environment variable. This is typically used
|
|
|
|
|
to add running programs to the set of roots (to prevent them
|
|
|
|
|
from being garbage collected). */
|
2008-06-18 09:34:17 +00:00
|
|
|
|
if (!options.ignoreLiveness)
|
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 13:28:26 +00:00
|
|
|
|
findRuntimeRoots(state.roots);
|
2006-07-20 12:17:25 +00:00
|
|
|
|
|
2005-01-31 10:27:25 +00: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. */
|
|
|
|
|
FDs fds;
|
2009-11-23 16:34:24 +00:00
|
|
|
|
readTempRoots(state.tempRoots, fds);
|
|
|
|
|
state.roots.insert(state.tempRoots.begin(), state.tempRoots.end());
|
2005-01-31 14:00:43 +00:00
|
|
|
|
|
|
|
|
|
/* After this point the set of roots or temporary roots cannot
|
|
|
|
|
increase, since we hold locks on everything. So everything
|
2014-08-01 14:37:47 +00:00
|
|
|
|
that is not reachable from `roots' is garbage. */
|
2009-11-23 16:34:24 +00:00
|
|
|
|
|
2013-03-08 00:24:59 +00:00
|
|
|
|
if (state.shouldDelete) {
|
|
|
|
|
if (pathExists(state.trashDir)) deleteGarbage(state, state.trashDir);
|
2015-06-30 19:41:26 +00:00
|
|
|
|
try {
|
|
|
|
|
createDirs(state.trashDir);
|
|
|
|
|
} catch (SysError & e) {
|
|
|
|
|
if (e.errNo == ENOSPC) {
|
|
|
|
|
printMsg(lvlInfo, format("note: can't create trash directory: %1%") % e.msg());
|
|
|
|
|
state.moveToTrash = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-03-08 00:24:59 +00:00
|
|
|
|
}
|
2012-12-20 16:32:15 +00:00
|
|
|
|
|
2009-11-23 16:34:24 +00:00
|
|
|
|
/* Now either delete all garbage paths, or just the specified
|
|
|
|
|
paths (for gcDeleteSpecific). */
|
|
|
|
|
|
|
|
|
|
if (options.action == GCOptions::gcDeleteSpecific) {
|
2008-09-17 10:02:55 +00:00
|
|
|
|
|
2015-07-17 17:24:28 +00:00
|
|
|
|
for (auto & i : options.pathsToDelete) {
|
|
|
|
|
assertStorePath(i);
|
|
|
|
|
tryToDelete(state, i);
|
|
|
|
|
if (state.dead.find(i) == state.dead.end())
|
|
|
|
|
throw Error(format("cannot delete path ‘%1%’ since it is still alive") % i);
|
2005-12-23 21:08:42 +00:00
|
|
|
|
}
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2012-08-01 23:14:30 +00:00
|
|
|
|
} else if (options.maxFreed > 0) {
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
if (state.shouldDelete)
|
2009-11-23 16:34:24 +00:00
|
|
|
|
printMsg(lvlError, format("deleting garbage..."));
|
|
|
|
|
else
|
|
|
|
|
printMsg(lvlError, format("determining live/dead paths..."));
|
2012-07-30 23:55:41 +00:00
|
|
|
|
|
2009-11-23 16:34:24 +00:00
|
|
|
|
try {
|
2011-12-22 15:55:53 +00:00
|
|
|
|
|
2012-07-30 23:55:41 +00:00
|
|
|
|
AutoCloseDir dir = opendir(settings.nixStore.c_str());
|
2014-08-20 15:00:17 +00:00
|
|
|
|
if (!dir) throw SysError(format("opening directory ‘%1%’") % settings.nixStore);
|
2011-12-22 15:55:53 +00: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
|
|
|
|
|
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;
|
|
|
|
|
while (errno = 0, dirent = readdir(dir)) {
|
|
|
|
|
checkInterrupt();
|
|
|
|
|
string name = dirent->d_name;
|
|
|
|
|
if (name == "." || name == "..") continue;
|
2012-07-30 23:55:41 +00:00
|
|
|
|
Path path = settings.nixStore + "/" + name;
|
2011-12-22 15:55:53 +00:00
|
|
|
|
if (isValidPath(path))
|
|
|
|
|
entries.push_back(path);
|
|
|
|
|
else
|
|
|
|
|
tryToDelete(state, path);
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-30 23:55:41 +00:00
|
|
|
|
dir.close();
|
2011-12-22 15:55:53 +00: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. */
|
|
|
|
|
vector<Path> entries_(entries.begin(), entries.end());
|
|
|
|
|
random_shuffle(entries_.begin(), entries_.end());
|
|
|
|
|
|
2015-07-17 17:24:28 +00:00
|
|
|
|
for (auto & i : entries_)
|
|
|
|
|
tryToDelete(state, i);
|
2011-12-22 15:55:53 +00:00
|
|
|
|
|
2009-11-23 16:34:24 +00:00
|
|
|
|
} catch (GCLimitReached & e) {
|
|
|
|
|
}
|
2011-12-22 15:55:53 +00:00
|
|
|
|
}
|
2012-03-26 18:43:33 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
if (state.options.action == GCOptions::gcReturnLive) {
|
|
|
|
|
state.results.paths = state.alive;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (state.options.action == GCOptions::gcReturnDead) {
|
|
|
|
|
state.results.paths = state.dead;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-26 18:43:33 +00:00
|
|
|
|
/* Allow other processes to add to the store from here on. */
|
|
|
|
|
fdGCLock.close();
|
2013-12-10 12:13:59 +00:00
|
|
|
|
fds.clear();
|
2012-03-26 18:43:33 +00:00
|
|
|
|
|
2012-12-20 16:32:15 +00:00
|
|
|
|
/* Delete the trash directory. */
|
2014-08-20 15:00:17 +00:00
|
|
|
|
printMsg(lvlInfo, format("deleting ‘%1%’") % state.trashDir);
|
2012-12-20 16:32:15 +00:00
|
|
|
|
deleteGarbage(state, state.trashDir);
|
2012-07-23 19:48:30 +00:00
|
|
|
|
|
|
|
|
|
/* Clean up the links directory. */
|
2012-09-12 22:49:35 +00:00
|
|
|
|
if (options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific) {
|
|
|
|
|
printMsg(lvlError, format("deleting unused links..."));
|
|
|
|
|
removeUnusedLinks(state);
|
|
|
|
|
}
|
2012-09-13 18:33:41 +00:00
|
|
|
|
|
|
|
|
|
/* While we're at it, vacuum the database. */
|
2014-11-19 17:02:39 +00:00
|
|
|
|
//if (options.action == GCOptions::gcDeleteDead) vacuumDB();
|
2004-08-25 11:43:49 +00:00
|
|
|
|
}
|
2006-09-04 21:06:23 +00:00
|
|
|
|
|
2008-09-17 10:02:55 +00:00
|
|
|
|
|
2006-09-04 21:06:23 +00:00
|
|
|
|
}
|