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