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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2003 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * syseventconfd - The sysevent conf daemon 31 * 32 * This daemon is a companion to the sysevent_conf_mod module. 33 * 34 * The sysevent_conf_mod module receives events from syseventd, 35 * and compares those events against event specs in the 36 * sysevent.conf files. For each matching event spec, the 37 * specified command is invoked. 38 * 39 * This daemon manages the fork/exec's on behalf of sysevent_conf_mod. 40 * The events and associated nvlist are delivered via a door upcall 41 * from sysevent_conf_mod. Arriving events are queued, and the 42 * main thread of this daemon dequeues events one by one, and 43 * builds the necessary arguments to fork/exec the command. 44 * 45 * Since sysevent_conf_mod is running in the context of syseventd, 46 * invoking the fork/exec from that module blocks the door upcalls 47 * from the kernel delivering events to syseventd. We avoid a 48 * major performance bottleneck in this fashion. 49 */ 50 51 #include <stdio.h> 52 #include <stdarg.h> 53 #include <stddef.h> 54 #include <stdlib.h> 55 #include <errno.h> 56 #include <fcntl.h> 57 #include <signal.h> 58 #include <strings.h> 59 #include <unistd.h> 60 #include <synch.h> 61 #include <syslog.h> 62 #include <pthread.h> 63 #include <door.h> 64 #include <libsysevent.h> 65 #include <limits.h> 66 #include <locale.h> 67 #include <sys/modctl.h> 68 #include <sys/stat.h> 69 #include <sys/systeminfo.h> 70 #include <sys/wait.h> 71 72 #include "syseventconfd.h" 73 #include "syseventconfd_door.h" 74 #include "message_confd.h" 75 76 77 78 static int debug_level = 0; 79 static char *root_dir = ""; /* Relative root for lock and door */ 80 static char *prog; 81 82 static struct cmd *cmd_list; 83 static struct cmd *cmd_tail; 84 85 static mutex_t cmd_list_lock; 86 static cond_t cmd_list_cv; 87 88 extern char *optarg; 89 90 /* 91 * Support for door server thread handling 92 */ 93 #define MAX_SERVER_THREADS 1 94 95 static mutex_t create_cnt_lock; 96 static int cnt_servers = 0; 97 98 99 static void 100 usage() { 101 (void) fprintf(stderr, "usage: syseventconfd [-d <debug_level>]\n"); 102 exit(2); 103 } 104 105 106 static void 107 set_root_dir(char *dir) 108 { 109 root_dir = malloc(strlen(dir) + 1); 110 if (root_dir == NULL) { 111 syserrmsg(INIT_ROOT_DIR_ERR, strerror(errno)); 112 exit(2); 113 } 114 (void) strcpy(root_dir, dir); 115 } 116 117 118 void 119 main(int argc, char **argv) 120 { 121 int c; 122 int fd; 123 sigset_t set; 124 struct cmd *cmd; 125 126 (void) setlocale(LC_ALL, ""); 127 (void) textdomain(TEXT_DOMAIN); 128 129 if (getuid() != 0) { 130 (void) fprintf(stderr, "Must be root to run syseventconfd\n"); 131 exit(1); 132 } 133 134 if ((prog = strrchr(argv[0], '/')) == NULL) { 135 prog = argv[0]; 136 } else { 137 prog++; 138 } 139 140 if ((c = getopt(argc, argv, "d:r:")) != EOF) { 141 switch (c) { 142 case 'd': 143 debug_level = atoi(optarg); 144 break; 145 case 'r': 146 /* 147 * Private flag for suninstall to run 148 * daemon during install. 149 */ 150 set_root_dir(optarg); 151 break; 152 case '?': 153 default: 154 usage(); 155 } 156 } 157 158 159 if (fork()) { 160 exit(0); 161 } 162 163 (void) chdir("/"); 164 165 (void) setsid(); 166 if (debug_level <= 1) { 167 closefrom(0); 168 fd = open("/dev/null", 0); 169 (void) dup2(fd, 1); 170 (void) dup2(fd, 2); 171 } 172 173 openlog("syseventconfd", LOG_PID, LOG_DAEMON); 174 175 printmsg(1, 176 "syseventconfd started, debug level = %d\n", debug_level); 177 178 /* 179 * Block all signals to all threads include the main thread. 180 * The sigwait_thr thread will catch and process all signals. 181 */ 182 (void) sigfillset(&set); 183 (void) thr_sigsetmask(SIG_BLOCK, &set, NULL); 184 185 /* Create signal catching thread */ 186 if (thr_create(NULL, NULL, (void *(*)(void *))sigwait_thr, 187 (void *)NULL, 0, NULL) < 0) { 188 syserrmsg(INIT_THR_CREATE_ERR, strerror(errno)); 189 exit(2); 190 } 191 192 /* 193 * Init mutex and list of cmds to be fork/exec'ed 194 * This is multi-threaded so the fork/exec can be 195 * done without blocking the door upcall. 196 */ 197 cmd_list = NULL; 198 cmd_tail = NULL; 199 200 (void) mutex_init(&create_cnt_lock, USYNC_THREAD, NULL); 201 (void) mutex_init(&cmd_list_lock, USYNC_THREAD, NULL); 202 (void) cond_init(&cmd_list_cv, USYNC_THREAD, NULL); 203 204 /* 205 * Open communication channel from sysevent_conf_mod 206 */ 207 if (open_channel() == NULL) { 208 exit(1); 209 } 210 211 /* 212 * main thread to wait for events to arrive and be placed 213 * on the queue. As events are queued, dequeue them 214 * here and invoke the associated fork/exec. 215 */ 216 (void) mutex_lock(&cmd_list_lock); 217 for (;;) { 218 while (cmd_list == NULL) 219 (void) cond_wait(&cmd_list_cv, &cmd_list_lock); 220 221 cmd = cmd_list; 222 cmd_list = cmd->cmd_next; 223 if (cmd_list == NULL) 224 cmd_tail = NULL; 225 226 (void) mutex_unlock(&cmd_list_lock); 227 exec_cmd(cmd); 228 free_cmd(cmd); 229 (void) mutex_lock(&cmd_list_lock); 230 } 231 /* NOTREACHED */ 232 } 233 234 /* 235 * Events sent via the door call from sysevent_conf_mod arrive 236 * here. Queue each event for the main thread to invoke, and 237 * return. We want to avoid doing the fork/exec while in the 238 * context of the door call. 239 */ 240 /*ARGSUSED*/ 241 static void 242 event_handler(sysevent_t *event) 243 { 244 nvlist_t *nvlist; 245 struct cmd *cmd; 246 247 nvlist = NULL; 248 if (sysevent_get_attr_list(event, &nvlist) != 0) { 249 syslog(LOG_ERR, NO_NVLIST_ERR); 250 return; 251 } 252 253 if ((cmd = alloc_cmd(nvlist)) != NULL) { 254 (void) mutex_lock(&cmd_list_lock); 255 if (cmd_list == NULL) { 256 cmd_list = cmd; 257 cmd_tail = cmd; 258 } else { 259 cmd_tail->cmd_next = cmd; 260 cmd_tail = cmd; 261 } 262 cmd->cmd_next = NULL; 263 (void) cond_signal(&cmd_list_cv); 264 (void) mutex_unlock(&cmd_list_lock); 265 } 266 267 nvlist_free(nvlist); 268 } 269 270 271 /* 272 * Decode the command, build the exec args and fork/exec the command 273 * All command attributes are packed into the nvlist bundled with 274 * the delivered event. 275 */ 276 static void 277 exec_cmd(struct cmd *cmd) 278 { 279 char *path; 280 char *cmdline; 281 uid_t uid; 282 gid_t gid; 283 char *file; 284 int line; 285 char *user; 286 arg_t *args; 287 pid_t pid; 288 char *lp; 289 char *p; 290 int i; 291 sigset_t set, prior_set; 292 293 if (nvlist_lookup_string(cmd->cmd_nvlist, "user", &user) != 0) { 294 syslog(LOG_ERR, NVLIST_FORMAT_ERR, "user"); 295 return; 296 } 297 if (nvlist_lookup_string(cmd->cmd_nvlist, "file", &file) != 0) { 298 syslog(LOG_ERR, NVLIST_FORMAT_ERR, "file"); 299 return; 300 } 301 302 if (nvlist_lookup_string(cmd->cmd_nvlist, "path", &path) != 0) { 303 syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "path"); 304 return; 305 } 306 if (nvlist_lookup_string(cmd->cmd_nvlist, "cmd", &cmdline) != 0) { 307 syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "cmd"); 308 return; 309 } 310 if (nvlist_lookup_int32(cmd->cmd_nvlist, "line", &line) != 0) { 311 syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "line"); 312 return; 313 } 314 if (nvlist_lookup_int32(cmd->cmd_nvlist, "uid", (int *)&uid) == 0) { 315 if (nvlist_lookup_int32(cmd->cmd_nvlist, 316 "gid", (int *)&gid) != 0) { 317 syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "gid"); 318 return; 319 } 320 } else { 321 uid = 0; 322 gid = 0; 323 } 324 325 args = init_arglist(32); 326 327 lp = cmdline; 328 while ((p = next_arg(&lp)) != NULL) { 329 if (add_arg(args, p)) { 330 free_arglist(args); 331 return; 332 } 333 } 334 335 if (debug_level >= DBG_EXEC) { 336 printmsg(DBG_EXEC, "path=%s\n", path); 337 printmsg(DBG_EXEC, "cmd=%s\n", cmdline); 338 } 339 340 if (debug_level >= DBG_EXEC_ARGS) { 341 for (i = 0; i < args->arg_nargs; i++) { 342 printmsg(DBG_EXEC_ARGS, 343 "arg[%d]: '%s'\n", i, args->arg_args[i]); 344 } 345 } 346 347 (void) sigprocmask(SIG_SETMASK, NULL, &set); 348 (void) sigaddset(&set, SIGCHLD); 349 (void) sigprocmask(SIG_SETMASK, &set, &prior_set); 350 351 again: 352 if ((pid = fork1()) == (pid_t)-1) { 353 if (errno == EINTR) 354 goto again; 355 syslog(LOG_ERR, CANNOT_FORK_ERR, strerror(errno)); 356 free_arglist(args); 357 return; 358 } 359 if (pid != (pid_t)0) { 360 (void) sigprocmask(SIG_SETMASK, &prior_set, NULL); 361 free_arglist(args); 362 return; 363 } 364 365 /* 366 * The child 367 */ 368 (void) close(0); 369 (void) close(1); 370 (void) close(2); 371 (void) open("/dev/null", O_RDONLY); 372 (void) dup2(0, 1); 373 (void) dup2(0, 2); 374 375 if (uid != (uid_t)0) { 376 i = setgid(gid); 377 if (i == 0) 378 i = setuid(uid); 379 if (i != 0) { 380 syslog(LOG_ERR, SETUID_ERR, 381 file, line, user, strerror(errno)); 382 _exit(0); 383 } 384 } 385 386 /* 387 * Unblock all signals in the child 388 */ 389 (void) sigprocmask(SIG_UNBLOCK, &prior_set, NULL); 390 391 if (execv(path, args->arg_args) == -1) { 392 syslog(LOG_ERR, CANNOT_EXEC_ERR, 393 path, strerror(errno)); 394 _exit(0); 395 } 396 } 397 398 399 /* 400 * Thread to handle in-coming signals 401 */ 402 static void 403 sigwait_thr() 404 { 405 int sig; 406 sigset_t signal_set; 407 408 /* 409 * SIGCHLD is ignored by default, and we need to handle this 410 * signal to reap the status of all children spawned by 411 * this daemon. 412 */ 413 (void) sigset(SIGCHLD, reapchild); 414 415 for (;;) { 416 (void) sigfillset(&signal_set); 417 if (sigwait(&signal_set, &sig) == 0) { 418 /* 419 * Block all signals until the signal handler completes 420 */ 421 (void) sigfillset(&signal_set); 422 (void) thr_sigsetmask(SIG_BLOCK, &signal_set, NULL); 423 424 if (sig == SIGCHLD) { 425 reapchild(sig); 426 } else { 427 flt_handler(sig); 428 } 429 } 430 } 431 /* NOTREACHED */ 432 } 433 434 435 436 /* 437 * reapchild - reap the status of each child as it exits 438 */ 439 /*ARGSUSED*/ 440 static void 441 reapchild(int sig) 442 { 443 siginfo_t info; 444 char *signam; 445 int err; 446 447 for (;;) { 448 (void) memset(&info, 0, sizeof (info)); 449 err = waitid(P_ALL, 0, &info, WNOHANG|WEXITED); 450 if (err == -1) { 451 if (errno != EINTR && errno != EAGAIN) 452 return; 453 } else if (info.si_pid == 0) { 454 return; 455 } 456 457 if (debug_level >= DBG_CHILD) { 458 printmsg(DBG_CHILD, CHILD_EXIT_STATUS_ERR, 459 info.si_pid, info.si_status); 460 } 461 462 if (info.si_status) { 463 if (info.si_code == CLD_EXITED) { 464 syserrmsg(CHILD_EXIT_STATUS_ERR, 465 info.si_pid, info.si_status); 466 } else { 467 signam = strsignal(info.si_status); 468 if (signam == NULL) 469 signam = ""; 470 if (info.si_code == CLD_DUMPED) { 471 syserrmsg( 472 CHILD_EXIT_CORE_ERR, 473 info.si_pid, signam); 474 } else { 475 syserrmsg( 476 CHILD_EXIT_SIGNAL_ERR, 477 info.si_pid, signam); 478 } 479 } 480 } 481 } 482 } 483 484 485 /* 486 * Fault handler for other signals caught 487 */ 488 /*ARGSUSED*/ 489 static void 490 flt_handler(int sig) 491 { 492 struct sigaction act; 493 494 (void) memset(&act, 0, sizeof (act)); 495 act.sa_handler = SIG_DFL; 496 act.sa_flags = SA_RESTART; 497 (void) sigfillset(&act.sa_mask); 498 (void) sigaction(sig, &act, NULL); 499 500 switch (sig) { 501 case SIGINT: 502 case SIGSTOP: 503 case SIGTERM: 504 case SIGHUP: 505 exit(1); 506 /*NOTREACHED*/ 507 } 508 } 509 510 511 static arg_t * 512 init_arglist(int hint) 513 { 514 arg_t *arglist; 515 516 if ((arglist = sc_malloc(sizeof (arg_t))) == NULL) 517 return (NULL); 518 arglist->arg_args = NULL; 519 arglist->arg_nargs = 0; 520 arglist->arg_alloc = 0; 521 arglist->arg_hint = hint; 522 return (arglist); 523 } 524 525 526 static void 527 free_arglist(arg_t *arglist) 528 { 529 if (arglist->arg_args) { 530 free(arglist->arg_args); 531 } 532 free(arglist); 533 } 534 535 536 static int 537 add_arg(arg_t *arglist, char *arg) 538 { 539 char **new_args; 540 int len; 541 542 len = arglist->arg_nargs + 2; 543 if (arglist->arg_alloc < len) { 544 arglist->arg_alloc = len + arglist->arg_hint; 545 new_args = (arglist->arg_nargs == 0) ? 546 sc_malloc(arglist->arg_alloc * sizeof (char **)) : 547 sc_realloc(arglist->arg_args, 548 arglist->arg_alloc * sizeof (char **)); 549 if (new_args == NULL) 550 return (1); 551 arglist->arg_args = new_args; 552 } 553 554 arglist->arg_args[arglist->arg_nargs++] = arg; 555 arglist->arg_args[arglist->arg_nargs] = NULL; 556 557 return (0); 558 } 559 560 /* 561 * next_arg() is used to break up a command line 562 * into the arguments for execv(2). Break up 563 * arguments separated by spaces, but respecting 564 * single/double quotes. 565 */ 566 static char * 567 next_arg(char **cpp) 568 { 569 char *cp = *cpp; 570 char *start; 571 char quote; 572 573 while (*cp == ' ' || *cp == '\t') 574 cp++; 575 if (*cp == 0) { 576 *cpp = 0; 577 return (NULL); 578 } 579 start = cp; 580 while (*cp && *cp != ' ' && *cp != '\t') { 581 if (*cp == '"' || *cp == '\'') { 582 quote = *cp++; 583 while (*cp && *cp != quote) { 584 cp++; 585 } 586 if (*cp == 0) { 587 *cpp = 0; 588 return (NULL); 589 } else { 590 cp++; 591 } 592 } else { 593 cp++; 594 } 595 } 596 if (*cp != 0) 597 *cp++ = 0; 598 *cpp = cp; 599 return (start); 600 } 601 602 603 static struct cmd * 604 alloc_cmd(nvlist_t *nvlist) 605 { 606 struct cmd *cmd; 607 608 cmd = sc_malloc(sizeof (struct cmd)); 609 if (cmd) { 610 if (nvlist_dup(nvlist, &cmd->cmd_nvlist, 0) != 0) { 611 syslog(LOG_ERR, OUT_OF_MEMORY_ERR); 612 free(cmd); 613 return (NULL); 614 } 615 } 616 return (cmd); 617 } 618 619 static void 620 free_cmd(struct cmd *cmd) 621 { 622 nvlist_free(cmd->cmd_nvlist); 623 free(cmd); 624 } 625 626 627 static void * 628 sc_malloc(size_t n) 629 { 630 void *p; 631 632 p = malloc(n); 633 if (p == NULL) { 634 syslog(LOG_ERR, OUT_OF_MEMORY_ERR); 635 } 636 return (p); 637 } 638 639 static void * 640 sc_realloc(void *p, size_t n) 641 { 642 p = realloc(p, n); 643 if (p == NULL) { 644 syslog(LOG_ERR, OUT_OF_MEMORY_ERR); 645 } 646 return (p); 647 } 648 649 650 651 /* 652 * syserrsg - print error messages to the terminal if not 653 * yet daemonized or to syslog. 654 */ 655 /*PRINTFLIKE1*/ 656 static void 657 syserrmsg(char *message, ...) 658 { 659 va_list ap; 660 661 va_start(ap, message); 662 (void) vsyslog(LOG_ERR, message, ap); 663 va_end(ap); 664 } 665 666 /* 667 * printmsg - print messages to the terminal or to syslog 668 * the following levels are implemented: 669 */ 670 /*PRINTFLIKE2*/ 671 static void 672 printmsg(int level, char *message, ...) 673 { 674 va_list ap; 675 676 if (level > debug_level) { 677 return; 678 } 679 680 va_start(ap, message); 681 (void) syslog(LOG_DEBUG, "%s[%ld]: ", prog, getpid()); 682 (void) vsyslog(LOG_DEBUG, message, ap); 683 va_end(ap); 684 } 685 686 /* ARGSUSED */ 687 static void * 688 create_door_thr(void *arg) 689 { 690 (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); 691 (void) door_return(NULL, 0, NULL, 0); 692 return (NULL); 693 } 694 695 /* 696 * Control creation of door server threads 697 * 698 * If first creation of server thread fails there is nothing 699 * we can do about. Doors would not work. 700 */ 701 /* ARGSUSED */ 702 static void 703 mk_thr_pool(door_info_t *dip) 704 { 705 (void) mutex_lock(&create_cnt_lock); 706 if (++cnt_servers > MAX_SERVER_THREADS) { 707 cnt_servers--; 708 (void) mutex_unlock(&create_cnt_lock); 709 return; 710 } 711 (void) mutex_unlock(&create_cnt_lock); 712 713 (void) thr_create(NULL, 0, create_door_thr, NULL, 714 THR_BOUND|THR_DETACHED, NULL); 715 } 716 717 static sysevent_handle_t * 718 open_channel() 719 { 720 char door_path[MAXPATHLEN]; 721 const char *subclass_list; 722 sysevent_handle_t *handle; 723 724 if (snprintf(door_path, sizeof (door_path), "%s/%s", 725 root_dir, SYSEVENTCONFD_SERVICE_DOOR) >= sizeof (door_path)) { 726 syserrmsg(CHANNEL_OPEN_ERR); 727 return (NULL); 728 } 729 730 /* 731 * Setup of door server create function to limit the 732 * amount of door servers 733 */ 734 (void) door_server_create(mk_thr_pool); 735 736 handle = sysevent_open_channel_alt(door_path); 737 if (handle == NULL) { 738 syserrmsg(CHANNEL_OPEN_ERR); 739 return (NULL); 740 } 741 if (sysevent_bind_subscriber(handle, event_handler) != 0) { 742 syserrmsg(CHANNEL_BIND_ERR); 743 sysevent_close_channel(handle); 744 return (NULL); 745 } 746 subclass_list = EC_SUB_ALL; 747 if (sysevent_register_event(handle, EC_ALL, &subclass_list, 1) 748 != 0) { 749 syserrmsg(CHANNEL_BIND_ERR); 750 (void) sysevent_unbind_subscriber(handle); 751 (void) sysevent_close_channel(handle); 752 return (NULL); 753 } 754 return (handle); 755 } 756