xref: /illumos-gate/usr/src/lib/libdiskmgt/common/events.c (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
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