about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--doc/manual/release-notes.xml3
-rw-r--r--nix.conf.example38
-rw-r--r--src/libstore/build.cc48
-rw-r--r--src/libutil/util.cc8
-rw-r--r--src/libutil/util.hh2
5 files changed, 80 insertions, 19 deletions
diff --git a/doc/manual/release-notes.xml b/doc/manual/release-notes.xml
index b19d9a38231f..c702fb45aaf6 100644
--- a/doc/manual/release-notes.xml
+++ b/doc/manual/release-notes.xml
@@ -116,6 +116,9 @@
   limited form of caching.  This is used by
   <command>nix-channel</command> to prevent unnecessary downloads when
   the channel hasn’t changed.</para></listitem>
+
+
+  <listitem><para>TODO: chroot support.</para></listitem>
   
 
 </itemizedlist>
diff --git a/nix.conf.example b/nix.conf.example
index be6a955a8d5f..99a94bfb9173 100644
--- a/nix.conf.example
+++ b/nix.conf.example
@@ -135,6 +135,44 @@
 #build-users-group =
 
 
+### Option `build-use-chroot'
+#
+# If set to `true', builds will be performed in a chroot environment,
+# i.e., the build will be isolated from the normal file system
+# hierarchy and will only see the Nix store, the temporary build
+# directory, and the directories configured with the
+# `build-chroot-dirs' option (such as /proc and /dev).  This is useful
+# to prevent undeclared dependencies on files in directories such as
+# /usr/bin.
+#
+# The use of a chroot requires that Nix is run as root (but you can
+# still use the "build users" feature to perform builds under
+# different users than root).  Currently, chroot builds only work on
+# Linux because Nix uses "bind mounts" to make the Nix store and other
+# directories available inside the chroot.
+#
+# The default is `false'.
+#
+# Example:
+#   build-use-chroot = true
+#build-use-chroot = false
+
+
+### Option `build-chroot-dirs'
+#
+# When builds are performed in a chroot environment, Nix will mount
+# (using `mount --bind' on Linux) some directories from the normal
+# file system hierarchy inside the chroot.  These are the Nix store,
+# the temporary build directory (usually /tmp/nix-<pid>-<number>) and
+# the directories listed here.  The default is "/dev /proc".  Files
+# in /dev (such as /dev/null) are needed by many builds, and some
+# files in /proc may also be needed occasionally.
+#
+# Example:
+#   build-use-chroot = /dev /proc /bin
+#build-chroot-dirs = /dev /proc
+
+
 ### Option `system'
 #
 # This option specifies the canonical Nix system name of the current
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 3c988ea42b95..6e2b748618b7 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -586,7 +586,8 @@ void deletePathWrapped(const Path & path)
 #include <sys/mount.h>
 
 
-/* Helper class for automatically unmounting bind-mounts in chroots. */
+/* Helper RAII class for automatically unmounting bind-mounts in
+   chroots. */
 struct BindMount
 {
     Path source, target;
@@ -612,7 +613,7 @@ struct BindMount
     
     void bind(const Path & source, const Path & target)
     {
-        printMsg(lvlError, format("bind mounting `%1%' to `%2%'") % source % target);
+        debug(format("bind mounting `%1%' to `%2%'") % source % target);
 
         this->source = source;
         this->target = target;
@@ -626,7 +627,8 @@ struct BindMount
     void unbind()
     {
         if (source == "") return;
-        printMsg(lvlError, format("umount `%1%'") % target);
+        
+        debug(format("unmount bind-mount `%1%'") % target);
 
         /* Urgh.  Unmount sometimes doesn't succeed right away because
            the mount point is still busy.  It shouldn't be, because
@@ -644,16 +646,18 @@ struct BindMount
                 sleep(1);
             }
             else
-                throw SysError(format("unmounting `%1%' failed") % target);
+                throw SysError(format("unmounting bind-mount `%1%' failed") % target);
         }
 
         /* Get rid of the directories for the mount point created in
            bind(). */
         for (Paths::reverse_iterator i = created.rbegin(); i != created.rend(); ++i) {
-            printMsg(lvlError, format("delete `%1%'") % *i);
+            debug(format("deleting `%1%'") % *i);
             if (remove(i->c_str()) == -1)
                 throw SysError(format("cannot unlink `%1%'") % *i);
         }
+
+        source = "";
     }
 };
 
@@ -704,11 +708,14 @@ private:
     /* Whether we're currently doing a chroot build. */
     bool useChroot;
 
+    /* A RAII object to delete the chroot directory. */
+    boost::shared_ptr<AutoDelete> autoDelChroot;
+    
     /* In chroot builds, the list of bind mounts currently active.
        The destructor of BindMount will cause the binds to be
        unmounted. */
     list<boost::shared_ptr<BindMount> > bindMounts;
-    
+
     typedef void (DerivationGoal::*GoalState)();
     GoalState state;
     
@@ -797,18 +804,11 @@ DerivationGoal::~DerivationGoal()
     /* Careful: we should never ever throw an exception from a
        destructor. */
     try {
-        printMsg(lvlError, "DESTROY");
         killChild();
         deleteTmpDir(false);
     } catch (...) {
         ignoreException();
     }
-    try {
-        //sleep(1);
-        bindMounts.clear();
-    } catch (...) {
-        ignoreException();
-    }
 }
 
 
