/*
  Copyright (c) 2008-2012 Red Hat, Inc. <http://www.redhat.com>
  This file is part of GlusterFS.

  This file is licensed to you under your choice of the GNU Lesser
  General Public License, version 3 or any later version (LGPLv3 or
  later), or the GNU General Public License, version 2 (GPLv2), in all
  cases as published by the Free Software Foundation.
*/

#include <fnmatch.h>

#include "glusterfs/xlator.h"
#include "glusterfs/defaults.h"
#include "glusterfs/libglusterfs-messages.h"

#define GF_OPTION_LIST_EMPTY(_opt) (_opt->value[0] == NULL)

static int
xlator_option_validate_path(xlator_t *xl, const char *key, const char *value,
                            volume_option_t *opt, char **op_errstr)
{
    int ret = -1;
    char errstr[256];

    if (strstr(value, "../")) {
        snprintf(errstr, 256, "invalid path given '%s'", value);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                errstr, NULL);
        goto out;
    }

    /* Make sure the given path is valid */
    if (value[0] != '/') {
        snprintf(errstr, 256,
                 "option %s %s: '%s' is not an "
                 "absolute path name",
                 key, value, value);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                errstr, NULL);
        goto out;
    }

    ret = 0;
out:
    if (ret && op_errstr)
        *op_errstr = gf_strdup(errstr);
    return ret;
}

static int
xlator_option_validate_int(xlator_t *xl, const char *key, const char *value,
                           volume_option_t *opt, char **op_errstr)
{
    long long inputll = 0;
    unsigned long long uinputll = 0;
    int ret = -1;
    char errstr[256];

    /* Check the range */
    if (gf_string2longlong(value, &inputll) != 0) {
        snprintf(errstr, 256, "invalid number format \"%s\" in option \"%s\"",
                 value, key);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                errstr, NULL);
        goto out;
    }

    /* Handle '-0' */
    if ((inputll == 0) && (gf_string2ulonglong(value, &uinputll) != 0)) {
        snprintf(errstr, 256, "invalid number format \"%s\" in option \"%s\"",
                 value, key);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                errstr, NULL);
        goto out;
    }

    if ((opt->min == 0) && (opt->max == 0) &&
        (opt->validate == GF_OPT_VALIDATE_BOTH)) {
        gf_msg_trace(xl->name, 0,
                     "no range check required for "
                     "'option %s %s'",
                     key, value);
        ret = 0;
        goto out;
    }

    if (opt->validate == GF_OPT_VALIDATE_MIN) {
        if (inputll < opt->min) {
            snprintf(errstr, 256,
                     "'%lld' in 'option %s %s' is smaller than "
                     "minimum value '%.0f'",
                     inputll, key, value, opt->min);
            gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                    errstr, NULL);
            goto out;
        }
    } else if (opt->validate == GF_OPT_VALIDATE_MAX) {
        if (inputll > opt->max) {
            snprintf(errstr, 256,
                     "'%lld' in 'option %s %s' is greater than "
                     "maximum value '%.0f'",
                     inputll, key, value, opt->max);
            gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                    errstr, NULL);
            goto out;
        }
    } else if ((inputll < opt->min) || (inputll > opt->max)) {
        snprintf(errstr, 256,
                 "'%lld' in 'option %s %s' is out of range "
                 "[%.0f - %.0f]",
                 inputll, key, value, opt->min, opt->max);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_OUT_OF_RANGE, "error=%s",
                errstr, NULL);
        goto out;
    }

    ret = 0;
out:
    if (ret && op_errstr)
        *op_errstr = gf_strdup(errstr);
    return ret;
}

static int
xlator_option_validate_sizet(xlator_t *xl, const char *key, const char *value,
                             volume_option_t *opt, char **op_errstr)
{
    uint64_t size = 0;
    int ret = 0;
    char errstr[256];

    /* Check the range */
    if (gf_string2bytesize_uint64(value, &size) != 0) {
        snprintf(errstr, 256, "invalid number format \"%s\" in option \"%s\"",
                 value, key);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                errstr, NULL);
        ret = -1;
        goto out;
    }

    if ((opt->min == 0) && (opt->max == 0)) {
        gf_msg_trace(xl->name, 0,
                     "no range check required for "
                     "'option %s %s'",
                     key, value);
        goto out;
    }

    if ((size < opt->min) || (size > opt->max)) {
        snprintf(errstr, 256,
                 "'%" PRIu64
                 "' in 'option %s %s' is out of range [%.0f - %.0f]",
                 size, key, value, opt->min, opt->max);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_OUT_OF_RANGE, "error=%s",
                errstr, NULL);
        ret = -1;
    }

