]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #10201 from yuwata/fix-10196
authorLennart Poettering <lennart@poettering.net>
Fri, 12 Oct 2018 09:36:08 +0000 (11:36 +0200)
committerGitHub <noreply@github.com>
Fri, 12 Oct 2018 09:36:08 +0000 (11:36 +0200)
sd-netlink: add destroy_callback to sd_netlink_call_async() and fix memleaks in networkd

62 files changed:
man/busctl.xml
man/journalctl.xml
man/meson.build
man/os-release.xml
man/systemd.network.xml
man/systemd.timer.xml
meson.build
shell-completion/bash/journalctl
shell-completion/bash/machinectl
shell-completion/bash/systemctl.in
shell-completion/zsh/_sd_outputmodes
src/analyze/analyze-verify.c
src/basic/bitmap.c
src/basic/capability-util.h
src/basic/hashmap.c
src/basic/json-internal.h [new file with mode: 0644]
src/basic/json.c [new file with mode: 0644]
src/basic/json.h [new file with mode: 0644]
src/basic/mempool.c
src/basic/mempool.h
src/basic/meson.build
src/basic/util.h
src/busctl/busctl.c
src/core/automount.c
src/core/cgroup.c
src/core/dbus-manager.c
src/core/emergency-action.c
src/core/load-fragment.c
src/core/main.c
src/core/manager.c
src/core/manager.h
src/core/unit.c
src/fuzz/fuzz-json.c [new file with mode: 0644]
src/fuzz/meson.build
src/journal-remote/journal-gatewayd.c
src/journal/journalctl.c
src/libsystemd/disable-mempool.c [new file with mode: 0644]
src/libsystemd/meson.build
src/libsystemd/sd-bus/bus-creds.c
src/login/loginctl.c
src/machine/machinectl.c
src/resolve/resolved-manager.c
src/shared/enable-mempool.c [new file with mode: 0644]
src/shared/logs-show.c
src/shared/meson.build
src/shared/output-mode.c
src/shared/output-mode.h
src/shared/path-lookup.c
src/test/meson.build
src/test/test-bpf.c
src/test/test-hexdecoct.c
src/test/test-json.c [new file with mode: 0644]
src/test/test-watch-pid.c
test/fuzz/meson.build
test/test-execute/exec-dynamicuser-supplementarygroups.service
test/test-execute/exec-supplementarygroups-multiple-groups-default-group-user.service
test/test-execute/exec-supplementarygroups-multiple-groups-withgid.service
test/test-execute/exec-supplementarygroups-multiple-groups-withuid.service
test/test-execute/exec-supplementarygroups-single-group-user.service
test/test-execute/exec-supplementarygroups.service
test/test-functions
tools/oss-fuzz.sh

index 5154c80efec861692f90f68d873cdfce1a59046b..539ef6c64557a7bb229385bfab3220f8a355a577 100644 (file)
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--json=</option><replaceable>MODE</replaceable></term>
+
+        <listitem>
+          <para>When used with the <command>call</command> or <command>get-property</command> command, shows output
+          formatted as JSON. Expects one of <literal>short</literal> (for the shortest possible output without any
+          redundant whitespace or line breaks) or <literal>pretty</literal> (for a pretty version of the same, with
+          indentation and line breaks). Note that transformation from D-Bus marshalling to JSON is done in a loss-less
+          way, which means type information is embedded into the JSON object tree.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-j</option></term>
+
+        <listitem>
+          <para>Equivalent to <option>--json=pretty</option> when invoked interactively from a terminal. Otherwise
+          equivalent to <option>--json=short</option>, in particular when the output is piped to some other
+          program.</para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--expect-reply=</option><replaceable>BOOL</replaceable></term>
 
index 91461a77323a42c204ef58a01c8be41502bc9efc..5102dcdd39f76c4c6588abb30a409ab39bfdc90f 100644 (file)
                 <option>json</option>
               </term>
               <listitem>
-                <para>formats entries as JSON data structures, one per
-                line (see
-                <ulink url="https://www.freedesktop.org/wiki/Software/systemd/json">Journal JSON Format</ulink>
-                for more information).</para>
+                <para>formats entries as JSON objects, separated by newline characters (see <ulink
+                url="https://www.freedesktop.org/wiki/Software/systemd/json">Journal JSON Format</ulink> for more
+                information). Field values are generally encoded as JSON strings, with three exceptions:
+                <orderedlist>
+                  <listitem><para>Fields larger than 4096 bytes are encoded as <constant>null</constant> values. (This
+                  may be turned off by passing <option>--all</option>, but be aware that this may allocate overly long
+                  JSON objects.) </para></listitem>
+
+                  <listitem><para>Journal entries permit non-unique fields within the same log entry. JSON does not allow
+                  non-unique fields within objects. Due to this, if a non-unique field is encountered a JSON array is
+                  used as field value, listing all field values as elements.</para></listitem>
+
+                  <listitem><para>Fields containing non-printable or non-UTF8 bytes are encoded as arrays containing
+                  the raw bytes individually formatted as unsigned numbers.</para></listitem>
+                </orderedlist>
+
+                Note that this encoding is reversible (with the exception of the size limit).</para>
               </listitem>
             </varlistentry>
 
               </listitem>
             </varlistentry>
 
+            <varlistentry>
+              <term>
+                <option>json-seq</option>
+              </term>
+              <listitem>
+                <para>formats entries as JSON data structures, but prefixes them with an ASCII Record Separator
+                character (0x1E) and suffixes them with an ASCII Line Feed character (0x0A), in accordance with <ulink
+                url="https://tools.ietf.org/html/rfc7464">JavaScript Object Notation (JSON) Text Sequences </ulink>
+                (<literal>application/json-seq</literal>).
+                </para>
+              </listitem>
+            </varlistentry>
+
             <varlistentry>
               <term>
                 <option>cat</option>
       <varlistentry>
         <term><option>--output-fields=</option></term>
 
-        <listitem><para>A comma separated list of the fields which should
-        be included in the output. This only has an effect for the output modes
-        which would normally show all fields (<option>verbose</option>,
-        <option>export</option>, <option>json</option>,
-        <option>json-pretty</option>, and <option>json-sse</option>). The
-        <literal>__CURSOR</literal>, <literal>__REALTIME_TIMESTAMP</literal>,
-        <literal>__MONOTONIC_TIMESTAMP</literal>, and
-        <literal>_BOOT_ID</literal> fields are always
+        <listitem><para>A comma separated list of the fields which should be included in the output. This only has an
+        effect for the output modes which would normally show all fields (<option>verbose</option>,
+        <option>export</option>, <option>json</option>, <option>json-pretty</option>, <option>json-sse</option> and
+        <option>json-seq</option>). The <literal>__CURSOR</literal>, <literal>__REALTIME_TIMESTAMP</literal>,
+        <literal>__MONOTONIC_TIMESTAMP</literal>, and <literal>_BOOT_ID</literal> fields are always
         printed.</para></listitem>
       </varlistentry>
 
