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 *
dm_get_event(int * errp)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
dm_init_event_queue(void (* cb)(nvlist_t *,int),int * errp)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
events_new_event(char * name,int dtype,char * etype)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
events_new_slice_event(char * dev,char * type)220 events_new_slice_event(char *dev, char *type)
221 {
222 events_new_event(basename(dev), DM_SLICE, type);
223 }
224
225 int
events_start_event_watcher()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
add_event_to_queue(nvlist_t * event)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
cb_watch_events()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
event_handler(sysevent_t * ev)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
print_nvlist(char * prefix,nvlist_t * list)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
walk_devtree()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
walker(void * arg)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