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