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 #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 int 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 return (0); 233 } 234 235 /* 236 * Events sent via the door call from sysevent_conf_mod arrive 237 * here. Queue each event for the main thread to invoke, and 238 * return. We want to avoid doing the fork/exec while in the 239 * context of the door call. 240 */ 241 /*ARGSUSED*/ 242 static void 243 event_handler(sysevent_t *event) 244 { 245 nvlist_t *nvlist; 246 struct cmd *cmd; 247 248 nvlist = NULL; 249 if (sysevent_get_attr_list(event, &nvlist) != 0) { 250 syslog(LOG_ERR, NO_NVLIST_ERR); 251 return; 252 } 253 254 if ((cmd = alloc_cmd(nvlist)) != NULL) { 255 (void) mutex_lock(&cmd_list_lock); 256 if (cmd_list == NULL) { 257 cmd_list = cmd; 258 cmd_tail = cmd; 259 } else { 260 cmd_tail->cmd_next = cmd; 261 cmd_tail = cmd; 262 } 263 cmd->cmd_next = NULL; 264 (void) cond_signal(&cmd_list_cv); 265 (void) mutex_unlock(&cmd_list_lock); 266 } 267 268 nvlist_free(nvlist); 269 } 270 271 272 /* 273 * Decode the command, build the exec args and fork/exec the command 274 * All command attributes are packed into the nvlist bundled with 275 * the delivered event. 276 */ 277 static void 278 exec_cmd(struct cmd *cmd) 279 { 280 char *path; 281 char *cmdline; 282 uid_t uid; 283 gid_t gid; 284 char *file; 285 int line; 286 char *user; 287 arg_t *args; 288 pid_t pid; 289 char *lp; 290 char *p; 291 int i; 292 sigset_t set, prior_set; 293 294 if (nvlist_lookup_string(cmd->cmd_nvlist, "user", &user) != 0) { 295 syslog(LOG_ERR, NVLIST_FORMAT_ERR, "user"); 296 return; 297 } 298 if (nvlist_lookup_string(cmd->cmd_nvlist, "file", &file) != 0) { 299 syslog(LOG_ERR, NVLIST_FORMAT_ERR, "file"); 300 return; 301 } 302 303 if (nvlist_lookup_string(cmd->cmd_nvlist, "path", &path) != 0) { 304 syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "path"); 305 return; 306 } 307 if (nvlist_lookup_string(cmd->cmd_nvlist, "cmd", &cmdline) != 0) { 308 syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "cmd"); 309 return; 310 } 311 if (nvlist_lookup_int32(cmd->cmd_nvlist, "line", &line) != 0) { 312 syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "line"); 313 return; 314 } 315 if (nvlist_lookup_int32(cmd->cmd_nvlist, "uid", (int *)&uid) == 0) { 316 if (nvlist_lookup_int32(cmd->cmd_nvlist, 317 "gid", (int *)&gid) != 0) { 318 syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "gid"); 319 return; 320 } 321 } else { 322 uid = 0; 323 gid = 0; 324 } 325 326 args = init_arglist(32); 327 328 lp = cmdline; 329 while ((p = next_arg(&lp)) != NULL) { 330 if (add_arg(args, p)) { 331 free_arglist(args); 332 return; 333 } 334 } 335 336 if (debug_level >= DBG_EXEC) { 337 printmsg(DBG_EXEC, "path=%s\n", path); 338 printmsg(DBG_EXEC, "cmd=%s\n", cmdline); 339 } 340 341 if (debug_level >= DBG_EXEC_ARGS) { 342 for (i = 0; i < args->arg_nargs; i++) { 343 printmsg(DBG_EXEC_ARGS, 344 "arg[%d]: '%s'\n", i, args->arg_args[i]); 345 } 346 } 347 348 (void) sigprocmask(SIG_SETMASK, NULL, &set); 349 (void) sigaddset(&set, SIGCHLD); 350 (void) sigprocmask(SIG_SETMASK, &set, &prior_set); 351 352 again: 353 if ((pid = fork1()) == (pid_t)-1) { 354 if (errno == EINTR) 355 goto again; 356 syslog(LOG_ERR, CANNOT_FORK_ERR, strerror(errno)); 357 free_arglist(args); 358 return; 359 } 360 if (pid != (pid_t)0) { 361 (void) sigprocmask(SIG_SETMASK, &prior_set, NULL); 362 free_arglist(args); 363 return; 364 } 365 366 /* 367 * The child 368 */ 369 (void) close(0); 370 (void) close(1); 371 (void) close(2); 372 (void) open("/dev/null", O_RDONLY); 373 (void) dup2(0, 1); 374 (void) dup2(0, 2); 375 376 if (uid != (uid_t)0) { 377 i = setgid(gid); 378 if (i == 0) 379 i = setuid(uid); 380 if (i != 0) { 381 syslog(LOG_ERR, SETUID_ERR, 382 file, line, user, strerror(errno)); 383 _exit(0); 384 } 385 } 386 387 /* 388 * Unblock all signals in the child 389 */ 390 (void) sigprocmask(SIG_UNBLOCK, &prior_set, NULL); 391 392 if (execv(path, args->arg_args) == -1) { 393 syslog(LOG_ERR, CANNOT_EXEC_ERR, 394 path, strerror(errno)); 395 _exit(0); 396 } 397 } 398 399 400 /* 401 * Thread to handle in-coming signals 402 */ 403 static void 404 sigwait_thr() 405 { 406 int sig; 407 sigset_t signal_set; 408 409 /* 410 * SIGCHLD is ignored by default, and we need to handle this 411 * signal to reap the status of all children spawned by 412 * this daemon. 413 */ 414 (void) sigset(SIGCHLD, reapchild); 415 416 for (;;) { 417 (void) sigfillset(&signal_set); 418 if (sigwait(&signal_set, &sig) == 0) { 419 /* 420 * Block all signals until the signal handler completes 421 */ 422 (void) sigfillset(&signal_set); 423 (void) thr_sigsetmask(SIG_BLOCK, &signal_set, NULL); 424 425 if (sig == SIGCHLD) { 426 reapchild(sig); 427 } else { 428 flt_handler(sig); 429 } 430 } 431 } 432 /* NOTREACHED */ 433 } 434 435 436 437 /* 438 * reapchild - reap the status of each child as it exits 439 */ 440 /*ARGSUSED*/ 441 static void 442 reapchild(int sig) 443 { 444 siginfo_t info; 445 char *signam; 446 int err; 447 448 for (;;) { 449 (void) memset(&info, 0, sizeof (info)); 450 err = waitid(P_ALL, 0, &info, WNOHANG|WEXITED); 451 if (err == -1) { 452 if (errno != EINTR && errno != EAGAIN) 453 return; 454 } else if (info.si_pid == 0) { 455 return; 456 } 457 458 if (debug_level >= DBG_CHILD) { 459 printmsg(DBG_CHILD, CHILD_EXIT_STATUS_ERR, 460 info.si_pid, info.si_status); 461 } 462 463 if (info.si_status) { 464 if (info.si_code == CLD_EXITED) { 465 syserrmsg(CHILD_EXIT_STATUS_ERR, 466 info.si_pid, info.si_status); 467 } else { 468 signam = strsignal(info.si_status); 469 if (signam == NULL) 470 signam = ""; 471 if (info.si_code == CLD_DUMPED) { 472 syserrmsg( 473 CHILD_EXIT_CORE_ERR, 474 info.si_pid, signam); 475 } else { 476 syserrmsg( 477 CHILD_EXIT_SIGNAL_ERR, 478 info.si_pid, signam); 479 } 480 } 481 } 482 } 483 } 484 485 486 /* 487 * Fault handler for other signals caught 488 */ 489 /*ARGSUSED*/ 490 static void 491 flt_handler(int sig) 492 { 493 struct sigaction act; 494 495 (void) memset(&act, 0, sizeof (act)); 496 act.sa_handler = SIG_DFL; 497 act.sa_flags = SA_RESTART; 498 (void) sigfillset(&act.sa_mask); 499 (void) sigaction(sig, &act, NULL); 500 501 switch (sig) { 502 case SIGINT: 503 case SIGSTOP: 504 case SIGTERM: 505 case SIGHUP: 506 exit(1); 507 /*NOTREACHED*/ 508 } 509 } 510 511 512 static arg_t * 513 init_arglist(int hint) 514 { 515 arg_t *arglist; 516 517 if ((arglist = sc_malloc(sizeof (arg_t))) == NULL) 518 return (NULL); 519 arglist->arg_args = NULL; 520 arglist->arg_nargs = 0; 521 arglist->arg_alloc = 0; 522 arglist->arg_hint = hint; 523 return (arglist); 524 } 525 526 527 static void 528 free_arglist(arg_t *arglist) 529 { 530 if (arglist->arg_args) { 531 free(arglist->arg_args); 532 } 533 free(arglist); 534 } 535 536 537 static int 538 add_arg(arg_t *arglist, char *arg) 539 { 540 char **new_args; 541 int len; 542 543 len = arglist->arg_nargs + 2; 544 if (arglist->arg_alloc < len) { 545 arglist->arg_alloc = len + arglist->arg_hint; 546 new_args = (arglist->arg_nargs == 0) ? 547 sc_malloc(arglist->arg_alloc * sizeof (char **)) : 548 sc_realloc(arglist->arg_args, 549 arglist->arg_alloc * sizeof (char **)); 550 if (new_args == NULL) 551 return (1); 552 arglist->arg_args = new_args; 553 } 554 555 arglist->arg_args[arglist->arg_nargs++] = arg; 556 arglist->arg_args[arglist->arg_nargs] = NULL; 557 558 return (0); 559 } 560 561 /* 562 * next_arg() is used to break up a command line 563 * into the arguments for execv(2). Break up 564 * arguments separated by spaces, but respecting 565 * single/double quotes. 566 */ 567 static char * 568 next_arg(char **cpp) 569 { 570 char *cp = *cpp; 571 char *start; 572 char quote; 573 574 while (*cp == ' ' || *cp == '\t') 575 cp++; 576 if (*cp == 0) { 577 *cpp = 0; 578 return (NULL); 579 } 580 start = cp; 581 while (*cp && *cp != ' ' && *cp != '\t') { 582 if (*cp == '"' || *cp == '\'') { 583 quote = *cp++; 584 while (*cp && *cp != quote) { 585 cp++; 586 } 587 if (*cp == 0) { 588 *cpp = 0; 589 return (NULL); 590 } else { 591 cp++; 592 } 593 } else { 594 cp++; 595 } 596 } 597 if (*cp != 0) 598 *cp++ = 0; 599 *cpp = cp; 600 return (start); 601 } 602 603 604 static struct cmd * 605 alloc_cmd(nvlist_t *nvlist) 606 { 607 struct cmd *cmd; 608 609 cmd = sc_malloc(sizeof (struct cmd)); 610 if (cmd) { 611 if (nvlist_dup(nvlist, &cmd->cmd_nvlist, 0) != 0) { 612 syslog(LOG_ERR, OUT_OF_MEMORY_ERR); 613 free(cmd); 614 return (NULL); 615 } 616 } 617 return (cmd); 618 } 619 620 static void 621 free_cmd(struct cmd *cmd) 622 { 623 nvlist_free(cmd->cmd_nvlist); 624 free(cmd); 625 } 626 627 628 static void * 629 sc_malloc(size_t n) 630 { 631 void *p; 632 633 p = malloc(n); 634 if (p == NULL) { 635 syslog(LOG_ERR, OUT_OF_MEMORY_ERR); 636 } 637 return (p); 638 } 639 640 static void * 641 sc_realloc(void *p, size_t n) 642 { 643 p = realloc(p, n); 644 if (p == NULL) { 645 syslog(LOG_ERR, OUT_OF_MEMORY_ERR); 646 } 647 return (p); 648 } 649 650 651 652 /* 653 * syserrsg - print error messages to the terminal if not 654 * yet daemonized or to syslog. 655 */ 656 /*PRINTFLIKE1*/ 657 static void 658 syserrmsg(char *message, ...) 659 { 660 va_list ap; 661 662 va_start(ap, message); 663 (void) vsyslog(LOG_ERR, message, ap); 664 va_end(ap); 665 } 666 667 /* 668 * printmsg - print messages to the terminal or to syslog 669 * the following levels are implemented: 670 */ 671 /*PRINTFLIKE2*/ 672 static void 673 printmsg(int level, char *message, ...) 674 { 675 va_list ap; 676 677 if (level > debug_level) { 678 return; 679 } 680 681 va_start(ap, message); 682 (void) syslog(LOG_DEBUG, "%s[%ld]: ", prog, getpid()); 683 (void) vsyslog(LOG_DEBUG, message, ap); 684 va_end(ap); 685 } 686 687 /* ARGSUSED */ 688 static void * 689 create_door_thr(void *arg) 690 { 691 (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); 692 (void) door_return(NULL, 0, NULL, 0); 693 return (NULL); 694 } 695 696 /* 697 * Control creation of door server threads 698 * 699 * If first creation of server thread fails there is nothing 700 * we can do about. Doors would not work. 701 */ 702 /* ARGSUSED */ 703 static void 704 mk_thr_pool(door_info_t *dip) 705 { 706 (void) mutex_lock(&create_cnt_lock); 707 if (++cnt_servers > MAX_SERVER_THREADS) { 708 cnt_servers--; 709 (void) mutex_unlock(&create_cnt_lock); 710 return; 711 } 712 (void) mutex_unlock(&create_cnt_lock); 713 714 (void) thr_create(NULL, 0, create_door_thr, NULL, 715 THR_BOUND|THR_DETACHED, NULL); 716 } 717 718 static sysevent_handle_t * 719 open_channel() 720 { 721 char door_path[MAXPATHLEN]; 722 const char *subclass_list; 723 sysevent_handle_t *handle; 724 725 if (snprintf(door_path, sizeof (door_path), "%s/%s", 726 root_dir, SYSEVENTCONFD_SERVICE_DOOR) >= sizeof (door_path)) { 727 syserrmsg(CHANNEL_OPEN_ERR); 728 return (NULL); 729 } 730 731 /* 732 * Setup of door server create function to limit the 733 * amount of door servers 734 */ 735 (void) door_server_create(mk_thr_pool); 736 737 handle = sysevent_open_channel_alt(door_path); 738 if (handle == NULL) { 739 syserrmsg(CHANNEL_OPEN_ERR); 740 return (NULL); 741 } 742 if (sysevent_bind_subscriber(handle, event_handler) != 0) { 743 syserrmsg(CHANNEL_BIND_ERR); 744 sysevent_close_channel(handle); 745 return (NULL); 746 } 747 subclass_list = EC_SUB_ALL; 748 if (sysevent_register_event(handle, EC_ALL, &subclass_list, 1) 749 != 0) { 750 syserrmsg(CHANNEL_BIND_ERR); 751 (void) sysevent_unbind_subscriber(handle); 752 (void) sysevent_close_channel(handle); 753 return (NULL); 754 } 755 return (handle); 756 } 757