about summary refs log tree commit diff
path: root/src/libstore/build.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstore/build.cc')
-rw-r--r--src/libstore/build.cc498
1 files changed, 269 insertions, 229 deletions
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index ef2f7adf3194..210486fbc205 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -64,6 +64,7 @@ static const uid_t rootUserId = 0;
 
 /* Forward definition. */
 class Worker;
+class HookInstance;
 
 
 /* A pointer to a goal. */
@@ -215,6 +216,8 @@ public:
 
     LocalStore & store;
 
+    boost::shared_ptr<HookInstance> hook;
+    
     Worker(LocalStore & store);
     ~Worker();
 
@@ -615,6 +618,107 @@ void deletePathWrapped(const Path & path)
 //////////////////////////////////////////////////////////////////////
 
 
+struct HookInstance
+{
+    /* Pipes for talking to the build hook. */
+    Pipe toHook;
+
+    /* Pipe for the hook's standard output/error. */
+    Pipe fromHook;
+
+    /* Pipe for the builder's standard output/error. */
+    Pipe builderOut;
+    
+    /* The process ID of the hook. */
+    Pid pid;
+
+    HookInstance();
+
+    ~HookInstance();
+};
+
+
+HookInstance::HookInstance()
+{
+    debug("starting build hook");
+    
+    Path buildHook = absPath(getEnv("NIX_BUILD_HOOK"));
+    
+    /* Create a pipe to get the output of the child. */
+    fromHook.create();
+    
+    /* Create the communication pipes. */
+    toHook.create();
+
+    /* Create a pipe to get the output of the builder. */
+    builderOut.create();
+
+    /* Fork the hook. */
+    pid = fork();
+    switch (pid) {
+        
+    case -1:
+        throw SysError("unable to fork");
+
+    case 0:
+        try { /* child */
+
+            commonChildInit(fromHook);
+
+            if (chdir("/") == -1) throw SysError("changing into `/");
+            
+            /* Dup the communication pipes. */
+            toHook.writeSide.close();
+            if (dup2(toHook.readSide, STDIN_FILENO) == -1)
+                throw SysError("dupping to-hook read side");
+
+            /* Use fd 4 for the builder's stdout/stderr. */
+            builderOut.readSide.close();
+            if (dup2(builderOut.writeSide, 4) == -1)
+                throw SysError("dupping builder's stdout/stderr");
+            
+            execl(buildHook.c_str(), buildHook.c_str(), thisSystem.c_str(),
+                (format("%1%") % maxSilentTime).str().c_str(),
+                (format("%1%") % printBuildTrace).str().c_str(),
+                NULL);
+            
+            throw SysError(format("executing `%1%'") % buildHook);
+            
+        } catch (std::exception & e) {
+            std::cerr << format("build hook error: %1%") % e.what() << std::endl;
+        }
+        quickExit(1);
+    }
+    
+    /* parent */
+    pid.setSeparatePG(true);
+    pid.setKillSignal(SIGTERM);
+    fromHook.writeSide.close();
+    toHook.readSide.close();
+}
+
+
+HookInstance::~HookInstance()
+{
+    try {
+        /* Cleanly shut down the hook by closing its stdin if it's not
+           already building.  Otherwise pid's destructor will kill
+           it. */
+        if (pid != -1 && toHook.writeSide != -1) {
+            toHook.writeSide.close();
+            pid.wait(true);
+        }
+    } catch (...) {
+        ignoreException();
+    }
+}
+
+
+//////////////////////////////////////////////////////////////////////
+
+
+typedef enum {rpAccept, rpDecline, rpPostpone} HookReply;
+
 class DerivationGoal : public Goal
 {
 private:
@@ -649,14 +753,11 @@ private:
     AutoCloseFD fdLogFile;
 
     /* Pipe for the builder's standard output/error. */
-    Pipe logPipe;
-
-    /* Whether we're building using a build hook. */
-    bool usingBuildHook;
-
-    /* Pipes for talking to the build hook (if any). */
-    Pipe toHook;
+    Pipe builderOut;
 
+    /* The build hook. */
+    boost::shared_ptr<HookInstance> hook;
+    
     /* Whether we're currently doing a chroot build. */
     bool useChroot;
     
@@ -694,12 +795,8 @@ private:
     void buildDone();
 
     /* Is the build hook willing to perform the build? */
-    typedef enum {rpAccept, rpDecline, rpPostpone} HookReply;
     HookReply tryBuildHook();
 
-    /* Synchronously wait for a build hook to finish. */
-    void terminateBuildHook(bool kill = false);
-
     /* Start building a derivation. */
     void startBuilder();
 
@@ -711,10 +808,6 @@ private:
     /* Open a log file and a pipe to it. */
     Path openLogFile();
 
-    /* Common initialisation to be performed in child processes (i.e.,
-       both in builders and in build hooks). */
-    void initChild();
-    
     /* Delete the temporary directory, if we have one. */
     void deleteTmpDir(bool force);
 
@@ -742,6 +835,7 @@ DerivationGoal::DerivationGoal(const Path & drvPath, Worker & worker)
     trace("created");
 }
 
