| /*****************************************************************************\ |
| * 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 lrint */ |
| |
| #include <ctype.h> |
| #include <math.h> |
| |
| #include "src/common/data.h" |
| #include "src/common/list.h" |
| #include "src/common/log.h" |
| #include "src/common/read_config.h" |
| #include "src/common/xassert.h" |
| #include "src/common/xmalloc.h" |
| #include "src/common/xstring.h" |
| |
| #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 mask; |
| } find_dict_match_t; |
| |
| typedef struct { |
| size_t count; |
| 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; |
| |
| 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 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) |
| { |
| _check_data_list_magic(dl); |
| _check_data_list_node_magic(dn); |
| _check_data_list_node_parent(dl, dn); |
| data_list_node_t *prev; |
| |
| log_flag(DATA, "%s: free data-list(0x%"PRIxPTR")[%zu]", |
| __func__, (uintptr_t) dl, dl->count); |
| |
| /* walk list to find new previous */ |
| for (prev = 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; |
| |
| 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; |
| } |
| |
| 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->type) { |
| case TYPE_STRING_INLINE: |
| case TYPE_STRING_PTR: |
| break; |
| case TYPE_BOOL: |
| data_set_string(data, (data->data.bool_u ? "true" : "false")); |
| break; |
| case TYPE_NULL: |
| data_set_string(data, ""); |
| break; |
| case TYPE_FLOAT: |
| { |
| char *str = xstrdup_printf("%lf", data->data.float_u); |
| data_set_string_own(data, str); |
| break; |
| } |
| case TYPE_INT_64: |
| { |
| char *str = xstrdup_printf("%"PRId64, data->data.int_u); |
| 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->type) { |
| case TYPE_STRING_INLINE: |
| case TYPE_STRING_PTR: |
| /* non-empty string but not recognized format */ |
| data_set_bool(data, true); |
| break; |
| case TYPE_BOOL: |
| break; |
| case TYPE_NULL: |
| data_set_bool(data, false); |
| break; |
| case TYPE_FLOAT: |
| data_set_bool(data, data->data.float_u != 0); |
| break; |
| case TYPE_INT_64: |
| data_set_bool(data, data->data.int_u != 0); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static int _convert_data_null(data_t *data) |
| { |
| _check_magic(data); |
| |
| switch (data->type) { |
| case TYPE_STRING_INLINE: |
| case TYPE_STRING_PTR: |
| { |
| 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 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->type) { |
| case TYPE_STRING_INLINE: |
| case TYPE_STRING_PTR: |
| { |
| 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 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->type) { |
| case TYPE_STRING_INLINE: |
| case TYPE_STRING_PTR: |
| { |
| int64_t x; |
| char end; |
| 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')) { |
| if (sscanf(str, "%"SCNx64"%c", &x, &end) == 1) { |
| log_flag_hex(DATA, str, strlen(str), |
| "%s: converted hex number %pD->%"PRId64, |
| __func__, data, x); |
| data_set_int(data, x); |
| return SLURM_SUCCESS; |
| } |
| |
| log_flag_hex(DATA, str, strlen(str), |
| "%s: conversion of hex string %pD to integer failed", |
| __func__, data); |
| return ESLURM_DATA_CONV_FAILED; |
| } |
| |
| if (!force) { |
| for (const char *p = str; *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; |
| } |
| } |
| } |
| |
| if (sscanf(str, "%"SCNd64"%c", &x, &end) == 1) { |
| log_flag_hex(DATA, str, strlen(str), |
| "%s: converted %pD->%"PRId64, |
| __func__, data, x); |
| data_set_int(data, x); |
| return SLURM_SUCCESS; |
| } else { |
| log_flag_hex(DATA, str, strlen(str), |
| "%s: conversion of %pD to integer failed", |
| __func__, data); |
| return ESLURM_DATA_CONV_FAILED; |
| } |
| } |
| case TYPE_FLOAT: |
| if (force) { |
| data_set_int(data, lrint(data_get_float(data))); |
| return SLURM_SUCCESS; |
| } |
| return ESLURM_DATA_CONV_FAILED; |
| case TYPE_INT_64: |
| return SLURM_SUCCESS; |
| case 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: %s->%lf", |
| __func__, data, str, 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->type) { |
| case TYPE_STRING_INLINE: |
| case TYPE_STRING_PTR: |
| return _convert_data_float_from_string(data); |
| case 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 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->type == TYPE_DICT) |
| return DATA_TYPE_DICT; |
| else if ((data->type == 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->type == TYPE_LIST) |
| return DATA_TYPE_LIST; |
| else if ((data->type == 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 type_t match) |
| { |
| convert_args_t args = { |
| .match = match, |
| }; |
| _check_magic(data); |
| |
| if (!data) |
| return 0; |
| |
| switch (data->type) { |
| case TYPE_DICT: |
| (void)data_dict_for_each(data, _convert_dict_entry, &args); |
| break; |
| case TYPE_LIST: |
| (void)data_list_for_each(data, _convert_list_entry, &args); |
| break; |
| default: |
| if (match == (int) data_convert_type(data, (int) 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, (int) 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->mask); |
| |
| 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, bool mask) |
| { |
| find_dict_match_t p = { |
| .mask = mask, |
| .a = a, |
| .b = b, |
| }; |
| |
| 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, bool mask) |
| { |
| 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); |
| |
| if (!ptr_b && mask) |
| /* ignore a if b is NULL when masking */ |
| continue; |
| |
| _check_data_list_node_magic(ptr_b); |
| if (data_check_match(ptr_a->data, ptr_b->data, mask)) { |
| ptr_a = ptr_a->next; |
| ptr_b = ptr_b->next; |
| } else |
| fail = true; |
| } |
| |
| return !fail; |
| } |
| |
| extern bool data_check_match(const data_t *a, const data_t *b, bool mask) |
| { |
| bool rc; |
| |
| if (a == NULL && b == NULL) |
| return true; |
| |
| if (a == NULL || b == NULL) |
| return false; |
| |
| _check_magic(a); |
| _check_magic(b); |
| |
| if (data_get_type(a) != data_get_type(b)) { |
| data_t *conv = data_copy(data_new(), b); |
| |
| 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, mask); |
| 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: |
| if (!(rc = (data_get_float(a) == data_get_float(b))) || |
| !(rc = fuzzy_equal(data_get_float(a), data_get_float(b)))) { |
| if (isnan(data_get_float(a)) == |
| isnan(data_get_float(a))) |
| rc = true; |
| else if (signbit(data_get_float(a)) != |
| signbit(data_get_float(b))) |
| rc = false; |
| else if (isinf(data_get_float(a)) != |
| isinf(data_get_float(b))) |
| rc = false; |
| else |
| rc = false; |
| } |
| |
| log_flag(DATA, "compare: %s(0x%"PRIXPTR")=%e %s %s(0x%"PRIXPTR")=%e", |
| _type_to_string(a->type), (uintptr_t) a, |
| data_get_float(a), (rc ? "=" : "!="), |
| _type_to_string(b->type), (uintptr_t) b, |
| data_get_float(b)); |
| return rc; |
| case TYPE_DICT: |
| rc = _data_match_dict(a, b, mask); |
| 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, mask); |
| 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__); |
| } |
| |
| extern data_t *data_resolve_dict_path(data_t *data, const char *path) |
| { |
| data_t *found = data; |
| char *save_ptr = NULL; |
| char *token = NULL; |
| char *str; |
| char local[DATA_DEFINE_DICT_PATH_BUFFER_SIZE]; |
| size_t len = strlen(path); |
| |
| _check_magic(data); |
| |
| if (!data) |
| return NULL; |
| |
| if (len < sizeof(local)) |
| str = memcpy(local, path, (len + 1)); |
| else |
| str = xstrdup(path); |
| |
| token = strtok_r(str, "/", &save_ptr); |
| while (token && found) { |
| /* walk forward any whitespace */ |
| while (*token && isspace(*token)) |
| token++; |
| |
| /* zero any ending whitespace */ |
| for (int i = strlen(token) - 1; i >= 0; i--) { |
| if (isspace(token[i])) |
| token[i] = '\0'; |
| else |
| break; |
| } |
| |
| if (!found || (found->type != TYPE_DICT)) { |
| found = NULL; |
| break; |
| } |
| |
| if (!(found = data_key_get(found, token))) |
| break; |
| |
| token = strtok_r(NULL, "/", &save_ptr); |
| } |
| |
| if (str != local) |
| xfree(str); |
| |
| if (found) |
| log_flag_hex(DATA, path, strlen(path), |
| "%s: %pD resolved dictionary path to %pD", |
| __func__, data, found); |
| else |
| log_flag_hex(DATA, path, strlen(path), |
| "%s: %pD failed to resolve dictionary path", |
| __func__, data); |
| return found; |
| } |
| |
| extern const data_t *data_resolve_dict_path_const(const data_t *data, |
| const char *path) |
| { |
| const data_t *found = data; |
| char *save_ptr = NULL; |
| char *token = NULL; |
| char *str; |
| |
| _check_magic(data); |
| |
| if (!data) |
| return NULL; |
| |
| str = xstrdup(path); |
| |
| token = strtok_r(str, "/", &save_ptr); |
| while (token && found) { |
| xstrtrim(token); |
| |
| if (!found || (found->type != TYPE_DICT)) { |
| found = NULL; |
| break; |
| } |
| |
| if (!(found = data_key_get_const(found, token))) |
| break; |
| |
| token = strtok_r(NULL, "/", &save_ptr); |
| } |
| xfree(str); |
| |
| if (found) |
| log_flag_hex(DATA, path, strlen(path), |
| "%s: data %pD resolved dictionary path to %pD", |
| __func__, data, found); |
| else |
| log_flag_hex(DATA, path, strlen(path), |
| "%s: data %pD failed to resolve dictionary path", |
| __func__, data); |
| |
| return found; |
| } |
| |
| extern data_t *data_define_dict_path(data_t *data, const char *path) |
| { |
| data_t *found = data; |
| char *save_ptr = NULL; |
| char *token = NULL; |
| char *str; |
| |
| _check_magic(data); |
| |
| if (!data) |
| return NULL; |
| |
| str = xstrdup(path); |
| |
| token = strtok_r(str, "/", &save_ptr); |
| while (token && found) { |
| xstrtrim(token); |
| |
| if (found->type == TYPE_NULL) |
| data_set_dict(found); |
| else if (found->type != TYPE_DICT) { |
| found = NULL; |
| break; |
| } |
| |
| if (!(found = data_key_set(found, token))) |
| break; |
| |
| token = strtok_r(NULL, "/", &save_ptr); |
| } |
| xfree(str); |
| |
| if (found) |
| log_flag_hex(DATA, path, strlen(path), |
| "%s: %pD defined dictionary path to %pD", |
| __func__, data, found); |
| else |
| log_flag_hex(DATA, path, strlen(path), |
| "%s: %pD failed to define dictionary path", |
| __func__, data); |
| |
| return found; |
| } |
| |
| extern data_t *data_copy(data_t *dest, const data_t *src) |
| { |
| if (!src) |
| return NULL; |
| |
| if (!dest) |
| dest = data_new(); |
| |
| _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: |
| return data_set_string(dest, data_get_string(src)); |
| case TYPE_BOOL: |
| return data_set_bool(dest, data_get_bool(src)); |
| case TYPE_INT_64: |
| return data_set_int(dest, data_get_int(src)); |
| case TYPE_FLOAT: |
| return data_set_float(dest, data_get_float(src)); |
| case TYPE_NULL: |
| return data_set_null(dest); |
| 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); |
| data_copy(data_list_append(dest), i->data); |
| i = i->next; |
| } |
| |
| return dest; |
| } |
| 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); |
| data_copy(data_key_set(dest, i->key), i->data); |
| i = i->next; |
| } |
| |
| return dest; |
| } |
| default: |
| fatal_abort("%s: unexpected data type", __func__); |
| return NULL; |
| } |
| } |
| |
| extern data_t *data_move(data_t *dest, data_t *src) |
| { |
| if (!src) |
| return NULL; |
| |
| if (!dest) |
| dest = data_new(); |
| |
| _check_magic(src); |
| _check_magic(dest); |
| |
| log_flag(DATA, "%s: move data %pD to %pD", __func__, src, 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"; |
| } |