Systemd/src/shared/json.c
Zbigniew Jędrzejewski-Szmek d284b82b3e Move various files that don't need to be in basic/ to shared/
This doesn't have much effect on the final build, because we link libbasic.a
into libsystemd-shared.so, so in the end, all the object built from basic/
end up in libsystemd-shared. And when the static library is linked into binaries,
any objects that are included in it but are not used are trimmed. Hence, the
size of output artifacts doesn't change:

$ du -sb /var/tmp/inst*
54181861	/var/tmp/inst1    (old)
54207441	/var/tmp/inst1s   (old split-usr)
54182477	/var/tmp/inst2    (new)
54208041	/var/tmp/inst2s   (new split-usr)

(The negligible change in size is because libsystemd-shared.so is bigger
by a few hundred bytes. I guess it's because symbols are named differently
or something like that.)

The effect is on the build process, in particular partial builds. This change
effectively moves the requirements on some build steps toward the leaves of the
dependency tree. Two effects:
- when building items that do not depend on libsystemd-shared, we
  build less stuff for libbasic.a (which wouldn't be used anyway,
  so it's a net win).
- when building items that do depend on libshared, we reduce libbasic.a as a
  synchronization point, possibly allowing better parallelism.

Method:
1. copy list of .h files from src/basic/meson.build to /tmp/basic
2. $ for i in $(grep '.h$' /tmp/basic); do echo $i; git --no-pager grep "include \"$i\"" src/basic/ 'src/lib*' 'src/nss-*' 'src/journal/sd-journal.c' |grep -v "${i%.h}.c";echo ;done | less
2018-11-20 07:27:37 +01:00

3386 lines
110 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#include <errno.h>
#include <math.h>
#include <stdarg.h>
#include <stdio_ext.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#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"
/* Refuse putting together variants with a larger depth than 4K by default (as a protection against overflowing stacks
* if code processes JSON objects recursively. Note that we store the depth in an uint16_t, hence make sure this
* remains under 2^16.
* The value was 16k, but it was discovered to be too high on llvm/x86-64. See also the issue #10738. */
#define DEPTH_MAX (4U*1024U)
assert_cc(DEPTH_MAX <= UINT16_MAX);
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;
};
/* If this was parsed from some file or buffer, this stores where from, as well as the source line/column */
JsonSource *source;
unsigned line, column;
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;
/* The current 'depth' of the JsonVariant, i.e. how many levels of member variants this has */
uint16_t depth;
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);
/* There are four kind of JsonVariant* pointers:
*
* 1. NULL
* 2. A 'regular' one, i.e. pointing to malloc() memory
* 3. A 'magic' one, i.e. one of the special JSON_VARIANT_MAGIC_XYZ values, that encode a few very basic values directly in the pointer.
* 4. A 'const string' one, i.e. a pointer to a const string.
*
* The four kinds of pointers can be discerned like this:
*
* Detecting #1 is easy, just compare with NULL. Detecting #3 is similarly easy: all magic pointers are below
* _JSON_VARIANT_MAGIC_MAX (which is pretty low, within the first memory page, which is special on Linux and other
* OSes, as it is a faulting page). In order to discern #2 and #4 we check the lowest bit. If it's off it's #2,
* otherwise #4. This makes use of the fact that malloc() will return "maximum aligned" memory, which definitely
* means the pointer is even. This means we can use the uneven pointers to reference static strings, as long as we
* make sure that all static strings used like this are aligned to 2 (or higher), and that we mask the bit on
* access. The JSON_VARIANT_STRING_CONST() macro encodes strings as JsonVariant* pointers, with the bit set. */
static bool json_variant_is_magic(const JsonVariant *v) {
if (!v)
return false;
return v < _JSON_VARIANT_MAGIC_MAX;
}
static bool json_variant_is_const_string(const JsonVariant *v) {
if (v < _JSON_VARIANT_MAGIC_MAX)
return false;
/* A proper JsonVariant is aligned to whatever malloc() aligns things too, which is definitely not uneven. We
* hence use all uneven pointers as indicators for const strings. */
return (((uintptr_t) v) & 1) != 0;
}
static bool json_variant_is_regular(const JsonVariant *v) {
if (v < _JSON_VARIANT_MAGIC_MAX)
return false;
return (((uintptr_t) v) & 1) == 0;
}
static JsonVariant *json_variant_dereference(JsonVariant *v) {
/* Recursively dereference variants that are references to other variants */
if (!v)
return NULL;
if (!json_variant_is_regular(v))
return v;
if (!v->is_reference)
return v;
return json_variant_dereference(v->reference);
}
static uint16_t json_variant_depth(JsonVariant *v) {
v = json_variant_dereference(v);
if (!v)
return 0;
if (!json_variant_is_regular(v))
return 0;
return v->depth;
}
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_regular(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_regular(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) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
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,
};
for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
JsonVariant *w = v + 1 + v->n_elements,
*c = array[v->n_elements];
uint16_t d;
d = json_variant_depth(c);
if (d >= DEPTH_MAX) /* Refuse too deep nesting */
return -ELNRNG;
if (d >= v->depth)
v->depth = d + 1;
*w = (JsonVariant) {
.is_embedded = true,
.parent = v,
};
json_variant_set(w, c);
json_variant_copy_source(w, c);
}
*ret = TAKE_PTR(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,
.depth = 1,
};
for (i = 0; i < n; i++) {
JsonVariant *w = v + 1 + i;
*w = (JsonVariant) {
.is_embedded = true,
.parent = v,
.type = JSON_VARIANT_UNSIGNED,
.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 = new(JsonVariant, n + 1);
if (!v)
return -ENOMEM;
*v = (JsonVariant) {
.n_ref = 1,
.type = JSON_VARIANT_ARRAY,
.depth = 1,
};
for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
JsonVariant *w = v + 1 + v->n_elements;
size_t k;
*w = (JsonVariant) {
.is_embedded = true,
.parent = v,
.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) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
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,
};
for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
JsonVariant *w = v + 1 + v->n_elements,
*c = array[v->n_elements];
uint16_t d;
if ((v->n_elements & 1) == 0 &&
!json_variant_is_string(c))
return -EINVAL; /* Every second one needs to be a string, as it is the key name */
d = json_variant_depth(c);
if (d >= DEPTH_MAX) /* Refuse too deep nesting */
return -ELNRNG;
if (d >= v->depth)
v->depth = d + 1;
*w = (JsonVariant) {
.is_embedded = true,
.parent = v,
};
json_variant_set(w, c);
json_variant_copy_source(w, c);
}
*ret = TAKE_PTR(v);
return 0;
}
static void json_variant_free_inner(JsonVariant *v) {
assert(v);
if (!json_variant_is_regular(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_regular(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_regular(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 (json_variant_is_const_string(v)) {
uintptr_t p = (uintptr_t) v;
assert((p & 1) != 0);
return (const char*) (p ^ 1U);
}
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_regular(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_regular(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_regular(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_regular(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.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_regular(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 (json_variant_is_const_string(v))
return JSON_VARIANT_STRING;
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;
/* If it's a const string, then it only can be a string, and if it is not, it's not */
if (json_variant_is_const_string(v))
return false;
/* 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_regular(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_regular(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_regular(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_regular(v) && v->source ? v->source->name : NULL;
if (ret_line)
*ret_line = json_variant_is_regular(v) ? v->line : 0;
if (ret_column)
*ret_column = json_variant_is_regular(v) ? v->column : 0;
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_regular(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 ((signed char) *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 {
_cleanup_free_ char *joined = NULL;
const char *prefix2;
if (flags & JSON_FORMAT_PRETTY) {
joined = strjoin(strempty(prefix), "\t");
if (!joined)
return -ENOMEM;
prefix2 = joined;
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 {
_cleanup_free_ char *joined = NULL;
const char *prefix2;
if (flags & JSON_FORMAT_PRETTY) {
joined = strjoin(strempty(prefix), "\t");
if (!joined)
return -ENOMEM;
prefix2 = joined;
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_regular(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_regular(*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_regular(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 ((signed char) *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 = TAKE_PTR(s);
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 (IN_SET(*c, 'e', '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 (;;) {
_cleanup_(json_variant_unrefp) 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++] = TAKE_PTR(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);