xref: /illumos-gate/usr/src/cmd/fm/fmd/common/fmd_scheme.c (revision b7daf79982d77b491ef9662483cd4549e0e5da9a)
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  */
45 ssize_t
46 fmd_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 
57 static int
58 fmd_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 
80 static int
81 fmd_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 
103 static int
104 fmd_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 
124 static int
125 fmd_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*/
146 static nvlist_t *
147 fmd_scheme_notranslate(nvlist_t *fmri, nvlist_t *auth)
148 {
149 	(void) nvlist_xdup(fmri, &fmri, &fmd.d_nva);
150 	return (fmri);
151 }
152 
153 static long
154 fmd_scheme_notsup(void)
155 {
156 	return (fmd_set_errno(EFMD_FMRI_NOTSUP));
157 }
158 
159 static int
160 fmd_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  */
169 static 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 
182 static 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  */
199 static 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 
214 static fmd_scheme_t *
215 fmd_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 
233 static void
234 fmd_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 
252 fmd_scheme_hash_t *
253 fmd_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 
275 void
276 fmd_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 
294 void
295 fmd_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 
315 static int
316 fmd_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 
329 fmd_scheme_t *
330 fmd_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  */
350 fmd_scheme_t *
351 fmd_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*/
436 void
437 fmd_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