/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include /* * The fmd resource scheme, used for fmd modules, must be implemented here for * the benefit of fmd-self-diagnosis and also in schemes/fmd for fmdump(8). */ ssize_t fmd_scheme_fmd_nvl2str(nvlist_t *nvl, char *buf, size_t buflen) { char *name; if (nvlist_lookup_string(nvl, FM_FMRI_FMD_NAME, &name) != 0) return (fmd_fmri_set_errno(EINVAL)); return (snprintf(buf, buflen, "%s:///module/%s", FM_FMRI_SCHEME_FMD, name)); } static int fmd_scheme_fmd_present(nvlist_t *nvl) { char *name, *version; fmd_module_t *mp; int rv = 1; if (nvlist_lookup_string(nvl, FM_FMRI_FMD_NAME, &name) != 0 || nvlist_lookup_string(nvl, FM_FMRI_FMD_VERSION, &version) != 0) return (fmd_fmri_set_errno(EINVAL)); if (!fmd.d_loaded) return (1); if ((mp = fmd_modhash_lookup(fmd.d_mod_hash, name)) != NULL) { rv = mp->mod_vers != NULL && strcmp(mp->mod_vers, version) == 0; fmd_module_rele(mp); } return (rv); } static int fmd_scheme_fmd_replaced(nvlist_t *nvl) { char *name, *version; fmd_module_t *mp; int rv = 1; if (nvlist_lookup_string(nvl, FM_FMRI_FMD_NAME, &name) != 0 || nvlist_lookup_string(nvl, FM_FMRI_FMD_VERSION, &version) != 0) return (fmd_fmri_set_errno(EINVAL)); if (!fmd.d_loaded) return (FMD_OBJ_STATE_UNKNOWN); if ((mp = fmd_modhash_lookup(fmd.d_mod_hash, name)) != NULL) { rv = mp->mod_vers != NULL && strcmp(mp->mod_vers, version) == 0; fmd_module_rele(mp); } return (rv ? FMD_OBJ_STATE_STILL_PRESENT : FMD_OBJ_STATE_REPLACED); } static int fmd_scheme_fmd_service_state(nvlist_t *nvl) { char *name; fmd_module_t *mp; int rv = 1; if (nvlist_lookup_string(nvl, FM_FMRI_FMD_NAME, &name) != 0) return (fmd_fmri_set_errno(EINVAL)); if (!fmd.d_loaded) return (FMD_SERVICE_STATE_UNKNOWN); if ((mp = fmd_modhash_lookup(fmd.d_mod_hash, name)) != NULL) { rv = mp->mod_error != 0; fmd_module_rele(mp); } return (rv ? FMD_SERVICE_STATE_UNUSABLE : FMD_SERVICE_STATE_OK); } static int fmd_scheme_fmd_unusable(nvlist_t *nvl) { char *name; fmd_module_t *mp; int rv = 1; if (nvlist_lookup_string(nvl, FM_FMRI_FMD_NAME, &name) != 0) return (fmd_fmri_set_errno(EINVAL)); if (!fmd.d_loaded) return (0); if ((mp = fmd_modhash_lookup(fmd.d_mod_hash, name)) != NULL) { rv = mp->mod_error != 0; fmd_module_rele(mp); } return (rv); } static nvlist_t * fmd_scheme_notranslate(nvlist_t *fmri, nvlist_t *auth __unused) { (void) nvlist_xdup(fmri, &fmri, &fmd.d_nva); return (fmri); } static ssize_t fmd_scheme_notsup_nvl2str(nvlist_t *fmri __unused, char *arg1 __unused, size_t arg2 __unused) { return (fmd_set_errno(EFMD_FMRI_NOTSUP)); } static int fmd_scheme_notsup(nvlist_t *fmri __unused) { return (fmd_set_errno(EFMD_FMRI_NOTSUP)); } static int fmd_scheme_notsup2(nvlist_t *fmri1 __unused, nvlist_t *fmri2 __unused) { return (fmd_set_errno(EFMD_FMRI_NOTSUP)); } static void fmd_scheme_vnop(void) { } static int fmd_scheme_nop(void) { return (0); } /* * Default values for the scheme ops. If a scheme function is not defined in * the module, then this operation is implemented using the default function. */ static const fmd_scheme_ops_t _fmd_scheme_default_ops = { .sop_init = fmd_scheme_nop, .sop_fini = fmd_scheme_vnop, .sop_nvl2str = fmd_scheme_notsup_nvl2str, .sop_expand = fmd_scheme_notsup, .sop_present = fmd_scheme_notsup, .sop_replaced = fmd_scheme_notsup, .sop_service_state = fmd_scheme_notsup, .sop_unusable = fmd_scheme_notsup, .sop_contains = fmd_scheme_notsup2, .sop_translate = fmd_scheme_notranslate }; static const fmd_scheme_ops_t _fmd_scheme_builtin_ops = { .sop_init = fmd_scheme_nop, .sop_fini = fmd_scheme_vnop, .sop_nvl2str = fmd_scheme_fmd_nvl2str, .sop_expand = fmd_scheme_notsup, .sop_present = fmd_scheme_fmd_present, .sop_replaced = fmd_scheme_fmd_replaced, .sop_service_state = fmd_scheme_fmd_service_state, .sop_unusable = fmd_scheme_fmd_unusable, .sop_contains = fmd_scheme_notsup2, .sop_translate = fmd_scheme_notranslate }; /* * Scheme ops descriptions. These names and offsets are used by the function * fmd_scheme_rtld_init(), defined below, to load up a fmd_scheme_ops_t. */ static const fmd_scheme_opd_t _fmd_scheme_ops[] = { { "fmd_fmri_init", offsetof(fmd_scheme_ops_t, sop_init) }, { "fmd_fmri_fini", offsetof(fmd_scheme_ops_t, sop_fini) }, { "fmd_fmri_nvl2str", offsetof(fmd_scheme_ops_t, sop_nvl2str) }, { "fmd_fmri_expand", offsetof(fmd_scheme_ops_t, sop_expand) }, { "fmd_fmri_present", offsetof(fmd_scheme_ops_t, sop_present) }, { "fmd_fmri_replaced", offsetof(fmd_scheme_ops_t, sop_replaced) }, { "fmd_fmri_service_state", offsetof(fmd_scheme_ops_t, sop_service_state) }, { "fmd_fmri_unusable", offsetof(fmd_scheme_ops_t, sop_unusable) }, { "fmd_fmri_contains", offsetof(fmd_scheme_ops_t, sop_contains) }, { "fmd_fmri_translate", offsetof(fmd_scheme_ops_t, sop_translate) }, { NULL, 0 } }; static fmd_scheme_t * fmd_scheme_create(const char *name) { fmd_scheme_t *sp = fmd_alloc(sizeof (fmd_scheme_t), FMD_SLEEP); (void) pthread_mutex_init(&sp->sch_lock, NULL); (void) pthread_cond_init(&sp->sch_cv, NULL); (void) pthread_mutex_init(&sp->sch_opslock, NULL); sp->sch_next = NULL; sp->sch_name = fmd_strdup(name, FMD_SLEEP); sp->sch_dlp = NULL; sp->sch_refs = 1; sp->sch_loaded = 0; sp->sch_ops = _fmd_scheme_default_ops; return (sp); } static void fmd_scheme_destroy(fmd_scheme_t *sp) { ASSERT(MUTEX_HELD(&sp->sch_lock)); ASSERT(sp->sch_refs == 0); if (sp->sch_dlp != NULL) { TRACE((FMD_DBG_FMRI, "dlclose scheme %s", sp->sch_name)); if (sp->sch_ops.sop_fini != NULL) sp->sch_ops.sop_fini(); (void) dlclose(sp->sch_dlp); } fmd_strfree(sp->sch_name); fmd_free(sp, sizeof (fmd_scheme_t)); } fmd_scheme_hash_t * fmd_scheme_hash_create(const char *rootdir, const char *dirpath) { fmd_scheme_hash_t *shp; char path[PATH_MAX]; fmd_scheme_t *sp; shp = fmd_alloc(sizeof (fmd_scheme_hash_t), FMD_SLEEP); (void) snprintf(path, sizeof (path), "%s/%s", rootdir, dirpath); shp->sch_dirpath = fmd_strdup(path, FMD_SLEEP); (void) pthread_rwlock_init(&shp->sch_rwlock, NULL); shp->sch_hashlen = fmd.d_str_buckets; shp->sch_hash = fmd_zalloc(sizeof (fmd_scheme_t *) * shp->sch_hashlen, FMD_SLEEP); sp = fmd_scheme_create(FM_FMRI_SCHEME_FMD); sp->sch_ops = _fmd_scheme_builtin_ops; sp->sch_loaded = FMD_B_TRUE; shp->sch_hash[fmd_strhash(sp->sch_name) % shp->sch_hashlen] = sp; return (shp); } void fmd_scheme_hash_destroy(fmd_scheme_hash_t *shp) { fmd_scheme_t *sp, *np; uint_t i; for (i = 0; i < shp->sch_hashlen; i++) { for (sp = shp->sch_hash[i]; sp != NULL; sp = np) { np = sp->sch_next; sp->sch_next = NULL; fmd_scheme_hash_release(shp, sp); } } fmd_free(shp->sch_hash, sizeof (fmd_scheme_t *) * shp->sch_hashlen); fmd_strfree(shp->sch_dirpath); fmd_free(shp, sizeof (fmd_scheme_hash_t)); } void fmd_scheme_hash_trygc(fmd_scheme_hash_t *shp) { fmd_scheme_t *sp, *np; uint_t i; if (shp == NULL || pthread_rwlock_trywrlock(&shp->sch_rwlock) != 0) return; /* failed to acquire lock: just skip garbage collect */ for (i = 0; i < shp->sch_hashlen; i++) { for (sp = shp->sch_hash[i]; sp != NULL; sp = np) { np = sp->sch_next; sp->sch_next = NULL; fmd_scheme_hash_release(shp, sp); } } bzero(shp->sch_hash, sizeof (fmd_scheme_t *) * shp->sch_hashlen); (void) pthread_rwlock_unlock(&shp->sch_rwlock); } static int fmd_scheme_rtld_init(fmd_scheme_t *sp) { const fmd_scheme_opd_t *opd; void *p; for (opd = _fmd_scheme_ops; opd->opd_name != NULL; opd++) { if ((p = dlsym(sp->sch_dlp, opd->opd_name)) != NULL) *(void **)((uintptr_t)&sp->sch_ops + opd->opd_off) = p; } return (0); } fmd_scheme_t * fmd_scheme_hash_xlookup(fmd_scheme_hash_t *shp, const char *name, uint_t h) { fmd_scheme_t *sp; ASSERT(RW_LOCK_HELD(&shp->sch_rwlock)); for (sp = shp->sch_hash[h]; sp != NULL; sp = sp->sch_next) { if (strcmp(sp->sch_name, name) == 0) break; } return (sp); } /* * Lookup a scheme module by name and return with a reference placed on it. We * use the scheme hash to cache "negative" entries (e.g. missing modules) as * well so this function always returns successfully with a non-NULL scheme. * The caller is responsible for applying fmd_scheme_hash_release() afterward. */ fmd_scheme_t * fmd_scheme_hash_lookup(fmd_scheme_hash_t *shp, const char *name) { fmd_scheme_t *sp, *nsp = NULL; uint_t h; /* * Grab the hash lock as reader and look for the appropriate scheme. * If the scheme isn't yet loaded, allocate a new scheme and grab the * hash lock as writer to insert it (after checking again for it). */ (void) pthread_rwlock_rdlock(&shp->sch_rwlock); h = fmd_strhash(name) % shp->sch_hashlen; if ((sp = fmd_scheme_hash_xlookup(shp, name, h)) == NULL) { (void) pthread_rwlock_unlock(&shp->sch_rwlock); nsp = fmd_scheme_create(name); (void) pthread_rwlock_wrlock(&shp->sch_rwlock); if ((sp = fmd_scheme_hash_xlookup(shp, name, h)) == NULL) { nsp->sch_next = shp->sch_hash[h]; shp->sch_hash[h] = sp = nsp; } else { fmd_scheme_hash_release(shp, nsp); nsp = NULL; } } /* * Grab the scheme lock so it can't disappear and then drop the hash * lock so that other lookups in the scheme hash can proceed. */ (void) pthread_mutex_lock(&sp->sch_lock); (void) pthread_rwlock_unlock(&shp->sch_rwlock); /* * If we created the scheme, compute its path and try to load it. If * we found an existing scheme, wait until its loaded bit is set. Once * we're done with either operation, increment sch_refs and return. */ if (nsp != NULL) { char path[PATH_MAX]; (void) snprintf(path, sizeof (path), "%s/%s.so", shp->sch_dirpath, sp->sch_name); TRACE((FMD_DBG_FMRI, "dlopen scheme %s", sp->sch_name)); sp->sch_dlp = dlopen(path, RTLD_LOCAL | RTLD_NOW); if (sp->sch_dlp == NULL) { fmd_error(EFMD_FMRI_SCHEME, "failed to load fmri scheme %s: %s\n", path, dlerror()); } else if (fmd_scheme_rtld_init(sp) != 0 || sp->sch_ops.sop_init() != 0) { fmd_error(EFMD_FMRI_SCHEME, "failed to initialize fmri scheme %s", path); (void) dlclose(sp->sch_dlp); sp->sch_dlp = NULL; sp->sch_ops = _fmd_scheme_default_ops; } sp->sch_loaded = FMD_B_TRUE; /* set regardless of success */ sp->sch_refs++; ASSERT(sp->sch_refs != 0); (void) pthread_cond_broadcast(&sp->sch_cv); (void) pthread_mutex_unlock(&sp->sch_lock); } else { while (!sp->sch_loaded) (void) pthread_cond_wait(&sp->sch_cv, &sp->sch_lock); sp->sch_refs++; ASSERT(sp->sch_refs != 0); (void) pthread_mutex_unlock(&sp->sch_lock); } return (sp); } /* * Release the hold on a scheme obtained using fmd_scheme_hash_lookup(). * We take 'shp' for symmetry and in case we need to use it in future work. */ /*ARGSUSED*/ void fmd_scheme_hash_release(fmd_scheme_hash_t *shp, fmd_scheme_t *sp) { (void) pthread_mutex_lock(&sp->sch_lock); ASSERT(sp->sch_refs != 0); if (--sp->sch_refs == 0) fmd_scheme_destroy(sp); else (void) pthread_mutex_unlock(&sp->sch_lock); }