#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. */ /* !!! */ } }