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
145static nvlist_t *
146fmd_scheme_notranslate(nvlist_t *fmri, nvlist_t *auth __unused)
147{
148	(void) nvlist_xdup(fmri, &fmri, &fmd.d_nva);
149	return (fmri);
150}
151
152static ssize_t
153fmd_scheme_notsup_nvl2str(nvlist_t *fmri __unused, char *arg1 __unused,
154    size_t arg2 __unused)
155{
156	return (fmd_set_errno(EFMD_FMRI_NOTSUP));
157}
158
159static int
160fmd_scheme_notsup(nvlist_t *fmri __unused)
161{
162	return (fmd_set_errno(EFMD_FMRI_NOTSUP));
163}
164
165static int
166fmd_scheme_notsup2(nvlist_t *fmri1 __unused, nvlist_t *fmri2 __unused)
167{
168	return (fmd_set_errno(EFMD_FMRI_NOTSUP));
169}
170
171static void
172fmd_scheme_vnop(void)
173{
174}
175
176static int
177fmd_scheme_nop(void)
178{
179	return (0);
180}
181
182/*
183 * Default values for the scheme ops.  If a scheme function is not defined in
184 * the module, then this operation is implemented using the default function.
185 */
186static const fmd_scheme_ops_t _fmd_scheme_default_ops = {
187	.sop_init = fmd_scheme_nop,
188	.sop_fini = fmd_scheme_vnop,
189	.sop_nvl2str = fmd_scheme_notsup_nvl2str,
190	.sop_expand = fmd_scheme_notsup,
191	.sop_present = fmd_scheme_notsup,
192	.sop_replaced = fmd_scheme_notsup,
193	.sop_service_state = fmd_scheme_notsup,
194	.sop_unusable = fmd_scheme_notsup,
195	.sop_contains = fmd_scheme_notsup2,
196	.sop_translate = fmd_scheme_notranslate
197};
198
199static const fmd_scheme_ops_t _fmd_scheme_builtin_ops = {
200	.sop_init = fmd_scheme_nop,
201	.sop_fini = fmd_scheme_vnop,
202	.sop_nvl2str = fmd_scheme_fmd_nvl2str,
203	.sop_expand = fmd_scheme_notsup,
204	.sop_present = fmd_scheme_fmd_present,
205	.sop_replaced = fmd_scheme_fmd_replaced,
206	.sop_service_state = fmd_scheme_fmd_service_state,
207	.sop_unusable = fmd_scheme_fmd_unusable,
208	.sop_contains = fmd_scheme_notsup2,
209	.sop_translate = fmd_scheme_notranslate
210};
211
212/*
213 * Scheme ops descriptions.  These names and offsets are used by the function
214 * fmd_scheme_rtld_init(), defined below, to load up a fmd_scheme_ops_t.
215 */
216static const fmd_scheme_opd_t _fmd_scheme_ops[] = {
217	{ "fmd_fmri_init", offsetof(fmd_scheme_ops_t, sop_init) },
218	{ "fmd_fmri_fini", offsetof(fmd_scheme_ops_t, sop_fini) },
219	{ "fmd_fmri_nvl2str", offsetof(fmd_scheme_ops_t, sop_nvl2str) },
220	{ "fmd_fmri_expand", offsetof(fmd_scheme_ops_t, sop_expand) },
221	{ "fmd_fmri_present", offsetof(fmd_scheme_ops_t, sop_present) },
222	{ "fmd_fmri_replaced", offsetof(fmd_scheme_ops_t, sop_replaced) },
223	{ "fmd_fmri_service_state", offsetof(fmd_scheme_ops_t,
224	    sop_service_state) },
225	{ "fmd_fmri_unusable", offsetof(fmd_scheme_ops_t, sop_unusable) },
226	{ "fmd_fmri_contains", offsetof(fmd_scheme_ops_t, sop_contains) },
227	{ "fmd_fmri_translate", offsetof(fmd_scheme_ops_t, sop_translate) },
228	{ NULL, 0 }
229};
230
231static fmd_scheme_t *
232fmd_scheme_create(const char *name)
233{
234	fmd_scheme_t *sp = fmd_alloc(sizeof (fmd_scheme_t), FMD_SLEEP);
235
236	(void) pthread_mutex_init(&sp->sch_lock, NULL);
237	(void) pthread_cond_init(&sp->sch_cv, NULL);
238	(void) pthread_mutex_init(&sp->sch_opslock, NULL);
239
240	sp->sch_next = NULL;
241	sp->sch_name = fmd_strdup(name, FMD_SLEEP);
242	sp->sch_dlp = NULL;
243	sp->sch_refs = 1;
244	sp->sch_loaded = 0;
245	sp->sch_ops = _fmd_scheme_default_ops;
246
247	return (sp);
248}
249
250static void
251fmd_scheme_destroy(fmd_scheme_t *sp)
252{
253	ASSERT(MUTEX_HELD(&sp->sch_lock));
254	ASSERT(sp->sch_refs == 0);
255
256	if (sp->sch_dlp != NULL) {
257		TRACE((FMD_DBG_FMRI, "dlclose scheme %s", sp->sch_name));
258
259		if (sp->sch_ops.sop_fini != NULL)
260			sp->sch_ops.sop_fini();
261
262		(void) dlclose(sp->sch_dlp);
263	}
264
265	fmd_strfree(sp->sch_name);
266	fmd_free(sp, sizeof (fmd_scheme_t));
267}
268
269fmd_scheme_hash_t *
270fmd_scheme_hash_create(const char *rootdir, const char *dirpath)
271{
272	fmd_scheme_hash_t *shp;
273	char path[PATH_MAX];
274	fmd_scheme_t *sp;
275
276	shp = fmd_alloc(sizeof (fmd_scheme_hash_t), FMD_SLEEP);
277	(void) snprintf(path, sizeof (path), "%s/%s", rootdir, dirpath);
278	shp->sch_dirpath = fmd_strdup(path, FMD_SLEEP);
279	(void) pthread_rwlock_init(&shp->sch_rwlock, NULL);
280	shp->sch_hashlen = fmd.d_str_buckets;
281	shp->sch_hash = fmd_zalloc(sizeof (fmd_scheme_t *) *
282	    shp->sch_hashlen, FMD_SLEEP);
283
284	sp = fmd_scheme_create(FM_FMRI_SCHEME_FMD);
285	sp->sch_ops = _fmd_scheme_builtin_ops;
286	sp->sch_loaded = FMD_B_TRUE;
287	shp->sch_hash[fmd_strhash(sp->sch_name) % shp->sch_hashlen] = sp;
288
289	return (shp);
290}
291
292void
293fmd_scheme_hash_destroy(fmd_scheme_hash_t *shp)
294{
295	fmd_scheme_t *sp, *np;
296	uint_t i;
297
298	for (i = 0; i < shp->sch_hashlen; i++) {
299		for (sp = shp->sch_hash[i]; sp != NULL; sp = np) {
300			np = sp->sch_next;
301			sp->sch_next = NULL;
302			fmd_scheme_hash_release(shp, sp);
303		}
304	}
305
306	fmd_free(shp->sch_hash, sizeof (fmd_scheme_t *) * shp->sch_hashlen);
307	fmd_strfree(shp->sch_dirpath);
308	fmd_free(shp, sizeof (fmd_scheme_hash_t));
309}
310
311void
312fmd_scheme_hash_trygc(fmd_scheme_hash_t *shp)
313{
314	fmd_scheme_t *sp, *np;
315	uint_t i;
316
317	if (shp == NULL || pthread_rwlock_trywrlock(&shp->sch_rwlock) != 0)
318		return; /* failed to acquire lock: just skip garbage collect */
319
320	for (i = 0; i < shp->sch_hashlen; i++) {
321		for (sp = shp->sch_hash[i]; sp != NULL; sp = np) {
322			np = sp->sch_next;
323			sp->sch_next = NULL;
324			fmd_scheme_hash_release(shp, sp);
325		}
326	}
327
328	bzero(shp->sch_hash, sizeof (fmd_scheme_t *) * shp->sch_hashlen);
329	(void) pthread_rwlock_unlock(&shp->sch_rwlock);
330}
331
332static int
333fmd_scheme_rtld_init(fmd_scheme_t *sp)
334{
335	const fmd_scheme_opd_t *opd;
336	void *p;
337
338	for (opd = _fmd_scheme_ops; opd->opd_name != NULL; opd++) {
339		if ((p = dlsym(sp->sch_dlp, opd->opd_name)) != NULL)
340			*(void **)((uintptr_t)&sp->sch_ops + opd->opd_off) = p;
341	}
342
343	return (0);
344}
345
346fmd_scheme_t *
347fmd_scheme_hash_xlookup(fmd_scheme_hash_t *shp, const char *name, uint_t h)
348{
349	fmd_scheme_t *sp;
350
351	ASSERT(RW_LOCK_HELD(&shp->sch_rwlock));
352
353	for (sp = shp->sch_hash[h]; sp != NULL; sp = sp->sch_next) {
354		if (strcmp(sp->sch_name, name) == 0)
355			break;
356	}
357
358	return (sp);
359}
360
361/*
362 * Lookup a scheme module by name and return with a reference placed on it.  We
363 * use the scheme hash to cache "negative" entries (e.g. missing modules) as
364 * well so this function always returns successfully with a non-NULL scheme.
365 * The caller is responsible for applying fmd_scheme_hash_release() afterward.
366 */
367fmd_scheme_t *
368fmd_scheme_hash_lookup(fmd_scheme_hash_t *shp, const char *name)
369{
370	fmd_scheme_t *sp, *nsp = NULL;
371	uint_t h;
372
373	/*
374	 * Grab the hash lock as reader and look for the appropriate scheme.
375	 * If the scheme isn't yet loaded, allocate a new scheme and grab the
376	 * hash lock as writer to insert it (after checking again for it).
377	 */
378	(void) pthread_rwlock_rdlock(&shp->sch_rwlock);
379	h = fmd_strhash(name) % shp->sch_hashlen;
380
381	if ((sp = fmd_scheme_hash_xlookup(shp, name, h)) == NULL) {
382		(void) pthread_rwlock_unlock(&shp->sch_rwlock);
383		nsp = fmd_scheme_create(name);
384		(void) pthread_rwlock_wrlock(&shp->sch_rwlock);
385
386		if ((sp = fmd_scheme_hash_xlookup(shp, name, h)) == NULL) {
387			nsp->sch_next = shp->sch_hash[h];
388			shp->sch_hash[h] = sp = nsp;
389		} else {
390			fmd_scheme_hash_release(shp, nsp);
391			nsp = NULL;
392		}
393	}
394
395	/*
396	 * Grab the scheme lock so it can't disappear and then drop the hash
397	 * lock so that other lookups in the scheme hash can proceed.
398	 */
399	(void) pthread_mutex_lock(&sp->sch_lock);
400	(void) pthread_rwlock_unlock(&shp->sch_rwlock);
401
402	/*
403	 * If we created the scheme, compute its path and try to load it.  If
404	 * we found an existing scheme, wait until its loaded bit is set.  Once
405	 * we're done with either operation, increment sch_refs and return.
406	 */
407	if (nsp != NULL) {
408		char path[PATH_MAX];
409
410		(void) snprintf(path, sizeof (path),
411		    "%s/%s.so", shp->sch_dirpath, sp->sch_name);
412
413		TRACE((FMD_DBG_FMRI, "dlopen scheme %s", sp->sch_name));
414		sp->sch_dlp = dlopen(path, RTLD_LOCAL | RTLD_NOW);
415
416		if (sp->sch_dlp == NULL) {
417			fmd_error(EFMD_FMRI_SCHEME,
418			    "failed to load fmri scheme %s: %s\n", path,
419			    dlerror());
420		} else if (fmd_scheme_rtld_init(sp) != 0 ||
421		    sp->sch_ops.sop_init() != 0) {
422			fmd_error(EFMD_FMRI_SCHEME,
423			    "failed to initialize fmri scheme %s", path);
424			(void) dlclose(sp->sch_dlp);
425			sp->sch_dlp = NULL;
426			sp->sch_ops = _fmd_scheme_default_ops;
427		}
428
429		sp->sch_loaded = FMD_B_TRUE; /* set regardless of success */
430		sp->sch_refs++;
431		ASSERT(sp->sch_refs != 0);
432
433		(void) pthread_cond_broadcast(&sp->sch_cv);
434		(void) pthread_mutex_unlock(&sp->sch_lock);
435
436	} else {
437		while (!sp->sch_loaded)
438			(void) pthread_cond_wait(&sp->sch_cv, &sp->sch_lock);
439
440		sp->sch_refs++;
441		ASSERT(sp->sch_refs != 0);
442		(void) pthread_mutex_unlock(&sp->sch_lock);
443	}
444
445	return (sp);
446}
447
448/*
449 * Release the hold on a scheme obtained using fmd_scheme_hash_lookup().
450 * We take 'shp' for symmetry and in case we need to use it in future work.
451 */
452/*ARGSUSED*/
453void
454fmd_scheme_hash_release(fmd_scheme_hash_t *shp, fmd_scheme_t *sp)
455{
456	(void) pthread_mutex_lock(&sp->sch_lock);
457
458	ASSERT(sp->sch_refs != 0);
459	if (--sp->sch_refs == 0)
460		fmd_scheme_destroy(sp);
461	else
462		(void) pthread_mutex_unlock(&sp->sch_lock);
463}
464