diff options
Diffstat (limited to 'usr/src/lib/libdiskmgt/common/events.c')
| -rw-r--r-- | usr/src/lib/libdiskmgt/common/events.c | 496 |
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); +} |