+
 DerivationGoal::~DerivationGoal()
 {
     /* Careful: we should never ever throw an exception from a
@@ -754,6 +848,7 @@ DerivationGoal::~DerivationGoal()
     }
 }
 
+
 void DerivationGoal::killChild()
 {
     if (pid != -1) {
@@ -778,6 +873,8 @@ void DerivationGoal::killChild()
         
         assert(pid == -1);
     }
+
+    hook.reset();
 }
 
 
@@ -887,7 +984,10 @@ void DerivationGoal::outputsSubstituted()
     foreach (PathSet::iterator, i, drv.inputSrcs)
         addWaitee(worker.makeSubstitutionGoal(*i));
 
-    state = &DerivationGoal::inputsRealised;
+    if (waitees.empty()) /* to prevent hang (no wake-up event) */
+        inputsRealised();
+    else
+        state = &DerivationGoal::inputsRealised;
 }
 
 
@@ -961,6 +1061,16 @@ PathSet outputPaths(const DerivationOutputs & outputs)
 }
 
 
+static bool canBuildLocally(const string & platform)
+{
+    return platform == thisSystem 
+#ifdef CAN_DO_LINUX32_BUILDS
+        || (platform == "i686-linux" && thisSystem == "x86_64-linux")
+#endif
+        ;
+}
+
+
 void DerivationGoal::tryToBuild()
 {
     trace("trying to build");
@@ -1028,28 +1138,36 @@ void DerivationGoal::tryToBuild()
     foreach (DerivationOutputs::iterator, i, drv.outputs)
         if (pathFailed(i->second.path)) return;
 
+    /* Don't do a remote build if the derivation has the attribute
+       `preferLocalBuild' set. */
+    bool preferLocalBuild =
+        drv.env["preferLocalBuild"] == "1" && canBuildLocally(drv.platform);
+
     /* Is the build hook willing to accept this job? */
-    usingBuildHook = true;
-    switch (tryBuildHook()) {
-        case rpAccept:
-            /* Yes, it has started doing so.  Wait until we get EOF
-               from the hook. */
-            state = &DerivationGoal::buildDone;
-            return;
-        case rpPostpone:
-            /* Not now; wait until at least one child finishes. */
-            worker.waitForAWhile(shared_from_this());
-            outputLocks.unlock();
-            return;
-        case rpDecline:
-            /* We should do it ourselves. */
-            break;
+    if (!preferLocalBuild) {
+        switch (tryBuildHook()) {
+            case rpAccept:
+                /* Yes, it has started doing so.  Wait until we get
+                   EOF from the hook. */
+                state = &DerivationGoal::buildDone;
+                return;
+            case rpPostpone:
+                /* Not now; wait until at least one child finishes or
+                   the wake-up timeout expires. */
+                worker.waitForAWhile(shared_from_this());
+                outputLocks.unlock();
+                return;
+            case rpDecline:
+                /* We should do it ourselves. */
+                break;
+        }
     }
-
-    usingBuildHook = false;
-
-    /* Make sure that we are allowed to start a build. */
-    if (worker.getNrLocalBuilds() >= maxBuildJobs) {
+    
+    /* Make sure that we are allowed to start a build.  If this
+       derivation prefers to be done locally, do it even if
+       maxBuildJobs is 0. */
+    unsigned int curBuilds = worker.getNrLocalBuilds();
+    if (curBuilds >= maxBuildJobs && !(preferLocalBuild && curBuilds == 0)) {
         worker.waitForBuildSlot(shared_from_this());
         outputLocks.unlock();
         return;
@@ -1085,18 +1203,29 @@ void DerivationGoal::buildDone()
        to have terminated.  In fact, the builder could also have
        simply have closed its end of the pipe --- just don't do that
        :-) */
-    /* !!! this could block! security problem! solution: kill the
-       child */
-    pid_t savedPid = pid;
-    int status = pid.wait(true);
+    int status;
+    pid_t savedPid;
+    if (hook) {
+        savedPid = hook->pid;
+        status = hook->pid.wait(true);
+    } else {
+        /* !!! this could block! security problem! solution: kill the
+           child */
+        savedPid = pid;
+        status = pid.wait(true);
+    }
 
     debug(format("builder process for `%1%' finished") % drvPath);
 
     /* So the child is gone now. */
     worker.childTerminated(savedPid);
-
+    
     /* Close the read side of the logger pipe. */
-    logPipe.readSide.close();
+    if (hook) {
+        hook->builderOut.readSide.close();
+        hook->fromHook.readSide.close();
+    }
+    else builderOut.readSide.close();
 
     /* Close the log file. */
     fdLogFile.close();
@@ -1169,11 +1298,11 @@ void DerivationGoal::buildDone()
         /* When using a build hook, the hook will return a remote
            build failure using exit code 100.  Anything else is a hook
            problem. */
-        bool hookError = usingBuildHook &&
+        bool hookError = hook &&
             (!WIFEXITED(status) || WEXITSTATUS(status) != 100);
         
         if (printBuildTrace) {
-            if (usingBuildHook && hookError)
+            if (hook && hookError)
                 printMsg(lvlError, format("@ hook-failed %1% %2% %3% %4%")
                     % drvPath % drv.outputs["out"].path % status % e.msg());
             else
@@ -1208,162 +1337,85 @@ void DerivationGoal::buildDone()
 }
 
 
-DerivationGoal::HookReply DerivationGoal::tryBuildHook()
+HookReply DerivationGoal::tryBuildHook()
 {
-    if (!useBuildHook) return rpDecline;
-    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. */
-    Path logFile = openLogFile();
-
-    /* Create the communication pipes. */
-    toHook.create();
-
-    /* Fork the hook. */
-    pid = fork();
-    switch (pid) {
-        
-    case -1:
-        throw SysError("unable to fork");
-
-    case 0:
-        try { /* child */
-
-            initChild();
+    if (!useBuildHook || getEnv("NIX_BUILD_HOOK") == "") return rpDecline;
 
-            string s;
-            foreach (DerivationOutputs::const_iterator, i, drv.outputs)
-                s += i->second.path + " ";
-            if (setenv("NIX_HELD_LOCKS", s.c_str(), 1))
-                throw SysError("setting an environment variable");
+    if (!worker.hook)
+        worker.hook = boost::shared_ptr<HookInstance>(new HookInstance);
 
-            execl(buildHook.c_str(), buildHook.c_str(),
-                (worker.getNrLocalBuilds() < maxBuildJobs ? (string) "1" : "0").c_str(),
-                thisSystem.c_str(),
-                drv.platform.c_str(),
-                drvPath.c_str(),
-                (format("%1%") % maxSilentTime).str().c_str(),
-                NULL);
-            
-            throw SysError(format("executing `%1%'") % buildHook);
-            
-        } catch (std::exception & e) {
-            std::cerr << format("build hook error: %1%") % e.what() << std::endl;
-        }
-        quickExit(1);
-    }
-    
-    /* parent */
-    pid.setSeparatePG(true);
-    pid.setKillSignal(SIGTERM);
-    logPipe.writeSide.close();
-    worker.childStarted(shared_from_this(),
-        pid, singleton<set<int> >(logPipe.readSide), false, false);
+    /* Tell the hook about system features (beyond the system type)
+       required from the build machine.  (The hook could parse the
+       drv file itself, but this is easier.) */
+    Strings features = tokenizeString(drv.env["requiredSystemFeatures"]);
+    foreach (Strings::iterator, i, features) checkStoreName(*i); /* !!! abuse */
 
-    toHook.readSide.close();
+    /* Send the request to the hook. */
+    writeLine(worker.hook->toHook.writeSide, (format("%1% %2% %3% %4%")
+        % (worker.getNrLocalBuilds() < maxBuildJobs ? "1" : "0")
+        % drv.platform % drvPath % concatStringsSep(",", features)).str());
 
     /* Read the first line of input, which should be a word indicating
        whether the hook wishes to perform the build. */
     string reply;
-    try {
-        while (true) {
-            string s = readLine(logPipe.readSide);
-            if (string(s, 0, 2) == "# ") {
-                reply = string(s, 2);
-                break;
-            }
-            handleChildOutput(logPipe.readSide, s + "\n");
+    while (true) {
+        string s = readLine(worker.hook->fromHook.readSide);
+        if (string(s, 0, 2) == "# ") {
+            reply = string(s, 2);
+            break;
         }
-    } catch (Error & e) {
-        terminateBuildHook(true);
-        throw;
+        s += "\n";
+        writeToStderr((unsigned char *) s.c_str(), s.size());
     }
 
     debug(format("hook reply is `%1%'") % reply);
 
-    if (reply == "decline" || reply == "postpone") {
-        /* Clean up the child.  !!! hacky / should verify */
-        terminateBuildHook();
+    if (reply == "decline" || reply == "postpone")
         return reply == "decline" ? rpDecline : rpPostpone;
-    }
+    else if (reply != "accept")
+        throw Error(format("bad hook reply `%1%'") % reply);
 
-    else if (reply == "accept") {
+    printMsg(lvlTalkative, format("using hook to build path(s) %1%")
+        % showPaths(outputPaths(drv.outputs)));
 
-        printMsg(lvlInfo, format("using hook to build path(s) %1%")
-            % showPaths(outputPaths(drv.outputs)));
-        
-        /* Write the information that the hook needs to perform the
-           build, i.e., the set of input paths, the set of output
-           paths, and the references (pointer graph) in the input
-           paths. */
+    hook = worker.hook;
+    worker.hook.reset();
         
-        Path inputListFN = tmpDir + "/inputs";
-        Path outputListFN = tmpDir + "/outputs";
-        Path referencesFN = tmpDir + "/references";
-
-        /* 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);
+    /* Tell the hook all the 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);
         
-        string s;
-        foreach (PathSet::iterator, i, allInputs) s += *i + "\n";
+    string s;
+    foreach (PathSet::iterator, i, allInputs) s += *i + " ";
+    writeLine(hook->toHook.writeSide, s);
         
-        writeFile(inputListFN, s);
-
-        /* The `outputs' file lists all outputs that have to be copied
-           from the remote system. */
-        s = "";
-        foreach (DerivationOutputs::iterator, i, drv.outputs)
-            s += i->second.path + "\n";
-        writeFile(outputListFN, s);
-
-        /* The `references' file has exactly the format accepted by
-           `nix-store --register-validity'. */
-        writeFile(referencesFN,
-            makeValidityRegistration(allInputs, true, false));
+    /* Tell the hooks the outputs that have to be copied back from the
+       remote system. */
+    s = "";
+    foreach (DerivationOutputs::iterator, i, drv.outputs)
+        s += i->second.path + " ";
+    writeLine(hook->toHook.writeSide, s);
+    
+    hook->toHook.writeSide.close();
 
-        /* Tell the hook to proceed. */
-        writeLine(toHook.writeSide, "okay");
-        toHook.writeSide.close();
+    /* Create the log file and pipe. */
+    Path logFile = openLogFile();
 
-        if (printBuildTrace)
-            printMsg(lvlError, format("@ build-started %1% %2% %3% %4%")
-                % drvPath % drv.outputs["out"].path % drv.platform % logFile);
+    set<int> fds;
+    fds.insert(hook->fromHook.readSide);
+    fds.insert(hook->builderOut.readSide);
+    worker.childStarted(shared_from_this(), hook->pid, fds, false, false);
+    
+    if (printBuildTrace)
+        printMsg(lvlError, format("@ build-started %1% %2% %3% %4%")
+            % drvPath % drv.outputs["out"].path % drv.platform % logFile);
         
-        return rpAccept;
-    }
-
-    else throw Error(format("bad hook reply `%1%'") % reply);
-}
-
-
-void DerivationGoal::terminateBuildHook(bool kill)
-{
-    debug("terminating build hook");
-    pid_t savedPid = pid;
-    if (kill)
-        pid.kill();
-    else
-        pid.wait(true);
-    /* `false' means don't wake up waiting goals, since we want to
-       keep this build slot ourselves. */
-    worker.childTerminated(savedPid, false);
-    toHook.writeSide.close();
-    fdLogFile.close();
-    logPipe.readSide.close();
-    deleteTmpDir(true); /* get rid of the hook's temporary directory */
+    return rpAccept;    
 }
 
 
@@ -1380,11 +1432,7 @@ void DerivationGoal::startBuilder()
         format("building path(s) %1%") % showPaths(outputPaths(drv.outputs)))
     
     /* Right platform? */
-    if (drv.platform != thisSystem 
-#ifdef CAN_DO_LINUX32_BUILDS
-        && !(drv.platform == "i686-linux" && thisSystem == "x86_64-linux")
-#endif
-        )
+    if (!canBuildLocally(drv.platform))
         throw Error(
             format("a `%1%' is required to build `%3%', but I am a `%2%'")
             % drv.platform % thisSystem % drvPath);
@@ -1549,6 +1597,9 @@ void DerivationGoal::startBuilder()
 
     if (fixedOutput) useChroot = false;
 
+    /* Hack to allow derivations to disable chroot builds. */
+    if (drv.env["__noChroot"] == "1") useChroot = false;
+
     if (useChroot) {
 #if CHROOT_ENABLED
         /* Create a temporary directory in which we set up the chroot
@@ -1572,7 +1623,7 @@ void DerivationGoal::startBuilder()
 
         /* 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.  */
+           Samba-in-QEMU. */
         createDirs(chrootRootDir + "/etc");
 
         writeFile(chrootRootDir + "/etc/passwd",
@@ -1580,13 +1631,13 @@ void DerivationGoal::startBuilder()
                 "nixbld:x:%1%:%2%:Nix build user:/:/noshell\n"
                 "nobody:x:65534:65534:Nobody:/:/noshell\n")
                 % (buildUser.enabled() ? buildUser.getUID() : getuid())
-	        % (buildUser.enabled() ? buildUser.getGID() : getgid())).str());
+                % (buildUser.enabled() ? buildUser.getGID() : getgid())).str());
 
 	/* Declare the build user's group so that programs get a consistent
-	   view of the system (e.g., "id -gn").  */
-	writeFile(chrootRootDir + "/etc/group",
-		  (format("nixbld:!:%1%:\n")
-		   % (buildUser.enabled() ? buildUser.getGID() : getgid())).str());
+	   view of the system (e.g., "id -gn"). */
+        writeFile(chrootRootDir + "/etc/group",
+            (format("nixbld:!:%1%:\n")
+                % (buildUser.enabled() ? buildUser.getGID() : getgid())).str());
 
         /* Bind-mount a user-configurable set of directories from the
            host file system.  The `/dev/pts' directory must be mounted
@@ -1645,9 +1696,12 @@ void DerivationGoal::startBuilder()
     printMsg(lvlChatty, format("executing builder `%1%'") %
         drv.builder);
 
-    /* Create the log file and pipe. */
+    /* Create the log file. */
     Path logFile = openLogFile();
     
+    /* Create a pipe to get the output of the builder. */
+    builderOut.create();
+
     /* 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
@@ -1688,18 +1742,23 @@ void DerivationGoal::startBuilder()
                         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.) */
+                /* Do the chroot().  Below we 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);
             }
 #endif
             
-            initChild();
+            commonChildInit(builderOut);
+    
+            if (chdir(tmpDir.c_str()) == -1)
+                throw SysError(format("changing into `%1%'") % tmpDir);
+
+            /* Close all other file descriptors. */
+            closeMostFDs(set<int>());
 
 #ifdef CAN_DO_LINUX32_BUILDS
             if (drv.platform == "i686-linux" && thisSystem == "x86_64-linux") {
@@ -1720,10 +1779,10 @@ void DerivationGoal::startBuilder()
             
             /* 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. */
+               all root privileges.  Note that above we have 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. */
             if (buildUser.enabled()) {
                 printMsg(lvlChatty, format("switching to user `%1%'") % buildUser.getUser());
 
@@ -1777,9 +1836,9 @@ void DerivationGoal::startBuilder()
     
     /* parent */
     pid.setSeparatePG(true);
-    logPipe.writeSide.close();
+    builderOut.writeSide.close();
     worker.childStarted(shared_from_this(), pid,
-        singleton<set<int> >(logPipe.readSide), true, true);
+        singleton<set<int> >(builderOut.readSide), true, true);
 
     if (printBuildTrace) {
         printMsg(lvlError, format("@ build-started %1% %2% %3% %4%")
@@ -1816,7 +1875,7 @@ void DerivationGoal::computeClosure()
     /* When using a build hook, the build hook can register the output
        as valid (by doing `nix-store --import').  If so we don't have
        to do anything here. */
-    if (usingBuildHook) {
+    if (hook) {
         bool allValid = true;
         foreach (DerivationOutputs::iterator, i, drv.outputs)
             if (!worker.store.isValidPath(i->second.path)) allValid = false;
@@ -1944,32 +2003,10 @@ Path DerivationGoal::openLogFile()
     if (fdLogFile == -1)
         throw SysError(format("creating log file `%1%'") % logFileName);
 
-    /* Create a pipe to get the output of the child. */
-    logPipe.create();
-
     return logFileName;
 }
 
 
-void DerivationGoal::initChild()
-{
-    commonChildInit(logPipe);
-    
-    if (chdir(tmpDir.c_str()) == -1)
-        throw SysError(format("changing into `%1%'") % tmpDir);
-
-    /* When running a hook, dup the communication pipes. */
-    if (usingBuildHook) {
-        toHook.writeSide.close();
-        if (dup2(toHook.readSide, STDIN_FILENO) == -1)
-            throw SysError("dupping to-hook read side");
-    }
-
-    /* Close all other file descriptors. */
-    closeMostFDs(set<int>());
-}
-
-
 void DerivationGoal::deleteTmpDir(bool force)
 {
     if (tmpDir != "") {
@@ -1989,19 +2026,22 @@ void DerivationGoal::deleteTmpDir(bool force)
 
 void DerivationGoal::handleChildOutput(int fd, const string & data)
 {
-    if (fd == logPipe.readSide) {
+    if ((hook && fd == hook->builderOut.readSide) ||
+        (!hook && fd == builderOut.readSide))
+    {
         if (verbosity >= buildVerbosity)
             writeToStderr((unsigned char *) data.c_str(), data.size());
         writeFull(fdLogFile, (unsigned char *) data.c_str(), data.size());
     }
 
-    else abort();
+    if (hook && fd == hook->fromHook.readSide)
+        writeToStderr((unsigned char *) data.c_str(), data.size());
 }
 
 
 void DerivationGoal::handleEOF(int fd)
 {
-    if (fd == logPipe.readSide) worker.wakeUp(shared_from_this());
+    worker.wakeUp(shared_from_this());
 }