about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rwxr-xr-xscripts/find-runtime-roots.pl.in79
-rw-r--r--scripts/local.mk2
-rw-r--r--src/libstore/gc.cc116
4 files changed, 111 insertions, 89 deletions
diff --git a/.gitignore b/.gitignore
index 178783d22d36..a524e9b6a29b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,6 +60,7 @@ Makefile.config
 
 # /src/libstore/
 /src/libstore/schema.sql.hh
+/src/libstore/sandbox-defaults.sb
 
 # /src/nix-env/
 /src/nix-env/nix-env
@@ -94,6 +95,8 @@ Makefile.config
 /misc/systemd/nix-daemon.socket
 /misc/upstart/nix-daemon.conf
 
+inst/
+
 *.a
 *.o
 *.so
diff --git a/scripts/find-runtime-roots.pl.in b/scripts/find-runtime-roots.pl.in
deleted file mode 100755
index e1a2dde556b6..000000000000
--- a/scripts/find-runtime-roots.pl.in
+++ /dev/null
@@ -1,79 +0,0 @@
-#! @perl@ -w @perlFlags@
-
-use strict;
-use Nix::Utils;
-use Nix::Config;
-
-
-sub readProc {
-    return unless -d "/proc";
-
-    opendir DIR, "/proc" or return;
-
-    foreach my $name (readdir DIR) {
-        next unless $name =~ /^\d+$/;
-
-        my $process = "/proc/$name";
-
-        #print STDERR "=== $process\n";
-
-        my $target;
-        print "$target\n" if $target = readlink "$process/exe";
-        print "$target\n" if $target = readlink "$process/cwd";
-
-        if (opendir FDS, "$process/fd") {
-            foreach my $name (readdir FDS) {
-                $target = readlink "$process/fd/$name";
-                print "$target\n" if $target && substr($target, 0, 1) eq "/";
-            }
-            closedir FDS;
-        }
-
-        if (open MAP, "<$process/maps") {
-            while (<MAP>) {
-                next unless /^ \s* \S+ \s+ \S+ \s+ \S+ \s+ \S+ \s+ \S+ \s+ (\/\S+) \s* $/x;
-                print "$1\n";
-            }
-            close MAP;
-        }
-
-        # Get all store paths that appear in the environment of this process.
-        eval {
-            my $env = Nix::Utils::readFile "$process/environ";
-            my @matches = $env =~ /\Q$Nix::Config::storeDir\E\/[0-9a-z]+[0-9a-zA-Z\+\-\._\?=]*/g;
-            print "$_\n" foreach @matches;
-        }
-    }
-
-    closedir DIR;
-}
-
-
-sub lsof {
-    return unless open LSOF, "lsof -n -w -F n 2> /dev/null |";
-
-    while (<LSOF>) {
-        next unless /^n (\/ .*)$/x;
-        print $1, "\n";
-    }
-
-    close LSOF;
-}
-
-
-readProc;
-lsof;
-
-
-sub printFile {
-    my ($fn) = @_;
-    if (-e $fn) {
-        print Nix::Utils::readFile($fn), "\n";
-    }
-}
-
-
-# This is rather NixOS-specific, so it probably shouldn't be here.
-printFile "/proc/sys/kernel/modprobe";
-printFile "/proc/sys/kernel/fbsplash";
-printFile "/proc/sys/kernel/poweroff_cmd";
diff --git a/scripts/local.mk b/scripts/local.mk
index 13b13a86bc6c..86b6d08500aa 100644
--- a/scripts/local.mk
+++ b/scripts/local.mk
@@ -9,7 +9,6 @@ bin-scripts += $(nix_bin_scripts)
 
 nix_noinst_scripts := \
   $(d)/build-remote.pl \
-  $(d)/find-runtime-roots.pl \
   $(d)/resolve-system-dependencies.pl \
   $(d)/nix-http-export.cgi \
   $(d)/nix-profile.sh \
@@ -20,7 +19,6 @@ noinst-scripts += $(nix_noinst_scripts)
 profiledir = $(sysconfdir)/profile.d
 
 $(eval $(call install-file-as, $(d)/nix-profile.sh, $(profiledir)/nix.sh, 0644))
-$(eval $(call install-program-in, $(d)/find-runtime-roots.pl, $(libexecdir)/nix))
 $(eval $(call install-program-in, $(d)/build-remote.pl, $(libexecdir)/nix))
 $(eval $(call install-program-in, $(d)/resolve-system-dependencies.pl, $(libexecdir)/nix))
 $(eval $(call install-symlink, nix-build, $(bindir)/nix-shell))
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index 9324508cc87c..2eab7de0d8bf 100644
--- a/src/libstore/gc.cc
+++ b/src/libstore/gc.cc
@@ -5,13 +5,14 @@
 #include <functional>
 #include <queue>
 #include <algorithm>
+#include <regex>
 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <unistd.h>
-
+#include <climits>
 
 namespace nix {
 
@@ -330,18 +331,117 @@ Roots LocalStore::findRoots()
 }
 
 
