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