* 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:
parent
0b4ed64d29
commit
9397cd30c8
3 changed files with 162 additions and 16 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -1545,6 +1641,31 @@ void DerivationGoal::startBuilder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 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%'") %
|
||||||
drv.builder);
|
drv.builder);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
@ -754,8 +768,8 @@ void killUser(uid_t 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);
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue