/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(1M), 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), ®_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(1M) 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(1M) 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); }