out:
    if (ret && op_errstr)
        *op_errstr = gf_strdup(errstr);
    return ret;
}

static int
xlator_option_validate_bool(xlator_t *xl, const char *key, const char *value,
                            volume_option_t *opt, char **op_errstr)
{
    int ret = -1;
    char errstr[256];
    gf_boolean_t is_valid;

    /* Check if the value is one of
       '0|1|on|off|no|yes|true|false|enable|disable' */

    if (gf_string2boolean(value, &is_valid) != 0) {
        snprintf(errstr, 256, "option %s %s: '%s' is not a valid boolean value",
                 key, value, value);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                errstr, NULL);
        goto out;
    }

    ret = 0;
out:
    if (ret && op_errstr)
        *op_errstr = gf_strdup(errstr);
    return ret;
}

static int
xlator_option_validate_xlator(xlator_t *xl, const char *key, const char *value,
                              volume_option_t *opt, char **op_errstr)
{
    int ret = -1;
    char errstr[256];
    xlator_t *xlopt = NULL;

    /* Check if the value is one of the xlators */
    xlopt = xl;
    while (xlopt->prev)
        xlopt = xlopt->prev;

    while (xlopt) {
        if (strcmp(value, xlopt->name) == 0) {
            ret = 0;
            break;
        }
        xlopt = xlopt->next;
    }

    if (!xlopt) {
        snprintf(errstr, 256, "option %s %s: '%s' is not a valid volume name",
                 key, value, value);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                errstr, NULL);
        goto out;
    }

    ret = 0;
out:
    if (ret && op_errstr)
        *op_errstr = gf_strdup(errstr);
    return ret;
}

static void
set_error_str(char *errstr, size_t len, volume_option_t *opt, const char *key,
              const char *value)
{
    int i = 0;
    int ret = 0;

    ret = snprintf(errstr, len,
                   "option %s %s: '%s' is not valid "
                   "(possible options are ",
                   key, value, value);

    for (i = 0; (i < ZR_OPTION_MAX_ARRAY_SIZE) && opt->value[i];) {
        ret += snprintf(errstr + ret, len - ret, "%s", opt->value[i]);
        if (((++i) < ZR_OPTION_MAX_ARRAY_SIZE) && (opt->value[i]))
            ret += snprintf(errstr + ret, len - ret, ", ");
        else
            ret += snprintf(errstr + ret, len - ret, ".)");
    }
    return;
}

static int
is_all_whitespaces(const char *value)
{
    int i = 0;

    if (value == NULL)
        return -1;

    for (i = 0; value[i] != '\0'; i++) {
        if (value[i] == ' ')
            continue;
        else
            return 0;
    }

    return 1;
}

static int
xlator_option_validate_str(xlator_t *xl, const char *key, const char *value,
                           volume_option_t *opt, char **op_errstr)
{
    int ret = -1;
    int i = 0;

    /* Check if the '*str' is valid */
    if (GF_OPTION_LIST_EMPTY(opt)) {
        ret = 0;
        goto out;
    }

    if (is_all_whitespaces(value) == 1)
        goto out;

    for (i = 0; (i < ZR_OPTION_MAX_ARRAY_SIZE) && opt->value[i]; i++) {
#ifdef GF_DARWIN_HOST_OS
        if (fnmatch(opt->value[i], value, 0) == 0) {
            ret = 0;
            break;
        }
#else
        if (fnmatch(opt->value[i], value, FNM_EXTMATCH) == 0) {
            ret = 0;
            break;
        }
#endif
    }

    if ((i == ZR_OPTION_MAX_ARRAY_SIZE) || (!opt->value[i]))
        goto out;
    /* enter here only if
     * 1. reached end of opt->value array and haven't
     *    validated input
     *                      OR
     * 2. valid input list is less than
     *    ZR_OPTION_MAX_ARRAY_SIZE and input has not
     *    matched all possible input values.
     */

    ret = 0;

out:
    if (ret) {
        char errstr[4096];
        set_error_str(errstr, sizeof(errstr), opt, key, value);

        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                errstr, NULL);
        if (op_errstr)
            *op_errstr = gf_strdup(errstr);
    }
    return ret;
}