-void LocalStore::findRuntimeRoots(PathSet & roots)
+static void readProcLink(const string & file, StringSet & paths)
 {
-    Path rootFinder = getEnv("NIX_ROOT_FINDER",
-        settings.nixLibexecDir + "/nix/find-runtime-roots.pl");
+    /* 64 is the starting buffer size gnu readlink uses... */
+    auto bufsiz = ssize_t{64};
+try_again:
+    char buf[bufsiz];
+    auto res = readlink(file.c_str(), buf, bufsiz);
+    if (res == -1) {
+        if (errno == ENOENT || errno == EACCES)
+            return;
+        throw SysError("reading symlink");
+    }
+    if (res == bufsiz) {
+        if (SSIZE_MAX / 2 < bufsiz)
+            throw Error("stupidly long symlink");
+        bufsiz *= 2;
+        goto try_again;
+    }
+    if (res > 0 && buf[0] == '/')
+        paths.emplace(static_cast<char *>(buf), res);
+    return;
+}
 
-    if (rootFinder.empty()) return;
+static string quoteRegexChars(const string & raw)
+{
+    static auto specialRegex = std::regex(R"([.^$\\*+?()\[\]{}|])");
+    return std::regex_replace(raw, specialRegex, R"(\$&)");
+}
 
-    debug(format("executing ‘%1%’ to find additional roots") % rootFinder);
+static void readFileRoots(const char * path, StringSet & paths)
+{
+    try {
+        paths.emplace(readFile(path));
+    } catch (SysError & e) {
+        if (e.errNo != ENOENT && e.errNo != EACCES)
+            throw;
+    }
+}
 
-    string result = runProgram(rootFinder);
+void LocalStore::findRuntimeRoots(PathSet & roots)
+{
+    StringSet paths;
+    auto procDir = AutoCloseDir{opendir("/proc")};
+    if (procDir) {
+        struct dirent * ent;
+        auto digitsRegex = std::regex(R"(^\d+$)");
+        auto mapRegex = std::regex(R"(^\s*\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(/\S+)\s*$)");
+        auto storePathRegex = std::regex(quoteRegexChars(storeDir) + R"(/[0-9a-z]+[0-9a-zA-Z\+\-\._\?=]*)");
+        while (errno = 0, ent = readdir(procDir)) {
+            checkInterrupt();
+            if (std::regex_match(ent->d_name, digitsRegex)) {
+                readProcLink((format("/proc/%1%/exe") % ent->d_name).str(), paths);
+                readProcLink((format("/proc/%1%/cwd") % ent->d_name).str(), paths);
+
+                auto fdStr = (format("/proc/%1%/fd") % ent->d_name).str();
+                auto fdDir = AutoCloseDir(opendir(fdStr.c_str()));
+                if (!fdDir) {
+                    if (errno == ENOENT || errno == EACCES)
+                        continue;
+                    throw SysError(format("opening %1%") % fdStr);
+                }
+                struct dirent * fd_ent;
+                while (errno = 0, fd_ent = readdir(fdDir)) {
+                    if (fd_ent->d_name[0] != '.') {
+                        readProcLink((format("%1%/%2%") % fdStr % fd_ent->d_name).str(), paths);
+                    }
+                }
+                if (errno)
+                    throw SysError(format("iterating /proc/%1%/fd") % ent->d_name);
+                fdDir.close();
+
+                auto mapLines =
+                    tokenizeString<std::vector<string>>(readFile((format("/proc/%1%/maps") % ent->d_name).str(), true), "\n");
+                for (const auto& line : mapLines) {
+                    auto match = std::smatch{};
+                    if (std::regex_match(line, match, mapRegex))
+                        paths.emplace(match[1]);
+                }
+
+                try {
+                    auto envString = readFile((format("/proc/%1%/environ") % ent->d_name).str(), true);
+                    auto env_end = std::sregex_iterator{};
+                    for (auto i = std::sregex_iterator{envString.begin(), envString.end(), storePathRegex}; i != env_end; ++i)
+                        paths.emplace(i->str());
+                } catch (SysError & e) {
+                    if (errno == ENOENT || errno == EACCES)
+                        continue;
+                    throw;
+                }
+            }
+        }
+        if (errno)
+            throw SysError("iterating /proc");
+    }
+
+    try {
+        auto lsofRegex = std::regex(R"(^n(/.*)$)");
+        auto lsofLines =
+            tokenizeString<std::vector<string>>(runProgram("lsof", true, { "-n", "-w", "-F", "n" }), "\n");
+        for (const auto & line : lsofLines) {
+            auto match = std::smatch{};
+            if (std::regex_match(line, match, lsofRegex))
+                paths.emplace(match[1]);
+        }
+    } catch (ExecError & e) {
+        /* lsof not installed, lsof failed */
+    }
 
-    StringSet paths = tokenizeString<StringSet>(result, "\n");
+    readFileRoots("/proc/sys/kernel/modprobe", paths);
+    readFileRoots("/proc/sys/kernel/fbsplash", paths);
+    readFileRoots("/proc/sys/kernel/poweroff_cmd", paths);
 
     for (auto & i : paths)
         if (isInStore(i)) {