xref: /illumos-gate/usr/src/cmd/syseventd/daemons/syseventconfd/syseventconfd.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
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