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