xref: /openbsd/usr.bin/rdist/child.c (revision d9a51c35)
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 
48 typedef enum _PROCSTATE {
49     PSrunning,
50     PSdead
51 } PROCSTATE;
52 
53 /*
54  * Structure for child rdist processes mainted by the parent
55  */
56 struct _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 };
63 typedef struct _child CHILD;
64 
65 static CHILD	       *childlist = NULL;	/* List of children */
66 int     		activechildren = 0;	/* Number of active children */
67 static int 		needscan = FALSE;	/* Need to scan children */
68 
69 static void removechild(CHILD *);
70 static CHILD *copychild(CHILD *);
71 static void addchild(CHILD *);
72 static void readchild(CHILD *);
73 static pid_t waitproc(int *, int);
74 static void reap(int);
75 static void childscan(void);
76 
77 /*
78  * Remove a child that has died (exited)
79  * from the list of active children
80  */
81 static void
removechild(CHILD * child)82 removechild(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  */
129 static CHILD *
copychild(CHILD * child)130 copychild(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  */
148 static void
addchild(CHILD * child)149 addchild(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  */
169 static void
readchild(CHILD * child)170 readchild(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  */
218 static pid_t
waitproc(int * statval,int block)219 waitproc(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  */
252 static void
reap(int dummy)253 reap(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  */
311 static void
childscan(void)312 childscan(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  */
335 void
waitup(void)336 waitup(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  */
431 static int
setnonblocking(int fd)432 setnonblocking(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  */
446 int
spawn(struct cmd * cmd,struct cmd * cmdlist)447 spawn(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