text: /*
   * ntp_worker.c
   */
  #include <config.h>
  #include "ntp_workimpl.h"
  
  #ifdef WORKER
  
  #include <stdio.h>
  #include <ctype.h>
  #include <signal.h>
  
  #include "iosignal.h"
  #include "ntp_stdlib.h"
  #include "ntp_malloc.h"
  #include "ntp_syslog.h"
  #include "ntpd.h"
  #include "ntp_io.h"
  #include "ntp_assert.h"
  #include "ntp_unixtime.h"
  #include "intreswork.h"
  
  
  #define CHILD_MAX_IDLE	(3 * 60)	/* seconds, idle worker limit */
  
  blocking_child **	blocking_children;
  size_t			blocking_children_alloc;
  int			worker_per_query;	/* boolean */
  int			intres_req_pending;
  volatile u_int		blocking_child_ready_seen;
  volatile u_int		blocking_child_ready_done;
  
  
  #ifndef HAVE_IO_COMPLETION_PORT
  /*
   * pipe_socketpair()
   *
   * Provides an AF_UNIX socketpair on systems which have them, otherwise
   * pair of unidirectional pipes.
   */
  int
  pipe_socketpair(
  	int	caller_fds[2],
  	int *	is_pipe
  	)
  {
  	int	rc;
  	int	fds[2];
  	int	called_pipe;
  
  #ifdef HAVE_SOCKETPAIR
  	rc = socketpair(AF_UNIX, SOCK_STREAM, 0, &fds[0]);
  #else
  	rc = -1;
  #endif
  
  	if (-1 == rc) {
  		rc = pipe(&fds[0]);
  		called_pipe = TRUE;
  	} else {
  		called_pipe = FALSE;
  	}
  
  	if (-1 == rc)
  		return rc;
  
  	caller_fds[0] = fds[0];
  	caller_fds[1] = fds[1];
  	if (is_pipe != NULL)
  		*is_pipe = called_pipe;
  
  	return 0;
  }
  
  
  /*
   * close_all_except()
   *
   * Close all file descriptors except the given keep_fd.
   */
  void
  close_all_except(
  	int keep_fd
  	)
  {
  	int fd;
  
  	for (fd = 0; fd < keep_fd; fd++)
  		close(fd);
  
  	close_all_beyond(keep_fd);
  }
  
  
  /*
   * close_all_beyond()
   *
   * Close all file descriptors after the given keep_fd, which is the
   * highest fd to keep open.
   */
  void
  close_all_beyond(
  	int keep_fd
  	)
  {
  # ifdef HAVE_CLOSEFROM
  	closefrom(keep_fd + 1);
  # elif defined(F_CLOSEM)
  	/*
  	 * From 'Writing Reliable AIX Daemons,' SG24-4946-00,
  	 * by Eric Agar (saves us from doing 32767 system
  	 * calls)
  	 */
  	if (fcntl(keep_fd + 1, F_CLOSEM, 0) == -1)
  		msyslog(LOG_ERR, "F_CLOSEM(%d): %m", keep_fd + 1);
  # else	/* !HAVE_CLOSEFROM && !F_CLOSEM follows */
  	int fd;
  	int max_fd;
  
  	max_fd = GETDTABLESIZE();
  	for (fd = keep_fd + 1; fd < max_fd; fd++)
  		close(fd);
  # endif	/* !HAVE_CLOSEFROM && !F_CLOSEM */
  }
  #endif	/* HAVE_IO_COMPLETION_PORT */
  
  
  u_int
  available_blocking_child_slot(void)
  {
  	const size_t	each = sizeof(blocking_children[0]);
  	u_int		slot;
  	size_t		prev_alloc;
  	size_t		new_alloc;
  	size_t		prev_octets;
  	size_t		octets;
  
  	for (slot = 0; slot < blocking_children_alloc; slot++) {
  		if (NULL == blocking_children[slot])
  			return slot;
  		if (blocking_children[slot]->reusable) {
  			blocking_children[slot]->reusable = FALSE;
  			return slot;
  		}
  	}
  
  	prev_alloc = blocking_children_alloc;
  	prev_octets = prev_alloc * each;
  	new_alloc = blocking_children_alloc + 4;
  	octets = new_alloc * each;
  	blocking_children = erealloc_zero(blocking_children, octets,
  					  prev_octets);
  	blocking_children_alloc = new_alloc;
  
  	/* assume we'll never have enough workers to overflow u_int */
  	return (u_int)prev_alloc;
  }
  
  
  int
  queue_blocking_request(
  	blocking_work_req	rtype,
  	void *			req,
  	size_t			reqsize,
  	blocking_work_callback	done_func,
  	void *			context
  	)
  {
  	static u_int		intres_slot = UINT_MAX;
  	u_int			child_slot;
  	blocking_child *	c;
  	blocking_pipe_header	req_hdr;
  
  	req_hdr.octets = sizeof(req_hdr) + reqsize;
  	req_hdr.magic_sig = BLOCKING_REQ_MAGIC;
  	req_hdr.rtype = rtype;
  	req_hdr.done_func = done_func;
  	req_hdr.context = context;
  
  	child_slot = UINT_MAX;
  	if (worker_per_query || UINT_MAX == intres_slot ||
  	    blocking_children[intres_slot]->reusable)
  		child_slot = available_blocking_child_slot();
  	if (!worker_per_query) {
  		if (UINT_MAX == intres_slot)
  			intres_slot = child_slot;
  		else
  			child_slot = intres_slot;
  		if (0 == intres_req_pending)
  			intres_timeout_req(0);
  	}
  	intres_req_pending++;
  	INSIST(UINT_MAX != child_slot);
  	c = blocking_children[child_slot];
  	if (NULL == c) {
  		c = emalloc_zero(sizeof(*c));
  #ifdef WORK_FORK
  		c->req_read_pipe = -1;
  		c->req_write_pipe = -1;
  #endif
  #ifdef WORK_PIPE
  		c->resp_read_pipe = -1;
  		c->resp_write_pipe = -1;
  #endif
  		blocking_children[child_slot] = c;
  	}
  	req_hdr.child_idx = child_slot;
  
  	return send_blocking_req_internal(c, &req_hdr, req);
  }
  
  
  int queue_blocking_response(
  	blocking_child *		c,
  	blocking_pipe_header *		resp,
  	size_t				respsize,
  	const blocking_pipe_header *	req
  	)
  {
  	resp->octets = respsize;
  	resp->magic_sig = BLOCKING_RESP_MAGIC;
  	resp->rtype = req->rtype;
  	resp->context = req->context;
  	resp->done_func = req->done_func;
  
  	return send_blocking_resp_internal(c, resp);
  }
  
  
  void
  process_blocking_resp(
  	blocking_child *	c
  	)
  {
  	blocking_pipe_header *	resp;
  	void *			data;
  
  	/*
  	 * On Windows send_blocking_resp_internal() may signal the
  	 * blocking_response_ready event multiple times while we're
  	 * processing a response, so always consume all available
  	 * responses before returning to test the event again.
  	 */
  #ifdef WORK_THREAD
  	do {
  #endif
  		resp = receive_blocking_resp_internal(c);
  		if (NULL != resp) {
  			DEBUG_REQUIRE(BLOCKING_RESP_MAGIC ==
  				      resp->magic_sig);
  			data = (char *)resp + sizeof(*resp);
  			intres_req_pending--;
  			(*resp->done_func)(resp->rtype, resp->context,
  					   resp->octets - sizeof(*resp),
  					   data);
  			free(resp);
  		}
  #ifdef WORK_THREAD
  	} while (NULL != resp);
  #endif
  	if (!worker_per_query && 0 == intres_req_pending)
  		intres_timeout_req(CHILD_MAX_IDLE);
  	else if (worker_per_query)
  		req_child_exit(c);
  }
  
  void
  harvest_blocking_responses(void)
  {
  	size_t		idx;
  	blocking_child*	cp;
  	u_int		scseen, scdone;
  
  	scseen = blocking_child_ready_seen;
  	scdone = blocking_child_ready_done;
  	if (scdone != scseen) {
  		blocking_child_ready_done = scseen;
  		for (idx = 0; idx < blocking_children_alloc; idx++) {
  			cp = blocking_children[idx];
  			if (NULL == cp)
  				continue;
  			scseen = cp->resp_ready_seen;
  			scdone = cp->resp_ready_done;
  			if (scdone != scseen) {
  				cp->resp_ready_done = scseen;
  				process_blocking_resp(cp);
  			}
  		}
  	}
  }
  
  
  /*
   * blocking_child_common runs as a forked child or a thread
   */
  int
  blocking_child_common(
  	blocking_child	*c
  	)
  {
  	int say_bye;
  	blocking_pipe_header *req;
  
  	say_bye = FALSE;
  	while (!say_bye) {
  		req = receive_blocking_req_internal(c);
  		if (NULL == req) {
  			say_bye = TRUE;
  			continue;
  		}
  
  		DEBUG_REQUIRE(BLOCKING_REQ_MAGIC == req->magic_sig);
  
  		switch (req->rtype) {
  		case BLOCKING_GETADDRINFO:
  			if (blocking_getaddrinfo(c, req))
  				say_bye = TRUE;
  			break;
  
  		case BLOCKING_GETNAMEINFO:
  			if (blocking_getnameinfo(c, req))
  				say_bye = TRUE;
  			break;
  
  		default:
  			msyslog(LOG_ERR, "unknown req %d to blocking worker", req->rtype);
  			say_bye = TRUE;
  		}
  
  		free(req);
  	}
  
  	return 0;
  }
  
  
  /*
   * worker_idle_timer_fired()
   *
   * The parent starts this timer when the last pending response has been
   * received from the child, making it idle, and clears the timer when a
   * request is dispatched to the child.  Once the timer expires, the
   * child is sent packing.
   *
   * This is called when worker_idle_timer is nonzero and less than or
   * equal to current_time.
   */
  void
  worker_idle_timer_fired(void)
  {
  	u_int			idx;
  	blocking_child *	c;
  
  	DEBUG_REQUIRE(0 == intres_req_pending);
  
  	intres_timeout_req(0);
  	for (idx = 0; idx < blocking_children_alloc; idx++) {
  		c = blocking_children[idx];
  		if (NULL == c)
  			continue;
  		req_child_exit(c);
  	}
  }
  
  
  #else	/* !WORKER follows */
  int ntp_worker_nonempty_compilation_unit;
  #endif
  