| /* ---------------------------------------------------------------------------- |
| * Copyright CEA/DAM/DIF (2007) |
| * contributeur : Thomas LEIBOVICI thomas.leibovici@cea.fr |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public License |
| * as published by the Free Software Foundation; either version 3 of |
| * the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| * 02110-1301 USA |
| * |
| * --------------------------------------- |
| */ |
| #include "config.h" |
| #include <stdio.h> |
| #include <errno.h> |
| #include <assert.h> |
| #if HAVE_STRING_H |
| #include <string.h> |
| #endif |
| #include <sys/types.h> |
| #include <ctype.h> |
| #include <arpa/inet.h> |
| #include <netdb.h> |
| #include <sys/socket.h> |
| #include "config_parsing.h" |
| #include "analyse.h" |
| #include "abstract_mem.h" |
| #include "conf_yacc.h" |
| #include "log.h" |
| #include "fsal_convert.h" |
| |
| /* config_ParseFile: |
| * Reads the content of a configuration file and |
| * stores it in a memory structure. |
| */ |
| |
| config_file_t config_ParseFile(char *file_path, |
| struct config_error_type *err_type) |
| { |
| struct parser_state st; |
| struct config_root *root; |
| int rc; |
| |
| memset(&st, 0, sizeof(struct parser_state)); |
| st.err_type = err_type; |
| rc = ganeshun_yy_init_parser(file_path, &st); |
| if (rc) { |
| return NULL; |
| } |
| rc = ganesha_yyparse(&st); |
| root = st.root_node; |
| if (rc != 0) |
| config_proc_error(root, err_type, |
| (rc == 1 |
| ? "Configuration syntax errors found" |
| : "Configuration parse ran out of memory")); |
| #ifdef DUMP_PARSE_TREE |
| print_parse_tree(stderr, root); |
| #endif |
| ganeshun_yy_cleanup_parser(&st); |
| return (config_file_t)root; |
| } |
| |
| /** |
| * config_Print: |
| * Print the content of the syntax tree |
| * to a file. |
| */ |
| void config_Print(FILE * output, config_file_t config) |
| { |
| print_parse_tree(output, (struct config_root *)config); |
| } |
| |
| /** |
| * config_Free: |
| * Free the memory structure that store the configuration. |
| */ |
| |
| void config_Free(config_file_t config) |
| { |
| if (config != NULL) |
| free_parse_tree((struct config_root *)config); |
| return; |
| |
| } |
| |
| /** |
| * @brief Return an error string constructed from an err_type |
| * |
| * The string is constructed in allocate memory that must be freed |
| * by the caller. |
| * |
| * @param err_type [IN] the err_type struct in question. |
| * |
| * @return a NULL term'd string or NULL on failure. |
| */ |
| |
| char *err_type_str(struct config_error_type *err_type) |
| { |
| char *buf = NULL; |
| size_t bufsize; |
| FILE *fp; |
| |
| if (config_error_no_error(err_type)) |
| return gsh_strdup("(no errors)"); |
| fp = open_memstream(&buf, &bufsize); |
| if (fp == NULL) { |
| LogCrit(COMPONENT_CONFIG, |
| "Could not open memstream for err_type string"); |
| return NULL; |
| } |
| fputc('(', fp); |
| if (err_type->scan) |
| fputs("token scan, ", fp); |
| if (err_type->parse) |
| fputs("parser rule, ", fp); |
| if (err_type->init) |
| fputs("block init, ", fp); |
| if (err_type->fsal) |
| fputs("fsal load, ", fp); |
| if (err_type->export_) |
| fputs("export create, ", fp); |
| if (err_type->resource) |
| fputs("resource alloc, ", fp); |
| if (err_type->unique) |
| fputs("not unique param, ", fp); |
| if (err_type->invalid) |
| fputs("invalid param value, ", fp); |
| if (err_type->missing) |
| fputs("missing mandatory param, ", fp); |
| if (err_type->validate) |
| fputs("block validation, ", fp); |
| if (err_type->exists) |
| fputs("block exists, ", fp); |
| if (err_type->internal) |
| fputs("internal error, ", fp); |
| if (err_type->bogus) |
| fputs("unknown param, ", fp); |
| if (ferror(fp)) |
| LogCrit(COMPONENT_CONFIG, |
| "file error while constructing err_type string"); |
| fclose(fp); |
| if (buf == NULL) { |
| LogCrit(COMPONENT_CONFIG, |
| "close of memstream for err_type string failed"); |
| return NULL; |
| } |
| /* each of the above strings (had better) have ', ' at the end! */ |
| if (buf[strlen(buf) -1] == ' ') { |
| buf[bufsize - 2] = ')'; |
| buf[bufsize - 1] = '\0'; |
| } |
| return buf; |
| } |
| |
| bool init_error_type(struct config_error_type *err_type) |
| { |
| memset(err_type, 0, sizeof(struct config_error_type)); |
| err_type->fp = open_memstream(&err_type->diag_buf, |
| &err_type->diag_buf_size); |
| if (err_type->fp == NULL) { |
| LogCrit(COMPONENT_MAIN, |
| "Could not open memory stream for parser errors"); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * @brief Log an error to the parse error stream |
| * |
| * cnode is void * because struct config_node is hidden outside parse code |
| * and we can report errors in fsal_manager.c etc. |
| */ |
| |
| void config_proc_error(void *cnode, |
| struct config_error_type *err_type, |
| char *format, ...) |
| { |
| struct config_node *node = cnode; |
| FILE *fp = err_type->fp; |
| char *filename = "<unknown file>"; |
| int linenumber = 0; |
| va_list arguments; |
| |
| if (fp == NULL) |
| return; /* no stream, no message */ |
| if (node != NULL && node->filename != NULL) { |
| filename = node->filename; |
| linenumber = node->linenumber; |
| } |
| va_start(arguments, format); |
| config_error(fp, filename, linenumber, |
| format, arguments); |
| va_end(arguments); |
| } |
| void config_errs_to_log(char *err, void *dest, |
| struct config_error_type *err_type) |
| { |
| log_levels_t log_level; |
| |
| if (config_error_is_fatal(err_type) || |
| config_error_is_crit(err_type)) |
| log_level = NIV_CRIT; |
| else if (config_error_is_harmless(err_type)) |
| log_level = NIV_WARN; |
| else |
| log_level = NIV_EVENT; |
| DisplayLogComponentLevel(COMPONENT_CONFIG, |
| __FILE__, __LINE__, (char *)__func__, |
| log_level, "%s", err); |
| } |
| |
| void report_config_errors(struct config_error_type *err_type, void *dest, |
| void (*logger)(char *msg, void *dest, |
| struct config_error_type *err_type)) |
| { |
| char *msgp, *cp; |
| |
| if (err_type->fp == NULL) |
| return; |
| fclose(err_type->fp); |
| err_type->fp = NULL; |
| msgp = err_type->diag_buf; |
| if (msgp == NULL) |
| return; |
| while (*msgp != '\0') { |
| cp = index(msgp, '\f'); |
| if (cp != NULL) { |
| *cp++ = '\0'; |
| logger(msgp, dest, err_type); |
| msgp = cp; |
| } else { |
| logger(msgp, dest, err_type); |
| break; |
| } |
| } |
| gsh_free(err_type->diag_buf); |
| err_type->diag_buf = NULL; |
| } |
| |
| static bool convert_bool(struct config_node *node, |
| bool *b, |
| struct config_error_type *err_type) |
| { |
| if (node->u.term.type == TERM_TRUE) |
| *b = true; |
| else if (node->u.term.type == TERM_FALSE) |
| *b = false; |
| else { |
| config_proc_error(node, err_type, |
| "Expected boolean (true/false) got (%s)", |
| node->u.term.varvalue); |
| err_type->errors++; |
| err_type->invalid = true; |
| return false; |
| } |
| return true; |
| } |
| |
| static bool convert_number(struct config_node *node, |
| struct config_item *item, |
| uint64_t *num, |
| struct config_error_type *err_type) |
| { |
| uint64_t val, mask, min, max; |
| int64_t sval, smin, smax; |
| char *endptr; |
| int base; |
| bool signed_int = false; |
| |
| if (node->type != TYPE_TERM) { |
| config_proc_error(node, err_type, |
| "Expected a number, got a %s", |
| (node->type == TYPE_ROOT |
| ? "root node" |
| : (node->type == TYPE_BLOCK |
| ? "block" : "statement"))); |
| goto errout; |
| } else if (node->u.term.type == TERM_DECNUM) { |
| base = 10; |
| } else if (node->u.term.type == TERM_HEXNUM) { |
| base = 16; |
| } else if (node->u.term.type == TERM_OCTNUM) { |
| base = 8; |
| } else { |
| config_proc_error(node, err_type, |
| "Expected a number, got a %s", |
| config_term_desc(node->u.term.type)); |
| goto errout; |
| } |
| errno = 0; |
| assert(*node->u.term.varvalue != '\0'); |
| val = strtoull(node->u.term.varvalue, &endptr, base); |
| if (*endptr != '\0' || errno != 0) { |
| config_proc_error(node, err_type, |
| "(%s) is not an integer", |
| node->u.term.varvalue); |
| goto errout; |
| } |
| switch (item->type) { |
| case CONFIG_INT16: |
| smin = item->u.i16.minval; |
| smax = item->u.i16.maxval; |
| signed_int = true; |
| break; |
| case CONFIG_UINT16: |
| mask = UINT16_MAX; |
| min = item->u.ui16.minval; |
| max = item->u.ui16.maxval; |
| break; |
| case CONFIG_INT32: |
| smin = item->u.i32.minval; |
| smax = item->u.i32.maxval; |
| signed_int = true; |
| break; |
| case CONFIG_UINT32: |
| mask = UINT32_MAX; |
| min = item->u.ui32.minval; |
| max = item->u.ui32.maxval; |
| break; |
| case CONFIG_ANON_ID: |
| /* Internal to config, anonymous id is treated as int64_t */ |
| smin = item->u.i64.minval; |
| smax = item->u.i64.maxval; |
| signed_int = true; |
| break; |
| case CONFIG_INT64: |
| smin = item->u.i64.minval; |
| smax = item->u.i64.maxval; |
| signed_int = true; |
| break; |
| case CONFIG_UINT64: |
| mask = UINT64_MAX; |
| min = item->u.ui64.minval; |
| max = item->u.ui64.maxval; |
| break; |
| default: |
| goto errout; |
| } |
| |
| if (signed_int) { |
| if (node->u.term.op_code == NULL) { |
| /* Check for overflow of int64_t on positive */ |
| if (val > (uint64_t) INT64_MAX) { |
| config_proc_error(node, err_type, |
| "(%s) is out of range", |
| node->u.term.varvalue); |
| goto errout; |
| } |
| sval = val; |
| } else if (*node->u.term.op_code == '-') { |
| /* Check for underflow of int64_t on negative */ |
| if (val > ((uint64_t) INT64_MAX) + 1) { |
| config_proc_error(node, err_type, |
| "(%s) is out of range", |
| node->u.term.varvalue); |
| goto errout; |
| } |
| sval = -((int64_t) val); |
| } else { |
| config_proc_error(node, err_type, |
| "(%c) is not allowed for signed values", |
| *node->u.term.op_code); |
| goto errout; |
| } |
| if (sval < smin || sval > smax) { |
| config_proc_error(node, err_type, |
| "(%s) is out of range", |
| node->u.term.varvalue); |
| goto errout; |
| } |
| val = (uint64_t) sval; |
| } else { |
| if (node->u.term.op_code != NULL && |
| *node->u.term.op_code == '~') { |
| /* Check for overflow before negation */ |
| if ((val & ~mask) != 0) { |
| config_proc_error(node, err_type, |
| "(%s) is out of range", |
| node->u.term.varvalue); |
| goto errout; |
| } |
| val = ~val & mask; |
| } else if (node->u.term.op_code != NULL) { |
| config_proc_error(node, err_type, |
| "(%c) is not allowed for signed values", |
| *node->u.term.op_code); |
| goto errout; |
| } |
| if (val < min || val > max) { |
| config_proc_error(node, err_type, |
| "(%s) is out of range", |
| node->u.term.varvalue); |
| goto errout; |
| } |
| } |
| |
| *num = val; |
| return true; |
| |
| errout: |
| err_type->errors++; |
| err_type->invalid = true; |
| return false; |
| } |
| |
| /** |
| * @brief convert an fsid which is a "<64bit num>.<64bit num" |
| * |
| * NOTE: using assert() here because the parser has already |
| * validated so bad things have happened to the parse tree if... |
| * |
| */ |
| |
| static bool convert_fsid(struct config_node *node, void *param, |
| struct config_error_type *err_type) |
| { |
| struct fsal_fsid__ *fsid = (struct fsal_fsid__ *)param; |
| uint64_t major, minor; |
| char *endptr, *sp; |
| int base; |
| |
| if (node->type != TYPE_TERM) { |
| config_proc_error(node, err_type, |
| "Expected an FSID, got a %s", |
| (node->type == TYPE_ROOT |
| ? "root node" |
| : (node->type == TYPE_BLOCK |
| ? "block" : "statement"))); |
| goto errout; |
| } |
| if (node->u.term.type != TERM_FSID) { |
| config_proc_error(node, err_type, |
| "Expected an FSID, got a %s", |
| config_term_desc(node->u.term.type)); |
| goto errout; |
| } |
| errno = 0; |
| sp = node->u.term.varvalue; |
| if (sp[0] == '0') { |
| if (sp[1] == 'x' || sp[1] == 'X') |
| base = 16; |
| else |
| base = 8; |
| } else |
| base = 10; |
| major = strtoull(sp, &endptr, base); |
| assert(*endptr == '.'); |
| if (errno != 0 || major == ULLONG_MAX) { |
| config_proc_error(node, err_type, |
| "(%s) major is out of range", |
| node->u.term.varvalue); |
| goto errout; |
| } |
| sp = endptr + 1; |
| if (sp[0] == '0') { |
| if (sp[1] == 'x' || sp[1] == 'X') |
| base = 16; |
| else |
| base = 8; |
| } else |
| base = 10; |
| minor = strtoull(sp, &endptr, base); |
| assert(*endptr == '\0'); |
| if (errno != 0 || minor == ULLONG_MAX) { |
| config_proc_error(node, err_type, |
| "(%s) minor is out of range", |
| node->u.term.varvalue); |
| goto errout; |
| } |
| fsid->major = major; |
| fsid->minor = minor; |
| return true; |
| |
| errout: |
| err_type->invalid = true; |
| err_type->errors++; |
| return false; |
| } |
| |
| /** |
| * @brief Scan a list of CSV tokens. |
| * |
| */ |
| |
| static bool convert_list(struct config_node *node, |
| struct config_item *item, |
| uint32_t *flags, |
| struct config_error_type *err_type) |
| { |
| struct config_item_list *tok; |
| struct config_node *sub_node; |
| struct glist_head *nsi, *nsn; |
| bool found; |
| int errors = 0; |
| |
| *flags = 0; |
| glist_for_each_safe(nsi, nsn, &node->u.nterm.sub_nodes) { |
| sub_node = glist_entry(nsi, struct config_node, node); |
| assert(sub_node->type == TYPE_TERM); |
| found = false; |
| for (tok = item->u.lst.tokens; |
| tok->token != NULL; |
| tok++) { |
| if (strcasecmp(sub_node->u.term.varvalue, |
| tok->token) == 0) { |
| *flags |= tok->value; |
| found = true; |
| } |
| } |
| if (!found) { |
| config_proc_error(node, err_type, |
| "Unknown token (%s)", |
| sub_node->u.term.varvalue); |
| err_type->bogus = true; |
| errors++; |
| } |
| } |
| err_type->errors += errors; |
| return errors == 0; |
| } |
| |
| static bool convert_enum(struct config_node *node, |
| struct config_item *item, |
| uint32_t *val, |
| struct config_error_type *err_type) |
| { |
| struct config_item_list *tok; |
| bool found; |
| |
| tok = item->u.lst.tokens; |
| found = false; |
| while (tok->token != NULL) { |
| if (strcasecmp(node->u.term.varvalue, tok->token) == 0) { |
| *val = tok->value; |
| found = true; |
| } |
| tok++; |
| } |
| if (!found) { |
| config_proc_error(node, err_type, |
| "Unknown token (%s)", |
| node->filename, |
| node->linenumber, |
| node->u.term.varvalue); |
| err_type->bogus = true; |
| err_type->errors++; |
| } |
| return found; |
| } |
| |
| static void convert_inet_addr(struct config_node *node, |
| struct config_item *item, |
| sockaddr_t *sock, |
| struct config_error_type *err_type) |
| { |
| struct addrinfo hints; |
| struct addrinfo *res = NULL; |
| int rc; |
| |
| if (node->u.term.type == TERM_V4ADDR || |
| node->u.term.type == TERM_V4_ANY) |
| hints.ai_family = AF_INET; |
| else if (node->u.term.type == TERM_V6ADDR) |
| hints.ai_family = AF_INET6; |
| else { |
| config_proc_error(node, err_type, |
| "Expected an IP address, got a %s", |
| config_term_desc(node->u.term.type)); |
| err_type->invalid = true; |
| err_type->errors++; |
| return; |
| } |
| hints.ai_flags = AI_ADDRCONFIG; |
| hints.ai_socktype = 0; |
| hints.ai_protocol = 0; |
| rc = getaddrinfo(node->u.term.varvalue, NULL, |
| &hints, &res); |
| if (rc == 0) { |
| memcpy(sock, res->ai_addr, res->ai_addrlen); |
| } else { |
| config_proc_error(node, err_type, |
| "No IP address found for %s because:%s", |
| node->u.term.varvalue, |
| gai_strerror(rc)); |
| err_type->invalid = true; |
| err_type->errors++; |
| } |
| if (res != NULL) |
| freeaddrinfo(res); |
| return; |
| } |
| |
| /** |
| * @brief Walk the term node list and call handler for each node |
| * |
| * This is effectively a callback on each token in the list. |
| * Pass along a type hint based on what the parser recognized. |
| * |
| * @param node [IN] pointer to the statement node |
| * @param item [IN] pointer to the config_item table entry |
| * @param param_addr [IN] pointer to target struct member |
| * @param err_type [OUT] error handling ref |
| * @return number of errors |
| */ |
| |
| static void do_proc(struct config_node *node, |
| struct config_item *item, |
| void *param_addr, |
| struct config_error_type *err_type) |
| { |
| struct config_node *term_node; |
| struct glist_head *nsi, *nsn; |
| int rc = 0; |
| |
| assert(node->type == TYPE_STMT); |
| glist_for_each_safe(nsi, nsn, |
| &node->u.nterm.sub_nodes) { |
| term_node = glist_entry(nsi, |
| struct config_node, |
| node); |
| rc += item->u.proc.handler(term_node->u.term.varvalue, |
| term_node->u.term.type, |
| item, |
| param_addr, |
| term_node, |
| err_type); |
| } |
| err_type->errors += rc; |
| } |
| |
| /** |
| * @brief Lookup the first node in the list by this name |
| * |
| * @param list - head of the glist |
| * @param name - node name of interest |
| * |
| * @return first matching node or NULL |
| */ |
| |
| static struct config_node *lookup_node(struct glist_head *list, |
| const char *name) |
| { |
| struct config_node *node; |
| struct glist_head *ns; |
| |
| glist_for_each(ns, list) { |
| node = glist_entry(ns, struct config_node, node); |
| assert(node->type == TYPE_BLOCK || |
| node->type == TYPE_STMT); |
| if (strcasecmp(name, node->u.nterm.name) == 0) { |
| node->found = true; |
| return node; |
| } |
| } |
| return NULL; |
| } |
| |
| /** |
| * @brief Lookup the next node in list |
| * |
| * @param list - head of the glist |
| * @param start - continue the lookup from here |
| * @param name - node name of interest |
| * |
| * @return first matching node or NULL |
| */ |
| |
| |
| static struct config_node *lookup_next_node(struct glist_head *list, |
| struct glist_head *start, |
| const char *name) |
| { |
| struct config_node *node; |
| struct glist_head *ns; |
| |
| glist_for_each_next(start, ns, list) { |
| node = glist_entry(ns, struct config_node, node); |
| assert(node->type == TYPE_BLOCK || |
| node->type == TYPE_STMT); |
| if (strcasecmp(name, node->u.nterm.name) == 0) { |
| node->found = true; |
| return node; |
| } |
| } |
| return NULL; |
| } |
| |
| static const char *config_type_str(enum config_type type) |
| { |
| switch(type) { |
| case CONFIG_NULL: |
| return "CONFIG_NULL"; |
| case CONFIG_INT16: |
| return "CONFIG_INT16"; |
| case CONFIG_UINT16: |
| return "CONFIG_UINT16"; |
| case CONFIG_INT32: |
| return "CONFIG_INT32"; |
| case CONFIG_UINT32: |
| return "CONFIG_UINT32"; |
| case CONFIG_INT64: |
| return "CONFIG_INT64"; |
| case CONFIG_UINT64: |
| return "CONFIG_UINT64"; |
| case CONFIG_FSID: |
| return "CONFIG_FSID"; |
| case CONFIG_ANON_ID: |
| return "CONFIG_ANON_ID"; |
| case CONFIG_STRING: |
| return "CONFIG_STRING"; |
| case CONFIG_PATH: |
| return "CONFIG_PATH"; |
| case CONFIG_LIST: |
| return "CONFIG_LIST"; |
| case CONFIG_ENUM: |
| return "CONFIG_ENUM"; |
| case CONFIG_TOKEN: |
| return "CONFIG_TOKEN"; |
| case CONFIG_BOOL: |
| return "CONFIG_BOOL"; |
| case CONFIG_BOOLBIT: |
| return "CONFIG_BOOLBIT"; |
| case CONFIG_IP_ADDR: |
| return "CONFIG_IP_ADDR"; |
| case CONFIG_INET_PORT: |
| return "CONFIG_INET_PORT"; |
| case CONFIG_BLOCK: |
| return "CONFIG_BLOCK"; |
| case CONFIG_PROC: |
| return "CONFIG_PROC"; |
| } |
| return "unknown"; |
| } |
| |
| static bool do_block_init(struct config_node *blk_node, |
| struct config_item *params, |
| void *param_struct, |
| struct config_error_type *err_type) |
| { |
| struct config_item *item; |
| void *param_addr; |
| sockaddr_t *sock; |
| int rc; |
| int errors = 0; |
| |
| for (item = params; item->name != NULL; item++) { |
| param_addr = ((char *)param_struct + item->off); |
| LogFullDebug(COMPONENT_CONFIG, |
| "%p name=%s type=%s", |
| param_addr, item->name, config_type_str(item->type)); |
| switch (item->type) { |
| case CONFIG_NULL: |
| break; |
| case CONFIG_INT16: |
| *(int16_t *)param_addr = item->u.i16.def; |
| break; |
| case CONFIG_UINT16: |
| *(uint16_t *)param_addr = item->u.ui16.def; |
| break; |
| case CONFIG_INT32: |
| *(int32_t *)param_addr = item->u.i32.def; |
| break; |
| case CONFIG_UINT32: |
| *(uint32_t *)param_addr = item->u.ui32.def; |
| break; |
| case CONFIG_INT64: |
| *(int64_t *)param_addr = item->u.i64.def; |
| break; |
| case CONFIG_UINT64: |
| *(uint64_t *)param_addr = item->u.ui64.def; |
| break; |
| case CONFIG_ANON_ID: |
| *(uid_t *)param_addr = item->u.i64.def; |
| break; |
| case CONFIG_FSID: |
| ((struct fsal_fsid__ *)param_addr)->major |
| = item->u.fsid.def_maj; |
| ((struct fsal_fsid__ *)param_addr)->minor |
| = item->u.fsid.def_min; |
| break; |
| case CONFIG_STRING: |
| case CONFIG_PATH: |
| if (item->u.str.def) |
| *(char **)param_addr |
| = gsh_strdup(item->u.str.def); |
| else |
| *(char **)param_addr = NULL; |
| break; |
| case CONFIG_TOKEN: |
| *(uint32_t *)param_addr = item->u.lst.def; |
| break; |
| case CONFIG_BOOL: |
| *(bool *)param_addr = item->u.b.def; |
| break; |
| case CONFIG_BOOLBIT: |
| if (item->u.bit.def) |
| *(uint32_t *)param_addr |= item->u.bit.bit; |
| else |
| *(uint32_t *)param_addr &= ~item->u.bit.bit; |
| break; |
| case CONFIG_LIST: |
| *(uint32_t *)param_addr |= item->u.lst.def; |
| LogFullDebug(COMPONENT_CONFIG, |
| "%p CONFIG_LIST %s mask=%08x def=%08x" |
| " value=%08"PRIx32, |
| param_addr, |
| item->name, |
| item->u.lst.mask, item->u.lst.def, |
| *(uint32_t *)param_addr); |
| break; |
| case CONFIG_ENUM: |
| *(uint32_t *)param_addr |= item->u.lst.def; |
| LogFullDebug(COMPONENT_CONFIG, |
| "%p CONFIG_ENUM %s mask=%08x def=%08x" |
| " value=%08"PRIx32, |
| param_addr, |
| item->name, |
| item->u.lst.mask, item->u.lst.def, |
| *(uint32_t *)param_addr); |
| break; |
| case CONFIG_IP_ADDR: |
| sock = (sockaddr_t *)param_addr; |
| memset(sock, 0, sizeof(sockaddr_t)); |
| errno = 0; |
| if (index(item->u.ip.def, ':') != NULL) { |
| sock->ss_family = AF_INET6; |
| rc = inet_pton(AF_INET6, |
| item->u.ip.def, |
| &((struct sockaddr_in6 *)sock) |
| ->sin6_addr); |
| } else if (index(item->u.ip.def, '.') != NULL) { |
| sock->ss_family = AF_INET; |
| rc = inet_pton(AF_INET, |
| item->u.ip.def, |
| &((struct sockaddr_in *)sock) |
| ->sin_addr); |
| } else { |
| config_proc_error(blk_node, err_type, |
| "Bad default for %s: %s", |
| item->name, |
| item->u.ip.def); |
| errors++; |
| break; |
| } |
| if (rc <= 0) { |
| config_proc_error(blk_node, err_type, |
| "Cannot set IP default for %s to %s because %s", |
| item->name, |
| item->u.ip.def, |
| strerror(errno)); |
| errors++; |
| } |
| break; |
| case CONFIG_INET_PORT: |
| *(uint16_t *)param_addr = htons(item->u.ui16.def); |
| break; |
| case CONFIG_BLOCK: |
| (void) item->u.blk.init(NULL, param_addr); |
| break; |
| case CONFIG_PROC: |
| (void) item->u.proc.init(NULL, param_addr); |
| break; |
| default: |
| config_proc_error(blk_node, err_type, |
| "Cannot set default for parameter %s, type(%d) yet", |
| item->name, item->type); |
| errors++; |
| break; |
| } |
| } |
| err_type->errors += errors; |
| return errors == 0; |
| } |
| |
| /** |
| * @brief This is the NOOP init for block and proc parsing |
| * |
| * @param link_mem [IN] pointer to member in referencing structure |
| * @param self_struct [IN] pointer to space reserved/allocated for block |
| * |
| * @return a pointer depending on context |
| */ |
| |
| void *noop_conf_init(void *link_mem, void *self_struct) |
| { |
| assert(link_mem != NULL || self_struct != NULL); |
| |
| if (link_mem == NULL) |
| return self_struct; |
| else if (self_struct == NULL) |
| return link_mem; |
| else |
| return NULL; |
| } |
| |
| int noop_conf_commit(void *node, void *link_mem, void *self_struct, |
| struct config_error_type *err_type) |
| { |
| return 0; |
| } |
| |
| static bool proc_block(struct config_node *node, |
| struct config_item *item, |
| void *link_mem, |
| struct config_error_type *err_type); |
| |
| /* |
| * All the types of integers supported by the config processor |
| */ |
| |
| union gen_int { |
| bool b; |
| int16_t i16; |
| uint16_t ui16; |
| int32_t i32; |
| uint32_t ui32; |
| int64_t i64; |
| uint64_t ui64; |
| }; |
| |
| /** |
| * @brief Process the defined tokens in the params table |
| * |
| * The order of the parameter table is important. If any |
| * parameter requires that another parameter in the table be |
| * processed first, order the table appropriately. |
| * |
| * @param blk [IN] a parse tree node of type CONFIG_BLOCK |
| * @param params [IN] points to a NULL term'd table of config_item's |
| * @param relax [IN] if true, don't report unrecognized params. |
| * @param param_struct [IN/OUT] the structure to be filled. |
| * |
| * @returns 0 on success, number of errors on failure. |
| */ |
| |
| static int do_block_load(struct config_node *blk, |
| struct config_item *params, |
| bool relax, |
| void *param_struct, |
| struct config_error_type *err_type) |
| { |
| struct config_item *item; |
| void *param_addr; |
| struct config_node *node, *term_node, *next_node = NULL; |
| struct glist_head *ns; |
| int errors = 0; |
| |
| for (item = params; item->name != NULL; item++) { |
| uint64_t num64; |
| bool bool_val; |
| uint32_t num32; |
| |
| node = lookup_node(&blk->u.nterm.sub_nodes, item->name); |
| if ((item->flags & CONFIG_MANDATORY) && (node == NULL)) { |
| err_type->missing = true; |
| errors = ++err_type->errors; |
| config_proc_error(blk, err_type, |
| "Mandatory field, %s is missing from block (%s)", |
| item->name, blk->u.nterm.name); |
| continue; |
| } |
| while (node != NULL) { |
| next_node = lookup_next_node(&blk->u.nterm.sub_nodes, |
| &node->node, item->name); |
| if (next_node != NULL && |
| (item->flags & CONFIG_UNIQUE)) { |
| config_proc_error(next_node, err_type, |
| "Parameter %s set more than once", |
| next_node->u.nterm.name); |
| err_type->unique = true; |
| errors = ++err_type->errors; |
| node = next_node; |
| continue; |
| } |
| param_addr = ((char *)param_struct + item->off); |
| LogFullDebug(COMPONENT_CONFIG, |
| "%p name=%s type=%s", |
| param_addr, item->name, |
| config_type_str(item->type)); |
| if (glist_empty(&node->u.nterm.sub_nodes)) { |
| LogInfo(COMPONENT_CONFIG, |
| "%s %s is empty", |
| (node->type == TYPE_STMT |
| ? "Statement" : "Block"), |
| node->u.nterm.name); |
| node = next_node; |
| continue; |
| } |
| term_node = glist_first_entry(&node->u.nterm.sub_nodes, |
| struct config_node, |
| node); |
| if ((item->type != CONFIG_BLOCK && |
| item->type != CONFIG_PROC && |
| item->type != CONFIG_LIST) && |
| glist_length(&node->u.nterm.sub_nodes) > 1) { |
| config_proc_error(node, err_type, |
| "%s can have only one option. First one is (%s)", |
| node->u.nterm.name, |
| term_node->u.term.varvalue); |
| err_type->invalid = true; |
| errors = ++err_type->errors; |
| node = next_node; |
| continue; |
| } |
| switch (item->type) { |
| case CONFIG_NULL: |
| break; |
| case CONFIG_INT16: |
| if (convert_number(term_node, item, |
| &num64, err_type)) |
| *(int16_t *)param_addr = num64; |
| break; |
| case CONFIG_UINT16: |
| if (convert_number(term_node, item, |
| &num64, err_type)) |
| *(uint16_t *)param_addr = num64; |
| break; |
| case CONFIG_INT32: |
| if (convert_number(term_node, item, |
| &num64, err_type)) { |
| *(int32_t *)param_addr = num64; |
| if (item->flags & CONFIG_MARK_SET) { |
| void *mask_addr; |
| |
| mask_addr = |
| ((char *)param_struct |
| + item->u.i32.set_off); |
| *(uint32_t *)mask_addr |
| |= item->u.i32.bit; |
| } |
| } |
| break; |
| case CONFIG_UINT32: |
| if (convert_number(term_node, item, |
| &num64, err_type)) |
| *(uint32_t *)param_addr = num64; |
| break; |
| case CONFIG_INT64: |
| if (convert_number(term_node, item, |
| &num64, err_type)) |
| *(int64_t *)param_addr = num64; |
| break; |
| case CONFIG_UINT64: |
| if (convert_number(term_node, item, |
| &num64, err_type)) |
| *(uint64_t *)param_addr = num64; |
| break; |
| case CONFIG_ANON_ID: |
| if (convert_number(term_node, item, |
| &num64, err_type)) { |
| *(uid_t *)param_addr = num64; |
| if (item->flags & CONFIG_MARK_SET) { |
| void *mask_addr; |
| |
| mask_addr = |
| ((char *)param_struct |
| + item->u.i64.set_off); |
| *(uint32_t *)mask_addr |
| |= item->u.i64.bit; |
| } |
| } |
| break; |
| case CONFIG_FSID: |
| if (convert_fsid(term_node, param_addr, |
| err_type)) { |
| if (item->flags & CONFIG_MARK_SET) { |
| void *mask_addr; |
| |
| mask_addr = |
| ((char *)param_struct |
| + item->u.fsid.set_off); |
| *(uint32_t *)mask_addr |
| |= item->u.fsid.bit; |
| } |
| } |
| break; |
| case CONFIG_STRING: |
| if (*(char **)param_addr != NULL) |
| gsh_free(*(char **)param_addr); |
| *(char **)param_addr = |
| gsh_strdup(term_node->u.term.varvalue); |
| break; |
| case CONFIG_PATH: |
| if (*(char **)param_addr != NULL) |
| gsh_free(*(char **)param_addr); |
| /** @todo validate path with access() */ |
| *(char **)param_addr = |
| gsh_strdup(term_node->u.term.varvalue); |
| break; |
| case CONFIG_TOKEN: |
| if (convert_enum(term_node, item, &num32, |
| err_type)) |
| *(uint32_t *)param_addr = num32; |
| break; |
| case CONFIG_BOOL: |
| if (convert_bool(term_node, &bool_val, |
| err_type)) |
| *(bool *)param_addr = bool_val; |
| break; |
| case CONFIG_BOOLBIT: |
| if (convert_bool(term_node, &bool_val, |
| err_type)) { |
| if (bool_val) |
| *(uint32_t *)param_addr |
| |= item->u.bit.bit; |
| else |
| *(uint32_t *)param_addr |
| &= ~item->u.bit.bit; |
| if (item->flags & CONFIG_MARK_SET) { |
| void *mask_addr; |
| |
| mask_addr = |
| ((char *)param_struct |
| + item->u.bit.set_off); |
| *(uint32_t *)mask_addr |
| |= item->u.bit.bit; |
| } |
| } |
| break; |
| case CONFIG_LIST: |
| if (item->u.lst.def == |
| (*(uint32_t *)param_addr & item->u.lst.mask)) |
| *(uint32_t *)param_addr &= |
| ~item->u.lst.mask; |
| if (convert_list(node, item, &num32, |
| err_type)) { |
| *(uint32_t *)param_addr |= num32; |
| if (item->flags & CONFIG_MARK_SET) { |
| void *mask_addr; |
| |
| mask_addr = |
| ((char *)param_struct |
| + item->u.lst.set_off); |
| *(uint32_t *)mask_addr |
| |= item->u.lst.mask; |
| } |
| } |
| LogFullDebug(COMPONENT_CONFIG, |
| "%p CONFIG_LIST %s mask=%08x flags=%08x" |
| " value=%08"PRIx32, |
| param_addr, |
| item->name, |
| item->u.lst.mask, num32, |
| *(uint32_t *)param_addr); |
| break; |
| case CONFIG_ENUM: |
| if (item->u.lst.def == |
| (*(uint32_t *)param_addr & item->u.lst.mask)) |
| *(uint32_t *)param_addr &= |
| ~item->u.lst.mask; |
| if (convert_enum(term_node, item, &num32, |
| err_type)) { |
| *(uint32_t *)param_addr |= num32; |
| if (item->flags & CONFIG_MARK_SET) { |
| void *mask_addr; |
| |
| mask_addr = |
| ((char *)param_struct |
| + item->u.lst.set_off); |
| *(uint32_t *)mask_addr |
| |= item->u.lst.mask; |
| } |
| } |
| LogFullDebug(COMPONENT_CONFIG, |
| "%p CONFIG_ENUM %s mask=%08x flags=%08x" |
| " value=%08"PRIx32, |
| param_addr, |
| item->name, |
| item->u.lst.mask, num32, |
| *(uint32_t *)param_addr); |
| break; |
| case CONFIG_IP_ADDR: |
| convert_inet_addr(term_node, item, |
| (sockaddr_t *)param_addr, |
| err_type); |
| break; |
| case CONFIG_INET_PORT: |
| if (convert_number(term_node, item, |
| &num64, err_type)) |
| *(uint16_t *)param_addr = |
| htons((uint16_t)num64); |
| break; |
| case CONFIG_BLOCK: |
| if (!proc_block(node, item, param_addr, |
| err_type)) |
| config_proc_error(node, err_type, |
| "Errors processing block (%s)", |
| node->u.nterm.name); |
| break; |
| case CONFIG_PROC: |
| do_proc(node, item, param_addr, err_type); |
| break; |
| default: |
| config_proc_error(term_node, err_type, |
| "Cannot set value for type(%d) yet", |
| item->type); |
| err_type->internal = true; |
| errors = ++err_type->errors; |
| break; |
| } |
| node = next_node; |
| } |
| } |
| if (relax) |
| return errors; |
| |
| /* We've been marking config nodes as being "seen" during the |
| * scans. Report the bogus and typo inflicted bits. |
| */ |
| glist_for_each(ns, &blk->u.nterm.sub_nodes) { |
| node = glist_entry(ns, struct config_node, node); |
| if (node->found) |
| node->found = false; |
| else { |
| config_proc_error(node, err_type, |
| "Unknown parameter (%s)", |
| node->u.nterm.name); |
| err_type->bogus = true; |
| errors++; |
| } |
| } |
| return errors; |
| } |
| |
| /** |
| * @brief Process a block |
| * |
| * The item arg supplies two function pointers that |
| * are defined as follows: |
| * |
| * init |
| * This function manages memory for the sub-block's processing. |
| * It has two arguments, a pointer to the link_mem param struct and |
| * a pointer to the self_struct param struct. |
| * If the self_struct argument is NULL, it returns a pointer to a usable |
| * self_struct param struct. This can either be allocate memory |
| * or a pointer to existing memory. |
| * |
| * If the self_struct argument is not NULL, it is the pointer it returned above. |
| * The function reverts whatever it did above. |
| * |
| * commit |
| * This function attaches the build param struct to its link_mem. |
| * Before it does the attach, it will do validation of input if required. |
| * Returns 0 if validation passes and the attach is successful. Otherwise, |
| * it returns an error which will trigger a release of resourcs acquired |
| * by the self_struct_init. |
| * |
| * Both of these functions are called in the context of the link_mem parse. |
| * It is assumed that the link_mem has already been initialized including |
| * the init of any glists before this function is called. |
| * The self_struct does its own init of things like glist in param_mem. |
| * |
| * @param node - parse node of the subblock |
| * @param item - config_item describing block |
| * @param link_mem - pointer to the link_mem structure |
| * @param err_type [OUT] pointer to error type return |
| * |
| * @ return true on success, false on errors. |
| */ |
| |
| static bool proc_block(struct config_node *node, |
| struct config_item *item, |
| void *link_mem, |
| struct config_error_type *err_type) |
| { |
| void *param_struct; |
| int errors = 0; |
| |
| assert(item->type == CONFIG_BLOCK); |
| |
| if (node->type != TYPE_BLOCK) { |
| config_proc_error(node, err_type, |
| "%s is not a block!", |
| item->name); |
| err_type->invalid = true; |
| err_type->errors++; |
| return false; |
| } |
| param_struct = item->u.blk.init(link_mem, NULL); |
| if (param_struct == NULL) { |
| config_proc_error(node, err_type, |
| "Could not init block for %s", |
| item->name); |
| err_type->init = true; |
| err_type->errors++; |
| return false; |
| } |
| LogFullDebug(COMPONENT_CONFIG, |
| "------ At (%s:%d): do_block_init %s", |
| node->filename, |
| node->linenumber, |
| item->name); |
| if (!do_block_init(node, item->u.blk.params, |
| param_struct, err_type)) { |
| config_proc_error(node, err_type, |
| "Could not initialize parameters for %s", |
| item->name); |
| err_type->init = true; |
| goto err_out; |
| } |
| if (item->u.blk.display != NULL) |
| item->u.blk.display("DEFAULTS", node, |
| link_mem, param_struct); |
| LogFullDebug(COMPONENT_CONFIG, |
| "------ At (%s:%d): do_block_load %s", |
| node->filename, |
| node->linenumber, |
| item->name); |
| errors = do_block_load(node, |
| item->u.blk.params, |
| (item->flags & CONFIG_RELAX) ? true : false, |
| param_struct, err_type); |
| if (errors > 0 && !config_error_is_harmless(err_type)) { |
| config_proc_error(node, err_type, |
| "%d errors while processing parameters for %s", |
| errors, |
| item->name); |
| goto err_out; |
| } |
| LogFullDebug(COMPONENT_CONFIG, |
| "------ At (%s:%d): commit %s", |
| node->filename, |
| node->linenumber, |
| item->name); |
| errors = item->u.blk.commit(node, link_mem, param_struct, err_type); |
| if (errors > 0 && !config_error_is_harmless(err_type)) { |
| config_proc_error(node, err_type, |
| "%d validation errors in block %s", |
| errors, |
| item->name); |
| goto err_out; |
| } |
| if (item->u.blk.display != NULL) |
| item->u.blk.display("RESULT", node, link_mem, param_struct); |
| |
| if (err_type->dispose) { |
| /* We had a config update case where this block must be |
| * disposed of. Need to clear the flag so the next config |
| * block processed gets a clear slate. |
| */ |
| (void)item->u.blk.init(link_mem, param_struct); |
| err_type->dispose = false; |
| } |
| |
| return true; |
| |
| err_out: |
| (void)item->u.blk.init(link_mem, param_struct); |
| return false; |
| } |
| |
| /** |
| * @brief Find the root of the parse tree given a node. |
| * |
| * @param node [IN] pointer to a TYPE_BLOCK node. |
| * |
| * @return the root of the tree. Errors are asserted. |
| */ |
| |
| config_file_t get_parse_root(void *node) |
| { |
| struct config_node *parent; |
| struct config_root *root; |
| |
| parent = (struct config_node *)node; |
| assert(parent->type == TYPE_BLOCK); |
| while (parent->u.nterm.parent != NULL) { |
| parent = parent->u.nterm.parent; |
| assert(parent->type == TYPE_ROOT || |
| parent->type == TYPE_BLOCK); |
| } |
| assert(parent->type == TYPE_ROOT); |
| root = container_of(parent, struct config_root, root); |
| return (config_file_t)root; |
| } |
| |
| /** |
| * @brief Data structures for walking parse trees |
| * |
| * These structures hold the result of the parse of a block |
| * description string that is passed to find_config_nodes |
| * |
| * expr_parse is for blocks |
| * |
| * expr_parse_arg is for indexing/matching parameters. |
| */ |
| |
| struct expr_parse_arg { |
| char *name; |
| char *value; |
| struct expr_parse_arg *next; |
| }; |
| |
| struct expr_parse { |
| char *name; |
| struct expr_parse_arg *arg; |
| struct expr_parse *next; |
| }; |
| |
| /** |
| * @brief Skip 0 or more white space chars |
| * |
| * @return pointer to first non-white space char |
| */ |
| |
| static inline char *skip_white(char *sp) |
| { |
| while (isspace(*sp)) |
| sp++; |
| return sp; |
| } |
| |
| /** |
| * @brief Find the end of a token, i.e. [A-Za-z0-9_]+ |
| * |
| * @return pointer to first unmatched char. |
| */ |
| |
| static inline char *end_of_token(char *sp) |
| { |
| while (isalnum(*sp) || *sp == '_') |
| sp++; |
| return sp; |
| } |
| |
| /** |
| * @brief Find end of a value string. |
| * |
| * @return pointer to first white or syntax token |
| */ |
| |
| static inline char *end_of_value(char *sp) |
| { |
| while (*sp != '\0') { |
| if (isspace(*sp) || *sp == ',' || *sp == ')' || *sp == '(') |
| break; |
| sp++; |
| } |
| return sp; |
| } |
| |
| /** |
| * @brief Release storage for arg list |
| */ |
| |
| static void free_expr_parse_arg(struct expr_parse_arg *argp) |
| { |
| struct expr_parse_arg *nxtarg, *arg = NULL; |
| |
| if (argp == NULL) |
| return; |
| for (arg = argp; arg != NULL; arg = nxtarg) { |
| nxtarg = arg->next; |
| gsh_free(arg->name); |
| gsh_free(arg->value); |
| gsh_free(arg); |
| } |
| } |
| |
| /** |
| * @brief Release storage for the expression parse tree |
| */ |
| |
| static void free_expr_parse(struct expr_parse *snode) |
| { |
| struct expr_parse *nxtnode, *node = NULL; |
| |
| if (snode == NULL) |
| return; |
| for (node = snode; node != NULL; node = nxtnode) { |
| nxtnode = node->next; |
| gsh_free(node->name); |
| free_expr_parse_arg(node->arg); |
| gsh_free(node); |
| } |
| } |
| |
| /** |
| * @brief Parse "token = string" or "token1 = string1, ..." |
| * |
| * @param arg_str [IN] pointer to first char to parse |
| * @param argp [OUT] list of parsed expr_parse_arg |
| * |
| * @return pointer to first unmatching char or NULL on parse error |
| */ |
| |
| static char *parse_args(char *arg_str, struct expr_parse_arg **argp) |
| { |
| char *sp, *name, *val; |
| int saved_char; |
| struct expr_parse_arg *arg = NULL; |
| |
| sp = skip_white(arg_str); |
| if (*sp == '\0') |
| return NULL; |
| name = sp; /* name matches [A-Za-z_][A-Za-z0-9_]* */ |
| if (!(isalpha(*sp) || *sp == '_')) |
| return NULL; |
| sp = end_of_token(sp); |
| if (isspace(*sp)) |
| *sp++ = '\0'; |
| sp = skip_white(sp); |
| if (*sp != '=') |
| return NULL; |
| *sp++ = '\0'; /* name = ... */ |
| sp = skip_white(sp); |
| if (*sp == '\0') |
| return NULL; |
| val = sp; |
| sp = end_of_value(sp); |
| if (*sp == '\0') |
| return NULL; |
| if (isspace(*sp)) { /* name = val */ |
| *sp++ = '\0'; |
| sp = skip_white(sp); |
| } |
| if (*sp == '\0') |
| return NULL; |
| saved_char = *sp; |
| *sp = '\0'; |
| arg = gsh_calloc(1, sizeof(struct expr_parse_arg)); |
| arg->name = gsh_strdup(name); |
| arg->value = gsh_strdup(val); |
| *argp = arg; |
| if (saved_char != ',') { |
| *sp = saved_char; |
| return sp; |
| } |
| sp++; /* name = val , ... */ |
| return parse_args(sp, &arg->next); |
| } |
| |
| /** |
| * @brief Parse "token ( .... )" and "token ( .... ) . token ( ... )" |
| * |
| * @param str [IN] pointer to first char to be parsed |
| * @param node [OUT] reference pointer to returned expr_parse list |
| * |
| * @return pointer to first char after parse or NULL on errors |
| */ |
| |
| static char *parse_block(char *str, struct expr_parse **node) |
| { |
| char *sp, *name; |
| struct expr_parse_arg *arg = NULL; |
| struct expr_parse *new_node; |
| |
| sp = skip_white(str); |
| if (*sp == '\0') |
| return NULL; |
| name = sp; /* name matches [A-Za-z_][A-Za-z0-9_]* */ |
| if (!(isalpha(*sp) || *sp != '_')) |
| return NULL; |
| if (*sp == '_') |
| sp++; |
| sp = end_of_token(sp); |
| if (isspace(*sp)) |
| *sp++ = '\0'; |
| sp = skip_white(sp); |
| if (*sp != '(') |
| return NULL; |
| *sp++ = '\0'; /* name ( ... */ |
| sp = parse_args(sp, &arg); |
| if (sp == NULL) |
| goto errout; |
| sp = skip_white(sp); |
| if (*sp == ')') { /* name ( ... ) */ |
| new_node = gsh_calloc(1, sizeof(struct expr_parse)); |
| new_node->name = gsh_strdup(name); |
| new_node->arg = arg; |
| *node = new_node; |
| return sp + 1; |
| } |
| |
| errout: |
| free_expr_parse_arg(arg); |
| return NULL; |
| } |
| |
| /** |
| * @brief Parse a treewalk expression |
| * |
| * A full expression describes the path down a parse tree with |
| * each node described as "block_name ( qualifiers )". |
| * Each node in the path is a series of block name and qualifiers |
| * separated by '.'. |
| * |
| * The parse errors are detected as either ret val == NULL |
| * or *retval != '\0', i.e. pointing to a garbage char |
| * |
| * @param expr [IN] pointer to the expression to be parsed |
| * @param expr_node [OUT] pointer to expression parse tree |
| * |
| * @return pointer to first char past parse or NULL for erros. |
| */ |
| |
| static char *parse_expr(char *expr, struct expr_parse **expr_node) |
| { |
| char *sp; |
| struct expr_parse *node = NULL, *prev_node = NULL, *root_node = NULL; |
| char *lexpr = alloca(strlen(expr) + 1); |
| |
| strcpy(lexpr, expr); |
| sp = lexpr; |
| while (sp != NULL && *sp != '\0') { |
| sp = parse_block(sp, &node); /* block is name ( ... ) */ |
| if (root_node == NULL) |
| root_node = node; |
| else |
| prev_node->next = node; |
| prev_node = node; |
| if (sp == NULL) |
| break; |
| sp = skip_white(sp); |
| if (*sp == '.') { |
| sp++; /* boock '.' block ... */ |
| } else if (*sp != '\0') { |
| sp = NULL; |
| break; |
| } |
| } |
| *expr_node = root_node; |
| if (sp != NULL) |
| return expr + (sp - lexpr); |
| else |
| return NULL; |
| } |
| |
| static inline bool match_one_term(char *value, struct config_node *node) |
| { |
| struct config_node *term_node; |
| struct glist_head *ts; |
| |
| glist_for_each(ts, &node->u.nterm.sub_nodes) { |
| term_node = glist_entry(ts, struct config_node, node); |
| assert(term_node->type == TYPE_TERM); |
| if (strcasecmp(value, term_node->u.term.varvalue) == 0) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * @brief Select block based on the evaluation of the qualifier args |
| * |
| * use each qualifier to match. The token name is found in the block. |
| * once found, the token value is compared. '*' matches anything. else |
| * it is a case-insensitive string compare. |
| * |
| * @param blk [IN] pointer to config_node descibing the block |
| * @param expr [IN] expr_parse node to match |
| * |
| * @return true if matched, else false |
| */ |
| |
| static bool match_block(struct config_node *blk, |
| struct expr_parse *expr) |
| { |
| struct glist_head *ns; |
| struct config_node *sub_node; |
| struct expr_parse_arg *arg; |
| bool found = false; |
| |
| assert(blk->type == TYPE_BLOCK); |
| for (arg = expr->arg; arg != NULL; arg = arg->next) { |
| glist_for_each(ns, &blk->u.nterm.sub_nodes) { |
| sub_node = glist_entry(ns, struct config_node, node); |
| if (sub_node->type == TYPE_STMT && |
| strcasecmp(arg->name, |
| sub_node->u.nterm.name) == 0) { |
| found = true; |
| if (expr->next == NULL && |
| strcasecmp(arg->value, "*") == 0) |
| continue; |
| if (!match_one_term(arg->value, sub_node)) |
| return false; |
| } |
| } |
| } |
| return found; |
| } |
| |
| /** |
| * @brief Find nodes in parse tree using expression |
| * |
| * Lookup signature describes the nested block it is of the form: |
| * block_name '(' param_name '=' param_value ')' ... |
| * where block_name and param_name are alphanumerics and param_value is |
| * and arbitrary token. |
| * This can name a subblock (the ...) part by a '.' sub-block_name... |
| * as in: |
| |
| * some_block(indexing_param = foo).the_subblock(its_index = baz) |
| * |
| * NOTE: This will return ENOENT not only if the the comparison |
| * fails but also if the search cannot find the token. In other words, |
| * the match succeeds only if there is a node in the parse tree and it |
| * matches. |
| * |
| * @param config [IN] root of parse tree |
| * @param expr_str [IN] expression description of block |
| * @param node_list [OUT] pointer to store node list |
| * @param err_type [OUT] error processing |
| * |
| * @return 0 on success, errno for errors |
| */ |
| |
| int find_config_nodes(config_file_t config, char *expr_str, |
| struct config_node_list **node_list, |
| struct config_error_type *err_type) |
| { |
| struct config_root *tree = (struct config_root *)config; |
| struct glist_head *ns; |
| struct config_node *sub_node; |
| struct config_node *top; |
| struct expr_parse *expr, *expr_head = NULL; |
| struct config_node_list *list = NULL, *list_tail = NULL; |
| char *ep; |
| int rc = EINVAL; |
| bool found = false; |
| |
| if (tree->root.type != TYPE_ROOT) { |
| config_proc_error(&tree->root, err_type, |
| "Expected to start at parse tree root for (%s)", |
| expr_str); |
| goto out; |
| } |
| top = &tree->root; |
| ep = parse_expr(expr_str, &expr_head); |
| if (ep == NULL || *ep != '\0') |
| goto out; |
| expr = expr_head; |
| *node_list = NULL; |
| again: |
| glist_for_each(ns, &top->u.nterm.sub_nodes) { |
| #ifdef DS_ONLY_WAS |
| /* recent changes to parsing may prevent this, |
| * but retain code here for future reference. |
| * -- WAS |
| */ |
| if (ns == NULL) { |
| config_proc_error(top, err_type, |
| "Missing sub_node for (%s)", |
| expr_str); |
| break; |
| } |
| #endif |
| sub_node = glist_entry(ns, struct config_node, node); |
| if (strcasecmp(expr->name, sub_node->u.nterm.name) == 0 && |
| sub_node->type == TYPE_BLOCK && |
| match_block(sub_node, expr)) { |
| if (expr->next != NULL) { |
| top = sub_node; |
| expr = expr->next; |
| goto again; |
| } |
| list = gsh_calloc(1, sizeof(struct config_node_list)); |
| list->tree_node = sub_node; |
| if (*node_list == NULL) |
| *node_list = list; |
| else |
| list_tail->next = list; |
| list_tail = list; |
| found = true; |
| } |
| } |
| if (found) |
| rc = 0; |
| else |
| rc = ENOENT; |
| out: |
| free_expr_parse(expr_head); |
| return rc; |
| } |
| |
| /** |
| * @brief Fill configuration structure from a parse tree node |
| * |
| * If param == NULL, there is no link_mem and the self_struct storage |
| * is allocated and attached to its destination dynamically. |
| * |
| * @param tree_node [IN] A CONFIG_BLOCK node in the parse tree |
| * @param conf_blk [IN] pointer to configuration description |
| * @param param [IN] pointer to struct to fill or NULL |
| * @param unique [IN] bool if true, more than one is an error |
| * @param err_type [OUT] error type return |
| * |
| * @returns -1 on errors, 0 for success |
| */ |
| |
| int load_config_from_node(void *tree_node, |
| struct config_block *conf_blk, |
| void *param, |
| bool unique, |
| struct config_error_type *err_type) |
| { |
| struct config_node *node = (struct config_node *)tree_node; |
| char *blkname = conf_blk->blk_desc.name; |
| |
| if (node == NULL) { |
| config_proc_error(NULL, err_type, |
| "Missing tree_node for (%s)", |
| blkname); |
| err_type->missing = true; |
| return -1; |
| } |
| if (node->type == TYPE_BLOCK) { |
| if (strcasecmp(node->u.nterm.name, blkname) != 0) { |
| config_proc_error(node, err_type, |
| "Looking for block (%s), got (%s)", |
| blkname, node->u.nterm.name); |
| err_type->invalid = true; |
| err_type->errors++; |
| return -1; |
| } |
| } else { |
| config_proc_error(node, err_type, |
| "Unrecognized parse tree node type for block (%s)", |
| blkname); |
| err_type->invalid = true; |
| err_type->errors++; |
| return -1; |
| } |
| if (!proc_block(node, &conf_blk->blk_desc, param, err_type)) { |
| config_proc_error(node, err_type, |
| "Errors found in configuration block %s", |
| blkname); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /** |
| * @brief Fill configuration structure from parse tree |
| * |
| * If param == NULL, there is no link_mem and the self_struct storage |
| * is allocated and attached to its destination dynamically. |
| * |
| * @param config [IN] root of parse tree |
| * @param conf_blk [IN] pointer to configuration description |
| * @param param [IN] pointer to struct to fill or NULL |
| * @param unique [IN] bool if true, more than one is an error |
| * @param err_type [OUT] pointer to error type return |
| * |
| * @returns number of blocks found. -1 on errors, errors are in err_type |
| */ |
| |
| int load_config_from_parse(config_file_t config, |
| struct config_block *conf_blk, |
| void *param, |
| bool unique, |
| struct config_error_type *err_type) |
| { |
| struct config_root *tree = (struct config_root *)config; |
| struct config_node *node = NULL; |
| struct glist_head *ns; |
| char *blkname = conf_blk->blk_desc.name; |
| int found = 0; |
| int prev_errs = err_type->errors; |
| void *blk_mem = NULL; |
| |
| if (tree == NULL) { |
| config_proc_error(NULL, err_type, |
| "Missing parse tree root for (%s)", |
| blkname); |
| err_type->missing = true; |
| return -1; |
| } |
| if (tree->root.type != TYPE_ROOT) { |
| config_proc_error(&tree->root, err_type, |
| "Expected to start at parse tree root for (%s)", |
| blkname); |
| err_type->internal = true; |
| return -1; |
| } |
| if (param != NULL) { |
| blk_mem = conf_blk->blk_desc.u.blk.init(NULL, param); |
| if (blk_mem == NULL) { |
| config_proc_error(&tree->root, err_type, |
| "Top level block init failed for (%s)", |
| blkname); |
| err_type->internal = true; |
| return -1; |
| } |
| } |
| glist_for_each(ns, &tree->root.u.nterm.sub_nodes) { |
| node = glist_entry(ns, struct config_node, node); |
| if (node->type == TYPE_BLOCK && |
| strcasecmp(blkname, node->u.nterm.name) == 0) { |
| if (found > 0 && |
| (conf_blk->blk_desc.flags & CONFIG_UNIQUE)) { |
| config_proc_error(node, err_type, |
| "Only one %s block allowed", |
| blkname); |
| } else { |
| if (!proc_block(node, |
| &conf_blk->blk_desc, |
| blk_mem, |
| err_type)) |
| config_proc_error(node, err_type, |
| "Errors processing block (%s)", |
| blkname); |
| else |
| found++; |
| } |
| } |
| } |
| if (found == 0) { |
| /* Found nothing but we have to do the allocate and init |
| * at least. Use a fake, not NULL link_mem */ |
| blk_mem = param != NULL ? |
| param : conf_blk->blk_desc.u.blk.init((void *)~0UL, |
| NULL); |
| assert(blk_mem != NULL); |
| if (!do_block_init(&tree->root, |
| conf_blk->blk_desc.u.blk.params, |
| blk_mem, err_type)) { |
| config_proc_error(&tree->root, err_type, |
| "Could not initialize defaults for block %s", |
| blkname); |
| err_type->init = true; |
| } |
| } |
| if (err_type->errors > prev_errs) { |
| char *errstr = err_type_str(err_type); |
| |
| config_proc_error(node, err_type, |
| "%d %s errors found block %s", |
| err_type->errors - prev_errs, |
| errstr != NULL ? errstr : "unknown", |
| blkname); |
| if (errstr != NULL) |
| gsh_free(errstr); |
| } |
| return found; |
| } |
| |