about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/download-via-ssh/download-via-ssh.cc1
-rw-r--r--src/libexpr/eval.cc3
-rw-r--r--src/libexpr/eval.hh10
-rw-r--r--src/libexpr/lexer.l2
-rw-r--r--src/libexpr/nixexpr.cc2
-rw-r--r--src/libexpr/parser.y16
-rw-r--r--src/libexpr/primops.cc8
-rw-r--r--src/libmain/shared.cc10
-rw-r--r--src/libstore/build.cc361
-rw-r--r--src/libstore/local-store.cc25
-rw-r--r--src/libstore/optimise-store.cc1
-rw-r--r--src/libstore/remote-store.cc1
-rw-r--r--src/libutil/hash.cc99
-rw-r--r--src/libutil/monitor-fd.hh1
-rw-r--r--src/libutil/util.cc90
-rw-r--r--src/libutil/util.hh7
-rw-r--r--src/nix-env/nix-env.cc4
-rw-r--r--src/nix-store/local.mk2
-rw-r--r--src/nix-store/nix-store.cc38
19 files changed, 491 insertions, 190 deletions
diff --git a/src/download-via-ssh/download-via-ssh.cc b/src/download-via-ssh/download-via-ssh.cc
index b64455eb1724..f71cf56507b8 100644
--- a/src/download-via-ssh/download-via-ssh.cc
+++ b/src/download-via-ssh/download-via-ssh.cc
@@ -9,6 +9,7 @@
 #include "store-api.hh"
 
 #include <iostream>
+#include <cstdlib>
 #include <unistd.h>
 
 using namespace nix;
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 298f6a3a60e3..95b56e84d89a 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -1174,7 +1174,8 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
     else if (firstType == tPath) {
         if (!context.empty())
             throwEvalError("a string that refers to a store path cannot be appended to a path, at %1%", pos);
-        mkPath(v, s.str().c_str());
+        auto path = canonPath(s.str());
+        mkPath(v, path.c_str());
     } else
         mkString(v, s.str(), context);
 }
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index 78942927fd24..f7415fb78dfd 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -39,10 +39,10 @@ public:
     typedef uint32_t size_t;
 
 private:
-    size_t size_, capacity;
+    size_t size_, capacity_;
     Attr attrs[0];
 
-    Bindings(uint32_t capacity) : size_(0), capacity(capacity) { }
+    Bindings(size_t capacity) : size_(0), capacity_(capacity) { }
     Bindings(const Bindings & bindings) = delete;
 
 public:
@@ -54,7 +54,7 @@ public:
 
     void push_back(const Attr & attr)
     {
-        assert(size_ < capacity);
+        assert(size_ < capacity_);
         attrs[size_++] = attr;
     }
 
@@ -76,6 +76,8 @@ public:
 
     void sort();
 
+    size_t capacity() { return capacity_; }
+
     friend class EvalState;
 };
 
@@ -169,7 +171,7 @@ public:
 
     /* Look up a file in the search path. */
     Path findFile(const string & path);
-    Path findFile(SearchPath & searchPath, const string & path);
+    Path findFile(SearchPath & searchPath, const string & path, const Pos & pos = noPos);
 
     /* Evaluate an expression to normal form, storing the result in
        value `v'. */
diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l
index 82520ee7a59a..7051909008d1 100644
--- a/src/libexpr/lexer.l
+++ b/src/libexpr/lexer.l
@@ -80,6 +80,7 @@ static Expr * unescapeStr(SymbolTable & symbols, const char * s)
 ID          [a-zA-Z\_][a-zA-Z0-9\_\'\-]*
 INT         [0-9]+
 PATH        [a-zA-Z0-9\.\_\-\+]*(\/[a-zA-Z0-9\.\_\-\+]+)+
+HPATH       \~(\/[a-zA-Z0-9\.\_\-\+]+)+
 SPATH       \<[a-zA-Z0-9\.\_\-\+]+(\/[a-zA-Z0-9\.\_\-\+]+)*\>
 URI         [a-zA-Z][a-zA-Z0-9\+\-\.]*\:[a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\']+
 
@@ -159,6 +160,7 @@ or          { return OR_KW; }
 <IND_STRING>.    return yytext[0]; /* just in case: shouldn't be reached */
 
 {PATH}      { yylval->path = strdup(yytext); return PATH; }
+{HPATH}     { yylval->path = strdup(yytext); return HPATH; }
 {SPATH}     { yylval->path = strdup(yytext); return SPATH; }
 {URI}       { yylval->uri = strdup(yytext); return URI; }
 
diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc
index 6945e4daed80..50997e096fd1 100644
--- a/src/libexpr/nixexpr.cc
+++ b/src/libexpr/nixexpr.cc
@@ -191,7 +191,7 @@ std::ostream & operator << (std::ostream & str, const Pos & pos)
     if (!pos)
         str << "undefined position";
     else
-        str << (format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%") % pos.file % pos.line % pos.column).str();
+        str << (format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%") % (string) pos.file % pos.line % pos.column).str();
     return str;
 }
 
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index dcb270b862a3..d70d29be8ba7 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -53,10 +53,6 @@ namespace nix {
 #include "parser-tab.hh"
 #include "lexer-tab.hh"
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
 YY_DECL;
 
 using namespace nix;
@@ -272,7 +268,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
 %token <id> ID ATTRPATH
 %token <e> STR IND_STR
 %token <n> INT
-%token <path> PATH SPATH
+%token <path> PATH HPATH SPATH
 %token <uri> URI
 %token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL OR_KW
 %token DOLLAR_CURLY /* == ${ */
@@ -290,7 +286,6 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
 %left '*' '/'
 %right CONCAT
 %nonassoc '?'
-%nonassoc '~'
 %nonassoc NEGATE
 
 %%
@@ -380,6 +375,7 @@ expr_simple
       $$ = stripIndentation(CUR_POS, data->symbols, *$2);
   }
   | PATH { $$ = new ExprPath(absPath($1, data->basePath)); }
+  | HPATH { $$ = new ExprPath(getEnv("HOME", "") + string{$1 + 1}); }
   | SPATH {
       string path($1 + 1, strlen($1) - 2);
       $$ = new ExprApp(CUR_POS,
@@ -630,7 +626,7 @@ Path EvalState::findFile(const string & path)
 }
 
 
-Path EvalState::findFile(SearchPath & searchPath, const string & path)
+Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos & pos)
 {
     foreach (SearchPath::iterator, i, searchPath) {
         Path res;
@@ -645,7 +641,11 @@ Path EvalState::findFile(SearchPath & searchPath, const string & path)
         }
         if (pathExists(res)) return canonPath(res);
     }
-    throw ThrownError(format("file ‘%1%’ was not found in the Nix search path (add it using $NIX_PATH or -I)") % path);
+    format f = format(
+        "file ‘%1%’ was not found in the Nix search path (add it using $NIX_PATH or -I)"
+        + string(pos ? ", at %2%" : ""));
+    f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
+    throw ThrownError(f % path % pos);
 }
 
 
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index cd7b287e29c3..a4efd397ec7a 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -187,7 +187,7 @@ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Valu
         case tPrimOpApp:
             t = "lambda";
             break;
-	case tExternal:
+        case tExternal:
             t = args[0]->external->typeOf();
             break;
         default: abort();
@@ -710,7 +710,7 @@ static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args,
 static void prim_baseNameOf(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
     PathSet context;
-    mkString(v, baseNameOf(state.coerceToString(pos, *args[0], context)), context);
+    mkString(v, baseNameOf(state.coerceToString(pos, *args[0], context, false, false)), context);
 }
 
 
@@ -775,7 +775,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
             % path % e.path % pos);
     }
 
