root/usr/src/lib/libsysevent/libsysevent.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 (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <door.h>
#include <unistd.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <synch.h>
#include <pthread.h>
#include <signal.h>
#include <thread.h>
#include <libnvpair.h>
#include <assert.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/modctl.h>
#include <sys/mnttab.h>
#include <sys/sysevent.h>
#include <sys/sysevent_impl.h>

#include "libsysevent.h"
#include "libsysevent_impl.h"

/*
 * libsysevent - The system event framework library
 *
 *              This library provides routines to help with marshalling
 *              and unmarshalling of data contained in a sysevent event
 *              buffer.
 */

#define SE_ENCODE_METHOD        NV_ENCODE_NATIVE

#define dprint  if (libsysevent_debug) (void) printf
static int libsysevent_debug = 0;

static sysevent_t *se_unpack(sysevent_t *);
static int cleanup_id(sysevent_handle_t *shp, uint32_t id, int type);

/*
 * The following routines allow system event publication to the sysevent
 * framework.
 */

/*
 * sysevent_alloc - allocate a sysevent buffer
 */
static sysevent_t *
sysevent_alloc(char *class, int class_sz, char *subclass, int subclass_sz,
    char *pub, int pub_sz, nvlist_t *attr_list)
{
        int payload_sz;
        int aligned_class_sz, aligned_subclass_sz, aligned_pub_sz;
        size_t nvlist_sz = 0;
        char *attr;
        uint64_t attr_offset;
        sysevent_t *ev;

        if (attr_list != NULL) {
                if (nvlist_size(attr_list, &nvlist_sz, SE_ENCODE_METHOD)
                    != 0) {
                        return (NULL);
                }
        }

        /*
         * Calculate and reserve space for the class, subclass and
         * publisher strings in the event buffer
         */

        /* String sizes must be 64-bit aligned in the event buffer */
        aligned_class_sz = SE_ALIGN(class_sz);
        aligned_subclass_sz = SE_ALIGN(subclass_sz);
        aligned_pub_sz = SE_ALIGN(pub_sz);

        payload_sz = (aligned_class_sz - sizeof (uint64_t)) +
            (aligned_subclass_sz - sizeof (uint64_t)) +
            (aligned_pub_sz - sizeof (uint64_t)) - sizeof (uint64_t) +
            nvlist_sz;

        /*
         * Allocate event buffer plus additional payload overhead.
         */
        ev = calloc(1, sizeof (sysevent_impl_t) + payload_sz);
        if (ev == NULL) {
                return (NULL);
        }

        /* Initialize the event buffer data */
        SE_VERSION(ev) = SYS_EVENT_VERSION;
        (void) bcopy(class, SE_CLASS_NAME(ev), class_sz);

        SE_SUBCLASS_OFF(ev) = SE_ALIGN(offsetof(sysevent_impl_t, se_class_name))
                + aligned_class_sz;
        (void) bcopy(subclass, SE_SUBCLASS_NAME(ev), subclass_sz);

        SE_PUB_OFF(ev) = SE_SUBCLASS_OFF(ev) + aligned_subclass_sz;
        (void) bcopy(pub, SE_PUB_NAME(ev), pub_sz);

        SE_PAYLOAD_SZ(ev) = payload_sz;
        SE_ATTR_PTR(ev) = (uint64_t)0;

        /* Check for attribute list */
        if (attr_list == NULL) {
                return (ev);
        }

        /* Copy attribute data to contiguous memory */
        SE_FLAG(ev) = SE_PACKED_BUF;
        attr_offset = SE_ATTR_OFF(ev);
        attr = (char *)((caddr_t)ev + attr_offset);
        if (nvlist_pack(attr_list, &attr, &nvlist_sz, SE_ENCODE_METHOD,
            0) != 0) {
                free(ev);
                return (NULL);
        }

        return (ev);
}

/*
 * sysevent_post_event - generate a system event via the sysevent framework
 */
int
sysevent_post_event(char *class, char *subclass, char *vendor, char *pub_name,
    nvlist_t *attr_list, sysevent_id_t *eid)
{
        int error;
        sysevent_t *ev;

        ev = sysevent_alloc_event(class, subclass, vendor, pub_name, attr_list);
        if (ev == NULL) {
                return (-1);
        }

        error = modctl(MODEVENTS, (uintptr_t)MODEVENTS_POST_EVENT,
            (uintptr_t)ev, (uintptr_t)SE_SIZE(ev), (uintptr_t)eid, 0);

        sysevent_free(ev);

        if (error) {
                errno = EIO;
                return (-1);
        }

        return (0);
}

/*
 * The following routines are used to free or duplicate a
 * sysevent event buffer.
 */

/*
 * sysevent_dup - Allocate and copy an event buffer
 *      Copies both packed and unpacked to unpacked sysevent.
 */
sysevent_t *
sysevent_dup(sysevent_t *ev)
{
        nvlist_t *nvl, *cnvl = NULL;
        uint64_t attr_offset;
        sysevent_t *copy;

        if (SE_FLAG(ev) == SE_PACKED_BUF)
                return (se_unpack(ev));

        /* Copy event header information */
        attr_offset = SE_ATTR_OFF(ev);
        copy = calloc(1, attr_offset);
        if (copy == NULL)
                return (NULL);
        bcopy(ev, copy, attr_offset);

        nvl = (nvlist_t *)(uintptr_t)SE_ATTR_PTR(ev);
        if (nvl && nvlist_dup(nvl, &cnvl, 0) != 0) {
                free(copy);
                return (NULL);
        }

        SE_ATTR_PTR(copy) = (uintptr_t)cnvl;
        SE_FLAG(copy) = 0;      /* unpacked */
        return (copy);
}

/*
 * sysevent_free - Free memory allocated for an event buffer
 */
void
sysevent_free(sysevent_t *ev)
{
        nvlist_t *attr_list = (nvlist_t *)(uintptr_t)SE_ATTR_PTR(ev);

        nvlist_free(attr_list);
        free(ev);
}

/*
 * The following routines are used to extract attribute data from a sysevent
 * handle.
 */

/*
 * sysevent_get_attr_list - allocate and return an attribute associated with
 *                      the given sysevent buffer.
 */
int
sysevent_get_attr_list(sysevent_t *ev, nvlist_t **nvlist)
{
        int error;
        caddr_t attr;
        size_t attr_len;
        uint64_t attr_offset;
        nvlist_t *nvl;

        *nvlist = NULL;

        /* Duplicate attribute for an unpacked sysevent buffer */
        if (SE_FLAG(ev) != SE_PACKED_BUF) {
                nvl = (nvlist_t *)(uintptr_t)SE_ATTR_PTR(ev);
                if (nvl == NULL) {
                        return (0);
                }
                if ((error = nvlist_dup(nvl, nvlist, 0)) != 0) {
                        if (error == ENOMEM) {
                                errno = error;
                        } else {
                                errno = EINVAL;
                        }
                        return (-1);
                }
                return (0);
        }

        attr_offset = SE_ATTR_OFF(ev);
        if (SE_SIZE(ev) == attr_offset) {
                return (0);
        }

        /* unpack nvlist */
        attr = (caddr_t)ev + attr_offset;
        attr_len = SE_SIZE(ev) - attr_offset;
        if ((error = nvlist_unpack(attr, attr_len, nvlist, 0)) != 0) {
                if (error == ENOMEM) {
                        errno = error;
                } else {
                        errno = EINVAL;
                }
                return (-1);
        }

        return (0);
}

/*
 * sysevent_attr_name - Get name of attribute
 */
char *
sysevent_attr_name(sysevent_attr_t *attr)
{
        if (attr == NULL) {
                errno = EINVAL;
                return (NULL);
        }
        return (nvpair_name((nvpair_t *)attr));
}

/*
 * sysevent_attr_value - Get attribute value data and type
 */
int
sysevent_attr_value(sysevent_attr_t *attr, sysevent_value_t *se_value)
{
        nvpair_t *nvp = attr;

        if (nvp == NULL)
                return (EINVAL);

        /* Convert DATA_TYPE_* to SE_DATA_TYPE_* */
        switch (nvpair_type(nvp)) {
        case DATA_TYPE_BYTE:
                se_value->value_type = SE_DATA_TYPE_BYTE;
                (void) nvpair_value_byte(nvp, &se_value->value.sv_byte);
                break;
        case DATA_TYPE_INT16:
                se_value->value_type = SE_DATA_TYPE_INT16;
                (void) nvpair_value_int16(nvp, &se_value->value.sv_int16);
                break;
        case DATA_TYPE_UINT16:
                se_value->value_type = SE_DATA_TYPE_UINT16;
                (void) nvpair_value_uint16(nvp, &se_value->value.sv_uint16);
                break;
        case DATA_TYPE_INT32:
                se_value->value_type = SE_DATA_TYPE_INT32;
                (void) nvpair_value_int32(nvp, &se_value->value.sv_int32);
                break;
        case DATA_TYPE_UINT32:
                se_value->value_type = SE_DATA_TYPE_UINT32;
                (void) nvpair_value_uint32(nvp, &se_value->value.sv_uint32);
                break;
        case DATA_TYPE_INT64:
                se_value->value_type = SE_DATA_TYPE_INT64;
                (void) nvpair_value_int64(nvp, &se_value->value.sv_int64);
                break;
        case DATA_TYPE_UINT64:
                se_value->value_type = SE_DATA_TYPE_UINT64;
                (void) nvpair_value_uint64(nvp, &se_value->value.sv_uint64);
                break;
        case DATA_TYPE_STRING:
                se_value->value_type = SE_DATA_TYPE_STRING;
                (void) nvpair_value_string(nvp, &se_value->value.sv_string);
                break;
        case DATA_TYPE_BYTE_ARRAY:
                se_value->value_type = SE_DATA_TYPE_BYTES;
                (void) nvpair_value_byte_array(nvp,
                    &se_value->value.sv_bytes.data,
                    (uint_t *)&se_value->value.sv_bytes.size);
                break;
        case DATA_TYPE_HRTIME:
                se_value->value_type = SE_DATA_TYPE_TIME;
                (void) nvpair_value_hrtime(nvp, &se_value->value.sv_time);
                break;
        default:
                return (ENOTSUP);
        }
        return (0);
}

/*
 * sysevent_attr_next - Get next attribute in event attribute list
 */
sysevent_attr_t *
sysevent_attr_next(sysevent_t *ev, sysevent_attr_t *attr)
{
        nvlist_t *nvl;
        nvpair_t *nvp = attr;

        /* all user visible sysevent_t's are unpacked */
        assert(SE_FLAG(ev) != SE_PACKED_BUF);

        if (SE_ATTR_PTR(ev) == (uint64_t)0) {
                return (NULL);
        }

        nvl = (nvlist_t *)(uintptr_t)SE_ATTR_PTR(ev);
        return (nvlist_next_nvpair(nvl, nvp));
}

/*
 * sysevent_lookup_attr - Lookup attribute by name and datatype.
 */
int
sysevent_lookup_attr(sysevent_t *ev, char *name, int datatype,
    sysevent_value_t *se_value)
{
        nvpair_t *nvp;
        nvlist_t *nvl;

        assert(SE_FLAG(ev) != SE_PACKED_BUF);

        if (SE_ATTR_PTR(ev) == (uint64_t)0) {
                return (ENOENT);
        }

        /*
         * sysevent matches on both name and datatype
         * nvlist_look mataches name only. So we walk
         * nvlist manually here.
         */
        nvl = (nvlist_t *)(uintptr_t)SE_ATTR_PTR(ev);
        nvp = nvlist_next_nvpair(nvl, NULL);
        while (nvp) {
                if ((strcmp(name, nvpair_name(nvp)) == 0) &&
                    (sysevent_attr_value(nvp, se_value) == 0) &&
                    (se_value->value_type == datatype))
                        return (0);
                nvp = nvlist_next_nvpair(nvl, nvp);
        }
        return (ENOENT);
}

/* Routines to extract event header information */

/*
 * sysevent_get_class - Get class id
 */
int
sysevent_get_class(sysevent_t *ev)
{
        return (SE_CLASS(ev));
}

/*
 * sysevent_get_subclass - Get subclass id
 */
int
sysevent_get_subclass(sysevent_t *ev)
{
        return (SE_SUBCLASS(ev));
}

/*
 * sysevent_get_class_name - Get class name string
 */
char *
sysevent_get_class_name(sysevent_t *ev)
{
        return (SE_CLASS_NAME(ev));
}

typedef enum {
        PUB_VEND,
        PUB_KEYWD,
        PUB_NAME,
        PUB_PID
} se_pub_id_t;

/*
 * sysevent_get_pub - Get publisher name string
 */
char *
sysevent_get_pub(sysevent_t *ev)
{
        return (SE_PUB_NAME(ev));
}

/*
 * Get the requested string pointed by the token.
 *
 * Return NULL if not found or for insufficient memory.
 */
static char *
parse_pub_id(sysevent_t *ev, se_pub_id_t token)
{
        int i;
        char *pub_id, *pub_element, *str, *next;

        next = pub_id = strdup(sysevent_get_pub(ev));
        for (i = 0; i <= token; ++i) {
                str = strtok_r(next, ":", &next);
                if (str == NULL) {
                        free(pub_id);
                        return (NULL);
                }
        }

        pub_element = strdup(str);
        free(pub_id);
        return (pub_element);
}

/*
 * Return a pointer to the string following the token
 *
 * Note: This is a dedicated function for parsing
 * publisher strings and not for general purpose.
 */
static const char *
pub_idx(const char *pstr, int token)
{
        int i;

        for (i = 1; i <= token; i++) {
                if ((pstr = index(pstr, ':')) == NULL)
                        return (NULL);
                pstr++;
        }

        /* String might be empty */
        if (pstr) {
                if (*pstr == '\0' || *pstr == ':')
                        return (NULL);
        }
        return (pstr);
}

char *
sysevent_get_vendor_name(sysevent_t *ev)
{
        return (parse_pub_id(ev, PUB_VEND));
}

char *
sysevent_get_pub_name(sysevent_t *ev)
{
        return (parse_pub_id(ev, PUB_NAME));
}

/*
 * Provide the pid encoded in the publisher string
 * w/o allocating any resouces.
 */
void
sysevent_get_pid(sysevent_t *ev, pid_t *pid)
{
        const char *part_str;
        const char *pub_str = sysevent_get_pub(ev);

        *pid = (pid_t)SE_KERN_PID;

        part_str = pub_idx(pub_str, PUB_KEYWD);
        if (part_str != NULL && strstr(part_str, SE_KERN_PUB) != NULL)
                return;

        if ((part_str = pub_idx(pub_str, PUB_PID)) == NULL)
                return;

        *pid = (pid_t)atoi(part_str);
}

/*
 * sysevent_get_subclass_name - Get subclass name string
 */
char *
sysevent_get_subclass_name(sysevent_t *ev)
{
        return (SE_SUBCLASS_NAME(ev));
}

/*
 * sysevent_get_seq - Get event sequence id
 */
uint64_t
sysevent_get_seq(sysevent_t *ev)
{
        return (SE_SEQ(ev));
}

/*
 * sysevent_get_time - Get event timestamp
 */
void
sysevent_get_time(sysevent_t *ev, hrtime_t *etime)
{
        *etime = SE_TIME(ev);
}

/*
 * sysevent_get_size - Get event buffer size
 */
size_t
sysevent_get_size(sysevent_t *ev)
{
        return ((size_t)SE_SIZE(ev));
}

/*
 * The following routines are used by devfsadm_mod.c to propagate event
 * buffers to devfsadmd.  These routines will serve as the basis for
 * event channel publication and subscription.
 */

/*
 * sysevent_alloc_event -
 *      allocate a sysevent buffer for sending through an established event
 *      channel.
 */
sysevent_t *
sysevent_alloc_event(char *class, char *subclass, char *vendor, char *pub_name,
    nvlist_t *attr_list)
{
        int class_sz, subclass_sz, pub_sz;
        char *pub_id;
        sysevent_t *ev;

        if ((class == NULL) || (subclass == NULL) || (vendor == NULL) ||
            (pub_name == NULL)) {
                errno = EINVAL;
                return (NULL);
        }

        class_sz = strlen(class) + 1;
        subclass_sz = strlen(subclass) + 1;
        if ((class_sz > MAX_CLASS_LEN) ||
            (subclass_sz > MAX_SUBCLASS_LEN)) {
                errno = EINVAL;
                return (NULL);
        }

        /*
         * Calculate the publisher size plus string seperators and maximum
         * pid characters
         */
        pub_sz = strlen(vendor) + sizeof (SE_USR_PUB) + strlen(pub_name) + 14;
        if (pub_sz > MAX_PUB_LEN) {
                errno = EINVAL;
                return (NULL);
        }
        pub_id = malloc(pub_sz);
        if (pub_id == NULL) {
                errno = ENOMEM;
                return (NULL);
        }
        if (snprintf(pub_id, pub_sz, "%s:%s%s:%d", vendor, SE_USR_PUB,
            pub_name, (int)getpid()) >= pub_sz) {
                free(pub_id);
                errno = EINVAL;
                return (NULL);
        }
        pub_sz = strlen(pub_id) + 1;

        ev = sysevent_alloc(class, class_sz, subclass, subclass_sz,
            pub_id, pub_sz, attr_list);
        free(pub_id);
        if (ev == NULL) {
                errno = ENOMEM;
                return (NULL);
        }

        return (ev);
}

/*
 * se_unpack - unpack nvlist to a searchable list.
 *      If already unpacked, will do a dup.
 */
static sysevent_t *
se_unpack(sysevent_t *ev)
{
        caddr_t attr;
        size_t attr_len;
        nvlist_t *attrp = NULL;
        uint64_t attr_offset;
        sysevent_t *copy;

        assert(SE_FLAG(ev) == SE_PACKED_BUF);

        /* Copy event header information */
        attr_offset = SE_ATTR_OFF(ev);
        copy = calloc(1, attr_offset);
        if (copy == NULL)
                return (NULL);
        bcopy(ev, copy, attr_offset);
        SE_FLAG(copy) = 0;      /* unpacked */

        /* unpack nvlist */
        attr = (caddr_t)ev + attr_offset;
        attr_len = SE_SIZE(ev) - attr_offset;
        if (attr_len == 0) {
                return (copy);
        }
        if (nvlist_unpack(attr, attr_len, &attrp, 0) != 0) {
                free(copy);
                return (NULL);
        }

        SE_ATTR_PTR(copy) = (uintptr_t)attrp;
        return (copy);
}

/*
 * se_print - Prints elements in an event buffer
 */
void
se_print(FILE *fp, sysevent_t *ev)
{
        char *vendor, *pub;
        pid_t pid;
        hrtime_t hrt;
        nvlist_t *attr_list = NULL;

        (void) sysevent_get_time(ev, &hrt);
        (void) fprintf(fp, "received sysevent id = 0X%llx:%llx\n",
            hrt, (longlong_t)sysevent_get_seq(ev));
        (void) fprintf(fp, "\tclass = %s\n", sysevent_get_class_name(ev));
        (void) fprintf(fp, "\tsubclass = %s\n", sysevent_get_subclass_name(ev));
        if ((vendor =  sysevent_get_vendor_name(ev)) != NULL) {
                (void) fprintf(fp, "\tvendor = %s\n", vendor);
                free(vendor);
        }
        if ((pub = sysevent_get_pub_name(ev)) != NULL) {
                sysevent_get_pid(ev, &pid);
                (void) fprintf(fp, "\tpublisher = %s:%d\n", pub, (int)pid);
                free(pub);
        }

        if (sysevent_get_attr_list(ev, &attr_list) == 0 && attr_list != NULL) {
                nvlist_print(fp, attr_list);
                nvlist_free(attr_list);
        }
}

/*
 * The following routines are provided to support establishment and use
 * of sysevent channels.  A sysevent channel is established between
 * publishers and subscribers of sysevents for an agreed upon channel name.
 * These routines currently support sysevent channels between user-level
 * applications running on the same system.
 *
 * Sysevent channels may be created by a single publisher or subscriber process.
 * Once established, up to MAX_SUBSRCIBERS subscribers may subscribe interest in
 * receiving sysevent notifications on the named channel.  At present, only
 * one publisher is allowed per sysevent channel.
 *
 * The registration information for each channel is kept in the kernel.  A
 * kernel-based registration was chosen for persistence and reliability reasons.
 * If either a publisher or a subscriber exits for any reason, the channel
 * properties are maintained until all publishers and subscribers have exited.
 * Additionally, an in-kernel registration allows the API to be extended to
 * include kernel subscribers as well as userland subscribers in the future.
 *
 * To insure fast lookup of subscriptions, a cached copy of the registration
 * is kept and maintained for the publisher process.  Updates are made
 * everytime a change is made in the kernel.  Changes to the registration are
 * expected to be infrequent.
 *
 * Channel communication between publisher and subscriber processes is
 * implemented primarily via doors.  Each publisher creates a door for
 * registration notifications and each subscriber creates a door for event
 * delivery.
 *
 * Most of these routines are used by syseventd(8), the sysevent publisher
 * for the syseventd channel.  Processes wishing to receive sysevent
 * notifications from syseventd may use a set of public
 * APIs designed to subscribe to syseventd sysevents.  The subscription
 * APIs are implemented in accordance with PSARC/2001/076.
 *
 */

/*
 * Door handlers for the channel subscribers
 */

/*
 * subscriber_event_handler - generic event handling wrapper for subscribers
 *                      This handler is used to process incoming sysevent
 *                      notifications from channel publishers.
 *                      It is created as a seperate thread in each subscriber
 *                      process per subscription.
 */
static void *
subscriber_event_handler(void *arg)
{
        sysevent_handle_t *shp = arg;
        subscriber_priv_t *sub_info;
        sysevent_queue_t *evqp;

        sub_info = (subscriber_priv_t *)SH_PRIV_DATA(shp);

        /* See hack alert in sysevent_bind_subscriber_cmn */
        if (sub_info->sp_handler_tid == 0)
                sub_info->sp_handler_tid = thr_self();

        (void) mutex_lock(&sub_info->sp_qlock);
        for (;;) {
                while (sub_info->sp_evq_head == NULL && SH_BOUND(shp)) {
                        (void) cond_wait(&sub_info->sp_cv, &sub_info->sp_qlock);
                }
                evqp = sub_info->sp_evq_head;
                while (evqp) {
                        (void) mutex_unlock(&sub_info->sp_qlock);
                        (void) sub_info->sp_func(evqp->sq_ev);
                        (void) mutex_lock(&sub_info->sp_qlock);
                        sub_info->sp_evq_head = sub_info->sp_evq_head->sq_next;
                        free(evqp->sq_ev);
                        free(evqp);
                        evqp = sub_info->sp_evq_head;
                }
                if (!SH_BOUND(shp)) {
                        (void) mutex_unlock(&sub_info->sp_qlock);
                        return (NULL);
                }
        }

        /* NOTREACHED */
}

/*
 * Data structure used to communicate event subscription cache updates
 * to publishers via a registration door
 */
struct reg_args {
        uint32_t ra_sub_id;
        uint32_t ra_op;
        uint64_t ra_buf_ptr;
};


/*
 * event_deliver_service - generic event delivery service routine.  This routine
 *              is called in response to a door call to post an event.
 *
 */
/*ARGSUSED*/
static void
event_deliver_service(void *cookie, char *args, size_t alen,
    door_desc_t *ddp, uint_t ndid)
{
        int     ret = 0;
        subscriber_priv_t *sub_info;
        sysevent_handle_t *shp;
        sysevent_queue_t *new_eq;

        if (args == NULL || alen < sizeof (uint32_t)) {
                ret = EINVAL;
                goto return_from_door;
        }

        /* Publisher checking on subscriber */
        if (alen == sizeof (uint32_t)) {
                ret = 0;
                goto return_from_door;
        }

        shp = (sysevent_handle_t *)cookie;
        if (shp == NULL) {
                ret = EBADF;
                goto return_from_door;
        }

        /*
         * Mustn't block if we are trying to update the registration with
         * the publisher
         */
        if (mutex_trylock(SH_LOCK(shp)) != 0) {
                ret = EAGAIN;
                goto return_from_door;
        }

        if (!SH_BOUND(shp)) {
                ret = EBADF;
                (void) mutex_unlock(SH_LOCK(shp));
                goto return_from_door;
        }

        sub_info = (subscriber_priv_t *)SH_PRIV_DATA(shp);
        if (sub_info == NULL) {
                ret = EBADF;
                (void) mutex_unlock(SH_LOCK(shp));
                goto return_from_door;
        }

        new_eq = (sysevent_queue_t *)calloc(1,
            sizeof (sysevent_queue_t));
        if (new_eq == NULL) {
                ret = EAGAIN;
                (void) mutex_unlock(SH_LOCK(shp));
                goto return_from_door;
        }

        /*
         * Allocate and copy the event buffer into the subscriber's
         * address space
         */
        new_eq->sq_ev = calloc(1, alen);
        if (new_eq->sq_ev == NULL) {
                free(new_eq);
                ret = EAGAIN;
                (void) mutex_unlock(SH_LOCK(shp));
                goto return_from_door;
        }
        (void) bcopy(args, new_eq->sq_ev, alen);

        (void) mutex_lock(&sub_info->sp_qlock);
        if (sub_info->sp_evq_head == NULL) {
                sub_info->sp_evq_head = new_eq;
        } else {
                sub_info->sp_evq_tail->sq_next = new_eq;
        }
        sub_info->sp_evq_tail = new_eq;

        (void) cond_signal(&sub_info->sp_cv);
        (void) mutex_unlock(&sub_info->sp_qlock);
        (void) mutex_unlock(SH_LOCK(shp));

return_from_door:
        (void) door_return((void *)&ret, sizeof (ret), NULL, 0);
        (void) door_return(NULL, 0, NULL, 0);
}

/*
 * Sysevent subscription information is maintained in the kernel.  Updates
 * to the in-kernel registration database is expected to be infrequent and
 * offers consistency for publishers and subscribers that may come and go
 * for a given channel.
 *
 * To expedite registration lookups by publishers, a cached copy of the
 * kernel registration database is kept per-channel.  Caches are invalidated
 * and refreshed upon state changes to the in-kernel registration database.
 *
 * To prevent stale subscriber data, publishers may remove subsriber
 * registrations from the in-kernel registration database in the event
 * that a particular subscribing process is unresponsive.
 *
 * The following routines provide a mechanism to update publisher and subscriber
 * information for a specified channel.
 */

/*
 * clnt_deliver_event - Deliver an event through the consumer's event
 *                      delivery door
 *
 * Returns -1 if message not delivered. With errno set to cause of error.
 * Returns 0 for success with the results returned in posting buffer.
 */
static int
clnt_deliver_event(int service_door, void *data, size_t datalen,
    void *result, size_t rlen)
{
        int error = 0;
        door_arg_t door_arg;

        door_arg.rbuf = result;
        door_arg.rsize = rlen;
        door_arg.data_ptr = data;
        door_arg.data_size = datalen;
        door_arg.desc_ptr = NULL;
        door_arg.desc_num = 0;

        /*
         * Make door call
         */
        while ((error = door_call(service_door, &door_arg)) != 0) {
                if (errno == EAGAIN || errno == EINTR) {
                        continue;
                } else {
                        error = errno;
                        break;
                }
        }

        return (error);
}

static int
update_publisher_cache(subscriber_priv_t *sub_info, int update_op,
    uint32_t sub_id, size_t datasz, uchar_t *data)
{
        int pub_fd;
        uint32_t result = 0;
        struct reg_args *rargs;

        rargs = (struct reg_args *)calloc(1, sizeof (struct reg_args) +
            datasz);
        if (rargs == NULL) {
                errno = ENOMEM;
                return (-1);
        }

        rargs->ra_sub_id = sub_id;
        rargs->ra_op = update_op;
        bcopy(data, (char *)&rargs->ra_buf_ptr, datasz);

        pub_fd = open(sub_info->sp_door_name, O_RDONLY);
        (void) clnt_deliver_event(pub_fd, (void *)rargs,
            sizeof (struct reg_args) + datasz, &result, sizeof (result));
        (void) close(pub_fd);

        free(rargs);
        if (result != 0) {
                errno = result;
                return (-1);
        }

        return (0);
}


/*
 * update_kernel_registration - update the in-kernel registration for the
 * given channel.
 */
static int
update_kernel_registration(sysevent_handle_t *shp, int update_type,
    int update_op, uint32_t *sub_id, size_t datasz, uchar_t *data)
{
        int error;
        char *channel_name = SH_CHANNEL_NAME(shp);
        se_pubsub_t udata;

        udata.ps_channel_name_len = strlen(channel_name) + 1;
        udata.ps_op = update_op;
        udata.ps_type = update_type;
        udata.ps_buflen = datasz;
        udata.ps_id = *sub_id;

        if ((error = modctl(MODEVENTS, (uintptr_t)MODEVENTS_REGISTER_EVENT,
            (uintptr_t)channel_name, (uintptr_t)data, (uintptr_t)&udata, 0))
            != 0) {
                return (error);
        }

        *sub_id = udata.ps_id;

        return (error);
}

/*
 * get_kernel_registration - get the current subscriber registration for
 * the given channel
 */
static nvlist_t *
get_kernel_registration(char *channel_name, uint32_t class_id)
{
        char *nvlbuf;
        nvlist_t *nvl;
        se_pubsub_t udata;

        nvlbuf = calloc(1, MAX_SUBSCRIPTION_SZ);
        if (nvlbuf == NULL) {
                return (NULL);
        }

        udata.ps_buflen = MAX_SUBSCRIPTION_SZ;
        udata.ps_channel_name_len = strlen(channel_name) + 1;
        udata.ps_id = class_id;
        udata.ps_op = SE_GET_REGISTRATION;
        udata.ps_type = PUBLISHER;

        if (modctl(MODEVENTS, (uintptr_t)MODEVENTS_REGISTER_EVENT,
            (uintptr_t)channel_name, (uintptr_t)nvlbuf, (uintptr_t)&udata, 0)
            != 0) {

                /* Need a bigger buffer to hold channel registration */
                if (errno == EAGAIN) {
                        free(nvlbuf);
                        nvlbuf = calloc(1, udata.ps_buflen);
                        if (nvlbuf == NULL)
                                return (NULL);

                        /* Try again */
                        if (modctl(MODEVENTS,
                            (uintptr_t)MODEVENTS_REGISTER_EVENT,
                            (uintptr_t)channel_name, (uintptr_t)nvlbuf,
                            (uintptr_t)&udata, 0) != 0) {
                                free(nvlbuf);
                                return (NULL);
                        }
                } else {
                        free(nvlbuf);
                        return (NULL);
                }
        }

        if (nvlist_unpack(nvlbuf, udata.ps_buflen, &nvl, 0) != 0) {
                free(nvlbuf);
                return (NULL);
        }
        free(nvlbuf);

        return (nvl);
}

/*
 * The following routines provide a mechanism for publishers to maintain
 * subscriber information.
 */

static void
dealloc_subscribers(sysevent_handle_t *shp)
{
        int i;
        subscriber_data_t *sub;

        for (i = 1; i <= MAX_SUBSCRIBERS; ++i) {
                sub = SH_SUBSCRIBER(shp, i);
                if (sub != NULL) {
                        free(sub->sd_door_name);
                        free(sub);
                }
                SH_SUBSCRIBER(shp, i) = NULL;
        }
}

/*ARGSUSED*/
static int
alloc_subscriber(sysevent_handle_t *shp, uint32_t sub_id, int oflag)
{
        subscriber_data_t *sub;
        char door_name[MAXPATHLEN];

        if (SH_SUBSCRIBER(shp, sub_id) != NULL) {
                return (0);
        }

        /* Allocate and initialize the subscriber data */
        sub = (subscriber_data_t *)calloc(1,
            sizeof (subscriber_data_t));
        if (sub == NULL) {
                return (-1);
        }
        if (snprintf(door_name, MAXPATHLEN, "%s/%d",
            SH_CHANNEL_PATH(shp), sub_id) >= MAXPATHLEN) {
                free(sub);
                return (-1);
        }

        sub->sd_flag = ACTIVE;
        sub->sd_door_name = strdup(door_name);
        if (sub->sd_door_name == NULL) {
                free(sub);
                return (-1);
        }

        SH_SUBSCRIBER(shp, sub_id) = sub;
        return (0);

}

/*
 * The following routines are used to update and maintain the registration cache
 * for a particular sysevent channel.
 */

static uint32_t
hash_func(const char *s)
{
        uint32_t result = 0;
        uint_t g;

        while (*s != '\0') {
                result <<= 4;
                result += (uint32_t)*s++;
                g = result & 0xf0000000;
                if (g != 0) {
                        result ^= g >> 24;
                        result ^= g;
                }
        }

        return (result);
}

subclass_lst_t *
cache_find_subclass(class_lst_t *c_list, char *subclass)
{
        subclass_lst_t *sc_list;

        if (c_list == NULL)
                return (NULL);

        sc_list = c_list->cl_subclass_list;

        while (sc_list != NULL) {
                if (strcmp(sc_list->sl_name, subclass) == 0) {
                        return (sc_list);
                }
                sc_list = sc_list->sl_next;
        }

        return (NULL);
}


static class_lst_t *
cache_find_class(sysevent_handle_t *shp, char *class)
{
        int index;
        class_lst_t *c_list;
        class_lst_t **class_hash = SH_CLASS_HASH(shp);

        if (strcmp(class, EC_ALL) == 0) {
                return (class_hash[0]);
        }

        index = CLASS_HASH(class);
        c_list = class_hash[index];
        while (c_list != NULL) {
                if (strcmp(class, c_list->cl_name) == 0) {
                        break;
                }
                c_list = c_list->cl_next;
        }

        return (c_list);
}

static int
cache_insert_subclass(class_lst_t *c_list, char **subclass_names,
    int subclass_num, uint32_t sub_id)
{
        int i;
        subclass_lst_t *sc_list;

        for (i = 0; i < subclass_num; ++i) {
                if ((sc_list = cache_find_subclass(c_list, subclass_names[i]))
                    != NULL) {
                        sc_list->sl_num[sub_id] = 1;
                } else {
                        sc_list = (subclass_lst_t *)calloc(1,
                            sizeof (subclass_lst_t));
                        if (sc_list == NULL)
                                return (-1);

                        sc_list->sl_name = strdup(subclass_names[i]);
                        if (sc_list->sl_name == NULL) {
                                free(sc_list);
                                return (-1);
                        }

                        sc_list->sl_num[sub_id] = 1;
                        sc_list->sl_next = c_list->cl_subclass_list;
                        c_list->cl_subclass_list = sc_list;
                }
        }

        return (0);
}

static int
cache_insert_class(sysevent_handle_t *shp, char *class,
    char **subclass_names, int subclass_num, uint32_t sub_id)
{
        class_lst_t *c_list;

        if (strcmp(class, EC_ALL) == 0) {
                char *subclass_all = EC_SUB_ALL;

                (void) cache_insert_subclass(SH_CLASS_HASH(shp)[0],
                    (char **)&subclass_all, 1, sub_id);
                return (0);
        }

        /* New class, add to the registration cache */
        if ((c_list = cache_find_class(shp, class)) == NULL) {

                c_list = (class_lst_t *)calloc(1, sizeof (class_lst_t));
                if (c_list == NULL) {
                        return (1);
                }
                c_list->cl_name = strdup(class);
                if (c_list->cl_name == NULL) {
                        free(c_list);
                        return (1);
                }

                c_list->cl_subclass_list = (subclass_lst_t *)
                    calloc(1, sizeof (subclass_lst_t));
                if (c_list->cl_subclass_list == NULL) {
                        free(c_list->cl_name);
                        free(c_list);
                        return (1);
                }
                c_list->cl_subclass_list->sl_name = strdup(EC_SUB_ALL);
                if (c_list->cl_subclass_list->sl_name == NULL) {
                        free(c_list->cl_subclass_list);
                        free(c_list->cl_name);
                        free(c_list);
                        return (1);
                }
                c_list->cl_next = SH_CLASS_HASH(shp)[CLASS_HASH(class)];
                SH_CLASS_HASH(shp)[CLASS_HASH(class)] = c_list;

        }

        /* Update the subclass list */
        if (cache_insert_subclass(c_list, subclass_names, subclass_num,
            sub_id) != 0)
                return (1);

        return (0);
}

static void
cache_remove_all_class(sysevent_handle_t *shp, uint32_t sub_id)
{
        int i;
        class_lst_t *c_list;
        subclass_lst_t *sc_list;

        for (i = 0; i < CLASS_HASH_SZ + 1; ++i) {
                c_list = SH_CLASS_HASH(shp)[i];
                while (c_list != NULL) {
                        sc_list = c_list->cl_subclass_list;
                        while (sc_list != NULL) {
                                sc_list->sl_num[sub_id] = 0;
                                sc_list = sc_list->sl_next;
                        }
                        c_list = c_list->cl_next;
                }
        }
}

static void
cache_remove_class(sysevent_handle_t *shp, char *class, uint32_t sub_id)
{
        class_lst_t *c_list;
        subclass_lst_t *sc_list;

        if (strcmp(class, EC_ALL) == 0) {
                cache_remove_all_class(shp, sub_id);
                return;
        }

        if ((c_list = cache_find_class(shp, class)) == NULL) {
                return;
        }

        sc_list = c_list->cl_subclass_list;
        while (sc_list != NULL) {
                sc_list->sl_num[sub_id] = 0;
                sc_list = sc_list->sl_next;
        }
}

static void
free_cached_registration(sysevent_handle_t *shp)
{
        int i;
        class_lst_t *clist, *next_clist;
        subclass_lst_t *sc_list, *next_sc;

        for (i = 0; i < CLASS_HASH_SZ + 1; i++) {
                clist = SH_CLASS_HASH(shp)[i];
                while (clist != NULL) {
                        sc_list = clist->cl_subclass_list;
                        while (sc_list != NULL) {
                                free(sc_list->sl_name);
                                next_sc = sc_list->sl_next;
                                free(sc_list);
                                sc_list = next_sc;
                        }
                        free(clist->cl_name);
                        next_clist = clist->cl_next;
                        free(clist);
                        clist = next_clist;
                }
                SH_CLASS_HASH(shp)[i] = NULL;
        }
}

static int
create_cached_registration(sysevent_handle_t *shp,
    class_lst_t **class_hash)
{
        int i, j, new_class;
        char *class_name;
        uint_t num_elem;
        uchar_t *subscribers;
        nvlist_t *nvl;
        nvpair_t *nvpair;
        class_lst_t *clist;
        subclass_lst_t *sc_list;

        for (i = 0; i < CLASS_HASH_SZ + 1; ++i) {

                if ((nvl = get_kernel_registration(SH_CHANNEL_NAME(shp), i))
                    == NULL) {
                        if (errno == ENOENT) {
                                class_hash[i] = NULL;
                                continue;
                        } else {
                                goto create_failed;
                        }
                }


                nvpair = NULL;
                if ((nvpair = nvlist_next_nvpair(nvl, nvpair)) == NULL) {
                        goto create_failed;
                }

                new_class = 1;
                while (new_class) {
                        /* Extract the class name from the nvpair */
                        if (nvpair_value_string(nvpair, &class_name) != 0) {
                                goto create_failed;
                        }
                        clist = (class_lst_t *)
                            calloc(1, sizeof (class_lst_t));
                        if (clist == NULL) {
                                goto create_failed;
                        }

                        clist->cl_name = strdup(class_name);
                        if (clist->cl_name == NULL) {
                                free(clist);
                                goto create_failed;
                        }

                        /*
                         * Extract the subclass name and registration
                         * from the nvpair
                         */
                        if ((nvpair = nvlist_next_nvpair(nvl, nvpair))
                            == NULL) {
                                free(clist->cl_name);
                                free(clist);
                                goto create_failed;
                        }

                        clist->cl_next = class_hash[i];
                        class_hash[i] = clist;

                        for (;;) {

                                sc_list = (subclass_lst_t *)calloc(1,
                                    sizeof (subclass_lst_t));
                                if (sc_list == NULL) {
                                        goto create_failed;
                                }

                                sc_list->sl_next = clist->cl_subclass_list;
                                clist->cl_subclass_list = sc_list;

                                sc_list->sl_name = strdup(nvpair_name(nvpair));
                                if (sc_list->sl_name == NULL) {
                                        goto create_failed;
                                }

                                if (nvpair_value_byte_array(nvpair,
                                    &subscribers, &num_elem) != 0) {
                                        goto create_failed;
                                }
                                bcopy(subscribers, (uchar_t *)sc_list->sl_num,
                                    MAX_SUBSCRIBERS + 1);

                                for (j = 1; j <= MAX_SUBSCRIBERS; ++j) {
                                        if (sc_list->sl_num[j] == 0)
                                                continue;

                                        if (alloc_subscriber(shp, j, 1) != 0) {
                                                goto create_failed;
                                        }
                                }

                                /*
                                 * Check next nvpair - either subclass or
                                 * class
                                 */
                                if ((nvpair = nvlist_next_nvpair(nvl, nvpair))
                                    == NULL) {
                                        new_class = 0;
                                        break;
                                } else if (strcmp(nvpair_name(nvpair),
                                    CLASS_NAME) == 0) {
                                        break;
                                }
                        }
                }
                nvlist_free(nvl);
        }
        return (0);

create_failed:
        dealloc_subscribers(shp);
        free_cached_registration(shp);
        nvlist_free(nvl);
        return (-1);

}

/*
 * cache_update_service - generic event publisher service routine.  This routine
 *              is called in response to a registration cache update.
 *
 */
/*ARGSUSED*/
static void
cache_update_service(void *cookie, char *args, size_t alen,
    door_desc_t *ddp, uint_t ndid)
{
        int ret = 0;
        uint_t num_elem;
        char *class, **event_list;
        size_t datalen;
        uint32_t sub_id;
        nvlist_t *nvl;
        nvpair_t *nvpair = NULL;
        struct reg_args *rargs;
        sysevent_handle_t *shp;
        subscriber_data_t *sub;

        if (alen < sizeof (struct reg_args) || cookie == NULL) {
                ret = EINVAL;
                goto return_from_door;
        }

        /* LINTED: E_BAD_PTR_CAST_ALIGN */
        rargs = (struct reg_args *)args;
        shp = (sysevent_handle_t *)cookie;

        datalen = alen - sizeof (struct reg_args);
        sub_id = rargs->ra_sub_id;

        (void) mutex_lock(SH_LOCK(shp));

        switch (rargs->ra_op) {
        case SE_UNREGISTER:
                class = (char *)&rargs->ra_buf_ptr;
                cache_remove_class(shp, (char *)class,
                    sub_id);
                break;
        case SE_UNBIND_REGISTRATION:

                sub = SH_SUBSCRIBER(shp, sub_id);
                if (sub == NULL)
                        break;

                free(sub->sd_door_name);
                free(sub);
                cache_remove_class(shp, EC_ALL, sub_id);
                SH_SUBSCRIBER(shp, sub_id) = NULL;

                break;
        case SE_BIND_REGISTRATION:

                /* New subscriber */
                if (alloc_subscriber(shp, sub_id, 0) != 0) {
                        ret = ENOMEM;
                        break;
                }
                break;
        case SE_REGISTER:

                if (SH_SUBSCRIBER(shp, sub_id) == NULL) {
                        ret = EINVAL;
                        break;
                }
                /* Get new registration data */
                if (nvlist_unpack((char *)&rargs->ra_buf_ptr, datalen,
                    &nvl, 0) != 0) {
                        ret =  EFAULT;
                        break;
                }
                if ((nvpair = nvlist_next_nvpair(nvl, nvpair)) == NULL) {
                        nvlist_free(nvl);
                        ret = EFAULT;
                        break;
                }
                if (nvpair_value_string_array(nvpair, &event_list, &num_elem)
                    != 0) {
                        nvlist_free(nvl);
                        ret =  EFAULT;
                        break;
                }
                class = nvpair_name(nvpair);

                ret = cache_insert_class(shp, class,
                    event_list, num_elem, sub_id);
                if (ret != 0) {
                        cache_remove_class(shp, class, sub_id);
                        nvlist_free(nvl);
                        ret =  EFAULT;
                        break;
                }

                nvlist_free(nvl);

                break;
        case SE_CLEANUP:
                /* Cleanup stale subscribers */
                sysevent_cleanup_subscribers(shp);
                break;
        default:
                ret =  EINVAL;
        }

        (void) mutex_unlock(SH_LOCK(shp));

return_from_door:
        (void) door_return((void *)&ret, sizeof (ret), NULL, 0);
        (void) door_return(NULL, 0, NULL, 0);
}

/*
 * sysevent_send_event -
 * Send an event via the communication channel associated with the sysevent
 * handle.  Event notifications are broadcast to all subscribers based upon
 * the event class and subclass.  The handle must have been previously
 * allocated and bound by
 * sysevent_open_channel() and sysevent_bind_publisher()
 */
int
sysevent_send_event(sysevent_handle_t *shp, sysevent_t *ev)
{
        int i, error, sub_fd, result = 0;
        int deliver_error = 0;
        int subscribers_sent = 0;
        int want_resend, resend_cnt = 0;
        char *event_class, *event_subclass;
        uchar_t *all_class_subscribers, *all_subclass_subscribers;
        uchar_t *subclass_subscribers;
        subscriber_data_t *sub;
        subclass_lst_t *sc_lst;

        /* Check for proper registration */
        event_class = sysevent_get_class_name(ev);
        event_subclass = sysevent_get_subclass_name(ev);

        (void) mutex_lock(SH_LOCK(shp));

send_event:

        want_resend = 0;
        if (!SH_BOUND(shp)) {
                (void) mutex_unlock(SH_LOCK(shp));
                errno = EINVAL;
                return (-1);
        }

        /* Find all subscribers for this event class/subclass */
        sc_lst = cache_find_subclass(
            cache_find_class(shp, EC_ALL), EC_SUB_ALL);
        all_class_subscribers = sc_lst->sl_num;

        sc_lst = cache_find_subclass(
            cache_find_class(shp, event_class), EC_SUB_ALL);
        if (sc_lst)
                all_subclass_subscribers = sc_lst->sl_num;
        else
                all_subclass_subscribers = NULL;

        sc_lst = cache_find_subclass(
            cache_find_class(shp, event_class), event_subclass);
        if (sc_lst)
                subclass_subscribers = sc_lst->sl_num;
        else
                subclass_subscribers = NULL;

        /* Send event buffer to all valid subscribers */
        for (i = 1; i <= MAX_SUBSCRIBERS; ++i) {
                if ((all_class_subscribers[i] |
                    (all_subclass_subscribers && all_subclass_subscribers[i]) |
                    (subclass_subscribers && subclass_subscribers[i])) == 0)
                        continue;

                sub = SH_SUBSCRIBER(shp, i);
                assert(sub != NULL);

                /* Check for active subscriber */
                if (!(sub->sd_flag & ACTIVE)) {
                        dprint("sysevent_send_event: subscriber %d inactive\n",
                            i);
                        continue;
                }

                /* Process only resend requests */
                if (resend_cnt > 0 && !(sub->sd_flag & SEND_AGAIN)) {
                        continue;
                }

                if ((sub_fd = open(sub->sd_door_name, O_RDONLY)) == -1) {
                        dprint("sysevent_send_event: Failed to open "
                            "%s: %s\n", sub->sd_door_name, strerror(errno));
                        continue;
                }
                result = 0;
                error = clnt_deliver_event(sub_fd, ev,
                    sysevent_get_size(ev), &result, sizeof (result));

                (void) close(sub_fd);

                /* Successful door call */
                if (error == 0) {
                        switch (result) {
                        /* Subscriber requested EAGAIN */
                        case EAGAIN:
                                if (resend_cnt > SE_MAX_RETRY_LIMIT) {
                                        deliver_error = 1;
                                } else {
                                        want_resend = 1;
                                        dprint("sysevent_send_event: resend "
                                            "requested for %d\n", i);
                                        sub->sd_flag |= SEND_AGAIN;
                                }
                                break;
                        /* Bad sysevent handle for subscriber */
                        case EBADF:
                        case EINVAL:
                                dprint("sysevent_send_event: Bad sysevent "
                                    "handle for %s", sub->sd_door_name);
                                sub->sd_flag = 0;
                                deliver_error = 1;
                                break;
                        /* Successful delivery */
                        default:
                                sub->sd_flag &= ~SEND_AGAIN;
                                ++subscribers_sent;
                        }
                } else {
                        dprint("sysevent_send_event: Failed door call "
                            "to %s: %s: %d\n", sub->sd_door_name,
                            strerror(errno), result);
                        sub->sd_flag = 0;
                        deliver_error = 1;
                }
        }

        if (want_resend) {
                resend_cnt++;
                goto send_event;
        }

        if (deliver_error) {
                sysevent_cleanup_subscribers(shp);
                (void) mutex_unlock(SH_LOCK(shp));
                errno = EFAULT;
                return (-1);
        }

        (void) mutex_unlock(SH_LOCK(shp));

        if (subscribers_sent == 0) {
                dprint("sysevent_send_event: No subscribers for %s:%s\n",
                    event_class, event_subclass);
                errno = ENOENT;
                return (-1);
        }

        return (0);
}

/*
 * Common routine to establish an event channel through which an event
 * publisher or subscriber may post or receive events.
 */
static sysevent_handle_t *
sysevent_open_channel_common(const char *channel_path)
{
        uint32_t sub_id = 0;
        char *begin_path;
        struct stat chan_stat;
        sysevent_handle_t *shp;


        if (channel_path == NULL || strlen(channel_path) + 1 > MAXPATHLEN) {
                errno = EINVAL;
                return (NULL);
        }

        if (mkdir(channel_path, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) < 0) {
                if (errno != EEXIST) {
                        errno = EACCES;
                        return (NULL);
                }
        }

        /* Check channel file permissions */
        if (stat(channel_path, &chan_stat) != 0) {
                dprint("sysevent_open_channel: Invalid permissions for channel "
                    "%s\n", channel_path);
                errno = EACCES;
                return (NULL);
        } else if (chan_stat.st_uid != getuid() ||
            !S_ISDIR(chan_stat.st_mode)) {
                dprint("sysevent_open_channel: Invalid "
                    "permissions for channel %s\n: %d:%d:%d", channel_path,
                    (int)chan_stat.st_uid, (int)chan_stat.st_gid,
                    (int)chan_stat.st_mode);

                errno = EACCES;
                return (NULL);
        }

        shp = calloc(1, sizeof (sysevent_impl_hdl_t));
        if (shp == NULL) {
                errno = ENOMEM;
                return (NULL);
        }

        SH_CHANNEL_NAME(shp) = NULL;
        SH_CHANNEL_PATH(shp) = strdup(channel_path);
        if (SH_CHANNEL_PATH(shp) == NULL) {
                free(shp);
                errno = ENOMEM;
                return (NULL);
        }

        /* Extract the channel name */
        begin_path = SH_CHANNEL_PATH(shp);
        while (*begin_path != '\0' &&
            (begin_path = strpbrk(begin_path, "/")) != NULL) {
                ++begin_path;
                SH_CHANNEL_NAME(shp) = begin_path;
        }

        if (update_kernel_registration(shp, 0,
            SE_OPEN_REGISTRATION, &sub_id, 0, NULL) != 0) {
                dprint("sysevent_open_channel: Failed for channel %s\n",
                    SH_CHANNEL_NAME(shp));
                free(SH_CHANNEL_PATH(shp));
                free(shp);
                errno = EFAULT;
                return (NULL);
        }

        (void) mutex_init(SH_LOCK(shp), USYNC_THREAD, NULL);

        return (shp);
}

/*
 * Establish a sysevent channel for publication and subscription
 */
sysevent_handle_t *
sysevent_open_channel(const char *channel)
{
        int var_run_mounted = 0;
        char full_channel[MAXPATHLEN + 1];
        FILE *fp;
        struct stat chan_stat;
        struct extmnttab m;

        if (channel == NULL) {
                errno = EINVAL;
                return (NULL);
        }

        /*
         * Check that /var/run is mounted as tmpfs before allowing a channel
         * to be opened.
         */
        if ((fp = fopen(MNTTAB, "rF")) == NULL) {
                errno = EACCES;
                return (NULL);
        }

        resetmnttab(fp);

        while (getextmntent(fp, &m, sizeof (struct extmnttab)) == 0) {
                if (strcmp(m.mnt_mountp, "/var/run") == 0 &&
                    strcmp(m.mnt_fstype, "tmpfs") == 0) {
                        var_run_mounted = 1;
                        break;
                }
        }
        (void) fclose(fp);

        if (!var_run_mounted) {
                errno = EACCES;
                return (NULL);
        }

        if (stat(CHAN_PATH, &chan_stat) < 0) {
                if (mkdir(CHAN_PATH,
                    S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) < 0) {
                        dprint("sysevent_open_channel: Unable "
                            "to create channel directory %s:%s\n", CHAN_PATH,
                            strerror(errno));
                        if (errno != EEXIST) {
                                errno = EACCES;
                                return (NULL);
                        }
                }
        }

        if (snprintf(full_channel, MAXPATHLEN, "%s/%s", CHAN_PATH, channel) >=
            MAXPATHLEN) {
                errno = EINVAL;
                return (NULL);
        }

        return (sysevent_open_channel_common(full_channel));
}

/*
 * Establish a sysevent channel for publication and subscription
 * Full path to the channel determined by the caller
 */
sysevent_handle_t *
sysevent_open_channel_alt(const char *channel_path)
{
        return (sysevent_open_channel_common(channel_path));
}

/*
 * sysevent_close_channel - Clean up resources associated with a previously
 *                              opened sysevent channel
 */
void
sysevent_close_channel(sysevent_handle_t *shp)
{
        int error = errno;
        uint32_t sub_id = 0;

        if (shp == NULL) {
                return;
        }

        (void) mutex_lock(SH_LOCK(shp));
        if (SH_BOUND(shp)) {
                (void) mutex_unlock(SH_LOCK(shp));
                if (SH_TYPE(shp) == PUBLISHER)
                        sysevent_unbind_publisher(shp);
                else if (SH_TYPE(shp) == SUBSCRIBER)
                        sysevent_unbind_subscriber(shp);
                (void) mutex_lock(SH_LOCK(shp));
        }

        (void) update_kernel_registration(shp, 0,
            SE_CLOSE_REGISTRATION, &sub_id, 0, NULL);
        (void) mutex_unlock(SH_LOCK(shp));

        free(SH_CHANNEL_PATH(shp));
        free(shp);
        errno = error;
}

/*
 * sysevent_bind_publisher - Bind an event publisher to an event channel
 */
int
sysevent_bind_publisher(sysevent_handle_t *shp)
{
        int error = 0;
        int fd = -1;
        char door_name[MAXPATHLEN];
        uint32_t pub_id;
        struct stat reg_stat;
        publisher_priv_t *pub;

        if (shp == NULL) {
                errno = EINVAL;
                return (-1);
        }

        (void) mutex_lock(SH_LOCK(shp));
        if (SH_BOUND(shp)) {
                (void) mutex_unlock(SH_LOCK(shp));
                errno = EINVAL;
                return (-1);
        }

        if ((pub = (publisher_priv_t *)calloc(1, sizeof (publisher_priv_t))) ==
            NULL) {
                (void) mutex_unlock(SH_LOCK(shp));
                errno = ENOMEM;
                return (-1);
        }
        SH_PRIV_DATA(shp) = (void *)pub;

        if (snprintf(door_name, MAXPATHLEN, "%s/%s",
            SH_CHANNEL_PATH(shp), REG_DOOR) >= MAXPATHLEN) {
                free(pub);
                (void) mutex_unlock(SH_LOCK(shp));
                errno = ENOMEM;
                return (-1);
        }
        if ((SH_DOOR_NAME(shp) = strdup(door_name)) == NULL) {
                free(pub);
                (void) mutex_unlock(SH_LOCK(shp));
                errno = ENOMEM;
                return (-1);
        }

        /* Only one publisher allowed per channel */
        if (stat(SH_DOOR_NAME(shp), &reg_stat) != 0) {
                if (errno != ENOENT) {
                        error = EINVAL;
                        goto fail;
                }
        }

        /*
         * Remove door file for robustness.
         */
        if (unlink(SH_DOOR_NAME(shp)) != 0)
                dprint("sysevent_bind_publisher: Unlink of %s failed.\n",
                    SH_DOOR_NAME(shp));

        /* Open channel registration door */
        fd = open(SH_DOOR_NAME(shp), O_CREAT|O_RDWR,
            S_IREAD|S_IWRITE);
        if (fd == -1) {
                error = EINVAL;
                goto fail;
        }

        /*
         * Create the registration service for this publisher.
         */
        if ((SH_DOOR_DESC(shp) = door_create(cache_update_service,
            (void *)shp, DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) {
                dprint("sysevent_bind_publisher: door create failed: "
                    "%s\n", strerror(errno));
                error = EFAULT;
                goto fail;
        }

        (void) fdetach(SH_DOOR_NAME(shp));
        if (fattach(SH_DOOR_DESC(shp), SH_DOOR_NAME(shp)) != 0) {
                dprint("sysevent_bind_publisher: unable to "
                    "bind event channel: fattach: %s\n",
                    SH_DOOR_NAME(shp));
                error = EACCES;
                goto fail;
        }

        /* Bind this publisher in the kernel registration database */
        if (update_kernel_registration(shp, PUBLISHER,
            SE_BIND_REGISTRATION, &pub_id, 0, NULL) != 0) {
                error = errno;
                goto fail;
        }

        SH_ID(shp) = pub_id;
        SH_BOUND(shp) = 1;
        SH_TYPE(shp) = PUBLISHER;


        /* Create the subscription registration cache */
        if (create_cached_registration(shp, SH_CLASS_HASH(shp)) != 0) {
                (void) update_kernel_registration(shp,
                    PUBLISHER, SE_UNBIND_REGISTRATION, &pub_id, 0, NULL);
                error = EFAULT;
                goto fail;
        }
        (void) close(fd);

        (void) mutex_unlock(SH_LOCK(shp));

        return (0);

fail:
        SH_BOUND(shp) = 0;
        (void) door_revoke(SH_DOOR_DESC(shp));
        (void) fdetach(SH_DOOR_NAME(shp));
        free(SH_DOOR_NAME(shp));
        free(pub);
        (void) close(fd);
        (void) mutex_unlock(SH_LOCK(shp));
        errno = error;
        return (-1);
}

static pthread_once_t xdoor_thrattr_once = PTHREAD_ONCE_INIT;
static pthread_attr_t xdoor_thrattr;

static void
xdoor_thrattr_init(void)
{
        (void) pthread_attr_init(&xdoor_thrattr);
        (void) pthread_attr_setdetachstate(&xdoor_thrattr,
            PTHREAD_CREATE_DETACHED);
        (void) pthread_attr_setscope(&xdoor_thrattr, PTHREAD_SCOPE_SYSTEM);
}

static int
xdoor_server_create(door_info_t *dip, void *(*startf)(void *),
    void *startfarg, void *cookie)
{
        struct sysevent_subattr_impl *xsa = cookie;
        pthread_attr_t *thrattr;
        sigset_t oset;
        int err;

        if (xsa->xs_thrcreate) {
                return (xsa->xs_thrcreate(dip, startf, startfarg,
                    xsa->xs_thrcreate_cookie));
        }

        if (xsa->xs_thrattr == NULL) {
                (void) pthread_once(&xdoor_thrattr_once, xdoor_thrattr_init);
                thrattr = &xdoor_thrattr;
        } else {
                thrattr = xsa->xs_thrattr;
        }

        (void) pthread_sigmask(SIG_SETMASK, &xsa->xs_sigmask, &oset);
        err = pthread_create(NULL, thrattr, startf, startfarg);
        (void) pthread_sigmask(SIG_SETMASK, &oset, NULL);

        return (err == 0 ? 1 : -1);
}

static void
xdoor_server_setup(void *cookie)
{
        struct sysevent_subattr_impl *xsa = cookie;

        if (xsa->xs_thrsetup) {
                xsa->xs_thrsetup(xsa->xs_thrsetup_cookie);
        } else {
                (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
                (void) pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
        }
}

static int
sysevent_bind_subscriber_cmn(sysevent_handle_t *shp,
    void (*event_handler)(sysevent_t *ev),
    sysevent_subattr_t *subattr)
{
        int fd = -1;
        int error = 0;
        uint32_t sub_id = 0;
        char door_name[MAXPATHLEN];
        subscriber_priv_t *sub_info;
        int created;
        struct sysevent_subattr_impl *xsa =
            (struct sysevent_subattr_impl *)subattr;

        if (shp == NULL || event_handler == NULL) {
                errno = EINVAL;
                return (-1);
        }

        (void) mutex_lock(SH_LOCK(shp));
        if (SH_BOUND(shp)) {
                errno = EINVAL;
                (void) mutex_unlock(SH_LOCK(shp));
                return (-1);
        }

        if ((sub_info = (subscriber_priv_t *)calloc(1,
            sizeof (subscriber_priv_t))) == NULL) {
                errno = ENOMEM;
                (void) mutex_unlock(SH_LOCK(shp));
                return (-1);
        }

        if (snprintf(door_name, MAXPATHLEN, "%s/%s",
            SH_CHANNEL_PATH(shp), REG_DOOR) >= MAXPATHLEN) {
                free(sub_info);
                errno = EINVAL;
                (void) mutex_unlock(SH_LOCK(shp));
                return (-1);
        }

        if ((sub_info->sp_door_name = strdup(door_name)) == NULL) {
                free(sub_info);
                errno = ENOMEM;
                (void) mutex_unlock(SH_LOCK(shp));
                return (-1);
        }
        (void) cond_init(&sub_info->sp_cv, USYNC_THREAD, NULL);
        (void) mutex_init(&sub_info->sp_qlock, USYNC_THREAD, NULL);
        sub_info->sp_func = event_handler;

        /* Update the in-kernel registration */
        if (update_kernel_registration(shp, SUBSCRIBER,
            SE_BIND_REGISTRATION, &sub_id, 0, NULL) != 0) {
                error = errno;
                goto fail;
        }
        SH_ID(shp) = sub_id;

        if (snprintf(door_name, MAXPATHLEN, "%s/%d",
            SH_CHANNEL_PATH(shp), sub_id) >= MAXPATHLEN) {
                error = EINVAL;
                goto fail;
        }
        if ((SH_DOOR_NAME(shp) = strdup(door_name)) == NULL) {
                error = ENOMEM;
                goto fail;
        }

        /*
         * Remove door file for robustness.
         */
        if (unlink(SH_DOOR_NAME(shp)) != 0)
                dprint("sysevent_bind_subscriber: Unlink of %s failed.\n",
                    SH_DOOR_NAME(shp));

        fd = open(SH_DOOR_NAME(shp), O_CREAT|O_RDWR, S_IREAD|S_IWRITE);
        if (fd == -1) {
                error = EFAULT;
                goto fail;
        }

        /*
         * Create the sysevent door service for this client.
         * syseventd will use this door service to propagate
         * events to the client.
         */
        if (subattr == NULL) {
                SH_DOOR_DESC(shp) = door_create(event_deliver_service,
                    (void *)shp, DOOR_REFUSE_DESC | DOOR_NO_CANCEL);
        } else {
                SH_DOOR_DESC(shp) = door_xcreate(event_deliver_service,
                    (void *)shp,
                    DOOR_REFUSE_DESC | DOOR_NO_CANCEL | DOOR_NO_DEPLETION_CB,
                    xdoor_server_create, xdoor_server_setup,
                    (void *)subattr, 1);
        }

        if (SH_DOOR_DESC(shp) == -1) {
                dprint("sysevent_bind_subscriber: door create failed: "
                    "%s\n", strerror(errno));
                error = EFAULT;
                goto fail;
        }

        (void) fdetach(SH_DOOR_NAME(shp));
        if (fattach(SH_DOOR_DESC(shp), SH_DOOR_NAME(shp)) != 0) {
                error = EFAULT;
                goto fail;
        }
        (void) close(fd);

        if (update_publisher_cache(sub_info, SE_BIND_REGISTRATION,
            sub_id, 0, NULL) != 0) {
                error = errno;
                (void) update_kernel_registration(shp, SUBSCRIBER,
                    SE_UNBIND_REGISTRATION, &sub_id, 0, NULL);
                goto fail;
        }

        SH_BOUND(shp) = 1;
        SH_TYPE(shp) = SUBSCRIBER;
        SH_PRIV_DATA(shp) = (void *)sub_info;

        /* Create an event handler thread */
        if (xsa == NULL || xsa->xs_thrcreate == NULL) {
                created = thr_create(NULL, 0, subscriber_event_handler,
                    shp, THR_BOUND, &sub_info->sp_handler_tid) == 0;
        } else {
                /*
                 * A terrible hack.  We will use the extended private
                 * door thread creation function the caller passed in to
                 * create the event handler thread.  That function will
                 * be called with our chosen thread start function and arg
                 * instead of the usual libc-provided ones, but that's ok
                 * as it is required to use them verbatim anyway.  We will
                 * pass a NULL door_info_t pointer to the function - so
                 * callers depending on this hack had better be prepared
                 * for that.  All this allow the caller to rubberstamp
                 * the created thread as it wishes.  But we don't get
                 * the created threadid with this, so we modify the
                 * thread start function to stash it.
                 */

                created = xsa->xs_thrcreate(NULL, subscriber_event_handler,
                    shp, xsa->xs_thrcreate_cookie) == 1;
        }

        if (!created) {
                error = EFAULT;
                goto fail;
        }

        (void) mutex_unlock(SH_LOCK(shp));

        return (0);

fail:
        (void) close(fd);
        (void) door_revoke(SH_DOOR_DESC(shp));
        (void) fdetach(SH_DOOR_NAME(shp));
        (void) cond_destroy(&sub_info->sp_cv);
        (void) mutex_destroy(&sub_info->sp_qlock);
        free(sub_info->sp_door_name);
        free(sub_info);
        if (SH_ID(shp)) {
                (void) update_kernel_registration(shp, SUBSCRIBER,
                    SE_UNBIND_REGISTRATION, &sub_id, 0, NULL);
                SH_ID(shp) = 0;
        }
        if (SH_BOUND(shp)) {
                (void) update_publisher_cache(sub_info, SE_UNBIND_REGISTRATION,
                    sub_id, 0, NULL);
                free(SH_DOOR_NAME(shp));
                SH_BOUND(shp) = 0;
        }
        (void) mutex_unlock(SH_LOCK(shp));

        errno = error;

        return (-1);
}

/*
 * sysevent_bind_subscriber - Bind an event receiver to an event channel
 */
int
sysevent_bind_subscriber(sysevent_handle_t *shp,
    void (*event_handler)(sysevent_t *ev))
{
        return (sysevent_bind_subscriber_cmn(shp, event_handler, NULL));
}

/*
 * sysevent_bind_xsubscriber - Bind a subscriber using door_xcreate with
 * attributes specified.
 */
int
sysevent_bind_xsubscriber(sysevent_handle_t *shp,
    void (*event_handler)(sysevent_t *ev), sysevent_subattr_t *subattr)
{
        return (sysevent_bind_subscriber_cmn(shp, event_handler, subattr));
}

/*
 * sysevent_register_event - register an event class and associated subclasses
 *              for an event subscriber
 */
int
sysevent_register_event(sysevent_handle_t *shp,
    const char *ev_class, const char **ev_subclass,
    int subclass_num)
{
        int error;
        char *event_class = (char *)ev_class;
        char **event_subclass_list = (char **)ev_subclass;
        char *nvlbuf = NULL;
        size_t datalen;
        nvlist_t *nvl;

        (void) mutex_lock(SH_LOCK(shp));
        if (event_class == NULL || event_subclass_list == NULL ||
            event_subclass_list[0] == NULL || SH_BOUND(shp) != 1 ||
            subclass_num <= 0) {
                (void) mutex_unlock(SH_LOCK(shp));
                errno = EINVAL;
                return (-1);
        }

        if (nvlist_alloc(&nvl, NV_UNIQUE_NAME_TYPE, 0) != 0) {
                (void) mutex_unlock(SH_LOCK(shp));
                return (-1);
        }
        if (nvlist_add_string_array(nvl, event_class, event_subclass_list,
            subclass_num) != 0) {
                nvlist_free(nvl);
                (void) mutex_unlock(SH_LOCK(shp));
                return (-1);
        }
        if (nvlist_pack(nvl, &nvlbuf, &datalen, NV_ENCODE_NATIVE, 0) != 0) {
                nvlist_free(nvl);
                (void) mutex_unlock(SH_LOCK(shp));
                return (-1);
        }
        nvlist_free(nvl);

        /* Store new subscriber in in-kernel registration */
        if (update_kernel_registration(shp, SUBSCRIBER,
            SE_REGISTER, &SH_ID(shp), datalen, (uchar_t *)nvlbuf)
            != 0) {
                error = errno;
                free(nvlbuf);
                (void) mutex_unlock(SH_LOCK(shp));
                errno = error;
                return (-1);
        }
        /* Update the publisher's cached registration */
        if (update_publisher_cache(
            (subscriber_priv_t *)SH_PRIV_DATA(shp), SE_REGISTER,
            SH_ID(shp), datalen, (uchar_t *)nvlbuf) != 0) {
                error = errno;
                free(nvlbuf);
                (void) mutex_unlock(SH_LOCK(shp));
                errno = error;
                return (-1);
        }

        free(nvlbuf);

        (void) mutex_unlock(SH_LOCK(shp));

        return (0);
}

/*
 * sysevent_unregister_event - Unregister an event class and associated
 *                              subclasses for an event subscriber
 */
void
sysevent_unregister_event(sysevent_handle_t *shp, const char *class)
{
        size_t class_sz;

        (void) mutex_lock(SH_LOCK(shp));

        if (!SH_BOUND(shp)) {
                (void) mutex_unlock(SH_LOCK(shp));
                return;
        }

        /* Remove subscriber from in-kernel registration */
        class_sz = strlen(class) + 1;
        (void) update_kernel_registration(shp, SUBSCRIBER,
            SE_UNREGISTER, &SH_ID(shp), class_sz, (uchar_t *)class);
        /* Update the publisher's cached registration */
        (void) update_publisher_cache(
            (subscriber_priv_t *)SH_PRIV_DATA(shp), SE_UNREGISTER,
            SH_ID(shp), class_sz, (uchar_t *)class);

        (void) mutex_unlock(SH_LOCK(shp));
}

static int
cleanup_id(sysevent_handle_t *shp, uint32_t id, int type)
{
        dprint("cleanup_id: Cleaning up %s/%d\n", SH_CHANNEL_NAME(shp), id);

        /* Remove registration from the kernel */
        if (update_kernel_registration(shp, type, SE_CLEANUP, &id,
            0, NULL) != 0) {
                dprint("cleanup_id: Unable to clean "
                    "up %s/%d\n", SH_CHANNEL_NAME(shp), id);
                return (-1);
        }

        return (0);
}

/*
 * sysevent_cleanup_subscribers: Allows the caller to cleanup resources
 *              allocated to unresponsive subscribers.
 */
void
sysevent_cleanup_subscribers(sysevent_handle_t *shp)
{
        uint32_t ping, result;
        int i, error, sub_fd;
        subscriber_data_t *sub;

        if (!SH_BOUND(shp)) {
                return;
        }

        for (i = 1; i <= MAX_SUBSCRIBERS; ++i) {

                sub = SH_SUBSCRIBER(shp, i);
                if (sub == NULL) {
                        continue;
                }

                if ((sub_fd = open(sub->sd_door_name, O_RDONLY)) == -1) {
                        continue;
                }
                /* Check for valid and responsive subscriber */
                error = clnt_deliver_event(sub_fd, &ping,
                    sizeof (uint32_t), &result, sizeof (result));
                (void) close(sub_fd);

                /* Only cleanup on EBADF (Invalid door descriptor) */
                if (error != EBADF)
                        continue;

                if (cleanup_id(shp, i, SUBSCRIBER) != 0)
                        continue;

                cache_remove_class(shp, EC_ALL, i);

                free(sub->sd_door_name);
                free(sub);
                SH_SUBSCRIBER(shp, i) = NULL;
        }

}

/*
 * sysevent_cleanup_publishers: Allows stale publisher handles to be deallocated
 *              as needed.
 */
void
sysevent_cleanup_publishers(sysevent_handle_t *shp)
{
        (void) cleanup_id(shp, 1, PUBLISHER);
}

/*
 * sysevent_unbind_subscriber: Unbind the subscriber from the sysevent channel.
 */
void
sysevent_unbind_subscriber(sysevent_handle_t *shp)
{
        subscriber_priv_t *sub_info;

        if (shp == NULL)
                return;

        (void) mutex_lock(SH_LOCK(shp));
        if (SH_BOUND(shp) == 0) {
                (void) mutex_unlock(SH_LOCK(shp));
                return;
        }

        /* Update the in-kernel registration */
        (void) update_kernel_registration(shp, SUBSCRIBER,
            SE_UNBIND_REGISTRATION, &SH_ID(shp), 0, NULL);

        /* Update the sysevent channel publisher */
        sub_info = (subscriber_priv_t *)SH_PRIV_DATA(shp);
        (void) update_publisher_cache(sub_info, SE_UNBIND_REGISTRATION,
            SH_ID(shp), 0, NULL);

        /* Close down event delivery facilities */
        (void) door_revoke(SH_DOOR_DESC(shp));
        (void) fdetach(SH_DOOR_NAME(shp));

        /*
         * Release resources and wait for pending event delivery to
         * complete.
         */
        (void) mutex_lock(&sub_info->sp_qlock);
        SH_BOUND(shp) = 0;
        /* Signal event handler and drain the subscriber's event queue */
        (void) cond_signal(&sub_info->sp_cv);
        (void) mutex_unlock(&sub_info->sp_qlock);
        if (sub_info->sp_handler_tid != 0)
                (void) thr_join(sub_info->sp_handler_tid, NULL, NULL);

        (void) cond_destroy(&sub_info->sp_cv);
        (void) mutex_destroy(&sub_info->sp_qlock);
        free(sub_info->sp_door_name);
        free(sub_info);
        free(SH_DOOR_NAME(shp));
        (void) mutex_unlock(SH_LOCK(shp));
}

/*
 * sysevent_unbind_publisher: Unbind publisher from the sysevent channel.
 */
void
sysevent_unbind_publisher(sysevent_handle_t *shp)
{
        if (shp == NULL)
                return;

        (void) mutex_lock(SH_LOCK(shp));
        if (SH_BOUND(shp) == 0) {
                (void) mutex_unlock(SH_LOCK(shp));
                return;
        }

        /* Close down the registration facilities */
        (void) door_revoke(SH_DOOR_DESC(shp));
        (void) fdetach(SH_DOOR_NAME(shp));

        /* Update the in-kernel registration */
        (void) update_kernel_registration(shp, PUBLISHER,
            SE_UNBIND_REGISTRATION, &SH_ID(shp), 0, NULL);
        SH_BOUND(shp) = 0;

        /* Free resources associated with bind */
        free_cached_registration(shp);
        dealloc_subscribers(shp);

        free(SH_PRIV_DATA(shp));
        free(SH_DOOR_NAME(shp));
        SH_ID(shp) = 0;
        (void) mutex_unlock(SH_LOCK(shp));
}

/*
 * Evolving APIs to subscribe to syseventd(8) system events.
 */

static sysevent_handle_t *
sysevent_bind_handle_cmn(void (*event_handler)(sysevent_t *ev),
    sysevent_subattr_t *subattr)
{
        sysevent_handle_t *shp;

        if (getuid() != 0) {
                errno = EACCES;
                return (NULL);
        }

        if (event_handler == NULL) {
                errno = EINVAL;
                return (NULL);
        }

        if ((shp = sysevent_open_channel(SYSEVENTD_CHAN)) == NULL) {
                return (NULL);
        }

        if (sysevent_bind_xsubscriber(shp, event_handler, subattr) != 0) {
                /*
                 * Ask syseventd to clean-up any stale subcribers and try to
                 * to bind again
                 */
                if (errno == EBUSY) {
                        int pub_fd;
                        char door_name[MAXPATHLEN];
                        uint32_t result;
                        struct reg_args rargs;

                        if (snprintf(door_name, MAXPATHLEN, "%s/%s",
                            SH_CHANNEL_PATH(shp), REG_DOOR) >= MAXPATHLEN) {
                                sysevent_close_channel(shp);
                                errno = EINVAL;
                                return (NULL);
                        }

                        rargs.ra_op = SE_CLEANUP;
                        pub_fd = open(door_name, O_RDONLY);
                        (void) clnt_deliver_event(pub_fd, (void *)&rargs,
                            sizeof (struct reg_args), &result, sizeof (result));
                        (void) close(pub_fd);

                        /* Try to bind again */
                        if (sysevent_bind_xsubscriber(shp, event_handler,
                            subattr) != 0) {
                                sysevent_close_channel(shp);
                                return (NULL);
                        }
                } else {
                        sysevent_close_channel(shp);
                        return (NULL);
                }
        }

        return (shp);
}

/*
 * sysevent_bind_handle - Bind application event handler for syseventd
 *              subscription.
 */
sysevent_handle_t *
sysevent_bind_handle(void (*event_handler)(sysevent_t *ev))
{
        return (sysevent_bind_handle_cmn(event_handler, NULL));
}

/*
 * sysevent_bind_xhandle - Bind application event handler for syseventd
 *              subscription, using door_xcreate and attributes as specified.
 */
sysevent_handle_t *
sysevent_bind_xhandle(void (*event_handler)(sysevent_t *ev),
    sysevent_subattr_t *subattr)
{
        return (sysevent_bind_handle_cmn(event_handler, subattr));
}

/*
 * sysevent_unbind_handle - Unbind caller from syseventd subscriptions
 */
void
sysevent_unbind_handle(sysevent_handle_t *shp)
{
        sysevent_unbind_subscriber(shp);
        sysevent_close_channel(shp);
}

/*
 * sysevent_subscribe_event - Subscribe to system event notification from
 *                      syseventd(8) for the class and subclasses specified.
 */
int
sysevent_subscribe_event(sysevent_handle_t *shp, const char *event_class,
    const char **event_subclass_list, int num_subclasses)
{
        return (sysevent_register_event(shp, event_class,
            event_subclass_list, num_subclasses));
}

void
sysevent_unsubscribe_event(sysevent_handle_t *shp, const char *event_class)
{
        sysevent_unregister_event(shp, event_class);
}