| /*****************************************************************************\ | |
| * data.c - generic data_t structures | |
| ***************************************************************************** | |
| * Copyright (C) SchedMD LLC. | |
| * | |
| * This file is part of Slurm, a resource management program. | |
| * For details, see <https://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. | |
| \*****************************************************************************/ | |
| #define _ISOC99_SOURCE /* needed for llrint */ | |
| #include <ctype.h> | |
| #include <errno.h> | |
| #include <math.h> | |
| #include <stdlib.h> | |
| #include "slurm/slurm.h" | |
| #include "src/common/data.h" | |
| #include "src/common/http.h" | |
| #include "src/common/list.h" | |
| #include "src/common/log.h" | |
| #include "src/common/macros.h" | |
| #include "src/common/read_config.h" | |
| #include "src/common/xassert.h" | |
| #include "src/common/xmalloc.h" | |
| #include "src/common/xstring.h" | |
| strong_alias(_data_set_string_own, slurm__data_set_string_own); | |
| strong_alias(data_convert_tree, slurm_data_convert_tree); | |
| strong_alias(data_convert_type, slurm_data_convert_type); | |
| strong_alias(data_dict_for_each_const, slurm_data_dict_for_each_const); | |
| strong_alias(data_free, slurm_data_free); | |
| strong_alias(data_get_bool, slurm_data_get_bool); | |
| strong_alias(data_get_dict_length, slurm_data_get_dict_length); | |
| strong_alias(data_get_float, slurm_data_get_float); | |
| strong_alias(data_get_int, slurm_data_get_int); | |
| strong_alias(data_get_int_converted, slurm_data_get_int_converted); | |
| strong_alias(data_get_list_length, slurm_data_get_list_length); | |
| strong_alias(data_get_string, slurm_data_get_string); | |
| strong_alias(data_get_string_converted, slurm_data_get_string_converted); | |
| strong_alias(data_get_type, slurm_data_get_type); | |
| strong_alias(data_key_get, slurm_data_key_get); | |
| strong_alias(data_key_get_const, slurm_data_key_get_const); | |
| strong_alias(data_key_set, slurm_data_key_set); | |
| strong_alias(data_list_append, slurm_data_list_append); | |
| strong_alias(data_list_for_each, slurm_data_list_for_each); | |
| strong_alias(data_list_for_each_const, slurm_data_list_for_each_const); | |
| strong_alias(data_list_get_at, slurm_data_list_get_at); | |
| strong_alias(data_list_insert_at, slurm_data_list_insert_at); | |
| strong_alias(data_list_remove_at, slurm_data_list_remove_at); | |
| strong_alias(data_move, slurm_data_move); | |
| strong_alias(data_new, slurm_data_new); | |
| strong_alias(data_set_bool, slurm_data_set_bool); | |
| strong_alias(data_set_dict, slurm_data_set_dict); | |
| strong_alias(data_set_float, slurm_data_set_float); | |
| strong_alias(data_set_int, slurm_data_set_int); | |
| strong_alias(data_set_list, slurm_data_set_list); | |
| strong_alias(data_set_null, slurm_data_set_null); | |
| strong_alias(data_set_string, slurm_data_set_string); | |
| strong_alias(data_type_to_string, slurm_data_type_to_string); | |
| #define DATA_DEFINE_DICT_PATH_BUFFER_SIZE 1024 | |
| #define DATA_MAGIC 0x1992189F | |
| #define DATA_LIST_MAGIC 0x1992F89F | |
| #define DATA_LIST_NODE_MAGIC 0x1921F89F | |
| /* max chars PRId64 could printf(). strlen("-9223372036854775808") = 20 */ | |
| #define INT64_CHAR_MAX 20 | |
| typedef struct data_list_s data_list_t; | |
| typedef struct data_list_node_s data_list_node_t; | |
| typedef enum { | |
| TYPE_NONE = 0, /* invalid or unknown type */ | |
| /* only for bounds checks to avoid any overlap with DATA_TYPE_* */ | |
| TYPE_START = 0xFF00, | |
| TYPE_NULL, /* ECMA-262:4.3.13 NULL type */ | |
| TYPE_LIST, /* ECMA-262:22.1 Array Object (ordered list) */ | |
| TYPE_DICT, /* ECMA-262:23.1 Map Object (dictionary) */ | |
| TYPE_INT_64, /* 64bit signed integer | |
| This exists as an convenient storage type. | |
| ECMA does not have an integer primitive. | |
| ECMA-262:7.1.4 ToInteger() returns approx | |
| this value with some rounding. */ | |
| TYPE_STRING_PTR, /* ECMA-262:4.3.18 String type */ | |
| TYPE_STRING_INLINE, /* string stored in union directly */ | |
| TYPE_FLOAT, /* ECMA-262:6.1.6 Number type */ | |
| TYPE_BOOL, /* ECMA-262:4.3.15 Boolean type */ | |
| TYPE_MAX /* only for bounds checking */ | |
| } type_t; | |
| static const struct { | |
| data_type_t external_type; | |
| type_t internal_type; | |
| } type_map[] = { | |
| { DATA_TYPE_NULL, TYPE_NULL }, | |
| { DATA_TYPE_LIST, TYPE_LIST }, | |
| { DATA_TYPE_DICT, TYPE_DICT }, | |
| { DATA_TYPE_INT_64, TYPE_INT_64 }, | |
| { DATA_TYPE_STRING, TYPE_STRING_PTR }, | |
| { DATA_TYPE_STRING, TYPE_STRING_INLINE }, | |
| { DATA_TYPE_FLOAT, TYPE_FLOAT }, | |
| { DATA_TYPE_BOOL, TYPE_BOOL }, | |
| }; | |
| typedef struct data_list_node_s { | |
| int magic; | |
| data_list_node_t *next; | |
| data_t *data; | |
| char *key; /* key for dictionary (only) */ | |
| } data_list_node_t; | |
| /* Single linked list for list_u and dict_u */ | |
| typedef struct data_list_s { | |
| int magic; | |
| size_t count; | |
| data_list_node_t *begin; | |
| data_list_node_t *end; | |
| } data_list_t; | |
| /* | |
| * Data is based on the JSON data type and has the same types. | |
| * Data forms a tree structure. | |
| * Please avoid direct access of this struct and only use access functions. | |
| * The nature of this struct may change at any time, only pass around pointers | |
| * created from data_new(). | |
| */ | |
| struct data_s { | |
| int magic; | |
| type_t type; | |
| union { /* append "_u" to every type to avoid reserved words */ | |
| data_list_t *list_u; | |
| data_list_t *dict_u; | |
| int64_t int_u; | |
| char string_inline_u[sizeof(void *)]; | |
| char *string_ptr_u; | |
| double float_u; | |
| bool bool_u; | |
| } data; | |
| }; | |
| typedef struct { | |
| char *path; | |
| char *at; | |
| const char *token; | |
| } merge_path_strings_t; | |
| typedef struct { | |
| const data_t *a; | |
| const data_t *b; | |
| bool exact; | |
| } find_dict_match_t; | |
| typedef struct { | |
| size_t count; | |
| data_type_t match; | |
| } convert_args_t; | |
| #define CONVERT_DATA_FOREACH_LIST_DICT_ARGS_MAGIC 0x139414ab | |
| typedef struct { | |
| int magic; /* CONVERT_DATA_FOREACH_LIST_DICT_ARGS_MAGIC */ | |
| data_t *src; | |
| int64_t index; | |
| } convert_data_foreach_list_dict_args_t; | |
| #define PARSE_DICT_PATH_ARGS_MAGIC 0x8aa834af | |
| typedef struct { | |
| int magic; /* PARSE_DICT_PATH_ARGS_MAGIC */ | |
| data_t *found; | |
| } dict_path_args_t; | |
| #define PARSE_DICT_PATH_CONST_ARGS_MAGIC 0x802834af | |
| typedef struct { | |
| int magic; /* PARSE_DICT_PATH_CONST_ARGS_MAGIC */ | |
| const data_t *found; | |
| } dict_path_const_args_t; | |
| #define DEFINE_DICT_PATH_ARGS_MAGIC 0x02bbafff | |
| typedef struct { | |
| int magic; /* DEFINE_DICT_PATH_ARGS_MAGIC */ | |
| data_t *found; | |
| } dict_path_define_args_t; | |
| static void _check_magic(const data_t *data); | |
| static void _release(data_t *data); | |
| static void _release_data_list_node(data_list_t *dl, data_list_node_t *dn); | |
| static size_t _convert_tree(data_t *data, const data_type_t match); | |
| static char *_type_to_string(type_t type); | |
| static data_list_t *_data_list_new(void) | |
| { | |
| data_list_t *dl = xmalloc(sizeof(*dl)); | |
| dl->magic = DATA_LIST_MAGIC; | |
| log_flag(DATA, "%s: new data-list(0x%"PRIxPTR")[%zu]", | |
| __func__, (uintptr_t) dl, dl->count); | |
| return dl; | |
| } | |
| static void _check_data_list_node_magic(const data_list_node_t *dn) | |
| { | |
| xassert(dn); | |
| xassert(dn->magic == DATA_LIST_NODE_MAGIC); | |
| /* make sure not linking to self */ | |
| xassert(dn->next != dn); | |
| } | |
| static void _check_data_list_magic(const data_list_t *dl) | |
| { | |
| #ifndef NDEBUG | |
| xassert(dl); | |
| xassert(dl->magic == DATA_LIST_MAGIC); | |
| if (slurm_conf.debug_flags & DEBUG_FLAG_DATA) { | |
| data_list_node_t *end = NULL; | |
| if (dl->begin) { | |
| /* walk forwards verify */ | |
| int c = 0; | |
| data_list_node_t *i = dl->begin; | |
| while (i) { | |
| c++; | |
| _check_data_list_node_magic(i); | |
| end = i; | |
| i = i->next; | |
| } | |
| xassert(c == dl->count); | |
| } | |
| xassert(end == dl->end); | |
| } | |
| #endif /* !NDEBUG */ | |
| } | |
| /* verify node is in parent list */ | |
| static void _check_data_list_node_parent(const data_list_t *dl, | |
| const data_list_node_t *dn) | |
| { | |
| #ifndef NDEBUG | |
| if (slurm_conf.debug_flags & DEBUG_FLAG_DATA) { | |
| data_list_node_t *i = dl->begin; | |
| while (i) { | |
| if (i == dn) | |
| return; | |
| i = i->next; | |
| } | |
| /* found an orphan? */ | |
| fatal_abort("%s: unexpected orphan node", __func__); | |
| } | |
| #endif /* !NDEBUG */ | |
| } | |
| static void _release_data_list_node(data_list_t *dl, data_list_node_t *dn) | |
| { | |
| data_list_node_t *prev = NULL; | |
| _check_data_list_magic(dl); | |
| _check_data_list_node_magic(dn); | |
| _check_data_list_node_parent(dl, dn); | |
| log_flag(DATA, "%s: free data-list(0x%"PRIxPTR")[%zu]", | |
| __func__, (uintptr_t) dl, dl->count); | |
| /* walk list to find new previous */ | |
| for (prev = ((dn == dl->begin) ? NULL : dl->begin); | |
| prev && (prev->next != dn);) { | |
| _check_data_list_node_magic(prev); | |
| prev = prev->next; | |
| if (prev) | |
| _check_data_list_node_magic(prev); | |
| } | |
| if (dn == dl->begin) { | |
| /* at the beginning */ | |
| xassert(!prev); | |
| dl->begin = dn->next; | |
| if (dl->end == dn) { | |
| dl->end = NULL; | |
| xassert(!dn->next); | |
| } | |
| } else if (dn == dl->end) { | |
| /* at the end */ | |
| xassert(!dn->next); | |
| xassert(prev); | |
| dl->end = prev; | |
| prev->next = NULL; | |
| } else { | |
| /* somewhere in middle */ | |
| xassert(prev); | |
| xassert(prev != dn); | |
| xassert(prev->next == dn); | |
| xassert(dl->begin != dn); | |
| xassert(dl->end != dn); | |
| prev->next = dn->next; | |
| } | |
| dl->count--; | |
| FREE_NULL_DATA(dn->data); | |
| xfree(dn->key); | |
| dn->magic = ~DATA_LIST_NODE_MAGIC; | |
| xfree(dn); | |
| } | |
| static void _release_data_list(data_list_t *dl) | |
| { | |
| data_list_node_t *n = dl->begin, *i; | |
| #ifndef NDEBUG | |
| int count = 0; | |
| const int init_count = dl->count; | |
| #endif | |
| _check_data_list_magic(dl); | |
| if (!n) { | |
| xassert(!dl->count); | |
| xassert(!dl->end); | |
| goto finish; | |
| } | |
| xassert(dl->end); | |
| while((i = n)) { | |
| n = i->next; | |
| _release_data_list_node(dl, i); | |
| #ifndef NDEBUG | |
| count++; | |
| #endif | |
| } | |
| #ifndef NDEBUG | |
| xassert(count == init_count); | |
| #endif | |
| finish: | |
| dl->magic = ~DATA_LIST_MAGIC; | |
| xfree(dl); | |
| } | |
| /* | |
| * Create new data list node entry | |
| * IN d - data type to take ownership of | |
| * IN key - dictionary key to dup or NULL | |
| */ | |
| static data_list_node_t *_new_data_list_node(data_t *d, const char *key) | |
| { | |
| data_list_node_t *dn = xmalloc(sizeof(*dn)); | |
| dn->magic = DATA_LIST_NODE_MAGIC; | |
| _check_magic(d); | |
| dn->data = d; | |
| if (key) { | |
| dn->key = xstrdup(key); | |
| log_flag(DATA, "%s: new dictionary entry data-list-node(0x%"PRIxPTR")[%s]=%pD", | |
| __func__, (uintptr_t) dn, dn->key, dn->data); | |
| } else { | |
| log_flag(DATA, "%s: new list entry data-list-node(0x%"PRIxPTR")=%pD", | |
| __func__, (uintptr_t) dn, dn->data); | |
| } | |
| return dn; | |
| } | |
| static void _data_list_append(data_list_t *dl, data_t *d, const char *key) | |
| { | |
| data_list_node_t *n = _new_data_list_node(d, key); | |
| _check_data_list_magic(dl); | |
| _check_magic(d); | |
| if (dl->end) { | |
| xassert(!dl->end->next); | |
| _check_data_list_node_magic(dl->end); | |
| _check_data_list_node_magic(dl->begin); | |
| dl->end->next = n; | |
| dl->end = n; | |
| } else { | |
| xassert(!dl->count); | |
| dl->end = n; | |
| dl->begin = n; | |
| } | |
| dl->count++; | |
| if (n->key) | |
| log_flag(DATA, "%s: append dictionary entry data-list-node(0x%"PRIxPTR")[%s]=%pD", | |
| __func__, (uintptr_t) n, n->key, n->data); | |
| else | |
| log_flag(DATA, "%s: append list entry data-list-node(0x%"PRIxPTR")=%pD", | |
| __func__, (uintptr_t) n, n->data); | |
| } | |
| static void _data_list_prepend(data_list_t *dl, data_t *d, const char *key) | |
| { | |
| data_list_node_t *n = _new_data_list_node(d, key); | |
| _check_data_list_magic(dl); | |
| _check_magic(d); | |
| if (dl->begin) { | |
| _check_data_list_node_magic(dl->begin); | |
| n->next = dl->begin; | |
| dl->begin = n; | |
| } else { | |
| xassert(!dl->count); | |
| dl->begin = n; | |
| dl->end = n; | |
| } | |
| dl->count++; | |
| log_flag(DATA, "%s: prepend %pD[%s]->data-list-node(0x%"PRIxPTR")[%s]=%pD", | |
| __func__, d, key, (uintptr_t) n, n->key, n->data); | |
| } | |
| extern data_t *data_new(void) | |
| { | |
| data_t *data = xmalloc(sizeof(*data)); | |
| data->magic = DATA_MAGIC; | |
| data->type = TYPE_NULL; | |
| log_flag(DATA, "%s: new %pD", __func__, data); | |
| return data; | |
| } | |
| static void _check_magic(const data_t *data) | |
| { | |
| if (!data) | |
| return; | |
| xassert(data->magic == DATA_MAGIC); | |
| if (slurm_conf.debug_flags & DEBUG_FLAG_DATA) { | |
| xassert(data->type > TYPE_START); | |
| xassert(data->type < TYPE_MAX); | |
| if (data->type == TYPE_LIST) | |
| _check_data_list_magic(data->data.list_u); | |
| if (data->type == TYPE_DICT) | |
| _check_data_list_magic(data->data.dict_u); | |
| } | |
| } | |
| static void _release(data_t *data) | |
| { | |
| _check_magic(data); | |
| switch (data->type) { | |
| case TYPE_LIST: | |
| _release_data_list(data->data.list_u); | |
| break; | |
| case TYPE_DICT: | |
| _release_data_list(data->data.dict_u); | |
| break; | |
| case TYPE_STRING_PTR: | |
| xfree(data->data.string_ptr_u); | |
| break; | |
| default: | |
| /* other types don't need to be freed */ | |
| break; | |
| } | |
| data->type = TYPE_NONE; | |
| } | |
| extern void data_free(data_t *data) | |
| { | |
| if (!data) | |
| return; | |
| log_flag(DATA, "%s: free %pD", __func__, data); | |
| _check_magic(data); | |
| _release(data); | |
| data->magic = ~DATA_MAGIC; | |
| data->type = TYPE_NONE; | |
| xfree(data); | |
| } | |
| extern data_type_t data_get_type(const data_t *data) | |
| { | |
| if (!data) | |
| return DATA_TYPE_NONE; | |
| _check_magic(data); | |
| for (int i = 0; i < ARRAY_SIZE(type_map); i++) | |
| if (type_map[i].internal_type == data->type) | |
| return type_map[i].external_type; | |
| return DATA_TYPE_NONE; | |
| } | |
| extern data_t *data_set_float(data_t *data, double value) | |
| { | |
| _check_magic(data); | |
| if (!data) | |
| return NULL; | |
| _release(data); | |
| data->type = TYPE_FLOAT; | |
| data->data.float_u = value; | |
| log_flag(DATA, "%s: set %pD=%e", __func__, data, value); | |
| return data; | |
| } | |
| extern data_t *data_set_null(data_t *data) | |
| { | |
| _check_magic(data); | |
| if (!data) | |
| return NULL; | |
| _release(data); | |
| data->type = TYPE_NULL; | |
| log_flag(DATA, "%s: set %pD=null", __func__, data); | |
| return data; | |
| } | |
| extern data_t *data_set_bool(data_t *data, bool value) | |
| { | |
| _check_magic(data); | |
| if (!data) | |
| return NULL; | |
| _release(data); | |
| data->type = TYPE_BOOL; | |
| data->data.bool_u = value; | |
| log_flag(DATA, "%s: set %pD=%s", | |
| __func__, data, (value ? "true" : "false")); | |
| return data; | |
| } | |
| extern data_t *data_set_int(data_t *data, int64_t value) | |
| { | |
| _check_magic(data); | |
| if (!data) | |
| return NULL; | |
| _release(data); | |
| data->type = TYPE_INT_64; | |
| data->data.int_u = value; | |
| log_flag(DATA, "%s: set %pD=%"PRId64, __func__, data, value); | |
| return data; | |
| } | |
| static void _set_data_string_ptr(data_t *data, const size_t len, | |
| char **value_ptr) | |
| { | |
| data->type = TYPE_STRING_PTR; | |
| data->data.string_ptr_u = *value_ptr; | |
| *value_ptr = NULL; | |
| log_flag_hex(DATA, data->data.string_ptr_u, len, "%s: set string %pD", | |
| __func__, data); | |
| } | |
| static void _set_data_string_inline(data_t *data, const size_t len, | |
| const char *value) | |
| { | |
| memmove(data->data.string_inline_u, value, len + 1); | |
| data->type = TYPE_STRING_INLINE; | |
| log_flag_hex(DATA, data->data.string_inline_u, len, | |
| "%s: set inline string %pD", __func__, data); | |
| } | |
| extern data_t *data_set_string(data_t *data, const char *value) | |
| { | |
| int len; | |
| _check_magic(data); | |
| if (!data) | |
| return NULL; | |
| _release(data); | |
| if (!value) { | |
| data->type = TYPE_NULL; | |
| log_flag(DATA, "%s: set %pD=null", __func__, data); | |
| return data; | |
| } | |
| if ((len = strlen(value)) < sizeof(data->data.string_inline_u)) { | |
| _set_data_string_inline(data, len, value); | |
| } else { | |
| char *dval = xstrdup(value); | |
| _set_data_string_ptr(data, len, &dval); | |
| } | |
| return data; | |
| } | |
| extern data_t *_data_set_string_own(data_t *data, char **value_ptr) | |
| { | |
| char *value; | |
| int len; | |
| _check_magic(data); | |
| if (!data) { | |
| xfree(*value_ptr); | |
| return NULL; | |
| } | |
| _release(data); | |
| value = *value_ptr; | |
| *value_ptr = NULL; | |
| if (!value) { | |
| data->type = TYPE_NULL; | |
| log_flag(DATA, "%s: set %pD=null", __func__, data); | |
| return data; | |
| } | |
| #ifndef NDEBUG | |
| if (slurm_conf.debug_flags & DEBUG_FLAG_DATA) { | |
| char *old_value = value; | |
| /* check that the string was xmalloc()ed and actually has contents */ | |
| xassert(xsize(value) > 0); | |
| /* | |
| * catch use after free by the caller by using the existing xfree() | |
| * functionality | |
| */ | |
| value = xstrdup(value); | |
| /* releasing original string instead of NULLing original pointer */ | |
| xfree(old_value); | |
| } | |
| #endif | |
| if ((len = strlen(value)) < sizeof(data->data.string_inline_u)) { | |
| _set_data_string_inline(data, len, value); | |
| /* we don't need to keep this string alloc */ | |
| xfree(value); | |
| } else { | |
| _set_data_string_ptr(data, len, &value); | |
| } | |
| xassert(!value); | |
| return data; | |
| } | |
| extern data_t *data_set_dict(data_t *data) | |
| { | |
| _check_magic(data); | |
| if (!data) | |
| return NULL; | |
| _release(data); | |
| data->type = TYPE_DICT; | |
| data->data.dict_u = _data_list_new(); | |
| log_flag(DATA, "%s: set %pD to dictionary", __func__, data); | |
| return data; | |
| } | |
| extern data_t *data_set_list(data_t *data) | |
| { | |
| _check_magic(data); | |
| if (!data) | |
| return NULL; | |
| _release(data); | |
| data->type = TYPE_LIST; | |
| data->data.list_u = _data_list_new(); | |
| log_flag(DATA, "%s: set %pD to list", __func__, data); | |
| return data; | |
| } | |
| extern data_t *data_list_append(data_t *data) | |
| { | |
| data_t *ndata = NULL; | |
| _check_magic(data); | |
| xassert(data && (data->type == TYPE_LIST)); | |
| if (!data || data->type != TYPE_LIST) | |
| return NULL; | |
| ndata = data_new(); | |
| _data_list_append(data->data.list_u, ndata, NULL); | |
| log_flag(DATA, "%s: appended %pD[%zu]=%pD", | |
| __func__, data, data->data.list_u->count, ndata); | |
| return ndata; | |
| } | |
| extern data_t *data_list_prepend(data_t *data) | |
| { | |
| data_t *ndata = NULL; | |
| _check_magic(data); | |
| if (!data || data->type != TYPE_LIST) | |
| return NULL; | |
| ndata = data_new(); | |
| _data_list_prepend(data->data.list_u, ndata, NULL); | |
| log_flag(DATA, "%s: prepended %pD[%zu]=%pD", | |
| __func__, data, data->data.list_u->count, ndata); | |
| return ndata; | |
| } | |
| extern data_t *data_list_dequeue(data_t *data) | |
| { | |
| data_list_node_t *n; | |
| data_t *ret = NULL; | |
| _check_magic(data); | |
| if (!data || data->type != TYPE_LIST) | |
| return NULL; | |
| if (!(n = data->data.list_u->begin)) | |
| return NULL; | |
| _check_data_list_node_magic(n); | |
| /* extract out data for caller */ | |
| SWAP(ret, n->data); | |
| /* remove node from list */ | |
| _release_data_list_node(data->data.list_u, n); | |
| log_flag(DATA, "%s: dequeued %pD[%zu]=%pD", | |
| __func__, data, data->data.list_u->count, ret); | |
| return ret; | |
| } | |
| /* | |
| * Walk to the node at index. | |
| * RET node at index or NULL if index is out of range | |
| */ | |
| static data_list_node_t *_data_list_node_at(const data_list_t *dl, size_t index) | |
| { | |
| data_list_node_t *n = dl->begin; | |
| _check_data_list_magic(dl); | |
| if (index >= dl->count) | |
| return NULL; | |
| for (size_t i = 0; i < index; i++) { | |
| _check_data_list_node_magic(n); | |
| n = n->next; | |
| } | |
| _check_data_list_node_magic(n); | |
| return n; | |
| } | |
| extern data_t *data_list_get_at(data_t *data, size_t index) | |
| { | |
| data_list_node_t *n = NULL; | |
| _check_magic(data); | |
| xassert(data && (data->type == TYPE_LIST)); | |
| if (!data || (data->type != TYPE_LIST)) | |
| return NULL; | |
| if (!(n = _data_list_node_at(data->data.list_u, index))) | |
| return NULL; | |
| return n->data; | |
| } | |
| extern const data_t *data_list_get_at_const(const data_t *data, size_t index) | |
| { | |
| const data_list_node_t *n = NULL; | |
| _check_magic(data); | |
| xassert(data && (data->type == TYPE_LIST)); | |
| if (!data || (data->type != TYPE_LIST)) | |
| return NULL; | |
| if (!(n = _data_list_node_at(data->data.list_u, index))) | |
| return NULL; | |
| return n->data; | |
| } | |
| extern data_t *data_list_insert_at(data_t *data, size_t index) | |
| { | |
| data_list_t *dl = NULL; | |
| data_list_node_t *n = NULL, *prev = NULL; | |
| data_t *ndata = NULL; | |
| _check_magic(data); | |
| xassert(data && (data->type == TYPE_LIST)); | |
| if (!data || (data->type != TYPE_LIST)) | |
| return NULL; | |
| dl = data->data.list_u; | |
| _check_data_list_magic(dl); | |
| /* inserting at the length appends, anything past it is out of range */ | |
| if (index > dl->count) { | |
| error("%s: index %zu is past the end of %pD[%zu]", | |
| __func__, index, data, dl->count); | |
| return NULL; | |
| } | |
| if (index == dl->count) | |
| return data_list_append(data); | |
| if (!index) { | |
| ndata = data_list_prepend(data); | |
| log_flag(DATA, "%s: inserted %pD[%zu]=%pD", | |
| __func__, data, index, ndata); | |
| return ndata; | |
| } | |
| /* insert after the node before index, which always exists here */ | |
| prev = _data_list_node_at(dl, index - 1); | |
| xassert(prev); | |
| ndata = data_new(); | |
| n = _new_data_list_node(ndata, NULL); | |
| n->next = prev->next; | |
| prev->next = n; | |
| dl->count++; | |
| log_flag(DATA, "%s: inserted %pD[%zu]=%pD", | |
| __func__, data, index, ndata); | |
| return ndata; | |
| } | |
| extern data_t *data_list_remove_at(data_t *data, size_t index) | |
| { | |
| data_list_node_t *n = NULL; | |
| data_t *ret = NULL; | |
| _check_magic(data); | |
| xassert(data && (data->type == TYPE_LIST)); | |
| if (!data || (data->type != TYPE_LIST)) | |
| return NULL; | |
| if (!(n = _data_list_node_at(data->data.list_u, index))) | |
| return NULL; | |
| /* extract out data for caller before the node is released */ | |
| SWAP(ret, n->data); | |
| _release_data_list_node(data->data.list_u, n); | |
| log_flag(DATA, "%s: removed %pD[%zu]=%pD", | |
| __func__, data, index, ret); | |
| return ret; | |
| } | |
| static data_for_each_cmd_t _data_list_join(const data_t *src, void *arg) | |
| { | |
| data_t *dst = (data_t *) arg; | |
| data_t *dst_entry; | |
| _check_magic(src); | |
| _check_magic(dst); | |
| xassert(dst->type == TYPE_LIST); | |
| log_flag(DATA, "%s: list join data %pD to %pD", __func__, src, dst); | |
| dst_entry = data_list_append(dst); | |
| data_copy(dst_entry, src); | |
| log_flag(DATA, "%s: list join %pD to %pD[%zu]=%pD", | |
| __func__, src, dst, dst->data.list_u->count, dst_entry); | |
| return DATA_FOR_EACH_CONT; | |
| } | |
| extern data_t *data_list_join(const data_t **data, bool flatten_lists) | |
| { | |
| data_t *dst = data_set_list(data_new()); | |
| for (size_t i = 0; data[i]; i++) { | |
| log_flag(DATA, "%s: %s list join %pD to %pD[%zu]", | |
| __func__, (flatten_lists ? "flattened" : ""), | |
| data[i], dst, dst->data.list_u->count); | |
| if (flatten_lists && (data[i]->type == TYPE_LIST)) | |
| (void) data_list_for_each_const(data[i], | |
| _data_list_join, dst); | |
| else /* simple join */ | |
| _data_list_join(data[i], dst); | |
| } | |
| return dst; | |
| } | |
| extern const data_t *data_key_get_const(const data_t *data, const char *key) | |
| { | |
| const data_list_node_t *i; | |
| _check_magic(data); | |
| if (!data) | |
| return NULL; | |
| xassert(data->type == TYPE_DICT); | |
| if (!key || data->type != TYPE_DICT) | |
| return NULL; | |
| /* don't bother searching empty dictionary */ | |
| if (!data->data.dict_u->count) | |
| return NULL; | |
| _check_data_list_magic(data->data.dict_u); | |
| i = data->data.dict_u->begin; | |
| while (i) { | |
| _check_data_list_node_magic(i); | |
| if (!xstrcmp(key, i->key)) | |
| break; | |
| i = i->next; | |
| } | |
| if (i) | |
| return i->data; | |
| else | |
| return NULL; | |
| } | |
| static bool _match_string(const char *key, data_t *data, void *needle_ptr) | |
| { | |
| const char *needle = needle_ptr; | |
| return !xstrcmp(key, needle); | |
| } | |
| extern data_t *data_key_get(data_t *data, const char *key) | |
| { | |
| return data_dict_find_first(data, _match_string, (void *) key); | |
| } | |
| extern data_t *data_key_get_int(data_t *data, int64_t key) | |
| { | |
| char key_str[INT64_CHAR_MAX + 1]; | |
| (void) snprintf(key_str, sizeof(key_str), "%"PRId64, key); | |
| return data_key_get(data, key_str); | |
| } | |
| extern data_t *data_list_find_first( | |
| data_t *data, | |
| bool (*match)(const data_t *data, void *needle), | |
| void *needle) | |
| { | |
| data_list_node_t *i; | |
| _check_magic(data); | |
| if (!data) | |
| return NULL; | |
| xassert(data->type == TYPE_LIST); | |
| if (data->type != TYPE_LIST) | |
| return NULL; | |
| /* don't bother searching empty list */ | |
| if (!data->data.list_u->count) | |
| return NULL; | |
| _check_data_list_magic(data->data.list_u); | |
| i = data->data.list_u->begin; | |
| while (i) { | |
| _check_data_list_node_magic(i); | |
| if (match(i->data, needle)) | |
| break; | |
| i = i->next; | |
| } | |
| if (i) | |
| return i->data; | |
| else | |
| return NULL; | |
| } | |
| extern data_t *data_dict_find_first( | |
| data_t *data, | |
| bool (*match)(const char *key, data_t *data, void *needle), | |
| void *needle) | |
| { | |
| data_list_node_t *i; | |
| _check_magic(data); | |
| if (!data) | |
| return NULL; | |
| xassert(data->type == TYPE_DICT); | |
| if (data->type != TYPE_DICT) | |
| return NULL; | |
| /* don't bother searching empty dictionary */ | |
| if (!data->data.dict_u->count) | |
| return NULL; | |
| _check_data_list_magic(data->data.dict_u); | |
| i = data->data.dict_u->begin; | |
| while (i) { | |
| _check_data_list_node_magic(i); | |
| if (match(i->key, i->data, needle)) | |
| break; | |
| i = i->next; | |
| } | |
| if (i) | |
| return i->data; | |
| else | |
| return NULL; | |
| } | |
| extern data_t *data_key_set(data_t *data, const char *key) | |
| { | |
| data_t *d; | |
| _check_magic(data); | |
| if (!data) | |
| return NULL; | |
| xassert(data->type == TYPE_DICT); | |
| if (!key || (data->type != TYPE_DICT)) | |
| return NULL; | |
| if ((d = data_key_get(data, key))) { | |
| log_flag(DATA, "%s: overwrite existing key in %pD[%s]=%pD", | |
| __func__, data, key, d); | |
| return d; | |
| } | |
| d = data_new(); | |
| _data_list_append(data->data.dict_u, d, key); | |
| log_flag(DATA, "%s: populate new key in %pD[%s]=%pD", | |
| __func__, data, key, d); | |
| return d; | |
| } | |
| extern data_t *data_key_set_int(data_t *data, int64_t key) | |
| { | |
| char key_str[INT64_CHAR_MAX + 1]; | |
| (void) snprintf(key_str, sizeof(key_str), "%"PRId64, key); | |
| return data_key_set(data, key_str); | |
| } | |
| extern bool data_key_unset(data_t *data, const char *key) | |
| { | |
| data_list_node_t *i; | |
| _check_magic(data); | |
| if (!data) | |
| return false; | |
| xassert(data->type == TYPE_DICT); | |
| if (!key || data->type != TYPE_DICT) | |
| return NULL; | |
| _check_data_list_magic(data->data.dict_u); | |
| i = data->data.dict_u->begin; | |
| while (i) { | |
| _check_data_list_node_magic(i); | |
| if (!xstrcmp(key, i->key)) | |
| break; | |
| i = i->next; | |
| } | |
| if (!i) { | |
| log_flag(DATA, "%s: remove non-existent key in %pD[%s]", | |
| __func__, data, key); | |
| return false; | |
| } | |
| log_flag(DATA, "%s: remove existing key in %pD[%s]=data-list-node(0x%"PRIxPTR")[%s]=%pD", | |
| __func__, data, key, (uintptr_t) i, i->key, i->data); | |
| _release_data_list_node(data->data.dict_u, i); | |
| return true; | |
| } | |
| extern double data_get_float(const data_t *data) | |
| { | |
| _check_magic(data); | |
| if (!data) | |
| return NAN; | |
| xassert(data->type == TYPE_FLOAT); | |
| return data->data.float_u; | |
| } | |
| extern bool data_get_bool(const data_t *data) | |
| { | |
| _check_magic(data); | |
| if (!data) | |
| return false; | |
| xassert(data->type == TYPE_BOOL); | |
| return data->data.bool_u; | |
| } | |
| extern int64_t data_get_int(const data_t *data) | |
| { | |
| _check_magic(data); | |
| if (!data) | |
| return 0; | |
| if (data->type == TYPE_NULL) | |
| return 0; | |
| xassert(data->type == TYPE_INT_64); | |
| return data->data.int_u; | |
| } | |
| extern const char *data_get_string(const data_t *data) | |
| { | |
| _check_magic(data); | |
| if (!data) | |
| return NULL; | |
| xassert((data->type == TYPE_STRING_PTR) || | |
| (data->type == TYPE_STRING_INLINE) || | |
| (data->type == TYPE_NULL)); | |
| if (data->type == TYPE_STRING_PTR) { | |
| return data->data.string_ptr_u; | |
| } else if (data->type == TYPE_STRING_INLINE) { | |
| return data->data.string_inline_u; | |
| } else { | |
| return NULL; | |
| } | |
| } | |
| extern int data_get_string_converted(const data_t *d, char **buffer) | |
| { | |
| _check_magic(d); | |
| char *_buffer = NULL; | |
| bool cloned; | |
| if (!d || !buffer) | |
| return ESLURM_DATA_PTR_NULL; | |
| if ((d->type != TYPE_STRING_PTR) && (d->type != TYPE_STRING_INLINE)) { | |
| /* copy the data and then convert it to a string type */ | |
| data_t *dclone = data_new(); | |
| data_copy(dclone, d); | |
| if (data_convert_type(dclone, DATA_TYPE_STRING) == | |
| DATA_TYPE_STRING) | |
| _buffer = xstrdup(data_get_string(dclone)); | |
| FREE_NULL_DATA(dclone); | |
| cloned = true; | |
| } else { | |
| _buffer = xstrdup(data_get_string(d)); | |
| if (!_buffer) | |
| _buffer = xstrdup(""); | |
| cloned = false; | |
| } | |
| if (_buffer) { | |
| *buffer = _buffer; | |
| log_flag_hex(DATA, _buffer, strlen(_buffer), | |
| "%s: string %sat %pD=string@0x%"PRIxPTR"[%zu]", | |
| __func__, (cloned ? "conversion and cloned " : ""), | |
| d, (uintptr_t) _buffer, strlen(_buffer)); | |
| return SLURM_SUCCESS; | |
| } | |
| log_flag(DATA, "%s: %pD string conversion failed", __func__, d); | |
| return ESLURM_DATA_CONV_FAILED; | |
| } | |
| extern int data_copy_bool_converted(const data_t *d, bool *buffer) | |
| { | |
| _check_magic(d); | |
| int rc = ESLURM_DATA_CONV_FAILED; | |
| if (!d || !buffer) | |
| return ESLURM_DATA_PTR_NULL; | |
| if (d->type != TYPE_BOOL) { | |
| data_t *dclone = data_new(); | |
| data_copy(dclone, d); | |
| if (data_convert_type(dclone, DATA_TYPE_BOOL) == | |
| DATA_TYPE_BOOL) { | |
| *buffer = data_get_bool(dclone); | |
| rc = SLURM_SUCCESS; | |
| } | |
| FREE_NULL_DATA(dclone); | |
| log_flag(DATA, "%s: converted %pD=%s", | |
| __func__, d, (*buffer ? "true" : "false")); | |
| return rc; | |
| } | |
| *buffer = data_get_bool(d); | |
| return SLURM_SUCCESS; | |
| } | |
| extern int data_get_bool_converted(data_t *d, bool *buffer) | |
| { | |
| int rc; | |
| _check_magic(d); | |
| if (!d || !buffer) | |
| return ESLURM_DATA_PTR_NULL; | |
| /* assign value if converted successfully */ | |
| rc = data_copy_bool_converted(d, buffer); | |
| if (!rc) | |
| data_set_bool(d, *buffer); | |
| return rc; | |
| } | |
| extern int data_get_int_converted(const data_t *d, int64_t *buffer) | |
| { | |
| _check_magic(d); | |
| int rc = SLURM_SUCCESS; | |
| if (!d || !buffer) | |
| return ESLURM_DATA_PTR_NULL; | |
| if (d->type != TYPE_INT_64) { | |
| data_t *dclone = data_new(); | |
| data_copy(dclone, d); | |
| if (data_convert_type(dclone, DATA_TYPE_INT_64) == | |
| DATA_TYPE_INT_64) | |
| *buffer = data_get_int(dclone); | |
| else | |
| rc = ESLURM_DATA_CONV_FAILED; | |
| FREE_NULL_DATA(dclone); | |
| } else { | |
| *buffer = data_get_int(d); | |
| } | |
| log_flag(DATA, "%s: converted %pD=%"PRId64, __func__, d, *buffer); | |
| return rc; | |
| } | |
| extern size_t data_get_dict_length(const data_t *data) | |
| { | |
| _check_magic(data); | |
| if (!data) | |
| return 0; | |
| xassert(data->type == TYPE_DICT); | |
| return data->data.dict_u->count; | |
| } | |
| extern size_t data_get_list_length(const data_t *data) | |
| { | |
| _check_magic(data); | |
| if (!data) | |
| return 0; | |
| xassert(data->type == TYPE_LIST); | |
| return data->data.list_u->count; | |
| } | |
| extern data_t *data_get_list_last(data_t *data) | |
| { | |
| data_list_node_t *i; | |
| _check_magic(data); | |
| if (!data) | |
| return NULL; | |
| xassert(data->type == TYPE_LIST); | |
| if (data->type != TYPE_LIST) | |
| return NULL; | |
| if (!data->data.list_u->count) | |
| return NULL; | |
| i = data->data.list_u->begin; | |
| _check_data_list_magic(data->data.list_u); | |
| while (i) { | |
| _check_data_list_node_magic(i); | |
| xassert(!i->key); | |
| if (!i->next) { | |
| log_flag(DATA, "%s: %pD[%s]=%pD", | |
| __func__, data, i->key, i->data); | |
| return i->data; | |
| } | |
| i = i->next; | |
| } | |
| fatal_abort("%s: malformed data list", __func__); | |
| } | |
| extern int data_list_split_str(data_t *dst, const char *src, const char *token) | |
| { | |
| char *save_ptr = NULL; | |
| char *tok = NULL; | |
| char *str = xstrdup(src); | |
| if (dst->type == TYPE_NULL) | |
| data_set_list(dst); | |
| xassert(dst->type == TYPE_LIST); | |
| if (dst->type != TYPE_LIST) | |
| return SLURM_ERROR; | |
| if (str && !str[0]) | |
| xfree(str); | |
| if (!str) | |
| return SLURM_SUCCESS; | |
| tok = strtok_r(str, "/", &save_ptr); | |
| while (tok) { | |
| data_t *e = data_list_append(dst); | |
| xstrtrim(tok); | |
| data_set_string(e, tok); | |
| log_flag_hex(DATA, tok, strlen(tok), | |
| "%s: split string from 0x%"PRIxPTR" to %pD[%zu]=%pD", | |
| __func__, src, dst, dst->data.list_u->count, e); | |
| tok = strtok_r(NULL, "/", &save_ptr); | |
| } | |
| xfree(str); | |
| return SLURM_SUCCESS; | |
| } | |
| static data_for_each_cmd_t _foreach_join_str(const data_t *data, void *arg) | |
| { | |
| char *b = NULL; | |
| merge_path_strings_t *args = arg; | |
| if (!data_get_string_converted(data, &b)) | |
| xstrfmtcatat(args->path, &args->at, "%s%s%s", | |
| (!args->path ? args->token : ""), | |
| (args->at ? args->token : ""), b); | |
| xfree(b); | |
| return DATA_FOR_EACH_CONT; | |
| } | |
| extern int data_list_join_str(char **dst, const data_t *src, const char *token) | |
| { | |
| merge_path_strings_t args = { | |
| .token = token, | |
| }; | |
| xassert(!*dst); | |
| xassert(src->type == TYPE_LIST); | |
| if (data_list_for_each_const(src, _foreach_join_str, &args) < 0) { | |
| xfree(args.path); | |
| return SLURM_ERROR; | |
| } | |
| *dst = args.path; | |
| log_flag_hex(DATA, *dst, strlen(*dst), | |
| "%s: %pD string joined with token %s", | |
| __func__, src, token); | |
| return SLURM_SUCCESS; | |
| } | |
| extern int data_list_for_each_const(const data_t *d, DataListForFConst f, void *arg) | |
| { | |
| int count = 0; | |
| const data_list_node_t *i; | |
| _check_magic(d); | |
| if (!d || (d->type != TYPE_LIST)) { | |
| error("%s: for each attempted on non-list object (0x%"PRIXPTR")", | |
| __func__, (uintptr_t) d); | |
| return -1; | |
| } | |
| i = d->data.list_u->begin; | |
| _check_data_list_magic(d->data.list_u); | |
| while (i) { | |
| _check_data_list_node_magic(i); | |
| xassert(!i->key); | |
| data_for_each_cmd_t cmd = f(i->data, arg); | |
| count++; | |
| xassert(cmd > DATA_FOR_EACH_INVALID); | |
| xassert(cmd < DATA_FOR_EACH_MAX); | |
| switch (cmd) { | |
| case DATA_FOR_EACH_CONT: | |
| break; | |
| case DATA_FOR_EACH_DELETE: | |
| fatal_abort("%s: delete attempted against const", | |
| __func__); | |
| break; | |
| case DATA_FOR_EACH_FAIL: | |
| count *= -1; | |
| /* fall through */ | |
| case DATA_FOR_EACH_STOP: | |
| i = NULL; | |
| break; | |
| default: | |
| fatal_abort("%s: invalid cmd", __func__); | |
| } | |
| if (i) | |
| i = i->next; | |
| } | |
| return count; | |
| } | |
| extern int data_list_for_each(data_t *d, DataListForF f, void *arg) | |
| { | |
| int count = 0; | |
| data_list_node_t *i; | |
| _check_magic(d); | |
| if (!d || (d->type != TYPE_LIST)) { | |
| error("%s: for each attempted on non-list %pD", __func__, d); | |
| return -1; | |
| } | |
| i = d->data.list_u->begin; | |
| _check_data_list_magic(d->data.list_u); | |
| while (i) { | |
| _check_data_list_node_magic(i); | |
| xassert(!i->key); | |
| data_for_each_cmd_t cmd = f(i->data, arg); | |
| count++; | |
| xassert(cmd > DATA_FOR_EACH_INVALID); | |
| xassert(cmd < DATA_FOR_EACH_MAX); | |
| switch (cmd) { | |
| case DATA_FOR_EACH_CONT: | |
| if (i) | |
| i = i->next; | |
| break; | |
| case DATA_FOR_EACH_DELETE: | |
| { | |
| data_list_node_t *idel = i; | |
| i = i->next; | |
| _release_data_list_node(d->data.list_u, idel); | |
| _check_data_list_magic(d->data.list_u); | |
| break; | |
| } | |
| case DATA_FOR_EACH_FAIL: | |
| count *= -1; | |
| /* fall through */ | |
| case DATA_FOR_EACH_STOP: | |
| i = NULL; | |
| break; | |
| default: | |
| fatal_abort("%s: invalid cmd", __func__); | |
| } | |
| } | |
| return count; | |
| } | |
| extern int data_dict_for_each_const(const data_t *d, DataDictForFConst f, void *arg) | |
| { | |
| int count = 0; | |
| const data_list_node_t *i; | |
| if (!d) | |
| return 0; | |
| _check_magic(d); | |
| if (data_get_type(d) != DATA_TYPE_DICT) { | |
| error("%s: for each attempted on non-dict %pD", __func__, d); | |
| return -1; | |
| } | |
| i = d->data.dict_u->begin; | |
| _check_data_list_magic(d->data.dict_u); | |
| while (i) { | |
| data_for_each_cmd_t cmd; | |
| _check_data_list_node_magic(i); | |
| cmd = f(i->key, i->data, arg); | |
| count++; | |
| xassert(cmd > DATA_FOR_EACH_INVALID); | |
| xassert(cmd < DATA_FOR_EACH_MAX); | |
| switch (cmd) { | |
| case DATA_FOR_EACH_CONT: | |
| break; | |
| case DATA_FOR_EACH_DELETE: | |
| fatal_abort("%s: delete attempted against const", | |
| __func__); | |
| break; | |
| case DATA_FOR_EACH_FAIL: | |
| count *= -1; | |
| /* fall through */ | |
| case DATA_FOR_EACH_STOP: | |
| i = NULL; | |
| break; | |
| default: | |
| fatal_abort("%s: invalid cmd", __func__); | |
| } | |
| if (i) | |
| i = i->next; | |
| } | |
| return count; | |
| } | |
| extern int data_dict_for_each(data_t *d, DataDictForF f, void *arg) | |
| { | |
| int count = 0; | |
| data_list_node_t *i; | |
| if (!d) | |
| return 0; | |
| _check_magic(d); | |
| if (data_get_type(d) != DATA_TYPE_DICT) { | |
| error("%s: for each attempted on non-dict %pD", __func__, d); | |
| return -1; | |
| } | |
| i = d->data.dict_u->begin; | |
| _check_data_list_magic(d->data.dict_u); | |
| while (i) { | |
| _check_data_list_node_magic(i); | |
| data_for_each_cmd_t cmd = f(i->key, i->data, arg); | |
| count++; | |
| xassert(cmd > DATA_FOR_EACH_INVALID); | |
| xassert(cmd < DATA_FOR_EACH_MAX); | |
| switch (cmd) { | |
| case DATA_FOR_EACH_CONT: | |
| if (i) | |
| i = i->next; | |
| break; | |
| case DATA_FOR_EACH_DELETE: | |
| { | |
| data_list_node_t *idel = i; | |
| i = i->next; | |
| _release_data_list_node(d->data.list_u, idel); | |
| _check_data_list_magic(d->data.list_u); | |
| break; | |
| } | |
| case DATA_FOR_EACH_FAIL: | |
| count *= -1; | |
| /* fall through */ | |
| case DATA_FOR_EACH_STOP: | |
| i = NULL; | |
| break; | |
| default: | |
| fatal_abort("%s: invalid cmd", __func__); | |
| } | |
| } | |
| return count; | |
| } | |
| static void _convert_data_string(data_t *data) | |
| { | |
| _check_magic(data); | |
| switch (data_get_type(data)) { | |
| case DATA_TYPE_STRING: | |
| break; | |
| case DATA_TYPE_BOOL: | |
| data_set_string(data, (data_get_bool(data) ? "true" : "false")); | |
| break; | |
| case DATA_TYPE_NULL: | |
| data_set_string(data, ""); | |
| break; | |
| case DATA_TYPE_FLOAT: | |
| { | |
| char *str = xstrdup_printf("%lf", data_get_float(data)); | |
| data_set_string_own(data, str); | |
| break; | |
| } | |
| case DATA_TYPE_INT_64: | |
| { | |
| char *str = xstrdup_printf("%"PRId64, data_get_int(data)); | |
| data_set_string_own(data, str); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| static void _convert_data_force_bool(data_t *data) | |
| { | |
| _check_magic(data); | |
| /* attempt to detect the type first */ | |
| (void) data_convert_type(data, DATA_TYPE_NONE); | |
| switch (data_get_type(data)) { | |
| case DATA_TYPE_STRING: | |
| /* non-empty string but not recognized format */ | |
| data_set_bool(data, true); | |
| break; | |
| case DATA_TYPE_BOOL: | |
| break; | |
| case DATA_TYPE_NULL: | |
| data_set_bool(data, false); | |
| break; | |
| case DATA_TYPE_FLOAT: | |
| data_set_bool(data, data_get_float(data) != 0); | |
| break; | |
| case DATA_TYPE_INT_64: | |
| data_set_bool(data, data_get_int(data) != 0); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| static int _convert_data_null(data_t *data) | |
| { | |
| _check_magic(data); | |
| switch (data_get_type(data)) { | |
| case DATA_TYPE_STRING: | |
| { | |
| const char *str = data_get_string(data); | |
| if (!str[0]) | |
| goto convert; | |
| if (str[0] == '~') | |
| goto convert; | |
| if (!xstrcasecmp(str, "null")) | |
| goto convert; | |
| goto fail; | |
| } | |
| case DATA_TYPE_NULL: | |
| return SLURM_SUCCESS; | |
| default: | |
| return ESLURM_DATA_CONV_FAILED; | |
| } | |
| fail: | |
| return ESLURM_DATA_CONV_FAILED; | |
| convert: | |
| log_flag_hex(DATA, data_get_string(data), strlen(data_get_string(data)), | |
| "%s: converted %pD->null", __func__, data); | |
| data_set_null(data); | |
| return SLURM_SUCCESS; | |
| } | |
| static int _convert_data_bool(data_t *data) | |
| { | |
| const char *str = NULL; | |
| _check_magic(data); | |
| switch (data_get_type(data)) { | |
| case DATA_TYPE_STRING: | |
| { | |
| str = data_get_string(data); | |
| if (tolower(str[0]) == 'y') { | |
| if (!str[1] || ((tolower(str[1]) == 'e') && | |
| (tolower(str[2]) == 's') && | |
| (str[3] == '\0'))) { | |
| data_set_bool(data, true); | |
| goto converted; | |
| } | |
| goto fail; | |
| } else if (tolower(str[0]) == 't') { | |
| if (!str[1] || ((tolower(str[1]) == 'r') && | |
| (tolower(str[2]) == 'u') && | |
| (tolower(str[3]) == 'e') && | |
| (str[4] == '\0'))) { | |
| data_set_bool(data, true); | |
| goto converted; | |
| } | |
| goto fail; | |
| } else if ((tolower(str[0]) == 'o') && | |
| (tolower(str[1]) == 'n') && | |
| (str[2] == '\0')) { | |
| data_set_bool(data, true); | |
| goto converted; | |
| } else if (tolower(str[0]) == 'n') { | |
| if (!str[1] || ((tolower(str[1]) == 'o') && | |
| (str[2] == '\0'))) { | |
| data_set_bool(data, false); | |
| goto converted; | |
| } | |
| goto fail; | |
| } else if (tolower(str[0]) == 'f') { | |
| if (!str[1] || ((tolower(str[1]) == 'a') && | |
| (tolower(str[2]) == 'l') && | |
| (tolower(str[3]) == 's') && | |
| (tolower(str[4]) == 'e') && | |
| (str[5] == '\0'))) { | |
| data_set_bool(data, false); | |
| goto converted; | |
| } | |
| goto fail; | |
| } else if ((tolower(str[0]) == 'o') && | |
| (tolower(str[1]) == 'f') && | |
| (tolower(str[2]) == 'f') && | |
| (str[3] == '\0')) { | |
| data_set_bool(data, false); | |
| goto converted; | |
| } | |
| goto fail; | |
| } | |
| case DATA_TYPE_BOOL: | |
| return SLURM_SUCCESS; | |
| default: | |
| goto fail; | |
| } | |
| converted: | |
| log_flag_hex(DATA, str, strlen(str), | |
| "%s: converted %pD->%s", | |
| __func__, data, (data_get_bool(data) ? "true" : "false")); | |
| return SLURM_SUCCESS; | |
| fail: | |
| if (str) | |
| log_flag_hex(DATA, str, strlen(str), | |
| "%s: converting %pD to bool failed", | |
| __func__, data); | |
| else | |
| log_flag(DATA, "%s: converting %pD to bool failed", | |
| __func__, data); | |
| return ESLURM_DATA_CONV_FAILED; | |
| } | |
| static int _convert_data_int(data_t *data, bool force) | |
| { | |
| _check_magic(data); | |
| switch (data_get_type(data)) { | |
| case DATA_TYPE_STRING: | |
| { | |
| int64_t x = -1; | |
| char *end_ptr = NULL; | |
| const char *str = data_get_string(data); | |
| if (!str[0]) { | |
| log_flag_hex(DATA, str, strlen(str), | |
| "%s: convert empty string %pD to integer failed", | |
| __func__, data); | |
| return ESLURM_DATA_CONV_FAILED; | |
| } | |
| if ((str[0] == '0') && (tolower(str[1]) == 'x')) { | |
| uint64_t u = INFINITE64; | |
| errno = 0; | |
| u = strtoull(str, &end_ptr, 16); | |
| if (errno) { | |
| log_flag_hex(DATA, str, strlen(str), | |
| "%s: conversion of hex string %pD to integer failed: %m", | |
| __func__, data); | |
| return errno; | |
| } | |
| if ((end_ptr == str) || end_ptr[0]) { | |
| log_flag_hex(DATA, str, strlen(str), | |
| "%s: conversion of hex string %pD to integer did not parse entire string", | |
| __func__, data); | |
| return ESLURM_DATA_CONV_FAILED; | |
| } | |
| x = (int64_t) u; | |
| log_flag_hex(DATA, str, strlen(str), | |
| "%s: converted hex number %pD->%"PRId64, | |
| __func__, data, x); | |
| data_set_int(data, x); | |
| return SLURM_SUCCESS; | |
| } | |
| if (!force) { | |
| const char *p = str; | |
| /* Allow explicitly positive and negative integers */ | |
| if ((p[0] == '-') || (p[0] == '+')) | |
| p++; | |
| for (; *p; p++) { | |
| if ((*p < '0') || (*p > '9')) { | |
| log_flag_hex(DATA, str, strlen(str), | |
| "%s: rejecting non-numeric conversion of %pD to integer failed", | |
| __func__, data); | |
| return ESLURM_DATA_CONV_FAILED; | |
| } | |
| } | |
| } | |
| errno = 0; | |
| x = strtoll(str, &end_ptr, 10); | |
| if (errno || (end_ptr == str) || end_ptr[0]) { | |
| log_flag_hex(DATA, str, strlen(str), | |
| "%s: conversion of %pD to integer failed", | |
| __func__, data); | |
| return ESLURM_DATA_CONV_FAILED; | |
| } | |
| log_flag_hex(DATA, str, strlen(str), | |
| "%s: converted %pD->%"PRId64, | |
| __func__, data, x); | |
| data_set_int(data, x); | |
| return SLURM_SUCCESS; | |
| } | |
| case DATA_TYPE_FLOAT: | |
| if (force) { | |
| const double f = data_get_float(data); | |
| /* | |
| * llrint() is undefined for a value outside of the | |
| * range of its return type and returns INT64_MIN on | |
| * glibc. Refuse the conversion instead of storing a | |
| * different number than was given. | |
| */ | |
| if (!isfinite(f) || isless(f, (double) INT64_MIN) || | |
| isgreater(f, nextafter((double) INT64_MAX, 0.0))) { | |
| log_flag(DATA, "%s: conversion of %pD to integer failed", | |
| __func__, data); | |
| return ESLURM_DATA_CONV_FAILED; | |
| } | |
| (void) data_set_int(data, llrint(f)); | |
| return SLURM_SUCCESS; | |
| } | |
| return ESLURM_DATA_CONV_FAILED; | |
| case DATA_TYPE_INT_64: | |
| return SLURM_SUCCESS; | |
| case DATA_TYPE_NULL: | |
| if (force) { | |
| /* | |
| * Conversion from NULL to integer is a loss of | |
| * information as NULL implies value is not set where as | |
| * integer 0 could just mean there is a a value zero as | |
| * opposed to there be no value set. This conversion is | |
| * only done when force is true as such. | |
| */ | |
| data_set_int(data, 0); | |
| return SLURM_SUCCESS; | |
| } | |
| default: | |
| return ESLURM_DATA_CONV_FAILED; | |
| } | |
| } | |
| static int _convert_data_float_from_string(data_t *data) | |
| { | |
| const char *str = data_get_string(data); | |
| int i = 0; | |
| bool negative = false; | |
| xassert(str); | |
| if (str[i] == '+') { | |
| i++; | |
| } else if (str[i] == '-') { | |
| i++; | |
| negative = true; | |
| } | |
| if ((tolower(str[i]) == 'i')) { | |
| i++; | |
| if (!xstrcasecmp(&str[i], "nf") || | |
| !xstrcasecmp(&str[i], "nfinity")) { | |
| if (negative) | |
| data_set_float(data, -INFINITY); | |
| else | |
| data_set_float(data, INFINITY); | |
| goto converted; | |
| } | |
| goto fail; | |
| } | |
| if ((tolower(str[i]) == 'n')) { | |
| i++; | |
| if (!xstrcasecmp(&str[i], "an")) { | |
| if (negative) | |
| data_set_float(data, -NAN); | |
| else | |
| data_set_float(data, NAN); | |
| goto converted; | |
| } | |
| goto fail; | |
| } | |
| if ((str[i] >= '0') && (str[i] <= '9')) { | |
| char end; | |
| double x; | |
| if (sscanf(&str[i], "%lf%c", &x, &end) == 1) { | |
| if (negative) | |
| x *= -1; | |
| data_set_float(data, x); | |
| goto converted; | |
| } | |
| } | |
| goto fail; | |
| converted: | |
| log_flag(DATA, "%s: converted %pD to float: %lf", | |
| __func__, data, data_get_float(data)); | |
| return SLURM_SUCCESS; | |
| fail: | |
| log_flag_hex(DATA, str, strlen(str), | |
| "%s: convert %pD to double float failed", __func__, data); | |
| return ESLURM_DATA_CONV_FAILED; | |
| } | |
| static int _convert_data_float(data_t *data) | |
| { | |
| _check_magic(data); | |
| switch (data_get_type(data)) { | |
| case DATA_TYPE_STRING: | |
| return _convert_data_float_from_string(data); | |
| case DATA_TYPE_INT_64: | |
| if (data_get_int(data) == INFINITE64) | |
| data_set_float(data, HUGE_VAL); | |
| else if (data_get_int(data) == NO_VAL64) | |
| data_set_float(data, NAN); | |
| else /* attempt normal fp conversion */ | |
| data_set_float(data, data_get_int(data)); | |
| return SLURM_SUCCESS; | |
| case DATA_TYPE_FLOAT: | |
| return SLURM_SUCCESS; | |
| default: | |
| return ESLURM_DATA_CONV_FAILED; | |
| } | |
| return ESLURM_DATA_CONV_FAILED; | |
| } | |
| static data_for_each_cmd_t _convert_data_foreach_dict_list(const char *key, | |
| data_t *data, | |
| void *arg) | |
| { | |
| data_t *src = arg; | |
| _check_magic(src); | |
| (void) data_move(data_list_append(src), data); | |
| return DATA_FOR_EACH_CONT; | |
| } | |
| static int _convert_data_dict_list(data_t *src) | |
| { | |
| int rc = SLURM_SUCCESS; | |
| data_t *dict = data_new(); | |
| (void) data_move(dict, src); | |
| (void) data_set_list(src); | |
| if (data_dict_for_each(dict, _convert_data_foreach_dict_list, src) < 0) | |
| rc = ESLURM_DATA_CONV_FAILED; | |
| FREE_NULL_DATA(dict); | |
| return rc; | |
| } | |
| static data_for_each_cmd_t _convert_data_foreach_list_dict(data_t *data, | |
| void *arg) | |
| { | |
| convert_data_foreach_list_dict_args_t *args = arg; | |
| xassert(args->magic == CONVERT_DATA_FOREACH_LIST_DICT_ARGS_MAGIC); | |
| (void) data_move(data_key_set_int(args->src, args->index), data); | |
| args->index++; | |
| return DATA_FOR_EACH_CONT; | |
| } | |
| static int _convert_data_list_dict(data_t *src) | |
| { | |
| int rc = SLURM_SUCCESS; | |
| convert_data_foreach_list_dict_args_t args = { | |
| .magic = CONVERT_DATA_FOREACH_LIST_DICT_ARGS_MAGIC, | |
| .src = src, | |
| }; | |
| data_t *list = data_new(); | |
| (void) data_move(list, src); | |
| (void) data_set_dict(src); | |
| if (data_list_for_each(list, _convert_data_foreach_list_dict, &args) < | |
| 0) | |
| rc = ESLURM_DATA_CONV_FAILED; | |
| FREE_NULL_DATA(list); | |
| return rc; | |
| } | |
| extern data_type_t data_convert_type(data_t *data, data_type_t match) | |
| { | |
| _check_magic(data); | |
| if (!data) | |
| return DATA_TYPE_NONE; | |
| switch (match) { | |
| case DATA_TYPE_STRING: | |
| _convert_data_string(data); | |
| break; | |
| case DATA_TYPE_BOOL: | |
| _convert_data_force_bool(data); | |
| break; | |
| case DATA_TYPE_INT_64: | |
| (void) _convert_data_int(data, true); | |
| break; | |
| case DATA_TYPE_FLOAT: | |
| (void) _convert_data_float(data); | |
| break; | |
| case DATA_TYPE_NULL: | |
| (void) _convert_data_null(data); | |
| break; | |
| case DATA_TYPE_NONE: | |
| /* If a conversion succeeds skip calling the others */ | |
| if (!_convert_data_null(data) || | |
| !_convert_data_int(data, false) || | |
| !_convert_data_float(data) || | |
| !_convert_data_int(data, true) || !_convert_data_bool(data)) | |
| ; /* blank on purpose */ | |
| break; | |
| case DATA_TYPE_DICT: | |
| if (data_get_type(data) == DATA_TYPE_DICT) | |
| return DATA_TYPE_DICT; | |
| else if ((data_get_type(data) == DATA_TYPE_LIST) && | |
| !_convert_data_list_dict(data)) | |
| return DATA_TYPE_DICT; | |
| /* data_parser should be used for this conversion instead. */ | |
| break; | |
| case DATA_TYPE_LIST: | |
| if (data_get_type(data) == DATA_TYPE_LIST) | |
| return DATA_TYPE_LIST; | |
| else if ((data_get_type(data) == DATA_TYPE_DICT) && | |
| !_convert_data_dict_list(data)) | |
| return DATA_TYPE_LIST; | |
| /* data_parser should be used for this conversion instead. */ | |
| break; | |
| case DATA_TYPE_MAX: | |
| fatal_abort("%s: unexpected data type", __func__); | |
| default: | |
| fatal_abort("%s: invalid conversion requested", __func__); | |
| } | |
| return data_get_type(data); | |
| } | |
| static data_for_each_cmd_t _convert_list_entry(data_t *data, void *arg) | |
| { | |
| convert_args_t *args = arg; | |
| args->count += _convert_tree(data, args->match); | |
| return DATA_FOR_EACH_CONT; | |
| } | |
| static data_for_each_cmd_t _convert_dict_entry(const char *key, data_t *data, | |
| void *arg) | |
| { | |
| convert_args_t *args = arg; | |
| args->count += _convert_tree(data, args->match); | |
| return DATA_FOR_EACH_CONT; | |
| } | |
| static size_t _convert_tree(data_t *data, const data_type_t match) | |
| { | |
| convert_args_t args = { | |
| .match = match, | |
| }; | |
| _check_magic(data); | |
| if (!data) | |
| return 0; | |
| switch (data_get_type(data)) { | |
| case DATA_TYPE_DICT: | |
| (void)data_dict_for_each(data, _convert_dict_entry, &args); | |
| break; | |
| case DATA_TYPE_LIST: | |
| (void)data_list_for_each(data, _convert_list_entry, &args); | |
| break; | |
| default: | |
| if (match == data_convert_type(data, match)) | |
| args.count++; | |
| break; | |
| } | |
| return args.count; | |
| } | |
| extern size_t data_convert_tree(data_t *data, const data_type_t match) | |
| { | |
| return _convert_tree(data, match); | |
| } | |
| static data_for_each_cmd_t _find_dict_match(const char *key, const data_t *a, | |
| void *arg) | |
| { | |
| bool rc; | |
| find_dict_match_t *p = arg; | |
| const data_t *b = data_key_get_const(p->b, key); | |
| rc = data_check_match(a, b, p->exact); | |
| log_flag(DATA, "dictionary compare: %s(0x%"PRIXPTR")=%s(0x%"PRIXPTR") %s %s(0x%"PRIXPTR")=%s(0x%"PRIXPTR")", | |
| key, (uintptr_t) p->a, _type_to_string(a->type), (uintptr_t) a, | |
| (rc ? "\u2261" : "\u2260"), key, (uintptr_t) p->b, | |
| (b ? _type_to_string(b->type) : _type_to_string(TYPE_NONE)), | |
| (uintptr_t) b); | |
| return rc ? DATA_FOR_EACH_CONT : DATA_FOR_EACH_FAIL; | |
| } | |
| static bool _data_match_dict(const data_t *a, const data_t *b, const bool exact) | |
| { | |
| find_dict_match_t p = { | |
| .a = a, | |
| .b = b, | |
| .exact = exact, | |
| }; | |
| if (!a || (a->type != TYPE_DICT)) | |
| return false; | |
| if (!b || (b->type != TYPE_DICT)) | |
| return false; | |
| _check_magic(a); | |
| _check_magic(b); | |
| if (a->data.dict_u->count != b->data.dict_u->count) | |
| return false; | |
| /* match by key and not order with dictionary */ | |
| return (data_dict_for_each_const(a, _find_dict_match, &p) >= 0); | |
| } | |
| static bool _data_match_lists(const data_t *a, const data_t *b, | |
| const bool exact) | |
| { | |
| bool fail = false; | |
| const data_list_node_t *ptr_a; | |
| const data_list_node_t *ptr_b; | |
| if (!a || (a->type != TYPE_LIST)) | |
| return false; | |
| if (!b || (b->type != TYPE_LIST)) | |
| return false; | |
| _check_magic(a); | |
| _check_magic(b); | |
| if (a->data.list_u->count != b->data.list_u->count) | |
| return false; | |
| ptr_a = a->data.list_u->begin; | |
| ptr_b = b->data.list_u->begin; | |
| while (ptr_a && !fail) { | |
| _check_data_list_node_magic(ptr_a); | |
| _check_data_list_node_magic(ptr_b); | |
| if (data_check_match(ptr_a->data, ptr_b->data, exact)) { | |
| ptr_a = ptr_a->next; | |
| ptr_b = ptr_b->next; | |
| } else | |
| fail = true; | |
| } | |
| return !fail; | |
| } | |
| /* compare an integer and a float without rounding the integer to a double */ | |
| static bool _match_int_float_exact(int64_t i, double f) | |
| { | |
| int64_t fi; | |
| /* | |
| * INT64_MIN is exact as a double. INT64_MAX is not and rounds up to | |
| * 2^63, one past the largest int64_t, so it is an exclusive bound. | |
| */ | |
| if (isnan(f) || (f < (double) INT64_MIN) || (f >= (double) INT64_MAX)) | |
| return false; | |
| fi = (int64_t) f; | |
| /* a fractional float can never equal an integer */ | |
| if ((double) fi != f) | |
| return false; | |
| return (fi == i); | |
| } | |
| /* | |
| * JSON has a single number type, so an integer and a float holding the same | |
| * value are the same number. Every other type mismatch is a mismatch: no | |
| * conversion is attempted. | |
| */ | |
| static bool _match_type_mismatch_exact(const data_t *a, const data_t *b) | |
| { | |
| if ((a->type == TYPE_INT_64) && (b->type == TYPE_FLOAT)) | |
| return _match_int_float_exact(a->data.int_u, b->data.float_u); | |
| if ((a->type == TYPE_FLOAT) && (b->type == TYPE_INT_64)) | |
| return _match_int_float_exact(b->data.int_u, a->data.float_u); | |
| return false; | |
| } | |
| extern bool data_check_match(const data_t *a, const data_t *b, const bool exact) | |
| { | |
| bool rc; | |
| if (a == NULL && b == NULL) | |
| return true; | |
| if (a == NULL || b == NULL) | |
| return false; | |
| _check_magic(a); | |
| _check_magic(b); | |
| if (exact && (data_get_type(a) != data_get_type(b))) { | |
| rc = _match_type_mismatch_exact(a, b); | |
| log_flag(DATA, "exact compare: %pD %s %pD", | |
| a, (rc ? "=" : "!="), b); | |
| return rc; | |
| } | |
| if (data_get_type(a) != data_get_type(b)) { | |
| data_t *conv = NULL; | |
| (void) data_clone(b, conv); | |
| if ((a->type == TYPE_NULL) || (b->type == TYPE_NULL) || | |
| (data_convert_type(conv, data_get_type(a)) != | |
| data_get_type(a))) { | |
| log_flag(DATA, "type mismatch: %s(0x%"PRIXPTR") != %s(0x%"PRIXPTR")", | |
| _type_to_string(a->type), (uintptr_t) a, | |
| _type_to_string(b->type), (uintptr_t) b); | |
| FREE_NULL_DATA(conv); | |
| return false; | |
| } | |
| rc = data_check_match(a, conv, exact); | |
| log_flag(DATA, "compare: %pD %s %pD (converted from %pD)", | |
| a, (rc ? "=" : "!="), conv, b); | |
| FREE_NULL_DATA(conv); | |
| return rc; | |
| } | |
| switch (a->type) { | |
| case TYPE_NULL: | |
| rc = (b->type == TYPE_NULL); | |
| log_flag(DATA, "compare: %s(0x%"PRIXPTR") %s %s(0x%"PRIXPTR")", | |
| _type_to_string(a->type), (uintptr_t) a, | |
| (rc ? "=" : "!="), | |
| _type_to_string(b->type), (uintptr_t) b); | |
| return rc; | |
| case TYPE_STRING_INLINE: | |
| case TYPE_STRING_PTR: | |
| rc = !xstrcmp(data_get_string(a), data_get_string(b)); | |
| log_flag(DATA, "compare: %s(0x%"PRIXPTR")=%s %s %s(0x%"PRIXPTR")=%s", | |
| _type_to_string(a->type), (uintptr_t) a, | |
| data_get_string(a), (rc ? "=" : "!="), | |
| _type_to_string(b->type), (uintptr_t) b, | |
| data_get_string(b)); | |
| return rc; | |
| case TYPE_BOOL: | |
| rc = (data_get_bool(a) == data_get_bool(b)); | |
| log_flag(DATA, "compare: %s(0x%"PRIXPTR")=%s %s %s(0x%"PRIXPTR")=%s", | |
| _type_to_string(a->type), (uintptr_t) a, | |
| (data_get_bool(a) ? "True" : "False"), | |
| (rc ? "=" : "!="), | |
| _type_to_string(b->type), (uintptr_t) b, | |
| (data_get_bool(b) ? "True" : "False")); | |
| return rc; | |
| case TYPE_INT_64: | |
| rc = data_get_int(a) == data_get_int(b); | |
| log_flag(DATA, "compare: %s(0x%"PRIXPTR")=%"PRId64" %s %s(0x%"PRIXPTR")=%"PRId64, | |
| _type_to_string(a->type), (uintptr_t) a, | |
| data_get_int(a), (rc ? "=" : "!="), | |
| _type_to_string(b->type), (uintptr_t) b, | |
| data_get_int(b)); | |
| return rc; | |
| case TYPE_FLOAT: | |
| { | |
| const double x = data_get_float(a), y = data_get_float(b); | |
| /* | |
| * NaN never compares equal to itself, so it has to be matched | |
| * explicitly. Infinities only match when they share a sign. | |
| * Everything else matches exactly or, unless exact is set, | |
| * within tolerance. | |
| */ | |
| if (isnan(x) || isnan(y)) | |
| rc = (isnan(x) && isnan(y)); | |
| else if (isinf(x) || isinf(y)) | |
| rc = (isinf(x) && isinf(y) && | |
| (signbit(x) == signbit(y))); | |
| else if (exact) | |
| rc = (x == y); | |
| else | |
| rc = ((x == y) || fuzzy_equal(x, y)); | |
| log_flag(DATA, "compare: %s(0x%"PRIXPTR")=%e %s %s(0x%"PRIXPTR")=%e", | |
| _type_to_string(a->type), (uintptr_t) a, x, | |
| (rc ? "=" : "!="), | |
| _type_to_string(b->type), (uintptr_t) b, y); | |
| return rc; | |
| } | |
| case TYPE_DICT: | |
| rc = _data_match_dict(a, b, exact); | |
| log_flag(DATA, "compare dictionary: %s(0x%"PRIXPTR")[%zd] %s %s(0x%"PRIXPTR")[%zd]", | |
| _type_to_string(a->type), (uintptr_t) a, | |
| data_get_dict_length(a), (rc ? "=" : "!="), | |
| _type_to_string(b->type), (uintptr_t) b, | |
| data_get_dict_length(b)); | |
| return rc; | |
| case TYPE_LIST: | |
| rc = _data_match_lists(a, b, exact); | |
| log_flag(DATA, "compare list: %s(0x%"PRIXPTR")[%zd] %s %s(0x%"PRIXPTR")[%zd]", | |
| _type_to_string(a->type), (uintptr_t) a, | |
| data_get_list_length(a), (rc ? "=" : "!="), | |
| _type_to_string(b->type), (uintptr_t) b, | |
| data_get_list_length(b)); | |
| return rc; | |
| case TYPE_NONE: | |
| /* fall through */ | |
| case TYPE_START: | |
| /* fall through */ | |
| case TYPE_MAX: | |
| fatal_abort("%s: unexpected data type", __func__); | |
| } | |
| fatal_abort("%s: should never run", __func__); | |
| } | |
| static int _on_dict_path(const char *entry, bool template, void *arg) | |
| { | |
| dict_path_args_t *args = arg; | |
| data_t *src = args->found; | |
| _check_magic(src); | |
| xassert(args->magic == PARSE_DICT_PATH_ARGS_MAGIC); | |
| xassert(!template); | |
| if (args->found->type != TYPE_DICT) | |
| return EINVAL; | |
| if (!(args->found = data_key_get(src, entry))) | |
| return EINVAL; | |
| return SLURM_SUCCESS; | |
| } | |
| extern data_t *data_resolve_dict_path(data_t *data, const char *path) | |
| { | |
| dict_path_args_t args = { | |
| .magic = PARSE_DICT_PATH_ARGS_MAGIC, | |
| .found = data, | |
| }; | |
| _check_magic(data); | |
| if (!data || (data->type != TYPE_DICT)) | |
| return NULL; | |
| if (!url_path_walk(path, false, _on_dict_path, &args)) { | |
| log_flag_hex(DATA, path, strlen(path), | |
| "%s: %pD resolved dictionary path to %pD", | |
| __func__, data, args.found); | |
| return args.found; | |
| } else { | |
| log_flag_hex(DATA, path, strlen(path), | |
| "%s: %pD failed to resolve dictionary path", | |
| __func__, data); | |
| return NULL; | |
| } | |
| } | |
| static int _on_dict_path_const(const char *entry, bool template, void *arg) | |
| { | |
| dict_path_const_args_t *args = arg; | |
| const data_t *src = args->found; | |
| _check_magic(src); | |
| xassert(args->magic == PARSE_DICT_PATH_CONST_ARGS_MAGIC); | |
| xassert(!template); | |
| if (args->found->type != TYPE_DICT) | |
| return EINVAL; | |
| if (!(args->found = data_key_get_const(src, entry))) | |
| return EINVAL; | |
| return SLURM_SUCCESS; | |
| } | |
| extern const data_t *data_resolve_dict_path_const(const data_t *data, | |
| const char *path) | |
| { | |
| dict_path_const_args_t args = { | |
| .magic = PARSE_DICT_PATH_CONST_ARGS_MAGIC, | |
| .found = data, | |
| }; | |
| _check_magic(data); | |
| if (!data || (data->type != TYPE_DICT)) | |
| return NULL; | |
| if (!url_path_walk(path, false, _on_dict_path_const, &args)) { | |
| log_flag_hex(DATA, path, strlen(path), | |
| "%s: %pD resolved dictionary path to %pD", | |
| __func__, data, args.found); | |
| return args.found; | |
| } else { | |
| log_flag_hex(DATA, path, strlen(path), | |
| "%s: %pD failed to resolve dictionary path", | |
| __func__, data); | |
| return NULL; | |
| } | |
| } | |
| static int _on_dict_path_define(const char *entry, bool template, void *arg) | |
| { | |
| dict_path_define_args_t *args = arg; | |
| data_t *src = args->found; | |
| _check_magic(src); | |
| xassert(args->magic == PARSE_DICT_PATH_ARGS_MAGIC); | |
| xassert(!template); | |
| if (args->found->type == TYPE_NULL) | |
| data_set_dict(src); | |
| else if (args->found->type != TYPE_DICT) | |
| return EINVAL; | |
| if (!(args->found = data_key_set(src, entry))) | |
| return EINVAL; | |
| return SLURM_SUCCESS; | |
| } | |
| extern data_t *data_define_dict_path(data_t *data, const char *path) | |
| { | |
| dict_path_define_args_t args = { | |
| .magic = PARSE_DICT_PATH_ARGS_MAGIC, | |
| .found = data, | |
| }; | |
| _check_magic(data); | |
| if (!data) | |
| return NULL; | |
| if (!url_path_walk(path, false, _on_dict_path_define, &args)) { | |
| log_flag_hex(DATA, path, strlen(path), | |
| "%s: %pD defined dictionary path to %pD", | |
| __func__, data, args.found); | |
| return args.found; | |
| } else { | |
| log_flag_hex(DATA, path, strlen(path), | |
| "%s: %pD failed to define dictionary path", | |
| __func__, data); | |
| return NULL; | |
| } | |
| } | |
| static int _copy(data_t *dest, const data_t *src) | |
| { | |
| int rc = SLURM_SUCCESS; | |
| xassert(dest); | |
| xassert(src); | |
| _check_magic(src); | |
| _check_magic(dest); | |
| log_flag(DATA, "%s: copy data %pD to %pD", __func__, src, dest); | |
| switch (src->type) { | |
| case TYPE_STRING_INLINE: | |
| case TYPE_STRING_PTR: | |
| (void) data_set_string(dest, data_get_string(src)); | |
| return SLURM_SUCCESS; | |
| case TYPE_BOOL: | |
| (void) data_set_bool(dest, data_get_bool(src)); | |
| return SLURM_SUCCESS; | |
| case TYPE_INT_64: | |
| (void) data_set_int(dest, data_get_int(src)); | |
| return SLURM_SUCCESS; | |
| case TYPE_FLOAT: | |
| (void) data_set_float(dest, data_get_float(src)); | |
| return SLURM_SUCCESS; | |
| case TYPE_NULL: | |
| (void) data_set_null(dest); | |
| return SLURM_SUCCESS; | |
| case TYPE_LIST: | |
| { | |
| data_list_node_t *i = src->data.list_u->begin; | |
| data_set_list(dest); | |
| while (i) { | |
| _check_data_list_node_magic(i); | |
| xassert(!i->key); | |
| if ((rc = _copy(data_list_append(dest), i->data))) | |
| return rc; | |
| i = i->next; | |
| } | |
| return SLURM_SUCCESS; | |
| } | |
| case TYPE_DICT: | |
| { | |
| data_list_node_t *i = src->data.dict_u->begin; | |
| data_set_dict(dest); | |
| while (i) { | |
| _check_data_list_node_magic(i); | |
| if ((rc = _copy(data_key_set(dest, i->key), i->data))) | |
| return rc; | |
| i = i->next; | |
| } | |
| return SLURM_SUCCESS; | |
| } | |
| default: | |
| fatal_abort("%s: unexpected data type", __func__); | |
| } | |
| } | |
| extern data_t *data_copy(data_t *dest, const data_t *src) | |
| { | |
| xassert(dest); | |
| if (!src) | |
| return NULL; | |
| if (_copy(dest, src)) | |
| return NULL; | |
| return dest; | |
| } | |
| extern int slurm_data_clone(const data_t *src, data_t **dst_ptr) | |
| { | |
| xassert(dst_ptr); | |
| if (!dst_ptr) | |
| return EINVAL; | |
| if (!src) { | |
| FREE_NULL_DATA(*dst_ptr); | |
| return SLURM_SUCCESS; | |
| } | |
| _check_magic(src); | |
| if (!*dst_ptr) | |
| *dst_ptr = data_new(); | |
| return _copy(*dst_ptr, src); | |
| } | |
| extern data_t *data_move(data_t *dest, data_t *src) | |
| { | |
| xassert(src); | |
| xassert(dest != src); | |
| _check_magic(src); | |
| if (!dest) { | |
| dest = data_new(); | |
| log_flag(DATA, "%s: move data %pD to new %pD", | |
| __func__, src, dest); | |
| } else { | |
| _check_magic(dest); | |
| log_flag(DATA, "%s: move data %pD to existing %pD", | |
| __func__, src, dest); | |
| _release(dest); | |
| } | |
| memmove(&dest->data, &src->data, sizeof(src->data)); | |
| dest->type = src->type; | |
| src->type = TYPE_NULL; | |
| return dest; | |
| } | |
| extern int data_retrieve_dict_path_string(const data_t *data, const char *path, | |
| char **ptr_buffer) | |
| { | |
| const data_t *d = NULL; | |
| int rc; | |
| _check_magic(data); | |
| if (!(d = data_resolve_dict_path_const(data, path))) | |
| return ESLURM_DATA_PATH_NOT_FOUND; | |
| rc = data_get_string_converted(d, ptr_buffer); | |
| if (rc) | |
| log_flag(DATA, "%s: data %pD failed to resolve string at path:%s", | |
| __func__, data, path); | |
| else | |
| log_flag_hex(DATA, *ptr_buffer, strlen(*ptr_buffer), | |
| "%s: data %pD resolved string at path:%s", | |
| __func__, data, path); | |
| return rc; | |
| } | |
| extern int data_retrieve_dict_path_bool(const data_t *data, const char *path, | |
| bool *ptr_buffer) | |
| { | |
| const data_t *d = NULL; | |
| int rc; | |
| _check_magic(data); | |
| if (!(d = data_resolve_dict_path_const(data, path))) | |
| return ESLURM_DATA_PATH_NOT_FOUND; | |
| rc = data_copy_bool_converted(d, ptr_buffer); | |
| log_flag(DATA, "%s: data %pD resolved string at path %s=%s: %s", | |
| __func__, data, path, | |
| (*ptr_buffer ? "true" : "false"), slurm_strerror(rc)); | |
| return rc; | |
| } | |
| extern int data_retrieve_dict_path_int(const data_t *data, const char *path, | |
| int64_t *ptr_buffer) | |
| { | |
| const data_t *d = NULL; | |
| int rc; | |
| _check_magic(data); | |
| if (!(d = data_resolve_dict_path_const(data, path))) | |
| return ESLURM_DATA_PATH_NOT_FOUND; | |
| rc = data_get_int_converted(d, ptr_buffer); | |
| log_flag(DATA, "%s: data %pD resolved string at path %s to %"PRId64": %s", | |
| __func__, data, path, *ptr_buffer, slurm_strerror(rc)); | |
| return rc; | |
| } | |
| static char *_type_to_string(type_t type) | |
| { | |
| return data_type_to_string((int) type); | |
| } | |
| extern char *data_type_to_string(data_type_t type) | |
| { | |
| switch(type) { | |
| case DATA_TYPE_NULL: | |
| return "null"; | |
| case DATA_TYPE_LIST: | |
| return "list"; | |
| case DATA_TYPE_DICT: | |
| return "dictionary"; | |
| case DATA_TYPE_INT_64: | |
| return "64 bit integer"; | |
| case DATA_TYPE_STRING: | |
| return "string"; | |
| case DATA_TYPE_FLOAT: | |
| return "floating point number"; | |
| case DATA_TYPE_BOOL: | |
| return "boolean"; | |
| case DATA_TYPE_NONE: | |
| /* fall through */ | |
| case DATA_TYPE_MAX: | |
| return "INVALID"; | |
| } | |
| for (int i = 0; i < ARRAY_SIZE(type_map); i++) { | |
| if (type_map[i].internal_type == (int) type) | |
| return data_type_to_string(type_map[i].external_type); | |
| } | |
| return "INVALID"; | |
| } | |
| extern const char *data_get_type_string(const data_t *data) | |
| { | |
| if (!data) | |
| return "INVALID"; | |
| for (int i = 0; i < ARRAY_SIZE(type_map); i++) | |
| if (type_map[i].internal_type == data->type) | |
| return data_type_to_string(type_map[i].external_type); | |
| xassert(false); | |
| return "INVALID"; | |
| } |