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