app.c revision 1.1
1/*	$NetBSD: app.c,v 1.1 2020/05/24 19:36:45 christos Exp $	*/
2
3/*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public
7 * License, v. 2.0. If a copy of the MPL was not distributed with this
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * See the COPYRIGHT file distributed with this work for additional
11 * information regarding copyright ownership.
12 */
13
14/*! \file */
15
16#include <errno.h>
17#include <stdbool.h>
18#include <stddef.h>
19#include <stdlib.h>
20#include <sys/types.h>
21#include <unistd.h>
22
23#ifndef WIN32
24#include <inttypes.h>
25#include <signal.h>
26#include <sys/time.h>
27#endif /* WIN32 */
28
29#include <isc/app.h>
30#include <isc/atomic.h>
31#include <isc/condition.h>
32#include <isc/event.h>
33#include <isc/mem.h>
34#include <isc/mutex.h>
35#include <isc/platform.h>
36#include <isc/strerr.h>
37#include <isc/string.h>
38#include <isc/task.h>
39#include <isc/thread.h>
40#include <isc/time.h>
41#include <isc/util.h>
42
43#ifdef WIN32
44#include <process.h>
45#else /* WIN32 */
46#include <pthread.h>
47#endif /* WIN32 */
48
49/*%
50 * For BIND9 internal applications built with threads, we use a single app
51 * context and let multiple worker, I/O, timer threads do actual jobs.
52 */
53
54static isc_thread_t blockedthread;
55static atomic_bool is_running;
56
57#ifdef WIN32
58/*
59 * We need to remember which thread is the main thread...
60 */
61static isc_thread_t main_thread;
62#endif /* ifdef WIN32 */
63
64/*
65 * The application context of this module.
66 */
67#define APPCTX_MAGIC	ISC_MAGIC('A', 'p', 'c', 'x')
68#define VALID_APPCTX(c) ISC_MAGIC_VALID(c, APPCTX_MAGIC)
69
70#ifdef WIN32
71#define NUM_EVENTS 2
72
73enum { RELOAD_EVENT, SHUTDOWN_EVENT };
74#endif /* WIN32 */
75
76struct isc_appctx {
77	unsigned int magic;
78	isc_mem_t *mctx;
79	isc_mutex_t lock;
80	isc_eventlist_t on_run;
81	atomic_bool shutdown_requested;
82	atomic_bool running;
83	atomic_bool want_shutdown;
84	atomic_bool want_reload;
85	atomic_bool blocked;
86#ifdef WIN32
87	HANDLE hEvents[NUM_EVENTS];
88#else  /* WIN32 */
89	isc_mutex_t readylock;
90	isc_condition_t ready;
91#endif /* WIN32 */
92};
93
94static isc_appctx_t isc_g_appctx;
95
96#ifndef WIN32
97static void
98handle_signal(int sig, void (*handler)(int)) {
99	struct sigaction sa;
100
101	memset(&sa, 0, sizeof(sa));
102	sa.sa_handler = handler;
103
104	if (sigfillset(&sa.sa_mask) != 0 || sigaction(sig, &sa, NULL) < 0) {
105		char strbuf[ISC_STRERRORSIZE];
106		strerror_r(errno, strbuf, sizeof(strbuf));
107		isc_error_fatal(__FILE__, __LINE__,
108				"handle_signal() %d setup: %s", sig, strbuf);
109	}
110}
111#endif /* ifndef WIN32 */
112
113isc_result_t
114isc_app_ctxstart(isc_appctx_t *ctx) {
115	REQUIRE(VALID_APPCTX(ctx));
116
117	/*
118	 * Start an ISC library application.
119	 */
120
121	isc_mutex_init(&ctx->lock);
122
123#ifndef WIN32
124	isc_mutex_init(&ctx->readylock);
125	isc_condition_init(&ctx->ready);
126#endif /* WIN32 */
127
128	ISC_LIST_INIT(ctx->on_run);
129
130	atomic_init(&ctx->shutdown_requested, false);
131	atomic_init(&ctx->running, false);
132	atomic_init(&ctx->want_shutdown, false);
133	atomic_init(&ctx->want_reload, false);
134	atomic_init(&ctx->blocked, false);
135
136#ifdef WIN32
137	main_thread = GetCurrentThread();
138
139	/* Create the reload event in a non-signaled state */
140	ctx->hEvents[RELOAD_EVENT] = CreateEvent(NULL, FALSE, FALSE, NULL);
141
142	/* Create the shutdown event in a non-signaled state */
143	ctx->hEvents[SHUTDOWN_EVENT] = CreateEvent(NULL, FALSE, FALSE, NULL);
144#else /* WIN32 */
145	int presult;
146	sigset_t sset;
147	char strbuf[ISC_STRERRORSIZE];
148
149	/*
150	 * Always ignore SIGPIPE.
151	 */
152	handle_signal(SIGPIPE, SIG_IGN);
153
154	handle_signal(SIGHUP, SIG_DFL);
155	handle_signal(SIGTERM, SIG_DFL);
156	handle_signal(SIGINT, SIG_DFL);
157
158	/*
159	 * Block SIGHUP, SIGINT, SIGTERM.
160	 *
161	 * If isc_app_start() is called from the main thread before any other
162	 * threads have been created, then the pthread_sigmask() call below
163	 * will result in all threads having SIGHUP, SIGINT and SIGTERM
164	 * blocked by default, ensuring that only the thread that calls
165	 * sigwait() for them will get those signals.
166	 */
167	if (sigemptyset(&sset) != 0 || sigaddset(&sset, SIGHUP) != 0 ||
168	    sigaddset(&sset, SIGINT) != 0 || sigaddset(&sset, SIGTERM) != 0)
169	{
170		strerror_r(errno, strbuf, sizeof(strbuf));
171		isc_error_fatal(__FILE__, __LINE__,
172				"isc_app_start() sigsetops: %s", strbuf);
173	}
174	presult = pthread_sigmask(SIG_BLOCK, &sset, NULL);
175	if (presult != 0) {
176		strerror_r(presult, strbuf, sizeof(strbuf));
177		isc_error_fatal(__FILE__, __LINE__,
178				"isc_app_start() pthread_sigmask: %s", strbuf);
179	}
180
181#endif /* WIN32 */
182
183	return (ISC_R_SUCCESS);
184}
185
186isc_result_t
187isc_app_start(void) {
188	isc_g_appctx.magic = APPCTX_MAGIC;
189	isc_g_appctx.mctx = NULL;
190	/* The remaining members will be initialized in ctxstart() */
191
192	return (isc_app_ctxstart(&isc_g_appctx));
193}
194
195isc_result_t
196isc_app_onrun(isc_mem_t *mctx, isc_task_t *task, isc_taskaction_t action,
197	      void *arg) {
198	return (isc_app_ctxonrun(&isc_g_appctx, mctx, task, action, arg));
199}
200
201isc_result_t
202isc_app_ctxonrun(isc_appctx_t *ctx, isc_mem_t *mctx, isc_task_t *task,
203		 isc_taskaction_t action, void *arg) {
204	isc_event_t *event;
205	isc_task_t *cloned_task = NULL;
206
207	if (atomic_load_acquire(&ctx->running)) {
208		return (ISC_R_ALREADYRUNNING);
209	}
210
211	/*
212	 * Note that we store the task to which we're going to send the event
213	 * in the event's "sender" field.
214	 */
215	isc_task_attach(task, &cloned_task);
216	event = isc_event_allocate(mctx, cloned_task, ISC_APPEVENT_SHUTDOWN,
217				   action, arg, sizeof(*event));
218
219	LOCK(&ctx->lock);
220	ISC_LINK_INIT(event, ev_link);
221	ISC_LIST_APPEND(ctx->on_run, event, ev_link);
222	UNLOCK(&ctx->lock);
223
224	return (ISC_R_SUCCESS);
225}
226
227isc_result_t
228isc_app_ctxrun(isc_appctx_t *ctx) {
229	isc_event_t *event, *next_event;
230	isc_task_t *task;
231
232	REQUIRE(VALID_APPCTX(ctx));
233
234#ifdef WIN32
235	REQUIRE(main_thread == GetCurrentThread());
236#endif /* ifdef WIN32 */
237
238	if (atomic_compare_exchange_strong_acq_rel(
239		    &ctx->running, &(bool){ false }, true) == true)
240	{
241		/*
242		 * Post any on-run events (in FIFO order).
243		 */
244		LOCK(&ctx->lock);
245		for (event = ISC_LIST_HEAD(ctx->on_run); event != NULL;
246		     event = next_event) {
247			next_event = ISC_LIST_NEXT(event, ev_link);
248			ISC_LIST_UNLINK(ctx->on_run, event, ev_link);
249			task = event->ev_sender;
250			event->ev_sender = NULL;
251			isc_task_sendanddetach(&task, &event);
252		}
253		UNLOCK(&ctx->lock);
254	}
255
256#ifndef WIN32
257	/*
258	 * BIND9 internal tools using multiple contexts do not
259	 * rely on signal. */
260	if (isc_bind9 && ctx != &isc_g_appctx) {
261		return (ISC_R_SUCCESS);
262	}
263#endif /* WIN32 */
264
265	/*
266	 * There is no danger if isc_app_shutdown() is called before we
267	 * wait for signals.  Signals are blocked, so any such signal will
268	 * simply be made pending and we will get it when we call
269	 * sigwait().
270	 */
271	while (atomic_load_acquire(&ctx->want_shutdown) == false) {
272#ifdef WIN32
273		DWORD dwWaitResult = WaitForMultipleObjects(
274			NUM_EVENTS, ctx->hEvents, FALSE, INFINITE);
275
276		/* See why we returned */
277
278		if (WaitSucceeded(dwWaitResult, NUM_EVENTS)) {
279			/*
280			 * The return was due to one of the events
281			 * being signaled
282			 */
283			switch (WaitSucceededIndex(dwWaitResult)) {
284			case RELOAD_EVENT:
285				atomic_store_release(&ctx->want_reload, true);
286
287				break;
288
289			case SHUTDOWN_EVENT:
290				atomic_store_release(&ctx->want_shutdown, true);
291				break;
292			}
293		}
294#else  /* WIN32 */
295		if (isc_bind9) {
296			sigset_t sset;
297			int sig;
298			/*
299			 * BIND9 internal; single context:
300			 * Wait for SIGHUP, SIGINT, or SIGTERM.
301			 */
302			if (sigemptyset(&sset) != 0 ||
303			    sigaddset(&sset, SIGHUP) != 0 ||
304			    sigaddset(&sset, SIGINT) != 0 ||
305			    sigaddset(&sset, SIGTERM) != 0)
306			{
307				char strbuf[ISC_STRERRORSIZE];
308				strerror_r(errno, strbuf, sizeof(strbuf));
309				isc_error_fatal(__FILE__, __LINE__,
310						"isc_app_run() sigsetops: %s",
311						strbuf);
312			}
313
314			if (sigwait(&sset, &sig) == 0) {
315				switch (sig) {
316				case SIGINT:
317				case SIGTERM:
318					atomic_store_release(
319						&ctx->want_shutdown, true);
320					break;
321				case SIGHUP:
322					atomic_store_release(&ctx->want_reload,
323							     true);
324					break;
325				default:
326					INSIST(0);
327					ISC_UNREACHABLE();
328				}
329			}
330		} else {
331			/*
332			 * External, or BIND9 using multiple contexts:
333			 * wait until woken up.
334			 */
335			if (atomic_load_acquire(&ctx->want_shutdown)) {
336				break;
337			}
338			if (!atomic_load_acquire(&ctx->want_reload)) {
339				LOCK(&ctx->readylock);
340				WAIT(&ctx->ready, &ctx->readylock);
341				UNLOCK(&ctx->readylock);
342			}
343		}
344#endif /* WIN32 */
345		if (atomic_compare_exchange_strong_acq_rel(
346			    &ctx->want_reload, &(bool){ true }, false))
347		{
348			return (ISC_R_RELOAD);
349		}
350
351		if (atomic_load_acquire(&ctx->want_shutdown) &&
352		    atomic_load_acquire(&ctx->blocked))
353		{
354			exit(1);
355		}
356	}
357
358	return (ISC_R_SUCCESS);
359}
360
361isc_result_t
362isc_app_run(void) {
363	isc_result_t result;
364
365	REQUIRE(atomic_compare_exchange_strong_acq_rel(
366			&is_running, &(bool){ false }, true) == true);
367	result = isc_app_ctxrun(&isc_g_appctx);
368	atomic_store_release(&is_running, false);
369
370	return (result);
371}
372
373bool
374isc_app_isrunning() {
375	return (atomic_load_acquire(&is_running));
376}
377
378void
379isc_app_ctxshutdown(isc_appctx_t *ctx) {
380	REQUIRE(VALID_APPCTX(ctx));
381
382	REQUIRE(atomic_load_acquire(&ctx->running));
383
384	/* If ctx->shutdown_requested == true, we are already shutting
385	 * down and we want to just bail out.
386	 */
387	if (atomic_compare_exchange_strong_acq_rel(&ctx->shutdown_requested,
388						   &(bool){ false }, true))
389	{
390#ifdef WIN32
391		SetEvent(ctx->hEvents[SHUTDOWN_EVENT]);
392#else  /* WIN32 */
393		if (isc_bind9 && ctx != &isc_g_appctx) {
394			/* BIND9 internal, but using multiple contexts */
395			atomic_store_release(&ctx->want_shutdown, true);
396		} else if (isc_bind9) {
397			/* BIND9 internal, single context */
398			if (kill(getpid(), SIGTERM) < 0) {
399				char strbuf[ISC_STRERRORSIZE];
400				strerror_r(errno, strbuf, sizeof(strbuf));
401				isc_error_fatal(__FILE__, __LINE__,
402						"isc_app_shutdown() "
403						"kill: %s",
404						strbuf);
405			}
406		} else {
407			/* External, multiple contexts */
408			atomic_store_release(&ctx->want_shutdown, true);
409			SIGNAL(&ctx->ready);
410		}
411#endif /* WIN32 */
412	}
413}
414
415void
416isc_app_shutdown(void) {
417	isc_app_ctxshutdown(&isc_g_appctx);
418}
419
420void
421isc_app_ctxsuspend(isc_appctx_t *ctx) {
422	REQUIRE(VALID_APPCTX(ctx));
423
424	REQUIRE(atomic_load(&ctx->running));
425
426	/*
427	 * Don't send the reload signal if we're shutting down.
428	 */
429	if (atomic_load_acquire(&ctx->shutdown_requested) == false) {
430#ifdef WIN32
431		SetEvent(ctx->hEvents[RELOAD_EVENT]);
432#else  /* WIN32 */
433		if (isc_bind9 && ctx != &isc_g_appctx) {
434			/* BIND9 internal, but using multiple contexts */
435			atomic_store_release(&ctx->want_reload, true);
436		} else if (isc_bind9) {
437			/* BIND9 internal, single context */
438			if (kill(getpid(), SIGHUP) < 0) {
439				char strbuf[ISC_STRERRORSIZE];
440				strerror_r(errno, strbuf, sizeof(strbuf));
441				isc_error_fatal(__FILE__, __LINE__,
442						"isc_app_reload() "
443						"kill: %s",
444						strbuf);
445			}
446		} else {
447			/* External, multiple contexts */
448			atomic_store_release(&ctx->want_reload, true);
449			SIGNAL(&ctx->ready);
450		}
451#endif /* WIN32 */
452	}
453}
454
455void
456isc_app_reload(void) {
457	isc_app_ctxsuspend(&isc_g_appctx);
458}
459
460void
461isc_app_ctxfinish(isc_appctx_t *ctx) {
462	REQUIRE(VALID_APPCTX(ctx));
463
464	isc_mutex_destroy(&ctx->lock);
465#ifndef WIN32
466	isc_mutex_destroy(&ctx->readylock);
467	isc_condition_destroy(&ctx->ready);
468#endif /* WIN32 */
469}
470
471void
472isc_app_finish(void) {
473	isc_app_ctxfinish(&isc_g_appctx);
474}
475
476void
477isc_app_block(void) {
478	REQUIRE(atomic_load_acquire(&isc_g_appctx.running));
479	REQUIRE(atomic_compare_exchange_strong_acq_rel(&isc_g_appctx.blocked,
480						       &(bool){ false }, true));
481
482#ifdef WIN32
483	blockedthread = GetCurrentThread();
484#else  /* WIN32 */
485	sigset_t sset;
486	blockedthread = pthread_self();
487	RUNTIME_CHECK(sigemptyset(&sset) == 0 &&
488		      sigaddset(&sset, SIGINT) == 0 &&
489		      sigaddset(&sset, SIGTERM) == 0);
490	RUNTIME_CHECK(pthread_sigmask(SIG_UNBLOCK, &sset, NULL) == 0);
491#endif /* WIN32 */
492}
493
494void
495isc_app_unblock(void) {
496	REQUIRE(atomic_load_acquire(&isc_g_appctx.running));
497	REQUIRE(atomic_compare_exchange_strong_acq_rel(&isc_g_appctx.blocked,
498						       &(bool){ true }, false));
499
500#ifdef WIN32
501	REQUIRE(blockedthread == GetCurrentThread());
502#else  /* WIN32 */
503	REQUIRE(blockedthread == pthread_self());
504
505	sigset_t sset;
506	RUNTIME_CHECK(sigemptyset(&sset) == 0 &&
507		      sigaddset(&sset, SIGINT) == 0 &&
508		      sigaddset(&sset, SIGTERM) == 0);
509	RUNTIME_CHECK(pthread_sigmask(SIG_BLOCK, &sset, NULL) == 0);
510#endif /* WIN32 */
511}
512
513isc_result_t
514isc_appctx_create(isc_mem_t *mctx, isc_appctx_t **ctxp) {
515	isc_appctx_t *ctx;
516
517	REQUIRE(mctx != NULL);
518	REQUIRE(ctxp != NULL && *ctxp == NULL);
519
520	ctx = isc_mem_get(mctx, sizeof(*ctx));
521
522	ctx->magic = APPCTX_MAGIC;
523
524	ctx->mctx = NULL;
525	isc_mem_attach(mctx, &ctx->mctx);
526
527	*ctxp = ctx;
528
529	return (ISC_R_SUCCESS);
530}
531
532void
533isc_appctx_destroy(isc_appctx_t **ctxp) {
534	isc_appctx_t *ctx;
535
536	REQUIRE(ctxp != NULL);
537	ctx = *ctxp;
538	*ctxp = NULL;
539	REQUIRE(VALID_APPCTX(ctx));
540
541	ctx->magic = 0;
542
543	isc_mem_putanddetach(&ctx->mctx, ctx, sizeof(*ctx));
544}
545