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 *
fmd_dispqelem_create(const char * name)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
fmd_dispqelem_destroy(fmd_dispqelem_t * dep)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 *
fmd_dispqelem_lookup(fmd_dispqelem_t * dep,const char * name)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 *
fmd_dispq_create(void)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
fmd_dispq_destroy(fmd_dispq_t * dqp)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 *
fmd_dispq_insert_one(fmd_dispqelem_t * dep,const char * name)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
fmd_dispq_insert(fmd_dispq_t * dqp,fmd_eventq_t * eqp,const char * pattern)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
fmd_dispq_delete_one(fmd_dispqelem_t * dep,fmd_eventq_t * eqp,int patc,char * patv[])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
fmd_dispq_delete(fmd_dispq_t * dqp,fmd_eventq_t * eqp,const char * pattern)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
fmd_dispq_dispatch_one(fmd_dispqelem_t * dep,ulong_t * gids,fmd_event_t * ep,const char * class)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
fmd_dispq_dispatchv(fmd_dispqelem_t * root,ulong_t * gids,fmd_event_t * ep,const char * class,uint_t cc,char * cv[])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
fmd_dispq_tokenize(const char * class,char * buf,size_t buflen,char ** cv,uint_t cvlen)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
fmd_dispq_dispatch_gid(fmd_dispq_t * dqp,fmd_event_t * ep,const char * class,id_t gid)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
fmd_dispq_dispatch(fmd_dispq_t * dqp,fmd_event_t * ep,const char * class)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
fmd_dispq_getgid(fmd_dispq_t * dqp,void * cookie)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
fmd_dispq_delgid(fmd_dispq_t * dqp,id_t gid)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