static int
xlator_option_validate_percent(xlator_t *xl, const char *key, const char *value,
                               volume_option_t *opt, char **op_errstr)
{
    double percent = 0;
    int ret = -1;
    char errstr[256];

    /* Check if the value is valid percentage */
    if (gf_string2percent(value, &percent) != 0) {
        snprintf(errstr, 256, "invalid percent format \"%s\" in \"option %s\"",
                 value, key);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                errstr, NULL);
        goto out;
    }

    if ((percent < 0.0) || (percent > 100.0)) {
        snprintf(errstr, 256,
                 "'%lf' in 'option %s %s' is out of range [0 - 100]", percent,
                 key, value);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_OUT_OF_RANGE, "error=%s",
                errstr, NULL);
        goto out;
    }

    ret = 0;
out:
    if (ret && op_errstr)
        *op_errstr = gf_strdup(errstr);
    return ret;
}

static int
xlator_option_validate_fractional_value(const char *value)
{
    const char *s = NULL;
    int ret = 0;

    s = strchr(value, '.');
    if (s) {
        for (s = s + 1; *s != '\0'; s++) {
            if (*s != '0') {
                return -1;
            }
        }
    }

    return ret;
}

static int
xlator_option_validate_percent_or_sizet(xlator_t *xl, const char *key,
                                        const char *value, volume_option_t *opt,
                                        char **op_errstr)
{
    int ret = -1;
    char errstr[256];
    double size = 0;
    gf_boolean_t is_percent = _gf_false;

    if (gf_string2percent_or_bytesize(value, &size, &is_percent) == 0) {
        if (is_percent) {
            if ((size < 0.0) || (size > 100.0)) {
                snprintf(errstr, sizeof(errstr),
                         "'%lf' in 'option %s %s' is out"
                         " of range [0 - 100]",
                         size, key, value);
                gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_OUT_OF_RANGE,
                        "error=%s", errstr, NULL);
                goto out;
            }
            ret = 0;
            goto out;
        }

        /*Input value of size(in byte) should not be fractional*/
        ret = xlator_option_validate_fractional_value(value);
        if (ret) {
            snprintf(errstr, sizeof(errstr),
                     "'%lf' in 'option %s"
                     " %s' should not be fractional value. Use "
                     "valid unsigned integer value.",
                     size, key, value);
            gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                    errstr, NULL);
            goto out;
        }

        /* Check the range */
        if ((opt->min == 0) && (opt->max == 0)) {
            gf_msg_trace(xl->name, 0,
                         "no range check required "
                         "for 'option %s %s'",
                         key, value);
            ret = 0;
            goto out;
        }
        if ((size < opt->min) || (size > opt->max)) {
            snprintf(errstr, 256,
                     "'%lf' in 'option %s %s'"
                     " is out of range [%.0f - %.0f]",
                     size, key, value, opt->min, opt->max);
            gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_OUT_OF_RANGE, "error=%s",
                    errstr, NULL);
            goto out;
        }
        ret = 0;
        goto out;
    }

    /* If control reaches here, invalid argument */

    snprintf(errstr, 256, "invalid number format \"%s\" in \"option %s\"",
             value, key);
    gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s", errstr,
            NULL);

out:
    if (ret && op_errstr)
        *op_errstr = gf_strdup(errstr);
    return ret;
}

static int
xlator_option_validate_time(xlator_t *xl, const char *key, const char *value,
                            volume_option_t *opt, char **op_errstr)
{
    int ret = -1;
    char errstr[256];
    uint32_t input_time = 0;

    /* Check if the value is valid time */
    if (gf_string2time(value, &input_time) != 0) {
        snprintf(errstr, 256,
                 "invalid time format \"%s\" in "
                 "\"option %s\"",
                 value, key);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                errstr, NULL);
        goto out;
    }

    if ((opt->min == 0) && (opt->max == 0)) {
        gf_msg_trace(xl->name, 0,
                     "no range check required for "
                     "'option %s %s'",
                     key, value);
        ret = 0;
        goto out;
    }

    if ((input_time < opt->min) || (input_time > opt->max)) {
        snprintf(errstr, 256,
                 "'%" PRIu32
                 "' in 'option %s %s' is "
                 "out of range [%.0f - %.0f]",
                 input_time, key, value, opt->min, opt->max);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_OUT_OF_RANGE, "error=%s",
                errstr, NULL);
        goto out;
    }

    ret = 0;
out:
    if (ret && op_errstr)
        *op_errstr = gf_strdup(errstr);
    return ret;
}

