about summary refs log tree commit diff
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/c-rbtree/src/c-rbtree-private.h40
-rw-r--r--shared/c-rbtree/src/c-rbtree.c1118
-rw-r--r--shared/c-rbtree/src/c-rbtree.h430
-rw-r--r--shared/c-siphash/src/c-siphash.c4
-rw-r--r--shared/meson.build234
-rw-r--r--shared/n-acd/src/n-acd-bpf-fallback.c29
-rw-r--r--shared/n-acd/src/n-acd-bpf.c316
-rw-r--r--shared/n-acd/src/n-acd-private.h172
-rw-r--r--shared/n-acd/src/n-acd-probe.c636
-rw-r--r--shared/n-acd/src/n-acd.c1556
-rw-r--r--shared/n-acd/src/n-acd.h126
-rw-r--r--shared/n-acd/src/util/timer.c189
-rw-r--r--shared/n-acd/src/util/timer.h53
-rw-r--r--shared/nm-common-macros.h1
-rw-r--r--shared/nm-default.h11
-rw-r--r--shared/nm-ethtool-utils.c2
-rw-r--r--shared/nm-meta-setting.c24
-rw-r--r--shared/nm-meta-setting.h2
-rw-r--r--shared/nm-test-utils-impl.c1
-rw-r--r--shared/nm-utils/nm-c-list.h36
-rw-r--r--shared/nm-utils/nm-dedup-multi.c61
-rw-r--r--shared/nm-utils/nm-dedup-multi.h8
-rw-r--r--shared/nm-utils/nm-errno.c198
-rw-r--r--shared/nm-utils/nm-errno.h185
-rw-r--r--shared/nm-utils/nm-glib.h32
-rw-r--r--shared/nm-utils/nm-hash-utils.c2
-rw-r--r--shared/nm-utils/nm-hash-utils.h3
-rw-r--r--shared/nm-utils/nm-io-utils.c81
-rw-r--r--shared/nm-utils/nm-jansson.h10
-rw-r--r--shared/nm-utils/nm-logging-fwd.h113
-rw-r--r--shared/nm-utils/nm-macros-internal.h215
-rw-r--r--shared/nm-utils/nm-random-utils.c2
-rw-r--r--shared/nm-utils/nm-secret-utils.c27
-rw-r--r--shared/nm-utils/nm-secret-utils.h29
-rw-r--r--shared/nm-utils/nm-shared-utils.c709
-rw-r--r--shared/nm-utils/nm-shared-utils.h300
-rw-r--r--shared/nm-utils/nm-test-utils.h145
-rw-r--r--shared/nm-utils/nm-time-utils.c273
-rw-r--r--shared/nm-utils/nm-time-utils.h45
-rw-r--r--shared/nm-utils/nm-vpn-plugin-utils.c46
-rw-r--r--shared/nm-utils/tests/test-shared-general.c267
-rw-r--r--shared/nm-utils/unaligned.h24
-rw-r--r--shared/nm-version-macros.h8
-rw-r--r--shared/nm-version-macros.h.in4
-rw-r--r--shared/systemd/nm-logging-stub.c47
-rw-r--r--shared/systemd/nm-sd-utils-shared.c82
-rw-r--r--shared/systemd/nm-sd-utils-shared.h38
-rw-r--r--shared/systemd/sd-adapt-shared/architecture.h3
-rw-r--r--shared/systemd/sd-adapt-shared/btrfs-util.h3
-rw-r--r--shared/systemd/sd-adapt-shared/build.h3
-rw-r--r--shared/systemd/sd-adapt-shared/cgroup-util.h3
-rw-r--r--shared/systemd/sd-adapt-shared/copy.h3
-rw-r--r--shared/systemd/sd-adapt-shared/def.h3
-rw-r--r--shared/systemd/sd-adapt-shared/device-nodes.h3
-rw-r--r--shared/systemd/sd-adapt-shared/dirent-util.h5
-rw-r--r--shared/systemd/sd-adapt-shared/errno-list.h3
-rw-r--r--shared/systemd/sd-adapt-shared/format-util.h3
-rw-r--r--shared/systemd/sd-adapt-shared/glob-util.h3
-rw-r--r--shared/systemd/sd-adapt-shared/gunicode.h3
-rw-r--r--shared/systemd/sd-adapt-shared/ioprio.h3
-rw-r--r--shared/systemd/sd-adapt-shared/locale-util.h3
-rw-r--r--shared/systemd/sd-adapt-shared/memfd-util.h3
-rw-r--r--shared/systemd/sd-adapt-shared/missing.h6
-rw-r--r--shared/systemd/sd-adapt-shared/missing_socket.h3
-rw-r--r--shared/systemd/sd-adapt-shared/missing_syscall.h3
-rw-r--r--shared/systemd/sd-adapt-shared/missing_timerfd.h3
-rw-r--r--shared/systemd/sd-adapt-shared/mkdir.h3
-rw-r--r--shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h139
-rw-r--r--shared/systemd/sd-adapt-shared/procfs-util.h3
-rw-r--r--shared/systemd/sd-adapt-shared/raw-clone.h3
-rw-r--r--shared/systemd/sd-adapt-shared/rlimit-util.h3
-rw-r--r--shared/systemd/sd-adapt-shared/terminal-util.h3
-rw-r--r--shared/systemd/sd-adapt-shared/unaligned.h3
-rw-r--r--shared/systemd/sd-adapt-shared/user-util.h3
-rw-r--r--shared/systemd/sd-adapt-shared/virt.h3
-rw-r--r--shared/systemd/src/basic/alloc-util.c83
-rw-r--r--shared/systemd/src/basic/alloc-util.h162
-rw-r--r--shared/systemd/src/basic/async.h7
-rw-r--r--shared/systemd/src/basic/env-file.c568
-rw-r--r--shared/systemd/src/basic/env-file.h17
-rw-r--r--shared/systemd/src/basic/env-util.c758
-rw-r--r--shared/systemd/src/basic/env-util.h47
-rw-r--r--shared/systemd/src/basic/escape.c508
-rw-r--r--shared/systemd/src/basic/escape.h53
-rw-r--r--shared/systemd/src/basic/ether-addr-util.c113
-rw-r--r--shared/systemd/src/basic/ether-addr-util.h28
-rw-r--r--shared/systemd/src/basic/extract-word.c289
-rw-r--r--shared/systemd/src/basic/extract-word.h17
-rw-r--r--shared/systemd/src/basic/fd-util.c975
-rw-r--r--shared/systemd/src/basic/fd-util.h110
-rw-r--r--shared/systemd/src/basic/fileio.c832
-rw-r--r--shared/systemd/src/basic/fileio.h78
-rw-r--r--shared/systemd/src/basic/fs-util.c1368
-rw-r--r--shared/systemd/src/basic/fs-util.h111
-rw-r--r--shared/systemd/src/basic/hash-funcs.c97
-rw-r--r--shared/systemd/src/basic/hash-funcs.h106
-rw-r--r--shared/systemd/src/basic/hashmap.c1913
-rw-r--r--shared/systemd/src/basic/hashmap.h429
-rw-r--r--shared/systemd/src/basic/hexdecoct.c828
-rw-r--r--shared/systemd/src/basic/hexdecoct.h38
-rw-r--r--shared/systemd/src/basic/hostname-util.c314
-rw-r--r--shared/systemd/src/basic/hostname-util.h28
-rw-r--r--shared/systemd/src/basic/in-addr-util.c636
-rw-r--r--shared/systemd/src/basic/in-addr-util.h72
-rw-r--r--shared/systemd/src/basic/io-util.c272
-rw-r--r--shared/systemd/src/basic/io-util.h75
-rw-r--r--shared/systemd/src/basic/list.h171
-rw-r--r--shared/systemd/src/basic/log.h331
-rw-r--r--shared/systemd/src/basic/macro.h558
-rw-r--r--shared/systemd/src/basic/mempool.c101
-rw-r--r--shared/systemd/src/basic/mempool.h31
-rw-r--r--shared/systemd/src/basic/missing_fcntl.h60
-rw-r--r--shared/systemd/src/basic/missing_type.h12
-rw-r--r--shared/systemd/src/basic/parse-util.c785
-rw-r--r--shared/systemd/src/basic/parse-util.h120
-rw-r--r--shared/systemd/src/basic/path-util.c1160
-rw-r--r--shared/systemd/src/basic/path-util.h192
-rw-r--r--shared/systemd/src/basic/prioq.c302
-rw-r--r--shared/systemd/src/basic/prioq.h32
-rw-r--r--shared/systemd/src/basic/process-util.c1575
-rw-r--r--shared/systemd/src/basic/process-util.h196
-rw-r--r--shared/systemd/src/basic/random-util.c274
-rw-r--r--shared/systemd/src/basic/random-util.h33
-rw-r--r--shared/systemd/src/basic/refcnt.h54
-rw-r--r--shared/systemd/src/basic/set.h130
-rw-r--r--shared/systemd/src/basic/signal-util.h43
-rw-r--r--shared/systemd/src/basic/siphash24.h58
-rw-r--r--shared/systemd/src/basic/socket-util.c1353
-rw-r--r--shared/systemd/src/basic/socket-util.h202
-rw-r--r--shared/systemd/src/basic/sparse-endian.h90
-rw-r--r--shared/systemd/src/basic/stat-util.c433
-rw-r--r--shared/systemd/src/basic/stat-util.h90
-rw-r--r--shared/systemd/src/basic/stdio-util.h64
-rw-r--r--shared/systemd/src/basic/string-table.c19
-rw-r--r--shared/systemd/src/basic/string-table.h112
-rw-r--r--shared/systemd/src/basic/string-util.c1107
-rw-r--r--shared/systemd/src/basic/string-util.h266
-rw-r--r--shared/systemd/src/basic/strv.c893
-rw-r--r--shared/systemd/src/basic/strv.h190
-rw-r--r--shared/systemd/src/basic/time-util.c1488
-rw-r--r--shared/systemd/src/basic/time-util.h180
-rw-r--r--shared/systemd/src/basic/tmpfile-util.c336
-rw-r--r--shared/systemd/src/basic/tmpfile-util.h19
-rw-r--r--shared/systemd/src/basic/umask-util.h28
-rw-r--r--shared/systemd/src/basic/utf8.c546
-rw-r--r--shared/systemd/src/basic/utf8.h51
-rw-r--r--shared/systemd/src/basic/util.c643
-rw-r--r--shared/systemd/src/basic/util.h253
148 files changed, 32343 insertions, 1329 deletions
diff --git a/shared/c-rbtree/src/c-rbtree-private.h b/shared/c-rbtree/src/c-rbtree-private.h
new file mode 100644
index 00000000..25b9ba01
--- /dev/null
+++ b/shared/c-rbtree/src/c-rbtree-private.h
@@ -0,0 +1,40 @@
+#pragma once
+
+/*
+ * Private definitions
+ * This file contains private definitions for the RB-Tree implementation, but
+ * which are used by our test-suite.
+ */
+
+#include <stddef.h>
+#include "c-rbtree.h"
+
+/*
+ * Macros
+ */
+
+#define _public_ __attribute__((__visibility__("default")))
+
+/*
+ * Nodes
+ */
+
+static inline void *c_rbnode_raw(CRBNode *n) {
+        return (void *)(n->__parent_and_flags & ~C_RBNODE_FLAG_MASK);
+}
+
+static inline unsigned long c_rbnode_flags(CRBNode *n) {
+        return n->__parent_and_flags & C_RBNODE_FLAG_MASK;
+}
+
+static inline _Bool c_rbnode_is_red(CRBNode *n) {
+        return c_rbnode_flags(n) & C_RBNODE_RED;
+}
+
+static inline _Bool c_rbnode_is_black(CRBNode *n) {
+        return !(c_rbnode_flags(n) & C_RBNODE_RED);
+}
+
+static inline _Bool c_rbnode_is_root(CRBNode *n) {
+        return c_rbnode_flags(n) & C_RBNODE_ROOT;
+}
diff --git a/shared/c-rbtree/src/c-rbtree.c b/shared/c-rbtree/src/c-rbtree.c
new file mode 100644
index 00000000..f58db849
--- /dev/null
+++ b/shared/c-rbtree/src/c-rbtree.c
@@ -0,0 +1,1118 @@
+/*
+ * RB-Tree Implementation
+ * This implements the insertion/removal of elements in RB-Trees. You're highly
+ * recommended to have an RB-Tree documentation at hand when reading this. Both
+ * insertion and removal can be split into a handful of situations that can
+ * occur. Those situations are enumerated as "Case 1" to "Case n" here, and
+ * follow closely the cases described in most RB-Tree documentations. This file
+ * does not explain why it is enough to handle just those cases, nor does it
+ * provide a proof of correctness. Dig out your algorithm 101 handbook if
+ * you're interested.
+ *
+ * This implementation is *not* straightforward. Usually, a handful of
+ * rotation, reparent, swap and link helpers can be used to implement the
+ * rebalance operations. However, those often perform unnecessary writes.
+ * Therefore, this implementation hard-codes all the operations. You're highly
+ * recommended to look at the two basic helpers before reading the code:
+ *     c_rbnode_swap_child()
+ *     c_rbnode_set_parent_and_flags()
+ * Those are the only helpers used, hence, you should really know what they do
+ * before digging into the code.
+ *
+ * For a highlevel documentation of the API, see the header file and docbook
+ * comments.
+ */
+
+#include <assert.h>
+#include <stdalign.h>
+#include <stddef.h>
+
+#include "c-rbtree-private.h"
+#include "c-rbtree.h"
+
+/*
+ * We use alignas(8) to enforce 64bit alignment of structure fields. This is
+ * according to ISO-C11, so we rely on the compiler to implement this. However,
+ * at the same time we don't want to exceed native malloc() alignment on target
+ * platforms. Hence, we also verify against max_align_t.
+ */
+static_assert(alignof(CRBNode) <= alignof(max_align_t), "Invalid RBNode alignment");
+static_assert(alignof(CRBNode) >= 8, "Invalid CRBNode alignment");
+static_assert(alignof(CRBTree) <= alignof(max_align_t), "Invalid RBTree alignment");
+static_assert(alignof(CRBTree) >= 8, "Invalid CRBTree alignment");
+
+/**
+ * c_rbnode_leftmost() - return leftmost child
+ * @n:          current node, or NULL
+ *
+ * This returns the leftmost child of @n. If @n is NULL, this will return NULL.
+ * In all other cases, this function returns a valid pointer. That is, if @n
+ * does not have any left children, this returns @n.
+ *
+ * Worst case runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to leftmost child, or NULL.
+ */
+_public_ CRBNode *c_rbnode_leftmost(CRBNode *n) {
+        if (n)
+                while (n->left)
+                        n = n->left;
+        return n;
+}
+
+/**
+ * c_rbnode_rightmost() - return rightmost child
+ * @n:          current node, or NULL
+ *
+ * This returns the rightmost child of @n. If @n is NULL, this will return
+ * NULL. In all other cases, this function returns a valid pointer. That is, if
+ * @n does not have any right children, this returns @n.
+ *
+ * Worst case runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to rightmost child, or NULL.
+ */
+_public_ CRBNode *c_rbnode_rightmost(CRBNode *n) {
+        if (n)
+                while (n->right)
+                        n = n->right;
+        return n;
+}
+
+/**
+ * c_rbnode_leftdeepest() - return left-deepest child
+ * @n:          current node, or NULL
+ *
+ * This returns the left-deepest child of @n. If @n is NULL, this will return
+ * NULL. In all other cases, this function returns a valid pointer. That is, if
+ * @n does not have any children, this returns @n.
+ *
+ * The left-deepest child is defined as the deepest child without any left
+ * (grand-...)siblings.
+ *
+ * Worst case runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to left-deepest child, or NULL.
+ */
+_public_ CRBNode *c_rbnode_leftdeepest(CRBNode *n) {
+        if (n) {
+                for (;;) {
+                        if (n->left)
+                                n = n->left;
+                        else if (n->right)
+                                n = n->right;
+                        else
+                                break;
+                }
+        }
+        return n;
+}
+
+/**
+ * c_rbnode_rightdeepest() - return right-deepest child
+ * @n:          current node, or NULL
+ *
+ * This returns the right-deepest child of @n. If @n is NULL, this will return
+ * NULL. In all other cases, this function returns a valid pointer. That is, if
+ * @n does not have any children, this returns @n.
+ *
+ * The right-deepest child is defined as the deepest child without any right
+ * (grand-...)siblings.
+ *
+ * Worst case runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to right-deepest child, or NULL.
+ */
+_public_ CRBNode *c_rbnode_rightdeepest(CRBNode *n) {
+        if (n) {
+                for (;;) {
+                        if (n->right)
+                                n = n->right;
+                        else if (n->left)
+                                n = n->left;
+                        else
+                                break;
+                }
+        }
+        return n;
+}
+
+/**
+ * c_rbnode_next() - return next node
+ * @n:          current node, or NULL
+ *
+ * An RB-Tree always defines a linear order of its elements. This function
+ * returns the logically next node to @n. If @n is NULL, the last node or
+ * unlinked, this returns NULL.
+ *
+ * Worst case runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to next node, or NULL.
+ */
+_public_ CRBNode *c_rbnode_next(CRBNode *n) {
+        CRBNode *p;
+
+        if (!c_rbnode_is_linked(n))
+                return NULL;
+        if (n->right)
+                return c_rbnode_leftmost(n->right);
+
+        while ((p = c_rbnode_parent(n)) && n == p->right)
+                n = p;
+
+        return p;
+}
+
+/**
+ * c_rbnode_prev() - return previous node
+ * @n:          current node, or NULL
+ *
+ * An RB-Tree always defines a linear order of its elements. This function
+ * returns the logically previous node to @n. If @n is NULL, the first node or
+ * unlinked, this returns NULL.
+ *
+ * Worst case runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to previous node, or NULL.
+ */
+_public_ CRBNode *c_rbnode_prev(CRBNode *n) {
+        CRBNode *p;
+
+        if (!c_rbnode_is_linked(n))
+                return NULL;
+        if (n->left)
+                return c_rbnode_rightmost(n->left);
+
+        while ((p = c_rbnode_parent(n)) && n == p->left)
+                n = p;
+
+        return p;
+}
+
+/**
+ * c_rbnode_next_postorder() - return next node in post-order
+ * @n:          current node, or NULL
+ *
+ * This returns the next node to @n, based on a left-to-right post-order
+ * traversal. If @n is NULL, the root node, or unlinked, this returns NULL.
+ *
+ * This implements a left-to-right post-order traversal: First visit the left
+ * child of a node, then the right, and lastly the node itself. Children are
+ * traversed recursively.
+ *
+ * This function can be used to implement a left-to-right post-order traversal:
+ *
+ *     for (n = c_rbtree_first_postorder(t); n; n = c_rbnode_next_postorder(n))
+ *             visit(n);
+ *
+ * Worst case runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to next node, or NULL.
+ */
+_public_ CRBNode *c_rbnode_next_postorder(CRBNode *n) {
+        CRBNode *p;
+
+        if (!c_rbnode_is_linked(n))
+                return NULL;
+
+        p = c_rbnode_parent(n);
+        if (p && n == p->left && p->right)
+                return c_rbnode_leftdeepest(p->right);
+
+        return p;
+}
+
+/**
+ * c_rbnode_prev_postorder() - return previous node in post-order
+ * @n:          current node, or NULL
+ *
+ * This returns the previous node to @n, based on a left-to-right post-order
+ * traversal. That is, it is the inverse operation to c_rbnode_next_postorder().
+ * If @n is NULL, the left-deepest node, or unlinked, this returns NULL.
+ *
+ * This function returns the logical previous node in a directed post-order
+ * traversal. That is, it effectively does a pre-order traversal (since a
+ * reverse post-order traversal is a pre-order traversal). This function does
+ * NOT do a right-to-left post-order traversal! In other words, the following
+ * invariant is guaranteed, if c_rbnode_next_postorder(n) is non-NULL:
+ *
+ *     n == c_rbnode_prev_postorder(c_rbnode_next_postorder(n))
+ *
+ * This function can be used to implement a right-to-left pre-order traversal,
+ * using the fact that a reverse post-order traversal is also a valid pre-order
+ * traversal:
+ *
+ *     for (n = c_rbtree_last_postorder(t); n; n = c_rbnode_prev_postorder(n))
+ *             visit(n);
+ *
+ * This would effectively perform a right-to-left pre-order traversal: first
+ * visit a parent, then its right child, then its left child. Both children are
+ * traversed recursively.
+ *
+ * Worst case runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to previous node in post-order, or NULL.
+ */
+_public_ CRBNode *c_rbnode_prev_postorder(CRBNode *n) {
+        CRBNode *p;
+
+        if (!c_rbnode_is_linked(n))
+                return NULL;
+        if (n->right)
+                return n->right;
+        if (n->left)
+                return n->left;
+
+        while ((p = c_rbnode_parent(n))) {
+                if (p->left && n != p->left)
+                        return p->left;
+                n = p;
+        }
+
+        return NULL;
+}
+
+/**
+ * c_rbtree_first() - return first node
+ * @t:          tree to operate on
+ *
+ * An RB-Tree always defines a linear order of its elements. This function
+ * returns the logically first node in @t. If @t is empty, NULL is returned.
+ *
+ * Fixed runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to first node, or NULL.
+ */
+_public_ CRBNode *c_rbtree_first(CRBTree *t) {
+        assert(t);
+        return c_rbnode_leftmost(t->root);
+}
+
+/**
+ * c_rbtree_last() - return last node
+ * @t:          tree to operate on
+ *
+ * An RB-Tree always defines a linear order of its elements. This function
+ * returns the logically last node in @t. If @t is empty, NULL is returned.
+ *
+ * Fixed runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to last node, or NULL.
+ */
+_public_ CRBNode *c_rbtree_last(CRBTree *t) {
+        assert(t);
+        return c_rbnode_rightmost(t->root);
+}
+
+/**
+ * c_rbtree_first_postorder() - return first node in post-order
+ * @t:          tree to operate on
+ *
+ * This returns the first node of a left-to-right post-order traversal. That
+ * is, it returns the left-deepest leaf. If the tree is empty, this returns
+ * NULL.
+ *
+ * This can also be interpreted as the last node of a right-to-left pre-order
+ * traversal.
+ *
+ * Fixed runtime (n: number of elements in tree): O(log(n))
+ *
+ * Return: Pointer to first node in post-order, or NULL.
+ */
+_public_ CRBNode *c_rbtree_first_postorder(CRBTree *t) {
+        assert(t);
+        return c_rbnode_leftdeepest(t->root);
+}
+
+/**
+ * c_rbtree_last_postorder() - return last node in post-order
+ * @t:          tree to operate on
+ *
+ * This returns the last node of a left-to-right post-order traversal. That is,
+ * it always returns the root node, or NULL if the tree is empty.
+ *
+ * This can also be interpreted as the first node of a right-to-left pre-order
+ * traversal.
+ *
+ * Fixed runtime (n: number of elements in tree): O(1)
+ *
+ * Return: Pointer to last node in post-order, or NULL.
+ */
+_public_ CRBNode *c_rbtree_last_postorder(CRBTree *t) {
+        assert(t);
+        return t->root;
+}
+
+static inline void c_rbtree_store(CRBNode **ptr, CRBNode *addr) {
+        /*
+         * We use volatile accesses whenever we STORE @left or @right members
+         * of a node. This guarantees that any parallel, lockless lookup gets
+         * to see those stores in the correct order, which itself guarantees
+         * that there're no temporary loops during tree rotation.
+         * Note that you still need to properly synchronize your accesses via
+         * seqlocks, rcu, whatever. We just guarantee that you get *some*
+         * result on a lockless traversal and never run into endless loops, or
+         * undefined behavior.
+         */
+        *(volatile CRBNode **)ptr = addr;
+}
+
+/*
+ * Set the flags and parent of a node. This should be treated as a simple
+ * assignment of the 'flags' and 'parent' fields of the node. No other magic is
+ * applied. But since both fields share its backing memory, this helper
+ * function is provided.
+ */
+static inline void c_rbnode_set_parent_and_flags(CRBNode *n, CRBNode *p, unsigned long flags) {
+        n->__parent_and_flags = (unsigned long)p | flags;
+}
+
+/*
+ * Nodes in the tree do not separately store a point to the tree root. That is,
+ * there is no way to access the tree-root in O(1) given an arbitrary node.
+ * Fortunately, this is usually not required. The only situation where this is
+ * needed is when rotating the root-node itself.
+ *
+ * In case of the root node, c_rbnode_parent() returns NULL. We use this fact
+ * to re-use the parent-pointer storage of the root node to point to the
+ * CRBTree root. This way, we can rotate the root-node (or add/remove it)
+ * without requiring a separate tree-root pointer.
+ *
+ * However, to keep the tree-modification functions simple, we hide this detail
+ * whenever possible. This means, c_rbnode_parent() will continue to return
+ * NULL, and tree modifications will boldly reset the pointer to NULL on
+ * rotation. Hence, the only way to retain this pointer is to call
+ * c_rbnode_pop_root() on a possible root-node before rotating. This returns
+ * NULL if the node in question is not the root node. Otherwise, it returns the
+ * tree-root, and clears the pointer/flag from the node in question. This way,
+ * you can perform tree operations as usual. Afterwards, use
+ * c_rbnode_push_root() to restore the root-pointer on any possible new root.
+ */
+static inline CRBTree *c_rbnode_pop_root(CRBNode *n) {
+        CRBTree *t = NULL;
+
+        if (c_rbnode_is_root(n)) {
+                t = c_rbnode_raw(n);
+                n->__parent_and_flags = c_rbnode_flags(n) & ~C_RBNODE_ROOT;
+        }
+
+        return t;
+}
+
+/* counter-part to c_rbnode_pop_root() */
+static inline CRBTree *c_rbnode_push_root(CRBNode *n, CRBTree *t) {
+        if (t) {
+                if (n)
+                        n->__parent_and_flags = (unsigned long)t
+                                                | c_rbnode_flags(n)
+                                                | C_RBNODE_ROOT;
+                c_rbtree_store(&t->root, n);
+        }
+
+        return NULL;
+}
+
+/*
+ * This function partially swaps a child node with another one. That is, this
+ * function changes the parent of @old to point to @new. That is, you use it
+ * when swapping @old with @new, to update the parent's left/right pointer.
+ * This function does *NOT* perform a full swap, nor does it touch any 'parent'
+ * pointer.
+ *
+ * The sole purpose of this function is to shortcut left/right conditionals
+ * like this:
+ *
+ *     if (old == old->parent->left)
+ *             old->parent->left = new;
+ *     else
+ *             old->parent->right = new;
+ *
+ * That's it! If @old is the root node, this will do nothing. The caller must
+ * employ c_rbnode_pop_root() and c_rbnode_push_root().
+ */
+static inline void c_rbnode_swap_child(CRBNode *old, CRBNode *new) {
+        CRBNode *p = c_rbnode_parent(old);
+
+        if (p) {
+                if (p->left == old)
+                        c_rbtree_store(&p->left, new);
+                else
+                        c_rbtree_store(&p->right, new);
+        }
+}
+
+/**
+ * c_rbtree_move() - move tree
+ * @to:         destination tree
+ * @from:       source tree
+ *
+ * This imports the entire tree from @from into @to. @to must be empty! @from
+ * will be empty afterwards.
+ *
+ * Note that this operates in O(1) time. Only the root-entry is updated to
+ * point to the new tree-root.
+ */
+_public_ void c_rbtree_move(CRBTree *to, CRBTree *from) {
+        CRBTree *t;
+
+        assert(!to->root);
+
+        if (from->root) {
+                t = c_rbnode_pop_root(from->root);
+                assert(t == from);
+
+                to->root = from->root;
+                from->root = NULL;
+
+                c_rbnode_push_root(to->root, to);
+        }
+}
+
+static inline void c_rbtree_paint_terminal(CRBNode *n) {
+        CRBNode *p, *g, *gg, *x;
+        CRBTree *t;
+
+        /*
+         * Case 4:
+         * This path assumes @n is red, @p is red, but the uncle is unset or
+         * black. This implies @g exists and is black.
+         *
+         * This case requires up to 2 rotations to restore the tree invariants.
+         * That is, it runs in O(1) time and fully restores the RB-Tree
+         * invariants, all at the cost of performing at mots 2 rotations.
+         */
+
+        p = c_rbnode_parent(n);
+        g = c_rbnode_parent(p);
+        gg = c_rbnode_parent(g);
+
+        assert(c_rbnode_is_red(p));
+        assert(c_rbnode_is_black(g));
+        assert(p == g->left || !g->left || c_rbnode_is_black(g->left));
+        assert(p == g->right || !g->right || c_rbnode_is_black(g->right));
+
+        if (p == g->left) {
+                if (n == p->right) {
+                        /*
+                         * We're the right red child of a red parent, which is
+                         * a left child. Rotate on parent and consider us to be
+                         * the old parent and the old parent to be us, making us
+                         * the left child instead of the right child so we can
+                         * handle it the same as below. Rotating two red nodes
+                         * changes none of the invariants.
+                         */
+                        x = n->left;
+                        c_rbtree_store(&p->right, x);
+                        c_rbtree_store(&n->left, p);
+                        if (x)
+                                c_rbnode_set_parent_and_flags(x, p, c_rbnode_flags(x));
+                        c_rbnode_set_parent_and_flags(p, n, c_rbnode_flags(p));
+                        p = n;
+                }
+
+                /* 'n' is invalid from here on! */
+
+                /*
+                 * We're the red left child of a red parent, black grandparent
+                 * and uncle. Rotate parent on grandparent and switch their
+                 * colors, making the parent black and the grandparent red. The
+                 * root of this subtree was changed from the grandparent to the
+                 * parent, but the color remained black, so the number of black
+                 * nodes on each path stays the same. However, we got rid of
+                 * the double red path as we are still the (red) child of the
+                 * parent, which has now turned black. Note that had we been
+                 * the right child, rather than the left child, we would now be
+                 * the left child of the old grandparent, and we would still
+                 * have a double red path. As the new grandparent remains
+                 * black, we're done.
+                 */
+                x = p->right;
+                t = c_rbnode_pop_root(g);
+                c_rbtree_store(&g->left, x);
+                c_rbtree_store(&p->right, g);
+                c_rbnode_swap_child(g, p);
+                if (x)
+                        c_rbnode_set_parent_and_flags(x, g, c_rbnode_flags(x) & ~C_RBNODE_RED);
+                c_rbnode_set_parent_and_flags(p, gg, c_rbnode_flags(p) & ~C_RBNODE_RED);
+                c_rbnode_set_parent_and_flags(g, p, c_rbnode_flags(g) | C_RBNODE_RED);
+                c_rbnode_push_root(p, t);
+        } else /* if (p == g->right) */ { /* same as above, but mirrored */
+                if (n == p->left) {
+                        x = n->right;
+                        c_rbtree_store(&p->left, n->right);
+                        c_rbtree_store(&n->right, p);
+                        if (x)
+                                c_rbnode_set_parent_and_flags(x, p, c_rbnode_flags(x));
+                        c_rbnode_set_parent_and_flags(p, n, c_rbnode_flags(p));
+                        p = n;
+                }
+
+                x = p->left;
+                t = c_rbnode_pop_root(g);
+                c_rbtree_store(&g->right, x);
+                c_rbtree_store(&p->left, g);
+                c_rbnode_swap_child(g, p);
+                if (x)
+                        c_rbnode_set_parent_and_flags(x, g, c_rbnode_flags(x) & ~C_RBNODE_RED);
+                c_rbnode_set_parent_and_flags(p, gg, c_rbnode_flags(p) & ~C_RBNODE_RED);
+                c_rbnode_set_parent_and_flags(g, p, c_rbnode_flags(g) | C_RBNODE_RED);
+                c_rbnode_push_root(p, t);
+        }
+}
+
+static inline CRBNode *c_rbtree_paint_path(CRBNode *n) {
+        CRBNode *p, *g, *u;
+
+        for (;;) {
+                p = c_rbnode_parent(n);
+                if (!p) {
+                        /*
+                         * Case 1:
+                         * We reached the root. Mark it black and be done. As
+                         * all leaf-paths share the root, the ratio of black
+                         * nodes on each path stays the same.
+                         */
+                        c_rbnode_set_parent_and_flags(n, c_rbnode_raw(n), c_rbnode_flags(n) & ~C_RBNODE_RED);
+                        return NULL;
+                } else if (c_rbnode_is_black(p)) {
+                        /*
+                         * Case 2:
+                         * The parent is already black. As our node is red, we
+                         * did not change the number of black nodes on any
+                         * path, nor do we have multiple consecutive red nodes.
+                         * There is nothing to be done.
+                         */
+                        return NULL;
+                }
+
+                g = c_rbnode_parent(p);
+                u = (p == g->left) ? g->right : g->left;
+                if (!u || !c_rbnode_is_red(u)) {
+                        /*
+                         * Case 4:
+                         * The parent is red, but its uncle is black. By
+                         * rotating the parent above the uncle, we distribute
+                         * the red nodes and thus restore the tree invariants.
+                         * No recursive fixup will be needed afterwards. Hence,
+                         * just let the caller know about @n and make them do
+                         * the rotations.
+                         */
+                        return n;
+                }
+
+                /*
+                 * Case 3:
+                 * Parent and uncle are both red, and grandparent is black.
+                 * Repaint parent and uncle black, the grandparent red and
+                 * recurse into the grandparent. Note that this is the only
+                 * recursive case. That is, this step restores the tree
+                 * invariants for the sub-tree below @p (including @n), but
+                 * needs to continue the re-coloring two levels up.
+                 */
+                c_rbnode_set_parent_and_flags(p, g, c_rbnode_flags(p) & ~C_RBNODE_RED);
+                c_rbnode_set_parent_and_flags(u, g, c_rbnode_flags(u) & ~C_RBNODE_RED);
+                c_rbnode_set_parent_and_flags(g, c_rbnode_raw(g), c_rbnode_flags(g) | C_RBNODE_RED);
+                n = g;
+        }
+}
+
+static inline void c_rbtree_paint(CRBNode *n) {
+        /*
+         * When a new node is inserted into an RB-Tree, we always link it as a
+         * tail-node and paint it red. This way, the node will not violate the
+         * rb-tree invariants regarding the number of black nodes on all paths.
+         *
+         * However, a red node must never have another bordering red-node (ie.,
+         * child or parent). Since the node is newly linked, it does not have
+         * any children. Therefore, all we need to do is fix the path upwards
+         * through all parents until we hit a black parent or can otherwise fix
+         * the coloring.
+         *
+         * This function first walks up the path from @n towards the tree root
+         * (done in c_rbtree_paint_path()). This recolors its parent/uncle, if
+         * possible, until it hits a sub-tree that cannot be fixed via
+         * re-coloring. After c_rbtree_paint_path() returns, there are two
+         * possible outcomes:
+         *
+         *         1) @n is NULL, in which case the tree invariants were
+         *            restored by mere recoloring. Nothing is to be done.
+         *
+         *         2) @n is non-NULL, but points to a red ancestor of the
+         *            original node. In this case we need to restore the tree
+         *            invariants via a simple left or right rotation. This will
+         *            be done by c_rbtree_paint_terminal().
+         *
+         * As a summary, this function runs O(log(n)) re-coloring operations in
+         * the worst case, followed by O(1) rotations as final restoration. The
+         * amortized cost, however, is O(1), since re-coloring only recurses
+         * upwards if it hits a red uncle (which can only happen if a previous
+         * operation terminated its operation on that layer).
+         * While amortized painting of inserted nodes is O(1), finding the
+         * correct spot to link the node (before painting it) still requires a
+         * search in the binary tree in O(log(n)).
+         */
+        n = c_rbtree_paint_path(n);
+        if (n)
+                c_rbtree_paint_terminal(n);
+}
+
+/**
+ * c_rbnode_link() - link node into tree
+ * @p:          parent node to link under
+ * @l:          left/right slot of @p to link at
+ * @n:          node to add
+ *
+ * This links @n into an tree underneath another node. The caller must provide
+ * the exact spot where to link the node. That is, the caller must traverse the
+ * tree based on their search order. Once they hit a leaf where to insert the
+ * node, call this function to link it and rebalance the tree.
+ *
+ * For this to work, the caller must provide a pointer to the parent node. If
+ * the tree might be empty, you must resort to c_rbtree_add().
+ *
+ * In most cases you are better off using c_rbtree_add(). See there for details
+ * how tree-insertion works.
+ */
+_public_ void c_rbnode_link(CRBNode *p, CRBNode **l, CRBNode *n) {
+        assert(p);
+        assert(l);
+        assert(n);
+        assert(l == &p->left || l == &p->right);
+
+        c_rbnode_set_parent_and_flags(n, p, C_RBNODE_RED);
+        c_rbtree_store(&n->left, NULL);
+        c_rbtree_store(&n->right, NULL);
+        c_rbtree_store(l, n);
+
+        c_rbtree_paint(n);
+}
+
+/**
+ * c_rbtree_add() - add node to tree
+ * @t:          tree to operate one
+ * @p:          parent node to link under, or NULL
+ * @l:          left/right slot of @p (or root) to link at
+ * @n:          node to add
+ *
+ * This links @n into the tree given as @t. The caller must provide the exact
+ * spot where to link the node. That is, the caller must traverse the tree
+ * based on their search order. Once they hit a leaf where to insert the node,
+ * call this function to link it and rebalance the tree.
+ *
+ * A typical insertion would look like this (@t is your tree, @n is your node):
+ *
+ *        CRBNode **i, *p;
+ *
+ *        i = &t->root;
+ *        p = NULL;
+ *        while (*i) {
+ *                p = *i;
+ *                if (compare(n, *i) < 0)
+ *                        i = &(*i)->left;
+ *                else
+ *                        i = &(*i)->right;
+ *        }
+ *
+ *        c_rbtree_add(t, p, i, n);
+ *
+ * Once the node is linked into the tree, a simple lookup on the same tree can
+ * be coded like this:
+ *
+ *        CRBNode *i;
+ *
+ *        i = t->root;
+ *        while (i) {
+ *                int v = compare(n, i);
+ *                if (v < 0)
+ *                        i = (*i)->left;
+ *                else if (v > 0)
+ *                        i = (*i)->right;
+ *                else
+ *                        break;
+ *        }
+ *
+ * When you add nodes to a tree, the memory contents of the node do not matter.
+ * That is, there is no need to initialize the node via c_rbnode_init().
+ * However, if you relink nodes multiple times during their lifetime, it is
+ * usually very convenient to use c_rbnode_init() and c_rbnode_unlink() (rather
+ * than c_rbnode_unlink_stale()). In those cases, you should validate that a
+ * node is unlinked before you call c_rbtree_add().
+ */
+_public_ void c_rbtree_add(CRBTree *t, CRBNode *p, CRBNode **l, CRBNode *n) {
+        assert(t);
+        assert(l);
+        assert(n);
+        assert(!p || l == &p->left || l == &p->right);
+        assert(p || l == &t->root);
+
+        c_rbnode_set_parent_and_flags(n, p, C_RBNODE_RED);
+        c_rbtree_store(&n->left, NULL);
+        c_rbtree_store(&n->right, NULL);
+
+        if (p)
+                c_rbtree_store(l, n);
+        else
+                c_rbnode_push_root(n, t);
+
+        c_rbtree_paint(n);
+}
+
+static inline void c_rbnode_rebalance_terminal(CRBNode *p, CRBNode *previous) {
+        CRBNode *s, *x, *y, *g;
+        CRBTree *t;
+
+        if (previous == p->left) {
+                s = p->right;
+                if (c_rbnode_is_red(s)) {
+                        /*
+                         * Case 2:
+                         * We have a red node as sibling. Rotate it onto our
+                         * side so we can later on turn it black. This way, we
+                         * gain the additional black node in our path.
+                         */
+                        t = c_rbnode_pop_root(p);
+                        g = c_rbnode_parent(p);
+                        x = s->left;
+                        c_rbtree_store(&p->right, x);
+                        c_rbtree_store(&s->left, p);
+                        c_rbnode_swap_child(p, s);
+                        c_rbnode_set_parent_and_flags(x, p, c_rbnode_flags(x) & ~C_RBNODE_RED);
+                        c_rbnode_set_parent_and_flags(s, g, c_rbnode_flags(s) & ~C_RBNODE_RED);
+                        c_rbnode_set_parent_and_flags(p, s, c_rbnode_flags(p) | C_RBNODE_RED);
+                        c_rbnode_push_root(s, t);
+                        s = x;
+                }
+
+                x = s->right;
+                if (!x || c_rbnode_is_black(x)) {
+                        y = s->left;
+                        if (!y || c_rbnode_is_black(y)) {
+                                /*
+                                 * Case 3+4:
+                                 * Our sibling is black and has only black
+                                 * children. Flip it red and turn parent black.
+                                 * This way we gained a black node in our path.
+                                 * Note that the parent must be red, otherwise
+                                 * it must have been handled by our caller.
+                                 */
+                                assert(c_rbnode_is_red(p));
+                                c_rbnode_set_parent_and_flags(s, p, c_rbnode_flags(s) | C_RBNODE_RED);
+                                c_rbnode_set_parent_and_flags(p, c_rbnode_parent(p), c_rbnode_flags(p) & ~C_RBNODE_RED);
+                                return;
+                        }
+
+                        /*
+                         * Case 5:
+                         * Left child of our sibling is red, right one is black.
+                         * Rotate on parent so the right child of our sibling is
+                         * now red, and we can fall through to case 6.
+                         */
+                        x = y->right;
+                        c_rbtree_store(&s->left, y->right);
+                        c_rbtree_store(&y->right, s);
+                        c_rbtree_store(&p->right, y);
+                        if (x)
+                                c_rbnode_set_parent_and_flags(x, s, c_rbnode_flags(x) & ~C_RBNODE_RED);
+                        x = s;
+                        s = y;
+                }
+
+                /*
+                 * Case 6:
+                 * The right child of our sibling is red. Rotate left and flip
+                 * colors, which gains us an additional black node in our path,
+                 * that was previously on our sibling.
+                 */
+                t = c_rbnode_pop_root(p);
+                g = c_rbnode_parent(p);
+                y = s->left;
+                c_rbtree_store(&p->right, y);
+                c_rbtree_store(&s->left, p);
+                c_rbnode_swap_child(p, s);
+                c_rbnode_set_parent_and_flags(x, s, c_rbnode_flags(x) & ~C_RBNODE_RED);
+                if (y)
+                        c_rbnode_set_parent_and_flags(y, p, c_rbnode_flags(y));
+                c_rbnode_set_parent_and_flags(s, g, c_rbnode_flags(p));
+                c_rbnode_set_parent_and_flags(p, s, c_rbnode_flags(p) & ~C_RBNODE_RED);
+                c_rbnode_push_root(s, t);
+        } else /* if (previous == p->right) */ { /* same as above, but mirrored */
+                s = p->left;
+                if (c_rbnode_is_red(s)) {
+                        t = c_rbnode_pop_root(p);
+                        g = c_rbnode_parent(p);
+                        x = s->right;
+                        c_rbtree_store(&p->left, x);
+                        c_rbtree_store(&s->right, p);
+                        c_rbnode_swap_child(p, s);
+                        c_rbnode_set_parent_and_flags(x, p, c_rbnode_flags(x) & ~C_RBNODE_RED);
+                        c_rbnode_set_parent_and_flags(s, g, c_rbnode_flags(s) & ~C_RBNODE_RED);
+                        c_rbnode_set_parent_and_flags(p, s, c_rbnode_flags(p) | C_RBNODE_RED);
+                        c_rbnode_push_root(s, t);
+                        s = x;
+                }
+
+                x = s->left;
+                if (!x || c_rbnode_is_black(x)) {
+                        y = s->right;
+                        if (!y || c_rbnode_is_black(y)) {
+                                assert(c_rbnode_is_red(p));
+                                c_rbnode_set_parent_and_flags(s, p, c_rbnode_flags(s) | C_RBNODE_RED);
+                                c_rbnode_set_parent_and_flags(p, c_rbnode_parent(p), c_rbnode_flags(p) & ~C_RBNODE_RED);
+                                return;
+                        }
+
+                        x = y->left;
+                        c_rbtree_store(&s->right, y->left);
+                        c_rbtree_store(&y->left, s);
+                        c_rbtree_store(&p->left, y);
+                        if (x)
+                                c_rbnode_set_parent_and_flags(x, s, c_rbnode_flags(x) & ~C_RBNODE_RED);
+                        x = s;
+                        s = y;
+                }
+
+                t = c_rbnode_pop_root(p);
+                g = c_rbnode_parent(p);
+                y = s->right;
+                c_rbtree_store(&p->left, y);
+                c_rbtree_store(&s->right, p);
+                c_rbnode_swap_child(p, s);
+                c_rbnode_set_parent_and_flags(x, s, c_rbnode_flags(x) & ~C_RBNODE_RED);
+                if (y)
+                        c_rbnode_set_parent_and_flags(y, p, c_rbnode_flags(y));
+                c_rbnode_set_parent_and_flags(s, g, c_rbnode_flags(p));
+                c_rbnode_set_parent_and_flags(p, s, c_rbnode_flags(p) & ~C_RBNODE_RED);
+                c_rbnode_push_root(s, t);
+        }
+}
+
+static inline CRBNode *c_rbnode_rebalance_path(CRBNode *p, CRBNode **previous) {
+        CRBNode *s, *nl, *nr;
+
+        while (p) {
+                s = (*previous == p->left) ? p->right : p->left;
+                nl = s->left;
+                nr = s->right;
+
+                /*
+                 * If the sibling under @p is black and exclusively has black
+                 * children itself (i.e., nephews/nieces in @nl/@nr), then we
+                 * can easily re-color to fix this sub-tree, and continue one
+                 * layer up. However, if that's not the case, we have tree
+                 * rotations at our hands to move one of the black nodes into
+                 * our path, then turning the red node black to fully restore
+                 * the RB-Tree invariants again. This fixup will be done by the
+                 * caller, so we just let them know where to do that.
+                 */
+                if (c_rbnode_is_red(s) ||
+                    (nl && c_rbnode_is_red(nl)) ||
+                    (nr && c_rbnode_is_red(nr)))
+                        return p;
+
+                /*
+                 * Case 3+4:
+                 * Sibling is black, and all nephews/nieces are black. Flip
+                 * sibling red. This way the sibling lost a black node in its
+                 * path, thus getting even with our path. However, paths not
+                 * going through @p haven't been fixed up, hence we proceed
+                 * recursively one layer up.
+                 * Before we continue one layer up, there are two possible
+                 * terminations: If the parent is red, we can turn it black.
+                 * This terminates the rebalancing, since the entire point of
+                 * rebalancing is that everything below @p has one black node
+                 * less than everything else. Lastly, if there is no layer
+                 * above, we hit the tree root and nothing is left to be done.
+                 */
+                c_rbnode_set_parent_and_flags(s, p, c_rbnode_flags(s) | C_RBNODE_RED);
+                if (c_rbnode_is_red(p)) {
+                        c_rbnode_set_parent_and_flags(p, c_rbnode_parent(p), c_rbnode_flags(p) & ~C_RBNODE_RED);
+                        return NULL;
+                }
+
+                *previous = p;
+                p = c_rbnode_parent(p);
+        }
+
+        return NULL;
+}
+
+static inline void c_rbnode_rebalance(CRBNode *n) {
+        CRBNode *previous = NULL;
+
+        /*
+         * Rebalance a tree after a node was removed. This function must be
+         * called on the parent of the leaf that was removed. It will first
+         * perform a recursive re-coloring on the parents of @n, until it
+         * either hits the tree-root, or a condition where a tree-rotation is
+         * needed to restore the RB-Tree invariants.
+         */
+
+        n = c_rbnode_rebalance_path(n, &previous);
+        if (n)
+                c_rbnode_rebalance_terminal(n, previous);
+}
+
+/**
+ * c_rbnode_unlink_stale() - remove node from tree
+ * @n:          node to remove
+ *
+ * This removes the given node from its tree. Once unlinked, the tree is
+ * rebalanced.
+ *
+ * This does *NOT* reset @n to being unlinked. If you need this, use
+ * c_rbtree_unlink().
+ */
+_public_ void c_rbnode_unlink_stale(CRBNode *n) {
+        CRBTree *t;
+
+        assert(n);
+        assert(c_rbnode_is_linked(n));
+
+        /*
+         * There are three distinct cases during node removal of a tree:
+         *  * The node has no children, in which case it can simply be removed.
+         *  * The node has exactly one child, in which case the child displaces
+         *    its parent.
+         *  * The node has two children, in which case there is guaranteed to
+         *    be a successor to the node (successor being the node ordered
+         *    directly after it). This successor is the leftmost descendant of
+         *    the node's right child, so it cannot have a left child of its own.
+         *    Therefore, we can simply swap the node with its successor (including
+         *    color) and remove the node from its new place, which will be one of
+         *    the first two cases.
+         *
+         * Whenever the node we removed was black, we have to rebalance the
+         * tree. Note that this affects the actual node we _remove_, not @n (in
+         * case we swap it).
+         */
+
+        if (!n->left && !n->right) {
+                /*
+                 * Case 1.0
+                 * The node has no children, it is a leaf-node and we
+                 * can simply unlink it. If it was also black, we have
+                 * to rebalance.
+                 */
+                t = c_rbnode_pop_root(n);
+                c_rbnode_swap_child(n, NULL);
+                c_rbnode_push_root(NULL, t);
+
+                if (c_rbnode_is_black(n))
+                        c_rbnode_rebalance(c_rbnode_parent(n));
+        } else if (!n->left && n->right) {
+                /*
+                 * Case 1.1:
+                 * The node has exactly one child, and it is on the
+                 * right. The child *must* be red (otherwise, the right
+                 * path has more black nodes than the non-existing left
+                 * path), and the node to be removed must hence be
+                 * black. We simply replace the node with its child,
+                 * turning the red child black, and thus no rebalancing
+                 * is required.
+                 */
+                t = c_rbnode_pop_root(n);
+                c_rbnode_swap_child(n, n->right);
+                c_rbnode_set_parent_and_flags(n->right, c_rbnode_parent(n), c_rbnode_flags(n->right) & ~C_RBNODE_RED);
+                c_rbnode_push_root(n->right, t);
+        } else if (n->left && !n->right) {
+                /*
+                 * Case 1.2:
+                 * The node has exactly one child, and it is on the left. Treat
+                 * it as mirrored case of Case 1.1 (i.e., replace the node by
+                 * its child).
+                 */
+                t = c_rbnode_pop_root(n);
+                c_rbnode_swap_child(n, n->left);
+                c_rbnode_set_parent_and_flags(n->left, c_rbnode_parent(n), c_rbnode_flags(n->left) & ~C_RBNODE_RED);
+                c_rbnode_push_root(n->left, t);
+        } else /* if (n->left && n->right) */ {
+                CRBNode *s, *p, *c, *next = NULL;
+
+                /* Cache possible tree-root during tree-rotations. */
+                t = c_rbnode_pop_root(n);
+
+                /*
+                 * Case 1.3:
+                 * We are dealing with a full interior node with a child on
+                 * both sides. We want to find its successor and swap it,
+                 * then remove the node similar to Case 1. For performance
+                 * reasons we don't perform the full swap, but skip links
+                 * that are about to be removed, anyway.
+                 *
+                 * First locate the successor, remember its child and the
+                 * parent the original node should have been linked on,
+                 * before being removed. Then link up both the successor's
+                 * new children and old child.
+                 *
+                 *      s: successor
+                 *      p: parent
+                 *      c: right (and only potential) child of successor
+                 *      next: next node to rebalance on
+                 */
+                s = n->right;
+                if (!s->left) {
+                        /*
+                         * The immediate right child is the successor,
+                         * the successor's right child remains linked
+                         * as before.
+                         */
+                        p = s;
+                        c = s->right;
+                } else {
+                        s = c_rbnode_leftmost(s);
+                        p = c_rbnode_parent(s);
+                        c = s->right;
+
+                        /*
+                         * The new parent pointer of the successor's
+                         * child is set below.
+                         */
+                        c_rbtree_store(&p->left, c);
+
+                        c_rbtree_store(&s->right, n->right);
+                        c_rbnode_set_parent_and_flags(n->right, s, c_rbnode_flags(n->right));
+                }
+
+                /*
+                 * In both the above cases, the successor's left child
+                 * needs to be replaced with the left child of the node
+                 * that is being removed.
+                 */
+                c_rbtree_store(&s->left, n->left);
+                c_rbnode_set_parent_and_flags(n->left, s, c_rbnode_flags(n->left));
+
+                /*
+                 * As in cases 1.1 and 1.0 above, if successor was a
+                 * black leaf, we need to rebalance the tree, otherwise
+                 * it must have a red child, so simply recolor that black
+                 * and continue. Note that @next must be stored here, as
+                 * the original color of the successor is forgotten below.
+                 */
+                if (c)
+                        c_rbnode_set_parent_and_flags(c, p, c_rbnode_flags(c) & ~C_RBNODE_RED);
+                else
+                        next = c_rbnode_is_black(s) ? p : NULL;
+
+                /*
+                 * Update the successor, to inherit the parent and color
+                 * from the node being removed.
+                 */
+                if (c_rbnode_is_red(n))
+                        c_rbnode_set_parent_and_flags(s, c_rbnode_parent(n), c_rbnode_flags(s) | C_RBNODE_RED);
+                else
+                        c_rbnode_set_parent_and_flags(s, c_rbnode_parent(n), c_rbnode_flags(s) & ~C_RBNODE_RED);
+
+                /*
+                 * Update the parent of the node being removed. Note that this
+                 * needs to happen after the parent of the successor is set
+                 * above, as that call would clear the root pointer, if set.
+                 */
+                c_rbnode_swap_child(n, s);
+
+                /* Possibly restore saved tree-root. */
+                c_rbnode_push_root(s, t);
+
+                if (next)
+                        c_rbnode_rebalance(next);
+        }
+}
diff --git a/shared/c-rbtree/src/c-rbtree.h b/shared/c-rbtree/src/c-rbtree.h
new file mode 100644
index 00000000..cb33fcf7
--- /dev/null
+++ b/shared/c-rbtree/src/c-rbtree.h
@@ -0,0 +1,430 @@
+#pragma once
+
+/**
+ * Standalone Red-Black-Tree Implementation in Standard ISO-C11
+ *
+ * This library provides an RB-Tree API, that is fully implemented in ISO-C11
+ * and has no external dependencies. Furthermore, tree traversal, memory
+ * allocations, and key comparisons are completely controlled by the API user.
+ * The implementation only provides the RB-Tree specific rebalancing and
+ * coloring.
+ *
+ * A tree is represented by the "CRBTree" structure. It contains a *single*
+ * field, which is a pointer to the root node. If NULL, the tree is empty. If
+ * non-NULL, there is at least a single element in the tree.
+ *
+ * Each node of the tree is represented by the "CRBNode" structure. It has
+ * three fields. The @left and @right members can be accessed by the API user
+ * directly to traverse the tree. The third member is a combination of the
+ * parent pointer and a set of flags.
+ * API users are required to embed the CRBNode object into their own objects
+ * and then use offsetof() (i.e., container_of() and friends) to turn CRBNode
+ * pointers into pointers to their own structure.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <assert.h>
+#include <stdalign.h>
+#include <stddef.h>
+
+typedef struct CRBNode CRBNode;
+typedef struct CRBTree CRBTree;
+
+/* implementation detail */
+#define C_RBNODE_RED                    (0x1UL)
+#define C_RBNODE_ROOT                   (0x2UL)
+#define C_RBNODE_UNUSED3                (0x4UL)
+#define C_RBNODE_FLAG_MASK              (0x7UL)
+
+/**
+ * struct CRBNode - Node of a Red-Black Tree
+ * @__parent_and_flags:         internal state
+ * @left:                       left child, or NULL
+ * @right:                      right child, or NULL
+ *
+ * Each node in an RB-Tree must embed a CRBNode object. This object contains
+ * pointers to its left and right child, which can be freely accessed by the
+ * API user at any time. They are NULL, if the node does not have a left/right
+ * child.
+ *
+ * The @__parent_and_flags field must never be accessed directly. It encodes
+ * the pointer to the parent node, and the color of the node. Use the accessor
+ * functions instead.
+ *
+ * There is no reason to initialize a CRBNode object before linking it.
+ * However, if you need a boolean state that tells you whether the node is
+ * linked or not, you should initialize the node via c_rbnode_init() or
+ * C_RBNODE_INIT.
+ */
+struct CRBNode {
+        alignas(8) unsigned long __parent_and_flags;
+        CRBNode *left;
+        CRBNode *right;
+};
+
+#define C_RBNODE_INIT(_var) { .__parent_and_flags = (unsigned long)&(_var) }
+
+CRBNode *c_rbnode_leftmost(CRBNode *n);
+CRBNode *c_rbnode_rightmost(CRBNode *n);
+CRBNode *c_rbnode_leftdeepest(CRBNode *n);
+CRBNode *c_rbnode_rightdeepest(CRBNode *n);
+CRBNode *c_rbnode_next(CRBNode *n);
+CRBNode *c_rbnode_prev(CRBNode *n);
+CRBNode *c_rbnode_next_postorder(CRBNode *n);
+CRBNode *c_rbnode_prev_postorder(CRBNode *n);
+
+void c_rbnode_link(CRBNode *p, CRBNode **l, CRBNode *n);
+void c_rbnode_unlink_stale(CRBNode *n);
+
+/**
+ * struct CRBTree - Red-Black Tree
+ * @root:       pointer to the root node, or NULL
+ *
+ * Each Red-Black Tree is rooted in an CRBTree object. This object contains a
+ * pointer to the root node of the tree. The API user is free to access the
+ * @root member at any time, and use it to traverse the tree.
+ *
+ * To initialize an RB-Tree, set it to NULL / all zero.
+ */
+struct CRBTree {
+        alignas(8) CRBNode *root;
+};
+
+#define C_RBTREE_INIT {}
+
+CRBNode *c_rbtree_first(CRBTree *t);
+CRBNode *c_rbtree_last(CRBTree *t);
+CRBNode *c_rbtree_first_postorder(CRBTree *t);
+CRBNode *c_rbtree_last_postorder(CRBTree *t);
+
+void c_rbtree_move(CRBTree *to, CRBTree *from);
+void c_rbtree_add(CRBTree *t, CRBNode *p, CRBNode **l, CRBNode *n);
+
+/**
+ * c_rbnode_init() - mark a node as unlinked
+ * @n:          node to operate on
+ *
+ * This marks the node @n as unlinked. The node will be set to a valid state
+ * that can never happen if the node is linked in a tree. Furthermore, this
+ * state is fully known to the implementation, and as such handled gracefully
+ * in all cases.
+ *
+ * You are *NOT* required to call this on your node. c_rbtree_add() can handle
+ * uninitialized nodes just fine. However, calling this allows to use
+ * c_rbnode_is_linked() to check for the state of a node. Furthermore,
+ * iterators and accessors can be called on initialized (yet unlinked) nodes.
+ *
+ * Use the C_RBNODE_INIT macro if you want to initialize static variables.
+ */
+static inline void c_rbnode_init(CRBNode *n) {
+        *n = (CRBNode)C_RBNODE_INIT(*n);
+}
+
+/**
+ * c_rbnode_entry() - get parent container of tree node
+ * @_what:              tree node, or NULL
+ * @_t:                 type of parent container
+ * @_m:                 member name of tree node in @_t
+ *
+ * If the tree node @_what is embedded into a surrounding structure, this will
+ * turn the tree node pointer @_what into a pointer to the parent container
+ * (using offsetof(3), or sometimes called container_of(3)).
+ *
+ * If @_what is NULL, this will also return NULL.
+ *
+ * Return: Pointer to parent container, or NULL.
+ */
+#define c_rbnode_entry(_what, _t, _m) \
+        ((_t *)(void *)(((unsigned long)(void *)(_what) ?: \
+                         offsetof(_t, _m)) - offsetof(_t, _m)))
+
+/**
+ * c_rbnode_parent() - return parent pointer
+ * @n           node to access
+ *
+ * This returns a pointer to the parent of the given node @n. If @n does not
+ * have a parent, NULL is returned. If @n is not linked, @n itself is returned.
+ *
+ * You should not call this on unlinked or uninitialized nodes! If you do, you
+ * better know its semantics.
+ *
+ * Return: Pointer to parent.
+ */
+static inline CRBNode *c_rbnode_parent(CRBNode *n) {
+        return (n->__parent_and_flags & C_RBNODE_ROOT) ?
+                        NULL :
+                        (void *)(n->__parent_and_flags & ~C_RBNODE_FLAG_MASK);
+}
+
+/**
+ * c_rbnode_is_linked() - check whether a node is linked
+ * @n:          node to check, or NULL
+ *
+ * This checks whether the passed node is linked. If you pass NULL, or if the
+ * node is not linked into a tree, this will return false. Otherwise, this
+ * returns true.
+ *
+ * Note that you must have either linked the node or initialized it, before
+ * calling this function. Never call this function on uninitialized nodes.
+ * Furthermore, removing a node via c_rbnode_unlink_stale() does *NOT* mark the
+ * node as unlinked. You have to call c_rbnode_init() yourself after removal, or
+ * use the c_rbnode_unlink() helper.
+ *
+ * Return: true if the node is linked, false if not.
+ */
+static inline _Bool c_rbnode_is_linked(CRBNode *n) {
+        return n && c_rbnode_parent(n) != n;
+}
+
+/**
+ * c_rbnode_unlink() - safely remove node from tree and reinitialize it
+ * @n:          node to remove, or NULL
+ *
+ * This is almost the same as c_rbnode_unlink_stale(), but extends it slightly, to be
+ * more convenient to use in many cases:
+ *  - if @n is unlinked or NULL, this is a no-op
+ *  - @n is reinitialized after being removed
+ */
+static inline void c_rbnode_unlink(CRBNode *n) {
+        if (c_rbnode_is_linked(n)) {
+                c_rbnode_unlink_stale(n);
+                c_rbnode_init(n);
+        }
+}
+
+/**
+ * c_rbtree_init() - initialize a new RB-Tree
+ * @t:          tree to operate on
+ *
+ * This initializes a new, empty RB-Tree. An RB-Tree must be initialized before
+ * any other functions are called on it. Alternatively, you can zero its memory
+ * or assign C_RBTREE_INIT.
+ */
+static inline void c_rbtree_init(CRBTree *t) {
+        *t = (CRBTree)C_RBTREE_INIT;
+}
+
+/**
+ * c_rbtree_is_empty() - check whether an RB-tree is empty
+ * @t:          tree to operate on
+ *
+ * This checks whether the passed RB-Tree is empty.
+ *
+ * Return: True if tree is empty, false otherwise.
+ */
+static inline _Bool c_rbtree_is_empty(CRBTree *t) {
+        return !t->root;
+}
+
+/**
+ * CRBCompareFunc - compare a node to a key
+ * @t:          tree where the node is linked to
+ * @k:          key to compare
+ * @n:          node to compare
+ *
+ * If you use the tree-traversal helpers (which are optional), you need to
+ * provide this callback so they can compare nodes in a tree to the key you
+ * look for.
+ *
+ * The tree @t is provided as optional context to this callback. The key you
+ * look for is provided as @k, the current node that should be compared to is
+ * provided as @n. This function should work like strcmp(), that is, return <0
+ * if @key orders before @n, 0 if both compare equal, and >0 if it orders after
+ * @n.
+ */
+typedef int (*CRBCompareFunc) (CRBTree *t, void *k, CRBNode *n);
+
+/**
+ * c_rbtree_find_node() - find node
+ * @t:          tree to search through
+ * @f:          comparison function
+ * @k:          key to search for
+ *
+ * This searches through @t for a node that compares equal to @k. The function
+ * @f must be provided by the caller, which is used to compare nodes to @k. See
+ * the documentation of CRBCompareFunc for details.
+ *
+ * If there are multiple entries that compare equal to @k, this will return a
+ * pseudo-randomly picked node. If you need stable lookup functions for trees
+ * where duplicate entries are allowed, you better code your own lookup.
+ *
+ * Return: Pointer to matching node, or NULL.
+ */
+static inline CRBNode *c_rbtree_find_node(CRBTree *t, CRBCompareFunc f, const void *k) {
+        CRBNode *i;
+
+        assert(t);
+        assert(f);
+
+        i = t->root;
+        while (i) {
+                int v = f(t, (void *)k, i);
+                if (v < 0)
+                        i = i->left;
+                else if (v > 0)
+                        i = i->right;
+                else
+                        return i;
+        }
+
+        return NULL;
+}
+
+/**
+ * c_rbtree_find_entry() - find entry
+ * @_t:         tree to search through
+ * @_f:         comparison function
+ * @_k:         key to search for
+ * @_s:         type of the structure that embeds the nodes
+ * @_m:         name of the node-member in type @_t
+ *
+ * This is very similar to c_rbtree_find_node(), but instead of returning a
+ * pointer to the CRBNode, it returns a pointer to the surrounding object. This
+ * object must embed the CRBNode object. The type of the surrounding object
+ * must be given as @_s, and the name of the embedded CRBNode member as @_m.
+ *
+ * See c_rbtree_find_node() and c_rbnode_entry() for more details.
+ *
+ * Return: Pointer to found entry, NULL if not found.
+ */
+#define c_rbtree_find_entry(_t, _f, _k, _s, _m) \
+        c_rbnode_entry(c_rbtree_find_node((_t), (_f), (_k)), _s, _m)
+
+/**
+ * c_rbtree_find_slot() - find slot to insert new node
+ * @t:          tree to search through
+ * @f:          comparison function
+ * @k:          key to search for
+ * @p:          output storage for parent pointer
+ *
+ * This searches through @t just like c_rbtree_find_node() does. However,
+ * instead of returning a pointer to a node that compares equal to @k, this
+ * searches for a slot to insert a node with key @k. A pointer to the slot is
+ * returned, and a pointer to the parent of the slot is stored in @p. Both
+ * can be passed directly to c_rbtree_add(), together with your node to insert.
+ *
+ * If there already is a node in the tree, that compares equal to @k, this will
+ * return NULL and store the conflicting node in @p. In all other cases,
+ * this will return a pointer (non-NULL) to the empty slot to insert the node
+ * at. @p will point to the parent node of that slot.
+ *
+ * If you want trees that allow duplicate nodes, you better code your own
+ * insertion function.
+ *
+ * Return: Pointer to slot to insert node, or NULL on conflicts.
+ */
+static inline CRBNode **c_rbtree_find_slot(CRBTree *t, CRBCompareFunc f, const void *k, CRBNode **p) {
+        CRBNode **i;
+
+        assert(t);
+        assert(f);
+        assert(p);
+
+        i = &t->root;
+        *p = NULL;
+        while (*i) {
+                int v = f(t, (void *)k, *i);
+                *p = *i;
+                if (v < 0)
+                        i = &(*i)->left;
+                else if (v > 0)
+                        i = &(*i)->right;
+                else
+                        return NULL;
+        }
+
+        return i;
+}
+
+/**
+ * c_rbtree_for_each*() - iterators
+ *
+ * The c_rbtree_for_each*() macros provide simple for-loop wrappers to iterate
+ * an RB-Tree. They come in a set of flavours:
+ *
+ *   - "entry": This combines c_rbnode_entry() with the loop iterator, so the
+ *              iterator always has the type of the surrounding object, rather
+ *              than CRBNode.
+ *
+ *   - "safe": The loop iterator always keeps track of the next element to
+ *             visit. This means, you can safely modify the current element,
+ *             while retaining loop-integrity.
+ *             You still must not touch any other entry of the tree. Otherwise,
+ *             the loop-iterator will be corrupted. Also remember to only
+ *             modify the tree in a way compatible with your iterator-order.
+ *             That is, if you use in-order iteration (default), you can unlink
+ *             your current object, including re-balancing the tree. However,
+ *             if you use post-order, you must not trigger a tree rebalance
+ *             operation, since it is not an invariant of post-order iteration.
+ *
+ *   - "postorder": Rather than the default in-order iteration, this iterates
+ *                  the tree in post-order.
+ *
+ *   - "unlink": This unlinks the current element from the tree before the loop
+ *               code is run. Note that the tree is not rebalanced. That is,
+ *               you must never break out of the loop. If you do so, the tree
+ *               is corrupted.
+ */
+
+#define c_rbtree_for_each(_iter, _tree)                                                                 \
+        for (_iter = c_rbtree_first(_tree);                                                             \
+             _iter;                                                                                     \
+             _iter = c_rbnode_next(_iter))
+
+#define c_rbtree_for_each_entry(_iter, _tree, _m)                                                       \
+        for (_iter = c_rbnode_entry(c_rbtree_first(_tree), __typeof__(*_iter), _m);                     \
+             _iter;                                                                                     \
+             _iter = c_rbnode_entry(c_rbnode_next(&_iter->_m), __typeof__(*_iter), _m))
+
+#define c_rbtree_for_each_safe(_iter, _safe, _tree)                                                     \
+        for (_iter = c_rbtree_first(_tree), _safe = c_rbnode_next(_iter);                               \
+             _iter;                                                                                     \
+             _iter = _safe, _safe = c_rbnode_next(_safe))
+
+#define c_rbtree_for_each_entry_safe(_iter, _safe, _tree, _m)                                           \
+        for (_iter = c_rbnode_entry(c_rbtree_first(_tree), __typeof__(*_iter), _m),                     \
+             _safe = _iter ? c_rbnode_entry(c_rbnode_next(&_iter->_m), __typeof__(*_iter), _m) : NULL;  \
+             _iter;                                                                                     \
+             _iter = _safe,                                                                             \
+             _safe = _safe ? c_rbnode_entry(c_rbnode_next(&_safe->_m), __typeof__(*_iter), _m) : NULL)
+
+#define c_rbtree_for_each_postorder(_iter, _tree)                                                       \
+        for (_iter = c_rbtree_first_postorder(_tree);                                                   \
+             _iter;                                                                                     \
+             _iter = c_rbnode_next_postorder(_iter))                                                    \
+
+#define c_rbtree_for_each_entry_postorder(_iter, _tree, _m)                                             \
+        for (_iter = c_rbnode_entry(c_rbtree_first_postorder(_tree), __typeof__(*_iter), _m);           \
+             _iter;                                                                                     \
+             _iter = c_rbnode_entry(c_rbnode_next_postorder(&_iter->_m), __typeof__(*_iter), _m))
+
+#define c_rbtree_for_each_safe_postorder(_iter, _safe, _tree)                                           \
+        for (_iter = c_rbtree_first_postorder(_tree), _safe = c_rbnode_next_postorder(_iter);           \
+             _iter;                                                                                     \
+             _iter = _safe, _safe = c_rbnode_next_postorder(_safe))
+
+#define c_rbtree_for_each_entry_safe_postorder(_iter, _safe, _tree, _m)                                                 \
+        for (_iter = c_rbnode_entry(c_rbtree_first_postorder(_tree), __typeof__(*_iter), _m),                           \
+             _safe = _iter ? c_rbnode_entry(c_rbnode_next_postorder(&_iter->_m), __typeof__(*_iter), _m) : NULL;        \
+             _iter;                                                                                                     \
+             _iter = _safe,                                                                                             \
+             _safe = _safe ? c_rbnode_entry(c_rbnode_next_postorder(&_safe->_m), __typeof__(*_iter), _m) : NULL)
+
+#define c_rbtree_for_each_safe_postorder_unlink(_iter, _safe, _tree)                                    \
+        for (_iter = c_rbtree_first_postorder(_tree), _safe = c_rbnode_next_postorder(_iter);           \
+             _iter ? ((*_iter = (CRBNode)C_RBNODE_INIT(*_iter)), 1) : (((_tree)->root = NULL), 0);      \
+             _iter = _safe, _safe = c_rbnode_next_postorder(_safe))                                     \
+
+#define c_rbtree_for_each_entry_safe_postorder_unlink(_iter, _safe, _tree, _m)                                          \
+        for (_iter = c_rbnode_entry(c_rbtree_first_postorder(_tree), __typeof__(*_iter), _m),                           \
+             _safe = _iter ? c_rbnode_entry(c_rbnode_next_postorder(&_iter->_m), __typeof__(*_iter), _m) : NULL;        \
+             _iter ? ((_iter->_m = (CRBNode)C_RBNODE_INIT(_iter->_m)), 1) : (((_tree)->root = NULL), 0);                \
+             _iter = _safe,                                                                                             \
+             _safe = _safe ? c_rbnode_entry(c_rbnode_next_postorder(&_safe->_m), __typeof__(*_iter), _m) : NULL)
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/shared/c-siphash/src/c-siphash.c b/shared/c-siphash/src/c-siphash.c
index 76b25b86..5cea6f2b 100644
--- a/shared/c-siphash/src/c-siphash.c
+++ b/shared/c-siphash/src/c-siphash.c
@@ -55,7 +55,7 @@ static inline void c_siphash_sipround(CSipHash *state) {
  * @seed:               128bit seed
  *
  * This initializes the siphash state context. Once initialized, it can be used
- * to hash arbitary input. To feed data into it, use c_siphash_append(). To get
+ * to hash arbitrary input. To feed data into it, use c_siphash_append(). To get
  * the final hash, use c_siphash_finalize().
  *
  * Note that the siphash context does not allocate state. There is no need to
@@ -134,7 +134,7 @@ _public_ void c_siphash_append(CSipHash *state, const uint8_t *bytes, size_t n_b
         end -= (state->n_bytes % sizeof(uint64_t));
 
         /*
-         * We are now guaranteed to be at a 64bit state boudary. Hence, we can
+         * We are now guaranteed to be at a 64bit state boundary. Hence, we can
          * operate in 64bit chunks on all input. This is much faster than the
          * one-byte-at-a-time loop.
          */
diff --git a/shared/meson.build b/shared/meson.build
index e1cf620b..a6e94d6b 100644
--- a/shared/meson.build
+++ b/shared/meson.build
@@ -10,14 +10,51 @@ shared_c_siphash_dep = declare_dependency(
     link_with: shared_c_siphash,
 )
 
+shared_c_rbtree = static_library(
+    'c-rbtree',
+    c_args: '-std=c11',
+    sources: files('c-rbtree/src/c-rbtree.c',
+                   'c-rbtree/src/c-rbtree.h',
+                   'c-rbtree/src/c-rbtree-private.h'),
+)
+
+shared_c_rbtree_dep = declare_dependency(
+    include_directories: shared_inc,
+    link_with: shared_c_rbtree,
+)
+
+
+if enable_ebpf
+    shared_n_acd_bpf_files = files('n-acd/src/n-acd-bpf.c')
+else
+    shared_n_acd_bpf_files = files('n-acd/src/n-acd-bpf-fallback.c')
+endif
+
 shared_n_acd = static_library(
     'n-acd',
-    sources: 'n-acd/src/n-acd.c',
+    sources: files('n-acd/src/n-acd.c',
+                   'n-acd/src/n-acd.h',
+                   'n-acd/src/n-acd-private.h',
+                   'n-acd/src/n-acd-probe.c',
+                   'n-acd/src/util/timer.c',
+                   'n-acd/src/util/timer.h')
+             + shared_n_acd_bpf_files,
+    c_args: [
+        '-D_GNU_SOURCE',
+        '-DSO_ATTACH_BPF=50',
+        '-std=c11',
+        '-Wno-pointer-arith',
+        '-Wno-vla',
+    ],
     include_directories: [
         include_directories('c-siphash/src'),
         include_directories('c-list/src'),
+        include_directories('c-rbtree/src'),
+    ],
+    dependencies: [
+        shared_c_siphash_dep,
+        shared_c_rbtree_dep,
     ],
-    dependencies: shared_c_siphash_dep,
 )
 
 shared_n_acd_dep = declare_dependency(
@@ -44,39 +81,172 @@ shared_nm_test_utils_impl_c = files('nm-test-utils-impl.c')
 
 shared_nm_utils_nm_vpn_plugin_utils_c = files('nm-utils/nm-vpn-plugin-utils.c')
 
-shared_files_libnm_core = files('''
-    c-siphash/src/c-siphash.c
-    nm-utils/c-list-util.c
-    nm-utils/nm-dedup-multi.c
-    nm-utils/nm-enum-utils.c
-    nm-utils/nm-hash-utils.c
-    nm-utils/nm-io-utils.c
-    nm-utils/nm-random-utils.c
-    nm-utils/nm-secret-utils.c
-    nm-utils/nm-shared-utils.c
-    nm-utils/nm-udev-utils.c
-'''.split())
-
-shared_files_clients_common = files('''
-    c-siphash/src/c-siphash.c
-    nm-utils/nm-enum-utils.c
-    nm-utils/nm-hash-utils.c
-    nm-utils/nm-random-utils.c
-    nm-utils/nm-shared-utils.c
-'''.split())
-
-shared_files_libnm_util = files('''
-    nm-utils/nm-shared-utils.c
-'''.split())
-
-shared_files_libnm_glib = files('''
-    nm-utils/nm-udev-utils.c
-'''.split())
-
-shared_dep = declare_dependency(
+###############################################################################
+
+shared_nm_utils_c_args = [
+    '-DG_LOG_DOMAIN="@0@"'.format(libnm_name),
+    '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB)',
+]
+
+shared_nm_utils_base = static_library(
+    'nm-utils-base',
+    sources: files('nm-utils/c-list-util.c',
+                   'nm-utils/nm-dedup-multi.c',
+                   'nm-utils/nm-enum-utils.c',
+                   'nm-utils/nm-errno.c',
+                   'nm-utils/nm-hash-utils.c',
+                   'nm-utils/nm-io-utils.c',
+                   'nm-utils/nm-random-utils.c',
+                   'nm-utils/nm-secret-utils.c',
+                   'nm-utils/nm-shared-utils.c',
+                   'nm-utils/nm-time-utils.c'),
+    c_args: shared_nm_utils_c_args,
+    include_directories: [
+        top_inc,
+        shared_inc,
+    ],
+    dependencies: [
+        glib_dep,
+    ],
+)
+
+shared_nm_utils_base_dep = declare_dependency(
+    link_with: shared_nm_utils_base,
     include_directories: [
         top_inc,
         shared_inc,
     ],
     dependencies: glib_dep,
 )
+
+shared_nm_utils_udev = static_library(
+    'nm-utils-udev',
+    sources: files('nm-utils/nm-udev-utils.c'),
+    c_args: shared_nm_utils_c_args,
+    include_directories: [
+        top_inc,
+        shared_inc,
+    ],
+    dependencies: [
+        glib_dep,
+        shared_nm_utils_base_dep,
+        libudev_dep,
+    ],
+)
+
+shared_nm_utils_udev_dep = declare_dependency(
+    link_with: shared_nm_utils_udev,
+    include_directories: [
+        top_inc,
+        shared_inc,
+    ],
+    dependencies: [
+        glib_dep,
+        shared_nm_utils_base_dep,
+        libudev_dep,
+    ],
+)
+
+###############################################################################
+
+test_shared_general = executable(
+  'nm-utils/tests/test-shared-general',
+  [ 'nm-utils/tests/test-shared-general.c', ],
+  c_args: [
+      '-DNETWORKMANAGER_COMPILATION_TEST',
+      '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG)',
+    ],
+  dependencies: shared_nm_utils_base_dep,
+  link_with: shared_c_siphash,
+)
+test(
+  'shared/nm-utils/test-shared-general',
+  test_script,
+  args: test_args + [test_shared_general.full_path()]
+)
+
+###############################################################################
+
+libnm_systemd_shared = static_library(
+  'nm-systemd-shared',
+  sources: files(
+    'systemd/src/basic/alloc-util.c',
+    'systemd/src/basic/escape.c',
+    'systemd/src/basic/env-file.c',
+    'systemd/src/basic/env-util.c',
+    'systemd/src/basic/ether-addr-util.c',
+    'systemd/src/basic/extract-word.c',
+    'systemd/src/basic/fd-util.c',
+    'systemd/src/basic/fileio.c',
+    'systemd/src/basic/fs-util.c',
+    'systemd/src/basic/hash-funcs.c',
+    'systemd/src/basic/hashmap.c',
+    'systemd/src/basic/hexdecoct.c',
+    'systemd/src/basic/hostname-util.c',
+    'systemd/src/basic/in-addr-util.c',
+    'systemd/src/basic/io-util.c',
+    'systemd/src/basic/mempool.c',
+    'systemd/src/basic/parse-util.c',
+    'systemd/src/basic/path-util.c',
+    'systemd/src/basic/prioq.c',
+    'systemd/src/basic/process-util.c',
+    'systemd/src/basic/random-util.c',
+    'systemd/src/basic/socket-util.c',
+    'systemd/src/basic/stat-util.c',
+    'systemd/src/basic/string-table.c',
+    'systemd/src/basic/string-util.c',
+    'systemd/src/basic/strv.c',
+    'systemd/src/basic/time-util.c',
+    'systemd/src/basic/tmpfile-util.c',
+    'systemd/src/basic/utf8.c',
+    'systemd/src/basic/util.c',
+    'systemd/nm-sd-utils-shared.c',
+  ),
+  include_directories: include_directories(
+    'systemd/sd-adapt-shared',
+    'systemd/src/basic',
+  ),
+  dependencies: shared_nm_utils_base_dep,
+  c_args: [
+    '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD_SHARED',
+    '-DG_LOG_DOMAIN="libnm"',
+  ],
+)
+
+libnm_systemd_shared_dep = declare_dependency(
+  include_directories: include_directories(
+    'systemd/sd-adapt-shared',
+    'systemd/src/basic',
+  ),
+  dependencies: [
+    shared_nm_utils_base_dep,
+  ],
+  link_with: [
+    libnm_systemd_shared,
+  ],
+)
+
+libnm_systemd_logging_stub = static_library(
+  'nm-systemd-logging-stub',
+  sources: files(
+    'systemd/nm-logging-stub.c',
+  ),
+  include_directories: include_directories(
+    'systemd/sd-adapt-shared',
+    'systemd/src/basic',
+  ),
+  dependencies: shared_nm_utils_base_dep,
+  c_args: [
+    '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD_SHARED',
+    '-DG_LOG_DOMAIN="libnm"',
+  ],
+)
+
+libnm_systemd_shared_no_logging_dep = declare_dependency(
+  dependencies: [
+    libnm_systemd_shared_dep,
+  ],
+  link_with: [
+    libnm_systemd_logging_stub,
+  ],
+)
diff --git a/shared/n-acd/src/n-acd-bpf-fallback.c b/shared/n-acd/src/n-acd-bpf-fallback.c
new file mode 100644
index 00000000..7270cfd9
--- /dev/null
+++ b/shared/n-acd/src/n-acd-bpf-fallback.c
@@ -0,0 +1,29 @@
+/*
+ * A noop implementation of eBPF filter for IPv4 Address Conflict Detection
+ *
+ * These are a collection of dummy functions that have no effect, but allows
+ * n-acd to compile without eBPF support.
+ *
+ * See n-acd-bpf.c for documentation.
+ */
+
+#include <stddef.h>
+#include "n-acd-private.h"
+
+int n_acd_bpf_map_create(int *mapfdp, size_t max_entries) {
+        *mapfdp = -1;
+        return 0;
+}
+
+int n_acd_bpf_map_add(int mapfd, struct in_addr *addrp) {
+        return 0;
+}
+
+int n_acd_bpf_map_remove(int mapfd, struct in_addr *addrp) {
+        return 0;
+}
+
+int n_acd_bpf_compile(int *progfdp, int mapfd, struct ether_addr *macp) {
+        *progfdp = -1;
+        return 0;
+}
diff --git a/shared/n-acd/src/n-acd-bpf.c b/shared/n-acd/src/n-acd-bpf.c
new file mode 100644
index 00000000..771a28ee
--- /dev/null
+++ b/shared/n-acd/src/n-acd-bpf.c
@@ -0,0 +1,316 @@
+/*
+ * eBPF filter for IPv4 Address Conflict Detection
+ *
+ * An eBPF map and an eBPF program are provided. The map contains all the
+ * addresses address conflict detection is performed on, and the program
+ * filters out all packets except exactly the packets relevant to the ACD
+ * protocol on the addresses currently in the map.
+ *
+ * Note that userspace still has to filter the incoming packets, as filter
+ * are applied when packets are queued on the socket, not when userspace
+ * receives them. It is therefore possible to receive packets about addresses
+ * that have already been removed.
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <linux/bpf.h>
+#include <netinet/if_ether.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include "n-acd-private.h"
+
+#define BPF_LD_ABS(SIZE, IMM)                                                   \
+        ((struct bpf_insn) {                                                    \
+                .code           = BPF_LD | BPF_SIZE(SIZE) | BPF_ABS,            \
+                .dst_reg        = 0,                                            \
+                .src_reg        = 0,                                            \
+                .off            = 0,                                            \
+                .imm            = IMM,                                          \
+        })
+
+#define BPF_LDX_MEM(SIZE, DST, SRC, OFF)                                        \
+        ((struct bpf_insn) {                                                    \
+                .code           = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM,           \
+                .dst_reg        = DST,                                          \
+                .src_reg        = SRC,                                          \
+                .off            = OFF,                                          \
+                .imm            = 0,                                            \
+        })
+
+#define BPF_LD_MAP_FD(DST, MAP_FD)                                              \
+        ((struct bpf_insn) {                                                    \
+                .code           = BPF_LD | BPF_DW | BPF_IMM,                    \
+                .dst_reg        = DST,                                          \
+                .src_reg        = BPF_PSEUDO_MAP_FD,                            \
+                .off            = 0,                                            \
+                .imm            = (__u32) (MAP_FD),                             \
+        }),                                                                     \
+        ((struct bpf_insn) {                                                    \
+                .code           = 0, /* zero is reserved opcode */              \
+                .dst_reg        = 0,                                            \
+                .src_reg        = 0,                                            \
+                .off            = 0,                                            \
+                .imm            = ((__u64) (MAP_FD)) >> 32,                     \
+        })
+
+#define BPF_ALU_REG(OP, DST, SRC)                                               \
+        ((struct bpf_insn) {                                                    \
+                .code           = BPF_ALU64 | BPF_OP(OP) | BPF_X,               \
+                .dst_reg        = DST,                                          \
+                .src_reg        = SRC,                                          \
+                .off            = 0,                                            \
+                .imm            = 0,                                            \
+        })
+
+#define BPF_ALU_IMM(OP, DST, IMM)                                               \
+        ((struct bpf_insn) {                                                    \
+                .code           = BPF_ALU64 | BPF_OP(OP) | BPF_K,               \
+                .dst_reg        = DST,                                          \
+                .src_reg        = 0,                                            \
+                .off            = 0,                                            \
+                .imm            = IMM,                                          \
+        })
+
+#define BPF_MOV_REG(DST, SRC)                                                   \
+        ((struct bpf_insn) {                                                    \
+                .code           = BPF_ALU64 | BPF_MOV | BPF_X,                  \
+                .dst_reg        = DST,                                          \
+                .src_reg        = SRC,                                          \
+                .off            = 0,                                            \
+                .imm            = 0,                                            \
+        })
+
+#define BPF_MOV_IMM(DST, IMM)                                                   \
+        ((struct bpf_insn) {                                                    \
+                .code           = BPF_ALU64 | BPF_MOV | BPF_K,                  \
+                .dst_reg        = DST,                                          \
+                .src_reg        = 0,                                            \
+                .off            = 0,                                            \
+                .imm            = IMM,                                          \
+        })
+
+#define BPF_STX_MEM(SIZE, DST, SRC, OFF)                                        \
+        ((struct bpf_insn) {                                                    \
+                .code           = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM,           \
+                .dst_reg        = DST,                                          \
+                .src_reg        = SRC,                                          \
+                .off            = OFF,                                          \
+                .imm            = 0,                                            \
+        })
+
+#define BPF_JMP_REG(OP, DST, SRC, OFF)                                          \
+        ((struct bpf_insn) {                                                    \
+                .code           = BPF_JMP | BPF_OP(OP) | BPF_X,                 \
+                .dst_reg        = DST,                                          \
+                .src_reg        = SRC,                                          \
+                .off            = OFF,                                          \
+                .imm            = 0,                                            \
+        })
+
+#define BPF_JMP_IMM(OP, DST, IMM, OFF)                                          \
+        ((struct bpf_insn) {                                                    \
+                .code           = BPF_JMP | BPF_OP(OP) | BPF_K,                 \
+                .dst_reg        = DST,                                          \
+                .src_reg        = 0,                                            \
+                .off            = OFF,                                          \
+                .imm            = IMM,                                          \
+        })
+
+#define BPF_EMIT_CALL(FUNC)                                                     \
+        ((struct bpf_insn) {                                                    \
+                .code           = BPF_JMP | BPF_CALL,                           \
+                .dst_reg        = 0,                                            \
+                .src_reg        = 0,                                            \
+                .off            = 0,                                            \
+                .imm            = FUNC,                                         \
+        })
+
+#define BPF_EXIT_INSN()                                                         \
+        ((struct bpf_insn) {                                                    \
+                .code           = BPF_JMP | BPF_EXIT,                           \
+                .dst_reg        = 0,                                            \
+                .src_reg        = 0,                                            \
+                .off            = 0,                                            \
+                .imm            = 0,                                            \
+        })
+
+static int n_acd_syscall_bpf(int cmd, union bpf_attr *attr, unsigned int size) {
+        return (int)syscall(__NR_bpf, cmd, attr, size);
+}
+
+int n_acd_bpf_map_create(int *mapfdp, size_t max_entries) {
+        union bpf_attr attr;
+        int mapfd;
+
+        memset(&attr, 0, sizeof(attr));
+        attr = (union bpf_attr){
+                .map_type    = BPF_MAP_TYPE_HASH,
+                .key_size    = sizeof(uint32_t),
+                .value_size  = sizeof(uint8_t), /* values are never used, but must be set */
+                .max_entries = max_entries,
+        };
+
+        mapfd = n_acd_syscall_bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
+        if (mapfd < 0)
+                return -errno;
+
+        *mapfdp = mapfd;
+        return 0;
+}
+
+int n_acd_bpf_map_add(int mapfd, struct in_addr *addrp) {
+        union bpf_attr attr;
+        uint32_t addr = be32toh(addrp->s_addr);
+        uint8_t _dummy = 0;
+        int r;
+
+        memset(&attr, 0, sizeof(attr));
+        attr = (union bpf_attr){
+                .map_fd = mapfd,
+                .key    = (uint64_t)(unsigned long)&addr,
+                .value  = (uint64_t)(unsigned long)&_dummy,
+                .flags  = BPF_NOEXIST,
+        };
+
+        r = n_acd_syscall_bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
+        if (r < 0)
+                return -errno;
+
+        return 0;
+}
+
+int n_acd_bpf_map_remove(int mapfd, struct in_addr *addrp) {
+        uint32_t addr = be32toh(addrp->s_addr);
+        union bpf_attr attr;
+        int r;
+
+        memset(&attr, 0, sizeof(attr));
+        attr = (union bpf_attr){
+                .map_fd = mapfd,
+                .key    = (uint64_t)(unsigned long)&addr,
+        };
+
+        r = n_acd_syscall_bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr));
+        if (r < 0)
+                return -errno;
+
+        return 0;
+}
+
+int n_acd_bpf_compile(int *progfdp, int mapfd, struct ether_addr *macp) {
+        const union {
+                uint8_t u8[6];
+                uint16_t u16[3];
+                uint32_t u32[1];
+        } mac = {
+                .u8 = {
+                        macp->ether_addr_octet[0],
+                        macp->ether_addr_octet[1],
+                        macp->ether_addr_octet[2],
+                        macp->ether_addr_octet[3],
+                        macp->ether_addr_octet[4],
+                        macp->ether_addr_octet[5],
+                },
+        };
+        struct bpf_insn prog[] = {
+                /* for using BPF_LD_ABS r6 must point to the skb, currently in r1 */
+                BPF_MOV_REG(6, 1),                                              /* r6 = r1 */
+
+                /* drop the packet if it is too short */
+                BPF_LDX_MEM(BPF_W, 0, 6, offsetof(struct __sk_buff, len)),      /* r0 = skb->len */
+                BPF_JMP_IMM(BPF_JGE, 0, sizeof(struct ether_arp), 2),           /* if (r0 >= sizeof(ether_arp)) skip 2 */
+                BPF_MOV_IMM(0, 0),                                              /* r0 = 0 */
+                BPF_EXIT_INSN(),                                                /* return */
+
+                /* drop the packet if the header is not as expected */
+                BPF_LD_ABS(BPF_H, offsetof(struct ether_arp, arp_hrd)),         /* r0 = header type */
+                BPF_JMP_IMM(BPF_JEQ, 0, ARPHRD_ETHER, 2),                       /* if (r0 == ethernet) skip 2 */
+                BPF_MOV_IMM(0, 0),                                              /* r0 = 0 */
+                BPF_EXIT_INSN(),                                                /* return */
+
+                BPF_LD_ABS(BPF_H, offsetof(struct ether_arp, arp_pro)),         /* r0 = protocol */
+                BPF_JMP_IMM(BPF_JEQ, 0, ETHERTYPE_IP, 2),                       /* if (r0 == IP) skip 2 */
+                BPF_MOV_IMM(0, 0),                                              /* r0 = 0 */
+                BPF_EXIT_INSN(),                                                /* return */
+
+                BPF_LD_ABS(BPF_B, offsetof(struct ether_arp, arp_hln)),         /* r0 = hw addr length */
+                BPF_JMP_IMM(BPF_JEQ, 0, sizeof(struct ether_addr), 2),          /* if (r0 == sizeof(ether_addr)) skip 2 */
+                BPF_MOV_IMM(0, 0),                                              /* r0 = 0 */
+                BPF_EXIT_INSN(),                                                /* return */
+
+                BPF_LD_ABS(BPF_B, offsetof(struct ether_arp, arp_pln)),         /* r0 = protocol addr length */
+                BPF_JMP_IMM(BPF_JEQ, 0, sizeof(struct in_addr), 2),             /* if (r0 == sizeof(in_addr)) skip 2 */
+                BPF_MOV_IMM(0, 0),                                              /* r0 = 0 */
+                BPF_EXIT_INSN(),                                                /* return */
+
+                /* drop packets from our own mac address */
+                BPF_LD_ABS(BPF_W, offsetof(struct ether_arp, arp_sha)),         /* r0 = first four bytes of packet mac address */
+                BPF_JMP_IMM(BPF_JNE, 0, be32toh(mac.u32[0]), 4),                /* if (r0 != first four bytes of our mac address) skip 4 */
+                BPF_LD_ABS(BPF_H, offsetof(struct ether_arp, arp_sha) + 4),     /* r0 = last two bytes of packet mac address */
+                BPF_JMP_IMM(BPF_JNE, 0, be16toh(mac.u16[2]), 2),                /* if (r0 != last two bytes of our mac address) skip 2 */
+                BPF_MOV_IMM(0, 0),                                              /* r0 = 0 */
+                BPF_EXIT_INSN(),                                                /* return */
+
+                /*
+                 * We listen for two kinds of packets:
+                 *  Conflicts)
+                 *    These are requests or replies with the sender address not set to INADDR_ANY. The
+                 *    conflicted address is the sender address, remember this in r7.
+                 *  Probes)
+                 *    These are requests with the sender address set to INADDR_ANY. The probed address
+                 *    is the target address, remember this in r7.
+                 *  Any other packets are dropped.
+                 */
+                BPF_LD_ABS(BPF_W, offsetof(struct ether_arp, arp_spa)),         /* r0 = sender ip address */
+                BPF_JMP_IMM(BPF_JEQ, 0, 0, 7),                                  /* if (r0 == 0) skip 7 */
+                BPF_MOV_REG(7, 0),                                              /* r7 = r0 */
+                BPF_LD_ABS(BPF_H, offsetof(struct ether_arp, arp_op)),          /* r0 = operation */
+                BPF_JMP_IMM(BPF_JEQ, 0, ARPOP_REQUEST, 3),                      /* if (r0 == request) skip 3 */
+                BPF_JMP_IMM(BPF_JEQ, 0, ARPOP_REPLY, 2),                        /* if (r0 == reply) skip 2 */
+                BPF_MOV_IMM(0, 0),                                              /* r0 = 0 */
+                BPF_EXIT_INSN(),                                                /* return */
+                BPF_JMP_IMM(BPF_JA, 0, 0, 6),                                   /* skip 6 */
+                BPF_LD_ABS(BPF_W, offsetof(struct ether_arp, arp_tpa)),         /* r0 = target ip address */
+                BPF_MOV_REG(7, 0),                                              /* r7 = r0 */
+                BPF_LD_ABS(BPF_H, offsetof(struct ether_arp, arp_op)),          /* r0 = operation */
+                BPF_JMP_IMM(BPF_JEQ, 0, ARPOP_REQUEST, 2),                      /* if (r0 == request) skip 2 */
+                BPF_MOV_IMM(0, 0),                                              /* r0 = 0 */
+                BPF_EXIT_INSN(),                                                /* return */
+
+                /* check if the probe or conflict is for an address we are monitoring */
+                BPF_STX_MEM(BPF_W, 10, 7, -4),                                  /* *(uint32_t*)fp - 4 = r7 */
+                BPF_MOV_REG(2, 10),                                             /* r2 = fp */
+                BPF_ALU_IMM(BPF_ADD, 2, -4),                                    /* r2 -= 4 */
+                BPF_LD_MAP_FD(1, mapfd),                                        /* r1 = mapfd */
+                BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem),                        /* r0 = map_lookup_elem(r1, r2) */
+                BPF_JMP_IMM(BPF_JNE, 0, 0, 2),                                  /* if (r0 != NULL) skip 2 */
+                BPF_MOV_IMM(0, 0),                                              /* r0 = 0 */
+                BPF_EXIT_INSN(),                                                /* return */
+
+                /* return exactly the packet length*/
+                BPF_MOV_IMM(0, sizeof(struct ether_arp)),                       /* r0 = sizeof(struct ether_arp) */
+                BPF_EXIT_INSN(),                                                /* return */
+        };
+        union bpf_attr attr;
+        int progfd;
+
+        memset(&attr, 0, sizeof(attr));
+        attr = (union bpf_attr){
+                .prog_type = BPF_PROG_TYPE_SOCKET_FILTER,
+                .insns     = (uint64_t)(unsigned long)prog,
+                .insn_cnt  = sizeof(prog) / sizeof(*prog),
+                .license   = (uint64_t)(unsigned long)"ASL",
+        };
+
+        progfd = n_acd_syscall_bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
+        if (progfd < 0)
+                return -errno;
+
+        *progfdp = progfd;
+        return 0;
+}
diff --git a/shared/n-acd/src/n-acd-private.h b/shared/n-acd/src/n-acd-private.h
new file mode 100644
index 00000000..3f207912
--- /dev/null
+++ b/shared/n-acd/src/n-acd-private.h
@@ -0,0 +1,172 @@
+#pragma once
+
+#include <c-list.h>
+#include <c-rbtree.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <netinet/if_ether.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include "util/timer.h"
+#include "n-acd.h"
+
+typedef struct NAcdEventNode NAcdEventNode;
+
+#define _cleanup_(_x) __attribute__((__cleanup__(_x)))
+#define _public_ __attribute__((__visibility__("default")))
+
+/* This augments the error-codes with internal ones that are never exposed. */
+enum {
+        _N_ACD_INTERNAL = _N_ACD_E_N,
+
+        N_ACD_E_DROPPED,
+};
+
+enum {
+        N_ACD_PROBE_STATE_PROBING,
+        N_ACD_PROBE_STATE_CONFIGURING,
+        N_ACD_PROBE_STATE_ANNOUNCING,
+        N_ACD_PROBE_STATE_FAILED,
+};
+
+struct NAcdConfig {
+        int ifindex;
+        unsigned int transport;
+        uint8_t mac[ETH_ALEN];
+        size_t n_mac;
+};
+
+#define N_ACD_CONFIG_NULL(_x) {                                                 \
+                .transport = _N_ACD_TRANSPORT_N,                                \
+        }
+
+struct NAcdProbeConfig {
+        struct in_addr ip;
+        uint64_t timeout_msecs;
+};
+
+#define N_ACD_PROBE_CONFIG_NULL(_x) {                                           \
+                .timeout_msecs = N_ACD_TIMEOUT_RFC5227,                         \
+        }
+
+struct NAcdEventNode {
+        CList acd_link;
+        CList probe_link;
+        NAcdEvent event;
+        uint8_t sender[ETH_ALEN];
+        bool is_public : 1;
+};
+
+#define N_ACD_EVENT_NODE_NULL(_x) {                                             \
+                .acd_link = C_LIST_INIT((_x).acd_link),                         \
+                .probe_link = C_LIST_INIT((_x).probe_link),                     \
+        }
+
+struct NAcd {
+        unsigned long n_refs;
+        unsigned int seed;
+        int fd_epoll;
+        int fd_socket;
+        CRBTree ip_tree;
+        CList event_list;
+        Timer timer;
+
+        /* BPF map */
+        int fd_bpf_map;
+        size_t n_bpf_map;
+        size_t max_bpf_map;
+
+        /* configuration */
+        int ifindex;
+        uint8_t mac[ETH_ALEN];
+
+        /* flags */
+        bool preempted : 1;
+};
+
+#define N_ACD_NULL(_x) {                                                        \
+                .n_refs = 1,                                                    \
+                .fd_epoll = -1,                                                 \
+                .fd_socket = -1,                                                \
+                .ip_tree = C_RBTREE_INIT,                                       \
+                .event_list = C_LIST_INIT((_x).event_list),                     \
+                .timer = TIMER_NULL((_x).timer),                                \
+                .fd_bpf_map = -1,                                               \
+        }
+
+struct NAcdProbe {
+        NAcd *acd;
+        CRBNode ip_node;
+        CList event_list;
+        Timeout timeout;
+
+        /* configuration */
+        struct in_addr ip;
+        uint64_t timeout_multiplier;
+        void *userdata;
+
+        /* state */
+        unsigned int state;
+        unsigned int n_iteration;
+        unsigned int defend;
+        uint64_t last_defend;
+};
+
+#define N_ACD_PROBE_NULL(_x) {                                                  \
+                .ip_node = C_RBNODE_INIT((_x).ip_node),                         \
+                .event_list = C_LIST_INIT((_x).event_list),                     \
+                .timeout = TIMEOUT_INIT((_x).timeout),                          \
+                .state = N_ACD_PROBE_STATE_PROBING,                             \
+                .defend = N_ACD_DEFEND_NEVER,                                   \
+        }
+
+/* events */
+
+int n_acd_event_node_new(NAcdEventNode **nodep);
+NAcdEventNode *n_acd_event_node_free(NAcdEventNode *node);
+
+/* contexts */
+
+void n_acd_remember(NAcd *acd, uint64_t now, bool success);
+int n_acd_raise(NAcd *acd, NAcdEventNode **nodep, unsigned int event);
+int n_acd_send(NAcd *acd, const struct in_addr *tpa, const struct in_addr *spa);
+int n_acd_ensure_bpf_map_space(NAcd *acd);
+
+/* probes */
+
+int n_acd_probe_new(NAcdProbe **probep, NAcd *acd, NAcdProbeConfig *config);
+int n_acd_probe_raise(NAcdProbe *probe, NAcdEventNode **nodep, unsigned int event);
+int n_acd_probe_handle_timeout(NAcdProbe *probe);
+int n_acd_probe_handle_packet(NAcdProbe *probe, struct ether_arp *packet, bool hard_conflict);
+
+/* eBPF */
+
+int n_acd_bpf_map_create(int *mapfdp, size_t max_elements);
+int n_acd_bpf_map_add(int mapfd, struct in_addr *addr);
+int n_acd_bpf_map_remove(int mapfd, struct in_addr *addr);
+
+int n_acd_bpf_compile(int *progfdp, int mapfd, struct ether_addr *mac);
+
+/* inline helpers */
+
+static inline int n_acd_errno(void) {
+        /*
+         * Compilers continuously warn about uninitialized variables since they
+         * cannot deduce that `return -errno;` will always be negative. This
+         * small wrapper makes sure compilers figure that out. Use it as
+         * replacement for `errno` read access. Yes, it generates worse code,
+         * but only marginally and only affects slow-paths.
+         */
+        return abs(errno) ? : EIO;
+}
+
+static inline void n_acd_event_node_freep(NAcdEventNode **node) {
+        if (*node)
+                n_acd_event_node_free(*node);
+}
+
+static inline void n_acd_closep(int *fdp) {
+        if (*fdp >= 0)
+                close(*fdp);
+}
diff --git a/shared/n-acd/src/n-acd-probe.c b/shared/n-acd/src/n-acd-probe.c
new file mode 100644
index 00000000..8c233b56
--- /dev/null
+++ b/shared/n-acd/src/n-acd-probe.c
@@ -0,0 +1,636 @@
+/*
+ * IPv4 Address Conflict Detection
+ */
+
+#include <assert.h>
+#include <c-rbtree.h>
+#include <endian.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <netinet/if_ether.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "n-acd.h"
+#include "n-acd-private.h"
+
+/*
+ * These parameters and timing intervals specified in RFC-5227. The original
+ * values are:
+ *
+ *     PROBE_NUM                                3
+ *     PROBE_WAIT                               1s
+ *     PROBE_MIN                                1s
+ *     PROBE_MAX                                3s
+ *     ANNOUNCE_NUM                             3
+ *     ANNOUNCE_WAIT                            2s
+ *     ANNOUNCE_INTERVAL                        2s
+ *     MAX_CONFLICTS                            10
+ *     RATE_LIMIT_INTERVAL                      60s
+ *     DEFEND_INTERVAL                          10s
+ *
+ * If we assume a best-case and worst-case scenario for non-conflicted runs, we
+ * end up with a runtime between 4s and 9s to finish the probe. Then it still
+ * takes a fixed 4s to finish the announcements.
+ *
+ * RFC 5227 section 1.1:
+ *     [...] (Note that the values listed here are fixed constants; they are
+ *     not intended to be modifiable by implementers, operators, or end users.
+ *     These constants are given symbolic names here to facilitate the writing
+ *     of future standards that may want to reference this document with
+ *     different values for these named constants; however, at the present time
+ *     no such future standards exist.) [...]
+ *
+ * Unfortunately, no-one ever stepped up to write a "future standard" to revise
+ * the timings. A 9s timeout for successful link setups is not acceptable today.
+ * Hence, we will just go forward and ignore the proposed values. On both
+ * wired and wireless local links round-trip latencies of below 3ms are common.
+ * We require the caller to set a timeout multiplier, where 1 corresponds to a
+ * total probe time between 0.5 ms and 1.0 ms. On modern networks a multiplier
+ * of about 100 should be a reasonable default. To comply with the RFC select a
+ * multiplier of 9000.
+ */
+#define N_ACD_RFC_PROBE_NUM                     (3)
+#define N_ACD_RFC_PROBE_WAIT_NSEC               (UINT64_C(111111)) /* 1/9 ms */
+#define N_ACD_RFC_PROBE_MIN_NSEC                (UINT64_C(111111)) /* 1/9 ms */
+#define N_ACD_RFC_PROBE_MAX_NSEC                (UINT64_C(333333)) /* 3/9 ms */
+#define N_ACD_RFC_ANNOUNCE_NUM                  (3)
+#define N_ACD_RFC_ANNOUNCE_WAIT_NSEC            (UINT64_C(222222)) /* 2/9 ms */
+#define N_ACD_RFC_ANNOUNCE_INTERVAL_NSEC        (UINT64_C(222222)) /* 2/9 ms */
+#define N_ACD_RFC_MAX_CONFLICTS                 (10)
+#define N_ACD_RFC_RATE_LIMIT_INTERVAL_NSEC      (UINT64_C(60000000000)) /* 60s */
+#define N_ACD_RFC_DEFEND_INTERVAL_NSEC          (UINT64_C(10000000000)) /* 10s */
+
+/**
+ * XXX
+ */
+_public_ int n_acd_probe_config_new(NAcdProbeConfig **configp) {
+        _cleanup_(n_acd_probe_config_freep) NAcdProbeConfig *config = NULL;
+
+        config = malloc(sizeof(*config));
+        if (!config)
+                return -ENOMEM;
+
+        *config = (NAcdProbeConfig)N_ACD_PROBE_CONFIG_NULL(*config);
+
+        *configp = config;
+        config = NULL;
+        return 0;
+}
+
+/**
+ * XXX
+ */
+_public_ NAcdProbeConfig *n_acd_probe_config_free(NAcdProbeConfig *config) {
+        if (!config)
+                return NULL;
+
+        free(config);
+
+        return NULL;
+}
+
+/**
+ * XXX
+ */
+_public_ void n_acd_probe_config_set_ip(NAcdProbeConfig *config, struct in_addr ip) {
+        config->ip = ip;
+}
+
+/**
+ * XXX
+ */
+_public_ void n_acd_probe_config_set_timeout(NAcdProbeConfig *config, uint64_t msecs) {
+        config->timeout_msecs = msecs;
+}
+
+static void n_acd_probe_schedule(NAcdProbe *probe, uint64_t n_timeout, unsigned int n_jitter) {
+        uint64_t n_time;
+
+        timer_now(&probe->acd->timer, &n_time);
+        n_time += n_timeout;
+
+        /*
+         * ACD specifies jitter values to reduce packet storms on the local
+         * link. This call accepts the maximum relative jitter value in
+         * nanoseconds as @n_jitter. We then use rand_r(3p) to get a
+         * pseudo-random jitter on top of the real timeout given as @n_timeout.
+         */
+        if (n_jitter) {
+                uint64_t random;
+
+                random = ((uint64_t)rand_r(&probe->acd->seed) << 32) | (uint64_t)rand_r(&probe->acd->seed);
+                n_time += random % n_jitter;
+        }
+
+        timeout_schedule(&probe->timeout, &probe->acd->timer, n_time);
+}
+
+static void n_acd_probe_unschedule(NAcdProbe *probe) {
+        timeout_unschedule(&probe->timeout);
+}
+
+static bool n_acd_probe_is_unique(NAcdProbe *probe) {
+        NAcdProbe *sibling;
+
+        if (!c_rbnode_is_linked(&probe->ip_node))
+                return false;
+
+        sibling = c_rbnode_entry(c_rbnode_next(&probe->ip_node), NAcdProbe, ip_node);
+        if (sibling && sibling->ip.s_addr == probe->ip.s_addr)
+                return false;
+
+        sibling = c_rbnode_entry(c_rbnode_prev(&probe->ip_node), NAcdProbe, ip_node);
+        if (sibling && sibling->ip.s_addr == probe->ip.s_addr)
+                return false;
+
+        return true;
+}
+
+static int n_acd_probe_link(NAcdProbe *probe) {
+        int r;
+
+        /*
+         * Make sure the kernel bpf map has space for at least one more
+         * entry.
+         */
+        r = n_acd_ensure_bpf_map_space(probe->acd);
+        if (r)
+                return r;
+
+        /*
+         * Link entry into context, indexed by its IP. Note that we allow
+         * duplicates just fine. It is up to you to decide whether to avoid
+         * duplicates, if you don't want them. Duplicates on the same context
+         * do not conflict with each other, though.
+         */
+        {
+                CRBNode **slot, *parent;
+                NAcdProbe *other;
+
+                slot = &probe->acd->ip_tree.root;
+                parent = NULL;
+                while (*slot) {
+                        other = c_rbnode_entry(*slot, NAcdProbe, ip_node);
+                        parent = *slot;
+                        if (probe->ip.s_addr < other->ip.s_addr)
+                                slot = &(*slot)->left;
+                        else
+                                slot = &(*slot)->right;
+                }
+
+                c_rbtree_add(&probe->acd->ip_tree, parent, slot, &probe->ip_node);
+        }
+
+        /*
+         * Add the ip address to the map, if it is not already there.
+         */
+        if (n_acd_probe_is_unique(probe)) {
+                r = n_acd_bpf_map_add(probe->acd->fd_bpf_map, &probe->ip);
+                if (r) {
+                        /*
+                         * Make sure the IP address is linked in userspace iff
+                         * it is linked in the kernel.
+                         */
+                        c_rbnode_unlink(&probe->ip_node);
+                        return r;
+                }
+                ++probe->acd->n_bpf_map;
+        }
+
+        return 0;
+}
+
+static void n_acd_probe_unlink(NAcdProbe *probe) {
+        int r;
+
+        /*
+         * If this is the only probe for a given IP, remove the IP from the
+         * kernel BPF map.
+         */
+        if (n_acd_probe_is_unique(probe)) {
+                r = n_acd_bpf_map_remove(probe->acd->fd_bpf_map, &probe->ip);
+                assert(r >= 0);
+                --probe->acd->n_bpf_map;
+        }
+        c_rbnode_unlink(&probe->ip_node);
+}
+
+int n_acd_probe_new(NAcdProbe **probep, NAcd *acd, NAcdProbeConfig *config) {
+        _cleanup_(n_acd_probe_freep) NAcdProbe *probe = NULL;
+        int r;
+
+        if (!config->ip.s_addr)
+                return N_ACD_E_INVALID_ARGUMENT;
+
+        probe = malloc(sizeof(*probe));
+        if (!probe)
+                return -ENOMEM;
+
+        *probe = (NAcdProbe)N_ACD_PROBE_NULL(*probe);
+        probe->acd = n_acd_ref(acd);
+        probe->ip = config->ip;
+
+        /*
+         * We use the provided timeout-length as multiplier for all our
+         * timeouts. The provided timeout defines the maximum length of an
+         * entire probe-interval until the first announcement. Given the
+         * spec-provided parameters, this ends up as:
+         *
+         *     PROBE_WAIT + PROBE_MAX + PROBE_MAX + ANNOUNCE_WAIT
+         *   =         1s +        3s +        3s +            2s
+         *   = 9s
+         *
+         * Hence, the default value for this timeout is 9000ms, which just
+         * ends up matching the spec-provided values.
+         *
+         * What we now semantically do is divide this timeout by 1ns/1000000.
+         * This first turns it into nanoseconds, then strips the unit by
+         * turning it into a multiplier. However, rather than performing the
+         * division here, we multiplier all our timeouts by 1000000 statically
+         * at compile time. Therefore, we can use the user-provided timeout as
+         * unmodified multiplier. No conversion necessary.
+         */
+        probe->timeout_multiplier = config->timeout_msecs;
+
+        r = n_acd_probe_link(probe);
+        if (r)
+                return r;
+
+        /*
+         * Now that everything is set up, we have to send the first probe. This
+         * is done after ~PROBE_WAIT seconds, hence we schedule our timer.
+         * In case no timeout-multiplier is set, we pretend we already sent all
+         * probes successfully and schedule the timer so we proceed with the
+         * announcements. We must schedule a fake timer there, since we are not
+         * allowed to advance the state machine outside of n_acd_dispatch().
+         */
+        if (probe->timeout_multiplier) {
+                probe->n_iteration = 0;
+                n_acd_probe_schedule(probe,
+                                     0,
+                                     probe->timeout_multiplier * N_ACD_RFC_PROBE_WAIT_NSEC);
+        } else {
+                probe->n_iteration = N_ACD_RFC_PROBE_NUM;
+                n_acd_probe_schedule(probe, 0, 0);
+        }
+
+        *probep = probe;
+        probe = NULL;
+        return 0;
+}
+
+/**
+ * XXX
+ */
+_public_ NAcdProbe *n_acd_probe_free(NAcdProbe *probe) {
+        NAcdEventNode *node, *t_node;
+
+        if (!probe)
+                return NULL;
+
+        c_list_for_each_entry_safe(node, t_node, &probe->event_list, probe_link)
+                n_acd_event_node_free(node);
+
+        n_acd_probe_unschedule(probe);
+        n_acd_probe_unlink(probe);
+        probe->acd = n_acd_unref(probe->acd);
+        free(probe);
+
+        return NULL;
+}
+
+int n_acd_probe_raise(NAcdProbe *probe, NAcdEventNode **nodep, unsigned int event) {
+        _cleanup_(n_acd_event_node_freep) NAcdEventNode *node = NULL;
+        int r;
+
+        r = n_acd_raise(probe->acd, &node, event);
+        if (r)
+                return r;
+
+        switch (event) {
+        case N_ACD_EVENT_READY:
+                node->event.ready.probe = probe;
+                break;
+        case N_ACD_EVENT_USED:
+                node->event.used.probe = probe;
+                break;
+        case N_ACD_EVENT_DEFENDED:
+                node->event.defended.probe = probe;
+                break;
+        case N_ACD_EVENT_CONFLICT:
+                node->event.conflict.probe = probe;
+                break;
+        default:
+                assert(0);
+                return -EIO;
+        }
+
+        c_list_link_tail(&probe->event_list, &node->probe_link);
+
+        if (nodep)
+                *nodep = node;
+        node = NULL;
+        return 0;
+}
+
+int n_acd_probe_handle_timeout(NAcdProbe *probe) {
+        int r;
+
+        switch (probe->state) {
+        case N_ACD_PROBE_STATE_PROBING:
+                /*
+                 * We are still PROBING. We send 3 probes with a random timeout
+                 * scheduled between each. If, after a fixed timeout, we did
+                 * not receive any conflict we consider the probing successful.
+                 */
+                if (probe->n_iteration < N_ACD_RFC_PROBE_NUM) {
+                        /*
+                         * We have not sent all 3 probes, yet. A timer fired,
+                         * so we are ready to send the next probe. If this is
+                         * the third probe, schedule a timer for ANNOUNCE_WAIT
+                         * to give other peers a chance to answer. If this is
+                         * not the third probe, wait between PROBE_MIN and
+                         * PROBE_MAX for the next probe.
+                         */
+
+                        r = n_acd_send(probe->acd, &probe->ip, NULL);
+                        if (r) {
+                                if (r != -N_ACD_E_DROPPED)
+                                        return r;
+
+                                /*
+                                 * Packet was dropped, and we know about it. It
+                                 * never reached the network. Reasons are
+                                 * manifold, and n_acd_send() raises events if
+                                 * necessary.
+                                 * From a probe-perspective, we simply pretend
+                                 * we never sent the probe and schedule a
+                                 * timeout for the next probe, effectively
+                                 * doubling a single probe-interval.
+                                 */
+                        } else {
+                                /* Successfully sent, so advance counter. */
+                                ++probe->n_iteration;
+                        }
+
+                        if (probe->n_iteration < N_ACD_RFC_PROBE_NUM)
+                                n_acd_probe_schedule(probe,
+                                                     probe->timeout_multiplier * N_ACD_RFC_PROBE_MIN_NSEC,
+                                                     probe->timeout_multiplier * (N_ACD_RFC_PROBE_MAX_NSEC - N_ACD_RFC_PROBE_MIN_NSEC));
+                        else
+                                n_acd_probe_schedule(probe,
+                                                     probe->timeout_multiplier * N_ACD_RFC_ANNOUNCE_WAIT_NSEC,
+                                                     0);
+                } else {
+                        /*
+                         * All 3 probes succeeded and we waited enough to
+                         * consider this address usable by now. Do not announce
+                         * the address, yet. We must first give the caller a
+                         * chance to configure the address (so they can answer
+                         * ARP requests), before announcing it.
+                         */
+                        r = n_acd_probe_raise(probe, NULL, N_ACD_EVENT_READY);
+                        if (r)
+                                return r;
+
+                        probe->state = N_ACD_PROBE_STATE_CONFIGURING;
+                }
+
+                break;
+
+        case N_ACD_PROBE_STATE_ANNOUNCING:
+                /*
+                 * We are ANNOUNCING, meaning the caller configured the address
+                 * on the interface and is actively using it. We send 3
+                 * announcements out, in a short interval, and then just
+                 * perform passive conflict detection.
+                 * Note that once all 3 announcements are sent, we no longer
+                 * schedule a timer, so this part should not trigger, anymore.
+                 */
+
+                r = n_acd_send(probe->acd, &probe->ip, &probe->ip);
+                if (r) {
+                        if (r != -N_ACD_E_DROPPED)
+                                return r;
+
+                        /*
+                         * See above in STATE_PROBING for details. We know the
+                         * packet was never sent, so we simply try again after
+                         * extending the timer.
+                         */
+                } else {
+                        /* Successfully sent, so advance counter. */
+                        ++probe->n_iteration;
+                }
+
+                if (probe->n_iteration < N_ACD_RFC_ANNOUNCE_NUM) {
+                        /*
+                         * Announcements are always scheduled according to the
+                         * time-intervals specified in the spec. We always use
+                         * the RFC5227-mandated multiplier.
+                         * If you reconsider this, note that timeout_multiplier
+                         * might be 0 here.
+                         */
+                        n_acd_probe_schedule(probe,
+                                             N_ACD_TIMEOUT_RFC5227 * N_ACD_RFC_ANNOUNCE_INTERVAL_NSEC,
+                                             0);
+                }
+
+                break;
+
+        case N_ACD_PROBE_STATE_CONFIGURING:
+        case N_ACD_PROBE_STATE_FAILED:
+        default:
+                /*
+                 * There are no timeouts in these states. If we trigger one,
+                 * something is fishy.
+                 */
+                assert(0);
+                return -EIO;
+        }
+
+        return 0;
+}
+
+int n_acd_probe_handle_packet(NAcdProbe *probe, struct ether_arp *packet, bool hard_conflict) {
+        NAcdEventNode *node;
+        uint64_t now;
+        int r;
+
+        timer_now(&probe->acd->timer, &now);
+
+        switch (probe->state) {
+        case N_ACD_PROBE_STATE_PROBING:
+                /*
+                 * Regardless whether this is a hard or soft conflict, we must
+                 * treat this as a probe failure. That is, notify the caller of
+                 * the conflict and wait for further instructions. We do not
+                 * react to this, until the caller tells us what to do, but we
+                 * do stop sending further probes.
+                 */
+                r = n_acd_probe_raise(probe, &node, N_ACD_EVENT_USED);
+                if (r)
+                        return r;
+
+                node->event.used.sender = node->sender;
+                node->event.used.n_sender = ETH_ALEN;
+                memcpy(node->sender, packet->arp_sha, ETH_ALEN);
+
+                n_acd_probe_unschedule(probe);
+                n_acd_probe_unlink(probe);
+                probe->state = N_ACD_PROBE_STATE_FAILED;
+
+                break;
+
+        case N_ACD_PROBE_STATE_CONFIGURING:
+                /*
+                 * We are waiting for the caller to configure the interface and
+                 * start ANNOUNCING. In this state, we cannot defend the
+                 * address as that would indicate that it is ready to be used,
+                 * and we cannot signal CONFLICT or USED as the caller may
+                 * already have started to use the address (and may have
+                 * configured the engine to always defend it, which means they
+                 * should be able to rely on never losing it after READY).
+                 * Simply drop the event, and rely on the anticipated ANNOUNCE
+                 * to trigger it again.
+                 */
+
+                break;
+
+        case N_ACD_PROBE_STATE_ANNOUNCING: {
+                /*
+                 * We were already instructed to announce the address, which
+                 * means the address is configured and in use. Hence, the
+                 * caller is responsible to serve regular ARP queries. Meaning,
+                 * we can ignore any soft conflicts (other peers doing ACD).
+                 *
+                 * But if we see a hard-conflict, we either defend the address
+                 * according to the caller's instructions, or we report the
+                 * conflict and bail out.
+                 */
+                bool conflict = false, rate_limited = false;
+
+                if (!hard_conflict)
+                        break;
+
+                rate_limited = now < probe->last_defend + N_ACD_RFC_DEFEND_INTERVAL_NSEC;
+
+                switch (probe->defend) {
+                case N_ACD_DEFEND_NEVER:
+                        conflict = true;
+                        break;
+                case N_ACD_DEFEND_ONCE:
+                        if (rate_limited) {
+                                conflict = true;
+                                break;
+                        }
+
+                        /* fallthrough */
+                case N_ACD_DEFEND_ALWAYS:
+                        if (!rate_limited) {
+                                r = n_acd_send(probe->acd, &probe->ip, &probe->ip);
+                                if (r) {
+                                        if (r != -N_ACD_E_DROPPED)
+                                                return r;
+
+                                        if (probe->defend == N_ACD_DEFEND_ONCE) {
+                                                conflict = true;
+                                                break;
+                                        }
+                                }
+
+                                if (r != -N_ACD_E_DROPPED)
+                                        probe->last_defend = now;
+                        }
+
+                        r = n_acd_probe_raise(probe, &node, N_ACD_EVENT_DEFENDED);
+                        if (r)
+                                return r;
+
+                        node->event.defended.sender = node->sender;
+                        node->event.defended.n_sender = ETH_ALEN;
+                        memcpy(node->sender, packet->arp_sha, ETH_ALEN);
+
+                        break;
+                }
+
+                if (conflict) {
+                        r = n_acd_probe_raise(probe, &node, N_ACD_EVENT_CONFLICT);
+                        if (r)
+                                return r;
+
+                        node->event.conflict.sender = node->sender;
+                        node->event.conflict.n_sender = ETH_ALEN;
+                        memcpy(node->sender, packet->arp_sha, ETH_ALEN);
+
+                        n_acd_probe_unschedule(probe);
+                        n_acd_probe_unlink(probe);
+                        probe->state = N_ACD_PROBE_STATE_FAILED;
+                }
+
+                break;
+        }
+
+        case N_ACD_PROBE_STATE_FAILED:
+        default:
+                /*
+                 * We are not listening for packets in these states. If we receive one,
+                 * something is fishy.
+                 */
+                assert(0);
+                return -EIO;
+        }
+
+        return 0;
+}
+
+/**
+ * n_acd_probe_set_userdata - XXX
+ */
+_public_ void n_acd_probe_set_userdata(NAcdProbe *probe, void *userdata) {
+        probe->userdata = userdata;
+}
+
+/**
+ * n_acd_probe_get_userdata - XXX
+ */
+_public_ void n_acd_probe_get_userdata(NAcdProbe *probe, void **userdatap) {
+        *userdatap = probe->userdata;
+}
+
+/**
+ * n_acd_probe_announce() - announce the configured IP address
+ * @probe:      probe object
+ * @defend:     defence policy
+ *
+ * Announce the IP address on the local link, and start defending it according
+ * to the given policy, which mut be one of N_ACD_DEFEND_ONCE,
+ * N_ACD_DEFEND_NEVER, or N_ACD_DEFEND_ALWAYS.
+ *
+ * This must be called in response to an N_ACD_EVENT_READY event, and only
+ * after the given address has been configured on the given network interface.
+ *
+ * Return: 0 on success, N_ACD_E_INVALID_ARGUMENT in case the defence policy
+ *         is invalid, negative error code on failure.
+ */
+_public_ int n_acd_probe_announce(NAcdProbe *probe, unsigned int defend) {
+        if (defend >= _N_ACD_DEFEND_N)
+                return N_ACD_E_INVALID_ARGUMENT;
+
+        probe->state = N_ACD_PROBE_STATE_ANNOUNCING;
+        probe->defend = defend;
+        probe->n_iteration = 0;
+
+        /*
+         * We must schedule a fake-timeout, since we are not allowed to
+         * advance the state-machine outside of n_acd_dispatch().
+         */
+        n_acd_probe_schedule(probe, 0, 0);
+
+        return 0;
+}
diff --git a/shared/n-acd/src/n-acd.c b/shared/n-acd/src/n-acd.c
index 9164f958..def56a21 100644
--- a/shared/n-acd/src/n-acd.c
+++ b/shared/n-acd/src/n-acd.c
@@ -1,188 +1,38 @@
 /*
  * IPv4 Address Conflict Detection
- *
- * This implements the main n-acd API. It is built around an epoll-fd to
- * encapsulate a timerfd+socket. The n-acd context has quite straightforward
- * lifetime rules. The parameters must be set when the engine is started, and
- * they can only be changed by stopping and restartding the engine. The engine
- * is started on demand and stopped when no longer needed.
- * During the entire lifetime the context can be dispatched. That is, the
- * dispatcher does not have to be aware of the context state. After each call
- * to dispatch(), the caller must pop all pending events until -EAGAIN is
- * returned.
- *
- * If a conflict is detected, the ACD engine reports to the caller and stops
- * the engine. The caller can now modify parameters and restart the engine, if
- * required.
  */
 
 #include <assert.h>
 #include <c-list.h>
+#include <c-rbtree.h>
 #include <c-siphash.h>
 #include <endian.h>
 #include <errno.h>
+#include <inttypes.h>
 #include <limits.h>
-#include <linux/filter.h>
-#include <linux/if_ether.h>
 #include <linux/if_packet.h>
-#include <net/ethernet.h>
 #include <netinet/if_ether.h>
 #include <netinet/in.h>
-#include <stddef.h>
-#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/auxv.h>
 #include <sys/epoll.h>
 #include <sys/socket.h>
-#include <sys/timerfd.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include "n-acd.h"
-
-#define _public_ __attribute__((__visibility__("default")))
-
-/*
- * These parameters and timing intervals specified in RFC-5227. The original
- * values are:
- *
- *     PROBE_NUM                                3
- *     PROBE_WAIT                               1s
- *     PROBE_MIN                                1s
- *     PROBE_MAX                                3s
- *     ANNOUNCE_NUM                             3
- *     ANNOUNCE_WAIT                            2s
- *     ANNOUNCE_INTERVAL                        2s
- *     MAX_CONFLICTS                            10
- *     RATE_LIMIT_INTERVAL                      60s
- *     DEFEND_INTERVAL                          10s
- *
- * If we assume a best-case and worst-case scenario for non-conflicted runs, we
- * end up with a runtime between 4s and 9s to finish the probe. Then it still
- * takes a fixed 4s to finish the announcements.
- *
- * RFC 5227 section 1.1:
- *     [...] (Note that the values listed here are fixed constants; they are
- *     not intended to be modifiable by implementers, operators, or end users.
- *     These constants are given symbolic names here to facilitate the writing
- *     of future standards that may want to reference this document with
- *     different values for these named constants; however, at the present time
- *     no such future standards exist.) [...]
- *
- * Unfortunately, no-one ever stepped up to write a "future standard" to revise
- * the timings. A 9s timeout for successful link setups is not acceptable today.
- * Hence, we will just go forward and ignore the proposed values. On both
- * wired and wireless local links round-trip latencies of below 3ms are common,
- * while latencies above 10ms are rarely seen. We require the caller to set a
- * timeout multiplier, where 1 corresponds to a total probe time of 0.5 ms and
- * 1.0 ms. On modern networks a multiplier of about 100 should be a reasonable
- * default. To comply with the RFC select a multiplier of 9000.
- */
-#define N_ACD_RFC_PROBE_NUM                     (3)
-#define N_ACD_RFC_PROBE_WAIT_USEC               (UINT64_C(111)) /* 111us */
-#define N_ACD_RFC_PROBE_MIN_USEC                (UINT64_C(111)) /* 111us */
-#define N_ACD_RFC_PROBE_MAX_USEC                (UINT64_C(333)) /* 333us */
-#define N_ACD_RFC_ANNOUNCE_NUM                  (3)
-#define N_ACD_RFC_ANNOUNCE_WAIT_USEC            (UINT64_C(222)) /* 222us */
-#define N_ACD_RFC_ANNOUNCE_INTERVAL_USEC        (UINT64_C(222)) /* 222us */
-#define N_ACD_RFC_MAX_CONFLICTS                 (10)
-#define N_ACD_RFC_RATE_LIMIT_INTERVAL_USEC      (UINT64_C(60000000)) /* 60s */
-#define N_ACD_RFC_DEFEND_INTERVAL_USEC          (UINT64_C(10000000)) /* 10s */
-
-/*
- * Fake ENETDOWN error-code. We use this as replacement for known EFOOBAR error
- * codes. It is explicitly chosen to be outside the known error-code range.
- * Whenever we are deep down in a call-stack and notice a ENETDOWN error, we
- * return this instead. It is caught by the top-level dispatcher and then
- * properly handled.
- * This avoids gracefully handling ENETDOWN in call-stacks, but then continuing
- * with some work in the callers without noticing the soft failure.
- */
-#define N_ACD_E_DOWN (INT_MAX)
-
-#define TIME_INFINITY ((uint64_t) -1)
+#include "n-acd-private.h"
 
 enum {
         N_ACD_EPOLL_TIMER,
         N_ACD_EPOLL_SOCKET,
 };
 
-enum {
-        N_ACD_STATE_INIT,
-        N_ACD_STATE_PROBING,
-        N_ACD_STATE_CONFIGURING,
-        N_ACD_STATE_ANNOUNCING,
-};
-
-typedef struct NAcdEventNode {
-        NAcdEvent event;
-        uint8_t sender[ETH_ALEN];
-        CList link;
-} NAcdEventNode;
-
-struct NAcd {
-        /* context */
-        unsigned int seed;
-        int fd_epoll;
-        int fd_timer;
-
-        /* configuration */
-        NAcdConfig config;
-        uint8_t mac[ETH_ALEN];
-        uint64_t timeout_multiplier;
-
-        /* runtime */
-        int fd_socket;
-        unsigned int state;
-        unsigned int n_iteration;
-        unsigned int n_conflicts;
-        unsigned int defend;
-        uint64_t last_defend;
-        uint64_t last_conflict;
-
-        /* pending events */
-        CList events;
-        NAcdEventNode *current;
-};
-
-static int n_acd_errno(void) {
-        /*
-         * Compilers continuously warn about uninitialized variables since they
-         * cannot deduce that `return -errno;` will always be negative. This
-         * small wrapper makes sure compilers figure that out. Use it as
-         * replacement for `errno` read access. Yes, it generates worse code,
-         * but only marginally and only affects slow-paths.
-         */
-        return abs(errno) ? : EIO;
-}
-
-static int n_acd_event_node_new(NAcdEventNode **nodep, unsigned int event) {
-        NAcdEventNode *node;
-
-        node = calloc(1, sizeof(*node));
-        if (!node)
-                return -ENOMEM;
-
-        node->event.event = event;
-        node->link = (CList)C_LIST_INIT(node->link);
-
-        *nodep = node;
-
-        return 0;
-}
-
-static NAcdEventNode *n_acd_event_node_free(NAcdEventNode *node) {
-        if (!node)
-                return NULL;
-
-        c_list_unlink(&node->link);
-        free(node);
-
-        return NULL;
-}
-
 static int n_acd_get_random(unsigned int *random) {
-        uint8_t hash_seed[] = { 0x3a, 0x0c, 0xa6, 0xdd, 0x44, 0xef, 0x5f, 0x7a, 0x5e, 0xd7, 0x25, 0x37, 0xbf, 0x4e, 0x80, 0xa1 };
+        uint8_t hash_seed[] = {
+                0x3a, 0x0c, 0xa6, 0xdd, 0x44, 0xef, 0x5f, 0x7a,
+                0x5e, 0xd7, 0x25, 0x37, 0xbf, 0x4e, 0x80, 0xa1,
+        };
         CSipHash hash = C_SIPHASH_NULL;
         struct timespec ts;
         const uint8_t *p;
@@ -203,7 +53,7 @@ static int n_acd_get_random(unsigned int *random) {
         if (p)
                 c_siphash_append(&hash, p, 16);
 
-        r = clock_gettime(CLOCK_BOOTTIME, &ts);
+        r = clock_gettime(CLOCK_MONOTONIC, &ts);
         if (r < 0)
                 return -n_acd_errno();
 
@@ -214,114 +64,260 @@ static int n_acd_get_random(unsigned int *random) {
         return 0;
 }
 
-static void n_acd_reset(NAcd *acd) {
-        acd->state = N_ACD_STATE_INIT;
-        acd->defend = N_ACD_DEFEND_NEVER;
-        acd->n_iteration = 0;
-        acd->last_defend = 0;
-        timerfd_settime(acd->fd_timer, 0, &(struct itimerspec){}, NULL);
+static int n_acd_socket_new(int *fdp, int fd_bpf_prog, NAcdConfig *config) {
+        const struct sockaddr_ll address = {
+                .sll_family = AF_PACKET,
+                .sll_protocol = htobe16(ETH_P_ARP),
+                .sll_ifindex = config->ifindex,
+                .sll_halen = ETH_ALEN,
+                .sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+        };
+        int r, s = -1;
 
-        if (acd->fd_socket >= 0) {
-                assert(acd->fd_epoll >= 0);
-                epoll_ctl(acd->fd_epoll, EPOLL_CTL_DEL, acd->fd_socket, NULL);
-                close(acd->fd_socket);
-                acd->fd_socket = -1;
+        s = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+        if (s < 0) {
+                r = -n_acd_errno();
+                goto error;
+        }
+
+        if (fd_bpf_prog >= 0) {
+                r = setsockopt(s, SOL_SOCKET, SO_ATTACH_BPF, &fd_bpf_prog, sizeof(fd_bpf_prog));
+                if (r < 0)
+                        return -n_acd_errno();
         }
+
+        r = bind(s, (struct sockaddr *)&address, sizeof(address));
+        if (r < 0) {
+                r = -n_acd_errno();
+                goto error;
+        }
+
+        *fdp = s;
+        s = -1;
+        return 0;
+
+error:
+        if (s >= 0)
+                close(s);
+        return r;
+}
+
+/**
+ * XXX
+ */
+_public_ int n_acd_config_new(NAcdConfig **configp) {
+        _cleanup_(n_acd_config_freep) NAcdConfig *config = NULL;
+
+        config = malloc(sizeof(*config));
+        if (!config)
+                return -ENOMEM;
+
+        *config = (NAcdConfig)N_ACD_CONFIG_NULL(*config);
+
+        *configp = config;
+        config = NULL;
+        return 0;
+}
+
+/**
+ * XXX
+ */
+_public_ NAcdConfig *n_acd_config_free(NAcdConfig *config) {
+        if (!config)
+                return NULL;
+
+        free(config);
+
+        return NULL;
+}
+
+/**
+ * XXX
+ */
+_public_ void n_acd_config_set_ifindex(NAcdConfig *config, int ifindex) {
+        config->ifindex = ifindex;
+}
+
+/**
+ * XXX
+ */
+_public_ void n_acd_config_set_transport(NAcdConfig *config, unsigned int transport) {
+        config->transport = transport;
+}
+
+/**
+ * XXX
+ */
+_public_ void n_acd_config_set_mac(NAcdConfig *config, const uint8_t *mac, size_t n_mac) {
+        config->n_mac = n_mac;
+        memcpy(config->mac, mac, n_mac > ETH_ALEN ? ETH_ALEN : n_mac);
+}
+
+int n_acd_event_node_new(NAcdEventNode **nodep) {
+        NAcdEventNode *node;
+
+        node = malloc(sizeof(*node));
+        if (!node)
+                return -ENOMEM;
+
+        *node = (NAcdEventNode)N_ACD_EVENT_NODE_NULL(*node);
+
+        *nodep = node;
+        return 0;
+}
+
+NAcdEventNode *n_acd_event_node_free(NAcdEventNode *node) {
+        if (!node)
+                return NULL;
+
+        c_list_unlink(&node->probe_link);
+        c_list_unlink(&node->acd_link);
+        free(node);
+
+        return NULL;
+}
+
+int n_acd_ensure_bpf_map_space(NAcd *acd) {
+        NAcdProbe *probe;
+        _cleanup_(n_acd_closep) int fd_map = -1, fd_prog = -1;
+        size_t  max_map;
+        int r;
+
+        if (acd->n_bpf_map < acd->max_bpf_map)
+                return 0;
+
+        max_map = 2 * acd->max_bpf_map;
+
+        r = n_acd_bpf_map_create(&fd_map, max_map);
+        if (r)
+                return r;
+
+        c_rbtree_for_each_entry(probe, &acd->ip_tree, ip_node) {
+                r = n_acd_bpf_map_add(fd_map, &probe->ip);
+                if (r)
+                        return r;
+        }
+
+        r = n_acd_bpf_compile(&fd_prog, fd_map, (struct ether_addr*) acd->mac);
+        if (r)
+                return r;
+
+        if (fd_prog >= 0) {
+                r = setsockopt(acd->fd_socket, SOL_SOCKET, SO_ATTACH_BPF, &fd_prog, sizeof(fd_prog));
+                if (r)
+                        return -n_acd_errno();
+        }
+
+        if (acd->fd_bpf_map >= 0)
+                close(acd->fd_bpf_map);
+        acd->fd_bpf_map = fd_map;
+        fd_map = -1;
+        acd->max_bpf_map = max_map;
+        return 0;
 }
 
 /**
  * n_acd_new() - create a new ACD context
  * @acdp:       output argument for context
+ * @config:     configuration parameters
  *
  * Create a new ACD context and return it in @acdp.
  *
  * Return: 0 on success, or a negative error code on failure.
  */
-_public_ int n_acd_new(NAcd **acdp) {
-        NAcd *acd;
+_public_ int n_acd_new(NAcd **acdp, NAcdConfig *config) {
+        _cleanup_(n_acd_unrefp) NAcd *acd = NULL;
+        _cleanup_(n_acd_closep) int fd_bpf_prog = -1;
         int r;
 
-        acd = calloc(1, sizeof(*acd));
+        if (config->ifindex <= 0 ||
+            config->transport != N_ACD_TRANSPORT_ETHERNET ||
+            config->n_mac != ETH_ALEN ||
+            !memcmp(config->mac, (uint8_t[ETH_ALEN]){ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, ETH_ALEN))
+                return N_ACD_E_INVALID_ARGUMENT;
+
+        acd = malloc(sizeof(*acd));
         if (!acd)
                 return -ENOMEM;
 
-        acd->fd_epoll = -1;
-        acd->fd_timer = -1;
-        acd->fd_socket = -1;
-        acd->state = N_ACD_STATE_INIT;
-        acd->defend = N_ACD_DEFEND_NEVER;
-        acd->events = (CList)C_LIST_INIT(acd->events);
-        acd->last_conflict = TIME_INFINITY;
+        *acd = (NAcd)N_ACD_NULL(*acd);
+        acd->ifindex = config->ifindex;
+        memcpy(acd->mac, config->mac, ETH_ALEN);
 
         r = n_acd_get_random(&acd->seed);
-        if (r < 0)
+        if (r)
                 return r;
 
         acd->fd_epoll = epoll_create1(EPOLL_CLOEXEC);
-        if (acd->fd_epoll < 0) {
-                r = -n_acd_errno();
-                goto error;
-        }
+        if (acd->fd_epoll < 0)
+                return -n_acd_errno();
 
-        acd->fd_timer = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK);
-        if (acd->fd_timer < 0 && errno == EINVAL) {
-                /*
-		 * Fall back to CLOCK_MONOTONIC when CLOCK_BOOTTIME is
-		 * not available (kernel < 3.15).
-                 */
-                acd->fd_timer = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
-        }
-        if (acd->fd_timer < 0) {
-                r = -n_acd_errno();
-                goto error;
-        }
+        r = timer_init(&acd->timer);
+        if (r < 0)
+                return r;
 
-        r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, acd->fd_timer,
+        acd->max_bpf_map = 8;
+
+        r = n_acd_bpf_map_create(&acd->fd_bpf_map, acd->max_bpf_map);
+        if (r)
+                return r;
+
+        r = n_acd_bpf_compile(&fd_bpf_prog, acd->fd_bpf_map, (struct ether_addr*) acd->mac);
+        if (r)
+                return r;
+
+        r = n_acd_socket_new(&acd->fd_socket, fd_bpf_prog, config);
+        if (r)
+                return r;
+
+        r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, acd->timer.fd,
                       &(struct epoll_event){
                               .events = EPOLLIN,
                               .data.u32 = N_ACD_EPOLL_TIMER,
                       });
-        if (r < 0) {
-                r = -n_acd_errno();
-                goto error;
-        }
+        if (r < 0)
+                return -n_acd_errno();
+
+        r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, acd->fd_socket,
+                      &(struct epoll_event){
+                              .events = EPOLLIN,
+                              .data.u32 = N_ACD_EPOLL_SOCKET,
+                      });
+        if (r < 0)
+                return -n_acd_errno();
 
         *acdp = acd;
+        acd = NULL;
         return 0;
-
-error:
-        n_acd_free(acd);
-        return r;
 }
 
-/**
- * n_acd_free() - free an ACD context
- *
- * Frees all resources held by the context. This may be called at any time,
- * but doing so invalidates all data owned by the context.
- *
- * Return: NULL.
- */
-_public_ void n_acd_free(NAcd *acd) {
-        NAcdEventNode *node;
+static void n_acd_free(NAcd *acd) {
+        NAcdEventNode *node, *t_node;
 
         if (!acd)
                 return;
 
-        n_acd_reset(acd);
+        c_list_for_each_entry_safe(node, t_node, &acd->event_list, acd_link)
+                n_acd_event_node_free(node);
 
-        acd->current = n_acd_event_node_free(acd->current);
+        assert(c_rbtree_is_empty(&acd->ip_tree));
 
-        while ((node = c_list_first_entry(&acd->events, NAcdEventNode, link)))
-                n_acd_event_node_free(node);
+        if (acd->fd_socket >= 0) {
+                assert(acd->fd_epoll >= 0);
+                epoll_ctl(acd->fd_epoll, EPOLL_CTL_DEL, acd->fd_socket, NULL);
+                close(acd->fd_socket);
+                acd->fd_socket = -1;
+        }
 
-        assert(acd->fd_socket < 0);
+        if (acd->fd_bpf_map >= 0) {
+                close(acd->fd_bpf_map);
+                acd->fd_bpf_map = -1;
+        }
 
-        if (acd->fd_timer >= 0) {
+        if (acd->timer.fd >= 0) {
                 assert(acd->fd_epoll >= 0);
-                epoll_ctl(acd->fd_epoll, EPOLL_CTL_DEL, acd->fd_timer, NULL);
-                close(acd->fd_timer);
-                acd->fd_timer = -1;
+                epoll_ctl(acd->fd_epoll, EPOLL_CTL_DEL, acd->timer.fd, NULL);
+                timer_deinit(&acd->timer);
         }
 
         if (acd->fd_epoll >= 0) {
@@ -333,270 +329,163 @@ _public_ void n_acd_free(NAcd *acd) {
 }
 
 /**
- * n_acd_get_fd() - get pollable file descriptor
- * @acd:        ACD context
- * @fdp:        output argument for file descriptor
- *
- * Returns a file descriptor in @fdp. This filedescriptor can be polled by
- * the caller to indicate when the ACD context can be dispatched.
+ * XXX
  */
-_public_ void n_acd_get_fd(NAcd *acd, int *fdp) {
-        *fdp = acd->fd_epoll;
+_public_ NAcd *n_acd_ref(NAcd *acd) {
+        if (acd)
+                ++acd->n_refs;
+        return acd;
 }
 
-static int n_acd_push_event(NAcd *acd, unsigned int event, uint16_t *operation, uint8_t (*sender)[6], uint8_t (*target)[4]) {
-        NAcdEventNode *node;
-        int r;
-
-        r = n_acd_event_node_new(&node, event);
-        if (r < 0)
-                return r;
-
-        switch (event) {
-        case N_ACD_EVENT_USED:
-                node->event.used.operation = be16toh(*operation);
-                memcpy(node->sender, sender, sizeof(node->sender));
-                node->event.used.sender = node->sender;
-                node->event.used.n_sender = sizeof(node->sender);
-                memcpy(&node->event.used.target, target, sizeof(node->event.used.target));
-                break;
-        case N_ACD_EVENT_CONFLICT:
-                node->event.conflict.operation = be16toh(*operation);
-                memcpy(node->sender, sender, sizeof(node->sender));
-                node->event.used.sender = node->sender;
-                node->event.used.n_sender = sizeof(node->sender);
-                memcpy(&node->event.conflict.target, target, sizeof(node->event.conflict.target));
-                break;
-        case N_ACD_EVENT_DEFENDED:
-                node->event.defended.operation = be16toh(*operation);
-                memcpy(node->sender, sender, sizeof(node->sender));
-                node->event.used.sender = node->sender;
-                node->event.used.n_sender = sizeof(node->sender);
-                memcpy(&node->event.defended.target, target, sizeof(node->event.defended.target));
-                break;
-        case N_ACD_EVENT_READY:
-        case N_ACD_EVENT_DOWN:
-                break;
-        default:
-                assert(0);
-        }
-
-        c_list_link_tail(&acd->events, &node->link);
-
-        return 0;
-}
-
-static int n_acd_now(uint64_t *nowp) {
-        struct timespec ts;
-        int r;
-
-        r = clock_gettime(CLOCK_BOOTTIME, &ts);
-        if (r < 0)
-                return -n_acd_errno();
-
-        *nowp = ts.tv_sec * UINT64_C(1000000) + ts.tv_nsec / UINT64_C(1000);
-        return 0;
+/**
+ * XXX
+ */
+_public_ NAcd *n_acd_unref(NAcd *acd) {
+        if (acd && !--acd->n_refs)
+                n_acd_free(acd);
+        return NULL;
 }
 
-static int n_acd_schedule(NAcd *acd, uint64_t u_timeout, unsigned int u_jitter) {
-        uint64_t u_next = u_timeout;
+int n_acd_raise(NAcd *acd, NAcdEventNode **nodep, unsigned int event) {
+        NAcdEventNode *node;
         int r;
 
-        /*
-         * ACD specifies jitter values to reduce packet storms on the local
-         * link. This call accepts the maximum relative jitter value in
-         * microseconds as @u_jitter. We then use rand_r(3p) to get a
-         * pseudo-random jitter on top of the real timeout given as @u_timeout.
-         * Note that rand_r() is fine for this. Before you try to improve the
-         * RNG, you better spend some time securing ARP.
-         */
-        if (u_jitter)
-                u_next += rand_r(&acd->seed) % u_jitter;
+        r = n_acd_event_node_new(&node);
+        if (r)
+                return r;
 
-        /*
-         * Setting .it_value to 0 in timerfd_settime() disarms the timer. Avoid
-         * this and always schedule at least 1us. Otherwise, we'd have to
-         * recursively call into the time-out handler, which we really want to
-         * avoid. No reason to optimize performance here.
-         */
-        if (!u_next)
-                u_next = 1;
-
-        r = timerfd_settime(acd->fd_timer, 0,
-                            &(struct itimerspec){ .it_value = {
-                                    .tv_sec = u_next / UINT64_C(1000000),
-                                    .tv_nsec = u_next % UINT64_C(1000000) * UINT64_C(1000),
-                            } }, NULL);
-        if (r < 0)
-                return -n_acd_errno();
+        node->event.event = event;
+        c_list_link_tail(&acd->event_list, &node->acd_link);
 
+        if (nodep)
+                *nodep = node;
         return 0;
 }
 
-static int n_acd_send(NAcd *acd, const struct in_addr *spa) {
+int n_acd_send(NAcd *acd, const struct in_addr *tpa, const struct in_addr *spa) {
         struct sockaddr_ll address = {
                 .sll_family = AF_PACKET,
                 .sll_protocol = htobe16(ETH_P_ARP),
-                .sll_ifindex = acd->config.ifindex,
+                .sll_ifindex = acd->ifindex,
                 .sll_halen = ETH_ALEN,
                 .sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
         };
         struct ether_arp arp = {
-                .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER),
-                .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP),
-                .ea_hdr.ar_hln = sizeof(acd->mac),
-                .ea_hdr.ar_pln = sizeof(uint32_t),
-                .ea_hdr.ar_op = htobe16(ARPOP_REQUEST),
+                .ea_hdr = {
+                        .ar_hrd = htobe16(ARPHRD_ETHER),
+                        .ar_pro = htobe16(ETHERTYPE_IP),
+                        .ar_hln = sizeof(acd->mac),
+                        .ar_pln = sizeof(uint32_t),
+                        .ar_op = htobe16(ARPOP_REQUEST),
+                },
         };
         ssize_t l;
+        int r;
 
         memcpy(arp.arp_sha, acd->mac, sizeof(acd->mac));
-        memcpy(arp.arp_tpa, &acd->config.ip.s_addr, sizeof(uint32_t));
+        memcpy(arp.arp_tpa, &tpa->s_addr, sizeof(uint32_t));
 
         if (spa)
                 memcpy(arp.arp_spa, &spa->s_addr, sizeof(spa->s_addr));
 
-        l = sendto(acd->fd_socket, &arp, sizeof(arp), MSG_NOSIGNAL, (struct sockaddr *)&address, sizeof(address));
-        if (l == (ssize_t)sizeof(arp)) {
-                /* Packet was properly sent. */
-                return 0;
-        } else if (l >= 0) {
-                /*
-                 * Ugh. The packet was truncated. This should not happen, but
-                 * lets just pretend the packet was dropped.
-                 */
-                return 0;
-        } else if (errno == EAGAIN || errno == ENOBUFS) {
+        l = sendto(acd->fd_socket,
+                   &arp,
+                   sizeof(arp),
+                   MSG_NOSIGNAL,
+                   (struct sockaddr *)&address,
+                   sizeof(address));
+        if (l < 0) {
+                if (errno == EAGAIN || errno == ENOBUFS) {
+                        /*
+                         * We never maintain outgoing queues. We rely on the
+                         * network device to do that for us. In case the queues
+                         * are full, or the kernel refuses to queue the packet
+                         * for other reasons, we must tell our caller that the
+                         * packet was dropped.
+                         */
+                        return N_ACD_E_DROPPED;
+                } else if (errno == ENETDOWN || errno == ENXIO) {
+                        /*
+                         * These errors happen if the network device went down
+                         * or was actually removed. We always propagate this as
+                         * event, so the user can react accordingly (similarly
+                         * to the recvmmsg(2) handler). In case the user does
+                         * not immediately react, we also tell our caller that
+                         * the packet was dropped, so we don't erroneously
+                         * treat this as success.
+                         */
+
+                        r = n_acd_raise(acd, NULL, N_ACD_EVENT_DOWN);
+                        if (r)
+                                return r;
+
+                        return N_ACD_E_DROPPED;
+                }
+
                 /*
-                 * In case the output buffer is full, the packet is silently
-                 * dropped. This is just as if the physical layer happened to
-                 * drop the packet. We are not on a reliable medium, so no
-                 * reason to pretend we are.
+                 * Random network error. We treat this as fatal and propagate
+                 * the error, so it is noticed and can be investigated.
                  */
-                return 0;
-        } else if (errno == ENETDOWN || errno == ENXIO) {
+                return -n_acd_errno();
+        } else if (l != (ssize_t)sizeof(arp)) {
                 /*
-                 * We get ENETDOWN if the network-device goes down or is
-                 * removed. ENXIO might happen on async send-operations if the
-                 * network-device was unplugged and thus the kernel is no
-                 * longer aware of it.
-                 * In any case, we do not allow proceeding with this socket. We
-                 * stop the engine and notify the user gracefully.
+                 * Ugh, the kernel modified the packet. This is unexpected. We
+                 * consider the packet lost.
                  */
-                return -N_ACD_E_DOWN;
+                return N_ACD_E_DROPPED;
         }
 
-        return -n_acd_errno();
+        return 0;
 }
 
-static void n_acd_remember_conflict(NAcd *acd, uint64_t now) {
-        if (++acd->n_conflicts >= N_ACD_RFC_MAX_CONFLICTS) {
-                acd->n_conflicts = N_ACD_RFC_MAX_CONFLICTS;
-                acd->last_conflict = now;
-        }
+/**
+ * n_acd_get_fd() - get pollable file descriptor
+ * @acd:        ACD context
+ * @fdp:        output argument for file descriptor
+ *
+ * Returns a file descriptor in @fdp. This file descriptor can be polled by
+ * the caller to indicate when the ACD context can be dispatched.
+ */
+_public_ void n_acd_get_fd(NAcd *acd, int *fdp) {
+        *fdp = acd->fd_epoll;
 }
 
 static int n_acd_handle_timeout(NAcd *acd) {
+        NAcdProbe *probe;
+        uint64_t now;
         int r;
 
-        switch (acd->state) {
-        case N_ACD_STATE_PROBING:
-                /*
-                 * We are still PROBING. We send 3 probes with a random timeout
-                 * scheduled between each. If, after a fixed timeout, we did
-                 * not receive any conflict we consider the probing successful.
-                 */
-                if (acd->n_iteration >= N_ACD_RFC_PROBE_NUM) {
-                        /*
-                         * All 3 probes succeeded and we waited enough to
-                         * consider this address usable by now. Do not announce
-                         * the address, yet. We must first give the caller a
-                         * chance to configure the address (so they can answer
-                         * ARP requests), before announcing it. But our
-                         * callbacks are not necessarily synchronous (we want
-                         * to allow IPC there), so just notify the caller and
-                         * wait for further instructions, thus effectively
-                         * increasing the probe-wait.
-                         */
-                        r = n_acd_push_event(acd, N_ACD_EVENT_READY, NULL, NULL, NULL);
-                        if (r)
-                                return r;
-
-                        acd->state = N_ACD_STATE_CONFIGURING;
-                } else {
-                        /*
-                         * We have not sent all 3 probes, yet. A timer fired,
-                         * so we are ready to send the next probe. If this is
-                         * the third probe, schedule a timer for ANNOUNCE_WAIT
-                         * to give other peers a chance to answer. If this is
-                         * not the third probe, wait between PROBE_MIN and
-                         * PROBE_MAX for the next probe.
-                         */
-
-                        r = n_acd_send(acd, NULL);
-                        /*
-                         * During probe we must respect the total timeout and so
-                         * we ignore errors caused by a down interface.
-                         */
-                        if (r < 0 && r != -N_ACD_E_DOWN)
-                                return r;
-
-                        if (++acd->n_iteration >= N_ACD_RFC_PROBE_NUM)
-                                r = n_acd_schedule(acd, acd->timeout_multiplier * N_ACD_RFC_ANNOUNCE_WAIT_USEC, 0);
-                        else
-                                r = n_acd_schedule(acd, acd->timeout_multiplier * N_ACD_RFC_PROBE_MIN_USEC,
-                                                   acd->timeout_multiplier * (N_ACD_RFC_PROBE_MAX_USEC - N_ACD_RFC_PROBE_MIN_USEC));
-                        if (r < 0)
-                                return r;
-                }
-
-                break;
+        /*
+         * Read the current time once, and handle all timouts that triggered
+         * before the current time. Rereading the current time in each loop
+         * might risk creating a live-lock, and the fact that we read the
+         * time after reading the timer guarantees that the timeout which
+         * woke us up is hanlded.
+         *
+         * When there are no more timeouts to handle at the given time, we
+         * rearm the timer to potentially wake us up again in the future.
+         */
+        timer_now(&acd->timer, &now);
 
-        case N_ACD_STATE_ANNOUNCING:
-                /*
-                 * We are ANNOUNCING, meaning the caller configured the address
-                 * on the interface and is actively using it. We send 3
-                 * announcements out, in a short interval, and then just
-                 * perform passive conflict detection.
-                 * Note that once all 3 announcements are sent, we no longer
-                 * schedule a timer, so this part should not trigger, anymore.
-                 */
+        for (;;) {
+                Timeout *timeout;
 
-                r = n_acd_send(acd, &acd->config.ip);
+                r = timer_pop_timeout(&acd->timer, now, &timeout);
                 if (r < 0) {
-                        if (r != -N_ACD_E_DOWN)
-                                return r;
-                        /*
-                         * We want to send all the 3 announcements even if the
-                         * interface goes temporarily down. Therefore, if send()
-                         * fails, don't increment the iteration and try again.
-                         */
-                } else
-                        acd->n_iteration++;
-
-                if (acd->n_iteration < N_ACD_RFC_ANNOUNCE_NUM) {
+                        return r;
+                } else if (!timeout) {
                         /*
-                         * Announcements are always scheduled according to the
-                         * time-intervals specified in the spec. We always use
-                         * the RFC5227-mandated multiplier.
-                         * If you reconsider this, note that timeout_multiplier
-                         * might be 0 here.
+                         * There are no more timeouts pending before @now. Rearm
+                         * the timer to fire again at the next timeout.
                          */
-                        r = n_acd_schedule(acd, N_ACD_TIMEOUT_RFC5227 * N_ACD_RFC_ANNOUNCE_INTERVAL_USEC, 0);
-                        if (r < 0)
-                                return r;
+                        timer_rearm(&acd->timer);
+                        break;
                 }
 
-                break;
-
-        case N_ACD_STATE_INIT:
-        case N_ACD_STATE_CONFIGURING:
-        default:
-                /*
-                 * There are no timeouts in these states. If we trigger one,
-                 * something is fishy. Let the caller deal with this.
-                 */
-                return -EIO;
+                probe = (void *)timeout - offsetof(NAcdProbe, timeout);
+                r = n_acd_probe_handle_timeout(probe);
+                if (r)
+                        return r;
         }
 
         return 0;
@@ -604,136 +493,94 @@ static int n_acd_handle_timeout(NAcd *acd) {
 
 static int n_acd_handle_packet(NAcd *acd, struct ether_arp *packet) {
         bool hard_conflict;
-        uint64_t now;
+        NAcdProbe *probe;
+        uint32_t addr;
+        CRBNode *node;
         int r;
 
         /*
-         * Via BPF we discard any non-conflict packets. There are only 2 types
-         * that can pass: A conflict on the Sender Protocol Address, or a
-         * conflict on the Target Protocol Address.
+         * We are interested in 2 kinds of ARP messages:
+         *
+         *  1) Someone who is *NOT* us sends *ANY* ARP message with our IP
+         *     address as sender. This is never good, because it implies an
+         *     address conflict.
+         *     We call this a hard-conflict.
+         *
+         *  2) Someone who is *NOT* us sends an ARP REQUEST without any sender
+         *     IP, but our IP as target. This implies someone else performs an
+         *     ARP Probe with our address. This also implies a conflict, but
+         *     one that can be resolved by responding to the probe.
+         *     We call this a soft-conflict.
+         *
+         * We are never interested in any other ARP message. The kernel already
+         * deals with everything else, hence, we can silently ignore those.
          *
-         * The former we call a hard-conflict. It implies that the sender uses
-         * the address already. We must always catch this and in some way react
-         * to it. Any kind, REQUEST or REPLY must be caught (though it is
-         * unlikely that we ever catch REPLIES since they tend to be unicasts).
+         * Now, we simply check whether a sender-address is set. This allows us
+         * to distinguish both cases. We then check further conditions, so we
+         * can bail out early if neither is the case.
          *
-         * However, in case the Target Protocol Address matches, we just know
-         * that somebody is looking for the address. Hence, we must also check
-         * that the packet is an ARP-Probe (Sender Protocol Address is 0). If
-         * it is, it means someone else does ACD on our address. We call this a
-         * soft conflict.
+         * Lastly, we perform a lookup in our probe-set to check whether the
+         * address actually matches, so we can let these probes dispatch the
+         * message. Note that we allow duplicate probes, so we need to dispatch
+         * each matching probe, not just one.
          */
-        if (!memcmp(packet->arp_spa, (uint8_t[4]){ }, sizeof(packet->arp_spa)) &&
-            !memcmp(packet->arp_tpa, &acd->config.ip.s_addr, sizeof(packet->arp_tpa)) &&
-            packet->ea_hdr.ar_op == htobe16(ARPOP_REQUEST)) {
-                hard_conflict = false;
-        } else if (!memcmp(packet->arp_spa, &acd->config.ip.s_addr, sizeof(packet->arp_spa))) {
+
+        if (memcmp(packet->arp_spa, (uint8_t[4]){ }, sizeof(packet->arp_spa))) {
+                memcpy(&addr, packet->arp_spa, sizeof(addr));
                 hard_conflict = true;
+        } else if (packet->ea_hdr.ar_op == htobe16(ARPOP_REQUEST)) {
+                memcpy(&addr, packet->arp_tpa, sizeof(addr));
+                hard_conflict = false;
         } else {
                 /*
-                 * Ignore anything that is specific enough to match the BPF
-                 * filter, but is none of the conflicts described above.
+                 * The BPF filter will not let through any other packet.
                  */
-                return 0;
+                return -EIO;
         }
 
-        r = n_acd_now(&now);
-        if (r < 0)
-                return r;
-
-        switch (acd->state) {
-        case N_ACD_STATE_PROBING:
-                /*
-                 * Regardless whether this is a hard or soft conflict, we must
-                 * treat this as a probe failure. That is, notify the caller of
-                 * the conflict and wait for further instructions. We do not
-                 * react to this, until the caller tells us what to do. But we
-                 * immediately stop the engine, since there is no point in
-                 * continuing the probing.
-                 */
-                n_acd_remember_conflict(acd, now);
-                n_acd_reset(acd);
-                r = n_acd_push_event(acd, N_ACD_EVENT_USED, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa);
-                if (r)
-                        return r;
-
-                break;
-
-        case N_ACD_STATE_CONFIGURING:
-                /*
-                 * We are waiting for the caller to configure the interface and
-                 * start ANNOUNCING. In this state, we cannot defend the address
-                 * as that would indicate that it is ready to be used, and we
-                 * cannot signal CONFLICT or USED as the caller may already have
-                 * started to use the address (and may have configured the engine
-                 * to always defend it, which means they should be able to rely on
-                 * never losing it after READY). Simply drop the event, and rely
-                 * on the anticipated ANNOUNCE to trigger it again.
-                 */
+        /* Find top-most node that matches @addr. */
+        node = acd->ip_tree.root;
+        while (node) {
+                probe = c_rbnode_entry(node, NAcdProbe, ip_node);
+                if (addr < probe->ip.s_addr)
+                        node = node->left;
+                else if (addr > probe->ip.s_addr)
+                        node = node->right;
+                else
+                        break;
+        }
 
-                break;
+        /*
+         * If the address is unknown, we drop the package. This might happen if
+         * the kernel queued the packet and passed the BPF filter, but we
+         * modified the set before dequeuing the message.
+         */
+        if (!node)
+                return 0;
 
-        case N_ACD_STATE_ANNOUNCING:
-                /*
-                 * We were already instructed to announce the address, which
-                 * means the address is configured and in use. Hence, the
-                 * caller is responsible to serve regular ARP queries. Meaning,
-                 * we can ignore any soft conflicts (other peers doing ACD).
-                 *
-                 * But if we see a hard-conflict, we either defend the address
-                 * according to the caller's instructions, or we report the
-                 * conflict and bail out.
-                 */
+        /* Forward to left-most child that still matches @addr. */
+        while (node->left && addr == c_rbnode_entry(node->left,
+                                                    NAcdProbe,
+                                                    ip_node)->ip.s_addr)
+                node = node->left;
 
-                if (!hard_conflict)
-                        break;
+        /* Iterate all matching entries in-order. */
+        do {
+                probe = c_rbnode_entry(node, NAcdProbe, ip_node);
 
-                if (acd->defend == N_ACD_DEFEND_NEVER) {
-                        n_acd_remember_conflict(acd, now);
-                        n_acd_reset(acd);
-                        r = n_acd_push_event(acd, N_ACD_EVENT_CONFLICT, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa);
-                        if (r)
-                                return r;
-                } else {
-                        if (now > acd->last_defend + N_ACD_RFC_DEFEND_INTERVAL_USEC) {
-                                r = n_acd_send(acd, &acd->config.ip);
-                                if (r < 0)
-                                        return r;
-
-                                acd->last_defend = now;
-                                r = n_acd_push_event(acd, N_ACD_EVENT_DEFENDED, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa);
-                                if (r)
-                                        return r;
-                        } else if (acd->defend == N_ACD_DEFEND_ONCE) {
-                                n_acd_remember_conflict(acd, now);
-                                n_acd_reset(acd);
-                                r = n_acd_push_event(acd, N_ACD_EVENT_CONFLICT, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa);
-                                if (r)
-                                        return r;
-                        } else {
-                                r = n_acd_push_event(acd, N_ACD_EVENT_DEFENDED, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa);
-                                if (r)
-                                        return r;
-                        }
-                }
+                r = n_acd_probe_handle_packet(probe, packet, hard_conflict);
+                if (r)
+                        return r;
 
-                break;
-
-        case N_ACD_STATE_INIT:
-        default:
-                /*
-                 * The socket should not be dispatched in those states, since
-                 * it is neither allocated nor added to epoll. Fail hard if we
-                 * trigger this somehow.
-                 */
-                return -EIO;
-        }
+                node = c_rbnode_next(node);
+        } while (node && addr == c_rbnode_entry(node,
+                                                NAcdProbe,
+                                                ip_node)->ip.s_addr);
 
         return 0;
 }
 
 static int n_acd_dispatch_timer(NAcd *acd, struct epoll_event *event) {
-        uint64_t v;
         int r;
 
         if (event->events & (EPOLLHUP | EPOLLERR)) {
@@ -746,97 +593,113 @@ static int n_acd_dispatch_timer(NAcd *acd, struct epoll_event *event) {
         }
 
         if (event->events & EPOLLIN) {
-                for (unsigned int i = 0; i < 128; ++i) {
-                        r = read(acd->fd_timer, &v, sizeof(v));
-                        if (r == sizeof(v)) {
-                                /*
-                                 * We successfully read a timer-value. Handle it and
-                                 * return. We do NOT fall-through to EPOLLHUP handling,
-                                 * as we always must drain buffers first.
-                                 */
-                                return n_acd_handle_timeout(acd);
-                        } else if (r >= 0) {
-                                /*
-                                 * Kernel guarantees 8-byte reads; fail hard if it
-                                 * suddenly starts doing weird shit. No clue what to do
-                                 * with those values, anyway.
-                                 */
-                                return -EIO;
-                        } else if (errno == EAGAIN) {
-                                /*
-                                 * No more pending events.
-                                 */
-                                return 0;
-                        } else {
-                                /*
-                                 * Something failed. We use CLOCK_BOOTTIME, so
-                                 * ECANCELED cannot happen. Hence, there is no error
-                                 * that we could gracefully handle. Fail hard and let
-                                 * the caller deal with it.
-                                 */
-                                return -n_acd_errno();
-                        }
-                }
+                r = timer_read(&acd->timer);
+                if (r <= 0)
+                        return r;
 
-                return N_ACD_E_PREEMPTED;
+                assert(r == TIMER_E_TRIGGERED);
+
+                /*
+                 * A timer triggered, handle all pending timeouts at a given
+                 * point in time. There can only be a finite number of pending
+                 * timeouts, any new ones will be in the future, so not handled
+                 * now, but guaranteed to wake us up again when they do trigger.
+                 */
+                r = n_acd_handle_timeout(acd);
+                if (r)
+                        return r;
         }
 
         return 0;
 }
 
+static bool n_acd_packet_is_valid(NAcd *acd, void *packet, size_t n_packet) {
+        struct ether_arp *arp;
+
+        /*
+         * The eBPF filter will ensure that this function always returns true, however,
+         * this allows the eBPF filter to be an optional optimization which is necessary
+         * on older kernels.
+         *
+         * See comments in n-acd-bpf.c for details.
+         */
+
+        if (n_packet != sizeof(*arp))
+                return false;
+
+        arp = packet;
+
+        if (arp->arp_hrd != htobe16(ARPHRD_ETHER))
+                return false;
+
+        if (arp->arp_pro != htobe16(ETHERTYPE_IP))
+                return false;
+
+        if (arp->arp_hln != sizeof(struct ether_addr))
+                return false;
+
+        if (arp->arp_pln != sizeof(struct in_addr))
+                return false;
+
+        if (!memcmp(arp->arp_sha, acd->mac, sizeof(struct ether_addr)))
+                return false;
+
+        if (memcmp(arp->arp_spa, &((struct in_addr) { INADDR_ANY }), sizeof(struct in_addr))) {
+                if (arp->arp_op != htobe16(ARPOP_REQUEST) && arp->arp_op != htobe16(ARPOP_REPLY))
+                        return false;
+        } else if (arp->arp_op != htobe16(ARPOP_REQUEST)) {
+                return false;
+        }
+
+        return true;
+}
+
 static int n_acd_dispatch_socket(NAcd *acd, struct epoll_event *event) {
-        struct ether_arp packet;
-        ssize_t l;
+        const size_t n_batch = 8;
+        struct mmsghdr msgs[n_batch];
+        struct iovec iovecs[n_batch];
+        struct ether_arp data[n_batch];
+        size_t i;
+        int r, n;
+
+        for (i = 0; i < n_batch; ++i) {
+                iovecs[i].iov_base = data + i;
+                iovecs[i].iov_len = sizeof(data[i]);
+                msgs[i].msg_hdr = (struct msghdr){
+                        .msg_iov = iovecs + i,
+                        .msg_iovlen = 1,
+                };
+        }
 
-        for (unsigned int i = 0; i < 128; ++i) {
-                /*
-                 * Regardless whether EPOLLIN is set in @event->events, we always
-                 * invoke recv(2). This is a safety-net for sockets, which always fetch
-                 * queued errors on all syscalls. That means, if anything failed on the
-                 * socket, we will be notified via recv(2). This simplifies the code
-                 * and avoid magic EPOLLIN/ERR/HUP juggling.
-                 *
-                 * Note that we must use recv(2) over read(2), since the latter cannot
-                 * deal with empty packets properly.
-                 *
-                 * We explicitly skip passing MSG_TRUNC here. We *WANT*
-                 * overlong packets to be retrieved and truncated. Ethernet
-                 * frames might not have byte-granular lengths. Real hardware
-                 * does add trailing padding/garbage, so we must discard this
-                 * here.
-                 */
-                l = recv(acd->fd_socket, &packet, sizeof(packet), 0);
-                if (l == (ssize_t)sizeof(packet)) {
-                        /*
-                         * We read a full ARP packet. We never fall-through to EPOLLHUP
-                         * handling, as we always must drain buffers first.
-                         */
-                        return n_acd_handle_packet(acd, &packet);
-                } else if (l >= 0) {
-                        /*
-                         * The BPF filter discards short packets, so error out
-                         * if something slips through for any reason. Don't silently
-                         * ignore it, since we explicitly want to know if something
-                         * went fishy.
-                         */
-                        return -EIO;
-                } else if (errno == ENETDOWN || errno == ENXIO) {
+        /*
+         * We always directly call into recvmmsg(2), regardless which EPOLL*
+         * event is signalled. On sockets, the recv(2)-family of syscalls does
+         * a suitable job of handling all possible scenarios and telling us
+         * about it. Hence, lets take the easy route and always ask the kernel
+         * about the current state.
+         */
+        n = recvmmsg(acd->fd_socket, msgs, n_batch, 0, NULL);
+        if (n < 0) {
+                if (errno == ENETDOWN) {
                         /*
-                         * The network device went down or was removed. Ignore
-                         * such errors and let the pending probe time out.
-                         * Subsequent reads will simply return EAGAIN until the
-                         * device is up again and has data queued.
+                         * We get ENETDOWN if the network-device goes down or
+                         * is removed. This error is temporary and only queued
+                         * once. Subsequent reads will simply return EAGAIN
+                         * until the device is up again and has data queued.
+                         * Usually, the caller should tear down all probes when
+                         * an interface goes down, but we leave it up to the
+                         * caller to decide what to do. We propagate the code
+                         * and continue.
                          */
-                        return 0;
+                        return n_acd_raise(acd, NULL, N_ACD_EVENT_DOWN);
                 } else if (errno == EAGAIN) {
                         /*
-                         * We cannot read data from the socket (we got EAGAIN). As a safety net
-                         * check for EPOLLHUP/ERR. Those cannot be disabled with epoll, so we
-                         * must make sure to not busy-loop by ignoring them. Note that we know
-                         * recv(2) on sockets to return an error if either of these epoll-flags
-                         * is set. Hence, if we did not handle it above, we have no other way
-                         * but treating those flags as fatal errors and returning them to the
-                         * caller.
+                         * There is no more data queued and we did not get
+                         * preempted. Everything is good to go.
+                         * As a safety-net against busy-looping, we do check
+                         * for HUP/ERR. Neither should be set, since they imply
+                         * error-dequeue behavior on all socket calls. Lets
+                         * fail hard if we trigger it, so we can investigate.
                          */
                         if (event->events & (EPOLLHUP | EPOLLERR))
                                 return -EIO;
@@ -844,35 +707,63 @@ static int n_acd_dispatch_socket(NAcd *acd, struct epoll_event *event) {
                         return 0;
                 } else {
                         /*
-                         * Cannot dispatch the packet. This might be due to OOM, HUP,
-                         * or something else. We cannot handle it gracefully so forward
-                         * to the caller.
+                         * Something went wrong. Propagate the error-code, so
+                         * this can be investigated.
                          */
                         return -n_acd_errno();
                 }
+        } else if (n >= (ssize_t)n_batch) {
+                /*
+                 * If all buffers were filled with data, we cannot be sure that
+                 * there is nothing left to read. But to avoid starvation, we
+                 * cannot loop on this condition. Instead, we mark the context
+                 * as preempted so the caller can call us again.
+                 * Note that in level-triggered event-loops this condition can
+                 * be neglected, but in edge-triggered event-loops it is
+                 * crucial to forward this information.
+                 *
+                 * On the other hand, there are several conditions where the
+                 * kernel might return less batches than requested, but was
+                 * still preempted. However, all of those cases require the
+                 * preemption to have triggered a wakeup *after* we entered
+                 * recvmmsg(). Hence, even if we did not recognize the
+                 * preemption, an edge must have triggered and as such we will
+                 * handle the event on the next turn.
+                 */
+                acd->preempted = true;
+        }
+
+        for (i = 0; (ssize_t)i < n; ++i) {
+                if (!n_acd_packet_is_valid(acd, data + i, msgs[i].msg_len))
+                        continue;
+                /*
+                 * Handle the packet. Bail out if something went wrong. Note
+                 * that this must be fatal errors, since we discard all other
+                 * packets that follow.
+                 */
+                r = n_acd_handle_packet(acd, data + i);
+                if (r)
+                        return r;
         }
 
-        return N_ACD_E_PREEMPTED;
+        return 0;
 }
 
 /**
- * n_acd_dispatch() - dispatch ACD context
- * @acd:        ACD context
- *
- * Return: 0 on successful dispatch of all pending events, N_ACD_E_PREEMPT in
- *         case there are more still more events to be dispatched, or a
- *         negative error code on failure.
+ * XXX
  */
 _public_ int n_acd_dispatch(NAcd *acd) {
         struct epoll_event events[2];
         int n, i, r = 0;
-        bool preempted = false;
 
         n = epoll_wait(acd->fd_epoll, events, sizeof(events) / sizeof(*events), 0);
         if (n < 0) {
+                /* Linux never returns EINTR if `timeout == 0'. */
                 return -n_acd_errno();
         }
 
+        acd->preempted = false;
+
         for (i = 0; i < n; ++i) {
                 switch (events[i].data.u32) {
                 case N_ACD_EPOLL_TIMER:
@@ -882,35 +773,16 @@ _public_ int n_acd_dispatch(NAcd *acd) {
                         r = n_acd_dispatch_socket(acd, events + i);
                         break;
                 default:
+                        assert(0);
                         r = 0;
                         break;
                 }
 
-                if (r == N_ACD_E_PREEMPTED)
-                        preempted = true;
-                else if (r != 0)
-                        break;
-        }
-
-        if (r == -N_ACD_E_DOWN) {
-                /*
-                 * N_ACD_E_DOWN is synthesized whenever we notice
-                 * ENETDOWN-related errors on the network interface. This
-                 * allows bailing out of deep call-paths and then handling the
-                 * error gracefully here.
-                 */
-                n_acd_reset(acd);
-                r = n_acd_push_event(acd, N_ACD_EVENT_DOWN, NULL, NULL, NULL);
                 if (r)
                         return r;
-
-                return 0;
         }
 
-        if (preempted)
-                return N_ACD_E_PREEMPTED;
-        else
-                return r;
+        return acd->preempted ? N_ACD_E_PREEMPTED : 0;
 }
 
 /**
@@ -920,343 +792,75 @@ _public_ int n_acd_dispatch(NAcd *acd) {
  *
  * Returns a pointer to the next pending event. The event is still owend by
  * the context, and is only valid until the next call to n_acd_pop_event()
- * or until the context is freed.
+ * or until the owning object is freed (either the ACD context or the indicated
+ * probe object).
+ *
+ * An event either originates on the ACD context, or one of the configured
+ * probes. If the event-type has a 'probe' pointer, it originated on the
+ * indicated probe (which is *never* NULL), otherwise it originated on the
+ * context.
+ *
+ * Users must call this function repeatedly until either an error is returned,
+ * or the event-pointer is NULL. Wakeups on the epoll-fd are only guaranteed
+ * for each batch of events. Hence, it is the callers responsibility to drain
+ * the event-queue somehow after each call to n_acd_dispatch(). Note that
+ * events can only be added by n_acd_dispatch(), hence, you cannot live-lock
+ * when draining the event queue.
  *
  * The possible events are:
- *  * N_ACD_EVENT_READY:    The configured IP address was probed successfully
+ *  * N_ACD_EVENT_READY:    A configured IP address was probed successfully
  *                          and is ready to be used. Once configured on the
  *                          interface, the caller must call n_acd_announce()
  *                          to announce and start defending the address.
- *                          No further events may be received before
- *                          n_acd_announce() has been called.
  *  * N_ACD_EVENT_USED:     Someone is already using the IP address being
- *                          probed. The engine was stopped, and the caller
- *                          may restart it to try again.
- *  * N_ACD_EVENT_DEFENDED: A conflict was detected for the announced IP
+ *                          probed. The probe is put into stopped state and
+ *                          should be freed by the caller.
+ *  * N_ACD_EVENT_DEFENDED: A conflict was detected for an announced IP
  *                          address, and the engine attempted to defend it.
  *                          This is purely informational, and no action is
  *                          required by the caller.
- *  * N_ACD_EVENT_CONFLICT: A conflict was detected for the announced IP
- *                          address, and the engine was not able to defend
+ *  * N_ACD_EVENT_CONFLICT: A conflict was detected for an announced IP
+ *                          address, and the probe was not able to defend
  *                          it (according to the configured policy). The
- *                          engine has stoppde, the caller must stop using
- *                          the address immediately, and may restart the
- *                          engine to retry.
- *  * N_ACD_EVENT_DOWN:     A network error was detected. The engine was
- *                          stopped and it is the responsibility of the
- *                          caller to restart it once the network may be
- *                          functional again.
+ *                          probe halted, the caller must stop using
+ *                          the address immediately, and should free the probe.
+ *  * N_ACD_EVENT_DOWN:     The specified network interface was put down. The
+ *                          user is recommended to free *ALL* probes and
+ *                          recreate them as soon as the interface is up again.
+ *                          Note that this event is purely informational. The
+ *                          probes will continue running, but all packets will
+ *                          be blackholed, and no network packets are received,
+ *                          until the network is back up again. Hence, from an
+ *                          operational perspective, the legitimacy of the ACD
+ *                          probes is lost and the user better re-probes all
+ *                          addresses.
  *
- * Returns: 0 on success, N_ACD_E_STOPPED if there are no more events and
- *          the engine has been stopped, N_ACD_E_DONE if there are no more
- *          events, but the engine is still running, or a negative error
- *          code on failure.
+ * Returns: 0 on success, negative error code on failure. The popped event is
+ *          returned in @eventp. If no event is pending, NULL is placed in
+ *          @eventp and 0 is returned. If an error is returned, @eventp is left
+ *          untouched.
  */
 _public_ int n_acd_pop_event(NAcd *acd, NAcdEvent **eventp) {
-        acd->current = n_acd_event_node_free(acd->current);
-
-        if (c_list_is_empty(&acd->events)) {
-                if (acd->state == N_ACD_STATE_INIT)
-                        return N_ACD_E_STOPPED;
-                else
-                        return N_ACD_E_DONE;
-        }
-
-        acd->current = c_list_first_entry(&acd->events, NAcdEventNode, link);
-        c_list_unlink(&acd->current->link);
-
-        if (eventp)
-                *eventp = &acd->current->event;
-
-        return 0;
-}
-
-static int n_acd_bind_socket(NAcd *acd, int s) {
-        /*
-         * Due to strict aliasing, we cannot get uint32_t/uint16_t pointers to
-         * acd->config.mac, so provide a union accessor.
-         */
-        const union {
-                uint8_t u8[6];
-                uint16_t u16[3];
-                uint32_t u32[1];
-        } mac = {
-                .u8 = {
-                        acd->mac[0],
-                        acd->mac[1],
-                        acd->mac[2],
-                        acd->mac[3],
-                        acd->mac[4],
-                        acd->mac[5],
-                },
-        };
-        struct sock_filter filter[] = {
-                /*
-                 * Basic ARP header validation. Make sure the packet-length,
-                 * wire type, protocol type, and address lengths are correct.
-                 */
-                BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0),                                                          /* A <- packet length */
-                BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0),                            /* #packet >= #arp-packet ? */
-                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
-                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)),                  /* A <- header */
-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0),                                        /* header == ethernet ? */
-                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
-                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)),                  /* A <- protocol */
-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0),                                        /* protocol == IP ? */
-                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
-                BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hln)),                  /* A <- hardware address length */
-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct ether_addr), 1, 0),                           /* length == sizeof(ether_addr)? */
-                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
-                BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pln)),                  /* A <- protocol address length */
-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct in_addr), 1, 0),                              /* length == sizeof(in_addr) ? */
-                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
-                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)),                   /* A <- operation */
-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0),                                       /* protocol == request ? */
-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0),                                         /* protocol == reply ? */
-                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
-
-                /*
-                 * Sender hardware address must be different from ours. Note
-                 * that BPF runs in big-endian mode, but assumes immediates are
-                 * given in native-endian. This might look weird on 6-byte mac
-                 * addresses, but is needed to revert the BPF magic.
-                 */
-                BPF_STMT(BPF_LD + BPF_IMM, be32toh(mac.u32[0])),                                                /* A <- 4 bytes of client's MAC */
-                BPF_STMT(BPF_MISC + BPF_TAX, 0),                                                                /* X <- A */
-                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_sha)),                        /* A <- 4 bytes of SHA */
-                BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                                         /* A xor X */
-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 6),                                                   /* A == 0 ? */
-                BPF_STMT(BPF_LD + BPF_IMM, be16toh(mac.u16[2])),                                                /* A <- remainder of client's MAC */
-                BPF_STMT(BPF_MISC + BPF_TAX, 0),                                                                /* X <- A */
-                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, arp_sha) + 4),                    /* A <- remainder of SHA */
-                BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                                         /* A xor X */
-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1),                                                   /* A == 0 ? */
-                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
-
-                /*
-                 * Sender protocol address or target protocol address must be
-                 * equal to the one we care about. Again, immediates must be
-                 * given in native-endian.
-                 */
-                BPF_STMT(BPF_LD + BPF_IMM, be32toh(acd->config.ip.s_addr)),                                     /* A <- clients IP */
-                BPF_STMT(BPF_MISC + BPF_TAX, 0),                                                                /* X <- A */
-                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)),                        /* A <- SPA */
-                BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                                         /* X xor A */
-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1),                                                   /* A == 0 ? */
-                BPF_STMT(BPF_RET + BPF_K, 65535),                                                               /* return all */
-                BPF_STMT(BPF_LD + BPF_IMM, be32toh(acd->config.ip.s_addr)),                                     /* A <- clients IP */
-                BPF_STMT(BPF_MISC + BPF_TAX, 0),                                                                /* X <- A */
-                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)),                        /* A <- TPA */
-                BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                                         /* X xor A */
-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1),                                                   /* A == 0 ? */
-                BPF_STMT(BPF_RET + BPF_K, 65535),                                                               /* return all */
-                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
-        };
-        const struct sock_fprog fprog = {
-                .len = sizeof(filter) / sizeof(*filter),
-                .filter = filter,
-        };
-        const struct sockaddr_ll address = {
-                .sll_family = AF_PACKET,
-                .sll_protocol = htobe16(ETH_P_ARP),
-                .sll_ifindex = acd->config.ifindex,
-                .sll_halen = ETH_ALEN,
-                .sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
-        };
-        int r;
-
-        /*
-         * Install a packet filter that matches on the ARP header and
-         * addresses, to reduce the number of wake-ups to a minimum.
-         */
-        r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
-        if (r < 0)
-                return -n_acd_errno();
-
-        /*
-         * Bind the packet-socket to ETH_P_ARP and the specified network
-         * interface.
-         */
-        r = bind(s, (struct sockaddr *)&address, sizeof(address));
-        if (r < 0)
-                return -n_acd_errno();
-
-        return 0;
-}
-
-static int n_acd_setup_socket(NAcd *acd) {
-        int r, s;
-
-        s = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
-        if (s < 0)
-                return -n_acd_errno();
-
-        r = n_acd_bind_socket(acd, s);
-        if (r < 0)
-                goto error;
-
-        r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, s,
-                      &(struct epoll_event){
-                              .events = EPOLLIN,
-                              .data.u32 = N_ACD_EPOLL_SOCKET,
-                      });
-        if (r < 0) {
-                r = -n_acd_errno();
-                goto error;
-        }
+        NAcdEventNode *node, *t_node;
 
-        acd->fd_socket = s;
-        return 0;
-
-error:
-        close(s);
-        return r;
-}
-
-/**
- * n_acd_start() - start the ACD engine
- * @acd:        ACD context
- * @config:     description of interface and desired IP address
- *
- * Start probing the given address on the given interface.
- *
- * The engine must not already be running, and there must not be
- * any pending events.
- *
- * Returns: 0 on success, N_ACD_E_INVALID_ARGUMENT in case the configuration
- *          was invalid, N_ACD_E_BUSY if the engine is running or there are
- *          pending events, or a negative error code on failure.
- */
-_public_ int n_acd_start(NAcd *acd, NAcdConfig *config) {
-        uint64_t now, delay;
-        int r;
-
-        if (config->ifindex <= 0 ||
-            config->transport != N_ACD_TRANSPORT_ETHERNET ||
-            config->n_mac != ETH_ALEN ||
-            !memcmp(config->mac, (uint8_t[ETH_ALEN]){ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, ETH_ALEN) ||
-            !config->ip.s_addr)
-                return N_ACD_E_INVALID_ARGUMENT;
-
-        if (acd->state != N_ACD_STATE_INIT || !c_list_is_empty(&acd->events))
-                return N_ACD_E_BUSY;
-
-        acd->config = *config;
-        memcpy(acd->mac, config->mac, config->n_mac);
-        acd->config.mac = acd->mac;
-        acd->timeout_multiplier = config->timeout_msec;
-
-        r = n_acd_setup_socket(acd);
-        if (r < 0)
-                goto error;
-
-        if (acd->timeout_multiplier) {
-                delay = 0;
-                acd->n_iteration = 0;
-
-                if (acd->last_conflict != TIME_INFINITY) {
-                        r = n_acd_now(&now);
-                        if (r < 0)
-                                goto error;
-
-                        if (now < acd->last_conflict + N_ACD_RFC_RATE_LIMIT_INTERVAL_USEC)
-                                delay = acd->last_conflict + N_ACD_RFC_RATE_LIMIT_INTERVAL_USEC - now;
+        c_list_for_each_entry_safe(node, t_node, &acd->event_list, acd_link) {
+                if (node->is_public) {
+                        n_acd_event_node_free(node);
+                        continue;
                 }
 
-                r = n_acd_schedule(acd, delay, acd->timeout_multiplier * N_ACD_RFC_PROBE_WAIT_USEC);
-                if (r < 0)
-                        goto error;
-        } else {
-                /*
-                 * A zero timeout means we drop the probing alltogether, and behave as if
-                 * the last probe succeeded immediately.
-                 */
-                acd->n_iteration = N_ACD_RFC_PROBE_NUM;
-
-                r = n_acd_schedule(acd, 0, 0);
-                if (r < 0)
-                        goto error;
+                node->is_public = true;
+                *eventp = &node->event;
+                return 0;
         }
 
-        acd->state = N_ACD_STATE_PROBING;
-        acd->defend = N_ACD_DEFEND_NEVER;
-        acd->last_defend = 0;
+        *eventp = NULL;
         return 0;
-
-error:
-        n_acd_reset(acd);
-        return r;
 }
 
 /**
- * n_acd_stop() - stop the ACD engine
- * @acd:        ACD context
- *
- * Stop the engine. No new events may be triggered, but pending events are not
- * flushed. Before calling n_acd_start() again all pending events must be popped.
- *
- * Return: 0 on success, negative error code on failure.
+ * XXX
  */
-_public_ int n_acd_stop(NAcd *acd) {
-        n_acd_reset(acd);
-        return 0;
-}
-
-/**
- * n_acd_announce() - announce the configured IP address
- * @acd:        ACD context
- * @defend:     defence policy
- *
- * Announce the IP address on the local link, and start defending it according
- * to the given policy, which mut be one of N_ACD_DEFEND_ONCE,
- * N_ACD_DEFEND_NEVER, or N_ACD_DEFEND_ALWAYS.
- *
- * This must be called after the engine in response to an N_ACD_EVENT_READY
- * event, and only after the given address has been configured on the given
- * interface.
- *
- * Return: 0 on success, N_ACD_E_INVALID_ARGUMENT in case the defence policy
- *         is invalid, N_ACD_E_BUSY if this is not in response to a
- *         N_ACD_EVENT_READY event, or a negative error code on failure.
- */
-_public_ int n_acd_announce(NAcd *acd, unsigned int defend) {
-        uint64_t now;
-        int r;
-
-        if (defend >= _N_ACD_DEFEND_N)
-                return N_ACD_E_INVALID_ARGUMENT;
-        if (acd->state != N_ACD_STATE_CONFIGURING)
-                return N_ACD_E_BUSY;
-
-        /*
-         * Sending announcements means we finished probing and use the address
-         * now. We therefore reset the conflict counter in case we adhered to
-         * the rate-limit. Since probing is properly delayed, a well-behaving
-         * client will always reset the conflict counter here. However, if you
-         * force-use an address regardless of conflicts, then this will not
-         * trigger and the conflict counter stays untouched.
-         */
-        if (acd->last_conflict != TIME_INFINITY) {
-                r = n_acd_now(&now);
-                if (r < 0)
-                        return r;
-
-                if (now >= acd->last_conflict + N_ACD_RFC_RATE_LIMIT_INTERVAL_USEC)
-                        acd->n_conflicts = 0;
-        }
-
-        /*
-         * Instead of sending the first announcement here, we schedule an idle
-         * timer. This avoids possibly recursing into the user callback. We
-         * should never trigger callbacks from arbitrary stacks, but always
-         * restrict them to the dispatcher.
-         */
-        r = n_acd_schedule(acd, 0, 0);
-        if (r < 0)
-                return r;
-
-        acd->state = N_ACD_STATE_ANNOUNCING;
-        acd->defend = defend;
-        acd->n_iteration = 0;
-        return 0;
+_public_ int n_acd_probe(NAcd *acd, NAcdProbe **probep, NAcdProbeConfig *config) {
+        return n_acd_probe_new(probep, acd, config);
 }
diff --git a/shared/n-acd/src/n-acd.h b/shared/n-acd/src/n-acd.h
index 75646243..74b0aacb 100644
--- a/shared/n-acd/src/n-acd.h
+++ b/shared/n-acd/src/n-acd.h
@@ -15,43 +15,22 @@ extern "C" {
 #include <netinet/in.h>
 #include <stdbool.h>
 
+typedef struct NAcd NAcd;
+typedef struct NAcdConfig NAcdConfig;
+typedef struct NAcdEvent NAcdEvent;
+typedef struct NAcdProbe NAcdProbe;
+typedef struct NAcdProbeConfig NAcdProbeConfig;
+
 #define N_ACD_TIMEOUT_RFC5227 (UINT64_C(9000))
 
 enum {
         _N_ACD_E_SUCCESS,
 
-        N_ACD_E_DONE,
-        N_ACD_E_STOPPED,
         N_ACD_E_PREEMPTED,
-
         N_ACD_E_INVALID_ARGUMENT,
-        N_ACD_E_BUSY,
-};
-
-typedef struct NAcd NAcd;
-
-typedef struct NAcdConfig {
-        int ifindex;
-        unsigned int transport;
-        const uint8_t *mac;
-        size_t n_mac;
-        struct in_addr ip;
-        uint64_t timeout_msec;
-} NAcdConfig;
 
-typedef struct NAcdEvent {
-        unsigned int event;
-        union {
-                struct {
-                } ready, down;
-                struct {
-                        uint16_t operation;
-                        uint8_t *sender;
-                        size_t n_sender;
-                        struct in_addr target;
-                } used, defended, conflict;
-        };
-} NAcdEvent;
+        _N_ACD_E_N,
+};
 
 enum {
         N_ACD_TRANSPORT_ETHERNET,
@@ -74,21 +53,94 @@ enum {
         _N_ACD_DEFEND_N,
 };
 
-int n_acd_new(NAcd **acdp);
-void n_acd_free(NAcd *acd);
+struct NAcdEvent {
+        unsigned int event;
+        union {
+                struct {
+                        NAcdProbe *probe;
+                } ready;
+                struct {
+                } down;
+                struct {
+                        NAcdProbe *probe;
+                        uint8_t *sender;
+                        size_t n_sender;
+                } used, defended, conflict;
+        };
+};
 
-void n_acd_get_fd(NAcd *acd, int *fdp);
+/* configs */
 
+int n_acd_config_new(NAcdConfig **configp);
+NAcdConfig *n_acd_config_free(NAcdConfig *config);
+
+void n_acd_config_set_ifindex(NAcdConfig *config, int ifindex);
+void n_acd_config_set_transport(NAcdConfig *config, unsigned int transport);
+void n_acd_config_set_mac(NAcdConfig *config, const uint8_t *mac, size_t n_mac);
+
+int n_acd_probe_config_new(NAcdProbeConfig **configp);
+NAcdProbeConfig *n_acd_probe_config_free(NAcdProbeConfig *config);
+
+void n_acd_probe_config_set_ip(NAcdProbeConfig *config, struct in_addr ip);
+void n_acd_probe_config_set_timeout(NAcdProbeConfig *config, uint64_t msecs);
+
+/* contexts */
+
+int n_acd_new(NAcd **acdp, NAcdConfig *config);
+NAcd *n_acd_ref(NAcd *acd);
+NAcd *n_acd_unref(NAcd *acd);
+
+void n_acd_get_fd(NAcd *acd, int *fdp);
 int n_acd_dispatch(NAcd *acd);
 int n_acd_pop_event(NAcd *acd, NAcdEvent **eventp);
-int n_acd_announce(NAcd *acd, unsigned int defend);
 
-int n_acd_start(NAcd *acd, NAcdConfig *config);
-int n_acd_stop(NAcd *acd);
+int n_acd_probe(NAcd *acd, NAcdProbe **probep, NAcdProbeConfig *config);
+
+/* probes */
+
+NAcdProbe *n_acd_probe_free(NAcdProbe *probe);
+
+void n_acd_probe_set_userdata(NAcdProbe *probe, void *userdata);
+void n_acd_probe_get_userdata(NAcdProbe *probe, void **userdatap);
+
+int n_acd_probe_announce(NAcdProbe *probe, unsigned int defend);
+
+/* inline helpers */
+
+static inline void n_acd_config_freep(NAcdConfig **config) {
+        if (*config)
+                n_acd_config_free(*config);
+}
+
+static inline void n_acd_config_freev(NAcdConfig *config) {
+        n_acd_config_free(config);
+}
+
+static inline void n_acd_probe_config_freep(NAcdProbeConfig **config) {
+        if (*config)
+                n_acd_probe_config_free(*config);
+}
+
+static inline void n_acd_probe_config_freev(NAcdProbeConfig *config) {
+        n_acd_probe_config_free(config);
+}
 
-static inline void n_acd_freep(NAcd **acd) {
+static inline void n_acd_unrefp(NAcd **acd) {
         if (*acd)
-                n_acd_free(*acd);
+                n_acd_unref(*acd);
+}
+
+static inline void n_acd_unrefv(NAcd *acd) {
+        n_acd_unref(acd);
+}
+
+static inline void n_acd_probe_freep(NAcdProbe **probe) {
+        if (*probe)
+                n_acd_probe_free(*probe);
+}
+
+static inline void n_acd_probe_freev(NAcdProbe *probe) {
+        n_acd_probe_free(probe);
 }
 
 #ifdef __cplusplus
diff --git a/shared/n-acd/src/util/timer.c b/shared/n-acd/src/util/timer.c
new file mode 100644
index 00000000..29627af7
--- /dev/null
+++ b/shared/n-acd/src/util/timer.c
@@ -0,0 +1,189 @@
+/*
+ * Timer Utility Library
+ */
+
+#include <assert.h>
+#include <c-rbtree.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/timerfd.h>
+#include <time.h>
+#include "timer.h"
+
+int timer_init(Timer *timer) {
+        clockid_t clock = CLOCK_BOOTTIME;
+        int r;
+
+        r = timerfd_create(clock, TFD_CLOEXEC | TFD_NONBLOCK);
+        if (r < 0 && errno == EINVAL) {
+                clock = CLOCK_MONOTONIC;
+                r = timerfd_create(clock, TFD_CLOEXEC | TFD_NONBLOCK);
+        }
+        if (r < 0)
+                return -errno;
+
+        *timer = (Timer)TIMER_NULL(*timer);
+        timer->fd = r;
+        timer->clock = clock;
+
+        return 0;
+}
+
+void timer_deinit(Timer *timer) {
+        assert(c_rbtree_is_empty(&timer->tree));
+
+        if (timer->fd >= 0) {
+                close(timer->fd);
+                timer->fd = -1;
+        }
+}
+
+void timer_now(Timer *timer, uint64_t *nowp) {
+        struct timespec ts;
+        int r;
+
+        r = clock_gettime(timer->clock, &ts);
+        assert(r >= 0);
+
+        *nowp = ts.tv_sec * UINT64_C(1000000000) + ts.tv_nsec;
+}
+
+void timer_rearm(Timer *timer) {
+        uint64_t time;
+        Timeout *timeout;
+        int r;
+
+        /*
+         * A timeout value of 0 clears the timer, we sholud only set that if
+         * no timeout exists in the tree.
+         */
+
+        timeout = c_rbnode_entry(c_rbtree_first(&timer->tree), Timeout, node);
+        assert(!timeout || timeout->timeout);
+
+        time = timeout ? timeout->timeout : 0;
+
+        if (time != timer->scheduled_timeout) {
+                r = timerfd_settime(timer->fd,
+                                    TFD_TIMER_ABSTIME,
+                                    &(struct itimerspec){
+                                            .it_value = {
+                                                    .tv_sec = time / UINT64_C(1000000000),
+                                                    .tv_nsec = time % UINT64_C(1000000000),
+                                            },
+                                    },
+                                    NULL);
+                assert(r >= 0);
+
+                timer->scheduled_timeout = time;
+        }
+}
+
+int timer_read(Timer *timer) {
+        uint64_t v;
+        int r;
+
+        r = read(timer->fd, &v, sizeof(v));
+        if (r < 0) {
+                if (errno == EAGAIN) {
+                        /*
+                         * No more pending events.
+                         */
+                        return 0;
+                } else {
+                        /*
+                         * Something failed. We use CLOCK_BOOTTIME/MONOTONIC,
+                         * so ECANCELED cannot happen. Hence, there is no
+                         * error that we could gracefully handle. Fail hard
+                         * and let the caller deal with it.
+                         */
+                        return -errno;
+                }
+        } else if (r != sizeof(v) || v == 0) {
+                /*
+                 * Kernel guarantees 8-byte reads, and only to return
+                 * data if at least one timer triggered; fail hard if
+                 * it suddenly starts doing weird shit.
+                 */
+                return -EIO;
+        }
+
+        return TIMER_E_TRIGGERED;
+}
+
+
+int timer_pop_timeout(Timer *timer, uint64_t until, Timeout **timeoutp) {
+        Timeout *timeout;
+
+        /*
+         * If the first timeout is scheduled before @until, then unlink
+         * it and return it. Otherwise, return NULL.
+         */
+        timeout = c_rbnode_entry(c_rbtree_first(&timer->tree), Timeout, node);
+        if (timeout && timeout->timeout <= until) {
+                c_rbnode_unlink(&timeout->node);
+                timeout->timeout = 0;
+                *timeoutp = timeout;
+        } else {
+                *timeoutp = NULL;
+        }
+
+        return 0;
+}
+
+void timeout_schedule(Timeout *timeout, Timer *timer, uint64_t time) {
+
+        assert(time);
+
+        /*
+         * In case @timeout was already scheduled, remove it from the
+         * tree. If we are moving it to a new timer, rearm the old one.
+         */
+        if (timeout->timer) {
+                c_rbnode_unlink(&timeout->node);
+                if (timeout->timer != timer)
+                        timer_rearm(timeout->timer);
+        }
+        timeout->timer = timer;
+        timeout->timeout = time;
+
+        /*
+         * Now insert it back into the tree in the correct new position.
+         * We allow duplicates in the tree, so this insertion is open-coded.
+         */
+        {
+                Timeout *other;
+                CRBNode **slot, *parent;
+
+                slot = &timer->tree.root;
+                parent = NULL;
+                while (*slot) {
+                        other = c_rbnode_entry(*slot, Timeout, node);
+                        parent = *slot;
+                        if (timeout->timeout < other->timeout)
+                                slot = &(*slot)->left;
+                        else
+                                slot = &(*slot)->right;
+                }
+
+                c_rbtree_add(&timer->tree, parent, slot, &timeout->node);
+        }
+
+        /*
+         * Rearm the timer as we updated the timeout tree.
+         */
+        timer_rearm(timer);
+}
+
+void timeout_unschedule(Timeout *timeout) {
+        Timer *timer = timeout->timer;
+
+        if (!timer)
+                return;
+
+        c_rbnode_unlink(&timeout->node);
+        timeout->timeout = 0;
+        timeout->timer = NULL;
+
+        timer_rearm(timer);
+}
diff --git a/shared/n-acd/src/util/timer.h b/shared/n-acd/src/util/timer.h
new file mode 100644
index 00000000..2acc99e3
--- /dev/null
+++ b/shared/n-acd/src/util/timer.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <c-rbtree.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+typedef struct Timer Timer;
+typedef struct Timeout Timeout;
+
+enum {
+        _TIMER_E_SUCCESS,
+
+        TIMER_E_TRIGGERED,
+
+        _TIMER_E_N,
+};
+
+struct Timer {
+        int fd;
+        clockid_t clock;
+        CRBTree tree;
+        uint64_t scheduled_timeout;
+};
+
+#define TIMER_NULL(_x) {                                                        \
+                .fd = -1,                                                       \
+                .tree = C_RBTREE_INIT,                                          \
+        }
+
+struct Timeout {
+        Timer *timer;
+        CRBNode node;
+        uint64_t timeout;
+};
+
+#define TIMEOUT_INIT(_x) {                                                      \
+                .node = C_RBNODE_INIT((_x).node),                               \
+        }
+
+int timer_init(Timer *timer);
+void timer_deinit(Timer *timer);
+
+void timer_now(Timer *timer, uint64_t *nowp);
+
+int timer_pop_timeout(Timer *timer, uint64_t now, Timeout **timerp);
+void timer_rearm(Timer *timer);
+int timer_read(Timer *timer);
+
+void timeout_schedule(Timeout *timeout, Timer *timer, uint64_t time);
+void timeout_unschedule(Timeout *timeout);
+
diff --git a/shared/nm-common-macros.h b/shared/nm-common-macros.h
index 2edb9728..f5aa3a1e 100644
--- a/shared/nm-common-macros.h
+++ b/shared/nm-common-macros.h
@@ -40,6 +40,7 @@
 #define NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK        "org.freedesktop.NetworkManager.checkpoint-rollback"
 #define NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS  "org.freedesktop.NetworkManager.enable-disable-statistics"
 #define NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK "org.freedesktop.NetworkManager.enable-disable-connectivity-check"
+#define NM_AUTH_PERMISSION_WIFI_SCAN                  "org.freedesktop.NetworkManager.wifi.scan"
 
 #define NM_CLONED_MAC_PRESERVE                          "preserve"
 #define NM_CLONED_MAC_PERMANENT                         "permanent"
diff --git a/shared/nm-default.h b/shared/nm-default.h
index 8bcc5c70..26d6476a 100644
--- a/shared/nm-default.h
+++ b/shared/nm-default.h
@@ -78,9 +78,14 @@
                                                      | NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON \
                                                      )
 
+#define NM_NETWORKMANAGER_COMPILATION_SYSTEMD_SHARED ( 0 \
+                                                     | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \
+                                                     | NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD \
+                                                     )
+
 #define NM_NETWORKMANAGER_COMPILATION_SYSTEMD        ( 0 \
                                                      | NM_NETWORKMANAGER_COMPILATION_DAEMON \
-                                                     | NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD \
+                                                     | NM_NETWORKMANAGER_COMPILATION_SYSTEMD_SHARED \
                                                      )
 
 #define NM_NETWORKMANAGER_COMPILATION_GLIB           ( 0 \
@@ -179,7 +184,7 @@
 #if NM_MORE_ASSERTS == 0
 #ifndef G_DISABLE_CAST_CHECKS
 /* Unless compiling with G_DISABLE_CAST_CHECKS, glib performs type checking
- * during G_VARIANT_TYPE() via g_variant_type_checked_(). This is not necesary
+ * during G_VARIANT_TYPE() via g_variant_type_checked_(). This is not necessary
  * because commonly this cast is needed during something like
  *
  *   g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}"));
@@ -287,6 +292,7 @@ _nm_g_return_if_fail_warning (const char *log_domain,
 
 #include "nm-utils/nm-macros-internal.h"
 #include "nm-utils/nm-shared-utils.h"
+#include "nm-utils/nm-errno.h"
 
 #if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL
 /* no hash-utils in legacy code. */
@@ -303,6 +309,7 @@ _nm_g_return_if_fail_warning (const char *log_domain,
 /*****************************************************************************/
 
 #if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON
+#include "nm-core-types.h"
 #include "nm-types.h"
 #include "nm-logging.h"
 #endif
diff --git a/shared/nm-ethtool-utils.c b/shared/nm-ethtool-utils.c
index d50695ae..3313274a 100644
--- a/shared/nm-ethtool-utils.c
+++ b/shared/nm-ethtool-utils.c
@@ -90,7 +90,7 @@ const NMEthtoolData *const nm_ethtool_data[_NM_ETHTOOL_ID_NUM + 1] = {
 	[_NM_ETHTOOL_ID_NUM] = NULL,
 };
 
-const guint8 const _by_name[_NM_ETHTOOL_ID_NUM] = {
+static const guint8 _by_name[_NM_ETHTOOL_ID_NUM] = {
 	/* sorted by optname. */
 	NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD,
 	NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD,
diff --git a/shared/nm-meta-setting.c b/shared/nm-meta-setting.c
index 3e79747f..e666e0b2 100644
--- a/shared/nm-meta-setting.c
+++ b/shared/nm-meta-setting.c
@@ -28,8 +28,8 @@
 #include "nm-setting-adsl.h"
 #include "nm-setting-bluetooth.h"
 #include "nm-setting-bond.h"
-#include "nm-setting-bridge.h"
 #include "nm-setting-bridge-port.h"
+#include "nm-setting-bridge.h"
 #include "nm-setting-cdma.h"
 #include "nm-setting-connection.h"
 #include "nm-setting-dcb.h"
@@ -38,10 +38,10 @@
 #include "nm-setting-generic.h"
 #include "nm-setting-gsm.h"
 #include "nm-setting-infiniband.h"
-#include "nm-setting-ip4-config.h"
-#include "nm-setting-ip6-config.h"
 #include "nm-setting-ip-config.h"
 #include "nm-setting-ip-tunnel.h"
+#include "nm-setting-ip4-config.h"
+#include "nm-setting-ip6-config.h"
 #include "nm-setting-macsec.h"
 #include "nm-setting-macvlan.h"
 #include "nm-setting-match.h"
@@ -55,17 +55,19 @@
 #include "nm-setting-proxy.h"
 #include "nm-setting-serial.h"
 #include "nm-setting-tc-config.h"
-#include "nm-setting-team.h"
 #include "nm-setting-team-port.h"
+#include "nm-setting-team.h"
 #include "nm-setting-tun.h"
 #include "nm-setting-user.h"
 #include "nm-setting-vlan.h"
 #include "nm-setting-vpn.h"
 #include "nm-setting-vxlan.h"
+#include "nm-setting-wifi-p2p.h"
 #include "nm-setting-wimax.h"
 #include "nm-setting-wired.h"
-#include "nm-setting-wireless.h"
+#include "nm-setting-wireguard.h"
 #include "nm-setting-wireless-security.h"
+#include "nm-setting-wireless.h"
 #include "nm-setting-wpan.h"
 
 /*****************************************************************************/
@@ -383,6 +385,12 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = {
 		.setting_name =             NM_SETTING_VXLAN_SETTING_NAME,
 		.get_setting_gtype =        nm_setting_vxlan_get_type,
 	},
+	[NM_META_SETTING_TYPE_WIFI_P2P] = {
+		.meta_type =                NM_META_SETTING_TYPE_WIFI_P2P,
+		.setting_priority =         NM_SETTING_PRIORITY_HW_BASE,
+		.setting_name =             NM_SETTING_WIFI_P2P_SETTING_NAME,
+		.get_setting_gtype =        nm_setting_wifi_p2p_get_type,
+	},
 	[NM_META_SETTING_TYPE_WIMAX] = {
 		.meta_type =                NM_META_SETTING_TYPE_WIMAX,
 		.setting_priority =         NM_SETTING_PRIORITY_HW_BASE,
@@ -395,6 +403,12 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = {
 		.setting_name =             NM_SETTING_WIRED_SETTING_NAME,
 		.get_setting_gtype =        nm_setting_wired_get_type,
 	},
+	[NM_META_SETTING_TYPE_WIREGUARD] = {
+		.meta_type =                NM_META_SETTING_TYPE_WIREGUARD,
+		.setting_priority =         NM_SETTING_PRIORITY_HW_BASE,
+		.setting_name =             NM_SETTING_WIREGUARD_SETTING_NAME,
+		.get_setting_gtype =        nm_setting_wireguard_get_type,
+	},
 	[NM_META_SETTING_TYPE_WIRELESS] = {
 		.meta_type =                NM_META_SETTING_TYPE_WIRELESS,
 		.setting_priority =         NM_SETTING_PRIORITY_HW_BASE,
diff --git a/shared/nm-meta-setting.h b/shared/nm-meta-setting.h
index 26c29bea..18727a16 100644
--- a/shared/nm-meta-setting.h
+++ b/shared/nm-meta-setting.h
@@ -145,7 +145,9 @@ typedef enum {
 	NM_META_SETTING_TYPE_VLAN,
 	NM_META_SETTING_TYPE_VPN,
 	NM_META_SETTING_TYPE_VXLAN,
+	NM_META_SETTING_TYPE_WIFI_P2P,
 	NM_META_SETTING_TYPE_WIMAX,
+	NM_META_SETTING_TYPE_WIREGUARD,
 	NM_META_SETTING_TYPE_WPAN,
 
 	NM_META_SETTING_TYPE_UNKNOWN,
diff --git a/shared/nm-test-utils-impl.c b/shared/nm-test-utils-impl.c
index 1da9014e..509b235a 100644
--- a/shared/nm-test-utils-impl.c
+++ b/shared/nm-test-utils-impl.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <sys/wait.h>
 
 #include "NetworkManager.h"
diff --git a/shared/nm-utils/nm-c-list.h b/shared/nm-utils/nm-c-list.h
index b43d1441..5c73f574 100644
--- a/shared/nm-utils/nm-c-list.h
+++ b/shared/nm-utils/nm-c-list.h
@@ -78,4 +78,40 @@ nm_c_list_elem_free_all (CList *head, GDestroyNotify free_fcn)
 	}
 }
 
+/*****************************************************************************/
+
+static inline gboolean
+nm_c_list_move_before (CList *lst, CList *elem)
+{
+	nm_assert (lst);
+	nm_assert (elem);
+	nm_assert (c_list_contains (lst, elem));
+
+	if (   lst != elem
+	    && lst->prev != elem) {
+		c_list_unlink_stale (elem);
+		c_list_link_before (lst, elem);
+		return TRUE;
+	}
+	return FALSE;
+}
+#define nm_c_list_move_tail(lst, elem) nm_c_list_move_before (lst, elem)
+
+static inline gboolean
+nm_c_list_move_after (CList *lst, CList *elem)
+{
+	nm_assert (lst);
+	nm_assert (elem);
+	nm_assert (c_list_contains (lst, elem));
+
+	if (   lst != elem
+	    && lst->next != elem) {
+		c_list_unlink_stale (elem);
+		c_list_link_after (lst, elem);
+		return TRUE;
+	}
+	return FALSE;
+}
+#define nm_c_list_move_front(lst, elem) nm_c_list_move_after (lst, elem)
+
 #endif /* __NM_C_LIST_H__ */
diff --git a/shared/nm-utils/nm-dedup-multi.c b/shared/nm-utils/nm-dedup-multi.c
index fc134e25..5bdc3e3c 100644
--- a/shared/nm-utils/nm-dedup-multi.c
+++ b/shared/nm-utils/nm-dedup-multi.c
@@ -24,6 +24,7 @@
 #include "nm-dedup-multi.h"
 
 #include "nm-hash-utils.h"
+#include "nm-c-list.h"
 
 /*****************************************************************************/
 
@@ -159,7 +160,7 @@ _entry_unpack (const NMDedupMultiEntry *entry,
 	ASSERT_idx_type (*out_idx_type);
 
 	/* for lookup of the head, we allow to omit object, but only
-	 * if the idx_type does not parition the objects. Otherwise, we
+	 * if the idx_type does not partition the objects. Otherwise, we
 	 * require a obj to compare. */
 	nm_assert (   !*out_lookup_head
 	           || (   *out_obj
@@ -260,44 +261,27 @@ _add (NMDedupMultiIndex *self,
 		nm_dedup_multi_entry_set_dirty (entry, FALSE);
 
 		nm_assert (!head_existing || entry->head == head_existing);
-
-		if (entry_order) {
-			nm_assert (entry_order->head == entry->head);
-			nm_assert (c_list_contains (&entry->lst_entries, &entry_order->lst_entries));
-			nm_assert (c_list_contains (&entry_order->lst_entries, &entry->lst_entries));
-		}
+		nm_assert (!entry_order || entry_order->head == entry->head);
+		nm_assert (!entry_order || c_list_contains (&entry->lst_entries, &entry_order->lst_entries));
+		nm_assert (!entry_order || c_list_contains (&entry_order->lst_entries, &entry->lst_entries));
 
 		switch (mode) {
 		case NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE:
 			if (entry_order) {
-				if (   entry_order != entry
-				    && entry->lst_entries.next != &entry_order->lst_entries) {
-					c_list_unlink_stale (&entry->lst_entries);
-					c_list_link_before ((CList *) &entry_order->lst_entries, &entry->lst_entries);
+				if (nm_c_list_move_before ((CList *) &entry_order->lst_entries, &entry->lst_entries))
 					changed = TRUE;
-				}
 			} else {
-				if (entry->lst_entries.prev != &entry->head->lst_entries_head) {
-					c_list_unlink_stale (&entry->lst_entries);
-					c_list_link_front ((CList *) &entry->head->lst_entries_head, &entry->lst_entries);
+				if (nm_c_list_move_front ((CList *) &entry->head->lst_entries_head, &entry->lst_entries))
 					changed = TRUE;
-				}
 			}
 			break;
 		case NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE:
 			if (entry_order) {
-				if (   entry_order != entry
-				    && entry->lst_entries.prev != &entry_order->lst_entries) {
-					c_list_unlink_stale (&entry->lst_entries);
-					c_list_link_after ((CList *) &entry_order->lst_entries, &entry->lst_entries);
+				if (nm_c_list_move_after ((CList *) &entry_order->lst_entries, &entry->lst_entries))
 					changed = TRUE;
-				}
 			} else {
-				if (entry->lst_entries.next != &entry->head->lst_entries_head) {
-					c_list_unlink_stale (&entry->lst_entries);
-					c_list_link_tail ((CList *) &entry->head->lst_entries_head, &entry->lst_entries);
+				if (nm_c_list_move_tail ((CList *) &entry->head->lst_entries_head, &entry->lst_entries))
 					changed = TRUE;
-				}
 			}
 			break;
 		case NM_DEDUP_MULTI_IDX_MODE_PREPEND:
@@ -441,10 +425,10 @@ nm_dedup_multi_index_add (NMDedupMultiIndex *self,
  *   object will be placed in. You can omit this, and it will be automatically
  *   detected (at the expense of an additional hash lookup).
  *   Basically, this is the result of nm_dedup_multi_index_lookup_obj(),
- *   with the pecularity that if you know that @obj is not yet tracked,
+ *   with the peculiarity that if you know that @obj is not yet tracked,
  *   you may specify %NM_DEDUP_MULTI_ENTRY_MISSING.
  * @head_existing: an optional argument to safe a lookup for the head. If specified,
- *   it must be identical to nm_dedup_multi_index_lookup_head(), with the pecularity
+ *   it must be identical to nm_dedup_multi_index_lookup_head(), with the peculiarity
  *   that if the head is not yet tracked, you may specify %NM_DEDUP_MULTI_HEAD_ENTRY_MISSING
  * @out_entry: if give, return the added entry. This entry may have already exists (update)
  *   or be newly created. If @obj is not partitionable according to @idx_type, @obj
@@ -1022,33 +1006,20 @@ nm_dedup_multi_entry_reorder (const NMDedupMultiEntry *entry,
 	if (!entry_order) {
 		const NMDedupMultiHeadEntry *head_entry = entry->head;
 
-		nm_assert (c_list_contains (&head_entry->lst_entries_head, &entry->lst_entries));
 		if (order_after) {
-			if (head_entry->lst_entries_head.prev != &entry->lst_entries) {
-				c_list_unlink_stale ((CList *) &entry->lst_entries);
-				c_list_link_tail ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries);
+			if (nm_c_list_move_tail ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries))
 				return TRUE;
-			}
 		} else {
-			if (head_entry->lst_entries_head.next != &entry->lst_entries) {
-				c_list_unlink_stale ((CList *) &entry->lst_entries);
-				c_list_link_front ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries);
+			if (nm_c_list_move_front ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries))
 				return TRUE;
-			}
 		}
-	} else if (entry != entry_order) {
+	} else {
 		if (order_after) {
-			if (entry_order->lst_entries.next != &entry->lst_entries) {
-				c_list_unlink_stale ((CList *) &entry->lst_entries);
-				c_list_link_after ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries);
+			if (nm_c_list_move_after ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries))
 				return TRUE;
-			}
 		} else {
-			if (entry_order->lst_entries.prev != &entry->lst_entries) {
-				c_list_unlink_stale ((CList *) &entry->lst_entries);
-				c_list_link_before ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries);
+			if (nm_c_list_move_before ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries))
 				return TRUE;
-			}
 		}
 	}
 
diff --git a/shared/nm-utils/nm-dedup-multi.h b/shared/nm-utils/nm-dedup-multi.h
index 8d482de9..845b4c3e 100644
--- a/shared/nm-utils/nm-dedup-multi.h
+++ b/shared/nm-utils/nm-dedup-multi.h
@@ -47,7 +47,7 @@ typedef enum _NMDedupMultiIdxMode {
 	NM_DEDUP_MULTI_IDX_MODE_APPEND,
 
 	/* like NM_DEDUP_MULTI_IDX_MODE_APPEND, but if the object
-	 * is already in teh cache, move it to the end. */
+	 * is already in the cache, move it to the end. */
 	NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE,
 } NMDedupMultiIdxMode;
 
@@ -85,7 +85,7 @@ static inline const NMDedupMultiObj *
 nm_dedup_multi_obj_ref (const NMDedupMultiObj *obj)
 {
 	/* ref and unref accept const pointers. Objects is supposed to be shared
-	 * and kept immutable. Disallowing to take/retrun a reference to a const
+	 * and kept immutable. Disallowing to take/return a reference to a const
 	 * NMPObject is cumbersome, because callers are precisely expected to
 	 * keep a ref on the otherwise immutable object. */
 
@@ -131,12 +131,12 @@ void nm_dedup_multi_index_obj_release (NMDedupMultiIndex *self,
  * routes by ifindex. As the ifindex is dynamic, it does not create an
  * idx-type instance for each ifindex. Instead, it has one idx-type for
  * all routes. But whenever accessing NMDedupMultiIndex with an NMDedupMultiObj,
- * the partitioning NMDedupMultiIdxType takes into accound the NMDedupMultiObj
+ * the partitioning NMDedupMultiIdxType takes into account the NMDedupMultiObj
  * instance to associate it with the right list.
  *
  * Hence, a NMDedupMultiIdxEntry has a list of possibly multiple NMDedupMultiHeadEntry
  * instances, which each is the head for a list of NMDedupMultiEntry instances.
- * In the platform example, the NMDedupMultiHeadEntry parition the indexed objects
+ * In the platform example, the NMDedupMultiHeadEntry partition the indexed objects
  * by their ifindex. */
 struct _NMDedupMultiIdxType {
 	union {
diff --git a/shared/nm-utils/nm-errno.c b/shared/nm-utils/nm-errno.c
new file mode 100644
index 00000000..30eb9a8e
--- /dev/null
+++ b/shared/nm-utils/nm-errno.c
@@ -0,0 +1,198 @@
+/* NetworkManager -- Network link manager
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-errno.h"
+
+#include <pthread.h>
+
+/*****************************************************************************/
+
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_geterror,
+#if 0
+	enum _NMErrno,
+#else
+	int,
+#endif
+	NM_UTILS_LOOKUP_DEFAULT (NULL),
+
+	NM_UTILS_LOOKUP_STR_ITEM (NME_ERRNO_SUCCESS,      "NME_ERRNO_SUCCESS"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_ERRNO_OUT_OF_RANGE, "NME_ERRNO_OUT_OF_RANGE"),
+
+	NM_UTILS_LOOKUP_STR_ITEM (NME_UNSPEC,             "NME_UNSPEC"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_BUG,                "NME_BUG"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NATIVE_ERRNO,       "NME_NATIVE_ERRNO"),
+
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_ATTRSIZE,        "NME_NL_ATTRSIZE"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_BAD_SOCK,        "NME_NL_BAD_SOCK"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_DUMP_INTR,       "NME_NL_DUMP_INTR"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_MSG_OVERFLOW,    "NME_NL_MSG_OVERFLOW"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_MSG_TOOSHORT,    "NME_NL_MSG_TOOSHORT"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_MSG_TRUNC,       "NME_NL_MSG_TRUNC"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_SEQ_MISMATCH,    "NME_NL_SEQ_MISMATCH"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_NOADDR,          "NME_NL_NOADDR"),
+
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NOT_FOUND,       "not-found"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_EXISTS,          "exists"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_WRONG_TYPE,      "wrong-type"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NOT_SLAVE,       "not-slave"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NO_FIRMWARE,     "no-firmware"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_OPNOTSUPP,       "not-supported"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NETLINK,         "netlink"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_CANT_SET_MTU,    "cant-set-mtu"),
+
+	NM_UTILS_LOOKUP_ITEM_IGNORE (_NM_ERRNO_MININT),
+	NM_UTILS_LOOKUP_ITEM_IGNORE (_NM_ERRNO_RESERVED_LAST_PLUS_1),
+);
+
+/**
+ * nm_strerror():
+ * @nmerr: the NetworkManager specific errno to be converted
+ *   to string.
+ *
+ * NetworkManager specific error numbers reserve a range in "errno.h" with
+ * our own defines. For numbers that don't fall into this range, the numbers
+ * are identical to the common error numbers.
+ *
+ * Idential to strerror(), g_strerror(), nm_strerror_native() for error numbers
+ * that are not in the reserved range of NetworkManager specific errors.
+ *
+ * Returns: (transfer none): the string representation of the error number.
+ */
+const char *
+nm_strerror (int nmerr)
+{
+	const char *s;
+
+	nmerr = nm_errno (nmerr);
+
+	if (nmerr >= _NM_ERRNO_RESERVED_FIRST) {
+		s = _geterror (nmerr);
+		if (s)
+			return s;
+	}
+	return nm_strerror_native (nmerr);
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_strerror_native_r:
+ * @errsv: the errno to convert to string.
+ * @buf: the output buffer where to write the string to.
+ * @buf_size: the length of buffer.
+ *
+ * This is like strerror_r(), with one difference: depending on the
+ * locale, the returned string is guaranteed to be valid UTF-8.
+ * Also, there is some confusion as to whether to use glibc's
+ * strerror_r() or the POXIX/XSI variant. This is abstracted
+ * by the function.
+ *
+ * Note that the returned buffer may also be a statically allocated
+ * buffer, and not the input buffer @buf. Consequently, the returned
+ * string may be longer than @buf_size.
+ *
+ * Returns: (transfer none): a NUL terminated error message. This is either a static
+ *   string (that is never freed), or the provided @buf argumnt.
+ */
+const char *
+nm_strerror_native_r (int errsv, char *buf, gsize buf_size)
+{
+	char *buf2;
+
+	nm_assert (buf);
+	nm_assert (buf_size > 0);
+
+#if (_POSIX_C_SOURCE >= 200112L) && !  _GNU_SOURCE
+	/* XSI-compliant */
+	{
+		int errno_saved = errno;
+
+		if (strerror_r (errsv, buf, buf_size) != 0) {
+			g_snprintf (buf, buf_size, "Unspecified errno %d", errsv);
+			errno = errno_saved;
+		}
+		buf2 = buf;
+	}
+#else
+	/* GNU-specific */
+	buf2 = strerror_r (errsv, buf, buf_size);
+#endif
+
+	/* like g_strerror(), ensure that the error message is UTF-8. */
+	if (   !g_get_charset (NULL)
+	    && !g_utf8_validate (buf2, -1, NULL)) {
+		gs_free char *msg = NULL;
+
+		msg = g_locale_to_utf8 (buf2, -1, NULL, NULL, NULL);
+		if (msg) {
+			g_strlcpy (buf, msg, buf_size);
+			buf2 = buf;
+		}
+	}
+
+	return buf2;
+}
+
+/**
+ * nm_strerror_native:
+ * @errsv: the errno integer from <errno.h>
+ *
+ * Like strerror(), but strerror() is not thread-safe and not guaranteed
+ * to be UTF-8.
+ *
+ * g_strerror() is a thread-safe variant of strerror(), however it caches
+ * all returned strings in a dictionary. That means, using this on untrusted
+ * error numbers can result in this cache to grow without limits.
+ *
+ * Instead, return a tread-local buffer. This way, it's thread-safe.
+ *
+ * There is a downside to this: subsequent calls of nm_strerror_native()
+ * overwrite the error message.
+ *
+ * Returns: (transfer none): the text representation of the error number.
+ */
+const char *
+nm_strerror_native (int errsv)
+{
+	static _nm_thread_local char *buf_static = NULL;
+	char *buf;
+
+	buf = buf_static;
+	if (G_UNLIKELY (!buf)) {
+		int errno_saved = errno;
+		pthread_key_t key;
+
+		buf = g_malloc (NM_STRERROR_BUFSIZE);
+		buf_static = buf;
+
+		if (   pthread_key_create (&key, g_free) != 0
+		    || pthread_setspecific (key, buf) != 0) {
+			/* Failure. We will leak the buffer when the thread exits.
+			 *
+			 * Nothing we can do about it really. For Debug builds we fail with an assertion. */
+			nm_assert_not_reached ();
+		}
+		errno = errno_saved;
+	}
+
+	return nm_strerror_native_r (errsv, buf, NM_STRERROR_BUFSIZE);
+}
diff --git a/shared/nm-utils/nm-errno.h b/shared/nm-utils/nm-errno.h
new file mode 100644
index 00000000..d77735a7
--- /dev/null
+++ b/shared/nm-utils/nm-errno.h
@@ -0,0 +1,185 @@
+/* NetworkManager -- Network link manager
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_ERRNO_H__
+#define __NM_ERRNO_H__
+
+#include <errno.h>
+
+/*****************************************************************************/
+
+enum _NMErrno {
+	_NM_ERRNO_MININT         = G_MININT,
+	_NM_ERRNO_MAXINT         = G_MAXINT,
+	_NM_ERRNO_RESERVED_FIRST = 100000,
+
+
+	/* when we cannot represent a number as positive number, we resort to this
+	 * number. Basically, the values G_MININT, -NME_ERRNO_SUCCESS, NME_ERRNO_SUCCESS
+	 * and G_MAXINT all map to the same value. */
+	NME_ERRNO_OUT_OF_RANGE   = G_MAXINT,
+
+	/* Indicate that the original errno was zero. Zero denotes *no error*, but we know something
+	 * went wrong and we want to report some error. This is a placeholder to mean, something
+	 * was wrong, but errno was zero. */
+	NME_ERRNO_SUCCESS        = G_MAXINT - 1,
+
+
+	/* an unspecified error. */
+	NME_UNSPEC = _NM_ERRNO_RESERVED_FIRST,
+
+	/* A bug, for example when an assertion failed.
+	 * Should never happen. */
+	NME_BUG,
+
+	/* a native error number (from <errno.h>) cannot be mapped as
+	 * an nm-error, because it is in the range [_NM_ERRNO_RESERVED_FIRST,
+	 * _NM_ERRNO_RESERVED_LAST]. */
+	NME_NATIVE_ERRNO,
+
+	/* netlink errors. */
+	NME_NL_SEQ_MISMATCH,
+	NME_NL_MSG_TRUNC,
+	NME_NL_MSG_TOOSHORT,
+	NME_NL_DUMP_INTR,
+	NME_NL_ATTRSIZE,
+	NME_NL_BAD_SOCK,
+	NME_NL_NOADDR,
+	NME_NL_MSG_OVERFLOW,
+
+	/* platform errors. */
+	NME_PL_NOT_FOUND,
+	NME_PL_EXISTS,
+	NME_PL_WRONG_TYPE,
+	NME_PL_NOT_SLAVE,
+	NME_PL_NO_FIRMWARE,
+	NME_PL_OPNOTSUPP,
+	NME_PL_NETLINK,
+	NME_PL_CANT_SET_MTU,
+
+	_NM_ERRNO_RESERVED_LAST_PLUS_1,
+	_NM_ERRNO_RESERVED_LAST = _NM_ERRNO_RESERVED_LAST_PLUS_1 - 1,
+};
+
+/*****************************************************************************/
+
+/* When we receive an errno from a system function, we can safely assume
+ * that the error number is not negative. We rely on that, and possibly just
+ * "return -errsv;" to signal an error. We also rely on that, because libc
+ * is our trusted base: meaning, if it cannot even succeed at setting errno
+ * according to specification, all bets are off.
+ *
+ * This macro returns the input argument, and asserts that the error variable
+ * is positive.
+ *
+ * In a sense, the macro is related to nm_errno_native() function, but the difference
+ * is that this macro asserts that @errsv is positive, while nm_errno_native() coerces
+ * negative values to be non-negative. */
+#define NM_ERRNO_NATIVE(errsv) \
+	({ \
+		const int _errsv_x = (errsv); \
+		\
+		nm_assert (_errsv_x > 0); \
+		_errsv_x; \
+	})
+
+/* Normalize native errno.
+ *
+ * Our API may return native error codes (<errno.h>) as negative values. This function
+ * takes such an errno, and normalizes it to their positive value.
+ *
+ * The special values G_MININT and zero are coerced to NME_ERRNO_OUT_OF_RANGE and NME_ERRNO_SUCCESS
+ * respectively.
+ * Other values are coerced to their inverse.
+ * Other positive values are returned unchanged.
+ *
+ * Basically, this normalizes errsv to be positive (taking care of two pathological cases).
+ */
+static inline int
+nm_errno_native (int errsv)
+{
+	switch (errsv) {
+	case 0:                  return NME_ERRNO_SUCCESS;
+	case G_MININT:           return NME_ERRNO_OUT_OF_RANGE;
+	default:
+		return errsv >= 0 ? errsv : -errsv;
+	}
+}
+
+/* Normalizes an nm-error to be positive.
+ *
+ * Various API returns negative error codes, and this function converts the negative
+ * value to its positive.
+ *
+ * Note that @nmerr is on the domain of NetworkManager specific error numbers,
+ * which is not the same as the native error numbers (errsv from <errno.h>). But
+ * as far as normalizing goes, nm_errno() does exactly the same remapping as
+ * nm_errno_native(). */
+static inline int
+nm_errno (int nmerr)
+{
+	return nm_errno_native (nmerr);
+}
+
+/* this maps a native errno to a (always non-negative) nm-error number.
+ *
+ * Note that nm-error numbers are embedded into the range of regular
+ * errno. The only difference is, that nm-error numbers reserve a
+ * range (_NM_ERRNO_RESERVED_FIRST, _NM_ERRNO_RESERVED_LAST) for their
+ * own purpose.
+ *
+ * That means, converting an errno to nm-error number means in
+ * most cases just returning itself.
+ * Only pathological cases need special handling:
+ *
+ *  - 0 is mapped to NME_ERRNO_SUCCESS;
+ *  - G_MININT is mapped to NME_ERRNO_OUT_OF_RANGE;
+ *  - values in the range of (+/-) [_NM_ERRNO_RESERVED_FIRST, _NM_ERRNO_RESERVED_LAST]
+ *    are mapped to NME_NATIVE_ERRNO
+ *  - all other values are their (positive) absolute value.
+ */
+static inline int
+nm_errno_from_native (int errsv)
+{
+	switch (errsv) {
+	case 0:                  return NME_ERRNO_SUCCESS;
+	case G_MININT:           return NME_ERRNO_OUT_OF_RANGE;
+	default:
+		if (errsv < 0)
+			errsv = -errsv;
+		return   G_UNLIKELY (   errsv >= _NM_ERRNO_RESERVED_FIRST
+		                     && errsv <= _NM_ERRNO_RESERVED_LAST)
+		       ? NME_NATIVE_ERRNO
+		       : errsv;
+	}
+}
+
+const char *nm_strerror (int nmerr);
+
+/*****************************************************************************/
+
+#define NM_STRERROR_BUFSIZE 1024
+
+const char *nm_strerror_native_r (int errsv, char *buf, gsize buf_size);
+const char *nm_strerror_native (int errsv);
+
+/*****************************************************************************/
+
+#endif /* __NM_ERRNO_H__ */
diff --git a/shared/nm-utils/nm-glib.h b/shared/nm-utils/nm-glib.h
index 770cf0fe..e941e067 100644
--- a/shared/nm-utils/nm-glib.h
+++ b/shared/nm-utils/nm-glib.h
@@ -424,11 +424,13 @@ g_steal_pointer (gpointer pp)
 
 	return ref;
 }
+#endif
 
-/* type safety */
-#define g_steal_pointer(pp) \
-  (0 ? (*(pp)) : (g_steal_pointer) (pp))
+#ifdef g_steal_pointer
+#undef g_steal_pointer
 #endif
+#define g_steal_pointer(pp) \
+	((typeof (*(pp))) g_steal_pointer (pp))
 
 /*****************************************************************************/
 
@@ -538,4 +540,28 @@ _nm_g_variant_new_printf (const char *format_string, ...)
 
 /*****************************************************************************/
 
+#if !GLIB_CHECK_VERSION (2, 47, 1)
+/* Older versions of g_value_unset() only allowed to unset a GValue which
+ * was initialized previously. This was relaxed ([1], [2], [3]).
+ *
+ * Our nm_auto_unset_gvalue macro requires to be able to call g_value_unset().
+ * Also, it is our general practice to allow for that. Add a compat implementation.
+ *
+ * [1] https://gitlab.gnome.org/GNOME/glib/commit/4b2d92a864f1505f1b08eb639d74293fa32681da
+ * [2] commit "Allow passing unset GValues to g_value_unset()"
+ * [3] https://bugzilla.gnome.org/show_bug.cgi?id=755766
+ */
+static inline void
+_nm_g_value_unset (GValue *value)
+{
+	g_return_if_fail (value);
+
+	if (value->g_type != 0)
+		g_value_unset (value);
+}
+#define g_value_unset _nm_g_value_unset
+#endif
+
+/*****************************************************************************/
+
 #endif  /* __NM_GLIB_H__ */
diff --git a/shared/nm-utils/nm-hash-utils.c b/shared/nm-utils/nm-hash-utils.c
index 80387c71..6e728e6b 100644
--- a/shared/nm-utils/nm-hash-utils.c
+++ b/shared/nm-utils/nm-hash-utils.c
@@ -71,7 +71,7 @@ again:
 		 * the first guint has only the entropy that nm_utils_random_bytes()
 		 * generated for the first 4 bytes and relies on a good random generator.
 		 *
-		 * The first int is especially intersting for nm_hash_static() below, and we
+		 * The first int is especially interesting for nm_hash_static() below, and we
 		 * want to have it all the entropy of t_arr. */
 		c_siphash_init (&siph_state, t_arr.v8);
 		c_siphash_append (&siph_state, (const guint8 *) &t_arr, sizeof (t_arr));
diff --git a/shared/nm-utils/nm-hash-utils.h b/shared/nm-utils/nm-hash-utils.h
index cf71a7e9..1a1e44f5 100644
--- a/shared/nm-utils/nm-hash-utils.h
+++ b/shared/nm-utils/nm-hash-utils.h
@@ -122,6 +122,9 @@ nm_hash_update (NMHashState *state, const void *ptr, gsize n)
 		nm_hash_update ((state), &_val, sizeof (_val)); \
 	} G_STMT_END
 
+#define nm_hash_update_valp(state, val) \
+	nm_hash_update ((state), (val), sizeof (*(val))) \
+
 static inline void
 nm_hash_update_bool (NMHashState *state, bool val)
 {
diff --git a/shared/nm-utils/nm-io-utils.c b/shared/nm-utils/nm-io-utils.c
index 88cb13ff..51312748 100644
--- a/shared/nm-utils/nm-io-utils.c
+++ b/shared/nm-utils/nm-io-utils.c
@@ -29,6 +29,7 @@
 
 #include "nm-shared-utils.h"
 #include "nm-secret-utils.h"
+#include "nm-errno.h"
 
 /*****************************************************************************/
 
@@ -36,14 +37,12 @@ _nm_printf (3, 4)
 static int
 _get_contents_error (GError **error, int errsv, const char *format, ...)
 {
-	if (errsv < 0)
-		errsv = -errsv;
-	else if (!errsv)
-		errsv = errno;
+	nm_assert (NM_ERRNO_NATIVE (errsv));
 
 	if (error) {
-		char *msg;
+		gs_free char *msg = NULL;
 		va_list args;
+		char bstrerr[NM_STRERROR_BUFSIZE];
 
 		va_start (args, format);
 		msg = g_strdup_vprintf (format, args);
@@ -52,11 +51,17 @@ _get_contents_error (GError **error, int errsv, const char *format, ...)
 		             G_FILE_ERROR,
 		             g_file_error_from_errno (errsv),
 		             "%s: %s",
-		             msg, g_strerror (errsv));
-		g_free (msg);
+		             msg,
+		             nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr)));
 	}
 	return -errsv;
 }
+#define _get_contents_error_errno(error, ...) \
+	({ \
+		int _errsv = (errno); \
+		\
+		_get_contents_error (error, _errsv, __VA_ARGS__); \
+	})
 
 static char *
 _mem_realloc (char *old, gboolean do_bzero_mem, gsize cur_len, gsize new_len)
@@ -110,7 +115,7 @@ _mem_realloc (char *old, gboolean do_bzero_mem, gsize cur_len, gsize new_len)
  * A reimplementation of g_file_get_contents() with a few differences:
  *   - accepts an open fd, instead of a path name. This allows you to
  *     use openat().
- *   - limits the maxium filesize to max_length.
+ *   - limits the maximum filesize to max_length.
  *
  * Returns: a negative error code on failure.
  */
@@ -127,13 +132,14 @@ nm_utils_fd_get_contents (int fd,
 	struct stat stat_buf;
 	gs_free char *str = NULL;
 	const bool do_bzero_mem = NM_FLAGS_HAS (flags, NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET);
+	int errsv;
 
 	g_return_val_if_fail (fd >= 0, -EINVAL);
 	g_return_val_if_fail (contents, -EINVAL);
 	g_return_val_if_fail (!error || !*error, -EINVAL);
 
 	if (fstat (fd, &stat_buf) < 0)
-		return _get_contents_error (error, 0, "failure during fstat");
+		return _get_contents_error_errno (error, "failure during fstat");
 
 	if (!max_length) {
 		/* default to a very large size, but not extreme */
@@ -156,7 +162,7 @@ nm_utils_fd_get_contents (int fd,
 		if (n_read < 0) {
 			if (do_bzero_mem)
 				nm_explicit_bzero (str, n_stat);
-			return _get_contents_error (error, n_read, "error reading %zu bytes from file descriptor", n_stat);
+			return _get_contents_error (error, -n_read, "error reading %zu bytes from file descriptor", n_stat);
 		}
 		str[n_read] = '\0';
 
@@ -176,19 +182,19 @@ nm_utils_fd_get_contents (int fd,
 		else {
 			fd2 = fcntl (fd, F_DUPFD_CLOEXEC, 0);
 			if (fd2 < 0)
-				return _get_contents_error (error, 0, "error during dup");
+				return _get_contents_error_errno (error, "error during dup");
 		}
 
 		if (!(f = fdopen (fd2, "r"))) {
+			errsv = errno;
 			nm_close (fd2);
-			return _get_contents_error (error, 0, "failure during fdopen");
+			return _get_contents_error (error, errsv, "failure during fdopen");
 		}
 
 		n_have = 0;
 		n_alloc = 0;
 
 		while (!feof (f)) {
-			int errsv;
 			gsize n_read;
 
 			n_read = fread (buf, 1, sizeof (buf), f);
@@ -262,13 +268,13 @@ nm_utils_fd_get_contents (int fd,
  * @flags: %NMUtilsFileGetContentsFlags for reading the file.
  * @contents: the output buffer with the file read. It is always
  *   NUL terminated. The buffer is at most @max_length long, including
- *  the NUL byte. That is, it reads only files up to a length of
- *  @max_length - 1 bytes.
+ *   the NUL byte. That is, it reads only files up to a length of
+ *   @max_length - 1 bytes.
  * @length: optional output argument of the read file size.
  *
  * A reimplementation of g_file_get_contents() with a few differences:
  *   - accepts an @dirfd to open @filename relative to that path via openat().
- *   - limits the maxium filesize to max_length.
+ *   - limits the maximum filesize to max_length.
  *   - uses O_CLOEXEC on internal file descriptor
  *
  * Returns: a negative error code on failure.
@@ -284,6 +290,7 @@ nm_utils_file_get_contents (int dirfd,
 {
 	int fd;
 	int errsv;
+	char bstrerr[NM_STRERROR_BUFSIZE];
 
 	g_return_val_if_fail (filename && filename[0], -EINVAL);
 
@@ -297,8 +304,8 @@ nm_utils_file_get_contents (int dirfd,
 			             g_file_error_from_errno (errsv),
 			             "Failed to open file \"%s\" with openat: %s",
 			             filename,
-			             g_strerror (errsv));
-			return -errsv;
+			             nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr)));
+			return -NM_ERRNO_NATIVE (errsv);
 		}
 	} else {
 		fd = open (filename, O_RDONLY | O_CLOEXEC);
@@ -310,8 +317,8 @@ nm_utils_file_get_contents (int dirfd,
 			             g_file_error_from_errno (errsv),
 			             "Failed to open file \"%s\": %s",
 			             filename,
-			             g_strerror (errsv));
-			return -errsv;
+			             nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr)));
+			return -NM_ERRNO_NATIVE (errsv);
 		}
 	}
 	return nm_utils_fd_get_contents (fd,
@@ -341,6 +348,7 @@ nm_utils_file_set_contents (const char *filename,
 	int errsv;
 	gssize s;
 	int fd;
+	char bstrerr[NM_STRERROR_BUFSIZE];
 
 	g_return_val_if_fail (filename, FALSE);
 	g_return_val_if_fail (contents || !length, FALSE);
@@ -351,7 +359,7 @@ nm_utils_file_set_contents (const char *filename,
 		length = strlen (contents);
 
 	tmp_name = g_strdup_printf ("%s.XXXXXX", filename);
-	fd = g_mkstemp_full (tmp_name, O_RDWR, mode);
+	fd = g_mkstemp_full (tmp_name, O_RDWR | O_CLOEXEC, mode);
 	if (fd < 0) {
 		errsv = errno;
 		g_set_error (error,
@@ -359,7 +367,7 @@ nm_utils_file_set_contents (const char *filename,
 		             g_file_error_from_errno (errsv),
 		             "failed to create file %s: %s",
 		             tmp_name,
-		             g_strerror (errsv));
+		             nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr)));
 		return FALSE;
 	}
 
@@ -378,7 +386,7 @@ nm_utils_file_set_contents (const char *filename,
 			             g_file_error_from_errno (errsv),
 			             "failed to write to file %s: %s",
 			             tmp_name,
-			             g_strerror (errsv));
+			             nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr)));
 			return FALSE;
 		}
 
@@ -395,20 +403,21 @@ nm_utils_file_set_contents (const char *filename,
 	 * guarantee the data is written to the disk before the metadata.)
 	 */
 	if (   lstat (filename, &statbuf) == 0
-	    && statbuf.st_size > 0
-	    && fsync (fd) != 0) {
-		errsv = errno;
+	    && statbuf.st_size > 0) {
+		if (fsync (fd) != 0) {
+			errsv = errno;
 
-		nm_close (fd);
-		unlink (tmp_name);
+			nm_close (fd);
+			unlink (tmp_name);
 
-		g_set_error (error,
-		             G_FILE_ERROR,
-		             g_file_error_from_errno (errsv),
-		             "failed to fsync %s: %s",
-		             tmp_name,
-		             g_strerror (errsv));
-		return FALSE;
+			g_set_error (error,
+			             G_FILE_ERROR,
+			             g_file_error_from_errno (errsv),
+			             "failed to fsync %s: %s",
+			             tmp_name,
+			             nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr)));
+			return FALSE;
+		}
 	}
 
 	nm_close (fd);
@@ -422,7 +431,7 @@ nm_utils_file_set_contents (const char *filename,
 		             "failed to rename %s to %s: %s",
 		             tmp_name,
 		             filename,
-		             g_strerror (errsv));
+		             nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr)));
 		return FALSE;
 	}
 
diff --git a/shared/nm-utils/nm-jansson.h b/shared/nm-utils/nm-jansson.h
index cacf87a6..5a73231f 100644
--- a/shared/nm-utils/nm-jansson.h
+++ b/shared/nm-utils/nm-jansson.h
@@ -34,11 +34,11 @@
 /* Added in Jansson v2.8 */
 #ifndef json_object_foreach_safe
 #define json_object_foreach_safe(object, n, key, value)     \
-    for(key = json_object_iter_key(json_object_iter(object)), \
-            n = json_object_iter_next(object, json_object_key_to_iter(key)); \
-        key && (value = json_object_iter_value(json_object_key_to_iter(key))); \
-        key = json_object_iter_key(n), \
-            n = json_object_iter_next(object, json_object_key_to_iter(key)))
+    for (key = json_object_iter_key(json_object_iter(object)), \
+             n = json_object_iter_next(object, json_object_key_to_iter(key)); \
+         key && (value = json_object_iter_value(json_object_key_to_iter(key))); \
+         key = json_object_iter_key(n), \
+             n = json_object_iter_next(object, json_object_key_to_iter(key)))
 #endif
 
 NM_AUTO_DEFINE_FCN0 (json_t *, _nm_auto_decref_json, json_decref)
diff --git a/shared/nm-utils/nm-logging-fwd.h b/shared/nm-utils/nm-logging-fwd.h
new file mode 100644
index 00000000..900dfff8
--- /dev/null
+++ b/shared/nm-utils/nm-logging-fwd.h
@@ -0,0 +1,113 @@
+/* NetworkManager -- Network link manager
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2006 - 2018 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ */
+
+#ifndef __NM_LOGGING_DEFINES_H__
+#define __NM_LOGGING_DEFINES_H__
+
+/* Log domains */
+
+typedef enum  { /*< skip >*/
+	LOGD_NONE       = 0LL,
+	LOGD_PLATFORM   = (1LL << 0), /* Platform services */
+	LOGD_RFKILL     = (1LL << 1),
+	LOGD_ETHER      = (1LL << 2),
+	LOGD_WIFI       = (1LL << 3),
+	LOGD_BT         = (1LL << 4),
+	LOGD_MB         = (1LL << 5), /* mobile broadband */
+	LOGD_DHCP4      = (1LL << 6),
+	LOGD_DHCP6      = (1LL << 7),
+	LOGD_PPP        = (1LL << 8),
+	LOGD_WIFI_SCAN  = (1LL << 9),
+	LOGD_IP4        = (1LL << 10),
+	LOGD_IP6        = (1LL << 11),
+	LOGD_AUTOIP4    = (1LL << 12),
+	LOGD_DNS        = (1LL << 13),
+	LOGD_VPN        = (1LL << 14),
+	LOGD_SHARING    = (1LL << 15), /* Connection sharing/dnsmasq */
+	LOGD_SUPPLICANT = (1LL << 16), /* Wi-Fi and 802.1x */
+	LOGD_AGENTS     = (1LL << 17), /* Secret agents */
+	LOGD_SETTINGS   = (1LL << 18), /* Settings */
+	LOGD_SUSPEND    = (1LL << 19), /* Suspend/Resume */
+	LOGD_CORE       = (1LL << 20), /* Core daemon and policy stuff */
+	LOGD_DEVICE     = (1LL << 21), /* Device state and activation */
+	LOGD_OLPC       = (1LL << 22),
+	LOGD_INFINIBAND = (1LL << 23),
+	LOGD_FIREWALL   = (1LL << 24),
+	LOGD_ADSL       = (1LL << 25),
+	LOGD_BOND       = (1LL << 26),
+	LOGD_VLAN       = (1LL << 27),
+	LOGD_BRIDGE     = (1LL << 28),
+	LOGD_DBUS_PROPS = (1LL << 29),
+	LOGD_TEAM       = (1LL << 30),
+	LOGD_CONCHECK   = (1LL << 31),
+	LOGD_DCB        = (1LL << 32), /* Data Center Bridging */
+	LOGD_DISPATCH   = (1LL << 33),
+	LOGD_AUDIT      = (1LL << 34),
+	LOGD_SYSTEMD    = (1LL << 35),
+	LOGD_VPN_PLUGIN = (1LL << 36),
+	LOGD_PROXY      = (1LL << 37),
+
+	__LOGD_MAX,
+	LOGD_ALL       = (((__LOGD_MAX - 1LL) << 1) - 1LL),
+	LOGD_DEFAULT   = LOGD_ALL & ~(
+	                              LOGD_DBUS_PROPS |
+	                              LOGD_WIFI_SCAN |
+	                              LOGD_VPN_PLUGIN |
+	                              0),
+
+	/* aliases: */
+	LOGD_DHCP       = LOGD_DHCP4 | LOGD_DHCP6,
+	LOGD_IP         = LOGD_IP4 | LOGD_IP6,
+} NMLogDomain;
+
+/* Log levels */
+typedef enum  { /*< skip >*/
+	LOGL_TRACE,
+	LOGL_DEBUG,
+	LOGL_INFO,
+	LOGL_WARN,
+	LOGL_ERR,
+
+	_LOGL_N_REAL, /* the number of actual logging levels */
+
+	_LOGL_OFF = _LOGL_N_REAL, /* special logging level that is always disabled. */
+	_LOGL_KEEP,               /* special logging level to indicate that the logging level should not be changed. */
+
+	_LOGL_N, /* the number of logging levels including "OFF" */
+} NMLogLevel;
+
+gboolean _nm_log_enabled_impl (gboolean mt_require_locking,
+                               NMLogLevel level,
+                               NMLogDomain domain);
+
+void _nm_log_impl (const char *file,
+                   guint line,
+                   const char *func,
+                   gboolean mt_require_locking,
+                   NMLogLevel level,
+                   NMLogDomain domain,
+                   int error,
+                   const char *ifname,
+                   const char *con_uuid,
+                   const char *fmt,
+                   ...) _nm_printf (10, 11);
+
+#endif /* __NM_LOGGING_DEFINES_H__ */
diff --git a/shared/nm-utils/nm-macros-internal.h b/shared/nm-utils/nm-macros-internal.h
index 9059783f..42299c96 100644
--- a/shared/nm-utils/nm-macros-internal.h
+++ b/shared/nm-utils/nm-macros-internal.h
@@ -32,19 +32,33 @@
 
 /*****************************************************************************/
 
-#define _nm_packed           __attribute__ ((packed))
-#define _nm_unused           __attribute__ ((unused))
-#define _nm_pure             __attribute__ ((pure))
-#define _nm_const            __attribute__ ((const))
+#define _nm_packed           __attribute__ ((__packed__))
+#define _nm_unused           __attribute__ ((__unused__))
+#define _nm_used             __attribute__ ((__used__))
+#define _nm_pure             __attribute__ ((__pure__))
+#define _nm_const            __attribute__ ((__const__))
 #define _nm_printf(a,b)      __attribute__ ((__format__ (__printf__, a, b)))
-#define _nm_align(s)         __attribute__ ((aligned (s)))
+#define _nm_align(s)         __attribute__ ((__aligned__ (s)))
+#define _nm_section(s)       __attribute__ ((__section__ (s)))
 #define _nm_alignof(type)    __alignof (type)
 #define _nm_alignas(type)    _nm_align (_nm_alignof (type))
-#define nm_auto(fcn)         __attribute__ ((cleanup(fcn)))
+#define nm_auto(fcn)         __attribute__ ((__cleanup__(fcn)))
+
+
+/* This is required to make LTO working.
+ *
+ * See https://gitlab.freedesktop.org/NetworkManager/NetworkManager/merge_requests/76#note_112694
+ *     https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48200#c28
+ */
+#ifndef __clang__
+#define _nm_externally_visible __attribute__ ((__externally_visible__))
+#else
+#define _nm_externally_visible
+#endif
 
 
 #if __GNUC__ >= 7
-#define _nm_fallthrough      __attribute__ ((fallthrough))
+#define _nm_fallthrough      __attribute__ ((__fallthrough__))
 #else
 #define _nm_fallthrough
 #endif
@@ -65,6 +79,28 @@
 
 /*****************************************************************************/
 
+/* most of our code is single-threaded with a mainloop. Hence, we usually don't need
+ * any thread-safety. Sometimes, we do need thread-safety (nm-logging), but we can
+ * avoid locking if we are on the main-thread by:
+ *
+ *   - modifications of shared data is done infrequently and only from the
+ *     main-thread (nm_logging_setup())
+ *   - read-only access is done frequently (nm_logging_enabled())
+ *     - from the main-thread, we can do that without locking (because
+ *       all modifications are also done on the main thread.
+ *     - from other threads, we need locking. But this is expected to be
+ *       done infrequently too. Important is the lock-free fast-path on the
+ *       main-thread.
+ *
+ * By defining NM_THREAD_SAFE_ON_MAIN_THREAD you indicate that this code runs
+ * on the main-thread. It is by default defined to "1". If you have code that
+ * is also used on another thread, redefine the define to 0 (to opt in into
+ * the slow-path).
+ */
+#define NM_THREAD_SAFE_ON_MAIN_THREAD 1
+
+/*****************************************************************************/
+
 #define NM_AUTO_DEFINE_FCN_VOID(CastType, name, func) \
 static inline void name (void *v) \
 { \
@@ -99,7 +135,7 @@ static inline void name (Type *v) \
  * Call g_free() on a variable location when it goes out of scope.
  */
 #define gs_free nm_auto(gs_local_free)
-NM_AUTO_DEFINE_FCN_VOID (void *, gs_local_free, g_free)
+NM_AUTO_DEFINE_FCN_VOID0 (void *, gs_local_free, g_free)
 
 /**
  * gs_unref_object:
@@ -160,7 +196,7 @@ NM_AUTO_DEFINE_FCN0 (GHashTable *, gs_local_hashtable_unref, g_hash_table_unref)
  * of scope.
  */
 #define gs_free_slist nm_auto(gs_local_free_slist)
-NM_AUTO_DEFINE_FCN (GSList *, gs_local_free_slist, g_slist_free)
+NM_AUTO_DEFINE_FCN0 (GSList *, gs_local_free_slist, g_slist_free)
 
 /**
  * gs_unref_bytes:
@@ -178,7 +214,7 @@ NM_AUTO_DEFINE_FCN0 (GBytes *, gs_local_bytes_unref, g_bytes_unref)
  * Call g_strfreev() on a variable location when it goes out of scope.
  */
 #define gs_strfreev nm_auto(gs_local_strfreev)
-NM_AUTO_DEFINE_FCN (char **, gs_local_strfreev, g_strfreev)
+NM_AUTO_DEFINE_FCN0 (char **, gs_local_strfreev, g_strfreev)
 
 /**
  * gs_free_error:
@@ -222,7 +258,7 @@ static inline int nm_close (int fd);
  * However, let's never mix them. To free malloc'ed memory, always use
  * free() or nm_auto_free.
  */
-NM_AUTO_DEFINE_FCN_VOID (void *, _nm_auto_free_impl, free)
+NM_AUTO_DEFINE_FCN_VOID0 (void *, _nm_auto_free_impl, free)
 #define nm_auto_free nm_auto(_nm_auto_free_impl)
 
 NM_AUTO_DEFINE_FCN0 (GVariantIter *, _nm_auto_free_variant_iter, g_variant_iter_free)
@@ -231,7 +267,7 @@ NM_AUTO_DEFINE_FCN0 (GVariantIter *, _nm_auto_free_variant_iter, g_variant_iter_
 NM_AUTO_DEFINE_FCN0 (GVariantBuilder *, _nm_auto_unref_variant_builder, g_variant_builder_unref)
 #define nm_auto_unref_variant_builder nm_auto(_nm_auto_unref_variant_builder)
 
-NM_AUTO_DEFINE_FCN (GList *, _nm_auto_free_list, g_list_free)
+NM_AUTO_DEFINE_FCN0 (GList *, _nm_auto_free_list, g_list_free)
 #define nm_auto_free_list nm_auto(_nm_auto_free_list)
 
 NM_AUTO_DEFINE_FCN0 (GChecksum *, _nm_auto_checksum_free, g_checksum_free)
@@ -413,7 +449,7 @@ NM_G_ERROR_MSG (GError *error)
 /*****************************************************************************/
 
 /* macro to return strlen() of a compile time string. */
-#define NM_STRLEN(str)     ( sizeof ("" str) - 1 )
+#define NM_STRLEN(str)     ( sizeof (""str"") - 1 )
 
 /* returns the length of a NULL terminated array of pointers,
  * like g_strv_length() does. The difference is:
@@ -458,6 +494,10 @@ NM_G_ERROR_MSG (GError *error)
 #endif
 
 #ifndef _NM_CC_SUPPORT_GENERIC
+/* In the meantime, NetworkManager requires C11 and _Generic() should always be available.
+ * However, shared/nm-utils may also be used in VPN/applet, which possibly did not yet
+ * bump the C standard requirement. Leave this for the moment, but eventually we can
+ * drop it. */
 #if (defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9 ))) || (defined (__clang__))
 #define _NM_CC_SUPPORT_GENERIC 1
 #else
@@ -605,7 +645,7 @@ NM_G_ERROR_MSG (GError *error)
  * argument is not modified (CC), but you want to make it work also
  * for "char **". C doesn't allow this form of casting (for good reasons),
  * so the function makes a choice like g_strdupv(char**). That means,
- * every time you want to call ith with a const argument, you need to
+ * every time you want to call it with a const argument, you need to
  * explicitly cast it.
  *
  * These macros do the cast, but they only accept a compatible input
@@ -639,8 +679,11 @@ NM_G_ERROR_MSG (GError *error)
 #define NM_PROPAGATE_CONST(test_expr, ptr) (ptr)
 #endif
 
+/* with the way it is implemented, the caller may or may not pass a trailing
+ * ',' and it will work. However, this makes the macro unsuitable for initializing
+ * an array. */
 #define NM_MAKE_STRV(...) \
-	((const char *const[]) { __VA_ARGS__, NULL })
+	((const char *const[(sizeof (((const char *const[]) { __VA_ARGS__ })) / sizeof (const char *)) + 1]) { __VA_ARGS__ })
 
 /*****************************************************************************/
 
@@ -676,7 +719,7 @@ NM_G_ERROR_MSG (GError *error)
 
 /* Beware that this does short-circuit evaluation (use "||" instead of "|")
  * which has a possibly unexpected non-function-like behavior.
- * Use NM_IN_SET_SE if you need all arguments to be evaluted. */
+ * Use NM_IN_SET_SE if you need all arguments to be evaluated. */
 #define NM_IN_SET(x, ...)                   _NM_IN_SET(||, typeof (x), x, __VA_ARGS__)
 
 /* "SE" stands for "side-effect". Contrary to NM_IN_SET(), this does not do
@@ -727,7 +770,7 @@ _NM_IN_STRSET_streq (const char *x, const char *s)
 
 /* Beware that this does short-circuit evaluation (use "||" instead of "|")
  * which has a possibly unexpected non-function-like behavior.
- * Use NM_IN_STRSET_SE if you need all arguments to be evaluted. */
+ * Use NM_IN_STRSET_SE if you need all arguments to be evaluated. */
 #define NM_IN_STRSET(x, ...)               _NM_IN_STRSET_EVAL_N(||, x, NM_NARG (__VA_ARGS__), __VA_ARGS__)
 
 /* "SE" stands for "side-effect". Contrary to NM_IN_STRSET(), this does not do
@@ -817,8 +860,32 @@ fcn (void) \
 
 /*****************************************************************************/
 
-#define nm_streq(s1, s2)  (strcmp (s1, s2) == 0)
-#define nm_streq0(s1, s2) (g_strcmp0 (s1, s2) == 0)
+static inline gboolean
+nm_streq (const char *s1, const char *s2)
+{
+	return strcmp (s1, s2) == 0;
+}
+
+static inline gboolean
+nm_streq0 (const char *s1, const char *s2)
+{
+	return    (s1 == s2)
+	       || (s1 && s2 && strcmp (s1, s2) == 0);
+}
+
+#define NM_STR_HAS_PREFIX(str, prefix) \
+	(strncmp ((str), ""prefix"", NM_STRLEN (prefix)) == 0)
+
+#define NM_STR_HAS_SUFFIX(str, suffix) \
+	({ \
+		const char *_str = (str); \
+		gsize _l = strlen (_str); \
+		\
+		(   (_l >= NM_STRLEN (suffix)) \
+		 && (memcmp (&_str[_l - NM_STRLEN (suffix)], \
+		             ""suffix"", \
+		             NM_STRLEN (suffix)) == 0)); \
+	})
 
 /*****************************************************************************/
 
@@ -832,6 +899,14 @@ nm_gstring_prepare (GString **l)
 	return *l;
 }
 
+static inline GString *
+nm_gstring_add_space_delimiter (GString *str)
+{
+	if (str->len > 0)
+		g_string_append_c (str, ' ');
+	return str;
+}
+
 static inline const char *
 nm_str_not_empty (const char *str)
 {
@@ -976,7 +1051,7 @@ static inline void
 nm_g_object_unref (gpointer obj)
 {
 	/* g_object_unref() doesn't accept NULL. Usully, we workaround that
-	 * by using g_clear_object(), but sometimes that is not convinient
+	 * by using g_clear_object(), but sometimes that is not convenient
 	 * (for example as as destroy function for a hash table that can contain
 	 * NULL values). */
 	if (obj)
@@ -1107,6 +1182,45 @@ nm_clear_g_cancellable (GCancellable **cancellable)
 	return FALSE;
 }
 
+/* If @cancellable_id is not 0, clear it and call g_cancellable_disconnect().
+ * @cancellable may be %NULL, if there is nothing to disconnect.
+ *
+ * It's like nm_clear_g_signal_handler(), except that it uses g_cancellable_disconnect()
+ * instead of g_signal_handler_disconnect().
+ *
+ * Note the warning in glib documentation about dead-lock and what g_cancellable_disconnect()
+ * actually does. */
+static inline gboolean
+nm_clear_g_cancellable_disconnect (GCancellable *cancellable, gulong *cancellable_id)
+{
+	gulong id;
+
+	if (   cancellable_id
+	    && (id = *cancellable_id) != 0) {
+		*cancellable_id = 0;
+		g_cancellable_disconnect (cancellable, id);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/*****************************************************************************/
+
+static inline GVariant *
+nm_g_variant_ref (GVariant *v)
+{
+	if (v)
+		g_variant_ref (v);
+	return v;
+}
+
+static inline void
+nm_g_variant_unref (GVariant *v)
+{
+	if (v)
+		g_variant_unref (v);
+}
+
 /*****************************************************************************/
 
 /* Determine whether @x is a power of two (@x being an integer type).
@@ -1157,7 +1271,7 @@ fcn_name (lookup_type val) \
 /* Call the string-lookup-table function @fcn_name. If the function returns
  * %NULL, the numeric index is converted to string using a alloca() buffer.
  * Beware: this macro uses alloca(). */
-#define NM_UTILS_LOOKUP_STR(fcn_name, idx) \
+#define NM_UTILS_LOOKUP_STR_A(fcn_name, idx) \
 	({ \
 		typeof (idx) _idx = (idx); \
 		const char *_s; \
@@ -1206,17 +1320,17 @@ fcn_name (lookup_type val) \
 
 /*****************************************************************************/
 
-#define _NM_BACKPORT_SYMBOL_IMPL(VERSION, RETURN_TYPE, ORIG_FUNC, VERSIONED_FUNC, ARGS_TYPED, ARGS) \
-RETURN_TYPE VERSIONED_FUNC ARGS_TYPED; \
-RETURN_TYPE VERSIONED_FUNC ARGS_TYPED \
+#define _NM_BACKPORT_SYMBOL_IMPL(version, return_type, orig_func, versioned_func, args_typed, args) \
+return_type versioned_func args_typed; \
+_nm_externally_visible return_type versioned_func args_typed \
 { \
-    return ORIG_FUNC ARGS; \
+    return orig_func args; \
 } \
-RETURN_TYPE ORIG_FUNC ARGS_TYPED; \
-__asm__(".symver "G_STRINGIFY(VERSIONED_FUNC)", "G_STRINGIFY(ORIG_FUNC)"@"G_STRINGIFY(VERSION))
+return_type orig_func args_typed; \
+__asm__(".symver "G_STRINGIFY(versioned_func)", "G_STRINGIFY(orig_func)"@"G_STRINGIFY(version))
 
-#define NM_BACKPORT_SYMBOL(VERSION, RETURN_TYPE, FUNC, ARGS_TYPED, ARGS) \
-_NM_BACKPORT_SYMBOL_IMPL(VERSION, RETURN_TYPE, FUNC, _##FUNC##_##VERSION, ARGS_TYPED, ARGS)
+#define NM_BACKPORT_SYMBOL(version, return_type, func, args_typed, args) \
+_NM_BACKPORT_SYMBOL_IMPL(version, return_type, func, _##func##_##version, args_typed, args)
 
 /*****************************************************************************/
 
@@ -1334,6 +1448,14 @@ nm_strcmp_p (gconstpointer a, gconstpointer b)
 		 : _b); \
 	})
 
+/* evaluates to (void) if _A or _B are not constant or of different types */
+#define NM_CONST_MAX(_A, _B) \
+	(__builtin_choose_expr ((   __builtin_constant_p (_A) \
+	                         && __builtin_constant_p (_B) \
+	                         && __builtin_types_compatible_p (typeof (_A), typeof (_B))), \
+	                        ((_A) > (_B)) ? (_A) : (_B),                            \
+	                        ((void)  0)))
+
 /*****************************************************************************/
 
 static inline guint
@@ -1372,7 +1494,8 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro)
  * If @str is longer then @trunc_at, the string is truncated and the closing
  * quote is instead '^' to indicate truncation.
  *
- * Thus, the maximum stack allocated buffer will be @trunc_at+3. */
+ * Thus, the maximum stack allocated buffer will be @trunc_at+3. The maximum
+ * buffer size must be a constant and not larger than 300. */
 #define nm_strquote_a(trunc_at, str) \
 	({ \
 		const char *const _str = (str); \
@@ -1383,6 +1506,8 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro)
 				const gsize _strlen_trunc = NM_MIN (strlen (_str), _trunc_at); \
 				char *_buf; \
 				\
+				G_STATIC_ASSERT_EXPR ((trunc_at) <= 300); \
+				\
 				_buf = g_alloca (_strlen_trunc + 3); \
 				_buf[0] = '"'; \
 				memcpy (&_buf[1], _str, _strlen_trunc); \
@@ -1407,19 +1532,30 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro)
 		_buf; \
 	})
 
-#define nm_sprintf_bufa(n_elements, format, ...) \
+/* it is "unsafe" because @bufsize must not be a constant expression and
+ * there is no check at compiletime. Regardless of that, the buffer size
+ * must not be larger than 300 bytes, as this gets stack allocated. */
+#define nm_sprintf_buf_unsafe_a(bufsize, format, ...) \
 	({ \
 		char *_buf; \
 		int _buf_len; \
-		typeof (n_elements) _n_elements = (n_elements); \
+		typeof (bufsize) _bufsize = (bufsize); \
+		\
+		nm_assert (_bufsize <= 300); \
 		\
-		_buf = g_alloca (_n_elements); \
-		_buf_len = g_snprintf (_buf, _n_elements, \
+		_buf = g_alloca (_bufsize); \
+		_buf_len = g_snprintf (_buf, _bufsize, \
 		                       ""format"", ##__VA_ARGS__); \
-		nm_assert (_buf_len < _n_elements); \
+		nm_assert (_buf_len >= 0 && _buf_len < _bufsize); \
 		_buf; \
 	})
 
+#define nm_sprintf_bufa(bufsize, format, ...) \
+	({ \
+		G_STATIC_ASSERT_EXPR ((bufsize) <= 300); \
+		nm_sprintf_buf_unsafe_a ((bufsize), format, ##__VA_ARGS__); \
+	})
+
 /* aims to alloca() a buffer and fill it with printf(format, name).
  * Note that format must not contain any format specifier except
  * "%s".
@@ -1433,8 +1569,9 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro)
 		char *_buf2; \
 		\
 		nm_assert (_p_val_to_free && !*_p_val_to_free); \
-		if (NM_STRLEN (format) + _name_len < 200) \
-			_buf2 = nm_sprintf_bufa (NM_STRLEN (format) + _name_len, format, _name); \
+		if (   NM_STRLEN (format) <= 290 \
+		    && _name_len < (gsize) (290 - NM_STRLEN (format))) \
+			_buf2 = nm_sprintf_buf_unsafe_a (NM_STRLEN (format) + _name_len, format, _name); \
 		else { \
 			_buf2 = g_strdup_printf (format, _name); \
 			*_p_val_to_free = _buf2; \
@@ -1446,7 +1583,7 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro)
 
 /**
  * The boolean type _Bool is C99 while we mostly stick to C89. However, _Bool is too
- * convinient to miss and is effectively available in gcc and clang. So, just use it.
+ * convenient to miss and is effectively available in gcc and clang. So, just use it.
  *
  * Usually, one would include "stdbool.h" to get the "bool" define which aliases
  * _Bool. We provide this define here, because we want to make use of it anywhere.
@@ -1458,7 +1595,7 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro)
  *   is a typedef for int). Especially when having boolean fields in a struct, we can
  *   thereby easily save some space.
  *
- * - _Bool type guarantees that two "true" expressions compare equal. E.g. the follwing
+ * - _Bool type guarantees that two "true" expressions compare equal. E.g. the following
  *   will not work:
  *        gboolean v1 = 1;
  *        gboolean v2 = 2;
diff --git a/shared/nm-utils/nm-random-utils.c b/shared/nm-utils/nm-random-utils.c
index 3e968a8e..d7c7da42 100644
--- a/shared/nm-utils/nm-random-utils.c
+++ b/shared/nm-utils/nm-random-utils.c
@@ -81,7 +81,7 @@ nm_utils_random_bytes (void *p, size_t n)
 
 				/* no or partial read. There is not enough entropy.
 				 * Fill the rest reading from urandom, and remember that
-				 * some bits are not hight quality. */
+				 * some bits are not high quality. */
 				nm_assert (r < n);
 				buf += r;
 				n -= r;
diff --git a/shared/nm-utils/nm-secret-utils.c b/shared/nm-utils/nm-secret-utils.c
index 65f99c65..ec5cc6b1 100644
--- a/shared/nm-utils/nm-secret-utils.c
+++ b/shared/nm-utils/nm-secret-utils.c
@@ -17,6 +17,7 @@
  * Boston, MA 02110-1301 USA.
  *
  * (C) Copyright 2018 Red Hat, Inc.
+ * (C) Copyright 2015 - 2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
  */
 
 #include "nm-default.h"
@@ -132,3 +133,29 @@ nm_secret_buf_to_gbytes_take (NMSecretBuf *secret, gssize actual_len)
 	                                   _secret_buf_free,
 	                                   secret);
 }
+
+/*****************************************************************************/
+
+/**
+ * nm_utils_memeqzero_secret:
+ * @data: the data pointer to check (may be %NULL if @length is zero).
+ * @length: the number of bytes to check.
+ *
+ * Checks that all bytes are zero. This always takes the same amount
+ * of time to prevent timing attacks.
+ *
+ * Returns: whether all bytes are zero.
+ */
+gboolean
+nm_utils_memeqzero_secret (gconstpointer data, gsize length)
+{
+	const guint8 *const key = data;
+	volatile guint8 acc = 0;
+	gsize i;
+
+	for (i = 0; i < length; i++) {
+		acc |= key[i];
+		asm volatile("" : "=r"(acc) : "0"(acc));
+	}
+	return 1 & ((acc - 1) >> 8);
+}
diff --git a/shared/nm-utils/nm-secret-utils.h b/shared/nm-utils/nm-secret-utils.h
index 21a3c1ba..034ef7bd 100644
--- a/shared/nm-utils/nm-secret-utils.h
+++ b/shared/nm-utils/nm-secret-utils.h
@@ -43,7 +43,7 @@ nm_free_secret (char *secret)
 	}
 }
 
-NM_AUTO_DEFINE_FCN (char *, _nm_auto_free_secret, nm_free_secret)
+NM_AUTO_DEFINE_FCN0 (char *, _nm_auto_free_secret, nm_free_secret)
 /**
  * nm_auto_free_secret:
  *
@@ -76,6 +76,19 @@ typedef struct {
 } NMSecretPtr;
 
 static inline void
+nm_secret_ptr_bzero (NMSecretPtr *secret)
+{
+	if (secret) {
+		if (secret->len > 0) {
+			if (secret->ptr)
+				nm_explicit_bzero (secret->ptr, secret->len);
+		}
+	}
+}
+
+#define nm_auto_bzero_secret_ptr nm_auto(nm_secret_ptr_bzero)
+
+static inline void
 nm_secret_ptr_clear (NMSecretPtr *secret)
 {
 	if (secret) {
@@ -90,12 +103,24 @@ nm_secret_ptr_clear (NMSecretPtr *secret)
 
 #define nm_auto_clear_secret_ptr nm_auto(nm_secret_ptr_clear)
 
+#define NM_SECRET_PTR_INIT() \
+	((const NMSecretPtr) { \
+		.len = 0, \
+		.ptr = NULL, \
+	})
+
 #define NM_SECRET_PTR_STATIC(_len) \
 	((const NMSecretPtr) { \
 		.len = _len, \
 		.ptr = ((guint8 [_len]) { }), \
 	})
 
+#define NM_SECRET_PTR_ARRAY(_arr) \
+	((const NMSecretPtr) { \
+		.len = G_N_ELEMENTS (_arr) * sizeof ((_arr)[0]), \
+		.ptr = &((_arr)[0]), \
+	})
+
 static inline void
 nm_secret_ptr_clear_static (const NMSecretPtr *secret)
 {
@@ -148,4 +173,6 @@ GBytes *nm_secret_buf_to_gbytes_take (NMSecretBuf *secret, gssize actual_len);
 
 /*****************************************************************************/
 
+gboolean nm_utils_memeqzero_secret (gconstpointer data, gsize length);
+
 #endif /* __NM_SECRET_UTILS_H__ */
diff --git a/shared/nm-utils/nm-shared-utils.c b/shared/nm-utils/nm-shared-utils.c
index d399ce3f..6a43c670 100644
--- a/shared/nm-utils/nm-shared-utils.c
+++ b/shared/nm-utils/nm-shared-utils.c
@@ -23,10 +23,12 @@
 
 #include "nm-shared-utils.h"
 
-#include <errno.h>
 #include <arpa/inet.h>
 #include <poll.h>
 #include <fcntl.h>
+#include <sys/syscall.h>
+
+#include "nm-errno.h"
 
 /*****************************************************************************/
 
@@ -34,7 +36,116 @@ const void *const _NM_PTRARRAY_EMPTY[1] = { NULL };
 
 /*****************************************************************************/
 
-const NMIPAddr nm_ip_addr_zero = { 0 };
+const NMIPAddr nm_ip_addr_zero = { };
+
+/* this initializes a struct in_addr/in6_addr and allows for untrusted
+ * arguments (like unsuitable @addr_family or @src_len). It's almost safe
+ * in the sense that it verifies input arguments strictly. Also, it
+ * uses memcpy() to access @src, so alignment is not an issue.
+ *
+ * Only potential pitfalls:
+ *
+ * - it allows for @addr_family to be AF_UNSPEC. If that is the case (and the
+ *   caller allows for that), the caller MUST provide @out_addr_family.
+ * - when setting @dst to an IPv4 address, the trailing bytes are not touched.
+ *   Meaning, if @dst is an NMIPAddr union, only the first bytes will be set.
+ *   If that matter to you, clear @dst before. */
+gboolean
+nm_ip_addr_set_from_untrusted (int addr_family,
+                               gpointer dst,
+                               gconstpointer src,
+                               gsize src_len,
+                               int *out_addr_family)
+{
+	nm_assert (dst);
+
+	switch (addr_family) {
+	case AF_UNSPEC:
+		if (!out_addr_family) {
+			/* when the callers allow undefined @addr_family, they must provide
+			 * an @out_addr_family argument. */
+			nm_assert_not_reached ();
+			return FALSE;
+		}
+		switch (src_len) {
+		case sizeof (struct in_addr):  addr_family = AF_INET;  break;
+		case sizeof (struct in6_addr): addr_family = AF_INET6; break;
+		default:
+			return FALSE;
+		}
+		break;
+	case AF_INET:
+		if (src_len != sizeof (struct in_addr))
+			return FALSE;
+		break;
+	case AF_INET6:
+		if (src_len != sizeof (struct in6_addr))
+			return FALSE;
+		break;
+	default:
+		/* when the callers allow undefined @addr_family, they must provide
+		 * an @out_addr_family argument. */
+		nm_assert (out_addr_family);
+		return FALSE;
+	}
+
+	nm_assert (src);
+
+	memcpy (dst, src, src_len);
+	NM_SET_OUT (out_addr_family, addr_family);
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+pid_t
+nm_utils_gettid (void)
+{
+	return (pid_t) syscall (SYS_gettid);
+}
+
+/* Used for asserting that this function is called on the main-thread.
+ * The main-thread is determined by remembering the thread-id
+ * of when the function was called the first time.
+ *
+ * When forking, the thread-id is again reset upon first call. */
+gboolean
+_nm_assert_on_main_thread (void)
+{
+	G_LOCK_DEFINE_STATIC (lock);
+	static pid_t seen_tid;
+	static pid_t seen_pid;
+	pid_t tid;
+	pid_t pid;
+	gboolean success = FALSE;
+
+	tid = nm_utils_gettid ();
+	nm_assert (tid != 0);
+
+	G_LOCK (lock);
+
+	if (G_LIKELY (tid == seen_tid)) {
+		/* we don't care about false positives (when the process forked, and the thread-id
+		 * is accidentally re-used) . It's for assertions only. */
+		success = TRUE;
+	} else {
+		pid = getpid ();
+		nm_assert (pid != 0);
+
+		if (   seen_tid == 0
+			|| seen_pid != pid) {
+			/* either this is the first time we call the function, or the process
+			 * forked. In both cases, remember the thread-id. */
+			seen_tid = tid;
+			seen_pid = pid;
+			success = TRUE;
+		}
+	}
+
+	G_UNLOCK (lock);
+
+	return success;
+}
 
 /*****************************************************************************/
 
@@ -59,6 +170,41 @@ nm_utils_strbuf_append_c (char **buf, gsize *len, char c)
 }
 
 void
+nm_utils_strbuf_append_bin (char **buf, gsize *len, gconstpointer str, gsize str_len)
+{
+	switch (*len) {
+	case 0:
+		return;
+	case 1:
+		if (str_len == 0) {
+			(*buf)[0] = '\0';
+			return;
+		}
+		(*buf)[0] = '\0';
+		*len = 0;
+		(*buf)++;
+		return;
+	default:
+		if (str_len == 0) {
+			(*buf)[0] = '\0';
+			return;
+		}
+		if (str_len >= *len) {
+			memcpy (*buf, str, *len - 1);
+			(*buf)[*len - 1] = '\0';
+			*buf = &(*buf)[*len];
+			*len = 0;
+		} else {
+			memcpy (*buf, str, str_len);
+			*buf = &(*buf)[str_len];
+			(*buf)[0] = '\0';
+			*len -= str_len;
+		}
+		return;
+	}
+}
+
+void
 nm_utils_strbuf_append_str (char **buf, gsize *len, const char *str)
 {
 	gsize src_len;
@@ -118,7 +264,7 @@ nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...)
 /**
  * nm_utils_strbuf_seek_end:
  * @buf: the input/output buffer
- * @len: the input/output lenght of the buffer.
+ * @len: the input/output length of the buffer.
  *
  * Commonly, one uses nm_utils_strbuf_append*(), to incrementally
  * append strings to the buffer. However, sometimes we need to use
@@ -459,34 +605,25 @@ nm_utils_ip_is_site_local (int addr_family,
 gboolean
 nm_utils_parse_inaddr_bin (int addr_family,
                            const char *text,
+                           int *out_addr_family,
                            gpointer out_addr)
 {
 	NMIPAddr addrbin;
 
 	g_return_val_if_fail (text, FALSE);
 
-	if (addr_family == AF_UNSPEC)
+	if (addr_family == AF_UNSPEC) {
+		g_return_val_if_fail (!out_addr || out_addr_family, FALSE);
 		addr_family = strchr (text, ':') ? AF_INET6 : AF_INET;
-	else
+	} else
 		g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), FALSE);
 
-	/* use a temporary variable @addrbin, to guarantee that @out_addr
-	 * is only modified on success. */
 	if (inet_pton (addr_family, text, &addrbin) != 1)
 		return FALSE;
 
-	if (out_addr) {
-		switch (addr_family) {
-		case AF_INET:
-			*((in_addr_t *) out_addr) = addrbin.addr4;
-			break;
-		case AF_INET6:
-			*((struct in6_addr *) out_addr) = addrbin.addr6;
-			break;
-		default:
-			nm_assert_not_reached ();
-		}
-	}
+	NM_SET_OUT (out_addr_family, addr_family);
+	if (out_addr)
+		nm_ip_addr_set (addr_family, out_addr, &addrbin);
 	return TRUE;
 }
 
@@ -498,9 +635,7 @@ nm_utils_parse_inaddr (int addr_family,
 	NMIPAddr addrbin;
 	char addrstr_buf[MAX (INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
 
-	nm_assert (!out_addr || !*out_addr);
-
-	if (!nm_utils_parse_inaddr_bin (addr_family, text, &addrbin))
+	if (!nm_utils_parse_inaddr_bin (addr_family, text, &addr_family, &addrbin))
 		return FALSE;
 	NM_SET_OUT (out_addr, g_strdup (inet_ntop (addr_family, &addrbin, addrstr_buf, sizeof (addrstr_buf))));
 	return TRUE;
@@ -509,6 +644,7 @@ nm_utils_parse_inaddr (int addr_family,
 gboolean
 nm_utils_parse_inaddr_prefix_bin (int addr_family,
                                   const char *text,
+                                  int *out_addr_family,
                                   gpointer out_addr,
                                   int *out_prefix)
 {
@@ -517,19 +653,14 @@ nm_utils_parse_inaddr_prefix_bin (int addr_family,
 	const char *slash;
 	const char *addrstr;
 	NMIPAddr addrbin;
-	int addr_len;
 
 	g_return_val_if_fail (text, FALSE);
 
-	if (addr_family == AF_UNSPEC)
+	if (addr_family == AF_UNSPEC) {
+		g_return_val_if_fail (!out_addr || out_addr_family, FALSE);
 		addr_family = strchr (text, ':') ? AF_INET6 : AF_INET;
-
-	if (addr_family == AF_INET)
-		addr_len = sizeof (in_addr_t);
-	else if (addr_family == AF_INET6)
-		addr_len = sizeof (struct in6_addr);
-	else
-		g_return_val_if_reached (FALSE);
+	} else
+		g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), FALSE);
 
 	slash = strchr (text, '/');
 	if (slash)
@@ -541,6 +672,8 @@ nm_utils_parse_inaddr_prefix_bin (int addr_family,
 		return FALSE;
 
 	if (slash) {
+		/* For IPv4, `ip addr add` supports the prefix-length as a netmask. We don't
+		 * do that. */
 		prefix = _nm_utils_ascii_str_to_int64 (slash + 1, 10,
 		                                       0,
 		                                       addr_family == AF_INET ? 32 : 128,
@@ -549,8 +682,9 @@ nm_utils_parse_inaddr_prefix_bin (int addr_family,
 			return FALSE;
 	}
 
+	NM_SET_OUT (out_addr_family, addr_family);
 	if (out_addr)
-		memcpy (out_addr, &addrbin, addr_len);
+		nm_ip_addr_set (addr_family, out_addr, &addrbin);
 	NM_SET_OUT (out_prefix, prefix);
 	return TRUE;
 }
@@ -564,7 +698,7 @@ nm_utils_parse_inaddr_prefix (int addr_family,
 	NMIPAddr addrbin;
 	char addrstr_buf[MAX (INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
 
-	if (!nm_utils_parse_inaddr_prefix_bin (addr_family, text, &addrbin, out_prefix))
+	if (!nm_utils_parse_inaddr_prefix_bin (addr_family, text, &addr_family, &addrbin, out_prefix))
 		return FALSE;
 	NM_SET_OUT (out_addr, g_strdup (inet_ntop (addr_family, &addrbin, addrstr_buf, sizeof (addrstr_buf))));
 	return TRUE;
@@ -1122,7 +1256,7 @@ nm_utils_error_is_notfound (GError *error)
  */
 gboolean
 nm_g_object_set_property (GObject *object,
-                          const char   *property_name,
+                          const char *property_name,
                           const GValue *value,
                           GError **error)
 {
@@ -1195,30 +1329,136 @@ nm_g_object_set_property (GObject *object,
 	return TRUE;
 }
 
+#define _set_property(object, property_name, gtype, gtype_set, value, error) \
+	G_STMT_START { \
+		nm_auto_unset_gvalue GValue gvalue = { 0 }; \
+		\
+		g_value_init (&gvalue, gtype); \
+		gtype_set (&gvalue, (value)); \
+		return nm_g_object_set_property ((object), (property_name), &gvalue, (error)); \
+	} G_STMT_END
+
+gboolean
+nm_g_object_set_property_string (GObject *object,
+                                 const char *property_name,
+                                 const char *value,
+                                 GError **error)
+{
+	_set_property (object, property_name, G_TYPE_STRING, g_value_set_string, value, error);
+}
+
+gboolean
+nm_g_object_set_property_string_static (GObject *object,
+                                        const char *property_name,
+                                        const char *value,
+                                        GError **error)
+{
+	_set_property (object, property_name, G_TYPE_STRING, g_value_set_static_string, value, error);
+}
+
+gboolean
+nm_g_object_set_property_string_take (GObject *object,
+                                      const char *property_name,
+                                      char *value,
+                                      GError **error)
+{
+	_set_property (object, property_name, G_TYPE_STRING, g_value_take_string, value, error);
+}
+
 gboolean
 nm_g_object_set_property_boolean (GObject *object,
-                                  const char   *property_name,
+                                  const char *property_name,
                                   gboolean value,
                                   GError **error)
 {
-	nm_auto_unset_gvalue GValue gvalue = { 0 };
+	_set_property (object, property_name, G_TYPE_BOOLEAN, g_value_set_boolean, !!value, error);
+}
 
-	g_value_init (&gvalue, G_TYPE_BOOLEAN);
-	g_value_set_boolean (&gvalue, !!value);
-	return nm_g_object_set_property (object, property_name, &gvalue, error);
+gboolean
+nm_g_object_set_property_char (GObject *object,
+                               const char *property_name,
+                               gint8 value,
+                               GError **error)
+{
+	/* glib says about G_TYPE_CHAR:
+	 *
+	 * The type designated by G_TYPE_CHAR is unconditionally an 8-bit signed integer.
+	 *
+	 * This is always a (signed!) char. */
+	_set_property (object, property_name, G_TYPE_CHAR, g_value_set_schar, value, error);
+}
+
+gboolean
+nm_g_object_set_property_uchar (GObject *object,
+                                const char *property_name,
+                                guint8 value,
+                                GError **error)
+{
+	_set_property (object, property_name, G_TYPE_UCHAR, g_value_set_uchar, value, error);
+}
+
+gboolean
+nm_g_object_set_property_int (GObject *object,
+                              const char *property_name,
+                              int value,
+                              GError **error)
+{
+	_set_property (object, property_name, G_TYPE_INT, g_value_set_int, value, error);
+}
+
+gboolean
+nm_g_object_set_property_int64 (GObject *object,
+                                const char *property_name,
+                                gint64 value,
+                                GError **error)
+{
+	_set_property (object, property_name, G_TYPE_INT64, g_value_set_int64, value, error);
 }
 
 gboolean
 nm_g_object_set_property_uint (GObject *object,
-                               const char   *property_name,
+                               const char *property_name,
                                guint value,
                                GError **error)
 {
-	nm_auto_unset_gvalue GValue gvalue = { 0 };
+	_set_property (object, property_name, G_TYPE_UINT, g_value_set_uint, value, error);
+}
+
+gboolean
+nm_g_object_set_property_uint64 (GObject *object,
+                                 const char *property_name,
+                                 guint64 value,
+                                 GError **error)
+{
+	_set_property (object, property_name, G_TYPE_UINT64, g_value_set_uint64, value, error);
+}
 
-	g_value_init (&gvalue, G_TYPE_UINT);
-	g_value_set_uint (&gvalue, value);
-	return nm_g_object_set_property (object, property_name, &gvalue, error);
+gboolean
+nm_g_object_set_property_flags (GObject *object,
+                                const char *property_name,
+                                GType gtype,
+                                guint value,
+                                GError **error)
+{
+	nm_assert (({
+	                nm_auto_unref_gtypeclass GTypeClass *gtypeclass = g_type_class_ref (gtype);
+	                G_IS_FLAGS_CLASS (gtypeclass);
+	           }));
+	_set_property (object, property_name, gtype, g_value_set_flags, value, error);
+}
+
+gboolean
+nm_g_object_set_property_enum (GObject *object,
+                               const char *property_name,
+                               GType gtype,
+                               int value,
+                               GError **error)
+{
+	nm_assert (({
+	                nm_auto_unref_gtypeclass GTypeClass *gtypeclass = g_type_class_ref (gtype);
+	                G_IS_ENUM_CLASS (gtypeclass);
+	           }));
+	_set_property (object, property_name, gtype, g_value_set_enum, value, error);
 }
 
 GParamSpec *
@@ -1233,6 +1473,53 @@ nm_g_object_class_find_property_from_gtype (GType gtype,
 
 /*****************************************************************************/
 
+/**
+ * nm_g_type_find_implementing_class_for_property:
+ * @gtype: the GObject type which has a property @pname
+ * @pname: the name of the property to look up
+ *
+ * This is only a helper function for printf debugging. It's not
+ * used in actual code. Hence, the function just asserts that
+ * @pname and @gtype arguments are suitable. It cannot fail.
+ *
+ * Returns: the most ancestor type of @gtype, that
+ *   implements the property @pname. It means, it
+ *   searches the type hierarchy to find the type
+ *   that added @pname.
+ */
+GType
+nm_g_type_find_implementing_class_for_property (GType gtype,
+                                                const char *pname)
+{
+	nm_auto_unref_gtypeclass GObjectClass *klass = NULL;
+	GParamSpec *pspec;
+
+	g_return_val_if_fail (pname, G_TYPE_INVALID);
+
+	klass = g_type_class_ref (gtype);
+	g_return_val_if_fail (G_IS_OBJECT_CLASS (klass), G_TYPE_INVALID);
+
+	pspec = g_object_class_find_property (klass, pname);
+	g_return_val_if_fail (pspec, G_TYPE_INVALID);
+
+	gtype = G_TYPE_FROM_CLASS (klass);
+
+	while (TRUE) {
+		nm_auto_unref_gtypeclass GObjectClass *k = NULL;
+
+		k = g_type_class_ref (g_type_parent (gtype));
+
+		g_return_val_if_fail (G_IS_OBJECT_CLASS (k), G_TYPE_INVALID);
+
+		if (g_object_class_find_property (k, pname) != pspec)
+			return gtype;
+
+		gtype = G_TYPE_FROM_CLASS (k);
+	}
+}
+
+/*****************************************************************************/
+
 static void
 _str_append_escape (GString *s, char ch)
 {
@@ -1564,7 +1851,7 @@ nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns)
 
 	r = ppoll (&pollfd, 1, pts, NULL);
 	if (r < 0)
-		return -errno;
+		return -NM_ERRNO_NATIVE (errno);
 	if (r == 0)
 		return 0;
 	return pollfd.revents;
@@ -1591,10 +1878,12 @@ nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll)
 
 		k = read (fd, p, nbytes);
 		if (k < 0) {
-			if (errno == EINTR)
+			int errsv = errno;
+
+			if (errsv == EINTR)
 				continue;
 
-			if (errno == EAGAIN && do_poll) {
+			if (errsv == EAGAIN && do_poll) {
 
 				/* We knowingly ignore any return value here,
 				 * and expect that any error/EOF is reported
@@ -1604,7 +1893,7 @@ nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll)
 				continue;
 			}
 
-			return n > 0 ? n : -errno;
+			return n > 0 ? n : -NM_ERRNO_NATIVE (errsv);
 		}
 
 		if (k == 0)
@@ -2126,3 +2415,327 @@ _nm_utils_unescape_spaces (char *str)
 }
 
 #undef IS_SPACE
+
+/*****************************************************************************/
+
+typedef struct {
+	gpointer callback_user_data;
+	GCancellable *cancellable;
+	NMUtilsInvokeOnIdleCallback callback;
+	gulong cancelled_id;
+	guint idle_id;
+} InvokeOnIdleData;
+
+static gboolean
+_nm_utils_invoke_on_idle_cb_idle (gpointer user_data)
+{
+	InvokeOnIdleData *data = user_data;
+
+	data->idle_id = 0;
+	nm_clear_g_signal_handler (data->cancellable, &data->cancelled_id);
+
+	data->callback (data->callback_user_data, data->cancellable);
+	nm_g_object_unref (data->cancellable);
+	g_slice_free (InvokeOnIdleData, data);
+	return G_SOURCE_REMOVE;
+}
+
+static void
+_nm_utils_invoke_on_idle_cb_cancelled (GCancellable *cancellable,
+                                       InvokeOnIdleData *data)
+{
+	/* on cancellation, we invoke the callback synchronously. */
+	nm_clear_g_signal_handler (data->cancellable, &data->cancelled_id);
+	nm_clear_g_source (&data->idle_id);
+	data->callback (data->callback_user_data, data->cancellable);
+	nm_g_object_unref (data->cancellable);
+	g_slice_free (InvokeOnIdleData, data);
+}
+
+void
+nm_utils_invoke_on_idle (NMUtilsInvokeOnIdleCallback callback,
+                         gpointer callback_user_data,
+                         GCancellable *cancellable)
+{
+	InvokeOnIdleData *data;
+
+	g_return_if_fail (callback);
+
+	data = g_slice_new (InvokeOnIdleData);
+	data->callback = callback;
+	data->callback_user_data = callback_user_data;
+	data->cancellable = nm_g_object_ref (cancellable);
+	if (   cancellable
+	    && !g_cancellable_is_cancelled (cancellable)) {
+		/* if we are passed a non-cancelled cancellable, we register to the "cancelled"
+		 * signal an invoke the callback synchronously (from the signal handler).
+		 *
+		 * We don't do that,
+		 *  - if the cancellable is already cancelled (because we don't want to invoke
+		 *    the callback synchronously from the caller).
+		 *  - if we have no cancellable at hand. */
+		data->cancelled_id = g_signal_connect (cancellable,
+		                                       "cancelled",
+		                                       G_CALLBACK (_nm_utils_invoke_on_idle_cb_cancelled),
+		                                       data);
+	} else
+		data->cancelled_id = 0;
+	data->idle_id = g_idle_add (_nm_utils_invoke_on_idle_cb_idle, data);
+}
+
+/*****************************************************************************/
+
+int
+nm_utils_getpagesize (void)
+{
+	static volatile int val = 0;
+	long l;
+	int v;
+
+	v = g_atomic_int_get (&val);
+
+	if (G_UNLIKELY (v == 0)) {
+		l = sysconf (_SC_PAGESIZE);
+
+		g_return_val_if_fail (l > 0 && l < G_MAXINT, 4*1024);
+
+		v = (int) l;
+		if (!g_atomic_int_compare_and_exchange (&val, 0, v)) {
+			v = g_atomic_int_get (&val);
+			g_return_val_if_fail (v > 0, 4*1024);
+		}
+	}
+
+	nm_assert (v > 0);
+#if NM_MORE_ASSERTS > 5
+	nm_assert (v == getpagesize ());
+	nm_assert (v == sysconf (_SC_PAGESIZE));
+#endif
+
+	return v;
+}
+
+gboolean
+nm_utils_memeqzero (gconstpointer data, gsize length)
+{
+	const unsigned char *p = data;
+	int len;
+
+	/* Taken from https://github.com/rustyrussell/ccan/blob/9d2d2c49f053018724bcc6e37029da10b7c3d60d/ccan/mem/mem.c#L92,
+	 * CC-0 licensed. */
+
+	/* Check first 16 bytes manually */
+	for (len = 0; len < 16; len++) {
+		if (!length)
+			return TRUE;
+		if (*p)
+			return FALSE;
+		p++;
+		length--;
+	}
+
+	/* Now we know that's zero, memcmp with self. */
+	return memcmp (data, p, length) == 0;
+}
+
+/**
+ * nm_utils_bin2hexstr_full:
+ * @addr: pointer of @length bytes. If @length is zero, this may
+ *   also be %NULL.
+ * @length: number of bytes in @addr. May also be zero, in which
+ *   case this will return an empty string.
+ * @delimiter: either '\0', otherwise the output string will have the
+ *   given delimiter character between each two hex numbers.
+ * @upper_case: if TRUE, use upper case ASCII characters for hex.
+ * @out: if %NULL, the function will allocate a new buffer of
+ *   either (@length*2+1) or (@length*3) bytes, depending on whether
+ *   a @delimiter is specified. In that case, the allocated buffer will
+ *   be returned and must be freed by the caller.
+ *   If not %NULL, the buffer must already be preallocated and contain
+ *   at least (@length*2+1) or (@length*3) bytes, depending on the delimiter.
+ *
+ * Returns: the binary value converted to a hex string. If @out is given,
+ *   this always returns @out. If @out is %NULL, a newly allocated string
+ *   is returned.
+ */
+char *
+nm_utils_bin2hexstr_full (gconstpointer addr,
+                          gsize length,
+                          char delimiter,
+                          gboolean upper_case,
+                          char *out)
+{
+	const guint8 *in = addr;
+	const char *LOOKUP = upper_case ? "0123456789ABCDEF" : "0123456789abcdef";
+	char *out0;
+
+	if (out)
+		out0 = out;
+	else {
+		out0 = out = g_new (char, delimiter == '\0'
+		                          ? length * 2 + 1
+		                          : length * 3);
+	}
+
+	/* @out must contain at least @length*3 bytes if @delimiter is set,
+	 * otherwise, @length*2+1. */
+
+	if (length > 0) {
+		nm_assert (in);
+		for (;;) {
+			const guint8 v = *in++;
+
+			*out++ = LOOKUP[v >> 4];
+			*out++ = LOOKUP[v & 0x0F];
+			length--;
+			if (!length)
+				break;
+			if (delimiter)
+				*out++ = delimiter;
+		}
+	}
+
+	*out = '\0';
+	return out0;
+}
+
+guint8 *
+nm_utils_hexstr2bin_full (const char *hexstr,
+                          gboolean allow_0x_prefix,
+                          gboolean delimiter_required,
+                          const char *delimiter_candidates,
+                          gsize required_len,
+                          guint8 *buffer,
+                          gsize buffer_len,
+                          gsize *out_len)
+{
+	const char *in = hexstr;
+	guint8 *out = buffer;
+	gboolean delimiter_has = TRUE;
+	guint8 delimiter = '\0';
+	gsize len;
+
+	nm_assert (hexstr);
+	nm_assert (buffer);
+	nm_assert (required_len > 0 || out_len);
+
+	if (   allow_0x_prefix
+	    && in[0] == '0'
+	    && in[1] == 'x')
+		in += 2;
+
+	while (TRUE) {
+		const guint8 d1 = in[0];
+		guint8 d2;
+		int i1, i2;
+
+		i1 = nm_utils_hexchar_to_int (d1);
+		if (i1 < 0)
+			goto fail;
+
+		/* If there's no leading zero (ie "aa:b:cc") then fake it */
+		d2 = in[1];
+		if (   d2
+		    && (i2 = nm_utils_hexchar_to_int (d2)) >= 0) {
+			*out++ = (i1 << 4) + i2;
+			d2 = in[2];
+			if (!d2)
+				break;
+			in += 2;
+		} else {
+			/* Fake leading zero */
+			*out++ = i1;
+			if (!d2) {
+				if (!delimiter_has) {
+					/* when using no delimiter, there must be pairs of hex chars */
+					goto fail;
+				}
+				break;
+			}
+			in += 1;
+		}
+
+		if (--buffer_len == 0)
+			goto fail;
+
+		if (delimiter_has) {
+			if (d2 != delimiter) {
+				if (delimiter)
+					goto fail;
+				if (delimiter_candidates) {
+					while (delimiter_candidates[0]) {
+						if (delimiter_candidates++[0] == d2)
+							delimiter = d2;
+					}
+				}
+				if (!delimiter) {
+					if (delimiter_required)
+						goto fail;
+					delimiter_has = FALSE;
+					continue;
+				}
+			}
+			in++;
+		}
+	}
+
+	len = out - buffer;
+	if (   required_len == 0
+	    || len == required_len) {
+		NM_SET_OUT (out_len, len);
+		return buffer;
+	}
+
+fail:
+	NM_SET_OUT (out_len, 0);
+	return NULL;
+}
+
+guint8 *
+nm_utils_hexstr2bin_alloc (const char *hexstr,
+                           gboolean allow_0x_prefix,
+                           gboolean delimiter_required,
+                           const char *delimiter_candidates,
+                           gsize required_len,
+                           gsize *out_len)
+{
+	guint8 *buffer;
+	gsize buffer_len, len;
+
+	g_return_val_if_fail (hexstr, NULL);
+
+	nm_assert (required_len > 0 || out_len);
+
+	if (   allow_0x_prefix
+	    && hexstr[0] == '0'
+	    && hexstr[1] == 'x')
+		hexstr += 2;
+
+	if (!hexstr[0])
+		goto fail;
+
+	if (required_len > 0)
+		buffer_len = required_len;
+	else
+		buffer_len = strlen (hexstr) / 2 + 3;
+
+	buffer = g_malloc (buffer_len);
+
+	if (nm_utils_hexstr2bin_full (hexstr,
+	                              FALSE,
+	                              delimiter_required,
+	                              delimiter_candidates,
+	                              required_len,
+	                              buffer,
+	                              buffer_len,
+	                              &len)) {
+		NM_SET_OUT (out_len, len);
+		return buffer;
+	}
+
+	g_free (buffer);
+
+fail:
+	NM_SET_OUT (out_len, 0);
+	return NULL;
+}
diff --git a/shared/nm-utils/nm-shared-utils.h b/shared/nm-utils/nm-shared-utils.h
index 82eebc9d..65e34959 100644
--- a/shared/nm-utils/nm-shared-utils.h
+++ b/shared/nm-utils/nm-shared-utils.h
@@ -26,6 +26,18 @@
 
 /*****************************************************************************/
 
+pid_t nm_utils_gettid (void);
+
+gboolean _nm_assert_on_main_thread (void);
+
+#if NM_MORE_ASSERTS > 5
+#define NM_ASSERT_ON_MAIN_THREAD() G_STMT_START { nm_assert (_nm_assert_on_main_thread ()); } G_STMT_END
+#else
+#define NM_ASSERT_ON_MAIN_THREAD() G_STMT_START {                                         ; } G_STMT_END
+#endif
+
+/*****************************************************************************/
+
 static inline gboolean
 _NM_INT_NOT_NEGATIVE (gssize val)
 {
@@ -76,8 +88,9 @@ static inline char
 nm_utils_addr_family_to_char (int addr_family)
 {
 	switch (addr_family) {
-	case AF_INET:  return '4';
-	case AF_INET6: return '6';
+	case AF_UNSPEC: return 'X';
+	case AF_INET:   return '4';
+	case AF_INET6:  return '6';
 	}
 	g_return_val_if_reached ('?');
 }
@@ -101,6 +114,7 @@ typedef struct {
 	union {
 		guint8 addr_ptr[1];
 		in_addr_t addr4;
+		struct in_addr addr4_struct;
 		struct in6_addr addr6;
 
 		/* NMIPAddr is really a union for IP addresses.
@@ -113,16 +127,29 @@ typedef struct {
 extern const NMIPAddr nm_ip_addr_zero;
 
 static inline void
-nm_ip_addr_set (int addr_family, gpointer dst, const NMIPAddr *src)
+nm_ip_addr_set (int addr_family, gpointer dst, gconstpointer src)
 {
 	nm_assert_addr_family (addr_family);
 	nm_assert (dst);
 	nm_assert (src);
 
-	if (addr_family != AF_INET6)
-		*((in_addr_t *) dst) = src->addr4;
-	else
-		*((struct in6_addr *) dst) = src->addr6;
+	memcpy (dst,
+	        src,
+	        (addr_family != AF_INET6)
+	          ? sizeof (in_addr_t)
+	          : sizeof (struct in6_addr));
+}
+
+gboolean nm_ip_addr_set_from_untrusted (int addr_family,
+                                        gpointer dst,
+                                        gconstpointer src,
+                                        gsize src_len,
+                                        int *out_addr_family);
+
+static inline gboolean
+nm_ip4_addr_is_localhost (in_addr_t addr4)
+{
+	return (addr4 & htonl (0xFF000000u)) == htonl (0x7F000000u);
 }
 
 /*****************************************************************************/
@@ -217,19 +244,7 @@ nm_ip_addr_set (int addr_family, gpointer dst, const NMIPAddr *src)
 
 /*****************************************************************************/
 
-static inline gboolean
-nm_utils_mem_all_zero (gconstpointer mem, gsize len)
-{
-	const guint8 *p;
-
-	for (p = mem; len-- > 0; p++) {
-		if (*p != 0)
-			return FALSE;
-	}
-
-	/* incidentally, a buffer with len==0, is also *all-zero*. */
-	return TRUE;
-}
+gboolean nm_utils_memeqzero (gconstpointer data, gsize length);
 
 /*****************************************************************************/
 
@@ -262,6 +277,17 @@ nm_memdup (gconstpointer data, gsize size)
 	return p;
 }
 
+static inline char *
+_nm_strndup_a_step (char *s, const char *str, gsize len)
+{
+	NM_PRAGMA_WARNING_DISABLE ("-Wstringop-truncation");
+	if (len > 0)
+		strncpy (s, str, len);
+	s[len] = '\0';
+	return s;
+	NM_PRAGMA_WARNING_REENABLE;
+}
+
 /* Similar to g_strndup(), however, if the string (including the terminating
  * NUL char) fits into alloca_maxlen, this will alloca() the memory.
  *
@@ -270,7 +296,12 @@ nm_memdup (gconstpointer data, gsize size)
  *
  * In case malloc() is necessary, @out_str_free will be set (this string
  * must be freed afterwards). It is permissible to pass %NULL as @out_str_free,
- * if you ensure that len < alloca_maxlen. */
+ * if you ensure that len < alloca_maxlen.
+ *
+ * Note that just like g_strndup(), this always returns a buffer with @len + 1
+ * bytes, even if strlen(@str) is shorter than that (NUL terminated early). We fill
+ * the buffer with strncpy(), which means, that @str is copied up to the first
+ * NUL character and then filled with NUL characters. */
 #define nm_strndup_a(alloca_maxlen, str, len, out_str_free) \
 	({ \
 		const gsize _alloca_maxlen = (alloca_maxlen); \
@@ -279,6 +310,8 @@ nm_memdup (gconstpointer data, gsize size)
 		char **const _out_str_free = (out_str_free); \
 		char *_s; \
 		\
+		G_STATIC_ASSERT_EXPR ((alloca_maxlen) <= 300); \
+		\
 		if (   _out_str_free \
 		    && _len >= _alloca_maxlen) { \
 			_s = g_malloc (_len + 1); \
@@ -287,14 +320,46 @@ nm_memdup (gconstpointer data, gsize size)
 			g_assert (_len < _alloca_maxlen); \
 			_s = g_alloca (_len + 1); \
 		} \
-		if (_len > 0) \
-			strncpy (_s, _str, _len); \
-		_s[_len] = '\0'; \
-		_s; \
+		_nm_strndup_a_step (_s, _str, _len); \
 	})
 
 /*****************************************************************************/
 
+/* generic macro to convert an int to a (heap allocated) string.
+ *
+ * Usually, an inline function nm_strdup_int64() would be enough. However,
+ * that cannot be used for guint64. So, we would also need nm_strdup_uint64().
+ * This causes subtle error potential, because the caller needs to ensure to
+ * use the right one (and compiler isn't going to help as it silently casts).
+ *
+ * Instead, this generic macro is supposed to handle all integers correctly. */
+#if _NM_CC_SUPPORT_GENERIC
+#define nm_strdup_int(val) \
+	_Generic ((val), \
+	          char:               g_strdup_printf ("%d",   (int)                (val)), \
+	          \
+	          signed char:        g_strdup_printf ("%d",   (signed)             (val)), \
+	          signed short:       g_strdup_printf ("%d",   (signed)             (val)), \
+	          signed:             g_strdup_printf ("%d",   (signed)             (val)), \
+	          signed long:        g_strdup_printf ("%ld",  (signed long)        (val)), \
+	          signed long long:   g_strdup_printf ("%lld", (signed long long)   (val)), \
+	          \
+	          unsigned char:      g_strdup_printf ("%u",   (unsigned)           (val)), \
+	          unsigned short:     g_strdup_printf ("%u",   (unsigned)           (val)), \
+	          unsigned:           g_strdup_printf ("%u",   (unsigned)           (val)), \
+	          unsigned long:      g_strdup_printf ("%lu",  (unsigned long)      (val)), \
+	          unsigned long long: g_strdup_printf ("%llu", (unsigned long long) (val))  \
+	)
+#else
+#define nm_strdup_int(val) \
+	(  (   sizeof (val) == sizeof (guint64) \
+	    && ((typeof (val)) -1) > 0) \
+	 ? g_strdup_printf ("%"G_GUINT64_FORMAT, (guint64) (val)) \
+	 : g_strdup_printf ("%"G_GINT64_FORMAT, (gint64) (val)))
+#endif
+
+/*****************************************************************************/
+
 extern const void *const _NM_PTRARRAY_EMPTY[1];
 
 #define NM_PTRARRAY_EMPTY(type) ((type const*) _NM_PTRARRAY_EMPTY)
@@ -315,6 +380,7 @@ _nm_utils_strbuf_init (char *buf, gsize len, char **p_buf_ptr, gsize *p_buf_len)
 void nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...) _nm_printf (3, 4);
 void nm_utils_strbuf_append_c (char **buf, gsize *len, char c);
 void nm_utils_strbuf_append_str (char **buf, gsize *len, const char *str);
+void nm_utils_strbuf_append_bin (char **buf, gsize *len, gconstpointer str, gsize str_len);
 void nm_utils_strbuf_seek_end (char **buf, gsize *len);
 
 const char *nm_strquote (char *buf, gsize buf_len, const char *str);
@@ -429,6 +495,7 @@ gboolean nm_utils_ip_is_site_local (int addr_family,
 
 gboolean nm_utils_parse_inaddr_bin  (int addr_family,
                                      const char *text,
+                                     int *out_addr_family,
                                      gpointer out_addr);
 
 gboolean nm_utils_parse_inaddr (int addr_family,
@@ -437,6 +504,7 @@ gboolean nm_utils_parse_inaddr (int addr_family,
 
 gboolean nm_utils_parse_inaddr_prefix_bin (int addr_family,
                                            const char *text,
+                                           int *out_addr_family,
                                            gpointer out_addr,
                                            int *out_prefix);
 
@@ -579,19 +647,6 @@ _nm_g_slice_free_fcn_define (16)
 
 /*****************************************************************************/
 
-static inline int
-nm_errno (int errsv)
-{
-	/* several API returns negative errno values as errors. Normalize
-	 * negative values to positive values.
-	 *
-	 * As a special case, map G_MININT to G_MAXINT. If you care about the
-	 * distinction, then check for G_MININT before. */
-	return errsv >= 0
-	       ? errsv
-	       : ((errsv == G_MININT) ? G_MAXINT : -errsv);
-}
-
 /**
  * NMUtilsError:
  * @NM_UTILS_ERROR_UNKNOWN: unknown or unclassified error
@@ -657,35 +712,106 @@ nm_utils_error_set_literal (GError **error, int error_code, const char *literal)
 	g_set_error ((error), NM_UTILS_ERROR, error_code, __VA_ARGS__)
 
 #define nm_utils_error_set_errno(error, errsv, fmt, ...) \
-	g_set_error ((error), \
-	             NM_UTILS_ERROR, \
-	             NM_UTILS_ERROR_UNKNOWN, \
-	             fmt, \
-	             ##__VA_ARGS__, \
-	             g_strerror (nm_errno (errsv)))
+	G_STMT_START { \
+		char _bstrerr[NM_STRERROR_BUFSIZE]; \
+		\
+		g_set_error ((error), \
+		             NM_UTILS_ERROR, \
+		             NM_UTILS_ERROR_UNKNOWN, \
+		             fmt, \
+		             ##__VA_ARGS__, \
+		             nm_strerror_native_r (({ \
+		                                      const int _errsv = (errsv); \
+		                                      \
+		                                      (  _errsv >= 0 \
+		                                       ? _errsv \
+		                                       : (  G_UNLIKELY (_errsv == G_MININT) \
+		                                          ? G_MAXINT \
+		                                          : -errsv)); \
+		                                   }), \
+		                                   _bstrerr, \
+		                                   sizeof (_bstrerr))); \
+	} G_STMT_END
 
 /*****************************************************************************/
 
 gboolean nm_g_object_set_property (GObject *object,
-                                   const char   *property_name,
+                                   const char *property_name,
                                    const GValue *value,
                                    GError **error);
 
+gboolean nm_g_object_set_property_string (GObject *object,
+                                          const char *property_name,
+                                          const char *value,
+                                          GError **error);
+
+gboolean nm_g_object_set_property_string_static (GObject *object,
+                                                 const char *property_name,
+                                                 const char *value,
+                                                 GError **error);
+
+gboolean nm_g_object_set_property_string_take (GObject *object,
+                                               const char *property_name,
+                                               char *value,
+                                               GError **error);
+
 gboolean nm_g_object_set_property_boolean (GObject *object,
-                                           const char   *property_name,
+                                           const char *property_name,
                                            gboolean value,
                                            GError **error);
 
+gboolean nm_g_object_set_property_char (GObject *object,
+                                        const char *property_name,
+                                        gint8 value,
+                                        GError **error);
+
+gboolean nm_g_object_set_property_uchar (GObject *object,
+                                         const char *property_name,
+                                         guint8 value,
+                                         GError **error);
+
+gboolean nm_g_object_set_property_int (GObject *object,
+                                       const char *property_name,
+                                       int value,
+                                       GError **error);
+
+gboolean nm_g_object_set_property_int64 (GObject *object,
+                                         const char *property_name,
+                                         gint64 value,
+                                         GError **error);
+
 gboolean nm_g_object_set_property_uint (GObject *object,
-                                        const char   *property_name,
+                                        const char *property_name,
                                         guint value,
                                         GError **error);
 
+gboolean nm_g_object_set_property_uint64 (GObject *object,
+                                          const char *property_name,
+                                          guint64 value,
+                                          GError **error);
+
+gboolean nm_g_object_set_property_flags (GObject *object,
+                                         const char *property_name,
+                                         GType gtype,
+                                         guint value,
+                                         GError **error);
+
+gboolean nm_g_object_set_property_enum (GObject *object,
+                                        const char *property_name,
+                                        GType gtype,
+                                        int value,
+                                        GError **error);
+
 GParamSpec *nm_g_object_class_find_property_from_gtype (GType gtype,
                                                         const char *property_name);
 
 /*****************************************************************************/
 
+GType nm_g_type_find_implementing_class_for_property (GType gtype,
+                                                      const char *pname);
+
+/*****************************************************************************/
+
 typedef enum {
 	NM_UTILS_STR_UTF8_SAFE_FLAG_NONE                = 0,
 	NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL         = 0x0001,
@@ -949,4 +1075,84 @@ void _nm_utils_user_data_unpack (gpointer user_data, int nargs, ...);
 const char *_nm_utils_escape_spaces (const char *str, char **to_free);
 char *_nm_utils_unescape_spaces (char *str);
 
+/*****************************************************************************/
+
+typedef void (*NMUtilsInvokeOnIdleCallback) (gpointer callback_user_data,
+                                             GCancellable *cancellable);
+
+void nm_utils_invoke_on_idle (NMUtilsInvokeOnIdleCallback callback,
+                              gpointer callback_user_data,
+                              GCancellable *cancellable);
+
+/*****************************************************************************/
+
+static inline void
+nm_strv_ptrarray_add_string_take (GPtrArray *cmd,
+                                  char *str)
+{
+	nm_assert (cmd);
+	nm_assert (str);
+
+	g_ptr_array_add (cmd, str);
+}
+
+static inline void
+nm_strv_ptrarray_add_string_dup (GPtrArray *cmd,
+                                 const char *str)
+{
+	nm_strv_ptrarray_add_string_take (cmd,
+	                                  g_strdup (str));
+}
+
+#define nm_strv_ptrarray_add_string_concat(cmd, ...) \
+	nm_strv_ptrarray_add_string_take ((cmd), g_strconcat (__VA_ARGS__, NULL))
+
+#define nm_strv_ptrarray_add_string_printf(cmd, ...) \
+	nm_strv_ptrarray_add_string_take ((cmd), g_strdup_printf (__VA_ARGS__))
+
+#define nm_strv_ptrarray_add_int(cmd, val) \
+	nm_strv_ptrarray_add_string_take ((cmd), nm_strdup_int (val))
+
+static inline void
+nm_strv_ptrarray_take_gstring (GPtrArray *cmd,
+                               GString **gstr)
+{
+	nm_assert (gstr && *gstr);
+
+	nm_strv_ptrarray_add_string_take (cmd,
+	                                  g_string_free (g_steal_pointer (gstr),
+	                                                 FALSE));
+}
+
+/*****************************************************************************/
+
+int nm_utils_getpagesize (void);
+
+/*****************************************************************************/
+
+char *nm_utils_bin2hexstr_full (gconstpointer addr,
+                                gsize length,
+                                char delimiter,
+                                gboolean upper_case,
+                                char *out);
+
+guint8 *nm_utils_hexstr2bin_full (const char *hexstr,
+                                  gboolean allow_0x_prefix,
+                                  gboolean delimiter_required,
+                                  const char *delimiter_candidates,
+                                  gsize required_len,
+                                  guint8 *buffer,
+                                  gsize buffer_len,
+                                  gsize *out_len);
+
+#define nm_utils_hexstr2bin_buf(hexstr, allow_0x_prefix, delimiter_required, delimiter_candidates, buffer) \
+    nm_utils_hexstr2bin_full ((hexstr), (allow_0x_prefix), (delimiter_required), (delimiter_candidates), G_N_ELEMENTS (buffer), (buffer), G_N_ELEMENTS (buffer), NULL)
+
+guint8 *nm_utils_hexstr2bin_alloc (const char *hexstr,
+                                   gboolean allow_0x_prefix,
+                                   gboolean delimiter_required,
+                                   const char *delimiter_candidates,
+                                   gsize required_len,
+                                   gsize *out_len);
+
 #endif /* __NM_SHARED_UTILS_H__ */
diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h
index 7decd363..c5ea5e3f 100644
--- a/shared/nm-utils/nm-test-utils.h
+++ b/shared/nm-utils/nm-test-utils.h
@@ -30,6 +30,9 @@
  *
  * Our tests (make check) include this header-only file nm-test-utils.h.
  *
+ * You should always include this header *as last*. Reason is, that depending on
+ * previous includes, functionality will be enabled.
+ *
  * Logging:
  *   In tests, nm-logging redirects to glib logging. By default, glib suppresses all debug
  *   messages unless you set G_MESSAGES_DEBUG. To enable debug logging, you can explicitly set
@@ -110,7 +113,9 @@
 #include <string.h>
 #include <errno.h>
 
+#ifndef NM_TEST_UTILS_NO_LIBNM
 #include "nm-utils.h"
+#endif
 
 /*****************************************************************************/
 
@@ -191,6 +196,25 @@
 
 /*****************************************************************************/
 
+/* Our nm-error error numbers use negative values to signal failure.
+ * A non-negative value signals success. Hence, the correct way for checking
+ * is always (r < 0) vs. (r >= 0). Never (r == 0).
+ *
+ * For assertions in tests, we also want to assert that no positive values
+ * are returned. For a lot of functions, positive return values are unexpected
+ * and a bug. This macro evaluates @r to success or failure, while asserting
+ * that @r is not positive. */
+#define NMTST_NM_ERR_SUCCESS(r) \
+	({ \
+		const int _r = (r); \
+		\
+		if (_r >= 0) \
+			g_assert_cmpint (_r, ==, 0); \
+		(_r >= 0); \
+	})
+
+/*****************************************************************************/
+
 struct __nmtst_internal
 {
 	GRand *rand0;
@@ -695,6 +719,7 @@ nmtst_test_quick (void)
 #else
 #define NMTST_EXPECT_LIBNM(level, msg)          NMTST_EXPECT ("libnm", level, msg)
 
+#define NMTST_EXPECT_LIBNM_WARNING(msg)         NMTST_EXPECT_LIBNM (G_LOG_LEVEL_WARNING, msg)
 #define NMTST_EXPECT_LIBNM_CRITICAL(msg)        NMTST_EXPECT_LIBNM (G_LOG_LEVEL_CRITICAL, msg)
 #endif
 
@@ -880,6 +905,16 @@ nmtst_rand_buf (GRand *rand, gpointer buffer, gsize buffer_length)
 	return buffer;
 }
 
+#define _nmtst_rand_select(uniq, v0, ...) \
+	({ \
+		typeof (v0) NM_UNIQ_T (UNIQ, uniq)[1 + NM_NARG (__VA_ARGS__)] = { (v0), __VA_ARGS__ }; \
+		\
+		NM_UNIQ_T (UNIQ, uniq)[nmtst_get_rand_int () % G_N_ELEMENTS (NM_UNIQ_T (UNIQ, uniq))]; \
+	})
+
+#define nmtst_rand_select(...) \
+	_nmtst_rand_select (NM_UNIQ, __VA_ARGS__)
+
 static inline void *
 nmtst_rand_perm (GRand *rand, void *dst, const void *src, gsize elmt_size, gsize n_elmt)
 {
@@ -1025,7 +1060,7 @@ nmtst_reexec_sudo (void)
 	execvp (__nmtst_internal.sudo_cmd, argv);
 
 	errsv = errno;
-	g_error (">> exec %s failed: %d - %s", __nmtst_internal.sudo_cmd, errsv, strerror (errsv));
+	g_error (">> exec %s failed: %d - %s", __nmtst_internal.sudo_cmd, errsv, nm_strerror_native (errsv));
 }
 
 /*****************************************************************************/
@@ -1089,6 +1124,8 @@ __define_nmtst_static(02, 1024)
 __define_nmtst_static(03, 1024)
 #undef __define_nmtst_static
 
+#if defined (__NM_UTILS_H__) || defined (NM_UTILS_H)
+
 #define NMTST_UUID_INIT(uuid) \
 	gs_free char *_nmtst_hidden_##uuid = nm_utils_uuid_generate (); \
 	const char *const uuid = _nmtst_hidden_##uuid
@@ -1105,6 +1142,8 @@ nmtst_uuid_generate (void)
 	return u;
 }
 
+#endif
+
 #define NMTST_SWAP(x,y) \
 	G_STMT_START { \
 		char __nmtst_swap_temp[sizeof(x) == sizeof(y) ? (signed) sizeof(x) : -1]; \
@@ -1156,6 +1195,48 @@ nmtst_inet6_from_string (const char *str)
 	return &addr;
 }
 
+static inline gconstpointer
+nmtst_inet_from_string (int addr_family, const char *str)
+{
+	if (addr_family == AF_INET) {
+		static in_addr_t a;
+
+		a = nmtst_inet4_from_string (str);
+		return &a;
+	}
+	if (addr_family == AF_INET6)
+		return nmtst_inet6_from_string (str);
+
+	g_assert_not_reached ();
+	return NULL;
+}
+
+static inline const char *
+nmtst_inet_to_string (int addr_family, gconstpointer addr)
+{
+	static char buf[NM_CONST_MAX (INET6_ADDRSTRLEN, INET_ADDRSTRLEN)];
+
+	g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	g_assert (addr);
+
+	if (inet_ntop (addr_family, addr, buf, sizeof (buf)) != buf)
+		g_assert_not_reached ();
+
+	return buf;
+}
+
+static inline const char *
+nmtst_inet4_to_string (in_addr_t addr)
+{
+	return nmtst_inet_to_string (AF_INET, &addr);
+}
+
+static inline const char *
+nmtst_inet6_to_string (const struct in6_addr *addr)
+{
+	return nmtst_inet_to_string (AF_INET6, addr);
+}
+
 static inline void
 _nmtst_assert_ip4_address (const char *file, int line, in_addr_t addr, const char *str_expected)
 {
@@ -1289,7 +1370,7 @@ nmtst_file_unlink_if_exists (const char *name)
 	if (unlink (name) != 0) {
 		errsv = errno;
 		if (errsv != ENOENT)
-			g_error ("nmtst_file_unlink_if_exists(%s): failed with %s", name, strerror (errsv));
+			g_error ("nmtst_file_unlink_if_exists(%s): failed with %s", name, nm_strerror_native (errsv));
 	}
 }
 
@@ -1302,7 +1383,7 @@ nmtst_file_unlink (const char *name)
 
 	if (unlink (name) != 0) {
 		errsv = errno;
-		g_error ("nmtst_file_unlink(%s): failed with %s", name, strerror (errsv));
+		g_error ("nmtst_file_unlink(%s): failed with %s", name, nm_strerror_native (errsv));
 	}
 }
 
@@ -1895,8 +1976,8 @@ nmtst_assert_hwaddr_equals (gconstpointer hwaddr1, gssize hwaddr1_len, const cha
 static inline NMConnection *
 nmtst_create_connection_from_keyfile (const char *keyfile_str, const char *full_filename)
 {
-	GKeyFile *keyfile;
-	GError *error = NULL;
+	gs_unref_keyfile GKeyFile *keyfile = NULL;
+	gs_free_error GError *error = NULL;
 	gboolean success;
 	NMConnection *con;
 	gs_free char *filename = g_path_get_basename (full_filename);
@@ -1907,14 +1988,10 @@ nmtst_create_connection_from_keyfile (const char *keyfile_str, const char *full_
 
 	keyfile =  g_key_file_new ();
 	success = g_key_file_load_from_data (keyfile, keyfile_str, strlen (keyfile_str), G_KEY_FILE_NONE, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	nmtst_assert_success (success, error);
 
 	con = nm_keyfile_read (keyfile, base_dir, NULL, NULL, &error);
-	g_assert_no_error (error);
-	g_assert (NM_IS_CONNECTION (con));
-
-	g_key_file_unref (keyfile);
+	nmtst_assert_success (NM_IS_CONNECTION (con), error);
 
 	nm_keyfile_read_ensure_id (con, filename);
 	nm_keyfile_read_ensure_uuid (con, full_filename);
@@ -2070,4 +2147,50 @@ typedef enum {
 
 #endif /* __NM_CONNECTION_H__ */
 
+/*****************************************************************************/
+
+static inline void
+nmtst_keyfile_assert_data (GKeyFile *kf, const char *data, gssize data_len)
+{
+	gs_unref_keyfile GKeyFile *kf2 = NULL;
+	gs_free_error GError *error = NULL;
+	gs_free char *d1 = NULL;
+	gs_free char *d2 = NULL;
+	gboolean success;
+	gsize d1_len;
+	gsize d2_len;
+
+	g_assert (kf);
+	g_assert (data || data_len == 0);
+	g_assert (data_len >= -1);
+
+	d1 = g_key_file_to_data (kf, &d1_len, &error);
+	nmtst_assert_success (d1, error);
+
+	if (data_len == -1) {
+		g_assert_cmpint (strlen (d1), ==, d1_len);
+		data_len = strlen (data);
+		g_assert_cmpstr (d1, ==, data);
+	}
+
+	g_assert_cmpmem (d1, d1_len, data, (gsize) data_len);
+
+	/* also check that we can re-generate the same keyfile from the data. */
+
+	kf2 = g_key_file_new ();
+	success = g_key_file_load_from_data (kf2,
+	                                     d1,
+	                                     d1_len,
+	                                     G_KEY_FILE_NONE,
+	                                     &error);
+	nmtst_assert_success (success, error);
+
+	d2 = g_key_file_to_data (kf2, &d2_len, &error);
+	nmtst_assert_success (d2, error);
+
+	g_assert_cmpmem (d2, d2_len, d1, d1_len);
+}
+
+/*****************************************************************************/
+
 #endif /* __NM_TEST_UTILS_H__ */
diff --git a/shared/nm-utils/nm-time-utils.c b/shared/nm-utils/nm-time-utils.c
new file mode 100644
index 00000000..ae526c34
--- /dev/null
+++ b/shared/nm-utils/nm-time-utils.c
@@ -0,0 +1,273 @@
+/* NetworkManager -- Network link manager
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-time-utils.h"
+
+/*****************************************************************************/
+
+typedef struct {
+	/* the offset to the native clock, in seconds. */
+	gint64 offset_sec;
+	clockid_t clk_id;
+} GlobalState;
+
+static const GlobalState *volatile p_global_state;
+
+static const GlobalState *
+_t_init_global_state (void)
+{
+	static GlobalState global_state = { };
+	static gsize init_once = 0;
+	const GlobalState *p;
+	clockid_t clk_id;
+	struct timespec tp;
+	gint64 offset_sec;
+	int r;
+
+	clk_id = CLOCK_BOOTTIME;
+	r = clock_gettime (clk_id, &tp);
+	if (r == -1 && errno == EINVAL) {
+		clk_id = CLOCK_MONOTONIC;
+		r = clock_gettime (clk_id, &tp);
+	}
+
+	/* The only failure we tolerate is that CLOCK_BOOTTIME is not supported.
+	 * Other than that, we rely on kernel to not fail on this. */
+	g_assert (r == 0);
+	g_assert (tp.tv_nsec >= 0 && tp.tv_nsec < NM_UTILS_NS_PER_SECOND);
+
+	/* Calculate an offset for the time stamp.
+	 *
+	 * We always want positive values, because then we can initialize
+	 * a timestamp with 0 and be sure, that it will be less then any
+	 * value nm_utils_get_monotonic_timestamp_*() might return.
+	 * For this to be true also for nm_utils_get_monotonic_timestamp_s() at
+	 * early boot, we have to shift the timestamp to start counting at
+	 * least from 1 second onward.
+	 *
+	 * Another advantage of shifting is, that this way we make use of the whole 31 bit
+	 * range of signed int, before the time stamp for nm_utils_get_monotonic_timestamp_s()
+	 * wraps (~68 years).
+	 **/
+	offset_sec = (- ((gint64) tp.tv_sec)) + 1;
+
+	if (!g_once_init_enter (&init_once)) {
+		/* there was a race. We expect the pointer to be fully initialized now. */
+		p = g_atomic_pointer_get (&p_global_state);
+		g_assert (p);
+		return p;
+	}
+
+	global_state.offset_sec = offset_sec;
+	global_state.clk_id = clk_id;
+	p = &global_state;
+	g_atomic_pointer_set (&p_global_state, p);
+	g_once_init_leave (&init_once, 1);
+
+	_nm_utils_monotonic_timestamp_initialized (&tp,
+	                                           p->offset_sec,
+	                                           p->clk_id == CLOCK_BOOTTIME);
+
+	return p;
+}
+
+#define _t_get_global_state() \
+	({ \
+		const GlobalState *_p; \
+		\
+		_p = g_atomic_pointer_get (&p_global_state); \
+		(G_LIKELY (_p) ? _p : _t_init_global_state ()); \
+	})
+
+#define _t_clock_gettime_eval(p, tp) \
+	({ \
+		struct timespec *const _tp = (tp); \
+		const GlobalState *const _p2 = (p); \
+		int _r; \
+		\
+		nm_assert (_tp); \
+		\
+		_r = clock_gettime (_p2->clk_id, _tp); \
+		\
+		nm_assert (_r == 0); \
+		nm_assert (_tp->tv_nsec >= 0 && _tp->tv_nsec < NM_UTILS_NS_PER_SECOND); \
+		\
+		_p2; \
+	})
+
+#define _t_clock_gettime(tp) \
+	_t_clock_gettime_eval (_t_get_global_state (), tp);
+
+/*****************************************************************************/
+
+/**
+ * nm_utils_get_monotonic_timestamp_ns:
+ *
+ * Returns: a monotonically increasing time stamp in nanoseconds,
+ * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME.
+ *
+ * The returned value will start counting at an undefined point
+ * in the past and will always be positive.
+ *
+ * All the nm_utils_get_monotonic_timestamp_*s functions return the same
+ * timestamp but in different scales (nsec, usec, msec, sec).
+ **/
+gint64
+nm_utils_get_monotonic_timestamp_ns (void)
+{
+	const GlobalState *p;
+	struct timespec tp;
+
+	p = _t_clock_gettime (&tp);
+
+	/* Although the result will always be positive, we return a signed
+	 * integer, which makes it easier to calculate time differences (when
+	 * you want to subtract signed values).
+	 **/
+	return (((gint64) tp.tv_sec) + p->offset_sec) * NM_UTILS_NS_PER_SECOND +
+	       tp.tv_nsec;
+}
+
+/**
+ * nm_utils_get_monotonic_timestamp_us:
+ *
+ * Returns: a monotonically increasing time stamp in microseconds,
+ * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME.
+ *
+ * The returned value will start counting at an undefined point
+ * in the past and will always be positive.
+ *
+ * All the nm_utils_get_monotonic_timestamp_*s functions return the same
+ * timestamp but in different scales (nsec, usec, msec, sec).
+ **/
+gint64
+nm_utils_get_monotonic_timestamp_us (void)
+{
+	const GlobalState *p;
+	struct timespec tp;
+
+	p = _t_clock_gettime (&tp);
+
+	/* Although the result will always be positive, we return a signed
+	 * integer, which makes it easier to calculate time differences (when
+	 * you want to subtract signed values).
+	 **/
+	return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) G_USEC_PER_SEC) +
+	       (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/G_USEC_PER_SEC));
+}
+
+/**
+ * nm_utils_get_monotonic_timestamp_ms:
+ *
+ * Returns: a monotonically increasing time stamp in milliseconds,
+ * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME.
+ *
+ * The returned value will start counting at an undefined point
+ * in the past and will always be positive.
+ *
+ * All the nm_utils_get_monotonic_timestamp_*s functions return the same
+ * timestamp but in different scales (nsec, usec, msec, sec).
+ **/
+gint64
+nm_utils_get_monotonic_timestamp_ms (void)
+{
+	const GlobalState *p;
+	struct timespec tp;
+
+	p = _t_clock_gettime (&tp);
+
+	/* Although the result will always be positive, we return a signed
+	 * integer, which makes it easier to calculate time differences (when
+	 * you want to subtract signed values).
+	 **/
+	return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) 1000) +
+	       (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000));
+}
+
+/**
+ * nm_utils_get_monotonic_timestamp_s:
+ *
+ * Returns: nm_utils_get_monotonic_timestamp_ms() in seconds (throwing
+ * away sub second parts). The returned value will always be positive.
+ *
+ * This value wraps after roughly 68 years which should be fine for any
+ * practical purpose.
+ *
+ * All the nm_utils_get_monotonic_timestamp_*s functions return the same
+ * timestamp but in different scales (nsec, usec, msec, sec).
+ **/
+gint32
+nm_utils_get_monotonic_timestamp_s (void)
+{
+	const GlobalState *p;
+	struct timespec tp;
+
+	p = _t_clock_gettime (&tp);
+
+	return (((gint64) tp.tv_sec) + p->offset_sec);
+}
+
+/**
+ * nm_utils_monotonic_timestamp_as_boottime:
+ * @timestamp: the monotonic-timestamp that should be converted into CLOCK_BOOTTIME.
+ * @timestamp_ns_per_tick: How many nano seconds make one unit of @timestamp? E.g. if
+ * @timestamp is in unit seconds, pass %NM_UTILS_NS_PER_SECOND; @timestamp in nano
+ * seconds, pass 1; @timestamp in milli seconds, pass %NM_UTILS_NS_PER_SECOND/1000; etc.
+ *
+ * Returns: the monotonic-timestamp as CLOCK_BOOTTIME, as returned by clock_gettime().
+ * The unit is the same as the passed in @timestamp basd on @timestamp_ns_per_tick.
+ * E.g. if you passed @timestamp in as seconds, it will return boottime in seconds.
+ * If @timestamp is a non-positive, it returns -1. Note that a (valid) monotonic-timestamp
+ * is always positive.
+ *
+ * On older kernels that don't support CLOCK_BOOTTIME, the returned time is instead CLOCK_MONOTONIC.
+ **/
+gint64
+nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_per_tick)
+{
+	const GlobalState *p;
+	gint64 offset;
+
+	/* only support ns-per-tick being a multiple of 10. */
+	g_return_val_if_fail (timestamp_ns_per_tick == 1
+	                      || (timestamp_ns_per_tick > 0 &&
+	                          timestamp_ns_per_tick <= NM_UTILS_NS_PER_SECOND &&
+	                          timestamp_ns_per_tick % 10 == 0),
+	                      -1);
+
+	/* Check that the timestamp is in a valid range. */
+	g_return_val_if_fail (timestamp >= 0, -1);
+
+	/* if the caller didn't yet ever fetch a monotonic-timestamp, he cannot pass any meaningful
+	 * value (because he has no idea what these timestamps would be). That would be a bug. */
+	nm_assert (g_atomic_pointer_get (&p_global_state));
+
+	p = _t_get_global_state ();
+
+	/* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */
+	offset = p->offset_sec * (NM_UTILS_NS_PER_SECOND / timestamp_ns_per_tick);
+
+	/* check for overflow. */
+	g_return_val_if_fail (offset > 0 || timestamp < G_MAXINT64 + offset, G_MAXINT64);
+
+	return timestamp - offset;
+}
diff --git a/shared/nm-utils/nm-time-utils.h b/shared/nm-utils/nm-time-utils.h
new file mode 100644
index 00000000..7e4f4f25
--- /dev/null
+++ b/shared/nm-utils/nm-time-utils.h
@@ -0,0 +1,45 @@
+/* NetworkManager -- Network link manager
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_TIME_UTILS_H__
+#define __NM_TIME_UTILS_H__
+
+gint64 nm_utils_get_monotonic_timestamp_ns (void);
+gint64 nm_utils_get_monotonic_timestamp_us (void);
+gint64 nm_utils_get_monotonic_timestamp_ms (void);
+gint32 nm_utils_get_monotonic_timestamp_s (void);
+gint64 nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ticks_per_ns);
+
+static inline gint64
+nm_utils_get_monotonic_timestamp_ns_cached (gint64 *cache_now)
+{
+	return    (*cache_now)
+	       ?: (*cache_now = nm_utils_get_monotonic_timestamp_ns ());
+}
+
+struct timespec;
+
+/* this function must be implemented to handle the notification when
+ * the first monotonic-timestamp is fetched. */
+extern void _nm_utils_monotonic_timestamp_initialized (const struct timespec *tp,
+                                                       gint64 offset_sec,
+                                                       gboolean is_boottime);
+
+#endif /* __NM_TIME_UTILS_H__ */
diff --git a/shared/nm-utils/nm-vpn-plugin-utils.c b/shared/nm-utils/nm-vpn-plugin-utils.c
index 772aa39a..353a2817 100644
--- a/shared/nm-utils/nm-vpn-plugin-utils.c
+++ b/shared/nm-utils/nm-vpn-plugin-utils.c
@@ -16,7 +16,7 @@
  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  * Boston, MA 02110-1301 USA.
  *
- * Copyright 2016 Red Hat, Inc.
+ * Copyright 2016,2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -44,14 +44,37 @@ nm_vpn_plugin_utils_load_editor (const char *module_name,
 		char *factory_name;
 	} cached = { 0 };
 	NMVpnEditor *editor;
+	gs_free char *module_path = NULL;
+	gs_free char *dirname = NULL;
+	Dl_info plugin_info;
 
-	g_return_val_if_fail (module_name && g_path_is_absolute (module_name), NULL);
+	g_return_val_if_fail (module_name, NULL);
 	g_return_val_if_fail (factory_name && factory_name[0], NULL);
 	g_return_val_if_fail (editor_factory, NULL);
 	g_return_val_if_fail (NM_IS_VPN_EDITOR_PLUGIN (editor_plugin), NULL);
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
 	g_return_val_if_fail (!error || !*error, NULL);
 
+	if (!g_path_is_absolute (module_name)) {
+		/*
+		 * Load an editor from the same directory this plugin is in.
+		 * Ideally, we'd get our .so name from the NMVpnEditorPlugin if it
+		 * would just have a property with it...
+		 */
+		if (!dladdr(nm_vpn_plugin_utils_load_editor, &plugin_info)) {
+			/* Really a "can not happen" scenario. */
+			g_set_error (error,
+			             NM_VPN_PLUGIN_ERROR,
+			             NM_VPN_PLUGIN_ERROR_FAILED,
+			             _("unable to get editor plugin name: %s"), dlerror ());
+		}
+
+		dirname = g_path_get_dirname (plugin_info.dli_fname);
+		module_path = g_build_filename (dirname, module_name, NULL);
+	} else {
+		module_path = g_strdup (module_name);
+	}
+
 	/* we really expect this function to be called with unchanging @module_name
 	 * and @factory_name. And we only want to load the module once, hence it would
 	 * be more complicated to accept changing @module_name/@factory_name arguments.
@@ -71,18 +94,18 @@ nm_vpn_plugin_utils_load_editor (const char *module_name,
 		gpointer factory;
 		void *dl_module;
 
-		dl_module = dlopen (module_name, RTLD_LAZY | RTLD_LOCAL);
+		dl_module = dlopen (module_path, RTLD_LAZY | RTLD_LOCAL);
 		if (!dl_module) {
-			if (!g_file_test (module_name, G_FILE_TEST_EXISTS)) {
+			if (!g_file_test (module_path, G_FILE_TEST_EXISTS)) {
 				g_set_error (error,
 				             G_FILE_ERROR,
 				             G_FILE_ERROR_NOENT,
-				             _("missing plugin file \"%s\""), module_name);
+				             _("missing plugin file \"%s\""), module_path);
 				return NULL;
 			}
 			g_set_error (error,
-			             NM_CONNECTION_ERROR,
-			             NM_CONNECTION_ERROR_FAILED,
+			             NM_VPN_PLUGIN_ERROR,
+			             NM_VPN_PLUGIN_ERROR_FAILED,
 			             _("cannot load editor plugin: %s"), dlerror ());
 			return NULL;
 		}
@@ -90,8 +113,8 @@ nm_vpn_plugin_utils_load_editor (const char *module_name,
 		factory = dlsym (dl_module, factory_name);
 		if (!factory) {
 			g_set_error (error,
-			             NM_CONNECTION_ERROR,
-			             NM_CONNECTION_ERROR_FAILED,
+			             NM_VPN_PLUGIN_ERROR,
+			             NM_VPN_PLUGIN_ERROR_FAILED,
 			             _("cannot load factory %s from plugin: %s"),
 			             factory_name, dlerror ());
 			dlclose (dl_module);
@@ -116,8 +139,8 @@ nm_vpn_plugin_utils_load_editor (const char *module_name,
 	if (!editor) {
 		if (error && !*error ) {
 			g_set_error_literal (error,
-			                     NM_CONNECTION_ERROR,
-			                     NM_CONNECTION_ERROR_FAILED,
+			                     NM_VPN_PLUGIN_ERROR,
+			                     NM_VPN_PLUGIN_ERROR_FAILED,
 			                     _("unknown error creating editor instance"));
 			g_return_val_if_reached (NULL);
 		}
@@ -127,4 +150,3 @@ nm_vpn_plugin_utils_load_editor (const char *module_name,
 	g_return_val_if_fail (NM_IS_VPN_EDITOR (editor), NULL);
 	return editor;
 }
-
diff --git a/shared/nm-utils/tests/test-shared-general.c b/shared/nm-utils/tests/test-shared-general.c
new file mode 100644
index 00000000..d53b21d9
--- /dev/null
+++ b/shared/nm-utils/tests/test-shared-general.c
@@ -0,0 +1,267 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#define NM_TEST_UTILS_NO_LIBNM 1
+
+#include "nm-default.h"
+
+#include "nm-utils/nm-time-utils.h"
+#include "nm-utils/nm-random-utils.h"
+#include "nm-utils/unaligned.h"
+
+#include "nm-utils/nm-test-utils.h"
+
+/*****************************************************************************/
+
+static int _monotonic_timestamp_initialized;
+
+void
+_nm_utils_monotonic_timestamp_initialized (const struct timespec *tp,
+                                           gint64 offset_sec,
+                                           gboolean is_boottime)
+{
+	g_assert (!_monotonic_timestamp_initialized);
+	_monotonic_timestamp_initialized = 1;
+}
+
+/*****************************************************************************/
+
+static void
+test_monotonic_timestamp (void)
+{
+	g_assert (nm_utils_get_monotonic_timestamp_s () > 0);
+	g_assert (_monotonic_timestamp_initialized);
+}
+
+/*****************************************************************************/
+
+static void
+test_nmhash (void)
+{
+	int rnd;
+
+	nm_utils_random_bytes (&rnd, sizeof (rnd));
+
+	g_assert (nm_hash_val (555, 4) != 0);
+}
+
+/*****************************************************************************/
+
+static const char *
+_make_strv_foo (void)
+{
+	return "foo";
+}
+
+static const char *const*const _tst_make_strv_1 = NM_MAKE_STRV ("1", "2");
+
+static void
+test_make_strv (void)
+{
+	const char *const*v1a = NM_MAKE_STRV ("a");
+	const char *const*v1b = NM_MAKE_STRV ("a", );
+	const char *const*v2a = NM_MAKE_STRV ("a", "b");
+	const char *const*v2b = NM_MAKE_STRV ("a", "b", );
+	const char *const v3[] = { "a", "b", };
+	const char *const*v4b = NM_MAKE_STRV ("a", _make_strv_foo (), );
+
+	g_assert (NM_PTRARRAY_LEN (v1a) == 1);
+	g_assert (NM_PTRARRAY_LEN (v1b) == 1);
+	g_assert (NM_PTRARRAY_LEN (v2a) == 2);
+	g_assert (NM_PTRARRAY_LEN (v2b) == 2);
+
+	g_assert (NM_PTRARRAY_LEN (_tst_make_strv_1) == 2);
+	g_assert_cmpstr (_tst_make_strv_1[0], ==, "1");
+	g_assert_cmpstr (_tst_make_strv_1[1], ==, "2");
+	/* writing the static read-only variable leads to crash .*/
+	//((char **) _tst_make_strv_1)[0] = NULL;
+	//((char **) _tst_make_strv_1)[2] = "c";
+
+	G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (v3) == 2);
+
+	g_assert (NM_PTRARRAY_LEN (v4b) == 2);
+
+	G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (NM_MAKE_STRV ("a", "b"  )) == 3);
+	G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (NM_MAKE_STRV ("a", "b", )) == 3);
+
+	nm_strquote_a (300, "");
+}
+
+/*****************************************************************************/
+
+typedef enum {
+	TEST_NM_STRDUP_ENUM_m1 = -1,
+	TEST_NM_STRDUP_ENUM_3  = 3,
+} TestNMStrdupIntEnum;
+
+static void
+test_nm_strdup_int (void)
+{
+#define _NM_STRDUP_INT_TEST(num, str) \
+	G_STMT_START { \
+		gs_free char *_s1 = NULL; \
+		\
+		_s1 = nm_strdup_int ((num)); \
+		\
+		g_assert (_s1); \
+		g_assert_cmpstr (_s1, ==, str); \
+	} G_STMT_END
+
+#define _NM_STRDUP_INT_TEST_TYPED(type, num) \
+	G_STMT_START { \
+		type _num = ((type) num); \
+		\
+		_NM_STRDUP_INT_TEST (_num, G_STRINGIFY (num)); \
+	} G_STMT_END
+
+	_NM_STRDUP_INT_TEST_TYPED (char, 0);
+	_NM_STRDUP_INT_TEST_TYPED (char, 1);
+	_NM_STRDUP_INT_TEST_TYPED (guint8, 0);
+	_NM_STRDUP_INT_TEST_TYPED (gint8, 25);
+	_NM_STRDUP_INT_TEST_TYPED (char, 47);
+	_NM_STRDUP_INT_TEST_TYPED (short, 47);
+	_NM_STRDUP_INT_TEST_TYPED (int, 47);
+	_NM_STRDUP_INT_TEST_TYPED (long, 47);
+	_NM_STRDUP_INT_TEST_TYPED (unsigned char, 47);
+	_NM_STRDUP_INT_TEST_TYPED (unsigned short, 47);
+	_NM_STRDUP_INT_TEST_TYPED (unsigned, 47);
+	_NM_STRDUP_INT_TEST_TYPED (unsigned long, 47);
+	_NM_STRDUP_INT_TEST_TYPED (gint64, 9223372036854775807);
+	_NM_STRDUP_INT_TEST_TYPED (gint64, -9223372036854775807);
+	_NM_STRDUP_INT_TEST_TYPED (guint64, 0);
+	_NM_STRDUP_INT_TEST_TYPED (guint64, 9223372036854775807);
+
+	_NM_STRDUP_INT_TEST (TEST_NM_STRDUP_ENUM_m1, "-1");
+	_NM_STRDUP_INT_TEST (TEST_NM_STRDUP_ENUM_3,  "3");
+}
+
+/*****************************************************************************/
+
+static void
+test_nm_strndup_a (void)
+{
+	int run;
+
+	for (run = 0; run < 20; run++) {
+		gs_free char *input = NULL;
+		char ch;
+		gsize i, l;
+
+		input = g_strnfill (nmtst_get_rand_int () % 20, 'x');
+
+		for (i = 0; input[i]; i++) {
+			while ((ch = ((char) nmtst_get_rand_int ())) == '\0') {
+				/* repeat. */
+			}
+			input[i] = ch;
+		}
+
+		{
+			gs_free char *dup_free = NULL;
+			const char *dup;
+
+			l = strlen (input) + 1;
+			dup = nm_strndup_a (10, input, l - 1, &dup_free);
+			g_assert_cmpstr (dup, ==, input);
+			if (strlen (dup) < 10)
+				g_assert (!dup_free);
+			else
+				g_assert (dup == dup_free);
+		}
+
+		{
+			gs_free char *dup_free = NULL;
+			const char *dup;
+
+			l = nmtst_get_rand_int () % 23;
+			dup = nm_strndup_a (10, input, l, &dup_free);
+			g_assert (strncmp (dup, input, l) == 0);
+			g_assert (strlen (dup) <= l);
+			if (l < 10)
+				g_assert (!dup_free);
+			else
+				g_assert (dup == dup_free);
+			if (strlen (input) < l)
+				g_assert (nm_utils_memeqzero (&dup[strlen (input)], l - strlen (input)));
+		}
+	}
+}
+
+/*****************************************************************************/
+
+static void
+test_nm_ip4_addr_is_localhost (void)
+{
+	g_assert ( nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("127.0.0.0")));
+	g_assert ( nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("127.0.0.1")));
+	g_assert ( nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("127.5.0.1")));
+	g_assert (!nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("126.5.0.1")));
+	g_assert (!nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("128.5.0.1")));
+	g_assert (!nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("129.5.0.1")));
+}
+
+/*****************************************************************************/
+
+static void
+test_unaligned (void)
+{
+	int shift;
+
+	for (shift = 0; shift <= 32; shift++) {
+		guint8 buf[100] = { };
+		guint8 val = 0;
+
+		while (val == 0)
+			val = nmtst_get_rand_int () % 256;
+
+		buf[shift] = val;
+
+		g_assert_cmpint (unaligned_read_le64 (&buf[shift]), ==, (guint64) val);
+		g_assert_cmpint (unaligned_read_be64 (&buf[shift]), ==, ((guint64) val) << 56);
+		g_assert_cmpint (unaligned_read_ne64 (&buf[shift]), !=, 0);
+
+		g_assert_cmpint (unaligned_read_le32 (&buf[shift]), ==, (guint32) val);
+		g_assert_cmpint (unaligned_read_be32 (&buf[shift]), ==, ((guint32) val) << 24);
+		g_assert_cmpint (unaligned_read_ne32 (&buf[shift]), !=, 0);
+
+		g_assert_cmpint (unaligned_read_le16 (&buf[shift]), ==, (guint16) val);
+		g_assert_cmpint (unaligned_read_be16 (&buf[shift]), ==, ((guint16) val) << 8);
+		g_assert_cmpint (unaligned_read_ne16 (&buf[shift]), !=, 0);
+	}
+}
+
+/*****************************************************************************/
+
+NMTST_DEFINE ();
+
+int main (int argc, char **argv)
+{
+	nmtst_init (&argc, &argv, TRUE);
+
+	g_test_add_func ("/general/test_monotonic_timestamp", test_monotonic_timestamp);
+	g_test_add_func ("/general/test_nmhash", test_nmhash);
+	g_test_add_func ("/general/test_nm_make_strv", test_make_strv);
+	g_test_add_func ("/general/test_nm_strdup_int", test_nm_strdup_int);
+	g_test_add_func ("/general/test_nm_strndup_a", test_nm_strndup_a);
+	g_test_add_func ("/general/test_nm_ip4_addr_is_localhost", test_nm_ip4_addr_is_localhost);
+	g_test_add_func ("/general/test_unaligned", test_unaligned);
+
+	return g_test_run ();
+}
+
diff --git a/shared/nm-utils/unaligned.h b/shared/nm-utils/unaligned.h
index e62188d1..00c17f87 100644
--- a/shared/nm-utils/unaligned.h
+++ b/shared/nm-utils/unaligned.h
@@ -7,37 +7,37 @@
 /* BE */
 
 static inline uint16_t unaligned_read_be16(const void *_u) {
-        const struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u;
+        const struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u;
 
         return be16toh(u->x);
 }
 
 static inline uint32_t unaligned_read_be32(const void *_u) {
-        const struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u;
+        const struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u;
 
         return be32toh(u->x);
 }
 
 static inline uint64_t unaligned_read_be64(const void *_u) {
-        const struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u;
+        const struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u;
 
         return be64toh(u->x);
 }
 
 static inline void unaligned_write_be16(void *_u, uint16_t a) {
-        struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u;
+        struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u;
 
         u->x = be16toh(a);
 }
 
 static inline void unaligned_write_be32(void *_u, uint32_t a) {
-        struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u;
+        struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u;
 
         u->x = be32toh(a);
 }
 
 static inline void unaligned_write_be64(void *_u, uint64_t a) {
-        struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u;
+        struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u;
 
         u->x = be64toh(a);
 }
@@ -45,37 +45,37 @@ static inline void unaligned_write_be64(void *_u, uint64_t a) {
 /* LE */
 
 static inline uint16_t unaligned_read_le16(const void *_u) {
-        const struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u;
+        const struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u;
 
         return le16toh(u->x);
 }
 
 static inline uint32_t unaligned_read_le32(const void *_u) {
-        const struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u;
+        const struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u;
 
         return le32toh(u->x);
 }
 
 static inline uint64_t unaligned_read_le64(const void *_u) {
-        const struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u;
+        const struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u;
 
         return le64toh(u->x);
 }
 
 static inline void unaligned_write_le16(void *_u, uint16_t a) {
-        struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u;
+        struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u;
 
         u->x = le16toh(a);
 }
 
 static inline void unaligned_write_le32(void *_u, uint32_t a) {
-        struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u;
+        struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u;
 
         u->x = le32toh(a);
 }
 
 static inline void unaligned_write_le64(void *_u, uint64_t a) {
-        struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u;
+        struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u;
 
         u->x = le64toh(a);
 }
diff --git a/shared/nm-version-macros.h b/shared/nm-version-macros.h
index 7dc2760e..3906e7c8 100644
--- a/shared/nm-version-macros.h
+++ b/shared/nm-version-macros.h
@@ -37,7 +37,7 @@
  * Evaluates to the minor version number of NetworkManager which this source
  * is compiled against.
  */
-#define NM_MINOR_VERSION (14)
+#define NM_MINOR_VERSION (16)
 
 /**
  * NM_MICRO_VERSION:
@@ -45,7 +45,7 @@
  * Evaluates to the micro version number of NetworkManager which this source
  * compiled against.
  */
-#define NM_MICRO_VERSION (6)
+#define NM_MICRO_VERSION (0)
 
 /**
  * NM_CHECK_VERSION:
@@ -74,9 +74,7 @@
 #define NM_VERSION_1_10   (NM_ENCODE_VERSION (1, 10, 0))
 #define NM_VERSION_1_12   (NM_ENCODE_VERSION (1, 12, 0))
 #define NM_VERSION_1_14   (NM_ENCODE_VERSION (1, 14, 0))
-#define NM_VERSION_1_14_2 (NM_ENCODE_VERSION (1, 14, 2))
-#define NM_VERSION_1_14_4 (NM_ENCODE_VERSION (1, 14, 4))
-#define NM_VERSION_1_14_6 (NM_ENCODE_VERSION (1, 14, 6))
+#define NM_VERSION_1_16   (NM_ENCODE_VERSION (1, 16, 0))
 
 /* For releases, NM_API_VERSION is equal to NM_VERSION.
  *
diff --git a/shared/nm-version-macros.h.in b/shared/nm-version-macros.h.in
index dead985a..22af1428 100644
--- a/shared/nm-version-macros.h.in
+++ b/shared/nm-version-macros.h.in
@@ -74,9 +74,7 @@
 #define NM_VERSION_1_10   (NM_ENCODE_VERSION (1, 10, 0))
 #define NM_VERSION_1_12   (NM_ENCODE_VERSION (1, 12, 0))
 #define NM_VERSION_1_14   (NM_ENCODE_VERSION (1, 14, 0))
-#define NM_VERSION_1_14_2 (NM_ENCODE_VERSION (1, 14, 2))
-#define NM_VERSION_1_14_4 (NM_ENCODE_VERSION (1, 14, 4))
-#define NM_VERSION_1_14_6 (NM_ENCODE_VERSION (1, 14, 6))
+#define NM_VERSION_1_16   (NM_ENCODE_VERSION (1, 16, 0))
 
 /* For releases, NM_API_VERSION is equal to NM_VERSION.
  *
diff --git a/shared/systemd/nm-logging-stub.c b/shared/systemd/nm-logging-stub.c
new file mode 100644
index 00000000..5be69b4b
--- /dev/null
+++ b/shared/systemd/nm-logging-stub.c
@@ -0,0 +1,47 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-utils/nm-logging-fwd.h"
+
+/*****************************************************************************/
+
+gboolean
+_nm_log_enabled_impl (gboolean mt_require_locking,
+                      NMLogLevel level,
+                      NMLogDomain domain)
+{
+	return FALSE;
+}
+
+void
+_nm_log_impl (const char *file,
+              guint line,
+              const char *func,
+              gboolean mt_require_locking,
+              NMLogLevel level,
+              NMLogDomain domain,
+              int error,
+              const char *ifname,
+              const char *con_uuid,
+              const char *fmt,
+              ...)
+{
+}
diff --git a/shared/systemd/nm-sd-utils-shared.c b/shared/systemd/nm-sd-utils-shared.c
new file mode 100644
index 00000000..0e89fbb7
--- /dev/null
+++ b/shared/systemd/nm-sd-utils-shared.c
@@ -0,0 +1,82 @@
+/* This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-sd-utils-shared.h"
+
+#include "nm-sd-adapt-shared.h"
+
+#include "path-util.h"
+#include "hexdecoct.h"
+
+/*****************************************************************************/
+
+gboolean
+nm_sd_utils_path_equal (const char *a, const char *b)
+{
+	return path_equal (a, b);
+}
+
+char *
+nm_sd_utils_path_simplify (char *path, gboolean kill_dots)
+{
+	return path_simplify (path, kill_dots);
+}
+
+const char *
+nm_sd_utils_path_startswith (const char *path, const char *prefix)
+{
+	return path_startswith (path, prefix);
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_sd_utils_unbase64char (char ch, gboolean accept_padding_equal)
+{
+	if (   ch == '='
+	    && accept_padding_equal)
+		return G_MAXINT;
+	return unbase64char (ch);
+}
+
+/**
+ * nm_sd_utils_unbase64mem:
+ * @p: a valid base64 string. Whitespace is ignored, but invalid encodings
+ *   will cause the function to fail.
+ * @l: the length of @p. @p is not treated as NUL terminated string but
+ *   merely as a buffer of ascii characters.
+ * @mem: (transfer full): the decoded buffer on success.
+ * @len: the length of @mem on success.
+ *
+ * glib provides g_base64_decode(), but that does not report any errors
+ * from invalid encodings. Expose systemd's implementation which does
+ * reject invalid inputs.
+ *
+ * Returns: a non-negative code on success. Invalid encoding let the
+ *   function fail.
+ */
+int
+nm_sd_utils_unbase64mem (const char *p,
+                         size_t l,
+                         guint8 **mem,
+                         size_t *len)
+{
+	return unbase64mem (p, l, (void **) mem, len);
+}
diff --git a/shared/systemd/nm-sd-utils-shared.h b/shared/systemd/nm-sd-utils-shared.h
new file mode 100644
index 00000000..eddf0c28
--- /dev/null
+++ b/shared/systemd/nm-sd-utils-shared.h
@@ -0,0 +1,38 @@
+/* This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_SD_UTILS_SHARED_H__
+#define __NM_SD_UTILS_SHARED_H__
+
+/*****************************************************************************/
+
+gboolean nm_sd_utils_path_equal (const char *a, const char *b);
+
+char *nm_sd_utils_path_simplify (char *path, gboolean kill_dots);
+
+const char *nm_sd_utils_path_startswith (const char *path, const char *prefix);
+
+/*****************************************************************************/
+
+int nm_sd_utils_unbase64char (char ch, gboolean accept_padding_equal);
+
+int nm_sd_utils_unbase64mem (const char *p, size_t l, guint8 **mem, size_t *len);
+
+/*****************************************************************************/
+
+#endif /* __NM_SD_UTILS_SHARED_H__ */
diff --git a/shared/systemd/sd-adapt-shared/architecture.h b/shared/systemd/sd-adapt-shared/architecture.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/architecture.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/btrfs-util.h b/shared/systemd/sd-adapt-shared/btrfs-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/btrfs-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/build.h b/shared/systemd/sd-adapt-shared/build.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/build.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/cgroup-util.h b/shared/systemd/sd-adapt-shared/cgroup-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/cgroup-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/copy.h b/shared/systemd/sd-adapt-shared/copy.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/copy.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/def.h b/shared/systemd/sd-adapt-shared/def.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/def.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/device-nodes.h b/shared/systemd/sd-adapt-shared/device-nodes.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/device-nodes.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/dirent-util.h b/shared/systemd/sd-adapt-shared/dirent-util.h
new file mode 100644
index 00000000..7132dfcc
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/dirent-util.h
@@ -0,0 +1,5 @@
+#pragma once
+
+/* dummy header */
+
+#include "path-util.h"
diff --git a/shared/systemd/sd-adapt-shared/errno-list.h b/shared/systemd/sd-adapt-shared/errno-list.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/errno-list.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/format-util.h b/shared/systemd/sd-adapt-shared/format-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/format-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/glob-util.h b/shared/systemd/sd-adapt-shared/glob-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/glob-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/gunicode.h b/shared/systemd/sd-adapt-shared/gunicode.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/gunicode.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/ioprio.h b/shared/systemd/sd-adapt-shared/ioprio.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/ioprio.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/locale-util.h b/shared/systemd/sd-adapt-shared/locale-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/locale-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/memfd-util.h b/shared/systemd/sd-adapt-shared/memfd-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/memfd-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/missing.h b/shared/systemd/sd-adapt-shared/missing.h
new file mode 100644
index 00000000..2ee34b6a
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/missing.h
@@ -0,0 +1,6 @@
+#pragma once
+
+/* dummy header */
+
+#include "missing_fcntl.h"
+#include "missing_type.h"
diff --git a/shared/systemd/sd-adapt-shared/missing_socket.h b/shared/systemd/sd-adapt-shared/missing_socket.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/missing_socket.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/missing_syscall.h b/shared/systemd/sd-adapt-shared/missing_syscall.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/missing_syscall.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/missing_timerfd.h b/shared/systemd/sd-adapt-shared/missing_timerfd.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/missing_timerfd.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/mkdir.h b/shared/systemd/sd-adapt-shared/mkdir.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/mkdir.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h b/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h
new file mode 100644
index 00000000..b10722d7
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h
@@ -0,0 +1,139 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2014 - 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_SD_ADAPT_SHARED_H__
+#define __NM_SD_ADAPT_SHARED_H__
+
+#include "nm-default.h"
+
+#include <syslog.h>
+
+#include "nm-utils/nm-logging-fwd.h"
+
+/*****************************************************************************/
+
+/* strerror() is not thread-safe. Patch systemd-sources via a define. */
+#define strerror(errsv) nm_strerror_native (errsv)
+
+/*****************************************************************************/
+
+static inline NMLogLevel
+_slog_level_to_nm (int slevel)
+{
+	switch (LOG_PRI (slevel)) {
+	case LOG_DEBUG:   return LOGL_DEBUG;
+	case LOG_WARNING: return LOGL_WARN;
+	case LOG_CRIT:
+	case LOG_ERR:     return LOGL_ERR;
+	case LOG_INFO:
+	case LOG_NOTICE:
+	default:          return LOGL_INFO;
+	}
+}
+
+static inline int
+_nm_log_get_max_level_realm (void)
+{
+	/* inline function, to avoid coverity warning about constant expression. */
+	return LOG_DEBUG;
+}
+#define log_get_max_level_realm(realm) _nm_log_get_max_level_realm ()
+
+#define log_internal_realm(level, error, file, line, func, format, ...) \
+({ \
+	const int _nm_e = (error); \
+	const NMLogLevel _nm_l = _slog_level_to_nm ((level)); \
+	\
+	if (_nm_log_enabled_impl (!(NM_THREAD_SAFE_ON_MAIN_THREAD), _nm_l, LOGD_SYSTEMD)) { \
+		const char *_nm_location = strrchr ((""file), '/'); \
+		\
+		_nm_log_impl (_nm_location ? _nm_location + 1 : (""file), (line), (func), !(NM_THREAD_SAFE_ON_MAIN_THREAD), _nm_l, LOGD_SYSTEMD, _nm_e, NULL, NULL, ("%s"format), "libsystemd: ", ## __VA_ARGS__); \
+	} \
+	(_nm_e > 0 ? -_nm_e : _nm_e); \
+})
+
+#define log_assert_failed(text, file, line, func) \
+G_STMT_START { \
+	log_internal (LOG_CRIT, 0, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Aborting.", text, file, line, func); \
+	g_assert_not_reached (); \
+} G_STMT_END
+
+#define log_assert_failed_unreachable(text, file, line, func) \
+G_STMT_START { \
+	log_internal (LOG_CRIT, 0, file, line, func, "Code should not be reached '%s' at %s:%u, function %s(). Aborting.", text, file, line, func); \
+	g_assert_not_reached (); \
+} G_STMT_END
+
+#define log_assert_failed_return(text, file, line, func) \
+({ \
+	log_internal (LOG_DEBUG, 0, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Ignoring.", text, file, line, func); \
+	g_return_if_fail_warning (G_LOG_DOMAIN, G_STRFUNC, text); \
+	(void) 0; \
+})
+
+/*****************************************************************************/
+
+#define VALGRIND 0
+
+#define ENABLE_DEBUG_HASHMAP 0
+
+/*****************************************************************************
+ * The remainder of the header is only enabled when building the systemd code
+ * itself.
+ *****************************************************************************/
+
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD
+
+#include <sys/syscall.h>
+#include <sys/ioctl.h>
+
+static inline pid_t
+raw_getpid (void) {
+#if defined(__alpha__)
+	return (pid_t) syscall (__NR_getxpid);
+#else
+	return (pid_t) syscall (__NR_getpid);
+#endif
+}
+
+static inline pid_t _nm_gettid(void) {
+        return (pid_t) syscall(SYS_gettid);
+}
+#define gettid() _nm_gettid ()
+
+/* we build with C11 and thus <uchar.h> provides char32_t,char16_t. */
+#define HAVE_CHAR32_T 1
+#define HAVE_CHAR16_T 1
+
+#if defined(HAVE_DECL_REALLOCARRAY) && HAVE_DECL_REALLOCARRAY == 1
+#define HAVE_REALLOCARRAY 1
+#else
+#define HAVE_REALLOCARRAY 0
+#endif
+
+#if defined(HAVE_DECL_EXPLICIT_BZERO) && HAVE_DECL_EXPLICIT_BZERO == 1
+#define HAVE_EXPLICIT_BZERO 1
+#else
+#define HAVE_EXPLICIT_BZERO 0
+#endif
+
+#endif /* (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD */
+
+/*****************************************************************************/
+
+#endif /* __NM_SD_ADAPT_SHARED_H__ */
diff --git a/shared/systemd/sd-adapt-shared/procfs-util.h b/shared/systemd/sd-adapt-shared/procfs-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/procfs-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/raw-clone.h b/shared/systemd/sd-adapt-shared/raw-clone.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/raw-clone.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/rlimit-util.h b/shared/systemd/sd-adapt-shared/rlimit-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/rlimit-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/terminal-util.h b/shared/systemd/sd-adapt-shared/terminal-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/terminal-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/unaligned.h b/shared/systemd/sd-adapt-shared/unaligned.h
new file mode 100644
index 00000000..17dc0444
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/unaligned.h
@@ -0,0 +1,3 @@
+#pragma once
+
+#include "nm-utils/unaligned.h"
diff --git a/shared/systemd/sd-adapt-shared/user-util.h b/shared/systemd/sd-adapt-shared/user-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/user-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/virt.h b/shared/systemd/sd-adapt-shared/virt.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/virt.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/src/basic/alloc-util.c b/shared/systemd/src/basic/alloc-util.c
new file mode 100644
index 00000000..d23624d8
--- /dev/null
+++ b/shared/systemd/src/basic/alloc-util.c
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "macro.h"
+#include "util.h"
+
+void* memdup(const void *p, size_t l) {
+        void *ret;
+
+        assert(l == 0 || p);
+
+        ret = malloc(l ?: 1);
+        if (!ret)
+                return NULL;
+
+        memcpy(ret, p, l);
+        return ret;
+}
+
+void* memdup_suffix0(const void *p, size_t l) {
+        void *ret;
+
+        assert(l == 0 || p);
+
+        /* The same as memdup() but place a safety NUL byte after the allocated memory */
+
+        ret = malloc(l + 1);
+        if (!ret)
+                return NULL;
+
+        *((uint8_t*) mempcpy(ret, p, l)) = 0;
+        return ret;
+}
+
+void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) {
+        size_t a, newalloc;
+        void *q;
+
+        assert(p);
+        assert(allocated);
+
+        if (*allocated >= need)
+                return *p;
+
+        newalloc = MAX(need * 2, 64u / size);
+        a = newalloc * size;
+
+        /* check for overflows */
+        if (a < size * need)
+                return NULL;
+
+        q = realloc(*p, a);
+        if (!q)
+                return NULL;
+
+        *p = q;
+        *allocated = newalloc;
+        return q;
+}
+
+void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size) {
+        size_t prev;
+        uint8_t *q;
+
+        assert(p);
+        assert(allocated);
+
+        prev = *allocated;
+
+        q = greedy_realloc(p, allocated, need, size);
+        if (!q)
+                return NULL;
+
+        if (*allocated > prev)
+                memzero(q + prev * size, (*allocated - prev) * size);
+
+        return q;
+}
diff --git a/shared/systemd/src/basic/alloc-util.h b/shared/systemd/src/basic/alloc-util.h
new file mode 100644
index 00000000..893a1238
--- /dev/null
+++ b/shared/systemd/src/basic/alloc-util.h
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <alloca.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "macro.h"
+
+typedef void (*free_func_t)(void *p);
+
+/* If for some reason more than 4M are allocated on the stack, let's abort immediately. It's better than
+ * proceeding and smashing the stack limits. Note that by default RLIMIT_STACK is 8M on Linux. */
+#define ALLOCA_MAX (4U*1024U*1024U)
+
+#define new(t, n) ((t*) malloc_multiply(sizeof(t), (n)))
+
+#define new0(t, n) ((t*) calloc((n) ?: 1, sizeof(t)))
+
+#define newa(t, n)                                                      \
+        ({                                                              \
+                size_t _n_ = n;                                         \
+                assert(!size_multiply_overflow(sizeof(t), _n_));        \
+                assert(sizeof(t)*_n_ <= ALLOCA_MAX);                    \
+                (t*) alloca(sizeof(t)*_n_);                             \
+        })
+
+#define newa0(t, n)                                                     \
+        ({                                                              \
+                size_t _n_ = n;                                         \
+                assert(!size_multiply_overflow(sizeof(t), _n_));        \
+                assert(sizeof(t)*_n_ <= ALLOCA_MAX);                    \
+                (t*) alloca0(sizeof(t)*_n_);                            \
+        })
+
+#define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n)))
+
+#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, sizeof(t), (n)))
+
+#define malloc0(n) (calloc(1, (n)))
+
+static inline void *mfree(void *memory) {
+        free(memory);
+        return NULL;
+}
+
+#define free_and_replace(a, b)                  \
+        ({                                      \
+                free(a);                        \
+                (a) = (b);                      \
+                (b) = NULL;                     \
+                0;                              \
+        })
+
+void* memdup(const void *p, size_t l) _alloc_(2);
+void* memdup_suffix0(const void *p, size_t l) _alloc_(2);
+
+#define memdupa(p, l)                           \
+        ({                                      \
+                void *_q_;                      \
+                size_t _l_ = l;                 \
+                assert(_l_ <= ALLOCA_MAX);      \
+                _q_ = alloca(_l_);              \
+                memcpy(_q_, p, _l_);            \
+        })
+
+#define memdupa_suffix0(p, l)                   \
+        ({                                      \
+                void *_q_;                      \
+                size_t _l_ = l;                 \
+                assert(_l_ <= ALLOCA_MAX);      \
+                _q_ = alloca(_l_ + 1);          \
+                ((uint8_t*) _q_)[_l_] = 0;      \
+                memcpy(_q_, p, _l_);            \
+        })
+
+static inline void freep(void *p) {
+        free(*(void**) p);
+}
+
+#define _cleanup_free_ _cleanup_(freep)
+
+static inline bool size_multiply_overflow(size_t size, size_t need) {
+        return _unlikely_(need != 0 && size > (SIZE_MAX / need));
+}
+
+_malloc_  _alloc_(1, 2) static inline void *malloc_multiply(size_t size, size_t need) {
+        if (size_multiply_overflow(size, need))
+                return NULL;
+
+        return malloc(size * need ?: 1);
+}
+
+#if !HAVE_REALLOCARRAY
+_alloc_(2, 3) static inline void *reallocarray(void *p, size_t need, size_t size) {
+        if (size_multiply_overflow(size, need))
+                return NULL;
+
+        return realloc(p, size * need ?: 1);
+}
+#endif
+
+_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, size_t need) {
+        if (size_multiply_overflow(size, need))
+                return NULL;
+
+        return memdup(p, size * need);
+}
+
+_alloc_(2, 3) static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t need) {
+        if (size_multiply_overflow(size, need))
+                return NULL;
+
+        return memdup_suffix0(p, size * need);
+}
+
+void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size);
+void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size);
+
+#define GREEDY_REALLOC(array, allocated, need)                          \
+        greedy_realloc((void**) &(array), &(allocated), (need), sizeof((array)[0]))
+
+#define GREEDY_REALLOC0(array, allocated, need)                         \
+        greedy_realloc0((void**) &(array), &(allocated), (need), sizeof((array)[0]))
+
+#define alloca0(n)                                      \
+        ({                                              \
+                char *_new_;                            \
+                size_t _len_ = n;                       \
+                assert(_len_ <= ALLOCA_MAX);            \
+                _new_ = alloca(_len_);                  \
+                (void *) memset(_new_, 0, _len_);       \
+        })
+
+/* It's not clear what alignment glibc/gcc alloca() guarantee, hence provide a guaranteed safe version */
+#define alloca_align(size, align)                                       \
+        ({                                                              \
+                void *_ptr_;                                            \
+                size_t _mask_ = (align) - 1;                            \
+                size_t _size_ = size;                                   \
+                assert(_size_ <= ALLOCA_MAX);                           \
+                _ptr_ = alloca(_size_ + _mask_);                        \
+                (void*)(((uintptr_t)_ptr_ + _mask_) & ~_mask_);         \
+        })
+
+#define alloca0_align(size, align)                                      \
+        ({                                                              \
+                void *_new_;                                            \
+                size_t _xsize_ = (size);                                \
+                _new_ = alloca_align(_xsize_, (align));                 \
+                (void*)memset(_new_, 0, _xsize_);                       \
+        })
+
+/* Takes inspiration from Rusts's Option::take() method: reads and returns a pointer, but at the same time resets it to
+ * NULL. See: https://doc.rust-lang.org/std/option/enum.Option.html#method.take */
+#define TAKE_PTR(ptr)                           \
+        ({                                      \
+                typeof(ptr) _ptr_ = (ptr);      \
+                (ptr) = NULL;                   \
+                _ptr_;                          \
+        })
diff --git a/shared/systemd/src/basic/async.h b/shared/systemd/src/basic/async.h
new file mode 100644
index 00000000..31606131
--- /dev/null
+++ b/shared/systemd/src/basic/async.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+int asynchronous_job(void* (*func)(void *p), void *arg);
+
+int asynchronous_sync(pid_t *ret_pid);
+int asynchronous_close(int fd);
diff --git a/shared/systemd/src/basic/env-file.c b/shared/systemd/src/basic/env-file.c
new file mode 100644
index 00000000..4babe753
--- /dev/null
+++ b/shared/systemd/src/basic/env-file.c
@@ -0,0 +1,568 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <stdio_ext.h>
+
+#include "alloc-util.h"
+#include "env-file.h"
+#include "env-util.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "tmpfile-util.h"
+#include "utf8.h"
+
+static int parse_env_file_internal(
+                FILE *f,
+                const char *fname,
+                int (*push) (const char *filename, unsigned line,
+                             const char *key, char *value, void *userdata, int *n_pushed),
+                void *userdata,
+                int *n_pushed) {
+
+        size_t key_alloc = 0, n_key = 0, value_alloc = 0, n_value = 0, last_value_whitespace = (size_t) -1, last_key_whitespace = (size_t) -1;
+        _cleanup_free_ char *contents = NULL, *key = NULL, *value = NULL;
+        unsigned line = 1;
+        char *p;
+        int r;
+
+        enum {
+                PRE_KEY,
+                KEY,
+                PRE_VALUE,
+                VALUE,
+                VALUE_ESCAPE,
+                SINGLE_QUOTE_VALUE,
+                DOUBLE_QUOTE_VALUE,
+                DOUBLE_QUOTE_VALUE_ESCAPE,
+                COMMENT,
+                COMMENT_ESCAPE
+        } state = PRE_KEY;
+
+        if (f)
+                r = read_full_stream(f, &contents, NULL);
+        else
+                r = read_full_file(fname, &contents, NULL);
+        if (r < 0)
+                return r;
+
+        for (p = contents; *p; p++) {
+                char c = *p;
+
+                switch (state) {
+
+                case PRE_KEY:
+                        if (strchr(COMMENTS, c))
+                                state = COMMENT;
+                        else if (!strchr(WHITESPACE, c)) {
+                                state = KEY;
+                                last_key_whitespace = (size_t) -1;
+
+                                if (!GREEDY_REALLOC(key, key_alloc, n_key+2))
+                                        return -ENOMEM;
+
+                                key[n_key++] = c;
+                        }
+                        break;
+
+                case KEY:
+                        if (strchr(NEWLINE, c)) {
+                                state = PRE_KEY;
+                                line++;
+                                n_key = 0;
+                        } else if (c == '=') {
+                                state = PRE_VALUE;
+                                last_value_whitespace = (size_t) -1;
+                        } else {
+                                if (!strchr(WHITESPACE, c))
+                                        last_key_whitespace = (size_t) -1;
+                                else if (last_key_whitespace == (size_t) -1)
+                                         last_key_whitespace = n_key;
+
+                                if (!GREEDY_REALLOC(key, key_alloc, n_key+2))
+                                        return -ENOMEM;
+
+                                key[n_key++] = c;
+                        }
+
+                        break;
+
+                case PRE_VALUE:
+                        if (strchr(NEWLINE, c)) {
+                                state = PRE_KEY;
+                                line++;
+                                key[n_key] = 0;
+
+                                if (value)
+                                        value[n_value] = 0;
+
+                                /* strip trailing whitespace from key */
+                                if (last_key_whitespace != (size_t) -1)
+                                        key[last_key_whitespace] = 0;
+
+                                r = push(fname, line, key, value, userdata, n_pushed);
+                                if (r < 0)
+                                        return r;
+
+                                n_key = 0;
+                                value = NULL;
+                                value_alloc = n_value = 0;
+
+                        } else if (c == '\'')
+                                state = SINGLE_QUOTE_VALUE;
+                        else if (c == '"')
+                                state = DOUBLE_QUOTE_VALUE;
+                        else if (c == '\\')
+                                state = VALUE_ESCAPE;
+                        else if (!strchr(WHITESPACE, c)) {
+                                state = VALUE;
+
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+                                        return  -ENOMEM;
+
+                                value[n_value++] = c;
+                        }
+
+                        break;
+
+                case VALUE:
+                        if (strchr(NEWLINE, c)) {
+                                state = PRE_KEY;
+                                line++;
+
+                                key[n_key] = 0;
+
+                                if (value)
+                                        value[n_value] = 0;
+
+                                /* Chomp off trailing whitespace from value */
+                                if (last_value_whitespace != (size_t) -1)
+                                        value[last_value_whitespace] = 0;
+
+                                /* strip trailing whitespace from key */
+                                if (last_key_whitespace != (size_t) -1)
+                                        key[last_key_whitespace] = 0;
+
+                                r = push(fname, line, key, value, userdata, n_pushed);
+                                if (r < 0)
+                                        return r;
+
+                                n_key = 0;
+                                value = NULL;
+                                value_alloc = n_value = 0;
+
+                        } else if (c == '\\') {
+                                state = VALUE_ESCAPE;
+                                last_value_whitespace = (size_t) -1;
+                        } else {
+                                if (!strchr(WHITESPACE, c))
+                                        last_value_whitespace = (size_t) -1;
+                                else if (last_value_whitespace == (size_t) -1)
+                                        last_value_whitespace = n_value;
+
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+                                        return -ENOMEM;
+
+                                value[n_value++] = c;
+                        }
+
+                        break;
+
+                case VALUE_ESCAPE:
+                        state = VALUE;
+
+                        if (!strchr(NEWLINE, c)) {
+                                /* Escaped newlines we eat up entirely */
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+                                        return -ENOMEM;
+
+                                value[n_value++] = c;
+                        }
+                        break;
+
+                case SINGLE_QUOTE_VALUE:
+                        if (c == '\'')
+                                state = PRE_VALUE;
+                        else {
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+                                        return -ENOMEM;
+
+                                value[n_value++] = c;
+                        }
+
+                        break;
+
+                case DOUBLE_QUOTE_VALUE:
+                        if (c == '"')
+                                state = PRE_VALUE;
+                        else if (c == '\\')
+                                state = DOUBLE_QUOTE_VALUE_ESCAPE;
+                        else {
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+                                        return -ENOMEM;
+
+                                value[n_value++] = c;
+                        }
+
+                        break;
+
+                case DOUBLE_QUOTE_VALUE_ESCAPE:
+                        state = DOUBLE_QUOTE_VALUE;
+
+                        if (c == '"') {
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+                                        return -ENOMEM;
+                                value[n_value++] = '"';
+                        } else if (!strchr(NEWLINE, c)) {
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+3))
+                                        return -ENOMEM;
+                                value[n_value++] = '\\';
+                                value[n_value++] = c;
+                        }
+
+                        break;
+
+                case COMMENT:
+                        if (c == '\\')
+                                state = COMMENT_ESCAPE;
+                        else if (strchr(NEWLINE, c)) {
+                                state = PRE_KEY;
+                                line++;
+                        }
+                        break;
+
+                case COMMENT_ESCAPE:
+                        state = COMMENT;
+                        break;
+                }
+        }
+
+        if (IN_SET(state,
+                   PRE_VALUE,
+                   VALUE,
+                   VALUE_ESCAPE,
+                   SINGLE_QUOTE_VALUE,
+                   DOUBLE_QUOTE_VALUE,
+                   DOUBLE_QUOTE_VALUE_ESCAPE)) {
+
+                key[n_key] = 0;
+
+                if (value)
+                        value[n_value] = 0;
+
+                if (state == VALUE)
+                        if (last_value_whitespace != (size_t) -1)
+                                value[last_value_whitespace] = 0;
+
+                /* strip trailing whitespace from key */
+                if (last_key_whitespace != (size_t) -1)
+                        key[last_key_whitespace] = 0;
+
+                r = push(fname, line, key, value, userdata, n_pushed);
+                if (r < 0)
+                        return r;
+
+                value = NULL;
+        }
+
+        return 0;
+}
+
+static int check_utf8ness_and_warn(
+                const char *filename, unsigned line,
+                const char *key, char *value) {
+
+        if (!utf8_is_valid(key)) {
+                _cleanup_free_ char *p = NULL;
+
+                p = utf8_escape_invalid(key);
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "%s:%u: invalid UTF-8 in key '%s', ignoring.",
+                                       strna(filename), line, p);
+        }
+
+        if (value && !utf8_is_valid(value)) {
+                _cleanup_free_ char *p = NULL;
+
+                p = utf8_escape_invalid(value);
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.",
+                                       strna(filename), line, key, p);
+        }
+
+        return 0;
+}
+
+static int parse_env_file_push(
+                const char *filename, unsigned line,
+                const char *key, char *value,
+                void *userdata,
+                int *n_pushed) {
+
+        const char *k;
+        va_list aq, *ap = userdata;
+        int r;
+
+        r = check_utf8ness_and_warn(filename, line, key, value);
+        if (r < 0)
+                return r;
+
+        va_copy(aq, *ap);
+
+        while ((k = va_arg(aq, const char *))) {
+                char **v;
+
+                v = va_arg(aq, char **);
+
+                if (streq(key, k)) {
+                        va_end(aq);
+                        free(*v);
+                        *v = value;
+
+                        if (n_pushed)
+                                (*n_pushed)++;
+
+                        return 1;
+                }
+        }
+
+        va_end(aq);
+        free(value);
+
+        return 0;
+}
+
+int parse_env_filev(
+                FILE *f,
+                const char *fname,
+                va_list ap) {
+
+        int r, n_pushed = 0;
+        va_list aq;
+
+        va_copy(aq, ap);
+        r = parse_env_file_internal(f, fname, parse_env_file_push, &aq, &n_pushed);
+        va_end(aq);
+        if (r < 0)
+                return r;
+
+        return n_pushed;
+}
+
+int parse_env_file_sentinel(
+                FILE *f,
+                const char *fname,
+                ...) {
+
+        va_list ap;
+        int r;
+
+        va_start(ap, fname);
+        r = parse_env_filev(f, fname, ap);
+        va_end(ap);
+
+        return r;
+}
+
+#if 0 /* NM_IGNORED */
+static int load_env_file_push(
+                const char *filename, unsigned line,
+                const char *key, char *value,
+                void *userdata,
+                int *n_pushed) {
+        char ***m = userdata;
+        char *p;
+        int r;
+
+        r = check_utf8ness_and_warn(filename, line, key, value);
+        if (r < 0)
+                return r;
+
+        p = strjoin(key, "=", value);
+        if (!p)
+                return -ENOMEM;
+
+        r = strv_env_replace(m, p);
+        if (r < 0) {
+                free(p);
+                return r;
+        }
+
+        if (n_pushed)
+                (*n_pushed)++;
+
+        free(value);
+        return 0;
+}
+
+int load_env_file(FILE *f, const char *fname, char ***rl) {
+        char **m = NULL;
+        int r;
+
+        r = parse_env_file_internal(f, fname, load_env_file_push, &m, NULL);
+        if (r < 0) {
+                strv_free(m);
+                return r;
+        }
+
+        *rl = m;
+        return 0;
+}
+
+static int load_env_file_push_pairs(
+                const char *filename, unsigned line,
+                const char *key, char *value,
+                void *userdata,
+                int *n_pushed) {
+        char ***m = userdata;
+        int r;
+
+        r = check_utf8ness_and_warn(filename, line, key, value);
+        if (r < 0)
+                return r;
+
+        r = strv_extend(m, key);
+        if (r < 0)
+                return -ENOMEM;
+
+        if (!value) {
+                r = strv_extend(m, "");
+                if (r < 0)
+                        return -ENOMEM;
+        } else {
+                r = strv_push(m, value);
+                if (r < 0)
+                        return r;
+        }
+
+        if (n_pushed)
+                (*n_pushed)++;
+
+        return 0;
+}
+
+int load_env_file_pairs(FILE *f, const char *fname, char ***rl) {
+        char **m = NULL;
+        int r;
+
+        r = parse_env_file_internal(f, fname, load_env_file_push_pairs, &m, NULL);
+        if (r < 0) {
+                strv_free(m);
+                return r;
+        }
+
+        *rl = m;
+        return 0;
+}
+
+static int merge_env_file_push(
+                const char *filename, unsigned line,
+                const char *key, char *value,
+                void *userdata,
+                int *n_pushed) {
+
+        char ***env = userdata;
+        char *expanded_value;
+
+        assert(env);
+
+        if (!value) {
+                log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename), line, key);
+                return 0;
+        }
+
+        if (!env_name_is_valid(key)) {
+                log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename), line, key);
+                free(value);
+                return 0;
+        }
+
+        expanded_value = replace_env(value, *env,
+                                     REPLACE_ENV_USE_ENVIRONMENT|
+                                     REPLACE_ENV_ALLOW_BRACELESS|
+                                     REPLACE_ENV_ALLOW_EXTENDED);
+        if (!expanded_value)
+                return -ENOMEM;
+
+        free_and_replace(value, expanded_value);
+
+        return load_env_file_push(filename, line, key, value, env, n_pushed);
+}
+
+int merge_env_file(
+                char ***env,
+                FILE *f,
+                const char *fname) {
+
+        /* NOTE: this function supports braceful and braceless variable expansions,
+         * plus "extended" substitutions, unlike other exported parsing functions.
+         */
+
+        return parse_env_file_internal(f, fname, merge_env_file_push, env, NULL);
+}
+
+static void write_env_var(FILE *f, const char *v) {
+        const char *p;
+
+        p = strchr(v, '=');
+        if (!p) {
+                /* Fallback */
+                fputs_unlocked(v, f);
+                fputc_unlocked('\n', f);
+                return;
+        }
+
+        p++;
+        fwrite_unlocked(v, 1, p-v, f);
+
+        if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) {
+                fputc_unlocked('"', f);
+
+                for (; *p; p++) {
+                        if (strchr(SHELL_NEED_ESCAPE, *p))
+                                fputc_unlocked('\\', f);
+
+                        fputc_unlocked(*p, f);
+                }
+
+                fputc_unlocked('"', f);
+        } else
+                fputs_unlocked(p, f);
+
+        fputc_unlocked('\n', f);
+}
+
+int write_env_file(const char *fname, char **l) {
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *p = NULL;
+        char **i;
+        int r;
+
+        assert(fname);
+
+        r = fopen_temporary(fname, &f, &p);
+        if (r < 0)
+                return r;
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+        (void) fchmod_umask(fileno(f), 0644);
+
+        STRV_FOREACH(i, l)
+                write_env_var(f, *i);
+
+        r = fflush_and_check(f);
+        if (r >= 0) {
+                if (rename(p, fname) >= 0)
+                        return 0;
+
+                r = -errno;
+        }
+
+        unlink(p);
+        return r;
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/env-file.h b/shared/systemd/src/basic/env-file.h
new file mode 100644
index 00000000..e1ca195f
--- /dev/null
+++ b/shared/systemd/src/basic/env-file.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "macro.h"
+
+int parse_env_filev(FILE *f, const char *fname, va_list ap);
+int parse_env_file_sentinel(FILE *f, const char *fname, ...) _sentinel_;
+#define parse_env_file(f, fname, ...) parse_env_file_sentinel(f, fname, __VA_ARGS__, NULL)
+int load_env_file(FILE *f, const char *fname, char ***l);
+int load_env_file_pairs(FILE *f, const char *fname, char ***l);
+
+int merge_env_file(char ***env, FILE *f, const char *fname);
+
+int write_env_file(const char *fname, char **l);
diff --git a/shared/systemd/src/basic/env-util.c b/shared/systemd/src/basic/env-util.c
new file mode 100644
index 00000000..dc10362d
--- /dev/null
+++ b/shared/systemd/src/basic/env-util.c
@@ -0,0 +1,758 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "env-util.h"
+#include "escape.h"
+#include "extract-word.h"
+#include "macro.h"
+#include "parse-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "utf8.h"
+
+#if 0 /* NM_IGNORED */
+#define VALID_CHARS_ENV_NAME                    \
+        DIGITS LETTERS                          \
+        "_"
+
+static bool env_name_is_valid_n(const char *e, size_t n) {
+        const char *p;
+
+        if (!e)
+                return false;
+
+        if (n <= 0)
+                return false;
+
+        if (e[0] >= '0' && e[0] <= '9')
+                return false;
+
+        /* POSIX says the overall size of the environment block cannot
+         * be > ARG_MAX, an individual assignment hence cannot be
+         * either. Discounting the equal sign and trailing NUL this
+         * hence leaves ARG_MAX-2 as longest possible variable
+         * name. */
+        if (n > (size_t) sysconf(_SC_ARG_MAX) - 2)
+                return false;
+
+        for (p = e; p < e + n; p++)
+                if (!strchr(VALID_CHARS_ENV_NAME, *p))
+                        return false;
+
+        return true;
+}
+
+bool env_name_is_valid(const char *e) {
+        if (!e)
+                return false;
+
+        return env_name_is_valid_n(e, strlen(e));
+}
+
+bool env_value_is_valid(const char *e) {
+        if (!e)
+                return false;
+
+        if (!utf8_is_valid(e))
+                return false;
+
+        /* bash allows tabs and newlines in environment variables, and so
+         * should we */
+        if (string_has_cc(e, "\t\n"))
+                return false;
+
+        /* POSIX says the overall size of the environment block cannot
+         * be > ARG_MAX, an individual assignment hence cannot be
+         * either. Discounting the shortest possible variable name of
+         * length 1, the equal sign and trailing NUL this hence leaves
+         * ARG_MAX-3 as longest possible variable value. */
+        if (strlen(e) > (size_t) sysconf(_SC_ARG_MAX) - 3)
+                return false;
+
+        return true;
+}
+
+bool env_assignment_is_valid(const char *e) {
+        const char *eq;
+
+        eq = strchr(e, '=');
+        if (!eq)
+                return false;
+
+        if (!env_name_is_valid_n(e, eq - e))
+                return false;
+
+        if (!env_value_is_valid(eq + 1))
+                return false;
+
+        /* POSIX says the overall size of the environment block cannot
+         * be > ARG_MAX, hence the individual variable assignments
+         * cannot be either, but let's leave room for one trailing NUL
+         * byte. */
+        if (strlen(e) > (size_t) sysconf(_SC_ARG_MAX) - 1)
+                return false;
+
+        return true;
+}
+
+bool strv_env_is_valid(char **e) {
+        char **p, **q;
+
+        STRV_FOREACH(p, e) {
+                size_t k;
+
+                if (!env_assignment_is_valid(*p))
+                        return false;
+
+                /* Check if there are duplicate assignments */
+                k = strcspn(*p, "=");
+                STRV_FOREACH(q, p + 1)
+                        if (strneq(*p, *q, k) && (*q)[k] == '=')
+                                return false;
+        }
+
+        return true;
+}
+
+bool strv_env_name_is_valid(char **l) {
+        char **p;
+
+        STRV_FOREACH(p, l) {
+                if (!env_name_is_valid(*p))
+                        return false;
+
+                if (strv_contains(p + 1, *p))
+                        return false;
+        }
+
+        return true;
+}
+
+bool strv_env_name_or_assignment_is_valid(char **l) {
+        char **p;
+
+        STRV_FOREACH(p, l) {
+                if (!env_assignment_is_valid(*p) && !env_name_is_valid(*p))
+                        return false;
+
+                if (strv_contains(p + 1, *p))
+                        return false;
+        }
+
+        return true;
+}
+
+static int env_append(char **r, char ***k, char **a) {
+        assert(r);
+        assert(k);
+        assert(*k >= r);
+
+        if (!a)
+                return 0;
+
+        /* Expects the following arguments: 'r' shall point to the beginning of an strv we are going to append to, 'k'
+         * to a pointer pointing to the NULL entry at the end of the same array. 'a' shall point to another strv.
+         *
+         * This call adds every entry of 'a' to 'r', either overriding an existing matching entry, or appending to it.
+         *
+         * This call assumes 'r' has enough pre-allocated space to grow by all of 'a''s items. */
+
+        for (; *a; a++) {
+                char **j, *c;
+                size_t n;
+
+                n = strcspn(*a, "=");
+                if ((*a)[n] == '=')
+                        n++;
+
+                for (j = r; j < *k; j++)
+                        if (strneq(*j, *a, n))
+                                break;
+
+                c = strdup(*a);
+                if (!c)
+                        return -ENOMEM;
+
+                if (j >= *k) { /* Append to the end? */
+                        (*k)[0] = c;
+                        (*k)[1] = NULL;
+                        (*k)++;
+                } else
+                        free_and_replace(*j, c); /* Override existing item */
+        }
+
+        return 0;
+}
+
+char **strv_env_merge(size_t n_lists, ...) {
+        _cleanup_strv_free_ char **ret = NULL;
+        size_t n = 0, i;
+        char **l, **k;
+        va_list ap;
+
+        /* Merges an arbitrary number of environment sets */
+
+        va_start(ap, n_lists);
+        for (i = 0; i < n_lists; i++) {
+                l = va_arg(ap, char**);
+                n += strv_length(l);
+        }
+        va_end(ap);
+
+        ret = new(char*, n+1);
+        if (!ret)
+                return NULL;
+
+        *ret = NULL;
+        k = ret;
+
+        va_start(ap, n_lists);
+        for (i = 0; i < n_lists; i++) {
+                l = va_arg(ap, char**);
+                if (env_append(ret, &k, l) < 0) {
+                        va_end(ap);
+                        return NULL;
+                }
+        }
+        va_end(ap);
+
+        return TAKE_PTR(ret);
+}
+
+static bool env_match(const char *t, const char *pattern) {
+        assert(t);
+        assert(pattern);
+
+        /* pattern a matches string a
+         *         a matches a=
+         *         a matches a=b
+         *         a= matches a=
+         *         a=b matches a=b
+         *         a= does not match a
+         *         a=b does not match a=
+         *         a=b does not match a
+         *         a=b does not match a=c */
+
+        if (streq(t, pattern))
+                return true;
+
+        if (!strchr(pattern, '=')) {
+                size_t l = strlen(pattern);
+
+                return strneq(t, pattern, l) && t[l] == '=';
+        }
+
+        return false;
+}
+
+static bool env_entry_has_name(const char *entry, const char *name) {
+        const char *t;
+
+        assert(entry);
+        assert(name);
+
+        t = startswith(entry, name);
+        if (!t)
+                return false;
+
+        return *t == '=';
+}
+
+char **strv_env_delete(char **x, size_t n_lists, ...) {
+        size_t n, i = 0;
+        char **k, **r;
+        va_list ap;
+
+        /* Deletes every entry from x that is mentioned in the other
+         * string lists */
+
+        n = strv_length(x);
+
+        r = new(char*, n+1);
+        if (!r)
+                return NULL;
+
+        STRV_FOREACH(k, x) {
+                size_t v;
+
+                va_start(ap, n_lists);
+                for (v = 0; v < n_lists; v++) {
+                        char **l, **j;
+
+                        l = va_arg(ap, char**);
+                        STRV_FOREACH(j, l)
+                                if (env_match(*k, *j))
+                                        goto skip;
+                }
+                va_end(ap);
+
+                r[i] = strdup(*k);
+                if (!r[i]) {
+                        strv_free(r);
+                        return NULL;
+                }
+
+                i++;
+                continue;
+
+        skip:
+                va_end(ap);
+        }
+
+        r[i] = NULL;
+
+        assert(i <= n);
+
+        return r;
+}
+
+char **strv_env_unset(char **l, const char *p) {
+
+        char **f, **t;
+
+        if (!l)
+                return NULL;
+
+        assert(p);
+
+        /* Drops every occurrence of the env var setting p in the
+         * string list. Edits in-place. */
+
+        for (f = t = l; *f; f++) {
+
+                if (env_match(*f, p)) {
+                        free(*f);
+                        continue;
+                }
+
+                *(t++) = *f;
+        }
+
+        *t = NULL;
+        return l;
+}
+
+char **strv_env_unset_many(char **l, ...) {
+        char **f, **t;
+
+        if (!l)
+                return NULL;
+
+        /* Like strv_env_unset() but applies many at once. Edits in-place. */
+
+        for (f = t = l; *f; f++) {
+                bool found = false;
+                const char *p;
+                va_list ap;
+
+                va_start(ap, l);
+
+                while ((p = va_arg(ap, const char*))) {
+                        if (env_match(*f, p)) {
+                                found = true;
+                                break;
+                        }
+                }
+
+                va_end(ap);
+
+                if (found) {
+                        free(*f);
+                        continue;
+                }
+
+                *(t++) = *f;
+        }
+
+        *t = NULL;
+        return l;
+}
+
+int strv_env_replace(char ***l, char *p) {
+        const char *t, *name;
+        char **f;
+        int r;
+
+        assert(p);
+
+        /* Replace first occurrence of the env var or add a new one in the string list. Drop other occurrences. Edits
+         * in-place. Does not copy p.  p must be a valid key=value assignment.
+         */
+
+        t = strchr(p, '=');
+        if (!t)
+                return -EINVAL;
+
+        name = strndupa(p, t - p);
+
+        STRV_FOREACH(f, *l)
+                if (env_entry_has_name(*f, name)) {
+                        free_and_replace(*f, p);
+                        strv_env_unset(f + 1, *f);
+                        return 0;
+                }
+
+        /* We didn't find a match, we need to append p or create a new strv */
+        r = strv_push(l, p);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+char **strv_env_set(char **x, const char *p) {
+        _cleanup_strv_free_ char **ret = NULL;
+        size_t n, m;
+        char **k;
+
+        /* Overrides the env var setting of p, returns a new copy */
+
+        n = strv_length(x);
+        m = n + 2;
+        if (m < n) /* overflow? */
+                return NULL;
+
+        ret = new(char*, m);
+        if (!ret)
+                return NULL;
+
+        *ret = NULL;
+        k = ret;
+
+        if (env_append(ret, &k, x) < 0)
+                return NULL;
+
+        if (env_append(ret, &k, STRV_MAKE(p)) < 0)
+                return NULL;
+
+        return TAKE_PTR(ret);
+}
+
+char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) {
+        char **i;
+
+        assert(name);
+
+        if (k <= 0)
+                return NULL;
+
+        STRV_FOREACH_BACKWARDS(i, l)
+                if (strneq(*i, name, k) &&
+                    (*i)[k] == '=')
+                        return *i + k + 1;
+
+        if (flags & REPLACE_ENV_USE_ENVIRONMENT) {
+                const char *t;
+
+                t = strndupa(name, k);
+                return getenv(t);
+        };
+
+        return NULL;
+}
+
+char *strv_env_get(char **l, const char *name) {
+        assert(name);
+
+        return strv_env_get_n(l, name, strlen(name), 0);
+}
+
+char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) {
+        char **p, **q;
+        int k = 0;
+
+        STRV_FOREACH(p, e) {
+                size_t n;
+                bool duplicate = false;
+
+                if (!env_assignment_is_valid(*p)) {
+                        if (invalid_callback)
+                                invalid_callback(*p, userdata);
+                        free(*p);
+                        continue;
+                }
+
+                n = strcspn(*p, "=");
+                STRV_FOREACH(q, p + 1)
+                        if (strneq(*p, *q, n) && (*q)[n] == '=') {
+                                duplicate = true;
+                                break;
+                        }
+
+                if (duplicate) {
+                        free(*p);
+                        continue;
+                }
+
+                e[k++] = *p;
+        }
+
+        if (e)
+                e[k] = NULL;
+
+        return e;
+}
+
+char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
+        enum {
+                WORD,
+                CURLY,
+                VARIABLE,
+                VARIABLE_RAW,
+                TEST,
+                DEFAULT_VALUE,
+                ALTERNATE_VALUE,
+        } state = WORD;
+
+        const char *e, *word = format, *test_value;
+        char *k;
+        _cleanup_free_ char *r = NULL;
+        size_t i, len;
+        int nest = 0;
+
+        assert(format);
+
+        for (e = format, i = 0; *e && i < n; e ++, i ++)
+                switch (state) {
+
+                case WORD:
+                        if (*e == '$')
+                                state = CURLY;
+                        break;
+
+                case CURLY:
+                        if (*e == '{') {
+                                k = strnappend(r, word, e-word-1);
+                                if (!k)
+                                        return NULL;
+
+                                free_and_replace(r, k);
+
+                                word = e-1;
+                                state = VARIABLE;
+                                nest++;
+                        } else if (*e == '$') {
+                                k = strnappend(r, word, e-word);
+                                if (!k)
+                                        return NULL;
+
+                                free_and_replace(r, k);
+
+                                word = e+1;
+                                state = WORD;
+
+                        } else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_CHARS_ENV_NAME, *e)) {
+                                k = strnappend(r, word, e-word-1);
+                                if (!k)
+                                        return NULL;
+
+                                free_and_replace(r, k);
+
+                                word = e-1;
+                                state = VARIABLE_RAW;
+
+                        } else
+                                state = WORD;
+                        break;
+
+                case VARIABLE:
+                        if (*e == '}') {
+                                const char *t;
+
+                                t = strv_env_get_n(env, word+2, e-word-2, flags);
+
+                                k = strappend(r, t);
+                                if (!k)
+                                        return NULL;
+
+                                free_and_replace(r, k);
+
+                                word = e+1;
+                                state = WORD;
+                        } else if (*e == ':') {
+                                if (!(flags & REPLACE_ENV_ALLOW_EXTENDED))
+                                        /* Treat this as unsupported syntax, i.e. do no replacement */
+                                        state = WORD;
+                                else {
+                                        len = e-word-2;
+                                        state = TEST;
+                                }
+                        }
+                        break;
+
+                case TEST:
+                        if (*e == '-')
+                                state = DEFAULT_VALUE;
+                        else if (*e == '+')
+                                state = ALTERNATE_VALUE;
+                        else {
+                                state = WORD;
+                                break;
+                        }
+
+                        test_value = e+1;
+                        break;
+
+                case DEFAULT_VALUE: /* fall through */
+                case ALTERNATE_VALUE:
+                        assert(flags & REPLACE_ENV_ALLOW_EXTENDED);
+
+                        if (*e == '{') {
+                                nest++;
+                                break;
+                        }
+
+                        if (*e != '}')
+                                break;
+
+                        nest--;
+                        if (nest == 0) {
+                                const char *t;
+                                _cleanup_free_ char *v = NULL;
+
+                                t = strv_env_get_n(env, word+2, len, flags);
+
+                                if (t && state == ALTERNATE_VALUE)
+                                        t = v = replace_env_n(test_value, e-test_value, env, flags);
+                                else if (!t && state == DEFAULT_VALUE)
+                                        t = v = replace_env_n(test_value, e-test_value, env, flags);
+
+                                k = strappend(r, t);
+                                if (!k)
+                                        return NULL;
+
+                                free_and_replace(r, k);
+
+                                word = e+1;
+                                state = WORD;
+                        }
+                        break;
+
+                case VARIABLE_RAW:
+                        assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
+
+                        if (!strchr(VALID_CHARS_ENV_NAME, *e)) {
+                                const char *t;
+
+                                t = strv_env_get_n(env, word+1, e-word-1, flags);
+
+                                k = strappend(r, t);
+                                if (!k)
+                                        return NULL;
+
+                                free_and_replace(r, k);
+
+                                word = e--;
+                                i--;
+                                state = WORD;
+                        }
+                        break;
+                }
+
+        if (state == VARIABLE_RAW) {
+                const char *t;
+
+                assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
+
+                t = strv_env_get_n(env, word+1, e-word-1, flags);
+                return strappend(r, t);
+        } else
+                return strnappend(r, word, e-word);
+}
+
+char **replace_env_argv(char **argv, char **env) {
+        char **ret, **i;
+        size_t k = 0, l = 0;
+
+        l = strv_length(argv);
+
+        ret = new(char*, l+1);
+        if (!ret)
+                return NULL;
+
+        STRV_FOREACH(i, argv) {
+
+                /* If $FOO appears as single word, replace it by the split up variable */
+                if ((*i)[0] == '$' && !IN_SET((*i)[1], '{', '$')) {
+                        char *e;
+                        char **w, **m = NULL;
+                        size_t q;
+
+                        e = strv_env_get(env, *i+1);
+                        if (e) {
+                                int r;
+
+                                r = strv_split_extract(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_QUOTES);
+                                if (r < 0) {
+                                        ret[k] = NULL;
+                                        strv_free(ret);
+                                        return NULL;
+                                }
+                        } else
+                                m = NULL;
+
+                        q = strv_length(m);
+                        l = l + q - 1;
+
+                        w = reallocarray(ret, l + 1, sizeof(char *));
+                        if (!w) {
+                                ret[k] = NULL;
+                                strv_free(ret);
+                                strv_free(m);
+                                return NULL;
+                        }
+
+                        ret = w;
+                        if (m) {
+                                memcpy(ret + k, m, q * sizeof(char*));
+                                free(m);
+                        }
+
+                        k += q;
+                        continue;
+                }
+
+                /* If ${FOO} appears as part of a word, replace it by the variable as-is */
+                ret[k] = replace_env(*i, env, 0);
+                if (!ret[k]) {
+                        strv_free(ret);
+                        return NULL;
+                }
+                k++;
+        }
+
+        ret[k] = NULL;
+        return ret;
+}
+#endif /* NM_IGNORED */
+
+int getenv_bool(const char *p) {
+        const char *e;
+
+        e = getenv(p);
+        if (!e)
+                return -ENXIO;
+
+        return parse_boolean(e);
+}
+
+#if 0 /* NM_IGNORED */
+int getenv_bool_secure(const char *p) {
+        const char *e;
+
+        e = secure_getenv(p);
+        if (!e)
+                return -ENXIO;
+
+        return parse_boolean(e);
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/env-util.h b/shared/systemd/src/basic/env-util.h
new file mode 100644
index 00000000..d54f9965
--- /dev/null
+++ b/shared/systemd/src/basic/env-util.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#include "macro.h"
+#include "string.h"
+
+bool env_name_is_valid(const char *e);
+bool env_value_is_valid(const char *e);
+bool env_assignment_is_valid(const char *e);
+
+enum {
+        REPLACE_ENV_USE_ENVIRONMENT = 1 << 0,
+        REPLACE_ENV_ALLOW_BRACELESS = 1 << 1,
+        REPLACE_ENV_ALLOW_EXTENDED  = 1 << 2,
+};
+
+char *replace_env_n(const char *format, size_t n, char **env, unsigned flags);
+char **replace_env_argv(char **argv, char **env);
+
+static inline char *replace_env(const char *format, char **env, unsigned flags) {
+        return replace_env_n(format, strlen(format), env, flags);
+}
+
+bool strv_env_is_valid(char **e);
+#define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL)
+char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata);
+
+bool strv_env_name_is_valid(char **l);
+bool strv_env_name_or_assignment_is_valid(char **l);
+
+char **strv_env_merge(size_t n_lists, ...);
+char **strv_env_delete(char **x, size_t n_lists, ...); /* New copy */
+
+char **strv_env_set(char **x, const char *p); /* New copy ... */
+char **strv_env_unset(char **l, const char *p); /* In place ... */
+char **strv_env_unset_many(char **l, ...) _sentinel_;
+int strv_env_replace(char ***l, char *p); /* In place ... */
+
+char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_;
+char *strv_env_get(char **x, const char *n) _pure_;
+
+int getenv_bool(const char *p);
+int getenv_bool_secure(const char *p);
diff --git a/shared/systemd/src/basic/escape.c b/shared/systemd/src/basic/escape.c
new file mode 100644
index 00000000..8f7a1b33
--- /dev/null
+++ b/shared/systemd/src/basic/escape.c
@@ -0,0 +1,508 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "escape.h"
+#include "hexdecoct.h"
+#include "macro.h"
+#include "utf8.h"
+
+int cescape_char(char c, char *buf) {
+        char *buf_old = buf;
+
+        /* Needs space for 4 characters in the buffer */
+
+        switch (c) {
+
+                case '\a':
+                        *(buf++) = '\\';
+                        *(buf++) = 'a';
+                        break;
+                case '\b':
+                        *(buf++) = '\\';
+                        *(buf++) = 'b';
+                        break;
+                case '\f':
+                        *(buf++) = '\\';
+                        *(buf++) = 'f';
+                        break;
+                case '\n':
+                        *(buf++) = '\\';
+                        *(buf++) = 'n';
+                        break;
+                case '\r':
+                        *(buf++) = '\\';
+                        *(buf++) = 'r';
+                        break;
+                case '\t':
+                        *(buf++) = '\\';
+                        *(buf++) = 't';
+                        break;
+                case '\v':
+                        *(buf++) = '\\';
+                        *(buf++) = 'v';
+                        break;
+                case '\\':
+                        *(buf++) = '\\';
+                        *(buf++) = '\\';
+                        break;
+                case '"':
+                        *(buf++) = '\\';
+                        *(buf++) = '"';
+                        break;
+                case '\'':
+                        *(buf++) = '\\';
+                        *(buf++) = '\'';
+                        break;
+
+                default:
+                        /* For special chars we prefer octal over
+                         * hexadecimal encoding, simply because glib's
+                         * g_strescape() does the same */
+                        if ((c < ' ') || (c >= 127)) {
+                                *(buf++) = '\\';
+                                *(buf++) = octchar((unsigned char) c >> 6);
+                                *(buf++) = octchar((unsigned char) c >> 3);
+                                *(buf++) = octchar((unsigned char) c);
+                        } else
+                                *(buf++) = c;
+                        break;
+        }
+
+        return buf - buf_old;
+}
+
+char *cescape_length(const char *s, size_t n) {
+        const char *f;
+        char *r, *t;
+
+        assert(s || n == 0);
+
+        /* Does C style string escaping. May be reversed with
+         * cunescape(). */
+
+        r = new(char, n*4 + 1);
+        if (!r)
+                return NULL;
+
+        for (f = s, t = r; f < s + n; f++)
+                t += cescape_char(*f, t);
+
+        *t = 0;
+
+        return r;
+}
+
+char *cescape(const char *s) {
+        assert(s);
+
+        return cescape_length(s, strlen(s));
+}
+
+int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) {
+        int r = 1;
+
+        assert(p);
+        assert(ret);
+
+        /* Unescapes C style. Returns the unescaped character in ret.
+         * Sets *eight_bit to true if the escaped sequence either fits in
+         * one byte in UTF-8 or is a non-unicode literal byte and should
+         * instead be copied directly.
+         */
+
+        if (length != (size_t) -1 && length < 1)
+                return -EINVAL;
+
+        switch (p[0]) {
+
+        case 'a':
+                *ret = '\a';
+                break;
+        case 'b':
+                *ret = '\b';
+                break;
+        case 'f':
+                *ret = '\f';
+                break;
+        case 'n':
+                *ret = '\n';
+                break;
+        case 'r':
+                *ret = '\r';
+                break;
+        case 't':
+                *ret = '\t';
+                break;
+        case 'v':
+                *ret = '\v';
+                break;
+        case '\\':
+                *ret = '\\';
+                break;
+        case '"':
+                *ret = '"';
+                break;
+        case '\'':
+                *ret = '\'';
+                break;
+
+        case 's':
+                /* This is an extension of the XDG syntax files */
+                *ret = ' ';
+                break;
+
+        case 'x': {
+                /* hexadecimal encoding */
+                int a, b;
+
+                if (length != (size_t) -1 && length < 3)
+                        return -EINVAL;
+
+                a = unhexchar(p[1]);
+                if (a < 0)
+                        return -EINVAL;
+
+                b = unhexchar(p[2]);
+                if (b < 0)
+                        return -EINVAL;
+
+                /* Don't allow NUL bytes */
+                if (a == 0 && b == 0)
+                        return -EINVAL;
+
+                *ret = (a << 4U) | b;
+                *eight_bit = true;
+                r = 3;
+                break;
+        }
+
+        case 'u': {
+                /* C++11 style 16bit unicode */
+
+                int a[4];
+                size_t i;
+                uint32_t c;
+
+                if (length != (size_t) -1 && length < 5)
+                        return -EINVAL;
+
+                for (i = 0; i < 4; i++) {
+                        a[i] = unhexchar(p[1 + i]);
+                        if (a[i] < 0)
+                                return a[i];
+                }
+
+                c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3];
+
+                /* Don't allow 0 chars */
+                if (c == 0)
+                        return -EINVAL;
+
+                *ret = c;
+                r = 5;
+                break;
+        }
+
+        case 'U': {
+                /* C++11 style 32bit unicode */
+
+                int a[8];
+                size_t i;
+                char32_t c;
+
+                if (length != (size_t) -1 && length < 9)
+                        return -EINVAL;
+
+                for (i = 0; i < 8; i++) {
+                        a[i] = unhexchar(p[1 + i]);
+                        if (a[i] < 0)
+                                return a[i];
+                }
+
+                c = ((uint32_t) a[0] << 28U) | ((uint32_t) a[1] << 24U) | ((uint32_t) a[2] << 20U) | ((uint32_t) a[3] << 16U) |
+                    ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] <<  8U) | ((uint32_t) a[6] <<  4U) |  (uint32_t) a[7];
+
+                /* Don't allow 0 chars */
+                if (c == 0)
+                        return -EINVAL;
+
+                /* Don't allow invalid code points */
+                if (!unichar_is_valid(c))
+                        return -EINVAL;
+
+                *ret = c;
+                r = 9;
+                break;
+        }
+
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7': {
+                /* octal encoding */
+                int a, b, c;
+                char32_t m;
+
+                if (length != (size_t) -1 && length < 3)
+                        return -EINVAL;
+
+                a = unoctchar(p[0]);
+                if (a < 0)
+                        return -EINVAL;
+
+                b = unoctchar(p[1]);
+                if (b < 0)
+                        return -EINVAL;
+
+                c = unoctchar(p[2]);
+                if (c < 0)
+                        return -EINVAL;
+
+                /* don't allow NUL bytes */
+                if (a == 0 && b == 0 && c == 0)
+                        return -EINVAL;
+
+                /* Don't allow bytes above 255 */
+                m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c;
+                if (m > 255)
+                        return -EINVAL;
+
+                *ret = m;
+                *eight_bit = true;
+                r = 3;
+                break;
+        }
+
+        default:
+                return -EINVAL;
+        }
+
+        return r;
+}
+
+int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) {
+        char *r, *t;
+        const char *f;
+        size_t pl;
+
+        assert(s);
+        assert(ret);
+
+        /* Undoes C style string escaping, and optionally prefixes it. */
+
+        pl = strlen_ptr(prefix);
+
+        r = new(char, pl+length+1);
+        if (!r)
+                return -ENOMEM;
+
+        if (prefix)
+                memcpy(r, prefix, pl);
+
+        for (f = s, t = r + pl; f < s + length; f++) {
+                size_t remaining;
+                bool eight_bit = false;
+                char32_t u;
+                int k;
+
+                remaining = s + length - f;
+                assert(remaining > 0);
+
+                if (*f != '\\') {
+                        /* A literal, copy verbatim */
+                        *(t++) = *f;
+                        continue;
+                }
+
+                if (remaining == 1) {
+                        if (flags & UNESCAPE_RELAX) {
+                                /* A trailing backslash, copy verbatim */
+                                *(t++) = *f;
+                                continue;
+                        }
+
+                        free(r);
+                        return -EINVAL;
+                }
+
+                k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit);
+                if (k < 0) {
+                        if (flags & UNESCAPE_RELAX) {
+                                /* Invalid escape code, let's take it literal then */
+                                *(t++) = '\\';
+                                continue;
+                        }
+
+                        free(r);
+                        return k;
+                }
+
+                f += k;
+                if (eight_bit)
+                        /* One byte? Set directly as specified */
+                        *(t++) = u;
+                else
+                        /* Otherwise encode as multi-byte UTF-8 */
+                        t += utf8_encode_unichar(t, u);
+        }
+
+        *t = 0;
+
+        *ret = r;
+        return t - r;
+}
+
+int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) {
+        return cunescape_length_with_prefix(s, length, NULL, flags, ret);
+}
+
+int cunescape(const char *s, UnescapeFlags flags, char **ret) {
+        return cunescape_length(s, strlen(s), flags, ret);
+}
+
+char *xescape(const char *s, const char *bad) {
+        char *r, *t;
+        const char *f;
+
+        /* Escapes all chars in bad, in addition to \ and all special
+         * chars, in \xFF style escaping. May be reversed with
+         * cunescape(). */
+
+        r = new(char, strlen(s) * 4 + 1);
+        if (!r)
+                return NULL;
+
+        for (f = s, t = r; *f; f++) {
+
+                if ((*f < ' ') || (*f >= 127) ||
+                    (*f == '\\') || strchr(bad, *f)) {
+                        *(t++) = '\\';
+                        *(t++) = 'x';
+                        *(t++) = hexchar(*f >> 4);
+                        *(t++) = hexchar(*f);
+                } else
+                        *(t++) = *f;
+        }
+
+        *t = 0;
+
+        return r;
+}
+
+char *octescape(const char *s, size_t len) {
+        char *r, *t;
+        const char *f;
+
+        /* Escapes all chars in bad, in addition to \ and " chars,
+         * in \nnn style escaping. */
+
+        r = new(char, len * 4 + 1);
+        if (!r)
+                return NULL;
+
+        for (f = s, t = r; f < s + len; f++) {
+
+                if (*f < ' ' || *f >= 127 || IN_SET(*f, '\\', '"')) {
+                        *(t++) = '\\';
+                        *(t++) = '0' + (*f >> 6);
+                        *(t++) = '0' + ((*f >> 3) & 8);
+                        *(t++) = '0' + (*f & 8);
+                } else
+                        *(t++) = *f;
+        }
+
+        *t = 0;
+
+        return r;
+
+}
+
+static char *strcpy_backslash_escaped(char *t, const char *s, const char *bad, bool escape_tab_nl) {
+        assert(bad);
+
+        for (; *s; s++) {
+                if (escape_tab_nl && IN_SET(*s, '\n', '\t')) {
+                        *(t++) = '\\';
+                        *(t++) = *s == '\n' ? 'n' : 't';
+                        continue;
+                }
+
+                if (*s == '\\' || strchr(bad, *s))
+                        *(t++) = '\\';
+
+                *(t++) = *s;
+        }
+
+        return t;
+}
+
+char *shell_escape(const char *s, const char *bad) {
+        char *r, *t;
+
+        r = new(char, strlen(s)*2+1);
+        if (!r)
+                return NULL;
+
+        t = strcpy_backslash_escaped(r, s, bad, false);
+        *t = 0;
+
+        return r;
+}
+
+char* shell_maybe_quote(const char *s, EscapeStyle style) {
+        const char *p;
+        char *r, *t;
+
+        assert(s);
+
+        /* Encloses a string in quotes if necessary to make it OK as a shell
+         * string. Note that we treat benign UTF-8 characters as needing
+         * escaping too, but that should be OK. */
+
+        for (p = s; *p; p++)
+                if (*p <= ' ' ||
+                    *p >= 127 ||
+                    strchr(SHELL_NEED_QUOTES, *p))
+                        break;
+
+        if (!*p)
+                return strdup(s);
+
+        r = new(char, (style == ESCAPE_POSIX) + 1 + strlen(s)*2 + 1 + 1);
+        if (!r)
+                return NULL;
+
+        t = r;
+        if (style == ESCAPE_BACKSLASH)
+                *(t++) = '"';
+        else if (style == ESCAPE_POSIX) {
+                *(t++) = '$';
+                *(t++) = '\'';
+        } else
+                assert_not_reached("Bad EscapeStyle");
+
+        t = mempcpy(t, s, p - s);
+
+        if (style == ESCAPE_BACKSLASH)
+                t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE, false);
+        else
+                t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE_POSIX, true);
+
+        if (style == ESCAPE_BACKSLASH)
+                *(t++) = '"';
+        else
+                *(t++) = '\'';
+        *t = 0;
+
+        return r;
+}
diff --git a/shared/systemd/src/basic/escape.h b/shared/systemd/src/basic/escape.h
new file mode 100644
index 00000000..51562099
--- /dev/null
+++ b/shared/systemd/src/basic/escape.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <uchar.h>
+
+#include "string-util.h"
+#include "missing_type.h"
+
+/* What characters are special in the shell? */
+/* must be escaped outside and inside double-quotes */
+#define SHELL_NEED_ESCAPE "\"\\`$"
+
+/* Those that can be escaped or double-quoted.
+ *
+ * Stricly speaking, ! does not need to be escaped, except in interactive
+ * mode, but let's be extra nice to the user and quote ! in case this
+ * output is ever used in interactive mode. */
+#define SHELL_NEED_QUOTES SHELL_NEED_ESCAPE GLOB_CHARS "'()<>|&;!"
+
+/* Note that we assume control characters would need to be escaped too in
+ * addition to the "special" characters listed here, if they appear in the
+ * string. Current users disallow control characters. Also '"' shall not
+ * be escaped.
+ */
+#define SHELL_NEED_ESCAPE_POSIX "\\\'"
+
+typedef enum UnescapeFlags {
+        UNESCAPE_RELAX = 1,
+} UnescapeFlags;
+
+typedef enum EscapeStyle {
+        ESCAPE_BACKSLASH = 1,
+        ESCAPE_POSIX = 2,
+} EscapeStyle;
+
+char *cescape(const char *s);
+char *cescape_length(const char *s, size_t n);
+int cescape_char(char c, char *buf);
+
+int cunescape(const char *s, UnescapeFlags flags, char **ret);
+int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret);
+int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret);
+int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit);
+
+char *xescape(const char *s, const char *bad);
+char *octescape(const char *s, size_t len);
+
+char *shell_escape(const char *s, const char *bad);
+char* shell_maybe_quote(const char *s, EscapeStyle style);
diff --git a/shared/systemd/src/basic/ether-addr-util.c b/shared/systemd/src/basic/ether-addr-util.c
new file mode 100644
index 00000000..4878a3d2
--- /dev/null
+++ b/shared/systemd/src/basic/ether-addr-util.c
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <net/ethernet.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "ether-addr-util.h"
+#include "macro.h"
+#include "string-util.h"
+
+char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]) {
+        assert(addr);
+        assert(buffer);
+
+        /* Like ether_ntoa() but uses %02x instead of %x to print
+         * ethernet addresses, which makes them look less funny. Also,
+         * doesn't use a static buffer. */
+
+        sprintf(buffer, "%02x:%02x:%02x:%02x:%02x:%02x",
+                addr->ether_addr_octet[0],
+                addr->ether_addr_octet[1],
+                addr->ether_addr_octet[2],
+                addr->ether_addr_octet[3],
+                addr->ether_addr_octet[4],
+                addr->ether_addr_octet[5]);
+
+        return buffer;
+}
+
+int ether_addr_compare(const struct ether_addr *a, const struct ether_addr *b) {
+        return memcmp(a, b, ETH_ALEN);
+}
+
+static void ether_addr_hash_func(const struct ether_addr *p, struct siphash *state) {
+        siphash24_compress(p, sizeof(struct ether_addr), state);
+}
+
+DEFINE_HASH_OPS(ether_addr_hash_ops, struct ether_addr, ether_addr_hash_func, ether_addr_compare);
+
+int ether_addr_from_string(const char *s, struct ether_addr *ret) {
+        size_t pos = 0, n, field;
+        char sep = '\0';
+        const char *hex = HEXDIGITS, *hexoff;
+        size_t x;
+        bool touched;
+
+#define parse_fields(v)                                         \
+        for (field = 0; field < ELEMENTSOF(v); field++) {       \
+                touched = false;                                \
+                for (n = 0; n < (2 * sizeof(v[0])); n++) {      \
+                        if (s[pos] == '\0')                     \
+                                break;                          \
+                        hexoff = strchr(hex, s[pos]);           \
+                        if (!hexoff)                            \
+                                break;                          \
+                        assert(hexoff >= hex);                  \
+                        x = hexoff - hex;                       \
+                        if (x >= 16)                            \
+                                x -= 6; /* A-F */               \
+                        assert(x < 16);                         \
+                        touched = true;                         \
+                        v[field] <<= 4;                         \
+                        v[field] += x;                          \
+                        pos++;                                  \
+                }                                               \
+                if (!touched)                                   \
+                        return -EINVAL;                         \
+                if (field < (ELEMENTSOF(v)-1)) {                \
+                        if (s[pos] != sep)                      \
+                                return -EINVAL;                 \
+                        else                                    \
+                                pos++;                          \
+                }                                               \
+        }
+
+        assert(s);
+        assert(ret);
+
+        s += strspn(s, WHITESPACE);
+        sep = s[strspn(s, hex)];
+
+        if (sep == '.') {
+                uint16_t shorts[3] = { 0 };
+
+                parse_fields(shorts);
+
+                if (s[pos] != '\0')
+                        return -EINVAL;
+
+                for (n = 0; n < ELEMENTSOF(shorts); n++) {
+                        ret->ether_addr_octet[2*n] = ((shorts[n] & (uint16_t)0xff00) >> 8);
+                        ret->ether_addr_octet[2*n + 1] = (shorts[n] & (uint16_t)0x00ff);
+                }
+
+        } else if (IN_SET(sep, ':', '-')) {
+                struct ether_addr out = ETHER_ADDR_NULL;
+
+                parse_fields(out.ether_addr_octet);
+
+                if (s[pos] != '\0')
+                        return -EINVAL;
+
+                for (n = 0; n < ELEMENTSOF(out.ether_addr_octet); n++)
+                        ret->ether_addr_octet[n] = out.ether_addr_octet[n];
+
+        } else
+                return -EINVAL;
+
+        return 0;
+}
diff --git a/shared/systemd/src/basic/ether-addr-util.h b/shared/systemd/src/basic/ether-addr-util.h
new file mode 100644
index 00000000..4e44b30b
--- /dev/null
+++ b/shared/systemd/src/basic/ether-addr-util.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <net/ethernet.h>
+#include <stdbool.h>
+
+#include "hash-funcs.h"
+
+#define ETHER_ADDR_FORMAT_STR "%02X%02X%02X%02X%02X%02X"
+#define ETHER_ADDR_FORMAT_VAL(x) (x).ether_addr_octet[0], (x).ether_addr_octet[1], (x).ether_addr_octet[2], (x).ether_addr_octet[3], (x).ether_addr_octet[4], (x).ether_addr_octet[5]
+
+#define ETHER_ADDR_TO_STRING_MAX (3*6)
+char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]);
+
+int ether_addr_compare(const struct ether_addr *a, const struct ether_addr *b);
+static inline bool ether_addr_equal(const struct ether_addr *a, const struct ether_addr *b) {
+        return ether_addr_compare(a, b) == 0;
+}
+
+#define ETHER_ADDR_NULL ((const struct ether_addr){})
+
+static inline bool ether_addr_is_null(const struct ether_addr *addr) {
+        return ether_addr_equal(addr, &ETHER_ADDR_NULL);
+}
+
+int ether_addr_from_string(const char *s, struct ether_addr *ret);
+
+extern const struct hash_ops ether_addr_hash_ops;
diff --git a/shared/systemd/src/basic/extract-word.c b/shared/systemd/src/basic/extract-word.c
new file mode 100644
index 00000000..782c868b
--- /dev/null
+++ b/shared/systemd/src/basic/extract-word.c
@@ -0,0 +1,289 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "alloc-util.h"
+#include "escape.h"
+#include "extract-word.h"
+#include "log.h"
+#include "macro.h"
+#include "string-util.h"
+#include "utf8.h"
+
+int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) {
+        _cleanup_free_ char *s = NULL;
+        size_t allocated = 0, sz = 0;
+        char c;
+        int r;
+
+        char quote = 0;                 /* 0 or ' or " */
+        bool backslash = false;         /* whether we've just seen a backslash */
+
+        assert(p);
+        assert(ret);
+
+        /* Bail early if called after last value or with no input */
+        if (!*p)
+                goto finish;
+        c = **p;
+
+        if (!separators)
+                separators = WHITESPACE;
+
+        /* Parses the first word of a string, and returns it in
+         * *ret. Removes all quotes in the process. When parsing fails
+         * (because of an uneven number of quotes or similar), leaves
+         * the pointer *p at the first invalid character. */
+
+        if (flags & EXTRACT_DONT_COALESCE_SEPARATORS)
+                if (!GREEDY_REALLOC(s, allocated, sz+1))
+                        return -ENOMEM;
+
+        for (;; (*p)++, c = **p) {
+                if (c == 0)
+                        goto finish_force_terminate;
+                else if (strchr(separators, c)) {
+                        if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
+                                (*p)++;
+                                goto finish_force_next;
+                        }
+                } else {
+                        /* We found a non-blank character, so we will always
+                         * want to return a string (even if it is empty),
+                         * allocate it here. */
+                        if (!GREEDY_REALLOC(s, allocated, sz+1))
+                                return -ENOMEM;
+                        break;
+                }
+        }
+
+        for (;; (*p)++, c = **p) {
+                if (backslash) {
+                        if (!GREEDY_REALLOC(s, allocated, sz+7))
+                                return -ENOMEM;
+
+                        if (c == 0) {
+                                if ((flags & EXTRACT_CUNESCAPE_RELAX) &&
+                                    (!quote || flags & EXTRACT_RELAX)) {
+                                        /* If we find an unquoted trailing backslash and we're in
+                                         * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the
+                                         * output.
+                                         *
+                                         * Unbalanced quotes will only be allowed in EXTRACT_RELAX
+                                         * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them.
+                                         */
+                                        s[sz++] = '\\';
+                                        goto finish_force_terminate;
+                                }
+                                if (flags & EXTRACT_RELAX)
+                                        goto finish_force_terminate;
+                                return -EINVAL;
+                        }
+
+                        if (flags & EXTRACT_CUNESCAPE) {
+                                bool eight_bit = false;
+                                char32_t u;
+
+                                r = cunescape_one(*p, (size_t) -1, &u, &eight_bit);
+                                if (r < 0) {
+                                        if (flags & EXTRACT_CUNESCAPE_RELAX) {
+                                                s[sz++] = '\\';
+                                                s[sz++] = c;
+                                        } else
+                                                return -EINVAL;
+                                } else {
+                                        (*p) += r - 1;
+
+                                        if (eight_bit)
+                                                s[sz++] = u;
+                                        else
+                                                sz += utf8_encode_unichar(s + sz, u);
+                                }
+                        } else
+                                s[sz++] = c;
+
+                        backslash = false;
+
+                } else if (quote) {     /* inside either single or double quotes */
+                        for (;; (*p)++, c = **p) {
+                                if (c == 0) {
+                                        if (flags & EXTRACT_RELAX)
+                                                goto finish_force_terminate;
+                                        return -EINVAL;
+                                } else if (c == quote) {        /* found the end quote */
+                                        quote = 0;
+                                        break;
+                                } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
+                                        backslash = true;
+                                        break;
+                                } else {
+                                        if (!GREEDY_REALLOC(s, allocated, sz+2))
+                                                return -ENOMEM;
+
+                                        s[sz++] = c;
+                                }
+                        }
+
+                } else {
+                        for (;; (*p)++, c = **p) {
+                                if (c == 0)
+                                        goto finish_force_terminate;
+                                else if (IN_SET(c, '\'', '"') && (flags & EXTRACT_QUOTES)) {
+                                        quote = c;
+                                        break;
+                                } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
+                                        backslash = true;
+                                        break;
+                                } else if (strchr(separators, c)) {
+                                        if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
+                                                (*p)++;
+                                                goto finish_force_next;
+                                        }
+                                        /* Skip additional coalesced separators. */
+                                        for (;; (*p)++, c = **p) {
+                                                if (c == 0)
+                                                        goto finish_force_terminate;
+                                                if (!strchr(separators, c))
+                                                        break;
+                                        }
+                                        goto finish;
+
+                                } else {
+                                        if (!GREEDY_REALLOC(s, allocated, sz+2))
+                                                return -ENOMEM;
+
+                                        s[sz++] = c;
+                                }
+                        }
+                }
+        }
+
+finish_force_terminate:
+        *p = NULL;
+finish:
+        if (!s) {
+                *p = NULL;
+                *ret = NULL;
+                return 0;
+        }
+
+finish_force_next:
+        s[sz] = 0;
+        *ret = TAKE_PTR(s);
+
+        return 1;
+}
+
+#if 0 /* NM_IGNORED */
+int extract_first_word_and_warn(
+                const char **p,
+                char **ret,
+                const char *separators,
+                ExtractFlags flags,
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *rvalue) {
+
+        /* Try to unquote it, if it fails, warn about it and try again
+         * but this time using EXTRACT_CUNESCAPE_RELAX to keep the
+         * backslashes verbatim in invalid escape sequences. */
+
+        const char *save;
+        int r;
+
+        save = *p;
+        r = extract_first_word(p, ret, separators, flags);
+        if (r >= 0)
+                return r;
+
+        if (r == -EINVAL && !(flags & EXTRACT_CUNESCAPE_RELAX)) {
+
+                /* Retry it with EXTRACT_CUNESCAPE_RELAX. */
+                *p = save;
+                r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX);
+                if (r >= 0) {
+                        /* It worked this time, hence it must have been an invalid escape sequence. */
+                        log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Ignoring unknown escape sequences: \"%s\"", *ret);
+                        return r;
+                }
+
+                /* If it's still EINVAL; then it must be unbalanced quoting, report this. */
+                if (r == -EINVAL)
+                        return log_syntax(unit, LOG_ERR, filename, line, r, "Unbalanced quoting, ignoring: \"%s\"", rvalue);
+        }
+
+        /* Can be any error, report it */
+        return log_syntax(unit, LOG_ERR, filename, line, r, "Unable to decode word \"%s\", ignoring: %m", rvalue);
+}
+
+/* We pass ExtractFlags as unsigned int (to avoid undefined behaviour when passing
+ * an object that undergoes default argument promotion as an argument to va_start).
+ * Let's make sure that ExtractFlags fits into an unsigned int. */
+assert_cc(sizeof(enum ExtractFlags) <= sizeof(unsigned));
+
+int extract_many_words(const char **p, const char *separators, unsigned flags, ...) {
+        va_list ap;
+        char **l;
+        int n = 0, i, c, r;
+
+        /* Parses a number of words from a string, stripping any
+         * quotes if necessary. */
+
+        assert(p);
+
+        /* Count how many words are expected */
+        va_start(ap, flags);
+        for (;;) {
+                if (!va_arg(ap, char **))
+                        break;
+                n++;
+        }
+        va_end(ap);
+
+        if (n <= 0)
+                return 0;
+
+        /* Read all words into a temporary array */
+        l = newa0(char*, n);
+        for (c = 0; c < n; c++) {
+
+                r = extract_first_word(p, &l[c], separators, flags);
+                if (r < 0) {
+                        int j;
+
+                        for (j = 0; j < c; j++)
+                                free(l[j]);
+
+                        return r;
+                }
+
+                if (r == 0)
+                        break;
+        }
+
+        /* If we managed to parse all words, return them in the passed
+         * in parameters */
+        va_start(ap, flags);
+        for (i = 0; i < n; i++) {
+                char **v;
+
+                v = va_arg(ap, char **);
+                assert(v);
+
+                *v = l[i];
+        }
+        va_end(ap);
+
+        return c;
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/extract-word.h b/shared/systemd/src/basic/extract-word.h
new file mode 100644
index 00000000..705ebbe9
--- /dev/null
+++ b/shared/systemd/src/basic/extract-word.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "macro.h"
+
+typedef enum ExtractFlags {
+        EXTRACT_RELAX                    = 1 << 0,
+        EXTRACT_CUNESCAPE                = 1 << 1,
+        EXTRACT_CUNESCAPE_RELAX          = 1 << 2,
+        EXTRACT_QUOTES                   = 1 << 3,
+        EXTRACT_DONT_COALESCE_SEPARATORS = 1 << 4,
+        EXTRACT_RETAIN_ESCAPE            = 1 << 5,
+} ExtractFlags;
+
+int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags);
+int extract_first_word_and_warn(const char **p, char **ret, const char *separators, ExtractFlags flags, const char *unit, const char *filename, unsigned line, const char *rvalue);
+int extract_many_words(const char **p, const char *separators, unsigned flags, ...) _sentinel_;
diff --git a/shared/systemd/src/basic/fd-util.c b/shared/systemd/src/basic/fd-util.c
new file mode 100644
index 00000000..0cc0c6b5
--- /dev/null
+++ b/shared/systemd/src/basic/fd-util.c
@@ -0,0 +1,975 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "copy.h"
+#include "dirent-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "io-util.h"
+#include "macro.h"
+#include "memfd-util.h"
+#include "missing.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "socket-util.h"
+#include "stdio-util.h"
+#include "util.h"
+#include "tmpfile-util.h"
+
+int close_nointr(int fd) {
+        assert(fd >= 0);
+
+        if (close(fd) >= 0)
+                return 0;
+
+        /*
+         * Just ignore EINTR; a retry loop is the wrong thing to do on
+         * Linux.
+         *
+         * http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html
+         * https://bugzilla.gnome.org/show_bug.cgi?id=682819
+         * http://utcc.utoronto.ca/~cks/space/blog/unix/CloseEINTR
+         * https://sites.google.com/site/michaelsafyan/software-engineering/checkforeintrwheninvokingclosethinkagain
+         */
+        if (errno == EINTR)
+                return 0;
+
+        return -errno;
+}
+
+int safe_close(int fd) {
+
+        /*
+         * Like close_nointr() but cannot fail. Guarantees errno is
+         * unchanged. Is a NOP with negative fds passed, and returns
+         * -1, so that it can be used in this syntax:
+         *
+         * fd = safe_close(fd);
+         */
+
+        if (fd >= 0) {
+                PROTECT_ERRNO;
+
+                /* The kernel might return pretty much any error code
+                 * via close(), but the fd will be closed anyway. The
+                 * only condition we want to check for here is whether
+                 * the fd was invalid at all... */
+
+                assert_se(close_nointr(fd) != -EBADF);
+        }
+
+        return -1;
+}
+
+void safe_close_pair(int p[static 2]) {
+        assert(p);
+
+        if (p[0] == p[1]) {
+                /* Special case pairs which use the same fd in both
+                 * directions... */
+                p[0] = p[1] = safe_close(p[0]);
+                return;
+        }
+
+        p[0] = safe_close(p[0]);
+        p[1] = safe_close(p[1]);
+}
+
+void close_many(const int fds[], size_t n_fd) {
+        size_t i;
+
+        assert(fds || n_fd <= 0);
+
+        for (i = 0; i < n_fd; i++)
+                safe_close(fds[i]);
+}
+
+int fclose_nointr(FILE *f) {
+        assert(f);
+
+        /* Same as close_nointr(), but for fclose() */
+
+        if (fclose(f) == 0)
+                return 0;
+
+        if (errno == EINTR)
+                return 0;
+
+        return -errno;
+}
+
+FILE* safe_fclose(FILE *f) {
+
+        /* Same as safe_close(), but for fclose() */
+
+        if (f) {
+                PROTECT_ERRNO;
+
+                assert_se(fclose_nointr(f) != -EBADF);
+        }
+
+        return NULL;
+}
+
+DIR* safe_closedir(DIR *d) {
+
+        if (d) {
+                PROTECT_ERRNO;
+
+                assert_se(closedir(d) >= 0 || errno != EBADF);
+        }
+
+        return NULL;
+}
+
+int fd_nonblock(int fd, bool nonblock) {
+        int flags, nflags;
+
+        assert(fd >= 0);
+
+        flags = fcntl(fd, F_GETFL, 0);
+        if (flags < 0)
+                return -errno;
+
+        if (nonblock)
+                nflags = flags | O_NONBLOCK;
+        else
+                nflags = flags & ~O_NONBLOCK;
+
+        if (nflags == flags)
+                return 0;
+
+        if (fcntl(fd, F_SETFL, nflags) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int fd_cloexec(int fd, bool cloexec) {
+        int flags, nflags;
+
+        assert(fd >= 0);
+
+        flags = fcntl(fd, F_GETFD, 0);
+        if (flags < 0)
+                return -errno;
+
+        if (cloexec)
+                nflags = flags | FD_CLOEXEC;
+        else
+                nflags = flags & ~FD_CLOEXEC;
+
+        if (nflags == flags)
+                return 0;
+
+        if (fcntl(fd, F_SETFD, nflags) < 0)
+                return -errno;
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+_pure_ static bool fd_in_set(int fd, const int fdset[], size_t n_fdset) {
+        size_t i;
+
+        assert(n_fdset == 0 || fdset);
+
+        for (i = 0; i < n_fdset; i++)
+                if (fdset[i] == fd)
+                        return true;
+
+        return false;
+}
+
+static int get_max_fd(void) {
+        struct rlimit rl;
+        rlim_t m;
+
+        /* Return the highest possible fd, based RLIMIT_NOFILE, but enforcing FD_SETSIZE-1 as lower boundary
+         * and INT_MAX as upper boundary. */
+
+        if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
+                return -errno;
+
+        m = MAX(rl.rlim_cur, rl.rlim_max);
+        if (m < FD_SETSIZE) /* Let's always cover at least 1024 fds */
+                return FD_SETSIZE-1;
+
+        if (m == RLIM_INFINITY || m > INT_MAX) /* Saturate on overflow. After all fds are "int", hence can
+                                                * never be above INT_MAX */
+                return INT_MAX;
+
+        return (int) (m - 1);
+}
+
+int close_all_fds(const int except[], size_t n_except) {
+        _cleanup_closedir_ DIR *d = NULL;
+        struct dirent *de;
+        int r = 0;
+
+        assert(n_except == 0 || except);
+
+        d = opendir("/proc/self/fd");
+        if (!d) {
+                int fd, max_fd;
+
+                /* When /proc isn't available (for example in chroots) the fallback is brute forcing through
+                 * the fd table */
+
+                max_fd = get_max_fd();
+                if (max_fd < 0)
+                        return max_fd;
+
+                for (fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -1) {
+                        int q;
+
+                        if (fd_in_set(fd, except, n_except))
+                                continue;
+
+                        q = close_nointr(fd);
+                        if (q < 0 && q != -EBADF && r >= 0)
+                                r = q;
+                }
+
+                return r;
+        }
+
+        FOREACH_DIRENT(de, d, return -errno) {
+                int fd = -1, q;
+
+                if (safe_atoi(de->d_name, &fd) < 0)
+                        /* Let's better ignore this, just in case */
+                        continue;
+
+                if (fd < 3)
+                        continue;
+
+                if (fd == dirfd(d))
+                        continue;
+
+                if (fd_in_set(fd, except, n_except))
+                        continue;
+
+                q = close_nointr(fd);
+                if (q < 0 && q != -EBADF && r >= 0) /* Valgrind has its own FD and doesn't want to have it closed */
+                        r = q;
+        }
+
+        return r;
+}
+
+int same_fd(int a, int b) {
+        struct stat sta, stb;
+        pid_t pid;
+        int r, fa, fb;
+
+        assert(a >= 0);
+        assert(b >= 0);
+
+        /* Compares two file descriptors. Note that semantics are
+         * quite different depending on whether we have kcmp() or we
+         * don't. If we have kcmp() this will only return true for
+         * dup()ed file descriptors, but not otherwise. If we don't
+         * have kcmp() this will also return true for two fds of the same
+         * file, created by separate open() calls. Since we use this
+         * call mostly for filtering out duplicates in the fd store
+         * this difference hopefully doesn't matter too much. */
+
+        if (a == b)
+                return true;
+
+        /* Try to use kcmp() if we have it. */
+        pid = getpid_cached();
+        r = kcmp(pid, pid, KCMP_FILE, a, b);
+        if (r == 0)
+                return true;
+        if (r > 0)
+                return false;
+        if (!IN_SET(errno, ENOSYS, EACCES, EPERM))
+                return -errno;
+
+        /* We don't have kcmp(), use fstat() instead. */
+        if (fstat(a, &sta) < 0)
+                return -errno;
+
+        if (fstat(b, &stb) < 0)
+                return -errno;
+
+        if ((sta.st_mode & S_IFMT) != (stb.st_mode & S_IFMT))
+                return false;
+
+        /* We consider all device fds different, since two device fds
+         * might refer to quite different device contexts even though
+         * they share the same inode and backing dev_t. */
+
+        if (S_ISCHR(sta.st_mode) || S_ISBLK(sta.st_mode))
+                return false;
+
+        if (sta.st_dev != stb.st_dev || sta.st_ino != stb.st_ino)
+                return false;
+
+        /* The fds refer to the same inode on disk, let's also check
+         * if they have the same fd flags. This is useful to
+         * distinguish the read and write side of a pipe created with
+         * pipe(). */
+        fa = fcntl(a, F_GETFL);
+        if (fa < 0)
+                return -errno;
+
+        fb = fcntl(b, F_GETFL);
+        if (fb < 0)
+                return -errno;
+
+        return fa == fb;
+}
+#endif /* NM_IGNORED */
+
+void cmsg_close_all(struct msghdr *mh) {
+        struct cmsghdr *cmsg;
+
+        assert(mh);
+
+        CMSG_FOREACH(cmsg, mh)
+                if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
+                        close_many((int*) CMSG_DATA(cmsg), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int));
+}
+
+bool fdname_is_valid(const char *s) {
+        const char *p;
+
+        /* Validates a name for $LISTEN_FDNAMES. We basically allow
+         * everything ASCII that's not a control character. Also, as
+         * special exception the ":" character is not allowed, as we
+         * use that as field separator in $LISTEN_FDNAMES.
+         *
+         * Note that the empty string is explicitly allowed
+         * here. However, we limit the length of the names to 255
+         * characters. */
+
+        if (!s)
+                return false;
+
+        for (p = s; *p; p++) {
+                if (*p < ' ')
+                        return false;
+                if (*p >= 127)
+                        return false;
+                if (*p == ':')
+                        return false;
+        }
+
+        return p - s < 256;
+}
+
+int fd_get_path(int fd, char **ret) {
+        char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+        int r;
+
+        xsprintf(procfs_path, "/proc/self/fd/%i", fd);
+        r = readlink_malloc(procfs_path, ret);
+        if (r == -ENOENT) {
+                /* ENOENT can mean two things: that the fd does not exist or that /proc is not mounted. Let's make
+                 * things debuggable and distinguish the two. */
+
+                if (access("/proc/self/fd/", F_OK) < 0)
+                        /* /proc is not available or not set up properly, we're most likely in some chroot
+                         * environment. */
+                        return errno == ENOENT ? -EOPNOTSUPP : -errno;
+
+                return -EBADF; /* The directory exists, hence it's the fd that doesn't. */
+        }
+
+        return r;
+}
+
+#if 0 /* NM_IGNORED */
+int move_fd(int from, int to, int cloexec) {
+        int r;
+
+        /* Move fd 'from' to 'to', make sure FD_CLOEXEC remains equal if requested, and release the old fd. If
+         * 'cloexec' is passed as -1, the original FD_CLOEXEC is inherited for the new fd. If it is 0, it is turned
+         * off, if it is > 0 it is turned on. */
+
+        if (from < 0)
+                return -EBADF;
+        if (to < 0)
+                return -EBADF;
+
+        if (from == to) {
+
+                if (cloexec >= 0) {
+                        r = fd_cloexec(to, cloexec);
+                        if (r < 0)
+                                return r;
+                }
+
+                return to;
+        }
+
+        if (cloexec < 0) {
+                int fl;
+
+                fl = fcntl(from, F_GETFD, 0);
+                if (fl < 0)
+                        return -errno;
+
+                cloexec = !!(fl & FD_CLOEXEC);
+        }
+
+        r = dup3(from, to, cloexec ? O_CLOEXEC : 0);
+        if (r < 0)
+                return -errno;
+
+        assert(r == to);
+
+        safe_close(from);
+
+        return to;
+}
+
+int acquire_data_fd(const void *data, size_t size, unsigned flags) {
+
+        _cleanup_close_pair_ int pipefds[2] = { -1, -1 };
+        char pattern[] = "/dev/shm/data-fd-XXXXXX";
+        _cleanup_close_ int fd = -1;
+        int isz = 0, r;
+        ssize_t n;
+        off_t f;
+
+        assert(data || size == 0);
+
+        /* Acquire a read-only file descriptor that when read from returns the specified data. This is much more
+         * complex than I wish it was. But here's why:
+         *
+         * a) First we try to use memfds. They are the best option, as we can seal them nicely to make them
+         *    read-only. Unfortunately they require kernel 3.17, and – at the time of writing – we still support 3.14.
+         *
+         * b) Then, we try classic pipes. They are the second best options, as we can close the writing side, retaining
+         *    a nicely read-only fd in the reading side. However, they are by default quite small, and unprivileged
+         *    clients can only bump their size to a system-wide limit, which might be quite low.
+         *
+         * c) Then, we try an O_TMPFILE file in /dev/shm (that dir is the only suitable one known to exist from
+         *    earliest boot on). To make it read-only we open the fd a second time with O_RDONLY via
+         *    /proc/self/<fd>. Unfortunately O_TMPFILE is not available on older kernels on tmpfs.
+         *
+         * d) Finally, we try creating a regular file in /dev/shm, which we then delete.
+         *
+         * It sucks a bit that depending on the situation we return very different objects here, but that's Linux I
+         * figure. */
+
+        if (size == 0 && ((flags & ACQUIRE_NO_DEV_NULL) == 0)) {
+                /* As a special case, return /dev/null if we have been called for an empty data block */
+                r = open("/dev/null", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+                if (r < 0)
+                        return -errno;
+
+                return r;
+        }
+
+        if ((flags & ACQUIRE_NO_MEMFD) == 0) {
+                fd = memfd_new("data-fd");
+                if (fd < 0)
+                        goto try_pipe;
+
+                n = write(fd, data, size);
+                if (n < 0)
+                        return -errno;
+                if ((size_t) n != size)
+                        return -EIO;
+
+                f = lseek(fd, 0, SEEK_SET);
+                if (f != 0)
+                        return -errno;
+
+                r = memfd_set_sealed(fd);
+                if (r < 0)
+                        return r;
+
+                return TAKE_FD(fd);
+        }
+
+try_pipe:
+        if ((flags & ACQUIRE_NO_PIPE) == 0) {
+                if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0)
+                        return -errno;
+
+                isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
+                if (isz < 0)
+                        return -errno;
+
+                if ((size_t) isz < size) {
+                        isz = (int) size;
+                        if (isz < 0 || (size_t) isz != size)
+                                return -E2BIG;
+
+                        /* Try to bump the pipe size */
+                        (void) fcntl(pipefds[1], F_SETPIPE_SZ, isz);
+
+                        /* See if that worked */
+                        isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
+                        if (isz < 0)
+                                return -errno;
+
+                        if ((size_t) isz < size)
+                                goto try_dev_shm;
+                }
+
+                n = write(pipefds[1], data, size);
+                if (n < 0)
+                        return -errno;
+                if ((size_t) n != size)
+                        return -EIO;
+
+                (void) fd_nonblock(pipefds[0], false);
+
+                return TAKE_FD(pipefds[0]);
+        }
+
+try_dev_shm:
+        if ((flags & ACQUIRE_NO_TMPFILE) == 0) {
+                fd = open("/dev/shm", O_RDWR|O_TMPFILE|O_CLOEXEC, 0500);
+                if (fd < 0)
+                        goto try_dev_shm_without_o_tmpfile;
+
+                n = write(fd, data, size);
+                if (n < 0)
+                        return -errno;
+                if ((size_t) n != size)
+                        return -EIO;
+
+                /* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */
+                return fd_reopen(fd, O_RDONLY|O_CLOEXEC);
+        }
+
+try_dev_shm_without_o_tmpfile:
+        if ((flags & ACQUIRE_NO_REGULAR) == 0) {
+                fd = mkostemp_safe(pattern);
+                if (fd < 0)
+                        return fd;
+
+                n = write(fd, data, size);
+                if (n < 0) {
+                        r = -errno;
+                        goto unlink_and_return;
+                }
+                if ((size_t) n != size) {
+                        r = -EIO;
+                        goto unlink_and_return;
+                }
+
+                /* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */
+                r = open(pattern, O_RDONLY|O_CLOEXEC);
+                if (r < 0)
+                        r = -errno;
+
+        unlink_and_return:
+                (void) unlink(pattern);
+                return r;
+        }
+
+        return -EOPNOTSUPP;
+}
+
+/* When the data is smaller or equal to 64K, try to place the copy in a memfd/pipe */
+#define DATA_FD_MEMORY_LIMIT (64U*1024U)
+
+/* If memfd/pipe didn't work out, then let's use a file in /tmp up to a size of 1M. If it's large than that use /var/tmp instead. */
+#define DATA_FD_TMP_LIMIT (1024U*1024U)
+
+int fd_duplicate_data_fd(int fd) {
+
+        _cleanup_close_ int copy_fd = -1, tmp_fd = -1;
+        _cleanup_free_ void *remains = NULL;
+        size_t remains_size = 0;
+        const char *td;
+        struct stat st;
+        int r;
+
+        /* Creates a 'data' fd from the specified source fd, containing all the same data in a read-only fashion, but
+         * independent of it (i.e. the source fd can be closed and unmounted after this call succeeded). Tries to be
+         * somewhat smart about where to place the data. In the best case uses a memfd(). If memfd() are not supported
+         * uses a pipe instead. For larger data will use an unlinked file in /tmp, and for even larger data one in
+         * /var/tmp. */
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        /* For now, let's only accept regular files, sockets, pipes and char devices */
+        if (S_ISDIR(st.st_mode))
+                return -EISDIR;
+        if (S_ISLNK(st.st_mode))
+                return -ELOOP;
+        if (!S_ISREG(st.st_mode) && !S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode) && !S_ISCHR(st.st_mode))
+                return -EBADFD;
+
+        /* If we have reason to believe the data is bounded in size, then let's use memfds or pipes as backing fd. Note
+         * that we use the reported regular file size only as a hint, given that there are plenty special files in
+         * /proc and /sys which report a zero file size but can be read from. */
+
+        if (!S_ISREG(st.st_mode) || st.st_size < DATA_FD_MEMORY_LIMIT) {
+
+                /* Try a memfd first */
+                copy_fd = memfd_new("data-fd");
+                if (copy_fd >= 0) {
+                        off_t f;
+
+                        r = copy_bytes(fd, copy_fd, DATA_FD_MEMORY_LIMIT, 0);
+                        if (r < 0)
+                                return r;
+
+                        f = lseek(copy_fd, 0, SEEK_SET);
+                        if (f != 0)
+                                return -errno;
+
+                        if (r == 0) {
+                                /* Did it fit into the limit? If so, we are done. */
+                                r = memfd_set_sealed(copy_fd);
+                                if (r < 0)
+                                        return r;
+
+                                return TAKE_FD(copy_fd);
+                        }
+
+                        /* Hmm, pity, this didn't fit. Let's fall back to /tmp then, see below */
+
+                } else {
+                        _cleanup_(close_pairp) int pipefds[2] = { -1, -1 };
+                        int isz;
+
+                        /* If memfds aren't available, use a pipe. Set O_NONBLOCK so that we will get EAGAIN rather
+                         * then block indefinitely when we hit the pipe size limit */
+
+                        if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0)
+                                return -errno;
+
+                        isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
+                        if (isz < 0)
+                                return -errno;
+
+                        /* Try to enlarge the pipe size if necessary */
+                        if ((size_t) isz < DATA_FD_MEMORY_LIMIT) {
+
+                                (void) fcntl(pipefds[1], F_SETPIPE_SZ, DATA_FD_MEMORY_LIMIT);
+
+                                isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
+                                if (isz < 0)
+                                        return -errno;
+                        }
+
+                        if ((size_t) isz >= DATA_FD_MEMORY_LIMIT) {
+
+                                r = copy_bytes_full(fd, pipefds[1], DATA_FD_MEMORY_LIMIT, 0, &remains, &remains_size, NULL, NULL);
+                                if (r < 0 && r != -EAGAIN)
+                                        return r; /* If we get EAGAIN it could be because of the source or because of
+                                                   * the destination fd, we can't know, as sendfile() and friends won't
+                                                   * tell us. Hence, treat this as reason to fall back, just to be
+                                                   * sure. */
+                                if (r == 0) {
+                                        /* Everything fit in, yay! */
+                                        (void) fd_nonblock(pipefds[0], false);
+
+                                        return TAKE_FD(pipefds[0]);
+                                }
+
+                                /* Things didn't fit in. But we read data into the pipe, let's remember that, so that
+                                 * when writing the new file we incorporate this first. */
+                                copy_fd = TAKE_FD(pipefds[0]);
+                        }
+                }
+        }
+
+        /* If we have reason to believe this will fit fine in /tmp, then use that as first fallback. */
+        if ((!S_ISREG(st.st_mode) || st.st_size < DATA_FD_TMP_LIMIT) &&
+            (DATA_FD_MEMORY_LIMIT + remains_size) < DATA_FD_TMP_LIMIT) {
+                off_t f;
+
+                tmp_fd = open_tmpfile_unlinkable(NULL /* NULL as directory means /tmp */, O_RDWR|O_CLOEXEC);
+                if (tmp_fd < 0)
+                        return tmp_fd;
+
+                if (copy_fd >= 0) {
+                        /* If we tried a memfd/pipe first and it ended up being too large, then copy this into the
+                         * temporary file first. */
+
+                        r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, 0);
+                        if (r < 0)
+                                return r;
+
+                        assert(r == 0);
+                }
+
+                if (remains_size > 0) {
+                        /* If there were remaining bytes (i.e. read into memory, but not written out yet) from the
+                         * failed copy operation, let's flush them out next. */
+
+                        r = loop_write(tmp_fd, remains, remains_size, false);
+                        if (r < 0)
+                                return r;
+                }
+
+                r = copy_bytes(fd, tmp_fd, DATA_FD_TMP_LIMIT - DATA_FD_MEMORY_LIMIT - remains_size, COPY_REFLINK);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        goto finish;  /* Yay, it fit in */
+
+                /* It didn't fit in. Let's not forget to use what we already used */
+                f = lseek(tmp_fd, 0, SEEK_SET);
+                if (f != 0)
+                        return -errno;
+
+                safe_close(copy_fd);
+                copy_fd = TAKE_FD(tmp_fd);
+
+                remains = mfree(remains);
+                remains_size = 0;
+        }
+
+        /* As last fallback use /var/tmp */
+        r = var_tmp_dir(&td);
+        if (r < 0)
+                return r;
+
+        tmp_fd = open_tmpfile_unlinkable(td, O_RDWR|O_CLOEXEC);
+        if (tmp_fd < 0)
+                return tmp_fd;
+
+        if (copy_fd >= 0) {
+                /* If we tried a memfd/pipe first, or a file in /tmp, and it ended up being too large, than copy this
+                 * into the temporary file first. */
+                r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, COPY_REFLINK);
+                if (r < 0)
+                        return r;
+
+                assert(r == 0);
+        }
+
+        if (remains_size > 0) {
+                /* Then, copy in any read but not yet written bytes. */
+                r = loop_write(tmp_fd, remains, remains_size, false);
+                if (r < 0)
+                        return r;
+        }
+
+        /* Copy in the rest */
+        r = copy_bytes(fd, tmp_fd, UINT64_MAX, COPY_REFLINK);
+        if (r < 0)
+                return r;
+
+        assert(r == 0);
+
+finish:
+        /* Now convert the O_RDWR file descriptor into an O_RDONLY one (and as side effect seek to the beginning of the
+         * file again */
+
+        return fd_reopen(tmp_fd, O_RDONLY|O_CLOEXEC);
+}
+#endif /* NM_IGNORED */
+
+int fd_move_above_stdio(int fd) {
+        int flags, copy;
+        PROTECT_ERRNO;
+
+        /* Moves the specified file descriptor if possible out of the range [0…2], i.e. the range of
+         * stdin/stdout/stderr. If it can't be moved outside of this range the original file descriptor is
+         * returned. This call is supposed to be used for long-lasting file descriptors we allocate in our code that
+         * might get loaded into foreign code, and where we want ensure our fds are unlikely used accidentally as
+         * stdin/stdout/stderr of unrelated code.
+         *
+         * Note that this doesn't fix any real bugs, it just makes it less likely that our code will be affected by
+         * buggy code from others that mindlessly invokes 'fprintf(stderr, …' or similar in places where stderr has
+         * been closed before.
+         *
+         * This function is written in a "best-effort" and "least-impact" style. This means whenever we encounter an
+         * error we simply return the original file descriptor, and we do not touch errno. */
+
+        if (fd < 0 || fd > 2)
+                return fd;
+
+        flags = fcntl(fd, F_GETFD, 0);
+        if (flags < 0)
+                return fd;
+
+        if (flags & FD_CLOEXEC)
+                copy = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+        else
+                copy = fcntl(fd, F_DUPFD, 3);
+        if (copy < 0)
+                return fd;
+
+        assert(copy > 2);
+
+        (void) close(fd);
+        return copy;
+}
+
+#if 0 /* NM_IGNORED */
+int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd) {
+
+        int fd[3] = { /* Put together an array of fds we work on */
+                original_input_fd,
+                original_output_fd,
+                original_error_fd
+        };
+
+        int r, i,
+                null_fd = -1,                /* if we open /dev/null, we store the fd to it here */
+                copy_fd[3] = { -1, -1, -1 }; /* This contains all fds we duplicate here temporarily, and hence need to close at the end */
+        bool null_readable, null_writable;
+
+        /* Sets up stdin, stdout, stderr with the three file descriptors passed in. If any of the descriptors is
+         * specified as -1 it will be connected with /dev/null instead. If any of the file descriptors is passed as
+         * itself (e.g. stdin as STDIN_FILENO) it is left unmodified, but the O_CLOEXEC bit is turned off should it be
+         * on.
+         *
+         * Note that if any of the passed file descriptors are > 2 they will be closed — both on success and on
+         * failure! Thus, callers should assume that when this function returns the input fds are invalidated.
+         *
+         * Note that when this function fails stdin/stdout/stderr might remain half set up!
+         *
+         * O_CLOEXEC is turned off for all three file descriptors (which is how it should be for
+         * stdin/stdout/stderr). */
+
+        null_readable = original_input_fd < 0;
+        null_writable = original_output_fd < 0 || original_error_fd < 0;
+
+        /* First step, open /dev/null once, if we need it */
+        if (null_readable || null_writable) {
+
+                /* Let's open this with O_CLOEXEC first, and convert it to non-O_CLOEXEC when we move the fd to the final position. */
+                null_fd = open("/dev/null", (null_readable && null_writable ? O_RDWR :
+                                             null_readable ? O_RDONLY : O_WRONLY) | O_CLOEXEC);
+                if (null_fd < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                /* If this fd is in the 0…2 range, let's move it out of it */
+                if (null_fd < 3) {
+                        int copy;
+
+                        copy = fcntl(null_fd, F_DUPFD_CLOEXEC, 3); /* Duplicate this with O_CLOEXEC set */
+                        if (copy < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        safe_close(null_fd);
+                        null_fd = copy;
+                }
+        }
+
+        /* Let's assemble fd[] with the fds to install in place of stdin/stdout/stderr */
+        for (i = 0; i < 3; i++) {
+
+                if (fd[i] < 0)
+                        fd[i] = null_fd;        /* A negative parameter means: connect this one to /dev/null */
+                else if (fd[i] != i && fd[i] < 3) {
+                        /* This fd is in the 0…2 territory, but not at its intended place, move it out of there, so that we can work there. */
+                        copy_fd[i] = fcntl(fd[i], F_DUPFD_CLOEXEC, 3); /* Duplicate this with O_CLOEXEC set */
+                        if (copy_fd[i] < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        fd[i] = copy_fd[i];
+                }
+        }
+
+        /* At this point we now have the fds to use in fd[], and they are all above the stdio range, so that we
+         * have freedom to move them around. If the fds already were at the right places then the specific fds are
+         * -1. Let's now move them to the right places. This is the point of no return. */
+        for (i = 0; i < 3; i++) {
+
+                if (fd[i] == i) {
+
+                        /* fd is already in place, but let's make sure O_CLOEXEC is off */
+                        r = fd_cloexec(i, false);
+                        if (r < 0)
+                                goto finish;
+
+                } else {
+                        assert(fd[i] > 2);
+
+                        if (dup2(fd[i], i) < 0) { /* Turns off O_CLOEXEC on the new fd. */
+                                r = -errno;
+                                goto finish;
+                        }
+                }
+        }
+
+        r = 0;
+
+finish:
+        /* Close the original fds, but only if they were outside of the stdio range. Also, properly check for the same
+         * fd passed in multiple times. */
+        safe_close_above_stdio(original_input_fd);
+        if (original_output_fd != original_input_fd)
+                safe_close_above_stdio(original_output_fd);
+        if (original_error_fd != original_input_fd && original_error_fd != original_output_fd)
+                safe_close_above_stdio(original_error_fd);
+
+        /* Close the copies we moved > 2 */
+        for (i = 0; i < 3; i++)
+                safe_close(copy_fd[i]);
+
+        /* Close our null fd, if it's > 2 */
+        safe_close_above_stdio(null_fd);
+
+        return r;
+}
+
+int fd_reopen(int fd, int flags) {
+        char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+        int new_fd;
+
+        /* Reopens the specified fd with new flags. This is useful for convert an O_PATH fd into a regular one, or to
+         * turn O_RDWR fds into O_RDONLY fds.
+         *
+         * This doesn't work on sockets (since they cannot be open()ed, ever).
+         *
+         * This implicitly resets the file read index to 0. */
+
+        xsprintf(procfs_path, "/proc/self/fd/%i", fd);
+        new_fd = open(procfs_path, flags);
+        if (new_fd < 0)
+                return -errno;
+
+        return new_fd;
+}
+
+int read_nr_open(void) {
+        _cleanup_free_ char *nr_open = NULL;
+        int r;
+
+        /* Returns the kernel's current fd limit, either by reading it of /proc/sys if that works, or using the
+         * hard-coded default compiled-in value of current kernels (1M) if not. This call will never fail. */
+
+        r = read_one_line_file("/proc/sys/fs/nr_open", &nr_open);
+        if (r < 0)
+                log_debug_errno(r, "Failed to read /proc/sys/fs/nr_open, ignoring: %m");
+        else {
+                int v;
+
+                r = safe_atoi(nr_open, &v);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse /proc/sys/fs/nr_open value '%s', ignoring: %m", nr_open);
+                else
+                        return v;
+        }
+
+        /* If we fail, fallback to the hard-coded kernel limit of 1024 * 1024. */
+        return 1024 * 1024;
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/fd-util.h b/shared/systemd/src/basic/fd-util.h
new file mode 100644
index 00000000..4085a244
--- /dev/null
+++ b/shared/systemd/src/basic/fd-util.h
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <dirent.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/socket.h>
+
+#include "macro.h"
+
+/* Make sure we can distinguish fd 0 and NULL */
+#define FD_TO_PTR(fd) INT_TO_PTR((fd)+1)
+#define PTR_TO_FD(p) (PTR_TO_INT(p)-1)
+
+int close_nointr(int fd);
+int safe_close(int fd);
+void safe_close_pair(int p[static 2]);
+
+static inline int safe_close_above_stdio(int fd) {
+        if (fd < 3) /* Don't close stdin/stdout/stderr, but still invalidate the fd by returning -1 */
+                return -1;
+
+        return safe_close(fd);
+}
+
+void close_many(const int fds[], size_t n_fd);
+
+int fclose_nointr(FILE *f);
+FILE* safe_fclose(FILE *f);
+DIR* safe_closedir(DIR *f);
+
+static inline void closep(int *fd) {
+        safe_close(*fd);
+}
+
+static inline void close_pairp(int (*p)[2]) {
+        safe_close_pair(*p);
+}
+
+static inline void fclosep(FILE **f) {
+        safe_fclose(*f);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, pclose);
+DEFINE_TRIVIAL_CLEANUP_FUNC(DIR*, closedir);
+
+#define _cleanup_close_ _cleanup_(closep)
+#define _cleanup_fclose_ _cleanup_(fclosep)
+#define _cleanup_pclose_ _cleanup_(pclosep)
+#define _cleanup_closedir_ _cleanup_(closedirp)
+#define _cleanup_close_pair_ _cleanup_(close_pairp)
+
+int fd_nonblock(int fd, bool nonblock);
+int fd_cloexec(int fd, bool cloexec);
+
+int close_all_fds(const int except[], size_t n_except);
+
+int same_fd(int a, int b);
+
+void cmsg_close_all(struct msghdr *mh);
+
+bool fdname_is_valid(const char *s);
+
+int fd_get_path(int fd, char **ret);
+
+int move_fd(int from, int to, int cloexec);
+
+enum {
+        ACQUIRE_NO_DEV_NULL = 1 << 0,
+        ACQUIRE_NO_MEMFD    = 1 << 1,
+        ACQUIRE_NO_PIPE     = 1 << 2,
+        ACQUIRE_NO_TMPFILE  = 1 << 3,
+        ACQUIRE_NO_REGULAR  = 1 << 4,
+};
+
+int acquire_data_fd(const void *data, size_t size, unsigned flags);
+
+int fd_duplicate_data_fd(int fd);
+
+/* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */
+/* The kernel sends e.g., EHOSTUNREACH or ENONET to userspace in some ICMP error cases.
+ * See the icmp_err_convert[] in net/ipv4/icmp.c in the kernel sources */
+#define ERRNO_IS_DISCONNECT(r)                                          \
+        IN_SET(r,                                                       \
+               ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, \
+               ENETUNREACH, EHOSTUNREACH, ENOPROTOOPT, EHOSTDOWN, ENONET)
+
+/* Resource exhaustion, could be our fault or general system trouble */
+#define ERRNO_IS_RESOURCE(r) \
+        IN_SET(r, ENOMEM, EMFILE, ENFILE)
+
+int fd_move_above_stdio(int fd);
+
+int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd);
+
+static inline int make_null_stdio(void) {
+        return rearrange_stdio(-1, -1, -1);
+}
+
+/* Like TAKE_PTR() but for file descriptors, resetting them to -1 */
+#define TAKE_FD(fd)                             \
+        ({                                      \
+                int _fd_ = (fd);                \
+                (fd) = -1;                      \
+                _fd_;                           \
+        })
+
+int fd_reopen(int fd, int flags);
+
+int read_nr_open(void);
diff --git a/shared/systemd/src/basic/fileio.c b/shared/systemd/src/basic/fileio.c
new file mode 100644
index 00000000..ee66190f
--- /dev/null
+++ b/shared/systemd/src/basic/fileio.c
@@ -0,0 +1,832 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "tmpfile-util.h"
+
+#define READ_FULL_BYTES_MAX (4U*1024U*1024U)
+
+#if 0 /* NM_IGNORED */
+int write_string_stream_ts(
+                FILE *f,
+                const char *line,
+                WriteStringFileFlags flags,
+                struct timespec *ts) {
+
+        bool needs_nl;
+        int r;
+
+        assert(f);
+        assert(line);
+
+        if (ferror(f))
+                return -EIO;
+
+        needs_nl = !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) && !endswith(line, "\n");
+
+        if (needs_nl && (flags & WRITE_STRING_FILE_DISABLE_BUFFER)) {
+                /* If STDIO buffering was disabled, then let's append the newline character to the string itself, so
+                 * that the write goes out in one go, instead of two */
+
+                line = strjoina(line, "\n");
+                needs_nl = false;
+        }
+
+        if (fputs(line, f) == EOF)
+                return -errno;
+
+        if (needs_nl)
+                if (fputc('\n', f) == EOF)
+                        return -errno;
+
+        if (flags & WRITE_STRING_FILE_SYNC)
+                r = fflush_sync_and_check(f);
+        else
+                r = fflush_and_check(f);
+        if (r < 0)
+                return r;
+
+        if (ts) {
+                struct timespec twice[2] = {*ts, *ts};
+
+                if (futimens(fileno(f), twice) < 0)
+                        return -errno;
+        }
+
+        return 0;
+}
+
+static int write_string_file_atomic(
+                const char *fn,
+                const char *line,
+                WriteStringFileFlags flags,
+                struct timespec *ts) {
+
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *p = NULL;
+        int r;
+
+        assert(fn);
+        assert(line);
+
+        r = fopen_temporary(fn, &f, &p);
+        if (r < 0)
+                return r;
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+        (void) fchmod_umask(fileno(f), 0644);
+
+        r = write_string_stream_ts(f, line, flags, ts);
+        if (r < 0)
+                goto fail;
+
+        if (rename(p, fn) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        return 0;
+
+fail:
+        (void) unlink(p);
+        return r;
+}
+
+int write_string_file_ts(
+                const char *fn,
+                const char *line,
+                WriteStringFileFlags flags,
+                struct timespec *ts) {
+
+        _cleanup_fclose_ FILE *f = NULL;
+        int q, r;
+
+        assert(fn);
+        assert(line);
+
+        /* We don't know how to verify whether the file contents was already on-disk. */
+        assert(!((flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE) && (flags & WRITE_STRING_FILE_SYNC)));
+
+        if (flags & WRITE_STRING_FILE_ATOMIC) {
+                assert(flags & WRITE_STRING_FILE_CREATE);
+
+                r = write_string_file_atomic(fn, line, flags, ts);
+                if (r < 0)
+                        goto fail;
+
+                return r;
+        } else
+                assert(!ts);
+
+        if (flags & WRITE_STRING_FILE_CREATE) {
+                f = fopen(fn, "we");
+                if (!f) {
+                        r = -errno;
+                        goto fail;
+                }
+        } else {
+                int fd;
+
+                /* We manually build our own version of fopen(..., "we") that
+                 * works without O_CREAT */
+                fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY | ((flags & WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0));
+                if (fd < 0) {
+                        r = -errno;
+                        goto fail;
+                }
+
+                f = fdopen(fd, "w");
+                if (!f) {
+                        r = -errno;
+                        safe_close(fd);
+                        goto fail;
+                }
+        }
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        if (flags & WRITE_STRING_FILE_DISABLE_BUFFER)
+                setvbuf(f, NULL, _IONBF, 0);
+
+        r = write_string_stream_ts(f, line, flags, ts);
+        if (r < 0)
+                goto fail;
+
+        return 0;
+
+fail:
+        if (!(flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE))
+                return r;
+
+        f = safe_fclose(f);
+
+        /* OK, the operation failed, but let's see if the right
+         * contents in place already. If so, eat up the error. */
+
+        q = verify_file(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE));
+        if (q <= 0)
+                return r;
+
+        return 0;
+}
+
+int write_string_filef(
+                const char *fn,
+                WriteStringFileFlags flags,
+                const char *format, ...) {
+
+        _cleanup_free_ char *p = NULL;
+        va_list ap;
+        int r;
+
+        va_start(ap, format);
+        r = vasprintf(&p, format, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return -ENOMEM;
+
+        return write_string_file(fn, p, flags);
+}
+
+int read_one_line_file(const char *fn, char **line) {
+        _cleanup_fclose_ FILE *f = NULL;
+
+        assert(fn);
+        assert(line);
+
+        f = fopen(fn, "re");
+        if (!f)
+                return -errno;
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        return read_line(f, LONG_LINE_MAX, line);
+}
+
+int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *buf = NULL;
+        size_t l, k;
+
+        assert(fn);
+        assert(blob);
+
+        l = strlen(blob);
+
+        if (accept_extra_nl && endswith(blob, "\n"))
+                accept_extra_nl = false;
+
+        buf = malloc(l + accept_extra_nl + 1);
+        if (!buf)
+                return -ENOMEM;
+
+        f = fopen(fn, "re");
+        if (!f)
+                return -errno;
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        /* We try to read one byte more than we need, so that we know whether we hit eof */
+        errno = 0;
+        k = fread(buf, 1, l + accept_extra_nl + 1, f);
+        if (ferror(f))
+                return errno > 0 ? -errno : -EIO;
+
+        if (k != l && k != l + accept_extra_nl)
+                return 0;
+        if (memcmp(buf, blob, l) != 0)
+                return 0;
+        if (k > l && buf[l] != '\n')
+                return 0;
+
+        return 1;
+}
+#endif /* NM_IGNORED */
+
+int read_full_stream(
+                FILE *f,
+                char **ret_contents,
+                size_t *ret_size) {
+
+        _cleanup_free_ char *buf = NULL;
+        struct stat st;
+        size_t n, l;
+        int fd;
+
+        assert(f);
+        assert(ret_contents);
+
+        n = LINE_MAX; /* Start size */
+
+        fd = fileno(f);
+        if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see fmemopen(), let's
+                        * optimize our buffering) */
+
+                if (fstat(fileno(f), &st) < 0)
+                        return -errno;
+
+                if (S_ISREG(st.st_mode)) {
+
+                        /* Safety check */
+                        if (st.st_size > READ_FULL_BYTES_MAX)
+                                return -E2BIG;
+
+                        /* Start with the right file size, but be prepared for files from /proc which generally report a file
+                         * size of 0. Note that we increase the size to read here by one, so that the first read attempt
+                         * already makes us notice the EOF. */
+                        if (st.st_size > 0)
+                                n = st.st_size + 1;
+                }
+        }
+
+        l = 0;
+        for (;;) {
+                char *t;
+                size_t k;
+
+                t = realloc(buf, n + 1);
+                if (!t)
+                        return -ENOMEM;
+
+                buf = t;
+                errno = 0;
+                k = fread(buf + l, 1, n - l, f);
+                if (k > 0)
+                        l += k;
+
+                if (ferror(f))
+                        return errno > 0 ? -errno : -EIO;
+
+                if (feof(f))
+                        break;
+
+                /* We aren't expecting fread() to return a short read outside
+                 * of (error && eof), assert buffer is full and enlarge buffer.
+                 */
+                assert(l == n);
+
+                /* Safety check */
+                if (n >= READ_FULL_BYTES_MAX)
+                        return -E2BIG;
+
+                n = MIN(n * 2, READ_FULL_BYTES_MAX);
+        }
+
+        if (!ret_size) {
+                /* Safety check: if the caller doesn't want to know the size of what we just read it will rely on the
+                 * trailing NUL byte. But if there's an embedded NUL byte, then we should refuse operation as otherwise
+                 * there'd be ambiguity about what we just read. */
+
+                if (memchr(buf, 0, l))
+                        return -EBADMSG;
+        }
+
+        buf[l] = 0;
+        *ret_contents = TAKE_PTR(buf);
+
+        if (ret_size)
+                *ret_size = l;
+
+        return 0;
+}
+
+int read_full_file(const char *fn, char **contents, size_t *size) {
+        _cleanup_fclose_ FILE *f = NULL;
+
+        assert(fn);
+        assert(contents);
+
+        f = fopen(fn, "re");
+        if (!f)
+                return -errno;
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        return read_full_stream(f, contents, size);
+}
+
+#if 0 /* NM_IGNORED */
+int executable_is_script(const char *path, char **interpreter) {
+        _cleanup_free_ char *line = NULL;
+        size_t len;
+        char *ans;
+        int r;
+
+        assert(path);
+
+        r = read_one_line_file(path, &line);
+        if (r == -ENOBUFS) /* First line overly long? if so, then it's not a script */
+                return 0;
+        if (r < 0)
+                return r;
+
+        if (!startswith(line, "#!"))
+                return 0;
+
+        ans = strstrip(line + 2);
+        len = strcspn(ans, " \t");
+
+        if (len == 0)
+                return 0;
+
+        ans = strndup(ans, len);
+        if (!ans)
+                return -ENOMEM;
+
+        *interpreter = ans;
+        return 1;
+}
+#endif /* NM_IGNORED */
+
+/**
+ * Retrieve one field from a file like /proc/self/status.  pattern
+ * should not include whitespace or the delimiter (':'). pattern matches only
+ * the beginning of a line. Whitespace before ':' is skipped. Whitespace and
+ * zeros after the ':' will be skipped. field must be freed afterwards.
+ * terminator specifies the terminating characters of the field value (not
+ * included in the value).
+ */
+int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field) {
+        _cleanup_free_ char *status = NULL;
+        char *t, *f;
+        size_t len;
+        int r;
+
+        assert(terminator);
+        assert(filename);
+        assert(pattern);
+        assert(field);
+
+        r = read_full_file(filename, &status, NULL);
+        if (r < 0)
+                return r;
+
+        t = status;
+
+        do {
+                bool pattern_ok;
+
+                do {
+                        t = strstr(t, pattern);
+                        if (!t)
+                                return -ENOENT;
+
+                        /* Check that pattern occurs in beginning of line. */
+                        pattern_ok = (t == status || t[-1] == '\n');
+
+                        t += strlen(pattern);
+
+                } while (!pattern_ok);
+
+                t += strspn(t, " \t");
+                if (!*t)
+                        return -ENOENT;
+
+        } while (*t != ':');
+
+        t++;
+
+        if (*t) {
+                t += strspn(t, " \t");
+
+                /* Also skip zeros, because when this is used for
+                 * capabilities, we don't want the zeros. This way the
+                 * same capability set always maps to the same string,
+                 * irrespective of the total capability set size. For
+                 * other numbers it shouldn't matter. */
+                t += strspn(t, "0");
+                /* Back off one char if there's nothing but whitespace
+                   and zeros */
+                if (!*t || isspace(*t))
+                        t--;
+        }
+
+        len = strcspn(t, terminator);
+
+        f = strndup(t, len);
+        if (!f)
+                return -ENOMEM;
+
+        *field = f;
+        return 0;
+}
+
+DIR *xopendirat(int fd, const char *name, int flags) {
+        int nfd;
+        DIR *d;
+
+        assert(!(flags & O_CREAT));
+
+        nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags, 0);
+        if (nfd < 0)
+                return NULL;
+
+        d = fdopendir(nfd);
+        if (!d) {
+                safe_close(nfd);
+                return NULL;
+        }
+
+        return d;
+}
+
+#if 0 /* NM_IGNORED */
+static int search_and_fopen_internal(const char *path, const char *mode, const char *root, char **search, FILE **_f) {
+        char **i;
+
+        assert(path);
+        assert(mode);
+        assert(_f);
+
+        if (!path_strv_resolve_uniq(search, root))
+                return -ENOMEM;
+
+        STRV_FOREACH(i, search) {
+                _cleanup_free_ char *p = NULL;
+                FILE *f;
+
+                if (root)
+                        p = strjoin(root, *i, "/", path);
+                else
+                        p = strjoin(*i, "/", path);
+                if (!p)
+                        return -ENOMEM;
+
+                f = fopen(p, mode);
+                if (f) {
+                        *_f = f;
+                        return 0;
+                }
+
+                if (errno != ENOENT)
+                        return -errno;
+        }
+
+        return -ENOENT;
+}
+
+int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f) {
+        _cleanup_strv_free_ char **copy = NULL;
+
+        assert(path);
+        assert(mode);
+        assert(_f);
+
+        if (path_is_absolute(path)) {
+                FILE *f;
+
+                f = fopen(path, mode);
+                if (f) {
+                        *_f = f;
+                        return 0;
+                }
+
+                return -errno;
+        }
+
+        copy = strv_copy((char**) search);
+        if (!copy)
+                return -ENOMEM;
+
+        return search_and_fopen_internal(path, mode, root, copy, _f);
+}
+
+int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f) {
+        _cleanup_strv_free_ char **s = NULL;
+
+        if (path_is_absolute(path)) {
+                FILE *f;
+
+                f = fopen(path, mode);
+                if (f) {
+                        *_f = f;
+                        return 0;
+                }
+
+                return -errno;
+        }
+
+        s = strv_split_nulstr(search);
+        if (!s)
+                return -ENOMEM;
+
+        return search_and_fopen_internal(path, mode, root, s, _f);
+}
+#endif /* NM_IGNORED */
+
+int fflush_and_check(FILE *f) {
+        assert(f);
+
+        errno = 0;
+        fflush(f);
+
+        if (ferror(f))
+                return errno > 0 ? -errno : -EIO;
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+int fflush_sync_and_check(FILE *f) {
+        int r;
+
+        assert(f);
+
+        r = fflush_and_check(f);
+        if (r < 0)
+                return r;
+
+        if (fsync(fileno(f)) < 0)
+                return -errno;
+
+        r = fsync_directory_of_file(fileno(f));
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+int write_timestamp_file_atomic(const char *fn, usec_t n) {
+        char ln[DECIMAL_STR_MAX(n)+2];
+
+        /* Creates a "timestamp" file, that contains nothing but a
+         * usec_t timestamp, formatted in ASCII. */
+
+        if (n <= 0 || n >= USEC_INFINITY)
+                return -ERANGE;
+
+        xsprintf(ln, USEC_FMT "\n", n);
+
+        return write_string_file(fn, ln, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
+}
+
+int read_timestamp_file(const char *fn, usec_t *ret) {
+        _cleanup_free_ char *ln = NULL;
+        uint64_t t;
+        int r;
+
+        r = read_one_line_file(fn, &ln);
+        if (r < 0)
+                return r;
+
+        r = safe_atou64(ln, &t);
+        if (r < 0)
+                return r;
+
+        if (t <= 0 || t >= (uint64_t) USEC_INFINITY)
+                return -ERANGE;
+
+        *ret = (usec_t) t;
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) {
+        int r;
+
+        assert(s);
+
+        /* Outputs the specified string with fputs(), but optionally prefixes it with a separator. The *space parameter
+         * when specified shall initially point to a boolean variable initialized to false. It is set to true after the
+         * first invocation. This call is supposed to be use in loops, where a separator shall be inserted between each
+         * element, but not before the first one. */
+
+        if (!f)
+                f = stdout;
+
+        if (space) {
+                if (!separator)
+                        separator = " ";
+
+                if (*space) {
+                        r = fputs(separator, f);
+                        if (r < 0)
+                                return r;
+                }
+
+                *space = true;
+        }
+
+        return fputs(s, f);
+}
+
+#if 0 /* NM_IGNORED */
+/* A bitmask of the EOL markers we know */
+typedef enum EndOfLineMarker {
+        EOL_NONE     = 0,
+        EOL_ZERO     = 1 << 0,  /* \0 (aka NUL) */
+        EOL_TEN      = 1 << 1,  /* \n (aka NL, aka LF)  */
+        EOL_THIRTEEN = 1 << 2,  /* \r (aka CR)  */
+} EndOfLineMarker;
+
+static EndOfLineMarker categorize_eol(char c, ReadLineFlags flags) {
+
+        if (!IN_SET(flags, READ_LINE_ONLY_NUL)) {
+                if (c == '\n')
+                        return EOL_TEN;
+                if (c == '\r')
+                        return EOL_THIRTEEN;
+        }
+
+        if (c == '\0')
+                return EOL_ZERO;
+
+        return EOL_NONE;
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, funlockfile);
+
+int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) {
+        size_t n = 0, allocated = 0, count = 0;
+        _cleanup_free_ char *buffer = NULL;
+        int r;
+
+        assert(f);
+
+        /* Something like a bounded version of getline().
+         *
+         * Considers EOF, \n, \r and \0 end of line delimiters (or combinations of these), and does not include these
+         * delimiters in the string returned. Specifically, recognizes the following combinations of markers as line
+         * endings:
+         *
+         *     • \n        (UNIX)
+         *     • \r        (old MacOS)
+         *     • \0        (C strings)
+         *     • \n\0
+         *     • \r\0
+         *     • \r\n      (Windows)
+         *     • \n\r
+         *     • \r\n\0
+         *     • \n\r\0
+         *
+         * Returns the number of bytes read from the files (i.e. including delimiters — this hence usually differs from
+         * the number of characters in the returned string). When EOF is hit, 0 is returned.
+         *
+         * The input parameter limit is the maximum numbers of characters in the returned string, i.e. excluding
+         * delimiters. If the limit is hit we fail and return -ENOBUFS.
+         *
+         * If a line shall be skipped ret may be initialized as NULL. */
+
+        if (ret) {
+                if (!GREEDY_REALLOC(buffer, allocated, 1))
+                        return -ENOMEM;
+        }
+
+        {
+                _unused_ _cleanup_(funlockfilep) FILE *flocked = f;
+                EndOfLineMarker previous_eol = EOL_NONE;
+                flockfile(f);
+
+                for (;;) {
+                        EndOfLineMarker eol;
+                        char c;
+
+                        if (n >= limit)
+                                return -ENOBUFS;
+
+                        if (count >= INT_MAX) /* We couldn't return the counter anymore as "int", hence refuse this */
+                                return -ENOBUFS;
+
+                        r = safe_fgetc(f, &c);
+                        if (r < 0)
+                                return r;
+                        if (r == 0) /* EOF is definitely EOL */
+                                break;
+
+                        eol = categorize_eol(c, flags);
+
+                        if (FLAGS_SET(previous_eol, EOL_ZERO) ||
+                            (eol == EOL_NONE && previous_eol != EOL_NONE) ||
+                            (eol != EOL_NONE && (previous_eol & eol) != 0)) {
+                                /* Previous char was a NUL? This is not an EOL, but the previous char was? This type of
+                                 * EOL marker has been seen right before?  In either of these three cases we are
+                                 * done. But first, let's put this character back in the queue. (Note that we have to
+                                 * cast this to (unsigned char) here as ungetc() expects a positive 'int', and if we
+                                 * are on an architecture where 'char' equals 'signed char' we need to ensure we don't
+                                 * pass a negative value here. That said, to complicate things further ungetc() is
+                                 * actually happy with most negative characters and implicitly casts them back to
+                                 * positive ones as needed, except for \xff (aka -1, aka EOF), which it refuses. What a
+                                 * godawful API!) */
+                                assert_se(ungetc((unsigned char) c, f) != EOF);
+                                break;
+                        }
+
+                        count++;
+
+                        if (eol != EOL_NONE) {
+                                previous_eol |= eol;
+                                continue;
+                        }
+
+                        if (ret) {
+                                if (!GREEDY_REALLOC(buffer, allocated, n + 2))
+                                        return -ENOMEM;
+
+                                buffer[n] = c;
+                        }
+
+                        n++;
+                }
+        }
+
+        if (ret) {
+                buffer[n] = 0;
+
+                *ret = TAKE_PTR(buffer);
+        }
+
+        return (int) count;
+}
+
+int safe_fgetc(FILE *f, char *ret) {
+        int k;
+
+        assert(f);
+
+        /* A safer version of plain fgetc(): let's propagate the error that happened while reading as such, and
+         * separate the EOF condition from the byte read, to avoid those confusion signed/unsigned issues fgetc()
+         * has. */
+
+        errno = 0;
+        k = fgetc(f);
+        if (k == EOF) {
+                if (ferror(f))
+                        return errno > 0 ? -errno : -EIO;
+
+                if (ret)
+                        *ret = 0;
+
+                return 0;
+        }
+
+        if (ret)
+                *ret = k;
+
+        return 1;
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/fileio.h b/shared/systemd/src/basic/fileio.h
new file mode 100644
index 00000000..53e3f4ef
--- /dev/null
+++ b/shared/systemd/src/basic/fileio.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <dirent.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "macro.h"
+#include "time-util.h"
+
+#define LONG_LINE_MAX (1U*1024U*1024U)
+
+typedef enum {
+        WRITE_STRING_FILE_CREATE            = 1 << 0,
+        WRITE_STRING_FILE_ATOMIC            = 1 << 1,
+        WRITE_STRING_FILE_AVOID_NEWLINE     = 1 << 2,
+        WRITE_STRING_FILE_VERIFY_ON_FAILURE = 1 << 3,
+        WRITE_STRING_FILE_SYNC              = 1 << 4,
+        WRITE_STRING_FILE_DISABLE_BUFFER    = 1 << 5,
+        WRITE_STRING_FILE_NOFOLLOW          = 1 << 6,
+
+        /* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one
+           more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file()
+           and friends. */
+
+} WriteStringFileFlags;
+
+int write_string_stream_ts(FILE *f, const char *line, WriteStringFileFlags flags, struct timespec *ts);
+static inline int write_string_stream(FILE *f, const char *line, WriteStringFileFlags flags) {
+        return write_string_stream_ts(f, line, flags, NULL);
+}
+int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, struct timespec *ts);
+static inline int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) {
+        return write_string_file_ts(fn, line, flags, NULL);
+}
+
+int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4);
+
+int read_one_line_file(const char *fn, char **line);
+int read_full_file(const char *fn, char **contents, size_t *size);
+int read_full_stream(FILE *f, char **contents, size_t *size);
+
+int verify_file(const char *fn, const char *blob, bool accept_extra_nl);
+
+int executable_is_script(const char *path, char **interpreter);
+
+int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field);
+
+DIR *xopendirat(int dirfd, const char *name, int flags);
+
+int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f);
+int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f);
+
+int fflush_and_check(FILE *f);
+int fflush_sync_and_check(FILE *f);
+
+int write_timestamp_file_atomic(const char *fn, usec_t n);
+int read_timestamp_file(const char *fn, usec_t *ret);
+
+int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space);
+
+typedef enum ReadLineFlags {
+        READ_LINE_ONLY_NUL = 1 << 0,
+} ReadLineFlags;
+
+int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret);
+
+static inline int read_line(FILE *f, size_t limit, char **ret) {
+        return read_line_full(f, limit, 0, ret);
+}
+
+static inline int read_nul_string(FILE *f, size_t limit, char **ret) {
+        return read_line_full(f, limit, READ_LINE_ONLY_NUL, ret);
+}
+
+int safe_fgetc(FILE *f, char *ret);
diff --git a/shared/systemd/src/basic/fs-util.c b/shared/systemd/src/basic/fs-util.c
new file mode 100644
index 00000000..4b234139
--- /dev/null
+++ b/shared/systemd/src/basic/fs-util.c
@@ -0,0 +1,1368 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <linux/magic.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "dirent-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "locale-util.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "mkdir.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "stat-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "time-util.h"
+#include "tmpfile-util.h"
+#include "user-util.h"
+#include "util.h"
+
+int unlink_noerrno(const char *path) {
+        PROTECT_ERRNO;
+        int r;
+
+        r = unlink(path);
+        if (r < 0)
+                return -errno;
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+int rmdir_parents(const char *path, const char *stop) {
+        size_t l;
+        int r = 0;
+
+        assert(path);
+        assert(stop);
+
+        l = strlen(path);
+
+        /* Skip trailing slashes */
+        while (l > 0 && path[l-1] == '/')
+                l--;
+
+        while (l > 0) {
+                char *t;
+
+                /* Skip last component */
+                while (l > 0 && path[l-1] != '/')
+                        l--;
+
+                /* Skip trailing slashes */
+                while (l > 0 && path[l-1] == '/')
+                        l--;
+
+                if (l <= 0)
+                        break;
+
+                t = strndup(path, l);
+                if (!t)
+                        return -ENOMEM;
+
+                if (path_startswith(stop, t)) {
+                        free(t);
+                        return 0;
+                }
+
+                r = rmdir(t);
+                free(t);
+
+                if (r < 0)
+                        if (errno != ENOENT)
+                                return -errno;
+        }
+
+        return 0;
+}
+
+int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
+        int r;
+
+        /* Try the ideal approach first */
+        if (renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) >= 0)
+                return 0;
+
+        /* renameat2() exists since Linux 3.15, btrfs and FAT added support for it later. If it is not implemented,
+         * fall back to a different method. */
+        if (!IN_SET(errno, EINVAL, ENOSYS, ENOTTY))
+                return -errno;
+
+        /* Let's try to use linkat()+unlinkat() as fallback. This doesn't work on directories and on some file systems
+         * that do not support hard links (such as FAT, most prominently), but for files it's pretty close to what we
+         * want — though not atomic (i.e. for a short period both the new and the old filename will exist). */
+        if (linkat(olddirfd, oldpath, newdirfd, newpath, 0) >= 0) {
+
+                if (unlinkat(olddirfd, oldpath, 0) < 0) {
+                        r = -errno; /* Backup errno before the following unlinkat() alters it */
+                        (void) unlinkat(newdirfd, newpath, 0);
+                        return r;
+                }
+
+                return 0;
+        }
+
+        if (!IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM)) /* FAT returns EPERM on link()… */
+                return -errno;
+
+        /* OK, neither RENAME_NOREPLACE nor linkat()+unlinkat() worked. Let's then fallback to the racy TOCTOU
+         * vulnerable accessat(F_OK) check followed by classic, replacing renameat(), we have nothing better. */
+
+        if (faccessat(newdirfd, newpath, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
+                return -EEXIST;
+        if (errno != ENOENT)
+                return -errno;
+
+        if (renameat(olddirfd, oldpath, newdirfd, newpath) < 0)
+                return -errno;
+
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+int readlinkat_malloc(int fd, const char *p, char **ret) {
+        size_t l = FILENAME_MAX+1;
+        int r;
+
+        assert(p);
+        assert(ret);
+
+        for (;;) {
+                char *c;
+                ssize_t n;
+
+                c = new(char, l);
+                if (!c)
+                        return -ENOMEM;
+
+                n = readlinkat(fd, p, c, l-1);
+                if (n < 0) {
+                        r = -errno;
+                        free(c);
+                        return r;
+                }
+
+                if ((size_t) n < l-1) {
+                        c[n] = 0;
+                        *ret = c;
+                        return 0;
+                }
+
+                free(c);
+                l *= 2;
+        }
+}
+
+int readlink_malloc(const char *p, char **ret) {
+        return readlinkat_malloc(AT_FDCWD, p, ret);
+}
+
+#if 0 /* NM_IGNORED */
+int readlink_value(const char *p, char **ret) {
+        _cleanup_free_ char *link = NULL;
+        char *value;
+        int r;
+
+        r = readlink_malloc(p, &link);
+        if (r < 0)
+                return r;
+
+        value = basename(link);
+        if (!value)
+                return -ENOENT;
+
+        value = strdup(value);
+        if (!value)
+                return -ENOMEM;
+
+        *ret = value;
+
+        return 0;
+}
+
+int readlink_and_make_absolute(const char *p, char **r) {
+        _cleanup_free_ char *target = NULL;
+        char *k;
+        int j;
+
+        assert(p);
+        assert(r);
+
+        j = readlink_malloc(p, &target);
+        if (j < 0)
+                return j;
+
+        k = file_in_same_dir(p, target);
+        if (!k)
+                return -ENOMEM;
+
+        *r = k;
+        return 0;
+}
+
+int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
+        char fd_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
+        _cleanup_close_ int fd = -1;
+        assert(path);
+
+        /* Under the assumption that we are running privileged we first change the access mode and only then hand out
+         * ownership to avoid a window where access is too open. */
+
+        fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW); /* Let's acquire an O_PATH fd, as precaution to change mode/owner
+                                                       * on the same file */
+        if (fd < 0)
+                return -errno;
+
+        xsprintf(fd_path, "/proc/self/fd/%i", fd);
+
+        if (mode != MODE_INVALID) {
+
+                if ((mode & S_IFMT) != 0) {
+                        struct stat st;
+
+                        if (stat(fd_path, &st) < 0)
+                                return -errno;
+
+                        if ((mode & S_IFMT) != (st.st_mode & S_IFMT))
+                                return -EINVAL;
+                }
+
+                if (chmod(fd_path, mode & 07777) < 0)
+                        return -errno;
+        }
+
+        if (uid != UID_INVALID || gid != GID_INVALID)
+                if (chown(fd_path, uid, gid) < 0)
+                        return -errno;
+
+        return 0;
+}
+
+int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid) {
+        /* Under the assumption that we are running privileged we first change the access mode and only then hand out
+         * ownership to avoid a window where access is too open. */
+
+        if (mode != MODE_INVALID) {
+
+                if ((mode & S_IFMT) != 0) {
+                        struct stat st;
+
+                        if (fstat(fd, &st) < 0)
+                                return -errno;
+
+                        if ((mode & S_IFMT) != (st.st_mode & S_IFMT))
+                                return -EINVAL;
+                }
+
+                if (fchmod(fd, mode & 0777) < 0)
+                        return -errno;
+        }
+
+        if (uid != UID_INVALID || gid != GID_INVALID)
+                if (fchown(fd, uid, gid) < 0)
+                        return -errno;
+
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+int fchmod_umask(int fd, mode_t m) {
+        mode_t u;
+        int r;
+
+        u = umask(0777);
+        r = fchmod(fd, m & (~u)) < 0 ? -errno : 0;
+        umask(u);
+
+        return r;
+}
+
+#if 0 /* NM_IGNORED */
+int fchmod_opath(int fd, mode_t m) {
+        char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+
+        /* This function operates also on fd that might have been opened with
+         * O_PATH. Indeed fchmodat() doesn't have the AT_EMPTY_PATH flag like
+         * fchownat() does. */
+
+        xsprintf(procfs_path, "/proc/self/fd/%i", fd);
+        if (chmod(procfs_path, m) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int fd_warn_permissions(const char *path, int fd) {
+        struct stat st;
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        if (st.st_mode & 0111)
+                log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path);
+
+        if (st.st_mode & 0002)
+                log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path);
+
+        if (getpid_cached() == 1 && (st.st_mode & 0044) != 0044)
+                log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path);
+
+        return 0;
+}
+
+int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
+        char fdpath[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+        _cleanup_close_ int fd = -1;
+        int r, ret = 0;
+
+        assert(path);
+
+        /* Note that touch_file() does not follow symlinks: if invoked on an existing symlink, then it is the symlink
+         * itself which is updated, not its target
+         *
+         * Returns the first error we encounter, but tries to apply as much as possible. */
+
+        if (parents)
+                (void) mkdir_parents(path, 0755);
+
+        /* Initially, we try to open the node with O_PATH, so that we get a reference to the node. This is useful in
+         * case the path refers to an existing device or socket node, as we can open it successfully in all cases, and
+         * won't trigger any driver magic or so. */
+        fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
+        if (fd < 0) {
+                if (errno != ENOENT)
+                        return -errno;
+
+                /* if the node doesn't exist yet, we create it, but with O_EXCL, so that we only create a regular file
+                 * here, and nothing else */
+                fd = open(path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, IN_SET(mode, 0, MODE_INVALID) ? 0644 : mode);
+                if (fd < 0)
+                        return -errno;
+        }
+
+        /* Let's make a path from the fd, and operate on that. With this logic, we can adjust the access mode,
+         * ownership and time of the file node in all cases, even if the fd refers to an O_PATH object — which is
+         * something fchown(), fchmod(), futimensat() don't allow. */
+        xsprintf(fdpath, "/proc/self/fd/%i", fd);
+
+        if (mode != MODE_INVALID)
+                if (chmod(fdpath, mode) < 0)
+                        ret = -errno;
+
+        if (uid_is_valid(uid) || gid_is_valid(gid))
+                if (chown(fdpath, uid, gid) < 0 && ret >= 0)
+                        ret = -errno;
+
+        if (stamp != USEC_INFINITY) {
+                struct timespec ts[2];
+
+                timespec_store(&ts[0], stamp);
+                ts[1] = ts[0];
+                r = utimensat(AT_FDCWD, fdpath, ts, 0);
+        } else
+                r = utimensat(AT_FDCWD, fdpath, NULL, 0);
+        if (r < 0 && ret >= 0)
+                return -errno;
+
+        return ret;
+}
+
+int touch(const char *path) {
+        return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID);
+}
+
+int symlink_idempotent(const char *from, const char *to, bool make_relative) {
+        _cleanup_free_ char *relpath = NULL;
+        int r;
+
+        assert(from);
+        assert(to);
+
+        if (make_relative) {
+                _cleanup_free_ char *parent = NULL;
+
+                parent = dirname_malloc(to);
+                if (!parent)
+                        return -ENOMEM;
+
+                r = path_make_relative(parent, from, &relpath);
+                if (r < 0)
+                        return r;
+
+                from = relpath;
+        }
+
+        if (symlink(from, to) < 0) {
+                _cleanup_free_ char *p = NULL;
+
+                if (errno != EEXIST)
+                        return -errno;
+
+                r = readlink_malloc(to, &p);
+                if (r == -EINVAL) /* Not a symlink? In that case return the original error we encountered: -EEXIST */
+                        return -EEXIST;
+                if (r < 0) /* Any other error? In that case propagate it as is */
+                        return r;
+
+                if (!streq(p, from)) /* Not the symlink we want it to be? In that case, propagate the original -EEXIST */
+                        return -EEXIST;
+        }
+
+        return 0;
+}
+
+int symlink_atomic(const char *from, const char *to) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        assert(from);
+        assert(to);
+
+        r = tempfn_random(to, NULL, &t);
+        if (r < 0)
+                return r;
+
+        if (symlink(from, t) < 0)
+                return -errno;
+
+        if (rename(t, to) < 0) {
+                unlink_noerrno(t);
+                return -errno;
+        }
+
+        return 0;
+}
+
+int mknod_atomic(const char *path, mode_t mode, dev_t dev) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        assert(path);
+
+        r = tempfn_random(path, NULL, &t);
+        if (r < 0)
+                return r;
+
+        if (mknod(t, mode, dev) < 0)
+                return -errno;
+
+        if (rename(t, path) < 0) {
+                unlink_noerrno(t);
+                return -errno;
+        }
+
+        return 0;
+}
+
+int mkfifo_atomic(const char *path, mode_t mode) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        assert(path);
+
+        r = tempfn_random(path, NULL, &t);
+        if (r < 0)
+                return r;
+
+        if (mkfifo(t, mode) < 0)
+                return -errno;
+
+        if (rename(t, path) < 0) {
+                unlink_noerrno(t);
+                return -errno;
+        }
+
+        return 0;
+}
+
+int mkfifoat_atomic(int dirfd, const char *path, mode_t mode) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        assert(path);
+
+        if (path_is_absolute(path))
+                return mkfifo_atomic(path, mode);
+
+        /* We're only interested in the (random) filename.  */
+        r = tempfn_random_child("", NULL, &t);
+        if (r < 0)
+                return r;
+
+        if (mkfifoat(dirfd, t, mode) < 0)
+                return -errno;
+
+        if (renameat(dirfd, t, dirfd, path) < 0) {
+                unlink_noerrno(t);
+                return -errno;
+        }
+
+        return 0;
+}
+
+int get_files_in_directory(const char *path, char ***list) {
+        _cleanup_closedir_ DIR *d = NULL;
+        struct dirent *de;
+        size_t bufsize = 0, n = 0;
+        _cleanup_strv_free_ char **l = NULL;
+
+        assert(path);
+
+        /* Returns all files in a directory in *list, and the number
+         * of files as return value. If list is NULL returns only the
+         * number. */
+
+        d = opendir(path);
+        if (!d)
+                return -errno;
+
+        FOREACH_DIRENT_ALL(de, d, return -errno) {
+                dirent_ensure_type(d, de);
+
+                if (!dirent_is_file(de))
+                        continue;
+
+                if (list) {
+                        /* one extra slot is needed for the terminating NULL */
+                        if (!GREEDY_REALLOC(l, bufsize, n + 2))
+                                return -ENOMEM;
+
+                        l[n] = strdup(de->d_name);
+                        if (!l[n])
+                                return -ENOMEM;
+
+                        l[++n] = NULL;
+                } else
+                        n++;
+        }
+
+        if (list)
+                *list = TAKE_PTR(l);
+
+        return n;
+}
+
+static int getenv_tmp_dir(const char **ret_path) {
+        const char *n;
+        int r, ret = 0;
+
+        assert(ret_path);
+
+        /* We use the same order of environment variables python uses in tempfile.gettempdir():
+         * https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir */
+        FOREACH_STRING(n, "TMPDIR", "TEMP", "TMP") {
+                const char *e;
+
+                e = secure_getenv(n);
+                if (!e)
+                        continue;
+                if (!path_is_absolute(e)) {
+                        r = -ENOTDIR;
+                        goto next;
+                }
+                if (!path_is_normalized(e)) {
+                        r = -EPERM;
+                        goto next;
+                }
+
+                r = is_dir(e, true);
+                if (r < 0)
+                        goto next;
+                if (r == 0) {
+                        r = -ENOTDIR;
+                        goto next;
+                }
+
+                *ret_path = e;
+                return 1;
+
+        next:
+                /* Remember first error, to make this more debuggable */
+                if (ret >= 0)
+                        ret = r;
+        }
+
+        if (ret < 0)
+                return ret;
+
+        *ret_path = NULL;
+        return ret;
+}
+
+static int tmp_dir_internal(const char *def, const char **ret) {
+        const char *e;
+        int r, k;
+
+        assert(def);
+        assert(ret);
+
+        r = getenv_tmp_dir(&e);
+        if (r > 0) {
+                *ret = e;
+                return 0;
+        }
+
+        k = is_dir(def, true);
+        if (k == 0)
+                k = -ENOTDIR;
+        if (k < 0)
+                return r < 0 ? r : k;
+
+        *ret = def;
+        return 0;
+}
+
+int var_tmp_dir(const char **ret) {
+
+        /* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus
+         * even might survive a boot: /var/tmp. If $TMPDIR (or related environment variables) are set, its value is
+         * returned preferably however. Note that both this function and tmp_dir() below are affected by $TMPDIR,
+         * making it a variable that overrides all temporary file storage locations. */
+
+        return tmp_dir_internal("/var/tmp", ret);
+}
+
+int tmp_dir(const char **ret) {
+
+        /* Similar to var_tmp_dir() above, but returns the location for "smaller" temporary files, which is usually
+         * backed by an in-memory file system: /tmp. */
+
+        return tmp_dir_internal("/tmp", ret);
+}
+
+int unlink_or_warn(const char *filename) {
+        if (unlink(filename) < 0 && errno != ENOENT)
+                /* If the file doesn't exist and the fs simply was read-only (in which
+                 * case unlink() returns EROFS even if the file doesn't exist), don't
+                 * complain */
+                if (errno != EROFS || access(filename, F_OK) >= 0)
+                        return log_error_errno(errno, "Failed to remove \"%s\": %m", filename);
+
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
+        char path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
+        int r;
+
+        /* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */
+        xsprintf(path, "/proc/self/fd/%i", what);
+
+        r = inotify_add_watch(fd, path, mask);
+        if (r < 0)
+                return -errno;
+
+        return r;
+}
+
+#if 0 /* NM_IGNORED */
+static bool unsafe_transition(const struct stat *a, const struct stat *b) {
+        /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to
+         * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files
+         * making us believe we read something safe even though it isn't safe in the specific context we open it in. */
+
+        if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */
+                return false;
+
+        return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */
+}
+
+static int log_unsafe_transition(int a, int b, const char *path, unsigned flags) {
+        _cleanup_free_ char *n1 = NULL, *n2 = NULL;
+
+        if (!FLAGS_SET(flags, CHASE_WARN))
+                return -ENOLINK;
+
+        (void) fd_get_path(a, &n1);
+        (void) fd_get_path(b, &n2);
+
+        return log_warning_errno(SYNTHETIC_ERRNO(ENOLINK),
+                                 "Detected unsafe path transition %s %s %s during canonicalization of %s.",
+                                 n1, special_glyph(SPECIAL_GLYPH_ARROW), n2, path);
+}
+
+static int log_autofs_mount_point(int fd, const char *path, unsigned flags) {
+        _cleanup_free_ char *n1 = NULL;
+
+        if (!FLAGS_SET(flags, CHASE_WARN))
+                return -EREMOTE;
+
+        (void) fd_get_path(fd, &n1);
+
+        return log_warning_errno(SYNTHETIC_ERRNO(EREMOTE),
+                                 "Detected autofs mount point %s during canonicalization of %s.",
+                                 n1, path);
+}
+
+int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret) {
+        _cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL;
+        _cleanup_close_ int fd = -1;
+        unsigned max_follow = CHASE_SYMLINKS_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
+        struct stat previous_stat;
+        bool exists = true;
+        char *todo;
+        int r;
+
+        assert(path);
+
+        /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
+        if (FLAGS_SET(flags, CHASE_NONEXISTENT | CHASE_OPEN))
+                return -EINVAL;
+
+        if (FLAGS_SET(flags, CHASE_STEP | CHASE_OPEN))
+                return -EINVAL;
+
+        if (isempty(path))
+                return -EINVAL;
+
+        /* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following
+         * symlinks relative to a root directory, instead of the root of the host.
+         *
+         * Note that "root" primarily matters if we encounter an absolute symlink. It is also used when following
+         * relative symlinks to ensure they cannot be used to "escape" the root directory. The path parameter passed is
+         * assumed to be already prefixed by it, except if the CHASE_PREFIX_ROOT flag is set, in which case it is first
+         * prefixed accordingly.
+         *
+         * Algorithmically this operates on two path buffers: "done" are the components of the path we already
+         * processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we still need to
+         * process. On each iteration, we move one component from "todo" to "done", processing it's special meaning
+         * each time. The "todo" path always starts with at least one slash, the "done" path always ends in no
+         * slash. We always keep an O_PATH fd to the component we are currently processing, thus keeping lookup races
+         * at a minimum.
+         *
+         * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute path you got
+         * as-is: fully qualified and relative to your host's root. Optionally, specify the root parameter to tell this
+         * function what to do when encountering a symlink with an absolute path as directory: prefix it by the
+         * specified path.
+         *
+         * There are three ways to invoke this function:
+         *
+         * 1. Without CHASE_STEP or CHASE_OPEN: in this case the path is resolved and the normalized path is returned
+         *    in `ret`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set 0 is returned if the file
+         *    doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set >= 0 is returned if the destination was
+         *    found, -ENOENT if it doesn't.
+         *
+         * 2. With CHASE_OPEN: in this case the destination is opened after chasing it as O_PATH and this file
+         *    descriptor is returned as return value. This is useful to open files relative to some root
+         *    directory. Note that the returned O_PATH file descriptors must be converted into a regular one (using
+         *    fd_reopen() or such) before it can be used for reading/writing. CHASE_OPEN may not be combined with
+         *    CHASE_NONEXISTENT.
+         *
+         * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only the first
+         *    symlink or ".." component of the path is resolved, and the resulting path is returned. This is useful if
+         *    a caller wants to trace the a path through the file system verbosely. Returns < 0 on error, > 0 if the
+         *    path is fully normalized, and == 0 for each normalization step. This may be combined with
+         *    CHASE_NONEXISTENT, in which case 1 is returned when a component is not found.
+         *
+         * 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions from
+         *    unprivileged to privileged files or directories. In such cases the return value is -ENOLINK. If
+         *    CHASE_WARN is also set a warning describing the unsafe transition is emitted.
+         *
+         * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, the path normalization is
+         *    aborted and -EREMOTE is returned. If CHASE_WARN is also set a warning showing the path of the mount point
+         *    is emitted.
+         *
+         * */
+
+        /* A root directory of "/" or "" is identical to none */
+        if (empty_or_root(original_root))
+                original_root = NULL;
+
+        if (!original_root && !ret && (flags & (CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_OPEN|CHASE_STEP)) == CHASE_OPEN) {
+                /* Shortcut the CHASE_OPEN case if the caller isn't interested in the actual path and has no root set
+                 * and doesn't care about any of the other special features we provide either. */
+                r = open(path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
+                if (r < 0)
+                        return -errno;
+
+                return r;
+        }
+
+        if (original_root) {
+                r = path_make_absolute_cwd(original_root, &root);
+                if (r < 0)
+                        return r;
+
+                if (flags & CHASE_PREFIX_ROOT) {
+
+                        /* We don't support relative paths in combination with a root directory */
+                        if (!path_is_absolute(path))
+                                return -EINVAL;
+
+                        path = prefix_roota(root, path);
+                }
+        }
+
+        r = path_make_absolute_cwd(path, &buffer);
+        if (r < 0)
+                return r;
+
+        fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
+        if (fd < 0)
+                return -errno;
+
+        if (flags & CHASE_SAFE) {
+                if (fstat(fd, &previous_stat) < 0)
+                        return -errno;
+        }
+
+        todo = buffer;
+        for (;;) {
+                _cleanup_free_ char *first = NULL;
+                _cleanup_close_ int child = -1;
+                struct stat st;
+                size_t n, m;
+
+                /* Determine length of first component in the path */
+                n = strspn(todo, "/");                  /* The slashes */
+                m = n + strcspn(todo + n, "/");         /* The entire length of the component */
+
+                /* Extract the first component. */
+                first = strndup(todo, m);
+                if (!first)
+                        return -ENOMEM;
+
+                todo += m;
+
+                /* Empty? Then we reached the end. */
+                if (isempty(first))
+                        break;
+
+                /* Just a single slash? Then we reached the end. */
+                if (path_equal(first, "/")) {
+                        /* Preserve the trailing slash */
+
+                        if (flags & CHASE_TRAIL_SLASH)
+                                if (!strextend(&done, "/", NULL))
+                                        return -ENOMEM;
+
+                        break;
+                }
+
+                /* Just a dot? Then let's eat this up. */
+                if (path_equal(first, "/."))
+                        continue;
+
+                /* Two dots? Then chop off the last bit of what we already found out. */
+                if (path_equal(first, "/..")) {
+                        _cleanup_free_ char *parent = NULL;
+                        _cleanup_close_ int fd_parent = -1;
+
+                        /* If we already are at the top, then going up will not change anything. This is in-line with
+                         * how the kernel handles this. */
+                        if (empty_or_root(done))
+                                continue;
+
+                        parent = dirname_malloc(done);
+                        if (!parent)
+                                return -ENOMEM;
+
+                        /* Don't allow this to leave the root dir.  */
+                        if (root &&
+                            path_startswith(done, root) &&
+                            !path_startswith(parent, root))
+                                continue;
+
+                        free_and_replace(done, parent);
+
+                        if (flags & CHASE_STEP)
+                                goto chased_one;
+
+                        fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH);
+                        if (fd_parent < 0)
+                                return -errno;
+
+                        if (flags & CHASE_SAFE) {
+                                if (fstat(fd_parent, &st) < 0)
+                                        return -errno;
+
+                                if (unsafe_transition(&previous_stat, &st))
+                                        return log_unsafe_transition(fd, fd_parent, path, flags);
+
+                                previous_stat = st;
+                        }
+
+                        safe_close(fd);
+                        fd = TAKE_FD(fd_parent);
+
+                        continue;
+                }
+
+                /* Otherwise let's see what this is. */
+                child = openat(fd, first + n, O_CLOEXEC|O_NOFOLLOW|O_PATH);
+                if (child < 0) {
+
+                        if (errno == ENOENT &&
+                            (flags & CHASE_NONEXISTENT) &&
+                            (isempty(todo) || path_is_normalized(todo))) {
+
+                                /* If CHASE_NONEXISTENT is set, and the path does not exist, then that's OK, return
+                                 * what we got so far. But don't allow this if the remaining path contains "../ or "./"
+                                 * or something else weird. */
+
+                                /* If done is "/", as first also contains slash at the head, then remove this redundant slash. */
+                                if (streq_ptr(done, "/"))
+                                        *done = '\0';
+
+                                if (!strextend(&done, first, todo, NULL))
+                                        return -ENOMEM;
+
+                                exists = false;
+                                break;
+                        }
+
+                        return -errno;
+                }
+
+                if (fstat(child, &st) < 0)
+                        return -errno;
+                if ((flags & CHASE_SAFE) &&
+                    unsafe_transition(&previous_stat, &st))
+                        return log_unsafe_transition(fd, child, path, flags);
+
+                previous_stat = st;
+
+                if ((flags & CHASE_NO_AUTOFS) &&
+                    fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
+                        return log_autofs_mount_point(child, path, flags);
+
+                if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
+                        char *joined;
+
+                        _cleanup_free_ char *destination = NULL;
+
+                        /* This is a symlink, in this case read the destination. But let's make sure we don't follow
+                         * symlinks without bounds. */
+                        if (--max_follow <= 0)
+                                return -ELOOP;
+
+                        r = readlinkat_malloc(fd, first + n, &destination);
+                        if (r < 0)
+                                return r;
+                        if (isempty(destination))
+                                return -EINVAL;
+
+                        if (path_is_absolute(destination)) {
+
+                                /* An absolute destination. Start the loop from the beginning, but use the root
+                                 * directory as base. */
+
+                                safe_close(fd);
+                                fd = open(root ?: "/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
+                                if (fd < 0)
+                                        return -errno;
+
+                                if (flags & CHASE_SAFE) {
+                                        if (fstat(fd, &st) < 0)
+                                                return -errno;
+
+                                        if (unsafe_transition(&previous_stat, &st))
+                                                return log_unsafe_transition(child, fd, path, flags);
+
+                                        previous_stat = st;
+                                }
+
+                                free(done);
+
+                                /* Note that we do not revalidate the root, we take it as is. */
+                                if (isempty(root))
+                                        done = NULL;
+                                else {
+                                        done = strdup(root);
+                                        if (!done)
+                                                return -ENOMEM;
+                                }
+
+                                /* Prefix what's left to do with what we just read, and start the loop again, but
+                                 * remain in the current directory. */
+                                joined = strjoin(destination, todo);
+                        } else
+                                joined = strjoin("/", destination, todo);
+                        if (!joined)
+                                return -ENOMEM;
+
+                        free(buffer);
+                        todo = buffer = joined;
+
+                        if (flags & CHASE_STEP)
+                                goto chased_one;
+
+                        continue;
+                }
+
+                /* If this is not a symlink, then let's just add the name we read to what we already verified. */
+                if (!done)
+                        done = TAKE_PTR(first);
+                else {
+                        /* If done is "/", as first also contains slash at the head, then remove this redundant slash. */
+                        if (streq(done, "/"))
+                                *done = '\0';
+
+                        if (!strextend(&done, first, NULL))
+                                return -ENOMEM;
+                }
+
+                /* And iterate again, but go one directory further down. */
+                safe_close(fd);
+                fd = TAKE_FD(child);
+        }
+
+        if (!done) {
+                /* Special case, turn the empty string into "/", to indicate the root directory. */
+                done = strdup("/");
+                if (!done)
+                        return -ENOMEM;
+        }
+
+        if (ret)
+                *ret = TAKE_PTR(done);
+
+        if (flags & CHASE_OPEN) {
+                /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a proper fd by
+                 * opening /proc/self/fd/xyz. */
+
+                assert(fd >= 0);
+                return TAKE_FD(fd);
+        }
+
+        if (flags & CHASE_STEP)
+                return 1;
+
+        return exists;
+
+chased_one:
+        if (ret) {
+                char *c;
+
+                c = strjoin(strempty(done), todo);
+                if (!c)
+                        return -ENOMEM;
+
+                *ret = c;
+        }
+
+        return 0;
+}
+
+int chase_symlinks_and_open(
+                const char *path,
+                const char *root,
+                unsigned chase_flags,
+                int open_flags,
+                char **ret_path) {
+
+        _cleanup_close_ int path_fd = -1;
+        _cleanup_free_ char *p = NULL;
+        int r;
+
+        if (chase_flags & CHASE_NONEXISTENT)
+                return -EINVAL;
+
+        if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) {
+                /* Shortcut this call if none of the special features of this call are requested */
+                r = open(path, open_flags);
+                if (r < 0)
+                        return -errno;
+
+                return r;
+        }
+
+        path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL);
+        if (path_fd < 0)
+                return path_fd;
+
+        r = fd_reopen(path_fd, open_flags);
+        if (r < 0)
+                return r;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(p);
+
+        return r;
+}
+
+int chase_symlinks_and_opendir(
+                const char *path,
+                const char *root,
+                unsigned chase_flags,
+                char **ret_path,
+                DIR **ret_dir) {
+
+        char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+        _cleanup_close_ int path_fd = -1;
+        _cleanup_free_ char *p = NULL;
+        DIR *d;
+
+        if (!ret_dir)
+                return -EINVAL;
+        if (chase_flags & CHASE_NONEXISTENT)
+                return -EINVAL;
+
+        if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) {
+                /* Shortcut this call if none of the special features of this call are requested */
+                d = opendir(path);
+                if (!d)
+                        return -errno;
+
+                *ret_dir = d;
+                return 0;
+        }
+
+        path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL);
+        if (path_fd < 0)
+                return path_fd;
+
+        xsprintf(procfs_path, "/proc/self/fd/%i", path_fd);
+        d = opendir(procfs_path);
+        if (!d)
+                return -errno;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(p);
+
+        *ret_dir = d;
+        return 0;
+}
+
+int chase_symlinks_and_stat(
+                const char *path,
+                const char *root,
+                unsigned chase_flags,
+                char **ret_path,
+                struct stat *ret_stat) {
+
+        _cleanup_close_ int path_fd = -1;
+        _cleanup_free_ char *p = NULL;
+
+        assert(path);
+        assert(ret_stat);
+
+        if (chase_flags & CHASE_NONEXISTENT)
+                return -EINVAL;
+
+        if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) {
+                /* Shortcut this call if none of the special features of this call are requested */
+                if (stat(path, ret_stat) < 0)
+                        return -errno;
+
+                return 1;
+        }
+
+        path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL);
+        if (path_fd < 0)
+                return path_fd;
+
+        if (fstat(path_fd, ret_stat) < 0)
+                return -errno;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(p);
+
+        if (chase_flags & CHASE_OPEN)
+                return TAKE_FD(path_fd);
+
+        return 1;
+}
+
+int access_fd(int fd, int mode) {
+        char p[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1];
+        int r;
+
+        /* Like access() but operates on an already open fd */
+
+        xsprintf(p, "/proc/self/fd/%i", fd);
+        r = access(p, mode);
+        if (r < 0)
+                return -errno;
+
+        return r;
+}
+
+void unlink_tempfilep(char (*p)[]) {
+        /* If the file is created with mkstemp(), it will (almost always)
+         * change the suffix. Treat this as a sign that the file was
+         * successfully created. We ignore both the rare case where the
+         * original suffix is used and unlink failures. */
+        if (!endswith(*p, ".XXXXXX"))
+                (void) unlink_noerrno(*p);
+}
+
+int unlinkat_deallocate(int fd, const char *name, int flags) {
+        _cleanup_close_ int truncate_fd = -1;
+        struct stat st;
+        off_t l, bs;
+
+        /* Operates like unlinkat() but also deallocates the file contents if it is a regular file and there's no other
+         * link to it. This is useful to ensure that other processes that might have the file open for reading won't be
+         * able to keep the data pinned on disk forever. This call is particular useful whenever we execute clean-up
+         * jobs ("vacuuming"), where we want to make sure the data is really gone and the disk space released and
+         * returned to the free pool.
+         *
+         * Deallocation is preferably done by FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE (👊) if supported, which means
+         * the file won't change size. That's a good thing since we shouldn't needlessly trigger SIGBUS in other
+         * programs that have mmap()ed the file. (The assumption here is that changing file contents to all zeroes
+         * underneath those programs is the better choice than simply triggering SIGBUS in them which truncation does.)
+         * However if hole punching is not implemented in the kernel or file system we'll fall back to normal file
+         * truncation (🔪), as our goal of deallocating the data space trumps our goal of being nice to readers (💐).
+         *
+         * Note that we attempt deallocation, but failure to succeed with that is not considered fatal, as long as the
+         * primary job – to delete the file – is accomplished. */
+
+        if ((flags & AT_REMOVEDIR) == 0) {
+                truncate_fd = openat(fd, name, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
+                if (truncate_fd < 0) {
+
+                        /* If this failed because the file doesn't exist propagate the error right-away. Also,
+                         * AT_REMOVEDIR wasn't set, and we tried to open the file for writing, which means EISDIR is
+                         * returned when this is a directory but we are not supposed to delete those, hence propagate
+                         * the error right-away too. */
+                        if (IN_SET(errno, ENOENT, EISDIR))
+                                return -errno;
+
+                        if (errno != ELOOP) /* don't complain if this is a symlink */
+                                log_debug_errno(errno, "Failed to open file '%s' for deallocation, ignoring: %m", name);
+                }
+        }
+
+        if (unlinkat(fd, name, flags) < 0)
+                return -errno;
+
+        if (truncate_fd < 0) /* Don't have a file handle, can't do more ☹️ */
+                return 0;
+
+        if (fstat(truncate_fd, &st) < 0) {
+                log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
+                return 0;
+        }
+
+        if (!S_ISREG(st.st_mode) || st.st_blocks == 0 || st.st_nlink > 0)
+                return 0;
+
+        /* If this is a regular file, it actually took up space on disk and there are no other links it's time to
+         * punch-hole/truncate this to release the disk space. */
+
+        bs = MAX(st.st_blksize, 512);
+        l = DIV_ROUND_UP(st.st_size, bs) * bs; /* Round up to next block size */
+
+        if (fallocate(truncate_fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, 0, l) >= 0)
+                return 0; /* Successfully punched a hole! 😊 */
+
+        /* Fall back to truncation */
+        if (ftruncate(truncate_fd, 0) < 0) {
+                log_debug_errno(errno, "Failed to truncate file to 0, ignoring: %m");
+                return 0;
+        }
+
+        return 0;
+}
+
+int fsync_directory_of_file(int fd) {
+        _cleanup_free_ char *path = NULL;
+        _cleanup_close_ int dfd = -1;
+        int r;
+
+        r = fd_verify_regular(fd);
+        if (r < 0)
+                return r;
+
+        r = fd_get_path(fd, &path);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to query /proc/self/fd/%d%s: %m",
+                                fd,
+                                r == -EOPNOTSUPP ? ", ignoring" : "");
+
+                if (r == -EOPNOTSUPP)
+                        /* If /proc is not available, we're most likely running in some
+                         * chroot environment, and syncing the directory is not very
+                         * important in that case. Let's just silently do nothing. */
+                        return 0;
+
+                return r;
+        }
+
+        if (!path_is_absolute(path))
+                return -EINVAL;
+
+        dfd = open_parent(path, O_CLOEXEC, 0);
+        if (dfd < 0)
+                return dfd;
+
+        if (fsync(dfd) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int fsync_path_at(int at_fd, const char *path) {
+        _cleanup_close_ int opened_fd = -1;
+        int fd;
+
+        if (isempty(path)) {
+                if (at_fd == AT_FDCWD) {
+                        opened_fd = open(".", O_RDONLY|O_DIRECTORY|O_CLOEXEC);
+                        if (opened_fd < 0)
+                                return -errno;
+
+                        fd = opened_fd;
+                } else
+                        fd = at_fd;
+        } else {
+
+                opened_fd = openat(at_fd, path, O_RDONLY|O_CLOEXEC);
+                if (opened_fd < 0)
+                        return -errno;
+
+                fd = opened_fd;
+        }
+
+        if (fsync(fd) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int open_parent(const char *path, int flags, mode_t mode) {
+        _cleanup_free_ char *parent = NULL;
+        int fd;
+
+        if (isempty(path))
+                return -EINVAL;
+        if (path_equal(path, "/")) /* requesting the parent of the root dir is fishy, let's prohibit that */
+                return -EINVAL;
+
+        parent = dirname_malloc(path);
+        if (!parent)
+                return -ENOMEM;
+
+        /* Let's insist on O_DIRECTORY since the parent of a file or directory is a directory. Except if we open an
+         * O_TMPFILE file, because in that case we are actually create a regular file below the parent directory. */
+
+        if ((flags & O_PATH) == O_PATH)
+                flags |= O_DIRECTORY;
+        else if ((flags & O_TMPFILE) != O_TMPFILE)
+                flags |= O_DIRECTORY|O_RDONLY;
+
+        fd = open(parent, flags, mode);
+        if (fd < 0)
+                return -errno;
+
+        return fd;
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/fs-util.h b/shared/systemd/src/basic/fs-util.h
new file mode 100644
index 00000000..7ad030be
--- /dev/null
+++ b/shared/systemd/src/basic/fs-util.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/inotify.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "time-util.h"
+#include "util.h"
+
+int unlink_noerrno(const char *path);
+
+int rmdir_parents(const char *path, const char *stop);
+
+int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
+
+int readlinkat_malloc(int fd, const char *p, char **ret);
+int readlink_malloc(const char *p, char **r);
+int readlink_value(const char *p, char **ret);
+int readlink_and_make_absolute(const char *p, char **r);
+
+int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid);
+int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid);
+
+int fchmod_umask(int fd, mode_t mode);
+int fchmod_opath(int fd, mode_t m);
+
+int fd_warn_permissions(const char *path, int fd);
+
+#define laccess(path, mode) faccessat(AT_FDCWD, (path), (mode), AT_SYMLINK_NOFOLLOW)
+
+int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode);
+int touch(const char *path);
+
+int symlink_idempotent(const char *from, const char *to, bool make_relative);
+
+int symlink_atomic(const char *from, const char *to);
+int mknod_atomic(const char *path, mode_t mode, dev_t dev);
+int mkfifo_atomic(const char *path, mode_t mode);
+int mkfifoat_atomic(int dir_fd, const char *path, mode_t mode);
+
+int get_files_in_directory(const char *path, char ***list);
+
+int tmp_dir(const char **ret);
+int var_tmp_dir(const char **ret);
+
+int unlink_or_warn(const char *filename);
+
+#define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1)
+
+#define FOREACH_INOTIFY_EVENT(e, buffer, sz) \
+        for ((e) = &buffer.ev;                                \
+             (uint8_t*) (e) < (uint8_t*) (buffer.raw) + (sz); \
+             (e) = (struct inotify_event*) ((uint8_t*) (e) + sizeof(struct inotify_event) + (e)->len))
+
+union inotify_event_buffer {
+        struct inotify_event ev;
+        uint8_t raw[INOTIFY_EVENT_MAX];
+};
+
+int inotify_add_watch_fd(int fd, int what, uint32_t mask);
+
+enum {
+        CHASE_PREFIX_ROOT = 1 << 0, /* If set, the specified path will be prefixed by the specified root before beginning the iteration */
+        CHASE_NONEXISTENT = 1 << 1, /* If set, it's OK if the path doesn't actually exist. */
+        CHASE_NO_AUTOFS   = 1 << 2, /* If set, return -EREMOTE if autofs mount point found */
+        CHASE_SAFE        = 1 << 3, /* If set, return EPERM if we ever traverse from unprivileged to privileged files or directories */
+        CHASE_OPEN        = 1 << 4, /* If set, return an O_PATH object to the final component */
+        CHASE_TRAIL_SLASH = 1 << 5, /* If set, any trailing slash will be preserved */
+        CHASE_STEP        = 1 << 6, /* If set, just execute a single step of the normalization */
+        CHASE_NOFOLLOW    = 1 << 7, /* Only valid with CHASE_OPEN: when the path's right-most component refers to symlink return O_PATH fd of the symlink, rather than following it. */
+        CHASE_WARN        = 1 << 8, /* Emit an appropriate warning when an error is encountered */
+};
+
+/* How many iterations to execute before returning -ELOOP */
+#define CHASE_SYMLINKS_MAX 32
+
+int chase_symlinks(const char *path_with_prefix, const char *root, unsigned flags, char **ret);
+
+int chase_symlinks_and_open(const char *path, const char *root, unsigned chase_flags, int open_flags, char **ret_path);
+int chase_symlinks_and_opendir(const char *path, const char *root, unsigned chase_flags, char **ret_path, DIR **ret_dir);
+int chase_symlinks_and_stat(const char *path, const char *root, unsigned chase_flags, char **ret_path, struct stat *ret_stat);
+
+/* Useful for usage with _cleanup_(), removes a directory and frees the pointer */
+static inline void rmdir_and_free(char *p) {
+        PROTECT_ERRNO;
+        (void) rmdir(p);
+        free(p);
+}
+DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rmdir_and_free);
+
+static inline void unlink_and_free(char *p) {
+        (void) unlink_noerrno(p);
+        free(p);
+}
+DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free);
+
+int access_fd(int fd, int mode);
+
+void unlink_tempfilep(char (*p)[]);
+int unlinkat_deallocate(int fd, const char *name, int flags);
+
+int fsync_directory_of_file(int fd);
+int fsync_path_at(int at_fd, const char *path);
+
+int open_parent(const char *path, int flags, mode_t mode);
diff --git a/shared/systemd/src/basic/hash-funcs.c b/shared/systemd/src/basic/hash-funcs.c
new file mode 100644
index 00000000..ec4f1de6
--- /dev/null
+++ b/shared/systemd/src/basic/hash-funcs.c
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <string.h>
+
+#include "hash-funcs.h"
+#include "path-util.h"
+
+void string_hash_func(const char *p, struct siphash *state) {
+        siphash24_compress(p, strlen(p) + 1, state);
+}
+
+#if 0 /* NM_IGNORED */
+DEFINE_HASH_OPS(string_hash_ops, char, string_hash_func, string_compare_func);
+
+void path_hash_func(const char *q, struct siphash *state) {
+        size_t n;
+
+        assert(q);
+        assert(state);
+
+        /* Calculates a hash for a path in a way this duplicate inner slashes don't make a differences, and also
+         * whether there's a trailing slash or not. This fits well with the semantics of path_compare(), which does
+         * similar checks and also doesn't care for trailing slashes. Note that relative and absolute paths (i.e. those
+         * which begin in a slash or not) will hash differently though. */
+
+        n = strspn(q, "/");
+        if (n > 0) { /* Eat up initial slashes, and add one "/" to the hash for all of them */
+                siphash24_compress(q, 1, state);
+                q += n;
+        }
+
+        for (;;) {
+                /* Determine length of next component */
+                n = strcspn(q, "/");
+                if (n == 0) /* Reached the end? */
+                        break;
+
+                /* Add this component to the hash and skip over it */
+                siphash24_compress(q, n, state);
+                q += n;
+
+                /* How many slashes follow this component? */
+                n = strspn(q, "/");
+                if (q[n] == 0) /* Is this a trailing slash? If so, we are at the end, and don't care about the slashes anymore */
+                        break;
+
+                /* We are not add the end yet. Hash exactly one slash for all of the ones we just encountered. */
+                siphash24_compress(q, 1, state);
+                q += n;
+        }
+}
+
+int path_compare_func(const char *a, const char *b) {
+        return path_compare(a, b);
+}
+
+DEFINE_HASH_OPS(path_hash_ops, char, path_hash_func, path_compare_func);
+#endif /* NM_IGNORED */
+
+void trivial_hash_func(const void *p, struct siphash *state) {
+        siphash24_compress(&p, sizeof(p), state);
+}
+
+int trivial_compare_func(const void *a, const void *b) {
+        return CMP(a, b);
+}
+
+const struct hash_ops trivial_hash_ops = {
+        .hash = trivial_hash_func,
+        .compare = trivial_compare_func,
+};
+
+void uint64_hash_func(const uint64_t *p, struct siphash *state) {
+        siphash24_compress(p, sizeof(uint64_t), state);
+}
+
+int uint64_compare_func(const uint64_t *a, const uint64_t *b) {
+        return CMP(*a, *b);
+}
+
+DEFINE_HASH_OPS(uint64_hash_ops, uint64_t, uint64_hash_func, uint64_compare_func);
+
+#if 0 /* NM_IGNORED */
+#if SIZEOF_DEV_T != 8
+void devt_hash_func(const dev_t *p, struct siphash *state) {
+        siphash24_compress(p, sizeof(dev_t), state);
+}
+
+int devt_compare_func(const dev_t *a, const dev_t *b) {
+        return CMP(*a, *b);
+}
+
+DEFINE_HASH_OPS(devt_hash_ops, dev_t, devt_hash_func, devt_compare_func);
+#endif
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/hash-funcs.h b/shared/systemd/src/basic/hash-funcs.h
new file mode 100644
index 00000000..3d2ae4b5
--- /dev/null
+++ b/shared/systemd/src/basic/hash-funcs.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "alloc-util.h"
+#include "macro.h"
+#include "siphash24.h"
+
+typedef void (*hash_func_t)(const void *p, struct siphash *state);
+typedef int (*compare_func_t)(const void *a, const void *b);
+
+struct hash_ops {
+        hash_func_t hash;
+        compare_func_t compare;
+        free_func_t free_key;
+        free_func_t free_value;
+};
+
+#define _DEFINE_HASH_OPS(uq, name, type, hash_func, compare_func, free_key_func, free_value_func, scope) \
+        _unused_ static void (* UNIQ_T(static_hash_wrapper, uq))(const type *, struct siphash *) = hash_func; \
+        _unused_ static int (* UNIQ_T(static_compare_wrapper, uq))(const type *, const type *) = compare_func; \
+        scope const struct hash_ops name = {                            \
+                .hash = (hash_func_t) hash_func,                        \
+                .compare = (compare_func_t) compare_func,               \
+                .free_key = free_key_func,                              \
+                .free_value = free_value_func,                          \
+        }
+
+#define _DEFINE_FREE_FUNC(uq, type, wrapper_name, func)                 \
+        /* Type-safe free function */                                   \
+        static void UNIQ_T(wrapper_name, uq)(void *a) {                 \
+                type *_a = a;                                           \
+                func(_a);                                               \
+        }
+
+#define _DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(uq, name, type, hash_func, compare_func, free_func, scope) \
+        _DEFINE_FREE_FUNC(uq, type, static_free_wrapper, free_func);    \
+        _DEFINE_HASH_OPS(uq, name, type, hash_func, compare_func,       \
+                         UNIQ_T(static_free_wrapper, uq), NULL, scope)
+
+#define _DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(uq, name, type, hash_func, compare_func, type_value, free_func, scope) \
+        _DEFINE_FREE_FUNC(uq, type_value, static_free_wrapper, free_func); \
+        _DEFINE_HASH_OPS(uq, name, type, hash_func, compare_func,       \
+                         NULL, UNIQ_T(static_free_wrapper, uq), scope)
+
+#define _DEFINE_HASH_OPS_FULL(uq, name, type, hash_func, compare_func, free_key_func, type_value, free_value_func, scope) \
+        _DEFINE_FREE_FUNC(uq, type, static_free_key_wrapper, free_key_func); \
+        _DEFINE_FREE_FUNC(uq, type_value, static_free_value_wrapper, free_value_func); \
+        _DEFINE_HASH_OPS(uq, name, type, hash_func, compare_func,       \
+                         UNIQ_T(static_free_key_wrapper, uq),           \
+                         UNIQ_T(static_free_value_wrapper, uq), scope)
+
+#define DEFINE_HASH_OPS(name, type, hash_func, compare_func)            \
+        _DEFINE_HASH_OPS(UNIQ, name, type, hash_func, compare_func, NULL, NULL,)
+
+#define DEFINE_PRIVATE_HASH_OPS(name, type, hash_func, compare_func)    \
+        _DEFINE_HASH_OPS(UNIQ, name, type, hash_func, compare_func, NULL, NULL, static)
+
+#define DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(name, type, hash_func, compare_func, free_func) \
+        _DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(UNIQ, name, type, hash_func, compare_func, free_func,)
+
+#define DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(name, type, hash_func, compare_func, free_func) \
+        _DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(UNIQ, name, type, hash_func, compare_func, free_func, static)
+
+#define DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(name, type, hash_func, compare_func, value_type, free_func) \
+        _DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(UNIQ, name, type, hash_func, compare_func, value_type, free_func,)
+
+#define DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(name, type, hash_func, compare_func, value_type, free_func) \
+        _DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(UNIQ, name, type, hash_func, compare_func, value_type, free_func, static)
+
+#define DEFINE_HASH_OPS_FULL(name, type, hash_func, compare_func, free_key_func, value_type, free_value_func) \
+        _DEFINE_HASH_OPS_FULL(UNIQ, name, type, hash_func, compare_func, free_key_func, value_type, free_value_func,)
+
+#define DEFINE_PRIVATE_HASH_OPS_FULL(name, type, hash_func, compare_func, free_key_func, value_type, free_value_func) \
+        _DEFINE_HASH_OPS_FULL(UNIQ, name, type, hash_func, compare_func, free_key_func, value_type, free_value_func, static)
+
+void string_hash_func(const char *p, struct siphash *state);
+#define string_compare_func strcmp
+extern const struct hash_ops string_hash_ops;
+
+void path_hash_func(const char *p, struct siphash *state);
+int path_compare_func(const char *a, const char *b) _pure_;
+extern const struct hash_ops path_hash_ops;
+
+/* This will compare the passed pointers directly, and will not dereference them. This is hence not useful for strings
+ * or suchlike. */
+void trivial_hash_func(const void *p, struct siphash *state);
+int trivial_compare_func(const void *a, const void *b) _const_;
+extern const struct hash_ops trivial_hash_ops;
+
+/* 32bit values we can always just embed in the pointer itself, but in order to support 32bit archs we need store 64bit
+ * values indirectly, since they don't fit in a pointer. */
+void uint64_hash_func(const uint64_t *p, struct siphash *state);
+int uint64_compare_func(const uint64_t *a, const uint64_t *b) _pure_;
+extern const struct hash_ops uint64_hash_ops;
+
+/* On some archs dev_t is 32bit, and on others 64bit. And sometimes it's 64bit on 32bit archs, and sometimes 32bit on
+ * 64bit archs. Yuck! */
+#if SIZEOF_DEV_T != 8
+void devt_hash_func(const dev_t *p, struct siphash *state) _pure_;
+int devt_compare_func(const dev_t *a, const dev_t *b) _pure_;
+extern const struct hash_ops devt_hash_ops;
+#else
+#define devt_hash_func uint64_hash_func
+#define devt_compare_func uint64_compare_func
+#define devt_hash_ops uint64_hash_ops
+#endif
diff --git a/shared/systemd/src/basic/hashmap.c b/shared/systemd/src/basic/hashmap.c
new file mode 100644
index 00000000..c0655831
--- /dev/null
+++ b/shared/systemd/src/basic/hashmap.c
@@ -0,0 +1,1913 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "fileio.h"
+#include "hashmap.h"
+#include "macro.h"
+#include "mempool.h"
+#include "process-util.h"
+#include "random-util.h"
+#include "set.h"
+#include "siphash24.h"
+#include "string-util.h"
+#include "strv.h"
+#include "util.h"
+
+#if ENABLE_DEBUG_HASHMAP
+#include <pthread.h>
+#include "list.h"
+#endif
+
+/*
+ * Implementation of hashmaps.
+ * Addressing: open
+ *   - uses less RAM compared to closed addressing (chaining), because
+ *     our entries are small (especially in Sets, which tend to contain
+ *     the majority of entries in systemd).
+ * Collision resolution: Robin Hood
+ *   - tends to equalize displacement of entries from their optimal buckets.
+ * Probe sequence: linear
+ *   - though theoretically worse than random probing/uniform hashing/double
+ *     hashing, it is good for cache locality.
+ *
+ * References:
+ * Celis, P. 1986. Robin Hood Hashing.
+ * Ph.D. Dissertation. University of Waterloo, Waterloo, Ont., Canada, Canada.
+ * https://cs.uwaterloo.ca/research/tr/1986/CS-86-14.pdf
+ * - The results are derived for random probing. Suggests deletion with
+ *   tombstones and two mean-centered search methods. None of that works
+ *   well for linear probing.
+ *
+ * Janson, S. 2005. Individual displacements for linear probing hashing with different insertion policies.
+ * ACM Trans. Algorithms 1, 2 (October 2005), 177-213.
+ * DOI=10.1145/1103963.1103964 http://doi.acm.org/10.1145/1103963.1103964
+ * http://www.math.uu.se/~svante/papers/sj157.pdf
+ * - Applies to Robin Hood with linear probing. Contains remarks on
+ *   the unsuitability of mean-centered search with linear probing.
+ *
+ * Viola, A. 2005. Exact distribution of individual displacements in linear probing hashing.
+ * ACM Trans. Algorithms 1, 2 (October 2005), 214-242.
+ * DOI=10.1145/1103963.1103965 http://doi.acm.org/10.1145/1103963.1103965
+ * - Similar to Janson. Note that Viola writes about C_{m,n} (number of probes
+ *   in a successful search), and Janson writes about displacement. C = d + 1.
+ *
+ * Goossaert, E. 2013. Robin Hood hashing: backward shift deletion.
+ * http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
+ * - Explanation of backward shift deletion with pictures.
+ *
+ * Khuong, P. 2013. The Other Robin Hood Hashing.
+ * http://www.pvk.ca/Blog/2013/11/26/the-other-robin-hood-hashing/
+ * - Short summary of random vs. linear probing, and tombstones vs. backward shift.
+ */
+
+/*
+ * XXX Ideas for improvement:
+ * For unordered hashmaps, randomize iteration order, similarly to Perl:
+ * http://blog.booking.com/hardening-perls-hash-function.html
+ */
+
+/* INV_KEEP_FREE = 1 / (1 - max_load_factor)
+ * e.g. 1 / (1 - 0.8) = 5 ... keep one fifth of the buckets free. */
+#define INV_KEEP_FREE            5U
+
+/* Fields common to entries of all hashmap/set types */
+struct hashmap_base_entry {
+        const void *key;
+};
+
+/* Entry types for specific hashmap/set types
+ * hashmap_base_entry must be at the beginning of each entry struct. */
+
+struct plain_hashmap_entry {
+        struct hashmap_base_entry b;
+        void *value;
+};
+
+struct ordered_hashmap_entry {
+        struct plain_hashmap_entry p;
+        unsigned iterate_next, iterate_previous;
+};
+
+struct set_entry {
+        struct hashmap_base_entry b;
+};
+
+/* In several functions it is advantageous to have the hash table extended
+ * virtually by a couple of additional buckets. We reserve special index values
+ * for these "swap" buckets. */
+#define _IDX_SWAP_BEGIN     (UINT_MAX - 3)
+#define IDX_PUT             (_IDX_SWAP_BEGIN + 0)
+#define IDX_TMP             (_IDX_SWAP_BEGIN + 1)
+#define _IDX_SWAP_END       (_IDX_SWAP_BEGIN + 2)
+
+#define IDX_FIRST           (UINT_MAX - 1) /* special index for freshly initialized iterators */
+#define IDX_NIL             UINT_MAX       /* special index value meaning "none" or "end" */
+
+assert_cc(IDX_FIRST == _IDX_SWAP_END);
+assert_cc(IDX_FIRST == _IDX_ITERATOR_FIRST);
+
+/* Storage space for the "swap" buckets.
+ * All entry types can fit into a ordered_hashmap_entry. */
+struct swap_entries {
+        struct ordered_hashmap_entry e[_IDX_SWAP_END - _IDX_SWAP_BEGIN];
+};
+
+/* Distance from Initial Bucket */
+typedef uint8_t dib_raw_t;
+#define DIB_RAW_OVERFLOW ((dib_raw_t)0xfdU)   /* indicates DIB value is greater than representable */
+#define DIB_RAW_REHASH   ((dib_raw_t)0xfeU)   /* entry yet to be rehashed during in-place resize */
+#define DIB_RAW_FREE     ((dib_raw_t)0xffU)   /* a free bucket */
+#define DIB_RAW_INIT     ((char)DIB_RAW_FREE) /* a byte to memset a DIB store with when initializing */
+
+#define DIB_FREE UINT_MAX
+
+#if ENABLE_DEBUG_HASHMAP
+struct hashmap_debug_info {
+        LIST_FIELDS(struct hashmap_debug_info, debug_list);
+        unsigned max_entries;  /* high watermark of n_entries */
+
+        /* who allocated this hashmap */
+        int line;
+        const char *file;
+        const char *func;
+
+        /* fields to detect modification while iterating */
+        unsigned put_count;    /* counts puts into the hashmap */
+        unsigned rem_count;    /* counts removals from hashmap */
+        unsigned last_rem_idx; /* remembers last removal index */
+};
+
+/* Tracks all existing hashmaps. Get at it from gdb. See sd_dump_hashmaps.py */
+static LIST_HEAD(struct hashmap_debug_info, hashmap_debug_list);
+static pthread_mutex_t hashmap_debug_list_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+#define HASHMAP_DEBUG_FIELDS struct hashmap_debug_info debug;
+
+#else /* !ENABLE_DEBUG_HASHMAP */
+#define HASHMAP_DEBUG_FIELDS
+#endif /* ENABLE_DEBUG_HASHMAP */
+
+enum HashmapType {
+        HASHMAP_TYPE_PLAIN,
+        HASHMAP_TYPE_ORDERED,
+        HASHMAP_TYPE_SET,
+        _HASHMAP_TYPE_MAX
+};
+
+struct _packed_ indirect_storage {
+        void *storage;                     /* where buckets and DIBs are stored */
+        uint8_t  hash_key[HASH_KEY_SIZE];  /* hash key; changes during resize */
+
+        unsigned n_entries;                /* number of stored entries */
+        unsigned n_buckets;                /* number of buckets */
+
+        unsigned idx_lowest_entry;         /* Index below which all buckets are free.
+                                              Makes "while(hashmap_steal_first())" loops
+                                              O(n) instead of O(n^2) for unordered hashmaps. */
+        uint8_t  _pad[3];                  /* padding for the whole HashmapBase */
+        /* The bitfields in HashmapBase complete the alignment of the whole thing. */
+};
+
+struct direct_storage {
+        /* This gives us 39 bytes on 64bit, or 35 bytes on 32bit.
+         * That's room for 4 set_entries + 4 DIB bytes + 3 unused bytes on 64bit,
+         *              or 7 set_entries + 7 DIB bytes + 0 unused bytes on 32bit. */
+        uint8_t storage[sizeof(struct indirect_storage)];
+};
+
+#define DIRECT_BUCKETS(entry_t) \
+        (sizeof(struct direct_storage) / (sizeof(entry_t) + sizeof(dib_raw_t)))
+
+/* We should be able to store at least one entry directly. */
+assert_cc(DIRECT_BUCKETS(struct ordered_hashmap_entry) >= 1);
+
+/* We have 3 bits for n_direct_entries. */
+assert_cc(DIRECT_BUCKETS(struct set_entry) < (1 << 3));
+
+/* Hashmaps with directly stored entries all use this shared hash key.
+ * It's no big deal if the key is guessed, because there can be only
+ * a handful of directly stored entries in a hashmap. When a hashmap
+ * outgrows direct storage, it gets its own key for indirect storage. */
+static uint8_t shared_hash_key[HASH_KEY_SIZE];
+static bool shared_hash_key_initialized;
+
+/* Fields that all hashmap/set types must have */
+struct HashmapBase {
+        const struct hash_ops *hash_ops;  /* hash and compare ops to use */
+
+        union _packed_ {
+                struct indirect_storage indirect; /* if  has_indirect */
+                struct direct_storage direct;     /* if !has_indirect */
+        };
+
+        enum HashmapType type:2;     /* HASHMAP_TYPE_* */
+        bool has_indirect:1;         /* whether indirect storage is used */
+        unsigned n_direct_entries:3; /* Number of entries in direct storage.
+                                      * Only valid if !has_indirect. */
+        bool from_pool:1;            /* whether was allocated from mempool */
+        bool dirty:1;                /* whether dirtied since last iterated_cache_get() */
+        bool cached:1;               /* whether this hashmap is being cached */
+        HASHMAP_DEBUG_FIELDS         /* optional hashmap_debug_info */
+};
+
+/* Specific hash types
+ * HashmapBase must be at the beginning of each hashmap struct. */
+
+struct Hashmap {
+        struct HashmapBase b;
+};
+
+struct OrderedHashmap {
+        struct HashmapBase b;
+        unsigned iterate_list_head, iterate_list_tail;
+};
+
+struct Set {
+        struct HashmapBase b;
+};
+
+typedef struct CacheMem {
+        const void **ptr;
+        size_t n_populated, n_allocated;
+        bool active:1;
+} CacheMem;
+
+struct IteratedCache {
+        HashmapBase *hashmap;
+        CacheMem keys, values;
+};
+
+DEFINE_MEMPOOL(hashmap_pool,         Hashmap,        8);
+DEFINE_MEMPOOL(ordered_hashmap_pool, OrderedHashmap, 8);
+/* No need for a separate Set pool */
+assert_cc(sizeof(Hashmap) == sizeof(Set));
+
+struct hashmap_type_info {
+        size_t head_size;
+        size_t entry_size;
+        struct mempool *mempool;
+        unsigned n_direct_buckets;
+};
+
+static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = {
+        [HASHMAP_TYPE_PLAIN] = {
+                .head_size        = sizeof(Hashmap),
+                .entry_size       = sizeof(struct plain_hashmap_entry),
+                .mempool          = &hashmap_pool,
+                .n_direct_buckets = DIRECT_BUCKETS(struct plain_hashmap_entry),
+        },
+        [HASHMAP_TYPE_ORDERED] = {
+                .head_size        = sizeof(OrderedHashmap),
+                .entry_size       = sizeof(struct ordered_hashmap_entry),
+                .mempool          = &ordered_hashmap_pool,
+                .n_direct_buckets = DIRECT_BUCKETS(struct ordered_hashmap_entry),
+        },
+        [HASHMAP_TYPE_SET] = {
+                .head_size        = sizeof(Set),
+                .entry_size       = sizeof(struct set_entry),
+                .mempool          = &hashmap_pool,
+                .n_direct_buckets = DIRECT_BUCKETS(struct set_entry),
+        },
+};
+
+#if VALGRIND
+_destructor_ static void cleanup_pools(void) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        /* Be nice to valgrind */
+
+        /* The pool is only allocated by the main thread, but the memory can
+         * be passed to other threads. Let's clean up if we are the main thread
+         * and no other threads are live. */
+        if (!is_main_thread())
+                return;
+
+        r = get_proc_field("/proc/self/status", "Threads", WHITESPACE, &t);
+        if (r < 0 || !streq(t, "1"))
+                return;
+
+        mempool_drop(&hashmap_pool);
+        mempool_drop(&ordered_hashmap_pool);
+}
+#endif
+
+static unsigned n_buckets(HashmapBase *h) {
+        return h->has_indirect ? h->indirect.n_buckets
+                               : hashmap_type_info[h->type].n_direct_buckets;
+}
+
+static unsigned n_entries(HashmapBase *h) {
+        return h->has_indirect ? h->indirect.n_entries
+                               : h->n_direct_entries;
+}
+
+static void n_entries_inc(HashmapBase *h) {
+        if (h->has_indirect)
+                h->indirect.n_entries++;
+        else
+                h->n_direct_entries++;
+}
+
+static void n_entries_dec(HashmapBase *h) {
+        if (h->has_indirect)
+                h->indirect.n_entries--;
+        else
+                h->n_direct_entries--;
+}
+
+static void *storage_ptr(HashmapBase *h) {
+        return h->has_indirect ? h->indirect.storage
+                               : h->direct.storage;
+}
+
+static uint8_t *hash_key(HashmapBase *h) {
+        return h->has_indirect ? h->indirect.hash_key
+                               : shared_hash_key;
+}
+
+static unsigned base_bucket_hash(HashmapBase *h, const void *p) {
+        struct siphash state;
+        uint64_t hash;
+
+        siphash24_init(&state, hash_key(h));
+
+        h->hash_ops->hash(p, &state);
+
+        hash = siphash24_finalize(&state);
+
+        return (unsigned) (hash % n_buckets(h));
+}
+#define bucket_hash(h, p) base_bucket_hash(HASHMAP_BASE(h), p)
+
+static void base_set_dirty(HashmapBase *h) {
+        h->dirty = true;
+}
+#define hashmap_set_dirty(h) base_set_dirty(HASHMAP_BASE(h))
+
+static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) {
+        static uint8_t current[HASH_KEY_SIZE];
+        static bool current_initialized = false;
+
+        /* Returns a hash function key to use. In order to keep things
+         * fast we will not generate a new key each time we allocate a
+         * new hash table. Instead, we'll just reuse the most recently
+         * generated one, except if we never generated one or when we
+         * are rehashing an entire hash table because we reached a
+         * fill level */
+
+        if (!current_initialized || !reuse_is_ok) {
+                random_bytes(current, sizeof(current));
+                current_initialized = true;
+        }
+
+        memcpy(hash_key, current, sizeof(current));
+}
+
+static struct hashmap_base_entry *bucket_at(HashmapBase *h, unsigned idx) {
+        return (struct hashmap_base_entry*)
+                ((uint8_t*) storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size);
+}
+
+static struct plain_hashmap_entry *plain_bucket_at(Hashmap *h, unsigned idx) {
+        return (struct plain_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx);
+}
+
+static struct ordered_hashmap_entry *ordered_bucket_at(OrderedHashmap *h, unsigned idx) {
+        return (struct ordered_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx);
+}
+
+static struct set_entry *set_bucket_at(Set *h, unsigned idx) {
+        return (struct set_entry*) bucket_at(HASHMAP_BASE(h), idx);
+}
+
+static struct ordered_hashmap_entry *bucket_at_swap(struct swap_entries *swap, unsigned idx) {
+        return &swap->e[idx - _IDX_SWAP_BEGIN];
+}
+
+/* Returns a pointer to the bucket at index idx.
+ * Understands real indexes and swap indexes, hence "_virtual". */
+static struct hashmap_base_entry *bucket_at_virtual(HashmapBase *h, struct swap_entries *swap,
+                                                    unsigned idx) {
+        if (idx < _IDX_SWAP_BEGIN)
+                return bucket_at(h, idx);
+
+        if (idx < _IDX_SWAP_END)
+                return &bucket_at_swap(swap, idx)->p.b;
+
+        assert_not_reached("Invalid index");
+}
+
+static dib_raw_t *dib_raw_ptr(HashmapBase *h) {
+        return (dib_raw_t*)
+                ((uint8_t*) storage_ptr(h) + hashmap_type_info[h->type].entry_size * n_buckets(h));
+}
+
+static unsigned bucket_distance(HashmapBase *h, unsigned idx, unsigned from) {
+        return idx >= from ? idx - from
+                           : n_buckets(h) + idx - from;
+}
+
+static unsigned bucket_calculate_dib(HashmapBase *h, unsigned idx, dib_raw_t raw_dib) {
+        unsigned initial_bucket;
+
+        if (raw_dib == DIB_RAW_FREE)
+                return DIB_FREE;
+
+        if (_likely_(raw_dib < DIB_RAW_OVERFLOW))
+                return raw_dib;
+
+        /*
+         * Having an overflow DIB value is very unlikely. The hash function
+         * would have to be bad. For example, in a table of size 2^24 filled
+         * to load factor 0.9 the maximum observed DIB is only about 60.
+         * In theory (assuming I used Maxima correctly), for an infinite size
+         * hash table with load factor 0.8 the probability of a given entry
+         * having DIB > 40 is 1.9e-8.
+         * This returns the correct DIB value by recomputing the hash value in
+         * the unlikely case. XXX Hitting this case could be a hint to rehash.
+         */
+        initial_bucket = bucket_hash(h, bucket_at(h, idx)->key);
+        return bucket_distance(h, idx, initial_bucket);
+}
+
+static void bucket_set_dib(HashmapBase *h, unsigned idx, unsigned dib) {
+        dib_raw_ptr(h)[idx] = dib != DIB_FREE ? MIN(dib, DIB_RAW_OVERFLOW) : DIB_RAW_FREE;
+}
+
+static unsigned skip_free_buckets(HashmapBase *h, unsigned idx) {
+        dib_raw_t *dibs;
+
+        dibs = dib_raw_ptr(h);
+
+        for ( ; idx < n_buckets(h); idx++)
+                if (dibs[idx] != DIB_RAW_FREE)
+                        return idx;
+
+        return IDX_NIL;
+}
+
+static void bucket_mark_free(HashmapBase *h, unsigned idx) {
+        memzero(bucket_at(h, idx), hashmap_type_info[h->type].entry_size);
+        bucket_set_dib(h, idx, DIB_FREE);
+}
+
+static void bucket_move_entry(HashmapBase *h, struct swap_entries *swap,
+                              unsigned from, unsigned to) {
+        struct hashmap_base_entry *e_from, *e_to;
+
+        assert(from != to);
+
+        e_from = bucket_at_virtual(h, swap, from);
+        e_to   = bucket_at_virtual(h, swap, to);
+
+        memcpy(e_to, e_from, hashmap_type_info[h->type].entry_size);
+
+        if (h->type == HASHMAP_TYPE_ORDERED) {
+                OrderedHashmap *lh = (OrderedHashmap*) h;
+                struct ordered_hashmap_entry *le, *le_to;
+
+                le_to = (struct ordered_hashmap_entry*) e_to;
+
+                if (le_to->iterate_next != IDX_NIL) {
+                        le = (struct ordered_hashmap_entry*)
+                             bucket_at_virtual(h, swap, le_to->iterate_next);
+                        le->iterate_previous = to;
+                }
+
+                if (le_to->iterate_previous != IDX_NIL) {
+                        le = (struct ordered_hashmap_entry*)
+                             bucket_at_virtual(h, swap, le_to->iterate_previous);
+                        le->iterate_next = to;
+                }
+
+                if (lh->iterate_list_head == from)
+                        lh->iterate_list_head = to;
+                if (lh->iterate_list_tail == from)
+                        lh->iterate_list_tail = to;
+        }
+}
+
+static unsigned next_idx(HashmapBase *h, unsigned idx) {
+        return (idx + 1U) % n_buckets(h);
+}
+
+static unsigned prev_idx(HashmapBase *h, unsigned idx) {
+        return (n_buckets(h) + idx - 1U) % n_buckets(h);
+}
+
+static void *entry_value(HashmapBase *h, struct hashmap_base_entry *e) {
+        switch (h->type) {
+
+        case HASHMAP_TYPE_PLAIN:
+        case HASHMAP_TYPE_ORDERED:
+                return ((struct plain_hashmap_entry*)e)->value;
+
+        case HASHMAP_TYPE_SET:
+                return (void*) e->key;
+
+        default:
+                assert_not_reached("Unknown hashmap type");
+        }
+}
+
+static void base_remove_entry(HashmapBase *h, unsigned idx) {
+        unsigned left, right, prev, dib;
+        dib_raw_t raw_dib, *dibs;
+
+        dibs = dib_raw_ptr(h);
+        assert(dibs[idx] != DIB_RAW_FREE);
+
+#if ENABLE_DEBUG_HASHMAP
+        h->debug.rem_count++;
+        h->debug.last_rem_idx = idx;
+#endif
+
+        left = idx;
+        /* Find the stop bucket ("right"). It is either free or has DIB == 0. */
+        for (right = next_idx(h, left); ; right = next_idx(h, right)) {
+                raw_dib = dibs[right];
+                if (IN_SET(raw_dib, 0, DIB_RAW_FREE))
+                        break;
+
+                /* The buckets are not supposed to be all occupied and with DIB > 0.
+                 * That would mean we could make everyone better off by shifting them
+                 * backward. This scenario is impossible. */
+                assert(left != right);
+        }
+
+        if (h->type == HASHMAP_TYPE_ORDERED) {
+                OrderedHashmap *lh = (OrderedHashmap*) h;
+                struct ordered_hashmap_entry *le = ordered_bucket_at(lh, idx);
+
+                if (le->iterate_next != IDX_NIL)
+                        ordered_bucket_at(lh, le->iterate_next)->iterate_previous = le->iterate_previous;
+                else
+                        lh->iterate_list_tail = le->iterate_previous;
+
+                if (le->iterate_previous != IDX_NIL)
+                        ordered_bucket_at(lh, le->iterate_previous)->iterate_next = le->iterate_next;
+                else
+                        lh->iterate_list_head = le->iterate_next;
+        }
+
+        /* Now shift all buckets in the interval (left, right) one step backwards */
+        for (prev = left, left = next_idx(h, left); left != right;
+             prev = left, left = next_idx(h, left)) {
+                dib = bucket_calculate_dib(h, left, dibs[left]);
+                assert(dib != 0);
+                bucket_move_entry(h, NULL, left, prev);
+                bucket_set_dib(h, prev, dib - 1);
+        }
+
+        bucket_mark_free(h, prev);
+        n_entries_dec(h);
+        base_set_dirty(h);
+}
+#define remove_entry(h, idx) base_remove_entry(HASHMAP_BASE(h), idx)
+
+static unsigned hashmap_iterate_in_insertion_order(OrderedHashmap *h, Iterator *i) {
+        struct ordered_hashmap_entry *e;
+        unsigned idx;
+
+        assert(h);
+        assert(i);
+
+        if (i->idx == IDX_NIL)
+                goto at_end;
+
+        if (i->idx == IDX_FIRST && h->iterate_list_head == IDX_NIL)
+                goto at_end;
+
+        if (i->idx == IDX_FIRST) {
+                idx = h->iterate_list_head;
+                e = ordered_bucket_at(h, idx);
+        } else {
+                idx = i->idx;
+                e = ordered_bucket_at(h, idx);
+                /*
+                 * We allow removing the current entry while iterating, but removal may cause
+                 * a backward shift. The next entry may thus move one bucket to the left.
+                 * To detect when it happens, we remember the key pointer of the entry we were
+                 * going to iterate next. If it does not match, there was a backward shift.
+                 */
+                if (e->p.b.key != i->next_key) {
+                        idx = prev_idx(HASHMAP_BASE(h), idx);
+                        e = ordered_bucket_at(h, idx);
+                }
+                assert(e->p.b.key == i->next_key);
+        }
+
+#if ENABLE_DEBUG_HASHMAP
+        i->prev_idx = idx;
+#endif
+
+        if (e->iterate_next != IDX_NIL) {
+                struct ordered_hashmap_entry *n;
+                i->idx = e->iterate_next;
+                n = ordered_bucket_at(h, i->idx);
+                i->next_key = n->p.b.key;
+        } else
+                i->idx = IDX_NIL;
+
+        return idx;
+
+at_end:
+        i->idx = IDX_NIL;
+        return IDX_NIL;
+}
+
+static unsigned hashmap_iterate_in_internal_order(HashmapBase *h, Iterator *i) {
+        unsigned idx;
+
+        assert(h);
+        assert(i);
+
+        if (i->idx == IDX_NIL)
+                goto at_end;
+
+        if (i->idx == IDX_FIRST) {
+                /* fast forward to the first occupied bucket */
+                if (h->has_indirect) {
+                        i->idx = skip_free_buckets(h, h->indirect.idx_lowest_entry);
+                        h->indirect.idx_lowest_entry = i->idx;
+                } else
+                        i->idx = skip_free_buckets(h, 0);
+
+                if (i->idx == IDX_NIL)
+                        goto at_end;
+        } else {
+                struct hashmap_base_entry *e;
+
+                assert(i->idx > 0);
+
+                e = bucket_at(h, i->idx);
+                /*
+                 * We allow removing the current entry while iterating, but removal may cause
+                 * a backward shift. The next entry may thus move one bucket to the left.
+                 * To detect when it happens, we remember the key pointer of the entry we were
+                 * going to iterate next. If it does not match, there was a backward shift.
+                 */
+                if (e->key != i->next_key)
+                        e = bucket_at(h, --i->idx);
+
+                assert(e->key == i->next_key);
+        }
+
+        idx = i->idx;
+#if ENABLE_DEBUG_HASHMAP
+        i->prev_idx = idx;
+#endif
+
+        i->idx = skip_free_buckets(h, i->idx + 1);
+        if (i->idx != IDX_NIL)
+                i->next_key = bucket_at(h, i->idx)->key;
+        else
+                i->idx = IDX_NIL;
+
+        return idx;
+
+at_end:
+        i->idx = IDX_NIL;
+        return IDX_NIL;
+}
+
+static unsigned hashmap_iterate_entry(HashmapBase *h, Iterator *i) {
+        if (!h) {
+                i->idx = IDX_NIL;
+                return IDX_NIL;
+        }
+
+#if ENABLE_DEBUG_HASHMAP
+        if (i->idx == IDX_FIRST) {
+                i->put_count = h->debug.put_count;
+                i->rem_count = h->debug.rem_count;
+        } else {
+                /* While iterating, must not add any new entries */
+                assert(i->put_count == h->debug.put_count);
+                /* ... or remove entries other than the current one */
+                assert(i->rem_count == h->debug.rem_count ||
+                       (i->rem_count == h->debug.rem_count - 1 &&
+                        i->prev_idx == h->debug.last_rem_idx));
+                /* Reset our removals counter */
+                i->rem_count = h->debug.rem_count;
+        }
+#endif
+
+        return h->type == HASHMAP_TYPE_ORDERED ? hashmap_iterate_in_insertion_order((OrderedHashmap*) h, i)
+                                               : hashmap_iterate_in_internal_order(h, i);
+}
+
+bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key) {
+        struct hashmap_base_entry *e;
+        void *data;
+        unsigned idx;
+
+        idx = hashmap_iterate_entry(h, i);
+        if (idx == IDX_NIL) {
+                if (value)
+                        *value = NULL;
+                if (key)
+                        *key = NULL;
+
+                return false;
+        }
+
+        e = bucket_at(h, idx);
+        data = entry_value(h, e);
+        if (value)
+                *value = data;
+        if (key)
+                *key = e->key;
+
+        return true;
+}
+
+bool set_iterate(Set *s, Iterator *i, void **value) {
+        return internal_hashmap_iterate(HASHMAP_BASE(s), i, value, NULL);
+}
+
+#define HASHMAP_FOREACH_IDX(idx, h, i) \
+        for ((i) = ITERATOR_FIRST, (idx) = hashmap_iterate_entry((h), &(i)); \
+             (idx != IDX_NIL); \
+             (idx) = hashmap_iterate_entry((h), &(i)))
+
+IteratedCache *internal_hashmap_iterated_cache_new(HashmapBase *h) {
+        IteratedCache *cache;
+
+        assert(h);
+        assert(!h->cached);
+
+        if (h->cached)
+                return NULL;
+
+        cache = new0(IteratedCache, 1);
+        if (!cache)
+                return NULL;
+
+        cache->hashmap = h;
+        h->cached = true;
+
+        return cache;
+}
+
+static void reset_direct_storage(HashmapBase *h) {
+        const struct hashmap_type_info *hi = &hashmap_type_info[h->type];
+        void *p;
+
+        assert(!h->has_indirect);
+
+        p = mempset(h->direct.storage, 0, hi->entry_size * hi->n_direct_buckets);
+        memset(p, DIB_RAW_INIT, sizeof(dib_raw_t) * hi->n_direct_buckets);
+}
+
+static struct HashmapBase *hashmap_base_new(const struct hash_ops *hash_ops, enum HashmapType type HASHMAP_DEBUG_PARAMS) {
+        HashmapBase *h;
+        const struct hashmap_type_info *hi = &hashmap_type_info[type];
+        bool up;
+
+        up = mempool_enabled();
+
+        h = up ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size);
+        if (!h)
+                return NULL;
+
+        h->type = type;
+        h->from_pool = up;
+        h->hash_ops = hash_ops ?: &trivial_hash_ops;
+
+        if (type == HASHMAP_TYPE_ORDERED) {
+                OrderedHashmap *lh = (OrderedHashmap*)h;
+                lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL;
+        }
+
+        reset_direct_storage(h);
+
+        if (!shared_hash_key_initialized) {
+                random_bytes(shared_hash_key, sizeof(shared_hash_key));
+                shared_hash_key_initialized= true;
+        }
+
+#if ENABLE_DEBUG_HASHMAP
+        h->debug.func = func;
+        h->debug.file = file;
+        h->debug.line = line;
+        assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0);
+        LIST_PREPEND(debug_list, hashmap_debug_list, &h->debug);
+        assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0);
+#endif
+
+        return h;
+}
+
+Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
+        return (Hashmap*)        hashmap_base_new(hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS);
+}
+
+OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
+        return (OrderedHashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS);
+}
+
+Set *internal_set_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
+        return (Set*)            hashmap_base_new(hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS);
+}
+
+static int hashmap_base_ensure_allocated(HashmapBase **h, const struct hash_ops *hash_ops,
+                                         enum HashmapType type HASHMAP_DEBUG_PARAMS) {
+        HashmapBase *q;
+
+        assert(h);
+
+        if (*h)
+                return 0;
+
+        q = hashmap_base_new(hash_ops, type HASHMAP_DEBUG_PASS_ARGS);
+        if (!q)
+                return -ENOMEM;
+
+        *h = q;
+        return 0;
+}
+
+int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
+        return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS);
+}
+
+int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
+        return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS);
+}
+
+int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
+        return hashmap_base_ensure_allocated((HashmapBase**)s, hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS);
+}
+
+static void hashmap_free_no_clear(HashmapBase *h) {
+        assert(!h->has_indirect);
+        assert(h->n_direct_entries == 0);
+
+#if ENABLE_DEBUG_HASHMAP
+        assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0);
+        LIST_REMOVE(debug_list, hashmap_debug_list, &h->debug);
+        assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0);
+#endif
+
+        if (h->from_pool) {
+                /* Ensure that the object didn't get migrated between threads. */
+                assert_se(is_main_thread());
+                mempool_free_tile(hashmap_type_info[h->type].mempool, h);
+        } else
+                free(h);
+}
+
+HashmapBase *internal_hashmap_free(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value) {
+        if (h) {
+                internal_hashmap_clear(h, default_free_key, default_free_value);
+                hashmap_free_no_clear(h);
+        }
+
+        return NULL;
+}
+
+void internal_hashmap_clear(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value) {
+        free_func_t free_key, free_value;
+        if (!h)
+                return;
+
+        free_key = h->hash_ops->free_key ?: default_free_key;
+        free_value = h->hash_ops->free_value ?: default_free_value;
+
+        if (free_key || free_value) {
+
+                /* If destructor calls are defined, let's destroy things defensively: let's take the item out of the
+                 * hash table, and only then call the destructor functions. If these destructors then try to unregister
+                 * themselves from our hash table a second time, the entry is already gone. */
+
+                while (internal_hashmap_size(h) > 0) {
+                        void *k = NULL;
+                        void *v;
+
+                        v = internal_hashmap_first_key_and_value(h, true, &k);
+
+                        if (free_key)
+                                free_key(k);
+
+                        if (free_value)
+                                free_value(v);
+                }
+        }
+
+        if (h->has_indirect) {
+                free(h->indirect.storage);
+                h->has_indirect = false;
+        }
+
+        h->n_direct_entries = 0;
+        reset_direct_storage(h);
+
+        if (h->type == HASHMAP_TYPE_ORDERED) {
+                OrderedHashmap *lh = (OrderedHashmap*) h;
+                lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL;
+        }
+
+        base_set_dirty(h);
+}
+
+static int resize_buckets(HashmapBase *h, unsigned entries_add);
+
+/*
+ * Finds an empty bucket to put an entry into, starting the scan at 'idx'.
+ * Performs Robin Hood swaps as it goes. The entry to put must be placed
+ * by the caller into swap slot IDX_PUT.
+ * If used for in-place resizing, may leave a displaced entry in swap slot
+ * IDX_PUT. Caller must rehash it next.
+ * Returns: true if it left a displaced entry to rehash next in IDX_PUT,
+ *          false otherwise.
+ */
+static bool hashmap_put_robin_hood(HashmapBase *h, unsigned idx,
+                                   struct swap_entries *swap) {
+        dib_raw_t raw_dib, *dibs;
+        unsigned dib, distance;
+
+#if ENABLE_DEBUG_HASHMAP
+        h->debug.put_count++;
+#endif
+
+        dibs = dib_raw_ptr(h);
+
+        for (distance = 0; ; distance++) {
+                raw_dib = dibs[idx];
+                if (IN_SET(raw_dib, DIB_RAW_FREE, DIB_RAW_REHASH)) {
+                        if (raw_dib == DIB_RAW_REHASH)
+                                bucket_move_entry(h, swap, idx, IDX_TMP);
+
+                        if (h->has_indirect && h->indirect.idx_lowest_entry > idx)
+                                h->indirect.idx_lowest_entry = idx;
+
+                        bucket_set_dib(h, idx, distance);
+                        bucket_move_entry(h, swap, IDX_PUT, idx);
+                        if (raw_dib == DIB_RAW_REHASH) {
+                                bucket_move_entry(h, swap, IDX_TMP, IDX_PUT);
+                                return true;
+                        }
+
+                        return false;
+                }
+
+                dib = bucket_calculate_dib(h, idx, raw_dib);
+
+                if (dib < distance) {
+                        /* Found a wealthier entry. Go Robin Hood! */
+                        bucket_set_dib(h, idx, distance);
+
+                        /* swap the entries */
+                        bucket_move_entry(h, swap, idx, IDX_TMP);
+                        bucket_move_entry(h, swap, IDX_PUT, idx);
+                        bucket_move_entry(h, swap, IDX_TMP, IDX_PUT);
+
+                        distance = dib;
+                }
+
+                idx = next_idx(h, idx);
+        }
+}
+
+/*
+ * Puts an entry into a hashmap, boldly - no check whether key already exists.
+ * The caller must place the entry (only its key and value, not link indexes)
+ * in swap slot IDX_PUT.
+ * Caller must ensure: the key does not exist yet in the hashmap.
+ *                     that resize is not needed if !may_resize.
+ * Returns: 1 if entry was put successfully.
+ *          -ENOMEM if may_resize==true and resize failed with -ENOMEM.
+ *          Cannot return -ENOMEM if !may_resize.
+ */
+static int hashmap_base_put_boldly(HashmapBase *h, unsigned idx,
+                                   struct swap_entries *swap, bool may_resize) {
+        struct ordered_hashmap_entry *new_entry;
+        int r;
+
+        assert(idx < n_buckets(h));
+
+        new_entry = bucket_at_swap(swap, IDX_PUT);
+
+        if (may_resize) {
+                r = resize_buckets(h, 1);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        idx = bucket_hash(h, new_entry->p.b.key);
+        }
+        assert(n_entries(h) < n_buckets(h));
+
+        if (h->type == HASHMAP_TYPE_ORDERED) {
+                OrderedHashmap *lh = (OrderedHashmap*) h;
+
+                new_entry->iterate_next = IDX_NIL;
+                new_entry->iterate_previous = lh->iterate_list_tail;
+
+                if (lh->iterate_list_tail != IDX_NIL) {
+                        struct ordered_hashmap_entry *old_tail;
+
+                        old_tail = ordered_bucket_at(lh, lh->iterate_list_tail);
+                        assert(old_tail->iterate_next == IDX_NIL);
+                        old_tail->iterate_next = IDX_PUT;
+                }
+
+                lh->iterate_list_tail = IDX_PUT;
+                if (lh->iterate_list_head == IDX_NIL)
+                        lh->iterate_list_head = IDX_PUT;
+        }
+
+        assert_se(hashmap_put_robin_hood(h, idx, swap) == false);
+
+        n_entries_inc(h);
+#if ENABLE_DEBUG_HASHMAP
+        h->debug.max_entries = MAX(h->debug.max_entries, n_entries(h));
+#endif
+
+        base_set_dirty(h);
+
+        return 1;
+}
+#define hashmap_put_boldly(h, idx, swap, may_resize) \
+        hashmap_base_put_boldly(HASHMAP_BASE(h), idx, swap, may_resize)
+
+/*
+ * Returns 0 if resize is not needed.
+ *         1 if successfully resized.
+ *         -ENOMEM on allocation failure.
+ */
+static int resize_buckets(HashmapBase *h, unsigned entries_add) {
+        struct swap_entries swap;
+        void *new_storage;
+        dib_raw_t *old_dibs, *new_dibs;
+        const struct hashmap_type_info *hi;
+        unsigned idx, optimal_idx;
+        unsigned old_n_buckets, new_n_buckets, n_rehashed, new_n_entries;
+        uint8_t new_shift;
+        bool rehash_next;
+
+        assert(h);
+
+        hi = &hashmap_type_info[h->type];
+        new_n_entries = n_entries(h) + entries_add;
+
+        /* overflow? */
+        if (_unlikely_(new_n_entries < entries_add))
+                return -ENOMEM;
+
+        /* For direct storage we allow 100% load, because it's tiny. */
+        if (!h->has_indirect && new_n_entries <= hi->n_direct_buckets)
+                return 0;
+
+        /*
+         * Load factor = n/m = 1 - (1/INV_KEEP_FREE).
+         * From it follows: m = n + n/(INV_KEEP_FREE - 1)
+         */
+        new_n_buckets = new_n_entries + new_n_entries / (INV_KEEP_FREE - 1);
+        /* overflow? */
+        if (_unlikely_(new_n_buckets < new_n_entries))
+                return -ENOMEM;
+
+        if (_unlikely_(new_n_buckets > UINT_MAX / (hi->entry_size + sizeof(dib_raw_t))))
+                return -ENOMEM;
+
+        old_n_buckets = n_buckets(h);
+
+        if (_likely_(new_n_buckets <= old_n_buckets))
+                return 0;
+
+        new_shift = log2u_round_up(MAX(
+                        new_n_buckets * (hi->entry_size + sizeof(dib_raw_t)),
+                        2 * sizeof(struct direct_storage)));
+
+        /* Realloc storage (buckets and DIB array). */
+        new_storage = realloc(h->has_indirect ? h->indirect.storage : NULL,
+                              1U << new_shift);
+        if (!new_storage)
+                return -ENOMEM;
+
+        /* Must upgrade direct to indirect storage. */
+        if (!h->has_indirect) {
+                memcpy(new_storage, h->direct.storage,
+                       old_n_buckets * (hi->entry_size + sizeof(dib_raw_t)));
+                h->indirect.n_entries = h->n_direct_entries;
+                h->indirect.idx_lowest_entry = 0;
+                h->n_direct_entries = 0;
+        }
+
+        /* Get a new hash key. If we've just upgraded to indirect storage,
+         * allow reusing a previously generated key. It's still a different key
+         * from the shared one that we used for direct storage. */
+        get_hash_key(h->indirect.hash_key, !h->has_indirect);
+
+        h->has_indirect = true;
+        h->indirect.storage = new_storage;
+        h->indirect.n_buckets = (1U << new_shift) /
+                                (hi->entry_size + sizeof(dib_raw_t));
+
+        old_dibs = (dib_raw_t*)((uint8_t*) new_storage + hi->entry_size * old_n_buckets);
+        new_dibs = dib_raw_ptr(h);
+
+        /*
+         * Move the DIB array to the new place, replacing valid DIB values with
+         * DIB_RAW_REHASH to indicate all of the used buckets need rehashing.
+         * Note: Overlap is not possible, because we have at least doubled the
+         * number of buckets and dib_raw_t is smaller than any entry type.
+         */
+        for (idx = 0; idx < old_n_buckets; idx++) {
+                assert(old_dibs[idx] != DIB_RAW_REHASH);
+                new_dibs[idx] = old_dibs[idx] == DIB_RAW_FREE ? DIB_RAW_FREE
+                                                              : DIB_RAW_REHASH;
+        }
+
+        /* Zero the area of newly added entries (including the old DIB area) */
+        memzero(bucket_at(h, old_n_buckets),
+               (n_buckets(h) - old_n_buckets) * hi->entry_size);
+
+        /* The upper half of the new DIB array needs initialization */
+        memset(&new_dibs[old_n_buckets], DIB_RAW_INIT,
+               (n_buckets(h) - old_n_buckets) * sizeof(dib_raw_t));
+
+        /* Rehash entries that need it */
+        n_rehashed = 0;
+        for (idx = 0; idx < old_n_buckets; idx++) {
+                if (new_dibs[idx] != DIB_RAW_REHASH)
+                        continue;
+
+                optimal_idx = bucket_hash(h, bucket_at(h, idx)->key);
+
+                /*
+                 * Not much to do if by luck the entry hashes to its current
+                 * location. Just set its DIB.
+                 */
+                if (optimal_idx == idx) {
+                        new_dibs[idx] = 0;
+                        n_rehashed++;
+                        continue;
+                }
+
+                new_dibs[idx] = DIB_RAW_FREE;
+                bucket_move_entry(h, &swap, idx, IDX_PUT);
+                /* bucket_move_entry does not clear the source */
+                memzero(bucket_at(h, idx), hi->entry_size);
+
+                do {
+                        /*
+                         * Find the new bucket for the current entry. This may make
+                         * another entry homeless and load it into IDX_PUT.
+                         */
+                        rehash_next = hashmap_put_robin_hood(h, optimal_idx, &swap);
+                        n_rehashed++;
+
+                        /* Did the current entry displace another one? */
+                        if (rehash_next)
+                                optimal_idx = bucket_hash(h, bucket_at_swap(&swap, IDX_PUT)->p.b.key);
+                } while (rehash_next);
+        }
+
+        assert(n_rehashed == n_entries(h));
+
+        return 1;
+}
+
+/*
+ * Finds an entry with a matching key
+ * Returns: index of the found entry, or IDX_NIL if not found.
+ */
+static unsigned base_bucket_scan(HashmapBase *h, unsigned idx, const void *key) {
+        struct hashmap_base_entry *e;
+        unsigned dib, distance;
+        dib_raw_t *dibs = dib_raw_ptr(h);
+
+        assert(idx < n_buckets(h));
+
+        for (distance = 0; ; distance++) {
+                if (dibs[idx] == DIB_RAW_FREE)
+                        return IDX_NIL;
+
+                dib = bucket_calculate_dib(h, idx, dibs[idx]);
+
+                if (dib < distance)
+                        return IDX_NIL;
+                if (dib == distance) {
+                        e = bucket_at(h, idx);
+                        if (h->hash_ops->compare(e->key, key) == 0)
+                                return idx;
+                }
+
+                idx = next_idx(h, idx);
+        }
+}
+#define bucket_scan(h, idx, key) base_bucket_scan(HASHMAP_BASE(h), idx, key)
+
+int hashmap_put(Hashmap *h, const void *key, void *value) {
+        struct swap_entries swap;
+        struct plain_hashmap_entry *e;
+        unsigned hash, idx;
+
+        assert(h);
+
+        hash = bucket_hash(h, key);
+        idx = bucket_scan(h, hash, key);
+        if (idx != IDX_NIL) {
+                e = plain_bucket_at(h, idx);
+                if (e->value == value)
+                        return 0;
+                return -EEXIST;
+        }
+
+        e = &bucket_at_swap(&swap, IDX_PUT)->p;
+        e->b.key = key;
+        e->value = value;
+        return hashmap_put_boldly(h, hash, &swap, true);
+}
+
+int set_put(Set *s, const void *key) {
+        struct swap_entries swap;
+        struct hashmap_base_entry *e;
+        unsigned hash, idx;
+
+        assert(s);
+
+        hash = bucket_hash(s, key);
+        idx = bucket_scan(s, hash, key);
+        if (idx != IDX_NIL)
+                return 0;
+
+        e = &bucket_at_swap(&swap, IDX_PUT)->p.b;
+        e->key = key;
+        return hashmap_put_boldly(s, hash, &swap, true);
+}
+
+int hashmap_replace(Hashmap *h, const void *key, void *value) {
+        struct swap_entries swap;
+        struct plain_hashmap_entry *e;
+        unsigned hash, idx;
+
+        assert(h);
+
+        hash = bucket_hash(h, key);
+        idx = bucket_scan(h, hash, key);
+        if (idx != IDX_NIL) {
+                e = plain_bucket_at(h, idx);
+#if ENABLE_DEBUG_HASHMAP
+                /* Although the key is equal, the key pointer may have changed,
+                 * and this would break our assumption for iterating. So count
+                 * this operation as incompatible with iteration. */
+                if (e->b.key != key) {
+                        h->b.debug.put_count++;
+                        h->b.debug.rem_count++;
+                        h->b.debug.last_rem_idx = idx;
+                }
+#endif
+                e->b.key = key;
+                e->value = value;
+                hashmap_set_dirty(h);
+
+                return 0;
+        }
+
+        e = &bucket_at_swap(&swap, IDX_PUT)->p;
+        e->b.key = key;
+        e->value = value;
+        return hashmap_put_boldly(h, hash, &swap, true);
+}
+
+int hashmap_update(Hashmap *h, const void *key, void *value) {
+        struct plain_hashmap_entry *e;
+        unsigned hash, idx;
+
+        assert(h);
+
+        hash = bucket_hash(h, key);
+        idx = bucket_scan(h, hash, key);
+        if (idx == IDX_NIL)
+                return -ENOENT;
+
+        e = plain_bucket_at(h, idx);
+        e->value = value;
+        hashmap_set_dirty(h);
+
+        return 0;
+}
+
+void *internal_hashmap_get(HashmapBase *h, const void *key) {
+        struct hashmap_base_entry *e;
+        unsigned hash, idx;
+
+        if (!h)
+                return NULL;
+
+        hash = bucket_hash(h, key);
+        idx = bucket_scan(h, hash, key);
+        if (idx == IDX_NIL)
+                return NULL;
+
+        e = bucket_at(h, idx);
+        return entry_value(h, e);
+}
+
+void *hashmap_get2(Hashmap *h, const void *key, void **key2) {
+        struct plain_hashmap_entry *e;
+        unsigned hash, idx;
+
+        if (!h)
+                return NULL;
+
+        hash = bucket_hash(h, key);
+        idx = bucket_scan(h, hash, key);
+        if (idx == IDX_NIL)
+                return NULL;
+
+        e = plain_bucket_at(h, idx);
+        if (key2)
+                *key2 = (void*) e->b.key;
+
+        return e->value;
+}
+
+bool internal_hashmap_contains(HashmapBase *h, const void *key) {
+        unsigned hash;
+
+        if (!h)
+                return false;
+
+        hash = bucket_hash(h, key);
+        return bucket_scan(h, hash, key) != IDX_NIL;
+}
+
+void *internal_hashmap_remove(HashmapBase *h, const void *key) {
+        struct hashmap_base_entry *e;
+        unsigned hash, idx;
+        void *data;
+
+        if (!h)
+                return NULL;
+
+        hash = bucket_hash(h, key);
+        idx = bucket_scan(h, hash, key);
+        if (idx == IDX_NIL)
+                return NULL;
+
+        e = bucket_at(h, idx);
+        data = entry_value(h, e);
+        remove_entry(h, idx);
+
+        return data;
+}
+
+void *hashmap_remove2(Hashmap *h, const void *key, void **rkey) {
+        struct plain_hashmap_entry *e;
+        unsigned hash, idx;
+        void *data;
+
+        if (!h) {
+                if (rkey)
+                        *rkey = NULL;
+                return NULL;
+        }
+
+        hash = bucket_hash(h, key);
+        idx = bucket_scan(h, hash, key);
+        if (idx == IDX_NIL) {
+                if (rkey)
+                        *rkey = NULL;
+                return NULL;
+        }
+
+        e = plain_bucket_at(h, idx);
+        data = e->value;
+        if (rkey)
+                *rkey = (void*) e->b.key;
+
+        remove_entry(h, idx);
+
+        return data;
+}
+
+int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) {
+        struct swap_entries swap;
+        struct plain_hashmap_entry *e;
+        unsigned old_hash, new_hash, idx;
+
+        if (!h)
+                return -ENOENT;
+
+        old_hash = bucket_hash(h, old_key);
+        idx = bucket_scan(h, old_hash, old_key);
+        if (idx == IDX_NIL)
+                return -ENOENT;
+
+        new_hash = bucket_hash(h, new_key);
+        if (bucket_scan(h, new_hash, new_key) != IDX_NIL)
+                return -EEXIST;
+
+        remove_entry(h, idx);
+
+        e = &bucket_at_swap(&swap, IDX_PUT)->p;
+        e->b.key = new_key;
+        e->value = value;
+        assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1);
+
+        return 0;
+}
+
+int set_remove_and_put(Set *s, const void *old_key, const void *new_key) {
+        struct swap_entries swap;
+        struct hashmap_base_entry *e;
+        unsigned old_hash, new_hash, idx;
+
+        if (!s)
+                return -ENOENT;
+
+        old_hash = bucket_hash(s, old_key);
+        idx = bucket_scan(s, old_hash, old_key);
+        if (idx == IDX_NIL)
+                return -ENOENT;
+
+        new_hash = bucket_hash(s, new_key);
+        if (bucket_scan(s, new_hash, new_key) != IDX_NIL)
+                return -EEXIST;
+
+        remove_entry(s, idx);
+
+        e = &bucket_at_swap(&swap, IDX_PUT)->p.b;
+        e->key = new_key;
+        assert_se(hashmap_put_boldly(s, new_hash, &swap, false) == 1);
+
+        return 0;
+}
+
+int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) {
+        struct swap_entries swap;
+        struct plain_hashmap_entry *e;
+        unsigned old_hash, new_hash, idx_old, idx_new;
+
+        if (!h)
+                return -ENOENT;
+
+        old_hash = bucket_hash(h, old_key);
+        idx_old = bucket_scan(h, old_hash, old_key);
+        if (idx_old == IDX_NIL)
+                return -ENOENT;
+
+        old_key = bucket_at(HASHMAP_BASE(h), idx_old)->key;
+
+        new_hash = bucket_hash(h, new_key);
+        idx_new = bucket_scan(h, new_hash, new_key);
+        if (idx_new != IDX_NIL)
+                if (idx_old != idx_new) {
+                        remove_entry(h, idx_new);
+                        /* Compensate for a possible backward shift. */
+                        if (old_key != bucket_at(HASHMAP_BASE(h), idx_old)->key)
+                                idx_old = prev_idx(HASHMAP_BASE(h), idx_old);
+                        assert(old_key == bucket_at(HASHMAP_BASE(h), idx_old)->key);
+                }
+
+        remove_entry(h, idx_old);
+
+        e = &bucket_at_swap(&swap, IDX_PUT)->p;
+        e->b.key = new_key;
+        e->value = value;
+        assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1);
+
+        return 0;
+}
+
+void *internal_hashmap_remove_value(HashmapBase *h, const void *key, void *value) {
+        struct hashmap_base_entry *e;
+        unsigned hash, idx;
+
+        if (!h)
+                return NULL;
+
+        hash = bucket_hash(h, key);
+        idx = bucket_scan(h, hash, key);
+        if (idx == IDX_NIL)
+                return NULL;
+
+        e = bucket_at(h, idx);
+        if (entry_value(h, e) != value)
+                return NULL;
+
+        remove_entry(h, idx);
+
+        return value;
+}
+
+static unsigned find_first_entry(HashmapBase *h) {
+        Iterator i = ITERATOR_FIRST;
+
+        if (!h || !n_entries(h))
+                return IDX_NIL;
+
+        return hashmap_iterate_entry(h, &i);
+}
+
+void *internal_hashmap_first_key_and_value(HashmapBase *h, bool remove, void **ret_key) {
+        struct hashmap_base_entry *e;
+        void *key, *data;
+        unsigned idx;
+
+        idx = find_first_entry(h);
+        if (idx == IDX_NIL) {
+                if (ret_key)
+                        *ret_key = NULL;
+                return NULL;
+        }
+
+        e = bucket_at(h, idx);
+        key = (void*) e->key;
+        data = entry_value(h, e);
+
+        if (remove)
+                remove_entry(h, idx);
+
+        if (ret_key)
+                *ret_key = key;
+
+        return data;
+}
+
+unsigned internal_hashmap_size(HashmapBase *h) {
+
+        if (!h)
+                return 0;
+
+        return n_entries(h);
+}
+
+unsigned internal_hashmap_buckets(HashmapBase *h) {
+
+        if (!h)
+                return 0;
+
+        return n_buckets(h);
+}
+
+int internal_hashmap_merge(Hashmap *h, Hashmap *other) {
+        Iterator i;
+        unsigned idx;
+
+        assert(h);
+
+        HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) {
+                struct plain_hashmap_entry *pe = plain_bucket_at(other, idx);
+                int r;
+
+                r = hashmap_put(h, pe->b.key, pe->value);
+                if (r < 0 && r != -EEXIST)
+                        return r;
+        }
+
+        return 0;
+}
+
+int set_merge(Set *s, Set *other) {
+        Iterator i;
+        unsigned idx;
+
+        assert(s);
+
+        HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) {
+                struct set_entry *se = set_bucket_at(other, idx);
+                int r;
+
+                r = set_put(s, se->b.key);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add) {
+        int r;
+
+        assert(h);
+
+        r = resize_buckets(h, entries_add);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+/*
+ * The same as hashmap_merge(), but every new item from other is moved to h.
+ * Keys already in h are skipped and stay in other.
+ * Returns: 0 on success.
+ *          -ENOMEM on alloc failure, in which case no move has been done.
+ */
+int internal_hashmap_move(HashmapBase *h, HashmapBase *other) {
+        struct swap_entries swap;
+        struct hashmap_base_entry *e, *n;
+        Iterator i;
+        unsigned idx;
+        int r;
+
+        assert(h);
+
+        if (!other)
+                return 0;
+
+        assert(other->type == h->type);
+
+        /*
+         * This reserves buckets for the worst case, where none of other's
+         * entries are yet present in h. This is preferable to risking
+         * an allocation failure in the middle of the moving and having to
+         * rollback or return a partial result.
+         */
+        r = resize_buckets(h, n_entries(other));
+        if (r < 0)
+                return r;
+
+        HASHMAP_FOREACH_IDX(idx, other, i) {
+                unsigned h_hash;
+
+                e = bucket_at(other, idx);
+                h_hash = bucket_hash(h, e->key);
+                if (bucket_scan(h, h_hash, e->key) != IDX_NIL)
+                        continue;
+
+                n = &bucket_at_swap(&swap, IDX_PUT)->p.b;
+                n->key = e->key;
+                if (h->type != HASHMAP_TYPE_SET)
+                        ((struct plain_hashmap_entry*) n)->value =
+                                ((struct plain_hashmap_entry*) e)->value;
+                assert_se(hashmap_put_boldly(h, h_hash, &swap, false) == 1);
+
+                remove_entry(other, idx);
+        }
+
+        return 0;
+}
+
+int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key) {
+        struct swap_entries swap;
+        unsigned h_hash, other_hash, idx;
+        struct hashmap_base_entry *e, *n;
+        int r;
+
+        assert(h);
+
+        h_hash = bucket_hash(h, key);
+        if (bucket_scan(h, h_hash, key) != IDX_NIL)
+                return -EEXIST;
+
+        if (!other)
+                return -ENOENT;
+
+        assert(other->type == h->type);
+
+        other_hash = bucket_hash(other, key);
+        idx = bucket_scan(other, other_hash, key);
+        if (idx == IDX_NIL)
+                return -ENOENT;
+
+        e = bucket_at(other, idx);
+
+        n = &bucket_at_swap(&swap, IDX_PUT)->p.b;
+        n->key = e->key;
+        if (h->type != HASHMAP_TYPE_SET)
+                ((struct plain_hashmap_entry*) n)->value =
+                        ((struct plain_hashmap_entry*) e)->value;
+        r = hashmap_put_boldly(h, h_hash, &swap, true);
+        if (r < 0)
+                return r;
+
+        remove_entry(other, idx);
+        return 0;
+}
+
+HashmapBase *internal_hashmap_copy(HashmapBase *h) {
+        HashmapBase *copy;
+        int r;
+
+        assert(h);
+
+        copy = hashmap_base_new(h->hash_ops, h->type  HASHMAP_DEBUG_SRC_ARGS);
+        if (!copy)
+                return NULL;
+
+        switch (h->type) {
+        case HASHMAP_TYPE_PLAIN:
+        case HASHMAP_TYPE_ORDERED:
+                r = hashmap_merge((Hashmap*)copy, (Hashmap*)h);
+                break;
+        case HASHMAP_TYPE_SET:
+                r = set_merge((Set*)copy, (Set*)h);
+                break;
+        default:
+                assert_not_reached("Unknown hashmap type");
+        }
+
+        if (r < 0) {
+                internal_hashmap_free(copy, false, false);
+                return NULL;
+        }
+
+        return copy;
+}
+
+char **internal_hashmap_get_strv(HashmapBase *h) {
+        char **sv;
+        Iterator i;
+        unsigned idx, n;
+
+        sv = new(char*, n_entries(h)+1);
+        if (!sv)
+                return NULL;
+
+        n = 0;
+        HASHMAP_FOREACH_IDX(idx, h, i)
+                sv[n++] = entry_value(h, bucket_at(h, idx));
+        sv[n] = NULL;
+
+        return sv;
+}
+
+void *ordered_hashmap_next(OrderedHashmap *h, const void *key) {
+        struct ordered_hashmap_entry *e;
+        unsigned hash, idx;
+
+        if (!h)
+                return NULL;
+
+        hash = bucket_hash(h, key);
+        idx = bucket_scan(h, hash, key);
+        if (idx == IDX_NIL)
+                return NULL;
+
+        e = ordered_bucket_at(h, idx);
+        if (e->iterate_next == IDX_NIL)
+                return NULL;
+        return ordered_bucket_at(h, e->iterate_next)->p.value;
+}
+
+int set_consume(Set *s, void *value) {
+        int r;
+
+        assert(s);
+        assert(value);
+
+        r = set_put(s, value);
+        if (r <= 0)
+                free(value);
+
+        return r;
+}
+
+int set_put_strdup(Set *s, const char *p) {
+        char *c;
+
+        assert(s);
+        assert(p);
+
+        if (set_contains(s, (char*) p))
+                return 0;
+
+        c = strdup(p);
+        if (!c)
+                return -ENOMEM;
+
+        return set_consume(s, c);
+}
+
+int set_put_strdupv(Set *s, char **l) {
+        int n = 0, r;
+        char **i;
+
+        assert(s);
+
+        STRV_FOREACH(i, l) {
+                r = set_put_strdup(s, *i);
+                if (r < 0)
+                        return r;
+
+                n += r;
+        }
+
+        return n;
+}
+
+int set_put_strsplit(Set *s, const char *v, const char *separators, ExtractFlags flags) {
+        const char *p = v;
+        int r;
+
+        assert(s);
+        assert(v);
+
+        for (;;) {
+                char *word;
+
+                r = extract_first_word(&p, &word, separators, flags);
+                if (r <= 0)
+                        return r;
+
+                r = set_consume(s, word);
+                if (r < 0)
+                        return r;
+        }
+}
+
+/* expand the cachemem if needed, return true if newly (re)activated. */
+static int cachemem_maintain(CacheMem *mem, unsigned size) {
+        assert(mem);
+
+        if (!GREEDY_REALLOC(mem->ptr, mem->n_allocated, size)) {
+                if (size > 0)
+                        return -ENOMEM;
+        }
+
+        if (!mem->active) {
+                mem->active = true;
+                return true;
+        }
+
+        return false;
+}
+
+int iterated_cache_get(IteratedCache *cache, const void ***res_keys, const void ***res_values, unsigned *res_n_entries) {
+        bool sync_keys = false, sync_values = false;
+        unsigned size;
+        int r;
+
+        assert(cache);
+        assert(cache->hashmap);
+
+        size = n_entries(cache->hashmap);
+
+        if (res_keys) {
+                r = cachemem_maintain(&cache->keys, size);
+                if (r < 0)
+                        return r;
+
+                sync_keys = r;
+        } else
+                cache->keys.active = false;
+
+        if (res_values) {
+                r = cachemem_maintain(&cache->values, size);
+                if (r < 0)
+                        return r;
+
+                sync_values = r;
+        } else
+                cache->values.active = false;
+
+        if (cache->hashmap->dirty) {
+                if (cache->keys.active)
+                        sync_keys = true;
+                if (cache->values.active)
+                        sync_values = true;
+
+                cache->hashmap->dirty = false;
+        }
+
+        if (sync_keys || sync_values) {
+                unsigned i, idx;
+                Iterator iter;
+
+                i = 0;
+                HASHMAP_FOREACH_IDX(idx, cache->hashmap, iter) {
+                        struct hashmap_base_entry *e;
+
+                        e = bucket_at(cache->hashmap, idx);
+
+                        if (sync_keys)
+                                cache->keys.ptr[i] = e->key;
+                        if (sync_values)
+                                cache->values.ptr[i] = entry_value(cache->hashmap, e);
+                        i++;
+                }
+        }
+
+        if (res_keys)
+                *res_keys = cache->keys.ptr;
+        if (res_values)
+                *res_values = cache->values.ptr;
+        if (res_n_entries)
+                *res_n_entries = size;
+
+        return 0;
+}
+
+IteratedCache *iterated_cache_free(IteratedCache *cache) {
+        if (cache) {
+                free(cache->keys.ptr);
+                free(cache->values.ptr);
+                free(cache);
+        }
+
+        return NULL;
+}
diff --git a/shared/systemd/src/basic/hashmap.h b/shared/systemd/src/basic/hashmap.h
new file mode 100644
index 00000000..e16a9f9e
--- /dev/null
+++ b/shared/systemd/src/basic/hashmap.h
@@ -0,0 +1,429 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "hash-funcs.h"
+#include "macro.h"
+#include "util.h"
+
+/*
+ * A hash table implementation. As a minor optimization a NULL hashmap object
+ * will be treated as empty hashmap for all read operations. That way it is not
+ * necessary to instantiate an object for each Hashmap use.
+ *
+ * If ENABLE_DEBUG_HASHMAP is defined (by configuring with --enable-debug=hashmap),
+ * the implementation will:
+ * - store extra data for debugging and statistics (see tools/gdb-sd_dump_hashmaps.py)
+ * - perform extra checks for invalid use of iterators
+ */
+
+#define HASH_KEY_SIZE 16
+
+typedef void* (*hashmap_destroy_t)(void *p);
+
+/* The base type for all hashmap and set types. Many functions in the
+ * implementation take (HashmapBase*) parameters and are run-time polymorphic,
+ * though the API is not meant to be polymorphic (do not call functions
+ * internal_*() directly). */
+typedef struct HashmapBase HashmapBase;
+
+/* Specific hashmap/set types */
+typedef struct Hashmap Hashmap;               /* Maps keys to values */
+typedef struct OrderedHashmap OrderedHashmap; /* Like Hashmap, but also remembers entry insertion order */
+typedef struct Set Set;                       /* Stores just keys */
+
+typedef struct IteratedCache IteratedCache;   /* Caches the iterated order of one of the above */
+
+/* Ideally the Iterator would be an opaque struct, but it is instantiated
+ * by hashmap users, so the definition has to be here. Do not use its fields
+ * directly. */
+typedef struct {
+        unsigned idx;         /* index of an entry to be iterated next */
+        const void *next_key; /* expected value of that entry's key pointer */
+#if ENABLE_DEBUG_HASHMAP
+        unsigned put_count;   /* hashmap's put_count recorded at start of iteration */
+        unsigned rem_count;   /* hashmap's rem_count in previous iteration */
+        unsigned prev_idx;    /* idx in previous iteration */
+#endif
+} Iterator;
+
+#define _IDX_ITERATOR_FIRST (UINT_MAX - 1)
+#define ITERATOR_FIRST ((Iterator) { .idx = _IDX_ITERATOR_FIRST, .next_key = NULL })
+
+/* Macros for type checking */
+#define PTR_COMPATIBLE_WITH_HASHMAP_BASE(h) \
+        (__builtin_types_compatible_p(typeof(h), HashmapBase*) || \
+         __builtin_types_compatible_p(typeof(h), Hashmap*) || \
+         __builtin_types_compatible_p(typeof(h), OrderedHashmap*) || \
+         __builtin_types_compatible_p(typeof(h), Set*))
+
+#define PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h) \
+        (__builtin_types_compatible_p(typeof(h), Hashmap*) || \
+         __builtin_types_compatible_p(typeof(h), OrderedHashmap*)) \
+
+#define HASHMAP_BASE(h) \
+        __builtin_choose_expr(PTR_COMPATIBLE_WITH_HASHMAP_BASE(h), \
+                (HashmapBase*)(h), \
+                (void)0)
+
+#define PLAIN_HASHMAP(h) \
+        __builtin_choose_expr(PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h), \
+                (Hashmap*)(h), \
+                (void)0)
+
+#if ENABLE_DEBUG_HASHMAP
+# define HASHMAP_DEBUG_PARAMS , const char *func, const char *file, int line
+# define HASHMAP_DEBUG_SRC_ARGS   , __func__, __FILE__, __LINE__
+# define HASHMAP_DEBUG_PASS_ARGS   , func, file, line
+#else
+# define HASHMAP_DEBUG_PARAMS
+# define HASHMAP_DEBUG_SRC_ARGS
+# define HASHMAP_DEBUG_PASS_ARGS
+#endif
+
+Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
+OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
+#define hashmap_new(ops) internal_hashmap_new(ops  HASHMAP_DEBUG_SRC_ARGS)
+#define ordered_hashmap_new(ops) internal_ordered_hashmap_new(ops  HASHMAP_DEBUG_SRC_ARGS)
+
+HashmapBase *internal_hashmap_free(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value);
+static inline Hashmap *hashmap_free(Hashmap *h) {
+        return (void*) internal_hashmap_free(HASHMAP_BASE(h), NULL, NULL);
+}
+static inline OrderedHashmap *ordered_hashmap_free(OrderedHashmap *h) {
+        return (void*) internal_hashmap_free(HASHMAP_BASE(h), NULL, NULL);
+}
+
+static inline Hashmap *hashmap_free_free(Hashmap *h) {
+        return (void*) internal_hashmap_free(HASHMAP_BASE(h), NULL, free);
+}
+static inline OrderedHashmap *ordered_hashmap_free_free(OrderedHashmap *h) {
+        return (void*) internal_hashmap_free(HASHMAP_BASE(h), NULL, free);
+}
+
+static inline Hashmap *hashmap_free_free_key(Hashmap *h) {
+        return (void*) internal_hashmap_free(HASHMAP_BASE(h), free, NULL);
+}
+static inline OrderedHashmap *ordered_hashmap_free_free_key(OrderedHashmap *h) {
+        return (void*) internal_hashmap_free(HASHMAP_BASE(h), free, NULL);
+}
+
+static inline Hashmap *hashmap_free_free_free(Hashmap *h) {
+        return (void*) internal_hashmap_free(HASHMAP_BASE(h), free, free);
+}
+static inline OrderedHashmap *ordered_hashmap_free_free_free(OrderedHashmap *h) {
+        return (void*) internal_hashmap_free(HASHMAP_BASE(h), free, free);
+}
+
+IteratedCache *iterated_cache_free(IteratedCache *cache);
+int iterated_cache_get(IteratedCache *cache, const void ***res_keys, const void ***res_values, unsigned *res_n_entries);
+
+HashmapBase *internal_hashmap_copy(HashmapBase *h);
+static inline Hashmap *hashmap_copy(Hashmap *h) {
+        return (Hashmap*) internal_hashmap_copy(HASHMAP_BASE(h));
+}
+static inline OrderedHashmap *ordered_hashmap_copy(OrderedHashmap *h) {
+        return (OrderedHashmap*) internal_hashmap_copy(HASHMAP_BASE(h));
+}
+
+int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
+int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
+#define hashmap_ensure_allocated(h, ops) internal_hashmap_ensure_allocated(h, ops  HASHMAP_DEBUG_SRC_ARGS)
+#define ordered_hashmap_ensure_allocated(h, ops) internal_ordered_hashmap_ensure_allocated(h, ops  HASHMAP_DEBUG_SRC_ARGS)
+
+IteratedCache *internal_hashmap_iterated_cache_new(HashmapBase *h);
+static inline IteratedCache *hashmap_iterated_cache_new(Hashmap *h) {
+        return (IteratedCache*) internal_hashmap_iterated_cache_new(HASHMAP_BASE(h));
+}
+static inline IteratedCache *ordered_hashmap_iterated_cache_new(OrderedHashmap *h) {
+        return (IteratedCache*) internal_hashmap_iterated_cache_new(HASHMAP_BASE(h));
+}
+
+int hashmap_put(Hashmap *h, const void *key, void *value);
+static inline int ordered_hashmap_put(OrderedHashmap *h, const void *key, void *value) {
+        return hashmap_put(PLAIN_HASHMAP(h), key, value);
+}
+
+int hashmap_update(Hashmap *h, const void *key, void *value);
+static inline int ordered_hashmap_update(OrderedHashmap *h, const void *key, void *value) {
+        return hashmap_update(PLAIN_HASHMAP(h), key, value);
+}
+
+int hashmap_replace(Hashmap *h, const void *key, void *value);
+static inline int ordered_hashmap_replace(OrderedHashmap *h, const void *key, void *value) {
+        return hashmap_replace(PLAIN_HASHMAP(h), key, value);
+}
+
+void *internal_hashmap_get(HashmapBase *h, const void *key);
+static inline void *hashmap_get(Hashmap *h, const void *key) {
+        return internal_hashmap_get(HASHMAP_BASE(h), key);
+}
+static inline void *ordered_hashmap_get(OrderedHashmap *h, const void *key) {
+        return internal_hashmap_get(HASHMAP_BASE(h), key);
+}
+
+void *hashmap_get2(Hashmap *h, const void *key, void **rkey);
+static inline void *ordered_hashmap_get2(OrderedHashmap *h, const void *key, void **rkey) {
+        return hashmap_get2(PLAIN_HASHMAP(h), key, rkey);
+}
+
+bool internal_hashmap_contains(HashmapBase *h, const void *key);
+static inline bool hashmap_contains(Hashmap *h, const void *key) {
+        return internal_hashmap_contains(HASHMAP_BASE(h), key);
+}
+static inline bool ordered_hashmap_contains(OrderedHashmap *h, const void *key) {
+        return internal_hashmap_contains(HASHMAP_BASE(h), key);
+}
+
+void *internal_hashmap_remove(HashmapBase *h, const void *key);
+static inline void *hashmap_remove(Hashmap *h, const void *key) {
+        return internal_hashmap_remove(HASHMAP_BASE(h), key);
+}
+static inline void *ordered_hashmap_remove(OrderedHashmap *h, const void *key) {
+        return internal_hashmap_remove(HASHMAP_BASE(h), key);
+}
+
+void *hashmap_remove2(Hashmap *h, const void *key, void **rkey);
+static inline void *ordered_hashmap_remove2(OrderedHashmap *h, const void *key, void **rkey) {
+        return hashmap_remove2(PLAIN_HASHMAP(h), key, rkey);
+}
+
+void *internal_hashmap_remove_value(HashmapBase *h, const void *key, void *value);
+static inline void *hashmap_remove_value(Hashmap *h, const void *key, void *value) {
+        return internal_hashmap_remove_value(HASHMAP_BASE(h), key, value);
+}
+
+static inline void *ordered_hashmap_remove_value(OrderedHashmap *h, const void *key, void *value) {
+        return hashmap_remove_value(PLAIN_HASHMAP(h), key, value);
+}
+
+int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value);
+static inline int ordered_hashmap_remove_and_put(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) {
+        return hashmap_remove_and_put(PLAIN_HASHMAP(h), old_key, new_key, value);
+}
+
+int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value);
+static inline int ordered_hashmap_remove_and_replace(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) {
+        return hashmap_remove_and_replace(PLAIN_HASHMAP(h), old_key, new_key, value);
+}
+
+/* Since merging data from a OrderedHashmap into a Hashmap or vice-versa
+ * should just work, allow this by having looser type-checking here. */
+int internal_hashmap_merge(Hashmap *h, Hashmap *other);
+#define hashmap_merge(h, other) internal_hashmap_merge(PLAIN_HASHMAP(h), PLAIN_HASHMAP(other))
+#define ordered_hashmap_merge(h, other) hashmap_merge(h, other)
+
+int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add);
+static inline int hashmap_reserve(Hashmap *h, unsigned entries_add) {
+        return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add);
+}
+static inline int ordered_hashmap_reserve(OrderedHashmap *h, unsigned entries_add) {
+        return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add);
+}
+
+int internal_hashmap_move(HashmapBase *h, HashmapBase *other);
+/* Unlike hashmap_merge, hashmap_move does not allow mixing the types. */
+static inline int hashmap_move(Hashmap *h, Hashmap *other) {
+        return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other));
+}
+static inline int ordered_hashmap_move(OrderedHashmap *h, OrderedHashmap *other) {
+        return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other));
+}
+
+int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key);
+static inline int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) {
+        return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key);
+}
+static inline int ordered_hashmap_move_one(OrderedHashmap *h, OrderedHashmap *other, const void *key) {
+        return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key);
+}
+
+unsigned internal_hashmap_size(HashmapBase *h) _pure_;
+static inline unsigned hashmap_size(Hashmap *h) {
+        return internal_hashmap_size(HASHMAP_BASE(h));
+}
+static inline unsigned ordered_hashmap_size(OrderedHashmap *h) {
+        return internal_hashmap_size(HASHMAP_BASE(h));
+}
+
+static inline bool hashmap_isempty(Hashmap *h) {
+        return hashmap_size(h) == 0;
+}
+static inline bool ordered_hashmap_isempty(OrderedHashmap *h) {
+        return ordered_hashmap_size(h) == 0;
+}
+
+unsigned internal_hashmap_buckets(HashmapBase *h) _pure_;
+static inline unsigned hashmap_buckets(Hashmap *h) {
+        return internal_hashmap_buckets(HASHMAP_BASE(h));
+}
+static inline unsigned ordered_hashmap_buckets(OrderedHashmap *h) {
+        return internal_hashmap_buckets(HASHMAP_BASE(h));
+}
+
+bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key);
+static inline bool hashmap_iterate(Hashmap *h, Iterator *i, void **value, const void **key) {
+        return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key);
+}
+static inline bool ordered_hashmap_iterate(OrderedHashmap *h, Iterator *i, void **value, const void **key) {
+        return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key);
+}
+
+void internal_hashmap_clear(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value);
+static inline void hashmap_clear(Hashmap *h) {
+        internal_hashmap_clear(HASHMAP_BASE(h), NULL, NULL);
+}
+static inline void ordered_hashmap_clear(OrderedHashmap *h) {
+        internal_hashmap_clear(HASHMAP_BASE(h), NULL, NULL);
+}
+
+static inline void hashmap_clear_free(Hashmap *h) {
+        internal_hashmap_clear(HASHMAP_BASE(h), NULL, free);
+}
+static inline void ordered_hashmap_clear_free(OrderedHashmap *h) {
+        internal_hashmap_clear(HASHMAP_BASE(h), NULL, free);
+}
+
+static inline void hashmap_clear_free_key(Hashmap *h) {
+        internal_hashmap_clear(HASHMAP_BASE(h), free, NULL);
+}
+static inline void ordered_hashmap_clear_free_key(OrderedHashmap *h) {
+        internal_hashmap_clear(HASHMAP_BASE(h), free, NULL);
+}
+
+static inline void hashmap_clear_free_free(Hashmap *h) {
+        internal_hashmap_clear(HASHMAP_BASE(h), free, free);
+}
+static inline void ordered_hashmap_clear_free_free(OrderedHashmap *h) {
+        internal_hashmap_clear(HASHMAP_BASE(h), free, free);
+}
+
+/*
+ * Note about all *_first*() functions
+ *
+ * For plain Hashmaps and Sets the order of entries is undefined.
+ * The functions find whatever entry is first in the implementation
+ * internal order.
+ *
+ * Only for OrderedHashmaps the order is well defined and finding
+ * the first entry is O(1).
+ */
+
+void *internal_hashmap_first_key_and_value(HashmapBase *h, bool remove, void **ret_key);
+static inline void *hashmap_steal_first_key_and_value(Hashmap *h, void **ret) {
+        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), true, ret);
+}
+static inline void *ordered_hashmap_steal_first_key_and_value(OrderedHashmap *h, void **ret) {
+        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), true, ret);
+}
+static inline void *hashmap_first_key_and_value(Hashmap *h, void **ret) {
+        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), false, ret);
+}
+static inline void *ordered_hashmap_first_key_and_value(OrderedHashmap *h, void **ret) {
+        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), false, ret);
+}
+
+static inline void *hashmap_steal_first(Hashmap *h) {
+        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), true, NULL);
+}
+static inline void *ordered_hashmap_steal_first(OrderedHashmap *h) {
+        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), true, NULL);
+}
+static inline void *hashmap_first(Hashmap *h) {
+        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), false, NULL);
+}
+static inline void *ordered_hashmap_first(OrderedHashmap *h) {
+        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), false, NULL);
+}
+
+static inline void *internal_hashmap_first_key(HashmapBase *h, bool remove) {
+        void *key = NULL;
+
+        (void) internal_hashmap_first_key_and_value(HASHMAP_BASE(h), remove, &key);
+        return key;
+}
+static inline void *hashmap_steal_first_key(Hashmap *h) {
+        return internal_hashmap_first_key(HASHMAP_BASE(h), true);
+}
+static inline void *ordered_hashmap_steal_first_key(OrderedHashmap *h) {
+        return internal_hashmap_first_key(HASHMAP_BASE(h), true);
+}
+static inline void *hashmap_first_key(Hashmap *h) {
+        return internal_hashmap_first_key(HASHMAP_BASE(h), false);
+}
+static inline void *ordered_hashmap_first_key(OrderedHashmap *h) {
+        return internal_hashmap_first_key(HASHMAP_BASE(h), false);
+}
+
+#define hashmap_clear_with_destructor(_s, _f)                   \
+        ({                                                      \
+                void *_item;                                    \
+                while ((_item = hashmap_steal_first(_s)))       \
+                        _f(_item);                              \
+        })
+#define hashmap_free_with_destructor(_s, _f)                    \
+        ({                                                      \
+                hashmap_clear_with_destructor(_s, _f);          \
+                hashmap_free(_s);                               \
+        })
+#define ordered_hashmap_clear_with_destructor(_s, _f)                   \
+        ({                                                              \
+                void *_item;                                            \
+                while ((_item = ordered_hashmap_steal_first(_s)))       \
+                        _f(_item);                                      \
+        })
+#define ordered_hashmap_free_with_destructor(_s, _f)                    \
+        ({                                                              \
+                ordered_hashmap_clear_with_destructor(_s, _f);          \
+                ordered_hashmap_free(_s);                               \
+        })
+
+/* no hashmap_next */
+void *ordered_hashmap_next(OrderedHashmap *h, const void *key);
+
+char **internal_hashmap_get_strv(HashmapBase *h);
+static inline char **hashmap_get_strv(Hashmap *h) {
+        return internal_hashmap_get_strv(HASHMAP_BASE(h));
+}
+static inline char **ordered_hashmap_get_strv(OrderedHashmap *h) {
+        return internal_hashmap_get_strv(HASHMAP_BASE(h));
+}
+
+/*
+ * Hashmaps are iterated in unpredictable order.
+ * OrderedHashmaps are an exception to this. They are iterated in the order
+ * the entries were inserted.
+ * It is safe to remove the current entry.
+ */
+#define HASHMAP_FOREACH(e, h, i) \
+        for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), NULL); )
+
+#define ORDERED_HASHMAP_FOREACH(e, h, i) \
+        for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), NULL); )
+
+#define HASHMAP_FOREACH_KEY(e, k, h, i) \
+        for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); )
+
+#define ORDERED_HASHMAP_FOREACH_KEY(e, k, h, i) \
+        for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); )
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free);
+
+#define _cleanup_hashmap_free_ _cleanup_(hashmap_freep)
+#define _cleanup_hashmap_free_free_ _cleanup_(hashmap_free_freep)
+#define _cleanup_hashmap_free_free_free_ _cleanup_(hashmap_free_free_freep)
+#define _cleanup_ordered_hashmap_free_ _cleanup_(ordered_hashmap_freep)
+#define _cleanup_ordered_hashmap_free_free_ _cleanup_(ordered_hashmap_free_freep)
+#define _cleanup_ordered_hashmap_free_free_free_ _cleanup_(ordered_hashmap_free_free_freep)
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(IteratedCache*, iterated_cache_free);
+
+#define _cleanup_iterated_cache_free_ _cleanup_(iterated_cache_freep)
diff --git a/shared/systemd/src/basic/hexdecoct.c b/shared/systemd/src/basic/hexdecoct.c
new file mode 100644
index 00000000..7c66cc62
--- /dev/null
+++ b/shared/systemd/src/basic/hexdecoct.c
@@ -0,0 +1,828 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "alloc-util.h"
+#include "hexdecoct.h"
+#include "macro.h"
+#include "string-util.h"
+#include "util.h"
+
+char octchar(int x) {
+        return '0' + (x & 7);
+}
+
+int unoctchar(char c) {
+
+        if (c >= '0' && c <= '7')
+                return c - '0';
+
+        return -EINVAL;
+}
+
+char decchar(int x) {
+        return '0' + (x % 10);
+}
+
+int undecchar(char c) {
+
+        if (c >= '0' && c <= '9')
+                return c - '0';
+
+        return -EINVAL;
+}
+
+char hexchar(int x) {
+        static const char table[16] = "0123456789abcdef";
+
+        return table[x & 15];
+}
+
+int unhexchar(char c) {
+
+        if (c >= '0' && c <= '9')
+                return c - '0';
+
+        if (c >= 'a' && c <= 'f')
+                return c - 'a' + 10;
+
+        if (c >= 'A' && c <= 'F')
+                return c - 'A' + 10;
+
+        return -EINVAL;
+}
+
+char *hexmem(const void *p, size_t l) {
+        const uint8_t *x;
+        char *r, *z;
+
+        z = r = new(char, l * 2 + 1);
+        if (!r)
+                return NULL;
+
+        for (x = p; x < (const uint8_t*) p + l; x++) {
+                *(z++) = hexchar(*x >> 4);
+                *(z++) = hexchar(*x & 15);
+        }
+
+        *z = 0;
+        return r;
+}
+
+static int unhex_next(const char **p, size_t *l) {
+        int r;
+
+        assert(p);
+        assert(l);
+
+        /* Find the next non-whitespace character, and decode it. We
+         * greedily skip all preceding and all following whitespace. */
+
+        for (;;) {
+                if (*l == 0)
+                        return -EPIPE;
+
+                if (!strchr(WHITESPACE, **p))
+                        break;
+
+                /* Skip leading whitespace */
+                (*p)++, (*l)--;
+        }
+
+        r = unhexchar(**p);
+        if (r < 0)
+                return r;
+
+        for (;;) {
+                (*p)++, (*l)--;
+
+                if (*l == 0 || !strchr(WHITESPACE, **p))
+                        break;
+
+                /* Skip following whitespace */
+        }
+
+        return r;
+}
+
+int unhexmem(const char *p, size_t l, void **ret, size_t *ret_len) {
+        _cleanup_free_ uint8_t *buf = NULL;
+        const char *x;
+        uint8_t *z;
+
+        assert(ret);
+        assert(ret_len);
+        assert(p || l == 0);
+
+        if (l == (size_t) -1)
+                l = strlen(p);
+
+        /* Note that the calculation of memory size is an upper boundary, as we ignore whitespace while decoding */
+        buf = malloc((l + 1) / 2 + 1);
+        if (!buf)
+                return -ENOMEM;
+
+        for (x = p, z = buf;;) {
+                int a, b;
+
+                a = unhex_next(&x, &l);
+                if (a == -EPIPE) /* End of string */
+                        break;
+                if (a < 0)
+                        return a;
+
+                b = unhex_next(&x, &l);
+                if (b < 0)
+                        return b;
+
+                *(z++) = (uint8_t) a << 4 | (uint8_t) b;
+        }
+
+        *z = 0;
+
+        *ret_len = (size_t) (z - buf);
+        *ret = TAKE_PTR(buf);
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+/* https://tools.ietf.org/html/rfc4648#section-6
+ * Notice that base32hex differs from base32 in the alphabet it uses.
+ * The distinction is that the base32hex representation preserves the
+ * order of the underlying data when compared as bytestrings, this is
+ * useful when representing NSEC3 hashes, as one can then verify the
+ * order of hashes directly from their representation. */
+char base32hexchar(int x) {
+        static const char table[32] = "0123456789"
+                                      "ABCDEFGHIJKLMNOPQRSTUV";
+
+        return table[x & 31];
+}
+
+int unbase32hexchar(char c) {
+        unsigned offset;
+
+        if (c >= '0' && c <= '9')
+                return c - '0';
+
+        offset = '9' - '0' + 1;
+
+        if (c >= 'A' && c <= 'V')
+                return c - 'A' + offset;
+
+        return -EINVAL;
+}
+
+char *base32hexmem(const void *p, size_t l, bool padding) {
+        char *r, *z;
+        const uint8_t *x;
+        size_t len;
+
+        assert(p || l == 0);
+
+        if (padding)
+                /* five input bytes makes eight output bytes, padding is added so we must round up */
+                len = 8 * (l + 4) / 5;
+        else {
+                /* same, but round down as there is no padding */
+                len = 8 * l / 5;
+
+                switch (l % 5) {
+                case 4:
+                        len += 7;
+                        break;
+                case 3:
+                        len += 5;
+                        break;
+                case 2:
+                        len += 4;
+                        break;
+                case 1:
+                        len += 2;
+                        break;
+                }
+        }
+
+        z = r = malloc(len + 1);
+        if (!r)
+                return NULL;
+
+        for (x = p; x < (const uint8_t*) p + (l / 5) * 5; x += 5) {
+                /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ
+                 * x[3] == QQQQQQQQ; x[4] == WWWWWWWW */
+                *(z++) = base32hexchar(x[0] >> 3);                    /* 000XXXXX */
+                *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6);  /* 000XXXYY */
+                *(z++) = base32hexchar((x[1] & 63) >> 1);             /* 000YYYYY */
+                *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4);  /* 000YZZZZ */
+                *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */
+                *(z++) = base32hexchar((x[3] & 127) >> 2);            /* 000QQQQQ */
+                *(z++) = base32hexchar((x[3] & 3) << 3 | x[4] >> 5);  /* 000QQWWW */
+                *(z++) = base32hexchar((x[4] & 31));                  /* 000WWWWW */
+        }
+
+        switch (l % 5) {
+        case 4:
+                *(z++) = base32hexchar(x[0] >> 3);                    /* 000XXXXX */
+                *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6);  /* 000XXXYY */
+                *(z++) = base32hexchar((x[1] & 63) >> 1);             /* 000YYYYY */
+                *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4);   /* 000YZZZZ */
+                *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */
+                *(z++) = base32hexchar((x[3] & 127) >> 2);            /* 000QQQQQ */
+                *(z++) = base32hexchar((x[3] & 3) << 3);              /* 000QQ000 */
+                if (padding)
+                        *(z++) = '=';
+
+                break;
+
+        case 3:
+                *(z++) = base32hexchar(x[0] >> 3);                   /* 000XXXXX */
+                *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */
+                *(z++) = base32hexchar((x[1] & 63) >> 1);            /* 000YYYYY */
+                *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */
+                *(z++) = base32hexchar((x[2] & 15) << 1);            /* 000ZZZZ0 */
+                if (padding) {
+                        *(z++) = '=';
+                        *(z++) = '=';
+                        *(z++) = '=';
+                }
+
+                break;
+
+        case 2:
+                *(z++) = base32hexchar(x[0] >> 3);                   /* 000XXXXX */
+                *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */
+                *(z++) = base32hexchar((x[1] & 63) >> 1);            /* 000YYYYY */
+                *(z++) = base32hexchar((x[1] & 1) << 4);             /* 000Y0000 */
+                if (padding) {
+                        *(z++) = '=';
+                        *(z++) = '=';
+                        *(z++) = '=';
+                        *(z++) = '=';
+                }
+
+                break;
+
+        case 1:
+                *(z++) = base32hexchar(x[0] >> 3);       /* 000XXXXX */
+                *(z++) = base32hexchar((x[0] & 7) << 2); /* 000XXX00 */
+                if (padding) {
+                        *(z++) = '=';
+                        *(z++) = '=';
+                        *(z++) = '=';
+                        *(z++) = '=';
+                        *(z++) = '=';
+                        *(z++) = '=';
+                }
+
+                break;
+        }
+
+        *z = 0;
+        return r;
+}
+
+int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *_len) {
+        _cleanup_free_ uint8_t *r = NULL;
+        int a, b, c, d, e, f, g, h;
+        uint8_t *z;
+        const char *x;
+        size_t len;
+        unsigned pad = 0;
+
+        assert(p || l == 0);
+        assert(mem);
+        assert(_len);
+
+        if (l == (size_t) -1)
+                l = strlen(p);
+
+        /* padding ensures any base32hex input has input divisible by 8 */
+        if (padding && l % 8 != 0)
+                return -EINVAL;
+
+        if (padding) {
+                /* strip the padding */
+                while (l > 0 && p[l - 1] == '=' && pad < 7) {
+                        pad++;
+                        l--;
+                }
+        }
+
+        /* a group of eight input bytes needs five output bytes, in case of
+         * padding we need to add some extra bytes */
+        len = (l / 8) * 5;
+
+        switch (l % 8) {
+        case 7:
+                len += 4;
+                break;
+        case 5:
+                len += 3;
+                break;
+        case 4:
+                len += 2;
+                break;
+        case 2:
+                len += 1;
+                break;
+        case 0:
+                break;
+        default:
+                return -EINVAL;
+        }
+
+        z = r = malloc(len + 1);
+        if (!r)
+                return -ENOMEM;
+
+        for (x = p; x < p + (l / 8) * 8; x += 8) {
+                /* a == 000XXXXX; b == 000YYYYY; c == 000ZZZZZ; d == 000WWWWW
+                 * e == 000SSSSS; f == 000QQQQQ; g == 000VVVVV; h == 000RRRRR */
+                a = unbase32hexchar(x[0]);
+                if (a < 0)
+                        return -EINVAL;
+
+                b = unbase32hexchar(x[1]);
+                if (b < 0)
+                        return -EINVAL;
+
+                c = unbase32hexchar(x[2]);
+                if (c < 0)
+                        return -EINVAL;
+
+                d = unbase32hexchar(x[3]);
+                if (d < 0)
+                        return -EINVAL;
+
+                e = unbase32hexchar(x[4]);
+                if (e < 0)
+                        return -EINVAL;
+
+                f = unbase32hexchar(x[5]);
+                if (f < 0)
+                        return -EINVAL;
+
+                g = unbase32hexchar(x[6]);
+                if (g < 0)
+                        return -EINVAL;
+
+                h = unbase32hexchar(x[7]);
+                if (h < 0)
+                        return -EINVAL;
+
+                *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2;                    /* XXXXXYYY */
+                *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */
+                *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1;                    /* WWWWSSSS */
+                *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */
+                *(z++) = (uint8_t) g << 5 | (uint8_t) h;                         /* VVVRRRRR */
+        }
+
+        switch (l % 8) {
+        case 7:
+                a = unbase32hexchar(x[0]);
+                if (a < 0)
+                        return -EINVAL;
+
+                b = unbase32hexchar(x[1]);
+                if (b < 0)
+                        return -EINVAL;
+
+                c = unbase32hexchar(x[2]);
+                if (c < 0)
+                        return -EINVAL;
+
+                d = unbase32hexchar(x[3]);
+                if (d < 0)
+                        return -EINVAL;
+
+                e = unbase32hexchar(x[4]);
+                if (e < 0)
+                        return -EINVAL;
+
+                f = unbase32hexchar(x[5]);
+                if (f < 0)
+                        return -EINVAL;
+
+                g = unbase32hexchar(x[6]);
+                if (g < 0)
+                        return -EINVAL;
+
+                /* g == 000VV000 */
+                if (g & 7)
+                        return -EINVAL;
+
+                *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2;                    /* XXXXXYYY */
+                *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */
+                *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1;                    /* WWWWSSSS */
+                *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */
+
+                break;
+        case 5:
+                a = unbase32hexchar(x[0]);
+                if (a < 0)
+                        return -EINVAL;
+
+                b = unbase32hexchar(x[1]);
+                if (b < 0)
+                        return -EINVAL;
+
+                c = unbase32hexchar(x[2]);
+                if (c < 0)
+                        return -EINVAL;
+
+                d = unbase32hexchar(x[3]);
+                if (d < 0)
+                        return -EINVAL;
+
+                e = unbase32hexchar(x[4]);
+                if (e < 0)
+                        return -EINVAL;
+
+                /* e == 000SSSS0 */
+                if (e & 1)
+                        return -EINVAL;
+
+                *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2;                    /* XXXXXYYY */
+                *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */
+                *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1;                    /* WWWWSSSS */
+
+                break;
+        case 4:
+                a = unbase32hexchar(x[0]);
+                if (a < 0)
+                        return -EINVAL;
+
+                b = unbase32hexchar(x[1]);
+                if (b < 0)
+                        return -EINVAL;
+
+                c = unbase32hexchar(x[2]);
+                if (c < 0)
+                        return -EINVAL;
+
+                d = unbase32hexchar(x[3]);
+                if (d < 0)
+                        return -EINVAL;
+
+                /* d == 000W0000 */
+                if (d & 15)
+                        return -EINVAL;
+
+                *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2;                    /* XXXXXYYY */
+                *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */
+
+                break;
+        case 2:
+                a = unbase32hexchar(x[0]);
+                if (a < 0)
+                        return -EINVAL;
+
+                b = unbase32hexchar(x[1]);
+                if (b < 0)
+                        return -EINVAL;
+
+                /* b == 000YYY00 */
+                if (b & 3)
+                        return -EINVAL;
+
+                *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */
+
+                break;
+        case 0:
+                break;
+        default:
+                return -EINVAL;
+        }
+
+        *z = 0;
+
+        *mem = TAKE_PTR(r);
+        *_len = len;
+
+        return 0;
+}
+
+/* https://tools.ietf.org/html/rfc4648#section-4 */
+char base64char(int x) {
+        static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                                      "abcdefghijklmnopqrstuvwxyz"
+                                      "0123456789+/";
+        return table[x & 63];
+}
+#endif /* NM_IGNORED */
+
+int unbase64char(char c) {
+        unsigned offset;
+
+        if (c >= 'A' && c <= 'Z')
+                return c - 'A';
+
+        offset = 'Z' - 'A' + 1;
+
+        if (c >= 'a' && c <= 'z')
+                return c - 'a' + offset;
+
+        offset += 'z' - 'a' + 1;
+
+        if (c >= '0' && c <= '9')
+                return c - '0' + offset;
+
+        offset += '9' - '0' + 1;
+
+        if (c == '+')
+                return offset;
+
+        offset++;
+
+        if (c == '/')
+                return offset;
+
+        return -EINVAL;
+}
+
+#if 0 /* NM_IGNORED */
+ssize_t base64mem(const void *p, size_t l, char **out) {
+        char *r, *z;
+        const uint8_t *x;
+
+        assert(p || l == 0);
+        assert(out);
+
+        /* three input bytes makes four output bytes, padding is added so we must round up */
+        z = r = malloc(4 * (l + 2) / 3 + 1);
+        if (!r)
+                return -ENOMEM;
+
+        for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) {
+                /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */
+                *(z++) = base64char(x[0] >> 2);                    /* 00XXXXXX */
+                *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4);  /* 00XXYYYY */
+                *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */
+                *(z++) = base64char(x[2] & 63);                    /* 00ZZZZZZ */
+        }
+
+        switch (l % 3) {
+        case 2:
+                *(z++) = base64char(x[0] >> 2);                   /* 00XXXXXX */
+                *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */
+                *(z++) = base64char((x[1] & 15) << 2);            /* 00YYYY00 */
+                *(z++) = '=';
+
+                break;
+        case 1:
+                *(z++) = base64char(x[0] >> 2);        /* 00XXXXXX */
+                *(z++) = base64char((x[0] & 3) << 4);  /* 00XX0000 */
+                *(z++) = '=';
+                *(z++) = '=';
+
+                break;
+        }
+
+        *z = 0;
+        *out = r;
+        return z - r;
+}
+
+static int base64_append_width(
+                char **prefix, int plen,
+                const char *sep, int indent,
+                const void *p, size_t l,
+                int width) {
+
+        _cleanup_free_ char *x = NULL;
+        char *t, *s;
+        ssize_t len, slen, avail, line, lines;
+
+        len = base64mem(p, l, &x);
+        if (len <= 0)
+                return len;
+
+        lines = DIV_ROUND_UP(len, width);
+
+        slen = strlen_ptr(sep);
+        if (plen >= SSIZE_MAX - 1 - slen ||
+            lines > (SSIZE_MAX - plen - 1 - slen) / (indent + width + 1))
+                return -ENOMEM;
+
+        t = realloc(*prefix, (ssize_t) plen + 1 + slen + (indent + width + 1) * lines);
+        if (!t)
+                return -ENOMEM;
+
+        memcpy_safe(t + plen, sep, slen);
+
+        for (line = 0, s = t + plen + slen, avail = len; line < lines; line++) {
+                int act = MIN(width, avail);
+
+                if (line > 0 || sep) {
+                        memset(s, ' ', indent);
+                        s += indent;
+                }
+
+                memcpy(s, x + width * line, act);
+                s += act;
+                *(s++) = line < lines - 1 ? '\n' : '\0';
+                avail -= act;
+        }
+        assert(avail == 0);
+
+        *prefix = t;
+        return 0;
+}
+
+int base64_append(
+                char **prefix, int plen,
+                const void *p, size_t l,
+                int indent, int width) {
+
+        if (plen > width / 2 || plen + indent > width)
+                /* leave indent on the left, keep last column free */
+                return base64_append_width(prefix, plen, "\n", indent, p, l, width - indent - 1);
+        else
+                /* leave plen on the left, keep last column free */
+                return base64_append_width(prefix, plen, " ", plen, p, l, width - plen - 1);
+}
+#endif /* NM_IGNORED */
+
+static int unbase64_next(const char **p, size_t *l) {
+        int ret;
+
+        assert(p);
+        assert(l);
+
+        /* Find the next non-whitespace character, and decode it. If we find padding, we return it as INT_MAX. We
+         * greedily skip all preceding and all following whitespace. */
+
+        for (;;) {
+                if (*l == 0)
+                        return -EPIPE;
+
+                if (!strchr(WHITESPACE, **p))
+                        break;
+
+                /* Skip leading whitespace */
+                (*p)++, (*l)--;
+        }
+
+        if (**p == '=')
+                ret = INT_MAX; /* return padding as INT_MAX */
+        else {
+                ret = unbase64char(**p);
+                if (ret < 0)
+                        return ret;
+        }
+
+        for (;;) {
+                (*p)++, (*l)--;
+
+                if (*l == 0)
+                        break;
+                if (!strchr(WHITESPACE, **p))
+                        break;
+
+                /* Skip following whitespace */
+        }
+
+        return ret;
+}
+
+int unbase64mem(const char *p, size_t l, void **ret, size_t *ret_size) {
+        _cleanup_free_ uint8_t *buf = NULL;
+        const char *x;
+        uint8_t *z;
+        size_t len;
+
+        assert(p || l == 0);
+        assert(ret);
+        assert(ret_size);
+
+        if (l == (size_t) -1)
+                l = strlen(p);
+
+        /* A group of four input bytes needs three output bytes, in case of padding we need to add two or three extra
+         * bytes. Note that this calculation is an upper boundary, as we ignore whitespace while decoding */
+        len = (l / 4) * 3 + (l % 4 != 0 ? (l % 4) - 1 : 0);
+
+        buf = malloc(len + 1);
+        if (!buf)
+                return -ENOMEM;
+
+        for (x = p, z = buf;;) {
+                int a, b, c, d; /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */
+
+                a = unbase64_next(&x, &l);
+                if (a == -EPIPE) /* End of string */
+                        break;
+                if (a < 0)
+                        return a;
+                if (a == INT_MAX) /* Padding is not allowed at the beginning of a 4ch block */
+                        return -EINVAL;
+
+                b = unbase64_next(&x, &l);
+                if (b < 0)
+                        return b;
+                if (b == INT_MAX) /* Padding is not allowed at the second character of a 4ch block either */
+                        return -EINVAL;
+
+                c = unbase64_next(&x, &l);
+                if (c < 0)
+                        return c;
+
+                d = unbase64_next(&x, &l);
+                if (d < 0)
+                        return d;
+
+                if (c == INT_MAX) { /* Padding at the third character */
+
+                        if (d != INT_MAX) /* If the third character is padding, the fourth must be too */
+                                return -EINVAL;
+
+                        /* b == 00YY0000 */
+                        if (b & 15)
+                                return -EINVAL;
+
+                        if (l > 0) /* Trailing rubbish? */
+                                return -ENAMETOOLONG;
+
+                        *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */
+                        break;
+                }
+
+                if (d == INT_MAX) {
+                        /* c == 00ZZZZ00 */
+                        if (c & 3)
+                                return -EINVAL;
+
+                        if (l > 0) /* Trailing rubbish? */
+                                return -ENAMETOOLONG;
+
+                        *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
+                        *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
+                        break;
+                }
+
+                *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
+                *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
+                *(z++) = (uint8_t) c << 6 | (uint8_t) d;      /* ZZWWWWWW */
+        }
+
+        *z = 0;
+
+        *ret_size = (size_t) (z - buf);
+        *ret = TAKE_PTR(buf);
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+void hexdump(FILE *f, const void *p, size_t s) {
+        const uint8_t *b = p;
+        unsigned n = 0;
+
+        assert(b || s == 0);
+
+        if (!f)
+                f = stdout;
+
+        while (s > 0) {
+                size_t i;
+
+                fprintf(f, "%04x  ", n);
+
+                for (i = 0; i < 16; i++) {
+
+                        if (i >= s)
+                                fputs("   ", f);
+                        else
+                                fprintf(f, "%02x ", b[i]);
+
+                        if (i == 7)
+                                fputc(' ', f);
+                }
+
+                fputc(' ', f);
+
+                for (i = 0; i < 16; i++) {
+
+                        if (i >= s)
+                                fputc(' ', f);
+                        else
+                                fputc(isprint(b[i]) ? (char) b[i] : '.', f);
+                }
+
+                fputc('\n', f);
+
+                if (s < 16)
+                        break;
+
+                n += 16;
+                b += 16;
+                s -= 16;
+        }
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/hexdecoct.h b/shared/systemd/src/basic/hexdecoct.h
new file mode 100644
index 00000000..9477d16e
--- /dev/null
+++ b/shared/systemd/src/basic/hexdecoct.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "macro.h"
+
+char octchar(int x) _const_;
+int unoctchar(char c) _const_;
+
+char decchar(int x) _const_;
+int undecchar(char c) _const_;
+
+char hexchar(int x) _const_;
+int unhexchar(char c) _const_;
+
+char *hexmem(const void *p, size_t l);
+int unhexmem(const char *p, size_t l, void **mem, size_t *len);
+
+char base32hexchar(int x) _const_;
+int unbase32hexchar(char c) _const_;
+
+char base64char(int x) _const_;
+int unbase64char(char c) _const_;
+
+char *base32hexmem(const void *p, size_t l, bool padding);
+int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *len);
+
+ssize_t base64mem(const void *p, size_t l, char **out);
+int base64_append(char **prefix, int plen,
+                  const void *p, size_t l,
+                  int margin, int width);
+int unbase64mem(const char *p, size_t l, void **mem, size_t *len);
+
+void hexdump(FILE *f, const void *p, size_t s);
diff --git a/shared/systemd/src/basic/hostname-util.c b/shared/systemd/src/basic/hostname-util.c
new file mode 100644
index 00000000..60a94b96
--- /dev/null
+++ b/shared/systemd/src/basic/hostname-util.c
@@ -0,0 +1,314 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "hostname-util.h"
+#include "macro.h"
+#include "string-util.h"
+
+#if 0 /* NM_IGNORED */
+bool hostname_is_set(void) {
+        struct utsname u;
+
+        assert_se(uname(&u) >= 0);
+
+        if (isempty(u.nodename))
+                return false;
+
+        /* This is the built-in kernel default host name */
+        if (streq(u.nodename, "(none)"))
+                return false;
+
+        return true;
+}
+
+char* gethostname_malloc(void) {
+        struct utsname u;
+
+        /* This call tries to return something useful, either the actual hostname
+         * or it makes something up. The only reason it might fail is OOM.
+         * It might even return "localhost" if that's set. */
+
+        assert_se(uname(&u) >= 0);
+
+        if (isempty(u.nodename) || streq(u.nodename, "(none)"))
+                return strdup(FALLBACK_HOSTNAME);
+
+        return strdup(u.nodename);
+}
+#endif /* NM_IGNORED */
+
+int gethostname_strict(char **ret) {
+        struct utsname u;
+        char *k;
+
+        /* This call will rather fail than make up a name. It will not return "localhost" either. */
+
+        assert_se(uname(&u) >= 0);
+
+        if (isempty(u.nodename))
+                return -ENXIO;
+
+        if (streq(u.nodename, "(none)"))
+                return -ENXIO;
+
+        if (is_localhost(u.nodename))
+                return -ENXIO;
+
+        k = strdup(u.nodename);
+        if (!k)
+                return -ENOMEM;
+
+        *ret = k;
+        return 0;
+}
+
+bool valid_ldh_char(char c) {
+        return
+                (c >= 'a' && c <= 'z') ||
+                (c >= 'A' && c <= 'Z') ||
+                (c >= '0' && c <= '9') ||
+                c == '-';
+}
+
+/**
+ * Check if s looks like a valid host name or FQDN. This does not do
+ * full DNS validation, but only checks if the name is composed of
+ * allowed characters and the length is not above the maximum allowed
+ * by Linux (c.f. dns_name_is_valid()). Trailing dot is allowed if
+ * allow_trailing_dot is true and at least two components are present
+ * in the name. Note that due to the restricted charset and length
+ * this call is substantially more conservative than
+ * dns_name_is_valid().
+ */
+bool hostname_is_valid(const char *s, bool allow_trailing_dot) {
+        unsigned n_dots = 0;
+        const char *p;
+        bool dot, hyphen;
+
+        if (isempty(s))
+                return false;
+
+        /* Doesn't accept empty hostnames, hostnames with
+         * leading dots, and hostnames with multiple dots in a
+         * sequence. Also ensures that the length stays below
+         * HOST_NAME_MAX. */
+
+        for (p = s, dot = hyphen = true; *p; p++)
+                if (*p == '.') {
+                        if (dot || hyphen)
+                                return false;
+
+                        dot = true;
+                        hyphen = false;
+                        n_dots++;
+
+                } else if (*p == '-') {
+                        if (dot)
+                                return false;
+
+                        dot = false;
+                        hyphen = true;
+
+                } else {
+                        if (!valid_ldh_char(*p))
+                                return false;
+
+                        dot = false;
+                        hyphen = false;
+                }
+
+        if (dot && (n_dots < 2 || !allow_trailing_dot))
+                return false;
+        if (hyphen)
+                return false;
+
+        if (p-s > HOST_NAME_MAX) /* Note that HOST_NAME_MAX is 64 on
+                                  * Linux, but DNS allows domain names
+                                  * up to 255 characters */
+                return false;
+
+        return true;
+}
+
+char* hostname_cleanup(char *s) {
+        char *p, *d;
+        bool dot, hyphen;
+
+        assert(s);
+
+        for (p = s, d = s, dot = hyphen = true; *p && d - s < HOST_NAME_MAX; p++)
+                if (*p == '.') {
+                        if (dot || hyphen)
+                                continue;
+
+                        *(d++) = '.';
+                        dot = true;
+                        hyphen = false;
+
+                } else if (*p == '-') {
+                        if (dot)
+                                continue;
+
+                        *(d++) = '-';
+                        dot = false;
+                        hyphen = true;
+
+                } else if (valid_ldh_char(*p)) {
+                        *(d++) = *p;
+                        dot = false;
+                        hyphen = false;
+                }
+
+        if (d > s && IN_SET(d[-1], '-', '.'))
+                /* The dot can occur at most once, but we might have multiple
+                 * hyphens, hence the loop */
+                d--;
+        *d = 0;
+
+        return s;
+}
+
+bool is_localhost(const char *hostname) {
+        assert(hostname);
+
+        /* This tries to identify local host and domain names
+         * described in RFC6761 plus the redhatism of localdomain */
+
+        return strcaseeq(hostname, "localhost") ||
+               strcaseeq(hostname, "localhost.") ||
+               strcaseeq(hostname, "localhost.localdomain") ||
+               strcaseeq(hostname, "localhost.localdomain.") ||
+               endswith_no_case(hostname, ".localhost") ||
+               endswith_no_case(hostname, ".localhost.") ||
+               endswith_no_case(hostname, ".localhost.localdomain") ||
+               endswith_no_case(hostname, ".localhost.localdomain.");
+}
+
+#if 0 /* NM_IGNORED */
+bool is_gateway_hostname(const char *hostname) {
+        assert(hostname);
+
+        /* This tries to identify the valid syntaxes for the our
+         * synthetic "gateway" host. */
+
+        return
+                strcaseeq(hostname, "_gateway") || strcaseeq(hostname, "_gateway.")
+#if ENABLE_COMPAT_GATEWAY_HOSTNAME
+                || strcaseeq(hostname, "gateway") || strcaseeq(hostname, "gateway.")
+#endif
+                ;
+}
+
+int sethostname_idempotent(const char *s) {
+        char buf[HOST_NAME_MAX + 1] = {};
+
+        assert(s);
+
+        if (gethostname(buf, sizeof(buf)) < 0)
+                return -errno;
+
+        if (streq(buf, s))
+                return 0;
+
+        if (sethostname(s, strlen(s)) < 0)
+                return -errno;
+
+        return 1;
+}
+
+int shorten_overlong(const char *s, char **ret) {
+        char *h, *p;
+
+        /* Shorten an overlong name to HOST_NAME_MAX or to the first dot,
+         * whatever comes earlier. */
+
+        assert(s);
+
+        h = strdup(s);
+        if (!h)
+                return -ENOMEM;
+
+        if (hostname_is_valid(h, false)) {
+                *ret = h;
+                return 0;
+        }
+
+        p = strchr(h, '.');
+        if (p)
+                *p = 0;
+
+        strshorten(h, HOST_NAME_MAX);
+
+        if (!hostname_is_valid(h, false)) {
+                free(h);
+                return -EDOM;
+        }
+
+        *ret = h;
+        return 1;
+}
+
+int read_etc_hostname_stream(FILE *f, char **ret) {
+        int r;
+
+        assert(f);
+        assert(ret);
+
+        for (;;) {
+                _cleanup_free_ char *line = NULL;
+                char *p;
+
+                r = read_line(f, LONG_LINE_MAX, &line);
+                if (r < 0)
+                        return r;
+                if (r == 0) /* EOF without any hostname? the file is empty, let's treat that exactly like no file at all: ENOENT */
+                        return -ENOENT;
+
+                p = strstrip(line);
+
+                /* File may have empty lines or comments, ignore them */
+                if (!IN_SET(*p, '\0', '#')) {
+                        char *copy;
+
+                        hostname_cleanup(p); /* normalize the hostname */
+
+                        if (!hostname_is_valid(p, true)) /* check that the hostname we return is valid */
+                                return -EBADMSG;
+
+                        copy = strdup(p);
+                        if (!copy)
+                                return -ENOMEM;
+
+                        *ret = copy;
+                        return 0;
+                }
+        }
+}
+
+int read_etc_hostname(const char *path, char **ret) {
+        _cleanup_fclose_ FILE *f = NULL;
+
+        assert(ret);
+
+        if (!path)
+                path = "/etc/hostname";
+
+        f = fopen(path, "re");
+        if (!f)
+                return -errno;
+
+        return read_etc_hostname_stream(f, ret);
+
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/hostname-util.h b/shared/systemd/src/basic/hostname-util.h
new file mode 100644
index 00000000..7ba386a0
--- /dev/null
+++ b/shared/systemd/src/basic/hostname-util.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "macro.h"
+
+bool hostname_is_set(void);
+
+char* gethostname_malloc(void);
+int gethostname_strict(char **ret);
+
+bool valid_ldh_char(char c) _const_;
+bool hostname_is_valid(const char *s, bool allow_trailing_dot) _pure_;
+char* hostname_cleanup(char *s);
+
+#define machine_name_is_valid(s) hostname_is_valid(s, false)
+
+bool is_localhost(const char *hostname);
+bool is_gateway_hostname(const char *hostname);
+
+int sethostname_idempotent(const char *s);
+
+int shorten_overlong(const char *s, char **ret);
+
+int read_etc_hostname_stream(FILE *f, char **ret);
+int read_etc_hostname(const char *path, char **ret);
diff --git a/shared/systemd/src/basic/in-addr-util.c b/shared/systemd/src/basic/in-addr-util.c
new file mode 100644
index 00000000..5ced3501
--- /dev/null
+++ b/shared/systemd/src/basic/in-addr-util.c
@@ -0,0 +1,636 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <arpa/inet.h>
+#include <endian.h>
+#include <errno.h>
+#include <net/if.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "alloc-util.h"
+#include "in-addr-util.h"
+#include "macro.h"
+#include "parse-util.h"
+#include "util.h"
+
+bool in4_addr_is_null(const struct in_addr *a) {
+        assert(a);
+
+        return a->s_addr == 0;
+}
+
+int in_addr_is_null(int family, const union in_addr_union *u) {
+        assert(u);
+
+        if (family == AF_INET)
+                return in4_addr_is_null(&u->in);
+
+        if (family == AF_INET6)
+                return IN6_IS_ADDR_UNSPECIFIED(&u->in6);
+
+        return -EAFNOSUPPORT;
+}
+
+bool in4_addr_is_link_local(const struct in_addr *a) {
+        assert(a);
+
+        return (be32toh(a->s_addr) & UINT32_C(0xFFFF0000)) == (UINT32_C(169) << 24 | UINT32_C(254) << 16);
+}
+
+int in_addr_is_link_local(int family, const union in_addr_union *u) {
+        assert(u);
+
+        if (family == AF_INET)
+                return in4_addr_is_link_local(&u->in);
+
+        if (family == AF_INET6)
+                return IN6_IS_ADDR_LINKLOCAL(&u->in6);
+
+        return -EAFNOSUPPORT;
+}
+
+int in_addr_is_multicast(int family, const union in_addr_union *u) {
+        assert(u);
+
+        if (family == AF_INET)
+                return IN_MULTICAST(be32toh(u->in.s_addr));
+
+        if (family == AF_INET6)
+                return IN6_IS_ADDR_MULTICAST(&u->in6);
+
+        return -EAFNOSUPPORT;
+}
+
+bool in4_addr_is_localhost(const struct in_addr *a) {
+        assert(a);
+
+        /* All of 127.x.x.x is localhost. */
+        return (be32toh(a->s_addr) & UINT32_C(0xFF000000)) == UINT32_C(127) << 24;
+}
+
+bool in4_addr_is_non_local(const struct in_addr *a) {
+        /* Whether the address is not null and not localhost.
+         *
+         * As such, it is suitable to configure as DNS/NTP server from DHCP. */
+        return !in4_addr_is_null(a) &&
+               !in4_addr_is_localhost(a);
+}
+
+int in_addr_is_localhost(int family, const union in_addr_union *u) {
+        assert(u);
+
+        if (family == AF_INET)
+                return in4_addr_is_localhost(&u->in);
+
+        if (family == AF_INET6)
+                return IN6_IS_ADDR_LOOPBACK(&u->in6);
+
+        return -EAFNOSUPPORT;
+}
+
+int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b) {
+        assert(a);
+        assert(b);
+
+        if (family == AF_INET)
+                return a->in.s_addr == b->in.s_addr;
+
+        if (family == AF_INET6)
+                return
+                        a->in6.s6_addr32[0] == b->in6.s6_addr32[0] &&
+                        a->in6.s6_addr32[1] == b->in6.s6_addr32[1] &&
+                        a->in6.s6_addr32[2] == b->in6.s6_addr32[2] &&
+                        a->in6.s6_addr32[3] == b->in6.s6_addr32[3];
+
+        return -EAFNOSUPPORT;
+}
+
+int in_addr_prefix_intersect(
+                int family,
+                const union in_addr_union *a,
+                unsigned aprefixlen,
+                const union in_addr_union *b,
+                unsigned bprefixlen) {
+
+        unsigned m;
+
+        assert(a);
+        assert(b);
+
+        /* Checks whether there are any addresses that are in both
+         * networks */
+
+        m = MIN(aprefixlen, bprefixlen);
+
+        if (family == AF_INET) {
+                uint32_t x, nm;
+
+                x = be32toh(a->in.s_addr ^ b->in.s_addr);
+                nm = (m == 0) ? 0 : 0xFFFFFFFFUL << (32 - m);
+
+                return (x & nm) == 0;
+        }
+
+        if (family == AF_INET6) {
+                unsigned i;
+
+                if (m > 128)
+                        m = 128;
+
+                for (i = 0; i < 16; i++) {
+                        uint8_t x, nm;
+
+                        x = a->in6.s6_addr[i] ^ b->in6.s6_addr[i];
+
+                        if (m < 8)
+                                nm = 0xFF << (8 - m);
+                        else
+                                nm = 0xFF;
+
+                        if ((x & nm) != 0)
+                                return 0;
+
+                        if (m > 8)
+                                m -= 8;
+                        else
+                                m = 0;
+                }
+
+                return 1;
+        }
+
+        return -EAFNOSUPPORT;
+}
+
+int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen) {
+        assert(u);
+
+        /* Increases the network part of an address by one. Returns
+         * positive it that succeeds, or 0 if this overflows. */
+
+        if (prefixlen <= 0)
+                return 0;
+
+        if (family == AF_INET) {
+                uint32_t c, n;
+
+                if (prefixlen > 32)
+                        prefixlen = 32;
+
+                c = be32toh(u->in.s_addr);
+                n = c + (1UL << (32 - prefixlen));
+                if (n < c)
+                        return 0;
+                n &= 0xFFFFFFFFUL << (32 - prefixlen);
+
+                u->in.s_addr = htobe32(n);
+                return 1;
+        }
+
+        if (family == AF_INET6) {
+                struct in6_addr add = {}, result;
+                uint8_t overflow = 0;
+                unsigned i;
+
+                if (prefixlen > 128)
+                        prefixlen = 128;
+
+                /* First calculate what we have to add */
+                add.s6_addr[(prefixlen-1) / 8] = 1 << (7 - (prefixlen-1) % 8);
+
+                for (i = 16; i > 0; i--) {
+                        unsigned j = i - 1;
+
+                        result.s6_addr[j] = u->in6.s6_addr[j] + add.s6_addr[j] + overflow;
+                        overflow = (result.s6_addr[j] < u->in6.s6_addr[j]);
+                }
+
+                if (overflow)
+                        return 0;
+
+                u->in6 = result;
+                return 1;
+        }
+
+        return -EAFNOSUPPORT;
+}
+
+int in_addr_to_string(int family, const union in_addr_union *u, char **ret) {
+        char *x;
+        size_t l;
+
+        assert(u);
+        assert(ret);
+
+        if (family == AF_INET)
+                l = INET_ADDRSTRLEN;
+        else if (family == AF_INET6)
+                l = INET6_ADDRSTRLEN;
+        else
+                return -EAFNOSUPPORT;
+
+        x = new(char, l);
+        if (!x)
+                return -ENOMEM;
+
+        errno = 0;
+        if (!inet_ntop(family, u, x, l)) {
+                free(x);
+                return errno > 0 ? -errno : -EINVAL;
+        }
+
+        *ret = x;
+        return 0;
+}
+
+int in_addr_ifindex_to_string(int family, const union in_addr_union *u, int ifindex, char **ret) {
+        size_t l;
+        char *x;
+        int r;
+
+        assert(u);
+        assert(ret);
+
+        /* Much like in_addr_to_string(), but optionally appends the zone interface index to the address, to properly
+         * handle IPv6 link-local addresses. */
+
+        if (family != AF_INET6)
+                goto fallback;
+        if (ifindex <= 0)
+                goto fallback;
+
+        r = in_addr_is_link_local(family, u);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                goto fallback;
+
+        l = INET6_ADDRSTRLEN + 1 + DECIMAL_STR_MAX(ifindex) + 1;
+        x = new(char, l);
+        if (!x)
+                return -ENOMEM;
+
+        errno = 0;
+        if (!inet_ntop(family, u, x, l)) {
+                free(x);
+                return errno > 0 ? -errno : -EINVAL;
+        }
+
+        sprintf(strchr(x, 0), "%%%i", ifindex);
+        *ret = x;
+
+        return 0;
+
+fallback:
+        return in_addr_to_string(family, u, ret);
+}
+
+int in_addr_from_string(int family, const char *s, union in_addr_union *ret) {
+        union in_addr_union buffer;
+        assert(s);
+
+        if (!IN_SET(family, AF_INET, AF_INET6))
+                return -EAFNOSUPPORT;
+
+        errno = 0;
+        if (inet_pton(family, s, ret ?: &buffer) <= 0)
+                return errno > 0 ? -errno : -EINVAL;
+
+        return 0;
+}
+
+int in_addr_from_string_auto(const char *s, int *ret_family, union in_addr_union *ret) {
+        int r;
+
+        assert(s);
+
+        r = in_addr_from_string(AF_INET, s, ret);
+        if (r >= 0) {
+                if (ret_family)
+                        *ret_family = AF_INET;
+                return 0;
+        }
+
+        r = in_addr_from_string(AF_INET6, s, ret);
+        if (r >= 0) {
+                if (ret_family)
+                        *ret_family = AF_INET6;
+                return 0;
+        }
+
+        return -EINVAL;
+}
+
+#if 0 /* NM_IGNORED */
+int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex) {
+        _cleanup_free_ char *buf = NULL;
+        const char *suffix;
+        int r, ifi = 0;
+
+        assert(s);
+        assert(family);
+        assert(ret);
+
+        /* Similar to in_addr_from_string_auto() but also parses an optionally appended IPv6 zone suffix ("scope id")
+         * if one is found. */
+
+        suffix = strchr(s, '%');
+        if (suffix) {
+
+                if (ifindex) {
+                        /* If we shall return the interface index, try to parse it */
+                        r = parse_ifindex(suffix + 1, &ifi);
+                        if (r < 0) {
+                                unsigned u;
+
+                                u = if_nametoindex(suffix + 1);
+                                if (u <= 0)
+                                        return -errno;
+
+                                ifi = (int) u;
+                        }
+                }
+
+                buf = strndup(s, suffix - s);
+                if (!buf)
+                        return -ENOMEM;
+
+                s = buf;
+        }
+
+        r = in_addr_from_string_auto(s, family, ret);
+        if (r < 0)
+                return r;
+
+        if (ifindex)
+                *ifindex = ifi;
+
+        return r;
+}
+#endif /* NM_IGNORED */
+
+unsigned char in4_addr_netmask_to_prefixlen(const struct in_addr *addr) {
+        assert(addr);
+
+        return 32U - u32ctz(be32toh(addr->s_addr));
+}
+
+struct in_addr* in4_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen) {
+        assert(addr);
+        assert(prefixlen <= 32);
+
+        /* Shifting beyond 32 is not defined, handle this specially. */
+        if (prefixlen == 0)
+                addr->s_addr = 0;
+        else
+                addr->s_addr = htobe32((0xffffffff << (32 - prefixlen)) & 0xffffffff);
+
+        return addr;
+}
+
+int in4_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen) {
+        uint8_t msb_octet = *(uint8_t*) addr;
+
+        /* addr may not be aligned, so make sure we only access it byte-wise */
+
+        assert(addr);
+        assert(prefixlen);
+
+        if (msb_octet < 128)
+                /* class A, leading bits: 0 */
+                *prefixlen = 8;
+        else if (msb_octet < 192)
+                /* class B, leading bits 10 */
+                *prefixlen = 16;
+        else if (msb_octet < 224)
+                /* class C, leading bits 110 */
+                *prefixlen = 24;
+        else
+                /* class D or E, no default prefixlen */
+                return -ERANGE;
+
+        return 0;
+}
+
+int in4_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask) {
+        unsigned char prefixlen;
+        int r;
+
+        assert(addr);
+        assert(mask);
+
+        r = in4_addr_default_prefixlen(addr, &prefixlen);
+        if (r < 0)
+                return r;
+
+        in4_addr_prefixlen_to_netmask(mask, prefixlen);
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen) {
+        assert(addr);
+
+        if (family == AF_INET) {
+                struct in_addr mask;
+
+                if (!in4_addr_prefixlen_to_netmask(&mask, prefixlen))
+                        return -EINVAL;
+
+                addr->in.s_addr &= mask.s_addr;
+                return 0;
+        }
+
+        if (family == AF_INET6) {
+                unsigned i;
+
+                for (i = 0; i < 16; i++) {
+                        uint8_t mask;
+
+                        if (prefixlen >= 8) {
+                                mask = 0xFF;
+                                prefixlen -= 8;
+                        } else {
+                                mask = 0xFF << (8 - prefixlen);
+                                prefixlen = 0;
+                        }
+
+                        addr->in6.s6_addr[i] &= mask;
+                }
+
+                return 0;
+        }
+
+        return -EAFNOSUPPORT;
+}
+
+int in_addr_prefix_covers(int family,
+                          const union in_addr_union *prefix,
+                          unsigned char prefixlen,
+                          const union in_addr_union *address) {
+
+        union in_addr_union masked_prefix, masked_address;
+        int r;
+
+        assert(prefix);
+        assert(address);
+
+        masked_prefix = *prefix;
+        r = in_addr_mask(family, &masked_prefix, prefixlen);
+        if (r < 0)
+                return r;
+
+        masked_address = *address;
+        r = in_addr_mask(family, &masked_address, prefixlen);
+        if (r < 0)
+                return r;
+
+        return in_addr_equal(family, &masked_prefix, &masked_address);
+}
+
+int in_addr_parse_prefixlen(int family, const char *p, unsigned char *ret) {
+        uint8_t u;
+        int r;
+
+        if (!IN_SET(family, AF_INET, AF_INET6))
+                return -EAFNOSUPPORT;
+
+        r = safe_atou8(p, &u);
+        if (r < 0)
+                return r;
+
+        if (u > FAMILY_ADDRESS_SIZE(family) * 8)
+                return -ERANGE;
+
+        *ret = u;
+        return 0;
+}
+
+int in_addr_prefix_from_string(
+                const char *p,
+                int family,
+                union in_addr_union *ret_prefix,
+                unsigned char *ret_prefixlen) {
+
+        _cleanup_free_ char *str = NULL;
+        union in_addr_union buffer;
+        const char *e, *l;
+        unsigned char k;
+        int r;
+
+        assert(p);
+
+        if (!IN_SET(family, AF_INET, AF_INET6))
+                return -EAFNOSUPPORT;
+
+        e = strchr(p, '/');
+        if (e) {
+                str = strndup(p, e - p);
+                if (!str)
+                        return -ENOMEM;
+
+                l = str;
+        } else
+                l = p;
+
+        r = in_addr_from_string(family, l, &buffer);
+        if (r < 0)
+                return r;
+
+        if (e) {
+                r = in_addr_parse_prefixlen(family, e+1, &k);
+                if (r < 0)
+                        return r;
+        } else
+                k = FAMILY_ADDRESS_SIZE(family) * 8;
+
+        if (ret_prefix)
+                *ret_prefix = buffer;
+        if (ret_prefixlen)
+                *ret_prefixlen = k;
+
+        return 0;
+}
+
+int in_addr_prefix_from_string_auto_internal(
+                const char *p,
+                InAddrPrefixLenMode mode,
+                int *ret_family,
+                union in_addr_union *ret_prefix,
+                unsigned char *ret_prefixlen) {
+
+        _cleanup_free_ char *str = NULL;
+        union in_addr_union buffer;
+        const char *e, *l;
+        unsigned char k;
+        int family, r;
+
+        assert(p);
+
+        e = strchr(p, '/');
+        if (e) {
+                str = strndup(p, e - p);
+                if (!str)
+                        return -ENOMEM;
+
+                l = str;
+        } else
+                l = p;
+
+        r = in_addr_from_string_auto(l, &family, &buffer);
+        if (r < 0)
+                return r;
+
+        if (e) {
+                r = in_addr_parse_prefixlen(family, e+1, &k);
+                if (r < 0)
+                        return r;
+        } else
+                switch (mode) {
+                case PREFIXLEN_FULL:
+                        k = FAMILY_ADDRESS_SIZE(family) * 8;
+                        break;
+                case PREFIXLEN_REFUSE:
+                        return -ENOANO; /* To distinguish this error from others. */
+                case PREFIXLEN_LEGACY:
+                        if (family == AF_INET) {
+                                r = in4_addr_default_prefixlen(&buffer.in, &k);
+                                if (r < 0)
+                                        return r;
+                        } else
+                                k = 0;
+                        break;
+                default:
+                        assert_not_reached("Invalid prefixlen mode");
+                }
+
+        if (ret_family)
+                *ret_family = family;
+        if (ret_prefix)
+                *ret_prefix = buffer;
+        if (ret_prefixlen)
+                *ret_prefixlen = k;
+
+        return 0;
+
+}
+
+static void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state) {
+        siphash24_compress(&a->family, sizeof(a->family), state);
+        siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state);
+}
+
+static int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y) {
+        int r;
+
+        r = CMP(x->family, y->family);
+        if (r != 0)
+                return r;
+
+        return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family));
+}
+
+DEFINE_HASH_OPS(in_addr_data_hash_ops, struct in_addr_data, in_addr_data_hash_func, in_addr_data_compare_func);
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/in-addr-util.h b/shared/systemd/src/basic/in-addr-util.h
new file mode 100644
index 00000000..c2156712
--- /dev/null
+++ b/shared/systemd/src/basic/in-addr-util.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <netinet/in.h>
+#include <stddef.h>
+#include <sys/socket.h>
+
+#include "hash-funcs.h"
+#include "macro.h"
+#include "util.h"
+
+union in_addr_union {
+        struct in_addr in;
+        struct in6_addr in6;
+};
+
+struct in_addr_data {
+        int family;
+        union in_addr_union address;
+};
+
+bool in4_addr_is_null(const struct in_addr *a);
+int in_addr_is_null(int family, const union in_addr_union *u);
+
+int in_addr_is_multicast(int family, const union in_addr_union *u);
+
+bool in4_addr_is_link_local(const struct in_addr *a);
+int in_addr_is_link_local(int family, const union in_addr_union *u);
+
+bool in4_addr_is_localhost(const struct in_addr *a);
+int in_addr_is_localhost(int family, const union in_addr_union *u);
+
+bool in4_addr_is_non_local(const struct in_addr *a);
+
+int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b);
+int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen);
+int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen);
+int in_addr_to_string(int family, const union in_addr_union *u, char **ret);
+int in_addr_ifindex_to_string(int family, const union in_addr_union *u, int ifindex, char **ret);
+int in_addr_from_string(int family, const char *s, union in_addr_union *ret);
+int in_addr_from_string_auto(const char *s, int *ret_family, union in_addr_union *ret);
+int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex);
+unsigned char in4_addr_netmask_to_prefixlen(const struct in_addr *addr);
+struct in_addr* in4_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen);
+int in4_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen);
+int in4_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask);
+int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen);
+int in_addr_prefix_covers(int family, const union in_addr_union *prefix, unsigned char prefixlen, const union in_addr_union *address);
+int in_addr_parse_prefixlen(int family, const char *p, unsigned char *ret);
+int in_addr_prefix_from_string(const char *p, int family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen);
+
+typedef enum InAddrPrefixLenMode {
+        PREFIXLEN_FULL,   /* Default to prefixlen of address size, 32 for IPv4 or 128 for IPv6, if not specified. */
+        PREFIXLEN_REFUSE, /* Fail with -ENOANO if prefixlen is not specified. */
+        PREFIXLEN_LEGACY, /* Default to legacy default prefixlen calculation from address if not specified. */
+} InAddrPrefixLenMode;
+
+int in_addr_prefix_from_string_auto_internal(const char *p, InAddrPrefixLenMode mode, int *ret_family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen);
+static inline int in_addr_prefix_from_string_auto(const char *p, int *ret_family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen) {
+        return in_addr_prefix_from_string_auto_internal(p, PREFIXLEN_FULL, ret_family, ret_prefix, ret_prefixlen);
+}
+
+static inline size_t FAMILY_ADDRESS_SIZE(int family) {
+        assert(IN_SET(family, AF_INET, AF_INET6));
+        return family == AF_INET6 ? 16 : 4;
+}
+
+/* Workaround for clang, explicitly specify the maximum-size element here.
+ * See also oss-fuzz#11344. */
+#define IN_ADDR_NULL ((union in_addr_union) { .in6 = {} })
+
+extern const struct hash_ops in_addr_data_hash_ops;
diff --git a/shared/systemd/src/basic/io-util.c b/shared/systemd/src/basic/io-util.c
new file mode 100644
index 00000000..3f47eff5
--- /dev/null
+++ b/shared/systemd/src/basic/io-util.c
@@ -0,0 +1,272 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "io-util.h"
+#include "string-util.h"
+#include "time-util.h"
+
+#if 0 /* NM_IGNORED */
+int flush_fd(int fd) {
+        struct pollfd pollfd = {
+                .fd = fd,
+                .events = POLLIN,
+        };
+        int count = 0;
+
+        /* Read from the specified file descriptor, until POLLIN is not set anymore, throwing away everything
+         * read. Note that some file descriptors (notable IP sockets) will trigger POLLIN even when no data can be read
+         * (due to IP packet checksum mismatches), hence this function is only safe to be non-blocking if the fd used
+         * was set to non-blocking too. */
+
+        for (;;) {
+                char buf[LINE_MAX];
+                ssize_t l;
+                int r;
+
+                r = poll(&pollfd, 1, 0);
+                if (r < 0) {
+                        if (errno == EINTR)
+                                continue;
+
+                        return -errno;
+
+                } else if (r == 0)
+                        return count;
+
+                l = read(fd, buf, sizeof(buf));
+                if (l < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        if (errno == EAGAIN)
+                                return count;
+
+                        return -errno;
+                } else if (l == 0)
+                        return count;
+
+                count += (int) l;
+        }
+}
+#endif /* NM_IGNORED */
+
+ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) {
+        uint8_t *p = buf;
+        ssize_t n = 0;
+
+        assert(fd >= 0);
+        assert(buf);
+
+        /* If called with nbytes == 0, let's call read() at least
+         * once, to validate the operation */
+
+        if (nbytes > (size_t) SSIZE_MAX)
+                return -EINVAL;
+
+        do {
+                ssize_t k;
+
+                k = read(fd, p, nbytes);
+                if (k < 0) {
+                        if (errno == EINTR)
+                                continue;
+
+                        if (errno == EAGAIN && do_poll) {
+
+                                /* We knowingly ignore any return value here,
+                                 * and expect that any error/EOF is reported
+                                 * via read() */
+
+                                (void) fd_wait_for_event(fd, POLLIN, USEC_INFINITY);
+                                continue;
+                        }
+
+                        return n > 0 ? n : -errno;
+                }
+
+                if (k == 0)
+                        return n;
+
+                assert((size_t) k <= nbytes);
+
+                p += k;
+                nbytes -= k;
+                n += k;
+        } while (nbytes > 0);
+
+        return n;
+}
+
+int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll) {
+        ssize_t n;
+
+        n = loop_read(fd, buf, nbytes, do_poll);
+        if (n < 0)
+                return (int) n;
+        if ((size_t) n != nbytes)
+                return -EIO;
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) {
+        const uint8_t *p = buf;
+
+        assert(fd >= 0);
+        assert(buf);
+
+        if (_unlikely_(nbytes > (size_t) SSIZE_MAX))
+                return -EINVAL;
+
+        do {
+                ssize_t k;
+
+                k = write(fd, p, nbytes);
+                if (k < 0) {
+                        if (errno == EINTR)
+                                continue;
+
+                        if (errno == EAGAIN && do_poll) {
+                                /* We knowingly ignore any return value here,
+                                 * and expect that any error/EOF is reported
+                                 * via write() */
+
+                                (void) fd_wait_for_event(fd, POLLOUT, USEC_INFINITY);
+                                continue;
+                        }
+
+                        return -errno;
+                }
+
+                if (_unlikely_(nbytes > 0 && k == 0)) /* Can't really happen */
+                        return -EIO;
+
+                assert((size_t) k <= nbytes);
+
+                p += k;
+                nbytes -= k;
+        } while (nbytes > 0);
+
+        return 0;
+}
+
+int pipe_eof(int fd) {
+        struct pollfd pollfd = {
+                .fd = fd,
+                .events = POLLIN|POLLHUP,
+        };
+
+        int r;
+
+        r = poll(&pollfd, 1, 0);
+        if (r < 0)
+                return -errno;
+
+        if (r == 0)
+                return 0;
+
+        return pollfd.revents & POLLHUP;
+}
+#endif /* NM_IGNORED */
+
+int fd_wait_for_event(int fd, int event, usec_t t) {
+
+        struct pollfd pollfd = {
+                .fd = fd,
+                .events = event,
+        };
+
+        struct timespec ts;
+        int r;
+
+        r = ppoll(&pollfd, 1, t == USEC_INFINITY ? NULL : timespec_store(&ts, t), NULL);
+        if (r < 0)
+                return -errno;
+        if (r == 0)
+                return 0;
+
+        return pollfd.revents;
+}
+
+#if 0 /* NM_IGNORED */
+static size_t nul_length(const uint8_t *p, size_t sz) {
+        size_t n = 0;
+
+        while (sz > 0) {
+                if (*p != 0)
+                        break;
+
+                n++;
+                p++;
+                sz--;
+        }
+
+        return n;
+}
+
+ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) {
+        const uint8_t *q, *w, *e;
+        ssize_t l;
+
+        q = w = p;
+        e = q + sz;
+        while (q < e) {
+                size_t n;
+
+                n = nul_length(q, e - q);
+
+                /* If there are more than the specified run length of
+                 * NUL bytes, or if this is the beginning or the end
+                 * of the buffer, then seek instead of write */
+                if ((n > run_length) ||
+                    (n > 0 && q == p) ||
+                    (n > 0 && q + n >= e)) {
+                        if (q > w) {
+                                l = write(fd, w, q - w);
+                                if (l < 0)
+                                        return -errno;
+                                if (l != q -w)
+                                        return -EIO;
+                        }
+
+                        if (lseek(fd, n, SEEK_CUR) == (off_t) -1)
+                                return -errno;
+
+                        q += n;
+                        w = q;
+                } else if (n > 0)
+                        q += n;
+                else
+                        q++;
+        }
+
+        if (q > w) {
+                l = write(fd, w, q - w);
+                if (l < 0)
+                        return -errno;
+                if (l != q - w)
+                        return -EIO;
+        }
+
+        return q - (const uint8_t*) p;
+}
+
+char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value) {
+        char *x;
+
+        x = strappend(field, value);
+        if (x)
+                iovec[(*n_iovec)++] = IOVEC_MAKE_STRING(x);
+        return x;
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/io-util.h b/shared/systemd/src/basic/io-util.h
new file mode 100644
index 00000000..792a64ad
--- /dev/null
+++ b/shared/systemd/src/basic/io-util.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include "macro.h"
+#include "time-util.h"
+
+int flush_fd(int fd);
+
+ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll);
+int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll);
+int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll);
+
+int pipe_eof(int fd);
+
+int fd_wait_for_event(int fd, int event, usec_t timeout);
+
+ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length);
+
+static inline size_t IOVEC_TOTAL_SIZE(const struct iovec *i, size_t n) {
+        size_t j, r = 0;
+
+        for (j = 0; j < n; j++)
+                r += i[j].iov_len;
+
+        return r;
+}
+
+static inline size_t IOVEC_INCREMENT(struct iovec *i, size_t n, size_t k) {
+        size_t j;
+
+        for (j = 0; j < n; j++) {
+                size_t sub;
+
+                if (_unlikely_(k <= 0))
+                        break;
+
+                sub = MIN(i[j].iov_len, k);
+                i[j].iov_len -= sub;
+                i[j].iov_base = (uint8_t*) i[j].iov_base + sub;
+                k -= sub;
+        }
+
+        return k;
+}
+
+static inline bool FILE_SIZE_VALID(uint64_t l) {
+        /* ftruncate() and friends take an unsigned file size, but actually cannot deal with file sizes larger than
+         * 2^63 since the kernel internally handles it as signed value. This call allows checking for this early. */
+
+        return (l >> 63) == 0;
+}
+
+static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) {
+
+        /* Same as above, but allows one extra value: -1 as indication for infinity. */
+
+        if (l == (uint64_t) -1)
+                return true;
+
+        return FILE_SIZE_VALID(l);
+
+}
+
+#define IOVEC_INIT(base, len) { .iov_base = (base), .iov_len = (len) }
+#define IOVEC_MAKE(base, len) (struct iovec) IOVEC_INIT(base, len)
+#define IOVEC_INIT_STRING(string) IOVEC_INIT((char*) string, strlen(string))
+#define IOVEC_MAKE_STRING(string) (struct iovec) IOVEC_INIT_STRING(string)
+
+char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value);
diff --git a/shared/systemd/src/basic/list.h b/shared/systemd/src/basic/list.h
new file mode 100644
index 00000000..f7f97000
--- /dev/null
+++ b/shared/systemd/src/basic/list.h
@@ -0,0 +1,171 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "macro.h"
+
+/* The head of the linked list. Use this in the structure that shall
+ * contain the head of the linked list */
+#define LIST_HEAD(t,name)                                               \
+        t *name
+
+/* The pointers in the linked list's items. Use this in the item structure */
+#define LIST_FIELDS(t,name)                                             \
+        t *name##_next, *name##_prev
+
+/* Initialize the list's head */
+#define LIST_HEAD_INIT(head)                                            \
+        do {                                                            \
+                (head) = NULL;                                          \
+        } while (false)
+
+/* Initialize a list item */
+#define LIST_INIT(name,item)                                            \
+        do {                                                            \
+                typeof(*(item)) *_item = (item);                        \
+                assert(_item);                                          \
+                _item->name##_prev = _item->name##_next = NULL;         \
+        } while (false)
+
+/* Prepend an item to the list */
+#define LIST_PREPEND(name,head,item)                                    \
+        do {                                                            \
+                typeof(*(head)) **_head = &(head), *_item = (item);     \
+                assert(_item);                                          \
+                if ((_item->name##_next = *_head))                      \
+                        _item->name##_next->name##_prev = _item;        \
+                _item->name##_prev = NULL;                              \
+                *_head = _item;                                         \
+        } while (false)
+
+/* Append an item to the list */
+#define LIST_APPEND(name,head,item)                                     \
+        do {                                                            \
+                typeof(*(head)) **_hhead = &(head), *_tail;             \
+                LIST_FIND_TAIL(name, *_hhead, _tail);                   \
+                LIST_INSERT_AFTER(name, *_hhead, _tail, item);          \
+        } while (false)
+
+/* Remove an item from the list */
+#define LIST_REMOVE(name,head,item)                                     \
+        do {                                                            \
+                typeof(*(head)) **_head = &(head), *_item = (item);     \
+                assert(_item);                                          \
+                if (_item->name##_next)                                 \
+                        _item->name##_next->name##_prev = _item->name##_prev; \
+                if (_item->name##_prev)                                 \
+                        _item->name##_prev->name##_next = _item->name##_next; \
+                else {                                                  \
+                        assert(*_head == _item);                        \
+                        *_head = _item->name##_next;                    \
+                }                                                       \
+                _item->name##_next = _item->name##_prev = NULL;         \
+        } while (false)
+
+/* Find the head of the list */
+#define LIST_FIND_HEAD(name,item,head)                                  \
+        do {                                                            \
+                typeof(*(item)) *_item = (item);                        \
+                if (!_item)                                             \
+                        (head) = NULL;                                  \
+                else {                                                  \
+                        while (_item->name##_prev)                      \
+                                _item = _item->name##_prev;             \
+                        (head) = _item;                                 \
+                }                                                       \
+        } while (false)
+
+/* Find the tail of the list */
+#define LIST_FIND_TAIL(name,item,tail)                                  \
+        do {                                                            \
+                typeof(*(item)) *_item = (item);                        \
+                if (!_item)                                             \
+                        (tail) = NULL;                                  \
+                else {                                                  \
+                        while (_item->name##_next)                      \
+                                _item = _item->name##_next;             \
+                        (tail) = _item;                                 \
+                }                                                       \
+        } while (false)
+
+/* Insert an item after another one (a = where, b = what) */
+#define LIST_INSERT_AFTER(name,head,a,b)                                \
+        do {                                                            \
+                typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \
+                assert(_b);                                             \
+                if (!_a) {                                              \
+                        if ((_b->name##_next = *_head))                 \
+                                _b->name##_next->name##_prev = _b;      \
+                        _b->name##_prev = NULL;                         \
+                        *_head = _b;                                    \
+                } else {                                                \
+                        if ((_b->name##_next = _a->name##_next))        \
+                                _b->name##_next->name##_prev = _b;      \
+                        _b->name##_prev = _a;                           \
+                        _a->name##_next = _b;                           \
+                }                                                       \
+        } while (false)
+
+/* Insert an item before another one (a = where, b = what) */
+#define LIST_INSERT_BEFORE(name,head,a,b)                               \
+        do {                                                            \
+                typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \
+                assert(_b);                                             \
+                if (!_a) {                                              \
+                        if (!*_head) {                                  \
+                                _b->name##_next = NULL;                 \
+                                _b->name##_prev = NULL;                 \
+                                *_head = _b;                            \
+                        } else {                                        \
+                                typeof(*(head)) *_tail = (head);        \
+                                while (_tail->name##_next)              \
+                                        _tail = _tail->name##_next;     \
+                                _b->name##_next = NULL;                 \
+                                _b->name##_prev = _tail;                \
+                                _tail->name##_next = _b;                \
+                        }                                               \
+                } else {                                                \
+                        if ((_b->name##_prev = _a->name##_prev))        \
+                                _b->name##_prev->name##_next = _b;      \
+                        else                                            \
+                                *_head = _b;                            \
+                        _b->name##_next = _a;                           \
+                        _a->name##_prev = _b;                           \
+                }                                                       \
+        } while (false)
+
+#define LIST_JUST_US(name,item)                                         \
+        (!(item)->name##_prev && !(item)->name##_next)                  \
+
+#define LIST_FOREACH(name,i,head)                                       \
+        for ((i) = (head); (i); (i) = (i)->name##_next)
+
+#define LIST_FOREACH_SAFE(name,i,n,head)                                \
+        for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n))
+
+#define LIST_FOREACH_BEFORE(name,i,p)                                   \
+        for ((i) = (p)->name##_prev; (i); (i) = (i)->name##_prev)
+
+#define LIST_FOREACH_AFTER(name,i,p)                                    \
+        for ((i) = (p)->name##_next; (i); (i) = (i)->name##_next)
+
+/* Iterate through all the members of the list p is included in, but skip over p */
+#define LIST_FOREACH_OTHERS(name,i,p)                                   \
+        for (({                                                         \
+                (i) = (p);                                              \
+                while ((i) && (i)->name##_prev)                         \
+                        (i) = (i)->name##_prev;                         \
+                if ((i) == (p))                                         \
+                        (i) = (p)->name##_next;                         \
+             });                                                        \
+             (i);                                                       \
+             (i) = (i)->name##_next == (p) ? (p)->name##_next : (i)->name##_next)
+
+/* Loop starting from p->next until p->prev.
+   p can be adjusted meanwhile. */
+#define LIST_LOOP_BUT_ONE(name,i,head,p)                                \
+        for ((i) = (p)->name##_next ? (p)->name##_next : (head);        \
+             (i) != (p);                                                \
+             (i) = (i)->name##_next ? (i)->name##_next : (head))
+
+#define LIST_IS_EMPTY(head)                                             \
+        (!(head))
diff --git a/shared/systemd/src/basic/log.h b/shared/systemd/src/basic/log.h
new file mode 100644
index 00000000..364b8a49
--- /dev/null
+++ b/shared/systemd/src/basic/log.h
@@ -0,0 +1,331 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#include "macro.h"
+
+/* Some structures we reference but don't want to pull in headers for */
+struct iovec;
+struct signalfd_siginfo;
+
+typedef enum LogRealm {
+        LOG_REALM_SYSTEMD,
+        LOG_REALM_UDEV,
+        _LOG_REALM_MAX,
+} LogRealm;
+
+#ifndef LOG_REALM
+#  define LOG_REALM LOG_REALM_SYSTEMD
+#endif
+
+typedef enum LogTarget{
+        LOG_TARGET_CONSOLE,
+        LOG_TARGET_CONSOLE_PREFIXED,
+        LOG_TARGET_KMSG,
+        LOG_TARGET_JOURNAL,
+        LOG_TARGET_JOURNAL_OR_KMSG,
+        LOG_TARGET_SYSLOG,
+        LOG_TARGET_SYSLOG_OR_KMSG,
+        LOG_TARGET_AUTO, /* console if stderr is tty, JOURNAL_OR_KMSG otherwise */
+        LOG_TARGET_NULL,
+        _LOG_TARGET_MAX,
+        _LOG_TARGET_INVALID = -1
+} LogTarget;
+
+/* Note to readers: << and >> have lower precedence than & and | */
+#define LOG_REALM_PLUS_LEVEL(realm, level)  ((realm) << 10 | (level))
+#define LOG_REALM_REMOVE_LEVEL(realm_level) ((realm_level) >> 10)
+#define SYNTHETIC_ERRNO(num)                (1 << 30 | (num))
+#define IS_SYNTHETIC_ERRNO(val)             ((val) >> 30 & 1)
+#define ERRNO_VALUE(val)                    (abs(val) & 255)
+
+void log_set_target(LogTarget target);
+void log_set_max_level_realm(LogRealm realm, int level);
+#define log_set_max_level(level)                \
+        log_set_max_level_realm(LOG_REALM, (level))
+
+void log_set_facility(int facility);
+
+int log_set_target_from_string(const char *e);
+int log_set_max_level_from_string_realm(LogRealm realm, const char *e);
+#define log_set_max_level_from_string(e)        \
+        log_set_max_level_from_string_realm(LOG_REALM, (e))
+
+void log_show_color(bool b);
+bool log_get_show_color(void) _pure_;
+void log_show_location(bool b);
+bool log_get_show_location(void) _pure_;
+
+int log_show_color_from_string(const char *e);
+int log_show_location_from_string(const char *e);
+
+LogTarget log_get_target(void) _pure_;
+#if 0 /* NM_IGNORED */
+int log_get_max_level_realm(LogRealm realm) _pure_;
+#endif /* NM_IGNORED */
+#define log_get_max_level()                     \
+        log_get_max_level_realm(LOG_REALM)
+
+/* Functions below that open and close logs or configure logging based on the
+ * environment should not be called from library code — this is always a job
+ * for the application itself.
+ */
+
+int log_open(void);
+void log_close(void);
+void log_forget_fds(void);
+
+void log_parse_environment_realm(LogRealm realm);
+#define log_parse_environment() \
+        log_parse_environment_realm(LOG_REALM)
+
+#if 0 /* NM_IGNORED */
+int log_dispatch_internal(
+                int level,
+                int error,
+                const char *file,
+                int line,
+                const char *func,
+                const char *object_field,
+                const char *object,
+                const char *extra,
+                const char *extra_field,
+                char *buffer);
+
+int log_internal_realm(
+                int level,
+                int error,
+                const char *file,
+                int line,
+                const char *func,
+                const char *format, ...) _printf_(6,7);
+#endif /* NM_IGNORED */
+#define log_internal(level, ...) \
+        log_internal_realm(LOG_REALM_PLUS_LEVEL(LOG_REALM, (level)), __VA_ARGS__)
+
+#if 0 /* NM_IGNORED */
+int log_internalv_realm(
+                int level,
+                int error,
+                const char *file,
+                int line,
+                const char *func,
+                const char *format,
+                va_list ap) _printf_(6,0);
+#define log_internalv(level, ...) \
+        log_internalv_realm(LOG_REALM_PLUS_LEVEL(LOG_REALM, (level)), __VA_ARGS__)
+
+/* Realm is fixed to LOG_REALM_SYSTEMD for those */
+int log_object_internal(
+                int level,
+                int error,
+                const char *file,
+                int line,
+                const char *func,
+                const char *object_field,
+                const char *object,
+                const char *extra_field,
+                const char *extra,
+                const char *format, ...) _printf_(10,11);
+
+int log_struct_internal(
+                int level,
+                int error,
+                const char *file,
+                int line,
+                const char *func,
+                const char *format, ...) _printf_(6,0) _sentinel_;
+
+int log_oom_internal(
+                LogRealm realm,
+                const char *file,
+                int line,
+                const char *func);
+
+int log_format_iovec(
+                struct iovec *iovec,
+                size_t iovec_len,
+                size_t *n,
+                bool newline_separator,
+                int error,
+                const char *format,
+                va_list ap) _printf_(6, 0);
+
+int log_struct_iovec_internal(
+                int level,
+                int error,
+                const char *file,
+                int line,
+                const char *func,
+                const struct iovec *input_iovec,
+                size_t n_input_iovec);
+
+/* This modifies the buffer passed! */
+int log_dump_internal(
+                int level,
+                int error,
+                const char *file,
+                int line,
+                const char *func,
+                char *buffer);
+
+/* Logging for various assertions */
+_noreturn_ void log_assert_failed_realm(
+                LogRealm realm,
+                const char *text,
+                const char *file,
+                int line,
+                const char *func);
+#define log_assert_failed(text, ...) \
+        log_assert_failed_realm(LOG_REALM, (text), __VA_ARGS__)
+
+
+_noreturn_ void log_assert_failed_unreachable_realm(
+                LogRealm realm,
+                const char *text,
+                const char *file,
+                int line,
+                const char *func);
+#define log_assert_failed_unreachable(text, ...) \
+        log_assert_failed_unreachable_realm(LOG_REALM, (text), __VA_ARGS__)
+
+void log_assert_failed_return_realm(
+                LogRealm realm,
+                const char *text,
+                const char *file,
+                int line,
+                const char *func);
+#define log_assert_failed_return(text, ...) \
+        log_assert_failed_return_realm(LOG_REALM, (text), __VA_ARGS__)
+
+#define log_dispatch(level, error, buffer)                              \
+        log_dispatch_internal(level, error, __FILE__, __LINE__, __func__, NULL, NULL, NULL, NULL, buffer)
+#endif /* NM_IGNORED */
+
+/* Logging with level */
+#define log_full_errno_realm(realm, level, error, ...)                  \
+        ({                                                              \
+                int _level = (level), _e = (error), _realm = (realm);   \
+                (log_get_max_level_realm(_realm) >= LOG_PRI(_level))    \
+                        ? log_internal_realm(LOG_REALM_PLUS_LEVEL(_realm, _level), _e, \
+                                             __FILE__, __LINE__, __func__, __VA_ARGS__) \
+                        : -ERRNO_VALUE(_e);                             \
+        })
+
+#define log_full_errno(level, error, ...)                               \
+        log_full_errno_realm(LOG_REALM, (level), (error), __VA_ARGS__)
+
+#define log_full(level, ...) log_full_errno((level), 0, __VA_ARGS__)
+
+int log_emergency_level(void);
+
+/* Normal logging */
+#define log_debug(...)     log_full(LOG_DEBUG,   __VA_ARGS__)
+#define log_info(...)      log_full(LOG_INFO,    __VA_ARGS__)
+#define log_notice(...)    log_full(LOG_NOTICE,  __VA_ARGS__)
+#define log_warning(...)   log_full(LOG_WARNING, __VA_ARGS__)
+#define log_error(...)     log_full(LOG_ERR,     __VA_ARGS__)
+#define log_emergency(...) log_full(log_emergency_level(), __VA_ARGS__)
+
+/* Logging triggered by an errno-like error */
+#define log_debug_errno(error, ...)     log_full_errno(LOG_DEBUG,   error, __VA_ARGS__)
+#define log_info_errno(error, ...)      log_full_errno(LOG_INFO,    error, __VA_ARGS__)
+#define log_notice_errno(error, ...)    log_full_errno(LOG_NOTICE,  error, __VA_ARGS__)
+#define log_warning_errno(error, ...)   log_full_errno(LOG_WARNING, error, __VA_ARGS__)
+#define log_error_errno(error, ...)     log_full_errno(LOG_ERR,     error, __VA_ARGS__)
+#define log_emergency_errno(error, ...) log_full_errno(log_emergency_level(), error, __VA_ARGS__)
+
+#ifdef LOG_TRACE
+#  define log_trace(...) log_debug(__VA_ARGS__)
+#else
+#  define log_trace(...) do {} while (0)
+#endif
+
+/* Structured logging */
+#define log_struct_errno(level, error, ...) \
+        log_struct_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \
+                            error, __FILE__, __LINE__, __func__, __VA_ARGS__, NULL)
+#define log_struct(level, ...) log_struct_errno(level, 0, __VA_ARGS__)
+
+#define log_struct_iovec_errno(level, error, iovec, n_iovec)            \
+        log_struct_iovec_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \
+                                  error, __FILE__, __LINE__, __func__, iovec, n_iovec)
+#define log_struct_iovec(level, iovec, n_iovec) log_struct_iovec_errno(level, 0, iovec, n_iovec)
+
+/* This modifies the buffer passed! */
+#define log_dump(level, buffer) \
+        log_dump_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \
+                          0, __FILE__, __LINE__, __func__, buffer)
+
+#define log_oom() log_oom_internal(LOG_REALM, __FILE__, __LINE__, __func__)
+
+bool log_on_console(void) _pure_;
+
+const char *log_target_to_string(LogTarget target) _const_;
+LogTarget log_target_from_string(const char *s) _pure_;
+
+/* Helper to prepare various field for structured logging */
+#define LOG_MESSAGE(fmt, ...) "MESSAGE=" fmt, ##__VA_ARGS__
+
+void log_received_signal(int level, const struct signalfd_siginfo *si);
+
+/* If turned on, any requests for a log target involving "syslog" will be implicitly upgraded to the equivalent journal target */
+void log_set_upgrade_syslog_to_journal(bool b);
+
+/* If turned on, and log_open() is called, we'll not use STDERR_FILENO for logging ever, but rather open /dev/console */
+void log_set_always_reopen_console(bool b);
+
+/* If turned on, we'll open the log stream implicitly if needed on each individual log call. This is normally not
+ * desired as we want to reuse our logging streams. It is useful however  */
+void log_set_open_when_needed(bool b);
+
+/* If turned on, then we'll never use IPC-based logging, i.e. never log to syslog or the journal. We'll only log to
+ * stderr, the console or kmsg */
+void log_set_prohibit_ipc(bool b);
+
+int log_dup_console(void);
+
+int log_syntax_internal(
+                const char *unit,
+                int level,
+                const char *config_file,
+                unsigned config_line,
+                int error,
+                const char *file,
+                int line,
+                const char *func,
+                const char *format, ...) _printf_(9, 10);
+
+int log_syntax_invalid_utf8_internal(
+                const char *unit,
+                int level,
+                const char *config_file,
+                unsigned config_line,
+                const char *file,
+                int line,
+                const char *func,
+                const char *rvalue);
+
+#define log_syntax(unit, level, config_file, config_line, error, ...)   \
+        ({                                                              \
+                int _level = (level), _e = (error);                     \
+                (log_get_max_level() >= LOG_PRI(_level))                \
+                        ? log_syntax_internal(unit, _level, config_file, config_line, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \
+                        : -abs(_e);                                     \
+        })
+
+#define log_syntax_invalid_utf8(unit, level, config_file, config_line, rvalue) \
+        ({                                                              \
+                int _level = (level);                                   \
+                (log_get_max_level() >= LOG_PRI(_level))                \
+                        ? log_syntax_invalid_utf8_internal(unit, _level, config_file, config_line, __FILE__, __LINE__, __func__, rvalue) \
+                        : -EINVAL;                                      \
+        })
+
+#define DEBUG_LOGGING _unlikely_(log_get_max_level() >= LOG_DEBUG)
+
+void log_setup_service(void);
diff --git a/shared/systemd/src/basic/macro.h b/shared/systemd/src/basic/macro.h
new file mode 100644
index 00000000..6a1fdab5
--- /dev/null
+++ b/shared/systemd/src/basic/macro.h
@@ -0,0 +1,558 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <sys/param.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+
+#define _printf_(a, b) __attribute__((__format__(printf, a, b)))
+#ifdef __clang__
+#  define _alloc_(...)
+#else
+#  define _alloc_(...) __attribute__((__alloc_size__(__VA_ARGS__)))
+#endif
+#define _sentinel_ __attribute__((__sentinel__))
+#define _section_(x) __attribute__((__section__(x)))
+#define _used_ __attribute__((__used__))
+#define _unused_ __attribute__((__unused__))
+#define _destructor_ __attribute__((__destructor__))
+#define _pure_ __attribute__((__pure__))
+#define _const_ __attribute__((__const__))
+#define _deprecated_ __attribute__((__deprecated__))
+#define _packed_ __attribute__((__packed__))
+#define _malloc_ __attribute__((__malloc__))
+#define _weak_ __attribute__((__weak__))
+#define _likely_(x) (__builtin_expect(!!(x), 1))
+#define _unlikely_(x) (__builtin_expect(!!(x), 0))
+#define _public_ __attribute__((__visibility__("default")))
+#define _hidden_ __attribute__((__visibility__("hidden")))
+#define _weakref_(x) __attribute__((__weakref__(#x)))
+#define _align_(x) __attribute__((__aligned__(x)))
+#define _alignas_(x) __attribute__((__aligned__(__alignof(x))))
+#define _alignptr_ __attribute__((__aligned__(sizeof(void*))))
+#define _cleanup_(x) __attribute__((__cleanup__(x)))
+#if __GNUC__ >= 7
+#define _fallthrough_ __attribute__((__fallthrough__))
+#else
+#define _fallthrough_
+#endif
+/* Define C11 noreturn without <stdnoreturn.h> and even on older gcc
+ * compiler versions */
+#ifndef _noreturn_
+#if __STDC_VERSION__ >= 201112L
+#define _noreturn_ _Noreturn
+#else
+#define _noreturn_ __attribute__((__noreturn__))
+#endif
+#endif
+
+#if !defined(HAS_FEATURE_MEMORY_SANITIZER)
+#  if defined(__has_feature)
+#    if __has_feature(memory_sanitizer)
+#      define HAS_FEATURE_MEMORY_SANITIZER 1
+#    endif
+#  endif
+#  if !defined(HAS_FEATURE_MEMORY_SANITIZER)
+#    define HAS_FEATURE_MEMORY_SANITIZER 0
+#  endif
+#endif
+
+#if !defined(HAS_FEATURE_ADDRESS_SANITIZER)
+#  ifdef __SANITIZE_ADDRESS__
+#      define HAS_FEATURE_ADDRESS_SANITIZER 1
+#  elif defined(__has_feature)
+#    if __has_feature(address_sanitizer)
+#      define HAS_FEATURE_ADDRESS_SANITIZER 1
+#    endif
+#  endif
+#  if !defined(HAS_FEATURE_ADDRESS_SANITIZER)
+#    define HAS_FEATURE_ADDRESS_SANITIZER 0
+#  endif
+#endif
+
+/* Note: on GCC "no_sanitize_address" is a function attribute only, on llvm it may also be applied to global
+ * variables. We define a specific macro which knows this. Note that on GCC we don't need this decorator so much, since
+ * our primary usecase for this attribute is registration structures placed in named ELF sections which shall not be
+ * padded, but GCC doesn't pad those anyway if AddressSanitizer is enabled. */
+#if HAS_FEATURE_ADDRESS_SANITIZER && defined(__clang__)
+#define _variable_no_sanitize_address_ __attribute__((__no_sanitize_address__))
+#else
+#define _variable_no_sanitize_address_
+#endif
+
+#if (defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) || defined (__clang__)
+/* Temporarily disable some warnings */
+#define DISABLE_WARNING_FORMAT_NONLITERAL                               \
+        _Pragma("GCC diagnostic push");                                 \
+        _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"")
+
+#define DISABLE_WARNING_MISSING_PROTOTYPES                              \
+        _Pragma("GCC diagnostic push");                                 \
+        _Pragma("GCC diagnostic ignored \"-Wmissing-prototypes\"")
+
+#define DISABLE_WARNING_NONNULL                                         \
+        _Pragma("GCC diagnostic push");                                 \
+        _Pragma("GCC diagnostic ignored \"-Wnonnull\"")
+
+#define DISABLE_WARNING_SHADOW                                          \
+        _Pragma("GCC diagnostic push");                                 \
+        _Pragma("GCC diagnostic ignored \"-Wshadow\"")
+
+#define DISABLE_WARNING_INCOMPATIBLE_POINTER_TYPES                      \
+        _Pragma("GCC diagnostic push");                                 \
+        _Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"")
+
+#define REENABLE_WARNING                                                \
+        _Pragma("GCC diagnostic pop")
+#else
+#define DISABLE_WARNING_DECLARATION_AFTER_STATEMENT
+#define DISABLE_WARNING_FORMAT_NONLITERAL
+#define DISABLE_WARNING_MISSING_PROTOTYPES
+#define DISABLE_WARNING_NONNULL
+#define DISABLE_WARNING_SHADOW
+#define REENABLE_WARNING
+#endif
+
+/* automake test harness */
+#define EXIT_TEST_SKIP 77
+
+#define XSTRINGIFY(x) #x
+#define STRINGIFY(x) XSTRINGIFY(x)
+
+#define XCONCATENATE(x, y) x ## y
+#define CONCATENATE(x, y) XCONCATENATE(x, y)
+
+#define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq))
+#define UNIQ __COUNTER__
+
+/* builtins */
+#if __SIZEOF_INT__ == 4
+#define BUILTIN_FFS_U32(x) __builtin_ffs(x);
+#elif __SIZEOF_LONG__ == 4
+#define BUILTIN_FFS_U32(x) __builtin_ffsl(x);
+#else
+#error "neither int nor long are four bytes long?!?"
+#endif
+
+/* Rounds up */
+
+#define ALIGN4(l) (((l) + 3) & ~3)
+#define ALIGN8(l) (((l) + 7) & ~7)
+
+#if __SIZEOF_POINTER__ == 8
+#define ALIGN(l) ALIGN8(l)
+#elif __SIZEOF_POINTER__ == 4
+#define ALIGN(l) ALIGN4(l)
+#else
+#error "Wut? Pointers are neither 4 nor 8 bytes long?"
+#endif
+
+#define ALIGN_PTR(p) ((void*) ALIGN((unsigned long) (p)))
+#define ALIGN4_PTR(p) ((void*) ALIGN4((unsigned long) (p)))
+#define ALIGN8_PTR(p) ((void*) ALIGN8((unsigned long) (p)))
+
+static inline size_t ALIGN_TO(size_t l, size_t ali) {
+        return ((l + ali - 1) & ~(ali - 1));
+}
+
+#define ALIGN_TO_PTR(p, ali) ((void*) ALIGN_TO((unsigned long) (p), (ali)))
+
+/* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */
+static inline unsigned long ALIGN_POWER2(unsigned long u) {
+        /* clz(0) is undefined */
+        if (u == 1)
+                return 1;
+
+        /* left-shift overflow is undefined */
+        if (__builtin_clzl(u - 1UL) < 1)
+                return 0;
+
+        return 1UL << (sizeof(u) * 8 - __builtin_clzl(u - 1UL));
+}
+
+#ifndef __COVERITY__
+#  define VOID_0 ((void)0)
+#else
+#  define VOID_0 ((void*)0)
+#endif
+
+#define ELEMENTSOF(x)                                                   \
+        (__builtin_choose_expr(                                         \
+                !__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \
+                sizeof(x)/sizeof((x)[0]),                               \
+                VOID_0))
+
+/*
+ * STRLEN - return the length of a string literal, minus the trailing NUL byte.
+ *          Contrary to strlen(), this is a constant expression.
+ * @x: a string literal.
+ */
+#define STRLEN(x) (sizeof(""x"") - 1)
+
+/*
+ * container_of - cast a member of a structure out to the containing structure
+ * @ptr: the pointer to the member.
+ * @type: the type of the container struct this is embedded in.
+ * @member: the name of the member within the struct.
+ */
+#define container_of(ptr, type, member) __container_of(UNIQ, (ptr), type, member)
+#define __container_of(uniq, ptr, type, member)                         \
+        ({                                                              \
+                const typeof( ((type*)0)->member ) *UNIQ_T(A, uniq) = (ptr); \
+                (type*)( (char *)UNIQ_T(A, uniq) - offsetof(type, member) ); \
+        })
+
+#undef MAX
+#define MAX(a, b) __MAX(UNIQ, (a), UNIQ, (b))
+#define __MAX(aq, a, bq, b)                             \
+        ({                                              \
+                const typeof(a) UNIQ_T(A, aq) = (a);    \
+                const typeof(b) UNIQ_T(B, bq) = (b);    \
+                UNIQ_T(A, aq) > UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \
+        })
+
+/* evaluates to (void) if _A or _B are not constant or of different types */
+#define CONST_MAX(_A, _B) \
+        (__builtin_choose_expr(                                         \
+                __builtin_constant_p(_A) &&                             \
+                __builtin_constant_p(_B) &&                             \
+                __builtin_types_compatible_p(typeof(_A), typeof(_B)),   \
+                ((_A) > (_B)) ? (_A) : (_B),                            \
+                VOID_0))
+
+/* takes two types and returns the size of the larger one */
+#define MAXSIZE(A, B) (sizeof(union _packed_ { typeof(A) a; typeof(B) b; }))
+
+#define MAX3(x, y, z)                                   \
+        ({                                              \
+                const typeof(x) _c = MAX(x, y);         \
+                MAX(_c, z);                             \
+        })
+
+#undef MIN
+#define MIN(a, b) __MIN(UNIQ, (a), UNIQ, (b))
+#define __MIN(aq, a, bq, b)                             \
+        ({                                              \
+                const typeof(a) UNIQ_T(A, aq) = (a);    \
+                const typeof(b) UNIQ_T(B, bq) = (b);    \
+                UNIQ_T(A, aq) < UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \
+        })
+
+#define MIN3(x, y, z)                                   \
+        ({                                              \
+                const typeof(x) _c = MIN(x, y);         \
+                MIN(_c, z);                             \
+        })
+
+#define LESS_BY(a, b) __LESS_BY(UNIQ, (a), UNIQ, (b))
+#define __LESS_BY(aq, a, bq, b)                         \
+        ({                                              \
+                const typeof(a) UNIQ_T(A, aq) = (a);    \
+                const typeof(b) UNIQ_T(B, bq) = (b);    \
+                UNIQ_T(A, aq) > UNIQ_T(B, bq) ? UNIQ_T(A, aq) - UNIQ_T(B, bq) : 0; \
+        })
+
+#define CMP(a, b) __CMP(UNIQ, (a), UNIQ, (b))
+#define __CMP(aq, a, bq, b)                             \
+        ({                                              \
+                const typeof(a) UNIQ_T(A, aq) = (a);    \
+                const typeof(b) UNIQ_T(B, bq) = (b);    \
+                UNIQ_T(A, aq) < UNIQ_T(B, bq) ? -1 :    \
+                UNIQ_T(A, aq) > UNIQ_T(B, bq) ? 1 : 0;  \
+        })
+
+#undef CLAMP
+#define CLAMP(x, low, high) __CLAMP(UNIQ, (x), UNIQ, (low), UNIQ, (high))
+#define __CLAMP(xq, x, lowq, low, highq, high)                          \
+        ({                                                              \
+                const typeof(x) UNIQ_T(X, xq) = (x);                    \
+                const typeof(low) UNIQ_T(LOW, lowq) = (low);            \
+                const typeof(high) UNIQ_T(HIGH, highq) = (high);        \
+                        UNIQ_T(X, xq) > UNIQ_T(HIGH, highq) ?           \
+                                UNIQ_T(HIGH, highq) :                   \
+                                UNIQ_T(X, xq) < UNIQ_T(LOW, lowq) ?     \
+                                        UNIQ_T(LOW, lowq) :             \
+                                        UNIQ_T(X, xq);                  \
+        })
+
+/* [(x + y - 1) / y] suffers from an integer overflow, even though the
+ * computation should be possible in the given type. Therefore, we use
+ * [x / y + !!(x % y)]. Note that on "Real CPUs" a division returns both the
+ * quotient and the remainder, so both should be equally fast. */
+#define DIV_ROUND_UP(x, y) __DIV_ROUND_UP(UNIQ, (x), UNIQ, (y))
+#define __DIV_ROUND_UP(xq, x, yq, y)                                    \
+        ({                                                              \
+                const typeof(x) UNIQ_T(X, xq) = (x);                    \
+                const typeof(y) UNIQ_T(Y, yq) = (y);                    \
+                (UNIQ_T(X, xq) / UNIQ_T(Y, yq) + !!(UNIQ_T(X, xq) % UNIQ_T(Y, yq))); \
+        })
+
+#ifdef __COVERITY__
+
+/* Use special definitions of assertion macros in order to prevent
+ * false positives of ASSERT_SIDE_EFFECT on Coverity static analyzer
+ * for uses of assert_se() and assert_return().
+ *
+ * These definitions make expression go through a (trivial) function
+ * call to ensure they are not discarded. Also use ! or !! to ensure
+ * the boolean expressions are seen as such.
+ *
+ * This technique has been described and recommended in:
+ * https://community.synopsys.com/s/question/0D534000046Yuzb/suppressing-assertsideeffect-for-functions-that-allow-for-sideeffects
+ */
+
+extern void __coverity_panic__(void);
+
+static inline int __coverity_check__(int condition) {
+        return condition;
+}
+
+#define assert_message_se(expr, message)                                \
+        do {                                                            \
+                if (__coverity_check__(!(expr)))                        \
+                        __coverity_panic__();                           \
+        } while (false)
+
+#define assert_log(expr, message) __coverity_check__(!!(expr))
+
+#else  /* ! __COVERITY__ */
+
+#define assert_message_se(expr, message)                                \
+        do {                                                            \
+                if (_unlikely_(!(expr)))                                \
+                        log_assert_failed(message, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+        } while (false)
+
+#define assert_log(expr, message) ((_likely_(expr))                     \
+        ? (true)                                                        \
+        : (log_assert_failed_return(message, __FILE__, __LINE__, __PRETTY_FUNCTION__), false))
+
+#endif  /* __COVERITY__ */
+
+#define assert_se(expr) assert_message_se(expr, #expr)
+
+/* We override the glibc assert() here. */
+#undef assert
+#ifdef NDEBUG
+#define assert(expr) do {} while (false)
+#else
+#define assert(expr) assert_message_se(expr, #expr)
+#endif
+
+#define assert_not_reached(t)                                           \
+        do {                                                            \
+                log_assert_failed_unreachable(t, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+        } while (false)
+
+#if defined(static_assert)
+#define assert_cc(expr)                                                 \
+        static_assert(expr, #expr);
+#else
+#define assert_cc(expr)                                                 \
+        struct CONCATENATE(_assert_struct_, __COUNTER__) {              \
+                char x[(expr) ? 0 : -1];                                \
+        };
+#endif
+
+#define assert_return(expr, r)                                          \
+        do {                                                            \
+                if (!assert_log(expr, #expr))                           \
+                        return (r);                                     \
+        } while (false)
+
+#define assert_return_errno(expr, r, err)                               \
+        do {                                                            \
+                if (!assert_log(expr, #expr)) {                         \
+                        errno = err;                                    \
+                        return (r);                                     \
+                }                                                       \
+        } while (false)
+
+#define return_with_errno(r, err)                     \
+        do {                                          \
+                errno = abs(err);                     \
+                return r;                             \
+        } while (false)
+
+#define PTR_TO_INT(p) ((int) ((intptr_t) (p)))
+#define INT_TO_PTR(u) ((void *) ((intptr_t) (u)))
+#define PTR_TO_UINT(p) ((unsigned) ((uintptr_t) (p)))
+#define UINT_TO_PTR(u) ((void *) ((uintptr_t) (u)))
+
+#define PTR_TO_LONG(p) ((long) ((intptr_t) (p)))
+#define LONG_TO_PTR(u) ((void *) ((intptr_t) (u)))
+#define PTR_TO_ULONG(p) ((unsigned long) ((uintptr_t) (p)))
+#define ULONG_TO_PTR(u) ((void *) ((uintptr_t) (u)))
+
+#define PTR_TO_INT32(p) ((int32_t) ((intptr_t) (p)))
+#define INT32_TO_PTR(u) ((void *) ((intptr_t) (u)))
+#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p)))
+#define UINT32_TO_PTR(u) ((void *) ((uintptr_t) (u)))
+
+#define PTR_TO_INT64(p) ((int64_t) ((intptr_t) (p)))
+#define INT64_TO_PTR(u) ((void *) ((intptr_t) (u)))
+#define PTR_TO_UINT64(p) ((uint64_t) ((uintptr_t) (p)))
+#define UINT64_TO_PTR(u) ((void *) ((uintptr_t) (u)))
+
+#define PTR_TO_SIZE(p) ((size_t) ((uintptr_t) (p)))
+#define SIZE_TO_PTR(u) ((void *) ((uintptr_t) (u)))
+
+#define CHAR_TO_STR(x) ((char[2]) { x, 0 })
+
+#define char_array_0(x) x[sizeof(x)-1] = 0;
+
+/* Returns the number of chars needed to format variables of the
+ * specified type as a decimal string. Adds in extra space for a
+ * negative '-' prefix (hence works correctly on signed
+ * types). Includes space for the trailing NUL. */
+#define DECIMAL_STR_MAX(type)                                           \
+        (2+(sizeof(type) <= 1 ? 3 :                                     \
+            sizeof(type) <= 2 ? 5 :                                     \
+            sizeof(type) <= 4 ? 10 :                                    \
+            sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)])))
+
+#define DECIMAL_STR_WIDTH(x)                            \
+        ({                                              \
+                typeof(x) _x_ = (x);                    \
+                unsigned ans = 1;                       \
+                while ((_x_ /= 10) != 0)                \
+                        ans++;                          \
+                ans;                                    \
+        })
+
+#define SET_FLAG(v, flag, b) \
+        (v) = (b) ? ((v) | (flag)) : ((v) & ~(flag))
+#define FLAGS_SET(v, flags) \
+        ((~(v) & (flags)) == 0)
+
+#define CASE_F(X) case X:
+#define CASE_F_1(CASE, X) CASE_F(X)
+#define CASE_F_2(CASE, X, ...)  CASE(X) CASE_F_1(CASE, __VA_ARGS__)
+#define CASE_F_3(CASE, X, ...)  CASE(X) CASE_F_2(CASE, __VA_ARGS__)
+#define CASE_F_4(CASE, X, ...)  CASE(X) CASE_F_3(CASE, __VA_ARGS__)
+#define CASE_F_5(CASE, X, ...)  CASE(X) CASE_F_4(CASE, __VA_ARGS__)
+#define CASE_F_6(CASE, X, ...)  CASE(X) CASE_F_5(CASE, __VA_ARGS__)
+#define CASE_F_7(CASE, X, ...)  CASE(X) CASE_F_6(CASE, __VA_ARGS__)
+#define CASE_F_8(CASE, X, ...)  CASE(X) CASE_F_7(CASE, __VA_ARGS__)
+#define CASE_F_9(CASE, X, ...)  CASE(X) CASE_F_8(CASE, __VA_ARGS__)
+#define CASE_F_10(CASE, X, ...) CASE(X) CASE_F_9(CASE, __VA_ARGS__)
+#define CASE_F_11(CASE, X, ...) CASE(X) CASE_F_10(CASE, __VA_ARGS__)
+#define CASE_F_12(CASE, X, ...) CASE(X) CASE_F_11(CASE, __VA_ARGS__)
+#define CASE_F_13(CASE, X, ...) CASE(X) CASE_F_12(CASE, __VA_ARGS__)
+#define CASE_F_14(CASE, X, ...) CASE(X) CASE_F_13(CASE, __VA_ARGS__)
+#define CASE_F_15(CASE, X, ...) CASE(X) CASE_F_14(CASE, __VA_ARGS__)
+#define CASE_F_16(CASE, X, ...) CASE(X) CASE_F_15(CASE, __VA_ARGS__)
+#define CASE_F_17(CASE, X, ...) CASE(X) CASE_F_16(CASE, __VA_ARGS__)
+#define CASE_F_18(CASE, X, ...) CASE(X) CASE_F_17(CASE, __VA_ARGS__)
+#define CASE_F_19(CASE, X, ...) CASE(X) CASE_F_18(CASE, __VA_ARGS__)
+#define CASE_F_20(CASE, X, ...) CASE(X) CASE_F_19(CASE, __VA_ARGS__)
+
+#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME
+#define FOR_EACH_MAKE_CASE(...) \
+        GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \
+                               CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \
+                   (CASE_F,__VA_ARGS__)
+
+#define IN_SET(x, ...)                          \
+        ({                                      \
+                bool _found = false;            \
+                /* If the build breaks in the line below, you need to extend the case macros. (We use "long double" as  \
+                 * type for the array, in the hope that checkers such as ubsan don't complain that the initializers for \
+                 * the array are not representable by the base type. Ideally we'd use typeof(x) as base type, but that  \
+                 * doesn't work, as we want to use this on bitfields and gcc refuses typeof() on bitfields.) */         \
+                assert_cc((sizeof((long double[]){__VA_ARGS__})/sizeof(long double)) <= 20); \
+                switch(x) {                     \
+                FOR_EACH_MAKE_CASE(__VA_ARGS__) \
+                        _found = true;          \
+                        break;                  \
+                default:                        \
+                        break;                  \
+                }                               \
+                _found;                         \
+        })
+
+#define SWAP_TWO(x, y) do {                        \
+                typeof(x) _t = (x);                \
+                (x) = (y);                         \
+                (y) = (_t);                        \
+        } while (false)
+
+/* Define C11 thread_local attribute even on older gcc compiler
+ * version */
+#ifndef thread_local
+/*
+ * Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__
+ * see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769
+ */
+#if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16))
+#define thread_local _Thread_local
+#else
+#define thread_local __thread
+#endif
+#endif
+
+#define DEFINE_TRIVIAL_DESTRUCTOR(name, type, func)             \
+        static inline void name(type *p) {                      \
+                func(p);                                        \
+        }
+
+#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func)                 \
+        static inline void func##p(type *p) {                   \
+                if (*p)                                         \
+                        func(*p);                               \
+        }
+
+#define _DEFINE_TRIVIAL_REF_FUNC(type, name, scope)             \
+        scope type *name##_ref(type *p) {                       \
+                if (!p)                                         \
+                        return NULL;                            \
+                                                                \
+                assert(p->n_ref > 0);                           \
+                p->n_ref++;                                     \
+                return p;                                       \
+        }
+
+#define _DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func, scope) \
+        scope type *name##_unref(type *p) {                      \
+                if (!p)                                          \
+                        return NULL;                             \
+                                                                 \
+                assert(p->n_ref > 0);                            \
+                p->n_ref--;                                      \
+                if (p->n_ref > 0)                                \
+                        return NULL;                             \
+                                                                 \
+                return free_func(p);                             \
+        }
+
+#define DEFINE_TRIVIAL_REF_FUNC(type, name)     \
+        _DEFINE_TRIVIAL_REF_FUNC(type, name,)
+#define DEFINE_PRIVATE_TRIVIAL_REF_FUNC(type, name)     \
+        _DEFINE_TRIVIAL_REF_FUNC(type, name, static)
+#define DEFINE_PUBLIC_TRIVIAL_REF_FUNC(type, name)      \
+        _DEFINE_TRIVIAL_REF_FUNC(type, name, _public_)
+
+#define DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func)        \
+        _DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func,)
+#define DEFINE_PRIVATE_TRIVIAL_UNREF_FUNC(type, name, free_func)        \
+        _DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func, static)
+#define DEFINE_PUBLIC_TRIVIAL_UNREF_FUNC(type, name, free_func)         \
+        _DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func, _public_)
+
+#define DEFINE_TRIVIAL_REF_UNREF_FUNC(type, name, free_func)    \
+        DEFINE_TRIVIAL_REF_FUNC(type, name);                    \
+        DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func);
+
+#define DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(type, name, free_func)    \
+        DEFINE_PRIVATE_TRIVIAL_REF_FUNC(type, name);                    \
+        DEFINE_PRIVATE_TRIVIAL_UNREF_FUNC(type, name, free_func);
+
+#define DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(type, name, free_func)    \
+        DEFINE_PUBLIC_TRIVIAL_REF_FUNC(type, name);                    \
+        DEFINE_PUBLIC_TRIVIAL_UNREF_FUNC(type, name, free_func);
+
+#include "log.h"
diff --git a/shared/systemd/src/basic/mempool.c b/shared/systemd/src/basic/mempool.c
new file mode 100644
index 00000000..0fa51fba
--- /dev/null
+++ b/shared/systemd/src/basic/mempool.c
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "env-util.h"
+#include "macro.h"
+#include "mempool.h"
+#include "process-util.h"
+#include "util.h"
+
+struct pool {
+        struct pool *next;
+        size_t n_tiles;
+        size_t n_used;
+};
+
+void* mempool_alloc_tile(struct mempool *mp) {
+        size_t i;
+
+        /* When a tile is released we add it to the list and simply
+         * place the next pointer at its offset 0. */
+
+        assert(mp->tile_size >= sizeof(void*));
+        assert(mp->at_least > 0);
+
+        if (mp->freelist) {
+                void *r;
+
+                r = mp->freelist;
+                mp->freelist = * (void**) mp->freelist;
+                return r;
+        }
+
+        if (_unlikely_(!mp->first_pool) ||
+            _unlikely_(mp->first_pool->n_used >= mp->first_pool->n_tiles)) {
+                size_t size, n;
+                struct pool *p;
+
+                n = mp->first_pool ? mp->first_pool->n_tiles : 0;
+                n = MAX(mp->at_least, n * 2);
+                size = PAGE_ALIGN(ALIGN(sizeof(struct pool)) + n*mp->tile_size);
+                n = (size - ALIGN(sizeof(struct pool))) / mp->tile_size;
+
+                p = malloc(size);
+                if (!p)
+                        return NULL;
+
+                p->next = mp->first_pool;
+                p->n_tiles = n;
+                p->n_used = 0;
+
+                mp->first_pool = p;
+        }
+
+        i = mp->first_pool->n_used++;
+
+        return ((uint8_t*) mp->first_pool) + ALIGN(sizeof(struct pool)) + i*mp->tile_size;
+}
+
+void* mempool_alloc0_tile(struct mempool *mp) {
+        void *p;
+
+        p = mempool_alloc_tile(mp);
+        if (p)
+                memzero(p, mp->tile_size);
+        return p;
+}
+
+void mempool_free_tile(struct mempool *mp, void *p) {
+        * (void**) p = mp->freelist;
+        mp->freelist = p;
+}
+
+bool mempool_enabled(void) {
+        static int b = -1;
+
+        if (!is_main_thread())
+                return false;
+
+        if (!mempool_use_allowed)
+                b = false;
+        if (b < 0)
+                b = getenv_bool("SYSTEMD_MEMPOOL") != 0;
+
+        return b;
+}
+
+#if VALGRIND
+void mempool_drop(struct mempool *mp) {
+        struct pool *p = mp->first_pool;
+        while (p) {
+                struct pool *n;
+                n = p->next;
+                free(p);
+                p = n;
+        }
+}
+#endif
diff --git a/shared/systemd/src/basic/mempool.h b/shared/systemd/src/basic/mempool.h
new file mode 100644
index 00000000..0eecca0f
--- /dev/null
+++ b/shared/systemd/src/basic/mempool.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct pool;
+
+struct mempool {
+        struct pool *first_pool;
+        void *freelist;
+        size_t tile_size;
+        unsigned at_least;
+};
+
+void* mempool_alloc_tile(struct mempool *mp);
+void* mempool_alloc0_tile(struct mempool *mp);
+void mempool_free_tile(struct mempool *mp, void *p);
+
+#define DEFINE_MEMPOOL(pool_name, tile_type, alloc_at_least) \
+static struct mempool pool_name = { \
+        .tile_size = sizeof(tile_type), \
+        .at_least = alloc_at_least, \
+}
+
+extern const bool mempool_use_allowed;
+bool mempool_enabled(void);
+
+#if VALGRIND
+void mempool_drop(struct mempool *mp);
+#endif
diff --git a/shared/systemd/src/basic/missing_fcntl.h b/shared/systemd/src/basic/missing_fcntl.h
new file mode 100644
index 00000000..5d1c6352
--- /dev/null
+++ b/shared/systemd/src/basic/missing_fcntl.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <fcntl.h>
+
+#ifndef F_LINUX_SPECIFIC_BASE
+#define F_LINUX_SPECIFIC_BASE 1024
+#endif
+
+#ifndef F_SETPIPE_SZ
+#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+#endif
+
+#ifndef F_GETPIPE_SZ
+#define F_GETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 8)
+#endif
+
+#ifndef F_ADD_SEALS
+#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9)
+#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10)
+
+#define F_SEAL_SEAL     0x0001  /* prevent further seals from being set */
+#define F_SEAL_SHRINK   0x0002  /* prevent file from shrinking */
+#define F_SEAL_GROW     0x0004  /* prevent file from growing */
+#define F_SEAL_WRITE    0x0008  /* prevent writes */
+#endif
+
+#ifndef F_OFD_GETLK
+#define F_OFD_GETLK     36
+#define F_OFD_SETLK     37
+#define F_OFD_SETLKW    38
+#endif
+
+#ifndef MAX_HANDLE_SZ
+#define MAX_HANDLE_SZ 128
+#endif
+
+/* The precise definition of __O_TMPFILE is arch specific; use the
+ * values defined by the kernel (note: some are hexa, some are octal,
+ * duplicated as-is from the kernel definitions):
+ * - alpha, parisc, sparc: each has a specific value;
+ * - others: they use the "generic" value.
+ */
+
+#ifndef __O_TMPFILE
+#if defined(__alpha__)
+#define __O_TMPFILE     0100000000
+#elif defined(__parisc__) || defined(__hppa__)
+#define __O_TMPFILE     0400000000
+#elif defined(__sparc__) || defined(__sparc64__)
+#define __O_TMPFILE     0x2000000
+#else
+#define __O_TMPFILE     020000000
+#endif
+#endif
+
+/* a horrid kludge trying to make sure that this will fail on old kernels */
+#ifndef O_TMPFILE
+#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
+#endif
diff --git a/shared/systemd/src/basic/missing_type.h b/shared/systemd/src/basic/missing_type.h
new file mode 100644
index 00000000..bf8a6caa
--- /dev/null
+++ b/shared/systemd/src/basic/missing_type.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <uchar.h>
+
+#if !HAVE_CHAR32_T
+#define char32_t uint32_t
+#endif
+
+#if !HAVE_CHAR16_T
+#define char16_t uint16_t
+#endif
diff --git a/shared/systemd/src/basic/parse-util.c b/shared/systemd/src/basic/parse-util.c
new file mode 100644
index 00000000..02ef426f
--- /dev/null
+++ b/shared/systemd/src/basic/parse-util.c
@@ -0,0 +1,785 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <linux/oom.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include "alloc-util.h"
+#include "errno-list.h"
+#include "extract-word.h"
+#include "locale-util.h"
+#include "macro.h"
+#include "missing.h"
+#include "parse-util.h"
+#include "process-util.h"
+#include "stat-util.h"
+#include "string-util.h"
+
+int parse_boolean(const char *v) {
+        if (!v)
+                return -EINVAL;
+
+        if (streq(v, "1") || strcaseeq(v, "yes") || strcaseeq(v, "y") || strcaseeq(v, "true") || strcaseeq(v, "t") || strcaseeq(v, "on"))
+                return 1;
+        else if (streq(v, "0") || strcaseeq(v, "no") || strcaseeq(v, "n") || strcaseeq(v, "false") || strcaseeq(v, "f") || strcaseeq(v, "off"))
+                return 0;
+
+        return -EINVAL;
+}
+
+#if 0 /* NM_IGNORED */
+int parse_pid(const char *s, pid_t* ret_pid) {
+        unsigned long ul = 0;
+        pid_t pid;
+        int r;
+
+        assert(s);
+        assert(ret_pid);
+
+        r = safe_atolu(s, &ul);
+        if (r < 0)
+                return r;
+
+        pid = (pid_t) ul;
+
+        if ((unsigned long) pid != ul)
+                return -ERANGE;
+
+        if (!pid_is_valid(pid))
+                return -ERANGE;
+
+        *ret_pid = pid;
+        return 0;
+}
+
+int parse_mode(const char *s, mode_t *ret) {
+        char *x;
+        long l;
+
+        assert(s);
+        assert(ret);
+
+        s += strspn(s, WHITESPACE);
+        if (s[0] == '-')
+                return -ERANGE;
+
+        errno = 0;
+        l = strtol(s, &x, 8);
+        if (errno > 0)
+                return -errno;
+        if (!x || x == s || *x != 0)
+                return -EINVAL;
+        if (l < 0 || l  > 07777)
+                return -ERANGE;
+
+        *ret = (mode_t) l;
+        return 0;
+}
+
+int parse_ifindex(const char *s, int *ret) {
+        int ifi, r;
+
+        r = safe_atoi(s, &ifi);
+        if (r < 0)
+                return r;
+        if (ifi <= 0)
+                return -EINVAL;
+
+        *ret = ifi;
+        return 0;
+}
+
+int parse_mtu(int family, const char *s, uint32_t *ret) {
+        uint64_t u;
+        size_t m;
+        int r;
+
+        r = parse_size(s, 1024, &u);
+        if (r < 0)
+                return r;
+
+        if (u > UINT32_MAX)
+                return -ERANGE;
+
+        if (family == AF_INET6)
+                m = IPV6_MIN_MTU; /* This is 1280 */
+        else
+                m = IPV4_MIN_MTU; /* For all other protocols, including 'unspecified' we assume the IPv4 minimal MTU */
+
+        if (u < m)
+                return -ERANGE;
+
+        *ret = (uint32_t) u;
+        return 0;
+}
+
+int parse_size(const char *t, uint64_t base, uint64_t *size) {
+
+        /* Soo, sometimes we want to parse IEC binary suffixes, and
+         * sometimes SI decimal suffixes. This function can parse
+         * both. Which one is the right way depends on the
+         * context. Wikipedia suggests that SI is customary for
+         * hardware metrics and network speeds, while IEC is
+         * customary for most data sizes used by software and volatile
+         * (RAM) memory. Hence be careful which one you pick!
+         *
+         * In either case we use just K, M, G as suffix, and not Ki,
+         * Mi, Gi or so (as IEC would suggest). That's because that's
+         * frickin' ugly. But this means you really need to make sure
+         * to document which base you are parsing when you use this
+         * call. */
+
+        struct table {
+                const char *suffix;
+                unsigned long long factor;
+        };
+
+        static const struct table iec[] = {
+                { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
+                { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
+                { "T", 1024ULL*1024ULL*1024ULL*1024ULL },
+                { "G", 1024ULL*1024ULL*1024ULL },
+                { "M", 1024ULL*1024ULL },
+                { "K", 1024ULL },
+                { "B", 1ULL },
+                { "",  1ULL },
+        };
+
+        static const struct table si[] = {
+                { "E", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL*1000ULL },
+                { "P", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL },
+                { "T", 1000ULL*1000ULL*1000ULL*1000ULL },
+                { "G", 1000ULL*1000ULL*1000ULL },
+                { "M", 1000ULL*1000ULL },
+                { "K", 1000ULL },
+                { "B", 1ULL },
+                { "",  1ULL },
+        };
+
+        const struct table *table;
+        const char *p;
+        unsigned long long r = 0;
+        unsigned n_entries, start_pos = 0;
+
+        assert(t);
+        assert(IN_SET(base, 1000, 1024));
+        assert(size);
+
+        if (base == 1000) {
+                table = si;
+                n_entries = ELEMENTSOF(si);
+        } else {
+                table = iec;
+                n_entries = ELEMENTSOF(iec);
+        }
+
+        p = t;
+        do {
+                unsigned long long l, tmp;
+                double frac = 0;
+                char *e;
+                unsigned i;
+
+                p += strspn(p, WHITESPACE);
+
+                errno = 0;
+                l = strtoull(p, &e, 10);
+                if (errno > 0)
+                        return -errno;
+                if (e == p)
+                        return -EINVAL;
+                if (*p == '-')
+                        return -ERANGE;
+
+                if (*e == '.') {
+                        e++;
+
+                        /* strtoull() itself would accept space/+/- */
+                        if (*e >= '0' && *e <= '9') {
+                                unsigned long long l2;
+                                char *e2;
+
+                                l2 = strtoull(e, &e2, 10);
+                                if (errno > 0)
+                                        return -errno;
+
+                                /* Ignore failure. E.g. 10.M is valid */
+                                frac = l2;
+                                for (; e < e2; e++)
+                                        frac /= 10;
+                        }
+                }
+
+                e += strspn(e, WHITESPACE);
+
+                for (i = start_pos; i < n_entries; i++)
+                        if (startswith(e, table[i].suffix))
+                                break;
+
+                if (i >= n_entries)
+                        return -EINVAL;
+
+                if (l + (frac > 0) > ULLONG_MAX / table[i].factor)
+                        return -ERANGE;
+
+                tmp = l * table[i].factor + (unsigned long long) (frac * table[i].factor);
+                if (tmp > ULLONG_MAX - r)
+                        return -ERANGE;
+
+                r += tmp;
+                if ((unsigned long long) (uint64_t) r != r)
+                        return -ERANGE;
+
+                p = e + strlen(table[i].suffix);
+
+                start_pos = i + 1;
+
+        } while (*p);
+
+        *size = r;
+
+        return 0;
+}
+
+int parse_range(const char *t, unsigned *lower, unsigned *upper) {
+        _cleanup_free_ char *word = NULL;
+        unsigned l, u;
+        int r;
+
+        assert(lower);
+        assert(upper);
+
+        /* Extract the lower bound. */
+        r = extract_first_word(&t, &word, "-", EXTRACT_DONT_COALESCE_SEPARATORS);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -EINVAL;
+
+        r = safe_atou(word, &l);
+        if (r < 0)
+                return r;
+
+        /* Check for the upper bound and extract it if needed */
+        if (!t)
+                /* Single number with no dashes. */
+                u = l;
+        else if (!*t)
+                /* Trailing dash is an error. */
+                return -EINVAL;
+        else {
+                r = safe_atou(t, &u);
+                if (r < 0)
+                        return r;
+        }
+
+        *lower = l;
+        *upper = u;
+        return 0;
+}
+
+int parse_errno(const char *t) {
+        int r, e;
+
+        assert(t);
+
+        r = errno_from_name(t);
+        if (r > 0)
+                return r;
+
+        r = safe_atoi(t, &e);
+        if (r < 0)
+                return r;
+
+        /* 0 is also allowed here */
+        if (!errno_is_valid(e) && e != 0)
+                return -ERANGE;
+
+        return e;
+}
+
+int parse_syscall_and_errno(const char *in, char **name, int *error) {
+        _cleanup_free_ char *n = NULL;
+        char *p;
+        int e = -1;
+
+        assert(in);
+        assert(name);
+        assert(error);
+
+        /*
+         * This parse "syscall:errno" like "uname:EILSEQ", "@sync:255".
+         * If errno is omitted, then error is set to -1.
+         * Empty syscall name is not allowed.
+         * Here, we do not check that the syscall name is valid or not.
+         */
+
+        p = strchr(in, ':');
+        if (p) {
+                e = parse_errno(p + 1);
+                if (e < 0)
+                        return e;
+
+                n = strndup(in, p - in);
+        } else
+                n = strdup(in);
+
+        if (!n)
+                return -ENOMEM;
+
+        if (isempty(n))
+                return -EINVAL;
+
+        *error = e;
+        *name = TAKE_PTR(n);
+
+        return 0;
+}
+
+char *format_bytes(char *buf, size_t l, uint64_t t) {
+        unsigned i;
+
+        /* This only does IEC units so far */
+
+        static const struct {
+                const char *suffix;
+                uint64_t factor;
+        } table[] = {
+                { "E", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
+                { "P", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
+                { "T", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
+                { "G", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
+                { "M", UINT64_C(1024)*UINT64_C(1024) },
+                { "K", UINT64_C(1024) },
+        };
+
+        if (t == (uint64_t) -1)
+                return NULL;
+
+        for (i = 0; i < ELEMENTSOF(table); i++) {
+
+                if (t >= table[i].factor) {
+                        snprintf(buf, l,
+                                 "%" PRIu64 ".%" PRIu64 "%s",
+                                 t / table[i].factor,
+                                 ((t*UINT64_C(10)) / table[i].factor) % UINT64_C(10),
+                                 table[i].suffix);
+
+                        goto finish;
+                }
+        }
+
+        snprintf(buf, l, "%" PRIu64 "B", t);
+
+finish:
+        buf[l-1] = 0;
+        return buf;
+
+}
+#endif /* NM_IGNORED */
+
+int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
+        char *x = NULL;
+        unsigned long l;
+
+        assert(s);
+        assert(ret_u);
+        assert(base <= 16);
+
+        /* strtoul() is happy to parse negative values, and silently
+         * converts them to unsigned values without generating an
+         * error. We want a clean error, hence let's look for the "-"
+         * prefix on our own, and generate an error. But let's do so
+         * only after strtoul() validated that the string is clean
+         * otherwise, so that we return EINVAL preferably over
+         * ERANGE. */
+
+        s += strspn(s, WHITESPACE);
+
+        errno = 0;
+        l = strtoul(s, &x, base);
+        if (errno > 0)
+                return -errno;
+        if (!x || x == s || *x != 0)
+                return -EINVAL;
+        if (s[0] == '-')
+                return -ERANGE;
+        if ((unsigned long) (unsigned) l != l)
+                return -ERANGE;
+
+        *ret_u = (unsigned) l;
+        return 0;
+}
+
+int safe_atoi(const char *s, int *ret_i) {
+        char *x = NULL;
+        long l;
+
+        assert(s);
+        assert(ret_i);
+
+        errno = 0;
+        l = strtol(s, &x, 0);
+        if (errno > 0)
+                return -errno;
+        if (!x || x == s || *x != 0)
+                return -EINVAL;
+        if ((long) (int) l != l)
+                return -ERANGE;
+
+        *ret_i = (int) l;
+        return 0;
+}
+
+int safe_atollu(const char *s, long long unsigned *ret_llu) {
+        char *x = NULL;
+        unsigned long long l;
+
+        assert(s);
+        assert(ret_llu);
+
+        s += strspn(s, WHITESPACE);
+
+        errno = 0;
+        l = strtoull(s, &x, 0);
+        if (errno > 0)
+                return -errno;
+        if (!x || x == s || *x != 0)
+                return -EINVAL;
+        if (*s == '-')
+                return -ERANGE;
+
+        *ret_llu = l;
+        return 0;
+}
+
+int safe_atolli(const char *s, long long int *ret_lli) {
+        char *x = NULL;
+        long long l;
+
+        assert(s);
+        assert(ret_lli);
+
+        errno = 0;
+        l = strtoll(s, &x, 0);
+        if (errno > 0)
+                return -errno;
+        if (!x || x == s || *x != 0)
+                return -EINVAL;
+
+        *ret_lli = l;
+        return 0;
+}
+
+int safe_atou8(const char *s, uint8_t *ret) {
+        char *x = NULL;
+        unsigned long l;
+
+        assert(s);
+        assert(ret);
+
+        s += strspn(s, WHITESPACE);
+
+        errno = 0;
+        l = strtoul(s, &x, 0);
+        if (errno > 0)
+                return -errno;
+        if (!x || x == s || *x != 0)
+                return -EINVAL;
+        if (s[0] == '-')
+                return -ERANGE;
+        if ((unsigned long) (uint8_t) l != l)
+                return -ERANGE;
+
+        *ret = (uint8_t) l;
+        return 0;
+}
+
+int safe_atou16_full(const char *s, unsigned base, uint16_t *ret) {
+        char *x = NULL;
+        unsigned long l;
+
+        assert(s);
+        assert(ret);
+        assert(base <= 16);
+
+        s += strspn(s, WHITESPACE);
+
+        errno = 0;
+        l = strtoul(s, &x, base);
+        if (errno > 0)
+                return -errno;
+        if (!x || x == s || *x != 0)
+                return -EINVAL;
+        if (s[0] == '-')
+                return -ERANGE;
+        if ((unsigned long) (uint16_t) l != l)
+                return -ERANGE;
+
+        *ret = (uint16_t) l;
+        return 0;
+}
+
+int safe_atoi16(const char *s, int16_t *ret) {
+        char *x = NULL;
+        long l;
+
+        assert(s);
+        assert(ret);
+
+        errno = 0;
+        l = strtol(s, &x, 0);
+        if (errno > 0)
+                return -errno;
+        if (!x || x == s || *x != 0)
+                return -EINVAL;
+        if ((long) (int16_t) l != l)
+                return -ERANGE;
+
+        *ret = (int16_t) l;
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+int safe_atod(const char *s, double *ret_d) {
+        _cleanup_(freelocalep) locale_t loc = (locale_t) 0;
+        char *x = NULL;
+        double d = 0;
+
+        assert(s);
+        assert(ret_d);
+
+        loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
+        if (loc == (locale_t) 0)
+                return -errno;
+
+        errno = 0;
+        d = strtod_l(s, &x, loc);
+        if (errno > 0)
+                return -errno;
+        if (!x || x == s || *x != 0)
+                return -EINVAL;
+
+        *ret_d = (double) d;
+        return 0;
+}
+
+int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) {
+        size_t i;
+        unsigned val = 0;
+        const char *s;
+
+        s = *p;
+
+        /* accept any number of digits, strtoull is limited to 19 */
+        for (i=0; i < digits; i++,s++) {
+                if (*s < '0' || *s > '9') {
+                        if (i == 0)
+                                return -EINVAL;
+
+                        /* too few digits, pad with 0 */
+                        for (; i < digits; i++)
+                                val *= 10;
+
+                        break;
+                }
+
+                val *= 10;
+                val += *s - '0';
+        }
+
+        /* maybe round up */
+        if (*s >= '5' && *s <= '9')
+                val++;
+
+        s += strspn(s, DIGITS);
+
+        *p = s;
+        *res = val;
+
+        return 0;
+}
+
+int parse_percent_unbounded(const char *p) {
+        const char *pc, *n;
+        int r, v;
+
+        pc = endswith(p, "%");
+        if (!pc)
+                return -EINVAL;
+
+        n = strndupa(p, pc - p);
+        r = safe_atoi(n, &v);
+        if (r < 0)
+                return r;
+        if (v < 0)
+                return -ERANGE;
+
+        return v;
+}
+
+int parse_percent(const char *p) {
+        int v;
+
+        v = parse_percent_unbounded(p);
+        if (v > 100)
+                return -ERANGE;
+
+        return v;
+}
+
+int parse_permille_unbounded(const char *p) {
+        const char *pc, *pm, *dot, *n;
+        int r, q, v;
+
+        pm = endswith(p, "‰");
+        if (pm) {
+                n = strndupa(p, pm - p);
+                r = safe_atoi(n, &v);
+                if (r < 0)
+                        return r;
+                if (v < 0)
+                        return -ERANGE;
+        } else {
+                pc = endswith(p, "%");
+                if (!pc)
+                        return -EINVAL;
+
+                dot = memchr(p, '.', pc - p);
+                if (dot) {
+                        if (dot + 2 != pc)
+                                return -EINVAL;
+                        if (dot[1] < '0' || dot[1] > '9')
+                                return -EINVAL;
+                        q = dot[1] - '0';
+                        n = strndupa(p, dot - p);
+                } else {
+                        q = 0;
+                        n = strndupa(p, pc - p);
+                }
+                r = safe_atoi(n, &v);
+                if (r < 0)
+                        return r;
+                if (v < 0)
+                        return -ERANGE;
+                if (v > (INT_MAX - q) / 10)
+                        return -ERANGE;
+
+                v = v * 10 + q;
+        }
+
+        return v;
+}
+
+int parse_permille(const char *p) {
+        int v;
+
+        v = parse_permille_unbounded(p);
+        if (v > 1000)
+                return -ERANGE;
+
+        return v;
+}
+
+int parse_nice(const char *p, int *ret) {
+        int n, r;
+
+        r = safe_atoi(p, &n);
+        if (r < 0)
+                return r;
+
+        if (!nice_is_valid(n))
+                return -ERANGE;
+
+        *ret = n;
+        return 0;
+}
+
+int parse_ip_port(const char *s, uint16_t *ret) {
+        uint16_t l;
+        int r;
+
+        r = safe_atou16(s, &l);
+        if (r < 0)
+                return r;
+
+        if (l == 0)
+                return -EINVAL;
+
+        *ret = (uint16_t) l;
+
+        return 0;
+}
+
+int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high) {
+        unsigned l, h;
+        int r;
+
+        r = parse_range(s, &l, &h);
+        if (r < 0)
+                return r;
+
+        if (l <= 0 || l > 65535 || h <= 0 || h > 65535)
+                return -EINVAL;
+
+        if (h < l)
+                return -EINVAL;
+
+        *low = l;
+        *high = h;
+
+        return 0;
+}
+
+int parse_dev(const char *s, dev_t *ret) {
+        const char *major;
+        unsigned x, y;
+        size_t n;
+        int r;
+
+        n = strspn(s, DIGITS);
+        if (n == 0)
+                return -EINVAL;
+        if (s[n] != ':')
+                return -EINVAL;
+
+        major = strndupa(s, n);
+        r = safe_atou(major, &x);
+        if (r < 0)
+                return r;
+
+        r = safe_atou(s + n + 1, &y);
+        if (r < 0)
+                return r;
+
+        if (!DEVICE_MAJOR_VALID(x) || !DEVICE_MINOR_VALID(y))
+                return -ERANGE;
+
+        *ret = makedev(x, y);
+        return 0;
+}
+
+int parse_oom_score_adjust(const char *s, int *ret) {
+        int r, v;
+
+        assert(s);
+        assert(ret);
+
+        r = safe_atoi(s, &v);
+        if (r < 0)
+                return r;
+
+        if (v < OOM_SCORE_ADJ_MIN || v > OOM_SCORE_ADJ_MAX)
+                return -ERANGE;
+
+        *ret = v;
+        return 0;
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/parse-util.h b/shared/systemd/src/basic/parse-util.h
new file mode 100644
index 00000000..e47641b4
--- /dev/null
+++ b/shared/systemd/src/basic/parse-util.h
@@ -0,0 +1,120 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "macro.h"
+
+#define MODE_INVALID ((mode_t) -1)
+
+int parse_boolean(const char *v) _pure_;
+int parse_dev(const char *s, dev_t *ret);
+int parse_pid(const char *s, pid_t* ret_pid);
+int parse_mode(const char *s, mode_t *ret);
+int parse_ifindex(const char *s, int *ret);
+int parse_mtu(int family, const char *s, uint32_t *ret);
+
+int parse_size(const char *t, uint64_t base, uint64_t *size);
+int parse_range(const char *t, unsigned *lower, unsigned *upper);
+int parse_errno(const char *t);
+int parse_syscall_and_errno(const char *in, char **name, int *error);
+
+#define FORMAT_BYTES_MAX 8
+char *format_bytes(char *buf, size_t l, uint64_t t);
+
+int safe_atou_full(const char *s, unsigned base, unsigned *ret_u);
+
+static inline int safe_atou(const char *s, unsigned *ret_u) {
+        return safe_atou_full(s, 0, ret_u);
+}
+
+int safe_atoi(const char *s, int *ret_i);
+int safe_atollu(const char *s, unsigned long long *ret_u);
+int safe_atolli(const char *s, long long int *ret_i);
+
+int safe_atou8(const char *s, uint8_t *ret);
+
+int safe_atou16_full(const char *s, unsigned base, uint16_t *ret);
+
+static inline int safe_atou16(const char *s, uint16_t *ret) {
+        return safe_atou16_full(s, 0, ret);
+}
+
+static inline int safe_atoux16(const char *s, uint16_t *ret) {
+        return safe_atou16_full(s, 16, ret);
+}
+
+int safe_atoi16(const char *s, int16_t *ret);
+
+static inline int safe_atou32(const char *s, uint32_t *ret_u) {
+        assert_cc(sizeof(uint32_t) == sizeof(unsigned));
+        return safe_atou(s, (unsigned*) ret_u);
+}
+
+static inline int safe_atoi32(const char *s, int32_t *ret_i) {
+        assert_cc(sizeof(int32_t) == sizeof(int));
+        return safe_atoi(s, (int*) ret_i);
+}
+
+static inline int safe_atou64(const char *s, uint64_t *ret_u) {
+        assert_cc(sizeof(uint64_t) == sizeof(unsigned long long));
+        return safe_atollu(s, (unsigned long long*) ret_u);
+}
+
+static inline int safe_atoi64(const char *s, int64_t *ret_i) {
+        assert_cc(sizeof(int64_t) == sizeof(long long int));
+        return safe_atolli(s, (long long int*) ret_i);
+}
+
+#if LONG_MAX == INT_MAX
+static inline int safe_atolu(const char *s, unsigned long *ret_u) {
+        assert_cc(sizeof(unsigned long) == sizeof(unsigned));
+        return safe_atou(s, (unsigned*) ret_u);
+}
+static inline int safe_atoli(const char *s, long int *ret_u) {
+        assert_cc(sizeof(long int) == sizeof(int));
+        return safe_atoi(s, (int*) ret_u);
+}
+#else
+static inline int safe_atolu(const char *s, unsigned long *ret_u) {
+        assert_cc(sizeof(unsigned long) == sizeof(unsigned long long));
+        return safe_atollu(s, (unsigned long long*) ret_u);
+}
+static inline int safe_atoli(const char *s, long int *ret_u) {
+        assert_cc(sizeof(long int) == sizeof(long long int));
+        return safe_atolli(s, (long long int*) ret_u);
+}
+#endif
+
+#if SIZE_MAX == UINT_MAX
+static inline int safe_atozu(const char *s, size_t *ret_u) {
+        assert_cc(sizeof(size_t) == sizeof(unsigned));
+        return safe_atou(s, (unsigned *) ret_u);
+}
+#else
+static inline int safe_atozu(const char *s, size_t *ret_u) {
+        assert_cc(sizeof(size_t) == sizeof(long unsigned));
+        return safe_atolu(s, ret_u);
+}
+#endif
+
+int safe_atod(const char *s, double *ret_d);
+
+int parse_fractional_part_u(const char **s, size_t digits, unsigned *res);
+
+int parse_percent_unbounded(const char *p);
+int parse_percent(const char *p);
+
+int parse_permille_unbounded(const char *p);
+int parse_permille(const char *p);
+
+int parse_nice(const char *p, int *ret);
+
+int parse_ip_port(const char *s, uint16_t *ret);
+int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high);
+
+int parse_oom_score_adjust(const char *s, int *ret);
diff --git a/shared/systemd/src/basic/path-util.c b/shared/systemd/src/basic/path-util.c
new file mode 100644
index 00000000..6c5e725d
--- /dev/null
+++ b/shared/systemd/src/basic/path-util.c
@@ -0,0 +1,1160 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* When we include libgen.h because we need dirname() we immediately
+ * undefine basename() since libgen.h defines it as a macro to the
+ * POSIX version which is really broken. We prefer GNU basename(). */
+#include <libgen.h>
+#undef basename
+
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "fs-util.h"
+#include "glob-util.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "stat-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "time-util.h"
+#include "utf8.h"
+
+bool path_is_absolute(const char *p) {
+        return p[0] == '/';
+}
+
+#if 0 /* NM_IGNORED */
+bool is_path(const char *p) {
+        return !!strchr(p, '/');
+}
+
+int path_split_and_make_absolute(const char *p, char ***ret) {
+        char **l;
+        int r;
+
+        assert(p);
+        assert(ret);
+
+        l = strv_split(p, ":");
+        if (!l)
+                return -ENOMEM;
+
+        r = path_strv_make_absolute_cwd(l);
+        if (r < 0) {
+                strv_free(l);
+                return r;
+        }
+
+        *ret = l;
+        return r;
+}
+
+char *path_make_absolute(const char *p, const char *prefix) {
+        assert(p);
+
+        /* Makes every item in the list an absolute path by prepending
+         * the prefix, if specified and necessary */
+
+        if (path_is_absolute(p) || isempty(prefix))
+                return strdup(p);
+
+        if (endswith(prefix, "/"))
+                return strjoin(prefix, p);
+        else
+                return strjoin(prefix, "/", p);
+}
+
+int safe_getcwd(char **ret) {
+        char *cwd;
+
+        cwd = get_current_dir_name();
+        if (!cwd)
+                return negative_errno();
+
+        /* Let's make sure the directory is really absolute, to protect us from the logic behind
+         * CVE-2018-1000001 */
+        if (cwd[0] != '/') {
+                free(cwd);
+                return -ENOMEDIUM;
+        }
+
+        *ret = cwd;
+        return 0;
+}
+
+int path_make_absolute_cwd(const char *p, char **ret) {
+        char *c;
+        int r;
+
+        assert(p);
+        assert(ret);
+
+        /* Similar to path_make_absolute(), but prefixes with the
+         * current working directory. */
+
+        if (path_is_absolute(p))
+                c = strdup(p);
+        else {
+                _cleanup_free_ char *cwd = NULL;
+
+                r = safe_getcwd(&cwd);
+                if (r < 0)
+                        return r;
+
+                c = path_join(cwd, p);
+        }
+        if (!c)
+                return -ENOMEM;
+
+        *ret = c;
+        return 0;
+}
+
+int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
+        char *f, *t, *r, *p;
+        unsigned n_parents = 0;
+
+        assert(from_dir);
+        assert(to_path);
+        assert(_r);
+
+        /* Strips the common part, and adds ".." elements as necessary. */
+
+        if (!path_is_absolute(from_dir) || !path_is_absolute(to_path))
+                return -EINVAL;
+
+        f = strdupa(from_dir);
+        t = strdupa(to_path);
+
+        path_simplify(f, true);
+        path_simplify(t, true);
+
+        /* Skip the common part. */
+        for (;;) {
+                size_t a, b;
+
+                f += *f == '/';
+                t += *t == '/';
+
+                if (!*f) {
+                        if (!*t)
+                                /* from_dir equals to_path. */
+                                r = strdup(".");
+                        else
+                                /* from_dir is a parent directory of to_path. */
+                                r = strdup(t);
+                        if (!r)
+                                return -ENOMEM;
+
+                        *_r = r;
+                        return 0;
+                }
+
+                if (!*t)
+                        break;
+
+                a = strcspn(f, "/");
+                b = strcspn(t, "/");
+
+                if (a != b || memcmp(f, t, a) != 0)
+                        break;
+
+                f += a;
+                t += b;
+        }
+
+        /* If we're here, then "from_dir" has one or more elements that need to
+         * be replaced with "..". */
+
+        /* Count the number of necessary ".." elements. */
+        for (; *f;) {
+                size_t w;
+
+                w = strcspn(f, "/");
+
+                /* If this includes ".." we can't do a simple series of "..", refuse */
+                if (w == 2 && f[0] == '.' && f[1] == '.')
+                        return -EINVAL;
+
+                /* Count number of elements */
+                n_parents++;
+
+                f += w;
+                f += *f == '/';
+        }
+
+        r = new(char, n_parents * 3 + strlen(t) + 1);
+        if (!r)
+                return -ENOMEM;
+
+        for (p = r; n_parents > 0; n_parents--)
+                p = mempcpy(p, "../", 3);
+
+        if (*t)
+                strcpy(p, t);
+        else
+                /* Remove trailing slash */
+                *(--p) = 0;
+
+        *_r = r;
+        return 0;
+}
+
+int path_strv_make_absolute_cwd(char **l) {
+        char **s;
+        int r;
+
+        /* Goes through every item in the string list and makes it
+         * absolute. This works in place and won't rollback any
+         * changes on failure. */
+
+        STRV_FOREACH(s, l) {
+                char *t;
+
+                r = path_make_absolute_cwd(*s, &t);
+                if (r < 0)
+                        return r;
+
+                path_simplify(t, false);
+                free_and_replace(*s, t);
+        }
+
+        return 0;
+}
+
+char **path_strv_resolve(char **l, const char *root) {
+        char **s;
+        unsigned k = 0;
+        bool enomem = false;
+        int r;
+
+        if (strv_isempty(l))
+                return l;
+
+        /* Goes through every item in the string list and canonicalize
+         * the path. This works in place and won't rollback any
+         * changes on failure. */
+
+        STRV_FOREACH(s, l) {
+                _cleanup_free_ char *orig = NULL;
+                char *t, *u;
+
+                if (!path_is_absolute(*s)) {
+                        free(*s);
+                        continue;
+                }
+
+                if (root) {
+                        orig = *s;
+                        t = prefix_root(root, orig);
+                        if (!t) {
+                                enomem = true;
+                                continue;
+                        }
+                } else
+                        t = *s;
+
+                r = chase_symlinks(t, root, 0, &u);
+                if (r == -ENOENT) {
+                        if (root) {
+                                u = TAKE_PTR(orig);
+                                free(t);
+                        } else
+                                u = t;
+                } else if (r < 0) {
+                        free(t);
+
+                        if (r == -ENOMEM)
+                                enomem = true;
+
+                        continue;
+                } else if (root) {
+                        char *x;
+
+                        free(t);
+                        x = path_startswith(u, root);
+                        if (x) {
+                                /* restore the slash if it was lost */
+                                if (!startswith(x, "/"))
+                                        *(--x) = '/';
+
+                                t = strdup(x);
+                                free(u);
+                                if (!t) {
+                                        enomem = true;
+                                        continue;
+                                }
+                                u = t;
+                        } else {
+                                /* canonicalized path goes outside of
+                                 * prefix, keep the original path instead */
+                                free_and_replace(u, orig);
+                        }
+                } else
+                        free(t);
+
+                l[k++] = u;
+        }
+
+        l[k] = NULL;
+
+        if (enomem)
+                return NULL;
+
+        return l;
+}
+
+char **path_strv_resolve_uniq(char **l, const char *root) {
+
+        if (strv_isempty(l))
+                return l;
+
+        if (!path_strv_resolve(l, root))
+                return NULL;
+
+        return strv_uniq(l);
+}
+#endif /* NM_IGNORED */
+
+char *path_simplify(char *path, bool kill_dots) {
+        char *f, *t;
+        bool slash = false, ignore_slash = false, absolute;
+
+        assert(path);
+
+        /* Removes redundant inner and trailing slashes. Also removes unnecessary dots
+         * if kill_dots is true. Modifies the passed string in-place.
+         *
+         * ///foo//./bar/.   becomes /foo/./bar/.      (if kill_dots is false)
+         * ///foo//./bar/.   becomes /foo/bar          (if kill_dots is true)
+         * .//./foo//./bar/. becomes ././foo/./bar/.   (if kill_dots is false)
+         * .//./foo//./bar/. becomes foo/bar           (if kill_dots is true)
+         */
+
+        if (isempty(path))
+                return path;
+
+        absolute = path_is_absolute(path);
+
+        f = path;
+        if (kill_dots && *f == '.' && IN_SET(f[1], 0, '/')) {
+                ignore_slash = true;
+                f++;
+        }
+
+        for (t = path; *f; f++) {
+
+                if (*f == '/') {
+                        slash = true;
+                        continue;
+                }
+
+                if (slash) {
+                        if (kill_dots && *f == '.' && IN_SET(f[1], 0, '/'))
+                                continue;
+
+                        slash = false;
+                        if (ignore_slash)
+                                ignore_slash = false;
+                        else
+                                *(t++) = '/';
+                }
+
+                *(t++) = *f;
+        }
+
+        /* Special rule, if we stripped everything, we either need a "/" (for the root directory)
+         * or "." for the current directory */
+        if (t == path) {
+                if (absolute)
+                        *(t++) = '/';
+                else
+                        *(t++) = '.';
+        }
+
+        *t = 0;
+        return path;
+}
+
+char* path_startswith(const char *path, const char *prefix) {
+        assert(path);
+        assert(prefix);
+
+        /* Returns a pointer to the start of the first component after the parts matched by
+         * the prefix, iff
+         * - both paths are absolute or both paths are relative,
+         * and
+         * - each component in prefix in turn matches a component in path at the same position.
+         * An empty string will be returned when the prefix and path are equivalent.
+         *
+         * Returns NULL otherwise.
+         */
+
+        if ((path[0] == '/') != (prefix[0] == '/'))
+                return NULL;
+
+        for (;;) {
+                size_t a, b;
+
+                path += strspn(path, "/");
+                prefix += strspn(prefix, "/");
+
+                if (*prefix == 0)
+                        return (char*) path;
+
+                if (*path == 0)
+                        return NULL;
+
+                a = strcspn(path, "/");
+                b = strcspn(prefix, "/");
+
+                if (a != b)
+                        return NULL;
+
+                if (memcmp(path, prefix, a) != 0)
+                        return NULL;
+
+                path += a;
+                prefix += b;
+        }
+}
+
+int path_compare(const char *a, const char *b) {
+        int d;
+
+        assert(a);
+        assert(b);
+
+        /* A relative path and an absolute path must not compare as equal.
+         * Which one is sorted before the other does not really matter.
+         * Here a relative path is ordered before an absolute path. */
+        d = (a[0] == '/') - (b[0] == '/');
+        if (d != 0)
+                return d;
+
+        for (;;) {
+                size_t j, k;
+
+                a += strspn(a, "/");
+                b += strspn(b, "/");
+
+                if (*a == 0 && *b == 0)
+                        return 0;
+
+                /* Order prefixes first: "/foo" before "/foo/bar" */
+                if (*a == 0)
+                        return -1;
+                if (*b == 0)
+                        return 1;
+
+                j = strcspn(a, "/");
+                k = strcspn(b, "/");
+
+                /* Alphabetical sort: "/foo/aaa" before "/foo/b" */
+                d = memcmp(a, b, MIN(j, k));
+                if (d != 0)
+                        return (d > 0) - (d < 0); /* sign of d */
+
+                /* Sort "/foo/a" before "/foo/aaa" */
+                d = (j > k) - (j < k);  /* sign of (j - k) */
+                if (d != 0)
+                        return d;
+
+                a += j;
+                b += k;
+        }
+}
+
+bool path_equal(const char *a, const char *b) {
+        return path_compare(a, b) == 0;
+}
+
+#if 0 /* NM_IGNORED */
+bool path_equal_or_files_same(const char *a, const char *b, int flags) {
+        return path_equal(a, b) || files_same(a, b, flags) > 0;
+}
+
+char* path_join_internal(const char *first, ...) {
+        char *joined, *q;
+        const char *p;
+        va_list ap;
+        bool slash;
+        size_t sz;
+
+        /* Joins all listed strings until the sentinel and places a "/" between them unless the strings end/begin
+         * already with one so that it is unnecessary. Note that slashes which are already duplicate won't be
+         * removed. The string returned is hence always equal to or longer than the sum of the lengths of each
+         * individual string.
+         *
+         * Note: any listed empty string is simply skipped. This can be useful for concatenating strings of which some
+         * are optional.
+         *
+         * Examples:
+         *
+         * path_join("foo", "bar") → "foo/bar"
+         * path_join("foo/", "bar") → "foo/bar"
+         * path_join("", "foo", "", "bar", "") → "foo/bar" */
+
+        sz = strlen_ptr(first);
+        va_start(ap, first);
+        while ((p = va_arg(ap, char*)) != (const char*) -1)
+                if (!isempty(p))
+                        sz += 1 + strlen(p);
+        va_end(ap);
+
+        joined = new(char, sz + 1);
+        if (!joined)
+                return NULL;
+
+        if (!isempty(first)) {
+                q = stpcpy(joined, first);
+                slash = endswith(first, "/");
+        } else {
+                /* Skip empty items */
+                joined[0] = 0;
+                q = joined;
+                slash = true; /* no need to generate a slash anymore */
+        }
+
+        va_start(ap, first);
+        while ((p = va_arg(ap, char*)) != (const char*) -1) {
+                if (isempty(p))
+                        continue;
+
+                if (!slash && p[0] != '/')
+                        *(q++) = '/';
+
+                q = stpcpy(q, p);
+                slash = endswith(p, "/");
+        }
+        va_end(ap);
+
+        return joined;
+}
+
+int find_binary(const char *name, char **ret) {
+        int last_error, r;
+        const char *p;
+
+        assert(name);
+
+        if (is_path(name)) {
+                if (access(name, X_OK) < 0)
+                        return -errno;
+
+                if (ret) {
+                        r = path_make_absolute_cwd(name, ret);
+                        if (r < 0)
+                                return r;
+                }
+
+                return 0;
+        }
+
+        /**
+         * Plain getenv, not secure_getenv, because we want
+         * to actually allow the user to pick the binary.
+         */
+        p = getenv("PATH");
+        if (!p)
+                p = DEFAULT_PATH;
+
+        last_error = -ENOENT;
+
+        for (;;) {
+                _cleanup_free_ char *j = NULL, *element = NULL;
+
+                r = extract_first_word(&p, &element, ":", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                if (!path_is_absolute(element))
+                        continue;
+
+                j = strjoin(element, "/", name);
+                if (!j)
+                        return -ENOMEM;
+
+                if (access(j, X_OK) >= 0) {
+                        /* Found it! */
+
+                        if (ret) {
+                                *ret = path_simplify(j, false);
+                                j = NULL;
+                        }
+
+                        return 0;
+                }
+
+                last_error = -errno;
+        }
+
+        return last_error;
+}
+
+bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
+        bool changed = false;
+        const char* const* i;
+
+        assert(timestamp);
+
+        if (!paths)
+                return false;
+
+        STRV_FOREACH(i, paths) {
+                struct stat stats;
+                usec_t u;
+
+                if (stat(*i, &stats) < 0)
+                        continue;
+
+                u = timespec_load(&stats.st_mtim);
+
+                /* first check */
+                if (*timestamp >= u)
+                        continue;
+
+                log_debug("timestamp of '%s' changed", *i);
+
+                /* update timestamp */
+                if (update) {
+                        *timestamp = u;
+                        changed = true;
+                } else
+                        return true;
+        }
+
+        return changed;
+}
+
+static int binary_is_good(const char *binary) {
+        _cleanup_free_ char *p = NULL, *d = NULL;
+        int r;
+
+        r = find_binary(binary, &p);
+        if (r == -ENOENT)
+                return 0;
+        if (r < 0)
+                return r;
+
+        /* An fsck that is linked to /bin/true is a non-existent
+         * fsck */
+
+        r = readlink_malloc(p, &d);
+        if (r == -EINVAL) /* not a symlink */
+                return 1;
+        if (r < 0)
+                return r;
+
+        return !PATH_IN_SET(d, "true"
+                               "/bin/true",
+                               "/usr/bin/true",
+                               "/dev/null");
+}
+
+int fsck_exists(const char *fstype) {
+        const char *checker;
+
+        assert(fstype);
+
+        if (streq(fstype, "auto"))
+                return -EINVAL;
+
+        checker = strjoina("fsck.", fstype);
+        return binary_is_good(checker);
+}
+
+int mkfs_exists(const char *fstype) {
+        const char *mkfs;
+
+        assert(fstype);
+
+        if (streq(fstype, "auto"))
+                return -EINVAL;
+
+        mkfs = strjoina("mkfs.", fstype);
+        return binary_is_good(mkfs);
+}
+
+char *prefix_root(const char *root, const char *path) {
+        char *n, *p;
+        size_t l;
+
+        /* If root is passed, prefixes path with it. Otherwise returns
+         * it as is. */
+
+        assert(path);
+
+        /* First, drop duplicate prefixing slashes from the path */
+        while (path[0] == '/' && path[1] == '/')
+                path++;
+
+        if (empty_or_root(root))
+                return strdup(path);
+
+        l = strlen(root) + 1 + strlen(path) + 1;
+
+        n = new(char, l);
+        if (!n)
+                return NULL;
+
+        p = stpcpy(n, root);
+
+        while (p > n && p[-1] == '/')
+                p--;
+
+        if (path[0] != '/')
+                *(p++) = '/';
+
+        strcpy(p, path);
+        return n;
+}
+
+int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg) {
+        char *p;
+        int r;
+
+        /*
+         * This function is intended to be used in command line
+         * parsers, to handle paths that are passed in. It makes the
+         * path absolute, and reduces it to NULL if omitted or
+         * root (the latter optionally).
+         *
+         * NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON
+         * SUCCESS! Hence, do not pass in uninitialized pointers.
+         */
+
+        if (isempty(path)) {
+                *arg = mfree(*arg);
+                return 0;
+        }
+
+        r = path_make_absolute_cwd(path, &p);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse path \"%s\" and make it absolute: %m", path);
+
+        path_simplify(p, false);
+        if (suppress_root && empty_or_root(p))
+                p = mfree(p);
+
+        free_and_replace(*arg, p);
+
+        return 0;
+}
+
+char* dirname_malloc(const char *path) {
+        char *d, *dir, *dir2;
+
+        assert(path);
+
+        d = strdup(path);
+        if (!d)
+                return NULL;
+
+        dir = dirname(d);
+        assert(dir);
+
+        if (dir == d)
+                return d;
+
+        dir2 = strdup(dir);
+        free(d);
+
+        return dir2;
+}
+
+const char *last_path_component(const char *path) {
+
+        /* Finds the last component of the path, preserving the optional trailing slash that signifies a directory.
+         *
+         *    a/b/c → c
+         *    a/b/c/ → c/
+         *    x → x
+         *    x/ → x/
+         *    /y → y
+         *    /y/ → y/
+         *    / → /
+         *    // → /
+         *    /foo/a → a
+         *    /foo/a/ → a/
+         *
+         *    Also, the empty string is mapped to itself.
+         *
+         * This is different than basename(), which returns "" when a trailing slash is present.
+         */
+
+        unsigned l, k;
+
+        if (!path)
+                return NULL;
+
+        l = k = strlen(path);
+        if (l == 0) /* special case — an empty string */
+                return path;
+
+        while (k > 0 && path[k-1] == '/')
+                k--;
+
+        if (k == 0) /* the root directory */
+                return path + l - 1;
+
+        while (k > 0 && path[k-1] != '/')
+                k--;
+
+        return path + k;
+}
+
+int path_extract_filename(const char *p, char **ret) {
+        _cleanup_free_ char *a = NULL;
+        const char *c, *e = NULL, *q;
+
+        /* Extracts the filename part (i.e. right-most component) from a path, i.e. string that passes
+         * filename_is_valid(). A wrapper around last_path_component(), but eats up trailing slashes. */
+
+        if (!p)
+                return -EINVAL;
+
+        c = last_path_component(p);
+
+        for (q = c; *q != 0; q++)
+                if (*q != '/')
+                        e = q + 1;
+
+        if (!e) /* no valid character? */
+                return -EINVAL;
+
+        a = strndup(c, e - c);
+        if (!a)
+                return -ENOMEM;
+
+        if (!filename_is_valid(a))
+                return -EINVAL;
+
+        *ret = TAKE_PTR(a);
+
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+bool filename_is_valid(const char *p) {
+        const char *e;
+
+        if (isempty(p))
+                return false;
+
+        if (dot_or_dot_dot(p))
+                return false;
+
+        e = strchrnul(p, '/');
+        if (*e != 0)
+                return false;
+
+        if (e - p > FILENAME_MAX) /* FILENAME_MAX is counted *without* the trailing NUL byte */
+                return false;
+
+        return true;
+}
+
+bool path_is_valid(const char *p) {
+
+        if (isempty(p))
+                return false;
+
+        if (strlen(p) >= PATH_MAX) /* PATH_MAX is counted *with* the trailing NUL byte */
+                return false;
+
+        return true;
+}
+
+bool path_is_normalized(const char *p) {
+
+        if (!path_is_valid(p))
+                return false;
+
+        if (dot_or_dot_dot(p))
+                return false;
+
+        if (startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../"))
+                return false;
+
+        if (startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./"))
+                return false;
+
+        if (strstr(p, "//"))
+                return false;
+
+        return true;
+}
+
+#if 0 /* NM_IGNORED */
+char *file_in_same_dir(const char *path, const char *filename) {
+        char *e, *ret;
+        size_t k;
+
+        assert(path);
+        assert(filename);
+
+        /* This removes the last component of path and appends
+         * filename, unless the latter is absolute anyway or the
+         * former isn't */
+
+        if (path_is_absolute(filename))
+                return strdup(filename);
+
+        e = strrchr(path, '/');
+        if (!e)
+                return strdup(filename);
+
+        k = strlen(filename);
+        ret = new(char, (e + 1 - path) + k + 1);
+        if (!ret)
+                return NULL;
+
+        memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1);
+        return ret;
+}
+
+bool hidden_or_backup_file(const char *filename) {
+        const char *p;
+
+        assert(filename);
+
+        if (filename[0] == '.' ||
+            streq(filename, "lost+found") ||
+            streq(filename, "aquota.user") ||
+            streq(filename, "aquota.group") ||
+            endswith(filename, "~"))
+                return true;
+
+        p = strrchr(filename, '.');
+        if (!p)
+                return false;
+
+        /* Please, let's not add more entries to the list below. If external projects think it's a good idea to come up
+         * with always new suffixes and that everybody else should just adjust to that, then it really should be on
+         * them. Hence, in future, let's not add any more entries. Instead, let's ask those packages to instead adopt
+         * one of the generic suffixes/prefixes for hidden files or backups, possibly augmented with an additional
+         * string. Specifically: there's now:
+         *
+         *    The generic suffixes "~" and ".bak" for backup files
+         *    The generic prefix "." for hidden files
+         *
+         * Thus, if a new package manager "foopkg" wants its own set of ".foopkg-new", ".foopkg-old", ".foopkg-dist"
+         * or so registered, let's refuse that and ask them to use ".foopkg.new", ".foopkg.old" or ".foopkg~" instead.
+         */
+
+        return STR_IN_SET(p + 1,
+                          "rpmnew",
+                          "rpmsave",
+                          "rpmorig",
+                          "dpkg-old",
+                          "dpkg-new",
+                          "dpkg-tmp",
+                          "dpkg-dist",
+                          "dpkg-bak",
+                          "dpkg-backup",
+                          "dpkg-remove",
+                          "ucf-new",
+                          "ucf-old",
+                          "ucf-dist",
+                          "swp",
+                          "bak",
+                          "old",
+                          "new");
+}
+
+bool is_device_path(const char *path) {
+
+        /* Returns true on paths that likely refer to a device, either by path in sysfs or to something in /dev */
+
+        return PATH_STARTSWITH_SET(path, "/dev/", "/sys/");
+}
+
+bool valid_device_node_path(const char *path) {
+
+        /* Some superficial checks whether the specified path is a valid device node path, all without looking at the
+         * actual device node. */
+
+        if (!PATH_STARTSWITH_SET(path, "/dev/", "/run/systemd/inaccessible/"))
+                return false;
+
+        if (endswith(path, "/")) /* can't be a device node if it ends in a slash */
+                return false;
+
+        return path_is_normalized(path);
+}
+
+bool valid_device_allow_pattern(const char *path) {
+        assert(path);
+
+        /* Like valid_device_node_path(), but also allows full-subsystem expressions, like DeviceAllow= and DeviceDeny=
+         * accept it */
+
+        if (STARTSWITH_SET(path, "block-", "char-"))
+                return true;
+
+        return valid_device_node_path(path);
+}
+
+int systemd_installation_has_version(const char *root, unsigned minimal_version) {
+        const char *pattern;
+        int r;
+
+        /* Try to guess if systemd installation is later than the specified version. This
+         * is hacky and likely to yield false negatives, particularly if the installation
+         * is non-standard. False positives should be relatively rare.
+         */
+
+        NULSTR_FOREACH(pattern,
+                       /* /lib works for systems without usr-merge, and for systems with a sane
+                        * usr-merge, where /lib is a symlink to /usr/lib. /usr/lib is necessary
+                        * for Gentoo which does a merge without making /lib a symlink.
+                        */
+                       "lib/systemd/libsystemd-shared-*.so\0"
+                       "lib64/systemd/libsystemd-shared-*.so\0"
+                       "usr/lib/systemd/libsystemd-shared-*.so\0"
+                       "usr/lib64/systemd/libsystemd-shared-*.so\0") {
+
+                _cleanup_strv_free_ char **names = NULL;
+                _cleanup_free_ char *path = NULL;
+                char *c, **name;
+
+                path = prefix_root(root, pattern);
+                if (!path)
+                        return -ENOMEM;
+
+                r = glob_extend(&names, path);
+                if (r == -ENOENT)
+                        continue;
+                if (r < 0)
+                        return r;
+
+                assert_se(c = endswith(path, "*.so"));
+                *c = '\0'; /* truncate the glob part */
+
+                STRV_FOREACH(name, names) {
+                        /* This is most likely to run only once, hence let's not optimize anything. */
+                        char *t, *t2;
+                        unsigned version;
+
+                        t = startswith(*name, path);
+                        if (!t)
+                                continue;
+
+                        t2 = endswith(t, ".so");
+                        if (!t2)
+                                continue;
+
+                        t2[0] = '\0'; /* truncate the suffix */
+
+                        r = safe_atou(t, &version);
+                        if (r < 0) {
+                                log_debug_errno(r, "Found libsystemd shared at \"%s.so\", but failed to parse version: %m", *name);
+                                continue;
+                        }
+
+                        log_debug("Found libsystemd shared at \"%s.so\", version %u (%s).",
+                                  *name, version,
+                                  version >= minimal_version ? "OK" : "too old");
+                        if (version >= minimal_version)
+                                return true;
+                }
+        }
+
+        return false;
+}
+#endif /* NM_IGNORED */
+
+bool dot_or_dot_dot(const char *path) {
+        if (!path)
+                return false;
+        if (path[0] != '.')
+                return false;
+        if (path[1] == 0)
+                return true;
+        if (path[1] != '.')
+                return false;
+
+        return path[2] == 0;
+}
+
+#if 0 /* NM_IGNORED */
+bool empty_or_root(const char *root) {
+
+        /* For operations relative to some root directory, returns true if the specified root directory is redundant,
+         * i.e. either / or NULL or the empty string or any equivalent. */
+
+        if (!root)
+                return true;
+
+        return root[strspn(root, "/")] == 0;
+}
+
+int path_simplify_and_warn(
+                char *path,
+                unsigned flag,
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *lvalue) {
+
+        bool absolute, fatal = flag & PATH_CHECK_FATAL;
+
+        assert(!FLAGS_SET(flag, PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE));
+
+        if (!utf8_is_valid(path)) {
+                log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, path);
+                return -EINVAL;
+        }
+
+        if (flag & (PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)) {
+                absolute = path_is_absolute(path);
+
+                if (!absolute && (flag & PATH_CHECK_ABSOLUTE)) {
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "%s= path is not absolute%s: %s",
+                                   lvalue, fatal ? "" : ", ignoring", path);
+                        return -EINVAL;
+                }
+
+                if (absolute && (flag & PATH_CHECK_RELATIVE)) {
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "%s= path is absolute%s: %s",
+                                   lvalue, fatal ? "" : ", ignoring", path);
+                        return -EINVAL;
+                }
+        }
+
+        path_simplify(path, true);
+
+        if (!path_is_normalized(path)) {
+                log_syntax(unit, LOG_ERR, filename, line, 0,
+                           "%s= path is not normalized%s: %s",
+                           lvalue, fatal ? "" : ", ignoring", path);
+                return -EINVAL;
+        }
+
+        if (!path_is_valid(path)) {
+                log_syntax(unit, LOG_ERR, filename, line, 0,
+                           "%s= path has invalid length (%zu bytes)%s.",
+                           lvalue, strlen(path), fatal ? "" : ", ignoring");
+                return -EINVAL;
+        }
+
+        return 0;
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/path-util.h b/shared/systemd/src/basic/path-util.h
new file mode 100644
index 00000000..5204adaa
--- /dev/null
+++ b/shared/systemd/src/basic/path-util.h
@@ -0,0 +1,192 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <alloca.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "macro.h"
+#include "string-util.h"
+#include "strv.h"
+#include "time-util.h"
+
+#if 0 /* NM_IGNORED */
+#define PATH_SPLIT_SBIN_BIN(x) x "sbin:" x "bin"
+#define PATH_SPLIT_SBIN_BIN_NULSTR(x) x "sbin\0" x "bin\0"
+
+#define PATH_NORMAL_SBIN_BIN(x) x "bin"
+#define PATH_NORMAL_SBIN_BIN_NULSTR(x) x "bin\0"
+
+#if HAVE_SPLIT_BIN
+#  define PATH_SBIN_BIN(x) PATH_SPLIT_SBIN_BIN(x)
+#  define PATH_SBIN_BIN_NULSTR(x) PATH_SPLIT_SBIN_BIN_NULSTR(x)
+#else
+#  define PATH_SBIN_BIN(x) PATH_NORMAL_SBIN_BIN(x)
+#  define PATH_SBIN_BIN_NULSTR(x) PATH_NORMAL_SBIN_BIN_NULSTR(x)
+#endif
+
+#define DEFAULT_PATH_NORMAL PATH_SBIN_BIN("/usr/local/") ":" PATH_SBIN_BIN("/usr/")
+#define DEFAULT_PATH_NORMAL_NULSTR PATH_SBIN_BIN_NULSTR("/usr/local/") PATH_SBIN_BIN_NULSTR("/usr/")
+#define DEFAULT_PATH_SPLIT_USR DEFAULT_PATH_NORMAL ":" PATH_SBIN_BIN("/")
+#define DEFAULT_PATH_SPLIT_USR_NULSTR DEFAULT_PATH_NORMAL_NULSTR PATH_SBIN_BIN_NULSTR("/")
+#define DEFAULT_PATH_COMPAT PATH_SPLIT_SBIN_BIN("/usr/local/") ":" PATH_SPLIT_SBIN_BIN("/usr/") ":" PATH_SPLIT_SBIN_BIN("/")
+
+#if HAVE_SPLIT_USR
+#  define DEFAULT_PATH DEFAULT_PATH_SPLIT_USR
+#  define DEFAULT_PATH_NULSTR DEFAULT_PATH_SPLIT_USR_NULSTR
+#else
+#  define DEFAULT_PATH DEFAULT_PATH_NORMAL
+#  define DEFAULT_PATH_NULSTR DEFAULT_PATH_NORMAL_NULSTR
+#endif
+#endif /* NM_IGNORED */
+
+bool is_path(const char *p) _pure_;
+int path_split_and_make_absolute(const char *p, char ***ret);
+bool path_is_absolute(const char *p) _pure_;
+char* path_make_absolute(const char *p, const char *prefix);
+int safe_getcwd(char **ret);
+int path_make_absolute_cwd(const char *p, char **ret);
+int path_make_relative(const char *from_dir, const char *to_path, char **_r);
+char* path_startswith(const char *path, const char *prefix) _pure_;
+int path_compare(const char *a, const char *b) _pure_;
+bool path_equal(const char *a, const char *b) _pure_;
+bool path_equal_or_files_same(const char *a, const char *b, int flags);
+char* path_join_internal(const char *first, ...);
+#define path_join(x, ...) path_join_internal(x, __VA_ARGS__, (const char*) -1)
+
+char* path_simplify(char *path, bool kill_dots);
+
+static inline bool path_equal_ptr(const char *a, const char *b) {
+        return !!a == !!b && (!a || path_equal(a, b));
+}
+
+/* Note: the search terminates on the first NULL item. */
+#define PATH_IN_SET(p, ...)                                     \
+        ({                                                      \
+                char **_s;                                      \
+                bool _found = false;                            \
+                STRV_FOREACH(_s, STRV_MAKE(__VA_ARGS__))        \
+                        if (path_equal(p, *_s)) {               \
+                               _found = true;                   \
+                               break;                           \
+                        }                                       \
+                _found;                                         \
+        })
+
+#define PATH_STARTSWITH_SET(p, ...)                             \
+        ({                                                      \
+                const char *_p = (p);                           \
+                char *_found = NULL, **_i;                      \
+                STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) {      \
+                        _found = path_startswith(_p, *_i);      \
+                        if (_found)                             \
+                                break;                          \
+                }                                               \
+                _found;                                         \
+        })
+
+int path_strv_make_absolute_cwd(char **l);
+char** path_strv_resolve(char **l, const char *root);
+char** path_strv_resolve_uniq(char **l, const char *root);
+
+int find_binary(const char *name, char **filename);
+
+bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update);
+
+int fsck_exists(const char *fstype);
+int mkfs_exists(const char *fstype);
+
+/* Iterates through the path prefixes of the specified path, going up
+ * the tree, to root. Also returns "" (and not "/"!) for the root
+ * directory. Excludes the specified directory itself */
+#define PATH_FOREACH_PREFIX(prefix, path)                               \
+        for (char *_slash = ({                                          \
+                                path_simplify(strcpy(prefix, path), false); \
+                                streq(prefix, "/") ? NULL : strrchr(prefix, '/'); \
+                        });                                             \
+             _slash && ((*_slash = 0), true);                           \
+             _slash = strrchr((prefix), '/'))
+
+/* Same as PATH_FOREACH_PREFIX but also includes the specified path itself */
+#define PATH_FOREACH_PREFIX_MORE(prefix, path)                          \
+        for (char *_slash = ({                                          \
+                                path_simplify(strcpy(prefix, path), false); \
+                                if (streq(prefix, "/"))                 \
+                                        prefix[0] = 0;                  \
+                                strrchr(prefix, 0);                     \
+                        });                                             \
+             _slash && ((*_slash = 0), true);                           \
+             _slash = strrchr((prefix), '/'))
+
+char *prefix_root(const char *root, const char *path);
+
+/* Similar to prefix_root(), but returns an alloca() buffer, or
+ * possibly a const pointer into the path parameter */
+#define prefix_roota(root, path)                                        \
+        ({                                                              \
+                const char* _path = (path), *_root = (root), *_ret;     \
+                char *_p, *_n;                                          \
+                size_t _l;                                              \
+                while (_path[0] == '/' && _path[1] == '/')              \
+                        _path ++;                                       \
+                if (empty_or_root(_root))                               \
+                        _ret = _path;                                   \
+                else {                                                  \
+                        _l = strlen(_root) + 1 + strlen(_path) + 1;     \
+                        _n = newa(char, _l);                            \
+                        _p = stpcpy(_n, _root);                         \
+                        while (_p > _n && _p[-1] == '/')                \
+                                _p--;                                   \
+                        if (_path[0] != '/')                            \
+                                *(_p++) = '/';                          \
+                        strcpy(_p, _path);                              \
+                        _ret = _n;                                      \
+                }                                                       \
+                _ret;                                                   \
+        })
+
+int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg);
+
+char* dirname_malloc(const char *path);
+const char *last_path_component(const char *path);
+int path_extract_filename(const char *p, char **ret);
+
+bool filename_is_valid(const char *p) _pure_;
+bool path_is_valid(const char *p) _pure_;
+bool path_is_normalized(const char *p) _pure_;
+
+char *file_in_same_dir(const char *path, const char *filename);
+
+bool hidden_or_backup_file(const char *filename) _pure_;
+
+bool is_device_path(const char *path);
+
+bool valid_device_node_path(const char *path);
+bool valid_device_allow_pattern(const char *path);
+
+int systemd_installation_has_version(const char *root, unsigned minimal_version);
+
+bool dot_or_dot_dot(const char *path);
+
+static inline const char *skip_dev_prefix(const char *p) {
+        const char *e;
+
+        /* Drop any /dev prefix if there is any */
+
+        e = path_startswith(p, "/dev/");
+
+        return e ?: p;
+}
+
+bool empty_or_root(const char *root);
+static inline const char *empty_to_root(const char *path) {
+        return isempty(path) ? "/" : path;
+}
+
+enum {
+        PATH_CHECK_FATAL    = 1 << 0,  /* If not set, then error message is appended with 'ignoring'. */
+        PATH_CHECK_ABSOLUTE = 1 << 1,
+        PATH_CHECK_RELATIVE = 1 << 2,
+};
+
+int path_simplify_and_warn(char *path, unsigned flag, const char *unit, const char *filename, unsigned line, const char *lvalue);
diff --git a/shared/systemd/src/basic/prioq.c b/shared/systemd/src/basic/prioq.c
new file mode 100644
index 00000000..dc048cc7
--- /dev/null
+++ b/shared/systemd/src/basic/prioq.c
@@ -0,0 +1,302 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+/*
+ * Priority Queue
+ * The prioq object implements a priority queue. That is, it orders objects by
+ * their priority and allows O(1) access to the object with the highest
+ * priority. Insertion and removal are Θ(log n). Optionally, the caller can
+ * provide a pointer to an index which will be kept up-to-date by the prioq.
+ *
+ * The underlying algorithm used in this implementation is a Heap.
+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include "alloc-util.h"
+#include "hashmap.h"
+#include "prioq.h"
+
+struct prioq_item {
+        void *data;
+        unsigned *idx;
+};
+
+struct Prioq {
+        compare_func_t compare_func;
+        unsigned n_items, n_allocated;
+
+        struct prioq_item *items;
+};
+
+Prioq *prioq_new(compare_func_t compare_func) {
+        Prioq *q;
+
+        q = new(Prioq, 1);
+        if (!q)
+                return q;
+
+        *q = (Prioq) {
+                .compare_func = compare_func,
+        };
+
+        return q;
+}
+
+Prioq* prioq_free(Prioq *q) {
+        if (!q)
+                return NULL;
+
+        free(q->items);
+        return mfree(q);
+}
+
+int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func) {
+        assert(q);
+
+        if (*q)
+                return 0;
+
+        *q = prioq_new(compare_func);
+        if (!*q)
+                return -ENOMEM;
+
+        return 0;
+}
+
+static void swap(Prioq *q, unsigned j, unsigned k) {
+        assert(q);
+        assert(j < q->n_items);
+        assert(k < q->n_items);
+
+        assert(!q->items[j].idx || *(q->items[j].idx) == j);
+        assert(!q->items[k].idx || *(q->items[k].idx) == k);
+
+        SWAP_TWO(q->items[j].data, q->items[k].data);
+        SWAP_TWO(q->items[j].idx, q->items[k].idx);
+
+        if (q->items[j].idx)
+                *q->items[j].idx = j;
+
+        if (q->items[k].idx)
+                *q->items[k].idx = k;
+}
+
+static unsigned shuffle_up(Prioq *q, unsigned idx) {
+        assert(q);
+        assert(idx < q->n_items);
+
+        while (idx > 0) {
+                unsigned k;
+
+                k = (idx-1)/2;
+
+                if (q->compare_func(q->items[k].data, q->items[idx].data) <= 0)
+                        break;
+
+                swap(q, idx, k);
+                idx = k;
+        }
+
+        return idx;
+}
+
+static unsigned shuffle_down(Prioq *q, unsigned idx) {
+        assert(q);
+
+        for (;;) {
+                unsigned j, k, s;
+
+                k = (idx+1)*2; /* right child */
+                j = k-1;       /* left child */
+
+                if (j >= q->n_items)
+                        break;
+
+                if (q->compare_func(q->items[j].data, q->items[idx].data) < 0)
+
+                        /* So our left child is smaller than we are, let's
+                         * remember this fact */
+                        s = j;
+                else
+                        s = idx;
+
+                if (k < q->n_items &&
+                    q->compare_func(q->items[k].data, q->items[s].data) < 0)
+
+                        /* So our right child is smaller than we are, let's
+                         * remember this fact */
+                        s = k;
+
+                /* s now points to the smallest of the three items */
+
+                if (s == idx)
+                        /* No swap necessary, we're done */
+                        break;
+
+                swap(q, idx, s);
+                idx = s;
+        }
+
+        return idx;
+}
+
+int prioq_put(Prioq *q, void *data, unsigned *idx) {
+        struct prioq_item *i;
+        unsigned k;
+
+        assert(q);
+
+        if (q->n_items >= q->n_allocated) {
+                unsigned n;
+                struct prioq_item *j;
+
+                n = MAX((q->n_items+1) * 2, 16u);
+                j = reallocarray(q->items, n, sizeof(struct prioq_item));
+                if (!j)
+                        return -ENOMEM;
+
+                q->items = j;
+                q->n_allocated = n;
+        }
+
+        k = q->n_items++;
+        i = q->items + k;
+        i->data = data;
+        i->idx = idx;
+
+        if (idx)
+                *idx = k;
+
+        shuffle_up(q, k);
+
+        return 0;
+}
+
+static void remove_item(Prioq *q, struct prioq_item *i) {
+        struct prioq_item *l;
+
+        assert(q);
+        assert(i);
+
+        l = q->items + q->n_items - 1;
+
+        if (i == l)
+                /* Last entry, let's just remove it */
+                q->n_items--;
+        else {
+                unsigned k;
+
+                /* Not last entry, let's replace the last entry with
+                 * this one, and reshuffle */
+
+                k = i - q->items;
+
+                i->data = l->data;
+                i->idx = l->idx;
+                if (i->idx)
+                        *i->idx = k;
+                q->n_items--;
+
+                k = shuffle_down(q, k);
+                shuffle_up(q, k);
+        }
+}
+
+_pure_ static struct prioq_item* find_item(Prioq *q, void *data, unsigned *idx) {
+        struct prioq_item *i;
+
+        assert(q);
+
+        if (q->n_items <= 0)
+                return NULL;
+
+        if (idx) {
+                if (*idx == PRIOQ_IDX_NULL ||
+                    *idx >= q->n_items)
+                        return NULL;
+
+                i = q->items + *idx;
+                if (i->data != data)
+                        return NULL;
+
+                return i;
+        } else {
+                for (i = q->items; i < q->items + q->n_items; i++)
+                        if (i->data == data)
+                                return i;
+                return NULL;
+        }
+}
+
+int prioq_remove(Prioq *q, void *data, unsigned *idx) {
+        struct prioq_item *i;
+
+        if (!q)
+                return 0;
+
+        i = find_item(q, data, idx);
+        if (!i)
+                return 0;
+
+        remove_item(q, i);
+        return 1;
+}
+
+int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) {
+        struct prioq_item *i;
+        unsigned k;
+
+        assert(q);
+
+        i = find_item(q, data, idx);
+        if (!i)
+                return 0;
+
+        k = i - q->items;
+        k = shuffle_down(q, k);
+        shuffle_up(q, k);
+        return 1;
+}
+
+void *prioq_peek_by_index(Prioq *q, unsigned idx) {
+        if (!q)
+                return NULL;
+
+        if (idx >= q->n_items)
+                return NULL;
+
+        return q->items[idx].data;
+}
+
+void *prioq_pop(Prioq *q) {
+        void *data;
+
+        if (!q)
+                return NULL;
+
+        if (q->n_items <= 0)
+                return NULL;
+
+        data = q->items[0].data;
+        remove_item(q, q->items);
+        return data;
+}
+
+unsigned prioq_size(Prioq *q) {
+
+        if (!q)
+                return 0;
+
+        return q->n_items;
+}
+
+bool prioq_isempty(Prioq *q) {
+
+        if (!q)
+                return true;
+
+        return q->n_items <= 0;
+}
diff --git a/shared/systemd/src/basic/prioq.h b/shared/systemd/src/basic/prioq.h
new file mode 100644
index 00000000..1fb57bfa
--- /dev/null
+++ b/shared/systemd/src/basic/prioq.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+
+#include "hashmap.h"
+#include "macro.h"
+
+typedef struct Prioq Prioq;
+
+#define PRIOQ_IDX_NULL ((unsigned) -1)
+
+Prioq *prioq_new(compare_func_t compare);
+Prioq *prioq_free(Prioq *q);
+DEFINE_TRIVIAL_CLEANUP_FUNC(Prioq*, prioq_free);
+int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func);
+
+int prioq_put(Prioq *q, void *data, unsigned *idx);
+int prioq_remove(Prioq *q, void *data, unsigned *idx);
+int prioq_reshuffle(Prioq *q, void *data, unsigned *idx);
+
+void *prioq_peek_by_index(Prioq *q, unsigned idx) _pure_;
+static inline void *prioq_peek(Prioq *q) {
+        return prioq_peek_by_index(q, 0);
+}
+void *prioq_pop(Prioq *q);
+
+#define PRIOQ_FOREACH_ITEM(q, p)                                \
+        for (unsigned _i = 0; (p = prioq_peek_by_index(q, _i)); _i++)
+
+unsigned prioq_size(Prioq *q) _pure_;
+bool prioq_isempty(Prioq *q) _pure_;
diff --git a/shared/systemd/src/basic/process-util.c b/shared/systemd/src/basic/process-util.c
new file mode 100644
index 00000000..b0afb5c8
--- /dev/null
+++ b/shared/systemd/src/basic/process-util.c
@@ -0,0 +1,1575 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <linux/oom.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/personality.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <syslog.h>
+#include <unistd.h>
+#if 0 /* NM_IGNORED */
+#if HAVE_VALGRIND_VALGRIND_H
+#include <valgrind/valgrind.h>
+#endif
+#endif /* NM_IGNORED */
+
+#include "alloc-util.h"
+#include "architecture.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "ioprio.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "process-util.h"
+#include "raw-clone.h"
+#include "rlimit-util.h"
+#include "signal-util.h"
+#include "stat-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "terminal-util.h"
+#include "user-util.h"
+#include "util.h"
+
+#if 0 /* NM_IGNORED */
+int get_process_state(pid_t pid) {
+        const char *p;
+        char state;
+        int r;
+        _cleanup_free_ char *line = NULL;
+
+        assert(pid >= 0);
+
+        p = procfs_file_alloca(pid, "stat");
+
+        r = read_one_line_file(p, &line);
+        if (r == -ENOENT)
+                return -ESRCH;
+        if (r < 0)
+                return r;
+
+        p = strrchr(line, ')');
+        if (!p)
+                return -EIO;
+
+        p++;
+
+        if (sscanf(p, " %c", &state) != 1)
+                return -EIO;
+
+        return (unsigned char) state;
+}
+
+int get_process_comm(pid_t pid, char **ret) {
+        _cleanup_free_ char *escaped = NULL, *comm = NULL;
+        const char *p;
+        int r;
+
+        assert(ret);
+        assert(pid >= 0);
+
+        escaped = new(char, TASK_COMM_LEN);
+        if (!escaped)
+                return -ENOMEM;
+
+        p = procfs_file_alloca(pid, "comm");
+
+        r = read_one_line_file(p, &comm);
+        if (r == -ENOENT)
+                return -ESRCH;
+        if (r < 0)
+                return r;
+
+        /* Escape unprintable characters, just in case, but don't grow the string beyond the underlying size */
+        cellescape(escaped, TASK_COMM_LEN, comm);
+
+        *ret = TAKE_PTR(escaped);
+        return 0;
+}
+
+int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) {
+        _cleanup_fclose_ FILE *f = NULL;
+        bool space = false;
+        char *k;
+        _cleanup_free_ char *ans = NULL;
+        const char *p;
+        int c;
+
+        assert(line);
+        assert(pid >= 0);
+
+        /* Retrieves a process' command line. Replaces unprintable characters while doing so by whitespace (coalescing
+         * multiple sequential ones into one). If max_length is != 0 will return a string of the specified size at most
+         * (the trailing NUL byte does count towards the length here!), abbreviated with a "..." ellipsis. If
+         * comm_fallback is true and the process has no command line set (the case for kernel threads), or has a
+         * command line that resolves to the empty string will return the "comm" name of the process instead.
+         *
+         * Returns -ESRCH if the process doesn't exist, and -ENOENT if the process has no command line (and
+         * comm_fallback is false). Returns 0 and sets *line otherwise. */
+
+        p = procfs_file_alloca(pid, "cmdline");
+
+        f = fopen(p, "re");
+        if (!f) {
+                if (errno == ENOENT)
+                        return -ESRCH;
+                return -errno;
+        }
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        if (max_length == 0) {
+                /* This is supposed to be a safety guard against runaway command lines. */
+                long l = sysconf(_SC_ARG_MAX);
+                assert(l > 0);
+                max_length = l;
+        }
+
+        if (max_length == 1) {
+
+                /* If there's only room for one byte, return the empty string */
+                ans = new0(char, 1);
+                if (!ans)
+                        return -ENOMEM;
+
+                *line = TAKE_PTR(ans);
+                return 0;
+
+        } else {
+                bool dotdotdot = false;
+                size_t left;
+
+                ans = new(char, max_length);
+                if (!ans)
+                        return -ENOMEM;
+
+                k = ans;
+                left = max_length;
+                while ((c = getc(f)) != EOF) {
+
+                        if (isprint(c)) {
+
+                                if (space) {
+                                        if (left <= 2) {
+                                                dotdotdot = true;
+                                                break;
+                                        }
+
+                                        *(k++) = ' ';
+                                        left--;
+                                        space = false;
+                                }
+
+                                if (left <= 1) {
+                                        dotdotdot = true;
+                                        break;
+                                }
+
+                                *(k++) = (char) c;
+                                left--;
+                        } else if (k > ans)
+                                space = true;
+                }
+
+                if (dotdotdot) {
+                        if (max_length <= 4) {
+                                k = ans;
+                                left = max_length;
+                        } else {
+                                k = ans + max_length - 4;
+                                left = 4;
+
+                                /* Eat up final spaces */
+                                while (k > ans && isspace(k[-1])) {
+                                        k--;
+                                        left++;
+                                }
+                        }
+
+                        strncpy(k, "...", left-1);
+                        k[left-1] = 0;
+                } else
+                        *k = 0;
+        }
+
+        /* Kernel threads have no argv[] */
+        if (isempty(ans)) {
+                _cleanup_free_ char *t = NULL;
+                int h;
+
+                ans = mfree(ans);
+
+                if (!comm_fallback)
+                        return -ENOENT;
+
+                h = get_process_comm(pid, &t);
+                if (h < 0)
+                        return h;
+
+                size_t l = strlen(t);
+
+                if (l + 3 <= max_length) {
+                        ans = strjoin("[", t, "]");
+                        if (!ans)
+                                return -ENOMEM;
+
+                } else if (max_length <= 6) {
+                        ans = new(char, max_length);
+                        if (!ans)
+                                return -ENOMEM;
+
+                        memcpy(ans, "[...]", max_length-1);
+                        ans[max_length-1] = 0;
+                } else {
+                        t[max_length - 6] = 0;
+
+                        /* Chop off final spaces */
+                        delete_trailing_chars(t, WHITESPACE);
+
+                        ans = strjoin("[", t, "...]");
+                        if (!ans)
+                                return -ENOMEM;
+                }
+
+                *line = TAKE_PTR(ans);
+                return 0;
+        }
+
+        k = realloc(ans, strlen(ans) + 1);
+        if (!k)
+                return -ENOMEM;
+
+        ans = NULL;
+        *line = k;
+
+        return 0;
+}
+
+int rename_process(const char name[]) {
+        static size_t mm_size = 0;
+        static char *mm = NULL;
+        bool truncated = false;
+        size_t l;
+
+        /* This is a like a poor man's setproctitle(). It changes the comm field, argv[0], and also the glibc's
+         * internally used name of the process. For the first one a limit of 16 chars applies; to the second one in
+         * many cases one of 10 (i.e. length of "/sbin/init") — however if we have CAP_SYS_RESOURCES it is unbounded;
+         * to the third one 7 (i.e. the length of "systemd". If you pass a longer string it will likely be
+         * truncated.
+         *
+         * Returns 0 if a name was set but truncated, > 0 if it was set but not truncated. */
+
+        if (isempty(name))
+                return -EINVAL; /* let's not confuse users unnecessarily with an empty name */
+
+        if (!is_main_thread())
+                return -EPERM; /* Let's not allow setting the process name from other threads than the main one, as we
+                                * cache things without locking, and we make assumptions that PR_SET_NAME sets the
+                                * process name that isn't correct on any other threads */
+
+        l = strlen(name);
+
+        /* First step, change the comm field. The main thread's comm is identical to the process comm. This means we
+         * can use PR_SET_NAME, which sets the thread name for the calling thread. */
+        if (prctl(PR_SET_NAME, name) < 0)
+                log_debug_errno(errno, "PR_SET_NAME failed: %m");
+        if (l >= TASK_COMM_LEN) /* Linux process names can be 15 chars at max */
+                truncated = true;
+
+        /* Second step, change glibc's ID of the process name. */
+        if (program_invocation_name) {
+                size_t k;
+
+                k = strlen(program_invocation_name);
+                strncpy(program_invocation_name, name, k);
+                if (l > k)
+                        truncated = true;
+        }
+
+        /* Third step, completely replace the argv[] array the kernel maintains for us. This requires privileges, but
+         * has the advantage that the argv[] array is exactly what we want it to be, and not filled up with zeros at
+         * the end. This is the best option for changing /proc/self/cmdline. */
+
+        /* Let's not bother with this if we don't have euid == 0. Strictly speaking we should check for the
+         * CAP_SYS_RESOURCE capability which is independent of the euid. In our own code the capability generally is
+         * present only for euid == 0, hence let's use this as quick bypass check, to avoid calling mmap() if
+         * PR_SET_MM_ARG_{START,END} fails with EPERM later on anyway. After all geteuid() is dead cheap to call, but
+         * mmap() is not. */
+        if (geteuid() != 0)
+                log_debug("Skipping PR_SET_MM, as we don't have privileges.");
+        else if (mm_size < l+1) {
+                size_t nn_size;
+                char *nn;
+
+                nn_size = PAGE_ALIGN(l+1);
+                nn = mmap(NULL, nn_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+                if (nn == MAP_FAILED) {
+                        log_debug_errno(errno, "mmap() failed: %m");
+                        goto use_saved_argv;
+                }
+
+                strncpy(nn, name, nn_size);
+
+                /* Now, let's tell the kernel about this new memory */
+                if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
+                        /* HACK: prctl() API is kind of dumb on this point.  The existing end address may already be
+                         * below the desired start address, in which case the kernel may have kicked this back due
+                         * to a range-check failure (see linux/kernel/sys.c:validate_prctl_map() to see this in
+                         * action).  The proper solution would be to have a prctl() API that could set both start+end
+                         * simultaneously, or at least let us query the existing address to anticipate this condition
+                         * and respond accordingly.  For now, we can only guess at the cause of this failure and try
+                         * a workaround--which will briefly expand the arg space to something potentially huge before
+                         * resizing it to what we want. */
+                        log_debug_errno(errno, "PR_SET_MM_ARG_START failed, attempting PR_SET_MM_ARG_END hack: %m");
+
+                        if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0) {
+                                log_debug_errno(errno, "PR_SET_MM_ARG_END hack failed, proceeding without: %m");
+                                (void) munmap(nn, nn_size);
+                                goto use_saved_argv;
+                        }
+
+                        if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
+                                log_debug_errno(errno, "PR_SET_MM_ARG_START still failed, proceeding without: %m");
+                                goto use_saved_argv;
+                        }
+                } else {
+                        /* And update the end pointer to the new end, too. If this fails, we don't really know what
+                         * to do, it's pretty unlikely that we can rollback, hence we'll just accept the failure,
+                         * and continue. */
+                        if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0)
+                                log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
+                }
+
+                if (mm)
+                        (void) munmap(mm, mm_size);
+
+                mm = nn;
+                mm_size = nn_size;
+        } else {
+                strncpy(mm, name, mm_size);
+
+                /* Update the end pointer, continuing regardless of any failure. */
+                if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) mm + l + 1, 0, 0) < 0)
+                        log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
+        }
+
+use_saved_argv:
+        /* Fourth step: in all cases we'll also update the original argv[], so that our own code gets it right too if
+         * it still looks here */
+
+        if (saved_argc > 0) {
+                int i;
+
+                if (saved_argv[0]) {
+                        size_t k;
+
+                        k = strlen(saved_argv[0]);
+                        strncpy(saved_argv[0], name, k);
+                        if (l > k)
+                                truncated = true;
+                }
+
+                for (i = 1; i < saved_argc; i++) {
+                        if (!saved_argv[i])
+                                break;
+
+                        memzero(saved_argv[i], strlen(saved_argv[i]));
+                }
+        }
+
+        return !truncated;
+}
+
+int is_kernel_thread(pid_t pid) {
+        _cleanup_free_ char *line = NULL;
+        unsigned long long flags;
+        size_t l, i;
+        const char *p;
+        char *q;
+        int r;
+
+        if (IN_SET(pid, 0, 1) || pid == getpid_cached()) /* pid 1, and we ourselves certainly aren't a kernel thread */
+                return 0;
+        if (!pid_is_valid(pid))
+                return -EINVAL;
+
+        p = procfs_file_alloca(pid, "stat");
+        r = read_one_line_file(p, &line);
+        if (r == -ENOENT)
+                return -ESRCH;
+        if (r < 0)
+                return r;
+
+        /* Skip past the comm field */
+        q = strrchr(line, ')');
+        if (!q)
+                return -EINVAL;
+        q++;
+
+        /* Skip 6 fields to reach the flags field */
+        for (i = 0; i < 6; i++) {
+                l = strspn(q, WHITESPACE);
+                if (l < 1)
+                        return -EINVAL;
+                q += l;
+
+                l = strcspn(q, WHITESPACE);
+                if (l < 1)
+                        return -EINVAL;
+                q += l;
+        }
+
+        /* Skip preceding whitespace */
+        l = strspn(q, WHITESPACE);
+        if (l < 1)
+                return -EINVAL;
+        q += l;
+
+        /* Truncate the rest */
+        l = strcspn(q, WHITESPACE);
+        if (l < 1)
+                return -EINVAL;
+        q[l] = 0;
+
+        r = safe_atollu(q, &flags);
+        if (r < 0)
+                return r;
+
+        return !!(flags & PF_KTHREAD);
+}
+
+int get_process_capeff(pid_t pid, char **capeff) {
+        const char *p;
+        int r;
+
+        assert(capeff);
+        assert(pid >= 0);
+
+        p = procfs_file_alloca(pid, "status");
+
+        r = get_proc_field(p, "CapEff", WHITESPACE, capeff);
+        if (r == -ENOENT)
+                return -ESRCH;
+
+        return r;
+}
+
+static int get_process_link_contents(const char *proc_file, char **name) {
+        int r;
+
+        assert(proc_file);
+        assert(name);
+
+        r = readlink_malloc(proc_file, name);
+        if (r == -ENOENT)
+                return -ESRCH;
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+int get_process_exe(pid_t pid, char **name) {
+        const char *p;
+        char *d;
+        int r;
+
+        assert(pid >= 0);
+
+        p = procfs_file_alloca(pid, "exe");
+        r = get_process_link_contents(p, name);
+        if (r < 0)
+                return r;
+
+        d = endswith(*name, " (deleted)");
+        if (d)
+                *d = '\0';
+
+        return 0;
+}
+
+static int get_process_id(pid_t pid, const char *field, uid_t *uid) {
+        _cleanup_fclose_ FILE *f = NULL;
+        const char *p;
+        int r;
+
+        assert(field);
+        assert(uid);
+
+        if (pid < 0)
+                return -EINVAL;
+
+        p = procfs_file_alloca(pid, "status");
+        f = fopen(p, "re");
+        if (!f) {
+                if (errno == ENOENT)
+                        return -ESRCH;
+                return -errno;
+        }
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        for (;;) {
+                _cleanup_free_ char *line = NULL;
+                char *l;
+
+                r = read_line(f, LONG_LINE_MAX, &line);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                l = strstrip(line);
+
+                if (startswith(l, field)) {
+                        l += strlen(field);
+                        l += strspn(l, WHITESPACE);
+
+                        l[strcspn(l, WHITESPACE)] = 0;
+
+                        return parse_uid(l, uid);
+                }
+        }
+
+        return -EIO;
+}
+
+int get_process_uid(pid_t pid, uid_t *uid) {
+
+        if (pid == 0 || pid == getpid_cached()) {
+                *uid = getuid();
+                return 0;
+        }
+
+        return get_process_id(pid, "Uid:", uid);
+}
+
+int get_process_gid(pid_t pid, gid_t *gid) {
+
+        if (pid == 0 || pid == getpid_cached()) {
+                *gid = getgid();
+                return 0;
+        }
+
+        assert_cc(sizeof(uid_t) == sizeof(gid_t));
+        return get_process_id(pid, "Gid:", gid);
+}
+
+int get_process_cwd(pid_t pid, char **cwd) {
+        const char *p;
+
+        assert(pid >= 0);
+
+        p = procfs_file_alloca(pid, "cwd");
+
+        return get_process_link_contents(p, cwd);
+}
+
+int get_process_root(pid_t pid, char **root) {
+        const char *p;
+
+        assert(pid >= 0);
+
+        p = procfs_file_alloca(pid, "root");
+
+        return get_process_link_contents(p, root);
+}
+
+#define ENVIRONMENT_BLOCK_MAX (5U*1024U*1024U)
+
+int get_process_environ(pid_t pid, char **env) {
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *outcome = NULL;
+        size_t allocated = 0, sz = 0;
+        const char *p;
+        int r;
+
+        assert(pid >= 0);
+        assert(env);
+
+        p = procfs_file_alloca(pid, "environ");
+
+        f = fopen(p, "re");
+        if (!f) {
+                if (errno == ENOENT)
+                        return -ESRCH;
+                return -errno;
+        }
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        for (;;) {
+                char c;
+
+                if (sz >= ENVIRONMENT_BLOCK_MAX)
+                        return -ENOBUFS;
+
+                if (!GREEDY_REALLOC(outcome, allocated, sz + 5))
+                        return -ENOMEM;
+
+                r = safe_fgetc(f, &c);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                if (c == '\0')
+                        outcome[sz++] = '\n';
+                else
+                        sz += cescape_char(c, outcome + sz);
+        }
+
+        outcome[sz] = '\0';
+        *env = TAKE_PTR(outcome);
+
+        return 0;
+}
+
+int get_process_ppid(pid_t pid, pid_t *_ppid) {
+        int r;
+        _cleanup_free_ char *line = NULL;
+        long unsigned ppid;
+        const char *p;
+
+        assert(pid >= 0);
+        assert(_ppid);
+
+        if (pid == 0 || pid == getpid_cached()) {
+                *_ppid = getppid();
+                return 0;
+        }
+
+        p = procfs_file_alloca(pid, "stat");
+        r = read_one_line_file(p, &line);
+        if (r == -ENOENT)
+                return -ESRCH;
+        if (r < 0)
+                return r;
+
+        /* Let's skip the pid and comm fields. The latter is enclosed
+         * in () but does not escape any () in its value, so let's
+         * skip over it manually */
+
+        p = strrchr(line, ')');
+        if (!p)
+                return -EIO;
+
+        p++;
+
+        if (sscanf(p, " "
+                   "%*c "  /* state */
+                   "%lu ", /* ppid */
+                   &ppid) != 1)
+                return -EIO;
+
+        if ((long unsigned) (pid_t) ppid != ppid)
+                return -ERANGE;
+
+        *_ppid = (pid_t) ppid;
+
+        return 0;
+}
+
+int wait_for_terminate(pid_t pid, siginfo_t *status) {
+        siginfo_t dummy;
+
+        assert(pid >= 1);
+
+        if (!status)
+                status = &dummy;
+
+        for (;;) {
+                zero(*status);
+
+                if (waitid(P_PID, pid, status, WEXITED) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        return negative_errno();
+                }
+
+                return 0;
+        }
+}
+
+/*
+ * Return values:
+ * < 0 : wait_for_terminate() failed to get the state of the
+ *       process, the process was terminated by a signal, or
+ *       failed for an unknown reason.
+ * >=0 : The process terminated normally, and its exit code is
+ *       returned.
+ *
+ * That is, success is indicated by a return value of zero, and an
+ * error is indicated by a non-zero value.
+ *
+ * A warning is emitted if the process terminates abnormally,
+ * and also if it returns non-zero unless check_exit_code is true.
+ */
+int wait_for_terminate_and_check(const char *name, pid_t pid, WaitFlags flags) {
+        _cleanup_free_ char *buffer = NULL;
+        siginfo_t status;
+        int r, prio;
+
+        assert(pid > 1);
+
+        if (!name) {
+                r = get_process_comm(pid, &buffer);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to acquire process name of " PID_FMT ", ignoring: %m", pid);
+                else
+                        name = buffer;
+        }
+
+        prio = flags & WAIT_LOG_ABNORMAL ? LOG_ERR : LOG_DEBUG;
+
+        r = wait_for_terminate(pid, &status);
+        if (r < 0)
+                return log_full_errno(prio, r, "Failed to wait for %s: %m", strna(name));
+
+        if (status.si_code == CLD_EXITED) {
+                if (status.si_status != EXIT_SUCCESS)
+                        log_full(flags & WAIT_LOG_NON_ZERO_EXIT_STATUS ? LOG_ERR : LOG_DEBUG,
+                                 "%s failed with exit status %i.", strna(name), status.si_status);
+                else
+                        log_debug("%s succeeded.", name);
+
+                return status.si_status;
+
+        } else if (IN_SET(status.si_code, CLD_KILLED, CLD_DUMPED)) {
+
+                log_full(prio, "%s terminated by signal %s.", strna(name), signal_to_string(status.si_status));
+                return -EPROTO;
+        }
+
+        log_full(prio, "%s failed due to unknown reason.", strna(name));
+        return -EPROTO;
+}
+
+/*
+ * Return values:
+ *
+ * < 0 : wait_for_terminate_with_timeout() failed to get the state of the process, the process timed out, the process
+ *       was terminated by a signal, or failed for an unknown reason.
+ *
+ * >=0 : The process terminated normally with no failures.
+ *
+ * Success is indicated by a return value of zero, a timeout is indicated by ETIMEDOUT, and all other child failure
+ * states are indicated by error is indicated by a non-zero value.
+ *
+ * This call assumes SIGCHLD has been blocked already, in particular before the child to wait for has been forked off
+ * to remain entirely race-free.
+ */
+int wait_for_terminate_with_timeout(pid_t pid, usec_t timeout) {
+        sigset_t mask;
+        int r;
+        usec_t until;
+
+        assert_se(sigemptyset(&mask) == 0);
+        assert_se(sigaddset(&mask, SIGCHLD) == 0);
+
+        /* Drop into a sigtimewait-based timeout. Waiting for the
+         * pid to exit. */
+        until = now(CLOCK_MONOTONIC) + timeout;
+        for (;;) {
+                usec_t n;
+                siginfo_t status = {};
+                struct timespec ts;
+
+                n = now(CLOCK_MONOTONIC);
+                if (n >= until)
+                        break;
+
+                r = sigtimedwait(&mask, NULL, timespec_store(&ts, until - n)) < 0 ? -errno : 0;
+                /* Assuming we woke due to the child exiting. */
+                if (waitid(P_PID, pid, &status, WEXITED|WNOHANG) == 0) {
+                        if (status.si_pid == pid) {
+                                /* This is the correct child.*/
+                                if (status.si_code == CLD_EXITED)
+                                        return (status.si_status == 0) ? 0 : -EPROTO;
+                                else
+                                        return -EPROTO;
+                        }
+                }
+                /* Not the child, check for errors and proceed appropriately */
+                if (r < 0) {
+                        switch (r) {
+                        case -EAGAIN:
+                                /* Timed out, child is likely hung. */
+                                return -ETIMEDOUT;
+                        case -EINTR:
+                                /* Received a different signal and should retry */
+                                continue;
+                        default:
+                                /* Return any unexpected errors */
+                                return r;
+                        }
+                }
+        }
+
+        return -EPROTO;
+}
+
+void sigkill_wait(pid_t pid) {
+        assert(pid > 1);
+
+        if (kill(pid, SIGKILL) >= 0)
+                (void) wait_for_terminate(pid, NULL);
+}
+
+void sigkill_waitp(pid_t *pid) {
+        PROTECT_ERRNO;
+
+        if (!pid)
+                return;
+        if (*pid <= 1)
+                return;
+
+        sigkill_wait(*pid);
+}
+
+void sigterm_wait(pid_t pid) {
+        assert(pid > 1);
+
+        if (kill_and_sigcont(pid, SIGTERM) >= 0)
+                (void) wait_for_terminate(pid, NULL);
+}
+
+int kill_and_sigcont(pid_t pid, int sig) {
+        int r;
+
+        r = kill(pid, sig) < 0 ? -errno : 0;
+
+        /* If this worked, also send SIGCONT, unless we already just sent a SIGCONT, or SIGKILL was sent which isn't
+         * affected by a process being suspended anyway. */
+        if (r >= 0 && !IN_SET(sig, SIGCONT, SIGKILL))
+                (void) kill(pid, SIGCONT);
+
+        return r;
+}
+
+int getenv_for_pid(pid_t pid, const char *field, char **ret) {
+        _cleanup_fclose_ FILE *f = NULL;
+        char *value = NULL;
+        const char *path;
+        size_t l, sum = 0;
+        int r;
+
+        assert(pid >= 0);
+        assert(field);
+        assert(ret);
+
+        if (pid == 0 || pid == getpid_cached()) {
+                const char *e;
+
+                e = getenv(field);
+                if (!e) {
+                        *ret = NULL;
+                        return 0;
+                }
+
+                value = strdup(e);
+                if (!value)
+                        return -ENOMEM;
+
+                *ret = value;
+                return 1;
+        }
+
+        if (!pid_is_valid(pid))
+                return -EINVAL;
+
+        path = procfs_file_alloca(pid, "environ");
+
+        f = fopen(path, "re");
+        if (!f) {
+                if (errno == ENOENT)
+                        return -ESRCH;
+
+                return -errno;
+        }
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        l = strlen(field);
+        for (;;) {
+                _cleanup_free_ char *line = NULL;
+
+                if (sum > ENVIRONMENT_BLOCK_MAX) /* Give up searching eventually */
+                        return -ENOBUFS;
+
+                r = read_nul_string(f, LONG_LINE_MAX, &line);
+                if (r < 0)
+                        return r;
+                if (r == 0)  /* EOF */
+                        break;
+
+                sum += r;
+
+                if (strneq(line, field, l) && line[l] == '=') {
+                        value = strdup(line + l + 1);
+                        if (!value)
+                                return -ENOMEM;
+
+                        *ret = value;
+                        return 1;
+                }
+        }
+
+        *ret = NULL;
+        return 0;
+}
+
+bool pid_is_unwaited(pid_t pid) {
+        /* Checks whether a PID is still valid at all, including a zombie */
+
+        if (pid < 0)
+                return false;
+
+        if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */
+                return true;
+
+        if (pid == getpid_cached())
+                return true;
+
+        if (kill(pid, 0) >= 0)
+                return true;
+
+        return errno != ESRCH;
+}
+
+bool pid_is_alive(pid_t pid) {
+        int r;
+
+        /* Checks whether a PID is still valid and not a zombie */
+
+        if (pid < 0)
+                return false;
+
+        if (pid <= 1) /* If we or PID 1 would be a zombie, this code would not be running */
+                return true;
+
+        if (pid == getpid_cached())
+                return true;
+
+        r = get_process_state(pid);
+        if (IN_SET(r, -ESRCH, 'Z'))
+                return false;
+
+        return true;
+}
+
+int pid_from_same_root_fs(pid_t pid) {
+        const char *root;
+
+        if (pid < 0)
+                return false;
+
+        if (pid == 0 || pid == getpid_cached())
+                return true;
+
+        root = procfs_file_alloca(pid, "root");
+
+        return files_same(root, "/proc/1/root", 0);
+}
+#endif /* NM_IGNORED */
+
+bool is_main_thread(void) {
+        static thread_local int cached = 0;
+
+        if (_unlikely_(cached == 0))
+                cached = getpid_cached() == gettid() ? 1 : -1;
+
+        return cached > 0;
+}
+
+#if 0 /* NM_IGNORED */
+_noreturn_ void freeze(void) {
+
+        log_close();
+
+        /* Make sure nobody waits for us on a socket anymore */
+        close_all_fds(NULL, 0);
+
+        sync();
+
+        /* Let's not freeze right away, but keep reaping zombies. */
+        for (;;) {
+                int r;
+                siginfo_t si = {};
+
+                r = waitid(P_ALL, 0, &si, WEXITED);
+                if (r < 0 && errno != EINTR)
+                        break;
+        }
+
+        /* waitid() failed with an unexpected error, things are really borked. Freeze now! */
+        for (;;)
+                pause();
+}
+
+bool oom_score_adjust_is_valid(int oa) {
+        return oa >= OOM_SCORE_ADJ_MIN && oa <= OOM_SCORE_ADJ_MAX;
+}
+
+unsigned long personality_from_string(const char *p) {
+        int architecture;
+
+        if (!p)
+                return PERSONALITY_INVALID;
+
+        /* Parse a personality specifier. We use our own identifiers that indicate specific ABIs, rather than just
+         * hints regarding the register size, since we want to keep things open for multiple locally supported ABIs for
+         * the same register size. */
+
+        architecture = architecture_from_string(p);
+        if (architecture < 0)
+                return PERSONALITY_INVALID;
+
+        if (architecture == native_architecture())
+                return PER_LINUX;
+#ifdef SECONDARY_ARCHITECTURE
+        if (architecture == SECONDARY_ARCHITECTURE)
+                return PER_LINUX32;
+#endif
+
+        return PERSONALITY_INVALID;
+}
+
+const char* personality_to_string(unsigned long p) {
+        int architecture = _ARCHITECTURE_INVALID;
+
+        if (p == PER_LINUX)
+                architecture = native_architecture();
+#ifdef SECONDARY_ARCHITECTURE
+        else if (p == PER_LINUX32)
+                architecture = SECONDARY_ARCHITECTURE;
+#endif
+
+        if (architecture < 0)
+                return NULL;
+
+        return architecture_to_string(architecture);
+}
+
+int safe_personality(unsigned long p) {
+        int ret;
+
+        /* So here's the deal, personality() is weirdly defined by glibc. In some cases it returns a failure via errno,
+         * and in others as negative return value containing an errno-like value. Let's work around this: this is a
+         * wrapper that uses errno if it is set, and uses the return value otherwise. And then it sets both errno and
+         * the return value indicating the same issue, so that we are definitely on the safe side.
+         *
+         * See https://github.com/systemd/systemd/issues/6737 */
+
+        errno = 0;
+        ret = personality(p);
+        if (ret < 0) {
+                if (errno != 0)
+                        return -errno;
+
+                errno = -ret;
+        }
+
+        return ret;
+}
+
+int opinionated_personality(unsigned long *ret) {
+        int current;
+
+        /* Returns the current personality, or PERSONALITY_INVALID if we can't determine it. This function is a bit
+         * opinionated though, and ignores all the finer-grained bits and exotic personalities, only distinguishing the
+         * two most relevant personalities: PER_LINUX and PER_LINUX32. */
+
+        current = safe_personality(PERSONALITY_INVALID);
+        if (current < 0)
+                return current;
+
+        if (((unsigned long) current & 0xffff) == PER_LINUX32)
+                *ret = PER_LINUX32;
+        else
+                *ret = PER_LINUX;
+
+        return 0;
+}
+
+void valgrind_summary_hack(void) {
+#if HAVE_VALGRIND_VALGRIND_H
+        if (getpid_cached() == 1 && RUNNING_ON_VALGRIND) {
+                pid_t pid;
+                pid = raw_clone(SIGCHLD);
+                if (pid < 0)
+                        log_emergency_errno(errno, "Failed to fork off valgrind helper: %m");
+                else if (pid == 0)
+                        exit(EXIT_SUCCESS);
+                else {
+                        log_info("Spawned valgrind helper as PID "PID_FMT".", pid);
+                        (void) wait_for_terminate(pid, NULL);
+                }
+        }
+#endif
+}
+
+int pid_compare_func(const pid_t *a, const pid_t *b) {
+        /* Suitable for usage in qsort() */
+        return CMP(*a, *b);
+}
+
+int ioprio_parse_priority(const char *s, int *ret) {
+        int i, r;
+
+        assert(s);
+        assert(ret);
+
+        r = safe_atoi(s, &i);
+        if (r < 0)
+                return r;
+
+        if (!ioprio_priority_is_valid(i))
+                return -EINVAL;
+
+        *ret = i;
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+/* The cached PID, possible values:
+ *
+ *     == UNSET [0]  → cache not initialized yet
+ *     == BUSY [-1]  → some thread is initializing it at the moment
+ *     any other     → the cached PID
+ */
+
+#define CACHED_PID_UNSET ((pid_t) 0)
+#define CACHED_PID_BUSY ((pid_t) -1)
+
+static pid_t cached_pid = CACHED_PID_UNSET;
+
+void reset_cached_pid(void) {
+        /* Invoked in the child after a fork(), i.e. at the first moment the PID changed */
+        cached_pid = CACHED_PID_UNSET;
+}
+
+/* We use glibc __register_atfork() + __dso_handle directly here, as they are not included in the glibc
+ * headers. __register_atfork() is mostly equivalent to pthread_atfork(), but doesn't require us to link against
+ * libpthread, as it is part of glibc anyway. */
+extern int __register_atfork(void (*prepare) (void), void (*parent) (void), void (*child) (void), void *dso_handle);
+extern void* __dso_handle _weak_;
+
+pid_t getpid_cached(void) {
+        static bool installed = false;
+        pid_t current_value;
+
+        /* getpid_cached() is much like getpid(), but caches the value in local memory, to avoid having to invoke a
+         * system call each time. This restores glibc behaviour from before 2.24, when getpid() was unconditionally
+         * cached. Starting with 2.24 getpid() started to become prohibitively expensive when used for detecting when
+         * objects were used across fork()s. With this caching the old behaviour is somewhat restored.
+         *
+         * https://bugzilla.redhat.com/show_bug.cgi?id=1443976
+         * https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=c579f48edba88380635ab98cb612030e3ed8691e
+         */
+
+        current_value = __sync_val_compare_and_swap(&cached_pid, CACHED_PID_UNSET, CACHED_PID_BUSY);
+
+        switch (current_value) {
+
+        case CACHED_PID_UNSET: { /* Not initialized yet, then do so now */
+                pid_t new_pid;
+
+                new_pid = raw_getpid();
+
+                if (!installed) {
+                        /* __register_atfork() either returns 0 or -ENOMEM, in its glibc implementation. Since it's
+                         * only half-documented (glibc doesn't document it but LSB does — though only superficially)
+                         * we'll check for errors only in the most generic fashion possible. */
+
+                        if (__register_atfork(NULL, NULL, reset_cached_pid, __dso_handle) != 0) {
+                                /* OOM? Let's try again later */
+                                cached_pid = CACHED_PID_UNSET;
+                                return new_pid;
+                        }
+
+                        installed = true;
+                }
+
+                cached_pid = new_pid;
+                return new_pid;
+        }
+
+        case CACHED_PID_BUSY: /* Somebody else is currently initializing */
+                return raw_getpid();
+
+        default: /* Properly initialized */
+                return current_value;
+        }
+}
+
+#if 0 /* NM_IGNORED */
+int must_be_root(void) {
+
+        if (geteuid() == 0)
+                return 0;
+
+        return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be root.");
+}
+
+int safe_fork_full(
+                const char *name,
+                const int except_fds[],
+                size_t n_except_fds,
+                ForkFlags flags,
+                pid_t *ret_pid) {
+
+        pid_t original_pid, pid;
+        sigset_t saved_ss, ss;
+        bool block_signals = false;
+        int prio, r;
+
+        /* A wrapper around fork(), that does a couple of important initializations in addition to mere forking. Always
+         * returns the child's PID in *ret_pid. Returns == 0 in the child, and > 0 in the parent. */
+
+        prio = flags & FORK_LOG ? LOG_ERR : LOG_DEBUG;
+
+        original_pid = getpid_cached();
+
+        if (flags & (FORK_RESET_SIGNALS|FORK_DEATHSIG)) {
+                /* We temporarily block all signals, so that the new child has them blocked initially. This way, we can
+                 * be sure that SIGTERMs are not lost we might send to the child. */
+
+                assert_se(sigfillset(&ss) >= 0);
+                block_signals = true;
+
+        } else if (flags & FORK_WAIT) {
+                /* Let's block SIGCHLD at least, so that we can safely watch for the child process */
+
+                assert_se(sigemptyset(&ss) >= 0);
+                assert_se(sigaddset(&ss, SIGCHLD) >= 0);
+                block_signals = true;
+        }
+
+        if (block_signals)
+                if (sigprocmask(SIG_SETMASK, &ss, &saved_ss) < 0)
+                        return log_full_errno(prio, errno, "Failed to set signal mask: %m");
+
+        if (flags & FORK_NEW_MOUNTNS)
+                pid = raw_clone(SIGCHLD|CLONE_NEWNS);
+        else
+                pid = fork();
+        if (pid < 0) {
+                r = -errno;
+
+                if (block_signals) /* undo what we did above */
+                        (void) sigprocmask(SIG_SETMASK, &saved_ss, NULL);
+
+                return log_full_errno(prio, r, "Failed to fork: %m");
+        }
+        if (pid > 0) {
+                /* We are in the parent process */
+
+                log_debug("Successfully forked off '%s' as PID " PID_FMT ".", strna(name), pid);
+
+                if (flags & FORK_WAIT) {
+                        r = wait_for_terminate_and_check(name, pid, (flags & FORK_LOG ? WAIT_LOG : 0));
+                        if (r < 0)
+                                return r;
+                        if (r != EXIT_SUCCESS) /* exit status > 0 should be treated as failure, too */
+                                return -EPROTO;
+                }
+
+                if (block_signals) /* undo what we did above */
+                        (void) sigprocmask(SIG_SETMASK, &saved_ss, NULL);
+
+                if (ret_pid)
+                        *ret_pid = pid;
+
+                return 1;
+        }
+
+        /* We are in the child process */
+
+        if (flags & FORK_REOPEN_LOG) {
+                /* Close the logs if requested, before we log anything. And make sure we reopen it if needed. */
+                log_close();
+                log_set_open_when_needed(true);
+        }
+
+        if (name) {
+                r = rename_process(name);
+                if (r < 0)
+                        log_full_errno(flags & FORK_LOG ? LOG_WARNING : LOG_DEBUG,
+                                       r, "Failed to rename process, ignoring: %m");
+        }
+
+        if (flags & FORK_DEATHSIG)
+                if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) {
+                        log_full_errno(prio, errno, "Failed to set death signal: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+        if (flags & FORK_RESET_SIGNALS) {
+                r = reset_all_signal_handlers();
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to reset signal handlers: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                /* This implicitly undoes the signal mask stuff we did before the fork()ing above */
+                r = reset_signal_mask();
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to reset signal mask: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        } else if (block_signals) { /* undo what we did above */
+                if (sigprocmask(SIG_SETMASK, &saved_ss, NULL) < 0) {
+                        log_full_errno(prio, errno, "Failed to restore signal mask: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
+        if (flags & FORK_DEATHSIG) {
+                pid_t ppid;
+                /* Let's see if the parent PID is still the one we started from? If not, then the parent
+                 * already died by the time we set PR_SET_PDEATHSIG, hence let's emulate the effect */
+
+                ppid = getppid();
+                if (ppid == 0)
+                        /* Parent is in a differn't PID namespace. */;
+                else if (ppid != original_pid) {
+                        log_debug("Parent died early, raising SIGTERM.");
+                        (void) raise(SIGTERM);
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
+        if (FLAGS_SET(flags, FORK_NEW_MOUNTNS | FORK_MOUNTNS_SLAVE)) {
+
+                /* Optionally, make sure we never propagate mounts to the host. */
+
+                if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) {
+                        log_full_errno(prio, errno, "Failed to remount root directory as MS_SLAVE: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
+        if (flags & FORK_CLOSE_ALL_FDS) {
+                /* Close the logs here in case it got reopened above, as close_all_fds() would close them for us */
+                log_close();
+
+                r = close_all_fds(except_fds, n_except_fds);
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to close all file descriptors: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
+        /* When we were asked to reopen the logs, do so again now */
+        if (flags & FORK_REOPEN_LOG) {
+                log_open();
+                log_set_open_when_needed(false);
+        }
+
+        if (flags & FORK_NULL_STDIO) {
+                r = make_null_stdio();
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to connect stdin/stdout to /dev/null: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
+        if (flags & FORK_RLIMIT_NOFILE_SAFE) {
+                r = rlimit_nofile_safe();
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to lower RLIMIT_NOFILE's soft limit to 1K: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
+        if (ret_pid)
+                *ret_pid = getpid_cached();
+
+        return 0;
+}
+
+int namespace_fork(
+                const char *outer_name,
+                const char *inner_name,
+                const int except_fds[],
+                size_t n_except_fds,
+                ForkFlags flags,
+                int pidns_fd,
+                int mntns_fd,
+                int netns_fd,
+                int userns_fd,
+                int root_fd,
+                pid_t *ret_pid) {
+
+        int r;
+
+        /* This is much like safe_fork(), but forks twice, and joins the specified namespaces in the middle
+         * process. This ensures that we are fully a member of the destination namespace, with pidns an all, so that
+         * /proc/self/fd works correctly. */
+
+        r = safe_fork_full(outer_name, except_fds, n_except_fds, (flags|FORK_DEATHSIG) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                pid_t pid;
+
+                /* Child */
+
+                r = namespace_enter(pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd);
+                if (r < 0) {
+                        log_full_errno(FLAGS_SET(flags, FORK_LOG) ? LOG_ERR : LOG_DEBUG, r, "Failed to join namespace: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                /* We mask a few flags here that either make no sense for the grandchild, or that we don't have to do again */
+                r = safe_fork_full(inner_name, except_fds, n_except_fds, flags & ~(FORK_WAIT|FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_NULL_STDIO), &pid);
+                if (r < 0)
+                        _exit(EXIT_FAILURE);
+                if (r == 0) {
+                        /* Child */
+                        if (ret_pid)
+                                *ret_pid = pid;
+                        return 0;
+                }
+
+                r = wait_for_terminate_and_check(inner_name, pid, FLAGS_SET(flags, FORK_LOG) ? WAIT_LOG : 0);
+                if (r < 0)
+                        _exit(EXIT_FAILURE);
+
+                _exit(r);
+        }
+
+        return 1;
+}
+
+int fork_agent(const char *name, const int except[], size_t n_except, pid_t *ret_pid, const char *path, ...) {
+        bool stdout_is_tty, stderr_is_tty;
+        size_t n, i;
+        va_list ap;
+        char **l;
+        int r;
+
+        assert(path);
+
+        /* Spawns a temporary TTY agent, making sure it goes away when we go away */
+
+        r = safe_fork_full(name, except, n_except, FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS, ret_pid);
+        if (r < 0)
+                return r;
+        if (r > 0)
+                return 0;
+
+        /* In the child: */
+
+        stdout_is_tty = isatty(STDOUT_FILENO);
+        stderr_is_tty = isatty(STDERR_FILENO);
+
+        if (!stdout_is_tty || !stderr_is_tty) {
+                int fd;
+
+                /* Detach from stdout/stderr. and reopen
+                 * /dev/tty for them. This is important to
+                 * ensure that when systemctl is started via
+                 * popen() or a similar call that expects to
+                 * read EOF we actually do generate EOF and
+                 * not delay this indefinitely by because we
+                 * keep an unused copy of stdin around. */
+                fd = open("/dev/tty", O_WRONLY);
+                if (fd < 0) {
+                        log_error_errno(errno, "Failed to open /dev/tty: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                if (!stdout_is_tty && dup2(fd, STDOUT_FILENO) < 0) {
+                        log_error_errno(errno, "Failed to dup2 /dev/tty: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                if (!stderr_is_tty && dup2(fd, STDERR_FILENO) < 0) {
+                        log_error_errno(errno, "Failed to dup2 /dev/tty: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                safe_close_above_stdio(fd);
+        }
+
+        (void) rlimit_nofile_safe();
+
+        /* Count arguments */
+        va_start(ap, path);
+        for (n = 0; va_arg(ap, char*); n++)
+                ;
+        va_end(ap);
+
+        /* Allocate strv */
+        l = newa(char*, n + 1);
+
+        /* Fill in arguments */
+        va_start(ap, path);
+        for (i = 0; i <= n; i++)
+                l[i] = va_arg(ap, char*);
+        va_end(ap);
+
+        execv(path, l);
+        _exit(EXIT_FAILURE);
+}
+
+int set_oom_score_adjust(int value) {
+        char t[DECIMAL_STR_MAX(int)];
+
+        sprintf(t, "%i", value);
+
+        return write_string_file("/proc/self/oom_score_adj", t,
+                                 WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_DISABLE_BUFFER);
+}
+
+static const char *const ioprio_class_table[] = {
+        [IOPRIO_CLASS_NONE] = "none",
+        [IOPRIO_CLASS_RT] = "realtime",
+        [IOPRIO_CLASS_BE] = "best-effort",
+        [IOPRIO_CLASS_IDLE] = "idle"
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, IOPRIO_N_CLASSES);
+
+static const char *const sigchld_code_table[] = {
+        [CLD_EXITED] = "exited",
+        [CLD_KILLED] = "killed",
+        [CLD_DUMPED] = "dumped",
+        [CLD_TRAPPED] = "trapped",
+        [CLD_STOPPED] = "stopped",
+        [CLD_CONTINUED] = "continued",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int);
+
+static const char* const sched_policy_table[] = {
+        [SCHED_OTHER] = "other",
+        [SCHED_BATCH] = "batch",
+        [SCHED_IDLE] = "idle",
+        [SCHED_FIFO] = "fifo",
+        [SCHED_RR] = "rr"
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX);
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/process-util.h b/shared/systemd/src/basic/process-util.h
new file mode 100644
index 00000000..0425042f
--- /dev/null
+++ b/shared/systemd/src/basic/process-util.h
@@ -0,0 +1,196 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <alloca.h>
+#include <errno.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+
+#include "format-util.h"
+#include "ioprio.h"
+#include "macro.h"
+#include "time-util.h"
+
+#define procfs_file_alloca(pid, field)                                  \
+        ({                                                              \
+                pid_t _pid_ = (pid);                                    \
+                const char *_r_;                                        \
+                if (_pid_ == 0) {                                       \
+                        _r_ = ("/proc/self/" field);                    \
+                } else {                                                \
+                        _r_ = newa(char, STRLEN("/proc/") + DECIMAL_STR_MAX(pid_t) + 1 + sizeof(field)); \
+                        sprintf((char*) _r_, "/proc/"PID_FMT"/" field, _pid_); \
+                }                                                       \
+                _r_;                                                    \
+        })
+
+int get_process_state(pid_t pid);
+int get_process_comm(pid_t pid, char **name);
+int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line);
+int get_process_exe(pid_t pid, char **name);
+int get_process_uid(pid_t pid, uid_t *uid);
+int get_process_gid(pid_t pid, gid_t *gid);
+int get_process_capeff(pid_t pid, char **capeff);
+int get_process_cwd(pid_t pid, char **cwd);
+int get_process_root(pid_t pid, char **root);
+int get_process_environ(pid_t pid, char **environ);
+int get_process_ppid(pid_t pid, pid_t *ppid);
+
+int wait_for_terminate(pid_t pid, siginfo_t *status);
+
+typedef enum WaitFlags {
+        WAIT_LOG_ABNORMAL             = 1 << 0,
+        WAIT_LOG_NON_ZERO_EXIT_STATUS = 1 << 1,
+
+        /* A shortcut for requesting the most complete logging */
+        WAIT_LOG = WAIT_LOG_ABNORMAL|WAIT_LOG_NON_ZERO_EXIT_STATUS,
+} WaitFlags;
+
+int wait_for_terminate_and_check(const char *name, pid_t pid, WaitFlags flags);
+int wait_for_terminate_with_timeout(pid_t pid, usec_t timeout);
+
+void sigkill_wait(pid_t pid);
+void sigkill_waitp(pid_t *pid);
+void sigterm_wait(pid_t pid);
+
+int kill_and_sigcont(pid_t pid, int sig);
+
+int rename_process(const char name[]);
+int is_kernel_thread(pid_t pid);
+
+int getenv_for_pid(pid_t pid, const char *field, char **_value);
+
+bool pid_is_alive(pid_t pid);
+bool pid_is_unwaited(pid_t pid);
+int pid_from_same_root_fs(pid_t pid);
+
+bool is_main_thread(void);
+
+_noreturn_ void freeze(void);
+
+bool oom_score_adjust_is_valid(int oa);
+
+#ifndef PERSONALITY_INVALID
+/* personality(7) documents that 0xffffffffUL is used for querying the
+ * current personality, hence let's use that here as error
+ * indicator. */
+#define PERSONALITY_INVALID 0xffffffffLU
+#endif
+
+unsigned long personality_from_string(const char *p);
+const char *personality_to_string(unsigned long);
+
+int safe_personality(unsigned long p);
+int opinionated_personality(unsigned long *ret);
+
+int ioprio_class_to_string_alloc(int i, char **s);
+int ioprio_class_from_string(const char *s);
+
+const char *sigchld_code_to_string(int i) _const_;
+int sigchld_code_from_string(const char *s) _pure_;
+
+int sched_policy_to_string_alloc(int i, char **s);
+int sched_policy_from_string(const char *s);
+
+static inline pid_t PTR_TO_PID(const void *p) {
+        return (pid_t) ((uintptr_t) p);
+}
+
+static inline void* PID_TO_PTR(pid_t pid) {
+        return (void*) ((uintptr_t) pid);
+}
+
+void valgrind_summary_hack(void);
+
+int pid_compare_func(const pid_t *a, const pid_t *b);
+
+#if 0 /* NM_IGNORED */
+static inline bool nice_is_valid(int n) {
+        return n >= PRIO_MIN && n < PRIO_MAX;
+}
+
+static inline bool sched_policy_is_valid(int i) {
+        return IN_SET(i, SCHED_OTHER, SCHED_BATCH, SCHED_IDLE, SCHED_FIFO, SCHED_RR);
+}
+
+static inline bool sched_priority_is_valid(int i) {
+        return i >= 0 && i <= sched_get_priority_max(SCHED_RR);
+}
+
+static inline bool ioprio_class_is_valid(int i) {
+        return IN_SET(i, IOPRIO_CLASS_NONE, IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE);
+}
+
+static inline bool ioprio_priority_is_valid(int i) {
+        return i >= 0 && i < IOPRIO_BE_NR;
+}
+
+static inline bool pid_is_valid(pid_t p) {
+        return p > 0;
+}
+#endif /* NM_IGNORED */
+
+int ioprio_parse_priority(const char *s, int *ret);
+
+pid_t getpid_cached(void);
+void reset_cached_pid(void);
+
+int must_be_root(void);
+
+typedef enum ForkFlags {
+        FORK_RESET_SIGNALS      = 1 << 0, /* Reset all signal handlers and signal mask */
+        FORK_CLOSE_ALL_FDS      = 1 << 1, /* Close all open file descriptors in the child, except for 0,1,2 */
+        FORK_DEATHSIG           = 1 << 2, /* Set PR_DEATHSIG in the child */
+        FORK_NULL_STDIO         = 1 << 3, /* Connect 0,1,2 to /dev/null */
+        FORK_REOPEN_LOG         = 1 << 4, /* Reopen log connection */
+        FORK_LOG                = 1 << 5, /* Log above LOG_DEBUG log level about failures */
+        FORK_WAIT               = 1 << 6, /* Wait until child exited */
+        FORK_NEW_MOUNTNS        = 1 << 7, /* Run child in its own mount namespace */
+        FORK_MOUNTNS_SLAVE      = 1 << 8, /* Make child's mount namespace MS_SLAVE */
+        FORK_RLIMIT_NOFILE_SAFE = 1 << 9, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */
+} ForkFlags;
+
+int safe_fork_full(const char *name, const int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid);
+
+static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) {
+        return safe_fork_full(name, NULL, 0, flags, ret_pid);
+}
+
+int namespace_fork(const char *outer_name, const char *inner_name, const int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd, pid_t *ret_pid);
+
+int fork_agent(const char *name, const int except[], size_t n_except, pid_t *pid, const char *path, ...) _sentinel_;
+
+int set_oom_score_adjust(int value);
+
+#if SIZEOF_PID_T == 4
+/* The highest possibly (theoretic) pid_t value on this architecture. */
+#define PID_T_MAX ((pid_t) INT32_MAX)
+/* The maximum number of concurrent processes Linux allows on this architecture, as well as the highest valid PID value
+ * the kernel will potentially assign. This reflects a value compiled into the kernel (PID_MAX_LIMIT), and sets the
+ * upper boundary on what may be written to the /proc/sys/kernel/pid_max sysctl (but do note that the sysctl is off by
+ * 1, since PID 0 can never exist and there can hence only be one process less than the limit would suggest). Since
+ * these values are documented in proc(5) we feel quite confident that they are stable enough for the near future at
+ * least to define them here too. */
+#define TASKS_MAX 4194303U
+#elif SIZEOF_PID_T == 2
+#define PID_T_MAX ((pid_t) INT16_MAX)
+#define TASKS_MAX 32767U
+#else
+#error "Unknown pid_t size"
+#endif
+
+assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX)
+
+/* Like TAKE_PTR() but for child PIDs, resetting them to 0 */
+#define TAKE_PID(pid)                           \
+        ({                                      \
+                pid_t _pid_ = (pid);            \
+                (pid) = 0;                      \
+                _pid_;                          \
+        })
diff --git a/shared/systemd/src/basic/random-util.c b/shared/systemd/src/basic/random-util.c
new file mode 100644
index 00000000..7c670e59
--- /dev/null
+++ b/shared/systemd/src/basic/random-util.c
@@ -0,0 +1,274 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#if defined(__i386__) || defined(__x86_64__)
+#include <cpuid.h>
+#endif
+
+#include <elf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#if HAVE_SYS_AUXV_H
+#  include <sys/auxv.h>
+#endif
+
+#if USE_SYS_RANDOM_H
+#  include <sys/random.h>
+#else
+#  include <linux/random.h>
+#endif
+
+#include "fd-util.h"
+#include "io-util.h"
+#include "missing.h"
+#include "random-util.h"
+#include "time-util.h"
+
+#if HAS_FEATURE_MEMORY_SANITIZER
+#include <sanitizer/msan_interface.h>
+#endif
+
+int rdrand(unsigned long *ret) {
+
+#if defined(__i386__) || defined(__x86_64__)
+        static int have_rdrand = -1;
+        unsigned char err;
+
+        if (have_rdrand < 0) {
+                uint32_t eax, ebx, ecx, edx;
+
+                /* Check if RDRAND is supported by the CPU */
+                if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0) {
+                        have_rdrand = false;
+                        return -EOPNOTSUPP;
+                }
+
+                have_rdrand = !!(ecx & (1U << 30));
+        }
+
+        if (have_rdrand == 0)
+                return -EOPNOTSUPP;
+
+        asm volatile("rdrand %0;"
+                     "setc %1"
+                     : "=r" (*ret),
+                       "=qm" (err));
+
+#if HAS_FEATURE_MEMORY_SANITIZER
+        __msan_unpoison(&err, sizeof(err));
+#endif
+
+        if (!err)
+                return -EAGAIN;
+
+        return 0;
+#else
+        return -EOPNOTSUPP;
+#endif
+}
+
+int genuine_random_bytes(void *p, size_t n, RandomFlags flags) {
+        static int have_syscall = -1;
+        _cleanup_close_ int fd = -1;
+        bool got_some = false;
+        int r;
+
+        /* Gathers some randomness from the kernel (or the CPU if the RANDOM_ALLOW_RDRAND flag is set). This call won't
+         * block, unless the RANDOM_BLOCK flag is set. If RANDOM_DONT_DRAIN is set, an error is returned if the random
+         * pool is not initialized. Otherwise it will always return some data from the kernel, regardless of whether
+         * the random pool is fully initialized or not. */
+
+        if (n == 0)
+                return 0;
+
+        if (FLAGS_SET(flags, RANDOM_ALLOW_RDRAND))
+                /* Try x86-64' RDRAND intrinsic if we have it. We only use it if high quality randomness is not
+                 * required, as we don't trust it (who does?). Note that we only do a single iteration of RDRAND here,
+                 * even though the Intel docs suggest calling this in a tight loop of 10 invocations or so. That's
+                 * because we don't really care about the quality here. We generally prefer using RDRAND if the caller
+                 * allows us too, since this way we won't drain the kernel randomness pool if we don't need it, as the
+                 * pool's entropy is scarce. */
+                for (;;) {
+                        unsigned long u;
+                        size_t m;
+
+                        if (rdrand(&u) < 0) {
+                                if (got_some && FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) {
+                                        /* Fill in the remaining bytes using pseudo-random values */
+                                        pseudo_random_bytes(p, n);
+                                        return 0;
+                                }
+
+                                /* OK, this didn't work, let's go to getrandom() + /dev/urandom instead */
+                                break;
+                        }
+
+                        m = MIN(sizeof(u), n);
+                        memcpy(p, &u, m);
+
+                        p = (uint8_t*) p + m;
+                        n -= m;
+
+                        if (n == 0)
+                                return 0; /* Yay, success! */
+
+                        got_some = true;
+                }
+
+        /* Use the getrandom() syscall unless we know we don't have it. */
+        if (have_syscall != 0 && !HAS_FEATURE_MEMORY_SANITIZER) {
+
+                for (;;) {
+#if !HAVE_GETRANDOM
+                        /* NetworkManager Note: systemd calls the syscall directly in this case. Don't add that workaround.
+                         * If you don't compile against a libc that provides getrandom(), you don't get it. */
+                        r = -1;
+                        errno = ENOSYS;
+#else
+                        r = getrandom(p, n, FLAGS_SET(flags, RANDOM_BLOCK) ? 0 : GRND_NONBLOCK);
+#endif
+                        if (r > 0) {
+                                have_syscall = true;
+
+                                if ((size_t) r == n)
+                                        return 0; /* Yay, success! */
+
+                                assert((size_t) r < n);
+                                p = (uint8_t*) p + r;
+                                n -= r;
+
+                                if (FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) {
+                                        /* Fill in the remaining bytes using pseudo-random values */
+                                        pseudo_random_bytes(p, n);
+                                        return 0;
+                                }
+
+                                got_some = true;
+
+                                /* Hmm, we didn't get enough good data but the caller insists on good data? Then try again */
+                                if (FLAGS_SET(flags, RANDOM_BLOCK))
+                                        continue;
+
+                                /* Fill in the rest with /dev/urandom */
+                                break;
+
+                        } else if (r == 0) {
+                                have_syscall = true;
+                                return -EIO;
+
+                        } else if (errno == ENOSYS) {
+                                /* We lack the syscall, continue with reading from /dev/urandom. */
+                                have_syscall = false;
+                                break;
+
+                        } else if (errno == EAGAIN) {
+                                /* The kernel has no entropy whatsoever. Let's remember to use the syscall the next
+                                 * time again though.
+                                 *
+                                 * If RANDOM_DONT_DRAIN is set, return an error so that random_bytes() can produce some
+                                 * pseudo-random bytes instead. Otherwise, fall back to /dev/urandom, which we know is empty,
+                                 * but the kernel will produce some bytes for us on a best-effort basis. */
+                                have_syscall = true;
+
+                                if (got_some && FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) {
+                                        /* Fill in the remaining bytes using pseudorandom values */
+                                        pseudo_random_bytes(p, n);
+                                        return 0;
+                                }
+
+                                if (FLAGS_SET(flags, RANDOM_DONT_DRAIN))
+                                        return -ENODATA;
+
+                                /* Use /dev/urandom instead */
+                                break;
+                        } else
+                                return -errno;
+                }
+        }
+
+        fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+        if (fd < 0)
+                return errno == ENOENT ? -ENOSYS : -errno;
+
+        return loop_read_exact(fd, p, n, true);
+}
+
+void initialize_srand(void) {
+        static bool srand_called = false;
+        unsigned x;
+#if HAVE_SYS_AUXV_H
+        const void *auxv;
+#endif
+        unsigned long k;
+
+        if (srand_called)
+                return;
+
+#if HAVE_SYS_AUXV_H
+        /* The kernel provides us with 16 bytes of entropy in auxv, so let's
+         * try to make use of that to seed the pseudo-random generator. It's
+         * better than nothing... */
+
+        auxv = (const void*) getauxval(AT_RANDOM);
+        if (auxv) {
+                assert_cc(sizeof(x) <= 16);
+                memcpy(&x, auxv, sizeof(x));
+        } else
+#endif
+                x = 0;
+
+        x ^= (unsigned) now(CLOCK_REALTIME);
+        x ^= (unsigned) gettid();
+
+        if (rdrand(&k) >= 0)
+                x ^= (unsigned) k;
+
+        srand(x);
+        srand_called = true;
+}
+
+/* INT_MAX gives us only 31 bits, so use 24 out of that. */
+#if RAND_MAX >= INT_MAX
+#  define RAND_STEP 3
+#else
+/* SHORT_INT_MAX or lower gives at most 15 bits, we just just 8 out of that. */
+#  define RAND_STEP 1
+#endif
+
+void pseudo_random_bytes(void *p, size_t n) {
+        uint8_t *q;
+
+        initialize_srand();
+
+        for (q = p; q < (uint8_t*) p + n; q += RAND_STEP) {
+                unsigned rr;
+
+                rr = (unsigned) rand();
+
+#if RAND_STEP >= 3
+                if ((size_t) (q - (uint8_t*) p + 2) < n)
+                        q[2] = rr >> 16;
+#endif
+#if RAND_STEP >= 2
+                if ((size_t) (q - (uint8_t*) p + 1) < n)
+                        q[1] = rr >> 8;
+#endif
+                q[0] = rr;
+        }
+}
+
+void random_bytes(void *p, size_t n) {
+
+        if (genuine_random_bytes(p, n, RANDOM_EXTEND_WITH_PSEUDO|RANDOM_DONT_DRAIN|RANDOM_ALLOW_RDRAND) >= 0)
+                return;
+
+        /* If for some reason some user made /dev/urandom unavailable to us, or the kernel has no entropy, use a PRNG instead. */
+        pseudo_random_bytes(p, n);
+}
diff --git a/shared/systemd/src/basic/random-util.h b/shared/systemd/src/basic/random-util.h
new file mode 100644
index 00000000..3e8c288d
--- /dev/null
+++ b/shared/systemd/src/basic/random-util.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+typedef enum RandomFlags {
+        RANDOM_EXTEND_WITH_PSEUDO = 1 << 0, /* If we can't get enough genuine randomness, but some, fill up the rest with pseudo-randomness */
+        RANDOM_BLOCK              = 1 << 1, /* Rather block than return crap randomness (only if the kernel supports that) */
+        RANDOM_DONT_DRAIN         = 1 << 2, /* If we can't get any randomness at all, return early with -EAGAIN */
+        RANDOM_ALLOW_RDRAND       = 1 << 3, /* Allow usage of the CPU RNG */
+} RandomFlags;
+
+int genuine_random_bytes(void *p, size_t n, RandomFlags flags); /* returns "genuine" randomness, optionally filled upwith pseudo random, if not enough is available */
+void pseudo_random_bytes(void *p, size_t n);                    /* returns only pseudo-randommess (but possibly seeded from something better) */
+void random_bytes(void *p, size_t n);                           /* returns genuine randomness if cheaply available, and pseudo randomness if not. */
+
+void initialize_srand(void);
+
+static inline uint64_t random_u64(void) {
+        uint64_t u;
+        random_bytes(&u, sizeof(u));
+        return u;
+}
+
+static inline uint32_t random_u32(void) {
+        uint32_t u;
+        random_bytes(&u, sizeof(u));
+        return u;
+}
+
+int rdrand(unsigned long *ret);
diff --git a/shared/systemd/src/basic/refcnt.h b/shared/systemd/src/basic/refcnt.h
new file mode 100644
index 00000000..40f9a84a
--- /dev/null
+++ b/shared/systemd/src/basic/refcnt.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+/* A type-safe atomic refcounter.
+ *
+ * DO NOT USE THIS UNLESS YOU ACTUALLY CARE ABOUT THREAD SAFETY! */
+
+typedef struct {
+        volatile unsigned _value;
+} RefCount;
+
+#define REFCNT_GET(r) ((r)._value)
+#define REFCNT_INC(r) (__sync_add_and_fetch(&(r)._value, 1))
+#define REFCNT_DEC(r) (__sync_sub_and_fetch(&(r)._value, 1))
+
+#define REFCNT_INIT ((RefCount) { ._value = 1 })
+
+#define _DEFINE_ATOMIC_REF_FUNC(type, name, scope)                    \
+        scope type *name##_ref(type *p) {                               \
+                if (!p)                                                 \
+                        return NULL;                                    \
+                                                                        \
+                assert_se(REFCNT_INC(p->n_ref) >= 2);                   \
+                return p;                                               \
+        }
+
+#define _DEFINE_ATOMIC_UNREF_FUNC(type, name, free_func, scope)       \
+        scope type *name##_unref(type *p) {                             \
+                if (!p)                                                 \
+                        return NULL;                                    \
+                                                                        \
+                if (REFCNT_DEC(p->n_ref) > 0)                           \
+                        return NULL;                                    \
+                                                                        \
+                return free_func(p);                                    \
+        }
+
+#define DEFINE_ATOMIC_REF_FUNC(type, name)    \
+        _DEFINE_ATOMIC_REF_FUNC(type, name,)
+#define DEFINE_PUBLIC_ATOMIC_REF_FUNC(type, name)     \
+        _DEFINE_ATOMIC_REF_FUNC(type, name, _public_)
+
+#define DEFINE_ATOMIC_UNREF_FUNC(type, name, free_func)       \
+        _DEFINE_ATOMIC_UNREF_FUNC(type, name, free_func,)
+#define DEFINE_PUBLIC_ATOMIC_UNREF_FUNC(type, name, free_func)        \
+        _DEFINE_ATOMIC_UNREF_FUNC(type, name, free_func, _public_)
+
+#define DEFINE_ATOMIC_REF_UNREF_FUNC(type, name, free_func)   \
+        DEFINE_ATOMIC_REF_FUNC(type, name);                   \
+        DEFINE_ATOMIC_UNREF_FUNC(type, name, free_func);
+
+#define DEFINE_PUBLIC_ATOMIC_REF_UNREF_FUNC(type, name, free_func)   \
+        DEFINE_PUBLIC_ATOMIC_REF_FUNC(type, name);                   \
+        DEFINE_PUBLIC_ATOMIC_UNREF_FUNC(type, name, free_func);
diff --git a/shared/systemd/src/basic/set.h b/shared/systemd/src/basic/set.h
new file mode 100644
index 00000000..2a80632b
--- /dev/null
+++ b/shared/systemd/src/basic/set.h
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "extract-word.h"
+#include "hashmap.h"
+#include "macro.h"
+
+Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS);
+#define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS)
+
+static inline Set *set_free(Set *s) {
+        return (Set*) internal_hashmap_free(HASHMAP_BASE(s), NULL, NULL);
+}
+
+static inline Set *set_free_free(Set *s) {
+        return (Set*) internal_hashmap_free(HASHMAP_BASE(s), free, NULL);
+}
+
+/* no set_free_free_free */
+
+static inline Set *set_copy(Set *s) {
+        return (Set*) internal_hashmap_copy(HASHMAP_BASE(s));
+}
+
+int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS);
+#define set_ensure_allocated(h, ops) internal_set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS)
+
+int set_put(Set *s, const void *key);
+/* no set_update */
+/* no set_replace */
+static inline void *set_get(Set *s, void *key) {
+        return internal_hashmap_get(HASHMAP_BASE(s), key);
+}
+/* no set_get2 */
+
+static inline bool set_contains(Set *s, const void *key) {
+        return internal_hashmap_contains(HASHMAP_BASE(s), key);
+}
+
+static inline void *set_remove(Set *s, const void *key) {
+        return internal_hashmap_remove(HASHMAP_BASE(s), key);
+}
+
+/* no set_remove2 */
+/* no set_remove_value */
+int set_remove_and_put(Set *s, const void *old_key, const void *new_key);
+/* no set_remove_and_replace */
+int set_merge(Set *s, Set *other);
+
+static inline int set_reserve(Set *h, unsigned entries_add) {
+        return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add);
+}
+
+static inline int set_move(Set *s, Set *other) {
+        return internal_hashmap_move(HASHMAP_BASE(s), HASHMAP_BASE(other));
+}
+
+static inline int set_move_one(Set *s, Set *other, const void *key) {
+        return internal_hashmap_move_one(HASHMAP_BASE(s), HASHMAP_BASE(other), key);
+}
+
+static inline unsigned set_size(Set *s) {
+        return internal_hashmap_size(HASHMAP_BASE(s));
+}
+
+static inline bool set_isempty(Set *s) {
+        return set_size(s) == 0;
+}
+
+static inline unsigned set_buckets(Set *s) {
+        return internal_hashmap_buckets(HASHMAP_BASE(s));
+}
+
+bool set_iterate(Set *s, Iterator *i, void **value);
+
+static inline void set_clear(Set *s) {
+        internal_hashmap_clear(HASHMAP_BASE(s), NULL, NULL);
+}
+
+static inline void set_clear_free(Set *s) {
+        internal_hashmap_clear(HASHMAP_BASE(s), free, NULL);
+}
+
+/* no set_clear_free_free */
+
+static inline void *set_steal_first(Set *s) {
+        return internal_hashmap_first_key_and_value(HASHMAP_BASE(s), true, NULL);
+}
+
+#define set_clear_with_destructor(_s, _f)               \
+        ({                                              \
+                void *_item;                            \
+                while ((_item = set_steal_first(_s)))   \
+                        _f(_item);                      \
+        })
+#define set_free_with_destructor(_s, _f)                \
+        ({                                              \
+                set_clear_with_destructor(_s, _f);      \
+                set_free(_s);                           \
+        })
+
+/* no set_steal_first_key */
+/* no set_first_key */
+
+static inline void *set_first(Set *s) {
+        return internal_hashmap_first_key_and_value(HASHMAP_BASE(s), false, NULL);
+}
+
+/* no set_next */
+
+static inline char **set_get_strv(Set *s) {
+        return internal_hashmap_get_strv(HASHMAP_BASE(s));
+}
+
+int set_consume(Set *s, void *value);
+int set_put_strdup(Set *s, const char *p);
+int set_put_strdupv(Set *s, char **l);
+int set_put_strsplit(Set *s, const char *v, const char *separators, ExtractFlags flags);
+
+#define SET_FOREACH(e, s, i) \
+        for ((i) = ITERATOR_FIRST; set_iterate((s), &(i), (void**)&(e)); )
+
+#define SET_FOREACH_MOVE(e, d, s)                                       \
+        for (; ({ e = set_first(s); assert_se(!e || set_move_one(d, s, e) >= 0); e; }); )
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free_free);
+
+#define _cleanup_set_free_ _cleanup_(set_freep)
+#define _cleanup_set_free_free_ _cleanup_(set_free_freep)
diff --git a/shared/systemd/src/basic/signal-util.h b/shared/systemd/src/basic/signal-util.h
new file mode 100644
index 00000000..92f2804c
--- /dev/null
+++ b/shared/systemd/src/basic/signal-util.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <signal.h>
+
+#include "macro.h"
+
+int reset_all_signal_handlers(void);
+int reset_signal_mask(void);
+
+int ignore_signals(int sig, ...);
+int default_signals(int sig, ...);
+int sigaction_many(const struct sigaction *sa, ...);
+
+int sigset_add_many(sigset_t *ss, ...);
+int sigprocmask_many(int how, sigset_t *old, ...);
+
+const char *signal_to_string(int i) _const_;
+int signal_from_string(const char *s) _pure_;
+
+void nop_signal_handler(int sig);
+
+static inline void block_signals_reset(sigset_t *ss) {
+        assert_se(sigprocmask(SIG_SETMASK, ss, NULL) >= 0);
+}
+
+#define BLOCK_SIGNALS(...)                                                         \
+        _cleanup_(block_signals_reset) _unused_ sigset_t _saved_sigset = ({        \
+                sigset_t _t;                                                       \
+                assert_se(sigprocmask_many(SIG_BLOCK, &_t, __VA_ARGS__, -1) >= 0); \
+                _t;                                                                \
+        })
+
+static inline bool SIGNAL_VALID(int signo) {
+        return signo > 0 && signo < _NSIG;
+}
+
+static inline const char* signal_to_string_with_check(int n) {
+        if (!SIGNAL_VALID(n))
+                return NULL;
+
+        return signal_to_string(n);
+}
diff --git a/shared/systemd/src/basic/siphash24.h b/shared/systemd/src/basic/siphash24.h
new file mode 100644
index 00000000..be1d3e00
--- /dev/null
+++ b/shared/systemd/src/basic/siphash24.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+
+#if 0 /* NM_IGNORED */
+struct siphash {
+        uint64_t v0;
+        uint64_t v1;
+        uint64_t v2;
+        uint64_t v3;
+        uint64_t padding;
+        size_t inlen;
+};
+#else /* NM_IGNORED */
+struct siphash {
+	CSipHash _csiphash;
+};
+
+static inline void
+siphash24_init (struct siphash *state, const uint8_t k[16])
+{
+	c_siphash_init ((CSipHash *) state, k);
+}
+
+static inline void
+siphash24_compress (const void *in, size_t inlen, struct siphash *state)
+{
+	c_siphash_append ((CSipHash *) state, in, inlen);
+}
+
+static inline uint64_t
+siphash24_finalize (struct siphash *state)
+{
+	return c_siphash_finalize ((CSipHash *) state);
+}
+
+static inline uint64_t
+siphash24 (const void *in, size_t inlen, const uint8_t k[16])
+{
+	return c_siphash_hash (k, in, inlen);
+}
+#endif /* NM_IGNORED */
+
+void siphash24_init(struct siphash *state, const uint8_t k[static 16]);
+void siphash24_compress(const void *in, size_t inlen, struct siphash *state);
+#define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state))
+
+uint64_t siphash24_finalize(struct siphash *state);
+
+uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[static 16]);
+
+static inline uint64_t siphash24_string(const char *s, const uint8_t k[static 16]) {
+        return siphash24(s, strlen(s) + 1, k);
+}
diff --git a/shared/systemd/src/basic/socket-util.c b/shared/systemd/src/basic/socket-util.c
new file mode 100644
index 00000000..68a62f85
--- /dev/null
+++ b/shared/systemd/src/basic/socket-util.c
@@ -0,0 +1,1353 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <limits.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/ip.h>
+#include <poll.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "socket-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "user-util.h"
+#include "utf8.h"
+#include "util.h"
+
+#if 0 /* NM_IGNORED */
+#if ENABLE_IDN
+#  define IDN_FLAGS NI_IDN
+#else
+#  define IDN_FLAGS 0
+#endif
+
+static const char* const socket_address_type_table[] = {
+        [SOCK_STREAM] = "Stream",
+        [SOCK_DGRAM] = "Datagram",
+        [SOCK_RAW] = "Raw",
+        [SOCK_RDM] = "ReliableDatagram",
+        [SOCK_SEQPACKET] = "SequentialPacket",
+        [SOCK_DCCP] = "DatagramCongestionControl",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(socket_address_type, int);
+
+int socket_address_parse(SocketAddress *a, const char *s) {
+        _cleanup_free_ char *n = NULL;
+        char *e;
+        int r;
+
+        assert(a);
+        assert(s);
+
+        *a = (SocketAddress) {
+                .type = SOCK_STREAM,
+        };
+
+        if (*s == '[') {
+                uint16_t port;
+
+                /* IPv6 in [x:.....:z]:p notation */
+
+                e = strchr(s+1, ']');
+                if (!e)
+                        return -EINVAL;
+
+                n = strndup(s+1, e-s-1);
+                if (!n)
+                        return -ENOMEM;
+
+                errno = 0;
+                if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0)
+                        return errno > 0 ? -errno : -EINVAL;
+
+                e++;
+                if (*e != ':')
+                        return -EINVAL;
+
+                e++;
+                r = parse_ip_port(e, &port);
+                if (r < 0)
+                        return r;
+
+                a->sockaddr.in6.sin6_family = AF_INET6;
+                a->sockaddr.in6.sin6_port = htobe16(port);
+                a->size = sizeof(struct sockaddr_in6);
+
+        } else if (*s == '/') {
+                /* AF_UNIX socket */
+
+                size_t l;
+
+                l = strlen(s);
+                if (l >= sizeof(a->sockaddr.un.sun_path)) /* Note that we refuse non-NUL-terminated sockets when
+                                                           * parsing (the kernel itself is less strict here in what it
+                                                           * accepts) */
+                        return -EINVAL;
+
+                a->sockaddr.un.sun_family = AF_UNIX;
+                memcpy(a->sockaddr.un.sun_path, s, l);
+                a->size = offsetof(struct sockaddr_un, sun_path) + l + 1;
+
+        } else if (*s == '@') {
+                /* Abstract AF_UNIX socket */
+                size_t l;
+
+                l = strlen(s+1);
+                if (l >= sizeof(a->sockaddr.un.sun_path) - 1) /* Note that we refuse non-NUL-terminated sockets here
+                                                               * when parsing, even though abstract namespace sockets
+                                                               * explicitly allow embedded NUL bytes and don't consider
+                                                               * them special. But it's simply annoying to debug such
+                                                               * sockets. */
+                        return -EINVAL;
+
+                a->sockaddr.un.sun_family = AF_UNIX;
+                memcpy(a->sockaddr.un.sun_path+1, s+1, l);
+                a->size = offsetof(struct sockaddr_un, sun_path) + 1 + l;
+
+        } else if (startswith(s, "vsock:")) {
+                /* AF_VSOCK socket in vsock:cid:port notation */
+                const char *cid_start = s + STRLEN("vsock:");
+                unsigned port;
+
+                e = strchr(cid_start, ':');
+                if (!e)
+                        return -EINVAL;
+
+                r = safe_atou(e+1, &port);
+                if (r < 0)
+                        return r;
+
+                n = strndup(cid_start, e - cid_start);
+                if (!n)
+                        return -ENOMEM;
+
+                if (!isempty(n)) {
+                        r = safe_atou(n, &a->sockaddr.vm.svm_cid);
+                        if (r < 0)
+                                return r;
+                } else
+                        a->sockaddr.vm.svm_cid = VMADDR_CID_ANY;
+
+                a->sockaddr.vm.svm_family = AF_VSOCK;
+                a->sockaddr.vm.svm_port = port;
+                a->size = sizeof(struct sockaddr_vm);
+
+        } else {
+                uint16_t port;
+
+                e = strchr(s, ':');
+                if (e) {
+                        r = parse_ip_port(e + 1, &port);
+                        if (r < 0)
+                                return r;
+
+                        n = strndup(s, e-s);
+                        if (!n)
+                                return -ENOMEM;
+
+                        /* IPv4 in w.x.y.z:p notation? */
+                        r = inet_pton(AF_INET, n, &a->sockaddr.in.sin_addr);
+                        if (r < 0)
+                                return -errno;
+
+                        if (r > 0) {
+                                /* Gotcha, it's a traditional IPv4 address */
+                                a->sockaddr.in.sin_family = AF_INET;
+                                a->sockaddr.in.sin_port = htobe16(port);
+                                a->size = sizeof(struct sockaddr_in);
+                        } else {
+                                unsigned idx;
+
+                                if (strlen(n) > IF_NAMESIZE-1)
+                                        return -EINVAL;
+
+                                /* Uh, our last resort, an interface name */
+                                idx = if_nametoindex(n);
+                                if (idx == 0)
+                                        return -EINVAL;
+
+                                a->sockaddr.in6.sin6_family = AF_INET6;
+                                a->sockaddr.in6.sin6_port = htobe16(port);
+                                a->sockaddr.in6.sin6_scope_id = idx;
+                                a->sockaddr.in6.sin6_addr = in6addr_any;
+                                a->size = sizeof(struct sockaddr_in6);
+                        }
+                } else {
+
+                        /* Just a port */
+                        r = parse_ip_port(s, &port);
+                        if (r < 0)
+                                return r;
+
+                        if (socket_ipv6_is_supported()) {
+                                a->sockaddr.in6.sin6_family = AF_INET6;
+                                a->sockaddr.in6.sin6_port = htobe16(port);
+                                a->sockaddr.in6.sin6_addr = in6addr_any;
+                                a->size = sizeof(struct sockaddr_in6);
+                        } else {
+                                a->sockaddr.in.sin_family = AF_INET;
+                                a->sockaddr.in.sin_port = htobe16(port);
+                                a->sockaddr.in.sin_addr.s_addr = INADDR_ANY;
+                                a->size = sizeof(struct sockaddr_in);
+                        }
+                }
+        }
+
+        return 0;
+}
+
+int socket_address_parse_and_warn(SocketAddress *a, const char *s) {
+        SocketAddress b;
+        int r;
+
+        /* Similar to socket_address_parse() but warns for IPv6 sockets when we don't support them. */
+
+        r = socket_address_parse(&b, s);
+        if (r < 0)
+                return r;
+
+        if (!socket_ipv6_is_supported() && b.sockaddr.sa.sa_family == AF_INET6) {
+                log_warning("Binding to IPv6 address not available since kernel does not support IPv6.");
+                return -EAFNOSUPPORT;
+        }
+
+        *a = b;
+        return 0;
+}
+
+int socket_address_parse_netlink(SocketAddress *a, const char *s) {
+        int family;
+        unsigned group = 0;
+        _cleanup_free_ char *sfamily = NULL;
+        assert(a);
+        assert(s);
+
+        zero(*a);
+        a->type = SOCK_RAW;
+
+        errno = 0;
+        if (sscanf(s, "%ms %u", &sfamily, &group) < 1)
+                return errno > 0 ? -errno : -EINVAL;
+
+        family = netlink_family_from_string(sfamily);
+        if (family < 0)
+                return -EINVAL;
+
+        a->sockaddr.nl.nl_family = AF_NETLINK;
+        a->sockaddr.nl.nl_groups = group;
+
+        a->type = SOCK_RAW;
+        a->size = sizeof(struct sockaddr_nl);
+        a->protocol = family;
+
+        return 0;
+}
+
+int socket_address_verify(const SocketAddress *a, bool strict) {
+        assert(a);
+
+        /* With 'strict' we enforce additional sanity constraints which are not set by the standard,
+         * but should only apply to sockets we create ourselves. */
+
+        switch (socket_address_family(a)) {
+
+        case AF_INET:
+                if (a->size != sizeof(struct sockaddr_in))
+                        return -EINVAL;
+
+                if (a->sockaddr.in.sin_port == 0)
+                        return -EINVAL;
+
+                if (!IN_SET(a->type, SOCK_STREAM, SOCK_DGRAM))
+                        return -EINVAL;
+
+                return 0;
+
+        case AF_INET6:
+                if (a->size != sizeof(struct sockaddr_in6))
+                        return -EINVAL;
+
+                if (a->sockaddr.in6.sin6_port == 0)
+                        return -EINVAL;
+
+                if (!IN_SET(a->type, SOCK_STREAM, SOCK_DGRAM))
+                        return -EINVAL;
+
+                return 0;
+
+        case AF_UNIX:
+                if (a->size < offsetof(struct sockaddr_un, sun_path))
+                        return -EINVAL;
+                if (a->size > sizeof(struct sockaddr_un) + !strict)
+                        /* If !strict, allow one extra byte, since getsockname() on Linux will append
+                         * a NUL byte if we have path sockets that are above sun_path's full size. */
+                        return -EINVAL;
+
+                if (a->size > offsetof(struct sockaddr_un, sun_path) &&
+                    a->sockaddr.un.sun_path[0] != 0 &&
+                    strict) {
+                        /* Only validate file system sockets here, and only in strict mode */
+                        const char *e;
+
+                        e = memchr(a->sockaddr.un.sun_path, 0, sizeof(a->sockaddr.un.sun_path));
+                        if (e) {
+                                /* If there's an embedded NUL byte, make sure the size of the socket address matches it */
+                                if (a->size != offsetof(struct sockaddr_un, sun_path) + (e - a->sockaddr.un.sun_path) + 1)
+                                        return -EINVAL;
+                        } else {
+                                /* If there's no embedded NUL byte, then then the size needs to match the whole
+                                 * structure or the structure with one extra NUL byte suffixed. (Yeah, Linux is awful,
+                                 * and considers both equivalent: getsockname() even extends sockaddr_un beyond its
+                                 * size if the path is non NUL terminated.)*/
+                                if (!IN_SET(a->size, sizeof(a->sockaddr.un.sun_path), sizeof(a->sockaddr.un.sun_path)+1))
+                                        return -EINVAL;
+                        }
+                }
+
+                if (!IN_SET(a->type, SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET))
+                        return -EINVAL;
+
+                return 0;
+
+        case AF_NETLINK:
+
+                if (a->size != sizeof(struct sockaddr_nl))
+                        return -EINVAL;
+
+                if (!IN_SET(a->type, SOCK_RAW, SOCK_DGRAM))
+                        return -EINVAL;
+
+                return 0;
+
+        case AF_VSOCK:
+                if (a->size != sizeof(struct sockaddr_vm))
+                        return -EINVAL;
+
+                if (!IN_SET(a->type, SOCK_STREAM, SOCK_DGRAM))
+                        return -EINVAL;
+
+                return 0;
+
+        default:
+                return -EAFNOSUPPORT;
+        }
+}
+
+int socket_address_print(const SocketAddress *a, char **ret) {
+        int r;
+
+        assert(a);
+        assert(ret);
+
+        r = socket_address_verify(a, false); /* We do non-strict validation, because we want to be
+                                              * able to pretty-print any socket the kernel considers
+                                              * valid. We still need to do validation to know if we
+                                              * can meaningfully print the address. */
+        if (r < 0)
+                return r;
+
+        if (socket_address_family(a) == AF_NETLINK) {
+                _cleanup_free_ char *sfamily = NULL;
+
+                r = netlink_family_to_string_alloc(a->protocol, &sfamily);
+                if (r < 0)
+                        return r;
+
+                r = asprintf(ret, "%s %u", sfamily, a->sockaddr.nl.nl_groups);
+                if (r < 0)
+                        return -ENOMEM;
+
+                return 0;
+        }
+
+        return sockaddr_pretty(&a->sockaddr.sa, a->size, false, true, ret);
+}
+
+bool socket_address_can_accept(const SocketAddress *a) {
+        assert(a);
+
+        return
+                IN_SET(a->type, SOCK_STREAM, SOCK_SEQPACKET);
+}
+
+bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) {
+        assert(a);
+        assert(b);
+
+        /* Invalid addresses are unequal to all */
+        if (socket_address_verify(a, false) < 0 ||
+            socket_address_verify(b, false) < 0)
+                return false;
+
+        if (a->type != b->type)
+                return false;
+
+        if (socket_address_family(a) != socket_address_family(b))
+                return false;
+
+        switch (socket_address_family(a)) {
+
+        case AF_INET:
+                if (a->sockaddr.in.sin_addr.s_addr != b->sockaddr.in.sin_addr.s_addr)
+                        return false;
+
+                if (a->sockaddr.in.sin_port != b->sockaddr.in.sin_port)
+                        return false;
+
+                break;
+
+        case AF_INET6:
+                if (memcmp(&a->sockaddr.in6.sin6_addr, &b->sockaddr.in6.sin6_addr, sizeof(a->sockaddr.in6.sin6_addr)) != 0)
+                        return false;
+
+                if (a->sockaddr.in6.sin6_port != b->sockaddr.in6.sin6_port)
+                        return false;
+
+                break;
+
+        case AF_UNIX:
+                if (a->size <= offsetof(struct sockaddr_un, sun_path) ||
+                    b->size <= offsetof(struct sockaddr_un, sun_path))
+                        return false;
+
+                if ((a->sockaddr.un.sun_path[0] == 0) != (b->sockaddr.un.sun_path[0] == 0))
+                        return false;
+
+                if (a->sockaddr.un.sun_path[0]) {
+                        if (!path_equal_or_files_same(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, 0))
+                                return false;
+                } else {
+                        if (a->size != b->size)
+                                return false;
+
+                        if (memcmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, a->size) != 0)
+                                return false;
+                }
+
+                break;
+
+        case AF_NETLINK:
+                if (a->protocol != b->protocol)
+                        return false;
+
+                if (a->sockaddr.nl.nl_groups != b->sockaddr.nl.nl_groups)
+                        return false;
+
+                break;
+
+        case AF_VSOCK:
+                if (a->sockaddr.vm.svm_cid != b->sockaddr.vm.svm_cid)
+                        return false;
+
+                if (a->sockaddr.vm.svm_port != b->sockaddr.vm.svm_port)
+                        return false;
+
+                break;
+
+        default:
+                /* Cannot compare, so we assume the addresses are different */
+                return false;
+        }
+
+        return true;
+}
+
+bool socket_address_is(const SocketAddress *a, const char *s, int type) {
+        struct SocketAddress b;
+
+        assert(a);
+        assert(s);
+
+        if (socket_address_parse(&b, s) < 0)
+                return false;
+
+        b.type = type;
+
+        return socket_address_equal(a, &b);
+}
+
+bool socket_address_is_netlink(const SocketAddress *a, const char *s) {
+        struct SocketAddress b;
+
+        assert(a);
+        assert(s);
+
+        if (socket_address_parse_netlink(&b, s) < 0)
+                return false;
+
+        return socket_address_equal(a, &b);
+}
+
+const char* socket_address_get_path(const SocketAddress *a) {
+        assert(a);
+
+        if (socket_address_family(a) != AF_UNIX)
+                return NULL;
+
+        if (a->sockaddr.un.sun_path[0] == 0)
+                return NULL;
+
+        /* Note that this is only safe because we know that there's an extra NUL byte after the sockaddr_un
+         * structure. On Linux AF_UNIX file system socket addresses don't have to be NUL terminated if they take up the
+         * full sun_path space. */
+        assert_cc(sizeof(union sockaddr_union) >= sizeof(struct sockaddr_un)+1);
+        return a->sockaddr.un.sun_path;
+}
+
+bool socket_ipv6_is_supported(void) {
+        if (access("/proc/net/if_inet6", F_OK) != 0)
+                return false;
+
+        return true;
+}
+
+bool socket_address_matches_fd(const SocketAddress *a, int fd) {
+        SocketAddress b;
+        socklen_t solen;
+
+        assert(a);
+        assert(fd >= 0);
+
+        b.size = sizeof(b.sockaddr);
+        if (getsockname(fd, &b.sockaddr.sa, &b.size) < 0)
+                return false;
+
+        if (b.sockaddr.sa.sa_family != a->sockaddr.sa.sa_family)
+                return false;
+
+        solen = sizeof(b.type);
+        if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &b.type, &solen) < 0)
+                return false;
+
+        if (b.type != a->type)
+                return false;
+
+        if (a->protocol != 0)  {
+                solen = sizeof(b.protocol);
+                if (getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &b.protocol, &solen) < 0)
+                        return false;
+
+                if (b.protocol != a->protocol)
+                        return false;
+        }
+
+        return socket_address_equal(a, &b);
+}
+
+int sockaddr_port(const struct sockaddr *_sa, unsigned *ret_port) {
+        union sockaddr_union *sa = (union sockaddr_union*) _sa;
+
+        /* Note, this returns the port as 'unsigned' rather than 'uint16_t', as AF_VSOCK knows larger ports */
+
+        assert(sa);
+
+        switch (sa->sa.sa_family) {
+
+        case AF_INET:
+                *ret_port = be16toh(sa->in.sin_port);
+                return 0;
+
+        case AF_INET6:
+                *ret_port = be16toh(sa->in6.sin6_port);
+                return 0;
+
+        case AF_VSOCK:
+                *ret_port = sa->vm.svm_port;
+                return 0;
+
+        default:
+                return -EAFNOSUPPORT;
+        }
+}
+
+int sockaddr_pretty(
+                const struct sockaddr *_sa,
+                socklen_t salen,
+                bool translate_ipv6,
+                bool include_port,
+                char **ret) {
+
+        union sockaddr_union *sa = (union sockaddr_union*) _sa;
+        char *p;
+        int r;
+
+        assert(sa);
+        assert(salen >= sizeof(sa->sa.sa_family));
+
+        switch (sa->sa.sa_family) {
+
+        case AF_INET: {
+                uint32_t a;
+
+                a = be32toh(sa->in.sin_addr.s_addr);
+
+                if (include_port)
+                        r = asprintf(&p,
+                                     "%u.%u.%u.%u:%u",
+                                     a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF,
+                                     be16toh(sa->in.sin_port));
+                else
+                        r = asprintf(&p,
+                                     "%u.%u.%u.%u",
+                                     a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF);
+                if (r < 0)
+                        return -ENOMEM;
+                break;
+        }
+
+        case AF_INET6: {
+                static const unsigned char ipv4_prefix[] = {
+                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF
+                };
+
+                if (translate_ipv6 &&
+                    memcmp(&sa->in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0) {
+                        const uint8_t *a = sa->in6.sin6_addr.s6_addr+12;
+                        if (include_port)
+                                r = asprintf(&p,
+                                             "%u.%u.%u.%u:%u",
+                                             a[0], a[1], a[2], a[3],
+                                             be16toh(sa->in6.sin6_port));
+                        else
+                                r = asprintf(&p,
+                                             "%u.%u.%u.%u",
+                                             a[0], a[1], a[2], a[3]);
+                        if (r < 0)
+                                return -ENOMEM;
+                } else {
+                        char a[INET6_ADDRSTRLEN];
+
+                        inet_ntop(AF_INET6, &sa->in6.sin6_addr, a, sizeof(a));
+
+                        if (include_port) {
+                                r = asprintf(&p,
+                                             "[%s]:%u",
+                                             a,
+                                             be16toh(sa->in6.sin6_port));
+                                if (r < 0)
+                                        return -ENOMEM;
+                        } else {
+                                p = strdup(a);
+                                if (!p)
+                                        return -ENOMEM;
+                        }
+                }
+
+                break;
+        }
+
+        case AF_UNIX:
+                if (salen <= offsetof(struct sockaddr_un, sun_path) ||
+                    (sa->un.sun_path[0] == 0 && salen == offsetof(struct sockaddr_un, sun_path) + 1))
+                        /* The name must have at least one character (and the leading NUL does not count) */
+                        p = strdup("<unnamed>");
+                else {
+                        /* Note that we calculate the path pointer here through the .un_buffer[] field, in order to
+                         * outtrick bounds checking tools such as ubsan, which are too smart for their own good: on
+                         * Linux the kernel may return sun_path[] data one byte longer than the declared size of the
+                         * field. */
+                        char *path = (char*) sa->un_buffer + offsetof(struct sockaddr_un, sun_path);
+                        size_t path_len = salen - offsetof(struct sockaddr_un, sun_path);
+
+                        if (path[0] == 0) {
+                                /* Abstract socket. When parsing address information from, we
+                                 * explicitly reject overly long paths and paths with embedded NULs.
+                                 * But we might get such a socket from the outside. Let's return
+                                 * something meaningful and printable in this case. */
+
+                                _cleanup_free_ char *e = NULL;
+
+                                e = cescape_length(path + 1, path_len - 1);
+                                if (!e)
+                                        return -ENOMEM;
+
+                                p = strjoin("@", e);
+                        } else {
+                                if (path[path_len - 1] == '\0')
+                                        /* We expect a terminating NUL and don't print it */
+                                        path_len --;
+
+                                p = cescape_length(path, path_len);
+                        }
+                }
+                if (!p)
+                        return -ENOMEM;
+
+                break;
+
+        case AF_VSOCK:
+                if (include_port) {
+                        if (sa->vm.svm_cid == VMADDR_CID_ANY)
+                                r = asprintf(&p, "vsock::%u", sa->vm.svm_port);
+                        else
+                                r = asprintf(&p, "vsock:%u:%u", sa->vm.svm_cid, sa->vm.svm_port);
+                } else
+                        r = asprintf(&p, "vsock:%u", sa->vm.svm_cid);
+                if (r < 0)
+                        return -ENOMEM;
+                break;
+
+        default:
+                return -EOPNOTSUPP;
+        }
+
+        *ret = p;
+        return 0;
+}
+
+int getpeername_pretty(int fd, bool include_port, char **ret) {
+        union sockaddr_union sa;
+        socklen_t salen = sizeof(sa);
+        int r;
+
+        assert(fd >= 0);
+        assert(ret);
+
+        if (getpeername(fd, &sa.sa, &salen) < 0)
+                return -errno;
+
+        if (sa.sa.sa_family == AF_UNIX) {
+                struct ucred ucred = {};
+
+                /* UNIX connection sockets are anonymous, so let's use
+                 * PID/UID as pretty credentials instead */
+
+                r = getpeercred(fd, &ucred);
+                if (r < 0)
+                        return r;
+
+                if (asprintf(ret, "PID "PID_FMT"/UID "UID_FMT, ucred.pid, ucred.uid) < 0)
+                        return -ENOMEM;
+
+                return 0;
+        }
+
+        /* For remote sockets we translate IPv6 addresses back to IPv4
+         * if applicable, since that's nicer. */
+
+        return sockaddr_pretty(&sa.sa, salen, true, include_port, ret);
+}
+
+int getsockname_pretty(int fd, char **ret) {
+        union sockaddr_union sa;
+        socklen_t salen = sizeof(sa);
+
+        assert(fd >= 0);
+        assert(ret);
+
+        if (getsockname(fd, &sa.sa, &salen) < 0)
+                return -errno;
+
+        /* For local sockets we do not translate IPv6 addresses back
+         * to IPv6 if applicable, since this is usually used for
+         * listening sockets where the difference between IPv4 and
+         * IPv6 matters. */
+
+        return sockaddr_pretty(&sa.sa, salen, false, true, ret);
+}
+
+int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret) {
+        int r;
+        char host[NI_MAXHOST], *ret;
+
+        assert(_ret);
+
+        r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0, IDN_FLAGS);
+        if (r != 0) {
+                int saved_errno = errno;
+
+                r = sockaddr_pretty(&sa->sa, salen, true, true, &ret);
+                if (r < 0)
+                        return r;
+
+                log_debug_errno(saved_errno, "getnameinfo(%s) failed: %m", ret);
+        } else {
+                ret = strdup(host);
+                if (!ret)
+                        return -ENOMEM;
+        }
+
+        *_ret = ret;
+        return 0;
+}
+
+static const char* const netlink_family_table[] = {
+        [NETLINK_ROUTE] = "route",
+        [NETLINK_FIREWALL] = "firewall",
+        [NETLINK_INET_DIAG] = "inet-diag",
+        [NETLINK_NFLOG] = "nflog",
+        [NETLINK_XFRM] = "xfrm",
+        [NETLINK_SELINUX] = "selinux",
+        [NETLINK_ISCSI] = "iscsi",
+        [NETLINK_AUDIT] = "audit",
+        [NETLINK_FIB_LOOKUP] = "fib-lookup",
+        [NETLINK_CONNECTOR] = "connector",
+        [NETLINK_NETFILTER] = "netfilter",
+        [NETLINK_IP6_FW] = "ip6-fw",
+        [NETLINK_DNRTMSG] = "dnrtmsg",
+        [NETLINK_KOBJECT_UEVENT] = "kobject-uevent",
+        [NETLINK_GENERIC] = "generic",
+        [NETLINK_SCSITRANSPORT] = "scsitransport",
+        [NETLINK_ECRYPTFS] = "ecryptfs",
+        [NETLINK_RDMA] = "rdma",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(netlink_family, int, INT_MAX);
+
+static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX] = {
+        [SOCKET_ADDRESS_DEFAULT] = "default",
+        [SOCKET_ADDRESS_BOTH] = "both",
+        [SOCKET_ADDRESS_IPV6_ONLY] = "ipv6-only"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(socket_address_bind_ipv6_only, SocketAddressBindIPv6Only);
+
+SocketAddressBindIPv6Only socket_address_bind_ipv6_only_or_bool_from_string(const char *n) {
+        int r;
+
+        r = parse_boolean(n);
+        if (r > 0)
+                return SOCKET_ADDRESS_IPV6_ONLY;
+        if (r == 0)
+                return SOCKET_ADDRESS_BOTH;
+
+        return socket_address_bind_ipv6_only_from_string(n);
+}
+
+bool sockaddr_equal(const union sockaddr_union *a, const union sockaddr_union *b) {
+        assert(a);
+        assert(b);
+
+        if (a->sa.sa_family != b->sa.sa_family)
+                return false;
+
+        if (a->sa.sa_family == AF_INET)
+                return a->in.sin_addr.s_addr == b->in.sin_addr.s_addr;
+
+        if (a->sa.sa_family == AF_INET6)
+                return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr)) == 0;
+
+        if (a->sa.sa_family == AF_VSOCK)
+                return a->vm.svm_cid == b->vm.svm_cid;
+
+        return false;
+}
+
+int fd_inc_sndbuf(int fd, size_t n) {
+        int r, value;
+        socklen_t l = sizeof(value);
+
+        r = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &l);
+        if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2)
+                return 0;
+
+        /* If we have the privileges we will ignore the kernel limit. */
+
+        if (setsockopt_int(fd, SOL_SOCKET, SO_SNDBUF, n) < 0) {
+                r = setsockopt_int(fd, SOL_SOCKET, SO_SNDBUFFORCE, n);
+                if (r < 0)
+                        return r;
+        }
+
+        return 1;
+}
+
+int fd_inc_rcvbuf(int fd, size_t n) {
+        int r, value;
+        socklen_t l = sizeof(value);
+
+        r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &l);
+        if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2)
+                return 0;
+
+        /* If we have the privileges we will ignore the kernel limit. */
+
+        if (setsockopt_int(fd, SOL_SOCKET, SO_RCVBUF, n) < 0) {
+                r = setsockopt_int(fd, SOL_SOCKET, SO_RCVBUFFORCE, n);
+                if (r < 0)
+                        return r;
+        }
+
+        return 1;
+}
+
+static const char* const ip_tos_table[] = {
+        [IPTOS_LOWDELAY] = "low-delay",
+        [IPTOS_THROUGHPUT] = "throughput",
+        [IPTOS_RELIABILITY] = "reliability",
+        [IPTOS_LOWCOST] = "low-cost",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff);
+
+bool ifname_valid(const char *p) {
+        bool numeric = true;
+
+        /* Checks whether a network interface name is valid. This is inspired by dev_valid_name() in the kernel sources
+         * but slightly stricter, as we only allow non-control, non-space ASCII characters in the interface name. We
+         * also don't permit names that only container numbers, to avoid confusion with numeric interface indexes. */
+
+        if (isempty(p))
+                return false;
+
+        if (strlen(p) >= IFNAMSIZ)
+                return false;
+
+        if (dot_or_dot_dot(p))
+                return false;
+
+        while (*p) {
+                if ((unsigned char) *p >= 127U)
+                        return false;
+
+                if ((unsigned char) *p <= 32U)
+                        return false;
+
+                if (IN_SET(*p, ':', '/'))
+                        return false;
+
+                numeric = numeric && (*p >= '0' && *p <= '9');
+                p++;
+        }
+
+        if (numeric)
+                return false;
+
+        return true;
+}
+
+bool address_label_valid(const char *p) {
+
+        if (isempty(p))
+                return false;
+
+        if (strlen(p) >= IFNAMSIZ)
+                return false;
+
+        while (*p) {
+                if ((uint8_t) *p >= 127U)
+                        return false;
+
+                if ((uint8_t) *p <= 31U)
+                        return false;
+                p++;
+        }
+
+        return true;
+}
+
+int getpeercred(int fd, struct ucred *ucred) {
+        socklen_t n = sizeof(struct ucred);
+        struct ucred u;
+        int r;
+
+        assert(fd >= 0);
+        assert(ucred);
+
+        r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n);
+        if (r < 0)
+                return -errno;
+
+        if (n != sizeof(struct ucred))
+                return -EIO;
+
+        /* Check if the data is actually useful and not suppressed due to namespacing issues */
+        if (!pid_is_valid(u.pid))
+                return -ENODATA;
+
+        /* Note that we don't check UID/GID here, as namespace translation works differently there: instead of
+         * receiving in "invalid" user/group we get the overflow UID/GID. */
+
+        *ucred = u;
+        return 0;
+}
+
+int getpeersec(int fd, char **ret) {
+        _cleanup_free_ char *s = NULL;
+        socklen_t n = 64;
+
+        assert(fd >= 0);
+        assert(ret);
+
+        for (;;) {
+                s = new0(char, n+1);
+                if (!s)
+                        return -ENOMEM;
+
+                if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n) >= 0)
+                        break;
+
+                if (errno != ERANGE)
+                        return -errno;
+
+                s = mfree(s);
+        }
+
+        if (isempty(s))
+                return -EOPNOTSUPP;
+
+        *ret = TAKE_PTR(s);
+
+        return 0;
+}
+
+int getpeergroups(int fd, gid_t **ret) {
+        socklen_t n = sizeof(gid_t) * 64;
+        _cleanup_free_ gid_t *d = NULL;
+
+        assert(fd >= 0);
+        assert(ret);
+
+        for (;;) {
+                d = malloc(n);
+                if (!d)
+                        return -ENOMEM;
+
+                if (getsockopt(fd, SOL_SOCKET, SO_PEERGROUPS, d, &n) >= 0)
+                        break;
+
+                if (errno != ERANGE)
+                        return -errno;
+
+                d = mfree(d);
+        }
+
+        assert_se(n % sizeof(gid_t) == 0);
+        n /= sizeof(gid_t);
+
+        if ((socklen_t) (int) n != n)
+                return -E2BIG;
+
+        *ret = TAKE_PTR(d);
+
+        return (int) n;
+}
+
+ssize_t send_one_fd_iov_sa(
+                int transport_fd,
+                int fd,
+                struct iovec *iov, size_t iovlen,
+                const struct sockaddr *sa, socklen_t len,
+                int flags) {
+
+        union {
+                struct cmsghdr cmsghdr;
+                uint8_t buf[CMSG_SPACE(sizeof(int))];
+        } control = {};
+        struct msghdr mh = {
+                .msg_name = (struct sockaddr*) sa,
+                .msg_namelen = len,
+                .msg_iov = iov,
+                .msg_iovlen = iovlen,
+        };
+        ssize_t k;
+
+        assert(transport_fd >= 0);
+
+        /*
+         * We need either an FD or data to send.
+         * If there's nothing, return an error.
+         */
+        if (fd < 0 && !iov)
+                return -EINVAL;
+
+        if (fd >= 0) {
+                struct cmsghdr *cmsg;
+
+                mh.msg_control = &control;
+                mh.msg_controllen = sizeof(control);
+
+                cmsg = CMSG_FIRSTHDR(&mh);
+                cmsg->cmsg_level = SOL_SOCKET;
+                cmsg->cmsg_type = SCM_RIGHTS;
+                cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+                memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
+
+                mh.msg_controllen = CMSG_SPACE(sizeof(int));
+        }
+        k = sendmsg(transport_fd, &mh, MSG_NOSIGNAL | flags);
+        if (k < 0)
+                return (ssize_t) -errno;
+
+        return k;
+}
+
+int send_one_fd_sa(
+                int transport_fd,
+                int fd,
+                const struct sockaddr *sa, socklen_t len,
+                int flags) {
+
+        assert(fd >= 0);
+
+        return (int) send_one_fd_iov_sa(transport_fd, fd, NULL, 0, sa, len, flags);
+}
+
+ssize_t receive_one_fd_iov(
+                int transport_fd,
+                struct iovec *iov, size_t iovlen,
+                int flags,
+                int *ret_fd) {
+
+        union {
+                struct cmsghdr cmsghdr;
+                uint8_t buf[CMSG_SPACE(sizeof(int))];
+        } control = {};
+        struct msghdr mh = {
+                .msg_control = &control,
+                .msg_controllen = sizeof(control),
+                .msg_iov = iov,
+                .msg_iovlen = iovlen,
+        };
+        struct cmsghdr *cmsg, *found = NULL;
+        ssize_t k;
+
+        assert(transport_fd >= 0);
+        assert(ret_fd);
+
+        /*
+         * Receive a single FD via @transport_fd. We don't care for
+         * the transport-type. We retrieve a single FD at most, so for
+         * packet-based transports, the caller must ensure to send
+         * only a single FD per packet.  This is best used in
+         * combination with send_one_fd().
+         */
+
+        k = recvmsg(transport_fd, &mh, MSG_CMSG_CLOEXEC | flags);
+        if (k < 0)
+                return (ssize_t) -errno;
+
+        CMSG_FOREACH(cmsg, &mh) {
+                if (cmsg->cmsg_level == SOL_SOCKET &&
+                    cmsg->cmsg_type == SCM_RIGHTS &&
+                    cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
+                        assert(!found);
+                        found = cmsg;
+                        break;
+                }
+        }
+
+        if (!found)
+                cmsg_close_all(&mh);
+
+        /* If didn't receive an FD or any data, return an error. */
+        if (k == 0 && !found)
+                return -EIO;
+
+        if (found)
+                *ret_fd = *(int*) CMSG_DATA(found);
+        else
+                *ret_fd = -1;
+
+        return k;
+}
+
+int receive_one_fd(int transport_fd, int flags) {
+        int fd;
+        ssize_t k;
+
+        k = receive_one_fd_iov(transport_fd, NULL, 0, flags, &fd);
+        if (k == 0)
+                return fd;
+
+        /* k must be negative, since receive_one_fd_iov() only returns
+         * a positive value if data was received through the iov. */
+        assert(k < 0);
+        return (int) k;
+}
+#endif /* NM_IGNORED */
+
+ssize_t next_datagram_size_fd(int fd) {
+        ssize_t l;
+        int k;
+
+        /* This is a bit like FIONREAD/SIOCINQ, however a bit more powerful. The difference being: recv(MSG_PEEK) will
+         * actually cause the next datagram in the queue to be validated regarding checksums, which FIONREAD doesn't
+         * do. This difference is actually of major importance as we need to be sure that the size returned here
+         * actually matches what we will read with recvmsg() next, as otherwise we might end up allocating a buffer of
+         * the wrong size. */
+
+        l = recv(fd, NULL, 0, MSG_PEEK|MSG_TRUNC);
+        if (l < 0) {
+                if (IN_SET(errno, EOPNOTSUPP, EFAULT))
+                        goto fallback;
+
+                return -errno;
+        }
+        if (l == 0)
+                goto fallback;
+
+        return l;
+
+fallback:
+        k = 0;
+
+        /* Some sockets (AF_PACKET) do not support null-sized recv() with MSG_TRUNC set, let's fall back to FIONREAD
+         * for them. Checksums don't matter for raw sockets anyway, hence this should be fine. */
+
+        if (ioctl(fd, FIONREAD, &k) < 0)
+                return -errno;
+
+        return (ssize_t) k;
+}
+
+#if 0 /* NM_IGNORED */
+int flush_accept(int fd) {
+
+        struct pollfd pollfd = {
+                .fd = fd,
+                .events = POLLIN,
+        };
+        int r;
+
+        /* Similar to flush_fd() but flushes all incoming connection by accepting them and immediately closing them. */
+
+        for (;;) {
+                int cfd;
+
+                r = poll(&pollfd, 1, 0);
+                if (r < 0) {
+                        if (errno == EINTR)
+                                continue;
+
+                        return -errno;
+
+                } else if (r == 0)
+                        return 0;
+
+                cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
+                if (cfd < 0) {
+                        if (errno == EINTR)
+                                continue;
+
+                        if (errno == EAGAIN)
+                                return 0;
+
+                        return -errno;
+                }
+
+                close(cfd);
+        }
+}
+
+struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length) {
+        struct cmsghdr *cmsg;
+
+        assert(mh);
+
+        CMSG_FOREACH(cmsg, mh)
+                if (cmsg->cmsg_level == level &&
+                    cmsg->cmsg_type == type &&
+                    (length == (socklen_t) -1 || length == cmsg->cmsg_len))
+                        return cmsg;
+
+        return NULL;
+}
+
+int socket_ioctl_fd(void) {
+        int fd;
+
+        /* Create a socket to invoke the various network interface ioctl()s on. Traditionally only AF_INET was good for
+         * that. Since kernel 4.6 AF_NETLINK works for this too. We first try to use AF_INET hence, but if that's not
+         * available (for example, because it is made unavailable via SECCOMP or such), we'll fall back to the more
+         * generic AF_NETLINK. */
+
+        fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, 0);
+        if (fd < 0)
+                fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_GENERIC);
+        if (fd < 0)
+                return -errno;
+
+        return fd;
+}
+
+int sockaddr_un_unlink(const struct sockaddr_un *sa) {
+        const char *p, * nul;
+
+        assert(sa);
+
+        if (sa->sun_family != AF_UNIX)
+                return -EPROTOTYPE;
+
+        if (sa->sun_path[0] == 0) /* Nothing to do for abstract sockets */
+                return 0;
+
+        /* The path in .sun_path is not necessarily NUL terminated. Let's fix that. */
+        nul = memchr(sa->sun_path, 0, sizeof(sa->sun_path));
+        if (nul)
+                p = sa->sun_path;
+        else
+                p = memdupa_suffix0(sa->sun_path, sizeof(sa->sun_path));
+
+        if (unlink(p) < 0)
+                return -errno;
+
+        return 1;
+}
+
+int sockaddr_un_set_path(struct sockaddr_un *ret, const char *path) {
+        size_t l;
+
+        assert(ret);
+        assert(path);
+
+        /* Initialize ret->sun_path from the specified argument. This will interpret paths starting with '@' as
+         * abstract namespace sockets, and those starting with '/' as regular filesystem sockets. It won't accept
+         * anything else (i.e. no relative paths), to avoid ambiguities. Note that this function cannot be used to
+         * reference paths in the abstract namespace that include NUL bytes in the name. */
+
+        l = strlen(path);
+        if (l == 0)
+                return -EINVAL;
+        if (!IN_SET(path[0], '/', '@'))
+                return -EINVAL;
+        if (path[1] == 0)
+                return -EINVAL;
+
+        /* Don't allow paths larger than the space in sockaddr_un. Note that we are a tiny bit more restrictive than
+         * the kernel is: we insist on NUL termination (both for abstract namespace and regular file system socket
+         * addresses!), which the kernel doesn't. We do this to reduce chance of incompatibility with other apps that
+         * do not expect non-NUL terminated file system path*/
+        if (l+1 > sizeof(ret->sun_path))
+                return -EINVAL;
+
+        *ret = (struct sockaddr_un) {
+                .sun_family = AF_UNIX,
+        };
+
+        if (path[0] == '@') {
+                /* Abstract namespace socket */
+                memcpy(ret->sun_path + 1, path + 1, l); /* copy *with* trailing NUL byte */
+                return (int) (offsetof(struct sockaddr_un, sun_path) + l); /* 🔥 *don't* 🔥 include trailing NUL in size */
+
+        } else {
+                assert(path[0] == '/');
+
+                /* File system socket */
+                memcpy(ret->sun_path, path, l + 1); /* copy *with* trailing NUL byte */
+                return (int) (offsetof(struct sockaddr_un, sun_path) + l + 1); /* include trailing NUL in size */
+        }
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/socket-util.h b/shared/systemd/src/basic/socket-util.h
new file mode 100644
index 00000000..d2246a8e
--- /dev/null
+++ b/shared/systemd/src/basic/socket-util.h
@@ -0,0 +1,202 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <inttypes.h>
+#include <linux/netlink.h>
+#include <linux/if_infiniband.h>
+#include <linux/if_packet.h>
+#include <netinet/ether.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include "macro.h"
+#include "missing_socket.h"
+#include "sparse-endian.h"
+
+union sockaddr_union {
+        /* The minimal, abstract version */
+        struct sockaddr sa;
+
+        /* The libc provided version that allocates "enough room" for every protocol */
+        struct sockaddr_storage storage;
+
+        /* Protoctol-specific implementations */
+        struct sockaddr_in in;
+        struct sockaddr_in6 in6;
+        struct sockaddr_un un;
+        struct sockaddr_nl nl;
+        struct sockaddr_ll ll;
+#if 0 /* NM_IGNORED */
+        struct sockaddr_vm vm;
+#endif /* NM_IGNORED */
+
+        /* Ensure there is enough space to store Infiniband addresses */
+        uint8_t ll_buffer[offsetof(struct sockaddr_ll, sll_addr) + CONST_MAX(ETH_ALEN, INFINIBAND_ALEN)];
+
+        /* Ensure there is enough space after the AF_UNIX sun_path for one more NUL byte, just to be sure that the path
+         * component is always followed by at least one NUL byte. */
+        uint8_t un_buffer[sizeof(struct sockaddr_un) + 1];
+};
+
+typedef struct SocketAddress {
+        union sockaddr_union sockaddr;
+
+        /* We store the size here explicitly due to the weird
+         * sockaddr_un semantics for abstract sockets */
+        socklen_t size;
+
+        /* Socket type, i.e. SOCK_STREAM, SOCK_DGRAM, ... */
+        int type;
+
+        /* Socket protocol, IPPROTO_xxx, usually 0, except for netlink */
+        int protocol;
+} SocketAddress;
+
+typedef enum SocketAddressBindIPv6Only {
+        SOCKET_ADDRESS_DEFAULT,
+        SOCKET_ADDRESS_BOTH,
+        SOCKET_ADDRESS_IPV6_ONLY,
+        _SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX,
+        _SOCKET_ADDRESS_BIND_IPV6_ONLY_INVALID = -1
+} SocketAddressBindIPv6Only;
+
+#define socket_address_family(a) ((a)->sockaddr.sa.sa_family)
+
+const char* socket_address_type_to_string(int t) _const_;
+int socket_address_type_from_string(const char *s) _pure_;
+
+int socket_address_parse(SocketAddress *a, const char *s);
+int socket_address_parse_and_warn(SocketAddress *a, const char *s);
+int socket_address_parse_netlink(SocketAddress *a, const char *s);
+int socket_address_print(const SocketAddress *a, char **p);
+int socket_address_verify(const SocketAddress *a, bool strict) _pure_;
+
+int sockaddr_un_unlink(const struct sockaddr_un *sa);
+
+static inline int socket_address_unlink(const SocketAddress *a) {
+        return socket_address_family(a) == AF_UNIX ? sockaddr_un_unlink(&a->sockaddr.un) : 0;
+}
+
+bool socket_address_can_accept(const SocketAddress *a) _pure_;
+
+int socket_address_listen(
+                const SocketAddress *a,
+                int flags,
+                int backlog,
+                SocketAddressBindIPv6Only only,
+                const char *bind_to_device,
+                bool reuse_port,
+                bool free_bind,
+                bool transparent,
+                mode_t directory_mode,
+                mode_t socket_mode,
+                const char *label);
+int make_socket_fd(int log_level, const char* address, int type, int flags);
+
+bool socket_address_is(const SocketAddress *a, const char *s, int type);
+bool socket_address_is_netlink(const SocketAddress *a, const char *s);
+
+bool socket_address_matches_fd(const SocketAddress *a, int fd);
+
+bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) _pure_;
+
+const char* socket_address_get_path(const SocketAddress *a);
+
+bool socket_ipv6_is_supported(void);
+
+int sockaddr_port(const struct sockaddr *_sa, unsigned *port);
+
+int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret);
+int getpeername_pretty(int fd, bool include_port, char **ret);
+int getsockname_pretty(int fd, char **ret);
+
+int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret);
+
+const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_;
+SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_;
+SocketAddressBindIPv6Only socket_address_bind_ipv6_only_or_bool_from_string(const char *s);
+
+int netlink_family_to_string_alloc(int b, char **s);
+int netlink_family_from_string(const char *s) _pure_;
+
+bool sockaddr_equal(const union sockaddr_union *a, const union sockaddr_union *b);
+
+int fd_inc_sndbuf(int fd, size_t n);
+int fd_inc_rcvbuf(int fd, size_t n);
+
+int ip_tos_to_string_alloc(int i, char **s);
+int ip_tos_from_string(const char *s);
+
+bool ifname_valid(const char *p);
+bool address_label_valid(const char *p);
+
+int getpeercred(int fd, struct ucred *ucred);
+int getpeersec(int fd, char **ret);
+int getpeergroups(int fd, gid_t **ret);
+
+ssize_t send_one_fd_iov_sa(
+                int transport_fd,
+                int fd,
+                struct iovec *iov, size_t iovlen,
+                const struct sockaddr *sa, socklen_t len,
+                int flags);
+int send_one_fd_sa(int transport_fd,
+                   int fd,
+                   const struct sockaddr *sa, socklen_t len,
+                   int flags);
+#define send_one_fd_iov(transport_fd, fd, iov, iovlen, flags) send_one_fd_iov_sa(transport_fd, fd, iov, iovlen, NULL, 0, flags)
+#define send_one_fd(transport_fd, fd, flags) send_one_fd_iov_sa(transport_fd, fd, NULL, 0, NULL, 0, flags)
+ssize_t receive_one_fd_iov(int transport_fd, struct iovec *iov, size_t iovlen, int flags, int *ret_fd);
+int receive_one_fd(int transport_fd, int flags);
+
+ssize_t next_datagram_size_fd(int fd);
+
+int flush_accept(int fd);
+
+#define CMSG_FOREACH(cmsg, mh)                                          \
+        for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg)))
+
+struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length);
+
+/*
+ * Certain hardware address types (e.g Infiniband) do not fit into sll_addr
+ * (8 bytes) and run over the structure. This macro returns the correct size that
+ * must be passed to kernel.
+ */
+#define SOCKADDR_LL_LEN(sa)                                             \
+        ({                                                              \
+                const struct sockaddr_ll *_sa = &(sa);                  \
+                size_t _mac_len = sizeof(_sa->sll_addr);                \
+                assert(_sa->sll_family == AF_PACKET);                   \
+                if (be16toh(_sa->sll_hatype) == ARPHRD_ETHER)           \
+                        _mac_len = MAX(_mac_len, (size_t) ETH_ALEN);    \
+                if (be16toh(_sa->sll_hatype) == ARPHRD_INFINIBAND)      \
+                        _mac_len = MAX(_mac_len, (size_t) INFINIBAND_ALEN); \
+                offsetof(struct sockaddr_ll, sll_addr) + _mac_len;      \
+        })
+
+/* Covers only file system and abstract AF_UNIX socket addresses, but not unnamed socket addresses. */
+#define SOCKADDR_UN_LEN(sa)                                             \
+        ({                                                              \
+                const struct sockaddr_un *_sa = &(sa);                  \
+                assert(_sa->sun_family == AF_UNIX);                     \
+                offsetof(struct sockaddr_un, sun_path) +                \
+                        (_sa->sun_path[0] == 0 ?                        \
+                         1 + strnlen(_sa->sun_path+1, sizeof(_sa->sun_path)-1) : \
+                         strnlen(_sa->sun_path, sizeof(_sa->sun_path))+1); \
+        })
+
+int socket_ioctl_fd(void);
+
+int sockaddr_un_set_path(struct sockaddr_un *ret, const char *path);
+
+static inline int setsockopt_int(int fd, int level, int optname, int value) {
+        if (setsockopt(fd, level, optname, &value, sizeof(value)) < 0)
+                return -errno;
+
+        return 0;
+}
diff --git a/shared/systemd/src/basic/sparse-endian.h b/shared/systemd/src/basic/sparse-endian.h
new file mode 100644
index 00000000..9583dda9
--- /dev/null
+++ b/shared/systemd/src/basic/sparse-endian.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (c) 2012 Josh Triplett <josh@joshtriplett.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#pragma once
+
+#include <byteswap.h>
+#include <endian.h>
+#include <stdint.h>
+
+#ifdef __CHECKER__
+#define __sd_bitwise __attribute__((__bitwise__))
+#define __sd_force __attribute__((__force__))
+#else
+#define __sd_bitwise
+#define __sd_force
+#endif
+
+typedef uint16_t __sd_bitwise le16_t;
+typedef uint16_t __sd_bitwise be16_t;
+typedef uint32_t __sd_bitwise le32_t;
+typedef uint32_t __sd_bitwise be32_t;
+typedef uint64_t __sd_bitwise le64_t;
+typedef uint64_t __sd_bitwise be64_t;
+
+#undef htobe16
+#undef htole16
+#undef be16toh
+#undef le16toh
+#undef htobe32
+#undef htole32
+#undef be32toh
+#undef le32toh
+#undef htobe64
+#undef htole64
+#undef be64toh
+#undef le64toh
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define bswap_16_on_le(x) __bswap_16(x)
+#define bswap_32_on_le(x) __bswap_32(x)
+#define bswap_64_on_le(x) __bswap_64(x)
+#define bswap_16_on_be(x) (x)
+#define bswap_32_on_be(x) (x)
+#define bswap_64_on_be(x) (x)
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define bswap_16_on_le(x) (x)
+#define bswap_32_on_le(x) (x)
+#define bswap_64_on_le(x) (x)
+#define bswap_16_on_be(x) __bswap_16(x)
+#define bswap_32_on_be(x) __bswap_32(x)
+#define bswap_64_on_be(x) __bswap_64(x)
+#endif
+
+static inline le16_t htole16(uint16_t value) { return (le16_t __sd_force) bswap_16_on_be(value); }
+static inline le32_t htole32(uint32_t value) { return (le32_t __sd_force) bswap_32_on_be(value); }
+static inline le64_t htole64(uint64_t value) { return (le64_t __sd_force) bswap_64_on_be(value); }
+
+static inline be16_t htobe16(uint16_t value) { return (be16_t __sd_force) bswap_16_on_le(value); }
+static inline be32_t htobe32(uint32_t value) { return (be32_t __sd_force) bswap_32_on_le(value); }
+static inline be64_t htobe64(uint64_t value) { return (be64_t __sd_force) bswap_64_on_le(value); }
+
+static inline uint16_t le16toh(le16_t value) { return bswap_16_on_be((uint16_t __sd_force)value); }
+static inline uint32_t le32toh(le32_t value) { return bswap_32_on_be((uint32_t __sd_force)value); }
+static inline uint64_t le64toh(le64_t value) { return bswap_64_on_be((uint64_t __sd_force)value); }
+
+static inline uint16_t be16toh(be16_t value) { return bswap_16_on_le((uint16_t __sd_force)value); }
+static inline uint32_t be32toh(be32_t value) { return bswap_32_on_le((uint32_t __sd_force)value); }
+static inline uint64_t be64toh(be64_t value) { return bswap_64_on_le((uint64_t __sd_force)value); }
+
+#undef __sd_bitwise
+#undef __sd_force
diff --git a/shared/systemd/src/basic/stat-util.c b/shared/systemd/src/basic/stat-util.c
new file mode 100644
index 00000000..686adaf1
--- /dev/null
+++ b/shared/systemd/src/basic/stat-util.c
@@ -0,0 +1,433 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/magic.h>
+#include <sched.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "dirent-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "macro.h"
+#include "missing.h"
+#include "parse-util.h"
+#include "stat-util.h"
+#include "string-util.h"
+
+#if 0 /* NM_IGNORED */
+int is_symlink(const char *path) {
+        struct stat info;
+
+        assert(path);
+
+        if (lstat(path, &info) < 0)
+                return -errno;
+
+        return !!S_ISLNK(info.st_mode);
+}
+
+int is_dir(const char* path, bool follow) {
+        struct stat st;
+        int r;
+
+        assert(path);
+
+        if (follow)
+                r = stat(path, &st);
+        else
+                r = lstat(path, &st);
+        if (r < 0)
+                return -errno;
+
+        return !!S_ISDIR(st.st_mode);
+}
+
+int is_dir_fd(int fd) {
+        struct stat st;
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        return !!S_ISDIR(st.st_mode);
+}
+
+int is_device_node(const char *path) {
+        struct stat info;
+
+        assert(path);
+
+        if (lstat(path, &info) < 0)
+                return -errno;
+
+        return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode));
+}
+
+int dir_is_empty_at(int dir_fd, const char *path) {
+        _cleanup_close_ int fd = -1;
+        _cleanup_closedir_ DIR *d = NULL;
+        struct dirent *de;
+
+        if (path)
+                fd = openat(dir_fd, path, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
+        else
+                fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+        if (fd < 0)
+                return -errno;
+
+        d = fdopendir(fd);
+        if (!d)
+                return -errno;
+        fd = -1;
+
+        FOREACH_DIRENT(de, d, return -errno)
+                return 0;
+
+        return 1;
+}
+
+bool null_or_empty(struct stat *st) {
+        assert(st);
+
+        if (S_ISREG(st->st_mode) && st->st_size <= 0)
+                return true;
+
+        /* We don't want to hardcode the major/minor of /dev/null,
+         * hence we do a simpler "is this a device node?" check. */
+
+        if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode))
+                return true;
+
+        return false;
+}
+
+int null_or_empty_path(const char *fn) {
+        struct stat st;
+
+        assert(fn);
+
+        if (stat(fn, &st) < 0)
+                return -errno;
+
+        return null_or_empty(&st);
+}
+
+int null_or_empty_fd(int fd) {
+        struct stat st;
+
+        assert(fd >= 0);
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        return null_or_empty(&st);
+}
+
+int path_is_read_only_fs(const char *path) {
+        struct statvfs st;
+
+        assert(path);
+
+        if (statvfs(path, &st) < 0)
+                return -errno;
+
+        if (st.f_flag & ST_RDONLY)
+                return true;
+
+        /* On NFS, statvfs() might not reflect whether we can actually
+         * write to the remote share. Let's try again with
+         * access(W_OK) which is more reliable, at least sometimes. */
+        if (access(path, W_OK) < 0 && errno == EROFS)
+                return true;
+
+        return false;
+}
+
+int files_same(const char *filea, const char *fileb, int flags) {
+        struct stat a, b;
+
+        assert(filea);
+        assert(fileb);
+
+        if (fstatat(AT_FDCWD, filea, &a, flags) < 0)
+                return -errno;
+
+        if (fstatat(AT_FDCWD, fileb, &b, flags) < 0)
+                return -errno;
+
+        return a.st_dev == b.st_dev &&
+               a.st_ino == b.st_ino;
+}
+
+bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) {
+        assert(s);
+        assert_cc(sizeof(statfs_f_type_t) >= sizeof(s->f_type));
+
+        return F_TYPE_EQUAL(s->f_type, magic_value);
+}
+
+int fd_is_fs_type(int fd, statfs_f_type_t magic_value) {
+        struct statfs s;
+
+        if (fstatfs(fd, &s) < 0)
+                return -errno;
+
+        return is_fs_type(&s, magic_value);
+}
+
+int path_is_fs_type(const char *path, statfs_f_type_t magic_value) {
+        _cleanup_close_ int fd = -1;
+
+        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH);
+        if (fd < 0)
+                return -errno;
+
+        return fd_is_fs_type(fd, magic_value);
+}
+
+bool is_temporary_fs(const struct statfs *s) {
+        return is_fs_type(s, TMPFS_MAGIC) ||
+                is_fs_type(s, RAMFS_MAGIC);
+}
+
+bool is_network_fs(const struct statfs *s) {
+        return is_fs_type(s, CIFS_MAGIC_NUMBER) ||
+                is_fs_type(s, CODA_SUPER_MAGIC) ||
+                is_fs_type(s, NCP_SUPER_MAGIC) ||
+                is_fs_type(s, NFS_SUPER_MAGIC) ||
+                is_fs_type(s, SMB_SUPER_MAGIC) ||
+                is_fs_type(s, V9FS_MAGIC) ||
+                is_fs_type(s, AFS_SUPER_MAGIC) ||
+                is_fs_type(s, OCFS2_SUPER_MAGIC);
+}
+
+int fd_is_temporary_fs(int fd) {
+        struct statfs s;
+
+        if (fstatfs(fd, &s) < 0)
+                return -errno;
+
+        return is_temporary_fs(&s);
+}
+
+int fd_is_network_fs(int fd) {
+        struct statfs s;
+
+        if (fstatfs(fd, &s) < 0)
+                return -errno;
+
+        return is_network_fs(&s);
+}
+
+int fd_is_network_ns(int fd) {
+        struct statfs s;
+        int r;
+
+        /* Checks whether the specified file descriptor refers to a network namespace. On old kernels there's no nice
+         * way to detect that, hence on those we'll return a recognizable error (EUCLEAN), so that callers can handle
+         * this somewhat nicely.
+         *
+         * This function returns > 0 if the fd definitely refers to a network namespace, 0 if it definitely does not
+         * refer to a network namespace, -EUCLEAN if we can't determine, and other negative error codes on error. */
+
+        if (fstatfs(fd, &s) < 0)
+                return -errno;
+
+        if (!is_fs_type(&s, NSFS_MAGIC)) {
+                /* On really old kernels, there was no "nsfs", and network namespace sockets belonged to procfs
+                 * instead. Handle that in a somewhat smart way. */
+
+                if (is_fs_type(&s, PROC_SUPER_MAGIC)) {
+                        struct statfs t;
+
+                        /* OK, so it is procfs. Let's see if our own network namespace is procfs, too. If so, then the
+                         * passed fd might refer to a network namespace, but we can't know for sure. In that case,
+                         * return a recognizable error. */
+
+                        if (statfs("/proc/self/ns/net", &t) < 0)
+                                return -errno;
+
+                        if (s.f_type == t.f_type)
+                                return -EUCLEAN; /* It's possible, we simply don't know */
+                }
+
+                return 0; /* No! */
+        }
+
+        r = ioctl(fd, NS_GET_NSTYPE);
+        if (r < 0) {
+                if (errno == ENOTTY) /* Old kernels didn't know this ioctl, let's also return a recognizable error in that case */
+                        return -EUCLEAN;
+
+                return -errno;
+        }
+
+        return r == CLONE_NEWNET;
+}
+
+int path_is_temporary_fs(const char *path) {
+        _cleanup_close_ int fd = -1;
+
+        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH);
+        if (fd < 0)
+                return -errno;
+
+        return fd_is_temporary_fs(fd);
+}
+#endif /* NM_IGNORED */
+
+int stat_verify_regular(const struct stat *st) {
+        assert(st);
+
+        /* Checks whether the specified stat() structure refers to a regular file. If not returns an appropriate error
+         * code. */
+
+        if (S_ISDIR(st->st_mode))
+                return -EISDIR;
+
+        if (S_ISLNK(st->st_mode))
+                return -ELOOP;
+
+        if (!S_ISREG(st->st_mode))
+                return -EBADFD;
+
+        return 0;
+}
+
+int fd_verify_regular(int fd) {
+        struct stat st;
+
+        assert(fd >= 0);
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        return stat_verify_regular(&st);
+}
+
+#if 0 /* NM_IGNORED */
+int stat_verify_directory(const struct stat *st) {
+        assert(st);
+
+        if (S_ISLNK(st->st_mode))
+                return -ELOOP;
+
+        if (!S_ISDIR(st->st_mode))
+                return -ENOTDIR;
+
+        return 0;
+}
+
+int fd_verify_directory(int fd) {
+        struct stat st;
+
+        assert(fd >= 0);
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        return stat_verify_directory(&st);
+}
+
+int device_path_make_major_minor(mode_t mode, dev_t devno, char **ret) {
+        const char *t;
+
+        /* Generates the /dev/{char|block}/MAJOR:MINOR path for a dev_t */
+
+        if (S_ISCHR(mode))
+                t = "char";
+        else if (S_ISBLK(mode))
+                t = "block";
+        else
+                return -ENODEV;
+
+        if (asprintf(ret, "/dev/%s/%u:%u", t, major(devno), minor(devno)) < 0)
+                return -ENOMEM;
+
+        return 0;
+}
+
+int device_path_make_canonical(mode_t mode, dev_t devno, char **ret) {
+        _cleanup_free_ char *p = NULL;
+        int r;
+
+        /* Finds the canonical path for a device, i.e. resolves the /dev/{char|block}/MAJOR:MINOR path to the end. */
+
+        assert(ret);
+
+        if (major(devno) == 0 && minor(devno) == 0) {
+                char *s;
+
+                /* A special hack to make sure our 'inaccessible' device nodes work. They won't have symlinks in
+                 * /dev/block/ and /dev/char/, hence we handle them specially here. */
+
+                if (S_ISCHR(mode))
+                        s = strdup("/run/systemd/inaccessible/chr");
+                else if (S_ISBLK(mode))
+                        s = strdup("/run/systemd/inaccessible/blk");
+                else
+                        return -ENODEV;
+
+                if (!s)
+                        return -ENOMEM;
+
+                *ret = s;
+                return 0;
+        }
+
+        r = device_path_make_major_minor(mode, devno, &p);
+        if (r < 0)
+                return r;
+
+        return chase_symlinks(p, NULL, 0, ret);
+}
+
+int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devno) {
+        mode_t mode;
+        dev_t devno;
+        int r;
+
+        /* Tries to extract the major/minor directly from the device path if we can. Handles /dev/block/ and /dev/char/
+         * paths, as well out synthetic inaccessible device nodes. Never goes to disk. Returns -ENODEV if the device
+         * path cannot be parsed like this.  */
+
+        if (path_equal(path, "/run/systemd/inaccessible/chr")) {
+                mode = S_IFCHR;
+                devno = makedev(0, 0);
+        } else if (path_equal(path, "/run/systemd/inaccessible/blk")) {
+                mode = S_IFBLK;
+                devno = makedev(0, 0);
+        } else {
+                const char *w;
+
+                w = path_startswith(path, "/dev/block/");
+                if (w)
+                        mode = S_IFBLK;
+                else {
+                        w = path_startswith(path, "/dev/char/");
+                        if (!w)
+                                return -ENODEV;
+
+                        mode = S_IFCHR;
+                }
+
+                r = parse_dev(w, &devno);
+                if (r < 0)
+                        return r;
+        }
+
+        if (ret_mode)
+                *ret_mode = mode;
+        if (ret_devno)
+                *ret_devno = devno;
+
+        return 0;
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/stat-util.h b/shared/systemd/src/basic/stat-util.h
new file mode 100644
index 00000000..74fb7251
--- /dev/null
+++ b/shared/systemd/src/basic/stat-util.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+
+#include "macro.h"
+
+int is_symlink(const char *path);
+int is_dir(const char *path, bool follow);
+int is_dir_fd(int fd);
+int is_device_node(const char *path);
+
+int dir_is_empty_at(int dir_fd, const char *path);
+static inline int dir_is_empty(const char *path) {
+        return dir_is_empty_at(AT_FDCWD, path);
+}
+
+static inline int dir_is_populated(const char *path) {
+        int r;
+        r = dir_is_empty(path);
+        if (r < 0)
+                return r;
+        return !r;
+}
+
+bool null_or_empty(struct stat *st) _pure_;
+int null_or_empty_path(const char *fn);
+int null_or_empty_fd(int fd);
+
+int path_is_read_only_fs(const char *path);
+
+int files_same(const char *filea, const char *fileb, int flags);
+
+/* The .f_type field of struct statfs is really weird defined on
+ * different archs. Let's give its type a name. */
+typedef typeof(((struct statfs*)NULL)->f_type) statfs_f_type_t;
+
+bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) _pure_;
+int fd_is_fs_type(int fd, statfs_f_type_t magic_value);
+int path_is_fs_type(const char *path, statfs_f_type_t magic_value);
+
+bool is_temporary_fs(const struct statfs *s) _pure_;
+bool is_network_fs(const struct statfs *s) _pure_;
+
+int fd_is_temporary_fs(int fd);
+int fd_is_network_fs(int fd);
+
+int fd_is_network_ns(int fd);
+
+int path_is_temporary_fs(const char *path);
+
+/* Because statfs.t_type can be int on some architectures, we have to cast
+ * the const magic to the type, otherwise the compiler warns about
+ * signed/unsigned comparison, because the magic can be 32 bit unsigned.
+ */
+#define F_TYPE_EQUAL(a, b) (a == (typeof(a)) b)
+
+int stat_verify_regular(const struct stat *st);
+int fd_verify_regular(int fd);
+
+int stat_verify_directory(const struct stat *st);
+int fd_verify_directory(int fd);
+
+/* glibc and the Linux kernel have different ideas about the major/minor size. These calls will check whether the
+ * specified major is valid by the Linux kernel's standards, not by glibc's. Linux has 20bits of minor, and 12 bits of
+ * major space. See MINORBITS in linux/kdev_t.h in the kernel sources. (If you wonder why we define _y here, instead of
+ * comparing directly >= 0: it's to trick out -Wtype-limits, which would otherwise complain if the type is unsigned, as
+ * such a test would be pointless in such a case.) */
+
+#define DEVICE_MAJOR_VALID(x)                                           \
+        ({                                                              \
+                typeof(x) _x = (x), _y = 0;                             \
+                _x >= _y && _x < (UINT32_C(1) << 12);                   \
+                                                                        \
+        })
+
+#define DEVICE_MINOR_VALID(x)                                           \
+        ({                                                              \
+                typeof(x) _x = (x), _y = 0;                             \
+                _x >= _y && _x < (UINT32_C(1) << 20);                   \
+        })
+
+int device_path_make_major_minor(mode_t mode, dev_t devno, char **ret);
+int device_path_make_canonical(mode_t mode, dev_t devno, char **ret);
+int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devno);
diff --git a/shared/systemd/src/basic/stdio-util.h b/shared/systemd/src/basic/stdio-util.h
new file mode 100644
index 00000000..dc67b6e7
--- /dev/null
+++ b/shared/systemd/src/basic/stdio-util.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <printf.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "macro.h"
+#include "util.h"
+
+#define snprintf_ok(buf, len, fmt, ...) \
+        ((size_t) snprintf(buf, len, fmt, __VA_ARGS__) < (len))
+
+#define xsprintf(buf, fmt, ...) \
+        assert_message_se(snprintf_ok(buf, ELEMENTSOF(buf), fmt, __VA_ARGS__), "xsprintf: " #buf "[] must be big enough")
+
+#define VA_FORMAT_ADVANCE(format, ap)                                   \
+do {                                                                    \
+        int _argtypes[128];                                             \
+        size_t _i, _k;                                                  \
+        /* See https://github.com/google/sanitizers/issues/992 */       \
+        if (HAS_FEATURE_MEMORY_SANITIZER)                               \
+                zero(_argtypes);                                        \
+        _k = parse_printf_format((format), ELEMENTSOF(_argtypes), _argtypes); \
+        assert(_k < ELEMENTSOF(_argtypes));                             \
+        for (_i = 0; _i < _k; _i++) {                                   \
+                if (_argtypes[_i] & PA_FLAG_PTR)  {                     \
+                        (void) va_arg(ap, void*);                       \
+                        continue;                                       \
+                }                                                       \
+                                                                        \
+                switch (_argtypes[_i]) {                                \
+                case PA_INT:                                            \
+                case PA_INT|PA_FLAG_SHORT:                              \
+                case PA_CHAR:                                           \
+                        (void) va_arg(ap, int);                         \
+                        break;                                          \
+                case PA_INT|PA_FLAG_LONG:                               \
+                        (void) va_arg(ap, long int);                    \
+                        break;                                          \
+                case PA_INT|PA_FLAG_LONG_LONG:                          \
+                        (void) va_arg(ap, long long int);               \
+                        break;                                          \
+                case PA_WCHAR:                                          \
+                        (void) va_arg(ap, wchar_t);                     \
+                        break;                                          \
+                case PA_WSTRING:                                        \
+                case PA_STRING:                                         \
+                case PA_POINTER:                                        \
+                        (void) va_arg(ap, void*);                       \
+                        break;                                          \
+                case PA_FLOAT:                                          \
+                case PA_DOUBLE:                                         \
+                        (void) va_arg(ap, double);                      \
+                        break;                                          \
+                case PA_DOUBLE|PA_FLAG_LONG_DOUBLE:                     \
+                        (void) va_arg(ap, long double);                 \
+                        break;                                          \
+                default:                                                \
+                        assert_not_reached("Unknown format string argument."); \
+                }                                                       \
+        }                                                               \
+} while (false)
diff --git a/shared/systemd/src/basic/string-table.c b/shared/systemd/src/basic/string-table.c
new file mode 100644
index 00000000..14ae6308
--- /dev/null
+++ b/shared/systemd/src/basic/string-table.c
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include "string-table.h"
+#include "string-util.h"
+
+ssize_t string_table_lookup(const char * const *table, size_t len, const char *key) {
+        size_t i;
+
+        if (!key)
+                return -1;
+
+        for (i = 0; i < len; ++i)
+                if (streq_ptr(table[i], key))
+                        return (ssize_t) i;
+
+        return -1;
+}
diff --git a/shared/systemd/src/basic/string-table.h b/shared/systemd/src/basic/string-table.h
new file mode 100644
index 00000000..228c12ad
--- /dev/null
+++ b/shared/systemd/src/basic/string-table.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#pragma once
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "macro.h"
+#include "parse-util.h"
+#include "string-util.h"
+
+ssize_t string_table_lookup(const char * const *table, size_t len, const char *key);
+
+/* For basic lookup tables with strictly enumerated entries */
+#define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope)          \
+        scope const char *name##_to_string(type i) {                    \
+                if (i < 0 || i >= (type) ELEMENTSOF(name##_table))      \
+                        return NULL;                                    \
+                return name##_table[i];                                 \
+        }
+
+#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope)        \
+        scope type name##_from_string(const char *s) {                  \
+                return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \
+        }
+
+#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \
+        scope type name##_from_string(const char *s) {                  \
+                int b;                                                  \
+                if (!s)                                                 \
+                        return -1;                                      \
+                b = parse_boolean(s);                                   \
+                if (b == 0)                                             \
+                        return (type) 0;                                \
+                else if (b > 0)                                         \
+                        return yes;                                     \
+                return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \
+        }
+
+#define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,scope) \
+        scope int name##_to_string_alloc(type i, char **str) {          \
+                char *s;                                                \
+                if (i < 0 || i > max)                                   \
+                        return -ERANGE;                                 \
+                if (i < (type) ELEMENTSOF(name##_table)) {              \
+                        s = strdup(name##_table[i]);                    \
+                        if (!s)                                         \
+                                return -ENOMEM;                         \
+                } else {                                                \
+                        if (asprintf(&s, "%i", i) < 0)                  \
+                                return -ENOMEM;                         \
+                }                                                       \
+                *str = s;                                               \
+                return 0;                                               \
+        }
+
+#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,scope) \
+        scope type name##_from_string(const char *s) {                  \
+                type i;                                                 \
+                unsigned u = 0;                                         \
+                if (!s)                                                 \
+                        return (type) -1;                               \
+                for (i = 0; i < (type) ELEMENTSOF(name##_table); i++)   \
+                        if (streq_ptr(name##_table[i], s))              \
+                                return i;                               \
+                if (safe_atou(s, &u) >= 0 && u <= max)                  \
+                        return (type) u;                                \
+                return (type) -1;                                       \
+        }                                                               \
+
+#define _DEFINE_STRING_TABLE_LOOKUP(name,type,scope)                    \
+        _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope)          \
+        _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope)
+
+#define _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,scope)   \
+        _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope)          \
+        _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope)
+
+#define DEFINE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,)
+#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,static)
+#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,static)
+#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,static)
+
+#define DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes) _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,)
+
+/* For string conversions where numbers are also acceptable */
+#define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max)         \
+        _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,)  \
+        _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,)
+
+#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max) \
+        _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,static)
+#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max) \
+        _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,static)
+
+#define DUMP_STRING_TABLE(name,type,max)                                \
+        do {                                                            \
+                type _k;                                                \
+                flockfile(stdout);                                      \
+                for (_k = 0; _k < (max); _k++) {                        \
+                        const char *_t;                                 \
+                        _t = name##_to_string(_k);                      \
+                        if (!_t)                                        \
+                                continue;                               \
+                        fputs_unlocked(_t, stdout);                     \
+                        fputc_unlocked('\n', stdout);                   \
+                }                                                       \
+                funlockfile(stdout);                                    \
+        } while(false)
diff --git a/shared/systemd/src/basic/string-util.c b/shared/systemd/src/basic/string-util.c
new file mode 100644
index 00000000..0e961927
--- /dev/null
+++ b/shared/systemd/src/basic/string-util.c
@@ -0,0 +1,1107 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "escape.h"
+#include "gunicode.h"
+#include "locale-util.h"
+#include "macro.h"
+#include "string-util.h"
+#include "terminal-util.h"
+#include "utf8.h"
+#include "util.h"
+#include "fileio.h"
+
+int strcmp_ptr(const char *a, const char *b) {
+
+        /* Like strcmp(), but tries to make sense of NULL pointers */
+        if (a && b)
+                return strcmp(a, b);
+
+        if (!a && b)
+                return -1;
+
+        if (a && !b)
+                return 1;
+
+        return 0;
+}
+
+char* endswith(const char *s, const char *postfix) {
+        size_t sl, pl;
+
+        assert(s);
+        assert(postfix);
+
+        sl = strlen(s);
+        pl = strlen(postfix);
+
+        if (pl == 0)
+                return (char*) s + sl;
+
+        if (sl < pl)
+                return NULL;
+
+        if (memcmp(s + sl - pl, postfix, pl) != 0)
+                return NULL;
+
+        return (char*) s + sl - pl;
+}
+
+char* endswith_no_case(const char *s, const char *postfix) {
+        size_t sl, pl;
+
+        assert(s);
+        assert(postfix);
+
+        sl = strlen(s);
+        pl = strlen(postfix);
+
+        if (pl == 0)
+                return (char*) s + sl;
+
+        if (sl < pl)
+                return NULL;
+
+        if (strcasecmp(s + sl - pl, postfix) != 0)
+                return NULL;
+
+        return (char*) s + sl - pl;
+}
+
+char* first_word(const char *s, const char *word) {
+        size_t sl, wl;
+        const char *p;
+
+        assert(s);
+        assert(word);
+
+        /* Checks if the string starts with the specified word, either
+         * followed by NUL or by whitespace. Returns a pointer to the
+         * NUL or the first character after the whitespace. */
+
+        sl = strlen(s);
+        wl = strlen(word);
+
+        if (sl < wl)
+                return NULL;
+
+        if (wl == 0)
+                return (char*) s;
+
+        if (memcmp(s, word, wl) != 0)
+                return NULL;
+
+        p = s + wl;
+        if (*p == 0)
+                return (char*) p;
+
+        if (!strchr(WHITESPACE, *p))
+                return NULL;
+
+        p += strspn(p, WHITESPACE);
+        return (char*) p;
+}
+
+static size_t strcspn_escaped(const char *s, const char *reject) {
+        bool escaped = false;
+        int n;
+
+        for (n=0; s[n]; n++) {
+                if (escaped)
+                        escaped = false;
+                else if (s[n] == '\\')
+                        escaped = true;
+                else if (strchr(reject, s[n]))
+                        break;
+        }
+
+        /* if s ends in \, return index of previous char */
+        return n - escaped;
+}
+
+/* Split a string into words. */
+const char* split(const char **state, size_t *l, const char *separator, SplitFlags flags) {
+        const char *current;
+
+        current = *state;
+
+        if (!*current) {
+                assert(**state == '\0');
+                return NULL;
+        }
+
+        current += strspn(current, separator);
+        if (!*current) {
+                *state = current;
+                return NULL;
+        }
+
+        if (flags & SPLIT_QUOTES && strchr("\'\"", *current)) {
+                char quotechars[2] = {*current, '\0'};
+
+                *l = strcspn_escaped(current + 1, quotechars);
+                if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] ||
+                    (current[*l + 2] && !strchr(separator, current[*l + 2]))) {
+                        /* right quote missing or garbage at the end */
+                        if (flags & SPLIT_RELAX) {
+                                *state = current + *l + 1 + (current[*l + 1] != '\0');
+                                return current + 1;
+                        }
+                        *state = current;
+                        return NULL;
+                }
+                *state = current++ + *l + 2;
+        } else if (flags & SPLIT_QUOTES) {
+                *l = strcspn_escaped(current, separator);
+                if (current[*l] && !strchr(separator, current[*l]) && !(flags & SPLIT_RELAX)) {
+                        /* unfinished escape */
+                        *state = current;
+                        return NULL;
+                }
+                *state = current + *l;
+        } else {
+                *l = strcspn(current, separator);
+                *state = current + *l;
+        }
+
+        return current;
+}
+
+char *strnappend(const char *s, const char *suffix, size_t b) {
+        size_t a;
+        char *r;
+
+        if (!s && !suffix)
+                return strdup("");
+
+        if (!s)
+                return strndup(suffix, b);
+
+        if (!suffix)
+                return strdup(s);
+
+        assert(s);
+        assert(suffix);
+
+        a = strlen(s);
+        if (b > ((size_t) -1) - a)
+                return NULL;
+
+        r = new(char, a+b+1);
+        if (!r)
+                return NULL;
+
+        memcpy(r, s, a);
+        memcpy(r+a, suffix, b);
+        r[a+b] = 0;
+
+        return r;
+}
+
+char *strappend(const char *s, const char *suffix) {
+        return strnappend(s, suffix, strlen_ptr(suffix));
+}
+
+#if 0 /* NM_IGNORED */
+char *strjoin_real(const char *x, ...) {
+        va_list ap;
+        size_t l;
+        char *r, *p;
+
+        va_start(ap, x);
+
+        if (x) {
+                l = strlen(x);
+
+                for (;;) {
+                        const char *t;
+                        size_t n;
+
+                        t = va_arg(ap, const char *);
+                        if (!t)
+                                break;
+
+                        n = strlen(t);
+                        if (n > ((size_t) -1) - l) {
+                                va_end(ap);
+                                return NULL;
+                        }
+
+                        l += n;
+                }
+        } else
+                l = 0;
+
+        va_end(ap);
+
+        r = new(char, l+1);
+        if (!r)
+                return NULL;
+
+        if (x) {
+                p = stpcpy(r, x);
+
+                va_start(ap, x);
+
+                for (;;) {
+                        const char *t;
+
+                        t = va_arg(ap, const char *);
+                        if (!t)
+                                break;
+
+                        p = stpcpy(p, t);
+                }
+
+                va_end(ap);
+        } else
+                r[0] = 0;
+
+        return r;
+}
+
+char *strstrip(char *s) {
+        if (!s)
+                return NULL;
+
+        /* Drops trailing whitespace. Modifies the string in place. Returns pointer to first non-space character */
+
+        return delete_trailing_chars(skip_leading_chars(s, WHITESPACE), WHITESPACE);
+}
+
+char *delete_chars(char *s, const char *bad) {
+        char *f, *t;
+
+        /* Drops all specified bad characters, regardless where in the string */
+
+        if (!s)
+                return NULL;
+
+        if (!bad)
+                bad = WHITESPACE;
+
+        for (f = s, t = s; *f; f++) {
+                if (strchr(bad, *f))
+                        continue;
+
+                *(t++) = *f;
+        }
+
+        *t = 0;
+
+        return s;
+}
+
+char *delete_trailing_chars(char *s, const char *bad) {
+        char *p, *c = s;
+
+        /* Drops all specified bad characters, at the end of the string */
+
+        if (!s)
+                return NULL;
+
+        if (!bad)
+                bad = WHITESPACE;
+
+        for (p = s; *p; p++)
+                if (!strchr(bad, *p))
+                        c = p + 1;
+
+        *c = 0;
+
+        return s;
+}
+#endif /* NM_IGNORED */
+
+char *truncate_nl(char *s) {
+        assert(s);
+
+        s[strcspn(s, NEWLINE)] = 0;
+        return s;
+}
+
+char ascii_tolower(char x) {
+
+        if (x >= 'A' && x <= 'Z')
+                return x - 'A' + 'a';
+
+        return x;
+}
+
+char ascii_toupper(char x) {
+
+        if (x >= 'a' && x <= 'z')
+                return x - 'a' + 'A';
+
+        return x;
+}
+
+char *ascii_strlower(char *t) {
+        char *p;
+
+        assert(t);
+
+        for (p = t; *p; p++)
+                *p = ascii_tolower(*p);
+
+        return t;
+}
+
+char *ascii_strupper(char *t) {
+        char *p;
+
+        assert(t);
+
+        for (p = t; *p; p++)
+                *p = ascii_toupper(*p);
+
+        return t;
+}
+
+char *ascii_strlower_n(char *t, size_t n) {
+        size_t i;
+
+        if (n <= 0)
+                return t;
+
+        for (i = 0; i < n; i++)
+                t[i] = ascii_tolower(t[i]);
+
+        return t;
+}
+
+int ascii_strcasecmp_n(const char *a, const char *b, size_t n) {
+
+        for (; n > 0; a++, b++, n--) {
+                int x, y;
+
+                x = (int) (uint8_t) ascii_tolower(*a);
+                y = (int) (uint8_t) ascii_tolower(*b);
+
+                if (x != y)
+                        return x - y;
+        }
+
+        return 0;
+}
+
+int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m) {
+        int r;
+
+        r = ascii_strcasecmp_n(a, b, MIN(n, m));
+        if (r != 0)
+                return r;
+
+        return CMP(n, m);
+}
+
+bool chars_intersect(const char *a, const char *b) {
+        const char *p;
+
+        /* Returns true if any of the chars in a are in b. */
+        for (p = a; *p; p++)
+                if (strchr(b, *p))
+                        return true;
+
+        return false;
+}
+
+bool string_has_cc(const char *p, const char *ok) {
+        const char *t;
+
+        assert(p);
+
+        /*
+         * Check if a string contains control characters. If 'ok' is
+         * non-NULL it may be a string containing additional CCs to be
+         * considered OK.
+         */
+
+        for (t = p; *t; t++) {
+                if (ok && strchr(ok, *t))
+                        continue;
+
+                if (*t > 0 && *t < ' ')
+                        return true;
+
+                if (*t == 127)
+                        return true;
+        }
+
+        return false;
+}
+
+#if 0 /* NM_IGNORED */
+static int write_ellipsis(char *buf, bool unicode) {
+        if (unicode || is_locale_utf8()) {
+                buf[0] = 0xe2; /* tri-dot ellipsis: … */
+                buf[1] = 0x80;
+                buf[2] = 0xa6;
+        } else {
+                buf[0] = '.';
+                buf[1] = '.';
+                buf[2] = '.';
+        }
+
+        return 3;
+}
+
+static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) {
+        size_t x, need_space, suffix_len;
+        char *t;
+
+        assert(s);
+        assert(percent <= 100);
+        assert(new_length != (size_t) -1);
+
+        if (old_length <= new_length)
+                return strndup(s, old_length);
+
+        /* Special case short ellipsations */
+        switch (new_length) {
+
+        case 0:
+                return strdup("");
+
+        case 1:
+                if (is_locale_utf8())
+                        return strdup("…");
+                else
+                        return strdup(".");
+
+        case 2:
+                if (!is_locale_utf8())
+                        return strdup("..");
+
+                break;
+
+        default:
+                break;
+        }
+
+        /* Calculate how much space the ellipsis will take up. If we are in UTF-8 mode we only need space for one
+         * character ("…"), otherwise for three characters ("..."). Note that in both cases we need 3 bytes of storage,
+         * either for the UTF-8 encoded character or for three ASCII characters. */
+        need_space = is_locale_utf8() ? 1 : 3;
+
+        t = new(char, new_length+3);
+        if (!t)
+                return NULL;
+
+        assert(new_length >= need_space);
+
+        x = ((new_length - need_space) * percent + 50) / 100;
+        assert(x <= new_length - need_space);
+
+        memcpy(t, s, x);
+        write_ellipsis(t + x, false);
+        suffix_len = new_length - x - need_space;
+        memcpy(t + x + 3, s + old_length - suffix_len, suffix_len);
+        *(t + x + 3 + suffix_len) = '\0';
+
+        return t;
+}
+
+char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) {
+        size_t x, k, len, len2;
+        const char *i, *j;
+        char *e;
+        int r;
+
+        /* Note that 'old_length' refers to bytes in the string, while 'new_length' refers to character cells taken up
+         * on screen. This distinction doesn't matter for ASCII strings, but it does matter for non-ASCII UTF-8
+         * strings.
+         *
+         * Ellipsation is done in a locale-dependent way:
+         * 1. If the string passed in is fully ASCII and the current locale is not UTF-8, three dots are used ("...")
+         * 2. Otherwise, a unicode ellipsis is used ("…")
+         *
+         * In other words: you'll get a unicode ellipsis as soon as either the string contains non-ASCII characters or
+         * the current locale is UTF-8.
+         */
+
+        assert(s);
+        assert(percent <= 100);
+
+        if (new_length == (size_t) -1)
+                return strndup(s, old_length);
+
+        if (new_length == 0)
+                return strdup("");
+
+        /* If no multibyte characters use ascii_ellipsize_mem for speed */
+        if (ascii_is_valid_n(s, old_length))
+                return ascii_ellipsize_mem(s, old_length, new_length, percent);
+
+        x = ((new_length - 1) * percent) / 100;
+        assert(x <= new_length - 1);
+
+        k = 0;
+        for (i = s; i < s + old_length; i = utf8_next_char(i)) {
+                char32_t c;
+                int w;
+
+                r = utf8_encoded_to_unichar(i, &c);
+                if (r < 0)
+                        return NULL;
+
+                w = unichar_iswide(c) ? 2 : 1;
+                if (k + w <= x)
+                        k += w;
+                else
+                        break;
+        }
+
+        for (j = s + old_length; j > i; ) {
+                char32_t c;
+                int w;
+                const char *jj;
+
+                jj = utf8_prev_char(j);
+                r = utf8_encoded_to_unichar(jj, &c);
+                if (r < 0)
+                        return NULL;
+
+                w = unichar_iswide(c) ? 2 : 1;
+                if (k + w <= new_length) {
+                        k += w;
+                        j = jj;
+                } else
+                        break;
+        }
+        assert(i <= j);
+
+        /* we don't actually need to ellipsize */
+        if (i == j)
+                return memdup_suffix0(s, old_length);
+
+        /* make space for ellipsis, if possible */
+        if (j < s + old_length)
+                j = utf8_next_char(j);
+        else if (i > s)
+                i = utf8_prev_char(i);
+
+        len = i - s;
+        len2 = s + old_length - j;
+        e = new(char, len + 3 + len2 + 1);
+        if (!e)
+                return NULL;
+
+        /*
+        printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n",
+               old_length, new_length, x, len, len2, k);
+        */
+
+        memcpy(e, s, len);
+        write_ellipsis(e + len, true);
+        memcpy(e + len + 3, j, len2);
+        *(e + len + 3 + len2) = '\0';
+
+        return e;
+}
+
+char *cellescape(char *buf, size_t len, const char *s) {
+        /* Escape and ellipsize s into buffer buf of size len. Only non-control ASCII
+         * characters are copied as they are, everything else is escaped. The result
+         * is different then if escaping and ellipsization was performed in two
+         * separate steps, because each sequence is either stored in full or skipped.
+         *
+         * This function should be used for logging about strings which expected to
+         * be plain ASCII in a safe way.
+         *
+         * An ellipsis will be used if s is too long. It was always placed at the
+         * very end.
+         */
+
+        size_t i = 0, last_char_width[4] = {}, k = 0, j;
+
+        assert(len > 0); /* at least a terminating NUL */
+
+        for (;;) {
+                char four[4];
+                int w;
+
+                if (*s == 0) /* terminating NUL detected? then we are done! */
+                        goto done;
+
+                w = cescape_char(*s, four);
+                if (i + w + 1 > len) /* This character doesn't fit into the buffer anymore? In that case let's
+                                      * ellipsize at the previous location */
+                        break;
+
+                /* OK, there was space, let's add this escaped character to the buffer */
+                memcpy(buf + i, four, w);
+                i += w;
+
+                /* And remember its width in the ring buffer */
+                last_char_width[k] = w;
+                k = (k + 1) % 4;
+
+                s++;
+        }
+
+        /* Ellipsation is necessary. This means we might need to truncate the string again to make space for 4
+         * characters ideally, but the buffer is shorter than that in the first place take what we can get */
+        for (j = 0; j < ELEMENTSOF(last_char_width); j++) {
+
+                if (i + 4 <= len) /* nice, we reached our space goal */
+                        break;
+
+                k = k == 0 ? 3 : k - 1;
+                if (last_char_width[k] == 0) /* bummer, we reached the beginning of the strings */
+                        break;
+
+                assert(i >= last_char_width[k]);
+                i -= last_char_width[k];
+        }
+
+        if (i + 4 <= len) /* yay, enough space */
+                i += write_ellipsis(buf + i, false);
+        else if (i + 3 <= len) { /* only space for ".." */
+                buf[i++] = '.';
+                buf[i++] = '.';
+        } else if (i + 2 <= len) /* only space for a single "." */
+                buf[i++] = '.';
+        else
+                assert(i + 1 <= len);
+
+ done:
+        buf[i] = '\0';
+        return buf;
+}
+#endif /* NM_IGNORED */
+
+bool nulstr_contains(const char *nulstr, const char *needle) {
+        const char *i;
+
+        if (!nulstr)
+                return false;
+
+        NULSTR_FOREACH(i, nulstr)
+                if (streq(i, needle))
+                        return true;
+
+        return false;
+}
+
+char* strshorten(char *s, size_t l) {
+        assert(s);
+
+        if (strnlen(s, l+1) > l)
+                s[l] = 0;
+
+        return s;
+}
+
+char *strreplace(const char *text, const char *old_string, const char *new_string) {
+        size_t l, old_len, new_len, allocated = 0;
+        char *t, *ret = NULL;
+        const char *f;
+
+        assert(old_string);
+        assert(new_string);
+
+        if (!text)
+                return NULL;
+
+        old_len = strlen(old_string);
+        new_len = strlen(new_string);
+
+        l = strlen(text);
+        if (!GREEDY_REALLOC(ret, allocated, l+1))
+                return NULL;
+
+        f = text;
+        t = ret;
+        while (*f) {
+                size_t d, nl;
+
+                if (!startswith(f, old_string)) {
+                        *(t++) = *(f++);
+                        continue;
+                }
+
+                d = t - ret;
+                nl = l - old_len + new_len;
+
+                if (!GREEDY_REALLOC(ret, allocated, nl + 1))
+                        return mfree(ret);
+
+                l = nl;
+                t = ret + d;
+
+                t = stpcpy(t, new_string);
+                f += old_len;
+        }
+
+        *t = 0;
+        return ret;
+}
+
+static void advance_offsets(ssize_t diff, size_t offsets[static 2], size_t shift[static 2], size_t size) {
+        if (!offsets)
+                return;
+
+        if ((size_t) diff < offsets[0])
+                shift[0] += size;
+        if ((size_t) diff < offsets[1])
+                shift[1] += size;
+}
+
+char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
+        const char *i, *begin = NULL;
+        enum {
+                STATE_OTHER,
+                STATE_ESCAPE,
+                STATE_CSI,
+                STATE_CSO,
+        } state = STATE_OTHER;
+        char *obuf = NULL;
+        size_t osz = 0, isz, shift[2] = {};
+        FILE *f;
+
+        assert(ibuf);
+        assert(*ibuf);
+
+        /* This does three things:
+         *
+         * 1. Replaces TABs by 8 spaces
+         * 2. Strips ANSI color sequences (a subset of CSI), i.e. ESC '[' … 'm' sequences
+         * 3. Strips ANSI operating system sequences (CSO), i.e. ESC ']' … BEL sequences
+         *
+         * Everything else will be left as it is. In particular other ANSI sequences are left as they are, as are any
+         * other special characters. Truncated ANSI sequences are left-as is too. This call is supposed to suppress the
+         * most basic formatting noise, but nothing else.
+         *
+         * Why care for CSO sequences? Well, to undo what terminal_urlify() and friends generate. */
+
+        isz = _isz ? *_isz : strlen(*ibuf);
+
+        f = open_memstream(&obuf, &osz);
+        if (!f)
+                return NULL;
+
+        /* Note we turn off internal locking on f for performance reasons.  It's safe to do so since we created f here
+         * and it doesn't leave our scope. */
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        for (i = *ibuf; i < *ibuf + isz + 1; i++) {
+
+                switch (state) {
+
+                case STATE_OTHER:
+                        if (i >= *ibuf + isz) /* EOT */
+                                break;
+                        else if (*i == '\x1B')
+                                state = STATE_ESCAPE;
+                        else if (*i == '\t') {
+                                fputs("        ", f);
+                                advance_offsets(i - *ibuf, highlight, shift, 7);
+                        } else
+                                fputc(*i, f);
+
+                        break;
+
+                case STATE_ESCAPE:
+                        if (i >= *ibuf + isz) { /* EOT */
+                                fputc('\x1B', f);
+                                advance_offsets(i - *ibuf, highlight, shift, 1);
+                                break;
+                        } else if (*i == '[') { /* ANSI CSI */
+                                state = STATE_CSI;
+                                begin = i + 1;
+                        } else if (*i == ']') { /* ANSI CSO */
+                                state = STATE_CSO;
+                                begin = i + 1;
+                        } else {
+                                fputc('\x1B', f);
+                                fputc(*i, f);
+                                advance_offsets(i - *ibuf, highlight, shift, 1);
+                                state = STATE_OTHER;
+                        }
+
+                        break;
+
+                case STATE_CSI:
+
+                        if (i >= *ibuf + isz || /* EOT … */
+                            !strchr("01234567890;m", *i)) { /* … or invalid chars in sequence */
+                                fputc('\x1B', f);
+                                fputc('[', f);
+                                advance_offsets(i - *ibuf, highlight, shift, 2);
+                                state = STATE_OTHER;
+                                i = begin-1;
+                        } else if (*i == 'm')
+                                state = STATE_OTHER;
+
+                        break;
+
+                case STATE_CSO:
+
+                        if (i >= *ibuf + isz || /* EOT … */
+                            (*i != '\a' && (uint8_t) *i < 32U) || (uint8_t) *i > 126U) { /* … or invalid chars in sequence */
+                                fputc('\x1B', f);
+                                fputc(']', f);
+                                advance_offsets(i - *ibuf, highlight, shift, 2);
+                                state = STATE_OTHER;
+                                i = begin-1;
+                        } else if (*i == '\a')
+                                state = STATE_OTHER;
+
+                        break;
+                }
+        }
+
+        if (fflush_and_check(f) < 0) {
+                fclose(f);
+                return mfree(obuf);
+        }
+
+        fclose(f);
+
+        free(*ibuf);
+        *ibuf = obuf;
+
+        if (_isz)
+                *_isz = osz;
+
+        if (highlight) {
+                highlight[0] += shift[0];
+                highlight[1] += shift[1];
+        }
+
+        return obuf;
+}
+
+#if 0 /* NM_IGNORED */
+char *strextend_with_separator(char **x, const char *separator, ...) {
+        bool need_separator;
+        size_t f, l, l_separator;
+        char *r, *p;
+        va_list ap;
+
+        assert(x);
+
+        l = f = strlen_ptr(*x);
+
+        need_separator = !isempty(*x);
+        l_separator = strlen_ptr(separator);
+
+        va_start(ap, separator);
+        for (;;) {
+                const char *t;
+                size_t n;
+
+                t = va_arg(ap, const char *);
+                if (!t)
+                        break;
+
+                n = strlen(t);
+
+                if (need_separator)
+                        n += l_separator;
+
+                if (n > ((size_t) -1) - l) {
+                        va_end(ap);
+                        return NULL;
+                }
+
+                l += n;
+                need_separator = true;
+        }
+        va_end(ap);
+
+        need_separator = !isempty(*x);
+
+        r = realloc(*x, l+1);
+        if (!r)
+                return NULL;
+
+        p = r + f;
+
+        va_start(ap, separator);
+        for (;;) {
+                const char *t;
+
+                t = va_arg(ap, const char *);
+                if (!t)
+                        break;
+
+                if (need_separator && separator)
+                        p = stpcpy(p, separator);
+
+                p = stpcpy(p, t);
+
+                need_separator = true;
+        }
+        va_end(ap);
+
+        assert(p == r + l);
+
+        *p = 0;
+        *x = r;
+
+        return r + l;
+}
+#endif /* NM_IGNORED */
+
+char *strrep(const char *s, unsigned n) {
+        size_t l;
+        char *r, *p;
+        unsigned i;
+
+        assert(s);
+
+        l = strlen(s);
+        p = r = malloc(l * n + 1);
+        if (!r)
+                return NULL;
+
+        for (i = 0; i < n; i++)
+                p = stpcpy(p, s);
+
+        *p = 0;
+        return r;
+}
+
+int split_pair(const char *s, const char *sep, char **l, char **r) {
+        char *x, *a, *b;
+
+        assert(s);
+        assert(sep);
+        assert(l);
+        assert(r);
+
+        if (isempty(sep))
+                return -EINVAL;
+
+        x = strstr(s, sep);
+        if (!x)
+                return -EINVAL;
+
+        a = strndup(s, x - s);
+        if (!a)
+                return -ENOMEM;
+
+        b = strdup(x + strlen(sep));
+        if (!b) {
+                free(a);
+                return -ENOMEM;
+        }
+
+        *l = a;
+        *r = b;
+
+        return 0;
+}
+
+int free_and_strdup(char **p, const char *s) {
+        char *t;
+
+        assert(p);
+
+        /* Replaces a string pointer with a strdup()ed new string,
+         * possibly freeing the old one. */
+
+        if (streq_ptr(*p, s))
+                return 0;
+
+        if (s) {
+                t = strdup(s);
+                if (!t)
+                        return -ENOMEM;
+        } else
+                t = NULL;
+
+        free(*p);
+        *p = t;
+
+        return 1;
+}
+
+int free_and_strndup(char **p, const char *s, size_t l) {
+        char *t;
+
+        assert(p);
+        assert(s || l == 0);
+
+        /* Replaces a string pointer with a strndup()ed new string,
+         * freeing the old one. */
+
+        if (!*p && !s)
+                return 0;
+
+        if (*p && s && strneq(*p, s, l) && (l > strlen(*p) || (*p)[l] == '\0'))
+                return 0;
+
+        if (s) {
+                t = strndup(s, l);
+                if (!t)
+                        return -ENOMEM;
+        } else
+                t = NULL;
+
+        free_and_replace(*p, t);
+        return 1;
+}
+
+#if !HAVE_EXPLICIT_BZERO
+/*
+ * Pointer to memset is volatile so that compiler must de-reference
+ * the pointer and can't assume that it points to any function in
+ * particular (such as memset, which it then might further "optimize")
+ * This approach is inspired by openssl's crypto/mem_clr.c.
+ */
+typedef void *(*memset_t)(void *,int,size_t);
+
+static volatile memset_t memset_func = memset;
+
+void* explicit_bzero_safe(void *p, size_t l) {
+        if (l > 0)
+                memset_func(p, '\0', l);
+
+        return p;
+}
+#endif
+
+char* string_erase(char *x) {
+        if (!x)
+                return NULL;
+
+        /* A delicious drop of snake-oil! To be called on memory where
+         * we stored passphrases or so, after we used them. */
+        explicit_bzero_safe(x, strlen(x));
+        return x;
+}
+
+char *string_free_erase(char *s) {
+        return mfree(string_erase(s));
+}
+
+bool string_is_safe(const char *p) {
+        const char *t;
+
+        if (!p)
+                return false;
+
+        for (t = p; *t; t++) {
+                if (*t > 0 && *t < ' ') /* no control characters */
+                        return false;
+
+                if (strchr(QUOTES "\\\x7f", *t))
+                        return false;
+        }
+
+        return true;
+}
diff --git a/shared/systemd/src/basic/string-util.h b/shared/systemd/src/basic/string-util.h
new file mode 100644
index 00000000..38070abb
--- /dev/null
+++ b/shared/systemd/src/basic/string-util.h
@@ -0,0 +1,266 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <alloca.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "macro.h"
+
+/* What is interpreted as whitespace? */
+#define WHITESPACE        " \t\n\r"
+#define NEWLINE           "\n\r"
+#define QUOTES            "\"\'"
+#define COMMENTS          "#;"
+#define GLOB_CHARS        "*?["
+#define DIGITS            "0123456789"
+#define LOWERCASE_LETTERS "abcdefghijklmnopqrstuvwxyz"
+#define UPPERCASE_LETTERS "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+#define LETTERS           LOWERCASE_LETTERS UPPERCASE_LETTERS
+#define ALPHANUMERICAL    LETTERS DIGITS
+#define HEXDIGITS         DIGITS "abcdefABCDEF"
+
+#define streq(a,b) (strcmp((a),(b)) == 0)
+#define strneq(a, b, n) (strncmp((a), (b), (n)) == 0)
+#define strcaseeq(a,b) (strcasecmp((a),(b)) == 0)
+#define strncaseeq(a, b, n) (strncasecmp((a), (b), (n)) == 0)
+
+int strcmp_ptr(const char *a, const char *b) _pure_;
+
+static inline bool streq_ptr(const char *a, const char *b) {
+        return strcmp_ptr(a, b) == 0;
+}
+
+static inline const char* strempty(const char *s) {
+        return s ?: "";
+}
+
+static inline const char* strnull(const char *s) {
+        return s ?: "(null)";
+}
+
+static inline const char *strna(const char *s) {
+        return s ?: "n/a";
+}
+
+static inline bool isempty(const char *p) {
+        return !p || !p[0];
+}
+
+static inline const char *empty_to_null(const char *p) {
+        return isempty(p) ? NULL : p;
+}
+
+static inline const char *empty_to_dash(const char *str) {
+        return isempty(str) ? "-" : str;
+}
+
+static inline char *startswith(const char *s, const char *prefix) {
+        size_t l;
+
+        l = strlen(prefix);
+        if (strncmp(s, prefix, l) == 0)
+                return (char*) s + l;
+
+        return NULL;
+}
+
+static inline char *startswith_no_case(const char *s, const char *prefix) {
+        size_t l;
+
+        l = strlen(prefix);
+        if (strncasecmp(s, prefix, l) == 0)
+                return (char*) s + l;
+
+        return NULL;
+}
+
+char *endswith(const char *s, const char *postfix) _pure_;
+char *endswith_no_case(const char *s, const char *postfix) _pure_;
+
+char *first_word(const char *s, const char *word) _pure_;
+
+typedef enum SplitFlags {
+        SPLIT_QUOTES                     = 0x01 << 0,
+        SPLIT_RELAX                      = 0x01 << 1,
+} SplitFlags;
+
+const char* split(const char **state, size_t *l, const char *separator, SplitFlags flags);
+
+#define FOREACH_WORD(word, length, s, state)                            \
+        _FOREACH_WORD(word, length, s, WHITESPACE, 0, state)
+
+#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state)       \
+        _FOREACH_WORD(word, length, s, separator, 0, state)
+
+#define _FOREACH_WORD(word, length, s, separator, flags, state)         \
+        for ((state) = (s), (word) = split(&(state), &(length), (separator), (flags)); (word); (word) = split(&(state), &(length), (separator), (flags)))
+
+char *strappend(const char *s, const char *suffix);
+char *strnappend(const char *s, const char *suffix, size_t length);
+
+char *strjoin_real(const char *x, ...) _sentinel_;
+#define strjoin(a, ...) strjoin_real((a), __VA_ARGS__, NULL)
+
+#define strjoina(a, ...)                                                \
+        ({                                                              \
+                const char *_appendees_[] = { a, __VA_ARGS__ };         \
+                char *_d_, *_p_;                                        \
+                size_t _len_ = 0;                                          \
+                size_t _i_;                                           \
+                for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \
+                        _len_ += strlen(_appendees_[_i_]);              \
+                _p_ = _d_ = newa(char, _len_ + 1);                      \
+                for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \
+                        _p_ = stpcpy(_p_, _appendees_[_i_]);            \
+                *_p_ = 0;                                               \
+                _d_;                                                    \
+        })
+
+char *strstrip(char *s);
+char *delete_chars(char *s, const char *bad);
+char *delete_trailing_chars(char *s, const char *bad);
+char *truncate_nl(char *s);
+
+static inline char *skip_leading_chars(const char *s, const char *bad) {
+
+        if (!s)
+                return NULL;
+
+        if (!bad)
+                bad = WHITESPACE;
+
+        return (char*) s + strspn(s, bad);
+}
+
+char ascii_tolower(char x);
+char *ascii_strlower(char *s);
+char *ascii_strlower_n(char *s, size_t n);
+
+char ascii_toupper(char x);
+char *ascii_strupper(char *s);
+
+int ascii_strcasecmp_n(const char *a, const char *b, size_t n);
+int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m);
+
+bool chars_intersect(const char *a, const char *b) _pure_;
+
+static inline bool _pure_ in_charset(const char *s, const char* charset) {
+        assert(s);
+        assert(charset);
+        return s[strspn(s, charset)] == '\0';
+}
+
+bool string_has_cc(const char *p, const char *ok) _pure_;
+
+char *ellipsize_mem(const char *s, size_t old_length_bytes, size_t new_length_columns, unsigned percent);
+static inline char *ellipsize(const char *s, size_t length, unsigned percent) {
+        return ellipsize_mem(s, strlen(s), length, percent);
+}
+
+char *cellescape(char *buf, size_t len, const char *s);
+
+/* This limit is arbitrary, enough to give some idea what the string contains */
+#define CELLESCAPE_DEFAULT_LENGTH 64
+
+bool nulstr_contains(const char *nulstr, const char *needle);
+
+char* strshorten(char *s, size_t l);
+
+char *strreplace(const char *text, const char *old_string, const char *new_string);
+
+char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]);
+
+char *strextend_with_separator(char **x, const char *separator, ...) _sentinel_;
+
+#define strextend(x, ...) strextend_with_separator(x, NULL, __VA_ARGS__)
+
+char *strrep(const char *s, unsigned n);
+
+int split_pair(const char *s, const char *sep, char **l, char **r);
+
+int free_and_strdup(char **p, const char *s);
+int free_and_strndup(char **p, const char *s, size_t l);
+
+/* Normal memmem() requires haystack to be nonnull, which is annoying for zero-length buffers */
+static inline void *memmem_safe(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) {
+
+        if (needlelen <= 0)
+                return (void*) haystack;
+
+        if (haystacklen < needlelen)
+                return NULL;
+
+        assert(haystack);
+        assert(needle);
+
+        return memmem(haystack, haystacklen, needle, needlelen);
+}
+
+#if HAVE_EXPLICIT_BZERO
+static inline void* explicit_bzero_safe(void *p, size_t l) {
+        if (l > 0)
+                explicit_bzero(p, l);
+
+        return p;
+}
+#else
+void *explicit_bzero_safe(void *p, size_t l);
+#endif
+
+char *string_erase(char *x);
+
+char *string_free_erase(char *s);
+DEFINE_TRIVIAL_CLEANUP_FUNC(char *, string_free_erase);
+#define _cleanup_string_free_erase_ _cleanup_(string_free_erasep)
+
+bool string_is_safe(const char *p) _pure_;
+
+static inline size_t strlen_ptr(const char *s) {
+        if (!s)
+                return 0;
+
+        return strlen(s);
+}
+
+/* Like startswith(), but operates on arbitrary memory blocks */
+static inline void *memory_startswith(const void *p, size_t sz, const char *token) {
+        size_t n;
+
+        assert(token);
+
+        n = strlen(token);
+        if (sz < n)
+                return NULL;
+
+        assert(p);
+
+        if (memcmp(p, token, n) != 0)
+                return NULL;
+
+        return (uint8_t*) p + n;
+}
+
+/* Like startswith_no_case(), but operates on arbitrary memory blocks.
+ * It works only for ASCII strings.
+ */
+static inline void *memory_startswith_no_case(const void *p, size_t sz, const char *token) {
+        size_t n, i;
+
+        assert(token);
+
+        n = strlen(token);
+        if (sz < n)
+                return NULL;
+
+        assert(p);
+
+        for (i = 0; i < n; i++) {
+                if (ascii_tolower(((char *)p)[i]) != ascii_tolower(token[i]))
+                        return NULL;
+        }
+
+        return (uint8_t*) p + n;
+}
diff --git a/shared/systemd/src/basic/strv.c b/shared/systemd/src/basic/strv.c
new file mode 100644
index 00000000..1615c9ba
--- /dev/null
+++ b/shared/systemd/src/basic/strv.c
@@ -0,0 +1,893 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <fnmatch.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "escape.h"
+#include "extract-word.h"
+#include "fileio.h"
+#include "string-util.h"
+#include "strv.h"
+#include "util.h"
+
+char *strv_find(char **l, const char *name) {
+        char **i;
+
+        assert(name);
+
+        STRV_FOREACH(i, l)
+                if (streq(*i, name))
+                        return *i;
+
+        return NULL;
+}
+
+char *strv_find_prefix(char **l, const char *name) {
+        char **i;
+
+        assert(name);
+
+        STRV_FOREACH(i, l)
+                if (startswith(*i, name))
+                        return *i;
+
+        return NULL;
+}
+
+char *strv_find_startswith(char **l, const char *name) {
+        char **i, *e;
+
+        assert(name);
+
+        /* Like strv_find_prefix, but actually returns only the
+         * suffix, not the whole item */
+
+        STRV_FOREACH(i, l) {
+                e = startswith(*i, name);
+                if (e)
+                        return e;
+        }
+
+        return NULL;
+}
+
+void strv_clear(char **l) {
+        char **k;
+
+        if (!l)
+                return;
+
+        for (k = l; *k; k++)
+                free(*k);
+
+        *l = NULL;
+}
+
+char **strv_free(char **l) {
+        strv_clear(l);
+        return mfree(l);
+}
+
+char **strv_free_erase(char **l) {
+        char **i;
+
+        STRV_FOREACH(i, l)
+                string_erase(*i);
+
+        return strv_free(l);
+}
+
+char **strv_copy(char * const *l) {
+        char **r, **k;
+
+        k = r = new(char*, strv_length(l) + 1);
+        if (!r)
+                return NULL;
+
+        if (l)
+                for (; *l; k++, l++) {
+                        *k = strdup(*l);
+                        if (!*k) {
+                                strv_free(r);
+                                return NULL;
+                        }
+                }
+
+        *k = NULL;
+        return r;
+}
+
+size_t strv_length(char * const *l) {
+        size_t n = 0;
+
+        if (!l)
+                return 0;
+
+        for (; *l; l++)
+                n++;
+
+        return n;
+}
+
+char **strv_new_ap(const char *x, va_list ap) {
+        const char *s;
+        _cleanup_strv_free_ char **a = NULL;
+        size_t n = 0, i = 0;
+        va_list aq;
+
+        /* As a special trick we ignore all listed strings that equal
+         * STRV_IGNORE. This is supposed to be used with the
+         * STRV_IFNOTNULL() macro to include possibly NULL strings in
+         * the string list. */
+
+        if (x) {
+                n = x == STRV_IGNORE ? 0 : 1;
+
+                va_copy(aq, ap);
+                while ((s = va_arg(aq, const char*))) {
+                        if (s == STRV_IGNORE)
+                                continue;
+
+                        n++;
+                }
+
+                va_end(aq);
+        }
+
+        a = new(char*, n+1);
+        if (!a)
+                return NULL;
+
+        if (x) {
+                if (x != STRV_IGNORE) {
+                        a[i] = strdup(x);
+                        if (!a[i])
+                                return NULL;
+                        i++;
+                }
+
+                while ((s = va_arg(ap, const char*))) {
+
+                        if (s == STRV_IGNORE)
+                                continue;
+
+                        a[i] = strdup(s);
+                        if (!a[i])
+                                return NULL;
+
+                        i++;
+                }
+        }
+
+        a[i] = NULL;
+
+        return TAKE_PTR(a);
+}
+
+char **strv_new_internal(const char *x, ...) {
+        char **r;
+        va_list ap;
+
+        va_start(ap, x);
+        r = strv_new_ap(x, ap);
+        va_end(ap);
+
+        return r;
+}
+
+int strv_extend_strv(char ***a, char **b, bool filter_duplicates) {
+        char **s, **t;
+        size_t p, q, i = 0, j;
+
+        assert(a);
+
+        if (strv_isempty(b))
+                return 0;
+
+        p = strv_length(*a);
+        q = strv_length(b);
+
+        t = reallocarray(*a, p + q + 1, sizeof(char *));
+        if (!t)
+                return -ENOMEM;
+
+        t[p] = NULL;
+        *a = t;
+
+        STRV_FOREACH(s, b) {
+
+                if (filter_duplicates && strv_contains(t, *s))
+                        continue;
+
+                t[p+i] = strdup(*s);
+                if (!t[p+i])
+                        goto rollback;
+
+                i++;
+                t[p+i] = NULL;
+        }
+
+        assert(i <= q);
+
+        return (int) i;
+
+rollback:
+        for (j = 0; j < i; j++)
+                free(t[p + j]);
+
+        t[p] = NULL;
+        return -ENOMEM;
+}
+
+int strv_extend_strv_concat(char ***a, char **b, const char *suffix) {
+        int r;
+        char **s;
+
+        STRV_FOREACH(s, b) {
+                char *v;
+
+                v = strappend(*s, suffix);
+                if (!v)
+                        return -ENOMEM;
+
+                r = strv_push(a, v);
+                if (r < 0) {
+                        free(v);
+                        return r;
+                }
+        }
+
+        return 0;
+}
+
+char **strv_split_full(const char *s, const char *separator, SplitFlags flags) {
+        const char *word, *state;
+        size_t l;
+        size_t n, i;
+        char **r;
+
+        assert(s);
+
+        if (!separator)
+                separator = WHITESPACE;
+
+        s += strspn(s, separator);
+        if (isempty(s))
+                return new0(char*, 1);
+
+        n = 0;
+        _FOREACH_WORD(word, l, s, separator, flags, state)
+                n++;
+
+        r = new(char*, n+1);
+        if (!r)
+                return NULL;
+
+        i = 0;
+        _FOREACH_WORD(word, l, s, separator, flags, state) {
+                r[i] = strndup(word, l);
+                if (!r[i]) {
+                        strv_free(r);
+                        return NULL;
+                }
+
+                i++;
+        }
+
+        r[i] = NULL;
+        return r;
+}
+
+char **strv_split_newlines(const char *s) {
+        char **l;
+        size_t n;
+
+        assert(s);
+
+        /* Special version of strv_split() that splits on newlines and
+         * suppresses an empty string at the end */
+
+        l = strv_split(s, NEWLINE);
+        if (!l)
+                return NULL;
+
+        n = strv_length(l);
+        if (n <= 0)
+                return l;
+
+        if (isempty(l[n - 1]))
+                l[n - 1] = mfree(l[n - 1]);
+
+        return l;
+}
+
+#if 0 /* NM_IGNORED */
+int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags) {
+        _cleanup_strv_free_ char **l = NULL;
+        size_t n = 0, allocated = 0;
+        int r;
+
+        assert(t);
+        assert(s);
+
+        for (;;) {
+                _cleanup_free_ char *word = NULL;
+
+                r = extract_first_word(&s, &word, separators, flags);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                if (!GREEDY_REALLOC(l, allocated, n + 2))
+                        return -ENOMEM;
+
+                l[n++] = TAKE_PTR(word);
+
+                l[n] = NULL;
+        }
+
+        if (!l) {
+                l = new0(char*, 1);
+                if (!l)
+                        return -ENOMEM;
+        }
+
+        *t = TAKE_PTR(l);
+
+        return (int) n;
+}
+#endif /* NM_IGNORED */
+
+char *strv_join_prefix(char **l, const char *separator, const char *prefix) {
+        char *r, *e;
+        char **s;
+        size_t n, k, m;
+
+        if (!separator)
+                separator = " ";
+
+        k = strlen(separator);
+        m = strlen_ptr(prefix);
+
+        n = 0;
+        STRV_FOREACH(s, l) {
+                if (s != l)
+                        n += k;
+                n += m + strlen(*s);
+        }
+
+        r = new(char, n+1);
+        if (!r)
+                return NULL;
+
+        e = r;
+        STRV_FOREACH(s, l) {
+                if (s != l)
+                        e = stpcpy(e, separator);
+
+                if (prefix)
+                        e = stpcpy(e, prefix);
+
+                e = stpcpy(e, *s);
+        }
+
+        *e = 0;
+
+        return r;
+}
+
+int strv_push(char ***l, char *value) {
+        char **c;
+        size_t n, m;
+
+        if (!value)
+                return 0;
+
+        n = strv_length(*l);
+
+        /* Increase and check for overflow */
+        m = n + 2;
+        if (m < n)
+                return -ENOMEM;
+
+        c = reallocarray(*l, m, sizeof(char*));
+        if (!c)
+                return -ENOMEM;
+
+        c[n] = value;
+        c[n+1] = NULL;
+
+        *l = c;
+        return 0;
+}
+
+int strv_push_pair(char ***l, char *a, char *b) {
+        char **c;
+        size_t n, m;
+
+        if (!a && !b)
+                return 0;
+
+        n = strv_length(*l);
+
+        /* increase and check for overflow */
+        m = n + !!a + !!b + 1;
+        if (m < n)
+                return -ENOMEM;
+
+        c = reallocarray(*l, m, sizeof(char*));
+        if (!c)
+                return -ENOMEM;
+
+        if (a)
+                c[n++] = a;
+        if (b)
+                c[n++] = b;
+        c[n] = NULL;
+
+        *l = c;
+        return 0;
+}
+
+int strv_insert(char ***l, size_t position, char *value) {
+        char **c;
+        size_t n, m, i;
+
+        if (!value)
+                return 0;
+
+        n = strv_length(*l);
+        position = MIN(position, n);
+
+        /* increase and check for overflow */
+        m = n + 2;
+        if (m < n)
+                return -ENOMEM;
+
+        c = new(char*, m);
+        if (!c)
+                return -ENOMEM;
+
+        for (i = 0; i < position; i++)
+                c[i] = (*l)[i];
+        c[position] = value;
+        for (i = position; i < n; i++)
+                c[i+1] = (*l)[i];
+
+        c[n+1] = NULL;
+
+        free(*l);
+        *l = c;
+
+        return 0;
+}
+
+int strv_consume(char ***l, char *value) {
+        int r;
+
+        r = strv_push(l, value);
+        if (r < 0)
+                free(value);
+
+        return r;
+}
+
+int strv_consume_pair(char ***l, char *a, char *b) {
+        int r;
+
+        r = strv_push_pair(l, a, b);
+        if (r < 0) {
+                free(a);
+                free(b);
+        }
+
+        return r;
+}
+
+int strv_consume_prepend(char ***l, char *value) {
+        int r;
+
+        r = strv_push_prepend(l, value);
+        if (r < 0)
+                free(value);
+
+        return r;
+}
+
+int strv_extend(char ***l, const char *value) {
+        char *v;
+
+        if (!value)
+                return 0;
+
+        v = strdup(value);
+        if (!v)
+                return -ENOMEM;
+
+        return strv_consume(l, v);
+}
+
+int strv_extend_front(char ***l, const char *value) {
+        size_t n, m;
+        char *v, **c;
+
+        assert(l);
+
+        /* Like strv_extend(), but prepends rather than appends the new entry */
+
+        if (!value)
+                return 0;
+
+        n = strv_length(*l);
+
+        /* Increase and overflow check. */
+        m = n + 2;
+        if (m < n)
+                return -ENOMEM;
+
+        v = strdup(value);
+        if (!v)
+                return -ENOMEM;
+
+        c = reallocarray(*l, m, sizeof(char*));
+        if (!c) {
+                free(v);
+                return -ENOMEM;
+        }
+
+        memmove(c+1, c, n * sizeof(char*));
+        c[0] = v;
+        c[n+1] = NULL;
+
+        *l = c;
+        return 0;
+}
+
+char **strv_uniq(char **l) {
+        char **i;
+
+        /* Drops duplicate entries. The first identical string will be
+         * kept, the others dropped */
+
+        STRV_FOREACH(i, l)
+                strv_remove(i+1, *i);
+
+        return l;
+}
+
+bool strv_is_uniq(char **l) {
+        char **i;
+
+        STRV_FOREACH(i, l)
+                if (strv_find(i+1, *i))
+                        return false;
+
+        return true;
+}
+
+char **strv_remove(char **l, const char *s) {
+        char **f, **t;
+
+        if (!l)
+                return NULL;
+
+        assert(s);
+
+        /* Drops every occurrence of s in the string list, edits
+         * in-place. */
+
+        for (f = t = l; *f; f++)
+                if (streq(*f, s))
+                        free(*f);
+                else
+                        *(t++) = *f;
+
+        *t = NULL;
+        return l;
+}
+
+char **strv_parse_nulstr(const char *s, size_t l) {
+        /* l is the length of the input data, which will be split at NULs into
+         * elements of the resulting strv. Hence, the number of items in the resulting strv
+         * will be equal to one plus the number of NUL bytes in the l bytes starting at s,
+         * unless s[l-1] is NUL, in which case the final empty string is not stored in
+         * the resulting strv, and length is equal to the number of NUL bytes.
+         *
+         * Note that contrary to a normal nulstr which cannot contain empty strings, because
+         * the input data is terminated by any two consequent NUL bytes, this parser accepts
+         * empty strings in s.
+         */
+
+        const char *p;
+        size_t c = 0, i = 0;
+        char **v;
+
+        assert(s || l <= 0);
+
+        if (l <= 0)
+                return new0(char*, 1);
+
+        for (p = s; p < s + l; p++)
+                if (*p == 0)
+                        c++;
+
+        if (s[l-1] != 0)
+                c++;
+
+        v = new0(char*, c+1);
+        if (!v)
+                return NULL;
+
+        p = s;
+        while (p < s + l) {
+                const char *e;
+
+                e = memchr(p, 0, s + l - p);
+
+                v[i] = strndup(p, e ? e - p : s + l - p);
+                if (!v[i]) {
+                        strv_free(v);
+                        return NULL;
+                }
+
+                i++;
+
+                if (!e)
+                        break;
+
+                p = e + 1;
+        }
+
+        assert(i == c);
+
+        return v;
+}
+
+char **strv_split_nulstr(const char *s) {
+        const char *i;
+        char **r = NULL;
+
+        NULSTR_FOREACH(i, s)
+                if (strv_extend(&r, i) < 0) {
+                        strv_free(r);
+                        return NULL;
+                }
+
+        if (!r)
+                return strv_new(NULL);
+
+        return r;
+}
+
+int strv_make_nulstr(char **l, char **p, size_t *q) {
+        /* A valid nulstr with two NULs at the end will be created, but
+         * q will be the length without the two trailing NULs. Thus the output
+         * string is a valid nulstr and can be iterated over using NULSTR_FOREACH,
+         * and can also be parsed by strv_parse_nulstr as long as the length
+         * is provided separately.
+         */
+
+        size_t n_allocated = 0, n = 0;
+        _cleanup_free_ char *m = NULL;
+        char **i;
+
+        assert(p);
+        assert(q);
+
+        STRV_FOREACH(i, l) {
+                size_t z;
+
+                z = strlen(*i);
+
+                if (!GREEDY_REALLOC(m, n_allocated, n + z + 2))
+                        return -ENOMEM;
+
+                memcpy(m + n, *i, z + 1);
+                n += z + 1;
+        }
+
+        if (!m) {
+                m = new0(char, 1);
+                if (!m)
+                        return -ENOMEM;
+                n = 1;
+        } else
+                /* make sure there is a second extra NUL at the end of resulting nulstr */
+                m[n] = '\0';
+
+        assert(n > 0);
+        *p = m;
+        *q = n - 1;
+
+        m = NULL;
+
+        return 0;
+}
+
+bool strv_overlap(char **a, char **b) {
+        char **i;
+
+        STRV_FOREACH(i, a)
+                if (strv_contains(b, *i))
+                        return true;
+
+        return false;
+}
+
+static int str_compare(char * const *a, char * const *b) {
+        return strcmp(*a, *b);
+}
+
+char **strv_sort(char **l) {
+        typesafe_qsort(l, strv_length(l), str_compare);
+        return l;
+}
+
+bool strv_equal(char **a, char **b) {
+
+        if (strv_isempty(a))
+                return strv_isempty(b);
+
+        if (strv_isempty(b))
+                return false;
+
+        for ( ; *a || *b; ++a, ++b)
+                if (!streq_ptr(*a, *b))
+                        return false;
+
+        return true;
+}
+
+void strv_print(char **l) {
+        char **s;
+
+        STRV_FOREACH(s, l)
+                puts(*s);
+}
+
+int strv_extendf(char ***l, const char *format, ...) {
+        va_list ap;
+        char *x;
+        int r;
+
+        va_start(ap, format);
+        r = vasprintf(&x, format, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return -ENOMEM;
+
+        return strv_consume(l, x);
+}
+
+char **strv_reverse(char **l) {
+        size_t n, i;
+
+        n = strv_length(l);
+        if (n <= 1)
+                return l;
+
+        for (i = 0; i < n / 2; i++)
+                SWAP_TWO(l[i], l[n-1-i]);
+
+        return l;
+}
+
+char **strv_shell_escape(char **l, const char *bad) {
+        char **s;
+
+        /* Escapes every character in every string in l that is in bad,
+         * edits in-place, does not roll-back on error. */
+
+        STRV_FOREACH(s, l) {
+                char *v;
+
+                v = shell_escape(*s, bad);
+                if (!v)
+                        return NULL;
+
+                free(*s);
+                *s = v;
+        }
+
+        return l;
+}
+
+bool strv_fnmatch(char* const* patterns, const char *s, int flags) {
+        char* const* p;
+
+        STRV_FOREACH(p, patterns)
+                if (fnmatch(*p, s, flags) == 0)
+                        return true;
+
+        return false;
+}
+
+char ***strv_free_free(char ***l) {
+        char ***i;
+
+        if (!l)
+                return NULL;
+
+        for (i = l; *i; i++)
+                strv_free(*i);
+
+        return mfree(l);
+}
+
+char **strv_skip(char **l, size_t n) {
+
+        while (n > 0) {
+                if (strv_isempty(l))
+                        return l;
+
+                l++, n--;
+        }
+
+        return l;
+}
+
+int strv_extend_n(char ***l, const char *value, size_t n) {
+        size_t i, j, k;
+        char **nl;
+
+        assert(l);
+
+        if (!value)
+                return 0;
+        if (n == 0)
+                return 0;
+
+        /* Adds the value n times to l */
+
+        k = strv_length(*l);
+
+        nl = reallocarray(*l, k + n + 1, sizeof(char *));
+        if (!nl)
+                return -ENOMEM;
+
+        *l = nl;
+
+        for (i = k; i < k + n; i++) {
+                nl[i] = strdup(value);
+                if (!nl[i])
+                        goto rollback;
+        }
+
+        nl[i] = NULL;
+        return 0;
+
+rollback:
+        for (j = k; j < i; j++)
+                free(nl[j]);
+
+        nl[k] = NULL;
+        return -ENOMEM;
+}
+
+int fputstrv(FILE *f, char **l, const char *separator, bool *space) {
+        bool b = false;
+        char **s;
+        int r;
+
+        /* Like fputs(), but for strv, and with a less stupid argument order */
+
+        if (!space)
+                space = &b;
+
+        STRV_FOREACH(s, l) {
+                r = fputs_with_space(f, *s, separator, space);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
diff --git a/shared/systemd/src/basic/strv.h b/shared/systemd/src/basic/strv.h
new file mode 100644
index 00000000..392cab65
--- /dev/null
+++ b/shared/systemd/src/basic/strv.h
@@ -0,0 +1,190 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <fnmatch.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "macro.h"
+#include "string-util.h"
+#include "util.h"
+
+char *strv_find(char **l, const char *name) _pure_;
+char *strv_find_prefix(char **l, const char *name) _pure_;
+char *strv_find_startswith(char **l, const char *name) _pure_;
+
+char **strv_free(char **l);
+DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free);
+#define _cleanup_strv_free_ _cleanup_(strv_freep)
+
+char **strv_free_erase(char **l);
+DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase);
+#define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep)
+
+void strv_clear(char **l);
+
+char **strv_copy(char * const *l);
+size_t strv_length(char * const *l) _pure_;
+
+int strv_extend_strv(char ***a, char **b, bool filter_duplicates);
+int strv_extend_strv_concat(char ***a, char **b, const char *suffix);
+int strv_extend(char ***l, const char *value);
+int strv_extendf(char ***l, const char *format, ...) _printf_(2,0);
+int strv_extend_front(char ***l, const char *value);
+int strv_push(char ***l, char *value);
+int strv_push_pair(char ***l, char *a, char *b);
+int strv_insert(char ***l, size_t position, char *value);
+
+static inline int strv_push_prepend(char ***l, char *value) {
+        return strv_insert(l, 0, value);
+}
+
+int strv_consume(char ***l, char *value);
+int strv_consume_pair(char ***l, char *a, char *b);
+int strv_consume_prepend(char ***l, char *value);
+
+char **strv_remove(char **l, const char *s);
+char **strv_uniq(char **l);
+bool strv_is_uniq(char **l);
+
+bool strv_equal(char **a, char **b);
+
+#define strv_contains(l, s) (!!strv_find((l), (s)))
+
+char **strv_new_internal(const char *x, ...) _sentinel_;
+char **strv_new_ap(const char *x, va_list ap);
+#define strv_new(...) strv_new_internal(__VA_ARGS__, NULL)
+
+#define STRV_IGNORE ((const char *) -1)
+
+static inline const char* STRV_IFNOTNULL(const char *x) {
+        return x ? x : STRV_IGNORE;
+}
+
+static inline bool strv_isempty(char * const *l) {
+        return !l || !*l;
+}
+
+char **strv_split_full(const char *s, const char *separator, SplitFlags flags);
+static inline char **strv_split(const char *s, const char *separator) {
+        return strv_split_full(s, separator, 0);
+}
+char **strv_split_newlines(const char *s);
+
+int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags);
+
+char *strv_join_prefix(char **l, const char *separator, const char *prefix);
+static inline char *strv_join(char **l, const char *separator) {
+        return strv_join_prefix(l, separator, NULL);
+}
+
+char **strv_parse_nulstr(const char *s, size_t l);
+char **strv_split_nulstr(const char *s);
+int strv_make_nulstr(char **l, char **p, size_t *n);
+
+bool strv_overlap(char **a, char **b) _pure_;
+
+#define STRV_FOREACH(s, l)                      \
+        for ((s) = (l); (s) && *(s); (s)++)
+
+#define STRV_FOREACH_BACKWARDS(s, l)                                \
+        for (s = ({                                                 \
+                        char **_l = l;                              \
+                        _l ? _l + strv_length(_l) - 1U : NULL;      \
+                        });                                         \
+             (l) && ((s) >= (l));                                   \
+             (s)--)
+
+#define STRV_FOREACH_PAIR(x, y, l)               \
+        for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1))
+
+char **strv_sort(char **l);
+void strv_print(char **l);
+
+#define STRV_MAKE(...) ((char**) ((const char*[]) { __VA_ARGS__, NULL }))
+
+#define STRV_MAKE_EMPTY ((char*[1]) { NULL })
+
+#define strv_from_stdarg_alloca(first)                          \
+        ({                                                      \
+                char **_l;                                      \
+                                                                \
+                if (!first)                                     \
+                        _l = (char**) &first;                   \
+                else {                                          \
+                        size_t _n;                              \
+                        va_list _ap;                            \
+                                                                \
+                        _n = 1;                                 \
+                        va_start(_ap, first);                   \
+                        while (va_arg(_ap, char*))              \
+                                _n++;                           \
+                        va_end(_ap);                            \
+                                                                \
+                        _l = newa(char*, _n+1);                 \
+                        _l[_n = 0] = (char*) first;             \
+                        va_start(_ap, first);                   \
+                        for (;;) {                              \
+                                _l[++_n] = va_arg(_ap, char*);  \
+                                if (!_l[_n])                    \
+                                        break;                  \
+                        }                                       \
+                        va_end(_ap);                            \
+                }                                               \
+                _l;                                             \
+        })
+
+#define STR_IN_SET(x, ...) strv_contains(STRV_MAKE(__VA_ARGS__), x)
+#define STRPTR_IN_SET(x, ...)                                    \
+        ({                                                       \
+                const char* _x = (x);                            \
+                _x && strv_contains(STRV_MAKE(__VA_ARGS__), _x); \
+        })
+
+#define STARTSWITH_SET(p, ...)                                  \
+        ({                                                      \
+                const char *_p = (p);                           \
+                char  *_found = NULL, **_i;                     \
+                STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) {      \
+                        _found = startswith(_p, *_i);           \
+                        if (_found)                             \
+                                break;                          \
+                }                                               \
+                _found;                                         \
+        })
+
+#define FOREACH_STRING(x, y, ...)                                       \
+        for (char **_l = STRV_MAKE(({ x = y; }), ##__VA_ARGS__);        \
+             x;                                                         \
+             x = *(++_l))
+
+char **strv_reverse(char **l);
+char **strv_shell_escape(char **l, const char *bad);
+
+bool strv_fnmatch(char* const* patterns, const char *s, int flags);
+
+static inline bool strv_fnmatch_or_empty(char* const* patterns, const char *s, int flags) {
+        assert(s);
+        return strv_isempty(patterns) ||
+               strv_fnmatch(patterns, s, flags);
+}
+
+char ***strv_free_free(char ***l);
+DEFINE_TRIVIAL_CLEANUP_FUNC(char***, strv_free_free);
+
+char **strv_skip(char **l, size_t n);
+
+int strv_extend_n(char ***l, const char *value, size_t n);
+
+int fputstrv(FILE *f, char **l, const char *separator, bool *space);
+
+#define strv_free_and_replace(a, b)             \
+        ({                                      \
+                strv_free(a);                   \
+                (a) = (b);                      \
+                (b) = NULL;                     \
+                0;                              \
+        })
diff --git a/shared/systemd/src/basic/time-util.c b/shared/systemd/src/basic/time-util.c
new file mode 100644
index 00000000..9ea0380a
--- /dev/null
+++ b/shared/systemd/src/basic/time-util.c
@@ -0,0 +1,1488 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/timerfd.h>
+#include <sys/timex.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "io-util.h"
+#include "log.h"
+#include "macro.h"
+#include "missing_timerfd.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "stat-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "time-util.h"
+
+static clockid_t map_clock_id(clockid_t c) {
+
+        /* Some more exotic archs (s390, ppc, …) lack the "ALARM" flavour of the clocks. Thus, clock_gettime() will
+         * fail for them. Since they are essentially the same as their non-ALARM pendants (their only difference is
+         * when timers are set on them), let's just map them accordingly. This way, we can get the correct time even on
+         * those archs. */
+
+        switch (c) {
+
+        case CLOCK_BOOTTIME_ALARM:
+                return CLOCK_BOOTTIME;
+
+        case CLOCK_REALTIME_ALARM:
+                return CLOCK_REALTIME;
+
+        default:
+                return c;
+        }
+}
+
+usec_t now(clockid_t clock_id) {
+        struct timespec ts;
+
+        assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0);
+
+        return timespec_load(&ts);
+}
+
+nsec_t now_nsec(clockid_t clock_id) {
+        struct timespec ts;
+
+        assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0);
+
+        return timespec_load_nsec(&ts);
+}
+
+dual_timestamp* dual_timestamp_get(dual_timestamp *ts) {
+        assert(ts);
+
+        ts->realtime = now(CLOCK_REALTIME);
+        ts->monotonic = now(CLOCK_MONOTONIC);
+
+        return ts;
+}
+
+triple_timestamp* triple_timestamp_get(triple_timestamp *ts) {
+        assert(ts);
+
+        ts->realtime = now(CLOCK_REALTIME);
+        ts->monotonic = now(CLOCK_MONOTONIC);
+        ts->boottime = clock_boottime_supported() ? now(CLOCK_BOOTTIME) : USEC_INFINITY;
+
+        return ts;
+}
+
+dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
+        int64_t delta;
+        assert(ts);
+
+        if (u == USEC_INFINITY || u <= 0) {
+                ts->realtime = ts->monotonic = u;
+                return ts;
+        }
+
+        ts->realtime = u;
+
+        delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
+        ts->monotonic = usec_sub_signed(now(CLOCK_MONOTONIC), delta);
+
+        return ts;
+}
+
+triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) {
+        int64_t delta;
+
+        assert(ts);
+
+        if (u == USEC_INFINITY || u <= 0) {
+                ts->realtime = ts->monotonic = ts->boottime = u;
+                return ts;
+        }
+
+        ts->realtime = u;
+        delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
+        ts->monotonic = usec_sub_signed(now(CLOCK_MONOTONIC), delta);
+        ts->boottime = clock_boottime_supported() ? usec_sub_signed(now(CLOCK_BOOTTIME), delta) : USEC_INFINITY;
+
+        return ts;
+}
+
+dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
+        int64_t delta;
+        assert(ts);
+
+        if (u == USEC_INFINITY) {
+                ts->realtime = ts->monotonic = USEC_INFINITY;
+                return ts;
+        }
+
+        ts->monotonic = u;
+        delta = (int64_t) now(CLOCK_MONOTONIC) - (int64_t) u;
+        ts->realtime = usec_sub_signed(now(CLOCK_REALTIME), delta);
+
+        return ts;
+}
+
+dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u) {
+        int64_t delta;
+
+        if (u == USEC_INFINITY) {
+                ts->realtime = ts->monotonic = USEC_INFINITY;
+                return ts;
+        }
+
+        dual_timestamp_get(ts);
+        delta = (int64_t) now(clock_boottime_or_monotonic()) - (int64_t) u;
+        ts->realtime = usec_sub_signed(ts->realtime, delta);
+        ts->monotonic = usec_sub_signed(ts->monotonic, delta);
+
+        return ts;
+}
+
+usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) {
+
+        switch (clock) {
+
+        case CLOCK_REALTIME:
+        case CLOCK_REALTIME_ALARM:
+                return ts->realtime;
+
+        case CLOCK_MONOTONIC:
+                return ts->monotonic;
+
+        case CLOCK_BOOTTIME:
+        case CLOCK_BOOTTIME_ALARM:
+                return ts->boottime;
+
+        default:
+                return USEC_INFINITY;
+        }
+}
+
+usec_t timespec_load(const struct timespec *ts) {
+        assert(ts);
+
+        if (ts->tv_sec < 0 || ts->tv_nsec < 0)
+                return USEC_INFINITY;
+
+        if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC)
+                return USEC_INFINITY;
+
+        return
+                (usec_t) ts->tv_sec * USEC_PER_SEC +
+                (usec_t) ts->tv_nsec / NSEC_PER_USEC;
+}
+
+nsec_t timespec_load_nsec(const struct timespec *ts) {
+        assert(ts);
+
+        if (ts->tv_sec < 0 || ts->tv_nsec < 0)
+                return NSEC_INFINITY;
+
+        if ((nsec_t) ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC)
+                return NSEC_INFINITY;
+
+        return (nsec_t) ts->tv_sec * NSEC_PER_SEC + (nsec_t) ts->tv_nsec;
+}
+
+struct timespec *timespec_store(struct timespec *ts, usec_t u)  {
+        assert(ts);
+
+        if (u == USEC_INFINITY ||
+            u / USEC_PER_SEC >= TIME_T_MAX) {
+                ts->tv_sec = (time_t) -1;
+                ts->tv_nsec = (long) -1;
+                return ts;
+        }
+
+        ts->tv_sec = (time_t) (u / USEC_PER_SEC);
+        ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC);
+
+        return ts;
+}
+
+#if 0 /* NM_IGNORED */
+usec_t timeval_load(const struct timeval *tv) {
+        assert(tv);
+
+        if (tv->tv_sec < 0 || tv->tv_usec < 0)
+                return USEC_INFINITY;
+
+        if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC)
+                return USEC_INFINITY;
+
+        return
+                (usec_t) tv->tv_sec * USEC_PER_SEC +
+                (usec_t) tv->tv_usec;
+}
+
+struct timeval *timeval_store(struct timeval *tv, usec_t u) {
+        assert(tv);
+
+        if (u == USEC_INFINITY ||
+            u / USEC_PER_SEC > TIME_T_MAX) {
+                tv->tv_sec = (time_t) -1;
+                tv->tv_usec = (suseconds_t) -1;
+        } else {
+                tv->tv_sec = (time_t) (u / USEC_PER_SEC);
+                tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC);
+        }
+
+        return tv;
+}
+
+static char *format_timestamp_internal(
+                char *buf,
+                size_t l,
+                usec_t t,
+                bool utc,
+                bool us) {
+
+        /* The weekdays in non-localized (English) form. We use this instead of the localized form, so that our
+         * generated timestamps may be parsed with parse_timestamp(), and always read the same. */
+        static const char * const weekdays[] = {
+                [0] = "Sun",
+                [1] = "Mon",
+                [2] = "Tue",
+                [3] = "Wed",
+                [4] = "Thu",
+                [5] = "Fri",
+                [6] = "Sat",
+        };
+
+        struct tm tm;
+        time_t sec;
+        size_t n;
+
+        assert(buf);
+
+        if (l <
+            3 +                  /* week day */
+            1 + 10 +             /* space and date */
+            1 + 8 +              /* space and time */
+            (us ? 1 + 6 : 0) +   /* "." and microsecond part */
+            1 + 1 +              /* space and shortest possible zone */
+            1)
+                return NULL; /* Not enough space even for the shortest form. */
+        if (t <= 0 || t == USEC_INFINITY)
+                return NULL; /* Timestamp is unset */
+
+        /* Let's not format times with years > 9999 */
+        if (t > USEC_TIMESTAMP_FORMATTABLE_MAX) {
+                assert(l >= STRLEN("--- XXXX-XX-XX XX:XX:XX") + 1);
+                strcpy(buf, "--- XXXX-XX-XX XX:XX:XX");
+                return buf;
+        }
+
+        sec = (time_t) (t / USEC_PER_SEC); /* Round down */
+
+        if (!localtime_or_gmtime_r(&sec, &tm, utc))
+                return NULL;
+
+        /* Start with the week day */
+        assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays));
+        memcpy(buf, weekdays[tm.tm_wday], 4);
+
+        /* Add the main components */
+        if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0)
+                return NULL; /* Doesn't fit */
+
+        /* Append the microseconds part, if that's requested */
+        if (us) {
+                n = strlen(buf);
+                if (n + 8 > l)
+                        return NULL; /* Microseconds part doesn't fit. */
+
+                sprintf(buf + n, ".%06"PRI_USEC, t % USEC_PER_SEC);
+        }
+
+        /* Append the timezone */
+        n = strlen(buf);
+        if (utc) {
+                /* If this is UTC then let's explicitly use the "UTC" string here, because gmtime_r() normally uses the
+                 * obsolete "GMT" instead. */
+                if (n + 5 > l)
+                        return NULL; /* "UTC" doesn't fit. */
+
+                strcpy(buf + n, " UTC");
+
+        } else if (!isempty(tm.tm_zone)) {
+                size_t tn;
+
+                /* An explicit timezone is specified, let's use it, if it fits */
+                tn = strlen(tm.tm_zone);
+                if (n + 1 + tn + 1 > l) {
+                        /* The full time zone does not fit in. Yuck. */
+
+                        if (n + 1 + _POSIX_TZNAME_MAX + 1 > l)
+                                return NULL; /* Not even enough space for the POSIX minimum (of 6)? In that case, complain that it doesn't fit */
+
+                        /* So the time zone doesn't fit in fully, but the caller passed enough space for the POSIX
+                         * minimum time zone length. In this case suppress the timezone entirely, in order not to dump
+                         * an overly long, hard to read string on the user. This should be safe, because the user will
+                         * assume the local timezone anyway if none is shown. And so does parse_timestamp(). */
+                } else {
+                        buf[n++] = ' ';
+                        strcpy(buf + n, tm.tm_zone);
+                }
+        }
+
+        return buf;
+}
+
+char *format_timestamp(char *buf, size_t l, usec_t t) {
+        return format_timestamp_internal(buf, l, t, false, false);
+}
+
+char *format_timestamp_utc(char *buf, size_t l, usec_t t) {
+        return format_timestamp_internal(buf, l, t, true, false);
+}
+
+char *format_timestamp_us(char *buf, size_t l, usec_t t) {
+        return format_timestamp_internal(buf, l, t, false, true);
+}
+
+char *format_timestamp_us_utc(char *buf, size_t l, usec_t t) {
+        return format_timestamp_internal(buf, l, t, true, true);
+}
+
+char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
+        const char *s;
+        usec_t n, d;
+
+        if (t <= 0 || t == USEC_INFINITY)
+                return NULL;
+
+        n = now(CLOCK_REALTIME);
+        if (n > t) {
+                d = n - t;
+                s = "ago";
+        } else {
+                d = t - n;
+                s = "left";
+        }
+
+        if (d >= USEC_PER_YEAR)
+                snprintf(buf, l, USEC_FMT " years " USEC_FMT " months %s",
+                         d / USEC_PER_YEAR,
+                         (d % USEC_PER_YEAR) / USEC_PER_MONTH, s);
+        else if (d >= USEC_PER_MONTH)
+                snprintf(buf, l, USEC_FMT " months " USEC_FMT " days %s",
+                         d / USEC_PER_MONTH,
+                         (d % USEC_PER_MONTH) / USEC_PER_DAY, s);
+        else if (d >= USEC_PER_WEEK)
+                snprintf(buf, l, USEC_FMT " weeks " USEC_FMT " days %s",
+                         d / USEC_PER_WEEK,
+                         (d % USEC_PER_WEEK) / USEC_PER_DAY, s);
+        else if (d >= 2*USEC_PER_DAY)
+                snprintf(buf, l, USEC_FMT " days %s", d / USEC_PER_DAY, s);
+        else if (d >= 25*USEC_PER_HOUR)
+                snprintf(buf, l, "1 day " USEC_FMT "h %s",
+                         (d - USEC_PER_DAY) / USEC_PER_HOUR, s);
+        else if (d >= 6*USEC_PER_HOUR)
+                snprintf(buf, l, USEC_FMT "h %s",
+                         d / USEC_PER_HOUR, s);
+        else if (d >= USEC_PER_HOUR)
+                snprintf(buf, l, USEC_FMT "h " USEC_FMT "min %s",
+                         d / USEC_PER_HOUR,
+                         (d % USEC_PER_HOUR) / USEC_PER_MINUTE, s);
+        else if (d >= 5*USEC_PER_MINUTE)
+                snprintf(buf, l, USEC_FMT "min %s",
+                         d / USEC_PER_MINUTE, s);
+        else if (d >= USEC_PER_MINUTE)
+                snprintf(buf, l, USEC_FMT "min " USEC_FMT "s %s",
+                         d / USEC_PER_MINUTE,
+                         (d % USEC_PER_MINUTE) / USEC_PER_SEC, s);
+        else if (d >= USEC_PER_SEC)
+                snprintf(buf, l, USEC_FMT "s %s",
+                         d / USEC_PER_SEC, s);
+        else if (d >= USEC_PER_MSEC)
+                snprintf(buf, l, USEC_FMT "ms %s",
+                         d / USEC_PER_MSEC, s);
+        else if (d > 0)
+                snprintf(buf, l, USEC_FMT"us %s",
+                         d, s);
+        else
+                snprintf(buf, l, "now");
+
+        buf[l-1] = 0;
+        return buf;
+}
+#endif /* NM_IGNORED */
+
+char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
+        static const struct {
+                const char *suffix;
+                usec_t usec;
+        } table[] = {
+                { "y",     USEC_PER_YEAR   },
+                { "month", USEC_PER_MONTH  },
+                { "w",     USEC_PER_WEEK   },
+                { "d",     USEC_PER_DAY    },
+                { "h",     USEC_PER_HOUR   },
+                { "min",   USEC_PER_MINUTE },
+                { "s",     USEC_PER_SEC    },
+                { "ms",    USEC_PER_MSEC   },
+                { "us",    1               },
+        };
+
+        size_t i;
+        char *p = buf;
+        bool something = false;
+
+        assert(buf);
+        assert(l > 0);
+
+        if (t == USEC_INFINITY) {
+                strncpy(p, "infinity", l-1);
+                p[l-1] = 0;
+                return p;
+        }
+
+        if (t <= 0) {
+                strncpy(p, "0", l-1);
+                p[l-1] = 0;
+                return p;
+        }
+
+        /* The result of this function can be parsed with parse_sec */
+
+        for (i = 0; i < ELEMENTSOF(table); i++) {
+                int k = 0;
+                size_t n;
+                bool done = false;
+                usec_t a, b;
+
+                if (t <= 0)
+                        break;
+
+                if (t < accuracy && something)
+                        break;
+
+                if (t < table[i].usec)
+                        continue;
+
+                if (l <= 1)
+                        break;
+
+                a = t / table[i].usec;
+                b = t % table[i].usec;
+
+                /* Let's see if we should shows this in dot notation */
+                if (t < USEC_PER_MINUTE && b > 0) {
+                        usec_t cc;
+                        signed char j;
+
+                        j = 0;
+                        for (cc = table[i].usec; cc > 1; cc /= 10)
+                                j++;
+
+                        for (cc = accuracy; cc > 1; cc /= 10) {
+                                b /= 10;
+                                j--;
+                        }
+
+                        if (j > 0) {
+                                k = snprintf(p, l,
+                                             "%s"USEC_FMT".%0*"PRI_USEC"%s",
+                                             p > buf ? " " : "",
+                                             a,
+                                             j,
+                                             b,
+                                             table[i].suffix);
+
+                                t = 0;
+                                done = true;
+                        }
+                }
+
+                /* No? Then let's show it normally */
+                if (!done) {
+                        k = snprintf(p, l,
+                                     "%s"USEC_FMT"%s",
+                                     p > buf ? " " : "",
+                                     a,
+                                     table[i].suffix);
+
+                        t = b;
+                }
+
+                n = MIN((size_t) k, l);
+
+                l -= n;
+                p += n;
+
+                something = true;
+        }
+
+        *p = 0;
+
+        return buf;
+}
+
+#if 0 /* NM_IGNORED */
+static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
+        static const struct {
+                const char *name;
+                const int nr;
+        } day_nr[] = {
+                { "Sunday",    0 },
+                { "Sun",       0 },
+                { "Monday",    1 },
+                { "Mon",       1 },
+                { "Tuesday",   2 },
+                { "Tue",       2 },
+                { "Wednesday", 3 },
+                { "Wed",       3 },
+                { "Thursday",  4 },
+                { "Thu",       4 },
+                { "Friday",    5 },
+                { "Fri",       5 },
+                { "Saturday",  6 },
+                { "Sat",       6 },
+        };
+
+        const char *k, *utc = NULL, *tzn = NULL;
+        struct tm tm, copy;
+        time_t x;
+        usec_t x_usec, plus = 0, minus = 0, ret;
+        int r, weekday = -1, dst = -1;
+        size_t i;
+
+        /* Allowed syntaxes:
+         *
+         *   2012-09-22 16:34:22
+         *   2012-09-22 16:34     (seconds will be set to 0)
+         *   2012-09-22           (time will be set to 00:00:00)
+         *   16:34:22             (date will be set to today)
+         *   16:34                (date will be set to today, seconds to 0)
+         *   now
+         *   yesterday            (time is set to 00:00:00)
+         *   today                (time is set to 00:00:00)
+         *   tomorrow             (time is set to 00:00:00)
+         *   +5min
+         *   -5days
+         *   @2147483647          (seconds since epoch)
+         */
+
+        assert(t);
+        assert(usec);
+
+        if (t[0] == '@' && !with_tz)
+                return parse_sec(t + 1, usec);
+
+        ret = now(CLOCK_REALTIME);
+
+        if (!with_tz) {
+                if (streq(t, "now"))
+                        goto finish;
+
+                else if (t[0] == '+') {
+                        r = parse_sec(t+1, &plus);
+                        if (r < 0)
+                                return r;
+
+                        goto finish;
+
+                } else if (t[0] == '-') {
+                        r = parse_sec(t+1, &minus);
+                        if (r < 0)
+                                return r;
+
+                        goto finish;
+
+                } else if ((k = endswith(t, " ago"))) {
+                        t = strndupa(t, k - t);
+
+                        r = parse_sec(t, &minus);
+                        if (r < 0)
+                                return r;
+
+                        goto finish;
+
+                } else if ((k = endswith(t, " left"))) {
+                        t = strndupa(t, k - t);
+
+                        r = parse_sec(t, &plus);
+                        if (r < 0)
+                                return r;
+
+                        goto finish;
+                }
+
+                /* See if the timestamp is suffixed with UTC */
+                utc = endswith_no_case(t, " UTC");
+                if (utc)
+                        t = strndupa(t, utc - t);
+                else {
+                        const char *e = NULL;
+                        int j;
+
+                        tzset();
+
+                        /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note that we only
+                         * support the local timezones here, nothing else. Not because we wouldn't want to, but simply because
+                         * there are no nice APIs available to cover this. By accepting the local time zone strings, we make
+                         * sure that all timestamps written by format_timestamp() can be parsed correctly, even though we don't
+                         * support arbitrary timezone specifications. */
+
+                        for (j = 0; j <= 1; j++) {
+
+                                if (isempty(tzname[j]))
+                                        continue;
+
+                                e = endswith_no_case(t, tzname[j]);
+                                if (!e)
+                                        continue;
+                                if (e == t)
+                                        continue;
+                                if (e[-1] != ' ')
+                                        continue;
+
+                                break;
+                        }
+
+                        if (IN_SET(j, 0, 1)) {
+                                /* Found one of the two timezones specified. */
+                                t = strndupa(t, e - t - 1);
+                                dst = j;
+                                tzn = tzname[j];
+                        }
+                }
+        }
+
+        x = (time_t) (ret / USEC_PER_SEC);
+        x_usec = 0;
+
+        if (!localtime_or_gmtime_r(&x, &tm, utc))
+                return -EINVAL;
+
+        tm.tm_isdst = dst;
+        if (!with_tz && tzn)
+                tm.tm_zone = tzn;
+
+        if (streq(t, "today")) {
+                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+                goto from_tm;
+
+        } else if (streq(t, "yesterday")) {
+                tm.tm_mday--;
+                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+                goto from_tm;
+
+        } else if (streq(t, "tomorrow")) {
+                tm.tm_mday++;
+                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+                goto from_tm;
+        }
+
+        for (i = 0; i < ELEMENTSOF(day_nr); i++) {
+                size_t skip;
+
+                if (!startswith_no_case(t, day_nr[i].name))
+                        continue;
+
+                skip = strlen(day_nr[i].name);
+                if (t[skip] != ' ')
+                        continue;
+
+                weekday = day_nr[i].nr;
+                t += skip + 1;
+                break;
+        }
+
+        copy = tm;
+        k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
+        if (k) {
+                if (*k == '.')
+                        goto parse_usec;
+                else if (*k == 0)
+                        goto from_tm;
+        }
+
+        tm = copy;
+        k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
+        if (k) {
+                if (*k == '.')
+                        goto parse_usec;
+                else if (*k == 0)
+                        goto from_tm;
+        }
+
+        tm = copy;
+        k = strptime(t, "%y-%m-%d %H:%M", &tm);
+        if (k && *k == 0) {
+                tm.tm_sec = 0;
+                goto from_tm;
+        }
+
+        tm = copy;
+        k = strptime(t, "%Y-%m-%d %H:%M", &tm);
+        if (k && *k == 0) {
+                tm.tm_sec = 0;
+                goto from_tm;
+        }
+
+        tm = copy;
+        k = strptime(t, "%y-%m-%d", &tm);
+        if (k && *k == 0) {
+                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+                goto from_tm;
+        }
+
+        tm = copy;
+        k = strptime(t, "%Y-%m-%d", &tm);
+        if (k && *k == 0) {
+                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+                goto from_tm;
+        }
+
+        tm = copy;
+        k = strptime(t, "%H:%M:%S", &tm);
+        if (k) {
+                if (*k == '.')
+                        goto parse_usec;
+                else if (*k == 0)
+                        goto from_tm;
+        }
+
+        tm = copy;
+        k = strptime(t, "%H:%M", &tm);
+        if (k && *k == 0) {
+                tm.tm_sec = 0;
+                goto from_tm;
+        }
+
+        return -EINVAL;
+
+parse_usec:
+        {
+                unsigned add;
+
+                k++;
+                r = parse_fractional_part_u(&k, 6, &add);
+                if (r < 0)
+                        return -EINVAL;
+
+                if (*k)
+                        return -EINVAL;
+
+                x_usec = add;
+        }
+
+from_tm:
+        if (weekday >= 0 && tm.tm_wday != weekday)
+                return -EINVAL;
+
+        x = mktime_or_timegm(&tm, utc);
+        if (x < 0)
+                return -EINVAL;
+
+        ret = (usec_t) x * USEC_PER_SEC + x_usec;
+        if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX)
+                return -EINVAL;
+
+finish:
+        if (ret + plus < ret) /* overflow? */
+                return -EINVAL;
+        ret += plus;
+        if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX)
+                return -EINVAL;
+
+        if (ret >= minus)
+                ret -= minus;
+        else
+                return -EINVAL;
+
+        *usec = ret;
+
+        return 0;
+}
+
+typedef struct ParseTimestampResult {
+        usec_t usec;
+        int return_value;
+} ParseTimestampResult;
+
+int parse_timestamp(const char *t, usec_t *usec) {
+        char *last_space, *tz = NULL;
+        ParseTimestampResult *shared, tmp;
+        int r;
+
+        last_space = strrchr(t, ' ');
+        if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG))
+                tz = last_space + 1;
+
+        if (!tz || endswith_no_case(t, " UTC"))
+                return parse_timestamp_impl(t, usec, false);
+
+        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-timestamp)", 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) {
+                bool with_tz = true;
+
+                if (setenv("TZ", tz, 1) != 0) {
+                        shared->return_value = negative_errno();
+                        _exit(EXIT_FAILURE);
+                }
+
+                tzset();
+
+                /* If there is a timezone that matches the tzname fields, leave the parsing to the implementation.
+                 * Otherwise just cut it off. */
+                with_tz = !STR_IN_SET(tz, tzname[0], tzname[1]);
+
+                /* Cut off the timezone if we don't need it. */
+                if (with_tz)
+                        t = strndupa(t, last_space - t);
+
+                shared->return_value = parse_timestamp_impl(t, &shared->usec, with_tz);
+
+                _exit(EXIT_SUCCESS);
+        }
+
+        tmp = *shared;
+        if (munmap(shared, sizeof *shared) != 0)
+                return negative_errno();
+
+        if (tmp.return_value == 0)
+                *usec = tmp.usec;
+
+        return tmp.return_value;
+}
+
+static const char* extract_multiplier(const char *p, usec_t *multiplier) {
+        static const struct {
+                const char *suffix;
+                usec_t usec;
+        } table[] = {
+                { "seconds", USEC_PER_SEC    },
+                { "second",  USEC_PER_SEC    },
+                { "sec",     USEC_PER_SEC    },
+                { "s",       USEC_PER_SEC    },
+                { "minutes", USEC_PER_MINUTE },
+                { "minute",  USEC_PER_MINUTE },
+                { "min",     USEC_PER_MINUTE },
+                { "months",  USEC_PER_MONTH  },
+                { "month",   USEC_PER_MONTH  },
+                { "M",       USEC_PER_MONTH  },
+                { "msec",    USEC_PER_MSEC   },
+                { "ms",      USEC_PER_MSEC   },
+                { "m",       USEC_PER_MINUTE },
+                { "hours",   USEC_PER_HOUR   },
+                { "hour",    USEC_PER_HOUR   },
+                { "hr",      USEC_PER_HOUR   },
+                { "h",       USEC_PER_HOUR   },
+                { "days",    USEC_PER_DAY    },
+                { "day",     USEC_PER_DAY    },
+                { "d",       USEC_PER_DAY    },
+                { "weeks",   USEC_PER_WEEK   },
+                { "week",    USEC_PER_WEEK   },
+                { "w",       USEC_PER_WEEK   },
+                { "years",   USEC_PER_YEAR   },
+                { "year",    USEC_PER_YEAR   },
+                { "y",       USEC_PER_YEAR   },
+                { "usec",    1ULL            },
+                { "us",      1ULL            },
+                { "µs",      1ULL            },
+        };
+        size_t i;
+
+        for (i = 0; i < ELEMENTSOF(table); i++) {
+                char *e;
+
+                e = startswith(p, table[i].suffix);
+                if (e) {
+                        *multiplier = table[i].usec;
+                        return e;
+                }
+        }
+
+        return p;
+}
+
+int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
+        const char *p, *s;
+        usec_t r = 0;
+        bool something = false;
+
+        assert(t);
+        assert(usec);
+        assert(default_unit > 0);
+
+        p = t;
+
+        p += strspn(p, WHITESPACE);
+        s = startswith(p, "infinity");
+        if (s) {
+                s += strspn(s, WHITESPACE);
+                if (*s != 0)
+                        return -EINVAL;
+
+                *usec = USEC_INFINITY;
+                return 0;
+        }
+
+        for (;;) {
+                usec_t multiplier = default_unit, k;
+                long long l;
+                char *e;
+
+                p += strspn(p, WHITESPACE);
+
+                if (*p == 0) {
+                        if (!something)
+                                return -EINVAL;
+
+                        break;
+                }
+
+                if (*p == '-') /* Don't allow "-0" */
+                        return -ERANGE;
+
+                errno = 0;
+                l = strtoll(p, &e, 10);
+                if (errno > 0)
+                        return -errno;
+                if (l < 0)
+                        return -ERANGE;
+
+                if (*e == '.') {
+                        p = e + 1;
+                        p += strspn(p, DIGITS);
+                } else if (e == p)
+                        return -EINVAL;
+                else
+                        p = e;
+
+                s = extract_multiplier(p + strspn(p, WHITESPACE), &multiplier);
+                if (s == p && *s != '\0')
+                        /* Don't allow '12.34.56', but accept '12.34 .56' or '12.34s.56'*/
+                        return -EINVAL;
+
+                p = s;
+
+                if ((usec_t) l >= USEC_INFINITY / multiplier)
+                        return -ERANGE;
+
+                k = (usec_t) l * multiplier;
+                if (k >= USEC_INFINITY - r)
+                        return -ERANGE;
+
+                r += k;
+
+                something = true;
+
+                if (*e == '.') {
+                        usec_t m = multiplier / 10;
+                        const char *b;
+
+                        for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) {
+                                k = (usec_t) (*b - '0') * m;
+                                if (k >= USEC_INFINITY - r)
+                                        return -ERANGE;
+
+                                r += k;
+                        }
+
+                        /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge"*/
+                        if (b == e + 1)
+                                return -EINVAL;
+                }
+        }
+
+        *usec = r;
+
+        return 0;
+}
+
+int parse_sec(const char *t, usec_t *usec) {
+        return parse_time(t, usec, USEC_PER_SEC);
+}
+
+int parse_sec_fix_0(const char *t, usec_t *ret) {
+        usec_t k;
+        int r;
+
+        assert(t);
+        assert(ret);
+
+        r = parse_sec(t, &k);
+        if (r < 0)
+                return r;
+
+        *ret = k == 0 ? USEC_INFINITY : k;
+        return r;
+}
+
+int parse_sec_def_infinity(const char *t, usec_t *ret) {
+        t += strspn(t, WHITESPACE);
+        if (isempty(t)) {
+                *ret = USEC_INFINITY;
+                return 0;
+        }
+        return parse_sec(t, ret);
+}
+
+static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) {
+        static const struct {
+                const char *suffix;
+                nsec_t nsec;
+        } table[] = {
+                { "seconds", NSEC_PER_SEC    },
+                { "second",  NSEC_PER_SEC    },
+                { "sec",     NSEC_PER_SEC    },
+                { "s",       NSEC_PER_SEC    },
+                { "minutes", NSEC_PER_MINUTE },
+                { "minute",  NSEC_PER_MINUTE },
+                { "min",     NSEC_PER_MINUTE },
+                { "months",  NSEC_PER_MONTH  },
+                { "month",   NSEC_PER_MONTH  },
+                { "M",       NSEC_PER_MONTH  },
+                { "msec",    NSEC_PER_MSEC   },
+                { "ms",      NSEC_PER_MSEC   },
+                { "m",       NSEC_PER_MINUTE },
+                { "hours",   NSEC_PER_HOUR   },
+                { "hour",    NSEC_PER_HOUR   },
+                { "hr",      NSEC_PER_HOUR   },
+                { "h",       NSEC_PER_HOUR   },
+                { "days",    NSEC_PER_DAY    },
+                { "day",     NSEC_PER_DAY    },
+                { "d",       NSEC_PER_DAY    },
+                { "weeks",   NSEC_PER_WEEK   },
+                { "week",    NSEC_PER_WEEK   },
+                { "w",       NSEC_PER_WEEK   },
+                { "years",   NSEC_PER_YEAR   },
+                { "year",    NSEC_PER_YEAR   },
+                { "y",       NSEC_PER_YEAR   },
+                { "usec",    NSEC_PER_USEC   },
+                { "us",      NSEC_PER_USEC   },
+                { "µs",      NSEC_PER_USEC   },
+                { "nsec",    1ULL            },
+                { "ns",      1ULL            },
+                { "",        1ULL            }, /* default is nsec */
+        };
+        size_t i;
+
+        for (i = 0; i < ELEMENTSOF(table); i++) {
+                char *e;
+
+                e = startswith(p, table[i].suffix);
+                if (e) {
+                        *multiplier = table[i].nsec;
+                        return e;
+                }
+        }
+
+        return p;
+}
+
+int parse_nsec(const char *t, nsec_t *nsec) {
+        const char *p, *s;
+        nsec_t r = 0;
+        bool something = false;
+
+        assert(t);
+        assert(nsec);
+
+        p = t;
+
+        p += strspn(p, WHITESPACE);
+        s = startswith(p, "infinity");
+        if (s) {
+                s += strspn(s, WHITESPACE);
+                if (*s != 0)
+                        return -EINVAL;
+
+                *nsec = NSEC_INFINITY;
+                return 0;
+        }
+
+        for (;;) {
+                nsec_t multiplier = 1, k;
+                long long l;
+                char *e;
+
+                p += strspn(p, WHITESPACE);
+
+                if (*p == 0) {
+                        if (!something)
+                                return -EINVAL;
+
+                        break;
+                }
+
+                if (*p == '-') /* Don't allow "-0" */
+                        return -ERANGE;
+
+                errno = 0;
+                l = strtoll(p, &e, 10);
+                if (errno > 0)
+                        return -errno;
+                if (l < 0)
+                        return -ERANGE;
+
+                if (*e == '.') {
+                        p = e + 1;
+                        p += strspn(p, DIGITS);
+                } else if (e == p)
+                        return -EINVAL;
+                else
+                        p = e;
+
+                s = extract_nsec_multiplier(p + strspn(p, WHITESPACE), &multiplier);
+                if (s == p && *s != '\0')
+                        /* Don't allow '12.34.56', but accept '12.34 .56' or '12.34s.56'*/
+                        return -EINVAL;
+
+                p = s;
+
+                if ((nsec_t) l >= NSEC_INFINITY / multiplier)
+                        return -ERANGE;
+
+                k = (nsec_t) l * multiplier;
+                if (k >= NSEC_INFINITY - r)
+                        return -ERANGE;
+
+                r += k;
+
+                something = true;
+
+                if (*e == '.') {
+                        nsec_t m = multiplier / 10;
+                        const char *b;
+
+                        for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) {
+                                k = (nsec_t) (*b - '0') * m;
+                                if (k >= NSEC_INFINITY - r)
+                                        return -ERANGE;
+
+                                r += k;
+                        }
+
+                        /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge"*/
+                        if (b == e + 1)
+                                return -EINVAL;
+                }
+        }
+
+        *nsec = r;
+
+        return 0;
+}
+
+bool ntp_synced(void) {
+        struct timex txc = {};
+
+        if (adjtimex(&txc) < 0)
+                return false;
+
+        if (txc.status & STA_UNSYNC)
+                return false;
+
+        return true;
+}
+
+int get_timezones(char ***ret) {
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_strv_free_ char **zones = NULL;
+        size_t n_zones = 0, n_allocated = 0;
+        int r;
+
+        assert(ret);
+
+        zones = strv_new("UTC");
+        if (!zones)
+                return -ENOMEM;
+
+        n_allocated = 2;
+        n_zones = 1;
+
+        f = fopen("/usr/share/zoneinfo/zone.tab", "re");
+        if (f) {
+                for (;;) {
+                        _cleanup_free_ char *line = NULL;
+                        char *p, *w;
+                        size_t k;
+
+                        r = read_line(f, LONG_LINE_MAX, &line);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                break;
+
+                        p = strstrip(line);
+
+                        if (isempty(p) || *p == '#')
+                                continue;
+
+                        /* Skip over country code */
+                        p += strcspn(p, WHITESPACE);
+                        p += strspn(p, WHITESPACE);
+
+                        /* Skip over coordinates */
+                        p += strcspn(p, WHITESPACE);
+                        p += strspn(p, WHITESPACE);
+
+                        /* Found timezone name */
+                        k = strcspn(p, WHITESPACE);
+                        if (k <= 0)
+                                continue;
+
+                        w = strndup(p, k);
+                        if (!w)
+                                return -ENOMEM;
+
+                        if (!GREEDY_REALLOC(zones, n_allocated, n_zones + 2)) {
+                                free(w);
+                                return -ENOMEM;
+                        }
+
+                        zones[n_zones++] = w;
+                        zones[n_zones] = NULL;
+                }
+
+                strv_sort(zones);
+
+        } else if (errno != ENOENT)
+                return -errno;
+
+        *ret = TAKE_PTR(zones);
+
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+bool timezone_is_valid(const char *name, int log_level) {
+        bool slash = false;
+        const char *p, *t;
+        _cleanup_close_ int fd = -1;
+        char buf[4];
+        int r;
+
+        if (isempty(name))
+                return false;
+
+        if (name[0] == '/')
+                return false;
+
+        for (p = name; *p; p++) {
+                if (!(*p >= '0' && *p <= '9') &&
+                    !(*p >= 'a' && *p <= 'z') &&
+                    !(*p >= 'A' && *p <= 'Z') &&
+                    !IN_SET(*p, '-', '_', '+', '/'))
+                        return false;
+
+                if (*p == '/') {
+
+                        if (slash)
+                                return false;
+
+                        slash = true;
+                } else
+                        slash = false;
+        }
+
+        if (slash)
+                return false;
+
+        if (p - name >= PATH_MAX)
+                return false;
+
+        t = strjoina("/usr/share/zoneinfo/", name);
+
+        fd = open(t, O_RDONLY|O_CLOEXEC);
+        if (fd < 0) {
+                log_full_errno(log_level, errno, "Failed to open timezone file '%s': %m", t);
+                return false;
+        }
+
+        r = fd_verify_regular(fd);
+        if (r < 0) {
+                log_full_errno(log_level, r, "Timezone file '%s' is not  a regular file: %m", t);
+                return false;
+        }
+
+        r = loop_read_exact(fd, buf, 4, false);
+        if (r < 0) {
+                log_full_errno(log_level, r, "Failed to read from timezone file '%s': %m", t);
+                return false;
+        }
+
+        /* Magic from tzfile(5) */
+        if (memcmp(buf, "TZif", 4) != 0) {
+                log_full(log_level, "Timezone file '%s' has wrong magic bytes", t);
+                return false;
+        }
+
+        return true;
+}
+
+bool clock_boottime_supported(void) {
+        static int supported = -1;
+
+        /* Note that this checks whether CLOCK_BOOTTIME is available in general as well as available for timerfds()! */
+
+        if (supported < 0) {
+                int fd;
+
+                fd = timerfd_create(CLOCK_BOOTTIME, TFD_NONBLOCK|TFD_CLOEXEC);
+                if (fd < 0)
+                        supported = false;
+                else {
+                        safe_close(fd);
+                        supported = true;
+                }
+        }
+
+        return supported;
+}
+
+clockid_t clock_boottime_or_monotonic(void) {
+        if (clock_boottime_supported())
+                return CLOCK_BOOTTIME;
+        else
+                return CLOCK_MONOTONIC;
+}
+
+bool clock_supported(clockid_t clock) {
+        struct timespec ts;
+
+        switch (clock) {
+
+        case CLOCK_MONOTONIC:
+        case CLOCK_REALTIME:
+                return true;
+
+        case CLOCK_BOOTTIME:
+                return clock_boottime_supported();
+
+        case CLOCK_BOOTTIME_ALARM:
+                if (!clock_boottime_supported())
+                        return false;
+
+                _fallthrough_;
+        default:
+                /* For everything else, check properly */
+                return clock_gettime(clock, &ts) >= 0;
+        }
+}
+
+#if 0 /* NM_IGNORED */
+int get_timezone(char **tz) {
+        _cleanup_free_ char *t = NULL;
+        const char *e;
+        char *z;
+        int r;
+
+        r = readlink_malloc("/etc/localtime", &t);
+        if (r < 0)
+                return r; /* returns EINVAL if not a symlink */
+
+        e = PATH_STARTSWITH_SET(t, "/usr/share/zoneinfo/", "../usr/share/zoneinfo/");
+        if (!e)
+                return -EINVAL;
+
+        if (!timezone_is_valid(e, LOG_DEBUG))
+                return -EINVAL;
+
+        z = strdup(e);
+        if (!z)
+                return -ENOMEM;
+
+        *tz = z;
+        return 0;
+}
+
+time_t mktime_or_timegm(struct tm *tm, bool utc) {
+        return utc ? timegm(tm) : mktime(tm);
+}
+
+struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) {
+        return utc ? gmtime_r(t, tm) : localtime_r(t, tm);
+}
+
+unsigned long usec_to_jiffies(usec_t u) {
+        static thread_local unsigned long hz = 0;
+        long r;
+
+        if (hz == 0) {
+                r = sysconf(_SC_CLK_TCK);
+
+                assert(r > 0);
+                hz = r;
+        }
+
+        return DIV_ROUND_UP(u , USEC_PER_SEC / hz);
+}
+
+usec_t usec_shift_clock(usec_t x, clockid_t from, clockid_t to) {
+        usec_t a, b;
+
+        if (x == USEC_INFINITY)
+                return USEC_INFINITY;
+        if (map_clock_id(from) == map_clock_id(to))
+                return x;
+
+        a = now(from);
+        b = now(to);
+
+        if (x > a)
+                /* x lies in the future */
+                return usec_add(b, usec_sub_unsigned(x, a));
+        else
+                /* x lies in the past */
+                return usec_sub_unsigned(b, usec_sub_unsigned(a, x));
+}
+
+bool in_utc_timezone(void) {
+        tzset();
+
+        return timezone == 0 && daylight == 0;
+}
+
+int time_change_fd(void) {
+
+        /* We only care for the cancellation event, hence we set the timeout to the latest possible value. */
+        static const struct itimerspec its = {
+                .it_value.tv_sec = TIME_T_MAX,
+        };
+
+        _cleanup_close_ int fd;
+
+        assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX));
+
+        /* Uses TFD_TIMER_CANCEL_ON_SET to get notifications whenever CLOCK_REALTIME makes a jump relative to
+         * CLOCK_MONOTONIC. */
+
+        fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0)
+                return -errno;
+
+        return TAKE_FD(fd);
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/time-util.h b/shared/systemd/src/basic/time-util.h
new file mode 100644
index 00000000..a238f691
--- /dev/null
+++ b/shared/systemd/src/basic/time-util.h
@@ -0,0 +1,180 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <time.h>
+
+typedef uint64_t usec_t;
+typedef uint64_t nsec_t;
+
+#define PRI_NSEC PRIu64
+#define PRI_USEC PRIu64
+#define NSEC_FMT "%" PRI_NSEC
+#define USEC_FMT "%" PRI_USEC
+
+#include "macro.h"
+
+typedef struct dual_timestamp {
+        usec_t realtime;
+        usec_t monotonic;
+} dual_timestamp;
+
+typedef struct triple_timestamp {
+        usec_t realtime;
+        usec_t monotonic;
+        usec_t boottime;
+} triple_timestamp;
+
+#define USEC_INFINITY ((usec_t) -1)
+#define NSEC_INFINITY ((nsec_t) -1)
+
+#define MSEC_PER_SEC  1000ULL
+#define USEC_PER_SEC  ((usec_t) 1000000ULL)
+#define USEC_PER_MSEC ((usec_t) 1000ULL)
+#define NSEC_PER_SEC  ((nsec_t) 1000000000ULL)
+#define NSEC_PER_MSEC ((nsec_t) 1000000ULL)
+#define NSEC_PER_USEC ((nsec_t) 1000ULL)
+
+#define USEC_PER_MINUTE ((usec_t) (60ULL*USEC_PER_SEC))
+#define NSEC_PER_MINUTE ((nsec_t) (60ULL*NSEC_PER_SEC))
+#define USEC_PER_HOUR ((usec_t) (60ULL*USEC_PER_MINUTE))
+#define NSEC_PER_HOUR ((nsec_t) (60ULL*NSEC_PER_MINUTE))
+#define USEC_PER_DAY ((usec_t) (24ULL*USEC_PER_HOUR))
+#define NSEC_PER_DAY ((nsec_t) (24ULL*NSEC_PER_HOUR))
+#define USEC_PER_WEEK ((usec_t) (7ULL*USEC_PER_DAY))
+#define NSEC_PER_WEEK ((nsec_t) (7ULL*NSEC_PER_DAY))
+#define USEC_PER_MONTH ((usec_t) (2629800ULL*USEC_PER_SEC))
+#define NSEC_PER_MONTH ((nsec_t) (2629800ULL*NSEC_PER_SEC))
+#define USEC_PER_YEAR ((usec_t) (31557600ULL*USEC_PER_SEC))
+#define NSEC_PER_YEAR ((nsec_t) (31557600ULL*NSEC_PER_SEC))
+
+/* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this
+ * to 6. Let's rely on that. */
+#define FORMAT_TIMESTAMP_MAX (3+1+10+1+8+1+6+1+6+1)
+#define FORMAT_TIMESTAMP_WIDTH 28 /* when outputting, assume this width */
+#define FORMAT_TIMESTAMP_RELATIVE_MAX 256
+#define FORMAT_TIMESPAN_MAX 64
+
+#define TIME_T_MAX (time_t)((UINTMAX_C(1) << ((sizeof(time_t) << 3) - 1)) - 1)
+
+#define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) {})
+#define TRIPLE_TIMESTAMP_NULL ((struct triple_timestamp) {})
+
+usec_t now(clockid_t clock);
+nsec_t now_nsec(clockid_t clock);
+
+dual_timestamp* dual_timestamp_get(dual_timestamp *ts);
+dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u);
+dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u);
+dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u);
+
+triple_timestamp* triple_timestamp_get(triple_timestamp *ts);
+triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u);
+
+#define DUAL_TIMESTAMP_HAS_CLOCK(clock)                               \
+        IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC)
+
+#define TRIPLE_TIMESTAMP_HAS_CLOCK(clock)                               \
+        IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM)
+
+static inline bool dual_timestamp_is_set(const dual_timestamp *ts) {
+        return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) ||
+                (ts->monotonic > 0 && ts->monotonic != USEC_INFINITY));
+}
+
+static inline bool triple_timestamp_is_set(const triple_timestamp *ts) {
+        return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) ||
+                (ts->monotonic > 0 && ts->monotonic != USEC_INFINITY) ||
+                (ts->boottime > 0 && ts->boottime != USEC_INFINITY));
+}
+
+usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock);
+
+usec_t timespec_load(const struct timespec *ts) _pure_;
+nsec_t timespec_load_nsec(const struct timespec *ts) _pure_;
+struct timespec *timespec_store(struct timespec *ts, usec_t u);
+
+usec_t timeval_load(const struct timeval *tv) _pure_;
+struct timeval *timeval_store(struct timeval *tv, usec_t u);
+
+char *format_timestamp(char *buf, size_t l, usec_t t);
+char *format_timestamp_utc(char *buf, size_t l, usec_t t);
+char *format_timestamp_us(char *buf, size_t l, usec_t t);
+char *format_timestamp_us_utc(char *buf, size_t l, usec_t t);
+char *format_timestamp_relative(char *buf, size_t l, usec_t t);
+char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy);
+
+int parse_timestamp(const char *t, usec_t *usec);
+
+int parse_sec(const char *t, usec_t *usec);
+int parse_sec_fix_0(const char *t, usec_t *usec);
+int parse_sec_def_infinity(const char *t, usec_t *usec);
+int parse_time(const char *t, usec_t *usec, usec_t default_unit);
+int parse_nsec(const char *t, nsec_t *nsec);
+
+bool ntp_synced(void);
+
+int get_timezones(char ***l);
+bool timezone_is_valid(const char *name, int log_level);
+
+bool clock_boottime_supported(void);
+bool clock_supported(clockid_t clock);
+clockid_t clock_boottime_or_monotonic(void);
+
+usec_t usec_shift_clock(usec_t, clockid_t from, clockid_t to);
+
+int get_timezone(char **timezone);
+
+time_t mktime_or_timegm(struct tm *tm, bool utc);
+struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc);
+
+unsigned long usec_to_jiffies(usec_t usec);
+
+bool in_utc_timezone(void);
+
+static inline usec_t usec_add(usec_t a, usec_t b) {
+        usec_t c;
+
+        /* Adds two time values, and makes sure USEC_INFINITY as input results as USEC_INFINITY in output, and doesn't
+         * overflow. */
+
+        c = a + b;
+        if (c < a || c < b) /* overflow check */
+                return USEC_INFINITY;
+
+        return c;
+}
+
+static inline usec_t usec_sub_unsigned(usec_t timestamp, usec_t delta) {
+
+        if (timestamp == USEC_INFINITY) /* Make sure infinity doesn't degrade */
+                return USEC_INFINITY;
+        if (timestamp < delta)
+                return 0;
+
+        return timestamp - delta;
+}
+
+static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) {
+        if (delta < 0)
+                return usec_add(timestamp, (usec_t) (-delta));
+        else
+                return usec_sub_unsigned(timestamp, (usec_t) delta);
+}
+
+#if SIZEOF_TIME_T == 8
+/* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit year
+ * territory. However, since we want to stay away from this in all timezones we take one day off. */
+#define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 253402214399000000)
+#elif SIZEOF_TIME_T == 4
+/* With a 32bit time_t we can't go beyond 2038... */
+#define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000)
+#else
+#error "Yuck, time_t is neither 4 nor 8 bytes wide?"
+#endif
+
+int time_change_fd(void);
diff --git a/shared/systemd/src/basic/tmpfile-util.c b/shared/systemd/src/basic/tmpfile-util.c
new file mode 100644
index 00000000..019121cb
--- /dev/null
+++ b/shared/systemd/src/basic/tmpfile-util.c
@@ -0,0 +1,336 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <sys/mman.h>
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "hexdecoct.h"
+#include "macro.h"
+#include "memfd-util.h"
+#include "missing_fcntl.h"
+#include "missing_syscall.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "random-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "tmpfile-util.h"
+#include "umask-util.h"
+
+int fopen_temporary(const char *path, FILE **_f, char **_temp_path) {
+        FILE *f;
+        char *t;
+        int r, fd;
+
+        assert(path);
+        assert(_f);
+        assert(_temp_path);
+
+        r = tempfn_xxxxxx(path, NULL, &t);
+        if (r < 0)
+                return r;
+
+        fd = mkostemp_safe(t);
+        if (fd < 0) {
+                free(t);
+                return -errno;
+        }
+
+        f = fdopen(fd, "w");
+        if (!f) {
+                unlink_noerrno(t);
+                free(t);
+                safe_close(fd);
+                return -errno;
+        }
+
+        *_f = f;
+        *_temp_path = t;
+
+        return 0;
+}
+
+/* This is much like mkostemp() but is subject to umask(). */
+int mkostemp_safe(char *pattern) {
+        _cleanup_umask_ mode_t u = 0;
+        int fd;
+
+        assert(pattern);
+
+        u = umask(077);
+
+        fd = mkostemp(pattern, O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        return fd;
+}
+
+#if 0 /* NM_IGNORED */
+int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) {
+        int fd;
+        FILE *f;
+
+        fd = mkostemp_safe(pattern);
+        if (fd < 0)
+                return fd;
+
+        f = fdopen(fd, mode);
+        if (!f) {
+                safe_close(fd);
+                return -errno;
+        }
+
+        *ret_f = f;
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
+        const char *fn;
+        char *t;
+
+        assert(ret);
+
+        if (isempty(p))
+                return -EINVAL;
+        if (path_equal(p, "/"))
+                return -EINVAL;
+
+        /*
+         * Turns this:
+         *         /foo/bar/waldo
+         *
+         * Into this:
+         *         /foo/bar/.#<extra>waldoXXXXXX
+         */
+
+        fn = basename(p);
+        if (!filename_is_valid(fn))
+                return -EINVAL;
+
+        extra = strempty(extra);
+
+        t = new(char, strlen(p) + 2 + strlen(extra) + 6 + 1);
+        if (!t)
+                return -ENOMEM;
+
+        strcpy(stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn), "XXXXXX");
+
+        *ret = path_simplify(t, false);
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+int tempfn_random(const char *p, const char *extra, char **ret) {
+        const char *fn;
+        char *t, *x;
+        uint64_t u;
+        unsigned i;
+
+        assert(ret);
+
+        if (isempty(p))
+                return -EINVAL;
+        if (path_equal(p, "/"))
+                return -EINVAL;
+
+        /*
+         * Turns this:
+         *         /foo/bar/waldo
+         *
+         * Into this:
+         *         /foo/bar/.#<extra>waldobaa2a261115984a9
+         */
+
+        fn = basename(p);
+        if (!filename_is_valid(fn))
+                return -EINVAL;
+
+        extra = strempty(extra);
+
+        t = new(char, strlen(p) + 2 + strlen(extra) + 16 + 1);
+        if (!t)
+                return -ENOMEM;
+
+        x = stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn);
+
+        u = random_u64();
+        for (i = 0; i < 16; i++) {
+                *(x++) = hexchar(u & 0xF);
+                u >>= 4;
+        }
+
+        *x = 0;
+
+        *ret = path_simplify(t, false);
+        return 0;
+}
+
+int tempfn_random_child(const char *p, const char *extra, char **ret) {
+        char *t, *x;
+        uint64_t u;
+        unsigned i;
+        int r;
+
+        assert(ret);
+
+        /* Turns this:
+         *         /foo/bar/waldo
+         * Into this:
+         *         /foo/bar/waldo/.#<extra>3c2b6219aa75d7d0
+         */
+
+        if (!p) {
+                r = tmp_dir(&p);
+                if (r < 0)
+                        return r;
+        }
+
+        extra = strempty(extra);
+
+        t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1);
+        if (!t)
+                return -ENOMEM;
+
+        if (isempty(p))
+                x = stpcpy(stpcpy(t, ".#"), extra);
+        else
+                x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra);
+
+        u = random_u64();
+        for (i = 0; i < 16; i++) {
+                *(x++) = hexchar(u & 0xF);
+                u >>= 4;
+        }
+
+        *x = 0;
+
+        *ret = path_simplify(t, false);
+        return 0;
+}
+
+int open_tmpfile_unlinkable(const char *directory, int flags) {
+        char *p;
+        int fd, r;
+
+        if (!directory) {
+                r = tmp_dir(&directory);
+                if (r < 0)
+                        return r;
+        } else if (isempty(directory))
+                return -EINVAL;
+
+        /* Returns an unlinked temporary file that cannot be linked into the file system anymore */
+
+        /* Try O_TMPFILE first, if it is supported */
+        fd = open(directory, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR);
+        if (fd >= 0)
+                return fd;
+
+        /* Fall back to unguessable name + unlinking */
+        p = strjoina(directory, "/systemd-tmp-XXXXXX");
+
+        fd = mkostemp_safe(p);
+        if (fd < 0)
+                return fd;
+
+        (void) unlink(p);
+
+        return fd;
+}
+
+int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
+        _cleanup_free_ char *tmp = NULL;
+        int r, fd;
+
+        assert(target);
+        assert(ret_path);
+
+        /* Don't allow O_EXCL, as that has a special meaning for O_TMPFILE */
+        assert((flags & O_EXCL) == 0);
+
+        /* Creates a temporary file, that shall be renamed to "target" later. If possible, this uses O_TMPFILE – in
+         * which case "ret_path" will be returned as NULL. If not possible a the tempoary path name used is returned in
+         * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */
+
+        fd = open_parent(target, O_TMPFILE|flags, 0640);
+        if (fd >= 0) {
+                *ret_path = NULL;
+                return fd;
+        }
+
+        log_debug_errno(fd, "Failed to use O_TMPFILE for %s: %m", target);
+
+        r = tempfn_random(target, NULL, &tmp);
+        if (r < 0)
+                return r;
+
+        fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
+        if (fd < 0)
+                return -errno;
+
+        *ret_path = TAKE_PTR(tmp);
+
+        return fd;
+}
+
+int link_tmpfile(int fd, const char *path, const char *target) {
+        int r;
+
+        assert(fd >= 0);
+        assert(target);
+
+        /* Moves a temporary file created with open_tmpfile() above into its final place. if "path" is NULL an fd
+         * created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE is not supported
+         * on the directory, and renameat2() is used instead.
+         *
+         * Note that in both cases we will not replace existing files. This is because linkat() does not support this
+         * operation currently (renameat2() does), and there is no nice way to emulate this. */
+
+        if (path) {
+                r = rename_noreplace(AT_FDCWD, path, AT_FDCWD, target);
+                if (r < 0)
+                        return r;
+        } else {
+                char proc_fd_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1];
+
+                xsprintf(proc_fd_path, "/proc/self/fd/%i", fd);
+
+                if (linkat(AT_FDCWD, proc_fd_path, AT_FDCWD, target, AT_SYMLINK_FOLLOW) < 0)
+                        return -errno;
+        }
+
+        return 0;
+}
+
+int mkdtemp_malloc(const char *template, char **ret) {
+        _cleanup_free_ char *p = NULL;
+        int r;
+
+        assert(ret);
+
+        if (template)
+                p = strdup(template);
+        else {
+                const char *tmp;
+
+                r = tmp_dir(&tmp);
+                if (r < 0)
+                        return r;
+
+                p = strjoin(tmp, "/XXXXXX");
+        }
+        if (!p)
+                return -ENOMEM;
+
+        if (!mkdtemp(p))
+                return -errno;
+
+        *ret = TAKE_PTR(p);
+        return 0;
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/tmpfile-util.h b/shared/systemd/src/basic/tmpfile-util.h
new file mode 100644
index 00000000..802c85d6
--- /dev/null
+++ b/shared/systemd/src/basic/tmpfile-util.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdio.h>
+
+int fopen_temporary(const char *path, FILE **_f, char **_temp_path);
+int mkostemp_safe(char *pattern);
+int fmkostemp_safe(char *pattern, const char *mode, FILE**_f);
+
+int tempfn_xxxxxx(const char *p, const char *extra, char **ret);
+int tempfn_random(const char *p, const char *extra, char **ret);
+int tempfn_random_child(const char *p, const char *extra, char **ret);
+
+int open_tmpfile_unlinkable(const char *directory, int flags);
+int open_tmpfile_linkable(const char *target, int flags, char **ret_path);
+
+int link_tmpfile(int fd, const char *path, const char *target);
+
+int mkdtemp_malloc(const char *template, char **ret);
diff --git a/shared/systemd/src/basic/umask-util.h b/shared/systemd/src/basic/umask-util.h
new file mode 100644
index 00000000..e964292e
--- /dev/null
+++ b/shared/systemd/src/basic/umask-util.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "macro.h"
+
+static inline void umaskp(mode_t *u) {
+        umask(*u);
+}
+
+#define _cleanup_umask_ _cleanup_(umaskp)
+
+struct _umask_struct_ {
+        mode_t mask;
+        bool quit;
+};
+
+static inline void _reset_umask_(struct _umask_struct_ *s) {
+        umask(s->mask);
+};
+
+#define RUN_WITH_UMASK(mask)                                            \
+        for (_cleanup_(_reset_umask_) struct _umask_struct_ _saved_umask_ = { umask(mask), false }; \
+             !_saved_umask_.quit ;                                      \
+             _saved_umask_.quit = true)
diff --git a/shared/systemd/src/basic/utf8.c b/shared/systemd/src/basic/utf8.c
new file mode 100644
index 00000000..e9958c91
--- /dev/null
+++ b/shared/systemd/src/basic/utf8.c
@@ -0,0 +1,546 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+/* Parts of this file are based on the GLIB utf8 validation functions. The
+ * original license text follows. */
+
+/* gutf8.c - Operations on UTF-8 strings.
+ *
+ * Copyright (C) 1999 Tom Tromey
+ * Copyright (C) 2000 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "gunicode.h"
+#include "hexdecoct.h"
+#include "macro.h"
+#include "utf8.h"
+
+bool unichar_is_valid(char32_t ch) {
+
+        if (ch >= 0x110000) /* End of unicode space */
+                return false;
+        if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */
+                return false;
+        if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */
+                return false;
+        if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */
+                return false;
+
+        return true;
+}
+
+#if 0 /* NM_IGNORED */
+static bool unichar_is_control(char32_t ch) {
+
+        /*
+          0 to ' '-1 is the C0 range.
+          DEL=0x7F, and DEL+1 to 0x9F is C1 range.
+          '\t' is in C0 range, but more or less harmless and commonly used.
+        */
+
+        return (ch < ' ' && !IN_SET(ch, '\t', '\n')) ||
+                (0x7F <= ch && ch <= 0x9F);
+}
+#endif /* NM_IGNORED */
+
+/* count of characters used to encode one unicode char */
+static size_t utf8_encoded_expected_len(const char *str) {
+        uint8_t c;
+
+        assert(str);
+
+        c = (uint8_t) str[0];
+        if (c < 0x80)
+                return 1;
+        if ((c & 0xe0) == 0xc0)
+                return 2;
+        if ((c & 0xf0) == 0xe0)
+                return 3;
+        if ((c & 0xf8) == 0xf0)
+                return 4;
+        if ((c & 0xfc) == 0xf8)
+                return 5;
+        if ((c & 0xfe) == 0xfc)
+                return 6;
+
+        return 0;
+}
+
+/* decode one unicode char */
+int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar) {
+        char32_t unichar;
+        size_t len, i;
+
+        assert(str);
+
+        len = utf8_encoded_expected_len(str);
+
+        switch (len) {
+        case 1:
+                *ret_unichar = (char32_t)str[0];
+                return 0;
+        case 2:
+                unichar = str[0] & 0x1f;
+                break;
+        case 3:
+                unichar = (char32_t)str[0] & 0x0f;
+                break;
+        case 4:
+                unichar = (char32_t)str[0] & 0x07;
+                break;
+        case 5:
+                unichar = (char32_t)str[0] & 0x03;
+                break;
+        case 6:
+                unichar = (char32_t)str[0] & 0x01;
+                break;
+        default:
+                return -EINVAL;
+        }
+
+        for (i = 1; i < len; i++) {
+                if (((char32_t)str[i] & 0xc0) != 0x80)
+                        return -EINVAL;
+
+                unichar <<= 6;
+                unichar |= (char32_t)str[i] & 0x3f;
+        }
+
+        *ret_unichar = unichar;
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+bool utf8_is_printable_newline(const char* str, size_t length, bool newline) {
+        const char *p;
+
+        assert(str);
+
+        for (p = str; length;) {
+                int encoded_len, r;
+                char32_t val;
+
+                encoded_len = utf8_encoded_valid_unichar(p);
+                if (encoded_len < 0 ||
+                    (size_t) encoded_len > length)
+                        return false;
+
+                r = utf8_encoded_to_unichar(p, &val);
+                if (r < 0 ||
+                    unichar_is_control(val) ||
+                    (!newline && val == '\n'))
+                        return false;
+
+                length -= encoded_len;
+                p += encoded_len;
+        }
+
+        return true;
+}
+#endif /* NM_IGNORED */
+
+char *utf8_is_valid(const char *str) {
+        const char *p;
+
+        assert(str);
+
+        p = str;
+        while (*p) {
+                int len;
+
+                len = utf8_encoded_valid_unichar(p);
+                if (len < 0)
+                        return NULL;
+
+                p += len;
+        }
+
+        return (char*) str;
+}
+
+char *utf8_escape_invalid(const char *str) {
+        char *p, *s;
+
+        assert(str);
+
+        p = s = malloc(strlen(str) * 4 + 1);
+        if (!p)
+                return NULL;
+
+        while (*str) {
+                int len;
+
+                len = utf8_encoded_valid_unichar(str);
+                if (len > 0) {
+                        s = mempcpy(s, str, len);
+                        str += len;
+                } else {
+                        s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER);
+                        str += 1;
+                }
+        }
+
+        *s = '\0';
+
+        return p;
+}
+
+#if 0 /* NM_IGNORED */
+char *utf8_escape_non_printable(const char *str) {
+        char *p, *s;
+
+        assert(str);
+
+        p = s = malloc(strlen(str) * 4 + 1);
+        if (!p)
+                return NULL;
+
+        while (*str) {
+                int len;
+
+                len = utf8_encoded_valid_unichar(str);
+                if (len > 0) {
+                        if (utf8_is_printable(str, len)) {
+                                s = mempcpy(s, str, len);
+                                str += len;
+                        } else {
+                                while (len > 0) {
+                                        *(s++) = '\\';
+                                        *(s++) = 'x';
+                                        *(s++) = hexchar((int) *str >> 4);
+                                        *(s++) = hexchar((int) *str);
+
+                                        str += 1;
+                                        len--;
+                                }
+                        }
+                } else {
+                        s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER);
+                        str += 1;
+                }
+        }
+
+        *s = '\0';
+
+        return p;
+}
+#endif /* NM_IGNORED */
+
+char *ascii_is_valid(const char *str) {
+        const char *p;
+
+        /* Check whether the string consists of valid ASCII bytes,
+         * i.e values between 0 and 127, inclusive. */
+
+        assert(str);
+
+        for (p = str; *p; p++)
+                if ((unsigned char) *p >= 128)
+                        return NULL;
+
+        return (char*) str;
+}
+
+#if 0 /* NM_IGNORED */
+char *ascii_is_valid_n(const char *str, size_t len) {
+        size_t i;
+
+        /* Very similar to ascii_is_valid(), but checks exactly len
+         * bytes and rejects any NULs in that range. */
+
+        assert(str);
+
+        for (i = 0; i < len; i++)
+                if ((unsigned char) str[i] >= 128 || str[i] == 0)
+                        return NULL;
+
+        return (char*) str;
+}
+#endif /* NM_IGNORED */
+
+/**
+ * utf8_encode_unichar() - Encode single UCS-4 character as UTF-8
+ * @out_utf8: output buffer of at least 4 bytes or NULL
+ * @g: UCS-4 character to encode
+ *
+ * This encodes a single UCS-4 character as UTF-8 and writes it into @out_utf8.
+ * The length of the character is returned. It is not zero-terminated! If the
+ * output buffer is NULL, only the length is returned.
+ *
+ * Returns: The length in bytes that the UTF-8 representation does or would
+ *          occupy.
+ */
+size_t utf8_encode_unichar(char *out_utf8, char32_t g) {
+
+        if (g < (1 << 7)) {
+                if (out_utf8)
+                        out_utf8[0] = g & 0x7f;
+                return 1;
+        } else if (g < (1 << 11)) {
+                if (out_utf8) {
+                        out_utf8[0] = 0xc0 | ((g >> 6) & 0x1f);
+                        out_utf8[1] = 0x80 | (g & 0x3f);
+                }
+                return 2;
+        } else if (g < (1 << 16)) {
+                if (out_utf8) {
+                        out_utf8[0] = 0xe0 | ((g >> 12) & 0x0f);
+                        out_utf8[1] = 0x80 | ((g >> 6) & 0x3f);
+                        out_utf8[2] = 0x80 | (g & 0x3f);
+                }
+                return 3;
+        } else if (g < (1 << 21)) {
+                if (out_utf8) {
+                        out_utf8[0] = 0xf0 | ((g >> 18) & 0x07);
+                        out_utf8[1] = 0x80 | ((g >> 12) & 0x3f);
+                        out_utf8[2] = 0x80 | ((g >> 6) & 0x3f);
+                        out_utf8[3] = 0x80 | (g & 0x3f);
+                }
+                return 4;
+        }
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+char *utf16_to_utf8(const char16_t *s, size_t length /* bytes! */) {
+        const uint8_t *f;
+        char *r, *t;
+
+        assert(s);
+
+        /* Input length is in bytes, i.e. the shortest possible character takes 2 bytes. Each unicode character may
+         * take up to 4 bytes in UTF-8. Let's also account for a trailing NUL byte. */
+        if (length * 2 < length)
+                return NULL; /* overflow */
+
+        r = new(char, length * 2 + 1);
+        if (!r)
+                return NULL;
+
+        f = (const uint8_t*) s;
+        t = r;
+
+        while (f + 1 < (const uint8_t*) s + length) {
+                char16_t w1, w2;
+
+                /* see RFC 2781 section 2.2 */
+
+                w1 = f[1] << 8 | f[0];
+                f += 2;
+
+                if (!utf16_is_surrogate(w1)) {
+                        t += utf8_encode_unichar(t, w1);
+                        continue;
+                }
+
+                if (utf16_is_trailing_surrogate(w1))
+                        continue; /* spurious trailing surrogate, ignore */
+
+                if (f + 1 >= (const uint8_t*) s + length)
+                        break;
+
+                w2 = f[1] << 8 | f[0];
+                f += 2;
+
+                if (!utf16_is_trailing_surrogate(w2)) {
+                        f -= 2;
+                        continue; /* surrogate missing its trailing surrogate, ignore */
+                }
+
+                t += utf8_encode_unichar(t, utf16_surrogate_pair_to_unichar(w1, w2));
+        }
+
+        *t = 0;
+        return r;
+}
+
+size_t utf16_encode_unichar(char16_t *out, char32_t c) {
+
+        /* Note that this encodes as little-endian. */
+
+        switch (c) {
+
+        case 0 ... 0xd7ffU:
+        case 0xe000U ... 0xffffU:
+                out[0] = htole16(c);
+                return 1;
+
+        case 0x10000U ... 0x10ffffU:
+                c -= 0x10000U;
+                out[0] = htole16((c >> 10) + 0xd800U);
+                out[1] = htole16((c & 0x3ffU) + 0xdc00U);
+                return 2;
+
+        default: /* A surrogate (invalid) */
+                return 0;
+        }
+}
+
+char16_t *utf8_to_utf16(const char *s, size_t length) {
+        char16_t *n, *p;
+        size_t i;
+        int r;
+
+        assert(s);
+
+        n = new(char16_t, length + 1);
+        if (!n)
+                return NULL;
+
+        p = n;
+
+        for (i = 0; i < length;) {
+                char32_t unichar;
+                size_t e;
+
+                e = utf8_encoded_expected_len(s + i);
+                if (e <= 1) /* Invalid and single byte characters are copied as they are */
+                        goto copy;
+
+                if (i + e > length) /* sequence longer than input buffer, then copy as-is */
+                        goto copy;
+
+                r = utf8_encoded_to_unichar(s + i, &unichar);
+                if (r < 0) /* sequence invalid, then copy as-is */
+                        goto copy;
+
+                p += utf16_encode_unichar(p, unichar);
+                i += e;
+                continue;
+
+        copy:
+                *(p++) = htole16(s[i++]);
+        }
+
+        *p = 0;
+        return n;
+}
+
+size_t char16_strlen(const char16_t *s) {
+        size_t n = 0;
+
+        assert(s);
+
+        while (*s != 0)
+                n++, s++;
+
+        return n;
+}
+#endif /* NM_IGNORED */
+
+/* expected size used to encode one unicode char */
+static int utf8_unichar_to_encoded_len(char32_t unichar) {
+
+        if (unichar < 0x80)
+                return 1;
+        if (unichar < 0x800)
+                return 2;
+        if (unichar < 0x10000)
+                return 3;
+        if (unichar < 0x200000)
+                return 4;
+        if (unichar < 0x4000000)
+                return 5;
+
+        return 6;
+}
+
+/* validate one encoded unicode char and return its length */
+int utf8_encoded_valid_unichar(const char *str) {
+        char32_t unichar;
+        size_t len, i;
+        int r;
+
+        assert(str);
+
+        len = utf8_encoded_expected_len(str);
+        if (len == 0)
+                return -EINVAL;
+
+        /* ascii is valid */
+        if (len == 1)
+                return 1;
+
+        /* check if expected encoded chars are available */
+        for (i = 0; i < len; i++)
+                if ((str[i] & 0x80) != 0x80)
+                        return -EINVAL;
+
+        r = utf8_encoded_to_unichar(str, &unichar);
+        if (r < 0)
+                return r;
+
+        /* check if encoded length matches encoded value */
+        if (utf8_unichar_to_encoded_len(unichar) != (int) len)
+                return -EINVAL;
+
+        /* check if value has valid range */
+        if (!unichar_is_valid(unichar))
+                return -EINVAL;
+
+        return (int) len;
+}
+
+#if 0 /* NM_IGNORED */
+size_t utf8_n_codepoints(const char *str) {
+        size_t n = 0;
+
+        /* Returns the number of UTF-8 codepoints in this string, or (size_t) -1 if the string is not valid UTF-8. */
+
+        while (*str != 0) {
+                int k;
+
+                k = utf8_encoded_valid_unichar(str);
+                if (k < 0)
+                        return (size_t) -1;
+
+                str += k;
+                n++;
+        }
+
+        return n;
+}
+
+size_t utf8_console_width(const char *str) {
+        size_t n = 0;
+
+        /* Returns the approximate width a string will take on screen when printed on a character cell
+         * terminal/console. */
+
+        while (*str != 0) {
+                char32_t c;
+
+                if (utf8_encoded_to_unichar(str, &c) < 0)
+                        return (size_t) -1;
+
+                str = utf8_next_char(str);
+
+                n += unichar_iswide(c) ? 2 : 1;
+        }
+
+        return n;
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/utf8.h b/shared/systemd/src/basic/utf8.h
new file mode 100644
index 00000000..62845693
--- /dev/null
+++ b/shared/systemd/src/basic/utf8.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <uchar.h>
+
+#include "macro.h"
+#include "missing_type.h"
+
+#define UTF8_REPLACEMENT_CHARACTER "\xef\xbf\xbd"
+#define UTF8_BYTE_ORDER_MARK "\xef\xbb\xbf"
+
+bool unichar_is_valid(char32_t c);
+
+char *utf8_is_valid(const char *s) _pure_;
+char *ascii_is_valid(const char *s) _pure_;
+char *ascii_is_valid_n(const char *str, size_t len);
+
+bool utf8_is_printable_newline(const char* str, size_t length, bool newline) _pure_;
+#define utf8_is_printable(str, length) utf8_is_printable_newline(str, length, true)
+
+char *utf8_escape_invalid(const char *s);
+char *utf8_escape_non_printable(const char *str);
+
+size_t utf8_encode_unichar(char *out_utf8, char32_t g);
+size_t utf16_encode_unichar(char16_t *out, char32_t c);
+
+char *utf16_to_utf8(const char16_t *s, size_t length /* bytes! */);
+char16_t *utf8_to_utf16(const char *s, size_t length);
+
+size_t char16_strlen(const char16_t *s); /* returns the number of 16bit words in the string (not bytes!) */
+
+int utf8_encoded_valid_unichar(const char *str);
+int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar);
+
+static inline bool utf16_is_surrogate(char16_t c) {
+        return c >= 0xd800U && c <= 0xdfffU;
+}
+
+static inline bool utf16_is_trailing_surrogate(char16_t c) {
+        return c >= 0xdc00U && c <= 0xdfffU;
+}
+
+static inline char32_t utf16_surrogate_pair_to_unichar(char16_t lead, char16_t trail) {
+        return ((((char32_t) lead - 0xd800U) << 10) + ((char32_t) trail - 0xdc00U) + 0x10000U);
+}
+
+size_t utf8_n_codepoints(const char *str);
+size_t utf8_console_width(const char *str);
diff --git a/shared/systemd/src/basic/util.c b/shared/systemd/src/basic/util.c
new file mode 100644
index 00000000..7686ecd2
--- /dev/null
+++ b/shared/systemd/src/basic/util.c
@@ -0,0 +1,643 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <alloca.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/statfs.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "btrfs-util.h"
+#include "build.h"
+#include "cgroup-util.h"
+#include "def.h"
+#include "device-nodes.h"
+#include "dirent-util.h"
+#include "env-file.h"
+#include "env-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "hashmap.h"
+#include "hostname-util.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "procfs-util.h"
+#include "set.h"
+#include "signal-util.h"
+#include "stat-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "time-util.h"
+#include "umask-util.h"
+#include "user-util.h"
+#include "util.h"
+#include "virt.h"
+
+#if 0 /* NM_IGNORED */
+int saved_argc = 0;
+char **saved_argv = NULL;
+static int saved_in_initrd = -1;
+#endif /* NM_IGNORED */
+
+size_t page_size(void) {
+        static thread_local size_t pgsz = 0;
+        long r;
+
+        if (_likely_(pgsz > 0))
+                return pgsz;
+
+        r = sysconf(_SC_PAGESIZE);
+        assert(r > 0);
+
+        pgsz = (size_t) r;
+        return pgsz;
+}
+
+#if 0 /* NM_IGNORED */
+bool plymouth_running(void) {
+        return access("/run/plymouth/pid", F_OK) >= 0;
+}
+
+bool display_is_local(const char *display) {
+        assert(display);
+
+        return
+                display[0] == ':' &&
+                display[1] >= '0' &&
+                display[1] <= '9';
+}
+
+bool kexec_loaded(void) {
+       _cleanup_free_ char *s = NULL;
+
+       if (read_one_line_file("/sys/kernel/kexec_loaded", &s) < 0)
+               return false;
+
+       return s[0] == '1';
+}
+
+int prot_from_flags(int flags) {
+
+        switch (flags & O_ACCMODE) {
+
+        case O_RDONLY:
+                return PROT_READ;
+
+        case O_WRONLY:
+                return PROT_WRITE;
+
+        case O_RDWR:
+                return PROT_READ|PROT_WRITE;
+
+        default:
+                return -EINVAL;
+        }
+}
+
+bool in_initrd(void) {
+        struct statfs s;
+        int r;
+
+        if (saved_in_initrd >= 0)
+                return saved_in_initrd;
+
+        /* We make two checks here:
+         *
+         * 1. the flag file /etc/initrd-release must exist
+         * 2. the root file system must be a memory file system
+         *
+         * The second check is extra paranoia, since misdetecting an
+         * initrd can have bad consequences due the initrd
+         * emptying when transititioning to the main systemd.
+         */
+
+        r = getenv_bool_secure("SYSTEMD_IN_INITRD");
+        if (r < 0 && r != -ENXIO)
+                log_debug_errno(r, "Failed to parse $SYSTEMD_IN_INITRD, ignoring: %m");
+
+        if (r >= 0)
+                saved_in_initrd = r > 0;
+        else
+                saved_in_initrd = access("/etc/initrd-release", F_OK) >= 0 &&
+                                  statfs("/", &s) >= 0 &&
+                                  is_temporary_fs(&s);
+
+        return saved_in_initrd;
+}
+
+void in_initrd_force(bool value) {
+        saved_in_initrd = value;
+}
+
+/* hey glibc, APIs with callbacks without a user pointer are so useless */
+void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
+                 __compar_d_fn_t compar, void *arg) {
+        size_t l, u, idx;
+        const void *p;
+        int comparison;
+
+        assert(!size_multiply_overflow(nmemb, size));
+
+        l = 0;
+        u = nmemb;
+        while (l < u) {
+                idx = (l + u) / 2;
+                p = (const uint8_t*) base + idx * size;
+                comparison = compar(key, p, arg);
+                if (comparison < 0)
+                        u = idx;
+                else if (comparison > 0)
+                        l = idx + 1;
+                else
+                        return (void *)p;
+        }
+        return NULL;
+}
+
+bool memeqzero(const void *data, size_t length) {
+        /* Does the buffer consist entirely of NULs?
+         * Copied from https://github.com/systemd/casync/, copied in turn from
+         * https://github.com/rustyrussell/ccan/blob/master/ccan/mem/mem.c#L92,
+         * which is licensed CC-0.
+         */
+
+        const uint8_t *p = data;
+        size_t i;
+
+        /* Check first 16 bytes manually */
+        for (i = 0; i < 16; i++, length--) {
+                if (length == 0)
+                        return true;
+                if (p[i])
+                        return false;
+        }
+
+        /* Now we know first 16 bytes are NUL, memcmp with self.  */
+        return memcmp(data, p + i, length) == 0;
+}
+
+int on_ac_power(void) {
+        bool found_offline = false, found_online = false;
+        _cleanup_closedir_ DIR *d = NULL;
+        struct dirent *de;
+
+        d = opendir("/sys/class/power_supply");
+        if (!d)
+                return errno == ENOENT ? true : -errno;
+
+        FOREACH_DIRENT(de, d, return -errno) {
+                _cleanup_close_ int fd = -1, device = -1;
+                char contents[6];
+                ssize_t n;
+
+                device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY);
+                if (device < 0) {
+                        if (IN_SET(errno, ENOENT, ENOTDIR))
+                                continue;
+
+                        return -errno;
+                }
+
+                fd = openat(device, "type", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+                if (fd < 0) {
+                        if (errno == ENOENT)
+                                continue;
+
+                        return -errno;
+                }
+
+                n = read(fd, contents, sizeof(contents));
+                if (n < 0)
+                        return -errno;
+
+                if (n != 6 || memcmp(contents, "Mains\n", 6))
+                        continue;
+
+                safe_close(fd);
+                fd = openat(device, "online", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+                if (fd < 0) {
+                        if (errno == ENOENT)
+                                continue;
+
+                        return -errno;
+                }
+
+                n = read(fd, contents, sizeof(contents));
+                if (n < 0)
+                        return -errno;
+
+                if (n != 2 || contents[1] != '\n')
+                        return -EIO;
+
+                if (contents[0] == '1') {
+                        found_online = true;
+                        break;
+                } else if (contents[0] == '0')
+                        found_offline = true;
+                else
+                        return -EIO;
+        }
+
+        return found_online || !found_offline;
+}
+
+int container_get_leader(const char *machine, pid_t *pid) {
+        _cleanup_free_ char *s = NULL, *class = NULL;
+        const char *p;
+        pid_t leader;
+        int r;
+
+        assert(machine);
+        assert(pid);
+
+        if (streq(machine, ".host")) {
+                *pid = 1;
+                return 0;
+        }
+
+        if (!machine_name_is_valid(machine))
+                return -EINVAL;
+
+        p = strjoina("/run/systemd/machines/", machine);
+        r = parse_env_file(NULL, p,
+                           "LEADER", &s,
+                           "CLASS", &class);
+        if (r == -ENOENT)
+                return -EHOSTDOWN;
+        if (r < 0)
+                return r;
+        if (!s)
+                return -EIO;
+
+        if (!streq_ptr(class, "container"))
+                return -EIO;
+
+        r = parse_pid(s, &leader);
+        if (r < 0)
+                return r;
+        if (leader <= 1)
+                return -EIO;
+
+        *pid = leader;
+        return 0;
+}
+
+int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd) {
+        _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, netnsfd = -1, usernsfd = -1;
+        int rfd = -1;
+
+        assert(pid >= 0);
+
+        if (mntns_fd) {
+                const char *mntns;
+
+                mntns = procfs_file_alloca(pid, "ns/mnt");
+                mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+                if (mntnsfd < 0)
+                        return -errno;
+        }
+
+        if (pidns_fd) {
+                const char *pidns;
+
+                pidns = procfs_file_alloca(pid, "ns/pid");
+                pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+                if (pidnsfd < 0)
+                        return -errno;
+        }
+
+        if (netns_fd) {
+                const char *netns;
+
+                netns = procfs_file_alloca(pid, "ns/net");
+                netnsfd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+                if (netnsfd < 0)
+                        return -errno;
+        }
+
+        if (userns_fd) {
+                const char *userns;
+
+                userns = procfs_file_alloca(pid, "ns/user");
+                usernsfd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+                if (usernsfd < 0 && errno != ENOENT)
+                        return -errno;
+        }
+
+        if (root_fd) {
+                const char *root;
+
+                root = procfs_file_alloca(pid, "root");
+                rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
+                if (rfd < 0)
+                        return -errno;
+        }
+
+        if (pidns_fd)
+                *pidns_fd = pidnsfd;
+
+        if (mntns_fd)
+                *mntns_fd = mntnsfd;
+
+        if (netns_fd)
+                *netns_fd = netnsfd;
+
+        if (userns_fd)
+                *userns_fd = usernsfd;
+
+        if (root_fd)
+                *root_fd = rfd;
+
+        pidnsfd = mntnsfd = netnsfd = usernsfd = -1;
+
+        return 0;
+}
+
+int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) {
+        if (userns_fd >= 0) {
+                /* Can't setns to your own userns, since then you could
+                 * escalate from non-root to root in your own namespace, so
+                 * check if namespaces equal before attempting to enter. */
+                _cleanup_free_ char *userns_fd_path = NULL;
+                int r;
+                if (asprintf(&userns_fd_path, "/proc/self/fd/%d", userns_fd) < 0)
+                        return -ENOMEM;
+
+                r = files_same(userns_fd_path, "/proc/self/ns/user", 0);
+                if (r < 0)
+                        return r;
+                if (r)
+                        userns_fd = -1;
+        }
+
+        if (pidns_fd >= 0)
+                if (setns(pidns_fd, CLONE_NEWPID) < 0)
+                        return -errno;
+
+        if (mntns_fd >= 0)
+                if (setns(mntns_fd, CLONE_NEWNS) < 0)
+                        return -errno;
+
+        if (netns_fd >= 0)
+                if (setns(netns_fd, CLONE_NEWNET) < 0)
+                        return -errno;
+
+        if (userns_fd >= 0)
+                if (setns(userns_fd, CLONE_NEWUSER) < 0)
+                        return -errno;
+
+        if (root_fd >= 0) {
+                if (fchdir(root_fd) < 0)
+                        return -errno;
+
+                if (chroot(".") < 0)
+                        return -errno;
+        }
+
+        return reset_uid_gid();
+}
+
+uint64_t physical_memory(void) {
+        _cleanup_free_ char *root = NULL, *value = NULL;
+        uint64_t mem, lim;
+        size_t ps;
+        long sc;
+        int r;
+
+        /* We return this as uint64_t in case we are running as 32bit process on a 64bit kernel with huge amounts of
+         * memory.
+         *
+         * In order to support containers nicely that have a configured memory limit we'll take the minimum of the
+         * physically reported amount of memory and the limit configured for the root cgroup, if there is any. */
+
+        sc = sysconf(_SC_PHYS_PAGES);
+        assert(sc > 0);
+
+        ps = page_size();
+        mem = (uint64_t) sc * (uint64_t) ps;
+
+        r = cg_get_root_path(&root);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to determine root cgroup, ignoring cgroup memory limit: %m");
+                return mem;
+        }
+
+        r = cg_all_unified();
+        if (r < 0) {
+                log_debug_errno(r, "Failed to determine root unified mode, ignoring cgroup memory limit: %m");
+                return mem;
+        }
+        if (r > 0) {
+                r = cg_get_attribute("memory", root, "memory.max", &value);
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to read memory.max cgroup attribute, ignoring cgroup memory limit: %m");
+                        return mem;
+                }
+
+                if (streq(value, "max"))
+                        return mem;
+        } else {
+                r = cg_get_attribute("memory", root, "memory.limit_in_bytes", &value);
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to read memory.limit_in_bytes cgroup attribute, ignoring cgroup memory limit: %m");
+                        return mem;
+                }
+        }
+
+        r = safe_atou64(value, &lim);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to parse cgroup memory limit '%s', ignoring: %m", value);
+                return mem;
+        }
+        if (lim == UINT64_MAX)
+                return mem;
+
+        /* Make sure the limit is a multiple of our own page size */
+        lim /= ps;
+        lim *= ps;
+
+        return MIN(mem, lim);
+}
+
+uint64_t physical_memory_scale(uint64_t v, uint64_t max) {
+        uint64_t p, m, ps, r;
+
+        assert(max > 0);
+
+        /* Returns the physical memory size, multiplied by v divided by max. Returns UINT64_MAX on overflow. On success
+         * the result is a multiple of the page size (rounds down). */
+
+        ps = page_size();
+        assert(ps > 0);
+
+        p = physical_memory() / ps;
+        assert(p > 0);
+
+        m = p * v;
+        if (m / p != v)
+                return UINT64_MAX;
+
+        m /= max;
+
+        r = m * ps;
+        if (r / ps != m)
+                return UINT64_MAX;
+
+        return r;
+}
+
+uint64_t system_tasks_max(void) {
+
+        uint64_t a = TASKS_MAX, b = TASKS_MAX;
+        _cleanup_free_ char *root = NULL;
+        int r;
+
+        /* Determine the maximum number of tasks that may run on this system. We check three sources to determine this
+         * limit:
+         *
+         * a) the maximum tasks value the kernel allows on this architecture
+         * b) the cgroups pids_max attribute for the system
+         * c) the kernel's configured maximum PID value
+         *
+         * And then pick the smallest of the three */
+
+        r = procfs_tasks_get_limit(&a);
+        if (r < 0)
+                log_debug_errno(r, "Failed to read maximum number of tasks from /proc, ignoring: %m");
+
+        r = cg_get_root_path(&root);
+        if (r < 0)
+                log_debug_errno(r, "Failed to determine cgroup root path, ignoring: %m");
+        else {
+                _cleanup_free_ char *value = NULL;
+
+                r = cg_get_attribute("pids", root, "pids.max", &value);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to read pids.max attribute of cgroup root, ignoring: %m");
+                else if (!streq(value, "max")) {
+                        r = safe_atou64(value, &b);
+                        if (r < 0)
+                                log_debug_errno(r, "Failed to parse pids.max attribute of cgroup root, ignoring: %m");
+                }
+        }
+
+        return MIN3(TASKS_MAX,
+                    a <= 0 ? TASKS_MAX : a,
+                    b <= 0 ? TASKS_MAX : b);
+}
+
+uint64_t system_tasks_max_scale(uint64_t v, uint64_t max) {
+        uint64_t t, m;
+
+        assert(max > 0);
+
+        /* Multiply the system's task value by the fraction v/max. Hence, if max==100 this calculates percentages
+         * relative to the system's maximum number of tasks. Returns UINT64_MAX on overflow. */
+
+        t = system_tasks_max();
+        assert(t > 0);
+
+        m = t * v;
+        if (m / t != v) /* overflow? */
+                return UINT64_MAX;
+
+        return m / max;
+}
+
+int version(void) {
+        puts("systemd " STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")\n"
+             SYSTEMD_FEATURES);
+        return 0;
+}
+
+/* This is a direct translation of str_verscmp from boot.c */
+static bool is_digit(int c) {
+        return c >= '0' && c <= '9';
+}
+
+static int c_order(int c) {
+        if (c == 0 || is_digit(c))
+                return 0;
+
+        if ((c >= 'a') && (c <= 'z'))
+                return c;
+
+        return c + 0x10000;
+}
+
+int str_verscmp(const char *s1, const char *s2) {
+        const char *os1, *os2;
+
+        assert(s1);
+        assert(s2);
+
+        os1 = s1;
+        os2 = s2;
+
+        while (*s1 || *s2) {
+                int first;
+
+                while ((*s1 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) {
+                        int order;
+
+                        order = c_order(*s1) - c_order(*s2);
+                        if (order != 0)
+                                return order;
+                        s1++;
+                        s2++;
+                }
+
+                while (*s1 == '0')
+                        s1++;
+                while (*s2 == '0')
+                        s2++;
+
+                first = 0;
+                while (is_digit(*s1) && is_digit(*s2)) {
+                        if (first == 0)
+                                first = *s1 - *s2;
+                        s1++;
+                        s2++;
+                }
+
+                if (is_digit(*s1))
+                        return 1;
+                if (is_digit(*s2))
+                        return -1;
+
+                if (first != 0)
+                        return first;
+        }
+
+        return strcmp(os1, os2);
+}
+
+/* Turn off core dumps but only if we're running outside of a container. */
+void disable_coredumps(void) {
+        int r;
+
+        if (detect_container() > 0)
+                return;
+
+        r = write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", WRITE_STRING_FILE_DISABLE_BUFFER);
+        if (r < 0)
+                log_debug_errno(r, "Failed to turn off coredumps, ignoring: %m");
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/util.h b/shared/systemd/src/basic/util.h
new file mode 100644
index 00000000..dc33d660
--- /dev/null
+++ b/shared/systemd/src/basic/util.h
@@ -0,0 +1,253 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <alloca.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "format-util.h"
+#include "macro.h"
+#include "time-util.h"
+
+size_t page_size(void) _pure_;
+#define PAGE_ALIGN(l) ALIGN_TO((l), page_size())
+
+static inline const char* yes_no(bool b) {
+        return b ? "yes" : "no";
+}
+
+static inline const char* true_false(bool b) {
+        return b ? "true" : "false";
+}
+
+static inline const char* one_zero(bool b) {
+        return b ? "1" : "0";
+}
+
+static inline const char* enable_disable(bool b) {
+        return b ? "enable" : "disable";
+}
+
+bool plymouth_running(void);
+
+bool display_is_local(const char *display) _pure_;
+
+#define NULSTR_FOREACH(i, l)                                    \
+        for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1)
+
+#define NULSTR_FOREACH_PAIR(i, j, l)                             \
+        for ((i) = (l), (j) = strchr((i), 0)+1; (i) && *(i); (i) = strchr((j), 0)+1, (j) = *(i) ? strchr((i), 0)+1 : (i))
+
+extern int saved_argc;
+extern char **saved_argv;
+
+bool kexec_loaded(void);
+
+int prot_from_flags(int flags) _const_;
+
+bool in_initrd(void);
+void in_initrd_force(bool value);
+
+void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
+                 __compar_d_fn_t compar, void *arg);
+
+#define typesafe_bsearch_r(k, b, n, func, userdata)                     \
+        ({                                                              \
+                const typeof(b[0]) *_k = k;                             \
+                int (*_func_)(const typeof(b[0])*, const typeof(b[0])*, typeof(userdata)) = func; \
+                xbsearch_r((const void*) _k, (b), (n), sizeof((b)[0]), (__compar_d_fn_t) _func_, userdata); \
+        })
+
+/**
+ * Normal bsearch requires base to be nonnull. Here were require
+ * that only if nmemb > 0.
+ */
+static inline void* bsearch_safe(const void *key, const void *base,
+                                 size_t nmemb, size_t size, __compar_fn_t compar) {
+        if (nmemb <= 0)
+                return NULL;
+
+        assert(base);
+        return bsearch(key, base, nmemb, size, compar);
+}
+
+#define typesafe_bsearch(k, b, n, func)                                 \
+        ({                                                              \
+                const typeof(b[0]) *_k = k;                             \
+                int (*_func_)(const typeof(b[0])*, const typeof(b[0])*) = func; \
+                bsearch_safe((const void*) _k, (b), (n), sizeof((b)[0]), (__compar_fn_t) _func_); \
+        })
+
+/**
+ * Normal qsort requires base to be nonnull. Here were require
+ * that only if nmemb > 0.
+ */
+static inline void qsort_safe(void *base, size_t nmemb, size_t size, __compar_fn_t compar) {
+        if (nmemb <= 1)
+                return;
+
+        assert(base);
+        qsort(base, nmemb, size, compar);
+}
+
+/* A wrapper around the above, but that adds typesafety: the element size is automatically derived from the type and so
+ * is the prototype for the comparison function */
+#define typesafe_qsort(p, n, func)                                      \
+        ({                                                              \
+                int (*_func_)(const typeof(p[0])*, const typeof(p[0])*) = func; \
+                qsort_safe((p), (n), sizeof((p)[0]), (__compar_fn_t) _func_); \
+        })
+
+static inline void qsort_r_safe(void *base, size_t nmemb, size_t size, __compar_d_fn_t compar, void *userdata) {
+        if (nmemb <= 1)
+                return;
+
+        assert(base);
+        qsort_r(base, nmemb, size, compar, userdata);
+}
+
+#define typesafe_qsort_r(p, n, func, userdata)                          \
+        ({                                                              \
+                int (*_func_)(const typeof(p[0])*, const typeof(p[0])*, typeof(userdata)) = func; \
+                qsort_r_safe((p), (n), sizeof((p)[0]), (__compar_d_fn_t) _func_, userdata); \
+        })
+
+/* Normal memcpy requires src to be nonnull. We do nothing if n is 0. */
+static inline void memcpy_safe(void *dst, const void *src, size_t n) {
+        if (n == 0)
+                return;
+        assert(src);
+        memcpy(dst, src, n);
+}
+
+/* Normal memcmp requires s1 and s2 to be nonnull. We do nothing if n is 0. */
+static inline int memcmp_safe(const void *s1, const void *s2, size_t n) {
+        if (n == 0)
+                return 0;
+        assert(s1);
+        assert(s2);
+        return memcmp(s1, s2, n);
+}
+
+/* Compare s1 (length n1) with s2 (length n2) in lexicographic order. */
+static inline int memcmp_nn(const void *s1, size_t n1, const void *s2, size_t n2) {
+        return memcmp_safe(s1, s2, MIN(n1, n2))
+            ?: CMP(n1, n2);
+}
+
+int on_ac_power(void);
+
+#define memzero(x,l)                                            \
+        ({                                                      \
+                size_t _l_ = (l);                               \
+                void *_x_ = (x);                                \
+                _l_ == 0 ? _x_ : memset(_x_, 0, _l_);           \
+        })
+
+#define zero(x) (memzero(&(x), sizeof(x)))
+
+bool memeqzero(const void *data, size_t length);
+
+#define eqzero(x) memeqzero(x, sizeof(x))
+
+static inline void *mempset(void *s, int c, size_t n) {
+        memset(s, c, n);
+        return (uint8_t*)s + n;
+}
+
+static inline void _reset_errno_(int *saved_errno) {
+        if (*saved_errno < 0) /* Invalidated by UNPROTECT_ERRNO? */
+                return;
+
+        errno = *saved_errno;
+}
+
+#define PROTECT_ERRNO                                                   \
+        _cleanup_(_reset_errno_) _unused_ int _saved_errno_ = errno
+
+#define UNPROTECT_ERRNO                         \
+        do {                                    \
+                errno = _saved_errno_;          \
+                _saved_errno_ = -1;             \
+        } while (false)
+
+static inline int negative_errno(void) {
+        /* This helper should be used to shut up gcc if you know 'errno' is
+         * negative. Instead of "return -errno;", use "return negative_errno();"
+         * It will suppress bogus gcc warnings in case it assumes 'errno' might
+         * be 0 and thus the caller's error-handling might not be triggered. */
+        assert_return(errno > 0, -EINVAL);
+        return -errno;
+}
+
+static inline unsigned u64log2(uint64_t n) {
+#if __SIZEOF_LONG_LONG__ == 8
+        return (n > 1) ? (unsigned) __builtin_clzll(n) ^ 63U : 0;
+#else
+#error "Wut?"
+#endif
+}
+
+static inline unsigned u32ctz(uint32_t n) {
+#if __SIZEOF_INT__ == 4
+        return n != 0 ? __builtin_ctz(n) : 32;
+#else
+#error "Wut?"
+#endif
+}
+
+static inline unsigned log2i(int x) {
+        assert(x > 0);
+
+        return __SIZEOF_INT__ * 8 - __builtin_clz(x) - 1;
+}
+
+static inline unsigned log2u(unsigned x) {
+        assert(x > 0);
+
+        return sizeof(unsigned) * 8 - __builtin_clz(x) - 1;
+}
+
+static inline unsigned log2u_round_up(unsigned x) {
+        assert(x > 0);
+
+        if (x == 1)
+                return 0;
+
+        return log2u(x - 1) + 1;
+}
+
+int container_get_leader(const char *machine, pid_t *pid);
+
+int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd);
+int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd);
+
+uint64_t physical_memory(void);
+uint64_t physical_memory_scale(uint64_t v, uint64_t max);
+
+uint64_t system_tasks_max(void);
+uint64_t system_tasks_max_scale(uint64_t v, uint64_t max);
+
+int version(void);
+
+int str_verscmp(const char *s1, const char *s2);
+
+void disable_coredumps(void);