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