static int
xlator_option_validate_double(xlator_t *xl, const char *key, const char *value,
                              volume_option_t *opt, char **op_errstr)
{
    double input = 0.0;
    int ret = -1;
    char errstr[256];

    /* Check the range */
    if (gf_string2double(value, &input) != 0) {
        snprintf(errstr, 256, "invalid number format \"%s\" in option \"%s\"",
                 value, key);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                errstr, NULL);
        goto out;
    }

    if ((opt->min == 0) && (opt->max == 0) &&
        (opt->validate == GF_OPT_VALIDATE_BOTH)) {
        gf_msg_trace(xl->name, 0,
                     "no range check required for "
                     "'option %s %s'",
                     key, value);
        ret = 0;
        goto out;
    }

    if (opt->validate == GF_OPT_VALIDATE_MIN) {
        if (input < opt->min) {
            snprintf(errstr, 256,
                     "'%f' in 'option %s %s' is smaller than "
                     "minimum value '%f'",
                     input, key, value, opt->min);
            gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                    errstr, NULL);
            goto out;
        }
    } else if (opt->validate == GF_OPT_VALIDATE_MAX) {
        if (input > opt->max) {
            snprintf(errstr, 256,
                     "'%f' in 'option %s %s' is greater than "
                     "maximum value '%f'",
                     input, key, value, opt->max);
            gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                    errstr, NULL);
            goto out;
        }
    } else if ((input < opt->min) || (input > opt->max)) {
        snprintf(errstr, 256,
                 "'%f' in 'option %s %s' is out of range "
                 "[%f - %f]",
                 input, key, value, opt->min, opt->max);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_OUT_OF_RANGE, "error=%s",
                errstr, NULL);
        goto out;
    }

    ret = 0;
out:
    if (ret && op_errstr)
        *op_errstr = gf_strdup(errstr);
    return ret;
}

static int
xlator_option_validate_addr(xlator_t *xl, const char *key, const char *value,
                            volume_option_t *opt, char **op_errstr)
{
    int ret = -1;
    char errstr[256];

    if (!valid_internet_address((char *)value, _gf_false, _gf_false)) {
        snprintf(errstr, 256, "option %s %s: Can not parse %s address", key,
                 value, value);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                errstr, NULL);
        if (op_errstr)
            *op_errstr = gf_strdup(errstr);
    }

    ret = 0;

    return ret;
}

int
xlator_option_validate_addr_list(xlator_t *xl, const char *key,
                                 const char *value, volume_option_t *opt,
                                 char **op_errstr)
{
    int ret = -1;
    char *dup_val = NULL;
    char *addr_tok = NULL;
    char *save_ptr = NULL;
    char *entry = NULL;
    char *entry_ptr = NULL;
    char *dir_and_addr = NULL;
    char *addr_ptr = NULL;
    char *addr_list = NULL;
    char *addr = NULL;
    char *dir = NULL;

    dup_val = gf_strdup(value);
    if (!dup_val)
        goto out;

    if (dup_val[0] != '/' && !strchr(dup_val, '(')) {
        /* Possible old format, handle it for back-ward compatibility */
        addr_tok = strtok_r(dup_val, ",", &save_ptr);
        while (addr_tok) {
            if (!valid_internet_address(addr_tok, _gf_true, _gf_true))
                goto out;

            addr_tok = strtok_r(NULL, ",", &save_ptr);
        }
        ret = 0;
        goto out;
    }

    /* Lets handle the value with new format */
    entry = strtok_r(dup_val, ",", &entry_ptr);
    while (entry) {
        dir_and_addr = gf_strdup(entry);
        if (!dir_and_addr)
            goto out;

        dir = strtok_r(dir_and_addr, "(", &addr_ptr);
        if (dir[0] != '/') {
            /* Valid format should be starting from '/' */
            goto out;
        }
        /* dir = strtok_r (NULL, " =", &addr_tmp); */
        addr = strtok_r(NULL, ")", &addr_ptr);
        if (!addr)
            goto out;

        addr_list = gf_strdup(addr);
        if (!addr_list)
            goto out;

        /* This format be separated by '|' */
        addr_tok = strtok_r(addr_list, "|", &save_ptr);
        if (addr_tok == NULL)
            goto out;
        while (addr_tok) {
            if (!valid_internet_address(addr_tok, _gf_true, _gf_true))
                goto out;

            addr_tok = strtok_r(NULL, "|", &save_ptr);
        }
        entry = strtok_r(NULL, ",", &entry_ptr);
        GF_FREE(dir_and_addr);
        GF_FREE(addr_list);
        addr_list = NULL;
        dir_and_addr = NULL;
    }

    ret = 0;

out:
    if (ret) {
        char errstr[4096];
        snprintf(errstr, sizeof(errstr),
                 "option %s %s: '%s' is not "
                 "a valid internet-address-list",
                 key, value, value);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                errstr, NULL);
        if (op_errstr)
            *op_errstr = gf_strdup(errstr);
    }
    GF_FREE(dup_val);
    GF_FREE(dir_and_addr);
    GF_FREE(addr_list);
    return ret;
}

