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