/*
  Copyright 2014-present Facebook. All Rights Reserved

  This file is part of GlusterFS.

   Author :
   Shreyas Siravara <shreyas.siravara@gmail.com>

  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 "exports.h"
#include <glusterfs/hashfn.h>
#include <glusterfs/parse-utils.h>
#include "nfs-messages.h"

static void
_exp_dict_destroy(dict_t *ng_dict);
static void
_export_options_print(const struct export_options *opts);
static void
_export_options_deinit(struct export_options *opts);
static void
_export_dir_deinit(struct export_dir *dir);

static struct parser *netgroup_parser;
static struct parser *hostname_parser;
static struct parser *options_parser;

/**
 * _exp_init_parsers -- Initialize parsers to be used in this file
 *
 * @return: success: 0
 *          failure: -1
 */
static int
_exp_init_parsers()
{
    int ret = -1;

    netgroup_parser = parser_init(NETGROUP_REGEX_PATTERN);
    if (!netgroup_parser)
        goto out;

    hostname_parser = parser_init(HOSTNAME_REGEX_PATTERN);
    if (!hostname_parser)
        goto out;

    options_parser = parser_init(OPTIONS_REGEX_PATTERN);
    if (!options_parser)
        goto out;

    ret = 0;
out:
    return ret;
}

/**
 * _exp_deinit_parsers -- Free parsers used in this file
 */
static void
_exp_deinit_parsers()
{
    parser_deinit(netgroup_parser);
    parser_deinit(hostname_parser);
    parser_deinit(options_parser);
}

/**
 * _export_file_init -- Initialize an exports file structure.
 *
 * @return  : success: Pointer to an allocated exports file struct
 *            failure: NULL
 *
 * Not for external use.
 */
struct exports_file *
_exports_file_init()
{
    struct exports_file *file = NULL;

    file = GF_CALLOC(1, sizeof(*file), gf_common_mt_nfs_exports);
    if (!file) {
        gf_msg(GF_EXP, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY,
               "Failed to allocate file struct!");
        goto out;
    }

    file->exports_dict = dict_new();
    file->exports_map = dict_new();
    if (!file->exports_dict || !file->exports_map) {
        gf_msg(GF_EXP, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY,
               "Failed to allocate dict!");
        goto free_and_out;
    }

    goto out;

free_and_out:
    if (file->exports_dict)
        dict_unref(file->exports_dict);

    GF_FREE(file);
    file = NULL;
out:
    return file;
}

/**
 * _exp_file_dict_destroy -- Delete each item in the dict
 *
 * @dict : Dict to free elements from
 * @key  : Key in the dict we are on
 * @val  : Value associated with that dict
 * @tmp  : Not used
 *
 * Not for external use.
 */
static int
_exp_file_dict_destroy(dict_t *dict, char *key, data_t *val, void *tmp)
{
    struct export_dir *dir = NULL;

    GF_VALIDATE_OR_GOTO(GF_EXP, dict, out);

    if (val) {
        dir = (struct export_dir *)val->data;

        if (dir) {
            _export_dir_deinit(dir);
            val->data = NULL;
        }
        dict_del(dict, key);
    }

out:
    return 0;
}

/**
 * _exp_file_deinit -- Free memory used by an export file
 *
 * @expfile : Pointer to the exports file to free
 *
 * Externally usable.
 */
void
exp_file_deinit(struct exports_file *expfile)
{
    if (!expfile)
        goto out;

    if (expfile->exports_dict) {
        dict_foreach(expfile->exports_dict, _exp_file_dict_destroy, NULL);
        dict_unref(expfile->exports_dict);
    }

    if (expfile->exports_map) {
        dict_foreach(expfile->exports_map, _exp_file_dict_destroy, NULL);
        dict_unref(expfile->exports_map);
    }

    GF_FREE(expfile->filename);
    GF_FREE(expfile);
out:
    return;
}

/**
 * _export_dir_init -- Initialize an export directory structure.
 *
 * @return  : success: Pointer to an allocated exports directory struct
 *            failure: NULL
 *
 * Not for external use.
 */
static struct export_dir *
_export_dir_init()
{
    struct export_dir *expdir = GF_CALLOC(1, sizeof(*expdir),
                                          gf_common_mt_nfs_exports);

    if (!expdir)
        gf_msg(GF_EXP, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY,
               "Failed to allocate export dir structure!");

    return expdir;
}

/**
 * _export_dir_deinit -- Free memory used by an export dir
 *
 * @expdir : Pointer to the export directory to free
 *
 * Not for external use.
 */
static void
_export_dir_deinit(struct export_dir *dir)
{
    GF_VALIDATE_OR_GOTO(GF_EXP, dir, out);
    GF_FREE(dir->dir_name);
    _exp_dict_destroy(dir->netgroups);
    dict_unref(dir->netgroups);
    _exp_dict_destroy(dir->hosts);
    dict_unref(dir->hosts);
    GF_FREE(dir);

out:
    return;
}

/**
 * _export_item_print -- Print the elements in the export item.
 *
 * @expdir : Pointer to the item struct to print out.
 *
 * Not for external use.
 */
static void
_export_item_print(const struct export_item *item)
{
    GF_VALIDATE_OR_GOTO(GF_EXP, item, out);
    printf("%s", item->name);
    _export_options_print(item->opts);
out:
    return;
}

/**
 * _export_item_deinit -- Free memory used by an export item
 *
 * @expdir : Pointer to the export item to free
 *
 * Not for external use.
 */
