1 /*        $NetBSD: ntp_worker.c,v 1.7 2020/05/25 20:47:24 christos Exp $        */
2 
3 /*
4  * ntp_worker.c
5  */
6 #include <config.h>
7 #include "ntp_workimpl.h"
8 
9 #ifdef WORKER
10 
11 #include <stdio.h>
12 #include <ctype.h>
13 #include <signal.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 "intreswork.h"
24 
25 
26 #define CHILD_MAX_IDLE        (3 * 60)  /* seconds, idle worker limit */
27 
28 blocking_child **   blocking_children;
29 size_t                        blocking_children_alloc;
30 int                           worker_per_query;   /* boolean */
31 int                           intres_req_pending;
32 volatile u_int                blocking_child_ready_seen;
33 volatile u_int                blocking_child_ready_done;
34 
35 
36 #ifndef HAVE_IO_COMPLETION_PORT
37 /*
38  * pipe_socketpair()
39  *
40  * Provides an AF_UNIX socketpair on systems which have them, otherwise
41  * pair of unidirectional pipes.
42  */
43 int
pipe_socketpair(int caller_fds[2],int * is_pipe)44 pipe_socketpair(
45           int       caller_fds[2],
46           int *     is_pipe
47           )
48 {
49           int       rc;
50           int       fds[2];
51           int       called_pipe;
52 
53 #ifdef HAVE_SOCKETPAIR
54           rc = socketpair(AF_UNIX, SOCK_STREAM, 0, &fds[0]);
55 #else
56           rc = -1;
57 #endif
58 
59           if (-1 == rc) {
60                     rc = pipe(&fds[0]);
61                     called_pipe = TRUE;
62           } else {
63                     called_pipe = FALSE;
64           }
65 
66           if (-1 == rc)
67                     return rc;
68 
69           caller_fds[0] = fds[0];
70           caller_fds[1] = fds[1];
71           if (is_pipe != NULL)
72                     *is_pipe = called_pipe;
73 
74           return 0;
75 }
76 
77 
78 /*
79  * close_all_except()
80  *
81  * Close all file descriptors except the given keep_fd.
82  */
83 void
close_all_except(int keep_fd)84 close_all_except(
85           int keep_fd
86           )
87 {
88           int fd;
89 
90           for (fd = 0; fd < keep_fd; fd++)
91                     close(fd);
92 
93           close_all_beyond(keep_fd);
94 }
95 
96 
97 /*
98  * close_all_beyond()
99  *
100  * Close all file descriptors after the given keep_fd, which is the
101  * highest fd to keep open.
102  */
103 void
close_all_beyond(int keep_fd)104 close_all_beyond(
105           int keep_fd
106           )
107 {
108 # ifdef HAVE_CLOSEFROM
109           closefrom(keep_fd + 1);
110 # elif defined(F_CLOSEM)
111           /*
112            * From 'Writing Reliable AIX Daemons,' SG24-4946-00,
113            * by Eric Agar (saves us from doing 32767 system
114            * calls)
115            */
116           if (fcntl(keep_fd + 1, F_CLOSEM, 0) == -1)
117                     msyslog(LOG_ERR, "F_CLOSEM(%d): %m", keep_fd + 1);
118 # else    /* !HAVE_CLOSEFROM && !F_CLOSEM follows */
119           int fd;
120           int max_fd;
121 
122           max_fd = GETDTABLESIZE();
123           for (fd = keep_fd + 1; fd < max_fd; fd++)
124                     close(fd);
125 # endif   /* !HAVE_CLOSEFROM && !F_CLOSEM */
126 }
127 #endif    /* HAVE_IO_COMPLETION_PORT */
128 
129 
130 u_int
available_blocking_child_slot(void)131 available_blocking_child_slot(void)
132 {
133           const size_t        each = sizeof(blocking_children[0]);
134           u_int               slot;
135           size_t              prev_alloc;
136           size_t              new_alloc;
137           size_t              prev_octets;
138           size_t              octets;
139 
140           for (slot = 0; slot < blocking_children_alloc; slot++) {
141                     if (NULL == blocking_children[slot])
142                               return slot;
143                     if (blocking_children[slot]->reusable) {
144                               blocking_children[slot]->reusable = FALSE;
145                               return slot;
146                     }
147           }
148 
149           prev_alloc = blocking_children_alloc;
150           prev_octets = prev_alloc * each;
151           new_alloc = blocking_children_alloc + 4;
152           octets = new_alloc * each;
153           blocking_children = erealloc_zero(blocking_children, octets,
154                                                     prev_octets);
155           blocking_children_alloc = new_alloc;
156 
157           /* assume we'll never have enough workers to overflow u_int */
158           return (u_int)prev_alloc;
159 }
160 
161 
162 int
queue_blocking_request(blocking_work_req rtype,void * req,size_t reqsize,blocking_work_callback done_func,void * context)163 queue_blocking_request(
164           blocking_work_req   rtype,
165           void *                        req,
166           size_t                        reqsize,
167           blocking_work_callback        done_func,
168           void *                        context
169           )
170 {
171           static u_int                  intres_slot = UINT_MAX;
172           u_int                         child_slot;
173           blocking_child *    c;
174           blocking_pipe_header          req_hdr;
175 
176           req_hdr.octets = sizeof(req_hdr) + reqsize;
177           req_hdr.magic_sig = BLOCKING_REQ_MAGIC;
178           req_hdr.rtype = rtype;
179           req_hdr.done_func = done_func;
180           req_hdr.context = context;
181 
182           child_slot = UINT_MAX;
183           if (worker_per_query || UINT_MAX == intres_slot ||
184               blocking_children[intres_slot]->reusable)
185                     child_slot = available_blocking_child_slot();
186           if (!worker_per_query) {
187                     if (UINT_MAX == intres_slot)
188                               intres_slot = child_slot;
189                     else
190                               child_slot = intres_slot;
191                     if (0 == intres_req_pending)
192                               intres_timeout_req(0);
193           }
194           intres_req_pending++;
195           INSIST(UINT_MAX != child_slot);
196           c = blocking_children[child_slot];
197           if (NULL == c) {
198                     c = emalloc_zero(sizeof(*c));
199 #ifdef WORK_FORK
200                     c->req_read_pipe = -1;
201                     c->req_write_pipe = -1;
202 #endif
203 #ifdef WORK_PIPE
204                     c->resp_read_pipe = -1;
205                     c->resp_write_pipe = -1;
206 #endif
207                     blocking_children[child_slot] = c;
208           }
209           req_hdr.child_idx = child_slot;
210 
211           return send_blocking_req_internal(c, &req_hdr, req);
212 }
213 
214 
queue_blocking_response(blocking_child * c,blocking_pipe_header * resp,size_t respsize,const blocking_pipe_header * req)215 int queue_blocking_response(
216           blocking_child *              c,
217           blocking_pipe_header *                  resp,
218           size_t                                  respsize,
219           const blocking_pipe_header *  req
220           )
221 {
222           resp->octets = respsize;
223           resp->magic_sig = BLOCKING_RESP_MAGIC;
224           resp->rtype = req->rtype;
225           resp->context = req->context;
226           resp->done_func = req->done_func;
227 
228           return send_blocking_resp_internal(c, resp);
229 }
230 
231 
232 void
process_blocking_resp(blocking_child * c)233 process_blocking_resp(
234           blocking_child *    c
235           )
236 {
237           blocking_pipe_header *        resp;
238           void *                        data;
239 
240           /*
241            * On Windows send_blocking_resp_internal() may signal the
242            * blocking_response_ready event multiple times while we're
243            * processing a response, so always consume all available
244            * responses before returning to test the event again.
245            */
246 #ifdef WORK_THREAD
247           do {
248 #endif
249                     resp = receive_blocking_resp_internal(c);
250                     if (NULL != resp) {
251                               DEBUG_REQUIRE(BLOCKING_RESP_MAGIC ==
252                                               resp->magic_sig);
253                               data = (char *)resp + sizeof(*resp);
254                               intres_req_pending--;
255                               (*resp->done_func)(resp->rtype, resp->context,
256                                                      resp->octets - sizeof(*resp),
257                                                      data);
258                               free(resp);
259                     }
260 #ifdef WORK_THREAD
261           } while (NULL != resp);
262 #endif
263           if (!worker_per_query && 0 == intres_req_pending)
264                     intres_timeout_req(CHILD_MAX_IDLE);
265           else if (worker_per_query)
266                     req_child_exit(c);
267 }
268 
269 void
harvest_blocking_responses(void)270 harvest_blocking_responses(void)
271 {
272           size_t              idx;
273           blocking_child*     cp;
274           u_int               scseen, scdone;
275 
276           scseen = blocking_child_ready_seen;
277           scdone = blocking_child_ready_done;
278           if (scdone != scseen) {
279                     blocking_child_ready_done = scseen;
280                     for (idx = 0; idx < blocking_children_alloc; idx++) {
281                               cp = blocking_children[idx];
282                               if (NULL == cp)
283                                         continue;
284                               scseen = cp->resp_ready_seen;
285                               scdone = cp->resp_ready_done;
286                               if (scdone != scseen) {
287                                         cp->resp_ready_done = scseen;
288                                         process_blocking_resp(cp);
289                               }
290                     }
291           }
292 }
293 
294 
295 /*
296  * blocking_child_common runs as a forked child or a thread
297  */
298 int
blocking_child_common(blocking_child * c)299 blocking_child_common(
300           blocking_child      *c
301           )
302 {
303           int say_bye;
304           blocking_pipe_header *req;
305 
306           say_bye = FALSE;
307           while (!say_bye) {
308                     req = receive_blocking_req_internal(c);
309                     if (NULL == req) {
310                               say_bye = TRUE;
311                               continue;
312                     }
313 
314                     DEBUG_REQUIRE(BLOCKING_REQ_MAGIC == req->magic_sig);
315 
316                     switch (req->rtype) {
317                     case BLOCKING_GETADDRINFO:
318                               if (blocking_getaddrinfo(c, req))
319                                         say_bye = TRUE;
320                               break;
321 
322                     case BLOCKING_GETNAMEINFO:
323                               if (blocking_getnameinfo(c, req))
324                                         say_bye = TRUE;
325                               break;
326 
327                     default:
328                               msyslog(LOG_ERR, "unknown req %d to blocking worker", req->rtype);
329                               say_bye = TRUE;
330                     }
331 
332                     free(req);
333           }
334 
335           return 0;
336 }
337 
338 
339 /*
340  * worker_idle_timer_fired()
341  *
342  * The parent starts this timer when the last pending response has been
343  * received from the child, making it idle, and clears the timer when a
344  * request is dispatched to the child.  Once the timer expires, the
345  * child is sent packing.
346  *
347  * This is called when worker_idle_timer is nonzero and less than or
348  * equal to current_time.
349  */
350 void
worker_idle_timer_fired(void)351 worker_idle_timer_fired(void)
352 {
353           u_int                         idx;
354           blocking_child *    c;
355 
356           DEBUG_REQUIRE(0 == intres_req_pending);
357 
358           intres_timeout_req(0);
359           for (idx = 0; idx < blocking_children_alloc; idx++) {
360                     c = blocking_children[idx];
361                     if (NULL == c)
362                               continue;
363                     req_child_exit(c);
364           }
365 }
366 
367 
368 #else     /* !WORKER follows */
369 int ntp_worker_nonempty_compilation_unit;
370 #endif
371