]> git.localhorst.tv Git - blank.git/blob - src/app/proc.cpp
Process
[blank.git] / src / app / proc.cpp
1 #include "Process.hpp"
2
3 #ifdef __WIN32
4 #  error "TODO: windows implementation of Process"
5 #else
6 #  include <cstdio>
7 #  include <fcntl.h>
8 #  include <unistd.h>
9 #  include <sys/wait.h>
10 #endif
11
12 #include <stdexcept>
13
14 using namespace std;
15
16
17 namespace blank {
18
19 struct Process::Impl {
20
21         Impl(
22                 const string &path_in,
23                 const vector<string> &args,
24                 const vector<string> &env);
25         ~Impl();
26
27         size_t WriteIn(const void *buffer, size_t max_len);
28         size_t ReadOut(void *buffer, size_t max_len);
29         size_t ReadErr(void *buffer, size_t max_len);
30
31         int Join();
32
33 #ifdef __WIN32
34         PROCESS_INFORMATION pi;
35         HANDLE fd_in[2];
36         HANDLE fd_out[2];
37         HANDLE fd_err[2];
38 #else
39         int pid;
40         int fd_in[2];
41         int fd_out[2];
42         int fd_err[2];
43 #endif
44
45 };
46
47
48 Process::Process(
49         const string &path,
50         const vector<string> &args,
51         const vector<string> &env)
52 : impl(new Impl(path, args, env))
53 , joined(false)
54 , status(0) {
55
56 }
57
58 Process::~Process() {
59         Join();
60 }
61
62
63 size_t Process::WriteIn(const void *buffer, size_t max_len) {
64         return impl->WriteIn(buffer, max_len);
65 }
66
67 size_t Process::ReadOut(void *buffer, size_t max_len) {
68         return impl->ReadOut(buffer, max_len);
69 }
70
71 size_t Process::ReadErr(void *buffer, size_t max_len) {
72         return impl->ReadErr(buffer, max_len);
73 }
74
75 int Process::Join() {
76         if (joined) {
77                 return status;
78         } else {
79                 status = impl->Join();
80                 joined = true;
81                 return status;
82         }
83 }
84
85
86 Process::Impl::Impl(
87         const string &path_in,
88         const vector<string> &args,
89         const vector<string> &env
90 ) {
91         const char *path = path_in.c_str();
92         char *envp[env.size() + 1];
93         for (size_t i = 0; i < env.size(); ++i) {
94                 envp[i] = const_cast<char *>(env[i].c_str());
95         }
96         envp[env.size()] = nullptr;
97 #ifdef __WIN32
98         string cmdline;
99         for (const auto &arg : args) {
100                 cmdline += '"';
101                 cmdline += arg;
102                 cmdline += '"';
103         }
104
105         SECURITY_ATTRIBUTES sa;
106         sa.nLength = sizeof(SECURITY_ATTRIBUTES);
107         sa.bInheritHandle = true;
108         sa.lpSecurityDescriptor = nullptr;
109         if (!CreatePipe(&fd_in[0], &fd_in[1], &sa, 0)) {
110                 throw runtime_error("failed to open pipe for child process' stdin");
111         }
112         if (!SetHandleInformation(fd_in[1], HANDLE_FLAG_INHERIT, 0)) {
113                 throw runtime_error("failed to stop child process from inheriting stdin write handle");
114         }
115         if (!CreatePipe(&fd_out[0], &fd_out[1], &sa, 0)) {
116                 throw runtime_error("failed to open pipe for child process' stdout");
117         }
118         if (!SetHandleInformation(fd_out[0], HANDLE_FLAG_INHERIT, 0)) {
119                 throw runtime_error("failed to stop child process from inheriting stdout read handle");
120         }
121         if (!CreatePipe(&fd_err[0], &fd_err[1], &sa, 0)) {
122                 throw runtime_error("failed to open pipe for child process' stderr");
123         }
124         if (!SetHandleInformation(fd_err[0], HANDLE_FLAG_INHERIT, 0)) {
125                 throw runtime_error("failed to stop child process from inheriting stderr read handle");
126         }
127
128         STARTUPINFO si;
129         ZeroMemory(&si, sizeof(STARTUPINFO));
130         si.cb = sizeof(STARTUPINFO);
131         si.hStdError = fd_err[1];
132         si.hStdOutput = fd_out[1];
133         si.hStdInput = fd_in[0];
134         si.dwFlags |= STARTF_USESTDHANDLES;
135         ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
136
137         if (!CreateProcess(
138                 path,
139                 cmdline.c_str(),
140                 nullptr,
141                 nullptr,
142                 true,
143                 0,
144                 envp,
145                 nullptr,
146                 &si,
147                 &pi
148         )) {
149                 throw runtime_error("CreateProcess");
150         }
151 #else
152         char *argv[args.size() + 1];
153         for (size_t i = 0; i < args.size(); ++i) {
154                 // I was promised exec won't modify my characters
155                 argv[i] = const_cast<char *>(args[i].c_str());
156         }
157         argv[args.size()] = nullptr;
158
159         if (pipe(fd_in) != 0) {
160                 throw runtime_error("failed to open pipe for child process' stdin");
161         }
162         if (pipe(fd_out) != 0) {
163                 throw runtime_error("failed to open pipe for child process' stdout");
164         }
165         if (pipe(fd_err) != 0) {
166                 throw runtime_error("failed to open pipe for child process' stderr");
167         }
168
169         pid = fork();
170         if (pid == -1) {
171                 throw runtime_error("fork");
172         } else if (pid == 0) {
173
174                 if (dup2(fd_in[0], STDIN_FILENO) == -1) {
175                         exit(EXIT_FAILURE);
176                 }
177                 if (dup2(fd_out[1], STDOUT_FILENO) == -1) {
178                         exit(EXIT_FAILURE);
179                 }
180                 if (dup2(fd_err[1], STDERR_FILENO) == -1) {
181                         exit(EXIT_FAILURE);
182                 }
183
184                 close(fd_in[0]);
185                 close(fd_in[1]);
186                 close(fd_out[0]);
187                 close(fd_out[1]);
188                 close(fd_err[0]);
189                 close(fd_err[1]);
190
191                 execve(path, argv, envp);
192                 // if execve returns, something bad happened
193                 exit(EXIT_FAILURE);
194
195         } else {
196
197                 close(fd_in[0]);
198                 close(fd_out[1]);
199                 close(fd_err[1]);
200
201         }
202 }
203 #endif
204
205 Process::Impl::~Impl() {
206
207 }
208
209 size_t Process::Impl::WriteIn(const void *buffer, size_t max_len) {
210 #ifdef __WIN32
211         DWORD written;
212         if (!WriteFile(fd_in[1], buffer, max_len, &written, nullptr)) {
213                 throw runtime_error("failed to write to child process' input stream");
214         }
215         return written;
216 #else
217         int written = write(fd_in[1], buffer, max_len);
218         if (written < 0) {
219                 if (errno == EAGAIN) {
220                         return 0;
221                 } else {
222                         throw runtime_error("failed to write to child process' input stream");
223                 }
224         }
225         return written;
226 #endif
227 }
228
229 size_t Process::Impl::ReadOut(void *buffer, size_t max_len) {
230 #ifdef __WIN32
231         DWORD ret;
232         if (!ReadFile(fd_out[0], buffer, max_len, &ret, nullptr)) {
233                 throw runtime_error("failed to read from child process' output stream");
234         }
235         return ret;
236 #else
237         int ret = read(fd_out[0], buffer, max_len);
238         if (ret < 0) {
239                 if (errno == EAGAIN) {
240                         return 0;
241                 } else {
242                         throw runtime_error("failed to read from child process' output stream");
243                 }
244         }
245         return ret;
246 #endif
247 }
248
249 size_t Process::Impl::ReadErr(void *buffer, size_t max_len) {
250 #ifdef __WIN32
251         DWORD ret;
252         if (!ReadFile(fd_err[0], buffer, max_len, &ret, nullptr)) {
253                 throw runtime_error("failed to read from child process' error stream");
254         }
255         return ret;
256 #else
257         int ret = read(fd_err[0], buffer, max_len);
258         if (ret < 0) {
259                 if (errno == EAGAIN) {
260                         return 0;
261                 } else {
262                         throw runtime_error("failed to read from child process' error stream");
263                 }
264         }
265         return ret;
266 #endif
267 }
268
269 int Process::Impl::Join() {
270 #ifdef __WIN32
271         CloseHandle(fd_in[1]);
272         CloseHandle(fd_out[0]);
273         CloseHandle(fd_err[0]);
274
275         DWORD exit_code;
276         WaitForSingleObject(pi.hProcess, INFINITE);
277         GetExitCodeProcess(pi.hProcess, &exit_code);
278         CloseHandle(pi.hProcess);
279         CloseHandle(pi.hThread);
280         return exit_code;
281 #else
282         // close streams before waiting on child termination
283         close(fd_in[1]);
284         close(fd_out[0]);
285         close(fd_err[0]);
286
287         while (true) {
288                 int status;
289                 int result = waitpid(pid, &status, 0);
290                 if (result == -1) {
291                         throw runtime_error("error waiting on child process");
292                 }
293                 if (result == pid && WIFEXITED(status)) {
294                         return WEXITSTATUS(status);
295                 }
296                 // otherwise, child probably signalled, which we don't care
297                 // about (please don't tell youth welfare), so try again
298         }
299 #endif
300 }
301
302 }