static void
_export_item_deinit(struct export_item *item)
{
    if (!item)
        return;

    _export_options_deinit(item->opts);
    GF_FREE(item->name);
    GF_FREE(item);
}

/**
 * _export_item_init -- Initialize an export item structure
 *
 * @return  : success: Pointer to an allocated exports item struct
 *            failure: NULL
 *
 * Not for external use.
 */
static struct export_item *
_export_item_init()
{
    struct export_item *item = GF_CALLOC(1, sizeof(*item),
                                         gf_common_mt_nfs_exports);

    if (item) {
        GF_REF_INIT(item, _export_item_deinit);
    } else {
        gf_msg(GF_EXP, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY,
               "Failed to allocate export item!");
    }

    return item;
}

/**
 * _export_host_init -- Initialize an export options struct
 *
 * @return  : success: Pointer to an allocated options struct
 *            failure: NULL
 *
 * Not for external use.
 */
static struct export_options *
_export_options_init()
{
    struct export_options *opts = GF_CALLOC(1, sizeof(*opts),
                                            gf_common_mt_nfs_exports);

    if (!opts)
        gf_msg(GF_EXP, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY,
               "Failed to allocate options structure!");

    return opts;
}

/**
 * _export_options_deinit -- Free memory used by a options struct
 *
 * @expdir : Pointer to the options struct to free
 *
 * Not for external use.
 */
static void
_export_options_deinit(struct export_options *opts)
{
    if (!opts)
        return;

    GF_FREE(opts->anon_uid);
    GF_FREE(opts->sec_type);
    GF_FREE(opts);
}

/**
 * _export_options_print -- Print the elements in the options struct.
 *
 * @expdir : Pointer to the options struct to print out.
 *
 * Not for external use.
 */
static void
_export_options_print(const struct export_options *opts)
{
    GF_VALIDATE_OR_GOTO(GF_EXP, opts, out);

    printf("(");
    if (opts->rw)
        printf("rw,");
    else
        printf("ro,");

    if (opts->nosuid)
        printf("nosuid,");

    if (opts->root)
        printf("root,");

    if (opts->anon_uid)
        printf("anonuid=%s,", opts->anon_uid);

    if (opts->sec_type)
        printf("sec=%s,", opts->sec_type);

    printf(") ");
out:
    return;
}

/**
 * __exp_dict_free_walk -- Delete each item in the dict
 *
 * @dict : Dict to free elements from
 * @key  : Key in the dict we are on
 * @val  : Value associated with that dict
 * @tmp  : Not used
 *
 * Passed as a function pointer to dict_foreach()
 *
 * Not for external use.
 */
static int
__exp_dict_free_walk(dict_t *dict, char *key, data_t *val, void *tmp)
{
    if (val) {
        GF_REF_PUT((struct export_item *)val->data);
        val->data = NULL;
        dict_del(dict, key);
    }
    return 0;
}

/**
 * _exp_dict_destroy -- Delete all the items from this dict
 *                               through the helper function above.
 *
 * @ng_dict : Dict to free
 *
 * Not for external use.
 */
static void
_exp_dict_destroy(dict_t *ng_dict)
{
    if (!ng_dict)
        goto out;

    dict_foreach(ng_dict, __exp_dict_free_walk, NULL);
out:
    return;
}

/**
 * exp_file_dir_from_uuid -- Using a uuid as the key, retrieve an exports
 *                           directory from the file.
 *
 * @file: File to retrieve data from
 * @export_uuid: UUID of the export (mountid in the NFS xlator)
 *
 * @return : success: Pointer to an export dir struct
 *           failure: NULL
 */
struct export_dir *
exp_file_dir_from_uuid(const struct exports_file *file,
                       const uuid_t export_uuid)
{
    char export_uuid_str[512] = {
        0,
    };
    data_t *dirdata = NULL;
    struct export_dir *dir = NULL;

    gf_uuid_unparse(export_uuid, export_uuid_str);

    dirdata = dict_get(file->exports_map, export_uuid_str);
    if (dirdata)
        dir = (struct export_dir *)dirdata->data;

    return dir;
}

/**
 * _exp_file_insert -- Insert the exports directory into the file structure
 *                     using the directory as a dict. Also hashes the dirname,
 *                     stores it in a uuid type, converts the uuid type to a
 *                     string and uses that as the key to the exports map.
 *                     The exports map maps an export "uuid" to an export
 *                     directory struct.
 *
 * @file : Exports file struct to insert into
 * @dir  : Export directory to insert
 *
 * Not for external use.
 */
static void
_exp_file_insert(struct exports_file *file, struct export_dir *dir)
{
    data_t *dirdata = NULL;
    uint32_t hashedval = 0;
    uuid_t export_uuid = {
        0,
    };
    char export_uuid_str[512] = {
        0,
    };
    char *dirdup = NULL;

    GF_VALIDATE_OR_GOTO(GF_EXP, file, out);
    GF_VALIDATE_OR_GOTO(GF_EXP, dir, out);

    dirdata = bin_to_data(dir, sizeof(*dir));
    dict_set(file->exports_dict, dir->dir_name, dirdata);

    dirdup = strdupa(dir->dir_name);
    while (strlen(dirdup) > 0 && dirdup[0] == '/')
        dirdup++;

    hashedval = SuperFastHash(dirdup, strlen(dirdup));
    memset(export_uuid, 0, sizeof(export_uuid));
    memcpy(export_uuid, &hashedval, sizeof(hashedval));
    gf_uuid_unparse(export_uuid, export_uuid_str);

    dict_set(file->exports_map, export_uuid_str, dirdata);
out:
    return;
}

