| /*****************************************************************************\ |
| * parse_config.c - parse any slurm.conf-like configuration file |
| * |
| * NOTE: when you see the prefix "s_p_", think "slurm parser". |
| ***************************************************************************** |
| * Copyright (C) 2006-2007 The Regents of the University of California. |
| * Copyright (C) 2008-2009 Lawrence Livermore National Security. |
| * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). |
| * Written by Christopher J. Morrone <morrone2@llnl.gov>. |
| * CODE-OCEC-09-009. All rights reserved. |
| * |
| * This file is part of SLURM, a resource management program. |
| * For details, see <http://slurm.schedmd.com/>. |
| * Please also read the included file: DISCLAIMER. |
| * |
| * SLURM is free software; you can redistribute it and/or modify it under |
| * the terms of the GNU General Public License as published by the Free |
| * Software Foundation; either version 2 of the License, or (at your option) |
| * any later version. |
| * |
| * In addition, as a special exception, the copyright holders give permission |
| * to link the code of portions of this program with the OpenSSL library under |
| * certain conditions as described in each individual source file, and |
| * distribute linked combinations including the two. You must obey the GNU |
| * General Public License in all respects for all of the code used other than |
| * OpenSSL. If you modify file(s) with this exception, you may extend this |
| * exception to your version of the file(s), but you are not obligated to do |
| * so. If you do not wish to do so, delete this exception statement from your |
| * version. If you delete this exception statement from all source files in |
| * the program, then also delete it here. |
| * |
| * SLURM 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 General Public License for more |
| * details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with SLURM; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| \*****************************************************************************/ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #include <ctype.h> |
| #include <regex.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| /* #include "src/common/slurm_protocol_defs.h" */ |
| #include "src/common/log.h" |
| #include "src/common/macros.h" |
| #include "src/common/xmalloc.h" |
| #include "src/common/xstring.h" |
| #include "src/common/xassert.h" |
| /* #include "src/common/slurm_rlimits_info.h" */ |
| #include "src/common/parse_config.h" |
| |
| #include "slurm/slurm.h" |
| |
| strong_alias(s_p_hashtbl_create, slurm_s_p_hashtbl_create); |
| strong_alias(s_p_hashtbl_destroy, slurm_s_p_hashtbl_destroy); |
| strong_alias(s_p_parse_file, slurm_s_p_parse_file); |
| strong_alias(s_p_parse_pair, slurm_s_p_parse_pair); |
| strong_alias(s_p_parse_line, slurm_s_p_parse_line); |
| strong_alias(s_p_hashtbl_merge, slurm_s_p_hashtbl_merge); |
| strong_alias(s_p_get_string, slurm_s_p_get_string); |
| strong_alias(s_p_get_long, slurm_s_p_get_long); |
| strong_alias(s_p_get_uint16, slurm_s_p_get_uint16); |
| strong_alias(s_p_get_uint32, slurm_s_p_get_uint32); |
| strong_alias(s_p_get_pointer, slurm_s_p_get_pointer); |
| strong_alias(s_p_get_array, slurm_s_p_get_array); |
| strong_alias(s_p_get_boolean, slurm_s_p_get_boolean); |
| strong_alias(s_p_dump_values, slurm_s_p_dump_values); |
| strong_alias(transfer_s_p_options, slurm_transfer_s_p_options); |
| |
| #define BUFFER_SIZE 4096 |
| |
| #define CONF_HASH_LEN 173 |
| |
| static regex_t keyvalue_re; |
| static char *keyvalue_pattern = |
| "^[[:space:]]*" |
| "([[:alnum:]]+)" /* key */ |
| "[[:space:]]*=[[:space:]]*" |
| "((\"([^\"]*)\")|([^[:space:]]+))" /* value: quoted with whitespace, |
| * or unquoted and no whitespace */ |
| "([[:space:]]|$)"; |
| static bool keyvalue_initialized = false; |
| |
| struct s_p_values { |
| char *key; |
| int type; |
| int data_count; |
| void *data; |
| int (*handler)(void **data, slurm_parser_enum_t type, |
| const char *key, const char *value, |
| const char *line, char **leftover); |
| void (*destroy)(void *data); |
| s_p_values_t *next; |
| }; |
| |
| /* |
| * NOTE - "key" is case insensitive. |
| */ |
| static int _conf_hashtbl_index(const char *key) |
| { |
| unsigned int hashval; |
| |
| xassert(key); |
| for (hashval = 0; *key != 0; key++) |
| hashval = tolower(*key) + 31 * hashval; |
| return hashval % CONF_HASH_LEN; |
| } |
| |
| static void _conf_hashtbl_insert(s_p_hashtbl_t *hashtbl, |
| s_p_values_t *value) |
| { |
| int idx; |
| |
| xassert(value); |
| idx = _conf_hashtbl_index(value->key); |
| value->next = hashtbl[idx]; |
| hashtbl[idx] = value; |
| } |
| |
| /* |
| * NOTE - "key" is case insensitive. |
| */ |
| static s_p_values_t *_conf_hashtbl_lookup( |
| const s_p_hashtbl_t *hashtbl, const char *key) |
| { |
| int idx; |
| s_p_values_t *p; |
| |
| xassert(key); |
| if (hashtbl == NULL) |
| return NULL; |
| |
| idx = _conf_hashtbl_index(key); |
| for (p = hashtbl[idx]; p != NULL; p = p->next) { |
| if (strcasecmp(p->key, key) == 0) |
| return p; |
| } |
| return NULL; |
| } |
| |
| s_p_hashtbl_t *s_p_hashtbl_create(s_p_options_t options[]) |
| { |
| s_p_options_t *op = NULL; |
| s_p_values_t *value = NULL; |
| s_p_hashtbl_t *hashtbl = NULL; |
| int len; |
| |
| len = CONF_HASH_LEN * sizeof(s_p_values_t *); |
| hashtbl = (s_p_hashtbl_t *)xmalloc(len); |
| |
| for (op = options; op->key != NULL; op++) { |
| value = xmalloc(sizeof(s_p_values_t)); |
| value->key = xstrdup(op->key); |
| value->type = op->type; |
| value->data_count = 0; |
| value->data = NULL; |
| value->next = NULL; |
| value->handler = op->handler; |
| value->destroy = op->destroy; |
| _conf_hashtbl_insert(hashtbl, value); |
| } |
| |
| return hashtbl; |
| } |
| |
| /* Swap the data in two data structures without changing the linked list |
| * pointers */ |
| static void _conf_hashtbl_swap_data(s_p_values_t *data_1, |
| s_p_values_t *data_2) |
| { |
| s_p_values_t *next_1, *next_2; |
| s_p_values_t tmp_values; |
| |
| next_1 = data_1->next; |
| next_2 = data_2->next; |
| |
| memcpy(&tmp_values, data_1, sizeof(s_p_values_t)); |
| memcpy(data_1, data_2, sizeof(s_p_values_t)); |
| memcpy(data_2, &tmp_values, sizeof(s_p_values_t)); |
| |
| data_1->next = next_1; |
| data_2->next = next_2; |
| } |
| |
| static void _conf_file_values_free(s_p_values_t *p) |
| { |
| int i; |
| |
| if (p->data_count > 0) { |
| switch(p->type) { |
| case S_P_ARRAY: |
| for (i = 0; i < p->data_count; i++) { |
| void **ptr_array = (void **)p->data; |
| if (p->destroy != NULL) { |
| p->destroy(ptr_array[i]); |
| } else { |
| xfree(ptr_array[i]); |
| } |
| } |
| xfree(p->data); |
| break; |
| default: |
| if (p->destroy != NULL) { |
| p->destroy(p->data); |
| } else { |
| xfree(p->data); |
| } |
| break; |
| } |
| } |
| xfree(p->key); |
| xfree(p); |
| } |
| |
| void s_p_hashtbl_destroy(s_p_hashtbl_t *hashtbl) { |
| int i; |
| s_p_values_t *p, *next; |
| |
| if (!hashtbl) |
| return; |
| |
| for (i = 0; i < CONF_HASH_LEN; i++) { |
| for (p = hashtbl[i]; p != NULL; p = next) { |
| next = p->next; |
| _conf_file_values_free(p); |
| } |
| } |
| xfree(hashtbl); |
| } |
| |
| static void _keyvalue_regex_init(void) |
| { |
| if (!keyvalue_initialized) { |
| if (regcomp(&keyvalue_re, keyvalue_pattern, |
| REG_EXTENDED) != 0) { |
| /* FIXME - should be fatal? */ |
| error("keyvalue regex compilation failed"); |
| } |
| keyvalue_initialized = true; |
| } |
| } |
| |
| /* |
| * IN line - string to be search for a key=value pair |
| * OUT key - pointer to the key string (caller must free with xfree()) |
| * OUT value - pointer to the value string (caller must free with xfree()) |
| * OUT remaining - pointer into the "line" string denoting the start |
| * of the unsearched portion of the string |
| * Return 0 when a key-value pair is found, and -1 otherwise. |
| */ |
| static int _keyvalue_regex(const char *line, |
| char **key, char **value, char **remaining) |
| { |
| size_t nmatch = 8; |
| regmatch_t pmatch[8]; |
| |
| *key = NULL; |
| *value = NULL; |
| *remaining = (char *)line; |
| memset(pmatch, 0, sizeof(regmatch_t)*nmatch); |
| |
| if (regexec(&keyvalue_re, line, nmatch, pmatch, 0) |
| == REG_NOMATCH) { |
| return -1; |
| } |
| |
| *key = (char *)(xstrndup(line + pmatch[1].rm_so, |
| pmatch[1].rm_eo - pmatch[1].rm_so)); |
| |
| if (pmatch[4].rm_so != -1) { |
| *value = (char *)(xstrndup(line + pmatch[4].rm_so, |
| pmatch[4].rm_eo - pmatch[4].rm_so)); |
| } else if (pmatch[5].rm_so != -1) { |
| *value = (char *)(xstrndup(line + pmatch[5].rm_so, |
| pmatch[5].rm_eo - pmatch[5].rm_so)); |
| } else { |
| *value = xstrdup(""); |
| } |
| |
| *remaining = (char *)(line + pmatch[2].rm_eo); |
| |
| return 0; |
| } |
| |
| static int _strip_continuation(char *buf, int len) |
| { |
| char *ptr; |
| int bs = 0; |
| |
| if (len == 0) |
| return len; /* Empty line */ |
| |
| for (ptr = buf+len-1; ptr >= buf; ptr--) { |
| if (*ptr == '\\') |
| bs++; |
| else if (isspace((int)*ptr) && (bs == 0)) |
| continue; |
| else |
| break; |
| } |
| /* Check for an odd number of contiguous backslashes at |
| * the end of the line */ |
| if ((bs % 2) == 1) { |
| ptr = ptr + bs; |
| *ptr = '\0'; |
| return (ptr - buf); |
| } else { |
| return len; /* no continuation */ |
| } |
| } |
| |
| /* |
| * Strip out trailing carriage returns and newlines |
| */ |
| static void _strip_cr_nl(char *line) |
| { |
| int len = strlen(line); |
| char *ptr; |
| |
| for (ptr = line+len-1; ptr >= line; ptr--) { |
| if (*ptr=='\r' || *ptr=='\n') { |
| *ptr = '\0'; |
| } else { |
| return; |
| } |
| } |
| } |
| |
| /* Strip comments from a line by terminating the string |
| * where the comment begins. |
| * Everything after a non-escaped "#" is a comment. |
| */ |
| static void _strip_comments(char *line) |
| { |
| int i; |
| int len = strlen(line); |
| int bs_count = 0; |
| |
| for (i = 0; i < len; i++) { |
| /* if # character is preceded by an even number of |
| * escape characters '\' */ |
| if (line[i] == '#' && (bs_count%2) == 0) { |
| line[i] = '\0'; |
| break; |
| } else if (line[i] == '\\') { |
| bs_count++; |
| } else { |
| bs_count = 0; |
| } |
| } |
| } |
| |
| /* |
| * Strips any escape characters, "\". If you WANT a back-slash, |
| * it must be escaped, "\\". |
| */ |
| static void _strip_escapes(char *line) |
| { |
| int i, j; |
| int len = strlen(line); |
| |
| for (i = 0, j = 0; i < len+1; i++, j++) { |
| if (line[i] == '\\') |
| i++; |
| line[j] = line[i]; |
| } |
| } |
| |
| /* This can be used to make sure files are the same across nodes if |
| * needed */ |
| static void _compute_hash_val(uint32_t *hash_val, char *line) |
| { |
| int idx, i, len; |
| |
| if (!hash_val) |
| return; |
| |
| len = strlen(line); |
| for (i = 0; i < len; i++) { |
| (*hash_val) = ( (*hash_val) ^ line[i] << 8 ); |
| |
| for (idx = 0; idx < 8; ++idx) { |
| if ((*hash_val) & 0x8000) { |
| (*hash_val) <<= 1; |
| (*hash_val) = (*hash_val) ^ 4129; |
| } else |
| (*hash_val) <<= 1; |
| } |
| } |
| } |
| |
| |
| /* |
| * Reads the next line from the "file" into buffer "buf". |
| * |
| * Concatonates together lines that are continued on |
| * the next line by a trailing "\". Strips out comments, |
| * replaces escaped "\#" with "#", and replaces "\\" with "\". |
| */ |
| static int _get_next_line(char *buf, int buf_size, |
| uint32_t *hash_val, FILE *file) |
| { |
| char *ptr = buf; |
| int leftover = buf_size; |
| int read_size, new_size; |
| int lines = 0; |
| |
| while (fgets(ptr, leftover, file)) { |
| lines++; |
| _compute_hash_val(hash_val, ptr); |
| _strip_comments(ptr); |
| read_size = strlen(ptr); |
| new_size = _strip_continuation(ptr, read_size); |
| if (new_size < read_size) { |
| ptr += new_size; |
| leftover -= new_size; |
| } else { /* no continuation */ |
| break; |
| } |
| } |
| /* _strip_cr_nl(buf); */ /* not necessary */ |
| _strip_escapes(buf); |
| |
| return lines; |
| } |
| |
| static int _handle_string(s_p_values_t *v, |
| const char *value, const char *line, char **leftover) |
| { |
| if (v->data_count != 0) { |
| error("%s specified more than once, latest value used", |
| v->key); |
| xfree(v->data); |
| v->data_count = 0; |
| } |
| |
| if (v->handler != NULL) { |
| /* call the handler function */ |
| int rc; |
| rc = v->handler(&v->data, v->type, v->key, value, |
| line, leftover); |
| if (rc != 1) |
| return rc == 0 ? 0 : -1; |
| } else { |
| v->data = xstrdup(value); |
| } |
| |
| v->data_count = 1; |
| return 1; |
| } |
| |
| static int _handle_long(s_p_values_t *v, |
| const char *value, const char *line, char **leftover) |
| { |
| if (v->data_count != 0) { |
| error("%s specified more than once, latest value used", |
| v->key); |
| xfree(v->data); |
| v->data_count = 0; |
| } |
| |
| if (v->handler != NULL) { |
| /* call the handler function */ |
| int rc; |
| rc = v->handler(&v->data, v->type, v->key, value, |
| line, leftover); |
| if (rc != 1) |
| return rc == 0 ? 0 : -1; |
| } else { |
| char *endptr; |
| long num; |
| errno = 0; |
| num = strtol(value, &endptr, 0); |
| if ((num == 0 && errno == EINVAL) |
| || (*endptr != '\0')) { |
| if (strcasecmp(value, "UNLIMITED") == 0 |
| || strcasecmp(value, "INFINITE") == 0) { |
| num = (long) INFINITE; |
| } else { |
| error("\"%s\" is not a valid number", value); |
| return -1; |
| } |
| } else if (errno == ERANGE) { |
| error("\"%s\" is out of range", value); |
| return -1; |
| } |
| v->data = xmalloc(sizeof(long)); |
| *(long *)v->data = num; |
| } |
| |
| v->data_count = 1; |
| return 1; |
| } |
| |
| static int _handle_uint16(s_p_values_t *v, |
| const char *value, const char *line, char **leftover) |
| { |
| if (v->data_count != 0) { |
| error("%s specified more than once, latest value used", |
| v->key); |
| xfree(v->data); |
| v->data_count = 0; |
| } |
| |
| if (v->handler != NULL) { |
| /* call the handler function */ |
| int rc; |
| rc = v->handler(&v->data, v->type, v->key, value, |
| line, leftover); |
| if (rc != 1) |
| return rc == 0 ? 0 : -1; |
| } else { |
| char *endptr; |
| unsigned long num; |
| |
| errno = 0; |
| num = strtoul(value, &endptr, 0); |
| if ((num == 0 && errno == EINVAL) |
| || (*endptr != '\0')) { |
| if (strcasecmp(value, "UNLIMITED") == 0 |
| || strcasecmp(value, "INFINITE") == 0) { |
| num = (uint16_t) INFINITE; |
| } else { |
| error("%s value \"%s\" is not a valid number", |
| v->key, value); |
| return -1; |
| } |
| } else if (errno == ERANGE) { |
| error("%s value (%s) is out of range", v->key, value); |
| return -1; |
| } else if (value[0] == '-') { |
| error("%s value (%s) is less than zero", v->key, |
| value); |
| return -1; |
| } else if (num > 0xffff) { |
| error("%s value (%s) is greater than 65535", v->key, |
| value); |
| return -1; |
| } |
| v->data = xmalloc(sizeof(uint16_t)); |
| *(uint16_t *)v->data = (uint16_t)num; |
| } |
| |
| v->data_count = 1; |
| return 1; |
| } |
| |
| static int _handle_uint32(s_p_values_t *v, |
| const char *value, const char *line, char **leftover) |
| { |
| if (v->data_count != 0) { |
| error("%s specified more than once, latest value used", |
| v->key); |
| xfree(v->data); |
| v->data_count = 0; |
| } |
| |
| if (v->handler != NULL) { |
| /* call the handler function */ |
| int rc; |
| rc = v->handler(&v->data, v->type, v->key, value, |
| line, leftover); |
| if (rc != 1) |
| return rc == 0 ? 0 : -1; |
| } else { |
| char *endptr; |
| unsigned long num; |
| |
| errno = 0; |
| num = strtoul(value, &endptr, 0); |
| if ((endptr[0] == 'k') || (endptr[0] == 'K')) { |
| num *= 1024; |
| endptr++; |
| } |
| if ((num == 0 && errno == EINVAL) |
| || (*endptr != '\0')) { |
| if ((strcasecmp(value, "UNLIMITED") == 0) || |
| (strcasecmp(value, "INFINITE") == 0)) { |
| num = (uint32_t) INFINITE; |
| } else { |
| error("%s value (%s) is not a valid number", |
| v->key, value); |
| return -1; |
| } |
| } else if (errno == ERANGE) { |
| error("%s value (%s) is out of range", v->key, value); |
| return -1; |
| } else if (value[0] == '-') { |
| error("%s value (%s) is less than zero", v->key, |
| value); |
| return -1; |
| } else if (num > 0xffffffff) { |
| error("%s value (%s) is greater than 4294967295", |
| v->key, value); |
| return -1; |
| } |
| v->data = xmalloc(sizeof(uint32_t)); |
| *(uint32_t *)v->data = (uint32_t)num; |
| } |
| |
| v->data_count = 1; |
| return 1; |
| } |
| |
| static int _handle_pointer(s_p_values_t *v, |
| const char *value, const char *line, |
| char **leftover) |
| { |
| if (v->handler != NULL) { |
| /* call the handler function */ |
| int rc; |
| rc = v->handler(&v->data, v->type, v->key, value, |
| line, leftover); |
| if (rc != 1) |
| return rc == 0 ? 0 : -1; |
| } else { |
| if (v->data_count != 0) { |
| error("%s specified more than once, " |
| "latest value used", v->key); |
| xfree(v->data); |
| v->data_count = 0; |
| } |
| v->data = xstrdup(value); |
| } |
| |
| v->data_count = 1; |
| return 1; |
| } |
| |
| static int _handle_array(s_p_values_t *v, |
| const char *value, const char *line, char **leftover) |
| { |
| void *new_ptr; |
| void **data; |
| |
| if (v->handler != NULL) { |
| /* call the handler function */ |
| int rc; |
| rc = v->handler(&new_ptr, v->type, v->key, value, |
| line, leftover); |
| if (rc != 1) |
| return rc == 0 ? 0 : -1; |
| } else { |
| new_ptr = xstrdup(value); |
| } |
| v->data_count += 1; |
| v->data = xrealloc(v->data, (v->data_count)*sizeof(void *)); |
| data = &((void**)v->data)[v->data_count-1]; |
| *data = new_ptr; |
| |
| return 1; |
| } |
| |
| static int _handle_boolean(s_p_values_t *v, |
| const char *value, const char *line, |
| char **leftover) |
| { |
| if (v->data_count != 0) { |
| error("%s specified more than once, latest value used", |
| v->key); |
| xfree(v->data); |
| v->data_count = 0; |
| } |
| |
| if (v->handler != NULL) { |
| /* call the handler function */ |
| int rc; |
| rc = v->handler(&v->data, v->type, v->key, value, |
| line, leftover); |
| if (rc != 1) |
| return rc == 0 ? 0 : -1; |
| } else { |
| bool flag; |
| |
| if (!strcasecmp(value, "yes") |
| || !strcasecmp(value, "up") |
| || !strcasecmp(value, "1")) { |
| flag = true; |
| } else if (!strcasecmp(value, "no") |
| || !strcasecmp(value, "down") |
| || !strcasecmp(value, "0")) { |
| flag = false; |
| } else { |
| error("\"%s\" is not a valid option for \"%s\"", |
| value, v->key); |
| return -1; |
| } |
| |
| v->data = xmalloc(sizeof(bool)); |
| *(bool *)v->data = flag; |
| } |
| |
| v->data_count = 1; |
| return 1; |
| } |
| |
| |
| /* |
| * IN line: the entire line that currently being parsed |
| * IN/OUT leftover: leftover is a pointer into the "line" string. |
| * The incoming leftover point is a pointer to the |
| * character just after the already parsed key/value pair. |
| * If the handler for that key parses more of the line, |
| * it will move the leftover pointer to point to the character |
| * after it has finished parsing in the line. |
| */ |
| static void _handle_keyvalue_match(s_p_values_t *v, |
| const char *value, const char *line, |
| char **leftover) |
| { |
| /* debug3("key = %s, value = %s, line = \"%s\"", */ |
| /* v->key, value, line); */ |
| switch (v->type) { |
| case S_P_IGNORE: |
| /* do nothing */ |
| break; |
| case S_P_STRING: |
| _handle_string(v, value, line, leftover); |
| break; |
| case S_P_LONG: |
| _handle_long(v, value, line, leftover); |
| break; |
| case S_P_UINT16: |
| _handle_uint16(v, value, line, leftover); |
| break; |
| case S_P_UINT32: |
| _handle_uint32(v, value, line, leftover); |
| break; |
| case S_P_POINTER: |
| _handle_pointer(v, value, line, leftover); |
| break; |
| case S_P_ARRAY: |
| _handle_array(v, value, line, leftover); |
| break; |
| case S_P_BOOLEAN: |
| _handle_boolean(v, value, line, leftover); |
| break; |
| } |
| } |
| |
| /* |
| * Return 1 if all characters in "line" are white-space characters, |
| * otherwise return 0. |
| */ |
| static int _line_is_space(const char *line) |
| { |
| int len; |
| int i; |
| |
| if (line == NULL) { |
| return 1; |
| } |
| len = strlen(line); |
| for (i = 0; i < len; i++) { |
| if (!isspace((int)line[i])) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| |
| /* |
| * Returns 1 if the line is parsed cleanly, and 0 otherwise. |
| */ |
| int s_p_parse_line(s_p_hashtbl_t *hashtbl, const char *line, char **leftover) |
| { |
| char *key, *value; |
| char *ptr = (char *)line; |
| s_p_values_t *p; |
| char *new_leftover; |
| |
| _keyvalue_regex_init(); |
| |
| while (_keyvalue_regex(ptr, &key, &value, &new_leftover) == 0) { |
| if ((p = _conf_hashtbl_lookup(hashtbl, key))) { |
| _handle_keyvalue_match(p, value, |
| new_leftover, &new_leftover); |
| *leftover = ptr = new_leftover; |
| } else { |
| error("Parsing error at unrecognized key: %s", key); |
| xfree(key); |
| xfree(value); |
| return 0; |
| } |
| xfree(key); |
| xfree(value); |
| } |
| |
| return 1; |
| } |
| |
| /* |
| * Returns 1 if the line is parsed cleanly, and 0 otherwise. |
| * IN ingore_new - if set do not treat unrecongized input as a fatal error |
| */ |
| static int _parse_next_key(s_p_hashtbl_t *hashtbl, |
| const char *line, char **leftover, bool ignore_new) |
| { |
| char *key, *value; |
| s_p_values_t *p; |
| char *new_leftover; |
| |
| _keyvalue_regex_init(); |
| |
| if (_keyvalue_regex(line, &key, &value, &new_leftover) == 0) { |
| if ((p = _conf_hashtbl_lookup(hashtbl, key))) { |
| _handle_keyvalue_match(p, value, |
| new_leftover, &new_leftover); |
| *leftover = new_leftover; |
| } else if (ignore_new) { |
| debug("Parsing error at unrecognized key: %s", key); |
| *leftover = (char *)line; |
| } else { |
| error("Parsing error at unrecognized key: %s", key); |
| xfree(key); |
| xfree(value); |
| *leftover = (char *)line; |
| return 0; |
| } |
| xfree(key); |
| xfree(value); |
| } else { |
| *leftover = (char *)line; |
| } |
| |
| return 1; |
| } |
| |
| /* |
| * Returns 1 if the line contained an include directive and the included |
| * file was parsed without error. Returns -1 if the line was an include |
| * directive but the included file contained errors. Returns 0 if |
| * no include directive is found. |
| */ |
| static int _parse_include_directive(s_p_hashtbl_t *hashtbl, uint32_t *hash_val, |
| const char *line, char **leftover, |
| bool ignore_new) |
| { |
| char *ptr; |
| char *fn_start, *fn_stop; |
| char *filename; |
| |
| *leftover = NULL; |
| if (strncasecmp("include", line, strlen("include")) == 0) { |
| ptr = (char *)line + strlen("include"); |
| if (!isspace((int)*ptr)) |
| return 0; |
| while (isspace((int)*ptr)) |
| ptr++; |
| fn_start = ptr; |
| while (!isspace((int)*ptr)) |
| ptr++; |
| fn_stop = *leftover = ptr; |
| filename = xstrndup(fn_start, fn_stop-fn_start); |
| if (s_p_parse_file(hashtbl, hash_val, filename, ignore_new) |
| == SLURM_SUCCESS) { |
| xfree(filename); |
| return 1; |
| } else { |
| xfree(filename); |
| return -1; |
| } |
| } else { |
| return 0; |
| } |
| } |
| |
| int s_p_parse_file(s_p_hashtbl_t *hashtbl, uint32_t *hash_val, char *filename, |
| bool ignore_new) |
| { |
| FILE *f; |
| char *leftover = NULL; |
| int i, rc = SLURM_SUCCESS; |
| int line_number; |
| int merged_lines; |
| int inc_rc; |
| struct stat stat_buf; |
| char *line = NULL; |
| |
| if (!filename) { |
| error("s_p_parse_file: No filename given."); |
| return SLURM_ERROR; |
| } |
| |
| _keyvalue_regex_init(); |
| for (i = 0; ; i++) { |
| if (i == 1) { /* Long once, on first retry */ |
| error("s_p_parse_file: unable to status file %s: %m, " |
| "retrying in 1sec upto 60sec", |
| filename); |
| } |
| if (i >= 60) /* Give up after 60 seconds */ |
| return SLURM_ERROR; |
| if (i > 0) |
| sleep(1); |
| if (stat(filename, &stat_buf) >= 0) |
| break; |
| } |
| if (stat_buf.st_size == 0) { |
| info("s_p_parse_file: file \"%s\" is empty", filename); |
| return SLURM_SUCCESS; |
| } |
| f = fopen(filename, "r"); |
| if (f == NULL) { |
| error("s_p_parse_file: unable to read \"%s\": %m", |
| filename); |
| return SLURM_ERROR; |
| } |
| |
| /* Buffer needs one extra byte for trailing '\0' */ |
| line = xmalloc(sizeof(char) * stat_buf.st_size + 1); |
| line_number = 1; |
| while ((merged_lines = _get_next_line( |
| line, stat_buf.st_size + 1, hash_val, f)) > 0) { |
| /* skip empty lines */ |
| if (line[0] == '\0') { |
| line_number += merged_lines; |
| continue; |
| } |
| |
| inc_rc = _parse_include_directive(hashtbl, hash_val, |
| line, &leftover, ignore_new); |
| if (inc_rc == 0) { |
| _parse_next_key(hashtbl, line, &leftover, ignore_new); |
| } else if (inc_rc < 0) { |
| error("\"Include\" failed in file %s line %d", |
| filename, line_number); |
| rc = SLURM_ERROR; |
| line_number += merged_lines; |
| continue; |
| } |
| |
| /* Make sure that after parsing only whitespace is left over */ |
| if (!_line_is_space(leftover)) { |
| char *ptr = xstrdup(leftover); |
| _strip_cr_nl(ptr); |
| if (ignore_new) { |
| debug("Parse error in file %s line %d: \"%s\"", |
| filename, line_number, ptr); |
| } else { |
| error("Parse error in file %s line %d: \"%s\"", |
| filename, line_number, ptr); |
| rc = SLURM_ERROR; |
| } |
| xfree(ptr); |
| } |
| line_number += merged_lines; |
| } |
| |
| xfree(line); |
| fclose(f); |
| return rc; |
| } |
| |
| /* |
| * s_p_hashtbl_merge |
| * |
| * Merge the contents of two s_p_hashtbl_t data structures. Anything in |
| * from_hashtbl that does not also appear in to_hashtbl is transfered to it. |
| * This is intended primary to support multiple lines of DEFAULT configuration |
| * information and preserve the default values while adding new defaults. |
| * |
| * IN from_hashtbl - Source of old data |
| * IN to_hashtbl - Destination for old data |
| */ |
| void s_p_hashtbl_merge(s_p_hashtbl_t *to_hashtbl, s_p_hashtbl_t *from_hashtbl) |
| { |
| int i; |
| s_p_values_t **val_pptr, *val_ptr, *match_ptr; |
| |
| if (!to_hashtbl || !from_hashtbl) |
| return; |
| |
| for (i = 0; i < CONF_HASH_LEN; i++) { |
| val_pptr = &from_hashtbl[i]; |
| val_ptr = from_hashtbl[i]; |
| while (val_ptr) { |
| if (val_ptr->data_count == 0) { |
| /* No data in from_hashtbl record to move. |
| * Skip record */ |
| val_pptr = &val_ptr->next; |
| val_ptr = val_ptr->next; |
| continue; |
| } |
| match_ptr = _conf_hashtbl_lookup(to_hashtbl, |
| val_ptr->key); |
| if (match_ptr) { /* Found matching key */ |
| if (match_ptr->data_count == 0) { |
| _conf_hashtbl_swap_data(val_ptr, |
| match_ptr); |
| } |
| val_pptr = &val_ptr->next; |
| val_ptr = val_ptr->next; |
| } else { /* No match, move record */ |
| *val_pptr = val_ptr->next; |
| val_ptr->next = NULL; |
| _conf_hashtbl_insert(to_hashtbl, val_ptr); |
| val_ptr = *val_pptr; |
| } |
| } |
| } |
| } |
| |
| /* |
| * Returns 1 if the line is parsed cleanly, and 0 otherwise. |
| */ |
| int s_p_parse_pair(s_p_hashtbl_t *hashtbl, const char *key, const char *value) |
| { |
| s_p_values_t *p; |
| char *leftover, *v; |
| |
| if ((p = _conf_hashtbl_lookup(hashtbl, key)) == NULL) { |
| error("Parsing error at unrecognized key: %s", key); |
| return 0; |
| } |
| /* we have value separated from key here so parse it different way */ |
| while (*value != '\0' && isspace(*value)) |
| value++; /* skip spaces at start if any */ |
| if (*value == '"') { /* quoted value */ |
| v = (char *)value + 1; |
| leftover = strchr(v, '"'); |
| if (leftover == NULL) { |
| error("Parse error in data for key %s: %s", key, value); |
| return 0; |
| } |
| } else { /* unqouted value */ |
| leftover = v = (char *)value; |
| while (*leftover != '\0' && !isspace(*leftover)) |
| leftover++; |
| } |
| value = xstrndup(v, leftover - v); |
| if (*leftover != '\0') |
| leftover++; |
| while (*leftover != '\0' && isspace(*leftover)) |
| leftover++; /* skip trailing spaces */ |
| _handle_keyvalue_match(p, value, leftover, &leftover); |
| xfree(value); |
| |
| return 1; |
| } |
| |
| /* |
| * s_p_get_string |
| * |
| * Search for a key in a s_p_hashtbl_t with value of type |
| * string. If the key is found and has a set value, the |
| * value is retuned in "str". |
| * |
| * OUT str - pointer to a copy of the string value |
| * (caller is resonsible for freeing str with xfree()) |
| * IN key - hash table key. |
| * IN hashtbl - hash table created by s_p_hashtbl_create() |
| * |
| * Returns 1 when a value was set for "key" during parsing and "str" |
| * was successfully set, otherwise returns 0; |
| * |
| * NOTE: Caller is responsible for freeing the returned string with xfree! |
| */ |
| int s_p_get_string(char **str, const char *key, const s_p_hashtbl_t *hashtbl) |
| { |
| s_p_values_t *p; |
| |
| if (!hashtbl) |
| return 0; |
| p = _conf_hashtbl_lookup(hashtbl, key); |
| if (p == NULL) { |
| error("Invalid key \"%s\"", key); |
| return 0; |
| } |
| if (p->type != S_P_STRING) { |
| error("Key \"%s\" is not a string", key); |
| return 0; |
| } |
| if (p->data_count == 0) { |
| return 0; |
| } |
| |
| *str = xstrdup((char *)p->data); |
| |
| return 1; |
| } |
| |
| /* |
| * s_p_get_long |
| * |
| * Search for a key in a s_p_hashtbl_t with value of type |
| * long. If the key is found and has a set value, the |
| * value is retuned in "num". |
| * |
| * OUT num - pointer to a long where the value is returned |
| * IN key - hash table key |
| * IN hashtbl - hash table created by s_p_hashtbl_create() |
| * |
| * Returns 1 when a value was set for "key" during parsing and "num" |
| * was successfully set, otherwise returns 0; |
| */ |
| int s_p_get_long(long *num, const char *key, const s_p_hashtbl_t *hashtbl) |
| { |
| s_p_values_t *p; |
| |
| if (!hashtbl) |
| return 0; |
| p = _conf_hashtbl_lookup(hashtbl, key); |
| if (p == NULL) { |
| error("Invalid key \"%s\"", key); |
| return 0; |
| } |
| if (p->type != S_P_LONG) { |
| error("Key \"%s\" is not a long", key); |
| return 0; |
| } |
| if (p->data_count == 0) { |
| return 0; |
| } |
| |
| *num = *(long *)p->data; |
| |
| return 1; |
| } |
| |
| /* |
| * s_p_get_uint16 |
| * |
| * Search for a key in a s_p_hashtbl_t with value of type |
| * uint16. If the key is found and has a set value, the |
| * value is retuned in "num". |
| * |
| * OUT num - pointer to a uint16_t where the value is returned |
| * IN key - hash table key |
| * IN hashtbl - hash table created by s_p_hashtbl_create() |
| * |
| * Returns 1 when a value was set for "key" during parsing and "num" |
| * was successfully set, otherwise returns 0; |
| */ |
| int s_p_get_uint16(uint16_t *num, const char *key, |
| const s_p_hashtbl_t *hashtbl) |
| { |
| s_p_values_t *p; |
| |
| if (!hashtbl) |
| return 0; |
| p = _conf_hashtbl_lookup(hashtbl, key); |
| if (p == NULL) { |
| error("Invalid key \"%s\"", key); |
| return 0; |
| } |
| if (p->type != S_P_UINT16) { |
| error("Key \"%s\" is not a uint16_t", key); |
| return 0; |
| } |
| if (p->data_count == 0) { |
| return 0; |
| } |
| |
| *num = *(uint16_t *)p->data; |
| |
| return 1; |
| } |
| |
| /* |
| * s_p_get_uint32 |
| * |
| * Search for a key in a s_p_hashtbl_t with value of type |
| * uint32. If the key is found and has a set value, the |
| * value is retuned in "num". |
| * |
| * OUT num - pointer to a uint32_t where the value is returned |
| * IN key - hash table key |
| * IN hashtbl - hash table created by s_p_hashtbl_create() |
| * |
| * Returns 1 when a value was set for "key" during parsing and "num" |
| * was successfully set, otherwise returns 0; |
| */ |
| int s_p_get_uint32(uint32_t *num, const char *key, |
| const s_p_hashtbl_t *hashtbl) |
| { |
| s_p_values_t *p; |
| |
| if (!hashtbl) |
| return 0; |
| p = _conf_hashtbl_lookup(hashtbl, key); |
| if (p == NULL) { |
| error("Invalid key \"%s\"", key); |
| return 0; |
| } |
| if (p->type != S_P_UINT32) { |
| error("Key \"%s\" is not a uint32_t", key); |
| return 0; |
| } |
| if (p->data_count == 0) { |
| return 0; |
| } |
| |
| *num = *(uint32_t *)p->data; |
| |
| return 1; |
| } |
| |
| /* |
| * s_p_get_pointer |
| * |
| * Search for a key in a s_p_hashtbl_t with value of type |
| * pointer. If the key is found and has a set value, the |
| * value is retuned in "ptr". |
| * |
| * OUT ptr - pointer to a void pointer where the value is returned |
| * IN key - hash table key |
| * IN hashtbl - hash table created by s_p_hashtbl_create() |
| * |
| * Returns 1 when a value was set for "key" during parsing and "ptr" |
| * was successfully set, otherwise returns 0; |
| */ |
| int s_p_get_pointer(void **ptr, const char *key, const s_p_hashtbl_t *hashtbl) |
| { |
| s_p_values_t *p; |
| |
| if (!hashtbl) |
| return 0; |
| p = _conf_hashtbl_lookup(hashtbl, key); |
| if (p == NULL) { |
| error("Invalid key \"%s\"", key); |
| return 0; |
| } |
| if (p->type != S_P_POINTER) { |
| error("Key \"%s\" is not a pointer", key); |
| return 0; |
| } |
| if (p->data_count == 0) { |
| return 0; |
| } |
| |
| *ptr = p->data; |
| |
| return 1; |
| } |
| |
| |
| /* |
| * s_p_get_array |
| * |
| * Most s_p_ data types allow a key to appear only once in a file |
| * (s_p_parse_file) or line (s_p_parse_line). S_P_ARRAY is the exception. |
| * |
| * S_P_ARRAY allows a key to appear any number of times. Each time |
| * a particular key is found the value array grows by one element, and |
| * that element contains a pointer to the newly parsed value. You can |
| * think of this as being an array of S_P_POINTER types. |
| * |
| * OUT ptr_array - pointer to void pointer-pointer where the value is returned |
| * OUT count - length of ptr_array |
| * IN key - hash table key |
| * IN hashtbl - hash table created by s_p_hashtbl_create() |
| * |
| * Returns 1 when a value was set for "key" during parsing and both |
| * "ptr_array" and "count" were successfully set, otherwise returns 0. |
| */ |
| int s_p_get_array(void **ptr_array[], int *count, |
| const char *key, const s_p_hashtbl_t *hashtbl) |
| { |
| s_p_values_t *p; |
| |
| if (!hashtbl) |
| return 0; |
| p = _conf_hashtbl_lookup(hashtbl, key); |
| if (p == NULL) { |
| error("Invalid key \"%s\"", key); |
| return 0; |
| } |
| if (p->type != S_P_ARRAY) { |
| error("Key \"%s\" is not an array", key); |
| return 0; |
| } |
| if (p->data_count == 0) { |
| return 0; |
| } |
| |
| *ptr_array = (void **)p->data; |
| *count = p->data_count; |
| |
| return 1; |
| } |
| |
| /* |
| * s_p_get_boolean |
| * |
| * Search for a key in a s_p_hashtbl_t with value of type |
| * boolean. If the key is found and has a set value, the |
| * value is retuned in "flag". |
| * |
| * OUT flag - pointer to a bool where the value is returned |
| * IN key - hash table key |
| * IN hashtbl - hash table created by s_p_hashtbl_create() |
| * |
| * Returns 1 when a value was set for "key" during parsing and "num" |
| * was successfully set, otherwise returns 0; |
| */ |
| int s_p_get_boolean(bool *flag, const char *key, const s_p_hashtbl_t *hashtbl) |
| { |
| s_p_values_t *p; |
| |
| if (!hashtbl) |
| return 0; |
| p = _conf_hashtbl_lookup(hashtbl, key); |
| if (p == NULL) { |
| error("Invalid key \"%s\"", key); |
| return 0; |
| } |
| if (p->type != S_P_BOOLEAN) { |
| error("Key \"%s\" is not a boolean", key); |
| return 0; |
| } |
| if (p->data_count == 0) { |
| return 0; |
| } |
| |
| *flag = *(bool *)p->data; |
| |
| return 1; |
| } |
| |
| |
| /* |
| * Given an "options" array, print the current values of all |
| * options in supplied hash table "hashtbl". |
| * |
| * Primarily for debugging purposes. |
| */ |
| void s_p_dump_values(const s_p_hashtbl_t *hashtbl, |
| const s_p_options_t options[]) |
| { |
| const s_p_options_t *op = NULL; |
| long num; |
| uint16_t num16; |
| uint32_t num32; |
| char *str; |
| void *ptr; |
| void **ptr_array; |
| int count; |
| bool flag; |
| |
| for (op = options; op->key != NULL; op++) { |
| switch(op->type) { |
| case S_P_STRING: |
| if (s_p_get_string(&str, op->key, hashtbl)) { |
| verbose("%s = %s", op->key, str); |
| xfree(str); |
| } else { |
| verbose("%s", op->key); |
| } |
| break; |
| case S_P_LONG: |
| if (s_p_get_long(&num, op->key, hashtbl)) |
| verbose("%s = %ld", op->key, num); |
| else |
| verbose("%s", op->key); |
| break; |
| case S_P_UINT16: |
| if (s_p_get_uint16(&num16, op->key, hashtbl)) |
| verbose("%s = %hu", op->key, num16); |
| else |
| verbose("%s", op->key); |
| break; |
| case S_P_UINT32: |
| if (s_p_get_uint32(&num32, op->key, hashtbl)) |
| verbose("%s = %u", op->key, num32); |
| else |
| verbose("%s", op->key); |
| break; |
| case S_P_POINTER: |
| if (s_p_get_pointer(&ptr, op->key, hashtbl)) |
| verbose("%s = %zx", op->key, (size_t)ptr); |
| else |
| verbose("%s", op->key); |
| break; |
| case S_P_ARRAY: |
| if (s_p_get_array(&ptr_array, &count, |
| op->key, hashtbl)) { |
| verbose("%s, count = %d", op->key, count); |
| } else { |
| verbose("%s", op->key); |
| } |
| break; |
| case S_P_BOOLEAN: |
| if (s_p_get_boolean(&flag, op->key, hashtbl)) { |
| verbose("%s = %s", op->key, |
| flag ? "TRUE" : "FALSE"); |
| } else { |
| verbose("%s", op->key); |
| } |
| break; |
| case S_P_IGNORE: |
| break; |
| } |
| } |
| } |
| |
| extern void transfer_s_p_options(s_p_options_t **full_options, |
| s_p_options_t *options, |
| int *full_options_cnt) |
| { |
| s_p_options_t *op = NULL; |
| s_p_options_t *full_options_ptr; |
| int cnt = *full_options_cnt; |
| |
| xassert(full_options); |
| |
| for (op = options; op->key != NULL; op++, cnt++) { |
| xrealloc(*full_options, ((cnt + 1) * sizeof(s_p_options_t))); |
| full_options_ptr = &(*full_options)[cnt]; |
| memcpy(full_options_ptr, op, sizeof(s_p_options_t)); |
| full_options_ptr->key = xstrdup(op->key); |
| } |
| *full_options_cnt = cnt; |
| } |