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,
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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)
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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,
-};
+++ /dev/null
-/* 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];
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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 }
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
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
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
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
list.h
locale-util.c
locale-util.h
- lockfile-util.c
- lockfile-util.h
log.c
log.h
login-util.c
nss-util.h
ordered-set.c
ordered-set.h
- os-util.c
- os-util.h
pager.c
pager.h
parse-util.c
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
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
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')
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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", ¶meter);
- 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");
-}
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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);
- }
-}
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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");
-}
+++ /dev/null
-/* 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);
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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)
--- /dev/null
+/* 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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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,
+};
--- /dev/null
+/* 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];
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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 }
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
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
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
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
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'
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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", ¶meter);
+ 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");
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
+ }
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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");
+}
--- /dev/null
+/* 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);