1/*	$OpenBSD: child.c,v 1.28 2022/12/26 19:16:02 jmc Exp $	*/
2
3/*
4 * Copyright (c) 1983 Regents of the University of California.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32/*
33 * Functions for rdist related to children
34 */
35
36#include <sys/types.h>
37#include <sys/select.h>
38#include <sys/wait.h>
39
40#include <errno.h>
41#include <fcntl.h>
42#include <stdlib.h>
43#include <string.h>
44#include <unistd.h>
45
46#include "client.h"
47
48typedef enum _PROCSTATE {
49    PSrunning,
50    PSdead
51} PROCSTATE;
52
53/*
54 * Structure for child rdist processes mainted by the parent
55 */
56struct _child {
57	char	       *c_name;			/* Name of child */
58	int		c_readfd;		/* Read file descriptor */
59	pid_t		c_pid;			/* Process ID */
60	PROCSTATE       c_state;		/* Running? */
61	struct _child  *c_next;			/* Next entry */
62};
63typedef struct _child CHILD;
64
65static CHILD	       *childlist = NULL;	/* List of children */
66int     		activechildren = 0;	/* Number of active children */
67static int 		needscan = FALSE;	/* Need to scan children */
68
69static void removechild(CHILD *);
70static CHILD *copychild(CHILD *);
71static void addchild(CHILD *);
72static void readchild(CHILD *);
73static pid_t waitproc(int *, int);
74static void reap(int);
75static void childscan(void);
76
77/*
78 * Remove a child that has died (exited)
79 * from the list of active children
80 */
81static void
82removechild(CHILD *child)
83{
84	CHILD *pc, *prevpc;
85
86	debugmsg(DM_CALL, "removechild(%s, %d, %d) start",
87		 child->c_name, child->c_pid, child->c_readfd);
88
89	/*
90	 * Find the child in the list
91	 */
92	for (pc = childlist, prevpc = NULL; pc != NULL;
93	     prevpc = pc, pc = pc->c_next)
94		if (pc == child)
95			break;
96
97	if (pc == NULL)
98		error("RemoveChild called with bad child %s %d %d",
99		      child->c_name, child->c_pid, child->c_readfd);
100	else {
101		/*
102		 * Remove the child
103		 */
104		sigset_t set, oset;
105
106		sigemptyset(&set);
107		sigaddset(&set, SIGCHLD);
108		sigprocmask(SIG_BLOCK, &set, &oset);
109
110		if (prevpc != NULL)
111			prevpc->c_next = pc->c_next;
112		else
113			childlist = pc->c_next;
114
115		sigprocmask(SIG_SETMASK, &oset, NULL);
116
117		(void) free(child->c_name);
118		--activechildren;
119		(void) close(child->c_readfd);
120		(void) free(pc);
121	}
122
123	debugmsg(DM_CALL, "removechild() end");
124}
125
126/*
127 * Create a totally new copy of a child.
128 */
129static CHILD *
130copychild(CHILD *child)
131{
132	CHILD *newc;
133
134	newc = xmalloc(sizeof *newc);
135
136	newc->c_name = xstrdup(child->c_name);
137	newc->c_readfd = child->c_readfd;
138	newc->c_pid = child->c_pid;
139	newc->c_state = child->c_state;
140	newc->c_next = NULL;
141
142	return(newc);
143}
144
145/*
146 * Add a child to the list of children.
147 */
148static void
149addchild(CHILD *child)
150{
151	CHILD *pc;
152
153	debugmsg(DM_CALL, "addchild() start\n");
154
155	pc = copychild(child);
156	pc->c_next = childlist;
157	childlist = pc;
158
159	++activechildren;
160
161	debugmsg(DM_MISC,
162		 "addchild() created '%s' pid %d fd %d (active=%d)\n",
163		 child->c_name, child->c_pid, child->c_readfd, activechildren);
164}
165
166/*
167 * Read input from a child process.
168 */
169static void
170readchild(CHILD *child)
171{
172	char rbuf[BUFSIZ];
173	ssize_t amt;
174
175	debugmsg(DM_CALL, "[readchild(%s, %d, %d) start]",
176		 child->c_name, child->c_pid, child->c_readfd);
177
178	/*
179	 * Check that this is a valid child.
180	 */
181	if (child->c_name == NULL || child->c_readfd <= 0) {
182		debugmsg(DM_MISC, "[readchild(%s, %d, %d) bad child]",
183			 child->c_name, child->c_pid, child->c_readfd);
184		return;
185	}
186
187	/*
188	 * Read from child and display the result.
189	 */
190	while ((amt = read(child->c_readfd, rbuf, sizeof(rbuf))) > 0) {
191		/* XXX remove these debug calls */
192		debugmsg(DM_MISC, "[readchild(%s, %d, %d) got %zd bytes]",
193			 child->c_name, child->c_pid, child->c_readfd, amt);
194
195		(void) xwrite(fileno(stdout), rbuf, amt);
196
197		debugmsg(DM_MISC, "[readchild(%s, %d, %d) write done]",
198			 child->c_name, child->c_pid, child->c_readfd);
199	}
200
201	debugmsg(DM_MISC, "readchild(%s, %d, %d) done: amt = %zd errno = %d\n",
202		 child->c_name, child->c_pid, child->c_readfd, amt, errno);
203
204	/*
205	 * See if we've reached EOF
206	 */
207	if (amt == 0)
208		debugmsg(DM_MISC, "readchild(%s, %d, %d) at EOF\n",
209			 child->c_name, child->c_pid, child->c_readfd);
210}
211
212/*
213 * Wait for processes to exit.  If "block" is true, then we block
214 * until a process exits.  Otherwise, we return right away.  If
215 * a process does exit, then the pointer "statval" is set to the
216 * exit status of the exiting process, if statval is not NULL.
217 */
218static pid_t
219waitproc(int *statval, int block)
220{
221	int status;
222	pid_t pid;
223	int exitval;
224
225	debugmsg(DM_CALL, "waitproc() %s, active children = %d...\n",
226		 (block) ? "blocking" : "nonblocking", activechildren);
227
228	pid = waitpid(-1, &status, (block) ? 0 : WNOHANG);
229
230	exitval = WEXITSTATUS(status);
231
232	if (pid > 0 && exitval != 0) {
233		nerrs++;
234		debugmsg(DM_MISC,
235			 "Child process %d exited with status %d.\n",
236			 pid, exitval);
237	}
238
239	if (statval)
240		*statval = exitval;
241
242	debugmsg(DM_CALL, "waitproc() done (activechildren = %d)\n",
243		 activechildren);
244
245	return(pid);
246}
247
248/*
249 * Check to see if any children have exited, and if so, read any unread
250 * input and then remove the child from the list of children.
251 */
252static void
253reap(int dummy)
254{
255	CHILD *pc;
256	int save_errno = errno;
257	int status = 0;
258	pid_t pid;
259
260	debugmsg(DM_CALL, "reap() called\n");
261
262	/*
263	 * Reap every child that has exited.  Break out of the
264	 * loop as soon as we run out of children that have
265	 * exited so far.
266	 */
267	for ( ; ; ) {
268		/*
269		 * Do a non-blocking check for exiting processes
270		 */
271		pid = waitproc(&status, FALSE);
272		debugmsg(DM_MISC,
273			 "reap() pid = %d status = %d activechildren=%d\n",
274			 pid, status, activechildren);
275
276		/*
277		 * See if a child really exited
278		 */
279		if (pid == 0)
280			break;
281		if (pid < 0) {
282			if (errno != ECHILD)
283				error("Wait failed: %s", SYSERR);
284			break;
285		}
286
287		/*
288		 * Find the process (pid) and mark it as dead.
289		 */
290		for (pc = childlist; pc; pc = pc->c_next)
291			if (pc->c_pid == pid) {
292				needscan = TRUE;
293				pc->c_state = PSdead;
294			}
295
296	}
297
298	/*
299	 * Reset signals
300	 */
301	(void) signal(SIGCHLD, reap);
302
303	debugmsg(DM_CALL, "reap() done\n");
304	errno = save_errno;
305}
306
307/*
308 * Scan the children list to find the child that just exited,
309 * read any unread input, then remove it from the list of active children.
310 */
311static void
312childscan(void)
313{
314	CHILD *pc, *nextpc;
315
316	debugmsg(DM_CALL, "childscan() start");
317
318	for (pc = childlist; pc; pc = nextpc) {
319		nextpc = pc->c_next;
320		if (pc->c_state == PSdead) {
321			readchild(pc);
322			removechild(pc);
323		}
324	}
325
326	needscan = FALSE;
327	debugmsg(DM_CALL, "childscan() end");
328}
329
330/*
331 *
332 * Wait for children to send output for us to read.
333 *
334 */
335void
336waitup(void)
337{
338	int count;
339	CHILD *pc;
340	fd_set *rchildfdsp = NULL;
341	int rchildfdsn = 0;
342
343	debugmsg(DM_CALL, "waitup() start\n");
344
345	if (needscan)
346		childscan();
347
348	if (activechildren <= 0)
349		return;
350
351	/*
352	 * Set up which children we want to select() on.
353	 */
354	for (pc = childlist; pc; pc = pc->c_next)
355		if (pc->c_readfd > rchildfdsn)
356			rchildfdsn = pc->c_readfd;
357	rchildfdsp = xcalloc(howmany(rchildfdsn+1, NFDBITS), sizeof(fd_mask));
358
359	for (pc = childlist; pc; pc = pc->c_next)
360		if (pc->c_readfd > 0) {
361			debugmsg(DM_MISC, "waitup() select on %d (%s)\n",
362				 pc->c_readfd, pc->c_name);
363			FD_SET(pc->c_readfd, rchildfdsp);
364		}
365
366	/*
367	 * Actually call select()
368	 */
369	/* XXX remove debugmsg() calls */
370	debugmsg(DM_MISC, "waitup() Call select(), activechildren=%d\n",
371		 activechildren);
372
373	count = select(rchildfdsn+1, rchildfdsp, NULL, NULL, NULL);
374
375	debugmsg(DM_MISC, "waitup() select returned %d activechildren = %d\n",
376		 count, activechildren);
377
378	/*
379	 * select() will return count < 0 and errno == EINTR when
380	 * there are no active children left.
381	 */
382	if (count < 0) {
383		if (errno != EINTR)
384			error("Select failed reading children input: %s",
385			      SYSERR);
386		free(rchildfdsp);
387		return;
388	}
389
390	/*
391	 * This should never happen.
392	 */
393	if (count == 0) {
394		error("Select returned an unexpected count of 0.");
395		free(rchildfdsp);
396		return;
397	}
398
399	/*
400	 * Go through the list of children and read from each child
401	 * which select() detected as ready for reading.
402	 */
403	for (pc = childlist; pc && count > 0; pc = pc->c_next) {
404		/*
405		 * Make sure child still exists
406		 */
407		if (pc->c_name && kill(pc->c_pid, 0) == -1 &&
408		    errno == ESRCH) {
409			debugmsg(DM_MISC,
410				 "waitup() proc %d (%s) died unexpectedly!",
411				 pc->c_pid, pc->c_name);
412			pc->c_state = PSdead;
413			needscan = TRUE;
414		}
415
416		if (pc->c_name == NULL ||
417		    !FD_ISSET(pc->c_readfd, rchildfdsp))
418			continue;
419
420		readchild(pc);
421		--count;
422	}
423	free(rchildfdsp);
424
425	debugmsg(DM_CALL, "waitup() end\n");
426}
427
428/*
429 * Enable non-blocking I/O.
430 */
431static int
432setnonblocking(int fd)
433{
434	int	flags;
435
436	if ((flags = fcntl(fd, F_GETFL)) == -1)
437		return (-1);
438	if (flags & O_NONBLOCK)
439		return (0);
440	return (fcntl(fd, F_SETFL, flags | O_NONBLOCK));
441}
442
443/*
444 * Spawn (create) a new child process for "cmd".
445 */
446int
447spawn(struct cmd *cmd, struct cmd *cmdlist)
448{
449	pid_t pid;
450	int fildes[2];
451	char *childname = cmd->c_name;
452
453	if (pipe(fildes) == -1) {
454		error("Cannot create pipe for %s: %s", childname, SYSERR);
455		return(-1);
456	}
457
458	pid = fork();
459	if (pid == (pid_t)-1) {
460		error("Cannot spawn child for %s: fork failed: %s",
461		      childname, SYSERR);
462		return(-1);
463	} else if (pid > 0) {
464		/*
465		 * Parent
466		 */
467		static CHILD newchild;
468
469		/* Receive notification when the child exits */
470		(void) signal(SIGCHLD, reap);
471
472		/* Setup the new child */
473		newchild.c_next = NULL;
474		newchild.c_name = childname;
475		newchild.c_readfd = fildes[PIPE_READ];
476		newchild.c_pid = pid;
477		newchild.c_state = PSrunning;
478
479		/* We're not going to write to the child */
480		(void) close(fildes[PIPE_WRITE]);
481
482		/* Set non-blocking I/O */
483		if (setnonblocking(newchild.c_readfd) < 0) {
484			error("Set nonblocking I/O failed: %s", SYSERR);
485			return(-1);
486		}
487
488		/* Add new child to child list */
489		addchild(&newchild);
490
491		/* Mark all other entries for this host as assigned */
492		markassigned(cmd, cmdlist);
493
494		debugmsg(DM_CALL,
495			 "spawn() Forked child %d for host %s active = %d\n",
496			 pid, childname, activechildren);
497		return(pid);
498	} else {
499		/*
500		 * Child
501		 */
502
503		/* We're not going to read from our parent */
504		(void) close(fildes[PIPE_READ]);
505
506		/* Make stdout and stderr go to PIPE_WRITE (our parent) */
507		if (dup2(fildes[PIPE_WRITE], (int)fileno(stdout)) == -1) {
508			error("Cannot duplicate stdout file descriptor: %s",
509			      SYSERR);
510			return(-1);
511		}
512		if (dup2(fildes[PIPE_WRITE], (int)fileno(stderr)) == -1) {
513			error("Cannot duplicate stderr file descriptor: %s",
514			      SYSERR);
515			return(-1);
516		}
517
518		return(0);
519	}
520}
521