| /*****************************************************************************\ |
| * serializer_json.c - Serializer for JSON. |
| ***************************************************************************** |
| * 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. |
| \*****************************************************************************/ |
| |
| #include "config.h" |
| #include "math.h" |
| |
| #if HAVE_JSON_C_INC |
| #include <json-c/json.h> |
| #else |
| #include <json/json.h> |
| #endif |
| |
| #include "slurm/slurm.h" |
| #include "src/common/slurm_xlator.h" |
| |
| #include "src/common/data.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" |
| #include "src/interfaces/serializer.h" |
| |
| #define SERIALIZER_JSON_DEFAULT_FORMAT JSON_C_TO_STRING_PLAIN |
| #define SERIALIZER_JSON_DEFAULT_FLAGS SER_FLAGS_PRETTY |
| |
| /* |
| * These variables are required by the generic plugin interface. If they |
| * are not found in the plugin, the plugin loader will ignore it. |
| * |
| * plugin_name - A string giving a human-readable description of the |
| * plugin. There is no maximum length, but the symbol must refer to |
| * a valid string. |
| * |
| * plugin_type - A string suggesting the type of the plugin or its |
| * applicability to a particular form of data or method of data handling. |
| * If the low-level plugin API is used, the contents of this string are |
| * unimportant and may be anything. Slurm uses the higher-level plugin |
| * interface which requires this string to be of the form |
| * |
| * <application>/<method> |
| * |
| * where <application> is a description of the intended application of |
| * the plugin (e.g., "auth" for Slurm authentication) and <method> is a |
| * description of how this plugin satisfies that application. Slurm will |
| * only load authentication plugins if the plugin_type string has a prefix |
| * of "auth/". |
| * |
| * plugin_version - an unsigned 32-bit integer containing the Slurm version |
| * (major.minor.micro combined into a single number). |
| */ |
| const char plugin_name[] = "Serializer JSON plugin"; |
| const char plugin_type[] = "serializer/json"; |
| const uint32_t plugin_version = SLURM_VERSION_NUMBER; |
| const char *mime_types[] = { |
| "application/json", |
| "application/jsonrequest", |
| NULL |
| }; |
| static serializer_flags_t global_flags = SERIALIZER_JSON_DEFAULT_FLAGS; |
| |
| #define MAGIC_CONVERT_FOREACH 0x0a0b0808 |
| |
| typedef struct { |
| int magic; /* MAGIC_CONVERT_FOREACH */ |
| json_object *jobj; |
| serializer_flags_t flags; |
| } convert_foreach_arg_t; |
| |
| static json_object *_data_to_json(const data_t *d, serializer_flags_t flags); |
| |
| /* Merge global_flags and flags into the coherent set of flags */ |
| static serializer_flags_t _merge_flags(serializer_flags_t flags) |
| { |
| serializer_flags_t ret = global_flags; |
| |
| /* Avoid conflicting flags from global */ |
| if (flags & (SER_FLAGS_COMPACT|SER_FLAGS_PRETTY)) |
| ret &= ~(SER_FLAGS_COMPACT|SER_FLAGS_PRETTY); |
| |
| return (ret | flags); |
| } |
| |
| extern int serialize_p_init(serializer_flags_t flags) |
| { |
| if (flags != SER_FLAGS_NONE) |
| global_flags = flags; |
| |
| log_flag(DATA, "loaded"); |
| |
| return SLURM_SUCCESS; |
| } |
| |
| extern void serialize_p_fini(void) |
| { |
| log_flag(DATA, "unloaded"); |
| } |
| |
| static json_object *_try_parse(const char *src, size_t stringlen, |
| struct json_tokener *tok) |
| { |
| json_object *jobj = json_tokener_parse_ex(tok, src, stringlen); |
| |
| if (jobj == NULL) { |
| enum json_tokener_error jerr = json_tokener_get_error(tok); |
| error("%s: JSON parsing error %zu bytes: %s", |
| __func__, stringlen, json_tokener_error_desc(jerr)); |
| return NULL; |
| } |
| if (tok->char_offset < stringlen) |
| log_flag(DATA, "%s: Extra %zu characters after JSON string detected", |
| __func__, (stringlen - tok->char_offset)); |
| |
| return jobj; |
| } |
| |
| static data_t *_json_to_data(json_object *jobj, data_t *d) |
| { |
| size_t arraylen = 0; |
| |
| if (!d) |
| d = data_new(); |
| |
| switch (json_object_get_type(jobj)) { |
| case json_type_null: |
| data_set_null(d); |
| break; |
| case json_type_boolean: |
| data_set_bool(d, json_object_get_boolean(jobj)); |
| break; |
| case json_type_double: |
| data_set_float(d, json_object_get_double(jobj)); |
| break; |
| case json_type_int: |
| data_set_int(d, json_object_get_int64(jobj)); |
| break; |
| case json_type_object: |
| data_set_dict(d); |
| /* warning: json_object_object_foreach is an evil macro */ |
| json_object_object_foreach(jobj, key, val) { |
| _json_to_data(val, data_key_set(d, key)); |
| } |
| break; |
| case json_type_array: |
| arraylen = json_object_array_length(jobj); |
| data_set_list(d); |
| for (size_t i = 0; i < arraylen; i++) |
| _json_to_data(json_object_array_get_idx(jobj, i), |
| data_list_append(d)); |
| break; |
| case json_type_string: |
| data_set_string(d, json_object_get_string(jobj)); |
| break; |
| default: |
| fatal_abort("%s: unknown JSON type", __func__); |
| }; |
| |
| return d; |
| } |
| |
| static data_for_each_cmd_t _convert_dict_json(const char *key, |
| const data_t *data, |
| void *arg) |
| { |
| convert_foreach_arg_t *args = arg; |
| json_object *jobj = args->jobj; |
| json_object *jobject = _data_to_json(data, args->flags); |
| |
| xassert(args->magic == MAGIC_CONVERT_FOREACH); |
| |
| json_object_object_add(jobj, key, jobject); |
| return DATA_FOR_EACH_CONT; |
| } |
| |
| static data_for_each_cmd_t _convert_list_json(const data_t *data, void *arg) |
| { |
| convert_foreach_arg_t *args = arg; |
| json_object *jobj = args->jobj; |
| json_object *jarray = _data_to_json(data, args->flags); |
| |
| xassert(args->magic == MAGIC_CONVERT_FOREACH); |
| |
| json_object_array_add(jobj, jarray); |
| return DATA_FOR_EACH_CONT; |
| } |
| |
| static json_object *_data_to_json(const data_t *d, serializer_flags_t flags) |
| { |
| if (!d) |
| return NULL; |
| |
| switch (data_get_type(d)) { |
| case DATA_TYPE_NULL: |
| return NULL; |
| break; |
| case DATA_TYPE_BOOL: |
| return json_object_new_boolean(data_get_bool(d)); |
| break; |
| case DATA_TYPE_FLOAT: |
| if (!(flags & SER_FLAGS_COMPLEX)) { |
| if (isinf(data_get_float(d))) |
| return json_object_new_double( |
| (double) INFINITE64); |
| else if (isnan(data_get_float(d))) |
| return json_object_new_double( |
| (double) NO_VAL64); |
| } |
| |
| return json_object_new_double(data_get_float(d)); |
| break; |
| case DATA_TYPE_INT_64: |
| return json_object_new_int64(data_get_int(d)); |
| break; |
| case DATA_TYPE_DICT: |
| { |
| json_object *jobj = json_object_new_object(); |
| convert_foreach_arg_t args = { |
| .magic = MAGIC_CONVERT_FOREACH, |
| .jobj = jobj, |
| .flags = flags, |
| }; |
| if (data_dict_for_each_const(d, _convert_dict_json, &args) < 0) |
| error("%s: unexpected error calling _convert_dict_json()", |
| __func__); |
| return jobj; |
| } |
| case DATA_TYPE_LIST: |
| { |
| json_object *jobj = json_object_new_array(); |
| convert_foreach_arg_t args = { |
| .magic = MAGIC_CONVERT_FOREACH, |
| .jobj = jobj, |
| .flags = flags, |
| }; |
| if (data_list_for_each_const(d, _convert_list_json, &args) < 0) |
| error("%s: unexpected error calling _convert_list_json()", |
| __func__); |
| return jobj; |
| } |
| case DATA_TYPE_STRING: |
| { |
| const char *str = data_get_string(d); |
| if (str) |
| return json_object_new_string(str); |
| else |
| return json_object_new_string(""); |
| break; |
| } |
| default: |
| fatal_abort("%s: unknown type", __func__); |
| }; |
| } |
| |
| extern int serialize_p_data_to_string(char **dest, size_t *length, |
| const data_t *src, |
| serializer_flags_t flags) |
| { |
| struct json_object *jobj = NULL; |
| int jflags = 0; |
| |
| flags = _merge_flags(flags); |
| jobj = _data_to_json(src, flags); |
| |
| /* can't be pretty and compact at the same time! */ |
| xassert((flags & (SER_FLAGS_PRETTY | SER_FLAGS_COMPACT)) != |
| (SER_FLAGS_PRETTY | SER_FLAGS_COMPACT)); |
| |
| if (flags & SER_FLAGS_PRETTY) |
| jflags = JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY; |
| else if (flags & SER_FLAGS_COMPACT) |
| jflags = JSON_C_TO_STRING_PLAIN; |
| else |
| jflags = SERIALIZER_JSON_DEFAULT_FORMAT; |
| |
| /* string will die with jobj */ |
| *dest = xstrdup(json_object_to_json_string_ext(jobj, jflags)); |
| if (length) { |
| /* add 1 for \0 */ |
| *length = strlen(*dest) + 1; |
| } |
| |
| /* put is equiv to free() */ |
| json_object_put(jobj); |
| |
| return SLURM_SUCCESS; |
| } |
| |
| extern int serialize_p_string_to_data(data_t **dest, const char *src, |
| size_t length) |
| { |
| json_object *jobj = NULL; |
| data_t *data = NULL; |
| struct json_tokener *tok = json_tokener_new(); |
| int rc; |
| |
| if (!tok) |
| return ENOMEM; |
| |
| if (!src) |
| return ESLURM_DATA_PTR_NULL; |
| |
| /* json-c has hard limit of 32 bits */ |
| if (length >= INT32_MAX) { |
| error("%s: unable to parse JSON: too large", |
| __func__); |
| return ESLURM_DATA_TOO_LARGE; |
| } |
| |
| jobj = _try_parse(src, length, tok); |
| if (jobj) { |
| data = _json_to_data(jobj, NULL); |
| json_object_put(jobj); |
| rc = SLURM_SUCCESS; |
| } else |
| rc = ESLURM_REST_FAIL_PARSING; |
| |
| json_tokener_free(tok); |
| |
| *dest = data; |
| return rc; |
| } |