* 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...)
This commit is contained in:
Eelco Dolstra 2007-10-27 00:46:59 +00:00
parent 0b4ed64d29
commit 9397cd30c8
3 changed files with 162 additions and 16 deletions

View file

@ -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 class DerivationGoal : public Goal
{ {
private: private:
@ -623,6 +701,14 @@ private:
Pipe toHook; Pipe toHook;
Pipe fromHook; 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)(); typedef void (DerivationGoal::*GoalState)();
GoalState state; GoalState state;
@ -678,7 +764,7 @@ private:
void openLogFile(); void openLogFile();
/* Common initialisation to be performed in child processes (i.e., /* 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(); void initChild();
/* Delete the temporary directory, if we have one. */ /* Delete the temporary directory, if we have one. */
@ -711,11 +797,18 @@ DerivationGoal::~DerivationGoal()
/* Careful: we should never ever throw an exception from a /* Careful: we should never ever throw an exception from a
destructor. */ destructor. */
try { try {
printMsg(lvlError, "DESTROY");
killChild(); killChild();
deleteTmpDir(false); deleteTmpDir(false);
} catch (...) { } catch (...) {
ignoreException(); ignoreException();
} }
try {
//sleep(1);
bindMounts.clear();
} catch (...) {
ignoreException();
}
} }
@ -1024,6 +1117,9 @@ void DerivationGoal::buildDone()
deleteTmpDir(true); 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 /* Compute the FS closure of the outputs and register them as
being valid. */ being valid. */
computeClosure(); computeClosure();
@ -1173,7 +1269,7 @@ DerivationGoal::HookReply DerivationGoal::tryBuildHook()
throw SysError(format("executing `%1%'") % buildHook); throw SysError(format("executing `%1%'") % buildHook);
} catch (std::exception & e) { } 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); quickExit(1);
} }
@ -1544,6 +1640,31 @@ void DerivationGoal::startBuilder()
% buildUser.getGID() % nixStore); % 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. */ /* Run the builder. */
printMsg(lvlChatty, format("executing builder `%1%'") % printMsg(lvlChatty, format("executing builder `%1%'") %
@ -1569,6 +1690,15 @@ void DerivationGoal::startBuilder()
try { /* child */ 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(); initChild();
/* Fill in the environment. */ /* Fill in the environment. */
@ -1632,7 +1762,7 @@ void DerivationGoal::startBuilder()
% drv.builder); % drv.builder);
} catch (std::exception & e) { } 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); quickExit(1);
} }
@ -2143,7 +2273,7 @@ void SubstitutionGoal::tryToRun()
throw SysError(format("executing `%1%'") % sub); throw SysError(format("executing `%1%'") % sub);
} catch (std::exception & e) { } 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); quickExit(1);
} }

View file

@ -350,13 +350,16 @@ Path createTempDir(const Path & tmpRoot)
} }
void createDirs(const Path & path) Paths createDirs(const Path & path)
{ {
if (path == "/") return; if (path == "/") return Paths();
createDirs(dirOf(path)); Paths created = createDirs(dirOf(path));
if (!pathExists(path)) if (!pathExists(path)) {
if (mkdir(path.c_str(), 0777) == -1) if (mkdir(path.c_str(), 0777) == -1)
throw SysError(format("creating directory `%1%'") % path); 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; del = true;
this->recursive = recursive;
} }
AutoDelete::~AutoDelete() 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() void AutoDelete::cancel()
@ -752,10 +766,10 @@ void killUser(uid_t uid)
if (errno != EINTR) if (errno != EINTR)
throw SysError(format("cannot kill processes for uid `%1%'") % uid); throw SysError(format("cannot kill processes for uid `%1%'") % uid);
} }
} catch (std::exception & e) { } catch (std::exception & e) {
std::cerr << format("killing processes beloging to uid `%1%': %1%\n") std::cerr << format("killing processes beloging to uid `%1%': %1%")
% uid % e.what(); % uid % e.what() << std::endl;
quickExit(1); quickExit(1);
} }
quickExit(0); quickExit(0);

View file

@ -72,8 +72,9 @@ void makePathReadOnly(const Path & path);
/* Create a temporary directory. */ /* Create a temporary directory. */
Path createTempDir(const Path & tmpRoot = ""); Path createTempDir(const Path & tmpRoot = "");
/* Create a directory and all its parents, if necessary. */ /* Create a directory and all its parents, if necessary. Returns the
void createDirs(const Path & path); 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 /* 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 in binary mode (i.e., no end-of-line conversions). The path should
@ -166,8 +167,9 @@ class AutoDelete
{ {
Path path; Path path;
bool del; bool del;
bool recursive;
public: public:
AutoDelete(const Path & p); AutoDelete(const Path & p, bool recursive = true);
~AutoDelete(); ~AutoDelete();
void cancel(); void cancel();
}; };