# # 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 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # cmd/syseventd/Makefile # # # The syseventd source is organized in a two-level hierarchy, # with the top level for daemons and modules. Daemons (cmds) # need to be built with the usr/src/cmd hierarchy of Makefiles # for the proper set of cc flags etc., whereas loadable modules # need usr/src/lib. Each bottom-level Makefile includes # the relevant set of common Makefiles (for daemons or modules), # and also inherit the upper level set of common Makefiles # for all syseventd components. # # # usr/src/cmd/syseventd # | # Makefile # Makefile.com # Makefile.targ # | # ------------------------------------------ # | | | # daemons modules etc # | | | # Makefile Makefile Makefile # Makefile.com Makefile.com # Makefile.targ Makefile.targ # | | # ------------------ ------------------ # | | | | # syseventd syseventconfd devfsadmd_mod sysevent_conf_mod # # Makefile Makefile Makefile Makefile # MANIFEST= sysevent.xml SVCMETHOD= svc-syseventd include ../Makefile.cmd .PARALLEL: SUBDIRS= \ etc \ daemons \ modules ROOTMANIFESTDIR= $(ROOTSVCSYSTEM) all: TARGET= all install: TARGET= install clean: TARGET= clean clobber: TARGET= clobber lint: TARGET= lint _msg: TARGET= _msg $(ROOTMANIFEST): FILEMODE= 444 .KEEP_STATE: all clean clobber lint _msg: $(SUBDIRS) install: $(SUBDIRS) $(ROOTMANIFEST) $(ROOTSVCMETHOD) check: $(CHKMANIFEST) 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 (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. # # # Compile flags & libraries for all sysevent daemons and modules # # NOTE: any library added to the next line must be present in the CD miniroot # together with all of their dependencies. # # Hammerhead: -ldl for dlopen/dlsym of sysevent modules LDLIBS += -lsysevent -ldl CPPFLAGS += -D_POSIX_PTHREAD_SEMANTICS CPPFLAGS += -D_REENTRANT CFLAGS += $(CCVERBOSE) LINTFLAGS += -m # # install specifics - directories # ROOTETC=$(ROOT)/etc ROOTLIB=$(ROOT)/usr/lib ROOTLIBSYSEVENTDIR = $(ROOTLIB)/sysevent ROOTLIBSYSEVENTSYSEVENTD = $(ROOTLIBSYSEVENTDIR)/syseventd ROOTLIBSYSEVENTSYSEVENTCONFD = $(ROOTLIBSYSEVENTDIR)/syseventconfd ROOTLIBSYSEVENTMODULEDIR= $(ROOTLIBSYSEVENTDIR)/modules ROOTETCSYSEVENTDIR = $(ROOTETC)/sysevent ROOTETCSYSEVENTCONFIGDIR= $(ROOTETCSYSEVENTDIR)/config # # To play well with what we inherit from Makefile.lib # LIBLINKS = DYNLIB = $(LIBRARY:%=%.so) LIBS = $(DYNLIB) ROOTLIBDIR = $(ROOTLIBSYSEVENTMODULEDIR) # # install macro for syseventd & syseventconfd # ROOTLIBSYSEVENTSYSEVENTD = $(PROG:%=$(ROOTLIBSYSEVENTDIR)/%) # # install macro for /etc/sysevent/config files # ROOTETCSYSEVENTCONFIGFILES= $(CONFIG_FILES:%=$(ROOTETCSYSEVENTCONFIGDIR)/%) # # explicit permissions # $(ROOTETCSYSEVENTCONFIGFILES) : FILEMODE= 0444 # # 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/Makefile.targ # # Common install rules and targets for both sysevent cmds & modules # $(ROOTLIBSYSEVENTDIR) $(ROOTETCSYSEVENTDIR) $(ROOTETCSYSEVENTCONFIGDIR): $(INS.dir) $(ROOTLIBSYSEVENTDIR)/%: % $(INS.file) $(ROOTLIBSYSEVENTMODULEDIR): $(INS.dir) $(ROOTETCSYSEVENTCONFIGDIR)/% : % $(INS.file) # # 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 */ # # 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) 2000-2001 by Sun Microsystems, Inc. # All rights reserved. # # cmd/syseventd/etc/Makefile # CONFIG_FILES= README include $(SRC)/cmd/Makefile.cmd include ../Makefile.com .KEEP_STATE: all clean clobber lint _msg: install: all \ $(ROOTETCSYSEVENTDIR) \ $(ROOTETCSYSEVENTCONFIGDIR) \ $(ROOTETCSYSEVENTCONFIGFILES) 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) 2001 by Sun Microsystems, Inc. # All rights reserved. # Configuration files for syseventd(8). These files are not intended to be edited and any changes made will not be preserved across upgrades. The format for file names in this directory is: ,[,][,[,]]sysevent.conf Files with a suffix other than ',sysevent.conf' are ignored. After making a change to a file in this directory, restart the syseventd daemon by issuing: pkill -HUP syseventd The format for each line in a sysevent.conf file is: , , or may be "-" to match any class, subclass, vendor or publisher, respectively. may be "-" for commands to be invoked as root, or a valid user name. and must be "-". must be the path of the command to be invoked in response to the event, and must be executable by . are optional, and may be any arbitrary arguments for the command to be invoked. Macros may be used to substitute actual parameters from the matching event on the command line. # # 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. # # cmd/syseventd/modules/Makefile # .PARALLEL: SUBDIRS= \ datalink_mod \ devfsadmd_mod \ sysevent_conf_mod \ sysevent_reg_mod \ zfs_mod 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 (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. # # ident "%Z%%M% %I% %E% SMI" # # # Common prologue for Makefiles for all sysevent loadable .so modules # SRCS = $(LIBRARY:%=%.c) OBJECTS = $(LIBRARY:%=%.o) include $(SRC)/lib/Makefile.lib include $(SRC)/cmd/syseventd/Makefile.com SRCDIR = . HSONAME = MAPFILES = $(SRC)/cmd/syseventd/modules/mapfile-extern # # sysevent loadable modules require sysevent header files # CPPFLAGS += -I ../../daemons/syseventd LDLIBS += -lc # Hammerhead: syseventd plugins have intentional undefined references # (syseventd_print, root_dir, etc.) resolved by the daemon at load time. # The mapfile-extern declares these as EXTERN but GNU ld still rejects them # under -zdefs. ZDEFS = POFILES = $(SRCS:.c=.po) POFILE = $(LIBRARY).po CLOBBERFILES += $(LIBRARY) # # 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 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" # # cmd/syseventd/modules/Makefile.targ # # Common sysevent module Makefile epilogue # lint: lintcheck _msg: $(MSGDOMAINPOFILE) include $(SRC)/lib/Makefile.targ include $(SRC)/cmd/syseventd/Makefile.targ include $(SRC)/Makefile.msg.targ # # 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. # LIBRARY= datalink_mod include ../Makefile.com LDLIBS += -lnvpair -lrcm .KEEP_STATE: all: $(DYNLIB) install: all \ $(ROOTLIBSYSEVENTDIR) \ $(ROOTLIBDIR) \ $(ROOTLIBS) include ../Makefile.targ /* * 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. * Copyright 2016 Nexenta Systems, Inc. */ /* * datalink syseventd module. * * The purpose of this module is to identify all datalink related events, * and react accordingly. */ #include #include #include #include #include #include #include "sysevent_signal.h" extern void syseventd_err_print(char *, ...); struct event_list { nvlist_t *ev; struct event_list *next; }; static rcm_handle_t *rcm_hdl = NULL; static boolean_t dl_exiting; static thread_t dl_notify_tid; static mutex_t dl_mx; static cond_t dl_cv; static struct event_list *dl_events; /* ARGSUSED */ static void * datalink_notify_thread(void *arg) { struct event_list *tmp_events, *ep; (void) mutex_lock(&dl_mx); while (! dl_exiting || dl_events != NULL) { if (dl_events == NULL) { (void) cond_wait(&dl_cv, &dl_mx); continue; } tmp_events = dl_events; dl_events = NULL; (void) mutex_unlock(&dl_mx); while (tmp_events != NULL) { struct sigaction cbuf, dfl; /* * Ignore SIGCLD for the * duration of the rcm_notify_event call. */ (void) memset(&dfl, 0, sizeof (dfl)); dfl.sa_handler = SIG_IGN; (void) sigaction(SIGCHLD, &dfl, &cbuf); /* * Send the PHYSLINK_NEW event to network_rcm to update * the network devices cache accordingly. */ if (rcm_notify_event(rcm_hdl, RCM_RESOURCE_PHYSLINK_NEW, 0, tmp_events->ev, NULL) != RCM_SUCCESS) syseventd_err_print("datalink_mod: Can not " "notify event: %s\n", strerror(errno)); (void) sigaction(SIGCHLD, &cbuf, NULL); ep = tmp_events; tmp_events = tmp_events->next; nvlist_free(ep->ev); free(ep); } (void) mutex_lock(&dl_mx); } (void) mutex_unlock(&dl_mx); return (NULL); } /*ARGSUSED*/ static int datalink_deliver_event(sysevent_t *ev, int unused) { const char *class = sysevent_get_class_name(ev); const char *subclass = sysevent_get_subclass_name(ev); nvlist_t *nvl; struct event_list *newp, **elpp; if (strcmp(class, EC_DATALINK) != 0 || strcmp(subclass, ESC_DATALINK_PHYS_ADD) != 0) { return (0); } if (sysevent_get_attr_list(ev, &nvl) != 0) return (EINVAL); /* * rcm_notify_event() needs to be called asynchronously otherwise when * sysevent queue is full, deadlock will happen. */ if ((newp = malloc(sizeof (struct event_list))) == NULL) return (ENOMEM); newp->ev = nvl; newp->next = NULL; /* * queue up at the end of the event list and signal notify_thread to * process it. */ (void) mutex_lock(&dl_mx); elpp = &dl_events; while (*elpp != NULL) elpp = &(*elpp)->next; *elpp = newp; (void) cond_signal(&dl_cv); (void) mutex_unlock(&dl_mx); return (0); } static struct slm_mod_ops datalink_mod_ops = { SE_MAJOR_VERSION, SE_MINOR_VERSION, SE_MAX_RETRY_LIMIT, datalink_deliver_event }; struct slm_mod_ops * slm_init() { dl_events = NULL; dl_exiting = B_FALSE; if (rcm_alloc_handle(NULL, 0, NULL, &rcm_hdl) != RCM_SUCCESS) return (NULL); (void) mutex_init(&dl_mx, USYNC_THREAD, NULL); (void) cond_init(&dl_cv, USYNC_THREAD, NULL); if (thr_create(NULL, 0, datalink_notify_thread, NULL, 0, &dl_notify_tid) != 0) { (void) rcm_free_handle(rcm_hdl); (void) mutex_destroy(&dl_mx); (void) cond_destroy(&dl_cv); return (NULL); } return (&datalink_mod_ops); } void slm_fini() { (void) mutex_lock(&dl_mx); dl_exiting = B_TRUE; (void) cond_signal(&dl_cv); (void) mutex_unlock(&dl_mx); (void) thr_join(dl_notify_tid, NULL, NULL); (void) mutex_destroy(&dl_mx); (void) cond_destroy(&dl_cv); (void) rcm_free_handle(rcm_hdl); rcm_hdl = 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) 2001 by Sun Microsystems, Inc. # All rights reserved. # LIBRARY = devfsadmd_mod include ../Makefile.com CPPFLAGS += -I $(SRC)/cmd/devfsadm CERRWARN += -Wno-parentheses .KEEP_STATE: all: $(DYNLIB) install: all \ $(ROOTLIBSYSEVENTDIR) \ $(ROOTLIBDIR) \ $(ROOTLIBS) include ../Makefile.targ /* * 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 (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sysevent_signal.h" #include "../devfsadm/devfsadm.h" /* * SLM for devfsadmd device configuration daemon */ extern char *root_dir; extern void syseventd_print(); sysevent_handle_t *sysevent_hp; /* Alternate root declarations during install */ static int use_alt_root = 0; static int devfsadmdeliver_event(sysevent_t *ev, int flag); static struct slm_mod_ops devfsadm_mod_ops = { SE_MAJOR_VERSION, SE_MINOR_VERSION, 10, devfsadmdeliver_event}; typedef struct ev_queue { struct ev_queue *evq_next; sysevent_t *evq_ev; } ev_queue_t; static mutex_t evq_lock; static cond_t evq_cv; static ev_queue_t *eventq_head; static ev_queue_t *eventq_tail; #define DELIVERY_FAILED \ gettext("devfsadmd not responding, /dev may not be current") #define DELIVERY_RESUMED \ gettext("devfsadmd now responding again") /* * Retry error recovery when attempting to send an event to devfsadmd */ #define RETRY_DAEMON_RESTART 0 #define RETRY_MSG_THRESHOLD 60 #define RETRY_DAEMON_INTERVAL 60 static int system1(const char *s_path, const char *s) { struct sigaction cbuf, ibuf, qbuf, ignore, dfl; sigset_t mask, savemask; struct stat st; pid_t pid; int status, w; /* Check the requested command */ if (s == NULL) { errno = EINVAL; return (-1); } /* Check the ability to execute devfsadmd from this process */ if (stat(s_path, &st) < 0) { return (-1); } if (((geteuid() == st.st_uid) && ((st.st_mode & S_IXUSR) == 0)) || ((getegid() == st.st_gid) && ((st.st_mode & S_IXGRP) == 0)) || ((st.st_mode & S_IXOTH) == 0)) { errno = EPERM; return (-1); } /* * Block SIGCHLD and set up a default handler for the duration of the * system1 call. */ (void) sigemptyset(&mask); (void) sigaddset(&mask, SIGCHLD); (void) sigprocmask(SIG_BLOCK, &mask, &savemask); (void) memset(&dfl, 0, sizeof (dfl)); dfl.sa_handler = SIG_DFL; (void) sigaction(SIGCHLD, &dfl, &cbuf); /* Fork off the child process (using fork1(), because it's MT-safe) */ switch (pid = fork1()) { case -1: /* Error */ (void) sigaction(SIGCHLD, &cbuf, NULL); (void) sigprocmask(SIG_SETMASK, &savemask, NULL); return (-1); case 0: /* Set-up an initial signal mask for the child */ (void) sigemptyset(&mask); (void) sigprocmask(SIG_SETMASK, &mask, NULL); closefrom(3); (void) execl(s_path, s, (char *)0); _exit(-1); break; default: /* Parent */ break; } (void) memset(&ignore, 0, sizeof (ignore)); ignore.sa_handler = SIG_IGN; (void) sigaction(SIGINT, &ignore, &ibuf); (void) sigaction(SIGQUIT, &ignore, &qbuf); do { w = waitpid(pid, &status, 0); } while (w == -1 && errno == EINTR); (void) sigaction(SIGINT, &ibuf, NULL); (void) sigaction(SIGQUIT, &qbuf, NULL); (void) sigaction(SIGCHLD, &cbuf, NULL); (void) sigprocmask(SIG_SETMASK, &savemask, NULL); return ((w == -1)? w: status); } /* * devfsadmdeliver_event - called by syseventd to deliver an event buffer. * The event buffer is subsequently delivered to * devfsadmd. If devfsadmd, is not responding to the * delivery attempt, we will try to startup the * daemon. MT protection is provided by syseventd * and the client lock. This insures sequential * event delivery and protection from re-entrance. */ /*ARGSUSED*/ static int devfsadmdeliver_event(sysevent_t *ev, int flag) { int ev_size; ev_queue_t *new_evq; /* Not initialized */ if (sysevent_hp == NULL) { return (0); } /* Quick return for uninteresting events */ if (strcmp(sysevent_get_class_name(ev), EC_DEVFS) != 0) { return (0); } /* Queue event for delivery to devfsadmd */ new_evq = (ev_queue_t *)calloc(1, sizeof (ev_queue_t)); if (new_evq == NULL) { return (EAGAIN); } ev_size = sysevent_get_size(ev); new_evq->evq_ev = (sysevent_t *)malloc(ev_size); if (new_evq->evq_ev == NULL) { free(new_evq); return (EAGAIN); } bcopy(ev, new_evq->evq_ev, ev_size); (void) mutex_lock(&evq_lock); if (eventq_head == NULL) { eventq_head = new_evq; } else { eventq_tail->evq_next = new_evq; } eventq_tail = new_evq; (void) cond_signal(&evq_cv); (void) mutex_unlock(&evq_lock); return (0); } static int cleanup; thread_t deliver_thr_id; void * devfsadmd_deliver_thr(void *arg __unused) { int retry = 0; int msg_emitted = 0; ev_queue_t *evqp; (void) mutex_lock(&evq_lock); for (;;) { while (eventq_head == NULL) { (void) cond_wait(&evq_cv, &evq_lock); if (cleanup && eventq_head == NULL) { (void) cond_signal(&evq_cv); (void) mutex_unlock(&evq_lock); return (NULL); } } /* Send events on to devfsadmd */ evqp = eventq_head; while (evqp) { (void) mutex_unlock(&evq_lock); retry = 0; while (sysevent_send_event(sysevent_hp, evqp->evq_ev) != 0) { /* * Invoke devfsadm to handle node creation * but not for an alternate root. */ if (use_alt_root != 0) break; /* * daemon unresponsive - * restart daemon and retry once more */ if ((errno == EBADF || errno == ENOENT) && (retry == RETRY_DAEMON_RESTART) || ((retry % RETRY_DAEMON_INTERVAL) == 0)) { (void) system1( DEVFSADMD_START_PATH, DEVFSADMD_START); } if (retry == RETRY_MSG_THRESHOLD) { syslog(LOG_ERR, DELIVERY_FAILED); msg_emitted = 1; } (void) sleep(1); ++retry; continue; } /* * Event delivered: remove from queue * and reset delivery retry state. */ if (msg_emitted) { syslog(LOG_ERR, DELIVERY_RESUMED); msg_emitted = 0; } retry = 0; (void) mutex_lock(&evq_lock); if (eventq_head != NULL) { eventq_head = eventq_head->evq_next; if (eventq_head == NULL) eventq_tail = NULL; } free(evqp->evq_ev); free(evqp); evqp = eventq_head; } if (cleanup) { (void) cond_signal(&evq_cv); (void) mutex_unlock(&evq_lock); return (NULL); } } /* NOTREACHED */ } struct slm_mod_ops * slm_init() { char alt_door[MAXPATHLEN]; if (strcmp(root_dir, "") == 0) { /* Initialize the private sysevent handle */ sysevent_hp = sysevent_open_channel_alt(DEVFSADM_SERVICE_DOOR); } else { /* Try alternate door during install time */ if (snprintf(alt_door, MAXPATHLEN, "%s%s", "/tmp", DEVFSADM_SERVICE_DOOR) >= MAXPATHLEN) return (NULL); sysevent_hp = sysevent_open_channel_alt(alt_door); use_alt_root = 1; } if (sysevent_hp == NULL) { syseventd_print(0, "Unable to allocate sysevent handle" " for devfsadm module\n"); return (NULL); } if (sysevent_bind_publisher(sysevent_hp) != 0) { if (errno == EBUSY) { sysevent_cleanup_publishers(sysevent_hp); if (sysevent_bind_publisher(sysevent_hp) != 0) { (void) sysevent_close_channel(sysevent_hp); return (NULL); } } } sysevent_cleanup_subscribers(sysevent_hp); cleanup = 0; eventq_head = NULL; eventq_tail = NULL; (void) mutex_init(&evq_lock, USYNC_THREAD, NULL); (void) cond_init(&evq_cv, USYNC_THREAD, NULL); if (thr_create(NULL, 0, devfsadmd_deliver_thr, NULL, THR_BOUND, &deliver_thr_id) != 0) { (void) mutex_destroy(&evq_lock); (void) cond_destroy(&evq_cv); sysevent_close_channel(sysevent_hp); return (NULL); } return (&devfsadm_mod_ops); } void slm_fini() { /* Wait for all events to be flushed out to devfsadmd */ (void) mutex_lock(&evq_lock); cleanup = 1; (void) cond_signal(&evq_cv); (void) cond_wait(&evq_cv, &evq_lock); (void) mutex_unlock(&evq_lock); /* Wait for delivery thread to exit */ (void) thr_join(deliver_thr_id, NULL, NULL); (void) mutex_destroy(&evq_lock); (void) cond_destroy(&evq_cv); sysevent_close_channel(sysevent_hp); sysevent_hp = NULL; } # # Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. # # 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 # # # MAPFILE HEADER START # # WARNING: STOP NOW. DO NOT MODIFY THIS FILE. # Object versioning must comply with the rules detailed in # # usr/src/lib/README.mapfiles # # You should not be making modifications here until you've read the most current # copy of that file. If you need help, contact a gatekeeper for guidance. # # MAPFILE HEADER END # $mapfile_version 2 # External interface requirements SYMBOL_SCOPE { global: debug_level { FLAGS = EXTERN }; root_dir { FLAGS = EXTERN }; syseventd_err_print { FLAGS = EXTERN }; syseventd_print { FLAGS = EXTERN }; }; # # 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. # # Copyright 2019 Joyent, Inc. # LIBRARY = sysevent_conf_mod include ../Makefile.com LDLIBS += -lnvpair CPPFLAGS += -I ../../daemons/syseventconfd CERRWARN += $(CNOWARN_UNINIT) # strange smatch false positive SMOFF += allocating_enough_data .KEEP_STATE: all: $(DYNLIB) install: all \ $(ROOTLIBSYSEVENTDIR) \ $(ROOTLIBDIR) \ $(ROOTLIBS) 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-2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MESSAGE_CONF_MOD_H #define _MESSAGE_CONF_MOD_H #ifdef __cplusplus extern "C" { #endif #define OUT_OF_MEMORY_ERR \ gettext("Out of memory.") #define CANNOT_OPEN_ERR \ gettext("cannot open %s - %s\n") #define NO_USER_ERR \ gettext("%s, line %d: " "user '%s' not recognized\n") #define RESERVED_FIELD_ERR \ gettext("%s, line %d: " "reserved field '%s' must be '-'\n") #define SETREGID_ERR \ gettext("%s: setregid(%d) - %s\n") #define SETREUID_ERR \ gettext("%s: setreuid(%d)- %s\n") #define CANNOT_EXECUTE_ERR \ gettext("%s, line %d: no execute access to %s - %s\n") #define SYNTAX_ERR \ gettext("%s, line %d: syntax error\n") #define PATHCONF_ERR \ gettext("pathconf(%s, NAME_MAX) failed - %s\n") #define READDIR_ERR \ gettext("readdir(%s) failed - %s\n") #define CLOSEDIR_ERR \ gettext("closedir(%s) failed - %s\n") #define MACRO_UNDEF_ERR \ gettext("%s, line %d: macro '%s' undefined\n") #define MACRO_MULT_DEF_ERR \ gettext("%s, line %d: multiple definitions of macro '%s'\n") #define ATTR_VALUE_ERR \ gettext("%s, line %d: attribute type error for macro '%s'\n") #define ATTR_UNSUPPORTED_ERR \ gettext("%s, line %d: unsupported attribute type (0x%x) " \ "for macro '%s'\n") #define GET_ATTR_LIST_ERR \ gettext("%s, line %d: unable to get nvlist - %s\n") #define NVLIST_ALLOC_ERR \ gettext("%s, line %d: error allocating nvlist - %s\n") #define NVLIST_BUILD_ERR \ gettext("%s, line %d: error building nvlist - %s\n") #define SYSEVENT_ALLOC_ERR \ gettext("%s, line %d: error allocating event - %s\n") #define SYSEVENT_SEND_ERR \ gettext("%s, line %d: error sending event (%d) - " \ "syseventconfd not responding?\n") #define CHANNEL_OPEN_ERR \ gettext("unable to open channel to syseventconfd\n") #define SYSEVENTCONFD_ERR \ gettext("syseventconfd not responding?\n") #define SYSEVENTCONFD_OK \ gettext("syseventconfd ok\n") #define SYSEVENTCONFD_TRAN_ERR \ gettext("syseventconfd transport error - %s\n") #define SYSEVENTCONFD_START_ERR \ gettext("error starting syseventconfd - %s\n") #define SYSEVENTCONFD_RESTART_ERR \ gettext("error restarting syseventconfd - %s\n") #define THR_CREATE_ERR \ gettext("thread create error at init - %s\n") #define THR_JOIN_ERR \ gettext("thread join error at fini - %s\n") #define N_EVENTS_DISCARDED_ERR \ gettext("discarding %d queued events\n") #define SERVICE_DISABLED_MSG \ gettext("sysevent_conf_mod service disabled - " \ "restart with 'pkill -HUP syseventd'\n") #define MSG_LOCK_CREATE_ERR \ gettext("%s: error creating lock %s - %s\n") #define MSG_LOCK_SET_ERR \ gettext("%s: error setting lock in %s - %s\n") #define MSG_LOCK_CLR_ERR \ gettext("%s: error clearing lock in %s - %s\n") #define MSG_LOCK_CLOSE_ERR \ gettext("%s: error closing lock %s - %s\n") #ifdef __cplusplus } #endif #endif /* _MESSAGE_CONF_MOD_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. */ /* * sysevent_conf_mod - syseventd daemon sysevent.conf module * * This module provides a configuration file registration * mechanism whereby event producers can define an event * specification to be matched against events, with an * associated command line to be invoked for each matching event. * It includes a simple macro capability for flexibility in * generating arbitrary command line formats from event-associated * data, and a user specification so that commands can be invoked * with reduced privileges to eliminate a security risk. * * sysevent.conf files contain event specifications and associated * command path and optional arguments. System events received * from the kernel by the sysevent daemon, syseventd, are * compared against the event specifications in the sysevent.conf * files. The command as specified by pathname and arguments * is invoked for each matching event. * * All sysevent.conf files reside in /etc/sysevent/config. * */ #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 "syseventd.h" #include "syseventconfd_door.h" #include "sysevent_conf_mod.h" #include "message_conf_mod.h" static char *whoami = "sysevent_conf_mod"; /* * Event sequencing, time stamp and retry count */ static int ev_nretries; /* retry count per event */ static uint64_t ev_seq; /* current event sequencing number */ static hrtime_t ev_ts; /* current event timestamp */ static int first_event; /* first event since init */ /* * State of the sysevent conf table, derived from * the /etc/sysevent/config files */ static conftab_t *conftab = NULL; static syseventtab_t *syseventtab = NULL; static syseventtab_t *syseventtab_tail = NULL; static sysevent_handle_t *confd_handle = NULL; /* * The cmd queue is a queue of commands ready to be sent * to syseventconfd. Each command consists of the path * and arguments to be fork/exec'ed. The daemon is unable * to handle events during an active fork/exec and returns * EAGAIN as a result. It is grossly inefficient to bounce * these events back to syseventd, so we queue them here for delivery. */ static cmdqueue_t *cmdq = NULL; static cmdqueue_t *cmdq_tail = NULL; static mutex_t cmdq_lock; static cond_t cmdq_cv; static int cmdq_cnt; static thread_t cmdq_thr_id; static cond_t cmdq_thr_cv; static int want_fini; /* * State of the door channel to syseventconfd */ static int confd_state = CONFD_STATE_NOT_RUNNING; /* * Number of times to retry event after restarting syeventconfd */ static int confd_retries; /* * Number of times to retry a failed transport */ static int transport_retries; /* * Normal sleep time when syseventconfd returns EAGAIN * is one second but to avoid thrashing, sleep for * something larger when syseventconfd not responding. * This should never happen of course but it seems better * to attempt to handle possible errors gracefully. */ static int confd_err_msg_emitted; static int sysevent_conf_dummy_event(sysevent_t *, int); /* * External references */ extern int debug_level; extern char *root_dir; extern void syseventd_print(int level, char *format, ...); extern void syseventd_err_print(char *format, ...); static struct slm_mod_ops sysevent_conf_mod_ops = { SE_MAJOR_VERSION, /* syseventd module major version */ SE_MINOR_VERSION, /* syseventd module minor version */ SE_MAX_RETRY_LIMIT, /* max retry if EAGAIN */ &sysevent_conf_event /* event handler */ }; static struct slm_mod_ops sysevent_conf_dummy_mod_ops = { SE_MAJOR_VERSION, /* syseventd module major version */ SE_MINOR_VERSION, /* syseventd module minor version */ 0, /* no retries, always succeeds */ &sysevent_conf_dummy_event /* dummy event handler */ }; /* * skip_spaces() - skip to next non-space character */ static char * skip_spaces(char **cpp) { char *cp = *cpp; while (*cp == ' ' || *cp == '\t') cp++; if (*cp == 0) { *cpp = 0; return (NULL); } return (cp); } /* * Get next white-space separated field. * next_field() will not check any characters on next line. * Each entry is composed of a single line. */ static char * next_field(char **cpp) { char *cp = *cpp; char *start; while (*cp == ' ' || *cp == '\t') cp++; if (*cp == 0) { *cpp = 0; return (NULL); } start = cp; while (*cp && *cp != ' ' && *cp != '\t') cp++; if (*cp != 0) *cp++ = 0; *cpp = cp; return (start); } /* * The following functions are simple wrappers/equivalents * for malloc, realloc, free, strdup and a special free * for strdup. * * These functions ensure that any failed mallocs are * reported via syslog() so if a command is not evoked * in response to an event, the reason should be logged. * These functions also provide a convenient place for * hooks for checking for memory leaks. */ static void * sc_malloc(size_t n) { void *p; p = malloc(n); if (p == NULL) { syslog(LOG_ERR, OUT_OF_MEMORY_ERR); } return (p); } /*ARGSUSED*/ static void * sc_realloc(void *p, size_t current, size_t n) { p = realloc(p, n); if (p == NULL) { syslog(LOG_ERR, OUT_OF_MEMORY_ERR); } return (p); } /*ARGSUSED*/ static void sc_free(void *p, size_t n) { free(p); } static char * sc_strdup(char *cp) { char *new; new = malloc((unsigned)(strlen(cp) + 1)); if (new == NULL) { syslog(LOG_ERR, OUT_OF_MEMORY_ERR); return (NULL); } (void) strcpy(new, cp); return (new); } static void sc_strfree(char *s) { if (s) free(s); } /* * The following functions provide some simple dynamic string * capability. This module has no hard-coded maximum string * lengths and should be able to parse and generate arbitrarily * long strings, macro expansion and command lines. * * Each string must be explicitly allocated and freed. */ /* * Allocate a dynamic string, with a hint to indicate how * much memory to dynamically add to the string as it grows * beyond its existing bounds, so as to avoid excessive * reallocs as a string grows. */ static str_t * initstr(int hint) { str_t *str; if ((str = sc_malloc(sizeof (str_t))) == NULL) return (NULL); str->s_str = NULL; str->s_len = 0; str->s_alloc = 0; str->s_hint = hint; return (str); } /* * Free a dynamically-allocated string */ static void freestr(str_t *str) { if (str->s_str) { sc_free(str->s_str, str->s_alloc); } sc_free(str, sizeof (str_t)); } /* * Reset a dynamically-allocated string, allows reuse * rather than freeing the old and allocating a new one. */ static void resetstr(str_t *str) { str->s_len = 0; } /* * Copy a (simple) string onto a dynamically-allocated string */ static int strcopys(str_t *str, char *s) { char *new_str; int len = strlen(s) + 1; if (str->s_alloc < len) { new_str = (str->s_str == NULL) ? sc_malloc(len+str->s_hint) : sc_realloc(str->s_str, str->s_alloc, len+str->s_hint); if (new_str == NULL) { return (1); } str->s_str = new_str; str->s_alloc = len + str->s_hint; } (void) strcpy(str->s_str, s); str->s_len = len - 1; return (0); } /* * Concatenate a (simple) string onto a dynamically-allocated string */ static int strcats(str_t *str, char *s) { char *new_str; int len = str->s_len + strlen(s) + 1; if (str->s_alloc < len) { new_str = (str->s_str == NULL) ? sc_malloc(len+str->s_hint) : sc_realloc(str->s_str, str->s_alloc, len+str->s_hint); if (new_str == NULL) { return (1); } str->s_str = new_str; str->s_alloc = len + str->s_hint; } (void) strcpy(str->s_str + str->s_len, s); str->s_len = len - 1; return (0); } /* * Concatenate a character onto a dynamically-allocated string */ static int strcatc(str_t *str, int c) { char *new_str; int len = str->s_len + 2; if (str->s_alloc < len) { new_str = (str->s_str == NULL) ? sc_malloc(len+str->s_hint) : sc_realloc(str->s_str, str->s_alloc, len+str->s_hint); if (new_str == NULL) { return (1); } str->s_str = new_str; str->s_alloc = len + str->s_hint; } *(str->s_str + str->s_len) = (char)c; *(str->s_str + str->s_len + 1) = 0; str->s_len++; return (0); } /* * fgets() equivalent using a dynamically-allocated string */ static char * fstrgets(str_t *line, FILE *fp) { int c; resetstr(line); while ((c = fgetc(fp)) != EOF) { if (strcatc(line, c)) return (NULL); if (c == '\n') break; } if (line->s_len == 0) return (NULL); return (line->s_str); } /* * Truncate a dynamically-allocated string at index position 'pos' */ static void strtrunc(str_t *str, int pos) { if (str->s_len > pos) { str->s_len = pos; *(str->s_str + pos) = 0; } } /* * Parse a sysevent.conf file, adding each entry spec to the event table. * * The format of an entry in a sysevent.conf file is: * * class subclass vendor publisher user reserved1 reserved path arguments * * Fields are separated by either SPACE or TAB characters. A * '#' (number sign) at the beginning of a line indicates a * comment. Comment lines and blank lines are ignored. * * class * The class of the event. * * subclass * The subclass of the event. * * vendor * The name of the vendor defining the event, usually the * stock symbol. Events generated by system components * provided by Sun Microsystems, Inc. always define vendor * as 'SUNW'. * * publisher * The name of the application, driver or system module * producing the event. * * user * The name of the user under which the command should be * run. This allows commands to run with access privileges * other than those for root. The user field should be '-' * for commands to be run as root. * * reserved1 * Must be '-'. * * reserved2 * Must be '-'. * * path * Pathname of the command to be invoked for matching events. * * arguments * Optional argument with possible macro substitution to permit * arbitrary command line construction with event-specific data. */ static void parse_conf_file(char *conf_file) { char conf_path[PATH_MAX]; FILE *fp; char *lp; str_t *line; int lineno = 0; char *vendor, *publisher; char *class, *subclass; char *user; char *reserved1, *reserved2; char *path, *args; syseventtab_t *sep; struct passwd pwd; struct passwd *pwdp; char pwdbuf[1024]; int do_setuid; pid_t saved_uid; gid_t saved_gid; int i, err; (void) snprintf(conf_path, PATH_MAX, "%s/%s", SYSEVENT_CONFIG_DIR, conf_file); syseventd_print(DBG_CONF_FILE, "%s: reading %s\n", whoami, conf_path); if ((fp = fopen(conf_path, "r")) == NULL) { syslog(LOG_ERR, CANNOT_OPEN_ERR, conf_file, strerror(errno)); return; } if ((line = initstr(128)) == NULL) return; while ((lp = fstrgets(line, fp)) != NULL) { lineno++; if (*lp == '\n' || *lp == '#') continue; *(lp + strlen(lp)-1) = 0; syseventd_print(DBG_CONF_FILE, "[%d]: %s\n", lineno, lp); if ((class = next_field(&lp)) == NULL) goto mal_formed; if ((subclass = next_field(&lp)) == NULL) goto mal_formed; if ((vendor = next_field(&lp)) == NULL) goto mal_formed; if ((publisher = next_field(&lp)) == NULL) goto mal_formed; if ((user = next_field(&lp)) == NULL) goto mal_formed; if ((reserved1 = next_field(&lp)) == NULL) goto mal_formed; if ((reserved2 = next_field(&lp)) == NULL) goto mal_formed; if ((path = next_field(&lp)) == NULL) goto mal_formed; args = skip_spaces(&lp); /* * validate user */ do_setuid = 0; if ((strcmp(user, "-") != 0) && (strcmp(user, "root") != 0)) { i = getpwnam_r(user, &pwd, pwdbuf, sizeof (pwdbuf), &pwdp); if (i != 0 || pwdp == NULL) { syslog(LOG_ERR, NO_USER_ERR, conf_file, lineno, user); continue; } do_setuid = 1; } /* * validate reserved fields */ if (strcmp(reserved1, "-") != 0) { syslog(LOG_ERR, RESERVED_FIELD_ERR, conf_file, lineno, reserved1); continue; } if (strcmp(reserved2, "-") != 0) { syslog(LOG_ERR, RESERVED_FIELD_ERR, conf_file, lineno, reserved2); continue; } /* * ensure path is executable by user */ err = 0; if (do_setuid) { saved_uid = getuid(); saved_gid = getgid(); if (setregid(pwdp->pw_gid, -1) == -1) { syslog(LOG_ERR, SETREGID_ERR, whoami, pwdp->pw_gid, strerror(errno)); err = -1; } if (setreuid(pwdp->pw_uid, -1) == -1) { syslog(LOG_ERR, SETREUID_ERR, whoami, pwdp->pw_uid, strerror(errno)); err = -1; } } if ((i = access(path, X_OK)) == -1) { syslog(LOG_ERR, CANNOT_EXECUTE_ERR, conf_file, lineno, path, strerror(errno)); } if (do_setuid) { if (setreuid(saved_uid, -1) == -1) { syslog(LOG_ERR, SETREUID_ERR, whoami, saved_uid, strerror(errno)); err = -1; } if (setregid(saved_gid, -1) == -1) { syslog(LOG_ERR, SETREGID_ERR, whoami, saved_gid, strerror(errno)); err = -1; } } if (i == -1 || err == -1) continue; /* * all sanity tests successful - perform allocations * to add entry to table */ if ((sep = sc_malloc(sizeof (syseventtab_t))) == NULL) break; sep->se_conf_file = conf_file; sep->se_lineno = lineno; sep->se_vendor = sc_strdup(vendor); sep->se_publisher = sc_strdup(publisher); sep->se_class = sc_strdup(class); sep->se_subclass = sc_strdup(subclass); sep->se_user = sc_strdup(user); if (do_setuid) { sep->se_uid = pwdp->pw_uid; sep->se_gid = pwdp->pw_gid; } else { sep->se_uid = 0; sep->se_gid = 0; } sep->se_reserved1 = sc_strdup(reserved1); sep->se_reserved2 = sc_strdup(reserved2); sep->se_path = sc_strdup(path); sep->se_args = (args == NULL) ? NULL : sc_strdup(args); sep->se_next = NULL; if (sep->se_vendor == NULL || sep->se_publisher == NULL || sep->se_class == NULL || sep->se_subclass == NULL || sep->se_user == NULL || sep->se_reserved1 == NULL || sep->se_reserved2 == NULL || sep->se_path == NULL || (args && sep->se_args == NULL)) { sc_strfree(sep->se_vendor); sc_strfree(sep->se_publisher); sc_strfree(sep->se_class); sc_strfree(sep->se_subclass); sc_strfree(sep->se_user); sc_strfree(sep->se_reserved1); sc_strfree(sep->se_reserved2); sc_strfree(sep->se_path); sc_strfree(sep->se_args); sc_free(sep, sizeof (syseventtab_t)); break; } /* * link new entry into the table */ if (syseventtab == NULL) { syseventtab = sep; syseventtab_tail = sep; } else { syseventtab_tail->se_next = sep; syseventtab_tail = sep; } if (debug_level >= DBG_DETAILED) { syseventtab_t *sp; for (sp = syseventtab; sp; sp = sp->se_next) { syseventd_print(DBG_DETAILED, " vendor=%s\n", sp->se_vendor); syseventd_print(DBG_DETAILED, " publisher=%s\n", sp->se_publisher); syseventd_print(DBG_DETAILED, " class=%s\n", sp->se_class); syseventd_print(DBG_DETAILED, " subclass=%s\n", sp->se_subclass); syseventd_print(DBG_DETAILED, " user=%s uid=%d gid=%d\n", sp->se_user, sp->se_uid, sp->se_gid); syseventd_print(DBG_DETAILED, " reserved1=%s\n", sp->se_reserved1); syseventd_print(DBG_DETAILED, " reserved2=%s\n", sp->se_reserved2); syseventd_print(DBG_DETAILED, " path=%s\n", sp->se_path); if (sp->se_args != NULL) { syseventd_print(DBG_DETAILED, " args=%s\n", sp->se_args); } } } continue; mal_formed: syslog(LOG_ERR, SYNTAX_ERR, conf_file, lineno); } freestr(line); (void) fclose(fp); } /* * Build the events specification table, a summation of all * event specification found in the installed sysevent.conf * configuration files. * * All sysevent.conf files reside in the /etc/sysevent/config * and may contain zero or more event/command specifications. * A sysevent.conf file should be named as follows: * * ,[,][,]sysevent.conf * * Event/command specifications delivered by the base Solaris * system are provided in /etc/sysevent/config/SUNW,sysevent.conf. * Event/command specifications delivered by optional * Sun-supplied packages may install additional sysevent.conf * files in /etc/sysevent/config using vendor SUNW, and additional * publisher and/or event class naming to distinguish the * events required for those products. Products provided * by third-party hardware or software companies may * distinguish their sysevent.conf files by vendor, and * by publisher and/or event class within vendor. * * Files residing in /etc/sysevent/config with a '.' (period) * as the first character of the name and files with a suffix * of other than "sysevent.conf" are ignored. */ static void build_event_table() { conftab_t *cfp = NULL; DIR *dir; struct dirent *result; conftab_t *new_cfp; char *str; if ((dir = opendir(SYSEVENT_CONFIG_DIR)) == NULL) { syslog(LOG_ERR, CANNOT_OPEN_ERR, SYSEVENT_CONFIG_DIR, strerror(errno)); return; } while ((result = readdir(dir)) != NULL) { if (result->d_name[0] == '.') continue; /* * file must have extension "sysevent.conf" */ if ((str = strrchr(result->d_name, ',')) != NULL) { str++; } else { str = result->d_name; } if (strcmp(str, "sysevent.conf") != 0) { syseventd_print(DBG_CONF_FILE, "%s: ignoring %s\n", whoami, str); continue; } /* * Add to file table and parse this conf file */ if ((str = sc_strdup(result->d_name)) == NULL) goto err; if ((new_cfp = sc_malloc(sizeof (conftab_t))) == NULL) { sc_strfree(str); goto err; } if (conftab == NULL) { conftab = new_cfp; } else { for (cfp = conftab; cfp->cf_next; cfp = cfp->cf_next) ; cfp->cf_next = new_cfp; } cfp = new_cfp; cfp->cf_conf_file = str; cfp->cf_next = NULL; parse_conf_file(cfp->cf_conf_file); } err: if (closedir(dir) == -1) { if (errno == EAGAIN) goto err; syslog(LOG_ERR, CLOSEDIR_ERR, SYSEVENT_CONFIG_DIR, strerror(errno)); } } static int enter_lock(char *lock_file) { struct flock lock; int lock_fd; (void) strlcpy(lock_file, LOCK_FILENAME, PATH_MAX); lock_fd = open(lock_file, O_CREAT|O_RDWR, 0644); if (lock_fd < 0) { syslog(LOG_ERR, MSG_LOCK_CREATE_ERR, whoami, lock_file, strerror(errno)); return (-1); } lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; retry: if (fcntl(lock_fd, F_SETLKW, &lock) == -1) { if (errno == EAGAIN || errno == EINTR) goto retry; (void) close(lock_fd); syslog(LOG_ERR, MSG_LOCK_SET_ERR, whoami, lock_file, strerror(errno)); return (-1); } return (lock_fd); } static void exit_lock(int lock_fd, char *lock_file) { struct flock lock; lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; if (fcntl(lock_fd, F_SETLK, &lock) == -1) { syslog(LOG_ERR, MSG_LOCK_CLR_ERR, whoami, lock_file, strerror(errno)); } if (close(lock_fd) == -1) { syslog(LOG_ERR, MSG_LOCK_CLOSE_ERR, whoami, lock_file, strerror(errno)); } } /* * Free the events specification table, constructed by * parsing all the sysevent.conf files found. * * The free of this table is in response to a HUP * given to the syseventd daemon, permitting the * table to be rebuilt after adding a new sysevent.conf * file or changing an existing one without shutting * down the daemon. */ static void free_event_table() { syseventtab_t *sep; syseventtab_t *sep_next; conftab_t *cfp; conftab_t *cfp_next; sep = syseventtab; while (sep) { sc_strfree(sep->se_vendor); sc_strfree(sep->se_publisher); sc_strfree(sep->se_class); sc_strfree(sep->se_subclass); sc_strfree(sep->se_user); sc_strfree(sep->se_reserved1); sc_strfree(sep->se_reserved2); sc_strfree(sep->se_path); if (sep->se_args) sc_strfree(sep->se_args); sep_next = sep->se_next; sc_free(sep, sizeof (syseventtab_t)); sep = sep_next; } syseventtab = NULL; cfp = conftab; while (cfp) { sc_strfree(cfp->cf_conf_file); cfp_next = cfp->cf_next; sc_free(cfp, sizeof (conftab_t)); cfp = cfp_next; } conftab = NULL; } static char ident_chars[] = "_"; /* * Return a dynamically-allocated string containing the * the next identifier in the string being parsed, pointed * at by 'id'. 'end' returns a pointer to the character * after the identifier. * * Identifiers are all alphanumeric ascii characters and * those contained in ident_chars. * * The returned string must be explicitly freed via * freestr(). */ static str_t * snip_identifier(char *id, char **end) { str_t *token; if ((token = initstr(32)) == NULL) return (NULL); while (*id != 0) { if (isascii(*id) && (isalnum(*id) || strchr(ident_chars, *id) != NULL)) { if (strcatc(token, *id++)) { freestr(token); return (NULL); } } else { *end = id; return (token); } } *end = id; return (token); } /* * Identical to snip_identifier(), but the identifier * is delimited by the characters { and }. */ static str_t * snip_delimited_identifier(char *id, char **end) { str_t *token; if ((token = initstr(32)) == NULL) return (NULL); while (*id != 0) { if (*id == '}') { *end = id+1; return (token); } if (strcatc(token, *id++)) { freestr(token); return (NULL); } } if (*id == 0) { freestr(token); return (NULL); } *end = id; return (token); } /* * Return a string with the name of the attribute type */ static char *nv_attr_type_strings[] = { "unknown", "boolean", "byte", "int16", "uint16", "int32", "uint32", "int64", "uint64", "string", "byte-array", "int16-array", "uint16-array", "int32-array", "uint32-array", "int64-array", "uint64-array", "string-array", "hrtime" }; static char * se_attr_type_to_str(int se_attr_type) { if (se_attr_type >= 0 && se_attr_type < sizeof (nv_attr_type_strings) / sizeof (char *)) { return (nv_attr_type_strings[se_attr_type]); } return (nv_attr_type_strings[DATA_TYPE_UNKNOWN]); } /* * Find and return the data matching the macro name 'token' * * Predefined macros are simply substituted with the * data from the event header: * * $vendor - the vendor string defining the event. * * $publisher - the publisher string defining the event. * * $class - the class string defining the event. * * $subclass - the subclass string defining the event. * * $sequence - the sequence number of the event. * * $timestamp - the timestamp of the event. * * Attributes with signed data types (DATA_TYPE_INT16, * DATA_TYPE_INT32 and DATA_TYPE_INT64) are expanded * as decimal digits. * * Attributes with unsigned data types (DATA_TYPE_BYTE, * DATA_TYPE_UINT16, DATA_TYPE_UINT32, DATA_TYPE_UINT64 and * DATA_TYPE_HTTIME) are expanded as hexadecimal digits * with a "0x" prefix. * * Attributes with string data type (DATA_TYPE_STRING) * are expanded with the string data. The data is * not quoted. If if it desired that the quoted strings * be generated on the command line, put quotes around * the macro call in the arguments. * * Array types are expanded with each element expanded * as defined for that scalar type, with a space separating * each element substitution. */ static str_t * find_macro_definition(sysevent_t *ev, nvlist_t *nvlist, syseventtab_t *sep, char *token, sysevent_hdr_info_t *hdr) { nvpair_t *nvp; int nmatches; char num[64]; str_t *replacement; int i; uint_t nelems; union { uchar_t x_byte; int16_t x_int16; uint16_t x_uint16; int32_t x_int32; uint32_t x_uint32; int64_t x_int64; uint64_t x_uint64; hrtime_t x_time; char *x_string; uchar_t *x_byte_array; int16_t *x_int16_array; int32_t *x_int32_array; int64_t *x_int64_array; uint16_t *x_uint16_array; uint32_t *x_uint32_array; uint64_t *x_uint64_array; char **x_string_array; } x; if ((replacement = initstr(128)) == NULL) { return (NULL); } if (strcmp(token, "vendor") == 0) { if (strcopys(replacement, hdr->vendor)) { freestr(replacement); return (NULL); } return (replacement); } if (strcmp(token, "publisher") == 0) { if (strcopys(replacement, hdr->publisher)) { freestr(replacement); return (NULL); } return (replacement); } if (strcmp(token, "class") == 0) { if (strcopys(replacement, hdr->class)) { freestr(replacement); return (NULL); } return (replacement); } if (strcmp(token, "subclass") == 0) { if (strcopys(replacement, hdr->subclass)) { freestr(replacement); return (NULL); } return (replacement); } if ((strcmp(token, "sequence") == 0) || (strcmp(token, "timestamp") == 0)) { if (strcmp(token, "sequence") == 0) { (void) snprintf(num, sizeof (num), "0x%llx", sysevent_get_seq(ev)); } else { hrtime_t ts; sysevent_get_time(ev, &ts); (void) snprintf(num, sizeof (num), "0x%llx", ts); } if (strcopys(replacement, num)) { freestr(replacement); return (NULL); } return (replacement); } nmatches = 0; if (nvlist) { nvpair_t *nvp_match; nvp = NULL; while ((nvp = nvlist_next_nvpair(nvlist, nvp)) != NULL) { if (debug_level >= DBG_DETAILED) { syseventd_print(DBG_DETAILED, " attribute: %s %s\n", nvpair_name(nvp), se_attr_type_to_str(nvpair_type(nvp))); } if (strcmp(token, nvpair_name(nvp)) == 0) { nmatches++; nvp_match = nvp; } } nvp = nvp_match; } if (nmatches == 0) { syslog(LOG_ERR, MACRO_UNDEF_ERR, sep->se_conf_file, sep->se_lineno, token); freestr(replacement); return (NULL); } else if (nmatches > 1) { syslog(LOG_ERR, MACRO_MULT_DEF_ERR, sep->se_conf_file, sep->se_lineno, token); freestr(replacement); return (NULL); } switch (nvpair_type(nvp)) { case DATA_TYPE_BYTE: (void) nvpair_value_byte(nvp, &x.x_byte); (void) snprintf(num, sizeof (num), "0x%x", x.x_byte); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } break; case DATA_TYPE_INT16: (void) nvpair_value_int16(nvp, &x.x_int16); (void) snprintf(num, sizeof (num), "%d", x.x_int16); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } break; case DATA_TYPE_UINT16: (void) nvpair_value_uint16(nvp, &x.x_uint16); (void) snprintf(num, sizeof (num), "0x%x", x.x_uint16); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } break; case DATA_TYPE_INT32: (void) nvpair_value_int32(nvp, &x.x_int32); (void) snprintf(num, sizeof (num), "%d", x.x_int32); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } break; case DATA_TYPE_UINT32: (void) nvpair_value_uint32(nvp, &x.x_uint32); (void) snprintf(num, sizeof (num), "0x%x", x.x_uint32); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } break; case DATA_TYPE_INT64: (void) nvpair_value_int64(nvp, &x.x_int64); (void) snprintf(num, sizeof (num), "%lld", x.x_int64); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } break; case DATA_TYPE_UINT64: (void) nvpair_value_uint64(nvp, &x.x_uint64); (void) snprintf(num, sizeof (num), "0x%llx", x.x_uint64); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } break; case DATA_TYPE_STRING: (void) nvpair_value_string(nvp, &x.x_string); if (strcats(replacement, x.x_string)) { freestr(replacement); return (NULL); } break; case DATA_TYPE_BYTE_ARRAY: { uchar_t *p; (void) nvpair_value_byte_array(nvp, &x.x_byte_array, &nelems); p = x.x_byte_array; for (i = 0; i < nelems; i++) { (void) snprintf(num, sizeof (num), "0x%x ", *p++ & 0xff); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } } } break; case DATA_TYPE_INT16_ARRAY: { int16_t *p; (void) nvpair_value_int16_array(nvp, &x.x_int16_array, &nelems); p = x.x_int16_array; for (i = 0; i < nelems; i++) { (void) snprintf(num, sizeof (num), "%d ", *p++); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } } } break; case DATA_TYPE_UINT16_ARRAY: { uint16_t *p; (void) nvpair_value_uint16_array(nvp, &x.x_uint16_array, &nelems); p = x.x_uint16_array; for (i = 0; i < nelems; i++) { (void) snprintf(num, sizeof (num), "0x%x ", *p++); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } } } break; case DATA_TYPE_INT32_ARRAY: { int32_t *p; (void) nvpair_value_int32_array(nvp, &x.x_int32_array, &nelems); p = x.x_int32_array; for (i = 0; i < nelems; i++) { (void) snprintf(num, sizeof (num), "%d ", *p++); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } } } break; case DATA_TYPE_UINT32_ARRAY: { uint32_t *p; (void) nvpair_value_uint32_array(nvp, &x.x_uint32_array, &nelems); p = x.x_uint32_array; for (i = 0; i < nelems; i++) { (void) snprintf(num, sizeof (num), "0x%x ", *p++); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } } } break; case DATA_TYPE_INT64_ARRAY: { int64_t *p; (void) nvpair_value_int64_array(nvp, &x.x_int64_array, &nelems); p = x.x_int64_array; for (i = 0; i < nelems; i++) { (void) snprintf(num, sizeof (num), "%lld ", *p++); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } } } break; case DATA_TYPE_UINT64_ARRAY: { uint64_t *p; (void) nvpair_value_uint64_array(nvp, &x.x_uint64_array, &nelems); p = x.x_uint64_array; for (i = 0; i < nelems; i++) { (void) snprintf(num, sizeof (num), "0x%llx ", *p++); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } } } break; case DATA_TYPE_STRING_ARRAY: { char **p; (void) nvpair_value_string_array(nvp, &x.x_string_array, &nelems); p = x.x_string_array; for (i = 0; i < nelems; i++) { if (strcats(replacement, *p++) || strcats(replacement, " ")) { freestr(replacement); return (NULL); } } } break; case DATA_TYPE_HRTIME: (void) nvpair_value_hrtime(nvp, &x.x_time); (void) snprintf(num, sizeof (num), "0x%llx", x.x_time); if (strcats(replacement, num)) { freestr(replacement); return (NULL); } break; default: syslog(LOG_ERR, ATTR_UNSUPPORTED_ERR, sep->se_conf_file, sep->se_lineno, nvpair_type(nvp), token); freestr(replacement); return (NULL); } return (replacement); } /* * Expand macros in the command template provided in an event * specification with the data from the event or event attributes. * * Macros are introduced by the '$' character, with the macro * name being the following token separated by a SPACE or * TAB character. If the macro name is embedded in text, * it may be delineated by '${' and "}'. A backslash before * the '$' causes macro expansion not to occur. * * The following predefined macros are defined for each event: * * $vendor - the vendor string defining the event. * * $publisher - the publisher string defining the event. * * $class - the class string defining the event. * * $subclass - the subclass string defining the event. * * $sequence - the sequence number of the event. * * $timestamp - the timestamp of the event. * * * Macro names other than those predefined are compared against * the attribute list provided with the event. An attribute * with name matching the macro name causes the value of * of the attribute to be substituted as ASCII text on the * generated command line. * * Use of a macro for which no attribute with that name * is defined, or for which multiple attributes with that * name are provided, cause an error and the command is * not invoked. */ static int expand_macros(sysevent_t *ev, nvlist_t *nvlist, syseventtab_t *sep, str_t *line, sysevent_hdr_info_t *hdr) { char *p; int state; char *end; str_t *token; str_t *remainder; str_t *replacement; int count; int dollar_position; syseventd_print(DBG_MACRO, " expanding macros: '%s'\n", line->s_str); reset: state = 0; count = 0; for (p = line->s_str; *p != 0; p++, count++) { switch (state) { case 0: /* initial state */ if (*p == '\\') { state = 1; } else if (*p == '$') { dollar_position = count; state = 2; } break; case 1: /* skip characters */ state = 0; /* after backslash */ break; case 2: /* character after $ */ if (*p == '{') { token = snip_delimited_identifier(p+1, &end); } else { token = snip_identifier(p, &end); } if (token == NULL) goto failed; if ((remainder = initstr(128)) == NULL) { freestr(token); return (1); } if (strcopys(remainder, end)) { freestr(token); freestr(remainder); return (1); } replacement = find_macro_definition(ev, nvlist, sep, token->s_str, hdr); if (replacement == NULL) { freestr(token); freestr(remainder); return (1); } syseventd_print(DBG_MACRO, " '%s' expands to '%s'\n", token->s_str, replacement->s_str); strtrunc(line, dollar_position); if (strcats(line, replacement->s_str)) { freestr(token); freestr(replacement); freestr(remainder); return (1); } if (strcats(line, remainder->s_str)) { freestr(token); freestr(replacement); freestr(remainder); return (1); } syseventd_print(DBG_MACRO, " with macro expanded: '%s'\n", line->s_str); freestr(token); freestr(replacement); freestr(remainder); goto reset; } } failed: if (state != 0) { syslog(LOG_ERR, SYNTAX_ERR, sep->se_conf_file, sep->se_lineno); return (1); } return (0); } static void start_syseventconfd() { int err; err = system1("/usr/lib/sysevent/syseventconfd", "/usr/lib/sysevent/syseventconfd"); if (err != 0 && confd_err_msg_emitted == 0) { if (confd_state == CONFD_STATE_NOT_RUNNING) { syslog(LOG_ERR, SYSEVENTCONFD_START_ERR, strerror(errno)); } else { syslog(LOG_ERR, SYSEVENTCONFD_RESTART_ERR, strerror(errno)); } } } static int system1(const char *s_path, const char *s) { struct sigaction cbuf, ibuf, qbuf, ignore, dfl; sigset_t mask; sigset_t savemask; struct stat st; pid_t pid; int status, w; /* Check the requested command */ if (s == NULL) { errno = EINVAL; return (-1); } /* Check the ability to execute devfsadmd from this process */ if (stat(s_path, &st) < 0) { return (-1); } if (((geteuid() == st.st_uid) && ((st.st_mode & S_IXUSR) == 0)) || ((getegid() == st.st_gid) && ((st.st_mode & S_IXGRP) == 0)) || ((st.st_mode & S_IXOTH) == 0)) { errno = EPERM; return (-1); } /* * Block SIGCHLD and set up a default handler for the duration of the * system1 call. */ (void) sigemptyset(&mask); (void) sigaddset(&mask, SIGCHLD); (void) sigprocmask(SIG_BLOCK, &mask, &savemask); (void) memset(&dfl, 0, sizeof (dfl)); dfl.sa_handler = SIG_DFL; (void) sigaction(SIGCHLD, &dfl, &cbuf); /* Fork off the child process (using fork1(), because it's MT-safe) */ switch (pid = fork1()) { case -1: /* Error */ (void) sigaction(SIGCHLD, &cbuf, NULL); (void) sigprocmask(SIG_SETMASK, &savemask, NULL); return (-1); case 0: /* Set-up an initial signal mask for the child */ (void) sigemptyset(&mask); (void) sigprocmask(SIG_SETMASK, &mask, NULL); closefrom(3); (void) execl(s_path, s, (char *)0); _exit(-1); break; default: /* Parent */ break; } (void) memset(&ignore, 0, sizeof (ignore)); ignore.sa_handler = SIG_IGN; (void) sigaction(SIGINT, &ignore, &ibuf); (void) sigaction(SIGQUIT, &ignore, &qbuf); do { w = waitpid(pid, &status, 0); } while (w == -1 && errno == EINTR); (void) sigaction(SIGINT, &ibuf, NULL); (void) sigaction(SIGQUIT, &qbuf, NULL); (void) sigaction(SIGCHLD, &cbuf, NULL); (void) sigprocmask(SIG_SETMASK, &savemask, NULL); return ((w == -1)? w: status); } /* * Free all commands on the cmd queue */ static void abort_cmd_queue() { cmdqueue_t *cmd; cmdqueue_t *next; int nevents = 0; while ((cmd = cmdq) != NULL) { next = cmd->next; cmdq_cnt--; sysevent_free(cmd->event); sc_free(cmd, sizeof (cmdqueue_t)); cmdq = next; nevents++; } cmdq_tail = NULL; /* * Generate error msgs if events were discarded or * we are entering the disabled state. */ if (nevents > 0) { syslog(LOG_ERR, N_EVENTS_DISCARDED_ERR, nevents); } if (want_fini == 0) { confd_state = CONFD_STATE_DISABLED; syslog(LOG_ERR, SERVICE_DISABLED_MSG); } } /* * For a matching event specification, build the command to be * invoked in response to the event. Building the command involves * expanding macros supplied in the event specification command * with values from the actual event. These macros can be * the class/subclass/vendor/publisher strings, or arbitrary * attribute data attached to the event. * * This module does not invoke (fork/exec) the command itself, * since this module is running in the context of the syseventd * daemon, and fork/exec's done here interfere with the door * upcall delivering events from the kernel to the daemon. * Instead, we build a separate event and nvlist with the * attributes of the command to be invoked, and pass that on * to the syseventconfd daemon, which is basically a fork/exec * server on our behalf. * * Errors queuing the event are returned to syseventd with * EAGAIN, allowing syseventd to manage a limited number of * retries after a short delay. */ static int queue_event(sysevent_t *ev, syseventtab_t *sep, sysevent_hdr_info_t *hdr) { str_t *line; nvlist_t *nvlist; char *argv0; sysevent_t *cmd_event; nvlist_t *cmd_nvlist; cmdqueue_t *new_cmd; if ((line = initstr(128)) == NULL) return (1); if ((argv0 = strrchr(sep->se_path, '/')) == NULL) { argv0 = sep->se_path; } else { argv0++; } if (strcopys(line, argv0)) { freestr(line); return (1); } if (sep->se_args) { if (strcats(line, " ")) { freestr(line); return (1); } if (strcats(line, sep->se_args)) { freestr(line); return (1); } if (sysevent_get_attr_list(ev, &nvlist) != 0) { syslog(LOG_ERR, GET_ATTR_LIST_ERR, sep->se_conf_file, sep->se_lineno, strerror(errno)); freestr(line); return (1); } if (expand_macros(ev, nvlist, sep, line, hdr)) { freestr(line); nvlist_free(nvlist); return (1); } nvlist_free(nvlist); } if (debug_level >= DBG_EXEC) { syseventd_print(DBG_EXEC, "%s, line %d: path = %s\n", sep->se_conf_file, sep->se_lineno, sep->se_path); syseventd_print(DBG_EXEC, " cmd = %s\n", line->s_str); } cmd_nvlist = NULL; if ((errno = nvlist_alloc(&cmd_nvlist, NV_UNIQUE_NAME, 0)) != 0) { freestr(line); syslog(LOG_ERR, NVLIST_ALLOC_ERR, sep->se_conf_file, sep->se_lineno, strerror(errno)); return (1); } if ((errno = nvlist_add_string(cmd_nvlist, "path", sep->se_path)) != 0) goto err; if ((errno = nvlist_add_string(cmd_nvlist, "cmd", line->s_str)) != 0) goto err; if ((errno = nvlist_add_string(cmd_nvlist, "file", sep->se_conf_file)) != 0) goto err; if ((errno = nvlist_add_int32(cmd_nvlist, "line", sep->se_lineno)) != 0) goto err; if ((errno = nvlist_add_string(cmd_nvlist, "user", sep->se_user)) != 0) goto err; if (sep->se_uid != (uid_t)0) { if ((errno = nvlist_add_int32(cmd_nvlist, "uid", sep->se_uid)) != 0) goto err; if ((errno = nvlist_add_int32(cmd_nvlist, "gid", sep->se_gid)) != 0) goto err; } cmd_event = sysevent_alloc_event(hdr->class, hdr->subclass, hdr->vendor, hdr->publisher, cmd_nvlist); if (cmd_event == NULL) { syslog(LOG_ERR, SYSEVENT_ALLOC_ERR, sep->se_conf_file, sep->se_lineno, strerror(errno)); nvlist_free(cmd_nvlist); freestr(line); return (1); } nvlist_free(cmd_nvlist); freestr(line); /* * Place cmd_event on queue to be transported to syseventconfd */ if ((new_cmd = sc_malloc(sizeof (cmdqueue_t))) == NULL) { sysevent_free(cmd_event); return (1); } new_cmd->event = cmd_event; new_cmd->next = NULL; (void) mutex_lock(&cmdq_lock); if (cmdq == NULL) { cmdq = new_cmd; } else { cmdq_tail->next = new_cmd; } cmdq_cnt++; cmdq_tail = new_cmd; /* * signal queue flush thread */ (void) cond_signal(&cmdq_cv); (void) mutex_unlock(&cmdq_lock); return (0); err: syslog(LOG_ERR, NVLIST_BUILD_ERR, sep->se_conf_file, sep->se_lineno, strerror(errno)); nvlist_free(cmd_nvlist); freestr(line); return (1); } static int transport_event(sysevent_t *event) { int rval; rval = sysevent_send_event(confd_handle, event); if (rval != 0) { switch (errno) { case EAGAIN: case EINTR: /* * syseventconfd daemon may be forking, stop * attempting to empty the queue momentarily. */ rval = errno; break; case ENOENT: case EBADF: /* * start/restart the syseventconfd daemon, * allowing for some delay when starting * up before it begins to reply. */ if (confd_state == CONFD_STATE_NOT_RUNNING || confd_state == CONFD_STATE_OK) { confd_state = CONFD_STATE_STARTED; start_syseventconfd(); confd_retries = 0; rval = EAGAIN; } else if (confd_state == CONFD_STATE_STARTED && confd_retries < 16) { if (++confd_retries == 16) { confd_state = CONFD_STATE_ERR; if (confd_err_msg_emitted == 0) { syslog(LOG_ERR, SYSEVENTCONFD_ERR); confd_err_msg_emitted = 1; } } rval = EAGAIN; } else { rval = errno; } break; default: syslog(LOG_ERR, SYSEVENTCONFD_TRAN_ERR, strerror(errno)); rval = errno; break; } } else if (confd_state != CONFD_STATE_OK) { if (confd_state == CONFD_STATE_ERR) { syslog(LOG_ERR, SYSEVENTCONFD_OK); confd_err_msg_emitted = 0; } confd_state = CONFD_STATE_OK; confd_retries = 0; confd_err_msg_emitted = 0; } return (rval); } /* * Send events on queue to syseventconfd daemon. We queue events * here since the daemon is unable to handle events during an * active fork/exec, returning EAGAIN as a result. It is grossly * inefficient to bounce these events back to syseventd, so * we queue them here for delivery. * * EAGAIN/EINTR don't indicate errors with the transport to * syseventconfd itself, just the daemon is busy or some * other transient difficulty. We retry EBADF and other errors * for some time, then eventually give up - something's broken. * * Error handling strategy: * If we're trying to shut down and the syseventconfd daemon isn't * responding, abort the queue so we don't cause the fini to hang * forever. Otherwise, EAGAIN/EINTR are retried forever, as * we presume the daemon is active but either busy or some transient * state is preventing the transport. We make considerable effort * to retry EBADF since the daemon may take some time to come up when * restarted so don't want to give up too easily. Once we enter * the DISABLED state, we stop handling events altogther to * avoid thrashing the system if the syseventconfd binary is * corrupted or missing. This state can be cleared by issuing * a HUP signal to the syseventd daemon. For errors other than * EAGAIN/EINTR/EBADF, we just drop the event and if we get * a certain number of these in a row, we enter the DISABLED * state. */ static void transport_queued_events() { int rval; cmdqueue_t *cmd; (void) mutex_lock(&cmdq_lock); while (cmdq != NULL) { cmd = cmdq; (void) mutex_unlock(&cmdq_lock); rval = transport_event(cmd->event); (void) mutex_lock(&cmdq_lock); if (rval != 0) { switch (rval) { case EAGAIN: case EINTR: /* * Limit retries in the case of fini */ if (want_fini) { if (++transport_retries == 16) { abort_cmd_queue(); } } (void) mutex_unlock(&cmdq_lock); return; case EBADF: /* * retry up to 16 times */ if (want_fini || ++transport_retries == 16) { abort_cmd_queue(); } (void) mutex_unlock(&cmdq_lock); return; default: /* * After 16 sequential errors, give up */ if (++transport_retries == 16) { abort_cmd_queue(); (void) mutex_unlock(&cmdq_lock); return; } /* * We don't retry these errors, we * fall through to remove this event * from the queue. */ break; } } else { transport_retries = 0; } /* * Take completed event off queue */ cmdq_cnt--; cmdq = cmdq->next; if (cmdq == NULL) { cmdq_tail = NULL; } (void) mutex_unlock(&cmdq_lock); sysevent_free(cmd->event); sc_free(cmd, sizeof (cmdqueue_t)); (void) mutex_lock(&cmdq_lock); } (void) mutex_unlock(&cmdq_lock); } static void * queue_flush_thr(void *arg __unused) { int n; (void) mutex_lock(&cmdq_lock); for (;;) { while (cmdq_cnt == 0 && want_fini == 0) { (void) cond_wait(&cmdq_cv, &cmdq_lock); } if (cmdq_cnt == 0 && want_fini) { (void) cond_signal(&cmdq_thr_cv); (void) mutex_unlock(&cmdq_lock); thr_exit(NULL); /*NOTREACHED*/ } (void) mutex_unlock(&cmdq_lock); transport_queued_events(); (void) mutex_lock(&cmdq_lock); if (cmdq_cnt != 0) { (void) mutex_unlock(&cmdq_lock); if (want_fini == 0 && confd_err_msg_emitted) { for (n = 0; n < 60; n++) { (void) sleep(1); if (want_fini) break; } } else { (void) sleep(1); } (void) mutex_lock(&cmdq_lock); } } } /* * syseventd daemon module event handler * * The syseventd daemon calls this handler with each event * for this module to handle the event as appropriate. * The task of this module is to compare the event's * class/subclass/publisher/vendor against the list of * event specifications provided in the installed * sysevent.conf files. Build and execute the * defined command for that event specification * for each match. * * Events are matched against the class, subclass, vendor * and publisher specifications. Any field not to be matched * against an event should be set to '-'. A specification * of '- - - -' generates a match against every event. */ /*ARGSUSED*/ static int sysevent_conf_event(sysevent_t *ev, int flag) { int ret = 0; char *vendor; char *publisher; char *class; char *subclass; syseventtab_t *sep; sysevent_hdr_info_t hdr; uint64_t seq; hrtime_t ts; /* * If we've been completely unable to communicate with * syseventconfd, there's not much we can do. */ if (confd_state == CONFD_STATE_DISABLED) { return (0); } /* * sysevent_get_seq(ev) < ev_seq): * an event we have played before, ignore it * sysevent_get_seq(ev) == ev_seq): * ev_nretries > 0, an event being retried * sysevent_get_seq(ev) > ev_seq): * a new event */ if (debug_level >= DBG_EVENTS) { if (sysevent_get_seq(ev) == ev_seq && ev_nretries > 0) { syseventd_print(DBG_EVENTS, "sequence: %lld/%lld, retry %d\n", sysevent_get_seq(ev), ev_seq, ev_nretries); } else if (sysevent_get_seq(ev) > ev_seq) { syseventd_print(DBG_EVENTS, "sequence: %lld/%lld\n", sysevent_get_seq(ev), ev_seq); } } seq = sysevent_get_seq(ev); sysevent_get_time(ev, &ts); if (seq > ev_seq || ts > ev_ts) { ev_nretries = 0; } else if (first_event == 0 && (((seq < ev_seq) || (seq == 0 && ts > ev_ts)) || (seq == ev_seq && ev_nretries == 0))) { syseventd_print(DBG_TEST, "out-of-order sequence: received %lld/0x%llx, " "expected %lld/0x%llx\n", seq, ts, ev_seq+1, ev_ts); return (ret); } ev_ts = ts; ev_seq = seq; first_event = 0; /* * sysevent_get_vendor_name() and sysevent_get_pub_name() * allocate strings which must be freed. */ vendor = sysevent_get_vendor_name(ev); publisher = sysevent_get_pub_name(ev); class = sysevent_get_class_name(ev); subclass = sysevent_get_subclass_name(ev); if (vendor == NULL || publisher == NULL) { syseventd_print(DBG_EVENTS, "Short on memory with vendor " "and/or publisher string generation\n"); /* Temporary short on memory */ ev_nretries++; free(publisher); free(vendor); return (EAGAIN); } syseventd_print(DBG_EVENTS, "%s event %lld: vendor='%s' publisher='%s' class='%s' " "subclass='%s'\n", whoami, sysevent_get_seq(ev), vendor, publisher, class, subclass); for (sep = syseventtab; sep; sep = sep->se_next) { if (strcmp(sep->se_vendor, "-") != 0) { if (strcmp(sep->se_vendor, vendor) != 0) continue; } if (strcmp(sep->se_publisher, "-") != 0) { if (strcmp(sep->se_publisher, publisher) != 0) continue; } if (strcmp(sep->se_class, "-") != 0) { if (strcmp(sep->se_class, class) != 0) continue; } if (strcmp(sep->se_subclass, "-") != 0) { if (strcmp(sep->se_subclass, subclass) != 0) continue; } syseventd_print(DBG_MATCHES, " event match: %s, line %d\n", sep->se_conf_file, sep->se_lineno); hdr.class = class; hdr.subclass = subclass; hdr.vendor = vendor; hdr.publisher = publisher; if ((ret = queue_event(ev, sep, &hdr)) != 0) break; } if (ret == 0) { ev_nretries = 0; } else { /* * Ask syseventd to retry any failed event. If we have * reached the limit on retries, emit a msg that we're * not going to be able to service it. */ if (ev_nretries == SE_MAX_RETRY_LIMIT) { syslog(LOG_ERR, SYSEVENT_SEND_ERR, sep->se_conf_file, sep->se_lineno, errno); } else { syseventd_print(DBG_TEST, "%s event %lld: " "'%s' '%s' '%s' '%s - errno %d, retry %d\n", whoami, sysevent_get_seq(ev), vendor, publisher, class, subclass, errno, ev_nretries); } ret = EAGAIN; ev_nretries++; } free(publisher); free(vendor); return (ret); } /* * syseventd daemon module initialization */ struct slm_mod_ops * slm_init() { char lock_file[PATH_MAX+1]; int lock_fd; int err; /* * This functionality is not supported in the mini-root * environment, ie install. If root_dir is set, implying * install, we quietly fail. Return dummy ops rather * than NULL to avoid error msgs out of syseventd. */ if (strcmp(root_dir, "") != 0) { return (&sysevent_conf_dummy_mod_ops); } ev_nretries = 0; first_event = 1; /* * Initialize the channel to syseventconfd */ confd_handle = sysevent_open_channel_alt(SYSEVENTCONFD_SERVICE_DOOR); if (confd_handle == NULL) { syslog(LOG_ERR, CHANNEL_OPEN_ERR); return (NULL); } if (sysevent_bind_publisher(confd_handle) != 0) { if (errno == EBUSY) { sysevent_cleanup_publishers(confd_handle); if (sysevent_bind_publisher(confd_handle) != 0) { sysevent_close_channel(confd_handle); return (NULL); } } } sysevent_cleanup_subscribers(confd_handle); cmdq = NULL; cmdq_tail = NULL; cmdq_cnt = 0; want_fini = 0; confd_err_msg_emitted = 0; if (confd_state != CONFD_STATE_OK) { confd_state = CONFD_STATE_NOT_RUNNING; } confd_retries = 0; transport_retries = 0; (void) mutex_init(&cmdq_lock, USYNC_THREAD, NULL); (void) cond_init(&cmdq_cv, USYNC_THREAD, NULL); (void) cond_init(&cmdq_thr_cv, USYNC_THREAD, NULL); /* * Create thread to flush cmd queue */ if ((err = thr_create(NULL, 0, queue_flush_thr, NULL, 0, &cmdq_thr_id)) != 0) { syslog(LOG_ERR, THR_CREATE_ERR, strerror(err)); sysevent_close_channel(confd_handle); confd_handle = NULL; (void) mutex_destroy(&cmdq_lock); (void) cond_destroy(&cmdq_cv); (void) cond_destroy(&cmdq_thr_cv); return (NULL); } if ((lock_fd = enter_lock(lock_file)) == -1) { (void) thr_join(cmdq_thr_id, NULL, NULL); sysevent_close_channel(confd_handle); confd_handle = NULL; (void) mutex_destroy(&cmdq_lock); (void) cond_destroy(&cmdq_cv); (void) cond_destroy(&cmdq_thr_cv); return (NULL); } build_event_table(); exit_lock(lock_fd, lock_file); return (&sysevent_conf_mod_ops); } /* * syseventd daemon module tear-down */ void slm_fini() { int err; /* * Nothing to clean up if we're in the install environment */ if (strcmp(root_dir, "") != 0) { return; } /* * Wait for the queue to drain */ (void) mutex_lock(&cmdq_lock); want_fini = 1; (void) cond_signal(&cmdq_cv); (void) cond_wait(&cmdq_thr_cv, &cmdq_lock); (void) mutex_unlock(&cmdq_lock); /* * Shut down the the queue flush thread */ if ((err = thr_join(cmdq_thr_id, NULL, NULL)) != 0) { syslog(LOG_ERR, THR_JOIN_ERR, strerror(err)); } sysevent_close_channel(confd_handle); confd_handle = NULL; (void) mutex_destroy(&cmdq_lock); (void) cond_destroy(&cmdq_cv); (void) cond_destroy(&cmdq_thr_cv); free_event_table(); } /*ARGSUSED*/ static int sysevent_conf_dummy_event(sysevent_t *ev, int flag) { return (0); } /* * 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 _SYSEVENT_CONF_MOD_H #define _SYSEVENT_CONF_MOD_H #ifdef __cplusplus extern "C" { #endif /* * syseventd_print debug levels for sysevent_conf_mod */ #define DBG_TEST 1 /* info of interest when testing */ #define DBG_EXEC 2 /* path and args to exec */ #define DBG_EVENTS 3 /* received events */ #define DBG_MATCHES 4 /* dump specs for matching events */ #define DBG_MACRO 5 /* macro expansion */ #define DBG_CONF_FILE 6 /* sysevent.conf parsing */ #define DBG_DETAILED 7 /* all the above and more */ /* * Directory where sysevent.conf files reside */ #define SYSEVENT_CONFIG_DIR "/etc/sysevent/config" /* * Lock file name to serialize registry updates */ #define LOCK_FILENAME "/var/run/syseventconf.lock" /* * sysevent.conf files list */ typedef struct conftab { char *cf_conf_file; /* source conf file */ struct conftab *cf_next; } conftab_t; /* * sysevent.conf table */ typedef struct syseventtab { char *se_conf_file; /* source conf file */ int se_lineno; /* line number */ char *se_vendor; /* vendor */ char *se_publisher; /* publisher */ char *se_class; /* event class */ char *se_subclass; /* event subclass */ char *se_user; /* user */ char *se_reserved1; /* reserved1 */ char *se_reserved2; /* reserved2 */ char *se_path; /* event path */ char *se_args; /* optional args */ uid_t se_uid; /* user id */ gid_t se_gid; /* group id */ struct syseventtab *se_next; } syseventtab_t; typedef struct sysevent_hdr_info { char *class; char *subclass; char *vendor; char *publisher; } sysevent_hdr_info_t; /* * Structures for building arbitarily long strings and argument lists */ typedef struct str { char *s_str; int s_len; int s_alloc; int s_hint; } str_t; /* * Queue of commands ready to be transported to syseventconfd */ typedef struct cmdqueue { sysevent_t *event; struct cmdqueue *next; } cmdqueue_t; /* * syseventconfd state */ enum { CONFD_STATE_OK, CONFD_STATE_NOT_RUNNING, CONFD_STATE_STARTED, CONFD_STATE_ERR, CONFD_STATE_DISABLED }; /* * Prototypes */ static char *skip_spaces(char **cpp); static char *next_field(char **cpp); static void *sc_malloc(size_t n); static void *sc_realloc(void *p, size_t current, size_t n); static void sc_free(void *p, size_t n); static char *sc_strdup(char *cp); static void sc_strfree(char *s); static str_t *initstr(int hint); static void freestr(str_t *str); static void resetstr(str_t *str); static int strcopys(str_t *str, char *s); static int strcats(str_t *str, char *s); static int strcatc(str_t *str, int c); static char *fstrgets(str_t *str, FILE *fp); static void strtrunc(str_t *str, int pos); static void build_event_table(void); static void free_event_table(void); static int enter_lock(char *lock_file); static void exit_lock(int lock_fd, char *lock_file); static str_t *snip_identifier(char *id, char **end); static str_t *snip_delimited_identifier(char *id, char **end); static char *se_attr_type_to_str(int se_attr_type); static str_t *find_macro_definition(sysevent_t *ev, nvlist_t *nvlist, syseventtab_t *sep, char *token, sysevent_hdr_info_t *hdr); static int expand_macros(sysevent_t *ev, nvlist_t *nvlist, syseventtab_t *sep, str_t *line, sysevent_hdr_info_t *hdr); static void start_syseventconfd(void); static int system1(const char *s_path, const char *s); static void abort_cmd_queue(void); static int queue_event(sysevent_t *ev, syseventtab_t *sep, sysevent_hdr_info_t *hdr); static int transport_event(sysevent_t *cmd); static void transport_queued_events(void); static int sysevent_conf_event(sysevent_t *ev, int flag); #ifdef __cplusplus } #endif #endif /* _SYSEVENT_CONF_MOD_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 # # #ident "%Z%%M% %I% %E% SMI" # # Copyright (c) 2001 by Sun Microsystems, Inc. # All rights reserved. # # cmd/syseventd/modules/sysevent_reg_mod/Makefile # LIBRARY = sysevent_reg_mod include ../Makefile.com .KEEP_STATE: all: $(DYNLIB) install: all \ $(ROOTLIBSYSEVENTDIR) \ $(ROOTLIBDIR) \ $(ROOTLIBS) 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-2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MESSAGE_REG_MOD_H #define _MESSAGE_REG_MOD_H #ifdef __cplusplus extern "C" { #endif #define INIT_SUB_OPEN_CHAN_ERR \ gettext("sysevent_reg_mod: Can not open subscriber channel: %s\n") #define INIT_SUB_BIND_PUB_ERR \ gettext("sysevent_reg_mod: Can not bind publisher: %s\n") #define INIT_SUB_THR_CREATE_ERR \ gettext("sysevent_reg_mod: Can not create subscriber " \ "deliver thread: %s\n") #define INIT_ACCESS_ERR \ gettext("sysevent_reg_mod: syseventd channel permissions invalid\n") #ifdef __cplusplus } #endif #endif /* _MESSAGE_REG_MOD_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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "message_reg_mod.h" /* * SLM for sysevent event subscribers */ extern char *root_dir; extern void syseventd_print(int level, char *format, ...); extern void syseventd_err_print(char *format, ...); sysevent_handle_t *sysevent_hp; typedef struct ev_queue { struct ev_queue *evq_next; sysevent_t *evq_ev; } ev_queue_t; static mutex_t evq_lock; static cond_t evq_cv; static ev_queue_t *event_q = NULL; static int cleanup; static thread_t deliver_thr_id; static int init_channel() { /* * This functionality is not supported in the mini-root * environment, ie install. If root_dir is set, implying * install, we quietly fail. */ if (strcmp(root_dir, "") != 0) { return (EACCES); } /* * Initialize the private sysevent handle */ sysevent_hp = sysevent_open_channel(SYSEVENTD_CHAN); if (sysevent_hp == NULL) { if (errno == EACCES) { syseventd_print(3, "sysevent_reg_mod: " "sysevent_open_channel failed with %s init " "deferred\n", strerror(errno)); return (errno); } else { syseventd_err_print(INIT_SUB_OPEN_CHAN_ERR, strerror(errno)); return (errno); } } if (sysevent_bind_publisher(sysevent_hp) != 0) { /* * Only one publisher allowed on the syseventd channel, * cleanup previously allocated syseventd channel publishers */ if (errno == EBUSY) { sysevent_cleanup_publishers(sysevent_hp); if (sysevent_bind_publisher(sysevent_hp) == 0) return (0); } syseventd_err_print(INIT_SUB_BIND_PUB_ERR, strerror(errno)); sysevent_close_channel(sysevent_hp); sysevent_hp = NULL; return (errno); } return (0); } static int deliver_event(sysevent_t *ev, int flag) { int ret, ev_size; ev_queue_t *new_evq, *tmp_evq; /* Not initialized */ if (sysevent_hp == NULL) { ret = init_channel(); if (ret != 0) { if (ret == EBUSY && flag != SE_NO_RETRY) { return (EAGAIN); } else if (ret == EACCES) { return (0); } else { syseventd_err_print(INIT_SUB_OPEN_CHAN_ERR, strerror(ret)); return (0); } } /* Check for stale syseventd subscribers */ sysevent_cleanup_subscribers(sysevent_hp); syseventd_print(3, "sysevent_reg_mod: init successful"); } /* Queue event for delivery to all subscribers */ new_evq = (ev_queue_t *)calloc(1, sizeof (ev_queue_t)); if (new_evq == NULL) { return (EAGAIN); } ev_size = sysevent_get_size(ev); new_evq->evq_ev = (sysevent_t *)malloc(ev_size); if (new_evq->evq_ev == NULL) { free(new_evq); return (EAGAIN); } bcopy(ev, new_evq->evq_ev, ev_size); (void) mutex_lock(&evq_lock); if (event_q == NULL) { event_q = new_evq; } else { tmp_evq = event_q; while (tmp_evq->evq_next != NULL) tmp_evq = tmp_evq->evq_next; tmp_evq->evq_next = new_evq; } syseventd_print(3, "sysevent_reg_mod: queue event 0X%llx\n", sysevent_get_seq(ev)); (void) cond_signal(&evq_cv); (void) mutex_unlock(&evq_lock); return (0); } void * subscriber_deliver_thr(void *arg __unused) { ev_queue_t *evqp; (void) mutex_lock(&evq_lock); for (;;) { while (event_q == NULL && cleanup == 0) { (void) cond_wait(&evq_cv, &evq_lock); } /* Send events on to all current subscribers */ evqp = event_q; while (evqp) { (void) mutex_unlock(&evq_lock); syseventd_print(3, "sysevent_reg_mod: sending event " "0X%llx\n", sysevent_get_seq(evqp->evq_ev)); if (sysevent_send_event(sysevent_hp, evqp->evq_ev) != 0) { syseventd_print(3, "sysevent_reg_mod: " "failed to send event\n"); } syseventd_print(3, "sysevent_reg_mod: event sent " "0X%llx\n", sysevent_get_seq(evqp->evq_ev)); (void) mutex_lock(&evq_lock); event_q = evqp->evq_next; free(evqp->evq_ev); free(evqp); evqp = event_q; } if (cleanup) { syseventd_print(3, "sysevent_reg_mod: deliver " "thread exiting\n"); (void) mutex_unlock(&evq_lock); (void) thr_exit(NULL); /* NOTREACHED */ } } /* NOTREACHED */ } static struct slm_mod_ops sysevent_reg_mod_ops = { SE_MAJOR_VERSION, SE_MINOR_VERSION, SE_MAX_RETRY_LIMIT, deliver_event}; struct slm_mod_ops * slm_init() { cleanup = 0; sysevent_hp = NULL; (void) init_channel(); (void) mutex_init(&evq_lock, USYNC_THREAD, NULL); (void) cond_init(&evq_cv, USYNC_THREAD, NULL); if (thr_create(NULL, 0, (void *(*)(void *))subscriber_deliver_thr, NULL, 0, &deliver_thr_id) != 0) { syseventd_err_print(INIT_SUB_THR_CREATE_ERR, strerror(errno)); return (NULL); } return (&sysevent_reg_mod_ops); } void slm_fini() { (void) mutex_lock(&evq_lock); cleanup = 1; (void) cond_signal(&evq_cv); (void) mutex_unlock(&evq_lock); /* Wait for delivery threads to exit */ (void) thr_join(deliver_thr_id, NULL, NULL); (void) mutex_destroy(&evq_lock); (void) cond_destroy(&evq_cv); sysevent_close_channel(sysevent_hp); sysevent_hp = NULL; } # # 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 (c) 2010, Oracle and/or its affiliates. All rights reserved. # LIBRARY= zfs_mod include ../Makefile.com CPPFLAGS += -I../../../../lib/libc/inc LDLIBS += -lzfs -ldevid -lcmdutils -lnvpair CSTD= $(CSTD_GNU99) CERRWARN += $(CNOWARN_UNINIT) .KEEP_STATE: all: $(DYNLIB) install: all \ $(ROOTLIBSYSEVENTDIR) \ $(ROOTLIBDIR) \ $(ROOTLIBS) include ../Makefile.targ /* * 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012 by Delphix. All rights reserved. * Copyright 2016 Nexenta Systems, Inc. All rights reserved. * Copyright 2022 OmniOS Community Edition (OmniOSce) Association. * Copyright 2022 Oxide Computer Company */ /* * ZFS syseventd module. * * The purpose of this module is to identify when devices are added to the * system, and appropriately online or replace the affected vdevs. * * When a device is added to the system: * * 1. Search for any vdevs whose devid matches that of the newly added * device. * * 2. If no vdevs are found, then search for any vdevs whose devfs path * matches that of the new device. * * 3. If no vdevs match by either method, then ignore the event. * * 4. Attempt to online the device with a flag to indicate that it should * be unspared when resilvering completes. If this succeeds, then the * same device was inserted and we should continue normally. * * 5. If the pool does not have the 'autoreplace' property set, attempt to * online the device again without the unspare flag, which will * generate a FMA fault. * * 6. If the pool has the 'autoreplace' property set, and the matching vdev * is a whole disk, then label the new disk and attempt a 'zpool * replace'. * * The module responds to EC_DEV_ADD events for both disks and lofi devices, * with the latter used for testing. The special ESC_ZFS_VDEV_CHECK event * indicates that a device failed to open during pool load, but the autoreplace * property was set. In this case, we deferred the associated FMA fault until * our module had a chance to process the autoreplace logic. If the device * could not be replaced, then the second online attempt will trigger the FMA * fault that we skipped earlier. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "syseventd.h" #if defined(__i386) || defined(__amd64) #define PHYS_PATH ":q" #define RAW_SLICE "p0" #elif defined(__sparc) #define PHYS_PATH ":c" #define RAW_SLICE "s2" #else #error Unknown architecture #endif typedef void (*zfs_process_func_t)(zpool_handle_t *, nvlist_t *, boolean_t); libzfs_handle_t *g_zfshdl; list_t g_pool_list; tpool_t *g_tpool; boolean_t g_enumeration_done; thread_t g_zfs_tid; typedef struct unavailpool { zpool_handle_t *uap_zhp; list_node_t uap_node; } unavailpool_t; int zfs_toplevel_state(zpool_handle_t *zhp) { nvlist_t *nvroot; vdev_stat_t *vs; unsigned int c; verify(nvlist_lookup_nvlist(zpool_get_config(zhp, NULL), ZPOOL_CONFIG_VDEV_TREE, &nvroot) == 0); verify(nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_VDEV_STATS, (uint64_t **)&vs, &c) == 0); return (vs->vs_state); } static int zfs_unavail_pool(zpool_handle_t *zhp, void *data) { if (zfs_toplevel_state(zhp) < VDEV_STATE_DEGRADED) { unavailpool_t *uap; uap = malloc(sizeof (unavailpool_t)); uap->uap_zhp = zhp; list_insert_tail((list_t *)data, uap); } else { zpool_close(zhp); } return (0); } /* * The device associated with the given vdev (either by devid or physical path) * has been added to the system. If 'isdisk' is set, then we only attempt a * replacement if it's a whole disk. This also implies that we should label the * disk first. * * First, we attempt to online the device (making sure to undo any spare * operation when finished). If this succeeds, then we're done. If it fails, * and the new state is VDEV_CANT_OPEN, it indicates that the device was opened, * but that the label was not what we expected. If the 'autoreplace' property * is not set, then we relabel the disk (if specified), and attempt a 'zpool * replace'. If the online is successful, but the new state is something else * (REMOVED or FAULTED), it indicates that we're out of sync or in some sort of * race, and we should avoid attempting to relabel the disk. */ static void zfs_process_add(zpool_handle_t *zhp, nvlist_t *vdev, boolean_t isdisk) { char *path; vdev_state_t newstate; nvlist_t *nvroot, *newvd; uint64_t wholedisk = 0ULL; uint64_t offline = 0ULL; char *physpath = NULL; char rawpath[PATH_MAX], fullpath[PATH_MAX]; zpool_boot_label_t boot_type; uint64_t boot_size; size_t len; if (nvlist_lookup_string(vdev, ZPOOL_CONFIG_PATH, &path) != 0) return; (void) nvlist_lookup_string(vdev, ZPOOL_CONFIG_PHYS_PATH, &physpath); (void) nvlist_lookup_uint64(vdev, ZPOOL_CONFIG_WHOLE_DISK, &wholedisk); (void) nvlist_lookup_uint64(vdev, ZPOOL_CONFIG_OFFLINE, &offline); /* * We should have a way to online a device by guid. With the current * interface, we are forced to chop off the 's0' for whole disks. */ (void) strlcpy(fullpath, path, sizeof (fullpath)); if (wholedisk) fullpath[strlen(fullpath) - 2] = '\0'; /* * Attempt to online the device. It would be nice to online this by * GUID, but the current interface only supports lookup by path. */ if (offline || (zpool_vdev_online(zhp, fullpath, ZFS_ONLINE_CHECKREMOVE | ZFS_ONLINE_UNSPARE, &newstate) == 0 && (newstate == VDEV_STATE_HEALTHY || newstate == VDEV_STATE_DEGRADED))) return; /* * If the pool doesn't have the autoreplace property set, then attempt a * true online (without the unspare flag), which will trigger a FMA * fault. */ if (!zpool_get_prop_int(zhp, ZPOOL_PROP_AUTOREPLACE, NULL) || (isdisk && !wholedisk)) { (void) zpool_vdev_online(zhp, fullpath, ZFS_ONLINE_FORCEFAULT, &newstate); return; } if (isdisk) { /* * If this is a request to label a whole disk, then attempt to * write out the label. Before we can label the disk, we need * access to a raw node. Ideally, we'd like to walk the devinfo * tree and find a raw node from the corresponding parent node. * This is overly complicated, and since we know how we labeled * this device in the first place, we know it's save to switch * from /dev/dsk to /dev/rdsk and append the backup slice. * * If any part of this process fails, then do a force online to * trigger a ZFS fault for the device (and any hot spare * replacement). */ if (strncmp(path, ZFS_DISK_ROOTD, strlen(ZFS_DISK_ROOTD)) != 0) { (void) zpool_vdev_online(zhp, fullpath, ZFS_ONLINE_FORCEFAULT, &newstate); return; } (void) strlcpy(rawpath, path + 9, sizeof (rawpath)); len = strlen(rawpath); rawpath[len - 2] = '\0'; if (zpool_is_bootable(zhp)) boot_type = ZPOOL_COPY_BOOT_LABEL; else boot_type = ZPOOL_NO_BOOT_LABEL; boot_size = zpool_get_prop_int(zhp, ZPOOL_PROP_BOOTSIZE, NULL); if (zpool_label_disk(g_zfshdl, zhp, rawpath, boot_type, boot_size, NULL) != 0) { (void) zpool_vdev_online(zhp, fullpath, ZFS_ONLINE_FORCEFAULT, &newstate); return; } } /* * Cosntruct the root vdev to pass to zpool_vdev_attach(). While adding * the entire vdev structure is harmless, we construct a reduced set of * path/physpath/wholedisk to keep it simple. */ if (nvlist_alloc(&nvroot, NV_UNIQUE_NAME, 0) != 0) return; if (nvlist_alloc(&newvd, NV_UNIQUE_NAME, 0) != 0) { nvlist_free(nvroot); return; } if (nvlist_add_string(newvd, ZPOOL_CONFIG_TYPE, VDEV_TYPE_DISK) != 0 || nvlist_add_string(newvd, ZPOOL_CONFIG_PATH, path) != 0 || (physpath != NULL && nvlist_add_string(newvd, ZPOOL_CONFIG_PHYS_PATH, physpath) != 0) || nvlist_add_uint64(newvd, ZPOOL_CONFIG_WHOLE_DISK, wholedisk) != 0 || nvlist_add_string(nvroot, ZPOOL_CONFIG_TYPE, VDEV_TYPE_ROOT) != 0 || nvlist_add_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN, &newvd, 1) != 0) { nvlist_free(newvd); nvlist_free(nvroot); return; } nvlist_free(newvd); (void) zpool_vdev_attach(zhp, fullpath, path, nvroot, B_TRUE); nvlist_free(nvroot); } /* * Utility functions to find a vdev matching given criteria. */ typedef struct dev_data { const char *dd_compare; const char *dd_prop; zfs_process_func_t dd_func; boolean_t dd_found; boolean_t dd_isdisk; uint64_t dd_pool_guid; uint64_t dd_vdev_guid; } dev_data_t; static void zfs_iter_vdev(zpool_handle_t *zhp, nvlist_t *nvl, void *data) { dev_data_t *dp = data; char *path; uint_t c, children; nvlist_t **child; size_t len; uint64_t guid; /* * First iterate over any children. */ if (nvlist_lookup_nvlist_array(nvl, ZPOOL_CONFIG_CHILDREN, &child, &children) == 0) { for (c = 0; c < children; c++) zfs_iter_vdev(zhp, child[c], data); return; } if (dp->dd_vdev_guid != 0) { if (nvlist_lookup_uint64(nvl, ZPOOL_CONFIG_GUID, &guid) != 0 || guid != dp->dd_vdev_guid) return; } else if (dp->dd_compare != NULL) { len = strlen(dp->dd_compare); if (nvlist_lookup_string(nvl, dp->dd_prop, &path) != 0 || strncmp(dp->dd_compare, path, len) != 0) return; /* * Normally, we want to have an exact match for the comparison * string. However, we allow substring matches in the following * cases: * * : This is a devpath, and the target is one * of its children. * * This is a devid for a whole disk, and * the target is one of its children. */ if (path[len] != '\0' && path[len] != ':' && path[len - 1] != '/') return; } (dp->dd_func)(zhp, nvl, dp->dd_isdisk); } void zfs_enable_ds(void *arg) { unavailpool_t *pool = (unavailpool_t *)arg; (void) zpool_enable_datasets(pool->uap_zhp, NULL, 0); zpool_close(pool->uap_zhp); free(pool); } static int zfs_iter_pool(zpool_handle_t *zhp, void *data) { nvlist_t *config, *nvl; dev_data_t *dp = data; uint64_t pool_guid; unavailpool_t *pool; if ((config = zpool_get_config(zhp, NULL)) != NULL) { if (dp->dd_pool_guid == 0 || (nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID, &pool_guid) == 0 && pool_guid == dp->dd_pool_guid)) { (void) nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &nvl); zfs_iter_vdev(zhp, nvl, data); } } if (g_enumeration_done) { for (pool = list_head(&g_pool_list); pool != NULL; pool = list_next(&g_pool_list, pool)) { if (strcmp(zpool_get_name(zhp), zpool_get_name(pool->uap_zhp))) continue; if (zfs_toplevel_state(zhp) >= VDEV_STATE_DEGRADED) { list_remove(&g_pool_list, pool); (void) tpool_dispatch(g_tpool, zfs_enable_ds, pool); break; } } } zpool_close(zhp); return (0); } /* * Given a physical device path, iterate over all (pool, vdev) pairs which * correspond to the given path. */ static boolean_t devpath_iter(const char *devpath, zfs_process_func_t func, boolean_t wholedisk) { dev_data_t data = { 0 }; data.dd_compare = devpath; data.dd_func = func; data.dd_prop = ZPOOL_CONFIG_PHYS_PATH; data.dd_found = B_FALSE; data.dd_isdisk = wholedisk; (void) zpool_iter(g_zfshdl, zfs_iter_pool, &data); return (data.dd_found); } /* * Given a /devices path, lookup the corresponding devid for each minor node, * and find any vdevs with matching devids. Doing this straight up would be * rather inefficient, O(minor nodes * vdevs in system), so we take advantage of * the fact that each devid ends with "/". Once we find any valid * minor node, we chop off the portion after the last slash, and then search for * matching vdevs, which is O(vdevs in system). */ static boolean_t devid_iter(const char *devpath, zfs_process_func_t func, boolean_t wholedisk) { size_t len = strlen(devpath) + sizeof ("/devices") + sizeof (PHYS_PATH) - 1; char *fullpath; int fd; ddi_devid_t devid; char *devidstr, *fulldevid; dev_data_t data = { 0 }; /* * Try to open a known minor node. */ fullpath = alloca(len); (void) snprintf(fullpath, len, "/devices%s%s", devpath, PHYS_PATH); if ((fd = open(fullpath, O_RDONLY)) < 0) return (B_FALSE); /* * Determine the devid as a string, with no trailing slash for the minor * node. */ if (devid_get(fd, &devid) != 0) { (void) close(fd); return (B_FALSE); } (void) close(fd); if ((devidstr = devid_str_encode(devid, NULL)) == NULL) { devid_free(devid); return (B_FALSE); } len = strlen(devidstr) + 2; fulldevid = alloca(len); (void) snprintf(fulldevid, len, "%s/", devidstr); data.dd_compare = fulldevid; data.dd_func = func; data.dd_prop = ZPOOL_CONFIG_DEVID; data.dd_found = B_FALSE; data.dd_isdisk = wholedisk; (void) zpool_iter(g_zfshdl, zfs_iter_pool, &data); devid_str_free(devidstr); devid_free(devid); return (data.dd_found); } /* * This function is called when we receive a devfs add event. This can be * either a disk event or a lofi event, and the behavior is slightly different * depending on which it is. */ static int zfs_deliver_add(nvlist_t *nvl, boolean_t is_lofi) { char *devpath, *devname; char path[PATH_MAX], realpath[PATH_MAX]; char *colon, *raw; int ret; /* * The main unit of operation is the physical device path. For disks, * this is the device node, as all minor nodes are affected. For lofi * devices, this includes the minor path. Unfortunately, this isn't * represented in the DEV_PHYS_PATH for various reasons. */ if (nvlist_lookup_string(nvl, DEV_PHYS_PATH, &devpath) != 0) return (-1); /* * If this is a lofi device, then also get the minor instance name. * Unfortunately, the current payload doesn't include an easy way to get * this information. So we cheat by resolving the 'dev_name' (which * refers to the raw device) and taking the portion between ':(*),raw'. */ (void) strlcpy(realpath, devpath, sizeof (realpath)); if (is_lofi) { if (nvlist_lookup_string(nvl, DEV_NAME, &devname) == 0 && (ret = resolvepath(devname, path, sizeof (path))) > 0) { path[ret] = '\0'; colon = strchr(path, ':'); if (colon != NULL) raw = strstr(colon + 1, ",raw"); if (colon != NULL && raw != NULL) { *raw = '\0'; (void) snprintf(realpath, sizeof (realpath), "%s%s", devpath, colon); *raw = ','; } } } /* * Iterate over all vdevs with a matching devid, and then those with a * matching /devices path. For disks, we only want to pay attention to * vdevs marked as whole disks. For lofi, we don't care (because we're * matching an exact minor name). */ if (!devid_iter(realpath, zfs_process_add, !is_lofi)) (void) devpath_iter(realpath, zfs_process_add, !is_lofi); return (0); } /* * Called when we receive a VDEV_CHECK event, which indicates a device could not * be opened during initial pool open, but the autoreplace property was set on * the pool. In this case, we treat it as if it were an add event. */ static int zfs_deliver_check(nvlist_t *nvl) { dev_data_t data = { 0 }; if (nvlist_lookup_uint64(nvl, ZFS_EV_POOL_GUID, &data.dd_pool_guid) != 0 || nvlist_lookup_uint64(nvl, ZFS_EV_VDEV_GUID, &data.dd_vdev_guid) != 0 || data.dd_vdev_guid == 0) return (0); data.dd_isdisk = B_TRUE; data.dd_func = zfs_process_add; (void) zpool_iter(g_zfshdl, zfs_iter_pool, &data); return (0); } #define DEVICE_PREFIX "/devices" static int zfsdle_vdev_online(zpool_handle_t *zhp, void *data) { char *devname = data; boolean_t avail_spare, l2cache; vdev_state_t newstate; nvlist_t *tgt; syseventd_print(9, "%s: searching for %s in pool %s\n", __func__, devname, zpool_get_name(zhp)); if ((tgt = zpool_find_vdev_by_physpath(zhp, devname, &avail_spare, &l2cache, NULL)) != NULL) { char *path, fullpath[MAXPATHLEN]; uint64_t wholedisk = 0ULL; /* * If the /dev path of the device is invalid because the disk * has been moved to a new location, we need to try to refresh * that path before onlining the device. */ zpool_vdev_refresh_path(g_zfshdl, zhp, tgt); verify(nvlist_lookup_string(tgt, ZPOOL_CONFIG_PATH, &path) == 0); verify(nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_WHOLE_DISK, &wholedisk) == 0); syseventd_print(9, "%s: " "found %s in pool %s (wholedisk: %s)\n", __func__, path, zpool_get_name(zhp), wholedisk != 0 ? "true" : "false"); (void) strlcpy(fullpath, path, sizeof (fullpath)); if (wholedisk) { fullpath[strlen(fullpath) - 2] = '\0'; /* * We need to reopen the pool associated with this * device so that the kernel can update the size * of the expanded device. */ (void) zpool_reopen(zhp); } if (zpool_get_prop_int(zhp, ZPOOL_PROP_AUTOEXPAND, NULL)) { syseventd_print(9, "%s: " "setting device %s to ONLINE state in pool %s.\n", __func__, fullpath, zpool_get_name(zhp)); if (zpool_get_state(zhp) != POOL_STATE_UNAVAIL) { (void) zpool_vdev_online(zhp, fullpath, 0, &newstate); } } zpool_close(zhp); return (1); } zpool_close(zhp); return (0); } /* * This function is called for each vdev of a pool for which any of the * following events was recieved: * - ESC_ZFS_vdev_add * - ESC_ZFS_vdev_attach * - ESC_ZFS_vdev_clear * - ESC_ZFS_vdev_online * - ESC_ZFS_pool_create * - ESC_ZFS_pool_import * It will update the vdevs FRU property if it is out of date. */ /*ARGSUSED2*/ static void zfs_update_vdev_fru(zpool_handle_t *zhp, nvlist_t *vdev, boolean_t isdisk) { char *devpath, *cptr, *oldfru = NULL; const char *newfru; uint64_t vdev_guid; (void) nvlist_lookup_uint64(vdev, ZPOOL_CONFIG_GUID, &vdev_guid); (void) nvlist_lookup_string(vdev, ZPOOL_CONFIG_PHYS_PATH, &devpath); (void) nvlist_lookup_string(vdev, ZPOOL_CONFIG_FRU, &oldfru); /* remove : from devpath */ cptr = strrchr(devpath, ':'); if (cptr != NULL) *cptr = '\0'; newfru = libzfs_fru_lookup(g_zfshdl, devpath); if (newfru == NULL) { syseventd_print(9, "zfs_update_vdev_fru: no FRU for %s\n", devpath); return; } /* do nothing if the FRU hasn't changed */ if (oldfru != NULL && libzfs_fru_compare(g_zfshdl, oldfru, newfru)) { syseventd_print(9, "zfs_update_vdev_fru: FRU unchanged\n"); return; } syseventd_print(9, "zfs_update_vdev_fru: devpath = %s\n", devpath); syseventd_print(9, "zfs_update_vdev_fru: FRU = %s\n", newfru); (void) zpool_fru_set(zhp, vdev_guid, newfru); } /* * This function handles the following events: * - ESC_ZFS_vdev_add * - ESC_ZFS_vdev_attach * - ESC_ZFS_vdev_clear * - ESC_ZFS_vdev_online * - ESC_ZFS_pool_create * - ESC_ZFS_pool_import * It will iterate over the pool vdevs to update the FRU property. */ int zfs_deliver_update(nvlist_t *nvl) { dev_data_t dd = { 0 }; char *pname; zpool_handle_t *zhp; nvlist_t *config, *vdev; if (nvlist_lookup_string(nvl, "pool_name", &pname) != 0) { syseventd_print(9, "zfs_deliver_update: no pool name\n"); return (-1); } /* * If this event was triggered by a pool export or destroy we cannot * open the pool. This is not an error, just return 0 as we don't care * about these events. */ zhp = zpool_open_canfail(g_zfshdl, pname); if (zhp == NULL) return (0); config = zpool_get_config(zhp, NULL); if (config == NULL) { syseventd_print(9, "zfs_deliver_update: " "failed to get pool config for %s\n", pname); zpool_close(zhp); return (-1); } if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &vdev) != 0) { syseventd_print(0, "zfs_deliver_update: " "failed to get vdev tree for %s\n", pname); zpool_close(zhp); return (-1); } libzfs_fru_refresh(g_zfshdl); dd.dd_func = zfs_update_vdev_fru; zfs_iter_vdev(zhp, vdev, &dd); zpool_close(zhp); return (0); } int zfs_deliver_dle(nvlist_t *nvl) { char *devname; if (nvlist_lookup_string(nvl, DEV_PHYS_PATH, &devname) != 0) { syseventd_print(9, "zfs_deliver_event: no physpath\n"); return (-1); } if (strncmp(devname, DEVICE_PREFIX, strlen(DEVICE_PREFIX)) != 0) { syseventd_print(9, "zfs_deliver_event: invalid " "device '%s'", devname); return (-1); } /* * We try to find the device using the physical * path that has been supplied. We need to strip off * the /devices prefix before starting our search. */ devname += strlen(DEVICE_PREFIX); if (zpool_iter(g_zfshdl, zfsdle_vdev_online, devname) != 1) { syseventd_print(9, "zfs_deliver_event: device '%s' not" " found\n", devname); return (1); } return (0); } /*ARGSUSED*/ static int zfs_deliver_event(sysevent_t *ev, int unused) { const char *class = sysevent_get_class_name(ev); const char *subclass = sysevent_get_subclass_name(ev); nvlist_t *nvl; int ret; boolean_t is_lofi = B_FALSE, is_check = B_FALSE; boolean_t is_dle = B_FALSE, is_update = B_FALSE; if (strcmp(class, EC_DEV_ADD) == 0) { /* * We're mainly interested in disk additions, but we also listen * for new lofi devices, to allow for simplified testing. */ if (strcmp(subclass, ESC_DISK) == 0) is_lofi = B_FALSE; else if (strcmp(subclass, ESC_LOFI) == 0) is_lofi = B_TRUE; else return (0); is_check = B_FALSE; } else if (strcmp(class, EC_ZFS) == 0) { if (strcmp(subclass, ESC_ZFS_VDEV_CHECK) == 0) { /* * This event signifies that a device failed to open * during pool load, but the 'autoreplace' property was * set, so we should pretend it's just been added. */ is_check = B_TRUE; } else if ((strcmp(subclass, ESC_ZFS_VDEV_ADD) == 0) || (strcmp(subclass, ESC_ZFS_VDEV_ATTACH) == 0) || (strcmp(subclass, ESC_ZFS_VDEV_CLEAR) == 0) || (strcmp(subclass, ESC_ZFS_VDEV_ONLINE) == 0) || (strcmp(subclass, ESC_ZFS_POOL_CREATE) == 0) || (strcmp(subclass, ESC_ZFS_POOL_IMPORT) == 0)) { /* * When we receive these events we check the pool * configuration and update the vdev FRUs if necessary. */ is_update = B_TRUE; } } else if (strcmp(class, EC_DEV_STATUS) == 0 && strcmp(subclass, ESC_DEV_DLE) == 0) { is_dle = B_TRUE; } else { return (0); } if (sysevent_get_attr_list(ev, &nvl) != 0) return (-1); if (is_dle) ret = zfs_deliver_dle(nvl); else if (is_update) ret = zfs_deliver_update(nvl); else if (is_check) ret = zfs_deliver_check(nvl); else ret = zfs_deliver_add(nvl, is_lofi); nvlist_free(nvl); return (ret); } /*ARGSUSED*/ void * zfs_enum_pools(void *arg) { (void) zpool_iter(g_zfshdl, zfs_unavail_pool, (void *)&g_pool_list); if (!list_is_empty(&g_pool_list)) g_tpool = tpool_create(1, sysconf(_SC_NPROCESSORS_ONLN), 0, NULL); g_enumeration_done = B_TRUE; return (NULL); } static struct slm_mod_ops zfs_mod_ops = { SE_MAJOR_VERSION, SE_MINOR_VERSION, 10, zfs_deliver_event }; struct slm_mod_ops * slm_init() { if ((g_zfshdl = libzfs_init()) == NULL) return (NULL); /* * collect a list of unavailable pools (asynchronously, * since this can take a while) */ list_create(&g_pool_list, sizeof (struct unavailpool), offsetof(struct unavailpool, uap_node)); if (thr_create(NULL, 0, zfs_enum_pools, NULL, 0, &g_zfs_tid) != 0) return (NULL); return (&zfs_mod_ops); } void slm_fini() { unavailpool_t *pool; (void) thr_join(g_zfs_tid, NULL, NULL); if (g_tpool != NULL) { tpool_wait(g_tpool); tpool_destroy(g_tpool); } while ((pool = (list_head(&g_pool_list))) != NULL) { list_remove(&g_pool_list, pool); zpool_close(pool->uap_zhp); free(pool); } list_destroy(&g_pool_list); libzfs_fini(g_zfshdl); } #!/sbin/sh # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" . /lib/svc/share/smf_include.sh case "$1" in 'start') /usr/lib/sysevent/syseventd >/dev/msglog 2>&1 rc=$? if [ $rc -ne 0 ]; then echo "WARNING: /usr/lib/sysevent/syseventd failed: exit status $rc" exit $SMF_EXIT_ERR_FATAL fi ;; # # devfsadmd and syseventconfd are started on-demand # by syseventd. syseventd should be stopped before devfsadm and syseventconfd. # 'stop') zone=`smf_zonename` /usr/bin/pkill -x -u 0 -P 1 -z $zone rcm_daemon /usr/bin/pkill -x -u 0 -P 1 -z $zone syseventd /usr/bin/pkill -x -u 0 -P 1 -z $zone devfsadm /usr/bin/pkill -x -u 0 -P 1 -z $zone syseventconfd # # Since pkill is not atomic (may miss forking processes), also # kill entire service contract. # smf_kill_contract $2 TERM 1 [ $? -ne 0 ] && exit 1 ;; *) echo "Usage: $0 { start | stop }" exit 1 ;; esac exit 0