/*
   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 "netgroups.h"
#include "parse-utils.h"
#include "nfs-messages.h"

static void _nge_print (const struct netgroup_entry *nge);
static void _netgroup_entry_deinit (struct netgroup_entry *ptr);
static void _netgroup_host_deinit (struct netgroup_host *host);

static dict_t *__deleted_entries;
static struct parser *ng_file_parser;
static struct parser *ng_host_parser;

/**
 * _ng_init_parser -- Initialize the parsers used in this file
 *
 * @return: success: 0 (on success the parsers are initialized)
 *          failure: -1
 */
static int
_ng_init_parsers ()
{
        int ret = -1;

        /* Initialize the parsers. The only reason this should
         * ever fail is because of 1) memory allocation errors
         * 2) the regex in netgroups.h has been changed and no
         * longer compiles.
         */
        ng_file_parser = parser_init (NG_FILE_PARSE_REGEX);
        if (!ng_file_parser)
                goto out;

        ng_host_parser = parser_init (NG_HOST_PARSE_REGEX);
        if (!ng_host_parser)
                goto out;

        ret = 0;
out:
        return ret;
}

/**
 * _ng_deinit_parsers - Free the parsers used in this file
 */
static void
_ng_deinit_parsers ()
{
        parser_deinit (ng_file_parser);
        parser_deinit (ng_host_parser);
}

/**
 * _netgroups_file_init - allocate a netgroup file struct
 * @return: success: Pointer to an allocated netgroup file struct
 *          failure: NULL
 *
 * Not for external use.
 */
static struct netgroups_file *
_netgroups_file_init ()
{
        struct netgroups_file *file  = GF_MALLOC (sizeof (*file),
                                                  gf_common_mt_nfs_netgroups);

        if (!file)
                goto out;

        file->filename     = NULL;
        file->ng_file_dict = NULL;
out:
        return file;
}

/**
 * __ngf_free_walk - walk the netgroup file dict and free each element
 *
 * This is passed as a function pointer to dict_foreach ()
 *
 * @dict: the dict we are walking
 * @key : the key we are processing in the dict
 * @val : the corresponding value in the dict
 * @tmp : Pointer to additional data that may be passed in (not used)
 *
 * @return : Nothing
 *
 * Not for external use.
 */
static int
__ngf_free_walk (dict_t *dict, char *key, data_t *val, void *tmp)
{
        struct netgroup_entry        *nge = NULL;

        if (val) {
                nge = (struct netgroup_entry *)val->data;
                _netgroup_entry_deinit (nge);
                val->data = NULL;
                dict_del (dict, key); /* Remove the key from this dict */
        }
        return 0;
}

/**
 * __deleted_entries_free_walk - free the strings in the temporary dict
 *
 * This is passed as a function pointer to dict_foreach ()
 *
 * @dict: the dict we are walking
 * @key : the key we are processing in the dict
 * @val : the corresponding value in the dict
 * @tmp : Pointer to additional data that may be passed in (not used)
 *
 * @return : Nothing
 *
 * Not for external use.
 */
static int
__deleted_entries_free_walk (dict_t *dict, char *key, data_t *val, void *tmp)
{
        dict_del (dict, key);
        return 0;
}

/**
 * ng_file_deinit - Free the netgroup file struct and any memory
 * that is allocated for its members.
 *
 * @ngfile : Pointer to the netgroup file structure that needs to be freed
 * @return : Nothing
 *
 * External facing function.
 *
 * Should be called by the caller of ng_file_parse () in order to free
 * the memory allocated when parsing the file.
 */
void
ng_file_deinit (struct netgroups_file *ngfile)
{
        GF_VALIDATE_OR_GOTO (GF_NG, ngfile, out);

        __deleted_entries = dict_new ();
        GF_VALIDATE_OR_GOTO (GF_NG, __deleted_entries, out);

        GF_FREE (ngfile->filename);
        dict_foreach (ngfile->ng_file_dict, __ngf_free_walk, NULL);
        dict_unref (ngfile->ng_file_dict);
        GF_FREE (ngfile);

        /* Clean up temporary dict we used to store "freed" names */
        dict_foreach (__deleted_entries, __deleted_entries_free_walk, NULL);
        dict_unref (__deleted_entries);
        __deleted_entries = NULL;
out:
        return;
}

