1 /*
2 * Copyright (c) 2001-2009, 2013, 2015, 2018-2020 Paul Mattes.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the names of Paul Mattes nor the names of his contributors
13 * may be used to endorse or promote products derived from this software
14 * without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY PAUL MATTES "AS IS" AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL PAUL MATTES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /*
29 * child.c
30 * Child process output support.
31 */
32 #include "globals.h"
33
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <assert.h>
37
38 #include "child.h"
39 #include "popups.h" /* must be before child_popups.h */
40 #include "child_popups.h"
41 #include "trace.h"
42 #include "utils.h"
43 #include "xio.h"
44
45 #if defined(_WIN32) /*[*/
46 # include "w3misc.h"
47 #endif /*]*/
48
49 #define CHILD_BUF 1024
50
51 static bool child_initted = false;
52 static bool child_broken = false;
53 static bool child_discarding = false;
54 #if !defined(_WIN32) /*[*/
55 static int child_outpipe[2];
56 static int child_errpipe[2];
57 #else /*]*/
58 static HANDLE child_stdout_rd = INVALID_HANDLE_VALUE;
59 static HANDLE child_stdout_wr = INVALID_HANDLE_VALUE;
60 static HANDLE child_stderr_rd = INVALID_HANDLE_VALUE;
61 static HANDLE child_stderr_wr = INVALID_HANDLE_VALUE;
62 #endif /*]*/
63
64 #if !defined(_WIN32) /*[*/
65 static struct pr3o {
66 int fd; /* file descriptor */
67 ioid_t input_id; /* input ID */
68 ioid_t timeout_id; /* timeout ID */
69 int count; /* input count */
70 char buf[CHILD_BUF]; /* input buffer */
71 } child_stdout = { -1, 0L, 0L, 0 },
72 child_stderr = { -1, 0L, 0L, 0 };
73 #else /*][*/
74 typedef struct {
75 HANDLE pipe_handle;
76 HANDLE enable_event;
77 HANDLE done_event;
78 HANDLE thread;
79 char buf[CHILD_BUF];
80 DWORD nr;
81 int error;
82 bool is_stderr;
83 } cr_t;
84 cr_t cr_stdout, cr_stderr;
85 #endif /*]*/
86
87 #if !defined(_WIN32) /*[*/
88 static void child_output(iosrc_t fd, ioid_t id);
89 static void child_error(iosrc_t fd, ioid_t id);
90 static void child_otimeout(ioid_t id);
91 static void child_etimeout(ioid_t id);
92 static void child_dump(struct pr3o *p, bool is_err);
93 #endif /*]*/
94
95 #if defined(_WIN32) /*[*/
96 static DWORD WINAPI child_read_thread(LPVOID parameter);
97 static void cr_output(iosrc_t fd, ioid_t id);
98 #endif /*]*/
99
100 static void
init_child(void)101 init_child(void)
102 {
103 #if defined(_WIN32) /*[*/
104 SECURITY_ATTRIBUTES sa;
105 DWORD mode;
106 #endif /*]*/
107
108 /* If initialization failed, there isn't much we can do. */
109 if (child_broken) {
110 return;
111 }
112
113 #if !defined(_WIN32) /*[*/
114 /* Create pipes. */
115 if (pipe(child_outpipe) < 0) {
116 popup_an_errno(errno, "pipe()");
117 child_broken = true;
118 return;
119 }
120 if (pipe(child_errpipe) < 0) {
121 popup_an_errno(errno, "pipe()");
122 close(child_outpipe[0]);
123 close(child_outpipe[1]);
124 child_broken = true;
125 return;
126 }
127 vtrace("init_child: child_outpipe is %d %d\n", child_outpipe[0], child_outpipe[1]);
128
129 /* Make sure their read ends are closed in child processes. */
130 fcntl(child_outpipe[0], F_SETFD, 1);
131 fcntl(child_errpipe[0], F_SETFD, 1);
132
133 /* Initialize the pop-ups. */
134 child_popup_init();
135
136 /* Express interest in their output. */
137 child_stdout.fd = child_outpipe[0];
138 child_stdout.input_id = AddInput(child_outpipe[0], child_output);
139 child_stderr.fd = child_errpipe[0];
140 child_stderr.input_id = AddInput(child_errpipe[0], child_error);
141
142 #else /*][*/
143
144 /* Create pipes. */
145 memset(&sa, 0, sizeof(sa));
146 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
147 sa.bInheritHandle = TRUE;
148 sa.lpSecurityDescriptor = NULL;
149 if (!CreatePipe(&child_stdout_rd, &child_stdout_wr, &sa, 0)) {
150 popup_an_error("CreatePipe(stdout) failed: %s",
151 win32_strerror(GetLastError()));
152 child_broken = true;
153 return;
154 }
155 if (!SetHandleInformation(child_stdout_rd, HANDLE_FLAG_INHERIT, 0)) {
156 popup_an_error("SetHandleInformation(stdout) failed: %s",
157 win32_strerror(GetLastError()));
158 CloseHandle(child_stdout_rd);
159 CloseHandle(child_stdout_wr);
160 child_broken = true;
161 return;
162 }
163 mode = PIPE_READMODE_BYTE;
164 if (!SetNamedPipeHandleState(child_stdout_rd, &mode, NULL, NULL)) {
165 popup_an_error("SetNamedPipeHandleState(stdout) failed: %s",
166 win32_strerror(GetLastError()));
167 CloseHandle(child_stdout_rd);
168 CloseHandle(child_stdout_wr);
169 child_broken = true;
170 return;
171 }
172
173 if (!CreatePipe(&child_stderr_rd, &child_stderr_wr, &sa, 0)) {
174 popup_an_error("CreatePipe(stderr) failed: %s",
175 win32_strerror(GetLastError()));
176 CloseHandle(child_stdout_rd);
177 CloseHandle(child_stdout_wr);
178 child_broken = true;
179 return;
180 }
181 if (!SetHandleInformation(child_stderr_rd, HANDLE_FLAG_INHERIT, 0)) {
182 popup_an_error("SetHandleInformation(stderr) failed: %s",
183 win32_strerror(GetLastError()));
184 CloseHandle(child_stdout_rd);
185 CloseHandle(child_stdout_wr);
186 CloseHandle(child_stderr_rd);
187 CloseHandle(child_stderr_wr);
188 child_broken = true;
189 return;
190 }
191 mode = PIPE_READMODE_BYTE;
192 if (!SetNamedPipeHandleState(child_stderr_rd, &mode, NULL, NULL)) {
193 popup_an_error("SetNamedPipeHandleState(stderr) failed: %s",
194 win32_strerror(GetLastError()));
195 CloseHandle(child_stdout_rd);
196 CloseHandle(child_stdout_wr);
197 CloseHandle(child_stderr_rd);
198 CloseHandle(child_stderr_wr);
199 child_broken = true;
200 return;
201 }
202
203 /* Initialize the pop-ups. */
204 child_popup_init();
205
206 /* Express interest in their output. */
207 cr_stdout.pipe_handle = child_stdout_rd;
208 cr_stdout.enable_event = CreateEvent(NULL, FALSE, FALSE, NULL);
209 cr_stdout.done_event = CreateEvent(NULL, FALSE, FALSE, NULL);
210 cr_stdout.thread = CreateThread(NULL, 0, child_read_thread, &cr_stdout, 0,
211 NULL);
212 AddInput(cr_stdout.done_event, cr_output);
213 SetEvent(cr_stdout.enable_event);
214
215 cr_stderr.pipe_handle = child_stderr_rd;
216 cr_stderr.enable_event = CreateEvent(NULL, FALSE, FALSE, NULL);
217 cr_stderr.done_event = CreateEvent(NULL, FALSE, FALSE, NULL);
218 cr_stderr.thread = CreateThread(NULL, 0, child_read_thread, &cr_stderr, 0,
219 NULL);
220 cr_stderr.is_stderr = true;
221 AddInput(cr_stderr.done_event, cr_output);
222 SetEvent(cr_stderr.enable_event);
223
224 #endif /*]*/
225
226 child_initted = true;
227 }
228
229 #if !defined(_WIN32) /*[*/
230 /*
231 * Fork a child process, with its stdout/stderr connected to pop-up windows.
232 * Returns -1 for an error, 0 for child context, pid for parent context.
233 */
234 int
fork_child(void)235 fork_child(void)
236 {
237 pid_t pid;
238
239 /* Do initialization, if it hasn't been done already. */
240 if (!child_initted) {
241 init_child();
242 }
243
244 /* If output was being dumped, turn it back on now. */
245 if (child_discarding) {
246 child_discarding = false;
247 }
248
249 /* Fork and rearrange output. */
250 pid = fork();
251 if (pid == 0) {
252 /* Child. */
253 dup2(child_outpipe[1], 1);
254 close(child_outpipe[1]);
255 dup2(child_errpipe[1], 2);
256 close(child_errpipe[1]);
257 }
258 return pid;
259 }
260 #else /*][*/
261 /* Get the stdout and sterr redirect handles. */
262 void
get_child_handles(HANDLE * out,HANDLE * err)263 get_child_handles(HANDLE *out, HANDLE *err)
264 {
265
266 /* Do initialization, if it hasn't been done already. */
267 if (!child_initted) {
268 init_child();
269 }
270
271 /* If output was being dumped, turn it back on now. */
272 if (child_discarding) {
273 child_discarding = false;
274 }
275
276 /* Return the handles. */
277 *out = child_stdout_wr;
278 *err = child_stderr_wr;
279 }
280 #endif /*]*/
281
282 #if !defined(_WIN32) /*[*/
283 /* There's data from a child. */
284 static void
child_data(struct pr3o * p,bool is_err)285 child_data(struct pr3o *p, bool is_err)
286 {
287 int space;
288 ssize_t nr;
289
290 /*
291 * If we're discarding output, pull it in and drop it on the floor.
292 */
293 if (child_discarding) {
294 nr = read(p->fd, p->buf, CHILD_BUF);
295 return;
296 }
297
298 /* Read whatever there is. */
299 space = CHILD_BUF - p->count - 1;
300 nr = read(p->fd, p->buf + p->count, space);
301 if (nr < 0) {
302 popup_an_errno(errno, "child session pipe input");
303 return;
304 }
305
306 /* Add it to the buffer, and add a NULL. */
307 p->count += nr;
308 p->buf[p->count] = '\0';
309
310 /*
311 * If there's no more room in the buffer, dump it now. Otherwise,
312 * give it a second to generate more output.
313 */
314 if (p->count >= CHILD_BUF - 1) {
315 child_dump(p, is_err);
316 } else if (p->timeout_id == 0L) {
317 p->timeout_id = AddTimeOut(1000,
318 is_err? child_etimeout: child_otimeout);
319 }
320 }
321
322 /* The child process has some output for us. */
323 static void
child_output(iosrc_t fd _is_unused,ioid_t id _is_unused)324 child_output(iosrc_t fd _is_unused, ioid_t id _is_unused)
325 {
326 child_data(&child_stdout, false);
327 }
328
329 /* The child process has some error output for us. */
330 static void
child_error(iosrc_t fd _is_unused,ioid_t id _is_unused)331 child_error(iosrc_t fd _is_unused, ioid_t id _is_unused)
332 {
333 child_data(&child_stderr, true);
334 }
335
336 /* Timeout from child output or error output. */
337 static void
child_timeout(struct pr3o * p,bool is_err)338 child_timeout(struct pr3o *p, bool is_err)
339 {
340 /* Forget the timeout ID. */
341 p->timeout_id = 0L;
342
343 /* Dump the output. */
344 child_dump(p, is_err);
345 }
346
347 /* Timeout from child output. */
348 static void
child_otimeout(ioid_t id _is_unused)349 child_otimeout(ioid_t id _is_unused)
350 {
351 child_timeout(&child_stdout, false);
352 }
353
354 /* Timeout from child error output. */
355 static void
child_etimeout(ioid_t id _is_unused)356 child_etimeout(ioid_t id _is_unused)
357 {
358 child_timeout(&child_stderr, true);
359 }
360
361 /*
362 * Abort button from child output.
363 * Ignore output from the child process, so the user can abort it.
364 */
365 void
child_ignore_output(void)366 child_ignore_output(void)
367 {
368 /* Pitch pending output. */
369 child_stdout.count = 0;
370 child_stderr.count = 0;
371
372 /* Remove pendnig timeouts. */
373 if (child_stdout.timeout_id) {
374 RemoveTimeOut(child_stdout.timeout_id);
375 child_stdout.timeout_id = 0L;
376 }
377 if (child_stderr.timeout_id) {
378 RemoveTimeOut(child_stderr.timeout_id);
379 child_stderr.timeout_id = 0L;
380 }
381
382 /* Remember it. */
383 child_discarding = true;
384 }
385
386 /* Dump pending child process output. */
387 static void
child_dump(struct pr3o * p,bool is_err)388 child_dump(struct pr3o *p, bool is_err)
389 {
390 if (p->count) {
391 /*
392 * Strip any trailing newline, and make sure the buffer is
393 * NULL terminated.
394 */
395 if (p->buf[p->count - 1] == '\n') {
396 p->buf[--(p->count)] = '\0';
397 } else if (p->buf[p->count]) {
398 p->buf[p->count] = '\0';
399 }
400
401 /* Dump it and clear the buffer. */
402 popup_child_output(is_err, child_ignore_output, "%s", p->buf);
403
404 p->count = 0;
405 }
406 }
407 #endif /*]*/
408
409 #if defined(_WIN32) /*[*/
410 /* Read from the child's stdout or stderr. */
411 static DWORD WINAPI
child_read_thread(LPVOID parameter)412 child_read_thread(LPVOID parameter)
413 {
414 cr_t *cr = (cr_t *)parameter;
415 DWORD success;
416
417 for (;;) {
418 DWORD rv = WaitForSingleObject(cr->enable_event, INFINITE);
419 switch (rv) {
420 case WAIT_OBJECT_0:
421 success = ReadFile(cr->pipe_handle, cr->buf, CHILD_BUF,
422 &cr->nr, NULL);
423 if (!success) {
424 cr->nr = 0;
425 cr->error = GetLastError();
426 } else {
427 cr->error = 0;
428 }
429 SetEvent(cr->done_event);
430 break;
431 default:
432 cr->nr = 0;
433 cr->error = ERROR_NO_DATA;
434 SetEvent(cr->done_event);
435 break;
436 }
437 }
438 return 0;
439 }
440
441 /* The child stdout or stderr thread produced output. */
442 static void
cr_output(iosrc_t fd,ioid_t id)443 cr_output(iosrc_t fd, ioid_t id)
444 {
445 cr_t *cr;
446
447 /* Find the descriptor. */
448 if (fd == cr_stdout.done_event) {
449 cr = &cr_stdout;
450 } else if (fd == cr_stderr.done_event) {
451 cr = &cr_stderr;
452 } else {
453 vtrace("cr_output: unknown handle\n");
454 return;
455 }
456
457 if (cr->nr == 0) {
458 fprintf(stderr, "cr_output failed: error %s\n",
459 win32_strerror(cr->error));
460 x3270_exit(1);
461 }
462
463 popup_child_output(cr->is_stderr, NULL, "%.*s", (int)cr->nr, cr->buf);
464
465 /* Ready for more. */
466 SetEvent(cr->enable_event);
467 }
468 #endif /*]*/
469