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 * 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 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 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 220 events_new_slice_event(char *dev, char *type) 221 { 222 events_new_event(basename(dev), DM_SLICE, type); 223 } 224 225 int 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 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 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 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 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 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 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