/**
 * _netgroup_entry_init - Initializes a netgroup entry struct.
 * A netgroup entry struct represents a single line in a netgroups file.
 *
 * @return : success: Pointer to a netgroup entry struct
 *         : failure: NULL
 *
 * Not for external use.
 */
static struct netgroup_entry *
_netgroup_entry_init ()
{
        struct netgroup_entry *entry = GF_CALLOC (1, sizeof (*entry),
                                                  gf_common_mt_nfs_netgroups);
        return entry;
}

/**
 * __ngh_free_walk - walk the netgroup host dict and free the host
 * structure associated with the key.
 *
 * This is passed as a function pointer to dict_foreach ()
 *
 * @dict: the dict we are walking
 * @key : the key we are processing in the dict
 * @val : the corresponding value in the dict
 * @tmp : Pointer to additional data that may be passed in (not used)
 *
 * @return : Nothing
 *
 * Not for external use.
 */
static int
__ngh_free_walk (dict_t *dict, char *key, data_t *val, void *tmp)
{
        struct netgroup_host *ngh = NULL;

        if (val) {
                ngh = (struct netgroup_host *)val->data;
                _netgroup_host_deinit (ngh);
                val->data = NULL;
                dict_del (dict, key);
        }
        return 0;
}

/**
 * __nge_free_walk - walk the netgroup entry dict and free the netgroup entry
 * structure associated with the key.
 *
 * This is passed as a function pointer to dict_foreach ()
 *
 * @dict: the dict we are walking
 * @key : the key we are processing in the dict
 * @val : the corresponding value in the dict
 * @tmp : Pointer to additional data that may be passed in (not used)
 *
 * @return : Nothing
 *
 * Not for external use.
 */
static int
__nge_free_walk (dict_t *dict, char *key, data_t *val, void *tmp)
{
        struct netgroup_entry *nge = NULL;

        GF_VALIDATE_OR_GOTO (GF_NG, dict, out);

        if (val) {
                nge = (struct netgroup_entry *)val->data;
                if (!dict_get (__deleted_entries, key)) {
                        _netgroup_entry_deinit (nge);
                        val->data = NULL;
                }
                dict_del (dict, key);
        }

out:
        return 0;
}

/**
 * _netgroup_entry_deinit - Free memory pointed to by the parameter
 *                          and any memory allocated for members
 *                          in the struct. This function walks the
 *                          netgroups and hosts dicts if they
 *                          are allocated and frees them.
 *
 * @ngentry: Pointer to a netgroup entry struct that needs to be freed
 *
 * @return : Nothing
 *
 * Not for external use.
 */
static void
_netgroup_entry_deinit (struct netgroup_entry *ngentry)
{
        dict_t  *ng_dict   = NULL;
        dict_t  *host_dict = NULL;
        char    *name      = NULL;
        data_t  *dint      = NULL;

        if (!ngentry)
                return;

        ng_dict = ngentry->netgroup_ngs;
        host_dict = ngentry->netgroup_hosts;

        if (ng_dict) {
                /* Free the dict of netgroup entries */
                dict_foreach (ng_dict, __nge_free_walk, NULL);
                dict_unref (ng_dict);
                ngentry->netgroup_ngs = NULL;
        }

        if (host_dict) {
                /* Free the dict of host entries */
                dict_foreach (host_dict, __ngh_free_walk, NULL);
                dict_unref (host_dict);
                ngentry->netgroup_hosts = NULL;
        }

        if (ngentry->netgroup_name) {
                /* Keep track of the netgroup names we've deallocated
                 * We need to do this because of the nature of this data
                 * structure. This data structure may hold multiple
                 * pointers to an already freed object, but these are
                 * uniquely identifiable by the name. We keep track
                 * of these names so when we encounter a key who has
                 * an association to an already freed object, we don't
                 * free it twice.
                 */
                name = strdupa (ngentry->netgroup_name);

                dint = int_to_data (1);
                dict_set (__deleted_entries, name, dint);

                GF_FREE (ngentry->netgroup_name);
                ngentry->netgroup_name = NULL;
        }

        GF_FREE (ngentry);
}

