root/usr/src/cmd/syseventd/modules/sysevent_conf_mod/sysevent_conf_mod.c
/*
 * 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 <stdio.h>

#include <unistd.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <limits.h>
#include <thread.h>
#include <synch.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <pwd.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sunddi.h>
#include <sys/sysevent.h>
#include <libsysevent.h>
#include <libnvpair.h>
#include <dirent.h>
#include <locale.h>
#include <signal.h>
#include <wait.h>

#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:
 *
 *        <vendor>,[<publisher>,][<class>,]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);
}