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