#include "Process.hpp"
+#include "error.hpp"
+
#ifdef _WIN32
# include <tchar.h>
# include <windows.h>
# include <fcntl.h>
# include <signal.h>
# include <unistd.h>
+# include <sys/select.h>
# include <sys/wait.h>
#endif
struct Process::Impl {
Impl(
- const string &path_in,
- const vector<string> &args,
- const vector<string> &env);
+ const string &path,
+ const Arguments &args,
+ char *const env[]);
~Impl();
+ static Impl *EnvHelper(
+ const string &path,
+ const Arguments &args,
+ const Environment &env);
+
size_t WriteIn(const void *buffer, size_t max_len);
- size_t ReadOut(void *buffer, size_t max_len);
- size_t ReadErr(void *buffer, size_t max_len);
+ void CloseIn();
+
+ size_t ReadOut(void *buffer, size_t max_len, int timeout);
+ void CloseOut();
+
+ size_t ReadErr(void *buffer, size_t max_len, int timeout);
+ void CloseErr();
void Terminate();
+ bool Terminated();
int Join();
+ bool joined;
+ int status;
+
+ bool in_closed;
+ bool out_closed;
+ bool err_closed;
+
#ifdef _WIN32
PROCESS_INFORMATION pi;
HANDLE fd_in[2];
Process::Process(
const string &path,
- const vector<string> &args,
- const vector<string> &env)
-: impl(new Impl(path, args, env))
-, joined(false)
-, status(0) {
+ const Arguments &args)
+: impl(new Impl(path, args, nullptr)) {
+
+}
+
+Process::Process(
+ const string &path,
+ const Arguments &args,
+ const Environment &env)
+: impl(Impl::EnvHelper(path, args, env)) {
}
return impl->WriteIn(buffer, max_len);
}
-size_t Process::ReadOut(void *buffer, size_t max_len) {
- return impl->ReadOut(buffer, max_len);
+void Process::CloseIn() {
+ impl->CloseIn();
}
-size_t Process::ReadErr(void *buffer, size_t max_len) {
- return impl->ReadErr(buffer, max_len);
+size_t Process::ReadOut(void *buffer, size_t max_len, int timeout) {
+ return impl->ReadOut(buffer, max_len, timeout);
+}
+
+void Process::CloseOut() {
+ impl->CloseOut();
+}
+
+size_t Process::ReadErr(void *buffer, size_t max_len, int timeout) {
+ return impl->ReadErr(buffer, max_len, timeout);
+}
+
+void Process::CloseErr() {
+ impl->CloseErr();
}
void Process::Terminate() {
- if (!joined) {
- impl->Terminate();
- }
+ impl->Terminate();
}
-int Process::Join() {
- if (joined) {
- return status;
- } else {
- status = impl->Join();
- joined = true;
- return status;
- }
+bool Process::Terminated() {
+ return impl->Terminated();
}
+int Process::Join() {
+ return impl->Join();
+}
-Process::Impl::Impl(
- const string &path_in,
- const vector<string> &args,
- const vector<string> &env
+Process::Impl *Process::Impl::EnvHelper(
+ const string &path,
+ const Arguments &args,
+ const Environment &env
) {
- const char *path = path_in.c_str();
char *envp[env.size() + 1];
for (size_t i = 0; i < env.size(); ++i) {
envp[i] = const_cast<char *>(env[i].c_str());
}
envp[env.size()] = nullptr;
+ return new Impl(path, args, envp);
+}
+
+Process::Impl::Impl(
+ const string &path_in,
+ const Arguments &args,
+ char *const envp[])
+: joined(false)
+, status(0)
+, in_closed(false)
+, out_closed(false)
+, err_closed(false) {
+ const char *path = path_in.c_str();
#ifdef _WIN32
string cmdline;
for (const auto &arg : args) {
argv[args.size()] = nullptr;
if (pipe(fd_in) != 0) {
- throw runtime_error("failed to open pipe for child process' stdin");
+ throw SysError("failed to open pipe for child process' stdin");
}
if (pipe(fd_out) != 0) {
- throw runtime_error("failed to open pipe for child process' stdout");
+ throw SysError("failed to open pipe for child process' stdout");
}
if (pipe(fd_err) != 0) {
- throw runtime_error("failed to open pipe for child process' stderr");
+ throw SysError("failed to open pipe for child process' stderr");
}
pid = fork();
if (pid == -1) {
- throw runtime_error("fork");
+ throw SysError("fork");
} else if (pid == 0) {
if (dup2(fd_in[0], STDIN_FILENO) == -1) {
close(fd_err[0]);
close(fd_err[1]);
- execve(path, argv, envp);
+ if (envp) {
+ execve(path, argv, envp);
+ } else {
+ execv(path, argv);
+ }
// if execve returns, something bad happened
exit(EXIT_FAILURE);
#endif
Process::Impl::~Impl() {
-
+ CloseIn();
+ CloseOut();
+ CloseErr();
}
size_t Process::Impl::WriteIn(const void *buffer, size_t max_len) {
if (errno == EAGAIN) {
return 0;
} else {
- throw runtime_error("failed to write to child process' input stream");
+ throw SysError("failed to write to child process' input stream");
}
}
return written;
#endif
}
-size_t Process::Impl::ReadOut(void *buffer, size_t max_len) {
+void Process::Impl::CloseIn() {
+ if (in_closed) {
+ return;
+ }
#ifdef _WIN32
+ CloseHandle(fd_in[1]);
+#else
+ close(fd_in[1]);
+#endif
+ in_closed = true;
+}
+
+size_t Process::Impl::ReadOut(void *buffer, size_t max_len, int timeout) {
+#ifdef _WIN32
+ // TODO: timeout implementation for windows child process I/O
DWORD ret;
if (!ReadFile(fd_out[0], buffer, max_len, &ret, nullptr)) {
throw runtime_error("failed to read from child process' output stream");
}
return ret;
#else
+ if (timeout >= 0) {
+ fd_set read_set;
+ fd_set error_set;
+ FD_ZERO(&read_set);
+ FD_ZERO(&error_set);
+ FD_SET(fd_out[0], &read_set);
+ FD_SET(fd_out[0], &error_set);
+ timeval timer;
+ timer.tv_sec = timeout / 1000;
+ timer.tv_usec = (timeout % 1000) * 1000;
+ if (select(fd_out[0] + 1, &read_set, nullptr, &error_set, &timer) == -1) {
+ throw SysError("error waiting on child process' output stream");
+ }
+ if (FD_ISSET(fd_out[0], &error_set)) {
+ throw runtime_error("error condition on child process' output stream");
+ }
+ if (!FD_ISSET(fd_out[0], &read_set)) {
+ throw runtime_error("timeout while waiting on child process' output stream");
+ }
+ }
int ret = read(fd_out[0], buffer, max_len);
if (ret < 0) {
if (errno == EAGAIN) {
return 0;
} else {
- throw runtime_error("failed to read from child process' output stream");
+ throw SysError("failed to read from child process' output stream");
}
}
return ret;
#endif
}
-size_t Process::Impl::ReadErr(void *buffer, size_t max_len) {
+void Process::Impl::CloseOut() {
+ if (out_closed) {
+ return;
+ }
#ifdef _WIN32
+ CloseHandle(fd_out[0]);
+#else
+ close(fd_out[0]);
+#endif
+ out_closed = true;
+}
+
+size_t Process::Impl::ReadErr(void *buffer, size_t max_len, int timeout) {
+#ifdef _WIN32
+ // TODO: timeout implementation for windows child process I/O
DWORD ret;
if (!ReadFile(fd_err[0], buffer, max_len, &ret, nullptr)) {
throw runtime_error("failed to read from child process' error stream");
}
return ret;
#else
+ if (timeout >= 0) {
+ fd_set read_set;
+ fd_set error_set;
+ FD_ZERO(&read_set);
+ FD_ZERO(&error_set);
+ FD_SET(fd_err[0], &read_set);
+ FD_SET(fd_err[0], &error_set);
+ timeval timer;
+ timer.tv_sec = timeout / 1000;
+ timer.tv_usec = (timeout % 1000) * 1000;
+ if (select(fd_err[0] + 1, &read_set, nullptr, &error_set, &timer) == -1) {
+ throw SysError("error waiting on child process' error stream");
+ }
+ if (FD_ISSET(fd_err[0], &error_set)) {
+ throw runtime_error("error condition on child process' error stream");
+ }
+ if (!FD_ISSET(fd_err[0], &read_set)) {
+ throw runtime_error("timeout while waiting on child process' error stream");
+ }
+ }
int ret = read(fd_err[0], buffer, max_len);
if (ret < 0) {
if (errno == EAGAIN) {
return 0;
} else {
- throw runtime_error("failed to read from child process' error stream");
+ throw SysError("failed to read from child process' error stream");
}
}
return ret;
#endif
}
+void Process::Impl::CloseErr() {
+ if (err_closed) {
+ return;
+ }
+#ifdef _WIN32
+ CloseHandle(fd_err[0]);
+#else
+ close(fd_err[0]);
+#endif
+ err_closed = true;
+}
+
void Process::Impl::Terminate() {
+ if (joined) {
+ // can only terminate once
+ return;
+ }
#ifdef _WIN32
if (!TerminateProcess(pi.hProcess, -1)) {
#else
if (kill(pid, SIGTERM) == -1) {
#endif
- throw runtime_error("failed to terminate child process");
+ throw SysError("failed to terminate child process");
}
}
-int Process::Impl::Join() {
+bool Process::Impl::Terminated() {
+ if (joined) {
+ return true;
+ }
#ifdef _WIN32
- CloseHandle(fd_in[1]);
- CloseHandle(fd_out[0]);
- CloseHandle(fd_err[0]);
+ DWORD exit_code;
+ GetExitCodeProcess(pi.hProcess, &exit_code);
+ if (exit_code == STILL_ACTIVE) {
+ return false;
+ } else {
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ status = exit_code;
+ joined = true;
+ return true;
+ }
+#else
+ int stat;
+ int result = waitpid(pid, &stat, WNOHANG);
+ if (result == -1) {
+ throw SysError("error polling child process");
+ } else if (result == 0) {
+ return false;
+ } else if (result == pid) {
+ // child just exited, reap
+ if (WIFEXITED(stat)) {
+ // autonomous termination
+ status = WEXITSTATUS(stat);
+ } else if (WIFSIGNALED(stat)) {
+ // signalled termination
+ status = WTERMSIG(stat);
+ }
+ joined = true;
+ return true;
+ } else {
+ throw runtime_error("bogus return value of waitpid");
+ }
+#endif
+}
+int Process::Impl::Join() {
+ if (joined) {
+ // can only join once
+ return status;
+ }
+#ifdef _WIN32
DWORD exit_code;
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &exit_code);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
- return exit_code;
+ status = exit_code;
+ joined = true;
+ return status;
#else
- // close streams before waiting on child termination
- close(fd_in[1]);
- close(fd_out[0]);
- close(fd_err[0]);
-
while (true) {
- int status;
- int result = waitpid(pid, &status, 0);
+ int stat;
+ int result = waitpid(pid, &stat, 0);
if (result == -1) {
- throw runtime_error("error waiting on child process");
+ throw SysError("error waiting on child process");
+ }
+ if (result != pid) {
+ // should in theory only happen with WNOHANG set
+ continue;
+ }
+ if (WIFEXITED(stat)) {
+ // autonomous termination
+ status = WEXITSTATUS(stat);
+ joined = true;
+ return status;
}
- if (result == pid && WIFEXITED(status)) {
- return WEXITSTATUS(status);
+ if (WIFSIGNALED(stat)) {
+ // signalled termination
+ status = WTERMSIG(stat);
+ joined = true;
+ return status;
}
- // otherwise, child probably signalled, which we don't care
- // about (please don't tell youth welfare), so try again
+ // otherwise, child probably signalled stop/continue, which we
+ // don't care about (please don't tell youth welfare), so try again
}
#endif
}