summaryrefslogtreecommitdiff
path: root/usr/src/lib/libsysevent/libevchannel.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/libsysevent/libevchannel.c')
-rw-r--r--usr/src/lib/libsysevent/libevchannel.c529
1 files changed, 529 insertions, 0 deletions
diff --git a/usr/src/lib/libsysevent/libevchannel.c b/usr/src/lib/libsysevent/libevchannel.c
new file mode 100644
index 0000000000..a18a62b0dd
--- /dev/null
+++ b/usr/src/lib/libsysevent/libevchannel.c
@@ -0,0 +1,529 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <stdio.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <door.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <sys/varargs.h>
+#include <sys/sysevent.h>
+#include <sys/sysevent_impl.h>
+
+#include "libsysevent.h"
+#include "libsysevent_impl.h"
+
+/*
+ * The functions below deal with the General Purpose Event Handling framework
+ *
+ * sysevent_evc_bind - create/bind application to named channel
+ * sysevent_evc_unbind - unbind from previously bound/created channel
+ * sysevent_evc_subscribe - subscribe to existing event channel
+ * sysevent_evc_unsubscribe - unsubscribe from existing event channel
+ * sysevent_evc_publish - generate a system event via an event channel
+ * sysevent_evc_control - various channel based control operation
+ */
+
+#define misaligned(p) ((uintptr_t)(p) & 3) /* 4-byte alignment required */
+
+/*
+ * Check syntax of a channel name
+ */
+static int
+sysevent_is_chan_name(const char *str)
+{
+ for (; *str != '\0'; str++) {
+ if (!EVCH_ISCHANCHAR(*str))
+ return (0);
+ }
+
+ return (1);
+}
+
+/*
+ * Check for printable characters
+ */
+static int
+strisprint(const char *s)
+{
+ for (; *s != '\0'; s++) {
+ if (*s < ' ' || *s > '~')
+ return (0);
+ }
+
+ return (1);
+}
+
+/*
+ * sysevent_evc_bind - Create/bind application to named channel
+ */
+int
+sysevent_evc_bind(const char *channel, evchan_t **scpp, uint32_t flags)
+{
+ int chanlen;
+ evchan_t *scp;
+ sev_bind_args_t uargs;
+ int ec;
+
+ if (scpp == NULL || misaligned(scpp)) {
+ return (errno = EINVAL);
+ }
+
+ /* Provide useful value in error case */
+ *scpp = NULL;
+
+ if (channel == NULL ||
+ (chanlen = strlen(channel) + 1) > MAX_CHNAME_LEN) {
+ return (errno = EINVAL);
+ }
+
+ /* Check channel syntax */
+ if (!sysevent_is_chan_name(channel)) {
+ return (errno = EINVAL);
+ }
+
+ if (flags & ~EVCH_B_FLAGS) {
+ return (errno = EINVAL);
+ }
+
+ scp = calloc(1, sizeof (evchan_impl_hdl_t));
+ if (scp == NULL) {
+ return (errno = ENOMEM);
+ }
+
+ /*
+ * Enable sysevent driver. Fallback if the device link doesn't exist;
+ * this situation can arise if a channel is bound early in system
+ * startup, prior to devfsadm(1M) being invoked.
+ */
+ EV_FD(scp) = open(DEVSYSEVENT, O_RDWR);
+ if (EV_FD(scp) == -1) {
+ if (errno != ENOENT) {
+ ec = errno == EACCES ? EPERM : errno;
+ free(scp);
+ return (errno = ec);
+ }
+
+ EV_FD(scp) = open(DEVICESYSEVENT, O_RDWR);
+ if (EV_FD(scp) == -1) {
+ ec = errno == EACCES ? EPERM : errno;
+ free(scp);
+ return (errno = ec);
+ }
+ }
+
+ /*
+ * Force to close the fd's when process is doing exec.
+ * The driver will then release stale binding handles.
+ * The driver will release also the associated subscriptions
+ * if EVCH_SUB_KEEP flag was not set.
+ */
+ (void) fcntl(EV_FD(scp), F_SETFD, FD_CLOEXEC);
+
+ uargs.chan_name.name = (uintptr_t)channel;
+ uargs.chan_name.len = chanlen;
+ uargs.flags = flags;
+
+ if (ioctl(EV_FD(scp), SEV_CHAN_OPEN, &uargs) != 0) {
+ ec = errno;
+ (void) close(EV_FD(scp));
+ free(scp);
+ return (errno = ec);
+ }
+
+ /* Needed to detect a fork() */
+ EV_PID(scp) = getpid();
+ (void) mutex_init(EV_LOCK(scp), USYNC_THREAD, NULL);
+
+ *scpp = scp;
+
+ return (0);
+}
+
+/*
+ * sysevent_evc_unbind - Unbind from previously bound/created channel
+ */
+void
+sysevent_evc_unbind(evchan_t *scp)
+{
+ sev_unsubscribe_args_t uargs;
+ evchan_subscr_t *subp, *tofree;
+
+ if (scp == NULL || misaligned(scp))
+ return;
+
+ (void) mutex_lock(EV_LOCK(scp));
+
+ /*
+ * Unsubscribe, if we are in the process which did the bind.
+ */
+ if (EV_PID(scp) == getpid()) {
+ uargs.sid.name = NULL;
+ uargs.sid.len = 0;
+ /*
+ * The unsubscribe ioctl will block until all door upcalls have
+ * drained.
+ */
+ if (ioctl(EV_FD(scp), SEV_UNSUBSCRIBE, (intptr_t)&uargs) != 0) {
+ (void) mutex_unlock(EV_LOCK(scp));
+ return;
+ }
+ }
+
+ subp = (evchan_subscr_t *)(void*)EV_SUB(scp);
+ while (subp->evsub_next != NULL) {
+ tofree = subp->evsub_next;
+ subp->evsub_next = tofree->evsub_next;
+ if (door_revoke(tofree->evsub_door_desc) != 0 && errno == EPERM)
+ (void) close(tofree->evsub_door_desc);
+ free(tofree->evsub_sid);
+ free(tofree);
+ }
+
+ (void) mutex_unlock(EV_LOCK(scp));
+
+ /*
+ * The close of the driver will do the unsubscribe if a) it is the last
+ * close and b) we are in a child which inherited subscriptions.
+ */
+ (void) close(EV_FD(scp));
+ (void) mutex_destroy(EV_LOCK(scp));
+ free(scp);
+}
+
+/*
+ * sysevent_evc_publish - Generate a system event via an event channel
+ */
+int
+sysevent_evc_publish(evchan_t *scp, const char *class,
+ const char *subclass, const char *vendor,
+ const char *pub_name, nvlist_t *attr_list,
+ uint32_t flags)
+{
+ sysevent_t *ev;
+ sev_publish_args_t uargs;
+ int rc;
+ int ec;
+
+ if (scp == NULL || misaligned(scp)) {
+ return (errno = EINVAL);
+ }
+
+ /* No inheritance of binding handles via fork() */
+ if (EV_PID(scp) != getpid()) {
+ return (errno = EINVAL);
+ }
+
+ ev = sysevent_alloc_event((char *)class, (char *)subclass,
+ (char *)vendor, (char *)pub_name, attr_list);
+ if (ev == NULL) {
+ return (errno);
+ }
+
+ uargs.ev.name = (uintptr_t)ev;
+ uargs.ev.len = SE_SIZE(ev);
+ uargs.flags = flags;
+
+ (void) mutex_lock(EV_LOCK(scp));
+
+ rc = ioctl(EV_FD(scp), SEV_PUBLISH, (intptr_t)&uargs);
+ ec = errno;
+
+ (void) mutex_unlock(EV_LOCK(scp));
+
+ sysevent_free(ev);
+
+ if (rc != 0) {
+ return (ec);
+ }
+ return (0);
+}
+
+/*
+ * Generic callback which catches events from the kernel and calls
+ * subscribers call back routine.
+ *
+ * Kernel guarantees that door_upcalls are disabled when unsubscription
+ * was issued that's why cookie points always to a valid evchan_subscr_t *.
+ *
+ * Furthermore it's not necessary to lock subp because the sysevent
+ * framework guarantees no unsubscription until door_return.
+ */
+/*ARGSUSED3*/
+static void
+door_upcall(void *cookie, char *args, size_t alen,
+ door_desc_t *ddp, uint_t ndid)
+{
+ evchan_subscr_t *subp = EVCHAN_SUBSCR(cookie);
+ int rval = 0;
+
+ if (args == NULL || alen <= (size_t)0) {
+ /* Skip callback execution */
+ rval = EINVAL;
+ } else {
+ rval = subp->evsub_func((sysevent_t *)(void *)args,
+ subp->evsub_cookie);
+ }
+
+ /*
+ * Fill in return values for door_return
+ */
+ alen = sizeof (rval);
+ bcopy(&rval, args, alen);
+
+ (void) door_return(args, alen, NULL, 0);
+}
+
+/*
+ * sysevent_evc_subscribe - Subscribe to an existing event channel
+ */
+int
+sysevent_evc_subscribe(evchan_t *scp, const char *sid, const char *class,
+ int (*event_handler)(sysevent_t *ev, void *cookie),
+ void *cookie, uint32_t flags)
+{
+ evchan_subscr_t *subp;
+ int upcall_door;
+ sev_subscribe_args_t uargs;
+ uint32_t sid_len;
+ uint32_t class_len;
+ int ec;
+
+ if (scp == NULL || misaligned(scp) || sid == NULL || class == NULL) {
+ return (errno = EINVAL);
+ }
+
+ /* No inheritance of binding handles via fork() */
+ if (EV_PID(scp) != getpid()) {
+ return (errno = EINVAL);
+ }
+
+ if ((sid_len = strlen(sid) + 1) > MAX_SUBID_LEN || sid_len == 1 ||
+ (class_len = strlen(class) + 1) > MAX_CLASS_LEN) {
+ return (errno = EINVAL);
+ }
+
+ /* Check for printable characters */
+ if (!strisprint(sid)) {
+ return (errno = EINVAL);
+ }
+
+ if (event_handler == NULL) {
+ return (errno = EINVAL);
+ }
+
+ /* Create subscriber data */
+ if ((subp = calloc(1, sizeof (evchan_subscr_t))) == NULL) {
+ return (errno);
+ }
+
+ if ((subp->evsub_sid = strdup(sid)) == NULL) {
+ ec = errno;
+ free(subp);
+ return (ec);
+ }
+
+ /*
+ * EC_ALL string will not be copied to kernel - NULL is assumed
+ */
+ if (strcmp(class, EC_ALL) == 0) {
+ class = NULL;
+ class_len = 0;
+ }
+
+ upcall_door = door_create(door_upcall, (void *)subp,
+ DOOR_REFUSE_DESC | DOOR_NO_CANCEL);
+ if (upcall_door == -1) {
+ ec = errno;
+ free(subp->evsub_sid);
+ free(subp);
+ return (ec);
+ }
+
+ /* Complete subscriber information */
+ subp->evsub_door_desc = upcall_door;
+ subp->evsub_func = event_handler;
+ subp->evsub_cookie = cookie;
+
+ (void) mutex_lock(EV_LOCK(scp));
+
+ subp->ev_subhead = EVCHAN_IMPL_HNDL(scp);
+
+ uargs.sid.name = (uintptr_t)sid;
+ uargs.sid.len = sid_len;
+ uargs.class_info.name = (uintptr_t)class;
+ uargs.class_info.len = class_len;
+ uargs.door_desc = subp->evsub_door_desc;
+ uargs.flags = flags;
+ if (ioctl(EV_FD(scp), SEV_SUBSCRIBE, (intptr_t)&uargs) != 0) {
+ ec = errno;
+ (void) mutex_unlock(EV_LOCK(scp));
+ (void) door_revoke(upcall_door);
+ free(subp->evsub_sid);
+ free(subp);
+ return (ec);
+ }
+
+ /* Attach to subscriber list */
+ subp->evsub_next = EV_SUB_NEXT(scp);
+ EV_SUB_NEXT(scp) = subp;
+
+ (void) mutex_unlock(EV_LOCK(scp));
+
+ return (0);
+}
+
+/*
+ * sysevent_evc_unsubscribe - Unsubscribe from an existing event channel
+ */
+void
+sysevent_evc_unsubscribe(evchan_t *scp, const char *sid)
+{
+ int all_subscribers = 0;
+ sev_unsubscribe_args_t uargs;
+ evchan_subscr_t *subp, *tofree;
+ int rc;
+
+ if (scp == NULL || misaligned(scp))
+ return;
+
+ if (sid == NULL || strlen(sid) == 0 ||
+ (strlen(sid) >= MAX_SUBID_LEN))
+ return;
+
+ /* No inheritance of binding handles via fork() */
+ if (EV_PID(scp) != getpid()) {
+ return;
+ }
+
+ if (strcmp(sid, EVCH_ALLSUB) == 0) {
+ all_subscribers++;
+ /* Indicates all subscriber id's for this channel */
+ uargs.sid.name = NULL;
+ uargs.sid.len = 0;
+ } else {
+ uargs.sid.name = (uintptr_t)sid;
+ uargs.sid.len = strlen(sid) + 1;
+ }
+
+ (void) mutex_lock(EV_LOCK(scp));
+
+ /*
+ * The unsubscribe ioctl will block until all door upcalls have drained.
+ */
+ rc = ioctl(EV_FD(scp), SEV_UNSUBSCRIBE, (intptr_t)&uargs);
+
+ if (rc != 0) {
+ (void) mutex_unlock(EV_LOCK(scp));
+ return;
+ }
+
+ /* Search for the matching subscriber */
+ subp = (evchan_subscr_t *)(void*)EV_SUB(scp);
+ while (subp->evsub_next != NULL) {
+
+ if (all_subscribers ||
+ (strcmp(subp->evsub_next->evsub_sid, sid) == 0)) {
+
+ tofree = subp->evsub_next;
+ subp->evsub_next = tofree->evsub_next;
+ (void) door_revoke(tofree->evsub_door_desc);
+ free(tofree->evsub_sid);
+ free(tofree);
+ /* Freed single subscriber already */
+ if (all_subscribers == 0) {
+ break;
+ }
+ } else
+ subp = subp->evsub_next;
+ }
+
+ (void) mutex_unlock(EV_LOCK(scp));
+}
+
+/*
+ * sysevent_evc_control - Various channel based control operation
+ */
+int
+sysevent_evc_control(evchan_t *scp, int cmd, /* arg */ ...)
+{
+ va_list ap;
+ uint32_t *chlenp;
+ sev_control_args_t uargs;
+ int rc = 0;
+
+ if (scp == NULL || misaligned(scp)) {
+ return (errno = EINVAL);
+ }
+
+ /* No inheritance of binding handles via fork() */
+ if (EV_PID(scp) != getpid()) {
+ return (errno = EINVAL);
+ }
+
+ va_start(ap, cmd);
+
+ uargs.cmd = cmd;
+
+ (void) mutex_lock(EV_LOCK(scp));
+
+ switch (cmd) {
+ case EVCH_GET_CHAN_LEN:
+ case EVCH_GET_CHAN_LEN_MAX:
+ chlenp = va_arg(ap, uint32_t *);
+ if (chlenp == NULL || misaligned(chlenp)) {
+ rc = EINVAL;
+ break;
+ }
+ rc = ioctl(EV_FD(scp), SEV_CHAN_CONTROL, (intptr_t)&uargs);
+ *chlenp = uargs.value;
+ break;
+ case EVCH_SET_CHAN_LEN:
+ /* Range change will be handled in framework */
+ uargs.value = va_arg(ap, uint32_t);
+ rc = ioctl(EV_FD(scp), SEV_CHAN_CONTROL, (intptr_t)&uargs);
+ break;
+ default:
+ rc = EINVAL;
+ }
+
+ (void) mutex_unlock(EV_LOCK(scp));
+
+ if (rc == -1) {
+ rc = errno;
+ }
+
+ va_end(ap);
+
+ return (errno = rc);
+}