1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <assert.h> 29 #include <door.h> 30 #include <errno.h> 31 #include <fcntl.h> 32 #include <limits.h> 33 #include <priv.h> 34 #include <procfs.h> 35 #include <pthread.h> 36 #include <signal.h> 37 #include <stdarg.h> 38 #include <stdio.h> 39 #include <stdio_ext.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <syslog.h> 43 #include <sys/corectl.h> 44 #include <sys/resource.h> 45 #include <sys/stat.h> 46 #include <sys/wait.h> 47 #include <ucontext.h> 48 #include <unistd.h> 49 50 #include "configd.h" 51 52 /* 53 * This file manages the overall startup and shutdown of configd, as well 54 * as managing its door thread pool and per-thread datastructures. 55 * 56 * 1. Per-thread Datastructures 57 * ----------------------------- 58 * Each configd thread has an associated thread_info_t which contains its 59 * current state. A pointer is kept to this in TSD, keyed by thread_info_key. 60 * The thread_info_ts for all threads in configd are kept on a single global 61 * list, thread_list. After creation, the state in the thread_info structure 62 * is only modified by the associated thread, so no locking is needed. A TSD 63 * destructor removes the thread_info from the global list and frees it at 64 * pthread_exit() time. 65 * 66 * Threads access their per-thread data using thread_self() 67 * 68 * The thread_list is protected by thread_lock, a leaf lock. 69 * 70 * 2. Door Thread Pool Management 71 * ------------------------------ 72 * Whenever door_return(3door) returns from the kernel and there are no 73 * other configd threads waiting for requests, libdoor automatically 74 * invokes a function registered with door_server_create(), to request a new 75 * door server thread. The default function just creates a thread that calls 76 * door_return(3door). Unfortunately, since it can take a while for the new 77 * thread to *get* to door_return(3door), a stream of requests can cause a 78 * large number of threads to be created, even though they aren't all needed. 79 * 80 * In our callback, new_server_needed(), we limit ourself to two new threads 81 * at a time -- this logic is handled in reserve_new_thread(). This keeps 82 * us from creating an absurd number of threads in response to peaking load. 83 */ 84 static pthread_key_t thread_info_key; 85 static pthread_attr_t thread_attr; 86 87 static pthread_mutex_t thread_lock = PTHREAD_MUTEX_INITIALIZER; 88 int num_started; /* number actually running */ 89 int num_servers; /* number in-progress or running */ 90 static uu_list_pool_t *thread_pool; 91 uu_list_t *thread_list; 92 93 static thread_info_t main_thread_info; 94 95 static int finished; 96 97 static pid_t privileged_pid = 0; 98 static int privileged_psinfo_fd = -1; 99 100 static int privileged_user = 0; 101 102 static priv_set_t *privileged_privs; 103 104 static int log_to_syslog = 0; 105 106 int is_main_repository = 1; 107 108 int max_repository_backups = 4; 109 110 #define CONFIGD_MAX_FDS 262144 111 112 /* 113 * Thanks, Mike 114 */ 115 void 116 abort_handler(int sig, siginfo_t *sip, ucontext_t *ucp) 117 { 118 struct sigaction act; 119 120 (void) sigemptyset(&act.sa_mask); 121 act.sa_handler = SIG_DFL; 122 act.sa_flags = 0; 123 (void) sigaction(sig, &act, NULL); 124 125 (void) printstack(2); 126 127 if (sip != NULL && SI_FROMUSER(sip)) 128 (void) pthread_kill(pthread_self(), sig); 129 (void) sigfillset(&ucp->uc_sigmask); 130 (void) sigdelset(&ucp->uc_sigmask, sig); 131 ucp->uc_flags |= UC_SIGMASK; 132 (void) setcontext(ucp); 133 } 134 135 /* 136 * Don't want to have more than a couple thread creates outstanding 137 */ 138 static int 139 reserve_new_thread(void) 140 { 141 (void) pthread_mutex_lock(&thread_lock); 142 assert(num_started >= 0); 143 if (num_servers > num_started + 1) { 144 (void) pthread_mutex_unlock(&thread_lock); 145 return (0); 146 } 147 ++num_servers; 148 (void) pthread_mutex_unlock(&thread_lock); 149 return (1); 150 } 151 152 static void 153 thread_info_free(thread_info_t *ti) 154 { 155 uu_list_node_fini(ti, &ti->ti_node, thread_pool); 156 if (ti->ti_ucred != NULL) 157 uu_free(ti->ti_ucred); 158 uu_free(ti); 159 } 160 161 static void 162 thread_exiting(void *arg) 163 { 164 thread_info_t *ti = arg; 165 166 if (ti != NULL) 167 log_enter(&ti->ti_log); 168 169 (void) pthread_mutex_lock(&thread_lock); 170 if (ti != NULL) { 171 num_started--; 172 uu_list_remove(thread_list, ti); 173 } 174 assert(num_servers > 0); 175 --num_servers; 176 177 if (num_servers == 0) { 178 configd_critical("no door server threads\n"); 179 abort(); 180 } 181 (void) pthread_mutex_unlock(&thread_lock); 182 183 if (ti != NULL && ti != &main_thread_info) 184 thread_info_free(ti); 185 } 186 187 void 188 thread_newstate(thread_info_t *ti, thread_state_t newstate) 189 { 190 ti->ti_ucred_read = 0; /* invalidate cached ucred */ 191 if (newstate != ti->ti_state) { 192 ti->ti_prev_state = ti->ti_state; 193 ti->ti_state = newstate; 194 ti->ti_lastchange = gethrtime(); 195 } 196 } 197 198 thread_info_t * 199 thread_self(void) 200 { 201 return (pthread_getspecific(thread_info_key)); 202 } 203 204 /* 205 * get_ucred() returns NULL if it was unable to get the credential 206 * information. 207 */ 208 ucred_t * 209 get_ucred(void) 210 { 211 thread_info_t *ti = thread_self(); 212 ucred_t **ret = &ti->ti_ucred; 213 214 if (ti->ti_ucred_read) 215 return (*ret); /* cached value */ 216 217 if (door_ucred(ret) != 0) 218 return (NULL); 219 ti->ti_ucred_read = 1; 220 221 return (*ret); 222 } 223 224 int 225 ucred_is_privileged(ucred_t *uc) 226 { 227 const priv_set_t *ps; 228 229 if ((ps = ucred_getprivset(uc, PRIV_EFFECTIVE)) != NULL) { 230 if (priv_isfullset(ps)) 231 return (1); /* process has all privs */ 232 233 if (privileged_privs != NULL && 234 priv_issubset(privileged_privs, ps)) 235 return (1); /* process has zone privs */ 236 } 237 238 return (0); 239 } 240 241 /* 242 * The purpose of this function is to get the audit session data for use in 243 * generating SMF audit events. We use a single audit session per client. 244 * 245 * get_audit_session() may return NULL. It is legal to use a NULL pointer 246 * in subsequent calls to adt_* functions. 247 */ 248 adt_session_data_t * 249 get_audit_session(void) 250 { 251 thread_info_t *ti = thread_self(); 252 253 return (ti->ti_active_client->rc_adt_session); 254 } 255 256 static void * 257 thread_start(void *arg) 258 { 259 thread_info_t *ti = arg; 260 261 (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); 262 263 (void) pthread_mutex_lock(&thread_lock); 264 num_started++; 265 (void) uu_list_insert_after(thread_list, uu_list_last(thread_list), 266 ti); 267 (void) pthread_mutex_unlock(&thread_lock); 268 (void) pthread_setspecific(thread_info_key, ti); 269 270 thread_newstate(ti, TI_DOOR_RETURN); 271 272 /* 273 * Start handling door calls 274 */ 275 (void) door_return(NULL, 0, NULL, 0); 276 return (arg); 277 } 278 279 static void 280 new_thread_needed(door_info_t *dip) 281 { 282 thread_info_t *ti; 283 284 sigset_t new, old; 285 286 assert(dip == NULL); 287 288 if (!reserve_new_thread()) 289 return; 290 291 if ((ti = uu_zalloc(sizeof (*ti))) == NULL) 292 goto fail; 293 294 uu_list_node_init(ti, &ti->ti_node, thread_pool); 295 ti->ti_state = TI_CREATED; 296 ti->ti_prev_state = TI_CREATED; 297 298 if ((ti->ti_ucred = uu_zalloc(ucred_size())) == NULL) 299 goto fail; 300 301 (void) sigfillset(&new); 302 (void) pthread_sigmask(SIG_SETMASK, &new, &old); 303 if ((errno = pthread_create(&ti->ti_thread, &thread_attr, thread_start, 304 ti)) != 0) { 305 (void) pthread_sigmask(SIG_SETMASK, &old, NULL); 306 goto fail; 307 } 308 309 (void) pthread_sigmask(SIG_SETMASK, &old, NULL); 310 return; 311 312 fail: 313 /* 314 * Since the thread_info structure was never linked onto the 315 * thread list, thread_exiting() can't handle the cleanup. 316 */ 317 thread_exiting(NULL); 318 if (ti != NULL) 319 thread_info_free(ti); 320 } 321 322 int 323 create_connection(ucred_t *uc, repository_door_request_t *rp, 324 size_t rp_size, int *out_fd) 325 { 326 int flags; 327 int privileged = 0; 328 uint32_t debugflags = 0; 329 psinfo_t info; 330 331 if (privileged_pid != 0) { 332 /* 333 * in privileged pid mode, we only allow connections from 334 * our original parent -- the psinfo read verifies that 335 * it is the same process which we started with. 336 */ 337 if (ucred_getpid(uc) != privileged_pid || 338 read(privileged_psinfo_fd, &info, sizeof (info)) != 339 sizeof (info)) 340 return (REPOSITORY_DOOR_FAIL_PERMISSION_DENIED); 341 342 privileged = 1; /* he gets full privileges */ 343 } else if (privileged_user != 0) { 344 /* 345 * in privileged user mode, only one particular user is 346 * allowed to connect to us, and he can do anything. 347 */ 348 if (ucred_geteuid(uc) != privileged_user) 349 return (REPOSITORY_DOOR_FAIL_PERMISSION_DENIED); 350 351 privileged = 1; 352 } 353 354 /* 355 * Check that rp, of size rp_size, is large enough to 356 * contain field 'f'. If so, write the value into *out, and return 1. 357 * Otherwise, return 0. 358 */ 359 #define GET_ARG(rp, rp_size, f, out) \ 360 (((rp_size) >= offsetofend(repository_door_request_t, f)) ? \ 361 ((*(out) = (rp)->f), 1) : 0) 362 363 if (!GET_ARG(rp, rp_size, rdr_flags, &flags)) 364 return (REPOSITORY_DOOR_FAIL_BAD_REQUEST); 365 366 #if (REPOSITORY_DOOR_FLAG_ALL != REPOSITORY_DOOR_FLAG_DEBUG) 367 #error Need to update flag checks 368 #endif 369 370 if (flags & ~REPOSITORY_DOOR_FLAG_ALL) 371 return (REPOSITORY_DOOR_FAIL_BAD_FLAG); 372 373 if (flags & REPOSITORY_DOOR_FLAG_DEBUG) 374 if (!GET_ARG(rp, rp_size, rdr_debug, &debugflags)) 375 return (REPOSITORY_DOOR_FAIL_BAD_REQUEST); 376 #undef GET_ARG 377 378 return (create_client(ucred_getpid(uc), debugflags, privileged, 379 out_fd)); 380 } 381 382 void 383 configd_vlog(int severity, const char *prefix, const char *message, 384 va_list args) 385 { 386 if (log_to_syslog) 387 vsyslog(severity, message, args); 388 else { 389 flockfile(stderr); 390 if (prefix != NULL) 391 (void) fprintf(stderr, "%s", prefix); 392 (void) vfprintf(stderr, message, args); 393 if (message[0] == 0 || message[strlen(message) - 1] != '\n') 394 (void) fprintf(stderr, "\n"); 395 funlockfile(stderr); 396 } 397 } 398 399 void 400 configd_vcritical(const char *message, va_list args) 401 { 402 configd_vlog(LOG_CRIT, "svc.configd: Fatal error: ", message, args); 403 } 404 405 void 406 configd_critical(const char *message, ...) 407 { 408 va_list args; 409 va_start(args, message); 410 configd_vcritical(message, args); 411 va_end(args); 412 } 413 414 void 415 configd_info(const char *message, ...) 416 { 417 va_list args; 418 va_start(args, message); 419 configd_vlog(LOG_INFO, "svc.configd: ", message, args); 420 va_end(args); 421 } 422 423 static void 424 usage(const char *prog, int ret) 425 { 426 (void) fprintf(stderr, 427 "usage: %s [-np] [-d door_path] [-r repository_path]\n" 428 " [-t nonpersist_repository]\n", prog); 429 exit(ret); 430 } 431 432 /*ARGSUSED*/ 433 static void 434 handler(int sig, siginfo_t *info, void *data) 435 { 436 finished = 1; 437 } 438 439 static int pipe_fd = -1; 440 441 static int 442 daemonize_start(void) 443 { 444 char data; 445 int status; 446 447 int filedes[2]; 448 pid_t pid; 449 450 (void) close(0); 451 (void) dup2(2, 1); /* stderr only */ 452 453 if (pipe(filedes) < 0) 454 return (-1); 455 456 if ((pid = fork1()) < 0) 457 return (-1); 458 459 if (pid != 0) { 460 /* 461 * parent 462 */ 463 struct sigaction act; 464 465 act.sa_sigaction = SIG_DFL; 466 (void) sigemptyset(&act.sa_mask); 467 act.sa_flags = 0; 468 469 (void) sigaction(SIGPIPE, &act, NULL); /* ignore SIGPIPE */ 470 471 (void) close(filedes[1]); 472 if (read(filedes[0], &data, 1) == 1) { 473 /* presume success */ 474 _exit(CONFIGD_EXIT_OKAY); 475 } 476 477 status = -1; 478 (void) wait4(pid, &status, 0, NULL); 479 if (WIFEXITED(status)) 480 _exit(WEXITSTATUS(status)); 481 else 482 _exit(-1); 483 } 484 485 /* 486 * child 487 */ 488 pipe_fd = filedes[1]; 489 (void) close(filedes[0]); 490 491 /* 492 * generic Unix setup 493 */ 494 (void) setsid(); 495 (void) umask(0077); 496 497 return (0); 498 } 499 500 static void 501 daemonize_ready(void) 502 { 503 char data = '\0'; 504 505 /* 506 * wake the parent 507 */ 508 (void) write(pipe_fd, &data, 1); 509 (void) close(pipe_fd); 510 } 511 512 const char * 513 regularize_path(const char *dir, const char *base, char *tmpbuf) 514 { 515 if (base == NULL) 516 return (NULL); 517 if (base[0] == '/') 518 return (base); 519 520 if (snprintf(tmpbuf, PATH_MAX, "%s/%s", dir, base) >= PATH_MAX) { 521 (void) fprintf(stderr, "svc.configd: %s/%s: path too long\n", 522 dir, base); 523 exit(CONFIGD_EXIT_BAD_ARGS); 524 } 525 526 return (tmpbuf); 527 } 528 529 int 530 main(int argc, char *argv[]) 531 { 532 thread_info_t *ti = &main_thread_info; 533 534 char pidpath[sizeof ("/proc/" "/psinfo") + 10]; 535 536 struct rlimit fd_new; 537 538 const char *endptr; 539 sigset_t myset; 540 int c; 541 int ret; 542 int fd; 543 544 char curdir[PATH_MAX]; 545 char dbtmp[PATH_MAX]; 546 char npdbtmp[PATH_MAX]; 547 char doortmp[PATH_MAX]; 548 549 const char *dbpath = NULL; 550 const char *npdbpath = NULL; 551 const char *doorpath = REPOSITORY_DOOR_NAME; 552 struct sigaction act; 553 554 int daemonize = 1; /* default to daemonizing */ 555 int have_npdb = 1; 556 557 closefrom(3); /* get rid of extraneous fds */ 558 559 if (getcwd(curdir, sizeof (curdir)) == NULL) { 560 (void) fprintf(stderr, 561 "%s: unable to get current directory: %s\n", 562 argv[0], strerror(errno)); 563 exit(CONFIGD_EXIT_INIT_FAILED); 564 } 565 566 while ((c = getopt(argc, argv, "Dnpd:r:t:")) != -1) { 567 switch (c) { 568 case 'n': 569 daemonize = 0; 570 break; 571 case 'd': 572 doorpath = regularize_path(curdir, optarg, doortmp); 573 have_npdb = 0; /* default to no non-persist */ 574 break; 575 case 'p': 576 log_to_syslog = 0; /* don't use syslog */ 577 578 /* 579 * If our parent exits while we're opening its /proc 580 * psinfo, we're vulnerable to a pid wrapping. To 581 * protect against that, re-check our ppid after 582 * opening it. 583 */ 584 privileged_pid = getppid(); 585 (void) snprintf(pidpath, sizeof (pidpath), 586 "/proc/%d/psinfo", privileged_pid); 587 if ((fd = open(pidpath, O_RDONLY)) < 0 || 588 getppid() != privileged_pid) { 589 (void) fprintf(stderr, 590 "%s: unable to get parent info\n", argv[0]); 591 exit(CONFIGD_EXIT_BAD_ARGS); 592 } 593 privileged_psinfo_fd = fd; 594 break; 595 case 'r': 596 dbpath = regularize_path(curdir, optarg, dbtmp); 597 is_main_repository = 0; 598 break; 599 case 't': 600 npdbpath = regularize_path(curdir, optarg, npdbtmp); 601 is_main_repository = 0; 602 break; 603 default: 604 usage(argv[0], CONFIGD_EXIT_BAD_ARGS); 605 break; 606 } 607 } 608 609 /* 610 * If we're not running as root, allow our euid full access, and 611 * everyone else no access. 612 */ 613 if (privileged_pid == 0 && geteuid() != 0) { 614 privileged_user = geteuid(); 615 } 616 617 privileged_privs = priv_str_to_set("zone", "", &endptr); 618 if (endptr != NULL && privileged_privs != NULL) { 619 priv_freeset(privileged_privs); 620 privileged_privs = NULL; 621 } 622 623 openlog("svc.configd", LOG_PID | LOG_CONS, LOG_DAEMON); 624 (void) setlogmask(LOG_UPTO(LOG_NOTICE)); 625 626 /* 627 * if a non-persist db is specified, always enable it 628 */ 629 if (npdbpath) 630 have_npdb = 1; 631 632 if (optind != argc) 633 usage(argv[0], CONFIGD_EXIT_BAD_ARGS); 634 635 if (daemonize) { 636 if (getuid() == 0) 637 (void) chdir("/"); 638 if (daemonize_start() < 0) { 639 (void) perror("unable to daemonize"); 640 exit(CONFIGD_EXIT_INIT_FAILED); 641 } 642 } 643 if (getuid() == 0) 644 (void) core_set_process_path(CONFIGD_CORE, 645 strlen(CONFIGD_CORE) + 1, getpid()); 646 647 /* 648 * this should be enabled once we can drop privileges and still get 649 * a core dump. 650 */ 651 #if 0 652 /* turn off basic privileges we do not need */ 653 (void) priv_set(PRIV_OFF, PRIV_PERMITTED, PRIV_FILE_LINK_ANY, 654 PRIV_PROC_EXEC, PRIV_PROC_FORK, PRIV_PROC_SESSION, NULL); 655 #endif 656 657 /* not that we can exec, but to be safe, shut them all off... */ 658 (void) priv_set(PRIV_SET, PRIV_INHERITABLE, NULL); 659 660 (void) sigfillset(&act.sa_mask); 661 662 /* signals to ignore */ 663 act.sa_sigaction = SIG_IGN; 664 act.sa_flags = 0; 665 (void) sigaction(SIGPIPE, &act, NULL); 666 (void) sigaction(SIGALRM, &act, NULL); 667 (void) sigaction(SIGUSR1, &act, NULL); 668 (void) sigaction(SIGUSR2, &act, NULL); 669 (void) sigaction(SIGPOLL, &act, NULL); 670 671 /* signals to abort on */ 672 act.sa_sigaction = (void (*)(int, siginfo_t *, void *))&abort_handler; 673 act.sa_flags = SA_SIGINFO; 674 675 (void) sigaction(SIGABRT, &act, NULL); 676 677 /* signals to handle */ 678 act.sa_sigaction = &handler; 679 act.sa_flags = SA_SIGINFO; 680 681 (void) sigaction(SIGHUP, &act, NULL); 682 (void) sigaction(SIGINT, &act, NULL); 683 (void) sigaction(SIGTERM, &act, NULL); 684 685 (void) sigemptyset(&myset); 686 (void) sigaddset(&myset, SIGHUP); 687 (void) sigaddset(&myset, SIGINT); 688 (void) sigaddset(&myset, SIGTERM); 689 690 if ((errno = pthread_attr_init(&thread_attr)) != 0) { 691 (void) perror("initializing"); 692 exit(CONFIGD_EXIT_INIT_FAILED); 693 } 694 695 /* 696 * Set the hard and soft limits to CONFIGD_MAX_FDS. 697 */ 698 fd_new.rlim_max = fd_new.rlim_cur = CONFIGD_MAX_FDS; 699 (void) setrlimit(RLIMIT_NOFILE, &fd_new); 700 701 #ifndef NATIVE_BUILD /* Allow building on snv_38 and earlier; remove later. */ 702 (void) enable_extended_FILE_stdio(-1, -1); 703 #endif 704 705 if ((ret = backend_init(dbpath, npdbpath, have_npdb)) != 706 CONFIGD_EXIT_OKAY) 707 exit(ret); 708 709 if (!client_init()) 710 exit(CONFIGD_EXIT_INIT_FAILED); 711 712 if (!rc_node_init()) 713 exit(CONFIGD_EXIT_INIT_FAILED); 714 715 (void) pthread_attr_setdetachstate(&thread_attr, 716 PTHREAD_CREATE_DETACHED); 717 (void) pthread_attr_setscope(&thread_attr, PTHREAD_SCOPE_SYSTEM); 718 719 if ((errno = pthread_key_create(&thread_info_key, 720 thread_exiting)) != 0) { 721 perror("pthread_key_create"); 722 exit(CONFIGD_EXIT_INIT_FAILED); 723 } 724 725 if ((thread_pool = uu_list_pool_create("thread_pool", 726 sizeof (thread_info_t), offsetof(thread_info_t, ti_node), 727 NULL, UU_LIST_POOL_DEBUG)) == NULL) { 728 configd_critical("uu_list_pool_create: %s\n", 729 uu_strerror(uu_error())); 730 exit(CONFIGD_EXIT_INIT_FAILED); 731 } 732 733 if ((thread_list = uu_list_create(thread_pool, NULL, 0)) == NULL) { 734 configd_critical("uu_list_create: %s\n", 735 uu_strerror(uu_error())); 736 exit(CONFIGD_EXIT_INIT_FAILED); 737 } 738 739 (void) memset(ti, '\0', sizeof (*ti)); 740 uu_list_node_init(ti, &ti->ti_node, thread_pool); 741 (void) uu_list_insert_before(thread_list, uu_list_first(thread_list), 742 ti); 743 744 ti->ti_thread = pthread_self(); 745 ti->ti_state = TI_SIGNAL_WAIT; 746 ti->ti_prev_state = TI_SIGNAL_WAIT; 747 748 (void) pthread_setspecific(thread_info_key, ti); 749 750 (void) door_server_create(new_thread_needed); 751 752 if (!setup_main_door(doorpath)) { 753 configd_critical("Setting up main door failed.\n"); 754 exit(CONFIGD_EXIT_DOOR_INIT_FAILED); 755 } 756 757 if (daemonize) 758 daemonize_ready(); 759 760 (void) pthread_sigmask(SIG_BLOCK, &myset, NULL); 761 while (!finished) { 762 int sig = sigwait(&myset); 763 if (sig > 0) { 764 break; 765 } 766 } 767 768 backend_fini(); 769 770 return (CONFIGD_EXIT_OKAY); 771 } 772