/**
 * _netgroup_host_init - Initializes a netgroup host structure.
 * A netgroup host struct represents an item in a line of a netgroups file that
 * looks like this : (hostname,user,domain)
 *
 * @return : success: Pointer to a netgroup host struct
 *         : failure: NULL
 *
 * Not for external use.
 */
static struct netgroup_host *
_netgroup_host_init ()
{
        struct netgroup_host *host = GF_CALLOC (1, sizeof (*host),
                                                gf_common_mt_nfs_netgroups);
        return host;
}

/**
 * _netgroup_host_deinit - Free memory pointed to by the parameter
 * and any memory allocated for members in the struct.
 *
 * @nghost : Pointer to a netgroup host struct that needs to be freed
 *
 * @return : Nothing
 *
 * Not for external use.
 */
static void
_netgroup_host_deinit (struct netgroup_host *host)
{
        /* Validate args */
        GF_VALIDATE_OR_GOTO (GF_NG, host, err);

        GF_FREE (host->hostname);
        host->hostname = NULL;

        GF_FREE (host->user);
        host->user = NULL;

        GF_FREE (host->domain);
        host->domain = NULL;

        GF_FREE (host);
err:
        return;
}

/**
 * _nge_dict_get - Lookup a netgroup entry from the dict based
 *                 on the netgroup name.
 *
 * @dict   : The dict we are looking up from. This function makes the
 *           assumption that the type of underlying data in the dict is of type
 *           struct netgroup_entry. The behavior is not defined otherwise.
 *
 * @ngname : Key used to lookup in the dict.
 *
 * @return : success: Pointer to a netgroup entry
 *           failure: NULL (if no such key exists in the dict)
 *
 * Not for external use.
 */
static struct netgroup_entry *
_nge_dict_get (dict_t *dict, const char *ngname)
{
        data_t *ngdata = NULL;

        /* Validate args */
        GF_VALIDATE_OR_GOTO (GF_NG, dict, err);
        GF_VALIDATE_OR_GOTO (GF_NG, ngname, err);

        ngdata = dict_get (dict, (char *)ngname);
        if (ngdata)
                return (struct netgroup_entry *)ngdata->data;
err:
        return NULL;
}

/**
 * _nge_dict_insert - Insert a netgroup entry into the dict using
 *                    the netgroup name as the key.
 *
 * @dict   : The dict we are inserting into.
 *
 * @nge    : The data to insert into the dict.
 *
 * @return : nothing
 *
 * Not for external use.
 */
static void
_nge_dict_insert (dict_t *dict, struct netgroup_entry *nge)
{
        data_t *ngdata = NULL;

        GF_VALIDATE_OR_GOTO (GF_NG, dict, err);
        GF_VALIDATE_OR_GOTO (GF_NG, nge, err);

        ngdata = bin_to_data (nge, sizeof (*nge));
        dict_set (dict, nge->netgroup_name, ngdata);
err:
        return;
}

/**
 * _ngh_dict_get - Lookup a netgroup host entry from the dict based
 *                 on the hostname.
 *
 * @dict   : The dict we are looking up from. This function makes the
 *           assumption that the type of underlying data in the dict is of type
 *           struct netgroup_host. The behavior is not defined otherwise.
 *
 * @ngname : Key used to lookup in the dict.
 *
 * @return : success: Pointer to a netgroup host entry
 *           failure: NULL (if no such key exists in the dict)
 *
 * Externally usable.
 */
struct netgroup_host *
ngh_dict_get (dict_t *dict, const char *hostname)
{
        data_t *ngdata = NULL;

        GF_VALIDATE_OR_GOTO (GF_NG, dict, err);
        GF_VALIDATE_OR_GOTO (GF_NG, hostname, err);

        ngdata = dict_get (dict, (char *)hostname);
        if (!ngdata)
                goto err;

        return (struct netgroup_host *)ngdata->data;

err:
        return NULL;
}

/**
 * _ngh_dict_insert - Insert a netgroup host entry into the dict using
 *                    the netgroup name as the key.
 *
 * @dict   : The dict we are inserting into.
 *
 * @nge    : The data to insert into the dict.
 *
 * @return : nothing
 *
 * Not for external use.
 */
