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