Home | History | Annotate | Download | only in common
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License, Version 1.0 only
      6  * (the "License").  You may not use this file except in compliance
      7  * with the License.
      8  *
      9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
     10  * or http://www.opensolaris.org/os/licensing.
     11  * See the License for the specific language governing permissions
     12  * and limitations under the License.
     13  *
     14  * When distributing Covered Code, include this CDDL HEADER in each
     15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     16  * If applicable, add the following below this CDDL HEADER, with the
     17  * fields enclosed by brackets "[]" replaced with your own identifying
     18  * information: Portions Copyright [yyyy] [name of copyright owner]
     19  *
     20  * CDDL HEADER END
     21  */
     22 /*
     23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
     24  * Use is subject to license terms.
     25  */
     26 
     27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     28 
     29 #include <stdio.h>
     30 #include <stdlib.h>
     31 #include <stropts.h>
     32 #include <synch.h>
     33 #include <thread.h>
     34 #include <libsysevent.h>
     35 #include <sys/sysevent/eventdefs.h>
     36 #include <sys/sysevent/dev.h>
     37 #include <errno.h>
     38 #include <libgen.h>
     39 #include <unistd.h>
     40 
     41 #include "libdiskmgt.h"
     42 #include "disks_private.h"
     43 
     44 struct event_list {
     45 	struct event_list	*next;
     46 	nvlist_t		*event;
     47 };
     48 
     49 static struct event_list	*events = NULL;
     50 static int			event_error = 0;
     51 static int			event_break = 0;
     52 static mutex_t			queue_lock;
     53 static sema_t			semaphore;
     54 
     55 /*
     56  * When we add a controller we get an add event for each drive on the
     57  * controller.  We don't want to walk the devtree for each drive since
     58  * we will get the same information each time.  So, the solution is to
     59  * wait for a few seconds for all of the add events to come in and then
     60  * do a single walk.  If an add event comes in after we start the walk, we
     61  * need to do another walk since we might have missed that drive.
     62  *
     63  * State: 0 - no walker; 1 - walker waiting; 2 - walker running
     64  *	0 -> 1; wait a few seconds
     65  *	1 -> 2; walking the devtree
     66  *	2 -> either 0 or 1 (see below)
     67  * While running (state 2), if event comes in, go back to waiting (state 1)
     68  * after the walk otherwise go back to none (state 0).
     69  *
     70  * walker_lock protects walker_state & events_pending
     71  */
     72 #define	WALK_NONE		0
     73 #define	WALK_WAITING		1
     74 #define	WALK_RUNNING		2
     75 #define	WALK_WAIT_TIME		60	/* wait 60 seconds */
     76 
     77 static mutex_t			walker_lock;
     78 static int			walker_state = WALK_NONE;
     79 static int			events_pending = 0;
     80 
     81 static int			sendevents = 0;
     82 
     83 static void		add_event_to_queue(nvlist_t *event);
     84 static void		cb_watch_events();
     85 static void		event_handler(sysevent_t *ev);
     86 static void		print_nvlist(char *prefix, nvlist_t *list);
     87 static void		walk_devtree();
     88 static void		walker(void *arg);
     89 
     90 static void(*callback)(nvlist_t *, int) = NULL;
     91 
     92 nvlist_t *
     93 dm_get_event(int *errp)
     94 {
     95 	nvlist_t *event = NULL;
     96 
     97 	*errp = 0;
     98 
     99 	/* wait until there is an event in the queue */
    100 	/*CONSTCOND*/
    101 	while (1) {
    102 	    (void) sema_wait(&semaphore);
    103 
    104 	    if (event_break) {
    105 		event_break = 0;
    106 		*errp = EINTR;
    107 		break;
    108 	    }
    109 
    110 	    (void) mutex_lock(&queue_lock);
    111 
    112 	    /* first see if we ran out of memory since the last call */
    113 	    if (event_error != 0) {
    114 		*errp = event_error;
    115 		event_error = 0;
    116 
    117 	    } else if (events != NULL) {
    118 		struct event_list *tmpp;
    119 
    120 		event = events->event;
    121 		tmpp = events->next;
    122 		free(events);
    123 		events = tmpp;
    124 	    }
    125 
    126 	    (void) mutex_unlock(&queue_lock);
    127 
    128 	    if (*errp != 0 || event != NULL) {
    129 		break;
    130 	    }
    131 	}
    132 
    133 	return (event);
    134 }
    135 
    136 void
    137 dm_init_event_queue(void (*cb)(nvlist_t *, int), int *errp)
    138 {
    139 	if (sendevents == 1) {
    140 	    /* we were already initialized, see what changes to make */
    141 	    *errp = 0;
    142 	    if (cb != callback) {
    143 
    144 		callback = cb;
    145 		if (cb == NULL) {
    146 		    /* clearing the cb so shutdown the internal cb thread */
    147 		    event_break = 1;
    148 		    (void) sema_post(&semaphore);
    149 
    150 		} else {
    151 		    /* installing a cb; we didn't have one before */
    152 		    thread_t watch_thread;
    153 
    154 		    *errp = thr_create(NULL, NULL,
    155 			(void *(*)(void *))cb_watch_events, NULL, THR_DAEMON,
    156 			&watch_thread);
    157 		}
    158 	    }
    159 
    160 	} else {
    161 	    /* first time to initialize */
    162 	    sendevents = 1;
    163 
    164 	    *errp = sema_init(&semaphore, 0, USYNC_THREAD, NULL);
    165 	    if (*errp != 0) {
    166 		return;
    167 	    }
    168 
    169 	    if (cb != NULL) {
    170 		thread_t watch_thread;
    171 
    172 		callback = cb;
    173 
    174 		*errp = thr_create(NULL, NULL,
    175 		    (void *(*)(void *))cb_watch_events, NULL, THR_DAEMON,
    176 		    &watch_thread);
    177 	    }
    178 	}
    179 }
    180 
    181 void
    182 events_new_event(char *name, int dtype, char *etype)
    183 {
    184 	nvlist_t	*event = NULL;
    185 
    186 	if (!sendevents) {
    187 	    return;
    188 	}
    189 
    190 	if (nvlist_alloc(&event, NVATTRS, 0) != 0) {
    191 	    event = NULL;
    192 
    193 	} else {
    194 	    int	error = 0;
    195 
    196 	    if (name != NULL &&
    197 		nvlist_add_string(event, DM_EV_NAME, name) != 0) {
    198 		error = ENOMEM;
    199 	    }
    200 
    201 	    if (dtype != -1 &&
    202 		nvlist_add_uint32(event, DM_EV_DTYPE, dtype) != 0) {
    203 		error = ENOMEM;
    204 	    }
    205 
    206 	    if (nvlist_add_string(event, DM_EV_TYPE, etype) != 0) {
    207 		error = ENOMEM;
    208 	    }
    209 
    210 	    if (error != 0) {
    211 		nvlist_free(event);
    212 		event = NULL;
    213 	    }
    214 	}
    215 
    216 	add_event_to_queue(event);
    217 }
    218 
    219 void
    220 events_new_slice_event(char *dev, char *type)
    221 {
    222 	events_new_event(basename(dev), DM_SLICE, type);
    223 }
    224 
    225 int
    226 events_start_event_watcher()
    227 {
    228 	sysevent_handle_t *shp;
    229 	const char *subclass_list[1];
    230 
    231 	/* Bind event handler and create subscriber handle */
    232 	shp = sysevent_bind_handle(event_handler);
    233 	if (shp == NULL) {
    234 	    if (dm_debug) {
    235 		/* keep going when we're debugging */
    236 		(void) fprintf(stderr, "ERROR: bind failed %d\n", errno);
    237 		return (0);
    238 	    }
    239 	    return (errno);
    240 	}
    241 
    242 	subclass_list[0] = ESC_DISK;
    243 	if (sysevent_subscribe_event(shp, EC_DEV_ADD, subclass_list, 1) != 0) {
    244 	    if (dm_debug) {
    245 		/* keep going when we're debugging */
    246 		(void) fprintf(stderr, "ERROR: subscribe failed\n");
    247 		return (0);
    248 	    }
    249 	    return (errno);
    250 	}
    251 	if (sysevent_subscribe_event(shp, EC_DEV_REMOVE, subclass_list, 1)
    252 	    != 0) {
    253 	    if (dm_debug) {
    254 		/* keep going when we're debugging */
    255 		(void) fprintf(stderr, "ERROR: subscribe failed\n");
    256 		return (0);
    257 	    }
    258 	    return (errno);
    259 	}
    260 
    261 	return (0);
    262 }
    263 
    264 static void
    265 add_event_to_queue(nvlist_t *event)
    266 {
    267 	(void) mutex_lock(&queue_lock);
    268 
    269 	if (event == NULL) {
    270 	    event_error = ENOMEM;
    271 	    (void) mutex_unlock(&queue_lock);
    272 	    return;
    273 	}
    274 
    275 	if (events == NULL) {
    276 
    277 	    events = (struct event_list *)malloc(sizeof (struct event_list));
    278 	    if (events == NULL) {
    279 		event_error = ENOMEM;
    280 		nvlist_free(event);
    281 	    } else {
    282 		events->next = NULL;
    283 		events->event = event;
    284 	    }
    285 
    286 	} else {
    287 	    /* already have events in the queue */
    288 	    struct event_list *ep;
    289 	    struct event_list *new_event;
    290 
    291 	    /* find the last element in the list */
    292 	    for (ep = events; ep->next != NULL; ep = ep->next);
    293 
    294 	    new_event = (struct event_list *)malloc(sizeof (struct event_list));
    295 	    if (new_event == NULL) {
    296 		event_error = ENOMEM;
    297 		nvlist_free(event);
    298 	    } else {
    299 		new_event->next = NULL;
    300 		new_event->event = event;
    301 		ep->next = new_event;
    302 	    }
    303 	}
    304 
    305 	(void) mutex_unlock(&queue_lock);
    306 
    307 	(void) sema_post(&semaphore);
    308 }
    309 
    310 static void
    311 cb_watch_events()
    312 {
    313 	nvlist_t	*event;
    314 	int		error;
    315 
    316 	/*CONSTCOND*/
    317 	while (1) {
    318 	    event = dm_get_event(&error);
    319 	    if (callback == NULL) {
    320 		/* end the thread */
    321 		return;
    322 	    }
    323 	    callback(event, error);
    324 	}
    325 }
    326 
    327 static void
    328 event_handler(sysevent_t *ev)
    329 {
    330 	char		*class_name;
    331 	char		*pub;
    332 
    333 	class_name = sysevent_get_class_name(ev);
    334 	if (dm_debug) {
    335 	    (void) fprintf(stderr, "****EVENT: %s %s ", class_name,
    336 		sysevent_get_subclass_name(ev));
    337 	    if ((pub = sysevent_get_pub_name(ev)) != NULL) {
    338 		(void) fprintf(stderr, "%s\n", pub);
    339 		free(pub);
    340 	    } else {
    341 		(void) fprintf(stderr, "\n");
    342 	    }
    343 	}
    344 
    345 	if (libdiskmgt_str_eq(class_name, EC_DEV_ADD)) {
    346 	    /* batch up the adds into a single devtree walk */
    347 	    walk_devtree();
    348 
    349 	} else if (libdiskmgt_str_eq(class_name, EC_DEV_REMOVE)) {
    350 	    nvlist_t	*nvlist = NULL;
    351 	    char	*dev_name = NULL;
    352 
    353 	    (void) sysevent_get_attr_list(ev, &nvlist);
    354 	    if (nvlist != NULL) {
    355 		(void) nvlist_lookup_string(nvlist, DEV_NAME, &dev_name);
    356 
    357 		if (dm_debug) {
    358 		    print_nvlist("**** ", nvlist);
    359 		}
    360 	    }
    361 
    362 	    if (dev_name != NULL) {
    363 		cache_update(DM_EV_DISK_DELETE, dev_name);
    364 	    }
    365 
    366 	    if (nvlist != NULL) {
    367 		nvlist_free(nvlist);
    368 	    }
    369 	}
    370 }
    371 
    372 /*
    373  * This is a debugging function only.
    374  */
    375 static void
    376 print_nvlist(char *prefix, nvlist_t *list)
    377 {
    378 	nvpair_t	*nvp;
    379 
    380 	nvp = nvlist_next_nvpair(list, NULL);
    381 	while (nvp != NULL) {
    382 	    char	*attrname;
    383 	    char	*str;
    384 	    uint32_t	ui32;
    385 	    uint64_t	ui64;
    386 	    char	**str_array;
    387 	    uint_t	cnt;
    388 	    int		i;
    389 
    390 	    attrname = nvpair_name(nvp);
    391 	    switch (nvpair_type(nvp)) {
    392 	    case DATA_TYPE_STRING:
    393 		(void) nvpair_value_string(nvp, &str);
    394 		(void) fprintf(stderr, "%s%s: %s\n", prefix, attrname, str);
    395 		break;
    396 
    397 	    case DATA_TYPE_STRING_ARRAY:
    398 		(void) nvpair_value_string_array(nvp, &str_array, &cnt);
    399 		(void) fprintf(stderr, "%s%s:\n", prefix, attrname);
    400 		for (i = 0; i < cnt; i++) {
    401 		    (void) fprintf(stderr, "%s    %s\n", prefix, str_array[i]);
    402 		}
    403 		break;
    404 
    405 	    case DATA_TYPE_UINT32:
    406 		(void) nvpair_value_uint32(nvp, &ui32);
    407 		(void) fprintf(stderr, "%s%s: %u\n", prefix, attrname, ui32);
    408 		break;
    409 
    410 	    case DATA_TYPE_UINT64:
    411 		(void) nvpair_value_uint64(nvp, &ui64);
    412 #ifdef _LP64
    413 		(void) fprintf(stderr, "%s%s: %lu\n", prefix, attrname, ui64);
    414 #else
    415 		(void) fprintf(stderr, "%s%s: %llu\n", prefix, attrname, ui64);
    416 #endif
    417 		break;
    418 
    419 
    420 	    case DATA_TYPE_BOOLEAN:
    421 		(void) fprintf(stderr, "%s%s: true\n", prefix, attrname);
    422 		break;
    423 
    424 	    default:
    425 		(void) fprintf(stderr, "%s%s: UNSUPPORTED TYPE\n", prefix,
    426 		    attrname);
    427 		break;
    428 	    }
    429 
    430 	    nvp = nvlist_next_nvpair(list, nvp);
    431 	}
    432 }
    433 
    434 /*
    435  * Batch up the adds into a single devtree walk.  We can get a bunch of
    436  * adds when we add a controller since we will get an add event for each
    437  * drive.
    438  */
    439 static void
    440 walk_devtree()
    441 {
    442 	thread_t	walk_thread;
    443 
    444 	(void) mutex_lock(&walker_lock);
    445 
    446 	switch (walker_state) {
    447 	case WALK_NONE:
    448 	    if (thr_create(NULL, NULL, (void *(*)(void *))walker, NULL,
    449 		THR_DAEMON, &walk_thread) == 0) {
    450 		walker_state = WALK_WAITING;
    451 	    }
    452 	    break;
    453 
    454 	case WALK_WAITING:
    455 	    /* absorb the event and do nothing */
    456 	    break;
    457 
    458 	case WALK_RUNNING:
    459 	    events_pending = 1;
    460 	    break;
    461 	}
    462 
    463 	(void) mutex_unlock(&walker_lock);
    464 }
    465 
    466 /*ARGSUSED*/
    467 static void
    468 walker(void *arg)
    469 {
    470 	int	walk_again = 0;
    471 
    472 	do {
    473 	    /* start by wating for a few seconds to absorb extra events */
    474 	    (void) sleep(WALK_WAIT_TIME);
    475 
    476 	    (void) mutex_lock(&walker_lock);
    477 	    walker_state = WALK_RUNNING;
    478 	    (void) mutex_unlock(&walker_lock);
    479 
    480 	    cache_update(DM_EV_DISK_ADD, NULL);
    481 
    482 	    (void) mutex_lock(&walker_lock);
    483 
    484 	    if (events_pending) {
    485 		events_pending = 0;
    486 		walker_state = WALK_WAITING;
    487 		walk_again = 1;
    488 	    } else {
    489 		walker_state = WALK_NONE;
    490 		walk_again = 0;
    491 	    }
    492 
    493 	    (void) mutex_unlock(&walker_lock);
    494 
    495 	} while (walk_again);
    496 }
    497