static void
_ngh_dict_insert (dict_t *dict, struct netgroup_host *ngh)
{
        data_t *ngdata = NULL;

        /* Validate args */
        GF_VALIDATE_OR_GOTO (GF_NG, dict, err);
        GF_VALIDATE_OR_GOTO (GF_NG, ngh, err);

        ngdata = bin_to_data (ngh, sizeof (*ngh));
        dict_set (dict, ngh->hostname, ngdata);
err:
        return;
}

/**
 * _ngh_print - Prints the netgroup host in the
 *              format '(hostname,user,domain)'
 *
 * @ngh    : The netgroup host to print out
 *
 * @return : nothing
 *
 * Not for external use.
 */
static void
_ngh_print (const struct netgroup_host *ngh)
{
        /* Validate args */
        GF_VALIDATE_OR_GOTO (GF_NG, ngh, err);

        printf ("(%s,%s,%s)", ngh->hostname, ngh->user ? ngh->user : "",
                ngh->domain ? ngh->domain : "");
err:
        return;
}

/**
 * __nge_print_walk - walk the netgroup entry dict and print each entry
 *                    associated with the key. This function prints
 *                    entries of type 'struct netgroup_entry'.
 *
 * This is passed as a function pointer to dict_foreach ()
 *
 * @dict: the dict we are walking
 * @key : the key we are processing in the dict
 * @val : the corresponding value in the dict
 * @tmp : Pointer to additional data that may be passed in (not used)
 *
 * @return : Nothing
 *
 * Not for external use.
 */
static int
__nge_print_walk (dict_t *dict, char *key, data_t *val, void *tmp)
{
        if (val)
                _nge_print ((struct netgroup_entry *)val->data);

        return 0;
}

/**
 * __ngh_print_walk - walk the netgroup entry dict and print each entry
 *                    associated with the key. This function prints entries
 *                    of type 'struct netgroup_host'
 *
 * This is passed as a function pointer to dict_foreach (),
 * which is called from _nge_print ().
 *
 * @dict: the dict we are walking
 * @key : the key we are processing in the dict
 * @val : the corresponding value in the dict
 * @tmp : Pointer to additional data that may be passed in (not used)
 *
 * @return : Nothing
 *
 * Not for external use.
 */
static int
__ngh_print_walk (dict_t *dict, char *key, data_t *val, void *tmp)
{
        if (val)
                _ngh_print ((struct netgroup_host *)val->data);

        return 0;
}

/**
 * _nge_print - Prints the netgroup entry in the
 *              format '<netgroup name> <following entries>'
 *
 * @ngh    : The netgroup entry to print out
 *
 * @return : nothing
 *
 * Not for external use.
 */
static void
_nge_print (const struct netgroup_entry *nge)
{
        /* Validate args */
        GF_VALIDATE_OR_GOTO (GF_NG, nge, err);

        printf ("%s ", nge->netgroup_name);
        if (nge->netgroup_ngs)
                dict_foreach (nge->netgroup_ngs, __nge_print_walk, NULL);

        if (nge->netgroup_hosts)
                dict_foreach (nge->netgroup_hosts, __ngh_print_walk, NULL);

err:
        return;
}

/**
 * __ngf_print_walk - walk through each entry in the netgroups file and print it
 *                    out. This calls helper functions _nge_print () to print
 *                    the netgroup entries.
 *
 * This is passed as a function pointer to dict_foreach (),
 * which is called from ng_file_print ().
 *
 * @dict: the dict we are walking
 * @key : the key we are processing in the dict
 * @val : the corresponding value in the dict
 * @tmp : Pointer to additional data that may be passed in (not used)
 *
 * @return : Nothing
 *
 * Not for external use.
 */
static int
__ngf_print_walk (dict_t *dict, char *key, data_t *val, void *tmp)
{
        struct netgroup_entry *snge = NULL;

        if (val) {
                snge = (struct netgroup_entry *)val->data;
                _nge_print (snge);
                printf ("\n");
        }
        return 0;
}