static int
xlator_option_validate_mntauth(xlator_t *xl, const char *key, const char *value,
                               volume_option_t *opt, char **op_errstr)
{
    int ret = -1;
    char *dup_val = NULL;
    char *addr_tok = NULL;
    char *save_ptr = NULL;

    dup_val = gf_strdup(value);
    if (!dup_val)
        goto out;

    addr_tok = strtok_r(dup_val, ",", &save_ptr);
    if (addr_tok == NULL)
        goto out;
    while (addr_tok) {
        if (!valid_mount_auth_address(addr_tok))
            goto out;

        addr_tok = strtok_r(NULL, ",", &save_ptr);
    }
    ret = 0;

out:
    if (ret) {
        char errstr[4096];
        snprintf(errstr, sizeof(errstr),
                 "option %s %s: '%s' is not "
                 "a valid mount-auth-address",
                 key, value, value);
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_INVALID_ENTRY, "error=%s",
                errstr, NULL);
        if (op_errstr)
            *op_errstr = gf_strdup(errstr);
    }
    GF_FREE(dup_val);

    return ret;
}

/*XXX: the rules to validate are as per block-size required for stripe xlator */
static int
gf_validate_size(const char *sizestr, volume_option_t *opt)
{
    uint64_t value = 0;
    int ret = 0;

    GF_ASSERT(opt);

    if (gf_string2bytesize_uint64(sizestr, &value) != 0 || value < opt->min ||
        value % 512) {
        ret = -1;
        goto out;
    }

out:
    gf_msg_debug(THIS->name, 0, "Returning %d", ret);
    return ret;
}

static int
gf_validate_number(const char *numstr, volume_option_t *opt)
{
    int32_t value;
    return gf_string2int32(numstr, &value);
}

/*  Parses the string to be of the form <key1>:<value1>,<key2>:<value2>...  *
 *  takes two optional validaters key_validator and value_validator         */
static int
validate_list_elements(const char *string, volume_option_t *opt,
                       int(key_validator)(const char *),
                       int(value_validator)(const char *, volume_option_t *))
{
    char *dup_string = NULL;
    char *str_sav = NULL;
    char *substr_sav = NULL;
    char *str_ptr = NULL;
    char *key = NULL;
    char *value = NULL;
    int ret = 0;

    GF_ASSERT(string);

    dup_string = gf_strdup(string);
    if (NULL == dup_string)
        goto out;

    str_ptr = strtok_r(dup_string, ",", &str_sav);
    if (str_ptr == NULL) {
        ret = -1;
        goto out;
    }
    while (str_ptr) {
        key = strtok_r(str_ptr, ":", &substr_sav);
        if (!key || (key_validator && key_validator(key))) {
            ret = -1;
            gf_smsg(THIS->name, GF_LOG_WARNING, 0, LG_MSG_INVALID_ENTRY,
                    "list=%s", string, "key=%s", key ? key : "", NULL);
            goto out;
        }

        value = strtok_r(NULL, ":", &substr_sav);
        if (!value || (value_validator && value_validator(value, opt))) {
            ret = -1;
            gf_smsg(THIS->name, GF_LOG_WARNING, 0, LG_MSG_INVALID_ENTRY,
                    "list=%s", string, "value=%s", key, NULL);
            goto out;
        }

        str_ptr = strtok_r(NULL, ",", &str_sav);
        substr_sav = NULL;
    }

out:
    GF_FREE(dup_string);
    gf_msg_debug(THIS->name, 0, "Returning %d", ret);
    return ret;
}

static int
xlator_option_validate_priority_list(xlator_t *xl, const char *key,
                                     const char *value, volume_option_t *opt,
                                     char **op_errstr)
{
    int ret = 0;
    char errstr[1024] = {
        0,
    };

    GF_ASSERT(value);

    ret = validate_list_elements(value, opt, NULL, &gf_validate_number);
    if (ret) {
        snprintf(errstr, 1024,
                 "option %s %s: '%s' is not a valid "
                 "priority-list",
                 key, value, value);
        *op_errstr = gf_strdup(errstr);
    }

    return ret;
}