@@ -1646,7 +1646,27 @@ void DerivationGoal::startBuilder()
     Path tmpRootDir;
     
     if (useChroot) {
-        tmpRootDir = createTempDir();
+        /* Create a temporary directory in which we set up the chroot
+           environment using bind-mounts.
+
+           !!! Big danger here: since we're doing this in /tmp, there
+           is a risk that the admin does something like "rm -rf
+           /tmp/chroot-nix-*" to clean up aborted builds, and if some
+           of the bind-mounts are still active, then "rm -rf" will
+           happily recurse into those mount points (thereby deleting,
+           say, /nix/store).  Ideally, tmpRootDir should be created in
+           some special location (maybe in /nix/var/nix) where Nix
+           takes care of unmounting / deleting old chroots
+           automatically. */
+        tmpRootDir = createTempDir("", "chroot-nix");
+
+        /* Clean up the chroot directory automatically, but don't
+           recurse; that would be very very bad if the unmount of a
+           bind-mount fails. Instead BindMount::unbind() unmounts and
+           deletes exactly those directories that it created to
+           produce the mount point, so that after all the BindMount
+           destructors have run, tmpRootDir should be empty. */
+        autoDelChroot = boost::shared_ptr<AutoDelete>(new AutoDelete(tmpRootDir, false));
         
         printMsg(lvlChatty, format("setting up chroot environment in `%1%'") % tmpRootDir);
 
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 428b1ff9a755..ed095717e262 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -318,19 +318,19 @@ void makePathReadOnly(const Path & path)
 }
 
 
-static Path tempName(const Path & tmpRoot)
+static Path tempName(const Path & tmpRoot, const Path & prefix)
 {
     static int counter = 0;
     Path tmpRoot2 = canonPath(tmpRoot.empty() ? getEnv("TMPDIR", "/tmp") : tmpRoot, true);
-    return (format("%1%/nix-%2%-%3%") % tmpRoot2 % getpid() % counter++).str();
+    return (format("%1%/%2%-%3%-%4%") % tmpRoot2 % prefix % getpid() % counter++).str();
 }
 
 
-Path createTempDir(const Path & tmpRoot)
+Path createTempDir(const Path & tmpRoot, const Path & prefix)
 {
     while (1) {
         checkInterrupt();
-	Path tmpDir = tempName(tmpRoot);
+	Path tmpDir = tempName(tmpRoot, prefix);
 	if (mkdir(tmpDir.c_str(), 0777) == 0) {
 	    /* Explicitly set the group of the directory.  This is to
 	       work around around problems caused by BSD's group
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 0ed98118c53b..657f45ced6e8 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -70,7 +70,7 @@ void deletePath(const Path & path, unsigned long long & bytesFreed);
 void makePathReadOnly(const Path & path);
 
 /* Create a temporary directory. */
-Path createTempDir(const Path & tmpRoot = "");
+Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix");
 
 /* Create a directory and all its parents, if necessary.  Returns the
    list of created directories, in order of creation. */