/**
 * ng_file_print - Prints the netgroup file in the
 *              format '<netgroup name> <following entries>', etc.
 *              The netgroup file is a dict of netgroup entries
 *              which, in turn is a combination of a other 'sub' netgroup
 *              entries and host entries. This function prints
 *              all of that out by calling the corresponding print functions
 *
 * @ngfile : The netgroup file to print out
 *
 * @return : nothing
 *
 * External facing function.
 *
 * Can be called on any valid 'struct netgroups_file *' type.
 */
void
ng_file_print (const struct netgroups_file *ngfile)
{
     dict_foreach (ngfile->ng_file_dict, __ngf_print_walk, NULL);
}

/**
 * ng_file_get_netgroup - Look up a netgroup entry from the netgroups file
 *                        based on the netgroup name and return a pointer
 *                        to the netgroup entry.
 *
 * @ngfile   : The netgroup file to lookup from.
 * @netgroup : The netgroup name used to lookup from the netgroup file.
 *
 * @return : nothing
 *
 * External facing function.
 *
 * Can be called on any valid 'struct netgroups_file *' type with a valid 'char
 * *' as the lookup key.
 */
struct netgroup_entry *
ng_file_get_netgroup (const struct netgroups_file *ngfile, const char *netgroup)
{
        data_t *ndata = NULL;

        GF_VALIDATE_OR_GOTO (GF_NG, ngfile, err);
        GF_VALIDATE_OR_GOTO (GF_NG, netgroup, err);

        ndata = dict_get (ngfile->ng_file_dict,
                          (char *)netgroup);
        if (!ndata)
                goto err;

        return (struct netgroup_entry *)ndata->data;

err:
        return NULL;
}

/**
 * __check_host_entry_str - Check if the host string which should be
 *                          in the format '(host,user,domain)' is
 *                          valid to be parsed. Currently checks
 *                          if the # of commas is correct and there
 *                          are no spaces in the string, but more
 *                          checks can be added.
 *
 * @host_str : String to check
 * @return   : success: TRUE if valid
 *             failure: FALSE if not
 *
 * Not for external use.
 */
static gf_boolean_t
__check_host_entry_str (const char *host_str)
{
        unsigned int comma_count = 0;
        unsigned int i           = 0;
        gf_boolean_t str_valid   = _gf_true;

        GF_VALIDATE_OR_GOTO (GF_NG, host_str, out);

        for (i = 0; i < strlen (host_str); i++) {
                if (host_str[i] == ',')
                        comma_count++;

                /* Spaces are not allowed in this string. e.g, (a,b,c) is valid
                 * but (a, b,c) is not.
                 */
                if (host_str[i] == ' ') {
                        str_valid = _gf_false;
                        goto out;
                }
        }

        str_valid = (comma_count == 2);
out:
        return str_valid;
}

/**
 * _parse_ng_host - Parse the netgroup host string into a netgroup host struct.
 *                  The netgroup host string is structured as follows:
 *                  (host, user, domain)
 *
 * @ng_str   : String to parse
 * @return   : success: 0 if the parsing succeeded
 *             failure: -EINVAL for bad args, -ENOMEM for allocation errors,
 *                      1 for parsing errors.
 *
 * Not for external use.
 */
static int
_parse_ng_host (char *ng_str, struct netgroup_host **ngh)
{
        struct netgroup_host *ng_host  = NULL;
        unsigned int          parts    = 0;
        char                 *match    = NULL;
        int                   ret      = -EINVAL;

        GF_VALIDATE_OR_GOTO (GF_NG, ng_str, out);
        GF_VALIDATE_OR_GOTO (GF_NG, ngh, out);

        if (!__check_host_entry_str (ng_str)) {
                ret = 1; /* Parse failed */
                goto out;
        }

        ret = parser_set_string (ng_host_parser, ng_str);
        if (ret < 0)
                goto out;

        ng_host = _netgroup_host_init ();
        GF_CHECK_ALLOC (ng_host, ret, free_and_out); /* Sets ret to -ENOMEM on
                                                      * failure.
                                                      */
        while ((match = parser_get_next_match (ng_host_parser)) != NULL) {
                switch (parts) {
                case 0:
                        ng_host->hostname = match;
                        break;
                case 1:
                        ng_host->user = match;
                        break;
                case 2:
                        ng_host->domain = match;
                        break;
                default:
                        GF_FREE (match);
                        break;
                };

                /* We only allow three parts in the host string;
                 * The format for the string is (a,b,c)
                 */
                parts++;
                if (parts > 2)
                        break;
        }

        /* Set the parameter */
        *ngh = ng_host;
        ret = 0;

free_and_out:
        parser_unset_string (ng_host_parser);
out:
        return ret;
}

