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