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