/**
 * _ng_handle_host_part - Parse the host string that looks like this :
 *                        '(dev1763.prn2.facebook.com,,)' into a host
 *                        struct and insert it into the parent netgroup's
 *                        host dict.
 * @match : The host string
 * @ngp   : The parent netgroup
 *
 * @return: success: 0 if parsing succeeded
 *          failure: -EINVAL for bad args, other errors bubbled up
 *                   from _parse_ng_host.
 *
 *
 * Not for external use.
 */
static int
_ng_handle_host_part (char *match, struct netgroup_entry *ngp)
{
        struct netgroup_host *ngh = NULL;
        int                   ret = -EINVAL;

        GF_VALIDATE_OR_GOTO (GF_NG, match, out);
        GF_VALIDATE_OR_GOTO (GF_NG, ngp, out);

        if (!ngp->netgroup_name) {
                gf_msg (GF_NG, GF_LOG_WARNING, EINVAL, NFS_MSG_INVALID_ENTRY,
                        "Invalid: Line starts with hostname!");
                goto out;
        }

        /* Parse the host string and get a struct for it */
        ret = _parse_ng_host (match, &ngh);
        if (ret < 0) {
                gf_msg (GF_NG, GF_LOG_CRITICAL, -ret, NFS_MSG_PARSE_FAIL,
                        "Critical error : %s", strerror (-ret));
                goto out;
        }
        if (ret != 0) {
                /* Cannot change to gf_msg
                 * gf_msg not giving output to STDOUT
                 * Bug id : BZ1215017
                 */
                gf_log (GF_NG, GF_LOG_WARNING,
                        "Parse error for: %s", match);
                goto out;
        }


        /* Make dict for the parent entry's netgroup hosts */
        if (!ngp->netgroup_hosts) {
                ngp->netgroup_hosts = dict_new ();
                GF_CHECK_ALLOC (ngp->netgroup_hosts, ret,
                                out);
        }

        /* Insert this entry into the parent netgroup dict */
        _ngh_dict_insert (ngp->netgroup_hosts, ngh);

out:
        return ret;
}

/**
 * _ng_handle_netgroup_part - Parse the netgroup string that should just be one
 *                            string. This may insert the netgroup into the file
 *                            struct if it does not already exist. Frees the
 *                            parameter match if the netgroup was already found
 *                            in the file.
 *
 * @match    : The netgroup string
 * @ngp      : The netgroup file we may insert the entry into
 * @ng_entry : Double pointer to the netgroup entry we want to allocate and set.
 *
 * @return: success: 0 if parsing succeeded
 *          failure: -EINVAL for bad args, other errors bubbled up
 *                   from _parse_ng_host.
 *
 *
 * Not for external use.
 */
static int
_ng_setup_netgroup_entry (char *match, struct netgroups_file *file,
                          struct netgroup_entry **ng_entry)
{
        struct netgroup_entry *nge           = NULL;
        int                    ret           = -EINVAL;

        GF_VALIDATE_OR_GOTO (GF_NG, match, out);
        GF_VALIDATE_OR_GOTO (GF_NG, file, out);
        GF_VALIDATE_OR_GOTO (GF_NG, ng_entry, out);

        nge = _netgroup_entry_init ();
        GF_CHECK_ALLOC (nge, ret, out);

        nge->netgroup_name = match;

        /* Insert this new entry into the file dict */
        _nge_dict_insert (file->ng_file_dict, nge);

        *ng_entry = nge;

        ret = 0;
out:
        return ret;
}

