]> git.localhorst.tv Git - blank.git/blobdiff - src/app/proc.cpp
allow cloned env with child processes
[blank.git] / src / app / proc.cpp
index 3137b0433a990e839a691a3d59f5d023e1c22cf7..dff8dee525fcb8957034e939b91f70fccd9618e8 100644 (file)
@@ -1,5 +1,7 @@
 #include "Process.hpp"
 
+#include "error.hpp"
+
 #ifdef _WIN32
 #  include <tchar.h>
 #  include <windows.h>
@@ -21,18 +23,36 @@ namespace blank {
 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);
+       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];
@@ -50,11 +70,16 @@ struct Process::Impl {
 
 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)) {
 
 }
 
@@ -67,42 +92,61 @@ 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();
 }
 
-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) {
@@ -166,18 +210,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) {
@@ -197,7 +241,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);
 
@@ -212,7 +260,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 +278,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 +310,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 +342,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
 }