/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include const char FMD_PROP_SUBSCRIPTIONS[] = "_subscriptions"; const char FMD_PROP_DICTIONARIES[] = "_dictionaries"; /* * The property formals defined in _fmd_conf_defv[] are added to every config * dictionary that is created. Here we define several special FMD_PROP_* * properties that are used to implement the config file keyword actions, as * well as properties that should be inherited by fmd_conf_t's from fmd.d_conf. */ static const fmd_conf_formal_t _fmd_conf_defv[] = { { FMD_PROP_SUBSCRIPTIONS, &fmd_conf_list, "" }, { FMD_PROP_DICTIONARIES, &fmd_conf_list, "" }, { "fmd.isaname", &fmd_conf_parent, "isaname" }, { "fmd.machine", &fmd_conf_parent, "machine" }, { "fmd.platform", &fmd_conf_parent, "platform" }, { "fmd.rootdir", &fmd_conf_parent, "rootdir" }, }; static const int _fmd_conf_defc = sizeof (_fmd_conf_defv) / sizeof (_fmd_conf_defv[0]); static int set_bool(fmd_conf_param_t *pp, const char *s) { if (strcasecmp(s, "true") == 0) pp->cp_value.cpv_num = 1; else if (strcasecmp(s, "false") == 0) pp->cp_value.cpv_num = 0; else return (fmd_set_errno(EFMD_CONF_INVAL)); return (0); } static void get_bool(const fmd_conf_param_t *pp, void *ptr) { *((int *)ptr) = (int)pp->cp_value.cpv_num; } static int set_i32(fmd_conf_param_t *pp, const char *s) { int64_t val; char *end; errno = 0; val = strtoll(s, &end, 0); if (errno == EOVERFLOW || val < INT32_MIN || val > INT32_MAX) return (fmd_set_errno(EFMD_CONF_OVERFLOW)); if (errno != 0 || end == s || *end != '\0') return (fmd_set_errno(EFMD_CONF_INVAL)); pp->cp_value.cpv_num = val; return (0); } static void get_i32(const fmd_conf_param_t *pp, void *ptr) { *((int32_t *)ptr) = (int32_t)pp->cp_value.cpv_num; } static int set_ui32(fmd_conf_param_t *pp, const char *s) { uint64_t val; char *end; errno = 0; val = strtoull(s, &end, 0); if (errno == EOVERFLOW || val > UINT32_MAX) return (fmd_set_errno(EFMD_CONF_OVERFLOW)); if (errno != 0 || end == s || *end != '\0') return (fmd_set_errno(EFMD_CONF_INVAL)); pp->cp_value.cpv_num = val; return (0); } static void get_ui32(const fmd_conf_param_t *pp, void *ptr) { *((uint32_t *)ptr) = (uint32_t)pp->cp_value.cpv_num; } static int set_i64(fmd_conf_param_t *pp, const char *s) { int64_t val; char *end; errno = 0; val = strtoll(s, &end, 0); if (errno == EOVERFLOW) return (fmd_set_errno(EFMD_CONF_OVERFLOW)); if (errno != 0 || end == s || *end != '\0') return (fmd_set_errno(EFMD_CONF_INVAL)); pp->cp_value.cpv_num = val; return (0); } static void get_i64(const fmd_conf_param_t *pp, void *ptr) { *((int64_t *)ptr) = (int64_t)pp->cp_value.cpv_num; } static int set_ui64(fmd_conf_param_t *pp, const char *s) { uint64_t val; char *end; errno = 0; val = strtoull(s, &end, 0); if (errno == EOVERFLOW) return (fmd_set_errno(EFMD_CONF_OVERFLOW)); if (errno != 0 || end == s || *end != '\0') return (fmd_set_errno(EFMD_CONF_INVAL)); pp->cp_value.cpv_num = val; return (0); } static void get_ui64(const fmd_conf_param_t *pp, void *ptr) { *((uint64_t *)ptr) = pp->cp_value.cpv_num; } static int set_str(fmd_conf_param_t *pp, const char *s) { fmd_strfree(pp->cp_value.cpv_str); pp->cp_value.cpv_str = fmd_strdup(s, FMD_SLEEP); return (0); } static void get_str(const fmd_conf_param_t *pp, void *ptr) { *((const char **)ptr) = pp->cp_value.cpv_str; } static void free_str(fmd_conf_param_t *pp) { fmd_strfree(pp->cp_value.cpv_str); pp->cp_value.cpv_str = NULL; } static int set_path(fmd_conf_param_t *pp, const char *value) { size_t len = strlen(value); char *s = alloca(len + 1); char **patv = alloca(sizeof (char *) * len / 2); int patc = 0; static const char *const percent_sign = "%"; char *p, *q; int c, i; static const struct fmd_conf_token { char tok_tag; const char *const *tok_val; } tokens[] = { { 'i', &fmd.d_platform }, { 'm', &fmd.d_machine }, { 'p', &fmd.d_isaname }, { 'r', &fmd.d_rootdir }, { '%', &percent_sign }, { 0, NULL } }; const struct fmd_conf_token *tok; fmd_conf_path_t *pap; pp->cp_formal->cf_ops->co_free(pp); (void) strcpy(s, value); for (p = strtok_r(s, ":", &q); p != NULL; p = strtok_r(NULL, ":", &q)) patv[patc++] = p; pap = fmd_alloc(sizeof (fmd_conf_path_t), FMD_SLEEP); pap->cpa_argv = fmd_alloc(sizeof (char *) * patc, FMD_SLEEP); pap->cpa_argc = patc; for (i = 0; i < patc; i++) { for (len = 0, p = patv[i]; (c = *p) != '\0'; p++, len++) { if (c != '%' || (c = p[1]) == '\0') continue; for (tok = tokens; tok->tok_tag != 0; tok++) { if (c == tok->tok_tag) { len += strlen(*tok->tok_val) - 1; p++; break; } } } pap->cpa_argv[i] = q = fmd_alloc(len + 1, FMD_SLEEP); q[len] = '\0'; for (p = patv[i]; (c = *p) != '\0'; p++) { if (c != '%' || (c = p[1]) == '\0') { *q++ = c; continue; } for (tok = tokens; tok->tok_tag != 0; tok++) { if (c == tok->tok_tag) { (void) strcpy(q, *tok->tok_val); q += strlen(q); p++; break; } } if (tok->tok_tag == 0) *q++ = c; } } pp->cp_value.cpv_ptr = pap; return (0); } static int set_lst(fmd_conf_param_t *pp, const char *value) { fmd_conf_path_t *old; old = pp->cp_value.cpv_ptr; pp->cp_value.cpv_ptr = NULL; if (set_path(pp, value) != 0) { pp->cp_value.cpv_ptr = old; return (-1); /* errno is set for us */ } if (old != NULL) { fmd_conf_path_t *new = pp->cp_value.cpv_ptr; int i, totc = old->cpa_argc + new->cpa_argc; int new_argc = new->cpa_argc; const char **new_argv = new->cpa_argv; new->cpa_argc = 0; new->cpa_argv = fmd_alloc(sizeof (char *) * totc, FMD_SLEEP); for (i = 0; i < old->cpa_argc; i++) new->cpa_argv[new->cpa_argc++] = old->cpa_argv[i]; for (i = 0; i < new_argc; i++) new->cpa_argv[new->cpa_argc++] = new_argv[i]; ASSERT(new->cpa_argc == totc); fmd_free(new_argv, sizeof (char *) * new_argc); fmd_free(old->cpa_argv, sizeof (char *) * old->cpa_argc); fmd_free(old, sizeof (fmd_conf_path_t)); } return (0); } static int del_lst(fmd_conf_param_t *pp, const char *value) { fmd_conf_path_t *pap = pp->cp_value.cpv_ptr; const char **new_argv; int i, new_argc; for (i = 0; i < pap->cpa_argc; i++) { if (strcmp(pap->cpa_argv[i], value) == 0) break; } if (i == pap->cpa_argc) return (fmd_set_errno(ENOENT)); fmd_strfree((char *)pap->cpa_argv[i]); pap->cpa_argv[i] = NULL; new_argc = 0; new_argv = fmd_alloc(sizeof (char *) * (pap->cpa_argc - 1), FMD_SLEEP); for (i = 0; i < pap->cpa_argc; i++) { if (pap->cpa_argv[i] != NULL) new_argv[new_argc++] = pap->cpa_argv[i]; } fmd_free(pap->cpa_argv, sizeof (char *) * pap->cpa_argc); pap->cpa_argv = new_argv; pap->cpa_argc = new_argc; return (0); } static void get_path(const fmd_conf_param_t *pp, void *ptr) { *((fmd_conf_path_t **)ptr) = (fmd_conf_path_t *)pp->cp_value.cpv_ptr; } static void free_path(fmd_conf_param_t *pp) { fmd_conf_path_t *pap = pp->cp_value.cpv_ptr; int i; if (pap == NULL) return; /* no value was ever set */ for (i = 0; i < pap->cpa_argc; i++) fmd_strfree((char *)pap->cpa_argv[i]); fmd_free(pap->cpa_argv, sizeof (char *) * pap->cpa_argc); fmd_free(pap, sizeof (fmd_conf_path_t)); pp->cp_value.cpv_ptr = NULL; } static int set_time(fmd_conf_param_t *pp, const char *s) { static const struct { const char *name; hrtime_t mul; } suffix[] = { { "ns", NANOSEC / NANOSEC }, { "nsec", NANOSEC / NANOSEC }, { "us", NANOSEC / MICROSEC }, { "usec", NANOSEC / MICROSEC }, { "ms", NANOSEC / MILLISEC }, { "msec", NANOSEC / MILLISEC }, { "s", NANOSEC / SEC }, { "sec", NANOSEC / SEC }, { "m", NANOSEC * (hrtime_t)60 }, { "min", NANOSEC * (hrtime_t)60 }, { "h", NANOSEC * (hrtime_t)(60 * 60) }, { "hour", NANOSEC * (hrtime_t)(60 * 60) }, { "d", NANOSEC * (hrtime_t)(24 * 60 * 60) }, { "day", NANOSEC * (hrtime_t)(24 * 60 * 60) }, { "hz", 0 }, { NULL } }; hrtime_t val, mul = 1; char *end; int i; errno = 0; val = strtoull(s, &end, 0); if (errno == EOVERFLOW) return (fmd_set_errno(EFMD_CONF_OVERFLOW)); if (errno != 0 || end == s) return (fmd_set_errno(EFMD_CONF_INVAL)); for (i = 0; suffix[i].name != NULL; i++) { if (strcasecmp(suffix[i].name, end) == 0) { mul = suffix[i].mul; break; } } if (suffix[i].name == NULL && *end != '\0') return (fmd_set_errno(EFMD_CONF_INVAL)); if (mul == 0) { if (val != 0) val = NANOSEC / val; /* compute val as value per sec */ } else val *= mul; pp->cp_value.cpv_num = val; return (0); } static int set_size(fmd_conf_param_t *pp, const char *s) { size_t len = strlen(s); uint64_t val, mul = 1; char *end; switch (s[len - 1]) { case 't': case 'T': mul *= 1024; /*FALLTHRU*/ case 'g': case 'G': mul *= 1024; /*FALLTHRU*/ case 'm': case 'M': mul *= 1024; /*FALLTHRU*/ case 'k': case 'K': mul *= 1024; /*FALLTHRU*/ default: break; } errno = 0; val = strtoull(s, &end, 0) * mul; if (errno == EOVERFLOW) return (fmd_set_errno(EFMD_CONF_OVERFLOW)); if ((mul != 1 && end != &s[len - 1]) || (mul == 1 && *end != '\0') || errno != 0) return (fmd_set_errno(EFMD_CONF_INVAL)); pp->cp_value.cpv_num = val; return (0); } static int set_sig(fmd_conf_param_t *pp, const char *s) { int sig; if (strncasecmp(s, "SIG", 3) == 0) s += 3; /* be friendlier than strsig() and permit the prefix */ if (str2sig(s, &sig) != 0) return (fmd_set_errno(EFMD_CONF_INVAL)); pp->cp_value.cpv_num = sig; return (0); } static void get_par(const fmd_conf_param_t *pp, void *ptr) { if (fmd_conf_getprop(fmd.d_conf, pp->cp_formal->cf_default, ptr) != 0) { fmd_panic("fmd.d_conf does not define '%s' (inherited as %s)\n", (char *)pp->cp_formal->cf_default, pp->cp_formal->cf_name); } } /*ARGSUSED*/ static int set_par(fmd_conf_param_t *pp, const char *s) { return (fmd_set_errno(EFMD_CONF_RDONLY)); } /* * Utility routine for callers who define custom ops where a list of string * tokens are translated into a bitmask. 'cmp' should be set to point to an * array of fmd_conf_mode_t's where the final element has cm_name == NULL. */ int fmd_conf_mode_set(const fmd_conf_mode_t *cmp, fmd_conf_param_t *pp, const char *value) { char *p, *q, *s = fmd_strdup(value, FMD_SLEEP); size_t len = value ? strlen(value) + 1 : 0; uint_t mode = 0; if (s == NULL) { pp->cp_value.cpv_num = 0; return (0); } for (p = strtok_r(s, ",", &q); p != NULL; p = strtok_r(NULL, ",", &q)) { for (; cmp->cm_name != NULL; cmp++) { if (strcmp(cmp->cm_name, p) == 0) { mode |= cmp->cm_bits; break; } } if (cmp->cm_name == NULL) { fmd_free(s, len); return (fmd_set_errno(EFMD_CONF_INVAL)); } } pp->cp_value.cpv_num = mode; fmd_free(s, len); return (0); } void fmd_conf_mode_get(const fmd_conf_param_t *pp, void *ptr) { *((uint_t *)ptr) = (uint_t)pp->cp_value.cpv_num; } /*ARGSUSED*/ int fmd_conf_notsup(fmd_conf_param_t *pp, const char *value) { return (fmd_set_errno(ENOTSUP)); } /*ARGSUSED*/ void fmd_conf_nop(fmd_conf_param_t *pp) { /* no free required for integer-type parameters */ } #define CONF_DEFINE(name, a, b, c, d) \ const fmd_conf_ops_t name = { a, b, c, d } CONF_DEFINE(fmd_conf_bool, set_bool, get_bool, fmd_conf_notsup, fmd_conf_nop); CONF_DEFINE(fmd_conf_int32, set_i32, get_i32, fmd_conf_notsup, fmd_conf_nop); CONF_DEFINE(fmd_conf_uint32, set_ui32, get_ui32, fmd_conf_notsup, fmd_conf_nop); CONF_DEFINE(fmd_conf_int64, set_i64, get_i64, fmd_conf_notsup, fmd_conf_nop); CONF_DEFINE(fmd_conf_uint64, set_ui64, get_ui64, fmd_conf_notsup, fmd_conf_nop); CONF_DEFINE(fmd_conf_string, set_str, get_str, fmd_conf_notsup, free_str); CONF_DEFINE(fmd_conf_path, set_path, get_path, fmd_conf_notsup, free_path); CONF_DEFINE(fmd_conf_list, set_lst, get_path, del_lst, free_path); CONF_DEFINE(fmd_conf_time, set_time, get_ui64, fmd_conf_notsup, fmd_conf_nop); CONF_DEFINE(fmd_conf_size, set_size, get_ui64, fmd_conf_notsup, fmd_conf_nop); CONF_DEFINE(fmd_conf_signal, set_sig, get_i32, fmd_conf_notsup, fmd_conf_nop); CONF_DEFINE(fmd_conf_parent, set_par, get_par, fmd_conf_notsup, fmd_conf_nop); static char * fmd_conf_skipstr(char *s) { int c; while ((c = *s) != '\0') { if (c == '\\') s++; else if (c == '"') break; s++; } return (s); } static char * fmd_conf_skipnws(char *s) { while (strchr("\f\n\r\t\v ", *s) == NULL) s++; return (s); } static int fmd_conf_tokenize(char *s, char *tokv[]) { int c, tokc = 0; while ((c = *s) != '\0') { switch (c) { case '"': tokv[tokc] = s + 1; s = fmd_conf_skipstr(s + 1); *s++ = '\0'; (void) fmd_stresc2chr(tokv[tokc++]); continue; case '\f': case '\n': case '\r': case '\t': case '\v': case ' ': s++; continue; default: tokv[tokc++] = s; s = fmd_conf_skipnws(s); *s++ = '\0'; } } return (tokc); } static int fmd_conf_exec_setprop(fmd_conf_t *cfp, int argc, char *argv[]) { if (argc != 2) return (fmd_set_errno(EFMD_CONF_USAGE)); return (fmd_conf_setprop(cfp, argv[0], argv[1])); } static int fmd_conf_exec_subscribe(fmd_conf_t *cfp, int argc, char *argv[]) { if (argc != 1) return (fmd_set_errno(EFMD_CONF_USAGE)); return (fmd_conf_setprop(cfp, FMD_PROP_SUBSCRIPTIONS, argv[0])); } static int fmd_conf_exec_dictionary(fmd_conf_t *cfp, int argc, char *argv[]) { if (argc != 1) return (fmd_set_errno(EFMD_CONF_USAGE)); return (fmd_conf_setprop(cfp, FMD_PROP_DICTIONARIES, argv[0])); } static int fmd_conf_parse(fmd_conf_t *cfp, const char *file) { static const fmd_conf_verb_t verbs[] = { { "setprop", fmd_conf_exec_setprop }, { "subscribe", fmd_conf_exec_subscribe }, { "dictionary", fmd_conf_exec_dictionary }, { NULL, NULL } }; int line, errs = 0; char buf[BUFSIZ]; FILE *fp; if ((fp = fopen(file, "r")) == NULL) { fmd_error(EFMD_CONF_OPEN, "failed to open %s: %s\n", file, fmd_strerror(errno)); return (fmd_set_errno(EFMD_CONF_OPEN)); } for (line = 1; fgets(buf, sizeof (buf), fp) != NULL; line++) { char *tokv[sizeof (buf) / 2 + 1]; int tokc = fmd_conf_tokenize(buf, tokv); const fmd_conf_verb_t *vp; if (tokc == 0 || tokv[0][0] == '#') continue; /* skip blank lines and comment lines */ for (vp = verbs; vp->cv_name != NULL; vp++) { if (strcmp(tokv[0], vp->cv_name) == 0) break; } if (vp->cv_name == NULL) { fmd_error(EFMD_CONF_KEYWORD, "\"%s\", line %d: " "invalid configuration file keyword: %s\n", file, line, tokv[0]); errs++; continue; } if (vp->cv_exec(cfp, tokc - 1, tokv + 1) != 0) { fmd_error(errno, "\"%s\", line %d", file, line); errs++; continue; } } if (ferror(fp) != 0 || fclose(fp) != 0) return (fmd_set_errno(EFMD_CONF_IO)); if (errs != 0) return (fmd_set_errno(EFMD_CONF_ERRS)); return (0); } static void fmd_conf_fill(fmd_conf_t *cfp, fmd_conf_param_t *ppbuf, int argc, const fmd_conf_formal_t *argv, int checkid) { int i; for (i = 0; i < argc; i++, argv++) { fmd_conf_param_t *op, *pp = ppbuf + i; const char *name = argv->cf_name; ulong_t h = fmd_strhash(name) % cfp->cf_parhashlen; if (fmd_strbadid(name, checkid) != NULL) { fmd_error(EFMD_CONF_PROPNAME, "ignoring invalid formal " "property %s\n", name); continue; } for (op = cfp->cf_parhash[h]; op != NULL; op = op->cp_next) { if (strcmp(op->cp_formal->cf_name, name) == 0) { fmd_error(EFMD_CONF_PROPDUP, "ignoring " "duplicate formal property %s\n", name); break; } } if (op != NULL) continue; pp->cp_formal = argv; pp->cp_next = cfp->cf_parhash[h]; cfp->cf_parhash[h] = pp; if (argv->cf_default && argv->cf_ops != &fmd_conf_parent && fmd_conf_setprop(cfp, name, argv->cf_default) != 0) { fmd_error(EFMD_CONF_DEFAULT, "ignoring invalid default " "<%s> for property %s: %s\n", argv->cf_default, name, fmd_strerror(errno)); } } } fmd_conf_t * fmd_conf_open(const char *file, int argc, const fmd_conf_formal_t *argv) { fmd_conf_t *cfp = fmd_alloc(sizeof (fmd_conf_t), FMD_SLEEP); (void) pthread_rwlock_init(&cfp->cf_lock, NULL); cfp->cf_argv = argv; cfp->cf_argc = argc; cfp->cf_params = fmd_zalloc( sizeof (fmd_conf_param_t) * (_fmd_conf_defc + argc), FMD_SLEEP); cfp->cf_parhashlen = fmd.d_str_buckets; cfp->cf_parhash = fmd_zalloc( sizeof (fmd_conf_param_t *) * cfp->cf_parhashlen, FMD_SLEEP); fmd_conf_fill(cfp, cfp->cf_params, _fmd_conf_defc, _fmd_conf_defv, 0); fmd_conf_fill(cfp, cfp->cf_params + _fmd_conf_defc, argc, argv, 1); if (file != NULL && fmd_conf_parse(cfp, file) != 0) { fmd_conf_close(cfp); return (NULL); } return (cfp); } void fmd_conf_merge(fmd_conf_t *cfp, const char *file) { (void) fmd_conf_parse(cfp, file); } void fmd_conf_close(fmd_conf_t *cfp) { fmd_conf_param_t *pp = cfp->cf_params; int i, nparams = _fmd_conf_defc + cfp->cf_argc; fmd_free(cfp->cf_parhash, sizeof (fmd_conf_param_t *) * cfp->cf_parhashlen); for (i = 0; i < nparams; i++, pp++) { if (pp->cp_formal != NULL) pp->cp_formal->cf_ops->co_free(pp); } fmd_free(cfp->cf_params, sizeof (fmd_conf_param_t) * nparams); fmd_free(cfp, sizeof (fmd_conf_t)); } static fmd_conf_param_t * fmd_conf_getparam(fmd_conf_t *cfp, const char *name) { ulong_t h = fmd_strhash(name) % cfp->cf_parhashlen; fmd_conf_param_t *pp = cfp->cf_parhash[h]; ASSERT(RW_LOCK_HELD(&cfp->cf_lock)); for (; pp != NULL; pp = pp->cp_next) { if (strcmp(name, pp->cp_formal->cf_name) == 0) return (pp); } return (NULL); } const fmd_conf_ops_t * fmd_conf_gettype(fmd_conf_t *cfp, const char *name) { const fmd_conf_param_t *pp; const fmd_conf_ops_t *ops = NULL; (void) pthread_rwlock_rdlock(&cfp->cf_lock); if ((pp = fmd_conf_getparam(cfp, name)) != NULL) { if ((ops = pp->cp_formal->cf_ops) == &fmd_conf_parent) { ops = fmd_conf_gettype(fmd.d_conf, pp->cp_formal->cf_default); } } else (void) fmd_set_errno(EFMD_CONF_NOPROP); (void) pthread_rwlock_unlock(&cfp->cf_lock); return (ops); } int fmd_conf_getprop(fmd_conf_t *cfp, const char *name, void *data) { const fmd_conf_param_t *pp; int err = 0; (void) pthread_rwlock_rdlock(&cfp->cf_lock); if ((pp = fmd_conf_getparam(cfp, name)) != NULL) pp->cp_formal->cf_ops->co_get(pp, data); else err = fmd_set_errno(EFMD_CONF_NOPROP); (void) pthread_rwlock_unlock(&cfp->cf_lock); return (err); } int fmd_conf_setprop(fmd_conf_t *cfp, const char *name, const char *value) { fmd_conf_param_t *pp; int err; (void) pthread_rwlock_wrlock(&cfp->cf_lock); if ((pp = fmd_conf_getparam(cfp, name)) != NULL) err = pp->cp_formal->cf_ops->co_set(pp, value); else err = fmd_set_errno(EFMD_CONF_NOPROP); (void) pthread_rwlock_unlock(&cfp->cf_lock); return (err); } int fmd_conf_delprop(fmd_conf_t *cfp, const char *name, const char *value) { fmd_conf_param_t *pp; int err; (void) pthread_rwlock_wrlock(&cfp->cf_lock); if ((pp = fmd_conf_getparam(cfp, name)) != NULL) err = pp->cp_formal->cf_ops->co_del(pp, value); else err = fmd_set_errno(EFMD_CONF_NOPROP); (void) pthread_rwlock_unlock(&cfp->cf_lock); return (err); }