summaryrefslogtreecommitdiffstats
path: root/src/libsystemd
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2024-05-02 14:26:21 +0200
committerLennart Poettering <lennart@poettering.net>2024-06-12 18:42:22 +0200
commit309a747fa6cfeac0a0165543f23a924866727c9b (patch)
treea51242aa3ac87243c0614bf93d27e9083182e485 /src/libsystemd
parentjson: merge json_dispatch_path() + json_dispatch_absolute_path() (diff)
downloadsystemd-309a747fa6cfeac0a0165543f23a924866727c9b.tar.xz
systemd-309a747fa6cfeac0a0165543f23a924866727c9b.zip
libsystemd: turn json.[ch] into a public API
This is preparation for making our Varlink API a public API. Since our Varlink API is built on top of our JSON API we need to make that public first (it's a nice API, but JSON APIs there are already enough, this is purely about the Varlink angle). I made most of the json.h APIs public, and just placed them in sd-json.h. Sometimes I wasn't so sure however, since the underlying data structures would have to be made public too. If in doubt I didn#t risk it, and moved the relevant API to src/libsystemd/sd-json/json-util.h instead (without any sd_* symbol prefixes). This is mostly a giant search/replace patch.
Diffstat (limited to 'src/libsystemd')
-rw-r--r--src/libsystemd/libsystemd.sym100
-rw-r--r--src/libsystemd/meson.build10
-rw-r--r--src/libsystemd/sd-json/json-internal.h76
-rw-r--r--src/libsystemd/sd-json/json-util.c135
-rw-r--r--src/libsystemd/sd-json/json-util.h178
-rw-r--r--src/libsystemd/sd-json/sd-json.c5278
6 files changed, 5776 insertions, 1 deletions
diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym
index 6874240b8a..1799706c3c 100644
--- a/src/libsystemd/libsystemd.sym
+++ b/src/libsystemd/libsystemd.sym
@@ -847,4 +847,104 @@ global:
LIBSYSTEMD_257 {
global:
sd_bus_pending_method_calls;
+ sd_json_build;
+ sd_json_buildv;
+ sd_json_dispatch;
+ sd_json_dispatch_const_string;
+ sd_json_dispatch_full;
+ sd_json_dispatch_id128;
+ sd_json_dispatch_int16;
+ sd_json_dispatch_int32;
+ sd_json_dispatch_int64;
+ sd_json_dispatch_int8;
+ sd_json_dispatch_intbool;
+ sd_json_dispatch_stdbool;
+ sd_json_dispatch_string;
+ sd_json_dispatch_strv;
+ sd_json_dispatch_tristate;
+ sd_json_dispatch_uid_gid;
+ sd_json_dispatch_uint16;
+ sd_json_dispatch_uint32;
+ sd_json_dispatch_uint64;
+ sd_json_dispatch_uint8;
+ sd_json_dispatch_unsupported;
+ sd_json_dispatch_variant;
+ sd_json_dispatch_variant_noref;
+ sd_json_parse;
+ sd_json_parse_continue;
+ sd_json_parse_file;
+ sd_json_parse_file_at;
+ sd_json_parse_with_source;
+ sd_json_parse_with_source_continue;
+ sd_json_variant_append_array;
+ sd_json_variant_append_array_nodup;
+ sd_json_variant_append_arrayb;
+ sd_json_variant_boolean;
+ sd_json_variant_by_index;
+ sd_json_variant_by_key;
+ sd_json_variant_by_key_full;
+ sd_json_variant_dump;
+ sd_json_variant_elements;
+ sd_json_variant_equal;
+ sd_json_variant_filter;
+ sd_json_variant_find;
+ sd_json_variant_format;
+ sd_json_variant_get_source;
+ sd_json_variant_has_type;
+ sd_json_variant_integer;
+ sd_json_variant_is_array;
+ sd_json_variant_is_blank_array;
+ sd_json_variant_is_blank_object;
+ sd_json_variant_is_boolean;
+ sd_json_variant_is_integer;
+ sd_json_variant_is_negative;
+ sd_json_variant_is_normalized;
+ sd_json_variant_is_null;
+ sd_json_variant_is_number;
+ sd_json_variant_is_object;
+ sd_json_variant_is_real;
+ sd_json_variant_is_sensitive;
+ sd_json_variant_is_sensitive_recursive;
+ sd_json_variant_is_sorted;
+ sd_json_variant_is_string;
+ sd_json_variant_is_unsigned;
+ sd_json_variant_merge_object;
+ sd_json_variant_merge_objectb;
+ sd_json_variant_new_array;
+ sd_json_variant_new_array_bytes;
+ sd_json_variant_new_array_strv;
+ sd_json_variant_new_base32hex;
+ sd_json_variant_new_base64;
+ sd_json_variant_new_boolean;
+ sd_json_variant_new_hex;
+ sd_json_variant_new_id128;
+ sd_json_variant_new_integer;
+ sd_json_variant_new_null;
+ sd_json_variant_new_object;
+ sd_json_variant_new_octescape;
+ sd_json_variant_new_real;
+ sd_json_variant_new_string;
+ sd_json_variant_new_stringn;
+ sd_json_variant_new_unsigned;
+ sd_json_variant_new_uuid;
+ sd_json_variant_normalize;
+ sd_json_variant_real;
+ sd_json_variant_ref;
+ sd_json_variant_sensitive;
+ sd_json_variant_set_field;
+ sd_json_variant_set_field_boolean;
+ sd_json_variant_set_field_integer;
+ sd_json_variant_set_field_string;
+ sd_json_variant_set_field_strv;
+ sd_json_variant_set_field_unsigned;
+ sd_json_variant_set_fieldb;
+ sd_json_variant_sort;
+ sd_json_variant_string;
+ sd_json_variant_strv;
+ sd_json_variant_type;
+ sd_json_variant_unbase64;
+ sd_json_variant_unhex;
+ sd_json_variant_unref;
+ sd_json_variant_unref_many;
+ sd_json_variant_unsigned;
} LIBSYSTEMD_256;
diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build
index 6d4337d1a7..2b0ad906f8 100644
--- a/src/libsystemd/meson.build
+++ b/src/libsystemd/meson.build
@@ -66,6 +66,13 @@ sd_login_sources = files('sd-login/sd-login.c')
############################################################
+sd_json_sources = files(
+ 'sd-json/json-util.c',
+ 'sd-json/sd-json.c',
+)
+
+############################################################
+
libsystemd_sources = files(
'sd-bus/bus-common-errors.c',
'sd-bus/bus-container.c',
@@ -109,7 +116,7 @@ libsystemd_sources = files(
'sd-network/sd-network.c',
'sd-path/sd-path.c',
'sd-resolve/sd-resolve.c',
-) + sd_journal_sources + id128_sources + sd_daemon_sources + sd_event_sources + sd_login_sources
+) + sd_journal_sources + id128_sources + sd_daemon_sources + sd_event_sources + sd_login_sources + sd_json_sources
libsystemd_c_args = ['-fvisibility=default']
@@ -120,6 +127,7 @@ libsystemd_static = static_library(
c_args : libsystemd_c_args,
link_with : [libbasic],
dependencies : [threads,
+ libm,
librt,
userspace],
build_by_default : false)
diff --git a/src/libsystemd/sd-json/json-internal.h b/src/libsystemd/sd-json/json-internal.h
new file mode 100644
index 0000000000..5117b39f45
--- /dev/null
+++ b/src/libsystemd/sd-json/json-internal.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#pragma once
+
+#include "sd-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. This structure is generally 8 bytes wide (as double is 64-bit). */
+ bool boolean;
+ double real;
+ int64_t integer;
+ uint64_t unsig;
+} JsonValue;
+
+/* Let's protect us against accidental structure size changes on our most relevant arch */
+#ifdef __x86_64__
+assert_cc(sizeof(JsonValue) == 8U);
+#endif
+
+#define JSON_VALUE_NULL ((JsonValue) {})
+
+/* We use fake sd_json_variant 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 sd_json_variant. We convert between both on-the-fly as necessary. */
+enum
+{
+ _JSON_VARIANT_MAGIC_TRUE = 1,
+#define JSON_VARIANT_MAGIC_TRUE ((sd_json_variant*) _JSON_VARIANT_MAGIC_TRUE)
+ _JSON_VARIANT_MAGIC_FALSE,
+#define JSON_VARIANT_MAGIC_FALSE ((sd_json_variant*) _JSON_VARIANT_MAGIC_FALSE)
+ _JSON_VARIANT_MAGIC_NULL,
+#define JSON_VARIANT_MAGIC_NULL ((sd_json_variant*) _JSON_VARIANT_MAGIC_NULL)
+ _JSON_VARIANT_MAGIC_ZERO_INTEGER,
+#define JSON_VARIANT_MAGIC_ZERO_INTEGER ((sd_json_variant*) _JSON_VARIANT_MAGIC_ZERO_INTEGER)
+ _JSON_VARIANT_MAGIC_ZERO_UNSIGNED,
+#define JSON_VARIANT_MAGIC_ZERO_UNSIGNED ((sd_json_variant*) _JSON_VARIANT_MAGIC_ZERO_UNSIGNED)
+ _JSON_VARIANT_MAGIC_ZERO_REAL,
+#define JSON_VARIANT_MAGIC_ZERO_REAL ((sd_json_variant*) _JSON_VARIANT_MAGIC_ZERO_REAL)
+ _JSON_VARIANT_MAGIC_EMPTY_STRING,
+#define JSON_VARIANT_MAGIC_EMPTY_STRING ((sd_json_variant*) _JSON_VARIANT_MAGIC_EMPTY_STRING)
+ _JSON_VARIANT_MAGIC_EMPTY_ARRAY,
+#define JSON_VARIANT_MAGIC_EMPTY_ARRAY ((sd_json_variant*) _JSON_VARIANT_MAGIC_EMPTY_ARRAY)
+ _JSON_VARIANT_MAGIC_EMPTY_OBJECT,
+#define JSON_VARIANT_MAGIC_EMPTY_OBJECT ((sd_json_variant*) _JSON_VARIANT_MAGIC_EMPTY_OBJECT)
+ __JSON_VARIANT_MAGIC_MAX
+#define _JSON_VARIANT_MAGIC_MAX ((sd_json_variant*) __JSON_VARIANT_MAGIC_MAX)
+};
+
+/* This is only safe as long as we don't define more than 4K magic pointers, i.e. the page size of the simplest
+ * architectures we support. That's because we rely on the fact that malloc() will never allocate from the first memory
+ * page, as it is a faulting page for catching NULL pointer dereferences. */
+assert_cc((unsigned) __JSON_VARIANT_MAGIC_MAX < 4096U);
+
+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 = -EINVAL,
+};
+
+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/libsystemd/sd-json/json-util.c b/src/libsystemd/sd-json/json-util.c
new file mode 100644
index 0000000000..7c14c03a99
--- /dev/null
+++ b/src/libsystemd/sd-json/json-util.c
@@ -0,0 +1,135 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "glyph-util.h"
+#include "in-addr-util.h"
+#include "iovec-util.h"
+#include "json-util.h"
+#include "path-util.h"
+#include "string-util.h"
+#include "user-util.h"
+
+int json_dispatch_unbase64_iovec(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ _cleanup_free_ void *buffer = NULL;
+ struct iovec *iov = ASSERT_PTR(userdata);
+ size_t sz;
+ int r;
+
+ if (!sd_json_variant_is_string(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
+
+ r = sd_json_variant_unbase64(variant, &buffer, &sz);
+ if (r < 0)
+ return json_log(variant, flags, r, "JSON field '%s' is not valid Base64 data.", strna(name));
+
+ free_and_replace(iov->iov_base, buffer);
+ iov->iov_len = sz;
+ return 0;
+}
+
+int json_dispatch_byte_array_iovec(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ _cleanup_free_ uint8_t *buffer = NULL;
+ struct iovec *iov = ASSERT_PTR(userdata);
+ size_t sz, k = 0;
+
+ assert(variant);
+
+ if (!sd_json_variant_is_array(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
+
+ sz = sd_json_variant_elements(variant);
+
+ buffer = new(uint8_t, sz + 1);
+ if (!buffer)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(ENOMEM), "Out of memory.");
+
+ sd_json_variant *i;
+ JSON_VARIANT_ARRAY_FOREACH(i, variant) {
+ uint64_t b;
+
+ if (!sd_json_variant_is_unsigned(i))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an unsigned integer.", k, strna(name));
+
+ b = sd_json_variant_unsigned(i);
+ if (b > 0xff)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
+ "Element %zu of JSON field '%s' is out of range 0%s255.",
+ k, strna(name), special_glyph(SPECIAL_GLYPH_ELLIPSIS));
+
+ buffer[k++] = (uint8_t) b;
+ }
+ assert(k == sz);
+
+ /* Append a NUL byte for safety, like we do in memdup_suffix0() and others. */
+ buffer[sz] = 0;
+
+ free_and_replace(iov->iov_base, buffer);
+ iov->iov_len = sz;
+ return 0;
+}
+
+int json_dispatch_user_group_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ char **s = userdata;
+ const char *n;
+ int r;
+
+ if (sd_json_variant_is_null(variant)) {
+ *s = mfree(*s);
+ return 0;
+ }
+
+ if (!sd_json_variant_is_string(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
+
+ n = sd_json_variant_string(variant);
+ if (!valid_user_group_name(n, FLAGS_SET(flags, SD_JSON_RELAX) ? VALID_USER_RELAX : 0))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid user/group name.", strna(name));
+
+ r = free_and_strdup(s, n);
+ if (r < 0)
+ return json_log(variant, flags, r, "Failed to allocate string: %m");
+
+ return 0;
+}
+
+int json_dispatch_in_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ struct in_addr *address = ASSERT_PTR(userdata);
+ _cleanup_(iovec_done) struct iovec iov = {};
+ int r;
+
+ r = json_dispatch_byte_array_iovec(name, variant, flags, &iov);
+ if (r < 0)
+ return r;
+
+ if (iov.iov_len != sizeof(struct in_addr))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name));
+
+ memcpy(address, iov.iov_base, iov.iov_len);
+ return 0;
+}
+
+int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ char **p = ASSERT_PTR(userdata);
+ const char *path;
+
+ assert(variant);
+
+ if (sd_json_variant_is_null(variant)) {
+ *p = mfree(*p);
+ return 0;
+ }
+
+ if (!sd_json_variant_is_string(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
+
+ path = sd_json_variant_string(variant);
+ if (!((flags & SD_JSON_SAFE) ? path_is_normalized(path) : path_is_valid(path)))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a normalized file system path.", strna(name));
+ if (!path_is_absolute(path))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an absolute file system path.", strna(name));
+
+ if (free_and_strdup(p, path) < 0)
+ return json_log_oom(variant, flags);
+
+ return 0;
+}
diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h
new file mode 100644
index 0000000000..0b48158a4c
--- /dev/null
+++ b/src/libsystemd/sd-json/json-util.h
@@ -0,0 +1,178 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <syslog.h>
+
+#include "sd-json.h"
+
+#include "macro.h"
+
+#define JSON_VARIANT_REPLACE(v, q) \
+ do { \
+ typeof(v)* _v = &(v); \
+ typeof(q) _q = (q); \
+ sd_json_variant_unref(*_v); \
+ *_v = _q; \
+ } while(false)
+
+static inline int json_variant_set_field_non_null(sd_json_variant **v, const char *field, sd_json_variant *value) {
+ return value && !sd_json_variant_is_null(value) ? sd_json_variant_set_field(v, field, value) : 0;
+}
+
+struct json_variant_foreach_state {
+ sd_json_variant *variant;
+ size_t idx;
+};
+
+#define _JSON_VARIANT_ARRAY_FOREACH(i, v, state) \
+ for (struct json_variant_foreach_state state = { (v), 0 }; \
+ sd_json_variant_is_array(state.variant) && \
+ state.idx < sd_json_variant_elements(state.variant) && \
+ ({ i = sd_json_variant_by_index(state.variant, state.idx); \
+ true; }); \
+ state.idx++)
+#define JSON_VARIANT_ARRAY_FOREACH(i, v) \
+ _JSON_VARIANT_ARRAY_FOREACH(i, v, UNIQ_T(state, UNIQ))
+
+#define _JSON_VARIANT_OBJECT_FOREACH(k, e, v, state) \
+ for (struct json_variant_foreach_state state = { (v), 0 }; \
+ sd_json_variant_is_object(state.variant) && \
+ state.idx < sd_json_variant_elements(state.variant) && \
+ ({ k = sd_json_variant_string(sd_json_variant_by_index(state.variant, state.idx)); \
+ e = sd_json_variant_by_index(state.variant, state.idx + 1); \
+ true; }); \
+ state.idx += 2)
+#define JSON_VARIANT_OBJECT_FOREACH(k, e, v) \
+ _JSON_VARIANT_OBJECT_FOREACH(k, e, v, UNIQ_T(state, UNIQ))
+
+#define JSON_DISPATCH_ENUM_DEFINE(name, type, func) \
+ int name(const char *n, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { \
+ type *c = ASSERT_PTR(userdata); \
+ \
+ assert(variant); \
+ \
+ if (sd_json_variant_is_null(variant)) { \
+ *c = (type) -EINVAL; \
+ return 0; \
+ } \
+ \
+ if (!sd_json_variant_is_string(variant)) \
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(n)); \
+ \
+ type cc = func(sd_json_variant_string(variant)); \
+ if (cc < 0) \
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Value of JSON field '%s' not recognized.", strna(n)); \
+ \
+ *c = cc; \
+ return 0; \
+ }
+
+static inline int json_dispatch_level(sd_json_dispatch_flags_t 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 & SD_JSON_LOG) ||
+ (flags & SD_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 & (SD_JSON_PERMISSIVE|SD_JSON_WARNING))
+ return LOG_WARNING;
+
+ /* Otherwise it's an error. */
+ return LOG_ERR;
+}
+
+int json_log_internal(sd_json_variant *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, PROJECT_FILE, __LINE__, __func__, __VA_ARGS__) \
+ : -ERRNO_VALUE(_e); \
+ })
+
+#define json_log_oom(variant, flags) \
+ json_log(variant, flags, SYNTHETIC_ERRNO(ENOMEM), "Out of memory.")
+
+int json_dispatch_unbase64_iovec(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
+int json_dispatch_byte_array_iovec(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
+int json_dispatch_user_group_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
+int json_dispatch_in_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
+int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
+
+static inline int json_variant_unbase64_iovec(sd_json_variant *v, struct iovec *ret) {
+ return sd_json_variant_unbase64(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL);
+}
+
+static inline int json_variant_unhex_iovec(sd_json_variant *v, struct iovec *ret) {
+ return sd_json_variant_unhex(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL);
+}
+
+#define JSON_VARIANT_STRING_CONST(x) _JSON_VARIANT_STRING_CONST(UNIQ, (x))
+
+#define _JSON_VARIANT_STRING_CONST(xq, x) \
+ ({ \
+ _align_(2) static const char UNIQ_T(json_string_const, xq)[] = (x); \
+ assert((((uintptr_t) UNIQ_T(json_string_const, xq)) & 1) == 0); \
+ (sd_json_variant*) ((uintptr_t) UNIQ_T(json_string_const, xq) + 1); \
+ })
+
+enum {
+ /* This extends the _SD_JSON_BUILD_* enums we define in the public API with some additional values
+ * that we want to keep private for now. (Mostly because the underlying structures are not public, or
+ * because we aren't sure yet they are useful for others). */
+ _JSON_BUILD_STRV_ENV_PAIR = _SD_JSON_BUILD_MAX,
+ _JSON_BUILD_IOVEC_BASE64,
+ _JSON_BUILD_IOVEC_HEX,
+ _JSON_BUILD_HW_ADDR,
+ _JSON_BUILD_STRING_SET,
+
+ _JSON_BUILD_PAIR_UNSIGNED_NON_ZERO,
+ _JSON_BUILD_PAIR_FINITE_USEC,
+ _JSON_BUILD_PAIR_STRING_NON_EMPTY,
+ _JSON_BUILD_PAIR_STRV_NON_EMPTY,
+ _JSON_BUILD_PAIR_VARIANT_NON_NULL,
+ /* _SD_JSON_BUILD_PAIR_VARIANT_ARRAY_NON_EMPTY, */
+ _JSON_BUILD_PAIR_IN4_ADDR_NON_NULL,
+ _JSON_BUILD_PAIR_IN6_ADDR_NON_NULL,
+ _JSON_BUILD_PAIR_IN_ADDR_NON_NULL,
+ _JSON_BUILD_PAIR_ETHER_ADDR_NON_NULL,
+ _JSON_BUILD_PAIR_HW_ADDR_NON_NULL,
+
+ _SD_JSON_BUILD_REALLYMAX,
+};
+
+#define JSON_BUILD_STRV_ENV_PAIR(l) _JSON_BUILD_STRV_ENV_PAIR, (char**) { l }
+#define JSON_BUILD_IOVEC_BASE64(iov) _JSON_BUILD_IOVEC_BASE64, (const struct iovec*) { iov }
+#define JSON_BUILD_IOVEC_HEX(iov) _JSON_BUILD_IOVEC_HEX, (const struct iovec*) { iov }
+#define JSON_BUILD_CONST_STRING(s) _SD_JSON_BUILD_VARIANT, JSON_VARIANT_STRING_CONST(s)
+#define JSON_BUILD_IN4_ADDR(v) SD_JSON_BUILD_BYTE_ARRAY((const struct in_addr*) { v }, sizeof(struct in_addr))
+#define JSON_BUILD_IN6_ADDR(v) SD_JSON_BUILD_BYTE_ARRAY((const struct in6_addr*) { v }, sizeof(struct in6_addr))
+#define JSON_BUILD_IN_ADDR(v, f) SD_JSON_BUILD_BYTE_ARRAY(((const union in_addr_union*) { v })->bytes, FAMILY_ADDRESS_SIZE_SAFE(f))
+#define JSON_BUILD_ETHER_ADDR(v) SD_JSON_BUILD_BYTE_ARRAY(((const struct ether_addr*) { v })->ether_addr_octet, sizeof(struct ether_addr))
+#define JSON_BUILD_HW_ADDR(v) _JSON_BUILD_HW_ADDR, (const struct hw_addr_data*) { v }
+#define JSON_BUILD_STRING_SET(s) _JSON_BUILD_STRING_SET, (Set *) { s }
+
+#define JSON_BUILD_PAIR_UNSIGNED_NON_ZERO(name, u) _JSON_BUILD_PAIR_UNSIGNED_NON_ZERO, (const char*) { name }, (uint64_t) { u }
+#define JSON_BUILD_PAIR_FINITE_USEC(name, u) _JSON_BUILD_PAIR_FINITE_USEC, (const char*) { name }, (usec_t) { u }
+#define JSON_BUILD_PAIR_STRING_NON_EMPTY(name, s) _JSON_BUILD_PAIR_STRING_NON_EMPTY, (const char*) { name }, (const char*) { s }
+#define JSON_BUILD_PAIR_STRV_NON_EMPTY(name, l) _JSON_BUILD_PAIR_STRV_NON_EMPTY, (const char*) { name }, (char**) { l }
+#define JSON_BUILD_PAIR_VARIANT_NON_NULL(name, v) _JSON_BUILD_PAIR_VARIANT_NON_NULL, (const char*) { name }, (sd_json_variant*) { v }
+#define JSON_BUILD_PAIR_IN4_ADDR_NON_NULL(name, v) _JSON_BUILD_PAIR_IN4_ADDR_NON_NULL, (const char*) { name }, (const struct in_addr*) { v }
+#define JSON_BUILD_PAIR_IN6_ADDR_NON_NULL(name, v) _JSON_BUILD_PAIR_IN6_ADDR_NON_NULL, (const char*) { name }, (const struct in6_addr*) { v }
+#define JSON_BUILD_PAIR_IN_ADDR_NON_NULL(name, v, f) _JSON_BUILD_PAIR_IN_ADDR_NON_NULL, (const char*) { name }, (const union in_addr_union*) { v }, (int) { f }
+#define JSON_BUILD_PAIR_ETHER_ADDR_NON_NULL(name, v) _JSON_BUILD_PAIR_ETHER_ADDR_NON_NULL, (const char*) { name }, (const struct ether_addr*) { v }
+#define JSON_BUILD_PAIR_HW_ADDR_NON_NULL(name, v) _JSON_BUILD_PAIR_HW_ADDR_NON_NULL, (const char*) { name }, (const struct hw_addr_data*) { v }
+
+#define JSON_BUILD_PAIR_IOVEC_BASE64(name, iov) SD_JSON_BUILD_PAIR(name, JSON_BUILD_IOVEC_BASE64(iov))
+#define JSON_BUILD_PAIR_IOVEC_HEX(name, iov) SD_JSON_BUILD_PAIR(name, JSON_BUILD_IOVEC_HEX(iov))
+#define JSON_BUILD_PAIR_IN4_ADDR(name, v) SD_JSON_BUILD_PAIR(name, JSON_BUILD_IN4_ADDR(v))
+#define JSON_BUILD_PAIR_IN6_ADDR(name, v) SD_JSON_BUILD_PAIR(name, JSON_BUILD_IN6_ADDR(v))
+#define JSON_BUILD_PAIR_IN_ADDR(name, v, f) SD_JSON_BUILD_PAIR(name, JSON_BUILD_IN_ADDR(v, f))
+#define JSON_BUILD_PAIR_ETHER_ADDR(name, v) SD_JSON_BUILD_PAIR(name, JSON_BUILD_ETHER_ADDR(v))
+#define JSON_BUILD_PAIR_HW_ADDR(name, v) SD_JSON_BUILD_PAIR(name, JSON_BUILD_HW_ADDR(v))
+#define JSON_BUILD_PAIR_STRING_SET(name, s) SD_JSON_BUILD_PAIR(name, JSON_BUILD_STRING_SET(s))
diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c
new file mode 100644
index 0000000000..5414b2257f
--- /dev/null
+++ b/src/libsystemd/sd-json/sd-json.c
@@ -0,0 +1,5278 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <locale.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include "sd-json.h"
+#include "sd-messages.h"
+
+#include "alloc-util.h"
+#include "errno-util.h"
+#include "escape.h"
+#include "ether-addr-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "float.h"
+#include "glyph-util.h"
+#include "hexdecoct.h"
+#include "in-addr-util.h"
+#include "iovec-util.h"
+#include "json-internal.h"
+#include "json-util.h"
+#include "macro.h"
+#include "math-util.h"
+#include "memory-util.h"
+#include "memstream-util.h"
+#include "path-util.h"
+#include "set.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "user-util.h"
+#include "utf8.h"
+
+/* Refuse putting together variants with a larger depth than 2K 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 first was 16k, but it was discovered to be too high on llvm/x86-64. See also:
+ * https://github.com/systemd/systemd/issues/10738
+ *
+ * The value then was 4k, but it was discovered to be too high on s390x/aarch64. See also:
+ * https://github.com/systemd/systemd/issues/14396 */
+
+#define DEPTH_MAX (2U*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 */
+ unsigned 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 sd_json_variant {
+ 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.) */
+ unsigned n_ref;
+
+ /* If this sd_json_variant 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.) */
+ sd_json_variant *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;
+
+ /* The current 'depth' of the sd_json_variant, i.e. how many levels of member variants this has */
+ uint16_t depth;
+
+ int type:8; /* actually sd_json_variant_type_t, but reduced to 8bit size */
+
+ /* 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;
+
+ /* Erase from memory when freeing */
+ bool sensitive:1;
+
+ /* True if we know that any referenced json object is marked sensitive */
+ bool recursive_sensitive:1;
+
+ /* If this is an object the fields are strictly ordered by name */
+ bool sorted:1;
+
+ /* If in addition to this object all objects referenced by it are also ordered strictly by name */
+ bool normalized:1;
+
+ 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. */
+ sd_json_variant *reference;
+
+ /* Strings are placed immediately after the structure. Note that when this is a sd_json_variant
+ * 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. */
+ DECLARE_FLEX_ARRAY(char, string);
+ };
+};
+
+/* Inside string arrays we have a series of sd_json_variant 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 7 chars are stored within the array elements, and all others in separate allocations) */
+#define INLINE_STRING_MAX (sizeof(sd_json_variant) - offsetof(sd_json_variant, 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). */
+#if defined(__x86_64__) && __SIZEOF_POINTER__ == 8
+assert_cc(sizeof(sd_json_variant) == 40U);
+assert_cc(INLINE_STRING_MAX == 7U);
+#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 sd_json_variant* 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 sd_json_variant* pointers, with the bit set. */
+
+static bool json_variant_is_magic(const sd_json_variant *v) {
+ if (!v)
+ return false;
+
+ return v < _JSON_VARIANT_MAGIC_MAX;
+}
+
+static bool json_variant_is_const_string(const sd_json_variant *v) {
+
+ if (v < _JSON_VARIANT_MAGIC_MAX)
+ return false;
+
+ /* A proper sd_json_variant 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 sd_json_variant *v) {
+
+ if (v < _JSON_VARIANT_MAGIC_MAX)
+ return false;
+
+ return (((uintptr_t) v) & 1) == 0;
+}
+
+static sd_json_variant *json_variant_dereference(sd_json_variant *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(sd_json_variant *v) {
+
+ v = json_variant_dereference(v);
+ if (!v)
+ return 0;
+
+ if (!json_variant_is_regular(v))
+ return 0;
+
+ return v->depth;
+}
+
+static sd_json_variant *json_variant_formalize(sd_json_variant *v) {
+
+ /* Converts json variant pointers 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 (sd_json_variant_type(v)) {
+
+ case SD_JSON_VARIANT_BOOLEAN:
+ return sd_json_variant_boolean(v) ? JSON_VARIANT_MAGIC_TRUE : JSON_VARIANT_MAGIC_FALSE;
+
+ case SD_JSON_VARIANT_NULL:
+ return JSON_VARIANT_MAGIC_NULL;
+
+ case SD_JSON_VARIANT_INTEGER:
+ return sd_json_variant_integer(v) == 0 ? JSON_VARIANT_MAGIC_ZERO_INTEGER : v;
+
+ case SD_JSON_VARIANT_UNSIGNED:
+ return sd_json_variant_unsigned(v) == 0 ? JSON_VARIANT_MAGIC_ZERO_UNSIGNED : v;
+
+ case SD_JSON_VARIANT_REAL:
+ return iszero_safe(sd_json_variant_real(v)) ? JSON_VARIANT_MAGIC_ZERO_REAL : v;
+
+ case SD_JSON_VARIANT_STRING:
+ return isempty(sd_json_variant_string(v)) ? JSON_VARIANT_MAGIC_EMPTY_STRING : v;
+
+ case SD_JSON_VARIANT_ARRAY:
+ return sd_json_variant_elements(v) == 0 ? JSON_VARIANT_MAGIC_EMPTY_ARRAY : v;
+
+ case SD_JSON_VARIANT_OBJECT:
+ return sd_json_variant_elements(v) == 0 ? JSON_VARIANT_MAGIC_EMPTY_OBJECT : v;
+
+ default:
+ return v;
+ }
+}
+
+static sd_json_variant *json_variant_conservative_formalize(sd_json_variant *v) {
+
+ /* Much like json_variant_formalize(), 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_formalize(v);
+}
+
+static int json_variant_new(sd_json_variant **ret, sd_json_variant_type_t type, size_t space) {
+ sd_json_variant *v;
+
+ assert_return(ret, -EINVAL);
+
+ v = malloc0(MAX(sizeof(sd_json_variant),
+ offsetof(sd_json_variant, value) + space));
+ if (!v)
+ return -ENOMEM;
+
+ v->n_ref = 1;
+ v->type = type;
+
+ *ret = v;
+ return 0;
+}
+
+_public_ int sd_json_variant_new_integer(sd_json_variant **ret, int64_t i) {
+ sd_json_variant *v;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ if (i == 0) {
+ *ret = JSON_VARIANT_MAGIC_ZERO_INTEGER;
+ return 0;
+ }
+
+ r = json_variant_new(&v, SD_JSON_VARIANT_INTEGER, sizeof(i));
+ if (r < 0)
+ return r;
+
+ v->value.integer = i;
+ *ret = v;
+
+ return 0;
+}
+
+_public_ int sd_json_variant_new_unsigned(sd_json_variant **ret, uint64_t u) {
+ sd_json_variant *v;
+ int r;
+
+ assert_return(ret, -EINVAL);
+ if (u == 0) {
+ *ret = JSON_VARIANT_MAGIC_ZERO_UNSIGNED;
+ return 0;
+ }
+
+ r = json_variant_new(&v, SD_JSON_VARIANT_UNSIGNED, sizeof(u));
+ if (r < 0)
+ return r;
+
+ v->value.unsig = u;
+ *ret = v;
+
+ return 0;
+}
+
+_public_ int sd_json_variant_new_real(sd_json_variant **ret, double d) {
+ sd_json_variant *v;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ r = fpclassify(d);
+ switch (r) {
+ case FP_NAN:
+ case FP_INFINITE:
+ /* JSON doesn't know NaN, +Infinity or -Infinity. Let's silently convert to 'null'. */
+ *ret = JSON_VARIANT_MAGIC_NULL;
+ return 0;
+
+ case FP_ZERO:
+ *ret = JSON_VARIANT_MAGIC_ZERO_REAL;
+ return 0;
+ }
+
+ r = json_variant_new(&v, SD_JSON_VARIANT_REAL, sizeof(d));
+ if (r < 0)
+ return r;
+
+ v->value.real = d;
+ *ret = v;
+
+ return 0;
+}
+
+_public_ int sd_json_variant_new_boolean(sd_json_variant **ret, int b) {
+ assert_return(ret, -EINVAL);
+
+ if (b)
+ *ret = JSON_VARIANT_MAGIC_TRUE;
+ else
+ *ret = JSON_VARIANT_MAGIC_FALSE;
+
+ return 0;
+}
+
+_public_ int sd_json_variant_new_null(sd_json_variant **ret) {
+ assert_return(ret, -EINVAL);
+
+ *ret = JSON_VARIANT_MAGIC_NULL;
+ return 0;
+}
+
+_public_ int sd_json_variant_new_stringn(sd_json_variant **ret, const char *s, size_t n) {
+ sd_json_variant *v;
+ int r;
+
+ assert_return(ret, -EINVAL);
+ if (!s) {
+ assert_return(IN_SET(n, 0, SIZE_MAX), -EINVAL);
+ return sd_json_variant_new_null(ret);
+ }
+ if (n == SIZE_MAX) /* determine length automatically */
+ n = strlen(s);
+ else if (memchr(s, 0, n)) /* don't allow embedded NUL, as we can't express that in JSON */
+ return -EINVAL;
+ if (n == 0) {
+ *ret = JSON_VARIANT_MAGIC_EMPTY_STRING;
+ return 0;
+ }
+
+ if (!utf8_is_valid_n(s, n)) /* JSON strings must be valid UTF-8 */
+ return -EUCLEAN;
+
+ r = json_variant_new(&v, SD_JSON_VARIANT_STRING, n + 1);
+ if (r < 0)
+ return r;
+
+ memcpy(v->string, s, n);
+ v->string[n] = 0;
+
+ *ret = v;
+ return 0;
+}
+
+_public_ int sd_json_variant_new_string(sd_json_variant **ret, const char *s) {
+ return sd_json_variant_new_stringn(ret, s, SIZE_MAX);
+}
+
+_public_ int sd_json_variant_new_base64(sd_json_variant **ret, const void *p, size_t n) {
+ _cleanup_free_ char *s = NULL;
+ ssize_t k;
+
+ assert_return(ret, -EINVAL);
+ assert_return(n == 0 || p, -EINVAL);
+
+ k = base64mem(p, n, &s);
+ if (k < 0)
+ return k;
+
+ return sd_json_variant_new_stringn(ret, s, k);
+}
+
+_public_ int sd_json_variant_new_base32hex(sd_json_variant **ret, const void *p, size_t n) {
+ _cleanup_free_ char *s = NULL;
+
+ assert_return(ret, -EINVAL);
+ assert_return(n == 0 || p, -EINVAL);
+
+ s = base32hexmem(p, n, false);
+ if (!s)
+ return -ENOMEM;
+
+ return sd_json_variant_new_string(ret, s);
+}
+
+_public_ int sd_json_variant_new_hex(sd_json_variant **ret, const void *p, size_t n) {
+ _cleanup_free_ char *s = NULL;
+
+ assert_return(ret, -EINVAL);
+ assert_return(n == 0 || p, -EINVAL);
+
+ s = hexmem(p, n);
+ if (!s)
+ return -ENOMEM;
+
+ return sd_json_variant_new_stringn(ret, s, n*2);
+}
+
+_public_ int sd_json_variant_new_octescape(sd_json_variant **ret, const void *p, size_t n) {
+ _cleanup_free_ char *s = NULL;
+
+ assert_return(ret, -EINVAL);
+ assert_return(n == 0 || p, -EINVAL);
+
+ s = octescape(p, n);
+ if (!s)
+ return -ENOMEM;
+
+ return sd_json_variant_new_string(ret, s);
+}
+
+_public_ int sd_json_variant_new_id128(sd_json_variant **ret, sd_id128_t id) {
+ return sd_json_variant_new_string(ret, SD_ID128_TO_STRING(id));
+}
+
+_public_ int sd_json_variant_new_uuid(sd_json_variant **ret, sd_id128_t id) {
+ return sd_json_variant_new_string(ret, SD_ID128_TO_UUID_STRING(id));
+}
+
+static void json_variant_set(sd_json_variant *a, sd_json_variant *b) {
+ assert(a);
+
+ b = json_variant_dereference(b);
+ if (!b) {
+ a->type = SD_JSON_VARIANT_NULL;
+ return;
+ }
+
+ a->type = sd_json_variant_type(b);
+ switch (a->type) {
+
+ case SD_JSON_VARIANT_INTEGER:
+ a->value.integer = sd_json_variant_integer(b);
+ break;
+
+ case SD_JSON_VARIANT_UNSIGNED:
+ a->value.unsig = sd_json_variant_unsigned(b);
+ break;
+
+ case SD_JSON_VARIANT_REAL:
+ a->value.real = sd_json_variant_real(b);
+ break;
+
+ case SD_JSON_VARIANT_BOOLEAN:
+ a->value.boolean = sd_json_variant_boolean(b);
+ break;
+
+ case SD_JSON_VARIANT_STRING: {
+ const char *s;
+
+ assert_se(s = sd_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 SD_JSON_VARIANT_ARRAY:
+ case SD_JSON_VARIANT_OBJECT:
+ a->is_reference = true;
+ a->reference = sd_json_variant_ref(json_variant_conservative_formalize(b));
+ break;
+
+ case SD_JSON_VARIANT_NULL:
+ break;
+
+ default:
+ assert_not_reached();
+ }
+}
+
+static void json_variant_copy_source(sd_json_variant *v, sd_json_variant *from) {
+ assert(v);
+
+ if (!json_variant_is_regular(from))
+ return;
+
+ v->line = from->line;
+ v->column = from->column;
+ v->source = json_source_ref(from->source);
+}
+
+static int json_variant_array_put_element(sd_json_variant *array, sd_json_variant *element) {
+ assert(array);
+ sd_json_variant *w = array + 1 + array->n_elements;
+
+ uint16_t d = json_variant_depth(element);
+ if (d >= DEPTH_MAX) /* Refuse too deep nesting */
+ return -ELNRNG;
+ if (d >= array->depth)
+ array->depth = d + 1;
+ array->n_elements++;
+
+ *w = (sd_json_variant) {
+ .is_embedded = true,
+ .parent = array,
+ };
+
+ json_variant_set(w, element);
+ json_variant_copy_source(w, element);
+
+ if (!sd_json_variant_is_normalized(element))
+ array->normalized = false;
+
+ return 0;
+}
+
+_public_ int sd_json_variant_new_array(sd_json_variant **ret, sd_json_variant **array, size_t n) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+ int r;
+
+ assert_return(ret, -EINVAL);
+ if (n == 0) {
+ *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY;
+ return 0;
+ }
+ assert_return(array, -EINVAL);
+
+ v = new(sd_json_variant, n + 1);
+ if (!v)
+ return -ENOMEM;
+
+ *v = (sd_json_variant) {
+ .n_ref = 1,
+ .type = SD_JSON_VARIANT_ARRAY,
+ .normalized = true,
+ };
+
+ while (v->n_elements < n) {
+ r = json_variant_array_put_element(v, array[v->n_elements]);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(v);
+ return 0;
+}
+
+_public_ int sd_json_variant_new_array_bytes(sd_json_variant **ret, const void *p, size_t n) {
+ assert_return(ret, -EINVAL);
+ if (n == 0) {
+ *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY;
+ return 0;
+ }
+ assert_return(p, -EINVAL);
+
+ sd_json_variant *v = new(sd_json_variant, n + 1);
+ if (!v)
+ return -ENOMEM;
+
+ *v = (sd_json_variant) {
+ .n_ref = 1,
+ .type = SD_JSON_VARIANT_ARRAY,
+ .n_elements = n,
+ .depth = 1,
+ };
+
+ for (size_t i = 0; i < n; i++) {
+ sd_json_variant *w = v + 1 + i;
+
+ *w = (sd_json_variant) {
+ .is_embedded = true,
+ .parent = v,
+ .type = SD_JSON_VARIANT_UNSIGNED,
+ .value.unsig = ((const uint8_t*) p)[i],
+ };
+ }
+
+ v->normalized = true;
+
+ *ret = v;
+ return 0;
+}
+
+_public_ int sd_json_variant_new_array_strv(sd_json_variant **ret, char **l) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *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(sd_json_variant, n + 1);
+ if (!v)
+ return -ENOMEM;
+
+ *v = (sd_json_variant) {
+ .n_ref = 1,
+ .type = SD_JSON_VARIANT_ARRAY,
+ .depth = 1,
+ };
+
+ for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
+ sd_json_variant *w = v + 1 + v->n_elements;
+ size_t k;
+
+ *w = (sd_json_variant) {
+ .is_embedded = true,
+ .parent = v,
+ .type = SD_JSON_VARIANT_STRING,
+ };
+
+ k = strlen(l[v->n_elements]);
+
+ if (k > INLINE_STRING_MAX) {
+ /* If string is too long, store it as reference. */
+
+ r = sd_json_variant_new_string(&w->reference, l[v->n_elements]);
+ if (r < 0)
+ return r;
+
+ w->is_reference = true;
+ } else {
+ if (!utf8_is_valid_n(l[v->n_elements], k)) /* JSON strings must be valid UTF-8 */
+ return -EUCLEAN;
+
+ memcpy(w->string, l[v->n_elements], k+1);
+ }
+ }
+
+ v->normalized = true;
+
+ *ret = TAKE_PTR(v);
+ return 0;
+}
+
+_public_ int sd_json_variant_new_object(sd_json_variant **ret, sd_json_variant **array, size_t n) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+ const char *prev = NULL;
+ bool sorted = true, normalized = true;
+
+ 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(sd_json_variant, n + 1);
+ if (!v)
+ return -ENOMEM;
+
+ *v = (sd_json_variant) {
+ .n_ref = 1,
+ .type = SD_JSON_VARIANT_OBJECT,
+ };
+
+ for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
+ sd_json_variant *w = v + 1 + v->n_elements,
+ *c = array[v->n_elements];
+ uint16_t d;
+
+ if ((v->n_elements & 1) == 0) {
+ const char *k;
+
+ if (!sd_json_variant_is_string(c))
+ return -EINVAL; /* Every second one needs to be a string, as it is the key name */
+
+ assert_se(k = sd_json_variant_string(c));
+
+ if (prev && strcmp(k, prev) <= 0)
+ sorted = normalized = false;
+
+ prev = k;
+ } else if (!sd_json_variant_is_normalized(c))
+ normalized = false;
+
+ d = json_variant_depth(c);
+ if (d >= DEPTH_MAX) /* Refuse too deep nesting */
+ return -ELNRNG;
+ if (d >= v->depth)
+ v->depth = d + 1;
+
+ *w = (sd_json_variant) {
+ .is_embedded = true,
+ .parent = v,
+ };
+
+ json_variant_set(w, c);
+ json_variant_copy_source(w, c);
+ }
+
+ v->normalized = normalized;
+ v->sorted = sorted;
+
+ *ret = TAKE_PTR(v);
+ return 0;
+}
+
+static size_t json_variant_size(sd_json_variant* v) {
+ if (!json_variant_is_regular(v))
+ return 0;
+
+ if (v->is_reference)
+ return offsetof(sd_json_variant, reference) + sizeof(sd_json_variant*);
+
+ switch (v->type) {
+
+ case SD_JSON_VARIANT_STRING:
+ return offsetof(sd_json_variant, string) + strlen(v->string) + 1;
+
+ case SD_JSON_VARIANT_REAL:
+ return offsetof(sd_json_variant, value) + sizeof(double);
+
+ case SD_JSON_VARIANT_UNSIGNED:
+ return offsetof(sd_json_variant, value) + sizeof(uint64_t);
+
+ case SD_JSON_VARIANT_INTEGER:
+ return offsetof(sd_json_variant, value) + sizeof(int64_t);
+
+ case SD_JSON_VARIANT_BOOLEAN:
+ return offsetof(sd_json_variant, value) + sizeof(bool);
+
+ case SD_JSON_VARIANT_ARRAY:
+ case SD_JSON_VARIANT_OBJECT:
+ return offsetof(sd_json_variant, n_elements) + sizeof(size_t);
+
+ case SD_JSON_VARIANT_NULL:
+ return offsetof(sd_json_variant, value);
+
+ default:
+ assert_not_reached();
+ }
+}
+
+static void json_variant_free_inner(sd_json_variant *v, bool force_sensitive) {
+ bool sensitive;
+
+ assert(v);
+
+ if (!json_variant_is_regular(v))
+ return;
+
+ json_source_unref(v->source);
+
+ sensitive = v->sensitive || force_sensitive;
+
+ if (v->is_reference) {
+ if (sensitive)
+ sd_json_variant_sensitive(v->reference);
+
+ sd_json_variant_unref(v->reference);
+ return;
+ }
+
+ if (IN_SET(v->type, SD_JSON_VARIANT_ARRAY, SD_JSON_VARIANT_OBJECT))
+ for (size_t i = 0; i < v->n_elements; i++)
+ json_variant_free_inner(v + 1 + i, sensitive);
+
+ if (sensitive)
+ explicit_bzero_safe(v, json_variant_size(v));
+}
+
+static unsigned json_variant_n_ref(const sd_json_variant *v) {
+ /* Return the number of references to v.
+ * 0 => NULL or not a regular object or embedded.
+ * >0 => number of references
+ */
+
+ if (!v || !json_variant_is_regular(v) || v->is_embedded)
+ return 0;
+
+ assert(v->n_ref > 0);
+ return v->n_ref;
+}
+
+_public_ sd_json_variant *sd_json_variant_ref(sd_json_variant *v) {
+ if (!v)
+ return NULL;
+ if (!json_variant_is_regular(v))
+ return v;
+
+ if (v->is_embedded)
+ sd_json_variant_ref(v->parent); /* ref the compounding variant instead */
+ else {
+ assert(v->n_ref > 0);
+ v->n_ref++;
+ }
+
+ return v;
+}
+
+_public_ sd_json_variant *sd_json_variant_unref(sd_json_variant *v) {
+ if (!v)
+ return NULL;
+ if (!json_variant_is_regular(v))
+ return NULL;
+
+ if (v->is_embedded)
+ sd_json_variant_unref(v->parent);
+ else {
+ assert(v->n_ref > 0);
+ v->n_ref--;
+
+ if (v->n_ref == 0) {
+ json_variant_free_inner(v, false);
+ free(v);
+ }
+ }
+
+ return NULL;
+}
+
+_public_ void sd_json_variant_unref_many(sd_json_variant **array, size_t n) {
+ assert(array || n == 0);
+
+ for (size_t i = 0; i < n; i++)
+ sd_json_variant_unref(array[i]);
+
+ free(array);
+}
+
+_public_ const char *sd_json_variant_string(sd_json_variant *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 sd_json_variant_string(v->reference);
+ if (v->type != SD_JSON_VARIANT_STRING)
+ goto mismatch;
+
+ return v->string;
+
+mismatch:
+ log_debug("Non-string JSON variant requested as string, returning NULL.");
+ return NULL;
+}
+
+_public_ int sd_json_variant_boolean(sd_json_variant *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 != SD_JSON_VARIANT_BOOLEAN)
+ goto mismatch;
+ if (v->is_reference)
+ return sd_json_variant_boolean(v->reference);
+
+ return v->value.boolean;
+
+mismatch:
+ log_debug("Non-boolean JSON variant requested as boolean, returning false.");
+ return false;
+}
+
+_public_ int64_t sd_json_variant_integer(sd_json_variant *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 sd_json_variant_integer(v->reference);
+
+ switch (v->type) {
+
+ case SD_JSON_VARIANT_INTEGER:
+ return v->value.integer;
+
+ case SD_JSON_VARIANT_UNSIGNED:
+ if (v->value.unsig <= INT64_MAX)
+ return (int64_t) v->value.unsig;
+
+ log_debug("Unsigned integer %" PRIu64 " requested as signed integer and out of range, returning 0.", v->value.unsig);
+ return 0;
+
+ case SD_JSON_VARIANT_REAL: {
+ int64_t converted;
+
+ converted = (int64_t) v->value.real;
+
+ if (fp_equal((double) converted, v->value.real))
+ return converted;
+
+ log_debug("Real %g 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;
+}
+
+_public_ uint64_t sd_json_variant_unsigned(sd_json_variant *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 sd_json_variant_integer(v->reference);
+
+ switch (v->type) {
+
+ case SD_JSON_VARIANT_INTEGER:
+ if (v->value.integer >= 0)
+ return (uint64_t) v->value.integer;
+
+ log_debug("Signed integer %" PRIi64 " requested as unsigned integer and out of range, returning 0.", v->value.integer);
+ return 0;
+
+ case SD_JSON_VARIANT_UNSIGNED:
+ return v->value.unsig;
+
+ case SD_JSON_VARIANT_REAL: {
+ uint64_t converted;
+
+ converted = (uint64_t) v->value.real;
+
+ if (fp_equal((double) converted, v->value.real))
+ return converted;
+
+ log_debug("Real %g 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;
+}
+
+_public_ double sd_json_variant_real(sd_json_variant *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 sd_json_variant_real(v->reference);
+
+ switch (v->type) {
+
+ case SD_JSON_VARIANT_REAL:
+ return v->value.real;
+
+ case SD_JSON_VARIANT_INTEGER: {
+ double converted = (double) v->value.integer;
+
+ if ((int64_t) converted == v->value.integer)
+ return converted;
+
+ log_debug("Signed integer %" PRIi64 " requested as real, and cannot be converted losslessly, returning 0.", v->value.integer);
+ return 0.0;
+ }
+
+ case SD_JSON_VARIANT_UNSIGNED: {
+ double converted = (double) v->value.unsig;
+
+ if ((uint64_t) converted == v->value.unsig)
+ return converted;
+
+ log_debug("Unsigned integer %" PRIu64 " 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;
+}
+
+_public_ int sd_json_variant_is_negative(sd_json_variant *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 sd_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 SD_JSON_VARIANT_REAL:
+ return v->value.real < 0;
+
+ case SD_JSON_VARIANT_INTEGER:
+ return v->value.integer < 0;
+
+ case SD_JSON_VARIANT_UNSIGNED:
+ return false;
+
+ default:
+ break;
+ }
+
+mismatch:
+ log_debug("Non-integer JSON variant tested for negativity, returning false.");
+ return false;
+}
+
+_public_ int sd_json_variant_is_blank_object(sd_json_variant *v) {
+ /* Returns true if the specified object is null or empty */
+ return !v ||
+ sd_json_variant_is_null(v) ||
+ (sd_json_variant_is_object(v) && sd_json_variant_elements(v) == 0);
+}
+
+_public_ int sd_json_variant_is_blank_array(sd_json_variant *v) {
+ return !v ||
+ sd_json_variant_is_null(v) ||
+ (sd_json_variant_is_array(v) && sd_json_variant_elements(v) == 0);
+}
+
+_public_ sd_json_variant_type_t sd_json_variant_type(sd_json_variant *v) {
+
+ if (!v)
+ return _SD_JSON_VARIANT_TYPE_INVALID;
+
+ if (json_variant_is_const_string(v))
+ return SD_JSON_VARIANT_STRING;
+
+ if (v == JSON_VARIANT_MAGIC_TRUE || v == JSON_VARIANT_MAGIC_FALSE)
+ return SD_JSON_VARIANT_BOOLEAN;
+
+ if (v == JSON_VARIANT_MAGIC_NULL)
+ return SD_JSON_VARIANT_NULL;
+
+ if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER)
+ return SD_JSON_VARIANT_INTEGER;
+
+ if (v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED)
+ return SD_JSON_VARIANT_UNSIGNED;
+
+ if (v == JSON_VARIANT_MAGIC_ZERO_REAL)
+ return SD_JSON_VARIANT_REAL;
+
+ if (v == JSON_VARIANT_MAGIC_EMPTY_STRING)
+ return SD_JSON_VARIANT_STRING;
+
+ if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY)
+ return SD_JSON_VARIANT_ARRAY;
+
+ if (v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
+ return SD_JSON_VARIANT_OBJECT;
+
+ return v->type;
+}
+
+_function_no_sanitize_float_cast_overflow_
+_public_ int sd_json_variant_has_type(sd_json_variant *v, sd_json_variant_type_t type) {
+ sd_json_variant_type_t rt;
+
+ /* Note: we turn off ubsan float cast overflow detection for this function, since it would complain
+ * about our float casts but we do them explicitly to detect conversion errors. */
+
+ v = json_variant_dereference(v);
+ if (!v)
+ return false;
+
+ rt = sd_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, SD_JSON_VARIANT_INTEGER, SD_JSON_VARIANT_UNSIGNED, SD_JSON_VARIANT_REAL, SD_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 == SD_JSON_VARIANT_NUMBER)
+ return IN_SET(rt, SD_JSON_VARIANT_INTEGER, SD_JSON_VARIANT_UNSIGNED, SD_JSON_VARIANT_REAL);
+
+ /* Integer conversions are OK in many cases */
+ if (rt == SD_JSON_VARIANT_INTEGER && type == SD_JSON_VARIANT_UNSIGNED)
+ return v->value.integer >= 0;
+ if (rt == SD_JSON_VARIANT_UNSIGNED && type == SD_JSON_VARIANT_INTEGER)
+ return v->value.unsig <= INT64_MAX;
+
+ /* Any integer that can be converted lossley to a real and back may also be considered a real */
+ if (rt == SD_JSON_VARIANT_INTEGER && type == SD_JSON_VARIANT_REAL)
+ return (int64_t) (double) v->value.integer == v->value.integer;
+ if (rt == SD_JSON_VARIANT_UNSIGNED && type == SD_JSON_VARIANT_REAL)
+ return (uint64_t) (double) v->value.unsig == v->value.unsig;
+
+ /* Any real that can be converted losslessly to an integer and back may also be considered an integer */
+ if (rt == SD_JSON_VARIANT_REAL && type == SD_JSON_VARIANT_INTEGER)
+ return fp_equal((double) (int64_t) v->value.real, v->value.real);
+ if (rt == SD_JSON_VARIANT_REAL && type == SD_JSON_VARIANT_UNSIGNED)
+ return fp_equal((double) (uint64_t) v->value.real, v->value.real);
+
+ return false;
+}
+
+_public_ int sd_json_variant_is_string(sd_json_variant *v) {
+ return sd_json_variant_has_type(v, SD_JSON_VARIANT_STRING);
+}
+
+_public_ int sd_json_variant_is_integer(sd_json_variant *v) {
+ return sd_json_variant_has_type(v, SD_JSON_VARIANT_INTEGER);
+}
+
+_public_ int sd_json_variant_is_unsigned(sd_json_variant *v) {
+ return sd_json_variant_has_type(v, SD_JSON_VARIANT_UNSIGNED);
+}
+
+_public_ int sd_json_variant_is_real(sd_json_variant *v) {
+ return sd_json_variant_has_type(v, SD_JSON_VARIANT_REAL);
+}
+
+_public_ int sd_json_variant_is_number(sd_json_variant *v) {
+ return sd_json_variant_has_type(v, SD_JSON_VARIANT_NUMBER);
+}
+
+_public_ int sd_json_variant_is_boolean(sd_json_variant *v) {
+ return sd_json_variant_has_type(v, SD_JSON_VARIANT_BOOLEAN);
+}
+
+_public_ int sd_json_variant_is_array(sd_json_variant *v) {
+ return sd_json_variant_has_type(v, SD_JSON_VARIANT_ARRAY);
+}
+
+_public_ int sd_json_variant_is_object(sd_json_variant *v) {
+ return sd_json_variant_has_type(v, SD_JSON_VARIANT_OBJECT);
+}
+
+_public_ int sd_json_variant_is_null(sd_json_variant *v) {
+ return sd_json_variant_has_type(v, SD_JSON_VARIANT_NULL);
+}
+
+_public_ size_t sd_json_variant_elements(sd_json_variant *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, SD_JSON_VARIANT_ARRAY, SD_JSON_VARIANT_OBJECT))
+ goto mismatch;
+ if (v->is_reference)
+ return sd_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;
+}
+
+_public_ sd_json_variant *sd_json_variant_by_index(sd_json_variant *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, SD_JSON_VARIANT_ARRAY, SD_JSON_VARIANT_OBJECT))
+ goto mismatch;
+ if (v->is_reference)
+ return sd_json_variant_by_index(v->reference, idx);
+ if (idx >= v->n_elements)
+ return NULL;
+
+ return json_variant_conservative_formalize(v + 1 + idx);
+
+mismatch:
+ log_debug("Element in non-array/non-object JSON variant requested by index, returning NULL.");
+ return NULL;
+}
+
+_public_ sd_json_variant *sd_json_variant_by_key_full(sd_json_variant *v, const char *key, sd_json_variant **ret_key) {
+ 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 != SD_JSON_VARIANT_OBJECT)
+ goto mismatch;
+ if (v->is_reference)
+ return sd_json_variant_by_key(v->reference, key);
+
+ if (v->sorted) {
+ size_t a = 0, b = v->n_elements/2;
+
+ /* If the variant is sorted we can use bisection to find the entry we need in O(log(n)) time */
+
+ while (b > a) {
+ sd_json_variant *p;
+ const char *f;
+ size_t i;
+ int c;
+
+ i = (a + b) / 2;
+ p = json_variant_dereference(v + 1 + i*2);
+
+ assert_se(f = sd_json_variant_string(p));
+
+ c = strcmp(key, f);
+ if (c == 0) {
+ if (ret_key)
+ *ret_key = json_variant_conservative_formalize(v + 1 + i*2);
+
+ return json_variant_conservative_formalize(v + 1 + i*2 + 1);
+ } else if (c < 0)
+ b = i;
+ else
+ a = i + 1;
+ }
+
+ goto not_found;
+ }
+
+ /* The variant is not sorted, hence search for the field linearly */
+ for (size_t i = 0; i < v->n_elements; i += 2) {
+ sd_json_variant *p;
+
+ p = json_variant_dereference(v + 1 + i);
+
+ if (!sd_json_variant_has_type(p, SD_JSON_VARIANT_STRING))
+ continue;
+
+ if (streq(sd_json_variant_string(p), key)) {
+
+ if (ret_key)
+ *ret_key = json_variant_conservative_formalize(v + 1 + i);
+
+ return json_variant_conservative_formalize(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;
+}
+
+_public_ sd_json_variant *sd_json_variant_by_key(sd_json_variant *v, const char *key) {
+ return sd_json_variant_by_key_full(v, key, NULL);
+}
+
+_public_ int sd_json_variant_equal(sd_json_variant *a, sd_json_variant *b) {
+ sd_json_variant_type_t t;
+
+ a = json_variant_formalize(a);
+ b = json_variant_formalize(b);
+
+ if (a == b)
+ return true;
+
+ t = sd_json_variant_type(a);
+ if (!sd_json_variant_has_type(b, t))
+ return false;
+
+ switch (t) {
+
+ case SD_JSON_VARIANT_STRING:
+ return streq(sd_json_variant_string(a), sd_json_variant_string(b));
+
+ case SD_JSON_VARIANT_INTEGER:
+ return sd_json_variant_integer(a) == sd_json_variant_integer(b);
+
+ case SD_JSON_VARIANT_UNSIGNED:
+ return sd_json_variant_unsigned(a) == sd_json_variant_unsigned(b);
+
+ case SD_JSON_VARIANT_REAL:
+ return fp_equal(sd_json_variant_real(a), sd_json_variant_real(b));
+
+ case SD_JSON_VARIANT_BOOLEAN:
+ return sd_json_variant_boolean(a) == sd_json_variant_boolean(b);
+
+ case SD_JSON_VARIANT_NULL:
+ return true;
+
+ case SD_JSON_VARIANT_ARRAY: {
+ size_t n = sd_json_variant_elements(a);
+ if (n != sd_json_variant_elements(b))
+ return false;
+
+ for (size_t i = 0; i < n; i++)
+ if (!sd_json_variant_equal(sd_json_variant_by_index(a, i), sd_json_variant_by_index(b, i)))
+ return false;
+
+ return true;
+ }
+
+ case SD_JSON_VARIANT_OBJECT: {
+ size_t n = sd_json_variant_elements(a);
+ if (n != sd_json_variant_elements(b))
+ return false;
+
+ /* Iterate through all keys in 'a' */
+ for (size_t i = 0; i < n; i += 2) {
+ bool found = false;
+
+ /* Match them against all keys in 'b' */
+ for (size_t j = 0; j < n; j += 2) {
+ sd_json_variant *key_b;
+
+ key_b = sd_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 (sd_json_variant_equal(sd_json_variant_by_index(a, i), key_b) &&
+ sd_json_variant_equal(sd_json_variant_by_index(a, i+1), sd_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();
+ }
+}
+
+_public_ void sd_json_variant_sensitive(sd_json_variant *v) {
+ assert(v);
+
+ /* Marks a variant as "sensitive", so that it is erased from memory when it is destroyed. This is a
+ * one-way operation: as soon as it is marked this way it remains marked this way until it's
+ * destroyed. A magic variant is never sensitive though, even when asked, since it's too
+ * basic. Similar, const string variant are never sensitive either, after all they are included in
+ * the source code as they are, which is not suitable for inclusion of secrets.
+ *
+ * Note that this flag has a recursive effect: when we destroy an object or array we'll propagate the
+ * flag to all contained variants. And if those are then destroyed this is propagated further down,
+ * and so on. */
+
+ v = json_variant_formalize(v);
+ if (!json_variant_is_regular(v))
+ return;
+
+ v->sensitive = true;
+}
+
+_public_ int sd_json_variant_is_sensitive(sd_json_variant *v) {
+ v = json_variant_formalize(v);
+ if (!json_variant_is_regular(v))
+ return false;
+
+ return v->sensitive;
+}
+
+_public_ int sd_json_variant_is_sensitive_recursive(sd_json_variant *v) {
+ if (!v)
+ return false;
+ if (sd_json_variant_is_sensitive(v))
+ return true;
+ if (!json_variant_is_regular(v))
+ return false;
+ if (v->recursive_sensitive) /* Already checked this before */
+ return true;
+ if (!IN_SET(v->type, SD_JSON_VARIANT_ARRAY, SD_JSON_VARIANT_OBJECT))
+ return false;
+ if (v->is_reference) {
+ if (!sd_json_variant_is_sensitive_recursive(v->reference))
+ return false;
+
+ return (v->recursive_sensitive = true);
+ }
+
+ for (size_t i = 0; i < sd_json_variant_elements(v); i++)
+ if (sd_json_variant_is_sensitive_recursive(sd_json_variant_by_index(v, i)))
+ return (v->recursive_sensitive = true);
+
+ /* Note: we only cache the result here in case true, since we allow all elements down the tree to
+ * have their sensitive flag toggled later on (but never off) */
+ return false;
+}
+
+static void json_variant_propagate_sensitive(sd_json_variant *from, sd_json_variant *to) {
+ if (sd_json_variant_is_sensitive(from))
+ sd_json_variant_sensitive(to);
+}
+
+_public_ int sd_json_variant_get_source(sd_json_variant *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, sd_json_variant *v, sd_json_format_flags_t flags, bool whitespace) {
+ size_t w, k;
+
+ if (!FLAGS_SET(flags, SD_JSON_FORMAT_SOURCE|SD_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 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 (size_t 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 void json_format_string(FILE *f, const char *q, sd_json_format_flags_t flags) {
+ assert(q);
+
+ fputc('"', f);
+
+ if (flags & SD_JSON_FORMAT_COLOR)
+ fputs(ansi_green(), f);
+
+ for (; *q; q++)
+ switch (*q) {
+ 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", (unsigned) *q);
+ else
+ fputc(*q, f);
+ break;
+ }
+
+ if (flags & SD_JSON_FORMAT_COLOR)
+ fputs(ANSI_NORMAL, f);
+
+ fputc('"', f);
+}
+
+static int json_format(FILE *f, sd_json_variant *v, sd_json_format_flags_t flags, const char *prefix) {
+ int r;
+
+ assert(f);
+ assert(v);
+
+ if (FLAGS_SET(flags, SD_JSON_FORMAT_CENSOR_SENSITIVE) && sd_json_variant_is_sensitive(v)) {
+ if (flags & SD_JSON_FORMAT_COLOR)
+ fputs(ansi_red(), f);
+ fputs("\"<sensitive data>\"", f);
+ if (flags & SD_JSON_FORMAT_COLOR)
+ fputs(ANSI_NORMAL, f);
+ return 0;
+ }
+
+ switch (sd_json_variant_type(v)) {
+
+ case SD_JSON_VARIANT_REAL: {
+ locale_t loc, old_loc;
+
+ loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
+ if (loc == (locale_t) 0)
+ return -errno;
+
+ if (flags & SD_JSON_FORMAT_COLOR)
+ fputs(ansi_highlight_blue(), f);
+
+ old_loc = uselocale(loc);
+ fprintf(f, "%.*e", DECIMAL_DIG, sd_json_variant_real(v));
+ uselocale(old_loc);
+
+ if (flags & SD_JSON_FORMAT_COLOR)
+ fputs(ANSI_NORMAL, f);
+
+ freelocale(loc);
+ break;
+ }
+
+ case SD_JSON_VARIANT_INTEGER:
+ if (flags & SD_JSON_FORMAT_COLOR)
+ fputs(ansi_highlight_blue(), f);
+
+ fprintf(f, "%" PRIdMAX, sd_json_variant_integer(v));
+
+ if (flags & SD_JSON_FORMAT_COLOR)
+ fputs(ANSI_NORMAL, f);
+ break;
+
+ case SD_JSON_VARIANT_UNSIGNED:
+ if (flags & SD_JSON_FORMAT_COLOR)
+ fputs(ansi_highlight_blue(), f);
+
+ fprintf(f, "%" PRIuMAX, sd_json_variant_unsigned(v));
+
+ if (flags & SD_JSON_FORMAT_COLOR)
+ fputs(ANSI_NORMAL, f);
+ break;
+
+ case SD_JSON_VARIANT_BOOLEAN:
+
+ if (flags & SD_JSON_FORMAT_COLOR)
+ fputs(ANSI_HIGHLIGHT, f);
+
+ if (sd_json_variant_boolean(v))
+ fputs("true", f);
+ else
+ fputs("false", f);
+
+ if (flags & SD_JSON_FORMAT_COLOR)
+ fputs(ANSI_NORMAL, f);
+
+ break;
+
+ case SD_JSON_VARIANT_NULL:
+ if (flags & SD_JSON_FORMAT_COLOR)
+ fputs(ANSI_HIGHLIGHT, f);
+
+ fputs("null", f);
+
+ if (flags & SD_JSON_FORMAT_COLOR)
+ fputs(ANSI_NORMAL, f);
+ break;
+
+ case SD_JSON_VARIANT_STRING:
+ json_format_string(f, sd_json_variant_string(v), flags);
+ break;
+
+ case SD_JSON_VARIANT_ARRAY: {
+ size_t n = sd_json_variant_elements(v);
+ if (n == 0)
+ fputs("[]", f);
+ else {
+ _cleanup_free_ char *joined = NULL;
+ const char *prefix2;
+
+ if (flags & SD_JSON_FORMAT_PRETTY) {
+ joined = strjoin(strempty(prefix), "\t");
+ if (!joined)
+ return -ENOMEM;
+
+ prefix2 = joined;
+ fputs("[\n", f);
+ } else {
+ prefix2 = strempty(prefix);
+ fputc('[', f);
+ }
+
+ for (size_t i = 0; i < n; i++) {
+ sd_json_variant *e;
+
+ assert_se(e = sd_json_variant_by_index(v, i));
+
+ if (i > 0) {
+ if (flags & SD_JSON_FORMAT_PRETTY)
+ fputs(",\n", f);
+ else
+ fputc(',', f);
+ }
+
+ if (flags & SD_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 & SD_JSON_FORMAT_PRETTY) {
+ fputc('\n', f);
+ print_source(f, v, flags, true);
+ fputs(strempty(prefix), f);
+ }
+
+ fputc(']', f);
+ }
+ break;
+ }
+
+ case SD_JSON_VARIANT_OBJECT: {
+ size_t n = sd_json_variant_elements(v);
+ if (n == 0)
+ fputs("{}", f);
+ else {
+ _cleanup_free_ char *joined = NULL;
+ const char *prefix2;
+
+ if (flags & SD_JSON_FORMAT_PRETTY) {
+ joined = strjoin(strempty(prefix), "\t");
+ if (!joined)
+ return -ENOMEM;
+
+ prefix2 = joined;
+ fputs("{\n", f);
+ } else {
+ prefix2 = strempty(prefix);
+ fputc('{', f);
+ }
+
+ for (size_t i = 0; i < n; i += 2) {
+ sd_json_variant *e;
+
+ e = sd_json_variant_by_index(v, i);
+
+ if (i > 0) {
+ if (flags & SD_JSON_FORMAT_PRETTY)
+ fputs(",\n", f);
+ else
+ fputc(',', f);
+ }
+
+ if (flags & SD_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 & SD_JSON_FORMAT_PRETTY ? " : " : ":", f);
+
+ r = json_format(f, sd_json_variant_by_index(v, i+1), flags, prefix2);
+ if (r < 0)
+ return r;
+ }
+
+ if (flags & SD_JSON_FORMAT_PRETTY) {
+ fputc('\n', f);
+ print_source(f, v, flags, true);
+ fputs(strempty(prefix), f);
+ }
+
+ fputc('}', f);
+ }
+ break;
+ }
+
+ default:
+ assert_not_reached();
+ }
+
+ return 0;
+}
+
+_public_ int sd_json_variant_format(sd_json_variant *v, sd_json_format_flags_t flags, char **ret) {
+ _cleanup_(memstream_done) MemStream m = {};
+ size_t sz;
+ FILE *f;
+ int r;
+
+ /* Returns the length of the generated string (without the terminating NUL),
+ * or negative on error. */
+
+ assert_return(v, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (flags & SD_JSON_FORMAT_OFF)
+ return -ENOEXEC;
+
+ f = memstream_init(&m);
+ if (!f)
+ return -ENOMEM;
+
+ r = sd_json_variant_dump(v, flags, f, NULL);
+ if (r < 0)
+ return r;
+
+ r = memstream_finalize(&m, ret, &sz);
+ if (r < 0)
+ return r;
+
+ return sz;
+}
+
+_public_ int sd_json_variant_dump(sd_json_variant *v, sd_json_format_flags_t flags, FILE *f, const char *prefix) {
+ if (!v) {
+ if (flags & SD_JSON_FORMAT_EMPTY_ARRAY)
+ v = JSON_VARIANT_MAGIC_EMPTY_ARRAY;
+ else
+ return 0;
+ }
+
+ if (!f)
+ f = stdout;
+
+ print_source(f, v, flags, false);
+
+ if (((flags & (SD_JSON_FORMAT_COLOR_AUTO|SD_JSON_FORMAT_COLOR)) == SD_JSON_FORMAT_COLOR_AUTO) && colors_enabled())
+ flags |= SD_JSON_FORMAT_COLOR;
+
+ if (((flags & (SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_PRETTY)) == SD_JSON_FORMAT_PRETTY_AUTO))
+ flags |= on_tty() ? SD_JSON_FORMAT_PRETTY : SD_JSON_FORMAT_NEWLINE;
+
+ if (flags & SD_JSON_FORMAT_SSE)
+ fputs("data: ", f);
+ if (flags & SD_JSON_FORMAT_SEQ)
+ fputc('\x1e', f); /* ASCII Record Separator */
+
+ json_format(f, v, flags, prefix);
+
+ if (flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_SEQ|SD_JSON_FORMAT_SSE|SD_JSON_FORMAT_NEWLINE))
+ fputc('\n', f);
+ if (flags & SD_JSON_FORMAT_SSE)
+ fputc('\n', f); /* In case of SSE add a second newline */
+
+ if (flags & SD_JSON_FORMAT_FLUSH)
+ return fflush_and_check(f);
+ return 0;
+}
+
+_public_ int sd_json_variant_filter(sd_json_variant **v, char **to_remove) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL;
+ _cleanup_free_ sd_json_variant **array = NULL;
+ size_t n = 0, k = 0;
+ int r;
+
+ assert(v);
+
+ if (sd_json_variant_is_blank_object(*v))
+ return 0;
+ if (!sd_json_variant_is_object(*v))
+ return -EINVAL;
+
+ if (strv_isempty(to_remove))
+ return 0;
+
+ for (size_t i = 0; i < sd_json_variant_elements(*v); i += 2) {
+ sd_json_variant *p;
+
+ p = sd_json_variant_by_index(*v, i);
+ if (!sd_json_variant_has_type(p, SD_JSON_VARIANT_STRING))
+ return -EINVAL;
+
+ if (strv_contains(to_remove, sd_json_variant_string(p))) {
+ if (!array) {
+ array = new(sd_json_variant*, sd_json_variant_elements(*v) - 2);
+ if (!array)
+ return -ENOMEM;
+
+ for (k = 0; k < i; k++)
+ array[k] = sd_json_variant_by_index(*v, k);
+ }
+
+ n++;
+ } else if (array) {
+ array[k++] = p;
+ array[k++] = sd_json_variant_by_index(*v, i + 1);
+ }
+ }
+
+ if (n == 0)
+ return 0;
+
+ r = sd_json_variant_new_object(&w, array, k);
+ if (r < 0)
+ return r;
+
+ json_variant_propagate_sensitive(*v, w);
+ JSON_VARIANT_REPLACE(*v, TAKE_PTR(w));
+
+ return (int) n;
+}
+
+_public_ int sd_json_variant_set_field(sd_json_variant **v, const char *field, sd_json_variant *value) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *field_variant = NULL, *w = NULL;
+ _cleanup_free_ sd_json_variant **array = NULL;
+ size_t k = 0;
+ int r;
+
+ assert(v);
+ assert(field);
+
+ if (sd_json_variant_is_blank_object(*v)) {
+ array = new(sd_json_variant*, 2);
+ if (!array)
+ return -ENOMEM;
+
+ } else {
+ if (!sd_json_variant_is_object(*v))
+ return -EINVAL;
+
+ for (size_t i = 0; i < sd_json_variant_elements(*v); i += 2) {
+ sd_json_variant *p;
+
+ p = sd_json_variant_by_index(*v, i);
+ if (!sd_json_variant_is_string(p))
+ return -EINVAL;
+
+ if (streq(sd_json_variant_string(p), field)) {
+
+ if (!array) {
+ array = new(sd_json_variant*, sd_json_variant_elements(*v));
+ if (!array)
+ return -ENOMEM;
+
+ for (k = 0; k < i; k++)
+ array[k] = sd_json_variant_by_index(*v, k);
+ }
+
+ } else if (array) {
+ array[k++] = p;
+ array[k++] = sd_json_variant_by_index(*v, i + 1);
+ }
+ }
+
+ if (!array) {
+ array = new(sd_json_variant*, sd_json_variant_elements(*v) + 2);
+ if (!array)
+ return -ENOMEM;
+
+ for (k = 0; k < sd_json_variant_elements(*v); k++)
+ array[k] = sd_json_variant_by_index(*v, k);
+ }
+ }
+
+ r = sd_json_variant_new_string(&field_variant, field);
+ if (r < 0)
+ return r;
+
+ array[k++] = field_variant;
+ array[k++] = value;
+
+ r = sd_json_variant_new_object(&w, array, k);
+ if (r < 0)
+ return r;
+
+ json_variant_propagate_sensitive(*v, w);
+ JSON_VARIANT_REPLACE(*v, TAKE_PTR(w));
+
+ return 1;
+}
+
+_public_ int sd_json_variant_set_fieldb(sd_json_variant **v, const char *field, ...) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL;
+ va_list ap;
+ int r;
+
+ va_start(ap, field);
+ r = sd_json_buildv(&w, ap);
+ va_end(ap);
+ if (r < 0)
+ return r;
+
+ return sd_json_variant_set_field(v, field, w);
+}
+
+_public_ int sd_json_variant_set_field_string(sd_json_variant **v, const char *field, const char *value) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *m = NULL;
+ int r;
+
+ r = sd_json_variant_new_string(&m, value);
+ if (r < 0)
+ return r;
+
+ return sd_json_variant_set_field(v, field, m);
+}
+
+_public_ int sd_json_variant_set_field_integer(sd_json_variant **v, const char *field, int64_t i) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *m = NULL;
+ int r;
+
+ r = sd_json_variant_new_integer(&m, i);
+ if (r < 0)
+ return r;
+
+ return sd_json_variant_set_field(v, field, m);
+}
+
+_public_ int sd_json_variant_set_field_unsigned(sd_json_variant **v, const char *field, uint64_t u) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *m = NULL;
+ int r;
+
+ r = sd_json_variant_new_unsigned(&m, u);
+ if (r < 0)
+ return r;
+
+ return sd_json_variant_set_field(v, field, m);
+}
+
+_public_ int sd_json_variant_set_field_boolean(sd_json_variant **v, const char *field, int b) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *m = NULL;
+ int r;
+
+ r = sd_json_variant_new_boolean(&m, b);
+ if (r < 0)
+ return r;
+
+ return sd_json_variant_set_field(v, field, m);
+}
+
+_public_ int sd_json_variant_set_field_strv(sd_json_variant **v, const char *field, char **l) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *m = NULL;
+ int r;
+
+ r = sd_json_variant_new_array_strv(&m, l);
+ if (r < 0)
+ return r;
+
+ return sd_json_variant_set_field(v, field, m);
+}
+
+_public_ int sd_json_variant_merge_object(sd_json_variant **v, sd_json_variant *m) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL;
+ _cleanup_free_ sd_json_variant **array = NULL;
+ size_t v_elements, m_elements, k;
+ bool v_blank, m_blank;
+ int r;
+
+ m = json_variant_dereference(m);
+
+ v_blank = sd_json_variant_is_blank_object(*v);
+ m_blank = sd_json_variant_is_blank_object(m);
+
+ if (!v_blank && !sd_json_variant_is_object(*v))
+ return -EINVAL;
+ if (!m_blank && !sd_json_variant_is_object(m))
+ return -EINVAL;
+
+ if (m_blank)
+ return 0; /* nothing to do */
+
+ if (v_blank) {
+ JSON_VARIANT_REPLACE(*v, sd_json_variant_ref(m));
+ return 1;
+ }
+
+ v_elements = sd_json_variant_elements(*v);
+ m_elements = sd_json_variant_elements(m);
+ if (v_elements > SIZE_MAX - m_elements) /* overflow check */
+ return -ENOMEM;
+
+ array = new(sd_json_variant*, v_elements + m_elements);
+ if (!array)
+ return -ENOMEM;
+
+ k = 0;
+ for (size_t i = 0; i < v_elements; i += 2) {
+ sd_json_variant *u;
+
+ u = sd_json_variant_by_index(*v, i);
+ if (!sd_json_variant_is_string(u))
+ return -EINVAL;
+
+ if (sd_json_variant_by_key(m, sd_json_variant_string(u)))
+ continue; /* skip if exists in second variant */
+
+ array[k++] = u;
+ array[k++] = sd_json_variant_by_index(*v, i + 1);
+ }
+
+ for (size_t i = 0; i < m_elements; i++)
+ array[k++] = sd_json_variant_by_index(m, i);
+
+ r = sd_json_variant_new_object(&w, array, k);
+ if (r < 0)
+ return r;
+
+ json_variant_propagate_sensitive(*v, w);
+ json_variant_propagate_sensitive(m, w);
+ JSON_VARIANT_REPLACE(*v, TAKE_PTR(w));
+
+ return 1;
+}
+
+_public_ int sd_json_variant_merge_objectb(sd_json_variant **v, ...) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL;
+ va_list ap;
+ int r;
+
+ va_start(ap, v);
+ r = sd_json_buildv(&w, ap);
+ va_end(ap);
+ if (r < 0)
+ return r;
+
+ return sd_json_variant_merge_object(v, w);
+}
+
+_public_ int sd_json_variant_append_array(sd_json_variant **v, sd_json_variant *element) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *nv = NULL;
+ bool blank;
+ int r;
+
+ assert(v);
+ assert(element);
+
+ if (!*v || sd_json_variant_is_null(*v))
+ blank = true;
+ else if (sd_json_variant_is_array(*v))
+ blank = sd_json_variant_elements(*v) == 0;
+ else
+ return -EINVAL;
+
+ if (blank) {
+ r = sd_json_variant_new_array(&nv, (sd_json_variant*[]) { element }, 1);
+ if (r < 0)
+ return r;
+ } else if (json_variant_n_ref(*v) == 1) {
+ /* Let's bump the reference count on element. We can't do the realloc if we're appending *v
+ * to itself, or one of the objects embedded in *v to *v. If the reference count grows, we
+ * need to fall back to the other method below. */
+
+ _unused_ _cleanup_(sd_json_variant_unrefp) sd_json_variant *dummy = sd_json_variant_ref(element);
+ if (json_variant_n_ref(*v) == 1) {
+ /* We hold the only reference. Let's mutate the object. */
+ size_t size = sd_json_variant_elements(*v);
+ void *old = *v;
+
+ if (!GREEDY_REALLOC(*v, size + 1 + 1))
+ return -ENOMEM;
+
+ if (old != *v)
+ /* Readjust the parent pointers to the new address */
+ for (size_t i = 1; i < size; i++)
+ (*v)[1 + i].parent = *v;
+
+ return json_variant_array_put_element(*v, element);
+ }
+ }
+
+ if (!blank) {
+ size_t size = sd_json_variant_elements(*v);
+
+ _cleanup_free_ sd_json_variant **array = new(sd_json_variant*, size + 1);
+ if (!array)
+ return -ENOMEM;
+
+ for (size_t i = 0; i < size; i++)
+ array[i] = sd_json_variant_by_index(*v, i);
+
+ array[size] = element;
+
+ r = sd_json_variant_new_array(&nv, array, size + 1);
+ if (r < 0)
+ return r;
+ }
+
+ json_variant_propagate_sensitive(*v, nv);
+ JSON_VARIANT_REPLACE(*v, TAKE_PTR(nv));
+
+ return 0;
+}
+
+_public_ int sd_json_variant_append_arrayb(sd_json_variant **v, ...) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL;
+ va_list ap;
+ int r;
+
+ va_start(ap, v);
+ r = sd_json_buildv(&w, ap);
+ va_end(ap);
+ if (r < 0)
+ return r;
+
+ return sd_json_variant_append_array(v, w);
+}
+
+_public_ sd_json_variant *sd_json_variant_find(sd_json_variant *haystack, sd_json_variant *needle) {
+ sd_json_variant *i;
+
+ /* Find a json object in an array. Returns NULL if not found, or if the array is not actually an array. */
+
+ JSON_VARIANT_ARRAY_FOREACH(i, haystack)
+ if (sd_json_variant_equal(i, needle))
+ return i;
+
+ return NULL;
+}
+
+_public_ int sd_json_variant_append_array_nodup(sd_json_variant **v, sd_json_variant *element) {
+ assert(v);
+
+ if (sd_json_variant_find(*v, element))
+ return 0;
+
+ return sd_json_variant_append_array(v, element);
+}
+
+_public_ int sd_json_variant_strv(sd_json_variant *v, char ***ret) {
+ char **l = NULL;
+ bool sensitive;
+ int r;
+
+ assert(ret);
+
+ if (!v || sd_json_variant_is_null(v)) {
+ l = new0(char*, 1);
+ if (!l)
+ return -ENOMEM;
+
+ *ret = l;
+ return 0;
+ }
+
+ if (!sd_json_variant_is_array(v))
+ return -EINVAL;
+
+ sensitive = sd_json_variant_is_sensitive(v);
+
+ size_t n = sd_json_variant_elements(v);
+ l = new(char*, n+1);
+ if (!l)
+ return -ENOMEM;
+
+ for (size_t i = 0; i < n; i++) {
+ sd_json_variant *e;
+
+ assert_se(e = sd_json_variant_by_index(v, i));
+ sensitive = sensitive || sd_json_variant_is_sensitive(e);
+
+ if (!sd_json_variant_is_string(e)) {
+ l[i] = NULL;
+ r = -EINVAL;
+ goto fail;
+ }
+
+ l[i] = strdup(sd_json_variant_string(e));
+ if (!l[i]) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ l[n] = NULL;
+ *ret = TAKE_PTR(l);
+
+ return 0;
+
+fail:
+ if (sensitive)
+ strv_free_erase(l);
+ else
+ strv_free(l);
+
+ return r;
+}
+
+static int json_variant_copy(sd_json_variant **nv, sd_json_variant *v) {
+ sd_json_variant_type_t t;
+ sd_json_variant *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 = sd_json_variant_type(v);
+ switch (t) {
+ case SD_JSON_VARIANT_INTEGER:
+ k = sizeof(int64_t);
+ value.integer = sd_json_variant_integer(v);
+ source = &value;
+ break;
+
+ case SD_JSON_VARIANT_UNSIGNED:
+ k = sizeof(uint64_t);
+ value.unsig = sd_json_variant_unsigned(v);
+ source = &value;
+ break;
+
+ case SD_JSON_VARIANT_REAL:
+ k = sizeof(double);
+ value.real = sd_json_variant_real(v);
+ source = &value;
+ break;
+
+ case SD_JSON_VARIANT_BOOLEAN:
+ k = sizeof(bool);
+ value.boolean = sd_json_variant_boolean(v);
+ source = &value;
+ break;
+
+ case SD_JSON_VARIANT_NULL:
+ k = 0;
+ source = NULL;
+ break;
+
+ case SD_JSON_VARIANT_STRING:
+ source = sd_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(MAX(sizeof(sd_json_variant),
+ offsetof(sd_json_variant, reference) + sizeof(sd_json_variant*)));
+ if (!c)
+ return -ENOMEM;
+
+ c->n_ref = 1;
+ c->type = t;
+ c->is_reference = true;
+ c->reference = sd_json_variant_ref(json_variant_formalize(v));
+
+ *nv = c;
+ return 0;
+ }
+
+ c = malloc0(MAX(sizeof(sd_json_variant),
+ offsetof(sd_json_variant, value) + k));
+ if (!c)
+ return -ENOMEM;
+
+ c->n_ref = 1;
+ c->type = t;
+
+ memcpy_safe(&c->value, source, k);
+
+ json_variant_propagate_sensitive(v, c);
+
+ *nv = c;
+ return 0;
+}
+
+static bool json_single_ref(sd_json_variant *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(sd_json_variant **v, JsonSource *source, unsigned line, unsigned column) {
+ sd_json_variant *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_REPLACE(*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, n);
+ 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;
+ 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, 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, n + 2))
+ return -ENOMEM;
+
+ s[n++] = ch;
+ c++;
+ continue;
+ }
+
+ len = utf8_encoded_valid_unichar(c, SIZE_MAX);
+ if (len < 0)
+ return len;
+
+ if (!GREEDY_REALLOC(s, 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;
+ double x = 0.0, y = 0.0, exponent = 0.0, shift = 1.0;
+ int64_t i = 0;
+ uint64_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 < INT64_MIN / 10) /* overflow */
+ is_real = true;
+ else {
+ int64_t t = 10 * i;
+
+ if (t < INT64_MIN + (*c - '0')) /* overflow */
+ is_real = true;
+ else
+ i = t - (*c - '0');
+ }
+ } else {
+ if (u > UINT64_MAX / 10) /* overflow */
+ is_real = true;
+ else {
+ uint64_t t = 10 * u;
+
+ if (t > UINT64_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))) * exp10((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, /* 'reterr_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();
+ }
+
+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 sd_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 sd_json_build() */
+ EXPECT_ARRAY_ELEMENT,
+ EXPECT_OBJECT_KEY,
+} JsonExpect;
+
+typedef struct JsonStack {
+ JsonExpect expect;
+ sd_json_variant **elements;
+ size_t n_elements;
+ unsigned line_before;
+ unsigned column_before;
+ size_t n_suppress; /* When building: if > 0, suppress this many subsequent elements. If == SIZE_MAX, suppress all subsequent elements */
+} JsonStack;
+
+static void json_stack_release(JsonStack *s) {
+ assert(s);
+
+ CLEANUP_ARRAY(s->elements, s->n_elements, sd_json_variant_unref_many);
+}
+
+static int json_parse_internal(
+ const char **input,
+ JsonSource *source,
+ sd_json_parse_flags_t flags,
+ sd_json_variant **ret,
+ unsigned *line,
+ unsigned *column,
+ bool continue_end) {
+
+ size_t n_stack = 1;
+ 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))
+ return -ENOMEM;
+
+ stack[0] = (JsonStack) {
+ .expect = EXPECT_TOPLEVEL,
+ };
+
+ if (!line)
+ line = &line_buffer;
+ if (!column)
+ column = &column_buffer;
+
+ for (;;) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *add = NULL;
+ _cleanup_free_ char *string = NULL;
+ unsigned line_token, column_token;
+ 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+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 = sd_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+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 = sd_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 = sd_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 = sd_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 = sd_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 = sd_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 = sd_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 = sd_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();
+ }
+
+ if (add) {
+ /* If we are asked to make this parsed object sensitive, then let's apply this
+ * immediately after allocating each variant, so that when we abort half-way
+ * everything we already allocated that is then freed is correctly marked. */
+ if (FLAGS_SET(flags, SD_JSON_PARSE_SENSITIVE))
+ sd_json_variant_sensitive(add);
+
+ (void) json_variant_set_source(&add, source, line_token, column_token);
+
+ if (!GREEDY_REALLOC(current->elements, 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 = sd_json_variant_ref(stack[0].elements[0]);
+ *input = p;
+ r = 0;
+
+finish:
+ for (size_t i = 0; i < n_stack; i++)
+ json_stack_release(stack + i);
+
+ free(stack);
+
+ return r;
+}
+
+_public_ int sd_json_parse_with_source(
+ const char *input,
+ const char *source,
+ sd_json_parse_flags_t flags,
+ sd_json_variant **ret,
+ unsigned *reterr_line,
+ unsigned *reterr_column) {
+
+ _cleanup_(json_source_unrefp) JsonSource *s = NULL;
+
+ if (source) {
+ s = json_source_new(source);
+ if (!s)
+ return -ENOMEM;
+ }
+
+ return json_parse_internal(&input, s, flags, ret, reterr_line, reterr_column, false);
+}
+
+_public_ int sd_json_parse_with_source_continue(
+ const char **p,
+ const char *source,
+ sd_json_parse_flags_t flags,
+ sd_json_variant **ret,
+ unsigned *reterr_line,
+ unsigned *reterr_column) {
+
+ _cleanup_(json_source_unrefp) JsonSource *s = NULL;
+
+ if (source) {
+ s = json_source_new(source);
+ if (!s)
+ return -ENOMEM;
+ }
+
+ return json_parse_internal(p, s, flags, ret, reterr_line, reterr_column, true);
+}
+
+_public_ int sd_json_parse(
+ const char *string,
+ sd_json_parse_flags_t flags,
+ sd_json_variant **ret,
+ unsigned *reterr_line,
+ unsigned *reterr_column) {
+
+ return sd_json_parse_with_source(string, NULL, flags, ret, reterr_line, reterr_column);
+}
+
+_public_ int sd_json_parse_continue(
+ const char **p,
+ sd_json_parse_flags_t flags,
+ sd_json_variant **ret,
+ unsigned *reterr_line,
+ unsigned *reterr_column) {
+
+ return sd_json_parse_with_source_continue(p, NULL, flags, ret, reterr_line, reterr_column);
+}
+
+_public_ int sd_json_parse_file_at(
+ FILE *f,
+ int dir_fd,
+ const char *path,
+ sd_json_parse_flags_t flags,
+ sd_json_variant **ret,
+ unsigned *reterr_line,
+ unsigned *reterr_column) {
+
+ _cleanup_free_ char *text = NULL;
+ int r;
+
+ if (f)
+ r = read_full_stream(f, &text, NULL);
+ else if (path)
+ r = read_full_file_full(dir_fd, path, UINT64_MAX, SIZE_MAX, 0, NULL, &text, NULL);
+ else
+ return -EINVAL;
+ if (r < 0)
+ return r;
+
+ if (isempty(text))
+ return -ENODATA;
+
+ return sd_json_parse_with_source(text, path, flags, ret, reterr_line, reterr_column);
+}
+
+_public_ int sd_json_parse_file(
+ FILE *f,
+ const char *path,
+ sd_json_parse_flags_t flags,
+ sd_json_variant **ret,
+ unsigned *reterr_line,
+ unsigned *reterr_column) {
+
+ return sd_json_parse_file_at(f, AT_FDCWD, path, flags, ret, reterr_line, reterr_column);
+}
+
+_public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
+ JsonStack *stack = NULL;
+ size_t n_stack = 1;
+ const char *name = NULL;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ if (!GREEDY_REALLOC(stack, n_stack))
+ return -ENOMEM;
+
+ stack[0] = (JsonStack) {
+ .expect = EXPECT_TOPLEVEL,
+ };
+
+ for (;;) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *add = NULL, *add_more = NULL;
+ size_t n_subtract = 0; /* how much to subtract from current->n_suppress, i.e. how many elements would
+ * have been added to the current variant */
+ 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 _SD_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 *);
+
+ if (current->n_suppress == 0) {
+ r = sd_json_variant_new_string(&add, p);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _SD_JSON_BUILD_INTEGER: {
+ int64_t j;
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ j = va_arg(ap, int64_t);
+
+ if (current->n_suppress == 0) {
+ r = sd_json_variant_new_integer(&add, j);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _SD_JSON_BUILD_UNSIGNED: {
+ uint64_t j;
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ j = va_arg(ap, uint64_t);
+
+ if (current->n_suppress == 0) {
+ r = sd_json_variant_new_unsigned(&add, j);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _SD_JSON_BUILD_REAL: {
+ double d;
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ d = va_arg(ap, double);
+
+ if (current->n_suppress == 0) {
+ r = sd_json_variant_new_real(&add, d);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _SD_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);
+
+ if (current->n_suppress == 0) {
+ r = sd_json_variant_new_boolean(&add, b);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _SD_JSON_BUILD_NULL:
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ if (current->n_suppress == 0) {
+ r = sd_json_variant_new_null(&add);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+
+ case _SD_JSON_BUILD_VARIANT:
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ /* Note that we don't care for current->n_suppress here, after all the variant is already
+ * allocated anyway... */
+ add = va_arg(ap, sd_json_variant*);
+ if (!add)
+ add = JSON_VARIANT_MAGIC_NULL;
+ else
+ sd_json_variant_ref(add);
+
+ n_subtract = 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);
+
+ break;
+
+ case _SD_JSON_BUILD_VARIANT_ARRAY: {
+ sd_json_variant **array;
+ size_t n;
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ array = va_arg(ap, sd_json_variant**);
+ n = va_arg(ap, size_t);
+
+ if (current->n_suppress == 0) {
+ r = sd_json_variant_new_array(&add, array, n);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _SD_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) {
+ /* Note that we don't care for current->n_suppress here, we should generate parsing
+ * errors even in suppressed object properties */
+
+ r = sd_json_parse(l, 0, &add, NULL, NULL);
+ if (r < 0)
+ goto finish;
+ } else
+ add = JSON_VARIANT_MAGIC_NULL;
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _SD_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+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,
+ .n_suppress = current->n_suppress != 0 ? SIZE_MAX : 0, /* if we shall suppress the
+ * new array, then we should
+ * also suppress all array
+ * members */
+ };
+
+ break;
+
+ case _SD_JSON_BUILD_ARRAY_END:
+ if (current->expect != EXPECT_ARRAY_ELEMENT) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ assert(n_stack > 1);
+
+ if (current->n_suppress == 0) {
+ r = sd_json_variant_new_array(&add, current->elements, current->n_elements);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 1;
+
+ json_stack_release(current);
+ n_stack--, current--;
+
+ break;
+
+ case _SD_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 **);
+
+ if (current->n_suppress == 0) {
+ r = sd_json_variant_new_array_strv(&add, l);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _JSON_BUILD_STRV_ENV_PAIR: {
+ char **l;
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ l = va_arg(ap, char **);
+
+ _cleanup_strv_free_ char **el = NULL;
+ STRV_FOREACH_PAIR(x, y, l) {
+ char *n = NULL;
+
+ n = strjoin(*x, "=", *y);
+ if (!n) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ r = strv_consume(&el, n);
+ if (r < 0)
+ goto finish;
+ }
+
+ if (current->n_suppress == 0) {
+ r = sd_json_variant_new_array_strv(&add, el);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _SD_JSON_BUILD_BASE64:
+ case _SD_JSON_BUILD_BASE32HEX:
+ case _SD_JSON_BUILD_HEX:
+ case _SD_JSON_BUILD_OCTESCAPE: {
+ const void *p;
+ size_t n;
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ p = va_arg(ap, const void *);
+ n = va_arg(ap, size_t);
+
+ if (current->n_suppress == 0) {
+ r = command == _SD_JSON_BUILD_BASE64 ? sd_json_variant_new_base64(&add, p, n) :
+ command == _SD_JSON_BUILD_BASE32HEX ? sd_json_variant_new_base32hex(&add, p, n) :
+ command == _SD_JSON_BUILD_HEX ? sd_json_variant_new_hex(&add, p, n) :
+ sd_json_variant_new_octescape(&add, p, n);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _JSON_BUILD_IOVEC_BASE64:
+ case _JSON_BUILD_IOVEC_HEX: {
+ const struct iovec *iov;
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ iov = va_arg(ap, const struct iovec*);
+
+ if (current->n_suppress == 0) {
+ if (iov)
+ r = command == _JSON_BUILD_IOVEC_BASE64 ? sd_json_variant_new_base64(&add, iov->iov_base, iov->iov_len) :
+ sd_json_variant_new_hex(&add, iov->iov_base, iov->iov_len);
+ else
+ r = sd_json_variant_new_string(&add, "");
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _SD_JSON_BUILD_ID128:
+ case _SD_JSON_BUILD_UUID: {
+ const sd_id128_t *id;
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ assert_se(id = va_arg(ap, sd_id128_t*));
+
+ if (current->n_suppress == 0) {
+ r = command == _SD_JSON_BUILD_ID128 ?
+ sd_json_variant_new_id128(&add, *id) :
+ sd_json_variant_new_uuid(&add, *id);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _SD_JSON_BUILD_BYTE_ARRAY: {
+ const void *array;
+ size_t n;
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ array = va_arg(ap, const void*);
+ n = va_arg(ap, size_t);
+
+ if (current->n_suppress == 0) {
+ r = sd_json_variant_new_array_bytes(&add, array, n);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _JSON_BUILD_HW_ADDR: {
+ const struct hw_addr_data *hw_addr;
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ assert_se(hw_addr = va_arg(ap, struct hw_addr_data*));
+
+ if (current->n_suppress == 0) {
+ r = sd_json_variant_new_array_bytes(&add, hw_addr->bytes, hw_addr->length);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _JSON_BUILD_STRING_SET: {
+ Set *set;
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ set = va_arg(ap, Set*);
+
+ if (current->n_suppress == 0) {
+ _cleanup_free_ char **sorted = NULL;
+
+ r = set_dump_sorted(set, (void ***) &sorted, NULL);
+ if (r < 0)
+ goto finish;
+
+ r = sd_json_variant_new_array_strv(&add, sorted);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _SD_JSON_BUILD_CALLBACK: {
+ sd_json_build_callback_t cb;
+ void *userdata;
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ cb = va_arg(ap, sd_json_build_callback_t);
+ userdata = va_arg(ap, void *);
+
+ if (current->n_suppress == 0) {
+ if (cb) {
+ r = cb(&add, name, userdata);
+ if (r < 0)
+ goto finish;
+ }
+
+ if (!add)
+ add = JSON_VARIANT_MAGIC_NULL;
+
+ name = NULL;
+ }
+
+ n_subtract = 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);
+
+ break;
+ }
+
+ case _SD_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+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,
+ .n_suppress = current->n_suppress != 0 ? SIZE_MAX : 0, /* If we shall suppress the
+ * new object, then we should
+ * also suppress all object
+ * members. */
+ };
+
+ break;
+
+ case _SD_JSON_BUILD_OBJECT_END:
+
+ if (current->expect != EXPECT_OBJECT_KEY) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ assert(n_stack > 1);
+
+ if (current->n_suppress == 0) {
+ r = sd_json_variant_new_object(&add, current->elements, current->n_elements);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 1;
+
+ json_stack_release(current);
+ n_stack--, current--;
+
+ break;
+
+ case _SD_JSON_BUILD_PAIR: {
+
+ if (current->expect != EXPECT_OBJECT_KEY) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ name = va_arg(ap, const char *);
+
+ if (current->n_suppress == 0) {
+ r = sd_json_variant_new_string(&add, name);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 1;
+
+ current->expect = EXPECT_OBJECT_VALUE;
+ break;
+ }
+
+ case _SD_JSON_BUILD_PAIR_CONDITION: {
+ bool b;
+
+ if (current->expect != EXPECT_OBJECT_KEY) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ b = va_arg(ap, int);
+ name = va_arg(ap, const char *);
+
+ if (b && current->n_suppress == 0) {
+ r = sd_json_variant_new_string(&add, name);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 1; /* we generated one item */
+
+ if (!b && current->n_suppress != SIZE_MAX)
+ current->n_suppress += 2; /* Suppress this one and the next item */
+
+ current->expect = EXPECT_OBJECT_VALUE;
+ break;
+ }
+
+ case _JSON_BUILD_PAIR_UNSIGNED_NON_ZERO: {
+ const char *n;
+ uint64_t u;
+
+ if (current->expect != EXPECT_OBJECT_KEY) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ n = va_arg(ap, const char *);
+ u = va_arg(ap, uint64_t);
+
+ if (u != 0 && current->n_suppress == 0) {
+ r = sd_json_variant_new_string(&add, n);
+ if (r < 0)
+ goto finish;
+
+ r = sd_json_variant_new_unsigned(&add_more, u);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 2; /* we generated two item */
+
+ current->expect = EXPECT_OBJECT_KEY;
+ break;
+ }
+
+ case _JSON_BUILD_PAIR_FINITE_USEC: {
+ const char *n;
+ usec_t u;
+
+ if (current->expect != EXPECT_OBJECT_KEY) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ n = va_arg(ap, const char *);
+ u = va_arg(ap, usec_t);
+
+ if (u != USEC_INFINITY && current->n_suppress == 0) {
+ r = sd_json_variant_new_string(&add, n);
+ if (r < 0)
+ goto finish;
+
+ r = sd_json_variant_new_unsigned(&add_more, u);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 2; /* we generated two item */
+
+ current->expect = EXPECT_OBJECT_KEY;
+ break;
+ }
+
+ case _JSON_BUILD_PAIR_STRING_NON_EMPTY: {
+ const char *n, *s;
+
+ if (current->expect != EXPECT_OBJECT_KEY) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ n = va_arg(ap, const char *);
+ s = va_arg(ap, const char *);
+
+ if (!isempty(s) && current->n_suppress == 0) {
+ r = sd_json_variant_new_string(&add, n);
+ if (r < 0)
+ goto finish;
+
+ r = sd_json_variant_new_string(&add_more, s);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 2; /* we generated two item */
+
+ current->expect = EXPECT_OBJECT_KEY;
+ break;
+ }
+
+ case _JSON_BUILD_PAIR_STRV_NON_EMPTY: {
+ const char *n;
+ char **l;
+
+ if (current->expect != EXPECT_OBJECT_KEY) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ n = va_arg(ap, const char *);
+ l = va_arg(ap, char **);
+
+ if (!strv_isempty(l) && current->n_suppress == 0) {
+ r = sd_json_variant_new_string(&add, n);
+ if (r < 0)
+ goto finish;
+
+ r = sd_json_variant_new_array_strv(&add_more, l);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 2; /* we generated two item */
+
+ current->expect = EXPECT_OBJECT_KEY;
+ break;
+ }
+
+ case _JSON_BUILD_PAIR_VARIANT_NON_NULL: {
+ sd_json_variant *v;
+ const char *n;
+
+ if (current->expect != EXPECT_OBJECT_KEY) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ n = va_arg(ap, const char *);
+ v = va_arg(ap, sd_json_variant *);
+
+ if (v && !sd_json_variant_is_null(v) && current->n_suppress == 0) {
+ r = sd_json_variant_new_string(&add, n);
+ if (r < 0)
+ goto finish;
+
+ add_more = sd_json_variant_ref(v);
+ }
+
+ n_subtract = 2; /* we generated two item */
+
+ current->expect = EXPECT_OBJECT_KEY;
+ break;
+ }
+
+ case _JSON_BUILD_PAIR_IN4_ADDR_NON_NULL: {
+ const struct in_addr *a;
+ const char *n;
+
+ if (current->expect != EXPECT_OBJECT_KEY) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ n = va_arg(ap, const char *);
+ a = va_arg(ap, const struct in_addr *);
+
+ if (a && in4_addr_is_set(a) && current->n_suppress == 0) {
+ r = sd_json_variant_new_string(&add, n);
+ if (r < 0)
+ goto finish;
+
+ r = sd_json_variant_new_array_bytes(&add_more, a, sizeof(struct in_addr));
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 2; /* we generated two item */
+
+ current->expect = EXPECT_OBJECT_KEY;
+ break;
+ }
+
+ case _JSON_BUILD_PAIR_IN6_ADDR_NON_NULL: {
+ const struct in6_addr *a;
+ const char *n;
+
+ if (current->expect != EXPECT_OBJECT_KEY) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ n = va_arg(ap, const char *);
+ a = va_arg(ap, const struct in6_addr *);
+
+ if (a && in6_addr_is_set(a) && current->n_suppress == 0) {
+ r = sd_json_variant_new_string(&add, n);
+ if (r < 0)
+ goto finish;
+
+ r = sd_json_variant_new_array_bytes(&add_more, a, sizeof(struct in6_addr));
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 2; /* we generated two item */
+
+ current->expect = EXPECT_OBJECT_KEY;
+ break;
+ }
+
+ case _JSON_BUILD_PAIR_IN_ADDR_NON_NULL: {
+ const union in_addr_union *a;
+ const char *n;
+ int f;
+
+ if (current->expect != EXPECT_OBJECT_KEY) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ n = va_arg(ap, const char *);
+ a = va_arg(ap, const union in_addr_union *);
+ f = va_arg(ap, int);
+
+ if (a && in_addr_is_set(f, a) && current->n_suppress == 0) {
+ r = sd_json_variant_new_string(&add, n);
+ if (r < 0)
+ goto finish;
+
+ r = sd_json_variant_new_array_bytes(&add_more, a->bytes, FAMILY_ADDRESS_SIZE(f));
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 2; /* we generated two item */
+
+ current->expect = EXPECT_OBJECT_KEY;
+ break;
+ }
+
+ case _JSON_BUILD_PAIR_ETHER_ADDR_NON_NULL: {
+ const struct ether_addr *a;
+ const char *n;
+
+ if (current->expect != EXPECT_OBJECT_KEY) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ n = va_arg(ap, const char *);
+ a = va_arg(ap, const struct ether_addr *);
+
+ if (a && !ether_addr_is_null(a) && current->n_suppress == 0) {
+ r = sd_json_variant_new_string(&add, n);
+ if (r < 0)
+ goto finish;
+
+ r = sd_json_variant_new_array_bytes(&add_more, a->ether_addr_octet, sizeof(struct ether_addr));
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 2; /* we generated two item */
+
+ current->expect = EXPECT_OBJECT_KEY;
+ break;
+ }
+
+ case _JSON_BUILD_PAIR_HW_ADDR_NON_NULL: {
+ const struct hw_addr_data *a;
+ const char *n;
+
+ if (current->expect != EXPECT_OBJECT_KEY) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ n = va_arg(ap, const char *);
+ a = va_arg(ap, const struct hw_addr_data *);
+
+ if (a && !hw_addr_is_null(a) && current->n_suppress == 0) {
+ r = sd_json_variant_new_string(&add, n);
+ if (r < 0)
+ goto finish;
+
+ r = sd_json_variant_new_array_bytes(&add_more, a->bytes, a->length);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 2; /* we generated two item */
+
+ current->expect = EXPECT_OBJECT_KEY;
+ break;
+ }
+ }
+
+ /* If variants were generated, add them to our current variant, but only if we are not supposed to suppress additions */
+ if (add && current->n_suppress == 0) {
+ if (!GREEDY_REALLOC(current->elements, current->n_elements + 1 + !!add_more)) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ current->elements[current->n_elements++] = TAKE_PTR(add);
+ if (add_more)
+ current->elements[current->n_elements++] = TAKE_PTR(add_more);
+ }
+
+ /* If we are supposed to suppress items, let's subtract how many items where generated from
+ * that counter. Except if the counter is SIZE_MAX, i.e. we shall suppress an infinite number
+ * of elements on this stack level */
+ if (current->n_suppress != SIZE_MAX) {
+ if (current->n_suppress <= n_subtract) /* Saturated */
+ current->n_suppress = 0;
+ else
+ current->n_suppress -= n_subtract;
+ }
+ }
+
+done:
+ assert(n_stack == 1);
+ assert(stack[0].n_elements == 1);
+
+ *ret = sd_json_variant_ref(stack[0].elements[0]);
+ r = 0;
+
+finish:
+ for (size_t i = 0; i < n_stack; i++)
+ json_stack_release(stack + i);
+
+ free(stack);
+
+ return r;
+}
+
+_public_ int sd_json_build(sd_json_variant **ret, ...) {
+ va_list ap;
+ int r;
+
+ va_start(ap, ret);
+ r = sd_json_buildv(ret, ap);
+ va_end(ap);
+
+ return r;
+}
+
+int json_log_internal(
+ sd_json_variant *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;
+
+ errno = ERRNO_VALUE(error);
+
+ va_start(ap, format);
+ (void) vsnprintf(buffer, sizeof buffer, format, ap);
+ va_end(ap);
+
+ if (variant) {
+ r = sd_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(
+ 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:%u: %s", source, source_line, source_column, buffer),
+ NULL);
+ else if (source_line > 0 && source_column > 0)
+ return log_struct_internal(
+ level,
+ error,
+ file, line, func,
+ "MESSAGE_ID=" SD_MESSAGE_INVALID_CONFIGURATION_STR,
+ "CONFIG_LINE=%u", source_line,
+ "CONFIG_COLUMN=%u", source_column,
+ LOG_MESSAGE("(string):%u:%u: %s", source_line, source_column, buffer),
+ NULL);
+ else
+ return log_struct_internal(
+ level,
+ error,
+ file, line, func,
+ "MESSAGE_ID=" SD_MESSAGE_INVALID_CONFIGURATION_STR,
+ LOG_MESSAGE("%s", buffer),
+ NULL);
+}
+
+static void *dispatch_userdata(const sd_json_dispatch_field *p, void *userdata) {
+
+ /* When the userdata pointer is passed in as NULL, then we'll just use the offset as a literal
+ * address, and convert it to a pointer. Note that might as well just add the offset to the NULL
+ * pointer, but UndefinedBehaviourSanitizer doesn't like pointer arithmetics based on NULL pointers,
+ * hence we code this explicitly here. */
+
+ if (userdata)
+ return (uint8_t*) userdata + p->offset;
+
+ return SIZE_TO_PTR(p->offset);
+}
+
+_public_ int sd_json_dispatch_full(
+ sd_json_variant *v,
+ const sd_json_dispatch_field table[],
+ sd_json_dispatch_callback_t bad,
+ sd_json_dispatch_flags_t flags,
+ void *userdata,
+ const char **reterr_bad_field) {
+ size_t m;
+ int r, done = 0;
+ bool *found;
+
+ if (!sd_json_variant_is_object(v)) {
+ json_log(v, flags, 0, "JSON variant is not an object.");
+
+ if (flags & SD_JSON_PERMISSIVE)
+ return 0;
+
+ if (reterr_bad_field)
+ *reterr_bad_field = NULL;
+
+ return -EINVAL;
+ }
+
+ m = 0;
+ for (const sd_json_dispatch_field *p = table; p->name; p++)
+ m++;
+
+ found = newa0(bool, m);
+
+ size_t n = sd_json_variant_elements(v);
+ for (size_t i = 0; i < n; i += 2) {
+ sd_json_variant *key, *value;
+ const sd_json_dispatch_field *p;
+
+ assert_se(key = sd_json_variant_by_index(v, i));
+ assert_se(value = sd_json_variant_by_index(v, i+1));
+
+ for (p = table; p->name; p++)
+ if (p->name == POINTER_MAX ||
+ streq_ptr(sd_json_variant_string(key), p->name))
+ break;
+
+ if (p->name) { /* Found a matching entry! 🙂 */
+ sd_json_dispatch_flags_t merged_flags;
+
+ merged_flags = flags | p->flags;
+
+ if (p->type != _SD_JSON_VARIANT_TYPE_INVALID &&
+ !sd_json_variant_has_type(value, p->type)) {
+
+ json_log(value, merged_flags, 0,
+ "Object field '%s' has wrong type %s, expected %s.", sd_json_variant_string(key),
+ sd_json_variant_type_to_string(sd_json_variant_type(value)), sd_json_variant_type_to_string(p->type));
+
+ if (merged_flags & SD_JSON_PERMISSIVE)
+ continue;
+
+ if (reterr_bad_field)
+ *reterr_bad_field = p->name;
+
+ return -EINVAL;
+ }
+
+ if (found[p-table]) {
+ json_log(value, merged_flags, 0, "Duplicate object field '%s'.", sd_json_variant_string(key));
+
+ if (merged_flags & SD_JSON_PERMISSIVE)
+ continue;
+
+ if (reterr_bad_field)
+ *reterr_bad_field = p->name;
+
+ return -ENOTUNIQ;
+ }
+
+ found[p-table] = true;
+
+ if (p->callback) {
+ r = p->callback(sd_json_variant_string(key), value, merged_flags, dispatch_userdata(p, userdata));
+ if (r < 0) {
+ if (merged_flags & SD_JSON_PERMISSIVE)
+ continue;
+
+ if (reterr_bad_field)
+ *reterr_bad_field = sd_json_variant_string(key);
+
+ return r;
+ }
+ }
+
+ done++;
+
+ } else { /* Didn't find a matching entry! ☚ī¸ */
+
+ if (bad) {
+ r = bad(sd_json_variant_string(key), value, flags, userdata);
+ if (r < 0) {
+ if (flags & SD_JSON_PERMISSIVE)
+ continue;
+
+ if (reterr_bad_field)
+ *reterr_bad_field = sd_json_variant_string(key);
+
+ return r;
+ } else
+ done++;
+
+ } else {
+ if (flags & SD_JSON_ALLOW_EXTENSIONS) {
+ json_log(value, flags|SD_JSON_DEBUG, 0, "Unrecognized object field '%s', assuming extension.", sd_json_variant_string(key));
+ continue;
+ }
+
+ json_log(value, flags, 0, "Unexpected object field '%s'.", sd_json_variant_string(key));
+ if (flags & SD_JSON_PERMISSIVE)
+ continue;
+
+ if (reterr_bad_field)
+ *reterr_bad_field = sd_json_variant_string(key);
+
+ return -EADDRNOTAVAIL;
+ }
+ }
+ }
+
+ for (const sd_json_dispatch_field *p = table; p->name; p++) {
+ sd_json_dispatch_flags_t merged_flags = p->flags | flags;
+
+ if ((merged_flags & SD_JSON_MANDATORY) && !found[p-table]) {
+ json_log(v, merged_flags, 0, "Missing object field '%s'.", p->name);
+
+ if ((merged_flags & SD_JSON_PERMISSIVE))
+ continue;
+
+ if (reterr_bad_field)
+ *reterr_bad_field = p->name;
+
+ return -ENXIO;
+ }
+ }
+
+ return done;
+}
+
+_public_ int sd_json_dispatch(
+ sd_json_variant *v,
+ const sd_json_dispatch_field table[],
+ sd_json_dispatch_flags_t flags,
+ void *userdata) {
+
+ return sd_json_dispatch_full(v, table, NULL, flags, userdata, NULL);
+}
+
+_public_ int sd_json_dispatch_stdbool(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ bool *b = ASSERT_PTR(userdata);
+
+ assert(variant);
+
+ if (!sd_json_variant_is_boolean(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a boolean.", strna(name));
+
+ *b = sd_json_variant_boolean(variant);
+ return 0;
+}
+
+_public_ int sd_json_dispatch_intbool(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ int *b = ASSERT_PTR(userdata);
+
+ assert(variant);
+
+ if (!sd_json_variant_is_boolean(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a boolean.", strna(name));
+
+ *b = sd_json_variant_boolean(variant);
+ return 0;
+}
+
+_public_ int sd_json_dispatch_tristate(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ int *b = ASSERT_PTR(userdata);
+
+ assert(variant);
+
+ if (sd_json_variant_is_null(variant)) {
+ *b = -1;
+ return 0;
+ }
+
+ if (!sd_json_variant_is_boolean(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a boolean.", strna(name));
+
+ *b = sd_json_variant_boolean(variant);
+ return 0;
+}
+
+_public_ int sd_json_dispatch_int64(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ int64_t *i = ASSERT_PTR(userdata);
+
+ assert(variant);
+
+ /* Also accept numbers formatted as string, to increase compatibility with less capable JSON
+ * implementations that cannot do 64bit integers. */
+ if (sd_json_variant_is_string(variant) && safe_atoi64(sd_json_variant_string(variant), i) >= 0)
+ return 0;
+
+ if (!sd_json_variant_is_integer(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer, nor one formatted as decimal string.", strna(name));
+
+ *i = sd_json_variant_integer(variant);
+ return 0;
+}
+
+_public_ int sd_json_dispatch_uint64(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ uint64_t *u = ASSERT_PTR(userdata);
+
+ assert(variant);
+
+ /* Since 64bit values (in particular unsigned ones) in JSON are problematic, let's also accept them
+ * formatted as strings. If this is not desired make sure to set the .type field in
+ * sd_json_dispatch_field to SD_JSON_UNSIGNED rather than _SD_JSON_VARIANT_TYPE_INVALID, so that
+ * json_dispatch() already filters out the non-matching type. */
+
+ if (sd_json_variant_is_string(variant) && safe_atou64(sd_json_variant_string(variant), u) >= 0)
+ return 0;
+
+ if (!sd_json_variant_is_unsigned(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an unsigned integer, nor one formatted as decimal string.", strna(name));
+
+ *u = sd_json_variant_unsigned(variant);
+ return 0;
+}
+
+_public_ int sd_json_dispatch_uint32(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ uint32_t *u = ASSERT_PTR(userdata);
+ uint64_t u64;
+ int r;
+
+ assert(variant);
+
+ r = sd_json_dispatch_uint64(name, variant, flags, &u64);
+ if (r < 0)
+ return r;
+
+ if (u64 > UINT32_MAX)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name));
+
+ *u = (uint32_t) u64;
+ return 0;
+}
+
+/* We define sd_json_dispatch_uint() as alias for json_dispatch_uint32(), let's make sure this is safe */
+assert_cc(sizeof(uint32_t) == sizeof(unsigned));
+
+_public_ int sd_json_dispatch_int32(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ int32_t *i = ASSERT_PTR(userdata);
+ int64_t i64;
+ int r;
+
+ assert(variant);
+
+ r = sd_json_dispatch_int64(name, variant, flags, &i64);
+ if (r < 0)
+ return r;
+
+ if (i64 < INT32_MIN || i64 > INT32_MAX)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name));
+
+ *i = (int32_t) i64;
+ return 0;
+}
+
+/* We define sd_json_dispatch_int() as alias for json_dispatch_int32(), let's make sure this is safe */
+assert_cc(sizeof(int32_t) == sizeof(int));
+
+_public_ int sd_json_dispatch_int16(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ int16_t *i = ASSERT_PTR(userdata);
+ int64_t i64;
+ int r;
+
+ assert(variant);
+
+ r = sd_json_dispatch_int64(name, variant, flags, &i64);
+ if (r < 0)
+ return r;
+
+ if (i64 < INT16_MIN || i64 > INT16_MAX)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name));
+
+ *i = (int16_t) i64;
+ return 0;
+}
+
+_public_ int sd_json_dispatch_uint16(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ uint16_t *u = ASSERT_PTR(userdata);
+ uint64_t u64;
+ int r;
+
+ assert(variant);
+
+ r = sd_json_dispatch_uint64(name, variant, flags, &u64);
+ if (r < 0)
+ return r;
+
+ if (u64 > UINT16_MAX)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name));
+
+ *u = (uint16_t) u64;
+ return 0;
+}
+
+_public_ int sd_json_dispatch_int8(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ int8_t *i = ASSERT_PTR(userdata);
+ int64_t i64;
+ int r;
+
+ assert(variant);
+
+ r = sd_json_dispatch_int64(name, variant, flags, &i64);
+ if (r < 0)
+ return r;
+
+ if (i64 < INT8_MIN || i64 > INT8_MAX)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name));
+
+ *i = (int8_t) i64;
+ return 0;
+}
+
+_public_ int sd_json_dispatch_uint8(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ uint8_t *u = ASSERT_PTR(userdata);
+ uint64_t u64;
+ int r;
+
+ assert(variant);
+
+ r = sd_json_dispatch_uint64(name, variant, flags, &u64);
+ if (r < 0)
+ return r;
+
+ if (u64 > UINT8_MAX)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name));
+
+ *u = (uint8_t) u64;
+ return 0;
+}
+
+_public_ int sd_json_dispatch_string(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ char **s = ASSERT_PTR(userdata);
+ int r;
+
+ assert(variant);
+
+ if (sd_json_variant_is_null(variant)) {
+ *s = mfree(*s);
+ return 0;
+ }
+
+ if (!sd_json_variant_is_string(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
+
+ if ((flags & SD_JSON_SAFE) && !string_is_safe(sd_json_variant_string(variant)))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name));
+
+ r = free_and_strdup(s, sd_json_variant_string(variant));
+ if (r < 0)
+ return json_log(variant, flags, r, "Failed to allocate string: %m");
+
+ return 0;
+}
+
+_public_ int sd_json_dispatch_const_string(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ const char **s = ASSERT_PTR(userdata);
+
+ assert(variant);
+
+ if (sd_json_variant_is_null(variant)) {
+ *s = NULL;
+ return 0;
+ }
+
+ if (!sd_json_variant_is_string(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
+
+ if ((flags & SD_JSON_SAFE) && !string_is_safe(sd_json_variant_string(variant)))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name));
+
+ *s = sd_json_variant_string(variant);
+ return 0;
+}
+
+_public_ int sd_json_dispatch_strv(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ _cleanup_strv_free_ char **l = NULL;
+ char ***s = ASSERT_PTR(userdata);
+ sd_json_variant *e;
+ int r;
+
+ assert(variant);
+
+ if (sd_json_variant_is_null(variant)) {
+ *s = strv_free(*s);
+ return 0;
+ }
+
+ /* Let's be flexible here: accept a single string in place of a single-item array */
+ if (sd_json_variant_is_string(variant)) {
+ if ((flags & SD_JSON_SAFE) && !string_is_safe(sd_json_variant_string(variant)))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name));
+
+ l = strv_new(sd_json_variant_string(variant));
+ if (!l)
+ return log_oom();
+
+ strv_free_and_replace(*s, l);
+ return 0;
+ }
+
+ if (!sd_json_variant_is_array(variant))
+ return json_log(variant, SYNTHETIC_ERRNO(EINVAL), flags, "JSON field '%s' is not an array.", strna(name));
+
+ JSON_VARIANT_ARRAY_FOREACH(e, variant) {
+ if (!sd_json_variant_is_string(e))
+ return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
+
+ if ((flags & SD_JSON_SAFE) && !string_is_safe(sd_json_variant_string(e)))
+ return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name));
+
+ r = strv_extend(&l, sd_json_variant_string(e));
+ if (r < 0)
+ return json_log(e, flags, r, "Failed to append array element: %m");
+ }
+
+ strv_free_and_replace(*s, l);
+ return 0;
+}
+
+_public_ int sd_json_dispatch_variant(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ sd_json_variant **p = ASSERT_PTR(userdata);
+ assert(variant);
+
+ /* Takes a reference */
+ JSON_VARIANT_REPLACE(*p, sd_json_variant_ref(variant));
+ return 0;
+}
+
+_public_ int sd_json_dispatch_variant_noref(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ sd_json_variant **p = ASSERT_PTR(userdata);
+ assert(variant);
+
+ /* Doesn't take a reference */
+ *p = variant;
+ return 0;
+}
+
+_public_ int sd_json_dispatch_uid_gid(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ uid_t *uid = userdata;
+ uint64_t k;
+
+ assert_cc(sizeof(uid_t) == sizeof(uint32_t));
+ assert_cc(sizeof(gid_t) == sizeof(uint32_t));
+
+ DISABLE_WARNING_TYPE_LIMITS;
+ assert_cc((UID_INVALID < (uid_t) 0) == (GID_INVALID < (gid_t) 0));
+ REENABLE_WARNING;
+
+ if (sd_json_variant_is_null(variant)) {
+ *uid = UID_INVALID;
+ return 0;
+ }
+
+ if (!sd_json_variant_is_unsigned(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name));
+
+ k = sd_json_variant_unsigned(variant);
+ if (k > UINT32_MAX || !uid_is_valid(k))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid UID/GID.", strna(name));
+
+ *uid = k;
+ return 0;
+}
+
+_public_ int sd_json_dispatch_id128(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ sd_id128_t *uuid = userdata;
+ int r;
+
+ if (sd_json_variant_is_null(variant)) {
+ *uuid = SD_ID128_NULL;
+ return 0;
+ }
+
+ if (!sd_json_variant_is_string(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
+
+ r = sd_id128_from_string(sd_json_variant_string(variant), uuid);
+ if (r < 0)
+ return json_log(variant, flags, r, "JSON field '%s' is not a valid UID.", strna(name));
+
+ return 0;
+}
+
+_public_ int sd_json_dispatch_unsupported(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not allowed in this object.", strna(name));
+}
+
+static int json_cmp_strings(const void *x, const void *y) {
+ sd_json_variant *const *a = x, *const *b = y;
+
+ if (!sd_json_variant_is_string(*a) || !sd_json_variant_is_string(*b))
+ return CMP(*a, *b);
+
+ return strcmp(sd_json_variant_string(*a), sd_json_variant_string(*b));
+}
+
+_public_ int sd_json_variant_sort(sd_json_variant **v) {
+ _cleanup_free_ sd_json_variant **a = NULL;
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *n = NULL;
+ size_t m;
+ int r;
+
+ assert(v);
+
+ if (sd_json_variant_is_sorted(*v))
+ return 0;
+
+ if (!sd_json_variant_is_object(*v))
+ return -EMEDIUMTYPE;
+
+ /* Sorts they key/value pairs in an object variant */
+
+ m = sd_json_variant_elements(*v);
+ a = new(sd_json_variant*, m);
+ if (!a)
+ return -ENOMEM;
+
+ for (size_t i = 0; i < m; i++)
+ a[i] = sd_json_variant_by_index(*v, i);
+
+ qsort(a, m/2, sizeof(sd_json_variant*)*2, json_cmp_strings);
+
+ r = sd_json_variant_new_object(&n, a, m);
+ if (r < 0)
+ return r;
+
+ json_variant_propagate_sensitive(*v, n);
+
+ if (!n->sorted) /* Check if this worked. This will fail if there are multiple identical keys used. */
+ return -ENOTUNIQ;
+
+ JSON_VARIANT_REPLACE(*v, TAKE_PTR(n));
+
+ return 1;
+}
+
+_public_ int sd_json_variant_normalize(sd_json_variant **v) {
+ _cleanup_free_ sd_json_variant **a = NULL;
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *n = NULL;
+ size_t i, m;
+ int r;
+
+ assert(v);
+
+ if (sd_json_variant_is_normalized(*v))
+ return 0;
+
+ if (!sd_json_variant_is_object(*v) && !sd_json_variant_is_array(*v))
+ return -EMEDIUMTYPE;
+
+ /* Sorts the key/value pairs in an object variant anywhere down the tree in the specified variant */
+
+ m = sd_json_variant_elements(*v);
+ a = new(sd_json_variant*, m);
+ if (!a)
+ return -ENOMEM;
+
+ for (i = 0; i < m; ) {
+ a[i] = sd_json_variant_ref(sd_json_variant_by_index(*v, i));
+ i++;
+
+ r = sd_json_variant_normalize(&a[i-1]);
+ if (r < 0)
+ goto finish;
+ }
+
+ qsort(a, m/2, sizeof(sd_json_variant*)*2, json_cmp_strings);
+
+ if (sd_json_variant_is_object(*v))
+ r = sd_json_variant_new_object(&n, a, m);
+ else {
+ assert(sd_json_variant_is_array(*v));
+ r = sd_json_variant_new_array(&n, a, m);
+ }
+ if (r < 0)
+ goto finish;
+
+ json_variant_propagate_sensitive(*v, n);
+
+ if (!n->normalized) { /* Let's see if normalization worked. It will fail if there are multiple
+ * identical keys used in the same object anywhere, or if there are floating
+ * point numbers used (see below) */
+ r = -ENOTUNIQ;
+ goto finish;
+ }
+
+ JSON_VARIANT_REPLACE(*v, TAKE_PTR(n));
+
+ r = 1;
+
+finish:
+ for (size_t j = 0; j < i; j++)
+ sd_json_variant_unref(a[j]);
+
+ return r;
+}
+
+_public_ int sd_json_variant_is_normalized(sd_json_variant *v) {
+ /* For now, let's consider anything containing numbers not expressible as integers as non-normalized.
+ * That's because we cannot sensibly compare them due to accuracy issues, nor even store them if they
+ * are too large. */
+ if (sd_json_variant_is_real(v) && !sd_json_variant_is_integer(v) && !sd_json_variant_is_unsigned(v))
+ return false;
+
+ /* The concept only applies to variants that include other variants, i.e. objects and arrays. All
+ * others are normalized anyway. */
+ if (!sd_json_variant_is_object(v) && !sd_json_variant_is_array(v))
+ return true;
+
+ /* Empty objects/arrays don't include any other variant, hence are always normalized too */
+ if (sd_json_variant_elements(v) == 0)
+ return true;
+
+ return v->normalized; /* For everything else there's an explicit boolean we maintain */
+}
+
+_public_ int sd_json_variant_is_sorted(sd_json_variant *v) {
+
+ /* Returns true if all key/value pairs of an object are properly sorted. Note that this only applies
+ * to objects, not arrays. */
+
+ if (!sd_json_variant_is_object(v))
+ return true;
+ if (sd_json_variant_elements(v) <= 1)
+ return true;
+
+ return v->sorted;
+}
+
+_public_ int sd_json_variant_unbase64(sd_json_variant *v, void **ret, size_t *ret_size) {
+ if (!sd_json_variant_is_string(v))
+ return -EINVAL;
+
+ return unbase64mem_full(sd_json_variant_string(v), SIZE_MAX, /* secure= */ sd_json_variant_is_sensitive(v), ret, ret_size);
+}
+
+_public_ int sd_json_variant_unhex(sd_json_variant *v, void **ret, size_t *ret_size) {
+ if (!sd_json_variant_is_string(v))
+ return -EINVAL;
+
+ return unhexmem_full(sd_json_variant_string(v), SIZE_MAX, /* secure= */ sd_json_variant_is_sensitive(v), ret, ret_size);
+}
+
+static const char* const sd_json_variant_type_table[_SD_JSON_VARIANT_TYPE_MAX] = {
+ [SD_JSON_VARIANT_STRING] = "string",
+ [SD_JSON_VARIANT_INTEGER] = "integer",
+ [SD_JSON_VARIANT_UNSIGNED] = "unsigned",
+ [SD_JSON_VARIANT_REAL] = "real",
+ [SD_JSON_VARIANT_NUMBER] = "number",
+ [SD_JSON_VARIANT_BOOLEAN] = "boolean",
+ [SD_JSON_VARIANT_ARRAY] = "array",
+ [SD_JSON_VARIANT_OBJECT] = "object",
+ [SD_JSON_VARIANT_NULL] = "null",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(sd_json_variant_type, sd_json_variant_type_t);