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