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