xref: /titanic_52/usr/src/cmd/fm/fmd/common/fmd_scheme.c (revision c28749e97052f09388969427adf7df641cdcdc22)
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 2004 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 <limits.h>
30 #include <stddef.h>
31 #include <unistd.h>
32 #include <dlfcn.h>
33 
34 #include <fmd_alloc.h>
35 #include <fmd_error.h>
36 #include <fmd_subr.h>
37 #include <fmd_string.h>
38 #include <fmd_scheme.h>
39 #include <fmd_fmri.h>
40 #include <fmd_module.h>
41 
42 #include <fmd.h>
43 
44 /*
45  * The fmd resource scheme, used for fmd modules, must be implemented here for
46  * the benefit of fmd-self-diagnosis and also in schemes/fmd for fmdump(1M).
47  */
48 ssize_t
49 fmd_scheme_fmd_nvl2str(nvlist_t *nvl, char *buf, size_t buflen)
50 {
51 	char *name;
52 
53 	if (nvlist_lookup_string(nvl, FM_FMRI_FMD_NAME, &name) != 0)
54 		return (fmd_fmri_set_errno(EINVAL));
55 
56 	return (snprintf(buf, buflen,
57 	    "%s:///module/%s", FM_FMRI_SCHEME_FMD, name));
58 }
59 
60 static int
61 fmd_scheme_fmd_present(nvlist_t *nvl)
62 {
63 	char *name, *version;
64 	fmd_module_t *mp;
65 	int rv = 0;
66 
67 	if (nvlist_lookup_string(nvl, FM_FMRI_FMD_NAME, &name) != 0 ||
68 	    nvlist_lookup_string(nvl, FM_FMRI_FMD_VERSION, &version) != 0)
69 		return (fmd_fmri_set_errno(EINVAL));
70 
71 	if ((mp = fmd_modhash_lookup(fmd.d_mod_hash, name)) != NULL) {
72 		fmd_module_lock(mp);
73 
74 		rv = mp->mod_info != NULL &&
75 		    strcmp(mp->mod_info->fmdi_vers, version) == 0;
76 
77 		fmd_module_unlock(mp);
78 		fmd_module_rele(mp);
79 	}
80 
81 	return (rv);
82 }
83 
84 static int
85 fmd_scheme_fmd_unusable(nvlist_t *nvl)
86 {
87 	char *name;
88 	fmd_module_t *mp;
89 	int rv = 1;
90 
91 	if (nvlist_lookup_string(nvl, FM_FMRI_FMD_NAME, &name) != 0)
92 		return (fmd_fmri_set_errno(EINVAL));
93 
94 	if ((mp = fmd_modhash_lookup(fmd.d_mod_hash, name)) != NULL) {
95 		rv = mp->mod_error != 0;
96 		fmd_module_rele(mp);
97 	}
98 
99 	return (rv);
100 }
101 
102 static long
103 fmd_scheme_notsup(void)
104 {
105 	return (fmd_set_errno(EFMD_FMRI_NOTSUP));
106 }
107 
108 static int
109 fmd_scheme_nop(void)
110 {
111 	return (0);
112 }
113 
114 /*
115  * Default values for the scheme ops.  If a scheme function is not defined in
116  * the module, then this operation is implemented using the default function.
117  */
118 static const fmd_scheme_ops_t _fmd_scheme_default_ops = {
119 	(int (*)())fmd_scheme_nop,		/* sop_init */
120 	(void (*)())fmd_scheme_nop,		/* sop_fini */
121 	(ssize_t (*)())fmd_scheme_notsup,	/* sop_nvl2str */
122 	(int (*)())fmd_scheme_nop,		/* sop_expand */
123 	(int (*)())fmd_scheme_notsup,		/* sop_present */
124 	(int (*)())fmd_scheme_notsup,		/* sop_unusable */
125 	(int (*)())fmd_scheme_notsup		/* sop_contains */
126 };
127 
128 static const fmd_scheme_ops_t _fmd_scheme_builtin_ops = {
129 	(int (*)())fmd_scheme_nop,		/* sop_init */
130 	(void (*)())fmd_scheme_nop,		/* sop_fini */
131 	fmd_scheme_fmd_nvl2str,			/* sop_nvl2str */
132 	(int (*)())fmd_scheme_nop,		/* sop_expand */
133 	fmd_scheme_fmd_present,			/* sop_present */
134 	fmd_scheme_fmd_unusable,		/* sop_unusable */
135 	(int (*)())fmd_scheme_notsup		/* sop_contains */
136 };
137 
138 /*
139  * Scheme ops descriptions.  These names and offsets are used by the function
140  * fmd_scheme_rtld_init(), defined below, to load up a fmd_scheme_ops_t.
141  */
142 static const fmd_scheme_opd_t _fmd_scheme_ops[] = {
143 	{ "fmd_fmri_init", offsetof(fmd_scheme_ops_t, sop_init) },
144 	{ "fmd_fmri_fini", offsetof(fmd_scheme_ops_t, sop_fini) },
145 	{ "fmd_fmri_nvl2str", offsetof(fmd_scheme_ops_t, sop_nvl2str) },
146 	{ "fmd_fmri_expand", offsetof(fmd_scheme_ops_t, sop_expand) },
147 	{ "fmd_fmri_present", offsetof(fmd_scheme_ops_t, sop_present) },
148 	{ "fmd_fmri_unusable", offsetof(fmd_scheme_ops_t, sop_unusable) },
149 	{ "fmd_fmri_contains", offsetof(fmd_scheme_ops_t, sop_contains) },
150 	{ NULL, 0 }
151 };
152 
153 static fmd_scheme_t *
154 fmd_scheme_create(const char *name)
155 {
156 	fmd_scheme_t *sp = fmd_alloc(sizeof (fmd_scheme_t), FMD_SLEEP);
157 
158 	(void) pthread_mutex_init(&sp->sch_lock, NULL);
159 	(void) pthread_cond_init(&sp->sch_cv, NULL);
160 	(void) pthread_mutex_init(&sp->sch_opslock, NULL);
161 
162 	sp->sch_next = NULL;
163 	sp->sch_name = fmd_strdup(name, FMD_SLEEP);
164 	sp->sch_dlp = NULL;
165 	sp->sch_refs = 1;
166 	sp->sch_loaded = 0;
167 	sp->sch_ops = _fmd_scheme_default_ops;
168 
169 	return (sp);
170 }
171 
172 static void
173 fmd_scheme_destroy(fmd_scheme_t *sp)
174 {
175 	ASSERT(MUTEX_HELD(&sp->sch_lock));
176 	ASSERT(sp->sch_refs == 0);
177 
178 	if (sp->sch_dlp != NULL) {
179 		TRACE((FMD_DBG_FMRI, "dlclose scheme %s", sp->sch_name));
180 
181 		if (sp->sch_ops.sop_fini != NULL)
182 			sp->sch_ops.sop_fini();
183 
184 		(void) dlclose(sp->sch_dlp);
185 	}
186 
187 	fmd_strfree(sp->sch_name);
188 	fmd_free(sp, sizeof (fmd_scheme_t));
189 }
190 
191 fmd_scheme_hash_t *
192 fmd_scheme_hash_create(const char *rootdir, const char *dirpath)
193 {
194 	fmd_scheme_hash_t *shp;
195 	char path[PATH_MAX];
196 	fmd_scheme_t *sp;
197 
198 	shp = fmd_alloc(sizeof (fmd_scheme_hash_t), FMD_SLEEP);
199 	(void) snprintf(path, sizeof (path), "%s/%s", rootdir, dirpath);
200 	shp->sch_dirpath = fmd_strdup(path, FMD_SLEEP);
201 	(void) pthread_rwlock_init(&shp->sch_rwlock, NULL);
202 	shp->sch_hashlen = fmd.d_str_buckets;
203 	shp->sch_hash = fmd_zalloc(sizeof (fmd_scheme_t *) *
204 	    shp->sch_hashlen, FMD_SLEEP);
205 
206 	sp = fmd_scheme_create(FM_FMRI_SCHEME_FMD);
207 	sp->sch_ops = _fmd_scheme_builtin_ops;
208 	sp->sch_loaded = FMD_B_TRUE;
209 	shp->sch_hash[fmd_strhash(sp->sch_name) % shp->sch_hashlen] = sp;
210 
211 	return (shp);
212 }
213 
214 void
215 fmd_scheme_hash_destroy(fmd_scheme_hash_t *shp)
216 {
217 	fmd_scheme_t *sp, *np;
218 	uint_t i;
219 
220 	for (i = 0; i < shp->sch_hashlen; i++) {
221 		for (sp = shp->sch_hash[i]; sp != NULL; sp = np) {
222 			np = sp->sch_next;
223 			sp->sch_next = NULL;
224 			fmd_scheme_hash_release(shp, sp);
225 		}
226 	}
227 
228 	fmd_free(shp->sch_hash, sizeof (fmd_scheme_t *) * shp->sch_hashlen);
229 	fmd_strfree(shp->sch_dirpath);
230 	fmd_free(shp, sizeof (fmd_scheme_hash_t));
231 }
232 
233 void
234 fmd_scheme_hash_trygc(fmd_scheme_hash_t *shp)
235 {
236 	fmd_scheme_t *sp, *np;
237 	uint_t i;
238 
239 	if (shp == NULL || pthread_rwlock_trywrlock(&shp->sch_rwlock) != 0)
240 		return; /* failed to acquire lock: just skip garbage collect */
241 
242 	for (i = 0; i < shp->sch_hashlen; i++) {
243 		for (sp = shp->sch_hash[i]; sp != NULL; sp = np) {
244 			np = sp->sch_next;
245 			sp->sch_next = NULL;
246 			fmd_scheme_hash_release(shp, sp);
247 		}
248 	}
249 
250 	bzero(shp->sch_hash, sizeof (fmd_scheme_t *) * shp->sch_hashlen);
251 	(void) pthread_rwlock_unlock(&shp->sch_rwlock);
252 }
253 
254 static int
255 fmd_scheme_rtld_init(fmd_scheme_t *sp)
256 {
257 	const fmd_scheme_opd_t *opd;
258 	void *p;
259 
260 	for (opd = _fmd_scheme_ops; opd->opd_name != NULL; opd++) {
261 		if ((p = dlsym(sp->sch_dlp, opd->opd_name)) != NULL)
262 			*(void **)((uintptr_t)&sp->sch_ops + opd->opd_off) = p;
263 	}
264 
265 	return (0);
266 }
267 
268 fmd_scheme_t *
269 fmd_scheme_hash_xlookup(fmd_scheme_hash_t *shp, const char *name, uint_t h)
270 {
271 	fmd_scheme_t *sp;
272 
273 	ASSERT(RW_LOCK_HELD(&shp->sch_rwlock));
274 
275 	for (sp = shp->sch_hash[h]; sp != NULL; sp = sp->sch_next) {
276 		if (strcmp(sp->sch_name, name) == 0)
277 			break;
278 	}
279 
280 	return (sp);
281 }
282 
283 /*
284  * Lookup a scheme module by name and return with a reference placed on it.  We
285  * use the scheme hash to cache "negative" entries (e.g. missing modules) as
286  * well so this function always returns successfully with a non-NULL scheme.
287  * The caller is responsible for applying fmd_scheme_hash_release() afterward.
288  */
289 fmd_scheme_t *
290 fmd_scheme_hash_lookup(fmd_scheme_hash_t *shp, const char *name)
291 {
292 	fmd_scheme_t *sp, *nsp = NULL;
293 	uint_t h;
294 
295 	/*
296 	 * Grab the hash lock as reader and look for the appropriate scheme.
297 	 * If the scheme isn't yet loaded, allocate a new scheme and grab the
298 	 * hash lock as writer to insert it (after checking again for it).
299 	 */
300 	(void) pthread_rwlock_rdlock(&shp->sch_rwlock);
301 	h = fmd_strhash(name) % shp->sch_hashlen;
302 
303 	if ((sp = fmd_scheme_hash_xlookup(shp, name, h)) == NULL) {
304 		(void) pthread_rwlock_unlock(&shp->sch_rwlock);
305 		nsp = fmd_scheme_create(name);
306 		(void) pthread_rwlock_wrlock(&shp->sch_rwlock);
307 
308 		if ((sp = fmd_scheme_hash_xlookup(shp, name, h)) == NULL) {
309 			nsp->sch_next = shp->sch_hash[h];
310 			shp->sch_hash[h] = sp = nsp;
311 		} else {
312 			fmd_scheme_hash_release(shp, nsp);
313 			nsp = NULL;
314 		}
315 	}
316 
317 	/*
318 	 * Grab the scheme lock so it can't disappear and then drop the hash
319 	 * lock so that other lookups in the scheme hash can proceed.
320 	 */
321 	(void) pthread_mutex_lock(&sp->sch_lock);
322 	(void) pthread_rwlock_unlock(&shp->sch_rwlock);
323 
324 	/*
325 	 * If we created the scheme, compute its path and try to load it.  If
326 	 * we found an existing scheme, wait until its loaded bit is set.  Once
327 	 * we're done with either operation, increment sch_refs and return.
328 	 */
329 	if (nsp != NULL) {
330 		char path[PATH_MAX];
331 
332 		(void) snprintf(path, sizeof (path),
333 		    "%s/%s.so", shp->sch_dirpath, sp->sch_name);
334 
335 		TRACE((FMD_DBG_FMRI, "dlopen scheme %s", sp->sch_name));
336 		sp->sch_dlp = dlopen(path, RTLD_LOCAL | RTLD_NOW);
337 
338 		if (sp->sch_dlp == NULL) {
339 			fmd_error(EFMD_FMRI_SCHEME,
340 			    "failed to load fmri scheme %s: %s\n", path,
341 			    dlerror());
342 		} else if (fmd_scheme_rtld_init(sp) != 0 ||
343 		    sp->sch_ops.sop_init() != 0) {
344 			fmd_error(EFMD_FMRI_SCHEME,
345 			    "failed to initialize fmri scheme %s", path);
346 			(void) dlclose(sp->sch_dlp);
347 			sp->sch_dlp = NULL;
348 			sp->sch_ops = _fmd_scheme_default_ops;
349 		}
350 
351 		sp->sch_loaded = FMD_B_TRUE; /* set regardless of success */
352 		sp->sch_refs++;
353 		ASSERT(sp->sch_refs != 0);
354 		(void) pthread_mutex_unlock(&sp->sch_lock);
355 		(void) pthread_cond_broadcast(&sp->sch_cv);
356 
357 	} else {
358 		while (!sp->sch_loaded)
359 			(void) pthread_cond_wait(&sp->sch_cv, &sp->sch_lock);
360 
361 		sp->sch_refs++;
362 		ASSERT(sp->sch_refs != 0);
363 		(void) pthread_mutex_unlock(&sp->sch_lock);
364 	}
365 
366 	return (sp);
367 }
368 
369 /*
370  * Release the hold on a scheme obtained using fmd_scheme_hash_lookup().
371  * We take 'shp' for symmetry and in case we need to use it in future work.
372  */
373 /*ARGSUSED*/
374 void
375 fmd_scheme_hash_release(fmd_scheme_hash_t *shp, fmd_scheme_t *sp)
376 {
377 	(void) pthread_mutex_lock(&sp->sch_lock);
378 
379 	ASSERT(sp->sch_refs != 0);
380 	if (--sp->sch_refs == 0)
381 		fmd_scheme_destroy(sp);
382 	else
383 		(void) pthread_mutex_unlock(&sp->sch_lock);
384 }
385