root/usr/src/cmd/fm/fmd/common/fmd_conf.c
/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <inttypes.h>
#include <alloca.h>
#include <strings.h>
#include <stdlib.h>
#include <stdio.h>

#include <fmd_conf.h>
#include <fmd_alloc.h>
#include <fmd_error.h>
#include <fmd_subr.h>
#include <fmd_string.h>
#include <fmd.h>

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_i32x(fmd_conf_param_t *pp, const char *s, int64_t min, int64_t max)
{
        int64_t val;
        char *end;

        errno = 0;
        val = strtoll(s, &end, 0);

        if (errno == EOVERFLOW || val < min || val > 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 int
set_i8(fmd_conf_param_t *pp, const char *s)
{
        return (set_i32x(pp, s, INT8_MIN, INT8_MAX));
}

static int
set_i16(fmd_conf_param_t *pp, const char *s)
{
        return (set_i32x(pp, s, INT16_MIN, INT16_MAX));
}

static int
set_i32(fmd_conf_param_t *pp, const char *s)
{
        return (set_i32x(pp, s, INT32_MIN, INT32_MAX));
}

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_ui32x(fmd_conf_param_t *pp, const char *s, uint64_t max)
{
        uint64_t val;
        char *end;

        errno = 0;
        val = strtoull(s, &end, 0);

        if (errno == EOVERFLOW || val > 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 int
set_ui8(fmd_conf_param_t *pp, const char *s)
{
        return (set_ui32x(pp, s, UINT8_MAX));
}

static int
set_ui16(fmd_conf_param_t *pp, const char *s)
{
        return (set_ui32x(pp, s, UINT16_MAX));
}

static int
set_ui32(fmd_conf_param_t *pp, const char *s)
{
        return (set_ui32x(pp, s, UINT32_MAX));
}

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 *cma,
    fmd_conf_param_t *pp, const char *value)
{
        const fmd_conf_mode_t *cmp;
        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 = cma; 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_int8, set_i8, get_i32, fmd_conf_notsup, fmd_conf_nop);
CONF_DEFINE(fmd_conf_uint8, set_ui8, get_ui32, fmd_conf_notsup, fmd_conf_nop);
CONF_DEFINE(fmd_conf_int16, set_i16, get_i32, fmd_conf_notsup, fmd_conf_nop);
CONF_DEFINE(fmd_conf_uint16, set_ui16, get_ui32, 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) {
                if (errno == EMFILE)
                        fmd_error(EFMD_EXIT, "failed to open %s: %s\n",
                            file, fmd_strerror(errno));
                else
                        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, uint_t flag)
{
        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_flag = flag;

        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);

        cfp->cf_defer = NULL;

        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_propagate(fmd_conf_t *src, fmd_conf_t *dst, const char *scope)
{
        size_t len = strlen(scope);
        fmd_conf_defer_t *cdp;

        (void) pthread_rwlock_rdlock(&src->cf_lock);

        for (cdp = src->cf_defer; cdp != NULL; cdp = cdp->cd_next) {
                if (len == (size_t)(strchr(cdp->cd_name, ':') - cdp->cd_name) &&
                    strncmp(cdp->cd_name, scope, len) == 0 && fmd_conf_setprop(
                    dst, cdp->cd_name + len + 1, cdp->cd_value) != 0) {
                        fmd_error(EFMD_CONF_DEFER,
                            "failed to apply deferred property %s to %s: %s\n",
                            cdp->cd_name, scope, fmd_strerror(errno));
                }
        }

        (void) pthread_rwlock_unlock(&src->cf_lock);
}

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_conf_defer_t *cdp, *ndp;

        for (cdp = cfp->cf_defer; cdp != NULL; cdp = ndp) {
                ndp = cdp->cd_next;
                fmd_strfree(cdp->cd_name);
                fmd_strfree(cdp->cd_value);
                fmd_free(cdp, sizeof (fmd_conf_defer_t));
        }

        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);
}

/*
 * String-friendly version of fmd_conf_getprop(): return the string as our
 * return value, and return NULL if the string is the empty string.
 */
const char *
fmd_conf_getnzstr(fmd_conf_t *cfp, const char *name)
{
        const fmd_conf_param_t *pp;
        char *s = NULL;

        (void) pthread_rwlock_rdlock(&cfp->cf_lock);

        if ((pp = fmd_conf_getparam(cfp, name)) != NULL) {
                ASSERT(pp->cp_formal->cf_ops == &fmd_conf_string);
                pp->cp_formal->cf_ops->co_get(pp, &s);
        } else
                (void) fmd_set_errno(EFMD_CONF_NOPROP);

        (void) pthread_rwlock_unlock(&cfp->cf_lock);

        if (s != NULL && s[0] == '\0') {
                (void) fmd_set_errno(EFMD_CONF_UNDEF);
                s = NULL;
        }

        return (s);
}

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);
}

static int
fmd_conf_setdefer(fmd_conf_t *cfp, const char *name, const char *value)
{
        fmd_conf_defer_t *cdp;

        if (!(cfp->cf_flag & FMD_CONF_DEFER))
                return (fmd_set_errno(EFMD_CONF_NODEFER));

        (void) pthread_rwlock_wrlock(&cfp->cf_lock);

        for (cdp = cfp->cf_defer; cdp != NULL; cdp = cdp->cd_next) {
                if (strcmp(name, cdp->cd_name) == 0) {
                        fmd_strfree(cdp->cd_value);
                        cdp->cd_value = fmd_strdup(value, FMD_SLEEP);
                        goto out;
                }
        }

        cdp = fmd_alloc(sizeof (fmd_conf_defer_t), FMD_SLEEP);

        cdp->cd_name = fmd_strdup(name, FMD_SLEEP);
        cdp->cd_value = fmd_strdup(value, FMD_SLEEP);
        cdp->cd_next = cfp->cf_defer;

        cfp->cf_defer = cdp;
out:
        (void) pthread_rwlock_unlock(&cfp->cf_lock);
        return (0);
}

int
fmd_conf_setprop(fmd_conf_t *cfp, const char *name, const char *value)
{
        fmd_conf_param_t *pp;
        int err;

        if (strchr(name, ':') != NULL)
                return (fmd_conf_setdefer(cfp, name, value));

        (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);
}