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