xref: /illumos-gate/usr/src/cmd/fm/fmd/common/fmd_dispq.c (revision f808c858)
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