#include <cstdlib>
-#include <unistd.h>
-#include <sys/wait.h>
#include <msp/fs/dir.h>
+#include <msp/io/console.h>
+#include <msp/io/file.h>
#include <msp/io/print.h>
+#include <msp/time/timedelta.h>
#include "externaltask.h"
using namespace std;
using namespace Msp;
-ExternalTask::ExternalTask(const vector<string> &a, const FS::Path &wd):
+ExternalTask::ExternalTask(const Arguments &a, const FS::Path &wd):
argv(a),
work_dir(wd),
- pid(-1),
- exit_code(-1)
-{ }
+ process(0),
+ exit_code(-1),
+ stdin_action(PASSTHROUGH),
+ stdout_action(PASSTHROUGH),
+ stderr_action(PASSTHROUGH),
+ capture_pipe(0)
+{
+ if(argv.empty())
+ throw invalid_argument("ExternalTask::ExternalTask");
+}
+
+ExternalTask::~ExternalTask()
+{
+ delete capture_pipe;
+}
string ExternalTask::get_command() const
{
string cmd;
- for(vector<string>::const_iterator i=argv.begin(); i!=argv.end(); ++i)
+ for(const string &a: argv)
{
- if(i!=argv.begin())
+ if(!cmd.empty())
cmd += ' ';
- for(string::const_iterator j=i->begin(); j!=i->end(); ++j)
+ for(char c: a)
{
- if(*j=='"' || *j=='\'' || *j==' ' || *j=='\\' || *j=='&')
+ if(c=='"' || c=='\'' || c==' ' || c=='\\' || c=='&')
cmd += '\\';
- cmd += *j;
+ cmd += c;
}
}
+ if(stdin_action==REDIRECT)
+ {
+ cmd += " <";
+ cmd += stdin_file.str();
+ }
+
+ if(stdout_action==REDIRECT)
+ {
+ cmd += " >";
+ cmd += stdout_file.str();
+ }
+
return cmd;
}
void ExternalTask::start()
{
- if((pid = fork()))
+ IO::File *devnull = 0;
+ IO::File *infile = 0;
+ IO::File *outfile = 0;
+
+ prepare();
+
+ process = new Process;
+
+ if(stdin_action==IGNORE || stdout_action==IGNORE || stderr_action==IGNORE)
{
- if(pid==-1)
- exit_code = 1026;
- else
- exit_code = 0;
+#ifdef _WIN32
+ devnull = new IO::File("nul", IO::M_RDWR);
+#else
+ devnull = new IO::File("/dev/null", IO::M_RDWR);
+#endif
+ if(stdin_action==IGNORE)
+ process->redirect_cin(*devnull);
+ if(stdout_action==IGNORE)
+ process->redirect_cout(*devnull);
+ if(stderr_action==IGNORE)
+ process->redirect_cerr(*devnull);
}
- else
+
+ if(stdout_action==REDIRECT)
{
- vector<const char *> cargv(argv.size()+1);
- for(unsigned i=0; i<argv.size(); ++i)
- cargv[i] = argv[i].c_str();
- cargv.back() = 0;
-
- if(!work_dir.empty())
- FS::chdir(work_dir);
- execvp(cargv.front(), const_cast<char *const *>(&cargv.front()));
- IO::print("Couldn't execute %s\n", argv.front());
- exit(1);
+ outfile = new IO::File((work_dir/stdout_file).str(), IO::M_WRITE);
+ process->redirect_cout(*outfile);
}
+
+ if(stdout_action==CAPTURE || stderr_action==CAPTURE)
+ {
+ capture_pipe = new IO::Pipe;
+ if(stdout_action==CAPTURE)
+ process->redirect_cout(*capture_pipe);
+ if(stderr_action==CAPTURE)
+ process->redirect_cerr(*capture_pipe);
+ }
+
+ if(stdin_action==REDIRECT)
+ {
+ infile = new IO::File((work_dir/stdin_file).str());
+ process->redirect_cin(*infile);
+ }
+
+ if(!work_dir.empty())
+ process->set_working_directory(work_dir);
+
+ Process::Arguments args(argv.begin()+1, argv.end());
+ process->execute(argv.front(), args);
+ if(capture_pipe)
+ capture_pipe->set_mode(IO::M_READ);
+
+ delete devnull;
+ delete infile;
+ delete outfile;
}
Task::Status ExternalTask::check()
{
- if(pid>0)
+ return do_wait(false);
+}
+
+Task::Status ExternalTask::wait()
+{
+ return do_wait(true);
+}
+
+Task::Status ExternalTask::do_wait(bool block)
+{
+ while(process)
{
- int status;
- if(waitpid(pid, &status, WNOHANG)==pid)
+ if(process->wait(block && !capture_pipe))
+ {
+ exit_code = process->get_exit_code();
+ delete process;
+ process = 0;
+ }
+
+ // Do this after waiting to avoid a race condition
+ while(capture_pipe && IO::poll(*capture_pipe, IO::P_INPUT, 10*Time::msec))
{
- if(WIFEXITED(status))
- exit_code = WEXITSTATUS(status);
- else if(WIFSIGNALED(status))
- exit_code = 256+WTERMSIG(status);
+ char buf[1024];
+ unsigned len = capture_pipe->read(buf, sizeof(buf));
+ if(len)
+ output.append(buf, len);
else
- exit_code = 1025;
- pid = 0;
+ break;
+ }
- signal_finished.emit(!exit_code);
+ if(process)
+ {
+ if(!block)
+ return RUNNING;
}
else
- return RUNNING;
+ signal_finished.emit(!exit_code);
}
return exit_code ? ERROR : SUCCESS;
}
+
+void ExternalTask::set_stdin(const FS::Path &f)
+{
+ stdin_action = REDIRECT;
+ stdin_file = f;
+}
+
+void ExternalTask::set_stdout(StreamAction a)
+{
+ if(a==REDIRECT)
+ throw invalid_argument("ExternalTask::set_stdout");
+ stdout_action = a;
+}
+
+void ExternalTask::set_stdout(const FS::Path &f)
+{
+ stdout_action = REDIRECT;
+ stdout_file = f;
+}
+
+void ExternalTask::set_stderr(StreamAction a)
+{
+ if(a==REDIRECT)
+ throw invalid_argument("ExternalTask::set_stdout");
+ stderr_action = a;
+}
+
+string ExternalTask::run_and_capture_output(const Arguments &argv, const FS::Path &wd, bool capture_stderr)
+{
+ ExternalTask task(argv, wd);
+ task.stdin_action = IGNORE;
+ task.set_stdout(CAPTURE);
+ task.set_stderr(capture_stderr ? CAPTURE : IGNORE);
+ task.start();
+ if(task.wait()!=SUCCESS)
+ throw runtime_error(format("%s failed", argv.front()));
+ return task.get_output();
+}