index 9cd93df8462408160531c9f00e89e9153e176f82..05197d6ef42285766f08aace0f08e567e59bba0c 100644 (file)
@@ -166,7 +166,8 @@ foreach tuple : xsltproc.found() ? [['systemd.directives', '7', systemd_directiv
         html_pages += p3
 endforeach
 
-# cannot use run_target until https://github.com/mesonbuild/meson/issues/1644 is resolved
+# Cannot use run_target because those targets are used in depends
+# Also see https://github.com/mesonbuild/meson/issues/368.
 man = custom_target(
         'man',
         output : 'man',
index 0efd2638410e4c10145fab6feebf2869a27cc651..ea71b36c1e040385d2aefe6207e39ad250fed8f1 100644 (file)
         </para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>LOGO=</varname></term>
+
+        <listitem><para>
+        A string, specifying the name of an icon as defined by <ulink
+        url="http://standards.freedesktop.org/icon-theme-spec/latest">
+        freedesktop.org Icon Theme Specification</ulink>. This can be
+        used by graphical applications to display an operating
+        system's or distributor's logo. This field is optional and
+        may not necessarily be implemented on all systems.
+        Examples:
+        <literal>LOGO=fedora-logo</literal>,
+        <literal>LOGO=distributor-logo-opensuse</literal>
+        </para></listitem>
+      </varlistentry>
+
     </variablelist>
 
     <para>If you are reading this file from C code or a shell script
index 2c564d26b47858786b628dc478df828ef24562f6..500467b67e534ba5701b8c139b92c2cbf0463bb1 100644 (file)
           the event that there is no address being assigned by DHCP or the
           cable is not plugged in, the link will simply remain offline and be
           skipped automatically by <literal>systemd-networkd-wait-online</literal>
-          if <literal>RequiredForOnline=true</literal>.</para>
+          if <literal>RequiredForOnline=no</literal>.</para>
         </listitem>
       </varlistentry>
     </variablelist>
index 24bdb4baf98786dd52e74663730b60ba4fb5ce3e..b761f16aa043eb7fbf7a5ff72ac79351d474b945 100644 (file)
         distributed amount of time between 0 and the specified time
         value. Defaults to 0, indicating that no randomized delay
         shall be applied. Each timer unit will determine this delay
-        randomly each time it is started, and the delay will simply be
+        randomly before each iteration, and the delay will simply be
         added on top of the next determined elapsing time. This is
         useful to stretch dispatching of similarly configured timer
         events over a certain amount time, to avoid that they all fire
index 51407098dc25ea3b66e54583363e4dd3da81494c..8766e7b01829a3b6fc0466a97e042be40ee93c23 100644 (file)
@@ -27,6 +27,13 @@ substs = configuration_data()
 substs.set('PACKAGE_URL',          'https://www.freedesktop.org/wiki/Software/systemd')
 substs.set('PACKAGE_VERSION',      meson.project_version())
 
+want_ossfuzz = get_option('oss-fuzz')
+want_libfuzzer = get_option('llvm-fuzz')
+if want_ossfuzz and want_libfuzzer
+        error('only one of oss-fuzz and llvm-fuzz can be specified')
+endif
+fuzzer_build = want_ossfuzz or want_libfuzzer
+
 #####################################################################
 
 # Try to install the git pre-commit hook
@@ -274,25 +281,18 @@ want_tests = get_option('tests')
 slow_tests = want_tests != 'false' and get_option('slow-tests')
 install_tests = get_option('install-tests')
 
-cxx = find_program('c++', required : false)
+cxx = find_program('c++', required : fuzzer_build)
 if cxx.found()
         #  Used only for tests
         add_languages('cpp')
-        cpp_cmd = ' '.join(meson.get_compiler('cpp').cmd_array())
+        cxx_cmd = ' '.join(meson.get_compiler('cpp').cmd_array())
 else
-        cpp_cmd = ''
+        cxx_cmd = ''
 endif
 
-want_ossfuzz = get_option('oss-fuzz')
-want_libfuzzer = get_option('llvm-fuzz')
-fuzzer_build = want_ossfuzz or want_libfuzzer
-if want_ossfuzz and want_libfuzzer
-        error('only one of oss-fuzz and llvm-fuzz can be specified')
-endif
 if want_libfuzzer
         fuzzing_engine = meson.get_compiler('cpp').find_library('Fuzzer')
-endif
-if want_ossfuzz
+elif want_ossfuzz
         fuzzing_engine = meson.get_compiler('cpp').find_library('FuzzingEngine')
 endif
 
@@ -395,7 +395,7 @@ if cc.compiles('''
         add_project_arguments('-Werror=shadow', language : 'c')
 endif
 
-cpp = ' '.join(cc.cmd_array() + get_option('c_args')) + ' -E'
+cpp = ' '.join(cc.cmd_array()) + ' -E'
 
 #####################################################################
 # compilation result tests
@@ -1383,7 +1383,7 @@ libjournal_core = static_library(
 libsystemd_sym_path = '@0@/@1@'.format(meson.current_source_dir(), libsystemd_sym)
 libsystemd = shared_library(
         'systemd',
-        'src/systemd/sd-id128.h', # pick a header file at random to work around old meson bug
+        disable_mempool_c,
         version : libsystemd_version,
         include_directories : includes,
         link_args : ['-shared',
@@ -1465,6 +1465,7 @@ subdir('test')
 test_dlopen = executable(
         'test-dlopen',
         test_dlopen_c,
+        disable_mempool_c,
         include_directories : includes,
         link_with : [libbasic],
         dependencies : [libdl],
@@ -1485,6 +1486,7 @@ foreach tuple : [['myhostname', 'ENABLE_NSS_MYHOSTNAME'],
                 nss = shared_library(
                         'nss_' + module,
                         'src/nss-@0@/nss-@0@.c'.format(module),
+                        disable_mempool_c,
                         version : '2',
                         include_directories : includes,
                         # Note that we link NSS modules with '-z nodelete' so that mempools never get orphaned
@@ -2782,12 +2784,9 @@ subdir('shell-completion/zsh')
 subdir('docs/sysvinit')
 subdir('docs/var-log')
 
-# FIXME: figure out if the warning is true:
-# https://github.com/mesonbuild/meson/wiki/Reference-manual#install_subdir
 install_subdir('factory/etc',
                install_dir : factorydir)
 
-
 install_data('xorg/50-systemd-user.sh',
              install_dir : xinitrcdir)
 install_data('modprobe.d/systemd.conf',
@@ -2984,6 +2983,10 @@ alt_time_epoch = run_command('date', '-Is', '-u', '-d',
 status += [
         'time epoch:                        @0@ (@1@)'.format(time_epoch, alt_time_epoch)]
 
+status += [
+        'static libsystemd:                 @0@'.format(static_libsystemd),
+        'static libudev:                    @0@'.format(static_libudev)]
+
 # TODO:
 # CFLAGS:   ${OUR_CFLAGS} ${CFLAGS}
 # CPPFLAGS: ${OUR_CPPFLAGS} ${CPPFLAGS}
@@ -3083,6 +3086,8 @@ foreach tuple : [
         ['debug mmap cache'],
         ['valgrind',         conf.get('VALGRIND') == 1],
         ['trace logging',    conf.get('LOG_TRACE') == 1],
+        ['link-udev-shared',      get_option('link-udev-shared')],
+        ['link-systemctl-shared', get_option('link-systemctl-shared')],
 ]
 
         if tuple.length() >= 2
index 5a5131e5b33790b692b7052663c70d9a0fb24566..829cf415be5af51e5284e03e4e8fd419a64ce443 100644 (file)
@@ -66,7 +66,7 @@ _journalctl() {
                                 compopt -o filenames
                         ;;
                         --output|-o)
-                                comps='short short-full short-iso short-iso-precise short-precise short-monotonic short-unix verbose export json json-pretty json-sse cat with-unit'
+                                comps='short short-full short-iso short-iso-precise short-precise short-monotonic short-unix verbose export json json-pretty json-sse json-seq cat with-unit'
                         ;;
                         --field|-F)
                                 comps=$(journalctl --fields | sort 2>/dev/null)
index aa5816bbf5dcac4fd2e25ef3f972fafeb642abe8..16d037a0009e5f699bfc8471ad3b54809bcbc3f2 100644 (file)
@@ -77,7 +77,7 @@ _machinectl() {
                                 comps=''
                         ;;
                         --output|-o)
-                                comps='short short-full short-iso short-iso-precise short-precise short-monotonic short-unix verbose export json json-pretty json-sse cat'
+                                comps='short short-full short-iso short-iso-precise short-precise short-monotonic short-unix verbose export json json-pretty json-sse json-seq cat with-unit'
                         ;;
                 esac
                 COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
index 933bb1844fe130672856ac4e15112597c5367257..8756bfb8a569007121a165a42d696810481121df 100644 (file)
@@ -169,7 +169,7 @@ _systemctl () {
                         ;;
                         --output|-o)
                                 comps='short short-full short-iso short-iso-precise short-precise short-monotonic short-unix verbose export json
-                                       json-pretty json-sse cat'
+                                       json-pretty json-sse json-seq cat with-unit'
                         ;;
                         --machine|-M)
                                 comps=$( __get_machines )
index 70ff7233afc2ac9bf54a07fb766697d1ae0489e1..763b106f3d93272a47b7013a78b7d557b20020f1 100644 (file)
@@ -2,5 +2,5 @@
 # SPDX-License-Identifier: LGPL-2.1+
 
 local -a _output_opts
-_output_opts=(short short-full short-iso short-iso-precise short-precise short-monotonic short-unix verbose export json json-pretty json-sse cat with-unit)
+_output_opts=(short short-full short-iso short-iso-precise short-precise short-monotonic short-unix verbose export json json-pretty json-sse json-seq cat with-unit)
 _describe -t output 'output mode' _output_opts || compadd "$@"
index ed369532d42325b5df9a38a141af6a7bf07af5ea..402203a3c79960ee3082fdd479dcaa934428f060 100644 (file)
@@ -225,9 +225,10 @@ static int verify_unit(Unit *u, bool check_man) {
 }
 
 int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run_generators) {
-        const uint8_t flags = MANAGER_TEST_RUN_BASIC |
-                              MANAGER_TEST_RUN_ENV_GENERATORS |
-                              run_generators * MANAGER_TEST_RUN_GENERATORS;
+        const ManagerTestRunFlags flags =
+                MANAGER_TEST_RUN_BASIC |
+                MANAGER_TEST_RUN_ENV_GENERATORS |
+                run_generators * MANAGER_TEST_RUN_GENERATORS;
 
         _cleanup_(manager_freep) Manager *m = NULL;
         Unit *units[strv_length(filenames)];
@@ -253,7 +254,7 @@ int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run
 
         r = manager_startup(m, NULL, NULL);
         if (r < 0)
-                return log_error_errno(r, "Failed to start manager: %m");
+                return r;
 
         manager_clear_jobs(m);
 
index c17c6a7a02f78f2772e7f446fff78ee96a616399..a4cd6451b0c0adf34d15862b8873c5a36698ca48 100644 (file)
@@ -206,7 +206,7 @@ bool bitmap_equal(Bitmap *a, Bitmap *b) {
                 return true;
 
         common_n_bitmaps = MIN(a->n_bitmaps, b->n_bitmaps);
-        if (memcmp(a->bitmaps, b->bitmaps, sizeof(uint64_t) * common_n_bitmaps) != 0)
+        if (memcmp_safe(a->bitmaps, b->bitmaps, sizeof(uint64_t) * common_n_bitmaps) != 0)
                 return false;
 
         c = a->n_bitmaps > b->n_bitmaps ? a : b;
index 4a4a86093a4b94756fb4ec94dc900bbecf0f4d04..59591d4b521eb56039b11a0bf62d0eae2f410e90 100644 (file)
@@ -39,3 +39,7 @@ static inline bool cap_test_all(uint64_t caps) {
 }
 
 bool ambient_capabilities_supported(void);
+
+/* Identical to linux/capability.h's CAP_TO_MASK(), but uses an unsigned 1U instead of a signed 1 for shifting left, in
+ * order to avoid complaints about shifting a signed int left by 31 bits, which would make it negative. */
+#define CAP_TO_MASK_CORRECTED(x) (1U << ((x) & 31U))
index 44d718c83d223b1c5abb11ed295e2ef967f3034f..018ef5e7d809dd278284178974e07b477e6a47ef 100644 (file)
@@ -6,7 +6,6 @@
 #include <string.h>
 
 #include "alloc-util.h"
-#include "env-util.h"
 #include "fileio.h"
 #include "hashmap.h"
 #include "macro.h"
@@ -767,24 +766,12 @@ static void reset_direct_storage(HashmapBase *h) {
         memset(p, DIB_RAW_INIT, sizeof(dib_raw_t) * hi->n_direct_buckets);
 }
 
-static bool use_pool(void) {
-        static int b = -1;
-
-        if (!is_main_thread())
-                return false;
-
-        if (b < 0)
-                b = getenv_bool("SYSTEMD_MEMPOOL") != 0;
-
-        return b;
-}
-
 static struct HashmapBase *hashmap_base_new(const struct hash_ops *hash_ops, enum HashmapType type HASHMAP_DEBUG_PARAMS) {
         HashmapBase *h;
         const struct hashmap_type_info *hi = &hashmap_type_info[type];
         bool up;
 
-        up = use_pool();
+        up = mempool_enabled();
 
         h = up ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size);
         if (!h)
diff --git a/src/basic/json-internal.h b/src/basic/json-internal.h
new file mode 100644 (file)
index 0000000..6d195eb
--- /dev/null
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#pragma once
+
+#include "json.h"
+
+/* This header should include all prototypes only the JSON parser itself and
+ * its tests need access to. Normal code consuming the JSON parser should not
+ * interface with this. */
+
+typedef union JsonValue  {
+        /* Encodes a simple value. On x86-64 this structure is 16 bytes wide (as long double is 128bit). */
+        bool boolean;
+        long double real;
+        intmax_t integer;
+        uintmax_t unsig;
+} JsonValue;
+
+/* Let's protect us against accidental structure size changes on our most relevant arch */
+#ifdef __x86_64__
+assert_cc(sizeof(JsonValue) == 16U);
+#endif
+
+#define JSON_VALUE_NULL ((JsonValue) {})
+
+/* We use fake JsonVariant objects for some special values, in order to avoid memory allocations for them. Note that
+ * effectively this means that there are multiple ways to encode the same objects: via these magic values or as
+ * properly allocated JsonVariant. We convert between both on-the-fly as necessary. */
+#define JSON_VARIANT_MAGIC_TRUE ((JsonVariant*) 1)
+#define JSON_VARIANT_MAGIC_FALSE ((JsonVariant*) 2)
+#define JSON_VARIANT_MAGIC_NULL ((JsonVariant*) 3)
+#define JSON_VARIANT_MAGIC_ZERO_INTEGER ((JsonVariant*) 4)
+#define JSON_VARIANT_MAGIC_ZERO_UNSIGNED ((JsonVariant*) 5)
+#define JSON_VARIANT_MAGIC_ZERO_REAL ((JsonVariant*) 6)
+#define JSON_VARIANT_MAGIC_EMPTY_STRING ((JsonVariant*) 7)
+#define JSON_VARIANT_MAGIC_EMPTY_ARRAY ((JsonVariant*) 8)
+#define JSON_VARIANT_MAGIC_EMPTY_OBJECT ((JsonVariant*) 9)
+
+enum { /* JSON tokens */
+        JSON_TOKEN_END,
+        JSON_TOKEN_COLON,
+        JSON_TOKEN_COMMA,
+        JSON_TOKEN_OBJECT_OPEN,
+        JSON_TOKEN_OBJECT_CLOSE,
+        JSON_TOKEN_ARRAY_OPEN,
+        JSON_TOKEN_ARRAY_CLOSE,
+        JSON_TOKEN_STRING,
+        JSON_TOKEN_REAL,
+        JSON_TOKEN_INTEGER,
+        JSON_TOKEN_UNSIGNED,
+        JSON_TOKEN_BOOLEAN,
+        JSON_TOKEN_NULL,
+        _JSON_TOKEN_MAX,
+        _JSON_TOKEN_INVALID = -1,
+};
+
+int json_tokenize(const char **p, char **ret_string, JsonValue *ret_value, unsigned *ret_line, unsigned *ret_column, void **state, unsigned *line, unsigned *column);
diff --git a/src/basic/json.c b/src/basic/json.c
new file mode 100644 (file)
index 0000000..df3141f
--- /dev/null
@@ -0,0 +1,3290 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "sd-messages.h"
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "float.h"
+#include "hexdecoct.h"
+#include "json-internal.h"
+#include "json.h"
+#include "macro.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "utf8.h"
+
+typedef struct JsonSource {
+        /* When we parse from a file or similar, encodes the filename, to indicate the source of a json variant */
+        size_t n_ref;
+        unsigned max_line;
+        unsigned max_column;
+        char name[];
+} JsonSource;
+
+/* On x86-64 this whole structure should have a size of 6 * 64 bit = 48 bytes */
+struct JsonVariant {
+        union {
+                /* We either maintain a reference counter for this variant itself, or we are embedded into an
+                 * array/object, in which case only that surrounding object is ref-counted. (If 'embedded' is false,
+                 * see below.) */
+                size_t n_ref;
+
+                /* If this JsonVariant is part of an array/object, then this field points to the surrounding
+                 * JSON_VARIANT_ARRAY/JSON_VARIANT_OBJECT object. (If 'embedded' is true, see below.) */
+                JsonVariant *parent;
+        };
+
+        JsonVariantType type:5;
+
+        /* A marker whether this variant is embedded into in array/object or not. If true, the 'parent' pointer above
+         * is valid. If false, the 'n_ref' field above is valid instead. */
+        bool is_embedded:1;
+
+        /* In some conditions (for example, if this object is part of an array of strings or objects), we don't store
+         * any data inline, but instead simply reference an external object and act as surrogate of it. In that case
+         * this bool is set, and the external object is referenced through the .reference field below. */
+        bool is_reference:1;
+
+        /* While comparing two arrays, we use this for marking what we already have seen */
+        bool is_marked:1;
+
+        /* If this was parsed from some file or buffer, this stores where from, as well as the source line/column */
+        unsigned line, column;
+        JsonSource *source;
+
+        union {
+                /* For simple types we store the value in-line. */
+                JsonValue value;
+
+                /* For objects and arrays we store the number of elements immediately following */
+                size_t n_elements;
+
+                /* If is_reference as indicated above is set, this is where the reference object is actually stored. */
+                JsonVariant *reference;
+
+                /* Strings are placed immediately after the structure. Note that when this is a JsonVariant embedded
+                 * into an array we might encode strings up to INLINE_STRING_LENGTH characters directly inside the
+                 * element, while longer strings are stored as references. When this object is not embedded into an
+                 * array, but stand-alone we allocate the right size for the whole structure, i.e. the array might be
+                 * much larger than INLINE_STRING_LENGTH.
+                 *
+                 * Note that because we want to allocate arrays of the JsonVariant structure we specify [0] here,
+                 * rather than the prettier []. If we wouldn't, then this char array would have undefined size, and so
+                 * would the union and then the struct this is included in. And of structures with undefined size we
+                 * can't allocate arrays (at least not easily). */
+                char string[0];
+        };
+};
+
+/* Inside string arrays we have a series of JasonVariant structures one after the other. In this case, strings longer
+ * than INLINE_STRING_MAX are stored as references, and all shorter ones inline. (This means — on x86-64 — strings up
+ * to 15 chars are stored within the array elements, and all others in separate allocations) */
+#define INLINE_STRING_MAX (sizeof(JsonVariant) - offsetof(JsonVariant, string) - 1U)
+
+/* Let's make sure this structure isn't increased in size accidentally. This check is only for our most relevant arch
+ * (x86-64). */
+#ifdef __x86_64__
+assert_cc(sizeof(JsonVariant) == 48U);
+assert_cc(INLINE_STRING_MAX == 15U);
+#endif
+
+static JsonSource* json_source_new(const char *name) {
+        JsonSource *s;
+
+        assert(name);
+
+        s = malloc(offsetof(JsonSource, name) + strlen(name) + 1);
+        if (!s)
+                return NULL;
+
+        *s = (JsonSource) {
+                .n_ref = 1,
+        };
+        strcpy(s->name, name);
+
+        return s;
+}
+
+DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(JsonSource, json_source, mfree);
+
+static bool json_source_equal(JsonSource *a, JsonSource *b) {
+        if (a == b)
+                return true;
+
+        if (!a || !b)
+                return false;
+
+        return streq(a->name, b->name);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(JsonSource*, json_source_unref);
+
+static bool json_variant_is_magic(const JsonVariant *v) {
+        return v == JSON_VARIANT_MAGIC_TRUE ||
+                v == JSON_VARIANT_MAGIC_FALSE ||
+                v == JSON_VARIANT_MAGIC_NULL ||
+                v == JSON_VARIANT_MAGIC_ZERO_INTEGER ||
+                v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
+                v == JSON_VARIANT_MAGIC_ZERO_REAL ||
+                v == JSON_VARIANT_MAGIC_EMPTY_STRING ||
+                v == JSON_VARIANT_MAGIC_EMPTY_ARRAY ||
+                v == JSON_VARIANT_MAGIC_EMPTY_OBJECT;
+}
+
+static JsonVariant *json_variant_dereference(JsonVariant *v) {
+
+        /* Recursively dereference variants that are references to other variants */
+
+        if (!v)
+                return NULL;
+
+        if (json_variant_is_magic(v))
+                return v;
+
+        if (!v->is_reference)
+                return v;
+
+        return json_variant_dereference(v->reference);
+}
+
+static JsonVariant *json_variant_normalize(JsonVariant *v) {
+
+        /* Converts json variants to their normalized form, i.e. fully dereferenced and wherever possible converted to
+         * the "magic" version if there is one */
+
+        if (!v)
+                return NULL;
+
+        v = json_variant_dereference(v);
+
+        switch (json_variant_type(v)) {
+
+        case JSON_VARIANT_BOOLEAN:
+                return json_variant_boolean(v) ? JSON_VARIANT_MAGIC_TRUE : JSON_VARIANT_MAGIC_FALSE;
+
+        case JSON_VARIANT_NULL:
+                return JSON_VARIANT_MAGIC_NULL;
+
+        case JSON_VARIANT_INTEGER:
+                return json_variant_integer(v) == 0 ? JSON_VARIANT_MAGIC_ZERO_INTEGER : v;
+
+        case JSON_VARIANT_UNSIGNED:
+                return json_variant_unsigned(v) == 0 ? JSON_VARIANT_MAGIC_ZERO_UNSIGNED : v;
+
+        case JSON_VARIANT_REAL:
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+                return json_variant_real(v) == 0.0 ? JSON_VARIANT_MAGIC_ZERO_REAL : v;
+#pragma GCC diagnostic pop
+
+        case JSON_VARIANT_STRING:
+                return isempty(json_variant_string(v)) ? JSON_VARIANT_MAGIC_EMPTY_STRING : v;
+
+        case JSON_VARIANT_ARRAY:
+                return json_variant_elements(v) == 0 ? JSON_VARIANT_MAGIC_EMPTY_ARRAY : v;
+
+        case JSON_VARIANT_OBJECT:
+                return json_variant_elements(v) == 0 ? JSON_VARIANT_MAGIC_EMPTY_OBJECT : v;
+
+        default:
+                return v;
+        }
+}
+
+static JsonVariant *json_variant_conservative_normalize(JsonVariant *v) {
+
+        /* Much like json_variant_normalize(), but won't simplify if the variant has a source/line location attached to
+         * it, in order not to lose context */
+
+        if (!v)
+                return NULL;
+
+        if (json_variant_is_magic(v))
+                return v;
+
+        if (v->source || v->line > 0 || v->column > 0)
+                return v;
+
+        return json_variant_normalize(v);
+}
+
+static int json_variant_new(JsonVariant **ret, JsonVariantType type, size_t space) {
+        JsonVariant *v;
+
+        assert_return(ret, -EINVAL);
+
+        v = malloc0(offsetof(JsonVariant, value) + space);
+        if (!v)
+                return -ENOMEM;
+
+        v->n_ref = 1;
+        v->type = type;
+
+        *ret = v;
+        return 0;
+}
+
+int json_variant_new_integer(JsonVariant **ret, intmax_t i) {
+        JsonVariant *v;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        if (i == 0) {
+                *ret = JSON_VARIANT_MAGIC_ZERO_INTEGER;
+                return 0;
+        }
+
+        r = json_variant_new(&v, JSON_VARIANT_INTEGER, sizeof(i));
+        if (r < 0)
+                return r;
+
+        v->value.integer = i;
+        *ret = v;
+
+        return 0;
+}
+
+int json_variant_new_unsigned(JsonVariant **ret, uintmax_t u) {
+        JsonVariant *v;
+        int r;
+
+        assert_return(ret, -EINVAL);
+        if (u == 0) {
+                *ret = JSON_VARIANT_MAGIC_ZERO_UNSIGNED;
+                return 0;
+        }
+
+        r = json_variant_new(&v, JSON_VARIANT_UNSIGNED, sizeof(u));
+        if (r < 0)
+                return r;
+
+        v->value.unsig = u;
+        *ret = v;
+
+        return 0;
+}
+
+int json_variant_new_real(JsonVariant **ret, long double d) {
+        JsonVariant *v;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+        if (d == 0.0) {
+#pragma GCC diagnostic pop
+                *ret = JSON_VARIANT_MAGIC_ZERO_REAL;
+                return 0;
+        }
+
+        r = json_variant_new(&v, JSON_VARIANT_REAL, sizeof(d));
+        if (r < 0)
+                return r;
+
+        v->value.real = d;
+        *ret = v;
+
+        return 0;
+}
+
+int json_variant_new_boolean(JsonVariant **ret, bool b) {
+        assert_return(ret, -EINVAL);
+
+        if (b)
+                *ret = JSON_VARIANT_MAGIC_TRUE;
+        else
+                *ret = JSON_VARIANT_MAGIC_FALSE;
+
+        return 0;
+}
+
+int json_variant_new_null(JsonVariant **ret) {
+        assert_return(ret, -EINVAL);
+
+        *ret = JSON_VARIANT_MAGIC_NULL;
+        return 0;
+}
+
+int json_variant_new_stringn(JsonVariant **ret, const char *s, size_t n) {
+        JsonVariant *v;
+        int r;
+
+        assert_return(ret, -EINVAL);
+        if (!s) {
+                assert_return(n == 0, -EINVAL);
+                return json_variant_new_null(ret);
+        }
+        if (n == 0) {
+                *ret = JSON_VARIANT_MAGIC_EMPTY_STRING;
+                return 0;
+        }
+
+        r = json_variant_new(&v, JSON_VARIANT_STRING, n + 1);
+        if (r < 0)
+                return r;
+
+        memcpy(v->string, s, n);
+        v->string[n] = 0;
+
+        *ret = v;
+        return 0;
+}
+
+static void json_variant_set(JsonVariant *a, JsonVariant *b) {
+        assert(a);
+
+        b = json_variant_dereference(b);
+        if (!b) {
+                a->type = JSON_VARIANT_NULL;
+                return;
+        }
+
+        a->type = json_variant_type(b);
+        switch (a->type) {
+
+        case JSON_VARIANT_INTEGER:
+                a->value.integer = json_variant_integer(b);
+                break;
+
+        case JSON_VARIANT_UNSIGNED:
+                a->value.unsig = json_variant_unsigned(b);
+                break;
+
+        case JSON_VARIANT_REAL:
+                a->value.real = json_variant_real(b);
+                break;
+
+        case JSON_VARIANT_BOOLEAN:
+                a->value.boolean = json_variant_boolean(b);
+                break;
+
+        case JSON_VARIANT_STRING: {
+                const char *s;
+
+                assert_se(s = json_variant_string(b));
+
+                /* Short strings we can store inline */
+                if (strnlen(s, INLINE_STRING_MAX+1) <= INLINE_STRING_MAX) {
+                        strcpy(a->string, s);
+                        break;
+                }
+
+                /* For longer strings, use a reference… */
+                _fallthrough_;
+        }
+
+        case JSON_VARIANT_ARRAY:
+        case JSON_VARIANT_OBJECT:
+                a->is_reference = true;
+                a->reference = json_variant_ref(json_variant_conservative_normalize(b));
+                break;
+
+        case JSON_VARIANT_NULL:
+                break;
+
+        default:
+                assert_not_reached("Unexpected variant type");
+        }
+}
+
+static void json_variant_copy_source(JsonVariant *v, JsonVariant *from) {
+        assert(v);
+        assert(from);
+
+        if (json_variant_is_magic(from))
+                return;
+
+        v->line = from->line;
+        v->column = from->column;
+        v->source = json_source_ref(from->source);
+}
+
+int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) {
+        JsonVariant *v;
+        size_t i;
+
+        assert_return(ret, -EINVAL);
+        if (n == 0) {
+                *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY;
+                return 0;
+        }
+        assert_return(array, -EINVAL);
+
+        v = new(JsonVariant, n + 1);
+        if (!v)
+                return -ENOMEM;
+
+        *v = (JsonVariant) {
+                .n_ref = 1,
+                .type = JSON_VARIANT_ARRAY,
+                .n_elements = n,
+        };
+
+        for (i = 0; i < n; i++) {
+                JsonVariant *w = v + 1 + i;
+
+                *w = (JsonVariant) {
+                        .is_embedded = true,
+                        .parent = v,
+                };
+
+                json_variant_set(w, array[i]);
+                json_variant_copy_source(w, array[i]);
+        }
+
+        *ret = v;
+        return 0;
+}
+
+int json_variant_new_array_bytes(JsonVariant **ret, const void *p, size_t n) {
+        JsonVariant *v;
+        size_t i;
+
+        assert_return(ret, -EINVAL);
+        if (n == 0) {
+                *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY;
+                return 0;
+        }
+        assert_return(p, -EINVAL);
+
+        v = new(JsonVariant, n + 1);
+        if (!v)
+                return -ENOMEM;
+
+        *v = (JsonVariant) {
+                .n_ref = 1,
+                .type = JSON_VARIANT_ARRAY,
+                .n_elements = n,
+        };
+
+        for (i = 0; i < n; i++) {
+                JsonVariant *w = v + 1 + i;
+
+                *w = (JsonVariant) {
+                        .is_embedded = true,
+                        .parent = v,
+                };
+
+                w->type = JSON_VARIANT_UNSIGNED;
+                w->value.unsig = ((const uint8_t*) p)[i];
+        }
+
+        *ret = v;
+        return 0;
+}
+
+int json_variant_new_array_strv(JsonVariant **ret, char **l) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        size_t n;
+        int r;
+
+        assert(ret);
+
+        n = strv_length(l);
+        if (n == 0) {
+                *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY;
+                return 0;
+        }
+
+        v = new0(JsonVariant, n + 1);
+        if (!v)
+                return -ENOMEM;
+
+        *v = (JsonVariant) {
+                .n_ref = 1,
+                .type = JSON_VARIANT_ARRAY,
+        };
+
+        for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
+                JsonVariant *w = v + 1 + v->n_elements;
+                size_t k;
+
+                w->is_embedded = true;
+                w->parent = v;
+                w->type = JSON_VARIANT_STRING;
+
+                k = strlen(l[v->n_elements]);
+
+                if (k > INLINE_STRING_MAX) {
+                        /* If string is too long, store it as reference. */
+
+                        r = json_variant_new_stringn(&w->reference, l[v->n_elements], k);
+                        if (r < 0)
+                                return r;
+
+                        w->is_reference = true;
+                } else
+                        memcpy(w->string, l[v->n_elements], k+1);
+        }
+
+        *ret = TAKE_PTR(v);
+        return 0;
+}
+
+int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) {
+        JsonVariant *v;
+        size_t i;
+
+        assert_return(ret, -EINVAL);
+        if (n == 0) {
+                *ret = JSON_VARIANT_MAGIC_EMPTY_OBJECT;
+                return 0;
+        }
+        assert_return(array, -EINVAL);
+        assert_return(n % 2 == 0, -EINVAL);
+
+        v = new(JsonVariant, n + 1);
+        if (!v)
+                return -ENOMEM;
+
+        *v = (JsonVariant) {
+                .n_ref = 1,
+                .type = JSON_VARIANT_OBJECT,
+                .n_elements = n,
+        };
+
+        for (i = 0; i < n; i++) {
+                JsonVariant *w = v + 1 + i;
+
+                *w = (JsonVariant) {
+                        .is_embedded = true,
+                        .parent = v,
+                };
+
+                json_variant_set(w, array[i]);
+                json_variant_copy_source(w, array[i]);
+        }
+
+        *ret = v;
+        return 0;
+}
+
+static void json_variant_free_inner(JsonVariant *v) {
+        assert(v);
+
+        if (json_variant_is_magic(v))
+                return;
+
+        json_source_unref(v->source);
+
+        if (v->is_reference) {
+                json_variant_unref(v->reference);
+                return;
+        }
+
+        if (IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT)) {
+                size_t i;
+
+                for (i = 0; i < v->n_elements; i++)
+                        json_variant_free_inner(v + 1 + i);
+        }
+}
+
+JsonVariant *json_variant_ref(JsonVariant *v) {
+        if (!v)
+                return NULL;
+        if (json_variant_is_magic(v))
+                return v;
+
+        if (v->is_embedded)
+                json_variant_ref(v->parent); /* ref the compounding variant instead */
+        else {
+                assert(v->n_ref > 0);
+                v->n_ref++;
+        }
+
+        return v;
+}
+
+JsonVariant *json_variant_unref(JsonVariant *v) {
+        if (!v)
+                return NULL;
+        if (json_variant_is_magic(v))
+                return NULL;
+
+        if (v->is_embedded)
+                json_variant_unref(v->parent);
+        else {
+                assert(v->n_ref > 0);
+                v->n_ref--;
+
+                if (v->n_ref == 0) {
+                        json_variant_free_inner(v);
+                        free(v);
+                }
+        }
+
+        return NULL;
+}
+
+void json_variant_unref_many(JsonVariant **array, size_t n) {
+        size_t i;
+
+        assert(array || n == 0);
+
+        for (i = 0; i < n; i++)
+                json_variant_unref(array[i]);
+}
+
+const char *json_variant_string(JsonVariant *v) {
+        if (!v)
+                return NULL;
+        if (v == JSON_VARIANT_MAGIC_EMPTY_STRING)
+                return "";
+        if (json_variant_is_magic(v))
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_string(v->reference);
+        if (v->type != JSON_VARIANT_STRING)
+                goto mismatch;
+
+        return v->string;
+
+mismatch:
+        log_debug("Non-string JSON variant requested as string, returning NULL.");
+        return NULL;
+}
+
+bool json_variant_boolean(JsonVariant *v) {
+        if (!v)
+                goto mismatch;
+        if (v == JSON_VARIANT_MAGIC_TRUE)
+                return true;
+        if (v == JSON_VARIANT_MAGIC_FALSE)
+                return false;
+        if (json_variant_is_magic(v))
+                goto mismatch;
+        if (v->type != JSON_VARIANT_BOOLEAN)
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_boolean(v->reference);
+
+        return v->value.boolean;
+
+mismatch:
+        log_debug("Non-boolean JSON variant requested as boolean, returning false.");
+        return false;
+}
+
+intmax_t json_variant_integer(JsonVariant *v) {
+        if (!v)
+                goto mismatch;
+        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER ||
+            v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
+            v == JSON_VARIANT_MAGIC_ZERO_REAL)
+                return 0;
+        if (json_variant_is_magic(v))
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_integer(v->reference);
+
+        switch (v->type) {
+
+        case JSON_VARIANT_INTEGER:
+                return v->value.integer;
+
+        case JSON_VARIANT_UNSIGNED:
+                if (v->value.unsig <= INTMAX_MAX)
+                        return (intmax_t) v->value.unsig;
+
+                log_debug("Unsigned integer %ju requested as signed integer and out of range, returning 0.", v->value.unsig);
+                return 0;
+
+        case JSON_VARIANT_REAL: {
+                intmax_t converted;
+
+                converted = (intmax_t) v->value.real;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+                if ((long double) converted == v->value.real)
+#pragma GCC diagnostic pop
+                        return converted;
+
+                log_debug("Real %Lg requested as integer, and cannot be converted losslessly, returning 0.", v->value.real);
+                return 0;
+        }
+
+        default:
+                break;
+        }
+
+mismatch:
+        log_debug("Non-integer JSON variant requested as integer, returning 0.");
+        return 0;
+}
+
+uintmax_t json_variant_unsigned(JsonVariant *v) {
+        if (!v)
+                goto mismatch;
+        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER ||
+            v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
+            v == JSON_VARIANT_MAGIC_ZERO_REAL)
+                return 0;
+        if (json_variant_is_magic(v))
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_integer(v->reference);
+
+        switch (v->type) {
+
+        case JSON_VARIANT_INTEGER:
+                if (v->value.integer >= 0)
+                        return (uintmax_t) v->value.integer;
+
+                log_debug("Signed integer %ju requested as unsigned integer and out of range, returning 0.", v->value.integer);
+                return 0;
+
+        case JSON_VARIANT_UNSIGNED:
+                return v->value.unsig;
+
+        case JSON_VARIANT_REAL: {
+                uintmax_t converted;
+
+                converted = (uintmax_t) v->value.real;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+                if ((long double) converted == v->value.real)
+#pragma GCC diagnostic pop
+                        return converted;
+
+                log_debug("Real %Lg requested as unsigned integer, and cannot be converted losslessly, returning 0.", v->value.real);
+                return 0;
+        }
+
+        default:
+                break;
+        }
+
+mismatch:
+        log_debug("Non-integer JSON variant requested as unsigned, returning 0.");
+        return 0;
+}
+
+long double json_variant_real(JsonVariant *v) {
+        if (!v)
+                return 0.0;
+        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER ||
+            v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
+            v == JSON_VARIANT_MAGIC_ZERO_REAL)
+                return 0.0;
+        if (json_variant_is_magic(v))
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_real(v->reference);
+
+        switch (v->type) {
+
+        case JSON_VARIANT_REAL:
+                return v->value.real;
+
+        case JSON_VARIANT_INTEGER: {
+                long double converted;
+
+                converted = (long double) v->value.integer;
+
+                if ((intmax_t) converted == v->value.integer)
+                        return converted;
+
+                log_debug("Signed integer %ji requested as real, and cannot be converted losslessly, returning 0.", v->value.integer);
+                return 0.0;
+        }
+
+        case JSON_VARIANT_UNSIGNED: {
+                long double converted;
+
+                converted = (long double) v->value.unsig;
+
+                if ((uintmax_t) converted == v->value.unsig)
+                        return converted;
+
+                log_debug("Unsigned integer %ju requested as real, and cannot be converted losslessly, returning 0.", v->value.unsig);
+                return 0.0;
+        }
+
+        default:
+                break;
+        }
+
+mismatch:
+        log_debug("Non-integer JSON variant requested as integer, returning 0.");
+        return 0;
+}
+
+bool json_variant_is_negative(JsonVariant *v) {
+        if (!v)
+                goto mismatch;
+        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER ||
+            v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
+            v == JSON_VARIANT_MAGIC_ZERO_REAL)
+                return false;
+        if (json_variant_is_magic(v))
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_is_negative(v->reference);
+
+        /* This function is useful as checking whether numbers are negative is pretty complex since we have three types
+         * of numbers. And some JSON code (OCI for example) uses negative numbers to mark "not defined" numeric
+         * values. */
+
+        switch (v->type) {
+
+        case JSON_VARIANT_REAL:
+                return v->value.real < 0;
+
+        case JSON_VARIANT_INTEGER:
+                return v->value.integer < 0;
+
+        case JSON_VARIANT_UNSIGNED:
+                return false;
+
+        default:
+                break;
+        }
+
+mismatch:
+        log_debug("Non-integer JSON variant tested for negativity, returning false.");
+        return false;
+}
+
+JsonVariantType json_variant_type(JsonVariant *v) {
+
+        if (!v)
+                return _JSON_VARIANT_TYPE_INVALID;
+
+        if (v == JSON_VARIANT_MAGIC_TRUE || v == JSON_VARIANT_MAGIC_FALSE)
+                return JSON_VARIANT_BOOLEAN;
+
+        if (v == JSON_VARIANT_MAGIC_NULL)
+                return JSON_VARIANT_NULL;
+
+        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER)
+                return JSON_VARIANT_INTEGER;
+
+        if (v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED)
+                return JSON_VARIANT_UNSIGNED;
+
+        if (v == JSON_VARIANT_MAGIC_ZERO_REAL)
+                return JSON_VARIANT_REAL;
+
+        if (v == JSON_VARIANT_MAGIC_EMPTY_STRING)
+                return JSON_VARIANT_STRING;
+
+        if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY)
+                return JSON_VARIANT_ARRAY;
+
+        if (v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
+                return JSON_VARIANT_OBJECT;
+
+        return v->type;
+}
+
+bool json_variant_has_type(JsonVariant *v, JsonVariantType type) {
+        JsonVariantType rt;
+
+        v = json_variant_dereference(v);
+
+        rt = json_variant_type(v);
+        if (rt == type)
+                return true;
+
+        /* All three magic zeroes qualify as integer, unsigned and as real */
+        if ((v == JSON_VARIANT_MAGIC_ZERO_INTEGER || v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || v == JSON_VARIANT_MAGIC_ZERO_REAL) &&
+            IN_SET(type, JSON_VARIANT_INTEGER, JSON_VARIANT_UNSIGNED, JSON_VARIANT_REAL, JSON_VARIANT_NUMBER))
+                return true;
+
+        /* All other magic variant types are only equal to themselves */
+        if (json_variant_is_magic(v))
+                return false;
+
+        /* Handle the "number" pseudo type */
+        if (type == JSON_VARIANT_NUMBER)
+                return IN_SET(rt, JSON_VARIANT_INTEGER, JSON_VARIANT_UNSIGNED, JSON_VARIANT_REAL);
+
+        /* Integer conversions are OK in many cases */
+        if (rt == JSON_VARIANT_INTEGER && type == JSON_VARIANT_UNSIGNED)
+                return v->value.integer >= 0;
+        if (rt == JSON_VARIANT_UNSIGNED && type == JSON_VARIANT_INTEGER)
+                return v->value.unsig <= INTMAX_MAX;
+
+        /* Any integer that can be converted lossley to a real and back may also be considered a real */
+        if (rt == JSON_VARIANT_INTEGER && type == JSON_VARIANT_REAL)
+                return (intmax_t) (long double) v->value.integer == v->value.integer;
+        if (rt == JSON_VARIANT_UNSIGNED && type == JSON_VARIANT_REAL)
+                return (uintmax_t) (long double) v->value.unsig == v->value.unsig;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+        /* Any real that can be converted losslessly to an integer and back may also be considered an integer */
+        if (rt == JSON_VARIANT_REAL && type == JSON_VARIANT_INTEGER)
+                return (long double) (intmax_t) v->value.real == v->value.real;
+        if (rt == JSON_VARIANT_REAL && type == JSON_VARIANT_UNSIGNED)
+                return (long double) (uintmax_t) v->value.real == v->value.real;
+#pragma GCC diagnostic pop
+
+        return false;
+}
+
+size_t json_variant_elements(JsonVariant *v) {
+        if (!v)
+                return 0;
+        if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY ||
+            v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
+                return 0;
+        if (json_variant_is_magic(v))
+                goto mismatch;
+        if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_elements(v->reference);
+
+        return v->n_elements;
+
+mismatch:
+        log_debug("Number of elements in non-array/non-object JSON variant requested, returning 0.");
+        return 0;
+}
+
+JsonVariant *json_variant_by_index(JsonVariant *v, size_t idx) {
+        if (!v)
+                return NULL;
+        if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY ||
+            v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
+                return NULL;
+        if (json_variant_is_magic(v))
+                goto mismatch;
+        if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_by_index(v->reference, idx);
+        if (idx >= v->n_elements)
+                return NULL;
+
+        return json_variant_conservative_normalize(v + 1 + idx);
+
+mismatch:
+        log_debug("Element in non-array/non-object JSON variant requested by index, returning NULL.");
+        return NULL;
+}
+
+JsonVariant *json_variant_by_key_full(JsonVariant *v, const char *key, JsonVariant **ret_key) {
+        size_t i;
+
+        if (!v)
+                goto not_found;
+        if (!key)
+                goto not_found;
+        if (v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
+                goto not_found;
+        if (json_variant_is_magic(v))
+                goto mismatch;
+        if (v->type != JSON_VARIANT_OBJECT)
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_by_key(v->reference, key);
+
+        for (i = 0; i < v->n_elements; i += 2) {
+                JsonVariant *p;
+
+                p = json_variant_dereference(v + 1 + i);
+
+                if (!json_variant_has_type(p, JSON_VARIANT_STRING))
+                        continue;
+
+                if (streq(json_variant_string(p), key)) {
+
+                        if (ret_key)
+                                *ret_key = json_variant_conservative_normalize(v + 1 + i);
+
+                        return json_variant_conservative_normalize(v + 1 + i + 1);
+                }
+        }
+
+not_found:
+        if (ret_key)
+                *ret_key = NULL;
+
+        return NULL;
+
+mismatch:
+        log_debug("Element in non-object JSON variant requested by key, returning NULL.");
+        if (ret_key)
+                *ret_key = NULL;
+
+        return NULL;
+}
+
+JsonVariant *json_variant_by_key(JsonVariant *v, const char *key) {
+        return json_variant_by_key_full(v, key, NULL);
+}
+
+bool json_variant_equal(JsonVariant *a, JsonVariant *b) {
+        JsonVariantType t;
+
+        a = json_variant_normalize(a);
+        b = json_variant_normalize(b);
+
+        if (a == b)
+                return true;
+
+        t = json_variant_type(a);
+        if (!json_variant_has_type(b, t))
+                return false;
+
+        switch (t) {
+
+        case JSON_VARIANT_STRING:
+                return streq(json_variant_string(a), json_variant_string(b));
+
+        case JSON_VARIANT_INTEGER:
+                return json_variant_integer(a) == json_variant_integer(b);
+
+        case JSON_VARIANT_UNSIGNED:
+                return json_variant_unsigned(a) == json_variant_unsigned(b);
+
+        case JSON_VARIANT_REAL:
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+                return json_variant_real(a) == json_variant_real(b);
+#pragma GCC diagnostic pop
+
+        case JSON_VARIANT_BOOLEAN:
+                return json_variant_boolean(a) == json_variant_boolean(b);
+
+        case JSON_VARIANT_NULL:
+                return true;
+
+        case JSON_VARIANT_ARRAY: {
+                size_t i, n;
+
+                n = json_variant_elements(a);
+                if (n != json_variant_elements(b))
+                        return false;
+
+                for (i = 0; i < n; i++) {
+                        if (!json_variant_equal(json_variant_by_index(a, i), json_variant_by_index(b, i)))
+                                return false;
+                }
+
+                return true;
+        }
+
+        case JSON_VARIANT_OBJECT: {
+                size_t i, n;
+
+                n = json_variant_elements(a);
+                if (n != json_variant_elements(b))
+                        return false;
+
+                /* Iterate through all keys in 'a' */
+                for (i = 0; i < n; i += 2) {
+                        bool found = false;
+                        size_t j;
+
+                        /* Match them against all keys in 'b' */
+                        for (j = 0; j < n; j += 2) {
+                                JsonVariant *key_b;
+
+                                key_b = json_variant_by_index(b, j);
+
+                                /* During the first iteration unmark everything */
+                                if (i == 0)
+                                        key_b->is_marked = false;
+                                else if (key_b->is_marked) /* In later iterations if we already marked something, don't bother with it again */
+                                        continue;
+
+                                if (found)
+                                        continue;
+
+                                if (json_variant_equal(json_variant_by_index(a, i), key_b) &&
+                                    json_variant_equal(json_variant_by_index(a, i+1), json_variant_by_index(b, j+1))) {
+                                        /* Key and values match! */
+                                        key_b->is_marked = found = true;
+
+                                        /* In the first iteration we continue the inner loop since we want to mark
+                                         * everything, otherwise exit the loop quickly after we found what we were
+                                         * looking for. */
+                                        if (i != 0)
+                                                break;
+                                }
+                        }
+
+                        if (!found)
+                                return false;
+                }
+
+                return true;
+        }
+
+        default:
+                assert_not_reached("Unknown variant type.");
+        }
+}
+
+int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column) {
+        assert_return(v, -EINVAL);
+
+        if (ret_source)
+                *ret_source = json_variant_is_magic(v) || !v->source ? NULL : v->source->name;
+
+        if (ret_line)
+                *ret_line = json_variant_is_magic(v) ? 0 : v->line;
+
+        if (ret_column)
+                *ret_column = json_variant_is_magic(v) ? 0 : v->column;
+
+        return 0;
+}
+
+static int print_source(FILE *f, JsonVariant *v, unsigned flags, bool whitespace) {
+        size_t w, k;
+
+        if (!FLAGS_SET(flags, JSON_FORMAT_SOURCE|JSON_FORMAT_PRETTY))
+                return 0;
+
+        if (json_variant_is_magic(v))
+                return 0;
+
+        if (!v->source && v->line == 0 && v->column == 0)
+                return 0;
+
+        /* The max width we need to format the line numbers for this source file */
+        w = (v->source && v->source->max_line > 0) ?
+                DECIMAL_STR_WIDTH(v->source->max_line) :
+                DECIMAL_STR_MAX(unsigned)-1;
+        k = (v->source && v->source->max_column > 0) ?
+                DECIMAL_STR_WIDTH(v->source->max_column) :
+                DECIMAL_STR_MAX(unsigned) -1;
+
+        if (whitespace) {
+                size_t i, n;
+
+                n = 1 + (v->source ? strlen(v->source->name) : 0) +
+                        ((v->source && (v->line > 0 || v->column > 0)) ? 1 : 0) +
+                        (v->line > 0 ? w : 0) +
+                        (((v->source || v->line > 0) && v->column > 0) ? 1 : 0) +
+                        (v->column > 0 ? k : 0) +
+                        2;
+
+                for (i = 0; i < n; i++)
+                        fputc(' ', f);
+        } else {
+                fputc('[', f);
+
+                if (v->source)
+                        fputs(v->source->name, f);
+                if (v->source && (v->line > 0 || v->column > 0))
+                        fputc(':', f);
+                if (v->line > 0)
+                        fprintf(f, "%*u", (int) w, v->line);
+                if ((v->source || v->line > 0) || v->column > 0)
+                        fputc(':', f);
+                if (v->column > 0)
+                        fprintf(f, "%*u", (int) k, v->column);
+
+                fputc(']', f);
+                fputc(' ', f);
+        }
+
+        return 0;
+}
+
+static int json_format(FILE *f, JsonVariant *v, unsigned flags, const char *prefix) {
+        int r;
+
+        assert(f);
+        assert(v);
+
+        switch (json_variant_type(v)) {
+
+        case JSON_VARIANT_REAL: {
+                locale_t loc;
+
+                loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
+                if (loc == (locale_t) 0)
+                        return -errno;
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_HIGHLIGHT_BLUE, f);
+
+                fprintf(f, "%.*Le", DECIMAL_DIG, json_variant_real(v));
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_NORMAL, f);
+
+                freelocale(loc);
+                break;
+        }
+
+        case JSON_VARIANT_INTEGER:
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_HIGHLIGHT_BLUE, f);
+
+                fprintf(f, "%" PRIdMAX, json_variant_integer(v));
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_NORMAL, f);
+                break;
+
+        case JSON_VARIANT_UNSIGNED:
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_HIGHLIGHT_BLUE, f);
+
+                fprintf(f, "%" PRIuMAX, json_variant_unsigned(v));
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_NORMAL, f);
+                break;
+
+        case JSON_VARIANT_BOOLEAN:
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_HIGHLIGHT, f);
+
+                if (json_variant_boolean(v))
+                        fputs("true", f);
+                else
+                        fputs("false", f);
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_NORMAL, f);
+
+                break;
+
+        case JSON_VARIANT_NULL:
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_HIGHLIGHT, f);
+
+                fputs("null", f);
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_NORMAL, f);
+                break;
+
+        case JSON_VARIANT_STRING: {
+                const char *q;
+
+                fputc('"', f);
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_GREEN, f);
+
+                for (q = json_variant_string(v); *q; q++) {
+
+                        switch (*q) {
+
+                        case '"':
+                                fputs("\\\"", f);
+                                break;
+
+                        case '\\':
+                                fputs("\\\\", f);
+                                break;
+
+                        case '/':
+                                fputs("\\/", f);
+                                break;
+
+                        case '\b':
+                                fputs("\\b", f);
+                                break;
+
+                        case '\f':
+                                fputs("\\f", f);
+                                break;
+
+                        case '\n':
+                                fputs("\\n", f);
+                                break;
+
+                        case '\r':
+                                fputs("\\r", f);
+                                break;
+
+                        case '\t':
+                                fputs("\\t", f);
+                                break;
+
+                        default:
+                                if (*q >= 0 && *q < ' ')
+                                        fprintf(f, "\\u%04x", *q);
+                                else
+                                        fputc(*q, f);
+                                break;
+                        }
+                }
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_NORMAL, f);
+
+                fputc('"', f);
+                break;
+        }
+
+        case JSON_VARIANT_ARRAY: {
+                size_t i, n;
+
+                n = json_variant_elements(v);
+
+                if (n == 0)
+                        fputs("[]", f);
+                else {
+                        const char *prefix2;
+
+                        if (flags & JSON_FORMAT_PRETTY) {
+                                prefix2 = strjoina(strempty(prefix), "\t");
+                                fputs("[\n", f);
+                        } else {
+                                prefix2 = strempty(prefix);
+                                fputc('[', f);
+                        }
+
+                        for (i = 0; i < n; i++) {
+                                JsonVariant *e;
+
+                                assert_se(e = json_variant_by_index(v, i));
+
+                                if (i > 0) {
+                                        if (flags & JSON_FORMAT_PRETTY)
+                                                fputs(",\n", f);
+                                        else
+                                                fputc(',', f);
+                                }
+
+                                if (flags & JSON_FORMAT_PRETTY) {
+                                        print_source(f, e, flags, false);
+                                        fputs(prefix2, f);
+                                }
+
+                                r = json_format(f, e, flags, prefix2);
+                                if (r < 0)
+                                        return r;
+                        }
+
+                        if (flags & JSON_FORMAT_PRETTY) {
+                                fputc('\n', f);
+                                print_source(f, v, flags, true);
+                                fputs(strempty(prefix), f);
+                        }
+
+                        fputc(']', f);
+                }
+                break;
+        }
+
+        case JSON_VARIANT_OBJECT: {
+                size_t i, n;
+
+                n = json_variant_elements(v);
+
+                if (n == 0)
+                        fputs("{}", f);
+                else {
+                        const char *prefix2;
+
+                        if (flags & JSON_FORMAT_PRETTY) {
+                                prefix2 = strjoina(strempty(prefix), "\t");
+                                fputs("{\n", f);
+                        } else {
+                                prefix2 = strempty(prefix);
+                                fputc('{', f);
+                        }
+
+                        for (i = 0; i < n; i += 2) {
+                                JsonVariant *e;
+
+                                e = json_variant_by_index(v, i);
+
+                                if (i > 0) {
+                                        if (flags & JSON_FORMAT_PRETTY)
+                                                fputs(",\n", f);
+                                        else
+                                                fputc(',', f);
+                                }
+
+                                if (flags & JSON_FORMAT_PRETTY) {
+                                        print_source(f, e, flags, false);
+                                        fputs(prefix2, f);
+                                }
+
+                                r = json_format(f, e, flags, prefix2);
+                                if (r < 0)
+                                        return r;
+
+                                fputs(flags & JSON_FORMAT_PRETTY ? " : " : ":", f);
+
+                                r = json_format(f, json_variant_by_index(v, i+1), flags, prefix2);
+                                if (r < 0)
+                                        return r;
+                        }
+
+                        if (flags & JSON_FORMAT_PRETTY) {
+                                fputc('\n', f);
+                                print_source(f, v, flags, true);
+                                fputs(strempty(prefix), f);
+                        }
+
+                        fputc('}', f);
+                }
+                break;
+        }
+
+        default:
+                assert_not_reached("Unexpected variant type.");
+        }
+
+        return 0;
+}
+
+int json_variant_format(JsonVariant *v, unsigned flags, char **ret) {
+        _cleanup_free_ char *s = NULL;
+        size_t sz = 0;
+        int r;
+
+        assert_return(v, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        {
+                _cleanup_fclose_ FILE *f = NULL;
+
+                f = open_memstream(&s, &sz);
+                if (!f)
+                        return -ENOMEM;
+
+                (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+                json_variant_dump(v, flags, f, NULL);
+
+                r = fflush_and_check(f);
+        }
+        if (r < 0)
+                return r;
+
+        assert(s);
+        *ret = TAKE_PTR(s);
+
+        return (int) sz;
+}
+
+void json_variant_dump(JsonVariant *v, unsigned flags, FILE *f, const char *prefix) {
+        if (!v)
+                return;
+
+        if (!f)
+                f = stdout;
+
+        print_source(f, v, flags, false);
+
+        if (flags & JSON_FORMAT_SSE)
+                fputs("data: ", f);
+        if (flags & JSON_FORMAT_SEQ)
+                fputc('\x1e', f); /* ASCII Record Separator */
+
+        json_format(f, v, flags, prefix);
+
+        if (flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_SEQ|JSON_FORMAT_SSE|JSON_FORMAT_NEWLINE))
+                fputc('\n', f);
+        if (flags & JSON_FORMAT_SSE)
+                fputc('\n', f); /* In case of SSE add a second newline */
+}
+
+static int json_variant_copy(JsonVariant **nv, JsonVariant *v) {
+        JsonVariantType t;
+        JsonVariant *c;
+        JsonValue value;
+        const void *source;
+        size_t k;
+
+        assert(nv);
+        assert(v);
+
+        /* Let's copy the simple types literally, and the larger types by references */
+        t = json_variant_type(v);
+        switch (t) {
+        case JSON_VARIANT_INTEGER:
+                k = sizeof(intmax_t);
+                value.integer = json_variant_integer(v);
+                source = &value;
+                break;
+
+        case JSON_VARIANT_UNSIGNED:
+                k = sizeof(uintmax_t);
+                value.unsig = json_variant_unsigned(v);
+                source = &value;
+                break;
+
+        case JSON_VARIANT_REAL:
+                k = sizeof(long double);
+                value.real = json_variant_real(v);
+                source = &value;
+                break;
+
+        case JSON_VARIANT_BOOLEAN:
+                k = sizeof(bool);
+                value.boolean = json_variant_boolean(v);
+                source = &value;
+                break;
+
+        case JSON_VARIANT_NULL:
+                k = 0;
+                source = NULL;
+                break;
+
+        case JSON_VARIANT_STRING:
+                source = json_variant_string(v);
+                k = strnlen(source, INLINE_STRING_MAX + 1);
+                if (k <= INLINE_STRING_MAX) {
+                        k ++;
+                        break;
+                }
+
+                _fallthrough_;
+
+        default:
+                /* Everything else copy by reference */
+
+                c = malloc0(offsetof(JsonVariant, reference) + sizeof(JsonVariant*));
+                if (!c)
+                        return -ENOMEM;
+
+                c->n_ref = 1;
+                c->type = t;
+                c->is_reference = true;
+                c->reference = json_variant_ref(json_variant_normalize(v));
+
+                *nv = c;
+                return 0;
+        }
+
+        c = malloc0(offsetof(JsonVariant, value) + k);
+        if (!c)
+                return -ENOMEM;
+
+        c->n_ref = 1;
+        c->type = t;
+
+        memcpy_safe(&c->value, source, k);
+
+        *nv = c;
+        return 0;
+}
+
+static bool json_single_ref(JsonVariant *v) {
+
+        /* Checks whether the caller is the single owner of the object, i.e. can get away with changing it */
+
+        if (json_variant_is_magic(v))
+                return false;
+
+        if (v->is_embedded)
+                return json_single_ref(v->parent);
+
+        assert(v->n_ref > 0);
+        return v->n_ref == 1;
+}
+
+static int json_variant_set_source(JsonVariant **v, JsonSource *source, unsigned line, unsigned column) {
+        JsonVariant *w;
+        int r;
+
+        assert(v);
+
+        /* Patch in source and line/column number. Tries to do this in-place if the caller is the sole referencer of
+         * the object. If not, allocates a new object, possibly a surrogate for the original one */
+
+        if (!*v)
+                return 0;
+
+        if (source && line > source->max_line)
+                source->max_line = line;
+        if (source && column > source->max_column)
+                source->max_column = column;
+
+        if (json_variant_is_magic(*v)) {
+
+                if (!source && line == 0 && column == 0)
+                        return 0;
+
+        } else {
+                if (json_source_equal((*v)->source, source) &&
+                    (*v)->line == line &&
+                    (*v)->column == column)
+                        return 0;
+
+                if (json_single_ref(*v)) { /* Sole reference? */
+                        json_source_unref((*v)->source);
+                        (*v)->source = json_source_ref(source);
+                        (*v)->line = line;
+                        (*v)->column = column;
+                        return 1;
+                }
+        }
+
+        r = json_variant_copy(&w, *v);
+        if (r < 0)
+                return r;
+
+        assert(!json_variant_is_magic(w));
+        assert(!w->is_embedded);
+        assert(w->n_ref == 1);
+        assert(!w->source);
+
+        w->source = json_source_ref(source);
+        w->line = line;
+        w->column = column;
+
+        json_variant_unref(*v);
+        *v = w;
+
+        return 1;
+}
+
+static void inc_lines_columns(unsigned *line, unsigned *column, const char *s, size_t n) {
+        assert(line);
+        assert(column);
+        assert(s || n == 0);
+
+        while (n > 0) {
+
+                if (*s == '\n') {
+                        (*line)++;
+                        *column = 1;
+                } else if (*s >= 0 && *s < 127) /* Process ASCII chars quickly */
+                        (*column)++;
+                else {
+                        int w;
+
+                        w = utf8_encoded_valid_unichar(s);
+                        if (w < 0) /* count invalid unichars as normal characters */
+                                w = 1;
+                        else if ((size_t) w > n) /* never read more than the specified number of characters */
+                                w = (int) n;
+
+                        (*column)++;
+
+                        s += w;
+                        n -= w;
+                        continue;
+                }
+
+                s++;
+                n--;
+        }
+}
+
+static int unhex_ucs2(const char *c, uint16_t *ret) {
+        int aa, bb, cc, dd;
+        uint16_t x;
+
+        assert(c);
+        assert(ret);
+
+        aa = unhexchar(c[0]);
+        if (aa < 0)
+                return -EINVAL;
+
+        bb = unhexchar(c[1]);
+        if (bb < 0)
+                return -EINVAL;
+
+        cc = unhexchar(c[2]);
+        if (cc < 0)
+                return -EINVAL;
+
+        dd = unhexchar(c[3]);
+        if (dd < 0)
+                return -EINVAL;
+
+        x =     ((uint16_t) aa << 12) |
+                ((uint16_t) bb << 8) |
+                ((uint16_t) cc << 4) |
+                ((uint16_t) dd);
+
+        if (x <= 0)
+                return -EINVAL;
+
+        *ret = x;
+
+        return 0;
+}
+
+static int json_parse_string(const char **p, char **ret) {
+        _cleanup_free_ char *s = NULL;
+        size_t n = 0, allocated = 0;
+        const char *c;
+
+        assert(p);
+        assert(*p);
+        assert(ret);
+
+        c = *p;
+
+        if (*c != '"')
+                return -EINVAL;
+
+        c++;
+
+        for (;;) {
+                int len;
+
+                /* Check for EOF */
+                if (*c == 0)
+                        return -EINVAL;
+
+                /* Check for control characters 0x00..0x1f */
+                if (*c > 0 && *c < ' ')
+                        return -EINVAL;
+
+                /* Check for control character 0x7f */
+                if (*c == 0x7f)
+                        return -EINVAL;
+
+                if (*c == '"') {
+                        if (!s) {
+                                s = strdup("");
+                                if (!s)
+                                        return -ENOMEM;
+                        } else
+                                s[n] = 0;
+
+                        *p = c + 1;
+
+                        *ret = s;
+                        s = NULL;
+                        return JSON_TOKEN_STRING;
+                }
+
+                if (*c == '\\') {
+                        char ch = 0;
+                        c++;
+
+                        if (*c == 0)
+                                return -EINVAL;
+
+                        if (IN_SET(*c, '"', '\\', '/'))
+                                ch = *c;
+                        else if (*c == 'b')
+                                ch = '\b';
+                        else if (*c == 'f')
+                                ch = '\f';
+                        else if (*c == 'n')
+                                ch = '\n';
+                        else if (*c == 'r')
+                                ch = '\r';
+                        else if (*c == 't')
+                                ch = '\t';
+                        else if (*c == 'u') {
+                                char16_t x;
+                                int r;
+
+                                r = unhex_ucs2(c + 1, &x);
+                                if (r < 0)
+                                        return r;
+
+                                c += 5;
+
+                                if (!GREEDY_REALLOC(s, allocated, n + 5))
+                                        return -ENOMEM;
+
+                                if (!utf16_is_surrogate(x))
+                                        n += utf8_encode_unichar(s + n, (char32_t) x);
+                                else if (utf16_is_trailing_surrogate(x))
+                                        return -EINVAL;
+                                else {
+                                        char16_t y;
+
+                                        if (c[0] != '\\' || c[1] != 'u')
+                                                return -EINVAL;
+
+                                        r = unhex_ucs2(c + 2, &y);
+                                        if (r < 0)
+                                                return r;
+
+                                        c += 6;
+
+                                        if (!utf16_is_trailing_surrogate(y))
+                                                return -EINVAL;
+
+                                        n += utf8_encode_unichar(s + n, utf16_surrogate_pair_to_unichar(x, y));
+                                }
+
+                                continue;
+                        } else
+                                return -EINVAL;
+
+                        if (!GREEDY_REALLOC(s, allocated, n + 2))
+                                return -ENOMEM;
+
+                        s[n++] = ch;
+                        c ++;
+                        continue;
+                }
+
+                len = utf8_encoded_valid_unichar(c);
+                if (len < 0)
+                        return len;
+
+                if (!GREEDY_REALLOC(s, allocated, n + len + 1))
+                        return -ENOMEM;
+
+                memcpy(s + n, c, len);
+                n += len;
+                c += len;
+        }
+}
+
+static int json_parse_number(const char **p, JsonValue *ret) {
+        bool negative = false, exponent_negative = false, is_real = false;
+        long double x = 0.0, y = 0.0, exponent = 0.0, shift = 1.0;
+        intmax_t i = 0;
+        uintmax_t u = 0;
+        const char *c;
+
+        assert(p);
+        assert(*p);
+        assert(ret);
+
+        c = *p;
+
+        if (*c == '-') {
+                negative = true;
+                c++;
+        }
+
+        if (*c == '0')
+                c++;
+        else {
+                if (!strchr("123456789", *c) || *c == 0)
+                        return -EINVAL;
+
+                do {
+                        if (!is_real) {
+                                if (negative) {
+
+                                        if (i < INTMAX_MIN / 10) /* overflow */
+                                                is_real = true;
+                                        else {
+                                                intmax_t t = 10 * i;
+
+                                                if (t < INTMAX_MIN + (*c - '0')) /* overflow */
+                                                        is_real = true;
+                                                else
+                                                        i = t - (*c - '0');
+                                        }
+                                } else {
+                                        if (u > UINTMAX_MAX / 10) /* overflow */
+                                                is_real = true;
+                                        else {
+                                                uintmax_t t = 10 * u;
+
+                                                if (t > UINTMAX_MAX - (*c - '0')) /* overflow */
+                                                        is_real = true;
+                                                else
+                                                        u = t + (*c - '0');
+                                        }
+                                }
+                        }
+
+                        x = 10.0 * x + (*c - '0');
+
+                        c++;
+                } while (strchr("0123456789", *c) && *c != 0);
+        }
+
+        if (*c == '.') {
+                is_real = true;
+                c++;
+
+                if (!strchr("0123456789", *c) || *c == 0)
+                        return -EINVAL;
+
+                do {
+                        y = 10.0 * y + (*c - '0');
+                        shift = 10.0 * shift;
+                        c++;
+                } while (strchr("0123456789", *c) && *c != 0);
+        }
+
+        if (*c == 'e' || *c == 'E') {
+                is_real = true;
+                c++;
+
+                if (*c == '-') {
+                        exponent_negative = true;
+                        c++;
+                } else if (*c == '+')
+                        c++;
+
+                if (!strchr("0123456789", *c) || *c == 0)
+                        return -EINVAL;
+
+                do {
+                        exponent = 10.0 * exponent + (*c - '0');
+                        c++;
+                } while (strchr("0123456789", *c) && *c != 0);
+        }
+
+        *p = c;
+
+        if (is_real) {
+                ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * exp10l((exponent_negative ? -1.0 : 1.0) * exponent);
+                return JSON_TOKEN_REAL;
+        } else if (negative) {
+                ret->integer = i;
+                return JSON_TOKEN_INTEGER;
+        } else  {
+                ret->unsig = u;
+                return JSON_TOKEN_UNSIGNED;
+        }
+}
+
+int json_tokenize(
+                const char **p,
+                char **ret_string,
+                JsonValue *ret_value,
+                unsigned *ret_line,   /* 'ret_line' returns the line at the beginning of this token */
+                unsigned *ret_column,
+                void **state,
+                unsigned *line,       /* 'line' is used as a line state, it always reflect the line we are at after the token was read */
+                unsigned *column) {
+
+        unsigned start_line, start_column;
+        const char *start, *c;
+        size_t n;
+        int t, r;
+
+        enum {
+                STATE_NULL,
+                STATE_VALUE,
+                STATE_VALUE_POST,
+        };
+
+        assert(p);
+        assert(*p);
+        assert(ret_string);
+        assert(ret_value);
+        assert(ret_line);
+        assert(ret_column);
+        assert(line);
+        assert(column);
+        assert(state);
+
+        t = PTR_TO_INT(*state);
+        if (t == STATE_NULL) {
+                *line = 1;
+                *column = 1;
+                t = STATE_VALUE;
+        }
+
+        /* Skip over the whitespace */
+        n = strspn(*p, WHITESPACE);
+        inc_lines_columns(line, column, *p, n);
+        c = *p + n;
+
+        /* Remember where we started processing this token */
+        start = c;
+        start_line = *line;
+        start_column = *column;
+
+        if (*c == 0) {
+                *ret_string = NULL;
+                *ret_value = JSON_VALUE_NULL;
+                r = JSON_TOKEN_END;
+                goto finish;
+        }
+
+        switch (t) {
+
+        case STATE_VALUE:
+
+                if (*c == '{') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE);
+                        r = JSON_TOKEN_OBJECT_OPEN;
+                        goto null_return;
+
+                } else if (*c == '}') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        r = JSON_TOKEN_OBJECT_CLOSE;
+                        goto null_return;
+
+                } else if (*c == '[') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE);
+                        r = JSON_TOKEN_ARRAY_OPEN;
+                        goto null_return;
+
+                } else if (*c == ']') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        r = JSON_TOKEN_ARRAY_CLOSE;
+                        goto null_return;
+
+                } else if (*c == '"') {
+
+                        r = json_parse_string(&c, ret_string);
+                        if (r < 0)
+                                return r;
+
+                        *ret_value = JSON_VALUE_NULL;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        goto finish;
+
+                } else if (strchr("-0123456789", *c)) {
+
+                        r = json_parse_number(&c, ret_value);
+                        if (r < 0)
+                                return r;
+
+                        *ret_string = NULL;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        goto finish;
+
+                } else if (startswith(c, "true")) {
+                        *ret_string = NULL;
+                        ret_value->boolean = true;
+                        c += 4;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        r = JSON_TOKEN_BOOLEAN;
+                        goto finish;
+
+                } else if (startswith(c, "false")) {
+                        *ret_string = NULL;
+                        ret_value->boolean = false;
+                        c += 5;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        r = JSON_TOKEN_BOOLEAN;
+                        goto finish;
+
+                } else if (startswith(c, "null")) {
+                        *ret_string = NULL;
+                        *ret_value = JSON_VALUE_NULL;
+                        c += 4;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        r = JSON_TOKEN_NULL;
+                        goto finish;
+
+                }
+
+                return -EINVAL;
+
+        case STATE_VALUE_POST:
+
+                if (*c == ':') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE);
+                        r = JSON_TOKEN_COLON;
+                        goto null_return;
+
+                } else if (*c == ',') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE);
+                        r = JSON_TOKEN_COMMA;
+                        goto null_return;
+
+                } else if (*c == '}') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        r = JSON_TOKEN_OBJECT_CLOSE;
+                        goto null_return;
+
+                } else if (*c == ']') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        r = JSON_TOKEN_ARRAY_CLOSE;
+                        goto null_return;
+                }
+
+                return -EINVAL;
+
+        default:
+                assert_not_reached("Unexpected tokenizer state");
+        }
+
+null_return:
+        *ret_string = NULL;
+        *ret_value = JSON_VALUE_NULL;
+
+finish:
+        inc_lines_columns(line, column, start, c - start);
+        *p = c;
+
+        *ret_line = start_line;
+        *ret_column = start_column;
+
+        return r;
+}
+
+typedef enum JsonExpect {
+        /* The following values are used by json_parse() */
+        EXPECT_TOPLEVEL,
+        EXPECT_END,
+        EXPECT_OBJECT_FIRST_KEY,
+        EXPECT_OBJECT_NEXT_KEY,
+        EXPECT_OBJECT_COLON,
+        EXPECT_OBJECT_VALUE,
+        EXPECT_OBJECT_COMMA,
+        EXPECT_ARRAY_FIRST_ELEMENT,
+        EXPECT_ARRAY_NEXT_ELEMENT,
+        EXPECT_ARRAY_COMMA,
+
+        /* And these are used by json_build() */
+        EXPECT_ARRAY_ELEMENT,
+        EXPECT_OBJECT_KEY,
+} JsonExpect;
+
+typedef struct JsonStack {
+        JsonExpect expect;
+        JsonVariant **elements;
+        size_t n_elements, n_elements_allocated;
+        unsigned line_before;
+        unsigned column_before;
+} JsonStack;
+
+static void json_stack_release(JsonStack *s) {
+        assert(s);
+
+        json_variant_unref_many(s->elements, s->n_elements);
+        s->elements = mfree(s->elements);
+}
+
+static int json_parse_internal(
+                const char **input,
+                JsonSource *source,
+                JsonVariant **ret,
+                unsigned *line,
+                unsigned *column,
+                bool continue_end) {
+
+        size_t n_stack = 1, n_stack_allocated = 0, i;
+        unsigned line_buffer = 0, column_buffer = 0;
+        void *tokenizer_state = NULL;
+        JsonStack *stack = NULL;
+        const char *p;
+        int r;
+
+        assert_return(input, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        p = *input;
+
+        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack))
+                return -ENOMEM;
+
+        stack[0] = (JsonStack) {
+                .expect = EXPECT_TOPLEVEL,
+        };
+
+        if (!line)
+                line = &line_buffer;
+        if (!column)
+                column = &column_buffer;
+
+        for (;;) {
+                _cleanup_free_ char *string = NULL;
+                unsigned line_token, column_token;
+                JsonVariant *add = NULL;
+                JsonStack *current;
+                JsonValue value;
+                int token;
+
+                assert(n_stack > 0);
+                current = stack + n_stack - 1;
+
+                if (continue_end && current->expect == EXPECT_END)
+                        goto done;
+
+                token = json_tokenize(&p, &string, &value, &line_token, &column_token, &tokenizer_state, line, column);
+                if (token < 0) {
+                        r = token;
+                        goto finish;
+                }
+
+                switch (token) {
+
+                case JSON_TOKEN_END:
+                        if (current->expect != EXPECT_END) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        assert(current->n_elements == 1);
+                        assert(n_stack == 1);
+                        goto done;
+
+                case JSON_TOKEN_COLON:
+
+                        if (current->expect != EXPECT_OBJECT_COLON) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        current->expect = EXPECT_OBJECT_VALUE;
+                        break;
+
+                case JSON_TOKEN_COMMA:
+
+                        if (current->expect == EXPECT_OBJECT_COMMA)
+                                current->expect = EXPECT_OBJECT_NEXT_KEY;
+                        else if (current->expect == EXPECT_ARRAY_COMMA)
+                                current->expect = EXPECT_ARRAY_NEXT_ELEMENT;
+                        else {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        break;
+
+                case JSON_TOKEN_OBJECT_OPEN:
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+                        current = stack + n_stack - 1;
+
+                        /* Prepare the expect for when we return from the child */
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        stack[n_stack++] = (JsonStack) {
+                                .expect = EXPECT_OBJECT_FIRST_KEY,
+                                .line_before = line_token,
+                                .column_before = column_token,
+                        };
+
+                        current = stack + n_stack - 1;
+                        break;
+
+                case JSON_TOKEN_OBJECT_CLOSE:
+                        if (!IN_SET(current->expect, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_COMMA)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        assert(n_stack > 1);
+
+                        r = json_variant_new_object(&add, current->elements, current->n_elements);
+                        if (r < 0)
+                                goto finish;
+
+                        line_token = current->line_before;
+                        column_token = current->column_before;
+
+                        json_stack_release(current);
+                        n_stack--, current--;
+
+                        break;
+
+                case JSON_TOKEN_ARRAY_OPEN:
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+                        current = stack + n_stack - 1;
+
+                        /* Prepare the expect for when we return from the child */
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        stack[n_stack++] = (JsonStack) {
+                                .expect = EXPECT_ARRAY_FIRST_ELEMENT,
+                                .line_before = line_token,
+                                .column_before = column_token,
+                        };
+
+                        break;
+
+                case JSON_TOKEN_ARRAY_CLOSE:
+                        if (!IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_COMMA)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        assert(n_stack > 1);
+
+                        r = json_variant_new_array(&add, current->elements, current->n_elements);
+                        if (r < 0)
+                                goto finish;
+
+                        line_token = current->line_before;
+                        column_token = current->column_before;
+
+                        json_stack_release(current);
+                        n_stack--, current--;
+                        break;
+
+                case JSON_TOKEN_STRING:
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_NEXT_KEY, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        r = json_variant_new_string(&add, string);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (IN_SET(current->expect, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_NEXT_KEY))
+                                current->expect = EXPECT_OBJECT_COLON;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        break;
+
+                case JSON_TOKEN_REAL:
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        r = json_variant_new_real(&add, value.real);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        break;
+
+                case JSON_TOKEN_INTEGER:
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        r = json_variant_new_integer(&add, value.integer);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        break;
+
+                case JSON_TOKEN_UNSIGNED:
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        r = json_variant_new_unsigned(&add, value.unsig);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        break;
+
+                case JSON_TOKEN_BOOLEAN:
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        r = json_variant_new_boolean(&add, value.boolean);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        break;
+
+                case JSON_TOKEN_NULL:
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        r = json_variant_new_null(&add);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        break;
+
+                default:
+                        assert_not_reached("Unexpected token");
+                }
+
+                if (add) {
+                        (void) json_variant_set_source(&add, source, line_token, column_token);
+
+                        if (!GREEDY_REALLOC(current->elements, current->n_elements_allocated, current->n_elements + 1)) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        current->elements[current->n_elements++] = add;
+                }
+        }
+
+done:
+        assert(n_stack == 1);
+        assert(stack[0].n_elements == 1);
+
+        *ret = json_variant_ref(stack[0].elements[0]);
+        *input = p;
+        r = 0;
+
+finish:
+        for (i = 0; i < n_stack; i++)
+                json_stack_release(stack + i);
+
+        free(stack);
+
+        return r;
+}
+
+int json_parse(const char *input, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
+        return json_parse_internal(&input, NULL, ret, ret_line, ret_column, false);
+}
+
+int json_parse_continue(const char **p, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
+        return json_parse_internal(p, NULL, ret, ret_line, ret_column, true);
+}
+
+int json_parse_file(FILE *f, const char *path, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
+        _cleanup_(json_source_unrefp) JsonSource *source = NULL;
+        _cleanup_free_ char *text = NULL;
+        const char *p;
+        int r;
+
+        if (f)
+                r = read_full_stream(f, &text, NULL);
+        else if (path)
+                r = read_full_file(path, &text, NULL);
+        else
+                return -EINVAL;
+        if (r < 0)
+                return r;
+
+        if (path) {
+                source = json_source_new(path);
+                if (!source)
+                        return -ENOMEM;
+        }
+
+        p = text;
+        return json_parse_internal(&p, source, ret, ret_line, ret_column, false);
+}
+
+int json_buildv(JsonVariant **ret, va_list ap) {
+        JsonStack *stack = NULL;
+        size_t n_stack = 1, n_stack_allocated = 0, i;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack))
+                return -ENOMEM;
+
+        stack[0] = (JsonStack) {
+                .expect = EXPECT_TOPLEVEL,
+        };
+
+        for (;;) {
+                JsonVariant *add = NULL;
+                JsonStack *current;
+                int command;
+
+                assert(n_stack > 0);
+                current = stack + n_stack - 1;
+
+                if (current->expect == EXPECT_END)
+                        goto done;
+
+                command = va_arg(ap, int);
+
+                switch (command) {
+
+                case _JSON_BUILD_STRING: {
+                        const char *p;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        p = va_arg(ap, const char *);
+
+                        r = json_variant_new_string(&add, p);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
+                case _JSON_BUILD_INTEGER: {
+                        intmax_t j;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        j = va_arg(ap, intmax_t);
+
+                        r = json_variant_new_integer(&add, j);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
+                case _JSON_BUILD_UNSIGNED: {
+                        uintmax_t j;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        j = va_arg(ap, uintmax_t);
+
+                        r = json_variant_new_unsigned(&add, j);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
+                case _JSON_BUILD_REAL: {
+                        long double d;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        d = va_arg(ap, long double);
+
+                        r = json_variant_new_real(&add, d);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
+                case _JSON_BUILD_BOOLEAN: {
+                        bool b;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        b = va_arg(ap, int);
+
+                        r = json_variant_new_boolean(&add, b);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
+                case _JSON_BUILD_NULL:
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        r = json_variant_new_null(&add);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+
+                case _JSON_BUILD_VARIANT:
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        add = va_arg(ap, JsonVariant*);
+                        if (!add)
+                                add = JSON_VARIANT_MAGIC_NULL;
+                        else
+                                json_variant_ref(add);
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+
+                case _JSON_BUILD_LITERAL: {
+                        const char *l;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        l = va_arg(ap, const char *);
+
+                        if (!l)
+                                add = JSON_VARIANT_MAGIC_NULL;
+                        else {
+                                r = json_parse(l, &add, NULL, NULL);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
+                case _JSON_BUILD_ARRAY_BEGIN:
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+                        current = stack + n_stack - 1;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        stack[n_stack++] = (JsonStack) {
+                                .expect = EXPECT_ARRAY_ELEMENT,
+                        };
+
+                        break;
+
+                case _JSON_BUILD_ARRAY_END:
+                        if (current->expect != EXPECT_ARRAY_ELEMENT) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        assert(n_stack > 1);
+
+                        r = json_variant_new_array(&add, current->elements, current->n_elements);
+                        if (r < 0)
+                                goto finish;
+
+                        json_stack_release(current);
+                        n_stack--, current--;
+
+                        break;
+
+                case _JSON_BUILD_STRV: {
+                        char **l;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        l = va_arg(ap, char **);
+
+                        r = json_variant_new_array_strv(&add, l);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
+                case _JSON_BUILD_OBJECT_BEGIN:
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+                        current = stack + n_stack - 1;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        stack[n_stack++] = (JsonStack) {
+                                .expect = EXPECT_OBJECT_KEY,
+                        };
+
+                        break;
+
+                case _JSON_BUILD_OBJECT_END:
+
+                        if (current->expect != EXPECT_OBJECT_KEY) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        assert(n_stack > 1);
+
+                        r = json_variant_new_object(&add, current->elements, current->n_elements);
+                        if (r < 0)
+                                goto finish;
+
+                        json_stack_release(current);
+                        n_stack--, current--;
+
+                        break;
+
+                case _JSON_BUILD_PAIR: {
+                        const char *n;
+
+                        if (current->expect != EXPECT_OBJECT_KEY) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        n = va_arg(ap, const char *);
+
+                        r = json_variant_new_string(&add, n);
+                        if (r < 0)
+                                goto finish;
+
+                        current->expect = EXPECT_OBJECT_VALUE;
+                        break;
+                }}
+
+                if (add) {
+                        if (!GREEDY_REALLOC(current->elements, current->n_elements_allocated, current->n_elements + 1)) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        current->elements[current->n_elements++] = add;
+                }
+        }
+
+done:
+        assert(n_stack == 1);
+        assert(stack[0].n_elements == 1);
+
+        *ret = json_variant_ref(stack[0].elements[0]);
+        r = 0;
+
+finish:
+        for (i = 0; i < n_stack; i++)
+                json_stack_release(stack + i);
+
+        free(stack);
+
+        va_end(ap);
+
+        return r;
+}
+
+int json_build(JsonVariant **ret, ...) {
+        va_list ap;
+        int r;
+
+        va_start(ap, ret);
+        r = json_buildv(ret, ap);
+        va_end(ap);
+
+        return r;
+}
+
+int json_log_internal(
+                JsonVariant *variant,
+                int level,
+                int error,
+                const char *file,
+                int line,
+                const char *func,
+                const char *format, ...) {
+
+        PROTECT_ERRNO;
+
+        unsigned source_line, source_column;
+        char buffer[LINE_MAX];
+        const char *source;
+        va_list ap;
+        int r;
+
+        if (error < 0)
+                error = -error;
+
+        errno = error;
+
+        va_start(ap, format);
+        (void) vsnprintf(buffer, sizeof buffer, format, ap);
+        va_end(ap);
+
+        if (variant) {
+                r = json_variant_get_source(variant, &source, &source_line, &source_column);
+                if (r < 0)
+                        return r;
+        } else {
+                source = NULL;
+                source_line = 0;
+                source_column = 0;
+        }
+
+        if (source && source_line > 0 && source_column > 0)
+                return log_struct_internal(
+                                LOG_REALM_PLUS_LEVEL(LOG_REALM_SYSTEMD, level),
+                                error,
+                                file, line, func,
+                                "MESSAGE_ID=" SD_MESSAGE_INVALID_CONFIGURATION_STR,
+                                "CONFIG_FILE=%s", source,
+                                "CONFIG_LINE=%u", source_line,
+                                "CONFIG_COLUMN=%u", source_column,
+                                LOG_MESSAGE("%s:%u: %s", source, line, buffer),
+                                NULL);
+        else
+                return log_struct_internal(
+                                LOG_REALM_PLUS_LEVEL(LOG_REALM_SYSTEMD, level),
+                                error,
+                                file, line, func,
+                                "MESSAGE_ID=" SD_MESSAGE_INVALID_CONFIGURATION_STR,
+                                LOG_MESSAGE("%s", buffer),
+                                NULL);
+}
+
+int json_dispatch(JsonVariant *v, const JsonDispatch table[], JsonDispatchCallback bad, JsonDispatchFlags flags, void *userdata) {
+        const JsonDispatch *p;
+        size_t i, n, m;
+        int r, done = 0;
+        bool *found;
+
+        if (!json_variant_is_object(v)) {
+                json_log(v, flags, 0, "JSON variant is not an object.");
+
+                if (flags & JSON_PERMISSIVE)
+                        return 0;
+
+                return -EINVAL;
+        }
+
+        for (p = table, m = 0; p->name; p++)
+                m++;
+
+        found = newa0(bool, m);
+
+        n = json_variant_elements(v);
+        for (i = 0; i < n; i += 2) {
+                JsonVariant *key, *value;
+
+                assert_se(key = json_variant_by_index(v, i));
+                assert_se(value = json_variant_by_index(v, i+1));
+
+                for (p = table; p->name; p++)
+                        if (p->name == (const char*) -1 ||
+                            streq_ptr(json_variant_string(key), p->name))
+                                break;
+
+                if (p->name) { /* Found a matching entry! :-) */
+                        JsonDispatchFlags merged_flags;
+
+                        merged_flags = flags | p->flags;
+
+                        if (p->type != _JSON_VARIANT_TYPE_INVALID &&
+                            !json_variant_has_type(value, p->type)) {
+
+                                json_log(value, merged_flags, 0,
+                                         "Object field '%s' has wrong type %s, expected %s.", json_variant_string(key),
+                                         json_variant_type_to_string(json_variant_type(value)), json_variant_type_to_string(p->type));
+
+                                if (merged_flags & JSON_PERMISSIVE)
+                                        continue;
+
+                                return -EINVAL;
+                        }
+
+                        if (found[p-table]) {
+                                json_log(value, merged_flags, 0, "Duplicate object field '%s'.", json_variant_string(key));
+
+                                if (merged_flags & JSON_PERMISSIVE)
+                                        continue;
+
+                                return -ENOTUNIQ;
+                        }
+
+                        found[p-table] = true;
+
+                        if (p->callback) {
+                                r = p->callback(json_variant_string(key), value, merged_flags, (uint8_t*) userdata + p->offset);
+                                if (r < 0) {
+                                        if (merged_flags & JSON_PERMISSIVE)
+                                                continue;
+
+                                        return r;
+                                }
+                        }
+
+                        done ++;
+
+                } else { /* Didn't find a matching entry! :-( */
+
+                        if (bad) {
+                                r = bad(json_variant_string(key), value, flags, userdata);
+                                if (r < 0) {
+                                        if (flags & JSON_PERMISSIVE)
+                                                continue;
+
+                                        return r;
+                                } else
+                                        done ++;
+
+                        } else  {
+                                json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key));
+
+                                if (flags & JSON_PERMISSIVE)
+                                        continue;
+
+                                return -EADDRNOTAVAIL;
+                        }
+                }
+        }
+
+        for (p = table; p->name; p++) {
+                JsonDispatchFlags merged_flags = p->flags | flags;
+
+                if ((merged_flags & JSON_MANDATORY) && !found[p-table]) {
+                        json_log(v, merged_flags, 0, "Missing object field '%s'.", p->name);
+
+                        if ((merged_flags & JSON_PERMISSIVE))
+                                continue;
+
+                        return -ENXIO;
+                }
+        }
+
+        return done;
+}
+
+int json_dispatch_boolean(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        bool *b = userdata;
+
+        assert(variant);
+        assert(b);
+
+        if (!json_variant_is_boolean(variant)) {
+                json_log(variant, flags, 0, "JSON field '%s' is not a boolean.", strna(name));
+                return -EINVAL;
+        }
+
+        *b = json_variant_boolean(variant);
+        return 0;
+}
+
+int json_dispatch_tristate(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        int *b = userdata;
+
+        assert(variant);
+        assert(b);
+
+        if (!json_variant_is_boolean(variant)) {
+                json_log(variant, flags, 0, "JSON field '%s' is not a boolean.", strna(name));
+                return -EINVAL;
+        }
+
+        *b = json_variant_boolean(variant);
+        return 0;
+}
+
+int json_dispatch_integer(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        intmax_t *i = userdata;
+
+        assert(variant);
+        assert(i);
+
+        if (!json_variant_is_integer(variant)) {
+                json_log(variant, flags, 0, "JSON field '%s' is not an integer.", strna(name));
+                return -EINVAL;
+        }
+
+        *i = json_variant_integer(variant);
+        return 0;
+}
+
+int json_dispatch_unsigned(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        uintmax_t *u = userdata;
+
+        assert(variant);
+        assert(u);
+
+        if (!json_variant_is_unsigned(variant)) {
+                json_log(variant, flags, 0, "JSON field '%s' is not an unsigned integer.", strna(name));
+                return -EINVAL;
+        }
+
+        *u = json_variant_unsigned(variant);
+        return 0;
+}
+
+int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        uint32_t *u = userdata;
+
+        assert(variant);
+        assert(u);
+
+        if (!json_variant_is_unsigned(variant)) {
+                json_log(variant, flags, 0, "JSON field '%s' is not an unsigned integer.", strna(name));
+                return -EINVAL;
+        }
+
+        if (json_variant_unsigned(variant) > UINT32_MAX) {
+                json_log(variant, flags, 0, "JSON field '%s' out of bounds.", strna(name));
+                return -ERANGE;
+        }
+
+        *u = (uint32_t) json_variant_unsigned(variant);
+        return 0;
+}
+
+int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        int32_t *i = userdata;
+
+        assert(variant);
+        assert(i);
+
+        if (!json_variant_is_integer(variant)) {
+                json_log(variant, flags, 0, "JSON field '%s' is not an integer.", strna(name));
+                return -EINVAL;
+        }
+
+        if (json_variant_integer(variant) < INT32_MIN || json_variant_integer(variant) > INT32_MAX) {
+                json_log(variant, flags, 0, "JSON field '%s' out of bounds.", strna(name));
+                return -ERANGE;
+        }
+
+        *i = (int32_t) json_variant_integer(variant);
+        return 0;
+}
+
+int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        char **s = userdata;
+        int r;
+
+        assert(variant);
+        assert(s);
+
+        if (json_variant_is_null(variant)) {
+                *s = mfree(*s);
+                return 0;
+        }
+
+        if (!json_variant_is_string(variant)) {
+                json_log(variant, flags, 0, "JSON field '%s' is not a string.", strna(name));
+                return -EINVAL;
+        }
+
+        r = free_and_strdup(s, json_variant_string(variant));
+        if (r < 0)
+                return json_log(variant, flags, r, "Failed to allocate string: %m");
+
+        return 0;
+}
+
+int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        _cleanup_strv_free_ char **l = NULL;
+        char ***s = userdata;
+        size_t i;
+        int r;
+
+        assert(variant);
+        assert(s);
+
+        if (json_variant_is_null(variant)) {
+                *s = strv_free(*s);
+                return 0;
+        }
+
+        if (!json_variant_is_array(variant)) {
+                json_log(variant, 0, flags, "JSON field '%s' is not an array.", strna(name));
+                return -EINVAL;
+        }
+
+        for (i = 0; i < json_variant_elements(variant); i++) {
+                JsonVariant *e;
+
+                assert_se(e = json_variant_by_index(variant, i));
+
+                if (!json_variant_is_string(e)) {
+                        json_log(e, 0, flags, "JSON array element is not a string.");
+                        return -EINVAL;
+                }
+
+                r = strv_extend(&l, json_variant_string(e));
+                if (r < 0)
+                        return json_log(variant, flags, r, "Failed to append array element: %m");
+        }
+
+        strv_free_and_replace(*s, l);
+        return 0;
+}
+
+int json_dispatch_variant(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        JsonVariant **p = userdata;
+
+        assert(variant);
+        assert(p);
+
+        json_variant_unref(*p);
+        *p = json_variant_ref(variant);
+
+        return 0;
+}
+
+static const char* const json_variant_type_table[_JSON_VARIANT_TYPE_MAX] = {
+        [JSON_VARIANT_STRING] = "string",
+        [JSON_VARIANT_INTEGER] = "integer",
+        [JSON_VARIANT_UNSIGNED] = "unsigned",
+        [JSON_VARIANT_REAL] = "real",
+        [JSON_VARIANT_NUMBER] = "number",
+        [JSON_VARIANT_BOOLEAN] = "boolean",
+        [JSON_VARIANT_ARRAY] = "array",
+        [JSON_VARIANT_OBJECT] = "object",
+        [JSON_VARIANT_NULL] = "null",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(json_variant_type, JsonVariantType);
diff --git a/src/basic/json.h b/src/basic/json.h
new file mode 100644 (file)
index 0000000..1f06fe2
--- /dev/null
@@ -0,0 +1,274 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "macro.h"
+#include "string-util.h"
+#include "util.h"
+
+/*
+  In case you wonder why we have our own JSON implementation, here are a couple of reasons why this implementation has
+  benefits over various other implementatins:
+
+  - We need support for 64bit signed and unsigned integers, i.e. the full 64,5bit range of -9223372036854775808…18446744073709551615
+  - All our variants are immutable after creation
+  - Special values such as true, false, zero, null, empty strings, empty array, empty objects require zero dynamic memory
+  - Progressive parsing
+  - Our integer/real type implicitly converts, but only if that's safe and loss-lessly possible
+  - There's a "builder" for putting together objects easily in varargs function calls
+  - There's a "dispatcher" for mapping objects to C data structures
+  - Every variant optionally carries parsing location information, which simplifies debugging and parse log error generation
+  - Formatter has color, line, column support
+
+  Limitations:
+  - Doesn't allow embedded NUL in strings
+  - Can't store integers outside of the -9223372036854775808…18446744073709551615 range (it will use 'long double' for
+    values outside this range, which is lossy)
+  - Can't store negative zero (will be treated identical to positive zero, and not retained across serialization)
+  - Can't store non-integer numbers that can't be stored in "long double" losslessly
+  - Allows creation and parsing of objects with duplicate keys. The "dispatcher" will refuse them however. This means
+    we can parse and pass around such objects, but will carefully refuse them when we convert them into our own data.
+
+  (These limitations should be pretty much in line with those of other JSON implementations, in fact might be less
+  limiting in most cases even.)
+*/
+
+typedef struct JsonVariant JsonVariant;
+
+typedef enum JsonVariantType {
+        JSON_VARIANT_STRING,
+        JSON_VARIANT_INTEGER,
+        JSON_VARIANT_UNSIGNED,
+        JSON_VARIANT_REAL,
+        JSON_VARIANT_NUMBER, /* This a pseudo-type: we can never create variants of this type, but we use it as wildcard check for the above three types */
+        JSON_VARIANT_BOOLEAN,
+        JSON_VARIANT_ARRAY,
+        JSON_VARIANT_OBJECT,
+        JSON_VARIANT_NULL,
+        _JSON_VARIANT_TYPE_MAX,
+        _JSON_VARIANT_TYPE_INVALID = -1
+} JsonVariantType;
+
+int json_variant_new_stringn(JsonVariant **ret, const char *s, size_t n);
+int json_variant_new_integer(JsonVariant **ret, intmax_t i);
+int json_variant_new_unsigned(JsonVariant **ret, uintmax_t u);
+int json_variant_new_real(JsonVariant **ret, long double d);
+int json_variant_new_boolean(JsonVariant **ret, bool b);
+int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n);
+int json_variant_new_array_bytes(JsonVariant **ret, const void *p, size_t n);
+int json_variant_new_array_strv(JsonVariant **ret, char **l);
+int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n);
+int json_variant_new_null(JsonVariant **ret);
+
+static inline int json_variant_new_string(JsonVariant **ret, const char *s) {
+        return json_variant_new_stringn(ret, s, strlen_ptr(s));
+}
+
+JsonVariant *json_variant_ref(JsonVariant *v);
+JsonVariant *json_variant_unref(JsonVariant *v);
+void json_variant_unref_many(JsonVariant **array, size_t n);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(JsonVariant *, json_variant_unref);
+
+const char *json_variant_string(JsonVariant *v);
+intmax_t json_variant_integer(JsonVariant *v);
+uintmax_t json_variant_unsigned(JsonVariant *v);
+long double json_variant_real(JsonVariant *v);
+bool json_variant_boolean(JsonVariant *v);
+
+JsonVariantType json_variant_type(JsonVariant *v);
+bool json_variant_has_type(JsonVariant *v, JsonVariantType type);
+
+static inline bool json_variant_is_string(JsonVariant *v) {
+        return json_variant_has_type(v, JSON_VARIANT_STRING);
+}
+
+static inline bool json_variant_is_integer(JsonVariant *v) {
+        return json_variant_has_type(v, JSON_VARIANT_INTEGER);
+}
+
+static inline bool json_variant_is_unsigned(JsonVariant *v) {
+        return json_variant_has_type(v, JSON_VARIANT_UNSIGNED);
+}
+
+static inline bool json_variant_is_real(JsonVariant *v) {
+        return json_variant_has_type(v, JSON_VARIANT_REAL);
+}
+
+static inline bool json_variant_is_number(JsonVariant *v) {
+        return json_variant_has_type(v, JSON_VARIANT_NUMBER);
+}
+
+static inline bool json_variant_is_boolean(JsonVariant *v) {
+        return json_variant_has_type(v, JSON_VARIANT_BOOLEAN);
+}
+
+static inline bool json_variant_is_array(JsonVariant *v) {
+        return json_variant_has_type(v, JSON_VARIANT_ARRAY);
+}
+
+static inline bool json_variant_is_object(JsonVariant *v) {
+        return json_variant_has_type(v, JSON_VARIANT_OBJECT);
+}
+
+static inline bool json_variant_is_null(JsonVariant *v) {
+        return json_variant_has_type(v, JSON_VARIANT_NULL);
+}
+
+bool json_variant_is_negative(JsonVariant *v);
+
+size_t json_variant_elements(JsonVariant *v);
+JsonVariant *json_variant_by_index(JsonVariant *v, size_t index);
+JsonVariant *json_variant_by_key(JsonVariant *v, const char *key);
+JsonVariant *json_variant_by_key_full(JsonVariant *v, const char *key, JsonVariant **ret_key);
+
+bool json_variant_equal(JsonVariant *a, JsonVariant *b);
+
+struct json_variant_foreach_state {
+        JsonVariant *variant;
+        size_t idx;
+};
+
+#define JSON_VARIANT_ARRAY_FOREACH(i, v)                                \
+        for (struct json_variant_foreach_state _state = { (v), 0 };     \
+             _state.idx < json_variant_elements(_state.variant) &&      \
+                     ({ i = json_variant_by_index(_state.variant, _state.idx); \
+                             true; });                                  \
+             _state.idx++)
+
+#define JSON_VARIANT_OBJECT_FOREACH(k, e, v)                            \
+        for (struct json_variant_foreach_state _state = { (v), 0 };     \
+             _state.idx < json_variant_elements(_state.variant) &&      \
+                     ({ k = json_variant_by_index(_state.variant, _state.idx); \
+                             e = json_variant_by_index(_state.variant, _state.idx + 1); \
+                             true; });                                  \
+             _state.idx += 2)
+
+int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column);
+
+enum {
+        JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */
+        JSON_FORMAT_PRETTY  = 1 << 1, /* add internal whitespace to appeal to human readers */
+        JSON_FORMAT_COLOR   = 1 << 2, /* insert ANSI color sequences */
+        JSON_FORMAT_SOURCE  = 1 << 3, /* prefix with source filename/line/column */
+        JSON_FORMAT_SSE     = 1 << 4, /* prefix/suffix with W3C server-sent events */
+        JSON_FORMAT_SEQ     = 1 << 5, /* prefix/suffix with RFC 7464 application/json-seq */
+};
+
+int json_variant_format(JsonVariant *v, unsigned flags, char **ret);
+void json_variant_dump(JsonVariant *v, unsigned flags, FILE *f, const char *prefix);
+
+int json_parse(const char *string, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column);
+int json_parse_continue(const char **p, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column);
+int json_parse_file(FILE *f, const char *path, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column);
+
+enum {
+        _JSON_BUILD_STRING,
+        _JSON_BUILD_INTEGER,
+        _JSON_BUILD_UNSIGNED,
+        _JSON_BUILD_REAL,
+        _JSON_BUILD_BOOLEAN,
+        _JSON_BUILD_ARRAY_BEGIN,
+        _JSON_BUILD_ARRAY_END,
+        _JSON_BUILD_OBJECT_BEGIN,
+        _JSON_BUILD_OBJECT_END,
+        _JSON_BUILD_PAIR,
+        _JSON_BUILD_NULL,
+        _JSON_BUILD_VARIANT,
+        _JSON_BUILD_LITERAL,
+        _JSON_BUILD_STRV,
+        _JSON_BUILD_MAX,
+};
+
+#define JSON_BUILD_STRING(s) _JSON_BUILD_STRING, ({ const char *_x = s; _x; })
+#define JSON_BUILD_INTEGER(i) _JSON_BUILD_INTEGER, ({ intmax_t _x = i; _x; })
+#define JSON_BUILD_UNSIGNED(u) _JSON_BUILD_UNSIGNED, ({ uintmax_t _x = u; _x; })
+#define JSON_BUILD_REAL(d) _JSON_BUILD_REAL, ({ long double _x = d; _x; })
+#define JSON_BUILD_BOOLEAN(b) _JSON_BUILD_BOOLEAN, ({ bool _x = b; _x; })
+#define JSON_BUILD_ARRAY(...) _JSON_BUILD_ARRAY_BEGIN, __VA_ARGS__, _JSON_BUILD_ARRAY_END
+#define JSON_BUILD_OBJECT(...) _JSON_BUILD_OBJECT_BEGIN, __VA_ARGS__, _JSON_BUILD_OBJECT_END
+#define JSON_BUILD_PAIR(n, ...) _JSON_BUILD_PAIR, ({ const char *_x = n; _x; }), __VA_ARGS__
+#define JSON_BUILD_NULL _JSON_BUILD_NULL
+#define JSON_BUILD_VARIANT(v) _JSON_BUILD_VARIANT, ({ JsonVariant *_x = v; _x; })
+#define JSON_BUILD_LITERAL(l) _JSON_BUILD_LITERAL, ({ const char *_x = l; _x; })
+#define JSON_BUILD_STRV(l) _JSON_BUILD_STRV, ({ char **_x = l; _x; })
+
+int json_build(JsonVariant **ret, ...);
+int json_buildv(JsonVariant **ret, va_list ap);
+
+/* A bitmask of flags used by the dispatch logic. Note that this is a combined bit mask, that is generated from the bit
+ * mask originally passed into json_dispatch(), the individual bitmask associated with the static JsonDispatch callout
+ * entry, as well the bitmask specified for json_log() calls */
+typedef enum JsonDispatchFlags {
+        /* The following three may be set in JsonDispatch's .flags field or the json_dispatch() flags parameter  */
+        JSON_PERMISSIVE = 1 << 0, /* Shall parsing errors be considered fatal for this property? */
+        JSON_MANDATORY  = 1 << 1, /* Should existance of this property be mandatory? */
+        JSON_LOG        = 1 << 2, /* Should the parser log about errors? */
+
+        /* The following two may be passed into log_json() in addition to the three above */
+        JSON_DEBUG      = 1 << 3, /* Indicates that this log message is a debug message */
+        JSON_WARNING    = 1 << 4, /* Indicates that this log message is a warning message */
+} JsonDispatchFlags;
+
+typedef int (*JsonDispatchCallback)(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+
+typedef struct JsonDispatch {
+        const char *name;
+        JsonVariantType type;
+        JsonDispatchCallback callback;
+        size_t offset;
+        JsonDispatchFlags flags;
+} JsonDispatch;
+
+int json_dispatch(JsonVariant *v, const JsonDispatch table[], JsonDispatchCallback bad, JsonDispatchFlags flags, void *userdata);
+
+int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_boolean(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_tristate(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_variant(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_integer(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_unsigned(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+
+assert_cc(sizeof(uintmax_t) == sizeof(uint64_t))
+#define json_dispatch_uint64 json_dispatch_unsigned
+
+assert_cc(sizeof(intmax_t) == sizeof(int64_t))
+#define json_dispatch_int64 json_dispatch_integer
+
+static inline int json_dispatch_level(JsonDispatchFlags flags) {
+
+        /* Did the user request no logging? If so, then never log higher than LOG_DEBUG. Also, if this is marked as
+         * debug message, then also log at debug level. */
+
+        if (!(flags & JSON_LOG) ||
+            (flags & JSON_DEBUG))
+                return LOG_DEBUG;
+
+        /* Are we invoked in permissive mode, or is this explicitly marked as warning message? Then this should be
+         * printed at LOG_WARNING */
+        if (flags & (JSON_PERMISSIVE|JSON_WARNING))
+                return LOG_WARNING;
+
+        /* Otherwise it's an error. */
+        return LOG_ERR;
+}
+
+int json_log_internal(JsonVariant *variant, int level, int error, const char *file, int line, const char *func, const char *format, ...)  _printf_(7, 8);
+
+#define json_log(variant, flags, error, ...)                       \
+        ({                                                              \
+                int _level = json_dispatch_level(flags), _e = (error);    \
+                (log_get_max_level() >= LOG_PRI(_level))                \
+                        ? json_log_internal(variant, _level, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \
+                        : -abs(_e);                                     \
+        })
+
+const char *json_variant_type_to_string(JsonVariantType t);
+JsonVariantType json_variant_type_from_string(const char *s);
index a5ec8a1020362c8dea7af0265f2bac17057740f7..159c963377888dc822c5e2faf4d395c0f53fa7f3 100644 (file)
@@ -3,8 +3,10 @@
 #include <stdint.h>
 #include <stdlib.h>
 
+#include "env-util.h"
 #include "macro.h"
 #include "mempool.h"
+#include "process-util.h"
 #include "util.h"
 
 struct pool {
@@ -70,8 +72,21 @@ void mempool_free_tile(struct mempool *mp, void *p) {
         mp->freelist = p;
 }
 
-#if VALGRIND
+bool mempool_enabled(void) {
+        static int b = -1;
+
+        if (!is_main_thread())
+                return false;
 
+        if (!mempool_use_allowed)
+                b = false;
+        if (b < 0)
+                b = getenv_bool("SYSTEMD_MEMPOOL") != 0;
+
+        return b;
+}
+
+#if VALGRIND
 void mempool_drop(struct mempool *mp) {
         struct pool *p = mp->first_pool;
         while (p) {
@@ -81,5 +96,4 @@ void mempool_drop(struct mempool *mp) {
                 p = n;
         }
 }
-
 #endif
index 4098535c6f39d3115e8c7733b6d1ad9b616ddcd1..0eecca0f92a9062d3cb46bab223b7215c7e71644 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
+#include <stdbool.h>
 #include <stddef.h>
 
 struct pool;
@@ -22,6 +23,9 @@ static struct mempool pool_name = { \
         .at_least = alloc_at_least, \
 }
 
+extern const bool mempool_use_allowed;
+bool mempool_enabled(void);
+
 #if VALGRIND
 void mempool_drop(struct mempool *mp);
 #endif
index 2fa2681b477dabb4dfce2f23f6b5382722695332..ac580c283c5c1385264ed8ffbff81dc2eaf023b7 100644 (file)
@@ -99,6 +99,9 @@ basic_sources = files('''
         ioprio.h
         journal-importer.c
         journal-importer.h
+        json-internal.h
+        json.c
+        json.h
         khash.c
         khash.h
         label.c
@@ -313,7 +316,8 @@ libbasic = static_library(
                         libcap,
                         libblkid,
                         libmount,
-                        libselinux],
+                        libselinux,
+                        libm],
         c_args : ['-fvisibility=default'],
         install : false)
 
index 5f3f982190214381b892f677e9aa304de165042b..8cba4ed7260e2380a9a46f7d098aa4445bee5eeb 100644 (file)
@@ -150,7 +150,13 @@ static inline int memcmp_safe(const void *s1, const void *s2, size_t n) {
 
 int on_ac_power(void);
 
-#define memzero(x,l) (memset((x), 0, (l)))
+#define memzero(x,l)                                            \
+        ({                                                      \
+                size_t _l_ = (l);                               \
+                void *_x_ = (x);                                \
+                _l_ == 0 ? _x_ : memset(_x_, 0, _l_);           \
+        })
+
 #define zero(x) (memzero(&(x), sizeof(x)))
 
 static inline void *mempset(void *s, int c, size_t n) {
index 7b651cfcd690fba568f6f0ac864aefa486d0e045..a5673731c0d25ce4a2501ee49e4f5d0de0291eb7 100644 (file)
@@ -15,6 +15,7 @@
 #include "escape.h"
 #include "fd-util.h"
 #include "fileio.h"
+#include "json.h"
 #include "locale-util.h"
 #include "log.h"
 #include "pager.h"
 #include "util.h"
 #include "verbs.h"
 
+static enum {
+        JSON_OFF,
+        JSON_SHORT,
+        JSON_PRETTY,
+} arg_json = JSON_OFF;
 static bool arg_no_pager = false;
 static bool arg_legend = true;
 static const char *arg_address = NULL;
@@ -1545,6 +1551,359 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
         return 0;
 }
 
+static int json_transform_one(sd_bus_message *m, JsonVariant **ret);
+
+static int json_transform_array_or_struct(sd_bus_message *m, JsonVariant **ret) {
+        size_t n_elements = 0, n_allocated = 0;
+        JsonVariant **elements = NULL;
+        int r;
+
+        assert(m);
+        assert(ret);
+
+        for (;;) {
+                r = sd_bus_message_at_end(m, false);
+                if (r < 0) {
+                        bus_log_parse_error(r);
+                        goto finish;
+                }
+                if (r > 0)
+                        break;
+
+                if (!GREEDY_REALLOC(elements, n_allocated, n_elements + 1)) {
+                        r = log_oom();
+                        goto finish;
+                }
+
+                r = json_transform_one(m, elements + n_elements);
+                if (r < 0)
+                        goto finish;
+
+                n_elements++;
+        }
+
+        r = json_variant_new_array(ret, elements, n_elements);
+
+finish:
+        json_variant_unref_many(elements, n_elements);
+        free(elements);
+
+        return r;
+}
+
+static int json_transform_variant(sd_bus_message *m, const char *contents, JsonVariant **ret) {
+        _cleanup_(json_variant_unrefp) JsonVariant *type = NULL, *value = NULL;
+        int r;
+
+        assert(m);
+        assert(contents);
+        assert(ret);
+
+        r = json_transform_one(m, &value);
+        if (r < 0)
+                return r;
+
+        r = json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("type", JSON_BUILD_STRING(contents)),
+                                              JSON_BUILD_PAIR("data", JSON_BUILD_VARIANT(value))));
+        if (r < 0)
+                return log_oom();
+
+        return r;
+}
+
+static int json_transform_dict_array(sd_bus_message *m, JsonVariant **ret) {
+        size_t n_elements = 0, n_allocated = 0;
+        JsonVariant **elements = NULL;
+        int r;
+
+        assert(m);
+        assert(ret);
+
+        for (;;) {
+                const char *contents;
+                char type;
+
+                r = sd_bus_message_at_end(m, false);
+                if (r < 0) {
+                        bus_log_parse_error(r);
+                        goto finish;
+                }
+                if (r > 0)
+                        break;
+
+                r = sd_bus_message_peek_type(m, &type, &contents);
+                if (r < 0)
+                        return r;
+
+                assert(type == 'e');
+
+                if (!GREEDY_REALLOC(elements, n_allocated, n_elements + 2)) {
+                        r = log_oom();
+                        goto finish;
+                }
+
+                r = sd_bus_message_enter_container(m, type, contents);
+                if (r < 0) {
+                        bus_log_parse_error(r);
+                        goto finish;
+                }
+
+                r = json_transform_one(m, elements + n_elements);
+                if (r < 0)
+                        goto finish;
+
+                n_elements++;
+
+                r = json_transform_one(m, elements + n_elements);
+                if (r < 0)
+                        goto finish;
+
+                n_elements++;
+
+                r = sd_bus_message_exit_container(m);
+                if (r < 0) {
+                        bus_log_parse_error(r);
+                        goto finish;
+                }
+        }
+
+        r = json_variant_new_object(ret, elements, n_elements);
+
+finish:
+        json_variant_unref_many(elements, n_elements);
+        free(elements);
+
+        return r;
+}
+
+static int json_transform_one(sd_bus_message *m, JsonVariant **ret) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        const char *contents;
+        char type;
+        int r;
+
+        assert(m);
+        assert(ret);
+
+        r = sd_bus_message_peek_type(m, &type, &contents);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        switch (type) {
+
+        case SD_BUS_TYPE_BYTE: {
+                uint8_t b;
+
+                r = sd_bus_message_read_basic(m, type, &b);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = json_variant_new_unsigned(&v, b);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to transform byte: %m");
+
+                break;
+        }
+
+        case SD_BUS_TYPE_BOOLEAN: {
+                int b;
+
+                r = sd_bus_message_read_basic(m, type, &b);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = json_variant_new_boolean(&v, b);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to transform boolean: %m");
+
+                break;
+        }
+
+        case SD_BUS_TYPE_INT16: {
+                int16_t b;
+
+                r = sd_bus_message_read_basic(m, type, &b);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = json_variant_new_integer(&v, b);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to transform int16: %m");
+
+                break;
+        }
+
+        case SD_BUS_TYPE_UINT16: {
+                uint16_t b;
+
+                r = sd_bus_message_read_basic(m, type, &b);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = json_variant_new_unsigned(&v, b);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to transform uint16: %m");
+
+                break;
+        }
+
+        case SD_BUS_TYPE_INT32: {
+                int32_t b;
+
+                r = sd_bus_message_read_basic(m, type, &b);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = json_variant_new_integer(&v, b);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to transform int32: %m");
+
+                break;
+        }
+
+        case SD_BUS_TYPE_UINT32: {
+                uint32_t b;
+
+                r = sd_bus_message_read_basic(m, type, &b);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = json_variant_new_unsigned(&v, b);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to transform uint32: %m");
+
+                break;
+        }
+
+        case SD_BUS_TYPE_INT64: {
+                int64_t b;
+
+                r = sd_bus_message_read_basic(m, type, &b);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = json_variant_new_integer(&v, b);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to transform int64: %m");
+
+                break;
+        }
+
+        case SD_BUS_TYPE_UINT64: {
+                uint64_t b;
+
+                r = sd_bus_message_read_basic(m, type, &b);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = json_variant_new_unsigned(&v, b);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to transform uint64: %m");
+
+                break;
+        }
+
+        case SD_BUS_TYPE_DOUBLE: {
+                double d;
+
+                r = sd_bus_message_read_basic(m, type, &d);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = json_variant_new_real(&v, d);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to transform double: %m");
+
+                break;
+        }
+
+        case SD_BUS_TYPE_STRING:
+        case SD_BUS_TYPE_OBJECT_PATH:
+        case SD_BUS_TYPE_SIGNATURE: {
+                const char *s;
+
+                r = sd_bus_message_read_basic(m, type, &s);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = json_variant_new_string(&v, s);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to transform double: %m");
+
+                break;
+        }
+
+        case SD_BUS_TYPE_UNIX_FD:
+                r = sd_bus_message_read_basic(m, type, NULL);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = json_variant_new_null(&v);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to transform fd: %m");
+
+                break;
+
+        case SD_BUS_TYPE_ARRAY:
+        case SD_BUS_TYPE_VARIANT:
+        case SD_BUS_TYPE_STRUCT:
+                r = sd_bus_message_enter_container(m, type, contents);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                if (type == SD_BUS_TYPE_VARIANT)
+                        r = json_transform_variant(m, contents, &v);
+                else if (type == SD_BUS_TYPE_ARRAY && contents[0] == '{')
+                        r = json_transform_dict_array(m, &v);
+                else
+                        r = json_transform_array_or_struct(m, &v);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_exit_container(m);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                break;
+
+        default:
+                assert_not_reached("Unexpected element type");
+        }
+
+        *ret = TAKE_PTR(v);
+        return 0;
+}
+
+static int json_transform_message(sd_bus_message *m, JsonVariant **ret) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        const char *type;
+        int r;
+
+        assert(m);
+        assert(ret);
+
+        assert_se(type = sd_bus_message_get_signature(m, false));
+
+        r = json_transform_array_or_struct(m, &v);
+        if (r < 0)
+                return r;
+
+        r = json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("type",  JSON_BUILD_STRING(type)),
+                                              JSON_BUILD_PAIR("data", JSON_BUILD_VARIANT(v))));
+        if (r < 0)
+                return log_oom();
+
+        return 0;
+}
+
+static void json_dump_with_flags(JsonVariant *v, FILE *f) {
+
+        json_variant_dump(v,
+                          (arg_json == JSON_PRETTY ? JSON_FORMAT_PRETTY : JSON_FORMAT_NEWLINE) |
+                          colors_enabled() * JSON_FORMAT_COLOR,
+                          f, NULL);
+}
+
 static int call(int argc, char **argv, void *userdata) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -1604,7 +1963,19 @@ static int call(int argc, char **argv, void *userdata) {
 
         if (r == 0 && !arg_quiet) {
 
-                if (arg_verbose) {
+                if (arg_json != JSON_OFF) {
+                        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+                        if (arg_json != JSON_SHORT)
+                                (void) pager_open(arg_no_pager, false);
+
+                        r = json_transform_message(reply, &v);
+                        if (r < 0)
+                                return r;
+
+                        json_dump_with_flags(v, stdout);
+
+                } else if (arg_verbose) {
                         (void) pager_open(arg_no_pager, false);
 
                         r = bus_message_dump(reply, stdout, 0);
@@ -1653,7 +2024,19 @@ static int get_property(int argc, char **argv, void *userdata) {
                 if (r < 0)
                         return bus_log_parse_error(r);
 
-                if (arg_verbose)  {
+                if (arg_json != JSON_OFF) {
+                        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+                        if (arg_json != JSON_SHORT)
+                                (void) pager_open(arg_no_pager, false);
+
+                        r = json_transform_variant(reply, contents, &v);
+                        if (r < 0)
+                                return r;
+
+                        json_dump_with_flags(v, stdout);
+
+                } else if (arg_verbose)  {
                         (void) pager_open(arg_no_pager, false);
 
                         r = bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_SUBTREE_ONLY);
@@ -1750,6 +2133,8 @@ static int help(void) {
                "     --list               Don't show tree, but simple object path list\n"
                "  -q --quiet              Don't show method call reply\n"
                "     --verbose            Show result values in long format\n"
+               "     --json=MODE          Output as JSON\n"
+               "  -j                      Same as --json=pretty on tty, --json=short otherwise\n"
                "     --expect-reply=BOOL  Expect a method call reply\n"
                "     --auto-start=BOOL    Auto-start destination service\n"
                "     --allow-interactive-authorization=BOOL\n"
@@ -1807,33 +2192,35 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_TIMEOUT,
                 ARG_AUGMENT_CREDS,
                 ARG_WATCH_BIND,
+                ARG_JSON,
         };
 
         static const struct option options[] = {
-                { "help",         no_argument,       NULL, 'h'              },
-                { "version",      no_argument,       NULL, ARG_VERSION      },
-                { "no-pager",     no_argument,       NULL, ARG_NO_PAGER     },
-                { "no-legend",    no_argument,       NULL, ARG_NO_LEGEND    },
-                { "system",       no_argument,       NULL, ARG_SYSTEM       },
-                { "user",         no_argument,       NULL, ARG_USER         },
-                { "address",      required_argument, NULL, ARG_ADDRESS      },
-                { "show-machine", no_argument,       NULL, ARG_SHOW_MACHINE },
-                { "unique",       no_argument,       NULL, ARG_UNIQUE       },
-                { "acquired",     no_argument,       NULL, ARG_ACQUIRED     },
-                { "activatable",  no_argument,       NULL, ARG_ACTIVATABLE  },
-                { "match",        required_argument, NULL, ARG_MATCH        },
-                { "host",         required_argument, NULL, 'H'              },
-                { "machine",      required_argument, NULL, 'M'              },
-                { "size",         required_argument, NULL, ARG_SIZE         },
-                { "list",         no_argument,       NULL, ARG_LIST         },
-                { "quiet",        no_argument,       NULL, 'q'              },
-                { "verbose",      no_argument,       NULL, ARG_VERBOSE      },
-                { "expect-reply", required_argument, NULL, ARG_EXPECT_REPLY },
-                { "auto-start",   required_argument, NULL, ARG_AUTO_START   },
+                { "help",                            no_argument,       NULL, 'h'                                 },
+                { "version",                         no_argument,       NULL, ARG_VERSION                         },
+                { "no-pager",                        no_argument,       NULL, ARG_NO_PAGER                        },
+                { "no-legend",                       no_argument,       NULL, ARG_NO_LEGEND                       },
+                { "system",                          no_argument,       NULL, ARG_SYSTEM                          },
+                { "user",                            no_argument,       NULL, ARG_USER                            },
+                { "address",                         required_argument, NULL, ARG_ADDRESS                         },
+                { "show-machine",                    no_argument,       NULL, ARG_SHOW_MACHINE                    },
+                { "unique",                          no_argument,       NULL, ARG_UNIQUE                          },
+                { "acquired",                        no_argument,       NULL, ARG_ACQUIRED                        },
+                { "activatable",                     no_argument,       NULL, ARG_ACTIVATABLE                     },
+                { "match",                           required_argument, NULL, ARG_MATCH                           },
+                { "host",                            required_argument, NULL, 'H'                                 },
+                { "machine",                         required_argument, NULL, 'M'                                 },
+                { "size",                            required_argument, NULL, ARG_SIZE                            },
+                { "list",                            no_argument,       NULL, ARG_LIST                            },
+                { "quiet",                           no_argument,       NULL, 'q'                                 },
+                { "verbose",                         no_argument,       NULL, ARG_VERBOSE                         },
+                { "expect-reply",                    required_argument, NULL, ARG_EXPECT_REPLY                    },
+                { "auto-start",                      required_argument, NULL, ARG_AUTO_START                      },
                 { "allow-interactive-authorization", required_argument, NULL, ARG_ALLOW_INTERACTIVE_AUTHORIZATION },
-                { "timeout",      required_argument, NULL, ARG_TIMEOUT      },
-                { "augment-creds",required_argument, NULL, ARG_AUGMENT_CREDS},
-                { "watch-bind",   required_argument, NULL, ARG_WATCH_BIND   },
+                { "timeout",                         required_argument, NULL, ARG_TIMEOUT                         },
+                { "augment-creds",                   required_argument, NULL, ARG_AUGMENT_CREDS                   },
+                { "watch-bind",                      required_argument, NULL, ARG_WATCH_BIND                      },
+                { "json",                            required_argument, NULL, ARG_JSON                            },
                 {},
         };
 
@@ -1842,7 +2229,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "hH:M:q", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "hH:M:qj", options, NULL)) >= 0)
 
                 switch (c) {
 
@@ -1978,6 +2365,29 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_watch_bind = r;
                         break;
 
+                case 'j':
+                        if (on_tty())
+                                arg_json = JSON_PRETTY;
+                        else
+                                arg_json = JSON_SHORT;
+                        break;
+
+                case ARG_JSON:
+                        if (streq(optarg, "short"))
+                                arg_json = JSON_SHORT;
+                        else if (streq(optarg, "pretty"))
+                                arg_json = JSON_PRETTY;
+                        else if (streq(optarg, "help")) {
+                                fputs("short\n"
+                                      "pretty\n", stdout);
+                                return 0;
+                        } else {
+                                log_error("Unknown JSON out mode: %s", optarg);
+                                return -EINVAL;
+                        }
+
+                        break;
+
                 case '?':
                         return -EINVAL;
 
index 21b3887e67b74f574cf35e6b9d7647bba9f1f556..70232f358d7c234ed189fbbe6531076d5f9c882b 100644 (file)
@@ -85,7 +85,7 @@ static void unmount_autofs(Automount *a) {
         a->pipe_fd = safe_close(a->pipe_fd);
 
         /* If we reload/reexecute things we keep the mount point around */
-        if (!IN_SET(UNIT(a)->manager->exit_code, MANAGER_RELOAD, MANAGER_REEXECUTE)) {
+        if (!IN_SET(UNIT(a)->manager->objective, MANAGER_RELOAD, MANAGER_REEXECUTE)) {
 
                 automount_send_ready(a, a->tokens, -EHOSTDOWN);
                 automount_send_ready(a, a->expire_tokens, -EHOSTDOWN);
index a34df2538fae18855a168c2caa06a91ecd269f2b..9f5e67ba22658cfe8229500129c0b08d2bbf0d6b 100644 (file)
@@ -2362,7 +2362,7 @@ int manager_setup_cgroup(Manager *m) {
 
                 (void) sd_event_source_set_description(m->cgroup_inotify_event_source, "cgroup-inotify");
 
-        } else if (MANAGER_IS_SYSTEM(m) && m->test_run_flags == 0) {
+        } else if (MANAGER_IS_SYSTEM(m) && !MANAGER_IS_TEST_RUN(m)) {
 
                 /* On the legacy hierarchy we only get notifications via cgroup agents. (Which isn't really reliable,
                  * since it does not generate events when control groups with children run empty. */
@@ -2391,11 +2391,11 @@ int manager_setup_cgroup(Manager *m) {
                 if (m->pin_cgroupfs_fd < 0)
                         return log_error_errno(errno, "Failed to open pin file: %m");
 
-        } else if (!m->test_run_flags)
+        } else if (!MANAGER_IS_TEST_RUN(m))
                 return log_error_errno(r, "Failed to create %s control group: %m", scope_path);
 
         /* 7. Always enable hierarchical support if it exists... */
-        if (!all_unified && m->test_run_flags == 0)
+        if (!all_unified && !MANAGER_IS_TEST_RUN(m))
                 (void) cg_set_attribute("memory", "/", "memory.use_hierarchy", "1");
 
         /* 8. Figure out which controllers are supported */
index 1d5237614e9831efa16328f6a7c760f1af338274..480a14309185051fd81e500062d3a634c8a12b20 100644 (file)
@@ -1334,7 +1334,7 @@ static int method_reload(sd_bus_message *message, void *userdata, sd_bus_error *
         if (r < 0)
                 return r;
 
-        m->exit_code = MANAGER_RELOAD;
+        m->objective = MANAGER_RELOAD;
 
         return 1;
 }
@@ -1363,7 +1363,7 @@ static int method_reexecute(sd_bus_message *message, void *userdata, sd_bus_erro
         /* We don't send a reply back here, the client should
          * just wait for us disconnecting. */
 
-        m->exit_code = MANAGER_REEXECUTE;
+        m->objective = MANAGER_REEXECUTE;
         return 1;
 }
 
@@ -1383,7 +1383,7 @@ static int method_exit(sd_bus_message *message, void *userdata, sd_bus_error *er
          * systemd-shutdown if it cannot do the exit() because it isn't a
          * container. */
 
-        m->exit_code = MANAGER_EXIT;
+        m->objective = MANAGER_EXIT;
 
         return sd_bus_reply_method_return(message, NULL);
 }
@@ -1402,7 +1402,7 @@ static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error *
         if (!MANAGER_IS_SYSTEM(m))
                 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Reboot is only supported for system managers.");
 
-        m->exit_code = MANAGER_REBOOT;
+        m->objective = MANAGER_REBOOT;
 
         return sd_bus_reply_method_return(message, NULL);
 }
@@ -1421,7 +1421,7 @@ static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error
         if (!MANAGER_IS_SYSTEM(m))
                 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Powering off is only supported for system managers.");
 
-        m->exit_code = MANAGER_POWEROFF;
+        m->objective = MANAGER_POWEROFF;
 
         return sd_bus_reply_method_return(message, NULL);
 }
@@ -1440,7 +1440,7 @@ static int method_halt(sd_bus_message *message, void *userdata, sd_bus_error *er
         if (!MANAGER_IS_SYSTEM(m))
                 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Halt is only supported for system managers.");
 
-        m->exit_code = MANAGER_HALT;
+        m->objective = MANAGER_HALT;
 
         return sd_bus_reply_method_return(message, NULL);
 }
@@ -1459,7 +1459,7 @@ static int method_kexec(sd_bus_message *message, void *userdata, sd_bus_error *e
         if (!MANAGER_IS_SYSTEM(m))
                 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "KExec is only supported for system managers.");
 
-        m->exit_code = MANAGER_KEXEC;
+        m->objective = MANAGER_KEXEC;
 
         return sd_bus_reply_method_return(message, NULL);
 }
@@ -1549,7 +1549,7 @@ static int method_switch_root(sd_bus_message *message, void *userdata, sd_bus_er
         free(m->switch_root_init);
         m->switch_root_init = ri;
 
-        m->exit_code = MANAGER_SWITCH_ROOT;
+        m->objective = MANAGER_SWITCH_ROOT;
 
         return sd_bus_reply_method_return(message, NULL);
 }
index 5420db8a87eb0ceac03130117c70966fbba2fadf..6f0b0a8f338a050ff7528355bd609b5e4425d497 100644 (file)
@@ -41,7 +41,7 @@ int emergency_action(
                  * in user mode */
 
                 log_warning("Exiting: %s", reason);
-                m->exit_code = MANAGER_EXIT;
+                m->objective = MANAGER_EXIT;
                 return -ECANCELED;
         }
 
@@ -59,7 +59,7 @@ int emergency_action(
                 log_and_status(m, "Forcibly rebooting", reason);
 
                 (void) update_reboot_parameter_and_warn(reboot_arg);
-                m->exit_code = MANAGER_REBOOT;
+                m->objective = MANAGER_REBOOT;
 
                 break;
 
@@ -85,7 +85,7 @@ int emergency_action(
 
         case EMERGENCY_ACTION_POWEROFF_FORCE:
                 log_and_status(m, "Forcibly powering off", reason);
-                m->exit_code = MANAGER_POWEROFF;
+                m->objective = MANAGER_POWEROFF;
                 break;
 
         case EMERGENCY_ACTION_POWEROFF_IMMEDIATE:
index 9f24b47b0b60a06e9ebd0cbc8a80ac830d7e0c7b..f96775ee7195c739f8830983a88117236904e7e5 100644 (file)
@@ -3093,7 +3093,7 @@ int config_parse_tasks_max(
         int r;
 
         if (isempty(rvalue)) {
-                *tasks_max = u->manager->default_tasks_max;
+                *tasks_max = u ? u->manager->default_tasks_max : UINT64_MAX;
                 return 0;
         }
 
index 88656dcabf567a30f5e44c931e2041e82a679c66..01f88827fe682283c95011b0c96fb582040ec757 100644 (file)
@@ -1115,14 +1115,19 @@ static int help(void) {
         return 0;
 }
 
-static int prepare_reexecute(Manager *m, FILE **_f, FDSet **_fds, bool switching_root) {
+static int prepare_reexecute(
+                Manager *m,
+                FILE **ret_f,
+                FDSet **ret_fds,
+                bool switching_root) {
+
         _cleanup_fdset_free_ FDSet *fds = NULL;
         _cleanup_fclose_ FILE *f = NULL;
         int r;
 
         assert(m);
-        assert(_f);
-        assert(_fds);
+        assert(ret_f);
+        assert(ret_fds);
 
         r = manager_open_serialization(m, &f);
         if (r < 0)
@@ -1151,8 +1156,8 @@ static int prepare_reexecute(Manager *m, FILE **_f, FDSet **_fds, bool switching
         if (r < 0)
                 return log_error_errno(r, "Failed to disable O_CLOEXEC for serialization fds: %m");
 
-        *_f = TAKE_PTR(f);
-        *_fds = TAKE_PTR(fds);
+        *ret_f = TAKE_PTR(f);
+        *ret_fds = TAKE_PTR(fds);
 
         return 0;
 }
@@ -1668,7 +1673,7 @@ static int invoke_main_loop(
                         return log_emergency_errno(r, "Failed to run main loop: %m");
                 }
 
-                switch (m->exit_code) {
+                switch ((ManagerObjective) r) {
 
                 case MANAGER_RELOAD: {
                         LogTarget saved_log_target;
@@ -1695,7 +1700,8 @@ static int invoke_main_loop(
 
                         r = manager_reload(m);
                         if (r < 0)
-                                log_warning_errno(r, "Failed to reload, ignoring: %m");
+                                /* Reloading failed before the point of no return. Let's continue running as if nothing happened. */
+                                m->objective = MANAGER_OK;
 
                         break;
                 }
@@ -1759,19 +1765,19 @@ static int invoke_main_loop(
                 case MANAGER_POWEROFF:
                 case MANAGER_HALT:
                 case MANAGER_KEXEC: {
-                        static const char * const table[_MANAGER_EXIT_CODE_MAX] = {
-                                [MANAGER_EXIT] = "exit",
-                                [MANAGER_REBOOT] = "reboot",
+                        static const char * const table[_MANAGER_OBJECTIVE_MAX] = {
+                                [MANAGER_EXIT]     = "exit",
+                                [MANAGER_REBOOT]   = "reboot",
                                 [MANAGER_POWEROFF] = "poweroff",
-                                [MANAGER_HALT] = "halt",
-                                [MANAGER_KEXEC] = "kexec"
+                                [MANAGER_HALT]     = "halt",
+                                [MANAGER_KEXEC]    = "kexec",
                         };
 
                         log_notice("Shutting down.");
 
                         *ret_reexecute = false;
                         *ret_retval = m->return_value;
-                        assert_se(*ret_shutdown_verb = table[m->exit_code]);
+                        assert_se(*ret_shutdown_verb = table[m->objective]);
                         *ret_fds = NULL;
                         *ret_switch_root_dir = *ret_switch_root_init = NULL;
 
@@ -1779,7 +1785,7 @@ static int invoke_main_loop(
                 }
 
                 default:
-                        assert_not_reached("Unknown exit code.");
+                        assert_not_reached("Unknown or unexpected manager objective.");
                 }
         }
 }
@@ -2425,7 +2431,6 @@ int main(int argc, char *argv[]) {
 
         r = manager_startup(m, arg_serialization, fds);
         if (r < 0) {
-                log_error_errno(r, "Failed to fully start up daemon: %m");
                 error_message = "Failed to start up manager";
                 goto finish;
         }
index c361a1ec076cf7d9c7a33d0ea8f628ab29d11c21..6e29ddde43c2ade1004b8718a062742263cd128e 100644 (file)
@@ -351,7 +351,7 @@ static int manager_setup_time_change(Manager *m) {
 
         assert(m);
 
-        if (m->test_run_flags)
+        if (MANAGER_IS_TEST_RUN(m))
                 return 0;
 
         m->time_change_event_source = sd_event_source_unref(m->time_change_event_source);
@@ -407,7 +407,7 @@ static int manager_setup_timezone_change(Manager *m) {
 
         assert(m);
 
-        if (m->test_run_flags != 0)
+        if (MANAGER_IS_TEST_RUN(m))
                 return 0;
 
         /* We watch /etc/localtime for three events: change of the link count (which might mean removal from /etc even
@@ -446,7 +446,7 @@ static int enable_special_signals(Manager *m) {
 
         assert(m);
 
-        if (m->test_run_flags)
+        if (MANAGER_IS_TEST_RUN(m))
                 return 0;
 
         /* Enable that we get SIGINT on control-alt-del. In containers
@@ -711,28 +711,51 @@ static int manager_setup_sigchld_event_source(Manager *m) {
         return 0;
 }
 
-int manager_new(UnitFileScope scope, unsigned test_run_flags, Manager **_m) {
+int manager_new(UnitFileScope scope, ManagerTestRunFlags test_run_flags, Manager **_m) {
         _cleanup_(manager_freep) Manager *m = NULL;
         int r;
 
         assert(_m);
         assert(IN_SET(scope, UNIT_FILE_SYSTEM, UNIT_FILE_USER));
 
-        m = new0(Manager, 1);
+        m = new(Manager, 1);
         if (!m)
                 return -ENOMEM;
 
-        m->unit_file_scope = scope;
-        m->exit_code = _MANAGER_EXIT_CODE_INVALID;
-        m->default_timer_accuracy_usec = USEC_PER_MINUTE;
-        m->default_memory_accounting = MEMORY_ACCOUNTING_DEFAULT;
-        m->default_tasks_accounting = true;
-        m->default_tasks_max = UINT64_MAX;
-        m->default_timeout_start_usec = DEFAULT_TIMEOUT_USEC;
-        m->default_timeout_stop_usec = DEFAULT_TIMEOUT_USEC;
-        m->default_restart_usec = DEFAULT_RESTART_USEC;
-        m->original_log_level = -1;
-        m->original_log_target = _LOG_TARGET_INVALID;
+        *m = (Manager) {
+                .unit_file_scope = scope,
+                .objective = _MANAGER_OBJECTIVE_INVALID,
+
+                .default_timer_accuracy_usec = USEC_PER_MINUTE,
+                .default_memory_accounting = MEMORY_ACCOUNTING_DEFAULT,
+                .default_tasks_accounting = true,
+                .default_tasks_max = UINT64_MAX,
+                .default_timeout_start_usec = DEFAULT_TIMEOUT_USEC,
+                .default_timeout_stop_usec = DEFAULT_TIMEOUT_USEC,
+                .default_restart_usec = DEFAULT_RESTART_USEC,
+
+                .original_log_level = -1,
+                .original_log_target = _LOG_TARGET_INVALID,
+
+                .notify_fd = -1,
+                .cgroups_agent_fd = -1,
+                .signal_fd = -1,
+                .time_change_fd = -1,
+                .user_lookup_fds = { -1, -1 },
+                .private_listen_fd = -1,
+                .dev_autofs_fd = -1,
+                .cgroup_inotify_fd = -1,
+                .pin_cgroupfs_fd = -1,
+                .ask_password_inotify_fd = -1,
+                .idle_pipe = { -1, -1, -1, -1},
+
+                 /* start as id #1, so that we can leave #0 around as "null-like" value */
+                .current_job_id = 1,
+
+                .have_ask_password = -EINVAL, /* we don't know */
+                .first_boot = -1,
+                .test_run_flags = test_run_flags,
+        };
 
 #if ENABLE_EFI
         if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0)
@@ -756,21 +779,6 @@ int manager_new(UnitFileScope scope, unsigned test_run_flags, Manager **_m) {
                 m->invocation_log_format_string = "USER_INVOCATION_ID=%s";
         }
 
-        m->idle_pipe[0] = m->idle_pipe[1] = m->idle_pipe[2] = m->idle_pipe[3] = -1;
-
-        m->pin_cgroupfs_fd = m->notify_fd = m->cgroups_agent_fd = m->signal_fd = m->time_change_fd =
-                m->dev_autofs_fd = m->private_listen_fd = m->cgroup_inotify_fd =
-                m->ask_password_inotify_fd = -1;
-
-        m->user_lookup_fds[0] = m->user_lookup_fds[1] = -1;
-
-        m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */
-
-        m->have_ask_password = -EINVAL; /* we don't know */
-        m->first_boot = -1;
-
-        m->test_run_flags = test_run_flags;
-
         /* Reboot immediately if the user hits C-A-D more often than 7x per 2s */
         RATELIMIT_INIT(m->ctrl_alt_del_ratelimit, 2 * USEC_PER_SEC, 7);
 
@@ -857,7 +865,7 @@ int manager_new(UnitFileScope scope, unsigned test_run_flags, Manager **_m) {
 static int manager_setup_notify(Manager *m) {
         int r;
 
-        if (m->test_run_flags)
+        if (MANAGER_IS_TEST_RUN(m))
                 return 0;
 
         if (m->notify_fd < 0) {
@@ -936,7 +944,7 @@ static int manager_setup_cgroups_agent(Manager *m) {
          * to it. The system instance hence listens on this special socket, but the user instances listen on the system
          * bus for these messages. */
 
-        if (m->test_run_flags)
+        if (MANAGER_IS_TEST_RUN(m))
                 return 0;
 
         if (!MANAGER_IS_SYSTEM(m))
@@ -1275,8 +1283,8 @@ static void manager_clear_jobs_and_units(Manager *m) {
 }
 
 Manager* manager_free(Manager *m) {
-        UnitType c;
         ExecDirectoryType dt;
+        UnitType c;
 
         if (!m)
                 return NULL;
@@ -1287,8 +1295,8 @@ Manager* manager_free(Manager *m) {
                 if (unit_vtable[c]->shutdown)
                         unit_vtable[c]->shutdown(m);
 
-        /* If we reexecute ourselves, we keep the root cgroup around */
-        manager_shutdown_cgroup(m, m->exit_code != MANAGER_REEXECUTE);
+        /* Keep the cgroup hierarchy in place except when we know we are going down for good */
+        manager_shutdown_cgroup(m, IN_SET(m->objective, MANAGER_EXIT, MANAGER_REBOOT, MANAGER_POWEROFF, MANAGER_HALT, MANAGER_KEXEC));
 
         lookup_paths_flush_generator(&m->lookup_paths);
 
@@ -1513,7 +1521,7 @@ static bool manager_dbus_is_running(Manager *m, bool deserialized) {
          * and the service unit. If the 'deserialized' parameter is true we'll check the deserialized state of the unit
          * rather than the current one. */
 
-        if (m->test_run_flags != 0)
+        if (MANAGER_IS_TEST_RUN(m))
                 return false;
 
         u = manager_get_unit(m, SPECIAL_DBUS_SOCKET);
@@ -1561,7 +1569,7 @@ static void manager_preset_all(Manager *m) {
         if (!MANAGER_IS_SYSTEM(m))
                 return;
 
-        if (m->test_run_flags != 0)
+        if (MANAGER_IS_TEST_RUN(m))
                 return;
 
         /* If this is the first boot, and we are in the host system, then preset everything */
@@ -1573,6 +1581,49 @@ static void manager_preset_all(Manager *m) {
                 log_info("Populated /etc with preset unit settings.");
 }
 
+static void manager_vacuum(Manager *m) {
+        assert(m);
+
+        /* Release any dynamic users no longer referenced */
+        dynamic_user_vacuum(m, true);
+
+        /* Release any references to UIDs/GIDs no longer referenced, and destroy any IPC owned by them */
+        manager_vacuum_uid_refs(m);
+        manager_vacuum_gid_refs(m);
+
+        /* Release any runtimes no longer referenced */
+        exec_runtime_vacuum(m);
+}
+
+static void manager_ready(Manager *m) {
+        assert(m);
+
+        /* After having loaded everything, do the final round of catching up with what might have changed */
+
+        m->objective = MANAGER_OK; /* Tell everyone we are up now */
+
+        /* It might be safe to log to the journal now and connect to dbus */
+        manager_recheck_journal(m);
+        manager_recheck_dbus(m);
+
+        /* Sync current state of bus names with our set of listening units */
+        (void) manager_enqueue_sync_bus_names(m);
+
+        /* Let's finally catch up with any changes that took place while we were reloading/reexecing */
+        manager_catchup(m);
+}
+
+static Manager* manager_reloading_start(Manager *m) {
+        m->n_reloading++;
+        return m;
+}
+static void manager_reloading_stopp(Manager **m) {
+        if (*m) {
+                assert((*m)->n_reloading > 0);
+                (*m)->n_reloading--;
+        }
+}
+
 int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
         int r;
 
@@ -1581,98 +1632,92 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
         /* If we are running in test mode, we still want to run the generators,
          * but we should not touch the real generator directories. */
         r = lookup_paths_init(&m->lookup_paths, m->unit_file_scope,
-                              m->test_run_flags ? LOOKUP_PATHS_TEMPORARY_GENERATED : 0,
+                              MANAGER_IS_TEST_RUN(m) ? LOOKUP_PATHS_TEMPORARY_GENERATED : 0,
                               NULL);
         if (r < 0)
-                return r;
-
-        r = manager_run_environment_generators(m);
-        if (r < 0)
-                return r;
+                return log_error_errno(r, "Failed to initialize path lookup table: %m");
 
         dual_timestamp_get(m->timestamps + manager_timestamp_initrd_mangle(MANAGER_TIMESTAMP_GENERATORS_START));
-        r = manager_run_generators(m);
+        r = manager_run_environment_generators(m);
+        if (r >= 0)
+                r = manager_run_generators(m);
         dual_timestamp_get(m->timestamps + manager_timestamp_initrd_mangle(MANAGER_TIMESTAMP_GENERATORS_FINISH));
         if (r < 0)
                 return r;
 
         manager_preset_all(m);
-        lookup_paths_reduce(&m->lookup_paths);
-        manager_build_unit_path_cache(m);
 
-        /* If we will deserialize make sure that during enumeration
-         * this is already known, so we increase the counter here
-         * already */
-        if (serialization)
-                m->n_reloading++;
+        r = lookup_paths_reduce(&m->lookup_paths);
+        if (r < 0)
+                log_warning_errno(r, "Failed ot reduce unit file paths, ignoring: %m");
 
-        /* First, enumerate what we can from all config files */
-        dual_timestamp_get(m->timestamps + manager_timestamp_initrd_mangle(MANAGER_TIMESTAMP_UNITS_LOAD_START));
-        manager_enumerate_perpetual(m);
-        manager_enumerate(m);
-        dual_timestamp_get(m->timestamps + manager_timestamp_initrd_mangle(MANAGER_TIMESTAMP_UNITS_LOAD_FINISH));
+        manager_build_unit_path_cache(m);
 
-        /* Second, deserialize if there is something to deserialize */
-        if (serialization) {
-                r = manager_deserialize(m, serialization, fds);
-                if (r < 0)
-                        return log_error_errno(r, "Deserialization failed: %m");
-        }
+        {
+                /* This block is (optionally) done with the reloading counter bumped */
+                _cleanup_(manager_reloading_stopp) Manager *reloading = NULL;
 
-        /* Any fds left? Find some unit which wants them. This is
-         * useful to allow container managers to pass some file
-         * descriptors to us pre-initialized. This enables
-         * socket-based activation of entire containers. */
-        manager_distribute_fds(m, fds);
+                /* If we will deserialize make sure that during enumeration this is already known, so we increase the
+                 * counter here already */
+                if (serialization)
+                        reloading = manager_reloading_start(m);
 
-        /* We might have deserialized the notify fd, but if we didn't
-         * then let's create the bus now */
-        r = manager_setup_notify(m);
-        if (r < 0)
-                /* No sense to continue without notifications, our children would fail anyway. */
-                return r;
+                /* First, enumerate what we can from all config files */
+                dual_timestamp_get(m->timestamps + manager_timestamp_initrd_mangle(MANAGER_TIMESTAMP_UNITS_LOAD_START));
+                manager_enumerate_perpetual(m);
+                manager_enumerate(m);
+                dual_timestamp_get(m->timestamps + manager_timestamp_initrd_mangle(MANAGER_TIMESTAMP_UNITS_LOAD_FINISH));
 
-        r = manager_setup_cgroups_agent(m);
-        if (r < 0)
-                /* Likewise, no sense to continue without empty cgroup notifications. */
-                return r;
+                /* Second, deserialize if there is something to deserialize */
+                if (serialization) {
+                        r = manager_deserialize(m, serialization, fds);
+                        if (r < 0)
+                                return log_error_errno(r, "Deserialization failed: %m");
+                }
 
-        r = manager_setup_user_lookup_fd(m);
-        if (r < 0)
-                /* This shouldn't fail, except if things are really broken. */
-                return r;
+                /* Any fds left? Find some unit which wants them. This is useful to allow container managers to pass
+                 * some file descriptors to us pre-initialized. This enables socket-based activation of entire
+                 * containers. */
+                manager_distribute_fds(m, fds);
 
-        /* Connect to the bus if we are good for it */
-        manager_setup_bus(m);
+                /* We might have deserialized the notify fd, but if we didn't then let's create the bus now */
+                r = manager_setup_notify(m);
+                if (r < 0)
+                        /* No sense to continue without notifications, our children would fail anyway. */
+                        return r;
 
-        /* Now that we are connected to all possible busses, let's deserialize who is tracking us. */
-        (void) bus_track_coldplug(m, &m->subscribed, false, m->deserialized_subscribed);
-        m->deserialized_subscribed = strv_free(m->deserialized_subscribed);
+                r = manager_setup_cgroups_agent(m);
+                if (r < 0)
+                        /* Likewise, no sense to continue without empty cgroup notifications. */
+                        return r;
 
-        /* Third, fire things up! */
-        manager_coldplug(m);
+                r = manager_setup_user_lookup_fd(m);
+                if (r < 0)
+                        /* This shouldn't fail, except if things are really broken. */
+                        return r;
 
-        /* Release any dynamic users no longer referenced */
-        dynamic_user_vacuum(m, true);
+                /* Connect to the bus if we are good for it */
+                manager_setup_bus(m);
 
-        exec_runtime_vacuum(m);
+                /* Now that we are connected to all possible busses, let's deserialize who is tracking us. */
+                r = bus_track_coldplug(m, &m->subscribed, false, m->deserialized_subscribed);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to deserialized tracked clients, ignoring: %m");
+                m->deserialized_subscribed = strv_free(m->deserialized_subscribed);
 
-        /* Release any references to UIDs/GIDs no longer referenced, and destroy any IPC owned by them */
-        manager_vacuum_uid_refs(m);
-        manager_vacuum_gid_refs(m);
+                /* Third, fire things up! */
+                manager_coldplug(m);
 
-        if (serialization) {
-                assert(m->n_reloading > 0);
-                m->n_reloading--;
+                /* Clean up runtime objects */
+                manager_vacuum(m);
 
-                /* Let's wait for the UnitNew/JobNew messages being
-                 * sent, before we notify that the reload is
-                 * finished */
-                m->send_reloading_done = true;
+                if (serialization)
+                        /* Let's wait for the UnitNew/JobNew messages being sent, before we notify that the
+                         * reload is finished */
+                        m->send_reloading_done = true;
         }
 
-        /* Let's finally catch up with any changes that took place while we were reloading/reexecing */
-        manager_catchup(m);
+        manager_ready(m);
 
         return 0;
 }
@@ -2543,9 +2588,10 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
         case SIGTERM:
                 if (MANAGER_IS_SYSTEM(m)) {
                         /* This is for compatibility with the original sysvinit */
-                        r = verify_run_space_and_log("Refusing to reexecute");
-                        if (r >= 0)
-                                m->exit_code = MANAGER_REEXECUTE;
+                        if (verify_run_space_and_log("Refusing to reexecute") < 0)
+                                break;
+
+                        m->objective = MANAGER_REEXECUTE;
                         break;
                 }
 
@@ -2601,9 +2647,10 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
         }
 
         case SIGHUP:
-                r = verify_run_space_and_log("Refusing to reload");
-                if (r >= 0)
-                        m->exit_code = MANAGER_RELOAD;
+                if (verify_run_space_and_log("Refusing to reload") < 0)
+                        break;
+
+                m->objective = MANAGER_RELOAD;
                 break;
 
         default: {
@@ -2623,7 +2670,7 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
                 };
 
                 /* Starting SIGRTMIN+13, so that target halt and system halt are 10 apart */
-                static const ManagerExitCode code_table[] = {
+                static const ManagerObjective objective_table[] = {
                         [0] = MANAGER_HALT,
                         [1] = MANAGER_POWEROFF,
                         [2] = MANAGER_REBOOT,
@@ -2639,8 +2686,8 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
                 }
 
                 if ((int) sfsi.ssi_signo >= SIGRTMIN+13 &&
-                    (int) sfsi.ssi_signo < SIGRTMIN+13+(int) ELEMENTSOF(code_table)) {
-                        m->exit_code = code_table[sfsi.ssi_signo - SIGRTMIN - 13];
+                    (int) sfsi.ssi_signo < SIGRTMIN+13+(int) ELEMENTSOF(objective_table)) {
+                        m->objective = objective_table[sfsi.ssi_signo - SIGRTMIN - 13];
                         break;
                 }
 
@@ -2664,7 +2711,7 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
 
                 case 24:
                         if (MANAGER_IS_USER(m)) {
-                                m->exit_code = MANAGER_EXIT;
+                                m->objective = MANAGER_EXIT;
                                 return 0;
                         }
 
@@ -2793,7 +2840,7 @@ int manager_loop(Manager *m) {
         RATELIMIT_DEFINE(rl, 1*USEC_PER_SEC, 50000);
 
         assert(m);
-        m->exit_code = MANAGER_OK;
+        assert(m->objective == MANAGER_OK); /* Ensure manager_startup() has been called */
 
         /* Release the path cache */
         m->unit_path_cache = set_free_free(m->unit_path_cache);
@@ -2805,7 +2852,7 @@ int manager_loop(Manager *m) {
         if (r < 0)
                 return log_error_errno(r, "Failed to enable SIGCHLD event source: %m");
 
-        while (m->exit_code == MANAGER_OK) {
+        while (m->objective == MANAGER_OK) {
                 usec_t wait_usec;
 
                 if (m->runtime_watchdog > 0 && m->runtime_watchdog != USEC_INFINITY && MANAGER_IS_SYSTEM(m))
@@ -2851,7 +2898,7 @@ int manager_loop(Manager *m) {
                         return log_error_errno(r, "Failed to run event loop: %m");
         }
 
-        return m->exit_code;
+        return m->objective;
 }
 
 int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u) {
@@ -3033,7 +3080,12 @@ int manager_open_serialization(Manager *m, FILE **_f) {
         return 0;
 }
 
-int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) {
+int manager_serialize(
+                Manager *m,
+                FILE *f,
+                FDSet *fds,
+                bool switching_root) {
+
         ManagerTimestamp q;
         const char *t;
         Iterator i;
@@ -3044,7 +3096,7 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) {
         assert(f);
         assert(fds);
 
-        m->n_reloading++;
+        _cleanup_(manager_reloading_stopp) Manager *reloading = manager_reloading_start(m);
 
         fprintf(f, "current-job-id=%"PRIu32"\n", m->current_job_id);
         fprintf(f, "n-installed-jobs=%u\n", m->n_installed_jobs);
@@ -3073,11 +3125,8 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) {
                         continue;
 
                 t = manager_timestamp_to_string(q);
-                {
-                        char field[strlen(t) + STRLEN("-timestamp") + 1];
-                        strcpy(stpcpy(field, t), "-timestamp");
-                        dual_timestamp_serialize(f, field, m->timestamps + q);
-                }
+                const char *field = strjoina(t, "-timestamp");
+                dual_timestamp_serialize(f, field, m->timestamps + q);
         }
 
         if (!switching_root)
@@ -3142,15 +3191,10 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) {
                 fputc('\n', f);
 
                 r = unit_serialize(u, f, fds, !switching_root);
-                if (r < 0) {
-                        m->n_reloading--;
+                if (r < 0)
                         return r;
-                }
         }
 
-        assert(m->n_reloading > 0);
-        m->n_reloading--;
-
         r = fflush_and_check(f);
         if (r < 0)
                 return r;
@@ -3170,19 +3214,20 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
 
         log_debug("Deserializing state...");
 
-        m->n_reloading++;
+        /* If we are not in reload mode yet, enter it now. Not that this is recursive, a caller might already have
+         * increased it to non-zero, which is why we just increase it by one here and down again at the end of this
+         * call. */
+        _cleanup_(manager_reloading_stopp) Manager *reloading = manager_reloading_start(m);
 
         for (;;) {
                 char line[LINE_MAX];
                 const char *val, *l;
 
+                errno = 0;
                 if (!fgets(line, sizeof(line), f)) {
-                        if (feof(f))
-                                r = 0;
-                        else
-                                r = -errno;
-
-                        goto finish;
+                        if (!feof(f))
+                                return -errno ?: -EIO;
+                        return 0;
                 }
 
                 char_array_0(line);
@@ -3195,7 +3240,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                         uint32_t id;
 
                         if (safe_atou32(val, &id) < 0)
-                                log_notice("Failed to parse current job id value %s", val);
+                                log_notice("Failed to parse current job id value '%s', ignoring.", val);
                         else
                                 m->current_job_id = MAX(m->current_job_id, id);
 
@@ -3203,7 +3248,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                         uint32_t n;
 
                         if (safe_atou32(val, &n) < 0)
-                                log_notice("Failed to parse installed jobs counter %s", val);
+                                log_notice("Failed to parse installed jobs counter '%s', ignoring.", val);
                         else
                                 m->n_installed_jobs += n;
 
@@ -3211,7 +3256,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                         uint32_t n;
 
                         if (safe_atou32(val, &n) < 0)
-                                log_notice("Failed to parse failed jobs counter %s", val);
+                                log_notice("Failed to parse failed jobs counter '%s', ignoring.", val);
                         else
                                 m->n_failed_jobs += n;
 
@@ -3220,7 +3265,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
 
                         b = parse_boolean(val);
                         if (b < 0)
-                                log_notice("Failed to parse taint /usr flag %s", val);
+                                log_notice("Failed to parse taint /usr flag '%s', ignoring.", val);
                         else
                                 m->taint_usr = m->taint_usr || b;
 
@@ -3229,7 +3274,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
 
                         b = parse_boolean(val);
                         if (b < 0)
-                                log_notice("Failed to parse ready-sent flag %s", val);
+                                log_notice("Failed to parse ready-sent flag '%s', ignoring.", val);
                         else
                                 m->ready_sent = m->ready_sent || b;
 
@@ -3238,7 +3283,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
 
                         b = parse_boolean(val);
                         if (b < 0)
-                                log_notice("Failed to parse taint-logged flag %s", val);
+                                log_notice("Failed to parse taint-logged flag '%s', ignoring.", val);
                         else
                                 m->taint_logged = m->taint_logged || b;
 
@@ -3247,7 +3292,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
 
                         b = parse_boolean(val);
                         if (b < 0)
-                                log_notice("Failed to parse service-watchdogs flag %s", val);
+                                log_notice("Failed to parse service-watchdogs flag '%s', ignoring.", val);
                         else
                                 m->service_watchdogs = b;
 
@@ -3256,7 +3301,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
 
                         s = show_status_from_string(val);
                         if (s < 0)
-                                log_notice("Failed to parse show-status flag %s", val);
+                                log_notice("Failed to parse show-status flag '%s', ignoring.", val);
                         else
                                 manager_set_show_status(m, s);
 
@@ -3281,15 +3326,15 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                 } else if (startswith(l, "env=")) {
                         r = deserialize_environment(&m->environment, l);
                         if (r == -ENOMEM)
-                                goto finish;
+                                return r;
                         if (r < 0)
-                                log_notice_errno(r, "Failed to parse environment entry: \"%s\": %m", l);
+                                log_notice_errno(r, "Failed to parse environment entry: \"%s\", ignoring: %m", l);
 
                 } else if ((val = startswith(l, "notify-fd="))) {
                         int fd;
 
                         if (safe_atoi(val, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
-                                log_notice("Failed to parse notify fd: \"%s\"", val);
+                                log_notice("Failed to parse notify fd, ignoring: \"%s\"", val);
                         else {
                                 m->notify_event_source = sd_event_source_unref(m->notify_event_source);
                                 safe_close(m->notify_fd);
@@ -3297,22 +3342,15 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                         }
 
                 } else if ((val = startswith(l, "notify-socket="))) {
-                        char *n;
-
-                        n = strdup(val);
-                        if (!n) {
-                                r = -ENOMEM;
-                                goto finish;
-                        }
-
-                        free(m->notify_socket);
-                        m->notify_socket = n;
+                        r = free_and_strdup(&m->notify_socket, val);
+                        if (r < 0)
+                                return r;
 
                 } else if ((val = startswith(l, "cgroups-agent-fd="))) {
                         int fd;
 
                         if (safe_atoi(val, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
-                                log_notice("Failed to parse cgroups agent fd: %s", val);
+                                log_notice("Failed to parse cgroups agent fd, ignoring.: %s", val);
                         else {
                                 m->cgroups_agent_event_source = sd_event_source_unref(m->cgroups_agent_event_source);
                                 safe_close(m->cgroups_agent_fd);
@@ -3323,7 +3361,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                         int fd0, fd1;
 
                         if (sscanf(val, "%i %i", &fd0, &fd1) != 2 || fd0 < 0 || fd1 < 0 || fd0 == fd1 || !fdset_contains(fds, fd0) || !fdset_contains(fds, fd1))
-                                log_notice("Failed to parse user lookup fd: %s", val);
+                                log_notice("Failed to parse user lookup fd, ignoring: %s", val);
                         else {
                                 m->user_lookup_event_source = sd_event_source_unref(m->user_lookup_event_source);
                                 safe_close_pair(m->user_lookup_fds);
@@ -3342,7 +3380,8 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                 else if ((val = startswith(l, "subscribed="))) {
 
                         if (strv_extend(&m->deserialized_subscribed, val) < 0)
-                                log_oom();
+                                return -ENOMEM;
+
                 } else {
                         ManagerTimestamp q;
 
@@ -3359,7 +3398,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                         if (q < _MANAGER_TIMESTAMP_MAX) /* found it */
                                 dual_timestamp_deserialize(val, m->timestamps + q);
                         else if (!startswith(l, "kdbus-fd=")) /* ignore kdbus */
-                                log_notice("Unknown serialization item '%s'", l);
+                                log_notice("Unknown serialization item '%s', ignoring.", l);
                 }
         }
 
@@ -3369,13 +3408,11 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                 const char* unit_name;
 
                 /* Start marker */
+                errno = 0;
                 if (!fgets(name, sizeof(name), f)) {
-                        if (feof(f))
-                                r = 0;
-                        else
-                                r = -errno;
-
-                        goto finish;
+                        if (!feof(f))
+                                return -errno ?: -EIO;
+                        return 0;
                 }
 
                 char_array_0(name);
@@ -3383,29 +3420,24 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
 
                 r = manager_load_unit(m, unit_name, NULL, NULL, &u);
                 if (r < 0) {
-                        log_notice_errno(r, "Failed to load unit \"%s\", skipping deserialization: %m", unit_name);
                         if (r == -ENOMEM)
-                                goto finish;
+                                return r;
+
+                        log_notice_errno(r, "Failed to load unit \"%s\", skipping deserialization: %m", unit_name);
                         unit_deserialize_skip(f);
                         continue;
                 }
 
                 r = unit_deserialize(u, f, fds);
                 if (r < 0) {
-                        log_notice_errno(r, "Failed to deserialize unit \"%s\": %m", unit_name);
                         if (r == -ENOMEM)
-                                goto finish;
+                                return r;
+
+                        log_notice_errno(r, "Failed to deserialize unit \"%s\": %m", unit_name);
                 }
         }
 
-finish:
-        if (ferror(f))
-                r = -EIO;
-
-        assert(m->n_reloading > 0);
-        m->n_reloading--;
-
-        return r;
+        return 0;
 }
 
 static void manager_flush_finished_jobs(Manager *m) {
@@ -3420,37 +3452,40 @@ static void manager_flush_finished_jobs(Manager *m) {
 }
 
 int manager_reload(Manager *m) {
-        int r, q;
-        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_(manager_reloading_stopp) Manager *reloading = NULL;
         _cleanup_fdset_free_ FDSet *fds = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        int r;
 
         assert(m);
 
         r = manager_open_serialization(m, &f);
         if (r < 0)
-                return r;
-
-        m->n_reloading++;
-        bus_manager_send_reloading(m, true);
+                return log_error_errno(r, "Failed to create serialization file: %m");
 
         fds = fdset_new();
-        if (!fds) {
-                m->n_reloading--;
-                return -ENOMEM;
-        }
+        if (!fds)
+                return log_oom();
+
+        /* We are officially in reload mode from here on. */
+        reloading = manager_reloading_start(m);
 
         r = manager_serialize(m, f, fds, false);
-        if (r < 0) {
-                m->n_reloading--;
-                return r;
-        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to serialize manager: %m");
 
-        if (fseeko(f, 0, SEEK_SET) < 0) {
-                m->n_reloading--;
-                return -errno;
-        }
+        if (fseeko(f, 0, SEEK_SET) < 0)
+                return log_error_errno(errno, "Failed to seek to beginning of serialization: %m");
+
+        /* 💀 This is the point of no return, from here on there is no way back. 💀 */
+        reloading = NULL;
+
+        bus_manager_send_reloading(m, true);
+
+        /* Start by flushing out all jobs and units, all generated units, all runtime environments, all dynamic users
+         * and everything else that is worth flushing out. We'll get it all back from the serialization — if we need
+         * it.*/
 
-        /* From here on there is no way back. */
         manager_clear_jobs_and_units(m);
         lookup_paths_flush_generator(&m->lookup_paths);
         lookup_paths_free(&m->lookup_paths);
@@ -3460,79 +3495,52 @@ int manager_reload(Manager *m) {
         m->gid_refs = hashmap_free(m->gid_refs);
 
         r = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL);
+        if (r < 0)
+                log_warning_errno(r, "Failed to initialize path lookup table, ignoring: %m");
 
-        q = manager_run_environment_generators(m);
-        if (q < 0 && r >= 0)
-                r = q;
+        (void) manager_run_environment_generators(m);
+        (void) manager_run_generators(m);
 
-        /* Find new unit paths */
-        q = manager_run_generators(m);
-        if (q < 0 && r >= 0)
-                r = q;
+        r = lookup_paths_reduce(&m->lookup_paths);
+        if (r < 0)
+                log_warning_errno(r, "Failed ot reduce unit file paths, ignoring: %m");
 
-        lookup_paths_reduce(&m->lookup_paths);
         manager_build_unit_path_cache(m);
 
-        /* First, enumerate what we can from all config files */
+        /* First, enumerate what we can from kernel and suchlike */
+        manager_enumerate_perpetual(m);
         manager_enumerate(m);
 
         /* Second, deserialize our stored data */
-        q = manager_deserialize(m, f, fds);
-        if (q < 0) {
-                log_error_errno(q, "Deserialization failed: %m");
-
-                if (r >= 0)
-                        r = q;
-        }
+        r = manager_deserialize(m, f, fds);
+        if (r < 0)
+                log_warning_errno(r, "Deserialization failed, proceeding anyway: %m");
 
+        /* We don't need the serialization anymore */
         f = safe_fclose(f);
 
-        /* Re-register notify_fd as event source */
-        q = manager_setup_notify(m);
-        if (q < 0 && r >= 0)
-                r = q;
-
-        q = manager_setup_cgroups_agent(m);
-        if (q < 0 && r >= 0)
-                r = q;
-
-        q = manager_setup_user_lookup_fd(m);
-        if (q < 0 && r >= 0)
-                r = q;
+        /* Re-register notify_fd as event source, and set up other sockets/communication channels we might need */
+        (void) manager_setup_notify(m);
+        (void) manager_setup_cgroups_agent(m);
+        (void) manager_setup_user_lookup_fd(m);
 
         /* Third, fire things up! */
         manager_coldplug(m);
 
-        /* Release any dynamic users no longer referenced */
-        dynamic_user_vacuum(m, true);
-
-        /* Release any references to UIDs/GIDs no longer referenced, and destroy any IPC owned by them */
-        manager_vacuum_uid_refs(m);
-        manager_vacuum_gid_refs(m);
-
-        exec_runtime_vacuum(m);
+        /* Clean up runtime objects no longer referenced */
+        manager_vacuum(m);
 
+        /* Consider the reload process complete now. */
         assert(m->n_reloading > 0);
         m->n_reloading--;
 
-        /* It might be safe to log to the journal now and connect to dbus */
-        manager_recheck_journal(m);
-        manager_recheck_dbus(m);
-
-        /* Let's finally catch up with any changes that took place while we were reloading/reexecing */
-        manager_catchup(m);
-
-        /* Sync current state of bus names with our set of listening units */
-        q = manager_enqueue_sync_bus_names(m);
-        if (q < 0 && r >= 0)
-                r = q;
+        manager_ready(m);
 
         if (!MANAGER_IS_RELOADING(m))
                 manager_flush_finished_jobs(m);
 
         m->send_reloading_done = true;
-
-        return r;
+        return 0;
 }
 
 void manager_reset_failed(Manager *m) {
@@ -3583,7 +3591,7 @@ static void manager_notify_finished(Manager *m) {
         char userspace[FORMAT_TIMESPAN_MAX], initrd[FORMAT_TIMESPAN_MAX], kernel[FORMAT_TIMESPAN_MAX], sum[FORMAT_TIMESPAN_MAX];
         usec_t firmware_usec, loader_usec, kernel_usec, initrd_usec, userspace_usec, total_usec;
 
-        if (m->test_run_flags)
+        if (MANAGER_IS_TEST_RUN(m))
                 return;
 
         if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0) {
@@ -3778,7 +3786,7 @@ static int manager_run_environment_generators(Manager *m) {
         const char **paths;
         void* args[] = {&tmp, &tmp, &m->environment};
 
-        if (m->test_run_flags && !(m->test_run_flags & MANAGER_TEST_RUN_ENV_GENERATORS))
+        if (MANAGER_IS_TEST_RUN(m) && !(m->test_run_flags & MANAGER_TEST_RUN_ENV_GENERATORS))
                 return 0;
 
         paths = MANAGER_IS_SYSTEM(m) ? system_env_generator_binary_paths : user_env_generator_binary_paths;
@@ -3796,7 +3804,7 @@ static int manager_run_generators(Manager *m) {
 
         assert(m);
 
-        if (m->test_run_flags && !(m->test_run_flags & MANAGER_TEST_RUN_GENERATORS))
+        if (MANAGER_IS_TEST_RUN(m) && !(m->test_run_flags & MANAGER_TEST_RUN_GENERATORS))
                 return 0;
 
         paths = generator_binary_paths(m->unit_file_scope);
@@ -3807,8 +3815,10 @@ static int manager_run_generators(Manager *m) {
                 return 0;
 
         r = lookup_paths_mkdir_generator(&m->lookup_paths);
-        if (r < 0)
+        if (r < 0) {
+                log_error_errno(r, "Failed to create generator directories: %m");
                 goto finish;
+        }
 
         argv[0] = NULL; /* Leave this empty, execute_directory() will fill something in */
         argv[1] = m->lookup_paths.generator;
@@ -3817,8 +3827,10 @@ static int manager_run_generators(Manager *m) {
         argv[4] = NULL;
 
         RUN_WITH_UMASK(0022)
-                execute_directories((const char* const*) paths, DEFAULT_TIMEOUT_USEC,
-                                    NULL, NULL, (char**) argv, m->environment);
+                (void) execute_directories((const char* const*) paths, DEFAULT_TIMEOUT_USEC,
+                                           NULL, NULL, (char**) argv, m->environment);
+
+        r = 0;
 
 finish:
         lookup_paths_trim_generator(&m->lookup_paths);
@@ -3910,7 +3922,7 @@ static bool manager_journal_is_running(Manager *m) {
 
         assert(m);
 
-        if (m->test_run_flags != 0)
+        if (MANAGER_IS_TEST_RUN(m))
                 return false;
 
         /* If we are the user manager we can safely assume that the journal is up */
index 23e8e48a4da6a73e809a49ff4387275af5814abb..6e6e251a55b2a4388df3c3f220b92acf4fd75af8 100644 (file)
@@ -23,6 +23,8 @@ typedef struct Unit Unit;
 
 typedef struct Manager Manager;
 
+/* An externally visible state. We don't actually maintain this as state variable, but derive it from various fields
+ * when requested */
 typedef enum ManagerState {
         MANAGER_INITIALIZING,
         MANAGER_STARTING,
@@ -34,7 +36,7 @@ typedef enum ManagerState {
         _MANAGER_STATE_INVALID = -1
 } ManagerState;
 
-typedef enum ManagerExitCode {
+typedef enum ManagerObjective {
         MANAGER_OK,
         MANAGER_EXIT,
         MANAGER_RELOAD,
@@ -44,9 +46,9 @@ typedef enum ManagerExitCode {
         MANAGER_HALT,
         MANAGER_KEXEC,
         MANAGER_SWITCH_ROOT,
-        _MANAGER_EXIT_CODE_MAX,
-        _MANAGER_EXIT_CODE_INVALID = -1
-} ManagerExitCode;
+        _MANAGER_OBJECTIVE_MAX,
+        _MANAGER_OBJECTIVE_INVALID = -1
+} ManagerObjective;
 
 typedef enum StatusType {
         STATUS_TYPE_EPHEMERAL,
@@ -106,14 +108,15 @@ typedef enum ManagerTimestamp {
 #include "show-status.h"
 #include "unit-name.h"
 
-enum {
-        /* 0 = run normally */
-        MANAGER_TEST_RUN_MINIMAL        = 1 << 1,  /* create basic data structures */
-        MANAGER_TEST_RUN_BASIC          = 1 << 2,  /* interact with the environment */
-        MANAGER_TEST_RUN_ENV_GENERATORS = 1 << 3,  /* also run env generators  */
-        MANAGER_TEST_RUN_GENERATORS     = 1 << 4,  /* also run unit generators */
+typedef enum ManagerTestRunFlags {
+        MANAGER_TEST_NORMAL             = 0,       /* run normally */
+        MANAGER_TEST_RUN_MINIMAL        = 1 << 0,  /* create basic data structures */
+        MANAGER_TEST_RUN_BASIC          = 1 << 1,  /* interact with the environment */
+        MANAGER_TEST_RUN_ENV_GENERATORS = 1 << 2,  /* also run env generators  */
+        MANAGER_TEST_RUN_GENERATORS     = 1 << 3,  /* also run unit generators */
         MANAGER_TEST_FULL = MANAGER_TEST_RUN_BASIC | MANAGER_TEST_RUN_ENV_GENERATORS | MANAGER_TEST_RUN_GENERATORS,
-};
+} ManagerTestRunFlags;
+
 assert_cc((MANAGER_TEST_FULL & UINT8_MAX) == MANAGER_TEST_FULL);
 
 struct Manager {
@@ -280,9 +283,9 @@ struct Manager {
         usec_t etc_localtime_mtime;
         bool etc_localtime_accessible:1;
 
-        /* Flags */
-        ManagerExitCode exit_code:5;
+        ManagerObjective objective:5;
 
+        /* Flags */
         bool dispatching_load_queue:1;
         bool dispatching_dbus_queue:1;
 
@@ -297,7 +300,7 @@ struct Manager {
         /* Have we ever changed the "kernel.pid_max" sysctl? */
         bool sysctl_pid_max_changed:1;
 
-        unsigned test_run_flags:8;
+        ManagerTestRunFlags test_run_flags:8;
 
         /* If non-zero, exit with the following value when the systemd
          * process terminate. Useful for containers: systemd-nspawn could get
@@ -405,10 +408,12 @@ struct Manager {
 
 #define MANAGER_IS_FINISHED(m) (dual_timestamp_is_set((m)->timestamps + MANAGER_TIMESTAMP_FINISH))
 
-/* The exit code is set to OK as soon as we enter the main loop, and set otherwise as soon as we are done with it */
-#define MANAGER_IS_RUNNING(m) ((m)->exit_code == MANAGER_OK)
+/* The objective is set to OK as soon as we enter the main loop, and set otherwise as soon as we are done with it */
+#define MANAGER_IS_RUNNING(m) ((m)->objective == MANAGER_OK)
+
+#define MANAGER_IS_TEST_RUN(m) ((m)->test_run_flags != 0)
 
-int manager_new(UnitFileScope scope, unsigned test_run_flags, Manager **m);
+int manager_new(UnitFileScope scope, ManagerTestRunFlags test_run_flags, Manager **m);
 Manager* manager_free(Manager *m);
 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
 
index 359b35b297631a52ff2a9e768b1d8997dc0056e8..1b9bbf70577854cc3a63d930145f036b249df87f 100644 (file)
@@ -5280,7 +5280,7 @@ void unit_export_state_files(Unit *u) {
         if (!MANAGER_IS_SYSTEM(u->manager))
                 return;
 
-        if (u->manager->test_run_flags != 0)
+        if (MANAGER_IS_TEST_RUN(u->manager))
                 return;
 
         /* Exports a couple of unit properties to /run/systemd/units/, so that journald can quickly query this data
diff --git a/src/fuzz/fuzz-json.c b/src/fuzz/fuzz-json.c
new file mode 100644 (file)
index 0000000..3aa9d08
--- /dev/null
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fuzz.h"
+#include "json.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+        _cleanup_free_ char *out = NULL; /* out should be freed after g */
+        size_t out_size;
+        _cleanup_fclose_ FILE *f = NULL, *g = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+        if (size == 0)
+                return 0;
+
+        f = fmemopen((char*) data, size, "re");
+        assert_se(f);
+
+        if (json_parse_file(f, NULL, &v, NULL, NULL) < 0)
+                return 0;
+
+        g = open_memstream(&out, &out_size);
+        assert_se(g);
+
+        json_variant_dump(v, 0, g, NULL);
+        json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR|JSON_FORMAT_SOURCE, g, NULL);
+
+        return 0;
+}
index 31ee41cbe02008be3401bffd750e1dcf225b7eee..4c238493529617d9a876fe2fd12cadb9e6028859 100644 (file)
@@ -37,6 +37,10 @@ fuzzers += [
           libsystemd_network],
          []],
 
+        [['src/fuzz/fuzz-json.c'],
+         [libshared],
+         []],
+
         [['src/fuzz/fuzz-unit-file.c'],
          [libcore,
           libshared],
index 88e65ed905a217125faeeaff3ab758ebd650365b..a4e25f228498b1c0236509b5ad062df2f104417e 100644 (file)
@@ -58,6 +58,7 @@ static const char* const mime_types[_OUTPUT_MODE_MAX] = {
         [OUTPUT_SHORT] = "text/plain",
         [OUTPUT_JSON] = "application/json",
         [OUTPUT_JSON_SSE] = "text/event-stream",
+        [OUTPUT_JSON_SEQ] = "application/json-seq",
         [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
 };
 
@@ -267,6 +268,8 @@ static int request_parse_accept(
                 m->mode = OUTPUT_JSON;
         else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
                 m->mode = OUTPUT_JSON_SSE;
+        else if (streq(header, mime_types[OUTPUT_JSON_SEQ]))
+                m->mode = OUTPUT_JSON_SEQ;
         else if (streq(header, mime_types[OUTPUT_EXPORT]))
                 m->mode = OUTPUT_EXPORT;
         else
index 8bed6df891a973f85529bff0c96d1dbe7051c59a..4d186014edb5ec167aae4548e93334c7df40f550 100644 (file)
@@ -330,7 +330,8 @@ static int help(void) {
                "  -o --output=STRING         Change journal output mode (short, short-precise,\n"
                "                               short-iso, short-iso-precise, short-full,\n"
                "                               short-monotonic, short-unix, verbose, export,\n"
-               "                               json, json-pretty, json-sse, cat, with-unit)\n"
+               "                               json, json-pretty, json-sse, json-seq, cat,\n"
+               "                               with-unit)\n"
                "     --output-fields=LIST    Select fields to print in verbose/export/json modes\n"
                "     --utc                   Express time in Coordinated Universal Time (UTC)\n"
                "  -x --catalog               Add message explanations where available\n"
@@ -516,7 +517,7 @@ static int parse_argv(int argc, char *argv[]) {
                                 return -EINVAL;
                         }
 
-                        if (IN_SET(arg_output, OUTPUT_EXPORT, OUTPUT_JSON, OUTPUT_JSON_PRETTY, OUTPUT_JSON_SSE, OUTPUT_CAT))
+                        if (IN_SET(arg_output, OUTPUT_EXPORT, OUTPUT_JSON, OUTPUT_JSON_PRETTY, OUTPUT_JSON_SSE, OUTPUT_JSON_SEQ, OUTPUT_CAT))
                                 arg_quiet = true;
 
                         break;
diff --git a/src/libsystemd/disable-mempool.c b/src/libsystemd/disable-mempool.c
new file mode 100644 (file)
index 0000000..034bd24
--- /dev/null
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "mempool.h"
+
+const bool mempool_use_allowed = false;
index e3716a68d87bc527b90ce175f39124ed26e67a54..d95406e7acb74dc415d427a36866c5f28439158d 100644 (file)
@@ -81,6 +81,8 @@ libsystemd_sources = files('''
         sd-utf8/sd-utf8.c
 '''.split()) + id128_sources + sd_daemon_c + sd_event_c + sd_login_c
 
+disable_mempool_c = files('disable-mempool.c')
+
 libsystemd_c_args = ['-fvisibility=default']
 
 libsystemd_static = static_library(
index 738c922ce0fa07528337a5932b5cdb8b73eb2f88..a6dda16876ce0bb7fbf7a222c0cc614fb15e0906 100644 (file)
@@ -649,19 +649,22 @@ _public_ int sd_bus_creds_get_description(sd_bus_creds *c, const char **ret) {
         return 0;
 }
 
-static int has_cap(sd_bus_creds *c, unsigned offset, int capability) {
+static int has_cap(sd_bus_creds *c, size_t offset, int capability) {
+        unsigned long lc;
         size_t sz;
 
         assert(c);
         assert(capability >= 0);
         assert(c->capability);
 
-        if ((unsigned) capability > cap_last_cap())
+        lc = cap_last_cap();
+
+        if ((unsigned long) capability > lc)
                 return 0;
 
-        sz = DIV_ROUND_UP(cap_last_cap(), 32U);
+        sz = DIV_ROUND_UP(lc, 32LU);
 
-        return !!(c->capability[offset * sz + CAP_TO_INDEX(capability)] & CAP_TO_MASK(capability));
+        return !!(c->capability[offset * sz + CAP_TO_INDEX((uint32_t) capability)] & CAP_TO_MASK_CORRECTED((uint32_t) capability));
 }
 
 _public_ int sd_bus_creds_has_effective_cap(sd_bus_creds *c, int capability) {
index bf1cca509f2e76008e3dc3dd10c77a903c2c388b..c9c3166f0c84f4a0b6c16c6767e394635357137c 100644 (file)
@@ -1308,7 +1308,8 @@ static int help(int argc, char *argv[], void *userdata) {
                "  -o --output=STRING       Change journal output mode (short, short-precise,\n"
                "                             short-iso, short-iso-precise, short-full,\n"
                "                             short-monotonic, short-unix, verbose, export,\n"
-               "                             json, json-pretty, json-sse, cat)\n"
+               "                             json, json-pretty, json-sse, json-seq, cat,\n"
+               "                             with-unit)\n"
                "Session Commands:\n"
                "  list-sessions            List sessions\n"
                "  session-status [ID...]   Show session status\n"
index 5a88d252e741b40021976b535a81226b9ef332a1..f28174bf8bfb36fde58632dc10f3c611e825ea16 100644 (file)
@@ -2642,7 +2642,8 @@ static int help(int argc, char *argv[], void *userdata) {
                "  -o --output=STRING          Change journal output mode (short, short-precise,\n"
                "                               short-iso, short-iso-precise, short-full,\n"
                "                               short-monotonic, short-unix, verbose, export,\n"
-               "                               json, json-pretty, json-sse, cat)\n"
+               "                               json, json-pretty, json-sse, json-seq, cat,\n"
+               "                               with-unit)\n"
                "     --verify=MODE            Verification mode for downloaded images (no,\n"
                "                              checksum, signature)\n"
                "     --force                  Download image even if already exists\n\n"
index 03ca5188cb8ca2d6cf5dd428f98955155c9f351e..f99a7b3d8d7f4f4098861896e4fa0f21c73d7f44 100644 (file)
@@ -562,26 +562,33 @@ int manager_new(Manager **ret) {
 
         assert(ret);
 
-        m = new0(Manager, 1);
+        m = new(Manager, 1);
         if (!m)
                 return -ENOMEM;
 
-        m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1;
-        m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1;
-        m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1;
-        m->dns_stub_udp_fd = m->dns_stub_tcp_fd = -1;
-        m->hostname_fd = -1;
-
-        m->llmnr_support = RESOLVE_SUPPORT_YES;
-        m->mdns_support = RESOLVE_SUPPORT_YES;
-        m->dnssec_mode = DEFAULT_DNSSEC_MODE;
-        m->dns_over_tls_mode = DEFAULT_DNS_OVER_TLS_MODE;
-        m->enable_cache = true;
-        m->dns_stub_listener_mode = DNS_STUB_LISTENER_UDP;
-        m->read_resolv_conf = true;
-        m->need_builtin_fallbacks = true;
-        m->etc_hosts_last = m->etc_hosts_mtime = USEC_INFINITY;
-        m->read_etc_hosts = true;
+        *m = (Manager) {
+                .llmnr_ipv4_udp_fd = -1,
+                .llmnr_ipv6_udp_fd = -1,
+                .llmnr_ipv4_tcp_fd = -1,
+                .llmnr_ipv6_tcp_fd = -1,
+                .mdns_ipv4_fd = -1,
+                .mdns_ipv6_fd = -1,
+                .dns_stub_udp_fd = -1,
+                .dns_stub_tcp_fd = -1,
+                .hostname_fd = -1,
+
+                .llmnr_support = RESOLVE_SUPPORT_YES,
+                .mdns_support = RESOLVE_SUPPORT_YES,
+                .dnssec_mode = DEFAULT_DNSSEC_MODE,
+                .dns_over_tls_mode = DEFAULT_DNS_OVER_TLS_MODE,
+                .enable_cache = true,
+                .dns_stub_listener_mode = DNS_STUB_LISTENER_UDP,
+                .read_resolv_conf = true,
+                .need_builtin_fallbacks = true,
+                .etc_hosts_last = USEC_INFINITY,
+                .etc_hosts_mtime = USEC_INFINITY,
+                .read_etc_hosts = true,
+        };
 
         r = dns_trust_anchor_load(&m->trust_anchor);
         if (r < 0)
@@ -723,9 +730,16 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
                                + EXTRA_CMSG_SPACE /* kernel appears to require extra buffer space */];
         } control;
         union sockaddr_union sa;
-        struct msghdr mh = {};
-        struct cmsghdr *cmsg;
         struct iovec iov;
+        struct msghdr mh = {
+                .msg_name = &sa.sa,
+                .msg_namelen = sizeof(sa),
+                .msg_iov = &iov,
+                .msg_iovlen = 1,
+                .msg_control = &control,
+                .msg_controllen = sizeof(control),
+        };
+        struct cmsghdr *cmsg;
         ssize_t ms, l;
         int r;
 
@@ -741,15 +755,10 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
         if (r < 0)
                 return r;
 
-        iov.iov_base = DNS_PACKET_DATA(p);
-        iov.iov_len = p->allocated;
-
-        mh.msg_name = &sa.sa;
-        mh.msg_namelen = sizeof(sa);
-        mh.msg_iov = &iov;
-        mh.msg_iovlen = 1;
-        mh.msg_control = &control;
-        mh.msg_controllen = sizeof(control);
+        iov = (struct iovec) {
+                .iov_base = DNS_PACKET_DATA(p),
+                iov.iov_len = p->allocated,
+        };
 
         l = recvmsg(fd, &mh, 0);
         if (l == 0)
@@ -909,15 +918,18 @@ static int manager_ipv4_send(
                 uint16_t port,
                 const struct in_addr *source,
                 DnsPacket *p) {
-        union sockaddr_union sa = {
-                .in.sin_family = AF_INET,
-        };
         union {
                 struct cmsghdr header; /* For alignment */
                 uint8_t buffer[CMSG_SPACE(sizeof(struct in_pktinfo))];
-        } control;
-        struct msghdr mh = {};
+        } control = {};
+        union sockaddr_union sa;
         struct iovec iov;
+        struct msghdr mh = {
+                .msg_iov = &iov,
+                .msg_iovlen = 1,
+                .msg_name = &sa.sa,
+                .msg_namelen = sizeof(sa.in),
+        };
 
         assert(m);
         assert(fd >= 0);
@@ -925,23 +937,21 @@ static int manager_ipv4_send(
         assert(port > 0);
         assert(p);
 
-        iov.iov_base = DNS_PACKET_DATA(p);
-        iov.iov_len = p->size;
-
-        sa.in.sin_addr = *destination;
-        sa.in.sin_port = htobe16(port),
+        iov = (struct iovec) {
+                .iov_base = DNS_PACKET_DATA(p),
+                .iov_len = p->size,
+        };
 
-        mh.msg_iov = &iov;
-        mh.msg_iovlen = 1;
-        mh.msg_name = &sa.sa;
-        mh.msg_namelen = sizeof(sa.in);
+        sa = (union sockaddr_union) {
+                .in.sin_family = AF_INET,
+                .in.sin_addr = *destination,
+                .in.sin_port = htobe16(port),
+        };
 
         if (ifindex > 0) {
                 struct cmsghdr *cmsg;
                 struct in_pktinfo *pi;
 
-                zero(control);
-
                 mh.msg_control = &control;
                 mh.msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo));
 
@@ -969,15 +979,18 @@ static int manager_ipv6_send(
                 const struct in6_addr *source,
                 DnsPacket *p) {
 
-        union sockaddr_union sa = {
-                .in6.sin6_family = AF_INET6,
-        };
         union {
                 struct cmsghdr header; /* For alignment */
                 uint8_t buffer[CMSG_SPACE(sizeof(struct in6_pktinfo))];
-        } control;
-        struct msghdr mh = {};
+        } control = {};
+        union sockaddr_union sa;
         struct iovec iov;
+        struct msghdr mh = {
+                .msg_iov = &iov,
+                .msg_iovlen = 1,
+                .msg_name = &sa.sa,
+                .msg_namelen = sizeof(sa.in6),
+        };
 
         assert(m);
         assert(fd >= 0);
@@ -985,24 +998,22 @@ static int manager_ipv6_send(
         assert(port > 0);
         assert(p);
 
-        iov.iov_base = DNS_PACKET_DATA(p);
-        iov.iov_len = p->size;
-
-        sa.in6.sin6_addr = *destination;
-        sa.in6.sin6_port = htobe16(port),
-        sa.in6.sin6_scope_id = ifindex;
+        iov = (struct iovec) {
+                .iov_base = DNS_PACKET_DATA(p),
+                .iov_len = p->size,
+        };
 
-        mh.msg_iov = &iov;
-        mh.msg_iovlen = 1;
-        mh.msg_name = &sa.sa;
-        mh.msg_namelen = sizeof(sa.in6);
+        sa = (union sockaddr_union) {
+                .in6.sin6_family = AF_INET6,
+                .in6.sin6_addr = *destination,
+                .in6.sin6_port = htobe16(port),
+                .in6.sin6_scope_id = ifindex,
+        };
 
         if (ifindex > 0) {
                 struct cmsghdr *cmsg;
                 struct in6_pktinfo *pi;
 
-                zero(control);
-
                 mh.msg_control = &control;
                 mh.msg_controllen = CMSG_LEN(sizeof(struct in6_pktinfo));
 
@@ -1040,9 +1051,9 @@ int manager_send(
         log_debug("Sending %s packet with id %" PRIu16 " on interface %i/%s.", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p), ifindex, af_to_name(family));
 
         if (family == AF_INET)
-                return manager_ipv4_send(m, fd, ifindex, &destination->in, port, &source->in, p);
+                return manager_ipv4_send(m, fd, ifindex, &destination->in, port, source ? &source->in : NULL, p);
         if (family == AF_INET6)
-                return manager_ipv6_send(m, fd, ifindex, &destination->in6, port, &source->in6, p);
+                return manager_ipv6_send(m, fd, ifindex, &destination->in6, port, source ? &source->in6 : NULL, p);
 
         return -EAFNOSUPPORT;
 }
diff --git a/src/shared/enable-mempool.c b/src/shared/enable-mempool.c
new file mode 100644 (file)
index 0000000..a571b43
--- /dev/null
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "mempool.h"
+
+const bool mempool_use_allowed = true;
index 6a6fc1fe24715eae54586eb33b1084d1809ca114..79e47b84177c066a9329db6e1086c3f3087a6b63 100644 (file)
@@ -21,6 +21,7 @@
 #include "hostname-util.h"
 #include "io-util.h"
 #include "journal-internal.h"
+#include "json.h"
 #include "log.h"
 #include "logs-show.h"
 #include "macro.h"
@@ -41,7 +42,7 @@
 #define PRINT_LINE_THRESHOLD 3
 #define PRINT_CHAR_THRESHOLD 300
 
-#define JSON_THRESHOLD 4096
+#define JSON_THRESHOLD 4096U
 
 static int print_catalog(FILE *f, sd_journal *j) {
         int r;
@@ -747,6 +748,96 @@ void json_escape(
         }
 }
 
+struct json_data {
+        JsonVariant* name;
+        size_t n_values;
+        JsonVariant* values[];
+};
+
+static int update_json_data(
+                Hashmap *h,
+                OutputFlags flags,
+                const char *name,
+                const void *value,
+                size_t size) {
+
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        struct json_data *d;
+        int r;
+
+        if (!(flags & OUTPUT_SHOW_ALL) && strlen(name) + 1 + size >= JSON_THRESHOLD)
+                r = json_variant_new_null(&v);
+        else if (utf8_is_printable(value, size))
+                r = json_variant_new_stringn(&v, value, size);
+        else
+                r = json_variant_new_array_bytes(&v, value, size);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate JSON data: %m");
+
+        d = hashmap_get(h, name);
+        if (d) {
+                struct json_data *w;
+
+                w = realloc(d, offsetof(struct json_data, values) + sizeof(JsonVariant*) * (d->n_values + 1));
+                if (!w)
+                        return log_oom();
+
+                d = w;
+                assert_se(hashmap_update(h, json_variant_string(d->name), d) >= 0);
+        } else {
+                _cleanup_(json_variant_unrefp) JsonVariant *n = NULL;
+
+                r = json_variant_new_string(&n, name);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to allocate JSON name variant: %m");
+
+                d = malloc0(offsetof(struct json_data, values) + sizeof(JsonVariant*));
+                if (!d)
+                        return log_oom();
+
+                r = hashmap_put(h, json_variant_string(n), d);
+                if (r < 0) {
+                        free(d);
+                        return log_error_errno(r, "Failed to insert JSON name into hashmap: %m");
+                }
+
+                d->name = TAKE_PTR(n);
+        }
+
+        d->values[d->n_values++] = TAKE_PTR(v);
+        return 0;
+}
+
+static int update_json_data_split(
+                Hashmap *h,
+                OutputFlags flags,
+                Set *output_fields,
+                const void *data,
+                size_t size) {
+
+        const char *eq;
+        char *name;
+
+        assert(h);
+        assert(data || size == 0);
+
+        if (memory_startswith(data, size, "_BOOT_ID="))
+                return 0;
+
+        eq = memchr(data, '=', MIN(size, JSON_THRESHOLD));
+        if (!eq)
+                return 0;
+
+        if (eq == data)
+                return 0;
+
+        name = strndupa(data, eq - (const char*) data);
+        if (output_fields && !set_get(output_fields, name))
+                return 0;
+
+        return update_json_data(h, flags, name, eq + 1, size - (eq - (const char*) data) - 1);
+}
+
 static int output_json(
                 FILE *f,
                 sd_journal *j,
@@ -756,19 +847,21 @@ static int output_json(
                 Set *output_fields,
                 const size_t highlight[2]) {
 
-        uint64_t realtime, monotonic;
+        char sid[SD_ID128_STRING_MAX], usecbuf[DECIMAL_STR_MAX(usec_t)];
+        _cleanup_(json_variant_unrefp) JsonVariant *object = NULL;
         _cleanup_free_ char *cursor = NULL;
-        const void *data;
-        size_t length;
+        uint64_t realtime, monotonic;
+        JsonVariant **array = NULL;
+        struct json_data *d;
         sd_id128_t boot_id;
-        char sid[33], *k;
-        int r;
         Hashmap *h = NULL;
-        bool done, separator;
+        size_t n = 0;
+        Iterator i;
+        int r;
 
         assert(j);
 
-        sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD);
+        (void) sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD);
 
         r = sd_journal_get_realtime_usec(j, &realtime);
         if (r < 0)
@@ -782,182 +875,109 @@ static int output_json(
         if (r < 0)
                 return log_error_errno(r, "Failed to get cursor: %m");
 
-        if (mode == OUTPUT_JSON_PRETTY)
-                fprintf(f,
-                        "{\n"
-                        "\t\"__CURSOR\" : \"%s\",\n"
-                        "\t\"__REALTIME_TIMESTAMP\" : \""USEC_FMT"\",\n"
-                        "\t\"__MONOTONIC_TIMESTAMP\" : \""USEC_FMT"\",\n"
-                        "\t\"_BOOT_ID\" : \"%s\"",
-                        cursor,
-                        realtime,
-                        monotonic,
-                        sd_id128_to_string(boot_id, sid));
-        else {
-                if (mode == OUTPUT_JSON_SSE)
-                        fputs("data: ", f);
-
-                fprintf(f,
-                        "{ \"__CURSOR\" : \"%s\", "
-                        "\"__REALTIME_TIMESTAMP\" : \""USEC_FMT"\", "
-                        "\"__MONOTONIC_TIMESTAMP\" : \""USEC_FMT"\", "
-                        "\"_BOOT_ID\" : \"%s\"",
-                        cursor,
-                        realtime,
-                        monotonic,
-                        sd_id128_to_string(boot_id, sid));
-        }
-
         h = hashmap_new(&string_hash_ops);
         if (!h)
                 return log_oom();
 
-        /* First round, iterate through the entry and count how often each field appears */
-        JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
-                const char *eq;
-                char *n;
-                unsigned u;
-
-                if (memory_startswith(data, length, "_BOOT_ID="))
-                        continue;
-
-                eq = memchr(data, '=', length);
-                if (!eq)
-                        continue;
-
-                n = memdup_suffix0(data, eq - (const char*) data);
-                if (!n) {
-                        r = log_oom();
-                        goto finish;
-                }
-
-                u = PTR_TO_UINT(hashmap_get(h, n));
-                if (u == 0) {
-                        r = hashmap_put(h, n, UINT_TO_PTR(1));
-                        if (r < 0) {
-                                free(n);
-                                log_oom();
-                                goto finish;
-                        }
-                } else {
-                        r = hashmap_update(h, n, UINT_TO_PTR(u + 1));
-                        free(n);
-                        if (r < 0) {
-                                log_oom();
-                                goto finish;
-                        }
-                }
-        }
-        if (r == -EBADMSG) {
-                log_debug_errno(r, "Skipping message we can't read: %m");
-                return 0;
-        }
+        r = update_json_data(h, flags, "__CURSOR", cursor, strlen(cursor));
         if (r < 0)
-                return r;
-
-        separator = true;
-        do {
-                done = true;
-
-                SD_JOURNAL_FOREACH_DATA(j, data, length) {
-                        const char *eq;
-                        char *kk;
-                        _cleanup_free_ char *n = NULL;
-                        size_t m;
-                        unsigned u;
-
-                        /* We already printed the boot id from the data in
-                         * the header, hence let's suppress it here */
-                        if (memory_startswith(data, length, "_BOOT_ID="))
-                                continue;
-
-                        eq = memchr(data, '=', length);
-                        if (!eq)
-                                continue;
-
-                        m = eq - (const char*) data;
-                        n = memdup_suffix0(data, m);
-                        if (!n) {
-                                r = log_oom();
-                                goto finish;
-                        }
-
-                        if (output_fields && !set_get(output_fields, n))
-                                continue;
-
-                        if (separator)
-                                fputs(mode == OUTPUT_JSON_PRETTY ? ",\n\t" : ", ", f);
-
-                        u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk));
-                        if (u == 0)
-                                /* We already printed this, let's jump to the next */
-                                separator = false;
-
-                        else if (u == 1) {
-                                /* Field only appears once, output it directly */
-
-                                json_escape(f, data, m, flags);
-                                fputs(" : ", f);
-
-                                json_escape(f, eq + 1, length - m - 1, flags);
-
-                                hashmap_remove(h, n);
-                                free(kk);
+                goto finish;
 
-                                separator = true;
+        xsprintf(usecbuf, USEC_FMT, realtime);
+        r = update_json_data(h, flags, "__REALTIME_TIMESTAMP", usecbuf, strlen(usecbuf));
+        if (r < 0)
+                goto finish;
 
-                        } else {
-                                /* Field appears multiple times, output it as array */
-                                json_escape(f, data, m, flags);
-                                fputs(" : [ ", f);
-                                json_escape(f, eq + 1, length - m - 1, flags);
+        xsprintf(usecbuf, USEC_FMT, monotonic);
+        r = update_json_data(h, flags, "__MONOTONIC_TIMESTAMP", usecbuf, strlen(usecbuf));
+        if (r < 0)
+                goto finish;
 
-                                /* Iterate through the end of the list */
+        sd_id128_to_string(boot_id, sid);
+        r = update_json_data(h, flags, "_BOOT_ID", sid, strlen(sid));
+        if (r < 0)
+                goto finish;
 
-                                while (sd_journal_enumerate_data(j, &data, &length) > 0) {
-                                        if (length < m + 1)
-                                                continue;
+        for (;;) {
+                const void *data;
+                size_t size;
 
-                                        if (memcmp(data, n, m) != 0)
-                                                continue;
+                r = sd_journal_enumerate_data(j, &data, &size);
+                if (r == -EBADMSG) {
+                        log_debug_errno(r, "Skipping message we can't read: %m");
+                        r = 0;
+                        goto finish;
+                }
+                if (r < 0) {
+                        log_error_errno(r, "Failed to read journal: %m");
+                        goto finish;
+                }
+                if (r == 0)
+                        break;
 
-                                        if (((const char*) data)[m] != '=')
-                                                continue;
+                r = update_json_data_split(h, flags, output_fields, data, size);
+                if (r < 0)
+                        goto finish;
+        }
 
-                                        fputs(", ", f);
-                                        json_escape(f, (const char*) data + m + 1, length - m - 1, flags);
-                                }
+        array = new(JsonVariant*, hashmap_size(h)*2);
+        if (!array) {
+                r = log_oom();
+                goto finish;
+        }
 
-                                fputs(" ]", f);
+        HASHMAP_FOREACH(d, h, i) {
+                assert(d->n_values > 0);
 
-                                hashmap_remove(h, n);
-                                free(kk);
+                array[n++] = json_variant_ref(d->name);
 
-                                /* Iterate data fields form the beginning */
-                                done = false;
-                                separator = true;
+                if (d->n_values == 1)
+                        array[n++] = json_variant_ref(d->values[0]);
+                else {
+                        _cleanup_(json_variant_unrefp) JsonVariant *q = NULL;
 
-                                break;
+                        r = json_variant_new_array(&q, d->values, d->n_values);
+                        if (r < 0) {
+                                log_error_errno(r, "Failed to create JSON array: %m");
+                                goto finish;
                         }
+
+                        array[n++] = TAKE_PTR(q);
                 }
+        }
 
-        } while (!done);
+        r = json_variant_new_object(&object, array, n);
+        if (r < 0) {
+                log_error_errno(r, "Failed to allocate JSON object: %m");
+                goto finish;
+        }
 
-        if (mode == OUTPUT_JSON_PRETTY)
-                fputs("\n}\n", f);
-        else if (mode == OUTPUT_JSON_SSE)
-                fputs("}\n\n", f);
-        else
-                fputs(" }\n", f);
+        json_variant_dump(object,
+                          (mode == OUTPUT_JSON_SSE    ? JSON_FORMAT_SSE :
+                           mode == OUTPUT_JSON_SEQ    ? JSON_FORMAT_SEQ :
+                           mode == OUTPUT_JSON_PRETTY ? JSON_FORMAT_PRETTY :
+                                                        JSON_FORMAT_NEWLINE) |
+                          (FLAGS_SET(flags, OUTPUT_COLOR) ? JSON_FORMAT_COLOR : 0),
+                          f, NULL);
 
         r = 0;
 
 finish:
-        while ((k = hashmap_steal_first_key(h)))
-                free(k);
+        while ((d = hashmap_steal_first(h))) {
+                size_t k;
+
+                json_variant_unref(d->name);
+                for (k = 0; k < d->n_values; k++)
+                        json_variant_unref(d->values[k]);
+
+                free(d);
+        }
 
         hashmap_free(h);
 
+        json_variant_unref_many(array, n);
+        free(array);
+
         return r;
 }
 
@@ -1037,6 +1057,7 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])(
         [OUTPUT_JSON] = output_json,
         [OUTPUT_JSON_PRETTY] = output_json,
         [OUTPUT_JSON_SSE] = output_json,
+        [OUTPUT_JSON_SEQ] = output_json,
         [OUTPUT_CAT] = output_cat,
         [OUTPUT_WITH_UNIT] = output_short,
 };
index ff9453f48032be41a43f350aa05aceb58e67a77a..83a2592c0a5f750b7eef4edf8a05e3b64479467c 100644 (file)
@@ -36,6 +36,7 @@ shared_sources = files('''
         dropin.h
         efivars.c
         efivars.h
+        enable-mempool.c
         fdset.c
         fdset.h
         firewall-util.h
index bb33ba3d10e25bec434560c8255fa61c18bedbc1..9463d185f0efe25bc432b8e95c22354935493b36 100644 (file)
@@ -16,6 +16,7 @@ static const char *const output_mode_table[_OUTPUT_MODE_MAX] = {
         [OUTPUT_JSON] = "json",
         [OUTPUT_JSON_PRETTY] = "json-pretty",
         [OUTPUT_JSON_SSE] = "json-sse",
+        [OUTPUT_JSON_SEQ] = "json-seq",
         [OUTPUT_CAT] = "cat",
         [OUTPUT_WITH_UNIT] = "with-unit",
 };
index fe3903b3c527174ecb3f402cddef04f50bed16ad..3cbaeadde6877bf198acbfb57c9fc3eabeb94efb 100644 (file)
@@ -16,6 +16,7 @@ typedef enum OutputMode {
         OUTPUT_JSON,
         OUTPUT_JSON_PRETTY,
         OUTPUT_JSON_SSE,
+        OUTPUT_JSON_SEQ,
         OUTPUT_CAT,
         OUTPUT_WITH_UNIT,
         _OUTPUT_MODE_MAX,
index 7d7b890b472d7fbab5e8f5405cfb92a5adda8b6b..075f4d1e6cb691d931db07b7be1996f425186665 100644 (file)
@@ -529,7 +529,7 @@ int lookup_paths_init(
         if (flags & LOOKUP_PATHS_TEMPORARY_GENERATED) {
                 r = mkdtemp_malloc("/tmp/systemd-temporary-XXXXXX", &tempdir);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to create temporary directory: %m");
+                        return log_debug_errno(r, "Failed to create temporary directory: %m");
         }
 
         /* Note: when XDG_RUNTIME_DIR is not set this will not return -ENXIO, but simply set runtime_config to NULL */
index 3d1e28bdf130e349ff0d51bfaecd2168d6c548ae..97cca3269c0972dbc4400cb34bb255cbde3593aa 100644 (file)
@@ -191,6 +191,10 @@ tests += [
          [],
          []],
 
+        [['src/test/test-json.c'],
+         [],
+         []],
+
         [['src/test/test-mount-util.c'],
          [],
          []],
index 2fb7968dfd074b8cccc92846be4cff5153ac886d..ea5f0f5bc68019edc72ed9412985201344976144 100644 (file)
@@ -63,7 +63,7 @@ int main(int argc, char *argv[]) {
 
         /* The simple tests suceeded. Now let's try full unit-based use-case. */
 
-        assert_se(manager_new(UNIT_FILE_USER, true, &m) >= 0);
+        assert_se(manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0);
         assert_se(manager_startup(m, NULL, NULL) >= 0);
 
         assert_se(u = unit_new(m, sizeof(Service)));
index da9f3008bb5e61d7a7299d085d3a77ba65034f6e..a972ddcef7c41b4519e62e4208f13757180eb27d 100644 (file)
@@ -84,7 +84,7 @@ static void test_unhexmem_one(const char *s, size_t l, int retval) {
                         l = strlen(s);
 
                 assert_se(hex = hexmem(mem, len));
-                answer = strndupa(s, l);
+                answer = strndupa(s ?: "", l);
                 assert_se(streq(delete_chars(answer, WHITESPACE), hex));
         }
 }
diff --git a/src/test/test-json.c b/src/test/test-json.c
new file mode 100644 (file)
index 0000000..c1c2105
--- /dev/null
@@ -0,0 +1,411 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <math.h>
+#if HAVE_VALGRIND_VALGRIND_H
+#include <valgrind/valgrind.h>
+#endif
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "json-internal.h"
+#include "json.h"
+#include "string-util.h"
+#include "strv.h"
+#include "util.h"
+
+static void test_tokenizer(const char *data, ...) {
+        unsigned line = 0, column = 0;
+        void *state = NULL;
+        va_list ap;
+
+        va_start(ap, data);
+
+        for (;;) {
+                unsigned token_line, token_column;
+                _cleanup_free_ char *str = NULL;
+                JsonValue v = JSON_VALUE_NULL;
+                int t, tt;
+
+                t = json_tokenize(&data, &str, &v, &token_line, &token_column, &state, &line, &column);
+                tt = va_arg(ap, int);
+
+                assert_se(t == tt);
+
+                if (t == JSON_TOKEN_END || t < 0)
+                        break;
+
+                else if (t == JSON_TOKEN_STRING) {
+                        const char *nn;
+
+                        nn = va_arg(ap, const char *);
+                        assert_se(streq_ptr(nn, str));
+
+                } else if (t == JSON_TOKEN_REAL) {
+                        long double d;
+
+                        d = va_arg(ap, long double);
+
+#if HAVE_VALGRIND_VALGRIND_H
+                        if (!RUNNING_ON_VALGRIND)
+#endif
+                                /* Valgrind doesn't support long double calculations and automatically downgrades to 80bit:
+                                 * http://www.valgrind.org/docs/manual/manual-core.html#manual-core.limits */
+                                assert_se(fabsl(d - v.real) < 0.001L);
+
+                } else if (t == JSON_TOKEN_INTEGER) {
+                        intmax_t i;
+
+                        i = va_arg(ap, intmax_t);
+                        assert_se(i == v.integer);
+
+                } else if (t == JSON_TOKEN_UNSIGNED) {
+                        uintmax_t u;
+
+                        u = va_arg(ap, uintmax_t);
+                        assert_se(u == v.unsig);
+
+                } else if (t == JSON_TOKEN_BOOLEAN) {
+                        bool b;
+
+                        b = va_arg(ap, int);
+                        assert_se(b == v.boolean);
+                }
+        }
+
+        va_end(ap);
+}
+
+typedef void (*Test)(JsonVariant *);
+
+static void test_variant(const char *data, Test test) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL;
+        _cleanup_free_ char *s = NULL;
+        int r;
+
+        r = json_parse(data, &v, NULL, NULL);
+        assert_se(r == 0);
+        assert_se(v);
+
+        r = json_variant_format(v, 0, &s);
+        assert_se(r >= 0);
+        assert_se(s);
+
+        log_info("formatted normally: %s\n", s);
+
+        r = json_parse(data, &w, NULL, NULL);
+        assert_se(r == 0);
+        assert_se(w);
+        assert_se(json_variant_has_type(v, json_variant_type(w)));
+        assert_se(json_variant_has_type(w, json_variant_type(v)));
+        assert_se(json_variant_equal(v, w));
+
+        s = mfree(s);
+        w = json_variant_unref(w);
+
+        r = json_variant_format(v, JSON_FORMAT_PRETTY, &s);
+        assert_se(r >= 0);
+        assert_se(s);
+
+        log_info("formatted prettily:\n%s", s);
+
+        r = json_parse(data, &w, NULL, NULL);
+        assert_se(r == 0);
+        assert_se(w);
+
+        assert_se(json_variant_has_type(v, json_variant_type(w)));
+        assert_se(json_variant_has_type(w, json_variant_type(v)));
+        assert_se(json_variant_equal(v, w));
+
+        s = mfree(s);
+        r = json_variant_format(v, JSON_FORMAT_COLOR, &s);
+        assert_se(r >= 0);
+        assert_se(s);
+        printf("Normal with color: %s\n", s);
+
+        s = mfree(s);
+        r = json_variant_format(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, &s);
+        assert_se(r >= 0);
+        assert_se(s);
+        printf("Pretty with color:\n%s\n", s);
+
+        if (test)
+                test(v);
+}
+
+static void test_1(JsonVariant *v) {
+        JsonVariant *p, *q;
+        unsigned i;
+
+        /* 3 keys + 3 values */
+        assert_se(json_variant_elements(v) == 6);
+
+        /* has k */
+        p = json_variant_by_key(v, "k");
+        assert_se(p && json_variant_type(p) == JSON_VARIANT_STRING);
+
+        /* k equals v */
+        assert_se(streq(json_variant_string(p), "v"));
+
+        /* has foo */
+        p = json_variant_by_key(v, "foo");
+        assert_se(p && json_variant_type(p) == JSON_VARIANT_ARRAY && json_variant_elements(p) == 3);
+
+        /* check  foo[0] = 1, foo[1] = 2, foo[2] = 3 */
+        for (i = 0; i < 3; ++i) {
+                q = json_variant_by_index(p, i);
+                assert_se(q && json_variant_type(q) == JSON_VARIANT_UNSIGNED && json_variant_unsigned(q) == (i+1));
+                assert_se(q && json_variant_has_type(q, JSON_VARIANT_INTEGER) && json_variant_integer(q) == (i+1));
+        }
+
+        /* has bar */
+        p = json_variant_by_key(v, "bar");
+        assert_se(p && json_variant_type(p) == JSON_VARIANT_OBJECT && json_variant_elements(p) == 2);
+
+        /* zap is null */
+        q = json_variant_by_key(p, "zap");
+        assert_se(q && json_variant_type(q) == JSON_VARIANT_NULL);
+}
+
+static void test_2(JsonVariant *v) {
+        JsonVariant *p, *q;
+
+        /* 2 keys + 2 values */
+        assert_se(json_variant_elements(v) == 4);
+
+        /* has mutant */
+        p = json_variant_by_key(v, "mutant");
+        assert_se(p && json_variant_type(p) == JSON_VARIANT_ARRAY && json_variant_elements(p) == 4);
+
+        /* mutant[0] == 1 */
+        q = json_variant_by_index(p, 0);
+        assert_se(q && json_variant_type(q) == JSON_VARIANT_UNSIGNED && json_variant_unsigned(q) == 1);
+        assert_se(q && json_variant_has_type(q, JSON_VARIANT_INTEGER) && json_variant_integer(q) == 1);
+
+        /* mutant[1] == null */
+        q = json_variant_by_index(p, 1);
+        assert_se(q && json_variant_type(q) == JSON_VARIANT_NULL);
+
+        /* mutant[2] == "1" */
+        q = json_variant_by_index(p, 2);
+        assert_se(q && json_variant_type(q) == JSON_VARIANT_STRING && streq(json_variant_string(q), "1"));
+
+        /* mutant[3] == JSON_VARIANT_OBJECT */
+        q = json_variant_by_index(p, 3);
+        assert_se(q && json_variant_type(q) == JSON_VARIANT_OBJECT && json_variant_elements(q) == 2);
+
+        /* has 1 */
+        p = json_variant_by_key(q, "1");
+        assert_se(p && json_variant_type(p) == JSON_VARIANT_ARRAY && json_variant_elements(p) == 2);
+
+        /* "1"[0] == 1 */
+        q = json_variant_by_index(p, 0);
+        assert_se(q && json_variant_type(q) == JSON_VARIANT_UNSIGNED && json_variant_unsigned(q) == 1);
+        assert_se(q && json_variant_has_type(q, JSON_VARIANT_INTEGER) && json_variant_integer(q) == 1);
+
+        /* "1"[1] == "1" */
+        q = json_variant_by_index(p, 1);
+        assert_se(q && json_variant_type(q) == JSON_VARIANT_STRING && streq(json_variant_string(q), "1"));
+
+        /* has thisisaverylongproperty */
+        p = json_variant_by_key(v, "thisisaverylongproperty");
+        assert_se(p && json_variant_type(p) == JSON_VARIANT_REAL && fabs(json_variant_real(p) - 1.27) < 0.001);
+}
+
+
+static void test_zeroes(JsonVariant *v) {
+        size_t i;
+
+        /* Make sure zero is how we expect it. */
+
+        assert_se(json_variant_elements(v) == 13);
+
+        for (i = 0; i < json_variant_elements(v); i++) {
+                JsonVariant *w;
+                size_t j;
+
+                assert_se(w = json_variant_by_index(v, i));
+
+                assert_se(json_variant_integer(w) == 0);
+                assert_se(json_variant_unsigned(w) == 0U);
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+                assert_se(json_variant_real(w) == 0.0L);
+#pragma GCC diagnostic pop
+
+                assert_se(json_variant_is_integer(w));
+                assert_se(json_variant_is_unsigned(w));
+                assert_se(json_variant_is_real(w));
+                assert_se(json_variant_is_number(w));
+
+                assert_se(!json_variant_is_negative(w));
+
+                assert_se(IN_SET(json_variant_type(w), JSON_VARIANT_INTEGER, JSON_VARIANT_UNSIGNED, JSON_VARIANT_REAL));
+
+                for (j = 0; j < json_variant_elements(v); j++) {
+                        JsonVariant *q;
+
+                        assert_se(q = json_variant_by_index(v, j));
+
+                        assert_se(json_variant_equal(w, q));
+                }
+        }
+}
+
+static void test_build(void) {
+        _cleanup_(json_variant_unrefp) JsonVariant *a = NULL, *b = NULL;
+        _cleanup_free_ char *s = NULL, *t = NULL;
+
+        assert_se(json_build(&a, JSON_BUILD_STRING("hallo")) >= 0);
+        assert_se(json_build(&b, JSON_BUILD_LITERAL(" \"hallo\"   ")) >= 0);
+        assert_se(json_variant_equal(a, b));
+
+        b = json_variant_unref(b);
+
+        assert_se(json_build(&b, JSON_BUILD_VARIANT(a)) >= 0);
+        assert_se(json_variant_equal(a, b));
+
+        b = json_variant_unref(b);
+        assert_se(json_build(&b, JSON_BUILD_STRING("pief")) >= 0);
+        assert_se(!json_variant_equal(a, b));
+
+        a = json_variant_unref(a);
+        b = json_variant_unref(b);
+
+        assert_se(json_build(&a, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("one", JSON_BUILD_INTEGER(7)),
+                                                   JSON_BUILD_PAIR("two", JSON_BUILD_REAL(2.0)),
+                                                   JSON_BUILD_PAIR("three", JSON_BUILD_INTEGER(0)))) >= 0);
+
+        assert_se(json_build(&b, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("two", JSON_BUILD_INTEGER(2)),
+                                                   JSON_BUILD_PAIR("three", JSON_BUILD_REAL(0)),
+                                                   JSON_BUILD_PAIR("one", JSON_BUILD_REAL(7)))) >= 0);
+
+        assert_se(json_variant_equal(a, b));
+
+        a = json_variant_unref(a);
+        b = json_variant_unref(b);
+
+        assert_se(json_build(&a, JSON_BUILD_ARRAY(JSON_BUILD_OBJECT(JSON_BUILD_PAIR("x", JSON_BUILD_BOOLEAN(true)),
+                                                                    JSON_BUILD_PAIR("y", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("this", JSON_BUILD_NULL)))),
+                                                  JSON_BUILD_VARIANT(NULL),
+                                                  JSON_BUILD_LITERAL(NULL),
+                                                  JSON_BUILD_STRING(NULL),
+                                                  JSON_BUILD_NULL,
+                                                  JSON_BUILD_INTEGER(77),
+                                                  JSON_BUILD_STRV(STRV_MAKE("one", "two", "three", "four")))) >= 0);
+
+        assert_se(json_variant_format(a, 0, &s) >= 0);
+        log_info("GOT: %s\n", s);
+        assert_se(json_parse(s, &b, NULL, NULL) >= 0);
+        assert_se(json_variant_equal(a, b));
+
+        a = json_variant_unref(a);
+        b = json_variant_unref(b);
+
+        assert_se(json_build(&a, JSON_BUILD_REAL(M_PIl)) >= 0);
+
+        s = mfree(s);
+        assert_se(json_variant_format(a, 0, &s) >= 0);
+        log_info("GOT: %s\n", s);
+        assert_se(json_parse(s, &b, NULL, NULL) >= 0);
+        assert_se(json_variant_format(b, 0, &t) >= 0);
+        log_info("GOT: %s\n", t);
+
+        assert_se(streq(s, t));
+
+        a = json_variant_unref(a);
+        b = json_variant_unref(b);
+}
+
+static void test_source(void) {
+        static const char data[] =
+                "\n"
+                "\n"
+                "{\n"
+                "\"foo\" : \"bar\", \n"
+                "\"qüüx\" : [ 1, 2, 3,\n"
+                "4,\n"
+                "5 ],\n"
+                "\"miep\" : { \"hallo\" : 1 },\n"
+                "\n"
+                "\"zzzzzz\" \n"
+                ":\n"
+                "[ true, \n"
+                "false, 7.5, {} ]\n"
+                "}\n";
+
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+        printf("--- original begin ---\n"
+               "%s"
+               "--- original end ---\n", data);
+
+        assert_se(f = fmemopen((void*) data, sizeof(data), "r"));
+
+        assert_se(json_parse_file(f, "waldo", &v, NULL, NULL) >= 0);
+
+        printf("--- non-pretty begin ---\n");
+        json_variant_dump(v, 0, stdout, NULL);
+        printf("\n--- non-pretty end ---\n");
+
+        printf("--- pretty begin ---\n");
+        json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR|JSON_FORMAT_SOURCE, stdout, NULL);
+        printf("--- pretty end ---\n");
+}
+
+int main(int argc, char *argv[]) {
+
+        log_set_max_level(LOG_DEBUG);
+        log_parse_environment();
+        log_open();
+
+        test_tokenizer("x", -EINVAL);
+        test_tokenizer("", JSON_TOKEN_END);
+        test_tokenizer(" ", JSON_TOKEN_END);
+        test_tokenizer("0", JSON_TOKEN_UNSIGNED, (uintmax_t) 0, JSON_TOKEN_END);
+        test_tokenizer("-0", JSON_TOKEN_INTEGER, (intmax_t) 0, JSON_TOKEN_END);
+        test_tokenizer("1234", JSON_TOKEN_UNSIGNED, (uintmax_t) 1234, JSON_TOKEN_END);
+        test_tokenizer("-1234", JSON_TOKEN_INTEGER, (intmax_t) -1234, JSON_TOKEN_END);
+        test_tokenizer("18446744073709551615", JSON_TOKEN_UNSIGNED, (uintmax_t) UINT64_MAX, JSON_TOKEN_END);
+        test_tokenizer("-9223372036854775808", JSON_TOKEN_INTEGER, (intmax_t) INT64_MIN, JSON_TOKEN_END);
+        test_tokenizer("18446744073709551616", JSON_TOKEN_REAL, (long double) 18446744073709551616.0L, JSON_TOKEN_END);
+        test_tokenizer("-9223372036854775809", JSON_TOKEN_REAL, (long double) -9223372036854775809.0L, JSON_TOKEN_END);
+        test_tokenizer("-1234", JSON_TOKEN_INTEGER, (intmax_t) -1234, JSON_TOKEN_END);
+        test_tokenizer("3.141", JSON_TOKEN_REAL, (long double) 3.141, JSON_TOKEN_END);
+        test_tokenizer("0.0", JSON_TOKEN_REAL, (long double) 0.0, JSON_TOKEN_END);
+        test_tokenizer("7e3", JSON_TOKEN_REAL, (long double) 7e3, JSON_TOKEN_END);
+        test_tokenizer("-7e-3", JSON_TOKEN_REAL, (long double) -7e-3, JSON_TOKEN_END);
+        test_tokenizer("true", JSON_TOKEN_BOOLEAN, true, JSON_TOKEN_END);
+        test_tokenizer("false", JSON_TOKEN_BOOLEAN, false, JSON_TOKEN_END);
+        test_tokenizer("null", JSON_TOKEN_NULL, JSON_TOKEN_END);
+        test_tokenizer("{}", JSON_TOKEN_OBJECT_OPEN, JSON_TOKEN_OBJECT_CLOSE, JSON_TOKEN_END);
+        test_tokenizer("\t {\n} \n", JSON_TOKEN_OBJECT_OPEN, JSON_TOKEN_OBJECT_CLOSE, JSON_TOKEN_END);
+        test_tokenizer("[]", JSON_TOKEN_ARRAY_OPEN, JSON_TOKEN_ARRAY_CLOSE, JSON_TOKEN_END);
+        test_tokenizer("\t [] \n\n", JSON_TOKEN_ARRAY_OPEN, JSON_TOKEN_ARRAY_CLOSE, JSON_TOKEN_END);
+        test_tokenizer("\"\"", JSON_TOKEN_STRING, "", JSON_TOKEN_END);
+        test_tokenizer("\"foo\"", JSON_TOKEN_STRING, "foo", JSON_TOKEN_END);
+        test_tokenizer("\"foo\\nfoo\"", JSON_TOKEN_STRING, "foo\nfoo", JSON_TOKEN_END);
+        test_tokenizer("{\"foo\" : \"bar\"}", JSON_TOKEN_OBJECT_OPEN, JSON_TOKEN_STRING, "foo", JSON_TOKEN_COLON, JSON_TOKEN_STRING, "bar", JSON_TOKEN_OBJECT_CLOSE, JSON_TOKEN_END);
+        test_tokenizer("{\"foo\" : [true, false]}", JSON_TOKEN_OBJECT_OPEN, JSON_TOKEN_STRING, "foo", JSON_TOKEN_COLON, JSON_TOKEN_ARRAY_OPEN, JSON_TOKEN_BOOLEAN, true, JSON_TOKEN_COMMA, JSON_TOKEN_BOOLEAN, false, JSON_TOKEN_ARRAY_CLOSE, JSON_TOKEN_OBJECT_CLOSE, JSON_TOKEN_END);
+        test_tokenizer("\"\xef\xbf\xbd\"", JSON_TOKEN_STRING, "\xef\xbf\xbd", JSON_TOKEN_END);
+        test_tokenizer("\"\\ufffd\"", JSON_TOKEN_STRING, "\xef\xbf\xbd", JSON_TOKEN_END);
+        test_tokenizer("\"\\uf\"", -EINVAL);
+        test_tokenizer("\"\\ud800a\"", -EINVAL);
+        test_tokenizer("\"\\udc00\\udc00\"", -EINVAL);
+        test_tokenizer("\"\\ud801\\udc37\"", JSON_TOKEN_STRING, "\xf0\x90\x90\xb7", JSON_TOKEN_END);
+
+        test_tokenizer("[1, 2, -3]", JSON_TOKEN_ARRAY_OPEN, JSON_TOKEN_UNSIGNED, (uintmax_t) 1, JSON_TOKEN_COMMA, JSON_TOKEN_UNSIGNED, (uintmax_t) 2, JSON_TOKEN_COMMA, JSON_TOKEN_INTEGER, (intmax_t) -3, JSON_TOKEN_ARRAY_CLOSE, JSON_TOKEN_END);
+
+        test_variant("{\"k\": \"v\", \"foo\": [1, 2, 3], \"bar\": {\"zap\": null}}", test_1);
+        test_variant("{\"mutant\": [1, null, \"1\", {\"1\": [1, \"1\"]}], \"thisisaverylongproperty\": 1.27}", test_2);
+        test_variant("{\"foo\" : \"\\uDBFF\\uDFFF\\\"\\uD9FF\\uDFFFFFF\\\"\\uDBFF\\uDFFF\\\"\\uD9FF\\uDFFF\\uDBFF\\uDFFFF\\uDBFF\\uDFFF\\uDBFF\\uDFFF\\uDBFF\\uDFFF\\uDBFF\\uDFFF\\\"\\uD9FF\\uDFFFFF\\\"\\uDBFF\\uDFFF\\\"\\uD9FF\\uDFFF\\uDBFF\\uDFFF\"}", NULL);
+
+        test_variant("[ 0, -0, 0.0, -0.0, 0.000, -0.000, 0e0, -0e0, 0e+0, -0e-0, 0e-0, -0e000, 0e+000 ]", test_zeroes);
+
+        test_build();
+
+        test_source();
+
+        return 0;
+}
index 03378ecf089c1aa0f17b32edb78913c2c36f002b..2c6ca0a1a292f51ca52e1cb552a79d3967af786e 100644 (file)
@@ -24,7 +24,7 @@ int main(int argc, char *argv[]) {
         assert_se(set_unit_path(get_testdata_dir()) >= 0);
         assert_se(runtime_dir = setup_fake_runtime_dir());
 
-        assert_se(manager_new(UNIT_FILE_USER, true, &m) >= 0);
+        assert_se(manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0);
         assert_se(manager_startup(m, NULL, NULL) >= 0);
 
         assert_se(a = unit_new(m, sizeof(Service)));
index b411e96915319a3192f0ce2a7211a12375a601aa..daec2ead884af57287b98bb05a5ab2f3891b78a1 100644 (file)
@@ -9,7 +9,7 @@ sanitize_address = custom_target(
                    'fuzzers',
                    '-Db_lundef=false -Db_sanitize=address',
                    ' '.join(cc.cmd_array()),
-                   cpp_cmd])
+                   cxx_cmd])
 
 sanitizers = [['address', sanitize_address]]
 
index 9ee6154f953b01116a7a5eb0fd4f12aab56b9970..e3549c22c9c48c58317f7ef1153d011c90a44002 100644 (file)
@@ -2,9 +2,9 @@
 Description=Test DynamicUser with SupplementaryGroups=
 
 [Service]
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "1" && HAVE=1; done; test "$$HAVE" -eq 1'
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "2" && HAVE=1; done; test "$$HAVE" -eq 1'
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "3" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "1" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "2" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "3" && HAVE=1; done; test "$$HAVE" -eq 1'
 Type=oneshot
 DynamicUser=yes
 SupplementaryGroups=1 2 3
index 1c565b412276d087aed6e8328f2311130419de42..3b8cf2445bad9685b87cf4285d3074c55d6d57c3 100644 (file)
@@ -2,10 +2,10 @@
 Description=Test for Supplementary Group with multiple groups without Group and User
 
 [Service]
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "0" && HAVE=1; done; test "$$HAVE" -eq 1'
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "1" && HAVE=1; done; test "$$HAVE" -eq 1'
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "2" && HAVE=1; done; test "$$HAVE" -eq 1'
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "3" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "0" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "1" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "2" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "3" && HAVE=1; done; test "$$HAVE" -eq 1'
 ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "0" && test "$$(id -u)" = "0"'
 Type=oneshot
 SupplementaryGroups=1 2 3
index 45bcf79222147fe5e261e0747704db9cce6628e8..b7feedff61f8f0258c86f7cc32adeefb420a5fb9 100644 (file)
@@ -2,9 +2,9 @@
 Description=Test for Supplementary Group with multiple groups and Group=1
 
 [Service]
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "1" && HAVE=1; done; test "$$HAVE" -eq 1'
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "2" && HAVE=1; done; test "$$HAVE" -eq 1'
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "3" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "1" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "2" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "3" && HAVE=1; done; test "$$HAVE" -eq 1'
 ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "0"'
 Type=oneshot
 Group=1
index 62e56a2c23be18e77a63fce2f29fe93c0ba6938c..2714235be6809b5ffce6d2b25675a7fc19a5d341 100644 (file)
@@ -2,9 +2,9 @@
 Description=Test for Supplementary Group with multiple groups and Uid=1
 
 [Service]
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "1" && HAVE=1; done; test "$$HAVE" -eq 1'
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "2" && HAVE=1; done; test "$$HAVE" -eq 1'
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "3" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "1" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "2" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "3" && HAVE=1; done; test "$$HAVE" -eq 1'
 Type=oneshot
 User=1
 SupplementaryGroups=1 2 3
index 97f9f9c786e8528b3e9ce15c48bfc02dbb407fbb..405c5f9bfe78d9e68ee4b53af8c0bde9c6b7fac7 100644 (file)
@@ -2,7 +2,7 @@
 Description=Test for Supplementary Group with only one group and uid 1
 
 [Service]
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "1" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "1" && HAVE=1; done; test "$$HAVE" -eq 1'
 ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "1"'
 Type=oneshot
 User=1
index 4dee6af748424efc939bda08e0af06a84ea7b5e8..5822c6b870766a196f8dd17fada15bb44b1756a2 100644 (file)
@@ -2,7 +2,7 @@
 Description=Test for Supplementary Group
 
 [Service]
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "0" && HAVE=1; done; test "$$HAVE" -eq 1'
-ExecStart=/bin/sh -x -c 'HAVE=; for g in $$(id -G); do test "$$g" = "1" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "0" && HAVE=1; done; test "$$HAVE" -eq 1'
+ExecStart=/bin/sh -x -c 'HAVE=0; for g in $$(id -G); do test "$$g" = "1" && HAVE=1; done; test "$$HAVE" -eq 1'
 Type=oneshot
 SupplementaryGroups=1
index 9919728454985f0ec4eadd8163ad81687c00367f..88e66fd72e3ad80228d3951fd1ddde644f3c6e7e 100644 (file)
@@ -331,7 +331,7 @@ set -x
 
 DEFAULT_ASAN_OPTIONS=strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1
 DEFAULT_UBSAN_OPTIONS=print_stacktrace=1:print_summary=1
-DEFAULT_ENVIRONMENT="ASAN_OPTIONS=\$DEFAULT_ASAN_OPTIONS UBSAN_OPTIONS=\$DEFAULT_UBSAN_OPTIONS"
+DEFAULT_ENVIRONMENT="ASAN_OPTIONS=\$DEFAULT_ASAN_OPTIONS UBSAN_OPTIONS=\$DEFAULT_UBSAN_OPTIONS:halt_on_error=1"
 
 mount -t proc proc /proc
 mount -t sysfs sysfs /sys
index 4d11e81ed60988b8d63152d0bdb826906a5ebb2a..df72e47d7dae79ab678d9774ad179cc6e28aa7ea 100755 (executable)
@@ -48,5 +48,8 @@ zip -jqr $OUT/fuzz-dns-packet_seed_corpus.zip $df/packet
 
 install -Dt $OUT/src/shared/ $build/src/shared/libsystemd-shared-*.so
 
+wget -O $OUT/fuzz-json_seed_corpus.zip https://storage.googleapis.com/skia-fuzzer/oss-fuzz/skjson_seed_corpus.zip
+wget -O $OUT/fuzz-json.dict https://raw.githubusercontent.com/rc0r/afl-fuzz/master/dictionaries/json.dict
+
 find $build -maxdepth 1 -type f -executable -name "fuzz-*" -exec mv {} $OUT \;
 cp src/fuzz/*.options $OUT