-    mkPath(v, state.findFile(searchPath, path).c_str());
+    mkPath(v, state.findFile(searchPath, path, pos).c_str());
 }
 
 /* Read a directory (without . or ..) */
@@ -796,7 +796,7 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val
     for (auto & ent : entries) {
         Value * ent_val = state.allocAttr(v, state.symbols.create(ent.name));
         if (ent.type == DT_UNKNOWN)
-            ent.type = getFileType(path);
+            ent.type = getFileType(path + "/" + ent.name);
         mkStringNoCopy(*ent_val,
             ent.type == DT_REG ? "regular" :
             ent.type == DT_DIR ? "directory" :
diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc
index 7b50bfa9bb32..c83e997b2307 100644
--- a/src/libmain/shared.cc
+++ b/src/libmain/shared.cc
@@ -11,6 +11,7 @@
 #include <exception>
 #include <algorithm>
 
+#include <cstdlib>
 #include <sys/time.h>
 #include <sys/stat.h>
 #include <unistd.h>
@@ -292,8 +293,9 @@ int handleExceptions(const string & programName, std::function<void()> fun)
 RunPager::RunPager()
 {
     if (!isatty(STDOUT_FILENO)) return;
-    string pager = getEnv("PAGER", "default");
-    if (pager == "" || pager == "cat") return;
+    char * pager = getenv("NIX_PAGER");
+    if (!pager) pager = getenv("PAGER");
+    if (pager && ((string) pager == "" || (string) pager == "cat")) return;
 
     /* Ignore SIGINT. The pager will handle it (and we'll get
        SIGPIPE). */
@@ -313,8 +315,8 @@ RunPager::RunPager()
             throw SysError("dupping stdin");
         if (!getenv("LESS"))
             setenv("LESS", "FRSXMK", 1);
-        if (pager != "default")
-            execl("/bin/sh", "sh", "-c", pager.c_str(), NULL);
+        if (pager)
+            execl("/bin/sh", "sh", "-c", pager, NULL);
         execlp("pager", "pager", NULL);
         execlp("less", "less", NULL);
         execlp("more", "more", NULL);
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 08f44b392b00..e64bd3fef587 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -38,6 +38,9 @@
 #if HAVE_SYS_MOUNT_H
 #include <sys/mount.h>
 #endif
+#if HAVE_SYS_SYSCALL_H
+#include <sys/syscall.h>
+#endif
 #if HAVE_SCHED_H
 #include <sched.h>
 #endif
@@ -48,7 +51,16 @@
 #include <linux/fs.h>
 #endif
 
-#define CHROOT_ENABLED HAVE_CHROOT && HAVE_UNSHARE && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_PRIVATE) && defined(CLONE_NEWNS)
+#define CHROOT_ENABLED HAVE_CHROOT && HAVE_UNSHARE && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_PRIVATE) && defined(CLONE_NEWNS) && defined(SYS_pivot_root)
+
+/* chroot-like behavior from Apple's sandbox */
+#if __APPLE__
+    #define SANDBOX_ENABLED 1
+    #define DEFAULT_ALLOWED_IMPURE_PREFIXES "/System/Library/Frameworks /usr/lib /dev /bin/sh"
+#else
+    #define SANDBOX_ENABLED 0
+    #define DEFAULT_ALLOWED_IMPURE_PREFIXES "/bin" "/usr/bin"
+#endif
 
 #if CHROOT_ENABLED
 #include <sys/socket.h>
@@ -1586,6 +1598,13 @@ void chmod_(const Path & path, mode_t mode)
 }
 
 
+int childEntry(void * arg)
+{
+    ((DerivationGoal *) arg)->runChild();
+    return 1;
+}
+
+
 void DerivationGoal::startBuilder()
 {
     startNest(nest, lvlInfo, format(
@@ -1627,14 +1646,26 @@ void DerivationGoal::startBuilder()
     /* The maximum number of cores to utilize for parallel building. */
     env["NIX_BUILD_CORES"] = (format("%d") % settings.buildCores).str();
 
-    /* Add all bindings specified in the derivation. */
-    foreach (StringPairs::iterator, i, drv.env)
-        env[i->first] = i->second;
-
     /* Create a temporary directory where the build will take
        place. */
     tmpDir = createTempDir("", "nix-build-" + storePathToName(drvPath), false, false, 0700);
 
+    /* Add all bindings specified in the derivation via the
+       environments, except those listed in the passAsFile
+       attribute. Those are passed as file names pointing to
+       temporary files containing the contents. */
+    StringSet passAsFile = tokenizeString<StringSet>(get(drv.env, "passAsFile"));
+    int fileNr = 0;
+    for (auto & i : drv.env) {
+        if (passAsFile.find(i.first) == passAsFile.end()) {
+            env[i.first] = i.second;
+        } else {
+            Path p = tmpDir + "/.attr-" + int2String(fileNr++);
+            writeFile(p, i.second);
+            env[i.first + "Path"] = p;
+        }
+    }
+
     /* For convenience, set an environment pointing to the top build
        directory. */
     env["NIX_BUILD_TOP"] = tmpDir;
@@ -1729,21 +1760,6 @@ void DerivationGoal::startBuilder()
         /* Change ownership of the temporary build directory. */
         if (chown(tmpDir.c_str(), buildUser.getUID(), buildUser.getGID()) == -1)
             throw SysError(format("cannot change ownership of ‘%1%’") % tmpDir);
-
-        /* Check that the Nix store has the appropriate permissions,
-           i.e., owned by root and mode 1775 (sticky bit on so that
-           the builder can create its output but not mess with the
-           outputs of other processes). */
-        struct stat st;
-        if (stat(settings.nixStore.c_str(), &st) == -1)
-            throw SysError(format("cannot stat ‘%1%’") % settings.nixStore);
-        if (!(st.st_mode & S_ISVTX) ||
-            ((st.st_mode & S_IRWXG) != S_IRWXG) ||
-            (st.st_gid != buildUser.getGID()))
-            throw Error(format(
-                "builder does not have write permission to ‘%2%’; "
-                "try ‘chgrp %1% %2%; chmod 1775 %2%’")
-                % buildUser.getGID() % settings.nixStore);
     }
 
 
@@ -1760,6 +1776,47 @@ void DerivationGoal::startBuilder()
     if (get(drv.env, "__noChroot") == "1") useChroot = false;
 
     if (useChroot) {
+        /* Allow a user-configurable set of directories from the
+           host file system. */
+        PathSet dirs = tokenizeString<StringSet>(settings.get("build-chroot-dirs", string(DEFAULT_CHROOT_DIRS)));
+        PathSet dirs2 = tokenizeString<StringSet>(settings.get("build-extra-chroot-dirs", string("")));
+        dirs.insert(dirs2.begin(), dirs2.end());
+
+        for (auto & i : dirs) {
+            size_t p = i.find('=');
+            if (p == string::npos)
+                dirsInChroot[i] = i;
+            else
+                dirsInChroot[string(i, 0, p)] = string(i, p + 1);
+        }
+        dirsInChroot[tmpDir] = tmpDir;
+
+        string allowed = settings.get("allowed-impure-host-deps", string(DEFAULT_ALLOWED_IMPURE_PREFIXES));
+        PathSet allowedPaths = tokenizeString<StringSet>(allowed);
+
+        /* This works like the above, except on a per-derivation level */
+        Strings impurePaths = tokenizeString<Strings>(get(drv.env, "__impureHostDeps"));
+
+        for (auto & i : impurePaths) {
+            bool found = false;
+            /* Note: we're not resolving symlinks here to prevent
+               giving a non-root user info about inaccessible
+               files. */
+            Path canonI = canonPath(i);
+            /* If only we had a trie to do this more efficiently :) luckily, these are generally going to be pretty small */
+            for (auto & a : allowedPaths) {
+                Path canonA = canonPath(a);
+                if (canonI == canonA || isInDir(canonI, canonA)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found)
+                throw Error(format("derivation '%1%' requested impure path ‘%2%’, but it was not in allowed-impure-host-deps (‘%3%’)") % drvPath % i % allowed);
+
+            dirsInChroot[i] = i;
+        }
+
 #if CHROOT_ENABLED
         /* Create a temporary directory in which we set up the chroot
            environment using bind-mounts.  We put it in the Nix store
@@ -1801,20 +1858,6 @@ void DerivationGoal::startBuilder()
         /* Create /etc/hosts with localhost entry. */
         writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n");
 
-        /* Bind-mount a user-configurable set of directories from the
-           host file system. */
-        PathSet dirs = tokenizeString<StringSet>(settings.get("build-chroot-dirs", string(DEFAULT_CHROOT_DIRS)));
-        PathSet dirs2 = tokenizeString<StringSet>(settings.get("build-extra-chroot-dirs", string("")));
-        dirs.insert(dirs2.begin(), dirs2.end());
-        for (auto & i : dirs) {
-            size_t p = i.find('=');
-            if (p == string::npos)
-                dirsInChroot[i] = i;
-            else
-                dirsInChroot[string(i, 0, p)] = string(i, p + 1);
-        }
-        dirsInChroot[tmpDir] = tmpDir;
-
         /* Make the closure of the inputs available in the chroot,
            rather than the whole Nix store.  This prevents any access
            to undeclared dependencies.  Directories are bind-mounted,
@@ -1858,6 +1901,9 @@ void DerivationGoal::startBuilder()
             foreach (DerivationOutputs::iterator, i, drv.outputs)
                 dirsInChroot.erase(i->second.path);
 
+#elif SANDBOX_ENABLED
+        /* We don't really have any parent prep work to do (yet?)
+           All work happens in the child, instead. */
 #else
         throw Error("chroot builds are not supported on this platform");
 #endif
@@ -1902,11 +1948,66 @@ void DerivationGoal::startBuilder()
     builderOut.create();
 
     /* Fork a child to build the package. */
-    ProcessOptions options;
-    options.allowVfork = !buildUser.enabled();
-    pid = startProcess([&]() {
-        runChild();
-    }, options);
+#if CHROOT_ENABLED
+    if (useChroot) {
+        /* Set up private namespaces for the build:
+
+           - The PID namespace causes the build to start as PID 1.
+             Processes outside of the chroot are not visible to those
+             on the inside, but processes inside the chroot are
+             visible from the outside (though with different PIDs).
+
+           - The private mount namespace ensures that all the bind
+             mounts we do will only show up in this process and its
+             children, and will disappear automatically when we're
+             done.
+
+           - The private network namespace ensures that the builder
+             cannot talk to the outside world (or vice versa).  It
+             only has a private loopback interface.
+
+           - The IPC namespace prevents the builder from communicating
+             with outside processes using SysV IPC mechanisms (shared
+             memory, message queues, semaphores).  It also ensures
+             that all IPC objects are destroyed when the builder
+             exits.
+
+           - The UTS namespace ensures that builders see a hostname of
+             localhost rather than the actual hostname.
+
+           We use a helper process to do the clone() to work around
+           clone() being broken in multi-threaded programs due to
+           at-fork handlers not being run. Note that we use
+           CLONE_PARENT to ensure that the real builder is parented to
+           us.
+        */
+        Pid helper = startProcess([&]() {
+            char stack[32 * 1024];
+            pid_t child = clone(childEntry, stack + sizeof(stack) - 8,
+                CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD, this);
+            if (child == -1) {
+                if (errno == EINVAL)
+                    throw SysError("cloning builder process (Linux chroot builds require 3.13 or later)");
+                else
+                    throw SysError("cloning builder process");
+            }
+            writeFull(builderOut.writeSide, int2String(child) + "\n");
+            _exit(0);
+        });
+        if (helper.wait(true) != 0)
+            throw Error("unable to start build process");
+        pid_t tmp;
+        if (!string2Int<pid_t>(readLine(builderOut.readSide), tmp)) abort();
+        pid = tmp;
+    } else
+#endif
+    {
+        ProcessOptions options;
+        options.allowVfork = !buildUser.enabled();
+        pid = startProcess([&]() {
+            runChild();
+        }, options);
+    }
 
     /* parent */
     pid.setSeparatePG(true);
@@ -1922,7 +2023,6 @@ void DerivationGoal::startBuilder()
         printMsg(lvlError, format("@ build-started %1% - %2% %3%")
             % drvPath % drv.platform % logFile);
     }
-
 }
 
 
@@ -1938,30 +2038,6 @@ void DerivationGoal::runChild()
 #if CHROOT_ENABLED
         if (useChroot) {
 
-            /* Set up private namespaces for the build:
-
-               - The private mount namespace ensures that all the bind
-                 mounts we do will only show up in this process and
-                 its children, and will disappear automatically when
-                 we're done.
-
-               - The private network namespace ensures that the
-                 builder cannot talk to the outside world (or vice
-                 versa).  It only has a private loopback interface.
-
-               - The IPC namespace prevents the builder from
-                 communicating with outside processes using SysV IPC
-                 mechanisms (shared memory, message queues,
-                 semaphores).  It also ensures that all IPC objects
-                 are destroyed when the builder exits.
-
-               - The UTS namespace ensures that builders see a
-                 hostname of localhost rather than the actual
-                 hostname.
-            */
-            if (unshare(CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC | CLONE_NEWUTS) == -1)
-                throw SysError("setting up private namespaces");
-
             /* Initialise the loopback interface. */
             AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
             if (fd == -1) throw SysError("cannot open IP socket");
@@ -1998,6 +2074,11 @@ void DerivationGoal::runChild()
                     throw SysError(format("unable to make filesystem ‘%1%’ private") % fs);
             }
 
+            /* Bind-mount chroot directory to itself, to treat it as a
+               different filesystem from /, as needed for pivot_root. */
+            if (mount(chrootRootDir.c_str(), chrootRootDir.c_str(), 0, MS_BIND, 0) == -1)
+                throw SysError(format("unable to bind mount ‘%1%’") % chrootRootDir);
+
             /* Set up a nearly empty /dev, unless the user asked to
                bind-mount the host /dev. */
             if (dirsInChroot.find("/dev") == dirsInChroot.end()) {
@@ -2069,13 +2150,26 @@ void DerivationGoal::runChild()
                 chmod_(chrootRootDir + "/dev/pts/ptmx", 0666);
             }
 
-            /* Do the chroot().  Below we do a chdir() to the
-               temporary build directory to make sure the current
-               directory is in the chroot.  (Actually the order
-               doesn't matter, since due to the bind mount tmpDir and
-               tmpRootDit/tmpDir are the same directories.) */
-            if (chroot(chrootRootDir.c_str()) == -1)
+            /* Do the chroot(). */
+            if (chdir(chrootRootDir.c_str()) == -1)
+                throw SysError(format("cannot change directory to ‘%1%’") % chrootRootDir);
+
+            if (mkdir("real-root", 0) == -1)
+                throw SysError("cannot create real-root directory");
+
+#define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old))
+            if (pivot_root(".", "real-root") == -1)
+                throw SysError(format("cannot pivot old root directory onto ‘%1%’") % (chrootRootDir + "/real-root"));
+#undef pivot_root
+
+            if (chroot(".") == -1)
                 throw SysError(format("cannot change root directory to ‘%1%’") % chrootRootDir);
+
+            if (umount2("real-root", MNT_DETACH) == -1)
+                throw SysError("cannot unmount real root filesystem");
+
+            if (rmdir("real-root") == -1)
+                throw SysError("cannot remove real-root directory");
         }
 #endif
 
@@ -2139,8 +2233,123 @@ void DerivationGoal::runChild()
 
         /* Fill in the arguments. */
         Strings args;
-        string builderBasename = baseNameOf(drv.builder);
-        args.push_back(builderBasename);
+
+        const char *builder = "invalid";
+
+        string sandboxProfile;
+        if (useChroot && SANDBOX_ENABLED) {
+            /* Lots and lots and lots of file functions freak out if they can't stat their full ancestry */
+            PathSet ancestry;
+
+            /* We build the ancestry before adding all inputPaths to the store because we know they'll
+               all have the same parents (the store), and there might be lots of inputs. This isn't
+               particularly efficient... I doubt it'll be a bottleneck in practice */
+            for (auto & i : dirsInChroot) {
+                Path cur = i.first;
+                while (cur.compare("/") != 0) {
+                    cur = dirOf(cur);
+                    ancestry.insert(cur);
+                }
+            }
+
+            /* And we want the store in there regardless of how empty dirsInChroot. We include the innermost
+               path component this time, since it's typically /nix/store and we care about that. */
+            Path cur = settings.nixStore;
+            while (cur.compare("/") != 0) {
+                ancestry.insert(cur);
+                cur = dirOf(cur);
+            }
+
+            /* Add all our input paths to the chroot */
+            for (auto & i : inputPaths)
+                dirsInChroot[i] = i;
+
+
+            /* TODO: we should factor out the policy cleanly, so we don't have to repeat the constants every time... */
+            sandboxProfile += "(version 1)\n";
+
+            /* Violations will go to the syslog if you set this. Unfortunately the destination does not appear to be configurable */
+            if (settings.get("darwin-log-sandbox-violations", false)) {
+                sandboxProfile += "(deny default)\n";
+            } else {
+                sandboxProfile += "(deny default (with no-log))\n";
+            }
+
+            sandboxProfile += "(allow file-read* file-write-data (literal \"/dev/null\"))\n";
+
+            sandboxProfile += "(allow file-read-metadata\n"
+                "\t(literal \"/var\")\n"
+                "\t(literal \"/tmp\")\n"
+                "\t(literal \"/etc\")\n"
+                "\t(literal \"/etc/nix\")\n"
+                "\t(literal \"/etc/nix/nix.conf\"))\n";
+
+            /* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try different mechanisms
+               to find temporary directories, so we want to open up a broader place for them to dump their files, if needed. */
+            Path globalTmpDir = canonPath(getEnv("TMPDIR", "/tmp"), true);
+
+            /* They don't like trailing slashes on subpath directives */
+            if (globalTmpDir.back() == '/') globalTmpDir.pop_back();
+
+            /* This is where our temp folders are and where most of the building will happen, so we want rwx on it. */
+            sandboxProfile += (format("(allow file-read* file-write* process-exec (subpath \"%1%\") (subpath \"/private/tmp\"))\n") % globalTmpDir).str();
+
+            sandboxProfile += "(allow process-fork)\n";
+            sandboxProfile += "(allow sysctl-read)\n";
+            sandboxProfile += "(allow signal (target same-sandbox))\n";
+
+            /* Enables getpwuid (used by git and others) */
+            sandboxProfile += "(allow mach-lookup (global-name \"com.apple.system.notification_center\") (global-name \"com.apple.system.opendirectoryd.libinfo\"))\n";
+
+
+            /* Our rwx outputs */
+            sandboxProfile += "(allow file-read* file-write* process-exec\n";
+            for (auto & i : missingPaths) {
+                sandboxProfile += (format("\t(subpath \"%1%\")\n") % i.c_str()).str();
+            }
+            sandboxProfile += ")\n";
+
+            /* Our inputs (transitive dependencies and any impurities computed above)
+               Note that the sandbox profile allows file-write* even though it isn't seemingly necessary. First of all, nix's standard user permissioning
+               mechanism still prevents builders from writing to input directories, so no security/purity is lost. The reason we allow file-write* is that
+               denying it means the `access` syscall will return EPERM instead of EACCESS, which confuses a few programs that assume (understandably, since
+               it appears to be a violation of the POSIX spec) that `access` won't do that, and don't deal with it nicely if it does. The most notable of
+               these is the entire GHC Haskell ecosystem. */
+            sandboxProfile += "(allow file-read* file-write* process-exec\n";
+            for (auto & i : dirsInChroot) {
+                if (i.first != i.second)
+                    throw SysError(format("can't map '%1%' to '%2%': mismatched impure paths not supported on darwin"));
+
+                string path = i.first;
+                struct stat st;
+                if (lstat(path.c_str(), &st))
+                    throw SysError(format("getting attributes of path ‘%1%’") % path);
+                if (S_ISDIR(st.st_mode))
+                    sandboxProfile += (format("\t(subpath \"%1%\")\n") % path).str();
+                else
+                    sandboxProfile += (format("\t(literal \"%1%\")\n") % path).str();
+            }
+            sandboxProfile += ")\n";
+
+            /* Our ancestry. N.B: this uses literal on folders, instead of subpath. Without that,
+               you open up the entire filesystem because you end up with (subpath "/") */
+            sandboxProfile += "(allow file-read-metadata\n";
+            for (auto & i : ancestry) {
+                sandboxProfile += (format("\t(literal \"%1%\")\n") % i.c_str()).str();
+            }
+            sandboxProfile += ")\n";
+
+            builder = "/usr/bin/sandbox-exec";
+            args.push_back("sandbox-exec");
+            args.push_back("-p");
+            args.push_back(sandboxProfile);
+            args.push_back(drv.builder);
+        } else {
+            builder = drv.builder.c_str();
+            string builderBasename = baseNameOf(drv.builder);
+            args.push_back(builderBasename);
+        }
+
         foreach (Strings::iterator, i, drv.args)
             args.push_back(rewriteHashes(*i, rewritesToTmp));
         auto argArr = stringsToCharPtrs(args);
@@ -2150,8 +2359,14 @@ void DerivationGoal::runChild()
         /* Indicate that we managed to set up the build environment. */
         writeFull(STDERR_FILENO, "\n");
 
+        /* This needs to be after that fateful '\n', and I didn't want to duplicate code */
+        if (useChroot && SANDBOX_ENABLED) {
+            printMsg(lvlDebug, "Generated sandbox profile:");
+            printMsg(lvlDebug, sandboxProfile);
+        }
+
         /* Execute the program.  This should not return. */
-        execve(drv.builder.c_str(), (char * *) &argArr[0], (char * *) &envArr[0]);
+        execve(builder, (char * *) &argArr[0], (char * *) &envArr[0]);
 
         throw SysError(format("executing ‘%1%’") % drv.builder);
 
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index 3ad80bc4e6f4..bc792baf296b 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -256,20 +256,23 @@ LocalStore::LocalStore(bool reserveSpace)
         if (chmod(perUserDir.c_str(), 01777) == -1)
             throw SysError(format("could not set permissions on ‘%1%’ to 1777") % perUserDir);
 
+        mode_t perm = 01735;
+
         struct group * gr = getgrnam(settings.buildUsersGroup.c_str());
         if (!gr)
-            throw Error(format("the group ‘%1%’ specified in ‘build-users-group’ does not exist")
+            printMsg(lvlError, format("warning: the group ‘%1%’ specified in ‘build-users-group’ does not exist")
                 % settings.buildUsersGroup);
-
-        struct stat st;
-        if (stat(settings.nixStore.c_str(), &st))
-            throw SysError(format("getting attributes of path ‘%1%’") % settings.nixStore);
-
-        if (st.st_uid != 0 || st.st_gid != gr->gr_gid || (st.st_mode & ~S_IFMT) != 01775) {
-            if (chown(settings.nixStore.c_str(), 0, gr->gr_gid) == -1)
-                throw SysError(format("changing ownership of path ‘%1%’") % settings.nixStore);
-            if (chmod(settings.nixStore.c_str(), 01775) == -1)
-                throw SysError(format("changing permissions on path ‘%1%’") % settings.nixStore);
+        else {
+            struct stat st;
+            if (stat(settings.nixStore.c_str(), &st))
+                throw SysError(format("getting attributes of path ‘%1%’") % settings.nixStore);
+
+            if (st.st_uid != 0 || st.st_gid != gr->gr_gid || (st.st_mode & ~S_IFMT) != perm) {
+                if (chown(settings.nixStore.c_str(), 0, gr->gr_gid) == -1)
+                    throw SysError(format("changing ownership of path ‘%1%’") % settings.nixStore);
+                if (chmod(settings.nixStore.c_str(), perm) == -1)
+                    throw SysError(format("changing permissions on path ‘%1%’") % settings.nixStore);
+            }
         }
     }
 
diff --git a/src/libstore/optimise-store.cc b/src/libstore/optimise-store.cc
index dd18d66fa008..55c252b9b2e3 100644
--- a/src/libstore/optimise-store.cc
+++ b/src/libstore/optimise-store.cc
@@ -4,6 +4,7 @@
 #include "local-store.hh"
 #include "globals.hh"
 
+#include <cstdlib>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index acc895409eba..d08913246321 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -10,6 +10,7 @@
 #include <sys/stat.h>
 #include <sys/socket.h>
 #include <sys/un.h>
+#include <errno.h>
 #include <fcntl.h>
 
 #include <iostream>
diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc
index 965f3ed47701..a83ba0a81817 100644
--- a/src/libutil/hash.cc
+++ b/src/libutil/hash.cc
@@ -84,7 +84,7 @@ string printHash(const Hash & hash)
     return string(buf, hash.hashSize * 2);
 }
 
-    
+
 Hash parseHash(HashType ht, const string & s)
 {
     Hash hash(ht);
@@ -92,7 +92,7 @@ Hash parseHash(HashType ht, const string & s)
         throw Error(format("invalid hash ‘%1%’") % s);
     for (unsigned int i = 0; i < hash.hashSize; i++) {
         string s2(s, i * 2, 2);
-        if (!isxdigit(s2[0]) || !isxdigit(s2[1])) 
+        if (!isxdigit(s2[0]) || !isxdigit(s2[1]))
             throw Error(format("invalid hash ‘%1%’") % s);
         std::istringstream str(s2);
         int n;
@@ -103,24 +103,6 @@ Hash parseHash(HashType ht, const string & s)
 }
 
 
-static unsigned char divMod(unsigned char * bytes, unsigned char y)
-{
-    unsigned int borrow = 0;
-
-    int pos = Hash::maxHashSize - 1;
-    while (pos >= 0 && !bytes[pos]) --pos;
-
-    for ( ; pos >= 0; --pos) {
-        unsigned int s = bytes[pos] + (borrow << 8);
-        unsigned int d = s / y;
-        borrow = s % y;
-        bytes[pos] = d;
-    }
-
-    return borrow;
-}
-
-
 unsigned int hashLength32(const Hash & hash)
 {
     return (hash.hashSize * 8 - 1) / 5 + 1;
@@ -136,19 +118,19 @@ string printHash32(const Hash & hash)
     Hash hash2(hash);
     unsigned int len = hashLength32(hash);
 
-    const char * chars = base32Chars.data();
-    
-    string s(len, '0');
-
-    int pos = len - 1;
-    while (pos >= 0) {
-        unsigned char digit = divMod(hash2.hash, 32);
-        s[pos--] = chars[digit];
+    string s;
+    s.reserve(len);
+
+    for (int n = len - 1; n >= 0; n--) {
+        unsigned int b = n * 5;
+        unsigned int i = b / 8;
+        unsigned int j = b % 8;
+        unsigned char c =
+            (hash.hash[i] >> j)
+            | (i >= hash.hashSize - 1 ? 0 : hash.hash[i + 1] << (8 - j));
+        s.push_back(base32Chars[c & 0x1f]);
     }
 
-    for (unsigned int i = 0; i < hash2.maxHashSize; ++i)
-        assert(hash2.hash[i] == 0);
-
     return s;
 }
 
@@ -159,51 +141,24 @@ string printHash16or32(const Hash & hash)
 }
 
 
-static bool mul(unsigned char * bytes, unsigned char y, int maxSize)
-{
-    unsigned char carry = 0;
-
-    for (int pos = 0; pos < maxSize; ++pos) {
-        unsigned int m = bytes[pos] * y + carry;
-        bytes[pos] = m & 0xff;
-        carry = m >> 8;
-    }
-
-    return carry;
-}
-
-
-static bool add(unsigned char * bytes, unsigned char y, int maxSize)
-{
-    unsigned char carry = y;
-
-    for (int pos = 0; pos < maxSize; ++pos) {
-        unsigned int m = bytes[pos] + carry;
-        bytes[pos] = m & 0xff;
-        carry = m >> 8;
-        if (carry == 0) break;
-    }
-
-    return carry;
-}
-
-
 Hash parseHash32(HashType ht, const string & s)
 {
     Hash hash(ht);
+    unsigned int len = hashLength32(ht);
+    assert(s.size() == len);
 
-    const char * chars = base32Chars.data();
-
-    for (unsigned int i = 0; i < s.length(); ++i) {
-        char c = s[i];
+    for (unsigned int n = 0; n < len; ++n) {
+        char c = s[len - n - 1];
         unsigned char digit;
         for (digit = 0; digit < base32Chars.size(); ++digit) /* !!! slow */
-            if (chars[digit] == c) break;
+            if (base32Chars[digit] == c) break;
         if (digit >= 32)
             throw Error(format("invalid base-32 hash ‘%1%’") % s);
-        if (mul(hash.hash, 32, hash.hashSize) ||
-            add(hash.hash, digit, hash.hashSize))
-            throw Error(format("base-32 hash ‘%1%’ is too large") % s);
+        unsigned int b = n * 5;
+        unsigned int i = b / 8;
+        unsigned int j = b % 8;
+        hash.hash[i] |= digit << j;
+        if (i < hash.hashSize - 1) hash.hash[i + 1] |= digit >> (8 - j);
     }
 
     return hash;
@@ -299,7 +254,7 @@ Hash hashFile(HashType ht, const Path & path)
         if (n == -1) throw SysError(format("reading file ‘%1%’") % path);
         update(ht, ctx, buf, n);
     }
-    
+
     finish(ht, ctx, hash.hash);
     return hash;
 }
@@ -311,7 +266,7 @@ HashSink::HashSink(HashType ht) : ht(ht)
     bytes = 0;
     start(ht, *ctx);
 }
-    
+
 HashSink::~HashSink()
 {
     bufPos = 0;
@@ -369,7 +324,7 @@ HashType parseHashType(const string & s)
     else return htUnknown;
 }
 
- 
+
 string printHashType(HashType ht)
 {
     if (ht == htMD5) return "md5";
@@ -378,5 +333,5 @@ string printHashType(HashType ht)
     else throw Error("cannot print unknown hash type");
 }
 
- 
+
 }
diff --git a/src/libutil/monitor-fd.hh b/src/libutil/monitor-fd.hh
index 72d23fb6934c..6f01ccd91a43 100644
--- a/src/libutil/monitor-fd.hh
+++ b/src/libutil/monitor-fd.hh
@@ -3,6 +3,7 @@
 #include <thread>
 #include <atomic>
 
+#include <cstdlib>
 #include <poll.h>
 #include <sys/types.h>
 #include <unistd.h>
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index dcdb438e03b2..be0a9bf317d1 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -193,8 +193,12 @@ Path readLink(const Path & path)
     if (!S_ISLNK(st.st_mode))
         throw Error(format("‘%1%’ is not a symlink") % path);
     char buf[st.st_size];
-    if (readlink(path.c_str(), buf, st.st_size) != st.st_size)
+    ssize_t rlsize = readlink(path.c_str(), buf, st.st_size);
+    if (rlsize == -1)
         throw SysError(format("reading symbolic link ‘%1%’") % path);
+    else if (rlsize > st.st_size)
+        throw Error(format("symbolic link ‘%1%’ size overflow %2% > %3%")
+            % path % rlsize % st.st_size);
     return string(buf, st.st_size);
 }
 
@@ -921,18 +925,24 @@ std::vector<const char *> stringsToCharPtrs(const Strings & ss)
 }
 
 
