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