/**
 * _parse_ng_line - Parse a line in the netgroups file into a netgroup entry
 *                  struct. The netgroup line is structured as follows:
 *                  'netgroupx netgroupy (hosta,usera,domaina)...' OR
 *                  'netgroupx netgroupy netgroupz...'  OR
 *                  'netgroupx (hosta,usera,domaina) (hostb,userb,domainb)'
 *                  This function parses this into a netgroup entry
 *                  which will hold either a dict of netgroups and/or
 *                  a dict of hosts that make up this netgroup.
 *
 * In general terms, the data structure to represent a netgroups file
 * is a set of nested dictionaries. Each line in the netgroups file
 * is compiled into a struct netgroup_entry structure that holds a dict
 * of netgroups and a dict of hostnames. The first string in the netgroups
 * line is the parent netgroup entry and the rest of the items in the line
 * are the children of that parent netgroup entry. (Hence variables ngp
 * and nge).
 *
 * A sample netgroup file may look like this:
 *
 * async async.ash3 async.ash4
 * async.ash3 async.04.ash3
 * async04.ash3 (async001.ash3.facebook.com,,) (async002.ash3.facebook.com,,)
 *
 * _parse_ng_line will get called on each line, so on the first call to this
 * function, our data structure looks like this:
 *
 *
 * dict [
 *       'async'   --> dict [
 *                              'async.ash3'
 *                              'async.ash4'
 *                          ]
 *      ]
 *
 * On the second call to the function with the second line, our data structure
 * looks like this:
 *
 * dict [
 *       'async' --> dict [
 *                              'async.ash3' -> dict [ 'async.04.ash3' ]
 *                              'async.ash4'      ^
 *                        ]                       |
 *                                                |
 *      'async.ash3' ------------------------------
 *      ]
 *
 * And so on.
 *
 * The obvious answer to storing this file in a data structure may be a tree
 * but lookups from a tree are expensive and since we may be looking up stuff
 * in this file in the I/O path, we can't afford expensive lookups.
 *
 * @ng_str   : String to parse
 * @file     : Netgroup file to put the parsed line into
 * @ng_entry : Double pointer to struct that we are going to allocate and fill
 *
 * The string gets parsed into a structure pointed to by
 * the parameter 'ng_entry'
 *
 * @return   : success: 0 if parsing succeeded
 *             failure: NULL if not
 *
 * Not for external use.
 */
static int
_parse_ng_line (char *ng_str, struct netgroups_file *file,
                struct netgroup_entry **ng_entry)
{
        struct netgroup_entry  *ngp         = NULL; /* Parent netgroup entry */
        struct netgroup_entry  *nge         = NULL; /* Generic netgroup entry */
        char                   *match       = NULL;
        int                     ret         = -EINVAL;
        unsigned int            num_entries = 0;

        /* Validate arguments */
        GF_VALIDATE_OR_GOTO (GF_NG, ng_str, out);
        GF_VALIDATE_OR_GOTO (GF_NG, file, out);

        if (*ng_str == ' ' || *ng_str == '\0' || *ng_str == '\n') {
                ret = 0;
                goto out;
        }

        ret = parser_set_string (ng_file_parser, ng_str);
        if (ret < 0)
                goto out;

        /* This is the first name in the line, and should be the
         * parent netgroup entry.
         */
        match = parser_get_next_match (ng_file_parser);
        if (!match) {
                ret = 1;
                gf_msg (GF_NG, GF_LOG_WARNING, 0,
                        NFS_MSG_FIND_FIRST_MATCH_FAIL, "Unable to find "
                        "first match.");
                gf_msg (GF_NG, GF_LOG_WARNING, 0, NFS_MSG_PARSE_FAIL,
                        "Error parsing str: %s", ng_str);
                goto out;
        }

        /* Lookup to see if the match already exists,
         * if not, set the parent.
         */
        ngp = _nge_dict_get (file->ng_file_dict, match);
        if (!ngp) {
                ret = _ng_setup_netgroup_entry (match, file, &ngp);
                if (ret < 0) {
                        /* Bubble up error to caller. We don't need to free ngp
                         * here because this can only fail if allocating the
                         * struct fails.
                         */
                        goto out;
                }
        } else
                GF_FREE (match);

        if (!ngp->netgroup_ngs) {
                /* If a netgroup dict has not been allocated
                 * for this parent, allocate it.
                 */
                ngp->netgroup_ngs = dict_new ();
                GF_CHECK_ALLOC (ngp->netgroup_ngs, ret, out);
                /* No need to free anything here since ngp is already
                 * a part of the file. When the file gets
                 * deallocated, we will free ngp.
                 */
        }

