2006-09-04 21:06:23 +00:00
|
|
|
#include "references.hh"
|
|
|
|
#include "pathlocks.hh"
|
|
|
|
#include "misc.hh"
|
|
|
|
#include "globals.hh"
|
2006-11-30 17:43:04 +00:00
|
|
|
#include "local-store.hh"
|
2006-09-04 21:06:23 +00:00
|
|
|
#include "util.hh"
|
|
|
|
|
2003-07-20 19:29:38 +00:00
|
|
|
#include <map>
|
2006-03-01 17:44:28 +00:00
|
|
|
#include <iostream>
|
2005-10-17 15:33:24 +00:00
|
|
|
#include <sstream>
|
2004-06-18 18:09:32 +00:00
|
|
|
#include <boost/shared_ptr.hpp>
|
2004-06-25 15:36:09 +00:00
|
|
|
#include <boost/weak_ptr.hpp>
|
2004-06-18 18:09:32 +00:00
|
|
|
#include <boost/enable_shared_from_this.hpp>
|
2003-07-20 19:29:38 +00:00
|
|
|
|
2006-12-08 15:44:00 +00:00
|
|
|
#include <time.h>
|
2006-05-30 11:37:21 +00:00
|
|
|
#include <sys/time.h>
|
2004-05-11 18:05:44 +00:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <unistd.h>
|
2004-07-01 11:11:16 +00:00
|
|
|
#include <errno.h>
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2005-10-17 15:33:24 +00:00
|
|
|
#include <pwd.h>
|
|
|
|
#include <grp.h>
|
|
|
|
|
2007-10-29 10:03:07 +00:00
|
|
|
|
|
|
|
/* Includes required for chroot support. */
|
2007-10-27 16:51:55 +00:00
|
|
|
#include "config.h"
|
|
|
|
|
2007-10-29 10:03:07 +00:00
|
|
|
#if HAVE_SYS_PARAM_H
|
|
|
|
#include <sys/param.h>
|
|
|
|
#endif
|
2007-10-27 16:51:55 +00:00
|
|
|
#if HAVE_SYS_MOUNT_H
|
|
|
|
#include <sys/mount.h>
|
|
|
|
#endif
|
2008-12-11 17:00:12 +00:00
|
|
|
#if HAVE_SCHED_H
|
|
|
|
#include <sched.h>
|
|
|
|
#endif
|
2007-10-27 16:51:55 +00:00
|
|
|
|
2008-12-11 17:00:12 +00:00
|
|
|
#define CHROOT_ENABLED HAVE_CHROOT && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(CLONE_NEWNS)
|
2007-10-27 16:51:55 +00:00
|
|
|
|
2003-07-20 19:29:38 +00:00
|
|
|
|
2006-09-04 21:06:23 +00:00
|
|
|
namespace nix {
|
|
|
|
|
|
|
|
using std::map;
|
|
|
|
|
2003-07-20 19:29:38 +00:00
|
|
|
|
2005-01-20 16:01:07 +00:00
|
|
|
/* !!! TODO derivationFromPath shouldn't be used here */
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
|
2004-05-11 18:05:44 +00:00
|
|
|
static string pathNullDevice = "/dev/null";
|
|
|
|
|
|
|
|
|
2005-10-17 15:33:24 +00:00
|
|
|
static const uid_t rootUserId = 0;
|
|
|
|
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
/* Forward definition. */
|
|
|
|
class Worker;
|
|
|
|
|
|
|
|
|
|
|
|
/* A pointer to a goal. */
|
|
|
|
class Goal;
|
2006-09-04 21:06:23 +00:00
|
|
|
typedef boost::shared_ptr<Goal> GoalPtr;
|
|
|
|
typedef boost::weak_ptr<Goal> WeakGoalPtr;
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
/* Set of goals. */
|
2004-06-18 18:09:32 +00:00
|
|
|
typedef set<GoalPtr> Goals;
|
2004-06-25 15:36:09 +00:00
|
|
|
typedef set<WeakGoalPtr> WeakGoals;
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
/* A map of paths to goals (and the other way around). */
|
2004-06-25 15:36:09 +00:00
|
|
|
typedef map<Path, WeakGoalPtr> WeakGoalMap;
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2006-09-04 21:06:23 +00:00
|
|
|
class Goal : public boost::enable_shared_from_this<Goal>
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
2005-02-23 11:19:27 +00:00
|
|
|
public:
|
|
|
|
typedef enum {ecBusy, ecSuccess, ecFailed} ExitCode;
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
protected:
|
|
|
|
|
|
|
|
/* Backlink to the worker. */
|
|
|
|
Worker & worker;
|
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
/* Goals that this goal is waiting for. */
|
|
|
|
Goals waitees;
|
|
|
|
|
|
|
|
/* Goals waiting for this one to finish. Must use weak pointers
|
|
|
|
here to prevent cycles. */
|
|
|
|
WeakGoals waiters;
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2004-08-30 11:51:36 +00:00
|
|
|
/* Number of goals we are/were waiting for that have failed. */
|
2004-06-25 10:21:44 +00:00
|
|
|
unsigned int nrFailed;
|
|
|
|
|
2005-02-18 09:50:20 +00:00
|
|
|
/* Name of this goal for debugging purposes. */
|
|
|
|
string name;
|
|
|
|
|
2005-02-23 11:19:27 +00:00
|
|
|
/* Whether the goal is finished. */
|
|
|
|
ExitCode exitCode;
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
Goal(Worker & worker) : worker(worker)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
2004-08-30 11:51:36 +00:00
|
|
|
nrFailed = 0;
|
2005-02-23 11:19:27 +00:00
|
|
|
exitCode = ecBusy;
|
2008-01-15 04:32:08 +00:00
|
|
|
forceInputs = false;
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual ~Goal()
|
|
|
|
{
|
2005-02-18 09:50:20 +00:00
|
|
|
trace("goal destroyed");
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
|
|
|
|
2008-01-15 04:32:08 +00:00
|
|
|
bool forceInputs;
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
public:
|
|
|
|
virtual void work() = 0;
|
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
void addWaitee(GoalPtr waitee);
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
virtual void waiteeDone(GoalPtr waitee, ExitCode result);
|
2004-06-25 10:21:44 +00:00
|
|
|
|
2005-10-17 15:33:24 +00:00
|
|
|
virtual void handleChildOutput(int fd, const string & data)
|
|
|
|
{
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void handleEOF(int fd)
|
2004-06-29 09:41:50 +00:00
|
|
|
{
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
void trace(const format & f);
|
2005-02-18 09:50:20 +00:00
|
|
|
|
|
|
|
string getName()
|
|
|
|
{
|
|
|
|
return name;
|
|
|
|
}
|
2004-06-25 15:36:09 +00:00
|
|
|
|
2005-02-23 11:19:27 +00:00
|
|
|
ExitCode getExitCode()
|
|
|
|
{
|
|
|
|
return exitCode;
|
|
|
|
}
|
2005-10-17 15:33:24 +00:00
|
|
|
|
2006-12-08 18:41:48 +00:00
|
|
|
/* Cancel the goal. It should wake up its waiters, get rid of any
|
|
|
|
running child processes that are being monitored by the worker
|
|
|
|
(important!), etc. */
|
|
|
|
virtual void cancel() = 0;
|
2006-12-08 17:26:21 +00:00
|
|
|
|
2008-01-15 04:32:08 +00:00
|
|
|
void setForceInputs(bool x)
|
|
|
|
{
|
|
|
|
forceInputs = x;
|
|
|
|
}
|
|
|
|
|
2004-06-25 10:21:44 +00:00
|
|
|
protected:
|
2006-12-08 17:26:21 +00:00
|
|
|
void amDone(ExitCode result);
|
2004-06-18 18:09:32 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* A mapping used to remember for each child process to what goal it
|
2005-10-17 15:33:24 +00:00
|
|
|
belongs, and file descriptors for receiving log data and output
|
|
|
|
path creation commands. */
|
2004-06-19 21:45:04 +00:00
|
|
|
struct Child
|
|
|
|
{
|
2004-06-25 15:36:09 +00:00
|
|
|
WeakGoalPtr goal;
|
2005-10-17 15:33:24 +00:00
|
|
|
set<int> fds;
|
2004-06-19 21:45:04 +00:00
|
|
|
bool inBuildSlot;
|
2006-12-08 15:44:00 +00:00
|
|
|
time_t lastOutput; /* time we last got output on stdout/stderr */
|
2004-06-19 21:45:04 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
typedef map<pid_t, Child> Children;
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
/* The worker class. */
|
|
|
|
class Worker
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
/* Note: the worker should only have strong pointers to the
|
|
|
|
top-level goals. */
|
|
|
|
|
|
|
|
/* The top-level goals of the worker. */
|
|
|
|
Goals topGoals;
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
/* Goals that are ready to do some work. */
|
2004-06-25 15:36:09 +00:00
|
|
|
WeakGoals awake;
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
/* Goals waiting for a build slot. */
|
2004-06-25 15:36:09 +00:00
|
|
|
WeakGoals wantingToBuild;
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
/* Child processes currently running. */
|
|
|
|
Children children;
|
|
|
|
|
2004-06-19 21:45:04 +00:00
|
|
|
/* Number of build slots occupied. Not all child processes
|
|
|
|
(namely build hooks) count as occupied build slots. */
|
|
|
|
unsigned int nrChildren;
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
/* Maps used to prevent multiple instantiations of a goal for the
|
2005-01-20 16:01:07 +00:00
|
|
|
same derivation / path. */
|
2005-01-19 11:16:11 +00:00
|
|
|
WeakGoalMap derivationGoals;
|
2004-06-25 15:36:09 +00:00
|
|
|
WeakGoalMap substitutionGoals;
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2007-08-28 11:36:17 +00:00
|
|
|
/* Goals waiting for busy paths to be unlocked. */
|
|
|
|
WeakGoals waitingForAnyGoal;
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
public:
|
|
|
|
|
2008-06-09 13:52:45 +00:00
|
|
|
LocalStore & store;
|
|
|
|
|
|
|
|
Worker(LocalStore & store);
|
2004-06-18 18:09:32 +00:00
|
|
|
~Worker();
|
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
/* Make a goal (with caching). */
|
2005-01-19 11:16:11 +00:00
|
|
|
GoalPtr makeDerivationGoal(const Path & drvPath);
|
2004-06-25 15:36:09 +00:00
|
|
|
GoalPtr makeSubstitutionGoal(const Path & storePath);
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
/* Remove a dead goal. */
|
2004-06-18 18:09:32 +00:00
|
|
|
void removeGoal(GoalPtr goal);
|
|
|
|
|
|
|
|
/* Wake up a goal (i.e., there is something for it to do). */
|
|
|
|
void wakeUp(GoalPtr goal);
|
|
|
|
|
|
|
|
/* Can we start another child process? */
|
|
|
|
bool canBuildMore();
|
|
|
|
|
2008-12-04 14:29:41 +00:00
|
|
|
/* Can we postpone a build right now? */
|
|
|
|
bool canPostpone();
|
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
/* Registers a running child process. `inBuildSlot' means that
|
|
|
|
the process counts towards the jobs limit. */
|
2005-10-17 15:33:24 +00:00
|
|
|
void childStarted(GoalPtr goal, pid_t pid,
|
|
|
|
const set<int> & fds, bool inBuildSlot);
|
2006-12-08 17:26:21 +00:00
|
|
|
|
|
|
|
/* Unregisters a running child process. `wakeSleepers' should be
|
|
|
|
false if there is no sense in waking up goals that are sleeping
|
|
|
|
because they can't run yet (e.g., there is no free build slot,
|
|
|
|
or the hook would still say `postpone'). */
|
2004-07-01 16:24:35 +00:00
|
|
|
void childTerminated(pid_t pid, bool wakeSleepers = true);
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
/* Put `goal' to sleep until a build slot becomes available (which
|
|
|
|
might be right away). */
|
|
|
|
void waitForBuildSlot(GoalPtr goal);
|
|
|
|
|
|
|
|
/* Put `goal' to sleep until a child process terminates, i.e., a
|
|
|
|
call is made to childTerminate(..., true). */
|
|
|
|
void waitForChildTermination(GoalPtr goal);
|
2007-08-12 00:29:28 +00:00
|
|
|
|
2007-08-28 11:36:17 +00:00
|
|
|
/* Wait for any goal to finish. Pretty indiscriminate way to
|
|
|
|
wait for some resource that some other goal is holding. */
|
|
|
|
void waitForAnyGoal(GoalPtr goal);
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2005-02-23 11:19:27 +00:00
|
|
|
/* Loop until the specified top-level goals have finished. */
|
|
|
|
void run(const Goals & topGoals);
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
/* Wait for input to become available. */
|
|
|
|
void waitForInput();
|
2007-08-12 00:29:28 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
MakeError(SubstError, Error)
|
|
|
|
MakeError(BuildError, Error)
|
2004-06-25 10:21:44 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
void Goal::addWaitee(GoalPtr waitee)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
2004-06-25 15:36:09 +00:00
|
|
|
waitees.insert(waitee);
|
|
|
|
waitee->waiters.insert(shared_from_this());
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
2004-06-25 15:36:09 +00:00
|
|
|
assert(waitees.find(waitee) != waitees.end());
|
|
|
|
waitees.erase(waitee);
|
2005-02-18 09:50:20 +00:00
|
|
|
|
|
|
|
trace(format("waitee `%1%' done; %2% left") %
|
|
|
|
waitee->name % waitees.size());
|
2004-06-28 10:42:57 +00:00
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
if (result == ecFailed) ++nrFailed;
|
2004-06-28 10:42:57 +00:00
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
if (waitees.empty() || (result == ecFailed && !keepGoing)) {
|
2004-06-28 10:42:57 +00:00
|
|
|
|
|
|
|
/* If we failed and keepGoing is not set, we remove all
|
|
|
|
remaining waitees. */
|
|
|
|
for (Goals::iterator i = waitees.begin(); i != waitees.end(); ++i) {
|
|
|
|
GoalPtr goal = *i;
|
|
|
|
WeakGoals waiters2;
|
|
|
|
for (WeakGoals::iterator j = goal->waiters.begin();
|
|
|
|
j != goal->waiters.end(); ++j)
|
|
|
|
if (j->lock() != shared_from_this())
|
|
|
|
waiters2.insert(*j);
|
|
|
|
goal->waiters = waiters2;
|
|
|
|
}
|
|
|
|
waitees.clear();
|
2004-07-01 16:24:35 +00:00
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
worker.wakeUp(shared_from_this());
|
2004-06-28 10:42:57 +00:00
|
|
|
}
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
void Goal::amDone(ExitCode result)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
2004-06-25 15:36:09 +00:00
|
|
|
trace("done");
|
2005-02-23 11:19:27 +00:00
|
|
|
assert(exitCode == ecBusy);
|
2006-12-08 17:26:21 +00:00
|
|
|
assert(result == ecSuccess || result == ecFailed);
|
|
|
|
exitCode = result;
|
2004-06-25 15:36:09 +00:00
|
|
|
for (WeakGoals::iterator i = waiters.begin(); i != waiters.end(); ++i) {
|
|
|
|
GoalPtr goal = i->lock();
|
2006-12-08 17:26:21 +00:00
|
|
|
if (goal) goal->waiteeDone(shared_from_this(), result);
|
2004-06-25 15:36:09 +00:00
|
|
|
}
|
|
|
|
waiters.clear();
|
2004-06-18 18:09:32 +00:00
|
|
|
worker.removeGoal(shared_from_this());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
void Goal::trace(const format & f)
|
|
|
|
{
|
2005-02-18 09:50:20 +00:00
|
|
|
debug(format("%1%: %2%") % name % f);
|
2004-06-25 15:36:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2004-06-22 10:21:44 +00:00
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
/* Common initialisation performed in child processes. */
|
|
|
|
void commonChildInit(Pipe & logPipe)
|
|
|
|
{
|
2006-02-03 14:20:59 +00:00
|
|
|
/* Put the child in a separate session (and thus a separate
|
|
|
|
process group) so that it has no controlling terminal (meaning
|
|
|
|
that e.g. ssh cannot open /dev/tty) and it doesn't receive
|
|
|
|
terminal signals. */
|
|
|
|
if (setsid() == -1)
|
|
|
|
throw SysError(format("creating a new session"));
|
|
|
|
|
2004-06-22 10:21:44 +00:00
|
|
|
/* Dup the write side of the logger pipe into stderr. */
|
|
|
|
if (dup2(logPipe.writeSide, STDERR_FILENO) == -1)
|
|
|
|
throw SysError("cannot pipe standard error into log file");
|
|
|
|
logPipe.readSide.close();
|
|
|
|
|
2006-02-03 14:20:59 +00:00
|
|
|
/* Dup stderr to stdout. */
|
2004-06-22 10:21:44 +00:00
|
|
|
if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1)
|
|
|
|
throw SysError("cannot dup stderr into stdout");
|
|
|
|
|
|
|
|
/* Reroute stdin to /dev/null. */
|
|
|
|
int fdDevNull = open(pathNullDevice.c_str(), O_RDWR);
|
|
|
|
if (fdDevNull == -1)
|
|
|
|
throw SysError(format("cannot open `%1%'") % pathNullDevice);
|
|
|
|
if (dup2(fdDevNull, STDIN_FILENO) == -1)
|
|
|
|
throw SysError("cannot dup null device into stdin");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-06-24 13:40:38 +00:00
|
|
|
/* Convert a string list to an array of char pointers. Careful: the
|
|
|
|
string list should outlive the array. */
|
2004-06-22 10:21:44 +00:00
|
|
|
const char * * strings2CharPtrs(const Strings & ss)
|
|
|
|
{
|
|
|
|
const char * * arr = new const char * [ss.size() + 1];
|
|
|
|
const char * * p = arr;
|
|
|
|
for (Strings::const_iterator i = ss.begin(); i != ss.end(); ++i)
|
|
|
|
*p++ = i->c_str();
|
|
|
|
*p = 0;
|
|
|
|
return arr;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-11-14 15:46:45 +00:00
|
|
|
/* Restore default handling of SIGPIPE, otherwise some programs will
|
|
|
|
randomly say "Broken pipe". */
|
|
|
|
static void restoreSIGPIPE()
|
|
|
|
{
|
|
|
|
struct sigaction act, oact;
|
|
|
|
act.sa_handler = SIG_DFL;
|
|
|
|
act.sa_flags = 0;
|
|
|
|
if (sigaction(SIGPIPE, &act, &oact)) throw SysError("resetting SIGPIPE");
|
|
|
|
}
|
2006-06-19 16:24:15 +00:00
|
|
|
|
2005-10-20 16:58:34 +00:00
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
class UserLock
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
/* POSIX locks suck. If we have a lock on a file, and we open and
|
|
|
|
close that file again (without closing the original file
|
|
|
|
descriptor), we lose the lock. So we have to be *very* careful
|
|
|
|
not to open a lock file on which we are holding a lock. */
|
|
|
|
static PathSet lockedPaths; /* !!! not thread-safe */
|
|
|
|
|
|
|
|
Path fnUserLock;
|
|
|
|
AutoCloseFD fdUserLock;
|
|
|
|
|
2006-12-07 11:27:32 +00:00
|
|
|
string user;
|
2005-10-20 16:58:34 +00:00
|
|
|
uid_t uid;
|
2006-12-06 20:00:15 +00:00
|
|
|
gid_t gid;
|
2005-10-20 16:58:34 +00:00
|
|
|
|
|
|
|
public:
|
|
|
|
UserLock();
|
|
|
|
~UserLock();
|
|
|
|
|
|
|
|
void acquire();
|
|
|
|
void release();
|
|
|
|
|
2006-12-07 16:33:31 +00:00
|
|
|
void kill();
|
|
|
|
|
2006-12-07 11:27:32 +00:00
|
|
|
string getUser() { return user; }
|
|
|
|
uid_t getUID() { return uid; }
|
|
|
|
uid_t getGID() { return gid; }
|
2006-12-07 00:16:07 +00:00
|
|
|
|
2006-12-07 11:27:32 +00:00
|
|
|
bool enabled() { return uid != 0; }
|
|
|
|
|
2005-10-20 16:58:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
PathSet UserLock::lockedPaths;
|
|
|
|
|
|
|
|
|
|
|
|
UserLock::UserLock()
|
|
|
|
{
|
2006-12-06 20:00:15 +00:00
|
|
|
uid = gid = 0;
|
2005-10-20 16:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UserLock::~UserLock()
|
|
|
|
{
|
|
|
|
release();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UserLock::acquire()
|
|
|
|
{
|
|
|
|
assert(uid == 0);
|
|
|
|
|
2006-12-06 20:00:15 +00:00
|
|
|
string buildUsersGroup = querySetting("build-users-group", "");
|
|
|
|
assert(buildUsersGroup != "");
|
|
|
|
|
|
|
|
/* Get the members of the build-users-group. */
|
|
|
|
struct group * gr = getgrnam(buildUsersGroup.c_str());
|
|
|
|
if (!gr)
|
|
|
|
throw Error(format("the group `%1%' specified in `build-users-group' does not exist")
|
|
|
|
% buildUsersGroup);
|
|
|
|
gid = gr->gr_gid;
|
|
|
|
|
|
|
|
/* Copy the result of getgrnam. */
|
|
|
|
Strings users;
|
|
|
|
for (char * * p = gr->gr_mem; *p; ++p) {
|
|
|
|
debug(format("found build user `%1%'") % *p);
|
|
|
|
users.push_back(*p);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (users.empty())
|
|
|
|
throw Error(format("the build users group `%1%' has no members")
|
|
|
|
% buildUsersGroup);
|
2005-10-20 16:58:34 +00:00
|
|
|
|
2006-12-06 20:00:15 +00:00
|
|
|
/* Find a user account that isn't currently in use for another
|
|
|
|
build. */
|
|
|
|
for (Strings::iterator i = users.begin(); i != users.end(); ++i) {
|
2006-12-05 19:01:19 +00:00
|
|
|
debug(format("trying user `%1%'") % *i);
|
2005-10-20 16:58:34 +00:00
|
|
|
|
|
|
|
struct passwd * pw = getpwnam(i->c_str());
|
|
|
|
if (!pw)
|
2006-12-06 20:00:15 +00:00
|
|
|
throw Error(format("the user `%1%' in the group `%2%' does not exist")
|
|
|
|
% *i % buildUsersGroup);
|
2006-12-07 00:19:27 +00:00
|
|
|
|
2005-10-20 16:58:34 +00:00
|
|
|
fnUserLock = (format("%1%/userpool/%2%") % nixStateDir % pw->pw_uid).str();
|
|
|
|
|
|
|
|
if (lockedPaths.find(fnUserLock) != lockedPaths.end())
|
|
|
|
/* We already have a lock on this one. */
|
|
|
|
continue;
|
|
|
|
|
|
|
|
AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT, 0600);
|
|
|
|
if (fd == -1)
|
|
|
|
throw SysError(format("opening user lock `%1%'") % fnUserLock);
|
|
|
|
|
|
|
|
if (lockFile(fd, ltWrite, false)) {
|
|
|
|
fdUserLock = fd.borrow();
|
|
|
|
lockedPaths.insert(fnUserLock);
|
2006-12-07 11:27:32 +00:00
|
|
|
user = *i;
|
2005-10-20 16:58:34 +00:00
|
|
|
uid = pw->pw_uid;
|
2006-12-07 00:19:27 +00:00
|
|
|
|
|
|
|
/* Sanity check... */
|
|
|
|
if (uid == getuid() || uid == geteuid())
|
|
|
|
throw Error(format("the Nix user should not be a member of `%1%'")
|
|
|
|
% buildUsersGroup);
|
|
|
|
|
2005-10-20 16:58:34 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
throw BuildError(format("all build users are currently in use; "
|
2006-12-06 20:00:15 +00:00
|
|
|
"consider creating additional users and adding them to the `%1%' group")
|
|
|
|
% buildUsersGroup);
|
2005-10-20 16:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UserLock::release()
|
|
|
|
{
|
|
|
|
if (uid == 0) return;
|
|
|
|
fdUserLock.close(); /* releases lock */
|
|
|
|
assert(lockedPaths.find(fnUserLock) != lockedPaths.end());
|
|
|
|
lockedPaths.erase(fnUserLock);
|
|
|
|
fnUserLock = "";
|
|
|
|
uid = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-12-07 16:33:31 +00:00
|
|
|
static void runSetuidHelper(const string & command,
|
|
|
|
const string & arg)
|
2006-12-07 14:14:35 +00:00
|
|
|
{
|
2007-03-20 22:04:25 +00:00
|
|
|
Path program = getEnv("NIX_SETUID_HELPER",
|
|
|
|
nixLibexecDir + "/nix-setuid-helper");
|
2006-12-07 14:14:35 +00:00
|
|
|
|
|
|
|
/* Fork. */
|
|
|
|
Pid pid;
|
|
|
|
pid = fork();
|
|
|
|
switch (pid) {
|
|
|
|
|
|
|
|
case -1:
|
|
|
|
throw SysError("unable to fork");
|
|
|
|
|
|
|
|
case 0: /* child */
|
|
|
|
try {
|
2007-02-21 14:31:42 +00:00
|
|
|
std::vector<const char *> args; /* careful with c_str()! */
|
2006-12-07 14:14:35 +00:00
|
|
|
args.push_back(program.c_str());
|
2006-12-07 16:33:31 +00:00
|
|
|
args.push_back(command.c_str());
|
|
|
|
args.push_back(arg.c_str());
|
2006-12-07 14:14:35 +00:00
|
|
|
args.push_back(0);
|
|
|
|
|
2008-11-14 15:46:45 +00:00
|
|
|
restoreSIGPIPE();
|
|
|
|
|
2006-12-07 14:14:35 +00:00
|
|
|
execve(program.c_str(), (char * *) &args[0], 0);
|
|
|
|
throw SysError(format("executing `%1%'") % program);
|
|
|
|
}
|
|
|
|
catch (std::exception & e) {
|
|
|
|
std::cerr << "error: " << e.what() << std::endl;
|
|
|
|
}
|
|
|
|
quickExit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parent. */
|
|
|
|
|
|
|
|
/* Wait for the child to finish. */
|
|
|
|
int status = pid.wait(true);
|
|
|
|
if (!statusOk(status))
|
|
|
|
throw Error(format("program `%1%' %2%")
|
|
|
|
% program % statusToString(status));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-12-07 16:33:31 +00:00
|
|
|
void UserLock::kill()
|
|
|
|
{
|
|
|
|
assert(enabled());
|
|
|
|
if (amPrivileged())
|
|
|
|
killUser(uid);
|
|
|
|
else
|
|
|
|
runSetuidHelper("kill", user);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool amPrivileged()
|
|
|
|
{
|
|
|
|
return geteuid() == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool haveBuildUsers()
|
|
|
|
{
|
|
|
|
return querySetting("build-users-group", "") != "";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void getOwnership(const Path & path)
|
|
|
|
{
|
|
|
|
runSetuidHelper("get-ownership", path);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-12-09 00:26:24 +00:00
|
|
|
void deletePathWrapped(const Path & path,
|
2008-06-18 09:34:17 +00:00
|
|
|
unsigned long long & bytesFreed, unsigned long long & blocksFreed)
|
2006-12-07 14:14:35 +00:00
|
|
|
{
|
2006-12-09 00:26:24 +00:00
|
|
|
try {
|
|
|
|
/* First try to delete it ourselves. */
|
2008-06-18 09:34:17 +00:00
|
|
|
deletePath(path, bytesFreed, blocksFreed);
|
2006-12-09 00:26:24 +00:00
|
|
|
} catch (SysError & e) {
|
|
|
|
/* If this failed due to a permission error, then try it with
|
|
|
|
the setuid helper. */
|
|
|
|
if (haveBuildUsers() && !amPrivileged()) {
|
|
|
|
getOwnership(path);
|
2008-06-18 09:34:17 +00:00
|
|
|
deletePath(path, bytesFreed, blocksFreed);
|
2006-12-09 00:26:24 +00:00
|
|
|
} else
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void deletePathWrapped(const Path & path)
|
|
|
|
{
|
2008-06-18 09:34:17 +00:00
|
|
|
unsigned long long dummy1, dummy2;
|
|
|
|
deletePathWrapped(path, dummy1, dummy2);
|
2006-12-07 14:14:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
class DerivationGoal : public Goal
|
2004-05-11 18:05:44 +00:00
|
|
|
{
|
2004-06-18 18:09:32 +00:00
|
|
|
private:
|
2005-01-20 16:01:07 +00:00
|
|
|
/* The path of the derivation. */
|
2005-01-19 11:16:11 +00:00
|
|
|
Path drvPath;
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2005-01-20 16:01:07 +00:00
|
|
|
/* The derivation stored at drvPath. */
|
2005-01-19 11:16:11 +00:00
|
|
|
Derivation drv;
|
2004-05-11 18:05:44 +00:00
|
|
|
|
|
|
|
/* The remainder is state held during the build. */
|
|
|
|
|
|
|
|
/* Locks on the output paths. */
|
|
|
|
PathLocks outputLocks;
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
/* All input paths (that is, the union of FS closures of the
|
|
|
|
immediate input paths). */
|
|
|
|
PathSet inputPaths;
|
2004-05-11 18:05:44 +00:00
|
|
|
|
|
|
|
/* Referenceable paths (i.e., input and output paths). */
|
|
|
|
PathSet allPaths;
|
|
|
|
|
2005-10-17 15:33:24 +00:00
|
|
|
/* User selected for running the builder. */
|
2005-10-20 16:58:34 +00:00
|
|
|
UserLock buildUser;
|
2005-10-17 15:33:24 +00:00
|
|
|
|
2004-05-11 18:05:44 +00:00
|
|
|
/* The process ID of the builder. */
|
2004-06-22 09:51:44 +00:00
|
|
|
Pid pid;
|
2004-05-11 18:05:44 +00:00
|
|
|
|
|
|
|
/* The temporary directory. */
|
|
|
|
Path tmpDir;
|
|
|
|
|
|
|
|
/* File descriptor for the log file. */
|
2004-06-15 13:49:42 +00:00
|
|
|
AutoCloseFD fdLogFile;
|
2004-05-11 18:05:44 +00:00
|
|
|
|
|
|
|
/* Pipe for the builder's standard output/error. */
|
2004-05-13 19:14:49 +00:00
|
|
|
Pipe logPipe;
|
|
|
|
|
|
|
|
/* Pipes for talking to the build hook (if any). */
|
|
|
|
Pipe toHook;
|
|
|
|
Pipe fromHook;
|
|
|
|
|
2007-10-27 00:46:59 +00:00
|
|
|
/* Whether we're currently doing a chroot build. */
|
|
|
|
bool useChroot;
|
2008-12-11 18:57:10 +00:00
|
|
|
|
|
|
|
Path chrootRootDir;
|
2007-10-27 00:46:59 +00:00
|
|
|
|
2008-12-11 17:00:12 +00:00
|
|
|
/* RAII object to delete the chroot directory. */
|
|
|
|
boost::shared_ptr<AutoDelete> autoDelChroot;
|
2007-10-27 16:06:38 +00:00
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
typedef void (DerivationGoal::*GoalState)();
|
2004-06-18 18:09:32 +00:00
|
|
|
GoalState state;
|
|
|
|
|
|
|
|
public:
|
2005-01-19 11:16:11 +00:00
|
|
|
DerivationGoal(const Path & drvPath, Worker & worker);
|
|
|
|
~DerivationGoal();
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2006-12-08 18:41:48 +00:00
|
|
|
void cancel();
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
void work();
|
2004-06-19 21:45:04 +00:00
|
|
|
|
2005-02-23 11:19:27 +00:00
|
|
|
Path getDrvPath()
|
|
|
|
{
|
|
|
|
return drvPath;
|
|
|
|
}
|
2008-01-15 04:32:08 +00:00
|
|
|
|
2004-06-19 21:45:04 +00:00
|
|
|
private:
|
2004-06-18 18:09:32 +00:00
|
|
|
/* The states. */
|
|
|
|
void init();
|
2006-12-07 23:58:36 +00:00
|
|
|
void haveDerivation();
|
2005-01-25 10:55:33 +00:00
|
|
|
void outputsSubstituted();
|
2005-01-19 11:16:11 +00:00
|
|
|
void inputsRealised();
|
2004-06-18 18:09:32 +00:00
|
|
|
void tryToBuild();
|
|
|
|
void buildDone();
|
|
|
|
|
2004-06-19 21:45:04 +00:00
|
|
|
/* Is the build hook willing to perform the build? */
|
2007-08-28 11:36:17 +00:00
|
|
|
typedef enum {rpAccept, rpDecline, rpPostpone, rpDone, rpRestart} HookReply;
|
2004-06-19 21:45:04 +00:00
|
|
|
HookReply tryBuildHook();
|
|
|
|
|
|
|
|
/* Synchronously wait for a build hook to finish. */
|
2007-03-19 12:48:45 +00:00
|
|
|
void terminateBuildHook(bool kill = false);
|
2004-06-19 21:45:04 +00:00
|
|
|
|
|
|
|
/* Acquires locks on the output paths and gathers information
|
|
|
|
about the build (e.g., the input closures). During this
|
|
|
|
process its possible that we find out that the build is
|
2007-08-28 11:36:17 +00:00
|
|
|
unnecessary, in which case we return prDone. It's also
|
|
|
|
possible that some other goal is already building/substituting
|
|
|
|
the output paths, in which case we return prRestart (go back to
|
|
|
|
the haveDerivation() state). Otherwise, prProceed is
|
|
|
|
returned. */
|
|
|
|
typedef enum {prProceed, prDone, prRestart} PrepareBuildReply;
|
|
|
|
PrepareBuildReply prepareBuild();
|
2004-06-19 21:45:04 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
/* Start building a derivation. */
|
|
|
|
void startBuilder();
|
|
|
|
|
|
|
|
/* Must be called after the output paths have become valid (either
|
|
|
|
due to a successful build or hook, or because they already
|
|
|
|
were). */
|
2005-01-19 11:16:11 +00:00
|
|
|
void computeClosure();
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
/* Open a log file and a pipe to it. */
|
2008-11-12 11:08:27 +00:00
|
|
|
Path openLogFile();
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
/* Common initialisation to be performed in child processes (i.e.,
|
2007-10-27 00:46:59 +00:00
|
|
|
both in builders and in build hooks). */
|
2004-06-18 18:09:32 +00:00
|
|
|
void initChild();
|
|
|
|
|
|
|
|
/* Delete the temporary directory, if we have one. */
|
2004-05-11 18:05:44 +00:00
|
|
|
void deleteTmpDir(bool force);
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2004-06-29 09:41:50 +00:00
|
|
|
/* Callback used by the worker to write to the log. */
|
2005-10-17 15:33:24 +00:00
|
|
|
void handleChildOutput(int fd, const string & data);
|
|
|
|
void handleEOF(int fd);
|
2004-06-29 09:41:50 +00:00
|
|
|
|
2005-01-25 10:55:33 +00:00
|
|
|
/* Return the set of (in)valid paths. */
|
|
|
|
PathSet checkPathValidity(bool returnValid);
|
2006-12-08 18:41:48 +00:00
|
|
|
|
|
|
|
/* Forcibly kill the child process, if any. */
|
|
|
|
void killChild();
|
2004-05-11 18:05:44 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
DerivationGoal::DerivationGoal(const Path & drvPath, Worker & worker)
|
|
|
|
: Goal(worker)
|
2004-05-11 18:05:44 +00:00
|
|
|
{
|
2005-01-19 11:16:11 +00:00
|
|
|
this->drvPath = drvPath;
|
|
|
|
state = &DerivationGoal::init;
|
2005-02-18 09:50:20 +00:00
|
|
|
name = (format("building of `%1%'") % drvPath).str();
|
|
|
|
trace("created");
|
2004-05-11 18:05:44 +00:00
|
|
|
}
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
DerivationGoal::~DerivationGoal()
|
2004-05-11 18:05:44 +00:00
|
|
|
{
|
|
|
|
/* Careful: we should never ever throw an exception from a
|
|
|
|
destructor. */
|
|
|
|
try {
|
2006-12-08 18:41:48 +00:00
|
|
|
killChild();
|
2004-05-11 18:05:44 +00:00
|
|
|
deleteTmpDir(false);
|
2007-05-01 15:16:17 +00:00
|
|
|
} catch (...) {
|
|
|
|
ignoreException();
|
2004-05-11 18:05:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-12-08 18:41:48 +00:00
|
|
|
void DerivationGoal::killChild()
|
|
|
|
{
|
|
|
|
if (pid != -1) {
|
|
|
|
worker.childTerminated(pid);
|
|
|
|
|
|
|
|
if (buildUser.enabled()) {
|
|
|
|
/* We can't use pid.kill(), since we may not have the
|
|
|
|
appropriate privilege. I.e., if we're not root, then
|
|
|
|
setuid helper should do it).
|
|
|
|
|
|
|
|
Also, if we're using a build user, then there is a
|
|
|
|
tricky race condition: if we kill the build user before
|
|
|
|
the child has done its setuid() to the build user uid,
|
|
|
|
then it won't be killed, and we'll potentially lock up
|
|
|
|
in pid.wait(). So also send a conventional kill to the
|
|
|
|
child. */
|
|
|
|
::kill(-pid, SIGKILL); /* ignore the result */
|
|
|
|
buildUser.kill();
|
|
|
|
pid.wait(true);
|
|
|
|
} else
|
|
|
|
pid.kill();
|
|
|
|
|
|
|
|
assert(pid == -1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DerivationGoal::cancel()
|
|
|
|
{
|
|
|
|
killChild();
|
|
|
|
amDone(ecFailed);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
void DerivationGoal::work()
|
2004-05-11 18:05:44 +00:00
|
|
|
{
|
2004-06-18 18:09:32 +00:00
|
|
|
(this->*state)();
|
2004-05-11 18:05:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
void DerivationGoal::init()
|
2004-05-11 18:05:44 +00:00
|
|
|
{
|
2004-06-22 14:48:59 +00:00
|
|
|
trace("init");
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2005-01-20 16:01:07 +00:00
|
|
|
/* The first thing to do is to make sure that the derivation
|
2004-06-18 18:09:32 +00:00
|
|
|
exists. If it doesn't, it may be created through a
|
|
|
|
substitute. */
|
2005-01-19 11:16:11 +00:00
|
|
|
addWaitee(worker.makeSubstitutionGoal(drvPath));
|
2003-08-01 14:11:19 +00:00
|
|
|
|
2006-12-07 23:58:36 +00:00
|
|
|
state = &DerivationGoal::haveDerivation;
|
2004-05-11 18:05:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-12-07 23:58:36 +00:00
|
|
|
void DerivationGoal::haveDerivation()
|
2004-05-11 18:05:44 +00:00
|
|
|
{
|
2005-01-19 11:16:11 +00:00
|
|
|
trace("loading derivation");
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2004-06-25 10:21:44 +00:00
|
|
|
if (nrFailed != 0) {
|
2008-12-11 18:57:10 +00:00
|
|
|
printMsg(lvlError, format("cannot build missing derivation `%1%'") % drvPath);
|
2006-12-08 17:26:21 +00:00
|
|
|
amDone(ecFailed);
|
2004-06-25 10:21:44 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-06-09 13:52:45 +00:00
|
|
|
assert(worker.store.isValidPath(drvPath));
|
2003-07-20 19:29:38 +00:00
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
/* Get the derivation. */
|
|
|
|
drv = derivationFromPath(drvPath);
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2005-01-31 10:27:25 +00:00
|
|
|
for (DerivationOutputs::iterator i = drv.outputs.begin();
|
|
|
|
i != drv.outputs.end(); ++i)
|
2008-06-09 13:52:45 +00:00
|
|
|
worker.store.addTempRoot(i->second.path);
|
2005-01-31 10:27:25 +00:00
|
|
|
|
2005-01-25 10:55:33 +00:00
|
|
|
/* Check what outputs paths are not already valid. */
|
|
|
|
PathSet invalidOutputs = checkPathValidity(false);
|
|
|
|
|
|
|
|
/* If they are all valid, then we're done. */
|
|
|
|
if (invalidOutputs.size() == 0) {
|
2008-06-13 17:21:20 +00:00
|
|
|
if(!forceInputs) {
|
2008-01-15 04:32:08 +00:00
|
|
|
amDone(ecSuccess);
|
|
|
|
return;
|
|
|
|
}
|
2005-01-25 10:55:33 +00:00
|
|
|
}
|
|
|
|
|
2007-08-28 11:36:17 +00:00
|
|
|
/* If this is a fixed-output derivation, it is possible that some
|
|
|
|
other goal is already building the output paths. (The case
|
|
|
|
where some other process is building it is handled through
|
|
|
|
normal locking mechanisms.) So if any output paths are already
|
|
|
|
being built, put this goal to sleep. */
|
|
|
|
for (PathSet::iterator i = invalidOutputs.begin();
|
|
|
|
i != invalidOutputs.end(); ++i)
|
|
|
|
if (pathIsLockedByMe(*i)) {
|
|
|
|
/* Wait until any goal finishes (hopefully the one that is
|
|
|
|
locking *i), then retry haveDerivation(). */
|
|
|
|
worker.waitForAnyGoal(shared_from_this());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2005-01-25 10:55:33 +00:00
|
|
|
/* We are first going to try to create the invalid output paths
|
|
|
|
through substitutes. If that doesn't work, we'll build
|
|
|
|
them. */
|
|
|
|
for (PathSet::iterator i = invalidOutputs.begin();
|
|
|
|
i != invalidOutputs.end(); ++i)
|
|
|
|
/* Don't bother creating a substitution goal if there are no
|
|
|
|
substitutes. */
|
2008-06-09 13:52:45 +00:00
|
|
|
if (worker.store.hasSubstitutes(*i))
|
2005-01-25 10:55:33 +00:00
|
|
|
addWaitee(worker.makeSubstitutionGoal(*i));
|
2007-08-12 00:29:28 +00:00
|
|
|
|
2005-01-25 10:55:33 +00:00
|
|
|
if (waitees.empty()) /* to prevent hang (no wake-up event) */
|
|
|
|
outputsSubstituted();
|
|
|
|
else
|
|
|
|
state = &DerivationGoal::outputsSubstituted;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DerivationGoal::outputsSubstituted()
|
|
|
|
{
|
|
|
|
trace("all outputs substituted (maybe)");
|
|
|
|
|
2005-01-25 17:08:52 +00:00
|
|
|
if (nrFailed > 0 && !tryFallback)
|
2005-01-25 13:00:12 +00:00
|
|
|
throw Error(format("some substitutes for the outputs of derivation `%1%' failed; try `--fallback'") % drvPath);
|
|
|
|
|
|
|
|
nrFailed = 0;
|
|
|
|
|
2005-01-25 10:55:33 +00:00
|
|
|
if (checkPathValidity(false).size() == 0) {
|
2008-01-15 04:32:08 +00:00
|
|
|
if (! forceInputs){
|
|
|
|
amDone(ecSuccess);
|
|
|
|
return;
|
|
|
|
}
|
2004-05-14 12:24:29 +00:00
|
|
|
}
|
2003-08-01 14:11:19 +00:00
|
|
|
|
2005-01-25 10:55:33 +00:00
|
|
|
/* Otherwise, at least one of the output paths could not be
|
|
|
|
produced using a substitute. So we have to build instead. */
|
|
|
|
|
|
|
|
/* The inputs must be built before we can build this goal. */
|
2005-01-20 14:10:19 +00:00
|
|
|
/* !!! but if possible, only install the paths that we need */
|
|
|
|
for (DerivationInputs::iterator i = drv.inputDrvs.begin();
|
2008-01-15 04:32:08 +00:00
|
|
|
i != drv.inputDrvs.end(); ++i){
|
|
|
|
GoalPtr newGoal = worker.makeDerivationGoal(i->first);
|
|
|
|
newGoal->setForceInputs(forceInputs);
|
|
|
|
addWaitee(newGoal);
|
|
|
|
}
|
2003-08-01 14:11:19 +00:00
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
for (PathSet::iterator i = drv.inputSrcs.begin();
|
|
|
|
i != drv.inputSrcs.end(); ++i)
|
|
|
|
addWaitee(worker.makeSubstitutionGoal(*i));
|
|
|
|
|
2008-01-15 04:32:08 +00:00
|
|
|
/* Actually, I do some work twice just to be on the safe side */
|
|
|
|
string s = drv.env["exportBuildReferencesGraph"];
|
|
|
|
Strings ss = tokenizeString(s);
|
|
|
|
if (ss.size() % 2 !=0)
|
|
|
|
throw BuildError(format("odd number of tokens in `exportBuildReferencesGraph': `%1%'") % s);
|
|
|
|
for (Strings::iterator i = ss.begin(); i != ss.end(); ) {
|
|
|
|
string fileName = *i++;
|
|
|
|
Path storePath=*i++;
|
|
|
|
|
|
|
|
if (!isInStore(storePath))
|
|
|
|
throw BuildError(format("`exportBuildReferencesGraph' contains a non-store path `%1%'")
|
|
|
|
% storePath);
|
|
|
|
storePath = toStorePath(storePath);
|
2008-06-09 13:52:45 +00:00
|
|
|
if (!worker.store.isValidPath(storePath))
|
2008-01-15 04:32:08 +00:00
|
|
|
throw BuildError(format("`exportBuildReferencesGraph' contains an invalid path `%1%'")
|
|
|
|
% storePath);
|
|
|
|
|
|
|
|
/* Build-time closure should be in dependencies
|
|
|
|
* We really want just derivation, its closure
|
|
|
|
* and outputs. Looks like we should build it.
|
|
|
|
* */
|
|
|
|
|
|
|
|
GoalPtr newGoal = worker.makeDerivationGoal(storePath);
|
|
|
|
newGoal->setForceInputs(true);
|
|
|
|
addWaitee(newGoal);
|
|
|
|
}
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
state = &DerivationGoal::inputsRealised;
|
2004-05-13 19:14:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
void DerivationGoal::inputsRealised()
|
2004-05-11 18:05:44 +00:00
|
|
|
{
|
2004-06-22 14:48:59 +00:00
|
|
|
trace("all inputs realised");
|
2004-05-13 19:14:49 +00:00
|
|
|
|
2004-06-25 10:21:44 +00:00
|
|
|
if (nrFailed != 0) {
|
|
|
|
printMsg(lvlError,
|
2005-01-19 11:16:11 +00:00
|
|
|
format("cannot build derivation `%1%': "
|
|
|
|
"%2% inputs could not be realised")
|
|
|
|
% drvPath % nrFailed);
|
2006-12-08 17:26:21 +00:00
|
|
|
amDone(ecFailed);
|
2004-06-25 10:21:44 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-01-15 04:32:08 +00:00
|
|
|
/* Maybe we just wanted to force build of inputs */
|
|
|
|
if (checkPathValidity(false).size() == 0) {
|
|
|
|
amDone(ecSuccess);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2004-06-19 21:45:04 +00:00
|
|
|
/* Okay, try to build. Note that here we don't wait for a build
|
|
|
|
slot to become available, since we don't need one if there is a
|
|
|
|
build hook. */
|
2005-01-19 11:16:11 +00:00
|
|
|
state = &DerivationGoal::tryToBuild;
|
2004-06-19 21:45:04 +00:00
|
|
|
worker.wakeUp(shared_from_this());
|
2004-05-13 19:14:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
void DerivationGoal::tryToBuild()
|
2004-05-13 19:14:49 +00:00
|
|
|
{
|
2004-06-22 14:48:59 +00:00
|
|
|
trace("trying to build");
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2004-06-25 10:21:44 +00:00
|
|
|
try {
|
|
|
|
|
|
|
|
/* Is the build hook willing to accept this job? */
|
|
|
|
switch (tryBuildHook()) {
|
|
|
|
case rpAccept:
|
|
|
|
/* Yes, it has started doing so. Wait until we get
|
|
|
|
EOF from the hook. */
|
2005-01-19 11:16:11 +00:00
|
|
|
state = &DerivationGoal::buildDone;
|
2004-06-25 10:21:44 +00:00
|
|
|
return;
|
|
|
|
case rpPostpone:
|
|
|
|
/* Not now; wait until at least one child finishes. */
|
2006-12-08 17:26:21 +00:00
|
|
|
worker.waitForChildTermination(shared_from_this());
|
2004-06-25 10:21:44 +00:00
|
|
|
return;
|
|
|
|
case rpDecline:
|
|
|
|
/* We should do it ourselves. */
|
|
|
|
break;
|
|
|
|
case rpDone:
|
2005-01-19 11:16:11 +00:00
|
|
|
/* Somebody else did it. */
|
2006-12-08 17:26:21 +00:00
|
|
|
amDone(ecSuccess);
|
2004-06-25 10:21:44 +00:00
|
|
|
return;
|
2007-08-28 11:36:17 +00:00
|
|
|
case rpRestart:
|
|
|
|
/* Somebody else is building this output path.
|
|
|
|
Restart from haveDerivation(). */
|
|
|
|
state = &DerivationGoal::haveDerivation;
|
|
|
|
worker.waitForAnyGoal(shared_from_this());
|
|
|
|
return;
|
2004-06-25 10:21:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Make sure that we are allowed to start a build. */
|
|
|
|
if (!worker.canBuildMore()) {
|
2004-06-19 21:45:04 +00:00
|
|
|
worker.waitForBuildSlot(shared_from_this());
|
|
|
|
return;
|
2004-06-25 10:21:44 +00:00
|
|
|
}
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
/* Acquire locks and such. If we then see that the build has
|
|
|
|
been done by somebody else, we're done. */
|
2007-08-28 11:36:17 +00:00
|
|
|
PrepareBuildReply preply = prepareBuild();
|
|
|
|
if (preply == prDone) {
|
2006-12-08 17:26:21 +00:00
|
|
|
amDone(ecSuccess);
|
2004-06-19 21:45:04 +00:00
|
|
|
return;
|
2007-08-28 11:36:17 +00:00
|
|
|
} else if (preply == prRestart) {
|
|
|
|
state = &DerivationGoal::haveDerivation;
|
|
|
|
worker.waitForAnyGoal(shared_from_this());
|
|
|
|
return;
|
2004-06-25 10:21:44 +00:00
|
|
|
}
|
2004-06-19 21:45:04 +00:00
|
|
|
|
2004-06-25 10:21:44 +00:00
|
|
|
/* Okay, we have to build. */
|
|
|
|
startBuilder();
|
2003-08-01 14:11:19 +00:00
|
|
|
|
2004-06-25 10:21:44 +00:00
|
|
|
} catch (BuildError & e) {
|
|
|
|
printMsg(lvlError, e.msg());
|
2008-11-12 11:08:27 +00:00
|
|
|
if (printBuildTrace) {
|
|
|
|
printMsg(lvlError, format("@ build-failed %1% %2% %3% %4%")
|
|
|
|
% drvPath % drv.outputs["out"].path % 0 % e.msg());
|
|
|
|
}
|
2006-12-08 17:26:21 +00:00
|
|
|
amDone(ecFailed);
|
2004-06-19 21:45:04 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This state will be reached when we get EOF on the child's
|
|
|
|
log pipe. */
|
2005-01-19 11:16:11 +00:00
|
|
|
state = &DerivationGoal::buildDone;
|
2004-06-19 21:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
void DerivationGoal::buildDone()
|
2004-06-19 21:45:04 +00:00
|
|
|
{
|
2004-06-22 14:48:59 +00:00
|
|
|
trace("build done");
|
2004-06-19 21:45:04 +00:00
|
|
|
|
|
|
|
/* Since we got an EOF on the logger pipe, the builder is presumed
|
|
|
|
to have terminated. In fact, the builder could also have
|
|
|
|
simply have closed its end of the pipe --- just don't do that
|
|
|
|
:-) */
|
2005-10-17 15:33:24 +00:00
|
|
|
/* !!! this could block! security problem! solution: kill the
|
|
|
|
child */
|
2004-06-22 09:51:44 +00:00
|
|
|
pid_t savedPid = pid;
|
|
|
|
int status = pid.wait(true);
|
2004-06-19 21:45:04 +00:00
|
|
|
|
2006-12-08 00:19:50 +00:00
|
|
|
debug(format("builder process for `%1%' finished") % drvPath);
|
|
|
|
|
2004-06-19 21:45:04 +00:00
|
|
|
/* So the child is gone now. */
|
2004-06-22 09:51:44 +00:00
|
|
|
worker.childTerminated(savedPid);
|
2004-06-19 21:45:04 +00:00
|
|
|
|
2006-12-08 00:19:50 +00:00
|
|
|
/* Close the read side of the logger pipe. */
|
|
|
|
logPipe.readSide.close();
|
|
|
|
|
|
|
|
/* Close the log file. */
|
|
|
|
fdLogFile.close();
|
|
|
|
|
2005-10-17 17:43:21 +00:00
|
|
|
/* When running under a build user, make sure that all processes
|
|
|
|
running under that uid are gone. This is to prevent a
|
|
|
|
malicious user from leaving behind a process that keeps files
|
|
|
|
open and modifies them after they have been chown'ed to
|
|
|
|
root. */
|
2006-12-07 00:16:07 +00:00
|
|
|
if (buildUser.enabled())
|
2006-12-07 16:33:31 +00:00
|
|
|
buildUser.kill();
|
2005-10-17 17:43:21 +00:00
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
try {
|
2004-06-19 21:45:04 +00:00
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
/* Some cleanup per path. We do this here and not in
|
|
|
|
computeClosure() for convenience when the build has
|
|
|
|
failed. */
|
|
|
|
for (DerivationOutputs::iterator i = drv.outputs.begin();
|
|
|
|
i != drv.outputs.end(); ++i)
|
|
|
|
{
|
|
|
|
Path path = i->second.path;
|
2008-12-11 18:57:10 +00:00
|
|
|
|
|
|
|
if (useChroot && pathExists(chrootRootDir + path)) {
|
|
|
|
if (rename((chrootRootDir + path).c_str(), path.c_str()) == -1)
|
|
|
|
throw SysError(format("moving build output `%1%' from the chroot to the Nix store") % path);
|
|
|
|
}
|
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
if (!pathExists(path)) continue;
|
|
|
|
|
|
|
|
struct stat st;
|
|
|
|
if (lstat(path.c_str(), &st) == -1)
|
|
|
|
throw SysError(format("getting attributes of path `%1%'") % path);
|
2006-12-08 00:19:50 +00:00
|
|
|
|
|
|
|
#ifndef __CYGWIN__
|
2006-12-08 17:26:21 +00:00
|
|
|
/* Check that the output is not group or world writable,
|
|
|
|
as that means that someone else can have interfered
|
|
|
|
with the build. Also, the output should be owned by
|
|
|
|
the build user. */
|
2006-12-12 23:05:01 +00:00
|
|
|
if ((!S_ISLNK(st.st_mode) && (st.st_mode & (S_IWGRP | S_IWOTH))) ||
|
2006-12-08 17:26:21 +00:00
|
|
|
(buildUser.enabled() && st.st_uid != buildUser.getUID()))
|
|
|
|
throw BuildError(format("suspicious ownership or permission on `%1%'; rejecting this build output") % path);
|
2006-12-08 00:19:50 +00:00
|
|
|
#endif
|
2004-06-19 21:45:04 +00:00
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
/* Gain ownership of the build result using the setuid
|
|
|
|
wrapper if we're not root. If we *are* root, then
|
|
|
|
canonicalisePathMetaData() will take care of this later
|
|
|
|
on. */
|
|
|
|
if (buildUser.enabled() && !amPrivileged())
|
|
|
|
getOwnership(path);
|
|
|
|
}
|
2006-12-08 00:19:50 +00:00
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
/* Check the exit status. */
|
|
|
|
if (!statusOk(status)) {
|
|
|
|
deleteTmpDir(false);
|
|
|
|
throw BuildError(format("builder for `%1%' %2%")
|
|
|
|
% drvPath % statusToString(status));
|
|
|
|
}
|
2004-06-20 19:17:54 +00:00
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
deleteTmpDir(true);
|
2004-06-19 21:45:04 +00:00
|
|
|
|
2008-12-11 17:00:12 +00:00
|
|
|
/* Delete the chroot (if we were using one). */
|
|
|
|
autoDelChroot.reset(); /* this runs the destructor */
|
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
/* Compute the FS closure of the outputs and register them as
|
|
|
|
being valid. */
|
2005-01-19 11:16:11 +00:00
|
|
|
computeClosure();
|
2008-11-12 11:08:27 +00:00
|
|
|
|
2004-06-25 10:21:44 +00:00
|
|
|
} catch (BuildError & e) {
|
|
|
|
printMsg(lvlError, e.msg());
|
2008-11-12 11:08:27 +00:00
|
|
|
if (printBuildTrace) {
|
|
|
|
printMsg(lvlError, format("@ build-failed %1% %2% %3% %4%")
|
|
|
|
% drvPath % drv.outputs["out"].path % status % e.msg());
|
|
|
|
}
|
2006-12-08 17:26:21 +00:00
|
|
|
amDone(ecFailed);
|
2004-06-25 10:21:44 +00:00
|
|
|
return;
|
|
|
|
}
|
2004-06-19 21:45:04 +00:00
|
|
|
|
2005-10-20 16:58:34 +00:00
|
|
|
/* Release the build user, if applicable. */
|
|
|
|
buildUser.release();
|
|
|
|
|
2008-11-12 11:08:27 +00:00
|
|
|
if (printBuildTrace) {
|
|
|
|
printMsg(lvlError, format("@ build-succeeded %1% %2%")
|
|
|
|
% drvPath % drv.outputs["out"].path);
|
|
|
|
}
|
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
amDone(ecSuccess);
|
2004-06-19 21:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static string readLine(int fd)
|
|
|
|
{
|
|
|
|
string s;
|
|
|
|
while (1) {
|
2007-03-19 12:48:45 +00:00
|
|
|
checkInterrupt();
|
2004-06-19 21:45:04 +00:00
|
|
|
char ch;
|
|
|
|
ssize_t rd = read(fd, &ch, 1);
|
|
|
|
if (rd == -1) {
|
|
|
|
if (errno != EINTR)
|
|
|
|
throw SysError("reading a line");
|
|
|
|
} else if (rd == 0)
|
|
|
|
throw Error("unexpected EOF reading a line");
|
|
|
|
else {
|
|
|
|
if (ch == '\n') return s;
|
|
|
|
s += ch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void writeLine(int fd, string s)
|
|
|
|
{
|
|
|
|
s += '\n';
|
|
|
|
writeFull(fd, (const unsigned char *) s.c_str(), s.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* !!! ugly hack */
|
|
|
|
static void drain(int fd)
|
|
|
|
{
|
|
|
|
unsigned char buffer[1024];
|
|
|
|
while (1) {
|
2007-03-19 12:48:45 +00:00
|
|
|
checkInterrupt();
|
2004-06-19 21:45:04 +00:00
|
|
|
ssize_t rd = read(fd, buffer, sizeof buffer);
|
|
|
|
if (rd == -1) {
|
|
|
|
if (errno != EINTR)
|
|
|
|
throw SysError("draining");
|
|
|
|
} else if (rd == 0) break;
|
2006-12-03 02:08:13 +00:00
|
|
|
else writeToStderr(buffer, rd);
|
2004-06-19 21:45:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
* Removed the `id' attribute hack.
* Formalise the notion of fixed-output derivations, i.e., derivations
for which a cryptographic hash of the output is known in advance.
Changes to such derivations should not propagate upwards through the
dependency graph. Previously this was done by specifying the hash
component of the output path through the `id' attribute, but this is
insecure since you can lie about it (i.e., you can specify any hash
and then produce a completely different output). Now the
responsibility for checking the output is moved from the builder to
Nix itself.
A fixed-output derivation can be created by specifying the
`outputHash' and `outputHashAlgo' attributes, the latter taking
values `md5', `sha1', and `sha256', and the former specifying the
actual hash in hexadecimal or in base-32 (auto-detected by looking
at the length of the attribute value). MD5 is included for
compatibility but should be considered deprecated.
* Removed the `drvPath' pseudo-attribute in derivation results. It's
no longer necessary.
* Cleaned up the support for multiple output paths in derivation store
expressions. Each output now has a unique identifier (e.g., `out',
`devel', `docs'). Previously there was no way to tell output paths
apart at the store expression level.
* `nix-hash' now has a flag `--base32' to specify that the hash should
be printed in base-32 notation.
* `fetchurl' accepts parameters `sha256' and `sha1' in addition to
`md5'.
* `nix-prefetch-url' now prints out a SHA-1 hash in base-32. (TODO: a
flag to specify the hash.)
2005-01-17 16:55:19 +00:00
|
|
|
PathSet outputPaths(const DerivationOutputs & outputs)
|
|
|
|
{
|
|
|
|
PathSet paths;
|
|
|
|
for (DerivationOutputs::const_iterator i = outputs.begin();
|
|
|
|
i != outputs.end(); ++i)
|
|
|
|
paths.insert(i->second.path);
|
|
|
|
return paths;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
DerivationGoal::HookReply DerivationGoal::tryBuildHook()
|
2004-06-19 21:45:04 +00:00
|
|
|
{
|
2007-11-16 16:15:26 +00:00
|
|
|
if (!useBuildHook) return rpDecline;
|
2004-06-19 21:45:04 +00:00
|
|
|
Path buildHook = getEnv("NIX_BUILD_HOOK");
|
|
|
|
if (buildHook == "") return rpDecline;
|
|
|
|
buildHook = absPath(buildHook);
|
|
|
|
|
|
|
|
/* Create a directory where we will store files used for
|
|
|
|
communication between us and the build hook. */
|
|
|
|
tmpDir = createTempDir();
|
|
|
|
|
|
|
|
/* Create the log file and pipe. */
|
2008-11-12 11:08:27 +00:00
|
|
|
Path logFile = openLogFile();
|
2004-06-19 21:45:04 +00:00
|
|
|
|
|
|
|
/* Create the communication pipes. */
|
|
|
|
toHook.create();
|
|
|
|
fromHook.create();
|
|
|
|
|
|
|
|
/* Fork the hook. */
|
2004-06-22 09:51:44 +00:00
|
|
|
pid = fork();
|
|
|
|
switch (pid) {
|
2004-06-19 21:45:04 +00:00
|
|
|
|
|
|
|
case -1:
|
|
|
|
throw SysError("unable to fork");
|
|
|
|
|
|
|
|
case 0:
|
|
|
|
try { /* child */
|
|
|
|
|
|
|
|
initChild();
|
|
|
|
|
|
|
|
execl(buildHook.c_str(), buildHook.c_str(),
|
|
|
|
(worker.canBuildMore() ? (string) "1" : "0").c_str(),
|
|
|
|
thisSystem.c_str(),
|
2005-01-19 11:16:11 +00:00
|
|
|
drv.platform.c_str(),
|
2008-12-04 14:29:41 +00:00
|
|
|
drvPath.c_str(),
|
|
|
|
(worker.canPostpone() ? (string) "0" : "1").c_str(),
|
2008-12-04 16:51:16 +00:00
|
|
|
(format("%1%") % maxSilentTime).str().c_str(),
|
2008-12-04 14:29:41 +00:00
|
|
|
NULL);
|
2004-06-19 21:45:04 +00:00
|
|
|
|
|
|
|
throw SysError(format("executing `%1%'") % buildHook);
|
|
|
|
|
2006-09-04 21:06:23 +00:00
|
|
|
} catch (std::exception & e) {
|
2007-10-27 00:46:59 +00:00
|
|
|
std::cerr << format("build hook error: %1%") % e.what() << std::endl;
|
2004-06-19 21:45:04 +00:00
|
|
|
}
|
2006-07-20 12:17:25 +00:00
|
|
|
quickExit(1);
|
2004-06-19 21:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* parent */
|
2006-02-02 16:27:31 +00:00
|
|
|
pid.setSeparatePG(true);
|
2007-03-19 12:48:45 +00:00
|
|
|
pid.setKillSignal(SIGTERM);
|
2004-06-19 21:45:04 +00:00
|
|
|
logPipe.writeSide.close();
|
|
|
|
worker.childStarted(shared_from_this(),
|
2005-10-17 15:33:24 +00:00
|
|
|
pid, singleton<set<int> >(logPipe.readSide), false);
|
2004-06-19 21:45:04 +00:00
|
|
|
|
|
|
|
fromHook.writeSide.close();
|
|
|
|
toHook.readSide.close();
|
|
|
|
|
|
|
|
/* Read the first line of input, which should be a word indicating
|
|
|
|
whether the hook wishes to perform the build. !!! potential
|
|
|
|
for deadlock here: we should also read from the child's logger
|
|
|
|
pipe. */
|
|
|
|
string reply;
|
|
|
|
try {
|
|
|
|
reply = readLine(fromHook.readSide);
|
|
|
|
} catch (Error & e) {
|
2007-03-19 12:48:45 +00:00
|
|
|
terminateBuildHook(true);
|
2004-06-19 21:45:04 +00:00
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug(format("hook reply is `%1%'") % reply);
|
|
|
|
|
|
|
|
if (reply == "decline" || reply == "postpone") {
|
|
|
|
/* Clean up the child. !!! hacky / should verify */
|
|
|
|
terminateBuildHook();
|
|
|
|
return reply == "decline" ? rpDecline : rpPostpone;
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (reply == "accept") {
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
/* Acquire locks and such. If we then see that the output
|
|
|
|
paths are now valid, we're done. */
|
2007-08-28 11:36:17 +00:00
|
|
|
PrepareBuildReply preply = prepareBuild();
|
|
|
|
if (preply == prDone || preply == prRestart) {
|
2004-06-20 19:17:54 +00:00
|
|
|
/* Tell the hook to exit. */
|
2004-06-19 21:45:04 +00:00
|
|
|
writeLine(toHook.writeSide, "cancel");
|
|
|
|
terminateBuildHook();
|
2007-08-28 11:36:17 +00:00
|
|
|
return preply == prDone ? rpDone : rpRestart;
|
2004-06-19 21:45:04 +00:00
|
|
|
}
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
printMsg(lvlInfo, format("running hook to build path(s) %1%")
|
|
|
|
% showPaths(outputPaths(drv.outputs)));
|
2004-06-22 14:48:59 +00:00
|
|
|
|
2004-06-19 21:45:04 +00:00
|
|
|
/* Write the information that the hook needs to perform the
|
2005-01-20 16:01:07 +00:00
|
|
|
build, i.e., the set of input paths, the set of output
|
2005-01-25 11:55:43 +00:00
|
|
|
paths, and the references (pointer graph) in the input
|
|
|
|
paths. */
|
2004-06-19 21:45:04 +00:00
|
|
|
|
|
|
|
Path inputListFN = tmpDir + "/inputs";
|
|
|
|
Path outputListFN = tmpDir + "/outputs";
|
2005-01-25 11:55:43 +00:00
|
|
|
Path referencesFN = tmpDir + "/references";
|
2004-06-19 21:45:04 +00:00
|
|
|
|
2005-03-23 13:16:36 +00:00
|
|
|
/* The `inputs' file lists all inputs that have to be copied
|
|
|
|
to the remote system. This unfortunately has to contain
|
|
|
|
the entire derivation closure to ensure that the validity
|
|
|
|
invariant holds on the remote system. (I.e., it's
|
|
|
|
unfortunate that we have to list it since the remote system
|
|
|
|
*probably* already has it.) */
|
|
|
|
PathSet allInputs;
|
|
|
|
allInputs.insert(inputPaths.begin(), inputPaths.end());
|
|
|
|
computeFSClosure(drvPath, allInputs);
|
|
|
|
|
2004-06-19 21:45:04 +00:00
|
|
|
string s;
|
2005-03-23 13:16:36 +00:00
|
|
|
for (PathSet::iterator i = allInputs.begin();
|
|
|
|
i != allInputs.end(); ++i)
|
2004-06-19 21:45:04 +00:00
|
|
|
s += *i + "\n";
|
|
|
|
|
2005-03-23 13:16:36 +00:00
|
|
|
writeStringToFile(inputListFN, s);
|
|
|
|
|
|
|
|
/* The `outputs' file lists all outputs that have to be copied
|
|
|
|
from the remote system. */
|
2004-06-19 21:45:04 +00:00
|
|
|
s = "";
|
2005-01-19 11:16:11 +00:00
|
|
|
for (DerivationOutputs::iterator i = drv.outputs.begin();
|
|
|
|
i != drv.outputs.end(); ++i)
|
* Removed the `id' attribute hack.
* Formalise the notion of fixed-output derivations, i.e., derivations
for which a cryptographic hash of the output is known in advance.
Changes to such derivations should not propagate upwards through the
dependency graph. Previously this was done by specifying the hash
component of the output path through the `id' attribute, but this is
insecure since you can lie about it (i.e., you can specify any hash
and then produce a completely different output). Now the
responsibility for checking the output is moved from the builder to
Nix itself.
A fixed-output derivation can be created by specifying the
`outputHash' and `outputHashAlgo' attributes, the latter taking
values `md5', `sha1', and `sha256', and the former specifying the
actual hash in hexadecimal or in base-32 (auto-detected by looking
at the length of the attribute value). MD5 is included for
compatibility but should be considered deprecated.
* Removed the `drvPath' pseudo-attribute in derivation results. It's
no longer necessary.
* Cleaned up the support for multiple output paths in derivation store
expressions. Each output now has a unique identifier (e.g., `out',
`devel', `docs'). Previously there was no way to tell output paths
apart at the store expression level.
* `nix-hash' now has a flag `--base32' to specify that the hash should
be printed in base-32 notation.
* `fetchurl' accepts parameters `sha256' and `sha1' in addition to
`md5'.
* `nix-prefetch-url' now prints out a SHA-1 hash in base-32. (TODO: a
flag to specify the hash.)
2005-01-17 16:55:19 +00:00
|
|
|
s += i->second.path + "\n";
|
2004-06-19 21:45:04 +00:00
|
|
|
writeStringToFile(outputListFN, s);
|
2005-01-25 11:55:43 +00:00
|
|
|
|
2005-03-23 13:16:36 +00:00
|
|
|
/* The `references' file has exactly the format accepted by
|
|
|
|
`nix-store --register-validity'. */
|
2006-11-13 18:18:13 +00:00
|
|
|
writeStringToFile(referencesFN,
|
2008-01-29 18:17:36 +00:00
|
|
|
makeValidityRegistration(allInputs, true, false));
|
2005-01-25 11:55:43 +00:00
|
|
|
|
2004-06-20 19:17:54 +00:00
|
|
|
/* Tell the hook to proceed. */
|
2004-06-19 21:45:04 +00:00
|
|
|
writeLine(toHook.writeSide, "okay");
|
|
|
|
|
2008-11-12 11:08:27 +00:00
|
|
|
if (printBuildTrace) {
|
|
|
|
printMsg(lvlError, format("@ build-started %1% %2% %3% %4%")
|
|
|
|
% drvPath % drv.outputs["out"].path % drv.platform % logFile);
|
|
|
|
}
|
|
|
|
|
2004-06-19 21:45:04 +00:00
|
|
|
return rpAccept;
|
|
|
|
}
|
|
|
|
|
|
|
|
else throw Error(format("bad hook reply `%1%'") % reply);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-03-19 12:48:45 +00:00
|
|
|
void DerivationGoal::terminateBuildHook(bool kill)
|
2004-06-19 21:45:04 +00:00
|
|
|
{
|
|
|
|
/* !!! drain stdout of hook */
|
|
|
|
debug("terminating build hook");
|
2004-06-22 09:51:44 +00:00
|
|
|
pid_t savedPid = pid;
|
2007-03-19 12:48:45 +00:00
|
|
|
if (kill)
|
|
|
|
pid.kill();
|
|
|
|
else
|
|
|
|
pid.wait(true);
|
2006-12-08 17:26:21 +00:00
|
|
|
/* `false' means don't wake up waiting goals, since we want to
|
2007-03-19 12:48:45 +00:00
|
|
|
keep this build slot ourselves. */
|
2004-07-01 16:24:35 +00:00
|
|
|
worker.childTerminated(savedPid, false);
|
2004-06-19 21:45:04 +00:00
|
|
|
fromHook.readSide.close();
|
|
|
|
toHook.writeSide.close();
|
|
|
|
fdLogFile.close();
|
2007-03-19 12:48:45 +00:00
|
|
|
drain(logPipe.readSide);
|
2004-06-19 21:45:04 +00:00
|
|
|
logPipe.readSide.close();
|
2004-08-05 14:53:27 +00:00
|
|
|
deleteTmpDir(true); /* get rid of the hook's temporary directory */
|
2004-06-19 21:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-28 11:36:17 +00:00
|
|
|
DerivationGoal::PrepareBuildReply DerivationGoal::prepareBuild()
|
2004-06-19 21:45:04 +00:00
|
|
|
{
|
2007-08-28 11:36:17 +00:00
|
|
|
/* Check for the possibility that some other goal in this process
|
|
|
|
has locked the output since we checked in haveDerivation().
|
|
|
|
(It can't happen between here and the lockPaths() call below
|
|
|
|
because we're not allowing multi-threading.) */
|
|
|
|
for (DerivationOutputs::iterator i = drv.outputs.begin();
|
|
|
|
i != drv.outputs.end(); ++i)
|
|
|
|
if (pathIsLockedByMe(i->second.path)) {
|
|
|
|
debug(format("restarting derivation `%1%' because `%2%' is locked by another goal")
|
|
|
|
% drvPath % i->second.path);
|
|
|
|
return prRestart;
|
|
|
|
}
|
|
|
|
|
2003-08-01 14:11:19 +00:00
|
|
|
/* Obtain locks on all output paths. The locks are automatically
|
|
|
|
released when we exit this function or Nix crashes. */
|
2004-05-12 09:35:51 +00:00
|
|
|
/* !!! BUG: this could block, which is not allowed. */
|
2007-08-28 16:22:08 +00:00
|
|
|
/* !!! and once we make this non-blocking, we should carefully
|
|
|
|
consider the case where some but not all locks are required; we
|
|
|
|
should then release the acquired locks so that the other
|
|
|
|
processes and the pathIsLockedByMe() test don't get confused. */
|
2006-06-16 10:13:03 +00:00
|
|
|
outputLocks.lockPaths(outputPaths(drv.outputs),
|
|
|
|
(format("waiting for lock on %1%") % showPaths(outputPaths(drv.outputs))).str());
|
2003-08-01 14:11:19 +00:00
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
/* Now check again whether the outputs are valid. This is because
|
2003-08-01 14:11:19 +00:00
|
|
|
another process may have started building in parallel. After
|
|
|
|
it has finished and released the locks, we can (and should)
|
2005-01-19 11:16:11 +00:00
|
|
|
reuse its results. (Strictly speaking the first check can be
|
|
|
|
omitted, but that would be less efficient.) Note that since we
|
|
|
|
now hold the locks on the output paths, no other process can
|
2005-01-20 16:01:07 +00:00
|
|
|
build this derivation, so no further checks are necessary. */
|
2005-01-25 10:55:33 +00:00
|
|
|
PathSet validPaths = checkPathValidity(true);
|
|
|
|
if (validPaths.size() == drv.outputs.size()) {
|
2005-01-19 11:16:11 +00:00
|
|
|
debug(format("skipping build of derivation `%1%', someone beat us to it")
|
|
|
|
% drvPath);
|
2004-06-18 18:09:32 +00:00
|
|
|
outputLocks.setDeletion(true);
|
2007-08-28 11:36:17 +00:00
|
|
|
return prDone;
|
2003-08-01 14:11:19 +00:00
|
|
|
}
|
2003-07-20 19:29:38 +00:00
|
|
|
|
2005-01-25 10:55:33 +00:00
|
|
|
if (validPaths.size() > 0) {
|
|
|
|
/* !!! fix this; try to delete valid paths */
|
2006-12-08 17:26:21 +00:00
|
|
|
throw BuildError(
|
2005-01-25 10:55:33 +00:00
|
|
|
format("derivation `%1%' is blocked by its output paths")
|
|
|
|
% drvPath);
|
|
|
|
}
|
|
|
|
|
2007-03-07 15:53:11 +00:00
|
|
|
/* If any of the outputs already exist but are not registered,
|
|
|
|
delete them. */
|
|
|
|
for (DerivationOutputs::iterator i = drv.outputs.begin();
|
|
|
|
i != drv.outputs.end(); ++i)
|
|
|
|
{
|
|
|
|
Path path = i->second.path;
|
2008-06-09 13:52:45 +00:00
|
|
|
if (worker.store.isValidPath(path))
|
2007-03-07 15:53:11 +00:00
|
|
|
throw BuildError(format("obstructed build: path `%1%' exists") % path);
|
|
|
|
if (pathExists(path)) {
|
|
|
|
debug(format("removing unregistered path `%1%'") % path);
|
|
|
|
deletePathWrapped(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
/* Gather information necessary for computing the closure and/or
|
|
|
|
running the build hook. */
|
|
|
|
|
|
|
|
/* The outputs are referenceable paths. */
|
2005-01-19 11:16:11 +00:00
|
|
|
for (DerivationOutputs::iterator i = drv.outputs.begin();
|
|
|
|
i != drv.outputs.end(); ++i)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
* Removed the `id' attribute hack.
* Formalise the notion of fixed-output derivations, i.e., derivations
for which a cryptographic hash of the output is known in advance.
Changes to such derivations should not propagate upwards through the
dependency graph. Previously this was done by specifying the hash
component of the output path through the `id' attribute, but this is
insecure since you can lie about it (i.e., you can specify any hash
and then produce a completely different output). Now the
responsibility for checking the output is moved from the builder to
Nix itself.
A fixed-output derivation can be created by specifying the
`outputHash' and `outputHashAlgo' attributes, the latter taking
values `md5', `sha1', and `sha256', and the former specifying the
actual hash in hexadecimal or in base-32 (auto-detected by looking
at the length of the attribute value). MD5 is included for
compatibility but should be considered deprecated.
* Removed the `drvPath' pseudo-attribute in derivation results. It's
no longer necessary.
* Cleaned up the support for multiple output paths in derivation store
expressions. Each output now has a unique identifier (e.g., `out',
`devel', `docs'). Previously there was no way to tell output paths
apart at the store expression level.
* `nix-hash' now has a flag `--base32' to specify that the hash should
be printed in base-32 notation.
* `fetchurl' accepts parameters `sha256' and `sha1' in addition to
`md5'.
* `nix-prefetch-url' now prints out a SHA-1 hash in base-32. (TODO: a
flag to specify the hash.)
2005-01-17 16:55:19 +00:00
|
|
|
debug(format("building path `%1%'") % i->second.path);
|
|
|
|
allPaths.insert(i->second.path);
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
2005-01-19 11:16:11 +00:00
|
|
|
|
|
|
|
/* Determine the full set of input paths. */
|
|
|
|
|
|
|
|
/* First, the input derivations. */
|
2005-01-20 14:10:19 +00:00
|
|
|
for (DerivationInputs::iterator i = drv.inputDrvs.begin();
|
2005-01-19 11:16:11 +00:00
|
|
|
i != drv.inputDrvs.end(); ++i)
|
2003-08-20 14:11:40 +00:00
|
|
|
{
|
2005-01-20 14:10:19 +00:00
|
|
|
/* Add the relevant output closures of the input derivation
|
|
|
|
`*i' as input paths. Only add the closures of output paths
|
|
|
|
that are specified as inputs. */
|
2008-06-09 13:52:45 +00:00
|
|
|
assert(worker.store.isValidPath(i->first));
|
2005-01-20 14:10:19 +00:00
|
|
|
Derivation inDrv = derivationFromPath(i->first);
|
|
|
|
for (StringSet::iterator j = i->second.begin();
|
2005-01-25 10:55:33 +00:00
|
|
|
j != i->second.end(); ++j)
|
2005-01-20 14:10:19 +00:00
|
|
|
if (inDrv.outputs.find(*j) != inDrv.outputs.end())
|
|
|
|
computeFSClosure(inDrv.outputs[*j].path, inputPaths);
|
|
|
|
else
|
2006-12-08 17:26:21 +00:00
|
|
|
throw BuildError(
|
2005-01-20 14:10:19 +00:00
|
|
|
format("derivation `%1%' requires non-existent output `%2%' from input derivation `%3%'")
|
|
|
|
% drvPath % *j % i->first);
|
2003-07-20 19:29:38 +00:00
|
|
|
}
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
/* Second, the input sources. */
|
|
|
|
for (PathSet::iterator i = drv.inputSrcs.begin();
|
|
|
|
i != drv.inputSrcs.end(); ++i)
|
|
|
|
computeFSClosure(*i, inputPaths);
|
|
|
|
|
2005-02-11 16:03:47 +00:00
|
|
|
debug(format("added input paths %1%") % showPaths(inputPaths));
|
|
|
|
|
|
|
|
allPaths.insert(inputPaths.begin(), inputPaths.end());
|
|
|
|
|
2007-08-28 11:36:17 +00:00
|
|
|
return prProceed;
|
2004-05-11 18:05:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-12-11 18:57:10 +00:00
|
|
|
void chmod(const Path & path, mode_t mode)
|
|
|
|
{
|
|
|
|
if (::chmod(path.c_str(), 01777) == -1)
|
|
|
|
throw SysError(format("setting permissions on `%1%'") % path);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
void DerivationGoal::startBuilder()
|
2004-05-11 18:05:44 +00:00
|
|
|
{
|
2004-06-22 14:48:59 +00:00
|
|
|
startNest(nest, lvlInfo,
|
2005-01-19 15:02:02 +00:00
|
|
|
format("building path(s) %1%") % showPaths(outputPaths(drv.outputs)))
|
2004-06-22 14:48:59 +00:00
|
|
|
|
2004-05-12 14:20:32 +00:00
|
|
|
/* Right platform? */
|
2005-01-19 11:16:11 +00:00
|
|
|
if (drv.platform != thisSystem)
|
2004-06-25 10:21:44 +00:00
|
|
|
throw BuildError(
|
|
|
|
format("a `%1%' is required to build `%3%', but I am a `%2%'")
|
2005-01-19 11:16:11 +00:00
|
|
|
% drv.platform % thisSystem % drvPath);
|
2004-05-12 14:20:32 +00:00
|
|
|
|
2004-05-11 18:05:44 +00:00
|
|
|
/* Construct the environment passed to the builder. */
|
|
|
|
typedef map<string, string> Environment;
|
|
|
|
Environment env;
|
|
|
|
|
2003-08-18 14:54:54 +00:00
|
|
|
/* Most shells initialise PATH to some default (/bin:/usr/bin:...) when
|
|
|
|
PATH is not set. We don't want this, so we fill it in with some dummy
|
|
|
|
value. */
|
|
|
|
env["PATH"] = "/path-not-set";
|
|
|
|
|
2003-08-22 20:12:44 +00:00
|
|
|
/* Set HOME to a non-existing path to prevent certain programs from using
|
|
|
|
/etc/passwd (or NIS, or whatever) to locate the home directory (for
|
|
|
|
example, wget looks for ~/.wgetrc). I.e., these tools use /etc/passwd
|
|
|
|
if HOME is not set, but they will just assume that the settings file
|
|
|
|
they are looking for does not exist if HOME is set but points to some
|
|
|
|
non-existing path. */
|
|
|
|
env["HOME"] = "/homeless-shelter";
|
|
|
|
|
2004-03-12 10:45:08 +00:00
|
|
|
/* Tell the builder where the Nix store is. Usually they
|
|
|
|
shouldn't care, but this is useful for purity checking (e.g.,
|
|
|
|
the compiler or linker might only want to accept paths to files
|
|
|
|
in the store or in the build directory). */
|
|
|
|
env["NIX_STORE"] = nixStore;
|
|
|
|
|
2005-01-20 16:01:07 +00:00
|
|
|
/* Add all bindings specified in the derivation. */
|
2005-01-19 11:16:11 +00:00
|
|
|
for (StringPairs::iterator i = drv.env.begin();
|
|
|
|
i != drv.env.end(); ++i)
|
2003-07-20 19:29:38 +00:00
|
|
|
env[i->first] = i->second;
|
|
|
|
|
2004-05-11 18:05:44 +00:00
|
|
|
/* Create a temporary directory where the build will take
|
|
|
|
place. */
|
2008-03-27 13:45:17 +00:00
|
|
|
tmpDir = createTempDir("", "nix-build-" + baseNameOf(drvPath), false, false);
|
2004-05-11 18:05:44 +00:00
|
|
|
|
|
|
|
/* For convenience, set an environment pointing to the top build
|
|
|
|
directory. */
|
2004-06-18 18:09:32 +00:00
|
|
|
env["NIX_BUILD_TOP"] = tmpDir;
|
2004-05-11 18:05:44 +00:00
|
|
|
|
|
|
|
/* Also set TMPDIR and variants to point to this directory. */
|
2004-06-18 18:09:32 +00:00
|
|
|
env["TMPDIR"] = env["TEMPDIR"] = env["TMP"] = env["TEMP"] = tmpDir;
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2008-08-27 17:20:25 +00:00
|
|
|
/* Explicitly set PWD to prevent problems with chroot builds. In
|
|
|
|
particular, dietlibc cannot figure out the cwd because the
|
|
|
|
inode of the current directory doesn't appear in .. (because
|
|
|
|
getdents returns the inode of the mount point). */
|
|
|
|
env["PWD"] = tmpDir;
|
|
|
|
|
2005-02-22 15:23:24 +00:00
|
|
|
/* Compatibility hack with Nix <= 0.7: if this is a fixed-output
|
|
|
|
derivation, tell the builder, so that for instance `fetchurl'
|
|
|
|
can skip checking the output. On older Nixes, this environment
|
|
|
|
variable won't be set, so `fetchurl' will do the check. */
|
2006-05-31 09:51:45 +00:00
|
|
|
bool fixedOutput = true;
|
2005-02-22 15:23:24 +00:00
|
|
|
for (DerivationOutputs::iterator i = drv.outputs.begin();
|
|
|
|
i != drv.outputs.end(); ++i)
|
2006-05-31 09:51:45 +00:00
|
|
|
if (i->second.hash == "") fixedOutput = false;
|
|
|
|
if (fixedOutput)
|
|
|
|
env["NIX_OUTPUT_CHECKED"] = "1";
|
|
|
|
|
|
|
|
/* *Only* if this is a fixed-output derivation, propagate the
|
|
|
|
values of the environment variables specified in the
|
|
|
|
`impureEnvVars' attribute to the builder. This allows for
|
|
|
|
instance environment variables for proxy configuration such as
|
|
|
|
`http_proxy' to be easily passed to downloaders like
|
|
|
|
`fetchurl'. Passing such environment variables from the caller
|
|
|
|
to the builder is generally impure, but the output of
|
|
|
|
fixed-output derivations is by definition pure (since we
|
|
|
|
already know the cryptographic hash of the output). */
|
|
|
|
if (fixedOutput) {
|
|
|
|
Strings varNames = tokenizeString(drv.env["impureEnvVars"]);
|
|
|
|
for (Strings::iterator i = varNames.begin(); i != varNames.end(); ++i)
|
|
|
|
env[*i] = getEnv(*i);
|
|
|
|
}
|
2005-10-17 15:33:24 +00:00
|
|
|
|
2006-11-13 18:18:13 +00:00
|
|
|
/* The `exportReferencesGraph' feature allows the references graph
|
|
|
|
to be passed to a builder. This attribute should be a list of
|
|
|
|
pairs [name1 path1 name2 path2 ...]. The references graph of
|
|
|
|
each `pathN' will be stored in a text file `nameN' in the
|
|
|
|
temporary build directory. The text files have the format used
|
|
|
|
by `nix-store --register-validity'. However, the deriver
|
|
|
|
fields are left empty. */
|
|
|
|
string s = drv.env["exportReferencesGraph"];
|
|
|
|
Strings ss = tokenizeString(s);
|
|
|
|
if (ss.size() % 2 != 0)
|
2006-12-08 17:26:21 +00:00
|
|
|
throw BuildError(format("odd number of tokens in `exportReferencesGraph': `%1%'") % s);
|
2006-11-13 18:18:13 +00:00
|
|
|
for (Strings::iterator i = ss.begin(); i != ss.end(); ) {
|
|
|
|
string fileName = *i++;
|
2007-01-23 16:57:43 +00:00
|
|
|
checkStoreName(fileName); /* !!! abuse of this function */
|
|
|
|
|
|
|
|
/* Check that the store path is valid. */
|
2006-11-13 18:18:13 +00:00
|
|
|
Path storePath = *i++;
|
2007-01-23 16:57:43 +00:00
|
|
|
if (!isInStore(storePath))
|
|
|
|
throw BuildError(format("`exportReferencesGraph' contains a non-store path `%1%'")
|
|
|
|
% storePath);
|
|
|
|
storePath = toStorePath(storePath);
|
2008-06-09 13:52:45 +00:00
|
|
|
if (!worker.store.isValidPath(storePath))
|
2007-01-23 16:57:43 +00:00
|
|
|
throw BuildError(format("`exportReferencesGraph' contains an invalid path `%1%'")
|
2006-11-13 18:18:13 +00:00
|
|
|
% storePath);
|
2007-01-23 16:57:43 +00:00
|
|
|
|
|
|
|
/* Write closure info to `fileName'. */
|
2006-11-13 18:18:13 +00:00
|
|
|
PathSet refs;
|
|
|
|
computeFSClosure(storePath, refs);
|
|
|
|
/* !!! in secure Nix, the writing should be done on the
|
|
|
|
build uid for security (maybe). */
|
|
|
|
writeStringToFile(tmpDir + "/" + fileName,
|
2008-01-29 18:17:36 +00:00
|
|
|
makeValidityRegistration(refs, false, false));
|
2006-11-13 18:18:13 +00:00
|
|
|
}
|
|
|
|
|
2007-12-30 09:30:56 +00:00
|
|
|
// The same for derivations
|
2008-08-25 15:49:22 +00:00
|
|
|
// !!! urgh, cut&paste duplication
|
2007-12-30 09:30:56 +00:00
|
|
|
s = drv.env["exportBuildReferencesGraph"];
|
|
|
|
ss = tokenizeString(s);
|
|
|
|
if (ss.size() % 2 != 0)
|
2008-01-15 04:32:08 +00:00
|
|
|
throw BuildError(format("odd number of tokens in `exportBuildReferencesGraph': `%1%'") % s);
|
2007-12-30 09:30:56 +00:00
|
|
|
for (Strings::iterator i = ss.begin(); i != ss.end(); ) {
|
|
|
|
string fileName = *i++;
|
|
|
|
checkStoreName(fileName); /* !!! abuse of this function */
|
|
|
|
|
|
|
|
/* Check that the store path is valid. */
|
|
|
|
Path storePath = *i++;
|
|
|
|
if (!isInStore(storePath))
|
2008-01-15 04:32:08 +00:00
|
|
|
throw BuildError(format("`exportBuildReferencesGraph' contains a non-store path `%1%'")
|
2007-12-30 09:30:56 +00:00
|
|
|
% storePath);
|
|
|
|
storePath = toStorePath(storePath);
|
2008-06-09 13:52:45 +00:00
|
|
|
if (!worker.store.isValidPath(storePath))
|
2008-01-15 04:32:08 +00:00
|
|
|
throw BuildError(format("`exportBuildReferencesGraph' contains an invalid path `%1%'")
|
2007-12-30 09:30:56 +00:00
|
|
|
% storePath);
|
|
|
|
|
|
|
|
/* Write closure info to `fileName'. */
|
|
|
|
PathSet refs1,refs;
|
|
|
|
computeFSClosure(storePath, refs1);
|
|
|
|
for (PathSet::iterator j = refs1.begin(); j != refs1.end() ; j++) {
|
|
|
|
refs.insert (*j);
|
|
|
|
if (isDerivation (*j)) {
|
|
|
|
Derivation deriv = derivationFromPath (*j);
|
|
|
|
for (DerivationOutputs::iterator k=deriv.outputs.begin();
|
|
|
|
k != deriv.outputs.end(); k++) {
|
|
|
|
refs.insert(k->second.path);
|
2008-01-15 04:32:08 +00:00
|
|
|
|
2007-12-30 09:30:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* !!! in secure Nix, the writing should be done on the
|
|
|
|
build uid for security (maybe). */
|
|
|
|
writeStringToFile(tmpDir + "/" + fileName,
|
2008-01-29 18:17:36 +00:00
|
|
|
makeValidityRegistration(refs, false, false));
|
2007-12-30 09:30:56 +00:00
|
|
|
}
|
|
|
|
|
2006-05-31 09:51:45 +00:00
|
|
|
|
2006-12-06 20:00:15 +00:00
|
|
|
/* If `build-users-group' is not empty, then we have to build as
|
|
|
|
one of the members of that group. */
|
2006-12-07 15:54:52 +00:00
|
|
|
if (haveBuildUsers()) {
|
2005-10-20 16:58:34 +00:00
|
|
|
buildUser.acquire();
|
|
|
|
assert(buildUser.getUID() != 0);
|
2006-12-06 20:00:15 +00:00
|
|
|
assert(buildUser.getGID() != 0);
|
2005-10-17 17:35:37 +00:00
|
|
|
|
|
|
|
/* Make sure that no other processes are executing under this
|
|
|
|
uid. */
|
2006-12-07 16:33:31 +00:00
|
|
|
buildUser.kill();
|
2005-10-17 15:33:24 +00:00
|
|
|
|
2006-12-07 00:16:07 +00:00
|
|
|
/* Change ownership of the temporary build directory, if we're
|
|
|
|
root. If we're not root, then the setuid helper will do it
|
|
|
|
just before it starts the builder. */
|
|
|
|
if (amPrivileged()) {
|
|
|
|
if (chown(tmpDir.c_str(), buildUser.getUID(), buildUser.getGID()) == -1)
|
|
|
|
throw SysError(format("cannot change ownership of `%1%'") % tmpDir);
|
|
|
|
}
|
2005-10-17 16:52:29 +00:00
|
|
|
|
|
|
|
/* Check that the Nix store has the appropriate permissions,
|
2006-12-06 20:00:15 +00:00
|
|
|
i.e., owned by root and mode 1775 (sticky bit on so that
|
2005-10-17 16:52:29 +00:00
|
|
|
the builder can create its output but not mess with the
|
|
|
|
outputs of other processes). */
|
|
|
|
struct stat st;
|
|
|
|
if (stat(nixStore.c_str(), &st) == -1)
|
|
|
|
throw SysError(format("cannot stat `%1%'") % nixStore);
|
|
|
|
if (!(st.st_mode & S_ISVTX) ||
|
2006-12-03 15:32:38 +00:00
|
|
|
((st.st_mode & S_IRWXG) != S_IRWXG) ||
|
2006-12-06 20:00:15 +00:00
|
|
|
(st.st_gid != buildUser.getGID()))
|
2005-10-17 16:52:29 +00:00
|
|
|
throw Error(format(
|
2006-12-07 00:16:07 +00:00
|
|
|
"builder does not have write permission to `%2%'; "
|
2006-12-03 15:32:38 +00:00
|
|
|
"try `chgrp %1% %2%; chmod 1775 %2%'")
|
2006-12-06 20:00:15 +00:00
|
|
|
% buildUser.getGID() % nixStore);
|
2005-10-17 15:33:24 +00:00
|
|
|
}
|
|
|
|
|
2007-10-27 00:46:59 +00:00
|
|
|
|
2008-08-25 15:49:22 +00:00
|
|
|
/* Are we doing a chroot build? Note that fixed-output
|
|
|
|
derivations are never done in a chroot, mainly so that
|
|
|
|
functions like fetchurl (which needs a proper /etc/resolv.conf)
|
|
|
|
work properly. Purity checking for fixed-output derivations
|
|
|
|
is somewhat pointless anyway. */
|
2007-10-27 00:46:59 +00:00
|
|
|
useChroot = queryBoolSetting("build-use-chroot", false);
|
2008-12-11 17:00:12 +00:00
|
|
|
Paths dirsInChroot;
|
2007-10-27 16:51:55 +00:00
|
|
|
|
2008-08-25 15:49:22 +00:00
|
|
|
if (fixedOutput) useChroot = false;
|
|
|
|
|
2007-10-27 00:46:59 +00:00
|
|
|
if (useChroot) {
|
2007-10-27 16:51:55 +00:00
|
|
|
#if CHROOT_ENABLED
|
2007-10-27 16:06:38 +00:00
|
|
|
/* Create a temporary directory in which we set up the chroot
|
2008-12-11 17:00:12 +00:00
|
|
|
environment using bind-mounts. */
|
2008-12-11 17:52:34 +00:00
|
|
|
chrootRootDir = createTempDir("", "nix-chroot");
|
2007-10-27 16:06:38 +00:00
|
|
|
|
2008-12-11 17:00:12 +00:00
|
|
|
/* Clean up the chroot directory automatically. */
|
|
|
|
autoDelChroot = boost::shared_ptr<AutoDelete>(new AutoDelete(chrootRootDir));
|
2007-10-27 00:46:59 +00:00
|
|
|
|
2008-08-27 16:03:03 +00:00
|
|
|
printMsg(lvlChatty, format("setting up chroot environment in `%1%'") % chrootRootDir);
|
2007-10-27 00:46:59 +00:00
|
|
|
|
2008-08-27 16:03:03 +00:00
|
|
|
/* Create a writable /tmp in the chroot. Many builders need
|
|
|
|
this. (Of course they should really respect $TMPDIR
|
|
|
|
instead.) */
|
|
|
|
Path chrootTmpDir = chrootRootDir + "/tmp";
|
|
|
|
createDirs(chrootTmpDir);
|
2008-12-11 18:57:10 +00:00
|
|
|
chmod(chrootTmpDir, 01777);
|
2008-08-27 16:03:03 +00:00
|
|
|
|
2008-12-11 17:44:02 +00:00
|
|
|
/* Create a /etc/passwd with entries for the build user and
|
|
|
|
the nobody account. The latter is kind of a hack to
|
|
|
|
support Samba-in-QEMU. */
|
|
|
|
createDirs(chrootRootDir + "/etc");
|
|
|
|
|
|
|
|
writeStringToFile(chrootRootDir + "/etc/passwd",
|
|
|
|
(format(
|
|
|
|
"nixbld:x:%1%:65534:Nix build user:/:/noshell\n"
|
|
|
|
"nobody:x:65534:65534:Nobody:/:/noshell\n")
|
|
|
|
% (buildUser.enabled() ? buildUser.getUID() : getuid())).str());
|
|
|
|
|
2008-08-27 16:03:03 +00:00
|
|
|
/* Bind-mount a user-configurable set of directories from the
|
2008-11-11 14:59:20 +00:00
|
|
|
host file system. The `/dev/pts' directory must be mounted
|
|
|
|
separately so that newly-created pseudo-terminals show
|
|
|
|
up. */
|
2007-10-27 00:46:59 +00:00
|
|
|
Paths defaultDirs;
|
|
|
|
defaultDirs.push_back("/dev");
|
2008-10-16 21:04:32 +00:00
|
|
|
defaultDirs.push_back("/dev/pts");
|
2007-10-27 00:46:59 +00:00
|
|
|
defaultDirs.push_back("/proc");
|
2008-11-11 14:59:20 +00:00
|
|
|
|
2008-12-11 17:00:12 +00:00
|
|
|
dirsInChroot = querySetting("build-chroot-dirs", defaultDirs);
|
2007-10-27 00:46:59 +00:00
|
|
|
|
|
|
|
dirsInChroot.push_front(tmpDir);
|
2008-12-11 18:57:10 +00:00
|
|
|
|
|
|
|
/* Make the closure of the inputs available in the chroot,
|
|
|
|
rather than the whole Nix store. This prevents any access
|
|
|
|
to undeclared dependencies. Directories are bind-mounted,
|
|
|
|
while other inputs are hard-linked (since only directories
|
|
|
|
can be bind-mounted). !!! As an extra security
|
|
|
|
precaution, make the fake Nix store only writable by the
|
|
|
|
build user. */
|
|
|
|
createDirs(chrootRootDir + nixStore);
|
|
|
|
chmod(chrootRootDir + nixStore, 01777);
|
|
|
|
|
|
|
|
foreach (PathSet::iterator, i, inputPaths) {
|
|
|
|
struct stat st;
|
|
|
|
if (lstat(i->c_str(), &st))
|
|
|
|
throw SysError(format("getting attributes of path `%1%'") % *i);
|
|
|
|
if (S_ISDIR(st.st_mode))
|
|
|
|
dirsInChroot.push_back(*i);
|
|
|
|
else {
|
|
|
|
Path p = chrootRootDir + *i;
|
|
|
|
if (link(i->c_str(), p.c_str()) == -1)
|
|
|
|
throw SysError(format("linking `%1%' to `%2%'") % p % *i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-10-27 16:51:55 +00:00
|
|
|
#else
|
|
|
|
throw Error("chroot builds are not supported on this platform");
|
|
|
|
#endif
|
2007-10-27 00:46:59 +00:00
|
|
|
}
|
|
|
|
|
2005-10-17 15:33:24 +00:00
|
|
|
|
2004-05-11 18:05:44 +00:00
|
|
|
/* Run the builder. */
|
|
|
|
printMsg(lvlChatty, format("executing builder `%1%'") %
|
2005-01-19 11:16:11 +00:00
|
|
|
drv.builder);
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2004-05-13 19:14:49 +00:00
|
|
|
/* Create the log file and pipe. */
|
2008-11-12 11:08:27 +00:00
|
|
|
Path logFile = openLogFile();
|
2004-05-13 19:14:49 +00:00
|
|
|
|
2004-05-11 18:05:44 +00:00
|
|
|
/* Fork a child to build the package. Note that while we
|
|
|
|
currently use forks to run and wait for the children, it
|
|
|
|
shouldn't be hard to use threads for this on systems where
|
|
|
|
fork() is unavailable or inefficient. */
|
2004-06-22 09:51:44 +00:00
|
|
|
pid = fork();
|
|
|
|
switch (pid) {
|
2004-05-11 18:05:44 +00:00
|
|
|
|
|
|
|
case -1:
|
|
|
|
throw SysError("unable to fork");
|
|
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
|
|
/* Warning: in the child we should absolutely not make any
|
|
|
|
Berkeley DB calls! */
|
|
|
|
|
|
|
|
try { /* child */
|
|
|
|
|
2007-10-27 16:51:55 +00:00
|
|
|
#if CHROOT_ENABLED
|
2008-12-11 17:00:12 +00:00
|
|
|
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;
|
2008-12-11 18:57:10 +00:00
|
|
|
debug(format("bind mounting `%1%' to `%2%'") % source % target);
|
2008-12-11 17:00:12 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2007-10-27 16:51:55 +00:00
|
|
|
#endif
|
2007-10-27 00:46:59 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
initChild();
|
2004-05-11 18:05:44 +00:00
|
|
|
|
|
|
|
/* Fill in the environment. */
|
|
|
|
Strings envStrs;
|
|
|
|
for (Environment::const_iterator i = env.begin();
|
2004-06-22 10:21:44 +00:00
|
|
|
i != env.end(); ++i)
|
|
|
|
envStrs.push_back(i->first + "=" + i->second);
|
|
|
|
const char * * envArr = strings2CharPtrs(envStrs);
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2006-12-07 00:42:30 +00:00
|
|
|
Path program = drv.builder.c_str();
|
|
|
|
std::vector<const char *> args; /* careful with c_str()! */
|
2006-12-07 11:27:32 +00:00
|
|
|
string user; /* must be here for its c_str()! */
|
2006-12-07 00:42:30 +00:00
|
|
|
|
2006-12-03 15:32:38 +00:00
|
|
|
/* If we are running in `build-users' mode, then switch to
|
|
|
|
the user we allocated above. Make sure that we drop
|
|
|
|
all root privileges. Note that initChild() above has
|
|
|
|
closed all file descriptors except std*, so that's
|
|
|
|
safe. Also note that setuid() when run as root sets
|
|
|
|
the real, effective and saved UIDs. */
|
2006-12-07 00:42:30 +00:00
|
|
|
if (buildUser.enabled()) {
|
2008-12-11 18:57:10 +00:00
|
|
|
printMsg(lvlChatty, format("switching to user `%1%'") % buildUser.getUser());
|
2006-12-07 00:42:30 +00:00
|
|
|
|
|
|
|
if (amPrivileged()) {
|
|
|
|
|
|
|
|
if (setgroups(0, 0) == -1)
|
|
|
|
throw SysError("cannot clear the set of supplementary groups");
|
2005-10-17 15:33:24 +00:00
|
|
|
|
2006-12-07 00:42:30 +00:00
|
|
|
if (setgid(buildUser.getGID()) == -1 ||
|
|
|
|
getgid() != buildUser.getGID() ||
|
|
|
|
getegid() != buildUser.getGID())
|
|
|
|
throw SysError("setgid failed");
|
|
|
|
|
|
|
|
if (setuid(buildUser.getUID()) == -1 ||
|
|
|
|
getuid() != buildUser.getUID() ||
|
|
|
|
geteuid() != buildUser.getUID())
|
|
|
|
throw SysError("setuid failed");
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/* Let the setuid helper take care of it. */
|
|
|
|
program = nixLibexecDir + "/nix-setuid-helper";
|
|
|
|
args.push_back(program.c_str());
|
|
|
|
args.push_back("run-builder");
|
2006-12-07 11:27:32 +00:00
|
|
|
user = buildUser.getUser().c_str();
|
|
|
|
args.push_back(user.c_str());
|
2006-12-07 00:42:30 +00:00
|
|
|
args.push_back(drv.builder.c_str());
|
|
|
|
}
|
2005-10-17 15:33:24 +00:00
|
|
|
}
|
|
|
|
|
2006-12-07 00:42:30 +00:00
|
|
|
/* Fill in the arguments. */
|
|
|
|
string builderBasename = baseNameOf(drv.builder);
|
|
|
|
args.push_back(builderBasename.c_str());
|
|
|
|
for (Strings::iterator i = drv.args.begin();
|
|
|
|
i != drv.args.end(); ++i)
|
|
|
|
args.push_back(i->c_str());
|
|
|
|
args.push_back(0);
|
|
|
|
|
2008-11-14 15:46:45 +00:00
|
|
|
restoreSIGPIPE();
|
|
|
|
|
2004-05-11 18:05:44 +00:00
|
|
|
/* Execute the program. This should not return. */
|
2006-12-07 00:42:30 +00:00
|
|
|
execve(program.c_str(), (char * *) &args[0], (char * *) envArr);
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2004-05-13 19:14:49 +00:00
|
|
|
throw SysError(format("executing `%1%'")
|
2005-01-19 11:16:11 +00:00
|
|
|
% drv.builder);
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2006-09-04 21:06:23 +00:00
|
|
|
} catch (std::exception & e) {
|
2007-10-27 00:46:59 +00:00
|
|
|
std::cerr << format("build error: %1%") % e.what() << std::endl;
|
2003-07-20 19:29:38 +00:00
|
|
|
}
|
2006-07-20 12:17:25 +00:00
|
|
|
quickExit(1);
|
2004-05-13 19:14:49 +00:00
|
|
|
}
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2005-10-17 15:33:24 +00:00
|
|
|
|
2004-05-13 19:14:49 +00:00
|
|
|
/* parent */
|
2004-06-22 09:51:44 +00:00
|
|
|
pid.setSeparatePG(true);
|
2004-06-18 18:09:32 +00:00
|
|
|
logPipe.writeSide.close();
|
2005-10-17 16:52:29 +00:00
|
|
|
worker.childStarted(shared_from_this(), pid,
|
|
|
|
singleton<set<int> >(logPipe.readSide), true);
|
2008-11-12 11:08:27 +00:00
|
|
|
|
|
|
|
if (printBuildTrace) {
|
|
|
|
printMsg(lvlError, format("@ build-started %1% %2% %3% %4%")
|
|
|
|
% drvPath % drv.outputs["out"].path % drv.platform % logFile);
|
|
|
|
}
|
2004-05-13 22:52:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-10-19 16:09:24 +00:00
|
|
|
/* Parse a list of reference specifiers. Each element must either be
|
|
|
|
a store path, or the symbolic name of the output of the derivation
|
|
|
|
(such as `out'). */
|
|
|
|
PathSet parseReferenceSpecifiers(const Derivation & drv, string attr)
|
|
|
|
{
|
|
|
|
PathSet result;
|
|
|
|
Paths paths = tokenizeString(attr);
|
|
|
|
for (Strings::iterator i = paths.begin(); i != paths.end(); ++i) {
|
|
|
|
if (isStorePath(*i))
|
|
|
|
result.insert(*i);
|
|
|
|
else if (drv.outputs.find(*i) != drv.outputs.end())
|
|
|
|
result.insert(drv.outputs.find(*i)->second.path);
|
2006-12-08 17:26:21 +00:00
|
|
|
else throw BuildError(
|
2006-10-19 16:09:24 +00:00
|
|
|
format("derivation contains an illegal reference specifier `%1%'")
|
|
|
|
% *i);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
void DerivationGoal::computeClosure()
|
2004-05-18 14:52:35 +00:00
|
|
|
{
|
2005-01-19 11:16:11 +00:00
|
|
|
map<Path, PathSet> allReferences;
|
2005-01-19 16:39:47 +00:00
|
|
|
map<Path, Hash> contentHashes;
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
/* Check whether the output paths were created, and grep each
|
|
|
|
output path to determine what other paths it references. Also make all
|
|
|
|
output paths read-only. */
|
2005-01-19 11:16:11 +00:00
|
|
|
for (DerivationOutputs::iterator i = drv.outputs.begin();
|
|
|
|
i != drv.outputs.end(); ++i)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
* Removed the `id' attribute hack.
* Formalise the notion of fixed-output derivations, i.e., derivations
for which a cryptographic hash of the output is known in advance.
Changes to such derivations should not propagate upwards through the
dependency graph. Previously this was done by specifying the hash
component of the output path through the `id' attribute, but this is
insecure since you can lie about it (i.e., you can specify any hash
and then produce a completely different output). Now the
responsibility for checking the output is moved from the builder to
Nix itself.
A fixed-output derivation can be created by specifying the
`outputHash' and `outputHashAlgo' attributes, the latter taking
values `md5', `sha1', and `sha256', and the former specifying the
actual hash in hexadecimal or in base-32 (auto-detected by looking
at the length of the attribute value). MD5 is included for
compatibility but should be considered deprecated.
* Removed the `drvPath' pseudo-attribute in derivation results. It's
no longer necessary.
* Cleaned up the support for multiple output paths in derivation store
expressions. Each output now has a unique identifier (e.g., `out',
`devel', `docs'). Previously there was no way to tell output paths
apart at the store expression level.
* `nix-hash' now has a flag `--base32' to specify that the hash should
be printed in base-32 notation.
* `fetchurl' accepts parameters `sha256' and `sha1' in addition to
`md5'.
* `nix-prefetch-url' now prints out a SHA-1 hash in base-32. (TODO: a
flag to specify the hash.)
2005-01-17 16:55:19 +00:00
|
|
|
Path path = i->second.path;
|
2004-06-25 10:21:44 +00:00
|
|
|
if (!pathExists(path)) {
|
|
|
|
throw BuildError(
|
2004-07-06 11:21:34 +00:00
|
|
|
format("builder for `%1%' failed to produce output path `%2%'")
|
2005-01-19 11:16:11 +00:00
|
|
|
% drvPath % path);
|
2004-06-25 10:21:44 +00:00
|
|
|
}
|
2004-05-18 14:52:35 +00:00
|
|
|
|
2005-10-17 16:59:25 +00:00
|
|
|
struct stat st;
|
2006-12-08 17:26:21 +00:00
|
|
|
if (lstat(path.c_str(), &st) == -1)
|
2005-10-17 16:59:25 +00:00
|
|
|
throw SysError(format("getting attributes of path `%1%'") % path);
|
|
|
|
|
2005-04-11 08:07:41 +00:00
|
|
|
startNest(nest, lvlTalkative,
|
|
|
|
format("scanning for references inside `%1%'") % path);
|
|
|
|
|
2005-01-17 19:01:48 +00:00
|
|
|
/* Check that fixed-output derivations produced the right
|
|
|
|
outputs (i.e., the content hash should match the specified
|
|
|
|
hash). */
|
|
|
|
if (i->second.hash != "") {
|
2005-02-22 21:14:41 +00:00
|
|
|
|
|
|
|
bool recursive = false;
|
|
|
|
string algo = i->second.hashAlgo;
|
|
|
|
|
|
|
|
if (string(algo, 0, 2) == "r:") {
|
|
|
|
recursive = true;
|
|
|
|
algo = string(algo, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!recursive) {
|
|
|
|
/* The output path should be a regular file without
|
|
|
|
execute permission. */
|
|
|
|
if (!S_ISREG(st.st_mode) || (st.st_mode & S_IXUSR) != 0)
|
2006-12-08 17:26:21 +00:00
|
|
|
throw BuildError(
|
2005-02-22 21:14:41 +00:00
|
|
|
format("output path `%1% should be a non-executable regular file")
|
|
|
|
% path);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the hash. */
|
|
|
|
HashType ht = parseHashType(algo);
|
2005-01-17 19:01:48 +00:00
|
|
|
if (ht == htUnknown)
|
2006-12-08 17:26:21 +00:00
|
|
|
throw BuildError(format("unknown hash algorithm `%1%'") % algo);
|
2005-01-17 19:01:48 +00:00
|
|
|
Hash h = parseHash(ht, i->second.hash);
|
2005-02-22 21:14:41 +00:00
|
|
|
Hash h2 = recursive ? hashPath(ht, path) : hashFile(ht, path);
|
2005-01-17 19:01:48 +00:00
|
|
|
if (h != h2)
|
2006-12-08 17:26:21 +00:00
|
|
|
throw BuildError(
|
2005-05-10 14:21:46 +00:00
|
|
|
format("output path `%1%' should have %2% hash `%3%', instead has `%4%'")
|
2005-02-22 21:14:41 +00:00
|
|
|
% path % algo % printHash(h) % printHash(h2));
|
2005-01-17 19:01:48 +00:00
|
|
|
}
|
|
|
|
|
2005-10-17 16:59:25 +00:00
|
|
|
/* Get rid of all weird permissions. */
|
2004-11-29 15:09:29 +00:00
|
|
|
canonicalisePathMetaData(path);
|
2004-05-13 19:14:49 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
/* For this output path, find the references to other paths contained
|
|
|
|
in it. */
|
2006-11-13 18:19:05 +00:00
|
|
|
PathSet references = scanForReferences(path, allPaths);
|
|
|
|
|
|
|
|
/* For debugging, print out the referenced and unreferenced
|
|
|
|
paths. */
|
|
|
|
for (PathSet::iterator i = inputPaths.begin();
|
|
|
|
i != inputPaths.end(); ++i)
|
|
|
|
{
|
|
|
|
PathSet::iterator j = references.find(*i);
|
|
|
|
if (j == references.end())
|
|
|
|
debug(format("unreferenced input: `%1%'") % *i);
|
|
|
|
else
|
|
|
|
debug(format("referenced input: `%1%'") % *i);
|
2004-05-13 19:14:49 +00:00
|
|
|
}
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
allReferences[path] = references;
|
2005-01-19 16:39:47 +00:00
|
|
|
|
2006-10-19 16:09:24 +00:00
|
|
|
/* If the derivation specifies an `allowedReferences'
|
|
|
|
attribute (containing a list of paths that the output may
|
|
|
|
refer to), check that all references are in that list. !!!
|
|
|
|
allowedReferences should really be per-output. */
|
|
|
|
if (drv.env.find("allowedReferences") != drv.env.end()) {
|
|
|
|
PathSet allowed = parseReferenceSpecifiers(drv, drv.env["allowedReferences"]);
|
|
|
|
for (PathSet::iterator i = references.begin(); i != references.end(); ++i)
|
|
|
|
if (allowed.find(*i) == allowed.end())
|
2006-12-08 17:26:21 +00:00
|
|
|
throw BuildError(format("output is not allowed to refer to path `%1%'") % *i);
|
2006-10-19 16:09:24 +00:00
|
|
|
}
|
|
|
|
|
2005-01-19 16:39:47 +00:00
|
|
|
/* Hash the contents of the path. The hash is stored in the
|
|
|
|
database so that we can verify later on whether nobody has
|
|
|
|
messed with the store. !!! inefficient: it would be nice
|
|
|
|
if we could combine this with filterReferences(). */
|
|
|
|
contentHashes[path] = hashPath(htSHA256, path);
|
2004-05-18 14:52:35 +00:00
|
|
|
}
|
2004-05-13 19:14:49 +00:00
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
/* Register each output path as valid, and register the sets of
|
2008-06-09 13:52:45 +00:00
|
|
|
paths referenced by each of them. !!! this should be
|
|
|
|
atomic so that either all paths are registered as valid, or
|
|
|
|
none are. */
|
2005-01-19 11:16:11 +00:00
|
|
|
for (DerivationOutputs::iterator i = drv.outputs.begin();
|
|
|
|
i != drv.outputs.end(); ++i)
|
|
|
|
{
|
2008-06-09 13:52:45 +00:00
|
|
|
worker.store.registerValidPath(i->second.path,
|
2005-01-25 21:28:25 +00:00
|
|
|
contentHashes[i->second.path],
|
2005-02-07 13:40:40 +00:00
|
|
|
allReferences[i->second.path],
|
|
|
|
drvPath);
|
2005-01-19 11:16:11 +00:00
|
|
|
}
|
2004-05-13 19:14:49 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
/* It is now safe to delete the lock files, since all future
|
2005-01-19 11:16:11 +00:00
|
|
|
lockers will see that the output paths are valid; they will not
|
|
|
|
create new lock files with the same names as the old (unlinked)
|
|
|
|
lock files. */
|
2004-06-18 18:09:32 +00:00
|
|
|
outputLocks.setDeletion(true);
|
2004-05-13 22:52:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-10-28 16:33:54 +00:00
|
|
|
string drvsLogDir = "drvs";
|
2005-03-24 17:46:38 +00:00
|
|
|
|
|
|
|
|
2008-11-12 11:08:27 +00:00
|
|
|
Path DerivationGoal::openLogFile()
|
2004-05-13 19:14:49 +00:00
|
|
|
{
|
|
|
|
/* Create a log file. */
|
2005-03-24 17:46:38 +00:00
|
|
|
Path dir = (format("%1%/%2%") % nixLogDir % drvsLogDir).str();
|
|
|
|
createDirs(dir);
|
|
|
|
|
|
|
|
Path logFileName = (format("%1%/%2%") % dir % baseNameOf(drvPath)).str();
|
2004-06-18 18:09:32 +00:00
|
|
|
fdLogFile = open(logFileName.c_str(),
|
2004-05-13 19:14:49 +00:00
|
|
|
O_CREAT | O_WRONLY | O_TRUNC, 0666);
|
2004-06-18 18:09:32 +00:00
|
|
|
if (fdLogFile == -1)
|
2004-05-13 19:14:49 +00:00
|
|
|
throw SysError(format("creating log file `%1%'") % logFileName);
|
|
|
|
|
|
|
|
/* Create a pipe to get the output of the child. */
|
2004-06-18 18:09:32 +00:00
|
|
|
logPipe.create();
|
2008-11-12 11:08:27 +00:00
|
|
|
|
|
|
|
return logFileName;
|
2004-05-13 19:14:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
void DerivationGoal::initChild()
|
2004-05-13 19:14:49 +00:00
|
|
|
{
|
2004-06-22 10:21:44 +00:00
|
|
|
commonChildInit(logPipe);
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
if (chdir(tmpDir.c_str()) == -1)
|
2004-06-25 10:21:44 +00:00
|
|
|
throw SysError(format("changing into `%1%'") % tmpDir);
|
2004-05-13 19:14:49 +00:00
|
|
|
|
|
|
|
/* When running a hook, dup the communication pipes. */
|
2004-06-18 18:09:32 +00:00
|
|
|
bool inHook = fromHook.writeSide.isOpen();
|
2004-05-13 19:14:49 +00:00
|
|
|
if (inHook) {
|
2004-06-18 18:09:32 +00:00
|
|
|
fromHook.readSide.close();
|
|
|
|
if (dup2(fromHook.writeSide, 3) == -1)
|
2004-06-25 10:21:44 +00:00
|
|
|
throw SysError("dupping from-hook write side");
|
2004-05-13 19:14:49 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
toHook.writeSide.close();
|
|
|
|
if (dup2(toHook.readSide, 4) == -1)
|
2004-06-25 10:21:44 +00:00
|
|
|
throw SysError("dupping to-hook read side");
|
2004-05-13 19:14:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Close all other file descriptors. */
|
2008-08-02 12:54:35 +00:00
|
|
|
set<int> exceptions;
|
|
|
|
if (inHook) {
|
|
|
|
exceptions.insert(3);
|
|
|
|
exceptions.insert(4);
|
|
|
|
}
|
|
|
|
closeMostFDs(exceptions);
|
2004-05-13 19:14:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
void DerivationGoal::deleteTmpDir(bool force)
|
2004-05-13 19:14:49 +00:00
|
|
|
{
|
2004-06-18 18:09:32 +00:00
|
|
|
if (tmpDir != "") {
|
2006-12-07 23:27:40 +00:00
|
|
|
if (keepFailed && !force) {
|
2004-06-22 14:48:59 +00:00
|
|
|
printMsg(lvlError,
|
2004-06-18 18:09:32 +00:00
|
|
|
format("builder for `%1%' failed; keeping build directory `%2%'")
|
2005-01-19 11:16:11 +00:00
|
|
|
% drvPath % tmpDir);
|
2006-12-07 23:27:40 +00:00
|
|
|
if (buildUser.enabled() && !amPrivileged())
|
|
|
|
getOwnership(tmpDir);
|
|
|
|
}
|
2004-06-18 18:09:32 +00:00
|
|
|
else
|
2006-12-07 14:14:35 +00:00
|
|
|
deletePathWrapped(tmpDir);
|
2004-06-18 18:09:32 +00:00
|
|
|
tmpDir = "";
|
|
|
|
}
|
2004-05-11 18:05:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-10-17 15:33:24 +00:00
|
|
|
void DerivationGoal::handleChildOutput(int fd, const string & data)
|
2004-06-29 09:41:50 +00:00
|
|
|
{
|
2005-10-17 15:33:24 +00:00
|
|
|
if (fd == logPipe.readSide) {
|
|
|
|
if (verbosity >= buildVerbosity)
|
2006-12-03 02:08:13 +00:00
|
|
|
writeToStderr((unsigned char *) data.c_str(), data.size());
|
2005-10-17 15:33:24 +00:00
|
|
|
writeFull(fdLogFile, (unsigned char *) data.c_str(), data.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
else abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DerivationGoal::handleEOF(int fd)
|
|
|
|
{
|
|
|
|
if (fd == logPipe.readSide) worker.wakeUp(shared_from_this());
|
2004-06-29 09:41:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-01-25 10:55:33 +00:00
|
|
|
PathSet DerivationGoal::checkPathValidity(bool returnValid)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
2005-01-25 10:55:33 +00:00
|
|
|
PathSet result;
|
2005-01-19 11:16:11 +00:00
|
|
|
for (DerivationOutputs::iterator i = drv.outputs.begin();
|
|
|
|
i != drv.outputs.end(); ++i)
|
2008-06-09 13:52:45 +00:00
|
|
|
if (worker.store.isValidPath(i->second.path)) {
|
2005-01-25 10:55:33 +00:00
|
|
|
if (returnValid) result.insert(i->second.path);
|
|
|
|
} else {
|
|
|
|
if (!returnValid) result.insert(i->second.path);
|
|
|
|
}
|
|
|
|
return result;
|
2004-06-28 10:42:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-07-20 19:29:38 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
//////////////////////////////////////////////////////////////////////
|
2003-07-20 19:29:38 +00:00
|
|
|
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
class SubstitutionGoal : public Goal
|
|
|
|
{
|
2007-08-12 00:29:28 +00:00
|
|
|
friend class Worker;
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
private:
|
|
|
|
/* The store path that should be realised through a substitute. */
|
|
|
|
Path storePath;
|
2003-08-20 11:30:45 +00:00
|
|
|
|
2007-08-12 00:29:28 +00:00
|
|
|
/* The remaining substituters. */
|
|
|
|
Paths subs;
|
2004-06-20 19:17:54 +00:00
|
|
|
|
2007-08-12 00:29:28 +00:00
|
|
|
/* The current substituter. */
|
|
|
|
Path sub;
|
2004-06-20 19:17:54 +00:00
|
|
|
|
2008-08-02 12:54:35 +00:00
|
|
|
/* Path info returned by the substituter's query info operation. */
|
|
|
|
SubstitutablePathInfo info;
|
2005-01-25 17:08:52 +00:00
|
|
|
|
2004-06-20 19:17:54 +00:00
|
|
|
/* Pipe for the substitute's standard output/error. */
|
|
|
|
Pipe logPipe;
|
|
|
|
|
|
|
|
/* The process ID of the builder. */
|
2004-06-22 09:51:44 +00:00
|
|
|
Pid pid;
|
2004-06-20 19:17:54 +00:00
|
|
|
|
2004-06-21 09:35:50 +00:00
|
|
|
/* Lock on the store path. */
|
2006-09-04 21:06:23 +00:00
|
|
|
boost::shared_ptr<PathLocks> outputLock;
|
2004-06-21 09:35:50 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
typedef void (SubstitutionGoal::*GoalState)();
|
|
|
|
GoalState state;
|
2003-08-20 11:30:45 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
public:
|
2005-01-19 11:16:11 +00:00
|
|
|
SubstitutionGoal(const Path & storePath, Worker & worker);
|
2004-06-25 15:36:09 +00:00
|
|
|
~SubstitutionGoal();
|
* Change the abstract syntax of slices. It used to be that ids were used as
keys to reference slice elements, e.g.,
Slice(["1ef7..."], [("/nix/store/1ef7...-foo", "1ef7", ["8c99..."]), ...])
This was wrong, since ids represent contents, not locations. Therefore we
now have:
Slice(["/nix/store/1ef7..."], [("/nix/store/1ef7...-foo", "1ef7", ["/nix/store/8c99-..."]), ...])
* Fix a bug in the computation of slice closures that could cause slice
elements to be duplicated.
2003-08-20 12:39:56 +00:00
|
|
|
|
2006-12-08 18:41:48 +00:00
|
|
|
void cancel();
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
void work();
|
2003-08-20 11:30:45 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
/* The states. */
|
|
|
|
void init();
|
2004-06-20 19:17:54 +00:00
|
|
|
void tryNext();
|
2007-08-12 00:29:28 +00:00
|
|
|
void gotInfo();
|
|
|
|
void referencesValid();
|
2004-06-22 09:00:31 +00:00
|
|
|
void tryToRun();
|
2004-06-20 19:17:54 +00:00
|
|
|
void finished();
|
2004-06-22 17:04:10 +00:00
|
|
|
|
2004-06-29 09:41:50 +00:00
|
|
|
/* Callback used by the worker to write to the log. */
|
2005-10-17 15:33:24 +00:00
|
|
|
void handleChildOutput(int fd, const string & data);
|
|
|
|
void handleEOF(int fd);
|
2004-06-18 18:09:32 +00:00
|
|
|
};
|
2003-08-20 11:30:45 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
SubstitutionGoal::SubstitutionGoal(const Path & storePath, Worker & worker)
|
|
|
|
: Goal(worker)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
2005-01-19 11:16:11 +00:00
|
|
|
this->storePath = storePath;
|
2004-06-18 18:09:32 +00:00
|
|
|
state = &SubstitutionGoal::init;
|
2005-02-18 09:50:20 +00:00
|
|
|
name = (format("substitution of `%1%'") % storePath).str();
|
|
|
|
trace("created");
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
SubstitutionGoal::~SubstitutionGoal()
|
|
|
|
{
|
2006-12-08 18:41:48 +00:00
|
|
|
/* !!! Once we let substitution goals run under a build user, we
|
|
|
|
need to do use the setuid helper just as in ~DerivationGoal().
|
|
|
|
Idem for cancel. */
|
2004-06-25 15:36:09 +00:00
|
|
|
if (pid != -1) worker.childTerminated(pid);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-12-08 18:41:48 +00:00
|
|
|
void SubstitutionGoal::cancel()
|
|
|
|
{
|
|
|
|
if (pid != -1) {
|
|
|
|
pid_t savedPid = pid;
|
|
|
|
pid.kill();
|
|
|
|
worker.childTerminated(savedPid);
|
|
|
|
}
|
|
|
|
amDone(ecFailed);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
void SubstitutionGoal::work()
|
|
|
|
{
|
|
|
|
(this->*state)();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SubstitutionGoal::init()
|
|
|
|
{
|
2004-06-22 17:04:10 +00:00
|
|
|
trace("init");
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2008-06-09 13:52:45 +00:00
|
|
|
worker.store.addTempRoot(storePath);
|
2005-01-31 10:27:25 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
/* If the path already exists we're done. */
|
2008-06-09 13:52:45 +00:00
|
|
|
if (worker.store.isValidPath(storePath)) {
|
2006-12-08 17:26:21 +00:00
|
|
|
amDone(ecSuccess);
|
2004-06-18 18:09:32 +00:00
|
|
|
return;
|
2003-08-20 11:30:45 +00:00
|
|
|
}
|
|
|
|
|
2008-08-04 13:15:35 +00:00
|
|
|
subs = substituters;
|
|
|
|
|
|
|
|
tryNext();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SubstitutionGoal::tryNext()
|
|
|
|
{
|
|
|
|
trace("trying next substituter");
|
|
|
|
|
|
|
|
if (subs.size() == 0) {
|
|
|
|
/* None left. Terminate this goal and let someone else deal
|
|
|
|
with it. */
|
2007-08-12 00:29:28 +00:00
|
|
|
printMsg(lvlError,
|
2008-08-04 13:15:35 +00:00
|
|
|
format("path `%1%' is required, but there is no substituter that can build it")
|
2007-08-12 00:29:28 +00:00
|
|
|
% storePath);
|
|
|
|
amDone(ecFailed);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-08-04 13:15:35 +00:00
|
|
|
sub = subs.front();
|
|
|
|
subs.pop_front();
|
|
|
|
|
|
|
|
if (!worker.store.querySubstitutablePathInfo(sub, storePath, info)) {
|
|
|
|
tryNext();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2005-09-20 16:14:00 +00:00
|
|
|
/* To maintain the closure invariant, we first have to realise the
|
2005-01-25 17:08:52 +00:00
|
|
|
paths referenced by this one. */
|
2008-08-02 12:54:35 +00:00
|
|
|
foreach (PathSet::iterator, i, info.references)
|
2005-02-01 09:23:38 +00:00
|
|
|
if (*i != storePath) /* ignore self-references */
|
|
|
|
addWaitee(worker.makeSubstitutionGoal(*i));
|
2005-01-25 17:08:52 +00:00
|
|
|
|
|
|
|
if (waitees.empty()) /* to prevent hang (no wake-up event) */
|
|
|
|
referencesValid();
|
|
|
|
else
|
|
|
|
state = &SubstitutionGoal::referencesValid;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SubstitutionGoal::referencesValid()
|
|
|
|
{
|
2007-08-12 00:29:28 +00:00
|
|
|
trace("all references realised");
|
2005-01-25 17:08:52 +00:00
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
if (nrFailed > 0) {
|
|
|
|
printMsg(lvlError,
|
|
|
|
format("some references of path `%1%' could not be realised") % storePath);
|
|
|
|
amDone(ecFailed);
|
|
|
|
return;
|
|
|
|
}
|
2005-01-25 17:08:52 +00:00
|
|
|
|
2008-08-02 12:54:35 +00:00
|
|
|
foreach (PathSet::iterator, i, info.references)
|
2005-02-01 09:23:38 +00:00
|
|
|
if (*i != storePath) /* ignore self-references */
|
2008-06-09 13:52:45 +00:00
|
|
|
assert(worker.store.isValidPath(*i));
|
2004-06-20 19:17:54 +00:00
|
|
|
|
2004-06-22 09:00:31 +00:00
|
|
|
state = &SubstitutionGoal::tryToRun;
|
|
|
|
worker.waitForBuildSlot(shared_from_this());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SubstitutionGoal::tryToRun()
|
|
|
|
{
|
2004-06-22 17:04:10 +00:00
|
|
|
trace("trying to run");
|
|
|
|
|
2004-06-22 09:00:31 +00:00
|
|
|
/* Make sure that we are allowed to start a build. */
|
|
|
|
if (!worker.canBuildMore()) {
|
|
|
|
worker.waitForBuildSlot(shared_from_this());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-08-28 11:36:17 +00:00
|
|
|
/* Maybe a derivation goal has already locked this path
|
|
|
|
(exceedingly unlikely, since it should have used a substitute
|
|
|
|
first, but let's be defensive). */
|
2007-08-28 16:22:08 +00:00
|
|
|
outputLock.reset(); // make sure this goal's lock is gone
|
2007-08-28 11:36:17 +00:00
|
|
|
if (pathIsLockedByMe(storePath)) {
|
|
|
|
debug(format("restarting substitution of `%1%' because it's locked by another goal")
|
|
|
|
% storePath);
|
|
|
|
worker.waitForAnyGoal(shared_from_this());
|
|
|
|
return; /* restart in the tryToRun() state when another goal finishes */
|
|
|
|
}
|
|
|
|
|
2004-06-21 09:35:50 +00:00
|
|
|
/* Acquire a lock on the output path. */
|
2006-09-04 21:06:23 +00:00
|
|
|
outputLock = boost::shared_ptr<PathLocks>(new PathLocks);
|
2006-06-16 10:13:03 +00:00
|
|
|
outputLock->lockPaths(singleton<PathSet>(storePath),
|
|
|
|
(format("waiting for lock on `%1%'") % storePath).str());
|
2004-06-21 09:35:50 +00:00
|
|
|
|
|
|
|
/* Check again whether the path is invalid. */
|
2008-06-09 13:52:45 +00:00
|
|
|
if (worker.store.isValidPath(storePath)) {
|
2004-06-21 09:35:50 +00:00
|
|
|
debug(format("store path `%1%' has become valid") % storePath);
|
2004-06-24 13:40:38 +00:00
|
|
|
outputLock->setDeletion(true);
|
2006-12-08 17:26:21 +00:00
|
|
|
amDone(ecSuccess);
|
2004-06-21 09:35:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2004-06-24 13:40:38 +00:00
|
|
|
printMsg(lvlInfo,
|
|
|
|
format("substituting path `%1%' using substituter `%2%'")
|
2007-08-12 00:29:28 +00:00
|
|
|
% storePath % sub);
|
2004-06-22 17:04:10 +00:00
|
|
|
|
|
|
|
logPipe.create();
|
|
|
|
|
2004-06-21 09:35:50 +00:00
|
|
|
/* Remove the (stale) output path if it exists. */
|
|
|
|
if (pathExists(storePath))
|
2006-12-07 14:14:35 +00:00
|
|
|
deletePathWrapped(storePath);
|
2004-06-21 09:35:50 +00:00
|
|
|
|
2004-06-20 19:17:54 +00:00
|
|
|
/* Fork the substitute program. */
|
2004-06-22 09:51:44 +00:00
|
|
|
pid = fork();
|
|
|
|
switch (pid) {
|
2004-06-20 19:17:54 +00:00
|
|
|
|
|
|
|
case -1:
|
|
|
|
throw SysError("unable to fork");
|
|
|
|
|
|
|
|
case 0:
|
|
|
|
try { /* child */
|
|
|
|
|
|
|
|
logPipe.readSide.close();
|
|
|
|
|
|
|
|
/* !!! close other handles */
|
|
|
|
|
2004-06-22 10:21:44 +00:00
|
|
|
commonChildInit(logPipe);
|
2004-06-20 19:17:54 +00:00
|
|
|
|
2004-06-22 10:21:44 +00:00
|
|
|
/* Fill in the arguments. */
|
2007-08-12 00:29:28 +00:00
|
|
|
Strings args;
|
|
|
|
args.push_back(baseNameOf(sub));
|
|
|
|
args.push_back("--substitute");
|
|
|
|
args.push_back(storePath);
|
2004-06-22 10:21:44 +00:00
|
|
|
const char * * argArr = strings2CharPtrs(args);
|
2004-06-20 19:17:54 +00:00
|
|
|
|
2007-08-12 00:29:28 +00:00
|
|
|
execv(sub.c_str(), (char * *) argArr);
|
2004-06-20 19:17:54 +00:00
|
|
|
|
2007-08-12 00:29:28 +00:00
|
|
|
throw SysError(format("executing `%1%'") % sub);
|
2004-06-20 19:17:54 +00:00
|
|
|
|
2006-09-04 21:06:23 +00:00
|
|
|
} catch (std::exception & e) {
|
2007-10-27 00:46:59 +00:00
|
|
|
std::cerr << format("substitute error: %1%") % e.what() << std::endl;
|
2004-06-20 19:17:54 +00:00
|
|
|
}
|
2006-07-20 12:17:25 +00:00
|
|
|
quickExit(1);
|
2004-06-20 19:17:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* parent */
|
2004-06-22 09:51:44 +00:00
|
|
|
pid.setSeparatePG(true);
|
2007-03-19 12:48:45 +00:00
|
|
|
pid.setKillSignal(SIGTERM);
|
2004-06-20 19:17:54 +00:00
|
|
|
logPipe.writeSide.close();
|
|
|
|
worker.childStarted(shared_from_this(),
|
2005-10-17 15:33:24 +00:00
|
|
|
pid, singleton<set<int> >(logPipe.readSide), true);
|
2004-06-20 19:17:54 +00:00
|
|
|
|
|
|
|
state = &SubstitutionGoal::finished;
|
2008-11-12 11:08:27 +00:00
|
|
|
|
|
|
|
if (printBuildTrace) {
|
|
|
|
printMsg(lvlError, format("@ substituter-started %1% %2%")
|
|
|
|
% storePath % sub);
|
|
|
|
}
|
2004-06-20 19:17:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SubstitutionGoal::finished()
|
|
|
|
{
|
2004-06-22 17:04:10 +00:00
|
|
|
trace("substitute finished");
|
2004-06-20 19:17:54 +00:00
|
|
|
|
|
|
|
/* Since we got an EOF on the logger pipe, the substitute is
|
|
|
|
presumed to have terminated. */
|
|
|
|
/* !!! this could block! */
|
2004-06-22 09:51:44 +00:00
|
|
|
pid_t savedPid = pid;
|
|
|
|
int status = pid.wait(true);
|
2004-06-20 19:17:54 +00:00
|
|
|
|
|
|
|
/* So the child is gone now. */
|
2004-06-22 09:51:44 +00:00
|
|
|
worker.childTerminated(savedPid);
|
2004-06-20 19:17:54 +00:00
|
|
|
|
|
|
|
/* Close the read side of the logger pipe. */
|
|
|
|
logPipe.readSide.close();
|
|
|
|
|
|
|
|
debug(format("substitute for `%1%' finished") % storePath);
|
|
|
|
|
2004-06-24 13:40:38 +00:00
|
|
|
/* Check the exit status and the build result. */
|
|
|
|
try {
|
|
|
|
|
|
|
|
if (!statusOk(status))
|
|
|
|
throw SubstError(format("builder for `%1%' %2%")
|
|
|
|
% storePath % statusToString(status));
|
|
|
|
|
|
|
|
if (!pathExists(storePath))
|
|
|
|
throw SubstError(
|
|
|
|
format("substitute did not produce path `%1%'")
|
|
|
|
% storePath);
|
|
|
|
|
|
|
|
} catch (SubstError & e) {
|
2004-06-20 19:17:54 +00:00
|
|
|
|
2004-06-24 13:40:38 +00:00
|
|
|
printMsg(lvlInfo,
|
|
|
|
format("substitution of path `%1%' using substituter `%2%' failed: %3%")
|
2007-08-12 00:29:28 +00:00
|
|
|
% storePath % sub % e.msg());
|
2004-06-24 13:40:38 +00:00
|
|
|
|
2008-11-12 11:08:27 +00:00
|
|
|
if (printBuildTrace) {
|
|
|
|
printMsg(lvlError, format("@ substituter-failed %1% %2% %3%")
|
|
|
|
% storePath % status % e.msg());
|
|
|
|
}
|
|
|
|
|
2004-06-24 13:40:38 +00:00
|
|
|
/* Try the next substitute. */
|
|
|
|
state = &SubstitutionGoal::tryNext;
|
|
|
|
worker.wakeUp(shared_from_this());
|
|
|
|
return;
|
|
|
|
}
|
2004-06-20 19:17:54 +00:00
|
|
|
|
2004-11-29 15:09:29 +00:00
|
|
|
canonicalisePathMetaData(storePath);
|
2004-09-22 12:15:04 +00:00
|
|
|
|
2005-01-19 16:39:47 +00:00
|
|
|
Hash contentHash = hashPath(htSHA256, storePath);
|
|
|
|
|
2008-06-09 13:52:45 +00:00
|
|
|
worker.store.registerValidPath(storePath, contentHash,
|
2008-08-02 12:54:35 +00:00
|
|
|
info.references, info.deriver);
|
2004-06-20 19:17:54 +00:00
|
|
|
|
2004-06-24 13:40:38 +00:00
|
|
|
outputLock->setDeletion(true);
|
2004-06-21 09:35:50 +00:00
|
|
|
|
2004-06-24 13:40:38 +00:00
|
|
|
printMsg(lvlChatty,
|
|
|
|
format("substitution of path `%1%' succeeded") % storePath);
|
|
|
|
|
2008-11-12 11:08:27 +00:00
|
|
|
if (printBuildTrace) {
|
|
|
|
printMsg(lvlError, format("@ substituter-succeeded %1%") % storePath);
|
|
|
|
}
|
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
amDone(ecSuccess);
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-10-17 15:33:24 +00:00
|
|
|
void SubstitutionGoal::handleChildOutput(int fd, const string & data)
|
2004-06-29 09:41:50 +00:00
|
|
|
{
|
|
|
|
assert(fd == logPipe.readSide);
|
2005-10-17 15:33:24 +00:00
|
|
|
if (verbosity >= buildVerbosity)
|
2006-12-03 02:08:13 +00:00
|
|
|
writeToStderr((unsigned char *) data.c_str(), data.size());
|
2004-06-29 09:41:50 +00:00
|
|
|
/* Don't write substitution output to a log file for now. We
|
|
|
|
probably should, though. */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-10-17 15:33:24 +00:00
|
|
|
void SubstitutionGoal::handleEOF(int fd)
|
|
|
|
{
|
|
|
|
if (fd == logPipe.readSide) worker.wakeUp(shared_from_this());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
static bool working = false;
|
|
|
|
|
|
|
|
|
2008-06-09 13:52:45 +00:00
|
|
|
Worker::Worker(LocalStore & store)
|
|
|
|
: store(store)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
|
|
|
/* Debugging: prevent recursive workers. */
|
|
|
|
if (working) abort();
|
|
|
|
working = true;
|
2004-06-21 08:51:55 +00:00
|
|
|
nrChildren = 0;
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Worker::~Worker()
|
|
|
|
{
|
|
|
|
working = false;
|
2004-06-25 15:36:09 +00:00
|
|
|
|
|
|
|
/* Explicitly get rid of all strong pointers now. After this all
|
|
|
|
goals that refer to this worker should be gone. (Otherwise we
|
|
|
|
are in trouble, since goals may call childTerminated() etc. in
|
|
|
|
their destructors). */
|
|
|
|
topGoals.clear();
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<class T>
|
2004-06-25 15:36:09 +00:00
|
|
|
static GoalPtr addGoal(const Path & path,
|
|
|
|
Worker & worker, WeakGoalMap & goalMap)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
2004-06-25 15:36:09 +00:00
|
|
|
GoalPtr goal = goalMap[path].lock();
|
2004-06-18 18:09:32 +00:00
|
|
|
if (!goal) {
|
|
|
|
goal = GoalPtr(new T(path, worker));
|
|
|
|
goalMap[path] = goal;
|
|
|
|
worker.wakeUp(goal);
|
2003-07-20 19:29:38 +00:00
|
|
|
}
|
2004-06-25 15:36:09 +00:00
|
|
|
return goal;
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
2003-07-20 19:29:38 +00:00
|
|
|
|
2003-08-01 15:41:47 +00:00
|
|
|
|
2005-01-19 11:16:11 +00:00
|
|
|
GoalPtr Worker::makeDerivationGoal(const Path & nePath)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
2005-01-19 11:16:11 +00:00
|
|
|
return addGoal<DerivationGoal>(nePath, *this, derivationGoals);
|
2004-05-11 18:05:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
GoalPtr Worker::makeSubstitutionGoal(const Path & storePath)
|
2004-05-11 18:05:44 +00:00
|
|
|
{
|
2004-06-25 15:36:09 +00:00
|
|
|
return addGoal<SubstitutionGoal>(storePath, *this, substitutionGoals);
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
static void removeGoal(GoalPtr goal, WeakGoalMap & goalMap)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
2005-02-18 09:50:20 +00:00
|
|
|
/* !!! inefficient */
|
|
|
|
for (WeakGoalMap::iterator i = goalMap.begin();
|
|
|
|
i != goalMap.end(); )
|
|
|
|
if (i->second.lock() == goal) {
|
|
|
|
WeakGoalMap::iterator j = i; ++j;
|
|
|
|
goalMap.erase(i);
|
|
|
|
i = j;
|
|
|
|
}
|
|
|
|
else ++i;
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
void Worker::removeGoal(GoalPtr goal)
|
|
|
|
{
|
2006-09-04 21:06:23 +00:00
|
|
|
nix::removeGoal(goal, derivationGoals);
|
|
|
|
nix::removeGoal(goal, substitutionGoals);
|
2005-02-23 11:19:27 +00:00
|
|
|
if (topGoals.find(goal) != topGoals.end()) {
|
|
|
|
topGoals.erase(goal);
|
|
|
|
/* If a top-level goal failed, then kill all other goals
|
|
|
|
(unless keepGoing was set). */
|
|
|
|
if (goal->getExitCode() == Goal::ecFailed && !keepGoing)
|
|
|
|
topGoals.clear();
|
|
|
|
}
|
2007-08-28 11:36:17 +00:00
|
|
|
|
|
|
|
/* Wake up goals waiting for any goal to finish. */
|
|
|
|
for (WeakGoals::iterator i = waitingForAnyGoal.begin();
|
|
|
|
i != waitingForAnyGoal.end(); ++i)
|
|
|
|
{
|
|
|
|
GoalPtr goal = i->lock();
|
|
|
|
if (goal) wakeUp(goal);
|
|
|
|
}
|
|
|
|
|
|
|
|
waitingForAnyGoal.clear();
|
2004-05-11 18:05:44 +00:00
|
|
|
}
|
2003-11-21 16:05:19 +00:00
|
|
|
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
void Worker::wakeUp(GoalPtr goal)
|
2004-05-11 18:05:44 +00:00
|
|
|
{
|
2004-06-25 15:36:09 +00:00
|
|
|
goal->trace("woken up");
|
2004-06-18 18:09:32 +00:00
|
|
|
awake.insert(goal);
|
|
|
|
}
|
2004-05-11 18:05:44 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
bool Worker::canBuildMore()
|
|
|
|
{
|
2004-06-19 21:45:04 +00:00
|
|
|
return nrChildren < maxBuildJobs;
|
2003-07-20 19:29:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-12-04 14:29:41 +00:00
|
|
|
bool Worker::canPostpone()
|
|
|
|
{
|
|
|
|
return children.size() != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-06-19 21:45:04 +00:00
|
|
|
void Worker::childStarted(GoalPtr goal,
|
2005-10-17 15:33:24 +00:00
|
|
|
pid_t pid, const set<int> & fds, bool inBuildSlot)
|
2003-07-20 19:29:38 +00:00
|
|
|
{
|
2004-06-19 21:45:04 +00:00
|
|
|
Child child;
|
|
|
|
child.goal = goal;
|
2005-10-17 15:33:24 +00:00
|
|
|
child.fds = fds;
|
2006-12-08 15:44:00 +00:00
|
|
|
child.lastOutput = time(0);
|
2004-06-19 21:45:04 +00:00
|
|
|
child.inBuildSlot = inBuildSlot;
|
|
|
|
children[pid] = child;
|
|
|
|
if (inBuildSlot) nrChildren++;
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
2003-07-20 19:29:38 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2004-07-01 16:24:35 +00:00
|
|
|
void Worker::childTerminated(pid_t pid, bool wakeSleepers)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
2006-12-08 18:41:48 +00:00
|
|
|
assert(pid != -1); /* common mistake */
|
|
|
|
|
2004-06-19 21:45:04 +00:00
|
|
|
Children::iterator i = children.find(pid);
|
|
|
|
assert(i != children.end());
|
|
|
|
|
|
|
|
if (i->second.inBuildSlot) {
|
|
|
|
assert(nrChildren > 0);
|
|
|
|
nrChildren--;
|
|
|
|
}
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
children.erase(pid);
|
|
|
|
|
2004-07-01 16:24:35 +00:00
|
|
|
if (wakeSleepers) {
|
|
|
|
|
|
|
|
/* Wake up goals waiting for a build slot. */
|
|
|
|
for (WeakGoals::iterator i = wantingToBuild.begin();
|
|
|
|
i != wantingToBuild.end(); ++i)
|
|
|
|
{
|
|
|
|
GoalPtr goal = i->lock();
|
|
|
|
if (goal) wakeUp(goal);
|
|
|
|
}
|
|
|
|
|
|
|
|
wantingToBuild.clear();
|
2004-06-25 15:36:09 +00:00
|
|
|
}
|
2003-10-16 16:29:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
void Worker::waitForBuildSlot(GoalPtr goal)
|
2003-10-16 16:29:57 +00:00
|
|
|
{
|
2004-06-18 18:09:32 +00:00
|
|
|
debug("wait for build slot");
|
2006-12-08 17:26:21 +00:00
|
|
|
if (canBuildMore())
|
2004-06-18 18:09:32 +00:00
|
|
|
wakeUp(goal); /* we can do it right away */
|
|
|
|
else
|
|
|
|
wantingToBuild.insert(goal);
|
|
|
|
}
|
2004-02-13 10:45:09 +00:00
|
|
|
|
2003-10-16 16:29:57 +00:00
|
|
|
|
2006-12-08 17:26:21 +00:00
|
|
|
void Worker::waitForChildTermination(GoalPtr goal)
|
|
|
|
{
|
|
|
|
debug("wait for child termination");
|
|
|
|
if (children.size() == 0)
|
|
|
|
throw Error("waiting for a build slot, yet there are no running children - "
|
|
|
|
"maybe the build hook gave an inappropriate `postpone' reply?");
|
|
|
|
wantingToBuild.insert(goal);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-28 11:36:17 +00:00
|
|
|
void Worker::waitForAnyGoal(GoalPtr goal)
|
|
|
|
{
|
|
|
|
debug("wait for any goal");
|
|
|
|
waitingForAnyGoal.insert(goal);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-02-23 11:19:27 +00:00
|
|
|
void Worker::run(const Goals & _topGoals)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
2005-01-19 15:02:02 +00:00
|
|
|
for (Goals::iterator i = _topGoals.begin();
|
|
|
|
i != _topGoals.end(); ++i)
|
|
|
|
topGoals.insert(*i);
|
2004-06-25 15:36:09 +00:00
|
|
|
|
2004-06-22 14:48:59 +00:00
|
|
|
startNest(nest, lvlDebug, format("entered goal loop"));
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
while (1) {
|
|
|
|
|
2004-01-15 20:23:55 +00:00
|
|
|
checkInterrupt();
|
2004-06-18 18:09:32 +00:00
|
|
|
|
|
|
|
/* Call every wake goal. */
|
2005-02-23 11:19:27 +00:00
|
|
|
while (!awake.empty() && !topGoals.empty()) {
|
2004-06-25 15:36:09 +00:00
|
|
|
WeakGoals awake2(awake);
|
2004-06-18 18:09:32 +00:00
|
|
|
awake.clear();
|
2004-06-25 15:36:09 +00:00
|
|
|
for (WeakGoals::iterator i = awake2.begin(); i != awake2.end(); ++i) {
|
2004-06-18 18:09:32 +00:00
|
|
|
checkInterrupt();
|
2004-06-25 15:36:09 +00:00
|
|
|
GoalPtr goal = i->lock();
|
|
|
|
if (goal) goal->work();
|
2005-02-23 11:19:27 +00:00
|
|
|
if (topGoals.empty()) break;
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
2003-10-16 16:29:57 +00:00
|
|
|
}
|
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
if (topGoals.empty()) break;
|
2003-10-16 16:29:57 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
/* Wait for input. */
|
2007-08-12 00:29:28 +00:00
|
|
|
if (!children.empty())
|
|
|
|
waitForInput();
|
|
|
|
else
|
|
|
|
/* !!! not when we're polling */
|
|
|
|
assert(!awake.empty());
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
2003-10-16 16:29:57 +00:00
|
|
|
|
2004-06-25 15:36:09 +00:00
|
|
|
/* If --keep-going is not set, it's possible that the main goal
|
|
|
|
exited while some of its subgoals were still active. But if
|
|
|
|
--keep-going *is* set, then they must all be finished now. */
|
|
|
|
assert(!keepGoing || awake.empty());
|
|
|
|
assert(!keepGoing || wantingToBuild.empty());
|
|
|
|
assert(!keepGoing || children.empty());
|
2003-07-20 19:29:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
void Worker::waitForInput()
|
2003-07-20 19:29:38 +00:00
|
|
|
{
|
2004-06-18 18:09:32 +00:00
|
|
|
printMsg(lvlVomit, "waiting for children");
|
2003-07-20 19:29:38 +00:00
|
|
|
|
2005-10-17 15:33:24 +00:00
|
|
|
/* Process output from the file descriptors attached to the
|
|
|
|
children, namely log output and output path creation commands.
|
|
|
|
We also use this to detect child termination: if we get EOF on
|
|
|
|
the logger pipe of a build, we assume that the builder has
|
|
|
|
terminated. */
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2006-12-08 15:44:00 +00:00
|
|
|
/* If we're monitoring for silence on stdout/stderr, sleep until
|
|
|
|
the first deadline for any child. */
|
|
|
|
struct timeval timeout;
|
|
|
|
if (maxSilentTime != 0) {
|
|
|
|
time_t oldest = 0;
|
|
|
|
for (Children::iterator i = children.begin();
|
|
|
|
i != children.end(); ++i)
|
|
|
|
{
|
|
|
|
oldest = oldest == 0 || i->second.lastOutput < oldest
|
|
|
|
? i->second.lastOutput : oldest;
|
|
|
|
}
|
|
|
|
time_t now = time(0);
|
|
|
|
timeout.tv_sec = (time_t) (oldest + maxSilentTime) <= now ? 0 :
|
|
|
|
oldest + maxSilentTime - now;
|
|
|
|
timeout.tv_usec = 0;
|
|
|
|
printMsg(lvlVomit, format("sleeping %1% seconds") % timeout.tv_sec);
|
|
|
|
}
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
/* Use select() to wait for the input side of any logger pipe to
|
|
|
|
become `available'. Note that `available' (i.e., non-blocking)
|
|
|
|
includes EOF. */
|
|
|
|
fd_set fds;
|
|
|
|
FD_ZERO(&fds);
|
|
|
|
int fdMax = 0;
|
|
|
|
for (Children::iterator i = children.begin();
|
|
|
|
i != children.end(); ++i)
|
|
|
|
{
|
2005-10-17 15:33:24 +00:00
|
|
|
for (set<int>::iterator j = i->second.fds.begin();
|
|
|
|
j != i->second.fds.end(); ++j)
|
|
|
|
{
|
|
|
|
FD_SET(*j, &fds);
|
|
|
|
if (*j >= fdMax) fdMax = *j + 1;
|
|
|
|
}
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
2003-07-20 19:29:38 +00:00
|
|
|
|
2006-12-08 15:44:00 +00:00
|
|
|
if (select(fdMax, &fds, 0, 0, maxSilentTime != 0 ? &timeout : 0) == -1) {
|
2004-06-18 18:09:32 +00:00
|
|
|
if (errno == EINTR) return;
|
|
|
|
throw SysError("waiting for input");
|
|
|
|
}
|
2003-07-20 19:29:38 +00:00
|
|
|
|
2006-12-08 15:44:00 +00:00
|
|
|
time_t now = time(0);
|
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
/* Process all available file descriptors. */
|
2006-12-08 18:41:48 +00:00
|
|
|
|
|
|
|
/* Since goals may be canceled from inside the loop below (causing
|
|
|
|
them go be erased from the `children' map), we have to be
|
|
|
|
careful that we don't keep iterators alive across calls to
|
|
|
|
cancel(). */
|
|
|
|
set<pid_t> pids;
|
2004-06-18 18:09:32 +00:00
|
|
|
for (Children::iterator i = children.begin();
|
|
|
|
i != children.end(); ++i)
|
2006-12-08 18:41:48 +00:00
|
|
|
pids.insert(i->first);
|
|
|
|
|
|
|
|
for (set<pid_t>::iterator i = pids.begin();
|
|
|
|
i != pids.end(); ++i)
|
2004-06-18 18:09:32 +00:00
|
|
|
{
|
|
|
|
checkInterrupt();
|
2006-12-08 18:41:48 +00:00
|
|
|
Children::iterator j = children.find(*i);
|
|
|
|
if (j == children.end()) continue; // child destroyed
|
|
|
|
GoalPtr goal = j->second.goal.lock();
|
2004-06-25 15:36:09 +00:00
|
|
|
assert(goal);
|
2006-12-08 18:41:48 +00:00
|
|
|
|
|
|
|
set<int> fds2(j->second.fds);
|
|
|
|
for (set<int>::iterator k = fds2.begin(); k != fds2.end(); ++k) {
|
|
|
|
if (FD_ISSET(*k, &fds)) {
|
2005-10-17 15:33:24 +00:00
|
|
|
unsigned char buffer[4096];
|
2006-12-08 18:41:48 +00:00
|
|
|
ssize_t rd = read(*k, buffer, sizeof(buffer));
|
2005-10-17 15:33:24 +00:00
|
|
|
if (rd == -1) {
|
|
|
|
if (errno != EINTR)
|
|
|
|
throw SysError(format("reading from %1%")
|
|
|
|
% goal->getName());
|
|
|
|
} else if (rd == 0) {
|
|
|
|
debug(format("%1%: got EOF") % goal->getName());
|
2006-12-08 18:41:48 +00:00
|
|
|
goal->handleEOF(*k);
|
|
|
|
j->second.fds.erase(*k);
|
2005-10-17 15:33:24 +00:00
|
|
|
} else {
|
|
|
|
printMsg(lvlVomit, format("%1%: read %2% bytes")
|
|
|
|
% goal->getName() % rd);
|
|
|
|
string data((char *) buffer, rd);
|
2006-12-08 18:41:48 +00:00
|
|
|
goal->handleChildOutput(*k, data);
|
|
|
|
j->second.lastOutput = now;
|
2005-10-17 15:33:24 +00:00
|
|
|
}
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
|
|
|
}
|
2006-12-08 15:44:00 +00:00
|
|
|
|
|
|
|
if (maxSilentTime != 0 &&
|
2006-12-08 18:41:48 +00:00
|
|
|
now - j->second.lastOutput >= (time_t) maxSilentTime)
|
2006-12-08 17:26:21 +00:00
|
|
|
{
|
|
|
|
printMsg(lvlError,
|
|
|
|
format("%1% timed out after %2% seconds of silence")
|
2006-12-08 15:44:00 +00:00
|
|
|
% goal->getName() % maxSilentTime);
|
2006-12-08 17:26:21 +00:00
|
|
|
goal->cancel();
|
|
|
|
}
|
2004-06-18 18:09:32 +00:00
|
|
|
}
|
2003-07-20 19:29:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-10-17 15:33:24 +00:00
|
|
|
|
2004-06-18 18:09:32 +00:00
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
2006-11-30 18:02:04 +00:00
|
|
|
void LocalStore::buildDerivations(const PathSet & drvPaths)
|
2003-07-29 10:43:12 +00:00
|
|
|
{
|
2005-01-19 15:02:02 +00:00
|
|
|
startNest(nest, lvlDebug,
|
|
|
|
format("building %1%") % showPaths(drvPaths));
|
2003-08-25 14:56:11 +00:00
|
|
|
|
2008-06-09 13:52:45 +00:00
|
|
|
Worker worker(*this);
|
2005-01-19 15:02:02 +00:00
|
|
|
|
|
|
|
Goals goals;
|
|
|
|
for (PathSet::const_iterator i = drvPaths.begin();
|
|
|
|
i != drvPaths.end(); ++i)
|
|
|
|
goals.insert(worker.makeDerivationGoal(*i));
|
2005-02-23 11:19:27 +00:00
|
|
|
|
|
|
|
worker.run(goals);
|
|
|
|
|
|
|
|
PathSet failed;
|
|
|
|
for (Goals::iterator i = goals.begin(); i != goals.end(); ++i)
|
|
|
|
if ((*i)->getExitCode() == Goal::ecFailed) {
|
|
|
|
DerivationGoal * i2 = dynamic_cast<DerivationGoal *>(i->get());
|
|
|
|
assert(i2);
|
|
|
|
failed.insert(i2->getDrvPath());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!failed.empty())
|
|
|
|
throw Error(format("build of %1% failed") % showPaths(failed));
|
2003-07-29 10:43:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-11-30 18:02:04 +00:00
|
|
|
void LocalStore::ensurePath(const Path & path)
|
2003-07-20 19:29:38 +00:00
|
|
|
{
|
2004-06-18 18:09:32 +00:00
|
|
|
/* If the path is already valid, we're done. */
|
2008-06-09 13:52:45 +00:00
|
|
|
if (isValidPath(path)) return;
|
2004-06-18 18:09:32 +00:00
|
|
|
|
2008-06-09 13:52:45 +00:00
|
|
|
Worker worker(*this);
|
2005-02-23 11:19:27 +00:00
|
|
|
GoalPtr goal = worker.makeSubstitutionGoal(path);
|
|
|
|
Goals goals = singleton<Goals>(goal);
|
|
|
|
|
|
|
|
worker.run(goals);
|
|
|
|
|
|
|
|
if (goal->getExitCode() != Goal::ecSuccess)
|
2004-06-25 10:21:44 +00:00
|
|
|
throw Error(format("path `%1%' does not exist and cannot be created") % path);
|
2003-07-20 19:29:38 +00:00
|
|
|
}
|
2006-09-04 21:06:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
}
|