xref: /titanic_44/usr/src/cmd/syseventd/daemons/syseventconfd/syseventconfd.c (revision 8eea8e29cc4374d1ee24c25a07f45af132db3499)
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