From 0c3d3a796bda37d8439855baf00137ad17714620 Mon Sep 17 00:00:00 2001 From: Niels de Vos Date: Thu, 1 Jan 2015 13:15:45 +0100 Subject: nfs: add support for separate 'exports' file The Linux kernel NFS server uses /etc/exports to manage permissions for the NFS-clients. Extending the Gluster/NFS server to support a similar scheme is needed for many deployments in enterprise environments. BUG: 1143880 Change-Id: I7e6aa6bc6aa1cd5f52458e023387ed38de9823d7 Original-author: Shreyas Siravara CC: Richard Wareing CC: Jiffin Tony Thottan Signed-off-by: Niels de Vos Reviewed-on: http://review.gluster.org/9361 Tested-by: Gluster Build System Reviewed-by: Vijay Bellur --- xlators/nfs/server/src/Makefile.am | 4 +- xlators/nfs/server/src/exports.c | 1437 ++++++++++++++++++++++++++++++++ xlators/nfs/server/src/exports.h | 92 ++ xlators/nfs/server/src/nfs-mem-types.h | 1 + xlators/nfs/server/src/nfs.h | 3 + 5 files changed, 1535 insertions(+), 2 deletions(-) create mode 100644 xlators/nfs/server/src/exports.c create mode 100644 xlators/nfs/server/src/exports.h (limited to 'xlators/nfs/server') diff --git a/xlators/nfs/server/src/Makefile.am b/xlators/nfs/server/src/Makefile.am index c72b314c458..94f47021178 100644 --- a/xlators/nfs/server/src/Makefile.am +++ b/xlators/nfs/server/src/Makefile.am @@ -4,13 +4,13 @@ nfsrpclibdir = $(top_srcdir)/rpc/rpc-lib/src server_la_LDFLAGS = -module -avoid-version server_la_SOURCES = nfs.c nfs-common.c nfs-fops.c nfs-inodes.c \ nfs-generics.c mount3.c nfs3-fh.c nfs3.c nfs3-helpers.c nlm4.c \ - nlmcbk_svc.c mount3udp_svc.c acl3.c netgroups.c + nlmcbk_svc.c mount3udp_svc.c acl3.c netgroups.c exports.c server_la_LIBADD = $(top_builddir)/libglusterfs/src/libglusterfs.la \ $(top_builddir)/api/src/libgfapi.la noinst_HEADERS = nfs.h nfs-common.h nfs-fops.h nfs-inodes.h nfs-generics.h \ mount3.h nfs3-fh.h nfs3.h nfs3-helpers.h nfs-mem-types.h nlm4.h \ - acl3.h netgroups.h + acl3.h netgroups.h exports.h AM_CPPFLAGS = $(GF_CPPFLAGS) \ -DLIBDIR=\"$(libdir)/glusterfs/$(PACKAGE_VERSION)/auth\" \ diff --git a/xlators/nfs/server/src/exports.c b/xlators/nfs/server/src/exports.c new file mode 100644 index 00000000000..3f704bdf9ce --- /dev/null +++ b/xlators/nfs/server/src/exports.c @@ -0,0 +1,1437 @@ +/* + Copyright 2014-present Facebook. All Rights Reserved + + This file is part of GlusterFS. + + Author : + Shreyas Siravara + + 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 "hashfn.h" +#include "parse-utils.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_nfs_mt_exports); + if (!file) { + gf_log (GF_EXP, GF_LOG_CRITICAL, + "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_log (GF_EXP, GF_LOG_CRITICAL, "Failed to allocate dict!"); + goto free_and_out; + } + + + + goto out; + +free_and_out: + 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; + + _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 || !expfile->exports_dict) + goto out; + + dict_foreach (expfile->exports_dict, _exp_file_dict_destroy, NULL); + 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_nfs_mt_exports); + + if (!expdir) + gf_log (GF_EXP, GF_LOG_CRITICAL, + "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); + _exp_dict_destroy (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_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_nfs_mt_exports); + + if (!item) + gf_log (GF_EXP, GF_LOG_CRITICAL, + "Failed to allocate export item!"); + + return item; +} + +/** + * _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_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_nfs_mt_exports); + + if (!opts) + gf_log (GF_EXP, GF_LOG_CRITICAL, + "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) { + _export_item_deinit ((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; + + 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)); + 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 assocated 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 assocated 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); + 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 + 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(), parsing @mygroup and () + * or myhost001.dom(), parsing myhost001.dom and () + * + * @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); + + /* 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 parantheses */ + optstr = parens; + + ret = __exp_line_opt_parse (optstr, &exp_opts); + if (ret != 0) { + /* Bubble up the error to the caller */ + _export_item_deinit (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; + } + + 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; + } + } + + 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; + } + + 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); + } + + 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 whats 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_log (GF_EXP, GF_LOG_DEBUG, "%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_log (GF_EXP, GF_LOG_ERROR, "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_log (GF_EXP, GF_LOG_ERROR, "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) + 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_log (GF_EXP, GF_LOG_ERROR, "Critical error: %s", + strerror (-ret)); + goto free_and_out; + } + if (ret != 0) { + if (ret == GF_EXP_PARSE_ITEM_FAILURE) + gf_log (GF_EXP, GF_LOG_WARNING, + "Error parsing hosts for: %s", line); + /* If netgroups parsing failed, AND + * host parsing failed, then theres 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_log (GF_EXP, GF_LOG_DEBUG, "%s not found for %s", + netgroup, expdir->dir_name); + } + + 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_log (GF_EXP, GF_LOG_DEBUG, "%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_log (GF_EXP, GF_LOG_DEBUG, "%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_log (GF_EXP, GF_LOG_ERROR, + "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_log (GF_EXP, GF_LOG_DEBUG, + "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", + parse_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: + exp_file_deinit (file); + +parse_done: + if (fp) + fclose (fp); + _exp_deinit_parsers (); + return ret; +} diff --git a/xlators/nfs/server/src/exports.h b/xlators/nfs/server/src/exports.h new file mode 100644 index 00000000000..51a3cd668a4 --- /dev/null +++ b/xlators/nfs/server/src/exports.h @@ -0,0 +1,92 @@ +/* + Copyright 2014-present Facebook. All Rights Reserved + + This file is part of GlusterFS. + + Author : + Shreyas Siravara + + 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. +*/ + +#ifndef _EXPORTS_H_ +#define _EXPORTS_H_ + +#include "nfs-mem-types.h" +#include "dict.h" +#include "nfs.h" + +#define GF_EXP GF_NFS"-exports" + +#define NETGROUP_REGEX_PATTERN "(@([a-zA-Z0-9\\(=, .])+)())" +#define HOSTNAME_REGEX_PATTERN "[[:space:]]([a-zA-Z0-9.\\(=,*/)]+)" +#define OPTIONS_REGEX_PATTERN "([a-zA-Z0-9=\\.]+)" + +#define NETGROUP_MAX_LEN 128 +#define FQDN_MAX_LEN 256 + +#define SEC_OPTION_MAX 10 +#define UID_MAX_LEN 6 + +#define DIR_MAX_LEN 1024 + +/* The following 2 definitions are in mount3.h + * but we don't want to include it because mount3.h + * depends on structs in this file so we get a cross + * dependency. + */ +struct mount3_state; + +extern struct mnt3_export * +mnt3_mntpath_to_export (struct mount3_state *ms, const char *dirpath, + gf_boolean_t export_parsing_match); + +struct export_options { + gf_boolean_t rw; /* Read-write option */ + gf_boolean_t nosuid; /* nosuid option */ + gf_boolean_t root; /* root option */ + char *anon_uid; /* anonuid option */ + char *sec_type; /* X, for sec=X */ +}; + +struct export_item { + char *name; /* Name of the export item */ + struct export_options *opts; /* NFS Options */ +}; + +struct export_dir { + char *dir_name; /* Directory */ + dict_t *netgroups; /* Dict of netgroups */ + dict_t *hosts; /* Dict of hosts */ +}; + +struct exports_file { + char *filename; /* Filename */ + dict_t *exports_dict; /* Dict of export_dir_t */ + dict_t *exports_map; /* Map of SuperFastHash() -> expdir */ +}; + +void +exp_file_deinit (struct exports_file *expfile); + +int +exp_file_parse (const char *filepath, struct exports_file **expfile, + struct mount3_state *ms); + +struct export_dir * +exp_file_get_dir (const struct exports_file *file, const char *dir); + +struct export_item * +exp_dir_get_host (const struct export_dir *expdir, const char *host); + +struct export_item * +exp_dir_get_netgroup (const struct export_dir *expdir, const char *netgroup); + +struct export_dir * +exp_file_dir_from_uuid (const struct exports_file *file, + const uuid_t export_uuid); + +#endif /* _EXPORTS_H_ */ diff --git a/xlators/nfs/server/src/nfs-mem-types.h b/xlators/nfs/server/src/nfs-mem-types.h index d9e2c9904c9..33adcf5e292 100644 --- a/xlators/nfs/server/src/nfs-mem-types.h +++ b/xlators/nfs/server/src/nfs-mem-types.h @@ -47,6 +47,7 @@ enum gf_nfs_mem_types_ { gf_nfs_mt_inode_ctx, gf_nfs_mt_auth_spec, gf_nfs_mt_netgroups, + gf_nfs_mt_exports, gf_nfs_mt_arr, gf_nfs_mt_end }; diff --git a/xlators/nfs/server/src/nfs.h b/xlators/nfs/server/src/nfs.h index fc745fbbdc4..4c51d11d49c 100644 --- a/xlators/nfs/server/src/nfs.h +++ b/xlators/nfs/server/src/nfs.h @@ -38,6 +38,9 @@ #define GF_NFS_DVM_ON 1 #define GF_NFS_DVM_OFF 0 +/* Disable using the exports file by default */ +#define GF_NFS_DEFAULT_EXPORT_AUTH 0 + /* This corresponds to the max 16 number of group IDs that are sent through an * RPC request. Since NFS is the only one going to set this, we can be safe * in keeping this size hardcoded. -- cgit