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