static int
xlator_option_validate_size_list(xlator_t *xl, const char *key,
                                 const char *value, volume_option_t *opt,
                                 char **op_errstr)
{
    int ret = 0;
    char errstr[1024] = {
        0,
    };

    GF_ASSERT(value);

    ret = gf_validate_size(value, opt);
    if (ret)
        ret = validate_list_elements(value, opt, NULL, &gf_validate_size);

    if (ret) {
        snprintf(errstr, 1024,
                 "option %s %s: '%s' is not a valid "
                 "size-list",
                 key, value, value);
        *op_errstr = gf_strdup(errstr);
    }

    return ret;
}

static int
xlator_option_validate_any(xlator_t *xl, const char *key, const char *value,
                           volume_option_t *opt, char **op_errstr)
{
    return 0;
}

typedef int(xlator_option_validator_t)(xlator_t *xl, const char *key,
                                       const char *value, volume_option_t *opt,
                                       char **operrstr);

int
xlator_option_validate(xlator_t *xl, char *key, char *value,
                       volume_option_t *opt, char **op_errstr)
{
    int ret = -1;
    xlator_option_validator_t *validate;
    xlator_option_validator_t *validators[] = {
        [GF_OPTION_TYPE_PATH] = xlator_option_validate_path,
        [GF_OPTION_TYPE_INT] = xlator_option_validate_int,
        [GF_OPTION_TYPE_SIZET] = xlator_option_validate_sizet,
        [GF_OPTION_TYPE_BOOL] = xlator_option_validate_bool,
        [GF_OPTION_TYPE_XLATOR] = xlator_option_validate_xlator,
        [GF_OPTION_TYPE_STR] = xlator_option_validate_str,
        [GF_OPTION_TYPE_PERCENT] = xlator_option_validate_percent,
        [GF_OPTION_TYPE_PERCENT_OR_SIZET] =
            xlator_option_validate_percent_or_sizet,
        [GF_OPTION_TYPE_TIME] = xlator_option_validate_time,
        [GF_OPTION_TYPE_DOUBLE] = xlator_option_validate_double,
        [GF_OPTION_TYPE_INTERNET_ADDRESS] = xlator_option_validate_addr,
        [GF_OPTION_TYPE_INTERNET_ADDRESS_LIST] =
            xlator_option_validate_addr_list,
        [GF_OPTION_TYPE_PRIORITY_LIST] = xlator_option_validate_priority_list,
        [GF_OPTION_TYPE_SIZE_LIST] = xlator_option_validate_size_list,
        [GF_OPTION_TYPE_ANY] = xlator_option_validate_any,
        [GF_OPTION_TYPE_CLIENT_AUTH_ADDR] = xlator_option_validate_mntauth,
        [GF_OPTION_TYPE_MAX] = NULL,
    };

    if (opt->type > GF_OPTION_TYPE_MAX) {
        gf_smsg(xl->name, GF_LOG_ERROR, 0, LG_MSG_UNKNOWN_OPTION_TYPE,
                "type=%d", opt->type, NULL);
        goto out;
    }

    validate = validators[opt->type];

    ret = validate(xl, key, value, opt, op_errstr);
out:
    return ret;
}

volume_option_t *
xlator_volume_option_get_list(volume_opt_list_t *vol_list, const char *key)
{
    volume_option_t *opt = NULL;
    volume_opt_list_t *opt_list = NULL;
    int index = 0;
    int i = 0;
    char *cmp_key = NULL;

    if (!vol_list->given_opt) {
        opt_list = list_entry(vol_list->list.next, volume_opt_list_t, list);
        opt = opt_list->given_opt;
    } else
        opt = vol_list->given_opt;

    for (index = 0; opt[index].key[0]; index++) {
        for (i = 0; i < ZR_VOLUME_MAX_NUM_KEY; i++) {
            cmp_key = opt[index].key[i];
            if (!cmp_key)
                break;
            if (fnmatch(cmp_key, key, FNM_NOESCAPE) == 0) {
                return &opt[index];
            }
        }
    }

    return NULL;
}

volume_option_t *
xlator_volume_option_get(xlator_t *xl, const char *key)
{
    volume_opt_list_t *vol_list = NULL;
    volume_option_t *found = NULL;

    list_for_each_entry(vol_list, &xl->volume_options, list)
    {
        found = xlator_volume_option_get_list(vol_list, key);
        if (found)
            break;
    }

    return found;
}

