diff --git a/src/basic/json-internal.h b/src/basic/json-internal.h new file mode 100644 index 0000000000..6d195eb672 --- /dev/null +++ b/src/basic/json-internal.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#pragma once + +#include "json.h" + +/* This header should include all prototypes only the JSON parser itself and + * its tests need access to. Normal code consuming the JSON parser should not + * interface with this. */ + +typedef union JsonValue { + /* Encodes a simple value. On x86-64 this structure is 16 bytes wide (as long double is 128bit). */ + bool boolean; + long double real; + intmax_t integer; + uintmax_t unsig; +} JsonValue; + +/* Let's protect us against accidental structure size changes on our most relevant arch */ +#ifdef __x86_64__ +assert_cc(sizeof(JsonValue) == 16U); +#endif + +#define JSON_VALUE_NULL ((JsonValue) {}) + +/* We use fake JsonVariant objects for some special values, in order to avoid memory allocations for them. Note that + * effectively this means that there are multiple ways to encode the same objects: via these magic values or as + * properly allocated JsonVariant. We convert between both on-the-fly as necessary. */ +#define JSON_VARIANT_MAGIC_TRUE ((JsonVariant*) 1) +#define JSON_VARIANT_MAGIC_FALSE ((JsonVariant*) 2) +#define JSON_VARIANT_MAGIC_NULL ((JsonVariant*) 3) +#define JSON_VARIANT_MAGIC_ZERO_INTEGER ((JsonVariant*) 4) +#define JSON_VARIANT_MAGIC_ZERO_UNSIGNED ((JsonVariant*) 5) +#define JSON_VARIANT_MAGIC_ZERO_REAL ((JsonVariant*) 6) +#define JSON_VARIANT_MAGIC_EMPTY_STRING ((JsonVariant*) 7) +#define JSON_VARIANT_MAGIC_EMPTY_ARRAY ((JsonVariant*) 8) +#define JSON_VARIANT_MAGIC_EMPTY_OBJECT ((JsonVariant*) 9) + +enum { /* JSON tokens */ + JSON_TOKEN_END, + JSON_TOKEN_COLON, + JSON_TOKEN_COMMA, + JSON_TOKEN_OBJECT_OPEN, + JSON_TOKEN_OBJECT_CLOSE, + JSON_TOKEN_ARRAY_OPEN, + JSON_TOKEN_ARRAY_CLOSE, + JSON_TOKEN_STRING, + JSON_TOKEN_REAL, + JSON_TOKEN_INTEGER, + JSON_TOKEN_UNSIGNED, + JSON_TOKEN_BOOLEAN, + JSON_TOKEN_NULL, + _JSON_TOKEN_MAX, + _JSON_TOKEN_INVALID = -1, +}; + +int json_tokenize(const char **p, char **ret_string, JsonValue *ret_value, unsigned *ret_line, unsigned *ret_column, void **state, unsigned *line, unsigned *column); diff --git a/src/basic/json.c b/src/basic/json.c new file mode 100644 index 0000000000..df3141fb2a --- /dev/null +++ b/src/basic/json.c @@ -0,0 +1,3290 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include +#include +#include +#include +#include +#include +#include + +#include "sd-messages.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "float.h" +#include "hexdecoct.h" +#include "json-internal.h" +#include "json.h" +#include "macro.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "utf8.h" + +typedef struct JsonSource { + /* When we parse from a file or similar, encodes the filename, to indicate the source of a json variant */ + size_t n_ref; + unsigned max_line; + unsigned max_column; + char name[]; +} JsonSource; + +/* On x86-64 this whole structure should have a size of 6 * 64 bit = 48 bytes */ +struct JsonVariant { + union { + /* We either maintain a reference counter for this variant itself, or we are embedded into an + * array/object, in which case only that surrounding object is ref-counted. (If 'embedded' is false, + * see below.) */ + size_t n_ref; + + /* If this JsonVariant is part of an array/object, then this field points to the surrounding + * JSON_VARIANT_ARRAY/JSON_VARIANT_OBJECT object. (If 'embedded' is true, see below.) */ + JsonVariant *parent; + }; + + JsonVariantType type:5; + + /* A marker whether this variant is embedded into in array/object or not. If true, the 'parent' pointer above + * is valid. If false, the 'n_ref' field above is valid instead. */ + bool is_embedded:1; + + /* In some conditions (for example, if this object is part of an array of strings or objects), we don't store + * any data inline, but instead simply reference an external object and act as surrogate of it. In that case + * this bool is set, and the external object is referenced through the .reference field below. */ + bool is_reference:1; + + /* While comparing two arrays, we use this for marking what we already have seen */ + bool is_marked:1; + + /* If this was parsed from some file or buffer, this stores where from, as well as the source line/column */ + unsigned line, column; + JsonSource *source; + + union { + /* For simple types we store the value in-line. */ + JsonValue value; + + /* For objects and arrays we store the number of elements immediately following */ + size_t n_elements; + + /* If is_reference as indicated above is set, this is where the reference object is actually stored. */ + JsonVariant *reference; + + /* Strings are placed immediately after the structure. Note that when this is a JsonVariant embedded + * into an array we might encode strings up to INLINE_STRING_LENGTH characters directly inside the + * element, while longer strings are stored as references. When this object is not embedded into an + * array, but stand-alone we allocate the right size for the whole structure, i.e. the array might be + * much larger than INLINE_STRING_LENGTH. + * + * Note that because we want to allocate arrays of the JsonVariant structure we specify [0] here, + * rather than the prettier []. If we wouldn't, then this char array would have undefined size, and so + * would the union and then the struct this is included in. And of structures with undefined size we + * can't allocate arrays (at least not easily). */ + char string[0]; + }; +}; + +/* Inside string arrays we have a series of JasonVariant structures one after the other. In this case, strings longer + * than INLINE_STRING_MAX are stored as references, and all shorter ones inline. (This means — on x86-64 — strings up + * to 15 chars are stored within the array elements, and all others in separate allocations) */ +#define INLINE_STRING_MAX (sizeof(JsonVariant) - offsetof(JsonVariant, string) - 1U) + +/* Let's make sure this structure isn't increased in size accidentally. This check is only for our most relevant arch + * (x86-64). */ +#ifdef __x86_64__ +assert_cc(sizeof(JsonVariant) == 48U); +assert_cc(INLINE_STRING_MAX == 15U); +#endif + +static JsonSource* json_source_new(const char *name) { + JsonSource *s; + + assert(name); + + s = malloc(offsetof(JsonSource, name) + strlen(name) + 1); + if (!s) + return NULL; + + *s = (JsonSource) { + .n_ref = 1, + }; + strcpy(s->name, name); + + return s; +} + +DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(JsonSource, json_source, mfree); + +static bool json_source_equal(JsonSource *a, JsonSource *b) { + if (a == b) + return true; + + if (!a || !b) + return false; + + return streq(a->name, b->name); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(JsonSource*, json_source_unref); + +static bool json_variant_is_magic(const JsonVariant *v) { + return v == JSON_VARIANT_MAGIC_TRUE || + v == JSON_VARIANT_MAGIC_FALSE || + v == JSON_VARIANT_MAGIC_NULL || + v == JSON_VARIANT_MAGIC_ZERO_INTEGER || + v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || + v == JSON_VARIANT_MAGIC_ZERO_REAL || + v == JSON_VARIANT_MAGIC_EMPTY_STRING || + v == JSON_VARIANT_MAGIC_EMPTY_ARRAY || + v == JSON_VARIANT_MAGIC_EMPTY_OBJECT; +} + +static JsonVariant *json_variant_dereference(JsonVariant *v) { + + /* Recursively dereference variants that are references to other variants */ + + if (!v) + return NULL; + + if (json_variant_is_magic(v)) + return v; + + if (!v->is_reference) + return v; + + return json_variant_dereference(v->reference); +} + +static JsonVariant *json_variant_normalize(JsonVariant *v) { + + /* Converts json variants to their normalized form, i.e. fully dereferenced and wherever possible converted to + * the "magic" version if there is one */ + + if (!v) + return NULL; + + v = json_variant_dereference(v); + + switch (json_variant_type(v)) { + + case JSON_VARIANT_BOOLEAN: + return json_variant_boolean(v) ? JSON_VARIANT_MAGIC_TRUE : JSON_VARIANT_MAGIC_FALSE; + + case JSON_VARIANT_NULL: + return JSON_VARIANT_MAGIC_NULL; + + case JSON_VARIANT_INTEGER: + return json_variant_integer(v) == 0 ? JSON_VARIANT_MAGIC_ZERO_INTEGER : v; + + case JSON_VARIANT_UNSIGNED: + return json_variant_unsigned(v) == 0 ? JSON_VARIANT_MAGIC_ZERO_UNSIGNED : v; + + case JSON_VARIANT_REAL: +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" + return json_variant_real(v) == 0.0 ? JSON_VARIANT_MAGIC_ZERO_REAL : v; +#pragma GCC diagnostic pop + + case JSON_VARIANT_STRING: + return isempty(json_variant_string(v)) ? JSON_VARIANT_MAGIC_EMPTY_STRING : v; + + case JSON_VARIANT_ARRAY: + return json_variant_elements(v) == 0 ? JSON_VARIANT_MAGIC_EMPTY_ARRAY : v; + + case JSON_VARIANT_OBJECT: + return json_variant_elements(v) == 0 ? JSON_VARIANT_MAGIC_EMPTY_OBJECT : v; + + default: + return v; + } +} + +static JsonVariant *json_variant_conservative_normalize(JsonVariant *v) { + + /* Much like json_variant_normalize(), but won't simplify if the variant has a source/line location attached to + * it, in order not to lose context */ + + if (!v) + return NULL; + + if (json_variant_is_magic(v)) + return v; + + if (v->source || v->line > 0 || v->column > 0) + return v; + + return json_variant_normalize(v); +} + +static int json_variant_new(JsonVariant **ret, JsonVariantType type, size_t space) { + JsonVariant *v; + + assert_return(ret, -EINVAL); + + v = malloc0(offsetof(JsonVariant, value) + space); + if (!v) + return -ENOMEM; + + v->n_ref = 1; + v->type = type; + + *ret = v; + return 0; +} + +int json_variant_new_integer(JsonVariant **ret, intmax_t i) { + JsonVariant *v; + int r; + + assert_return(ret, -EINVAL); + + if (i == 0) { + *ret = JSON_VARIANT_MAGIC_ZERO_INTEGER; + return 0; + } + + r = json_variant_new(&v, JSON_VARIANT_INTEGER, sizeof(i)); + if (r < 0) + return r; + + v->value.integer = i; + *ret = v; + + return 0; +} + +int json_variant_new_unsigned(JsonVariant **ret, uintmax_t u) { + JsonVariant *v; + int r; + + assert_return(ret, -EINVAL); + if (u == 0) { + *ret = JSON_VARIANT_MAGIC_ZERO_UNSIGNED; + return 0; + } + + r = json_variant_new(&v, JSON_VARIANT_UNSIGNED, sizeof(u)); + if (r < 0) + return r; + + v->value.unsig = u; + *ret = v; + + return 0; +} + +int json_variant_new_real(JsonVariant **ret, long double d) { + JsonVariant *v; + int r; + + assert_return(ret, -EINVAL); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" + if (d == 0.0) { +#pragma GCC diagnostic pop + *ret = JSON_VARIANT_MAGIC_ZERO_REAL; + return 0; + } + + r = json_variant_new(&v, JSON_VARIANT_REAL, sizeof(d)); + if (r < 0) + return r; + + v->value.real = d; + *ret = v; + + return 0; +} + +int json_variant_new_boolean(JsonVariant **ret, bool b) { + assert_return(ret, -EINVAL); + + if (b) + *ret = JSON_VARIANT_MAGIC_TRUE; + else + *ret = JSON_VARIANT_MAGIC_FALSE; + + return 0; +} + +int json_variant_new_null(JsonVariant **ret) { + assert_return(ret, -EINVAL); + + *ret = JSON_VARIANT_MAGIC_NULL; + return 0; +} + +int json_variant_new_stringn(JsonVariant **ret, const char *s, size_t n) { + JsonVariant *v; + int r; + + assert_return(ret, -EINVAL); + if (!s) { + assert_return(n == 0, -EINVAL); + return json_variant_new_null(ret); + } + if (n == 0) { + *ret = JSON_VARIANT_MAGIC_EMPTY_STRING; + return 0; + } + + r = json_variant_new(&v, JSON_VARIANT_STRING, n + 1); + if (r < 0) + return r; + + memcpy(v->string, s, n); + v->string[n] = 0; + + *ret = v; + return 0; +} + +static void json_variant_set(JsonVariant *a, JsonVariant *b) { + assert(a); + + b = json_variant_dereference(b); + if (!b) { + a->type = JSON_VARIANT_NULL; + return; + } + + a->type = json_variant_type(b); + switch (a->type) { + + case JSON_VARIANT_INTEGER: + a->value.integer = json_variant_integer(b); + break; + + case JSON_VARIANT_UNSIGNED: + a->value.unsig = json_variant_unsigned(b); + break; + + case JSON_VARIANT_REAL: + a->value.real = json_variant_real(b); + break; + + case JSON_VARIANT_BOOLEAN: + a->value.boolean = json_variant_boolean(b); + break; + + case JSON_VARIANT_STRING: { + const char *s; + + assert_se(s = json_variant_string(b)); + + /* Short strings we can store inline */ + if (strnlen(s, INLINE_STRING_MAX+1) <= INLINE_STRING_MAX) { + strcpy(a->string, s); + break; + } + + /* For longer strings, use a reference… */ + _fallthrough_; + } + + case JSON_VARIANT_ARRAY: + case JSON_VARIANT_OBJECT: + a->is_reference = true; + a->reference = json_variant_ref(json_variant_conservative_normalize(b)); + break; + + case JSON_VARIANT_NULL: + break; + + default: + assert_not_reached("Unexpected variant type"); + } +} + +static void json_variant_copy_source(JsonVariant *v, JsonVariant *from) { + assert(v); + assert(from); + + if (json_variant_is_magic(from)) + return; + + v->line = from->line; + v->column = from->column; + v->source = json_source_ref(from->source); +} + +int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) { + JsonVariant *v; + size_t i; + + assert_return(ret, -EINVAL); + if (n == 0) { + *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY; + return 0; + } + assert_return(array, -EINVAL); + + v = new(JsonVariant, n + 1); + if (!v) + return -ENOMEM; + + *v = (JsonVariant) { + .n_ref = 1, + .type = JSON_VARIANT_ARRAY, + .n_elements = n, + }; + + for (i = 0; i < n; i++) { + JsonVariant *w = v + 1 + i; + + *w = (JsonVariant) { + .is_embedded = true, + .parent = v, + }; + + json_variant_set(w, array[i]); + json_variant_copy_source(w, array[i]); + } + + *ret = v; + return 0; +} + +int json_variant_new_array_bytes(JsonVariant **ret, const void *p, size_t n) { + JsonVariant *v; + size_t i; + + assert_return(ret, -EINVAL); + if (n == 0) { + *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY; + return 0; + } + assert_return(p, -EINVAL); + + v = new(JsonVariant, n + 1); + if (!v) + return -ENOMEM; + + *v = (JsonVariant) { + .n_ref = 1, + .type = JSON_VARIANT_ARRAY, + .n_elements = n, + }; + + for (i = 0; i < n; i++) { + JsonVariant *w = v + 1 + i; + + *w = (JsonVariant) { + .is_embedded = true, + .parent = v, + }; + + w->type = JSON_VARIANT_UNSIGNED; + w->value.unsig = ((const uint8_t*) p)[i]; + } + + *ret = v; + return 0; +} + +int json_variant_new_array_strv(JsonVariant **ret, char **l) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + size_t n; + int r; + + assert(ret); + + n = strv_length(l); + if (n == 0) { + *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY; + return 0; + } + + v = new0(JsonVariant, n + 1); + if (!v) + return -ENOMEM; + + *v = (JsonVariant) { + .n_ref = 1, + .type = JSON_VARIANT_ARRAY, + }; + + for (v->n_elements = 0; v->n_elements < n; v->n_elements++) { + JsonVariant *w = v + 1 + v->n_elements; + size_t k; + + w->is_embedded = true; + w->parent = v; + w->type = JSON_VARIANT_STRING; + + k = strlen(l[v->n_elements]); + + if (k > INLINE_STRING_MAX) { + /* If string is too long, store it as reference. */ + + r = json_variant_new_stringn(&w->reference, l[v->n_elements], k); + if (r < 0) + return r; + + w->is_reference = true; + } else + memcpy(w->string, l[v->n_elements], k+1); + } + + *ret = TAKE_PTR(v); + return 0; +} + +int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) { + JsonVariant *v; + size_t i; + + assert_return(ret, -EINVAL); + if (n == 0) { + *ret = JSON_VARIANT_MAGIC_EMPTY_OBJECT; + return 0; + } + assert_return(array, -EINVAL); + assert_return(n % 2 == 0, -EINVAL); + + v = new(JsonVariant, n + 1); + if (!v) + return -ENOMEM; + + *v = (JsonVariant) { + .n_ref = 1, + .type = JSON_VARIANT_OBJECT, + .n_elements = n, + }; + + for (i = 0; i < n; i++) { + JsonVariant *w = v + 1 + i; + + *w = (JsonVariant) { + .is_embedded = true, + .parent = v, + }; + + json_variant_set(w, array[i]); + json_variant_copy_source(w, array[i]); + } + + *ret = v; + return 0; +} + +static void json_variant_free_inner(JsonVariant *v) { + assert(v); + + if (json_variant_is_magic(v)) + return; + + json_source_unref(v->source); + + if (v->is_reference) { + json_variant_unref(v->reference); + return; + } + + if (IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT)) { + size_t i; + + for (i = 0; i < v->n_elements; i++) + json_variant_free_inner(v + 1 + i); + } +} + +JsonVariant *json_variant_ref(JsonVariant *v) { + if (!v) + return NULL; + if (json_variant_is_magic(v)) + return v; + + if (v->is_embedded) + json_variant_ref(v->parent); /* ref the compounding variant instead */ + else { + assert(v->n_ref > 0); + v->n_ref++; + } + + return v; +} + +JsonVariant *json_variant_unref(JsonVariant *v) { + if (!v) + return NULL; + if (json_variant_is_magic(v)) + return NULL; + + if (v->is_embedded) + json_variant_unref(v->parent); + else { + assert(v->n_ref > 0); + v->n_ref--; + + if (v->n_ref == 0) { + json_variant_free_inner(v); + free(v); + } + } + + return NULL; +} + +void json_variant_unref_many(JsonVariant **array, size_t n) { + size_t i; + + assert(array || n == 0); + + for (i = 0; i < n; i++) + json_variant_unref(array[i]); +} + +const char *json_variant_string(JsonVariant *v) { + if (!v) + return NULL; + if (v == JSON_VARIANT_MAGIC_EMPTY_STRING) + return ""; + if (json_variant_is_magic(v)) + goto mismatch; + if (v->is_reference) + return json_variant_string(v->reference); + if (v->type != JSON_VARIANT_STRING) + goto mismatch; + + return v->string; + +mismatch: + log_debug("Non-string JSON variant requested as string, returning NULL."); + return NULL; +} + +bool json_variant_boolean(JsonVariant *v) { + if (!v) + goto mismatch; + if (v == JSON_VARIANT_MAGIC_TRUE) + return true; + if (v == JSON_VARIANT_MAGIC_FALSE) + return false; + if (json_variant_is_magic(v)) + goto mismatch; + if (v->type != JSON_VARIANT_BOOLEAN) + goto mismatch; + if (v->is_reference) + return json_variant_boolean(v->reference); + + return v->value.boolean; + +mismatch: + log_debug("Non-boolean JSON variant requested as boolean, returning false."); + return false; +} + +intmax_t json_variant_integer(JsonVariant *v) { + if (!v) + goto mismatch; + if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER || + v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || + v == JSON_VARIANT_MAGIC_ZERO_REAL) + return 0; + if (json_variant_is_magic(v)) + goto mismatch; + if (v->is_reference) + return json_variant_integer(v->reference); + + switch (v->type) { + + case JSON_VARIANT_INTEGER: + return v->value.integer; + + case JSON_VARIANT_UNSIGNED: + if (v->value.unsig <= INTMAX_MAX) + return (intmax_t) v->value.unsig; + + log_debug("Unsigned integer %ju requested as signed integer and out of range, returning 0.", v->value.unsig); + return 0; + + case JSON_VARIANT_REAL: { + intmax_t converted; + + converted = (intmax_t) v->value.real; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" + if ((long double) converted == v->value.real) +#pragma GCC diagnostic pop + return converted; + + log_debug("Real %Lg requested as integer, and cannot be converted losslessly, returning 0.", v->value.real); + return 0; + } + + default: + break; + } + +mismatch: + log_debug("Non-integer JSON variant requested as integer, returning 0."); + return 0; +} + +uintmax_t json_variant_unsigned(JsonVariant *v) { + if (!v) + goto mismatch; + if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER || + v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || + v == JSON_VARIANT_MAGIC_ZERO_REAL) + return 0; + if (json_variant_is_magic(v)) + goto mismatch; + if (v->is_reference) + return json_variant_integer(v->reference); + + switch (v->type) { + + case JSON_VARIANT_INTEGER: + if (v->value.integer >= 0) + return (uintmax_t) v->value.integer; + + log_debug("Signed integer %ju requested as unsigned integer and out of range, returning 0.", v->value.integer); + return 0; + + case JSON_VARIANT_UNSIGNED: + return v->value.unsig; + + case JSON_VARIANT_REAL: { + uintmax_t converted; + + converted = (uintmax_t) v->value.real; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" + if ((long double) converted == v->value.real) +#pragma GCC diagnostic pop + return converted; + + log_debug("Real %Lg requested as unsigned integer, and cannot be converted losslessly, returning 0.", v->value.real); + return 0; + } + + default: + break; + } + +mismatch: + log_debug("Non-integer JSON variant requested as unsigned, returning 0."); + return 0; +} + +long double json_variant_real(JsonVariant *v) { + if (!v) + return 0.0; + if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER || + v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || + v == JSON_VARIANT_MAGIC_ZERO_REAL) + return 0.0; + if (json_variant_is_magic(v)) + goto mismatch; + if (v->is_reference) + return json_variant_real(v->reference); + + switch (v->type) { + + case JSON_VARIANT_REAL: + return v->value.real; + + case JSON_VARIANT_INTEGER: { + long double converted; + + converted = (long double) v->value.integer; + + if ((intmax_t) converted == v->value.integer) + return converted; + + log_debug("Signed integer %ji requested as real, and cannot be converted losslessly, returning 0.", v->value.integer); + return 0.0; + } + + case JSON_VARIANT_UNSIGNED: { + long double converted; + + converted = (long double) v->value.unsig; + + if ((uintmax_t) converted == v->value.unsig) + return converted; + + log_debug("Unsigned integer %ju requested as real, and cannot be converted losslessly, returning 0.", v->value.unsig); + return 0.0; + } + + default: + break; + } + +mismatch: + log_debug("Non-integer JSON variant requested as integer, returning 0."); + return 0; +} + +bool json_variant_is_negative(JsonVariant *v) { + if (!v) + goto mismatch; + if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER || + v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || + v == JSON_VARIANT_MAGIC_ZERO_REAL) + return false; + if (json_variant_is_magic(v)) + goto mismatch; + if (v->is_reference) + return json_variant_is_negative(v->reference); + + /* This function is useful as checking whether numbers are negative is pretty complex since we have three types + * of numbers. And some JSON code (OCI for example) uses negative numbers to mark "not defined" numeric + * values. */ + + switch (v->type) { + + case JSON_VARIANT_REAL: + return v->value.real < 0; + + case JSON_VARIANT_INTEGER: + return v->value.integer < 0; + + case JSON_VARIANT_UNSIGNED: + return false; + + default: + break; + } + +mismatch: + log_debug("Non-integer JSON variant tested for negativity, returning false."); + return false; +} + +JsonVariantType json_variant_type(JsonVariant *v) { + + if (!v) + return _JSON_VARIANT_TYPE_INVALID; + + if (v == JSON_VARIANT_MAGIC_TRUE || v == JSON_VARIANT_MAGIC_FALSE) + return JSON_VARIANT_BOOLEAN; + + if (v == JSON_VARIANT_MAGIC_NULL) + return JSON_VARIANT_NULL; + + if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER) + return JSON_VARIANT_INTEGER; + + if (v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED) + return JSON_VARIANT_UNSIGNED; + + if (v == JSON_VARIANT_MAGIC_ZERO_REAL) + return JSON_VARIANT_REAL; + + if (v == JSON_VARIANT_MAGIC_EMPTY_STRING) + return JSON_VARIANT_STRING; + + if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY) + return JSON_VARIANT_ARRAY; + + if (v == JSON_VARIANT_MAGIC_EMPTY_OBJECT) + return JSON_VARIANT_OBJECT; + + return v->type; +} + +bool json_variant_has_type(JsonVariant *v, JsonVariantType type) { + JsonVariantType rt; + + v = json_variant_dereference(v); + + rt = json_variant_type(v); + if (rt == type) + return true; + + /* All three magic zeroes qualify as integer, unsigned and as real */ + if ((v == JSON_VARIANT_MAGIC_ZERO_INTEGER || v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || v == JSON_VARIANT_MAGIC_ZERO_REAL) && + IN_SET(type, JSON_VARIANT_INTEGER, JSON_VARIANT_UNSIGNED, JSON_VARIANT_REAL, JSON_VARIANT_NUMBER)) + return true; + + /* All other magic variant types are only equal to themselves */ + if (json_variant_is_magic(v)) + return false; + + /* Handle the "number" pseudo type */ + if (type == JSON_VARIANT_NUMBER) + return IN_SET(rt, JSON_VARIANT_INTEGER, JSON_VARIANT_UNSIGNED, JSON_VARIANT_REAL); + + /* Integer conversions are OK in many cases */ + if (rt == JSON_VARIANT_INTEGER && type == JSON_VARIANT_UNSIGNED) + return v->value.integer >= 0; + if (rt == JSON_VARIANT_UNSIGNED && type == JSON_VARIANT_INTEGER) + return v->value.unsig <= INTMAX_MAX; + + /* Any integer that can be converted lossley to a real and back may also be considered a real */ + if (rt == JSON_VARIANT_INTEGER && type == JSON_VARIANT_REAL) + return (intmax_t) (long double) v->value.integer == v->value.integer; + if (rt == JSON_VARIANT_UNSIGNED && type == JSON_VARIANT_REAL) + return (uintmax_t) (long double) v->value.unsig == v->value.unsig; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" + /* Any real that can be converted losslessly to an integer and back may also be considered an integer */ + if (rt == JSON_VARIANT_REAL && type == JSON_VARIANT_INTEGER) + return (long double) (intmax_t) v->value.real == v->value.real; + if (rt == JSON_VARIANT_REAL && type == JSON_VARIANT_UNSIGNED) + return (long double) (uintmax_t) v->value.real == v->value.real; +#pragma GCC diagnostic pop + + return false; +} + +size_t json_variant_elements(JsonVariant *v) { + if (!v) + return 0; + if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY || + v == JSON_VARIANT_MAGIC_EMPTY_OBJECT) + return 0; + if (json_variant_is_magic(v)) + goto mismatch; + if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT)) + goto mismatch; + if (v->is_reference) + return json_variant_elements(v->reference); + + return v->n_elements; + +mismatch: + log_debug("Number of elements in non-array/non-object JSON variant requested, returning 0."); + return 0; +} + +JsonVariant *json_variant_by_index(JsonVariant *v, size_t idx) { + if (!v) + return NULL; + if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY || + v == JSON_VARIANT_MAGIC_EMPTY_OBJECT) + return NULL; + if (json_variant_is_magic(v)) + goto mismatch; + if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT)) + goto mismatch; + if (v->is_reference) + return json_variant_by_index(v->reference, idx); + if (idx >= v->n_elements) + return NULL; + + return json_variant_conservative_normalize(v + 1 + idx); + +mismatch: + log_debug("Element in non-array/non-object JSON variant requested by index, returning NULL."); + return NULL; +} + +JsonVariant *json_variant_by_key_full(JsonVariant *v, const char *key, JsonVariant **ret_key) { + size_t i; + + if (!v) + goto not_found; + if (!key) + goto not_found; + if (v == JSON_VARIANT_MAGIC_EMPTY_OBJECT) + goto not_found; + if (json_variant_is_magic(v)) + goto mismatch; + if (v->type != JSON_VARIANT_OBJECT) + goto mismatch; + if (v->is_reference) + return json_variant_by_key(v->reference, key); + + for (i = 0; i < v->n_elements; i += 2) { + JsonVariant *p; + + p = json_variant_dereference(v + 1 + i); + + if (!json_variant_has_type(p, JSON_VARIANT_STRING)) + continue; + + if (streq(json_variant_string(p), key)) { + + if (ret_key) + *ret_key = json_variant_conservative_normalize(v + 1 + i); + + return json_variant_conservative_normalize(v + 1 + i + 1); + } + } + +not_found: + if (ret_key) + *ret_key = NULL; + + return NULL; + +mismatch: + log_debug("Element in non-object JSON variant requested by key, returning NULL."); + if (ret_key) + *ret_key = NULL; + + return NULL; +} + +JsonVariant *json_variant_by_key(JsonVariant *v, const char *key) { + return json_variant_by_key_full(v, key, NULL); +} + +bool json_variant_equal(JsonVariant *a, JsonVariant *b) { + JsonVariantType t; + + a = json_variant_normalize(a); + b = json_variant_normalize(b); + + if (a == b) + return true; + + t = json_variant_type(a); + if (!json_variant_has_type(b, t)) + return false; + + switch (t) { + + case JSON_VARIANT_STRING: + return streq(json_variant_string(a), json_variant_string(b)); + + case JSON_VARIANT_INTEGER: + return json_variant_integer(a) == json_variant_integer(b); + + case JSON_VARIANT_UNSIGNED: + return json_variant_unsigned(a) == json_variant_unsigned(b); + + case JSON_VARIANT_REAL: +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" + return json_variant_real(a) == json_variant_real(b); +#pragma GCC diagnostic pop + + case JSON_VARIANT_BOOLEAN: + return json_variant_boolean(a) == json_variant_boolean(b); + + case JSON_VARIANT_NULL: + return true; + + case JSON_VARIANT_ARRAY: { + size_t i, n; + + n = json_variant_elements(a); + if (n != json_variant_elements(b)) + return false; + + for (i = 0; i < n; i++) { + if (!json_variant_equal(json_variant_by_index(a, i), json_variant_by_index(b, i))) + return false; + } + + return true; + } + + case JSON_VARIANT_OBJECT: { + size_t i, n; + + n = json_variant_elements(a); + if (n != json_variant_elements(b)) + return false; + + /* Iterate through all keys in 'a' */ + for (i = 0; i < n; i += 2) { + bool found = false; + size_t j; + + /* Match them against all keys in 'b' */ + for (j = 0; j < n; j += 2) { + JsonVariant *key_b; + + key_b = json_variant_by_index(b, j); + + /* During the first iteration unmark everything */ + if (i == 0) + key_b->is_marked = false; + else if (key_b->is_marked) /* In later iterations if we already marked something, don't bother with it again */ + continue; + + if (found) + continue; + + if (json_variant_equal(json_variant_by_index(a, i), key_b) && + json_variant_equal(json_variant_by_index(a, i+1), json_variant_by_index(b, j+1))) { + /* Key and values match! */ + key_b->is_marked = found = true; + + /* In the first iteration we continue the inner loop since we want to mark + * everything, otherwise exit the loop quickly after we found what we were + * looking for. */ + if (i != 0) + break; + } + } + + if (!found) + return false; + } + + return true; + } + + default: + assert_not_reached("Unknown variant type."); + } +} + +int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column) { + assert_return(v, -EINVAL); + + if (ret_source) + *ret_source = json_variant_is_magic(v) || !v->source ? NULL : v->source->name; + + if (ret_line) + *ret_line = json_variant_is_magic(v) ? 0 : v->line; + + if (ret_column) + *ret_column = json_variant_is_magic(v) ? 0 : v->column; + + return 0; +} + +static int print_source(FILE *f, JsonVariant *v, unsigned flags, bool whitespace) { + size_t w, k; + + if (!FLAGS_SET(flags, JSON_FORMAT_SOURCE|JSON_FORMAT_PRETTY)) + return 0; + + if (json_variant_is_magic(v)) + return 0; + + if (!v->source && v->line == 0 && v->column == 0) + return 0; + + /* The max width we need to format the line numbers for this source file */ + w = (v->source && v->source->max_line > 0) ? + DECIMAL_STR_WIDTH(v->source->max_line) : + DECIMAL_STR_MAX(unsigned)-1; + k = (v->source && v->source->max_column > 0) ? + DECIMAL_STR_WIDTH(v->source->max_column) : + DECIMAL_STR_MAX(unsigned) -1; + + if (whitespace) { + size_t i, n; + + n = 1 + (v->source ? strlen(v->source->name) : 0) + + ((v->source && (v->line > 0 || v->column > 0)) ? 1 : 0) + + (v->line > 0 ? w : 0) + + (((v->source || v->line > 0) && v->column > 0) ? 1 : 0) + + (v->column > 0 ? k : 0) + + 2; + + for (i = 0; i < n; i++) + fputc(' ', f); + } else { + fputc('[', f); + + if (v->source) + fputs(v->source->name, f); + if (v->source && (v->line > 0 || v->column > 0)) + fputc(':', f); + if (v->line > 0) + fprintf(f, "%*u", (int) w, v->line); + if ((v->source || v->line > 0) || v->column > 0) + fputc(':', f); + if (v->column > 0) + fprintf(f, "%*u", (int) k, v->column); + + fputc(']', f); + fputc(' ', f); + } + + return 0; +} + +static int json_format(FILE *f, JsonVariant *v, unsigned flags, const char *prefix) { + int r; + + assert(f); + assert(v); + + switch (json_variant_type(v)) { + + case JSON_VARIANT_REAL: { + locale_t loc; + + loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); + if (loc == (locale_t) 0) + return -errno; + + if (flags & JSON_FORMAT_COLOR) + fputs(ANSI_HIGHLIGHT_BLUE, f); + + fprintf(f, "%.*Le", DECIMAL_DIG, json_variant_real(v)); + + if (flags & JSON_FORMAT_COLOR) + fputs(ANSI_NORMAL, f); + + freelocale(loc); + break; + } + + case JSON_VARIANT_INTEGER: + if (flags & JSON_FORMAT_COLOR) + fputs(ANSI_HIGHLIGHT_BLUE, f); + + fprintf(f, "%" PRIdMAX, json_variant_integer(v)); + + if (flags & JSON_FORMAT_COLOR) + fputs(ANSI_NORMAL, f); + break; + + case JSON_VARIANT_UNSIGNED: + if (flags & JSON_FORMAT_COLOR) + fputs(ANSI_HIGHLIGHT_BLUE, f); + + fprintf(f, "%" PRIuMAX, json_variant_unsigned(v)); + + if (flags & JSON_FORMAT_COLOR) + fputs(ANSI_NORMAL, f); + break; + + case JSON_VARIANT_BOOLEAN: + + if (flags & JSON_FORMAT_COLOR) + fputs(ANSI_HIGHLIGHT, f); + + if (json_variant_boolean(v)) + fputs("true", f); + else + fputs("false", f); + + if (flags & JSON_FORMAT_COLOR) + fputs(ANSI_NORMAL, f); + + break; + + case JSON_VARIANT_NULL: + if (flags & JSON_FORMAT_COLOR) + fputs(ANSI_HIGHLIGHT, f); + + fputs("null", f); + + if (flags & JSON_FORMAT_COLOR) + fputs(ANSI_NORMAL, f); + break; + + case JSON_VARIANT_STRING: { + const char *q; + + fputc('"', f); + + if (flags & JSON_FORMAT_COLOR) + fputs(ANSI_GREEN, f); + + for (q = json_variant_string(v); *q; q++) { + + switch (*q) { + + case '"': + fputs("\\\"", f); + break; + + case '\\': + fputs("\\\\", f); + break; + + case '/': + fputs("\\/", f); + break; + + case '\b': + fputs("\\b", f); + break; + + case '\f': + fputs("\\f", f); + break; + + case '\n': + fputs("\\n", f); + break; + + case '\r': + fputs("\\r", f); + break; + + case '\t': + fputs("\\t", f); + break; + + default: + if (*q >= 0 && *q < ' ') + fprintf(f, "\\u%04x", *q); + else + fputc(*q, f); + break; + } + } + + if (flags & JSON_FORMAT_COLOR) + fputs(ANSI_NORMAL, f); + + fputc('"', f); + break; + } + + case JSON_VARIANT_ARRAY: { + size_t i, n; + + n = json_variant_elements(v); + + if (n == 0) + fputs("[]", f); + else { + const char *prefix2; + + if (flags & JSON_FORMAT_PRETTY) { + prefix2 = strjoina(strempty(prefix), "\t"); + fputs("[\n", f); + } else { + prefix2 = strempty(prefix); + fputc('[', f); + } + + for (i = 0; i < n; i++) { + JsonVariant *e; + + assert_se(e = json_variant_by_index(v, i)); + + if (i > 0) { + if (flags & JSON_FORMAT_PRETTY) + fputs(",\n", f); + else + fputc(',', f); + } + + if (flags & JSON_FORMAT_PRETTY) { + print_source(f, e, flags, false); + fputs(prefix2, f); + } + + r = json_format(f, e, flags, prefix2); + if (r < 0) + return r; + } + + if (flags & JSON_FORMAT_PRETTY) { + fputc('\n', f); + print_source(f, v, flags, true); + fputs(strempty(prefix), f); + } + + fputc(']', f); + } + break; + } + + case JSON_VARIANT_OBJECT: { + size_t i, n; + + n = json_variant_elements(v); + + if (n == 0) + fputs("{}", f); + else { + const char *prefix2; + + if (flags & JSON_FORMAT_PRETTY) { + prefix2 = strjoina(strempty(prefix), "\t"); + fputs("{\n", f); + } else { + prefix2 = strempty(prefix); + fputc('{', f); + } + + for (i = 0; i < n; i += 2) { + JsonVariant *e; + + e = json_variant_by_index(v, i); + + if (i > 0) { + if (flags & JSON_FORMAT_PRETTY) + fputs(",\n", f); + else + fputc(',', f); + } + + if (flags & JSON_FORMAT_PRETTY) { + print_source(f, e, flags, false); + fputs(prefix2, f); + } + + r = json_format(f, e, flags, prefix2); + if (r < 0) + return r; + + fputs(flags & JSON_FORMAT_PRETTY ? " : " : ":", f); + + r = json_format(f, json_variant_by_index(v, i+1), flags, prefix2); + if (r < 0) + return r; + } + + if (flags & JSON_FORMAT_PRETTY) { + fputc('\n', f); + print_source(f, v, flags, true); + fputs(strempty(prefix), f); + } + + fputc('}', f); + } + break; + } + + default: + assert_not_reached("Unexpected variant type."); + } + + return 0; +} + +int json_variant_format(JsonVariant *v, unsigned flags, char **ret) { + _cleanup_free_ char *s = NULL; + size_t sz = 0; + int r; + + assert_return(v, -EINVAL); + assert_return(ret, -EINVAL); + + { + _cleanup_fclose_ FILE *f = NULL; + + f = open_memstream(&s, &sz); + if (!f) + return -ENOMEM; + + (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + + json_variant_dump(v, flags, f, NULL); + + r = fflush_and_check(f); + } + if (r < 0) + return r; + + assert(s); + *ret = TAKE_PTR(s); + + return (int) sz; +} + +void json_variant_dump(JsonVariant *v, unsigned flags, FILE *f, const char *prefix) { + if (!v) + return; + + if (!f) + f = stdout; + + print_source(f, v, flags, false); + + if (flags & JSON_FORMAT_SSE) + fputs("data: ", f); + if (flags & JSON_FORMAT_SEQ) + fputc('\x1e', f); /* ASCII Record Separator */ + + json_format(f, v, flags, prefix); + + if (flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_SEQ|JSON_FORMAT_SSE|JSON_FORMAT_NEWLINE)) + fputc('\n', f); + if (flags & JSON_FORMAT_SSE) + fputc('\n', f); /* In case of SSE add a second newline */ +} + +static int json_variant_copy(JsonVariant **nv, JsonVariant *v) { + JsonVariantType t; + JsonVariant *c; + JsonValue value; + const void *source; + size_t k; + + assert(nv); + assert(v); + + /* Let's copy the simple types literally, and the larger types by references */ + t = json_variant_type(v); + switch (t) { + case JSON_VARIANT_INTEGER: + k = sizeof(intmax_t); + value.integer = json_variant_integer(v); + source = &value; + break; + + case JSON_VARIANT_UNSIGNED: + k = sizeof(uintmax_t); + value.unsig = json_variant_unsigned(v); + source = &value; + break; + + case JSON_VARIANT_REAL: + k = sizeof(long double); + value.real = json_variant_real(v); + source = &value; + break; + + case JSON_VARIANT_BOOLEAN: + k = sizeof(bool); + value.boolean = json_variant_boolean(v); + source = &value; + break; + + case JSON_VARIANT_NULL: + k = 0; + source = NULL; + break; + + case JSON_VARIANT_STRING: + source = json_variant_string(v); + k = strnlen(source, INLINE_STRING_MAX + 1); + if (k <= INLINE_STRING_MAX) { + k ++; + break; + } + + _fallthrough_; + + default: + /* Everything else copy by reference */ + + c = malloc0(offsetof(JsonVariant, reference) + sizeof(JsonVariant*)); + if (!c) + return -ENOMEM; + + c->n_ref = 1; + c->type = t; + c->is_reference = true; + c->reference = json_variant_ref(json_variant_normalize(v)); + + *nv = c; + return 0; + } + + c = malloc0(offsetof(JsonVariant, value) + k); + if (!c) + return -ENOMEM; + + c->n_ref = 1; + c->type = t; + + memcpy_safe(&c->value, source, k); + + *nv = c; + return 0; +} + +static bool json_single_ref(JsonVariant *v) { + + /* Checks whether the caller is the single owner of the object, i.e. can get away with changing it */ + + if (json_variant_is_magic(v)) + return false; + + if (v->is_embedded) + return json_single_ref(v->parent); + + assert(v->n_ref > 0); + return v->n_ref == 1; +} + +static int json_variant_set_source(JsonVariant **v, JsonSource *source, unsigned line, unsigned column) { + JsonVariant *w; + int r; + + assert(v); + + /* Patch in source and line/column number. Tries to do this in-place if the caller is the sole referencer of + * the object. If not, allocates a new object, possibly a surrogate for the original one */ + + if (!*v) + return 0; + + if (source && line > source->max_line) + source->max_line = line; + if (source && column > source->max_column) + source->max_column = column; + + if (json_variant_is_magic(*v)) { + + if (!source && line == 0 && column == 0) + return 0; + + } else { + if (json_source_equal((*v)->source, source) && + (*v)->line == line && + (*v)->column == column) + return 0; + + if (json_single_ref(*v)) { /* Sole reference? */ + json_source_unref((*v)->source); + (*v)->source = json_source_ref(source); + (*v)->line = line; + (*v)->column = column; + return 1; + } + } + + r = json_variant_copy(&w, *v); + if (r < 0) + return r; + + assert(!json_variant_is_magic(w)); + assert(!w->is_embedded); + assert(w->n_ref == 1); + assert(!w->source); + + w->source = json_source_ref(source); + w->line = line; + w->column = column; + + json_variant_unref(*v); + *v = w; + + return 1; +} + +static void inc_lines_columns(unsigned *line, unsigned *column, const char *s, size_t n) { + assert(line); + assert(column); + assert(s || n == 0); + + while (n > 0) { + + if (*s == '\n') { + (*line)++; + *column = 1; + } else if (*s >= 0 && *s < 127) /* Process ASCII chars quickly */ + (*column)++; + else { + int w; + + w = utf8_encoded_valid_unichar(s); + if (w < 0) /* count invalid unichars as normal characters */ + w = 1; + else if ((size_t) w > n) /* never read more than the specified number of characters */ + w = (int) n; + + (*column)++; + + s += w; + n -= w; + continue; + } + + s++; + n--; + } +} + +static int unhex_ucs2(const char *c, uint16_t *ret) { + int aa, bb, cc, dd; + uint16_t x; + + assert(c); + assert(ret); + + aa = unhexchar(c[0]); + if (aa < 0) + return -EINVAL; + + bb = unhexchar(c[1]); + if (bb < 0) + return -EINVAL; + + cc = unhexchar(c[2]); + if (cc < 0) + return -EINVAL; + + dd = unhexchar(c[3]); + if (dd < 0) + return -EINVAL; + + x = ((uint16_t) aa << 12) | + ((uint16_t) bb << 8) | + ((uint16_t) cc << 4) | + ((uint16_t) dd); + + if (x <= 0) + return -EINVAL; + + *ret = x; + + return 0; +} + +static int json_parse_string(const char **p, char **ret) { + _cleanup_free_ char *s = NULL; + size_t n = 0, allocated = 0; + const char *c; + + assert(p); + assert(*p); + assert(ret); + + c = *p; + + if (*c != '"') + return -EINVAL; + + c++; + + for (;;) { + int len; + + /* Check for EOF */ + if (*c == 0) + return -EINVAL; + + /* Check for control characters 0x00..0x1f */ + if (*c > 0 && *c < ' ') + return -EINVAL; + + /* Check for control character 0x7f */ + if (*c == 0x7f) + return -EINVAL; + + if (*c == '"') { + if (!s) { + s = strdup(""); + if (!s) + return -ENOMEM; + } else + s[n] = 0; + + *p = c + 1; + + *ret = s; + s = NULL; + return JSON_TOKEN_STRING; + } + + if (*c == '\\') { + char ch = 0; + c++; + + if (*c == 0) + return -EINVAL; + + if (IN_SET(*c, '"', '\\', '/')) + ch = *c; + else if (*c == 'b') + ch = '\b'; + else if (*c == 'f') + ch = '\f'; + else if (*c == 'n') + ch = '\n'; + else if (*c == 'r') + ch = '\r'; + else if (*c == 't') + ch = '\t'; + else if (*c == 'u') { + char16_t x; + int r; + + r = unhex_ucs2(c + 1, &x); + if (r < 0) + return r; + + c += 5; + + if (!GREEDY_REALLOC(s, allocated, n + 5)) + return -ENOMEM; + + if (!utf16_is_surrogate(x)) + n += utf8_encode_unichar(s + n, (char32_t) x); + else if (utf16_is_trailing_surrogate(x)) + return -EINVAL; + else { + char16_t y; + + if (c[0] != '\\' || c[1] != 'u') + return -EINVAL; + + r = unhex_ucs2(c + 2, &y); + if (r < 0) + return r; + + c += 6; + + if (!utf16_is_trailing_surrogate(y)) + return -EINVAL; + + n += utf8_encode_unichar(s + n, utf16_surrogate_pair_to_unichar(x, y)); + } + + continue; + } else + return -EINVAL; + + if (!GREEDY_REALLOC(s, allocated, n + 2)) + return -ENOMEM; + + s[n++] = ch; + c ++; + continue; + } + + len = utf8_encoded_valid_unichar(c); + if (len < 0) + return len; + + if (!GREEDY_REALLOC(s, allocated, n + len + 1)) + return -ENOMEM; + + memcpy(s + n, c, len); + n += len; + c += len; + } +} + +static int json_parse_number(const char **p, JsonValue *ret) { + bool negative = false, exponent_negative = false, is_real = false; + long double x = 0.0, y = 0.0, exponent = 0.0, shift = 1.0; + intmax_t i = 0; + uintmax_t u = 0; + const char *c; + + assert(p); + assert(*p); + assert(ret); + + c = *p; + + if (*c == '-') { + negative = true; + c++; + } + + if (*c == '0') + c++; + else { + if (!strchr("123456789", *c) || *c == 0) + return -EINVAL; + + do { + if (!is_real) { + if (negative) { + + if (i < INTMAX_MIN / 10) /* overflow */ + is_real = true; + else { + intmax_t t = 10 * i; + + if (t < INTMAX_MIN + (*c - '0')) /* overflow */ + is_real = true; + else + i = t - (*c - '0'); + } + } else { + if (u > UINTMAX_MAX / 10) /* overflow */ + is_real = true; + else { + uintmax_t t = 10 * u; + + if (t > UINTMAX_MAX - (*c - '0')) /* overflow */ + is_real = true; + else + u = t + (*c - '0'); + } + } + } + + x = 10.0 * x + (*c - '0'); + + c++; + } while (strchr("0123456789", *c) && *c != 0); + } + + if (*c == '.') { + is_real = true; + c++; + + if (!strchr("0123456789", *c) || *c == 0) + return -EINVAL; + + do { + y = 10.0 * y + (*c - '0'); + shift = 10.0 * shift; + c++; + } while (strchr("0123456789", *c) && *c != 0); + } + + if (*c == 'e' || *c == 'E') { + is_real = true; + c++; + + if (*c == '-') { + exponent_negative = true; + c++; + } else if (*c == '+') + c++; + + if (!strchr("0123456789", *c) || *c == 0) + return -EINVAL; + + do { + exponent = 10.0 * exponent + (*c - '0'); + c++; + } while (strchr("0123456789", *c) && *c != 0); + } + + *p = c; + + if (is_real) { + ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * exp10l((exponent_negative ? -1.0 : 1.0) * exponent); + return JSON_TOKEN_REAL; + } else if (negative) { + ret->integer = i; + return JSON_TOKEN_INTEGER; + } else { + ret->unsig = u; + return JSON_TOKEN_UNSIGNED; + } +} + +int json_tokenize( + const char **p, + char **ret_string, + JsonValue *ret_value, + unsigned *ret_line, /* 'ret_line' returns the line at the beginning of this token */ + unsigned *ret_column, + void **state, + unsigned *line, /* 'line' is used as a line state, it always reflect the line we are at after the token was read */ + unsigned *column) { + + unsigned start_line, start_column; + const char *start, *c; + size_t n; + int t, r; + + enum { + STATE_NULL, + STATE_VALUE, + STATE_VALUE_POST, + }; + + assert(p); + assert(*p); + assert(ret_string); + assert(ret_value); + assert(ret_line); + assert(ret_column); + assert(line); + assert(column); + assert(state); + + t = PTR_TO_INT(*state); + if (t == STATE_NULL) { + *line = 1; + *column = 1; + t = STATE_VALUE; + } + + /* Skip over the whitespace */ + n = strspn(*p, WHITESPACE); + inc_lines_columns(line, column, *p, n); + c = *p + n; + + /* Remember where we started processing this token */ + start = c; + start_line = *line; + start_column = *column; + + if (*c == 0) { + *ret_string = NULL; + *ret_value = JSON_VALUE_NULL; + r = JSON_TOKEN_END; + goto finish; + } + + switch (t) { + + case STATE_VALUE: + + if (*c == '{') { + c++; + *state = INT_TO_PTR(STATE_VALUE); + r = JSON_TOKEN_OBJECT_OPEN; + goto null_return; + + } else if (*c == '}') { + c++; + *state = INT_TO_PTR(STATE_VALUE_POST); + r = JSON_TOKEN_OBJECT_CLOSE; + goto null_return; + + } else if (*c == '[') { + c++; + *state = INT_TO_PTR(STATE_VALUE); + r = JSON_TOKEN_ARRAY_OPEN; + goto null_return; + + } else if (*c == ']') { + c++; + *state = INT_TO_PTR(STATE_VALUE_POST); + r = JSON_TOKEN_ARRAY_CLOSE; + goto null_return; + + } else if (*c == '"') { + + r = json_parse_string(&c, ret_string); + if (r < 0) + return r; + + *ret_value = JSON_VALUE_NULL; + *state = INT_TO_PTR(STATE_VALUE_POST); + goto finish; + + } else if (strchr("-0123456789", *c)) { + + r = json_parse_number(&c, ret_value); + if (r < 0) + return r; + + *ret_string = NULL; + *state = INT_TO_PTR(STATE_VALUE_POST); + goto finish; + + } else if (startswith(c, "true")) { + *ret_string = NULL; + ret_value->boolean = true; + c += 4; + *state = INT_TO_PTR(STATE_VALUE_POST); + r = JSON_TOKEN_BOOLEAN; + goto finish; + + } else if (startswith(c, "false")) { + *ret_string = NULL; + ret_value->boolean = false; + c += 5; + *state = INT_TO_PTR(STATE_VALUE_POST); + r = JSON_TOKEN_BOOLEAN; + goto finish; + + } else if (startswith(c, "null")) { + *ret_string = NULL; + *ret_value = JSON_VALUE_NULL; + c += 4; + *state = INT_TO_PTR(STATE_VALUE_POST); + r = JSON_TOKEN_NULL; + goto finish; + + } + + return -EINVAL; + + case STATE_VALUE_POST: + + if (*c == ':') { + c++; + *state = INT_TO_PTR(STATE_VALUE); + r = JSON_TOKEN_COLON; + goto null_return; + + } else if (*c == ',') { + c++; + *state = INT_TO_PTR(STATE_VALUE); + r = JSON_TOKEN_COMMA; + goto null_return; + + } else if (*c == '}') { + c++; + *state = INT_TO_PTR(STATE_VALUE_POST); + r = JSON_TOKEN_OBJECT_CLOSE; + goto null_return; + + } else if (*c == ']') { + c++; + *state = INT_TO_PTR(STATE_VALUE_POST); + r = JSON_TOKEN_ARRAY_CLOSE; + goto null_return; + } + + return -EINVAL; + + default: + assert_not_reached("Unexpected tokenizer state"); + } + +null_return: + *ret_string = NULL; + *ret_value = JSON_VALUE_NULL; + +finish: + inc_lines_columns(line, column, start, c - start); + *p = c; + + *ret_line = start_line; + *ret_column = start_column; + + return r; +} + +typedef enum JsonExpect { + /* The following values are used by json_parse() */ + EXPECT_TOPLEVEL, + EXPECT_END, + EXPECT_OBJECT_FIRST_KEY, + EXPECT_OBJECT_NEXT_KEY, + EXPECT_OBJECT_COLON, + EXPECT_OBJECT_VALUE, + EXPECT_OBJECT_COMMA, + EXPECT_ARRAY_FIRST_ELEMENT, + EXPECT_ARRAY_NEXT_ELEMENT, + EXPECT_ARRAY_COMMA, + + /* And these are used by json_build() */ + EXPECT_ARRAY_ELEMENT, + EXPECT_OBJECT_KEY, +} JsonExpect; + +typedef struct JsonStack { + JsonExpect expect; + JsonVariant **elements; + size_t n_elements, n_elements_allocated; + unsigned line_before; + unsigned column_before; +} JsonStack; + +static void json_stack_release(JsonStack *s) { + assert(s); + + json_variant_unref_many(s->elements, s->n_elements); + s->elements = mfree(s->elements); +} + +static int json_parse_internal( + const char **input, + JsonSource *source, + JsonVariant **ret, + unsigned *line, + unsigned *column, + bool continue_end) { + + size_t n_stack = 1, n_stack_allocated = 0, i; + unsigned line_buffer = 0, column_buffer = 0; + void *tokenizer_state = NULL; + JsonStack *stack = NULL; + const char *p; + int r; + + assert_return(input, -EINVAL); + assert_return(ret, -EINVAL); + + p = *input; + + if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack)) + return -ENOMEM; + + stack[0] = (JsonStack) { + .expect = EXPECT_TOPLEVEL, + }; + + if (!line) + line = &line_buffer; + if (!column) + column = &column_buffer; + + for (;;) { + _cleanup_free_ char *string = NULL; + unsigned line_token, column_token; + JsonVariant *add = NULL; + JsonStack *current; + JsonValue value; + int token; + + assert(n_stack > 0); + current = stack + n_stack - 1; + + if (continue_end && current->expect == EXPECT_END) + goto done; + + token = json_tokenize(&p, &string, &value, &line_token, &column_token, &tokenizer_state, line, column); + if (token < 0) { + r = token; + goto finish; + } + + switch (token) { + + case JSON_TOKEN_END: + if (current->expect != EXPECT_END) { + r = -EINVAL; + goto finish; + } + + assert(current->n_elements == 1); + assert(n_stack == 1); + goto done; + + case JSON_TOKEN_COLON: + + if (current->expect != EXPECT_OBJECT_COLON) { + r = -EINVAL; + goto finish; + } + + current->expect = EXPECT_OBJECT_VALUE; + break; + + case JSON_TOKEN_COMMA: + + if (current->expect == EXPECT_OBJECT_COMMA) + current->expect = EXPECT_OBJECT_NEXT_KEY; + else if (current->expect == EXPECT_ARRAY_COMMA) + current->expect = EXPECT_ARRAY_NEXT_ELEMENT; + else { + r = -EINVAL; + goto finish; + } + + break; + + case JSON_TOKEN_OBJECT_OPEN: + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) { + r = -ENOMEM; + goto finish; + } + current = stack + n_stack - 1; + + /* Prepare the expect for when we return from the child */ + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_COMMA; + else { + assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); + current->expect = EXPECT_ARRAY_COMMA; + } + + stack[n_stack++] = (JsonStack) { + .expect = EXPECT_OBJECT_FIRST_KEY, + .line_before = line_token, + .column_before = column_token, + }; + + current = stack + n_stack - 1; + break; + + case JSON_TOKEN_OBJECT_CLOSE: + if (!IN_SET(current->expect, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_COMMA)) { + r = -EINVAL; + goto finish; + } + + assert(n_stack > 1); + + r = json_variant_new_object(&add, current->elements, current->n_elements); + if (r < 0) + goto finish; + + line_token = current->line_before; + column_token = current->column_before; + + json_stack_release(current); + n_stack--, current--; + + break; + + case JSON_TOKEN_ARRAY_OPEN: + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) { + r = -ENOMEM; + goto finish; + } + current = stack + n_stack - 1; + + /* Prepare the expect for when we return from the child */ + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_COMMA; + else { + assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); + current->expect = EXPECT_ARRAY_COMMA; + } + + stack[n_stack++] = (JsonStack) { + .expect = EXPECT_ARRAY_FIRST_ELEMENT, + .line_before = line_token, + .column_before = column_token, + }; + + break; + + case JSON_TOKEN_ARRAY_CLOSE: + if (!IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_COMMA)) { + r = -EINVAL; + goto finish; + } + + assert(n_stack > 1); + + r = json_variant_new_array(&add, current->elements, current->n_elements); + if (r < 0) + goto finish; + + line_token = current->line_before; + column_token = current->column_before; + + json_stack_release(current); + n_stack--, current--; + break; + + case JSON_TOKEN_STRING: + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_NEXT_KEY, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + r = json_variant_new_string(&add, string); + if (r < 0) + goto finish; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (IN_SET(current->expect, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_NEXT_KEY)) + current->expect = EXPECT_OBJECT_COLON; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_COMMA; + else { + assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); + current->expect = EXPECT_ARRAY_COMMA; + } + + break; + + case JSON_TOKEN_REAL: + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + r = json_variant_new_real(&add, value.real); + if (r < 0) + goto finish; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_COMMA; + else { + assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); + current->expect = EXPECT_ARRAY_COMMA; + } + + break; + + case JSON_TOKEN_INTEGER: + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + r = json_variant_new_integer(&add, value.integer); + if (r < 0) + goto finish; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_COMMA; + else { + assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); + current->expect = EXPECT_ARRAY_COMMA; + } + + break; + + case JSON_TOKEN_UNSIGNED: + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + r = json_variant_new_unsigned(&add, value.unsig); + if (r < 0) + goto finish; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_COMMA; + else { + assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); + current->expect = EXPECT_ARRAY_COMMA; + } + + break; + + case JSON_TOKEN_BOOLEAN: + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + r = json_variant_new_boolean(&add, value.boolean); + if (r < 0) + goto finish; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_COMMA; + else { + assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); + current->expect = EXPECT_ARRAY_COMMA; + } + + break; + + case JSON_TOKEN_NULL: + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + r = json_variant_new_null(&add); + if (r < 0) + goto finish; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_COMMA; + else { + assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)); + current->expect = EXPECT_ARRAY_COMMA; + } + + break; + + default: + assert_not_reached("Unexpected token"); + } + + if (add) { + (void) json_variant_set_source(&add, source, line_token, column_token); + + if (!GREEDY_REALLOC(current->elements, current->n_elements_allocated, current->n_elements + 1)) { + r = -ENOMEM; + goto finish; + } + + current->elements[current->n_elements++] = add; + } + } + +done: + assert(n_stack == 1); + assert(stack[0].n_elements == 1); + + *ret = json_variant_ref(stack[0].elements[0]); + *input = p; + r = 0; + +finish: + for (i = 0; i < n_stack; i++) + json_stack_release(stack + i); + + free(stack); + + return r; +} + +int json_parse(const char *input, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) { + return json_parse_internal(&input, NULL, ret, ret_line, ret_column, false); +} + +int json_parse_continue(const char **p, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) { + return json_parse_internal(p, NULL, ret, ret_line, ret_column, true); +} + +int json_parse_file(FILE *f, const char *path, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) { + _cleanup_(json_source_unrefp) JsonSource *source = NULL; + _cleanup_free_ char *text = NULL; + const char *p; + int r; + + if (f) + r = read_full_stream(f, &text, NULL); + else if (path) + r = read_full_file(path, &text, NULL); + else + return -EINVAL; + if (r < 0) + return r; + + if (path) { + source = json_source_new(path); + if (!source) + return -ENOMEM; + } + + p = text; + return json_parse_internal(&p, source, ret, ret_line, ret_column, false); +} + +int json_buildv(JsonVariant **ret, va_list ap) { + JsonStack *stack = NULL; + size_t n_stack = 1, n_stack_allocated = 0, i; + int r; + + assert_return(ret, -EINVAL); + + if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack)) + return -ENOMEM; + + stack[0] = (JsonStack) { + .expect = EXPECT_TOPLEVEL, + }; + + for (;;) { + JsonVariant *add = NULL; + JsonStack *current; + int command; + + assert(n_stack > 0); + current = stack + n_stack - 1; + + if (current->expect == EXPECT_END) + goto done; + + command = va_arg(ap, int); + + switch (command) { + + case _JSON_BUILD_STRING: { + const char *p; + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + p = va_arg(ap, const char *); + + r = json_variant_new_string(&add, p); + if (r < 0) + goto finish; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + break; + } + + case _JSON_BUILD_INTEGER: { + intmax_t j; + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + j = va_arg(ap, intmax_t); + + r = json_variant_new_integer(&add, j); + if (r < 0) + goto finish; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + break; + } + + case _JSON_BUILD_UNSIGNED: { + uintmax_t j; + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + j = va_arg(ap, uintmax_t); + + r = json_variant_new_unsigned(&add, j); + if (r < 0) + goto finish; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + break; + } + + case _JSON_BUILD_REAL: { + long double d; + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + d = va_arg(ap, long double); + + r = json_variant_new_real(&add, d); + if (r < 0) + goto finish; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + break; + } + + case _JSON_BUILD_BOOLEAN: { + bool b; + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + b = va_arg(ap, int); + + r = json_variant_new_boolean(&add, b); + if (r < 0) + goto finish; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + break; + } + + case _JSON_BUILD_NULL: + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + r = json_variant_new_null(&add); + if (r < 0) + goto finish; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + break; + + case _JSON_BUILD_VARIANT: + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + add = va_arg(ap, JsonVariant*); + if (!add) + add = JSON_VARIANT_MAGIC_NULL; + else + json_variant_ref(add); + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + break; + + case _JSON_BUILD_LITERAL: { + const char *l; + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + l = va_arg(ap, const char *); + + if (!l) + add = JSON_VARIANT_MAGIC_NULL; + else { + r = json_parse(l, &add, NULL, NULL); + if (r < 0) + goto finish; + } + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + break; + } + + case _JSON_BUILD_ARRAY_BEGIN: + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) { + r = -ENOMEM; + goto finish; + } + current = stack + n_stack - 1; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + stack[n_stack++] = (JsonStack) { + .expect = EXPECT_ARRAY_ELEMENT, + }; + + break; + + case _JSON_BUILD_ARRAY_END: + if (current->expect != EXPECT_ARRAY_ELEMENT) { + r = -EINVAL; + goto finish; + } + + assert(n_stack > 1); + + r = json_variant_new_array(&add, current->elements, current->n_elements); + if (r < 0) + goto finish; + + json_stack_release(current); + n_stack--, current--; + + break; + + case _JSON_BUILD_STRV: { + char **l; + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + l = va_arg(ap, char **); + + r = json_variant_new_array_strv(&add, l); + if (r < 0) + goto finish; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + break; + } + + case _JSON_BUILD_OBJECT_BEGIN: + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) { + r = -ENOMEM; + goto finish; + } + current = stack + n_stack - 1; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + stack[n_stack++] = (JsonStack) { + .expect = EXPECT_OBJECT_KEY, + }; + + break; + + case _JSON_BUILD_OBJECT_END: + + if (current->expect != EXPECT_OBJECT_KEY) { + r = -EINVAL; + goto finish; + } + + assert(n_stack > 1); + + r = json_variant_new_object(&add, current->elements, current->n_elements); + if (r < 0) + goto finish; + + json_stack_release(current); + n_stack--, current--; + + break; + + case _JSON_BUILD_PAIR: { + const char *n; + + if (current->expect != EXPECT_OBJECT_KEY) { + r = -EINVAL; + goto finish; + } + + n = va_arg(ap, const char *); + + r = json_variant_new_string(&add, n); + if (r < 0) + goto finish; + + current->expect = EXPECT_OBJECT_VALUE; + break; + }} + + if (add) { + if (!GREEDY_REALLOC(current->elements, current->n_elements_allocated, current->n_elements + 1)) { + r = -ENOMEM; + goto finish; + } + + current->elements[current->n_elements++] = add; + } + } + +done: + assert(n_stack == 1); + assert(stack[0].n_elements == 1); + + *ret = json_variant_ref(stack[0].elements[0]); + r = 0; + +finish: + for (i = 0; i < n_stack; i++) + json_stack_release(stack + i); + + free(stack); + + va_end(ap); + + return r; +} + +int json_build(JsonVariant **ret, ...) { + va_list ap; + int r; + + va_start(ap, ret); + r = json_buildv(ret, ap); + va_end(ap); + + return r; +} + +int json_log_internal( + JsonVariant *variant, + int level, + int error, + const char *file, + int line, + const char *func, + const char *format, ...) { + + PROTECT_ERRNO; + + unsigned source_line, source_column; + char buffer[LINE_MAX]; + const char *source; + va_list ap; + int r; + + if (error < 0) + error = -error; + + errno = error; + + va_start(ap, format); + (void) vsnprintf(buffer, sizeof buffer, format, ap); + va_end(ap); + + if (variant) { + r = json_variant_get_source(variant, &source, &source_line, &source_column); + if (r < 0) + return r; + } else { + source = NULL; + source_line = 0; + source_column = 0; + } + + if (source && source_line > 0 && source_column > 0) + return log_struct_internal( + LOG_REALM_PLUS_LEVEL(LOG_REALM_SYSTEMD, level), + error, + file, line, func, + "MESSAGE_ID=" SD_MESSAGE_INVALID_CONFIGURATION_STR, + "CONFIG_FILE=%s", source, + "CONFIG_LINE=%u", source_line, + "CONFIG_COLUMN=%u", source_column, + LOG_MESSAGE("%s:%u: %s", source, line, buffer), + NULL); + else + return log_struct_internal( + LOG_REALM_PLUS_LEVEL(LOG_REALM_SYSTEMD, level), + error, + file, line, func, + "MESSAGE_ID=" SD_MESSAGE_INVALID_CONFIGURATION_STR, + LOG_MESSAGE("%s", buffer), + NULL); +} + +int json_dispatch(JsonVariant *v, const JsonDispatch table[], JsonDispatchCallback bad, JsonDispatchFlags flags, void *userdata) { + const JsonDispatch *p; + size_t i, n, m; + int r, done = 0; + bool *found; + + if (!json_variant_is_object(v)) { + json_log(v, flags, 0, "JSON variant is not an object."); + + if (flags & JSON_PERMISSIVE) + return 0; + + return -EINVAL; + } + + for (p = table, m = 0; p->name; p++) + m++; + + found = newa0(bool, m); + + n = json_variant_elements(v); + for (i = 0; i < n; i += 2) { + JsonVariant *key, *value; + + assert_se(key = json_variant_by_index(v, i)); + assert_se(value = json_variant_by_index(v, i+1)); + + for (p = table; p->name; p++) + if (p->name == (const char*) -1 || + streq_ptr(json_variant_string(key), p->name)) + break; + + if (p->name) { /* Found a matching entry! :-) */ + JsonDispatchFlags merged_flags; + + merged_flags = flags | p->flags; + + if (p->type != _JSON_VARIANT_TYPE_INVALID && + !json_variant_has_type(value, p->type)) { + + json_log(value, merged_flags, 0, + "Object field '%s' has wrong type %s, expected %s.", json_variant_string(key), + json_variant_type_to_string(json_variant_type(value)), json_variant_type_to_string(p->type)); + + if (merged_flags & JSON_PERMISSIVE) + continue; + + return -EINVAL; + } + + if (found[p-table]) { + json_log(value, merged_flags, 0, "Duplicate object field '%s'.", json_variant_string(key)); + + if (merged_flags & JSON_PERMISSIVE) + continue; + + return -ENOTUNIQ; + } + + found[p-table] = true; + + if (p->callback) { + r = p->callback(json_variant_string(key), value, merged_flags, (uint8_t*) userdata + p->offset); + if (r < 0) { + if (merged_flags & JSON_PERMISSIVE) + continue; + + return r; + } + } + + done ++; + + } else { /* Didn't find a matching entry! :-( */ + + if (bad) { + r = bad(json_variant_string(key), value, flags, userdata); + if (r < 0) { + if (flags & JSON_PERMISSIVE) + continue; + + return r; + } else + done ++; + + } else { + json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key)); + + if (flags & JSON_PERMISSIVE) + continue; + + return -EADDRNOTAVAIL; + } + } + } + + for (p = table; p->name; p++) { + JsonDispatchFlags merged_flags = p->flags | flags; + + if ((merged_flags & JSON_MANDATORY) && !found[p-table]) { + json_log(v, merged_flags, 0, "Missing object field '%s'.", p->name); + + if ((merged_flags & JSON_PERMISSIVE)) + continue; + + return -ENXIO; + } + } + + return done; +} + +int json_dispatch_boolean(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + bool *b = userdata; + + assert(variant); + assert(b); + + if (!json_variant_is_boolean(variant)) { + json_log(variant, flags, 0, "JSON field '%s' is not a boolean.", strna(name)); + return -EINVAL; + } + + *b = json_variant_boolean(variant); + return 0; +} + +int json_dispatch_tristate(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + int *b = userdata; + + assert(variant); + assert(b); + + if (!json_variant_is_boolean(variant)) { + json_log(variant, flags, 0, "JSON field '%s' is not a boolean.", strna(name)); + return -EINVAL; + } + + *b = json_variant_boolean(variant); + return 0; +} + +int json_dispatch_integer(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + intmax_t *i = userdata; + + assert(variant); + assert(i); + + if (!json_variant_is_integer(variant)) { + json_log(variant, flags, 0, "JSON field '%s' is not an integer.", strna(name)); + return -EINVAL; + } + + *i = json_variant_integer(variant); + return 0; +} + +int json_dispatch_unsigned(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + uintmax_t *u = userdata; + + assert(variant); + assert(u); + + if (!json_variant_is_unsigned(variant)) { + json_log(variant, flags, 0, "JSON field '%s' is not an unsigned integer.", strna(name)); + return -EINVAL; + } + + *u = json_variant_unsigned(variant); + return 0; +} + +int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + uint32_t *u = userdata; + + assert(variant); + assert(u); + + if (!json_variant_is_unsigned(variant)) { + json_log(variant, flags, 0, "JSON field '%s' is not an unsigned integer.", strna(name)); + return -EINVAL; + } + + if (json_variant_unsigned(variant) > UINT32_MAX) { + json_log(variant, flags, 0, "JSON field '%s' out of bounds.", strna(name)); + return -ERANGE; + } + + *u = (uint32_t) json_variant_unsigned(variant); + return 0; +} + +int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + int32_t *i = userdata; + + assert(variant); + assert(i); + + if (!json_variant_is_integer(variant)) { + json_log(variant, flags, 0, "JSON field '%s' is not an integer.", strna(name)); + return -EINVAL; + } + + if (json_variant_integer(variant) < INT32_MIN || json_variant_integer(variant) > INT32_MAX) { + json_log(variant, flags, 0, "JSON field '%s' out of bounds.", strna(name)); + return -ERANGE; + } + + *i = (int32_t) json_variant_integer(variant); + return 0; +} + +int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + char **s = userdata; + int r; + + assert(variant); + assert(s); + + if (json_variant_is_null(variant)) { + *s = mfree(*s); + return 0; + } + + if (!json_variant_is_string(variant)) { + json_log(variant, flags, 0, "JSON field '%s' is not a string.", strna(name)); + return -EINVAL; + } + + r = free_and_strdup(s, json_variant_string(variant)); + if (r < 0) + return json_log(variant, flags, r, "Failed to allocate string: %m"); + + return 0; +} + +int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + _cleanup_strv_free_ char **l = NULL; + char ***s = userdata; + size_t i; + int r; + + assert(variant); + assert(s); + + if (json_variant_is_null(variant)) { + *s = strv_free(*s); + return 0; + } + + if (!json_variant_is_array(variant)) { + json_log(variant, 0, flags, "JSON field '%s' is not an array.", strna(name)); + return -EINVAL; + } + + for (i = 0; i < json_variant_elements(variant); i++) { + JsonVariant *e; + + assert_se(e = json_variant_by_index(variant, i)); + + if (!json_variant_is_string(e)) { + json_log(e, 0, flags, "JSON array element is not a string."); + return -EINVAL; + } + + r = strv_extend(&l, json_variant_string(e)); + if (r < 0) + return json_log(variant, flags, r, "Failed to append array element: %m"); + } + + strv_free_and_replace(*s, l); + return 0; +} + +int json_dispatch_variant(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + JsonVariant **p = userdata; + + assert(variant); + assert(p); + + json_variant_unref(*p); + *p = json_variant_ref(variant); + + return 0; +} + +static const char* const json_variant_type_table[_JSON_VARIANT_TYPE_MAX] = { + [JSON_VARIANT_STRING] = "string", + [JSON_VARIANT_INTEGER] = "integer", + [JSON_VARIANT_UNSIGNED] = "unsigned", + [JSON_VARIANT_REAL] = "real", + [JSON_VARIANT_NUMBER] = "number", + [JSON_VARIANT_BOOLEAN] = "boolean", + [JSON_VARIANT_ARRAY] = "array", + [JSON_VARIANT_OBJECT] = "object", + [JSON_VARIANT_NULL] = "null", +}; + +DEFINE_STRING_TABLE_LOOKUP(json_variant_type, JsonVariantType); diff --git a/src/basic/json.h b/src/basic/json.h new file mode 100644 index 0000000000..1f06fe2257 --- /dev/null +++ b/src/basic/json.h @@ -0,0 +1,274 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#pragma once + +#include +#include +#include + +#include "macro.h" +#include "string-util.h" +#include "util.h" + +/* + In case you wonder why we have our own JSON implementation, here are a couple of reasons why this implementation has + benefits over various other implementatins: + + - We need support for 64bit signed and unsigned integers, i.e. the full 64,5bit range of -9223372036854775808…18446744073709551615 + - All our variants are immutable after creation + - Special values such as true, false, zero, null, empty strings, empty array, empty objects require zero dynamic memory + - Progressive parsing + - Our integer/real type implicitly converts, but only if that's safe and loss-lessly possible + - There's a "builder" for putting together objects easily in varargs function calls + - There's a "dispatcher" for mapping objects to C data structures + - Every variant optionally carries parsing location information, which simplifies debugging and parse log error generation + - Formatter has color, line, column support + + Limitations: + - Doesn't allow embedded NUL in strings + - Can't store integers outside of the -9223372036854775808…18446744073709551615 range (it will use 'long double' for + values outside this range, which is lossy) + - Can't store negative zero (will be treated identical to positive zero, and not retained across serialization) + - Can't store non-integer numbers that can't be stored in "long double" losslessly + - Allows creation and parsing of objects with duplicate keys. The "dispatcher" will refuse them however. This means + we can parse and pass around such objects, but will carefully refuse them when we convert them into our own data. + + (These limitations should be pretty much in line with those of other JSON implementations, in fact might be less + limiting in most cases even.) +*/ + +typedef struct JsonVariant JsonVariant; + +typedef enum JsonVariantType { + JSON_VARIANT_STRING, + JSON_VARIANT_INTEGER, + JSON_VARIANT_UNSIGNED, + JSON_VARIANT_REAL, + JSON_VARIANT_NUMBER, /* This a pseudo-type: we can never create variants of this type, but we use it as wildcard check for the above three types */ + JSON_VARIANT_BOOLEAN, + JSON_VARIANT_ARRAY, + JSON_VARIANT_OBJECT, + JSON_VARIANT_NULL, + _JSON_VARIANT_TYPE_MAX, + _JSON_VARIANT_TYPE_INVALID = -1 +} JsonVariantType; + +int json_variant_new_stringn(JsonVariant **ret, const char *s, size_t n); +int json_variant_new_integer(JsonVariant **ret, intmax_t i); +int json_variant_new_unsigned(JsonVariant **ret, uintmax_t u); +int json_variant_new_real(JsonVariant **ret, long double d); +int json_variant_new_boolean(JsonVariant **ret, bool b); +int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n); +int json_variant_new_array_bytes(JsonVariant **ret, const void *p, size_t n); +int json_variant_new_array_strv(JsonVariant **ret, char **l); +int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n); +int json_variant_new_null(JsonVariant **ret); + +static inline int json_variant_new_string(JsonVariant **ret, const char *s) { + return json_variant_new_stringn(ret, s, strlen_ptr(s)); +} + +JsonVariant *json_variant_ref(JsonVariant *v); +JsonVariant *json_variant_unref(JsonVariant *v); +void json_variant_unref_many(JsonVariant **array, size_t n); + +DEFINE_TRIVIAL_CLEANUP_FUNC(JsonVariant *, json_variant_unref); + +const char *json_variant_string(JsonVariant *v); +intmax_t json_variant_integer(JsonVariant *v); +uintmax_t json_variant_unsigned(JsonVariant *v); +long double json_variant_real(JsonVariant *v); +bool json_variant_boolean(JsonVariant *v); + +JsonVariantType json_variant_type(JsonVariant *v); +bool json_variant_has_type(JsonVariant *v, JsonVariantType type); + +static inline bool json_variant_is_string(JsonVariant *v) { + return json_variant_has_type(v, JSON_VARIANT_STRING); +} + +static inline bool json_variant_is_integer(JsonVariant *v) { + return json_variant_has_type(v, JSON_VARIANT_INTEGER); +} + +static inline bool json_variant_is_unsigned(JsonVariant *v) { + return json_variant_has_type(v, JSON_VARIANT_UNSIGNED); +} + +static inline bool json_variant_is_real(JsonVariant *v) { + return json_variant_has_type(v, JSON_VARIANT_REAL); +} + +static inline bool json_variant_is_number(JsonVariant *v) { + return json_variant_has_type(v, JSON_VARIANT_NUMBER); +} + +static inline bool json_variant_is_boolean(JsonVariant *v) { + return json_variant_has_type(v, JSON_VARIANT_BOOLEAN); +} + +static inline bool json_variant_is_array(JsonVariant *v) { + return json_variant_has_type(v, JSON_VARIANT_ARRAY); +} + +static inline bool json_variant_is_object(JsonVariant *v) { + return json_variant_has_type(v, JSON_VARIANT_OBJECT); +} + +static inline bool json_variant_is_null(JsonVariant *v) { + return json_variant_has_type(v, JSON_VARIANT_NULL); +} + +bool json_variant_is_negative(JsonVariant *v); + +size_t json_variant_elements(JsonVariant *v); +JsonVariant *json_variant_by_index(JsonVariant *v, size_t index); +JsonVariant *json_variant_by_key(JsonVariant *v, const char *key); +JsonVariant *json_variant_by_key_full(JsonVariant *v, const char *key, JsonVariant **ret_key); + +bool json_variant_equal(JsonVariant *a, JsonVariant *b); + +struct json_variant_foreach_state { + JsonVariant *variant; + size_t idx; +}; + +#define JSON_VARIANT_ARRAY_FOREACH(i, v) \ + for (struct json_variant_foreach_state _state = { (v), 0 }; \ + _state.idx < json_variant_elements(_state.variant) && \ + ({ i = json_variant_by_index(_state.variant, _state.idx); \ + true; }); \ + _state.idx++) + +#define JSON_VARIANT_OBJECT_FOREACH(k, e, v) \ + for (struct json_variant_foreach_state _state = { (v), 0 }; \ + _state.idx < json_variant_elements(_state.variant) && \ + ({ k = json_variant_by_index(_state.variant, _state.idx); \ + e = json_variant_by_index(_state.variant, _state.idx + 1); \ + true; }); \ + _state.idx += 2) + +int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column); + +enum { + JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */ + JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */ + JSON_FORMAT_COLOR = 1 << 2, /* insert ANSI color sequences */ + JSON_FORMAT_SOURCE = 1 << 3, /* prefix with source filename/line/column */ + JSON_FORMAT_SSE = 1 << 4, /* prefix/suffix with W3C server-sent events */ + JSON_FORMAT_SEQ = 1 << 5, /* prefix/suffix with RFC 7464 application/json-seq */ +}; + +int json_variant_format(JsonVariant *v, unsigned flags, char **ret); +void json_variant_dump(JsonVariant *v, unsigned flags, FILE *f, const char *prefix); + +int json_parse(const char *string, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column); +int json_parse_continue(const char **p, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column); +int json_parse_file(FILE *f, const char *path, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column); + +enum { + _JSON_BUILD_STRING, + _JSON_BUILD_INTEGER, + _JSON_BUILD_UNSIGNED, + _JSON_BUILD_REAL, + _JSON_BUILD_BOOLEAN, + _JSON_BUILD_ARRAY_BEGIN, + _JSON_BUILD_ARRAY_END, + _JSON_BUILD_OBJECT_BEGIN, + _JSON_BUILD_OBJECT_END, + _JSON_BUILD_PAIR, + _JSON_BUILD_NULL, + _JSON_BUILD_VARIANT, + _JSON_BUILD_LITERAL, + _JSON_BUILD_STRV, + _JSON_BUILD_MAX, +}; + +#define JSON_BUILD_STRING(s) _JSON_BUILD_STRING, ({ const char *_x = s; _x; }) +#define JSON_BUILD_INTEGER(i) _JSON_BUILD_INTEGER, ({ intmax_t _x = i; _x; }) +#define JSON_BUILD_UNSIGNED(u) _JSON_BUILD_UNSIGNED, ({ uintmax_t _x = u; _x; }) +#define JSON_BUILD_REAL(d) _JSON_BUILD_REAL, ({ long double _x = d; _x; }) +#define JSON_BUILD_BOOLEAN(b) _JSON_BUILD_BOOLEAN, ({ bool _x = b; _x; }) +#define JSON_BUILD_ARRAY(...) _JSON_BUILD_ARRAY_BEGIN, __VA_ARGS__, _JSON_BUILD_ARRAY_END +#define JSON_BUILD_OBJECT(...) _JSON_BUILD_OBJECT_BEGIN, __VA_ARGS__, _JSON_BUILD_OBJECT_END +#define JSON_BUILD_PAIR(n, ...) _JSON_BUILD_PAIR, ({ const char *_x = n; _x; }), __VA_ARGS__ +#define JSON_BUILD_NULL _JSON_BUILD_NULL +#define JSON_BUILD_VARIANT(v) _JSON_BUILD_VARIANT, ({ JsonVariant *_x = v; _x; }) +#define JSON_BUILD_LITERAL(l) _JSON_BUILD_LITERAL, ({ const char *_x = l; _x; }) +#define JSON_BUILD_STRV(l) _JSON_BUILD_STRV, ({ char **_x = l; _x; }) + +int json_build(JsonVariant **ret, ...); +int json_buildv(JsonVariant **ret, va_list ap); + +/* A bitmask of flags used by the dispatch logic. Note that this is a combined bit mask, that is generated from the bit + * mask originally passed into json_dispatch(), the individual bitmask associated with the static JsonDispatch callout + * entry, as well the bitmask specified for json_log() calls */ +typedef enum JsonDispatchFlags { + /* The following three may be set in JsonDispatch's .flags field or the json_dispatch() flags parameter */ + JSON_PERMISSIVE = 1 << 0, /* Shall parsing errors be considered fatal for this property? */ + JSON_MANDATORY = 1 << 1, /* Should existance of this property be mandatory? */ + JSON_LOG = 1 << 2, /* Should the parser log about errors? */ + + /* The following two may be passed into log_json() in addition to the three above */ + JSON_DEBUG = 1 << 3, /* Indicates that this log message is a debug message */ + JSON_WARNING = 1 << 4, /* Indicates that this log message is a warning message */ +} JsonDispatchFlags; + +typedef int (*JsonDispatchCallback)(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); + +typedef struct JsonDispatch { + const char *name; + JsonVariantType type; + JsonDispatchCallback callback; + size_t offset; + JsonDispatchFlags flags; +} JsonDispatch; + +int json_dispatch(JsonVariant *v, const JsonDispatch table[], JsonDispatchCallback bad, JsonDispatchFlags flags, void *userdata); + +int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_boolean(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_tristate(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_variant(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_integer(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_unsigned(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); + +assert_cc(sizeof(uintmax_t) == sizeof(uint64_t)) +#define json_dispatch_uint64 json_dispatch_unsigned + +assert_cc(sizeof(intmax_t) == sizeof(int64_t)) +#define json_dispatch_int64 json_dispatch_integer + +static inline int json_dispatch_level(JsonDispatchFlags flags) { + + /* Did the user request no logging? If so, then never log higher than LOG_DEBUG. Also, if this is marked as + * debug message, then also log at debug level. */ + + if (!(flags & JSON_LOG) || + (flags & JSON_DEBUG)) + return LOG_DEBUG; + + /* Are we invoked in permissive mode, or is this explicitly marked as warning message? Then this should be + * printed at LOG_WARNING */ + if (flags & (JSON_PERMISSIVE|JSON_WARNING)) + return LOG_WARNING; + + /* Otherwise it's an error. */ + return LOG_ERR; +} + +int json_log_internal(JsonVariant *variant, int level, int error, const char *file, int line, const char *func, const char *format, ...) _printf_(7, 8); + +#define json_log(variant, flags, error, ...) \ + ({ \ + int _level = json_dispatch_level(flags), _e = (error); \ + (log_get_max_level() >= LOG_PRI(_level)) \ + ? json_log_internal(variant, _level, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \ + : -abs(_e); \ + }) + +const char *json_variant_type_to_string(JsonVariantType t); +JsonVariantType json_variant_type_from_string(const char *s); diff --git a/src/basic/meson.build b/src/basic/meson.build index 2fa2681b47..ac580c283c 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -99,6 +99,9 @@ basic_sources = files(''' ioprio.h journal-importer.c journal-importer.h + json-internal.h + json.c + json.h khash.c khash.h label.c @@ -313,7 +316,8 @@ libbasic = static_library( libcap, libblkid, libmount, - libselinux], + libselinux, + libm], c_args : ['-fvisibility=default'], install : false)