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(void *); 93 static void event_handler(sysevent_t *ev); 94 static void print_nvlist(char *prefix, nvlist_t *list); 95 static void walk_devtree(void); 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, cb_watch_events, NULL, 188 THR_DAEMON, &watch_thread); 189 } 190 } 191 192 } else { 193 /* first time to initialize */ 194 sendevents = 1; 195 196 *errp = sema_init(&semaphore, 0, USYNC_THREAD, NULL); 197 if (*errp != 0) { 198 return; 199 } 200 201 if (cb != NULL) { 202 thread_t watch_thread; 203 204 callback = cb; 205 206 *errp = thr_create(NULL, 0, cb_watch_events, NULL, THR_DAEMON, 207 &watch_thread); 208 } 209 } 210 } 211 212 void 213 events_new_event(char *name, int dtype, char *etype) 214 { 215 nvlist_t *event = NULL; 216 217 if (!sendevents) { 218 return; 219 } 220 221 if (nvlist_alloc(&event, NVATTRS, 0) != 0) { 222 event = NULL; 223 224 } else { 225 int error = 0; 226 227 if (name != NULL && 228 nvlist_add_string(event, DM_EV_NAME, name) != 0) { 229 error = ENOMEM; 230 } 231 232 if (dtype != -1 && 233 nvlist_add_uint32(event, DM_EV_DTYPE, dtype) != 0) { 234 error = ENOMEM; 235 } 236 237 if (nvlist_add_string(event, DM_EV_TYPE, etype) != 0) { 238 error = ENOMEM; 239 } 240 241 if (error != 0) { 242 nvlist_free(event); 243 event = NULL; 244 } 245 } 246 247 add_event_to_queue(event); 248 } 249 250 void 251 events_new_slice_event(char *dev, char *type) 252 { 253 events_new_event(basename(dev), DM_SLICE, type); 254 } 255 256 int 257 events_start_event_watcher() 258 { 259 const char *subclass_list[1]; 260 int ret = -1; 261 262 mutex_enter(&shp_lock); 263 if (shp != NULL) { 264 ret = 0; 265 goto out; 266 } 267 268 /* Bind event handler and create subscriber handle */ 269 shp = sysevent_bind_handle(event_handler); 270 if (shp == NULL) { 271 if (dm_debug) { 272 (void) fprintf(stderr, "ERROR: sysevent bind failed: " 273 "%d\n", errno); 274 } 275 goto out; 276 } 277 278 subclass_list[0] = ESC_DISK; 279 if (sysevent_subscribe_event(shp, EC_DEV_ADD, subclass_list, 1) != 0 || 280 sysevent_subscribe_event(shp, EC_DEV_REMOVE, subclass_list, 1) != 281 0) { 282 283 sysevent_unsubscribe_event(shp, EC_ALL); 284 sysevent_unbind_handle(shp); 285 shp = NULL; 286 287 if (dm_debug) { 288 (void) fprintf(stderr, "ERROR: sysevent subscribe " 289 "failed: %d\n", errno); 290 } 291 goto out; 292 } 293 ret = 0; 294 out: 295 mutex_exit(&shp_lock); 296 return (ret); 297 } 298 299 static void 300 add_event_to_queue(nvlist_t *event) 301 { 302 (void) mutex_lock(&queue_lock); 303 304 if (event == NULL) { 305 event_error = ENOMEM; 306 (void) mutex_unlock(&queue_lock); 307 return; 308 } 309 310 if (events == NULL) { 311 312 events = (struct event_list *)malloc(sizeof (struct event_list)); 313 if (events == NULL) { 314 event_error = ENOMEM; 315 nvlist_free(event); 316 } else { 317 events->next = NULL; 318 events->event = event; 319 } 320 321 } else { 322 /* already have events in the queue */ 323 struct event_list *ep; 324 struct event_list *new_event; 325 326 /* find the last element in the list */ 327 for (ep = events; ep->next != NULL; ep = ep->next); 328 329 new_event = (struct event_list *)malloc(sizeof (struct event_list)); 330 if (new_event == NULL) { 331 event_error = ENOMEM; 332 nvlist_free(event); 333 } else { 334 new_event->next = NULL; 335 new_event->event = event; 336 ep->next = new_event; 337 } 338 } 339 340 (void) mutex_unlock(&queue_lock); 341 342 (void) sema_post(&semaphore); 343 } 344 345 static void * 346 cb_watch_events(void *arg __unused) 347 { 348 nvlist_t *event; 349 int error; 350 351 /*CONSTCOND*/ 352 while (1) { 353 event = dm_get_event(&error); 354 if (callback == NULL) { 355 /* end the thread */ 356 return (NULL); 357 } 358 callback(event, error); 359 } 360 } 361 362 static void 363 event_handler(sysevent_t *ev) 364 { 365 char *class_name; 366 char *pub; 367 368 class_name = sysevent_get_class_name(ev); 369 if (dm_debug) { 370 (void) fprintf(stderr, "****EVENT: %s %s ", class_name, 371 sysevent_get_subclass_name(ev)); 372 if ((pub = sysevent_get_pub_name(ev)) != NULL) { 373 (void) fprintf(stderr, "%s\n", pub); 374 free(pub); 375 } else { 376 (void) fprintf(stderr, "\n"); 377 } 378 } 379 380 if (libdiskmgt_str_eq(class_name, EC_DEV_ADD)) { 381 /* batch up the adds into a single devtree walk */ 382 walk_devtree(); 383 384 } else if (libdiskmgt_str_eq(class_name, EC_DEV_REMOVE)) { 385 nvlist_t *nvlist = NULL; 386 char *dev_name = NULL; 387 388 (void) sysevent_get_attr_list(ev, &nvlist); 389 if (nvlist != NULL) { 390 (void) nvlist_lookup_string(nvlist, DEV_NAME, &dev_name); 391 392 if (dm_debug) { 393 print_nvlist("**** ", nvlist); 394 } 395 } 396 397 if (dev_name != NULL) { 398 cache_update(DM_EV_DISK_DELETE, dev_name); 399 } 400 401 if (nvlist != NULL) { 402 nvlist_free(nvlist); 403 } 404 } 405 } 406 407 /* 408 * This is a debugging function only. 409 */ 410 static void 411 print_nvlist(char *prefix, nvlist_t *list) 412 { 413 nvpair_t *nvp; 414 415 nvp = nvlist_next_nvpair(list, NULL); 416 while (nvp != NULL) { 417 char *attrname; 418 char *str; 419 uint32_t ui32; 420 uint64_t ui64; 421 char **str_array; 422 uint_t cnt; 423 int i; 424 425 attrname = nvpair_name(nvp); 426 switch (nvpair_type(nvp)) { 427 case DATA_TYPE_STRING: 428 (void) nvpair_value_string(nvp, &str); 429 (void) fprintf(stderr, "%s%s: %s\n", prefix, attrname, str); 430 break; 431 432 case DATA_TYPE_STRING_ARRAY: 433 (void) nvpair_value_string_array(nvp, &str_array, &cnt); 434 (void) fprintf(stderr, "%s%s:\n", prefix, attrname); 435 for (i = 0; i < cnt; i++) { 436 (void) fprintf(stderr, "%s %s\n", prefix, str_array[i]); 437 } 438 break; 439 440 case DATA_TYPE_UINT32: 441 (void) nvpair_value_uint32(nvp, &ui32); 442 (void) fprintf(stderr, "%s%s: %u\n", prefix, attrname, ui32); 443 break; 444 445 case DATA_TYPE_UINT64: 446 (void) nvpair_value_uint64(nvp, &ui64); 447 #ifdef _LP64 448 (void) fprintf(stderr, "%s%s: %lu\n", prefix, attrname, ui64); 449 #else 450 (void) fprintf(stderr, "%s%s: %llu\n", prefix, attrname, ui64); 451 #endif 452 break; 453 454 455 case DATA_TYPE_BOOLEAN: 456 (void) fprintf(stderr, "%s%s: true\n", prefix, attrname); 457 break; 458 459 default: 460 (void) fprintf(stderr, "%s%s: UNSUPPORTED TYPE\n", prefix, 461 attrname); 462 break; 463 } 464 465 nvp = nvlist_next_nvpair(list, nvp); 466 } 467 } 468 469 /* 470 * Batch up the adds into a single devtree walk. We can get a bunch of 471 * adds when we add a controller since we will get an add event for each 472 * drive. 473 */ 474 static void 475 walk_devtree(void) 476 { 477 thread_t walk_thread; 478 479 mutex_enter(&walker_lock); 480 481 switch (walker_state) { 482 case WALK_NONE: 483 if (thr_create(NULL, 0, walker, NULL, 484 THR_DAEMON, &walk_thread) == 0) { 485 walker_state = WALK_WAITING; 486 } 487 break; 488 489 case WALK_WAITING: 490 /* absorb the event and do nothing */ 491 break; 492 493 case WALK_RUNNING: 494 events_pending = 1; 495 break; 496 } 497 498 mutex_exit(&walker_lock); 499 } 500 501 static void * 502 walker(void *arg __unused) 503 { 504 int walk_again = 0; 505 506 do { 507 /* start by waiting for a few seconds to absorb extra events */ 508 (void) sleep(WALK_WAIT_TIME); 509 510 mutex_enter(&walker_lock); 511 if (shutting_down) { 512 walker_state = WALK_NONE; 513 (void) cond_broadcast(&walker_cv); 514 mutex_exit(&walker_lock); 515 return (NULL); 516 } 517 walker_state = WALK_RUNNING; 518 mutex_exit(&walker_lock); 519 520 cache_update(DM_EV_DISK_ADD, NULL); 521 522 mutex_enter(&walker_lock); 523 if (shutting_down) { 524 walker_state = WALK_NONE; 525 (void) cond_broadcast(&walker_cv); 526 mutex_exit(&walker_lock); 527 return (NULL); 528 } 529 530 if (events_pending) { 531 events_pending = 0; 532 walker_state = WALK_WAITING; 533 walk_again = 1; 534 } else { 535 walker_state = WALK_NONE; 536 walk_again = 0; 537 } 538 539 mutex_exit(&walker_lock); 540 541 } while (walk_again); 542 return (NULL); 543 } 544