/**
 * __exp_item_print_walk -- Print all the keys and values in the dict
 *
 * @dict : the dict to walk
 * @key  : the key in the dict we are currently on
 * @val  : the value in the dict associated with the key
 * @tmp  : Additional parameter data (not used)
 *
 * Passed as a function pointer to dict_foreach ().
 *
 * Not for external use.
 */
static int
__exp_item_print_walk(dict_t *dict, char *key, data_t *val, void *tmp)
{
    if (val)
        _export_item_print((struct export_item *)val->data);

    return 0;
}

/**
 * __exp_file_print_walk -- Print all the keys and values in the dict
 *
 * @dict : the dict to walk
 * @key  : the key in the dict we are currently on
 * @val  : the value in the dict associated with the key
 * @tmp  : Additional parameter data (not used)
 *
 * Passed as a function pointer to dict_foreach ().
 *
 * Not for external use.
 */
static int
__exp_file_print_walk(dict_t *dict, char *key, data_t *val, void *tmp)
{
    if (val) {
        struct export_dir *dir = (struct export_dir *)val->data;

        printf("%s ", key);

        if (dir->netgroups)
            dict_foreach(dir->netgroups, __exp_item_print_walk, NULL);

        if (dir->hosts)
            dict_foreach(dir->hosts, __exp_item_print_walk, NULL);

        printf("\n");
    }
    return 0;
}

/**
 * exp_file_print --  Print out the contents of the exports file
 *
 * @file : Exports file to print
 *
 * Not for external use.
 */
void
exp_file_print(const struct exports_file *file)
{
    GF_VALIDATE_OR_GOTO(GF_EXP, file, out);
    dict_foreach(file->exports_dict, __exp_file_print_walk, NULL);
out:
    return;
}

#define __exp_line_get_opt_val(val, equals, ret, errlabel)                     \
    do {                                                                       \
        (val) = (equals) + 1;                                                  \
        if (!(*(val))) {                                                       \
            (ret) = 1;                                                         \
            goto errlabel;                                                     \
        }                                                                      \
    } while (0)

enum gf_exp_parse_status {
    GF_EXP_PARSE_SUCCESS = 0,
    GF_EXP_PARSE_ITEM_NOT_FOUND = 1,
    GF_EXP_PARSE_ITEM_FAILURE = 2,
    GF_EXP_PARSE_ITEM_NOT_IN_MOUNT_STATE = 3,
    GF_EXP_PARSE_LINE_IGNORING = 4,
};

/**
 * __exp_line_opt_key_value_parse -- Parse the key-value options in the options
 *                                   string.
 *
 * Given a string like (sec=sys,anonuid=0,rw), to parse, this function
 * will get called once with 'sec=sys' and again with 'anonuid=0'.
 * It will check for the '=', make sure there is data to be read
 * after the '=' and copy the data into the options struct.
 *
 * @option    : An option string like sec=sys or anonuid=0
 * @opts      : Pointer to an struct export_options that holds all the export
 *              options.
 *
 * @return: success: GF_EXP_PARSE_SUCCESS
 *          failure: GF_EXP_PARSE_ITEM_FAILURE on parse failure,
 *                   -EINVAL on bad args, -ENOMEM on allocation errors.
 *
 * Not for external use.
 */
static int
__exp_line_opt_key_value_parse(char *option, struct export_options *opts)
{
    char *equals = NULL;
    char *right = NULL;
    char *strmatch = option;
    int ret = -EINVAL;

    GF_VALIDATE_OR_GOTO(GF_EXP, option, out);
    GF_VALIDATE_OR_GOTO(GF_EXP, opts, out);

    equals = strchr(option, '=');
    if (!equals) {
        ret = GF_EXP_PARSE_ITEM_FAILURE;
        goto out;
    }

    *equals = 0;
    /* Now that an '=' has been found the left side is the option and
     * the right side is the value. We simply have to compare those and
     * extract it.
     */
    if (strcmp(strmatch, "anonuid") == 0) {
        *equals = '=';
        /* Get the value for this option */
        __exp_line_get_opt_val(right, equals, ret, out);
        opts->anon_uid = gf_strdup(right);
        GF_CHECK_ALLOC(opts->anon_uid, ret, out);
    } else if (strcmp(strmatch, "sec") == 0) {
        *equals = '=';
        /* Get the value for this option */
        __exp_line_get_opt_val(right, equals, ret, out);
        opts->sec_type = gf_strdup(right);
        GF_CHECK_ALLOC(opts->sec_type, ret, out);
    } else {
        *equals = '=';
        ret = GF_EXP_PARSE_ITEM_FAILURE;
        goto out;
    }

    ret = GF_EXP_PARSE_SUCCESS;
out:
    return ret;
}

/**
 * __exp_line_opt_parse -- Parse the options part of an
 *                          exports or netgroups string.
 *
 * @opt_str     : The option string to parse
 * @exp_opts    : Double pointer to the options we are going
 *                to allocate and setup.
 *
 *
 * @return: success: GF_EXP_PARSE_SUCCESS
 *          failure: GF_EXP_PARSE_ITEM_FAILURE on parse failure,
 *                   -EINVAL on bad args, -ENOMEM on allocation errors.
 *
 * Not for external use.
 */
