about summary refs log tree commit diff
path: root/src/libnix/exec.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnix/exec.cc')
-rw-r--r--src/libnix/exec.cc144
1 files changed, 144 insertions, 0 deletions
diff --git a/src/libnix/exec.cc b/src/libnix/exec.cc
new file mode 100644
index 000000000000..2e092b2e0dd6
--- /dev/null
+++ b/src/libnix/exec.cc
@@ -0,0 +1,144 @@
+#include <iostream>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "exec.hh"
+#include "util.hh"
+#include "globals.hh"
+
+
+class AutoDelete
+{
+    string path;
+    bool del;
+public:
+
+    AutoDelete(const string & p) : path(p) 
+    {
+        del = true;
+    }
+
+    ~AutoDelete()
+    {
+        if (del) deletePath(path);
+    }
+
+    void cancel()
+    {
+        del = false;
+    }
+};
+
+
+static string pathNullDevice = "/dev/null";
+
+
+/* Run a program. */
+void runProgram(const string & program, 
+    const Strings & args, const Environment & env)
+{
+    /* Create a log file. */
+    string logFileName = nixLogDir + "/run.log";
+    string logCommand = 
+	verbosity >= lvlDebug 
+	? "tee -a "  + logFileName + " >&2"
+	: "cat >> " + logFileName;
+    /* !!! auto-pclose on exit */
+    FILE * logFile = popen(logCommand.c_str(), "w"); /* !!! escaping */
+    if (!logFile)
+        throw SysError(format("creating log file `%1%'") % logFileName);
+
+    /* Create a temporary directory where the build will take
+       place. */
+    string tmpDir = createTempDir();
+
+    AutoDelete delTmpDir(tmpDir);
+
+    /* Fork a child to build the package. */
+    pid_t pid;
+    switch (pid = fork()) {
+            
+    case -1:
+        throw SysError("unable to fork");
+
+    case 0: 
+
+        try { /* child */
+
+            if (chdir(tmpDir.c_str()) == -1)
+                throw SysError(format("changing into to `%1%'") % tmpDir);
+
+            /* Fill in the arguments. */
+            const char * argArr[args.size() + 2];
+            const char * * p = argArr;
+            string progName = baseNameOf(program);
+            *p++ = progName.c_str();
+            for (Strings::const_iterator i = args.begin();
+                 i != args.end(); i++)
+                *p++ = i->c_str();
+            *p = 0;
+
+            /* Fill in the environment. */
+            Strings envStrs;
+            const char * envArr[env.size() + 1];
+            p = envArr;
+            for (Environment::const_iterator i = env.begin();
+                 i != env.end(); i++)
+                *p++ = envStrs.insert(envStrs.end(), 
+                    i->first + "=" + i->second)->c_str();
+            *p = 0;
+
+            /* Dup the log handle into stderr. */
+            if (dup2(fileno(logFile), STDERR_FILENO) == -1)
+                throw SysError("cannot pipe standard error into log file");
+            
+            /* Dup stderr to stdin. */
+            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");
+
+            /* Execute the program.  This should not return. */
+            execve(program.c_str(), (char * *) argArr, (char * *) envArr);
+
+            throw SysError(format("unable to execute %1%") % program);
+            
+        } catch (exception & e) {
+            cerr << format("build error: %1%\n") % e.what();
+        }
+        _exit(1);
+
+    }
+
+    /* parent */
+
+    /* Close the logging pipe.  Note that this should not cause
+       the logger to exit until builder exits (because the latter
+       has an open file handle to the former). */
+    pclose(logFile);
+    
+    /* Wait for the child to finish. */
+    int status;
+    if (waitpid(pid, &status, 0) != pid)
+        throw Error("unable to wait for child");
+    
+    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+	if (keepFailed) {
+	    msg(lvlTalkative, 
+		format("build failed; keeping build directory `%1%'") % tmpDir);
+	    delTmpDir.cancel();
+	}
+        throw Error("unable to build package");
+    }
+}
+
+