about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2007-10-27T00·46+0000
committerEelco Dolstra <e.dolstra@tudelft.nl>2007-10-27T00·46+0000
commit9397cd30c8a6ffd65fc3b85985ea59ecfb72672b (patch)
treea3382c4031b84e518e6d8a133a98cdc84ba8e3d1 /src
parent0b4ed64d295316146fc4de8a5a2e971b771058b8 (diff)
* Support for doing builds in a chroot under Linux. The builder is
  executed in a chroot that contains just the Nix store, the temporary
  build directory, and a configurable set of additional directories
  (/dev and /proc by default).  This allows a bit more purity
  enforcement: hidden build-time dependencies on directories such as
  /usr or /nix/var/nix/profiles are no longer possible.  As an added
  benefit, accidental network downloads (cf. NIXPKGS-52) are prevented
  as well (because files such as /etc/resolv.conf are not available in
  the chroot).

  However the usefulness of chroots is diminished by the fact that
  many builders depend on /bin/sh, so you need /bin in the list of
  additional directories.  (And then on non-NixOS you need /lib as
  well...)

Diffstat (limited to 'src')
-rw-r--r--src/libstore/build.cc138
-rw-r--r--src/libutil/util.cc32
-rw-r--r--src/libutil/util.hh8
3 files changed, 162 insertions, 16 deletions
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 85933e84f2f2..3c988ea42b95 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -583,6 +583,84 @@ void deletePathWrapped(const Path & path)
 //////////////////////////////////////////////////////////////////////
 
 
+#include <sys/mount.h>
+
+
+/* Helper class for automatically unmounting bind-mounts in chroots. */
+struct BindMount
+{
+    Path source, target;
+    Paths created;
+
+    BindMount()
+    {
+    }
+
+    BindMount(const Path & source, const Path & target)
+    {
+        bind(source, target);
+    }
+
+    ~BindMount()
+    {
+        try {
+            unbind();
+        } catch (...) {
+            ignoreException();
+        }
+    }
+    
+    void bind(const Path & source, const Path & target)
+    {
+        printMsg(lvlError, format("bind mounting `%1%' to `%2%'") % source % target);
+
+        this->source = source;
+        this->target = target;
+        
+        created = createDirs(target);
+        
+        if (mount(source.c_str(), target.c_str(), "", MS_BIND, 0) == -1)
+            throw SysError(format("bind mount from `%1%' to `%2%' failed") % source % target);
+    }
+
+    void unbind()
+    {
+        if (source == "") return;
+        printMsg(lvlError, format("umount `%1%'") % target);
+
+        /* Urgh.  Unmount sometimes doesn't succeed right away because
+           the mount point is still busy.  It shouldn't be, because
+           we've killed all the build processes by now (at least when
+           using a build user; see the check in killUser()).  But
+           maybe this is because those processes are still zombies and
+           are keeping some kernel structures busy (open files,
+           current directories, etc.).  So retry a few times
+           (actually, a 1 second sleep is almost certainly enough for
+           the zombies to be cleaned up). */
+        unsigned int tries = 0;
+        while (umount(target.c_str()) == -1) {
+            if (errno == EBUSY && ++tries < 10) {
+                printMsg(lvlError, format("unmounting `%1%' failed, retrying after 1 second...") % target);
+                sleep(1);
+            }
+            else
+                throw SysError(format("unmounting `%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);
+            if (remove(i->c_str()) == -1)
+                throw SysError(format("cannot unlink `%1%'") % *i);
+        }
+    }
+};
+
+
+//////////////////////////////////////////////////////////////////////
+
+
 class DerivationGoal : public Goal
 {
 private:
@@ -623,6 +701,14 @@ private:
     Pipe toHook;
     Pipe fromHook;
 
+    /* Whether we're currently doing a chroot build. */
+    bool useChroot;
+
+    /* 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;
     
@@ -678,7 +764,7 @@ private:
     void openLogFile();
 
     /* Common initialisation to be performed in child processes (i.e.,
-       both in builders and in build hooks. */
+       both in builders and in build hooks). */
     void initChild();
     
     /* Delete the temporary directory, if we have one. */
@@ -711,11 +797,18 @@ 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();
+    }
 }
 
 
@@ -1024,6 +1117,9 @@ void DerivationGoal::buildDone()
     
         deleteTmpDir(true);
 
+        /* In chroot builds, unmount the bind mounts ASAP. */
+        bindMounts.clear(); /* the destructors will do the rest */
+
         /* Compute the FS closure of the outputs and register them as
            being valid. */
         computeClosure();
@@ -1173,7 +1269,7 @@ DerivationGoal::HookReply DerivationGoal::tryBuildHook()
             throw SysError(format("executing `%1%'") % buildHook);
             
         } catch (std::exception & e) {
-            std::cerr << format("build hook error: %1%\n") % e.what();
+            std::cerr << format("build hook error: %1%") % e.what() << std::endl;
         }
         quickExit(1);
     }
@@ -1544,6 +1640,31 @@ void DerivationGoal::startBuilder()
                 % buildUser.getGID() % nixStore);
     }
 
