LTP GCOV extension - code coverage report
Current view: directory - src/libstore - gc.cc
Test: app.info
Date: 2008-11-20 Instrumented lines: 328
Code covered: 70.4 % Executed lines: 231

       1                 : #include "globals.hh"
       2                 : #include "misc.hh"
       3                 : #include "pathlocks.hh"
       4                 : #include "local-store.hh"
       5                 : 
       6                 : #include <boost/shared_ptr.hpp>
       7                 : 
       8                 : #include <functional>
       9                 : #include <queue>
      10                 : 
      11                 : #include <sys/types.h>
      12                 : #include <sys/stat.h>
      13                 : #include <errno.h>
      14                 : #include <fcntl.h>
      15                 : #include <unistd.h>
      16                 : 
      17                 : #ifdef __CYGWIN__
      18                 : #include <windows.h>
      19                 : #include <sys/cygwin.h>
      20                 : #endif
      21                 : 
      22                 : 
      23                 : namespace nix {
      24                 : 
      25                 : 
      26            1704 : static string gcLockName = "gc.lock";
      27            1704 : static string tempRootsDir = "temproots";
      28            1704 : static string gcRootsDir = "gcroots";
      29                 : 
      30                 : static const int defaultGcLevel = 1000;
      31                 : 
      32                 : 
      33                 : /* Acquire the global GC lock.  This is used to prevent new Nix
      34                 :    processes from starting after the temporary root files have been
      35                 :    read.  To be precise: when they try to create a new temporary root
      36                 :    file, they will block until the garbage collector has finished /
      37                 :    yielded the GC lock. */
      38             227 : static int openGCLock(LockType lockType)
      39                 : {
      40                 :     Path fnGCLock = (format("%1%/%2%")
      41             227 :         % nixStateDir % gcLockName).str();
      42                 :          
      43             229 :     debug(format("acquiring global GC lock `%1%'") % fnGCLock);
      44                 :     
      45             227 :     AutoCloseFD fdGCLock = open(fnGCLock.c_str(), O_RDWR | O_CREAT, 0600);
      46             227 :     if (fdGCLock == -1)
      47               0 :         throw SysError(format("opening global GC lock `%1%'") % fnGCLock);
      48                 : 
      49             227 :     if (!lockFile(fdGCLock, lockType, false)) {
      50               0 :         printMsg(lvlError, format("waiting for the big garbage collector lock..."));
      51               0 :         lockFile(fdGCLock, lockType, true);
      52                 :     }
      53                 : 
      54                 :     /* !!! Restrict read permission on the GC root.  Otherwise any
      55                 :        process that can open the file for reading can DoS the
      56                 :        collector. */
      57                 :     
      58             227 :     return fdGCLock.borrow();
      59                 : }
      60                 : 
      61                 : 
      62              79 : void createSymlink(const Path & link, const Path & target, bool careful)
      63                 : {
      64                 :     /* Create directories up to `gcRoot'. */
      65              79 :     createDirs(dirOf(link));
      66                 : 
      67                 :     /* !!! shouldn't removing and creating the symlink be atomic? */
      68                 : 
      69                 :     /* Remove the old symlink. */
      70              79 :     if (pathExists(link)) {
      71              18 :         if (careful && (!isLink(link) || !isInStore(readLink(link))))
      72               0 :             throw Error(format("cannot create symlink `%1%'; already exists") % link);
      73              18 :         unlink(link.c_str());
      74                 :     }
      75                 : 
      76                 :     /* And create the new one. */
      77              79 :     if (symlink(target.c_str(), link.c_str()) == -1)
      78                 :         throw SysError(format("symlinking `%1%' to `%2%'")
      79               0 :             % link % target);
      80              79 : }
      81                 : 
      82                 : 
      83              61 : void LocalStore::syncWithGC()
      84                 : {
      85              61 :     AutoCloseFD fdGCLock = openGCLock(ltRead);
      86              61 : }
      87                 : 
      88                 : 
      89              18 : void LocalStore::addIndirectRoot(const Path & path)
      90                 : {
      91              18 :     string hash = printHash32(hashString(htSHA1, path));
      92                 :     Path realRoot = canonPath((format("%1%/%2%/auto/%3%")
      93              18 :         % nixStateDir % gcRootsDir % hash).str());
      94              18 :     createSymlink(realRoot, path, false);
      95              18 : }
      96                 : 
      97                 : 
      98                 : Path addPermRoot(const Path & _storePath, const Path & _gcRoot,
      99              61 :     bool indirect, bool allowOutsideRootsDir)
     100                 : {
     101              61 :     Path storePath(canonPath(_storePath));
     102              61 :     Path gcRoot(canonPath(_gcRoot));
     103              61 :     assertStorePath(storePath);
     104                 : 
     105              61 :     if (isInStore(gcRoot))
     106                 :         throw Error(format(
     107                 :                 "creating a garbage collector root (%1%) in the Nix store is forbidden "
     108               0 :                 "(are you running nix-build inside the store?)") % gcRoot);
     109                 : 
     110              61 :     if (indirect) {
     111              18 :         createSymlink(gcRoot, storePath, true);
     112              18 :         store->addIndirectRoot(gcRoot);
     113                 :     }
     114                 : 
     115                 :     else {
     116              43 :         if (!allowOutsideRootsDir) {
     117               0 :             Path rootsDir = canonPath((format("%1%/%2%") % nixStateDir % gcRootsDir).str());
     118                 :     
     119               0 :             if (string(gcRoot, 0, rootsDir.size() + 1) != rootsDir + "/")
     120                 :                 throw Error(format(
     121                 :                     "path `%1%' is not a valid garbage collector root; "
     122                 :                     "it's not in the directory `%2%'")
     123               0 :                     % gcRoot % rootsDir);
     124                 :         }
     125                 :             
     126              43 :         createSymlink(gcRoot, storePath, false);
     127                 :     }
     128                 : 
     129                 :     /* Check that the root can be found by the garbage collector.
     130                 :        !!! This can be very slow on machines that have many roots.
     131                 :        Instead of reading all the roots, it would be more efficient to
     132                 :        check if the root is in a directory in or linked from the
     133                 :        gcroots directory. */
     134              61 :     if (queryBoolSetting("gc-check-reachability", true)) {
     135              61 :         Roots roots = store->findRoots();
     136              61 :         if (roots.find(gcRoot) == roots.end())
     137               0 :             printMsg(lvlError, 
     138                 :                 format(
     139                 :                     "warning: `%1%' is not in a directory where the garbage collector looks for roots; "
     140                 :                     "therefore, `%2%' might be removed by the garbage collector")
     141                 :                 % gcRoot % storePath);
     142                 :     }
     143                 :         
     144                 :     /* Grab the global GC root, causing us to block while a GC is in
     145                 :        progress.  This prevents the set of permanent roots from
     146                 :        increasing while a GC is in progress. */
     147              61 :     store->syncWithGC();
     148                 :     
     149              61 :     return gcRoot;
     150                 : }
     151                 : 
     152                 : 
     153                 : /* The file to which we write our temporary roots. */
     154            1151 : static Path fnTempRoots;
     155            1151 : static AutoCloseFD fdTempRoots;
     156                 : 
     157                 : 
     158            1060 : void LocalStore::addTempRoot(const Path & path)
     159                 : {
     160                 :     /* Create the temporary roots file for this process. */
     161            1060 :     if (fdTempRoots == -1) {
     162                 : 
     163               0 :         while (1) {
     164             145 :             Path dir = (format("%1%/%2%") % nixStateDir % tempRootsDir).str();
     165             290 :             createDirs(dir);
     166                 :             
     167                 :             fnTempRoots = (format("%1%/%2%")
     168             145 :                 % dir % getpid()).str();
     169                 : 
     170             145 :             AutoCloseFD fdGCLock = openGCLock(ltRead);
     171                 :             
     172             145 :             if (pathExists(fnTempRoots))
     173                 :                 /* It *must* be stale, since there can be no two
     174                 :                    processes with the same pid. */
     175               0 :                 deletePath(fnTempRoots);
     176                 : 
     177             145 :             fdTempRoots = openLockFile(fnTempRoots, true);
     178                 : 
     179             145 :             fdGCLock.close();
     180                 :       
     181                 :             /* Note that on Cygwin a lot of the following complexity
     182                 :                is unnecessary, since we cannot delete open lock
     183                 :                files.  If we have the lock file open, then it's valid;
     184                 :                if we can delete it, then it wasn't in use any more. 
     185                 : 
     186                 :                Also note that on Cygwin we cannot "upgrade" a lock
     187                 :                from a read lock to a write lock. */
     188                 : 
     189                 : #ifndef __CYGWIN__
     190             145 :             debug(format("acquiring read lock on `%1%'") % fnTempRoots);
     191             145 :             lockFile(fdTempRoots, ltRead, true);
     192                 : 
     193                 :             /* Check whether the garbage collector didn't get in our
     194                 :                way. */
     195                 :             struct stat st;
     196             145 :             if (fstat(fdTempRoots, &st) == -1)
     197               0 :                 throw SysError(format("statting `%1%'") % fnTempRoots);
     198             290 :             if (st.st_size == 0) break;
     199                 :             
     200                 :             /* The garbage collector deleted this file before we could
     201                 :                get a lock.  (It won't delete the file after we get a
     202                 :                lock.)  Try again. */
     203                 : 
     204                 : #else
     205                 :             break;
     206                 : #endif
     207                 :         }
     208                 : 
     209                 :     }
     210                 : 
     211                 :     /* Upgrade the lock to a write lock.  This will cause us to block
     212                 :        if the garbage collector is holding our lock. */
     213            1060 :     debug(format("acquiring write lock on `%1%'") % fnTempRoots);
     214            1060 :     lockFile(fdTempRoots, ltWrite, true);
     215                 : 
     216            1060 :     string s = path + '\0';
     217            1060 :     writeFull(fdTempRoots, (const unsigned char *) s.c_str(), s.size());
     218                 : 
     219                 : #ifndef __CYGWIN__
     220                 :     /* Downgrade to a read lock. */
     221            1060 :     debug(format("downgrading to read lock on `%1%'") % fnTempRoots);
     222            1060 :     lockFile(fdTempRoots, ltRead, true);
     223                 : #else
     224                 :     debug(format("releasing write lock on `%1%'") % fnTempRoots);
     225                 :     lockFile(fdTempRoots, ltNone, true);
     226                 : #endif
     227            1060 : }
     228                 : 
     229                 : 
     230             639 : void removeTempRoots()
     231                 : {
     232             639 :     if (fdTempRoots != -1) {
     233             145 :         fdTempRoots.close();
     234             145 :         unlink(fnTempRoots.c_str());
     235                 :     }
     236             639 : }
     237                 : 
     238                 : 
     239                 : typedef boost::shared_ptr<AutoCloseFD> FDPtr;
     240                 : typedef list<FDPtr> FDs;
     241                 : 
     242                 : 
     243              19 : static void readTempRoots(PathSet & tempRoots, FDs & fds)
     244                 : {
     245                 :     /* Read the `temproots' directory for per-process temporary root
     246                 :        files. */
     247                 :     Strings tempRootFiles = readDirectory(
     248              19 :         (format("%1%/%2%") % nixStateDir % tempRootsDir).str());
     249                 : 
     250              40 :     for (Strings::iterator i = tempRootFiles.begin();
     251                 :          i != tempRootFiles.end(); ++i)
     252                 :     {
     253               1 :         Path path = (format("%1%/%2%/%3%") % nixStateDir % tempRootsDir % *i).str();
     254                 : 
     255               1 :         debug(format("reading temporary root file `%1%'") % path);
     256                 : 
     257                 : #ifdef __CYGWIN__
     258                 :         /* On Cygwin we just try to delete the lock file. */
     259                 :         char win32Path[MAX_PATH];
     260                 :         cygwin_conv_to_full_win32_path(path.c_str(), win32Path);
     261                 :         if (DeleteFile(win32Path)) {
     262                 :             printMsg(lvlError, format("removed stale temporary roots file `%1%'")
     263                 :                 % path);
     264                 :             continue;
     265                 :         } else
     266                 :             debug(format("delete of `%1%' failed: %2%") % path % GetLastError());
     267                 : #endif
     268                 : 
     269               1 :         FDPtr fd(new AutoCloseFD(open(path.c_str(), O_RDWR, 0666)));
     270               1 :         if (*fd == -1) {
     271                 :             /* It's okay if the file has disappeared. */
     272               0 :             if (errno == ENOENT) continue;
     273               0 :             throw SysError(format("opening temporary roots file `%1%'") % path);
     274                 :         }
     275                 : 
     276                 :         /* This should work, but doesn't, for some reason. */
     277                 :         //FDPtr fd(new AutoCloseFD(openLockFile(path, false)));
     278                 :         //if (*fd == -1) continue;
     279                 : 
     280                 : #ifndef __CYGWIN__
     281                 :         /* Try to acquire a write lock without blocking.  This can
     282                 :            only succeed if the owning process has died.  In that case
     283                 :            we don't care about its temporary roots. */
     284               1 :         if (lockFile(*fd, ltWrite, false)) {
     285               0 :             printMsg(lvlError, format("removing stale temporary roots file `%1%'")
     286                 :                 % path);
     287               0 :             unlink(path.c_str());
     288               0 :             writeFull(*fd, (const unsigned char *) "d", 1);
     289                 :             continue;
     290                 :         }
     291                 : #endif
     292                 : 
     293                 :         /* Acquire a read lock.  This will prevent the owning process
     294                 :            from upgrading to a write lock, therefore it will block in
     295                 :            addTempRoot(). */
     296               1 :         debug(format("waiting for read lock on `%1%'") % path);
     297               1 :         lockFile(*fd, ltRead, true);
     298                 : 
     299                 :         /* Read the entire file. */
     300               1 :         string contents = readFile(*fd);
     301                 : 
     302                 :         /* Extract the roots. */
     303               1 :         string::size_type pos = 0, end;
     304                 : 
     305              10 :         while ((end = contents.find((char) 0, pos)) != string::npos) {
     306               9 :             Path root(contents, pos, end - pos);
     307               9 :             debug(format("got temporary root `%1%'") % root);
     308               9 :             assertStorePath(root);
     309               9 :             tempRoots.insert(root);
     310               9 :             pos = end + 1;
     311                 :         }
     312                 : 
     313               1 :         fds.push_back(fd); /* keep open */
     314              19 :     }
     315              19 : }
     316                 : 
     317                 : 
     318                 : static void findRoots(const Path & path, bool recurseSymlinks,
     319             908 :     bool deleteStale, Roots & roots)
     320                 : {
     321                 :     try {
     322                 :         
     323                 :         struct stat st;
     324             908 :         if (lstat(path.c_str(), &st) == -1)
     325               0 :             throw SysError(format("statting `%1%'") % path);
     326                 : 
     327             908 :         printMsg(lvlVomit, format("looking at `%1%'") % path);
     328                 : 
     329             908 :         if (S_ISDIR(st.st_mode)) {
     330             217 :             Strings names = readDirectory(path);
     331             869 :             for (Strings::iterator i = names.begin(); i != names.end(); ++i)
     332             869 :                 findRoots(path + "/" + *i, recurseSymlinks, deleteStale, roots);
     333                 :         }
     334                 : 
     335             691 :         else if (S_ISLNK(st.st_mode)) {
     336             648 :             Path target = absPath(readLink(path), dirOf(path));
     337                 : 
     338             648 :             if (isInStore(target)) {
     339             400 :                 debug(format("found root `%1%' in `%2%'")
     340                 :                     % target % path);
     341             400 :                 Path storePath = toStorePath(target);
     342             400 :                 if (store->isValidPath(storePath)) 
     343             295 :                     roots[path] = storePath;
     344                 :                 else
     345             105 :                     printMsg(lvlInfo, format("skipping invalid root from `%1%' to `%2%'")
     346                 :                         % path % storePath);
     347                 :             }
     348                 : 
     349             248 :             else if (recurseSymlinks) {
     350             185 :                 if (pathExists(target))
     351             174 :                     findRoots(target, false, deleteStale, roots);
     352              11 :                 else if (deleteStale) {
     353               3 :                     printMsg(lvlInfo, format("removing stale link from `%1%' to `%2%'") % path % target);
     354                 :                     /* Note that we only delete when recursing, i.e.,
     355                 :                        when we are still in the `gcroots' tree.  We
     356                 :                        never delete stuff outside that tree. */
     357               3 :                     unlink(path.c_str());
     358                 :                 }
     359             648 :             }
     360                 :         }
     361                 : 
     362                 :     }
     363                 : 
     364               0 :     catch (SysError & e) {
     365                 :         /* We only ignore permanent failures. */
     366               0 :         if (e.errNo == EACCES || e.errNo == ENOENT || e.errNo == ENOTDIR)
     367               0 :             printMsg(lvlInfo, format("cannot read potential root `%1%'") % path);
     368                 :         else
     369               0 :             throw;
     370                 :     }
     371             908 : }
     372                 : 
     373                 : 
     374              82 : static Roots findRoots(bool deleteStale)
     375                 : {
     376              82 :     Roots roots;
     377              82 :     Path rootsDir = canonPath((format("%1%/%2%") % nixStateDir % gcRootsDir).str());
     378              82 :     findRoots(rootsDir, true, deleteStale, roots);
     379              82 :     return roots;
     380                 : }
     381                 : 
     382                 : 
     383              61 : Roots LocalStore::findRoots()
     384                 : {
     385              61 :     return nix::findRoots(false);
     386                 : }
     387                 : 
     388                 : 
     389              21 : static void addAdditionalRoots(PathSet & roots)
     390                 : {
     391                 :     Path rootFinder = getEnv("NIX_ROOT_FINDER",
     392              21 :         nixLibexecDir + "/nix/find-runtime-roots.pl");
     393                 : 
     394              62 :     if (rootFinder.empty()) return;
     395                 :     
     396               1 :     debug(format("executing `%1%' to find additional roots") % rootFinder);
     397                 : 
     398               1 :     string result = runProgram(rootFinder);
     399                 : 
     400               2 :     Strings paths = tokenizeString(result, "\n");
     401                 :     
     402             253 :     for (Strings::iterator i = paths.begin(); i != paths.end(); ++i) {
     403             251 :         if (isInStore(*i)) {
     404               1 :             Path path = toStorePath(*i);
     405               1 :             if (roots.find(path) == roots.end() && store->isValidPath(path)) {
     406               1 :                 debug(format("found additional root `%1%'") % path);
     407               1 :                 roots.insert(path);
     408               1 :             }
     409                 :         }
     410               1 :     }
     411                 : }
     412                 : 
     413                 : 
     414                 : static void dfsVisit(const PathSet & paths, const Path & path,
     415              48 :     PathSet & visited, Paths & sorted)
     416                 : {
     417              48 :     if (visited.find(path) != visited.end()) return;
     418              36 :     visited.insert(path);
     419                 :     
     420              36 :     PathSet references;
     421              36 :     if (store->isValidPath(path))
     422              36 :         store->queryReferences(path, references);
     423                 :     
     424              63 :     for (PathSet::iterator i = references.begin();
     425                 :          i != references.end(); ++i)
     426                 :         /* Don't traverse into paths that don't exist.  That can
     427                 :            happen due to substitutes for non-existent paths. */
     428              27 :         if (*i != path && paths.find(*i) != paths.end())
     429              12 :             dfsVisit(paths, *i, visited, sorted);
     430                 : 
     431              36 :     sorted.push_front(path);
     432                 : }
     433                 : 
     434                 : 
     435              27 : Paths topoSortPaths(const PathSet & paths)
     436                 : {
     437              27 :     Paths sorted;
     438              27 :     PathSet visited;
     439              63 :     for (PathSet::const_iterator i = paths.begin(); i != paths.end(); ++i)
     440              36 :         dfsVisit(paths, *i, visited, sorted);
     441              27 :     return sorted;
     442                 : }
     443                 : 
     444                 : 
     445               0 : static time_t lastFileAccessTime(const Path & path)
     446                 : {
     447               0 :     checkInterrupt();
     448                 :     
     449                 :     struct stat st;
     450               0 :     if (lstat(path.c_str(), &st) == -1)
     451               0 :         throw SysError(format("statting `%1%'") % path);
     452                 : 
     453               0 :     if (S_ISDIR(st.st_mode)) {
     454               0 :         time_t last = 0;
     455               0 :         Strings names = readDirectory(path);
     456               0 :         for (Strings::iterator i = names.begin(); i != names.end(); ++i) {
     457               0 :             time_t t = lastFileAccessTime(path + "/" + *i);
     458               0 :             if (t > last) last = t;
     459                 :         }
     460               0 :         return last;
     461                 :     }
     462                 : 
     463               0 :     else if (S_ISLNK(st.st_mode)) return 0;
     464                 : 
     465               0 :     else return st.st_atime;
     466                 : }
     467                 : 
     468                 : 
     469                 : struct GCLimitReached { };
     470                 : 
     471                 : 
     472                 : void LocalStore::gcPath(const GCOptions & options, GCResults & results, 
     473            2676 :     const Path & path)
     474                 : {
     475            2676 :     results.paths.insert(path);
     476                 : 
     477            2676 :     if (!pathExists(path)) return;
     478                 :                 
     479                 : #ifndef __CYGWIN__
     480            2676 :     AutoCloseFD fdLock;
     481                 :         
     482                 :     /* Only delete a lock file if we can acquire a write lock on it.
     483                 :        That means that it's either stale, or the process that created
     484                 :        it hasn't locked it yet.  In the latter case the other process
     485                 :        will detect that we deleted the lock, and retry (see
     486                 :        pathlocks.cc). */
     487            2676 :     if (path.size() >= 5 && string(path, path.size() - 5) == ".lock") {
     488               2 :         fdLock = openLockFile(path, false);
     489               2 :         if (fdLock != -1 && !lockFile(fdLock, ltWrite, false)) {
     490               1 :             debug(format("skipping active lock `%1%'") % path);
     491               1 :             return;
     492                 :         }
     493                 :     }
     494                 : #endif
     495                 : 
     496                 :     /* Okay, it's safe to delete. */
     497                 :     unsigned long long bytesFreed, blocksFreed;
     498            2675 :     deleteFromStore(path, bytesFreed, blocksFreed);
     499            2675 :     results.bytesFreed += bytesFreed;
     500            2675 :     results.blocksFreed += blocksFreed;
     501                 : 
     502            2675 :     if (results.bytesFreed > options.maxFreed) {
     503               0 :         printMsg(lvlInfo, format("deleted more than %1% bytes; stopping") % options.maxFreed);
     504               0 :         throw GCLimitReached();
     505                 :     }
     506                 : 
     507            2675 :     if (options.maxLinks) {
     508                 :         struct stat st;
     509               0 :         if (stat(nixStore.c_str(), &st) == -1)
     510               0 :             throw SysError(format("statting `%1%'") % nixStore);
     511               0 :         if (st.st_nlink < options.maxLinks) {
     512               0 :             printMsg(lvlInfo, format("link count on the store has dropped below %1%; stopping") % options.maxLinks);
     513               0 :             throw GCLimitReached();
     514                 :         }
     515                 :     }
     516                 : 
     517                 : #ifndef __CYGWIN__
     518            2675 :     if (fdLock != -1)
     519                 :         /* Write token to stale (deleted) lock file. */
     520               1 :         writeFull(fdLock, (const unsigned char *) "d", 1);
     521                 : #endif
     522                 : }
     523                 : 
     524                 : 
     525                 : void LocalStore::gcPathRecursive(const GCOptions & options,
     526            3737 :     GCResults & results, PathSet & done, const Path & path)
     527                 : {
     528            3737 :     if (done.find(path) != done.end()) return;
     529            2676 :     done.insert(path);
     530                 : 
     531            2676 :     startNest(nest, lvlDebug, format("looking at `%1%'") % path);
     532                 :         
     533                 :     /* Delete all the referrers first.  They must be garbage too,
     534                 :        since if they were live, then the current path would also be
     535                 :        live.  Note that deleteFromStore() below still makes sure that
     536                 :        the referrer set has become empty, just in case.  (However that
     537                 :        doesn't guard against deleting top-level paths that are only
     538                 :        reachable from GC roots.) */
     539            2676 :     PathSet referrers;
     540            2676 :     if (isValidPath(path))
     541            2673 :         queryReferrers(path, referrers);
     542            3741 :     foreach (PathSet::iterator, i, referrers)
     543            1065 :         if (*i != path) gcPathRecursive(options, results, done, *i);
     544                 : 
     545            2676 :     printMsg(lvlInfo, format("deleting `%1%'") % path);
     546                 :             
     547            2676 :     gcPath(options, results, path);
     548                 : }
     549                 : 
     550                 : 
     551                 : struct CachingAtimeComparator : public std::binary_function<Path, Path, bool> 
     552               0 : {
     553                 :     std::map<Path, time_t> cache;
     554                 : 
     555               0 :     time_t lookup(const Path & p)
     556                 :     {
     557               0 :         std::map<Path, time_t>::iterator i = cache.find(p);
     558               0 :         if (i != cache.end()) return i->second;
     559               0 :         debug(format("computing atime of `%1%'") % p);
     560               0 :         cache[p] = lastFileAccessTime(p);
     561               0 :         assert(cache.find(p) != cache.end());
     562               0 :         return cache[p];
     563                 :     }
     564                 :         
     565               0 :     bool operator () (const Path & p1, const Path & p2)
     566                 :     {
     567               0 :         return lookup(p2) < lookup(p1);
     568                 :     }
     569                 : };
     570                 : 
     571                 : 
     572               0 : string showTime(const string & format, time_t t)
     573                 : {
     574                 :     char s[128];
     575               0 :     strftime(s, sizeof s, format.c_str(), localtime(&t));
     576               0 :     return string(s);
     577                 : }
     578                 : 
     579                 : 
     580              21 : void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
     581                 : {
     582                 :     bool gcKeepOutputs =
     583              21 :         queryBoolSetting("gc-keep-outputs", false);
     584                 :     bool gcKeepDerivations =
     585              42 :         queryBoolSetting("gc-keep-derivations", true);
     586                 :     int gcKeepOutputsThreshold = 
     587              42 :         queryIntSetting ("gc-keep-outputs-threshold", defaultGcLevel);
     588                 : 
     589                 :     /* Acquire the global GC root.  This prevents
     590                 :        a) New roots from being added.
     591                 :        b) Processes from creating new temporary root files. */
     592              21 :     AutoCloseFD fdGCLock = openGCLock(ltWrite);
     593                 : 
     594                 :     /* Find the roots.  Since we've grabbed the GC lock, the set of
     595                 :        permanent roots cannot increase now. */
     596              42 :     printMsg(lvlError, format("finding garbage collector roots..."));
     597              21 :     Roots rootMap = options.ignoreLiveness ? Roots() : nix::findRoots(true);
     598                 : 
     599              21 :     PathSet roots;
     600              65 :     for (Roots::iterator i = rootMap.begin(); i != rootMap.end(); ++i)
     601              44 :         roots.insert(i->second);
     602                 : 
     603                 :     /* Add additional roots returned by the program specified by the
     604                 :        NIX_ROOT_FINDER environment variable.  This is typically used
     605                 :        to add running programs to the set of roots (to prevent them
     606                 :        from being garbage collected). */
     607              21 :     if (!options.ignoreLiveness)
     608              21 :         addAdditionalRoots(roots);
     609                 : 
     610              21 :     if (options.action == GCOptions::gcReturnRoots) {
     611               1 :         results.paths = roots;
     612               5 :         return;
     613                 :     }
     614                 : 
     615                 :     /* Determine the live paths which is just the closure of the
     616                 :        roots under the `references' relation. */
     617              20 :     printMsg(lvlError, format("computing live paths..."));
     618              20 :     PathSet livePaths;
     619              56 :     for (PathSet::const_iterator i = roots.begin(); i != roots.end(); ++i)
     620              36 :         computeFSClosure(canonPath(*i), livePaths);
     621                 : 
     622              20 :     if (gcKeepDerivations) {
     623               0 :         for (PathSet::iterator i = livePaths.begin();
     624                 :              i != livePaths.end(); ++i)
     625                 :         {
     626                 :             /* Note that the deriver need not be valid (e.g., if we
     627                 :                previously ran the collector with `gcKeepDerivations'
     628                 :                turned off). */
     629               0 :             Path deriver = queryDeriver(*i);
     630               0 :             if (deriver != "" && isValidPath(deriver))
     631               0 :                 computeFSClosure(deriver, livePaths);
     632                 :         }
     633                 :     }
     634                 : 
     635              20 :     if (gcKeepOutputs) {
     636                 :         /* Hmz, identical to storePathRequisites in nix-store. */
     637               0 :         for (PathSet::iterator i = livePaths.begin();
     638                 :              i != livePaths.end(); ++i)
     639               0 :             if (isDerivation(*i)) {
     640               0 :                 Derivation drv = derivationFromPath(*i);
     641                 : 
     642               0 :                 string gcLevelStr = drv.env["__gcLevel"];
     643                 :                 int gcLevel;
     644               0 :                 if (!string2Int(gcLevelStr, gcLevel))
     645               0 :                     gcLevel = defaultGcLevel;
     646                 :                 
     647               0 :                 if (gcLevel >= gcKeepOutputsThreshold)    
     648               0 :                     for (DerivationOutputs::iterator j = drv.outputs.begin();
     649                 :                          j != drv.outputs.end(); ++j)
     650               0 :                         if (isValidPath(j->second.path))
     651               0 :                             computeFSClosure(j->second.path, livePaths);
     652                 :             }
     653                 :     }
     654                 : 
     655              20 :     if (options.action == GCOptions::gcReturnLive) {
     656               1 :         results.paths = livePaths;
     657                 :         return;
     658                 :     }
     659                 : 
     660                 :     /* Read the temporary roots.  This acquires read locks on all
     661                 :        per-process temporary root files.  So after this point no paths
     662                 :        can be added to the set of temporary roots. */
     663              19 :     PathSet tempRoots;
     664              19 :     FDs fds;
     665              19 :     readTempRoots(tempRoots, fds);
     666                 : 
     667                 :     /* Close the temporary roots.  Note that we *cannot* do this in
     668                 :        readTempRoots(), because there we may not have all locks yet,
     669                 :        meaning that an invalid path can become valid (and thus add to
     670                 :        the references graph) after we have added it to the closure
     671                 :        (and computeFSClosure() assumes that the presence of a path
     672                 :        means that it has already been closed). */
     673              19 :     PathSet tempRootsClosed;
     674              28 :     for (PathSet::iterator i = tempRoots.begin(); i != tempRoots.end(); ++i)
     675               9 :         if (isValidPath(*i))
     676               8 :             computeFSClosure(*i, tempRootsClosed);
     677                 :         else
     678               1 :             tempRootsClosed.insert(*i);
     679                 : 
     680                 :     /* After this point the set of roots or temporary roots cannot
     681                 :        increase, since we hold locks on everything.  So everything
     682                 :        that is not currently in in `livePaths' or `tempRootsClosed'
     683                 :        can be deleted. */
     684                 :     
     685                 :     /* Read the Nix store directory to find all currently existing
     686                 :        paths and filter out all live paths. */
     687              19 :     printMsg(lvlError, format("reading the Nix store..."));
     688              19 :     PathSet storePaths;
     689                 :     
     690              19 :     if (options.action != GCOptions::gcDeleteSpecific) {
     691              16 :         Paths entries = readDirectory(nixStore);
     692            2861 :         foreach (Paths::iterator, i, entries) {
     693            2845 :             Path path = canonPath(nixStore + "/" + *i);
     694            2845 :             if (livePaths.find(path) == livePaths.end() &&
     695                 :                 tempRootsClosed.find(path) == tempRootsClosed.end())
     696            2762 :                 storePaths.insert(path);
     697              16 :         }
     698                 :     }
     699                 : 
     700                 :     else {
     701               4 :         foreach (PathSet::iterator, i, options.pathsToDelete) {
     702               3 :             assertStorePath(*i);
     703               3 :             storePaths.insert(*i);
     704               3 :             if (livePaths.find(*i) != livePaths.end())
     705               2 :                 throw Error(format("cannot delete path `%1%' since it is still alive") % *i);
     706               1 :             if (tempRootsClosed.find(*i) != tempRootsClosed.end())
     707               0 :                 throw Error(format("cannot delete path `%1%' since it is temporarily in use") % *i);
     708                 :         }
     709                 :     }
     710                 : 
     711              17 :     if (options.action == GCOptions::gcReturnDead) {
     712               3 :         results.paths.insert(storePaths.begin(), storePaths.end());
     713                 :         return;
     714                 :     }
     715                 : 
     716                 :     /* Delete all dead store paths (or until one of the stop
     717                 :        conditions is reached). */
     718                 : 
     719              14 :     PathSet done;
     720                 :     try {
     721                 : 
     722              14 :         if (!options.useAtime) {
     723                 :             /* Delete the paths, respecting the partial ordering
     724                 :                determined by the references graph. */
     725              14 :             printMsg(lvlError, format("deleting garbage..."));
     726            2690 :             foreach (PathSet::iterator, i, storePaths)
     727            2676 :                 gcPathRecursive(options, results, done, *i);
     728                 :         }
     729                 : 
     730                 :         else {
     731                 : 
     732                 :             /* Delete in order of ascending last access time, still
     733                 :                maintaining the partial ordering of the reference
     734                 :                graph.  Note that we can't use a topological sort for
     735                 :                this because that takes time O(V+E), and in this case
     736                 :                E=O(V^2) (i.e. the graph is dense because of the edges
     737                 :                due to the atime ordering).  So instead we put all
     738                 :                deletable paths in a priority queue (ordered by atime),
     739                 :                and after deleting a path, add additional paths that
     740                 :                have become deletable to the priority queue. */
     741                 : 
     742               0 :             CachingAtimeComparator atimeComp;
     743                 : 
     744                 :             /* Create a priority queue that orders paths by ascending
     745                 :                atime.  This is why C++ needs type inferencing... */
     746                 :             std::priority_queue<Path, vector<Path>, binary_function_ref_adapter<CachingAtimeComparator> > prioQueue =
     747               0 :                 std::priority_queue<Path, vector<Path>, binary_function_ref_adapter<CachingAtimeComparator> >(binary_function_ref_adapter<CachingAtimeComparator>(&atimeComp));
     748                 : 
     749                 :            /* Initially put the paths that are invalid or have no
     750                 :               referrers into the priority queue. */
     751               0 :             printMsg(lvlError, format("finding deletable paths..."));
     752               0 :             foreach (PathSet::iterator, i, storePaths) {
     753               0 :                 checkInterrupt();
     754                 :                 /* We can safely delete a path if it's invalid or
     755                 :                    it has no referrers.  Note that all the invalid
     756                 :                    paths will be deleted in the first round. */
     757               0 :                 if (isValidPath(*i)) {
     758               0 :                     if (queryReferrersNoSelf(*i).empty()) prioQueue.push(*i);
     759               0 :                 } else prioQueue.push(*i);
     760                 :             }
     761                 : 
     762               0 :             debug(format("%1% initially deletable paths") % prioQueue.size());
     763                 : 
     764                 :             /* Now delete everything in the order of the priority
     765                 :                queue until nothing is left. */
     766               0 :             printMsg(lvlError, format("deleting garbage..."));
     767               0 :             while (!prioQueue.empty()) {
     768               0 :                 checkInterrupt();
     769               0 :                 Path path = prioQueue.top(); prioQueue.pop();
     770                 : 
     771               0 :                 if (options.maxAtime != (time_t) -1 &&
     772                 :                     atimeComp.lookup(path) > options.maxAtime)
     773               0 :                     continue;
     774                 :                 
     775               0 :                 printMsg(lvlInfo, format("deleting `%1%' (last accessed %2%)") % path % showTime("%F %H:%M:%S", atimeComp.lookup(path)));
     776                 : 
     777               0 :                 PathSet references;
     778               0 :                 if (isValidPath(path)) references = queryReferencesNoSelf(path);
     779                 : 
     780               0 :                 gcPath(options, results, path);
     781                 : 
     782                 :                 /* For each reference of the current path, see if the
     783                 :                    reference has now become deletable (i.e. is in the
     784                 :                    set of dead paths and has no referrers left).  If
     785                 :                    so add it to the priority queue. */
     786               0 :                 foreach (PathSet::iterator, i, references) {
     787               0 :                     if (storePaths.find(*i) != storePaths.end() &&
     788                 :                         queryReferrersNoSelf(*i).empty())
     789                 :                     {
     790               0 :                         debug(format("path `%1%' has become deletable") % *i);
     791               0 :                         prioQueue.push(*i);
     792                 :                     }
     793                 :                 }
     794               0 :             }
     795                 :             
     796                 :         }
     797                 :         
     798               0 :     } catch (GCLimitReached & e) {
     799              14 :     }
     800                 : }
     801                 : 
     802               0 : 
     803            1106 : }

Generated by: LTP GCOV extension version 1.6