xref: /illumos-gate/usr/src/cmd/fm/fmd/common/fmd_dispq.c (revision 58bc78c7a7ad65c04aaa3ef379a396df23988691)
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 2008 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) + 1;
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 &&
331 	    (strcmp(class, FM_LIST_SUSPECT_CLASS) == 0 ||
332 	    strcmp(class, FM_LIST_REPAIRED_CLASS) == 0 ||
333 	    strcmp(class, FM_LIST_UPDATED_CLASS) == 0) &&
334 	    nvlist_lookup_nvlist_array(FMD_EVENT_NVL(ep), FM_SUSPECT_FAULT_LIST,
335 	    &nva, &nvc) == 0) {
336 		for (nvi = 0; nvi < nvc; nvi++) {
337 			if (nvlist_lookup_string(nva[nvi], FM_CLASS, &c) == 0) {
338 				size_t len = strlen(c) + 1;
339 				cvbuflen = MAX(cvbuflen, len);
340 			}
341 		}
342 	}
343 
344 	cvbuf = alloca(cvbuflen);
345 	cvlen = cvbuflen / 2 + 1;
346 	cv = alloca(sizeof (char *) * cvlen);
347 
348 	/*
349 	 * With dq_lock held as reader, allocate a bitmap on the stack for
350 	 * group IDs for this dispatch, zero it, and then do the dispatch.
351 	 */
352 	(void) pthread_rwlock_rdlock(&dqp->dq_lock);
353 
354 	glen = BT_BITOUL(dqp->dq_gmax);
355 	gids = alloca(sizeof (ulong_t) * glen);
356 	bzero(gids, sizeof (ulong_t) * glen);
357 
358 	/*
359 	 * If we are dispatching to only a single gid, set all bits in the
360 	 * group IDs mask and then clear only the bit for the specified gid.
361 	 */
362 	if (gid >= 0) {
363 		for (i = 0; i < glen; i++)
364 			gids[i] = BT_ULMAXMASK;
365 		BT_CLEAR(gids, gid);
366 	}
367 
368 	for (nvi = 0; nvi < nvc; nvi++) {
369 		if (nvlist_lookup_string(nva[nvi], FM_CLASS, &c) == 0) {
370 			cc = fmd_dispq_tokenize(c, cvbuf, cvbuflen, cv, cvlen);
371 			n += fmd_dispq_dispatchv(dqp->dq_root,
372 			    gids, ep, c, cc, cv);
373 		}
374 	}
375 
376 	cc = fmd_dispq_tokenize(class, cvbuf, cvbuflen, cv, cvlen);
377 	n += fmd_dispq_dispatchv(dqp->dq_root, gids, ep, class, cc, cv);
378 
379 	(void) pthread_rwlock_unlock(&dqp->dq_lock);
380 	fmd_dprintf(FMD_DBG_DISP, "%s dispatched to %u queues\n", class, n);
381 
382 	/*
383 	 * If the total subscriptions matched (n) was zero and we're not being
384 	 * called for a single gid, send the event to the self-diagnosis module.
385 	 */
386 	if (n == 0 && gid < 0 && fmd.d_self != NULL)
387 		fmd_eventq_insert_at_time(fmd.d_self->mod_queue, ep);
388 
389 	fmd_event_rele(ep);
390 }
391 
392 void
393 fmd_dispq_dispatch(fmd_dispq_t *dqp, fmd_event_t *ep, const char *class)
394 {
395 	fmd_dispq_dispatch_gid(dqp, ep, class, -1);
396 }
397 
398 id_t
399 fmd_dispq_getgid(fmd_dispq_t *dqp, void *cookie)
400 {
401 	id_t gid;
402 
403 	(void) pthread_rwlock_wrlock(&dqp->dq_lock);
404 
405 	gid = fmd_idspace_alloc_min(dqp->dq_gids, cookie);
406 	dqp->dq_gmax = MAX(dqp->dq_gmax, gid);
407 
408 	(void) pthread_rwlock_unlock(&dqp->dq_lock);
409 
410 	return (gid);
411 }
412 
413 void
414 fmd_dispq_delgid(fmd_dispq_t *dqp, id_t gid)
415 {
416 	(void) pthread_rwlock_wrlock(&dqp->dq_lock);
417 
418 	ASSERT(fmd_idspace_contains(dqp->dq_gids, gid));
419 	(void) fmd_idspace_free(dqp->dq_gids, gid);
420 
421 	(void) pthread_rwlock_unlock(&dqp->dq_lock);
422 }
423