static int
__exp_line_opt_parse(const char *opt_str, struct export_options **exp_opts)
{
    struct export_options *opts = NULL;
    char *strmatch = NULL;
    int ret = -EINVAL;
    char *equals = NULL;

    ret = parser_set_string(options_parser, opt_str);
    if (ret < 0)
        goto out;

    while ((strmatch = parser_get_next_match(options_parser))) {
        if (!opts) {
            /* If the options have not been allocated,
             * allocate it.
             */
            opts = _export_options_init();
            if (!opts) {
                ret = -ENOMEM;
                parser_unset_string(options_parser);
                GF_FREE(strmatch);
                goto out;
            }
        }

        /* First,  check for all the boolean options Second, check for
         * an '=', and check the available options there. The string
         * parsing here gets slightly messy, but the concept itself
         * is pretty simple.
         */
        equals = strchr(strmatch, '=');
        if (strcmp(strmatch, "root") == 0)
            opts->root = _gf_true;
        else if (strcmp(strmatch, "ro") == 0)
            opts->rw = _gf_false;
        else if (strcmp(strmatch, "rw") == 0)
            opts->rw = _gf_true;
        else if (strcmp(strmatch, "nosuid") == 0)
            opts->nosuid = _gf_true;
        else if (equals) {
            ret = __exp_line_opt_key_value_parse(strmatch, opts);
            if (ret < 0) {
                /* This means invalid key value options were
                 * specified, or memory allocation failed.
                 * The ret value gets bubbled up to the caller.
                 */
                GF_FREE(strmatch);
                parser_unset_string(options_parser);
                _export_options_deinit(opts);
                goto out;
            }
        } else {
            /* Cannot change to gf_msg.
             * gf_msg not giving output to STDOUT
             * Bug id : BZ1215017
             */
            gf_log(GF_EXP, GF_LOG_WARNING,
                   "Could not find any valid options for "
                   "string: %s",
                   strmatch);
        }
        GF_FREE(strmatch);
    }

    if (!opts) {
        /* If opts is not allocated
         * that means no matches were found
         * which is a parse error. Not marking
         * it as "not found" because it is a parse
         * error to not have options.
         */
        ret = GF_EXP_PARSE_ITEM_FAILURE;
        parser_unset_string(options_parser);
        goto out;
    }

    *exp_opts = opts;
    parser_unset_string(options_parser);
    ret = GF_EXP_PARSE_SUCCESS;
out:
    return ret;
}

/**
 * __exp_line_ng_host_str_parse -- Parse the netgroup or host string
 *
 *      e.g. @mygroup(<options>), parsing @mygroup and (<options>)
 *      or   myhost001.dom(<options>), parsing myhost001.dom and (<options>)
 *
 * @line      : The line to parse
 * @exp_item  : Double pointer to a struct export_item
 *
 * @return: success: GF_PARSE_SUCCESS
 *          failure: GF_EXP_PARSE_ITEM_FAILURE on parse failure,
 *                   -EINVAL on bad args, -ENOMEM on allocation errors.
 *
 * Not for external use.
 */
static int
__exp_line_ng_host_str_parse(char *str, struct export_item **exp_item)
{
    struct export_item *item = NULL;
    int ret = -EINVAL;
    char *parens = NULL;
    char *optstr = NULL;
    struct export_options *exp_opts = NULL;
    char *item_name = NULL;

    GF_VALIDATE_OR_GOTO(GF_EXP, str, out);
    GF_VALIDATE_OR_GOTO(GF_EXP, exp_item, out);

    /* A netgroup/host string looks like this:
     * @test(sec=sys,rw,anonuid=0) or host(sec=sys,rw,anonuid=0)
     * We want to extract the name, 'test' or 'host'
     * Again, we could setup a regex and use it here,
     * but its simpler to find the '(' and copy until
     * there.
     */
    parens = strchr(str, '(');
    if (!parens) {
        /* Parse error if there are no parens. */
        ret = GF_EXP_PARSE_ITEM_FAILURE;
        goto out;
    }

    *parens = '\0'; /* Temporarily terminate it so we can do a copy */

    if (strlen(str) > FQDN_MAX_LEN) {
        ret = GF_EXP_PARSE_ITEM_FAILURE;
        goto out;
    }

    /* Strip leading whitespaces */
    while (*str == ' ' || *str == '\t')
        str++;

    item_name = gf_strdup(str);
    GF_CHECK_ALLOC(item_name, ret, out);

    gf_msg_trace(GF_EXP, 0, "found hostname/netgroup: %s", item_name);

    /* Initialize an export item for this */
    item = _export_item_init();
    GF_CHECK_ALLOC(item, ret, free_and_out);
    item->name = item_name;

    *parens = '('; /* Restore the string */

    /* Options start at the parentheses */
    optstr = parens;

    ret = __exp_line_opt_parse(optstr, &exp_opts);
    if (ret != 0) {
        /* Bubble up the error to the caller */
        GF_REF_PUT(item);
        goto out;
    }

    item->opts = exp_opts;

    *exp_item = item;

    ret = GF_EXP_PARSE_SUCCESS;
    goto out;

free_and_out:
    GF_FREE(item_name);
out:
    return ret;
}

