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