        while ((match = parser_get_next_match (ng_file_parser)) != NULL) {
                num_entries++;
                /* This means that we hit a host entry in the line */
                if (*match == '(') {
                        ret = _ng_handle_host_part (match, ngp);
                        GF_FREE (match);
                        if (ret != 0) {
                                /* If parsing the host fails, bubble the error
                                 * code up to the caller.
                                 */
                                goto out;
                        }
                } else {
                        nge = _nge_dict_get (file->ng_file_dict, match);
                        if (!nge) {
                                ret = _ng_setup_netgroup_entry (match, file,
                                                                &nge);
                                if (ret < 0) {
                                        /* Bubble up error to caller. We don't
                                         * need to free nge here because this
                                         * can only fail if allocating the
                                         * struct fails.
                                         */
                                        goto out;
                                }
                        } else
                                GF_FREE (match);

                        /* Insert the netgroup into the parent's dict */
                        _nge_dict_insert (ngp->netgroup_ngs, nge);
                }
        }

        /* If there are no entries on the RHS, log an error, but continue */
        if (!num_entries) {
                /* Cannot change to gf_msg
                 * gf_msg not giving output to STDOUT
                 * Bug id : BZ1215017
                 */
                gf_log (GF_NG, GF_LOG_WARNING,
                        "No netgroups were specified except for the parent.");
        }

        *ng_entry = ngp;
        ret = 0;

out:
        parser_unset_string (ng_file_parser);
        return ret;
}

/**
 * ng_file_parse - Parse a netgroups file into a the netgroups file struct.
 *                 This is the external facing function that must be called
 *                 to parse a netgroups file. This function returns a netgroup
 *                 file struct that is allocated and must be freed using
 *                 ng_file_deinit.
 *
 * @filepath : Path to the netgroups file we need to parse
 *
 * @return   : success: Pointer to a netgroup file struct if parsing succeeded
 *             failure: NULL if not
 *
 * Externally facing function
 */
struct netgroups_file *
ng_file_parse (const char *filepath)
{
        FILE                  *fp    = NULL;
        size_t                 len   = 0;
        size_t                 read  = 0;
        char                  *line  = NULL;
        struct netgroups_file *file  = NULL;
        struct netgroup_entry *nge   = NULL;
        int                    ret   = 0;

        GF_VALIDATE_OR_GOTO (GF_NG, filepath, err);

        fp = fopen (filepath, "r");
        if (!fp)
                goto err;

        file = _netgroups_file_init ();
        if (!file)
                goto err;

        file->ng_file_dict = dict_new ();
        if (!file->ng_file_dict) {
                gf_msg (GF_NG, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY,
                        "Failed to allocate netgroup file dict");
                goto err;
        }

        file->filename = gf_strdup (filepath);
        if (!file->filename) {
                gf_msg (GF_NG, GF_LOG_CRITICAL, errno, NFS_MSG_FILE_OP_FAILED,
                        "Failed to duplicate filename");
                goto err;
        }

        ret = _ng_init_parsers ();
        if (ret < 0)
                goto err;

        /* Read the file line-by-line and parse it */
        while ((read = getline (&line, &len, fp)) != -1) {
                if (*line == '#') /* Lines starting with # are comments */
                        continue;

                /* Parse the line into a netgroup entry */
                ret = _parse_ng_line (line, file, &nge);
                if (ret == -ENOMEM) {
                        gf_msg (GF_NG, GF_LOG_CRITICAL, ENOMEM,
                                NFS_MSG_NO_MEMORY, "Allocation error "
                                "while parsing line!");
                        ng_file_deinit (file);
                        GF_FREE (line);
                        goto err;
                }
                if (ret != 0) {
                        gf_msg_debug (GF_NG, 0, "Failed to parse line %s",
                                      line);
                        continue;
                }
        }

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

        if (fp)
                fclose(fp);

        return file;

err:
        if (file)
                ng_file_deinit (file);

        _ng_deinit_parsers ();

        if (fp)
                fclose (fp);
        return NULL;
}