/**
 * __exp_line_ng_parse -- Extract the netgroups in the line
 *                        and call helper functions to parse
 *                        the string.
 *
 * The call chain goes like this:
 *
 * 1) __exp_line_ng_parse ("/test  @test(sec=sys,rw,anonuid=0)")
 * 2) __exp_line_ng_str_parse ("@test(sec=sys,rw,anonuid=0)");
 * 3) __exp_line_opt_parse("(sec=sys,rw,anonuid=0)");
 *
 *
 * @line    : The line to parse
 * @ng_dict : Double pointer to the dict we want to
 *            insert netgroups into.
 *
 * Allocates the dict, extracts netgroup strings from the line,
 * parses them into a struct export_item structure and inserts
 * them in the dict.
 *
 * @return: success: GF_EXP_PARSE_SUCCESS
 *          failure: GF_EXP_PARSE_ITEM_FAILURE on parse failure,
 *                   GF_EXP_PARSE_ITEM_NOT_FOUND if the netgroup was not found
 *                   -EINVAL on bad args, -ENOMEM on allocation errors.
 *
 * Not for external use.
 */
static int
__exp_line_ng_parse(const char *line, dict_t **ng_dict)
{
    dict_t *netgroups = NULL;
    char *strmatch = NULL;
    int ret = -EINVAL;
    struct export_item *exp_ng = NULL;
    data_t *ngdata = NULL;

    GF_VALIDATE_OR_GOTO(GF_EXP, line, out);
    GF_VALIDATE_OR_GOTO(GF_EXP, ng_dict, out);

    *ng_dict = NULL; /* Will be set if parse is successful */

    /* Initialize a parser with the line to parse
     * and the regex used to parse it.
     */
    ret = parser_set_string(netgroup_parser, line);
    if (ret < 0) {
        goto out;
    }

    gf_msg_trace(GF_EXP, 0, "parsing line: %s", line);

    while ((strmatch = parser_get_next_match(netgroup_parser))) {
        if (!netgroups) {
            /* Allocate a new dict to store the netgroups. */
            netgroups = dict_new();
            if (!netgroups) {
                ret = -ENOMEM;
                goto free_and_out;
            }
        }

        gf_msg_trace(GF_EXP, 0, "parsing netgroup: %s", strmatch);

        ret = __exp_line_ng_host_str_parse(strmatch, &exp_ng);

        if (ret != 0) {
            /* Parsing or other critical errors.
             * caller will handle return value.
             */
            _exp_dict_destroy(netgroups);
            goto free_and_out;
        }

        ngdata = bin_to_data(exp_ng, sizeof(*exp_ng));
        dict_set(netgroups, exp_ng->name, ngdata);

        /* Free this matched string and continue parsing. */
        GF_FREE(strmatch);
    }

    /* If the netgroups dict was not allocated, then we know that
     * no matches were found.
     */
    if (!netgroups) {
        ret = GF_EXP_PARSE_ITEM_NOT_FOUND;
        parser_unset_string(netgroup_parser);
        goto out;
    }

    ret = GF_EXP_PARSE_SUCCESS;
    *ng_dict = netgroups;

free_and_out:
    parser_unset_string(netgroup_parser);
    GF_FREE(strmatch);
out:
    return ret;
}

/**
 * __exp_line_host_parse -- Extract the hosts in the line
 *                          and call helper functions to parse
 *                          the string.
 *
 * The call chain goes like this:
 *
 * 1) __exp_line_host_parse ("/test  hostip(sec=sys,rw,anonuid=0)")
 * 2) __exp_line_ng_host_str_parse ("hostip(sec=sys,rw,anonuid=0)");
 * 3) __exp_line_opt_parse("(sec=sys,rw,anonuid=0)");
 *
 *
 * @line    : The line to parse
 * @ng_dict : Double pointer to the dict we want to
 *            insert hosts into.
 *
 * Allocates the dict, extracts host strings from the line,
 * parses them into a struct export_item structure and inserts
 * them in the dict.
 *
 * @return: success: GF_EXP_PARSE_SUCCESS
 *          failure: GF_EXP_PARSE_ITEM_FAILURE on parse failure,
 *                   GF_EXP_PARSE_ITEM_NOT_FOUND if the host was not found,
 *                   -EINVAL on bad args, -ENOMEM on allocation errors.
 *
 * Not for external use.
 */
static int
__exp_line_host_parse(const char *line, dict_t **host_dict)
{
    dict_t *hosts = NULL;
    char *strmatch = NULL;
    int ret = -EINVAL;
    struct export_item *exp_host = NULL;
    data_t *hostdata = NULL;

    GF_VALIDATE_OR_GOTO(GF_EXP, line, out);
    GF_VALIDATE_OR_GOTO(GF_EXP, host_dict, out);

    *host_dict = NULL;

    /* Initialize a parser with the line to parse and the regex used to
     * parse it.
     */
    ret = parser_set_string(hostname_parser, line);
    if (ret < 0) {
        goto out;
    }

    gf_msg_trace(GF_EXP, 0, "parsing line: %s", line);

    while ((strmatch = parser_get_next_match(hostname_parser))) {
        if (!hosts) {
            /* Allocate a new dictto store the netgroups. */
            hosts = dict_new();
            GF_CHECK_ALLOC(hosts, ret, free_and_out);
        }

        gf_msg_trace(GF_EXP, 0, "parsing hostname: %s", strmatch);

        ret = __exp_line_ng_host_str_parse(strmatch, &exp_host);

        if (ret != 0) {
            /* Parsing or other critical error, free allocated
             * memory and exit. The caller will handle the errors.
             */
            _exp_dict_destroy(hosts);
            goto free_and_out;
        }

        /* Insert export item structure into the hosts dict. */
        hostdata = bin_to_data(exp_host, sizeof(*exp_host));
        dict_set(hosts, exp_host->name, hostdata);

        /* Free this matched string and continue parsing.*/
        GF_FREE(strmatch);
    }

    /* If the hosts dict was not allocated, then we know that
     * no matches were found.
     */
    if (!exp_host) {
        ret = GF_EXP_PARSE_ITEM_NOT_FOUND;
        parser_unset_string(hostname_parser);
        goto out;
    }

    ret = GF_EXP_PARSE_SUCCESS;
    *host_dict = hosts;

free_and_out:
    parser_unset_string(hostname_parser);
    GF_FREE(strmatch);
out:
    return ret;
}