-string runProgram(Path program, bool searchPath, const Strings & args)
+string runProgram(Path program, bool searchPath, const Strings & args,
+    const string & input)
 {
     checkInterrupt();
 
     /* Create a pipe. */
-    Pipe pipe;
-    pipe.create();
+    Pipe stdout, stdin;
+    stdout.create();
+    if (!input.empty()) stdin.create();
 
     /* Fork. */
     Pid pid = startProcess([&]() {
-        if (dup2(pipe.writeSide, STDOUT_FILENO) == -1)
+        if (dup2(stdout.writeSide, STDOUT_FILENO) == -1)
             throw SysError("dupping stdout");
+        if (!input.empty()) {
+            if (dup2(stdin.readSide, STDIN_FILENO) == -1)
+                throw SysError("dupping stdin");
+        }
 
         Strings args_(args);
         args_.push_front(program);
@@ -946,9 +956,16 @@ string runProgram(Path program, bool searchPath, const Strings & args)
         throw SysError(format("executing ‘%1%’") % program);
     });
 
-    pipe.writeSide.close();
+    stdout.writeSide.close();
+
+    /* FIXME: This can deadlock if the input is too long. */
+    if (!input.empty()) {
+        stdin.readSide.close();
+        writeFull(stdin.writeSide, input);
+        stdin.writeSide.close();
+    }
 
-    string result = drainFD(pipe.readSide);
+    string result = drainFD(stdout.readSide);
 
     /* Wait for the child to finish. */
     int status = pid.wait(true);
@@ -1191,4 +1208,63 @@ string filterANSIEscapes(const string & s, bool nixOnly)
 }
 
 
