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