]> git.tdb.fi Git - builder.git/blob - source/externaltask.cpp
Utility function for capturing command output
[builder.git] / source / externaltask.cpp
1 #include <cstdlib>
2 #include <unistd.h>
3 #include <sys/wait.h>
4 #include <msp/fs/dir.h>
5 #include <msp/io/console.h>
6 #include <msp/io/file.h>
7 #include <msp/io/print.h>
8 #include <msp/time/units.h>
9 #include "externaltask.h"
10
11 using namespace std;
12 using namespace Msp;
13
14 ExternalTask::ExternalTask(const vector<string> &a, const FS::Path &wd):
15         argv(a),
16         work_dir(wd),
17         pid(-1),
18         exit_code(-1),
19         stdout_dest(PASSTHROUGH),
20         stderr_dest(PASSTHROUGH),
21         capture_pipe(0)
22 { }
23
24 ExternalTask::~ExternalTask()
25 {
26         delete capture_pipe;
27 }
28
29 string ExternalTask::get_command() const
30 {
31         string cmd;
32         for(vector<string>::const_iterator i=argv.begin(); i!=argv.end(); ++i)
33         {
34                 if(i!=argv.begin())
35                         cmd += ' ';
36
37                 for(string::const_iterator j=i->begin(); j!=i->end(); ++j)
38                 {
39                         if(*j=='"' || *j=='\'' || *j==' ' || *j=='\\' || *j=='&')
40                                 cmd += '\\';
41                         cmd += *j;
42                 }
43         }
44
45         return cmd;
46 }
47
48 void ExternalTask::start()
49 {
50         if(stdout_dest==CAPTURE || stderr_dest==CAPTURE)
51                 capture_pipe = new IO::Pipe;
52
53         if((pid = fork()))
54         {
55                 if(pid==-1)
56                         exit_code = 1026;
57                 else
58                         exit_code = 0;
59         }
60         else
61         {
62                 vector<const char *> cargv(argv.size()+1);
63                 for(unsigned i=0; i<argv.size(); ++i)
64                         cargv[i] = argv[i].c_str();
65                 cargv.back() = 0;
66
67                 if(stdout_dest==IGNORE || stderr_dest==IGNORE)
68                 {
69                         IO::File devnull("/dev/null", IO::M_WRITE);
70                         if(stdout_dest==IGNORE)
71                                 IO::cout.redirect(devnull);
72                         if(stderr_dest==IGNORE)
73                                 IO::cerr.redirect(devnull);
74                 }
75
76                 if(capture_pipe)
77                 {
78                         if(stdout_dest==CAPTURE)
79                                 IO::cout.redirect(*capture_pipe);
80                         if(stderr_dest==CAPTURE)
81                                 IO::cerr.redirect(*capture_pipe);
82                         delete capture_pipe;
83                 }
84
85                 if(!work_dir.empty())
86                         FS::chdir(work_dir);
87                 execvp(cargv.front(), const_cast<char *const *>(&cargv.front()));
88                 IO::print("Couldn't execute %s\n", argv.front());
89                 exit(1);
90         }
91 }
92
93 Task::Status ExternalTask::check()
94 {
95         if(pid>0)
96         {
97                 // XXX This is sub-optimal, should have support for a blocking wait
98                 int status;
99                 if(waitpid(pid, &status, WNOHANG)==pid)
100                 {
101                         if(WIFEXITED(status))
102                                 exit_code = WEXITSTATUS(status);
103                         else if(WIFSIGNALED(status))
104                                 exit_code = 256+WTERMSIG(status);
105                         else
106                                 exit_code = 1025;
107                         pid = 0;
108                 }
109
110                 // Do this after waiting to avoid a race condition
111                 if(capture_pipe)
112                 {
113                         while(IO::poll(*capture_pipe, IO::P_INPUT, Time::zero))
114                         {
115                                 char buf[1024];
116                                 unsigned len = capture_pipe->read(buf, sizeof(buf));
117                                 output.append(buf, len);
118                                 if(len<sizeof(buf))
119                                         break;
120                         }
121                 }
122
123                 if(pid>0)
124                         return RUNNING;
125                 else
126                         signal_finished.emit(!exit_code);
127         }
128
129         return exit_code ? ERROR : SUCCESS;
130 }
131
132 void ExternalTask::set_stdout(Destination d)
133 {
134         stdout_dest = d;
135 }
136
137 void ExternalTask::set_stderr(Destination d)
138 {
139         stderr_dest = d;
140 }
141
142 string ExternalTask::run_and_capture_output(const Arguments &argv, const FS::Path &wd)
143 {
144         ExternalTask task(argv, wd);
145         task.set_stdout(CAPTURE);
146         task.set_stderr(IGNORE);
147         task.start();
148         Task::Status status;
149         while((status=task.check())==RUNNING) ;
150         if(status!=SUCCESS)
151                 throw runtime_error(format("%s failed", argv.front()));
152         return task.get_output();
153 }