static int
xl_opt_validate(dict_t *dict, char *key, data_t *value, void *data)
{
    xlator_t *xl = NULL;
    volume_opt_list_t *vol_opt = NULL;
    volume_option_t *opt = NULL;
    int ret = 0;
    char *errstr = NULL;

    struct {
        xlator_t *this;
        volume_opt_list_t *vol_opt;
        char *errstr;
    } * stub;

    stub = data;
    xl = stub->this;
    vol_opt = stub->vol_opt;

    opt = xlator_volume_option_get_list(vol_opt, key);
    if (!opt)
        return 0;

    ret = xlator_option_validate(xl, key, value->data, opt, &errstr);
    if (ret)
        gf_smsg(xl->name, GF_LOG_WARNING, 0, LG_MSG_VALIDATE_RETURNS, "key=%s",
                key, "ret=%d", ret, NULL);

    if (errstr)
        /* possible small leak of previously set stub->errstr */
        stub->errstr = errstr;

    if (fnmatch(opt->key[0], key, FNM_NOESCAPE) != 0) {
        gf_smsg(xl->name, GF_LOG_DEBUG, 0, LG_MSG_OPTION_DEPRECATED, "key=%s",
                key, "preferred=%s", opt->key[0], NULL);
        dict_set(dict, opt->key[0], value);
        dict_del(dict, key);
    }
    return 0;
}

int
xlator_options_validate_list(xlator_t *xl, dict_t *options,
                             volume_opt_list_t *vol_opt, char **op_errstr)
{
    int ret = 0;
    struct {
        xlator_t *this;
        volume_opt_list_t *vol_opt;
        char *errstr;
    } stub;

    stub.this = xl;
    stub.vol_opt = vol_opt;
    stub.errstr = NULL;

    dict_foreach(options, xl_opt_validate, &stub);
    if (stub.errstr) {
        ret = -1;
        if (op_errstr)
            *op_errstr = stub.errstr;
    }

    return ret;
}

int
xlator_options_validate(xlator_t *xl, dict_t *options, char **op_errstr)
{
    int ret = 0;
    volume_opt_list_t *vol_opt = NULL;

    if (!xl) {
        gf_msg_debug(THIS->name, 0, "'this' not a valid ptr");
        ret = -1;
        goto out;
    }

    if (list_empty(&xl->volume_options))
        goto out;

    list_for_each_entry(vol_opt, &xl->volume_options, list)
    {
        ret = xlator_options_validate_list(xl, options, vol_opt, op_errstr);
    }
out:
    return ret;
}

int
xlator_validate_rec(xlator_t *xlator, char **op_errstr)
{
    int ret = -1;
    xlator_list_t *trav = NULL;
    xlator_t *old_THIS = NULL;

    GF_VALIDATE_OR_GOTO("xlator", xlator, out);

    trav = xlator->children;

    while (trav) {
        if (xlator_validate_rec(trav->xlator, op_errstr)) {
            gf_smsg("xlator", GF_LOG_WARNING, 0, LG_MSG_VALIDATE_REC_FAILED,
                    NULL);
            goto out;
        }

        trav = trav->next;
    }

    if (xlator_dynload(xlator))
        gf_msg_debug(xlator->name, 0, "Did not load the symbols");

    old_THIS = THIS;
    THIS = xlator;

    /* Need this here, as this graph has not yet called init() */
    if (!xlator->mem_acct) {
        if (!xlator->mem_acct_init)
            xlator->mem_acct_init = default_mem_acct_init;
        xlator->mem_acct_init(xlator);
    }

    ret = xlator_options_validate(xlator, xlator->options, op_errstr);
    THIS = old_THIS;

    if (ret) {
        gf_smsg(xlator->name, GF_LOG_INFO, 0, LG_MSG_INVALID_ENTRY, "%s",
                *op_errstr, NULL);
        goto out;
    }

    gf_msg_debug(xlator->name, 0, "Validated options");

    ret = 0;
out:
    return ret;
}

int
graph_reconf_validateopt(glusterfs_graph_t *graph, char **op_errstr)
{
    xlator_t *xlator = NULL;
    int ret = -1;

    GF_ASSERT(graph);

    xlator = graph->first;

    ret = xlator_validate_rec(xlator, op_errstr);

    return ret;
}

