#include "globals.hh"
#include "gc.hh"
#include "build.hh"
#include "pathlocks.hh"
#include <boost/shared_ptr.hpp>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
static string tempRootsDir = "temproots";
/* The file to which we write our temporary roots. */
Path fnTempRoots;
static AutoCloseFD fdTempRoots;
void addTempRoot(const Path & path)
{
/* Create the temporary roots file for this process. */
if (fdTempRoots == -1) {
while (1) {
fnTempRoots = (format("%1%/%2%/%3%")
% nixStateDir % tempRootsDir % getpid()).str();
fdTempRoots = open(fnTempRoots.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0600);
if (fdTempRoots == -1)
throw SysError(format("opening temporary roots file `%1%'") % fnTempRoots);
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. */
}
}
/* 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());
/* Downgrade to a read lock. */
debug(format("downgrading to read lock on `%1%'") % fnTempRoots);
lockFile(fdTempRoots, ltRead, true);
}
typedef shared_ptr<AutoCloseFD> FDPtr;
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);
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);
}
/* 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);
/* !!! write token, unlink */
continue;
}
/* Acquire a read lock. This will prevent the owning process
from upgrading to a write lock, therefore it will block in
addTempRoot(). */
debug(format("waiting for read lock on `%1%'") % path);
lockFile(*fd, ltRead, true);
/* Read the entire file. */
struct stat st;
if (fstat(*fd, &st) == -1)
throw SysError(format("statting `%1%'") % path);
unsigned char buf[st.st_size]; /* !!! stack space */
readFull(*fd, buf, st.st_size);
debug(format("FILE SIZE %1%") % st.st_size);
/* Extract the roots. */
string contents((char *) buf, st.st_size);
unsigned int pos = 0, end;
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 */
}
}
void collectGarbage(const PathSet & roots, GCAction action,
PathSet & result)
{
result.clear();
/* !!! TODO: Acquire the global GC root. This prevents
a) New roots from being added.
b) Processes from creating new temporary root files. */
/* !!! Restrict read permission on the GC root. Otherwise any
process that can open the file for reading can DoS the
collector. */
/* Determine the live paths which is just the closure of the
roots under the `references' relation. */
PathSet livePaths;
for (PathSet::const_iterator i = roots.begin(); i != roots.end(); ++i)
computeFSClosure(canonPath(*i), livePaths);
if (action == gcReturnLive) {
result = livePaths;
return;
}
/* 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);
for (FDs::iterator i = fds.begin(); i != fds.end(); ++i)
debug(format("FD %1%") % (int) **i);
/* !!! TODO: Try to acquire (without blocking) exclusive locks on
the files in the `pending' directory. Delete all files for
which we managed to acquire such a lock (since if we could get
such a lock, that means that the process that owned the file
has died). */
/* !!! TODO: Acquire shared locks on all files in the pending
directories. This prevents the set of pending paths from
increasing while we are garbage-collecting. Read the set of
pending paths from those files. */
/* Read the Nix store directory to find all currently existing
paths. */
Strings storeNames = readDirectory(nixStore);
for (Strings::iterator i = storeNames.begin(); i != storeNames.end(); ++i) {
Path path = canonPath(nixStore + "/" + *i);
if (livePaths.find(path) != livePaths.end()) {
debug(format("live path `%1%'") % path);
continue;
}
if (tempRoots.find(path) != tempRoots.end()) {
debug(format("temporary root `%1%'") % path);
continue;
}
debug(format("dead path `%1%'") % path);
result.insert(path);
if (action == gcDeleteDead) {
printMsg(lvlInfo, format("deleting `%1%'") % path);
deleteFromStore(path);
}
/* Only delete lock files if the path is belongs to doesn't
exist and isn't a temporary root and we can acquire an
exclusive lock on it. */
/* !!! */
}
}