+static char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+
+string base64Encode(const string & s)
+{
+    string res;
+    int data = 0, nbits = 0;
+
+    for (char c : s) {
+        data = data << 8 | (unsigned char) c;
+        nbits += 8;
+        while (nbits >= 6) {
+            nbits -= 6;
+            res.push_back(base64Chars[data >> nbits & 0x3f]);
+        }
+    }
+
+    if (nbits) res.push_back(base64Chars[data << (6 - nbits) & 0x3f]);
+    while (res.size() % 4) res.push_back('=');
+
+    return res;
+}
+
+
+string base64Decode(const string & s)
+{
+    bool init = false;
+    char decode[256];
+    if (!init) {
+        // FIXME: not thread-safe.
+        memset(decode, -1, sizeof(decode));
+        for (int i = 0; i < 64; i++)
+            decode[(int) base64Chars[i]] = i;
+        init = true;
+    }
+
+    string res;
+    unsigned int d = 0, bits = 0;
+
+    for (char c : s) {
+        if (c == '=') break;
+        if (c == '\n') continue;
+
+        char digit = decode[(unsigned char) c];
+        if (digit == -1)
+            throw Error("invalid character in Base64 string");
+
+        bits += 6;
+        d = d << 6 | digit;
+        if (bits >= 8) {
+            res.push_back(d >> (bits - 8) & 0xff);
+            bits -= 8;
+        }
+    }
+
+    return res;
+}
+
+
 }
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 186ee71f45d0..20330fb7699e 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -285,7 +285,7 @@ pid_t startProcess(std::function<void()> fun, const ProcessOptions & options = P
 /* Run a program and return its stdout in a string (i.e., like the
    shell backtick operator). */
 string runProgram(Path program, bool searchPath = false,
-    const Strings & args = Strings());
+    const Strings & args = Strings(), const string & input = "");
 
 MakeError(ExecError, Error)
 
