]> git.localhorst.tv Git - blank.git/blob - src/app/proc.cpp
dff8dee525fcb8957034e939b91f70fccd9618e8
[blank.git] / src / app / proc.cpp
1 #include "Process.hpp"
2
3 #include "error.hpp"
4
5 #ifdef _WIN32
6 #  include <tchar.h>
7 #  include <windows.h>
8 #else
9 #  include <fcntl.h>
10 #  include <signal.h>
11 #  include <unistd.h>
12 #  include <sys/wait.h>
13 #endif
14
15 #include <cstdio>
16 #include <stdexcept>
17
18 using namespace std;
19
20
21 namespace blank {
22
23 struct Process::Impl {
24
25         Impl(
26                 const string &path,
27                 const Arguments &args,
28                 char *const env[]);
29         ~Impl();
30
31         static Impl *EnvHelper(
32                 const string &path,
33                 const Arguments &args,
34                 const Environment &env);
35
36         size_t WriteIn(const void *buffer, size_t max_len);
37         void CloseIn();
38
39         size_t ReadOut(void *buffer, size_t max_len);
40         void CloseOut();
41
42         size_t ReadErr(void *buffer, size_t max_len);
43         void CloseErr();
44
45         void Terminate();
46         bool Terminated();
47         int Join();
48
49         bool joined;
50         int status;
51
52         bool in_closed;
53         bool out_closed;
54         bool err_closed;
55
56 #ifdef _WIN32
57         PROCESS_INFORMATION pi;
58         HANDLE fd_in[2];
59         HANDLE fd_out[2];
60         HANDLE fd_err[2];
61 #else
62         int pid;
63         int fd_in[2];
64         int fd_out[2];
65         int fd_err[2];
66 #endif
67
68 };
69
70
71 Process::Process(
72         const string &path,
73         const Arguments &args)
74 : impl(new Impl(path, args, nullptr)) {
75
76 }
77
78 Process::Process(
79         const string &path,
80         const Arguments &args,
81         const Environment &env)
82 : impl(Impl::EnvHelper(path, args, env)) {
83
84 }
85
86 Process::~Process() {
87         Join();
88 }
89
90
91 size_t Process::WriteIn(const void *buffer, size_t max_len) {
92         return impl->WriteIn(buffer, max_len);
93 }
94
95 void Process::CloseIn() {
96         impl->CloseIn();
97 }
98
99 size_t Process::ReadOut(void *buffer, size_t max_len) {
100         return impl->ReadOut(buffer, max_len);
101 }
102
103 void Process::CloseOut() {
104         impl->CloseOut();
105 }
106
107 size_t Process::ReadErr(void *buffer, size_t max_len) {
108         return impl->ReadErr(buffer, max_len);
109 }
110
111 void Process::CloseErr() {
112         impl->CloseErr();
113 }
114
115 void Process::Terminate() {
116         impl->Terminate();
117 }
118
119 bool Process::Terminated() {
120         return impl->Terminated();
121 }
122
123 int Process::Join() {
124         return impl->Join();
125 }
126
127 Process::Impl *Process::Impl::EnvHelper(
128         const string &path,
129         const Arguments &args,
130         const Environment &env
131 ) {
132         char *envp[env.size() + 1];
133         for (size_t i = 0; i < env.size(); ++i) {
134                 envp[i] = const_cast<char *>(env[i].c_str());
135         }
136         envp[env.size()] = nullptr;
137         return new Impl(path, args, envp);
138 }
139
140 Process::Impl::Impl(
141         const string &path_in,
142         const Arguments &args,
143         char *const envp[])
144 : joined(false)
145 , status(0)
146 , in_closed(false)
147 , out_closed(false)
148 , err_closed(false) {
149         const char *path = path_in.c_str();
150 #ifdef _WIN32
151         string cmdline;
152         for (const auto &arg : args) {
153                 cmdline += '"';
154                 cmdline += arg;
155                 cmdline += '"';
156         }
157
158         SECURITY_ATTRIBUTES sa;
159         sa.nLength = sizeof(SECURITY_ATTRIBUTES);
160         sa.bInheritHandle = true;
161         sa.lpSecurityDescriptor = nullptr;
162         if (!CreatePipe(&fd_in[0], &fd_in[1], &sa, 0)) {
163                 throw runtime_error("failed to open pipe for child process' stdin");
164         }
165         if (!SetHandleInformation(fd_in[1], HANDLE_FLAG_INHERIT, 0)) {
166                 throw runtime_error("failed to stop child process from inheriting stdin write handle");
167         }
168         if (!CreatePipe(&fd_out[0], &fd_out[1], &sa, 0)) {
169                 throw runtime_error("failed to open pipe for child process' stdout");
170         }
171         if (!SetHandleInformation(fd_out[0], HANDLE_FLAG_INHERIT, 0)) {
172                 throw runtime_error("failed to stop child process from inheriting stdout read handle");
173         }
174         if (!CreatePipe(&fd_err[0], &fd_err[1], &sa, 0)) {
175                 throw runtime_error("failed to open pipe for child process' stderr");
176         }
177         if (!SetHandleInformation(fd_err[0], HANDLE_FLAG_INHERIT, 0)) {
178                 throw runtime_error("failed to stop child process from inheriting stderr read handle");
179         }
180
181         STARTUPINFO si;
182         ZeroMemory(&si, sizeof(STARTUPINFO));
183         si.cb = sizeof(STARTUPINFO);
184         si.hStdError = fd_err[1];
185         si.hStdOutput = fd_out[1];
186         si.hStdInput = fd_in[0];
187         si.dwFlags |= STARTF_USESTDHANDLES;
188         ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
189
190         if (!CreateProcess(
191                 path,
192                 cmdline.c_str(),
193                 nullptr,
194                 nullptr,
195                 true,
196                 0,
197                 envp,
198                 nullptr,
199                 &si,
200                 &pi
201         )) {
202                 throw runtime_error("CreateProcess");
203         }
204 #else
205         char *argv[args.size() + 1];
206         for (size_t i = 0; i < args.size(); ++i) {
207                 // I was promised exec won't modify my characters
208                 argv[i] = const_cast<char *>(args[i].c_str());
209         }
210         argv[args.size()] = nullptr;
211
212         if (pipe(fd_in) != 0) {
213                 throw SysError("failed to open pipe for child process' stdin");
214         }
215         if (pipe(fd_out) != 0) {
216                 throw SysError("failed to open pipe for child process' stdout");
217         }
218         if (pipe(fd_err) != 0) {
219                 throw SysError("failed to open pipe for child process' stderr");
220         }
221
222         pid = fork();
223         if (pid == -1) {
224                 throw SysError("fork");
225         } else if (pid == 0) {
226
227                 if (dup2(fd_in[0], STDIN_FILENO) == -1) {
228                         exit(EXIT_FAILURE);
229                 }
230                 if (dup2(fd_out[1], STDOUT_FILENO) == -1) {
231                         exit(EXIT_FAILURE);
232                 }
233                 if (dup2(fd_err[1], STDERR_FILENO) == -1) {
234                         exit(EXIT_FAILURE);
235                 }
236
237                 close(fd_in[0]);
238                 close(fd_in[1]);
239                 close(fd_out[0]);
240                 close(fd_out[1]);
241                 close(fd_err[0]);
242                 close(fd_err[1]);
243
244                 if (envp) {
245                         execve(path, argv, envp);
246                 } else {
247                         execv(path, argv);
248                 }
249                 // if execve returns, something bad happened
250                 exit(EXIT_FAILURE);
251
252         } else {
253
254                 close(fd_in[0]);
255                 close(fd_out[1]);
256                 close(fd_err[1]);
257
258         }
259 }
260 #endif
261
262 Process::Impl::~Impl() {
263         CloseIn();
264         CloseOut();
265         CloseErr();
266 }
267
268 size_t Process::Impl::WriteIn(const void *buffer, size_t max_len) {
269 #ifdef _WIN32
270         DWORD written;
271         if (!WriteFile(fd_in[1], buffer, max_len, &written, nullptr)) {
272                 throw runtime_error("failed to write to child process' input stream");
273         }
274         return written;
275 #else
276         int written = write(fd_in[1], buffer, max_len);
277         if (written < 0) {
278                 if (errno == EAGAIN) {
279                         return 0;
280                 } else {
281                         throw SysError("failed to write to child process' input stream");
282                 }
283         }
284         return written;
285 #endif
286 }
287
288 void Process::Impl::CloseIn() {
289         if (in_closed) {
290                 return;
291         }
292 #ifdef _WIN32
293         CloseHandle(fd_in[1]);
294 #else
295         close(fd_in[1]);
296 #endif
297         in_closed = true;
298 }
299
300 size_t Process::Impl::ReadOut(void *buffer, size_t max_len) {
301 #ifdef _WIN32
302         DWORD ret;
303         if (!ReadFile(fd_out[0], buffer, max_len, &ret, nullptr)) {
304                 throw runtime_error("failed to read from child process' output stream");
305         }
306         return ret;
307 #else
308         int ret = read(fd_out[0], buffer, max_len);
309         if (ret < 0) {
310                 if (errno == EAGAIN) {
311                         return 0;
312                 } else {
313                         throw SysError("failed to read from child process' output stream");
314                 }
315         }
316         return ret;
317 #endif
318 }
319
320 void Process::Impl::CloseOut() {
321         if (out_closed) {
322                 return;
323         }
324 #ifdef _WIN32
325         CloseHandle(fd_out[0]);
326 #else
327         close(fd_out[0]);
328 #endif
329         out_closed = true;
330 }
331
332 size_t Process::Impl::ReadErr(void *buffer, size_t max_len) {
333 #ifdef _WIN32
334         DWORD ret;
335         if (!ReadFile(fd_err[0], buffer, max_len, &ret, nullptr)) {
336                 throw runtime_error("failed to read from child process' error stream");
337         }
338         return ret;
339 #else
340         int ret = read(fd_err[0], buffer, max_len);
341         if (ret < 0) {
342                 if (errno == EAGAIN) {
343                         return 0;
344                 } else {
345                         throw SysError("failed to read from child process' error stream");
346                 }
347         }
348         return ret;
349 #endif
350 }
351
352 void Process::Impl::CloseErr() {
353         if (err_closed) {
354                 return;
355         }
356 #ifdef _WIN32
357         CloseHandle(fd_err[0]);
358 #else
359         close(fd_err[0]);
360 #endif
361         err_closed = true;
362 }
363
364 void Process::Impl::Terminate() {
365         if (joined) {
366                 // can only terminate once
367                 return;
368         }
369 #ifdef _WIN32
370         if (!TerminateProcess(pi.hProcess, -1)) {
371 #else
372         if (kill(pid, SIGTERM) == -1) {
373 #endif
374                 throw SysError("failed to terminate child process");
375         }
376 }
377
378 bool Process::Impl::Terminated() {
379         if (joined) {
380                 return true;
381         }
382 #ifdef _WIN32
383         DWORD exit_code;
384         GetExitCodeProcess(pi.hProcess, &exit_code);
385         if (exit_code == STILL_ACTIVE) {
386                 return false;
387         } else {
388                 CloseHandle(pi.hProcess);
389                 CloseHandle(pi.hThread);
390                 status = exit_code;
391                 joined = true;
392                 return true;
393         }
394 #else
395         int stat;
396         int result = waitpid(pid, &stat, WNOHANG);
397         if (result == -1) {
398                 throw SysError("error polling child process");
399         } else if (result == 0) {
400                 return false;
401         } else if (result == pid) {
402                 // child just exited, reap
403                 if (WIFEXITED(stat)) {
404                         // autonomous termination
405                         status = WEXITSTATUS(stat);
406                 } else if (WIFSIGNALED(stat)) {
407                         // signalled termination
408                         status = WTERMSIG(stat);
409                 }
410                 joined = true;
411                 return true;
412         } else {
413                 throw runtime_error("bogus return value of waitpid");
414         }
415 #endif
416 }
417
418 int Process::Impl::Join() {
419         if (joined) {
420                 // can only join once
421                 return status;
422         }
423 #ifdef _WIN32
424         DWORD exit_code;
425         WaitForSingleObject(pi.hProcess, INFINITE);
426         GetExitCodeProcess(pi.hProcess, &exit_code);
427         CloseHandle(pi.hProcess);
428         CloseHandle(pi.hThread);
429         status = exit_code;
430         joined = true;
431         return status;
432 #else
433         while (true) {
434                 int stat;
435                 int result = waitpid(pid, &stat, 0);
436                 if (result == -1) {
437                         throw SysError("error waiting on child process");
438                 }
439                 if (result != pid) {
440                         // should in theory only happen with WNOHANG set
441                         continue;
442                 }
443                 if (WIFEXITED(stat)) {
444                         // autonomous termination
445                         status = WEXITSTATUS(stat);
446                         joined = true;
447                         return status;
448                 }
449                 if (WIFSIGNALED(stat)) {
450                         // signalled termination
451                         status = WTERMSIG(stat);
452                         joined = true;
453                         return status;
454                 }
455                 // otherwise, child probably signalled stop/continue, which we
456                 // don't care about (please don't tell youth welfare), so try again
457         }
458 #endif
459 }
460
461 }