Home | History | Annotate | Line # | Download | only in libntp
      1 /*	$NetBSD: work_fork.c,v 1.16 2026/02/08 14:52:28 christos Exp $	*/
      2 
      3 /*
      4  * work_fork.c - fork implementation for blocking worker child.
      5  */
      6 #include <config.h>
      7 #include "ntp_workimpl.h"
      8 
      9 #ifdef WORK_FORK
     10 #include <stdio.h>
     11 #include <ctype.h>
     12 #include <signal.h>
     13 #include <sys/wait.h>
     14 
     15 #include "iosignal.h"
     16 #include "ntp_stdlib.h"
     17 #include "ntp_malloc.h"
     18 #include "ntp_syslog.h"
     19 #include "ntpd.h"
     20 #include "ntp_io.h"
     21 #include "ntp_assert.h"
     22 #include "ntp_unixtime.h"
     23 #include "ntp_worker.h"
     24 
     25 /* === variables === */
     26 	int			worker_process;
     27 	addremove_io_fd_func	addremove_io_fd;
     28 static	volatile int		worker_sighup_received;
     29 int	saved_argc = 0;
     30 char	**saved_argv;
     31 
     32 /* === function prototypes === */
     33 static	void		fork_blocking_child(blocking_child *);
     34 static	RETSIGTYPE	worker_sighup(int);
     35 static	void		send_worker_home_atexit(void);
     36 static	void		cleanup_after_child(blocking_child *);
     37 
     38 /* === I/O helpers === */
     39 /* Since we have signals enabled, there's a good chance that blocking IO
     40  * via pipe suffers from EINTR -- and this goes for both directions.
     41  * The next two wrappers will loop until either all the data is written
     42  * or read, plus handling the EOF condition on read. They may return
     43  * zero if no data was transferred at all, and effectively every return
     44  * value that differs from the given transfer length signifies an error
     45  * condition.
     46  */
     47 
     48 static size_t
     49 netread(
     50 	int		fd,
     51 	void *		vb,
     52 	size_t		l
     53 	)
     54 {
     55 	char *		b = vb;
     56 	ssize_t		r;
     57 
     58 	while (l) {
     59 		r = read(fd, b, l);
     60 		if (r == -1 && errno == EAGAIN)
     61 			continue;
     62 		if (r > 0) {
     63 			l -= r;
     64 			b += r;
     65 		} else if (r == 0 || errno != EINTR) {
     66 			l = 0;
     67 		}
     68 	}
     69 	return (size_t)(b - (char *)vb);
     70 }
     71 
     72 
     73 static size_t
     74 netwrite(
     75 	int		fd,
     76 	const void *	vb,
     77 	size_t		l
     78 	)
     79 {
     80 	const char *	b = vb;
     81 	ssize_t		w;
     82 
     83 	while (l) {
     84 		w = write(fd, b, l);
     85 		if (w > 0) {
     86 			l -= w;
     87 			b += w;
     88 		} else if (errno != EINTR) {
     89 			l = 0;
     90 		}
     91 	}
     92 	return (size_t)(b - (const char *)vb);
     93 }
     94 
     95 
     96 #if defined(HAVE_DROPROOT)
     97 extern int set_user_group_ids(void);
     98 #endif
     99 
    100 /* === functions === */
    101 /*
    102  * exit_worker()
    103  *
    104  * On some systems _exit() is preferred to exit() for forked children.
    105  * For example, http://netbsd.gw.com/cgi-bin/man-cgi?fork++NetBSD-5.0
    106  * recommends _exit() to avoid double-flushing C runtime stream buffers
    107  * and also to avoid calling the parent's atexit() routines in the
    108  * child.  On those systems WORKER_CHILD_EXIT is _exit.  Since _exit
    109  * bypasses CRT cleanup, fflush() files we know might have output
    110  * buffered.
    111  */
    112 void
    113 exit_worker(
    114 	int	exitcode
    115 	)
    116 {
    117 	if (syslog_file != NULL)
    118 		fflush(syslog_file);
    119 	fflush(stdout);
    120 	fflush(stderr);
    121 	WORKER_CHILD_EXIT (exitcode);	/* space before ( required */
    122 }
    123 
    124 
    125 static RETSIGTYPE
    126 worker_sighup(
    127 	int sig
    128 	)
    129 {
    130 	if (SIGHUP == sig)
    131 		worker_sighup_received = 1;
    132 }
    133 
    134 
    135 int
    136 worker_sleep(
    137 	blocking_child *	c,
    138 	time_t			seconds
    139 	)
    140 {
    141 	u_int sleep_remain;
    142 
    143 	sleep_remain = (u_int)seconds;
    144 	do {
    145 		if (!worker_sighup_received)
    146 			sleep_remain = sleep(sleep_remain);
    147 		if (worker_sighup_received) {
    148 			TRACE(1, ("worker SIGHUP with %us left to sleep",
    149 				  sleep_remain));
    150 			worker_sighup_received = 0;
    151 			return -1;
    152 		}
    153 	} while (sleep_remain);
    154 
    155 	return 0;
    156 }
    157 
    158 
    159 void
    160 interrupt_worker_sleep(void)
    161 {
    162 	u_int			idx;
    163 	blocking_child *	c;
    164 	int			rc;
    165 
    166 	for (idx = 0; idx < blocking_children_alloc; idx++) {
    167 		c = blocking_children[idx];
    168 
    169 		if (NULL == c || c->reusable == TRUE)
    170 			continue;
    171 
    172 		rc = kill(c->pid, SIGHUP);
    173 		if (rc < 0)
    174 			msyslog(LOG_ERR,
    175 				"Unable to signal HUP to wake child pid %d: %m",
    176 				c->pid);
    177 	}
    178 }
    179 
    180 
    181 /*
    182  * harvest_child_status() runs in the parent.
    183  *
    184  * Note the error handling -- this is an interaction with SIGCHLD.
    185  * SIG_IGN on SIGCHLD on some OSes means do not wait but reap
    186  * automatically. Since we're not really interested in the result code,
    187  * we simply ignore the error.
    188  */
    189 static void
    190 harvest_child_status(
    191 	blocking_child *	c
    192 	)
    193 {
    194 	if (c->pid) {
    195 		/* Wait on the child so it can finish terminating */
    196 		if (waitpid(c->pid, NULL, 0) == c->pid)
    197 			TRACE(4, ("harvested child %d\n", c->pid));
    198 		else if (errno != ECHILD)
    199 			msyslog(LOG_ERR, "error waiting on child %d: %m", c->pid);
    200 		c->pid = 0;
    201 	}
    202 }
    203 
    204 /*
    205  * req_child_exit() runs in the parent.
    206  */
    207 int
    208 req_child_exit(
    209 	blocking_child *	c
    210 	)
    211 {
    212 	if (-1 != c->req_write_pipe) {
    213 		close(c->req_write_pipe);
    214 		c->req_write_pipe = -1;
    215 		return 0;
    216 	}
    217 	/* Closing the pipe forces the child to exit */
    218 	harvest_child_status(c);
    219 	return -1;
    220 }
    221 
    222 
    223 /*
    224  * cleanup_after_child() runs in parent.
    225  */
    226 static void
    227 cleanup_after_child(
    228 	blocking_child *	c
    229 	)
    230 {
    231 	harvest_child_status(c);
    232 	if (-1 != c->resp_read_pipe) {
    233 		(*addremove_io_fd)(c->resp_read_pipe, c->ispipe, TRUE);
    234 		close(c->resp_read_pipe);
    235 		c->resp_read_pipe = -1;
    236 	}
    237 	c->resp_read_ctx = NULL;
    238 	DEBUG_INSIST(-1 == c->req_read_pipe);
    239 	DEBUG_INSIST(-1 == c->resp_write_pipe);
    240 	c->reusable = TRUE;
    241 }
    242 
    243 
    244 static void
    245 send_worker_home_atexit(void)
    246 {
    247 	u_int			idx;
    248 	blocking_child *	c;
    249 
    250 	if (worker_process)
    251 		return;
    252 
    253 	for (idx = 0; idx < blocking_children_alloc; idx++) {
    254 		c = blocking_children[idx];
    255 		if (NULL == c)
    256 			continue;
    257 		req_child_exit(c);
    258 	}
    259 }
    260 
    261 
    262 int
    263 send_blocking_req_internal(
    264 	blocking_child *	c,
    265 	blocking_pipe_header *	hdr,
    266 	void *			data
    267 	)
    268 {
    269 	size_t	octets;
    270 	size_t	rc;
    271 
    272 	DEBUG_REQUIRE(hdr != NULL);
    273 	DEBUG_REQUIRE(data != NULL);
    274 	DEBUG_REQUIRE(BLOCKING_REQ_MAGIC == hdr->magic_sig);
    275 
    276 	if (-1 == c->req_write_pipe) {
    277 		fork_blocking_child(c);
    278 		DEBUG_INSIST(-1 != c->req_write_pipe);
    279 	}
    280 
    281 	octets = sizeof(*hdr);
    282 	rc = netwrite(c->req_write_pipe, hdr, octets);
    283 
    284 	if (rc == octets) {
    285 		octets = hdr->octets - sizeof(*hdr);
    286 		rc = netwrite(c->req_write_pipe, data, octets);
    287 		if (rc == octets)
    288 			return 0;
    289 	}
    290 
    291 	msyslog(LOG_ERR,
    292 		"send_blocking_req_internal: short write (%zu of %zu), %m",
    293 		rc, octets);
    294 
    295 	/* Fatal error.  Clean up the child process.  */
    296 	req_child_exit(c);
    297 	exit(1);	/* otherwise would be return -1 */
    298 }
    299 
    300 
    301 blocking_pipe_header *
    302 receive_blocking_req_internal(
    303 	blocking_child *	c
    304 	)
    305 {
    306 	blocking_pipe_header	hdr;
    307 	blocking_pipe_header *	req;
    308 	size_t			rc;
    309 	size_t			octets;
    310 
    311 	DEBUG_REQUIRE(-1 != c->req_read_pipe);
    312 
    313 	req = NULL;
    314 	rc = netread(c->req_read_pipe, &hdr, sizeof(hdr));
    315 
    316 	if (0 == rc) {
    317 		TRACE(4, ("parent closed request pipe, child %d terminating\n",
    318 			  c->pid));
    319 	} else if (rc != sizeof(hdr)) {
    320 		msyslog(LOG_ERR,
    321 			"receive_blocking_req_internal: short header read (%zu of %zu), %m",
    322 			rc, sizeof(hdr));
    323 	} else {
    324 		INSIST(sizeof(hdr) < hdr.octets && hdr.octets < 4 * 1024);
    325 		req = emalloc(hdr.octets);
    326 		memcpy(req, &hdr, sizeof(*req));
    327 		octets = hdr.octets - sizeof(hdr);
    328 		rc = netread(c->req_read_pipe, (char *)(req + 1),
    329 			     octets);
    330 
    331 		if (rc != octets)
    332 			msyslog(LOG_ERR,
    333 				"receive_blocking_req_internal: short read (%zu of %zu), %m",
    334 				rc, octets);
    335 		else if (BLOCKING_REQ_MAGIC != req->magic_sig)
    336 			msyslog(LOG_ERR,
    337 				"receive_blocking_req_internal: packet header mismatch (0x%x)",
    338 				req->magic_sig);
    339 		else
    340 			return req;
    341 	}
    342 
    343 	if (req != NULL)
    344 		free(req);
    345 
    346 	return NULL;
    347 }
    348 
    349 
    350 int
    351 send_blocking_resp_internal(
    352 	blocking_child *	c,
    353 	blocking_pipe_header *	resp
    354 	)
    355 {
    356 	size_t	octets;
    357 	size_t	rc;
    358 
    359 	DEBUG_REQUIRE(-1 != c->resp_write_pipe);
    360 
    361 	octets = resp->octets;
    362 	rc = netwrite(c->resp_write_pipe, resp, octets);
    363 	free(resp);
    364 
    365 	if (octets == rc)
    366 		return 0;
    367 
    368 	TRACE(1, ("send_blocking_resp_internal: short write (%zu of %zu), %m\n",
    369 		  rc, octets));
    370 	return -1;
    371 }
    372 
    373 
    374 blocking_pipe_header *
    375 receive_blocking_resp_internal(
    376 	blocking_child *	c
    377 	)
    378 {
    379 	blocking_pipe_header	hdr;
    380 	blocking_pipe_header *	resp;
    381 	size_t			rc;
    382 	size_t			octets;
    383 
    384 	DEBUG_REQUIRE(c->resp_read_pipe != -1);
    385 
    386 	resp = NULL;
    387 	rc = netread(c->resp_read_pipe, &hdr, sizeof(hdr));
    388 
    389 	if (0 == rc) {
    390 		/* this is the normal child exited indication */
    391 	} else if (rc != sizeof(hdr)) {
    392 		TRACE(1, ("receive_blocking_resp_internal: short header read (%zu of %zu), %m\n",
    393 			  rc, sizeof(hdr)));
    394 	} else if (BLOCKING_RESP_MAGIC != hdr.magic_sig) {
    395 		TRACE(1, ("receive_blocking_resp_internal: header mismatch (0x%x)\n",
    396 			  hdr.magic_sig));
    397 	} else {
    398 		INSIST(sizeof(hdr) < hdr.octets &&
    399 		       hdr.octets < 16 * 1024);
    400 		resp = emalloc(hdr.octets);
    401 		memcpy(resp, &hdr, sizeof(*resp));
    402 		octets = hdr.octets - sizeof(hdr);
    403 		rc = netread(c->resp_read_pipe, (char *)(resp + 1),
    404 			     octets);
    405 
    406 		if (rc != octets)
    407 			TRACE(1, ("receive_blocking_resp_internal: short read (%zu of %zu), %m\n",
    408 				  rc, octets));
    409 		else
    410 			return resp;
    411 	}
    412 
    413 	cleanup_after_child(c);
    414 
    415 	if (resp != NULL)
    416 		free(resp);
    417 
    418 	return NULL;
    419 }
    420 
    421 
    422 #if defined(HAVE_DROPROOT) && defined(WORK_FORK)
    423 void
    424 fork_deferred_worker(void)
    425 {
    426 	u_int			idx;
    427 	blocking_child *	c;
    428 
    429 	REQUIRE(droproot && root_dropped);
    430 
    431 	for (idx = 0; idx < blocking_children_alloc; idx++) {
    432 		c = blocking_children[idx];
    433 		if (NULL == c)
    434 			continue;
    435 		if (-1 != c->req_write_pipe && 0 == c->pid)
    436 			fork_blocking_child(c);
    437 	}
    438 }
    439 #endif
    440 
    441 #if HAVE_SETPROCTITLE == 0
    442 static void
    443 setproctitle(const char *fmt, ...)
    444 {
    445 	va_list ap;
    446 	char b1[128];
    447 	int argcc, argvlen, l;
    448 
    449 	if (saved_argc == 0)
    450 		return;
    451 
    452 	va_start(ap, fmt);
    453 	vsnprintf(b1, sizeof(b1), fmt, ap);
    454 	va_end(ap);
    455 
    456 	/* Clear argv */
    457 	for (argvlen = 0, argcc = 0; argcc < saved_argc; argcc++) {
    458 		l = strlen(saved_argv[argcc]);
    459 		argvlen += l + 1;
    460 		memset(saved_argv[argcc], 0, l);
    461 	}
    462 	l = snprintf(saved_argv[0], argvlen, "ntpd: %s", b1);
    463 	for (argcc = 1; argcc < saved_argc; argcc++)
    464 		saved_argv[argcc] = &saved_argv[0][l];
    465 }
    466 #endif
    467 
    468 static void
    469 fork_blocking_child(
    470 	blocking_child *	c
    471 	)
    472 {
    473 	static int	atexit_installed;
    474 	static int	blocking_pipes[4] = { -1, -1, -1, -1 };
    475 	int		rc;
    476 	int		was_pipe;
    477 	int		is_pipe;
    478 	int		saved_errno = 0;
    479 	int		childpid;
    480 	int		keep_fd;
    481 	int		fd;
    482 
    483 	/*
    484 	 * parent and child communicate via a pair of pipes.
    485 	 *
    486 	 * 0 child read request
    487 	 * 1 parent write request
    488 	 * 2 parent read response
    489 	 * 3 child write response
    490 	 */
    491 	if (-1 == c->req_write_pipe) {
    492 		rc = pipe_socketpair(&blocking_pipes[0], &was_pipe);
    493 		if (0 != rc) {
    494 			saved_errno = errno;
    495 		} else {
    496 			rc = pipe_socketpair(&blocking_pipes[2], &is_pipe);
    497 			if (0 != rc) {
    498 				saved_errno = errno;
    499 				close(blocking_pipes[0]);
    500 				close(blocking_pipes[1]);
    501 			} else {
    502 				INSIST(was_pipe == is_pipe);
    503 			}
    504 		}
    505 		if (0 != rc) {
    506 			errno = saved_errno;
    507 			msyslog(LOG_ERR, "unable to create worker pipes: %m");
    508 			exit(1);
    509 		}
    510 
    511 		/*
    512 		 * Move the descriptors the parent will keep open out of the
    513 		 * low descriptors preferred by C runtime buffered FILE *.
    514 		 */
    515 		c->req_write_pipe = move_fd(blocking_pipes[1]);
    516 		c->resp_read_pipe = move_fd(blocking_pipes[2]);
    517 		/*
    518 		 * wake any worker child on orderly shutdown of the
    519 		 * daemon so that it can notice the broken pipes and
    520 		 * go away promptly.
    521 		 */
    522 		if (!atexit_installed) {
    523 			atexit(&send_worker_home_atexit);
    524 			atexit_installed = TRUE;
    525 		}
    526 	}
    527 
    528 #if defined(HAVE_DROPROOT) && !defined(NEED_EARLY_FORK)
    529 	/* defer the fork until after root is dropped */
    530 	if (droproot && !root_dropped)
    531 		return;
    532 #endif
    533 	if (syslog_file != NULL)
    534 		fflush(syslog_file);
    535 	fflush(stdout);
    536 	fflush(stderr);
    537 
    538 	/* [BUG 3050] setting SIGCHLD to SIG_IGN likely causes unwanted
    539 	 * or undefined effects. We don't do it and leave SIGCHLD alone.
    540 	 */
    541 	/* signal_no_reset(SIGCHLD, SIG_IGN); */
    542 
    543 	childpid = fork();
    544 	if (-1 == childpid) {
    545 		msyslog(LOG_ERR, "unable to fork worker: %m");
    546 		exit(1);
    547 	}
    548 
    549 	if (childpid) {
    550 		/* this is the parent */
    551 		TRACE(1, ("forked worker child (pid %d)\n", childpid));
    552 		c->pid = childpid;
    553 		c->ispipe = is_pipe;
    554 
    555 		/* close the child's pipe descriptors. */
    556 		close(blocking_pipes[0]);
    557 		close(blocking_pipes[3]);
    558 
    559 		memset(blocking_pipes, -1, sizeof(blocking_pipes));
    560 
    561 		/* wire into I/O loop */
    562 		(*addremove_io_fd)(c->resp_read_pipe, is_pipe, FALSE);
    563 
    564 		/* wait until child is done */
    565 		rc = netread(c->resp_read_pipe, &rc, sizeof(rc));
    566 
    567 		return;		/* parent returns */
    568 	}
    569 
    570 	/*
    571 	 * The parent gets the child pid as the return value of fork().
    572 	 * The child must work for it.
    573 	 */
    574 	c->pid = getpid();
    575 	worker_process = TRUE;
    576 
    577 	/*
    578 	 * Change the process name of the child to avoid confusion
    579 	 * about ntpd trunning twice.
    580 	 */
    581 	setproctitle("asynchronous dns resolver");
    582 
    583 	/*
    584 	 * In the child, close all files except stdin, stdout, stderr,
    585 	 * and the two child ends of the pipes.
    586 	 */
    587 	DEBUG_INSIST(-1 == c->req_read_pipe);
    588 	DEBUG_INSIST(-1 == c->resp_write_pipe);
    589 	c->req_read_pipe = blocking_pipes[0];
    590 	c->resp_write_pipe = blocking_pipes[3];
    591 
    592 	kill_asyncio(0);
    593 
    594 	/* Tell parent we are ready */
    595 	rc = netwrite(c->resp_write_pipe, &rc, sizeof(rc));
    596 
    597 	closelog();
    598 	if (syslog_file != NULL) {
    599 		fclose(syslog_file);
    600 		syslog_file = NULL;
    601 		syslogit = TRUE;
    602 	}
    603 	keep_fd = max(c->req_read_pipe, c->resp_write_pipe);
    604 	for (fd = 3; fd < keep_fd; fd++)
    605 		if (fd != c->req_read_pipe &&
    606 		    fd != c->resp_write_pipe)
    607 			close(fd);
    608 	close_all_beyond(keep_fd);
    609 	/*
    610 	 * We get signals from refclock serial I/O on NetBSD in the
    611 	 * worker if we do not reset SIGIO's handler to the default.
    612 	 * It is not conditionalized for NetBSD alone because on
    613 	 * systems where it is not needed, it is harmless, and that
    614 	 * allows us to handle unknown others with NetBSD behavior.
    615 	 * [Bug 1386]
    616 	 */
    617 #if defined(USE_SIGIO)
    618 	signal_no_reset(SIGIO, SIG_DFL);
    619 #elif defined(USE_SIGPOLL)
    620 	signal_no_reset(SIGPOLL, SIG_DFL);
    621 #endif
    622 	signal_no_reset(SIGHUP, worker_sighup);
    623 	init_logging("ntp_intres", 0, FALSE);
    624 	setup_logfile(NULL);
    625 
    626 #ifdef HAVE_DROPROOT
    627 	(void) set_user_group_ids();
    628 #endif
    629 
    630 	/*
    631 	 * And now back to the portable code
    632 	 */
    633 	exit_worker(blocking_child_common(c));
    634 }
    635 
    636 
    637 void worker_global_lock(int inOrOut)
    638 {
    639 	(void)inOrOut;
    640 }
    641 
    642 #else	/* !WORK_FORK follows */
    643 char work_fork_nonempty_compilation_unit;
    644 #endif
    645