diff options
author | Eelco Dolstra <e.dolstra@tudelft.nl> | 2007-10-27T00·46+0000 |
---|---|---|
committer | Eelco Dolstra <e.dolstra@tudelft.nl> | 2007-10-27T00·46+0000 |
commit | 9397cd30c8a6ffd65fc3b85985ea59ecfb72672b (patch) | |
tree | a3382c4031b84e518e6d8a133a98cdc84ba8e3d1 /src | |
parent | 0b4ed64d295316146fc4de8a5a2e971b771058b8 (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.cc | 138 | ||||
-rw-r--r-- | src/libutil/util.cc | 32 | ||||
-rw-r--r-- | src/libutil/util.hh | 8 |
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(); }; |