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