X-Git-Url: http://git.localhorst.tv/?a=blobdiff_plain;f=src%2Fapp%2Fproc.cpp;h=876eefc0527aaf110d215a3c0ec683c1cae6c966;hb=0644360107ca50d681ae8b8cad608c7bc2ec7a40;hp=f30c0cb2dcab508be7ee480fb63955f8fef721ff;hpb=307482574d3acac09e38263de521a1826ceec7ce;p=blank.git diff --git a/src/app/proc.cpp b/src/app/proc.cpp index f30c0cb..876eefc 100644 --- a/src/app/proc.cpp +++ b/src/app/proc.cpp @@ -1,5 +1,7 @@ #include "Process.hpp" +#include "error.hpp" + #ifdef _WIN32 # include # include @@ -27,12 +29,25 @@ struct Process::Impl { ~Impl(); size_t WriteIn(const void *buffer, size_t max_len); + void CloseIn(); + size_t ReadOut(void *buffer, size_t max_len); + void CloseOut(); + size_t ReadErr(void *buffer, size_t max_len); + 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]; @@ -52,9 +67,7 @@ Process::Process( const string &path, const Arguments &args, const Environment &env) -: impl(new Impl(path, args, env)) -, joined(false) -, status(0) { +: impl(new Impl(path, args, env)) { } @@ -67,36 +80,48 @@ size_t Process::WriteIn(const void *buffer, size_t max_len) { return impl->WriteIn(buffer, max_len); } +void Process::CloseIn() { + impl->CloseIn(); +} + size_t Process::ReadOut(void *buffer, size_t max_len) { return impl->ReadOut(buffer, max_len); } +void Process::CloseOut() { + impl->CloseOut(); +} + size_t Process::ReadErr(void *buffer, size_t max_len) { return impl->ReadErr(buffer, max_len); } +void Process::CloseErr() { + impl->CloseErr(); +} + void Process::Terminate() { - if (!joined) { - impl->Terminate(); - } + impl->Terminate(); +} + +bool Process::Terminated() { + return impl->Terminated(); } int Process::Join() { - if (joined) { - return status; - } else { - status = impl->Join(); - joined = true; - return status; - } + return impl->Join(); } Process::Impl::Impl( const string &path_in, const Arguments &args, - const Environment &env -) { + const Environment &env) +: joined(false) +, status(0) +, in_closed(false) +, out_closed(false) +, err_closed(false) { const char *path = path_in.c_str(); char *envp[env.size() + 1]; for (size_t i = 0; i < env.size(); ++i) { @@ -166,18 +191,18 @@ Process::Impl::Impl( 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) { @@ -212,7 +237,9 @@ Process::Impl::Impl( #endif Process::Impl::~Impl() { - + CloseIn(); + CloseOut(); + CloseErr(); } size_t Process::Impl::WriteIn(const void *buffer, size_t max_len) { @@ -228,13 +255,25 @@ 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 } +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) { #ifdef _WIN32 DWORD ret; @@ -248,13 +287,25 @@ size_t Process::Impl::ReadOut(void *buffer, size_t max_len) { 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 } +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) { #ifdef _WIN32 DWORD ret; @@ -268,52 +319,118 @@ size_t Process::Impl::ReadErr(void *buffer, size_t max_len) { 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 }