X-Git-Url: http://git.localhorst.tv/?a=blobdiff_plain;f=src%2Fapp%2Fproc.cpp;h=1373a9107ff8029f1a80ab39adb9f501e02bba70;hb=7f829070c9a5e4e036b483863e5ee75a3a824c38;hp=add16d1787a4779068b6ce36b06be9c9d1d9e194;hpb=fab49d91255ef7d265817213c3da7e5f81df97a0;p=blank.git diff --git a/src/app/proc.cpp b/src/app/proc.cpp index add16d1..1373a91 100644 --- a/src/app/proc.cpp +++ b/src/app/proc.cpp @@ -9,6 +9,7 @@ # include # include # include +# include # include #endif @@ -23,18 +24,36 @@ namespace blank { struct Process::Impl { Impl( - const string &path_in, + const string &path, const Arguments &args, - const Environment &env); + 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]; @@ -50,13 +69,18 @@ struct Process::Impl { }; +Process::Process( + const string &path, + const Arguments &args) +: impl(new Impl(path, args, nullptr)) { + +} + Process::Process( const string &path, const Arguments &args, const Environment &env) -: impl(new Impl(path, args, env)) -, joined(false) -, status(0) { +: impl(Impl::EnvHelper(path, args, env)) { } @@ -69,42 +93,61 @@ size_t Process::WriteIn(const void *buffer, size_t max_len) { 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, +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(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) { @@ -199,7 +242,11 @@ Process::Impl::Impl( 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); @@ -214,7 +261,9 @@ Process::Impl::Impl( #endif Process::Impl::~Impl() { - + CloseIn(); + CloseOut(); + CloseErr(); } size_t Process::Impl::WriteIn(const void *buffer, size_t max_len) { @@ -237,14 +286,47 @@ size_t Process::Impl::WriteIn(const void *buffer, size_t max_len) { #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) { @@ -257,14 +339,47 @@ size_t Process::Impl::ReadOut(void *buffer, size_t max_len) { #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) { @@ -277,7 +392,23 @@ size_t Process::Impl::ReadErr(void *buffer, size_t max_len) { #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 @@ -287,27 +418,64 @@ void Process::Impl::Terminate() { } } -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 SysError("error waiting on child process"); } @@ -315,13 +483,17 @@ int Process::Impl::Join() { // should in theory only happen with WNOHANG set continue; } - if (WIFEXITED(status)) { + if (WIFEXITED(stat)) { // autonomous termination - return WEXITSTATUS(status); + status = WEXITSTATUS(stat); + joined = true; + return status; } - if (WIFSIGNALED(status)) { + if (WIFSIGNALED(stat)) { // signalled termination - return WTERMSIG(status); + status = WTERMSIG(stat); + joined = true; + return status; } // otherwise, child probably signalled stop/continue, which we // don't care about (please don't tell youth welfare), so try again