/**
 * __exp_line_dir_parse -- Extract directory name from a line in the exports
 *                         file.
 *
 * @line    : The line to parse
 * @dirname : Double pointer to the string we need to hold the directory name.
 *            If the parsing failed, the string will point to NULL, otherwise
 *            it will point to a valid memory region that is allocated by
 *            this function.
 * @check_ms: If this variable is set then we cross check the directory line
 *            with what's in gluster's vol files and reject them if they don't
 *            match.
 *
 * @return : success: GF_EXP_PARSE_SUCCESS
 *           failure: GF_EXP_PARSE_ITEM_FAILURE on parse failure,
 *           -EINVAL on bad arguments, -ENOMEM on allocation failures,
 *           GF_EXP_PARSE_ITEM_NOT_IN_MOUNT_STATE if we failed to match
 *           with gluster's mountstate.
 *
 * The caller is responsible for freeing memory allocated by this function
 *
 * Not for external use.
 */
static int
__exp_line_dir_parse(const char *line, char **dirname, struct mount3_state *ms)
{
    char *dir = NULL;
    char *delim = NULL;
    int ret = -EINVAL;
    char *linedup = NULL;
    struct mnt3_export *mnt3export = NULL;
    size_t dirlen = 0;

    GF_VALIDATE_OR_GOTO(GF_EXP, line, out);
    GF_VALIDATE_OR_GOTO(GF_EXP, dirname, out);

    /* Duplicate the line because we don't
     * want to modify the original string.
     */
    linedup = strdupa(line);

    /* We use strtok_r () here to split the string by space/tab and get the
     * the result. We only need the first result of the split.
     * a simple task. It is worth noting that dirnames always have to be
     * validated against gluster's vol files so if they don't
     * match it will be rejected.
     */
    dir = linedup;
    delim = linedup + strcspn(linedup, " \t");
    *delim = 0;

    if (ms) {
        /* Match the directory name with an existing
         * export in the mount state.
         */
        mnt3export = mnt3_mntpath_to_export(ms, dir, _gf_true);
        if (!mnt3export) {
            gf_msg_debug(GF_EXP, 0,
                         "%s not in mount state, "
                         "ignoring!",
                         dir);
            ret = GF_EXP_PARSE_ITEM_NOT_IN_MOUNT_STATE;
            goto out;
        }
    }

    /* Directories can be 1024 bytes in length, check
     * that the argument provided adheres to
     * that restriction.
     */
    if (strlen(dir) > DIR_MAX_LEN) {
        ret = -EINVAL;
        goto out;
    }

    /* Copy the result of the split */
    dir = gf_strdup(dir);
    GF_CHECK_ALLOC(dir, ret, out);

    /* Ensure that trailing slashes are stripped before storing the name */
    dirlen = strlen(dir);
    if (dirlen > 0 && dir[dirlen - 1] == '/')
        dir[dirlen - 1] = '\0';

    /* Set the argument to point to the allocated string */
    *dirname = dir;
    ret = GF_EXP_PARSE_SUCCESS;
out:
    return ret;
}

/**
 * _exp_line_parse -- Parse a line in an exports file into a structure
 *                    that holds all the parts of the line. An exports
 *                    structure has a dict of netgroups and a dict of hosts.
 *
 * An export line looks something like this /test  @test(sec=sys,rw,anonuid=0)
 * or /test  @test(sec=sys,rw,anonuid=0) hostA(sec=sys,rw,anonuid=0), etc.
 *
 * We use regexes to parse the line into three separate pieces:
 * 1) The directory (exports.h -- DIRECTORY_REGEX_PATTERN)
 * 2) The netgroup if it exists (exports.h -- NETGROUP_REGEX_PATTERN)
 * 3) The host if it exists (exports.h -- HOST_REGEX_PATTERN)
 *
 * In this case, the netgroup would be @test(sec=sys,rw,anonuid=0)
 * and the host would be hostA(sec=sys,rw,anonuid=0).
 *
 * @line        : The line to parse
 * @dir         : Double pointer to the struct we need to parse the line into.
 *                If the parsing failed, the struct will point to NULL,
 *                otherwise it will point to a valid memory region that is
 *                allocated by this function.
 * @parse_full  : This parameter tells us whether we should parse all the lines
 *                in the file, even if they are not present in gluster's config.
 *                The gluster config holds the volumes that it exports so
 *                if parse_full is set to FALSE then we will ensure that
 *                the export file structure holds only those volumes
 *                that gluster has exported. It is important to note that
 *                If gluster exports a volume named '/test', '/test' and all
 *                of its subdirectories that may be in the exports file
 *                are valid exports.
 *  @ms         : The mount state that holds the list of volumes that gluster
 *                currently exports.
 *
 * @return : success: GF_EXP_PARSE_SUCCESS on success, -EINVAL on bad arguments,
 *                    -ENOMEM on memory allocation errors,
 *                    GF_EXP_PARSE_LINE_IGNORING if we ignored the line,
 *                    GF_EXP_PARSE_ITEM_FAILURE if there was error parsing
 *           failure: NULL
 *
 * The caller is responsible for freeing memory allocated by this function
 * The caller should free this memory using the _exp_dir_deinit () function.
 *
 * Not for external use.
 */
