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