| /*****************************************************************************\ |
| * layouts_mgr.c - layouts manager data structures and main functions |
| ***************************************************************************** |
| * Initially written by Francois Chevallier <chevallierfrancois@free.fr> |
| * at Bull for slurm-2.6. |
| * Adapted by Matthieu Hautreux <matthieu.hautreux@cea.fr> for slurm-14.11. |
| * Enhanced by Matthieu Hautreux <matthieu.hautreux@cea.fr> for slurm-15.x. |
| * |
| * 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. |
| \*****************************************************************************/ |
| |
| #include <ctype.h> |
| #include <pthread.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| |
| #include "layouts_mgr.h" |
| |
| #include "slurm/slurm.h" |
| #include "slurm/slurm_errno.h" |
| #include "src/common/entity.h" |
| #include "src/common/layout.h" |
| #include "src/common/hostlist.h" |
| #include "src/common/list.h" |
| #include "src/common/node_conf.h" |
| #include "src/common/pack.h" |
| #include "src/common/plugin.h" |
| #include "src/common/read_config.h" |
| #include "src/common/slurm_protocol_api.h" |
| #include "src/common/strlcpy.h" |
| #include "src/common/timers.h" |
| #include "src/common/xstring.h" |
| #include "src/common/xtree.h" |
| #include "src/common/xmalloc.h" |
| |
| #define PATHLEN 256 |
| |
| /* use to specify which layout callbacks to perform while loading data |
| * from conf files, state files or input buffers */ |
| #define CONF_DONE 0x00000001 |
| #define PARSE_ENTITY 0x00000002 |
| #define UPDATE_DONE 0x00000004 |
| #define PARSE_RELATIONS 0x00000008 |
| |
| /*****************************************************************************\ |
| * STRUCTURES AND TYPES * |
| \*****************************************************************************/ |
| |
| /* |
| * layouts_conf_spec_t - structure used to keep track of layouts conf details |
| */ |
| typedef struct layouts_conf_spec_st { |
| char* whole_name; |
| char* name; |
| char* type; |
| } layouts_conf_spec_t; |
| |
| static void layouts_conf_spec_free(void* x) |
| { |
| layouts_conf_spec_t* spec = (layouts_conf_spec_t*)x; |
| xfree(spec->whole_name); |
| xfree(spec->type); |
| xfree(spec->name); |
| xfree(spec); |
| } |
| |
| /* |
| * layout ops - operations associated to layout plugins |
| * |
| * This struct is populated while opening the plugin and linking the |
| * associated symbols. See layout_syms description for the name of the "public" |
| * symbols associated to this structure fields. |
| * |
| * Notes : the layouts plugins are able to access the entities hashtable in order |
| * to read/create/modify entities as necessary during the load_entities and |
| * build_layout API calls. |
| * |
| */ |
| typedef struct layout_ops_st { |
| layouts_plugin_spec_t* spec; |
| int (*conf_done) (xhash_t* entities, layout_t* layout, |
| s_p_hashtbl_t* tbl); |
| void (*entity_parsing) (entity_t* e, s_p_hashtbl_t* etbl, |
| layout_t* layout); |
| int (*update_done) (layout_t* layout, entity_t** e_array, |
| int e_cnt); |
| } layout_ops_t; |
| |
| /* |
| * layout plugin symbols - must be synchronized with ops structure definition |
| * as previously detailed, that's why though being a global constant, |
| * it is placed in this section. |
| */ |
| const char *layout_syms[] = { |
| "plugin_spec", /* holds constants, definitions, ... */ |
| "layouts_p_conf_done", /* */ |
| "layouts_p_entity_parsing", |
| "layouts_p_update_done", |
| }; |
| |
| /* |
| * layout_plugin_t - it is the structure holding the plugin context of the |
| * associated layout plugin as well as the ptr to the dlsymed calls. |
| * It is used by the layouts manager to operate on the different layouts |
| * loaded during the layouts framework initialization |
| */ |
| typedef struct layout_plugin_st { |
| plugin_context_t* context; |
| layout_t* layout; |
| char* name; |
| layout_ops_t* ops; |
| } layout_plugin_t; |
| |
| static void _layout_plugins_destroy(layout_plugin_t *lp) |
| { |
| plugin_context_destroy(lp->context); |
| /* it might be interesting to also dlclose the ops here */ |
| xfree(lp->name); |
| xfree(lp->ops); |
| } |
| /* |
| * layouts_keydef_t - entities similar keys share a same key definition |
| * in order to avoid loosing too much memory duplicating similar data |
| * like the key str itself and custom destroy/dump functions. |
| * |
| * The layouts manager keeps an hash table of the various keydefs and use |
| * the factorized details while parsing the configuration and creating the |
| * entity_data_t structs associated to the entities. |
| * |
| * Note custom_* functions are used if they are not NULL* and type equals |
| * L_T_CUSTOM |
| */ |
| typedef struct layouts_keydef_st { |
| char* key; /* lower case key prefixed by the |
| "%layout_type%." string */ |
| char* shortkey; /* original key as defined in |
| the layout keys definition */ |
| layouts_keydef_types_t type; |
| uint32_t flags; |
| void (*custom_destroy)(void* value); |
| char* (*custom_dump)(void* value); |
| layout_plugin_t* plugin; |
| char* ref_key; /* lower case reference key prefixed by |
| the "%layout_type%." might be NULL |
| if not defined. */ |
| char* ref_shortkey; /* original ref key as defined in |
| the layout keys definition, |
| might be null too. */ |
| |
| } layouts_keydef_t; |
| |
| /* |
| * layouts_keydef_idfunc - identity function to build an hash table of |
| * layouts_keydef_t |
| */ |
| static void layouts_keydef_idfunc(void* item, const char** key, |
| uint32_t* key_len) |
| { |
| layouts_keydef_t* keydef = (layouts_keydef_t*)item; |
| *key = keydef->key; |
| *key_len = strlen(keydef->key); |
| } |
| |
| /* |
| * layouts_mgr_t - the main structure holding all the layouts, entities and |
| * shared keydefs as well as conf elements and plugins details. |
| */ |
| typedef struct layouts_mgr_st { |
| pthread_mutex_t lock; |
| bool init_done; /* Set if memory allocated for arrays/List */ |
| layout_plugin_t *plugins; |
| uint32_t plugins_count; |
| List layouts_desc; /* list of the layouts requested in conf */ |
| xhash_t *layouts; /* hash tbl of loaded layout structs (by type) */ |
| xhash_t *entities; /* hash tbl of loaded entity structs (by name) */ |
| xhash_t *keydefs; /* info on key types, how to free them etc */ |
| } layouts_mgr_t; |
| |
| /*****************************************************************************\ |
| * GLOBALS * |
| \*****************************************************************************/ |
| |
| /** global structure holding layouts and entities */ |
| static layouts_mgr_t layouts_mgr = {PTHREAD_MUTEX_INITIALIZER, false}; |
| static layouts_mgr_t* mgr = &layouts_mgr; |
| |
| /*****************************************************************************\ |
| * HELPERS * |
| \*****************************************************************************/ |
| |
| /* entities added to the layouts mgr hash table come from the heap, |
| * this function will help to free them while freeing the hash table */ |
| static void _entity_free(void* item) |
| { |
| entity_t* entity = (entity_t*) item; |
| entity_free(entity); |
| xfree(entity); |
| } |
| |
| /* layouts added to the layouts mgr hash table come from the heap, |
| * this function will help to free them while freeing the hash table */ |
| static void _layout_free(void* item) |
| { |
| layout_t* layout = (layout_t*) item; |
| layout_free(layout); |
| xfree(layout); |
| } |
| |
| /* keydef added to the layouts mgr hash table come from the heap, |
| * this function will help to free them while freeing the hash table */ |
| static void _layouts_keydef_free(void* x) |
| { |
| layouts_keydef_t* keydef = (layouts_keydef_t*)x; |
| xfree(keydef->key); |
| xfree(keydef->shortkey); |
| xfree(keydef->ref_key); |
| xfree(keydef->ref_shortkey); |
| xfree(keydef); |
| } |
| |
| /* generic xfree callback */ |
| static void xfree_as_callback(void* p) |
| { |
| xfree(p); |
| } |
| |
| /* safer behavior than plain strncat */ |
| static char* _cat(char* dest, const char* src, size_t n) |
| { |
| size_t len; |
| char* r; |
| if (n == 0) |
| return dest; |
| len = strlen(dest); |
| if (n - len - 1 <= 0) { |
| dest[n - 1] = 0; |
| return dest; |
| } |
| r = strncat(dest, src, n - len - 1); |
| dest[n - 1] = 0; |
| return r; |
| } |
| |
| static char* _trim(char* str) |
| { |
| char* str_modifier; |
| if (!str) |
| return str; |
| while (*str && isspace(*str)) ++str; |
| str_modifier = str + strlen(str) - 1; |
| while (str_modifier >= str && isspace(*str_modifier)) { |
| *str_modifier = '\0'; |
| --str_modifier; |
| } |
| return str; |
| } |
| |
| /* check if str is in strings (null terminated string array) */ |
| /* TODO: replace this with a xhash instead for next modification */ |
| static int _string_in_array(const char* str, const char** strings) |
| { |
| xassert(strings); /* if etypes no specified in plugin, no new entity |
| should be created */ |
| for (; *strings; ++strings) { |
| if (!xstrcmp(str, *strings)) |
| return 1; |
| } |
| return 0; |
| } |
| |
| static void _normalize_keydef_keycore(char* buffer, uint32_t size, |
| const char* key, const char* plugtype, |
| bool cat) |
| { |
| int i; |
| char keytmp[PATHLEN]; |
| |
| for (i = 0; plugtype[i] && i < PATHLEN - 1; ++i) { |
| keytmp[i] = tolower(plugtype[i]); |
| } |
| keytmp[i] = 0; |
| if (cat) { |
| _cat(buffer, keytmp, size); |
| } else { |
| strlcpy(buffer, keytmp, size); |
| } |
| _cat(buffer, ".", size); |
| for (i = 0; key[i] && i < PATHLEN - 1; ++i) { |
| keytmp[i] = tolower(key[i]); |
| } |
| keytmp[i] = 0; |
| _cat(buffer, keytmp, size); |
| } |
| |
| static void _normalize_keydef_key(char* buffer, uint32_t size, |
| const char* key, const char* plugtype) |
| { |
| _normalize_keydef_keycore(buffer, size, key, plugtype, false); |
| } |
| |
| static void _normalize_keydef_mgrkey(char* buffer, uint32_t size, |
| const char* key, const char* plugtype) |
| { |
| strlcpy(buffer, "mgr.", size); |
| _normalize_keydef_keycore(buffer, size, key, plugtype, true); |
| } |
| |
| static void _entity_add_data(entity_t* e, const char* key, void* data) |
| { |
| layouts_keydef_t* hkey = xhash_get_str(mgr->keydefs, key); |
| xassert(hkey); |
| void (*freefunc)(void* p) = xfree_as_callback; |
| if (hkey && hkey->type == L_T_CUSTOM) { |
| freefunc = hkey->custom_destroy; |
| } |
| entity_set_data_ref(e, hkey->key, data, freefunc); |
| } |
| |
| /* |
| * used in both automerge and autoupdate calls when dealing with |
| * advanced operations (SUM,MIN,MAX,AVG,...) while setting new key values |
| */ |
| #define _entity_update_kv_helper(type_t, operator) \ |
| type_t* lvalue = (type_t*) oldvalue; \ |
| type_t* rvalue = (type_t*) value; \ |
| uint32_t* divider; \ |
| switch (operator) { \ |
| case S_P_OPERATOR_SET: \ |
| *lvalue = *rvalue; \ |
| break; \ |
| case S_P_OPERATOR_ADD: \ |
| *lvalue += *rvalue; \ |
| break; \ |
| case S_P_OPERATOR_SUB: \ |
| *lvalue -= *rvalue; \ |
| break; \ |
| case S_P_OPERATOR_MUL: \ |
| *lvalue *= *rvalue; \ |
| break; \ |
| case S_P_OPERATOR_DIV: \ |
| if (*rvalue != (type_t) 0) \ |
| *lvalue /= *rvalue; \ |
| else { \ |
| error("layouts: entity_update: " \ |
| "key=%s val=0 operator=" \ |
| "DIV !! skipping !!", \ |
| keydef->key); \ |
| } \ |
| break; \ |
| case S_P_OPERATOR_AVG: \ |
| divider = (uint32_t*) value; \ |
| if (*divider != (uint32_t) 0) \ |
| *lvalue /= (type_t) *divider; \ |
| else { \ |
| error("layouts: entity_update: " \ |
| "key=%s val=0 operator=" \ |
| "AVG !! skipping !!", \ |
| keydef->key); \ |
| } \ |
| break; \ |
| case S_P_OPERATOR_SET_IF_MIN: \ |
| if (*rvalue < *lvalue) \ |
| *lvalue = *rvalue; \ |
| break; \ |
| case S_P_OPERATOR_SET_IF_MAX: \ |
| if (*rvalue > *lvalue) \ |
| *lvalue = *rvalue; \ |
| break; \ |
| default: \ |
| break; \ |
| } |
| |
| static int _layouts_autoupdate_layout(layout_t* layout); |
| static int _layouts_autoupdate_layout_if_allowed(layout_t* layout); |
| |
| /*****************************************************************************\ |
| * LAYOUTS INTERNAL LOCKLESS API * |
| \*****************************************************************************/ |
| |
| layouts_keydef_t* _layouts_entity_get_kv_keydef(layout_t* l, entity_t* e, |
| char* key) |
| { |
| char keytmp[PATHLEN]; |
| if (l == NULL || e == NULL || key == NULL) |
| return NULL; |
| _normalize_keydef_key(keytmp, PATHLEN, key, l->type); |
| return xhash_get_str(mgr->keydefs, keytmp); |
| } |
| |
| int _layouts_entity_get_kv_type(layout_t* l, entity_t* e, char* key) |
| { |
| layouts_keydef_t* keydef; |
| keydef = _layouts_entity_get_kv_keydef(l, e, key); |
| if (keydef != NULL) { |
| return keydef->type; |
| } |
| return SLURM_ERROR; |
| } |
| |
| int _layouts_entity_get_kv_flags(layout_t* l, entity_t* e, char* key) |
| { |
| layouts_keydef_t* keydef; |
| keydef = _layouts_entity_get_kv_keydef(l, e, key); |
| if (keydef != NULL) { |
| return keydef->flags; |
| } |
| return SLURM_ERROR; |
| } |
| |
| int _layouts_entity_get_kv_size(layout_t* l, entity_t* e, char* key, size_t *size) |
| { |
| layouts_keydef_t* keydef; |
| keydef = _layouts_entity_get_kv_keydef(l, e, key); |
| if (keydef != NULL) { |
| switch(keydef->type) { |
| case L_T_ERROR: |
| return SLURM_ERROR; |
| case L_T_STRING: |
| *size = sizeof(void*); |
| break; |
| case L_T_CUSTOM: |
| *size = sizeof(void*); |
| break; |
| case L_T_LONG: |
| *size = sizeof(long); |
| break; |
| case L_T_UINT16: |
| *size = sizeof(uint16_t); |
| break; |
| case L_T_UINT32: |
| *size = sizeof(uint32_t); |
| break; |
| case L_T_BOOLEAN: |
| *size = sizeof(bool); |
| break; |
| case L_T_FLOAT: |
| *size = sizeof(float); |
| break; |
| case L_T_DOUBLE: |
| *size = sizeof(double); |
| break; |
| case L_T_LONG_DOUBLE: |
| *size = sizeof(long double); |
| break; |
| } |
| } else |
| return SLURM_ERROR; |
| return SLURM_SUCCESS; |
| } |
| |
| bool _layouts_entity_check_kv_keytype(layout_t* l, entity_t* e, char* key, |
| layouts_keydef_types_t key_type) |
| { |
| layouts_keydef_types_t real_type; |
| if (l == NULL || e == NULL || key == NULL) |
| return SLURM_ERROR; |
| if (key_type) { |
| real_type = _layouts_entity_get_kv_type(l, e, key); |
| return (real_type == key_type); |
| } |
| /* no key type provided, consider that as a no-check request */ |
| return true; |
| } |
| |
| int _layouts_entity_push_kv(layout_t* l, entity_t* e, char* key) |
| { |
| /* a more advanced implementation should only pull what is necessary |
| * instead of forcing a full autoupdate */ |
| return _layouts_autoupdate_layout_if_allowed(l); |
| } |
| |
| int _layouts_entity_pull_kv(layout_t* l, entity_t* e, char* key) |
| { |
| /* a more advanced implementation should only pull what is necessary |
| * instead of forcing a full autoupdate */ |
| return _layouts_autoupdate_layout_if_allowed(l); |
| } |
| |
| int _layouts_entity_set_kv(layout_t* l, entity_t* e, char* key, void* value, |
| layouts_keydef_types_t key_type) |
| { |
| void* data; |
| size_t size; |
| layouts_keydef_types_t real_type; |
| char key_keydef[PATHLEN]; |
| |
| if (l == NULL || e == NULL || key == NULL || value == NULL) |
| return SLURM_ERROR; |
| |
| real_type = _layouts_entity_get_kv_type(l, e, key); |
| if (key_type > 0 && real_type != key_type) |
| return SLURM_ERROR; |
| |
| _normalize_keydef_key(key_keydef, PATHLEN, key, l->type); |
| |
| switch(real_type) { |
| case L_T_ERROR: |
| return SLURM_ERROR; |
| case L_T_STRING: |
| data = xstrdup(value); |
| return entity_set_data_ref(e, key_keydef, data, |
| xfree_as_callback); |
| case L_T_CUSTOM: |
| /* TBD : add a custom_set call */ |
| value = NULL; |
| return SLURM_ERROR; |
| case L_T_LONG: |
| size = sizeof(long); |
| break; |
| case L_T_UINT16: |
| size = sizeof(uint16_t); |
| break; |
| case L_T_UINT32: |
| size = sizeof(uint32_t); |
| break; |
| case L_T_BOOLEAN: |
| size = sizeof(bool); |
| break; |
| case L_T_FLOAT: |
| size = sizeof(float); |
| break; |
| case L_T_DOUBLE: |
| size = sizeof(double); |
| break; |
| case L_T_LONG_DOUBLE: |
| size = sizeof(long double); |
| break; |
| default: |
| value = NULL; |
| return SLURM_ERROR; |
| } |
| return entity_set_data(e, key_keydef, value, size); |
| } |
| |
| int _layouts_entity_set_kv_ref(layout_t* l, entity_t* e, char* key, void* value, |
| layouts_keydef_types_t key_type) |
| { |
| int rc = SLURM_ERROR; |
| char key_keydef[PATHLEN]; |
| |
| if (l == NULL || e == NULL || key == NULL || value == NULL) |
| return rc; |
| |
| if (!_layouts_entity_check_kv_keytype(l, e, key, key_type)) |
| return rc; |
| |
| _normalize_keydef_key(key_keydef, PATHLEN, key, l->type); |
| return entity_set_data_ref(e, key_keydef, value, xfree_as_callback); |
| } |
| |
| int _layouts_entity_setpush_kv(layout_t* l, entity_t* e, char* key, void* value, |
| layouts_keydef_types_t key_type) |
| { |
| int rc = SLURM_ERROR; |
| if (_layouts_entity_set_kv(l, e, key, value, key_type) == SLURM_SUCCESS) |
| rc = _layouts_entity_push_kv(l, e, key); |
| return rc; |
| } |
| |
| int _layouts_entity_setpush_kv_ref(layout_t* l, entity_t* e, char* key, |
| void* value, layouts_keydef_types_t key_type) |
| { |
| int rc = SLURM_ERROR; |
| if (_layouts_entity_set_kv_ref(l, e, key, value, key_type) == |
| SLURM_SUCCESS) |
| rc = _layouts_entity_push_kv(l, e, key); |
| return rc; |
| } |
| |
| int _layouts_entity_get_kv(layout_t* l, entity_t* e, char* key, void* value, |
| layouts_keydef_types_t key_type) |
| { |
| void* data; |
| size_t size; |
| layouts_keydef_types_t real_type; |
| char key_keydef[PATHLEN]; |
| char ** pstr; |
| |
| if (l == NULL || e == NULL || key == NULL || value == NULL) |
| return SLURM_ERROR; |
| |
| real_type = _layouts_entity_get_kv_type(l, e, key); |
| if (key_type > 0 && real_type != key_type) |
| return SLURM_ERROR; |
| |
| _normalize_keydef_key(key_keydef, PATHLEN, key, l->type); |
| |
| data = entity_get_data_ref(e, key_keydef); |
| if (data == NULL) { |
| return SLURM_ERROR; |
| } |
| |
| switch(real_type) { |
| case L_T_STRING: |
| pstr = (char**) value; |
| *pstr = xstrdup(data); |
| return SLURM_SUCCESS; |
| case L_T_CUSTOM: |
| /* TBD : add a custom_get call */ |
| pstr = (char**) value; |
| *pstr = NULL; |
| return SLURM_ERROR; |
| case L_T_LONG: |
| size = sizeof(long); |
| break; |
| case L_T_UINT16: |
| size = sizeof(uint16_t); |
| break; |
| case L_T_UINT32: |
| size = sizeof(uint32_t); |
| break; |
| case L_T_BOOLEAN: |
| size = sizeof(bool); |
| break; |
| case L_T_FLOAT: |
| size = sizeof(float); |
| break; |
| case L_T_DOUBLE: |
| size = sizeof(double); |
| break; |
| case L_T_LONG_DOUBLE: |
| size = sizeof(long double); |
| break; |
| case L_T_ERROR: |
| default: |
| return SLURM_ERROR; |
| } |
| memcpy(value, data, size); |
| return SLURM_SUCCESS; |
| } |
| |
| int _layouts_entity_get_mkv(layout_t* l, entity_t* e, char* keys, void* value, |
| size_t length, layouts_keydef_types_t key_type) |
| { |
| char *key = NULL; |
| hostlist_t kl; |
| size_t processed = 0; |
| size_t elt_size = sizeof(void*);; |
| int rc = 0; |
| |
| /* expand in order the requested keys (in hostlist format) |
| * and iterate over each one of them, collecting the different |
| * values into the provided buffer. |
| * if no more space is available in the buffer, then just count |
| * the missing elements for the exit code. |
| * the first error encountered fakes a full buffer to just add |
| * the remaining keys to the missing elements count before |
| * exiting. */ |
| kl = hostlist_create(keys); |
| while ((key = hostlist_shift(kl))) { |
| if (processed >= length) { |
| rc++; |
| } else if (_layouts_entity_get_kv_size(l, e, key, &elt_size) || |
| (processed + elt_size) > length || |
| _layouts_entity_get_kv(l, e, key, value, key_type)) { |
| rc++; |
| processed = length; |
| } else { |
| value += elt_size; |
| processed += elt_size; |
| } |
| free(key); |
| } |
| hostlist_destroy(kl); |
| |
| return rc; |
| } |
| |
| int _layouts_entity_get_kv_ref(layout_t* l, entity_t* e, |
| char* key, void** value, |
| layouts_keydef_types_t key_type) |
| { |
| int rc = SLURM_ERROR; |
| char key_keydef[PATHLEN]; |
| void* data; |
| |
| if (l == NULL || e == NULL || key == NULL || value == NULL) |
| return rc; |
| |
| if (!_layouts_entity_check_kv_keytype(l, e, key, key_type)) |
| return rc; |
| |
| _normalize_keydef_key(key_keydef, PATHLEN, key, l->type); |
| data = entity_get_data_ref(e, key_keydef); |
| if (data != NULL) { |
| *value = data; |
| rc = SLURM_SUCCESS; |
| } |
| return rc; |
| } |
| |
| int _layouts_entity_get_mkv_ref(layout_t* l, entity_t* e, char* keys, |
| void* value, size_t length, |
| layouts_keydef_types_t key_type) |
| { |
| char *key = NULL; |
| hostlist_t kl; |
| size_t processed = 0; |
| size_t elt_size = sizeof(void*); |
| int rc = 0; |
| |
| /* expand in order the requested keys (in hostlist format) |
| * and iterate over each one of them, collecting the different |
| * references into the provided buffer. |
| * if no more space is available in the buffer, then just count |
| * the missing elements for the exit code. |
| * the first error encountered fakes a full buffer to just add |
| * the remaining keys to the missing elements count before |
| * exiting. */ |
| kl = hostlist_create(keys); |
| while ((key = hostlist_shift(kl))) { |
| if (processed >= length) { |
| rc++; |
| } else if (_layouts_entity_get_kv_ref(l, e, key, value, key_type)) { |
| rc++; |
| processed = length; |
| } else { |
| value += elt_size; |
| processed += elt_size; |
| } |
| free(key); |
| } |
| hostlist_destroy(kl); |
| |
| return rc; |
| } |
| |
| int _layouts_entity_pullget_kv(layout_t* l, entity_t* e, char* key, void* value, |
| layouts_keydef_types_t key_type) |
| { |
| int rc = SLURM_ERROR; |
| if (!_layouts_entity_check_kv_keytype(l, e, key, key_type)) |
| return rc; |
| if (_layouts_entity_pull_kv(l, e, key) == SLURM_SUCCESS) |
| rc = _layouts_entity_get_kv(l, e, key, value, key_type); |
| return rc; |
| } |
| |
| int _layouts_entity_pullget_kv_ref(layout_t* l, entity_t* e, |
| char* key, void** value, |
| layouts_keydef_types_t key_type) |
| { |
| int rc = SLURM_ERROR; |
| if (!_layouts_entity_check_kv_keytype(l, e, key, key_type)) |
| return rc; |
| if (_layouts_entity_pull_kv(l, e, key) == SLURM_SUCCESS) |
| rc = _layouts_entity_get_kv_ref(l, e, key, value, key_type); |
| return rc; |
| } |
| |
| /*****************************************************************************\ |
| * MANAGER INIT * |
| \*****************************************************************************/ |
| |
| static void _layouts_init_keydef(xhash_t* keydefs, |
| const layouts_keyspec_t* plugin_keyspec, |
| layout_plugin_t* plugin) |
| { |
| char keytmp[PATHLEN]; |
| |
| const layouts_keyspec_t* current; |
| layouts_keydef_t* nkeydef; |
| |
| /* A layout plugin may have no data to store to entities but still |
| * being valid. */ |
| if (!plugin_keyspec) |
| return; |
| |
| /* iterate over the keys of the plugin */ |
| for (current = plugin_keyspec; current->key; ++current) { |
| /* if not end of list, a keyspec key is mandatory */ |
| _normalize_keydef_key(keytmp, PATHLEN, current->key, |
| plugin->layout->type); |
| xassert(xhash_get_str(keydefs, keytmp) == NULL); |
| nkeydef = (layouts_keydef_t*) |
| xmalloc(sizeof(layouts_keydef_t)); |
| nkeydef->key = xstrdup(keytmp); |
| nkeydef->shortkey = xstrdup(current->key); |
| nkeydef->type = current->type; |
| nkeydef->flags = current->flags; |
| nkeydef->custom_destroy = current->custom_destroy; |
| nkeydef->custom_dump = current->custom_dump; |
| nkeydef->plugin = plugin; |
| if (current->ref_key != NULL) { |
| _normalize_keydef_key(keytmp, PATHLEN, current->ref_key, |
| plugin->layout->type); |
| nkeydef->ref_key = xstrdup(keytmp); |
| nkeydef->ref_shortkey = xstrdup(current->ref_key); |
| } else { |
| nkeydef->ref_key = NULL; |
| nkeydef->ref_shortkey = NULL; |
| } |
| xhash_add(keydefs, nkeydef); |
| } |
| |
| /* then add keys managed by the layouts_mgr directly */ |
| switch(plugin->layout->struct_type) { |
| case LAYOUT_STRUCT_TREE: |
| _normalize_keydef_mgrkey(keytmp, PATHLEN, "enclosed", |
| plugin->layout->type); |
| xassert(xhash_get_str(keydefs, keytmp) == NULL); |
| nkeydef = (layouts_keydef_t*) |
| xmalloc(sizeof(layouts_keydef_t)); |
| nkeydef->key = xstrdup(keytmp); |
| nkeydef->shortkey = xstrdup("Enclosed"); |
| nkeydef->type = L_T_STRING; |
| nkeydef->plugin = plugin; |
| xhash_add(keydefs, nkeydef); |
| break; |
| } |
| } |
| |
| static void _debug_output_keydefs (void* item, void* args) |
| { |
| layouts_keydef_t* keydef = (layouts_keydef_t*) item; |
| debug3("layouts/keydefs: loaded: %s flags=0x%08lx refkey=%s", |
| keydef->key, (long unsigned int) keydef->flags, |
| (keydef->ref_key == NULL) ? "-":keydef->ref_key); |
| |
| } |
| |
| static int _layouts_init_layouts_walk_helper(void* x, void* arg) |
| { |
| layouts_conf_spec_t* spec = (layouts_conf_spec_t*)x; |
| int* i = (int*)arg; |
| layout_plugin_t* plugin = &mgr->plugins[*i]; |
| const char* plugin_type = "layouts"; |
| char plugin_name[PATHLEN]; |
| void* inserted_item; |
| plugin_context_t* plugin_context; |
| |
| snprintf(plugin_name, PATHLEN, |
| "layouts/%s_%s", spec->type, spec->name); |
| plugin->ops = (layout_ops_t*)xmalloc(sizeof(layout_ops_t)); |
| debug2("layouts: loading %s...", spec->whole_name); |
| plugin->context = plugin_context = plugin_context_create( |
| plugin_type, |
| plugin_name, |
| (void**)plugin->ops, |
| layout_syms, |
| sizeof(layout_syms)); |
| if (!plugin_context) { |
| error("layouts: error loading %s.", plugin_name); |
| return SLURM_ERROR; |
| } |
| if (!plugin->ops->spec) { |
| error("layouts: plugin_spec must be valid (%s plugin).", |
| plugin_name); |
| return SLURM_ERROR; |
| |
| } |
| plugin->name = xstrdup(spec->whole_name); |
| plugin->layout = (layout_t*)xmalloc(sizeof(layout_t)); |
| layout_init(plugin->layout, spec->name, spec->type, 0, |
| plugin->ops->spec->struct_type); |
| if ((inserted_item = xhash_add(mgr->layouts, plugin->layout))) |
| xassert(inserted_item == plugin->layout); |
| _layouts_init_keydef(mgr->keydefs, |
| plugin->ops->spec->keyspec, |
| plugin); |
| xhash_walk(mgr->keydefs, _debug_output_keydefs, NULL); |
| ++*i; |
| return SLURM_SUCCESS; |
| } |
| |
| static void _layouts_mgr_parse_global_conf(layouts_mgr_t* mgr) |
| { |
| char* layouts; |
| char* parser; |
| char* saveptr = NULL; |
| char* slash; |
| layouts_conf_spec_t* nspec; |
| |
| mgr->layouts_desc = list_create(layouts_conf_spec_free); |
| layouts = slurm_get_layouts(); |
| parser = strtok_r(layouts, ",", &saveptr); |
| while (parser) { |
| nspec = (layouts_conf_spec_t*)xmalloc( |
| sizeof(layouts_conf_spec_t)); |
| nspec->whole_name = xstrdup(_trim(parser)); |
| slash = strchr(parser, '/'); |
| if (slash) { |
| *slash = 0; |
| nspec->type = xstrdup(_trim(parser)); |
| nspec->name = xstrdup(_trim(slash+1)); |
| } else { |
| nspec->type = xstrdup(_trim(parser)); |
| nspec->name = xstrdup("default"); |
| } |
| list_append(mgr->layouts_desc, nspec); |
| parser = strtok_r(NULL, ",", &saveptr); |
| } |
| xfree(layouts); |
| } |
| |
| static void _layouts_mgr_free(layouts_mgr_t* mgr) |
| { |
| /* free the configuration details */ |
| FREE_NULL_LIST(mgr->layouts_desc); |
| |
| /* FIXME: can we do a faster free here? since each node removal will |
| * modify either the entities or layouts for back (or forward) |
| * references. */ |
| xhash_free(mgr->layouts); |
| xhash_free(mgr->entities); |
| xhash_free(mgr->keydefs); |
| mgr->init_done = false; |
| } |
| |
| static void _layouts_mgr_init(layouts_mgr_t* mgr) |
| { |
| if (mgr->init_done) |
| _layouts_mgr_free(mgr); |
| mgr->init_done = true; |
| _layouts_mgr_parse_global_conf(mgr); |
| mgr->layouts = xhash_init(layout_hashable_identify_by_type, |
| _layout_free); |
| mgr->entities = xhash_init(entity_hashable_identify, |
| _entity_free); |
| mgr->keydefs = xhash_init(layouts_keydef_idfunc, |
| _layouts_keydef_free); |
| } |
| |
| /*****************************************************************************\ |
| * CONFIGURATION * |
| \*****************************************************************************/ |
| |
| static char* _conf_get_filename(const char* type) |
| { |
| char path[PATHLEN]; |
| char* final_path; |
| strlcpy(path, "layouts.d/", PATHLEN); |
| _cat(path, type, PATHLEN); |
| _cat(path, ".conf", PATHLEN); |
| final_path = get_extra_conf_path(path); |
| return final_path; |
| } |
| |
| static char* _state_get_filename(const char* type) |
| { |
| return xstrdup_printf("%s/layouts_state_%s", |
| slurmctld_conf.state_save_location, |
| type); |
| } |
| |
| static s_p_hashtbl_t* _conf_make_hashtbl(int struct_type, |
| const s_p_options_t* layout_options) |
| { |
| s_p_hashtbl_t* tbl = NULL; |
| s_p_hashtbl_t* tbl_relational = NULL; |
| s_p_hashtbl_t* tbl_layout = NULL; |
| s_p_options_t* relational_options = NULL; |
| |
| /* generic line option */ |
| static s_p_options_t global_options_entity[] = { |
| {"Entity", S_P_STRING}, |
| {"Type", S_P_STRING}, |
| {NULL} |
| }; |
| static s_p_options_t global_options[] = { |
| {"Priority", S_P_UINT32}, |
| {"Entity", S_P_EXPLINE, NULL, NULL, global_options_entity}, |
| {NULL} |
| }; |
| |
| /* available for constructing a tree */ |
| static s_p_options_t tree_options_entity[] = { |
| {"Enclosed", S_P_STRING}, |
| {NULL} |
| }; |
| static s_p_options_t tree_options[] = { |
| {"Root", S_P_STRING}, |
| {"Entity", S_P_EXPLINE, NULL, NULL, tree_options_entity}, |
| {NULL} |
| }; |
| |
| xassert(layout_options); |
| |
| switch(struct_type) { |
| case LAYOUT_STRUCT_TREE: |
| relational_options = tree_options; |
| break; |
| default: |
| fatal("layouts: does not know what relation structure to " |
| "use for type %d", struct_type); |
| } |
| |
| tbl = s_p_hashtbl_create(global_options); |
| tbl_relational = s_p_hashtbl_create(relational_options); |
| tbl_layout = s_p_hashtbl_create(layout_options); |
| |
| s_p_hashtbl_merge_keys(tbl, tbl_relational); |
| s_p_hashtbl_merge_keys(tbl, tbl_layout); |
| |
| s_p_hashtbl_destroy(tbl_relational); |
| s_p_hashtbl_destroy(tbl_layout); |
| |
| return tbl; |
| } |
| |
| #define _layouts_load_merge(type_t, s_p_get_type) { \ |
| type_t rvalue; \ |
| type_t* value = &rvalue; \ |
| type_t* oldvalue; \ |
| slurm_parser_operator_t operator = S_P_OPERATOR_SET; \ |
| if (!s_p_get_type(&rvalue, option_key, etbl)) { \ |
| /* no value to merge/create */ \ |
| continue; \ |
| } \ |
| s_p_get_operator(&operator, option_key, etbl); \ |
| oldvalue = (type_t*)entity_get_data_ref(e, key_keydef); \ |
| if (oldvalue) { \ |
| _entity_update_kv_helper(type_t, operator); \ |
| } else { \ |
| type_t* newalloc = (type_t*) \ |
| xmalloc(sizeof(type_t)); \ |
| *newalloc = *value; \ |
| _entity_add_data(e, key_keydef, newalloc); \ |
| } \ |
| } \ |
| |
| #define _layouts_merge_check(type1, type2) \ |
| (entity_option->type == type1 && keydef->type == type2) |
| |
| static void _layouts_load_automerge(layout_plugin_t* plugin, entity_t* e, |
| s_p_hashtbl_t* etbl, uint32_t flags) |
| { |
| const s_p_options_t* layout_option; |
| const s_p_options_t* entity_option; |
| layouts_keydef_t* keydef; |
| char key_keydef[PATHLEN]; |
| char* option_key; |
| |
| for (layout_option = plugin->ops->spec->options; |
| layout_option && xstrcasecmp("Entity", layout_option->key); |
| ++layout_option); |
| xassert(layout_option); |
| |
| for (entity_option = layout_option->line_options; |
| entity_option->key; |
| ++entity_option) { |
| option_key = entity_option->key; |
| _normalize_keydef_key(key_keydef, PATHLEN, option_key, |
| plugin->layout->type); |
| keydef = xhash_get_str(mgr->keydefs, key_keydef); |
| if (!keydef) { |
| /* key is not meant to be automatically handled, |
| * ignore it for this function */ |
| continue; |
| } |
| /* do not perform automerge on updates for read-only keys */ |
| if (flags & UPDATE_DONE && |
| keydef->flags & KEYSPEC_RDONLY) { |
| debug4("layouts: do not try to merge RDONLY key '%s'", |
| keydef->key); |
| continue; |
| } |
| if (_layouts_merge_check(S_P_LONG, L_T_LONG)) { |
| _layouts_load_merge(long, s_p_get_long); |
| } else if (_layouts_merge_check(S_P_UINT16, L_T_UINT16)) { |
| _layouts_load_merge(uint16_t, s_p_get_uint16); |
| } else if (_layouts_merge_check(S_P_UINT32, L_T_UINT32)) { |
| _layouts_load_merge(uint32_t, s_p_get_uint32); |
| } else if (_layouts_merge_check(S_P_BOOLEAN, L_T_BOOLEAN)) { |
| bool newvalue; |
| if (s_p_get_boolean(&newvalue, option_key, etbl)) { |
| bool *newalloc = xmalloc(sizeof(bool)); |
| *newalloc = newvalue; |
| _entity_add_data(e, key_keydef, newalloc); |
| } |
| } else if (_layouts_merge_check(S_P_LONG, L_T_LONG)) { |
| _layouts_load_merge(long, s_p_get_long); |
| } else if (_layouts_merge_check(S_P_FLOAT, L_T_FLOAT)) { |
| _layouts_load_merge(float, s_p_get_float); |
| } else if (_layouts_merge_check(S_P_DOUBLE, L_T_DOUBLE)) { |
| _layouts_load_merge(double, s_p_get_double); |
| } else if (_layouts_merge_check(S_P_LONG_DOUBLE, |
| L_T_LONG_DOUBLE)) { |
| _layouts_load_merge(long double, s_p_get_long_double); |
| } else if (_layouts_merge_check(S_P_STRING, L_T_STRING)) { |
| char* newvalue; |
| if (s_p_get_string(&newvalue, option_key, etbl)) { |
| _entity_add_data(e, key_keydef, newvalue); |
| } |
| } |
| } |
| } |
| |
| /* extract Enlosed= attributes providing the relational structures info */ |
| static void _layouts_parse_relations(layout_plugin_t* plugin, entity_t* e, |
| s_p_hashtbl_t* entity_tbl) |
| { |
| char* e_enclosed; |
| char* e_already_enclosed; |
| char* e_new_enclosed; |
| char key[PATHLEN]; |
| switch(plugin->layout->struct_type) { |
| case LAYOUT_STRUCT_TREE: |
| if (s_p_get_string(&e_enclosed, "Enclosed", entity_tbl)) { |
| _normalize_keydef_mgrkey(key, PATHLEN, "enclosed", |
| plugin->layout->type); |
| e_already_enclosed = (char*) |
| entity_get_data_ref(e, key); |
| if (e_already_enclosed) { |
| e_new_enclosed = (char*) xmalloc( |
| strlen(e_already_enclosed) + |
| strlen(e_enclosed) + 2); |
| strcat(e_new_enclosed, e_already_enclosed); |
| strcat(e_new_enclosed, ","); |
| strcat(e_new_enclosed, e_enclosed); |
| xfree(e_enclosed); |
| e_enclosed = e_new_enclosed; |
| } |
| _entity_add_data(e, key, e_enclosed); |
| } |
| break; |
| } |
| } |
| |
| static int _layouts_read_config_post(layout_plugin_t* plugin, |
| s_p_hashtbl_t* tbl) |
| { |
| char* root_nodename; |
| entity_t* e; |
| entity_node_t* enode; |
| xtree_node_t* root_node; |
| xtree_t* tree; |
| switch(plugin->layout->struct_type) { |
| case LAYOUT_STRUCT_TREE: |
| tree = layout_get_tree(plugin->layout); |
| xassert(tree); |
| if (!s_p_get_string(&root_nodename, "Root", tbl)) { |
| error("layouts: unable to construct the layout tree, " |
| "no root node specified"); |
| xfree(root_nodename); |
| return SLURM_ERROR; |
| } |
| e = xhash_get_str(mgr->entities, _trim(root_nodename)); |
| if (!e) { |
| error("layouts: unable to find specified root " |
| "entity `%s'", _trim(root_nodename)); |
| xfree(root_nodename); |
| return SLURM_ERROR; |
| } |
| xfree(root_nodename); |
| |
| if (!(enode = entity_add_node(e, plugin->layout))) |
| xassert(enode); |
| if (!(root_node = xtree_add_child( |
| tree, NULL, enode, XTREE_APPEND))) |
| xassert(root_node); |
| enode->node = (void*) root_node; |
| break; |
| } |
| return SLURM_SUCCESS; |
| } |
| |
| /* |
| * _layouts_load_config_common - called by layouts_read_config, |
| * layouts_read_state or layouts_update_config with either a |
| * filename or a buffer as well as a flag to indicate if it |
| * is a full load or not (state save only) |
| */ |
| static int _layouts_load_config_common(layout_plugin_t* plugin, |
| char* filename, Buf buffer, |
| uint32_t flags) |
| { |
| s_p_hashtbl_t* tbl = NULL; |
| s_p_hashtbl_t** entities_tbl = NULL; |
| s_p_hashtbl_t* entity_tbl = NULL; |
| int entities_tbl_count = 0, i; |
| entity_t** updated_entities = NULL; |
| int rc = SLURM_SUCCESS; |
| |
| uint32_t l_priority; |
| |
| entity_t* e; |
| char* e_name = NULL; |
| char* e_type = NULL; |
| |
| if (!plugin->ops->spec->options) { |
| /* no option in this layout plugin, nothing to parse */ |
| return SLURM_SUCCESS; |
| } |
| |
| tbl = _conf_make_hashtbl(plugin->layout->struct_type, |
| plugin->ops->spec->options); |
| if (filename) { |
| if (s_p_parse_file(tbl, NULL, filename, false) == SLURM_ERROR) { |
| info("layouts: something went wrong when opening/reading " |
| "'%s': %m", filename); |
| rc = SLURM_ERROR; |
| goto cleanup; |
| } |
| debug3("layouts: configuration file '%s' is loaded", filename); |
| } else if (buffer) { |
| if (s_p_parse_buffer(tbl, NULL, buffer, false) == SLURM_ERROR) { |
| error("layouts: something went wrong when parsing " |
| "buffer : %m"); |
| rc = SLURM_ERROR; |
| goto cleanup; |
| } |
| debug3("layouts: buffer loaded"); |
| } else { |
| error("layouts: invalid usage of _layouts_load_config_common"); |
| rc = SLURM_ERROR; |
| goto cleanup; |
| } |
| |
| if (s_p_get_uint32(&l_priority, "Priority", tbl)) { |
| plugin->layout->priority = l_priority; |
| } |
| |
| /* get the config hash tables of the defined entities */ |
| if (!s_p_get_expline(&entities_tbl, &entities_tbl_count, |
| "Entity", tbl)) { |
| error("layouts: no valid Entity found, can not append any " |
| "information nor construct relations for %s/%s", |
| plugin->layout->type, plugin->layout->name); |
| rc = SLURM_ERROR; |
| goto cleanup; |
| } |
| |
| /* stage 0: xmalloc an array of entity_t* to save the updated entity_t |
| * and give their references in the update_done layout callback */ |
| updated_entities = (entity_t**) |
| xmalloc(entities_tbl_count*sizeof(entity_t*)); |
| |
| /* stage 1: create the described entities or update them */ |
| for (i = 0; i < entities_tbl_count; ++i) { |
| updated_entities[i] = NULL; |
| entity_tbl = entities_tbl[i]; |
| xfree(e_name); |
| xfree(e_type); |
| if (!s_p_get_string(&e_name, "Entity", entity_tbl)) { |
| info("layouts: no name associated to entity[%d], " |
| "skipping...", i); |
| rc = SLURM_ERROR; |
| continue; |
| } |
| |
| /* look for the entity in the entities hash table*/ |
| e = xhash_get_str(mgr->entities, e_name); |
| if (!e) { |
| /* if the entity does not already exists, create it */ |
| if (!s_p_get_string(&e_type, "Type", entity_tbl)) { |
| info("layouts: entity '%s' does not already " |
| "exists and no type was specified, " |
| "skipping", e_name); |
| rc = SLURM_ERROR; |
| continue; |
| } |
| if (!_string_in_array(e_type, |
| plugin->ops->spec->etypes)) { |
| info("layouts: entity '%s' type (%s) is " |
| "invalid, skipping", e_name, e_type); |
| rc = SLURM_ERROR; |
| continue; |
| } |
| |
| e = (entity_t*)xmalloc(sizeof(entity_t)); |
| entity_init(e, e_name, e_type); |
| xhash_add(mgr->entities, e); |
| |
| } else if (s_p_get_string(&e_type, "Type", entity_tbl)) { |
| /* if defined, check that the type is consistent */ |
| if (!_string_in_array(e_type, |
| plugin->ops->spec->etypes)) { |
| info("layouts: entity '%s' type (%s) is " |
| "invalid, skipping", e_name, e_type); |
| rc = SLURM_ERROR; |
| continue; |
| } |
| if (!e->type || xstrcmp(e_type, e->type)) { |
| info("layouts: entity '%s' type (%s) differs " |
| "from already registered entity type (%s)" |
| " skipping", e_name, e_type, e->type); |
| rc = SLURM_ERROR; |
| continue; |
| } |
| } |
| |
| /* ** Full load config only (flags==0) ** |
| * look for "Enclosed" pragmas identifying the relations |
| * among entities and kep that along with the entity for |
| * stage 2 */ |
| if (flags & PARSE_RELATIONS) |
| _layouts_parse_relations(plugin, e, entity_tbl); |
| |
| /* |
| * if the layout plugin requests automerge, try to automatically |
| * parse the conf hash table using the s_p_option_t description |
| * of the plugin, creating the key/vlaue with the right value |
| * type and adding them to the entity key hash table. |
| */ |
| if (plugin->ops->spec->automerge) { |
| _layouts_load_automerge(plugin, e, entity_tbl, flags); |
| } |
| |
| /* |
| * in case the automerge was not sufficient, the layout parsing |
| * callback is called for further actions. |
| */ |
| if ((flags & PARSE_ENTITY) && plugin->ops->entity_parsing) { |
| plugin->ops->entity_parsing(e, entity_tbl, |
| plugin->layout); |
| } |
| |
| /* add the entity ref to the array for further usage when |
| * calling the update_done layout callback */ |
| updated_entities[i] = e; |
| } |
| xfree(e_name); |
| xfree(e_type); |
| |
| /* ** Full load config only (flags==0) ** |
| * post-read-and-build (post stage 1) |
| * ensure that a Root entity was defined and set it as the root of |
| * the relational structure of the layout. |
| * fails in case of error as a root is mandatory to walk the relational |
| * structure of the layout */ |
| if ((flags & CONF_DONE) && |
| _layouts_read_config_post(plugin, tbl) != SLURM_SUCCESS) { |
| goto cleanup; |
| } |
| |
| /* ** Full load config only (flags==0) ** |
| * call the layout plugin conf_done callback for further |
| * layout specific actions. |
| */ |
| if ((flags & CONF_DONE) && plugin->ops->conf_done) { |
| if (!plugin->ops->conf_done(mgr->entities, plugin->layout, |
| tbl)) { |
| error("layouts: plugin %s/%s has an error parsing its" |
| " configuration", plugin->layout->type, |
| plugin->layout->name); |
| rc = SLURM_ERROR; |
| goto cleanup; |
| } |
| } |
| |
| /* |
| * In case we are processing an update (not a startup configuration) |
| * if the layout plugin requests autoupdate, call the autoupdate |
| * function on the current layout in order to set the inherited values |
| * according to the newly modified ones. |
| * (in startup configuration, the autoupdate is performed in stage 3 |
| * when the relational structures are available) |
| */ |
| if ((flags & UPDATE_DONE) && plugin->ops->spec->autoupdate) { |
| _layouts_autoupdate_layout(plugin->layout); |
| } |
| |
| /* |
| * Call the layout plugin update_done callback for further |
| * layout specific actions. |
| * Note : some entries of the updated_entities array might be NULL |
| * reflecting an issue while trying to analyze the corresponding |
| * parsed hash table. |
| */ |
| if ((flags & UPDATE_DONE) && plugin->ops->update_done) { |
| if (!plugin->ops->update_done(plugin->layout, updated_entities, |
| entities_tbl_count)) { |
| error("layouts: plugin %s/%s has an error reacting to" |
| " entities update", plugin->layout->type, |
| plugin->layout->name); |
| rc = SLURM_ERROR; |
| goto cleanup; |
| } |
| } |
| xfree(updated_entities); |
| |
| cleanup: |
| s_p_hashtbl_destroy(tbl); |
| |
| return rc; |
| } |
| |
| /* |
| * _layouts_read_config - called after base entities are loaded successfully |
| * |
| * This function is the stage 1 of the layouts loading stage, where we collect |
| * info on all the entities and store them in a global hash table. |
| * Entities that do not already exist are created, otherwise updated. |
| * |
| * Information concerning the relations among entities provided by the |
| * 'Enclosed' conf pragma are also extracted here for further usage in stage 2. |
| * |
| * When layout plugins callbacks are called, relational structures among |
| * entities are not yet built. |
| */ |
| static int _layouts_read_config(layout_plugin_t* plugin) |
| { |
| int rc; |
| char* filename = _conf_get_filename(plugin->layout->type); |
| if (!filename) { |
| fatal("layouts: cannot find configuration file for " |
| "required layout '%s'", plugin->name); |
| } |
| rc = _layouts_load_config_common(plugin, filename, NULL, |
| CONF_DONE | |
| PARSE_ENTITY | PARSE_RELATIONS); |
| xfree(filename); |
| return rc; |
| } |
| |
| /* |
| * _layouts_read_state - called to restore saved state of layout entities |
| * |
| * This function is the stage 1.1 of the layouts loading stage, where we collect |
| * info on all the entities stored in the state of the layout and store/update |
| * them in the global hash table. |
| * |
| * Information concerning the relations among entities provided by the |
| * 'Enclosed' conf pragma are not taken into account for now and will be those |
| * loaded during stage 1. |
| * |
| * No layout plugins callbacks are called when doing that for now. |
| */ |
| static int _layouts_read_state(layout_plugin_t* plugin) |
| { |
| int rc = SLURM_SUCCESS; |
| struct stat stat_buf; |
| char *filename = _state_get_filename(plugin->layout->type); |
| if (!filename) { |
| error("layouts: unable to build read state filename of layout" |
| " '%s/%s'", plugin->layout->type, plugin->layout->name); |
| return SLURM_ERROR; |
| } |
| /* check availability of the file otherwise it will later block |
| * waiting for a file to appear (in s_p_parse_file) */ |
| if (stat(filename, &stat_buf) < 0) { |
| debug("layouts: skipping non existent state file for '%s/%s'", |
| plugin->layout->type, plugin->layout->name); |
| } else { |
| rc = _layouts_load_config_common(plugin, filename, NULL, |
| PARSE_ENTITY); |
| } |
| xfree(filename); |
| return rc; |
| } |
| |
| static int _layouts_update_state(layout_plugin_t* plugin, Buf buffer) |
| { |
| return _layouts_load_config_common(plugin, NULL, buffer, |
| PARSE_ENTITY | UPDATE_DONE); |
| } |
| |
| typedef struct _layouts_build_xtree_walk_st { |
| layout_t* layout; |
| char* enclosed_key; |
| xtree_t* tree; |
| } _layouts_build_xtree_walk_t; |
| |
| uint8_t _layouts_build_xtree_walk(xtree_node_t* node, |
| uint8_t which, |
| uint32_t level, |
| void* arg) |
| { |
| _layouts_build_xtree_walk_t* p = (_layouts_build_xtree_walk_t*)arg; |
| entity_t* e; |
| entity_node_t* enode; |
| char* enclosed_str; |
| char* enclosed_name; |
| hostlist_t enclosed_hostlist; |
| entity_t* enclosed_e; |
| xtree_node_t* enclosed_node; |
| |
| xassert(arg); |
| |
| /* get the entity from the entity node associated with the tree node */ |
| enode = (entity_node_t*) xtree_node_get_data(node); |
| xassert(enode); |
| e = enode->entity; |
| xassert(e); |
| |
| /* |
| * FIXME: something goes wrong with the order... |
| * after a first growing, the first new child is called with preorder. |
| * |
| * for now, testing each time and use enclosed_str to know if it has |
| * been processed. |
| */ |
| if (which != XTREE_GROWING && which != XTREE_PREORDER) |
| return 1; |
| |
| enclosed_str = (char*) entity_get_data_ref(e, p->enclosed_key); |
| |
| if (enclosed_str) { |
| enclosed_hostlist = hostlist_create(enclosed_str); |
| entity_delete_data(e, p->enclosed_key); |
| while ((enclosed_name = hostlist_shift(enclosed_hostlist))) { |
| enclosed_e = xhash_get_str(mgr->entities, |
| enclosed_name); |
| if (!enclosed_e) { |
| error("layouts: entity '%s' specified in " |
| "enclosed entities of entity '%s' " |
| "not found, ignoring.", |
| enclosed_name, e->name); |
| free(enclosed_name); |
| continue; |
| } |
| free(enclosed_name); |
| /* create an entity node associated to the entity |
| * for this layout */ |
| enode = entity_add_node(enclosed_e, p->layout); |
| xassert(enode); |
| /* add it to the tree, getting an xtree_node_t ref */ |
| if (!(enclosed_node = xtree_add_child( |
| p->tree, node, enode, XTREE_APPEND))) |
| xassert(enclosed_node); |
| /* store the xtree_node_t ref in the entity node. It |
| * will be used to access this layout tree from the |
| * entity when necessary through the entity node */ |
| enode->node = enclosed_node; |
| } |
| hostlist_destroy(enclosed_hostlist); |
| } |
| |
| return 1; |
| } |
| |
| /* |
| * _layouts_build_relations - called after _layouts_read_config to create the |
| * relational structure of the layout according to the topological |
| * details parsed in stage 1. This is the stage 2 of the layouts |
| * configuration load. |
| * |
| * This function is the stage 2 of the layouts loading stage, where we use |
| * the relational details extracted from the parsing stage (Enclosed pragmas |
| * and Root entity) to build the relational structure of the layout. |
| * |
| */ |
| static int _layouts_build_relations(layout_plugin_t* plugin) |
| { |
| xtree_t* tree; |
| xtree_node_t* root_node; |
| char key[PATHLEN]; |
| switch(plugin->layout->struct_type) { |
| case LAYOUT_STRUCT_TREE: |
| tree = layout_get_tree(plugin->layout); |
| xassert(tree); |
| root_node = xtree_get_root(tree); |
| _normalize_keydef_mgrkey(key, PATHLEN, "enclosed", |
| plugin->layout->type); |
| _layouts_build_xtree_walk_t p = { |
| plugin->layout, |
| key, |
| tree |
| }; |
| xtree_walk(tree, |
| root_node, |
| 0, |
| XTREE_LEVEL_MAX, |
| _layouts_build_xtree_walk, |
| &p); |
| break; |
| } |
| return SLURM_SUCCESS; |
| } |
| |
| /*****************************************************************************\ |
| * STATE DUMP * |
| \*****************************************************************************/ |
| |
| /* |
| * _pack_args_t : helper struct/type used when passing args among the various |
| * functions used when packing layouts into a buffer as a set of strings. |
| */ |
| typedef struct _pack_args { |
| Buf buffer; |
| char *current_line; |
| layout_t *layout; |
| hostlist_t list_entities; |
| char *type; |
| uint32_t all; |
| uint32_t flags; |
| uint32_t record_count; |
| } _pack_args_t; |
| |
| /* |
| * _pack_data_key : internal function used to get the key=val |
| * string representation of a particular entity data value |
| */ |
| static char* _pack_data_key(layouts_keydef_t* keydef, void* value) |
| { |
| char val; |
| if (!keydef) { |
| return NULL; |
| } |
| switch(keydef->type) { |
| case L_T_ERROR: |
| return NULL; |
| case L_T_STRING: |
| return xstrdup_printf("%s=%s", keydef->shortkey, |
| (char*)value); |
| case L_T_LONG: |
| return xstrdup_printf("%s=%ld", keydef->shortkey, |
| *(long*)value); |
| case L_T_UINT16: |
| return xstrdup_printf("%s=%u", keydef->shortkey, |
| *(uint16_t*)value); |
| case L_T_UINT32: |
| return xstrdup_printf("%s=%"PRIu32, keydef->shortkey, |
| *(uint32_t*)value); |
| case L_T_BOOLEAN: |
| val = *(bool*)value; |
| return xstrdup_printf("%s=%s", keydef->shortkey, |
| val ? "true" : "false"); |
| case L_T_FLOAT: |
| return xstrdup_printf("%s=%f", keydef->shortkey, |
| *(float*)value); |
| case L_T_DOUBLE: |
| return xstrdup_printf("%s=%f", keydef->shortkey, |
| *(double*)value); |
| case L_T_LONG_DOUBLE: |
| return xstrdup_printf("%s=%Lf", keydef->shortkey, |
| *(long double*)value); |
| case L_T_CUSTOM: |
| if (keydef->custom_dump) { |
| return keydef->custom_dump(value); |
| } else |
| return NULL; |
| } |
| return NULL; |
| } |
| |
| /* |
| * _pack_entity_layout_data : internal function used to append the |
| * key/value of a entity data element corresponding to the targeted |
| * layout when walking an entity list of entity data elements |
| * |
| * - append the " %key%=%val%" to the char* received as an input arg member |
| * |
| */ |
| static void _pack_entity_layout_data(void* item, void* arg) |
| { |
| entity_data_t* data; |
| _pack_args_t *pargs; |
| |
| layouts_keydef_t* keydef; |
| char *data_dump; |
| |
| xassert(item); |
| xassert(arg); |
| |
| data = (entity_data_t*) item; |
| pargs = (_pack_args_t *) arg; |
| |
| /* the pack args must contain a valid char* to append to */ |
| xassert(pargs->current_line); |
| |
| /* we must be able to get the keydef associated to the data key */ |
| xassert(data); |
| keydef = xhash_get_str(mgr->keydefs, data->key); |
| xassert(keydef); |
| |
| /* only dump keys related to the targeted layout */ |
| if (!xstrncmp(keydef->plugin->layout->type, pargs->layout->type, |
| PATHLEN)) { |
| data_dump = _pack_data_key(keydef, data->value); |
| /* avoid printing any error in case of NULL pointer returned */ |
| if (data_dump) { |
| xstrcat(pargs->current_line, " "); |
| xstrcat(pargs->current_line, data_dump); |
| xfree(data_dump); |
| } |
| } |
| |
| return; |
| } |
| |
| /* |
| * _pack_layout_tree : internal function used when walking a layout tree |
| * |
| * - print one line per entity with the following pattern : |
| * Entity=%name% [Type=%type%] [Enclosed=%childrens%] [key1=val1 ...] |
| * |
| * - potentially print an header line if the entity is the root like : |
| * Root=%name% |
| * |
| */ |
| static uint8_t _pack_layout_tree(xtree_node_t* node, uint8_t which, |
| uint32_t level, void* arg) |
| { |
| _pack_args_t *pargs; |
| xtree_node_t* child; |
| entity_node_t* enode; |
| hostlist_t enclosed; |
| char *enclosed_str = NULL, *e_name = NULL, *e_type = NULL; |
| Buf buffer; |
| char *strdump, *str = NULL; |
| |
| /* only need to work for preorder and leaf cases */ |
| if (which != XTREE_PREORDER && which != XTREE_LEAF) { |
| return 1; |
| } |
| |
| /* get the buffer we need to pack the data too */ |
| pargs = (_pack_args_t *) arg; |
| buffer = pargs->buffer; |
| |
| /* aggregate children names to build the Enclosed=.. value */ |
| if (which == XTREE_PREORDER) { |
| enclosed = hostlist_create(NULL); |
| child = node->start; |
| while (child) { |
| enode = (entity_node_t*) xtree_node_get_data(child); |
| if (!enode || !enode->entity) { |
| hostlist_push(enclosed, "NULL"); |
| } else { |
| hostlist_push(enclosed, enode->entity->name); |
| } |
| child = child->next; |
| } |
| hostlist_uniq(enclosed); |
| if (hostlist_count(enclosed) > 0) { |
| enclosed_str = hostlist_ranged_string_xmalloc(enclosed); |
| } |
| hostlist_destroy(enclosed); |
| } |
| |
| /* get the entity associated to this xtree node */ |
| enode = (entity_node_t*) xtree_node_get_data(node); |
| if (!enode || !enode->entity) { |
| e_name = (char*) "NULL"; |
| e_type = NULL; |
| } else { |
| e_name = enode->entity->name; |
| e_type = enode->entity->type; |
| } |
| |
| /* print this entity as root if necessary */ |
| if (level == 0 && !(pargs->flags & LAYOUTS_DUMP_NOLAYOUT) |
| && pargs->type == NULL) { |
| if (pargs->all != 0 || |
| pargs->list_entities == NULL || |
| hostlist_find(pargs->list_entities, e_name) != -1) { |
| str = xstrdup_printf("Root=%s\n", e_name); |
| packstr(str, buffer); |
| pargs->record_count++; |
| xfree(str); |
| } |
| } |
| |
| /* print entity name and type when possible */ |
| str = xstrdup_printf("Entity=%s", e_name); |
| if (e_type) { |
| strdump = xstrdup_printf("%s Type=%s", str, e_type); |
| xfree(str); |
| str = strdump; |
| } |
| |
| /* add entity keys matching the layout to the current str */ |
| pargs->current_line = str; |
| if (enode && enode->entity) { |
| xhash_walk(enode->entity->data, _pack_entity_layout_data, |
| pargs); |
| } |
| /* the current line might have been extended/remalloced, so |
| * we need to sync it again in str for further actions */ |
| str = pargs->current_line; |
| pargs->current_line = NULL; |
| |
| /* don't print enclosed if no_relation option */ |
| if ((pargs->flags & LAYOUTS_DUMP_NOLAYOUT) |
| && enclosed_str != NULL |
| && pargs->list_entities == NULL) { |
| xfree(enclosed_str); |
| xfree(str); |
| return 1; |
| } |
| |
| /* don't print non enclosed if no "entities char*" option */ |
| if (pargs->all == 0 |
| && pargs->list_entities == NULL |
| && enclosed_str == NULL ) { |
| xfree(str); |
| return 1; |
| } |
| |
| /* don't print entities if not named in "entities char*" */ |
| if (pargs->all == 0 |
| && pargs->list_entities != NULL |
| && hostlist_find(pargs->list_entities, e_name) == -1) { |
| xfree(str); |
| return 1; |
| } |
| |
| /* don't print entities if not type of "type char*" */ |
| if (pargs->type != NULL |
| && (e_type == NULL || xstrcasecmp(e_type, pargs->type)!=0)) { |
| xfree(str); |
| return 1; |
| } |
| |
| /* print enclosed entities if any */ |
| if (!enclosed_str) { |
| xstrcat(str, "\n"); |
| } else { |
| strdump = xstrdup_printf("%s Enclosed=%s\n", str, enclosed_str); |
| xfree(enclosed_str); |
| xfree(str); |
| str = strdump; |
| } |
| |
| packstr(str, buffer); |
| pargs->record_count++; |
| xfree(str); |
| |
| return 1; |
| } |
| |
| /* helper function used by layouts_save_state when walking through |
| * the various layouts to save their state in Slurm state save location */ |
| static void _state_save_layout(void* item, void* arg) |
| { |
| layout_t* layout = (layout_t*)item; |
| layouts_state_save_layout(layout->type); |
| } |
| |
| /*****************************************************************************\ |
| * ENTITIES KVs AUTOUPDATE * |
| \*****************************************************************************/ |
| |
| /* |
| * helper structure used when walking the tree of relational nodes in order |
| * to automatically update the entities KVs based on their inheritance |
| * relationships |
| */ |
| typedef struct _autoupdate_tree_args { |
| entity_node_t* enode; |
| uint8_t which; |
| uint32_t level; |
| } _autoupdate_tree_args_t; |
| |
| /* |
| * helper function used to update a particular KV value of an entity according |
| * to a particular operator looking for the right type to apply during the |
| * operation |
| */ |
| static int _autoupdate_entity_kv(layouts_keydef_t* keydef, |
| layouts_keydef_t* ref_keydef, |
| slurm_parser_operator_t operator, |
| void* oldvalue, void* value) |
| { |
| int rc = SLURM_ERROR; |
| |
| if (keydef->type != ref_keydef->type) |
| return rc; |
| |
| if (keydef->type == L_T_LONG) { |
| _entity_update_kv_helper(long, operator); |
| } else if (keydef->type == L_T_UINT16) { |
| _entity_update_kv_helper(uint16_t, operator); |
| } else if (keydef->type == L_T_UINT32) { |
| _entity_update_kv_helper(uint32_t, operator); |
| } else if (keydef->type == L_T_FLOAT) { |
| _entity_update_kv_helper(float, operator); |
| } else if (keydef->type == L_T_DOUBLE) { |
| _entity_update_kv_helper(double, operator); |
| } else if (keydef->type == L_T_LONG_DOUBLE) { |
| _entity_update_kv_helper(long double, operator); |
| } else { |
| // L_T_BOOLEAN, L_T_STRING, L_T_CUSTOM not yet supported |
| return rc; |
| } |
| |
| return SLURM_SUCCESS; |
| } |
| |
| /* |
| * helper function used to update KVs of an entity using its xtree_node |
| * looking for known inheritance in the neighborhood (parents/children) */ |
| static void _tree_update_node_entity_data(void* item, void* arg) |
| { |
| uint32_t action; |
| entity_data_t* data; |
| _autoupdate_tree_args_t *pargs; |
| layouts_keydef_t* keydef; |
| layouts_keydef_t* ref_keydef; |
| slurm_parser_operator_t operator; |
| xtree_node_t *node, *child; |
| entity_node_t *enode, *cnode; |
| void* oldvalue; |
| void* value; |
| uint32_t count; |
| int setter; |
| |
| xassert(item); |
| xassert(arg); |
| |
| data = (entity_data_t*) item; |
| pargs = (_autoupdate_tree_args_t *) arg; |
| cnode = pargs->enode; |
| |
| /* we must be able to get the keydef associated to the data key */ |
| xassert(data); |
| keydef = xhash_get_str(mgr->keydefs, data->key); |
| xassert(keydef); |
| |
| /* only work on keys that depend of their neighborhood */ |
| if (!(keydef->flags & KEYSPEC_UPDATE_CHILDREN_MASK) && |
| !(keydef->flags & KEYSPEC_UPDATE_PARENTS_MASK)) { |
| return; |
| } |
| |
| /* if children dependant and we are at leaf level, nothing to do */ |
| if (keydef->flags & KEYSPEC_UPDATE_CHILDREN_MASK && |
| pargs->which == XTREE_LEAF) |
| return; |
| |
| /* only work on keys related to the targeted layout */ |
| if (xstrncmp(keydef->plugin->layout->type, pargs->enode->layout->type, |
| PATHLEN)) { |
| return; |
| } |
| |
| /* get ref_key (identical if not defined) */ |
| if (keydef->ref_key != NULL) { |
| ref_keydef = xhash_get_str(mgr->keydefs, keydef->ref_key); |
| if (!ref_keydef) { |
| debug2("layouts: autoupdate: key='%s': invalid " |
| "ref_key='%s'", keydef->key, keydef->ref_key); |
| return; |
| } |
| } else { |
| ref_keydef = keydef; |
| } |
| |
| /* process parents aggregation |
| * for now, xtree only provides one parent so any update op |
| * (MAX, MIN, FSHARE, ...) is a setter */ |
| if ((action = keydef->flags & KEYSPEC_UPDATE_PARENTS_MASK) && |
| (pargs->which == XTREE_PREORDER || pargs->which == XTREE_LEAF) && |
| (node = ((xtree_node_t*)pargs->enode->node)->parent) != NULL ) { |
| |
| /* get current node value reference */ |
| oldvalue = entity_get_data_ref(cnode->entity, keydef->key); |
| if (!oldvalue) |
| return; |
| |
| /* get siblings count */ |
| child = node->start; |
| count = 0; |
| while (child) { |
| count++; |
| child = child->next; |
| } |
| |
| /* get parent node KV data ref */ |
| enode = (entity_node_t*) xtree_node_get_data(node); |
| value = entity_get_data_ref(enode->entity, ref_keydef->key); |
| if (!value) |
| return; |
| |
| /* only set operation currently provided for parents except |
| * for fshare action */ |
| _autoupdate_entity_kv(keydef, ref_keydef, S_P_OPERATOR_SET, |
| oldvalue, value); |
| if (action == KEYSPEC_UPDATE_PARENTS_FSHARE) { |
| _autoupdate_entity_kv(keydef, ref_keydef, |
| S_P_OPERATOR_AVG, |
| oldvalue, (void*) &count); |
| } |
| |
| return; |
| } |
| |
| /* process children aggregation */ |
| if ((action = keydef->flags & KEYSPEC_UPDATE_CHILDREN_MASK) && |
| pargs->which == XTREE_ENDORDER) { |
| |
| /* get current node value reference */ |
| oldvalue = entity_get_data_ref(cnode->entity, keydef->key); |
| if (!oldvalue) |
| return; |
| |
| /* get children count */ |
| node = (xtree_node_t*)cnode->node; |
| child = node->start; |
| count = 0; |
| while (child) { |
| count++; |
| child = child->next; |
| } |
| |
| /* no action if no children */ |
| if (count == 0) |
| return; |
| |
| /* if count action, do what is necessary and return */ |
| if (action == KEYSPEC_UPDATE_CHILDREN_COUNT) { |
| _autoupdate_entity_kv(keydef, ref_keydef, |
| S_P_OPERATOR_SET, |
| oldvalue, (void*) &count); |
| return; |
| } |
| |
| /* iterate on the children */ |
| setter = 1; |
| child = node->start; |
| while (child) { |
| /* get child node KV data ref */ |
| enode = (entity_node_t*) xtree_node_get_data(child); |
| value = entity_get_data_ref(enode->entity, |
| ref_keydef->key); |
| |
| if (!value) { |
| /* try next child */ |
| child = child-> next; |
| continue; |
| } |
| |
| switch (action) { |
| case KEYSPEC_UPDATE_CHILDREN_SUM: |
| case KEYSPEC_UPDATE_CHILDREN_AVG: |
| /* first child is a setter */ |
| if (setter) { |
| operator = S_P_OPERATOR_SET; |
| setter = 0; |
| } |
| else |
| operator = S_P_OPERATOR_ADD; |
| break; |
| case KEYSPEC_UPDATE_CHILDREN_MIN: |
| operator = S_P_OPERATOR_SET_IF_MIN; |
| break; |
| case KEYSPEC_UPDATE_CHILDREN_MAX: |
| operator = S_P_OPERATOR_SET_IF_MAX; |
| break; |
| default: |
| /* should not be called! */ |
| return; |
| } |
| |
| /* update the value according to the operator */ |
| _autoupdate_entity_kv(keydef, ref_keydef, operator, |
| oldvalue, value); |
| |
| /* then next child */ |
| child = child-> next; |
| } |
| |
| /* if average action, do what is necessary before return */ |
| if (action == KEYSPEC_UPDATE_CHILDREN_AVG) { |
| _autoupdate_entity_kv(keydef, ref_keydef, |
| S_P_OPERATOR_AVG, |
| oldvalue, (void*) &count); |
| return; |
| } |
| |
| return; |
| } |
| |
| } |
| |
| /* |
| * _autoupdate_layout_tree : internal function used when automatically |
| * updating elements of a layout tree using _layouts_autoupdate_layout */ |
| static uint8_t _autoupdate_layout_tree(xtree_node_t* node, uint8_t which, |
| uint32_t level, void* arg) |
| { |
| entity_node_t* cnode; |
| _autoupdate_tree_args_t sync_args; |
| |
| /* only need to work for preorder, leaf and endorder cases */ |
| if (which != XTREE_PREORDER && |
| which != XTREE_LEAF && |
| which != XTREE_ENDORDER) { |
| return 1; |
| } |
| |
| /* extract current node entity_node to next browsing */ |
| cnode = (entity_node_t*) xtree_node_get_data(node); |
| if (!cnode) |
| return 1; |
| |
| /* prepare downcall args */ |
| sync_args.enode = cnode; |
| sync_args.which = which; |
| sync_args.level = level; |
| |
| /* iterate over the K/V of the entity, syncing them according |
| * to their autoupdate flags */ |
| xhash_walk(cnode->entity->data, _tree_update_node_entity_data, |
| &sync_args); |
| |
| return 1; |
| } |
| |
| /* helper function used to automatically update a layout internal |
| * entities KVs based on inheritance relations (parents/children) */ |
| static int _layouts_autoupdate_layout(layout_t* layout) |
| { |
| /* autoupdate according to the layout struct type */ |
| switch(layout->struct_type) { |
| case LAYOUT_STRUCT_TREE: |
| xtree_walk(layout->tree, NULL, 0, |
| XTREE_LEVEL_MAX, |
| _autoupdate_layout_tree, NULL); |
| break; |
| } |
| |
| return SLURM_SUCCESS; |
| } |
| |
| /* helper function used to automatically update a layout internal |
| * entities KVs based on inheritance relations (parents/children) |
| * only when allowed by the associated plugin */ |
| static int _layouts_autoupdate_layout_if_allowed(layout_t* layout) |
| { |
| int i, rc = SLURM_ERROR; |
| /* look if the corresponding layout plugin enables autoupdate */ |
| for (i = 0; i < mgr->plugins_count; i++) { |
| if (mgr->plugins[i].layout == layout) { |
| /* no autoupdate allowed, return success */ |
| if (!mgr->plugins[i].ops->spec->autoupdate) |
| rc = SLURM_SUCCESS; |
| else |
| rc = _layouts_autoupdate_layout(layout); |
| break; |
| } |
| } |
| return rc; |
| } |
| |
| /*****************************************************************************\ |
| * DEBUG DUMP * |
| \*****************************************************************************/ |
| |
| /* |
| * For debug purposes, dump functions helping to print the layout mgr |
| * internal states in a file after the load. |
| */ |
| #if 0 |
| static char* _dump_data_key(layouts_keydef_t* keydef, void* value) |
| { |
| char val; |
| if (!keydef) { |
| return xstrdup("ERROR_bad_keydef"); |
| } |
| switch(keydef->type) { |
| case L_T_ERROR: |
| return xstrdup("ERROR_keytype!"); |
| case L_T_STRING: |
| return xstrdup((char*)value); |
| case L_T_LONG: |
| return xstrdup_printf("%ld", *(long*)value); |
| case L_T_UINT16: |
| return xstrdup_printf("%u", *(uint16_t*)value); |
| case L_T_UINT32: |
| return xstrdup_printf("%ul", *(uint32_t*)value); |
| case L_T_BOOLEAN: |
| val = *(bool*)value; |
| return xstrdup_printf("%s", val ? "true" : "false"); |
| case L_T_FLOAT: |
| return xstrdup_printf("%f", *(float*)value); |
| case L_T_DOUBLE: |
| return xstrdup_printf("%f", *(double*)value); |
| case L_T_LONG_DOUBLE: |
| return xstrdup_printf("%Lf", *(long double*)value); |
| case L_T_CUSTOM: |
| if (keydef->custom_dump) { |
| return keydef->custom_dump(value); |
| } |
| return xstrdup_printf("custom_ptr(%p)", value); |
| } |
| return NULL; |
| } |
| |
| static void _dump_entity_data(void* item, void* arg) |
| { |
| entity_data_t* data = (entity_data_t*)item; |
| FILE* fdump = (FILE*)arg; |
| layouts_keydef_t* keydef; |
| char* data_dump; |
| |
| xassert(data); |
| keydef = xhash_get_str(mgr->keydefs, data->key); |
| xassert(keydef); |
| data_dump = _dump_data_key(keydef, data->value); |
| |
| fprintf(fdump, "data %s (type: %d): %s\n", |
| data->key, keydef->type, data_dump); |
| |
| xfree(data_dump); |
| } |
| |
| static void _dump_entities(void* item, void* arg) |
| { |
| entity_t* entity = (entity_t*)item; |
| FILE* fdump = (FILE*)arg; |
| fprintf(fdump, "-- entity %s --\n", entity->name); |
| fprintf(fdump, "type: %s\nnode count: %d\nptr: %p\n", |
| entity->type, list_count(entity->nodes), entity->ptr); |
| xhash_walk(entity->data, _dump_entity_data, fdump); |
| } |
| |
| static uint8_t _dump_layout_tree(xtree_node_t* node, uint8_t which, |
| uint32_t level, void* arg) |
| { |
| FILE* fdump = (FILE*)arg; |
| entity_t* e; |
| entity_node_t* enode; |
| if (which != XTREE_PREORDER && which != XTREE_LEAF) { |
| return 1; |
| } |
| enode = (entity_node_t*) xtree_node_get_data(node); |
| if (!enode || !enode->entity) { |
| fprintf(fdump, "NULL_entity\n"); |
| } |
| else { |
| fprintf(fdump, "%*s%s\n", level, " ", enode->entity->name); |
| } |
| return 1; |
| } |
| |
| static void _dump_layouts(void* item, void* arg) |
| { |
| layout_t* layout = (layout_t*)item; |
| FILE* fdump = (FILE*)arg; |
| fprintf(fdump, "-- layout %s --\n" |
| "type: %s\n" |
| "priority: %u\n" |
| "struct_type: %d\n" |
| "relational ptr: %p\n", |
| layout->name, |
| layout->type, |
| layout->priority, |
| layout->struct_type, |
| layout->tree); |
| switch(layout->struct_type) { |
| case LAYOUT_STRUCT_TREE: |
| fprintf(fdump, "struct_type(string): tree, count: %d\n" |
| "entities list:\n", |
| xtree_get_count(layout->tree)); |
| xtree_walk(layout->tree, NULL, 0, XTREE_LEVEL_MAX, |
| _dump_layout_tree, fdump); |
| break; |
| } |
| } |
| #endif |
| |
| |
| /*****************************************************************************\ |
| * SLURM LAYOUTS API * |
| \*****************************************************************************/ |
| |
| int layouts_init(void) |
| { |
| int i = 0; |
| uint32_t layouts_count; |
| |
| debug3("layouts: layouts_init()..."); |
| |
| if (mgr->plugins) { |
| return SLURM_SUCCESS; |
| } |
| |
| slurm_mutex_lock(&layouts_mgr.lock); |
| |
| _layouts_mgr_init(&layouts_mgr); |
| layouts_count = list_count(layouts_mgr.layouts_desc); |
| if (layouts_count == 0) |
| info("layouts: no layout to initialize"); |
| else |
| info("layouts: %d layout(s) to initialize", layouts_count); |
| |
| mgr->plugins = xmalloc(sizeof(layout_plugin_t) * layouts_count); |
| list_for_each(layouts_mgr.layouts_desc, |
| _layouts_init_layouts_walk_helper, |
| &i); |
| mgr->plugins_count = i; |
| |
| if (mgr->plugins_count != layouts_count) { |
| error("layouts: only %d/%d layouts loaded, aborting...", |
| mgr->plugins_count, layouts_count); |
| for (i = 0; i < mgr->plugins_count; i++) { |
| _layout_plugins_destroy(&mgr->plugins[i]); |
| } |
| xfree(mgr->plugins); |
| mgr->plugins = NULL; |
| } else if (layouts_count > 0) { |
| info("layouts: layouts_init done : %d layout(s) " |
| "initialized", layouts_count); |
| } |
| |
| slurm_mutex_unlock(&layouts_mgr.lock); |
| |
| return mgr->plugins_count == layouts_count ? |
| SLURM_SUCCESS : SLURM_ERROR; |
| } |
| |
| int layouts_fini(void) |
| { |
| int i; |
| |
| debug3("layouts: layouts_fini()..."); |
| |
| /* push layouts states to the state save location */ |
| layouts_state_save(); |
| |
| slurm_mutex_lock(&mgr->lock); |
| |
| /* |
| * free the layouts before destroying the plugins, |
| * otherwise we will get trouble xfreeing the layouts whose |
| * memory is owned by the plugins structs |
| */ |
| _layouts_mgr_free(mgr); |
| |
| for (i = 0; i < mgr->plugins_count; i++) { |
| _layout_plugins_destroy(&mgr->plugins[i]); |
| } |
| xfree(mgr->plugins); |
| mgr->plugins = NULL; |
| mgr->plugins_count = 0; |
| |
| slurm_mutex_unlock(&mgr->lock); |
| |
| info("layouts: all layouts are now unloaded."); |
| |
| return SLURM_SUCCESS; |
| } |
| |
| int layouts_load_config(int recover) |
| { |
| int i, rc, inx; |
| node_record_t *node_ptr; |
| layout_t *layout; |
| uint32_t layouts_count; |
| entity_t *entity; |
| entity_node_t *enode; |
| void *ptr; |
| |
| info("layouts: loading entities/relations information"); |
| rc = SLURM_SUCCESS; |
| |
| slurm_mutex_lock(&mgr->lock); |
| if (xhash_count(layouts_mgr.entities)) { |
| slurm_mutex_unlock(&mgr->lock); |
| return rc; |
| } |
| |
| /* |
| * create a base layout to contain the configured nodes |
| * Notes : it might be moved to its own external layout in the |
| * slurm source layouts directory. |
| */ |
| layout = (layout_t*) xmalloc(sizeof(layout_t)); |
| layout_init(layout, "slurm", "base", 0, LAYOUT_STRUCT_TREE); |
| if (xtree_add_child(layout->tree, NULL, NULL, XTREE_APPEND) == NULL) { |
| error("layouts: unable to create base layout tree root" |
| ", aborting"); |
| goto exit; |
| } |
| |
| /* |
| * generate and store the slurm node entities, |
| * add them to the base layout at the same time |
| */ |
| for (inx = 0, node_ptr = node_record_table_ptr; inx < node_record_count; |
| inx++, node_ptr++) { |
| debug3("layouts: loading node %s", node_ptr->name); |
| xassert (node_ptr->magic == NODE_MAGIC); |
| xassert (node_ptr->config_ptr->magic == CONFIG_MAGIC); |
| |
| /* init entity structure on the heap */ |
| entity = (entity_t*) xmalloc(sizeof(struct entity_st)); |
| entity_init(entity, node_ptr->name, "Node"); |
| entity->ptr = node_ptr; |
| |
| /* add to mgr entity hashtable */ |
| if (xhash_add(layouts_mgr.entities,(void*)entity) == NULL) { |
| error("layouts: unable to add entity of node %s" |
| "in the hashtable, aborting", node_ptr->name); |
| entity_free(entity); |
| xfree(entity); |
| rc = SLURM_ERROR; |
| break; |
| } |
| |
| /* add to the base layout (storing a callback ref to the |
| * layout node pointing to it) */ |
| enode = entity_add_node(entity, layout); |
| ptr = xtree_add_child(layout->tree, layout->tree->root, |
| (void*)enode, XTREE_APPEND); |
| if (!ptr) { |
| error("layouts: unable to add entity of node %s" |
| "in the hashtable, aborting", node_ptr->name); |
| entity_free(entity); |
| xfree(entity); |
| rc = SLURM_ERROR; |
| break; |
| } else { |
| enode->node = ptr; |
| } |
| } |
| debug("layouts: %d/%d nodes in hash table, rc=%d", |
| xhash_count(layouts_mgr.entities), node_record_count, rc); |
| |
| if (rc != SLURM_SUCCESS) |
| goto exit; |
| |
| /* add the base layout to the layouts manager dedicated hashtable */ |
| if (xhash_add(layouts_mgr.layouts, (void*)layout) == NULL) { |
| error("layouts: unable to add base layout into the hashtable"); |
| layout_free(layout); |
| rc = SLURM_ERROR; |
| } |
| |
| /* check that we get as many layouts as initiliazed plugins |
| * as layouts are added and referenced by type. |
| * do +1 in the operation as the base layout is currently managed |
| * separately. |
| * If this base layout is moved to a dedicated plugin and automatically |
| * added to the mgr layouts_desc at init, the +1 will have to be |
| * removed here as it will be counted as the other plugins in the sum |
| */ |
| layouts_count = xhash_count(layouts_mgr.layouts); |
| if ( layouts_count != mgr->plugins_count + 1) { |
| error("layouts: %d/%d layouts added to hashtable, aborting", |
| layouts_count, mgr->plugins_count+1); |
| rc = SLURM_ERROR; |
| } |
| |
| exit: |
| if (rc != SLURM_SUCCESS) { |
| layout_free(layout); |
| xfree(layout); |
| } else { |
| debug("layouts: loading stage 1"); |
| for (i = 0; i < mgr->plugins_count; ++i) { |
| debug3("layouts: reading config for %s", |
| mgr->plugins[i].name); |
| if (_layouts_read_config(&mgr->plugins[i]) != |
| SLURM_SUCCESS) { |
| rc = SLURM_ERROR; |
| break; |
| } |
| } |
| if (recover) { |
| debug("layouts: loading stage 1.1 (restore state)"); |
| for (i = 0; i < mgr->plugins_count; ++i) { |
| debug3("layouts: reading state of %s", |
| mgr->plugins[i].name); |
| _layouts_read_state(&mgr->plugins[i]); |
| } |
| } |
| debug("layouts: loading stage 2"); |
| for (i = 0; i < mgr->plugins_count; ++i) { |
| debug3("layouts: creating relations for %s", |
| mgr->plugins[i].name); |
| if (_layouts_build_relations(&mgr->plugins[i]) != |
| SLURM_SUCCESS) { |
| rc = SLURM_ERROR; |
| break; |
| } |
| } |
| debug("layouts: loading stage 3"); |
| for (i = 0; i < mgr->plugins_count; ++i) { |
| debug3("layouts: autoupdating %s", |
| mgr->plugins[i].name); |
| if (mgr->plugins[i].ops->spec->autoupdate) { |
| if (_layouts_autoupdate_layout(mgr->plugins[i]. |
| layout) != |
| SLURM_SUCCESS) { |
| rc = SLURM_ERROR; |
| break; |
| } |
| } |
| } |
| } |
| |
| /* |
| * For debug purposes, print the layout mgr internal states |
| * in a file after the load. |
| */ |
| #if 0 |
| /* temporary section to test layouts */ |
| FILE* fdump = fopen("/tmp/slurm-layouts-dump.txt", "wb"); |
| |
| xhash_walk(mgr->entities, _dump_entities, fdump); |
| xhash_walk(mgr->layouts, _dump_layouts, fdump); |
| |
| if (fdump) |
| fclose(fdump); |
| #endif |
| |
| slurm_mutex_unlock(&mgr->lock); |
| |
| return rc; |
| } |
| |
| layout_t* layouts_get_layout_nolock(const char* type) |
| { |
| return (layout_t*)xhash_get_str(mgr->layouts, type); |
| } |
| |
| layout_t* layouts_get_layout(const char* type) |
| { |
| layout_t *layout = NULL; |
| slurm_mutex_lock(&mgr->lock); |
| layout = layouts_get_layout_nolock(type); |
| slurm_mutex_unlock(&mgr->lock); |
| return layout; |
| } |
| |
| entity_t* layouts_get_entity_nolock(const char* name) |
| { |
| return (entity_t*)xhash_get_str(mgr->entities, name); |
| } |
| |
| entity_t* layouts_get_entity(const char* name) |
| { |
| entity_t* e; |
| slurm_mutex_lock(&mgr->lock); |
| e = layouts_get_entity_nolock(name); |
| slurm_mutex_unlock(&mgr->lock); |
| return e; |
| } |
| |
| |
| int layouts_pack_layout(char *l_type, char *char_entities, char *type, |
| uint32_t flags, Buf buffer) |
| { |
| _pack_args_t pargs; |
| layout_t* layout; |
| int orig_offset, fini_offset; |
| char *str; |
| |
| slurm_mutex_lock(&mgr->lock); |
| |
| layout = layouts_get_layout_nolock(l_type); |
| if (layout == NULL) { |
| slurm_mutex_unlock(&mgr->lock); |
| info("unable to get layout of type '%s'", l_type); |
| return SLURM_ERROR; |
| } |
| /* initialize args for recursive packing */ |
| pargs.buffer = buffer; |
| pargs.layout = layout; |
| pargs.current_line = NULL; |
| pargs.all = 0; |
| pargs.list_entities = NULL; |
| if (char_entities != NULL) { |
| if (xstrcmp(char_entities, "*") == 0) |
| pargs.all = 1; |
| else |
| pargs.list_entities = hostlist_create(char_entities); |
| } |
| pargs.type = type; |
| pargs.flags = flags; |
| pargs.record_count = 0; |
| orig_offset = get_buf_offset(buffer); |
| pack32(pargs.record_count, buffer); |
| |
| /* start by packing the layout priority in case we are dumping state */ |
| if (pargs.flags & LAYOUTS_DUMP_STATE) { |
| str = xstrdup_printf("Priority=%u\n", layout->priority); |
| packstr(str, buffer); |
| pargs.record_count++; |
| xfree(str); |
| } |
| |
| /* pack according to the layout struct type */ |
| switch (layout->struct_type) { |
| case LAYOUT_STRUCT_TREE: |
| xtree_walk(layout->tree, NULL, 0, XTREE_LEVEL_MAX, |
| _pack_layout_tree, &pargs); |
| break; |
| } |
| |
| if (pargs.list_entities != NULL) |
| slurm_hostlist_destroy(pargs.list_entities); |
| |
| fini_offset = get_buf_offset(buffer); |
| set_buf_offset(buffer, orig_offset); |
| pack32(pargs.record_count, buffer); |
| set_buf_offset(buffer, fini_offset); |
| |
| slurm_mutex_unlock(&mgr->lock); |
| |
| return SLURM_SUCCESS; |
| } |
| |
| int layouts_update_layout(char *l_type, Buf buffer) |
| { |
| int i, rc; |
| slurm_mutex_lock(&mgr->lock); |
| for (i = 0; i < mgr->plugins_count; i++) { |
| if (!xstrcmp(mgr->plugins[i].name, l_type)) { |
| rc = _layouts_update_state((layout_plugin_t*) |
| &mgr->plugins[i], |
| buffer); |
| slurm_mutex_unlock(&mgr->lock); |
| return rc; |
| } |
| } |
| info("%s: no plugin matching layout=%s, skipping", __func__, l_type); |
| slurm_mutex_unlock(&mgr->lock); |
| return SLURM_ERROR; |
| } |
| |
| int layouts_autoupdate_layout(char *l_type) |
| { |
| int rc = SLURM_ERROR; |
| layout_t* layout; |
| |
| slurm_mutex_lock(&mgr->lock); |
| layout = layouts_get_layout_nolock(l_type); |
| if (layout == NULL) { |
| info("unable to get layout of type '%s'", l_type); |
| } else { |
| rc = _layouts_autoupdate_layout(layout); |
| } |
| slurm_mutex_unlock(&mgr->lock); |
| |
| return rc; |
| } |
| |
| int layouts_state_save_layout(char* l_type) |
| { |
| int error_code = 0, log_fd, offset; |
| char *old_file = NULL, *new_file = NULL, *reg_file = NULL; |
| static int high_buffer_size = (16 * 1024); |
| Buf buffer = init_buf(high_buffer_size); |
| FILE* fdump; |
| uint32_t utmp32, record_count = 0; |
| char *tmp_str = NULL; |
| |
| DEF_TIMERS; |
| START_TIMER; |
| |
| /* pack the targeted layout into a tmp buffer */ |
| error_code = layouts_pack_layout(l_type, "*", NULL, |
| LAYOUTS_DUMP_STATE, buffer); |
| if (error_code != SLURM_SUCCESS) { |
| error("unable to save layout[%s] state", l_type); |
| return error_code; |
| } |
| |
| /* rewind the freshly created buffer to unpack it into a file */ |
| offset = get_buf_offset(buffer); |
| high_buffer_size = MAX(high_buffer_size, offset); |
| set_buf_offset(buffer, 0); |
| |
| /* create working files */ |
| reg_file = _state_get_filename(l_type); |
| old_file = xstrdup_printf("%s.old", reg_file); |
| new_file = xstrdup_printf("%s.new", reg_file); |
| log_fd = creat(new_file, 0600); |
| if (log_fd < 0 || !(fdump = fdopen(log_fd, "w"))) { |
| error("Can't save state, create file %s error %m", |
| new_file); |
| error_code = errno; |
| } else { |
| /* extract the amount of records and then proceed |
| * then dump packed strings into the temporary file */ |
| safe_unpack32(&record_count, buffer); |
| debug("layouts/%s: dumping %u records into state file", |
| l_type, record_count); |
| while (get_buf_offset(buffer) < offset) { |
| safe_unpackstr_xmalloc(&tmp_str, &utmp32, buffer); |
| if (tmp_str != NULL) { |
| if (*tmp_str == '\0') { |
| xfree(tmp_str); |
| break; |
| } |
| fprintf(fdump, "%s", tmp_str); |
| xfree(tmp_str); |
| continue; |
| } |
| unpack_error: |
| break; |
| } |
| fflush(fdump); |
| fsync(log_fd); |
| fclose(fdump); |
| } |
| if (error_code) |
| (void) unlink(new_file); |
| else { /* file shuffle */ |
| (void) unlink(old_file); |
| if (link(reg_file, old_file)) |
| debug4("unable to create link for %s -> %s: %m", |
| reg_file, old_file); |
| (void) unlink(reg_file); |
| if (link(new_file, reg_file)) |
| debug4("unable to create link for %s -> %s: %m", |
| new_file, reg_file); |
| (void) unlink(new_file); |
| } |
| xfree(old_file); |
| xfree(reg_file); |
| xfree(new_file); |
| |
| free_buf(buffer); |
| |
| END_TIMER2("layouts_state_save_layout"); |
| |
| return SLURM_SUCCESS; |
| } |
| |
| int layouts_state_save(void) |
| { |
| DEF_TIMERS; |
| START_TIMER; |
| xhash_walk(mgr->layouts, _state_save_layout, NULL); |
| END_TIMER2("layouts_state_save"); |
| return SLURM_SUCCESS; |
| } |
| |
| #define _layouts_entity_wrapper(func, l, e, r...) \ |
| layout_t* layout; \ |
| entity_t* entity; \ |
| int rc; \ |
| slurm_mutex_lock(&mgr->lock); \ |
| layout = layouts_get_layout_nolock(l); \ |
| entity = layouts_get_entity_nolock(e); \ |
| rc = func(layout, entity, ##r); \ |
| slurm_mutex_unlock(&mgr->lock); \ |
| return rc; \ |
| |
| int layouts_entity_get_kv_type(char* l, char* e, char* key) |
| { |
| _layouts_entity_wrapper(_layouts_entity_get_kv_type,l,e,key); |
| } |
| |
| int layouts_entity_get_kv_flags(char* l, char* e, char* key) |
| { |
| _layouts_entity_wrapper(_layouts_entity_get_kv_flags, l, e, key); |
| } |
| |
| int layouts_entity_push_kv(char* l, char* e, char* key) |
| { |
| _layouts_entity_wrapper(_layouts_entity_push_kv, l, e, key); |
| } |
| |
| int layouts_entity_pull_kv(char* l, char* e, char* key) |
| { |
| _layouts_entity_wrapper(_layouts_entity_pull_kv, l, e, key); |
| } |
| |
| int layouts_entity_set_kv(char* l, char* e, char* key, void* value, |
| layouts_keydef_types_t key_type) |
| { |
| _layouts_entity_wrapper(_layouts_entity_set_kv, l, e, |
| key, value, key_type); |
| } |
| |
| int layouts_entity_set_kv_ref(char* l, char* e, char* key, void* value, |
| layouts_keydef_types_t key_type) |
| { |
| _layouts_entity_wrapper(_layouts_entity_set_kv_ref, l, e, |
| key, value, key_type); |
| } |
| |
| int layouts_entity_setpush_kv(char* l, char* e, char* key, void* value, |
| layouts_keydef_types_t key_type) |
| { |
| _layouts_entity_wrapper(_layouts_entity_setpush_kv, l, e, |
| key, value, key_type); |
| } |
| |
| int layouts_entity_setpush_kv_ref(char* l, char* e, char* key, void* value, |
| layouts_keydef_types_t key_type) |
| { |
| _layouts_entity_wrapper(_layouts_entity_setpush_kv_ref, l, e, |
| key, value, key_type); |
| } |
| |
| int layouts_entity_get_kv(char* l, char* e, char* key, void* value, |
| layouts_keydef_types_t key_type) |
| { |
| _layouts_entity_wrapper(_layouts_entity_get_kv, l, e, |
| key, value, key_type); |
| } |
| |
| int layouts_entity_get_mkv(char* l, char* e, char* keys, void* value, |
| size_t size, layouts_keydef_types_t key_type) |
| { |
| _layouts_entity_wrapper(_layouts_entity_get_mkv, l, e, |
| keys, value, size, key_type); |
| } |
| |
| int layouts_entity_get_kv_ref(char* l, char* e, char* key, void** value, |
| layouts_keydef_types_t key_type) |
| { |
| _layouts_entity_wrapper(_layouts_entity_get_kv_ref, l, e, |
| key, value, key_type); |
| } |
| |
| int layouts_entity_get_mkv_ref(char* l, char* e, char* keys, void* value, |
| size_t size, layouts_keydef_types_t key_type) |
| { |
| _layouts_entity_wrapper(_layouts_entity_get_mkv_ref, l, e, |
| keys, value, size, key_type); |
| } |
| |
| int layouts_entity_pullget_kv(char* l, char* e, char* key, void* value, |
| layouts_keydef_types_t key_type) |
| { |
| _layouts_entity_wrapper(_layouts_entity_pullget_kv, l, e, |
| key, value, key_type); |
| } |
| |
| int layouts_entity_pullget_kv_ref(char* l, char* e, char* key, void** value, |
| layouts_keydef_types_t key_type) |
| { |
| _layouts_entity_wrapper(_layouts_entity_pullget_kv_ref, l, e, |
| key, value, key_type); |
| } |