about summary refs log tree commit diff
path: root/src/libstore/exec.cc
blob: 2adf03841c4fc9cd3384b05c91a6e0d96b8de210 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include <iostream>
#include <cstdio>

#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"


static string pathNullDevice = "/dev/null";


/* Run a program. */
void runProgram(const string & program, 
    const Strings & args, const Environment & env,
    const string & logFileName)
{
    /* Create a log file. */
    string logCommand = 
	verbosity >= lvlDebug 
	? "tee "  + 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) {
	    printMsg(lvlTalkative, 
		format("program `%1%' failed; keeping build directory `%2%'")
                % program % tmpDir);
	    delTmpDir.cancel();
	}
        if (WIFEXITED(status))
            throw Error(format("program `%1%' failed with exit code %2%")
                % program % WEXITSTATUS(status));
        else if (WIFSIGNALED(status))
            throw Error(format("program `%1%' failed due to signal %2%")
                % program % WTERMSIG(status));
        else
            throw Error(format("program `%1%' died abnormally") % program);
    }
}