Move various files that don't need to be in basic/ to shared/
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Mon, 19 Nov 2018 10:12:28 +0000 (11:12 +0100)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Tue, 20 Nov 2018 06:27:37 +0000 (07:27 +0100)
This doesn't have much effect on the final build, because we link libbasic.a
into libsystemd-shared.so, so in the end, all the object built from basic/
end up in libsystemd-shared. And when the static library is linked into binaries,
any objects that are included in it but are not used are trimmed. Hence, the
size of output artifacts doesn't change:

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

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

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

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

95 files changed:
meson.build
src/basic/barrier.c [deleted file]
src/basic/barrier.h [deleted file]
src/basic/bitmap.c [deleted file]
src/basic/bitmap.h [deleted file]
src/basic/blkid-util.h [deleted file]
src/basic/bpf-program.c [deleted file]
src/basic/bpf-program.h [deleted file]
src/basic/calendarspec.c [deleted file]
src/basic/calendarspec.h [deleted file]
src/basic/clock-util.c [deleted file]
src/basic/clock-util.h [deleted file]
src/basic/cpu-set-util.c [deleted file]
src/basic/cpu-set-util.h [deleted file]
src/basic/crypt-util.c [deleted file]
src/basic/crypt-util.h [deleted file]
src/basic/exec-util.c [deleted file]
src/basic/exec-util.h [deleted file]
src/basic/exit-status.c [deleted file]
src/basic/exit-status.h [deleted file]
src/basic/fileio-label.c [deleted file]
src/basic/fileio-label.h [deleted file]
src/basic/format-table.c [deleted file]
src/basic/format-table.h [deleted file]
src/basic/journal-importer.c [deleted file]
src/basic/journal-importer.h [deleted file]
src/basic/json-internal.h [deleted file]
src/basic/json.c [deleted file]
src/basic/json.h [deleted file]
src/basic/lockfile-util.c [deleted file]
src/basic/lockfile-util.h [deleted file]
src/basic/meson.build
src/basic/os-util.c [deleted file]
src/basic/os-util.h [deleted file]
src/basic/reboot-util.c [deleted file]
src/basic/reboot-util.h [deleted file]
src/basic/rlimit-util.c [deleted file]
src/basic/rlimit-util.h [deleted file]
src/basic/securebits-util.c [deleted file]
src/basic/securebits-util.h [deleted file]
src/basic/socket-protocol-list.c [deleted file]
src/basic/socket-protocol-list.h [deleted file]
src/basic/verbs.c [deleted file]
src/basic/verbs.h [deleted file]
src/basic/web-util.c [deleted file]
src/basic/web-util.h [deleted file]
src/basic/xml.c [deleted file]
src/basic/xml.h [deleted file]
src/shared/barrier.c [new file with mode: 0644]
src/shared/barrier.h [new file with mode: 0644]
src/shared/bitmap.c [new file with mode: 0644]
src/shared/bitmap.h [new file with mode: 0644]
src/shared/blkid-util.h [new file with mode: 0644]
src/shared/bpf-program.c [new file with mode: 0644]
src/shared/bpf-program.h [new file with mode: 0644]
src/shared/calendarspec.c [new file with mode: 0644]
src/shared/calendarspec.h [new file with mode: 0644]
src/shared/clock-util.c [new file with mode: 0644]
src/shared/clock-util.h [new file with mode: 0644]
src/shared/cpu-set-util.c [new file with mode: 0644]
src/shared/cpu-set-util.h [new file with mode: 0644]
src/shared/crypt-util.c [new file with mode: 0644]
src/shared/crypt-util.h [new file with mode: 0644]
src/shared/exec-util.c [new file with mode: 0644]
src/shared/exec-util.h [new file with mode: 0644]
src/shared/exit-status.c [new file with mode: 0644]
src/shared/exit-status.h [new file with mode: 0644]
src/shared/fileio-label.c [new file with mode: 0644]
src/shared/fileio-label.h [new file with mode: 0644]
src/shared/format-table.c [new file with mode: 0644]
src/shared/format-table.h [new file with mode: 0644]
src/shared/journal-importer.c [new file with mode: 0644]
src/shared/journal-importer.h [new file with mode: 0644]
src/shared/json-internal.h [new file with mode: 0644]
src/shared/json.c [new file with mode: 0644]
src/shared/json.h [new file with mode: 0644]
src/shared/lockfile-util.c [new file with mode: 0644]
src/shared/lockfile-util.h [new file with mode: 0644]
src/shared/meson.build
src/shared/os-util.c [new file with mode: 0644]
src/shared/os-util.h [new file with mode: 0644]
src/shared/reboot-util.c [new file with mode: 0644]
src/shared/reboot-util.h [new file with mode: 0644]
src/shared/rlimit-util.c [new file with mode: 0644]
src/shared/rlimit-util.h [new file with mode: 0644]
src/shared/securebits-util.c [new file with mode: 0644]
src/shared/securebits-util.h [new file with mode: 0644]
src/shared/socket-protocol-list.c [new file with mode: 0644]
src/shared/socket-protocol-list.h [new file with mode: 0644]
src/shared/verbs.c [new file with mode: 0644]
src/shared/verbs.h [new file with mode: 0644]
src/shared/web-util.c [new file with mode: 0644]
src/shared/web-util.h [new file with mode: 0644]
src/shared/xml.c [new file with mode: 0644]
src/shared/xml.h [new file with mode: 0644]

index 906960fb7174316c201f14510eb4054a634d04ba..b989c664b1ccaf277f51146d2fbf73f6b8fc30dc 100644 (file)
@@ -1666,7 +1666,7 @@ endif
 if conf.get('HAVE_BLKID') == 1
         executable('systemd-gpt-auto-generator',
                    'src/gpt-auto-generator/gpt-auto-generator.c',
-                   'src/basic/blkid-util.h',
+                   'src/shared/blkid-util.h',
                    include_directories : includes,
                    link_with : [libshared],
                    dependencies : libblkid,
diff --git a/src/basic/barrier.c b/src/basic/barrier.c
deleted file mode 100644 (file)
index 79316dd..0000000
+++ /dev/null
@@ -1,398 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <errno.h>
-#include <fcntl.h>
-#include <poll.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <sys/eventfd.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "barrier.h"
-#include "fd-util.h"
-#include "macro.h"
-
-/**
- * Barriers
- * This barrier implementation provides a simple synchronization method based
- * on file-descriptors that can safely be used between threads and processes. A
- * barrier object contains 2 shared counters based on eventfd. Both processes
- * can now place barriers and wait for the other end to reach a random or
- * specific barrier.
- * Barriers are numbered, so you can either wait for the other end to reach any
- * barrier or the last barrier that you placed. This way, you can use barriers
- * for one-way *and* full synchronization. Note that even-though barriers are
- * numbered, these numbers are internal and recycled once both sides reached the
- * same barrier (implemented as a simple signed counter). It is thus not
- * possible to address barriers by their ID.
- *
- * Barrier-API: Both ends can place as many barriers via barrier_place() as
- * they want and each pair of barriers on both sides will be implicitly linked.
- * Each side can use the barrier_wait/sync_*() family of calls to wait for the
- * other side to place a specific barrier. barrier_wait_next() waits until the
- * other side calls barrier_place(). No links between the barriers are
- * considered and this simply serves as most basic asynchronous barrier.
- * barrier_sync_next() is like barrier_wait_next() and waits for the other side
- * to place their next barrier via barrier_place(). However, it only waits for
- * barriers that are linked to a barrier we already placed. If the other side
- * already placed more barriers than we did, barrier_sync_next() returns
- * immediately.
- * barrier_sync() extends barrier_sync_next() and waits until the other end
- * placed as many barriers via barrier_place() as we did. If they already placed
- * as many as we did (or more), it returns immediately.
- *
- * Additionally to basic barriers, an abortion event is available.
- * barrier_abort() places an abortion event that cannot be undone. An abortion
- * immediately cancels all placed barriers and replaces them. Any running and
- * following wait/sync call besides barrier_wait_abortion() will immediately
- * return false on both sides (otherwise, they always return true).
- * barrier_abort() can be called multiple times on both ends and will be a
- * no-op if already called on this side.
- * barrier_wait_abortion() can be used to wait for the other side to call
- * barrier_abort() and is the only wait/sync call that does not return
- * immediately if we aborted outself. It only returns once the other side
- * called barrier_abort().
- *
- * Barriers can be used for in-process and inter-process synchronization.
- * However, for in-process synchronization you could just use mutexes.
- * Therefore, main target is IPC and we require both sides to *not* share the FD
- * table. If that's given, barriers provide target tracking: If the remote side
- * exit()s, an abortion event is implicitly queued on the other side. This way,
- * a sync/wait call will be woken up if the remote side crashed or exited
- * unexpectedly. However, note that these abortion events are only queued if the
- * barrier-queue has been drained. Therefore, it is safe to place a barrier and
- * exit. The other side can safely wait on the barrier even though the exit
- * queued an abortion event. Usually, the abortion event would overwrite the
- * barrier, however, that's not true for exit-abortion events. Those are only
- * queued if the barrier-queue is drained (thus, the receiving side has placed
- * more barriers than the remote side).
- */
-
-/**
- * barrier_create() - Initialize a barrier object
- * @obj: barrier to initialize
- *
- * This initializes a barrier object. The caller is responsible of allocating
- * the memory and keeping it valid. The memory does not have to be zeroed
- * beforehand.
- * Two eventfd objects are allocated for each barrier. If allocation fails, an
- * error is returned.
- *
- * If this function fails, the barrier is reset to an invalid state so it is
- * safe to call barrier_destroy() on the object regardless whether the
- * initialization succeeded or not.
- *
- * The caller is responsible to destroy the object via barrier_destroy() before
- * releasing the underlying memory.
- *
- * Returns: 0 on success, negative error code on failure.
- */
-int barrier_create(Barrier *b) {
-        _cleanup_(barrier_destroyp) Barrier *staging = b;
-        int r;
-
-        assert(b);
-
-        b->me = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
-        if (b->me < 0)
-                return -errno;
-
-        b->them = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
-        if (b->them < 0)
-                return -errno;
-
-        r = pipe2(b->pipe, O_CLOEXEC | O_NONBLOCK);
-        if (r < 0)
-                return -errno;
-
-        staging = NULL;
-        return 0;
-}
-
-/**
- * barrier_destroy() - Destroy a barrier object
- * @b: barrier to destroy or NULL
- *
- * This destroys a barrier object that has previously been passed to
- * barrier_create(). The object is released and reset to invalid
- * state. Therefore, it is safe to call barrier_destroy() multiple
- * times or even if barrier_create() failed. However, barrier must be
- * always initialized with BARRIER_NULL.
- *
- * If @b is NULL, this is a no-op.
- */
-void barrier_destroy(Barrier *b) {
-        if (!b)
-                return;
-
-        b->me = safe_close(b->me);
-        b->them = safe_close(b->them);
-        safe_close_pair(b->pipe);
-        b->barriers = 0;
-}
-
-/**
- * barrier_set_role() - Set the local role of the barrier
- * @b: barrier to operate on
- * @role: role to set on the barrier
- *
- * This sets the roles on a barrier object. This is needed to know
- * which side of the barrier you're on. Usually, the parent creates
- * the barrier via barrier_create() and then calls fork() or clone().
- * Therefore, the FDs are duplicated and the child retains the same
- * barrier object.
- *
- * Both sides need to call barrier_set_role() after fork() or clone()
- * are done. If this is not done, barriers will not work correctly.
- *
- * Note that barriers could be supported without fork() or clone(). However,
- * this is currently not needed so it hasn't been implemented.
- */
-void barrier_set_role(Barrier *b, unsigned role) {
-        int fd;
-
-        assert(b);
-        assert(IN_SET(role, BARRIER_PARENT, BARRIER_CHILD));
-        /* make sure this is only called once */
-        assert(b->pipe[0] >= 0 && b->pipe[1] >= 0);
-
-        if (role == BARRIER_PARENT)
-                b->pipe[1] = safe_close(b->pipe[1]);
-        else {
-                b->pipe[0] = safe_close(b->pipe[0]);
-
-                /* swap me/them for children */
-                fd = b->me;
-                b->me = b->them;
-                b->them = fd;
-        }
-}
-
-/* places barrier; returns false if we aborted, otherwise true */
-static bool barrier_write(Barrier *b, uint64_t buf) {
-        ssize_t len;
-
-        /* prevent new sync-points if we already aborted */
-        if (barrier_i_aborted(b))
-                return false;
-
-        assert(b->me >= 0);
-        do {
-                len = write(b->me, &buf, sizeof(buf));
-        } while (len < 0 && IN_SET(errno, EAGAIN, EINTR));
-
-        if (len != sizeof(buf))
-                goto error;
-
-        /* lock if we aborted */
-        if (buf >= (uint64_t)BARRIER_ABORTION) {
-                if (barrier_they_aborted(b))
-                        b->barriers = BARRIER_WE_ABORTED;
-                else
-                        b->barriers = BARRIER_I_ABORTED;
-        } else if (!barrier_is_aborted(b))
-                b->barriers += buf;
-
-        return !barrier_i_aborted(b);
-
-error:
-        /* If there is an unexpected error, we have to make this fatal. There
-         * is no way we can recover from sync-errors. Therefore, we close the
-         * pipe-ends and treat this as abortion. The other end will notice the
-         * pipe-close and treat it as abortion, too. */
-
-        safe_close_pair(b->pipe);
-        b->barriers = BARRIER_WE_ABORTED;
-        return false;
-}
-
-/* waits for barriers; returns false if they aborted, otherwise true */
-static bool barrier_read(Barrier *b, int64_t comp) {
-        if (barrier_they_aborted(b))
-                return false;
-
-        while (b->barriers > comp) {
-                struct pollfd pfd[2] = {
-                        { .fd = b->pipe[0] >= 0 ? b->pipe[0] : b->pipe[1],
-                          .events = POLLHUP },
-                        { .fd = b->them,
-                          .events = POLLIN }};
-                uint64_t buf;
-                int r;
-
-                r = poll(pfd, 2, -1);
-                if (r < 0 && IN_SET(errno, EAGAIN, EINTR))
-                        continue;
-                else if (r < 0)
-                        goto error;
-
-                if (pfd[1].revents) {
-                        ssize_t len;
-
-                        /* events on @them signal new data for us */
-                        len = read(b->them, &buf, sizeof(buf));
-                        if (len < 0 && IN_SET(errno, EAGAIN, EINTR))
-                                continue;
-
-                        if (len != sizeof(buf))
-                                goto error;
-                } else if (pfd[0].revents & (POLLHUP | POLLERR | POLLNVAL))
-                        /* POLLHUP on the pipe tells us the other side exited.
-                         * We treat this as implicit abortion. But we only
-                         * handle it if there's no event on the eventfd. This
-                         * guarantees that exit-abortions do not overwrite real
-                         * barriers. */
-                        buf = BARRIER_ABORTION;
-                else
-                        continue;
-
-                /* lock if they aborted */
-                if (buf >= (uint64_t)BARRIER_ABORTION) {
-                        if (barrier_i_aborted(b))
-                                b->barriers = BARRIER_WE_ABORTED;
-                        else
-                                b->barriers = BARRIER_THEY_ABORTED;
-                } else if (!barrier_is_aborted(b))
-                        b->barriers -= buf;
-        }
-
-        return !barrier_they_aborted(b);
-
-error:
-        /* If there is an unexpected error, we have to make this fatal. There
-         * is no way we can recover from sync-errors. Therefore, we close the
-         * pipe-ends and treat this as abortion. The other end will notice the
-         * pipe-close and treat it as abortion, too. */
-
-        safe_close_pair(b->pipe);
-        b->barriers = BARRIER_WE_ABORTED;
-        return false;
-}
-
-/**
- * barrier_place() - Place a new barrier
- * @b: barrier object
- *
- * This places a new barrier on the barrier object. If either side already
- * aborted, this is a no-op and returns "false". Otherwise, the barrier is
- * placed and this returns "true".
- *
- * Returns: true if barrier was placed, false if either side aborted.
- */
-bool barrier_place(Barrier *b) {
-        assert(b);
-
-        if (barrier_is_aborted(b))
-                return false;
-
-        barrier_write(b, BARRIER_SINGLE);
-        return true;
-}
-
-/**
- * barrier_abort() - Abort the synchronization
- * @b: barrier object to abort
- *
- * This aborts the barrier-synchronization. If barrier_abort() was already
- * called on this side, this is a no-op. Otherwise, the barrier is put into the
- * ABORT-state and will stay there. The other side is notified about the
- * abortion. Any following attempt to place normal barriers or to wait on normal
- * barriers will return immediately as "false".
- *
- * You can wait for the other side to call barrier_abort(), too. Use
- * barrier_wait_abortion() for that.
- *
- * Returns: false if the other side already aborted, true otherwise.
- */
-bool barrier_abort(Barrier *b) {
-        assert(b);
-
-        barrier_write(b, BARRIER_ABORTION);
-        return !barrier_they_aborted(b);
-}
-
-/**
- * barrier_wait_next() - Wait for the next barrier of the other side
- * @b: barrier to operate on
- *
- * This waits until the other side places its next barrier. This is independent
- * of any barrier-links and just waits for any next barrier of the other side.
- *
- * If either side aborted, this returns false.
- *
- * Returns: false if either side aborted, true otherwise.
- */
-bool barrier_wait_next(Barrier *b) {
-        assert(b);
-
-        if (barrier_is_aborted(b))
-                return false;
-
-        barrier_read(b, b->barriers - 1);
-        return !barrier_is_aborted(b);
-}
-
-/**
- * barrier_wait_abortion() - Wait for the other side to abort
- * @b: barrier to operate on
- *
- * This waits until the other side called barrier_abort(). This can be called
- * regardless whether the local side already called barrier_abort() or not.
- *
- * If the other side has already aborted, this returns immediately.
- *
- * Returns: false if the local side aborted, true otherwise.
- */
-bool barrier_wait_abortion(Barrier *b) {
-        assert(b);
-
-        barrier_read(b, BARRIER_THEY_ABORTED);
-        return !barrier_i_aborted(b);
-}
-
-/**
- * barrier_sync_next() - Wait for the other side to place a next linked barrier
- * @b: barrier to operate on
- *
- * This is like barrier_wait_next() and waits for the other side to call
- * barrier_place(). However, this only waits for linked barriers. That means, if
- * the other side already placed more barriers than (or as much as) we did, this
- * returns immediately instead of waiting.
- *
- * If either side aborted, this returns false.
- *
- * Returns: false if either side aborted, true otherwise.
- */
-bool barrier_sync_next(Barrier *b) {
-        assert(b);
-
-        if (barrier_is_aborted(b))
-                return false;
-
-        barrier_read(b, MAX((int64_t)0, b->barriers - 1));
-        return !barrier_is_aborted(b);
-}
-
-/**
- * barrier_sync() - Wait for the other side to place as many barriers as we did
- * @b: barrier to operate on
- *
- * This is like barrier_sync_next() but waits for the other side to call
- * barrier_place() as often as we did (in total). If they already placed as much
- * as we did (or more), this returns immediately instead of waiting.
- *
- * If either side aborted, this returns false.
- *
- * Returns: false if either side aborted, true otherwise.
- */
-bool barrier_sync(Barrier *b) {
-        assert(b);
-
-        if (barrier_is_aborted(b))
-                return false;
-
-        barrier_read(b, 0);
-        return !barrier_is_aborted(b);
-}
diff --git a/src/basic/barrier.h b/src/basic/barrier.h
deleted file mode 100644 (file)
index 0eb3d27..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <sys/types.h>
-
-#include "macro.h"
-
-/* See source file for an API description. */
-
-typedef struct Barrier Barrier;
-
-enum {
-        BARRIER_SINGLE                  = 1LL,
-        BARRIER_ABORTION                = INT64_MAX,
-
-        /* bias values to store state; keep @WE < @THEY < @I */
-        BARRIER_BIAS                    = INT64_MIN,
-        BARRIER_WE_ABORTED              = BARRIER_BIAS + 1LL,
-        BARRIER_THEY_ABORTED            = BARRIER_BIAS + 2LL,
-        BARRIER_I_ABORTED               = BARRIER_BIAS + 3LL,
-};
-
-enum {
-        BARRIER_PARENT,
-        BARRIER_CHILD,
-};
-
-struct Barrier {
-        int me;
-        int them;
-        int pipe[2];
-        int64_t barriers;
-};
-
-#define BARRIER_NULL {-1, -1, {-1, -1}, 0}
-
-int barrier_create(Barrier *obj);
-void barrier_destroy(Barrier *b);
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(Barrier*, barrier_destroy);
-
-void barrier_set_role(Barrier *b, unsigned role);
-
-bool barrier_place(Barrier *b);
-bool barrier_abort(Barrier *b);
-
-bool barrier_wait_next(Barrier *b);
-bool barrier_wait_abortion(Barrier *b);
-bool barrier_sync_next(Barrier *b);
-bool barrier_sync(Barrier *b);
-
-static inline bool barrier_i_aborted(Barrier *b) {
-        return IN_SET(b->barriers, BARRIER_I_ABORTED, BARRIER_WE_ABORTED);
-}
-
-static inline bool barrier_they_aborted(Barrier *b) {
-        return IN_SET(b->barriers, BARRIER_THEY_ABORTED, BARRIER_WE_ABORTED);
-}
-
-static inline bool barrier_we_aborted(Barrier *b) {
-        return b->barriers == BARRIER_WE_ABORTED;
-}
-
-static inline bool barrier_is_aborted(Barrier *b) {
-        return IN_SET(b->barriers,
-                      BARRIER_I_ABORTED, BARRIER_THEY_ABORTED, BARRIER_WE_ABORTED);
-}
-
-static inline bool barrier_place_and_sync(Barrier *b) {
-        (void) barrier_place(b);
-        return barrier_sync(b);
-}
diff --git a/src/basic/bitmap.c b/src/basic/bitmap.c
deleted file mode 100644 (file)
index a4cd645..0000000
+++ /dev/null
@@ -1,218 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <errno.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "alloc-util.h"
-#include "bitmap.h"
-#include "hashmap.h"
-#include "macro.h"
-
-struct Bitmap {
-        uint64_t *bitmaps;
-        size_t n_bitmaps;
-        size_t bitmaps_allocated;
-};
-
-/* Bitmaps are only meant to store relatively small numbers
- * (corresponding to, say, an enum), so it is ok to limit
- * the max entry. 64k should be plenty. */
-#define BITMAPS_MAX_ENTRY 0xffff
-
-/* This indicates that we reached the end of the bitmap */
-#define BITMAP_END ((unsigned) -1)
-
-#define BITMAP_NUM_TO_OFFSET(n)           ((n) / (sizeof(uint64_t) * 8))
-#define BITMAP_NUM_TO_REM(n)              ((n) % (sizeof(uint64_t) * 8))
-#define BITMAP_OFFSET_TO_NUM(offset, rem) ((offset) * sizeof(uint64_t) * 8 + (rem))
-
-Bitmap *bitmap_new(void) {
-        return new0(Bitmap, 1);
-}
-
-Bitmap *bitmap_copy(Bitmap *b) {
-        Bitmap *ret;
-
-        ret = bitmap_new();
-        if (!ret)
-                return NULL;
-
-        ret->bitmaps = newdup(uint64_t, b->bitmaps, b->n_bitmaps);
-        if (!ret->bitmaps)
-                return mfree(ret);
-
-        ret->n_bitmaps = ret->bitmaps_allocated = b->n_bitmaps;
-        return ret;
-}
-
-void bitmap_free(Bitmap *b) {
-        if (!b)
-                return;
-
-        free(b->bitmaps);
-        free(b);
-}
-
-int bitmap_ensure_allocated(Bitmap **b) {
-        Bitmap *a;
-
-        assert(b);
-
-        if (*b)
-                return 0;
-
-        a = bitmap_new();
-        if (!a)
-                return -ENOMEM;
-
-        *b = a;
-
-        return 0;
-}
-
-int bitmap_set(Bitmap *b, unsigned n) {
-        uint64_t bitmask;
-        unsigned offset;
-
-        assert(b);
-
-        /* we refuse to allocate huge bitmaps */
-        if (n > BITMAPS_MAX_ENTRY)
-                return -ERANGE;
-
-        offset = BITMAP_NUM_TO_OFFSET(n);
-
-        if (offset >= b->n_bitmaps) {
-                if (!GREEDY_REALLOC0(b->bitmaps, b->bitmaps_allocated, offset + 1))
-                        return -ENOMEM;
-
-                b->n_bitmaps = offset + 1;
-        }
-
-        bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n);
-
-        b->bitmaps[offset] |= bitmask;
-
-        return 0;
-}
-
-void bitmap_unset(Bitmap *b, unsigned n) {
-        uint64_t bitmask;
-        unsigned offset;
-
-        if (!b)
-                return;
-
-        offset = BITMAP_NUM_TO_OFFSET(n);
-
-        if (offset >= b->n_bitmaps)
-                return;
-
-        bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n);
-
-        b->bitmaps[offset] &= ~bitmask;
-}
-
-bool bitmap_isset(Bitmap *b, unsigned n) {
-        uint64_t bitmask;
-        unsigned offset;
-
-        if (!b)
-                return false;
-
-        offset = BITMAP_NUM_TO_OFFSET(n);
-
-        if (offset >= b->n_bitmaps)
-                return false;
-
-        bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n);
-
-        return !!(b->bitmaps[offset] & bitmask);
-}
-
-bool bitmap_isclear(Bitmap *b) {
-        unsigned i;
-
-        if (!b)
-                return true;
-
-        for (i = 0; i < b->n_bitmaps; i++)
-                if (b->bitmaps[i] != 0)
-                        return false;
-
-        return true;
-}
-
-void bitmap_clear(Bitmap *b) {
-
-        if (!b)
-                return;
-
-        b->bitmaps = mfree(b->bitmaps);
-        b->n_bitmaps = 0;
-        b->bitmaps_allocated = 0;
-}
-
-bool bitmap_iterate(Bitmap *b, Iterator *i, unsigned *n) {
-        uint64_t bitmask;
-        unsigned offset, rem;
-
-        assert(i);
-        assert(n);
-
-        if (!b || i->idx == BITMAP_END)
-                return false;
-
-        offset = BITMAP_NUM_TO_OFFSET(i->idx);
-        rem = BITMAP_NUM_TO_REM(i->idx);
-        bitmask = UINT64_C(1) << rem;
-
-        for (; offset < b->n_bitmaps; offset ++) {
-                if (b->bitmaps[offset]) {
-                        for (; bitmask; bitmask <<= 1, rem ++) {
-                                if (b->bitmaps[offset] & bitmask) {
-                                        *n = BITMAP_OFFSET_TO_NUM(offset, rem);
-                                        i->idx = *n + 1;
-
-                                        return true;
-                                }
-                        }
-                }
-
-                rem = 0;
-                bitmask = 1;
-        }
-
-        i->idx = BITMAP_END;
-
-        return false;
-}
-
-bool bitmap_equal(Bitmap *a, Bitmap *b) {
-        size_t common_n_bitmaps;
-        Bitmap *c;
-        unsigned i;
-
-        if (a == b)
-                return true;
-
-        if (!a != !b)
-                return false;
-
-        if (!a)
-                return true;
-
-        common_n_bitmaps = MIN(a->n_bitmaps, b->n_bitmaps);
-        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;
-        for (i = common_n_bitmaps; i < c->n_bitmaps; i++)
-                if (c->bitmaps[i] != 0)
-                        return false;
-
-        return true;
-}
diff --git a/src/basic/bitmap.h b/src/basic/bitmap.h
deleted file mode 100644 (file)
index 843d27d..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-
-#include "hashmap.h"
-#include "macro.h"
-
-typedef struct Bitmap Bitmap;
-
-Bitmap *bitmap_new(void);
-Bitmap *bitmap_copy(Bitmap *b);
-int bitmap_ensure_allocated(Bitmap **b);
-void bitmap_free(Bitmap *b);
-
-int bitmap_set(Bitmap *b, unsigned n);
-void bitmap_unset(Bitmap *b, unsigned n);
-bool bitmap_isset(Bitmap *b, unsigned n);
-bool bitmap_isclear(Bitmap *b);
-void bitmap_clear(Bitmap *b);
-
-bool bitmap_iterate(Bitmap *b, Iterator *i, unsigned *n);
-
-bool bitmap_equal(Bitmap *a, Bitmap *b);
-
-#define BITMAP_FOREACH(n, b, i) \
-        for ((i).idx = 0; bitmap_iterate((b), &(i), (unsigned*)&(n)); )
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(Bitmap*, bitmap_free);
-
-#define _cleanup_bitmap_free_ _cleanup_(bitmap_freep)
diff --git a/src/basic/blkid-util.h b/src/basic/blkid-util.h
deleted file mode 100644 (file)
index e4eb600..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#if HAVE_BLKID
-#include <blkid.h>
-#endif
-
-#include "util.h"
-
-#if HAVE_BLKID
-DEFINE_TRIVIAL_CLEANUP_FUNC(blkid_probe, blkid_free_probe);
-#endif
diff --git a/src/basic/bpf-program.c b/src/basic/bpf-program.c
deleted file mode 100644 (file)
index 2c61e04..0000000
+++ /dev/null
@@ -1,237 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "alloc-util.h"
-#include "bpf-program.h"
-#include "fd-util.h"
-#include "log.h"
-#include "missing.h"
-#include "path-util.h"
-#include "util.h"
-
-int bpf_program_new(uint32_t prog_type, BPFProgram **ret) {
-        _cleanup_(bpf_program_unrefp) BPFProgram *p = NULL;
-
-        p = new0(BPFProgram, 1);
-        if (!p)
-                return log_oom();
-
-        p->n_ref = 1;
-        p->prog_type = prog_type;
-        p->kernel_fd = -1;
-
-        *ret = TAKE_PTR(p);
-
-        return 0;
-}
-
-static BPFProgram *bpf_program_free(BPFProgram *p) {
-        assert(p);
-
-        /* Unfortunately, the kernel currently doesn't implicitly detach BPF programs from their cgroups when the last
-         * fd to the BPF program is closed. This has nasty side-effects since this means that abnormally terminated
-         * programs that attached one of their BPF programs to a cgroup will leave this programs pinned for good with
-         * zero chance of recovery, until the cgroup is removed. This is particularly problematic if the cgroup in
-         * question is the root cgroup (or any other cgroup belonging to a service that cannot be restarted during
-         * operation, such as dbus), as the memory for the BPF program can only be reclaimed through a reboot. To
-         * counter this, we track closely to which cgroup a program was attached to and will detach it on our own
-         * whenever we close the BPF fd. */
-        (void) bpf_program_cgroup_detach(p);
-
-        safe_close(p->kernel_fd);
-        free(p->instructions);
-        free(p->attached_path);
-
-        return mfree(p);
-}
-
-DEFINE_TRIVIAL_REF_UNREF_FUNC(BPFProgram, bpf_program, bpf_program_free);
-
-int bpf_program_add_instructions(BPFProgram *p, const struct bpf_insn *instructions, size_t count) {
-
-        assert(p);
-
-        if (p->kernel_fd >= 0) /* don't allow modification after we uploaded things to the kernel */
-                return -EBUSY;
-
-        if (!GREEDY_REALLOC(p->instructions, p->allocated, p->n_instructions + count))
-                return -ENOMEM;
-
-        memcpy(p->instructions + p->n_instructions, instructions, sizeof(struct bpf_insn) * count);
-        p->n_instructions += count;
-
-        return 0;
-}
-
-int bpf_program_load_kernel(BPFProgram *p, char *log_buf, size_t log_size) {
-        union bpf_attr attr;
-
-        assert(p);
-
-        if (p->kernel_fd >= 0) { /* make this idempotent */
-                memzero(log_buf, log_size);
-                return 0;
-        }
-
-        attr = (union bpf_attr) {
-                .prog_type = p->prog_type,
-                .insns = PTR_TO_UINT64(p->instructions),
-                .insn_cnt = p->n_instructions,
-                .license = PTR_TO_UINT64("GPL"),
-                .log_buf = PTR_TO_UINT64(log_buf),
-                .log_level = !!log_buf,
-                .log_size = log_size,
-        };
-
-        p->kernel_fd = bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
-        if (p->kernel_fd < 0)
-                return -errno;
-
-        return 0;
-}
-
-int bpf_program_cgroup_attach(BPFProgram *p, int type, const char *path, uint32_t flags) {
-        _cleanup_free_ char *copy = NULL;
-        _cleanup_close_ int fd = -1;
-        union bpf_attr attr;
-        int r;
-
-        assert(p);
-        assert(type >= 0);
-        assert(path);
-
-        if (!IN_SET(flags, 0, BPF_F_ALLOW_OVERRIDE, BPF_F_ALLOW_MULTI))
-                return -EINVAL;
-
-        /* We need to track which cgroup the program is attached to, and we can only track one attachment, hence let's
-        * refuse this early. */
-        if (p->attached_path) {
-                if (!path_equal(p->attached_path, path))
-                        return -EBUSY;
-                if (p->attached_type != type)
-                        return -EBUSY;
-                if (p->attached_flags != flags)
-                        return -EBUSY;
-
-                /* Here's a shortcut: if we previously attached this program already, then we don't have to do so
-                 * again. Well, with one exception: if we are in BPF_F_ALLOW_OVERRIDE mode then someone else might have
-                 * replaced our program since the last time, hence let's reattach it again, just to be safe. In flags
-                 * == 0 mode this is not an issue since nobody else can replace our program in that case, and in flags
-                 * == BPF_F_ALLOW_MULTI mode any other's program would be installed in addition to ours hence ours
-                 * would remain in effect. */
-                if (flags != BPF_F_ALLOW_OVERRIDE)
-                        return 0;
-        }
-
-        /* Ensure we have a kernel object for this. */
-        r = bpf_program_load_kernel(p, NULL, 0);
-        if (r < 0)
-                return r;
-
-        copy = strdup(path);
-        if (!copy)
-                return -ENOMEM;
-
-        fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
-        if (fd < 0)
-                return -errno;
-
-        attr = (union bpf_attr) {
-                .attach_type = type,
-                .target_fd = fd,
-                .attach_bpf_fd = p->kernel_fd,
-                .attach_flags = flags,
-        };
-
-        if (bpf(BPF_PROG_ATTACH, &attr, sizeof(attr)) < 0)
-                return -errno;
-
-        free_and_replace(p->attached_path, copy);
-        p->attached_type = type;
-        p->attached_flags = flags;
-
-        return 0;
-}
-
-int bpf_program_cgroup_detach(BPFProgram *p) {
-        _cleanup_close_ int fd = -1;
-
-        assert(p);
-
-        if (!p->attached_path)
-                return -EUNATCH;
-
-        fd = open(p->attached_path, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
-        if (fd < 0) {
-                if (errno != ENOENT)
-                        return -errno;
-
-                /* If the cgroup does not exist anymore, then we don't have to explicitly detach, it got detached
-                 * implicitly by the removal, hence don't complain */
-
-        } else {
-                union bpf_attr attr;
-
-                attr = (union bpf_attr) {
-                        .attach_type = p->attached_type,
-                        .target_fd = fd,
-                        .attach_bpf_fd = p->kernel_fd,
-                };
-
-                if (bpf(BPF_PROG_DETACH, &attr, sizeof(attr)) < 0)
-                        return -errno;
-        }
-
-        p->attached_path = mfree(p->attached_path);
-
-        return 0;
-}
-
-int bpf_map_new(enum bpf_map_type type, size_t key_size, size_t value_size, size_t max_entries, uint32_t flags) {
-        union bpf_attr attr = {
-                .map_type = type,
-                .key_size = key_size,
-                .value_size = value_size,
-                .max_entries = max_entries,
-                .map_flags = flags,
-        };
-        int fd;
-
-        fd = bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
-        if (fd < 0)
-                return -errno;
-
-        return fd;
-}
-
-int bpf_map_update_element(int fd, const void *key, void *value) {
-
-        union bpf_attr attr = {
-                .map_fd = fd,
-                .key = PTR_TO_UINT64(key),
-                .value = PTR_TO_UINT64(value),
-        };
-
-        if (bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)) < 0)
-                return -errno;
-
-        return 0;
-}
-
-int bpf_map_lookup_element(int fd, const void *key, void *value) {
-
-        union bpf_attr attr = {
-                .map_fd = fd,
-                .key = PTR_TO_UINT64(key),
-                .value = PTR_TO_UINT64(value),
-        };
-
-        if (bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)) < 0)
-                return -errno;
-
-        return 0;
-}
diff --git a/src/basic/bpf-program.h b/src/basic/bpf-program.h
deleted file mode 100644 (file)
index c21eb2f..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <linux/bpf.h>
-#include <stdint.h>
-#include <sys/syscall.h>
-
-#include "list.h"
-#include "macro.h"
-
-typedef struct BPFProgram BPFProgram;
-
-struct BPFProgram {
-        unsigned n_ref;
-
-        int kernel_fd;
-        uint32_t prog_type;
-
-        size_t n_instructions;
-        size_t allocated;
-        struct bpf_insn *instructions;
-
-        char *attached_path;
-        int attached_type;
-        uint32_t attached_flags;
-};
-
-int bpf_program_new(uint32_t prog_type, BPFProgram **ret);
-BPFProgram *bpf_program_unref(BPFProgram *p);
-BPFProgram *bpf_program_ref(BPFProgram *p);
-
-int bpf_program_add_instructions(BPFProgram *p, const struct bpf_insn *insn, size_t count);
-int bpf_program_load_kernel(BPFProgram *p, char *log_buf, size_t log_size);
-
-int bpf_program_cgroup_attach(BPFProgram *p, int type, const char *path, uint32_t flags);
-int bpf_program_cgroup_detach(BPFProgram *p);
-
-int bpf_map_new(enum bpf_map_type type, size_t key_size, size_t value_size, size_t max_entries, uint32_t flags);
-int bpf_map_update_element(int fd, const void *key, void *value);
-int bpf_map_lookup_element(int fd, const void *key, void *value);
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(BPFProgram*, bpf_program_unref);
diff --git a/src/basic/calendarspec.c b/src/basic/calendarspec.c
deleted file mode 100644 (file)
index dafc09e..0000000
+++ /dev/null
@@ -1,1370 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <alloca.h>
-#include <ctype.h>
-#include <errno.h>
-#include <limits.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdio_ext.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <time.h>
-
-#include "alloc-util.h"
-#include "calendarspec.h"
-#include "fileio.h"
-#include "macro.h"
-#include "parse-util.h"
-#include "process-util.h"
-#include "string-util.h"
-#include "time-util.h"
-
-#define BITS_WEEKDAYS 127
-#define MIN_YEAR 1970
-#define MAX_YEAR 2199
-
-/* An arbitrary limit on the length of the chains of components. We don't want to
- * build a very long linked list, which would be slow to iterate over and might cause
- * our stack to overflow. It's unlikely that legitimate uses require more than a few
- * linked compenents anyway. */
-#define CALENDARSPEC_COMPONENTS_MAX 240
-
-static void free_chain(CalendarComponent *c) {
-        CalendarComponent *n;
-
-        while (c) {
-                n = c->next;
-                free(c);
-                c = n;
-        }
-}
-
-CalendarSpec* calendar_spec_free(CalendarSpec *c) {
-
-        if (!c)
-                return NULL;
-
-        free_chain(c->year);
-        free_chain(c->month);
-        free_chain(c->day);
-        free_chain(c->hour);
-        free_chain(c->minute);
-        free_chain(c->microsecond);
-        free(c->timezone);
-
-        return mfree(c);
-}
-
-static int component_compare(CalendarComponent * const *a, CalendarComponent * const *b) {
-        int r;
-
-        r = CMP((*a)->start, (*b)->start);
-        if (r != 0)
-                return r;
-
-        r = CMP((*a)->stop, (*b)->stop);
-        if (r != 0)
-                return r;
-
-        return CMP((*a)->repeat, (*b)->repeat);
-}
-
-static void normalize_chain(CalendarComponent **c) {
-        CalendarComponent **b, *i, **j, *next;
-        size_t n = 0, k;
-
-        assert(c);
-
-        for (i = *c; i; i = i->next) {
-                n++;
-
-                /*
-                 * While we're counting the chain, also normalize `stop`
-                 * so the length of the range is a multiple of `repeat`
-                 */
-                if (i->stop > i->start && i->repeat > 0)
-                        i->stop -= (i->stop - i->start) % i->repeat;
-
-        }
-
-        if (n <= 1)
-                return;
-
-        j = b = newa(CalendarComponent*, n);
-        for (i = *c; i; i = i->next)
-                *(j++) = i;
-
-        typesafe_qsort(b, n, component_compare);
-
-        b[n-1]->next = NULL;
-        next = b[n-1];
-
-        /* Drop non-unique entries */
-        for (k = n-1; k > 0; k--) {
-                if (component_compare(&b[k-1], &next) == 0) {
-                        free(b[k-1]);
-                        continue;
-                }
-
-                b[k-1]->next = next;
-                next = b[k-1];
-        }
-
-        *c = next;
-}
-
-static void fix_year(CalendarComponent *c) {
-        /* Turns 12 → 2012, 89 → 1989 */
-
-        while (c) {
-                if (c->start >= 0 && c->start < 70)
-                        c->start += 2000;
-
-                if (c->stop >= 0 && c->stop < 70)
-                        c->stop += 2000;
-
-                if (c->start >= 70 && c->start < 100)
-                        c->start += 1900;
-
-                if (c->stop >= 70 && c->stop < 100)
-                        c->stop += 1900;
-
-                c = c->next;
-        }
-}
-
-int calendar_spec_normalize(CalendarSpec *c) {
-        assert(c);
-
-        if (streq_ptr(c->timezone, "UTC")) {
-                c->utc = true;
-                c->timezone = mfree(c->timezone);
-        }
-
-        if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS)
-                c->weekdays_bits = -1;
-
-        if (c->end_of_month && !c->day)
-                c->end_of_month = false;
-
-        fix_year(c->year);
-
-        normalize_chain(&c->year);
-        normalize_chain(&c->month);
-        normalize_chain(&c->day);
-        normalize_chain(&c->hour);
-        normalize_chain(&c->minute);
-        normalize_chain(&c->microsecond);
-
-        return 0;
-}
-
-_pure_ static bool chain_valid(CalendarComponent *c, int from, int to, bool end_of_month) {
-        assert(to >= from);
-
-        if (!c)
-                return true;
-
-        /* Forbid dates more than 28 days from the end of the month */
-        if (end_of_month)
-                to -= 3;
-
-        if (c->start < from || c->start > to)
-                return false;
-
-        /* Avoid overly large values that could cause overflow */
-        if (c->repeat > to - from)
-                return false;
-
-        /*
-         * c->repeat must be short enough so at least one repetition may
-         * occur before the end of the interval.  For dates scheduled
-         * relative to the end of the month, c->start and c->stop
-         * correspond to the Nth last day of the month.
-         */
-        if (c->stop >= 0) {
-                if (c->stop < from || c ->stop > to)
-                        return false;
-
-                if (c->start + c->repeat > c->stop)
-                        return false;
-        } else {
-                if (end_of_month && c->start - c->repeat < from)
-                        return false;
-
-                if (!end_of_month && c->start + c->repeat > to)
-                        return false;
-        }
-
-        if (c->next)
-                return chain_valid(c->next, from, to, end_of_month);
-
-        return true;
-}
-
-_pure_ bool calendar_spec_valid(CalendarSpec *c) {
-        assert(c);
-
-        if (c->weekdays_bits > BITS_WEEKDAYS)
-                return false;
-
-        if (!chain_valid(c->year, MIN_YEAR, MAX_YEAR, false))
-                return false;
-
-        if (!chain_valid(c->month, 1, 12, false))
-                return false;
-
-        if (!chain_valid(c->day, 1, 31, c->end_of_month))
-                return false;
-
-        if (!chain_valid(c->hour, 0, 23, false))
-                return false;
-
-        if (!chain_valid(c->minute, 0, 59, false))
-                return false;
-
-        if (!chain_valid(c->microsecond, 0, 60*USEC_PER_SEC-1, false))
-                return false;
-
-        return true;
-}
-
-static void format_weekdays(FILE *f, const CalendarSpec *c) {
-        static const char *const days[] = {
-                "Mon",
-                "Tue",
-                "Wed",
-                "Thu",
-                "Fri",
-                "Sat",
-                "Sun"
-        };
-
-        int l, x;
-        bool need_comma = false;
-
-        assert(f);
-        assert(c);
-        assert(c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS);
-
-        for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) {
-
-                if (c->weekdays_bits & (1 << x)) {
-
-                        if (l < 0) {
-                                if (need_comma)
-                                        fputc(',', f);
-                                else
-                                        need_comma = true;
-
-                                fputs(days[x], f);
-                                l = x;
-                        }
-
-                } else if (l >= 0) {
-
-                        if (x > l + 1) {
-                                fputs(x > l + 2 ? ".." : ",", f);
-                                fputs(days[x-1], f);
-                        }
-
-                        l = -1;
-                }
-        }
-
-        if (l >= 0 && x > l + 1) {
-                fputs(x > l + 2 ? ".." : ",", f);
-                fputs(days[x-1], f);
-        }
-}
-
-static void format_chain(FILE *f, int space, const CalendarComponent *c, bool usec) {
-        int d = usec ? (int) USEC_PER_SEC : 1;
-
-        assert(f);
-
-        if (!c) {
-                fputc('*', f);
-                return;
-        }
-
-        if (usec && c->start == 0 && c->repeat == USEC_PER_SEC && !c->next) {
-                fputc('*', f);
-                return;
-        }
-
-        assert(c->start >= 0);
-
-        fprintf(f, "%0*i", space, c->start / d);
-        if (c->start % d > 0)
-                fprintf(f, ".%06i", c->start % d);
-
-        if (c->stop > 0)
-                fprintf(f, "..%0*i", space, c->stop / d);
-        if (c->stop % d > 0)
-                fprintf(f, ".%06i", c->stop % d);
-
-        if (c->repeat > 0 && !(c->stop > 0 && c->repeat == d))
-                fprintf(f, "/%i", c->repeat / d);
-        if (c->repeat % d > 0)
-                fprintf(f, ".%06i", c->repeat % d);
-
-        if (c->next) {
-                fputc(',', f);
-                format_chain(f, space, c->next, usec);
-        }
-}
-
-int calendar_spec_to_string(const CalendarSpec *c, char **p) {
-        char *buf = NULL;
-        size_t sz = 0;
-        FILE *f;
-        int r;
-
-        assert(c);
-        assert(p);
-
-        f = open_memstream(&buf, &sz);
-        if (!f)
-                return -ENOMEM;
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
-        if (c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS) {
-                format_weekdays(f, c);
-                fputc(' ', f);
-        }
-
-        format_chain(f, 4, c->year, false);
-        fputc('-', f);
-        format_chain(f, 2, c->month, false);
-        fputc(c->end_of_month ? '~' : '-', f);
-        format_chain(f, 2, c->day, false);
-        fputc(' ', f);
-        format_chain(f, 2, c->hour, false);
-        fputc(':', f);
-        format_chain(f, 2, c->minute, false);
-        fputc(':', f);
-        format_chain(f, 2, c->microsecond, true);
-
-        if (c->utc)
-                fputs(" UTC", f);
-        else if (c->timezone != NULL) {
-                fputc(' ', f);
-                fputs(c->timezone, f);
-        } else if (IN_SET(c->dst, 0, 1)) {
-
-                /* If daylight saving is explicitly on or off, let's show the used timezone. */
-
-                tzset();
-
-                if (!isempty(tzname[c->dst])) {
-                        fputc(' ', f);
-                        fputs(tzname[c->dst], f);
-                }
-        }
-
-        r = fflush_and_check(f);
-        if (r < 0) {
-                free(buf);
-                fclose(f);
-                return r;
-        }
-
-        fclose(f);
-
-        *p = buf;
-        return 0;
-}
-
-static int parse_weekdays(const char **p, CalendarSpec *c) {
-        static const struct {
-                const char *name;
-                const int nr;
-        } day_nr[] = {
-                { "Monday",    0 },
-                { "Mon",       0 },
-                { "Tuesday",   1 },
-                { "Tue",       1 },
-                { "Wednesday", 2 },
-                { "Wed",       2 },
-                { "Thursday",  3 },
-                { "Thu",       3 },
-                { "Friday",    4 },
-                { "Fri",       4 },
-                { "Saturday",  5 },
-                { "Sat",       5 },
-                { "Sunday",    6 },
-                { "Sun",       6 }
-        };
-
-        int l = -1;
-        bool first = true;
-
-        assert(p);
-        assert(*p);
-        assert(c);
-
-        for (;;) {
-                size_t i;
-
-                for (i = 0; i < ELEMENTSOF(day_nr); i++) {
-                        size_t skip;
-
-                        if (!startswith_no_case(*p, day_nr[i].name))
-                                continue;
-
-                        skip = strlen(day_nr[i].name);
-
-                        if (!IN_SET((*p)[skip], 0, '-', '.', ',', ' '))
-                                return -EINVAL;
-
-                        c->weekdays_bits |= 1 << day_nr[i].nr;
-
-                        if (l >= 0) {
-                                int j;
-
-                                if (l > day_nr[i].nr)
-                                        return -EINVAL;
-
-                                for (j = l + 1; j < day_nr[i].nr; j++)
-                                        c->weekdays_bits |= 1 << j;
-                        }
-
-                        *p += skip;
-                        break;
-                }
-
-                /* Couldn't find this prefix, so let's assume the
-                   weekday was not specified and let's continue with
-                   the date */
-                if (i >= ELEMENTSOF(day_nr))
-                        return first ? 0 : -EINVAL;
-
-                /* We reached the end of the string */
-                if (**p == 0)
-                        return 0;
-
-                /* We reached the end of the weekday spec part */
-                if (**p == ' ') {
-                        *p += strspn(*p, " ");
-                        return 0;
-                }
-
-                if (**p == '.') {
-                        if (l >= 0)
-                                return -EINVAL;
-
-                        if ((*p)[1] != '.')
-                                return -EINVAL;
-
-                        l = day_nr[i].nr;
-                        *p += 2;
-
-                /* Support ranges with "-" for backwards compatibility */
-                } else if (**p == '-') {
-                        if (l >= 0)
-                                return -EINVAL;
-
-                        l = day_nr[i].nr;
-                        *p += 1;
-
-                } else if (**p == ',') {
-                        l = -1;
-                        *p += 1;
-                }
-
-                /* Allow a trailing comma but not an open range */
-                if (IN_SET(**p, 0, ' ')) {
-                        *p += strspn(*p, " ");
-                        return l < 0 ? 0 : -EINVAL;
-                }
-
-                first = false;
-        }
-}
-
-static int parse_one_number(const char *p, const char **e, unsigned long *ret) {
-        char *ee = NULL;
-        unsigned long value;
-
-        errno = 0;
-        value = strtoul(p, &ee, 10);
-        if (errno > 0)
-                return -errno;
-        if (ee == p)
-                return -EINVAL;
-
-        *ret = value;
-        *e = ee;
-        return 0;
-}
-
-static int parse_component_decimal(const char **p, bool usec, int *res) {
-        unsigned long value;
-        const char *e = NULL;
-        int r;
-
-        if (!isdigit(**p))
-                return -EINVAL;
-
-        r = parse_one_number(*p, &e, &value);
-        if (r < 0)
-                return r;
-
-        if (usec) {
-                if (value * USEC_PER_SEC / USEC_PER_SEC != value)
-                        return -ERANGE;
-
-                value *= USEC_PER_SEC;
-
-                /* One "." is a decimal point, but ".." is a range separator */
-                if (e[0] == '.' && e[1] != '.') {
-                        unsigned add;
-
-                        e++;
-                        r = parse_fractional_part_u(&e, 6, &add);
-                        if (r < 0)
-                                return r;
-
-                        if (add + value < value)
-                                return -ERANGE;
-                        value += add;
-                }
-        }
-
-        if (value > INT_MAX)
-                return -ERANGE;
-
-        *p = e;
-        *res = value;
-
-        return 0;
-}
-
-static int const_chain(int value, CalendarComponent **c) {
-        CalendarComponent *cc = NULL;
-
-        assert(c);
-
-        cc = new0(CalendarComponent, 1);
-        if (!cc)
-                return -ENOMEM;
-
-        cc->start = value;
-        cc->stop = -1;
-        cc->repeat = 0;
-        cc->next = *c;
-
-        *c = cc;
-
-        return 0;
-}
-
-static int calendarspec_from_time_t(CalendarSpec *c, time_t time) {
-        struct tm tm;
-        CalendarComponent *year = NULL, *month = NULL, *day = NULL, *hour = NULL, *minute = NULL, *us = NULL;
-        int r;
-
-        if (!gmtime_r(&time, &tm))
-                return -ERANGE;
-
-        r = const_chain(tm.tm_year + 1900, &year);
-        if (r < 0)
-                return r;
-
-        r = const_chain(tm.tm_mon + 1, &month);
-        if (r < 0)
-                return r;
-
-        r = const_chain(tm.tm_mday, &day);
-        if (r < 0)
-                return r;
-
-        r = const_chain(tm.tm_hour, &hour);
-        if (r < 0)
-                return r;
-
-        r = const_chain(tm.tm_min, &minute);
-        if (r < 0)
-                return r;
-
-        r = const_chain(tm.tm_sec * USEC_PER_SEC, &us);
-        if (r < 0)
-                return r;
-
-        c->utc = true;
-        c->year = year;
-        c->month = month;
-        c->day = day;
-        c->hour = hour;
-        c->minute = minute;
-        c->microsecond = us;
-        return 0;
-}
-
-static int prepend_component(const char **p, bool usec, unsigned nesting, CalendarComponent **c) {
-        int r, start, stop = -1, repeat = 0;
-        CalendarComponent *cc;
-        const char *e = *p;
-
-        assert(p);
-        assert(c);
-
-        if (nesting > CALENDARSPEC_COMPONENTS_MAX)
-                return -ENOBUFS;
-
-        r = parse_component_decimal(&e, usec, &start);
-        if (r < 0)
-                return r;
-
-        if (e[0] == '.' && e[1] == '.') {
-                e += 2;
-                r = parse_component_decimal(&e, usec, &stop);
-                if (r < 0)
-                        return r;
-
-                repeat = usec ? USEC_PER_SEC : 1;
-        }
-
-        if (*e == '/') {
-                e++;
-                r = parse_component_decimal(&e, usec, &repeat);
-                if (r < 0)
-                        return r;
-
-                if (repeat == 0)
-                        return -ERANGE;
-        }
-
-        if (!IN_SET(*e, 0, ' ', ',', '-', '~', ':'))
-                return -EINVAL;
-
-        cc = new0(CalendarComponent, 1);
-        if (!cc)
-                return -ENOMEM;
-
-        cc->start = start;
-        cc->stop = stop;
-        cc->repeat = repeat;
-        cc->next = *c;
-
-        *p = e;
-        *c = cc;
-
-        if (*e ==',') {
-                *p += 1;
-                return prepend_component(p, usec, nesting + 1, c);
-        }
-
-        return 0;
-}
-
-static int parse_chain(const char **p, bool usec, CalendarComponent **c) {
-        const char *t;
-        CalendarComponent *cc = NULL;
-        int r;
-
-        assert(p);
-        assert(c);
-
-        t = *p;
-
-        if (t[0] == '*') {
-                if (usec) {
-                        r = const_chain(0, c);
-                        if (r < 0)
-                                return r;
-                        (*c)->repeat = USEC_PER_SEC;
-                } else
-                        *c = NULL;
-
-                *p = t + 1;
-                return 0;
-        }
-
-        r = prepend_component(&t, usec, 0, &cc);
-        if (r < 0) {
-                free_chain(cc);
-                return r;
-        }
-
-        *p = t;
-        *c = cc;
-        return 0;
-}
-
-static int parse_date(const char **p, CalendarSpec *c) {
-        const char *t;
-        int r;
-        CalendarComponent *first, *second, *third;
-
-        assert(p);
-        assert(*p);
-        assert(c);
-
-        t = *p;
-
-        if (*t == 0)
-                return 0;
-
-        /* @TIMESTAMP — UNIX time in seconds since the epoch */
-        if (*t == '@') {
-                unsigned long value;
-                time_t time;
-
-                r = parse_one_number(t + 1, &t, &value);
-                if (r < 0)
-                        return r;
-
-                time = value;
-                if ((unsigned long) time != value)
-                        return -ERANGE;
-
-                r = calendarspec_from_time_t(c, time);
-                if (r < 0)
-                        return r;
-
-                *p = t;
-                return 1; /* finito, don't parse H:M:S after that */
-        }
-
-        r = parse_chain(&t, false, &first);
-        if (r < 0)
-                return r;
-
-        /* Already the end? A ':' as separator? In that case this was a time, not a date */
-        if (IN_SET(*t, 0, ':')) {
-                free_chain(first);
-                return 0;
-        }
-
-        if (*t == '~')
-                c->end_of_month = true;
-        else if (*t != '-') {
-                free_chain(first);
-                return -EINVAL;
-        }
-
-        t++;
-        r = parse_chain(&t, false, &second);
-        if (r < 0) {
-                free_chain(first);
-                return r;
-        }
-
-        /* Got two parts, hence it's month and day */
-        if (IN_SET(*t, 0, ' ')) {
-                *p = t + strspn(t, " ");
-                c->month = first;
-                c->day = second;
-                return 0;
-        } else if (c->end_of_month) {
-                free_chain(first);
-                free_chain(second);
-                return -EINVAL;
-        }
-
-        if (*t == '~')
-                c->end_of_month = true;
-        else if (*t != '-') {
-                free_chain(first);
-                free_chain(second);
-                return -EINVAL;
-        }
-
-        t++;
-        r = parse_chain(&t, false, &third);
-        if (r < 0) {
-                free_chain(first);
-                free_chain(second);
-                return r;
-        }
-
-        /* Got three parts, hence it is year, month and day */
-        if (IN_SET(*t, 0, ' ')) {
-                *p = t + strspn(t, " ");
-                c->year = first;
-                c->month = second;
-                c->day = third;
-                return 0;
-        }
-
-        free_chain(first);
-        free_chain(second);
-        free_chain(third);
-        return -EINVAL;
-}
-
-static int parse_calendar_time(const char **p, CalendarSpec *c) {
-        CalendarComponent *h = NULL, *m = NULL, *s = NULL;
-        const char *t;
-        int r;
-
-        assert(p);
-        assert(*p);
-        assert(c);
-
-        t = *p;
-
-        /* If no time is specified at all, then this means 00:00:00 */
-        if (*t == 0)
-                goto null_hour;
-
-        r = parse_chain(&t, false, &h);
-        if (r < 0)
-                goto fail;
-
-        if (*t != ':') {
-                r = -EINVAL;
-                goto fail;
-        }
-
-        t++;
-        r = parse_chain(&t, false, &m);
-        if (r < 0)
-                goto fail;
-
-        /* Already at the end? Then it's hours and minutes, and seconds are 0 */
-        if (*t == 0)
-                goto null_second;
-
-        if (*t != ':') {
-                r = -EINVAL;
-                goto fail;
-        }
-
-        t++;
-        r = parse_chain(&t, true, &s);
-        if (r < 0)
-                goto fail;
-
-        /* At the end? Then it's hours, minutes and seconds */
-        if (*t == 0)
-                goto finish;
-
-        r = -EINVAL;
-        goto fail;
-
-null_hour:
-        r = const_chain(0, &h);
-        if (r < 0)
-                goto fail;
-
-        r = const_chain(0, &m);
-        if (r < 0)
-                goto fail;
-
-null_second:
-        r = const_chain(0, &s);
-        if (r < 0)
-                goto fail;
-
-finish:
-        *p = t;
-        c->hour = h;
-        c->minute = m;
-        c->microsecond = s;
-
-        return 0;
-
-fail:
-        free_chain(h);
-        free_chain(m);
-        free_chain(s);
-        return r;
-}
-
-int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
-        const char *utc;
-        _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
-        int r;
-
-        assert(p);
-        assert(spec);
-
-        c = new0(CalendarSpec, 1);
-        if (!c)
-                return -ENOMEM;
-        c->dst = -1;
-        c->timezone = NULL;
-
-        utc = endswith_no_case(p, " UTC");
-        if (utc) {
-                c->utc = true;
-                p = strndupa(p, utc - p);
-        } else {
-                const char *e = NULL;
-                int j;
-
-                tzset();
-
-                /* Check if the local timezone was specified? */
-                for (j = 0; j <= 1; j++) {
-                        if (isempty(tzname[j]))
-                                continue;
-
-                        e = endswith_no_case(p, tzname[j]);
-                        if (!e)
-                                continue;
-                        if (e == p)
-                                continue;
-                        if (e[-1] != ' ')
-                                continue;
-
-                        break;
-                }
-
-                /* Found one of the two timezones specified? */
-                if (IN_SET(j, 0, 1)) {
-                        p = strndupa(p, e - p - 1);
-                        c->dst = j;
-                } else {
-                        const char *last_space;
-
-                        last_space = strrchr(p, ' ');
-                        if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG)) {
-                                c->timezone = strdup(last_space + 1);
-                                if (!c->timezone)
-                                        return -ENOMEM;
-
-                                p = strndupa(p, last_space - p);
-                        }
-                }
-        }
-
-        if (isempty(p))
-                return -EINVAL;
-
-        if (strcaseeq(p, "minutely")) {
-                r = const_chain(0, &c->microsecond);
-                if (r < 0)
-                        return r;
-
-        } else if (strcaseeq(p, "hourly")) {
-                r = const_chain(0, &c->minute);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->microsecond);
-                if (r < 0)
-                        return r;
-
-        } else if (strcaseeq(p, "daily")) {
-                r = const_chain(0, &c->hour);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->minute);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->microsecond);
-                if (r < 0)
-                        return r;
-
-        } else if (strcaseeq(p, "monthly")) {
-                r = const_chain(1, &c->day);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->hour);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->minute);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->microsecond);
-                if (r < 0)
-                        return r;
-
-        } else if (strcaseeq(p, "annually") ||
-                   strcaseeq(p, "yearly") ||
-                   strcaseeq(p, "anually") /* backwards compatibility */ ) {
-
-                r = const_chain(1, &c->month);
-                if (r < 0)
-                        return r;
-                r = const_chain(1, &c->day);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->hour);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->minute);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->microsecond);
-                if (r < 0)
-                        return r;
-
-        } else if (strcaseeq(p, "weekly")) {
-
-                c->weekdays_bits = 1;
-
-                r = const_chain(0, &c->hour);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->minute);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->microsecond);
-                if (r < 0)
-                        return r;
-
-        } else if (strcaseeq(p, "quarterly")) {
-
-                r = const_chain(1, &c->month);
-                if (r < 0)
-                        return r;
-                r = const_chain(4, &c->month);
-                if (r < 0)
-                        return r;
-                r = const_chain(7, &c->month);
-                if (r < 0)
-                        return r;
-                r = const_chain(10, &c->month);
-                if (r < 0)
-                        return r;
-                r = const_chain(1, &c->day);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->hour);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->minute);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->microsecond);
-                if (r < 0)
-                        return r;
-
-        } else if (strcaseeq(p, "biannually") ||
-                   strcaseeq(p, "bi-annually") ||
-                   strcaseeq(p, "semiannually") ||
-                   strcaseeq(p, "semi-annually")) {
-
-                r = const_chain(1, &c->month);
-                if (r < 0)
-                        return r;
-                r = const_chain(7, &c->month);
-                if (r < 0)
-                        return r;
-                r = const_chain(1, &c->day);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->hour);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->minute);
-                if (r < 0)
-                        return r;
-                r = const_chain(0, &c->microsecond);
-                if (r < 0)
-                        return r;
-
-        } else {
-                r = parse_weekdays(&p, c);
-                if (r < 0)
-                        return r;
-
-                r = parse_date(&p, c);
-                if (r < 0)
-                        return r;
-
-                if (r == 0) {
-                        r = parse_calendar_time(&p, c);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (*p != 0)
-                        return -EINVAL;
-        }
-
-        r = calendar_spec_normalize(c);
-        if (r < 0)
-                return r;
-
-        if (!calendar_spec_valid(c))
-                return -EINVAL;
-
-        *spec = TAKE_PTR(c);
-        return 0;
-}
-
-static int find_end_of_month(struct tm *tm, bool utc, int day) {
-        struct tm t = *tm;
-
-        t.tm_mon++;
-        t.tm_mday = 1 - day;
-
-        if (mktime_or_timegm(&t, utc) < 0 ||
-            t.tm_mon != tm->tm_mon)
-                return -1;
-
-        return t.tm_mday;
-}
-
-static int find_matching_component(const CalendarSpec *spec, const CalendarComponent *c,
-                                   struct tm *tm, int *val) {
-        const CalendarComponent *p = c;
-        int start, stop, d = -1;
-        bool d_set = false;
-        int r;
-
-        assert(val);
-
-        if (!c)
-                return 0;
-
-        while (c) {
-                start = c->start;
-                stop = c->stop;
-
-                if (spec->end_of_month && p == spec->day) {
-                        start = find_end_of_month(tm, spec->utc, start);
-                        stop = find_end_of_month(tm, spec->utc, stop);
-
-                        if (stop > 0)
-                                SWAP_TWO(start, stop);
-                }
-
-                if (start >= *val) {
-
-                        if (!d_set || start < d) {
-                                d = start;
-                                d_set = true;
-                        }
-
-                } else if (c->repeat > 0) {
-                        int k;
-
-                        k = start + c->repeat * DIV_ROUND_UP(*val - start, c->repeat);
-
-                        if ((!d_set || k < d) && (stop < 0 || k <= stop)) {
-                                d = k;
-                                d_set = true;
-                        }
-                }
-
-                c = c->next;
-        }
-
-        if (!d_set)
-                return -ENOENT;
-
-        r = *val != d;
-        *val = d;
-        return r;
-}
-
-static bool tm_out_of_bounds(const struct tm *tm, bool utc) {
-        struct tm t;
-        assert(tm);
-
-        t = *tm;
-
-        if (mktime_or_timegm(&t, utc) < 0)
-                return true;
-
-        /*
-         * Set an upper bound on the year so impossible dates like "*-02-31"
-         * don't cause find_next() to loop forever. tm_year contains years
-         * since 1900, so adjust it accordingly.
-         */
-        if (tm->tm_year + 1900 > MAX_YEAR)
-                return true;
-
-        /* Did any normalization take place? If so, it was out of bounds before */
-        return
-                t.tm_year != tm->tm_year ||
-                t.tm_mon != tm->tm_mon ||
-                t.tm_mday != tm->tm_mday ||
-                t.tm_hour != tm->tm_hour ||
-                t.tm_min != tm->tm_min ||
-                t.tm_sec != tm->tm_sec;
-}
-
-static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) {
-        struct tm t;
-        int k;
-
-        if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS)
-                return true;
-
-        t = *tm;
-        if (mktime_or_timegm(&t, utc) < 0)
-                return false;
-
-        k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
-        return (weekdays_bits & (1 << k));
-}
-
-static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
-        struct tm c;
-        int tm_usec;
-        int r;
-
-        assert(spec);
-        assert(tm);
-
-        c = *tm;
-        tm_usec = *usec;
-
-        for (;;) {
-                /* Normalize the current date */
-                (void) mktime_or_timegm(&c, spec->utc);
-                c.tm_isdst = spec->dst;
-
-                c.tm_year += 1900;
-                r = find_matching_component(spec, spec->year, &c, &c.tm_year);
-                c.tm_year -= 1900;
-
-                if (r > 0) {
-                        c.tm_mon = 0;
-                        c.tm_mday = 1;
-                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
-                }
-                if (r < 0)
-                        return r;
-                if (tm_out_of_bounds(&c, spec->utc))
-                        return -ENOENT;
-
-                c.tm_mon += 1;
-                r = find_matching_component(spec, spec->month, &c, &c.tm_mon);
-                c.tm_mon -= 1;
-
-                if (r > 0) {
-                        c.tm_mday = 1;
-                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
-                }
-                if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
-                        c.tm_year++;
-                        c.tm_mon = 0;
-                        c.tm_mday = 1;
-                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
-                        continue;
-                }
-
-                r = find_matching_component(spec, spec->day, &c, &c.tm_mday);
-                if (r > 0)
-                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
-                if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
-                        c.tm_mon++;
-                        c.tm_mday = 1;
-                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
-                        continue;
-                }
-
-                if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) {
-                        c.tm_mday++;
-                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
-                        continue;
-                }
-
-                r = find_matching_component(spec, spec->hour, &c, &c.tm_hour);
-                if (r > 0)
-                        c.tm_min = c.tm_sec = tm_usec = 0;
-                if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
-                        c.tm_mday++;
-                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
-                        continue;
-                }
-
-                r = find_matching_component(spec, spec->minute, &c, &c.tm_min);
-                if (r > 0)
-                        c.tm_sec = tm_usec = 0;
-                if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
-                        c.tm_hour++;
-                        c.tm_min = c.tm_sec = tm_usec = 0;
-                        continue;
-                }
-
-                c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec;
-                r = find_matching_component(spec, spec->microsecond, &c, &c.tm_sec);
-                tm_usec = c.tm_sec % USEC_PER_SEC;
-                c.tm_sec /= USEC_PER_SEC;
-
-                if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
-                        c.tm_min++;
-                        c.tm_sec = tm_usec = 0;
-                        continue;
-                }
-
-                *tm = c;
-                *usec = tm_usec;
-                return 0;
-        }
-}
-
-static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, usec_t *next) {
-        struct tm tm;
-        time_t t;
-        int r;
-        usec_t tm_usec;
-
-        assert(spec);
-        assert(next);
-
-        if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
-                return -EINVAL;
-
-        usec++;
-        t = (time_t) (usec / USEC_PER_SEC);
-        assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc));
-        tm_usec = usec % USEC_PER_SEC;
-
-        r = find_next(spec, &tm, &tm_usec);
-        if (r < 0)
-                return r;
-
-        t = mktime_or_timegm(&tm, spec->utc);
-        if (t < 0)
-                return -EINVAL;
-
-        *next = (usec_t) t * USEC_PER_SEC + tm_usec;
-        return 0;
-}
-
-typedef struct SpecNextResult {
-        usec_t next;
-        int return_value;
-} SpecNextResult;
-
-int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
-        SpecNextResult *shared, tmp;
-        int r;
-
-        if (isempty(spec->timezone))
-                return calendar_spec_next_usec_impl(spec, usec, next);
-
-        shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
-        if (shared == MAP_FAILED)
-                return negative_errno();
-
-        r = safe_fork("(sd-calendar)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT, NULL);
-        if (r < 0) {
-                (void) munmap(shared, sizeof *shared);
-                return r;
-        }
-        if (r == 0) {
-                if (setenv("TZ", spec->timezone, 1) != 0) {
-                        shared->return_value = negative_errno();
-                        _exit(EXIT_FAILURE);
-                }
-
-                tzset();
-
-                shared->return_value = calendar_spec_next_usec_impl(spec, usec, &shared->next);
-
-                _exit(EXIT_SUCCESS);
-        }
-
-        tmp = *shared;
-        if (munmap(shared, sizeof *shared) < 0)
-                return negative_errno();
-
-        if (tmp.return_value == 0)
-                *next = tmp.next;
-
-        return tmp.return_value;
-}
diff --git a/src/basic/calendarspec.h b/src/basic/calendarspec.h
deleted file mode 100644 (file)
index 3bf8a39..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-/* A structure for specifying (possibly repetitive) points in calendar
- * time, a la cron */
-
-#include <stdbool.h>
-
-#include "time-util.h"
-#include "util.h"
-
-typedef struct CalendarComponent {
-        int start;
-        int stop;
-        int repeat;
-
-        struct CalendarComponent *next;
-} CalendarComponent;
-
-typedef struct CalendarSpec {
-        int weekdays_bits;
-        bool end_of_month;
-        bool utc;
-        int dst;
-        char *timezone;
-
-        CalendarComponent *year;
-        CalendarComponent *month;
-        CalendarComponent *day;
-
-        CalendarComponent *hour;
-        CalendarComponent *minute;
-        CalendarComponent *microsecond;
-} CalendarSpec;
-
-CalendarSpec* calendar_spec_free(CalendarSpec *c);
-
-int calendar_spec_normalize(CalendarSpec *spec);
-bool calendar_spec_valid(CalendarSpec *spec);
-
-int calendar_spec_to_string(const CalendarSpec *spec, char **p);
-int calendar_spec_from_string(const char *p, CalendarSpec **spec);
-
-int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next);
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(CalendarSpec*, calendar_spec_free);
diff --git a/src/basic/clock-util.c b/src/basic/clock-util.c
deleted file mode 100644 (file)
index 1877a81..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <stdbool.h>
-#include <time.h>
-#include <linux/rtc.h>
-#include <stdio.h>
-#include <sys/ioctl.h>
-#include <sys/time.h>
-
-#include "alloc-util.h"
-#include "clock-util.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "macro.h"
-#include "string-util.h"
-#include "util.h"
-
-int clock_get_hwclock(struct tm *tm) {
-        _cleanup_close_ int fd = -1;
-
-        assert(tm);
-
-        fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC);
-        if (fd < 0)
-                return -errno;
-
-        /* This leaves the timezone fields of struct tm
-         * uninitialized! */
-        if (ioctl(fd, RTC_RD_TIME, tm) < 0)
-                return -errno;
-
-        /* We don't know daylight saving, so we reset this in order not
-         * to confuse mktime(). */
-        tm->tm_isdst = -1;
-
-        return 0;
-}
-
-int clock_set_hwclock(const struct tm *tm) {
-        _cleanup_close_ int fd = -1;
-
-        assert(tm);
-
-        fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC);
-        if (fd < 0)
-                return -errno;
-
-        if (ioctl(fd, RTC_SET_TIME, tm) < 0)
-                return -errno;
-
-        return 0;
-}
-
-int clock_is_localtime(const char* adjtime_path) {
-        _cleanup_fclose_ FILE *f;
-        int r;
-
-        if (!adjtime_path)
-                adjtime_path = "/etc/adjtime";
-
-        /*
-         * The third line of adjtime is "UTC" or "LOCAL" or nothing.
-         *   # /etc/adjtime
-         *   0.0 0 0
-         *   0
-         *   UTC
-         */
-        f = fopen(adjtime_path, "re");
-        if (f) {
-                _cleanup_free_ char *line = NULL;
-                unsigned i;
-
-                for (i = 0; i < 2; i++) { /* skip the first two lines */
-                        r = read_line(f, LONG_LINE_MAX, NULL);
-                        if (r < 0)
-                                return r;
-                        if (r == 0)
-                                return false; /* less than three lines → default to UTC */
-                }
-
-                r = read_line(f, LONG_LINE_MAX, &line);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        return false; /* less than three lines → default to UTC */
-
-                return streq(line, "LOCAL");
-
-        } else if (errno != ENOENT)
-                return -errno;
-
-        /* adjtime not present → default to UTC */
-        return false;
-}
-
-int clock_set_timezone(int *min) {
-        const struct timeval *tv_null = NULL;
-        struct timespec ts;
-        struct tm tm;
-        int minutesdelta;
-        struct timezone tz;
-
-        assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
-        assert_se(localtime_r(&ts.tv_sec, &tm));
-        minutesdelta = tm.tm_gmtoff / 60;
-
-        tz.tz_minuteswest = -minutesdelta;
-        tz.tz_dsttime = 0; /* DST_NONE */
-
-        /*
-         * If the RTC does not run in UTC but in local time, the very first
-         * call to settimeofday() will set the kernel's timezone and will warp the
-         * system clock, so that it runs in UTC instead of the local time we
-         * have read from the RTC.
-         */
-        if (settimeofday(tv_null, &tz) < 0)
-                return negative_errno();
-
-        if (min)
-                *min = minutesdelta;
-        return 0;
-}
-
-int clock_reset_timewarp(void) {
-        const struct timeval *tv_null = NULL;
-        struct timezone tz;
-
-        tz.tz_minuteswest = 0;
-        tz.tz_dsttime = 0; /* DST_NONE */
-
-        /*
-         * The very first call to settimeofday() does time warp magic. Do a
-         * dummy call here, so the time warping is sealed and all later calls
-         * behave as expected.
-         */
-        if (settimeofday(tv_null, &tz) < 0)
-                return -errno;
-
-        return 0;
-}
-
-#define TIME_EPOCH_USEC ((usec_t) TIME_EPOCH * USEC_PER_SEC)
-
-int clock_apply_epoch(void) {
-        struct timespec ts;
-
-        if (now(CLOCK_REALTIME) >= TIME_EPOCH_USEC)
-                return 0;
-
-        if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, TIME_EPOCH_USEC)) < 0)
-                return -errno;
-
-        return 1;
-}
diff --git a/src/basic/clock-util.h b/src/basic/clock-util.h
deleted file mode 100644 (file)
index b9db54e..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <time.h>
-
-int clock_is_localtime(const char* adjtime_path);
-int clock_set_timezone(int *min);
-int clock_reset_timewarp(void);
-int clock_get_hwclock(struct tm *tm);
-int clock_set_hwclock(const struct tm *tm);
-int clock_apply_epoch(void);
diff --git a/src/basic/cpu-set-util.c b/src/basic/cpu-set-util.c
deleted file mode 100644 (file)
index 9a789ae..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <errno.h>
-#include <stddef.h>
-#include <syslog.h>
-
-#include "alloc-util.h"
-#include "cpu-set-util.h"
-#include "extract-word.h"
-#include "log.h"
-#include "macro.h"
-#include "parse-util.h"
-#include "string-util.h"
-
-cpu_set_t* cpu_set_malloc(unsigned *ncpus) {
-        cpu_set_t *c;
-        unsigned n = 1024;
-
-        /* Allocates the cpuset in the right size */
-
-        for (;;) {
-                c = CPU_ALLOC(n);
-                if (!c)
-                        return NULL;
-
-                if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), c) >= 0) {
-                        CPU_ZERO_S(CPU_ALLOC_SIZE(n), c);
-
-                        if (ncpus)
-                                *ncpus = n;
-
-                        return c;
-                }
-
-                CPU_FREE(c);
-
-                if (errno != EINVAL)
-                        return NULL;
-
-                n *= 2;
-        }
-}
-
-int parse_cpu_set_internal(
-                const char *rvalue,
-                cpu_set_t **cpu_set,
-                bool warn,
-                const char *unit,
-                const char *filename,
-                unsigned line,
-                const char *lvalue) {
-
-        _cleanup_cpu_free_ cpu_set_t *c = NULL;
-        const char *p = rvalue;
-        unsigned ncpus = 0;
-
-        assert(rvalue);
-
-        for (;;) {
-                _cleanup_free_ char *word = NULL;
-                unsigned cpu, cpu_lower, cpu_upper;
-                int r;
-
-                r = extract_first_word(&p, &word, WHITESPACE ",", EXTRACT_QUOTES);
-                if (r == -ENOMEM)
-                        return warn ? log_oom() : -ENOMEM;
-                if (r < 0)
-                        return warn ? log_syntax(unit, LOG_ERR, filename, line, r, "Invalid value for %s: %s", lvalue, rvalue) : r;
-                if (r == 0)
-                        break;
-
-                if (!c) {
-                        c = cpu_set_malloc(&ncpus);
-                        if (!c)
-                                return warn ? log_oom() : -ENOMEM;
-                }
-
-                r = parse_range(word, &cpu_lower, &cpu_upper);
-                if (r < 0)
-                        return warn ? log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CPU affinity '%s'", word) : r;
-                if (cpu_lower >= ncpus || cpu_upper >= ncpus)
-                        return warn ? log_syntax(unit, LOG_ERR, filename, line, EINVAL, "CPU out of range '%s' ncpus is %u", word, ncpus) : -EINVAL;
-
-                if (cpu_lower > cpu_upper) {
-                        if (warn)
-                                log_syntax(unit, LOG_WARNING, filename, line, 0, "Range '%s' is invalid, %u > %u, ignoring", word, cpu_lower, cpu_upper);
-                        continue;
-                }
-
-                for (cpu = cpu_lower; cpu <= cpu_upper; cpu++)
-                        CPU_SET_S(cpu, CPU_ALLOC_SIZE(ncpus), c);
-        }
-
-        /* On success, sets *cpu_set and returns ncpus for the system. */
-        if (c)
-                *cpu_set = TAKE_PTR(c);
-
-        return (int) ncpus;
-}
diff --git a/src/basic/cpu-set-util.h b/src/basic/cpu-set-util.h
deleted file mode 100644 (file)
index 1b6bd35..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <sched.h>
-
-#include "macro.h"
-
-#ifdef __NCPUBITS
-#define CPU_SIZE_TO_NUM(n) ((n) * __NCPUBITS)
-#else
-#define CPU_SIZE_TO_NUM(n) ((n) * sizeof(cpu_set_t) * 8)
-#endif
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(cpu_set_t*, CPU_FREE);
-#define _cleanup_cpu_free_ _cleanup_(CPU_FREEp)
-
-static inline cpu_set_t* cpu_set_mfree(cpu_set_t *p) {
-        if (p)
-                CPU_FREE(p);
-        return NULL;
-}
-
-cpu_set_t* cpu_set_malloc(unsigned *ncpus);
-
-int parse_cpu_set_internal(const char *rvalue, cpu_set_t **cpu_set, bool warn, const char *unit, const char *filename, unsigned line, const char *lvalue);
-
-static inline int parse_cpu_set_and_warn(const char *rvalue, cpu_set_t **cpu_set, const char *unit, const char *filename, unsigned line, const char *lvalue) {
-        assert(lvalue);
-
-        return parse_cpu_set_internal(rvalue, cpu_set, true, unit, filename, line, lvalue);
-}
-
-static inline int parse_cpu_set(const char *rvalue, cpu_set_t **cpu_set){
-        return parse_cpu_set_internal(rvalue, cpu_set, false, NULL, NULL, 0, NULL);
-}
diff --git a/src/basic/crypt-util.c b/src/basic/crypt-util.c
deleted file mode 100644 (file)
index 20bdc54..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#if HAVE_LIBCRYPTSETUP
-#include "crypt-util.h"
-#include "log.h"
-
-void cryptsetup_log_glue(int level, const char *msg, void *usrptr) {
-        switch (level) {
-        case CRYPT_LOG_NORMAL:
-                level = LOG_NOTICE;
-                break;
-        case CRYPT_LOG_ERROR:
-                level = LOG_ERR;
-                break;
-        case CRYPT_LOG_VERBOSE:
-                level = LOG_INFO;
-                break;
-        case CRYPT_LOG_DEBUG:
-                level = LOG_DEBUG;
-                break;
-        default:
-                log_error("Unknown libcryptsetup log level: %d", level);
-                level = LOG_ERR;
-        }
-
-        log_full(level, "%s", msg);
-}
-#endif
diff --git a/src/basic/crypt-util.h b/src/basic/crypt-util.h
deleted file mode 100644 (file)
index 8c86714..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#if HAVE_LIBCRYPTSETUP
-#include <libcryptsetup.h>
-
-#include "macro.h"
-
-/* libcryptsetup define for any LUKS version, compatible with libcryptsetup 1.x */
-#ifndef CRYPT_LUKS
-#define CRYPT_LUKS NULL
-#endif
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(struct crypt_device *, crypt_free);
-
-void cryptsetup_log_glue(int level, const char *msg, void *usrptr);
-#endif
diff --git a/src/basic/exec-util.c b/src/basic/exec-util.c
deleted file mode 100644 (file)
index 10d774d..0000000
+++ /dev/null
@@ -1,348 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <dirent.h>
-#include <errno.h>
-#include <sys/prctl.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <stdio.h>
-
-#include "alloc-util.h"
-#include "conf-files.h"
-#include "env-util.h"
-#include "exec-util.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "hashmap.h"
-#include "macro.h"
-#include "process-util.h"
-#include "serialize.h"
-#include "set.h"
-#include "signal-util.h"
-#include "stat-util.h"
-#include "string-util.h"
-#include "strv.h"
-#include "terminal-util.h"
-#include "util.h"
-
-/* Put this test here for a lack of better place */
-assert_cc(EAGAIN == EWOULDBLOCK);
-
-static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid) {
-
-        pid_t _pid;
-        int r;
-
-        if (null_or_empty_path(path)) {
-                log_debug("%s is empty (a mask).", path);
-                return 0;
-        }
-
-        r = safe_fork("(direxec)", FORK_DEATHSIG|FORK_LOG, &_pid);
-        if (r < 0)
-                return r;
-        if (r == 0) {
-                char *_argv[2];
-
-                if (stdout_fd >= 0) {
-                        r = rearrange_stdio(STDIN_FILENO, stdout_fd, STDERR_FILENO);
-                        if (r < 0)
-                                _exit(EXIT_FAILURE);
-                }
-
-                if (!argv) {
-                        _argv[0] = (char*) path;
-                        _argv[1] = NULL;
-                        argv = _argv;
-                } else
-                        argv[0] = (char*) path;
-
-                execv(path, argv);
-                log_error_errno(errno, "Failed to execute %s: %m", path);
-                _exit(EXIT_FAILURE);
-        }
-
-        *pid = _pid;
-        return 1;
-}
-
-static int do_execute(
-                char **directories,
-                usec_t timeout,
-                gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
-                void* const callback_args[_STDOUT_CONSUME_MAX],
-                int output_fd,
-                char *argv[],
-                char *envp[]) {
-
-        _cleanup_hashmap_free_free_ Hashmap *pids = NULL;
-        _cleanup_strv_free_ char **paths = NULL;
-        char **path, **e;
-        int r;
-
-        /* We fork this all off from a child process so that we can somewhat cleanly make
-         * use of SIGALRM to set a time limit.
-         *
-         * If callbacks is nonnull, execution is serial. Otherwise, we default to parallel.
-         */
-
-        r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE|CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char* const*) directories);
-        if (r < 0)
-                return log_error_errno(r, "Failed to enumerate executables: %m");
-
-        if (!callbacks) {
-                pids = hashmap_new(NULL);
-                if (!pids)
-                        return log_oom();
-        }
-
-        /* Abort execution of this process after the timout. We simply rely on SIGALRM as
-         * default action terminating the process, and turn on alarm(). */
-
-        if (timeout != USEC_INFINITY)
-                alarm(DIV_ROUND_UP(timeout, USEC_PER_SEC));
-
-        STRV_FOREACH(e, envp)
-                if (putenv(*e) != 0)
-                        return log_error_errno(errno, "Failed to set environment variable: %m");
-
-        STRV_FOREACH(path, paths) {
-                _cleanup_free_ char *t = NULL;
-                _cleanup_close_ int fd = -1;
-                pid_t pid;
-
-                t = strdup(*path);
-                if (!t)
-                        return log_oom();
-
-                if (callbacks) {
-                        fd = open_serialization_fd(basename(*path));
-                        if (fd < 0)
-                                return log_error_errno(fd, "Failed to open serialization file: %m");
-                }
-
-                r = do_spawn(t, argv, fd, &pid);
-                if (r <= 0)
-                        continue;
-
-                if (pids) {
-                        r = hashmap_put(pids, PID_TO_PTR(pid), t);
-                        if (r < 0)
-                                return log_oom();
-                        t = NULL;
-                } else {
-                        r = wait_for_terminate_and_check(t, pid, WAIT_LOG);
-                        if (r < 0)
-                                continue;
-
-                        if (lseek(fd, 0, SEEK_SET) < 0)
-                                return log_error_errno(errno, "Failed to seek on serialization fd: %m");
-
-                        r = callbacks[STDOUT_GENERATE](fd, callback_args[STDOUT_GENERATE]);
-                        fd = -1;
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to process output from %s: %m", *path);
-                }
-        }
-
-        if (callbacks) {
-                r = callbacks[STDOUT_COLLECT](output_fd, callback_args[STDOUT_COLLECT]);
-                if (r < 0)
-                        return log_error_errno(r, "Callback two failed: %m");
-        }
-
-        while (!hashmap_isempty(pids)) {
-                _cleanup_free_ char *t = NULL;
-                pid_t pid;
-
-                pid = PTR_TO_PID(hashmap_first_key(pids));
-                assert(pid > 0);
-
-                t = hashmap_remove(pids, PID_TO_PTR(pid));
-                assert(t);
-
-                (void) wait_for_terminate_and_check(t, pid, WAIT_LOG);
-        }
-
-        return 0;
-}
-
-int execute_directories(
-                const char* const* directories,
-                usec_t timeout,
-                gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
-                void* const callback_args[_STDOUT_CONSUME_MAX],
-                char *argv[],
-                char *envp[]) {
-
-        char **dirs = (char**) directories;
-        _cleanup_close_ int fd = -1;
-        char *name;
-        int r;
-
-        assert(!strv_isempty(dirs));
-
-        name = basename(dirs[0]);
-        assert(!isempty(name));
-
-        if (callbacks) {
-                assert(callback_args);
-                assert(callbacks[STDOUT_GENERATE]);
-                assert(callbacks[STDOUT_COLLECT]);
-                assert(callbacks[STDOUT_CONSUME]);
-
-                fd = open_serialization_fd(name);
-                if (fd < 0)
-                        return log_error_errno(fd, "Failed to open serialization file: %m");
-        }
-
-        /* Executes all binaries in the directories serially or in parallel and waits for
-         * them to finish. Optionally a timeout is applied. If a file with the same name
-         * exists in more than one directory, the earliest one wins. */
-
-        r = safe_fork("(sd-executor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL);
-        if (r < 0)
-                return r;
-        if (r == 0) {
-                r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv, envp);
-                _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
-        }
-
-        if (!callbacks)
-                return 0;
-
-        if (lseek(fd, 0, SEEK_SET) < 0)
-                return log_error_errno(errno, "Failed to rewind serialization fd: %m");
-
-        r = callbacks[STDOUT_CONSUME](fd, callback_args[STDOUT_CONSUME]);
-        fd = -1;
-        if (r < 0)
-                return log_error_errno(r, "Failed to parse returned data: %m");
-        return 0;
-}
-
-static int gather_environment_generate(int fd, void *arg) {
-        char ***env = arg, **x, **y;
-        _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_strv_free_ char **new = NULL;
-        int r;
-
-        /* Read a series of VAR=value assignments from fd, use them to update the list of
-         * variables in env. Also update the exported environment.
-         *
-         * fd is always consumed, even on error.
-         */
-
-        assert(env);
-
-        f = fdopen(fd, "r");
-        if (!f) {
-                safe_close(fd);
-                return -errno;
-        }
-
-        r = load_env_file_pairs(f, NULL, &new);
-        if (r < 0)
-                return r;
-
-        STRV_FOREACH_PAIR(x, y, new) {
-                char *p;
-
-                if (!env_name_is_valid(*x)) {
-                        log_warning("Invalid variable assignment \"%s=...\", ignoring.", *x);
-                        continue;
-                }
-
-                p = strjoin(*x, "=", *y);
-                if (!p)
-                        return -ENOMEM;
-
-                r = strv_env_replace(env, p);
-                if (r < 0)
-                        return r;
-
-                if (setenv(*x, *y, true) < 0)
-                        return -errno;
-        }
-
-        return r;
-}
-
-static int gather_environment_collect(int fd, void *arg) {
-        _cleanup_fclose_ FILE *f = NULL;
-        char ***env = arg;
-        int r;
-
-        /* Write out a series of env=cescape(VAR=value) assignments to fd. */
-
-        assert(env);
-
-        f = fdopen(fd, "w");
-        if (!f) {
-                safe_close(fd);
-                return -errno;
-        }
-
-        r = serialize_strv(f, "env", *env);
-        if (r < 0)
-                return r;
-
-        r = fflush_and_check(f);
-        if (r < 0)
-                return r;
-
-        return 0;
-}
-
-static int gather_environment_consume(int fd, void *arg) {
-        _cleanup_fclose_ FILE *f = NULL;
-        char ***env = arg;
-        int r = 0;
-
-        /* Read a series of env=cescape(VAR=value) assignments from fd into env. */
-
-        assert(env);
-
-        f = fdopen(fd, "re");
-        if (!f) {
-                safe_close(fd);
-                return -errno;
-        }
-
-        for (;;) {
-                _cleanup_free_ char *line = NULL;
-                const char *v;
-                int k;
-
-                k = read_line(f, LONG_LINE_MAX, &line);
-                if (k < 0)
-                        return k;
-                if (k == 0)
-                        break;
-
-                v = startswith(line, "env=");
-                if (!v) {
-                        log_debug("Serialization line \"%s\" unexpectedly didn't start with \"env=\".", line);
-                        if (r == 0)
-                                r = -EINVAL;
-
-                        continue;
-                }
-
-                k = deserialize_environment(v, env);
-                if (k < 0) {
-                        log_debug_errno(k, "Invalid serialization line \"%s\": %m", line);
-
-                        if (r == 0)
-                                r = k;
-                }
-        }
-
-        return r;
-}
-
-const gather_stdout_callback_t gather_environment[] = {
-        gather_environment_generate,
-        gather_environment_collect,
-        gather_environment_consume,
-};
diff --git a/src/basic/exec-util.h b/src/basic/exec-util.h
deleted file mode 100644 (file)
index 6ac3c90..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-
-#include "time-util.h"
-
-typedef int (*gather_stdout_callback_t) (int fd, void *arg);
-
-enum {
-        STDOUT_GENERATE,   /* from generators to helper process */
-        STDOUT_COLLECT,    /* from helper process to main process */
-        STDOUT_CONSUME,    /* process data in main process */
-        _STDOUT_CONSUME_MAX,
-};
-
-int execute_directories(
-                const char* const* directories,
-                usec_t timeout,
-                gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
-                void* const callback_args[_STDOUT_CONSUME_MAX],
-                char *argv[],
-                char *envp[]);
-
-extern const gather_stdout_callback_t gather_environment[_STDOUT_CONSUME_MAX];
diff --git a/src/basic/exit-status.c b/src/basic/exit-status.c
deleted file mode 100644 (file)
index 21af8c4..0000000
+++ /dev/null
@@ -1,280 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <signal.h>
-#include <stdlib.h>
-#include <sysexits.h>
-
-#include "exit-status.h"
-#include "macro.h"
-#include "set.h"
-
-const char* exit_status_to_string(int status, ExitStatusLevel level) {
-
-        /* Exit status ranges:
-         *
-         *   0…1   │ ISO C, EXIT_SUCCESS + EXIT_FAILURE
-         *   2…7   │ LSB exit codes for init scripts
-         *   8…63  │ (Currently unmapped)
-         *  64…78  │ BSD defined exit codes
-         *  79…199 │ (Currently unmapped)
-         * 200…241 │ systemd's private error codes (might be extended to 254 in future development)
-         * 242…254 │ (Currently unmapped, but see above)
-         *   255   │ (We should probably stay away from that one, it's frequently used by applications to indicate an
-         *         │ exit reason that cannot really be expressed in a single exit status value — such as a propagated
-         *         │ signal or such)
-         */
-
-        switch (status) {  /* We always cover the ISO C ones */
-
-        case EXIT_SUCCESS:
-                return "SUCCESS";
-
-        case EXIT_FAILURE:
-                return "FAILURE";
-        }
-
-        if (IN_SET(level, EXIT_STATUS_SYSTEMD, EXIT_STATUS_LSB, EXIT_STATUS_FULL)) {
-                switch (status) { /* Optionally we cover our own ones */
-
-                case EXIT_CHDIR:
-                        return "CHDIR";
-
-                case EXIT_NICE:
-                        return "NICE";
-
-                case EXIT_FDS:
-                        return "FDS";
-
-                case EXIT_EXEC:
-                        return "EXEC";
-
-                case EXIT_MEMORY:
-                        return "MEMORY";
-
-                case EXIT_LIMITS:
-                        return "LIMITS";
-
-                case EXIT_OOM_ADJUST:
-                        return "OOM_ADJUST";
-
-                case EXIT_SIGNAL_MASK:
-                        return "SIGNAL_MASK";
-
-                case EXIT_STDIN:
-                        return "STDIN";
-
-                case EXIT_STDOUT:
-                        return "STDOUT";
-
-                case EXIT_CHROOT:
-                        return "CHROOT";
-
-                case EXIT_IOPRIO:
-                        return "IOPRIO";
-
-                case EXIT_TIMERSLACK:
-                        return "TIMERSLACK";
-
-                case EXIT_SECUREBITS:
-                        return "SECUREBITS";
-
-                case EXIT_SETSCHEDULER:
-                        return "SETSCHEDULER";
-
-                case EXIT_CPUAFFINITY:
-                        return "CPUAFFINITY";
-
-                case EXIT_GROUP:
-                        return "GROUP";
-
-                case EXIT_USER:
-                        return "USER";
-
-                case EXIT_CAPABILITIES:
-                        return "CAPABILITIES";
-
-                case EXIT_CGROUP:
-                        return "CGROUP";
-
-                case EXIT_SETSID:
-                        return "SETSID";
-
-                case EXIT_CONFIRM:
-                        return "CONFIRM";
-
-                case EXIT_STDERR:
-                        return "STDERR";
-
-                case EXIT_PAM:
-                        return "PAM";
-
-                case EXIT_NETWORK:
-                        return "NETWORK";
-
-                case EXIT_NAMESPACE:
-                        return "NAMESPACE";
-
-                case EXIT_NO_NEW_PRIVILEGES:
-                        return "NO_NEW_PRIVILEGES";
-
-                case EXIT_SECCOMP:
-                        return "SECCOMP";
-
-                case EXIT_SELINUX_CONTEXT:
-                        return "SELINUX_CONTEXT";
-
-                case EXIT_PERSONALITY:
-                        return "PERSONALITY";
-
-                case EXIT_APPARMOR_PROFILE:
-                        return "APPARMOR";
-
-                case EXIT_ADDRESS_FAMILIES:
-                        return "ADDRESS_FAMILIES";
-
-                case EXIT_RUNTIME_DIRECTORY:
-                        return "RUNTIME_DIRECTORY";
-
-                case EXIT_CHOWN:
-                        return "CHOWN";
-
-                case EXIT_SMACK_PROCESS_LABEL:
-                        return "SMACK_PROCESS_LABEL";
-
-                case EXIT_KEYRING:
-                        return "KEYRING";
-
-                case EXIT_STATE_DIRECTORY:
-                        return "STATE_DIRECTORY";
-
-                case EXIT_CACHE_DIRECTORY:
-                        return "CACHE_DIRECTORY";
-
-                case EXIT_LOGS_DIRECTORY:
-                        return "LOGS_DIRECTORY";
-
-                case EXIT_CONFIGURATION_DIRECTORY:
-                        return "CONFIGURATION_DIRECTORY";
-                }
-        }
-
-        if (IN_SET(level, EXIT_STATUS_LSB, EXIT_STATUS_FULL)) {
-                switch (status) { /* Optionally we support LSB ones */
-
-                case EXIT_INVALIDARGUMENT:
-                        return "INVALIDARGUMENT";
-
-                case EXIT_NOTIMPLEMENTED:
-                        return "NOTIMPLEMENTED";
-
-                case EXIT_NOPERMISSION:
-                        return "NOPERMISSION";
-
-                case EXIT_NOTINSTALLED:
-                        return "NOTINSTALLED";
-
-                case EXIT_NOTCONFIGURED:
-                        return "NOTCONFIGURED";
-
-                case EXIT_NOTRUNNING:
-                        return "NOTRUNNING";
-                }
-        }
-
-        if (level == EXIT_STATUS_FULL) {
-                switch (status) { /* Optionally, we support BSD exit statusses */
-
-                case EX_USAGE:
-                        return "USAGE";
-
-                case EX_DATAERR:
-                        return "DATAERR";
-
-                case EX_NOINPUT:
-                        return "NOINPUT";
-
-                case EX_NOUSER:
-                        return "NOUSER";
-
-                case EX_NOHOST:
-                        return "NOHOST";
-
-                case EX_UNAVAILABLE:
-                        return "UNAVAILABLE";
-
-                case EX_SOFTWARE:
-                        return "SOFTWARE";
-
-                case EX_OSERR:
-                        return "OSERR";
-
-                case EX_OSFILE:
-                        return "OSFILE";
-
-                case EX_CANTCREAT:
-                        return "CANTCREAT";
-
-                case EX_IOERR:
-                        return "IOERR";
-
-                case EX_TEMPFAIL:
-                        return "TEMPFAIL";
-
-                case EX_PROTOCOL:
-                        return "PROTOCOL";
-
-                case EX_NOPERM:
-                        return "NOPERM";
-
-                case EX_CONFIG:
-                        return "CONFIG";
-                }
-        }
-
-        return NULL;
-}
-
-bool is_clean_exit(int code, int status, ExitClean clean, ExitStatusSet *success_status) {
-
-        if (code == CLD_EXITED)
-                return status == 0 ||
-                       (success_status &&
-                        set_contains(success_status->status, INT_TO_PTR(status)));
-
-        /* If a daemon does not implement handlers for some of the signals that's not considered an unclean shutdown */
-        if (code == CLD_KILLED)
-                return
-                        (clean == EXIT_CLEAN_DAEMON && IN_SET(status, SIGHUP, SIGINT, SIGTERM, SIGPIPE)) ||
-                        (success_status &&
-                         set_contains(success_status->signal, INT_TO_PTR(status)));
-
-        return false;
-}
-
-void exit_status_set_free(ExitStatusSet *x) {
-        assert(x);
-
-        x->status = set_free(x->status);
-        x->signal = set_free(x->signal);
-}
-
-bool exit_status_set_is_empty(ExitStatusSet *x) {
-        if (!x)
-                return true;
-
-        return set_isempty(x->status) && set_isempty(x->signal);
-}
-
-bool exit_status_set_test(ExitStatusSet *x, int code, int status) {
-
-        if (exit_status_set_is_empty(x))
-                return false;
-
-        if (code == CLD_EXITED && set_contains(x->status, INT_TO_PTR(status)))
-                return true;
-
-        if (IN_SET(code, CLD_KILLED, CLD_DUMPED) && set_contains(x->signal, INT_TO_PTR(status)))
-                return true;
-
-        return false;
-}
diff --git a/src/basic/exit-status.h b/src/basic/exit-status.h
deleted file mode 100644 (file)
index c41e8b8..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-
-#include "hashmap.h"
-#include "macro.h"
-#include "set.h"
-
-/* This defines pretty names for the LSB 'start' verb exit codes. Note that they shouldn't be confused with the LSB
- * 'status' verb exit codes which are defined very differently. For details see:
- *
- * https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
- */
-
-enum {
-        /* EXIT_SUCCESS defined by libc */
-        /* EXIT_FAILURE defined by libc */
-        EXIT_INVALIDARGUMENT = 2,
-        EXIT_NOTIMPLEMENTED = 3,
-        EXIT_NOPERMISSION = 4,
-        EXIT_NOTINSTALLED = 5,
-        EXIT_NOTCONFIGURED = 6,
-        EXIT_NOTRUNNING = 7,
-
-        /* BSD's sysexits.h defines a couple EX_xyz exit codes in the range 64 … 78 */
-
-        /* The LSB suggests that error codes >= 200 are "reserved". We use them here under the assumption that they
-         * hence are unused by init scripts. */
-        EXIT_CHDIR = 200,
-        EXIT_NICE,
-        EXIT_FDS,
-        EXIT_EXEC,
-        EXIT_MEMORY,
-        EXIT_LIMITS,
-        EXIT_OOM_ADJUST,
-        EXIT_SIGNAL_MASK,
-        EXIT_STDIN,
-        EXIT_STDOUT,
-        EXIT_CHROOT,   /* 210 */
-        EXIT_IOPRIO,
-        EXIT_TIMERSLACK,
-        EXIT_SECUREBITS,
-        EXIT_SETSCHEDULER,
-        EXIT_CPUAFFINITY,
-        EXIT_GROUP,
-        EXIT_USER,
-        EXIT_CAPABILITIES,
-        EXIT_CGROUP,
-        EXIT_SETSID,   /* 220 */
-        EXIT_CONFIRM,
-        EXIT_STDERR,
-        _EXIT_RESERVED, /* used to be tcpwrap, don't reuse! */
-        EXIT_PAM,
-        EXIT_NETWORK,
-        EXIT_NAMESPACE,
-        EXIT_NO_NEW_PRIVILEGES,
-        EXIT_SECCOMP,
-        EXIT_SELINUX_CONTEXT,
-        EXIT_PERSONALITY,  /* 230 */
-        EXIT_APPARMOR_PROFILE,
-        EXIT_ADDRESS_FAMILIES,
-        EXIT_RUNTIME_DIRECTORY,
-        _EXIT_RESERVED2, /* used to be used by kdbus, don't reuse */
-        EXIT_CHOWN,
-        EXIT_SMACK_PROCESS_LABEL,
-        EXIT_KEYRING,
-        EXIT_STATE_DIRECTORY,
-        EXIT_CACHE_DIRECTORY,
-        EXIT_LOGS_DIRECTORY, /* 240 */
-        EXIT_CONFIGURATION_DIRECTORY,
-};
-
-typedef enum ExitStatusLevel {
-        EXIT_STATUS_MINIMAL,   /* only cover libc EXIT_STATUS/EXIT_FAILURE */
-        EXIT_STATUS_SYSTEMD,   /* cover libc and systemd's own exit codes */
-        EXIT_STATUS_LSB,       /* cover libc, systemd's own and LSB exit codes */
-        EXIT_STATUS_FULL,      /* cover libc, systemd's own, LSB and BSD (EX_xyz) exit codes */
-} ExitStatusLevel;
-
-typedef struct ExitStatusSet {
-        Set *status;
-        Set *signal;
-} ExitStatusSet;
-
-const char* exit_status_to_string(int status, ExitStatusLevel level) _const_;
-
-typedef enum ExitClean {
-        EXIT_CLEAN_DAEMON,
-        EXIT_CLEAN_COMMAND,
-} ExitClean;
-
-bool is_clean_exit(int code, int status, ExitClean clean, ExitStatusSet *success_status);
-
-void exit_status_set_free(ExitStatusSet *x);
-bool exit_status_set_is_empty(ExitStatusSet *x);
-bool exit_status_set_test(ExitStatusSet *x, int code, int status);
diff --git a/src/basic/fileio-label.c b/src/basic/fileio-label.c
deleted file mode 100644 (file)
index b5362b5..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <sys/stat.h>
-
-#include "fileio-label.h"
-#include "fileio.h"
-#include "selinux-util.h"
-
-int write_string_file_atomic_label_ts(const char *fn, const char *line, struct timespec *ts) {
-        int r;
-
-        r = mac_selinux_create_file_prepare(fn, S_IFREG);
-        if (r < 0)
-                return r;
-
-        r = write_string_file_ts(fn, line, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC, ts);
-
-        mac_selinux_create_file_clear();
-
-        return r;
-}
-
-int write_env_file_label(const char *fname, char **l) {
-        int r;
-
-        r = mac_selinux_create_file_prepare(fname, S_IFREG);
-        if (r < 0)
-                return r;
-
-        r = write_env_file(fname, l);
-
-        mac_selinux_create_file_clear();
-
-        return r;
-}
-
-int fopen_temporary_label(const char *target,
-                          const char *path, FILE **f, char **temp_path) {
-        int r;
-
-        r = mac_selinux_create_file_prepare(target, S_IFREG);
-        if (r < 0)
-                return r;
-
-        r = fopen_temporary(path, f, temp_path);
-
-        mac_selinux_create_file_clear();
-
-        return r;
-}
-
-int create_shutdown_run_nologin_or_warn(void) {
-        int r;
-
-        /* This is used twice: once in systemd-user-sessions.service, in order to block logins when we actually go
-         * down, and once in systemd-logind.service when shutdowns are scheduled, and logins are to be turned off a bit
-         * in advance. We use the same wording of the message in both cases. */
-
-        r = write_string_file_atomic_label("/run/nologin",
-                                           "System is going down. Unprivileged users are not permitted to log in anymore. "
-                                           "For technical details, see pam_nologin(8).");
-        if (r < 0)
-                return log_error_errno(r, "Failed to create /run/nologin: %m");
-
-        return 0;
-}
diff --git a/src/basic/fileio-label.h b/src/basic/fileio-label.h
deleted file mode 100644 (file)
index d11112d..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdio.h>
-
-#include "fileio.h"
-
-/* These functions are split out of fileio.h (and not for examplement just as flags to the functions they wrap) in
- * order to optimize linking: This way, -lselinux is needed only for the callers of these functions that need selinux,
- * but not for all */
-
-int write_string_file_atomic_label_ts(const char *fn, const char *line, struct timespec *ts);
-static inline int write_string_file_atomic_label(const char *fn, const char *line) {
-        return write_string_file_atomic_label_ts(fn, line, NULL);
-}
-int write_env_file_label(const char *fname, char **l);
-int fopen_temporary_label(const char *target, const char *path, FILE **f, char **temp_path);
-
-int create_shutdown_run_nologin_or_warn(void);
diff --git a/src/basic/format-table.c b/src/basic/format-table.c
deleted file mode 100644 (file)
index 10e15c9..0000000
+++ /dev/null
@@ -1,1201 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <stdio_ext.h>
-
-#include "alloc-util.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "format-table.h"
-#include "gunicode.h"
-#include "pager.h"
-#include "parse-util.h"
-#include "string-util.h"
-#include "terminal-util.h"
-#include "time-util.h"
-#include "utf8.h"
-#include "util.h"
-
-#define DEFAULT_WEIGHT 100
-
-/*
-   A few notes on implementation details:
-
- - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
-   table. It can be easily converted to an index number and back.
-
- - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
-   'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
-   ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
-   outside only sees Table and TableCell.
-
- - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
-   previous one.
-
- - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
-   derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
-   that. The first row is always the header row. If header display is turned off we simply skip outputting the first
-   row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
-
- - Note because there's no row and no column object some properties that might be appropriate as row/column properties
-   are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
-   add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
-   cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
-   instead.
-
- - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
-   from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
-   this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
-*/
-
-typedef struct TableData {
-        unsigned n_ref;
-        TableDataType type;
-
-        size_t minimum_width;       /* minimum width for the column */
-        size_t maximum_width;       /* maximum width for the column */
-        unsigned weight;            /* the horizontal weight for this column, in case the table is expanded/compressed */
-        unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
-        unsigned align_percent;     /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
-
-        const char *color;          /* ANSI color string to use for this cell. When written to terminal should not move cursor. Will automatically be reset after the cell */
-        char *formatted;            /* A cached textual representation of the cell data, before ellipsation/alignment */
-
-        union {
-                uint8_t data[0];    /* data is generic array */
-                bool boolean;
-                usec_t timestamp;
-                usec_t timespan;
-                uint64_t size;
-                char string[0];
-                uint32_t uint32;
-                /* … add more here as we start supporting more cell data types … */
-        };
-} TableData;
-
-static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
-        size_t i;
-
-        assert(cell);
-
-        i = PTR_TO_SIZE(cell);
-        assert(i > 0);
-
-        return i-1;
-}
-
-static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
-        assert(index != (size_t) -1);
-        return SIZE_TO_PTR(index + 1);
-}
-
-struct Table {
-        size_t n_columns;
-        size_t n_cells;
-
-        bool header;   /* Whether to show the header row? */
-        size_t width;  /* If != (size_t) -1 the width to format this table in */
-
-        TableData **data;
-        size_t n_allocated;
-
-        size_t *display_map;  /* List of columns to show (by their index). It's fine if columns are listed multiple times or not at all */
-        size_t n_display_map;
-
-        size_t *sort_map;     /* The columns to order rows by, in order of preference. */
-        size_t n_sort_map;
-};
-
-Table *table_new_raw(size_t n_columns) {
-        _cleanup_(table_unrefp) Table *t = NULL;
-
-        assert(n_columns > 0);
-
-        t = new(Table, 1);
-        if (!t)
-                return NULL;
-
-        *t = (struct Table) {
-                .n_columns = n_columns,
-                .header = true,
-                .width = (size_t) -1,
-        };
-
-        return TAKE_PTR(t);
-}
-
-Table *table_new_internal(const char *first_header, ...) {
-        _cleanup_(table_unrefp) Table *t = NULL;
-        size_t n_columns = 1;
-        va_list ap;
-        int r;
-
-        assert(first_header);
-
-        va_start(ap, first_header);
-        for (;;) {
-                const char *h;
-
-                h = va_arg(ap, const char*);
-                if (!h)
-                        break;
-
-                n_columns++;
-        }
-        va_end(ap);
-
-        t = table_new_raw(n_columns);
-        if (!t)
-                return NULL;
-
-        r = table_add_cell(t, NULL, TABLE_STRING, first_header);
-        if (r < 0)
-                return NULL;
-
-        va_start(ap, first_header);
-        for (;;) {
-                const char *h;
-
-                h = va_arg(ap, const char*);
-                if (!h)
-                        break;
-
-                r = table_add_cell(t, NULL, TABLE_STRING, h);
-                if (r < 0) {
-                        va_end(ap);
-                        return NULL;
-                }
-        }
-        va_end(ap);
-
-        assert(t->n_columns == t->n_cells);
-        return TAKE_PTR(t);
-}
-
-static TableData *table_data_free(TableData *d) {
-        assert(d);
-
-        free(d->formatted);
-        return mfree(d);
-}
-
-DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
-DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
-
-Table *table_unref(Table *t) {
-        size_t i;
-
-        if (!t)
-                return NULL;
-
-        for (i = 0; i < t->n_cells; i++)
-                table_data_unref(t->data[i]);
-
-        free(t->data);
-        free(t->display_map);
-        free(t->sort_map);
-
-        return mfree(t);
-}
-
-static size_t table_data_size(TableDataType type, const void *data) {
-
-        switch (type) {
-
-        case TABLE_EMPTY:
-                return 0;
-
-        case TABLE_STRING:
-                return strlen(data) + 1;
-
-        case TABLE_BOOLEAN:
-                return sizeof(bool);
-
-        case TABLE_TIMESTAMP:
-        case TABLE_TIMESPAN:
-                return sizeof(usec_t);
-
-        case TABLE_SIZE:
-                return sizeof(uint64_t);
-
-        case TABLE_UINT32:
-                return sizeof(uint32_t);
-
-        default:
-                assert_not_reached("Uh? Unexpected cell type");
-        }
-}
-
-static bool table_data_matches(
-                TableData *d,
-                TableDataType type,
-                const void *data,
-                size_t minimum_width,
-                size_t maximum_width,
-                unsigned weight,
-                unsigned align_percent,
-                unsigned ellipsize_percent) {
-
-        size_t k, l;
-        assert(d);
-
-        if (d->type != type)
-                return false;
-
-        if (d->minimum_width != minimum_width)
-                return false;
-
-        if (d->maximum_width != maximum_width)
-                return false;
-
-        if (d->weight != weight)
-                return false;
-
-        if (d->align_percent != align_percent)
-                return false;
-
-        if (d->ellipsize_percent != ellipsize_percent)
-                return false;
-
-        k = table_data_size(type, data);
-        l = table_data_size(d->type, d->data);
-
-        if (k != l)
-                return false;
-
-        return memcmp(data, d->data, l) == 0;
-}
-
-static TableData *table_data_new(
-                TableDataType type,
-                const void *data,
-                size_t minimum_width,
-                size_t maximum_width,
-                unsigned weight,
-                unsigned align_percent,
-                unsigned ellipsize_percent) {
-
-        size_t data_size;
-        TableData *d;
-
-        data_size = table_data_size(type, data);
-
-        d = malloc0(offsetof(TableData, data) + data_size);
-        if (!d)
-                return NULL;
-
-        d->n_ref = 1;
-        d->type = type;
-        d->minimum_width = minimum_width;
-        d->maximum_width = maximum_width;
-        d->weight = weight;
-        d->align_percent = align_percent;
-        d->ellipsize_percent = ellipsize_percent;
-        memcpy_safe(d->data, data, data_size);
-
-        return d;
-}
-
-int table_add_cell_full(
-                Table *t,
-                TableCell **ret_cell,
-                TableDataType type,
-                const void *data,
-                size_t minimum_width,
-                size_t maximum_width,
-                unsigned weight,
-                unsigned align_percent,
-                unsigned ellipsize_percent) {
-
-        _cleanup_(table_data_unrefp) TableData *d = NULL;
-        TableData *p;
-
-        assert(t);
-        assert(type >= 0);
-        assert(type < _TABLE_DATA_TYPE_MAX);
-
-        /* Determine the cell adjacent to the current one, but one row up */
-        if (t->n_cells >= t->n_columns)
-                assert_se(p = t->data[t->n_cells - t->n_columns]);
-        else
-                p = NULL;
-
-        /* If formatting parameters are left unspecified, copy from the previous row */
-        if (minimum_width == (size_t) -1)
-                minimum_width = p ? p->minimum_width : 1;
-
-        if (weight == (unsigned) -1)
-                weight = p ? p->weight : DEFAULT_WEIGHT;
-
-        if (align_percent == (unsigned) -1)
-                align_percent = p ? p->align_percent : 0;
-
-        if (ellipsize_percent == (unsigned) -1)
-                ellipsize_percent = p ? p->ellipsize_percent : 100;
-
-        assert(align_percent <= 100);
-        assert(ellipsize_percent <= 100);
-
-        /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
-         * formatting. Let's see if we can reuse the cell data and ref it once more. */
-
-        if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent))
-                d = table_data_ref(p);
-        else {
-                d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent);
-                if (!d)
-                        return -ENOMEM;
-        }
-
-        if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
-                return -ENOMEM;
-
-        if (ret_cell)
-                *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
-
-        t->data[t->n_cells++] = TAKE_PTR(d);
-
-        return 0;
-}
-
-int table_dup_cell(Table *t, TableCell *cell) {
-        size_t i;
-
-        assert(t);
-
-        /* Add the data of the specified cell a second time as a new cell to the end. */
-
-        i = TABLE_CELL_TO_INDEX(cell);
-        if (i >= t->n_cells)
-                return -ENXIO;
-
-        if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
-                return -ENOMEM;
-
-        t->data[t->n_cells++] = table_data_ref(t->data[i]);
-        return 0;
-}
-
-static int table_dedup_cell(Table *t, TableCell *cell) {
-        TableData *nd, *od;
-        size_t i;
-
-        assert(t);
-
-        /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
-         * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
-
-        i = TABLE_CELL_TO_INDEX(cell);
-        if (i >= t->n_cells)
-                return -ENXIO;
-
-        assert_se(od = t->data[i]);
-        if (od->n_ref == 1)
-                return 0;
-
-        assert(od->n_ref > 1);
-
-        nd = table_data_new(od->type, od->data, od->minimum_width, od->maximum_width, od->weight, od->align_percent, od->ellipsize_percent);
-        if (!nd)
-                return -ENOMEM;
-
-        table_data_unref(od);
-        t->data[i] = nd;
-
-        assert(nd->n_ref == 1);
-
-        return 1;
-}
-
-static TableData *table_get_data(Table *t, TableCell *cell) {
-        size_t i;
-
-        assert(t);
-        assert(cell);
-
-        /* Get the data object of the specified cell, or NULL if it doesn't exist */
-
-        i = TABLE_CELL_TO_INDEX(cell);
-        if (i >= t->n_cells)
-                return NULL;
-
-        assert(t->data[i]);
-        assert(t->data[i]->n_ref > 0);
-
-        return t->data[i];
-}
-
-int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
-        int r;
-
-        assert(t);
-        assert(cell);
-
-        if (minimum_width == (size_t) -1)
-                minimum_width = 1;
-
-        r = table_dedup_cell(t, cell);
-        if (r < 0)
-                return r;
-
-        table_get_data(t, cell)->minimum_width = minimum_width;
-        return 0;
-}
-
-int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width) {
-        int r;
-
-        assert(t);
-        assert(cell);
-
-        r = table_dedup_cell(t, cell);
-        if (r < 0)
-                return r;
-
-        table_get_data(t, cell)->maximum_width = maximum_width;
-        return 0;
-}
-
-int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
-        int r;
-
-        assert(t);
-        assert(cell);
-
-        if (weight == (unsigned) -1)
-                weight = DEFAULT_WEIGHT;
-
-        r = table_dedup_cell(t, cell);
-        if (r < 0)
-                return r;
-
-        table_get_data(t, cell)->weight = weight;
-        return 0;
-}
-
-int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
-        int r;
-
-        assert(t);
-        assert(cell);
-
-        if (percent == (unsigned) -1)
-                percent = 0;
-
-        assert(percent <= 100);
-
-        r = table_dedup_cell(t, cell);
-        if (r < 0)
-                return r;
-
-        table_get_data(t, cell)->align_percent = percent;
-        return 0;
-}
-
-int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
-        int r;
-
-        assert(t);
-        assert(cell);
-
-        if (percent == (unsigned) -1)
-                percent = 100;
-
-        assert(percent <= 100);
-
-        r = table_dedup_cell(t, cell);
-        if (r < 0)
-                return r;
-
-        table_get_data(t, cell)->ellipsize_percent = percent;
-        return 0;
-}
-
-int table_set_color(Table *t, TableCell *cell, const char *color) {
-        int r;
-
-        assert(t);
-        assert(cell);
-
-        r = table_dedup_cell(t, cell);
-        if (r < 0)
-                return r;
-
-        table_get_data(t, cell)->color = empty_to_null(color);
-        return 0;
-}
-
-int table_add_many_internal(Table *t, TableDataType first_type, ...) {
-        TableDataType type;
-        va_list ap;
-        int r;
-
-        assert(t);
-        assert(first_type >= 0);
-        assert(first_type < _TABLE_DATA_TYPE_MAX);
-
-        type = first_type;
-
-        va_start(ap, first_type);
-        for (;;) {
-                const void *data;
-                union {
-                        uint64_t size;
-                        usec_t usec;
-                        uint32_t uint32;
-                        bool b;
-                } buffer;
-
-                switch (type) {
-
-                case TABLE_EMPTY:
-                        data = NULL;
-                        break;
-
-                case TABLE_STRING:
-                        data = va_arg(ap, const char *);
-                        break;
-
-                case TABLE_BOOLEAN:
-                        buffer.b = va_arg(ap, int);
-                        data = &buffer.b;
-                        break;
-
-                case TABLE_TIMESTAMP:
-                case TABLE_TIMESPAN:
-                        buffer.usec = va_arg(ap, usec_t);
-                        data = &buffer.usec;
-                        break;
-
-                case TABLE_SIZE:
-                        buffer.size = va_arg(ap, uint64_t);
-                        data = &buffer.size;
-                        break;
-
-                case TABLE_UINT32:
-                        buffer.uint32 = va_arg(ap, uint32_t);
-                        data = &buffer.uint32;
-                        break;
-
-                case _TABLE_DATA_TYPE_MAX:
-                        /* Used as end marker */
-                        va_end(ap);
-                        return 0;
-
-                default:
-                        assert_not_reached("Uh? Unexpected data type.");
-                }
-
-                r = table_add_cell(t, NULL, type, data);
-                if (r < 0) {
-                        va_end(ap);
-                        return r;
-                }
-
-                type = va_arg(ap, TableDataType);
-        }
-}
-
-void table_set_header(Table *t, bool b) {
-        assert(t);
-
-        t->header = b;
-}
-
-void table_set_width(Table *t, size_t width) {
-        assert(t);
-
-        t->width = width;
-}
-
-int table_set_display(Table *t, size_t first_column, ...) {
-        size_t allocated, column;
-        va_list ap;
-
-        assert(t);
-
-        allocated = t->n_display_map;
-        column = first_column;
-
-        va_start(ap, first_column);
-        for (;;) {
-                assert(column < t->n_columns);
-
-                if (!GREEDY_REALLOC(t->display_map, allocated, MAX(t->n_columns, t->n_display_map+1))) {
-                        va_end(ap);
-                        return -ENOMEM;
-                }
-
-                t->display_map[t->n_display_map++] = column;
-
-                column = va_arg(ap, size_t);
-                if (column == (size_t) -1)
-                        break;
-
-        }
-        va_end(ap);
-
-        return 0;
-}
-
-int table_set_sort(Table *t, size_t first_column, ...) {
-        size_t allocated, column;
-        va_list ap;
-
-        assert(t);
-
-        allocated = t->n_sort_map;
-        column = first_column;
-
-        va_start(ap, first_column);
-        for (;;) {
-                assert(column < t->n_columns);
-
-                if (!GREEDY_REALLOC(t->sort_map, allocated, MAX(t->n_columns, t->n_sort_map+1))) {
-                        va_end(ap);
-                        return -ENOMEM;
-                }
-
-                t->sort_map[t->n_sort_map++] = column;
-
-                column = va_arg(ap, size_t);
-                if (column == (size_t) -1)
-                        break;
-        }
-        va_end(ap);
-
-        return 0;
-}
-
-static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
-        assert(a);
-        assert(b);
-
-        if (a->type == b->type) {
-
-                /* We only define ordering for cells of the same data type. If cells with different data types are
-                 * compared we follow the order the cells were originally added in */
-
-                switch (a->type) {
-
-                case TABLE_STRING:
-                        return strcmp(a->string, b->string);
-
-                case TABLE_BOOLEAN:
-                        if (!a->boolean && b->boolean)
-                                return -1;
-                        if (a->boolean && !b->boolean)
-                                return 1;
-                        return 0;
-
-                case TABLE_TIMESTAMP:
-                        return CMP(a->timestamp, b->timestamp);
-
-                case TABLE_TIMESPAN:
-                        return CMP(a->timespan, b->timespan);
-
-                case TABLE_SIZE:
-                        return CMP(a->size, b->size);
-
-                case TABLE_UINT32:
-                        return CMP(a->uint32, b->uint32);
-
-                default:
-                        ;
-                }
-        }
-
-        /* Generic fallback using the orginal order in which the cells where added. */
-        return CMP(index_a, index_b);
-}
-
-static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
-        size_t i;
-        int r;
-
-        assert(t);
-        assert(t->sort_map);
-
-        /* Make sure the header stays at the beginning */
-        if (*a < t->n_columns && *b < t->n_columns)
-                return 0;
-        if (*a < t->n_columns)
-                return -1;
-        if (*b < t->n_columns)
-                return 1;
-
-        /* Order other lines by the sorting map */
-        for (i = 0; i < t->n_sort_map; i++) {
-                TableData *d, *dd;
-
-                d = t->data[*a + t->sort_map[i]];
-                dd = t->data[*b + t->sort_map[i]];
-
-                r = cell_data_compare(d, *a, dd, *b);
-                if (r != 0)
-                        return r;
-        }
-
-        /* Order identical lines by the order there were originally added in */
-        return CMP(*a, *b);
-}
-
-static const char *table_data_format(TableData *d) {
-        assert(d);
-
-        if (d->formatted)
-                return d->formatted;
-
-        switch (d->type) {
-        case TABLE_EMPTY:
-                return "";
-
-        case TABLE_STRING:
-                return d->string;
-
-        case TABLE_BOOLEAN:
-                return yes_no(d->boolean);
-
-        case TABLE_TIMESTAMP: {
-                _cleanup_free_ char *p;
-
-                p = new(char, FORMAT_TIMESTAMP_MAX);
-                if (!p)
-                        return NULL;
-
-                if (!format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp))
-                        return "n/a";
-
-                d->formatted = TAKE_PTR(p);
-                break;
-        }
-
-        case TABLE_TIMESPAN: {
-                _cleanup_free_ char *p;
-
-                p = new(char, FORMAT_TIMESPAN_MAX);
-                if (!p)
-                        return NULL;
-
-                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timestamp, 0))
-                        return "n/a";
-
-                d->formatted = TAKE_PTR(p);
-                break;
-        }
-
-        case TABLE_SIZE: {
-                _cleanup_free_ char *p;
-
-                p = new(char, FORMAT_BYTES_MAX);
-                if (!p)
-                        return NULL;
-
-                if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
-                        return "n/a";
-
-                d->formatted = TAKE_PTR(p);
-                break;
-        }
-
-        case TABLE_UINT32: {
-                _cleanup_free_ char *p;
-
-                p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
-                if (!p)
-                        return NULL;
-
-                sprintf(p, "%" PRIu32, d->uint32);
-                d->formatted = TAKE_PTR(p);
-                break;
-        }
-
-        default:
-                assert_not_reached("Unexpected type?");
-        }
-
-        return d->formatted;
-}
-
-static int table_data_requested_width(TableData *d, size_t *ret) {
-        const char *t;
-        size_t l;
-
-        t = table_data_format(d);
-        if (!t)
-                return -ENOMEM;
-
-        l = utf8_console_width(t);
-        if (l == (size_t) -1)
-                return -EINVAL;
-
-        if (d->maximum_width != (size_t) -1 && l > d->maximum_width)
-                l = d->maximum_width;
-
-        if (l < d->minimum_width)
-                l = d->minimum_width;
-
-        *ret = l;
-        return 0;
-}
-
-static char *align_string_mem(const char *str, size_t new_length, unsigned percent) {
-        size_t w = 0, space, lspace, old_length;
-        const char *p;
-        char *ret;
-        size_t i;
-
-        /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
-
-        assert(str);
-        assert(percent <= 100);
-
-        old_length = strlen(str);
-
-        /* Determine current width on screen */
-        p = str;
-        while (p < str + old_length) {
-                char32_t c;
-
-                if (utf8_encoded_to_unichar(p, &c) < 0) {
-                        p++, w++; /* count invalid chars as 1 */
-                        continue;
-                }
-
-                p = utf8_next_char(p);
-                w += unichar_iswide(c) ? 2 : 1;
-        }
-
-        /* Already wider than the target, if so, don't do anything */
-        if (w >= new_length)
-                return strndup(str, old_length);
-
-        /* How much spaces shall we add? An how much on the left side? */
-        space = new_length - w;
-        lspace = space * percent / 100U;
-
-        ret = new(char, space + old_length + 1);
-        if (!ret)
-                return NULL;
-
-        for (i = 0; i < lspace; i++)
-                ret[i] = ' ';
-        memcpy(ret + lspace, str, old_length);
-        for (i = lspace + old_length; i < space + old_length; i++)
-                ret[i] = ' ';
-
-        ret[space + old_length] = 0;
-        return ret;
-}
-
-int table_print(Table *t, FILE *f) {
-        size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
-                i, j, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
-                *width;
-        _cleanup_free_ size_t *sorted = NULL;
-        uint64_t *column_weight, weight_sum;
-        int r;
-
-        assert(t);
-
-        if (!f)
-                f = stdout;
-
-        /* Ensure we have no incomplete rows */
-        assert(t->n_cells % t->n_columns == 0);
-
-        n_rows = t->n_cells / t->n_columns;
-        assert(n_rows > 0); /* at least the header row must be complete */
-
-        if (t->sort_map) {
-                /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
-
-                sorted = new(size_t, n_rows);
-                if (!sorted)
-                        return -ENOMEM;
-
-                for (i = 0; i < n_rows; i++)
-                        sorted[i] = i * t->n_columns;
-
-                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
-        }
-
-        if (t->display_map)
-                display_columns = t->n_display_map;
-        else
-                display_columns = t->n_columns;
-
-        assert(display_columns > 0);
-
-        minimum_width = newa(size_t, display_columns);
-        maximum_width = newa(size_t, display_columns);
-        requested_width = newa(size_t, display_columns);
-        width = newa(size_t, display_columns);
-        column_weight = newa0(uint64_t, display_columns);
-
-        for (j = 0; j < display_columns; j++) {
-                minimum_width[j] = 1;
-                maximum_width[j] = (size_t) -1;
-                requested_width[j] = (size_t) -1;
-        }
-
-        /* First pass: determine column sizes */
-        for (i = t->header ? 0 : 1; i < n_rows; i++) {
-                TableData **row;
-
-                /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
-                 * hence we don't care for sorted[] during the first pass. */
-                row = t->data + i * t->n_columns;
-
-                for (j = 0; j < display_columns; j++) {
-                        TableData *d;
-                        size_t req;
-
-                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
-
-                        r = table_data_requested_width(d, &req);
-                        if (r < 0)
-                                return r;
-
-                        /* Determine the biggest width that any cell in this column would like to have */
-                        if (requested_width[j] == (size_t) -1 ||
-                            requested_width[j] < req)
-                                requested_width[j] = req;
-
-                        /* Determine the minimum width any cell in this column needs */
-                        if (minimum_width[j] < d->minimum_width)
-                                minimum_width[j] = d->minimum_width;
-
-                        /* Determine the maximum width any cell in this column needs */
-                        if (d->maximum_width != (size_t) -1 &&
-                            (maximum_width[j] == (size_t) -1 ||
-                             maximum_width[j] > d->maximum_width))
-                                maximum_width[j] = d->maximum_width;
-
-                        /* Determine the full columns weight */
-                        column_weight[j] += d->weight;
-                }
-        }
-
-        /* One space between each column */
-        table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
-
-        /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
-        weight_sum = 0;
-        for (j = 0; j < display_columns; j++) {
-                weight_sum += column_weight[j];
-
-                table_minimum_width += minimum_width[j];
-
-                if (maximum_width[j] == (size_t) -1)
-                        table_maximum_width = (size_t) -1;
-                else
-                        table_maximum_width += maximum_width[j];
-
-                table_requested_width += requested_width[j];
-        }
-
-        /* Calculate effective table width */
-        if (t->width == (size_t) -1)
-                table_effective_width = pager_have() ? table_requested_width : MIN(table_requested_width, columns());
-        else
-                table_effective_width = t->width;
-
-        if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
-                table_effective_width = table_maximum_width;
-
-        if (table_effective_width < table_minimum_width)
-                table_effective_width = table_minimum_width;
-
-        if (table_effective_width >= table_requested_width) {
-                size_t extra;
-
-                /* We have extra room, let's distribute it among columns according to their weights. We first provide
-                 * each column with what it asked for and the distribute the rest.  */
-
-                extra = table_effective_width - table_requested_width;
-
-                for (j = 0; j < display_columns; j++) {
-                        size_t delta;
-
-                        if (weight_sum == 0)
-                                width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
-                        else
-                                width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
-
-                        if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
-                                width[j] = maximum_width[j];
-
-                        if (width[j] < minimum_width[j])
-                                width[j] = minimum_width[j];
-
-                        assert(width[j] >= requested_width[j]);
-                        delta = width[j] - requested_width[j];
-
-                        /* Subtract what we just added from the rest */
-                        if (extra > delta)
-                                extra -= delta;
-                        else
-                                extra = 0;
-
-                        assert(weight_sum >= column_weight[j]);
-                        weight_sum -= column_weight[j];
-                }
-
-        } else {
-                /* We need to compress the table, columns can't get what they asked for. We first provide each column
-                 * with the minimum they need, and then distribute anything left. */
-                bool finalize = false;
-                size_t extra;
-
-                extra = table_effective_width - table_minimum_width;
-
-                for (j = 0; j < display_columns; j++)
-                        width[j] = (size_t) -1;
-
-                for (;;) {
-                        bool restart = false;
-
-                        for (j = 0; j < display_columns; j++) {
-                                size_t delta, w;
-
-                                /* Did this column already get something assigned? If so, let's skip to the next */
-                                if (width[j] != (size_t) -1)
-                                        continue;
-
-                                if (weight_sum == 0)
-                                        w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
-                                else
-                                        w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
-
-                                if (w >= requested_width[j]) {
-                                        /* Never give more than requested. If we hit a column like this, there's more
-                                         * space to allocate to other columns which means we need to restart the
-                                         * iteration. However, if we hit a column like this, let's assign it the space
-                                         * it wanted for good early.*/
-
-                                        w = requested_width[j];
-                                        restart = true;
-
-                                } else if (!finalize)
-                                        continue;
-
-                                width[j] = w;
-
-                                assert(w >= minimum_width[j]);
-                                delta = w - minimum_width[j];
-
-                                assert(delta <= extra);
-                                extra -= delta;
-
-                                assert(weight_sum >= column_weight[j]);
-                                weight_sum -= column_weight[j];
-
-                                if (restart && !finalize)
-                                        break;
-                        }
-
-                        if (finalize)
-                                break;
-
-                        if (!restart)
-                                finalize = true;
-                }
-        }
-
-        /* Second pass: show output */
-        for (i = t->header ? 0 : 1; i < n_rows; i++) {
-                TableData **row;
-
-                if (sorted)
-                        row = t->data + sorted[i];
-                else
-                        row = t->data + i * t->n_columns;
-
-                for (j = 0; j < display_columns; j++) {
-                        _cleanup_free_ char *buffer = NULL;
-                        const char *field;
-                        TableData *d;
-                        size_t l;
-
-                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
-
-                        field = table_data_format(d);
-                        if (!field)
-                                return -ENOMEM;
-
-                        l = utf8_console_width(field);
-                        if (l > width[j]) {
-                                /* Field is wider than allocated space. Let's ellipsize */
-
-                                buffer = ellipsize(field, width[j], d->ellipsize_percent);
-                                if (!buffer)
-                                        return -ENOMEM;
-
-                                field = buffer;
-
-                        } else if (l < width[j]) {
-                                /* Field is shorter than allocated space. Let's align with spaces */
-
-                                buffer = align_string_mem(field, width[j], d->align_percent);
-                                if (!buffer)
-                                        return -ENOMEM;
-
-                                field = buffer;
-                        }
-
-                        if (j > 0)
-                                fputc(' ', f); /* column separator */
-
-                        if (d->color)
-                                fputs(d->color, f);
-
-                        fputs(field, f);
-
-                        if (d->color)
-                                fputs(ansi_normal(), f);
-                }
-
-                fputc('\n', f);
-        }
-
-        return fflush_and_check(f);
-}
-
-int table_format(Table *t, char **ret) {
-        _cleanup_fclose_ FILE *f = NULL;
-        char *buf = NULL;
-        size_t sz = 0;
-        int r;
-
-        f = open_memstream(&buf, &sz);
-        if (!f)
-                return -ENOMEM;
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
-        r = table_print(t, f);
-        if (r < 0)
-                return r;
-
-        f = safe_fclose(f);
-
-        *ret = buf;
-
-        return 0;
-}
-
-size_t table_get_rows(Table *t) {
-        if (!t)
-                return 0;
-
-        assert(t->n_columns > 0);
-        return t->n_cells / t->n_columns;
-}
-
-size_t table_get_columns(Table *t) {
-        if (!t)
-                return 0;
-
-        assert(t->n_columns > 0);
-        return t->n_columns;
-}
diff --git a/src/basic/format-table.h b/src/basic/format-table.h
deleted file mode 100644 (file)
index 6dc2d16..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-#include <stdio.h>
-#include <sys/types.h>
-
-#include "macro.h"
-
-typedef enum TableDataType {
-        TABLE_EMPTY,
-        TABLE_STRING,
-        TABLE_BOOLEAN,
-        TABLE_TIMESTAMP,
-        TABLE_TIMESPAN,
-        TABLE_SIZE,
-        TABLE_UINT32,
-        _TABLE_DATA_TYPE_MAX,
-        _TABLE_DATA_TYPE_INVALID = -1,
-} TableDataType;
-
-typedef struct Table Table;
-typedef struct TableCell TableCell;
-
-Table *table_new_internal(const char *first_header, ...) _sentinel_;
-#define table_new(...) table_new_internal(__VA_ARGS__, NULL)
-Table *table_new_raw(size_t n_columns);
-Table *table_unref(Table *t);
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(Table*, table_unref);
-
-int table_add_cell_full(Table *t, TableCell **ret_cell, TableDataType type, const void *data, size_t minimum_width, size_t maximum_width, unsigned weight, unsigned align_percent, unsigned ellipsize_percent);
-static inline int table_add_cell(Table *t, TableCell **ret_cell, TableDataType type, const void *data) {
-        return table_add_cell_full(t, ret_cell, type, data, (size_t) -1, (size_t) -1, (unsigned) -1, (unsigned) -1, (unsigned) -1);
-}
-
-int table_dup_cell(Table *t, TableCell *cell);
-
-int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width);
-int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width);
-int table_set_weight(Table *t, TableCell *cell, unsigned weight);
-int table_set_align_percent(Table *t, TableCell *cell, unsigned percent);
-int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent);
-int table_set_color(Table *t, TableCell *cell, const char *color);
-
-int table_add_many_internal(Table *t, TableDataType first_type, ...);
-#define table_add_many(t, ...) table_add_many_internal(t, __VA_ARGS__, _TABLE_DATA_TYPE_MAX)
-
-void table_set_header(Table *table, bool b);
-void table_set_width(Table *t, size_t width);
-int table_set_display(Table *t, size_t first_column, ...);
-int table_set_sort(Table *t, size_t first_column, ...);
-
-int table_print(Table *t, FILE *f);
-int table_format(Table *t, char **ret);
-
-static inline TableCell* TABLE_HEADER_CELL(size_t i) {
-        return SIZE_TO_PTR(i + 1);
-}
-
-size_t table_get_rows(Table *t);
-size_t table_get_columns(Table *t);
diff --git a/src/basic/journal-importer.c b/src/basic/journal-importer.c
deleted file mode 100644 (file)
index ca203bb..0000000
+++ /dev/null
@@ -1,502 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <errno.h>
-#include <unistd.h>
-
-#include "alloc-util.h"
-#include "escape.h"
-#include "fd-util.h"
-#include "io-util.h"
-#include "journal-file.h"
-#include "journal-importer.h"
-#include "journal-util.h"
-#include "parse-util.h"
-#include "string-util.h"
-#include "unaligned.h"
-
-enum {
-        IMPORTER_STATE_LINE = 0,    /* waiting to read, or reading line */
-        IMPORTER_STATE_DATA_START,  /* reading binary data header */
-        IMPORTER_STATE_DATA,        /* reading binary data */
-        IMPORTER_STATE_DATA_FINISH, /* expecting newline */
-        IMPORTER_STATE_EOF,         /* done */
-};
-
-static int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len) {
-        if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1))
-                return log_oom();
-
-        iovw->iovec[iovw->count++] = IOVEC_MAKE(data, len);
-        return 0;
-}
-
-static void iovw_free_contents(struct iovec_wrapper *iovw) {
-        iovw->iovec = mfree(iovw->iovec);
-        iovw->size_bytes = iovw->count = 0;
-}
-
-static void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new) {
-        size_t i;
-
-        for (i = 0; i < iovw->count; i++)
-                iovw->iovec[i].iov_base = (char*) iovw->iovec[i].iov_base - old + new;
-}
-
-size_t iovw_size(struct iovec_wrapper *iovw) {
-        size_t n = 0, i;
-
-        for (i = 0; i < iovw->count; i++)
-                n += iovw->iovec[i].iov_len;
-
-        return n;
-}
-
-void journal_importer_cleanup(JournalImporter *imp) {
-        if (imp->fd >= 0 && !imp->passive_fd) {
-                log_debug("Closing %s (fd=%d)", imp->name ?: "importer", imp->fd);
-                safe_close(imp->fd);
-        }
-
-        free(imp->name);
-        free(imp->buf);
-        iovw_free_contents(&imp->iovw);
-}
-
-static char* realloc_buffer(JournalImporter *imp, size_t size) {
-        char *b, *old = imp->buf;
-
-        b = GREEDY_REALLOC(imp->buf, imp->size, size);
-        if (!b)
-                return NULL;
-
-        iovw_rebase(&imp->iovw, old, imp->buf);
-
-        return b;
-}
-
-static int get_line(JournalImporter *imp, char **line, size_t *size) {
-        ssize_t n;
-        char *c = NULL;
-
-        assert(imp);
-        assert(imp->state == IMPORTER_STATE_LINE);
-        assert(imp->offset <= imp->filled);
-        assert(imp->filled <= imp->size);
-        assert(!imp->buf || imp->size > 0);
-        assert(imp->fd >= 0);
-
-        for (;;) {
-                if (imp->buf) {
-                        size_t start = MAX(imp->scanned, imp->offset);
-
-                        c = memchr(imp->buf + start, '\n',
-                                   imp->filled - start);
-                        if (c != NULL)
-                                break;
-                }
-
-                imp->scanned = imp->filled;
-                if (imp->scanned >= DATA_SIZE_MAX) {
-                        log_error("Entry is bigger than %u bytes.", DATA_SIZE_MAX);
-                        return -E2BIG;
-                }
-
-                if (imp->passive_fd)
-                        /* we have to wait for some data to come to us */
-                        return -EAGAIN;
-
-                /* We know that imp->filled is at most DATA_SIZE_MAX, so if
-                   we reallocate it, we'll increase the size at least a bit. */
-                assert_cc(DATA_SIZE_MAX < ENTRY_SIZE_MAX);
-                if (imp->size - imp->filled < LINE_CHUNK &&
-                    !realloc_buffer(imp, MIN(imp->filled + LINE_CHUNK, ENTRY_SIZE_MAX)))
-                                return log_oom();
-
-                assert(imp->buf);
-                assert(imp->size - imp->filled >= LINE_CHUNK ||
-                       imp->size == ENTRY_SIZE_MAX);
-
-                n = read(imp->fd,
-                         imp->buf + imp->filled,
-                         imp->size - imp->filled);
-                if (n < 0) {
-                        if (errno != EAGAIN)
-                                log_error_errno(errno, "read(%d, ..., %zu): %m",
-                                                imp->fd,
-                                                imp->size - imp->filled);
-                        return -errno;
-                } else if (n == 0)
-                        return 0;
-
-                imp->filled += n;
-        }
-
-        *line = imp->buf + imp->offset;
-        *size = c + 1 - imp->buf - imp->offset;
-        imp->offset += *size;
-
-        return 1;
-}
-
-static int fill_fixed_size(JournalImporter *imp, void **data, size_t size) {
-
-        assert(imp);
-        assert(IN_SET(imp->state, IMPORTER_STATE_DATA_START, IMPORTER_STATE_DATA, IMPORTER_STATE_DATA_FINISH));
-        assert(size <= DATA_SIZE_MAX);
-        assert(imp->offset <= imp->filled);
-        assert(imp->filled <= imp->size);
-        assert(imp->buf || imp->size == 0);
-        assert(!imp->buf || imp->size > 0);
-        assert(imp->fd >= 0);
-        assert(data);
-
-        while (imp->filled - imp->offset < size) {
-                int n;
-
-                if (imp->passive_fd)
-                        /* we have to wait for some data to come to us */
-                        return -EAGAIN;
-
-                if (!realloc_buffer(imp, imp->offset + size))
-                        return log_oom();
-
-                n = read(imp->fd, imp->buf + imp->filled,
-                         imp->size - imp->filled);
-                if (n < 0) {
-                        if (errno != EAGAIN)
-                                log_error_errno(errno, "read(%d, ..., %zu): %m", imp->fd,
-                                                imp->size - imp->filled);
-                        return -errno;
-                } else if (n == 0)
-                        return 0;
-
-                imp->filled += n;
-        }
-
-        *data = imp->buf + imp->offset;
-        imp->offset += size;
-
-        return 1;
-}
-
-static int get_data_size(JournalImporter *imp) {
-        int r;
-        void *data;
-
-        assert(imp);
-        assert(imp->state == IMPORTER_STATE_DATA_START);
-        assert(imp->data_size == 0);
-
-        r = fill_fixed_size(imp, &data, sizeof(uint64_t));
-        if (r <= 0)
-                return r;
-
-        imp->data_size = unaligned_read_le64(data);
-        if (imp->data_size > DATA_SIZE_MAX) {
-                log_error("Stream declares field with size %zu > DATA_SIZE_MAX = %u",
-                          imp->data_size, DATA_SIZE_MAX);
-                return -EINVAL;
-        }
-        if (imp->data_size == 0)
-                log_warning("Binary field with zero length");
-
-        return 1;
-}
-
-static int get_data_data(JournalImporter *imp, void **data) {
-        int r;
-
-        assert(imp);
-        assert(data);
-        assert(imp->state == IMPORTER_STATE_DATA);
-
-        r = fill_fixed_size(imp, data, imp->data_size);
-        if (r <= 0)
-                return r;
-
-        return 1;
-}
-
-static int get_data_newline(JournalImporter *imp) {
-        int r;
-        char *data;
-
-        assert(imp);
-        assert(imp->state == IMPORTER_STATE_DATA_FINISH);
-
-        r = fill_fixed_size(imp, (void**) &data, 1);
-        if (r <= 0)
-                return r;
-
-        assert(data);
-        if (*data != '\n') {
-                char buf[4];
-                int l;
-
-                l = cescape_char(*data, buf);
-                log_error("Expected newline, got '%.*s'", l, buf);
-                return -EINVAL;
-        }
-
-        return 1;
-}
-
-static int process_special_field(JournalImporter *imp, char *line) {
-        const char *value;
-        char buf[CELLESCAPE_DEFAULT_LENGTH];
-        int r;
-
-        assert(line);
-
-        value = startswith(line, "__CURSOR=");
-        if (value)
-                /* ignore __CURSOR */
-                return 1;
-
-        value = startswith(line, "__REALTIME_TIMESTAMP=");
-        if (value) {
-                uint64_t x;
-
-                r = safe_atou64(value, &x);
-                if (r < 0)
-                        return log_warning_errno(r, "Failed to parse __REALTIME_TIMESTAMP '%s': %m",
-                                                 cellescape(buf, sizeof buf, value));
-                else if (!VALID_REALTIME(x)) {
-                        log_warning("__REALTIME_TIMESTAMP out of range, ignoring: %"PRIu64, x);
-                        return -ERANGE;
-                }
-
-                imp->ts.realtime = x;
-                return 1;
-        }
-
-        value = startswith(line, "__MONOTONIC_TIMESTAMP=");
-        if (value) {
-                uint64_t x;
-
-                r = safe_atou64(value, &x);
-                if (r < 0)
-                        return log_warning_errno(r, "Failed to parse __MONOTONIC_TIMESTAMP '%s': %m",
-                                                 cellescape(buf, sizeof buf, value));
-                else if (!VALID_MONOTONIC(x)) {
-                        log_warning("__MONOTONIC_TIMESTAMP out of range, ignoring: %"PRIu64, x);
-                        return -ERANGE;
-                }
-
-                imp->ts.monotonic = x;
-                return 1;
-        }
-
-        /* Just a single underline, but it needs special treatment too. */
-        value = startswith(line, "_BOOT_ID=");
-        if (value) {
-                r = sd_id128_from_string(value, &imp->boot_id);
-                if (r < 0)
-                        return log_warning_errno(r, "Failed to parse _BOOT_ID '%s': %m",
-                                                 cellescape(buf, sizeof buf, value));
-
-                /* store the field in the usual fashion too */
-                return 0;
-        }
-
-        value = startswith(line, "__");
-        if (value) {
-                log_notice("Unknown dunder line __%s, ignoring.", cellescape(buf, sizeof buf, value));
-                return 1;
-        }
-
-        /* no dunder */
-        return 0;
-}
-
-int journal_importer_process_data(JournalImporter *imp) {
-        int r;
-
-        switch(imp->state) {
-        case IMPORTER_STATE_LINE: {
-                char *line, *sep;
-                size_t n = 0;
-
-                assert(imp->data_size == 0);
-
-                r = get_line(imp, &line, &n);
-                if (r < 0)
-                        return r;
-                if (r == 0) {
-                        imp->state = IMPORTER_STATE_EOF;
-                        return 0;
-                }
-                assert(n > 0);
-                assert(line[n-1] == '\n');
-
-                if (n == 1) {
-                        log_trace("Received empty line, event is ready");
-                        return 1;
-                }
-
-                /* MESSAGE=xxx\n
-                   or
-                   COREDUMP\n
-                   LLLLLLLL0011223344...\n
-                */
-                sep = memchr(line, '=', n);
-                if (sep) {
-                        /* chomp newline */
-                        n--;
-
-                        if (!journal_field_valid(line, sep - line, true)) {
-                                char buf[64], *t;
-
-                                t = strndupa(line, sep - line);
-                                log_debug("Ignoring invalid field: \"%s\"",
-                                          cellescape(buf, sizeof buf, t));
-
-                                return 0;
-                        }
-
-                        line[n] = '\0';
-                        r = process_special_field(imp, line);
-                        if (r != 0)
-                                return r < 0 ? r : 0;
-
-                        r = iovw_put(&imp->iovw, line, n);
-                        if (r < 0)
-                                return r;
-                } else {
-                        /* replace \n with = */
-                        line[n-1] = '=';
-
-                        imp->field_len = n;
-                        imp->state = IMPORTER_STATE_DATA_START;
-
-                        /* we cannot put the field in iovec until we have all data */
-                }
-
-                log_trace("Received: %.*s (%s)", (int) n, line, sep ? "text" : "binary");
-
-                return 0; /* continue */
-        }
-
-        case IMPORTER_STATE_DATA_START:
-                assert(imp->data_size == 0);
-
-                r = get_data_size(imp);
-                // log_debug("get_data_size() -> %d", r);
-                if (r < 0)
-                        return r;
-                if (r == 0) {
-                        imp->state = IMPORTER_STATE_EOF;
-                        return 0;
-                }
-
-                imp->state = imp->data_size > 0 ?
-                        IMPORTER_STATE_DATA : IMPORTER_STATE_DATA_FINISH;
-
-                return 0; /* continue */
-
-        case IMPORTER_STATE_DATA: {
-                void *data;
-                char *field;
-
-                assert(imp->data_size > 0);
-
-                r = get_data_data(imp, &data);
-                // log_debug("get_data_data() -> %d", r);
-                if (r < 0)
-                        return r;
-                if (r == 0) {
-                        imp->state = IMPORTER_STATE_EOF;
-                        return 0;
-                }
-
-                assert(data);
-
-                field = (char*) data - sizeof(uint64_t) - imp->field_len;
-                memmove(field + sizeof(uint64_t), field, imp->field_len);
-
-                r = iovw_put(&imp->iovw, field + sizeof(uint64_t), imp->field_len + imp->data_size);
-                if (r < 0)
-                        return r;
-
-                imp->state = IMPORTER_STATE_DATA_FINISH;
-
-                return 0; /* continue */
-        }
-
-        case IMPORTER_STATE_DATA_FINISH:
-                r = get_data_newline(imp);
-                // log_debug("get_data_newline() -> %d", r);
-                if (r < 0)
-                        return r;
-                if (r == 0) {
-                        imp->state = IMPORTER_STATE_EOF;
-                        return 0;
-                }
-
-                imp->data_size = 0;
-                imp->state = IMPORTER_STATE_LINE;
-
-                return 0; /* continue */
-        default:
-                assert_not_reached("wtf?");
-        }
-}
-
-int journal_importer_push_data(JournalImporter *imp, const char *data, size_t size) {
-        assert(imp);
-        assert(imp->state != IMPORTER_STATE_EOF);
-
-        if (!realloc_buffer(imp, imp->filled + size)) {
-                log_error("Failed to store received data of size %zu "
-                          "(in addition to existing %zu bytes with %zu filled): %s",
-                          size, imp->size, imp->filled, strerror(ENOMEM));
-                return -ENOMEM;
-        }
-
-        memcpy(imp->buf + imp->filled, data, size);
-        imp->filled += size;
-
-        return 0;
-}
-
-void journal_importer_drop_iovw(JournalImporter *imp) {
-        size_t remain, target;
-
-        /* This function drops processed data that along with the iovw that points at it */
-
-        iovw_free_contents(&imp->iovw);
-
-        /* possibly reset buffer position */
-        remain = imp->filled - imp->offset;
-
-        if (remain == 0) /* no brainer */
-                imp->offset = imp->scanned = imp->filled = 0;
-        else if (imp->offset > imp->size - imp->filled &&
-                 imp->offset > remain) {
-                memcpy(imp->buf, imp->buf + imp->offset, remain);
-                imp->offset = imp->scanned = 0;
-                imp->filled = remain;
-        }
-
-        target = imp->size;
-        while (target > 16 * LINE_CHUNK && imp->filled < target / 2)
-                target /= 2;
-        if (target < imp->size) {
-                char *tmp;
-
-                tmp = realloc(imp->buf, target);
-                if (!tmp)
-                        log_warning("Failed to reallocate buffer to (smaller) size %zu",
-                                    target);
-                else {
-                        log_debug("Reallocated buffer from %zu to %zu bytes",
-                                  imp->size, target);
-                        imp->buf = tmp;
-                        imp->size = target;
-                }
-        }
-}
-
-bool journal_importer_eof(const JournalImporter *imp) {
-        return imp->state == IMPORTER_STATE_EOF;
-}
diff --git a/src/basic/journal-importer.h b/src/basic/journal-importer.h
deleted file mode 100644 (file)
index 53354b7..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#pragma once
-
-#include <stddef.h>
-#include <stdbool.h>
-#include <sys/uio.h>
-
-#include "sd-id128.h"
-
-#include "time-util.h"
-
-/* Make sure not to make this smaller than the maximum coredump size.
- * See JOURNAL_SIZE_MAX in coredump.c */
-#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
-#define ENTRY_SIZE_MAX (1024*1024*770u)
-#define DATA_SIZE_MAX (1024*1024*768u)
-#else
-#define ENTRY_SIZE_MAX (1024*1024*13u)
-#define DATA_SIZE_MAX (1024*1024*11u)
-#endif
-#define LINE_CHUNK 8*1024u
-
-struct iovec_wrapper {
-        struct iovec *iovec;
-        size_t size_bytes;
-        size_t count;
-};
-
-size_t iovw_size(struct iovec_wrapper *iovw);
-
-typedef struct JournalImporter {
-        int fd;
-        bool passive_fd;
-        char *name;
-
-        char *buf;
-        size_t size;       /* total size of the buffer */
-        size_t offset;     /* offset to the beginning of live data in the buffer */
-        size_t scanned;    /* number of bytes since the beginning of data without a newline */
-        size_t filled;     /* total number of bytes in the buffer */
-
-        size_t field_len;  /* used for binary fields: the field name length */
-        size_t data_size;  /* and the size of the binary data chunk being processed */
-
-        struct iovec_wrapper iovw;
-
-        int state;
-        dual_timestamp ts;
-        sd_id128_t boot_id;
-} JournalImporter;
-
-void journal_importer_cleanup(JournalImporter *);
-int journal_importer_process_data(JournalImporter *);
-int journal_importer_push_data(JournalImporter *, const char *data, size_t size);
-void journal_importer_drop_iovw(JournalImporter *);
-bool journal_importer_eof(const JournalImporter *);
-
-static inline size_t journal_importer_bytes_remaining(const JournalImporter *imp) {
-        return imp->filled;
-}
diff --git a/src/basic/json-internal.h b/src/basic/json-internal.h
deleted file mode 100644 (file)
index bf158bf..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-/* 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)
-#define _JSON_VARIANT_MAGIC_MAX ((JsonVariant*) 10)
-
-/* This is only safe as long as we don't define more than 4K magic pointers, i.e. the page size of the simplest
- * architectures we support. That's because we rely on the fact that malloc() will never allocate from the first memory
- * page, as it is a faulting page for catching NULL pointer dereferences. */
-assert_cc((uintptr_t) _JSON_VARIANT_MAGIC_MAX < 4096U);
-
-enum { /* JSON tokens */
-        JSON_TOKEN_END,
-        JSON_TOKEN_COLON,
-        JSON_TOKEN_COMMA,
-        JSON_TOKEN_OBJECT_OPEN,
-        JSON_TOKEN_OBJECT_CLOSE,
-        JSON_TOKEN_ARRAY_OPEN,
-        JSON_TOKEN_ARRAY_CLOSE,
-        JSON_TOKEN_STRING,
-        JSON_TOKEN_REAL,
-        JSON_TOKEN_INTEGER,
-        JSON_TOKEN_UNSIGNED,
-        JSON_TOKEN_BOOLEAN,
-        JSON_TOKEN_NULL,
-        _JSON_TOKEN_MAX,
-        _JSON_TOKEN_INVALID = -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
deleted file mode 100644 (file)
index eec6ea7..0000000
+++ /dev/null
@@ -1,3385 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <errno.h>
-#include <math.h>
-#include <stdarg.h>
-#include <stdio_ext.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-
-#include "sd-messages.h"
-
-#include "alloc-util.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "float.h"
-#include "hexdecoct.h"
-#include "json-internal.h"
-#include "json.h"
-#include "macro.h"
-#include "string-table.h"
-#include "string-util.h"
-#include "strv.h"
-#include "terminal-util.h"
-#include "utf8.h"
-
-/* Refuse putting together variants with a larger depth than 4K by default (as a protection against overflowing stacks
- * if code processes JSON objects recursively. Note that we store the depth in an uint16_t, hence make sure this
- * remains under 2^16.
- * The value was 16k, but it was discovered to be too high on llvm/x86-64. See also the issue #10738. */
-#define DEPTH_MAX (4U*1024U)
-assert_cc(DEPTH_MAX <= UINT16_MAX);
-
-typedef struct JsonSource {
-        /* When we parse from a file or similar, encodes the filename, to indicate the source of a json variant */
-        size_t n_ref;
-        unsigned max_line;
-        unsigned max_column;
-        char name[];
-} JsonSource;
-
-/* On x86-64 this whole structure should have a size of 6 * 64 bit = 48 bytes */
-struct JsonVariant {
-        union {
-                /* We either maintain a reference counter for this variant itself, or we are embedded into an
-                 * array/object, in which case only that surrounding object is ref-counted. (If 'embedded' is false,
-                 * see below.) */
-                size_t n_ref;
-
-                /* If this JsonVariant is part of an array/object, then this field points to the surrounding
-                 * JSON_VARIANT_ARRAY/JSON_VARIANT_OBJECT object. (If 'embedded' is true, see below.) */
-                JsonVariant *parent;
-        };
-
-        /* If this was parsed from some file or buffer, this stores where from, as well as the source line/column */
-        JsonSource *source;
-        unsigned line, column;
-
-        JsonVariantType type:5;
-
-        /* A marker whether this variant is embedded into in array/object or not. If true, the 'parent' pointer above
-         * is valid. If false, the 'n_ref' field above is valid instead. */
-        bool is_embedded:1;
-
-        /* In some conditions (for example, if this object is part of an array of strings or objects), we don't store
-         * any data inline, but instead simply reference an external object and act as surrogate of it. In that case
-         * this bool is set, and the external object is referenced through the .reference field below. */
-        bool is_reference:1;
-
-        /* While comparing two arrays, we use this for marking what we already have seen */
-        bool is_marked:1;
-
-        /* The current 'depth' of the JsonVariant, i.e. how many levels of member variants this has */
-        uint16_t depth;
-
-        union {
-                /* For simple types we store the value in-line. */
-                JsonValue value;
-
-                /* For objects and arrays we store the number of elements immediately following */
-                size_t n_elements;
-
-                /* If is_reference as indicated above is set, this is where the reference object is actually stored. */
-                JsonVariant *reference;
-
-                /* Strings are placed immediately after the structure. Note that when this is a JsonVariant embedded
-                 * into an array we might encode strings up to INLINE_STRING_LENGTH characters directly inside the
-                 * element, while longer strings are stored as references. When this object is not embedded into an
-                 * array, but stand-alone we allocate the right size for the whole structure, i.e. the array might be
-                 * much larger than INLINE_STRING_LENGTH.
-                 *
-                 * Note that because we want to allocate arrays of the JsonVariant structure we specify [0] here,
-                 * rather than the prettier []. If we wouldn't, then this char array would have undefined size, and so
-                 * would the union and then the struct this is included in. And of structures with undefined size we
-                 * can't allocate arrays (at least not easily). */
-                char string[0];
-        };
-};
-
-/* Inside string arrays we have a series of JasonVariant structures one after the other. In this case, strings longer
- * than INLINE_STRING_MAX are stored as references, and all shorter ones inline. (This means — on x86-64 — strings up
- * to 15 chars are stored within the array elements, and all others in separate allocations) */
-#define INLINE_STRING_MAX (sizeof(JsonVariant) - offsetof(JsonVariant, string) - 1U)
-
-/* Let's make sure this structure isn't increased in size accidentally. This check is only for our most relevant arch
- * (x86-64). */
-#ifdef __x86_64__
-assert_cc(sizeof(JsonVariant) == 48U);
-assert_cc(INLINE_STRING_MAX == 15U);
-#endif
-
-static JsonSource* json_source_new(const char *name) {
-        JsonSource *s;
-
-        assert(name);
-
-        s = malloc(offsetof(JsonSource, name) + strlen(name) + 1);
-        if (!s)
-                return NULL;
-
-        *s = (JsonSource) {
-                .n_ref = 1,
-        };
-        strcpy(s->name, name);
-
-        return s;
-}
-
-DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(JsonSource, json_source, mfree);
-
-static bool json_source_equal(JsonSource *a, JsonSource *b) {
-        if (a == b)
-                return true;
-
-        if (!a || !b)
-                return false;
-
-        return streq(a->name, b->name);
-}
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(JsonSource*, json_source_unref);
-
-/* There are four kind of JsonVariant* pointers:
- *
- *    1. NULL
- *    2. A 'regular' one, i.e. pointing to malloc() memory
- *    3. A 'magic' one, i.e. one of the special JSON_VARIANT_MAGIC_XYZ values, that encode a few very basic values directly in the pointer.
- *    4. A 'const string' one, i.e. a pointer to a const string.
- *
- * The four kinds of pointers can be discerned like this:
- *
- *    Detecting #1 is easy, just compare with NULL. Detecting #3 is similarly easy: all magic pointers are below
- *    _JSON_VARIANT_MAGIC_MAX (which is pretty low, within the first memory page, which is special on Linux and other
- *    OSes, as it is a faulting page). In order to discern #2 and #4 we check the lowest bit. If it's off it's #2,
- *    otherwise #4. This makes use of the fact that malloc() will return "maximum aligned" memory, which definitely
- *    means the pointer is even. This means we can use the uneven pointers to reference static strings, as long as we
- *    make sure that all static strings used like this are aligned to 2 (or higher), and that we mask the bit on
- *    access. The JSON_VARIANT_STRING_CONST() macro encodes strings as JsonVariant* pointers, with the bit set. */
-
-static bool json_variant_is_magic(const JsonVariant *v) {
-        if (!v)
-                return false;
-
-        return v < _JSON_VARIANT_MAGIC_MAX;
-}
-
-static bool json_variant_is_const_string(const JsonVariant *v) {
-
-        if (v < _JSON_VARIANT_MAGIC_MAX)
-                return false;
-
-        /* A proper JsonVariant is aligned to whatever malloc() aligns things too, which is definitely not uneven. We
-         * hence use all uneven pointers as indicators for const strings. */
-
-        return (((uintptr_t) v) & 1) != 0;
-}
-
-static bool json_variant_is_regular(const JsonVariant *v) {
-
-        if (v < _JSON_VARIANT_MAGIC_MAX)
-                return false;
-
-        return (((uintptr_t) v) & 1) == 0;
-}
-
-static JsonVariant *json_variant_dereference(JsonVariant *v) {
-
-        /* Recursively dereference variants that are references to other variants */
-
-        if (!v)
-                return NULL;
-
-        if (!json_variant_is_regular(v))
-                return v;
-
-        if (!v->is_reference)
-                return v;
-
-        return json_variant_dereference(v->reference);
-}
-
-static uint16_t json_variant_depth(JsonVariant *v) {
-
-        v = json_variant_dereference(v);
-        if (!v)
-                return 0;
-
-        if (!json_variant_is_regular(v))
-                return 0;
-
-        return v->depth;
-}
-
-static JsonVariant *json_variant_normalize(JsonVariant *v) {
-
-        /* Converts json variants to their normalized form, i.e. fully dereferenced and wherever possible converted to
-         * the "magic" version if there is one */
-
-        if (!v)
-                return NULL;
-
-        v = json_variant_dereference(v);
-
-        switch (json_variant_type(v)) {
-
-        case JSON_VARIANT_BOOLEAN:
-                return json_variant_boolean(v) ? JSON_VARIANT_MAGIC_TRUE : JSON_VARIANT_MAGIC_FALSE;
-
-        case JSON_VARIANT_NULL:
-                return JSON_VARIANT_MAGIC_NULL;
-
-        case JSON_VARIANT_INTEGER:
-                return json_variant_integer(v) == 0 ? JSON_VARIANT_MAGIC_ZERO_INTEGER : v;
-
-        case JSON_VARIANT_UNSIGNED:
-                return json_variant_unsigned(v) == 0 ? JSON_VARIANT_MAGIC_ZERO_UNSIGNED : v;
-
-        case JSON_VARIANT_REAL:
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wfloat-equal"
-                return json_variant_real(v) == 0.0 ? JSON_VARIANT_MAGIC_ZERO_REAL : v;
-#pragma GCC diagnostic pop
-
-        case JSON_VARIANT_STRING:
-                return isempty(json_variant_string(v)) ? JSON_VARIANT_MAGIC_EMPTY_STRING : v;
-
-        case JSON_VARIANT_ARRAY:
-                return json_variant_elements(v) == 0 ? JSON_VARIANT_MAGIC_EMPTY_ARRAY : v;
-
-        case JSON_VARIANT_OBJECT:
-                return json_variant_elements(v) == 0 ? JSON_VARIANT_MAGIC_EMPTY_OBJECT : v;
-
-        default:
-                return v;
-        }
-}
-
-static JsonVariant *json_variant_conservative_normalize(JsonVariant *v) {
-
-        /* Much like json_variant_normalize(), but won't simplify if the variant has a source/line location attached to
-         * it, in order not to lose context */
-
-        if (!v)
-                return NULL;
-
-        if (!json_variant_is_regular(v))
-                return v;
-
-        if (v->source || v->line > 0 || v->column > 0)
-                return v;
-
-        return json_variant_normalize(v);
-}
-
-static int json_variant_new(JsonVariant **ret, JsonVariantType type, size_t space) {
-        JsonVariant *v;
-
-        assert_return(ret, -EINVAL);
-
-        v = malloc0(offsetof(JsonVariant, value) + space);
-        if (!v)
-                return -ENOMEM;
-
-        v->n_ref = 1;
-        v->type = type;
-
-        *ret = v;
-        return 0;
-}
-
-int json_variant_new_integer(JsonVariant **ret, intmax_t i) {
-        JsonVariant *v;
-        int r;
-
-        assert_return(ret, -EINVAL);
-
-        if (i == 0) {
-                *ret = JSON_VARIANT_MAGIC_ZERO_INTEGER;
-                return 0;
-        }
-
-        r = json_variant_new(&v, JSON_VARIANT_INTEGER, sizeof(i));
-        if (r < 0)
-                return r;
-
-        v->value.integer = i;
-        *ret = v;
-
-        return 0;
-}
-
-int json_variant_new_unsigned(JsonVariant **ret, uintmax_t u) {
-        JsonVariant *v;
-        int r;
-
-        assert_return(ret, -EINVAL);
-        if (u == 0) {
-                *ret = JSON_VARIANT_MAGIC_ZERO_UNSIGNED;
-                return 0;
-        }
-
-        r = json_variant_new(&v, JSON_VARIANT_UNSIGNED, sizeof(u));
-        if (r < 0)
-                return r;
-
-        v->value.unsig = u;
-        *ret = v;
-
-        return 0;
-}
-
-int json_variant_new_real(JsonVariant **ret, long double d) {
-        JsonVariant *v;
-        int r;
-
-        assert_return(ret, -EINVAL);
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wfloat-equal"
-        if (d == 0.0) {
-#pragma GCC diagnostic pop
-                *ret = JSON_VARIANT_MAGIC_ZERO_REAL;
-                return 0;
-        }
-
-        r = json_variant_new(&v, JSON_VARIANT_REAL, sizeof(d));
-        if (r < 0)
-                return r;
-
-        v->value.real = d;
-        *ret = v;
-
-        return 0;
-}
-
-int json_variant_new_boolean(JsonVariant **ret, bool b) {
-        assert_return(ret, -EINVAL);
-
-        if (b)
-                *ret = JSON_VARIANT_MAGIC_TRUE;
-        else
-                *ret = JSON_VARIANT_MAGIC_FALSE;
-
-        return 0;
-}
-
-int json_variant_new_null(JsonVariant **ret) {
-        assert_return(ret, -EINVAL);
-
-        *ret = JSON_VARIANT_MAGIC_NULL;
-        return 0;
-}
-
-int json_variant_new_stringn(JsonVariant **ret, const char *s, size_t n) {
-        JsonVariant *v;
-        int r;
-
-        assert_return(ret, -EINVAL);
-        if (!s) {
-                assert_return(n == 0, -EINVAL);
-                return json_variant_new_null(ret);
-        }
-        if (n == 0) {
-                *ret = JSON_VARIANT_MAGIC_EMPTY_STRING;
-                return 0;
-        }
-
-        r = json_variant_new(&v, JSON_VARIANT_STRING, n + 1);
-        if (r < 0)
-                return r;
-
-        memcpy(v->string, s, n);
-        v->string[n] = 0;
-
-        *ret = v;
-        return 0;
-}
-
-static void json_variant_set(JsonVariant *a, JsonVariant *b) {
-        assert(a);
-
-        b = json_variant_dereference(b);
-        if (!b) {
-                a->type = JSON_VARIANT_NULL;
-                return;
-        }
-
-        a->type = json_variant_type(b);
-        switch (a->type) {
-
-        case JSON_VARIANT_INTEGER:
-                a->value.integer = json_variant_integer(b);
-                break;
-
-        case JSON_VARIANT_UNSIGNED:
-                a->value.unsig = json_variant_unsigned(b);
-                break;
-
-        case JSON_VARIANT_REAL:
-                a->value.real = json_variant_real(b);
-                break;
-
-        case JSON_VARIANT_BOOLEAN:
-                a->value.boolean = json_variant_boolean(b);
-                break;
-
-        case JSON_VARIANT_STRING: {
-                const char *s;
-
-                assert_se(s = json_variant_string(b));
-
-                /* Short strings we can store inline */
-                if (strnlen(s, INLINE_STRING_MAX+1) <= INLINE_STRING_MAX) {
-                        strcpy(a->string, s);
-                        break;
-                }
-
-                /* For longer strings, use a reference… */
-                _fallthrough_;
-        }
-
-        case JSON_VARIANT_ARRAY:
-        case JSON_VARIANT_OBJECT:
-                a->is_reference = true;
-                a->reference = json_variant_ref(json_variant_conservative_normalize(b));
-                break;
-
-        case JSON_VARIANT_NULL:
-                break;
-
-        default:
-                assert_not_reached("Unexpected variant type");
-        }
-}
-
-static void json_variant_copy_source(JsonVariant *v, JsonVariant *from) {
-        assert(v);
-        assert(from);
-
-        if (!json_variant_is_regular(from))
-                return;
-
-        v->line = from->line;
-        v->column = from->column;
-        v->source = json_source_ref(from->source);
-}
-
-int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) {
-        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
-
-        assert_return(ret, -EINVAL);
-        if (n == 0) {
-                *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY;
-                return 0;
-        }
-        assert_return(array, -EINVAL);
-
-        v = new(JsonVariant, n + 1);
-        if (!v)
-                return -ENOMEM;
-
-        *v = (JsonVariant) {
-                .n_ref = 1,
-                .type = JSON_VARIANT_ARRAY,
-        };
-
-        for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
-                JsonVariant *w = v + 1 + v->n_elements,
-                        *c = array[v->n_elements];
-                uint16_t d;
-
-                d = json_variant_depth(c);
-                if (d >= DEPTH_MAX) /* Refuse too deep nesting */
-                        return -ELNRNG;
-                if (d >= v->depth)
-                        v->depth = d + 1;
-
-                *w = (JsonVariant) {
-                        .is_embedded = true,
-                        .parent = v,
-                };
-
-                json_variant_set(w, c);
-                json_variant_copy_source(w, c);
-        }
-
-        *ret = TAKE_PTR(v);
-        return 0;
-}
-
-int json_variant_new_array_bytes(JsonVariant **ret, const void *p, size_t n) {
-        JsonVariant *v;
-        size_t i;
-
-        assert_return(ret, -EINVAL);
-        if (n == 0) {
-                *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY;
-                return 0;
-        }
-        assert_return(p, -EINVAL);
-
-        v = new(JsonVariant, n + 1);
-        if (!v)
-                return -ENOMEM;
-
-        *v = (JsonVariant) {
-                .n_ref = 1,
-                .type = JSON_VARIANT_ARRAY,
-                .n_elements = n,
-                .depth = 1,
-        };
-
-        for (i = 0; i < n; i++) {
-                JsonVariant *w = v + 1 + i;
-
-                *w = (JsonVariant) {
-                        .is_embedded = true,
-                        .parent = v,
-                        .type = JSON_VARIANT_UNSIGNED,
-                        .value.unsig = ((const uint8_t*) p)[i],
-                };
-        }
-
-        *ret = v;
-        return 0;
-}
-
-int json_variant_new_array_strv(JsonVariant **ret, char **l) {
-        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
-        size_t n;
-        int r;
-
-        assert(ret);
-
-        n = strv_length(l);
-        if (n == 0) {
-                *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY;
-                return 0;
-        }
-
-        v = new(JsonVariant, n + 1);
-        if (!v)
-                return -ENOMEM;
-
-        *v = (JsonVariant) {
-                .n_ref = 1,
-                .type = JSON_VARIANT_ARRAY,
-                .depth = 1,
-        };
-
-        for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
-                JsonVariant *w = v + 1 + v->n_elements;
-                size_t k;
-
-                *w = (JsonVariant) {
-                        .is_embedded = true,
-                        .parent = v,
-                        .type = JSON_VARIANT_STRING,
-                };
-
-                k = strlen(l[v->n_elements]);
-
-                if (k > INLINE_STRING_MAX) {
-                        /* If string is too long, store it as reference. */
-
-                        r = json_variant_new_stringn(&w->reference, l[v->n_elements], k);
-                        if (r < 0)
-                                return r;
-
-                        w->is_reference = true;
-                } else
-                        memcpy(w->string, l[v->n_elements], k+1);
-        }
-
-        *ret = TAKE_PTR(v);
-        return 0;
-}
-
-int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) {
-        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
-
-        assert_return(ret, -EINVAL);
-        if (n == 0) {
-                *ret = JSON_VARIANT_MAGIC_EMPTY_OBJECT;
-                return 0;
-        }
-        assert_return(array, -EINVAL);
-        assert_return(n % 2 == 0, -EINVAL);
-
-        v = new(JsonVariant, n + 1);
-        if (!v)
-                return -ENOMEM;
-
-        *v = (JsonVariant) {
-                .n_ref = 1,
-                .type = JSON_VARIANT_OBJECT,
-        };
-
-        for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
-                JsonVariant *w = v + 1 + v->n_elements,
-                        *c = array[v->n_elements];
-                uint16_t d;
-
-                if ((v->n_elements & 1) == 0 &&
-                    !json_variant_is_string(c))
-                        return -EINVAL; /* Every second one needs to be a string, as it is the key name */
-
-                d = json_variant_depth(c);
-                if (d >= DEPTH_MAX) /* Refuse too deep nesting */
-                        return -ELNRNG;
-                if (d >= v->depth)
-                        v->depth = d + 1;
-
-                *w = (JsonVariant) {
-                        .is_embedded = true,
-                        .parent = v,
-                };
-
-                json_variant_set(w, c);
-                json_variant_copy_source(w, c);
-        }
-
-        *ret = TAKE_PTR(v);
-        return 0;
-}
-
-static void json_variant_free_inner(JsonVariant *v) {
-        assert(v);
-
-        if (!json_variant_is_regular(v))
-                return;
-
-        json_source_unref(v->source);
-
-        if (v->is_reference) {
-                json_variant_unref(v->reference);
-                return;
-        }
-
-        if (IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT)) {
-                size_t i;
-
-                for (i = 0; i < v->n_elements; i++)
-                        json_variant_free_inner(v + 1 + i);
-        }
-}
-
-JsonVariant *json_variant_ref(JsonVariant *v) {
-        if (!v)
-                return NULL;
-        if (!json_variant_is_regular(v))
-                return v;
-
-        if (v->is_embedded)
-                json_variant_ref(v->parent); /* ref the compounding variant instead */
-        else {
-                assert(v->n_ref > 0);
-                v->n_ref++;
-        }
-
-        return v;
-}
-
-JsonVariant *json_variant_unref(JsonVariant *v) {
-        if (!v)
-                return NULL;
-        if (!json_variant_is_regular(v))
-                return NULL;
-
-        if (v->is_embedded)
-                json_variant_unref(v->parent);
-        else {
-                assert(v->n_ref > 0);
-                v->n_ref--;
-
-                if (v->n_ref == 0) {
-                        json_variant_free_inner(v);
-                        free(v);
-                }
-        }
-
-        return NULL;
-}
-
-void json_variant_unref_many(JsonVariant **array, size_t n) {
-        size_t i;
-
-        assert(array || n == 0);
-
-        for (i = 0; i < n; i++)
-                json_variant_unref(array[i]);
-}
-
-const char *json_variant_string(JsonVariant *v) {
-        if (!v)
-                return NULL;
-        if (v == JSON_VARIANT_MAGIC_EMPTY_STRING)
-                return "";
-        if (json_variant_is_magic(v))
-                goto mismatch;
-        if (json_variant_is_const_string(v)) {
-                uintptr_t p = (uintptr_t) v;
-
-                assert((p & 1) != 0);
-                return (const char*) (p ^ 1U);
-        }
-
-        if (v->is_reference)
-                return json_variant_string(v->reference);
-        if (v->type != JSON_VARIANT_STRING)
-                goto mismatch;
-
-        return v->string;
-
-mismatch:
-        log_debug("Non-string JSON variant requested as string, returning NULL.");
-        return NULL;
-}
-
-bool json_variant_boolean(JsonVariant *v) {
-        if (!v)
-                goto mismatch;
-        if (v == JSON_VARIANT_MAGIC_TRUE)
-                return true;
-        if (v == JSON_VARIANT_MAGIC_FALSE)
-                return false;
-        if (!json_variant_is_regular(v))
-                goto mismatch;
-        if (v->type != JSON_VARIANT_BOOLEAN)
-                goto mismatch;
-        if (v->is_reference)
-                return json_variant_boolean(v->reference);
-
-        return v->value.boolean;
-
-mismatch:
-        log_debug("Non-boolean JSON variant requested as boolean, returning false.");
-        return false;
-}
-
-intmax_t json_variant_integer(JsonVariant *v) {
-        if (!v)
-                goto mismatch;
-        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER ||
-            v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
-            v == JSON_VARIANT_MAGIC_ZERO_REAL)
-                return 0;
-        if (!json_variant_is_regular(v))
-                goto mismatch;
-        if (v->is_reference)
-                return json_variant_integer(v->reference);
-
-        switch (v->type) {
-
-        case JSON_VARIANT_INTEGER:
-                return v->value.integer;
-
-        case JSON_VARIANT_UNSIGNED:
-                if (v->value.unsig <= INTMAX_MAX)
-                        return (intmax_t) v->value.unsig;
-
-                log_debug("Unsigned integer %ju requested as signed integer and out of range, returning 0.", v->value.unsig);
-                return 0;
-
-        case JSON_VARIANT_REAL: {
-                intmax_t converted;
-
-                converted = (intmax_t) v->value.real;
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wfloat-equal"
-                if ((long double) converted == v->value.real)
-#pragma GCC diagnostic pop
-                        return converted;
-
-                log_debug("Real %Lg requested as integer, and cannot be converted losslessly, returning 0.", v->value.real);
-                return 0;
-        }
-
-        default:
-                break;
-        }
-
-mismatch:
-        log_debug("Non-integer JSON variant requested as integer, returning 0.");
-        return 0;
-}
-
-uintmax_t json_variant_unsigned(JsonVariant *v) {
-        if (!v)
-                goto mismatch;
-        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER ||
-            v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
-            v == JSON_VARIANT_MAGIC_ZERO_REAL)
-                return 0;
-        if (!json_variant_is_regular(v))
-                goto mismatch;
-        if (v->is_reference)
-                return json_variant_integer(v->reference);
-
-        switch (v->type) {
-
-        case JSON_VARIANT_INTEGER:
-                if (v->value.integer >= 0)
-                        return (uintmax_t) v->value.integer;
-
-                log_debug("Signed integer %ju requested as unsigned integer and out of range, returning 0.", v->value.integer);
-                return 0;
-
-        case JSON_VARIANT_UNSIGNED:
-                return v->value.unsig;
-
-        case JSON_VARIANT_REAL: {
-                uintmax_t converted;
-
-                converted = (uintmax_t) v->value.real;
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wfloat-equal"
-                if ((long double) converted == v->value.real)
-#pragma GCC diagnostic pop
-                        return converted;
-
-                log_debug("Real %Lg requested as unsigned integer, and cannot be converted losslessly, returning 0.", v->value.real);
-                return 0;
-        }
-
-        default:
-                break;
-        }
-
-mismatch:
-        log_debug("Non-integer JSON variant requested as unsigned, returning 0.");
-        return 0;
-}
-
-long double json_variant_real(JsonVariant *v) {
-        if (!v)
-                return 0.0;
-        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER ||
-            v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
-            v == JSON_VARIANT_MAGIC_ZERO_REAL)
-                return 0.0;
-        if (!json_variant_is_regular(v))
-                goto mismatch;
-        if (v->is_reference)
-                return json_variant_real(v->reference);
-
-        switch (v->type) {
-
-        case JSON_VARIANT_REAL:
-                return v->value.real;
-
-        case JSON_VARIANT_INTEGER: {
-                long double converted;
-
-                converted = (long double) v->value.integer;
-
-                if ((intmax_t) converted == v->value.integer)
-                        return converted;
-
-                log_debug("Signed integer %ji requested as real, and cannot be converted losslessly, returning 0.", v->value.integer);
-                return 0.0;
-        }
-
-        case JSON_VARIANT_UNSIGNED: {
-                long double converted;
-
-                converted = (long double) v->value.unsig;
-
-                if ((uintmax_t) converted == v->value.unsig)
-                        return converted;
-
-                log_debug("Unsigned integer %ju requested as real, and cannot be converted losslessly, returning 0.", v->value.unsig);
-                return 0.0;
-        }
-
-        default:
-                break;
-        }
-
-mismatch:
-        log_debug("Non-integer JSON variant requested as integer, returning 0.");
-        return 0.0;
-}
-
-bool json_variant_is_negative(JsonVariant *v) {
-        if (!v)
-                goto mismatch;
-        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER ||
-            v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
-            v == JSON_VARIANT_MAGIC_ZERO_REAL)
-                return false;
-        if (!json_variant_is_regular(v))
-                goto mismatch;
-        if (v->is_reference)
-                return json_variant_is_negative(v->reference);
-
-        /* This function is useful as checking whether numbers are negative is pretty complex since we have three types
-         * of numbers. And some JSON code (OCI for example) uses negative numbers to mark "not defined" numeric
-         * values. */
-
-        switch (v->type) {
-
-        case JSON_VARIANT_REAL:
-                return v->value.real < 0;
-
-        case JSON_VARIANT_INTEGER:
-                return v->value.integer < 0;
-
-        case JSON_VARIANT_UNSIGNED:
-                return false;
-
-        default:
-                break;
-        }
-
-mismatch:
-        log_debug("Non-integer JSON variant tested for negativity, returning false.");
-        return false;
-}
-
-JsonVariantType json_variant_type(JsonVariant *v) {
-
-        if (!v)
-                return _JSON_VARIANT_TYPE_INVALID;
-
-        if (json_variant_is_const_string(v))
-                return JSON_VARIANT_STRING;
-
-        if (v == JSON_VARIANT_MAGIC_TRUE || v == JSON_VARIANT_MAGIC_FALSE)
-                return JSON_VARIANT_BOOLEAN;
-
-        if (v == JSON_VARIANT_MAGIC_NULL)
-                return JSON_VARIANT_NULL;
-
-        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER)
-                return JSON_VARIANT_INTEGER;
-
-        if (v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED)
-                return JSON_VARIANT_UNSIGNED;
-
-        if (v == JSON_VARIANT_MAGIC_ZERO_REAL)
-                return JSON_VARIANT_REAL;
-
-        if (v == JSON_VARIANT_MAGIC_EMPTY_STRING)
-                return JSON_VARIANT_STRING;
-
-        if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY)
-                return JSON_VARIANT_ARRAY;
-
-        if (v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
-                return JSON_VARIANT_OBJECT;
-
-        return v->type;
-}
-
-bool json_variant_has_type(JsonVariant *v, JsonVariantType type) {
-        JsonVariantType rt;
-
-        v = json_variant_dereference(v);
-
-        rt = json_variant_type(v);
-        if (rt == type)
-                return true;
-
-        /* If it's a const string, then it only can be a string, and if it is not, it's not */
-        if (json_variant_is_const_string(v))
-                return false;
-
-        /* All three magic zeroes qualify as integer, unsigned and as real */
-        if ((v == JSON_VARIANT_MAGIC_ZERO_INTEGER || v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || v == JSON_VARIANT_MAGIC_ZERO_REAL) &&
-            IN_SET(type, JSON_VARIANT_INTEGER, JSON_VARIANT_UNSIGNED, JSON_VARIANT_REAL, JSON_VARIANT_NUMBER))
-                return true;
-
-        /* All other magic variant types are only equal to themselves */
-        if (json_variant_is_magic(v))
-                return false;
-
-        /* Handle the "number" pseudo type */
-        if (type == JSON_VARIANT_NUMBER)
-                return IN_SET(rt, JSON_VARIANT_INTEGER, JSON_VARIANT_UNSIGNED, JSON_VARIANT_REAL);
-
-        /* Integer conversions are OK in many cases */
-        if (rt == JSON_VARIANT_INTEGER && type == JSON_VARIANT_UNSIGNED)
-                return v->value.integer >= 0;
-        if (rt == JSON_VARIANT_UNSIGNED && type == JSON_VARIANT_INTEGER)
-                return v->value.unsig <= INTMAX_MAX;
-
-        /* Any integer that can be converted lossley to a real and back may also be considered a real */
-        if (rt == JSON_VARIANT_INTEGER && type == JSON_VARIANT_REAL)
-                return (intmax_t) (long double) v->value.integer == v->value.integer;
-        if (rt == JSON_VARIANT_UNSIGNED && type == JSON_VARIANT_REAL)
-                return (uintmax_t) (long double) v->value.unsig == v->value.unsig;
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wfloat-equal"
-        /* Any real that can be converted losslessly to an integer and back may also be considered an integer */
-        if (rt == JSON_VARIANT_REAL && type == JSON_VARIANT_INTEGER)
-                return (long double) (intmax_t) v->value.real == v->value.real;
-        if (rt == JSON_VARIANT_REAL && type == JSON_VARIANT_UNSIGNED)
-                return (long double) (uintmax_t) v->value.real == v->value.real;
-#pragma GCC diagnostic pop
-
-        return false;
-}
-
-size_t json_variant_elements(JsonVariant *v) {
-        if (!v)
-                return 0;
-        if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY ||
-            v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
-                return 0;
-        if (!json_variant_is_regular(v))
-                goto mismatch;
-        if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
-                goto mismatch;
-        if (v->is_reference)
-                return json_variant_elements(v->reference);
-
-        return v->n_elements;
-
-mismatch:
-        log_debug("Number of elements in non-array/non-object JSON variant requested, returning 0.");
-        return 0;
-}
-
-JsonVariant *json_variant_by_index(JsonVariant *v, size_t idx) {
-        if (!v)
-                return NULL;
-        if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY ||
-            v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
-                return NULL;
-        if (!json_variant_is_regular(v))
-                goto mismatch;
-        if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
-                goto mismatch;
-        if (v->is_reference)
-                return json_variant_by_index(v->reference, idx);
-        if (idx >= v->n_elements)
-                return NULL;
-
-        return json_variant_conservative_normalize(v + 1 + idx);
-
-mismatch:
-        log_debug("Element in non-array/non-object JSON variant requested by index, returning NULL.");
-        return NULL;
-}
-
-JsonVariant *json_variant_by_key_full(JsonVariant *v, const char *key, JsonVariant **ret_key) {
-        size_t i;
-
-        if (!v)
-                goto not_found;
-        if (!key)
-                goto not_found;
-        if (v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
-                goto not_found;
-        if (!json_variant_is_regular(v))
-                goto mismatch;
-        if (v->type != JSON_VARIANT_OBJECT)
-                goto mismatch;
-        if (v->is_reference)
-                return json_variant_by_key(v->reference, key);
-
-        for (i = 0; i < v->n_elements; i += 2) {
-                JsonVariant *p;
-
-                p = json_variant_dereference(v + 1 + i);
-
-                if (!json_variant_has_type(p, JSON_VARIANT_STRING))
-                        continue;
-
-                if (streq(json_variant_string(p), key)) {
-
-                        if (ret_key)
-                                *ret_key = json_variant_conservative_normalize(v + 1 + i);
-
-                        return json_variant_conservative_normalize(v + 1 + i + 1);
-                }
-        }
-
-not_found:
-        if (ret_key)
-                *ret_key = NULL;
-
-        return NULL;
-
-mismatch:
-        log_debug("Element in non-object JSON variant requested by key, returning NULL.");
-        if (ret_key)
-                *ret_key = NULL;
-
-        return NULL;
-}
-
-JsonVariant *json_variant_by_key(JsonVariant *v, const char *key) {
-        return json_variant_by_key_full(v, key, NULL);
-}
-
-bool json_variant_equal(JsonVariant *a, JsonVariant *b) {
-        JsonVariantType t;
-
-        a = json_variant_normalize(a);
-        b = json_variant_normalize(b);
-
-        if (a == b)
-                return true;
-
-        t = json_variant_type(a);
-        if (!json_variant_has_type(b, t))
-                return false;
-
-        switch (t) {
-
-        case JSON_VARIANT_STRING:
-                return streq(json_variant_string(a), json_variant_string(b));
-
-        case JSON_VARIANT_INTEGER:
-                return json_variant_integer(a) == json_variant_integer(b);
-
-        case JSON_VARIANT_UNSIGNED:
-                return json_variant_unsigned(a) == json_variant_unsigned(b);
-
-        case JSON_VARIANT_REAL:
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wfloat-equal"
-                return json_variant_real(a) == json_variant_real(b);
-#pragma GCC diagnostic pop
-
-        case JSON_VARIANT_BOOLEAN:
-                return json_variant_boolean(a) == json_variant_boolean(b);
-
-        case JSON_VARIANT_NULL:
-                return true;
-
-        case JSON_VARIANT_ARRAY: {
-                size_t i, n;
-
-                n = json_variant_elements(a);
-                if (n != json_variant_elements(b))
-                        return false;
-
-                for (i = 0; i < n; i++) {
-                        if (!json_variant_equal(json_variant_by_index(a, i), json_variant_by_index(b, i)))
-                                return false;
-                }
-
-                return true;
-        }
-
-        case JSON_VARIANT_OBJECT: {
-                size_t i, n;
-
-                n = json_variant_elements(a);
-                if (n != json_variant_elements(b))
-                        return false;
-
-                /* Iterate through all keys in 'a' */
-                for (i = 0; i < n; i += 2) {
-                        bool found = false;
-                        size_t j;
-
-                        /* Match them against all keys in 'b' */
-                        for (j = 0; j < n; j += 2) {
-                                JsonVariant *key_b;
-
-                                key_b = json_variant_by_index(b, j);
-
-                                /* During the first iteration unmark everything */
-                                if (i == 0)
-                                        key_b->is_marked = false;
-                                else if (key_b->is_marked) /* In later iterations if we already marked something, don't bother with it again */
-                                        continue;
-
-                                if (found)
-                                        continue;
-
-                                if (json_variant_equal(json_variant_by_index(a, i), key_b) &&
-                                    json_variant_equal(json_variant_by_index(a, i+1), json_variant_by_index(b, j+1))) {
-                                        /* Key and values match! */
-                                        key_b->is_marked = found = true;
-
-                                        /* In the first iteration we continue the inner loop since we want to mark
-                                         * everything, otherwise exit the loop quickly after we found what we were
-                                         * looking for. */
-                                        if (i != 0)
-                                                break;
-                                }
-                        }
-
-                        if (!found)
-                                return false;
-                }
-
-                return true;
-        }
-
-        default:
-                assert_not_reached("Unknown variant type.");
-        }
-}
-
-int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column) {
-        assert_return(v, -EINVAL);
-
-        if (ret_source)
-                *ret_source = json_variant_is_regular(v) && v->source ? v->source->name : NULL;
-
-        if (ret_line)
-                *ret_line = json_variant_is_regular(v) ? v->line : 0;
-
-        if (ret_column)
-                *ret_column = json_variant_is_regular(v) ? v->column : 0;
-
-        return 0;
-}
-
-static int print_source(FILE *f, JsonVariant *v, unsigned flags, bool whitespace) {
-        size_t w, k;
-
-        if (!FLAGS_SET(flags, JSON_FORMAT_SOURCE|JSON_FORMAT_PRETTY))
-                return 0;
-
-        if (!json_variant_is_regular(v))
-                return 0;
-
-        if (!v->source && v->line == 0 && v->column == 0)
-                return 0;
-
-        /* The max width we need to format the line numbers for this source file */
-        w = (v->source && v->source->max_line > 0) ?
-                DECIMAL_STR_WIDTH(v->source->max_line) :
-                DECIMAL_STR_MAX(unsigned)-1;
-        k = (v->source && v->source->max_column > 0) ?
-                DECIMAL_STR_WIDTH(v->source->max_column) :
-                DECIMAL_STR_MAX(unsigned) -1;
-
-        if (whitespace) {
-                size_t i, n;
-
-                n = 1 + (v->source ? strlen(v->source->name) : 0) +
-                        ((v->source && (v->line > 0 || v->column > 0)) ? 1 : 0) +
-                        (v->line > 0 ? w : 0) +
-                        (((v->source || v->line > 0) && v->column > 0) ? 1 : 0) +
-                        (v->column > 0 ? k : 0) +
-                        2;
-
-                for (i = 0; i < n; i++)
-                        fputc(' ', f);
-        } else {
-                fputc('[', f);
-
-                if (v->source)
-                        fputs(v->source->name, f);
-                if (v->source && (v->line > 0 || v->column > 0))
-                        fputc(':', f);
-                if (v->line > 0)
-                        fprintf(f, "%*u", (int) w, v->line);
-                if ((v->source || v->line > 0) || v->column > 0)
-                        fputc(':', f);
-                if (v->column > 0)
-                        fprintf(f, "%*u", (int) k, v->column);
-
-                fputc(']', f);
-                fputc(' ', f);
-        }
-
-        return 0;
-}
-
-static int json_format(FILE *f, JsonVariant *v, unsigned flags, const char *prefix) {
-        int r;
-
-        assert(f);
-        assert(v);
-
-        switch (json_variant_type(v)) {
-
-        case JSON_VARIANT_REAL: {
-                locale_t loc;
-
-                loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
-                if (loc == (locale_t) 0)
-                        return -errno;
-
-                if (flags & JSON_FORMAT_COLOR)
-                        fputs(ANSI_HIGHLIGHT_BLUE, f);
-
-                fprintf(f, "%.*Le", DECIMAL_DIG, json_variant_real(v));
-
-                if (flags & JSON_FORMAT_COLOR)
-                        fputs(ANSI_NORMAL, f);
-
-                freelocale(loc);
-                break;
-        }
-
-        case JSON_VARIANT_INTEGER:
-                if (flags & JSON_FORMAT_COLOR)
-                        fputs(ANSI_HIGHLIGHT_BLUE, f);
-
-                fprintf(f, "%" PRIdMAX, json_variant_integer(v));
-
-                if (flags & JSON_FORMAT_COLOR)
-                        fputs(ANSI_NORMAL, f);
-                break;
-
-        case JSON_VARIANT_UNSIGNED:
-                if (flags & JSON_FORMAT_COLOR)
-                        fputs(ANSI_HIGHLIGHT_BLUE, f);
-
-                fprintf(f, "%" PRIuMAX, json_variant_unsigned(v));
-
-                if (flags & JSON_FORMAT_COLOR)
-                        fputs(ANSI_NORMAL, f);
-                break;
-
-        case JSON_VARIANT_BOOLEAN:
-
-                if (flags & JSON_FORMAT_COLOR)
-                        fputs(ANSI_HIGHLIGHT, f);
-
-                if (json_variant_boolean(v))
-                        fputs("true", f);
-                else
-                        fputs("false", f);
-
-                if (flags & JSON_FORMAT_COLOR)
-                        fputs(ANSI_NORMAL, f);
-
-                break;
-
-        case JSON_VARIANT_NULL:
-                if (flags & JSON_FORMAT_COLOR)
-                        fputs(ANSI_HIGHLIGHT, f);
-
-                fputs("null", f);
-
-                if (flags & JSON_FORMAT_COLOR)
-                        fputs(ANSI_NORMAL, f);
-                break;
-
-        case JSON_VARIANT_STRING: {
-                const char *q;
-
-                fputc('"', f);
-
-                if (flags & JSON_FORMAT_COLOR)
-                        fputs(ANSI_GREEN, f);
-
-                for (q = json_variant_string(v); *q; q++) {
-
-                        switch (*q) {
-
-                        case '"':
-                                fputs("\\\"", f);
-                                break;
-
-                        case '\\':
-                                fputs("\\\\", f);
-                                break;
-
-                        case '/':
-                                fputs("\\/", f);
-                                break;
-
-                        case '\b':
-                                fputs("\\b", f);
-                                break;
-
-                        case '\f':
-                                fputs("\\f", f);
-                                break;
-
-                        case '\n':
-                                fputs("\\n", f);
-                                break;
-
-                        case '\r':
-                                fputs("\\r", f);
-                                break;
-
-                        case '\t':
-                                fputs("\\t", f);
-                                break;
-
-                        default:
-                                if ((signed char) *q >= 0 && *q < ' ')
-                                        fprintf(f, "\\u%04x", *q);
-                                else
-                                        fputc(*q, f);
-                                break;
-                        }
-                }
-
-                if (flags & JSON_FORMAT_COLOR)
-                        fputs(ANSI_NORMAL, f);
-
-                fputc('"', f);
-                break;
-        }
-
-        case JSON_VARIANT_ARRAY: {
-                size_t i, n;
-
-                n = json_variant_elements(v);
-
-                if (n == 0)
-                        fputs("[]", f);
-                else {
-                        _cleanup_free_ char *joined = NULL;
-                        const char *prefix2;
-
-                        if (flags & JSON_FORMAT_PRETTY) {
-                                joined = strjoin(strempty(prefix), "\t");
-                                if (!joined)
-                                        return -ENOMEM;
-
-                                prefix2 = joined;
-                                fputs("[\n", f);
-                        } else {
-                                prefix2 = strempty(prefix);
-                                fputc('[', f);
-                        }
-
-                        for (i = 0; i < n; i++) {
-                                JsonVariant *e;
-
-                                assert_se(e = json_variant_by_index(v, i));
-
-                                if (i > 0) {
-                                        if (flags & JSON_FORMAT_PRETTY)
-                                                fputs(",\n", f);
-                                        else
-                                                fputc(',', f);
-                                }
-
-                                if (flags & JSON_FORMAT_PRETTY) {
-                                        print_source(f, e, flags, false);
-                                        fputs(prefix2, f);
-                                }
-
-                                r = json_format(f, e, flags, prefix2);
-                                if (r < 0)
-                                        return r;
-                        }
-
-                        if (flags & JSON_FORMAT_PRETTY) {
-                                fputc('\n', f);
-                                print_source(f, v, flags, true);
-                                fputs(strempty(prefix), f);
-                        }
-
-                        fputc(']', f);
-                }
-                break;
-        }
-
-        case JSON_VARIANT_OBJECT: {
-                size_t i, n;
-
-                n = json_variant_elements(v);
-
-                if (n == 0)
-                        fputs("{}", f);
-                else {
-                        _cleanup_free_ char *joined = NULL;
-                        const char *prefix2;
-
-                        if (flags & JSON_FORMAT_PRETTY) {
-                                joined = strjoin(strempty(prefix), "\t");
-                                if (!joined)
-                                        return -ENOMEM;
-
-                                prefix2 = joined;
-                                fputs("{\n", f);
-                        } else {
-                                prefix2 = strempty(prefix);
-                                fputc('{', f);
-                        }
-
-                        for (i = 0; i < n; i += 2) {
-                                JsonVariant *e;
-
-                                e = json_variant_by_index(v, i);
-
-                                if (i > 0) {
-                                        if (flags & JSON_FORMAT_PRETTY)
-                                                fputs(",\n", f);
-                                        else
-                                                fputc(',', f);
-                                }
-
-                                if (flags & JSON_FORMAT_PRETTY) {
-                                        print_source(f, e, flags, false);
-                                        fputs(prefix2, f);
-                                }
-
-                                r = json_format(f, e, flags, prefix2);
-                                if (r < 0)
-                                        return r;
-
-                                fputs(flags & JSON_FORMAT_PRETTY ? " : " : ":", f);
-
-                                r = json_format(f, json_variant_by_index(v, i+1), flags, prefix2);
-                                if (r < 0)
-                                        return r;
-                        }
-
-                        if (flags & JSON_FORMAT_PRETTY) {
-                                fputc('\n', f);
-                                print_source(f, v, flags, true);
-                                fputs(strempty(prefix), f);
-                        }
-
-                        fputc('}', f);
-                }
-                break;
-        }
-
-        default:
-                assert_not_reached("Unexpected variant type.");
-        }
-
-        return 0;
-}
-
-int json_variant_format(JsonVariant *v, unsigned flags, char **ret) {
-        _cleanup_free_ char *s = NULL;
-        size_t sz = 0;
-        int r;
-
-        assert_return(v, -EINVAL);
-        assert_return(ret, -EINVAL);
-
-        {
-                _cleanup_fclose_ FILE *f = NULL;
-
-                f = open_memstream(&s, &sz);
-                if (!f)
-                        return -ENOMEM;
-
-                (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
-                json_variant_dump(v, flags, f, NULL);
-
-                r = fflush_and_check(f);
-        }
-        if (r < 0)
-                return r;
-
-        assert(s);
-        *ret = TAKE_PTR(s);
-
-        return (int) sz;
-}
-
-void json_variant_dump(JsonVariant *v, unsigned flags, FILE *f, const char *prefix) {
-        if (!v)
-                return;
-
-        if (!f)
-                f = stdout;
-
-        print_source(f, v, flags, false);
-
-        if (flags & JSON_FORMAT_SSE)
-                fputs("data: ", f);
-        if (flags & JSON_FORMAT_SEQ)
-                fputc('\x1e', f); /* ASCII Record Separator */
-
-        json_format(f, v, flags, prefix);
-
-        if (flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_SEQ|JSON_FORMAT_SSE|JSON_FORMAT_NEWLINE))
-                fputc('\n', f);
-        if (flags & JSON_FORMAT_SSE)
-                fputc('\n', f); /* In case of SSE add a second newline */
-}
-
-static int json_variant_copy(JsonVariant **nv, JsonVariant *v) {
-        JsonVariantType t;
-        JsonVariant *c;
-        JsonValue value;
-        const void *source;
-        size_t k;
-
-        assert(nv);
-        assert(v);
-
-        /* Let's copy the simple types literally, and the larger types by references */
-        t = json_variant_type(v);
-        switch (t) {
-        case JSON_VARIANT_INTEGER:
-                k = sizeof(intmax_t);
-                value.integer = json_variant_integer(v);
-                source = &value;
-                break;
-
-        case JSON_VARIANT_UNSIGNED:
-                k = sizeof(uintmax_t);
-                value.unsig = json_variant_unsigned(v);
-                source = &value;
-                break;
-
-        case JSON_VARIANT_REAL:
-                k = sizeof(long double);
-                value.real = json_variant_real(v);
-                source = &value;
-                break;
-
-        case JSON_VARIANT_BOOLEAN:
-                k = sizeof(bool);
-                value.boolean = json_variant_boolean(v);
-                source = &value;
-                break;
-
-        case JSON_VARIANT_NULL:
-                k = 0;
-                source = NULL;
-                break;
-
-        case JSON_VARIANT_STRING:
-                source = json_variant_string(v);
-                k = strnlen(source, INLINE_STRING_MAX + 1);
-                if (k <= INLINE_STRING_MAX) {
-                        k ++;
-                        break;
-                }
-
-                _fallthrough_;
-
-        default:
-                /* Everything else copy by reference */
-
-                c = malloc0(offsetof(JsonVariant, reference) + sizeof(JsonVariant*));
-                if (!c)
-                        return -ENOMEM;
-
-                c->n_ref = 1;
-                c->type = t;
-                c->is_reference = true;
-                c->reference = json_variant_ref(json_variant_normalize(v));
-
-                *nv = c;
-                return 0;
-        }
-
-        c = malloc0(offsetof(JsonVariant, value) + k);
-        if (!c)
-                return -ENOMEM;
-
-        c->n_ref = 1;
-        c->type = t;
-
-        memcpy_safe(&c->value, source, k);
-
-        *nv = c;
-        return 0;
-}
-
-static bool json_single_ref(JsonVariant *v) {
-
-        /* Checks whether the caller is the single owner of the object, i.e. can get away with changing it */
-
-        if (!json_variant_is_regular(v))
-                return false;
-
-        if (v->is_embedded)
-                return json_single_ref(v->parent);
-
-        assert(v->n_ref > 0);
-        return v->n_ref == 1;
-}
-
-static int json_variant_set_source(JsonVariant **v, JsonSource *source, unsigned line, unsigned column) {
-        JsonVariant *w;
-        int r;
-
-        assert(v);
-
-        /* Patch in source and line/column number. Tries to do this in-place if the caller is the sole referencer of
-         * the object. If not, allocates a new object, possibly a surrogate for the original one */
-
-        if (!*v)
-                return 0;
-
-        if (source && line > source->max_line)
-                source->max_line = line;
-        if (source && column > source->max_column)
-                source->max_column = column;
-
-        if (!json_variant_is_regular(*v)) {
-
-                if (!source && line == 0 && column == 0)
-                        return 0;
-
-        } else {
-                if (json_source_equal((*v)->source, source) &&
-                    (*v)->line == line &&
-                    (*v)->column == column)
-                        return 0;
-
-                if (json_single_ref(*v)) { /* Sole reference? */
-                        json_source_unref((*v)->source);
-                        (*v)->source = json_source_ref(source);
-                        (*v)->line = line;
-                        (*v)->column = column;
-                        return 1;
-                }
-        }
-
-        r = json_variant_copy(&w, *v);
-        if (r < 0)
-                return r;
-
-        assert(json_variant_is_regular(w));
-        assert(!w->is_embedded);
-        assert(w->n_ref == 1);
-        assert(!w->source);
-
-        w->source = json_source_ref(source);
-        w->line = line;
-        w->column = column;
-
-        json_variant_unref(*v);
-        *v = w;
-
-        return 1;
-}
-
-static void inc_lines_columns(unsigned *line, unsigned *column, const char *s, size_t n) {
-        assert(line);
-        assert(column);
-        assert(s || n == 0);
-
-        while (n > 0) {
-
-                if (*s == '\n') {
-                        (*line)++;
-                        *column = 1;
-                } else if ((signed char) *s >= 0 && *s < 127) /* Process ASCII chars quickly */
-                        (*column)++;
-                else {
-                        int w;
-
-                        w = utf8_encoded_valid_unichar(s);
-                        if (w < 0) /* count invalid unichars as normal characters */
-                                w = 1;
-                        else if ((size_t) w > n) /* never read more than the specified number of characters */
-                                w = (int) n;
-
-                        (*column)++;
-
-                        s += w;
-                        n -= w;
-                        continue;
-                }
-
-                s++;
-                n--;
-        }
-}
-
-static int unhex_ucs2(const char *c, uint16_t *ret) {
-        int aa, bb, cc, dd;
-        uint16_t x;
-
-        assert(c);
-        assert(ret);
-
-        aa = unhexchar(c[0]);
-        if (aa < 0)
-                return -EINVAL;
-
-        bb = unhexchar(c[1]);
-        if (bb < 0)
-                return -EINVAL;
-
-        cc = unhexchar(c[2]);
-        if (cc < 0)
-                return -EINVAL;
-
-        dd = unhexchar(c[3]);
-        if (dd < 0)
-                return -EINVAL;
-
-        x =     ((uint16_t) aa << 12) |
-                ((uint16_t) bb << 8) |
-                ((uint16_t) cc << 4) |
-                ((uint16_t) dd);
-
-        if (x <= 0)
-                return -EINVAL;
-
-        *ret = x;
-
-        return 0;
-}
-
-static int json_parse_string(const char **p, char **ret) {
-        _cleanup_free_ char *s = NULL;
-        size_t n = 0, allocated = 0;
-        const char *c;
-
-        assert(p);
-        assert(*p);
-        assert(ret);
-
-        c = *p;
-
-        if (*c != '"')
-                return -EINVAL;
-
-        c++;
-
-        for (;;) {
-                int len;
-
-                /* Check for EOF */
-                if (*c == 0)
-                        return -EINVAL;
-
-                /* Check for control characters 0x00..0x1f */
-                if (*c > 0 && *c < ' ')
-                        return -EINVAL;
-
-                /* Check for control character 0x7f */
-                if (*c == 0x7f)
-                        return -EINVAL;
-
-                if (*c == '"') {
-                        if (!s) {
-                                s = strdup("");
-                                if (!s)
-                                        return -ENOMEM;
-                        } else
-                                s[n] = 0;
-
-                        *p = c + 1;
-
-                        *ret = TAKE_PTR(s);
-                        return JSON_TOKEN_STRING;
-                }
-
-                if (*c == '\\') {
-                        char ch = 0;
-                        c++;
-
-                        if (*c == 0)
-                                return -EINVAL;
-
-                        if (IN_SET(*c, '"', '\\', '/'))
-                                ch = *c;
-                        else if (*c == 'b')
-                                ch = '\b';
-                        else if (*c == 'f')
-                                ch = '\f';
-                        else if (*c == 'n')
-                                ch = '\n';
-                        else if (*c == 'r')
-                                ch = '\r';
-                        else if (*c == 't')
-                                ch = '\t';
-                        else if (*c == 'u') {
-                                char16_t x;
-                                int r;
-
-                                r = unhex_ucs2(c + 1, &x);
-                                if (r < 0)
-                                        return r;
-
-                                c += 5;
-
-                                if (!GREEDY_REALLOC(s, allocated, n + 5))
-                                        return -ENOMEM;
-
-                                if (!utf16_is_surrogate(x))
-                                        n += utf8_encode_unichar(s + n, (char32_t) x);
-                                else if (utf16_is_trailing_surrogate(x))
-                                        return -EINVAL;
-                                else {
-                                        char16_t y;
-
-                                        if (c[0] != '\\' || c[1] != 'u')
-                                                return -EINVAL;
-
-                                        r = unhex_ucs2(c + 2, &y);
-                                        if (r < 0)
-                                                return r;
-
-                                        c += 6;
-
-                                        if (!utf16_is_trailing_surrogate(y))
-                                                return -EINVAL;
-
-                                        n += utf8_encode_unichar(s + n, utf16_surrogate_pair_to_unichar(x, y));
-                                }
-
-                                continue;
-                        } else
-                                return -EINVAL;
-
-                        if (!GREEDY_REALLOC(s, allocated, n + 2))
-                                return -ENOMEM;
-
-                        s[n++] = ch;
-                        c ++;
-                        continue;
-                }
-
-                len = utf8_encoded_valid_unichar(c);
-                if (len < 0)
-                        return len;
-
-                if (!GREEDY_REALLOC(s, allocated, n + len + 1))
-                        return -ENOMEM;
-
-                memcpy(s + n, c, len);
-                n += len;
-                c += len;
-        }
-}
-
-static int json_parse_number(const char **p, JsonValue *ret) {
-        bool negative = false, exponent_negative = false, is_real = false;
-        long double x = 0.0, y = 0.0, exponent = 0.0, shift = 1.0;
-        intmax_t i = 0;
-        uintmax_t u = 0;
-        const char *c;
-
-        assert(p);
-        assert(*p);
-        assert(ret);
-
-        c = *p;
-
-        if (*c == '-') {
-                negative = true;
-                c++;
-        }
-
-        if (*c == '0')
-                c++;
-        else {
-                if (!strchr("123456789", *c) || *c == 0)
-                        return -EINVAL;
-
-                do {
-                        if (!is_real) {
-                                if (negative) {
-
-                                        if (i < INTMAX_MIN / 10) /* overflow */
-                                                is_real = true;
-                                        else {
-                                                intmax_t t = 10 * i;
-
-                                                if (t < INTMAX_MIN + (*c - '0')) /* overflow */
-                                                        is_real = true;
-                                                else
-                                                        i = t - (*c - '0');
-                                        }
-                                } else {
-                                        if (u > UINTMAX_MAX / 10) /* overflow */
-                                                is_real = true;
-                                        else {
-                                                uintmax_t t = 10 * u;
-
-                                                if (t > UINTMAX_MAX - (*c - '0')) /* overflow */
-                                                        is_real = true;
-                                                else
-                                                        u = t + (*c - '0');
-                                        }
-                                }
-                        }
-
-                        x = 10.0 * x + (*c - '0');
-
-                        c++;
-                } while (strchr("0123456789", *c) && *c != 0);
-        }
-
-        if (*c == '.') {
-                is_real = true;
-                c++;
-
-                if (!strchr("0123456789", *c) || *c == 0)
-                        return -EINVAL;
-
-                do {
-                        y = 10.0 * y + (*c - '0');
-                        shift = 10.0 * shift;
-                        c++;
-                } while (strchr("0123456789", *c) && *c != 0);
-        }
-
-        if (IN_SET(*c, 'e', 'E')) {
-                is_real = true;
-                c++;
-
-                if (*c == '-') {
-                        exponent_negative = true;
-                        c++;
-                } else if (*c == '+')
-                        c++;
-
-                if (!strchr("0123456789", *c) || *c == 0)
-                        return -EINVAL;
-
-                do {
-                        exponent = 10.0 * exponent + (*c - '0');
-                        c++;
-                } while (strchr("0123456789", *c) && *c != 0);
-        }
-
-        *p = c;
-
-        if (is_real) {
-                ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * exp10l((exponent_negative ? -1.0 : 1.0) * exponent);
-                return JSON_TOKEN_REAL;
-        } else if (negative) {
-                ret->integer = i;
-                return JSON_TOKEN_INTEGER;
-        } else  {
-                ret->unsig = u;
-                return JSON_TOKEN_UNSIGNED;
-        }
-}
-
-int json_tokenize(
-                const char **p,
-                char **ret_string,
-                JsonValue *ret_value,
-                unsigned *ret_line,   /* 'ret_line' returns the line at the beginning of this token */
-                unsigned *ret_column,
-                void **state,
-                unsigned *line,       /* 'line' is used as a line state, it always reflect the line we are at after the token was read */
-                unsigned *column) {
-
-        unsigned start_line, start_column;
-        const char *start, *c;
-        size_t n;
-        int t, r;
-
-        enum {
-                STATE_NULL,
-                STATE_VALUE,
-                STATE_VALUE_POST,
-        };
-
-        assert(p);
-        assert(*p);
-        assert(ret_string);
-        assert(ret_value);
-        assert(ret_line);
-        assert(ret_column);
-        assert(line);
-        assert(column);
-        assert(state);
-
-        t = PTR_TO_INT(*state);
-        if (t == STATE_NULL) {
-                *line = 1;
-                *column = 1;
-                t = STATE_VALUE;
-        }
-
-        /* Skip over the whitespace */
-        n = strspn(*p, WHITESPACE);
-        inc_lines_columns(line, column, *p, n);
-        c = *p + n;
-
-        /* Remember where we started processing this token */
-        start = c;
-        start_line = *line;
-        start_column = *column;
-
-        if (*c == 0) {
-                *ret_string = NULL;
-                *ret_value = JSON_VALUE_NULL;
-                r = JSON_TOKEN_END;
-                goto finish;
-        }
-
-        switch (t) {
-
-        case STATE_VALUE:
-
-                if (*c == '{') {
-                        c++;
-                        *state = INT_TO_PTR(STATE_VALUE);
-                        r = JSON_TOKEN_OBJECT_OPEN;
-                        goto null_return;
-
-                } else if (*c == '}') {
-                        c++;
-                        *state = INT_TO_PTR(STATE_VALUE_POST);
-                        r = JSON_TOKEN_OBJECT_CLOSE;
-                        goto null_return;
-
-                } else if (*c == '[') {
-                        c++;
-                        *state = INT_TO_PTR(STATE_VALUE);
-                        r = JSON_TOKEN_ARRAY_OPEN;
-                        goto null_return;
-
-                } else if (*c == ']') {
-                        c++;
-                        *state = INT_TO_PTR(STATE_VALUE_POST);
-                        r = JSON_TOKEN_ARRAY_CLOSE;
-                        goto null_return;
-
-                } else if (*c == '"') {
-
-                        r = json_parse_string(&c, ret_string);
-                        if (r < 0)
-                                return r;
-
-                        *ret_value = JSON_VALUE_NULL;
-                        *state = INT_TO_PTR(STATE_VALUE_POST);
-                        goto finish;
-
-                } else if (strchr("-0123456789", *c)) {
-
-                        r = json_parse_number(&c, ret_value);
-                        if (r < 0)
-                                return r;
-
-                        *ret_string = NULL;
-                        *state = INT_TO_PTR(STATE_VALUE_POST);
-                        goto finish;
-
-                } else if (startswith(c, "true")) {
-                        *ret_string = NULL;
-                        ret_value->boolean = true;
-                        c += 4;
-                        *state = INT_TO_PTR(STATE_VALUE_POST);
-                        r = JSON_TOKEN_BOOLEAN;
-                        goto finish;
-
-                } else if (startswith(c, "false")) {
-                        *ret_string = NULL;
-                        ret_value->boolean = false;
-                        c += 5;
-                        *state = INT_TO_PTR(STATE_VALUE_POST);
-                        r = JSON_TOKEN_BOOLEAN;
-                        goto finish;
-
-                } else if (startswith(c, "null")) {
-                        *ret_string = NULL;
-                        *ret_value = JSON_VALUE_NULL;
-                        c += 4;
-                        *state = INT_TO_PTR(STATE_VALUE_POST);
-                        r = JSON_TOKEN_NULL;
-                        goto finish;
-
-                }
-
-                return -EINVAL;
-
-        case STATE_VALUE_POST:
-
-                if (*c == ':') {
-                        c++;
-                        *state = INT_TO_PTR(STATE_VALUE);
-                        r = JSON_TOKEN_COLON;
-                        goto null_return;
-
-                } else if (*c == ',') {
-                        c++;
-                        *state = INT_TO_PTR(STATE_VALUE);
-                        r = JSON_TOKEN_COMMA;
-                        goto null_return;
-
-                } else if (*c == '}') {
-                        c++;
-                        *state = INT_TO_PTR(STATE_VALUE_POST);
-                        r = JSON_TOKEN_OBJECT_CLOSE;
-                        goto null_return;
-
-                } else if (*c == ']') {
-                        c++;
-                        *state = INT_TO_PTR(STATE_VALUE_POST);
-                        r = JSON_TOKEN_ARRAY_CLOSE;
-                        goto null_return;
-                }
-
-                return -EINVAL;
-
-        default:
-                assert_not_reached("Unexpected tokenizer state");
-        }
-
-null_return:
-        *ret_string = NULL;
-        *ret_value = JSON_VALUE_NULL;
-
-finish:
-        inc_lines_columns(line, column, start, c - start);
-        *p = c;
-
-        *ret_line = start_line;
-        *ret_column = start_column;
-
-        return r;
-}
-
-typedef enum JsonExpect {
-        /* The following values are used by json_parse() */
-        EXPECT_TOPLEVEL,
-        EXPECT_END,
-        EXPECT_OBJECT_FIRST_KEY,
-        EXPECT_OBJECT_NEXT_KEY,
-        EXPECT_OBJECT_COLON,
-        EXPECT_OBJECT_VALUE,
-        EXPECT_OBJECT_COMMA,
-        EXPECT_ARRAY_FIRST_ELEMENT,
-        EXPECT_ARRAY_NEXT_ELEMENT,
-        EXPECT_ARRAY_COMMA,
-
-        /* And these are used by json_build() */
-        EXPECT_ARRAY_ELEMENT,
-        EXPECT_OBJECT_KEY,
-} JsonExpect;
-
-typedef struct JsonStack {
-        JsonExpect expect;
-        JsonVariant **elements;
-        size_t n_elements, n_elements_allocated;
-        unsigned line_before;
-        unsigned column_before;
-} JsonStack;
-
-static void json_stack_release(JsonStack *s) {
-        assert(s);
-
-        json_variant_unref_many(s->elements, s->n_elements);
-        s->elements = mfree(s->elements);
-}
-
-static int json_parse_internal(
-                const char **input,
-                JsonSource *source,
-                JsonVariant **ret,
-                unsigned *line,
-                unsigned *column,
-                bool continue_end) {
-
-        size_t n_stack = 1, n_stack_allocated = 0, i;
-        unsigned line_buffer = 0, column_buffer = 0;
-        void *tokenizer_state = NULL;
-        JsonStack *stack = NULL;
-        const char *p;
-        int r;
-
-        assert_return(input, -EINVAL);
-        assert_return(ret, -EINVAL);
-
-        p = *input;
-
-        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack))
-                return -ENOMEM;
-
-        stack[0] = (JsonStack) {
-                .expect = EXPECT_TOPLEVEL,
-        };
-
-        if (!line)
-                line = &line_buffer;
-        if (!column)
-                column = &column_buffer;
-
-        for (;;) {
-                _cleanup_free_ char *string = NULL;
-                unsigned line_token, column_token;
-                JsonVariant *add = NULL;
-                JsonStack *current;
-                JsonValue value;
-                int token;
-
-                assert(n_stack > 0);
-                current = stack + n_stack - 1;
-
-                if (continue_end && current->expect == EXPECT_END)
-                        goto done;
-
-                token = json_tokenize(&p, &string, &value, &line_token, &column_token, &tokenizer_state, line, column);
-                if (token < 0) {
-                        r = token;
-                        goto finish;
-                }
-
-                switch (token) {
-
-                case JSON_TOKEN_END:
-                        if (current->expect != EXPECT_END) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        assert(current->n_elements == 1);
-                        assert(n_stack == 1);
-                        goto done;
-
-                case JSON_TOKEN_COLON:
-
-                        if (current->expect != EXPECT_OBJECT_COLON) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        current->expect = EXPECT_OBJECT_VALUE;
-                        break;
-
-                case JSON_TOKEN_COMMA:
-
-                        if (current->expect == EXPECT_OBJECT_COMMA)
-                                current->expect = EXPECT_OBJECT_NEXT_KEY;
-                        else if (current->expect == EXPECT_ARRAY_COMMA)
-                                current->expect = EXPECT_ARRAY_NEXT_ELEMENT;
-                        else {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        break;
-
-                case JSON_TOKEN_OBJECT_OPEN:
-
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) {
-                                r = -ENOMEM;
-                                goto finish;
-                        }
-                        current = stack + n_stack - 1;
-
-                        /* Prepare the expect for when we return from the child */
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_COMMA;
-                        else {
-                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
-                                current->expect = EXPECT_ARRAY_COMMA;
-                        }
-
-                        stack[n_stack++] = (JsonStack) {
-                                .expect = EXPECT_OBJECT_FIRST_KEY,
-                                .line_before = line_token,
-                                .column_before = column_token,
-                        };
-
-                        current = stack + n_stack - 1;
-                        break;
-
-                case JSON_TOKEN_OBJECT_CLOSE:
-                        if (!IN_SET(current->expect, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_COMMA)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        assert(n_stack > 1);
-
-                        r = json_variant_new_object(&add, current->elements, current->n_elements);
-                        if (r < 0)
-                                goto finish;
-
-                        line_token = current->line_before;
-                        column_token = current->column_before;
-
-                        json_stack_release(current);
-                        n_stack--, current--;
-
-                        break;
-
-                case JSON_TOKEN_ARRAY_OPEN:
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) {
-                                r = -ENOMEM;
-                                goto finish;
-                        }
-                        current = stack + n_stack - 1;
-
-                        /* Prepare the expect for when we return from the child */
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_COMMA;
-                        else {
-                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
-                                current->expect = EXPECT_ARRAY_COMMA;
-                        }
-
-                        stack[n_stack++] = (JsonStack) {
-                                .expect = EXPECT_ARRAY_FIRST_ELEMENT,
-                                .line_before = line_token,
-                                .column_before = column_token,
-                        };
-
-                        break;
-
-                case JSON_TOKEN_ARRAY_CLOSE:
-                        if (!IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_COMMA)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        assert(n_stack > 1);
-
-                        r = json_variant_new_array(&add, current->elements, current->n_elements);
-                        if (r < 0)
-                                goto finish;
-
-                        line_token = current->line_before;
-                        column_token = current->column_before;
-
-                        json_stack_release(current);
-                        n_stack--, current--;
-                        break;
-
-                case JSON_TOKEN_STRING:
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_NEXT_KEY, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        r = json_variant_new_string(&add, string);
-                        if (r < 0)
-                                goto finish;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (IN_SET(current->expect, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_NEXT_KEY))
-                                current->expect = EXPECT_OBJECT_COLON;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_COMMA;
-                        else {
-                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
-                                current->expect = EXPECT_ARRAY_COMMA;
-                        }
-
-                        break;
-
-                case JSON_TOKEN_REAL:
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        r = json_variant_new_real(&add, value.real);
-                        if (r < 0)
-                                goto finish;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_COMMA;
-                        else {
-                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
-                                current->expect = EXPECT_ARRAY_COMMA;
-                        }
-
-                        break;
-
-                case JSON_TOKEN_INTEGER:
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        r = json_variant_new_integer(&add, value.integer);
-                        if (r < 0)
-                                goto finish;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_COMMA;
-                        else {
-                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
-                                current->expect = EXPECT_ARRAY_COMMA;
-                        }
-
-                        break;
-
-                case JSON_TOKEN_UNSIGNED:
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        r = json_variant_new_unsigned(&add, value.unsig);
-                        if (r < 0)
-                                goto finish;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_COMMA;
-                        else {
-                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
-                                current->expect = EXPECT_ARRAY_COMMA;
-                        }
-
-                        break;
-
-                case JSON_TOKEN_BOOLEAN:
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        r = json_variant_new_boolean(&add, value.boolean);
-                        if (r < 0)
-                                goto finish;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_COMMA;
-                        else {
-                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
-                                current->expect = EXPECT_ARRAY_COMMA;
-                        }
-
-                        break;
-
-                case JSON_TOKEN_NULL:
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        r = json_variant_new_null(&add);
-                        if (r < 0)
-                                goto finish;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_COMMA;
-                        else {
-                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
-                                current->expect = EXPECT_ARRAY_COMMA;
-                        }
-
-                        break;
-
-                default:
-                        assert_not_reached("Unexpected token");
-                }
-
-                if (add) {
-                        (void) json_variant_set_source(&add, source, line_token, column_token);
-
-                        if (!GREEDY_REALLOC(current->elements, current->n_elements_allocated, current->n_elements + 1)) {
-                                r = -ENOMEM;
-                                goto finish;
-                        }
-
-                        current->elements[current->n_elements++] = add;
-                }
-        }
-
-done:
-        assert(n_stack == 1);
-        assert(stack[0].n_elements == 1);
-
-        *ret = json_variant_ref(stack[0].elements[0]);
-        *input = p;
-        r = 0;
-
-finish:
-        for (i = 0; i < n_stack; i++)
-                json_stack_release(stack + i);
-
-        free(stack);
-
-        return r;
-}
-
-int json_parse(const char *input, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
-        return json_parse_internal(&input, NULL, ret, ret_line, ret_column, false);
-}
-
-int json_parse_continue(const char **p, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
-        return json_parse_internal(p, NULL, ret, ret_line, ret_column, true);
-}
-
-int json_parse_file(FILE *f, const char *path, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
-        _cleanup_(json_source_unrefp) JsonSource *source = NULL;
-        _cleanup_free_ char *text = NULL;
-        const char *p;
-        int r;
-
-        if (f)
-                r = read_full_stream(f, &text, NULL);
-        else if (path)
-                r = read_full_file(path, &text, NULL);
-        else
-                return -EINVAL;
-        if (r < 0)
-                return r;
-
-        if (path) {
-                source = json_source_new(path);
-                if (!source)
-                        return -ENOMEM;
-        }
-
-        p = text;
-        return json_parse_internal(&p, source, ret, ret_line, ret_column, false);
-}
-
-int json_buildv(JsonVariant **ret, va_list ap) {
-        JsonStack *stack = NULL;
-        size_t n_stack = 1, n_stack_allocated = 0, i;
-        int r;
-
-        assert_return(ret, -EINVAL);
-
-        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack))
-                return -ENOMEM;
-
-        stack[0] = (JsonStack) {
-                .expect = EXPECT_TOPLEVEL,
-        };
-
-        for (;;) {
-                _cleanup_(json_variant_unrefp) JsonVariant *add = NULL;
-                JsonStack *current;
-                int command;
-
-                assert(n_stack > 0);
-                current = stack + n_stack - 1;
-
-                if (current->expect == EXPECT_END)
-                        goto done;
-
-                command = va_arg(ap, int);
-
-                switch (command) {
-
-                case _JSON_BUILD_STRING: {
-                        const char *p;
-
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        p = va_arg(ap, const char *);
-
-                        r = json_variant_new_string(&add, p);
-                        if (r < 0)
-                                goto finish;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_KEY;
-                        else
-                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
-
-                        break;
-                }
-
-                case _JSON_BUILD_INTEGER: {
-                        intmax_t j;
-
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        j = va_arg(ap, intmax_t);
-
-                        r = json_variant_new_integer(&add, j);
-                        if (r < 0)
-                                goto finish;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_KEY;
-                        else
-                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
-
-                        break;
-                }
-
-                case _JSON_BUILD_UNSIGNED: {
-                        uintmax_t j;
-
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        j = va_arg(ap, uintmax_t);
-
-                        r = json_variant_new_unsigned(&add, j);
-                        if (r < 0)
-                                goto finish;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_KEY;
-                        else
-                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
-
-                        break;
-                }
-
-                case _JSON_BUILD_REAL: {
-                        long double d;
-
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        d = va_arg(ap, long double);
-
-                        r = json_variant_new_real(&add, d);
-                        if (r < 0)
-                                goto finish;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_KEY;
-                        else
-                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
-
-                        break;
-                }
-
-                case _JSON_BUILD_BOOLEAN: {
-                        bool b;
-
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        b = va_arg(ap, int);
-
-                        r = json_variant_new_boolean(&add, b);
-                        if (r < 0)
-                                goto finish;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_KEY;
-                        else
-                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
-
-                        break;
-                }
-
-                case _JSON_BUILD_NULL:
-
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        r = json_variant_new_null(&add);
-                        if (r < 0)
-                                goto finish;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_KEY;
-                        else
-                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
-
-                        break;
-
-                case _JSON_BUILD_VARIANT:
-
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        add = va_arg(ap, JsonVariant*);
-                        if (!add)
-                                add = JSON_VARIANT_MAGIC_NULL;
-                        else
-                                json_variant_ref(add);
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_KEY;
-                        else
-                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
-
-                        break;
-
-                case _JSON_BUILD_LITERAL: {
-                        const char *l;
-
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        l = va_arg(ap, const char *);
-
-                        if (!l)
-                                add = JSON_VARIANT_MAGIC_NULL;
-                        else {
-                                r = json_parse(l, &add, NULL, NULL);
-                                if (r < 0)
-                                        goto finish;
-                        }
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_KEY;
-                        else
-                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
-
-                        break;
-                }
-
-                case _JSON_BUILD_ARRAY_BEGIN:
-
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) {
-                                r = -ENOMEM;
-                                goto finish;
-                        }
-                        current = stack + n_stack - 1;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_KEY;
-                        else
-                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
-
-                        stack[n_stack++] = (JsonStack) {
-                                .expect = EXPECT_ARRAY_ELEMENT,
-                        };
-
-                        break;
-
-                case _JSON_BUILD_ARRAY_END:
-                        if (current->expect != EXPECT_ARRAY_ELEMENT) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        assert(n_stack > 1);
-
-                        r = json_variant_new_array(&add, current->elements, current->n_elements);
-                        if (r < 0)
-                                goto finish;
-
-                        json_stack_release(current);
-                        n_stack--, current--;
-
-                        break;
-
-                case _JSON_BUILD_STRV: {
-                        char **l;
-
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        l = va_arg(ap, char **);
-
-                        r = json_variant_new_array_strv(&add, l);
-                        if (r < 0)
-                                goto finish;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_KEY;
-                        else
-                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
-
-                        break;
-                }
-
-                case _JSON_BUILD_OBJECT_BEGIN:
-
-                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) {
-                                r = -ENOMEM;
-                                goto finish;
-                        }
-                        current = stack + n_stack - 1;
-
-                        if (current->expect == EXPECT_TOPLEVEL)
-                                current->expect = EXPECT_END;
-                        else if (current->expect == EXPECT_OBJECT_VALUE)
-                                current->expect = EXPECT_OBJECT_KEY;
-                        else
-                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
-
-                        stack[n_stack++] = (JsonStack) {
-                                .expect = EXPECT_OBJECT_KEY,
-                        };
-
-                        break;
-
-                case _JSON_BUILD_OBJECT_END:
-
-                        if (current->expect != EXPECT_OBJECT_KEY) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        assert(n_stack > 1);
-
-                        r = json_variant_new_object(&add, current->elements, current->n_elements);
-                        if (r < 0)
-                                goto finish;
-
-                        json_stack_release(current);
-                        n_stack--, current--;
-
-                        break;
-
-                case _JSON_BUILD_PAIR: {
-                        const char *n;
-
-                        if (current->expect != EXPECT_OBJECT_KEY) {
-                                r = -EINVAL;
-                                goto finish;
-                        }
-
-                        n = va_arg(ap, const char *);
-
-                        r = json_variant_new_string(&add, n);
-                        if (r < 0)
-                                goto finish;
-
-                        current->expect = EXPECT_OBJECT_VALUE;
-                        break;
-                }}
-
-                if (add) {
-                        if (!GREEDY_REALLOC(current->elements, current->n_elements_allocated, current->n_elements + 1)) {
-                                r = -ENOMEM;
-                                goto finish;
-                        }
-
-                        current->elements[current->n_elements++] = TAKE_PTR(add);
-                }
-        }
-
-done:
-        assert(n_stack == 1);
-        assert(stack[0].n_elements == 1);
-
-        *ret = json_variant_ref(stack[0].elements[0]);
-        r = 0;
-
-finish:
-        for (i = 0; i < n_stack; i++)
-                json_stack_release(stack + i);
-
-        free(stack);
-
-        va_end(ap);
-
-        return r;
-}
-
-int json_build(JsonVariant **ret, ...) {
-        va_list ap;
-        int r;
-
-        va_start(ap, ret);
-        r = json_buildv(ret, ap);
-        va_end(ap);
-
-        return r;
-}
-
-int json_log_internal(
-                JsonVariant *variant,
-                int level,
-                int error,
-                const char *file,
-                int line,
-                const char *func,
-                const char *format, ...) {
-
-        PROTECT_ERRNO;
-
-        unsigned source_line, source_column;
-        char buffer[LINE_MAX];
-        const char *source;
-        va_list ap;
-        int r;
-
-        if (error < 0)
-                error = -error;
-
-        errno = error;
-
-        va_start(ap, format);
-        (void) vsnprintf(buffer, sizeof buffer, format, ap);
-        va_end(ap);
-
-        if (variant) {
-                r = json_variant_get_source(variant, &source, &source_line, &source_column);
-                if (r < 0)
-                        return r;
-        } else {
-                source = NULL;
-                source_line = 0;
-                source_column = 0;
-        }
-
-        if (source && source_line > 0 && source_column > 0)
-                return log_struct_internal(
-                                LOG_REALM_PLUS_LEVEL(LOG_REALM_SYSTEMD, level),
-                                error,
-                                file, line, func,
-                                "MESSAGE_ID=" SD_MESSAGE_INVALID_CONFIGURATION_STR,
-                                "CONFIG_FILE=%s", source,
-                                "CONFIG_LINE=%u", source_line,
-                                "CONFIG_COLUMN=%u", source_column,
-                                LOG_MESSAGE("%s:%u: %s", source, line, buffer),
-                                NULL);
-        else
-                return log_struct_internal(
-                                LOG_REALM_PLUS_LEVEL(LOG_REALM_SYSTEMD, level),
-                                error,
-                                file, line, func,
-                                "MESSAGE_ID=" SD_MESSAGE_INVALID_CONFIGURATION_STR,
-                                LOG_MESSAGE("%s", buffer),
-                                NULL);
-}
-
-int json_dispatch(JsonVariant *v, const JsonDispatch table[], JsonDispatchCallback bad, JsonDispatchFlags flags, void *userdata) {
-        const JsonDispatch *p;
-        size_t i, n, m;
-        int r, done = 0;
-        bool *found;
-
-        if (!json_variant_is_object(v)) {
-                json_log(v, flags, 0, "JSON variant is not an object.");
-
-                if (flags & JSON_PERMISSIVE)
-                        return 0;
-
-                return -EINVAL;
-        }
-
-        for (p = table, m = 0; p->name; p++)
-                m++;
-
-        found = newa0(bool, m);
-
-        n = json_variant_elements(v);
-        for (i = 0; i < n; i += 2) {
-                JsonVariant *key, *value;
-
-                assert_se(key = json_variant_by_index(v, i));
-                assert_se(value = json_variant_by_index(v, i+1));
-
-                for (p = table; p->name; p++)
-                        if (p->name == (const char*) -1 ||
-                            streq_ptr(json_variant_string(key), p->name))
-                                break;
-
-                if (p->name) { /* Found a matching entry! :-) */
-                        JsonDispatchFlags merged_flags;
-
-                        merged_flags = flags | p->flags;
-
-                        if (p->type != _JSON_VARIANT_TYPE_INVALID &&
-                            !json_variant_has_type(value, p->type)) {
-
-                                json_log(value, merged_flags, 0,
-                                         "Object field '%s' has wrong type %s, expected %s.", json_variant_string(key),
-                                         json_variant_type_to_string(json_variant_type(value)), json_variant_type_to_string(p->type));
-
-                                if (merged_flags & JSON_PERMISSIVE)
-                                        continue;
-
-                                return -EINVAL;
-                        }
-
-                        if (found[p-table]) {
-                                json_log(value, merged_flags, 0, "Duplicate object field '%s'.", json_variant_string(key));
-
-                                if (merged_flags & JSON_PERMISSIVE)
-                                        continue;
-
-                                return -ENOTUNIQ;
-                        }
-
-                        found[p-table] = true;
-
-                        if (p->callback) {
-                                r = p->callback(json_variant_string(key), value, merged_flags, (uint8_t*) userdata + p->offset);
-                                if (r < 0) {
-                                        if (merged_flags & JSON_PERMISSIVE)
-                                                continue;
-
-                                        return r;
-                                }
-                        }
-
-                        done ++;
-
-                } else { /* Didn't find a matching entry! :-( */
-
-                        if (bad) {
-                                r = bad(json_variant_string(key), value, flags, userdata);
-                                if (r < 0) {
-                                        if (flags & JSON_PERMISSIVE)
-                                                continue;
-
-                                        return r;
-                                } else
-                                        done ++;
-
-                        } else  {
-                                json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key));
-
-                                if (flags & JSON_PERMISSIVE)
-                                        continue;
-
-                                return -EADDRNOTAVAIL;
-                        }
-                }
-        }
-
-        for (p = table; p->name; p++) {
-                JsonDispatchFlags merged_flags = p->flags | flags;
-
-                if ((merged_flags & JSON_MANDATORY) && !found[p-table]) {
-                        json_log(v, merged_flags, 0, "Missing object field '%s'.", p->name);
-
-                        if ((merged_flags & JSON_PERMISSIVE))
-                                continue;
-
-                        return -ENXIO;
-                }
-        }
-
-        return done;
-}
-
-int json_dispatch_boolean(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
-        bool *b = userdata;
-
-        assert(variant);
-        assert(b);
-
-        if (!json_variant_is_boolean(variant)) {
-                json_log(variant, flags, 0, "JSON field '%s' is not a boolean.", strna(name));
-                return -EINVAL;
-        }
-
-        *b = json_variant_boolean(variant);
-        return 0;
-}
-
-int json_dispatch_tristate(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
-        int *b = userdata;
-
-        assert(variant);
-        assert(b);
-
-        if (!json_variant_is_boolean(variant)) {
-                json_log(variant, flags, 0, "JSON field '%s' is not a boolean.", strna(name));
-                return -EINVAL;
-        }
-
-        *b = json_variant_boolean(variant);
-        return 0;
-}
-
-int json_dispatch_integer(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
-        intmax_t *i = userdata;
-
-        assert(variant);
-        assert(i);
-
-        if (!json_variant_is_integer(variant)) {
-                json_log(variant, flags, 0, "JSON field '%s' is not an integer.", strna(name));
-                return -EINVAL;
-        }
-
-        *i = json_variant_integer(variant);
-        return 0;
-}
-
-int json_dispatch_unsigned(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
-        uintmax_t *u = userdata;
-
-        assert(variant);
-        assert(u);
-
-        if (!json_variant_is_unsigned(variant)) {
-                json_log(variant, flags, 0, "JSON field '%s' is not an unsigned integer.", strna(name));
-                return -EINVAL;
-        }
-
-        *u = json_variant_unsigned(variant);
-        return 0;
-}
-
-int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
-        uint32_t *u = userdata;
-
-        assert(variant);
-        assert(u);
-
-        if (!json_variant_is_unsigned(variant)) {
-                json_log(variant, flags, 0, "JSON field '%s' is not an unsigned integer.", strna(name));
-                return -EINVAL;
-        }
-
-        if (json_variant_unsigned(variant) > UINT32_MAX) {
-                json_log(variant, flags, 0, "JSON field '%s' out of bounds.", strna(name));
-                return -ERANGE;
-        }
-
-        *u = (uint32_t) json_variant_unsigned(variant);
-        return 0;
-}
-
-int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
-        int32_t *i = userdata;
-
-        assert(variant);
-        assert(i);
-
-        if (!json_variant_is_integer(variant)) {
-                json_log(variant, flags, 0, "JSON field '%s' is not an integer.", strna(name));
-                return -EINVAL;
-        }
-
-        if (json_variant_integer(variant) < INT32_MIN || json_variant_integer(variant) > INT32_MAX) {
-                json_log(variant, flags, 0, "JSON field '%s' out of bounds.", strna(name));
-                return -ERANGE;
-        }
-
-        *i = (int32_t) json_variant_integer(variant);
-        return 0;
-}
-
-int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
-        char **s = userdata;
-        int r;
-
-        assert(variant);
-        assert(s);
-
-        if (json_variant_is_null(variant)) {
-                *s = mfree(*s);
-                return 0;
-        }
-
-        if (!json_variant_is_string(variant)) {
-                json_log(variant, flags, 0, "JSON field '%s' is not a string.", strna(name));
-                return -EINVAL;
-        }
-
-        r = free_and_strdup(s, json_variant_string(variant));
-        if (r < 0)
-                return json_log(variant, flags, r, "Failed to allocate string: %m");
-
-        return 0;
-}
-
-int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
-        _cleanup_strv_free_ char **l = NULL;
-        char ***s = userdata;
-        size_t i;
-        int r;
-
-        assert(variant);
-        assert(s);
-
-        if (json_variant_is_null(variant)) {
-                *s = strv_free(*s);
-                return 0;
-        }
-
-        if (!json_variant_is_array(variant)) {
-                json_log(variant, 0, flags, "JSON field '%s' is not an array.", strna(name));
-                return -EINVAL;
-        }
-
-        for (i = 0; i < json_variant_elements(variant); i++) {
-                JsonVariant *e;
-
-                assert_se(e = json_variant_by_index(variant, i));
-
-                if (!json_variant_is_string(e)) {
-                        json_log(e, 0, flags, "JSON array element is not a string.");
-                        return -EINVAL;
-                }
-
-                r = strv_extend(&l, json_variant_string(e));
-                if (r < 0)
-                        return json_log(variant, flags, r, "Failed to append array element: %m");
-        }
-
-        strv_free_and_replace(*s, l);
-        return 0;
-}
-
-int json_dispatch_variant(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
-        JsonVariant **p = userdata;
-
-        assert(variant);
-        assert(p);
-
-        json_variant_unref(*p);
-        *p = json_variant_ref(variant);
-
-        return 0;
-}
-
-static const char* const json_variant_type_table[_JSON_VARIANT_TYPE_MAX] = {
-        [JSON_VARIANT_STRING] = "string",
-        [JSON_VARIANT_INTEGER] = "integer",
-        [JSON_VARIANT_UNSIGNED] = "unsigned",
-        [JSON_VARIANT_REAL] = "real",
-        [JSON_VARIANT_NUMBER] = "number",
-        [JSON_VARIANT_BOOLEAN] = "boolean",
-        [JSON_VARIANT_ARRAY] = "array",
-        [JSON_VARIANT_OBJECT] = "object",
-        [JSON_VARIANT_NULL] = "null",
-};
-
-DEFINE_STRING_TABLE_LOOKUP(json_variant_type, JsonVariantType);
diff --git a/src/basic/json.h b/src/basic/json.h
deleted file mode 100644 (file)
index c9482d2..0000000
+++ /dev/null
@@ -1,283 +0,0 @@
-/* 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);                                     \
-        })
-
-#define JSON_VARIANT_STRING_CONST(x) _JSON_VARIANT_STRING_CONST(UNIQ, (x))
-
-#define _JSON_VARIANT_STRING_CONST(xq, x)                                       \
-        ({                                                              \
-                __attribute__((__aligned__(2))) static const char UNIQ_T(json_string_const, xq)[] = (x); \
-                assert((((uintptr_t) UNIQ_T(json_string_const, xq)) & 1) == 0); \
-                (JsonVariant*) ((uintptr_t) UNIQ_T(json_string_const, xq) + 1); \
-        })
-
-const char *json_variant_type_to_string(JsonVariantType t);
-JsonVariantType json_variant_type_from_string(const char *s);
diff --git a/src/basic/lockfile-util.c b/src/basic/lockfile-util.c
deleted file mode 100644 (file)
index 4bae23b..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <errno.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/file.h>
-#include <sys/stat.h>
-
-#include "alloc-util.h"
-#include "fd-util.h"
-#include "fs-util.h"
-#include "lockfile-util.h"
-#include "macro.h"
-#include "path-util.h"
-
-int make_lock_file(const char *p, int operation, LockFile *ret) {
-        _cleanup_close_ int fd = -1;
-        _cleanup_free_ char *t = NULL;
-        int r;
-
-        /*
-         * We use UNPOSIX locks if they are available. They have nice
-         * semantics, and are mostly compatible with NFS. However,
-         * they are only available on new kernels. When we detect we
-         * are running on an older kernel, then we fall back to good
-         * old BSD locks. They also have nice semantics, but are
-         * slightly problematic on NFS, where they are upgraded to
-         * POSIX locks, even though locally they are orthogonal to
-         * POSIX locks.
-         */
-
-        t = strdup(p);
-        if (!t)
-                return -ENOMEM;
-
-        for (;;) {
-                struct flock fl = {
-                        .l_type = (operation & ~LOCK_NB) == LOCK_EX ? F_WRLCK : F_RDLCK,
-                        .l_whence = SEEK_SET,
-                };
-                struct stat st;
-
-                fd = open(p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600);
-                if (fd < 0)
-                        return -errno;
-
-                r = fcntl(fd, (operation & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &fl);
-                if (r < 0) {
-
-                        /* If the kernel is too old, use good old BSD locks */
-                        if (errno == EINVAL)
-                                r = flock(fd, operation);
-
-                        if (r < 0)
-                                return errno == EAGAIN ? -EBUSY : -errno;
-                }
-
-                /* If we acquired the lock, let's check if the file
-                 * still exists in the file system. If not, then the
-                 * previous exclusive owner removed it and then closed
-                 * it. In such a case our acquired lock is worthless,
-                 * hence try again. */
-
-                r = fstat(fd, &st);
-                if (r < 0)
-                        return -errno;
-                if (st.st_nlink > 0)
-                        break;
-
-                fd = safe_close(fd);
-        }
-
-        ret->path = t;
-        ret->fd = fd;
-        ret->operation = operation;
-
-        fd = -1;
-        t = NULL;
-
-        return r;
-}
-
-int make_lock_file_for(const char *p, int operation, LockFile *ret) {
-        const char *fn;
-        char *t;
-
-        assert(p);
-        assert(ret);
-
-        fn = basename(p);
-        if (!filename_is_valid(fn))
-                return -EINVAL;
-
-        t = newa(char, strlen(p) + 2 + 4 + 1);
-        stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), ".lck");
-
-        return make_lock_file(t, operation, ret);
-}
-
-void release_lock_file(LockFile *f) {
-        int r;
-
-        if (!f)
-                return;
-
-        if (f->path) {
-
-                /* If we are the exclusive owner we can safely delete
-                 * the lock file itself. If we are not the exclusive
-                 * owner, we can try becoming it. */
-
-                if (f->fd >= 0 &&
-                    (f->operation & ~LOCK_NB) == LOCK_SH) {
-                        static const struct flock fl = {
-                                .l_type = F_WRLCK,
-                                .l_whence = SEEK_SET,
-                        };
-
-                        r = fcntl(f->fd, F_OFD_SETLK, &fl);
-                        if (r < 0 && errno == EINVAL)
-                                r = flock(f->fd, LOCK_EX|LOCK_NB);
-
-                        if (r >= 0)
-                                f->operation = LOCK_EX|LOCK_NB;
-                }
-
-                if ((f->operation & ~LOCK_NB) == LOCK_EX)
-                        unlink_noerrno(f->path);
-
-                f->path = mfree(f->path);
-        }
-
-        f->fd = safe_close(f->fd);
-        f->operation = 0;
-}
diff --git a/src/basic/lockfile-util.h b/src/basic/lockfile-util.h
deleted file mode 100644 (file)
index c2abd99..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stddef.h>
-
-#include "macro.h"
-#include "missing.h"
-
-typedef struct LockFile {
-        char *path;
-        int fd;
-        int operation;
-} LockFile;
-
-int make_lock_file(const char *p, int operation, LockFile *ret);
-int make_lock_file_for(const char *p, int operation, LockFile *ret);
-void release_lock_file(LockFile *f);
-
-#define LOCK_FILE_INIT { .fd = -1, .path = NULL }
index 3e2325f41ff19a65d5ed354e75260f75f41e591d..3b3187de838bbe1a7aa1df8c2a29093ba323f83a 100644 (file)
@@ -15,23 +15,14 @@ basic_sources = files('''
         async.h
         audit-util.c
         audit-util.h
-        barrier.c
-        barrier.h
-        bitmap.c
-        bitmap.h
-        blkid-util.h
         blockdev-util.c
         blockdev-util.h
-        bpf-program.c
-        bpf-program.h
         btrfs-ctree.h
         btrfs-util.c
         btrfs-util.h
         build.h
         bus-label.c
         bus-label.h
-        calendarspec.c
-        calendarspec.h
         cap-list.c
         cap-list.h
         capability-util.c
@@ -40,16 +31,10 @@ basic_sources = files('''
         cgroup-util.h
         chattr-util.c
         chattr-util.h
-        clock-util.c
-        clock-util.h
         conf-files.c
         conf-files.h
         copy.c
         copy.h
-        cpu-set-util.c
-        cpu-set-util.h
-        crypt-util.c
-        crypt-util.h
         def.h
         device-nodes.c
         device-nodes.h
@@ -63,20 +48,12 @@ basic_sources = files('''
         escape.h
         ether-addr-util.c
         ether-addr-util.h
-        exec-util.c
-        exec-util.h
-        exit-status.c
-        exit-status.h
         extract-word.c
         extract-word.h
         fd-util.c
         fd-util.h
-        fileio-label.c
-        fileio-label.h
         fileio.c
         fileio.h
-        format-table.c
-        format-table.h
         format-util.h
         fs-util.c
         fs-util.h
@@ -97,11 +74,6 @@ basic_sources = files('''
         io-util.c
         io-util.h
         ioprio.h
-        journal-importer.c
-        journal-importer.h
-        json-internal.h
-        json.c
-        json.h
         khash.c
         khash.h
         label.c
@@ -109,8 +81,6 @@ basic_sources = files('''
         list.h
         locale-util.c
         locale-util.h
-        lockfile-util.c
-        lockfile-util.h
         log.c
         log.h
         login-util.c
@@ -129,8 +99,6 @@ basic_sources = files('''
         nss-util.h
         ordered-set.c
         ordered-set.h
-        os-util.c
-        os-util.h
         pager.c
         pager.h
         parse-util.c
@@ -151,17 +119,11 @@ basic_sources = files('''
         ratelimit.h
         raw-clone.h
         raw-reboot.h
-        reboot-util.c
-        reboot-util.h
         refcnt.h
         replace-var.c
         replace-var.h
-        rlimit-util.c
-        rlimit-util.h
         rm-rf.c
         rm-rf.h
-        securebits-util.c
-        securebits-util.h
         securebits.h
         selinux-util.c
         selinux-util.h
@@ -175,8 +137,6 @@ basic_sources = files('''
         smack-util.c
         smack-util.h
         socket-label.c
-        socket-protocol-list.c
-        socket-protocol-list.h
         socket-util.c
         socket-util.h
         sparse-endian.h
@@ -213,16 +173,10 @@ basic_sources = files('''
         utf8.h
         util.c
         util.h
-        verbs.c
-        verbs.h
         virt.c
         virt.h
-        web-util.c
-        web-util.h
         xattr-util.c
         xattr-util.h
-        xml.c
-        xml.h
 '''.split())
 
 missing_h = files('missing.h')
diff --git a/src/basic/os-util.c b/src/basic/os-util.c
deleted file mode 100644 (file)
index 82471a4..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "alloc-util.h"
-#include "fd-util.h"
-#include "fs-util.h"
-#include "macro.h"
-#include "os-util.h"
-#include "strv.h"
-#include "fileio.h"
-#include "string-util.h"
-
-int path_is_os_tree(const char *path) {
-        int r;
-
-        assert(path);
-
-        /* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir
-         * always results in -ENOENT, and we can properly distuingish the case where the whole root doesn't exist from
-         * the case where just the os-release file is missing. */
-        if (laccess(path, F_OK) < 0)
-                return -errno;
-
-        /* We use {/etc|/usr/lib}/os-release as flag file if something is an OS */
-        r = open_os_release(path, NULL, NULL);
-        if (r == -ENOENT) /* We got nothing */
-                return 0;
-        if (r < 0)
-                return r;
-
-        return 1;
-}
-
-int open_os_release(const char *root, char **ret_path, int *ret_fd) {
-        _cleanup_free_ char *q = NULL;
-        const char *p;
-        int k;
-
-        FOREACH_STRING(p, "/etc/os-release", "/usr/lib/os-release") {
-                k = chase_symlinks(p, root, CHASE_PREFIX_ROOT|(ret_fd ? CHASE_OPEN : 0), (ret_path ? &q : NULL));
-                if (k != -ENOENT)
-                        break;
-        }
-        if (k < 0)
-                return k;
-
-        if (ret_fd) {
-                int real_fd;
-
-                /* Convert the O_PATH fd into a proper, readable one */
-                real_fd = fd_reopen(k, O_RDONLY|O_CLOEXEC|O_NOCTTY);
-                safe_close(k);
-                if (real_fd < 0)
-                        return real_fd;
-
-                *ret_fd = real_fd;
-        }
-
-        if (ret_path)
-                *ret_path = TAKE_PTR(q);
-
-        return 0;
-}
-
-int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
-        _cleanup_free_ char *p = NULL;
-        _cleanup_close_ int fd = -1;
-        FILE *f;
-        int r;
-
-        if (!ret_file)
-                return open_os_release(root, ret_path, NULL);
-
-        r = open_os_release(root, ret_path ? &p : NULL, &fd);
-        if (r < 0)
-                return r;
-
-        f = fdopen(fd, "re");
-        if (!f)
-                return -errno;
-        fd = -1;
-
-        *ret_file = f;
-
-        if (ret_path)
-                *ret_path = TAKE_PTR(p);
-
-        return 0;
-}
-
-int parse_os_release(const char *root, ...) {
-        _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_free_ char *p = NULL;
-        va_list ap;
-        int r;
-
-        r = fopen_os_release(root, &p, &f);
-        if (r < 0)
-                return r;
-
-        va_start(ap, root);
-        r = parse_env_filev(f, p, ap);
-        va_end(ap);
-
-        return r;
-}
-
-int load_os_release_pairs(const char *root, char ***ret) {
-        _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_free_ char *p = NULL;
-        int r;
-
-        r = fopen_os_release(root, &p, &f);
-        if (r < 0)
-                return r;
-
-        return load_env_file_pairs(f, p, ret);
-}
diff --git a/src/basic/os-util.h b/src/basic/os-util.h
deleted file mode 100644 (file)
index 27ec7ac..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdio.h>
-
-int path_is_os_tree(const char *path);
-
-int open_os_release(const char *root, char **ret_path, int *ret_fd);
-int fopen_os_release(const char *root, char **ret_path, FILE **ret_file);
-
-int parse_os_release(const char *root, ...) _sentinel_;
-int load_os_release_pairs(const char *root, char ***ret);
diff --git a/src/basic/reboot-util.c b/src/basic/reboot-util.c
deleted file mode 100644 (file)
index ca40159..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <errno.h>
-#include <unistd.h>
-
-#include "alloc-util.h"
-#include "fileio.h"
-#include "log.h"
-#include "raw-reboot.h"
-#include "reboot-util.h"
-#include "string-util.h"
-#include "umask-util.h"
-#include "virt.h"
-
-int update_reboot_parameter_and_warn(const char *parameter) {
-        int r;
-
-        if (isempty(parameter)) {
-                if (unlink("/run/systemd/reboot-param") < 0) {
-                        if (errno == ENOENT)
-                                return 0;
-
-                        return log_warning_errno(errno, "Failed to unlink reboot parameter file: %m");
-                }
-
-                return 0;
-        }
-
-        RUN_WITH_UMASK(0022) {
-                r = write_string_file("/run/systemd/reboot-param", parameter,
-                                      WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
-                if (r < 0)
-                        return log_warning_errno(r, "Failed to write reboot parameter file: %m");
-        }
-
-        return 0;
-}
-
-int reboot_with_parameter(RebootFlags flags) {
-        int r;
-
-        /* Reboots the system with a parameter that is read from /run/systemd/reboot-param. Returns 0 if REBOOT_DRY_RUN
-         * was set and the actual reboot operation was hence skipped. If REBOOT_FALLBACK is set and the reboot with
-         * parameter doesn't work out a fallback to classic reboot() is attempted. If REBOOT_FALLBACK is not set, 0 is
-         * returned instead, which should be considered indication for the caller to fall back to reboot() on its own,
-         * or somehow else deal with this. If REBOOT_LOG is specified will log about what it is going to do, as well as
-         * all errors. */
-
-        if (detect_container() == 0) {
-                _cleanup_free_ char *parameter = NULL;
-
-                r = read_one_line_file("/run/systemd/reboot-param", &parameter);
-                if (r < 0 && r != -ENOENT)
-                        log_full_errno(flags & REBOOT_LOG ? LOG_WARNING : LOG_DEBUG, r,
-                                       "Failed to read reboot parameter file, ignoring: %m");
-
-                if (!isempty(parameter)) {
-
-                        log_full(flags & REBOOT_LOG ? LOG_INFO : LOG_DEBUG,
-                                 "Rebooting with argument '%s'.", parameter);
-
-                        if (flags & REBOOT_DRY_RUN)
-                                return 0;
-
-                        (void) raw_reboot(LINUX_REBOOT_CMD_RESTART2, parameter);
-
-                        log_full_errno(flags & REBOOT_LOG ? LOG_WARNING : LOG_DEBUG, errno,
-                                       "Failed to reboot with parameter, retrying without: %m");
-                }
-        }
-
-        if (!(flags & REBOOT_FALLBACK))
-                return 0;
-
-        log_full(flags & REBOOT_LOG ? LOG_INFO : LOG_DEBUG, "Rebooting.");
-
-        if (flags & REBOOT_DRY_RUN)
-                return 0;
-
-        (void) reboot(RB_AUTOBOOT);
-
-        return log_full_errno(flags & REBOOT_LOG ? LOG_ERR : LOG_DEBUG, errno, "Failed to reboot: %m");
-}
diff --git a/src/basic/reboot-util.h b/src/basic/reboot-util.h
deleted file mode 100644 (file)
index d459333..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-int update_reboot_parameter_and_warn(const char *parameter);
-
-typedef enum RebootFlags {
-        REBOOT_LOG      = 1 << 0, /* log about what we are going to do and all errors */
-        REBOOT_DRY_RUN  = 1 << 1, /* return 0 right before actually doing the reboot */
-        REBOOT_FALLBACK = 1 << 2, /* fallback to plain reboot() if argument-based reboot doesn't work, isn't configured or doesn't apply otherwise */
-} RebootFlags;
-
-int reboot_with_parameter(RebootFlags flags);
diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c
deleted file mode 100644 (file)
index c133f84..0000000
+++ /dev/null
@@ -1,391 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <errno.h>
-#include <sys/resource.h>
-
-#include "alloc-util.h"
-#include "extract-word.h"
-#include "fd-util.h"
-#include "format-util.h"
-#include "macro.h"
-#include "missing.h"
-#include "rlimit-util.h"
-#include "string-table.h"
-#include "time-util.h"
-
-int setrlimit_closest(int resource, const struct rlimit *rlim) {
-        struct rlimit highest, fixed;
-
-        assert(rlim);
-
-        if (setrlimit(resource, rlim) >= 0)
-                return 0;
-
-        if (errno != EPERM)
-                return -errno;
-
-        /* So we failed to set the desired setrlimit, then let's try
-         * to get as close as we can */
-        if (getrlimit(resource, &highest) < 0)
-                return -errno;
-
-        /* If the hard limit is unbounded anyway, then the EPERM had other reasons, let's propagate the original EPERM
-         * then */
-        if (highest.rlim_max == RLIM_INFINITY)
-                return -EPERM;
-
-        fixed = (struct rlimit) {
-                .rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max),
-                .rlim_max = MIN(rlim->rlim_max, highest.rlim_max),
-        };
-
-        /* Shortcut things if we wouldn't change anything. */
-        if (fixed.rlim_cur == highest.rlim_cur &&
-            fixed.rlim_max == highest.rlim_max)
-                return 0;
-
-        if (setrlimit(resource, &fixed) < 0)
-                return -errno;
-
-        return 0;
-}
-
-int setrlimit_closest_all(const struct rlimit *const *rlim, int *which_failed) {
-        int i, r;
-
-        assert(rlim);
-
-        /* On failure returns the limit's index that failed in *which_failed, but only if non-NULL */
-
-        for (i = 0; i < _RLIMIT_MAX; i++) {
-                if (!rlim[i])
-                        continue;
-
-                r = setrlimit_closest(i, rlim[i]);
-                if (r < 0) {
-                        if (which_failed)
-                                *which_failed = i;
-
-                        return r;
-                }
-        }
-
-        if (which_failed)
-                *which_failed = -1;
-
-        return 0;
-}
-
-static int rlimit_parse_u64(const char *val, rlim_t *ret) {
-        uint64_t u;
-        int r;
-
-        assert(val);
-        assert(ret);
-
-        if (streq(val, "infinity")) {
-                *ret = RLIM_INFINITY;
-                return 0;
-        }
-
-        /* setrlimit(2) suggests rlim_t is always 64bit on Linux. */
-        assert_cc(sizeof(rlim_t) == sizeof(uint64_t));
-
-        r = safe_atou64(val, &u);
-        if (r < 0)
-                return r;
-        if (u >= (uint64_t) RLIM_INFINITY)
-                return -ERANGE;
-
-        *ret = (rlim_t) u;
-        return 0;
-}
-
-static int rlimit_parse_size(const char *val, rlim_t *ret) {
-        uint64_t u;
-        int r;
-
-        assert(val);
-        assert(ret);
-
-        if (streq(val, "infinity")) {
-                *ret = RLIM_INFINITY;
-                return 0;
-        }
-
-        r = parse_size(val, 1024, &u);
-        if (r < 0)
-                return r;
-        if (u >= (uint64_t) RLIM_INFINITY)
-                return -ERANGE;
-
-        *ret = (rlim_t) u;
-        return 0;
-}
-
-static int rlimit_parse_sec(const char *val, rlim_t *ret) {
-        uint64_t u;
-        usec_t t;
-        int r;
-
-        assert(val);
-        assert(ret);
-
-        if (streq(val, "infinity")) {
-                *ret = RLIM_INFINITY;
-                return 0;
-        }
-
-        r = parse_sec(val, &t);
-        if (r < 0)
-                return r;
-        if (t == USEC_INFINITY) {
-                *ret = RLIM_INFINITY;
-                return 0;
-        }
-
-        u = (uint64_t) DIV_ROUND_UP(t, USEC_PER_SEC);
-        if (u >= (uint64_t) RLIM_INFINITY)
-                return -ERANGE;
-
-        *ret = (rlim_t) u;
-        return 0;
-}
-
-static int rlimit_parse_usec(const char *val, rlim_t *ret) {
-        usec_t t;
-        int r;
-
-        assert(val);
-        assert(ret);
-
-        if (streq(val, "infinity")) {
-                *ret = RLIM_INFINITY;
-                return 0;
-        }
-
-        r = parse_time(val, &t, 1);
-        if (r < 0)
-                return r;
-        if (t == USEC_INFINITY) {
-                *ret = RLIM_INFINITY;
-                return 0;
-        }
-
-        *ret = (rlim_t) t;
-        return 0;
-}
-
-static int rlimit_parse_nice(const char *val, rlim_t *ret) {
-        uint64_t rl;
-        int r;
-
-        /* So, Linux is weird. The range for RLIMIT_NICE is 40..1, mapping to the nice levels -20..19. However, the
-         * RLIMIT_NICE limit defaults to 0 by the kernel, i.e. a value that maps to nice level 20, which of course is
-         * bogus and does not exist. In order to permit parsing the RLIMIT_NICE of 0 here we hence implement a slight
-         * asymmetry: when parsing as positive nice level we permit 0..19. When parsing as negative nice level, we
-         * permit -20..0. But when parsing as raw resource limit value then we also allow the special value 0.
-         *
-         * Yeah, Linux is quality engineering sometimes... */
-
-        if (val[0] == '+') {
-
-                /* Prefixed with "+": Parse as positive user-friendly nice value */
-                r = safe_atou64(val + 1, &rl);
-                if (r < 0)
-                        return r;
-
-                if (rl >= PRIO_MAX)
-                        return -ERANGE;
-
-                rl = 20 - rl;
-
-        } else if (val[0] == '-') {
-
-                /* Prefixed with "-": Parse as negative user-friendly nice value */
-                r = safe_atou64(val + 1, &rl);
-                if (r < 0)
-                        return r;
-
-                if (rl > (uint64_t) (-PRIO_MIN))
-                        return -ERANGE;
-
-                rl = 20 + rl;
-        } else {
-
-                /* Not prefixed: parse as raw resource limit value */
-                r = safe_atou64(val, &rl);
-                if (r < 0)
-                        return r;
-
-                if (rl > (uint64_t) (20 - PRIO_MIN))
-                        return -ERANGE;
-        }
-
-        *ret = (rlim_t) rl;
-        return 0;
-}
-
-static int (*const rlimit_parse_table[_RLIMIT_MAX])(const char *val, rlim_t *ret) = {
-        [RLIMIT_CPU] = rlimit_parse_sec,
-        [RLIMIT_FSIZE] = rlimit_parse_size,
-        [RLIMIT_DATA] = rlimit_parse_size,
-        [RLIMIT_STACK] = rlimit_parse_size,
-        [RLIMIT_CORE] = rlimit_parse_size,
-        [RLIMIT_RSS] = rlimit_parse_size,
-        [RLIMIT_NOFILE] = rlimit_parse_u64,
-        [RLIMIT_AS] = rlimit_parse_size,
-        [RLIMIT_NPROC] = rlimit_parse_u64,
-        [RLIMIT_MEMLOCK] = rlimit_parse_size,
-        [RLIMIT_LOCKS] = rlimit_parse_u64,
-        [RLIMIT_SIGPENDING] = rlimit_parse_u64,
-        [RLIMIT_MSGQUEUE] = rlimit_parse_size,
-        [RLIMIT_NICE] = rlimit_parse_nice,
-        [RLIMIT_RTPRIO] = rlimit_parse_u64,
-        [RLIMIT_RTTIME] = rlimit_parse_usec,
-};
-
-int rlimit_parse_one(int resource, const char *val, rlim_t *ret) {
-        assert(val);
-        assert(ret);
-
-        if (resource < 0)
-                return -EINVAL;
-        if (resource >= _RLIMIT_MAX)
-                return -EINVAL;
-
-        return rlimit_parse_table[resource](val, ret);
-}
-
-int rlimit_parse(int resource, const char *val, struct rlimit *ret) {
-        _cleanup_free_ char *hard = NULL, *soft = NULL;
-        rlim_t hl, sl;
-        int r;
-
-        assert(val);
-        assert(ret);
-
-        r = extract_first_word(&val, &soft, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
-        if (r < 0)
-                return r;
-        if (r == 0)
-                return -EINVAL;
-
-        r = rlimit_parse_one(resource, soft, &sl);
-        if (r < 0)
-                return r;
-
-        r = extract_first_word(&val, &hard, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
-        if (r < 0)
-                return r;
-        if (!isempty(val))
-                return -EINVAL;
-        if (r == 0)
-                hl = sl;
-        else {
-                r = rlimit_parse_one(resource, hard, &hl);
-                if (r < 0)
-                        return r;
-                if (sl > hl)
-                        return -EILSEQ;
-        }
-
-        *ret = (struct rlimit) {
-                .rlim_cur = sl,
-                .rlim_max = hl,
-        };
-
-        return 0;
-}
-
-int rlimit_format(const struct rlimit *rl, char **ret) {
-        char *s = NULL;
-
-        assert(rl);
-        assert(ret);
-
-        if (rl->rlim_cur >= RLIM_INFINITY && rl->rlim_max >= RLIM_INFINITY)
-                s = strdup("infinity");
-        else if (rl->rlim_cur >= RLIM_INFINITY)
-                (void) asprintf(&s, "infinity:" RLIM_FMT, rl->rlim_max);
-        else if (rl->rlim_max >= RLIM_INFINITY)
-                (void) asprintf(&s, RLIM_FMT ":infinity", rl->rlim_cur);
-        else if (rl->rlim_cur == rl->rlim_max)
-                (void) asprintf(&s, RLIM_FMT, rl->rlim_cur);
-        else
-                (void) asprintf(&s, RLIM_FMT ":" RLIM_FMT, rl->rlim_cur, rl->rlim_max);
-
-        if (!s)
-                return -ENOMEM;
-
-        *ret = s;
-        return 0;
-}
-
-static const char* const rlimit_table[_RLIMIT_MAX] = {
-        [RLIMIT_AS]         = "AS",
-        [RLIMIT_CORE]       = "CORE",
-        [RLIMIT_CPU]        = "CPU",
-        [RLIMIT_DATA]       = "DATA",
-        [RLIMIT_FSIZE]      = "FSIZE",
-        [RLIMIT_LOCKS]      = "LOCKS",
-        [RLIMIT_MEMLOCK]    = "MEMLOCK",
-        [RLIMIT_MSGQUEUE]   = "MSGQUEUE",
-        [RLIMIT_NICE]       = "NICE",
-        [RLIMIT_NOFILE]     = "NOFILE",
-        [RLIMIT_NPROC]      = "NPROC",
-        [RLIMIT_RSS]        = "RSS",
-        [RLIMIT_RTPRIO]     = "RTPRIO",
-        [RLIMIT_RTTIME]     = "RTTIME",
-        [RLIMIT_SIGPENDING] = "SIGPENDING",
-        [RLIMIT_STACK]      = "STACK",
-};
-
-DEFINE_STRING_TABLE_LOOKUP(rlimit, int);
-
-int rlimit_from_string_harder(const char *s) {
-        const char *suffix;
-
-        /* The official prefix */
-        suffix = startswith(s, "RLIMIT_");
-        if (suffix)
-                return rlimit_from_string(suffix);
-
-        /* Our own unit file setting prefix */
-        suffix = startswith(s, "Limit");
-        if (suffix)
-                return rlimit_from_string(suffix);
-
-        return rlimit_from_string(s);
-}
-
-void rlimit_free_all(struct rlimit **rl) {
-        int i;
-
-        if (!rl)
-                return;
-
-        for (i = 0; i < _RLIMIT_MAX; i++)
-                rl[i] = mfree(rl[i]);
-}
-
-int rlimit_nofile_bump(int limit) {
-        int r;
-
-        /* Bumps the (soft) RLIMIT_NOFILE resource limit as close as possible to the specified limit. If a negative
-         * limit is specified, bumps it to the maximum the kernel and the hard resource limit allows. This call should
-         * be used by all our programs that might need a lot of fds, and that know how to deal with high fd numbers
-         * (i.e. do not use select() — which chokes on fds >= 1024) */
-
-        if (limit < 0)
-                limit = read_nr_open();
-
-        if (limit < 3)
-                limit = 3;
-
-        r = setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(limit));
-        if (r < 0)
-                return log_debug_errno(r, "Failed to set RLIMIT_NOFILE: %m");
-
-        return 0;
-}
diff --git a/src/basic/rlimit-util.h b/src/basic/rlimit-util.h
deleted file mode 100644 (file)
index 6139af3..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <sys/resource.h>
-
-#include "macro.h"
-
-const char *rlimit_to_string(int i) _const_;
-int rlimit_from_string(const char *s) _pure_;
-int rlimit_from_string_harder(const char *s) _pure_;
-
-int setrlimit_closest(int resource, const struct rlimit *rlim);
-int setrlimit_closest_all(const struct rlimit * const *rlim, int *which_failed);
-
-int rlimit_parse_one(int resource, const char *val, rlim_t *ret);
-int rlimit_parse(int resource, const char *val, struct rlimit *ret);
-
-int rlimit_format(const struct rlimit *rl, char **ret);
-
-void rlimit_free_all(struct rlimit **rl);
-
-#define RLIMIT_MAKE_CONST(lim) ((struct rlimit) { lim, lim })
-
-int rlimit_nofile_bump(int limit);
diff --git a/src/basic/securebits-util.c b/src/basic/securebits-util.c
deleted file mode 100644 (file)
index ad091f6..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <errno.h>
-#include <stdio.h>
-
-#include "alloc-util.h"
-#include "extract-word.h"
-#include "securebits.h"
-#include "securebits-util.h"
-#include "string-util.h"
-
-int secure_bits_to_string_alloc(int i, char **s) {
-        _cleanup_free_ char *str = NULL;
-        size_t len;
-        int r;
-
-        assert(s);
-
-        r = asprintf(&str, "%s%s%s%s%s%s",
-                     (i & (1 << SECURE_KEEP_CAPS)) ? "keep-caps " : "",
-                     (i & (1 << SECURE_KEEP_CAPS_LOCKED)) ? "keep-caps-locked " : "",
-                     (i & (1 << SECURE_NO_SETUID_FIXUP)) ? "no-setuid-fixup " : "",
-                     (i & (1 << SECURE_NO_SETUID_FIXUP_LOCKED)) ? "no-setuid-fixup-locked " : "",
-                     (i & (1 << SECURE_NOROOT)) ? "noroot " : "",
-                     (i & (1 << SECURE_NOROOT_LOCKED)) ? "noroot-locked " : "");
-        if (r < 0)
-                return -ENOMEM;
-
-        len = strlen(str);
-        if (len != 0)
-                str[len - 1] = '\0';
-
-        *s = TAKE_PTR(str);
-
-        return 0;
-}
-
-int secure_bits_from_string(const char *s) {
-        int secure_bits = 0;
-        const char *p;
-        int r;
-
-        for (p = s;;) {
-                _cleanup_free_ char *word = NULL;
-
-                r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
-                if (r == -ENOMEM)
-                        return r;
-                if (r <= 0)
-                        break;
-
-                if (streq(word, "keep-caps"))
-                        secure_bits |= 1 << SECURE_KEEP_CAPS;
-                else if (streq(word, "keep-caps-locked"))
-                        secure_bits |= 1 << SECURE_KEEP_CAPS_LOCKED;
-                else if (streq(word, "no-setuid-fixup"))
-                        secure_bits |= 1 << SECURE_NO_SETUID_FIXUP;
-                else if (streq(word, "no-setuid-fixup-locked"))
-                        secure_bits |= 1 << SECURE_NO_SETUID_FIXUP_LOCKED;
-                else if (streq(word, "noroot"))
-                        secure_bits |= 1 << SECURE_NOROOT;
-                else if (streq(word, "noroot-locked"))
-                        secure_bits |= 1 << SECURE_NOROOT_LOCKED;
-        }
-
-        return secure_bits;
-}
diff --git a/src/basic/securebits-util.h b/src/basic/securebits-util.h
deleted file mode 100644 (file)
index 3cb3cb3..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include "securebits.h"
-
-int secure_bits_to_string_alloc(int i, char **s);
-int secure_bits_from_string(const char *s);
-
-static inline bool secure_bits_is_valid(int i) {
-        return ((SECURE_ALL_BITS | SECURE_ALL_LOCKS) & i) == i;
-}
-
-static inline int secure_bits_to_string_alloc_with_check(int n, char **s) {
-        if (!secure_bits_is_valid(n))
-                return -EINVAL;
-
-        return secure_bits_to_string_alloc(n, s);
-}
diff --git a/src/basic/socket-protocol-list.c b/src/basic/socket-protocol-list.c
deleted file mode 100644 (file)
index 8041b84..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <netinet/in.h>
-#include <string.h>
-
-#include "socket-protocol-list.h"
-#include "macro.h"
-
-static const struct socket_protocol_name* lookup_socket_protocol(register const char *str, register GPERF_LEN_TYPE len);
-
-#include "socket-protocol-from-name.h"
-#include "socket-protocol-to-name.h"
-
-const char *socket_protocol_to_name(int id) {
-
-        if (id < 0)
-                return NULL;
-
-        if (id >= (int) ELEMENTSOF(socket_protocol_names))
-                return NULL;
-
-        return socket_protocol_names[id];
-}
-
-int socket_protocol_from_name(const char *name) {
-        const struct socket_protocol_name *sc;
-
-        assert(name);
-
-        sc = lookup_socket_protocol(name, strlen(name));
-        if (!sc)
-                return 0;
-
-        return sc->id;
-}
-
-int socket_protocol_max(void) {
-        return ELEMENTSOF(socket_protocol_names);
-}
diff --git a/src/basic/socket-protocol-list.h b/src/basic/socket-protocol-list.h
deleted file mode 100644 (file)
index 458904d..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-const char *socket_protocol_to_name(int id);
-int socket_protocol_from_name(const char *name);
-
-int socket_protocol_max(void);
diff --git a/src/basic/verbs.c b/src/basic/verbs.c
deleted file mode 100644 (file)
index f68c2ba..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <errno.h>
-#include <getopt.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <string.h>
-
-#include "env-util.h"
-#include "log.h"
-#include "macro.h"
-#include "process-util.h"
-#include "string-util.h"
-#include "verbs.h"
-#include "virt.h"
-
-/* Wraps running_in_chroot() which is used in various places, but also adds an environment variable check so external
- * processes can reliably force this on.
- */
-bool running_in_chroot_or_offline(void) {
-        int r;
-
-        /* Added to support use cases like rpm-ostree, where from %post scripts we only want to execute "preset", but
-         * not "start"/"restart" for example.
-         *
-         * See docs/ENVIRONMENT.md for docs.
-         */
-        r = getenv_bool("SYSTEMD_OFFLINE");
-        if (r < 0 && r != -ENXIO)
-                log_debug_errno(r, "Failed to parse $SYSTEMD_OFFLINE: %m");
-        else if (r >= 0)
-                return r > 0;
-
-        /* We've had this condition check for a long time which basically checks for legacy chroot case like Fedora's
-         * "mock", which is used for package builds.  We don't want to try to start systemd services there, since
-         * without --new-chroot we don't even have systemd running, and even if we did, adding a concept of background
-         * daemons to builds would be an enormous change, requiring considering things like how the journal output is
-         * handled, etc.  And there's really not a use case today for a build talking to a service.
-         *
-         * Note this call itself also looks for a different variable SYSTEMD_IGNORE_CHROOT=1.
-         */
-        r = running_in_chroot();
-        if (r < 0)
-                log_debug_errno(r, "running_in_chroot(): %m");
-
-        return r > 0;
-}
-
-int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) {
-        const Verb *verb;
-        const char *name;
-        unsigned i;
-        int left, r;
-
-        assert(verbs);
-        assert(verbs[0].dispatch);
-        assert(argc >= 0);
-        assert(argv);
-        assert(argc >= optind);
-
-        left = argc - optind;
-        argv += optind;
-        optind = 0;
-        name = argv[0];
-
-        for (i = 0;; i++) {
-                bool found;
-
-                /* At the end of the list? */
-                if (!verbs[i].dispatch) {
-                        if (name)
-                                log_error("Unknown operation %s.", name);
-                        else
-                                log_error("Requires operation parameter.");
-                        return -EINVAL;
-                }
-
-                if (name)
-                        found = streq(name, verbs[i].verb);
-                else
-                        found = verbs[i].flags & VERB_DEFAULT;
-
-                if (found) {
-                        verb = &verbs[i];
-                        break;
-                }
-        }
-
-        assert(verb);
-
-        if (!name)
-                left = 1;
-
-        if (verb->min_args != VERB_ANY &&
-            (unsigned) left < verb->min_args) {
-                log_error("Too few arguments.");
-                return -EINVAL;
-        }
-
-        if (verb->max_args != VERB_ANY &&
-            (unsigned) left > verb->max_args) {
-                log_error("Too many arguments.");
-                return -EINVAL;
-        }
-
-        if ((verb->flags & VERB_ONLINE_ONLY) && running_in_chroot_or_offline()) {
-                if (name)
-                        log_info("Running in chroot, ignoring request: %s", name);
-                else
-                        log_info("Running in chroot, ignoring request.");
-                return 0;
-        }
-
-        if (verb->flags & VERB_MUST_BE_ROOT) {
-                r = must_be_root();
-                if (r < 0)
-                        return r;
-        }
-
-        if (name)
-                return verb->dispatch(left, argv, userdata);
-        else {
-                char* fake[2] = {
-                        (char*) verb->verb,
-                        NULL
-                };
-
-                return verb->dispatch(1, fake, userdata);
-        }
-}
diff --git a/src/basic/verbs.h b/src/basic/verbs.h
deleted file mode 100644 (file)
index e174255..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#define VERB_ANY ((unsigned) -1)
-
-typedef enum VerbFlags {
-        VERB_DEFAULT      = 1 << 0,
-        VERB_ONLINE_ONLY  = 1 << 1,
-        VERB_MUST_BE_ROOT = 1 << 2,
-} VerbFlags;
-
-typedef struct {
-        const char *verb;
-        unsigned min_args, max_args;
-        VerbFlags flags;
-        int (* const dispatch)(int argc, char *argv[], void *userdata);
-} Verb;
-
-bool running_in_chroot_or_offline(void);
-
-int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata);
diff --git a/src/basic/web-util.c b/src/basic/web-util.c
deleted file mode 100644 (file)
index 82221af..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <stdbool.h>
-
-#include "string-util.h"
-#include "utf8.h"
-#include "web-util.h"
-
-bool http_etag_is_valid(const char *etag) {
-        if (isempty(etag))
-                return false;
-
-        if (!endswith(etag, "\""))
-                return false;
-
-        if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
-                return false;
-
-        return true;
-}
-
-bool http_url_is_valid(const char *url) {
-        const char *p;
-
-        if (isempty(url))
-                return false;
-
-        p = startswith(url, "http://");
-        if (!p)
-                p = startswith(url, "https://");
-        if (!p)
-                return false;
-
-        if (isempty(p))
-                return false;
-
-        return ascii_is_valid(p);
-}
-
-bool documentation_url_is_valid(const char *url) {
-        const char *p;
-
-        if (isempty(url))
-                return false;
-
-        if (http_url_is_valid(url))
-                return true;
-
-        p = startswith(url, "file:/");
-        if (!p)
-                p = startswith(url, "info:");
-        if (!p)
-                p = startswith(url, "man:");
-
-        if (isempty(p))
-                return false;
-
-        return ascii_is_valid(p);
-}
diff --git a/src/basic/web-util.h b/src/basic/web-util.h
deleted file mode 100644 (file)
index c9e67e5..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-
-#include "macro.h"
-
-bool http_url_is_valid(const char *url) _pure_;
-
-bool documentation_url_is_valid(const char *url) _pure_;
-
-bool http_etag_is_valid(const char *etag);
diff --git a/src/basic/xml.c b/src/basic/xml.c
deleted file mode 100644 (file)
index cb34d87..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <errno.h>
-#include <stddef.h>
-#include <string.h>
-
-#include "macro.h"
-#include "string-util.h"
-#include "xml.h"
-
-enum {
-        STATE_NULL,
-        STATE_TEXT,
-        STATE_TAG,
-        STATE_ATTRIBUTE,
-};
-
-static void inc_lines(unsigned *line, const char *s, size_t n) {
-        const char *p = s;
-
-        if (!line)
-                return;
-
-        for (;;) {
-                const char *f;
-
-                f = memchr(p, '\n', n);
-                if (!f)
-                        return;
-
-                n -= (f - p) + 1;
-                p = f + 1;
-                (*line)++;
-        }
-}
-
-/* We don't actually do real XML here. We only read a simplistic
- * subset, that is a bit less strict that XML and lacks all the more
- * complex features, like entities, or namespaces. However, we do
- * support some HTML5-like simplifications */
-
-int xml_tokenize(const char **p, char **name, void **state, unsigned *line) {
-        const char *c, *e, *b;
-        char *ret;
-        int t;
-
-        assert(p);
-        assert(*p);
-        assert(name);
-        assert(state);
-
-        t = PTR_TO_INT(*state);
-        c = *p;
-
-        if (t == STATE_NULL) {
-                if (line)
-                        *line = 1;
-                t = STATE_TEXT;
-        }
-
-        for (;;) {
-                if (*c == 0)
-                        return XML_END;
-
-                switch (t) {
-
-                case STATE_TEXT: {
-                        int x;
-
-                        e = strchrnul(c, '<');
-                        if (e > c) {
-                                /* More text... */
-                                ret = strndup(c, e - c);
-                                if (!ret)
-                                        return -ENOMEM;
-
-                                inc_lines(line, c, e - c);
-
-                                *name = ret;
-                                *p = e;
-                                *state = INT_TO_PTR(STATE_TEXT);
-
-                                return XML_TEXT;
-                        }
-
-                        assert(*e == '<');
-                        b = c + 1;
-
-                        if (startswith(b, "!--")) {
-                                /* A comment */
-                                e = strstr(b + 3, "-->");
-                                if (!e)
-                                        return -EINVAL;
-
-                                inc_lines(line, b, e + 3 - b);
-
-                                c = e + 3;
-                                continue;
-                        }
-
-                        if (*b == '?') {
-                                /* Processing instruction */
-
-                                e = strstr(b + 1, "?>");
-                                if (!e)
-                                        return -EINVAL;
-
-                                inc_lines(line, b, e + 2 - b);
-
-                                c = e + 2;
-                                continue;
-                        }
-
-                        if (*b == '!') {
-                                /* DTD */
-
-                                e = strchr(b + 1, '>');
-                                if (!e)
-                                        return -EINVAL;
-
-                                inc_lines(line, b, e + 1 - b);
-
-                                c = e + 1;
-                                continue;
-                        }
-
-                        if (*b == '/') {
-                                /* A closing tag */
-                                x = XML_TAG_CLOSE;
-                                b++;
-                        } else
-                                x = XML_TAG_OPEN;
-
-                        e = strpbrk(b, WHITESPACE "/>");
-                        if (!e)
-                                return -EINVAL;
-
-                        ret = strndup(b, e - b);
-                        if (!ret)
-                                return -ENOMEM;
-
-                        *name = ret;
-                        *p = e;
-                        *state = INT_TO_PTR(STATE_TAG);
-
-                        return x;
-                }
-
-                case STATE_TAG:
-
-                        b = c + strspn(c, WHITESPACE);
-                        if (*b == 0)
-                                return -EINVAL;
-
-                        inc_lines(line, c, b - c);
-
-                        e = b + strcspn(b, WHITESPACE "=/>");
-                        if (e > b) {
-                                /* An attribute */
-
-                                ret = strndup(b, e - b);
-                                if (!ret)
-                                        return -ENOMEM;
-
-                                *name = ret;
-                                *p = e;
-                                *state = INT_TO_PTR(STATE_ATTRIBUTE);
-
-                                return XML_ATTRIBUTE_NAME;
-                        }
-
-                        if (startswith(b, "/>")) {
-                                /* An empty tag */
-
-                                *name = NULL; /* For empty tags we return a NULL name, the caller must be prepared for that */
-                                *p = b + 2;
-                                *state = INT_TO_PTR(STATE_TEXT);
-
-                                return XML_TAG_CLOSE_EMPTY;
-                        }
-
-                        if (*b != '>')
-                                return -EINVAL;
-
-                        c = b + 1;
-                        t = STATE_TEXT;
-                        continue;
-
-                case STATE_ATTRIBUTE:
-
-                        if (*c == '=') {
-                                c++;
-
-                                if (IN_SET(*c, '\'', '\"')) {
-                                        /* Tag with a quoted value */
-
-                                        e = strchr(c+1, *c);
-                                        if (!e)
-                                                return -EINVAL;
-
-                                        inc_lines(line, c, e - c);
-
-                                        ret = strndup(c+1, e - c - 1);
-                                        if (!ret)
-                                                return -ENOMEM;
-
-                                        *name = ret;
-                                        *p = e + 1;
-                                        *state = INT_TO_PTR(STATE_TAG);
-
-                                        return XML_ATTRIBUTE_VALUE;
-
-                                }
-
-                                /* Tag with a value without quotes */
-
-                                b = strpbrk(c, WHITESPACE ">");
-                                if (!b)
-                                        b = c;
-
-                                ret = strndup(c, b - c);
-                                if (!ret)
-                                        return -ENOMEM;
-
-                                *name = ret;
-                                *p = b;
-                                *state = INT_TO_PTR(STATE_TAG);
-                                return XML_ATTRIBUTE_VALUE;
-                        }
-
-                        t = STATE_TAG;
-                        continue;
-                }
-
-        }
-
-        assert_not_reached("Bad state");
-}
diff --git a/src/basic/xml.h b/src/basic/xml.h
deleted file mode 100644 (file)
index 8da2ff5..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-enum {
-        XML_END,
-        XML_TEXT,
-        XML_TAG_OPEN,
-        XML_TAG_CLOSE,
-        XML_TAG_CLOSE_EMPTY,
-        XML_ATTRIBUTE_NAME,
-        XML_ATTRIBUTE_VALUE,
-};
-
-int xml_tokenize(const char **p, char **name, void **state, unsigned *line);
diff --git a/src/shared/barrier.c b/src/shared/barrier.c
new file mode 100644 (file)
index 0000000..79316dd
--- /dev/null
@@ -0,0 +1,398 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/eventfd.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "barrier.h"
+#include "fd-util.h"
+#include "macro.h"
+
+/**
+ * Barriers
+ * This barrier implementation provides a simple synchronization method based
+ * on file-descriptors that can safely be used between threads and processes. A
+ * barrier object contains 2 shared counters based on eventfd. Both processes
+ * can now place barriers and wait for the other end to reach a random or
+ * specific barrier.
+ * Barriers are numbered, so you can either wait for the other end to reach any
+ * barrier or the last barrier that you placed. This way, you can use barriers
+ * for one-way *and* full synchronization. Note that even-though barriers are
+ * numbered, these numbers are internal and recycled once both sides reached the
+ * same barrier (implemented as a simple signed counter). It is thus not
+ * possible to address barriers by their ID.
+ *
+ * Barrier-API: Both ends can place as many barriers via barrier_place() as
+ * they want and each pair of barriers on both sides will be implicitly linked.
+ * Each side can use the barrier_wait/sync_*() family of calls to wait for the
+ * other side to place a specific barrier. barrier_wait_next() waits until the
+ * other side calls barrier_place(). No links between the barriers are
+ * considered and this simply serves as most basic asynchronous barrier.
+ * barrier_sync_next() is like barrier_wait_next() and waits for the other side
+ * to place their next barrier via barrier_place(). However, it only waits for
+ * barriers that are linked to a barrier we already placed. If the other side
+ * already placed more barriers than we did, barrier_sync_next() returns
+ * immediately.
+ * barrier_sync() extends barrier_sync_next() and waits until the other end
+ * placed as many barriers via barrier_place() as we did. If they already placed
+ * as many as we did (or more), it returns immediately.
+ *
+ * Additionally to basic barriers, an abortion event is available.
+ * barrier_abort() places an abortion event that cannot be undone. An abortion
+ * immediately cancels all placed barriers and replaces them. Any running and
+ * following wait/sync call besides barrier_wait_abortion() will immediately
+ * return false on both sides (otherwise, they always return true).
+ * barrier_abort() can be called multiple times on both ends and will be a
+ * no-op if already called on this side.
+ * barrier_wait_abortion() can be used to wait for the other side to call
+ * barrier_abort() and is the only wait/sync call that does not return
+ * immediately if we aborted outself. It only returns once the other side
+ * called barrier_abort().
+ *
+ * Barriers can be used for in-process and inter-process synchronization.
+ * However, for in-process synchronization you could just use mutexes.
+ * Therefore, main target is IPC and we require both sides to *not* share the FD
+ * table. If that's given, barriers provide target tracking: If the remote side
+ * exit()s, an abortion event is implicitly queued on the other side. This way,
+ * a sync/wait call will be woken up if the remote side crashed or exited
+ * unexpectedly. However, note that these abortion events are only queued if the
+ * barrier-queue has been drained. Therefore, it is safe to place a barrier and
+ * exit. The other side can safely wait on the barrier even though the exit
+ * queued an abortion event. Usually, the abortion event would overwrite the
+ * barrier, however, that's not true for exit-abortion events. Those are only
+ * queued if the barrier-queue is drained (thus, the receiving side has placed
+ * more barriers than the remote side).
+ */
+
+/**
+ * barrier_create() - Initialize a barrier object
+ * @obj: barrier to initialize
+ *
+ * This initializes a barrier object. The caller is responsible of allocating
+ * the memory and keeping it valid. The memory does not have to be zeroed
+ * beforehand.
+ * Two eventfd objects are allocated for each barrier. If allocation fails, an
+ * error is returned.
+ *
+ * If this function fails, the barrier is reset to an invalid state so it is
+ * safe to call barrier_destroy() on the object regardless whether the
+ * initialization succeeded or not.
+ *
+ * The caller is responsible to destroy the object via barrier_destroy() before
+ * releasing the underlying memory.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int barrier_create(Barrier *b) {
+        _cleanup_(barrier_destroyp) Barrier *staging = b;
+        int r;
+
+        assert(b);
+
+        b->me = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
+        if (b->me < 0)
+                return -errno;
+
+        b->them = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
+        if (b->them < 0)
+                return -errno;
+
+        r = pipe2(b->pipe, O_CLOEXEC | O_NONBLOCK);
+        if (r < 0)
+                return -errno;
+
+        staging = NULL;
+        return 0;
+}
+
+/**
+ * barrier_destroy() - Destroy a barrier object
+ * @b: barrier to destroy or NULL
+ *
+ * This destroys a barrier object that has previously been passed to
+ * barrier_create(). The object is released and reset to invalid
+ * state. Therefore, it is safe to call barrier_destroy() multiple
+ * times or even if barrier_create() failed. However, barrier must be
+ * always initialized with BARRIER_NULL.
+ *
+ * If @b is NULL, this is a no-op.
+ */
+void barrier_destroy(Barrier *b) {
+        if (!b)
+                return;
+
+        b->me = safe_close(b->me);
+        b->them = safe_close(b->them);
+        safe_close_pair(b->pipe);
+        b->barriers = 0;
+}
+
+/**
+ * barrier_set_role() - Set the local role of the barrier
+ * @b: barrier to operate on
+ * @role: role to set on the barrier
+ *
+ * This sets the roles on a barrier object. This is needed to know
+ * which side of the barrier you're on. Usually, the parent creates
+ * the barrier via barrier_create() and then calls fork() or clone().
+ * Therefore, the FDs are duplicated and the child retains the same
+ * barrier object.
+ *
+ * Both sides need to call barrier_set_role() after fork() or clone()
+ * are done. If this is not done, barriers will not work correctly.
+ *
+ * Note that barriers could be supported without fork() or clone(). However,
+ * this is currently not needed so it hasn't been implemented.
+ */
+void barrier_set_role(Barrier *b, unsigned role) {
+        int fd;
+
+        assert(b);
+        assert(IN_SET(role, BARRIER_PARENT, BARRIER_CHILD));
+        /* make sure this is only called once */
+        assert(b->pipe[0] >= 0 && b->pipe[1] >= 0);
+
+        if (role == BARRIER_PARENT)
+                b->pipe[1] = safe_close(b->pipe[1]);
+        else {
+                b->pipe[0] = safe_close(b->pipe[0]);
+
+                /* swap me/them for children */
+                fd = b->me;
+                b->me = b->them;
+                b->them = fd;
+        }
+}
+
+/* places barrier; returns false if we aborted, otherwise true */
+static bool barrier_write(Barrier *b, uint64_t buf) {
+        ssize_t len;
+
+        /* prevent new sync-points if we already aborted */
+        if (barrier_i_aborted(b))
+                return false;
+
+        assert(b->me >= 0);
+        do {
+                len = write(b->me, &buf, sizeof(buf));
+        } while (len < 0 && IN_SET(errno, EAGAIN, EINTR));
+
+        if (len != sizeof(buf))
+                goto error;
+
+        /* lock if we aborted */
+        if (buf >= (uint64_t)BARRIER_ABORTION) {
+                if (barrier_they_aborted(b))
+                        b->barriers = BARRIER_WE_ABORTED;
+                else
+                        b->barriers = BARRIER_I_ABORTED;
+        } else if (!barrier_is_aborted(b))
+                b->barriers += buf;
+
+        return !barrier_i_aborted(b);
+
+error:
+        /* If there is an unexpected error, we have to make this fatal. There
+         * is no way we can recover from sync-errors. Therefore, we close the
+         * pipe-ends and treat this as abortion. The other end will notice the
+         * pipe-close and treat it as abortion, too. */
+
+        safe_close_pair(b->pipe);
+        b->barriers = BARRIER_WE_ABORTED;
+        return false;
+}
+
+/* waits for barriers; returns false if they aborted, otherwise true */
+static bool barrier_read(Barrier *b, int64_t comp) {
+        if (barrier_they_aborted(b))
+                return false;
+
+        while (b->barriers > comp) {
+                struct pollfd pfd[2] = {
+                        { .fd = b->pipe[0] >= 0 ? b->pipe[0] : b->pipe[1],
+                          .events = POLLHUP },
+                        { .fd = b->them,
+                          .events = POLLIN }};
+                uint64_t buf;
+                int r;
+
+                r = poll(pfd, 2, -1);
+                if (r < 0 && IN_SET(errno, EAGAIN, EINTR))
+                        continue;
+                else if (r < 0)
+                        goto error;
+
+                if (pfd[1].revents) {
+                        ssize_t len;
+
+                        /* events on @them signal new data for us */
+                        len = read(b->them, &buf, sizeof(buf));
+                        if (len < 0 && IN_SET(errno, EAGAIN, EINTR))
+                                continue;
+
+                        if (len != sizeof(buf))
+                                goto error;
+                } else if (pfd[0].revents & (POLLHUP | POLLERR | POLLNVAL))
+                        /* POLLHUP on the pipe tells us the other side exited.
+                         * We treat this as implicit abortion. But we only
+                         * handle it if there's no event on the eventfd. This
+                         * guarantees that exit-abortions do not overwrite real
+                         * barriers. */
+                        buf = BARRIER_ABORTION;
+                else
+                        continue;
+
+                /* lock if they aborted */
+                if (buf >= (uint64_t)BARRIER_ABORTION) {
+                        if (barrier_i_aborted(b))
+                                b->barriers = BARRIER_WE_ABORTED;
+                        else
+                                b->barriers = BARRIER_THEY_ABORTED;
+                } else if (!barrier_is_aborted(b))
+                        b->barriers -= buf;
+        }
+
+        return !barrier_they_aborted(b);
+
+error:
+        /* If there is an unexpected error, we have to make this fatal. There
+         * is no way we can recover from sync-errors. Therefore, we close the
+         * pipe-ends and treat this as abortion. The other end will notice the
+         * pipe-close and treat it as abortion, too. */
+
+        safe_close_pair(b->pipe);
+        b->barriers = BARRIER_WE_ABORTED;
+        return false;
+}
+
+/**
+ * barrier_place() - Place a new barrier
+ * @b: barrier object
+ *
+ * This places a new barrier on the barrier object. If either side already
+ * aborted, this is a no-op and returns "false". Otherwise, the barrier is
+ * placed and this returns "true".
+ *
+ * Returns: true if barrier was placed, false if either side aborted.
+ */
+bool barrier_place(Barrier *b) {
+        assert(b);
+
+        if (barrier_is_aborted(b))
+                return false;
+
+        barrier_write(b, BARRIER_SINGLE);
+        return true;
+}
+
+/**
+ * barrier_abort() - Abort the synchronization
+ * @b: barrier object to abort
+ *
+ * This aborts the barrier-synchronization. If barrier_abort() was already
+ * called on this side, this is a no-op. Otherwise, the barrier is put into the
+ * ABORT-state and will stay there. The other side is notified about the
+ * abortion. Any following attempt to place normal barriers or to wait on normal
+ * barriers will return immediately as "false".
+ *
+ * You can wait for the other side to call barrier_abort(), too. Use
+ * barrier_wait_abortion() for that.
+ *
+ * Returns: false if the other side already aborted, true otherwise.
+ */
+bool barrier_abort(Barrier *b) {
+        assert(b);
+
+        barrier_write(b, BARRIER_ABORTION);
+        return !barrier_they_aborted(b);
+}
+
+/**
+ * barrier_wait_next() - Wait for the next barrier of the other side
+ * @b: barrier to operate on
+ *
+ * This waits until the other side places its next barrier. This is independent
+ * of any barrier-links and just waits for any next barrier of the other side.
+ *
+ * If either side aborted, this returns false.
+ *
+ * Returns: false if either side aborted, true otherwise.
+ */
+bool barrier_wait_next(Barrier *b) {
+        assert(b);
+
+        if (barrier_is_aborted(b))
+                return false;
+
+        barrier_read(b, b->barriers - 1);
+        return !barrier_is_aborted(b);
+}
+
+/**
+ * barrier_wait_abortion() - Wait for the other side to abort
+ * @b: barrier to operate on
+ *
+ * This waits until the other side called barrier_abort(). This can be called
+ * regardless whether the local side already called barrier_abort() or not.
+ *
+ * If the other side has already aborted, this returns immediately.
+ *
+ * Returns: false if the local side aborted, true otherwise.
+ */
+bool barrier_wait_abortion(Barrier *b) {
+        assert(b);
+
+        barrier_read(b, BARRIER_THEY_ABORTED);
+        return !barrier_i_aborted(b);
+}
+
+/**
+ * barrier_sync_next() - Wait for the other side to place a next linked barrier
+ * @b: barrier to operate on
+ *
+ * This is like barrier_wait_next() and waits for the other side to call
+ * barrier_place(). However, this only waits for linked barriers. That means, if
+ * the other side already placed more barriers than (or as much as) we did, this
+ * returns immediately instead of waiting.
+ *
+ * If either side aborted, this returns false.
+ *
+ * Returns: false if either side aborted, true otherwise.
+ */
+bool barrier_sync_next(Barrier *b) {
+        assert(b);
+
+        if (barrier_is_aborted(b))
+                return false;
+
+        barrier_read(b, MAX((int64_t)0, b->barriers - 1));
+        return !barrier_is_aborted(b);
+}
+
+/**
+ * barrier_sync() - Wait for the other side to place as many barriers as we did
+ * @b: barrier to operate on
+ *
+ * This is like barrier_sync_next() but waits for the other side to call
+ * barrier_place() as often as we did (in total). If they already placed as much
+ * as we did (or more), this returns immediately instead of waiting.
+ *
+ * If either side aborted, this returns false.
+ *
+ * Returns: false if either side aborted, true otherwise.
+ */
+bool barrier_sync(Barrier *b) {
+        assert(b);
+
+        if (barrier_is_aborted(b))
+                return false;
+
+        barrier_read(b, 0);
+        return !barrier_is_aborted(b);
+}
diff --git a/src/shared/barrier.h b/src/shared/barrier.h
new file mode 100644 (file)
index 0000000..0eb3d27
--- /dev/null
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "macro.h"
+
+/* See source file for an API description. */
+
+typedef struct Barrier Barrier;
+
+enum {
+        BARRIER_SINGLE                  = 1LL,
+        BARRIER_ABORTION                = INT64_MAX,
+
+        /* bias values to store state; keep @WE < @THEY < @I */
+        BARRIER_BIAS                    = INT64_MIN,
+        BARRIER_WE_ABORTED              = BARRIER_BIAS + 1LL,
+        BARRIER_THEY_ABORTED            = BARRIER_BIAS + 2LL,
+        BARRIER_I_ABORTED               = BARRIER_BIAS + 3LL,
+};
+
+enum {
+        BARRIER_PARENT,
+        BARRIER_CHILD,
+};
+
+struct Barrier {
+        int me;
+        int them;
+        int pipe[2];
+        int64_t barriers;
+};
+
+#define BARRIER_NULL {-1, -1, {-1, -1}, 0}
+
+int barrier_create(Barrier *obj);
+void barrier_destroy(Barrier *b);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Barrier*, barrier_destroy);
+
+void barrier_set_role(Barrier *b, unsigned role);
+
+bool barrier_place(Barrier *b);
+bool barrier_abort(Barrier *b);
+
+bool barrier_wait_next(Barrier *b);
+bool barrier_wait_abortion(Barrier *b);
+bool barrier_sync_next(Barrier *b);
+bool barrier_sync(Barrier *b);
+
+static inline bool barrier_i_aborted(Barrier *b) {
+        return IN_SET(b->barriers, BARRIER_I_ABORTED, BARRIER_WE_ABORTED);
+}
+
+static inline bool barrier_they_aborted(Barrier *b) {
+        return IN_SET(b->barriers, BARRIER_THEY_ABORTED, BARRIER_WE_ABORTED);
+}
+
+static inline bool barrier_we_aborted(Barrier *b) {
+        return b->barriers == BARRIER_WE_ABORTED;
+}
+
+static inline bool barrier_is_aborted(Barrier *b) {
+        return IN_SET(b->barriers,
+                      BARRIER_I_ABORTED, BARRIER_THEY_ABORTED, BARRIER_WE_ABORTED);
+}
+
+static inline bool barrier_place_and_sync(Barrier *b) {
+        (void) barrier_place(b);
+        return barrier_sync(b);
+}
diff --git a/src/shared/bitmap.c b/src/shared/bitmap.c
new file mode 100644 (file)
index 0000000..a4cd645
--- /dev/null
@@ -0,0 +1,218 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "bitmap.h"
+#include "hashmap.h"
+#include "macro.h"
+
+struct Bitmap {
+        uint64_t *bitmaps;
+        size_t n_bitmaps;
+        size_t bitmaps_allocated;
+};
+
+/* Bitmaps are only meant to store relatively small numbers
+ * (corresponding to, say, an enum), so it is ok to limit
+ * the max entry. 64k should be plenty. */
+#define BITMAPS_MAX_ENTRY 0xffff
+
+/* This indicates that we reached the end of the bitmap */
+#define BITMAP_END ((unsigned) -1)
+
+#define BITMAP_NUM_TO_OFFSET(n)           ((n) / (sizeof(uint64_t) * 8))
+#define BITMAP_NUM_TO_REM(n)              ((n) % (sizeof(uint64_t) * 8))
+#define BITMAP_OFFSET_TO_NUM(offset, rem) ((offset) * sizeof(uint64_t) * 8 + (rem))
+
+Bitmap *bitmap_new(void) {
+        return new0(Bitmap, 1);
+}
+
+Bitmap *bitmap_copy(Bitmap *b) {
+        Bitmap *ret;
+
+        ret = bitmap_new();
+        if (!ret)
+                return NULL;
+
+        ret->bitmaps = newdup(uint64_t, b->bitmaps, b->n_bitmaps);
+        if (!ret->bitmaps)
+                return mfree(ret);
+
+        ret->n_bitmaps = ret->bitmaps_allocated = b->n_bitmaps;
+        return ret;
+}
+
+void bitmap_free(Bitmap *b) {
+        if (!b)
+                return;
+
+        free(b->bitmaps);
+        free(b);
+}
+
+int bitmap_ensure_allocated(Bitmap **b) {
+        Bitmap *a;
+
+        assert(b);
+
+        if (*b)
+                return 0;
+
+        a = bitmap_new();
+        if (!a)
+                return -ENOMEM;
+
+        *b = a;
+
+        return 0;
+}
+
+int bitmap_set(Bitmap *b, unsigned n) {
+        uint64_t bitmask;
+        unsigned offset;
+
+        assert(b);
+
+        /* we refuse to allocate huge bitmaps */
+        if (n > BITMAPS_MAX_ENTRY)
+                return -ERANGE;
+
+        offset = BITMAP_NUM_TO_OFFSET(n);
+
+        if (offset >= b->n_bitmaps) {
+                if (!GREEDY_REALLOC0(b->bitmaps, b->bitmaps_allocated, offset + 1))
+                        return -ENOMEM;
+
+                b->n_bitmaps = offset + 1;
+        }
+
+        bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n);
+
+        b->bitmaps[offset] |= bitmask;
+
+        return 0;
+}
+
+void bitmap_unset(Bitmap *b, unsigned n) {
+        uint64_t bitmask;
+        unsigned offset;
+
+        if (!b)
+                return;
+
+        offset = BITMAP_NUM_TO_OFFSET(n);
+
+        if (offset >= b->n_bitmaps)
+                return;
+
+        bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n);
+
+        b->bitmaps[offset] &= ~bitmask;
+}
+
+bool bitmap_isset(Bitmap *b, unsigned n) {
+        uint64_t bitmask;
+        unsigned offset;
+
+        if (!b)
+                return false;
+
+        offset = BITMAP_NUM_TO_OFFSET(n);
+
+        if (offset >= b->n_bitmaps)
+                return false;
+
+        bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n);
+
+        return !!(b->bitmaps[offset] & bitmask);
+}
+
+bool bitmap_isclear(Bitmap *b) {
+        unsigned i;
+
+        if (!b)
+                return true;
+
+        for (i = 0; i < b->n_bitmaps; i++)
+                if (b->bitmaps[i] != 0)
+                        return false;
+
+        return true;
+}
+
+void bitmap_clear(Bitmap *b) {
+
+        if (!b)
+                return;
+
+        b->bitmaps = mfree(b->bitmaps);
+        b->n_bitmaps = 0;
+        b->bitmaps_allocated = 0;
+}
+
+bool bitmap_iterate(Bitmap *b, Iterator *i, unsigned *n) {
+        uint64_t bitmask;
+        unsigned offset, rem;
+
+        assert(i);
+        assert(n);
+
+        if (!b || i->idx == BITMAP_END)
+                return false;
+
+        offset = BITMAP_NUM_TO_OFFSET(i->idx);
+        rem = BITMAP_NUM_TO_REM(i->idx);
+        bitmask = UINT64_C(1) << rem;
+
+        for (; offset < b->n_bitmaps; offset ++) {
+                if (b->bitmaps[offset]) {
+                        for (; bitmask; bitmask <<= 1, rem ++) {
+                                if (b->bitmaps[offset] & bitmask) {
+                                        *n = BITMAP_OFFSET_TO_NUM(offset, rem);
+                                        i->idx = *n + 1;
+
+                                        return true;
+                                }
+                        }
+                }
+
+                rem = 0;
+                bitmask = 1;
+        }
+
+        i->idx = BITMAP_END;
+
+        return false;
+}
+
+bool bitmap_equal(Bitmap *a, Bitmap *b) {
+        size_t common_n_bitmaps;
+        Bitmap *c;
+        unsigned i;
+
+        if (a == b)
+                return true;
+
+        if (!a != !b)
+                return false;
+
+        if (!a)
+                return true;
+
+        common_n_bitmaps = MIN(a->n_bitmaps, b->n_bitmaps);
+        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;
+        for (i = common_n_bitmaps; i < c->n_bitmaps; i++)
+                if (c->bitmaps[i] != 0)
+                        return false;
+
+        return true;
+}
diff --git a/src/shared/bitmap.h b/src/shared/bitmap.h
new file mode 100644 (file)
index 0000000..843d27d
--- /dev/null
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+
+#include "hashmap.h"
+#include "macro.h"
+
+typedef struct Bitmap Bitmap;
+
+Bitmap *bitmap_new(void);
+Bitmap *bitmap_copy(Bitmap *b);
+int bitmap_ensure_allocated(Bitmap **b);
+void bitmap_free(Bitmap *b);
+
+int bitmap_set(Bitmap *b, unsigned n);
+void bitmap_unset(Bitmap *b, unsigned n);
+bool bitmap_isset(Bitmap *b, unsigned n);
+bool bitmap_isclear(Bitmap *b);
+void bitmap_clear(Bitmap *b);
+
+bool bitmap_iterate(Bitmap *b, Iterator *i, unsigned *n);
+
+bool bitmap_equal(Bitmap *a, Bitmap *b);
+
+#define BITMAP_FOREACH(n, b, i) \
+        for ((i).idx = 0; bitmap_iterate((b), &(i), (unsigned*)&(n)); )
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Bitmap*, bitmap_free);
+
+#define _cleanup_bitmap_free_ _cleanup_(bitmap_freep)
diff --git a/src/shared/blkid-util.h b/src/shared/blkid-util.h
new file mode 100644 (file)
index 0000000..e4eb600
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#if HAVE_BLKID
+#include <blkid.h>
+#endif
+
+#include "util.h"
+
+#if HAVE_BLKID
+DEFINE_TRIVIAL_CLEANUP_FUNC(blkid_probe, blkid_free_probe);
+#endif
diff --git a/src/shared/bpf-program.c b/src/shared/bpf-program.c
new file mode 100644 (file)
index 0000000..2c61e04
--- /dev/null
@@ -0,0 +1,237 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "bpf-program.h"
+#include "fd-util.h"
+#include "log.h"
+#include "missing.h"
+#include "path-util.h"
+#include "util.h"
+
+int bpf_program_new(uint32_t prog_type, BPFProgram **ret) {
+        _cleanup_(bpf_program_unrefp) BPFProgram *p = NULL;
+
+        p = new0(BPFProgram, 1);
+        if (!p)
+                return log_oom();
+
+        p->n_ref = 1;
+        p->prog_type = prog_type;
+        p->kernel_fd = -1;
+
+        *ret = TAKE_PTR(p);
+
+        return 0;
+}
+
+static BPFProgram *bpf_program_free(BPFProgram *p) {
+        assert(p);
+
+        /* Unfortunately, the kernel currently doesn't implicitly detach BPF programs from their cgroups when the last
+         * fd to the BPF program is closed. This has nasty side-effects since this means that abnormally terminated
+         * programs that attached one of their BPF programs to a cgroup will leave this programs pinned for good with
+         * zero chance of recovery, until the cgroup is removed. This is particularly problematic if the cgroup in
+         * question is the root cgroup (or any other cgroup belonging to a service that cannot be restarted during
+         * operation, such as dbus), as the memory for the BPF program can only be reclaimed through a reboot. To
+         * counter this, we track closely to which cgroup a program was attached to and will detach it on our own
+         * whenever we close the BPF fd. */
+        (void) bpf_program_cgroup_detach(p);
+
+        safe_close(p->kernel_fd);
+        free(p->instructions);
+        free(p->attached_path);
+
+        return mfree(p);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(BPFProgram, bpf_program, bpf_program_free);
+
+int bpf_program_add_instructions(BPFProgram *p, const struct bpf_insn *instructions, size_t count) {
+
+        assert(p);
+
+        if (p->kernel_fd >= 0) /* don't allow modification after we uploaded things to the kernel */
+                return -EBUSY;
+
+        if (!GREEDY_REALLOC(p->instructions, p->allocated, p->n_instructions + count))
+                return -ENOMEM;
+
+        memcpy(p->instructions + p->n_instructions, instructions, sizeof(struct bpf_insn) * count);
+        p->n_instructions += count;
+
+        return 0;
+}
+
+int bpf_program_load_kernel(BPFProgram *p, char *log_buf, size_t log_size) {
+        union bpf_attr attr;
+
+        assert(p);
+
+        if (p->kernel_fd >= 0) { /* make this idempotent */
+                memzero(log_buf, log_size);
+                return 0;
+        }
+
+        attr = (union bpf_attr) {
+                .prog_type = p->prog_type,
+                .insns = PTR_TO_UINT64(p->instructions),
+                .insn_cnt = p->n_instructions,
+                .license = PTR_TO_UINT64("GPL"),
+                .log_buf = PTR_TO_UINT64(log_buf),
+                .log_level = !!log_buf,
+                .log_size = log_size,
+        };
+
+        p->kernel_fd = bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
+        if (p->kernel_fd < 0)
+                return -errno;
+
+        return 0;
+}
+
+int bpf_program_cgroup_attach(BPFProgram *p, int type, const char *path, uint32_t flags) {
+        _cleanup_free_ char *copy = NULL;
+        _cleanup_close_ int fd = -1;
+        union bpf_attr attr;
+        int r;
+
+        assert(p);
+        assert(type >= 0);
+        assert(path);
+
+        if (!IN_SET(flags, 0, BPF_F_ALLOW_OVERRIDE, BPF_F_ALLOW_MULTI))
+                return -EINVAL;
+
+        /* We need to track which cgroup the program is attached to, and we can only track one attachment, hence let's
+        * refuse this early. */
+        if (p->attached_path) {
+                if (!path_equal(p->attached_path, path))
+                        return -EBUSY;
+                if (p->attached_type != type)
+                        return -EBUSY;
+                if (p->attached_flags != flags)
+                        return -EBUSY;
+
+                /* Here's a shortcut: if we previously attached this program already, then we don't have to do so
+                 * again. Well, with one exception: if we are in BPF_F_ALLOW_OVERRIDE mode then someone else might have
+                 * replaced our program since the last time, hence let's reattach it again, just to be safe. In flags
+                 * == 0 mode this is not an issue since nobody else can replace our program in that case, and in flags
+                 * == BPF_F_ALLOW_MULTI mode any other's program would be installed in addition to ours hence ours
+                 * would remain in effect. */
+                if (flags != BPF_F_ALLOW_OVERRIDE)
+                        return 0;
+        }
+
+        /* Ensure we have a kernel object for this. */
+        r = bpf_program_load_kernel(p, NULL, 0);
+        if (r < 0)
+                return r;
+
+        copy = strdup(path);
+        if (!copy)
+                return -ENOMEM;
+
+        fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        attr = (union bpf_attr) {
+                .attach_type = type,
+                .target_fd = fd,
+                .attach_bpf_fd = p->kernel_fd,
+                .attach_flags = flags,
+        };
+
+        if (bpf(BPF_PROG_ATTACH, &attr, sizeof(attr)) < 0)
+                return -errno;
+
+        free_and_replace(p->attached_path, copy);
+        p->attached_type = type;
+        p->attached_flags = flags;
+
+        return 0;
+}
+
+int bpf_program_cgroup_detach(BPFProgram *p) {
+        _cleanup_close_ int fd = -1;
+
+        assert(p);
+
+        if (!p->attached_path)
+                return -EUNATCH;
+
+        fd = open(p->attached_path, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
+        if (fd < 0) {
+                if (errno != ENOENT)
+                        return -errno;
+
+                /* If the cgroup does not exist anymore, then we don't have to explicitly detach, it got detached
+                 * implicitly by the removal, hence don't complain */
+
+        } else {
+                union bpf_attr attr;
+
+                attr = (union bpf_attr) {
+                        .attach_type = p->attached_type,
+                        .target_fd = fd,
+                        .attach_bpf_fd = p->kernel_fd,
+                };
+
+                if (bpf(BPF_PROG_DETACH, &attr, sizeof(attr)) < 0)
+                        return -errno;
+        }
+
+        p->attached_path = mfree(p->attached_path);
+
+        return 0;
+}
+
+int bpf_map_new(enum bpf_map_type type, size_t key_size, size_t value_size, size_t max_entries, uint32_t flags) {
+        union bpf_attr attr = {
+                .map_type = type,
+                .key_size = key_size,
+                .value_size = value_size,
+                .max_entries = max_entries,
+                .map_flags = flags,
+        };
+        int fd;
+
+        fd = bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
+        if (fd < 0)
+                return -errno;
+
+        return fd;
+}
+
+int bpf_map_update_element(int fd, const void *key, void *value) {
+
+        union bpf_attr attr = {
+                .map_fd = fd,
+                .key = PTR_TO_UINT64(key),
+                .value = PTR_TO_UINT64(value),
+        };
+
+        if (bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int bpf_map_lookup_element(int fd, const void *key, void *value) {
+
+        union bpf_attr attr = {
+                .map_fd = fd,
+                .key = PTR_TO_UINT64(key),
+                .value = PTR_TO_UINT64(value),
+        };
+
+        if (bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)) < 0)
+                return -errno;
+
+        return 0;
+}
diff --git a/src/shared/bpf-program.h b/src/shared/bpf-program.h
new file mode 100644 (file)
index 0000000..c21eb2f
--- /dev/null
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <linux/bpf.h>
+#include <stdint.h>
+#include <sys/syscall.h>
+
+#include "list.h"
+#include "macro.h"
+
+typedef struct BPFProgram BPFProgram;
+
+struct BPFProgram {
+        unsigned n_ref;
+
+        int kernel_fd;
+        uint32_t prog_type;
+
+        size_t n_instructions;
+        size_t allocated;
+        struct bpf_insn *instructions;
+
+        char *attached_path;
+        int attached_type;
+        uint32_t attached_flags;
+};
+
+int bpf_program_new(uint32_t prog_type, BPFProgram **ret);
+BPFProgram *bpf_program_unref(BPFProgram *p);
+BPFProgram *bpf_program_ref(BPFProgram *p);
+
+int bpf_program_add_instructions(BPFProgram *p, const struct bpf_insn *insn, size_t count);
+int bpf_program_load_kernel(BPFProgram *p, char *log_buf, size_t log_size);
+
+int bpf_program_cgroup_attach(BPFProgram *p, int type, const char *path, uint32_t flags);
+int bpf_program_cgroup_detach(BPFProgram *p);
+
+int bpf_map_new(enum bpf_map_type type, size_t key_size, size_t value_size, size_t max_entries, uint32_t flags);
+int bpf_map_update_element(int fd, const void *key, void *value);
+int bpf_map_lookup_element(int fd, const void *key, void *value);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(BPFProgram*, bpf_program_unref);
diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c
new file mode 100644 (file)
index 0000000..dafc09e
--- /dev/null
@@ -0,0 +1,1370 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <alloca.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+
+#include "alloc-util.h"
+#include "calendarspec.h"
+#include "fileio.h"
+#include "macro.h"
+#include "parse-util.h"
+#include "process-util.h"
+#include "string-util.h"
+#include "time-util.h"
+
+#define BITS_WEEKDAYS 127
+#define MIN_YEAR 1970
+#define MAX_YEAR 2199
+
+/* An arbitrary limit on the length of the chains of components. We don't want to
+ * build a very long linked list, which would be slow to iterate over and might cause
+ * our stack to overflow. It's unlikely that legitimate uses require more than a few
+ * linked compenents anyway. */
+#define CALENDARSPEC_COMPONENTS_MAX 240
+
+static void free_chain(CalendarComponent *c) {
+        CalendarComponent *n;
+
+        while (c) {
+                n = c->next;
+                free(c);
+                c = n;
+        }
+}
+
+CalendarSpec* calendar_spec_free(CalendarSpec *c) {
+
+        if (!c)
+                return NULL;
+
+        free_chain(c->year);
+        free_chain(c->month);
+        free_chain(c->day);
+        free_chain(c->hour);
+        free_chain(c->minute);
+        free_chain(c->microsecond);
+        free(c->timezone);
+
+        return mfree(c);
+}
+
+static int component_compare(CalendarComponent * const *a, CalendarComponent * const *b) {
+        int r;
+
+        r = CMP((*a)->start, (*b)->start);
+        if (r != 0)
+                return r;
+
+        r = CMP((*a)->stop, (*b)->stop);
+        if (r != 0)
+                return r;
+
+        return CMP((*a)->repeat, (*b)->repeat);
+}
+
+static void normalize_chain(CalendarComponent **c) {
+        CalendarComponent **b, *i, **j, *next;
+        size_t n = 0, k;
+
+        assert(c);
+
+        for (i = *c; i; i = i->next) {
+                n++;
+
+                /*
+                 * While we're counting the chain, also normalize `stop`
+                 * so the length of the range is a multiple of `repeat`
+                 */
+                if (i->stop > i->start && i->repeat > 0)
+                        i->stop -= (i->stop - i->start) % i->repeat;
+
+        }
+
+        if (n <= 1)
+                return;
+
+        j = b = newa(CalendarComponent*, n);
+        for (i = *c; i; i = i->next)
+                *(j++) = i;
+
+        typesafe_qsort(b, n, component_compare);
+
+        b[n-1]->next = NULL;
+        next = b[n-1];
+
+        /* Drop non-unique entries */
+        for (k = n-1; k > 0; k--) {
+                if (component_compare(&b[k-1], &next) == 0) {
+                        free(b[k-1]);
+                        continue;
+                }
+
+                b[k-1]->next = next;
+                next = b[k-1];
+        }
+
+        *c = next;
+}
+
+static void fix_year(CalendarComponent *c) {
+        /* Turns 12 → 2012, 89 → 1989 */
+
+        while (c) {
+                if (c->start >= 0 && c->start < 70)
+                        c->start += 2000;
+
+                if (c->stop >= 0 && c->stop < 70)
+                        c->stop += 2000;
+
+                if (c->start >= 70 && c->start < 100)
+                        c->start += 1900;
+
+                if (c->stop >= 70 && c->stop < 100)
+                        c->stop += 1900;
+
+                c = c->next;
+        }
+}
+
+int calendar_spec_normalize(CalendarSpec *c) {
+        assert(c);
+
+        if (streq_ptr(c->timezone, "UTC")) {
+                c->utc = true;
+                c->timezone = mfree(c->timezone);
+        }
+
+        if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS)
+                c->weekdays_bits = -1;
+
+        if (c->end_of_month && !c->day)
+                c->end_of_month = false;
+
+        fix_year(c->year);
+
+        normalize_chain(&c->year);
+        normalize_chain(&c->month);
+        normalize_chain(&c->day);
+        normalize_chain(&c->hour);
+        normalize_chain(&c->minute);
+        normalize_chain(&c->microsecond);
+
+        return 0;
+}
+
+_pure_ static bool chain_valid(CalendarComponent *c, int from, int to, bool end_of_month) {
+        assert(to >= from);
+
+        if (!c)
+                return true;
+
+        /* Forbid dates more than 28 days from the end of the month */
+        if (end_of_month)
+                to -= 3;
+
+        if (c->start < from || c->start > to)
+                return false;
+
+        /* Avoid overly large values that could cause overflow */
+        if (c->repeat > to - from)
+                return false;
+
+        /*
+         * c->repeat must be short enough so at least one repetition may
+         * occur before the end of the interval.  For dates scheduled
+         * relative to the end of the month, c->start and c->stop
+         * correspond to the Nth last day of the month.
+         */
+        if (c->stop >= 0) {
+                if (c->stop < from || c ->stop > to)
+                        return false;
+
+                if (c->start + c->repeat > c->stop)
+                        return false;
+        } else {
+                if (end_of_month && c->start - c->repeat < from)
+                        return false;
+
+                if (!end_of_month && c->start + c->repeat > to)
+                        return false;
+        }
+
+        if (c->next)
+                return chain_valid(c->next, from, to, end_of_month);
+
+        return true;
+}
+
+_pure_ bool calendar_spec_valid(CalendarSpec *c) {
+        assert(c);
+
+        if (c->weekdays_bits > BITS_WEEKDAYS)
+                return false;
+
+        if (!chain_valid(c->year, MIN_YEAR, MAX_YEAR, false))
+                return false;
+
+        if (!chain_valid(c->month, 1, 12, false))
+                return false;
+
+        if (!chain_valid(c->day, 1, 31, c->end_of_month))
+                return false;
+
+        if (!chain_valid(c->hour, 0, 23, false))
+                return false;
+
+        if (!chain_valid(c->minute, 0, 59, false))
+                return false;
+
+        if (!chain_valid(c->microsecond, 0, 60*USEC_PER_SEC-1, false))
+                return false;
+
+        return true;
+}
+
+static void format_weekdays(FILE *f, const CalendarSpec *c) {
+        static const char *const days[] = {
+                "Mon",
+                "Tue",
+                "Wed",
+                "Thu",
+                "Fri",
+                "Sat",
+                "Sun"
+        };
+
+        int l, x;
+        bool need_comma = false;
+
+        assert(f);
+        assert(c);
+        assert(c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS);
+
+        for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) {
+
+                if (c->weekdays_bits & (1 << x)) {
+
+                        if (l < 0) {
+                                if (need_comma)
+                                        fputc(',', f);
+                                else
+                                        need_comma = true;
+
+                                fputs(days[x], f);
+                                l = x;
+                        }
+
+                } else if (l >= 0) {
+
+                        if (x > l + 1) {
+                                fputs(x > l + 2 ? ".." : ",", f);
+                                fputs(days[x-1], f);
+                        }
+
+                        l = -1;
+                }
+        }
+
+        if (l >= 0 && x > l + 1) {
+                fputs(x > l + 2 ? ".." : ",", f);
+                fputs(days[x-1], f);
+        }
+}
+
+static void format_chain(FILE *f, int space, const CalendarComponent *c, bool usec) {
+        int d = usec ? (int) USEC_PER_SEC : 1;
+
+        assert(f);
+
+        if (!c) {
+                fputc('*', f);
+                return;
+        }
+
+        if (usec && c->start == 0 && c->repeat == USEC_PER_SEC && !c->next) {
+                fputc('*', f);
+                return;
+        }
+
+        assert(c->start >= 0);
+
+        fprintf(f, "%0*i", space, c->start / d);
+        if (c->start % d > 0)
+                fprintf(f, ".%06i", c->start % d);
+
+        if (c->stop > 0)
+                fprintf(f, "..%0*i", space, c->stop / d);
+        if (c->stop % d > 0)
+                fprintf(f, ".%06i", c->stop % d);
+
+        if (c->repeat > 0 && !(c->stop > 0 && c->repeat == d))
+                fprintf(f, "/%i", c->repeat / d);
+        if (c->repeat % d > 0)
+                fprintf(f, ".%06i", c->repeat % d);
+
+        if (c->next) {
+                fputc(',', f);
+                format_chain(f, space, c->next, usec);
+        }
+}
+
+int calendar_spec_to_string(const CalendarSpec *c, char **p) {
+        char *buf = NULL;
+        size_t sz = 0;
+        FILE *f;
+        int r;
+
+        assert(c);
+        assert(p);
+
+        f = open_memstream(&buf, &sz);
+        if (!f)
+                return -ENOMEM;
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        if (c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS) {
+                format_weekdays(f, c);
+                fputc(' ', f);
+        }
+
+        format_chain(f, 4, c->year, false);
+        fputc('-', f);
+        format_chain(f, 2, c->month, false);
+        fputc(c->end_of_month ? '~' : '-', f);
+        format_chain(f, 2, c->day, false);
+        fputc(' ', f);
+        format_chain(f, 2, c->hour, false);
+        fputc(':', f);
+        format_chain(f, 2, c->minute, false);
+        fputc(':', f);
+        format_chain(f, 2, c->microsecond, true);
+
+        if (c->utc)
+                fputs(" UTC", f);
+        else if (c->timezone != NULL) {
+                fputc(' ', f);
+                fputs(c->timezone, f);
+        } else if (IN_SET(c->dst, 0, 1)) {
+
+                /* If daylight saving is explicitly on or off, let's show the used timezone. */
+
+                tzset();
+
+                if (!isempty(tzname[c->dst])) {
+                        fputc(' ', f);
+                        fputs(tzname[c->dst], f);
+                }
+        }
+
+        r = fflush_and_check(f);
+        if (r < 0) {
+                free(buf);
+                fclose(f);
+                return r;
+        }
+
+        fclose(f);
+
+        *p = buf;
+        return 0;
+}
+
+static int parse_weekdays(const char **p, CalendarSpec *c) {
+        static const struct {
+                const char *name;
+                const int nr;
+        } day_nr[] = {
+                { "Monday",    0 },
+                { "Mon",       0 },
+                { "Tuesday",   1 },
+                { "Tue",       1 },
+                { "Wednesday", 2 },
+                { "Wed",       2 },
+                { "Thursday",  3 },
+                { "Thu",       3 },
+                { "Friday",    4 },
+                { "Fri",       4 },
+                { "Saturday",  5 },
+                { "Sat",       5 },
+                { "Sunday",    6 },
+                { "Sun",       6 }
+        };
+
+        int l = -1;
+        bool first = true;
+
+        assert(p);
+        assert(*p);
+        assert(c);
+
+        for (;;) {
+                size_t i;
+
+                for (i = 0; i < ELEMENTSOF(day_nr); i++) {
+                        size_t skip;
+
+                        if (!startswith_no_case(*p, day_nr[i].name))
+                                continue;
+
+                        skip = strlen(day_nr[i].name);
+
+                        if (!IN_SET((*p)[skip], 0, '-', '.', ',', ' '))
+                                return -EINVAL;
+
+                        c->weekdays_bits |= 1 << day_nr[i].nr;
+
+                        if (l >= 0) {
+                                int j;
+
+                                if (l > day_nr[i].nr)
+                                        return -EINVAL;
+
+                                for (j = l + 1; j < day_nr[i].nr; j++)
+                                        c->weekdays_bits |= 1 << j;
+                        }
+
+                        *p += skip;
+                        break;
+                }
+
+                /* Couldn't find this prefix, so let's assume the
+                   weekday was not specified and let's continue with
+                   the date */
+                if (i >= ELEMENTSOF(day_nr))
+                        return first ? 0 : -EINVAL;
+
+                /* We reached the end of the string */
+                if (**p == 0)
+                        return 0;
+
+                /* We reached the end of the weekday spec part */
+                if (**p == ' ') {
+                        *p += strspn(*p, " ");
+                        return 0;
+                }
+
+                if (**p == '.') {
+                        if (l >= 0)
+                                return -EINVAL;
+
+                        if ((*p)[1] != '.')
+                                return -EINVAL;
+
+                        l = day_nr[i].nr;
+                        *p += 2;
+
+                /* Support ranges with "-" for backwards compatibility */
+                } else if (**p == '-') {
+                        if (l >= 0)
+                                return -EINVAL;
+
+                        l = day_nr[i].nr;
+                        *p += 1;
+
+                } else if (**p == ',') {
+                        l = -1;
+                        *p += 1;
+                }
+
+                /* Allow a trailing comma but not an open range */
+                if (IN_SET(**p, 0, ' ')) {
+                        *p += strspn(*p, " ");
+                        return l < 0 ? 0 : -EINVAL;
+                }
+
+                first = false;
+        }
+}
+
+static int parse_one_number(const char *p, const char **e, unsigned long *ret) {
+        char *ee = NULL;
+        unsigned long value;
+
+        errno = 0;
+        value = strtoul(p, &ee, 10);
+        if (errno > 0)
+                return -errno;
+        if (ee == p)
+                return -EINVAL;
+
+        *ret = value;
+        *e = ee;
+        return 0;
+}
+
+static int parse_component_decimal(const char **p, bool usec, int *res) {
+        unsigned long value;
+        const char *e = NULL;
+        int r;
+
+        if (!isdigit(**p))
+                return -EINVAL;
+
+        r = parse_one_number(*p, &e, &value);
+        if (r < 0)
+                return r;
+
+        if (usec) {
+                if (value * USEC_PER_SEC / USEC_PER_SEC != value)
+                        return -ERANGE;
+
+                value *= USEC_PER_SEC;
+
+                /* One "." is a decimal point, but ".." is a range separator */
+                if (e[0] == '.' && e[1] != '.') {
+                        unsigned add;
+
+                        e++;
+                        r = parse_fractional_part_u(&e, 6, &add);
+                        if (r < 0)
+                                return r;
+
+                        if (add + value < value)
+                                return -ERANGE;
+                        value += add;
+                }
+        }
+
+        if (value > INT_MAX)
+                return -ERANGE;
+
+        *p = e;
+        *res = value;
+
+        return 0;
+}
+
+static int const_chain(int value, CalendarComponent **c) {
+        CalendarComponent *cc = NULL;
+
+        assert(c);
+
+        cc = new0(CalendarComponent, 1);
+        if (!cc)
+                return -ENOMEM;
+
+        cc->start = value;
+        cc->stop = -1;
+        cc->repeat = 0;
+        cc->next = *c;
+
+        *c = cc;
+
+        return 0;
+}
+
+static int calendarspec_from_time_t(CalendarSpec *c, time_t time) {
+        struct tm tm;
+        CalendarComponent *year = NULL, *month = NULL, *day = NULL, *hour = NULL, *minute = NULL, *us = NULL;
+        int r;
+
+        if (!gmtime_r(&time, &tm))
+                return -ERANGE;
+
+        r = const_chain(tm.tm_year + 1900, &year);
+        if (r < 0)
+                return r;
+
+        r = const_chain(tm.tm_mon + 1, &month);
+        if (r < 0)
+                return r;
+
+        r = const_chain(tm.tm_mday, &day);
+        if (r < 0)
+                return r;
+
+        r = const_chain(tm.tm_hour, &hour);
+        if (r < 0)
+                return r;
+
+        r = const_chain(tm.tm_min, &minute);
+        if (r < 0)
+                return r;
+
+        r = const_chain(tm.tm_sec * USEC_PER_SEC, &us);
+        if (r < 0)
+                return r;
+
+        c->utc = true;
+        c->year = year;
+        c->month = month;
+        c->day = day;
+        c->hour = hour;
+        c->minute = minute;
+        c->microsecond = us;
+        return 0;
+}
+
+static int prepend_component(const char **p, bool usec, unsigned nesting, CalendarComponent **c) {
+        int r, start, stop = -1, repeat = 0;
+        CalendarComponent *cc;
+        const char *e = *p;
+
+        assert(p);
+        assert(c);
+
+        if (nesting > CALENDARSPEC_COMPONENTS_MAX)
+                return -ENOBUFS;
+
+        r = parse_component_decimal(&e, usec, &start);
+        if (r < 0)
+                return r;
+
+        if (e[0] == '.' && e[1] == '.') {
+                e += 2;
+                r = parse_component_decimal(&e, usec, &stop);
+                if (r < 0)
+                        return r;
+
+                repeat = usec ? USEC_PER_SEC : 1;
+        }
+
+        if (*e == '/') {
+                e++;
+                r = parse_component_decimal(&e, usec, &repeat);
+                if (r < 0)
+                        return r;
+
+                if (repeat == 0)
+                        return -ERANGE;
+        }
+
+        if (!IN_SET(*e, 0, ' ', ',', '-', '~', ':'))
+                return -EINVAL;
+
+        cc = new0(CalendarComponent, 1);
+        if (!cc)
+                return -ENOMEM;
+
+        cc->start = start;
+        cc->stop = stop;
+        cc->repeat = repeat;
+        cc->next = *c;
+
+        *p = e;
+        *c = cc;
+
+        if (*e ==',') {
+                *p += 1;
+                return prepend_component(p, usec, nesting + 1, c);
+        }
+
+        return 0;
+}
+
+static int parse_chain(const char **p, bool usec, CalendarComponent **c) {
+        const char *t;
+        CalendarComponent *cc = NULL;
+        int r;
+
+        assert(p);
+        assert(c);
+
+        t = *p;
+
+        if (t[0] == '*') {
+                if (usec) {
+                        r = const_chain(0, c);
+                        if (r < 0)
+                                return r;
+                        (*c)->repeat = USEC_PER_SEC;
+                } else
+                        *c = NULL;
+
+                *p = t + 1;
+                return 0;
+        }
+
+        r = prepend_component(&t, usec, 0, &cc);
+        if (r < 0) {
+                free_chain(cc);
+                return r;
+        }
+
+        *p = t;
+        *c = cc;
+        return 0;
+}
+
+static int parse_date(const char **p, CalendarSpec *c) {
+        const char *t;
+        int r;
+        CalendarComponent *first, *second, *third;
+
+        assert(p);
+        assert(*p);
+        assert(c);
+
+        t = *p;
+
+        if (*t == 0)
+                return 0;
+
+        /* @TIMESTAMP — UNIX time in seconds since the epoch */
+        if (*t == '@') {
+                unsigned long value;
+                time_t time;
+
+                r = parse_one_number(t + 1, &t, &value);
+                if (r < 0)
+                        return r;
+
+                time = value;
+                if ((unsigned long) time != value)
+                        return -ERANGE;
+
+                r = calendarspec_from_time_t(c, time);
+                if (r < 0)
+                        return r;
+
+                *p = t;
+                return 1; /* finito, don't parse H:M:S after that */
+        }
+
+        r = parse_chain(&t, false, &first);
+        if (r < 0)
+                return r;
+
+        /* Already the end? A ':' as separator? In that case this was a time, not a date */
+        if (IN_SET(*t, 0, ':')) {
+                free_chain(first);
+                return 0;
+        }
+
+        if (*t == '~')
+                c->end_of_month = true;
+        else if (*t != '-') {
+                free_chain(first);
+                return -EINVAL;
+        }
+
+        t++;
+        r = parse_chain(&t, false, &second);
+        if (r < 0) {
+                free_chain(first);
+                return r;
+        }
+
+        /* Got two parts, hence it's month and day */
+        if (IN_SET(*t, 0, ' ')) {
+                *p = t + strspn(t, " ");
+                c->month = first;
+                c->day = second;
+                return 0;
+        } else if (c->end_of_month) {
+                free_chain(first);
+                free_chain(second);
+                return -EINVAL;
+        }
+
+        if (*t == '~')
+                c->end_of_month = true;
+        else if (*t != '-') {
+                free_chain(first);
+                free_chain(second);
+                return -EINVAL;
+        }
+
+        t++;
+        r = parse_chain(&t, false, &third);
+        if (r < 0) {
+                free_chain(first);
+                free_chain(second);
+                return r;
+        }
+
+        /* Got three parts, hence it is year, month and day */
+        if (IN_SET(*t, 0, ' ')) {
+                *p = t + strspn(t, " ");
+                c->year = first;
+                c->month = second;
+                c->day = third;
+                return 0;
+        }
+
+        free_chain(first);
+        free_chain(second);
+        free_chain(third);
+        return -EINVAL;
+}
+
+static int parse_calendar_time(const char **p, CalendarSpec *c) {
+        CalendarComponent *h = NULL, *m = NULL, *s = NULL;
+        const char *t;
+        int r;
+
+        assert(p);
+        assert(*p);
+        assert(c);
+
+        t = *p;
+
+        /* If no time is specified at all, then this means 00:00:00 */
+        if (*t == 0)
+                goto null_hour;
+
+        r = parse_chain(&t, false, &h);
+        if (r < 0)
+                goto fail;
+
+        if (*t != ':') {
+                r = -EINVAL;
+                goto fail;
+        }
+
+        t++;
+        r = parse_chain(&t, false, &m);
+        if (r < 0)
+                goto fail;
+
+        /* Already at the end? Then it's hours and minutes, and seconds are 0 */
+        if (*t == 0)
+                goto null_second;
+
+        if (*t != ':') {
+                r = -EINVAL;
+                goto fail;
+        }
+
+        t++;
+        r = parse_chain(&t, true, &s);
+        if (r < 0)
+                goto fail;
+
+        /* At the end? Then it's hours, minutes and seconds */
+        if (*t == 0)
+                goto finish;
+
+        r = -EINVAL;
+        goto fail;
+
+null_hour:
+        r = const_chain(0, &h);
+        if (r < 0)
+                goto fail;
+
+        r = const_chain(0, &m);
+        if (r < 0)
+                goto fail;
+
+null_second:
+        r = const_chain(0, &s);
+        if (r < 0)
+                goto fail;
+
+finish:
+        *p = t;
+        c->hour = h;
+        c->minute = m;
+        c->microsecond = s;
+
+        return 0;
+
+fail:
+        free_chain(h);
+        free_chain(m);
+        free_chain(s);
+        return r;
+}
+
+int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
+        const char *utc;
+        _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
+        int r;
+
+        assert(p);
+        assert(spec);
+
+        c = new0(CalendarSpec, 1);
+        if (!c)
+                return -ENOMEM;
+        c->dst = -1;
+        c->timezone = NULL;
+
+        utc = endswith_no_case(p, " UTC");
+        if (utc) {
+                c->utc = true;
+                p = strndupa(p, utc - p);
+        } else {
+                const char *e = NULL;
+                int j;
+
+                tzset();
+
+                /* Check if the local timezone was specified? */
+                for (j = 0; j <= 1; j++) {
+                        if (isempty(tzname[j]))
+                                continue;
+
+                        e = endswith_no_case(p, tzname[j]);
+                        if (!e)
+                                continue;
+                        if (e == p)
+                                continue;
+                        if (e[-1] != ' ')
+                                continue;
+
+                        break;
+                }
+
+                /* Found one of the two timezones specified? */
+                if (IN_SET(j, 0, 1)) {
+                        p = strndupa(p, e - p - 1);
+                        c->dst = j;
+                } else {
+                        const char *last_space;
+
+                        last_space = strrchr(p, ' ');
+                        if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG)) {
+                                c->timezone = strdup(last_space + 1);
+                                if (!c->timezone)
+                                        return -ENOMEM;
+
+                                p = strndupa(p, last_space - p);
+                        }
+                }
+        }
+
+        if (isempty(p))
+                return -EINVAL;
+
+        if (strcaseeq(p, "minutely")) {
+                r = const_chain(0, &c->microsecond);
+                if (r < 0)
+                        return r;
+
+        } else if (strcaseeq(p, "hourly")) {
+                r = const_chain(0, &c->minute);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->microsecond);
+                if (r < 0)
+                        return r;
+
+        } else if (strcaseeq(p, "daily")) {
+                r = const_chain(0, &c->hour);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->minute);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->microsecond);
+                if (r < 0)
+                        return r;
+
+        } else if (strcaseeq(p, "monthly")) {
+                r = const_chain(1, &c->day);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->hour);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->minute);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->microsecond);
+                if (r < 0)
+                        return r;
+
+        } else if (strcaseeq(p, "annually") ||
+                   strcaseeq(p, "yearly") ||
+                   strcaseeq(p, "anually") /* backwards compatibility */ ) {
+
+                r = const_chain(1, &c->month);
+                if (r < 0)
+                        return r;
+                r = const_chain(1, &c->day);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->hour);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->minute);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->microsecond);
+                if (r < 0)
+                        return r;
+
+        } else if (strcaseeq(p, "weekly")) {
+
+                c->weekdays_bits = 1;
+
+                r = const_chain(0, &c->hour);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->minute);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->microsecond);
+                if (r < 0)
+                        return r;
+
+        } else if (strcaseeq(p, "quarterly")) {
+
+                r = const_chain(1, &c->month);
+                if (r < 0)
+                        return r;
+                r = const_chain(4, &c->month);
+                if (r < 0)
+                        return r;
+                r = const_chain(7, &c->month);
+                if (r < 0)
+                        return r;
+                r = const_chain(10, &c->month);
+                if (r < 0)
+                        return r;
+                r = const_chain(1, &c->day);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->hour);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->minute);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->microsecond);
+                if (r < 0)
+                        return r;
+
+        } else if (strcaseeq(p, "biannually") ||
+                   strcaseeq(p, "bi-annually") ||
+                   strcaseeq(p, "semiannually") ||
+                   strcaseeq(p, "semi-annually")) {
+
+                r = const_chain(1, &c->month);
+                if (r < 0)
+                        return r;
+                r = const_chain(7, &c->month);
+                if (r < 0)
+                        return r;
+                r = const_chain(1, &c->day);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->hour);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->minute);
+                if (r < 0)
+                        return r;
+                r = const_chain(0, &c->microsecond);
+                if (r < 0)
+                        return r;
+
+        } else {
+                r = parse_weekdays(&p, c);
+                if (r < 0)
+                        return r;
+
+                r = parse_date(&p, c);
+                if (r < 0)
+                        return r;
+
+                if (r == 0) {
+                        r = parse_calendar_time(&p, c);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (*p != 0)
+                        return -EINVAL;
+        }
+
+        r = calendar_spec_normalize(c);
+        if (r < 0)
+                return r;
+
+        if (!calendar_spec_valid(c))
+                return -EINVAL;
+
+        *spec = TAKE_PTR(c);
+        return 0;
+}
+
+static int find_end_of_month(struct tm *tm, bool utc, int day) {
+        struct tm t = *tm;
+
+        t.tm_mon++;
+        t.tm_mday = 1 - day;
+
+        if (mktime_or_timegm(&t, utc) < 0 ||
+            t.tm_mon != tm->tm_mon)
+                return -1;
+
+        return t.tm_mday;
+}
+
+static int find_matching_component(const CalendarSpec *spec, const CalendarComponent *c,
+                                   struct tm *tm, int *val) {
+        const CalendarComponent *p = c;
+        int start, stop, d = -1;
+        bool d_set = false;
+        int r;
+
+        assert(val);
+
+        if (!c)
+                return 0;
+
+        while (c) {
+                start = c->start;
+                stop = c->stop;
+
+                if (spec->end_of_month && p == spec->day) {
+                        start = find_end_of_month(tm, spec->utc, start);
+                        stop = find_end_of_month(tm, spec->utc, stop);
+
+                        if (stop > 0)
+                                SWAP_TWO(start, stop);
+                }
+
+                if (start >= *val) {
+
+                        if (!d_set || start < d) {
+                                d = start;
+                                d_set = true;
+                        }
+
+                } else if (c->repeat > 0) {
+                        int k;
+
+                        k = start + c->repeat * DIV_ROUND_UP(*val - start, c->repeat);
+
+                        if ((!d_set || k < d) && (stop < 0 || k <= stop)) {
+                                d = k;
+                                d_set = true;
+                        }
+                }
+
+                c = c->next;
+        }
+
+        if (!d_set)
+                return -ENOENT;
+
+        r = *val != d;
+        *val = d;
+        return r;
+}
+
+static bool tm_out_of_bounds(const struct tm *tm, bool utc) {
+        struct tm t;
+        assert(tm);
+
+        t = *tm;
+
+        if (mktime_or_timegm(&t, utc) < 0)
+                return true;
+
+        /*
+         * Set an upper bound on the year so impossible dates like "*-02-31"
+         * don't cause find_next() to loop forever. tm_year contains years
+         * since 1900, so adjust it accordingly.
+         */
+        if (tm->tm_year + 1900 > MAX_YEAR)
+                return true;
+
+        /* Did any normalization take place? If so, it was out of bounds before */
+        return
+                t.tm_year != tm->tm_year ||
+                t.tm_mon != tm->tm_mon ||
+                t.tm_mday != tm->tm_mday ||
+                t.tm_hour != tm->tm_hour ||
+                t.tm_min != tm->tm_min ||
+                t.tm_sec != tm->tm_sec;
+}
+
+static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) {
+        struct tm t;
+        int k;
+
+        if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS)
+                return true;
+
+        t = *tm;
+        if (mktime_or_timegm(&t, utc) < 0)
+                return false;
+
+        k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
+        return (weekdays_bits & (1 << k));
+}
+
+static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
+        struct tm c;
+        int tm_usec;
+        int r;
+
+        assert(spec);
+        assert(tm);
+
+        c = *tm;
+        tm_usec = *usec;
+
+        for (;;) {
+                /* Normalize the current date */
+                (void) mktime_or_timegm(&c, spec->utc);
+                c.tm_isdst = spec->dst;
+
+                c.tm_year += 1900;
+                r = find_matching_component(spec, spec->year, &c, &c.tm_year);
+                c.tm_year -= 1900;
+
+                if (r > 0) {
+                        c.tm_mon = 0;
+                        c.tm_mday = 1;
+                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
+                }
+                if (r < 0)
+                        return r;
+                if (tm_out_of_bounds(&c, spec->utc))
+                        return -ENOENT;
+
+                c.tm_mon += 1;
+                r = find_matching_component(spec, spec->month, &c, &c.tm_mon);
+                c.tm_mon -= 1;
+
+                if (r > 0) {
+                        c.tm_mday = 1;
+                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
+                }
+                if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
+                        c.tm_year++;
+                        c.tm_mon = 0;
+                        c.tm_mday = 1;
+                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
+                        continue;
+                }
+
+                r = find_matching_component(spec, spec->day, &c, &c.tm_mday);
+                if (r > 0)
+                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
+                if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
+                        c.tm_mon++;
+                        c.tm_mday = 1;
+                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
+                        continue;
+                }
+
+                if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) {
+                        c.tm_mday++;
+                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
+                        continue;
+                }
+
+                r = find_matching_component(spec, spec->hour, &c, &c.tm_hour);
+                if (r > 0)
+                        c.tm_min = c.tm_sec = tm_usec = 0;
+                if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
+                        c.tm_mday++;
+                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
+                        continue;
+                }
+
+                r = find_matching_component(spec, spec->minute, &c, &c.tm_min);
+                if (r > 0)
+                        c.tm_sec = tm_usec = 0;
+                if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
+                        c.tm_hour++;
+                        c.tm_min = c.tm_sec = tm_usec = 0;
+                        continue;
+                }
+
+                c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec;
+                r = find_matching_component(spec, spec->microsecond, &c, &c.tm_sec);
+                tm_usec = c.tm_sec % USEC_PER_SEC;
+                c.tm_sec /= USEC_PER_SEC;
+
+                if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
+                        c.tm_min++;
+                        c.tm_sec = tm_usec = 0;
+                        continue;
+                }
+
+                *tm = c;
+                *usec = tm_usec;
+                return 0;
+        }
+}
+
+static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, usec_t *next) {
+        struct tm tm;
+        time_t t;
+        int r;
+        usec_t tm_usec;
+
+        assert(spec);
+        assert(next);
+
+        if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
+                return -EINVAL;
+
+        usec++;
+        t = (time_t) (usec / USEC_PER_SEC);
+        assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc));
+        tm_usec = usec % USEC_PER_SEC;
+
+        r = find_next(spec, &tm, &tm_usec);
+        if (r < 0)
+                return r;
+
+        t = mktime_or_timegm(&tm, spec->utc);
+        if (t < 0)
+                return -EINVAL;
+
+        *next = (usec_t) t * USEC_PER_SEC + tm_usec;
+        return 0;
+}
+
+typedef struct SpecNextResult {
+        usec_t next;
+        int return_value;
+} SpecNextResult;
+
+int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
+        SpecNextResult *shared, tmp;
+        int r;
+
+        if (isempty(spec->timezone))
+                return calendar_spec_next_usec_impl(spec, usec, next);
+
+        shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
+        if (shared == MAP_FAILED)
+                return negative_errno();
+
+        r = safe_fork("(sd-calendar)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT, NULL);
+        if (r < 0) {
+                (void) munmap(shared, sizeof *shared);
+                return r;
+        }
+        if (r == 0) {
+                if (setenv("TZ", spec->timezone, 1) != 0) {
+                        shared->return_value = negative_errno();
+                        _exit(EXIT_FAILURE);
+                }
+
+                tzset();
+
+                shared->return_value = calendar_spec_next_usec_impl(spec, usec, &shared->next);
+
+                _exit(EXIT_SUCCESS);
+        }
+
+        tmp = *shared;
+        if (munmap(shared, sizeof *shared) < 0)
+                return negative_errno();
+
+        if (tmp.return_value == 0)
+                *next = tmp.next;
+
+        return tmp.return_value;
+}
diff --git a/src/shared/calendarspec.h b/src/shared/calendarspec.h
new file mode 100644 (file)
index 0000000..3bf8a39
--- /dev/null
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+/* A structure for specifying (possibly repetitive) points in calendar
+ * time, a la cron */
+
+#include <stdbool.h>
+
+#include "time-util.h"
+#include "util.h"
+
+typedef struct CalendarComponent {
+        int start;
+        int stop;
+        int repeat;
+
+        struct CalendarComponent *next;
+} CalendarComponent;
+
+typedef struct CalendarSpec {
+        int weekdays_bits;
+        bool end_of_month;
+        bool utc;
+        int dst;
+        char *timezone;
+
+        CalendarComponent *year;
+        CalendarComponent *month;
+        CalendarComponent *day;
+
+        CalendarComponent *hour;
+        CalendarComponent *minute;
+        CalendarComponent *microsecond;
+} CalendarSpec;
+
+CalendarSpec* calendar_spec_free(CalendarSpec *c);
+
+int calendar_spec_normalize(CalendarSpec *spec);
+bool calendar_spec_valid(CalendarSpec *spec);
+
+int calendar_spec_to_string(const CalendarSpec *spec, char **p);
+int calendar_spec_from_string(const char *p, CalendarSpec **spec);
+
+int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(CalendarSpec*, calendar_spec_free);
diff --git a/src/shared/clock-util.c b/src/shared/clock-util.c
new file mode 100644 (file)
index 0000000..1877a81
--- /dev/null
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <time.h>
+#include <linux/rtc.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+
+#include "alloc-util.h"
+#include "clock-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "macro.h"
+#include "string-util.h"
+#include "util.h"
+
+int clock_get_hwclock(struct tm *tm) {
+        _cleanup_close_ int fd = -1;
+
+        assert(tm);
+
+        fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        /* This leaves the timezone fields of struct tm
+         * uninitialized! */
+        if (ioctl(fd, RTC_RD_TIME, tm) < 0)
+                return -errno;
+
+        /* We don't know daylight saving, so we reset this in order not
+         * to confuse mktime(). */
+        tm->tm_isdst = -1;
+
+        return 0;
+}
+
+int clock_set_hwclock(const struct tm *tm) {
+        _cleanup_close_ int fd = -1;
+
+        assert(tm);
+
+        fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        if (ioctl(fd, RTC_SET_TIME, tm) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int clock_is_localtime(const char* adjtime_path) {
+        _cleanup_fclose_ FILE *f;
+        int r;
+
+        if (!adjtime_path)
+                adjtime_path = "/etc/adjtime";
+
+        /*
+         * The third line of adjtime is "UTC" or "LOCAL" or nothing.
+         *   # /etc/adjtime
+         *   0.0 0 0
+         *   0
+         *   UTC
+         */
+        f = fopen(adjtime_path, "re");
+        if (f) {
+                _cleanup_free_ char *line = NULL;
+                unsigned i;
+
+                for (i = 0; i < 2; i++) { /* skip the first two lines */
+                        r = read_line(f, LONG_LINE_MAX, NULL);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                return false; /* less than three lines → default to UTC */
+                }
+
+                r = read_line(f, LONG_LINE_MAX, &line);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return false; /* less than three lines → default to UTC */
+
+                return streq(line, "LOCAL");
+
+        } else if (errno != ENOENT)
+                return -errno;
+
+        /* adjtime not present → default to UTC */
+        return false;
+}
+
+int clock_set_timezone(int *min) {
+        const struct timeval *tv_null = NULL;
+        struct timespec ts;
+        struct tm tm;
+        int minutesdelta;
+        struct timezone tz;
+
+        assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
+        assert_se(localtime_r(&ts.tv_sec, &tm));
+        minutesdelta = tm.tm_gmtoff / 60;
+
+        tz.tz_minuteswest = -minutesdelta;
+        tz.tz_dsttime = 0; /* DST_NONE */
+
+        /*
+         * If the RTC does not run in UTC but in local time, the very first
+         * call to settimeofday() will set the kernel's timezone and will warp the
+         * system clock, so that it runs in UTC instead of the local time we
+         * have read from the RTC.
+         */
+        if (settimeofday(tv_null, &tz) < 0)
+                return negative_errno();
+
+        if (min)
+                *min = minutesdelta;
+        return 0;
+}
+
+int clock_reset_timewarp(void) {
+        const struct timeval *tv_null = NULL;
+        struct timezone tz;
+
+        tz.tz_minuteswest = 0;
+        tz.tz_dsttime = 0; /* DST_NONE */
+
+        /*
+         * The very first call to settimeofday() does time warp magic. Do a
+         * dummy call here, so the time warping is sealed and all later calls
+         * behave as expected.
+         */
+        if (settimeofday(tv_null, &tz) < 0)
+                return -errno;
+
+        return 0;
+}
+
+#define TIME_EPOCH_USEC ((usec_t) TIME_EPOCH * USEC_PER_SEC)
+
+int clock_apply_epoch(void) {
+        struct timespec ts;
+
+        if (now(CLOCK_REALTIME) >= TIME_EPOCH_USEC)
+                return 0;
+
+        if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, TIME_EPOCH_USEC)) < 0)
+                return -errno;
+
+        return 1;
+}
diff --git a/src/shared/clock-util.h b/src/shared/clock-util.h
new file mode 100644 (file)
index 0000000..b9db54e
--- /dev/null
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <time.h>
+
+int clock_is_localtime(const char* adjtime_path);
+int clock_set_timezone(int *min);
+int clock_reset_timewarp(void);
+int clock_get_hwclock(struct tm *tm);
+int clock_set_hwclock(const struct tm *tm);
+int clock_apply_epoch(void);
diff --git a/src/shared/cpu-set-util.c b/src/shared/cpu-set-util.c
new file mode 100644 (file)
index 0000000..9a789ae
--- /dev/null
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <stddef.h>
+#include <syslog.h>
+
+#include "alloc-util.h"
+#include "cpu-set-util.h"
+#include "extract-word.h"
+#include "log.h"
+#include "macro.h"
+#include "parse-util.h"
+#include "string-util.h"
+
+cpu_set_t* cpu_set_malloc(unsigned *ncpus) {
+        cpu_set_t *c;
+        unsigned n = 1024;
+
+        /* Allocates the cpuset in the right size */
+
+        for (;;) {
+                c = CPU_ALLOC(n);
+                if (!c)
+                        return NULL;
+
+                if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), c) >= 0) {
+                        CPU_ZERO_S(CPU_ALLOC_SIZE(n), c);
+
+                        if (ncpus)
+                                *ncpus = n;
+
+                        return c;
+                }
+
+                CPU_FREE(c);
+
+                if (errno != EINVAL)
+                        return NULL;
+
+                n *= 2;
+        }
+}
+
+int parse_cpu_set_internal(
+                const char *rvalue,
+                cpu_set_t **cpu_set,
+                bool warn,
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *lvalue) {
+
+        _cleanup_cpu_free_ cpu_set_t *c = NULL;
+        const char *p = rvalue;
+        unsigned ncpus = 0;
+
+        assert(rvalue);
+
+        for (;;) {
+                _cleanup_free_ char *word = NULL;
+                unsigned cpu, cpu_lower, cpu_upper;
+                int r;
+
+                r = extract_first_word(&p, &word, WHITESPACE ",", EXTRACT_QUOTES);
+                if (r == -ENOMEM)
+                        return warn ? log_oom() : -ENOMEM;
+                if (r < 0)
+                        return warn ? log_syntax(unit, LOG_ERR, filename, line, r, "Invalid value for %s: %s", lvalue, rvalue) : r;
+                if (r == 0)
+                        break;
+
+                if (!c) {
+                        c = cpu_set_malloc(&ncpus);
+                        if (!c)
+                                return warn ? log_oom() : -ENOMEM;
+                }
+
+                r = parse_range(word, &cpu_lower, &cpu_upper);
+                if (r < 0)
+                        return warn ? log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CPU affinity '%s'", word) : r;
+                if (cpu_lower >= ncpus || cpu_upper >= ncpus)
+                        return warn ? log_syntax(unit, LOG_ERR, filename, line, EINVAL, "CPU out of range '%s' ncpus is %u", word, ncpus) : -EINVAL;
+
+                if (cpu_lower > cpu_upper) {
+                        if (warn)
+                                log_syntax(unit, LOG_WARNING, filename, line, 0, "Range '%s' is invalid, %u > %u, ignoring", word, cpu_lower, cpu_upper);
+                        continue;
+                }
+
+                for (cpu = cpu_lower; cpu <= cpu_upper; cpu++)
+                        CPU_SET_S(cpu, CPU_ALLOC_SIZE(ncpus), c);
+        }
+
+        /* On success, sets *cpu_set and returns ncpus for the system. */
+        if (c)
+                *cpu_set = TAKE_PTR(c);
+
+        return (int) ncpus;
+}
diff --git a/src/shared/cpu-set-util.h b/src/shared/cpu-set-util.h
new file mode 100644 (file)
index 0000000..1b6bd35
--- /dev/null
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <sched.h>
+
+#include "macro.h"
+
+#ifdef __NCPUBITS
+#define CPU_SIZE_TO_NUM(n) ((n) * __NCPUBITS)
+#else
+#define CPU_SIZE_TO_NUM(n) ((n) * sizeof(cpu_set_t) * 8)
+#endif
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(cpu_set_t*, CPU_FREE);
+#define _cleanup_cpu_free_ _cleanup_(CPU_FREEp)
+
+static inline cpu_set_t* cpu_set_mfree(cpu_set_t *p) {
+        if (p)
+                CPU_FREE(p);
+        return NULL;
+}
+
+cpu_set_t* cpu_set_malloc(unsigned *ncpus);
+
+int parse_cpu_set_internal(const char *rvalue, cpu_set_t **cpu_set, bool warn, const char *unit, const char *filename, unsigned line, const char *lvalue);
+
+static inline int parse_cpu_set_and_warn(const char *rvalue, cpu_set_t **cpu_set, const char *unit, const char *filename, unsigned line, const char *lvalue) {
+        assert(lvalue);
+
+        return parse_cpu_set_internal(rvalue, cpu_set, true, unit, filename, line, lvalue);
+}
+
+static inline int parse_cpu_set(const char *rvalue, cpu_set_t **cpu_set){
+        return parse_cpu_set_internal(rvalue, cpu_set, false, NULL, NULL, 0, NULL);
+}
diff --git a/src/shared/crypt-util.c b/src/shared/crypt-util.c
new file mode 100644 (file)
index 0000000..20bdc54
--- /dev/null
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#if HAVE_LIBCRYPTSETUP
+#include "crypt-util.h"
+#include "log.h"
+
+void cryptsetup_log_glue(int level, const char *msg, void *usrptr) {
+        switch (level) {
+        case CRYPT_LOG_NORMAL:
+                level = LOG_NOTICE;
+                break;
+        case CRYPT_LOG_ERROR:
+                level = LOG_ERR;
+                break;
+        case CRYPT_LOG_VERBOSE:
+                level = LOG_INFO;
+                break;
+        case CRYPT_LOG_DEBUG:
+                level = LOG_DEBUG;
+                break;
+        default:
+                log_error("Unknown libcryptsetup log level: %d", level);
+                level = LOG_ERR;
+        }
+
+        log_full(level, "%s", msg);
+}
+#endif
diff --git a/src/shared/crypt-util.h b/src/shared/crypt-util.h
new file mode 100644 (file)
index 0000000..8c86714
--- /dev/null
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#if HAVE_LIBCRYPTSETUP
+#include <libcryptsetup.h>
+
+#include "macro.h"
+
+/* libcryptsetup define for any LUKS version, compatible with libcryptsetup 1.x */
+#ifndef CRYPT_LUKS
+#define CRYPT_LUKS NULL
+#endif
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct crypt_device *, crypt_free);
+
+void cryptsetup_log_glue(int level, const char *msg, void *usrptr);
+#endif
diff --git a/src/shared/exec-util.c b/src/shared/exec-util.c
new file mode 100644 (file)
index 0000000..10d774d
--- /dev/null
@@ -0,0 +1,348 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include "alloc-util.h"
+#include "conf-files.h"
+#include "env-util.h"
+#include "exec-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "hashmap.h"
+#include "macro.h"
+#include "process-util.h"
+#include "serialize.h"
+#include "set.h"
+#include "signal-util.h"
+#include "stat-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "util.h"
+
+/* Put this test here for a lack of better place */
+assert_cc(EAGAIN == EWOULDBLOCK);
+
+static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid) {
+
+        pid_t _pid;
+        int r;
+
+        if (null_or_empty_path(path)) {
+                log_debug("%s is empty (a mask).", path);
+                return 0;
+        }
+
+        r = safe_fork("(direxec)", FORK_DEATHSIG|FORK_LOG, &_pid);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                char *_argv[2];
+
+                if (stdout_fd >= 0) {
+                        r = rearrange_stdio(STDIN_FILENO, stdout_fd, STDERR_FILENO);
+                        if (r < 0)
+                                _exit(EXIT_FAILURE);
+                }
+
+                if (!argv) {
+                        _argv[0] = (char*) path;
+                        _argv[1] = NULL;
+                        argv = _argv;
+                } else
+                        argv[0] = (char*) path;
+
+                execv(path, argv);
+                log_error_errno(errno, "Failed to execute %s: %m", path);
+                _exit(EXIT_FAILURE);
+        }
+
+        *pid = _pid;
+        return 1;
+}
+
+static int do_execute(
+                char **directories,
+                usec_t timeout,
+                gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
+                void* const callback_args[_STDOUT_CONSUME_MAX],
+                int output_fd,
+                char *argv[],
+                char *envp[]) {
+
+        _cleanup_hashmap_free_free_ Hashmap *pids = NULL;
+        _cleanup_strv_free_ char **paths = NULL;
+        char **path, **e;
+        int r;
+
+        /* We fork this all off from a child process so that we can somewhat cleanly make
+         * use of SIGALRM to set a time limit.
+         *
+         * If callbacks is nonnull, execution is serial. Otherwise, we default to parallel.
+         */
+
+        r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE|CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char* const*) directories);
+        if (r < 0)
+                return log_error_errno(r, "Failed to enumerate executables: %m");
+
+        if (!callbacks) {
+                pids = hashmap_new(NULL);
+                if (!pids)
+                        return log_oom();
+        }
+
+        /* Abort execution of this process after the timout. We simply rely on SIGALRM as
+         * default action terminating the process, and turn on alarm(). */
+
+        if (timeout != USEC_INFINITY)
+                alarm(DIV_ROUND_UP(timeout, USEC_PER_SEC));
+
+        STRV_FOREACH(e, envp)
+                if (putenv(*e) != 0)
+                        return log_error_errno(errno, "Failed to set environment variable: %m");
+
+        STRV_FOREACH(path, paths) {
+                _cleanup_free_ char *t = NULL;
+                _cleanup_close_ int fd = -1;
+                pid_t pid;
+
+                t = strdup(*path);
+                if (!t)
+                        return log_oom();
+
+                if (callbacks) {
+                        fd = open_serialization_fd(basename(*path));
+                        if (fd < 0)
+                                return log_error_errno(fd, "Failed to open serialization file: %m");
+                }
+
+                r = do_spawn(t, argv, fd, &pid);
+                if (r <= 0)
+                        continue;
+
+                if (pids) {
+                        r = hashmap_put(pids, PID_TO_PTR(pid), t);
+                        if (r < 0)
+                                return log_oom();
+                        t = NULL;
+                } else {
+                        r = wait_for_terminate_and_check(t, pid, WAIT_LOG);
+                        if (r < 0)
+                                continue;
+
+                        if (lseek(fd, 0, SEEK_SET) < 0)
+                                return log_error_errno(errno, "Failed to seek on serialization fd: %m");
+
+                        r = callbacks[STDOUT_GENERATE](fd, callback_args[STDOUT_GENERATE]);
+                        fd = -1;
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to process output from %s: %m", *path);
+                }
+        }
+
+        if (callbacks) {
+                r = callbacks[STDOUT_COLLECT](output_fd, callback_args[STDOUT_COLLECT]);
+                if (r < 0)
+                        return log_error_errno(r, "Callback two failed: %m");
+        }
+
+        while (!hashmap_isempty(pids)) {
+                _cleanup_free_ char *t = NULL;
+                pid_t pid;
+
+                pid = PTR_TO_PID(hashmap_first_key(pids));
+                assert(pid > 0);
+
+                t = hashmap_remove(pids, PID_TO_PTR(pid));
+                assert(t);
+
+                (void) wait_for_terminate_and_check(t, pid, WAIT_LOG);
+        }
+
+        return 0;
+}
+
+int execute_directories(
+                const char* const* directories,
+                usec_t timeout,
+                gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
+                void* const callback_args[_STDOUT_CONSUME_MAX],
+                char *argv[],
+                char *envp[]) {
+
+        char **dirs = (char**) directories;
+        _cleanup_close_ int fd = -1;
+        char *name;
+        int r;
+
+        assert(!strv_isempty(dirs));
+
+        name = basename(dirs[0]);
+        assert(!isempty(name));
+
+        if (callbacks) {
+                assert(callback_args);
+                assert(callbacks[STDOUT_GENERATE]);
+                assert(callbacks[STDOUT_COLLECT]);
+                assert(callbacks[STDOUT_CONSUME]);
+
+                fd = open_serialization_fd(name);
+                if (fd < 0)
+                        return log_error_errno(fd, "Failed to open serialization file: %m");
+        }
+
+        /* Executes all binaries in the directories serially or in parallel and waits for
+         * them to finish. Optionally a timeout is applied. If a file with the same name
+         * exists in more than one directory, the earliest one wins. */
+
+        r = safe_fork("(sd-executor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv, envp);
+                _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+        }
+
+        if (!callbacks)
+                return 0;
+
+        if (lseek(fd, 0, SEEK_SET) < 0)
+                return log_error_errno(errno, "Failed to rewind serialization fd: %m");
+
+        r = callbacks[STDOUT_CONSUME](fd, callback_args[STDOUT_CONSUME]);
+        fd = -1;
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse returned data: %m");
+        return 0;
+}
+
+static int gather_environment_generate(int fd, void *arg) {
+        char ***env = arg, **x, **y;
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_strv_free_ char **new = NULL;
+        int r;
+
+        /* Read a series of VAR=value assignments from fd, use them to update the list of
+         * variables in env. Also update the exported environment.
+         *
+         * fd is always consumed, even on error.
+         */
+
+        assert(env);
+
+        f = fdopen(fd, "r");
+        if (!f) {
+                safe_close(fd);
+                return -errno;
+        }
+
+        r = load_env_file_pairs(f, NULL, &new);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH_PAIR(x, y, new) {
+                char *p;
+
+                if (!env_name_is_valid(*x)) {
+                        log_warning("Invalid variable assignment \"%s=...\", ignoring.", *x);
+                        continue;
+                }
+
+                p = strjoin(*x, "=", *y);
+                if (!p)
+                        return -ENOMEM;
+
+                r = strv_env_replace(env, p);
+                if (r < 0)
+                        return r;
+
+                if (setenv(*x, *y, true) < 0)
+                        return -errno;
+        }
+
+        return r;
+}
+
+static int gather_environment_collect(int fd, void *arg) {
+        _cleanup_fclose_ FILE *f = NULL;
+        char ***env = arg;
+        int r;
+
+        /* Write out a series of env=cescape(VAR=value) assignments to fd. */
+
+        assert(env);
+
+        f = fdopen(fd, "w");
+        if (!f) {
+                safe_close(fd);
+                return -errno;
+        }
+
+        r = serialize_strv(f, "env", *env);
+        if (r < 0)
+                return r;
+
+        r = fflush_and_check(f);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int gather_environment_consume(int fd, void *arg) {
+        _cleanup_fclose_ FILE *f = NULL;
+        char ***env = arg;
+        int r = 0;
+
+        /* Read a series of env=cescape(VAR=value) assignments from fd into env. */
+
+        assert(env);
+
+        f = fdopen(fd, "re");
+        if (!f) {
+                safe_close(fd);
+                return -errno;
+        }
+
+        for (;;) {
+                _cleanup_free_ char *line = NULL;
+                const char *v;
+                int k;
+
+                k = read_line(f, LONG_LINE_MAX, &line);
+                if (k < 0)
+                        return k;
+                if (k == 0)
+                        break;
+
+                v = startswith(line, "env=");
+                if (!v) {
+                        log_debug("Serialization line \"%s\" unexpectedly didn't start with \"env=\".", line);
+                        if (r == 0)
+                                r = -EINVAL;
+
+                        continue;
+                }
+
+                k = deserialize_environment(v, env);
+                if (k < 0) {
+                        log_debug_errno(k, "Invalid serialization line \"%s\": %m", line);
+
+                        if (r == 0)
+                                r = k;
+                }
+        }
+
+        return r;
+}
+
+const gather_stdout_callback_t gather_environment[] = {
+        gather_environment_generate,
+        gather_environment_collect,
+        gather_environment_consume,
+};
diff --git a/src/shared/exec-util.h b/src/shared/exec-util.h
new file mode 100644 (file)
index 0000000..6ac3c90
--- /dev/null
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+
+#include "time-util.h"
+
+typedef int (*gather_stdout_callback_t) (int fd, void *arg);
+
+enum {
+        STDOUT_GENERATE,   /* from generators to helper process */
+        STDOUT_COLLECT,    /* from helper process to main process */
+        STDOUT_CONSUME,    /* process data in main process */
+        _STDOUT_CONSUME_MAX,
+};
+
+int execute_directories(
+                const char* const* directories,
+                usec_t timeout,
+                gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
+                void* const callback_args[_STDOUT_CONSUME_MAX],
+                char *argv[],
+                char *envp[]);
+
+extern const gather_stdout_callback_t gather_environment[_STDOUT_CONSUME_MAX];
diff --git a/src/shared/exit-status.c b/src/shared/exit-status.c
new file mode 100644 (file)
index 0000000..21af8c4
--- /dev/null
@@ -0,0 +1,280 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <signal.h>
+#include <stdlib.h>
+#include <sysexits.h>
+
+#include "exit-status.h"
+#include "macro.h"
+#include "set.h"
+
+const char* exit_status_to_string(int status, ExitStatusLevel level) {
+
+        /* Exit status ranges:
+         *
+         *   0…1   │ ISO C, EXIT_SUCCESS + EXIT_FAILURE
+         *   2…7   │ LSB exit codes for init scripts
+         *   8…63  │ (Currently unmapped)
+         *  64…78  │ BSD defined exit codes
+         *  79…199 │ (Currently unmapped)
+         * 200…241 │ systemd's private error codes (might be extended to 254 in future development)
+         * 242…254 │ (Currently unmapped, but see above)
+         *   255   │ (We should probably stay away from that one, it's frequently used by applications to indicate an
+         *         │ exit reason that cannot really be expressed in a single exit status value — such as a propagated
+         *         │ signal or such)
+         */
+
+        switch (status) {  /* We always cover the ISO C ones */
+
+        case EXIT_SUCCESS:
+                return "SUCCESS";
+
+        case EXIT_FAILURE:
+                return "FAILURE";
+        }
+
+        if (IN_SET(level, EXIT_STATUS_SYSTEMD, EXIT_STATUS_LSB, EXIT_STATUS_FULL)) {
+                switch (status) { /* Optionally we cover our own ones */
+
+                case EXIT_CHDIR:
+                        return "CHDIR";
+
+                case EXIT_NICE:
+                        return "NICE";
+
+                case EXIT_FDS:
+                        return "FDS";
+
+                case EXIT_EXEC:
+                        return "EXEC";
+
+                case EXIT_MEMORY:
+                        return "MEMORY";
+
+                case EXIT_LIMITS:
+                        return "LIMITS";
+
+                case EXIT_OOM_ADJUST:
+                        return "OOM_ADJUST";
+
+                case EXIT_SIGNAL_MASK:
+                        return "SIGNAL_MASK";
+
+                case EXIT_STDIN:
+                        return "STDIN";
+
+                case EXIT_STDOUT:
+                        return "STDOUT";
+
+                case EXIT_CHROOT:
+                        return "CHROOT";
+
+                case EXIT_IOPRIO:
+                        return "IOPRIO";
+
+                case EXIT_TIMERSLACK:
+                        return "TIMERSLACK";
+
+                case EXIT_SECUREBITS:
+                        return "SECUREBITS";
+
+                case EXIT_SETSCHEDULER:
+                        return "SETSCHEDULER";
+
+                case EXIT_CPUAFFINITY:
+                        return "CPUAFFINITY";
+
+                case EXIT_GROUP:
+                        return "GROUP";
+
+                case EXIT_USER:
+                        return "USER";
+
+                case EXIT_CAPABILITIES:
+                        return "CAPABILITIES";
+
+                case EXIT_CGROUP:
+                        return "CGROUP";
+
+                case EXIT_SETSID:
+                        return "SETSID";
+
+                case EXIT_CONFIRM:
+                        return "CONFIRM";
+
+                case EXIT_STDERR:
+                        return "STDERR";
+
+                case EXIT_PAM:
+                        return "PAM";
+
+                case EXIT_NETWORK:
+                        return "NETWORK";
+
+                case EXIT_NAMESPACE:
+                        return "NAMESPACE";
+
+                case EXIT_NO_NEW_PRIVILEGES:
+                        return "NO_NEW_PRIVILEGES";
+
+                case EXIT_SECCOMP:
+                        return "SECCOMP";
+
+                case EXIT_SELINUX_CONTEXT:
+                        return "SELINUX_CONTEXT";
+
+                case EXIT_PERSONALITY:
+                        return "PERSONALITY";
+
+                case EXIT_APPARMOR_PROFILE:
+                        return "APPARMOR";
+
+                case EXIT_ADDRESS_FAMILIES:
+                        return "ADDRESS_FAMILIES";
+
+                case EXIT_RUNTIME_DIRECTORY:
+                        return "RUNTIME_DIRECTORY";
+
+                case EXIT_CHOWN:
+                        return "CHOWN";
+
+                case EXIT_SMACK_PROCESS_LABEL:
+                        return "SMACK_PROCESS_LABEL";
+
+                case EXIT_KEYRING:
+                        return "KEYRING";
+
+                case EXIT_STATE_DIRECTORY:
+                        return "STATE_DIRECTORY";
+
+                case EXIT_CACHE_DIRECTORY:
+                        return "CACHE_DIRECTORY";
+
+                case EXIT_LOGS_DIRECTORY:
+                        return "LOGS_DIRECTORY";
+
+                case EXIT_CONFIGURATION_DIRECTORY:
+                        return "CONFIGURATION_DIRECTORY";
+                }
+        }
+
+        if (IN_SET(level, EXIT_STATUS_LSB, EXIT_STATUS_FULL)) {
+                switch (status) { /* Optionally we support LSB ones */
+
+                case EXIT_INVALIDARGUMENT:
+                        return "INVALIDARGUMENT";
+
+                case EXIT_NOTIMPLEMENTED:
+                        return "NOTIMPLEMENTED";
+
+                case EXIT_NOPERMISSION:
+                        return "NOPERMISSION";
+
+                case EXIT_NOTINSTALLED:
+                        return "NOTINSTALLED";
+
+                case EXIT_NOTCONFIGURED:
+                        return "NOTCONFIGURED";
+
+                case EXIT_NOTRUNNING:
+                        return "NOTRUNNING";
+                }
+        }
+
+        if (level == EXIT_STATUS_FULL) {
+                switch (status) { /* Optionally, we support BSD exit statusses */
+
+                case EX_USAGE:
+                        return "USAGE";
+
+                case EX_DATAERR:
+                        return "DATAERR";
+
+                case EX_NOINPUT:
+                        return "NOINPUT";
+
+                case EX_NOUSER:
+                        return "NOUSER";
+
+                case EX_NOHOST:
+                        return "NOHOST";
+
+                case EX_UNAVAILABLE:
+                        return "UNAVAILABLE";
+
+                case EX_SOFTWARE:
+                        return "SOFTWARE";
+
+                case EX_OSERR:
+                        return "OSERR";
+
+                case EX_OSFILE:
+                        return "OSFILE";
+
+                case EX_CANTCREAT:
+                        return "CANTCREAT";
+
+                case EX_IOERR:
+                        return "IOERR";
+
+                case EX_TEMPFAIL:
+                        return "TEMPFAIL";
+
+                case EX_PROTOCOL:
+                        return "PROTOCOL";
+
+                case EX_NOPERM:
+                        return "NOPERM";
+
+                case EX_CONFIG:
+                        return "CONFIG";
+                }
+        }
+
+        return NULL;
+}
+
+bool is_clean_exit(int code, int status, ExitClean clean, ExitStatusSet *success_status) {
+
+        if (code == CLD_EXITED)
+                return status == 0 ||
+                       (success_status &&
+                        set_contains(success_status->status, INT_TO_PTR(status)));
+
+        /* If a daemon does not implement handlers for some of the signals that's not considered an unclean shutdown */
+        if (code == CLD_KILLED)
+                return
+                        (clean == EXIT_CLEAN_DAEMON && IN_SET(status, SIGHUP, SIGINT, SIGTERM, SIGPIPE)) ||
+                        (success_status &&
+                         set_contains(success_status->signal, INT_TO_PTR(status)));
+
+        return false;
+}
+
+void exit_status_set_free(ExitStatusSet *x) {
+        assert(x);
+
+        x->status = set_free(x->status);
+        x->signal = set_free(x->signal);
+}
+
+bool exit_status_set_is_empty(ExitStatusSet *x) {
+        if (!x)
+                return true;
+
+        return set_isempty(x->status) && set_isempty(x->signal);
+}
+
+bool exit_status_set_test(ExitStatusSet *x, int code, int status) {
+
+        if (exit_status_set_is_empty(x))
+                return false;
+
+        if (code == CLD_EXITED && set_contains(x->status, INT_TO_PTR(status)))
+                return true;
+
+        if (IN_SET(code, CLD_KILLED, CLD_DUMPED) && set_contains(x->signal, INT_TO_PTR(status)))
+                return true;
+
+        return false;
+}
diff --git a/src/shared/exit-status.h b/src/shared/exit-status.h
new file mode 100644 (file)
index 0000000..c41e8b8
--- /dev/null
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+
+#include "hashmap.h"
+#include "macro.h"
+#include "set.h"
+
+/* This defines pretty names for the LSB 'start' verb exit codes. Note that they shouldn't be confused with the LSB
+ * 'status' verb exit codes which are defined very differently. For details see:
+ *
+ * https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
+ */
+
+enum {
+        /* EXIT_SUCCESS defined by libc */
+        /* EXIT_FAILURE defined by libc */
+        EXIT_INVALIDARGUMENT = 2,
+        EXIT_NOTIMPLEMENTED = 3,
+        EXIT_NOPERMISSION = 4,
+        EXIT_NOTINSTALLED = 5,
+        EXIT_NOTCONFIGURED = 6,
+        EXIT_NOTRUNNING = 7,
+
+        /* BSD's sysexits.h defines a couple EX_xyz exit codes in the range 64 … 78 */
+
+        /* The LSB suggests that error codes >= 200 are "reserved". We use them here under the assumption that they
+         * hence are unused by init scripts. */
+        EXIT_CHDIR = 200,
+        EXIT_NICE,
+        EXIT_FDS,
+        EXIT_EXEC,
+        EXIT_MEMORY,
+        EXIT_LIMITS,
+        EXIT_OOM_ADJUST,
+        EXIT_SIGNAL_MASK,
+        EXIT_STDIN,
+        EXIT_STDOUT,
+        EXIT_CHROOT,   /* 210 */
+        EXIT_IOPRIO,
+        EXIT_TIMERSLACK,
+        EXIT_SECUREBITS,
+        EXIT_SETSCHEDULER,
+        EXIT_CPUAFFINITY,
+        EXIT_GROUP,
+        EXIT_USER,
+        EXIT_CAPABILITIES,
+        EXIT_CGROUP,
+        EXIT_SETSID,   /* 220 */
+        EXIT_CONFIRM,
+        EXIT_STDERR,
+        _EXIT_RESERVED, /* used to be tcpwrap, don't reuse! */
+        EXIT_PAM,
+        EXIT_NETWORK,
+        EXIT_NAMESPACE,
+        EXIT_NO_NEW_PRIVILEGES,
+        EXIT_SECCOMP,
+        EXIT_SELINUX_CONTEXT,
+        EXIT_PERSONALITY,  /* 230 */
+        EXIT_APPARMOR_PROFILE,
+        EXIT_ADDRESS_FAMILIES,
+        EXIT_RUNTIME_DIRECTORY,
+        _EXIT_RESERVED2, /* used to be used by kdbus, don't reuse */
+        EXIT_CHOWN,
+        EXIT_SMACK_PROCESS_LABEL,
+        EXIT_KEYRING,
+        EXIT_STATE_DIRECTORY,
+        EXIT_CACHE_DIRECTORY,
+        EXIT_LOGS_DIRECTORY, /* 240 */
+        EXIT_CONFIGURATION_DIRECTORY,
+};
+
+typedef enum ExitStatusLevel {
+        EXIT_STATUS_MINIMAL,   /* only cover libc EXIT_STATUS/EXIT_FAILURE */
+        EXIT_STATUS_SYSTEMD,   /* cover libc and systemd's own exit codes */
+        EXIT_STATUS_LSB,       /* cover libc, systemd's own and LSB exit codes */
+        EXIT_STATUS_FULL,      /* cover libc, systemd's own, LSB and BSD (EX_xyz) exit codes */
+} ExitStatusLevel;
+
+typedef struct ExitStatusSet {
+        Set *status;
+        Set *signal;
+} ExitStatusSet;
+
+const char* exit_status_to_string(int status, ExitStatusLevel level) _const_;
+
+typedef enum ExitClean {
+        EXIT_CLEAN_DAEMON,
+        EXIT_CLEAN_COMMAND,
+} ExitClean;
+
+bool is_clean_exit(int code, int status, ExitClean clean, ExitStatusSet *success_status);
+
+void exit_status_set_free(ExitStatusSet *x);
+bool exit_status_set_is_empty(ExitStatusSet *x);
+bool exit_status_set_test(ExitStatusSet *x, int code, int status);
diff --git a/src/shared/fileio-label.c b/src/shared/fileio-label.c
new file mode 100644 (file)
index 0000000..b5362b5
--- /dev/null
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <sys/stat.h>
+
+#include "fileio-label.h"
+#include "fileio.h"
+#include "selinux-util.h"
+
+int write_string_file_atomic_label_ts(const char *fn, const char *line, struct timespec *ts) {
+        int r;
+
+        r = mac_selinux_create_file_prepare(fn, S_IFREG);
+        if (r < 0)
+                return r;
+
+        r = write_string_file_ts(fn, line, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC, ts);
+
+        mac_selinux_create_file_clear();
+
+        return r;
+}
+
+int write_env_file_label(const char *fname, char **l) {
+        int r;
+
+        r = mac_selinux_create_file_prepare(fname, S_IFREG);
+        if (r < 0)
+                return r;
+
+        r = write_env_file(fname, l);
+
+        mac_selinux_create_file_clear();
+
+        return r;
+}
+
+int fopen_temporary_label(const char *target,
+                          const char *path, FILE **f, char **temp_path) {
+        int r;
+
+        r = mac_selinux_create_file_prepare(target, S_IFREG);
+        if (r < 0)
+                return r;
+
+        r = fopen_temporary(path, f, temp_path);
+
+        mac_selinux_create_file_clear();
+
+        return r;
+}
+
+int create_shutdown_run_nologin_or_warn(void) {
+        int r;
+
+        /* This is used twice: once in systemd-user-sessions.service, in order to block logins when we actually go
+         * down, and once in systemd-logind.service when shutdowns are scheduled, and logins are to be turned off a bit
+         * in advance. We use the same wording of the message in both cases. */
+
+        r = write_string_file_atomic_label("/run/nologin",
+                                           "System is going down. Unprivileged users are not permitted to log in anymore. "
+                                           "For technical details, see pam_nologin(8).");
+        if (r < 0)
+                return log_error_errno(r, "Failed to create /run/nologin: %m");
+
+        return 0;
+}
diff --git a/src/shared/fileio-label.h b/src/shared/fileio-label.h
new file mode 100644 (file)
index 0000000..d11112d
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdio.h>
+
+#include "fileio.h"
+
+/* These functions are split out of fileio.h (and not for examplement just as flags to the functions they wrap) in
+ * order to optimize linking: This way, -lselinux is needed only for the callers of these functions that need selinux,
+ * but not for all */
+
+int write_string_file_atomic_label_ts(const char *fn, const char *line, struct timespec *ts);
+static inline int write_string_file_atomic_label(const char *fn, const char *line) {
+        return write_string_file_atomic_label_ts(fn, line, NULL);
+}
+int write_env_file_label(const char *fname, char **l);
+int fopen_temporary_label(const char *target, const char *path, FILE **f, char **temp_path);
+
+int create_shutdown_run_nologin_or_warn(void);
diff --git a/src/shared/format-table.c b/src/shared/format-table.c
new file mode 100644 (file)
index 0000000..10e15c9
--- /dev/null
@@ -0,0 +1,1201 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <stdio_ext.h>
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-table.h"
+#include "gunicode.h"
+#include "pager.h"
+#include "parse-util.h"
+#include "string-util.h"
+#include "terminal-util.h"
+#include "time-util.h"
+#include "utf8.h"
+#include "util.h"
+
+#define DEFAULT_WEIGHT 100
+
+/*
+   A few notes on implementation details:
+
+ - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
+   table. It can be easily converted to an index number and back.
+
+ - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
+   'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
+   ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
+   outside only sees Table and TableCell.
+
+ - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
+   previous one.
+
+ - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
+   derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
+   that. The first row is always the header row. If header display is turned off we simply skip outputting the first
+   row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
+
+ - Note because there's no row and no column object some properties that might be appropriate as row/column properties
+   are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
+   add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
+   cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
+   instead.
+
+ - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
+   from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
+   this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
+*/
+
+typedef struct TableData {
+        unsigned n_ref;
+        TableDataType type;
+
+        size_t minimum_width;       /* minimum width for the column */
+        size_t maximum_width;       /* maximum width for the column */
+        unsigned weight;            /* the horizontal weight for this column, in case the table is expanded/compressed */
+        unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
+        unsigned align_percent;     /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
+
+        const char *color;          /* ANSI color string to use for this cell. When written to terminal should not move cursor. Will automatically be reset after the cell */
+        char *formatted;            /* A cached textual representation of the cell data, before ellipsation/alignment */
+
+        union {
+                uint8_t data[0];    /* data is generic array */
+                bool boolean;
+                usec_t timestamp;
+                usec_t timespan;
+                uint64_t size;
+                char string[0];
+                uint32_t uint32;
+                /* … add more here as we start supporting more cell data types … */
+        };
+} TableData;
+
+static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
+        size_t i;
+
+        assert(cell);
+
+        i = PTR_TO_SIZE(cell);
+        assert(i > 0);
+
+        return i-1;
+}
+
+static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
+        assert(index != (size_t) -1);
+        return SIZE_TO_PTR(index + 1);
+}
+
+struct Table {
+        size_t n_columns;
+        size_t n_cells;
+
+        bool header;   /* Whether to show the header row? */
+        size_t width;  /* If != (size_t) -1 the width to format this table in */
+
+        TableData **data;
+        size_t n_allocated;
+
+        size_t *display_map;  /* List of columns to show (by their index). It's fine if columns are listed multiple times or not at all */
+        size_t n_display_map;
+
+        size_t *sort_map;     /* The columns to order rows by, in order of preference. */
+        size_t n_sort_map;
+};
+
+Table *table_new_raw(size_t n_columns) {
+        _cleanup_(table_unrefp) Table *t = NULL;
+
+        assert(n_columns > 0);
+
+        t = new(Table, 1);
+        if (!t)
+                return NULL;
+
+        *t = (struct Table) {
+                .n_columns = n_columns,
+                .header = true,
+                .width = (size_t) -1,
+        };
+
+        return TAKE_PTR(t);
+}
+
+Table *table_new_internal(const char *first_header, ...) {
+        _cleanup_(table_unrefp) Table *t = NULL;
+        size_t n_columns = 1;
+        va_list ap;
+        int r;
+
+        assert(first_header);
+
+        va_start(ap, first_header);
+        for (;;) {
+                const char *h;
+
+                h = va_arg(ap, const char*);
+                if (!h)
+                        break;
+
+                n_columns++;
+        }
+        va_end(ap);
+
+        t = table_new_raw(n_columns);
+        if (!t)
+                return NULL;
+
+        r = table_add_cell(t, NULL, TABLE_STRING, first_header);
+        if (r < 0)
+                return NULL;
+
+        va_start(ap, first_header);
+        for (;;) {
+                const char *h;
+
+                h = va_arg(ap, const char*);
+                if (!h)
+                        break;
+
+                r = table_add_cell(t, NULL, TABLE_STRING, h);
+                if (r < 0) {
+                        va_end(ap);
+                        return NULL;
+                }
+        }
+        va_end(ap);
+
+        assert(t->n_columns == t->n_cells);
+        return TAKE_PTR(t);
+}
+
+static TableData *table_data_free(TableData *d) {
+        assert(d);
+
+        free(d->formatted);
+        return mfree(d);
+}
+
+DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
+
+Table *table_unref(Table *t) {
+        size_t i;
+
+        if (!t)
+                return NULL;
+
+        for (i = 0; i < t->n_cells; i++)
+                table_data_unref(t->data[i]);
+
+        free(t->data);
+        free(t->display_map);
+        free(t->sort_map);
+
+        return mfree(t);
+}
+
+static size_t table_data_size(TableDataType type, const void *data) {
+
+        switch (type) {
+
+        case TABLE_EMPTY:
+                return 0;
+
+        case TABLE_STRING:
+                return strlen(data) + 1;
+
+        case TABLE_BOOLEAN:
+                return sizeof(bool);
+
+        case TABLE_TIMESTAMP:
+        case TABLE_TIMESPAN:
+                return sizeof(usec_t);
+
+        case TABLE_SIZE:
+                return sizeof(uint64_t);
+
+        case TABLE_UINT32:
+                return sizeof(uint32_t);
+
+        default:
+                assert_not_reached("Uh? Unexpected cell type");
+        }
+}
+
+static bool table_data_matches(
+                TableData *d,
+                TableDataType type,
+                const void *data,
+                size_t minimum_width,
+                size_t maximum_width,
+                unsigned weight,
+                unsigned align_percent,
+                unsigned ellipsize_percent) {
+
+        size_t k, l;
+        assert(d);
+
+        if (d->type != type)
+                return false;
+
+        if (d->minimum_width != minimum_width)
+                return false;
+
+        if (d->maximum_width != maximum_width)
+                return false;
+
+        if (d->weight != weight)
+                return false;
+
+        if (d->align_percent != align_percent)
+                return false;
+
+        if (d->ellipsize_percent != ellipsize_percent)
+                return false;
+
+        k = table_data_size(type, data);
+        l = table_data_size(d->type, d->data);
+
+        if (k != l)
+                return false;
+
+        return memcmp(data, d->data, l) == 0;
+}
+
+static TableData *table_data_new(
+                TableDataType type,
+                const void *data,
+                size_t minimum_width,
+                size_t maximum_width,
+                unsigned weight,
+                unsigned align_percent,
+                unsigned ellipsize_percent) {
+
+        size_t data_size;
+        TableData *d;
+
+        data_size = table_data_size(type, data);
+
+        d = malloc0(offsetof(TableData, data) + data_size);
+        if (!d)
+                return NULL;
+
+        d->n_ref = 1;
+        d->type = type;
+        d->minimum_width = minimum_width;
+        d->maximum_width = maximum_width;
+        d->weight = weight;
+        d->align_percent = align_percent;
+        d->ellipsize_percent = ellipsize_percent;
+        memcpy_safe(d->data, data, data_size);
+
+        return d;
+}
+
+int table_add_cell_full(
+                Table *t,
+                TableCell **ret_cell,
+                TableDataType type,
+                const void *data,
+                size_t minimum_width,
+                size_t maximum_width,
+                unsigned weight,
+                unsigned align_percent,
+                unsigned ellipsize_percent) {
+
+        _cleanup_(table_data_unrefp) TableData *d = NULL;
+        TableData *p;
+
+        assert(t);
+        assert(type >= 0);
+        assert(type < _TABLE_DATA_TYPE_MAX);
+
+        /* Determine the cell adjacent to the current one, but one row up */
+        if (t->n_cells >= t->n_columns)
+                assert_se(p = t->data[t->n_cells - t->n_columns]);
+        else
+                p = NULL;
+
+        /* If formatting parameters are left unspecified, copy from the previous row */
+        if (minimum_width == (size_t) -1)
+                minimum_width = p ? p->minimum_width : 1;
+
+        if (weight == (unsigned) -1)
+                weight = p ? p->weight : DEFAULT_WEIGHT;
+
+        if (align_percent == (unsigned) -1)
+                align_percent = p ? p->align_percent : 0;
+
+        if (ellipsize_percent == (unsigned) -1)
+                ellipsize_percent = p ? p->ellipsize_percent : 100;
+
+        assert(align_percent <= 100);
+        assert(ellipsize_percent <= 100);
+
+        /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
+         * formatting. Let's see if we can reuse the cell data and ref it once more. */
+
+        if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent))
+                d = table_data_ref(p);
+        else {
+                d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent);
+                if (!d)
+                        return -ENOMEM;
+        }
+
+        if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
+                return -ENOMEM;
+
+        if (ret_cell)
+                *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
+
+        t->data[t->n_cells++] = TAKE_PTR(d);
+
+        return 0;
+}
+
+int table_dup_cell(Table *t, TableCell *cell) {
+        size_t i;
+
+        assert(t);
+
+        /* Add the data of the specified cell a second time as a new cell to the end. */
+
+        i = TABLE_CELL_TO_INDEX(cell);
+        if (i >= t->n_cells)
+                return -ENXIO;
+
+        if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
+                return -ENOMEM;
+
+        t->data[t->n_cells++] = table_data_ref(t->data[i]);
+        return 0;
+}
+
+static int table_dedup_cell(Table *t, TableCell *cell) {
+        TableData *nd, *od;
+        size_t i;
+
+        assert(t);
+
+        /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
+         * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
+
+        i = TABLE_CELL_TO_INDEX(cell);
+        if (i >= t->n_cells)
+                return -ENXIO;
+
+        assert_se(od = t->data[i]);
+        if (od->n_ref == 1)
+                return 0;
+
+        assert(od->n_ref > 1);
+
+        nd = table_data_new(od->type, od->data, od->minimum_width, od->maximum_width, od->weight, od->align_percent, od->ellipsize_percent);
+        if (!nd)
+                return -ENOMEM;
+
+        table_data_unref(od);
+        t->data[i] = nd;
+
+        assert(nd->n_ref == 1);
+
+        return 1;
+}
+
+static TableData *table_get_data(Table *t, TableCell *cell) {
+        size_t i;
+
+        assert(t);
+        assert(cell);
+
+        /* Get the data object of the specified cell, or NULL if it doesn't exist */
+
+        i = TABLE_CELL_TO_INDEX(cell);
+        if (i >= t->n_cells)
+                return NULL;
+
+        assert(t->data[i]);
+        assert(t->data[i]->n_ref > 0);
+
+        return t->data[i];
+}
+
+int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
+        int r;
+
+        assert(t);
+        assert(cell);
+
+        if (minimum_width == (size_t) -1)
+                minimum_width = 1;
+
+        r = table_dedup_cell(t, cell);
+        if (r < 0)
+                return r;
+
+        table_get_data(t, cell)->minimum_width = minimum_width;
+        return 0;
+}
+
+int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width) {
+        int r;
+
+        assert(t);
+        assert(cell);
+
+        r = table_dedup_cell(t, cell);
+        if (r < 0)
+                return r;
+
+        table_get_data(t, cell)->maximum_width = maximum_width;
+        return 0;
+}
+
+int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
+        int r;
+
+        assert(t);
+        assert(cell);
+
+        if (weight == (unsigned) -1)
+                weight = DEFAULT_WEIGHT;
+
+        r = table_dedup_cell(t, cell);
+        if (r < 0)
+                return r;
+
+        table_get_data(t, cell)->weight = weight;
+        return 0;
+}
+
+int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
+        int r;
+
+        assert(t);
+        assert(cell);
+
+        if (percent == (unsigned) -1)
+                percent = 0;
+
+        assert(percent <= 100);
+
+        r = table_dedup_cell(t, cell);
+        if (r < 0)
+                return r;
+
+        table_get_data(t, cell)->align_percent = percent;
+        return 0;
+}
+
+int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
+        int r;
+
+        assert(t);
+        assert(cell);
+
+        if (percent == (unsigned) -1)
+                percent = 100;
+
+        assert(percent <= 100);
+
+        r = table_dedup_cell(t, cell);
+        if (r < 0)
+                return r;
+
+        table_get_data(t, cell)->ellipsize_percent = percent;
+        return 0;
+}
+
+int table_set_color(Table *t, TableCell *cell, const char *color) {
+        int r;
+
+        assert(t);
+        assert(cell);
+
+        r = table_dedup_cell(t, cell);
+        if (r < 0)
+                return r;
+
+        table_get_data(t, cell)->color = empty_to_null(color);
+        return 0;
+}
+
+int table_add_many_internal(Table *t, TableDataType first_type, ...) {
+        TableDataType type;
+        va_list ap;
+        int r;
+
+        assert(t);
+        assert(first_type >= 0);
+        assert(first_type < _TABLE_DATA_TYPE_MAX);
+
+        type = first_type;
+
+        va_start(ap, first_type);
+        for (;;) {
+                const void *data;
+                union {
+                        uint64_t size;
+                        usec_t usec;
+                        uint32_t uint32;
+                        bool b;
+                } buffer;
+
+                switch (type) {
+
+                case TABLE_EMPTY:
+                        data = NULL;
+                        break;
+
+                case TABLE_STRING:
+                        data = va_arg(ap, const char *);
+                        break;
+
+                case TABLE_BOOLEAN:
+                        buffer.b = va_arg(ap, int);
+                        data = &buffer.b;
+                        break;
+
+                case TABLE_TIMESTAMP:
+                case TABLE_TIMESPAN:
+                        buffer.usec = va_arg(ap, usec_t);
+                        data = &buffer.usec;
+                        break;
+
+                case TABLE_SIZE:
+                        buffer.size = va_arg(ap, uint64_t);
+                        data = &buffer.size;
+                        break;
+
+                case TABLE_UINT32:
+                        buffer.uint32 = va_arg(ap, uint32_t);
+                        data = &buffer.uint32;
+                        break;
+
+                case _TABLE_DATA_TYPE_MAX:
+                        /* Used as end marker */
+                        va_end(ap);
+                        return 0;
+
+                default:
+                        assert_not_reached("Uh? Unexpected data type.");
+                }
+
+                r = table_add_cell(t, NULL, type, data);
+                if (r < 0) {
+                        va_end(ap);
+                        return r;
+                }
+
+                type = va_arg(ap, TableDataType);
+        }
+}
+
+void table_set_header(Table *t, bool b) {
+        assert(t);
+
+        t->header = b;
+}
+
+void table_set_width(Table *t, size_t width) {
+        assert(t);
+
+        t->width = width;
+}
+
+int table_set_display(Table *t, size_t first_column, ...) {
+        size_t allocated, column;
+        va_list ap;
+
+        assert(t);
+
+        allocated = t->n_display_map;
+        column = first_column;
+
+        va_start(ap, first_column);
+        for (;;) {
+                assert(column < t->n_columns);
+
+                if (!GREEDY_REALLOC(t->display_map, allocated, MAX(t->n_columns, t->n_display_map+1))) {
+                        va_end(ap);
+                        return -ENOMEM;
+                }
+
+                t->display_map[t->n_display_map++] = column;
+
+                column = va_arg(ap, size_t);
+                if (column == (size_t) -1)
+                        break;
+
+        }
+        va_end(ap);
+
+        return 0;
+}
+
+int table_set_sort(Table *t, size_t first_column, ...) {
+        size_t allocated, column;
+        va_list ap;
+
+        assert(t);
+
+        allocated = t->n_sort_map;
+        column = first_column;
+
+        va_start(ap, first_column);
+        for (;;) {
+                assert(column < t->n_columns);
+
+                if (!GREEDY_REALLOC(t->sort_map, allocated, MAX(t->n_columns, t->n_sort_map+1))) {
+                        va_end(ap);
+                        return -ENOMEM;
+                }
+
+                t->sort_map[t->n_sort_map++] = column;
+
+                column = va_arg(ap, size_t);
+                if (column == (size_t) -1)
+                        break;
+        }
+        va_end(ap);
+
+        return 0;
+}
+
+static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
+        assert(a);
+        assert(b);
+
+        if (a->type == b->type) {
+
+                /* We only define ordering for cells of the same data type. If cells with different data types are
+                 * compared we follow the order the cells were originally added in */
+
+                switch (a->type) {
+
+                case TABLE_STRING:
+                        return strcmp(a->string, b->string);
+
+                case TABLE_BOOLEAN:
+                        if (!a->boolean && b->boolean)
+                                return -1;
+                        if (a->boolean && !b->boolean)
+                                return 1;
+                        return 0;
+
+                case TABLE_TIMESTAMP:
+                        return CMP(a->timestamp, b->timestamp);
+
+                case TABLE_TIMESPAN:
+                        return CMP(a->timespan, b->timespan);
+
+                case TABLE_SIZE:
+                        return CMP(a->size, b->size);
+
+                case TABLE_UINT32:
+                        return CMP(a->uint32, b->uint32);
+
+                default:
+                        ;
+                }
+        }
+
+        /* Generic fallback using the orginal order in which the cells where added. */
+        return CMP(index_a, index_b);
+}
+
+static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
+        size_t i;
+        int r;
+
+        assert(t);
+        assert(t->sort_map);
+
+        /* Make sure the header stays at the beginning */
+        if (*a < t->n_columns && *b < t->n_columns)
+                return 0;
+        if (*a < t->n_columns)
+                return -1;
+        if (*b < t->n_columns)
+                return 1;
+
+        /* Order other lines by the sorting map */
+        for (i = 0; i < t->n_sort_map; i++) {
+                TableData *d, *dd;
+
+                d = t->data[*a + t->sort_map[i]];
+                dd = t->data[*b + t->sort_map[i]];
+
+                r = cell_data_compare(d, *a, dd, *b);
+                if (r != 0)
+                        return r;
+        }
+
+        /* Order identical lines by the order there were originally added in */
+        return CMP(*a, *b);
+}
+
+static const char *table_data_format(TableData *d) {
+        assert(d);
+
+        if (d->formatted)
+                return d->formatted;
+
+        switch (d->type) {
+        case TABLE_EMPTY:
+                return "";
+
+        case TABLE_STRING:
+                return d->string;
+
+        case TABLE_BOOLEAN:
+                return yes_no(d->boolean);
+
+        case TABLE_TIMESTAMP: {
+                _cleanup_free_ char *p;
+
+                p = new(char, FORMAT_TIMESTAMP_MAX);
+                if (!p)
+                        return NULL;
+
+                if (!format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp))
+                        return "n/a";
+
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
+        case TABLE_TIMESPAN: {
+                _cleanup_free_ char *p;
+
+                p = new(char, FORMAT_TIMESPAN_MAX);
+                if (!p)
+                        return NULL;
+
+                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timestamp, 0))
+                        return "n/a";
+
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
+        case TABLE_SIZE: {
+                _cleanup_free_ char *p;
+
+                p = new(char, FORMAT_BYTES_MAX);
+                if (!p)
+                        return NULL;
+
+                if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
+                        return "n/a";
+
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
+        case TABLE_UINT32: {
+                _cleanup_free_ char *p;
+
+                p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
+                if (!p)
+                        return NULL;
+
+                sprintf(p, "%" PRIu32, d->uint32);
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
+        default:
+                assert_not_reached("Unexpected type?");
+        }
+
+        return d->formatted;
+}
+
+static int table_data_requested_width(TableData *d, size_t *ret) {
+        const char *t;
+        size_t l;
+
+        t = table_data_format(d);
+        if (!t)
+                return -ENOMEM;
+
+        l = utf8_console_width(t);
+        if (l == (size_t) -1)
+                return -EINVAL;
+
+        if (d->maximum_width != (size_t) -1 && l > d->maximum_width)
+                l = d->maximum_width;
+
+        if (l < d->minimum_width)
+                l = d->minimum_width;
+
+        *ret = l;
+        return 0;
+}
+
+static char *align_string_mem(const char *str, size_t new_length, unsigned percent) {
+        size_t w = 0, space, lspace, old_length;
+        const char *p;
+        char *ret;
+        size_t i;
+
+        /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
+
+        assert(str);
+        assert(percent <= 100);
+
+        old_length = strlen(str);
+
+        /* Determine current width on screen */
+        p = str;
+        while (p < str + old_length) {
+                char32_t c;
+
+                if (utf8_encoded_to_unichar(p, &c) < 0) {
+                        p++, w++; /* count invalid chars as 1 */
+                        continue;
+                }
+
+                p = utf8_next_char(p);
+                w += unichar_iswide(c) ? 2 : 1;
+        }
+
+        /* Already wider than the target, if so, don't do anything */
+        if (w >= new_length)
+                return strndup(str, old_length);
+
+        /* How much spaces shall we add? An how much on the left side? */
+        space = new_length - w;
+        lspace = space * percent / 100U;
+
+        ret = new(char, space + old_length + 1);
+        if (!ret)
+                return NULL;
+
+        for (i = 0; i < lspace; i++)
+                ret[i] = ' ';
+        memcpy(ret + lspace, str, old_length);
+        for (i = lspace + old_length; i < space + old_length; i++)
+                ret[i] = ' ';
+
+        ret[space + old_length] = 0;
+        return ret;
+}
+
+int table_print(Table *t, FILE *f) {
+        size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
+                i, j, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
+                *width;
+        _cleanup_free_ size_t *sorted = NULL;
+        uint64_t *column_weight, weight_sum;
+        int r;
+
+        assert(t);
+
+        if (!f)
+                f = stdout;
+
+        /* Ensure we have no incomplete rows */
+        assert(t->n_cells % t->n_columns == 0);
+
+        n_rows = t->n_cells / t->n_columns;
+        assert(n_rows > 0); /* at least the header row must be complete */
+
+        if (t->sort_map) {
+                /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
+
+                sorted = new(size_t, n_rows);
+                if (!sorted)
+                        return -ENOMEM;
+
+                for (i = 0; i < n_rows; i++)
+                        sorted[i] = i * t->n_columns;
+
+                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
+        }
+
+        if (t->display_map)
+                display_columns = t->n_display_map;
+        else
+                display_columns = t->n_columns;
+
+        assert(display_columns > 0);
+
+        minimum_width = newa(size_t, display_columns);
+        maximum_width = newa(size_t, display_columns);
+        requested_width = newa(size_t, display_columns);
+        width = newa(size_t, display_columns);
+        column_weight = newa0(uint64_t, display_columns);
+
+        for (j = 0; j < display_columns; j++) {
+                minimum_width[j] = 1;
+                maximum_width[j] = (size_t) -1;
+                requested_width[j] = (size_t) -1;
+        }
+
+        /* First pass: determine column sizes */
+        for (i = t->header ? 0 : 1; i < n_rows; i++) {
+                TableData **row;
+
+                /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
+                 * hence we don't care for sorted[] during the first pass. */
+                row = t->data + i * t->n_columns;
+
+                for (j = 0; j < display_columns; j++) {
+                        TableData *d;
+                        size_t req;
+
+                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
+
+                        r = table_data_requested_width(d, &req);
+                        if (r < 0)
+                                return r;
+
+                        /* Determine the biggest width that any cell in this column would like to have */
+                        if (requested_width[j] == (size_t) -1 ||
+                            requested_width[j] < req)
+                                requested_width[j] = req;
+
+                        /* Determine the minimum width any cell in this column needs */
+                        if (minimum_width[j] < d->minimum_width)
+                                minimum_width[j] = d->minimum_width;
+
+                        /* Determine the maximum width any cell in this column needs */
+                        if (d->maximum_width != (size_t) -1 &&
+                            (maximum_width[j] == (size_t) -1 ||
+                             maximum_width[j] > d->maximum_width))
+                                maximum_width[j] = d->maximum_width;
+
+                        /* Determine the full columns weight */
+                        column_weight[j] += d->weight;
+                }
+        }
+
+        /* One space between each column */
+        table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
+
+        /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
+        weight_sum = 0;
+        for (j = 0; j < display_columns; j++) {
+                weight_sum += column_weight[j];
+
+                table_minimum_width += minimum_width[j];
+
+                if (maximum_width[j] == (size_t) -1)
+                        table_maximum_width = (size_t) -1;
+                else
+                        table_maximum_width += maximum_width[j];
+
+                table_requested_width += requested_width[j];
+        }
+
+        /* Calculate effective table width */
+        if (t->width == (size_t) -1)
+                table_effective_width = pager_have() ? table_requested_width : MIN(table_requested_width, columns());
+        else
+                table_effective_width = t->width;
+
+        if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
+                table_effective_width = table_maximum_width;
+
+        if (table_effective_width < table_minimum_width)
+                table_effective_width = table_minimum_width;
+
+        if (table_effective_width >= table_requested_width) {
+                size_t extra;
+
+                /* We have extra room, let's distribute it among columns according to their weights. We first provide
+                 * each column with what it asked for and the distribute the rest.  */
+
+                extra = table_effective_width - table_requested_width;
+
+                for (j = 0; j < display_columns; j++) {
+                        size_t delta;
+
+                        if (weight_sum == 0)
+                                width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
+                        else
+                                width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
+
+                        if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
+                                width[j] = maximum_width[j];
+
+                        if (width[j] < minimum_width[j])
+                                width[j] = minimum_width[j];
+
+                        assert(width[j] >= requested_width[j]);
+                        delta = width[j] - requested_width[j];
+
+                        /* Subtract what we just added from the rest */
+                        if (extra > delta)
+                                extra -= delta;
+                        else
+                                extra = 0;
+
+                        assert(weight_sum >= column_weight[j]);
+                        weight_sum -= column_weight[j];
+                }
+
+        } else {
+                /* We need to compress the table, columns can't get what they asked for. We first provide each column
+                 * with the minimum they need, and then distribute anything left. */
+                bool finalize = false;
+                size_t extra;
+
+                extra = table_effective_width - table_minimum_width;
+
+                for (j = 0; j < display_columns; j++)
+                        width[j] = (size_t) -1;
+
+                for (;;) {
+                        bool restart = false;
+
+                        for (j = 0; j < display_columns; j++) {
+                                size_t delta, w;
+
+                                /* Did this column already get something assigned? If so, let's skip to the next */
+                                if (width[j] != (size_t) -1)
+                                        continue;
+
+                                if (weight_sum == 0)
+                                        w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
+                                else
+                                        w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
+
+                                if (w >= requested_width[j]) {
+                                        /* Never give more than requested. If we hit a column like this, there's more
+                                         * space to allocate to other columns which means we need to restart the
+                                         * iteration. However, if we hit a column like this, let's assign it the space
+                                         * it wanted for good early.*/
+
+                                        w = requested_width[j];
+                                        restart = true;
+
+                                } else if (!finalize)
+                                        continue;
+
+                                width[j] = w;
+
+                                assert(w >= minimum_width[j]);
+                                delta = w - minimum_width[j];
+
+                                assert(delta <= extra);
+                                extra -= delta;
+
+                                assert(weight_sum >= column_weight[j]);
+                                weight_sum -= column_weight[j];
+
+                                if (restart && !finalize)
+                                        break;
+                        }
+
+                        if (finalize)
+                                break;
+
+                        if (!restart)
+                                finalize = true;
+                }
+        }
+
+        /* Second pass: show output */
+        for (i = t->header ? 0 : 1; i < n_rows; i++) {
+                TableData **row;
+
+                if (sorted)
+                        row = t->data + sorted[i];
+                else
+                        row = t->data + i * t->n_columns;
+
+                for (j = 0; j < display_columns; j++) {
+                        _cleanup_free_ char *buffer = NULL;
+                        const char *field;
+                        TableData *d;
+                        size_t l;
+
+                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
+
+                        field = table_data_format(d);
+                        if (!field)
+                                return -ENOMEM;
+
+                        l = utf8_console_width(field);
+                        if (l > width[j]) {
+                                /* Field is wider than allocated space. Let's ellipsize */
+
+                                buffer = ellipsize(field, width[j], d->ellipsize_percent);
+                                if (!buffer)
+                                        return -ENOMEM;
+
+                                field = buffer;
+
+                        } else if (l < width[j]) {
+                                /* Field is shorter than allocated space. Let's align with spaces */
+
+                                buffer = align_string_mem(field, width[j], d->align_percent);
+                                if (!buffer)
+                                        return -ENOMEM;
+
+                                field = buffer;
+                        }
+
+                        if (j > 0)
+                                fputc(' ', f); /* column separator */
+
+                        if (d->color)
+                                fputs(d->color, f);
+
+                        fputs(field, f);
+
+                        if (d->color)
+                                fputs(ansi_normal(), f);
+                }
+
+                fputc('\n', f);
+        }
+
+        return fflush_and_check(f);
+}
+
+int table_format(Table *t, char **ret) {
+        _cleanup_fclose_ FILE *f = NULL;
+        char *buf = NULL;
+        size_t sz = 0;
+        int r;
+
+        f = open_memstream(&buf, &sz);
+        if (!f)
+                return -ENOMEM;
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        r = table_print(t, f);
+        if (r < 0)
+                return r;
+
+        f = safe_fclose(f);
+
+        *ret = buf;
+
+        return 0;
+}
+
+size_t table_get_rows(Table *t) {
+        if (!t)
+                return 0;
+
+        assert(t->n_columns > 0);
+        return t->n_cells / t->n_columns;
+}
+
+size_t table_get_columns(Table *t) {
+        if (!t)
+                return 0;
+
+        assert(t->n_columns > 0);
+        return t->n_columns;
+}
diff --git a/src/shared/format-table.h b/src/shared/format-table.h
new file mode 100644 (file)
index 0000000..6dc2d16
--- /dev/null
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "macro.h"
+
+typedef enum TableDataType {
+        TABLE_EMPTY,
+        TABLE_STRING,
+        TABLE_BOOLEAN,
+        TABLE_TIMESTAMP,
+        TABLE_TIMESPAN,
+        TABLE_SIZE,
+        TABLE_UINT32,
+        _TABLE_DATA_TYPE_MAX,
+        _TABLE_DATA_TYPE_INVALID = -1,
+} TableDataType;
+
+typedef struct Table Table;
+typedef struct TableCell TableCell;
+
+Table *table_new_internal(const char *first_header, ...) _sentinel_;
+#define table_new(...) table_new_internal(__VA_ARGS__, NULL)
+Table *table_new_raw(size_t n_columns);
+Table *table_unref(Table *t);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Table*, table_unref);
+
+int table_add_cell_full(Table *t, TableCell **ret_cell, TableDataType type, const void *data, size_t minimum_width, size_t maximum_width, unsigned weight, unsigned align_percent, unsigned ellipsize_percent);
+static inline int table_add_cell(Table *t, TableCell **ret_cell, TableDataType type, const void *data) {
+        return table_add_cell_full(t, ret_cell, type, data, (size_t) -1, (size_t) -1, (unsigned) -1, (unsigned) -1, (unsigned) -1);
+}
+
+int table_dup_cell(Table *t, TableCell *cell);
+
+int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width);
+int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width);
+int table_set_weight(Table *t, TableCell *cell, unsigned weight);
+int table_set_align_percent(Table *t, TableCell *cell, unsigned percent);
+int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent);
+int table_set_color(Table *t, TableCell *cell, const char *color);
+
+int table_add_many_internal(Table *t, TableDataType first_type, ...);
+#define table_add_many(t, ...) table_add_many_internal(t, __VA_ARGS__, _TABLE_DATA_TYPE_MAX)
+
+void table_set_header(Table *table, bool b);
+void table_set_width(Table *t, size_t width);
+int table_set_display(Table *t, size_t first_column, ...);
+int table_set_sort(Table *t, size_t first_column, ...);
+
+int table_print(Table *t, FILE *f);
+int table_format(Table *t, char **ret);
+
+static inline TableCell* TABLE_HEADER_CELL(size_t i) {
+        return SIZE_TO_PTR(i + 1);
+}
+
+size_t table_get_rows(Table *t);
+size_t table_get_columns(Table *t);
diff --git a/src/shared/journal-importer.c b/src/shared/journal-importer.c
new file mode 100644 (file)
index 0000000..ca203bb
--- /dev/null
@@ -0,0 +1,502 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "io-util.h"
+#include "journal-file.h"
+#include "journal-importer.h"
+#include "journal-util.h"
+#include "parse-util.h"
+#include "string-util.h"
+#include "unaligned.h"
+
+enum {
+        IMPORTER_STATE_LINE = 0,    /* waiting to read, or reading line */
+        IMPORTER_STATE_DATA_START,  /* reading binary data header */
+        IMPORTER_STATE_DATA,        /* reading binary data */
+        IMPORTER_STATE_DATA_FINISH, /* expecting newline */
+        IMPORTER_STATE_EOF,         /* done */
+};
+
+static int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len) {
+        if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1))
+                return log_oom();
+
+        iovw->iovec[iovw->count++] = IOVEC_MAKE(data, len);
+        return 0;
+}
+
+static void iovw_free_contents(struct iovec_wrapper *iovw) {
+        iovw->iovec = mfree(iovw->iovec);
+        iovw->size_bytes = iovw->count = 0;
+}
+
+static void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new) {
+        size_t i;
+
+        for (i = 0; i < iovw->count; i++)
+                iovw->iovec[i].iov_base = (char*) iovw->iovec[i].iov_base - old + new;
+}
+
+size_t iovw_size(struct iovec_wrapper *iovw) {
+        size_t n = 0, i;
+
+        for (i = 0; i < iovw->count; i++)
+                n += iovw->iovec[i].iov_len;
+
+        return n;
+}
+
+void journal_importer_cleanup(JournalImporter *imp) {
+        if (imp->fd >= 0 && !imp->passive_fd) {
+                log_debug("Closing %s (fd=%d)", imp->name ?: "importer", imp->fd);
+                safe_close(imp->fd);
+        }
+
+        free(imp->name);
+        free(imp->buf);
+        iovw_free_contents(&imp->iovw);
+}
+
+static char* realloc_buffer(JournalImporter *imp, size_t size) {
+        char *b, *old = imp->buf;
+
+        b = GREEDY_REALLOC(imp->buf, imp->size, size);
+        if (!b)
+                return NULL;
+
+        iovw_rebase(&imp->iovw, old, imp->buf);
+
+        return b;
+}
+
+static int get_line(JournalImporter *imp, char **line, size_t *size) {
+        ssize_t n;
+        char *c = NULL;
+
+        assert(imp);
+        assert(imp->state == IMPORTER_STATE_LINE);
+        assert(imp->offset <= imp->filled);
+        assert(imp->filled <= imp->size);
+        assert(!imp->buf || imp->size > 0);
+        assert(imp->fd >= 0);
+
+        for (;;) {
+                if (imp->buf) {
+                        size_t start = MAX(imp->scanned, imp->offset);
+
+                        c = memchr(imp->buf + start, '\n',
+                                   imp->filled - start);
+                        if (c != NULL)
+                                break;
+                }
+
+                imp->scanned = imp->filled;
+                if (imp->scanned >= DATA_SIZE_MAX) {
+                        log_error("Entry is bigger than %u bytes.", DATA_SIZE_MAX);
+                        return -E2BIG;
+                }
+
+                if (imp->passive_fd)
+                        /* we have to wait for some data to come to us */
+                        return -EAGAIN;
+
+                /* We know that imp->filled is at most DATA_SIZE_MAX, so if
+                   we reallocate it, we'll increase the size at least a bit. */
+                assert_cc(DATA_SIZE_MAX < ENTRY_SIZE_MAX);
+                if (imp->size - imp->filled < LINE_CHUNK &&
+                    !realloc_buffer(imp, MIN(imp->filled + LINE_CHUNK, ENTRY_SIZE_MAX)))
+                                return log_oom();
+
+                assert(imp->buf);
+                assert(imp->size - imp->filled >= LINE_CHUNK ||
+                       imp->size == ENTRY_SIZE_MAX);
+
+                n = read(imp->fd,
+                         imp->buf + imp->filled,
+                         imp->size - imp->filled);
+                if (n < 0) {
+                        if (errno != EAGAIN)
+                                log_error_errno(errno, "read(%d, ..., %zu): %m",
+                                                imp->fd,
+                                                imp->size - imp->filled);
+                        return -errno;
+                } else if (n == 0)
+                        return 0;
+
+                imp->filled += n;
+        }
+
+        *line = imp->buf + imp->offset;
+        *size = c + 1 - imp->buf - imp->offset;
+        imp->offset += *size;
+
+        return 1;
+}
+
+static int fill_fixed_size(JournalImporter *imp, void **data, size_t size) {
+
+        assert(imp);
+        assert(IN_SET(imp->state, IMPORTER_STATE_DATA_START, IMPORTER_STATE_DATA, IMPORTER_STATE_DATA_FINISH));
+        assert(size <= DATA_SIZE_MAX);
+        assert(imp->offset <= imp->filled);
+        assert(imp->filled <= imp->size);
+        assert(imp->buf || imp->size == 0);
+        assert(!imp->buf || imp->size > 0);
+        assert(imp->fd >= 0);
+        assert(data);
+
+        while (imp->filled - imp->offset < size) {
+                int n;
+
+                if (imp->passive_fd)
+                        /* we have to wait for some data to come to us */
+                        return -EAGAIN;
+
+                if (!realloc_buffer(imp, imp->offset + size))
+                        return log_oom();
+
+                n = read(imp->fd, imp->buf + imp->filled,
+                         imp->size - imp->filled);
+                if (n < 0) {
+                        if (errno != EAGAIN)
+                                log_error_errno(errno, "read(%d, ..., %zu): %m", imp->fd,
+                                                imp->size - imp->filled);
+                        return -errno;
+                } else if (n == 0)
+                        return 0;
+
+                imp->filled += n;
+        }
+
+        *data = imp->buf + imp->offset;
+        imp->offset += size;
+
+        return 1;
+}
+
+static int get_data_size(JournalImporter *imp) {
+        int r;
+        void *data;
+
+        assert(imp);
+        assert(imp->state == IMPORTER_STATE_DATA_START);
+        assert(imp->data_size == 0);
+
+        r = fill_fixed_size(imp, &data, sizeof(uint64_t));
+        if (r <= 0)
+                return r;
+
+        imp->data_size = unaligned_read_le64(data);
+        if (imp->data_size > DATA_SIZE_MAX) {
+                log_error("Stream declares field with size %zu > DATA_SIZE_MAX = %u",
+                          imp->data_size, DATA_SIZE_MAX);
+                return -EINVAL;
+        }
+        if (imp->data_size == 0)
+                log_warning("Binary field with zero length");
+
+        return 1;
+}
+
+static int get_data_data(JournalImporter *imp, void **data) {
+        int r;
+
+        assert(imp);
+        assert(data);
+        assert(imp->state == IMPORTER_STATE_DATA);
+
+        r = fill_fixed_size(imp, data, imp->data_size);
+        if (r <= 0)
+                return r;
+
+        return 1;
+}
+
+static int get_data_newline(JournalImporter *imp) {
+        int r;
+        char *data;
+
+        assert(imp);
+        assert(imp->state == IMPORTER_STATE_DATA_FINISH);
+
+        r = fill_fixed_size(imp, (void**) &data, 1);
+        if (r <= 0)
+                return r;
+
+        assert(data);
+        if (*data != '\n') {
+                char buf[4];
+                int l;
+
+                l = cescape_char(*data, buf);
+                log_error("Expected newline, got '%.*s'", l, buf);
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+static int process_special_field(JournalImporter *imp, char *line) {
+        const char *value;
+        char buf[CELLESCAPE_DEFAULT_LENGTH];
+        int r;
+
+        assert(line);
+
+        value = startswith(line, "__CURSOR=");
+        if (value)
+                /* ignore __CURSOR */
+                return 1;
+
+        value = startswith(line, "__REALTIME_TIMESTAMP=");
+        if (value) {
+                uint64_t x;
+
+                r = safe_atou64(value, &x);
+                if (r < 0)
+                        return log_warning_errno(r, "Failed to parse __REALTIME_TIMESTAMP '%s': %m",
+                                                 cellescape(buf, sizeof buf, value));
+                else if (!VALID_REALTIME(x)) {
+                        log_warning("__REALTIME_TIMESTAMP out of range, ignoring: %"PRIu64, x);
+                        return -ERANGE;
+                }
+
+                imp->ts.realtime = x;
+                return 1;
+        }
+
+        value = startswith(line, "__MONOTONIC_TIMESTAMP=");
+        if (value) {
+                uint64_t x;
+
+                r = safe_atou64(value, &x);
+                if (r < 0)
+                        return log_warning_errno(r, "Failed to parse __MONOTONIC_TIMESTAMP '%s': %m",
+                                                 cellescape(buf, sizeof buf, value));
+                else if (!VALID_MONOTONIC(x)) {
+                        log_warning("__MONOTONIC_TIMESTAMP out of range, ignoring: %"PRIu64, x);
+                        return -ERANGE;
+                }
+
+                imp->ts.monotonic = x;
+                return 1;
+        }
+
+        /* Just a single underline, but it needs special treatment too. */
+        value = startswith(line, "_BOOT_ID=");
+        if (value) {
+                r = sd_id128_from_string(value, &imp->boot_id);
+                if (r < 0)
+                        return log_warning_errno(r, "Failed to parse _BOOT_ID '%s': %m",
+                                                 cellescape(buf, sizeof buf, value));
+
+                /* store the field in the usual fashion too */
+                return 0;
+        }
+
+        value = startswith(line, "__");
+        if (value) {
+                log_notice("Unknown dunder line __%s, ignoring.", cellescape(buf, sizeof buf, value));
+                return 1;
+        }
+
+        /* no dunder */
+        return 0;
+}
+
+int journal_importer_process_data(JournalImporter *imp) {
+        int r;
+
+        switch(imp->state) {
+        case IMPORTER_STATE_LINE: {
+                char *line, *sep;
+                size_t n = 0;
+
+                assert(imp->data_size == 0);
+
+                r = get_line(imp, &line, &n);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        imp->state = IMPORTER_STATE_EOF;
+                        return 0;
+                }
+                assert(n > 0);
+                assert(line[n-1] == '\n');
+
+                if (n == 1) {
+                        log_trace("Received empty line, event is ready");
+                        return 1;
+                }
+
+                /* MESSAGE=xxx\n
+                   or
+                   COREDUMP\n
+                   LLLLLLLL0011223344...\n
+                */
+                sep = memchr(line, '=', n);
+                if (sep) {
+                        /* chomp newline */
+                        n--;
+
+                        if (!journal_field_valid(line, sep - line, true)) {
+                                char buf[64], *t;
+
+                                t = strndupa(line, sep - line);
+                                log_debug("Ignoring invalid field: \"%s\"",
+                                          cellescape(buf, sizeof buf, t));
+
+                                return 0;
+                        }
+
+                        line[n] = '\0';
+                        r = process_special_field(imp, line);
+                        if (r != 0)
+                                return r < 0 ? r : 0;
+
+                        r = iovw_put(&imp->iovw, line, n);
+                        if (r < 0)
+                                return r;
+                } else {
+                        /* replace \n with = */
+                        line[n-1] = '=';
+
+                        imp->field_len = n;
+                        imp->state = IMPORTER_STATE_DATA_START;
+
+                        /* we cannot put the field in iovec until we have all data */
+                }
+
+                log_trace("Received: %.*s (%s)", (int) n, line, sep ? "text" : "binary");
+
+                return 0; /* continue */
+        }
+
+        case IMPORTER_STATE_DATA_START:
+                assert(imp->data_size == 0);
+
+                r = get_data_size(imp);
+                // log_debug("get_data_size() -> %d", r);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        imp->state = IMPORTER_STATE_EOF;
+                        return 0;
+                }
+
+                imp->state = imp->data_size > 0 ?
+                        IMPORTER_STATE_DATA : IMPORTER_STATE_DATA_FINISH;
+
+                return 0; /* continue */
+
+        case IMPORTER_STATE_DATA: {
+                void *data;
+                char *field;
+
+                assert(imp->data_size > 0);
+
+                r = get_data_data(imp, &data);
+                // log_debug("get_data_data() -> %d", r);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        imp->state = IMPORTER_STATE_EOF;
+                        return 0;
+                }
+
+                assert(data);
+
+                field = (char*) data - sizeof(uint64_t) - imp->field_len;
+                memmove(field + sizeof(uint64_t), field, imp->field_len);
+
+                r = iovw_put(&imp->iovw, field + sizeof(uint64_t), imp->field_len + imp->data_size);
+                if (r < 0)
+                        return r;
+
+                imp->state = IMPORTER_STATE_DATA_FINISH;
+
+                return 0; /* continue */
+        }
+
+        case IMPORTER_STATE_DATA_FINISH:
+                r = get_data_newline(imp);
+                // log_debug("get_data_newline() -> %d", r);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        imp->state = IMPORTER_STATE_EOF;
+                        return 0;
+                }
+
+                imp->data_size = 0;
+                imp->state = IMPORTER_STATE_LINE;
+
+                return 0; /* continue */
+        default:
+                assert_not_reached("wtf?");
+        }
+}
+
+int journal_importer_push_data(JournalImporter *imp, const char *data, size_t size) {
+        assert(imp);
+        assert(imp->state != IMPORTER_STATE_EOF);
+
+        if (!realloc_buffer(imp, imp->filled + size)) {
+                log_error("Failed to store received data of size %zu "
+                          "(in addition to existing %zu bytes with %zu filled): %s",
+                          size, imp->size, imp->filled, strerror(ENOMEM));
+                return -ENOMEM;
+        }
+
+        memcpy(imp->buf + imp->filled, data, size);
+        imp->filled += size;
+
+        return 0;
+}
+
+void journal_importer_drop_iovw(JournalImporter *imp) {
+        size_t remain, target;
+
+        /* This function drops processed data that along with the iovw that points at it */
+
+        iovw_free_contents(&imp->iovw);
+
+        /* possibly reset buffer position */
+        remain = imp->filled - imp->offset;
+
+        if (remain == 0) /* no brainer */
+                imp->offset = imp->scanned = imp->filled = 0;
+        else if (imp->offset > imp->size - imp->filled &&
+                 imp->offset > remain) {
+                memcpy(imp->buf, imp->buf + imp->offset, remain);
+                imp->offset = imp->scanned = 0;
+                imp->filled = remain;
+        }
+
+        target = imp->size;
+        while (target > 16 * LINE_CHUNK && imp->filled < target / 2)
+                target /= 2;
+        if (target < imp->size) {
+                char *tmp;
+
+                tmp = realloc(imp->buf, target);
+                if (!tmp)
+                        log_warning("Failed to reallocate buffer to (smaller) size %zu",
+                                    target);
+                else {
+                        log_debug("Reallocated buffer from %zu to %zu bytes",
+                                  imp->size, target);
+                        imp->buf = tmp;
+                        imp->size = target;
+                }
+        }
+}
+
+bool journal_importer_eof(const JournalImporter *imp) {
+        return imp->state == IMPORTER_STATE_EOF;
+}
diff --git a/src/shared/journal-importer.h b/src/shared/journal-importer.h
new file mode 100644 (file)
index 0000000..53354b7
--- /dev/null
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <sys/uio.h>
+
+#include "sd-id128.h"
+
+#include "time-util.h"
+
+/* Make sure not to make this smaller than the maximum coredump size.
+ * See JOURNAL_SIZE_MAX in coredump.c */
+#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+#define ENTRY_SIZE_MAX (1024*1024*770u)
+#define DATA_SIZE_MAX (1024*1024*768u)
+#else
+#define ENTRY_SIZE_MAX (1024*1024*13u)
+#define DATA_SIZE_MAX (1024*1024*11u)
+#endif
+#define LINE_CHUNK 8*1024u
+
+struct iovec_wrapper {
+        struct iovec *iovec;
+        size_t size_bytes;
+        size_t count;
+};
+
+size_t iovw_size(struct iovec_wrapper *iovw);
+
+typedef struct JournalImporter {
+        int fd;
+        bool passive_fd;
+        char *name;
+
+        char *buf;
+        size_t size;       /* total size of the buffer */
+        size_t offset;     /* offset to the beginning of live data in the buffer */
+        size_t scanned;    /* number of bytes since the beginning of data without a newline */
+        size_t filled;     /* total number of bytes in the buffer */
+
+        size_t field_len;  /* used for binary fields: the field name length */
+        size_t data_size;  /* and the size of the binary data chunk being processed */
+
+        struct iovec_wrapper iovw;
+
+        int state;
+        dual_timestamp ts;
+        sd_id128_t boot_id;
+} JournalImporter;
+
+void journal_importer_cleanup(JournalImporter *);
+int journal_importer_process_data(JournalImporter *);
+int journal_importer_push_data(JournalImporter *, const char *data, size_t size);
+void journal_importer_drop_iovw(JournalImporter *);
+bool journal_importer_eof(const JournalImporter *);
+
+static inline size_t journal_importer_bytes_remaining(const JournalImporter *imp) {
+        return imp->filled;
+}
diff --git a/src/shared/json-internal.h b/src/shared/json-internal.h
new file mode 100644 (file)
index 0000000..bf158bf
--- /dev/null
@@ -0,0 +1,63 @@
+/* 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)
+#define _JSON_VARIANT_MAGIC_MAX ((JsonVariant*) 10)
+
+/* This is only safe as long as we don't define more than 4K magic pointers, i.e. the page size of the simplest
+ * architectures we support. That's because we rely on the fact that malloc() will never allocate from the first memory
+ * page, as it is a faulting page for catching NULL pointer dereferences. */
+assert_cc((uintptr_t) _JSON_VARIANT_MAGIC_MAX < 4096U);
+
+enum { /* JSON tokens */
+        JSON_TOKEN_END,
+        JSON_TOKEN_COLON,
+        JSON_TOKEN_COMMA,
+        JSON_TOKEN_OBJECT_OPEN,
+        JSON_TOKEN_OBJECT_CLOSE,
+        JSON_TOKEN_ARRAY_OPEN,
+        JSON_TOKEN_ARRAY_CLOSE,
+        JSON_TOKEN_STRING,
+        JSON_TOKEN_REAL,
+        JSON_TOKEN_INTEGER,
+        JSON_TOKEN_UNSIGNED,
+        JSON_TOKEN_BOOLEAN,
+        JSON_TOKEN_NULL,
+        _JSON_TOKEN_MAX,
+        _JSON_TOKEN_INVALID = -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/shared/json.c b/src/shared/json.c
new file mode 100644 (file)
index 0000000..eec6ea7
--- /dev/null
@@ -0,0 +1,3385 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "sd-messages.h"
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "float.h"
+#include "hexdecoct.h"
+#include "json-internal.h"
+#include "json.h"
+#include "macro.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "utf8.h"
+
+/* Refuse putting together variants with a larger depth than 4K by default (as a protection against overflowing stacks
+ * if code processes JSON objects recursively. Note that we store the depth in an uint16_t, hence make sure this
+ * remains under 2^16.
+ * The value was 16k, but it was discovered to be too high on llvm/x86-64. See also the issue #10738. */
+#define DEPTH_MAX (4U*1024U)
+assert_cc(DEPTH_MAX <= UINT16_MAX);
+
+typedef struct JsonSource {
+        /* When we parse from a file or similar, encodes the filename, to indicate the source of a json variant */
+        size_t n_ref;
+        unsigned max_line;
+        unsigned max_column;
+        char name[];
+} JsonSource;
+
+/* On x86-64 this whole structure should have a size of 6 * 64 bit = 48 bytes */
+struct JsonVariant {
+        union {
+                /* We either maintain a reference counter for this variant itself, or we are embedded into an
+                 * array/object, in which case only that surrounding object is ref-counted. (If 'embedded' is false,
+                 * see below.) */
+                size_t n_ref;
+
+                /* If this JsonVariant is part of an array/object, then this field points to the surrounding
+                 * JSON_VARIANT_ARRAY/JSON_VARIANT_OBJECT object. (If 'embedded' is true, see below.) */
+                JsonVariant *parent;
+        };
+
+        /* If this was parsed from some file or buffer, this stores where from, as well as the source line/column */
+        JsonSource *source;
+        unsigned line, column;
+
+        JsonVariantType type:5;
+
+        /* A marker whether this variant is embedded into in array/object or not. If true, the 'parent' pointer above
+         * is valid. If false, the 'n_ref' field above is valid instead. */
+        bool is_embedded:1;
+
+        /* In some conditions (for example, if this object is part of an array of strings or objects), we don't store
+         * any data inline, but instead simply reference an external object and act as surrogate of it. In that case
+         * this bool is set, and the external object is referenced through the .reference field below. */
+        bool is_reference:1;
+
+        /* While comparing two arrays, we use this for marking what we already have seen */
+        bool is_marked:1;
+
+        /* The current 'depth' of the JsonVariant, i.e. how many levels of member variants this has */
+        uint16_t depth;
+
+        union {
+                /* For simple types we store the value in-line. */
+                JsonValue value;
+
+                /* For objects and arrays we store the number of elements immediately following */
+                size_t n_elements;
+
+                /* If is_reference as indicated above is set, this is where the reference object is actually stored. */
+                JsonVariant *reference;
+
+                /* Strings are placed immediately after the structure. Note that when this is a JsonVariant embedded
+                 * into an array we might encode strings up to INLINE_STRING_LENGTH characters directly inside the
+                 * element, while longer strings are stored as references. When this object is not embedded into an
+                 * array, but stand-alone we allocate the right size for the whole structure, i.e. the array might be
+                 * much larger than INLINE_STRING_LENGTH.
+                 *
+                 * Note that because we want to allocate arrays of the JsonVariant structure we specify [0] here,
+                 * rather than the prettier []. If we wouldn't, then this char array would have undefined size, and so
+                 * would the union and then the struct this is included in. And of structures with undefined size we
+                 * can't allocate arrays (at least not easily). */
+                char string[0];
+        };
+};
+
+/* Inside string arrays we have a series of JasonVariant structures one after the other. In this case, strings longer
+ * than INLINE_STRING_MAX are stored as references, and all shorter ones inline. (This means — on x86-64 — strings up
+ * to 15 chars are stored within the array elements, and all others in separate allocations) */
+#define INLINE_STRING_MAX (sizeof(JsonVariant) - offsetof(JsonVariant, string) - 1U)
+
+/* Let's make sure this structure isn't increased in size accidentally. This check is only for our most relevant arch
+ * (x86-64). */
+#ifdef __x86_64__
+assert_cc(sizeof(JsonVariant) == 48U);
+assert_cc(INLINE_STRING_MAX == 15U);
+#endif
+
+static JsonSource* json_source_new(const char *name) {
+        JsonSource *s;
+
+        assert(name);
+
+        s = malloc(offsetof(JsonSource, name) + strlen(name) + 1);
+        if (!s)
+                return NULL;
+
+        *s = (JsonSource) {
+                .n_ref = 1,
+        };
+        strcpy(s->name, name);
+
+        return s;
+}
+
+DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(JsonSource, json_source, mfree);
+
+static bool json_source_equal(JsonSource *a, JsonSource *b) {
+        if (a == b)
+                return true;
+
+        if (!a || !b)
+                return false;
+
+        return streq(a->name, b->name);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(JsonSource*, json_source_unref);
+
+/* There are four kind of JsonVariant* pointers:
+ *
+ *    1. NULL
+ *    2. A 'regular' one, i.e. pointing to malloc() memory
+ *    3. A 'magic' one, i.e. one of the special JSON_VARIANT_MAGIC_XYZ values, that encode a few very basic values directly in the pointer.
+ *    4. A 'const string' one, i.e. a pointer to a const string.
+ *
+ * The four kinds of pointers can be discerned like this:
+ *
+ *    Detecting #1 is easy, just compare with NULL. Detecting #3 is similarly easy: all magic pointers are below
+ *    _JSON_VARIANT_MAGIC_MAX (which is pretty low, within the first memory page, which is special on Linux and other
+ *    OSes, as it is a faulting page). In order to discern #2 and #4 we check the lowest bit. If it's off it's #2,
+ *    otherwise #4. This makes use of the fact that malloc() will return "maximum aligned" memory, which definitely
+ *    means the pointer is even. This means we can use the uneven pointers to reference static strings, as long as we
+ *    make sure that all static strings used like this are aligned to 2 (or higher), and that we mask the bit on
+ *    access. The JSON_VARIANT_STRING_CONST() macro encodes strings as JsonVariant* pointers, with the bit set. */
+
+static bool json_variant_is_magic(const JsonVariant *v) {
+        if (!v)
+                return false;
+
+        return v < _JSON_VARIANT_MAGIC_MAX;
+}
+
+static bool json_variant_is_const_string(const JsonVariant *v) {
+
+        if (v < _JSON_VARIANT_MAGIC_MAX)
+                return false;
+
+        /* A proper JsonVariant is aligned to whatever malloc() aligns things too, which is definitely not uneven. We
+         * hence use all uneven pointers as indicators for const strings. */
+
+        return (((uintptr_t) v) & 1) != 0;
+}
+
+static bool json_variant_is_regular(const JsonVariant *v) {
+
+        if (v < _JSON_VARIANT_MAGIC_MAX)
+                return false;
+
+        return (((uintptr_t) v) & 1) == 0;
+}
+
+static JsonVariant *json_variant_dereference(JsonVariant *v) {
+
+        /* Recursively dereference variants that are references to other variants */
+
+        if (!v)
+                return NULL;
+
+        if (!json_variant_is_regular(v))
+                return v;
+
+        if (!v->is_reference)
+                return v;
+
+        return json_variant_dereference(v->reference);
+}
+
+static uint16_t json_variant_depth(JsonVariant *v) {
+
+        v = json_variant_dereference(v);
+        if (!v)
+                return 0;
+
+        if (!json_variant_is_regular(v))
+                return 0;
+
+        return v->depth;
+}
+
+static JsonVariant *json_variant_normalize(JsonVariant *v) {
+
+        /* Converts json variants to their normalized form, i.e. fully dereferenced and wherever possible converted to
+         * the "magic" version if there is one */
+
+        if (!v)
+                return NULL;
+
+        v = json_variant_dereference(v);
+
+        switch (json_variant_type(v)) {
+
+        case JSON_VARIANT_BOOLEAN:
+                return json_variant_boolean(v) ? JSON_VARIANT_MAGIC_TRUE : JSON_VARIANT_MAGIC_FALSE;
+
+        case JSON_VARIANT_NULL:
+                return JSON_VARIANT_MAGIC_NULL;
+
+        case JSON_VARIANT_INTEGER:
+                return json_variant_integer(v) == 0 ? JSON_VARIANT_MAGIC_ZERO_INTEGER : v;
+
+        case JSON_VARIANT_UNSIGNED:
+                return json_variant_unsigned(v) == 0 ? JSON_VARIANT_MAGIC_ZERO_UNSIGNED : v;
+
+        case JSON_VARIANT_REAL:
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+                return json_variant_real(v) == 0.0 ? JSON_VARIANT_MAGIC_ZERO_REAL : v;
+#pragma GCC diagnostic pop
+
+        case JSON_VARIANT_STRING:
+                return isempty(json_variant_string(v)) ? JSON_VARIANT_MAGIC_EMPTY_STRING : v;
+
+        case JSON_VARIANT_ARRAY:
+                return json_variant_elements(v) == 0 ? JSON_VARIANT_MAGIC_EMPTY_ARRAY : v;
+
+        case JSON_VARIANT_OBJECT:
+                return json_variant_elements(v) == 0 ? JSON_VARIANT_MAGIC_EMPTY_OBJECT : v;
+
+        default:
+                return v;
+        }
+}
+
+static JsonVariant *json_variant_conservative_normalize(JsonVariant *v) {
+
+        /* Much like json_variant_normalize(), but won't simplify if the variant has a source/line location attached to
+         * it, in order not to lose context */
+
+        if (!v)
+                return NULL;
+
+        if (!json_variant_is_regular(v))
+                return v;
+
+        if (v->source || v->line > 0 || v->column > 0)
+                return v;
+
+        return json_variant_normalize(v);
+}
+
+static int json_variant_new(JsonVariant **ret, JsonVariantType type, size_t space) {
+        JsonVariant *v;
+
+        assert_return(ret, -EINVAL);
+
+        v = malloc0(offsetof(JsonVariant, value) + space);
+        if (!v)
+                return -ENOMEM;
+
+        v->n_ref = 1;
+        v->type = type;
+
+        *ret = v;
+        return 0;
+}
+
+int json_variant_new_integer(JsonVariant **ret, intmax_t i) {
+        JsonVariant *v;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        if (i == 0) {
+                *ret = JSON_VARIANT_MAGIC_ZERO_INTEGER;
+                return 0;
+        }
+
+        r = json_variant_new(&v, JSON_VARIANT_INTEGER, sizeof(i));
+        if (r < 0)
+                return r;
+
+        v->value.integer = i;
+        *ret = v;
+
+        return 0;
+}
+
+int json_variant_new_unsigned(JsonVariant **ret, uintmax_t u) {
+        JsonVariant *v;
+        int r;
+
+        assert_return(ret, -EINVAL);
+        if (u == 0) {
+                *ret = JSON_VARIANT_MAGIC_ZERO_UNSIGNED;
+                return 0;
+        }
+
+        r = json_variant_new(&v, JSON_VARIANT_UNSIGNED, sizeof(u));
+        if (r < 0)
+                return r;
+
+        v->value.unsig = u;
+        *ret = v;
+
+        return 0;
+}
+
+int json_variant_new_real(JsonVariant **ret, long double d) {
+        JsonVariant *v;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+        if (d == 0.0) {
+#pragma GCC diagnostic pop
+                *ret = JSON_VARIANT_MAGIC_ZERO_REAL;
+                return 0;
+        }
+
+        r = json_variant_new(&v, JSON_VARIANT_REAL, sizeof(d));
+        if (r < 0)
+                return r;
+
+        v->value.real = d;
+        *ret = v;
+
+        return 0;
+}
+
+int json_variant_new_boolean(JsonVariant **ret, bool b) {
+        assert_return(ret, -EINVAL);
+
+        if (b)
+                *ret = JSON_VARIANT_MAGIC_TRUE;
+        else
+                *ret = JSON_VARIANT_MAGIC_FALSE;
+
+        return 0;
+}
+
+int json_variant_new_null(JsonVariant **ret) {
+        assert_return(ret, -EINVAL);
+
+        *ret = JSON_VARIANT_MAGIC_NULL;
+        return 0;
+}
+
+int json_variant_new_stringn(JsonVariant **ret, const char *s, size_t n) {
+        JsonVariant *v;
+        int r;
+
+        assert_return(ret, -EINVAL);
+        if (!s) {
+                assert_return(n == 0, -EINVAL);
+                return json_variant_new_null(ret);
+        }
+        if (n == 0) {
+                *ret = JSON_VARIANT_MAGIC_EMPTY_STRING;
+                return 0;
+        }
+
+        r = json_variant_new(&v, JSON_VARIANT_STRING, n + 1);
+        if (r < 0)
+                return r;
+
+        memcpy(v->string, s, n);
+        v->string[n] = 0;
+
+        *ret = v;
+        return 0;
+}
+
+static void json_variant_set(JsonVariant *a, JsonVariant *b) {
+        assert(a);
+
+        b = json_variant_dereference(b);
+        if (!b) {
+                a->type = JSON_VARIANT_NULL;
+                return;
+        }
+
+        a->type = json_variant_type(b);
+        switch (a->type) {
+
+        case JSON_VARIANT_INTEGER:
+                a->value.integer = json_variant_integer(b);
+                break;
+
+        case JSON_VARIANT_UNSIGNED:
+                a->value.unsig = json_variant_unsigned(b);
+                break;
+
+        case JSON_VARIANT_REAL:
+                a->value.real = json_variant_real(b);
+                break;
+
+        case JSON_VARIANT_BOOLEAN:
+                a->value.boolean = json_variant_boolean(b);
+                break;
+
+        case JSON_VARIANT_STRING: {
+                const char *s;
+
+                assert_se(s = json_variant_string(b));
+
+                /* Short strings we can store inline */
+                if (strnlen(s, INLINE_STRING_MAX+1) <= INLINE_STRING_MAX) {
+                        strcpy(a->string, s);
+                        break;
+                }
+
+                /* For longer strings, use a reference… */
+                _fallthrough_;
+        }
+
+        case JSON_VARIANT_ARRAY:
+        case JSON_VARIANT_OBJECT:
+                a->is_reference = true;
+                a->reference = json_variant_ref(json_variant_conservative_normalize(b));
+                break;
+
+        case JSON_VARIANT_NULL:
+                break;
+
+        default:
+                assert_not_reached("Unexpected variant type");
+        }
+}
+
+static void json_variant_copy_source(JsonVariant *v, JsonVariant *from) {
+        assert(v);
+        assert(from);
+
+        if (!json_variant_is_regular(from))
+                return;
+
+        v->line = from->line;
+        v->column = from->column;
+        v->source = json_source_ref(from->source);
+}
+
+int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+        assert_return(ret, -EINVAL);
+        if (n == 0) {
+                *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY;
+                return 0;
+        }
+        assert_return(array, -EINVAL);
+
+        v = new(JsonVariant, n + 1);
+        if (!v)
+                return -ENOMEM;
+
+        *v = (JsonVariant) {
+                .n_ref = 1,
+                .type = JSON_VARIANT_ARRAY,
+        };
+
+        for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
+                JsonVariant *w = v + 1 + v->n_elements,
+                        *c = array[v->n_elements];
+                uint16_t d;
+
+                d = json_variant_depth(c);
+                if (d >= DEPTH_MAX) /* Refuse too deep nesting */
+                        return -ELNRNG;
+                if (d >= v->depth)
+                        v->depth = d + 1;
+
+                *w = (JsonVariant) {
+                        .is_embedded = true,
+                        .parent = v,
+                };
+
+                json_variant_set(w, c);
+                json_variant_copy_source(w, c);
+        }
+
+        *ret = TAKE_PTR(v);
+        return 0;
+}
+
+int json_variant_new_array_bytes(JsonVariant **ret, const void *p, size_t n) {
+        JsonVariant *v;
+        size_t i;
+
+        assert_return(ret, -EINVAL);
+        if (n == 0) {
+                *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY;
+                return 0;
+        }
+        assert_return(p, -EINVAL);
+
+        v = new(JsonVariant, n + 1);
+        if (!v)
+                return -ENOMEM;
+
+        *v = (JsonVariant) {
+                .n_ref = 1,
+                .type = JSON_VARIANT_ARRAY,
+                .n_elements = n,
+                .depth = 1,
+        };
+
+        for (i = 0; i < n; i++) {
+                JsonVariant *w = v + 1 + i;
+
+                *w = (JsonVariant) {
+                        .is_embedded = true,
+                        .parent = v,
+                        .type = JSON_VARIANT_UNSIGNED,
+                        .value.unsig = ((const uint8_t*) p)[i],
+                };
+        }
+
+        *ret = v;
+        return 0;
+}
+
+int json_variant_new_array_strv(JsonVariant **ret, char **l) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        size_t n;
+        int r;
+
+        assert(ret);
+
+        n = strv_length(l);
+        if (n == 0) {
+                *ret = JSON_VARIANT_MAGIC_EMPTY_ARRAY;
+                return 0;
+        }
+
+        v = new(JsonVariant, n + 1);
+        if (!v)
+                return -ENOMEM;
+
+        *v = (JsonVariant) {
+                .n_ref = 1,
+                .type = JSON_VARIANT_ARRAY,
+                .depth = 1,
+        };
+
+        for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
+                JsonVariant *w = v + 1 + v->n_elements;
+                size_t k;
+
+                *w = (JsonVariant) {
+                        .is_embedded = true,
+                        .parent = v,
+                        .type = JSON_VARIANT_STRING,
+                };
+
+                k = strlen(l[v->n_elements]);
+
+                if (k > INLINE_STRING_MAX) {
+                        /* If string is too long, store it as reference. */
+
+                        r = json_variant_new_stringn(&w->reference, l[v->n_elements], k);
+                        if (r < 0)
+                                return r;
+
+                        w->is_reference = true;
+                } else
+                        memcpy(w->string, l[v->n_elements], k+1);
+        }
+
+        *ret = TAKE_PTR(v);
+        return 0;
+}
+
+int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+        assert_return(ret, -EINVAL);
+        if (n == 0) {
+                *ret = JSON_VARIANT_MAGIC_EMPTY_OBJECT;
+                return 0;
+        }
+        assert_return(array, -EINVAL);
+        assert_return(n % 2 == 0, -EINVAL);
+
+        v = new(JsonVariant, n + 1);
+        if (!v)
+                return -ENOMEM;
+
+        *v = (JsonVariant) {
+                .n_ref = 1,
+                .type = JSON_VARIANT_OBJECT,
+        };
+
+        for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
+                JsonVariant *w = v + 1 + v->n_elements,
+                        *c = array[v->n_elements];
+                uint16_t d;
+
+                if ((v->n_elements & 1) == 0 &&
+                    !json_variant_is_string(c))
+                        return -EINVAL; /* Every second one needs to be a string, as it is the key name */
+
+                d = json_variant_depth(c);
+                if (d >= DEPTH_MAX) /* Refuse too deep nesting */
+                        return -ELNRNG;
+                if (d >= v->depth)
+                        v->depth = d + 1;
+
+                *w = (JsonVariant) {
+                        .is_embedded = true,
+                        .parent = v,
+                };
+
+                json_variant_set(w, c);
+                json_variant_copy_source(w, c);
+        }
+
+        *ret = TAKE_PTR(v);
+        return 0;
+}
+
+static void json_variant_free_inner(JsonVariant *v) {
+        assert(v);
+
+        if (!json_variant_is_regular(v))
+                return;
+
+        json_source_unref(v->source);
+
+        if (v->is_reference) {
+                json_variant_unref(v->reference);
+                return;
+        }
+
+        if (IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT)) {
+                size_t i;
+
+                for (i = 0; i < v->n_elements; i++)
+                        json_variant_free_inner(v + 1 + i);
+        }
+}
+
+JsonVariant *json_variant_ref(JsonVariant *v) {
+        if (!v)
+                return NULL;
+        if (!json_variant_is_regular(v))
+                return v;
+
+        if (v->is_embedded)
+                json_variant_ref(v->parent); /* ref the compounding variant instead */
+        else {
+                assert(v->n_ref > 0);
+                v->n_ref++;
+        }
+
+        return v;
+}
+
+JsonVariant *json_variant_unref(JsonVariant *v) {
+        if (!v)
+                return NULL;
+        if (!json_variant_is_regular(v))
+                return NULL;
+
+        if (v->is_embedded)
+                json_variant_unref(v->parent);
+        else {
+                assert(v->n_ref > 0);
+                v->n_ref--;
+
+                if (v->n_ref == 0) {
+                        json_variant_free_inner(v);
+                        free(v);
+                }
+        }
+
+        return NULL;
+}
+
+void json_variant_unref_many(JsonVariant **array, size_t n) {
+        size_t i;
+
+        assert(array || n == 0);
+
+        for (i = 0; i < n; i++)
+                json_variant_unref(array[i]);
+}
+
+const char *json_variant_string(JsonVariant *v) {
+        if (!v)
+                return NULL;
+        if (v == JSON_VARIANT_MAGIC_EMPTY_STRING)
+                return "";
+        if (json_variant_is_magic(v))
+                goto mismatch;
+        if (json_variant_is_const_string(v)) {
+                uintptr_t p = (uintptr_t) v;
+
+                assert((p & 1) != 0);
+                return (const char*) (p ^ 1U);
+        }
+
+        if (v->is_reference)
+                return json_variant_string(v->reference);
+        if (v->type != JSON_VARIANT_STRING)
+                goto mismatch;
+
+        return v->string;
+
+mismatch:
+        log_debug("Non-string JSON variant requested as string, returning NULL.");
+        return NULL;
+}
+
+bool json_variant_boolean(JsonVariant *v) {
+        if (!v)
+                goto mismatch;
+        if (v == JSON_VARIANT_MAGIC_TRUE)
+                return true;
+        if (v == JSON_VARIANT_MAGIC_FALSE)
+                return false;
+        if (!json_variant_is_regular(v))
+                goto mismatch;
+        if (v->type != JSON_VARIANT_BOOLEAN)
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_boolean(v->reference);
+
+        return v->value.boolean;
+
+mismatch:
+        log_debug("Non-boolean JSON variant requested as boolean, returning false.");
+        return false;
+}
+
+intmax_t json_variant_integer(JsonVariant *v) {
+        if (!v)
+                goto mismatch;
+        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER ||
+            v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
+            v == JSON_VARIANT_MAGIC_ZERO_REAL)
+                return 0;
+        if (!json_variant_is_regular(v))
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_integer(v->reference);
+
+        switch (v->type) {
+
+        case JSON_VARIANT_INTEGER:
+                return v->value.integer;
+
+        case JSON_VARIANT_UNSIGNED:
+                if (v->value.unsig <= INTMAX_MAX)
+                        return (intmax_t) v->value.unsig;
+
+                log_debug("Unsigned integer %ju requested as signed integer and out of range, returning 0.", v->value.unsig);
+                return 0;
+
+        case JSON_VARIANT_REAL: {
+                intmax_t converted;
+
+                converted = (intmax_t) v->value.real;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+                if ((long double) converted == v->value.real)
+#pragma GCC diagnostic pop
+                        return converted;
+
+                log_debug("Real %Lg requested as integer, and cannot be converted losslessly, returning 0.", v->value.real);
+                return 0;
+        }
+
+        default:
+                break;
+        }
+
+mismatch:
+        log_debug("Non-integer JSON variant requested as integer, returning 0.");
+        return 0;
+}
+
+uintmax_t json_variant_unsigned(JsonVariant *v) {
+        if (!v)
+                goto mismatch;
+        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER ||
+            v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
+            v == JSON_VARIANT_MAGIC_ZERO_REAL)
+                return 0;
+        if (!json_variant_is_regular(v))
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_integer(v->reference);
+
+        switch (v->type) {
+
+        case JSON_VARIANT_INTEGER:
+                if (v->value.integer >= 0)
+                        return (uintmax_t) v->value.integer;
+
+                log_debug("Signed integer %ju requested as unsigned integer and out of range, returning 0.", v->value.integer);
+                return 0;
+
+        case JSON_VARIANT_UNSIGNED:
+                return v->value.unsig;
+
+        case JSON_VARIANT_REAL: {
+                uintmax_t converted;
+
+                converted = (uintmax_t) v->value.real;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+                if ((long double) converted == v->value.real)
+#pragma GCC diagnostic pop
+                        return converted;
+
+                log_debug("Real %Lg requested as unsigned integer, and cannot be converted losslessly, returning 0.", v->value.real);
+                return 0;
+        }
+
+        default:
+                break;
+        }
+
+mismatch:
+        log_debug("Non-integer JSON variant requested as unsigned, returning 0.");
+        return 0;
+}
+
+long double json_variant_real(JsonVariant *v) {
+        if (!v)
+                return 0.0;
+        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER ||
+            v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
+            v == JSON_VARIANT_MAGIC_ZERO_REAL)
+                return 0.0;
+        if (!json_variant_is_regular(v))
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_real(v->reference);
+
+        switch (v->type) {
+
+        case JSON_VARIANT_REAL:
+                return v->value.real;
+
+        case JSON_VARIANT_INTEGER: {
+                long double converted;
+
+                converted = (long double) v->value.integer;
+
+                if ((intmax_t) converted == v->value.integer)
+                        return converted;
+
+                log_debug("Signed integer %ji requested as real, and cannot be converted losslessly, returning 0.", v->value.integer);
+                return 0.0;
+        }
+
+        case JSON_VARIANT_UNSIGNED: {
+                long double converted;
+
+                converted = (long double) v->value.unsig;
+
+                if ((uintmax_t) converted == v->value.unsig)
+                        return converted;
+
+                log_debug("Unsigned integer %ju requested as real, and cannot be converted losslessly, returning 0.", v->value.unsig);
+                return 0.0;
+        }
+
+        default:
+                break;
+        }
+
+mismatch:
+        log_debug("Non-integer JSON variant requested as integer, returning 0.");
+        return 0.0;
+}
+
+bool json_variant_is_negative(JsonVariant *v) {
+        if (!v)
+                goto mismatch;
+        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER ||
+            v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED ||
+            v == JSON_VARIANT_MAGIC_ZERO_REAL)
+                return false;
+        if (!json_variant_is_regular(v))
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_is_negative(v->reference);
+
+        /* This function is useful as checking whether numbers are negative is pretty complex since we have three types
+         * of numbers. And some JSON code (OCI for example) uses negative numbers to mark "not defined" numeric
+         * values. */
+
+        switch (v->type) {
+
+        case JSON_VARIANT_REAL:
+                return v->value.real < 0;
+
+        case JSON_VARIANT_INTEGER:
+                return v->value.integer < 0;
+
+        case JSON_VARIANT_UNSIGNED:
+                return false;
+
+        default:
+                break;
+        }
+
+mismatch:
+        log_debug("Non-integer JSON variant tested for negativity, returning false.");
+        return false;
+}
+
+JsonVariantType json_variant_type(JsonVariant *v) {
+
+        if (!v)
+                return _JSON_VARIANT_TYPE_INVALID;
+
+        if (json_variant_is_const_string(v))
+                return JSON_VARIANT_STRING;
+
+        if (v == JSON_VARIANT_MAGIC_TRUE || v == JSON_VARIANT_MAGIC_FALSE)
+                return JSON_VARIANT_BOOLEAN;
+
+        if (v == JSON_VARIANT_MAGIC_NULL)
+                return JSON_VARIANT_NULL;
+
+        if (v == JSON_VARIANT_MAGIC_ZERO_INTEGER)
+                return JSON_VARIANT_INTEGER;
+
+        if (v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED)
+                return JSON_VARIANT_UNSIGNED;
+
+        if (v == JSON_VARIANT_MAGIC_ZERO_REAL)
+                return JSON_VARIANT_REAL;
+
+        if (v == JSON_VARIANT_MAGIC_EMPTY_STRING)
+                return JSON_VARIANT_STRING;
+
+        if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY)
+                return JSON_VARIANT_ARRAY;
+
+        if (v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
+                return JSON_VARIANT_OBJECT;
+
+        return v->type;
+}
+
+bool json_variant_has_type(JsonVariant *v, JsonVariantType type) {
+        JsonVariantType rt;
+
+        v = json_variant_dereference(v);
+
+        rt = json_variant_type(v);
+        if (rt == type)
+                return true;
+
+        /* If it's a const string, then it only can be a string, and if it is not, it's not */
+        if (json_variant_is_const_string(v))
+                return false;
+
+        /* All three magic zeroes qualify as integer, unsigned and as real */
+        if ((v == JSON_VARIANT_MAGIC_ZERO_INTEGER || v == JSON_VARIANT_MAGIC_ZERO_UNSIGNED || v == JSON_VARIANT_MAGIC_ZERO_REAL) &&
+            IN_SET(type, JSON_VARIANT_INTEGER, JSON_VARIANT_UNSIGNED, JSON_VARIANT_REAL, JSON_VARIANT_NUMBER))
+                return true;
+
+        /* All other magic variant types are only equal to themselves */
+        if (json_variant_is_magic(v))
+                return false;
+
+        /* Handle the "number" pseudo type */
+        if (type == JSON_VARIANT_NUMBER)
+                return IN_SET(rt, JSON_VARIANT_INTEGER, JSON_VARIANT_UNSIGNED, JSON_VARIANT_REAL);
+
+        /* Integer conversions are OK in many cases */
+        if (rt == JSON_VARIANT_INTEGER && type == JSON_VARIANT_UNSIGNED)
+                return v->value.integer >= 0;
+        if (rt == JSON_VARIANT_UNSIGNED && type == JSON_VARIANT_INTEGER)
+                return v->value.unsig <= INTMAX_MAX;
+
+        /* Any integer that can be converted lossley to a real and back may also be considered a real */
+        if (rt == JSON_VARIANT_INTEGER && type == JSON_VARIANT_REAL)
+                return (intmax_t) (long double) v->value.integer == v->value.integer;
+        if (rt == JSON_VARIANT_UNSIGNED && type == JSON_VARIANT_REAL)
+                return (uintmax_t) (long double) v->value.unsig == v->value.unsig;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+        /* Any real that can be converted losslessly to an integer and back may also be considered an integer */
+        if (rt == JSON_VARIANT_REAL && type == JSON_VARIANT_INTEGER)
+                return (long double) (intmax_t) v->value.real == v->value.real;
+        if (rt == JSON_VARIANT_REAL && type == JSON_VARIANT_UNSIGNED)
+                return (long double) (uintmax_t) v->value.real == v->value.real;
+#pragma GCC diagnostic pop
+
+        return false;
+}
+
+size_t json_variant_elements(JsonVariant *v) {
+        if (!v)
+                return 0;
+        if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY ||
+            v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
+                return 0;
+        if (!json_variant_is_regular(v))
+                goto mismatch;
+        if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_elements(v->reference);
+
+        return v->n_elements;
+
+mismatch:
+        log_debug("Number of elements in non-array/non-object JSON variant requested, returning 0.");
+        return 0;
+}
+
+JsonVariant *json_variant_by_index(JsonVariant *v, size_t idx) {
+        if (!v)
+                return NULL;
+        if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY ||
+            v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
+                return NULL;
+        if (!json_variant_is_regular(v))
+                goto mismatch;
+        if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_by_index(v->reference, idx);
+        if (idx >= v->n_elements)
+                return NULL;
+
+        return json_variant_conservative_normalize(v + 1 + idx);
+
+mismatch:
+        log_debug("Element in non-array/non-object JSON variant requested by index, returning NULL.");
+        return NULL;
+}
+
+JsonVariant *json_variant_by_key_full(JsonVariant *v, const char *key, JsonVariant **ret_key) {
+        size_t i;
+
+        if (!v)
+                goto not_found;
+        if (!key)
+                goto not_found;
+        if (v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
+                goto not_found;
+        if (!json_variant_is_regular(v))
+                goto mismatch;
+        if (v->type != JSON_VARIANT_OBJECT)
+                goto mismatch;
+        if (v->is_reference)
+                return json_variant_by_key(v->reference, key);
+
+        for (i = 0; i < v->n_elements; i += 2) {
+                JsonVariant *p;
+
+                p = json_variant_dereference(v + 1 + i);
+
+                if (!json_variant_has_type(p, JSON_VARIANT_STRING))
+                        continue;
+
+                if (streq(json_variant_string(p), key)) {
+
+                        if (ret_key)
+                                *ret_key = json_variant_conservative_normalize(v + 1 + i);
+
+                        return json_variant_conservative_normalize(v + 1 + i + 1);
+                }
+        }
+
+not_found:
+        if (ret_key)
+                *ret_key = NULL;
+
+        return NULL;
+
+mismatch:
+        log_debug("Element in non-object JSON variant requested by key, returning NULL.");
+        if (ret_key)
+                *ret_key = NULL;
+
+        return NULL;
+}
+
+JsonVariant *json_variant_by_key(JsonVariant *v, const char *key) {
+        return json_variant_by_key_full(v, key, NULL);
+}
+
+bool json_variant_equal(JsonVariant *a, JsonVariant *b) {
+        JsonVariantType t;
+
+        a = json_variant_normalize(a);
+        b = json_variant_normalize(b);
+
+        if (a == b)
+                return true;
+
+        t = json_variant_type(a);
+        if (!json_variant_has_type(b, t))
+                return false;
+
+        switch (t) {
+
+        case JSON_VARIANT_STRING:
+                return streq(json_variant_string(a), json_variant_string(b));
+
+        case JSON_VARIANT_INTEGER:
+                return json_variant_integer(a) == json_variant_integer(b);
+
+        case JSON_VARIANT_UNSIGNED:
+                return json_variant_unsigned(a) == json_variant_unsigned(b);
+
+        case JSON_VARIANT_REAL:
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+                return json_variant_real(a) == json_variant_real(b);
+#pragma GCC diagnostic pop
+
+        case JSON_VARIANT_BOOLEAN:
+                return json_variant_boolean(a) == json_variant_boolean(b);
+
+        case JSON_VARIANT_NULL:
+                return true;
+
+        case JSON_VARIANT_ARRAY: {
+                size_t i, n;
+
+                n = json_variant_elements(a);
+                if (n != json_variant_elements(b))
+                        return false;
+
+                for (i = 0; i < n; i++) {
+                        if (!json_variant_equal(json_variant_by_index(a, i), json_variant_by_index(b, i)))
+                                return false;
+                }
+
+                return true;
+        }
+
+        case JSON_VARIANT_OBJECT: {
+                size_t i, n;
+
+                n = json_variant_elements(a);
+                if (n != json_variant_elements(b))
+                        return false;
+
+                /* Iterate through all keys in 'a' */
+                for (i = 0; i < n; i += 2) {
+                        bool found = false;
+                        size_t j;
+
+                        /* Match them against all keys in 'b' */
+                        for (j = 0; j < n; j += 2) {
+                                JsonVariant *key_b;
+
+                                key_b = json_variant_by_index(b, j);
+
+                                /* During the first iteration unmark everything */
+                                if (i == 0)
+                                        key_b->is_marked = false;
+                                else if (key_b->is_marked) /* In later iterations if we already marked something, don't bother with it again */
+                                        continue;
+
+                                if (found)
+                                        continue;
+
+                                if (json_variant_equal(json_variant_by_index(a, i), key_b) &&
+                                    json_variant_equal(json_variant_by_index(a, i+1), json_variant_by_index(b, j+1))) {
+                                        /* Key and values match! */
+                                        key_b->is_marked = found = true;
+
+                                        /* In the first iteration we continue the inner loop since we want to mark
+                                         * everything, otherwise exit the loop quickly after we found what we were
+                                         * looking for. */
+                                        if (i != 0)
+                                                break;
+                                }
+                        }
+
+                        if (!found)
+                                return false;
+                }
+
+                return true;
+        }
+
+        default:
+                assert_not_reached("Unknown variant type.");
+        }
+}
+
+int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column) {
+        assert_return(v, -EINVAL);
+
+        if (ret_source)
+                *ret_source = json_variant_is_regular(v) && v->source ? v->source->name : NULL;
+
+        if (ret_line)
+                *ret_line = json_variant_is_regular(v) ? v->line : 0;
+
+        if (ret_column)
+                *ret_column = json_variant_is_regular(v) ? v->column : 0;
+
+        return 0;
+}
+
+static int print_source(FILE *f, JsonVariant *v, unsigned flags, bool whitespace) {
+        size_t w, k;
+
+        if (!FLAGS_SET(flags, JSON_FORMAT_SOURCE|JSON_FORMAT_PRETTY))
+                return 0;
+
+        if (!json_variant_is_regular(v))
+                return 0;
+
+        if (!v->source && v->line == 0 && v->column == 0)
+                return 0;
+
+        /* The max width we need to format the line numbers for this source file */
+        w = (v->source && v->source->max_line > 0) ?
+                DECIMAL_STR_WIDTH(v->source->max_line) :
+                DECIMAL_STR_MAX(unsigned)-1;
+        k = (v->source && v->source->max_column > 0) ?
+                DECIMAL_STR_WIDTH(v->source->max_column) :
+                DECIMAL_STR_MAX(unsigned) -1;
+
+        if (whitespace) {
+                size_t i, n;
+
+                n = 1 + (v->source ? strlen(v->source->name) : 0) +
+                        ((v->source && (v->line > 0 || v->column > 0)) ? 1 : 0) +
+                        (v->line > 0 ? w : 0) +
+                        (((v->source || v->line > 0) && v->column > 0) ? 1 : 0) +
+                        (v->column > 0 ? k : 0) +
+                        2;
+
+                for (i = 0; i < n; i++)
+                        fputc(' ', f);
+        } else {
+                fputc('[', f);
+
+                if (v->source)
+                        fputs(v->source->name, f);
+                if (v->source && (v->line > 0 || v->column > 0))
+                        fputc(':', f);
+                if (v->line > 0)
+                        fprintf(f, "%*u", (int) w, v->line);
+                if ((v->source || v->line > 0) || v->column > 0)
+                        fputc(':', f);
+                if (v->column > 0)
+                        fprintf(f, "%*u", (int) k, v->column);
+
+                fputc(']', f);
+                fputc(' ', f);
+        }
+
+        return 0;
+}
+
+static int json_format(FILE *f, JsonVariant *v, unsigned flags, const char *prefix) {
+        int r;
+
+        assert(f);
+        assert(v);
+
+        switch (json_variant_type(v)) {
+
+        case JSON_VARIANT_REAL: {
+                locale_t loc;
+
+                loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
+                if (loc == (locale_t) 0)
+                        return -errno;
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_HIGHLIGHT_BLUE, f);
+
+                fprintf(f, "%.*Le", DECIMAL_DIG, json_variant_real(v));
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_NORMAL, f);
+
+                freelocale(loc);
+                break;
+        }
+
+        case JSON_VARIANT_INTEGER:
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_HIGHLIGHT_BLUE, f);
+
+                fprintf(f, "%" PRIdMAX, json_variant_integer(v));
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_NORMAL, f);
+                break;
+
+        case JSON_VARIANT_UNSIGNED:
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_HIGHLIGHT_BLUE, f);
+
+                fprintf(f, "%" PRIuMAX, json_variant_unsigned(v));
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_NORMAL, f);
+                break;
+
+        case JSON_VARIANT_BOOLEAN:
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_HIGHLIGHT, f);
+
+                if (json_variant_boolean(v))
+                        fputs("true", f);
+                else
+                        fputs("false", f);
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_NORMAL, f);
+
+                break;
+
+        case JSON_VARIANT_NULL:
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_HIGHLIGHT, f);
+
+                fputs("null", f);
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_NORMAL, f);
+                break;
+
+        case JSON_VARIANT_STRING: {
+                const char *q;
+
+                fputc('"', f);
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_GREEN, f);
+
+                for (q = json_variant_string(v); *q; q++) {
+
+                        switch (*q) {
+
+                        case '"':
+                                fputs("\\\"", f);
+                                break;
+
+                        case '\\':
+                                fputs("\\\\", f);
+                                break;
+
+                        case '/':
+                                fputs("\\/", f);
+                                break;
+
+                        case '\b':
+                                fputs("\\b", f);
+                                break;
+
+                        case '\f':
+                                fputs("\\f", f);
+                                break;
+
+                        case '\n':
+                                fputs("\\n", f);
+                                break;
+
+                        case '\r':
+                                fputs("\\r", f);
+                                break;
+
+                        case '\t':
+                                fputs("\\t", f);
+                                break;
+
+                        default:
+                                if ((signed char) *q >= 0 && *q < ' ')
+                                        fprintf(f, "\\u%04x", *q);
+                                else
+                                        fputc(*q, f);
+                                break;
+                        }
+                }
+
+                if (flags & JSON_FORMAT_COLOR)
+                        fputs(ANSI_NORMAL, f);
+
+                fputc('"', f);
+                break;
+        }
+
+        case JSON_VARIANT_ARRAY: {
+                size_t i, n;
+
+                n = json_variant_elements(v);
+
+                if (n == 0)
+                        fputs("[]", f);
+                else {
+                        _cleanup_free_ char *joined = NULL;
+                        const char *prefix2;
+
+                        if (flags & JSON_FORMAT_PRETTY) {
+                                joined = strjoin(strempty(prefix), "\t");
+                                if (!joined)
+                                        return -ENOMEM;
+
+                                prefix2 = joined;
+                                fputs("[\n", f);
+                        } else {
+                                prefix2 = strempty(prefix);
+                                fputc('[', f);
+                        }
+
+                        for (i = 0; i < n; i++) {
+                                JsonVariant *e;
+
+                                assert_se(e = json_variant_by_index(v, i));
+
+                                if (i > 0) {
+                                        if (flags & JSON_FORMAT_PRETTY)
+                                                fputs(",\n", f);
+                                        else
+                                                fputc(',', f);
+                                }
+
+                                if (flags & JSON_FORMAT_PRETTY) {
+                                        print_source(f, e, flags, false);
+                                        fputs(prefix2, f);
+                                }
+
+                                r = json_format(f, e, flags, prefix2);
+                                if (r < 0)
+                                        return r;
+                        }
+
+                        if (flags & JSON_FORMAT_PRETTY) {
+                                fputc('\n', f);
+                                print_source(f, v, flags, true);
+                                fputs(strempty(prefix), f);
+                        }
+
+                        fputc(']', f);
+                }
+                break;
+        }
+
+        case JSON_VARIANT_OBJECT: {
+                size_t i, n;
+
+                n = json_variant_elements(v);
+
+                if (n == 0)
+                        fputs("{}", f);
+                else {
+                        _cleanup_free_ char *joined = NULL;
+                        const char *prefix2;
+
+                        if (flags & JSON_FORMAT_PRETTY) {
+                                joined = strjoin(strempty(prefix), "\t");
+                                if (!joined)
+                                        return -ENOMEM;
+
+                                prefix2 = joined;
+                                fputs("{\n", f);
+                        } else {
+                                prefix2 = strempty(prefix);
+                                fputc('{', f);
+                        }
+
+                        for (i = 0; i < n; i += 2) {
+                                JsonVariant *e;
+
+                                e = json_variant_by_index(v, i);
+
+                                if (i > 0) {
+                                        if (flags & JSON_FORMAT_PRETTY)
+                                                fputs(",\n", f);
+                                        else
+                                                fputc(',', f);
+                                }
+
+                                if (flags & JSON_FORMAT_PRETTY) {
+                                        print_source(f, e, flags, false);
+                                        fputs(prefix2, f);
+                                }
+
+                                r = json_format(f, e, flags, prefix2);
+                                if (r < 0)
+                                        return r;
+
+                                fputs(flags & JSON_FORMAT_PRETTY ? " : " : ":", f);
+
+                                r = json_format(f, json_variant_by_index(v, i+1), flags, prefix2);
+                                if (r < 0)
+                                        return r;
+                        }
+
+                        if (flags & JSON_FORMAT_PRETTY) {
+                                fputc('\n', f);
+                                print_source(f, v, flags, true);
+                                fputs(strempty(prefix), f);
+                        }
+
+                        fputc('}', f);
+                }
+                break;
+        }
+
+        default:
+                assert_not_reached("Unexpected variant type.");
+        }
+
+        return 0;
+}
+
+int json_variant_format(JsonVariant *v, unsigned flags, char **ret) {
+        _cleanup_free_ char *s = NULL;
+        size_t sz = 0;
+        int r;
+
+        assert_return(v, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        {
+                _cleanup_fclose_ FILE *f = NULL;
+
+                f = open_memstream(&s, &sz);
+                if (!f)
+                        return -ENOMEM;
+
+                (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+                json_variant_dump(v, flags, f, NULL);
+
+                r = fflush_and_check(f);
+        }
+        if (r < 0)
+                return r;
+
+        assert(s);
+        *ret = TAKE_PTR(s);
+
+        return (int) sz;
+}
+
+void json_variant_dump(JsonVariant *v, unsigned flags, FILE *f, const char *prefix) {
+        if (!v)
+                return;
+
+        if (!f)
+                f = stdout;
+
+        print_source(f, v, flags, false);
+
+        if (flags & JSON_FORMAT_SSE)
+                fputs("data: ", f);
+        if (flags & JSON_FORMAT_SEQ)
+                fputc('\x1e', f); /* ASCII Record Separator */
+
+        json_format(f, v, flags, prefix);
+
+        if (flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_SEQ|JSON_FORMAT_SSE|JSON_FORMAT_NEWLINE))
+                fputc('\n', f);
+        if (flags & JSON_FORMAT_SSE)
+                fputc('\n', f); /* In case of SSE add a second newline */
+}
+
+static int json_variant_copy(JsonVariant **nv, JsonVariant *v) {
+        JsonVariantType t;
+        JsonVariant *c;
+        JsonValue value;
+        const void *source;
+        size_t k;
+
+        assert(nv);
+        assert(v);
+
+        /* Let's copy the simple types literally, and the larger types by references */
+        t = json_variant_type(v);
+        switch (t) {
+        case JSON_VARIANT_INTEGER:
+                k = sizeof(intmax_t);
+                value.integer = json_variant_integer(v);
+                source = &value;
+                break;
+
+        case JSON_VARIANT_UNSIGNED:
+                k = sizeof(uintmax_t);
+                value.unsig = json_variant_unsigned(v);
+                source = &value;
+                break;
+
+        case JSON_VARIANT_REAL:
+                k = sizeof(long double);
+                value.real = json_variant_real(v);
+                source = &value;
+                break;
+
+        case JSON_VARIANT_BOOLEAN:
+                k = sizeof(bool);
+                value.boolean = json_variant_boolean(v);
+                source = &value;
+                break;
+
+        case JSON_VARIANT_NULL:
+                k = 0;
+                source = NULL;
+                break;
+
+        case JSON_VARIANT_STRING:
+                source = json_variant_string(v);
+                k = strnlen(source, INLINE_STRING_MAX + 1);
+                if (k <= INLINE_STRING_MAX) {
+                        k ++;
+                        break;
+                }
+
+                _fallthrough_;
+
+        default:
+                /* Everything else copy by reference */
+
+                c = malloc0(offsetof(JsonVariant, reference) + sizeof(JsonVariant*));
+                if (!c)
+                        return -ENOMEM;
+
+                c->n_ref = 1;
+                c->type = t;
+                c->is_reference = true;
+                c->reference = json_variant_ref(json_variant_normalize(v));
+
+                *nv = c;
+                return 0;
+        }
+
+        c = malloc0(offsetof(JsonVariant, value) + k);
+        if (!c)
+                return -ENOMEM;
+
+        c->n_ref = 1;
+        c->type = t;
+
+        memcpy_safe(&c->value, source, k);
+
+        *nv = c;
+        return 0;
+}
+
+static bool json_single_ref(JsonVariant *v) {
+
+        /* Checks whether the caller is the single owner of the object, i.e. can get away with changing it */
+
+        if (!json_variant_is_regular(v))
+                return false;
+
+        if (v->is_embedded)
+                return json_single_ref(v->parent);
+
+        assert(v->n_ref > 0);
+        return v->n_ref == 1;
+}
+
+static int json_variant_set_source(JsonVariant **v, JsonSource *source, unsigned line, unsigned column) {
+        JsonVariant *w;
+        int r;
+
+        assert(v);
+
+        /* Patch in source and line/column number. Tries to do this in-place if the caller is the sole referencer of
+         * the object. If not, allocates a new object, possibly a surrogate for the original one */
+
+        if (!*v)
+                return 0;
+
+        if (source && line > source->max_line)
+                source->max_line = line;
+        if (source && column > source->max_column)
+                source->max_column = column;
+
+        if (!json_variant_is_regular(*v)) {
+
+                if (!source && line == 0 && column == 0)
+                        return 0;
+
+        } else {
+                if (json_source_equal((*v)->source, source) &&
+                    (*v)->line == line &&
+                    (*v)->column == column)
+                        return 0;
+
+                if (json_single_ref(*v)) { /* Sole reference? */
+                        json_source_unref((*v)->source);
+                        (*v)->source = json_source_ref(source);
+                        (*v)->line = line;
+                        (*v)->column = column;
+                        return 1;
+                }
+        }
+
+        r = json_variant_copy(&w, *v);
+        if (r < 0)
+                return r;
+
+        assert(json_variant_is_regular(w));
+        assert(!w->is_embedded);
+        assert(w->n_ref == 1);
+        assert(!w->source);
+
+        w->source = json_source_ref(source);
+        w->line = line;
+        w->column = column;
+
+        json_variant_unref(*v);
+        *v = w;
+
+        return 1;
+}
+
+static void inc_lines_columns(unsigned *line, unsigned *column, const char *s, size_t n) {
+        assert(line);
+        assert(column);
+        assert(s || n == 0);
+
+        while (n > 0) {
+
+                if (*s == '\n') {
+                        (*line)++;
+                        *column = 1;
+                } else if ((signed char) *s >= 0 && *s < 127) /* Process ASCII chars quickly */
+                        (*column)++;
+                else {
+                        int w;
+
+                        w = utf8_encoded_valid_unichar(s);
+                        if (w < 0) /* count invalid unichars as normal characters */
+                                w = 1;
+                        else if ((size_t) w > n) /* never read more than the specified number of characters */
+                                w = (int) n;
+
+                        (*column)++;
+
+                        s += w;
+                        n -= w;
+                        continue;
+                }
+
+                s++;
+                n--;
+        }
+}
+
+static int unhex_ucs2(const char *c, uint16_t *ret) {
+        int aa, bb, cc, dd;
+        uint16_t x;
+
+        assert(c);
+        assert(ret);
+
+        aa = unhexchar(c[0]);
+        if (aa < 0)
+                return -EINVAL;
+
+        bb = unhexchar(c[1]);
+        if (bb < 0)
+                return -EINVAL;
+
+        cc = unhexchar(c[2]);
+        if (cc < 0)
+                return -EINVAL;
+
+        dd = unhexchar(c[3]);
+        if (dd < 0)
+                return -EINVAL;
+
+        x =     ((uint16_t) aa << 12) |
+                ((uint16_t) bb << 8) |
+                ((uint16_t) cc << 4) |
+                ((uint16_t) dd);
+
+        if (x <= 0)
+                return -EINVAL;
+
+        *ret = x;
+
+        return 0;
+}
+
+static int json_parse_string(const char **p, char **ret) {
+        _cleanup_free_ char *s = NULL;
+        size_t n = 0, allocated = 0;
+        const char *c;
+
+        assert(p);
+        assert(*p);
+        assert(ret);
+
+        c = *p;
+
+        if (*c != '"')
+                return -EINVAL;
+
+        c++;
+
+        for (;;) {
+                int len;
+
+                /* Check for EOF */
+                if (*c == 0)
+                        return -EINVAL;
+
+                /* Check for control characters 0x00..0x1f */
+                if (*c > 0 && *c < ' ')
+                        return -EINVAL;
+
+                /* Check for control character 0x7f */
+                if (*c == 0x7f)
+                        return -EINVAL;
+
+                if (*c == '"') {
+                        if (!s) {
+                                s = strdup("");
+                                if (!s)
+                                        return -ENOMEM;
+                        } else
+                                s[n] = 0;
+
+                        *p = c + 1;
+
+                        *ret = TAKE_PTR(s);
+                        return JSON_TOKEN_STRING;
+                }
+
+                if (*c == '\\') {
+                        char ch = 0;
+                        c++;
+
+                        if (*c == 0)
+                                return -EINVAL;
+
+                        if (IN_SET(*c, '"', '\\', '/'))
+                                ch = *c;
+                        else if (*c == 'b')
+                                ch = '\b';
+                        else if (*c == 'f')
+                                ch = '\f';
+                        else if (*c == 'n')
+                                ch = '\n';
+                        else if (*c == 'r')
+                                ch = '\r';
+                        else if (*c == 't')
+                                ch = '\t';
+                        else if (*c == 'u') {
+                                char16_t x;
+                                int r;
+
+                                r = unhex_ucs2(c + 1, &x);
+                                if (r < 0)
+                                        return r;
+
+                                c += 5;
+
+                                if (!GREEDY_REALLOC(s, allocated, n + 5))
+                                        return -ENOMEM;
+
+                                if (!utf16_is_surrogate(x))
+                                        n += utf8_encode_unichar(s + n, (char32_t) x);
+                                else if (utf16_is_trailing_surrogate(x))
+                                        return -EINVAL;
+                                else {
+                                        char16_t y;
+
+                                        if (c[0] != '\\' || c[1] != 'u')
+                                                return -EINVAL;
+
+                                        r = unhex_ucs2(c + 2, &y);
+                                        if (r < 0)
+                                                return r;
+
+                                        c += 6;
+
+                                        if (!utf16_is_trailing_surrogate(y))
+                                                return -EINVAL;
+
+                                        n += utf8_encode_unichar(s + n, utf16_surrogate_pair_to_unichar(x, y));
+                                }
+
+                                continue;
+                        } else
+                                return -EINVAL;
+
+                        if (!GREEDY_REALLOC(s, allocated, n + 2))
+                                return -ENOMEM;
+
+                        s[n++] = ch;
+                        c ++;
+                        continue;
+                }
+
+                len = utf8_encoded_valid_unichar(c);
+                if (len < 0)
+                        return len;
+
+                if (!GREEDY_REALLOC(s, allocated, n + len + 1))
+                        return -ENOMEM;
+
+                memcpy(s + n, c, len);
+                n += len;
+                c += len;
+        }
+}
+
+static int json_parse_number(const char **p, JsonValue *ret) {
+        bool negative = false, exponent_negative = false, is_real = false;
+        long double x = 0.0, y = 0.0, exponent = 0.0, shift = 1.0;
+        intmax_t i = 0;
+        uintmax_t u = 0;
+        const char *c;
+
+        assert(p);
+        assert(*p);
+        assert(ret);
+
+        c = *p;
+
+        if (*c == '-') {
+                negative = true;
+                c++;
+        }
+
+        if (*c == '0')
+                c++;
+        else {
+                if (!strchr("123456789", *c) || *c == 0)
+                        return -EINVAL;
+
+                do {
+                        if (!is_real) {
+                                if (negative) {
+
+                                        if (i < INTMAX_MIN / 10) /* overflow */
+                                                is_real = true;
+                                        else {
+                                                intmax_t t = 10 * i;
+
+                                                if (t < INTMAX_MIN + (*c - '0')) /* overflow */
+                                                        is_real = true;
+                                                else
+                                                        i = t - (*c - '0');
+                                        }
+                                } else {
+                                        if (u > UINTMAX_MAX / 10) /* overflow */
+                                                is_real = true;
+                                        else {
+                                                uintmax_t t = 10 * u;
+
+                                                if (t > UINTMAX_MAX - (*c - '0')) /* overflow */
+                                                        is_real = true;
+                                                else
+                                                        u = t + (*c - '0');
+                                        }
+                                }
+                        }
+
+                        x = 10.0 * x + (*c - '0');
+
+                        c++;
+                } while (strchr("0123456789", *c) && *c != 0);
+        }
+
+        if (*c == '.') {
+                is_real = true;
+                c++;
+
+                if (!strchr("0123456789", *c) || *c == 0)
+                        return -EINVAL;
+
+                do {
+                        y = 10.0 * y + (*c - '0');
+                        shift = 10.0 * shift;
+                        c++;
+                } while (strchr("0123456789", *c) && *c != 0);
+        }
+
+        if (IN_SET(*c, 'e', 'E')) {
+                is_real = true;
+                c++;
+
+                if (*c == '-') {
+                        exponent_negative = true;
+                        c++;
+                } else if (*c == '+')
+                        c++;
+
+                if (!strchr("0123456789", *c) || *c == 0)
+                        return -EINVAL;
+
+                do {
+                        exponent = 10.0 * exponent + (*c - '0');
+                        c++;
+                } while (strchr("0123456789", *c) && *c != 0);
+        }
+
+        *p = c;
+
+        if (is_real) {
+                ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * exp10l((exponent_negative ? -1.0 : 1.0) * exponent);
+                return JSON_TOKEN_REAL;
+        } else if (negative) {
+                ret->integer = i;
+                return JSON_TOKEN_INTEGER;
+        } else  {
+                ret->unsig = u;
+                return JSON_TOKEN_UNSIGNED;
+        }
+}
+
+int json_tokenize(
+                const char **p,
+                char **ret_string,
+                JsonValue *ret_value,
+                unsigned *ret_line,   /* 'ret_line' returns the line at the beginning of this token */
+                unsigned *ret_column,
+                void **state,
+                unsigned *line,       /* 'line' is used as a line state, it always reflect the line we are at after the token was read */
+                unsigned *column) {
+
+        unsigned start_line, start_column;
+        const char *start, *c;
+        size_t n;
+        int t, r;
+
+        enum {
+                STATE_NULL,
+                STATE_VALUE,
+                STATE_VALUE_POST,
+        };
+
+        assert(p);
+        assert(*p);
+        assert(ret_string);
+        assert(ret_value);
+        assert(ret_line);
+        assert(ret_column);
+        assert(line);
+        assert(column);
+        assert(state);
+
+        t = PTR_TO_INT(*state);
+        if (t == STATE_NULL) {
+                *line = 1;
+                *column = 1;
+                t = STATE_VALUE;
+        }
+
+        /* Skip over the whitespace */
+        n = strspn(*p, WHITESPACE);
+        inc_lines_columns(line, column, *p, n);
+        c = *p + n;
+
+        /* Remember where we started processing this token */
+        start = c;
+        start_line = *line;
+        start_column = *column;
+
+        if (*c == 0) {
+                *ret_string = NULL;
+                *ret_value = JSON_VALUE_NULL;
+                r = JSON_TOKEN_END;
+                goto finish;
+        }
+
+        switch (t) {
+
+        case STATE_VALUE:
+
+                if (*c == '{') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE);
+                        r = JSON_TOKEN_OBJECT_OPEN;
+                        goto null_return;
+
+                } else if (*c == '}') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        r = JSON_TOKEN_OBJECT_CLOSE;
+                        goto null_return;
+
+                } else if (*c == '[') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE);
+                        r = JSON_TOKEN_ARRAY_OPEN;
+                        goto null_return;
+
+                } else if (*c == ']') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        r = JSON_TOKEN_ARRAY_CLOSE;
+                        goto null_return;
+
+                } else if (*c == '"') {
+
+                        r = json_parse_string(&c, ret_string);
+                        if (r < 0)
+                                return r;
+
+                        *ret_value = JSON_VALUE_NULL;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        goto finish;
+
+                } else if (strchr("-0123456789", *c)) {
+
+                        r = json_parse_number(&c, ret_value);
+                        if (r < 0)
+                                return r;
+
+                        *ret_string = NULL;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        goto finish;
+
+                } else if (startswith(c, "true")) {
+                        *ret_string = NULL;
+                        ret_value->boolean = true;
+                        c += 4;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        r = JSON_TOKEN_BOOLEAN;
+                        goto finish;
+
+                } else if (startswith(c, "false")) {
+                        *ret_string = NULL;
+                        ret_value->boolean = false;
+                        c += 5;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        r = JSON_TOKEN_BOOLEAN;
+                        goto finish;
+
+                } else if (startswith(c, "null")) {
+                        *ret_string = NULL;
+                        *ret_value = JSON_VALUE_NULL;
+                        c += 4;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        r = JSON_TOKEN_NULL;
+                        goto finish;
+
+                }
+
+                return -EINVAL;
+
+        case STATE_VALUE_POST:
+
+                if (*c == ':') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE);
+                        r = JSON_TOKEN_COLON;
+                        goto null_return;
+
+                } else if (*c == ',') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE);
+                        r = JSON_TOKEN_COMMA;
+                        goto null_return;
+
+                } else if (*c == '}') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        r = JSON_TOKEN_OBJECT_CLOSE;
+                        goto null_return;
+
+                } else if (*c == ']') {
+                        c++;
+                        *state = INT_TO_PTR(STATE_VALUE_POST);
+                        r = JSON_TOKEN_ARRAY_CLOSE;
+                        goto null_return;
+                }
+
+                return -EINVAL;
+
+        default:
+                assert_not_reached("Unexpected tokenizer state");
+        }
+
+null_return:
+        *ret_string = NULL;
+        *ret_value = JSON_VALUE_NULL;
+
+finish:
+        inc_lines_columns(line, column, start, c - start);
+        *p = c;
+
+        *ret_line = start_line;
+        *ret_column = start_column;
+
+        return r;
+}
+
+typedef enum JsonExpect {
+        /* The following values are used by json_parse() */
+        EXPECT_TOPLEVEL,
+        EXPECT_END,
+        EXPECT_OBJECT_FIRST_KEY,
+        EXPECT_OBJECT_NEXT_KEY,
+        EXPECT_OBJECT_COLON,
+        EXPECT_OBJECT_VALUE,
+        EXPECT_OBJECT_COMMA,
+        EXPECT_ARRAY_FIRST_ELEMENT,
+        EXPECT_ARRAY_NEXT_ELEMENT,
+        EXPECT_ARRAY_COMMA,
+
+        /* And these are used by json_build() */
+        EXPECT_ARRAY_ELEMENT,
+        EXPECT_OBJECT_KEY,
+} JsonExpect;
+
+typedef struct JsonStack {
+        JsonExpect expect;
+        JsonVariant **elements;
+        size_t n_elements, n_elements_allocated;
+        unsigned line_before;
+        unsigned column_before;
+} JsonStack;
+
+static void json_stack_release(JsonStack *s) {
+        assert(s);
+
+        json_variant_unref_many(s->elements, s->n_elements);
+        s->elements = mfree(s->elements);
+}
+
+static int json_parse_internal(
+                const char **input,
+                JsonSource *source,
+                JsonVariant **ret,
+                unsigned *line,
+                unsigned *column,
+                bool continue_end) {
+
+        size_t n_stack = 1, n_stack_allocated = 0, i;
+        unsigned line_buffer = 0, column_buffer = 0;
+        void *tokenizer_state = NULL;
+        JsonStack *stack = NULL;
+        const char *p;
+        int r;
+
+        assert_return(input, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        p = *input;
+
+        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack))
+                return -ENOMEM;
+
+        stack[0] = (JsonStack) {
+                .expect = EXPECT_TOPLEVEL,
+        };
+
+        if (!line)
+                line = &line_buffer;
+        if (!column)
+                column = &column_buffer;
+
+        for (;;) {
+                _cleanup_free_ char *string = NULL;
+                unsigned line_token, column_token;
+                JsonVariant *add = NULL;
+                JsonStack *current;
+                JsonValue value;
+                int token;
+
+                assert(n_stack > 0);
+                current = stack + n_stack - 1;
+
+                if (continue_end && current->expect == EXPECT_END)
+                        goto done;
+
+                token = json_tokenize(&p, &string, &value, &line_token, &column_token, &tokenizer_state, line, column);
+                if (token < 0) {
+                        r = token;
+                        goto finish;
+                }
+
+                switch (token) {
+
+                case JSON_TOKEN_END:
+                        if (current->expect != EXPECT_END) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        assert(current->n_elements == 1);
+                        assert(n_stack == 1);
+                        goto done;
+
+                case JSON_TOKEN_COLON:
+
+                        if (current->expect != EXPECT_OBJECT_COLON) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        current->expect = EXPECT_OBJECT_VALUE;
+                        break;
+
+                case JSON_TOKEN_COMMA:
+
+                        if (current->expect == EXPECT_OBJECT_COMMA)
+                                current->expect = EXPECT_OBJECT_NEXT_KEY;
+                        else if (current->expect == EXPECT_ARRAY_COMMA)
+                                current->expect = EXPECT_ARRAY_NEXT_ELEMENT;
+                        else {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        break;
+
+                case JSON_TOKEN_OBJECT_OPEN:
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+                        current = stack + n_stack - 1;
+
+                        /* Prepare the expect for when we return from the child */
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        stack[n_stack++] = (JsonStack) {
+                                .expect = EXPECT_OBJECT_FIRST_KEY,
+                                .line_before = line_token,
+                                .column_before = column_token,
+                        };
+
+                        current = stack + n_stack - 1;
+                        break;
+
+                case JSON_TOKEN_OBJECT_CLOSE:
+                        if (!IN_SET(current->expect, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_COMMA)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        assert(n_stack > 1);
+
+                        r = json_variant_new_object(&add, current->elements, current->n_elements);
+                        if (r < 0)
+                                goto finish;
+
+                        line_token = current->line_before;
+                        column_token = current->column_before;
+
+                        json_stack_release(current);
+                        n_stack--, current--;
+
+                        break;
+
+                case JSON_TOKEN_ARRAY_OPEN:
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+                        current = stack + n_stack - 1;
+
+                        /* Prepare the expect for when we return from the child */
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        stack[n_stack++] = (JsonStack) {
+                                .expect = EXPECT_ARRAY_FIRST_ELEMENT,
+                                .line_before = line_token,
+                                .column_before = column_token,
+                        };
+
+                        break;
+
+                case JSON_TOKEN_ARRAY_CLOSE:
+                        if (!IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_COMMA)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        assert(n_stack > 1);
+
+                        r = json_variant_new_array(&add, current->elements, current->n_elements);
+                        if (r < 0)
+                                goto finish;
+
+                        line_token = current->line_before;
+                        column_token = current->column_before;
+
+                        json_stack_release(current);
+                        n_stack--, current--;
+                        break;
+
+                case JSON_TOKEN_STRING:
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_NEXT_KEY, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        r = json_variant_new_string(&add, string);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (IN_SET(current->expect, EXPECT_OBJECT_FIRST_KEY, EXPECT_OBJECT_NEXT_KEY))
+                                current->expect = EXPECT_OBJECT_COLON;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        break;
+
+                case JSON_TOKEN_REAL:
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        r = json_variant_new_real(&add, value.real);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        break;
+
+                case JSON_TOKEN_INTEGER:
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        r = json_variant_new_integer(&add, value.integer);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        break;
+
+                case JSON_TOKEN_UNSIGNED:
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        r = json_variant_new_unsigned(&add, value.unsig);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        break;
+
+                case JSON_TOKEN_BOOLEAN:
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        r = json_variant_new_boolean(&add, value.boolean);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        break;
+
+                case JSON_TOKEN_NULL:
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        r = json_variant_new_null(&add);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_COMMA;
+                        else {
+                                assert(IN_SET(current->expect, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT));
+                                current->expect = EXPECT_ARRAY_COMMA;
+                        }
+
+                        break;
+
+                default:
+                        assert_not_reached("Unexpected token");
+                }
+
+                if (add) {
+                        (void) json_variant_set_source(&add, source, line_token, column_token);
+
+                        if (!GREEDY_REALLOC(current->elements, current->n_elements_allocated, current->n_elements + 1)) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        current->elements[current->n_elements++] = add;
+                }
+        }
+
+done:
+        assert(n_stack == 1);
+        assert(stack[0].n_elements == 1);
+
+        *ret = json_variant_ref(stack[0].elements[0]);
+        *input = p;
+        r = 0;
+
+finish:
+        for (i = 0; i < n_stack; i++)
+                json_stack_release(stack + i);
+
+        free(stack);
+
+        return r;
+}
+
+int json_parse(const char *input, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
+        return json_parse_internal(&input, NULL, ret, ret_line, ret_column, false);
+}
+
+int json_parse_continue(const char **p, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
+        return json_parse_internal(p, NULL, ret, ret_line, ret_column, true);
+}
+
+int json_parse_file(FILE *f, const char *path, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
+        _cleanup_(json_source_unrefp) JsonSource *source = NULL;
+        _cleanup_free_ char *text = NULL;
+        const char *p;
+        int r;
+
+        if (f)
+                r = read_full_stream(f, &text, NULL);
+        else if (path)
+                r = read_full_file(path, &text, NULL);
+        else
+                return -EINVAL;
+        if (r < 0)
+                return r;
+
+        if (path) {
+                source = json_source_new(path);
+                if (!source)
+                        return -ENOMEM;
+        }
+
+        p = text;
+        return json_parse_internal(&p, source, ret, ret_line, ret_column, false);
+}
+
+int json_buildv(JsonVariant **ret, va_list ap) {
+        JsonStack *stack = NULL;
+        size_t n_stack = 1, n_stack_allocated = 0, i;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack))
+                return -ENOMEM;
+
+        stack[0] = (JsonStack) {
+                .expect = EXPECT_TOPLEVEL,
+        };
+
+        for (;;) {
+                _cleanup_(json_variant_unrefp) JsonVariant *add = NULL;
+                JsonStack *current;
+                int command;
+
+                assert(n_stack > 0);
+                current = stack + n_stack - 1;
+
+                if (current->expect == EXPECT_END)
+                        goto done;
+
+                command = va_arg(ap, int);
+
+                switch (command) {
+
+                case _JSON_BUILD_STRING: {
+                        const char *p;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        p = va_arg(ap, const char *);
+
+                        r = json_variant_new_string(&add, p);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
+                case _JSON_BUILD_INTEGER: {
+                        intmax_t j;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        j = va_arg(ap, intmax_t);
+
+                        r = json_variant_new_integer(&add, j);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
+                case _JSON_BUILD_UNSIGNED: {
+                        uintmax_t j;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        j = va_arg(ap, uintmax_t);
+
+                        r = json_variant_new_unsigned(&add, j);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
+                case _JSON_BUILD_REAL: {
+                        long double d;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        d = va_arg(ap, long double);
+
+                        r = json_variant_new_real(&add, d);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
+                case _JSON_BUILD_BOOLEAN: {
+                        bool b;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        b = va_arg(ap, int);
+
+                        r = json_variant_new_boolean(&add, b);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
+                case _JSON_BUILD_NULL:
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        r = json_variant_new_null(&add);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+
+                case _JSON_BUILD_VARIANT:
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        add = va_arg(ap, JsonVariant*);
+                        if (!add)
+                                add = JSON_VARIANT_MAGIC_NULL;
+                        else
+                                json_variant_ref(add);
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+
+                case _JSON_BUILD_LITERAL: {
+                        const char *l;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        l = va_arg(ap, const char *);
+
+                        if (!l)
+                                add = JSON_VARIANT_MAGIC_NULL;
+                        else {
+                                r = json_parse(l, &add, NULL, NULL);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
+                case _JSON_BUILD_ARRAY_BEGIN:
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+                        current = stack + n_stack - 1;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        stack[n_stack++] = (JsonStack) {
+                                .expect = EXPECT_ARRAY_ELEMENT,
+                        };
+
+                        break;
+
+                case _JSON_BUILD_ARRAY_END:
+                        if (current->expect != EXPECT_ARRAY_ELEMENT) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        assert(n_stack > 1);
+
+                        r = json_variant_new_array(&add, current->elements, current->n_elements);
+                        if (r < 0)
+                                goto finish;
+
+                        json_stack_release(current);
+                        n_stack--, current--;
+
+                        break;
+
+                case _JSON_BUILD_STRV: {
+                        char **l;
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        l = va_arg(ap, char **);
+
+                        r = json_variant_new_array_strv(&add, l);
+                        if (r < 0)
+                                goto finish;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        break;
+                }
+
+                case _JSON_BUILD_OBJECT_BEGIN:
+
+                        if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        if (!GREEDY_REALLOC(stack, n_stack_allocated, n_stack+1)) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+                        current = stack + n_stack - 1;
+
+                        if (current->expect == EXPECT_TOPLEVEL)
+                                current->expect = EXPECT_END;
+                        else if (current->expect == EXPECT_OBJECT_VALUE)
+                                current->expect = EXPECT_OBJECT_KEY;
+                        else
+                                assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+                        stack[n_stack++] = (JsonStack) {
+                                .expect = EXPECT_OBJECT_KEY,
+                        };
+
+                        break;
+
+                case _JSON_BUILD_OBJECT_END:
+
+                        if (current->expect != EXPECT_OBJECT_KEY) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        assert(n_stack > 1);
+
+                        r = json_variant_new_object(&add, current->elements, current->n_elements);
+                        if (r < 0)
+                                goto finish;
+
+                        json_stack_release(current);
+                        n_stack--, current--;
+
+                        break;
+
+                case _JSON_BUILD_PAIR: {
+                        const char *n;
+
+                        if (current->expect != EXPECT_OBJECT_KEY) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        n = va_arg(ap, const char *);
+
+                        r = json_variant_new_string(&add, n);
+                        if (r < 0)
+                                goto finish;
+
+                        current->expect = EXPECT_OBJECT_VALUE;
+                        break;
+                }}
+
+                if (add) {
+                        if (!GREEDY_REALLOC(current->elements, current->n_elements_allocated, current->n_elements + 1)) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        current->elements[current->n_elements++] = TAKE_PTR(add);
+                }
+        }
+
+done:
+        assert(n_stack == 1);
+        assert(stack[0].n_elements == 1);
+
+        *ret = json_variant_ref(stack[0].elements[0]);
+        r = 0;
+
+finish:
+        for (i = 0; i < n_stack; i++)
+                json_stack_release(stack + i);
+
+        free(stack);
+
+        va_end(ap);
+
+        return r;
+}
+
+int json_build(JsonVariant **ret, ...) {
+        va_list ap;
+        int r;
+
+        va_start(ap, ret);
+        r = json_buildv(ret, ap);
+        va_end(ap);
+
+        return r;
+}
+
+int json_log_internal(
+                JsonVariant *variant,
+                int level,
+                int error,
+                const char *file,
+                int line,
+                const char *func,
+                const char *format, ...) {
+
+        PROTECT_ERRNO;
+
+        unsigned source_line, source_column;
+        char buffer[LINE_MAX];
+        const char *source;
+        va_list ap;
+        int r;
+
+        if (error < 0)
+                error = -error;
+
+        errno = error;
+
+        va_start(ap, format);
+        (void) vsnprintf(buffer, sizeof buffer, format, ap);
+        va_end(ap);
+
+        if (variant) {
+                r = json_variant_get_source(variant, &source, &source_line, &source_column);
+                if (r < 0)
+                        return r;
+        } else {
+                source = NULL;
+                source_line = 0;
+                source_column = 0;
+        }
+
+        if (source && source_line > 0 && source_column > 0)
+                return log_struct_internal(
+                                LOG_REALM_PLUS_LEVEL(LOG_REALM_SYSTEMD, level),
+                                error,
+                                file, line, func,
+                                "MESSAGE_ID=" SD_MESSAGE_INVALID_CONFIGURATION_STR,
+                                "CONFIG_FILE=%s", source,
+                                "CONFIG_LINE=%u", source_line,
+                                "CONFIG_COLUMN=%u", source_column,
+                                LOG_MESSAGE("%s:%u: %s", source, line, buffer),
+                                NULL);
+        else
+                return log_struct_internal(
+                                LOG_REALM_PLUS_LEVEL(LOG_REALM_SYSTEMD, level),
+                                error,
+                                file, line, func,
+                                "MESSAGE_ID=" SD_MESSAGE_INVALID_CONFIGURATION_STR,
+                                LOG_MESSAGE("%s", buffer),
+                                NULL);
+}
+
+int json_dispatch(JsonVariant *v, const JsonDispatch table[], JsonDispatchCallback bad, JsonDispatchFlags flags, void *userdata) {
+        const JsonDispatch *p;
+        size_t i, n, m;
+        int r, done = 0;
+        bool *found;
+
+        if (!json_variant_is_object(v)) {
+                json_log(v, flags, 0, "JSON variant is not an object.");
+
+                if (flags & JSON_PERMISSIVE)
+                        return 0;
+
+                return -EINVAL;
+        }
+
+        for (p = table, m = 0; p->name; p++)
+                m++;
+
+        found = newa0(bool, m);
+
+        n = json_variant_elements(v);
+        for (i = 0; i < n; i += 2) {
+                JsonVariant *key, *value;
+
+                assert_se(key = json_variant_by_index(v, i));
+                assert_se(value = json_variant_by_index(v, i+1));
+
+                for (p = table; p->name; p++)
+                        if (p->name == (const char*) -1 ||
+                            streq_ptr(json_variant_string(key), p->name))
+                                break;
+
+                if (p->name) { /* Found a matching entry! :-) */
+                        JsonDispatchFlags merged_flags;
+
+                        merged_flags = flags | p->flags;
+
+                        if (p->type != _JSON_VARIANT_TYPE_INVALID &&
+                            !json_variant_has_type(value, p->type)) {
+
+                                json_log(value, merged_flags, 0,
+                                         "Object field '%s' has wrong type %s, expected %s.", json_variant_string(key),
+                                         json_variant_type_to_string(json_variant_type(value)), json_variant_type_to_string(p->type));
+
+                                if (merged_flags & JSON_PERMISSIVE)
+                                        continue;
+
+                                return -EINVAL;
+                        }
+
+                        if (found[p-table]) {
+                                json_log(value, merged_flags, 0, "Duplicate object field '%s'.", json_variant_string(key));
+
+                                if (merged_flags & JSON_PERMISSIVE)
+                                        continue;
+
+                                return -ENOTUNIQ;
+                        }
+
+                        found[p-table] = true;
+
+                        if (p->callback) {
+                                r = p->callback(json_variant_string(key), value, merged_flags, (uint8_t*) userdata + p->offset);
+                                if (r < 0) {
+                                        if (merged_flags & JSON_PERMISSIVE)
+                                                continue;
+
+                                        return r;
+                                }
+                        }
+
+                        done ++;
+
+                } else { /* Didn't find a matching entry! :-( */
+
+                        if (bad) {
+                                r = bad(json_variant_string(key), value, flags, userdata);
+                                if (r < 0) {
+                                        if (flags & JSON_PERMISSIVE)
+                                                continue;
+
+                                        return r;
+                                } else
+                                        done ++;
+
+                        } else  {
+                                json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key));
+
+                                if (flags & JSON_PERMISSIVE)
+                                        continue;
+
+                                return -EADDRNOTAVAIL;
+                        }
+                }
+        }
+
+        for (p = table; p->name; p++) {
+                JsonDispatchFlags merged_flags = p->flags | flags;
+
+                if ((merged_flags & JSON_MANDATORY) && !found[p-table]) {
+                        json_log(v, merged_flags, 0, "Missing object field '%s'.", p->name);
+
+                        if ((merged_flags & JSON_PERMISSIVE))
+                                continue;
+
+                        return -ENXIO;
+                }
+        }
+
+        return done;
+}
+
+int json_dispatch_boolean(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        bool *b = userdata;
+
+        assert(variant);
+        assert(b);
+
+        if (!json_variant_is_boolean(variant)) {
+                json_log(variant, flags, 0, "JSON field '%s' is not a boolean.", strna(name));
+                return -EINVAL;
+        }
+
+        *b = json_variant_boolean(variant);
+        return 0;
+}
+
+int json_dispatch_tristate(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        int *b = userdata;
+
+        assert(variant);
+        assert(b);
+
+        if (!json_variant_is_boolean(variant)) {
+                json_log(variant, flags, 0, "JSON field '%s' is not a boolean.", strna(name));
+                return -EINVAL;
+        }
+
+        *b = json_variant_boolean(variant);
+        return 0;
+}
+
+int json_dispatch_integer(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        intmax_t *i = userdata;
+
+        assert(variant);
+        assert(i);
+
+        if (!json_variant_is_integer(variant)) {
+                json_log(variant, flags, 0, "JSON field '%s' is not an integer.", strna(name));
+                return -EINVAL;
+        }
+
+        *i = json_variant_integer(variant);
+        return 0;
+}
+
+int json_dispatch_unsigned(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        uintmax_t *u = userdata;
+
+        assert(variant);
+        assert(u);
+
+        if (!json_variant_is_unsigned(variant)) {
+                json_log(variant, flags, 0, "JSON field '%s' is not an unsigned integer.", strna(name));
+                return -EINVAL;
+        }
+
+        *u = json_variant_unsigned(variant);
+        return 0;
+}
+
+int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        uint32_t *u = userdata;
+
+        assert(variant);
+        assert(u);
+
+        if (!json_variant_is_unsigned(variant)) {
+                json_log(variant, flags, 0, "JSON field '%s' is not an unsigned integer.", strna(name));
+                return -EINVAL;
+        }
+
+        if (json_variant_unsigned(variant) > UINT32_MAX) {
+                json_log(variant, flags, 0, "JSON field '%s' out of bounds.", strna(name));
+                return -ERANGE;
+        }
+
+        *u = (uint32_t) json_variant_unsigned(variant);
+        return 0;
+}
+
+int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        int32_t *i = userdata;
+
+        assert(variant);
+        assert(i);
+
+        if (!json_variant_is_integer(variant)) {
+                json_log(variant, flags, 0, "JSON field '%s' is not an integer.", strna(name));
+                return -EINVAL;
+        }
+
+        if (json_variant_integer(variant) < INT32_MIN || json_variant_integer(variant) > INT32_MAX) {
+                json_log(variant, flags, 0, "JSON field '%s' out of bounds.", strna(name));
+                return -ERANGE;
+        }
+
+        *i = (int32_t) json_variant_integer(variant);
+        return 0;
+}
+
+int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        char **s = userdata;
+        int r;
+
+        assert(variant);
+        assert(s);
+
+        if (json_variant_is_null(variant)) {
+                *s = mfree(*s);
+                return 0;
+        }
+
+        if (!json_variant_is_string(variant)) {
+                json_log(variant, flags, 0, "JSON field '%s' is not a string.", strna(name));
+                return -EINVAL;
+        }
+
+        r = free_and_strdup(s, json_variant_string(variant));
+        if (r < 0)
+                return json_log(variant, flags, r, "Failed to allocate string: %m");
+
+        return 0;
+}
+
+int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        _cleanup_strv_free_ char **l = NULL;
+        char ***s = userdata;
+        size_t i;
+        int r;
+
+        assert(variant);
+        assert(s);
+
+        if (json_variant_is_null(variant)) {
+                *s = strv_free(*s);
+                return 0;
+        }
+
+        if (!json_variant_is_array(variant)) {
+                json_log(variant, 0, flags, "JSON field '%s' is not an array.", strna(name));
+                return -EINVAL;
+        }
+
+        for (i = 0; i < json_variant_elements(variant); i++) {
+                JsonVariant *e;
+
+                assert_se(e = json_variant_by_index(variant, i));
+
+                if (!json_variant_is_string(e)) {
+                        json_log(e, 0, flags, "JSON array element is not a string.");
+                        return -EINVAL;
+                }
+
+                r = strv_extend(&l, json_variant_string(e));
+                if (r < 0)
+                        return json_log(variant, flags, r, "Failed to append array element: %m");
+        }
+
+        strv_free_and_replace(*s, l);
+        return 0;
+}
+
+int json_dispatch_variant(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+        JsonVariant **p = userdata;
+
+        assert(variant);
+        assert(p);
+
+        json_variant_unref(*p);
+        *p = json_variant_ref(variant);
+
+        return 0;
+}
+
+static const char* const json_variant_type_table[_JSON_VARIANT_TYPE_MAX] = {
+        [JSON_VARIANT_STRING] = "string",
+        [JSON_VARIANT_INTEGER] = "integer",
+        [JSON_VARIANT_UNSIGNED] = "unsigned",
+        [JSON_VARIANT_REAL] = "real",
+        [JSON_VARIANT_NUMBER] = "number",
+        [JSON_VARIANT_BOOLEAN] = "boolean",
+        [JSON_VARIANT_ARRAY] = "array",
+        [JSON_VARIANT_OBJECT] = "object",
+        [JSON_VARIANT_NULL] = "null",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(json_variant_type, JsonVariantType);
diff --git a/src/shared/json.h b/src/shared/json.h
new file mode 100644 (file)
index 0000000..c9482d2
--- /dev/null
@@ -0,0 +1,283 @@
+/* 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);                                     \
+        })
+
+#define JSON_VARIANT_STRING_CONST(x) _JSON_VARIANT_STRING_CONST(UNIQ, (x))
+
+#define _JSON_VARIANT_STRING_CONST(xq, x)                                       \
+        ({                                                              \
+                __attribute__((__aligned__(2))) static const char UNIQ_T(json_string_const, xq)[] = (x); \
+                assert((((uintptr_t) UNIQ_T(json_string_const, xq)) & 1) == 0); \
+                (JsonVariant*) ((uintptr_t) UNIQ_T(json_string_const, xq) + 1); \
+        })
+
+const char *json_variant_type_to_string(JsonVariantType t);
+JsonVariantType json_variant_type_from_string(const char *s);
diff --git a/src/shared/lockfile-util.c b/src/shared/lockfile-util.c
new file mode 100644 (file)
index 0000000..4bae23b
--- /dev/null
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "lockfile-util.h"
+#include "macro.h"
+#include "path-util.h"
+
+int make_lock_file(const char *p, int operation, LockFile *ret) {
+        _cleanup_close_ int fd = -1;
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        /*
+         * We use UNPOSIX locks if they are available. They have nice
+         * semantics, and are mostly compatible with NFS. However,
+         * they are only available on new kernels. When we detect we
+         * are running on an older kernel, then we fall back to good
+         * old BSD locks. They also have nice semantics, but are
+         * slightly problematic on NFS, where they are upgraded to
+         * POSIX locks, even though locally they are orthogonal to
+         * POSIX locks.
+         */
+
+        t = strdup(p);
+        if (!t)
+                return -ENOMEM;
+
+        for (;;) {
+                struct flock fl = {
+                        .l_type = (operation & ~LOCK_NB) == LOCK_EX ? F_WRLCK : F_RDLCK,
+                        .l_whence = SEEK_SET,
+                };
+                struct stat st;
+
+                fd = open(p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600);
+                if (fd < 0)
+                        return -errno;
+
+                r = fcntl(fd, (operation & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &fl);
+                if (r < 0) {
+
+                        /* If the kernel is too old, use good old BSD locks */
+                        if (errno == EINVAL)
+                                r = flock(fd, operation);
+
+                        if (r < 0)
+                                return errno == EAGAIN ? -EBUSY : -errno;
+                }
+
+                /* If we acquired the lock, let's check if the file
+                 * still exists in the file system. If not, then the
+                 * previous exclusive owner removed it and then closed
+                 * it. In such a case our acquired lock is worthless,
+                 * hence try again. */
+
+                r = fstat(fd, &st);
+                if (r < 0)
+                        return -errno;
+                if (st.st_nlink > 0)
+                        break;
+
+                fd = safe_close(fd);
+        }
+
+        ret->path = t;
+        ret->fd = fd;
+        ret->operation = operation;
+
+        fd = -1;
+        t = NULL;
+
+        return r;
+}
+
+int make_lock_file_for(const char *p, int operation, LockFile *ret) {
+        const char *fn;
+        char *t;
+
+        assert(p);
+        assert(ret);
+
+        fn = basename(p);
+        if (!filename_is_valid(fn))
+                return -EINVAL;
+
+        t = newa(char, strlen(p) + 2 + 4 + 1);
+        stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), ".lck");
+
+        return make_lock_file(t, operation, ret);
+}
+
+void release_lock_file(LockFile *f) {
+        int r;
+
+        if (!f)
+                return;
+
+        if (f->path) {
+
+                /* If we are the exclusive owner we can safely delete
+                 * the lock file itself. If we are not the exclusive
+                 * owner, we can try becoming it. */
+
+                if (f->fd >= 0 &&
+                    (f->operation & ~LOCK_NB) == LOCK_SH) {
+                        static const struct flock fl = {
+                                .l_type = F_WRLCK,
+                                .l_whence = SEEK_SET,
+                        };
+
+                        r = fcntl(f->fd, F_OFD_SETLK, &fl);
+                        if (r < 0 && errno == EINVAL)
+                                r = flock(f->fd, LOCK_EX|LOCK_NB);
+
+                        if (r >= 0)
+                                f->operation = LOCK_EX|LOCK_NB;
+                }
+
+                if ((f->operation & ~LOCK_NB) == LOCK_EX)
+                        unlink_noerrno(f->path);
+
+                f->path = mfree(f->path);
+        }
+
+        f->fd = safe_close(f->fd);
+        f->operation = 0;
+}
diff --git a/src/shared/lockfile-util.h b/src/shared/lockfile-util.h
new file mode 100644 (file)
index 0000000..c2abd99
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stddef.h>
+
+#include "macro.h"
+#include "missing.h"
+
+typedef struct LockFile {
+        char *path;
+        int fd;
+        int operation;
+} LockFile;
+
+int make_lock_file(const char *p, int operation, LockFile *ret);
+int make_lock_file_for(const char *p, int operation, LockFile *ret);
+void release_lock_file(LockFile *f);
+
+#define LOCK_FILE_INIT { .fd = -1, .path = NULL }
index c692f012b17c965ec362ccbdc15fb0ae72917378..f7f63a54042cae6561e193d4b03aabbb86862b3b 100644 (file)
@@ -8,24 +8,39 @@ shared_sources = files('''
         apparmor-util.h
         ask-password-api.c
         ask-password-api.h
+        barrier.c
+        barrier.h
         base-filesystem.c
         base-filesystem.h
+        bitmap.c
+        bitmap.h
+        blkid-util.h
         boot-timestamps.c
         boot-timestamps.h
         bootspec.c
         bootspec.h
+        bpf-program.c
+        bpf-program.h
         bus-unit-util.c
         bus-unit-util.h
         bus-util.c
         bus-util.h
+        calendarspec.c
+        calendarspec.h
         cgroup-show.c
         cgroup-show.h
         clean-ipc.c
         clean-ipc.h
+        clock-util.c
+        clock-util.h
         condition.c
         condition.h
         conf-parser.c
         conf-parser.h
+        cpu-set-util.c
+        cpu-set-util.h
+        crypt-util.c
+        crypt-util.h
         dev-setup.c
         dev-setup.h
         dissect-image.c
@@ -37,9 +52,17 @@ shared_sources = files('''
         efivars.c
         efivars.h
         enable-mempool.c
+        exec-util.c
+        exec-util.h
+        exit-status.c
+        exit-status.h
         fdset.c
         fdset.h
+        fileio-label.c
+        fileio-label.h
         firewall-util.h
+        format-table.c
+        format-table.h
         fstab-util.c
         fstab-util.h
         generator.c
@@ -56,8 +79,15 @@ shared_sources = files('''
         install-printf.h
         install.c
         install.h
+        journal-importer.c
+        journal-importer.h
         journal-util.c
         journal-util.h
+        json-internal.h
+        json.c
+        json.h
+        lockfile-util.c
+        lockfile-util.h
         logs-show.c
         logs-show.h
         loop-util.c
@@ -69,19 +99,29 @@ shared_sources = files('''
         module-util.h
         nsflags.c
         nsflags.h
+        os-util.c
+        os-util.h
         output-mode.c
         output-mode.h
         path-lookup.c
         path-lookup.h
         ptyfwd.c
         ptyfwd.h
+        reboot-util.c
+        reboot-util.h
         resolve-util.c
         resolve-util.h
+        rlimit-util.c
+        rlimit-util.h
         seccomp-util.h
+        securebits-util.c
+        securebits-util.h
         serialize.c
         serialize.h
         sleep-config.c
         sleep-config.h
+        socket-protocol-list.c
+        socket-protocol-list.h
         spawn-ask-password-agent.c
         spawn-ask-password-agent.h
         spawn-polkit-agent.c
@@ -99,13 +139,19 @@ shared_sources = files('''
         uid-range.c
         uid-range.h
         utmp-wtmp.h
+        verbs.c
+        verbs.h
         vlan-util.c
         vlan-util.h
         volatile-util.c
         volatile-util.h
         watchdog.c
         watchdog.h
+        web-util.c
+        web-util.h
         wireguard-netlink.h
+        xml.c
+        xml.h
 '''.split())
 
 if get_option('tests') != 'false'
diff --git a/src/shared/os-util.c b/src/shared/os-util.c
new file mode 100644 (file)
index 0000000..82471a4
--- /dev/null
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "macro.h"
+#include "os-util.h"
+#include "strv.h"
+#include "fileio.h"
+#include "string-util.h"
+
+int path_is_os_tree(const char *path) {
+        int r;
+
+        assert(path);
+
+        /* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir
+         * always results in -ENOENT, and we can properly distuingish the case where the whole root doesn't exist from
+         * the case where just the os-release file is missing. */
+        if (laccess(path, F_OK) < 0)
+                return -errno;
+
+        /* We use {/etc|/usr/lib}/os-release as flag file if something is an OS */
+        r = open_os_release(path, NULL, NULL);
+        if (r == -ENOENT) /* We got nothing */
+                return 0;
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+int open_os_release(const char *root, char **ret_path, int *ret_fd) {
+        _cleanup_free_ char *q = NULL;
+        const char *p;
+        int k;
+
+        FOREACH_STRING(p, "/etc/os-release", "/usr/lib/os-release") {
+                k = chase_symlinks(p, root, CHASE_PREFIX_ROOT|(ret_fd ? CHASE_OPEN : 0), (ret_path ? &q : NULL));
+                if (k != -ENOENT)
+                        break;
+        }
+        if (k < 0)
+                return k;
+
+        if (ret_fd) {
+                int real_fd;
+
+                /* Convert the O_PATH fd into a proper, readable one */
+                real_fd = fd_reopen(k, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+                safe_close(k);
+                if (real_fd < 0)
+                        return real_fd;
+
+                *ret_fd = real_fd;
+        }
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(q);
+
+        return 0;
+}
+
+int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
+        _cleanup_free_ char *p = NULL;
+        _cleanup_close_ int fd = -1;
+        FILE *f;
+        int r;
+
+        if (!ret_file)
+                return open_os_release(root, ret_path, NULL);
+
+        r = open_os_release(root, ret_path ? &p : NULL, &fd);
+        if (r < 0)
+                return r;
+
+        f = fdopen(fd, "re");
+        if (!f)
+                return -errno;
+        fd = -1;
+
+        *ret_file = f;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(p);
+
+        return 0;
+}
+
+int parse_os_release(const char *root, ...) {
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *p = NULL;
+        va_list ap;
+        int r;
+
+        r = fopen_os_release(root, &p, &f);
+        if (r < 0)
+                return r;
+
+        va_start(ap, root);
+        r = parse_env_filev(f, p, ap);
+        va_end(ap);
+
+        return r;
+}
+
+int load_os_release_pairs(const char *root, char ***ret) {
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *p = NULL;
+        int r;
+
+        r = fopen_os_release(root, &p, &f);
+        if (r < 0)
+                return r;
+
+        return load_env_file_pairs(f, p, ret);
+}
diff --git a/src/shared/os-util.h b/src/shared/os-util.h
new file mode 100644 (file)
index 0000000..27ec7ac
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdio.h>
+
+int path_is_os_tree(const char *path);
+
+int open_os_release(const char *root, char **ret_path, int *ret_fd);
+int fopen_os_release(const char *root, char **ret_path, FILE **ret_file);
+
+int parse_os_release(const char *root, ...) _sentinel_;
+int load_os_release_pairs(const char *root, char ***ret);
diff --git a/src/shared/reboot-util.c b/src/shared/reboot-util.c
new file mode 100644 (file)
index 0000000..ca40159
--- /dev/null
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "fileio.h"
+#include "log.h"
+#include "raw-reboot.h"
+#include "reboot-util.h"
+#include "string-util.h"
+#include "umask-util.h"
+#include "virt.h"
+
+int update_reboot_parameter_and_warn(const char *parameter) {
+        int r;
+
+        if (isempty(parameter)) {
+                if (unlink("/run/systemd/reboot-param") < 0) {
+                        if (errno == ENOENT)
+                                return 0;
+
+                        return log_warning_errno(errno, "Failed to unlink reboot parameter file: %m");
+                }
+
+                return 0;
+        }
+
+        RUN_WITH_UMASK(0022) {
+                r = write_string_file("/run/systemd/reboot-param", parameter,
+                                      WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
+                if (r < 0)
+                        return log_warning_errno(r, "Failed to write reboot parameter file: %m");
+        }
+
+        return 0;
+}
+
+int reboot_with_parameter(RebootFlags flags) {
+        int r;
+
+        /* Reboots the system with a parameter that is read from /run/systemd/reboot-param. Returns 0 if REBOOT_DRY_RUN
+         * was set and the actual reboot operation was hence skipped. If REBOOT_FALLBACK is set and the reboot with
+         * parameter doesn't work out a fallback to classic reboot() is attempted. If REBOOT_FALLBACK is not set, 0 is
+         * returned instead, which should be considered indication for the caller to fall back to reboot() on its own,
+         * or somehow else deal with this. If REBOOT_LOG is specified will log about what it is going to do, as well as
+         * all errors. */
+
+        if (detect_container() == 0) {
+                _cleanup_free_ char *parameter = NULL;
+
+                r = read_one_line_file("/run/systemd/reboot-param", &parameter);
+                if (r < 0 && r != -ENOENT)
+                        log_full_errno(flags & REBOOT_LOG ? LOG_WARNING : LOG_DEBUG, r,
+                                       "Failed to read reboot parameter file, ignoring: %m");
+
+                if (!isempty(parameter)) {
+
+                        log_full(flags & REBOOT_LOG ? LOG_INFO : LOG_DEBUG,
+                                 "Rebooting with argument '%s'.", parameter);
+
+                        if (flags & REBOOT_DRY_RUN)
+                                return 0;
+
+                        (void) raw_reboot(LINUX_REBOOT_CMD_RESTART2, parameter);
+
+                        log_full_errno(flags & REBOOT_LOG ? LOG_WARNING : LOG_DEBUG, errno,
+                                       "Failed to reboot with parameter, retrying without: %m");
+                }
+        }
+
+        if (!(flags & REBOOT_FALLBACK))
+                return 0;
+
+        log_full(flags & REBOOT_LOG ? LOG_INFO : LOG_DEBUG, "Rebooting.");
+
+        if (flags & REBOOT_DRY_RUN)
+                return 0;
+
+        (void) reboot(RB_AUTOBOOT);
+
+        return log_full_errno(flags & REBOOT_LOG ? LOG_ERR : LOG_DEBUG, errno, "Failed to reboot: %m");
+}
diff --git a/src/shared/reboot-util.h b/src/shared/reboot-util.h
new file mode 100644 (file)
index 0000000..d459333
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+int update_reboot_parameter_and_warn(const char *parameter);
+
+typedef enum RebootFlags {
+        REBOOT_LOG      = 1 << 0, /* log about what we are going to do and all errors */
+        REBOOT_DRY_RUN  = 1 << 1, /* return 0 right before actually doing the reboot */
+        REBOOT_FALLBACK = 1 << 2, /* fallback to plain reboot() if argument-based reboot doesn't work, isn't configured or doesn't apply otherwise */
+} RebootFlags;
+
+int reboot_with_parameter(RebootFlags flags);
diff --git a/src/shared/rlimit-util.c b/src/shared/rlimit-util.c
new file mode 100644 (file)
index 0000000..c133f84
--- /dev/null
@@ -0,0 +1,391 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <sys/resource.h>
+
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "fd-util.h"
+#include "format-util.h"
+#include "macro.h"
+#include "missing.h"
+#include "rlimit-util.h"
+#include "string-table.h"
+#include "time-util.h"
+
+int setrlimit_closest(int resource, const struct rlimit *rlim) {
+        struct rlimit highest, fixed;
+
+        assert(rlim);
+
+        if (setrlimit(resource, rlim) >= 0)
+                return 0;
+
+        if (errno != EPERM)
+                return -errno;
+
+        /* So we failed to set the desired setrlimit, then let's try
+         * to get as close as we can */
+        if (getrlimit(resource, &highest) < 0)
+                return -errno;
+
+        /* If the hard limit is unbounded anyway, then the EPERM had other reasons, let's propagate the original EPERM
+         * then */
+        if (highest.rlim_max == RLIM_INFINITY)
+                return -EPERM;
+
+        fixed = (struct rlimit) {
+                .rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max),
+                .rlim_max = MIN(rlim->rlim_max, highest.rlim_max),
+        };
+
+        /* Shortcut things if we wouldn't change anything. */
+        if (fixed.rlim_cur == highest.rlim_cur &&
+            fixed.rlim_max == highest.rlim_max)
+                return 0;
+
+        if (setrlimit(resource, &fixed) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int setrlimit_closest_all(const struct rlimit *const *rlim, int *which_failed) {
+        int i, r;
+
+        assert(rlim);
+
+        /* On failure returns the limit's index that failed in *which_failed, but only if non-NULL */
+
+        for (i = 0; i < _RLIMIT_MAX; i++) {
+                if (!rlim[i])
+                        continue;
+
+                r = setrlimit_closest(i, rlim[i]);
+                if (r < 0) {
+                        if (which_failed)
+                                *which_failed = i;
+
+                        return r;
+                }
+        }
+
+        if (which_failed)
+                *which_failed = -1;
+
+        return 0;
+}
+
+static int rlimit_parse_u64(const char *val, rlim_t *ret) {
+        uint64_t u;
+        int r;
+
+        assert(val);
+        assert(ret);
+
+        if (streq(val, "infinity")) {
+                *ret = RLIM_INFINITY;
+                return 0;
+        }
+
+        /* setrlimit(2) suggests rlim_t is always 64bit on Linux. */
+        assert_cc(sizeof(rlim_t) == sizeof(uint64_t));
+
+        r = safe_atou64(val, &u);
+        if (r < 0)
+                return r;
+        if (u >= (uint64_t) RLIM_INFINITY)
+                return -ERANGE;
+
+        *ret = (rlim_t) u;
+        return 0;
+}
+
+static int rlimit_parse_size(const char *val, rlim_t *ret) {
+        uint64_t u;
+        int r;
+
+        assert(val);
+        assert(ret);
+
+        if (streq(val, "infinity")) {
+                *ret = RLIM_INFINITY;
+                return 0;
+        }
+
+        r = parse_size(val, 1024, &u);
+        if (r < 0)
+                return r;
+        if (u >= (uint64_t) RLIM_INFINITY)
+                return -ERANGE;
+
+        *ret = (rlim_t) u;
+        return 0;
+}
+
+static int rlimit_parse_sec(const char *val, rlim_t *ret) {
+        uint64_t u;
+        usec_t t;
+        int r;
+
+        assert(val);
+        assert(ret);
+
+        if (streq(val, "infinity")) {
+                *ret = RLIM_INFINITY;
+                return 0;
+        }
+
+        r = parse_sec(val, &t);
+        if (r < 0)
+                return r;
+        if (t == USEC_INFINITY) {
+                *ret = RLIM_INFINITY;
+                return 0;
+        }
+
+        u = (uint64_t) DIV_ROUND_UP(t, USEC_PER_SEC);
+        if (u >= (uint64_t) RLIM_INFINITY)
+                return -ERANGE;
+
+        *ret = (rlim_t) u;
+        return 0;
+}
+
+static int rlimit_parse_usec(const char *val, rlim_t *ret) {
+        usec_t t;
+        int r;
+
+        assert(val);
+        assert(ret);
+
+        if (streq(val, "infinity")) {
+                *ret = RLIM_INFINITY;
+                return 0;
+        }
+
+        r = parse_time(val, &t, 1);
+        if (r < 0)
+                return r;
+        if (t == USEC_INFINITY) {
+                *ret = RLIM_INFINITY;
+                return 0;
+        }
+
+        *ret = (rlim_t) t;
+        return 0;
+}
+
+static int rlimit_parse_nice(const char *val, rlim_t *ret) {
+        uint64_t rl;
+        int r;
+
+        /* So, Linux is weird. The range for RLIMIT_NICE is 40..1, mapping to the nice levels -20..19. However, the
+         * RLIMIT_NICE limit defaults to 0 by the kernel, i.e. a value that maps to nice level 20, which of course is
+         * bogus and does not exist. In order to permit parsing the RLIMIT_NICE of 0 here we hence implement a slight
+         * asymmetry: when parsing as positive nice level we permit 0..19. When parsing as negative nice level, we
+         * permit -20..0. But when parsing as raw resource limit value then we also allow the special value 0.
+         *
+         * Yeah, Linux is quality engineering sometimes... */
+
+        if (val[0] == '+') {
+
+                /* Prefixed with "+": Parse as positive user-friendly nice value */
+                r = safe_atou64(val + 1, &rl);
+                if (r < 0)
+                        return r;
+
+                if (rl >= PRIO_MAX)
+                        return -ERANGE;
+
+                rl = 20 - rl;
+
+        } else if (val[0] == '-') {
+
+                /* Prefixed with "-": Parse as negative user-friendly nice value */
+                r = safe_atou64(val + 1, &rl);
+                if (r < 0)
+                        return r;
+
+                if (rl > (uint64_t) (-PRIO_MIN))
+                        return -ERANGE;
+
+                rl = 20 + rl;
+        } else {
+
+                /* Not prefixed: parse as raw resource limit value */
+                r = safe_atou64(val, &rl);
+                if (r < 0)
+                        return r;
+
+                if (rl > (uint64_t) (20 - PRIO_MIN))
+                        return -ERANGE;
+        }
+
+        *ret = (rlim_t) rl;
+        return 0;
+}
+
+static int (*const rlimit_parse_table[_RLIMIT_MAX])(const char *val, rlim_t *ret) = {
+        [RLIMIT_CPU] = rlimit_parse_sec,
+        [RLIMIT_FSIZE] = rlimit_parse_size,
+        [RLIMIT_DATA] = rlimit_parse_size,
+        [RLIMIT_STACK] = rlimit_parse_size,
+        [RLIMIT_CORE] = rlimit_parse_size,
+        [RLIMIT_RSS] = rlimit_parse_size,
+        [RLIMIT_NOFILE] = rlimit_parse_u64,
+        [RLIMIT_AS] = rlimit_parse_size,
+        [RLIMIT_NPROC] = rlimit_parse_u64,
+        [RLIMIT_MEMLOCK] = rlimit_parse_size,
+        [RLIMIT_LOCKS] = rlimit_parse_u64,
+        [RLIMIT_SIGPENDING] = rlimit_parse_u64,
+        [RLIMIT_MSGQUEUE] = rlimit_parse_size,
+        [RLIMIT_NICE] = rlimit_parse_nice,
+        [RLIMIT_RTPRIO] = rlimit_parse_u64,
+        [RLIMIT_RTTIME] = rlimit_parse_usec,
+};
+
+int rlimit_parse_one(int resource, const char *val, rlim_t *ret) {
+        assert(val);
+        assert(ret);
+
+        if (resource < 0)
+                return -EINVAL;
+        if (resource >= _RLIMIT_MAX)
+                return -EINVAL;
+
+        return rlimit_parse_table[resource](val, ret);
+}
+
+int rlimit_parse(int resource, const char *val, struct rlimit *ret) {
+        _cleanup_free_ char *hard = NULL, *soft = NULL;
+        rlim_t hl, sl;
+        int r;
+
+        assert(val);
+        assert(ret);
+
+        r = extract_first_word(&val, &soft, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -EINVAL;
+
+        r = rlimit_parse_one(resource, soft, &sl);
+        if (r < 0)
+                return r;
+
+        r = extract_first_word(&val, &hard, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+        if (r < 0)
+                return r;
+        if (!isempty(val))
+                return -EINVAL;
+        if (r == 0)
+                hl = sl;
+        else {
+                r = rlimit_parse_one(resource, hard, &hl);
+                if (r < 0)
+                        return r;
+                if (sl > hl)
+                        return -EILSEQ;
+        }
+
+        *ret = (struct rlimit) {
+                .rlim_cur = sl,
+                .rlim_max = hl,
+        };
+
+        return 0;
+}
+
+int rlimit_format(const struct rlimit *rl, char **ret) {
+        char *s = NULL;
+
+        assert(rl);
+        assert(ret);
+
+        if (rl->rlim_cur >= RLIM_INFINITY && rl->rlim_max >= RLIM_INFINITY)
+                s = strdup("infinity");
+        else if (rl->rlim_cur >= RLIM_INFINITY)
+                (void) asprintf(&s, "infinity:" RLIM_FMT, rl->rlim_max);
+        else if (rl->rlim_max >= RLIM_INFINITY)
+                (void) asprintf(&s, RLIM_FMT ":infinity", rl->rlim_cur);
+        else if (rl->rlim_cur == rl->rlim_max)
+                (void) asprintf(&s, RLIM_FMT, rl->rlim_cur);
+        else
+                (void) asprintf(&s, RLIM_FMT ":" RLIM_FMT, rl->rlim_cur, rl->rlim_max);
+
+        if (!s)
+                return -ENOMEM;
+
+        *ret = s;
+        return 0;
+}
+
+static const char* const rlimit_table[_RLIMIT_MAX] = {
+        [RLIMIT_AS]         = "AS",
+        [RLIMIT_CORE]       = "CORE",
+        [RLIMIT_CPU]        = "CPU",
+        [RLIMIT_DATA]       = "DATA",
+        [RLIMIT_FSIZE]      = "FSIZE",
+        [RLIMIT_LOCKS]      = "LOCKS",
+        [RLIMIT_MEMLOCK]    = "MEMLOCK",
+        [RLIMIT_MSGQUEUE]   = "MSGQUEUE",
+        [RLIMIT_NICE]       = "NICE",
+        [RLIMIT_NOFILE]     = "NOFILE",
+        [RLIMIT_NPROC]      = "NPROC",
+        [RLIMIT_RSS]        = "RSS",
+        [RLIMIT_RTPRIO]     = "RTPRIO",
+        [RLIMIT_RTTIME]     = "RTTIME",
+        [RLIMIT_SIGPENDING] = "SIGPENDING",
+        [RLIMIT_STACK]      = "STACK",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(rlimit, int);
+
+int rlimit_from_string_harder(const char *s) {
+        const char *suffix;
+
+        /* The official prefix */
+        suffix = startswith(s, "RLIMIT_");
+        if (suffix)
+                return rlimit_from_string(suffix);
+
+        /* Our own unit file setting prefix */
+        suffix = startswith(s, "Limit");
+        if (suffix)
+                return rlimit_from_string(suffix);
+
+        return rlimit_from_string(s);
+}
+
+void rlimit_free_all(struct rlimit **rl) {
+        int i;
+
+        if (!rl)
+                return;
+
+        for (i = 0; i < _RLIMIT_MAX; i++)
+                rl[i] = mfree(rl[i]);
+}
+
+int rlimit_nofile_bump(int limit) {
+        int r;
+
+        /* Bumps the (soft) RLIMIT_NOFILE resource limit as close as possible to the specified limit. If a negative
+         * limit is specified, bumps it to the maximum the kernel and the hard resource limit allows. This call should
+         * be used by all our programs that might need a lot of fds, and that know how to deal with high fd numbers
+         * (i.e. do not use select() — which chokes on fds >= 1024) */
+
+        if (limit < 0)
+                limit = read_nr_open();
+
+        if (limit < 3)
+                limit = 3;
+
+        r = setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(limit));
+        if (r < 0)
+                return log_debug_errno(r, "Failed to set RLIMIT_NOFILE: %m");
+
+        return 0;
+}
diff --git a/src/shared/rlimit-util.h b/src/shared/rlimit-util.h
new file mode 100644 (file)
index 0000000..6139af3
--- /dev/null
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <sys/resource.h>
+
+#include "macro.h"
+
+const char *rlimit_to_string(int i) _const_;
+int rlimit_from_string(const char *s) _pure_;
+int rlimit_from_string_harder(const char *s) _pure_;
+
+int setrlimit_closest(int resource, const struct rlimit *rlim);
+int setrlimit_closest_all(const struct rlimit * const *rlim, int *which_failed);
+
+int rlimit_parse_one(int resource, const char *val, rlim_t *ret);
+int rlimit_parse(int resource, const char *val, struct rlimit *ret);
+
+int rlimit_format(const struct rlimit *rl, char **ret);
+
+void rlimit_free_all(struct rlimit **rl);
+
+#define RLIMIT_MAKE_CONST(lim) ((struct rlimit) { lim, lim })
+
+int rlimit_nofile_bump(int limit);
diff --git a/src/shared/securebits-util.c b/src/shared/securebits-util.c
new file mode 100644 (file)
index 0000000..ad091f6
--- /dev/null
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <stdio.h>
+
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "securebits.h"
+#include "securebits-util.h"
+#include "string-util.h"
+
+int secure_bits_to_string_alloc(int i, char **s) {
+        _cleanup_free_ char *str = NULL;
+        size_t len;
+        int r;
+
+        assert(s);
+
+        r = asprintf(&str, "%s%s%s%s%s%s",
+                     (i & (1 << SECURE_KEEP_CAPS)) ? "keep-caps " : "",
+                     (i & (1 << SECURE_KEEP_CAPS_LOCKED)) ? "keep-caps-locked " : "",
+                     (i & (1 << SECURE_NO_SETUID_FIXUP)) ? "no-setuid-fixup " : "",
+                     (i & (1 << SECURE_NO_SETUID_FIXUP_LOCKED)) ? "no-setuid-fixup-locked " : "",
+                     (i & (1 << SECURE_NOROOT)) ? "noroot " : "",
+                     (i & (1 << SECURE_NOROOT_LOCKED)) ? "noroot-locked " : "");
+        if (r < 0)
+                return -ENOMEM;
+
+        len = strlen(str);
+        if (len != 0)
+                str[len - 1] = '\0';
+
+        *s = TAKE_PTR(str);
+
+        return 0;
+}
+
+int secure_bits_from_string(const char *s) {
+        int secure_bits = 0;
+        const char *p;
+        int r;
+
+        for (p = s;;) {
+                _cleanup_free_ char *word = NULL;
+
+                r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
+                if (r == -ENOMEM)
+                        return r;
+                if (r <= 0)
+                        break;
+
+                if (streq(word, "keep-caps"))
+                        secure_bits |= 1 << SECURE_KEEP_CAPS;
+                else if (streq(word, "keep-caps-locked"))
+                        secure_bits |= 1 << SECURE_KEEP_CAPS_LOCKED;
+                else if (streq(word, "no-setuid-fixup"))
+                        secure_bits |= 1 << SECURE_NO_SETUID_FIXUP;
+                else if (streq(word, "no-setuid-fixup-locked"))
+                        secure_bits |= 1 << SECURE_NO_SETUID_FIXUP_LOCKED;
+                else if (streq(word, "noroot"))
+                        secure_bits |= 1 << SECURE_NOROOT;
+                else if (streq(word, "noroot-locked"))
+                        secure_bits |= 1 << SECURE_NOROOT_LOCKED;
+        }
+
+        return secure_bits;
+}
diff --git a/src/shared/securebits-util.h b/src/shared/securebits-util.h
new file mode 100644 (file)
index 0000000..3cb3cb3
--- /dev/null
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "securebits.h"
+
+int secure_bits_to_string_alloc(int i, char **s);
+int secure_bits_from_string(const char *s);
+
+static inline bool secure_bits_is_valid(int i) {
+        return ((SECURE_ALL_BITS | SECURE_ALL_LOCKS) & i) == i;
+}
+
+static inline int secure_bits_to_string_alloc_with_check(int n, char **s) {
+        if (!secure_bits_is_valid(n))
+                return -EINVAL;
+
+        return secure_bits_to_string_alloc(n, s);
+}
diff --git a/src/shared/socket-protocol-list.c b/src/shared/socket-protocol-list.c
new file mode 100644 (file)
index 0000000..8041b84
--- /dev/null
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <netinet/in.h>
+#include <string.h>
+
+#include "socket-protocol-list.h"
+#include "macro.h"
+
+static const struct socket_protocol_name* lookup_socket_protocol(register const char *str, register GPERF_LEN_TYPE len);
+
+#include "socket-protocol-from-name.h"
+#include "socket-protocol-to-name.h"
+
+const char *socket_protocol_to_name(int id) {
+
+        if (id < 0)
+                return NULL;
+
+        if (id >= (int) ELEMENTSOF(socket_protocol_names))
+                return NULL;
+
+        return socket_protocol_names[id];
+}
+
+int socket_protocol_from_name(const char *name) {
+        const struct socket_protocol_name *sc;
+
+        assert(name);
+
+        sc = lookup_socket_protocol(name, strlen(name));
+        if (!sc)
+                return 0;
+
+        return sc->id;
+}
+
+int socket_protocol_max(void) {
+        return ELEMENTSOF(socket_protocol_names);
+}
diff --git a/src/shared/socket-protocol-list.h b/src/shared/socket-protocol-list.h
new file mode 100644 (file)
index 0000000..458904d
--- /dev/null
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+const char *socket_protocol_to_name(int id);
+int socket_protocol_from_name(const char *name);
+
+int socket_protocol_max(void);
diff --git a/src/shared/verbs.c b/src/shared/verbs.c
new file mode 100644 (file)
index 0000000..f68c2ba
--- /dev/null
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "env-util.h"
+#include "log.h"
+#include "macro.h"
+#include "process-util.h"
+#include "string-util.h"
+#include "verbs.h"
+#include "virt.h"
+
+/* Wraps running_in_chroot() which is used in various places, but also adds an environment variable check so external
+ * processes can reliably force this on.
+ */
+bool running_in_chroot_or_offline(void) {
+        int r;
+
+        /* Added to support use cases like rpm-ostree, where from %post scripts we only want to execute "preset", but
+         * not "start"/"restart" for example.
+         *
+         * See docs/ENVIRONMENT.md for docs.
+         */
+        r = getenv_bool("SYSTEMD_OFFLINE");
+        if (r < 0 && r != -ENXIO)
+                log_debug_errno(r, "Failed to parse $SYSTEMD_OFFLINE: %m");
+        else if (r >= 0)
+                return r > 0;
+
+        /* We've had this condition check for a long time which basically checks for legacy chroot case like Fedora's
+         * "mock", which is used for package builds.  We don't want to try to start systemd services there, since
+         * without --new-chroot we don't even have systemd running, and even if we did, adding a concept of background
+         * daemons to builds would be an enormous change, requiring considering things like how the journal output is
+         * handled, etc.  And there's really not a use case today for a build talking to a service.
+         *
+         * Note this call itself also looks for a different variable SYSTEMD_IGNORE_CHROOT=1.
+         */
+        r = running_in_chroot();
+        if (r < 0)
+                log_debug_errno(r, "running_in_chroot(): %m");
+
+        return r > 0;
+}
+
+int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) {
+        const Verb *verb;
+        const char *name;
+        unsigned i;
+        int left, r;
+
+        assert(verbs);
+        assert(verbs[0].dispatch);
+        assert(argc >= 0);
+        assert(argv);
+        assert(argc >= optind);
+
+        left = argc - optind;
+        argv += optind;
+        optind = 0;
+        name = argv[0];
+
+        for (i = 0;; i++) {
+                bool found;
+
+                /* At the end of the list? */
+                if (!verbs[i].dispatch) {
+                        if (name)
+                                log_error("Unknown operation %s.", name);
+                        else
+                                log_error("Requires operation parameter.");
+                        return -EINVAL;
+                }
+
+                if (name)
+                        found = streq(name, verbs[i].verb);
+                else
+                        found = verbs[i].flags & VERB_DEFAULT;
+
+                if (found) {
+                        verb = &verbs[i];
+                        break;
+                }
+        }
+
+        assert(verb);
+
+        if (!name)
+                left = 1;
+
+        if (verb->min_args != VERB_ANY &&
+            (unsigned) left < verb->min_args) {
+                log_error("Too few arguments.");
+                return -EINVAL;
+        }
+
+        if (verb->max_args != VERB_ANY &&
+            (unsigned) left > verb->max_args) {
+                log_error("Too many arguments.");
+                return -EINVAL;
+        }
+
+        if ((verb->flags & VERB_ONLINE_ONLY) && running_in_chroot_or_offline()) {
+                if (name)
+                        log_info("Running in chroot, ignoring request: %s", name);
+                else
+                        log_info("Running in chroot, ignoring request.");
+                return 0;
+        }
+
+        if (verb->flags & VERB_MUST_BE_ROOT) {
+                r = must_be_root();
+                if (r < 0)
+                        return r;
+        }
+
+        if (name)
+                return verb->dispatch(left, argv, userdata);
+        else {
+                char* fake[2] = {
+                        (char*) verb->verb,
+                        NULL
+                };
+
+                return verb->dispatch(1, fake, userdata);
+        }
+}
diff --git a/src/shared/verbs.h b/src/shared/verbs.h
new file mode 100644 (file)
index 0000000..e174255
--- /dev/null
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#define VERB_ANY ((unsigned) -1)
+
+typedef enum VerbFlags {
+        VERB_DEFAULT      = 1 << 0,
+        VERB_ONLINE_ONLY  = 1 << 1,
+        VERB_MUST_BE_ROOT = 1 << 2,
+} VerbFlags;
+
+typedef struct {
+        const char *verb;
+        unsigned min_args, max_args;
+        VerbFlags flags;
+        int (* const dispatch)(int argc, char *argv[], void *userdata);
+} Verb;
+
+bool running_in_chroot_or_offline(void);
+
+int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata);
diff --git a/src/shared/web-util.c b/src/shared/web-util.c
new file mode 100644 (file)
index 0000000..82221af
--- /dev/null
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <stdbool.h>
+
+#include "string-util.h"
+#include "utf8.h"
+#include "web-util.h"
+
+bool http_etag_is_valid(const char *etag) {
+        if (isempty(etag))
+                return false;
+
+        if (!endswith(etag, "\""))
+                return false;
+
+        if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
+                return false;
+
+        return true;
+}
+
+bool http_url_is_valid(const char *url) {
+        const char *p;
+
+        if (isempty(url))
+                return false;
+
+        p = startswith(url, "http://");
+        if (!p)
+                p = startswith(url, "https://");
+        if (!p)
+                return false;
+
+        if (isempty(p))
+                return false;
+
+        return ascii_is_valid(p);
+}
+
+bool documentation_url_is_valid(const char *url) {
+        const char *p;
+
+        if (isempty(url))
+                return false;
+
+        if (http_url_is_valid(url))
+                return true;
+
+        p = startswith(url, "file:/");
+        if (!p)
+                p = startswith(url, "info:");
+        if (!p)
+                p = startswith(url, "man:");
+
+        if (isempty(p))
+                return false;
+
+        return ascii_is_valid(p);
+}
diff --git a/src/shared/web-util.h b/src/shared/web-util.h
new file mode 100644 (file)
index 0000000..c9e67e5
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+
+#include "macro.h"
+
+bool http_url_is_valid(const char *url) _pure_;
+
+bool documentation_url_is_valid(const char *url) _pure_;
+
+bool http_etag_is_valid(const char *etag);
diff --git a/src/shared/xml.c b/src/shared/xml.c
new file mode 100644 (file)
index 0000000..cb34d87
--- /dev/null
@@ -0,0 +1,238 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "macro.h"
+#include "string-util.h"
+#include "xml.h"
+
+enum {
+        STATE_NULL,
+        STATE_TEXT,
+        STATE_TAG,
+        STATE_ATTRIBUTE,
+};
+
+static void inc_lines(unsigned *line, const char *s, size_t n) {
+        const char *p = s;
+
+        if (!line)
+                return;
+
+        for (;;) {
+                const char *f;
+
+                f = memchr(p, '\n', n);
+                if (!f)
+                        return;
+
+                n -= (f - p) + 1;
+                p = f + 1;
+                (*line)++;
+        }
+}
+
+/* We don't actually do real XML here. We only read a simplistic
+ * subset, that is a bit less strict that XML and lacks all the more
+ * complex features, like entities, or namespaces. However, we do
+ * support some HTML5-like simplifications */
+
+int xml_tokenize(const char **p, char **name, void **state, unsigned *line) {
+        const char *c, *e, *b;
+        char *ret;
+        int t;
+
+        assert(p);
+        assert(*p);
+        assert(name);
+        assert(state);
+
+        t = PTR_TO_INT(*state);
+        c = *p;
+
+        if (t == STATE_NULL) {
+                if (line)
+                        *line = 1;
+                t = STATE_TEXT;
+        }
+
+        for (;;) {
+                if (*c == 0)
+                        return XML_END;
+
+                switch (t) {
+
+                case STATE_TEXT: {
+                        int x;
+
+                        e = strchrnul(c, '<');
+                        if (e > c) {
+                                /* More text... */
+                                ret = strndup(c, e - c);
+                                if (!ret)
+                                        return -ENOMEM;
+
+                                inc_lines(line, c, e - c);
+
+                                *name = ret;
+                                *p = e;
+                                *state = INT_TO_PTR(STATE_TEXT);
+
+                                return XML_TEXT;
+                        }
+
+                        assert(*e == '<');
+                        b = c + 1;
+
+                        if (startswith(b, "!--")) {
+                                /* A comment */
+                                e = strstr(b + 3, "-->");
+                                if (!e)
+                                        return -EINVAL;
+
+                                inc_lines(line, b, e + 3 - b);
+
+                                c = e + 3;
+                                continue;
+                        }
+
+                        if (*b == '?') {
+                                /* Processing instruction */
+
+                                e = strstr(b + 1, "?>");
+                                if (!e)
+                                        return -EINVAL;
+
+                                inc_lines(line, b, e + 2 - b);
+
+                                c = e + 2;
+                                continue;
+                        }
+
+                        if (*b == '!') {
+                                /* DTD */
+
+                                e = strchr(b + 1, '>');
+                                if (!e)
+                                        return -EINVAL;
+
+                                inc_lines(line, b, e + 1 - b);
+
+                                c = e + 1;
+                                continue;
+                        }
+
+                        if (*b == '/') {
+                                /* A closing tag */
+                                x = XML_TAG_CLOSE;
+                                b++;
+                        } else
+                                x = XML_TAG_OPEN;
+
+                        e = strpbrk(b, WHITESPACE "/>");
+                        if (!e)
+                                return -EINVAL;
+
+                        ret = strndup(b, e - b);
+                        if (!ret)
+                                return -ENOMEM;
+
+                        *name = ret;
+                        *p = e;
+                        *state = INT_TO_PTR(STATE_TAG);
+
+                        return x;
+                }
+
+                case STATE_TAG:
+
+                        b = c + strspn(c, WHITESPACE);
+                        if (*b == 0)
+                                return -EINVAL;
+
+                        inc_lines(line, c, b - c);
+
+                        e = b + strcspn(b, WHITESPACE "=/>");
+                        if (e > b) {
+                                /* An attribute */
+
+                                ret = strndup(b, e - b);
+                                if (!ret)
+                                        return -ENOMEM;
+
+                                *name = ret;
+                                *p = e;
+                                *state = INT_TO_PTR(STATE_ATTRIBUTE);
+
+                                return XML_ATTRIBUTE_NAME;
+                        }
+
+                        if (startswith(b, "/>")) {
+                                /* An empty tag */
+
+                                *name = NULL; /* For empty tags we return a NULL name, the caller must be prepared for that */
+                                *p = b + 2;
+                                *state = INT_TO_PTR(STATE_TEXT);
+
+                                return XML_TAG_CLOSE_EMPTY;
+                        }
+
+                        if (*b != '>')
+                                return -EINVAL;
+
+                        c = b + 1;
+                        t = STATE_TEXT;
+                        continue;
+
+                case STATE_ATTRIBUTE:
+
+                        if (*c == '=') {
+                                c++;
+
+                                if (IN_SET(*c, '\'', '\"')) {
+                                        /* Tag with a quoted value */
+
+                                        e = strchr(c+1, *c);
+                                        if (!e)
+                                                return -EINVAL;
+
+                                        inc_lines(line, c, e - c);
+
+                                        ret = strndup(c+1, e - c - 1);
+                                        if (!ret)
+                                                return -ENOMEM;
+
+                                        *name = ret;
+                                        *p = e + 1;
+                                        *state = INT_TO_PTR(STATE_TAG);
+
+                                        return XML_ATTRIBUTE_VALUE;
+
+                                }
+
+                                /* Tag with a value without quotes */
+
+                                b = strpbrk(c, WHITESPACE ">");
+                                if (!b)
+                                        b = c;
+
+                                ret = strndup(c, b - c);
+                                if (!ret)
+                                        return -ENOMEM;
+
+                                *name = ret;
+                                *p = b;
+                                *state = INT_TO_PTR(STATE_TAG);
+                                return XML_ATTRIBUTE_VALUE;
+                        }
+
+                        t = STATE_TAG;
+                        continue;
+                }
+
+        }
+
+        assert_not_reached("Bad state");
+}
diff --git a/src/shared/xml.h b/src/shared/xml.h
new file mode 100644 (file)
index 0000000..8da2ff5
--- /dev/null
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+enum {
+        XML_END,
+        XML_TEXT,
+        XML_TAG_OPEN,
+        XML_TAG_CLOSE,
+        XML_TAG_CLOSE_EMPTY,
+        XML_ATTRIBUTE_NAME,
+        XML_ATTRIBUTE_VALUE,
+};
+
+int xml_tokenize(const char **p, char **name, void **state, unsigned *line);