dmake: do not set MAKEFLAGS=k
[unleashed/tickless.git] / usr / src / cmd / syseventd / daemons / syseventconfd / syseventconfd.c
blob948038de634fc13ac2c45b362787f5969b4e76f9
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
23 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
28 * syseventconfd - The sysevent conf daemon
30 * This daemon is a companion to the sysevent_conf_mod module.
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.
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.
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.
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>
70 #include "syseventconfd.h"
71 #include "syseventconfd_door.h"
72 #include "message_confd.h"
76 static int debug_level = 0;
77 static char *root_dir = ""; /* Relative root for lock and door */
78 static char *prog;
80 static struct cmd *cmd_list;
81 static struct cmd *cmd_tail;
83 static mutex_t cmd_list_lock;
84 static cond_t cmd_list_cv;
86 extern char *optarg;
89 * Support for door server thread handling
91 #define MAX_SERVER_THREADS 1
93 static mutex_t create_cnt_lock;
94 static int cnt_servers = 0;
97 static void
98 usage() {
99 (void) fprintf(stderr, "usage: syseventconfd [-d <debug_level>]\n");
100 exit(2);
104 static void
105 set_root_dir(char *dir)
107 root_dir = malloc(strlen(dir) + 1);
108 if (root_dir == NULL) {
109 syserrmsg(INIT_ROOT_DIR_ERR, strerror(errno));
110 exit(2);
112 (void) strcpy(root_dir, dir);
117 main(int argc, char **argv)
119 int c;
120 int fd;
121 sigset_t set;
122 struct cmd *cmd;
124 (void) setlocale(LC_ALL, "");
125 (void) textdomain(TEXT_DOMAIN);
127 if (getuid() != 0) {
128 (void) fprintf(stderr, "Must be root to run syseventconfd\n");
129 exit(1);
132 if ((prog = strrchr(argv[0], '/')) == NULL) {
133 prog = argv[0];
134 } else {
135 prog++;
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':
145 * Private flag for suninstall to run
146 * daemon during install.
148 set_root_dir(optarg);
149 break;
150 case '?':
151 default:
152 usage();
157 if (fork()) {
158 exit(0);
161 (void) chdir("/");
163 (void) setsid();
164 if (debug_level <= 1) {
165 closefrom(0);
166 fd = open("/dev/null", O_RDONLY);
167 (void) dup2(fd, 1);
168 (void) dup2(fd, 2);
171 openlog("syseventconfd", LOG_PID, LOG_DAEMON);
173 printmsg(1,
174 "syseventconfd started, debug level = %d\n", debug_level);
177 * Block all signals to all threads include the main thread.
178 * The sigwait_thr thread will catch and process all signals.
180 (void) sigfillset(&set);
181 (void) thr_sigsetmask(SIG_BLOCK, &set, NULL);
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);
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.
195 cmd_list = NULL;
196 cmd_tail = NULL;
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);
203 * Open communication channel from sysevent_conf_mod
205 if (open_channel() == NULL) {
206 exit(1);
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.
214 (void) mutex_lock(&cmd_list_lock);
215 for (;;) {
216 while (cmd_list == NULL)
217 (void) cond_wait(&cmd_list_cv, &cmd_list_lock);
219 cmd = cmd_list;
220 cmd_list = cmd->cmd_next;
221 if (cmd_list == NULL)
222 cmd_tail = NULL;
224 (void) mutex_unlock(&cmd_list_lock);
225 exec_cmd(cmd);
226 free_cmd(cmd);
227 (void) mutex_lock(&cmd_list_lock);
229 /* NOTREACHED */
230 return (0);
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.
239 /*ARGSUSED*/
240 static void
241 event_handler(sysevent_t *event)
243 nvlist_t *nvlist;
244 struct cmd *cmd;
246 nvlist = NULL;
247 if (sysevent_get_attr_list(event, &nvlist) != 0) {
248 syslog(LOG_ERR, NO_NVLIST_ERR);
249 return;
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;
261 cmd->cmd_next = NULL;
262 (void) cond_signal(&cmd_list_cv);
263 (void) mutex_unlock(&cmd_list_lock);
266 nvlist_free(nvlist);
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.
275 static void
276 exec_cmd(struct cmd *cmd)
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;
292 if (nvlist_lookup_string(cmd->cmd_nvlist, "user", &user) != 0) {
293 syslog(LOG_ERR, NVLIST_FORMAT_ERR, "user");
294 return;
296 if (nvlist_lookup_string(cmd->cmd_nvlist, "file", &file) != 0) {
297 syslog(LOG_ERR, NVLIST_FORMAT_ERR, "file");
298 return;
301 if (nvlist_lookup_string(cmd->cmd_nvlist, "path", &path) != 0) {
302 syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "path");
303 return;
305 if (nvlist_lookup_string(cmd->cmd_nvlist, "cmd", &cmdline) != 0) {
306 syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "cmd");
307 return;
309 if (nvlist_lookup_int32(cmd->cmd_nvlist, "line", &line) != 0) {
310 syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "line");
311 return;
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;
319 } else {
320 uid = 0;
321 gid = 0;
324 args = init_arglist(32);
326 lp = cmdline;
327 while ((p = next_arg(&lp)) != NULL) {
328 if (add_arg(args, p)) {
329 free_arglist(args);
330 return;
334 if (debug_level >= DBG_EXEC) {
335 printmsg(DBG_EXEC, "path=%s\n", path);
336 printmsg(DBG_EXEC, "cmd=%s\n", cmdline);
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]);
346 (void) sigprocmask(SIG_SETMASK, NULL, &set);
347 (void) sigaddset(&set, SIGCHLD);
348 (void) sigprocmask(SIG_SETMASK, &set, &prior_set);
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;
358 if (pid != (pid_t)0) {
359 (void) sigprocmask(SIG_SETMASK, &prior_set, NULL);
360 free_arglist(args);
361 return;
365 * The child
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);
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);
386 * Unblock all signals in the child
388 (void) sigprocmask(SIG_UNBLOCK, &prior_set, NULL);
390 if (execv(path, args->arg_args) == -1) {
391 syslog(LOG_ERR, CANNOT_EXEC_ERR,
392 path, strerror(errno));
393 _exit(0);
399 * Thread to handle in-coming signals
401 static void
402 sigwait_thr()
404 int sig;
405 sigset_t signal_set;
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.
412 (void) sigset(SIGCHLD, reapchild);
414 for (;;) {
415 (void) sigfillset(&signal_set);
416 if (sigwait(&signal_set, &sig) == 0) {
418 * Block all signals until the signal handler completes
420 (void) sigfillset(&signal_set);
421 (void) thr_sigsetmask(SIG_BLOCK, &signal_set, NULL);
423 if (sig == SIGCHLD) {
424 reapchild(sig);
425 } else {
426 flt_handler(sig);
430 /* NOTREACHED */
436 * reapchild - reap the status of each child as it exits
438 /*ARGSUSED*/
439 static void
440 reapchild(int sig)
442 siginfo_t info;
443 char *signam;
444 int err;
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;
456 if (debug_level >= DBG_CHILD) {
457 printmsg(DBG_CHILD, CHILD_EXIT_STATUS_ERR,
458 info.si_pid, info.si_status);
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);
485 * Fault handler for other signals caught
487 /*ARGSUSED*/
488 static void
489 flt_handler(int sig)
491 struct sigaction act;
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);
499 switch (sig) {
500 case SIGINT:
501 case SIGSTOP:
502 case SIGTERM:
503 case SIGHUP:
504 exit(1);
505 /*NOTREACHED*/
510 static arg_t *
511 init_arglist(int hint)
513 arg_t *arglist;
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);
525 static void
526 free_arglist(arg_t *arglist)
528 if (arglist->arg_args) {
529 free(arglist->arg_args);
531 free(arglist);
535 static int
536 add_arg(arg_t *arglist, char *arg)
538 char **new_args;
539 int len;
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;
553 arglist->arg_args[arglist->arg_nargs++] = arg;
554 arglist->arg_args[arglist->arg_nargs] = NULL;
556 return (0);
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.
565 static char *
566 next_arg(char **cpp)
568 char *cp = *cpp;
569 char *start;
570 char quote;
572 while (*cp == ' ' || *cp == '\t')
573 cp++;
574 if (*cp == 0) {
575 *cpp = 0;
576 return (NULL);
578 start = cp;
579 while (*cp && *cp != ' ' && *cp != '\t') {
580 if (*cp == '"' || *cp == '\'') {
581 quote = *cp++;
582 while (*cp && *cp != quote) {
583 cp++;
585 if (*cp == 0) {
586 *cpp = 0;
587 return (NULL);
588 } else {
589 cp++;
591 } else {
592 cp++;
595 if (*cp != 0)
596 *cp++ = 0;
597 *cpp = cp;
598 return (start);
602 static struct cmd *
603 alloc_cmd(nvlist_t *nvlist)
605 struct cmd *cmd;
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);
615 return (cmd);
618 static void
619 free_cmd(struct cmd *cmd)
621 nvlist_free(cmd->cmd_nvlist);
622 free(cmd);
626 static void *
627 sc_malloc(size_t n)
629 void *p;
631 p = malloc(n);
632 if (p == NULL) {
633 syslog(LOG_ERR, OUT_OF_MEMORY_ERR);
635 return (p);
638 static void *
639 sc_realloc(void *p, size_t n)
641 p = realloc(p, n);
642 if (p == NULL) {
643 syslog(LOG_ERR, OUT_OF_MEMORY_ERR);
645 return (p);
651 * syserrsg - print error messages to the terminal if not
652 * yet daemonized or to syslog.
654 /*PRINTFLIKE1*/
655 static void
656 syserrmsg(char *message, ...)
658 va_list ap;
660 va_start(ap, message);
661 (void) vsyslog(LOG_ERR, message, ap);
662 va_end(ap);
666 * printmsg - print messages to the terminal or to syslog
667 * the following levels are implemented:
669 /*PRINTFLIKE2*/
670 static void
671 printmsg(int level, char *message, ...)
673 va_list ap;
675 if (level > debug_level) {
676 return;
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);
685 /* ARGSUSED */
686 static void *
687 create_door_thr(void *arg)
689 (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
690 (void) door_return(NULL, 0, NULL, 0);
691 return (NULL);
695 * Control creation of door server threads
697 * If first creation of server thread fails there is nothing
698 * we can do about. Doors would not work.
700 /* ARGSUSED */
701 static void
702 mk_thr_pool(door_info_t *dip)
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;
710 (void) mutex_unlock(&create_cnt_lock);
712 (void) thr_create(NULL, 0, create_door_thr, NULL,
713 THR_BOUND|THR_DETACHED, NULL);
716 static sysevent_handle_t *
717 open_channel()
719 char door_path[MAXPATHLEN];
720 const char *subclass_list;
721 sysevent_handle_t *handle;
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);
730 * Setup of door server create function to limit the
731 * amount of door servers
733 (void) door_server_create(mk_thr_pool);
735 handle = sysevent_open_channel_alt(door_path);
736 if (handle == NULL) {
737 syserrmsg(CHANNEL_OPEN_ERR);
738 return (NULL);
740 if (sysevent_bind_subscriber(handle, event_handler) != 0) {
741 syserrmsg(CHANNEL_BIND_ERR);
742 sysevent_close_channel(handle);
743 return (NULL);
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);
753 return (handle);