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 /* 24 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 25 * Use is subject to license terms. 26 */ 27 28 #pragma ident "%Z%%M% %I% %E% SMI" 29 30 #include <sys/fm/protocol.h> 31 #include <sys/bitmap.h> 32 33 #include <strings.h> 34 #include <limits.h> 35 #include <alloca.h> 36 37 #include <fmd_alloc.h> 38 #include <fmd_string.h> 39 #include <fmd_module.h> 40 #include <fmd_dispq.h> 41 #include <fmd_subr.h> 42 43 #include <fmd.h> 44 45 static fmd_dispqelem_t * 46 fmd_dispqelem_create(const char *name) 47 { 48 fmd_dispqelem_t *dep = fmd_alloc(sizeof (fmd_dispqelem_t), FMD_SLEEP); 49 50 dep->dq_name = fmd_strdup(name, FMD_SLEEP); 51 dep->dq_link = NULL; 52 dep->dq_hashlen = fmd.d_str_buckets; 53 dep->dq_hash = fmd_zalloc(sizeof (void *) * dep->dq_hashlen, FMD_SLEEP); 54 dep->dq_list = NULL; 55 dep->dq_refs = 0; 56 57 return (dep); 58 } 59 60 static void 61 fmd_dispqelem_destroy(fmd_dispqelem_t *dep) 62 { 63 fmd_dispqlist_t *dlp, *nlp; 64 fmd_dispqelem_t *p, *q; 65 uint_t i; 66 67 for (dlp = dep->dq_list; dlp != NULL; dlp = nlp) { 68 nlp = dlp->dq_next; 69 fmd_free(dlp, sizeof (fmd_dispqlist_t)); 70 } 71 72 for (i = 0; i < dep->dq_hashlen; i++) { 73 for (p = dep->dq_hash[i]; p != NULL; p = q) { 74 q = p->dq_link; 75 fmd_dispqelem_destroy(p); 76 } 77 } 78 79 fmd_free(dep->dq_hash, sizeof (void *) * dep->dq_hashlen); 80 fmd_strfree(dep->dq_name); 81 82 fmd_free(dep, sizeof (fmd_dispqelem_t)); 83 } 84 85 static fmd_dispqelem_t * 86 fmd_dispqelem_lookup(fmd_dispqelem_t *dep, const char *name) 87 { 88 uint_t h = fmd_strhash(name) % dep->dq_hashlen; 89 90 for (dep = dep->dq_hash[h]; dep != NULL; dep = dep->dq_link) { 91 if (strcmp(dep->dq_name, name) == 0) 92 break; 93 } 94 95 return (dep); 96 } 97 98 fmd_dispq_t * 99 fmd_dispq_create(void) 100 { 101 fmd_dispq_t *dqp = fmd_alloc(sizeof (fmd_dispq_t), FMD_SLEEP); 102 103 (void) pthread_rwlock_init(&dqp->dq_lock, NULL); 104 dqp->dq_root = fmd_dispqelem_create(NULL); 105 dqp->dq_gids = fmd_idspace_create("dispq_gids", 1, INT_MAX); 106 dqp->dq_gmax = 0; 107 108 return (dqp); 109 } 110 111 void 112 fmd_dispq_destroy(fmd_dispq_t *dqp) 113 { 114 fmd_dispqelem_destroy(dqp->dq_root); 115 fmd_idspace_destroy(dqp->dq_gids); 116 fmd_free(dqp, sizeof (fmd_dispq_t)); 117 } 118 119 static fmd_dispqelem_t * 120 fmd_dispq_insert_one(fmd_dispqelem_t *dep, const char *name) 121 { 122 uint_t h = fmd_strhash(name) % dep->dq_hashlen; 123 fmd_dispqelem_t *ep; 124 125 for (ep = dep->dq_hash[h]; ep != NULL; ep = ep->dq_link) { 126 if (strcmp(ep->dq_name, name) == 0) 127 break; 128 } 129 130 if (ep == NULL) { 131 ep = fmd_dispqelem_create(name); 132 133 ep->dq_link = dep->dq_hash[h]; 134 dep->dq_hash[h] = ep; 135 136 dep->dq_refs++; 137 ASSERT(dep->dq_refs != 0); 138 } 139 140 return (ep); 141 } 142 143 void 144 fmd_dispq_insert(fmd_dispq_t *dqp, fmd_eventq_t *eqp, const char *pattern) 145 { 146 char *p, *q, *s = fmd_strdup(pattern, FMD_SLEEP); 147 size_t len = strlen(s); 148 149 fmd_dispqlist_t *dlp = fmd_alloc(sizeof (fmd_dispqlist_t), FMD_SLEEP); 150 fmd_dispqelem_t *dep; 151 152 (void) pthread_rwlock_wrlock(&dqp->dq_lock); 153 dep = dqp->dq_root; 154 155 for (p = strtok_r(s, ".", &q); p != NULL; p = strtok_r(NULL, ".", &q)) 156 dep = fmd_dispq_insert_one(dep, p); 157 158 dlp->dq_next = dep->dq_list; 159 dlp->dq_eventq = eqp; 160 161 dep->dq_list = dlp; 162 dep->dq_refs++; 163 ASSERT(dep->dq_refs != 0); 164 165 (void) pthread_rwlock_unlock(&dqp->dq_lock); 166 fmd_free(s, len + 1); 167 } 168 169 static void 170 fmd_dispq_delete_one(fmd_dispqelem_t *dep, 171 fmd_eventq_t *eqp, int patc, char *patv[]) 172 { 173 fmd_dispqlist_t *lp, **lpp; 174 fmd_dispqelem_t *ep, **epp; 175 176 uint_t h = fmd_strhash(patv[0]) % dep->dq_hashlen; 177 epp = &dep->dq_hash[h]; 178 179 for (ep = *epp; ep != NULL; ep = ep->dq_link) { 180 if (strcmp(ep->dq_name, patv[0]) != 0) 181 epp = &ep->dq_link; 182 else 183 break; 184 } 185 186 ASSERT(ep != NULL); 187 lpp = &ep->dq_list; 188 189 if (patc > 1) { 190 fmd_dispq_delete_one(ep, eqp, patc - 1, patv + 1); 191 } else { 192 for (lp = *lpp; lp != NULL; lp = lp->dq_next) { 193 if (lp->dq_eventq != eqp) 194 lpp = &lp->dq_next; 195 else 196 break; 197 } 198 199 if (lp != NULL) { 200 *lpp = lp->dq_next; 201 fmd_free(lp, sizeof (fmd_dispqlist_t)); 202 ASSERT(ep->dq_refs != 0); 203 ep->dq_refs--; 204 } 205 } 206 207 if (ep->dq_refs == 0) { 208 *epp = ep->dq_link; 209 fmd_dispqelem_destroy(ep); 210 ASSERT(dep->dq_refs != 0); 211 dep->dq_refs--; 212 } 213 } 214 215 void 216 fmd_dispq_delete(fmd_dispq_t *dqp, fmd_eventq_t *eqp, const char *pattern) 217 { 218 char *p, *q, *s = fmd_strdup(pattern, FMD_SLEEP); 219 size_t len = strlen(s); 220 221 char **patv = fmd_zalloc(sizeof (char *) * (len / 2 + 1), FMD_SLEEP); 222 int patc = 0; 223 224 for (p = strtok_r(s, ".", &q); p != NULL; p = strtok_r(NULL, ".", &q)) 225 patv[patc++] = p; 226 227 if (patc != 0) { 228 (void) pthread_rwlock_wrlock(&dqp->dq_lock); 229 fmd_dispq_delete_one(dqp->dq_root, eqp, patc, patv); 230 (void) pthread_rwlock_unlock(&dqp->dq_lock); 231 } 232 233 fmd_free(patv, sizeof (char *) * (len / 2 + 1)); 234 fmd_free(s, len + 1); 235 } 236 237 static uint_t 238 fmd_dispq_dispatch_one(fmd_dispqelem_t *dep, ulong_t *gids, 239 fmd_event_t *ep, const char *class) 240 { 241 fmd_dispqlist_t *dlp; 242 uint_t n = 0; 243 244 for (dlp = dep->dq_list; dlp != NULL; dlp = dlp->dq_next, n++) { 245 id_t gid = dlp->dq_eventq->eq_sgid; 246 247 if (BT_TEST(gids, gid) != 0) 248 continue; /* event already queued for this group ID */ 249 250 TRACE((FMD_DBG_DISP, "queue %p (%s) for %s (%d)", (void *)ep, 251 class, dlp->dq_eventq->eq_mod->mod_name, (int)gid)); 252 253 fmd_eventq_insert_at_time(dlp->dq_eventq, ep); 254 BT_SET(gids, gid); 255 } 256 257 return (n); 258 } 259 260 /* 261 * This function handles the descent of the dispatch queue hash tree on behalf 262 * of fmd_dispq_dispatch(). We recursively descend the tree along two paths: 263 * one using the next component of the split class string (stored in cv[0]) and 264 * one using the wildcard "*" in place of cv[0]. If we can't find either one, 265 * our descent stops. If we descend far enough to consume cv[] (i.e. cc == 0), 266 * then we have a match and we dispatch the event to all modules at that level. 267 * We also dispatch the event to modules found at any interior "*" element, 268 * allowing a subscription to "a.*" to match "a.b", "a.b.c", and so on. 269 */ 270 static uint_t 271 fmd_dispq_dispatchv(fmd_dispqelem_t *root, ulong_t *gids, 272 fmd_event_t *ep, const char *class, uint_t cc, char *cv[]) 273 { 274 fmd_dispqelem_t *dep; 275 uint_t n = 0; 276 277 if (cc == 0) 278 return (fmd_dispq_dispatch_one(root, gids, ep, class)); 279 280 if ((dep = fmd_dispqelem_lookup(root, cv[0])) != NULL) 281 n += fmd_dispq_dispatchv(dep, gids, ep, class, cc - 1, cv + 1); 282 283 if ((dep = fmd_dispqelem_lookup(root, "*")) != NULL) 284 n += fmd_dispq_dispatchv(dep, gids, ep, class, cc - 1, cv + 1); 285 286 if (dep != NULL && cc > 1) 287 n += fmd_dispq_dispatch_one(dep, gids, ep, class); 288 289 return (n); 290 } 291 292 static uint_t 293 fmd_dispq_tokenize(const char *class, 294 char *buf, size_t buflen, char **cv, uint_t cvlen) 295 { 296 uint_t cc = 0; 297 char *p, *q; 298 299 (void) strlcpy(buf, class, buflen); 300 301 for (p = strtok_r(buf, ".", &q); p != NULL; p = strtok_r(NULL, ".", &q)) 302 cv[cc++] = p; 303 304 if (cc > cvlen) 305 fmd_panic("fmd_dispq_tokenize() cc=%u > cv[%u]\n", cc, cvlen); 306 307 return (cc); 308 } 309 310 void 311 fmd_dispq_dispatch_gid(fmd_dispq_t *dqp, 312 fmd_event_t *ep, const char *class, id_t gid) 313 { 314 size_t cvbuflen = strlen(class) + 1; 315 uint_t cc, cvlen, n = 0; 316 char *c, *cvbuf, **cv; 317 318 ulong_t *gids; 319 uint_t glen, i; 320 321 nvlist_t **nva; 322 uint_t nvi, nvc = 0; 323 324 fmd_event_hold(ep); 325 326 /* 327 * If the event is a protocol list.suspect event with one or more 328 * events contained inside of it, determine the maximum length of all 329 * class strings that will be used in this dispatch operation. 330 */ 331 if (FMD_EVENT_TYPE(ep) == FMD_EVT_PROTOCOL && strcmp(class, 332 FM_LIST_SUSPECT_CLASS) == 0 && nvlist_lookup_nvlist_array( 333 FMD_EVENT_NVL(ep), FM_SUSPECT_FAULT_LIST, &nva, &nvc) == 0) { 334 for (nvi = 0; nvi < nvc; nvi++) { 335 if (nvlist_lookup_string(nva[nvi], FM_CLASS, &c) == 0) { 336 size_t len = strlen(c) + 1; 337 cvbuflen = MAX(cvbuflen, len); 338 } 339 } 340 } 341 342 cvbuf = alloca(cvbuflen); 343 cvlen = cvbuflen / 2 + 1; 344 cv = alloca(sizeof (char *) * cvlen); 345 346 /* 347 * With dq_lock held as reader, allocate a bitmap on the stack for 348 * group IDs for this dispatch, zero it, and then do the dispatch. 349 */ 350 (void) pthread_rwlock_rdlock(&dqp->dq_lock); 351 352 glen = BT_BITOUL(dqp->dq_gmax); 353 gids = alloca(sizeof (ulong_t) * glen); 354 bzero(gids, sizeof (ulong_t) * glen); 355 356 /* 357 * If we are dispatching to only a single gid, set all bits in the 358 * group IDs mask and then clear only the bit for the specified gid. 359 */ 360 if (gid >= 0) { 361 for (i = 0; i < glen; i++) 362 gids[i] = BT_ULMAXMASK; 363 BT_CLEAR(gids, gid); 364 } 365 366 for (nvi = 0; nvi < nvc; nvi++) { 367 if (nvlist_lookup_string(nva[nvi], FM_CLASS, &c) == 0) { 368 cc = fmd_dispq_tokenize(c, cvbuf, cvbuflen, cv, cvlen); 369 n += fmd_dispq_dispatchv(dqp->dq_root, 370 gids, ep, c, cc, cv); 371 } 372 } 373 374 cc = fmd_dispq_tokenize(class, cvbuf, cvbuflen, cv, cvlen); 375 n += fmd_dispq_dispatchv(dqp->dq_root, gids, ep, class, cc, cv); 376 377 (void) pthread_rwlock_unlock(&dqp->dq_lock); 378 fmd_dprintf(FMD_DBG_DISP, "%s dispatched to %u queues\n", class, n); 379 380 /* 381 * If the total subscriptions matched (n) was zero and we're not being 382 * called for a single gid, send the event to the self-diagnosis module. 383 */ 384 if (n == 0 && gid < 0 && fmd.d_self != NULL) 385 fmd_eventq_insert_at_time(fmd.d_self->mod_queue, ep); 386 387 fmd_event_rele(ep); 388 } 389 390 void 391 fmd_dispq_dispatch(fmd_dispq_t *dqp, fmd_event_t *ep, const char *class) 392 { 393 fmd_dispq_dispatch_gid(dqp, ep, class, -1); 394 } 395 396 id_t 397 fmd_dispq_getgid(fmd_dispq_t *dqp, void *cookie) 398 { 399 id_t gid; 400 401 (void) pthread_rwlock_wrlock(&dqp->dq_lock); 402 403 gid = fmd_idspace_alloc_min(dqp->dq_gids, cookie); 404 dqp->dq_gmax = MAX(dqp->dq_gmax, gid); 405 406 (void) pthread_rwlock_unlock(&dqp->dq_lock); 407 408 return (gid); 409 } 410 411 void 412 fmd_dispq_delgid(fmd_dispq_t *dqp, id_t gid) 413 { 414 (void) pthread_rwlock_wrlock(&dqp->dq_lock); 415 416 ASSERT(fmd_idspace_contains(dqp->dq_gids, gid)); 417 (void) fmd_idspace_free(dqp->dq_gids, gid); 418 419 (void) pthread_rwlock_unlock(&dqp->dq_lock); 420 } 421