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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 23 /* 24 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 25 * Use is subject to license terms. 26 */ 27 28 #pragma ident "%Z%%M% %I% %E% SMI" 29 30 #include <limits.h> 31 #include <stddef.h> 32 #include <unistd.h> 33 #include <dlfcn.h> 34 35 #include <fmd_alloc.h> 36 #include <fmd_error.h> 37 #include <fmd_subr.h> 38 #include <fmd_string.h> 39 #include <fmd_scheme.h> 40 #include <fmd_fmri.h> 41 #include <fmd_module.h> 42 43 #include <fmd.h> 44 45 /* 46 * The fmd resource scheme, used for fmd modules, must be implemented here for 47 * the benefit of fmd-self-diagnosis and also in schemes/fmd for fmdump(1M). 48 */ 49 ssize_t 50 fmd_scheme_fmd_nvl2str(nvlist_t *nvl, char *buf, size_t buflen) 51 { 52 char *name; 53 54 if (nvlist_lookup_string(nvl, FM_FMRI_FMD_NAME, &name) != 0) 55 return (fmd_fmri_set_errno(EINVAL)); 56 57 return (snprintf(buf, buflen, 58 "%s:///module/%s", FM_FMRI_SCHEME_FMD, name)); 59 } 60 61 static int 62 fmd_scheme_fmd_present(nvlist_t *nvl) 63 { 64 char *name, *version; 65 fmd_module_t *mp; 66 int rv = 0; 67 68 if (nvlist_lookup_string(nvl, FM_FMRI_FMD_NAME, &name) != 0 || 69 nvlist_lookup_string(nvl, FM_FMRI_FMD_VERSION, &version) != 0) 70 return (fmd_fmri_set_errno(EINVAL)); 71 72 if ((mp = fmd_modhash_lookup(fmd.d_mod_hash, name)) != NULL) { 73 fmd_module_lock(mp); 74 75 rv = mp->mod_info != NULL && 76 strcmp(mp->mod_info->fmdi_vers, version) == 0; 77 78 fmd_module_unlock(mp); 79 fmd_module_rele(mp); 80 } 81 82 return (rv); 83 } 84 85 static int 86 fmd_scheme_fmd_unusable(nvlist_t *nvl) 87 { 88 char *name; 89 fmd_module_t *mp; 90 int rv = 1; 91 92 if (nvlist_lookup_string(nvl, FM_FMRI_FMD_NAME, &name) != 0) 93 return (fmd_fmri_set_errno(EINVAL)); 94 95 if ((mp = fmd_modhash_lookup(fmd.d_mod_hash, name)) != NULL) { 96 rv = mp->mod_error != 0; 97 fmd_module_rele(mp); 98 } 99 100 return (rv); 101 } 102 103 /*ARGSUSED*/ 104 static nvlist_t * 105 fmd_scheme_notranslate(nvlist_t *fmri, nvlist_t *auth) 106 { 107 (void) nvlist_xdup(fmri, &fmri, &fmd.d_nva); 108 return (fmri); 109 } 110 111 static long 112 fmd_scheme_notsup(void) 113 { 114 return (fmd_set_errno(EFMD_FMRI_NOTSUP)); 115 } 116 117 static int 118 fmd_scheme_nop(void) 119 { 120 return (0); 121 } 122 123 /* 124 * Default values for the scheme ops. If a scheme function is not defined in 125 * the module, then this operation is implemented using the default function. 126 */ 127 static const fmd_scheme_ops_t _fmd_scheme_default_ops = { 128 (int (*)())fmd_scheme_nop, /* sop_init */ 129 (void (*)())fmd_scheme_nop, /* sop_fini */ 130 (ssize_t (*)())fmd_scheme_notsup, /* sop_nvl2str */ 131 (int (*)())fmd_scheme_nop, /* sop_expand */ 132 (int (*)())fmd_scheme_notsup, /* sop_present */ 133 (int (*)())fmd_scheme_notsup, /* sop_unusable */ 134 (int (*)())fmd_scheme_notsup, /* sop_contains */ 135 fmd_scheme_notranslate /* sop_translate */ 136 }; 137 138 static const fmd_scheme_ops_t _fmd_scheme_builtin_ops = { 139 (int (*)())fmd_scheme_nop, /* sop_init */ 140 (void (*)())fmd_scheme_nop, /* sop_fini */ 141 fmd_scheme_fmd_nvl2str, /* sop_nvl2str */ 142 (int (*)())fmd_scheme_nop, /* sop_expand */ 143 fmd_scheme_fmd_present, /* sop_present */ 144 fmd_scheme_fmd_unusable, /* sop_unusable */ 145 (int (*)())fmd_scheme_notsup, /* sop_contains */ 146 fmd_scheme_notranslate /* sop_translate */ 147 }; 148 149 /* 150 * Scheme ops descriptions. These names and offsets are used by the function 151 * fmd_scheme_rtld_init(), defined below, to load up a fmd_scheme_ops_t. 152 */ 153 static const fmd_scheme_opd_t _fmd_scheme_ops[] = { 154 { "fmd_fmri_init", offsetof(fmd_scheme_ops_t, sop_init) }, 155 { "fmd_fmri_fini", offsetof(fmd_scheme_ops_t, sop_fini) }, 156 { "fmd_fmri_nvl2str", offsetof(fmd_scheme_ops_t, sop_nvl2str) }, 157 { "fmd_fmri_expand", offsetof(fmd_scheme_ops_t, sop_expand) }, 158 { "fmd_fmri_present", offsetof(fmd_scheme_ops_t, sop_present) }, 159 { "fmd_fmri_unusable", offsetof(fmd_scheme_ops_t, sop_unusable) }, 160 { "fmd_fmri_contains", offsetof(fmd_scheme_ops_t, sop_contains) }, 161 { "fmd_fmri_translate", offsetof(fmd_scheme_ops_t, sop_translate) }, 162 { NULL, 0 } 163 }; 164 165 static fmd_scheme_t * 166 fmd_scheme_create(const char *name) 167 { 168 fmd_scheme_t *sp = fmd_alloc(sizeof (fmd_scheme_t), FMD_SLEEP); 169 170 (void) pthread_mutex_init(&sp->sch_lock, NULL); 171 (void) pthread_cond_init(&sp->sch_cv, NULL); 172 (void) pthread_mutex_init(&sp->sch_opslock, NULL); 173 174 sp->sch_next = NULL; 175 sp->sch_name = fmd_strdup(name, FMD_SLEEP); 176 sp->sch_dlp = NULL; 177 sp->sch_refs = 1; 178 sp->sch_loaded = 0; 179 sp->sch_ops = _fmd_scheme_default_ops; 180 181 return (sp); 182 } 183 184 static void 185 fmd_scheme_destroy(fmd_scheme_t *sp) 186 { 187 ASSERT(MUTEX_HELD(&sp->sch_lock)); 188 ASSERT(sp->sch_refs == 0); 189 190 if (sp->sch_dlp != NULL) { 191 TRACE((FMD_DBG_FMRI, "dlclose scheme %s", sp->sch_name)); 192 193 if (sp->sch_ops.sop_fini != NULL) 194 sp->sch_ops.sop_fini(); 195 196 (void) dlclose(sp->sch_dlp); 197 } 198 199 fmd_strfree(sp->sch_name); 200 fmd_free(sp, sizeof (fmd_scheme_t)); 201 } 202 203 fmd_scheme_hash_t * 204 fmd_scheme_hash_create(const char *rootdir, const char *dirpath) 205 { 206 fmd_scheme_hash_t *shp; 207 char path[PATH_MAX]; 208 fmd_scheme_t *sp; 209 210 shp = fmd_alloc(sizeof (fmd_scheme_hash_t), FMD_SLEEP); 211 (void) snprintf(path, sizeof (path), "%s/%s", rootdir, dirpath); 212 shp->sch_dirpath = fmd_strdup(path, FMD_SLEEP); 213 (void) pthread_rwlock_init(&shp->sch_rwlock, NULL); 214 shp->sch_hashlen = fmd.d_str_buckets; 215 shp->sch_hash = fmd_zalloc(sizeof (fmd_scheme_t *) * 216 shp->sch_hashlen, FMD_SLEEP); 217 218 sp = fmd_scheme_create(FM_FMRI_SCHEME_FMD); 219 sp->sch_ops = _fmd_scheme_builtin_ops; 220 sp->sch_loaded = FMD_B_TRUE; 221 shp->sch_hash[fmd_strhash(sp->sch_name) % shp->sch_hashlen] = sp; 222 223 return (shp); 224 } 225 226 void 227 fmd_scheme_hash_destroy(fmd_scheme_hash_t *shp) 228 { 229 fmd_scheme_t *sp, *np; 230 uint_t i; 231 232 for (i = 0; i < shp->sch_hashlen; i++) { 233 for (sp = shp->sch_hash[i]; sp != NULL; sp = np) { 234 np = sp->sch_next; 235 sp->sch_next = NULL; 236 fmd_scheme_hash_release(shp, sp); 237 } 238 } 239 240 fmd_free(shp->sch_hash, sizeof (fmd_scheme_t *) * shp->sch_hashlen); 241 fmd_strfree(shp->sch_dirpath); 242 fmd_free(shp, sizeof (fmd_scheme_hash_t)); 243 } 244 245 void 246 fmd_scheme_hash_trygc(fmd_scheme_hash_t *shp) 247 { 248 fmd_scheme_t *sp, *np; 249 uint_t i; 250 251 if (shp == NULL || pthread_rwlock_trywrlock(&shp->sch_rwlock) != 0) 252 return; /* failed to acquire lock: just skip garbage collect */ 253 254 for (i = 0; i < shp->sch_hashlen; i++) { 255 for (sp = shp->sch_hash[i]; sp != NULL; sp = np) { 256 np = sp->sch_next; 257 sp->sch_next = NULL; 258 fmd_scheme_hash_release(shp, sp); 259 } 260 } 261 262 bzero(shp->sch_hash, sizeof (fmd_scheme_t *) * shp->sch_hashlen); 263 (void) pthread_rwlock_unlock(&shp->sch_rwlock); 264 } 265 266 static int 267 fmd_scheme_rtld_init(fmd_scheme_t *sp) 268 { 269 const fmd_scheme_opd_t *opd; 270 void *p; 271 272 for (opd = _fmd_scheme_ops; opd->opd_name != NULL; opd++) { 273 if ((p = dlsym(sp->sch_dlp, opd->opd_name)) != NULL) 274 *(void **)((uintptr_t)&sp->sch_ops + opd->opd_off) = p; 275 } 276 277 return (0); 278 } 279 280 fmd_scheme_t * 281 fmd_scheme_hash_xlookup(fmd_scheme_hash_t *shp, const char *name, uint_t h) 282 { 283 fmd_scheme_t *sp; 284 285 ASSERT(RW_LOCK_HELD(&shp->sch_rwlock)); 286 287 for (sp = shp->sch_hash[h]; sp != NULL; sp = sp->sch_next) { 288 if (strcmp(sp->sch_name, name) == 0) 289 break; 290 } 291 292 return (sp); 293 } 294 295 /* 296 * Lookup a scheme module by name and return with a reference placed on it. We 297 * use the scheme hash to cache "negative" entries (e.g. missing modules) as 298 * well so this function always returns successfully with a non-NULL scheme. 299 * The caller is responsible for applying fmd_scheme_hash_release() afterward. 300 */ 301 fmd_scheme_t * 302 fmd_scheme_hash_lookup(fmd_scheme_hash_t *shp, const char *name) 303 { 304 fmd_scheme_t *sp, *nsp = NULL; 305 uint_t h; 306 307 /* 308 * Grab the hash lock as reader and look for the appropriate scheme. 309 * If the scheme isn't yet loaded, allocate a new scheme and grab the 310 * hash lock as writer to insert it (after checking again for it). 311 */ 312 (void) pthread_rwlock_rdlock(&shp->sch_rwlock); 313 h = fmd_strhash(name) % shp->sch_hashlen; 314 315 if ((sp = fmd_scheme_hash_xlookup(shp, name, h)) == NULL) { 316 (void) pthread_rwlock_unlock(&shp->sch_rwlock); 317 nsp = fmd_scheme_create(name); 318 (void) pthread_rwlock_wrlock(&shp->sch_rwlock); 319 320 if ((sp = fmd_scheme_hash_xlookup(shp, name, h)) == NULL) { 321 nsp->sch_next = shp->sch_hash[h]; 322 shp->sch_hash[h] = sp = nsp; 323 } else { 324 fmd_scheme_hash_release(shp, nsp); 325 nsp = NULL; 326 } 327 } 328 329 /* 330 * Grab the scheme lock so it can't disappear and then drop the hash 331 * lock so that other lookups in the scheme hash can proceed. 332 */ 333 (void) pthread_mutex_lock(&sp->sch_lock); 334 (void) pthread_rwlock_unlock(&shp->sch_rwlock); 335 336 /* 337 * If we created the scheme, compute its path and try to load it. If 338 * we found an existing scheme, wait until its loaded bit is set. Once 339 * we're done with either operation, increment sch_refs and return. 340 */ 341 if (nsp != NULL) { 342 char path[PATH_MAX]; 343 344 (void) snprintf(path, sizeof (path), 345 "%s/%s.so", shp->sch_dirpath, sp->sch_name); 346 347 TRACE((FMD_DBG_FMRI, "dlopen scheme %s", sp->sch_name)); 348 sp->sch_dlp = dlopen(path, RTLD_LOCAL | RTLD_NOW); 349 350 if (sp->sch_dlp == NULL) { 351 fmd_error(EFMD_FMRI_SCHEME, 352 "failed to load fmri scheme %s: %s\n", path, 353 dlerror()); 354 } else if (fmd_scheme_rtld_init(sp) != 0 || 355 sp->sch_ops.sop_init() != 0) { 356 fmd_error(EFMD_FMRI_SCHEME, 357 "failed to initialize fmri scheme %s", path); 358 (void) dlclose(sp->sch_dlp); 359 sp->sch_dlp = NULL; 360 sp->sch_ops = _fmd_scheme_default_ops; 361 } 362 363 sp->sch_loaded = FMD_B_TRUE; /* set regardless of success */ 364 sp->sch_refs++; 365 ASSERT(sp->sch_refs != 0); 366 367 (void) pthread_cond_broadcast(&sp->sch_cv); 368 (void) pthread_mutex_unlock(&sp->sch_lock); 369 370 } else { 371 while (!sp->sch_loaded) 372 (void) pthread_cond_wait(&sp->sch_cv, &sp->sch_lock); 373 374 sp->sch_refs++; 375 ASSERT(sp->sch_refs != 0); 376 (void) pthread_mutex_unlock(&sp->sch_lock); 377 } 378 379 return (sp); 380 } 381 382 /* 383 * Release the hold on a scheme obtained using fmd_scheme_hash_lookup(). 384 * We take 'shp' for symmetry and in case we need to use it in future work. 385 */ 386 /*ARGSUSED*/ 387 void 388 fmd_scheme_hash_release(fmd_scheme_hash_t *shp, fmd_scheme_t *sp) 389 { 390 (void) pthread_mutex_lock(&sp->sch_lock); 391 392 ASSERT(sp->sch_refs != 0); 393 if (--sp->sch_refs == 0) 394 fmd_scheme_destroy(sp); 395 else 396 (void) pthread_mutex_unlock(&sp->sch_lock); 397 } 398