static int
_exp_line_parse(const char *line, struct export_dir **dir,
                gf_boolean_t parse_full, struct mount3_state *ms)
{
    struct export_dir *expdir = NULL;
    char *dirstr = NULL;
    dict_t *netgroups = NULL;
    dict_t *hosts = NULL;
    int ret = -EINVAL;
    gf_boolean_t netgroups_failed = _gf_false;

    GF_VALIDATE_OR_GOTO(GF_EXP, line, out);
    GF_VALIDATE_OR_GOTO(GF_EXP, dir, out);

    if (*line == '#' || *line == ' ' || *line == '\t' || *line == '\0' ||
        *line == '\n') {
        ret = GF_EXP_PARSE_LINE_IGNORING;
        goto out;
    }

    expdir = _export_dir_init();
    if (!expdir) {
        *dir = NULL;
        ret = -ENOMEM;
        goto out;
    }

    /* Get the directory string from the line */
    ret = __exp_line_dir_parse(line, &dirstr, ms);
    if (ret < 0) {
        gf_msg(GF_EXP, GF_LOG_ERROR, 0, NFS_MSG_PARSE_DIR_FAIL,
               "Parsing directory failed: %s", strerror(-ret));
        /* If parsing the directory failed,
         * we should simply fail because there's
         * nothing else we can extract from the string to make
         * the data valuable.
         */
        goto free_and_out;
    }

    /* Set the dir str */
    expdir->dir_name = dirstr;

    /* Parse the netgroup part of the string */
    ret = __exp_line_ng_parse(line, &netgroups);
    if (ret < 0) {
        gf_msg(GF_EXP, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL,
               "Critical error: %s", strerror(-ret));
        /* Return values less than 0
         * indicate critical failures (null parameters,
         * failure to allocate memory, etc).
         */
        goto free_and_out;
    }
    if (ret != 0) {
        if (ret == GF_EXP_PARSE_ITEM_FAILURE)
            /* Cannot change to gf_msg.
             * gf_msg not giving output to STDOUT
             * Bug id : BZ1215017
             */
            gf_log(GF_EXP, GF_LOG_WARNING, "Error parsing netgroups for: %s",
                   line);
        /* Even though parsing failed for the netgroups we should let
         * host parsing proceed.
         */
        netgroups_failed = _gf_true;
    }

    /* Parse the host part of the string */
    ret = __exp_line_host_parse(line, &hosts);
    if (ret < 0) {
        gf_msg(GF_EXP, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL,
               "Critical error: %s", strerror(-ret));
        goto free_and_out;
    }
    if (ret != 0) {
        if (ret == GF_EXP_PARSE_ITEM_FAILURE)
            gf_msg(GF_EXP, GF_LOG_WARNING, 0, NFS_MSG_PARSE_FAIL,
                   "Error parsing hosts for: %s", line);
        /* If netgroups parsing failed, AND
         * host parsing failed, then there's something really
         * wrong with this line, so we're just going to
         * log it and fail out.
         */
        if (netgroups_failed)
            goto free_and_out;
    }

    expdir->hosts = hosts;
    expdir->netgroups = netgroups;
    *dir = expdir;
    goto out;

free_and_out:
    _export_dir_deinit(expdir);
out:
    return ret;
}

struct export_item *
exp_dir_get_netgroup(const struct export_dir *expdir, const char *netgroup)
{
    struct export_item *lookup_res = NULL;
    data_t *dict_res = NULL;

    GF_VALIDATE_OR_GOTO(GF_EXP, expdir, out);
    GF_VALIDATE_OR_GOTO(GF_EXP, netgroup, out);

    if (!expdir->netgroups)
        goto out;

    dict_res = dict_get(expdir->netgroups, (char *)netgroup);
    if (!dict_res) {
        gf_msg_debug(GF_EXP, 0, "%s not found for %s", netgroup,
                     expdir->dir_name);
        goto out;
    }

    lookup_res = (struct export_item *)dict_res->data;
out:
    return lookup_res;
}
/**
 * exp_dir_get_host -- Given a host string and an exports directory structure,
 *                     find and return an struct export_item structure that
 *                     represents the requested host.
 *
 * @expdir: Export directory to lookup from
 * @host  : Host string to lookup
 *
 * @return: success: Pointer to a export item structure
 *          failure: NULL
 */
struct export_item *
exp_dir_get_host(const struct export_dir *expdir, const char *host)
{
    struct export_item *lookup_res = NULL;
    data_t *dict_res = NULL;

    GF_VALIDATE_OR_GOTO(GF_EXP, expdir, out);
    GF_VALIDATE_OR_GOTO(GF_EXP, host, out);

    if (!expdir->hosts)
        goto out;

    dict_res = dict_get(expdir->hosts, (char *)host);
    if (!dict_res) {
        gf_msg_debug(GF_EXP, 0, "%s not found for %s", host, expdir->dir_name);

        /* Check if wildcards are allowed for the host */
        dict_res = dict_get(expdir->hosts, "*");
        if (!dict_res) {
            goto out;
        }
    }

    lookup_res = (struct export_item *)dict_res->data;
out:
    return lookup_res;
}

/**
 * exp_file_get_dir -- Return an export dir given a directory name
 *                     Does a lookup from the dict in the file structure.
 *
 * @file : Exports file structure to lookup from
 * @dir  : Directory name to lookup
 *
 * @return : success: Pointer to an export directory structure
 *           failure: NULL
 */
