2006-09-04 23:06:23 +02:00
|
|
|
#include "globals.hh"
|
2006-03-06 12:21:15 +01:00
|
|
|
#include "misc.hh"
|
2005-01-31 11:27:25 +01:00
|
|
|
#include "pathlocks.hh"
|
2006-11-30 18:43:04 +01:00
|
|
|
#include "local-store.hh"
|
2005-01-31 11:27:25 +01:00
|
|
|
|
|
|
|
#include <boost/shared_ptr.hpp>
|
2004-08-25 13:43:49 +02:00
|
|
|
|
2008-09-17 12:02:55 +02:00
|
|
|
#include <functional>
|
|
|
|
#include <queue>
|
|
|
|
|
2004-08-25 18:54:08 +02:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
2005-01-31 11:27:25 +01:00
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
2004-08-25 18:54:08 +02:00
|
|
|
#include <unistd.h>
|
|
|
|
|
2006-06-20 19:48:10 +02:00
|
|
|
#ifdef __CYGWIN__
|
|
|
|
#include <windows.h>
|
|
|
|
#include <sys/cygwin.h>
|
|
|
|
#endif
|
|
|
|
|
2004-08-25 18:54:08 +02:00
|
|
|
|
2006-09-04 23:06:23 +02:00
|
|
|
namespace nix {
|
|
|
|
|
|
|
|
|
2005-01-31 23:23:49 +01:00
|
|
|
static string gcLockName = "gc.lock";
|
2005-02-01 14:48:46 +01:00
|
|
|
static string tempRootsDir = "temproots";
|
|
|
|
static string gcRootsDir = "gcroots";
|
2005-01-31 23:23:49 +01:00
|
|
|
|
2007-11-29 17:18:24 +01:00
|
|
|
static const int defaultGcLevel = 1000;
|
2007-11-15 16:07:27 +01:00
|
|
|
|
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. */
|
|
|
|
static int openGCLock(LockType lockType)
|
2005-01-31 23:01:55 +01:00
|
|
|
{
|
2005-01-31 23:23:49 +01:00
|
|
|
Path fnGCLock = (format("%1%/%2%")
|
|
|
|
% nixStateDir % gcLockName).str();
|
2005-02-01 16:05:32 +01:00
|
|
|
|
|
|
|
debug(format("acquiring global GC lock `%1%'") % fnGCLock);
|
|
|
|
|
2005-01-31 23:23:49 +01:00
|
|
|
AutoCloseFD fdGCLock = open(fnGCLock.c_str(), O_RDWR | O_CREAT, 0600);
|
|
|
|
if (fdGCLock == -1)
|
|
|
|
throw SysError(format("opening global GC lock `%1%'") % fnGCLock);
|
|
|
|
|
2006-09-15 00:30:33 +02:00
|
|
|
if (!lockFile(fdGCLock, lockType, false)) {
|
|
|
|
printMsg(lvlError, format("waiting for the big garbage collector lock..."));
|
|
|
|
lockFile(fdGCLock, lockType, true);
|
|
|
|
}
|
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. */
|
|
|
|
|
|
|
|
return fdGCLock.borrow();
|
2005-01-31 23:01:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-02-01 14:48:46 +01:00
|
|
|
void createSymlink(const Path & link, const Path & target, bool careful)
|
|
|
|
{
|
|
|
|
/* Create directories up to `gcRoot'. */
|
|
|
|
createDirs(dirOf(link));
|
|
|
|
|
2007-08-28 11:39:03 +02:00
|
|
|
/* !!! shouldn't removing and creating the symlink be atomic? */
|
|
|
|
|
2005-02-01 14:48:46 +01:00
|
|
|
/* Remove the old symlink. */
|
|
|
|
if (pathExists(link)) {
|
2005-02-07 15:32:44 +01:00
|
|
|
if (careful && (!isLink(link) || !isInStore(readLink(link))))
|
2005-02-01 14:48:46 +01:00
|
|
|
throw Error(format("cannot create symlink `%1%'; already exists") % link);
|
|
|
|
unlink(link.c_str());
|
|
|
|
}
|
|
|
|
|
2007-08-28 11:39:03 +02:00
|
|
|
/* And create the new one. */
|
2005-02-01 14:48:46 +01:00
|
|
|
if (symlink(target.c_str(), link.c_str()) == -1)
|
|
|
|
throw SysError(format("symlinking `%1%' to `%2%'")
|
|
|
|
% link % target);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
string hash = printHash32(hashString(htSHA1, path));
|
|
|
|
Path realRoot = canonPath((format("%1%/%2%/auto/%3%")
|
|
|
|
% nixStateDir % gcRootsDir % hash).str());
|
|
|
|
createSymlink(realRoot, path, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-02-01 14:48:46 +01:00
|
|
|
Path addPermRoot(const Path & _storePath, const Path & _gcRoot,
|
2006-09-15 00:30:33 +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);
|
2005-02-01 13:36:25 +01: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);
|
|
|
|
|
2005-02-01 14:48:46 +01:00
|
|
|
if (indirect) {
|
2006-12-02 15:34:14 +01:00
|
|
|
createSymlink(gcRoot, storePath, true);
|
2006-12-05 00:29:16 +01:00
|
|
|
store->addIndirectRoot(gcRoot);
|
2005-02-01 14:48:46 +01:00
|
|
|
}
|
2005-02-01 13:36:25 +01:00
|
|
|
|
2005-02-01 14:48:46 +01:00
|
|
|
else {
|
2006-09-15 00:30:33 +02:00
|
|
|
if (!allowOutsideRootsDir) {
|
|
|
|
Path rootsDir = canonPath((format("%1%/%2%") % nixStateDir % gcRootsDir).str());
|
2005-02-01 14:48:46 +01:00
|
|
|
|
2006-09-15 00:30:33 +02:00
|
|
|
if (string(gcRoot, 0, rootsDir.size() + 1) != rootsDir + "/")
|
|
|
|
throw Error(format(
|
|
|
|
"path `%1%' is not a valid garbage collector root; "
|
|
|
|
"it's not in the directory `%2%'")
|
|
|
|
% gcRoot % rootsDir);
|
|
|
|
}
|
|
|
|
|
2005-02-01 14:48:46 +01:00
|
|
|
createSymlink(gcRoot, storePath, false);
|
|
|
|
}
|
2005-02-01 13:36:25 +01: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. */
|
2007-03-19 10:16:47 +01:00
|
|
|
if (queryBoolSetting("gc-check-reachability", true)) {
|
|
|
|
Roots roots = store->findRoots();
|
|
|
|
if (roots.find(gcRoot) == roots.end())
|
|
|
|
printMsg(lvlError,
|
|
|
|
format(
|
|
|
|
"warning: `%1%' is not in a directory where the garbage collector looks for roots; "
|
|
|
|
"therefore, `%2%' might be removed by the garbage collector")
|
|
|
|
% gcRoot % storePath);
|
|
|
|
}
|
2006-12-05 02:31:45 +01: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. */
|
|
|
|
store->syncWithGC();
|
|
|
|
|
2005-02-01 13:36:25 +01:00
|
|
|
return gcRoot;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-01-31 11:27:25 +01:00
|
|
|
/* The file to which we write our temporary roots. */
|
2005-01-31 23:01:55 +01:00
|
|
|
static Path fnTempRoots;
|
2005-01-31 11:27:25 +01:00
|
|
|
static AutoCloseFD fdTempRoots;
|
|
|
|
|
|
|
|
|
2006-12-02 17:41:36 +01:00
|
|
|
void LocalStore::addTempRoot(const Path & path)
|
2005-01-31 11:27:25 +01:00
|
|
|
{
|
|
|
|
/* Create the temporary roots file for this process. */
|
|
|
|
if (fdTempRoots == -1) {
|
|
|
|
|
|
|
|
while (1) {
|
2005-03-24 18:46:38 +01:00
|
|
|
Path dir = (format("%1%/%2%") % nixStateDir % tempRootsDir).str();
|
|
|
|
createDirs(dir);
|
|
|
|
|
|
|
|
fnTempRoots = (format("%1%/%2%")
|
|
|
|
% dir % getpid()).str();
|
2005-01-31 23:23:49 +01:00
|
|
|
|
|
|
|
AutoCloseFD fdGCLock = openGCLock(ltRead);
|
|
|
|
|
2006-06-20 19:48:10 +02:00
|
|
|
if (pathExists(fnTempRoots))
|
|
|
|
/* It *must* be stale, since there can be no two
|
|
|
|
processes with the same pid. */
|
|
|
|
deletePath(fnTempRoots);
|
|
|
|
|
|
|
|
fdTempRoots = openLockFile(fnTempRoots, true);
|
2005-01-31 11:27:25 +01:00
|
|
|
|
2005-01-31 23:23:49 +01:00
|
|
|
fdGCLock.close();
|
2006-06-20 19:48:10 +02:00
|
|
|
|
|
|
|
/* Note that on Cygwin a lot of the following complexity
|
|
|
|
is unnecessary, since we cannot delete open lock
|
|
|
|
files. If we have the lock file open, then it's valid;
|
|
|
|
if we can delete it, then it wasn't in use any more.
|
|
|
|
|
|
|
|
Also note that on Cygwin we cannot "upgrade" a lock
|
|
|
|
from a read lock to a write lock. */
|
|
|
|
|
|
|
|
#ifndef __CYGWIN__
|
2005-01-31 11:27:25 +01:00
|
|
|
debug(format("acquiring read lock on `%1%'") % fnTempRoots);
|
|
|
|
lockFile(fdTempRoots, ltRead, true);
|
|
|
|
|
|
|
|
/* Check whether the garbage collector didn't get in our
|
|
|
|
way. */
|
|
|
|
struct stat st;
|
|
|
|
if (fstat(fdTempRoots, &st) == -1)
|
|
|
|
throw SysError(format("statting `%1%'") % fnTempRoots);
|
|
|
|
if (st.st_size == 0) break;
|
|
|
|
|
|
|
|
/* The garbage collector deleted this file before we could
|
|
|
|
get a lock. (It won't delete the file after we get a
|
|
|
|
lock.) Try again. */
|
2006-06-20 19:48:10 +02:00
|
|
|
|
|
|
|
#else
|
|
|
|
break;
|
|
|
|
#endif
|
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. */
|
|
|
|
debug(format("acquiring write lock on `%1%'") % fnTempRoots);
|
|
|
|
lockFile(fdTempRoots, ltWrite, true);
|
|
|
|
|
|
|
|
string s = path + '\0';
|
|
|
|
writeFull(fdTempRoots, (const unsigned char *) s.c_str(), s.size());
|
|
|
|
|
2006-06-20 19:48:10 +02:00
|
|
|
#ifndef __CYGWIN__
|
2005-01-31 11:27:25 +01:00
|
|
|
/* Downgrade to a read lock. */
|
|
|
|
debug(format("downgrading to read lock on `%1%'") % fnTempRoots);
|
|
|
|
lockFile(fdTempRoots, ltRead, true);
|
2006-06-20 19:48:10 +02:00
|
|
|
#else
|
|
|
|
debug(format("releasing write lock on `%1%'") % fnTempRoots);
|
|
|
|
lockFile(fdTempRoots, ltNone, true);
|
|
|
|
#endif
|
2005-01-31 11:27:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-01-31 22:20:59 +01:00
|
|
|
void removeTempRoots()
|
|
|
|
{
|
|
|
|
if (fdTempRoots != -1) {
|
|
|
|
fdTempRoots.close();
|
|
|
|
unlink(fnTempRoots.c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-09-04 23:06:23 +02:00
|
|
|
typedef boost::shared_ptr<AutoCloseFD> FDPtr;
|
2005-01-31 11:27:25 +01:00
|
|
|
typedef list<FDPtr> FDs;
|
|
|
|
|
|
|
|
|
|
|
|
static void readTempRoots(PathSet & tempRoots, FDs & fds)
|
|
|
|
{
|
|
|
|
/* Read the `temproots' directory for per-process temporary root
|
|
|
|
files. */
|
|
|
|
Strings tempRootFiles = readDirectory(
|
|
|
|
(format("%1%/%2%") % nixStateDir % tempRootsDir).str());
|
|
|
|
|
|
|
|
for (Strings::iterator i = tempRootFiles.begin();
|
|
|
|
i != tempRootFiles.end(); ++i)
|
|
|
|
{
|
|
|
|
Path path = (format("%1%/%2%/%3%") % nixStateDir % tempRootsDir % *i).str();
|
|
|
|
|
|
|
|
debug(format("reading temporary root file `%1%'") % path);
|
2006-06-20 19:48:10 +02:00
|
|
|
|
|
|
|
#ifdef __CYGWIN__
|
|
|
|
/* On Cygwin we just try to delete the lock file. */
|
|
|
|
char win32Path[MAX_PATH];
|
|
|
|
cygwin_conv_to_full_win32_path(path.c_str(), win32Path);
|
|
|
|
if (DeleteFile(win32Path)) {
|
|
|
|
printMsg(lvlError, format("removed stale temporary roots file `%1%'")
|
|
|
|
% path);
|
|
|
|
continue;
|
|
|
|
} else
|
|
|
|
debug(format("delete of `%1%' failed: %2%") % path % GetLastError());
|
|
|
|
#endif
|
|
|
|
|
2005-01-31 11:27:25 +01: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;
|
|
|
|
throw SysError(format("opening temporary roots file `%1%'") % path);
|
|
|
|
}
|
|
|
|
|
2006-06-20 19:48:10 +02:00
|
|
|
/* This should work, but doesn't, for some reason. */
|
|
|
|
//FDPtr fd(new AutoCloseFD(openLockFile(path, false)));
|
|
|
|
//if (*fd == -1) continue;
|
|
|
|
|
|
|
|
#ifndef __CYGWIN__
|
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, ltWrite, false)) {
|
|
|
|
printMsg(lvlError, format("removing stale temporary roots file `%1%'")
|
|
|
|
% path);
|
2005-01-31 22:20:59 +01:00
|
|
|
unlink(path.c_str());
|
|
|
|
writeFull(*fd, (const unsigned char *) "d", 1);
|
2005-01-31 11:27:25 +01:00
|
|
|
continue;
|
|
|
|
}
|
2006-06-20 19:48:10 +02:00
|
|
|
#endif
|
2005-01-31 11:27:25 +01:00
|
|
|
|
|
|
|
/* Acquire a read lock. This will prevent the owning process
|
|
|
|
from upgrading to a write lock, therefore it will block in
|
|
|
|
addTempRoot(). */
|
|
|
|
debug(format("waiting for read lock on `%1%'") % path);
|
|
|
|
lockFile(*fd, ltRead, true);
|
|
|
|
|
|
|
|
/* Read the entire file. */
|
2005-02-01 23:07:48 +01:00
|
|
|
string contents = readFile(*fd);
|
2005-01-31 11:27:25 +01:00
|
|
|
|
|
|
|
/* Extract the roots. */
|
2006-05-11 04:19:43 +02:00
|
|
|
string::size_type pos = 0, end;
|
2005-01-31 11:27:25 +01:00
|
|
|
|
|
|
|
while ((end = contents.find((char) 0, pos)) != string::npos) {
|
|
|
|
Path root(contents, pos, end - pos);
|
|
|
|
debug(format("got temporary root `%1%'") % root);
|
|
|
|
assertStorePath(root);
|
|
|
|
tempRoots.insert(root);
|
|
|
|
pos = end + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
fds.push_back(fd); /* keep open */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-02-01 16:05:32 +01:00
|
|
|
static void findRoots(const Path & path, bool recurseSymlinks,
|
2006-12-05 02:31:45 +01:00
|
|
|
bool deleteStale, Roots & roots)
|
2005-02-01 16:05:32 +01:00
|
|
|
{
|
2006-12-05 02:31:45 +01:00
|
|
|
try {
|
|
|
|
|
|
|
|
struct stat st;
|
|
|
|
if (lstat(path.c_str(), &st) == -1)
|
|
|
|
throw SysError(format("statting `%1%'") % path);
|
|
|
|
|
|
|
|
printMsg(lvlVomit, format("looking at `%1%'") % path);
|
|
|
|
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
|
|
Strings names = readDirectory(path);
|
|
|
|
for (Strings::iterator i = names.begin(); i != names.end(); ++i)
|
|
|
|
findRoots(path + "/" + *i, recurseSymlinks, deleteStale, roots);
|
2005-02-01 16:05:32 +01:00
|
|
|
}
|
|
|
|
|
2006-12-05 02:31:45 +01:00
|
|
|
else if (S_ISLNK(st.st_mode)) {
|
|
|
|
Path target = absPath(readLink(path), dirOf(path));
|
|
|
|
|
|
|
|
if (isInStore(target)) {
|
|
|
|
debug(format("found root `%1%' in `%2%'")
|
|
|
|
% target % path);
|
|
|
|
Path storePath = toStorePath(target);
|
|
|
|
if (store->isValidPath(storePath))
|
|
|
|
roots[path] = storePath;
|
|
|
|
else
|
|
|
|
printMsg(lvlInfo, format("skipping invalid root from `%1%' to `%2%'")
|
|
|
|
% path % storePath);
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (recurseSymlinks) {
|
|
|
|
if (pathExists(target))
|
|
|
|
findRoots(target, false, deleteStale, roots);
|
|
|
|
else if (deleteStale) {
|
|
|
|
printMsg(lvlInfo, format("removing stale link from `%1%' to `%2%'") % path % target);
|
|
|
|
/* Note that we only delete when recursing, i.e.,
|
|
|
|
when we are still in the `gcroots' tree. We
|
|
|
|
never delete stuff outside that tree. */
|
|
|
|
unlink(path.c_str());
|
|
|
|
}
|
2005-02-01 16:05:32 +01:00
|
|
|
}
|
|
|
|
}
|
2006-12-05 02:31:45 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (SysError & e) {
|
|
|
|
/* We only ignore permanent failures. */
|
|
|
|
if (e.errNo == EACCES || e.errNo == ENOENT || e.errNo == ENOTDIR)
|
|
|
|
printMsg(lvlInfo, format("cannot read potential root `%1%'") % path);
|
|
|
|
else
|
|
|
|
throw;
|
2005-02-01 16:05:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-12-05 02:31:45 +01:00
|
|
|
static Roots findRoots(bool deleteStale)
|
2006-12-05 01:34:42 +01:00
|
|
|
{
|
2006-12-05 02:31:45 +01:00
|
|
|
Roots roots;
|
2006-12-05 01:34:42 +01:00
|
|
|
Path rootsDir = canonPath((format("%1%/%2%") % nixStateDir % gcRootsDir).str());
|
2006-12-05 02:31:45 +01:00
|
|
|
findRoots(rootsDir, true, deleteStale, roots);
|
|
|
|
return roots;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Roots LocalStore::findRoots()
|
|
|
|
{
|
|
|
|
return nix::findRoots(false);
|
2006-12-05 01:34:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-07-20 14:17:25 +02:00
|
|
|
static void addAdditionalRoots(PathSet & roots)
|
|
|
|
{
|
|
|
|
Path rootFinder = getEnv("NIX_ROOT_FINDER",
|
2006-07-20 15:21:37 +02:00
|
|
|
nixLibexecDir + "/nix/find-runtime-roots.pl");
|
2006-07-20 14:17:25 +02:00
|
|
|
|
|
|
|
if (rootFinder.empty()) return;
|
|
|
|
|
2006-07-20 14:19:55 +02:00
|
|
|
debug(format("executing `%1%' to find additional roots") % rootFinder);
|
2006-07-20 14:17:25 +02:00
|
|
|
|
|
|
|
string result = runProgram(rootFinder);
|
|
|
|
|
|
|
|
Strings paths = tokenizeString(result, "\n");
|
|
|
|
|
|
|
|
for (Strings::iterator i = paths.begin(); i != paths.end(); ++i) {
|
|
|
|
if (isInStore(*i)) {
|
|
|
|
Path path = toStorePath(*i);
|
2006-11-30 18:43:04 +01:00
|
|
|
if (roots.find(path) == roots.end() && store->isValidPath(path)) {
|
2006-07-20 14:17:25 +02:00
|
|
|
debug(format("found additional root `%1%'") % path);
|
|
|
|
roots.insert(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-01-31 15:00:43 +01:00
|
|
|
static void dfsVisit(const PathSet & paths, const Path & path,
|
|
|
|
PathSet & visited, Paths & sorted)
|
|
|
|
{
|
|
|
|
if (visited.find(path) != visited.end()) return;
|
|
|
|
visited.insert(path);
|
|
|
|
|
|
|
|
PathSet references;
|
2006-11-30 18:43:04 +01:00
|
|
|
if (store->isValidPath(path))
|
|
|
|
store->queryReferences(path, references);
|
2005-01-31 15:00:43 +01:00
|
|
|
|
|
|
|
for (PathSet::iterator i = references.begin();
|
|
|
|
i != references.end(); ++i)
|
|
|
|
/* Don't traverse into paths that don't exist. That can
|
|
|
|
happen due to substitutes for non-existent paths. */
|
|
|
|
if (*i != path && paths.find(*i) != paths.end())
|
|
|
|
dfsVisit(paths, *i, visited, sorted);
|
|
|
|
|
|
|
|
sorted.push_front(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-02-21 23:45:10 +01:00
|
|
|
Paths topoSortPaths(const PathSet & paths)
|
2005-01-31 15:00:43 +01:00
|
|
|
{
|
|
|
|
Paths sorted;
|
|
|
|
PathSet visited;
|
|
|
|
for (PathSet::const_iterator i = paths.begin(); i != paths.end(); ++i)
|
|
|
|
dfsVisit(paths, *i, visited, sorted);
|
|
|
|
return sorted;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-09-17 12:02:55 +02:00
|
|
|
static time_t lastFileAccessTime(const Path & path)
|
|
|
|
{
|
2008-09-17 14:54:07 +02:00
|
|
|
checkInterrupt();
|
|
|
|
|
2008-09-17 12:02:55 +02:00
|
|
|
struct stat st;
|
|
|
|
if (lstat(path.c_str(), &st) == -1)
|
|
|
|
throw SysError(format("statting `%1%'") % path);
|
|
|
|
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
|
|
time_t last = 0;
|
|
|
|
Strings names = readDirectory(path);
|
|
|
|
for (Strings::iterator i = names.begin(); i != names.end(); ++i) {
|
|
|
|
time_t t = lastFileAccessTime(path + "/" + *i);
|
|
|
|
if (t > last) last = t;
|
|
|
|
}
|
|
|
|
return last;
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (S_ISLNK(st.st_mode)) return 0;
|
|
|
|
|
|
|
|
else return st.st_atime;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-06-18 16:20:16 +02:00
|
|
|
struct GCLimitReached { };
|
|
|
|
|
|
|
|
|
2008-09-17 14:54:07 +02:00
|
|
|
void LocalStore::gcPath(const GCOptions & options, GCResults & results,
|
|
|
|
const Path & path)
|
2008-06-13 20:25:24 +02:00
|
|
|
{
|
2008-06-18 11:34:17 +02:00
|
|
|
results.paths.insert(path);
|
2008-06-13 20:25:24 +02:00
|
|
|
|
2008-09-17 12:02:55 +02:00
|
|
|
if (!pathExists(path)) return;
|
|
|
|
|
2008-06-13 20:25:24 +02:00
|
|
|
/* Okay, it's safe to delete. */
|
2008-06-18 11:34:17 +02:00
|
|
|
unsigned long long bytesFreed, blocksFreed;
|
|
|
|
deleteFromStore(path, bytesFreed, blocksFreed);
|
|
|
|
results.bytesFreed += bytesFreed;
|
|
|
|
results.blocksFreed += blocksFreed;
|
2008-06-13 20:25:24 +02:00
|
|
|
|
2009-03-26 12:02:07 +01:00
|
|
|
if (options.maxFreed && results.bytesFreed > options.maxFreed) {
|
2008-06-18 16:20:16 +02:00
|
|
|
printMsg(lvlInfo, format("deleted more than %1% bytes; stopping") % options.maxFreed);
|
|
|
|
throw GCLimitReached();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.maxLinks) {
|
|
|
|
struct stat st;
|
|
|
|
if (stat(nixStore.c_str(), &st) == -1)
|
|
|
|
throw SysError(format("statting `%1%'") % nixStore);
|
|
|
|
if (st.st_nlink < options.maxLinks) {
|
|
|
|
printMsg(lvlInfo, format("link count on the store has dropped below %1%; stopping") % options.maxLinks);
|
|
|
|
throw GCLimitReached();
|
|
|
|
}
|
|
|
|
}
|
2008-06-13 20:25:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-09-17 14:54:07 +02:00
|
|
|
void LocalStore::gcPathRecursive(const GCOptions & options,
|
|
|
|
GCResults & results, PathSet & done, const Path & path)
|
|
|
|
{
|
|
|
|
if (done.find(path) != done.end()) return;
|
|
|
|
done.insert(path);
|
|
|
|
|
|
|
|
startNest(nest, lvlDebug, format("looking at `%1%'") % path);
|
|
|
|
|
|
|
|
/* Delete all the referrers first. They must be garbage too,
|
2008-09-17 16:52:35 +02:00
|
|
|
since if they were live, then the current path would also be
|
|
|
|
live. Note that deleteFromStore() below still makes sure that
|
|
|
|
the referrer set has become empty, just in case. (However that
|
|
|
|
doesn't guard against deleting top-level paths that are only
|
|
|
|
reachable from GC roots.) */
|
2008-09-17 14:54:07 +02:00
|
|
|
PathSet referrers;
|
|
|
|
if (isValidPath(path))
|
|
|
|
queryReferrers(path, referrers);
|
|
|
|
foreach (PathSet::iterator, i, referrers)
|
|
|
|
if (*i != path) gcPathRecursive(options, results, done, *i);
|
|
|
|
|
|
|
|
printMsg(lvlInfo, format("deleting `%1%'") % path);
|
|
|
|
|
|
|
|
gcPath(options, results, path);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-09-17 12:02:55 +02:00
|
|
|
struct CachingAtimeComparator : public std::binary_function<Path, Path, bool>
|
|
|
|
{
|
|
|
|
std::map<Path, time_t> cache;
|
|
|
|
|
|
|
|
time_t lookup(const Path & p)
|
|
|
|
{
|
|
|
|
std::map<Path, time_t>::iterator i = cache.find(p);
|
|
|
|
if (i != cache.end()) return i->second;
|
|
|
|
debug(format("computing atime of `%1%'") % p);
|
|
|
|
cache[p] = lastFileAccessTime(p);
|
|
|
|
assert(cache.find(p) != cache.end());
|
|
|
|
return cache[p];
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator () (const Path & p1, const Path & p2)
|
|
|
|
{
|
|
|
|
return lookup(p2) < lookup(p1);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2008-12-12 18:03:18 +01:00
|
|
|
static string showTime(const string & format, time_t t)
|
2008-09-17 12:02:55 +02:00
|
|
|
{
|
|
|
|
char s[128];
|
2008-09-17 16:52:35 +02:00
|
|
|
strftime(s, sizeof s, format.c_str(), localtime(&t));
|
2008-09-17 12:02:55 +02:00
|
|
|
return string(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-12-12 18:03:18 +01:00
|
|
|
static bool isLive(const Path & path, const PathSet & livePaths,
|
|
|
|
const PathSet & tempRoots, const PathSet & tempRootsClosed)
|
|
|
|
{
|
|
|
|
if (livePaths.find(path) != livePaths.end() ||
|
|
|
|
tempRootsClosed.find(path) != tempRootsClosed.end()) return true;
|
|
|
|
|
|
|
|
/* A lock file belonging to a path that we're building right
|
|
|
|
now isn't garbage. */
|
|
|
|
if (hasSuffix(path, ".lock") && tempRoots.find(string(path, 0, path.size() - 5)) != tempRoots.end())
|
|
|
|
return true;
|
|
|
|
|
2008-12-12 18:14:57 +01:00
|
|
|
/* Don't delete .chroot directories for derivations that are
|
|
|
|
currently being built. */
|
|
|
|
if (hasSuffix(path, ".chroot") && tempRoots.find(string(path, 0, path.size() - 7)) != tempRoots.end())
|
|
|
|
return true;
|
|
|
|
|
2008-12-12 18:03:18 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-06-18 11:34:17 +02:00
|
|
|
void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
2005-01-27 16:21:29 +01:00
|
|
|
{
|
2005-02-14 15:16:56 +01:00
|
|
|
bool gcKeepOutputs =
|
|
|
|
queryBoolSetting("gc-keep-outputs", false);
|
|
|
|
bool gcKeepDerivations =
|
|
|
|
queryBoolSetting("gc-keep-derivations", true);
|
2007-11-29 17:18:24 +01:00
|
|
|
int gcKeepOutputsThreshold =
|
2007-11-15 04:47:12 +01:00
|
|
|
queryIntSetting ("gc-keep-outputs-threshold", defaultGcLevel);
|
2005-02-01 23:07:48 +01: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);
|
2005-01-31 11:27:25 +01: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. */
|
2008-06-13 19:21:20 +02:00
|
|
|
printMsg(lvlError, format("finding garbage collector roots..."));
|
2008-06-18 11:34:17 +02:00
|
|
|
Roots rootMap = options.ignoreLiveness ? Roots() : nix::findRoots(true);
|
2006-12-05 01:48:36 +01:00
|
|
|
|
2005-02-01 16:05:32 +01:00
|
|
|
PathSet roots;
|
2006-12-05 01:48:36 +01:00
|
|
|
for (Roots::iterator i = rootMap.begin(); i != rootMap.end(); ++i)
|
|
|
|
roots.insert(i->second);
|
2005-02-01 16:05:32 +01:00
|
|
|
|
2006-07-20 14:17:25 +02: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 11:34:17 +02:00
|
|
|
if (!options.ignoreLiveness)
|
2006-12-05 03:18:46 +01:00
|
|
|
addAdditionalRoots(roots);
|
2006-07-20 14:17:25 +02:00
|
|
|
|
2008-06-18 11:34:17 +02:00
|
|
|
if (options.action == GCOptions::gcReturnRoots) {
|
|
|
|
results.paths = roots;
|
2005-02-01 16:05:32 +01:00
|
|
|
return;
|
|
|
|
}
|
2005-02-01 13:36:25 +01:00
|
|
|
|
2005-01-27 16:21:29 +01:00
|
|
|
/* Determine the live paths which is just the closure of the
|
|
|
|
roots under the `references' relation. */
|
2008-06-13 19:21:20 +02:00
|
|
|
printMsg(lvlError, format("computing live paths..."));
|
2005-01-27 16:21:29 +01:00
|
|
|
PathSet livePaths;
|
|
|
|
for (PathSet::const_iterator i = roots.begin(); i != roots.end(); ++i)
|
|
|
|
computeFSClosure(canonPath(*i), livePaths);
|
|
|
|
|
2005-02-14 15:16:56 +01:00
|
|
|
if (gcKeepDerivations) {
|
|
|
|
for (PathSet::iterator i = livePaths.begin();
|
|
|
|
i != livePaths.end(); ++i)
|
|
|
|
{
|
2005-03-25 15:31:12 +01:00
|
|
|
/* Note that the deriver need not be valid (e.g., if we
|
|
|
|
previously ran the collector with `gcKeepDerivations'
|
|
|
|
turned off). */
|
2008-09-17 12:02:55 +02:00
|
|
|
Path deriver = queryDeriver(*i);
|
|
|
|
if (deriver != "" && isValidPath(deriver))
|
2005-02-14 15:16:56 +01:00
|
|
|
computeFSClosure(deriver, livePaths);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-02-14 14:07:09 +01:00
|
|
|
if (gcKeepOutputs) {
|
2005-02-01 23:07:48 +01:00
|
|
|
/* Hmz, identical to storePathRequisites in nix-store. */
|
|
|
|
for (PathSet::iterator i = livePaths.begin();
|
|
|
|
i != livePaths.end(); ++i)
|
|
|
|
if (isDerivation(*i)) {
|
|
|
|
Derivation drv = derivationFromPath(*i);
|
2007-11-15 04:47:12 +01:00
|
|
|
|
|
|
|
string gcLevelStr = drv.env["__gcLevel"];
|
|
|
|
int gcLevel;
|
2007-11-29 17:18:24 +01:00
|
|
|
if (!string2Int(gcLevelStr, gcLevel))
|
2007-11-15 04:47:12 +01:00
|
|
|
gcLevel = defaultGcLevel;
|
|
|
|
|
|
|
|
if (gcLevel >= gcKeepOutputsThreshold)
|
|
|
|
for (DerivationOutputs::iterator j = drv.outputs.begin();
|
2007-11-29 17:18:24 +01:00
|
|
|
j != drv.outputs.end(); ++j)
|
2008-09-17 12:02:55 +02:00
|
|
|
if (isValidPath(j->second.path))
|
2007-11-15 04:47:12 +01:00
|
|
|
computeFSClosure(j->second.path, livePaths);
|
2005-02-01 23:07:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-06-18 11:34:17 +02:00
|
|
|
if (options.action == GCOptions::gcReturnLive) {
|
|
|
|
results.paths = livePaths;
|
2005-01-27 16:21:29 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
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. */
|
|
|
|
PathSet tempRoots;
|
|
|
|
FDs fds;
|
|
|
|
readTempRoots(tempRoots, fds);
|
|
|
|
|
2005-01-31 15:00:43 +01:00
|
|
|
/* Close the temporary roots. Note that we *cannot* do this in
|
|
|
|
readTempRoots(), because there we may not have all locks yet,
|
|
|
|
meaning that an invalid path can become valid (and thus add to
|
|
|
|
the references graph) after we have added it to the closure
|
|
|
|
(and computeFSClosure() assumes that the presence of a path
|
|
|
|
means that it has already been closed). */
|
|
|
|
PathSet tempRootsClosed;
|
|
|
|
for (PathSet::iterator i = tempRoots.begin(); i != tempRoots.end(); ++i)
|
2008-09-17 12:02:55 +02:00
|
|
|
if (isValidPath(*i))
|
2005-01-31 15:00:43 +01:00
|
|
|
computeFSClosure(*i, tempRootsClosed);
|
|
|
|
else
|
|
|
|
tempRootsClosed.insert(*i);
|
|
|
|
|
|
|
|
/* After this point the set of roots or temporary roots cannot
|
|
|
|
increase, since we hold locks on everything. So everything
|
|
|
|
that is not currently in in `livePaths' or `tempRootsClosed'
|
|
|
|
can be deleted. */
|
2005-01-31 11:27:25 +01:00
|
|
|
|
2005-01-27 16:21:29 +01:00
|
|
|
/* Read the Nix store directory to find all currently existing
|
2008-09-17 12:02:55 +02:00
|
|
|
paths and filter out all live paths. */
|
2008-06-13 19:21:20 +02:00
|
|
|
printMsg(lvlError, format("reading the Nix store..."));
|
2008-06-13 20:25:24 +02:00
|
|
|
PathSet storePaths;
|
2008-09-17 12:02:55 +02:00
|
|
|
|
2008-06-18 11:34:17 +02:00
|
|
|
if (options.action != GCOptions::gcDeleteSpecific) {
|
2005-12-23 22:08:42 +01:00
|
|
|
Paths entries = readDirectory(nixStore);
|
2008-09-17 12:02:55 +02:00
|
|
|
foreach (Paths::iterator, i, entries) {
|
|
|
|
Path path = canonPath(nixStore + "/" + *i);
|
2008-12-12 18:03:18 +01:00
|
|
|
if (!isLive(path, livePaths, tempRoots, tempRootsClosed)) storePaths.insert(path);
|
2008-09-17 12:02:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
2008-06-18 11:34:17 +02:00
|
|
|
foreach (PathSet::iterator, i, options.pathsToDelete) {
|
2005-12-23 22:08:42 +01:00
|
|
|
assertStorePath(*i);
|
2008-06-13 20:25:24 +02:00
|
|
|
storePaths.insert(*i);
|
2008-12-12 18:03:18 +01:00
|
|
|
if (isLive(*i, livePaths, tempRoots, tempRootsClosed))
|
2008-09-17 12:02:55 +02:00
|
|
|
throw Error(format("cannot delete path `%1%' since it is still alive") % *i);
|
2005-12-23 22:08:42 +01:00
|
|
|
}
|
|
|
|
}
|
2005-01-31 15:00:43 +01:00
|
|
|
|
2008-09-17 12:02:55 +02:00
|
|
|
if (options.action == GCOptions::gcReturnDead) {
|
|
|
|
results.paths.insert(storePaths.begin(), storePaths.end());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Delete all dead store paths (or until one of the stop
|
|
|
|
conditions is reached). */
|
2005-01-31 13:19:53 +01:00
|
|
|
|
2008-06-13 20:25:24 +02:00
|
|
|
PathSet done;
|
2008-06-18 16:20:16 +02:00
|
|
|
try {
|
2008-09-17 12:02:55 +02:00
|
|
|
|
|
|
|
if (!options.useAtime) {
|
|
|
|
/* Delete the paths, respecting the partial ordering
|
|
|
|
determined by the references graph. */
|
|
|
|
printMsg(lvlError, format("deleting garbage..."));
|
|
|
|
foreach (PathSet::iterator, i, storePaths)
|
2008-09-17 14:54:07 +02:00
|
|
|
gcPathRecursive(options, results, done, *i);
|
2008-09-17 12:02:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
/* Delete in order of ascending last access time, still
|
|
|
|
maintaining the partial ordering of the reference
|
|
|
|
graph. Note that we can't use a topological sort for
|
|
|
|
this because that takes time O(V+E), and in this case
|
|
|
|
E=O(V^2) (i.e. the graph is dense because of the edges
|
|
|
|
due to the atime ordering). So instead we put all
|
|
|
|
deletable paths in a priority queue (ordered by atime),
|
|
|
|
and after deleting a path, add additional paths that
|
|
|
|
have become deletable to the priority queue. */
|
|
|
|
|
|
|
|
CachingAtimeComparator atimeComp;
|
|
|
|
|
|
|
|
/* Create a priority queue that orders paths by ascending
|
|
|
|
atime. This is why C++ needs type inferencing... */
|
|
|
|
std::priority_queue<Path, vector<Path>, binary_function_ref_adapter<CachingAtimeComparator> > prioQueue =
|
|
|
|
std::priority_queue<Path, vector<Path>, binary_function_ref_adapter<CachingAtimeComparator> >(binary_function_ref_adapter<CachingAtimeComparator>(&atimeComp));
|
|
|
|
|
|
|
|
/* Initially put the paths that are invalid or have no
|
|
|
|
referrers into the priority queue. */
|
|
|
|
printMsg(lvlError, format("finding deletable paths..."));
|
|
|
|
foreach (PathSet::iterator, i, storePaths) {
|
2008-09-17 14:54:07 +02:00
|
|
|
checkInterrupt();
|
2008-09-17 12:02:55 +02:00
|
|
|
/* We can safely delete a path if it's invalid or
|
|
|
|
it has no referrers. Note that all the invalid
|
|
|
|
paths will be deleted in the first round. */
|
|
|
|
if (isValidPath(*i)) {
|
|
|
|
if (queryReferrersNoSelf(*i).empty()) prioQueue.push(*i);
|
|
|
|
} else prioQueue.push(*i);
|
|
|
|
}
|
|
|
|
|
|
|
|
debug(format("%1% initially deletable paths") % prioQueue.size());
|
|
|
|
|
|
|
|
/* Now delete everything in the order of the priority
|
|
|
|
queue until nothing is left. */
|
2008-09-17 14:54:07 +02:00
|
|
|
printMsg(lvlError, format("deleting garbage..."));
|
2008-09-17 12:02:55 +02:00
|
|
|
while (!prioQueue.empty()) {
|
2008-09-17 14:54:07 +02:00
|
|
|
checkInterrupt();
|
2008-09-17 12:02:55 +02:00
|
|
|
Path path = prioQueue.top(); prioQueue.pop();
|
2008-09-17 16:52:35 +02:00
|
|
|
|
|
|
|
if (options.maxAtime != (time_t) -1 &&
|
|
|
|
atimeComp.lookup(path) > options.maxAtime)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
printMsg(lvlInfo, format("deleting `%1%' (last accessed %2%)") % path % showTime("%F %H:%M:%S", atimeComp.lookup(path)));
|
2008-09-17 12:02:55 +02:00
|
|
|
|
|
|
|
PathSet references;
|
|
|
|
if (isValidPath(path)) references = queryReferencesNoSelf(path);
|
|
|
|
|
2008-09-17 14:54:07 +02:00
|
|
|
gcPath(options, results, path);
|
2008-09-17 12:02:55 +02:00
|
|
|
|
|
|
|
/* For each reference of the current path, see if the
|
|
|
|
reference has now become deletable (i.e. is in the
|
|
|
|
set of dead paths and has no referrers left). If
|
|
|
|
so add it to the priority queue. */
|
|
|
|
foreach (PathSet::iterator, i, references) {
|
|
|
|
if (storePaths.find(*i) != storePaths.end() &&
|
|
|
|
queryReferrersNoSelf(*i).empty())
|
|
|
|
{
|
|
|
|
debug(format("path `%1%' has become deletable") % *i);
|
|
|
|
prioQueue.push(*i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2008-06-18 16:20:16 +02:00
|
|
|
} catch (GCLimitReached & e) {
|
|
|
|
}
|
2004-08-25 13:43:49 +02:00
|
|
|
}
|
2006-09-04 23:06:23 +02:00
|
|
|
|
2008-09-17 12:02:55 +02:00
|
|
|
|
2006-09-04 23:06:23 +02:00
|
|
|
}
|