static int
xlator_reconfigure_rec(xlator_t *old_xl, xlator_t *new_xl)
{
    xlator_list_t *trav1 = NULL;
    xlator_list_t *trav2 = NULL;
    int32_t ret = -1;
    xlator_t *old_THIS = NULL;

    GF_VALIDATE_OR_GOTO("xlator", old_xl, out);
    GF_VALIDATE_OR_GOTO("xlator", new_xl, out);

    trav1 = old_xl->children;
    trav2 = new_xl->children;

    while (trav1 && trav2) {
        ret = xlator_reconfigure_rec(trav1->xlator, trav2->xlator);
        if (ret)
            goto out;

        gf_msg_debug(trav1->xlator->name, 0, "reconfigured");

        trav1 = trav1->next;
        trav2 = trav2->next;
    }

    if (old_xl->reconfigure) {
        old_THIS = THIS;
        THIS = old_xl;

        xlator_init_lock();
        handle_default_options(old_xl, new_xl->options);
        ret = old_xl->reconfigure(old_xl, new_xl->options);
        xlator_init_unlock();

        THIS = old_THIS;

        if (ret)
            goto out;
    } else {
        gf_msg_debug(old_xl->name, 0, "No reconfigure() found");
    }

    ret = 0;
out:
    return ret;
}

int
xlator_tree_reconfigure(xlator_t *old_xl, xlator_t *new_xl)
{
    xlator_t *new_top = NULL;
    xlator_t *old_top = NULL;

    GF_ASSERT(old_xl);
    GF_ASSERT(new_xl);

    old_top = old_xl;
    new_top = new_xl;

    return xlator_reconfigure_rec(old_top, new_top);
}

int
xlator_option_info_list(volume_opt_list_t *list, char *key, char **def_val,
                        char **descr)
{
    int ret = -1;
    volume_option_t *opt = NULL;

    opt = xlator_volume_option_get_list(list, key);
    if (!opt)
        goto out;

    if (def_val)
        *def_val = opt->default_value;
    if (descr)
        *descr = opt->description;

    ret = 0;
out:
    return ret;
}

static int
pass(char *in, char **out)
{
    *out = in;
    return 0;
}

static int
xl_by_name(char *in, xlator_t **out)
{
    xlator_t *xl = NULL;

    xl = xlator_search_by_name(THIS, in);

    if (!xl)
        return -1;
    *out = xl;
    return 0;
}

static int
pc_or_size(char *in, double *out)
{
    double pc = 0;
    int ret = 0;
    uint64_t size = 0;

    if (gf_string2percent(in, &pc) == 0) {
        if (pc > 100.0) {
            ret = gf_string2bytesize_uint64(in, &size);
            if (!ret)
                *out = size;
        } else {
            *out = pc;
        }
    } else {
        ret = gf_string2bytesize_uint64(in, &size);
        if (!ret)
            *out = size;
    }
    return ret;
}

DEFINE_INIT_OPT(char *, str, pass);
DEFINE_INIT_OPT(uint64_t, uint64, gf_string2uint64);
DEFINE_INIT_OPT(int64_t, int64, gf_string2int64);
DEFINE_INIT_OPT(uint32_t, uint32, gf_string2uint32);
DEFINE_INIT_OPT(int32_t, int32, gf_string2int32);
DEFINE_INIT_OPT(uint64_t, size, gf_string2bytesize_uint64);
DEFINE_INIT_OPT(uint64_t, size_uint64, gf_string2bytesize_uint64);
DEFINE_INIT_OPT(double, percent, gf_string2percent);
DEFINE_INIT_OPT(double, percent_or_size, pc_or_size);
DEFINE_INIT_OPT(gf_boolean_t, bool, gf_string2boolean);
DEFINE_INIT_OPT(xlator_t *, xlator, xl_by_name);
DEFINE_INIT_OPT(char *, path, pass);
DEFINE_INIT_OPT(double, double, gf_string2double);
DEFINE_INIT_OPT(uint32_t, time, gf_string2time);

DEFINE_RECONF_OPT(char *, str, pass);
DEFINE_RECONF_OPT(uint64_t, uint64, gf_string2uint64);
DEFINE_RECONF_OPT(int64_t, int64, gf_string2int64);
DEFINE_RECONF_OPT(uint32_t, uint32, gf_string2uint32);
DEFINE_RECONF_OPT(int32_t, int32, gf_string2int32);
DEFINE_RECONF_OPT(uint64_t, size, gf_string2bytesize_uint64);
DEFINE_RECONF_OPT(uint64_t, size_uint64, gf_string2bytesize_uint64);
DEFINE_RECONF_OPT(double, percent, gf_string2percent);
DEFINE_RECONF_OPT(double, percent_or_size, pc_or_size);
DEFINE_RECONF_OPT(gf_boolean_t, bool, gf_string2boolean);
DEFINE_RECONF_OPT(xlator_t *, xlator, xl_by_name);
DEFINE_RECONF_OPT(char *, path, pass);
DEFINE_RECONF_OPT(double, double, gf_string2double);
DEFINE_RECONF_OPT(uint32_t, time, gf_string2time);