struct export_dir *
exp_file_get_dir(const struct exports_file *file, const char *dir)
{
    struct export_dir *lookup_res = NULL;
    data_t *dict_res = NULL;
    char *dirdup = NULL;
    size_t dirlen = 0;

    GF_VALIDATE_OR_GOTO(GF_EXP, file, out);
    GF_VALIDATE_OR_GOTO(GF_EXP, dir, out);

    dirlen = strlen(dir);
    if (dirlen <= 0)
        goto out;

    dirdup = (char *)dir; /* Point at the directory */

    /* If directories don't contain a leading slash */
    if (*dir != '/') {
        dirdup = alloca(dirlen + 2); /* Leading slash & null byte */
        snprintf(dirdup, dirlen + 2, "/%s", dir);
    }

    dict_res = dict_get(file->exports_dict, dirdup);
    if (!dict_res) {
        gf_msg_debug(GF_EXP, 0, "%s not found in %s", dirdup, file->filename);
        goto out;
    }

    lookup_res = (struct export_dir *)dict_res->data;
out:
    return lookup_res;
}

/**
 * exp_file_parse -- Parse an exports file into a structure
 *                   that can be looked up through simple
 *                   function calls.
 *
 * @filepath: Path to the exports file
 * @ms      : Current mount state (useful to match with gluster vol files)
 *
 * @return  : success: 0
 *            failure: -1 on parsing failure, -EINVAL on bad arguments,
 *                     -ENOMEM on allocation failures.
 *
 * The caller is responsible for freeing memory allocated by this function.
 * The caller should free this memory using the exp_file_deinit () function.
 * Calling GF_FREE ( ) on the pointer will NOT free all the allocated memory.
 *
 * Externally usable.
 */
int
exp_file_parse(const char *filepath, struct exports_file **expfile,
               struct mount3_state *ms)
{
    FILE *fp = NULL;
    struct exports_file *file = NULL;
    size_t len = 0;
    int ret = -EINVAL;
    unsigned long line_number = 0;
    char *line = NULL;
    struct export_dir *expdir = NULL;

    /* Sets whether we we should parse the entire file or just that which
     * is present in the mount state */
    gf_boolean_t parse_complete_file = _gf_false;

    GF_VALIDATE_OR_GOTO(GF_EXP, expfile, parse_done);

    if (!ms) {
        /* If mount state is null that means that we
         * should go through and parse the whole file
         * since we don't have anything to compare against.
         */
        parse_complete_file = _gf_true;
    }

    fp = fopen(filepath, "r");
    if (!fp) {
        ret = -errno;
        goto parse_done;
    }

    ret = _exp_init_parsers();
    if (ret < 0)
        goto parse_done;

    /* Process the file line by line, with each line being parsed into
     * an struct export_dir struct. If 'parse_complete_file' is set to TRUE
     * then
     */
    while (getline(&line, &len, fp) != -1) {
        line_number++;      /* Keeping track of line number allows us to
                             * to log which line numbers were wrong
                             */
        strtok(line, "\n"); /* removes the newline character from
                             * the line
                             */

        /* Parse the line from the file into an struct export_dir
         * structure. The process is as follows:
         * Given a line like :
         * "/vol @test(sec=sys,rw,anonuid=0) 10.35.11.31(sec=sys,rw)"
         *
         * This function will allocate an export dir and set its name
         * to '/vol', using the function _exp_line_dir_parse ().
         *
         * Then it will extract the netgroups from the line, in this
         * case it would be '@test(sec=sys,rw,anonuid=0)', and set the
         * item structure's name to '@test'.
         * It will also extract the options from that string and parse
         * them into an struct export_options which will be pointed
         * to by the item structure. This will be put into a dict
         * which will be pointed to by the export directory structure.
         *
         * The same process happens above for the host string
         * '10.35.11.32(sec=sys,rw)'
         */
        ret = _exp_line_parse(line, &expdir, parse_complete_file, ms);
        if (ret == -ENOMEM) {
            /* If we get memory allocation errors, we really should
             * not continue parsing, so just free the allocated
             * memory and exit.
             */
            goto free_and_done;
        }

        if (ret < 0) {
            gf_msg(GF_EXP, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL,
                   "Failed to parse line #%lu", line_number);
            continue; /* Skip entering this line and continue */
        }

        if (ret == GF_EXP_PARSE_LINE_IGNORING) {
            /* This just means the line was empty or started with a
             * '#' or a ' ' and we are ignoring it.
             */
            gf_msg_debug(GF_EXP, 0,
                         "Ignoring line #%lu because it started "
                         "with a %c",
                         line_number, *line);
            continue;
        }

        if (!file) {
            file = _exports_file_init();
            GF_CHECK_ALLOC_AND_LOG(GF_EXP, file, ret,
                                   "Allocation error while "
                                   "allocating file struct",
                                   free_and_done);

            file->filename = gf_strdup(filepath);
            GF_CHECK_ALLOC_AND_LOG(GF_EXP, file, ret,
                                   "Allocation error while "
                                   "duping filepath",
                                   free_and_done);
        }

        /* If the parsing is successful store the export directory
         * in the file structure.
         */
        _exp_file_insert(file, expdir);
    }

    /* line got allocated through getline(), don't use GF_FREE() for it */
    free(line);

    *expfile = file;
    goto parse_done;

free_and_done:
    if (file)
        exp_file_deinit(file);
    _export_dir_deinit(expdir);

parse_done:
    if (fp)
        fclose(fp);
    _exp_deinit_parsers();
    return ret;
}
