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