diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 85933e84f..3c988ea42 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -583,6 +583,84 @@ void deletePathWrapped(const Path & path) ////////////////////////////////////////////////////////////////////// +#include + + +/* 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 > 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(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 d8d3751a1..428b1ff9a 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 f72a6f820..0ed98118c 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(); };