@@ -398,4 +398,9 @@ void ignoreException();
 string filterANSIEscapes(const string & s, bool nixOnly = false);
 
 
+/* Base64 encoding/decoding. */
+string base64Encode(const string & s);
+string base64Decode(const string & s);
+
+
 }
diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc
index f5e8ee08c42f..f3c8d3ba8953 100644
--- a/src/nix-env/nix-env.cc
+++ b/src/nix-env/nix-env.cc
@@ -132,6 +132,8 @@ static void getAllExprs(EvalState & state,
             Value & vArg(*state.allocValue());
             state.getBuiltin("import", vFun);
             mkString(vArg, path2);
+            if (v.attrs->size() == v.attrs->capacity())
+                throw Error(format("too many Nix expressions in directory ‘%1%’") % path);
             mkApp(*state.allocAttr(v, state.symbols.create(attrName)), vFun, vArg);
         }
         else if (S_ISDIR(st.st_mode))
@@ -160,7 +162,7 @@ static void loadSourceExpr(EvalState & state, const Path & path, Value & v)
        ~/.nix-defexpr directory that includes some system-wide
        directory). */
     if (S_ISDIR(st.st_mode)) {
-        state.mkAttrs(v, 16);
+        state.mkAttrs(v, 1024);
         state.mkList(*state.allocAttr(v, state.symbols.create("_combineChannels")), 0);
         StringSet attrs;
         getAllExprs(state, path, attrs, v);
diff --git a/src/nix-store/local.mk b/src/nix-store/local.mk
index b887fe03389b..84ff15b241f3 100644
--- a/src/nix-store/local.mk
+++ b/src/nix-store/local.mk
@@ -6,6 +6,6 @@ nix-store_SOURCES := $(wildcard $(d)/*.cc)
 
 nix-store_LIBS = libmain libstore libutil libformat
 
-nix-store_LDFLAGS = -lbz2 -pthread
+nix-store_LDFLAGS = -lbz2 -pthread $(SODIUM_LIBS)
 
 nix-store_CXXFLAGS = -DCURL=\"$(curl)\"
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index 87bc8c379de5..7ce5f63c2d2f 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -20,6 +20,10 @@
 
 #include <bzlib.h>
 
+#if HAVE_SODIUM
+#include <sodium.h>
+#endif
+
 
 using namespace nix;
 using std::cin;
@@ -1006,6 +1010,34 @@ static void opServe(Strings opFlags, Strings opArgs)
 }
 
 
+static void opGenerateBinaryCacheKey(Strings opFlags, Strings opArgs)
+{
+    foreach (Strings::iterator, i, opFlags)
+        throw UsageError(format("unknown flag ‘%1%’") % *i);
+
+    if (opArgs.size() != 3) throw UsageError("three arguments expected");
+    auto i = opArgs.begin();
+    string keyName = *i++;
+    string secretKeyFile = *i++;
+    string publicKeyFile = *i++;
+
+#if HAVE_SODIUM
+    sodium_init();
+
+    unsigned char pk[crypto_sign_PUBLICKEYBYTES];
+    unsigned char sk[crypto_sign_SECRETKEYBYTES];
+    if (crypto_sign_keypair(pk, sk) != 0)
+        throw Error("key generation failed");
+
+    writeFile(publicKeyFile, keyName + ":" + base64Encode(string((char *) pk, crypto_sign_PUBLICKEYBYTES)));
+    umask(0077);
+    writeFile(secretKeyFile, keyName + ":" + base64Encode(string((char *) sk, crypto_sign_SECRETKEYBYTES)));
+#else
+    throw Error("Nix was not compiled with libsodium, required for signed binary cache support");
+#endif
+}
+
+
 /* Scan the arguments; find the operation, set global flags, put all
    other flags in a list, and put all other arguments in another
    list. */
@@ -1072,14 +1104,16 @@ int main(int argc, char * * argv)
                 op = opQueryFailedPaths;
             else if (*arg == "--clear-failed-paths")
                 op = opClearFailedPaths;
+            else if (*arg == "--serve")
+                op = opServe;
+            else if (*arg == "--generate-binary-cache-key")
+                op = opGenerateBinaryCacheKey;
             else if (*arg == "--add-root")
                 gcRoot = absPath(getArg(*arg, arg, end));
             else if (*arg == "--indirect")
                 indirectRoot = true;
             else if (*arg == "--no-output")
                 noOutput = true;
-            else if (*arg == "--serve")
-                op = opServe;
             else if (*arg != "" && arg->at(0) == '-') {
                 opFlags.push_back(*arg);
                 if (*arg == "--max-freed" || *arg == "--max-links" || *arg == "--max-atime") /* !!! hack */