| /*****************************************************************************\ | |
| * 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 | |
| /* Required Slurm plugin symbols: */ | |
| const char plugin_name[] = "Serializer JSON plugin"; | |
| const char plugin_type[] = "serializer/json"; | |
| const uint32_t plugin_version = SLURM_VERSION_NUMBER; | |
| /* Required for serializer plugins: */ | |
| const serializer_mimetype_t mime_types[] = { | |
| { .flags = SER_MIMETYPE_FLAGS_OPENAPI, .mimetype = "application/json" }, | |
| { .mimetype = "application/jsonrequest" }, | |
| { 0 }, | |
| }; | |
| 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) | |
| { | |
| /* | |
| * Only replace the default formatting when a formatting flag was | |
| * actually requested. Merging otherwise keeps a non-formatting flag | |
| * (complex), or a flag only meaningful to another serializer (no_tag), | |
| * from silently dropping this plugin's default. | |
| */ | |
| if (flags & (SER_FLAGS_COMPACT | SER_FLAGS_PRETTY)) | |
| global_flags = flags; | |
| else | |
| global_flags = (SERIALIZER_JSON_DEFAULT_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, 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) | |
| *length = strlen(*dest); | |
| /* 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; | |
| xassert(*dest == NULL); | |
| 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; | |
| } | |
| extern int serialize_p_dump(serialize_dump_state_t **state_ptr, | |
| data_parser_t *parser, data_parser_type_t type, | |
| void *src, ssize_t src_bytes, buf_t *dst, | |
| serializer_flags_t flags) | |
| { | |
| return ESLURM_NOT_SUPPORTED; | |
| } | |
| extern int serialize_p_parse(serialize_parse_state_t **state_ptr, | |
| data_parser_t *parser, data_parser_type_t type, | |
| void *dst, ssize_t dst_bytes, buf_t *src) | |
| { | |
| return ESLURM_NOT_SUPPORTED; | |
| } |