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