+
+    /* Are we doing a chroot build? */
+    useChroot = queryBoolSetting("build-use-chroot", false);
+    Path tmpRootDir;
+    
+    if (useChroot) {
+        tmpRootDir = createTempDir();
+        
+        printMsg(lvlChatty, format("setting up chroot environment in `%1%'") % tmpRootDir);
+
+        Paths defaultDirs;
+        defaultDirs.push_back("/dev");
+        defaultDirs.push_back("/proc");
+        Paths dirsInChroot = querySetting("build-chroot-dirs", defaultDirs);
+
+        dirsInChroot.push_front(nixStore);
+        dirsInChroot.push_front(tmpDir);
+
+        /* Push BindMounts at the front of the list so that they get
+           unmounted in LIFO order.  (!!! Does the C++ standard
+           guarantee that list elements are destroyed in order?) */
+        for (Paths::iterator i = dirsInChroot.begin(); i != dirsInChroot.end(); ++i)
+            bindMounts.push_front(boost::shared_ptr<BindMount>(new BindMount(*i, tmpRootDir + *i)));
+    }
+    
     
     /* Run the builder. */
     printMsg(lvlChatty, format("executing builder `%1%'") %
@@ -1569,6 +1690,15 @@ void DerivationGoal::startBuilder()
 
         try { /* child */
 
+            /* If building in a chroot, do the chroot right away.
+               initChild() will 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 (useChroot && chroot(tmpRootDir.c_str()) == -1)
+                throw SysError(format("cannot change root directory to `%1%'") % tmpRootDir);
+            
             initChild();
 
             /* Fill in the environment. */
@@ -1632,7 +1762,7 @@ void DerivationGoal::startBuilder()
                 % drv.builder);
             
         } catch (std::exception & e) {
-            std::cerr << format("build error: %1%\n") % e.what();
+            std::cerr << format("build error: %1%") % e.what() << std::endl;
         }
         quickExit(1);
     }
@@ -2143,7 +2273,7 @@ void SubstitutionGoal::tryToRun()
             throw SysError(format("executing `%1%'") % sub);
             
         } catch (std::exception & e) {
-            std::cerr << format("substitute error: %1%\n") % e.what();
+            std::cerr << format("substitute error: %1%") % e.what() << std::endl;
         }
         quickExit(1);
     }
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index d8d3751a1fc3..428b1ff9a755 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -350,13 +350,16 @@ Path createTempDir(const Path & tmpRoot)
 }
 
 
-void createDirs(const Path & path)
+Paths createDirs(const Path & path)
 {
-    if (path == "/") return;
-    createDirs(dirOf(path));
-    if (!pathExists(path))
+    if (path == "/") return Paths();
+    Paths created = createDirs(dirOf(path));
+    if (!pathExists(path)) {
         if (mkdir(path.c_str(), 0777) == -1)
             throw SysError(format("creating directory `%1%'") % path);
+        created.push_back(path);
+    }
+    return created;
 }
 
 
@@ -509,14 +512,25 @@ string drainFD(int fd)
 //////////////////////////////////////////////////////////////////////
 
 
-AutoDelete::AutoDelete(const string & p) : path(p)
+AutoDelete::AutoDelete(const string & p, bool recursive) : path(p)
 {
     del = true;
+    this->recursive = recursive;
 }
 
 AutoDelete::~AutoDelete()
 {
-    if (del) deletePath(path);
+    try {
+        if (del)
+            if (recursive)
+                deletePath(path);
+            else {
+                if (remove(path.c_str()) == -1)
+                    throw SysError(format("cannot unlink `%1%'") % path);
+            }
+    } catch (...) {
+        ignoreException();
+    }
 }
 
 void AutoDelete::cancel()
@@ -752,10 +766,10 @@ void killUser(uid_t uid)
 		if (errno != EINTR)
 		    throw SysError(format("cannot kill processes for uid `%1%'") % uid);
 	    }
-        
+
         } catch (std::exception & e) {
-            std::cerr << format("killing processes beloging to uid `%1%': %1%\n")
-                % uid % e.what();
+            std::cerr << format("killing processes beloging to uid `%1%': %1%")
+                % uid % e.what() << std::endl;
             quickExit(1);
         }
         quickExit(0);
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index f72a6f82011b..0ed98118c53b 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -72,8 +72,9 @@ void makePathReadOnly(const Path & path);
 /* Create a temporary directory. */
 Path createTempDir(const Path & tmpRoot = "");
 
-/* Create a directory and all its parents, if necessary. */
-void createDirs(const Path & path);
+/* Create a directory and all its parents, if necessary.  Returns the
+   list of created directories, in order of creation. */
+Paths createDirs(const Path & path);
 
 /* Create a file and write the given text to it.  The file is written
    in binary mode (i.e., no end-of-line conversions).  The path should
@@ -166,8 +167,9 @@ class AutoDelete
 {
     Path path;
     bool del;
+    bool recursive;    
 public:
-    AutoDelete(const Path & p);
+    AutoDelete(const Path & p, bool recursive = true);
     ~AutoDelete();
     void cancel();
 };