aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2008-12-11 17:00:12 +0000
committerEelco Dolstra <e.dolstra@tudelft.nl>2008-12-11 17:00:12 +0000
commit7c54f1603f0c793bfc1f191aaa0ec71441038515 (patch)
treefca233b8416294da7404ccf5ca203ed23f5aeebf
parent07cdfb09fb74cf8e1c3c70c070ad20b5d1119fcf (diff)
* Do chroot builds in a private namespace. This means that all the
bind-mounts we do are only visible to the builder process and its children. So accidentally doing "rm -rf" on the chroot directory won't wipe out /nix/store and other bind-mounted directories anymore. Also, the bind-mounts in the private namespace disappear automatically when the builder exits.
-rw-r--r--configure.ac1
-rw-r--r--src/libstore/build.cc179
2 files changed, 46 insertions, 134 deletions
diff --git a/configure.ac b/configure.ac
index e666b8c47..7b8c36f1e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -90,6 +90,7 @@ AC_LANG_POP(C++)
# Check for chroot support (requires chroot() and bind mounts).
AC_CHECK_FUNCS([chroot])
+AC_CHECK_HEADERS([sched.h], [], [], [])
AC_CHECK_HEADERS([sys/param.h], [], [], [])
AC_CHECK_HEADERS([sys/mount.h], [], [],
[#ifdef HAVE_SYS_PARAM_H
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 6a3ba0f1c..ab3a46aa8 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -33,8 +33,11 @@
#if HAVE_SYS_MOUNT_H
#include <sys/mount.h>
#endif
+#if HAVE_SCHED_H
+#include <sched.h>
+#endif
-#define CHROOT_ENABLED HAVE_CHROOT && HAVE_SYS_MOUNT_H && defined(MS_BIND)
+#define CHROOT_ENABLED HAVE_CHROOT && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(CLONE_NEWNS)
namespace nix {
@@ -603,89 +606,6 @@ void deletePathWrapped(const Path & path)
//////////////////////////////////////////////////////////////////////
-/* Helper RAII 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)
- {
-#if CHROOT_ENABLED
- debug(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);
-#endif
- }
-
- void unbind()
- {
-#if CHROOT_ENABLED
- if (source == "") return;
-
- 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
- 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 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) {
- debug(format("deleting `%1%'") % *i);
- if (remove(i->c_str()) == -1)
- throw SysError(format("cannot unlink `%1%'") % *i);
- }
-
- source = "";
-#endif
- }
-};
-
-
-//////////////////////////////////////////////////////////////////////
-
-
class DerivationGoal : public Goal
{
private:
@@ -729,17 +649,9 @@ private:
/* Whether we're currently doing a chroot build. */
bool useChroot;
- /* RAII objects to delete the chroot directory and its /tmp
- directory. Don't change the order: autoDelChrootTmp has to be
- deleted before autoDelChrootRoot. */
- boost::shared_ptr<AutoDelete> autoDelChrootRoot;
- boost::shared_ptr<AutoDelete> autoDelChrootTmp;
+ /* 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. Keep this *below* autoDelChroot* just to be safe! */
- list<boost::shared_ptr<BindMount> > bindMounts;
-
typedef void (DerivationGoal::*GoalState)();
GoalState state;
@@ -1183,9 +1095,9 @@ void DerivationGoal::buildDone()
deleteTmpDir(true);
- /* In chroot builds, unmount the bind mounts ASAP. */
- bindMounts.clear(); /* the destructors will do the rest */
-
+ /* Delete the chroot (if we were using one). */
+ autoDelChroot.reset(); /* this runs the destructor */
+
/* Compute the FS closure of the outputs and register them as
being valid. */
computeClosure();
@@ -1737,29 +1649,18 @@ void DerivationGoal::startBuilder()
is somewhat pointless anyway. */
useChroot = queryBoolSetting("build-use-chroot", false);
Path chrootRootDir;
+ Paths dirsInChroot;
if (fixedOutput) useChroot = false;
if (useChroot) {
#if CHROOT_ENABLED
/* Create a temporary directory in which we set up the chroot
- environment using bind-mounts.
-
- !!! Bind mounts are potentially dangerous: if the user
- cleans up his system by doing "rm -rf
- /nix/var/nix/chroots/*", this will recurse into /nix/store
- via the bind mounts (and potentially other parts of the
- filesystem, depending on the setting of the
- `build-chroot-dirs' option). */
+ environment using bind-mounts. */
chrootRootDir = createTempDir(nixChrootsDir, "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, chrootRootDir should be empty. */
- autoDelChrootRoot = boost::shared_ptr<AutoDelete>(new AutoDelete(chrootRootDir, false));
+ /* Clean up the chroot directory automatically. */
+ autoDelChroot = boost::shared_ptr<AutoDelete>(new AutoDelete(chrootRootDir));
printMsg(lvlChatty, format("setting up chroot environment in `%1%'") % chrootRootDir);
@@ -1772,12 +1673,6 @@ void DerivationGoal::startBuilder()
if (chmod(chrootTmpDir.c_str(), 01777) == -1)
throw SysError("creating /tmp in the chroot");
- /* When deleting this, do recurse (the builder might have left
- crap there). As long as nothing important is bind-mounted
- under /tmp it's okay (and the bind-mounts are unmounted
- before autoDelChrootTmp's destructor runs, anyway). */
- autoDelChrootTmp = boost::shared_ptr<AutoDelete>(new AutoDelete(chrootTmpDir));
-
/* Bind-mount a user-configurable set of directories from the
host file system. The `/dev/pts' directory must be mounted
separately so that newly-created pseudo-terminals show
@@ -1787,17 +1682,10 @@ void DerivationGoal::startBuilder()
defaultDirs.push_back("/dev/pts");
defaultDirs.push_back("/proc");
- Paths dirsInChroot = querySetting("build-chroot-dirs", defaultDirs);
+ 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, chrootRootDir + *i)));
-
#else
throw Error("chroot builds are not supported on this platform");
#endif
@@ -1829,14 +1717,37 @@ void DerivationGoal::startBuilder()
try { /* child */
#if CHROOT_ENABLED
- /* 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(chrootRootDir.c_str()) == -1)
- throw SysError(format("cannot change root directory to `%1%'") % chrootRootDir);
+ if (useChroot) {
+ /* Create our own mount namespace. This means 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. */
+ if (unshare(CLONE_NEWNS) == -1)
+ throw SysError(format("cannot set up a private mount namespace"));
+
+ /* Bind-mount all the directories from the "host"
+ filesystem that we want in the chroot
+ environment. */
+ foreach (Paths::iterator, i, dirsInChroot) {
+ Path source = *i;
+ Path target = chrootRootDir + source;
+ printMsg(lvlError, format("bind mounting `%1%' to `%2%'") % source % target);
+
+ 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);
+ }
+
+ /* Do the chroot(). 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 (chroot(chrootRootDir.c_str()) == -1)
+ throw SysError(format("cannot change root directory to `%1%'") % chrootRootDir);
+ }
#endif
initChild();