# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # #ident "%Z%%M% %I% %E% SMI" # # Copyright (c) 1998-2001 by Sun Microsystems, Inc. # All rights reserved. # # cmd/syseventd/daemons/Makefile # .PARALLEL: SUBDIRS= \ syseventd \ syseventconfd all: TARGET= all install: TARGET= install clean: TARGET= clean clobber: TARGET= clobber lint: TARGET= lint _msg: TARGET= _msg .KEEP_STATE: all install clean clobber lint _msg: $(SUBDIRS) FRC: $(SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # #ident "%Z%%M% %I% %E% SMI" # # Copyright (c) 2001 by Sun Microsystems, Inc. # All rights reserved. # # cmd/syseventd/daemons/Makefile.com # # Common prologue for sysevent daemons # include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/syseventd/Makefile.com OBJS = $(SRCS:%.c=%.o) POFILES = $(SRCS:.c=.po) POFILE = $(PROG)_msg.po # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # #ident "%Z%%M% %I% %E% SMI" # # Copyright (c) 2001 by Sun Microsystems, Inc. # All rights reserved. # # cmd/syseventd/daemons/Makefile.targ # # Common epilogue for sysevent daemons # lint: lint_SRCS clean: $(RM) $(OBJS) include $(SRC)/cmd/Makefile.targ include $(SRC)/cmd/syseventd/Makefile.targ $(POFILE): $(POFILES) $(CAT) $(POFILES) >$@ # Hammerhead: --export-dynamic makes syseventd symbols visible to # dlopen'd sysevent modules (GNU ld requirement). $(PROG): $(OBJS) $(LINK.c) -o $(PROG) -Wl,--export-dynamic $(OBJS) $(LDLIBS) $(POST_PROCESS) # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" # PROG = syseventconfd SRCS = $(PROG:%=%.c) include ../Makefile.com LDLIBS += -lnvpair .KEEP_STATE: all: $(PROG) install: all \ $(ROOTLIBSYSEVENTDIR) \ $(ROOTLIBSYSEVENTSYSEVENTCONFD) include ../Makefile.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2000 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MESSAGE_CONFD_H #define _MESSAGE_CONFD_H #ifdef __cplusplus extern "C" { #endif #define OUT_OF_MEMORY_ERR \ gettext("Out of memory.") #define INIT_ROOT_DIR_ERR \ gettext("Initialization error: could not allocate space for the local \ root directory - %s\n") #define INIT_THR_CREATE_ERR \ gettext("Initialization error:could not create signal thread - '%s' \ \n") #define CANNOT_FORK_ERR \ gettext("cannot fork - %s\n") #define SETUID_ERR \ gettext("%s, line %d: " "cannot setuid to user '%s' - %s\n") #define CANNOT_EXEC_ERR \ gettext("cannot exec %s - %s\n") #define CHILD_EXIT_STATUS_ERR \ gettext("process %d exited with status %d\n") #define CHILD_EXIT_CORE_ERR \ gettext("process %d dumped core - %s\n") #define CHILD_EXIT_SIGNAL_ERR \ gettext("process %d - %s\n") #define CHANNEL_OPEN_ERR \ gettext("unable to open channel to syseventd\n") #define CHANNEL_BIND_ERR \ gettext("unable to bind channel to syseventd\n") #define NO_NVLIST_ERR \ gettext("missing nvlist\n") #define NVLIST_FORMAT_ERR \ gettext("nvlist missing '%s'\n") #define NVLIST_FILE_LINE_FORMAT_ERR \ gettext("%s, line %d: nvlist missing '%s'\n") #ifdef __cplusplus } #endif #endif /* _MESSAGE_CONFD_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * syseventconfd - The sysevent conf daemon * * This daemon is a companion to the sysevent_conf_mod module. * * The sysevent_conf_mod module receives events from syseventd, * and compares those events against event specs in the * sysevent.conf files. For each matching event spec, the * specified command is invoked. * * This daemon manages the fork/exec's on behalf of sysevent_conf_mod. * The events and associated nvlist are delivered via a door upcall * from sysevent_conf_mod. Arriving events are queued, and the * main thread of this daemon dequeues events one by one, and * builds the necessary arguments to fork/exec the command. * * Since sysevent_conf_mod is running in the context of syseventd, * invoking the fork/exec from that module blocks the door upcalls * from the kernel delivering events to syseventd. We avoid a * major performance bottleneck in this fashion. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "syseventconfd.h" #include "syseventconfd_door.h" #include "message_confd.h" static int debug_level = 0; static char *root_dir = ""; /* Relative root for lock and door */ static char *prog; static struct cmd *cmd_list; static struct cmd *cmd_tail; static mutex_t cmd_list_lock; static cond_t cmd_list_cv; extern char *optarg; /* * Support for door server thread handling */ #define MAX_SERVER_THREADS 1 static mutex_t create_cnt_lock; static int cnt_servers = 0; static void usage() { (void) fprintf(stderr, "usage: syseventconfd [-d ]\n"); exit(2); } static void set_root_dir(char *dir) { root_dir = malloc(strlen(dir) + 1); if (root_dir == NULL) { syserrmsg(INIT_ROOT_DIR_ERR, strerror(errno)); exit(2); } (void) strcpy(root_dir, dir); } int main(int argc, char **argv) { int c; int fd; sigset_t set; struct cmd *cmd; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); if (getuid() != 0) { (void) fprintf(stderr, "Must be root to run syseventconfd\n"); exit(1); } if ((prog = strrchr(argv[0], '/')) == NULL) { prog = argv[0]; } else { prog++; } if ((c = getopt(argc, argv, "d:r:")) != EOF) { switch (c) { case 'd': debug_level = atoi(optarg); break; case 'r': /* * Private flag for suninstall to run * daemon during install. */ set_root_dir(optarg); break; case '?': default: usage(); } } if (fork()) { exit(0); } (void) chdir("/"); (void) setsid(); if (debug_level <= 1) { closefrom(0); fd = open("/dev/null", 0); (void) dup2(fd, 1); (void) dup2(fd, 2); } openlog("syseventconfd", LOG_PID, LOG_DAEMON); printmsg(1, "syseventconfd started, debug level = %d\n", debug_level); /* * Block all signals to all threads include the main thread. * The sigwait_thr thread will catch and process all signals. */ (void) sigfillset(&set); (void) thr_sigsetmask(SIG_BLOCK, &set, NULL); /* Create signal catching thread */ if (thr_create(NULL, 0, (void *(*)(void *))sigwait_thr, NULL, 0, NULL) < 0) { syserrmsg(INIT_THR_CREATE_ERR, strerror(errno)); exit(2); } /* * Init mutex and list of cmds to be fork/exec'ed * This is multi-threaded so the fork/exec can be * done without blocking the door upcall. */ cmd_list = NULL; cmd_tail = NULL; (void) mutex_init(&create_cnt_lock, USYNC_THREAD, NULL); (void) mutex_init(&cmd_list_lock, USYNC_THREAD, NULL); (void) cond_init(&cmd_list_cv, USYNC_THREAD, NULL); /* * Open communication channel from sysevent_conf_mod */ if (open_channel() == NULL) { exit(1); } /* * main thread to wait for events to arrive and be placed * on the queue. As events are queued, dequeue them * here and invoke the associated fork/exec. */ (void) mutex_lock(&cmd_list_lock); for (;;) { while (cmd_list == NULL) (void) cond_wait(&cmd_list_cv, &cmd_list_lock); cmd = cmd_list; cmd_list = cmd->cmd_next; if (cmd_list == NULL) cmd_tail = NULL; (void) mutex_unlock(&cmd_list_lock); exec_cmd(cmd); free_cmd(cmd); (void) mutex_lock(&cmd_list_lock); } /* NOTREACHED */ return (0); } /* * Events sent via the door call from sysevent_conf_mod arrive * here. Queue each event for the main thread to invoke, and * return. We want to avoid doing the fork/exec while in the * context of the door call. */ /*ARGSUSED*/ static void event_handler(sysevent_t *event) { nvlist_t *nvlist; struct cmd *cmd; nvlist = NULL; if (sysevent_get_attr_list(event, &nvlist) != 0) { syslog(LOG_ERR, NO_NVLIST_ERR); return; } if ((cmd = alloc_cmd(nvlist)) != NULL) { (void) mutex_lock(&cmd_list_lock); if (cmd_list == NULL) { cmd_list = cmd; cmd_tail = cmd; } else { cmd_tail->cmd_next = cmd; cmd_tail = cmd; } cmd->cmd_next = NULL; (void) cond_signal(&cmd_list_cv); (void) mutex_unlock(&cmd_list_lock); } nvlist_free(nvlist); } /* * Decode the command, build the exec args and fork/exec the command * All command attributes are packed into the nvlist bundled with * the delivered event. */ static void exec_cmd(struct cmd *cmd) { char *path; char *cmdline; uid_t uid; gid_t gid; char *file; int line; char *user; arg_t *args; pid_t pid; char *lp; char *p; int i; sigset_t set, prior_set; if (nvlist_lookup_string(cmd->cmd_nvlist, "user", &user) != 0) { syslog(LOG_ERR, NVLIST_FORMAT_ERR, "user"); return; } if (nvlist_lookup_string(cmd->cmd_nvlist, "file", &file) != 0) { syslog(LOG_ERR, NVLIST_FORMAT_ERR, "file"); return; } if (nvlist_lookup_string(cmd->cmd_nvlist, "path", &path) != 0) { syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "path"); return; } if (nvlist_lookup_string(cmd->cmd_nvlist, "cmd", &cmdline) != 0) { syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "cmd"); return; } if (nvlist_lookup_int32(cmd->cmd_nvlist, "line", &line) != 0) { syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "line"); return; } if (nvlist_lookup_int32(cmd->cmd_nvlist, "uid", (int *)&uid) == 0) { if (nvlist_lookup_int32(cmd->cmd_nvlist, "gid", (int *)&gid) != 0) { syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "gid"); return; } } else { uid = 0; gid = 0; } args = init_arglist(32); lp = cmdline; while ((p = next_arg(&lp)) != NULL) { if (add_arg(args, p)) { free_arglist(args); return; } } if (debug_level >= DBG_EXEC) { printmsg(DBG_EXEC, "path=%s\n", path); printmsg(DBG_EXEC, "cmd=%s\n", cmdline); } if (debug_level >= DBG_EXEC_ARGS) { for (i = 0; i < args->arg_nargs; i++) { printmsg(DBG_EXEC_ARGS, "arg[%d]: '%s'\n", i, args->arg_args[i]); } } (void) sigprocmask(SIG_SETMASK, NULL, &set); (void) sigaddset(&set, SIGCHLD); (void) sigprocmask(SIG_SETMASK, &set, &prior_set); again: if ((pid = fork1()) == (pid_t)-1) { if (errno == EINTR) goto again; syslog(LOG_ERR, CANNOT_FORK_ERR, strerror(errno)); free_arglist(args); return; } if (pid != (pid_t)0) { (void) sigprocmask(SIG_SETMASK, &prior_set, NULL); free_arglist(args); return; } /* * The child */ (void) close(0); (void) close(1); (void) close(2); (void) open("/dev/null", O_RDONLY); (void) dup2(0, 1); (void) dup2(0, 2); if (uid != (uid_t)0) { i = setgid(gid); if (i == 0) i = setuid(uid); if (i != 0) { syslog(LOG_ERR, SETUID_ERR, file, line, user, strerror(errno)); _exit(0); } } /* * Unblock all signals in the child */ (void) sigprocmask(SIG_UNBLOCK, &prior_set, NULL); if (execv(path, args->arg_args) == -1) { syslog(LOG_ERR, CANNOT_EXEC_ERR, path, strerror(errno)); _exit(0); } } /* * Thread to handle in-coming signals */ static void sigwait_thr() { int sig; sigset_t signal_set; /* * SIGCHLD is ignored by default, and we need to handle this * signal to reap the status of all children spawned by * this daemon. */ (void) sigset(SIGCHLD, reapchild); for (;;) { (void) sigfillset(&signal_set); if (sigwait(&signal_set, &sig) == 0) { /* * Block all signals until the signal handler completes */ (void) sigfillset(&signal_set); (void) thr_sigsetmask(SIG_BLOCK, &signal_set, NULL); if (sig == SIGCHLD) { reapchild(sig); } else { flt_handler(sig); } } } /* NOTREACHED */ } /* * reapchild - reap the status of each child as it exits */ /*ARGSUSED*/ static void reapchild(int sig) { siginfo_t info; char *signam; int err; for (;;) { (void) memset(&info, 0, sizeof (info)); err = waitid(P_ALL, 0, &info, WNOHANG|WEXITED); if (err == -1) { if (errno != EINTR && errno != EAGAIN) return; } else if (info.si_pid == 0) { return; } if (debug_level >= DBG_CHILD) { printmsg(DBG_CHILD, CHILD_EXIT_STATUS_ERR, info.si_pid, info.si_status); } if (info.si_status) { if (info.si_code == CLD_EXITED) { syserrmsg(CHILD_EXIT_STATUS_ERR, info.si_pid, info.si_status); } else { signam = strsignal(info.si_status); if (signam == NULL) signam = ""; if (info.si_code == CLD_DUMPED) { syserrmsg( CHILD_EXIT_CORE_ERR, info.si_pid, signam); } else { syserrmsg( CHILD_EXIT_SIGNAL_ERR, info.si_pid, signam); } } } } } /* * Fault handler for other signals caught */ /*ARGSUSED*/ static void flt_handler(int sig) { struct sigaction act; (void) memset(&act, 0, sizeof (act)); act.sa_handler = SIG_DFL; act.sa_flags = SA_RESTART; (void) sigfillset(&act.sa_mask); (void) sigaction(sig, &act, NULL); switch (sig) { case SIGINT: case SIGSTOP: case SIGTERM: case SIGHUP: exit(1); /*NOTREACHED*/ } } static arg_t * init_arglist(int hint) { arg_t *arglist; if ((arglist = sc_malloc(sizeof (arg_t))) == NULL) return (NULL); arglist->arg_args = NULL; arglist->arg_nargs = 0; arglist->arg_alloc = 0; arglist->arg_hint = hint; return (arglist); } static void free_arglist(arg_t *arglist) { if (arglist->arg_args) { free(arglist->arg_args); } free(arglist); } static int add_arg(arg_t *arglist, char *arg) { char **new_args; int len; len = arglist->arg_nargs + 2; if (arglist->arg_alloc < len) { arglist->arg_alloc = len + arglist->arg_hint; new_args = (arglist->arg_nargs == 0) ? sc_malloc(arglist->arg_alloc * sizeof (char **)) : sc_realloc(arglist->arg_args, arglist->arg_alloc * sizeof (char **)); if (new_args == NULL) return (1); arglist->arg_args = new_args; } arglist->arg_args[arglist->arg_nargs++] = arg; arglist->arg_args[arglist->arg_nargs] = NULL; return (0); } /* * next_arg() is used to break up a command line * into the arguments for execv(2). Break up * arguments separated by spaces, but respecting * single/double quotes. */ static char * next_arg(char **cpp) { char *cp = *cpp; char *start; char quote; while (*cp == ' ' || *cp == '\t') cp++; if (*cp == 0) { *cpp = 0; return (NULL); } start = cp; while (*cp && *cp != ' ' && *cp != '\t') { if (*cp == '"' || *cp == '\'') { quote = *cp++; while (*cp && *cp != quote) { cp++; } if (*cp == 0) { *cpp = 0; return (NULL); } else { cp++; } } else { cp++; } } if (*cp != 0) *cp++ = 0; *cpp = cp; return (start); } static struct cmd * alloc_cmd(nvlist_t *nvlist) { struct cmd *cmd; cmd = sc_malloc(sizeof (struct cmd)); if (cmd) { if (nvlist_dup(nvlist, &cmd->cmd_nvlist, 0) != 0) { syslog(LOG_ERR, OUT_OF_MEMORY_ERR); free(cmd); return (NULL); } } return (cmd); } static void free_cmd(struct cmd *cmd) { nvlist_free(cmd->cmd_nvlist); free(cmd); } static void * sc_malloc(size_t n) { void *p; p = malloc(n); if (p == NULL) { syslog(LOG_ERR, OUT_OF_MEMORY_ERR); } return (p); } static void * sc_realloc(void *p, size_t n) { p = realloc(p, n); if (p == NULL) { syslog(LOG_ERR, OUT_OF_MEMORY_ERR); } return (p); } /* * syserrsg - print error messages to the terminal if not * yet daemonized or to syslog. */ /*PRINTFLIKE1*/ static void syserrmsg(char *message, ...) { va_list ap; va_start(ap, message); (void) vsyslog(LOG_ERR, message, ap); va_end(ap); } /* * printmsg - print messages to the terminal or to syslog * the following levels are implemented: */ /*PRINTFLIKE2*/ static void printmsg(int level, char *message, ...) { va_list ap; if (level > debug_level) { return; } va_start(ap, message); (void) syslog(LOG_DEBUG, "%s[%ld]: ", prog, getpid()); (void) vsyslog(LOG_DEBUG, message, ap); va_end(ap); } /* ARGSUSED */ static void * create_door_thr(void *arg) { (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); (void) door_return(NULL, 0, NULL, 0); return (NULL); } /* * Control creation of door server threads * * If first creation of server thread fails there is nothing * we can do about. Doors would not work. */ /* ARGSUSED */ static void mk_thr_pool(door_info_t *dip) { (void) mutex_lock(&create_cnt_lock); if (++cnt_servers > MAX_SERVER_THREADS) { cnt_servers--; (void) mutex_unlock(&create_cnt_lock); return; } (void) mutex_unlock(&create_cnt_lock); (void) thr_create(NULL, 0, create_door_thr, NULL, THR_BOUND|THR_DETACHED, NULL); } static sysevent_handle_t * open_channel() { char door_path[MAXPATHLEN]; const char *subclass_list; sysevent_handle_t *handle; if (snprintf(door_path, sizeof (door_path), "%s/%s", root_dir, SYSEVENTCONFD_SERVICE_DOOR) >= sizeof (door_path)) { syserrmsg(CHANNEL_OPEN_ERR); return (NULL); } /* * Setup of door server create function to limit the * amount of door servers */ (void) door_server_create(mk_thr_pool); handle = sysevent_open_channel_alt(door_path); if (handle == NULL) { syserrmsg(CHANNEL_OPEN_ERR); return (NULL); } if (sysevent_bind_subscriber(handle, event_handler) != 0) { syserrmsg(CHANNEL_BIND_ERR); sysevent_close_channel(handle); return (NULL); } subclass_list = EC_SUB_ALL; if (sysevent_register_event(handle, EC_ALL, &subclass_list, 1) != 0) { syserrmsg(CHANNEL_BIND_ERR); (void) sysevent_unbind_subscriber(handle); (void) sysevent_close_channel(handle); return (NULL); } return (handle); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SYSEVENTCONFD_H #define _SYSEVENTCONFD_H #ifdef __cplusplus extern "C" { #endif /* * debug levels */ #define DBG_EXEC 1 /* path and args for each fork/exec */ #define DBG_EVENTS 2 /* received events */ #define DBG_CHILD 3 /* child exit status */ #define DBG_EXEC_ARGS 4 /* more detail on exec args */ /* * list of cmds received from syseventd/sysevent_conf_mod */ struct cmd { nvlist_t *cmd_nvlist; struct cmd *cmd_next; struct cmd *cmd_taiL; }; /* * Structures for building arbitarily long strings and argument lists */ typedef struct arg { char **arg_args; int arg_nargs; int arg_alloc; int arg_hint; } arg_t; typedef struct str { char *s_str; int s_len; int s_alloc; int s_hint; } str_t; /* * Prototypes */ static void event_handler(sysevent_t *event); static void exec_cmd(struct cmd *cmd); static void sigwait_thr(void); static void reapchild(int sig); static void flt_handler(int sig); static void syserrmsg(char *message, ...); static void printmsg(int level, char *message, ...); static void set_root_dir(char *dir); static void usage(void); static arg_t *init_arglist(int hint); static void free_arglist(arg_t *arglist); static int add_arg(arg_t *arglist, char *arg); static char *next_arg(char **cpp); static struct cmd *alloc_cmd(nvlist_t *nvlist); static void free_cmd(struct cmd *cmd); static void *sc_malloc(size_t n); static void *sc_realloc(void *p, size_t n); static sysevent_handle_t *open_channel(void); #ifdef __cplusplus } #endif #endif /* _SYSEVENTCONFD_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SYSEVENTCONFD_DOOR_H #define _SYSEVENTCONFD_DOOR_H #ifdef __cplusplus extern "C" { #endif /* * Door for channel sysevent_conf_mod -> syseventconfd */ #define SYSEVENTCONFD_SERVICE_DOOR \ "/var/run/syseventconfd_door" #ifdef __cplusplus } #endif #endif /* _SYSEVENTCONFD_DOOR_H */ # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # PROG = syseventd SRCS = syseventd.c sysevent_signal.c sysevent_client.c include ../Makefile.com CERRWARN += $(CNOWARN_UNINIT) .KEEP_STATE: all: $(PROG) install: all \ $(ROOTLIBSYSEVENTDIR) \ $(ROOTLIBSYSEVENTSYSEVENTD) include ../Makefile.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MESSAGE_H #define _MESSAGE_H #ifdef __cplusplus extern "C" { #endif #define SIGHUP_CAUGHT \ gettext("SIGHUP caught - reloading modules\n") #define DAEMON_RESTARTED \ gettext("Daemon restarted\n") #define UNKNOWN_SIGNAL_CAUGHT \ gettext("Signal '%d' caught\n") #define FATAL_ERROR \ gettext("Fatal:attempting to dump core\n") #define INIT_ROOT_DIR_ERR \ gettext("Initialization error: could not allocate space for the local \ root directory - %s\n") #define INIT_EV_BUF_ERR \ gettext("Initialization error: could not allocate space for the event \ buffer - %s\n") #define INIT_SIG_BLOCK_ERR \ gettext("Initialization error: Unable to block signals before creating \ event dispatch thread\n") #define INIT_THR_CREATE_ERR \ gettext("Initialization error:could not create dispatch thread - '%s' \ \n") #define INIT_SIG_UNBLOCK_ERR \ gettext("Initialization error: Unable to unblock signals after \ creating event dispatch thread\n") #define KERNEL_REPLAY_ERR \ gettext("Kernel unable to post events: '%s'\n") #define LOAD_MOD_ALLOC_ERR \ gettext("Unable to allocate load module data structure %s: %s") #define LOAD_MOD_OPEN_ERR \ gettext("Unable to open module directory '%s': '%s'") #define LOAD_MOD_READ_ERR \ gettext("Unable to read module directory '%s': '%s'") #define LOAD_MOD_DLOPEN_ERR \ gettext("Unable to open module '%s': '%s'") #define LOAD_MOD_DLSYM_ERR \ gettext("Unable to read symbols for module '%s': '%s'") #define LOAD_MOD_NO_INIT \ gettext("Invalid module init routine for '%s': '%s'") #define LOAD_MOD_EINVAL \ gettext("Invalid ops vector for module '%s'") #define LOAD_MOD_VERSION_MISMATCH \ gettext("Invalid major number for module '%s': \ syseventd version '%d' module version '%d'") #define INIT_OPEN_DOOR_ERR \ gettext("Unable to open kernel event door: '%s'") #define INIT_CREATE_DOOR_ERR \ gettext("Unable to create kernel event door: '%s'") #define INIT_FATTACH_ERR \ gettext("Kernel door failed to attach: '%s'") #define INIT_DOOR_NAME_ERR \ gettext("Unable to establish door name with kernel: '%s'") #define INIT_LOCK_OPEN_ERR \ gettext("Unable to open daemon lock file '%s': '%s'") #define INIT_LOCK_ERR \ gettext("Unable to obtain daemon lock file '%s': '%s'") #define INIT_PATH_ERR \ gettext("Unable to open '%s': file path invalid") #define INIT_UNLOCK_ERR \ gettext("Unable to release daemon lock file '%s': '%s'") #define INIT_LOCK_CLOSE_ERR \ gettext("Unable to close daemon lock file '%s': '%s'") #define INIT_CLIENT_TBL_ERR \ gettext("Unable to initialize event client table\n") #define GET_DATA_FAILED \ gettext("Incomplete event buffer 0X%llx.%llx") #define WAIT_FAILED_ERR \ gettext("waitpid() failed: %s\n") #define SEMA_WAIT_FAILED_ERR \ gettext("sema_wait() failed: %s\n") #ifdef __cplusplus } #endif #endif /* _MESSAGE_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. */ /* * Copyright (c) 2018, Joyent, Inc. */ /* * syseventd client interfaces */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "syseventd.h" #include "message.h" /* * sysevent_client.c - contains routines particular to syseventd client * management (addition and deletion). */ /* Global client table and lock */ struct sysevent_client *sysevent_client_tbl[MAX_SLM]; mutex_t client_tbl_lock; /* * initialize_client_tbl - Initialize each client entry in the syseventd * client table. Each entry in the client table * entry represents one shared-object (SLM) client. */ void initialize_client_tbl() { struct sysevent_client *scp; int i; for (i = 0; i < MAX_SLM; ++i) { if ((scp = (struct sysevent_client *)malloc( sizeof (struct sysevent_client))) == NULL) goto init_error; if (mutex_init(&scp->client_lock, USYNC_THREAD, NULL) != 0) goto init_error; scp->client_data = NULL; scp->client_num = i; scp->eventq = NULL; /* Clear all flags when setting UNLOADED */ scp->client_flags = SE_CLIENT_UNLOADED; sysevent_client_tbl[i] = scp; } return; init_error: syseventd_err_print(INIT_CLIENT_TBL_ERR); syseventd_exit(1); } /* * insert_client - called when a new SLM is loaded with syseventd. The * client specific data is updated to reflect this addition */ int insert_client(void *client_data, int client_type, int retry_limit) { int i; struct sysevent_client *scp; (void) mutex_lock(&client_tbl_lock); for (i = 0; i < MAX_SLM; ++i) { scp = sysevent_client_tbl[i]; if (scp->client_data == NULL) { (void) mutex_lock(&scp->client_lock); scp->client_data = client_data; scp->client_type = client_type; scp->retry_limit = retry_limit; scp->client_flags |= SE_CLIENT_LOADED; (void) cond_init(&scp->client_cv, USYNC_THREAD, NULL); (void) mutex_unlock(&scp->client_lock); (void) mutex_unlock(&client_tbl_lock); return (i); } } (void) mutex_unlock(&client_tbl_lock); syseventd_print(1, "Unable to insert into syseventd client table\n"); return (-1); } /* * delete_client - called to remove an SLM from the client table. Client * removal may occur when syseventd terminates, receives * a SIGHUP or the client must be force unloaded due * it's unresponsive nature. */ void delete_client(int id) { struct sysevent_client *scp; scp = sysevent_client_tbl[id]; free(scp->client_data); scp->client_data = NULL; /* Clear all flags when setting UNLOADED */ scp->client_flags = SE_CLIENT_UNLOADED; (void) cond_destroy(&scp->client_cv); bzero(&scp->client_cv, sizeof (cond_t)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2000 by Sun Microsystems, Inc. * All rights reserved. */ #include #include #include #include #include "sysevent_signal.h" static se_signal_f *sig_handlers[NSIG]; static void *sig_data[NSIG]; static void sig_stub(int sig, siginfo_t *sip, void *ucp) { sig_handlers[sig](sig, sip, (ucontext_t *)ucp, sig_data[sig]); } int se_signal_sethandler(int sig, se_signal_f *handler, void *data) { struct sigaction act; int status; sig_handlers[sig] = handler; sig_data[sig] = data; if (handler == SE_SIG_DFL) { act.sa_handler = SIG_DFL; act.sa_flags = SA_RESTART; } else if (handler == SE_SIG_IGN) { act.sa_handler = SIG_IGN; act.sa_flags = SA_RESTART; } else { act.sa_sigaction = sig_stub; act.sa_flags = SA_SIGINFO | SA_RESTART; } (void) sigfillset(&act.sa_mask); if ((status = sigaction(sig, &act, NULL)) == 0) (void) se_signal_unblock(sig); return (status); } int se_signal_unblock(int sig) { sigset_t set; (void) sigemptyset(&set); (void) sigaddset(&set, sig); return (thr_sigsetmask(SIG_UNBLOCK, &set, NULL)); } int se_signal_blockall(void) { sigset_t set; (void) sigfillset(&set); return (thr_sigsetmask(SIG_BLOCK, &set, NULL)); } int se_signal_unblockall(void) { sigset_t set; (void) sigfillset(&set); return (thr_sigsetmask(SIG_UNBLOCK, &set, NULL)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2000 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _DEVFSEVENT_SIGNAL_H #define _DEVFSEVENT_SIGNAL_H #ifdef __cplusplus extern "C" { #endif #include #include typedef void se_signal_f(int, siginfo_t *, ucontext_t *, void *); #define SE_SIG_DFL (se_signal_f *)0 #define SE_SIG_IGN (se_signal_f *)1 extern int se_signal_sethandler(int, se_signal_f *, void *); extern se_signal_f *se_signal_gethandler(int, void **); extern int se_signal_raise(int); extern int se_signal_pgrp(int); extern int se_signal_block(int); extern int se_signal_unblock(int); extern int se_signal_blockall(void); extern int se_signal_unblockall(void); #ifdef __cplusplus } #endif #endif /* _DEVFSEVENT_SIGNAL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * syseventd - The system event daemon * * This daemon dispatches event buffers received from the * kernel to all interested SLM clients. SLMs in turn * deliver the buffers to their particular application * clients. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sysevent_signal.h" #include "syseventd.h" #include "message.h" extern int insert_client(void *client, int client_type, int retry_limit); extern void delete_client(int id); extern void initialize_client_tbl(void); extern struct sysevent_client *sysevent_client_tbl[]; extern mutex_t client_tbl_lock; #define DEBUG_LEVEL_FORK 9 /* will run in background at all */ /* levels less than DEBUG_LEVEL_FORK */ int debug_level = 0; char *root_dir = ""; /* Relative root for lock and door */ /* Maximum number of outstanding events dispatched */ #define SE_EVENT_DISPATCH_CNT 100 static int upcall_door; /* Kernel event door */ static int door_upcall_retval; /* Kernel event posting return value */ static int fini_pending = 0; /* fini pending flag */ static int deliver_buf = 0; /* Current event buffer from kernel */ static int dispatch_buf = 0; /* Current event buffer dispatched */ static sysevent_t **eventbuf; /* Global array of event buffers */ static struct ev_completion *event_compq; /* Event completion queue */ static mutex_t ev_comp_lock; /* Event completion queue lock */ static mutex_t err_mutex; /* error logging lock */ static mutex_t door_lock; /* sync door return access */ static rwlock_t mod_unload_lock; /* sync module unloading */ /* declarations and definitions for avoiding multiple daemons running */ #define DAEMON_LOCK_FILE "/var/run/syseventd.lock" char local_lock_file[PATH_MAX + 1]; static int hold_daemon_lock; static int daemon_lock_fd; /* * sema_eventbuf - guards against the global buffer eventbuf * being written to before it has been dispatched to clients * * sema_dispatch - synchronizes between the kernel uploading thread * (producer) and the userland dispatch_message thread (consumer). * * sema_resource - throttles outstanding event consumption. * * event_comp_cv - synchronizes threads waiting for the event completion queue * to empty or become active. */ static sema_t sema_eventbuf, sema_dispatch, sema_resource; static cond_t event_comp_cv; /* Self-tuning concurrency level */ #define MIN_CONCURRENCY_LEVEL 4 static int concurrency_level = MIN_CONCURRENCY_LEVEL; /* SLM defines */ #define MODULE_SUFFIX ".so" #define EVENT_FINI "slm_fini" #define EVENT_INIT "slm_init" #define SE_TIMEOUT 60 /* Client dispatch timeout (seconds) */ /* syslog message related */ static int logflag = 0; static char *prog; /* function prototypes */ static void door_upcall(void *cookie, char *args, size_t alen, door_desc_t *ddp, uint_t ndid); static void dispatch_message(void); static int dispatch(void); static void event_completion_thr(void); static void usage(void); static void syseventd_init(void); static void syseventd_fini(int sig); static pid_t enter_daemon_lock(void); static void exit_daemon_lock(void); static void usage() { (void) fprintf(stderr, "usage: syseventd [-d ] " "[-r ]\n"); (void) fprintf(stderr, "higher debug levels get progressively "); (void) fprintf(stderr, "more detailed debug information.\n"); (void) fprintf(stderr, "syseventd will run in background if "); (void) fprintf(stderr, "run with a debug_level less than %d.\n", DEBUG_LEVEL_FORK); exit(2); } /* common exit function which ensures releasing locks */ void syseventd_exit(int status) { syseventd_print(1, "exit status = %d\n", status); if (hold_daemon_lock) { exit_daemon_lock(); } exit(status); } /* * hup_handler - SIGHUP handler. SIGHUP is used to force a reload of * all SLMs. During fini, events are drained from all * client event queues. The events that have been consumed * by all clients are freed from the kernel event queue. * * Events that have not yet been delivered to all clients * are not freed and will be replayed after all SLMs have * been (re)loaded. * * After all client event queues have been drained, each * SLM client is unloaded. The init phase will (re)load * each SLM and initiate event replay and delivery from * the kernel. * */ /*ARGSUSED*/ static void hup_handler(int sig) { syseventd_err_print(SIGHUP_CAUGHT); (void) fflush(0); syseventd_fini(sig); syseventd_init(); syseventd_err_print(DAEMON_RESTARTED); (void) fflush(0); } /* * Fault handler for other signals caught */ /*ARGSUSED*/ static void flt_handler(int sig) { char signame[SIG2STR_MAX]; if (sig2str(sig, signame) == -1) { syseventd_err_print(UNKNOWN_SIGNAL_CAUGHT, sig); } (void) se_signal_sethandler(sig, SE_SIG_DFL, NULL); switch (sig) { case SIGINT: case SIGSTOP: case SIGTERM: /* Close kernel door */ (void) door_revoke(upcall_door); /* Gracefully exit current event delivery threads */ syseventd_fini(sig); (void) fflush(0); (void) se_signal_unblockall(); syseventd_exit(1); /*NOTREACHED*/ case SIGCLD: case SIGPWR: case SIGWINCH: case SIGURG: case SIGCONT: case SIGWAITING: case SIGLWP: case SIGFREEZE: case SIGTHAW: case SIGCANCEL: case SIGXRES: case SIGJVM1: case SIGJVM2: case SIGINFO: /* No need to abort */ break; default: syseventd_err_print(FATAL_ERROR); abort(); } } /* * Daemon parent process only. * Child process signal to indicate successful daemon initialization. * This is the normal and expected exit path of the daemon parent. */ /*ARGSUSED*/ static void sigusr1(int sig) { syseventd_exit(0); } static void * sigwait_thr(void *arg __unused) { int sig; int err; sigset_t signal_set; for (;;) { syseventd_print(3, "sigwait thread waiting for signal\n"); (void) sigfillset(&signal_set); err = sigwait(&signal_set, &sig); if (err) { syseventd_exit(2); } /* * Block all signals until the signal handler completes */ if (sig == SIGHUP) { hup_handler(sig); } else { flt_handler(sig); } } return (NULL); } static void set_root_dir(char *dir) { root_dir = malloc(strlen(dir) + 1); if (root_dir == NULL) { syseventd_err_print(INIT_ROOT_DIR_ERR, strerror(errno)); syseventd_exit(2); } (void) strcpy(root_dir, dir); } int main(int argc, char **argv) { int i, c; int fd; pid_t pid; int has_forked = 0; extern char *optarg; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); if (getuid() != 0) { (void) fprintf(stderr, "Must be root to run syseventd\n"); syseventd_exit(1); } if (argc > 5) { usage(); } if ((prog = strrchr(argv[0], '/')) == NULL) { prog = argv[0]; } else { prog++; } while ((c = getopt(argc, argv, "d:r:")) != EOF) { switch (c) { case 'd': debug_level = atoi(optarg); break; case 'r': /* * Private flag for suninstall to run * daemon during install. */ set_root_dir(optarg); break; case '?': default: usage(); } } /* demonize ourselves */ if (debug_level < DEBUG_LEVEL_FORK) { sigset_t mask; (void) sigset(SIGUSR1, sigusr1); (void) sigemptyset(&mask); (void) sigaddset(&mask, SIGUSR1); (void) sigprocmask(SIG_BLOCK, &mask, NULL); if ((pid = fork()) == (pid_t)-1) { (void) fprintf(stderr, "syseventd: fork failed - %s\n", strerror(errno)); syseventd_exit(1); } if (pid != 0) { /* * parent * handshake with the daemon so that dependents * of the syseventd service don't start up until * the service is actually functional */ int status; (void) sigprocmask(SIG_UNBLOCK, &mask, NULL); if (waitpid(pid, &status, 0) != pid) { /* * child process signal indicating * successful daemon initialization */ syseventd_exit(0); } /* child exited implying unsuccessful startup */ syseventd_exit(1); } /* child */ has_forked = 1; (void) sigset(SIGUSR1, SIG_DFL); (void) sigprocmask(SIG_UNBLOCK, &mask, NULL); (void) chdir("/"); (void) setsid(); if (debug_level <= 1) { closefrom(0); fd = open("/dev/null", 0); (void) dup2(fd, 1); (void) dup2(fd, 2); logflag = 1; } } openlog("syseventd", LOG_PID, LOG_DAEMON); (void) mutex_init(&err_mutex, USYNC_THREAD, NULL); syseventd_print(8, "syseventd started, debug level = %d\n", debug_level); /* only one instance of syseventd can run at a time */ if ((pid = enter_daemon_lock()) != getpid()) { syseventd_print(1, "event daemon pid %ld already running\n", pid); exit(3); } /* initialize semaphores and eventbuf */ (void) sema_init(&sema_eventbuf, SE_EVENT_DISPATCH_CNT, USYNC_THREAD, NULL); (void) sema_init(&sema_dispatch, 0, USYNC_THREAD, NULL); (void) sema_init(&sema_resource, SE_EVENT_DISPATCH_CNT, USYNC_THREAD, NULL); (void) cond_init(&event_comp_cv, USYNC_THREAD, NULL); eventbuf = (sysevent_t **)calloc(SE_EVENT_DISPATCH_CNT, sizeof (sysevent_t *)); if (eventbuf == NULL) { syseventd_print(1, "Unable to allocate event buffer array\n"); exit(2); } for (i = 0; i < SE_EVENT_DISPATCH_CNT; ++i) { eventbuf[i] = malloc(LOGEVENT_BUFSIZE); if (eventbuf[i] == NULL) { syseventd_print(1, "Unable to allocate event " "buffers\n"); exit(2); } } (void) mutex_init(&client_tbl_lock, USYNC_THREAD, NULL); (void) mutex_init(&ev_comp_lock, USYNC_THREAD, NULL); (void) mutex_init(&door_lock, USYNC_THREAD, NULL); (void) rwlock_init(&mod_unload_lock, USYNC_THREAD, NULL); event_compq = NULL; syseventd_print(8, "start the message thread running\n"); /* * Block all signals to all threads include the main thread. * The sigwait_thr thread will process any signals and initiate * a graceful recovery if possible. */ if (se_signal_blockall() < 0) { syseventd_err_print(INIT_SIG_BLOCK_ERR); syseventd_exit(2); } if (thr_create(NULL, 0, (void *(*)(void *))dispatch_message, (void *)0, 0, NULL) < 0) { syseventd_err_print(INIT_THR_CREATE_ERR, strerror(errno)); syseventd_exit(2); } if (thr_create(NULL, 0, (void *(*)(void *))event_completion_thr, NULL, THR_BOUND, NULL) != 0) { syseventd_err_print(INIT_THR_CREATE_ERR, strerror(errno)); syseventd_exit(2); } /* Create signal catching thread */ if (thr_create(NULL, 0, sigwait_thr, NULL, 0, NULL) < 0) { syseventd_err_print(INIT_THR_CREATE_ERR, strerror(errno)); syseventd_exit(2); } setbuf(stdout, (char *)NULL); /* Initialize and load SLM clients */ initialize_client_tbl(); syseventd_init(); /* signal parent to indicate successful daemon initialization */ if (has_forked) { if (kill(getppid(), SIGUSR1) != 0) { syseventd_err_print( "signal to the parent failed - %s\n", strerror(errno)); syseventd_exit(2); } } syseventd_print(8, "Pausing\n"); for (;;) { (void) pause(); } /* NOTREACHED */ return (0); } /* * door_upcall - called from the kernel via kernel sysevent door * to upload event(s). * * This routine should never block. If resources are * not available to immediately accept the event buffer * EAGAIN is returned to the kernel. * * Once resources are available, the kernel is notified * via a modctl interface to resume event delivery to * syseventd. * */ /*ARGSUSED*/ static void door_upcall(void *cookie, char *args, size_t alen, door_desc_t *ddp, uint_t ndid) { sysevent_t *ev; int rval; (void) mutex_lock(&door_lock); if (args == NULL) { rval = EINVAL; } else if (sema_trywait(&sema_eventbuf)) { ev = (sysevent_t *) &((log_event_upcall_arg_t *)(void *)args)->buf; syseventd_print(2, "door_upcall: busy event %llx " "retry\n", sysevent_get_seq(ev)); rval = door_upcall_retval = EAGAIN; } else { /* * Copy received message to local buffer. */ size_t size; ev = (sysevent_t *) &((log_event_upcall_arg_t *)(void *)args)->buf; syseventd_print(2, "door_upcall: event %llx in eventbuf %d\n", sysevent_get_seq(ev), deliver_buf); size = sysevent_get_size(ev) > LOGEVENT_BUFSIZE ? LOGEVENT_BUFSIZE : sysevent_get_size(ev); (void) bcopy(ev, eventbuf[deliver_buf], size); deliver_buf = (deliver_buf + 1) % SE_EVENT_DISPATCH_CNT; rval = 0; (void) sema_post(&sema_dispatch); } (void) mutex_unlock(&door_lock); /* * Filling in return values for door_return */ (void) door_return((void *)&rval, sizeof (rval), NULL, 0); (void) door_return(NULL, 0, NULL, 0); } /* * dispatch_message - dispatch message thread * This thread spins until an event buffer is delivered * delivered from the kernel. * * It will wait to dispatch an event to any clients * until adequate resources are available to process * the event buffer. */ static void dispatch_message(void) { int error; for (;;) { syseventd_print(3, "dispatch_message: thread started\n"); /* * Spin till a message comes */ while (sema_wait(&sema_dispatch) != 0) { syseventd_print(1, "dispatch_message: sema_wait failed\n"); (void) sleep(1); } syseventd_print(3, "dispatch_message: sema_dispatch\n"); /* * Wait for available resources */ while (sema_wait(&sema_resource) != 0) { syseventd_print(1, "dispatch_message: sema_wait " "failed\n"); (void) sleep(1); } syseventd_print(2, "dispatch_message: eventbuf %d\n", dispatch_buf); /* * Client dispatch */ do { error = dispatch(); } while (error == EAGAIN); syseventd_print(2, "eventbuf %d dispatched\n", dispatch_buf); dispatch_buf = (dispatch_buf + 1) % SE_EVENT_DISPATCH_CNT; /* * kernel received a busy signal - * kickstart the kernel delivery thread * door_lock blocks the kernel so we hold it for the * shortest time possible. */ (void) mutex_lock(&door_lock); if (door_upcall_retval == EAGAIN && !fini_pending) { syseventd_print(3, "dispatch_message: retrigger " "door_upcall_retval = %d\n", door_upcall_retval); (void) modctl(MODEVENTS, (uintptr_t)MODEVENTS_FLUSH, NULL, NULL, NULL, 0); door_upcall_retval = 0; } (void) mutex_unlock(&door_lock); } /* NOTREACHED */ } /* * drain_eventq - Called to drain all pending events from the client's * event queue. */ static void drain_eventq(struct sysevent_client *scp, int status) { struct event_dispatch_pkg *d_pkg; struct event_dispatchq *eventq, *eventq_next; syseventd_print(3, "Draining eventq for client %d\n", scp->client_num); eventq = scp->eventq; while (eventq) { /* * Mark all dispatched events as completed, but indicate the * error status */ d_pkg = eventq->d_pkg; syseventd_print(4, "drain event 0X%llx for client %d\n", sysevent_get_seq(d_pkg->ev), scp->client_num); if (d_pkg->completion_state == SE_NOT_DISPATCHED) { d_pkg->completion_status = status; d_pkg->completion_state = SE_COMPLETE; (void) sema_post(d_pkg->completion_sema); } eventq_next = eventq->next; free(eventq); eventq = eventq_next; scp->eventq = eventq; } } /* * client_deliver_event_thr - Client delivery thread * This thread will process any events on this * client's eventq. */ static void * client_deliver_event_thr(void *arg) { int flag, error, i; sysevent_t *ev; hrtime_t now; module_t *mod; struct event_dispatchq *eventq; struct sysevent_client *scp; struct event_dispatch_pkg *d_pkg; scp = (struct sysevent_client *)arg; mod = (module_t *)scp->client_data; (void) mutex_lock(&scp->client_lock); for (;;) { while (scp->eventq == NULL) { /* * Client has been suspended or unloaded, go no further. */ if (fini_pending) { scp->client_flags &= ~SE_CLIENT_THR_RUNNING; syseventd_print(3, "Client %d delivery thread " "exiting flags: 0X%x\n", scp->client_num, scp->client_flags); (void) mutex_unlock(&scp->client_lock); return (NULL); } (void) cond_wait(&scp->client_cv, &scp->client_lock); } /* * Process events from the head of the eventq, eventq is locked * going into the processing. */ eventq = scp->eventq; while (eventq != NULL) { d_pkg = eventq->d_pkg; d_pkg->completion_state = SE_OUTSTANDING; scp->eventq = eventq->next; free(eventq); (void) mutex_unlock(&scp->client_lock); flag = error = 0; ev = d_pkg->ev; syseventd_print(3, "Start delivery for client %d " "with retry count %d\n", scp->client_num, d_pkg->retry_count); /* * Retry limit has been reached by this client, indicate * that no further retries are allowed */ for (i = 0; i <= scp->retry_limit; ++i) { if (i == scp->retry_limit) flag = SE_NO_RETRY; /* Start the clock for the event delivery */ d_pkg->start_time = gethrtime(); syseventd_print(9, "Deliver to module client " "%s\n", mod->name); error = mod->deliver_event(ev, flag); /* Can not allow another retry */ if (i == scp->retry_limit) error = 0; /* Stop the clock */ now = gethrtime(); /* * Suspend event processing and drain the * event q for latent clients */ if (now - d_pkg->start_time > ((hrtime_t)SE_TIMEOUT * NANOSEC)) { syseventd_print(1, "Unresponsive " "client %d: Draining eventq and " "suspending event delivery\n", scp->client_num); (void) mutex_lock(&scp->client_lock); scp->client_flags &= ~SE_CLIENT_THR_RUNNING; scp->client_flags |= SE_CLIENT_SUSPENDED; /* Cleanup current event */ d_pkg->completion_status = EFAULT; d_pkg->completion_state = SE_COMPLETE; (void) sema_post( d_pkg->completion_sema); /* * Drain the remaining events from the * queue. */ drain_eventq(scp, EINVAL); (void) mutex_unlock(&scp->client_lock); return (NULL); } /* Event delivery retry requested */ if (fini_pending || error != EAGAIN) { break; } else { (void) sleep(SE_RETRY_TIME); } } (void) mutex_lock(&scp->client_lock); d_pkg->completion_status = error; d_pkg->completion_state = SE_COMPLETE; (void) sema_post(d_pkg->completion_sema); syseventd_print(3, "Completed delivery with " "error %d\n", error); eventq = scp->eventq; } syseventd_print(3, "No more events to process for client %d\n", scp->client_num); /* Return if this was a synchronous delivery */ if (!SE_CLIENT_IS_THR_RUNNING(scp)) { (void) mutex_unlock(&scp->client_lock); return (NULL); } } } /* * client_deliver_event - Client specific event delivery * This routine will allocate and initialize the * neccessary per-client dispatch data. * * If the eventq is not empty, it may be assumed that * a delivery thread exists for this client and the * dispatch data is appended to the eventq. * * The dispatch package is freed by the event completion * thread (event_completion_thr) and the eventq entry * is freed by the event delivery thread. */ static struct event_dispatch_pkg * client_deliver_event(struct sysevent_client *scp, sysevent_t *ev, sema_t *completion_sema) { size_t ev_sz = sysevent_get_size(ev); struct event_dispatchq *newq, *tmp; struct event_dispatch_pkg *d_pkg; syseventd_print(3, "client_deliver_event: id 0x%llx size %d\n", (longlong_t)sysevent_get_seq(ev), ev_sz); if (debug_level == 9) { se_print(stdout, ev); } /* * Check for suspended client */ (void) mutex_lock(&scp->client_lock); if (SE_CLIENT_IS_SUSPENDED(scp) || !SE_CLIENT_IS_THR_RUNNING(scp)) { (void) mutex_unlock(&scp->client_lock); return (NULL); } /* * Allocate a new dispatch package and eventq entry */ newq = (struct event_dispatchq *)malloc( sizeof (struct event_dispatchq)); if (newq == NULL) { (void) mutex_unlock(&scp->client_lock); return (NULL); } d_pkg = (struct event_dispatch_pkg *)malloc( sizeof (struct event_dispatch_pkg)); if (d_pkg == NULL) { free(newq); (void) mutex_unlock(&scp->client_lock); return (NULL); } /* Initialize the dispatch package */ d_pkg->scp = scp; d_pkg->retry_count = 0; d_pkg->completion_status = 0; d_pkg->completion_state = SE_NOT_DISPATCHED; d_pkg->completion_sema = completion_sema; d_pkg->ev = ev; newq->d_pkg = d_pkg; newq->next = NULL; if (scp->eventq != NULL) { /* Add entry to the end of the eventq */ tmp = scp->eventq; while (tmp->next != NULL) tmp = tmp->next; tmp->next = newq; } else { /* event queue empty, wakeup delivery thread */ scp->eventq = newq; (void) cond_signal(&scp->client_cv); } (void) mutex_unlock(&scp->client_lock); return (d_pkg); } /* * event_completion_thr - Event completion thread. This thread routine * waits for all client delivery thread to complete * delivery of a particular event. */ static void event_completion_thr() { int ret, i, client_count, ok_to_free; sysevent_id_t eid; struct sysevent_client *scp; struct ev_completion *ev_comp; struct event_dispatchq *dispatchq; struct event_dispatch_pkg *d_pkg; (void) mutex_lock(&ev_comp_lock); for (;;) { while (event_compq == NULL) { (void) cond_wait(&event_comp_cv, &ev_comp_lock); } /* * Process event completions from the head of the * completion queue */ ev_comp = event_compq; while (ev_comp) { (void) mutex_unlock(&ev_comp_lock); eid.eid_seq = sysevent_get_seq(ev_comp->ev); sysevent_get_time(ev_comp->ev, &eid.eid_ts); client_count = ev_comp->client_count; ok_to_free = 1; syseventd_print(3, "Wait for event completion of " "event 0X%llx on %d clients\n", eid.eid_seq, client_count); while (client_count) { syseventd_print(9, "Waiting for %d clients on " "event id 0X%llx\n", client_count, eid.eid_seq); (void) sema_wait(&ev_comp->client_sema); --client_count; } syseventd_print(3, "Cleaning up clients for event " "0X%llx\n", eid.eid_seq); dispatchq = ev_comp->dispatch_list; while (dispatchq != NULL) { d_pkg = dispatchq->d_pkg; scp = d_pkg->scp; if (d_pkg->completion_status == EAGAIN) ok_to_free = 0; syseventd_print(4, "Delivery of 0X%llx " "complete for client %d retry count %d " "status %d\n", eid.eid_seq, scp->client_num, d_pkg->retry_count, d_pkg->completion_status); free(d_pkg); ev_comp->dispatch_list = dispatchq->next; free(dispatchq); dispatchq = ev_comp->dispatch_list; } if (ok_to_free) { for (i = 0; i < MAX_MODCTL_RETRY; ++i) { if ((ret = modctl(MODEVENTS, (uintptr_t)MODEVENTS_FREEDATA, (uintptr_t)&eid, NULL, NULL, 0)) != 0) { syseventd_print(1, "attempting " "to free event 0X%llx\n", eid.eid_seq); /* * Kernel may need time to * move this event buffer to * the sysevent sent queue */ (void) sleep(1); } else { break; } } if (ret) { syseventd_print(1, "Unable to free " "event 0X%llx from the " "kernel\n", eid.eid_seq); } } else { syseventd_print(1, "Not freeing event 0X%llx\n", eid.eid_seq); } syseventd_print(2, "Event delivery complete for id " "0X%llx\n", eid.eid_seq); (void) mutex_lock(&ev_comp_lock); event_compq = ev_comp->next; free(ev_comp->ev); free(ev_comp); ev_comp = event_compq; (void) sema_post(&sema_resource); } /* * Event completion queue is empty, signal possible unload * operation */ (void) cond_signal(&event_comp_cv); syseventd_print(3, "No more events\n"); } } /* * dispatch - Dispatch the current event buffer to all valid SLM clients. */ static int dispatch(void) { int ev_sz, i, client_count = 0; sysevent_t *new_ev; sysevent_id_t eid; struct ev_completion *ev_comp, *tmp; struct event_dispatchq *dispatchq, *client_list; struct event_dispatch_pkg *d_pkg; /* Check for module unload operation */ if (rw_tryrdlock(&mod_unload_lock) != 0) { syseventd_print(2, "unload in progress abort delivery\n"); (void) sema_post(&sema_eventbuf); (void) sema_post(&sema_resource); return (0); } syseventd_print(3, "deliver dispatch buffer %d", dispatch_buf); eid.eid_seq = sysevent_get_seq(eventbuf[dispatch_buf]); sysevent_get_time(eventbuf[dispatch_buf], &eid.eid_ts); syseventd_print(3, "deliver msg id: 0x%llx\n", eid.eid_seq); /* * ev_comp is used to hold event completion data. It is freed * by the event completion thread (event_completion_thr). */ ev_comp = (struct ev_completion *) malloc(sizeof (struct ev_completion)); if (ev_comp == NULL) { (void) rw_unlock(&mod_unload_lock); syseventd_print(1, "Can not allocate event completion buffer " "for event id 0X%llx\n", eid.eid_seq); return (EAGAIN); } ev_comp->dispatch_list = NULL; ev_comp->next = NULL; (void) sema_init(&ev_comp->client_sema, 0, USYNC_THREAD, NULL); ev_sz = sysevent_get_size(eventbuf[dispatch_buf]); new_ev = calloc(1, ev_sz); if (new_ev == NULL) { free(ev_comp); (void) rw_unlock(&mod_unload_lock); syseventd_print(1, "Can not allocate new event buffer " "for event id 0X%llx\n", eid.eid_seq); return (EAGAIN); } /* * For long messages, copy additional data from kernel */ if (ev_sz > LOGEVENT_BUFSIZE) { int ret = 0; /* Ok to release eventbuf for next event buffer from kernel */ (void) sema_post(&sema_eventbuf); for (i = 0; i < MAX_MODCTL_RETRY; ++i) { if ((ret = modctl(MODEVENTS, (uintptr_t)MODEVENTS_GETDATA, (uintptr_t)&eid, (uintptr_t)ev_sz, (uintptr_t)new_ev, 0)) == 0) break; else (void) sleep(1); } if (ret) { syseventd_print(1, "GET_DATA failed for 0X%llx:%llx\n", eid.eid_ts, eid.eid_seq); free(new_ev); free(ev_comp); (void) rw_unlock(&mod_unload_lock); return (EAGAIN); } } else { (void) bcopy(eventbuf[dispatch_buf], new_ev, ev_sz); /* Ok to release eventbuf for next event buffer from kernel */ (void) sema_post(&sema_eventbuf); } /* * Deliver a copy of eventbuf to clients so * eventbuf can be used for the next message */ for (i = 0; i < MAX_SLM; ++i) { /* Don't bother for suspended or unloaded clients */ if (!SE_CLIENT_IS_LOADED(sysevent_client_tbl[i]) || SE_CLIENT_IS_SUSPENDED(sysevent_client_tbl[i])) continue; /* * Allocate event dispatch queue entry. All queue entries * are freed by the event completion thread as client * delivery completes. */ dispatchq = (struct event_dispatchq *)malloc( sizeof (struct event_dispatchq)); if (dispatchq == NULL) { syseventd_print(1, "Can not allocate dispatch q " "for event id 0X%llx client %d\n", eid.eid_seq, i); continue; } dispatchq->next = NULL; /* Initiate client delivery */ d_pkg = client_deliver_event(sysevent_client_tbl[i], new_ev, &ev_comp->client_sema); if (d_pkg == NULL) { syseventd_print(1, "Can not allocate dispatch " "package for event id 0X%llx client %d\n", eid.eid_seq, i); free(dispatchq); continue; } dispatchq->d_pkg = d_pkg; ++client_count; if (ev_comp->dispatch_list == NULL) { ev_comp->dispatch_list = dispatchq; client_list = dispatchq; } else { client_list->next = dispatchq; client_list = client_list->next; } } ev_comp->client_count = client_count; ev_comp->ev = new_ev; (void) mutex_lock(&ev_comp_lock); if (event_compq == NULL) { syseventd_print(3, "Wakeup event completion thread for " "id 0X%llx\n", eid.eid_seq); event_compq = ev_comp; (void) cond_signal(&event_comp_cv); } else { /* Add entry to the end of the event completion queue */ tmp = event_compq; while (tmp->next != NULL) tmp = tmp->next; tmp->next = ev_comp; syseventd_print(3, "event added to completion queue for " "id 0X%llx\n", eid.eid_seq); } (void) mutex_unlock(&ev_comp_lock); (void) rw_unlock(&mod_unload_lock); return (0); } #define MODULE_DIR_HW "/usr/platform/%s/lib/sysevent/modules/" #define MODULE_DIR_GEN "/usr/lib/sysevent/modules/" #define MOD_DIR_NUM 3 static char dirname[MOD_DIR_NUM][MAXPATHLEN]; static char * dir_num2name(int dirnum) { char infobuf[MAXPATHLEN]; if (dirnum >= MOD_DIR_NUM) return (NULL); if (dirname[0][0] == '\0') { if (sysinfo(SI_PLATFORM, infobuf, MAXPATHLEN) == -1) { syseventd_print(1, "dir_num2name: " "sysinfo error %s\n", strerror(errno)); return (NULL); } else if (snprintf(dirname[0], sizeof (dirname[0]), MODULE_DIR_HW, infobuf) >= sizeof (dirname[0])) { syseventd_print(1, "dir_num2name: " "platform name too long: %s\n", infobuf); return (NULL); } if (sysinfo(SI_MACHINE, infobuf, MAXPATHLEN) == -1) { syseventd_print(1, "dir_num2name: " "sysinfo error %s\n", strerror(errno)); return (NULL); } else if (snprintf(dirname[1], sizeof (dirname[1]), MODULE_DIR_HW, infobuf) >= sizeof (dirname[1])) { syseventd_print(1, "dir_num2name: " "machine name too long: %s\n", infobuf); return (NULL); } (void) strcpy(dirname[2], MODULE_DIR_GEN); } return (dirname[dirnum]); } /* * load_modules - Load modules found in the common syseventd module directories * Modules that do not provide valid interfaces are rejected. */ static void load_modules(char *dirname) { int client_id; DIR *mod_dir; module_t *mod; struct dirent *entp; struct slm_mod_ops *mod_ops; struct sysevent_client *scp; if (dirname == NULL) return; /* Return silently if module directory does not exist */ if ((mod_dir = opendir(dirname)) == NULL) { syseventd_print(1, "Unable to open module directory %s: %s\n", dirname, strerror(errno)); return; } syseventd_print(3, "loading modules from %s\n", dirname); /* * Go through directory, looking for files ending with .so */ while ((entp = readdir(mod_dir)) != NULL) { void *dlh, *f; char *tmp, modpath[MAXPATHLEN]; if (((tmp = strstr(entp->d_name, MODULE_SUFFIX)) == NULL) || (tmp[strlen(MODULE_SUFFIX)] != '\0')) { continue; } if (snprintf(modpath, sizeof (modpath), "%s%s", dirname, entp->d_name) >= sizeof (modpath)) { syseventd_err_print(INIT_PATH_ERR, modpath); continue; } if ((dlh = dlopen(modpath, RTLD_LAZY)) == NULL) { syseventd_err_print(LOAD_MOD_DLOPEN_ERR, modpath, dlerror()); continue; } else if ((f = dlsym(dlh, EVENT_INIT)) == NULL) { syseventd_err_print(LOAD_MOD_NO_INIT, modpath, dlerror()); (void) dlclose(dlh); continue; } mod = malloc(sizeof (*mod)); if (mod == NULL) { syseventd_err_print(LOAD_MOD_ALLOC_ERR, "mod", strerror(errno)); (void) dlclose(dlh); continue; } mod->name = strdup(entp->d_name); if (mod->name == NULL) { syseventd_err_print(LOAD_MOD_ALLOC_ERR, "mod->name", strerror(errno)); (void) dlclose(dlh); free(mod); continue; } mod->dlhandle = dlh; mod->event_mod_init = (struct slm_mod_ops *(*)())f; /* load in other module functions */ mod->event_mod_fini = (void (*)())dlsym(dlh, EVENT_FINI); if (mod->event_mod_fini == NULL) { syseventd_err_print(LOAD_MOD_DLSYM_ERR, mod->name, dlerror()); free(mod->name); free(mod); (void) dlclose(dlh); continue; } /* Call module init routine */ if ((mod_ops = mod->event_mod_init()) == NULL) { syseventd_err_print(LOAD_MOD_EINVAL, mod->name); free(mod->name); free(mod); (void) dlclose(dlh); continue; } if (mod_ops->major_version != SE_MAJOR_VERSION) { syseventd_err_print(LOAD_MOD_VERSION_MISMATCH, mod->name, SE_MAJOR_VERSION, mod_ops->major_version); mod->event_mod_fini(); free(mod->name); free(mod); (void) dlclose(dlh); continue; } mod->deliver_event = mod_ops->deliver_event; /* Add module entry to client list */ if ((client_id = insert_client((void *)mod, SLM_CLIENT, (mod_ops->retry_limit <= SE_MAX_RETRY_LIMIT ? mod_ops->retry_limit : SE_MAX_RETRY_LIMIT))) < 0) { syseventd_err_print(LOAD_MOD_ALLOC_ERR, "insert_client", strerror(errno)); mod->event_mod_fini(); free(mod->name); free(mod); (void) dlclose(dlh); continue; } scp = sysevent_client_tbl[client_id]; ++concurrency_level; (void) thr_setconcurrency(concurrency_level); if (thr_create(NULL, 0, client_deliver_event_thr, scp, THR_BOUND, &scp->tid) != 0) { syseventd_err_print(LOAD_MOD_ALLOC_ERR, "insert_client", strerror(errno)); mod->event_mod_fini(); free(mod->name); free(mod); (void) dlclose(dlh); continue; } scp->client_flags |= SE_CLIENT_THR_RUNNING; syseventd_print(3, "loaded module %s\n", entp->d_name); } (void) closedir(mod_dir); syseventd_print(3, "modules loaded\n"); } /* * unload_modules - modules are unloaded prior to graceful shutdown or * before restarting the daemon upon receipt of * SIGHUP. */ static void unload_modules(int sig) { int i, count, done; module_t *mod; struct sysevent_client *scp; /* * unload modules that are ready, skip those that have not * drained their event queues. */ count = done = 0; while (done < MAX_SLM) { /* Don't wait indefinitely for unresponsive clients */ if (sig != SIGHUP && count > SE_TIMEOUT) { break; } done = 0; /* Shutdown clients */ for (i = 0; i < MAX_SLM; ++i) { scp = sysevent_client_tbl[i]; if (mutex_trylock(&scp->client_lock) == 0) { if (scp->client_type != SLM_CLIENT || scp->client_data == NULL) { (void) mutex_unlock(&scp->client_lock); done++; continue; } } else { syseventd_print(3, "Skipping unload of " "client %d: client locked\n", scp->client_num); continue; } /* * Drain the eventq and wait for delivery thread to * cleanly exit */ drain_eventq(scp, EAGAIN); (void) cond_signal(&scp->client_cv); (void) mutex_unlock(&scp->client_lock); (void) thr_join(scp->tid, NULL, NULL); /* * It is now safe to unload the module */ mod = (module_t *)scp->client_data; syseventd_print(2, "Unload %s\n", mod->name); mod->event_mod_fini(); (void) dlclose(mod->dlhandle); free(mod->name); (void) mutex_lock(&client_tbl_lock); delete_client(i); (void) mutex_unlock(&client_tbl_lock); ++done; } ++count; (void) sleep(1); } /* * Wait for event completions */ syseventd_print(2, "waiting for event completions\n"); (void) mutex_lock(&ev_comp_lock); while (event_compq != NULL) { (void) cond_wait(&event_comp_cv, &ev_comp_lock); } (void) mutex_unlock(&ev_comp_lock); } /* * syseventd_init - Called at daemon (re)start-up time to load modules * and kickstart the kernel delivery engine. */ static void syseventd_init() { int i, fd; char local_door_file[PATH_MAX + 1]; fini_pending = 0; concurrency_level = MIN_CONCURRENCY_LEVEL; (void) thr_setconcurrency(concurrency_level); /* * Load client modules for event delivering */ for (i = 0; i < MOD_DIR_NUM; ++i) { load_modules(dir_num2name(i)); } /* * Create kernel delivery door service */ syseventd_print(8, "Create a door for kernel upcalls\n"); if (snprintf(local_door_file, sizeof (local_door_file), "%s%s", root_dir, LOGEVENT_DOOR_UPCALL) >= sizeof (local_door_file)) { syseventd_err_print(INIT_PATH_ERR, local_door_file); syseventd_exit(5); } /* * Remove door file for robustness. */ if (unlink(local_door_file) != 0) syseventd_print(8, "Unlink of %s failed.\n", local_door_file); fd = open(local_door_file, O_CREAT|O_RDWR, S_IREAD|S_IWRITE); if ((fd == -1) && (errno != EEXIST)) { syseventd_err_print(INIT_OPEN_DOOR_ERR, strerror(errno)); syseventd_exit(5); } (void) close(fd); upcall_door = door_create(door_upcall, NULL, DOOR_REFUSE_DESC | DOOR_NO_CANCEL); if (upcall_door == -1) { syseventd_err_print(INIT_CREATE_DOOR_ERR, strerror(errno)); syseventd_exit(5); } (void) fdetach(local_door_file); retry: if (fattach(upcall_door, local_door_file) != 0) { if (errno == EBUSY) goto retry; syseventd_err_print(INIT_FATTACH_ERR, strerror(errno)); (void) door_revoke(upcall_door); syseventd_exit(5); } /* * Tell kernel the door name and start delivery */ syseventd_print(2, "local_door_file = %s\n", local_door_file); if (modctl(MODEVENTS, (uintptr_t)MODEVENTS_SET_DOOR_UPCALL_FILENAME, (uintptr_t)local_door_file, NULL, NULL, 0) < 0) { syseventd_err_print(INIT_DOOR_NAME_ERR, strerror(errno)); syseventd_exit(6); } door_upcall_retval = 0; if (modctl(MODEVENTS, (uintptr_t)MODEVENTS_FLUSH, NULL, NULL, NULL, 0) < 0) { syseventd_err_print(KERNEL_REPLAY_ERR, strerror(errno)); syseventd_exit(7); } } /* * syseventd_fini - shut down daemon, but do not exit */ static void syseventd_fini(int sig) { /* * Indicate that event queues should be drained and no * additional events be accepted */ fini_pending = 1; /* Close the kernel event door to halt delivery */ (void) door_revoke(upcall_door); syseventd_print(1, "Unloading modules\n"); (void) rw_wrlock(&mod_unload_lock); unload_modules(sig); (void) rw_unlock(&mod_unload_lock); } /* * enter_daemon_lock - lock the daemon file lock * * Use an advisory lock to ensure that only one daemon process is active * in the system at any point in time. If the lock is held by another * process, do not block but return the pid owner of the lock to the * caller immediately. The lock is cleared if the holding daemon process * exits for any reason even if the lock file remains, so the daemon can * be restarted if necessary. The lock file is DAEMON_LOCK_FILE. */ static pid_t enter_daemon_lock(void) { struct flock lock; syseventd_print(8, "enter_daemon_lock: lock file = %s\n", DAEMON_LOCK_FILE); if (snprintf(local_lock_file, sizeof (local_lock_file), "%s%s", root_dir, DAEMON_LOCK_FILE) >= sizeof (local_lock_file)) { syseventd_err_print(INIT_PATH_ERR, local_lock_file); syseventd_exit(8); } daemon_lock_fd = open(local_lock_file, O_CREAT|O_RDWR, 0644); if (daemon_lock_fd < 0) { syseventd_err_print(INIT_LOCK_OPEN_ERR, local_lock_file, strerror(errno)); syseventd_exit(8); } lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; if (fcntl(daemon_lock_fd, F_SETLK, &lock) == -1) { if (fcntl(daemon_lock_fd, F_GETLK, &lock) == -1) { syseventd_err_print(INIT_LOCK_ERR, local_lock_file, strerror(errno)); exit(2); } return (lock.l_pid); } hold_daemon_lock = 1; return (getpid()); } /* * exit_daemon_lock - release the daemon file lock */ static void exit_daemon_lock(void) { struct flock lock; lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; if (fcntl(daemon_lock_fd, F_SETLK, &lock) == -1) { syseventd_err_print(INIT_UNLOCK_ERR, local_lock_file, strerror(errno)); } if (close(daemon_lock_fd) == -1) { syseventd_err_print(INIT_LOCK_CLOSE_ERR, local_lock_file, strerror(errno)); exit(-1); } } /* * syseventd_err_print - print error messages to the terminal if not * yet daemonized or to syslog. */ /*PRINTFLIKE1*/ void syseventd_err_print(char *message, ...) { va_list ap; (void) mutex_lock(&err_mutex); va_start(ap, message); if (logflag) { (void) vsyslog(LOG_ERR, message, ap); } else { (void) fprintf(stderr, "%s: ", prog); (void) vfprintf(stderr, message, ap); } va_end(ap); (void) mutex_unlock(&err_mutex); } /* * syseventd_print - print messages to the terminal or to syslog * the following levels are implemented: * * 1 - transient errors that does not affect normal program flow * 2 - upcall/dispatch interaction * 3 - program flow trace as each message goes through the daemon * 8 - all the nit-gritty details of startup and shutdown * 9 - very verbose event flow tracing (no daemonization of syseventd) * */ /*PRINTFLIKE2*/ void syseventd_print(int level, char *message, ...) { va_list ap; static int newline = 1; if (level > debug_level) { return; } (void) mutex_lock(&err_mutex); va_start(ap, message); if (logflag) { (void) syslog(LOG_DEBUG, "%s[%ld]: ", prog, getpid()); (void) vsyslog(LOG_DEBUG, message, ap); } else { if (newline) { (void) fprintf(stdout, "%s[%ld]: ", prog, getpid()); (void) vfprintf(stdout, message, ap); } else { (void) vfprintf(stdout, message, ap); } } if (message[strlen(message)-1] == '\n') { newline = 1; } else { newline = 0; } va_end(ap); (void) mutex_unlock(&err_mutex); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _SYSEVENTD_H #define _SYSEVENTD_H #ifdef __cplusplus extern "C" { #endif #define MAX_SLM 20 /* Max sysevent loadable modules */ #define MAX_MODCTL_RETRY 3 /* Maximum number of modctl retries */ /* for free and get data */ #define LOGEVENT_BUFSIZE 1024 /* message size greater than this */ /* requires another kernel trip */ /* Debug and errlogging stuff */ extern void syseventd_print(int level, char *message, ...); extern void syseventd_err_print(char *message, ...); extern void syseventd_exit(int status); extern int debug_level; /* * struct event_dispatch_pkg - per-client event dispatch package */ struct event_dispatch_pkg { hrtime_t start_time; /* start time ev delivery */ struct sysevent_client *scp; /* Client data */ sysevent_t *ev; /* event buf to deliver */ sema_t *completion_sema; int completion_state; int completion_status; int retry_count; /* running retry counter */ }; /* completion states */ #define SE_NOT_DISPATCHED 0 /* Not yet dispatched to client */ #define SE_OUTSTANDING 1 /* Dispatched and outstanding */ #define SE_COMPLETE 2 /* Delivery complete */ /* * struct event_dispatchq - queue of dispatched event dispatch pacakges */ struct event_dispatchq { struct event_dispatchq *next; struct event_dispatch_pkg *d_pkg; }; /* * struct ev_completion - event completion data * This structure contains information regarding the * event delivery status for each client dispatched. */ struct ev_completion { sysevent_t *ev; /* event */ struct ev_completion *next; struct event_dispatchq *dispatch_list; /* per_client dspatch packages */ int client_count; /* Number of clients */ /* event was dispatched to. */ sema_t client_sema; /* Client completion */ /* synchronization */ }; /* * module_t - SLM client module data */ typedef struct module { struct module *next; char *name; void *dlhandle; int (*deliver_event)(); struct slm_mod_ops *(*event_mod_init)(); void (*event_mod_fini)(); } module_t; /* * struct sysevent_client - per-client data */ struct sysevent_client { mutex_t client_lock; /* Lock for struct data */ cond_t client_cv; /* Deliver cond variable */ thread_t tid; /* Client deliver thread id */ int client_type; /* SLM only */ int client_num; /* Assigned at load time */ int client_flags; /* Client flags */ int retry_limit; /* Defined by slm_mod_ops */ void *client_data; /* Client-type specific data */ struct event_dispatchq *eventq; /* Client event queue */ }; /* Client types */ #define SLM_CLIENT 0 /* Client flags */ #define SE_CLIENT_UNLOADED 0 #define SE_CLIENT_LOADED 0X00000001 #define SE_CLIENT_SUSPENDED 0X00000002 #define SE_CLIENT_THR_RUNNING 0X00000004 #define SE_CLIENT_IS_UNLOADED(scp) \ ((scp)->client_flags == SE_CLIENT_UNLOADED) #define SE_CLIENT_IS_LOADED(scp) \ ((scp)->client_flags & SE_CLIENT_LOADED) #define SE_CLIENT_IS_SUSPENDED(scp) \ ((scp)->client_flags & SE_CLIENT_SUSPENDED) #define SE_CLIENT_IS_THR_RUNNING(scp) \ ((scp)->client_flags & SE_CLIENT_THR_RUNNING) #ifdef __cplusplus } #endif #endif /* _SYSEVENTD_H */