xref: /illumos-gate/usr/src/cmd/fm/fmd/common/fmd_scheme.c (revision dd72704bd9e794056c558153663c739e2012d721)
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(8).
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 static nvlist_t *
146 fmd_scheme_notranslate(nvlist_t *fmri, nvlist_t *auth __unused)
147 {
148 	(void) nvlist_xdup(fmri, &fmri, &fmd.d_nva);
149 	return (fmri);
150 }
151 
152 static ssize_t
153 fmd_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 
159 static int
160 fmd_scheme_notsup(nvlist_t *fmri __unused)
161 {
162 	return (fmd_set_errno(EFMD_FMRI_NOTSUP));
163 }
164 
165 static int
166 fmd_scheme_notsup2(nvlist_t *fmri1 __unused, nvlist_t *fmri2 __unused)
167 {
168 	return (fmd_set_errno(EFMD_FMRI_NOTSUP));
169 }
170 
171 static void
172 fmd_scheme_vnop(void)
173 {
174 }
175 
176 static int
177 fmd_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  */
186 static 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 
199 static 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  */
216 static 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 
231 static fmd_scheme_t *
232 fmd_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 
250 static void
251 fmd_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 
269 fmd_scheme_hash_t *
270 fmd_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 
292 void
293 fmd_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 
311 void
312 fmd_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 
332 static int
333 fmd_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 
346 fmd_scheme_t *
347 fmd_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  */
367 fmd_scheme_t *
368 fmd_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*/
453 void
454 fmd_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