summaryrefslogtreecommitdiff
path: root/usr/src/lib/libdiskmgt/common/events.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/libdiskmgt/common/events.c')
-rw-r--r--usr/src/lib/libdiskmgt/common/events.c496
1 files changed, 496 insertions, 0 deletions
diff --git a/usr/src/lib/libdiskmgt/common/events.c b/usr/src/lib/libdiskmgt/common/events.c
new file mode 100644
index 0000000000..e6dc869d2a
--- /dev/null
+++ b/usr/src/lib/libdiskmgt/common/events.c
@@ -0,0 +1,496 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stropts.h>
+#include <synch.h>
+#include <thread.h>
+#include <libsysevent.h>
+#include <sys/sysevent/eventdefs.h>
+#include <sys/sysevent/dev.h>
+#include <errno.h>
+#include <libgen.h>
+#include <unistd.h>
+
+#include "libdiskmgt.h"
+#include "disks_private.h"
+
+struct event_list {
+ struct event_list *next;
+ nvlist_t *event;
+};
+
+static struct event_list *events = NULL;
+static int event_error = 0;
+static int event_break = 0;
+static mutex_t queue_lock;
+static sema_t semaphore;
+
+/*
+ * When we add a controller we get an add event for each drive on the
+ * controller. We don't want to walk the devtree for each drive since
+ * we will get the same information each time. So, the solution is to
+ * wait for a few seconds for all of the add events to come in and then
+ * do a single walk. If an add event comes in after we start the walk, we
+ * need to do another walk since we might have missed that drive.
+ *
+ * State: 0 - no walker; 1 - walker waiting; 2 - walker running
+ * 0 -> 1; wait a few seconds
+ * 1 -> 2; walking the devtree
+ * 2 -> either 0 or 1 (see below)
+ * While running (state 2), if event comes in, go back to waiting (state 1)
+ * after the walk otherwise go back to none (state 0).
+ *
+ * walker_lock protects walker_state & events_pending
+ */
+#define WALK_NONE 0
+#define WALK_WAITING 1
+#define WALK_RUNNING 2
+#define WALK_WAIT_TIME 60 /* wait 60 seconds */
+
+static mutex_t walker_lock;
+static int walker_state = WALK_NONE;
+static int events_pending = 0;
+
+static int sendevents = 0;
+
+static void add_event_to_queue(nvlist_t *event);
+static void cb_watch_events();
+static void event_handler(sysevent_t *ev);
+static void print_nvlist(char *prefix, nvlist_t *list);
+static void walk_devtree();
+static void walker(void *arg);
+
+static void(*callback)(nvlist_t *, int) = NULL;
+
+nvlist_t *
+dm_get_event(int *errp)
+{
+ nvlist_t *event = NULL;
+
+ *errp = 0;
+
+ /* wait until there is an event in the queue */
+ /*CONSTCOND*/
+ while (1) {
+ (void) sema_wait(&semaphore);
+
+ if (event_break) {
+ event_break = 0;
+ *errp = EINTR;
+ break;
+ }
+
+ (void) mutex_lock(&queue_lock);
+
+ /* first see if we ran out of memory since the last call */
+ if (event_error != 0) {
+ *errp = event_error;
+ event_error = 0;
+
+ } else if (events != NULL) {
+ struct event_list *tmpp;
+
+ event = events->event;
+ tmpp = events->next;
+ free(events);
+ events = tmpp;
+ }
+
+ (void) mutex_unlock(&queue_lock);
+
+ if (*errp != 0 || event != NULL) {
+ break;
+ }
+ }
+
+ return (event);
+}
+
+void
+dm_init_event_queue(void (*cb)(nvlist_t *, int), int *errp)
+{
+ if (sendevents == 1) {
+ /* we were already initialized, see what changes to make */
+ *errp = 0;
+ if (cb != callback) {
+
+ callback = cb;
+ if (cb == NULL) {
+ /* clearing the cb so shutdown the internal cb thread */
+ event_break = 1;
+ (void) sema_post(&semaphore);
+
+ } else {
+ /* installing a cb; we didn't have one before */
+ thread_t watch_thread;
+
+ *errp = thr_create(NULL, NULL,
+ (void *(*)(void *))cb_watch_events, NULL, THR_DAEMON,
+ &watch_thread);
+ }
+ }
+
+ } else {
+ /* first time to initialize */
+ sendevents = 1;
+
+ *errp = sema_init(&semaphore, 0, USYNC_THREAD, NULL);
+ if (*errp != 0) {
+ return;
+ }
+
+ if (cb != NULL) {
+ thread_t watch_thread;
+
+ callback = cb;
+
+ *errp = thr_create(NULL, NULL,
+ (void *(*)(void *))cb_watch_events, NULL, THR_DAEMON,
+ &watch_thread);
+ }
+ }
+}
+
+void
+events_new_event(char *name, int dtype, char *etype)
+{
+ nvlist_t *event = NULL;
+
+ if (!sendevents) {
+ return;
+ }
+
+ if (nvlist_alloc(&event, NVATTRS, 0) != 0) {
+ event = NULL;
+
+ } else {
+ int error = 0;
+
+ if (name != NULL &&
+ nvlist_add_string(event, DM_EV_NAME, name) != 0) {
+ error = ENOMEM;
+ }
+
+ if (dtype != -1 &&
+ nvlist_add_uint32(event, DM_EV_DTYPE, dtype) != 0) {
+ error = ENOMEM;
+ }
+
+ if (nvlist_add_string(event, DM_EV_TYPE, etype) != 0) {
+ error = ENOMEM;
+ }
+
+ if (error != 0) {
+ nvlist_free(event);
+ event = NULL;
+ }
+ }
+
+ add_event_to_queue(event);
+}
+
+void
+events_new_slice_event(char *dev, char *type)
+{
+ events_new_event(basename(dev), DM_SLICE, type);
+}
+
+int
+events_start_event_watcher()
+{
+ sysevent_handle_t *shp;
+ const char *subclass_list[1];
+
+ /* Bind event handler and create subscriber handle */
+ shp = sysevent_bind_handle(event_handler);
+ if (shp == NULL) {
+ if (dm_debug) {
+ /* keep going when we're debugging */
+ (void) fprintf(stderr, "ERROR: bind failed %d\n", errno);
+ return (0);
+ }
+ return (errno);
+ }
+
+ subclass_list[0] = ESC_DISK;
+ if (sysevent_subscribe_event(shp, EC_DEV_ADD, subclass_list, 1) != 0) {
+ if (dm_debug) {
+ /* keep going when we're debugging */
+ (void) fprintf(stderr, "ERROR: subscribe failed\n");
+ return (0);
+ }
+ return (errno);
+ }
+ if (sysevent_subscribe_event(shp, EC_DEV_REMOVE, subclass_list, 1)
+ != 0) {
+ if (dm_debug) {
+ /* keep going when we're debugging */
+ (void) fprintf(stderr, "ERROR: subscribe failed\n");
+ return (0);
+ }
+ return (errno);
+ }
+
+ return (0);
+}
+
+static void
+add_event_to_queue(nvlist_t *event)
+{
+ (void) mutex_lock(&queue_lock);
+
+ if (event == NULL) {
+ event_error = ENOMEM;
+ (void) mutex_unlock(&queue_lock);
+ return;
+ }
+
+ if (events == NULL) {
+
+ events = (struct event_list *)malloc(sizeof (struct event_list));
+ if (events == NULL) {
+ event_error = ENOMEM;
+ nvlist_free(event);
+ } else {
+ events->next = NULL;
+ events->event = event;
+ }
+
+ } else {
+ /* already have events in the queue */
+ struct event_list *ep;
+ struct event_list *new_event;
+
+ /* find the last element in the list */
+ for (ep = events; ep->next != NULL; ep = ep->next);
+
+ new_event = (struct event_list *)malloc(sizeof (struct event_list));
+ if (new_event == NULL) {
+ event_error = ENOMEM;
+ nvlist_free(event);
+ } else {
+ new_event->next = NULL;
+ new_event->event = event;
+ ep->next = new_event;
+ }
+ }
+
+ (void) mutex_unlock(&queue_lock);
+
+ (void) sema_post(&semaphore);
+}
+
+static void
+cb_watch_events()
+{
+ nvlist_t *event;
+ int error;
+
+ /*CONSTCOND*/
+ while (1) {
+ event = dm_get_event(&error);
+ if (callback == NULL) {
+ /* end the thread */
+ return;
+ }
+ callback(event, error);
+ }
+}
+
+static void
+event_handler(sysevent_t *ev)
+{
+ char *class_name;
+ char *pub;
+
+ class_name = sysevent_get_class_name(ev);
+ if (dm_debug) {
+ (void) fprintf(stderr, "****EVENT: %s %s ", class_name,
+ sysevent_get_subclass_name(ev));
+ if ((pub = sysevent_get_pub_name(ev)) != NULL) {
+ (void) fprintf(stderr, "%s\n", pub);
+ free(pub);
+ } else {
+ (void) fprintf(stderr, "\n");
+ }
+ }
+
+ if (libdiskmgt_str_eq(class_name, EC_DEV_ADD)) {
+ /* batch up the adds into a single devtree walk */
+ walk_devtree();
+
+ } else if (libdiskmgt_str_eq(class_name, EC_DEV_REMOVE)) {
+ nvlist_t *nvlist = NULL;
+ char *dev_name = NULL;
+
+ (void) sysevent_get_attr_list(ev, &nvlist);
+ if (nvlist != NULL) {
+ (void) nvlist_lookup_string(nvlist, DEV_NAME, &dev_name);
+
+ if (dm_debug) {
+ print_nvlist("**** ", nvlist);
+ }
+ }
+
+ if (dev_name != NULL) {
+ cache_update(DM_EV_DISK_DELETE, dev_name);
+ }
+
+ if (nvlist != NULL) {
+ nvlist_free(nvlist);
+ }
+ }
+}
+
+/*
+ * This is a debugging function only.
+ */
+static void
+print_nvlist(char *prefix, nvlist_t *list)
+{
+ nvpair_t *nvp;
+
+ nvp = nvlist_next_nvpair(list, NULL);
+ while (nvp != NULL) {
+ char *attrname;
+ char *str;
+ uint32_t ui32;
+ uint64_t ui64;
+ char **str_array;
+ uint_t cnt;
+ int i;
+
+ attrname = nvpair_name(nvp);
+ switch (nvpair_type(nvp)) {
+ case DATA_TYPE_STRING:
+ (void) nvpair_value_string(nvp, &str);
+ (void) fprintf(stderr, "%s%s: %s\n", prefix, attrname, str);
+ break;
+
+ case DATA_TYPE_STRING_ARRAY:
+ (void) nvpair_value_string_array(nvp, &str_array, &cnt);
+ (void) fprintf(stderr, "%s%s:\n", prefix, attrname);
+ for (i = 0; i < cnt; i++) {
+ (void) fprintf(stderr, "%s %s\n", prefix, str_array[i]);
+ }
+ break;
+
+ case DATA_TYPE_UINT32:
+ (void) nvpair_value_uint32(nvp, &ui32);
+ (void) fprintf(stderr, "%s%s: %u\n", prefix, attrname, ui32);
+ break;
+
+ case DATA_TYPE_UINT64:
+ (void) nvpair_value_uint64(nvp, &ui64);
+#ifdef _LP64
+ (void) fprintf(stderr, "%s%s: %lu\n", prefix, attrname, ui64);
+#else
+ (void) fprintf(stderr, "%s%s: %llu\n", prefix, attrname, ui64);
+#endif
+ break;
+
+
+ case DATA_TYPE_BOOLEAN:
+ (void) fprintf(stderr, "%s%s: true\n", prefix, attrname);
+ break;
+
+ default:
+ (void) fprintf(stderr, "%s%s: UNSUPPORTED TYPE\n", prefix,
+ attrname);
+ break;
+ }
+
+ nvp = nvlist_next_nvpair(list, nvp);
+ }
+}
+
+/*
+ * Batch up the adds into a single devtree walk. We can get a bunch of
+ * adds when we add a controller since we will get an add event for each
+ * drive.
+ */
+static void
+walk_devtree()
+{
+ thread_t walk_thread;
+
+ (void) mutex_lock(&walker_lock);
+
+ switch (walker_state) {
+ case WALK_NONE:
+ if (thr_create(NULL, NULL, (void *(*)(void *))walker, NULL,
+ THR_DAEMON, &walk_thread) == 0) {
+ walker_state = WALK_WAITING;
+ }
+ break;
+
+ case WALK_WAITING:
+ /* absorb the event and do nothing */
+ break;
+
+ case WALK_RUNNING:
+ events_pending = 1;
+ break;
+ }
+
+ (void) mutex_unlock(&walker_lock);
+}
+
+/*ARGSUSED*/
+static void
+walker(void *arg)
+{
+ int walk_again = 0;
+
+ do {
+ /* start by wating for a few seconds to absorb extra events */
+ (void) sleep(WALK_WAIT_TIME);
+
+ (void) mutex_lock(&walker_lock);
+ walker_state = WALK_RUNNING;
+ (void) mutex_unlock(&walker_lock);
+
+ cache_update(DM_EV_DISK_ADD, NULL);
+
+ (void) mutex_lock(&walker_lock);
+
+ if (events_pending) {
+ events_pending = 0;
+ walker_state = WALK_WAITING;
+ walk_again = 1;
+ } else {
+ walker_state = WALK_NONE;
+ walk_again = 0;
+ }
+
+ (void) mutex_unlock(&walker_lock);
+
+ } while (walk_again);
+}