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