about summary refs log tree commit diff
path: root/src/libnm-systemd-shared
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2024-05-05 00:07:30 +0200
committerMichael Biebl <biebl@debian.org>2024-05-05 00:07:30 +0200
commit34bb501be08aa2b313d88e67d6e0a7e0a3f9cfa6 (patch)
tree4e6220877828be4c6f261de09ec0cb2d80e32389 /src/libnm-systemd-shared
parentbba2e4b4de668db525cbfdfc35292e5a0b51671a (diff)
New upstream version 1.47.90 upstream/1.47.90
Diffstat (limited to 'src/libnm-systemd-shared')
-rw-r--r--src/libnm-systemd-shared/README.md57
-rw-r--r--src/libnm-systemd-shared/meson.build3
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/sd-messages.h (renamed from src/libnm-systemd-shared/sd-adapt-shared/netif-util.h)0
-rw-r--r--src/libnm-systemd-shared/src/basic/alloc-util.c27
-rw-r--r--src/libnm-systemd-shared/src/basic/alloc-util.h53
-rw-r--r--src/libnm-systemd-shared/src/basic/arphrd-util.h10
-rw-r--r--src/libnm-systemd-shared/src/basic/async.h13
-rw-r--r--src/libnm-systemd-shared/src/basic/btrfs.c100
-rw-r--r--src/libnm-systemd-shared/src/basic/btrfs.h9
-rw-r--r--src/libnm-systemd-shared/src/basic/cgroup-util.h101
-rw-r--r--src/libnm-systemd-shared/src/basic/chase.h64
-rw-r--r--src/libnm-systemd-shared/src/basic/constants.h23
-rw-r--r--src/libnm-systemd-shared/src/basic/devnum-util.c142
-rw-r--r--src/libnm-systemd-shared/src/basic/devnum-util.h56
-rw-r--r--src/libnm-systemd-shared/src/basic/env-file.c39
-rw-r--r--src/libnm-systemd-shared/src/basic/env-file.h7
-rw-r--r--src/libnm-systemd-shared/src/basic/env-util.c400
-rw-r--r--src/libnm-systemd-shared/src/basic/env-util.h36
-rw-r--r--src/libnm-systemd-shared/src/basic/errno-util.h142
-rw-r--r--src/libnm-systemd-shared/src/basic/escape.c31
-rw-r--r--src/libnm-systemd-shared/src/basic/escape.h1
-rw-r--r--src/libnm-systemd-shared/src/basic/ether-addr-util.c14
-rw-r--r--src/libnm-systemd-shared/src/basic/ether-addr-util.h2
-rw-r--r--src/libnm-systemd-shared/src/basic/extract-word.c41
-rw-r--r--src/libnm-systemd-shared/src/basic/extract-word.h5
-rw-r--r--src/libnm-systemd-shared/src/basic/fd-util.c266
-rw-r--r--src/libnm-systemd-shared/src/basic/fd-util.h37
-rw-r--r--src/libnm-systemd-shared/src/basic/fileio.c272
-rw-r--r--src/libnm-systemd-shared/src/basic/fileio.h15
-rw-r--r--src/libnm-systemd-shared/src/basic/format-util.h8
-rw-r--r--src/libnm-systemd-shared/src/basic/fs-util.c55
-rw-r--r--src/libnm-systemd-shared/src/basic/fs-util.h13
-rw-r--r--src/libnm-systemd-shared/src/basic/glyph-util.c27
-rw-r--r--src/libnm-systemd-shared/src/basic/glyph-util.h28
-rw-r--r--src/libnm-systemd-shared/src/basic/hash-funcs.c10
-rw-r--r--src/libnm-systemd-shared/src/basic/hash-funcs.h6
-rw-r--r--src/libnm-systemd-shared/src/basic/hashmap.c95
-rw-r--r--src/libnm-systemd-shared/src/basic/hashmap.h31
-rw-r--r--src/libnm-systemd-shared/src/basic/hexdecoct.c16
-rw-r--r--src/libnm-systemd-shared/src/basic/hexdecoct.h12
-rw-r--r--src/libnm-systemd-shared/src/basic/in-addr-util.c54
-rw-r--r--src/libnm-systemd-shared/src/basic/in-addr-util.h28
-rw-r--r--src/libnm-systemd-shared/src/basic/inotify-util.c37
-rw-r--r--src/libnm-systemd-shared/src/basic/inotify-util.h26
-rw-r--r--src/libnm-systemd-shared/src/basic/io-util.c158
-rw-r--r--src/libnm-systemd-shared/src/basic/io-util.h76
-rw-r--r--src/libnm-systemd-shared/src/basic/iovec-util.h99
-rw-r--r--src/libnm-systemd-shared/src/basic/list.h12
-rw-r--r--src/libnm-systemd-shared/src/basic/locale-util.c27
-rw-r--r--src/libnm-systemd-shared/src/basic/locale-util.h3
-rw-r--r--src/libnm-systemd-shared/src/basic/lock-util.h3
-rw-r--r--src/libnm-systemd-shared/src/basic/log.h7
-rw-r--r--src/libnm-systemd-shared/src/basic/macro.h56
-rw-r--r--src/libnm-systemd-shared/src/basic/memory-util.c16
-rw-r--r--src/libnm-systemd-shared/src/basic/memory-util.h51
-rw-r--r--src/libnm-systemd-shared/src/basic/missing_fcntl.h21
-rw-r--r--src/libnm-systemd-shared/src/basic/missing_socket.h25
-rw-r--r--src/libnm-systemd-shared/src/basic/missing_stat.h2
-rw-r--r--src/libnm-systemd-shared/src/basic/missing_syscall.h52
-rw-r--r--src/libnm-systemd-shared/src/basic/namespace-util.h57
-rw-r--r--src/libnm-systemd-shared/src/basic/ordered-set.c5
-rw-r--r--src/libnm-systemd-shared/src/basic/parse-util.c74
-rw-r--r--src/libnm-systemd-shared/src/basic/parse-util.h11
-rw-r--r--src/libnm-systemd-shared/src/basic/path-util.c96
-rw-r--r--src/libnm-systemd-shared/src/basic/path-util.h55
-rw-r--r--src/libnm-systemd-shared/src/basic/pidref.h78
-rw-r--r--src/libnm-systemd-shared/src/basic/prioq.c2
-rw-r--r--src/libnm-systemd-shared/src/basic/process-util.c695
-rw-r--r--src/libnm-systemd-shared/src/basic/process-util.h116
-rw-r--r--src/libnm-systemd-shared/src/basic/random-util.c6
-rw-r--r--src/libnm-systemd-shared/src/basic/ratelimit.h2
-rw-r--r--src/libnm-systemd-shared/src/basic/signal-util.c86
-rw-r--r--src/libnm-systemd-shared/src/basic/signal-util.h9
-rw-r--r--src/libnm-systemd-shared/src/basic/siphash24.h9
-rw-r--r--src/libnm-systemd-shared/src/basic/socket-util.c255
-rw-r--r--src/libnm-systemd-shared/src/basic/socket-util.h43
-rw-r--r--src/libnm-systemd-shared/src/basic/sort-util.h7
-rw-r--r--src/libnm-systemd-shared/src/basic/stat-util.c272
-rw-r--r--src/libnm-systemd-shared/src/basic/stat-util.h40
-rw-r--r--src/libnm-systemd-shared/src/basic/string-util.c324
-rw-r--r--src/libnm-systemd-shared/src/basic/string-util.h55
-rw-r--r--src/libnm-systemd-shared/src/basic/strv.c139
-rw-r--r--src/libnm-systemd-shared/src/basic/strv.h30
-rw-r--r--src/libnm-systemd-shared/src/basic/time-util.c126
-rw-r--r--src/libnm-systemd-shared/src/basic/time-util.h21
-rw-r--r--src/libnm-systemd-shared/src/basic/tmpfile-util.h1
-rw-r--r--src/libnm-systemd-shared/src/basic/umask-util.h4
-rw-r--r--src/libnm-systemd-shared/src/basic/user-util.h23
-rw-r--r--src/libnm-systemd-shared/src/basic/utf8.c33
-rw-r--r--src/libnm-systemd-shared/src/basic/utf8.h2
-rw-r--r--src/libnm-systemd-shared/src/fundamental/macro-fundamental.h152
-rw-r--r--src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h42
-rw-r--r--src/libnm-systemd-shared/src/fundamental/sha256.c11
-rw-r--r--src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c16
-rw-r--r--src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h8
-rw-r--r--src/libnm-systemd-shared/src/shared/dns-domain.c43
96 files changed, 4592 insertions, 1376 deletions
diff --git a/src/libnm-systemd-shared/README.md b/src/libnm-systemd-shared/README.md
new file mode 100644
index 00000000..6609df6c
--- /dev/null
+++ b/src/libnm-systemd-shared/README.md
@@ -0,0 +1,57 @@
+libnm-systemd-shared
+====================
+
+This is a fork of systemd source files that are compiled as a static library
+with general purpose helpers.
+
+We mainly need this for [../libnm-systemd-core/](../libnm-systemd-core/), which
+contains builds network tools that we use (our internal DHCPv6 library).
+
+We should not use systemd directly from our sources, beyond what we really need
+to make get libnm-systemd-core working. That means, although the systemd code
+contains many useful utility functions, we should not use them beyond what we
+really need, because one day we want to drop this code again.
+
+
+Reimport Upstream Code
+----------------------
+
+We want to avoid deviations in our fork, and frequently re-import latest
+systemd version (every 4 to 8 weeks). The reason is that we frequently should
+check whether important fixes were done in upstream systemd, and the effort of
+doing that check is half the work of a full reimport.  Also, by reimporting
+frequently, we avoid deviating hugely and fall back too much.
+
+Of course this is cumbersome. We therefore should avoid using the systemd code
+as much as we can, and work towards dropping it altogether.
+
+To do a re-import, do:
+
+- checkout `systemd` branch.
+
+- Use the last commit message (`git commit --allow-empty -C origin/systemd`).
+  Then modify the commit message (`git commit --allow-empty --amend`). The
+  commit message contains a long script that is used to re-import the code.
+  Adjust the script in the commit message, and run it. Commit the changes on
+  `systemd` branch.
+
+- checkout `main` branch, and `git merge systemd`. Fix all issues, test,
+  repeat.
+
+- open merge request, check that all tests pass. In particular, enable build on
+  all test distributions in gitlab-ci.
+
+- push `main` and `systemd` branches. Compare how it was done during past imports.
+
+### Hints
+
+- Eagerly commented out unused functions definitions with `#if 0` and `#endif`.
+- Patching header files is best avoided and keep function declarations.
+- We may create some dummy header files in `src/libnm-systemd-{shared,core}/sd-adapt-\*/`
+  if that is a suitable way to get the code to compile, while having minimal modifications
+  to systemd code.
+- Let git be aware of the merge history. Git can help you to better resolve merge conflicts.
+  For example, a plain rebase of the branch on `main` will result in conflicts. Instead,
+  create a temporary branch and merge the code (no rebase). With the help of the history,
+  git will not run into conflicts during merging. Then, the merged git-tree contains the
+  desired content of the files.
diff --git a/src/libnm-systemd-shared/meson.build b/src/libnm-systemd-shared/meson.build
index b32bd7f6..c93930f3 100644
--- a/src/libnm-systemd-shared/meson.build
+++ b/src/libnm-systemd-shared/meson.build
@@ -5,6 +5,8 @@ libnm_systemd_shared = static_library(
   sources: files(
     'nm-sd-utils-shared.c',
     'src/basic/alloc-util.c',
+    'src/basic/btrfs.c',
+    'src/basic/devnum-util.c',
     'src/basic/env-file.c',
     'src/basic/env-util.c',
     'src/basic/escape.c',
@@ -58,6 +60,7 @@ libnm_systemd_shared = static_library(
     top_inc,
     src_inc,
   ],
+  c_args: libnm_systemd_common_cflags,
   dependencies: glib_dep,
 )
 
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/netif-util.h b/src/libnm-systemd-shared/sd-adapt-shared/sd-messages.h
index 637892c2..637892c2 100644
--- a/src/libnm-systemd-shared/sd-adapt-shared/netif-util.h
+++ b/src/libnm-systemd-shared/sd-adapt-shared/sd-messages.h
diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.c b/src/libnm-systemd-shared/src/basic/alloc-util.c
index c07ab589..243ff521 100644
--- a/src/libnm-systemd-shared/src/basic/alloc-util.c
+++ b/src/libnm-systemd-shared/src/basic/alloc-util.c
@@ -105,6 +105,33 @@ void* greedy_realloc0(
         return q;
 }
 
+void* greedy_realloc_append(
+                void **p,
+                size_t *n_p,
+                const void *from,
+                size_t n_from,
+                size_t size) {
+
+        uint8_t *q;
+
+        assert(p);
+        assert(n_p);
+        assert(from || n_from == 0);
+
+        if (n_from > SIZE_MAX - *n_p)
+                return NULL;
+
+        q = greedy_realloc(p, *n_p + n_from, size);
+        if (!q)
+                return NULL;
+
+        memcpy_safe(q + *n_p * size, from, n_from * size);
+
+        *n_p += n_from;
+
+        return q;
+}
+
 void *expand_to_usable(void *ptr, size_t newsize _unused_) {
         return ptr;
 }
diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.h b/src/libnm-systemd-shared/src/basic/alloc-util.h
index 9a62381d..c215c33f 100644
--- a/src/libnm-systemd-shared/src/basic/alloc-util.h
+++ b/src/libnm-systemd-shared/src/basic/alloc-util.h
@@ -15,13 +15,12 @@
 
 typedef void (*free_func_t)(void *p);
 typedef void* (*mfree_func_t)(void *p);
-typedef void (*free_array_func_t)(void *p, size_t n);
 
 /* 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 new(t, n) ((t*) malloc_multiply(n, sizeof(t)))
 
 #define new0(t, n) ((t*) calloc((n) ?: 1, sizeof(t)))
 
@@ -46,9 +45,9 @@ typedef void (*free_array_func_t)(void *p, size_t n);
                 (t*) alloca0((sizeof(t)*_n_));                          \
         })
 
-#define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n)))
+#define newdup(t, p, n) ((t*) memdup_multiply(p, n, sizeof(t)))
 
-#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, sizeof(t), (n)))
+#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, n, sizeof(t)))
 
 #define malloc0(n) (calloc(1, (n) ?: 1))
 
@@ -113,7 +112,7 @@ 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) {
+_malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t need, size_t size) {
         if (size_multiply_overflow(size, need))
                 return NULL;
 
@@ -129,7 +128,7 @@ _alloc_(2, 3) static inline void *reallocarray(void *p, size_t need, size_t size
 }
 #endif
 
-_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, size_t need) {
+_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t need, size_t size) {
         if (size_multiply_overflow(size, need))
                 return NULL;
 
@@ -138,7 +137,7 @@ _alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, si
 
 /* Note that we can't decorate this function with _alloc_() since the returned memory area is one byte larger
  * than the product of its parameters. */
-static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t need) {
+static inline void *memdup_suffix0_multiply(const void *p, size_t need, size_t size) {
         if (size_multiply_overflow(size, need))
                 return NULL;
 
@@ -147,6 +146,7 @@ static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t n
 
 void* greedy_realloc(void **p, size_t need, size_t size);
 void* greedy_realloc0(void **p, size_t need, size_t size);
+void* greedy_realloc_append(void **p, size_t *n_p, const void *from, size_t n_from, size_t size);
 
 #define GREEDY_REALLOC(array, need)                                     \
         greedy_realloc((void**) &(array), (need), sizeof((array)[0]))
@@ -154,6 +154,9 @@ void* greedy_realloc0(void **p, size_t need, size_t size);
 #define GREEDY_REALLOC0(array, need)                                    \
         greedy_realloc0((void**) &(array), (need), sizeof((array)[0]))
 
+#define GREEDY_REALLOC_APPEND(array, n_array, from, n_from)             \
+        greedy_realloc_append((void**) &(array), (size_t*) &(n_array), (from), (n_from), sizeof((array)[0]))
+
 #define alloca0(n)                                      \
         ({                                              \
                 char *_new_;                            \
@@ -224,7 +227,6 @@ static inline size_t malloc_sizeof_safe(void **xp) {
                 MALLOC_SIZEOF_SAFE(x)/sizeof((x)[0]),                   \
                 VOID_0))
 
-
 /* These are like strdupa()/strndupa(), but honour ALLOCA_MAX */
 #define strdupa_safe(s)                                                 \
         ({                                                              \
@@ -235,7 +237,40 @@ static inline size_t malloc_sizeof_safe(void **xp) {
 #define strndupa_safe(s, n)                                             \
         ({                                                              \
                 const char *_t = (s);                                   \
-                (char*) memdupa_suffix0(_t, strnlen(_t, (n)));          \
+                (char*) memdupa_suffix0(_t, strnlen(_t, n));            \
         })
 
+/* Free every element of the array. */
+static inline void free_many(void **p, size_t n) {
+        assert(p || n == 0);
+
+        FOREACH_ARRAY(i, p, n)
+                *i = mfree(*i);
+}
+
+/* Typesafe wrapper for char** rather than void**. Unfortunately C won't implicitly cast this. */
+static inline void free_many_charp(char **c, size_t n) {
+        free_many((void**) c, n);
+}
+
+_alloc_(2) static inline void *realloc0(void *p, size_t new_size) {
+        size_t old_size;
+        void *q;
+
+        /* Like realloc(), but initializes anything appended to zero */
+
+        old_size = MALLOC_SIZEOF_SAFE(p);
+
+        q = realloc(p, new_size);
+        if (!q)
+                return NULL;
+
+        new_size = MALLOC_SIZEOF_SAFE(q); /* Update with actually allocated space */
+
+        if (new_size > old_size)
+                memset((uint8_t*) q + old_size, 0, new_size - old_size);
+
+        return q;
+}
+
 #include "memory-util.h"
diff --git a/src/libnm-systemd-shared/src/basic/arphrd-util.h b/src/libnm-systemd-shared/src/basic/arphrd-util.h
new file mode 100644
index 00000000..33f5694a
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/arphrd-util.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <inttypes.h>
+#include <stddef.h>
+
+const char *arphrd_to_name(int id);
+int arphrd_from_name(const char *name);
+
+size_t arphrd_to_hw_addr_len(uint16_t arphrd);
diff --git a/src/libnm-systemd-shared/src/basic/async.h b/src/libnm-systemd-shared/src/basic/async.h
deleted file mode 100644
index e0bbaa56..00000000
--- a/src/libnm-systemd-shared/src/basic/async.h
+++ /dev/null
@@ -1,13 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-#include <sys/types.h>
-
-#include "macro.h"
-
-int asynchronous_job(void* (*func)(void *p), void *arg);
-
-int asynchronous_sync(pid_t *ret_pid);
-int asynchronous_close(int fd);
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(int, asynchronous_close);
diff --git a/src/libnm-systemd-shared/src/basic/btrfs.c b/src/libnm-systemd-shared/src/basic/btrfs.c
new file mode 100644
index 00000000..3b236078
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/btrfs.c
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <linux/btrfs.h>
+#include <sys/ioctl.h>
+
+#include "btrfs.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "path-util.h"
+
+int btrfs_validate_subvolume_name(const char *name) {
+
+        if (!filename_is_valid(name))
+                return -EINVAL;
+
+        if (strlen(name) > BTRFS_SUBVOL_NAME_MAX)
+                return -E2BIG;
+
+        return 0;
+}
+
+static int extract_subvolume_name(const char *path, char **ret) {
+        _cleanup_free_ char *fn = NULL;
+        int r;
+
+        assert(path);
+        assert(ret);
+
+        r = path_extract_filename(path, &fn);
+        if (r < 0)
+                return r;
+
+        r = btrfs_validate_subvolume_name(fn);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(fn);
+        return 0;
+}
+
+int btrfs_subvol_make(int dir_fd, const char *path) {
+        struct btrfs_ioctl_vol_args args = {};
+        _cleanup_free_ char *subvolume = NULL, *parent = NULL;
+        _cleanup_close_ int fd = -EBADF;
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(!isempty(path));
+
+        r = extract_subvolume_name(path, &subvolume);
+        if (r < 0)
+                return r;
+
+        r = path_extract_directory(path, &parent);
+        if (r < 0) {
+                if (r != -EDESTADDRREQ) /* Propagate error, unless only a filename was specified, which is OK */
+                        return r;
+
+                dir_fd = fd_reopen_condition(dir_fd, O_CLOEXEC, O_PATH, &fd); /* drop O_PATH if it is set */
+                if (dir_fd < 0)
+                        return dir_fd;
+        } else {
+                fd = openat(dir_fd, parent, O_DIRECTORY|O_RDONLY|O_CLOEXEC, 0);
+                if (fd < 0)
+                        return -errno;
+
+                dir_fd = fd;
+        }
+
+        strncpy(args.name, subvolume, sizeof(args.name)-1);
+
+        return RET_NERRNO(ioctl(dir_fd, BTRFS_IOC_SUBVOL_CREATE, &args));
+}
+
+int btrfs_subvol_make_fallback(int dir_fd, const char *path, mode_t mode) {
+        mode_t old, combined;
+        int r;
+
+        assert(path);
+
+        /* Let's work like mkdir(), i.e. take the specified mode, and mask it with the current umask. */
+        old = umask(~mode);
+        combined = old | ~mode;
+        if (combined != ~mode)
+                umask(combined);
+        r = btrfs_subvol_make(dir_fd, path);
+        umask(old);
+
+        if (r >= 0)
+                return 1; /* subvol worked */
+        if (!ERRNO_IS_NOT_SUPPORTED(r))
+                return r;
+
+        if (mkdirat(dir_fd, path, mode) < 0)
+                return -errno;
+
+        return 0; /* plain directory */
+}
diff --git a/src/libnm-systemd-shared/src/basic/btrfs.h b/src/libnm-systemd-shared/src/basic/btrfs.h
new file mode 100644
index 00000000..38be9d2b
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/btrfs.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+
+int btrfs_validate_subvolume_name(const char *name);
+
+int btrfs_subvol_make(int dir_fd, const char *path);
+
+int btrfs_subvol_make_fallback(int dir_fd, const char *path, mode_t mode);
diff --git a/src/libnm-systemd-shared/src/basic/cgroup-util.h b/src/libnm-systemd-shared/src/basic/cgroup-util.h
index 9b30ae03..244f3b65 100644
--- a/src/libnm-systemd-shared/src/basic/cgroup-util.h
+++ b/src/libnm-systemd-shared/src/basic/cgroup-util.h
@@ -10,6 +10,7 @@
 #include <sys/types.h>
 
 #include "constants.h"
+#include "pidref.h"
 #include "set.h"
 
 #define SYSTEMD_CGROUP_CONTROLLER_LEGACY "name=systemd"
@@ -35,7 +36,7 @@ typedef enum CGroupController {
         CGROUP_CONTROLLER_BPF_SOCKET_BIND,
         CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES,
         /* The BPF hook implementing RestrictFileSystems= is not defined here.
-         * It's applied as late as possible in exec_child() so we don't block
+         * It's applied as late as possible in exec_invoke() so we don't block
          * our own unit setup code. */
 
         _CGROUP_CONTROLLER_MAX,
@@ -66,10 +67,13 @@ typedef enum CGroupMask {
         /* All real cgroup v2 controllers */
         CGROUP_MASK_V2 = CGROUP_MASK_CPU|CGROUP_MASK_CPUSET|CGROUP_MASK_IO|CGROUP_MASK_MEMORY|CGROUP_MASK_PIDS,
 
+        /* All controllers we want to delegate in case of Delegate=yes. Which are pretty much the v2 controllers only, as delegation on v1 is not safe, and bpf stuff isn't a real controller */
+        CGROUP_MASK_DELEGATE = CGROUP_MASK_V2,
+
         /* All cgroup v2 BPF pseudo-controllers */
         CGROUP_MASK_BPF = CGROUP_MASK_BPF_FIREWALL|CGROUP_MASK_BPF_DEVICES|CGROUP_MASK_BPF_FOREIGN|CGROUP_MASK_BPF_SOCKET_BIND|CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES,
 
-        _CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1
+        _CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1,
 } CGroupMask;
 
 static inline CGroupMask CGROUP_MASK_EXTEND_JOINED(CGroupMask mask) {
@@ -176,13 +180,13 @@ typedef enum CGroupUnified {
  * generate paths with multiple adjacent / removed.
  */
 
-int cg_enumerate_processes(const char *controller, const char *path, FILE **_f);
-int cg_read_pid(FILE *f, pid_t *_pid);
-int cg_read_event(const char *controller, const char *path, const char *event,
-                  char **val);
+int cg_enumerate_processes(const char *controller, const char *path, FILE **ret);
+int cg_read_pid(FILE *f, pid_t *ret);
+int cg_read_pidref(FILE *f, PidRef *ret);
+int cg_read_event(const char *controller, const char *path, const char *event, char **ret);
 
-int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d);
-int cg_read_subgroup(DIR *d, char **fn);
+int cg_enumerate_subgroups(const char *controller, const char *path, DIR **ret);
+int cg_read_subgroup(DIR *d, char **ret);
 
 typedef enum CGroupFlags {
         CGROUP_SIGCONT     = 1 << 0,
@@ -190,25 +194,31 @@ typedef enum CGroupFlags {
         CGROUP_REMOVE      = 1 << 2,
 } CGroupFlags;
 
-typedef int (*cg_kill_log_func_t)(pid_t pid, int sig, void *userdata);
+typedef int (*cg_kill_log_func_t)(const PidRef *pid, int sig, void *userdata);
 
-int cg_kill(const char *controller, const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata);
-int cg_kill_kernel_sigkill(const char *controller, const char *path);
-int cg_kill_recursive(const char *controller, const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata);
+int cg_kill(const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata);
+int cg_kill_kernel_sigkill(const char *path);
+int cg_kill_recursive(const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata);
 
 int cg_split_spec(const char *spec, char **ret_controller, char **ret_path);
-int cg_mangle_path(const char *path, char **result);
+int cg_mangle_path(const char *path, char **ret);
 
-int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs);
-int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs);
+int cg_get_path(const char *controller, const char *path, const char *suffix, char **ret);
+int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **ret);
 
-int cg_pid_get_path(const char *controller, pid_t pid, char **path);
+int cg_pid_get_path(const char *controller, pid_t pid, char **ret);
+int cg_pidref_get_path(const char *controller, const PidRef *pidref, char **ret);
 
 int cg_rmdir(const char *controller, const char *path);
 
-int cg_is_threaded(const char *controller, const char *path);
+int cg_is_threaded(const char *path);
+
+int cg_is_delegated(const char *path);
+int cg_is_delegated_fd(int fd);
+
+int cg_has_coredump_receive(const char *path);
 
-typedef enum  {
+typedef enum {
         CG_KEY_MODE_GRACEFUL = 1 << 0,
 } CGroupKeyMode;
 
@@ -239,14 +249,14 @@ int cg_get_attribute_as_uint64(const char *controller, const char *path, const c
 /* Does a parse_boolean() on the attribute contents and sets ret accordingly */
 int cg_get_attribute_as_bool(const char *controller, const char *path, const char *attribute, bool *ret);
 
-int cg_get_owner(const char *controller, const char *path, uid_t *ret_uid);
+int cg_get_owner(const char *path, uid_t *ret_uid);
 
-int cg_set_xattr(const char *controller, const char *path, const char *name, const void *value, size_t size, int flags);
-int cg_get_xattr(const char *controller, const char *path, const char *name, void *value, size_t size);
-int cg_get_xattr_malloc(const char *controller, const char *path, const char *name, char **ret);
+int cg_set_xattr(const char *path, const char *name, const void *value, size_t size, int flags);
+int cg_get_xattr(const char *path, const char *name, void *value, size_t size);
+int cg_get_xattr_malloc(const char *path, const char *name, char **ret);
 /* Returns negative on error, and 0 or 1 on success for the bool value */
-int cg_get_xattr_bool(const char *controller, const char *path, const char *name);
-int cg_remove_xattr(const char *controller, const char *path, const char *name);
+int cg_get_xattr_bool(const char *path, const char *name);
+int cg_remove_xattr(const char *path, const char *name);
 
 int cg_install_release_agent(const char *controller, const char *agent);
 int cg_uninstall_release_agent(const char *controller);
@@ -257,27 +267,28 @@ int cg_is_empty_recursive(const char *controller, const char *path);
 int cg_get_root_path(char **path);
 
 int cg_path_get_cgroupid(const char *path, uint64_t *ret);
-int cg_path_get_session(const char *path, char **session);
-int cg_path_get_owner_uid(const char *path, uid_t *uid);
-int cg_path_get_unit(const char *path, char **unit);
-int cg_path_get_unit_path(const char *path, char **unit);
-int cg_path_get_user_unit(const char *path, char **unit);
-int cg_path_get_machine_name(const char *path, char **machine);
-int cg_path_get_slice(const char *path, char **slice);
-int cg_path_get_user_slice(const char *path, char **slice);
-
-int cg_shift_path(const char *cgroup, const char *cached_root, const char **shifted);
-int cg_pid_get_path_shifted(pid_t pid, const char *cached_root, char **cgroup);
-
-int cg_pid_get_session(pid_t pid, char **session);
-int cg_pid_get_owner_uid(pid_t pid, uid_t *uid);
-int cg_pid_get_unit(pid_t pid, char **unit);
-int cg_pid_get_user_unit(pid_t pid, char **unit);
-int cg_pid_get_machine_name(pid_t pid, char **machine);
-int cg_pid_get_slice(pid_t pid, char **slice);
-int cg_pid_get_user_slice(pid_t pid, char **slice);
-
-int cg_path_decode_unit(const char *cgroup, char **unit);
+int cg_path_get_session(const char *path, char **ret_session);
+int cg_path_get_owner_uid(const char *path, uid_t *ret_uid);
+int cg_path_get_unit(const char *path, char **ret_unit);
+int cg_path_get_unit_path(const char *path, char **ret_unit);
+int cg_path_get_user_unit(const char *path, char **ret_unit);
+int cg_path_get_machine_name(const char *path, char **ret_machine);
+int cg_path_get_slice(const char *path, char **ret_slice);
+int cg_path_get_user_slice(const char *path, char **ret_slice);
+
+int cg_shift_path(const char *cgroup, const char *cached_root, const char **ret_shifted);
+int cg_pid_get_path_shifted(pid_t pid, const char *cached_root, char **ret_cgroup);
+
+int cg_pid_get_session(pid_t pid, char **ret_session);
+int cg_pid_get_owner_uid(pid_t pid, uid_t *ret_uid);
+int cg_pid_get_unit(pid_t pid, char **ret_unit);
+int cg_pidref_get_unit(const PidRef *pidref, char **ret);
+int cg_pid_get_user_unit(pid_t pid, char **ret_unit);
+int cg_pid_get_machine_name(pid_t pid, char **ret_machine);
+int cg_pid_get_slice(pid_t pid, char **ret_slice);
+int cg_pid_get_user_slice(pid_t pid, char **ret_slice);
+
+int cg_path_decode_unit(const char *cgroup, char **ret_unit);
 
 bool cg_needs_escape(const char *p);
 int cg_escape(const char *p, char **ret);
diff --git a/src/libnm-systemd-shared/src/basic/chase.h b/src/libnm-systemd-shared/src/basic/chase.h
new file mode 100644
index 00000000..cfc714b9
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/chase.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <dirent.h>
+#include <stdio.h>
+
+#include "stat-util.h"
+
+typedef enum ChaseFlags {
+        CHASE_PREFIX_ROOT        = 1 << 0,  /* The specified path will be prefixed by the specified root before beginning the iteration */
+        CHASE_NONEXISTENT        = 1 << 1,  /* It's OK if the path doesn't actually exist. */
+        CHASE_NO_AUTOFS          = 1 << 2,  /* Return -EREMOTE if autofs mount point found */
+        CHASE_SAFE               = 1 << 3,  /* Return -EPERM if we ever traverse from unprivileged to privileged files or directories */
+        CHASE_TRAIL_SLASH        = 1 << 4,  /* Any trailing slash will be preserved */
+        CHASE_STEP               = 1 << 5,  /* Just execute a single step of the normalization */
+        CHASE_NOFOLLOW           = 1 << 6,  /* Do not follow the path's right-most component. With ret_fd, when the path's
+                                             * right-most component refers to symlink, return O_PATH fd of the symlink. */
+        CHASE_WARN               = 1 << 7,  /* Emit an appropriate warning when an error is encountered.
+                                             * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */
+        CHASE_AT_RESOLVE_IN_ROOT = 1 << 8,  /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved
+                                             * relative to the given directory fd instead of root. */
+        CHASE_PROHIBIT_SYMLINKS  = 1 << 9,  /* Refuse all symlinks */
+        CHASE_PARENT             = 1 << 10, /* Chase the parent directory of the given path. Note that the
+                                             * full path is still stored in ret_path and only the returned
+                                             * file descriptor will point to the parent directory. Note that
+                                             * the result path is the root or '.', then the file descriptor
+                                             * also points to the result path even if this flag is set.
+                                             * When this specified, chase() will succeed with 1 even if the
+                                             * file points to the last path component does not exist. */
+        CHASE_MKDIR_0755         = 1 << 11, /* Create any missing parent directories in the given path. This
+                                             * needs to be set with CHASE_NONEXISTENT and/or CHASE_PARENT.
+                                             * Note, chase_and_open() or friends always add CHASE_PARENT flag
+                                             * when internally call chase(), hence CHASE_MKDIR_0755 can be
+                                             * safely set without CHASE_NONEXISTENT and CHASE_PARENT. */
+        CHASE_EXTRACT_FILENAME   = 1 << 12, /* Only return the last component of the resolved path */
+} ChaseFlags;
+
+bool unsafe_transition(const struct stat *a, const struct stat *b);
+
+/* How many iterations to execute before returning -ELOOP */
+#define CHASE_MAX 32
+
+int chase(const char *path_with_prefix, const char *root, ChaseFlags chase_flags, char **ret_path, int *ret_fd);
+
+int chaseat_prefix_root(const char *path, const char *root, char **ret);
+int chase_extract_filename(const char *path, const char *root, char **ret);
+
+int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path);
+int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir);
+int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat);
+int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path);
+int chase_and_fopen_unlocked(const char *path, const char *root, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
+int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path);
+int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename);
+
+int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd);
+
+int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path);
+int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir);
+int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat);
+int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path);
+int chase_and_fopenat_unlocked(int dir_fd, const char *path, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
+int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path);
+int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename);
diff --git a/src/libnm-systemd-shared/src/basic/constants.h b/src/libnm-systemd-shared/src/basic/constants.h
index 3f96786d..6bb5f3c2 100644
--- a/src/libnm-systemd-shared/src/basic/constants.h
+++ b/src/libnm-systemd-shared/src/basic/constants.h
@@ -59,22 +59,13 @@
 #define NOTIFY_FD_MAX 768
 #define NOTIFY_BUFFER_MAX PIPE_BUF
 
-#if HAVE_SPLIT_USR
-#  define _CONF_PATHS_SPLIT_USR_NULSTR(n) "/lib/" n "\0"
-#  define _CONF_PATHS_SPLIT_USR(n) , "/lib/" n
-#else
-#  define _CONF_PATHS_SPLIT_USR_NULSTR(n)
-#  define _CONF_PATHS_SPLIT_USR(n)
-#endif
-
 /* Return a nulstr for a standard cascade of configuration paths, suitable to pass to
  * conf_files_list_nulstr() to implement drop-in directories for extending configuration files. */
 #define CONF_PATHS_NULSTR(n)                    \
         "/etc/" n "\0"                          \
         "/run/" n "\0"                          \
         "/usr/local/lib/" n "\0"                \
-        "/usr/lib/" n "\0"                      \
-        _CONF_PATHS_SPLIT_USR_NULSTR(n)
+        "/usr/lib/" n "\0"
 
 #define CONF_PATHS_USR(n)                       \
         "/etc/" n,                              \
@@ -83,8 +74,7 @@
         "/usr/lib/" n
 
 #define CONF_PATHS(n)                           \
-        CONF_PATHS_USR(n)                       \
-        _CONF_PATHS_SPLIT_USR(n)
+        CONF_PATHS_USR(n)
 
 #define CONF_PATHS_USR_STRV(n)                  \
         STRV_MAKE(CONF_PATHS_USR(n))
@@ -99,14 +89,9 @@
  * in containers so that our children inherit that. */
 #define DEFAULT_RLIMIT_MEMLOCK (1024ULL*1024ULL*8ULL)
 
-#define PLYMOUTH_SOCKET {                                       \
-                .un.sun_family = AF_UNIX,                       \
-                .un.sun_path = "\0/org/freedesktop/plymouthd",  \
-        }
-
 /* Path where PID1 listens for varlink subscriptions from systemd-oomd to notify of changes in ManagedOOM settings. */
-#define VARLINK_ADDR_PATH_MANAGED_OOM_SYSTEM "/run/systemd/io.system.ManagedOOM"
+#define VARLINK_ADDR_PATH_MANAGED_OOM_SYSTEM "/run/systemd/io.systemd.ManagedOOM"
 /* Path where systemd-oomd listens for varlink connections from user managers to report changes in ManagedOOM settings. */
-#define VARLINK_ADDR_PATH_MANAGED_OOM_USER "/run/systemd/oom/io.system.ManagedOOM"
+#define VARLINK_ADDR_PATH_MANAGED_OOM_USER "/run/systemd/oom/io.systemd.ManagedOOM"
 
 #define KERNEL_BASELINE_VERSION "4.15"
diff --git a/src/libnm-systemd-shared/src/basic/devnum-util.c b/src/libnm-systemd-shared/src/basic/devnum-util.c
new file mode 100644
index 00000000..b13c9fc6
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/devnum-util.c
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <string.h>
+#include <sys/stat.h>
+
+#include "chase.h"
+#include "devnum-util.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "string-util.h"
+
+int parse_devnum(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 (n > DECIMAL_STR_MAX(dev_t))
+                return -EINVAL;
+        if (s[n] != ':')
+                return -EINVAL;
+
+        major = strndupa_safe(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 device_path_make_major_minor(mode_t mode, dev_t devnum, 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/" DEVNUM_FORMAT_STR, t, DEVNUM_FORMAT_VAL(devnum)) < 0)
+                return -ENOMEM;
+
+        return 0;
+}
+
+int device_path_make_inaccessible(mode_t mode, char **ret) {
+        char *s;
+
+        assert(ret);
+
+        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;
+}
+
+#if 0 /* NM_IGNORED */
+int device_path_make_canonical(mode_t mode, dev_t devnum, 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 (devnum_is_zero(devnum))
+                /* 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. */
+                return device_path_make_inaccessible(mode, ret);
+
+        r = device_path_make_major_minor(mode, devnum, &p);
+        if (r < 0)
+                return r;
+
+        return chase(p, NULL, 0, ret, NULL);
+}
+#endif /* NM_IGNORED */
+
+int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devnum) {
+        mode_t mode;
+        dev_t devnum;
+        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;
+                devnum = makedev(0, 0);
+        } else if (path_equal(path, "/run/systemd/inaccessible/blk")) {
+                mode = S_IFBLK;
+                devnum = 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_devnum(w, &devnum);
+                if (r < 0)
+                        return r;
+        }
+
+        if (ret_mode)
+                *ret_mode = mode;
+        if (ret_devnum)
+                *ret_devnum = devnum;
+
+        return 0;
+}
diff --git a/src/libnm-systemd-shared/src/basic/devnum-util.h b/src/libnm-systemd-shared/src/basic/devnum-util.h
new file mode 100644
index 00000000..e109de99
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/devnum-util.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "stdio-util.h"
+
+int parse_devnum(const char *s, dev_t *ret);
+
+/* 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 devnum, char **ret);
+int device_path_make_inaccessible(mode_t mode, char **ret);
+int device_path_make_canonical(mode_t mode, dev_t devnum, char **ret);
+int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devnum);
+
+static inline bool devnum_set_and_equal(dev_t a, dev_t b) {
+        /* Returns true if a and b definitely refer to the same device. If either is zero, this means "don't
+         * know" and we'll return false */
+        return a == b && a != 0;
+}
+
+/* Maximum string length for a major:minor string. (Note that DECIMAL_STR_MAX includes space for a trailing NUL) */
+#define DEVNUM_STR_MAX (DECIMAL_STR_MAX(dev_t)-1+1+DECIMAL_STR_MAX(dev_t))
+
+#define DEVNUM_FORMAT_STR "%u:%u"
+#define DEVNUM_FORMAT_VAL(d) major(d), minor(d)
+
+static inline char *format_devnum(dev_t d, char buf[static DEVNUM_STR_MAX]) {
+        return ASSERT_PTR(snprintf_ok(buf, DEVNUM_STR_MAX, DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d)));
+}
+
+#define FORMAT_DEVNUM(d) format_devnum((d), (char[DEVNUM_STR_MAX]) {})
+
+static inline bool devnum_is_zero(dev_t d) {
+        return major(d) == 0 && minor(d) == 0;
+}
diff --git a/src/libnm-systemd-shared/src/basic/env-file.c b/src/libnm-systemd-shared/src/basic/env-file.c
index db270bed..75b2febf 100644
--- a/src/libnm-systemd-shared/src/basic/env-file.c
+++ b/src/libnm-systemd-shared/src/basic/env-file.c
@@ -127,7 +127,7 @@ static int parse_env_file_internal(
                                 state = VALUE;
 
                                 if (!GREEDY_REALLOC(value, n_value+2))
-                                        return  -ENOMEM;
+                                        return -ENOMEM;
 
                                 value[n_value++] = c;
                         }
@@ -245,7 +245,13 @@ static int parse_env_file_internal(
                         break;
 
                 case COMMENT_ESCAPE:
-                        state = COMMENT;
+                        log_debug("The line which doesn't begin with \";\" or \"#\", but follows a comment" \
+                                  " line trailing with escape is now treated as a non comment line since v254.");
+                        if (strchr(NEWLINE, c)) {
+                                state = PRE_KEY;
+                                line++;
+                        } else
+                                state = COMMENT;
                         break;
                 }
         }
@@ -522,6 +528,7 @@ static int merge_env_file_push(
 
         char ***env = ASSERT_PTR(userdata);
         char *expanded_value;
+        int r;
 
         assert(key);
 
@@ -536,12 +543,12 @@ static int merge_env_file_push(
                 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;
+        r = replace_env(value,
+                        *env,
+                        REPLACE_ENV_USE_ENVIRONMENT|REPLACE_ENV_ALLOW_BRACELESS|REPLACE_ENV_ALLOW_EXTENDED,
+                        &expanded_value);
+        if (r < 0)
+                return log_error_errno(r, "%s:%u: Failed to expand variable '%s': %m", strna(filename), line, value);
 
         free_and_replace(value, expanded_value);
 
@@ -599,7 +606,7 @@ static void write_env_var(FILE *f, const char *v) {
         fputc_unlocked('\n', f);
 }
 
-int write_env_file_at(int dir_fd, const char *fname, char **l) {
+int write_env_file(int dir_fd, const char *fname, char **headers, char **l) {
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *p = NULL;
         int r;
@@ -613,6 +620,12 @@ int write_env_file_at(int dir_fd, const char *fname, char **l) {
 
         (void) fchmod_umask(fileno(f), 0644);
 
+        STRV_FOREACH(i, headers) {
+                assert(isempty(*i) || startswith(*i, "#"));
+                fputs_unlocked(*i, f);
+                fputc_unlocked('\n', f);
+        }
+
         STRV_FOREACH(i, l)
                 write_env_var(f, *i);
 
@@ -627,4 +640,12 @@ int write_env_file_at(int dir_fd, const char *fname, char **l) {
         (void) unlinkat(dir_fd, p, 0);
         return r;
 }
+
+int write_vconsole_conf(int dir_fd, const char *fname, char **l) {
+        char **headers = STRV_MAKE(
+                "# Written by systemd-localed(8) or systemd-firstboot(1), read by systemd-localed",
+                "# and systemd-vconsole-setup(8). Use localectl(1) to update this file.");
+
+        return write_env_file(dir_fd, fname, headers, l);
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/env-file.h b/src/libnm-systemd-shared/src/basic/env-file.h
index 2465eedd..37db3076 100644
--- a/src/libnm-systemd-shared/src/basic/env-file.h
+++ b/src/libnm-systemd-shared/src/basic/env-file.h
@@ -19,7 +19,6 @@ int load_env_file_pairs_fd(int fd, const char *fname, char ***ret);
 
 int merge_env_file(char ***env, FILE *f, const char *fname);
 
-int write_env_file_at(int dir_fd, const char *fname, char **l);
-static inline int write_env_file(const char *fname, char **l) {
-        return write_env_file_at(AT_FDCWD, fname, l);
-}
+int write_env_file(int dir_fd, const char *fname, char **headers, char **l);
+
+int write_vconsole_conf(int dir_fd, const char *fname, char **l);
diff --git a/src/libnm-systemd-shared/src/basic/env-util.c b/src/libnm-systemd-shared/src/basic/env-util.c
index fa2753bc..54509a55 100644
--- a/src/libnm-systemd-shared/src/basic/env-util.c
+++ b/src/libnm-systemd-shared/src/basic/env-util.c
@@ -29,20 +29,21 @@
         "_"
 
 static bool env_name_is_valid_n(const char *e, size_t n) {
-        if (!e)
-                return false;
+
+        if (n == SIZE_MAX)
+                n = strlen_ptr(e);
 
         if (n <= 0)
                 return false;
 
+        assert(e);
+
         if (ascii_isdigit(e[0]))
                 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. */
+        /* 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;
 
@@ -246,9 +247,9 @@ static bool env_match(const char *t, const char *pattern) {
                 return true;
 
         if (!strchr(pattern, '=')) {
-                size_t l = strlen(pattern);
+                t = startswith(t, pattern);
 
-                return strneq(t, pattern, l) && t[l] == '=';
+                return t && *t == '=';
         }
 
         return false;
@@ -311,19 +312,17 @@ char **strv_env_delete(char **x, size_t n_lists, ...) {
         return TAKE_PTR(t);
 }
 
-char **strv_env_unset(char **l, const char *p) {
-        char **f, **t;
+char** strv_env_unset(char **l, const char *p) {
+        assert(p);
 
         if (!l)
                 return NULL;
 
-        assert(p);
-
         /* Drops every occurrence of the env var setting p in the
          * string list. Edits in-place. */
 
+        char **f, **t;
         for (f = t = l; *f; f++) {
-
                 if (env_match(*f, p)) {
                         free(*f);
                         continue;
@@ -336,14 +335,13 @@ char **strv_env_unset(char **l, const char *p) {
         return l;
 }
 
-char **strv_env_unset_many(char **l, ...) {
-        char **f, **t;
-
+char** strv_env_unset_many_internal(char **l, ...) {
         if (!l)
                 return NULL;
 
         /* Like strv_env_unset() but applies many at once. Edits in-place. */
 
+        char **f, **t;
         for (f = t = l; *f; f++) {
                 bool found = false;
                 const char *p;
@@ -351,12 +349,11 @@ char **strv_env_unset_many(char **l, ...) {
 
                 va_start(ap, l);
 
-                while ((p = va_arg(ap, const char*))) {
+                while ((p = va_arg(ap, const char*)))
                         if (env_match(*f, p)) {
                                 found = true;
                                 break;
                         }
-                }
 
                 va_end(ap);
 
@@ -460,6 +457,35 @@ int strv_env_assign(char ***l, const char *key, const char *value) {
         return strv_env_replace_consume(l, p);
 }
 
+int strv_env_assignf(char ***l, const char *key, const char *valuef, ...) {
+        int r;
+
+        assert(l);
+        assert(key);
+
+        if (!env_name_is_valid(key))
+                return -EINVAL;
+
+        if (!valuef) {
+                strv_env_unset(*l, key);
+                return 0;
+        }
+
+        _cleanup_free_ char *value = NULL;
+        va_list ap;
+        va_start(ap, valuef);
+        r = vasprintf(&value, valuef, ap);
+        va_end(ap);
+        if (r < 0)
+                return -ENOMEM;
+
+        char *p = strjoin(key, "=", value);
+        if (!p)
+                return -ENOMEM;
+
+        return strv_env_replace_consume(l, p);
+}
+
 int _strv_env_assign_many(char ***l, ...) {
         va_list ap;
         int r;
@@ -502,32 +528,31 @@ int _strv_env_assign_many(char ***l, ...) {
         return 0;
 }
 
-char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) {
+char* strv_env_get_n(char * const *l, const char *name, size_t k, ReplaceEnvFlags flags) {
         assert(name);
 
+        if (k == SIZE_MAX)
+                k = strlen(name);
         if (k <= 0)
                 return NULL;
 
         STRV_FOREACH_BACKWARDS(i, l)
-                if (strneq(*i, name, k) &&
-                    (*i)[k] == '=')
-                        return *i + k + 1;
+                if (strneq(*i, name, k) && (*i)[k] == '=')
+                        return (char*) *i + k + 1;
 
         if (flags & REPLACE_ENV_USE_ENVIRONMENT) {
                 const char *t;
 
+                /* Safety check that the name is not overly long, before we do a stack allocation */
+                if (k > (size_t) sysconf(_SC_ARG_MAX) - 2)
+                        return NULL;
+
                 t = strndupa_safe(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);
-}
 #endif /* NM_IGNORED */
 
 char *strv_env_pairs_get(char **l, const char *name) {
@@ -578,7 +603,61 @@ char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const cha
         return e;
 }
 
-char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
+static int strv_extend_with_length(char ***l, const char *s, size_t n) {
+        char *c;
+
+        c = strndup(s, n);
+        if (!c)
+                return -ENOMEM;
+
+        return strv_consume(l, c);
+}
+
+static int strv_env_get_n_validated(
+                char **env,
+                const char *name,
+                size_t l,
+                ReplaceEnvFlags flags,
+                char **ret,              /* points into the env block! do not free! */
+                char ***unset_variables, /* updated in place */
+                char ***bad_variables) { /* ditto */
+
+        char *e;
+        int r;
+
+        assert(l == 0 || name);
+        assert(ret);
+
+        if (env_name_is_valid_n(name, l)) {
+                e = strv_env_get_n(env, name, l, flags);
+                if (!e && unset_variables) {
+                        r = strv_extend_with_length(unset_variables, name, l);
+                        if (r < 0)
+                                return r;
+                }
+        } else {
+                e = NULL; /* Resolve invalid variable names the same way as unset ones */
+
+                if (bad_variables) {
+                        r = strv_extend_with_length(bad_variables, name, l);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        *ret = e;
+        return !!e;
+}
+
+int replace_env_full(
+                const char *format,
+                size_t n,
+                char **env,
+                ReplaceEnvFlags flags,
+                char **ret,
+                char ***ret_unset_variables,
+                char ***ret_bad_variables) {
+
         enum {
                 WORD,
                 CURLY,
@@ -589,15 +668,22 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
                 ALTERNATE_VALUE,
         } state = WORD;
 
+        _cleanup_strv_free_ char **unset_variables = NULL, **bad_variables = NULL;
         const char *e, *word = format, *test_value = NULL; /* test_value is initialized to appease gcc */
-        char *k;
         _cleanup_free_ char *s = NULL;
+        char ***pu, ***pb, *k;
         size_t i, len = 0; /* len is initialized to appease gcc */
-        int nest = 0;
+        int nest = 0, r;
 
         assert(format);
 
-        for (e = format, i = 0; *e && i < n; e ++, i ++)
+        if (n == SIZE_MAX)
+                n = strlen(format);
+
+        pu = ret_unset_variables ? &unset_variables : NULL;
+        pb = ret_bad_variables ? &bad_variables : NULL;
+
+        for (e = format, i = 0; *e && i < n; e++, i++)
                 switch (state) {
 
                 case WORD:
@@ -609,27 +695,28 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
                         if (*e == '{') {
                                 k = strnappend(s, word, e-word-1);
                                 if (!k)
-                                        return NULL;
+                                        return -ENOMEM;
 
                                 free_and_replace(s, k);
 
                                 word = e-1;
                                 state = VARIABLE;
                                 nest++;
+
                         } else if (*e == '$') {
                                 k = strnappend(s, word, e-word);
                                 if (!k)
-                                        return NULL;
+                                        return -ENOMEM;
 
                                 free_and_replace(s, k);
 
                                 word = e+1;
                                 state = WORD;
 
-                        } else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_BASH_ENV_NAME_CHARS, *e)) {
+                        } else if (FLAGS_SET(flags, REPLACE_ENV_ALLOW_BRACELESS) && strchr(VALID_BASH_ENV_NAME_CHARS, *e)) {
                                 k = strnappend(s, word, e-word-1);
                                 if (!k)
-                                        return NULL;
+                                        return -ENOMEM;
 
                                 free_and_replace(s, k);
 
@@ -642,12 +729,14 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
 
                 case VARIABLE:
                         if (*e == '}') {
-                                const char *t;
+                                char *t;
 
-                                t = strv_env_get_n(env, word+2, e-word-2, flags);
+                                r = strv_env_get_n_validated(env, word+2, e-word-2, flags, &t, pu, pb);
+                                if (r < 0)
+                                        return r;
 
                                 if (!strextend(&s, t))
-                                        return NULL;
+                                        return -ENOMEM;
 
                                 word = e+1;
                                 state = WORD;
@@ -689,18 +778,37 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
 
                         nest--;
                         if (nest == 0) {
-                                const char *t;
+                                _cleanup_strv_free_ char **u = NULL, **b = NULL;
                                 _cleanup_free_ char *v = NULL;
+                                char *t = NULL;
+
+                                r = strv_env_get_n_validated(env, word+2, len, flags, &t, pu, pb);
+                                if (r < 0)
+                                        return r;
 
-                                t = strv_env_get_n(env, word+2, len, flags);
+                                if (t && state == ALTERNATE_VALUE) {
+                                        r = replace_env_full(test_value, e-test_value, env, flags, &v, pu ? &u : NULL, pb ? &b : NULL);
+                                        if (r < 0)
+                                                return r;
 
-                                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);
+                                        t = v;
+                                } else if (!t && state == DEFAULT_VALUE) {
+                                        r = replace_env_full(test_value, e-test_value, env, flags, &v, pu ? &u : NULL, pb ? &b : NULL);
+                                        if (r < 0)
+                                                return r;
+
+                                        t = v;
+                                }
+
+                                r = strv_extend_strv(&unset_variables, u, /* filter_duplicates= */ true);
+                                if (r < 0)
+                                        return r;
+                                r = strv_extend_strv(&bad_variables, b, /* filter_duplicates= */ true);
+                                if (r < 0)
+                                        return r;
 
                                 if (!strextend(&s, t))
-                                        return NULL;
+                                        return -ENOMEM;
 
                                 word = e+1;
                                 state = WORD;
@@ -711,12 +819,14 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
                         assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
 
                         if (!strchr(VALID_BASH_ENV_NAME_CHARS, *e)) {
-                                const char *t;
+                                char *t = NULL;
 
-                                t = strv_env_get_n(env, word+1, e-word-1, flags);
+                                r = strv_env_get_n_validated(env, word+1, e-word-1, flags, &t, &unset_variables, &bad_variables);
+                                if (r < 0)
+                                        return r;
 
                                 if (!strextend(&s, t))
-                                        return NULL;
+                                        return -ENOMEM;
 
                                 word = e--;
                                 i--;
@@ -726,58 +836,83 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
                 }
 
         if (state == VARIABLE_RAW) {
-                const char *t;
+                char *t;
 
                 assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
 
-                t = strv_env_get_n(env, word+1, e-word-1, flags);
-                return strjoin(s, t);
-        } else
-                return strnappend(s, word, e-word);
+                r = strv_env_get_n_validated(env, word+1, e-word-1, flags, &t, &unset_variables, &bad_variables);
+                if (r < 0)
+                        return r;
+
+                if (!strextend(&s, t))
+                        return -ENOMEM;
+
+        } else if (!strextendn(&s, word, e-word))
+                return -ENOMEM;
+
+        if (ret_unset_variables)
+                *ret_unset_variables = TAKE_PTR(unset_variables);
+        if (ret_bad_variables)
+                *ret_bad_variables = TAKE_PTR(bad_variables);
+
+        if (ret)
+                *ret = TAKE_PTR(s);
+
+        return 0;
 }
 
-char **replace_env_argv(char **argv, char **env) {
-        _cleanup_strv_free_ char **ret = NULL;
+int replace_env_argv(
+                char **argv,
+                char **env,
+                char ***ret,
+                char ***ret_unset_variables,
+                char ***ret_bad_variables) {
+
+        _cleanup_strv_free_ char **n = NULL, **unset_variables = NULL, **bad_variables = NULL;
         size_t k = 0, l = 0;
+        int r;
 
         l = strv_length(argv);
 
-        ret = new(char*, l+1);
-        if (!ret)
-                return NULL;
+        n = new(char*, l+1);
+        if (!n)
+                return -ENOMEM;
 
         STRV_FOREACH(i, argv) {
+                const char *word = *i;
 
                 /* If $FOO appears as single word, replace it by the split up variable */
-                if ((*i)[0] == '$' && !IN_SET((*i)[1], '{', '$')) {
-                        char *e;
-                        char **w;
+                if (word[0] == '$' && !IN_SET(word[1], '{', '$')) {
                         _cleanup_strv_free_ char **m = NULL;
+                        const char *name = word + 1;
+                        char *e, **w;
                         size_t q;
 
-                        e = strv_env_get(env, *i+1);
-                        if (e) {
-                                int r;
-
-                                r = strv_split_full(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_UNQUOTE);
-                                if (r < 0) {
-                                        ret[k] = NULL;
-                                        return NULL;
-                                }
-                        }
+                        if (env_name_is_valid(name)) {
+                                e = strv_env_get(env, name);
+                                if (e)
+                                        r = strv_split_full(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_UNQUOTE);
+                                else if (ret_unset_variables)
+                                        r = strv_extend(&unset_variables, name);
+                                else
+                                        r = 0;
+                        } else if (ret_bad_variables)
+                                r = strv_extend(&bad_variables, name);
+                        else
+                                r = 0;
+                        if (r < 0)
+                                return r;
 
                         q = strv_length(m);
                         l = l + q - 1;
 
-                        w = reallocarray(ret, l + 1, sizeof(char *));
-                        if (!w) {
-                                ret[k] = NULL;
-                                return NULL;
-                        }
+                        w = reallocarray(n, l + 1, sizeof(char*));
+                        if (!w)
+                                return -ENOMEM;
 
-                        ret = w;
+                        n = w;
                         if (m) {
-                                memcpy(ret + k, m, q * sizeof(char*));
+                                memcpy(n + k, m, (q + 1) * sizeof(char*));
                                 m = mfree(m);
                         }
 
@@ -785,15 +920,41 @@ char **replace_env_argv(char **argv, char **env) {
                         continue;
                 }
 
+                _cleanup_strv_free_ char **u = NULL, **b = NULL;
+
                 /* 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])
-                        return NULL;
-                k++;
+                r = replace_env_full(
+                                word,
+                                /* length= */ SIZE_MAX,
+                                env,
+                                /* flags= */ 0,
+                                n + k,
+                                ret_unset_variables ? &u : NULL,
+                                ret_bad_variables ? &b : NULL);
+                if (r < 0)
+                        return r;
+                n[++k] = NULL;
+
+                r = strv_extend_strv(&unset_variables, u, /* filter_duplicates= */ true);
+                if (r < 0)
+                        return r;
+
+                r = strv_extend_strv(&bad_variables, b, /*filter_duplicates= */ true);
+                if (r < 0)
+                        return r;
         }
 
-        ret[k] = NULL;
-        return TAKE_PTR(ret);
+        if (ret_unset_variables) {
+                strv_uniq(strv_sort(unset_variables));
+                *ret_unset_variables = TAKE_PTR(unset_variables);
+        }
+        if (ret_bad_variables) {
+                strv_uniq(strv_sort(bad_variables));
+                *ret_bad_variables = TAKE_PTR(bad_variables);
+        }
+
+        *ret = TAKE_PTR(n);
+        return 0;
 }
 #endif /* NM_IGNORED */
 
@@ -807,7 +968,7 @@ int getenv_bool(const char *p) {
         return parse_boolean(e);
 }
 
-int getenv_bool_secure(const char *p) {
+int secure_getenv_bool(const char *p) {
         const char *e;
 
         e = secure_getenv(p);
@@ -817,8 +978,7 @@ int getenv_bool_secure(const char *p) {
         return parse_boolean(e);
 }
 
-#if 0 /* NM_IGNORED */
-int getenv_uint64_secure(const char *p, uint64_t *ret) {
+int secure_getenv_uint64(const char *p, uint64_t *ret) {
         const char *e;
 
         assert(p);
@@ -852,9 +1012,10 @@ int putenv_dup(const char *assignment, bool override) {
         return RET_NERRNO(setenv(n, e + 1, override));
 }
 
+#if 0 /* NM_IGNORED */
 int setenv_systemd_exec_pid(bool update_only) {
-        char str[DECIMAL_STR_MAX(pid_t)];
         const char *e;
+        int r;
 
         /* Update $SYSTEMD_EXEC_PID=pid except when '*' is set for the variable. */
 
@@ -865,14 +1026,24 @@ int setenv_systemd_exec_pid(bool update_only) {
         if (streq_ptr(e, "*"))
                 return 0;
 
-        xsprintf(str, PID_FMT, getpid_cached());
-
-        if (setenv("SYSTEMD_EXEC_PID", str, 1) < 0)
-                return -errno;
+        r = setenvf("SYSTEMD_EXEC_PID", /* overwrite= */ 1, PID_FMT, getpid_cached());
+        if (r < 0)
+                return r;
 
         return 1;
 }
 
+int setenv_systemd_log_level(void) {
+        _cleanup_free_ char *val = NULL;
+        int r;
+
+        r = log_level_to_string_alloc(log_get_max_level(), &val);
+        if (r < 0)
+                return r;
+
+        return RET_NERRNO(setenv("SYSTEMD_LOG_LEVEL", val, /* overwrite= */ true));
+}
+
 int getenv_path_list(const char *name, char ***ret_paths) {
         _cleanup_strv_free_ char **l = NULL;
         const char *e;
@@ -944,4 +1115,45 @@ int getenv_steal_erase(const char *name, char **ret) {
 
         return 1;
 }
+
+int set_full_environment(char **env) {
+        int r;
+
+        clearenv();
+
+        STRV_FOREACH(e, env) {
+                _cleanup_free_ char *k = NULL, *v = NULL;
+
+                r = split_pair(*e, "=", &k, &v);
+                if (r < 0)
+                        return r;
+
+                if (setenv(k, v, /* overwrite= */ true) < 0)
+                        return -errno;
+        }
+
+        return 0;
+}
+
+int setenvf(const char *name, bool overwrite, const char *valuef, ...) {
+        _cleanup_free_ char *value = NULL;
+        va_list ap;
+        int r;
+
+        assert(name);
+
+        if (!valuef)
+                return RET_NERRNO(unsetenv(name));
+
+        va_start(ap, valuef);
+        DISABLE_WARNING_FORMAT_NONLITERAL;
+        r = vasprintf(&value, valuef, ap);
+        REENABLE_WARNING;
+        va_end(ap);
+
+        if (r < 0)
+                return -ENOMEM;
+
+        return RET_NERRNO(setenv(name, value, overwrite));
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/env-util.h b/src/libnm-systemd-shared/src/basic/env-util.h
index b0ff5a11..6610ca8c 100644
--- a/src/libnm-systemd-shared/src/basic/env-util.h
+++ b/src/libnm-systemd-shared/src/basic/env-util.h
@@ -19,19 +19,19 @@ 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 {
+typedef enum ReplaceEnvFlags {
         REPLACE_ENV_USE_ENVIRONMENT = 1 << 0,
         REPLACE_ENV_ALLOW_BRACELESS = 1 << 1,
         REPLACE_ENV_ALLOW_EXTENDED  = 1 << 2,
-};
+} ReplaceEnvFlags;
 
-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);
+int replace_env_full(const char *format, size_t n, char **env, ReplaceEnvFlags flags, char **ret, char ***ret_unset_variables, char ***ret_bad_variables);
+static inline int replace_env(const char *format, char **env, ReplaceEnvFlags flags, char **ret) {
+        return replace_env_full(format, SIZE_MAX, env, flags, ret, NULL, NULL);
 }
 
+int replace_env_argv(char **argv, char **env, char ***ret, char ***ret_unset_variables, char ***ret_bad_variables);
+
 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);
@@ -43,23 +43,28 @@ char** _strv_env_merge(char **first, ...);
 #define strv_env_merge(first, ...) _strv_env_merge(first, __VA_ARGS__, POINTER_MAX)
 char **strv_env_delete(char **x, size_t n_lists, ...); /* New copy */
 
-char **strv_env_unset(char **l, const char *p); /* In place ... */
-char **strv_env_unset_many(char **l, ...) _sentinel_;
+char** strv_env_unset(char **l, const char *p); /* In place ... */
+char** strv_env_unset_many_internal(char **l, ...) _sentinel_;
+#define strv_env_unset_many(l, ...) strv_env_unset_many_internal(l, __VA_ARGS__, NULL)
 int strv_env_replace_consume(char ***l, char *p); /* In place ... */
 int strv_env_replace_strdup(char ***l, const char *assignment);
 int strv_env_replace_strdup_passthrough(char ***l, const char *assignment);
 int strv_env_assign(char ***l, const char *key, const char *value);
+int strv_env_assignf(char ***l, const char *key, const char *valuef, ...) _printf_(3, 4);
 int _strv_env_assign_many(char ***l, ...) _sentinel_;
 #define strv_env_assign_many(l, ...) _strv_env_assign_many(l, __VA_ARGS__, NULL)
 
-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_;
+char* strv_env_get_n(char * const *l, const char *name, size_t k, ReplaceEnvFlags flags);
+static inline char* strv_env_get(char * const *x, const char *n) {
+        return strv_env_get_n(x, n, SIZE_MAX, 0);
+}
+
 char *strv_env_pairs_get(char **l, const char *name) _pure_;
 
 int getenv_bool(const char *p);
-int getenv_bool_secure(const char *p);
+int secure_getenv_bool(const char *p);
 
-int getenv_uint64_secure(const char *p, uint64_t *ret);
+int secure_getenv_uint64(const char *p, uint64_t *ret);
 
 /* Like setenv, but calls unsetenv if value == NULL. */
 int set_unset_env(const char *name, const char *value, bool overwrite);
@@ -68,9 +73,14 @@ int set_unset_env(const char *name, const char *value, bool overwrite);
 int putenv_dup(const char *assignment, bool override);
 
 int setenv_systemd_exec_pid(bool update_only);
+int setenv_systemd_log_level(void);
 
 /* Parses and does sanity checks on an environment variable containing
  * PATH-like colon-separated absolute paths */
 int getenv_path_list(const char *name, char ***ret_paths);
 
 int getenv_steal_erase(const char *name, char **ret);
+
+int set_full_environment(char **env);
+
+int setenvf(const char *name, bool overwrite, const char *valuef, ...) _printf_(3,4);
diff --git a/src/libnm-systemd-shared/src/basic/errno-util.h b/src/libnm-systemd-shared/src/basic/errno-util.h
index 091f99c5..27804e63 100644
--- a/src/libnm-systemd-shared/src/basic/errno-util.h
+++ b/src/libnm-systemd-shared/src/basic/errno-util.h
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <inttypes.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -73,6 +74,16 @@ static inline int RET_NERRNO(int ret) {
         return ret;
 }
 
+/* Collect possible errors in <acc>, so that the first error can be returned.
+ * Returns (possibly updated) <acc>. */
+#define RET_GATHER(acc, err)                    \
+        ({                                      \
+                int *__a = &(acc), __e = (err); \
+                if (*__a >= 0 && __e < 0)       \
+                        *__a = __e;             \
+                *__a;                           \
+        })
+
 static inline int errno_or_else(int fallback) {
         /* To be used when invoking library calls where errno handling is not defined clearly: we return
          * errno if it is set, and the specified error otherwise. The idea is that the caller initializes
@@ -84,12 +95,23 @@ static inline int errno_or_else(int fallback) {
         return -abs(fallback);
 }
 
+/* abs(3) says: Trying to take the absolute value of the most negative integer is not defined. */
+#define _DEFINE_ABS_WRAPPER(name)                         \
+        static inline bool ERRNO_IS_##name(intmax_t r) {  \
+                if (r == INTMAX_MIN)                      \
+                        return false;                     \
+                return ERRNO_IS_NEG_##name(-imaxabs(r));  \
+        }
+
+assert_cc(INT_MAX <= INTMAX_MAX);
+
 /* For send()/recv() or read()/write(). */
-static inline bool ERRNO_IS_TRANSIENT(int r) {
-        return IN_SET(abs(r),
-                      EAGAIN,
-                      EINTR);
+static inline bool ERRNO_IS_NEG_TRANSIENT(intmax_t r) {
+        return IN_SET(r,
+                      -EAGAIN,
+                      -EINTR);
 }
+_DEFINE_ABS_WRAPPER(TRANSIENT);
 
 /* Hint #1: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5.
  *
@@ -98,79 +120,87 @@ static inline bool ERRNO_IS_TRANSIENT(int r) {
  *
  * Hint #3: When asynchronous connect() on TCP fails because the host never acknowledges a single packet,
  *          kernel tells us that with ETIMEDOUT, see tcp(7). */
-static inline bool ERRNO_IS_DISCONNECT(int r) {
-        return IN_SET(abs(r),
-                      ECONNABORTED,
-                      ECONNREFUSED,
-                      ECONNRESET,
-                      EHOSTDOWN,
-                      EHOSTUNREACH,
-                      ENETDOWN,
-                      ENETRESET,
-                      ENETUNREACH,
-                      ENONET,
-                      ENOPROTOOPT,
-                      ENOTCONN,
-                      EPIPE,
-                      EPROTO,
-                      ESHUTDOWN,
-                      ETIMEDOUT);
+static inline bool ERRNO_IS_NEG_DISCONNECT(intmax_t r) {
+        return IN_SET(r,
+                      -ECONNABORTED,
+                      -ECONNREFUSED,
+                      -ECONNRESET,
+                      -EHOSTDOWN,
+                      -EHOSTUNREACH,
+                      -ENETDOWN,
+                      -ENETRESET,
+                      -ENETUNREACH,
+                      -ENONET,
+                      -ENOPROTOOPT,
+                      -ENOTCONN,
+                      -EPIPE,
+                      -EPROTO,
+                      -ESHUTDOWN,
+                      -ETIMEDOUT);
 }
+_DEFINE_ABS_WRAPPER(DISCONNECT);
 
 /* Transient errors we might get on accept() that we should ignore. As per error handling comment in
  * the accept(2) man page. */
-static inline bool ERRNO_IS_ACCEPT_AGAIN(int r) {
-        return ERRNO_IS_DISCONNECT(r) ||
-                ERRNO_IS_TRANSIENT(r) ||
-                abs(r) == EOPNOTSUPP;
+static inline bool ERRNO_IS_NEG_ACCEPT_AGAIN(intmax_t r) {
+        return ERRNO_IS_NEG_DISCONNECT(r) ||
+                ERRNO_IS_NEG_TRANSIENT(r) ||
+                r == -EOPNOTSUPP;
 }
+_DEFINE_ABS_WRAPPER(ACCEPT_AGAIN);
 
 /* Resource exhaustion, could be our fault or general system trouble */
-static inline bool ERRNO_IS_RESOURCE(int r) {
-        return IN_SET(abs(r),
-                      EMFILE,
-                      ENFILE,
-                      ENOMEM);
+static inline bool ERRNO_IS_NEG_RESOURCE(intmax_t r) {
+        return IN_SET(r,
+                      -EMFILE,
+                      -ENFILE,
+                      -ENOMEM);
 }
+_DEFINE_ABS_WRAPPER(RESOURCE);
 
 /* Seven different errors for "operation/system call/ioctl/socket feature not supported" */
-static inline bool ERRNO_IS_NOT_SUPPORTED(int r) {
-        return IN_SET(abs(r),
-                      EOPNOTSUPP,
-                      ENOTTY,
-                      ENOSYS,
-                      EAFNOSUPPORT,
-                      EPFNOSUPPORT,
-                      EPROTONOSUPPORT,
-                      ESOCKTNOSUPPORT);
+static inline bool ERRNO_IS_NEG_NOT_SUPPORTED(intmax_t r) {
+        return IN_SET(r,
+                      -EOPNOTSUPP,
+                      -ENOTTY,
+                      -ENOSYS,
+                      -EAFNOSUPPORT,
+                      -EPFNOSUPPORT,
+                      -EPROTONOSUPPORT,
+                      -ESOCKTNOSUPPORT);
 }
+_DEFINE_ABS_WRAPPER(NOT_SUPPORTED);
 
 /* Two different errors for access problems */
-static inline bool ERRNO_IS_PRIVILEGE(int r) {
-        return IN_SET(abs(r),
-                      EACCES,
-                      EPERM);
+static inline bool ERRNO_IS_NEG_PRIVILEGE(intmax_t r) {
+        return IN_SET(r,
+                      -EACCES,
+                      -EPERM);
 }
+_DEFINE_ABS_WRAPPER(PRIVILEGE);
 
 /* Three different errors for "not enough disk space" */
-static inline bool ERRNO_IS_DISK_SPACE(int r) {
-        return IN_SET(abs(r),
-                      ENOSPC,
-                      EDQUOT,
-                      EFBIG);
+static inline bool ERRNO_IS_NEG_DISK_SPACE(intmax_t r) {
+        return IN_SET(r,
+                      -ENOSPC,
+                      -EDQUOT,
+                      -EFBIG);
 }
+_DEFINE_ABS_WRAPPER(DISK_SPACE);
 
 /* Three different errors for "this device does not quite exist" */
-static inline bool ERRNO_IS_DEVICE_ABSENT(int r) {
-        return IN_SET(abs(r),
-                      ENODEV,
-                      ENXIO,
-                      ENOENT);
+static inline bool ERRNO_IS_NEG_DEVICE_ABSENT(intmax_t r) {
+        return IN_SET(r,
+                      -ENODEV,
+                      -ENXIO,
+                      -ENOENT);
 }
+_DEFINE_ABS_WRAPPER(DEVICE_ABSENT);
 
 /* Quite often we want to handle cases where the backing FS doesn't support extended attributes at all and
  * where it simply doesn't have the requested xattr the same way */
-static inline bool ERRNO_IS_XATTR_ABSENT(int r) {
-        return abs(r) == ENODATA ||
-                ERRNO_IS_NOT_SUPPORTED(r);
+static inline bool ERRNO_IS_NEG_XATTR_ABSENT(intmax_t r) {
+        return r == -ENODATA ||
+                ERRNO_IS_NEG_NOT_SUPPORTED(r);
 }
+_DEFINE_ABS_WRAPPER(XATTR_ABSENT);
diff --git a/src/libnm-systemd-shared/src/basic/escape.c b/src/libnm-systemd-shared/src/basic/escape.c
index 6d2c1d4d..29f8b9cd 100644
--- a/src/libnm-systemd-shared/src/basic/escape.c
+++ b/src/libnm-systemd-shared/src/basic/escape.c
@@ -184,7 +184,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit,
         }
 
         case 'u': {
-                /* C++11 style 16bit unicode */
+                /* C++11 style 16-bit unicode */
 
                 int a[4];
                 size_t i;
@@ -211,7 +211,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit,
         }
 
         case 'U': {
-                /* C++11 style 32bit unicode */
+                /* C++11 style 32-bit unicode */
 
                 int a[8];
                 size_t i;
@@ -474,6 +474,33 @@ char* octescape(const char *s, size_t len) {
         return buf;
 }
 
+char* decescape(const char *s, const char *bad, size_t len) {
+        char *buf, *t;
+
+        /* Escapes all chars in bad, in addition to \ and " chars, in \nnn decimal style escaping. */
+
+        assert(s || len == 0);
+
+        t = buf = new(char, len * 4 + 1);
+        if (!buf)
+                return NULL;
+
+        for (size_t i = 0; i < len; i++) {
+                uint8_t u = (uint8_t) s[i];
+
+                if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || strchr(bad, u)) {
+                        *(t++) = '\\';
+                        *(t++) = '0' + (u / 100);
+                        *(t++) = '0' + ((u / 10) % 10);
+                        *(t++) = '0' + (u % 10);
+                } else
+                        *(t++) = u;
+        }
+
+        *t = 0;
+        return buf;
+}
+
 static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad) {
         assert(bad);
         assert(t);
diff --git a/src/libnm-systemd-shared/src/basic/escape.h b/src/libnm-systemd-shared/src/basic/escape.h
index 318da6f2..65caf0db 100644
--- a/src/libnm-systemd-shared/src/basic/escape.h
+++ b/src/libnm-systemd-shared/src/basic/escape.h
@@ -65,6 +65,7 @@ static inline char* xescape(const char *s, const char *bad) {
         return xescape_full(s, bad, SIZE_MAX, 0);
 }
 char* octescape(const char *s, size_t len);
+char* decescape(const char *s, const char *bad, size_t len);
 char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags);
 
 char* shell_escape(const char *s, const char *bad);
diff --git a/src/libnm-systemd-shared/src/basic/ether-addr-util.c b/src/libnm-systemd-shared/src/basic/ether-addr-util.c
index 7984ddf4..1eb2e700 100644
--- a/src/libnm-systemd-shared/src/basic/ether-addr-util.c
+++ b/src/libnm-systemd-shared/src/basic/ether-addr-util.c
@@ -61,8 +61,8 @@ void hw_addr_hash_func(const struct hw_addr_data *p, struct siphash *state) {
         assert(p);
         assert(state);
 
-        siphash24_compress(&p->length, sizeof(p->length), state);
-        siphash24_compress(p->bytes, p->length, state);
+        siphash24_compress_typesafe(p->length, state);
+        siphash24_compress_safe(p->bytes, p->length, state);
 }
 
 DEFINE_HASH_OPS(hw_addr_hash_ops, struct hw_addr_data, hw_addr_hash_func, hw_addr_compare);
@@ -108,7 +108,7 @@ int ether_addr_compare(const struct ether_addr *a, const struct ether_addr *b) {
 }
 
 static void ether_addr_hash_func(const struct ether_addr *p, struct siphash *state) {
-        siphash24_compress(p, sizeof(struct ether_addr), state);
+        siphash24_compress_typesafe(*p, state);
 }
 
 DEFINE_HASH_OPS(ether_addr_hash_ops, struct ether_addr, ether_addr_hash_func, ether_addr_compare);
@@ -272,3 +272,11 @@ int parse_ether_addr(const char *s, struct ether_addr *ret) {
         *ret = a.ether;
         return 0;
 }
+
+void ether_addr_mark_random(struct ether_addr *addr) {
+        assert(addr);
+
+        /* see eth_random_addr in the kernel */
+        addr->ether_addr_octet[0] &= 0xfe;        /* clear multicast bit */
+        addr->ether_addr_octet[0] |= 0x02;        /* set local assignment bit (IEEE802) */
+}
diff --git a/src/libnm-systemd-shared/src/basic/ether-addr-util.h b/src/libnm-systemd-shared/src/basic/ether-addr-util.h
index 83ed77d6..187e4ef5 100644
--- a/src/libnm-systemd-shared/src/basic/ether-addr-util.h
+++ b/src/libnm-systemd-shared/src/basic/ether-addr-util.h
@@ -113,3 +113,5 @@ static inline bool ether_addr_is_global(const struct ether_addr *addr) {
 
 extern const struct hash_ops ether_addr_hash_ops;
 extern const struct hash_ops ether_addr_hash_ops_free;
+
+void ether_addr_mark_random(struct ether_addr *addr);
diff --git a/src/libnm-systemd-shared/src/basic/extract-word.c b/src/libnm-systemd-shared/src/basic/extract-word.c
index 6781fb55..bf7bc44f 100644
--- a/src/libnm-systemd-shared/src/basic/extract-word.c
+++ b/src/libnm-systemd-shared/src/basic/extract-word.c
@@ -247,56 +247,43 @@ int extract_first_word_and_warn(
  * 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, ...) {
+int extract_many_words_internal(const char **p, const char *separators, unsigned flags, ...) {
         va_list ap;
-        char **l;
-        int n = 0, i, c, r;
+        unsigned n = 0;
+        int r;
 
-        /* Parses a number of words from a string, stripping any
-         * quotes if necessary. */
+        /* 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;
+        while (va_arg(ap, char**))
                 n++;
-        }
         va_end(ap);
 
-        if (n <= 0)
+        if (n == 0)
                 return 0;
 
         /* Read all words into a temporary array */
-        l = newa0(char*, n);
-        for (c = 0; c < n; c++) {
+        char **l = newa0(char*, n);
+        unsigned c;
 
+        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]);
-
+                        free_many_charp(l, c);
                         return r;
                 }
-
                 if (r == 0)
                         break;
         }
 
-        /* If we managed to parse all words, return them in the passed
-         * in parameters */
+        /* 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];
+        FOREACH_ARRAY(i, l, n) {
+                char **v = ASSERT_PTR(va_arg(ap, char**));
+                *v = *i;
         }
         va_end(ap);
 
diff --git a/src/libnm-systemd-shared/src/basic/extract-word.h b/src/libnm-systemd-shared/src/basic/extract-word.h
index c82ad761..da4f6ae6 100644
--- a/src/libnm-systemd-shared/src/basic/extract-word.h
+++ b/src/libnm-systemd-shared/src/basic/extract-word.h
@@ -19,4 +19,7 @@ typedef enum 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_;
+
+int extract_many_words_internal(const char **p, const char *separators, unsigned flags, ...) _sentinel_;
+#define extract_many_words(p, separators, flags, ...) \
+        extract_many_words_internal(p, separators, flags, ##__VA_ARGS__, NULL)
diff --git a/src/libnm-systemd-shared/src/basic/fd-util.c b/src/libnm-systemd-shared/src/basic/fd-util.c
index a0e2f4eb..584d02f0 100644
--- a/src/libnm-systemd-shared/src/basic/fd-util.c
+++ b/src/libnm-systemd-shared/src/basic/fd-util.c
@@ -94,11 +94,25 @@ void safe_close_pair(int p[static 2]) {
         p[1] = safe_close(p[1]);
 }
 
-void close_many(const int fds[], size_t n_fd) {
-        assert(fds || n_fd <= 0);
+void close_many(const int fds[], size_t n_fds) {
+        assert(fds || n_fds == 0);
 
-        for (size_t i = 0; i < n_fd; i++)
-                safe_close(fds[i]);
+        FOREACH_ARRAY(fd, fds, n_fds)
+                safe_close(*fd);
+}
+
+void close_many_unset(int fds[], size_t n_fds) {
+        assert(fds || n_fds == 0);
+
+        FOREACH_ARRAY(fd, fds, n_fds)
+                *fd = safe_close(*fd);
+}
+
+void close_many_and_free(int *fds, size_t n_fds) {
+        assert(fds || n_fds == 0);
+
+        close_many(fds, n_fds);
+        free(fds);
 }
 
 int fclose_nointr(FILE *f) {
@@ -158,6 +172,19 @@ int fd_nonblock(int fd, bool nonblock) {
         return RET_NERRNO(fcntl(fd, F_SETFL, nflags));
 }
 
+int stdio_disable_nonblock(void) {
+        int ret = 0;
+
+        /* stdin/stdout/stderr really should have O_NONBLOCK, which would confuse apps if left on, as
+         * write()s might unexpectedly fail with EAGAIN. */
+
+        RET_GATHER(ret, fd_nonblock(STDIN_FILENO, false));
+        RET_GATHER(ret, fd_nonblock(STDOUT_FILENO, false));
+        RET_GATHER(ret, fd_nonblock(STDERR_FILENO, false));
+
+        return ret;
+}
+
 int fd_cloexec(int fd, bool cloexec) {
         int flags, nflags;
 
@@ -176,32 +203,32 @@ int fd_cloexec(int fd, bool cloexec) {
 
 #if 0 /* NM_IGNORED */
 int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec) {
-        int ret = 0, r;
+        int r = 0;
 
-        assert(n_fds == 0 || fds);
+        assert(fds || n_fds == 0);
 
-        for (size_t i = 0; i < n_fds; i++) {
-                if (fds[i] < 0) /* Skip gracefully over already invalidated fds */
+        FOREACH_ARRAY(fd, fds, n_fds) {
+                if (*fd < 0) /* Skip gracefully over already invalidated fds */
                         continue;
 
-                r = fd_cloexec(fds[i], cloexec);
-                if (r < 0 && ret >= 0) /* Continue going, but return first error */
-                        ret = r;
-                else
-                        ret = 1; /* report if we did anything */
+                RET_GATHER(r, fd_cloexec(*fd, cloexec));
+
+                if (r >= 0)
+                        r = 1; /* report if we did anything */
         }
 
-        return ret;
+        return r;
 }
 
-_pure_ static bool fd_in_set(int fd, const int fdset[], size_t n_fdset) {
-        assert(n_fdset == 0 || fdset);
+static bool fd_in_set(int fd, const int fds[], size_t n_fds) {
+        assert(fd >= 0);
+        assert(fds || n_fds == 0);
 
-        for (size_t i = 0; i < n_fdset; i++) {
-                if (fdset[i] < 0)
+        FOREACH_ARRAY(i, fds, n_fds) {
+                if (*i < 0)
                         continue;
 
-                if (fdset[i] == fd)
+                if (*i == fd)
                         return true;
         }
 
@@ -232,7 +259,7 @@ int get_max_fd(void) {
 static int close_all_fds_frugal(const int except[], size_t n_except) {
         int max_fd, r = 0;
 
-        assert(n_except == 0 || except);
+        assert(except || n_except == 0);
 
         /* This is the inner fallback core of close_all_fds(). This never calls malloc() or opendir() or so
          * and hence is safe to be called in signal handler context. Most users should call close_all_fds(),
@@ -247,8 +274,7 @@ static int close_all_fds_frugal(const int except[], size_t n_except) {
          * spin the CPU for a long time. */
         if (max_fd > MAX_FD_LOOP_LIMIT)
                 return log_debug_errno(SYNTHETIC_ERRNO(EPERM),
-                                       "Refusing to loop over %d potential fds.",
-                                       max_fd);
+                                       "Refusing to loop over %d potential fds.", max_fd);
 
         for (int fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -EBADF) {
                 int q;
@@ -257,8 +283,8 @@ static int close_all_fds_frugal(const int except[], size_t n_except) {
                         continue;
 
                 q = close_nointr(fd);
-                if (q < 0 && q != -EBADF && r >= 0)
-                        r = q;
+                if (q != -EBADF)
+                        RET_GATHER(r, q);
         }
 
         return r;
@@ -285,7 +311,7 @@ static int close_all_fds_special_case(const int except[], size_t n_except) {
         case 0:
                 /* Close everything. Yay! */
 
-                if (close_range(3, -1, 0) >= 0)
+                if (close_range(3, INT_MAX, 0) >= 0)
                         return 1;
 
                 if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno)) {
@@ -396,7 +422,7 @@ int close_all_fds(const int except[], size_t n_except) {
                                 if (sorted[n_sorted-1] >= INT_MAX) /* Dont let the addition below overflow */
                                         return 0;
 
-                                if (close_range(sorted[n_sorted-1] + 1, -1, 0) >= 0)
+                                if (close_range(sorted[n_sorted-1] + 1, INT_MAX, 0) >= 0)
                                         return 0;
 
                                 if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
@@ -441,6 +467,53 @@ int close_all_fds(const int except[], size_t n_except) {
         return r;
 }
 
+int pack_fds(int fds[], size_t n_fds) {
+        if (n_fds <= 0)
+                return 0;
+
+        /* Shifts around the fds in the provided array such that they
+         * all end up packed next to each-other, in order, starting
+         * from SD_LISTEN_FDS_START. This must be called after close_all_fds();
+         * it is likely to freeze up otherwise. You should probably use safe_fork_full
+         * with FORK_CLOSE_ALL_FDS|FORK_PACK_FDS set, to ensure that this is done correctly.
+         * The fds array is modified in place with the new FD numbers. */
+
+        assert(fds);
+
+        for (int start = 0;;) {
+                int restart_from = -1;
+
+                for (int i = start; i < (int) n_fds; i++) {
+                        int nfd;
+
+                        /* Already at right index? */
+                        if (fds[i] == i + 3)
+                                continue;
+
+                        nfd = fcntl(fds[i], F_DUPFD, i + 3);
+                        if (nfd < 0)
+                                return -errno;
+
+                        safe_close(fds[i]);
+                        fds[i] = nfd;
+
+                        /* Hmm, the fd we wanted isn't free? Then
+                         * let's remember that and try again from here */
+                        if (nfd != i + 3 && restart_from < 0)
+                                restart_from = i;
+                }
+
+                if (restart_from < 0)
+                        break;
+
+                start = restart_from;
+        }
+
+        assert(fds[0] == 3);
+
+        return 0;
+}
+
 int same_fd(int a, int b) {
         struct stat sta, stb;
         pid_t pid;
@@ -589,7 +662,7 @@ int move_fd(int from, int to, int cloexec) {
                 if (fl < 0)
                         return -errno;
 
-                cloexec = !!(fl & FD_CLOEXEC);
+                cloexec = FLAGS_SET(fl, FD_CLOEXEC);
         }
 
         r = dup3(from, to, cloexec ? O_CLOEXEC : 0);
@@ -647,7 +720,7 @@ int rearrange_stdio(int original_input_fd, int original_output_fd, int original_
                       original_output_fd,
                       original_error_fd },
             null_fd = -EBADF,                        /* If we open /dev/null, we store the fd to it here */
-            copy_fd[3] = { -EBADF, -EBADF, -EBADF }, /* This contains all fds we duplicate here
+            copy_fd[3] = EBADF_TRIPLET,              /* This contains all fds we duplicate here
                                                       * temporarily, and hence need to close at the end. */
             r;
         bool null_readable, null_writable;
@@ -745,8 +818,7 @@ finish:
                 safe_close_above_stdio(original_error_fd);
 
         /* Close the copies we moved > 2 */
-        for (int i = 0; i < 3; i++)
-                safe_close(copy_fd[i]);
+        close_many(copy_fd, 3);
 
         /* Close our null fd, if it's > 2 */
         safe_close_above_stdio(null_fd);
@@ -756,9 +828,10 @@ finish:
 #endif /* NM_IGNORED */
 
 int fd_reopen(int fd, int flags) {
-        int new_fd, r;
+        int r;
 
         assert(fd >= 0 || fd == AT_FDCWD);
+        assert(!FLAGS_SET(flags, O_CREAT));
 
         /* 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.
@@ -782,19 +855,12 @@ int fd_reopen(int fd, int flags) {
                  * the same way as the non-O_DIRECTORY case. */
                 return -ELOOP;
 
-        if (FLAGS_SET(flags, O_DIRECTORY) || fd == AT_FDCWD) {
+        if (FLAGS_SET(flags, O_DIRECTORY) || fd == AT_FDCWD)
                 /* If we shall reopen the fd as directory we can just go via "." and thus bypass the whole
                  * magic /proc/ directory, and make ourselves independent of that being mounted. */
-                new_fd = openat(fd, ".", flags | O_DIRECTORY);
-                if (new_fd < 0)
-                        return -errno;
-
-                return new_fd;
-        }
-
-        assert(fd >= 0);
+                return RET_NERRNO(openat(fd, ".", flags | O_DIRECTORY));
 
-        new_fd = open(FORMAT_PROC_FD_PATH(fd), flags);
+        int new_fd = open(FORMAT_PROC_FD_PATH(fd), flags);
         if (new_fd < 0) {
                 if (errno != ENOENT)
                         return -errno;
@@ -811,7 +877,6 @@ int fd_reopen(int fd, int flags) {
         return new_fd;
 }
 
-#if 0 /* NM_IGNORED */
 int fd_reopen_condition(
                 int fd,
                 int flags,
@@ -821,6 +886,7 @@ int fd_reopen_condition(
         int r, new_fd;
 
         assert(fd >= 0);
+        assert(!FLAGS_SET(flags, O_CREAT));
 
         /* Invokes fd_reopen(fd, flags), but only if the existing F_GETFL flags don't match the specified
          * flags (masked by the specified mask). This is useful for converting O_PATH fds into real fds if
@@ -843,6 +909,7 @@ int fd_reopen_condition(
         return new_fd;
 }
 
+#if 0 /* NM_IGNORED */
 int fd_is_opath(int fd) {
         int r;
 
@@ -855,6 +922,38 @@ int fd_is_opath(int fd) {
         return FLAGS_SET(r, O_PATH);
 }
 
+int fd_verify_safe_flags(int fd) {
+        int flags, unexpected_flags;
+
+        /* Check if an extrinsic fd is safe to work on (by a privileged service). This ensures that clients
+         * can't trick a privileged service into giving access to a file the client doesn't already have
+         * access to (especially via something like O_PATH).
+         *
+         * O_NOFOLLOW: For some reason the kernel will return this flag from fcntl; it doesn't go away
+         *             immediately after open(). It should have no effect whatsoever to an already-opened FD,
+         *             and since we refuse O_PATH it should be safe.
+         *
+         * RAW_O_LARGEFILE: glibc secretly sets this and neglects to hide it from us if we call fcntl.
+         *                  See comment in missing_fcntl.h for more details about this.
+         *
+         * O_DIRECTORY: this is set for directories, which are totally fine
+         */
+
+        assert(fd >= 0);
+
+        flags = fcntl(fd, F_GETFL);
+        if (flags < 0)
+                return -errno;
+
+        unexpected_flags = flags & ~(O_ACCMODE|O_NOFOLLOW|RAW_O_LARGEFILE|O_DIRECTORY);
+        if (unexpected_flags != 0)
+                return log_debug_errno(SYNTHETIC_ERRNO(EREMOTEIO),
+                                       "Unexpected flags set for extrinsic fd: 0%o",
+                                       (unsigned) unexpected_flags);
+
+        return 0;
+}
+
 int read_nr_open(void) {
         _cleanup_free_ char *nr_open = NULL;
         int r;
@@ -901,75 +1000,86 @@ int fd_get_diskseq(int fd, uint64_t *ret) {
 }
 
 int path_is_root_at(int dir_fd, const char *path) {
-        STRUCT_NEW_STATX_DEFINE(st);
-        STRUCT_NEW_STATX_DEFINE(pst);
-        _cleanup_close_ int fd = -EBADF;
-        int r;
+        _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
 
         assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
 
         if (!isempty(path)) {
-                fd = openat(dir_fd, path, O_PATH|O_CLOEXEC);
+                fd = openat(dir_fd, path, O_PATH|O_DIRECTORY|O_CLOEXEC);
                 if (fd < 0)
-                        return -errno;
+                        return errno == ENOTDIR ? false : -errno;
 
                 dir_fd = fd;
         }
 
-        r = statx_fallback(dir_fd, ".", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st.sx);
-        if (r == -ENOTDIR)
-                return false;
+        pfd = openat(dir_fd, "..", O_PATH|O_DIRECTORY|O_CLOEXEC);
+        if (pfd < 0)
+                return errno == ENOTDIR ? false : -errno;
+
+        /* Even if the parent directory has the same inode, the fd may not point to the root directory "/",
+         * and we also need to check that the mount ids are the same. Otherwise, a construct like the
+         * following could be used to trick us:
+         *
+         * $ mkdir /tmp/x /tmp/x/y
+         * $ mount --bind /tmp/x /tmp/x/y
+         */
+
+        return fds_are_same_mount(dir_fd, pfd);
+}
+
+int fds_are_same_mount(int fd1, int fd2) {
+        STRUCT_NEW_STATX_DEFINE(st1);
+        STRUCT_NEW_STATX_DEFINE(st2);
+        int r;
+
+        assert(fd1 >= 0);
+        assert(fd2 >= 0);
+
+        r = statx_fallback(fd1, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st1.sx);
         if (r < 0)
                 return r;
 
-        r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.sx);
+        r = statx_fallback(fd2, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st2.sx);
         if (r < 0)
                 return r;
 
         /* First, compare inode. If these are different, the fd does not point to the root directory "/". */
-        if (!statx_inode_same(&st.sx, &pst.sx))
+        if (!statx_inode_same(&st1.sx, &st2.sx))
                 return false;
 
-        /* Even if the parent directory has the same inode, the fd may not point to the root directory "/",
-         * and we also need to check that the mount ids are the same. Otherwise, a construct like the
-         * following could be used to trick us:
-         *
-         * $ mkdir /tmp/x /tmp/x/y
-         * $ mount --bind /tmp/x /tmp/x/y
-         *
-         * Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old
-         * kernel is used without /proc mounted. In that case, let's assume that we do not have such spurious
-         * mount points in an early boot stage, and silently skip the following check. */
+        /* Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old
+         * kernel is used. In that case, let's assume that we do not have such spurious mount points in an
+         * early boot stage, and silently skip the following check. */
 
-        if (!FLAGS_SET(st.nsx.stx_mask, STATX_MNT_ID)) {
+        if (!FLAGS_SET(st1.nsx.stx_mask, STATX_MNT_ID)) {
                 int mntid;
 
-                r = path_get_mnt_id_at(dir_fd, "", &mntid);
-                if (r == -ENOSYS)
+                r = path_get_mnt_id_at_fallback(fd1, "", &mntid);
+                if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
                         return true; /* skip the mount ID check */
                 if (r < 0)
                         return r;
                 assert(mntid >= 0);
 
-                st.nsx.stx_mnt_id = mntid;
-                st.nsx.stx_mask |= STATX_MNT_ID;
+                st1.nsx.stx_mnt_id = mntid;
+                st1.nsx.stx_mask |= STATX_MNT_ID;
         }
 
-        if (!FLAGS_SET(pst.nsx.stx_mask, STATX_MNT_ID)) {
+        if (!FLAGS_SET(st2.nsx.stx_mask, STATX_MNT_ID)) {
                 int mntid;
 
-                r = path_get_mnt_id_at(dir_fd, "..", &mntid);
-                if (r == -ENOSYS)
+                r = path_get_mnt_id_at_fallback(fd2, "", &mntid);
+                if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
                         return true; /* skip the mount ID check */
                 if (r < 0)
                         return r;
                 assert(mntid >= 0);
 
-                pst.nsx.stx_mnt_id = mntid;
-                pst.nsx.stx_mask |= STATX_MNT_ID;
+                st2.nsx.stx_mnt_id = mntid;
+                st2.nsx.stx_mask |= STATX_MNT_ID;
         }
 
-        return statx_mount_same(&st.nsx, &pst.nsx);
+        return statx_mount_same(&st1.nsx, &st2.nsx);
 }
 
 const char *accmode_to_string(int flags) {
@@ -984,4 +1094,12 @@ const char *accmode_to_string(int flags) {
                 return NULL;
         }
 }
+
+char *format_proc_pid_fd_path(char buf[static PROC_PID_FD_PATH_MAX], pid_t pid, int fd) {
+        assert(buf);
+        assert(fd >= 0);
+        assert(pid >= 0);
+        assert_se(snprintf_ok(buf, PROC_PID_FD_PATH_MAX, "/proc/" PID_FMT "/fd/%i", pid == 0 ? getpid_cached() : pid, fd));
+        return buf;
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/fd-util.h b/src/libnm-systemd-shared/src/basic/fd-util.h
index c870a1b8..f5498310 100644
--- a/src/libnm-systemd-shared/src/basic/fd-util.h
+++ b/src/libnm-systemd-shared/src/basic/fd-util.h
@@ -8,6 +8,7 @@
 #include <sys/socket.h>
 
 #include "macro.h"
+#include "missing_fcntl.h"
 #include "stdio-util.h"
 
 /* maximum length of fdname */
@@ -16,7 +17,10 @@
 /* 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)
-#define PIPE_EBADF { -EBADF, -EBADF }
+
+/* Useful helpers for initializing pipe(), socketpair() or stdio fd arrays */
+#define EBADF_PAIR { -EBADF, -EBADF }
+#define EBADF_TRIPLET { -EBADF, -EBADF, -EBADF }
 
 int close_nointr(int fd);
 int safe_close(int fd);
@@ -29,7 +33,9 @@ static inline int safe_close_above_stdio(int fd) {
         return safe_close(fd);
 }
 
-void close_many(const int fds[], size_t n_fd);
+void close_many(const int fds[], size_t n_fds);
+void close_many_unset(int fds[], size_t n_fds);
+void close_many_and_free(int *fds, size_t n_fds);
 
 int fclose_nointr(FILE *f);
 FILE* safe_fclose(FILE *f);
@@ -47,6 +53,11 @@ static inline void fclosep(FILE **f) {
         safe_fclose(*f);
 }
 
+static inline void* close_fd_ptr(void *p) {
+        safe_close(PTR_TO_FD(p));
+        return NULL;
+}
+
 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(FILE*, pclose, NULL);
 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL);
 
@@ -57,6 +68,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL);
 #define _cleanup_close_pair_ _cleanup_(close_pairp)
 
 int fd_nonblock(int fd, bool nonblock);
+int stdio_disable_nonblock(void);
+
 int fd_cloexec(int fd, bool cloexec);
 int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec);
 
@@ -65,6 +78,8 @@ int get_max_fd(void);
 int close_all_fds(const int except[], size_t n_except);
 int close_all_fds_without_malloc(const int except[], size_t n_except);
 
+int pack_fds(int fds[], size_t n);
+
 int same_fd(int a, int b);
 
 void cmsg_close_all(struct msghdr *mh);
@@ -97,11 +112,17 @@ static inline int make_null_stdio(void) {
 
 int fd_reopen(int fd, int flags);
 int fd_reopen_condition(int fd, int flags, int mask, int *ret_new_fd);
+
 int fd_is_opath(int fd);
+int fd_verify_safe_flags(int fd);
+
 int read_nr_open(void);
 int fd_get_diskseq(int fd, uint64_t *ret);
 
 int path_is_root_at(int dir_fd, const char *path);
+static inline int path_is_root(const char *path) {
+        return path_is_root_at(AT_FDCWD, path);
+}
 static inline int dir_fd_is_root(int dir_fd) {
         return path_is_root_at(dir_fd, NULL);
 }
@@ -109,6 +130,8 @@ static inline int dir_fd_is_root_or_cwd(int dir_fd) {
         return dir_fd == AT_FDCWD ? true : path_is_root_at(dir_fd, NULL);
 }
 
+int fds_are_same_mount(int fd1, int fd2);
+
 /* The maximum length a buffer for a /proc/self/fd/<fd> path needs */
 #define PROC_FD_PATH_MAX \
         (STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int))
@@ -123,6 +146,16 @@ static inline char *format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int f
 #define FORMAT_PROC_FD_PATH(fd) \
         format_proc_fd_path((char[PROC_FD_PATH_MAX]) {}, (fd))
 
+/* The maximum length a buffer for a /proc/<pid>/fd/<fd> path needs */
+#define PROC_PID_FD_PATH_MAX \
+        (STRLEN("/proc//fd/") + DECIMAL_STR_MAX(pid_t) + DECIMAL_STR_MAX(int))
+
+char *format_proc_pid_fd_path(char buf[static PROC_PID_FD_PATH_MAX], pid_t pid, int fd);
+
+/* Kinda the same as FORMAT_PROC_FD_PATH(), but goes by PID rather than "self" symlink */
+#define FORMAT_PROC_PID_FD_PATH(pid, fd)                                \
+        format_proc_pid_fd_path((char[PROC_PID_FD_PATH_MAX]) {}, (pid), (fd))
+
 const char *accmode_to_string(int flags);
 
 /* Like ASSERT_PTR, but for fds */
diff --git a/src/libnm-systemd-shared/src/basic/fileio.c b/src/libnm-systemd-shared/src/basic/fileio.c
index 908a0309..7ab29816 100644
--- a/src/libnm-systemd-shared/src/basic/fileio.c
+++ b/src/libnm-systemd-shared/src/basic/fileio.c
@@ -30,10 +30,13 @@
 #include "stdio-util.h"
 #include "string-util.h"
 #include "sync-util.h"
+#include "terminal-util.h"
 #include "tmpfile-util.h"
 
 /* The maximum size of the file we'll read in one go in read_full_file() (64M). */
 #define READ_FULL_BYTES_MAX (64U*1024U*1024U - 1U)
+/* Used when a size is specified for read_full_file() with READ_FULL_FILE_UNBASE64 or _UNHEX */
+#define READ_FULL_FILE_ENCODED_STRING_AMPLIFICATION_BOUNDARY 3
 
 /* The maximum size of virtual files (i.e. procfs, sysfs, and other virtual "API" files) we'll read in one go
  * in read_virtual_file(). Note that this limit is different (and much lower) than the READ_FULL_BYTES_MAX
@@ -200,6 +203,19 @@ int write_string_stream_ts(
         return 0;
 }
 
+static mode_t write_string_file_flags_to_mode(WriteStringFileFlags flags) {
+
+        /* We support three different modes, that are the ones that really make sense for text files like this:
+         *
+         *     → 0600 (i.e. root-only)
+         *     → 0444 (i.e. read-only)
+         *     → 0644 (i.e. writable for root, readable for everyone else)
+         */
+
+        return FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 :
+                FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0444) ? 0444 : 0644;
+}
+
 static int write_string_file_atomic_at(
                 int dir_fd,
                 const char *fn,
@@ -225,7 +241,7 @@ static int write_string_file_atomic_at(
         if (r < 0)
                 goto fail;
 
-        r = fchmod_umask(fileno(f), FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0644);
+        r = fchmod_umask(fileno(f), write_string_file_flags_to_mode(flags));
         if (r < 0)
                 goto fail;
 
@@ -288,7 +304,7 @@ int write_string_file_ts_at(
                     (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0) |
                     (FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) |
                     (FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY),
-                    (FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666));
+                    write_string_file_flags_to_mode(flags));
         if (fd < 0) {
                 r = -errno;
                 goto fail;
@@ -576,7 +592,7 @@ int read_full_stream_full(
                 size_t *ret_size) {
 
         _cleanup_free_ char *buf = NULL;
-        size_t n, n_next = 0, l;
+        size_t n, n_next = 0, l, expected_decoded_size = size;
         int fd, r;
 
         assert(f);
@@ -587,6 +603,13 @@ int read_full_stream_full(
         if (offset != UINT64_MAX && offset > LONG_MAX) /* fseek() can only deal with "long" offsets */
                 return -ERANGE;
 
+        if ((flags & (READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)) != 0) {
+                if (size <= SIZE_MAX / READ_FULL_FILE_ENCODED_STRING_AMPLIFICATION_BOUNDARY)
+                        size *= READ_FULL_FILE_ENCODED_STRING_AMPLIFICATION_BOUNDARY;
+                else
+                        size = SIZE_MAX;
+        }
+
         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 */
@@ -711,6 +734,11 @@ int read_full_stream_full(
                         explicit_bzero_safe(buf, n);
                 free_and_replace(buf, decoded);
                 n = l = decoded_size;
+
+                if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && l > expected_decoded_size) {
+                        r = -E2BIG;
+                        goto finalize;
+                }
         }
 
         if (!ret_size) {
@@ -1057,7 +1085,9 @@ int fdopen_independent(int fd, const char *mode, FILE **ret) {
         if (mode_flags < 0)
                 return mode_flags;
 
-        copy_fd = fd_reopen(fd, mode_flags);
+        /* Flags returned by fopen_mode_to_flags might contain O_CREAT, but it doesn't make sense for fd_reopen
+         * since we're working on an existing fd anyway. Let's drop it here to avoid triggering assertion. */
+        copy_fd = fd_reopen(fd, mode_flags & ~O_CREAT);
         if (copy_fd < 0)
                 return copy_fd;
 
@@ -1069,123 +1099,171 @@ int fdopen_independent(int fd, const char *mode, FILE **ret) {
         return 0;
 }
 
-static int search_and_fopen_internal(
+static int search_and_open_internal(
                 const char *path,
-                const char *mode,
+                int mode,            /* if ret_fd is NULL this is an [FRWX]_OK mode for access(), otherwise an open mode for open() */
                 const char *root,
                 char **search,
-                FILE **ret,
+                int *ret_fd,
                 char **ret_path) {
 
+        int r;
+
+        assert(!ret_fd || !FLAGS_SET(mode, O_CREAT)); /* We don't support O_CREAT for this */
         assert(path);
-        assert(mode);
-        assert(ret);
+
+        if (path_is_absolute(path)) {
+                _cleanup_close_ int fd = -EBADF;
+
+                if (ret_fd)
+                        /* We only specify 0777 here to appease static analyzers, it's never used since we
+                         * don't support O_CREAT here */
+                        r = fd = RET_NERRNO(open(path, mode, 0777));
+                else
+                        r = RET_NERRNO(access(path, mode));
+                if (r < 0)
+                        return r;
+
+                if (ret_path) {
+                        r = path_simplify_alloc(path, ret_path);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (ret_fd)
+                        *ret_fd = TAKE_FD(fd);
+
+                return 0;
+        }
 
         if (!path_strv_resolve_uniq(search, root))
                 return -ENOMEM;
 
         STRV_FOREACH(i, search) {
+                _cleanup_close_ int fd = -EBADF;
                 _cleanup_free_ char *p = NULL;
-                FILE *f;
 
                 p = path_join(root, *i, path);
                 if (!p)
                         return -ENOMEM;
 
-                f = fopen(p, mode);
-                if (f) {
+                if (ret_fd)
+                        /* as above, 0777 is static analyzer appeasement */
+                        r = fd = RET_NERRNO(open(p, mode, 0777));
+                else
+                        r = RET_NERRNO(access(p, F_OK));
+                if (r >= 0) {
                         if (ret_path)
                                 *ret_path = path_simplify(TAKE_PTR(p));
 
-                        *ret = f;
+                        if (ret_fd)
+                                *ret_fd = TAKE_FD(fd);
+
                         return 0;
                 }
-
-                if (errno != ENOENT)
-                        return -errno;
+                if (r != -ENOENT)
+                        return r;
         }
 
         return -ENOENT;
 }
 
-int search_and_fopen(
-                const char *filename,
-                const char *mode,
+int search_and_open(
+                const char *path,
+                int mode,
                 const char *root,
-                const char **search,
-                FILE **ret,
+                char **search,
+                int *ret_fd,
                 char **ret_path) {
 
         _cleanup_strv_free_ char **copy = NULL;
 
-        assert(filename);
-        assert(mode);
-        assert(ret);
+        assert(path);
 
-        if (path_is_absolute(filename)) {
-                _cleanup_fclose_ FILE *f = NULL;
+        copy = strv_copy((char**) search);
+        if (!copy)
+                return -ENOMEM;
+
+        return search_and_open_internal(path, mode, root, copy, ret_fd, ret_path);
+}
+
+static int search_and_fopen_internal(
+                const char *path,
+                const char *mode,
+                const char *root,
+                char **search,
+                FILE **ret_file,
+                char **ret_path) {
+
+        _cleanup_free_ char *found_path = NULL;
+        _cleanup_close_ int fd = -EBADF;
+        int r;
+
+        assert(path);
+        assert(mode || !ret_file);
+
+        r = search_and_open(
+                        path,
+                        mode ? fopen_mode_to_flags(mode) : 0,
+                        root,
+                        search,
+                        ret_file ? &fd : NULL,
+                        ret_path ? &found_path : NULL);
+        if (r < 0)
+                return r;
 
-                f = fopen(filename, mode);
+        if (ret_file) {
+                FILE *f = take_fdopen(&fd, mode);
                 if (!f)
                         return -errno;
 
-                if (ret_path) {
-                        char *p;
+                *ret_file = f;
+        }
 
-                        p = strdup(filename);
-                        if (!p)
-                                return -ENOMEM;
+        if (ret_path)
+                *ret_path = TAKE_PTR(found_path);
 
-                        *ret_path = path_simplify(p);
-                }
+        return 0;
+}
 
-                *ret = TAKE_PTR(f);
-                return 0;
-        }
+int search_and_fopen(
+                const char *path,
+                const char *mode,
+                const char *root,
+                const char **search,
+                FILE **ret_file,
+                char **ret_path) {
+
+        _cleanup_strv_free_ char **copy = NULL;
+
+        assert(path);
+        assert(mode || !ret_file);
 
         copy = strv_copy((char**) search);
         if (!copy)
                 return -ENOMEM;
 
-        return search_and_fopen_internal(filename, mode, root, copy, ret, ret_path);
+        return search_and_fopen_internal(path, mode, root, copy, ret_file, ret_path);
 }
 
 int search_and_fopen_nulstr(
-                const char *filename,
+                const char *path,
                 const char *mode,
                 const char *root,
                 const char *search,
-                FILE **ret,
+                FILE **ret_file,
                 char **ret_path) {
 
-        _cleanup_strv_free_ char **s = NULL;
-
-        if (path_is_absolute(filename)) {
-                _cleanup_fclose_ FILE *f = NULL;
-
-                f = fopen(filename, mode);
-                if (!f)
-                        return -errno;
-
-                if (ret_path) {
-                        char *p;
-
-                        p = strdup(filename);
-                        if (!p)
-                                return -ENOMEM;
-
-                        *ret_path = path_simplify(p);
-                }
+        _cleanup_strv_free_ char **l = NULL;
 
-                *ret = TAKE_PTR(f);
-                return 0;
-        }
+        assert(path);
+        assert(mode || !ret_file);
 
-        s = strv_split_nulstr(search);
-        if (!s)
+        l = strv_split_nulstr(search);
+        if (!l)
                 return -ENOMEM;
 
-        return search_and_fopen_internal(filename, mode, root, s, ret, ret_path);
+        return search_and_fopen_internal(path, mode, root, l, ret_file, ret_path);
 }
 #endif /* NM_IGNORED */
 
@@ -1259,33 +1337,31 @@ int read_timestamp_file(const char *fn, usec_t *ret) {
 }
 #endif /* NM_IGNORED */
 
-int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) {
-        int r;
-
+int fputs_with_separator(FILE *f, const char *s, const char *separator, bool *space) {
         assert(s);
+        assert(space);
 
-        /* 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. */
+        /* 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 (!separator)
+                separator = " ";
 
-                if (*space) {
-                        r = fputs(separator, f);
-                        if (r < 0)
-                                return r;
-                }
+        if (*space)
+                if (fputs(separator, f) < 0)
+                        return -EIO;
 
-                *space = true;
-        }
+        *space = true;
+
+        if (fputs(s, f) < 0)
+                return -EIO;
 
-        return fputs(s, f);
+        return 0;
 }
 
 #if 0 /* NM_IGNORED */
@@ -1406,7 +1482,7 @@ int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) {
                                                      * and don't call isatty() on an invalid fd */
                                                 flags |= READ_LINE_NOT_A_TTY;
                                         else
-                                                flags |= isatty(fd) ? READ_LINE_IS_A_TTY : READ_LINE_NOT_A_TTY;
+                                                flags |= isatty_safe(fd) ? READ_LINE_IS_A_TTY : READ_LINE_NOT_A_TTY;
                                 }
                                 if (FLAGS_SET(flags, READ_LINE_IS_A_TTY))
                                         break;
@@ -1437,6 +1513,36 @@ int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) {
         return (int) count;
 }
 
+int read_stripped_line(FILE *f, size_t limit, char **ret) {
+        _cleanup_free_ char *s = NULL;
+        int r;
+
+        assert(f);
+
+        r = read_line(f, limit, ret ? &s : NULL);
+        if (r < 0)
+                return r;
+
+        if (ret) {
+                const char *p;
+
+                p = strstrip(s);
+                if (p == s)
+                        *ret = TAKE_PTR(s);
+                else {
+                        char *copy;
+
+                        copy = strdup(p);
+                        if (!copy)
+                                return -ENOMEM;
+
+                        *ret = copy;
+                }
+        }
+
+        return r;
+}
+
 int safe_fgetc(FILE *f, char *ret) {
         int k;
 
diff --git a/src/libnm-systemd-shared/src/basic/fileio.h b/src/libnm-systemd-shared/src/basic/fileio.h
index 769bf394..03c3f3ff 100644
--- a/src/libnm-systemd-shared/src/basic/fileio.h
+++ b/src/libnm-systemd-shared/src/basic/fileio.h
@@ -26,7 +26,8 @@ typedef enum {
         WRITE_STRING_FILE_NOFOLLOW                   = 1 << 8,
         WRITE_STRING_FILE_MKDIR_0755                 = 1 << 9,
         WRITE_STRING_FILE_MODE_0600                  = 1 << 10,
-        WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL = 1 << 11,
+        WRITE_STRING_FILE_MODE_0444                  = 1 << 11,
+        WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL = 1 << 12,
 
         /* 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()
@@ -129,8 +130,12 @@ static inline int fopen_unlocked(const char *path, const char *mode, FILE **ret)
 
 int fdopen_independent(int fd, const char *mode, FILE **ret);
 
-int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **ret, char **ret_path);
-int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **ret, char **ret_path);
+int search_and_open(const char *path, int mode, const char *root, char **search, int *ret_fd, char **ret_path);
+static inline int search_and_access(const char *path, int mode, const char *root, char**search, char **ret_path) {
+        return search_and_open(path, mode, root, search, NULL, ret_path);
+}
+int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **ret_file, char **ret_path);
+int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **ret_file, char **ret_path);
 
 int fflush_and_check(FILE *f);
 int fflush_sync_and_check(FILE *f);
@@ -138,7 +143,7 @@ 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);
+int fputs_with_separator(FILE *f, const char *s, const char *separator, bool *space);
 
 typedef enum ReadLineFlags {
         READ_LINE_ONLY_NUL  = 1 << 0,
@@ -162,6 +167,8 @@ 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 read_stripped_line(FILE *f, size_t limit, char **ret);
+
 int safe_fgetc(FILE *f, char *ret);
 
 int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line);
diff --git a/src/libnm-systemd-shared/src/basic/format-util.h b/src/libnm-systemd-shared/src/basic/format-util.h
index 7db8b61d..8a80eb33 100644
--- a/src/libnm-systemd-shared/src/basic/format-util.h
+++ b/src/libnm-systemd-shared/src/basic/format-util.h
@@ -18,6 +18,14 @@ assert_cc(sizeof(uid_t) == sizeof(uint32_t));
 assert_cc(sizeof(gid_t) == sizeof(uint32_t));
 #define GID_FMT "%" PRIu32
 
+/* Note: the lifetime of the compound literal is the immediately surrounding block,
+ * see C11 §6.5.2.5, and
+ * https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */
+#define FORMAT_UID(uid) \
+        snprintf_ok((char[DECIMAL_STR_MAX(uid_t)]){}, DECIMAL_STR_MAX(uid_t), UID_FMT, uid)
+#define FORMAT_GID(gid) \
+        snprintf_ok((char[DECIMAL_STR_MAX(gid_t)]){}, DECIMAL_STR_MAX(gid_t), GID_FMT, gid)
+
 #if SIZEOF_TIME_T == 8
 #  define PRI_TIME PRIi64
 #elif SIZEOF_TIME_T == 4
diff --git a/src/libnm-systemd-shared/src/basic/fs-util.c b/src/libnm-systemd-shared/src/basic/fs-util.c
index 32fd849d..3f414794 100644
--- a/src/libnm-systemd-shared/src/basic/fs-util.c
+++ b/src/libnm-systemd-shared/src/basic/fs-util.c
@@ -11,6 +11,7 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
+#include "btrfs.h"
 #include "dirent-util.h"
 #include "fd-util.h"
 #include "fileio.h"
@@ -119,7 +120,11 @@ int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char
 int readlinkat_malloc(int fd, const char *p, char **ret) {
         size_t l = PATH_MAX;
 
-        assert(p);
+        assert(fd >= 0 || fd == AT_FDCWD);
+
+        if (fd < 0 && isempty(p))
+                return -EISDIR; /* In this case, the fd points to the current working directory, and is
+                                 * definitely not a symlink. Let's return earlier. */
 
         for (;;) {
                 _cleanup_free_ char *c = NULL;
@@ -129,7 +134,7 @@ int readlinkat_malloc(int fd, const char *p, char **ret) {
                 if (!c)
                         return -ENOMEM;
 
-                n = readlinkat(fd, p, c, l);
+                n = readlinkat(fd, strempty(p), c, l);
                 if (n < 0)
                         return -errno;
 
@@ -155,7 +160,6 @@ 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, *name = NULL;
         int r;
@@ -177,6 +181,7 @@ int readlink_value(const char *p, char **ret) {
         return 0;
 }
 
+#if 0 /* NM_IGNORED */
 int readlink_and_make_absolute(const char *p, char **ret) {
         _cleanup_free_ char *target = NULL;
         int r;
@@ -292,8 +297,22 @@ int fchmod_umask(int fd, mode_t m) {
 
 int fchmod_opath(int fd, mode_t m) {
         /* 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. */
+         * O_PATH. The tool set we have is non-intuitive:
+         * - fchmod(2) only operates on open files (i. e., fds with an open file description);
+         * - fchmodat(2) does not have a flag arg like fchownat(2) does, so no way to pass AT_EMPTY_PATH;
+         *   + it should not be confused with the libc fchmodat(3) interface, which adds 4th flag argument,
+         *     but does not support AT_EMPTY_PATH (only supports AT_SYMLINK_NOFOLLOW);
+         * - fchmodat2(2) supports all the AT_* flags, but is still very recent.
+         *
+         * We try to use fchmodat2(), and, if it is not supported, resort
+         * to the /proc/self/fd dance. */
+
+        assert(fd >= 0);
+
+        if (fchmodat2(fd, "", m, AT_EMPTY_PATH) >= 0)
+                return 0;
+        if (!IN_SET(errno, ENOSYS, EPERM)) /* Some container managers block unknown syscalls with EPERM */
+                return -errno;
 
         if (chmod(FORMAT_PROC_FD_PATH(fd), m) < 0) {
                 if (errno != ENOENT)
@@ -1045,7 +1064,7 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
                 path = fname;
         }
 
-        fd = xopenat(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, /* xopen_flags = */ 0, mode);
+        fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, /* xopen_flags = */ 0, mode);
         if (IN_SET(fd, -ELOOP, -ENOTDIR))
                 return -EEXIST;
         if (fd < 0)
@@ -1101,13 +1120,23 @@ int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, b
         }
 }
 
-int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) {
+int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) {
         _cleanup_close_ int fd = -EBADF;
         bool made = false;
         int r;
 
         assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
 
+        /* This is like openat(), but has a few tricks up its sleeves, extending behaviour:
+         *
+         *   • O_DIRECTORY|O_CREAT is supported, which causes a directory to be created, and immediately
+         *     opened. When used with the XO_SUBVOLUME flag this will even create a btrfs subvolume.
+         *
+         *   • If O_CREAT is used with XO_LABEL, any created file will be immediately relabelled.
+         *
+         *   • If the path is specified NULL or empty, behaves like fd_reopen().
+         */
+
         if (isempty(path)) {
                 assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
                 return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW);
@@ -1120,7 +1149,10 @@ int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags
         }
 
         if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
-                r = RET_NERRNO(mkdirat(dir_fd, path, mode));
+                if (FLAGS_SET(xopen_flags, XO_SUBVOLUME))
+                        r = btrfs_subvol_make_fallback(dir_fd, path, mode);
+                else
+                        r = RET_NERRNO(mkdirat(dir_fd, path, mode));
                 if (r == -EEXIST) {
                         if (FLAGS_SET(open_flags, O_EXCL))
                                 return -EEXIST;
@@ -1170,7 +1202,7 @@ int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags
 }
 
 #if 0 /* NM_IGNORED */
-int xopenat_lock(
+int xopenat_lock_full(
                 int dir_fd,
                 const char *path,
                 int open_flags,
@@ -1183,18 +1215,17 @@ int xopenat_lock(
         int r;
 
         assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
-        assert(path);
         assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
 
         /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with
          * the same error here). */
-        if (FLAGS_SET(open_flags, O_DIRECTORY) && locktype != LOCK_BSD)
+        if (FLAGS_SET(open_flags, O_DIRECTORY) && !IN_SET(locktype, LOCK_BSD, LOCK_NONE))
                 return -EBADF;
 
         for (;;) {
                 struct stat st;
 
-                fd = xopenat(dir_fd, path, open_flags, xopen_flags, mode);
+                fd = xopenat_full(dir_fd, path, open_flags, xopen_flags, mode);
                 if (fd < 0)
                         return fd;
 
diff --git a/src/libnm-systemd-shared/src/basic/fs-util.h b/src/libnm-systemd-shared/src/basic/fs-util.h
index a19836d1..6a1e2e76 100644
--- a/src/libnm-systemd-shared/src/basic/fs-util.h
+++ b/src/libnm-systemd-shared/src/basic/fs-util.h
@@ -133,9 +133,16 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode);
 int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created);
 
 typedef enum XOpenFlags {
-        XO_LABEL = 1 << 0,
+        XO_LABEL     = 1 << 0,
+        XO_SUBVOLUME = 1 << 1,
 } XOpenFlags;
 
-int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode);
+int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode);
+static inline int xopenat(int dir_fd, const char *path, int open_flags) {
+        return xopenat_full(dir_fd, path, open_flags, 0, 0);
+}
 
-int xopenat_lock(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode, LockType locktype, int operation);
+int xopenat_lock_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode, LockType locktype, int operation);
+static inline int xopenat_lock(int dir_fd, const char *path, int open_flags, LockType locktype, int operation) {
+        return xopenat_lock_full(dir_fd, path, open_flags, 0, 0, locktype, operation);
+}
diff --git a/src/libnm-systemd-shared/src/basic/glyph-util.c b/src/libnm-systemd-shared/src/basic/glyph-util.c
index 1ea8a645..58d64a03 100644
--- a/src/libnm-systemd-shared/src/basic/glyph-util.c
+++ b/src/libnm-systemd-shared/src/basic/glyph-util.c
@@ -25,7 +25,7 @@ bool emoji_enabled(void) {
         return cached_emoji_enabled;
 }
 
-const char *special_glyph(SpecialGlyph code) {
+const char *special_glyph_full(SpecialGlyph code, bool force_utf) {
 
         /* A list of a number of interesting unicode glyphs we can use to decorate our output. It's probably wise to be
          * conservative here, and primarily stick to the glyphs defined in the eurlatgr font, so that display still
@@ -43,6 +43,8 @@ const char *special_glyph(SpecialGlyph code) {
                         [SPECIAL_GLYPH_TREE_SPACE]              = "  ",
                         [SPECIAL_GLYPH_TREE_TOP]                = ",-",
                         [SPECIAL_GLYPH_VERTICAL_DOTTED]         = ":",
+                        [SPECIAL_GLYPH_HORIZONTAL_DOTTED]       = "-",
+                        [SPECIAL_GLYPH_HORIZONTAL_FAT]          = "=",
                         [SPECIAL_GLYPH_TRIANGULAR_BULLET]       = ">",
                         [SPECIAL_GLYPH_BLACK_CIRCLE]            = "*",
                         [SPECIAL_GLYPH_WHITE_CIRCLE]            = "*",
@@ -54,11 +56,12 @@ const char *special_glyph(SpecialGlyph code) {
                         [SPECIAL_GLYPH_CROSS_MARK]              = "-",
                         [SPECIAL_GLYPH_LIGHT_SHADE]             = "-",
                         [SPECIAL_GLYPH_DARK_SHADE]              = "X",
+                        [SPECIAL_GLYPH_FULL_BLOCK]              = "#",
                         [SPECIAL_GLYPH_SIGMA]                   = "S",
-                        [SPECIAL_GLYPH_ARROW_LEFT]              = "<-",
-                        [SPECIAL_GLYPH_ARROW_RIGHT]             = "->",
                         [SPECIAL_GLYPH_ARROW_UP]                = "^",
                         [SPECIAL_GLYPH_ARROW_DOWN]              = "v",
+                        [SPECIAL_GLYPH_ARROW_LEFT]              = "<-",
+                        [SPECIAL_GLYPH_ARROW_RIGHT]             = "->",
                         [SPECIAL_GLYPH_ELLIPSIS]                = "...",
                         [SPECIAL_GLYPH_EXTERNAL_LINK]           = "[LNK]",
                         [SPECIAL_GLYPH_ECSTATIC_SMILEY]         = ":-]",
@@ -73,7 +76,12 @@ const char *special_glyph(SpecialGlyph code) {
                         [SPECIAL_GLYPH_RECYCLING]               = "~",
                         [SPECIAL_GLYPH_DOWNLOAD]                = "\\",
                         [SPECIAL_GLYPH_SPARKLES]                = "*",
+                        [SPECIAL_GLYPH_LOW_BATTERY]             = "!",
                         [SPECIAL_GLYPH_WARNING_SIGN]            = "!",
+                        [SPECIAL_GLYPH_RED_CIRCLE]              = "o",
+                        [SPECIAL_GLYPH_YELLOW_CIRCLE]           = "o",
+                        [SPECIAL_GLYPH_BLUE_CIRCLE]             = "o",
+                        [SPECIAL_GLYPH_GREEN_CIRCLE]            = "o",
                 },
 
                 /* UTF-8 */
@@ -87,6 +95,8 @@ const char *special_glyph(SpecialGlyph code) {
 
                         /* Single glyphs in both cases */
                         [SPECIAL_GLYPH_VERTICAL_DOTTED]         = u8"┆",
+                        [SPECIAL_GLYPH_HORIZONTAL_DOTTED]       = u8"┄",
+                        [SPECIAL_GLYPH_HORIZONTAL_FAT]          = u8"━",
                         [SPECIAL_GLYPH_TRIANGULAR_BULLET]       = u8"‣",
                         [SPECIAL_GLYPH_BLACK_CIRCLE]            = u8"●",
                         [SPECIAL_GLYPH_WHITE_CIRCLE]            = u8"○",
@@ -98,6 +108,7 @@ const char *special_glyph(SpecialGlyph code) {
                         [SPECIAL_GLYPH_CROSS_MARK]              = u8"✗",        /* actually called: BALLOT X */
                         [SPECIAL_GLYPH_LIGHT_SHADE]             = u8"░",
                         [SPECIAL_GLYPH_DARK_SHADE]              = u8"▒",
+                        [SPECIAL_GLYPH_FULL_BLOCK]              = u8"█",
                         [SPECIAL_GLYPH_SIGMA]                   = u8"Σ",
                         [SPECIAL_GLYPH_ARROW_UP]                = u8"↑",       /* actually called: UPWARDS ARROW */
                         [SPECIAL_GLYPH_ARROW_DOWN]              = u8"↓",       /* actually called: DOWNWARDS ARROW */
@@ -131,7 +142,15 @@ const char *special_glyph(SpecialGlyph code) {
                         [SPECIAL_GLYPH_RECYCLING]               = u8"♻️",        /* actually called: UNIVERSAL RECYCLNG SYMBOL */
                         [SPECIAL_GLYPH_DOWNLOAD]                = u8"⤵️",        /* actually called: RIGHT ARROW CURVING DOWN */
                         [SPECIAL_GLYPH_SPARKLES]                = u8"✨",
+                        [SPECIAL_GLYPH_LOW_BATTERY]             = u8"🪫",
                         [SPECIAL_GLYPH_WARNING_SIGN]            = u8"⚠️",
+                        [SPECIAL_GLYPH_COMPUTER_DISK]           = u8"💽",
+                        [SPECIAL_GLYPH_WORLD]                   = u8"🌍",
+
+                        [SPECIAL_GLYPH_RED_CIRCLE]              = u8"🔴",
+                        [SPECIAL_GLYPH_YELLOW_CIRCLE]           = u8"🟡",
+                        [SPECIAL_GLYPH_BLUE_CIRCLE]             = u8"🔵",
+                        [SPECIAL_GLYPH_GREEN_CIRCLE]            = u8"🟢",
                 },
         };
 
@@ -139,5 +158,5 @@ const char *special_glyph(SpecialGlyph code) {
                 return NULL;
 
         assert(code < _SPECIAL_GLYPH_MAX);
-        return draw_table[code >= _SPECIAL_GLYPH_FIRST_EMOJI ? emoji_enabled() : is_locale_utf8()][code];
+        return draw_table[force_utf || (code >= _SPECIAL_GLYPH_FIRST_EMOJI ? emoji_enabled() : is_locale_utf8())][code];
 }
diff --git a/src/libnm-systemd-shared/src/basic/glyph-util.h b/src/libnm-systemd-shared/src/basic/glyph-util.h
index b6463962..db8dbbff 100644
--- a/src/libnm-systemd-shared/src/basic/glyph-util.h
+++ b/src/libnm-systemd-shared/src/basic/glyph-util.h
@@ -13,6 +13,8 @@ typedef enum SpecialGlyph {
         SPECIAL_GLYPH_TREE_SPACE,
         SPECIAL_GLYPH_TREE_TOP,
         SPECIAL_GLYPH_VERTICAL_DOTTED,
+        SPECIAL_GLYPH_HORIZONTAL_DOTTED,
+        SPECIAL_GLYPH_HORIZONTAL_FAT,
         SPECIAL_GLYPH_TRIANGULAR_BULLET,
         SPECIAL_GLYPH_BLACK_CIRCLE,
         SPECIAL_GLYPH_WHITE_CIRCLE,
@@ -22,14 +24,15 @@ typedef enum SpecialGlyph {
         SPECIAL_GLYPH_MU,
         SPECIAL_GLYPH_CHECK_MARK,
         SPECIAL_GLYPH_CROSS_MARK,
-        SPECIAL_GLYPH_ARROW_LEFT,
-        SPECIAL_GLYPH_ARROW_RIGHT,
-        SPECIAL_GLYPH_ARROW_UP,
-        SPECIAL_GLYPH_ARROW_DOWN,
-        SPECIAL_GLYPH_ELLIPSIS,
         SPECIAL_GLYPH_LIGHT_SHADE,
         SPECIAL_GLYPH_DARK_SHADE,
+        SPECIAL_GLYPH_FULL_BLOCK,
         SPECIAL_GLYPH_SIGMA,
+        SPECIAL_GLYPH_ARROW_UP,
+        SPECIAL_GLYPH_ARROW_DOWN,
+        SPECIAL_GLYPH_ARROW_LEFT,
+        SPECIAL_GLYPH_ARROW_RIGHT,
+        SPECIAL_GLYPH_ELLIPSIS,
         SPECIAL_GLYPH_EXTERNAL_LINK,
         _SPECIAL_GLYPH_FIRST_EMOJI,
         SPECIAL_GLYPH_ECSTATIC_SMILEY = _SPECIAL_GLYPH_FIRST_EMOJI,
@@ -44,15 +47,26 @@ typedef enum SpecialGlyph {
         SPECIAL_GLYPH_RECYCLING,
         SPECIAL_GLYPH_DOWNLOAD,
         SPECIAL_GLYPH_SPARKLES,
+        SPECIAL_GLYPH_LOW_BATTERY,
         SPECIAL_GLYPH_WARNING_SIGN,
+        SPECIAL_GLYPH_COMPUTER_DISK,
+        SPECIAL_GLYPH_WORLD,
+        SPECIAL_GLYPH_RED_CIRCLE,
+        SPECIAL_GLYPH_YELLOW_CIRCLE,
+        SPECIAL_GLYPH_BLUE_CIRCLE,
+        SPECIAL_GLYPH_GREEN_CIRCLE,
         _SPECIAL_GLYPH_MAX,
         _SPECIAL_GLYPH_INVALID = -EINVAL,
 } SpecialGlyph;
 
-const char *special_glyph(SpecialGlyph code) _const_;
-
 bool emoji_enabled(void);
 
+const char *special_glyph_full(SpecialGlyph code, bool force_utf) _const_;
+
+static inline const char *special_glyph(SpecialGlyph code) {
+        return special_glyph_full(code, false);
+}
+
 static inline const char *special_glyph_check_mark(bool b) {
         return b ? special_glyph(SPECIAL_GLYPH_CHECK_MARK) : special_glyph(SPECIAL_GLYPH_CROSS_MARK);
 }
diff --git a/src/libnm-systemd-shared/src/basic/hash-funcs.c b/src/libnm-systemd-shared/src/basic/hash-funcs.c
index eed30f09..d20ca3c3 100644
--- a/src/libnm-systemd-shared/src/basic/hash-funcs.c
+++ b/src/libnm-systemd-shared/src/basic/hash-funcs.c
@@ -22,7 +22,6 @@ DEFINE_HASH_OPS_FULL(string_hash_ops_free_strv_free,
                      char, string_hash_func, string_compare_func, free,
                      char*, strv_free);
 
-#if 0 /* NM_IGNORED */
 void path_hash_func(const char *q, struct siphash *state) {
         bool add_slash = false;
 
@@ -36,7 +35,7 @@ void path_hash_func(const char *q, struct siphash *state) {
 
         /* if path is absolute, add one "/" to the hash. */
         if (path_is_absolute(q))
-                siphash24_compress("/", 1, state);
+                siphash24_compress_byte('/', state);
 
         for (;;) {
                 const char *e;
@@ -68,10 +67,9 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(path_hash_ops_free,
 DEFINE_HASH_OPS_FULL(path_hash_ops_free_free,
                      char, path_hash_func, path_compare, free,
                      void, free);
-#endif /* NM_IGNORED */
 
 void trivial_hash_func(const void *p, struct siphash *state) {
-        siphash24_compress(&p, sizeof(p), state);
+        siphash24_compress_typesafe(p, state);
 }
 
 int trivial_compare_func(const void *a, const void *b) {
@@ -97,7 +95,7 @@ const struct hash_ops trivial_hash_ops_free_free = {
 };
 
 void uint64_hash_func(const uint64_t *p, struct siphash *state) {
-        siphash24_compress(p, sizeof(uint64_t), state);
+        siphash24_compress_typesafe(*p, state);
 }
 
 int uint64_compare_func(const uint64_t *a, const uint64_t *b) {
@@ -109,7 +107,7 @@ 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);
+        siphash24_compress_typesafe(*p, state);
 }
 #endif
 
diff --git a/src/libnm-systemd-shared/src/basic/hash-funcs.h b/src/libnm-systemd-shared/src/basic/hash-funcs.h
index be642892..3804e94d 100644
--- a/src/libnm-systemd-shared/src/basic/hash-funcs.h
+++ b/src/libnm-systemd-shared/src/basic/hash-funcs.h
@@ -93,14 +93,14 @@ extern const struct hash_ops trivial_hash_ops;
 extern const struct hash_ops trivial_hash_ops_free;
 extern const struct hash_ops trivial_hash_ops_free_free;
 
-/* 32bit values we can always just embed in the pointer itself, but in order to support 32bit archs we need store 64bit
+/* 32-bit values we can always just embed in the pointer itself, but in order to support 32-bit archs we need store 64-bit
  * 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! */
+/* On some archs dev_t is 32-bit, and on others 64-bit. And sometimes it's 64-bit on 32-bit archs, and sometimes 32-bit on
+ * 64-bit archs. Yuck! */
 #if SIZEOF_DEV_T != 8
 void devt_hash_func(const dev_t *p, struct siphash *state);
 #else
diff --git a/src/libnm-systemd-shared/src/basic/hashmap.c b/src/libnm-systemd-shared/src/basic/hashmap.c
index 356200cf..9686af0d 100644
--- a/src/libnm-systemd-shared/src/basic/hashmap.c
+++ b/src/libnm-systemd-shared/src/basic/hashmap.c
@@ -23,6 +23,7 @@
 #include "random-util.h"
 #include "set.h"
 #include "siphash24.h"
+#include "sort-util.h"
 #include "string-util.h"
 #include "strv.h"
 
@@ -176,9 +177,9 @@ struct _packed_ indirect_storage {
 };
 
 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. */
+        /* This gives us 39 bytes on 64-bit, or 35 bytes on 32-bit.
+         * That's room for 4 set_entries + 4 DIB bytes + 3 unused bytes on 64-bit,
+         *              or 7 set_entries + 7 DIB bytes + 0 unused bytes on 32-bit. */
         uint8_t storage[sizeof(struct indirect_storage)];
 };
 
@@ -2112,3 +2113,91 @@ bool set_fnmatch(Set *include_patterns, Set *exclude_patterns, const char *needl
 
         return set_fnmatch_one(include_patterns, needle);
 }
+
+static int hashmap_entry_compare(
+                struct hashmap_base_entry * const *a,
+                struct hashmap_base_entry * const *b,
+                compare_func_t compare) {
+
+        assert(a && *a);
+        assert(b && *b);
+        assert(compare);
+
+        return compare((*a)->key, (*b)->key);
+}
+
+static int _hashmap_dump_entries_sorted(
+                HashmapBase *h,
+                void ***ret,
+                size_t *ret_n) {
+        _cleanup_free_ void **entries = NULL;
+        Iterator iter;
+        unsigned idx;
+        size_t n = 0;
+
+        assert(ret);
+        assert(ret_n);
+
+        if (_hashmap_size(h) == 0) {
+                *ret = NULL;
+                *ret_n = 0;
+                return 0;
+        }
+
+        /* We append one more element than needed so that the resulting array can be used as a strv. We
+         * don't count this entry in the returned size. */
+        entries = new(void*, _hashmap_size(h) + 1);
+        if (!entries)
+                return -ENOMEM;
+
+        HASHMAP_FOREACH_IDX(idx, h, iter)
+                entries[n++] = bucket_at(h, idx);
+
+        assert(n == _hashmap_size(h));
+        entries[n] = NULL;
+
+        typesafe_qsort_r((struct hashmap_base_entry**) entries, n,
+                         hashmap_entry_compare, h->hash_ops->compare);
+
+        *ret = TAKE_PTR(entries);
+        *ret_n = n;
+        return 0;
+}
+
+int _hashmap_dump_keys_sorted(HashmapBase *h, void ***ret, size_t *ret_n) {
+        _cleanup_free_ void **entries = NULL;
+        size_t n;
+        int r;
+
+        r = _hashmap_dump_entries_sorted(h, &entries, &n);
+        if (r < 0)
+                return r;
+
+        /* Reuse the array. */
+        FOREACH_ARRAY(e, entries, n)
+                *e = (void*) (*(struct hashmap_base_entry**) e)->key;
+
+        *ret = TAKE_PTR(entries);
+        if (ret_n)
+                *ret_n = n;
+        return 0;
+}
+
+int _hashmap_dump_sorted(HashmapBase *h, void ***ret, size_t *ret_n) {
+        _cleanup_free_ void **entries = NULL;
+        size_t n;
+        int r;
+
+        r = _hashmap_dump_entries_sorted(h, &entries, &n);
+        if (r < 0)
+                return r;
+
+        /* Reuse the array. */
+        FOREACH_ARRAY(e, entries, n)
+                *e = entry_value(h, *(struct hashmap_base_entry**) e);
+
+        *ret = TAKE_PTR(entries);
+        if (ret_n)
+                *ret_n = n;
+        return 0;
+}
diff --git a/src/libnm-systemd-shared/src/basic/hashmap.h b/src/libnm-systemd-shared/src/basic/hashmap.h
index 68d9b81c..49d9d118 100644
--- a/src/libnm-systemd-shared/src/basic/hashmap.h
+++ b/src/libnm-systemd-shared/src/basic/hashmap.h
@@ -39,8 +39,8 @@ typedef struct IteratedCache IteratedCache;   /* Caches the iterated order of on
  * 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 */
+        unsigned idx;         /* index of an entry to be iterated next */
 #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 */
@@ -398,12 +398,36 @@ static inline char** ordered_hashmap_get_strv(OrderedHashmap *h) {
         return _hashmap_get_strv(HASHMAP_BASE(h));
 }
 
+int _hashmap_dump_sorted(HashmapBase *h, void ***ret, size_t *ret_n);
+static inline int hashmap_dump_sorted(Hashmap *h, void ***ret, size_t *ret_n) {
+        return _hashmap_dump_sorted(HASHMAP_BASE(h), ret, ret_n);
+}
+static inline int ordered_hashmap_dump_sorted(OrderedHashmap *h, void ***ret, size_t *ret_n) {
+        return _hashmap_dump_sorted(HASHMAP_BASE(h), ret, ret_n);
+}
+static inline int set_dump_sorted(Set *h, void ***ret, size_t *ret_n) {
+        return _hashmap_dump_sorted(HASHMAP_BASE(h), ret, ret_n);
+}
+
+int _hashmap_dump_keys_sorted(HashmapBase *h, void ***ret, size_t *ret_n);
+static inline int hashmap_dump_keys_sorted(Hashmap *h, void ***ret, size_t *ret_n) {
+        return _hashmap_dump_keys_sorted(HASHMAP_BASE(h), ret, ret_n);
+}
+static inline int ordered_hashmap_dump_keys_sorted(OrderedHashmap *h, void ***ret, size_t *ret_n) {
+        return _hashmap_dump_keys_sorted(HASHMAP_BASE(h), ret, ret_n);
+}
+
 /*
  * 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_BASE_FOREACH(e, h, i) \
+        for (Iterator i = ITERATOR_FIRST; _hashmap_iterate((h), &i, (void**)&(e), NULL); )
+#define HASHMAP_BASE_FOREACH(e, h) \
+        _HASHMAP_BASE_FOREACH(e, h, UNIQ_T(i, UNIQ))
+
 #define _HASHMAP_FOREACH(e, h, i) \
         for (Iterator i = ITERATOR_FIRST; hashmap_iterate((h), &i, (void**)&(e), NULL); )
 #define HASHMAP_FOREACH(e, h) \
@@ -414,6 +438,11 @@ static inline char** ordered_hashmap_get_strv(OrderedHashmap *h) {
 #define ORDERED_HASHMAP_FOREACH(e, h) \
         _ORDERED_HASHMAP_FOREACH(e, h, UNIQ_T(i, UNIQ))
 
+#define _HASHMAP_BASE_FOREACH_KEY(e, k, h, i) \
+        for (Iterator i = ITERATOR_FIRST; _hashmap_iterate((h), &i, (void**)&(e), (const void**) &(k)); )
+#define HASHMAP_BASE_FOREACH_KEY(e, k, h) \
+        _HASHMAP_BASE_FOREACH_KEY(e, k, h, UNIQ_T(i, UNIQ))
+
 #define _HASHMAP_FOREACH_KEY(e, k, h, i) \
         for (Iterator i = ITERATOR_FIRST; hashmap_iterate((h), &i, (void**)&(e), (const void**) &(k)); )
 #define HASHMAP_FOREACH_KEY(e, k, h) \
diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.c b/src/libnm-systemd-shared/src/basic/hexdecoct.c
index d41d2ea0..41228520 100644
--- a/src/libnm-systemd-shared/src/basic/hexdecoct.c
+++ b/src/libnm-systemd-shared/src/basic/hexdecoct.c
@@ -116,7 +116,7 @@ int unhexmem_full(
                 const char *p,
                 size_t l,
                 bool secure,
-                void **ret,
+                void **ret_data,
                 size_t *ret_len) {
 
         _cleanup_free_ uint8_t *buf = NULL;
@@ -157,8 +157,8 @@ int unhexmem_full(
 
         if (ret_len)
                 *ret_len = (size_t) (z - buf);
-        if (ret)
-                *ret = TAKE_PTR(buf);
+        if (ret_data)
+                *ret_data = TAKE_PTR(buf);
 
         return 0;
 }
@@ -557,12 +557,12 @@ int unbase64char(char c) {
 
         offset += '9' - '0' + 1;
 
-        if (c == '+')
+        if (IN_SET(c, '+', '-')) /* Support both the regular and the URL safe character set (see above) */
                 return offset;
 
         offset++;
 
-        if (c == '/')
+        if (IN_SET(c, '/', '_')) /* ditto */
                 return offset;
 
         return -EINVAL;
@@ -772,7 +772,7 @@ int unbase64mem_full(
                 const char *p,
                 size_t l,
                 bool secure,
-                void **ret,
+                void **ret_data,
                 size_t *ret_size) {
 
         _cleanup_free_ uint8_t *buf = NULL;
@@ -860,8 +860,8 @@ int unbase64mem_full(
 
         if (ret_size)
                 *ret_size = (size_t) (z - buf);
-        if (ret)
-                *ret = TAKE_PTR(buf);
+        if (ret_data)
+                *ret_data = TAKE_PTR(buf);
 
         return 0;
 }
diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.h b/src/libnm-systemd-shared/src/basic/hexdecoct.h
index 319b21a1..0a10af3e 100644
--- a/src/libnm-systemd-shared/src/basic/hexdecoct.h
+++ b/src/libnm-systemd-shared/src/basic/hexdecoct.h
@@ -18,9 +18,9 @@ char hexchar(int x) _const_;
 int unhexchar(char c) _const_;
 
 char *hexmem(const void *p, size_t l);
-int unhexmem_full(const char *p, size_t l, bool secure, void **mem, size_t *len);
-static inline int unhexmem(const char *p, size_t l, void **mem, size_t *len) {
-        return unhexmem_full(p, l, false, mem, len);
+int unhexmem_full(const char *p, size_t l, bool secure, void **ret_data, size_t *ret_size);
+static inline int unhexmem(const char *p, void **ret_data, size_t *ret_size) {
+        return unhexmem_full(p, SIZE_MAX, false, ret_data, ret_size);
 }
 
 char base32hexchar(int x) _const_;
@@ -45,9 +45,9 @@ ssize_t base64_append(
                 size_t l,
                 size_t margin,
                 size_t width);
-int unbase64mem_full(const char *p, size_t l, bool secure, void **mem, size_t *len);
-static inline int unbase64mem(const char *p, size_t l, void **mem, size_t *len) {
-        return unbase64mem_full(p, l, false, mem, len);
+int unbase64mem_full(const char *p, size_t l, bool secure, void **ret_data, size_t *ret_size);
+static inline int unbase64mem(const char *p, void **ret_data, size_t *ret_size) {
+        return unbase64mem_full(p, SIZE_MAX, false, ret_data, ret_size);
 }
 
 void hexdump(FILE *f, const void *p, size_t s);
diff --git a/src/libnm-systemd-shared/src/basic/in-addr-util.c b/src/libnm-systemd-shared/src/basic/in-addr-util.c
index b863aec3..310e057a 100644
--- a/src/libnm-systemd-shared/src/basic/in-addr-util.c
+++ b/src/libnm-systemd-shared/src/basic/in-addr-util.c
@@ -93,14 +93,26 @@ bool in6_addr_is_link_local_all_nodes(const struct in6_addr *a) {
                 be32toh(a->s6_addr32[3]) == UINT32_C(0x00000001);
 }
 
+bool in4_addr_is_multicast(const struct in_addr *a) {
+        assert(a);
+
+        return IN_MULTICAST(be32toh(a->s_addr));
+}
+
+bool in6_addr_is_multicast(const struct in6_addr *a) {
+        assert(a);
+
+        return IN6_IS_ADDR_MULTICAST(a);
+}
+
 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));
+                return in4_addr_is_multicast(&u->in);
 
         if (family == AF_INET6)
-                return IN6_IS_ADDR_MULTICAST(&u->in6);
+                return in6_addr_is_multicast(&u->in6);
 
         return -EAFNOSUPPORT;
 }
@@ -736,10 +748,11 @@ int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen)
         }
 }
 
-int in4_addr_prefix_covers(
+int in4_addr_prefix_covers_full(
                 const struct in_addr *prefix,
                 unsigned char prefixlen,
-                const struct in_addr *address) {
+                const struct in_addr *address,
+                unsigned char address_prefixlen) {
 
         struct in_addr masked_prefix, masked_address;
         int r;
@@ -747,6 +760,9 @@ int in4_addr_prefix_covers(
         assert(prefix);
         assert(address);
 
+        if (prefixlen > address_prefixlen)
+                return false;
+
         masked_prefix = *prefix;
         r = in4_addr_mask(&masked_prefix, prefixlen);
         if (r < 0)
@@ -760,10 +776,11 @@ int in4_addr_prefix_covers(
         return in4_addr_equal(&masked_prefix, &masked_address);
 }
 
-int in6_addr_prefix_covers(
+int in6_addr_prefix_covers_full(
                 const struct in6_addr *prefix,
                 unsigned char prefixlen,
-                const struct in6_addr *address) {
+                const struct in6_addr *address,
+                unsigned char address_prefixlen) {
 
         struct in6_addr masked_prefix, masked_address;
         int r;
@@ -771,6 +788,9 @@ int in6_addr_prefix_covers(
         assert(prefix);
         assert(address);
 
+        if (prefixlen > address_prefixlen)
+                return false;
+
         masked_prefix = *prefix;
         r = in6_addr_mask(&masked_prefix, prefixlen);
         if (r < 0)
@@ -784,20 +804,21 @@ int in6_addr_prefix_covers(
         return in6_addr_equal(&masked_prefix, &masked_address);
 }
 
-int in_addr_prefix_covers(
+int in_addr_prefix_covers_full(
                 int family,
                 const union in_addr_union *prefix,
                 unsigned char prefixlen,
-                const union in_addr_union *address) {
+                const union in_addr_union *address,
+                unsigned char address_prefixlen) {
 
         assert(prefix);
         assert(address);
 
         switch (family) {
         case AF_INET:
-                return in4_addr_prefix_covers(&prefix->in, prefixlen, &address->in);
+                return in4_addr_prefix_covers_full(&prefix->in, prefixlen, &address->in, address_prefixlen);
         case AF_INET6:
-                return in6_addr_prefix_covers(&prefix->in6, prefixlen, &address->in6);
+                return in6_addr_prefix_covers_full(&prefix->in6, prefixlen, &address->in6, address_prefixlen);
         default:
                 return -EAFNOSUPPORT;
         }
@@ -922,12 +943,19 @@ int in_addr_prefix_from_string_auto_internal(
 
 }
 
+void in_addr_hash_func(const union in_addr_union *u, int family, struct siphash *state) {
+        assert(u);
+        assert(state);
+
+        siphash24_compress(u->bytes, FAMILY_ADDRESS_SIZE(family), state);
+}
+
 void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state) {
         assert(a);
         assert(state);
 
-        siphash24_compress(&a->family, sizeof(a->family), state);
-        siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state);
+        siphash24_compress_typesafe(a->family, state);
+        in_addr_hash_func(&a->address, a->family, state);
 }
 
 int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y) {
@@ -960,7 +988,7 @@ void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state) {
         assert(addr);
         assert(state);
 
-        siphash24_compress(addr, sizeof(*addr), state);
+        siphash24_compress_typesafe(*addr, state);
 }
 
 int in6_addr_compare_func(const struct in6_addr *a, const struct in6_addr *b) {
diff --git a/src/libnm-systemd-shared/src/basic/in-addr-util.h b/src/libnm-systemd-shared/src/basic/in-addr-util.h
index 200b9eb6..5c820c6e 100644
--- a/src/libnm-systemd-shared/src/basic/in-addr-util.h
+++ b/src/libnm-systemd-shared/src/basic/in-addr-util.h
@@ -40,6 +40,8 @@ static inline bool in_addr_data_is_set(const struct in_addr_data *a) {
         return in_addr_data_is_null(a);
 }
 
+bool in4_addr_is_multicast(const struct in_addr *a);
+bool in6_addr_is_multicast(const struct in6_addr *a);
 int in_addr_is_multicast(int family, const union in_addr_union *u);
 
 bool in4_addr_is_link_local(const struct in_addr *a);
@@ -144,9 +146,18 @@ int in4_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mas
 int in4_addr_mask(struct in_addr *addr, unsigned char prefixlen);
 int in6_addr_mask(struct in6_addr *addr, unsigned char prefixlen);
 int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen);
-int in4_addr_prefix_covers(const struct in_addr *prefix, unsigned char prefixlen, const struct in_addr *address);
-int in6_addr_prefix_covers(const struct in6_addr *prefix, unsigned char prefixlen, const struct in6_addr *address);
-int in_addr_prefix_covers(int family, const union in_addr_union *prefix, unsigned char prefixlen, const union in_addr_union *address);
+int in4_addr_prefix_covers_full(const struct in_addr *prefix, unsigned char prefixlen, const struct in_addr *address, unsigned char address_prefixlen);
+int in6_addr_prefix_covers_full(const struct in6_addr *prefix, unsigned char prefixlen, const struct in6_addr *address, unsigned char address_prefixlen);
+int in_addr_prefix_covers_full(int family, const union in_addr_union *prefix, unsigned char prefixlen, const union in_addr_union *address, unsigned char address_prefixlen);
+static inline int in4_addr_prefix_covers(const struct in_addr *prefix, unsigned char prefixlen, const struct in_addr *address) {
+        return in4_addr_prefix_covers_full(prefix, prefixlen, address, 32);
+}
+static inline int in6_addr_prefix_covers(const struct in6_addr *prefix, unsigned char prefixlen, const struct in6_addr *address) {
+        return in6_addr_prefix_covers_full(prefix, prefixlen, address, 128);
+}
+static inline int in_addr_prefix_covers(int family, const union in_addr_union *prefix, unsigned char prefixlen, const union in_addr_union *address) {
+        return in_addr_prefix_covers_full(family, prefix, prefixlen, address, family == AF_INET ? 32 : family == AF_INET6 ? 128 : 0);
+}
 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);
 
@@ -176,6 +187,7 @@ static inline size_t FAMILY_ADDRESS_SIZE(int family) {
  * See also oss-fuzz#11344. */
 #define IN_ADDR_NULL ((union in_addr_union) { .in6 = {} })
 
+void in_addr_hash_func(const union in_addr_union *u, int family, struct siphash *state);
 void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state);
 int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y);
 void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state);
@@ -186,6 +198,16 @@ extern const struct hash_ops in_addr_data_hash_ops_free;
 extern const struct hash_ops in6_addr_hash_ops;
 extern const struct hash_ops in6_addr_hash_ops_free;
 
+static inline void PTR_TO_IN4_ADDR(const void *p, struct in_addr *ret) {
+        assert(ret);
+        ret->s_addr = (uint32_t) ((uintptr_t) p);
+}
+
+static inline void* IN4_ADDR_TO_PTR(const struct in_addr *a) {
+        assert(a);
+        return (void*) ((uintptr_t) a->s_addr);
+}
+
 #define IPV4_ADDRESS_FMT_STR     "%u.%u.%u.%u"
 #define IPV4_ADDRESS_FMT_VAL(address)              \
         be32toh((address).s_addr) >> 24,           \
diff --git a/src/libnm-systemd-shared/src/basic/inotify-util.c b/src/libnm-systemd-shared/src/basic/inotify-util.c
index 59e03e62..c748bf1b 100644
--- a/src/libnm-systemd-shared/src/basic/inotify-util.c
+++ b/src/libnm-systemd-shared/src/basic/inotify-util.c
@@ -6,6 +6,43 @@
 #include "inotify-util.h"
 #include "stat-util.h"
 
+bool inotify_event_next(
+                union inotify_event_buffer *buffer,
+                size_t size,
+                struct inotify_event **iterator,
+                int log_level) {
+
+        struct inotify_event *e;
+        size_t offset = 0;
+
+        assert(buffer);
+        assert(iterator);
+
+        if (*iterator) {
+                assert((uint8_t*) *iterator >= buffer->raw);
+                offset = (uint8_t*) *iterator - buffer->raw;
+                offset += offsetof(struct inotify_event, name) + (*iterator)->len;
+        }
+
+        if (size == offset)
+                return false; /* reached end of list */
+
+        if (size < offset ||
+            size - offset < offsetof(struct inotify_event, name)) {
+                log_full(log_level, "Received invalid inotify event, ignoring.");
+                return false;
+        }
+
+        e = CAST_ALIGN_PTR(struct inotify_event, buffer->raw + offset);
+        if (size - offset - offsetof(struct inotify_event, name) < e->len) {
+                log_full(log_level, "Received invalid inotify event, ignoring.");
+                return false;
+        }
+
+        *iterator = e;
+        return true;
+}
+
 int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
         int wd, r;
 
diff --git a/src/libnm-systemd-shared/src/basic/inotify-util.h b/src/libnm-systemd-shared/src/basic/inotify-util.h
index 61951ff3..665fdaca 100644
--- a/src/libnm-systemd-shared/src/basic/inotify-util.h
+++ b/src/libnm-systemd-shared/src/basic/inotify-util.h
@@ -10,29 +10,27 @@
 
 #define INOTIFY_EVENT_MAX (offsetof(struct inotify_event, name) + NAME_MAX + 1)
 
-#define _FOREACH_INOTIFY_EVENT(e, buffer, sz, log_level, start, end)    \
-        for (struct inotify_event                                       \
-                     *start = &((buffer).ev),                           \
-                     *end = (struct inotify_event*) ((uint8_t*) start + (sz)), \
-                     *e = start;                                        \
-             (size_t) ((uint8_t*) end - (uint8_t*) e) >= sizeof(struct inotify_event) && \
-             ((size_t) ((uint8_t*) end - (uint8_t*) e) >= sizeof(struct inotify_event) + e->len || \
-              (log_full(log_level, "Received invalid inotify event, ignoring."), false)); \
-             e = (struct inotify_event*) ((uint8_t*) e + sizeof(struct inotify_event) + e->len))
-
-#define _FOREACH_INOTIFY_EVENT_FULL(e, buffer, sz, log_level)           \
-        _FOREACH_INOTIFY_EVENT(e, buffer, sz, log_level, UNIQ_T(start, UNIQ), UNIQ_T(end, UNIQ))
+/* This evaluates arguments multiple times */
+#define FOREACH_INOTIFY_EVENT_FULL(e, buffer, sz, log_level)            \
+        for (struct inotify_event *e = NULL;                            \
+             inotify_event_next(&buffer, sz, &e, log_level); )
 
 #define FOREACH_INOTIFY_EVENT(e, buffer, sz)                    \
-        _FOREACH_INOTIFY_EVENT_FULL(e, buffer, sz, LOG_DEBUG)
+        FOREACH_INOTIFY_EVENT_FULL(e, buffer, sz, LOG_DEBUG)
 
 #define FOREACH_INOTIFY_EVENT_WARN(e, buffer, sz)               \
-        _FOREACH_INOTIFY_EVENT_FULL(e, buffer, sz, LOG_WARNING)
+        FOREACH_INOTIFY_EVENT_FULL(e, buffer, sz, LOG_WARNING)
 
 union inotify_event_buffer {
         struct inotify_event ev;
         uint8_t raw[INOTIFY_EVENT_MAX];
 };
 
+bool inotify_event_next(
+                union inotify_event_buffer *buffer,
+                size_t size,
+                struct inotify_event **iterator,
+                int log_level);
+
 int inotify_add_watch_fd(int fd, int what, uint32_t mask);
 int inotify_add_watch_and_warn(int fd, const char *pathname, uint32_t mask);
diff --git a/src/libnm-systemd-shared/src/basic/io-util.c b/src/libnm-systemd-shared/src/basic/io-util.c
index 0c480091..abe61ed5 100644
--- a/src/libnm-systemd-shared/src/basic/io-util.c
+++ b/src/libnm-systemd-shared/src/basic/io-util.c
@@ -7,7 +7,9 @@
 #include <stdio.h>
 #include <unistd.h>
 
+#include "errno-util.h"
 #include "io-util.h"
+#include "iovec-util.h"
 #include "string-util.h"
 #include "time-util.h"
 
@@ -58,8 +60,7 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) {
 
         assert(fd >= 0);
 
-        /* If called with nbytes == 0, let's call read() at least
-         * once, to validate the operation */
+        /* If called with nbytes == 0, let's call read() at least once, to validate the operation */
 
         if (nbytes > (size_t) SSIZE_MAX)
                 return -EINVAL;
@@ -111,13 +112,29 @@ int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll) {
 }
 
 #if 0 /* NM_IGNORED */
-int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) {
-        const uint8_t *p = ASSERT_PTR(buf);
+int loop_write_full(int fd, const void *buf, size_t nbytes, usec_t timeout) {
+        const uint8_t *p;
+        usec_t end;
+        int r;
 
         assert(fd >= 0);
+        assert(buf || nbytes == 0);
+
+        if (nbytes == 0) {
+                static const dummy_t dummy[0];
+                assert_cc(sizeof(dummy) == 0);
+                p = (const void*) dummy; /* Some valid pointer, in case NULL was specified */
+        } else {
+                if (nbytes == SIZE_MAX)
+                        nbytes = strlen(buf);
+                else if (_unlikely_(nbytes > (size_t) SSIZE_MAX))
+                        return -EINVAL;
+
+                p = buf;
+        }
 
-        if (_unlikely_(nbytes > (size_t) SSIZE_MAX))
-                return -EINVAL;
+        /* When timeout is 0 or USEC_INFINITY this is not used. But we initialize it to a sensible value. */
+        end = timestamp_is_set(timeout) ? usec_add(now(CLOCK_MONOTONIC), timeout) : USEC_INFINITY;
 
         do {
                 ssize_t k;
@@ -127,16 +144,31 @@ int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) {
                         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() */
+                        if (errno != EAGAIN || timeout == 0)
+                                return -errno;
 
-                                (void) fd_wait_for_event(fd, POLLOUT, USEC_INFINITY);
-                                continue;
+                        usec_t wait_for;
+
+                        if (timeout == USEC_INFINITY)
+                                wait_for = USEC_INFINITY;
+                        else {
+                                usec_t t = now(CLOCK_MONOTONIC);
+                                if (t >= end)
+                                        return -ETIME;
+
+                                wait_for = usec_sub_unsigned(end, t);
                         }
 
-                        return -errno;
+                        r = fd_wait_for_event(fd, POLLOUT, wait_for);
+                        if (timeout == USEC_INFINITY || ERRNO_IS_NEG_TRANSIENT(r))
+                                /* If timeout == USEC_INFINITY we knowingly ignore any return value
+                                 * here, and expect that any error/EOF is reported via write() */
+                                continue;
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                return -ETIME;
+                        continue;
                 }
 
                 if (_unlikely_(nbytes > 0 && k == 0)) /* Can't really happen */
@@ -260,7 +292,7 @@ ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) {
                                         return -EIO;
                         }
 
-                        if (lseek(fd, n, SEEK_CUR) == (off_t) -1)
+                        if (lseek(fd, n, SEEK_CUR) < 0)
                                 return -errno;
 
                         q += n;
@@ -281,102 +313,4 @@ ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) {
 
         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 = strjoin(field, value);
-        if (x)
-                iovec[(*n_iovec)++] = IOVEC_MAKE_STRING(x);
-        return x;
-}
-
-char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value) {
-        char *x;
-
-        x = set_iovec_string_field(iovec, n_iovec, field, value);
-        free(value);
-        return x;
-}
-
-struct iovec_wrapper *iovw_new(void) {
-        return malloc0(sizeof(struct iovec_wrapper));
-}
-
-void iovw_free_contents(struct iovec_wrapper *iovw, bool free_vectors) {
-        if (free_vectors)
-                for (size_t i = 0; i < iovw->count; i++)
-                        free(iovw->iovec[i].iov_base);
-
-        iovw->iovec = mfree(iovw->iovec);
-        iovw->count = 0;
-}
-
-struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw) {
-        iovw_free_contents(iovw, true);
-
-        return mfree(iovw);
-}
-
-struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw) {
-        iovw_free_contents(iovw, false);
-
-        return mfree(iovw);
-}
-
-int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) {
-        if (iovw->count >= IOV_MAX)
-                return -E2BIG;
-
-        if (!GREEDY_REALLOC(iovw->iovec, iovw->count + 1))
-                return -ENOMEM;
-
-        iovw->iovec[iovw->count++] = IOVEC_MAKE(data, len);
-        return 0;
-}
-
-int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const char *value) {
-        _cleanup_free_ char *x = NULL;
-        int r;
-
-        x = strjoin(field, value);
-        if (!x)
-                return -ENOMEM;
-
-        r = iovw_put(iovw, x, strlen(x));
-        if (r >= 0)
-                TAKE_PTR(x);
-
-        return r;
-}
-
-int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value) {
-        _cleanup_free_ _unused_ char *free_ptr = value;
-
-        return iovw_put_string_field(iovw, field, value);
-}
-
-void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new) {
-        for (size_t i = 0; i < iovw->count; i++)
-                iovw->iovec[i].iov_base = (char *)iovw->iovec[i].iov_base - old + new;
-}
-
-size_t iovw_size(struct iovec_wrapper *iovw) {
-        size_t n = 0;
-
-        for (size_t i = 0; i < iovw->count; i++)
-                n += iovw->iovec[i].iov_len;
-
-        return n;
-}
-
-void iovec_array_free(struct iovec *iov, size_t n) {
-        if (!iov)
-                return;
-
-        for (size_t i = 0; i < n; i++)
-                free(iov[i].iov_base);
-
-        free(iov);
-}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/io-util.h b/src/libnm-systemd-shared/src/basic/io-util.h
index 3ad82679..e027c1a8 100644
--- a/src/libnm-systemd-shared/src/basic/io-util.h
+++ b/src/libnm-systemd-shared/src/basic/io-util.h
@@ -6,7 +6,6 @@
 #include <stddef.h>
 #include <stdint.h>
 #include <sys/types.h>
-#include <sys/uio.h>
 
 #include "macro.h"
 #include "time-util.h"
@@ -15,7 +14,11 @@ 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 loop_write_full(int fd, const void *buf, size_t nbytes, usec_t timeout);
+static inline int loop_write(int fd, const void *buf, size_t nbytes) {
+        return loop_write_full(fd, buf, nbytes, 0);
+}
 
 int pipe_eof(int fd);
 
@@ -24,38 +27,6 @@ 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 r = 0;
-
-        for (size_t j = 0; j < n; j++)
-                r += i[j].iov_len;
-
-        return r;
-}
-
-static inline bool IOVEC_INCREMENT(struct iovec *i, size_t n, size_t k) {
-        /* Returns true if there is nothing else to send (bytes written cover all of the iovec),
-         * false if there's still work to do. */
-
-        for (size_t j = 0; j < n; j++) {
-                size_t sub;
-
-                if (i[j].iov_len == 0)
-                        continue;
-                if (k == 0)
-                        return false;
-
-                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;
-        }
-
-        assert(k == 0); /* Anything else would mean that we wrote more bytes than available,
-                         * or the kernel reported writing more bytes than sent. */
-        return true;
-}
-
 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. */
@@ -73,40 +44,3 @@ static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) {
         return FILE_SIZE_VALID(l);
 
 }
-
-#define IOVEC_NULL (struct iovec) {}
-#define IOVEC_MAKE(base, len) (struct iovec) { .iov_base = (base), .iov_len = (len) }
-#define IOVEC_MAKE_STRING(string)               \
-        ({                                      \
-                char *_s = (char*) (string);    \
-                IOVEC_MAKE(_s, strlen(_s));     \
-        })
-
-char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value);
-char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value);
-
-struct iovec_wrapper {
-        struct iovec *iovec;
-        size_t count;
-};
-
-struct iovec_wrapper *iovw_new(void);
-struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw);
-struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw);
-void iovw_free_contents(struct iovec_wrapper *iovw, bool free_vectors);
-
-int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len);
-static inline int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) {
-        /* Move data into iovw or free on error */
-        int r = iovw_put(iovw, data, len);
-        if (r < 0)
-                free(data);
-        return r;
-}
-
-int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const char *value);
-int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value);
-void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new);
-size_t iovw_size(struct iovec_wrapper *iovw);
-
-void iovec_array_free(struct iovec *iov, size_t n);
diff --git a/src/libnm-systemd-shared/src/basic/iovec-util.h b/src/libnm-systemd-shared/src/basic/iovec-util.h
new file mode 100644
index 00000000..8cfa5717
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/iovec-util.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include "alloc-util.h"
+#include "macro.h"
+
+/* An iovec pointing to a single NUL byte */
+#define IOVEC_NUL_BYTE (const struct iovec) {                   \
+                .iov_base = (void*) (const uint8_t[1]) { 0 },   \
+                .iov_len = 1,                                   \
+        }
+
+size_t iovec_total_size(const struct iovec *iovec, size_t n);
+
+bool iovec_increment(struct iovec *iovec, size_t n, size_t k);
+
+/* This accepts both const and non-const pointers */
+#define IOVEC_MAKE(base, len)                                           \
+        (struct iovec) {                                                \
+                .iov_base = (void*) (base),                             \
+                .iov_len = (len),                                       \
+        }
+
+static inline struct iovec* iovec_make_string(struct iovec *iovec, const char *s) {
+        assert(iovec);
+        /* We don't use strlen_ptr() here, because we don't want to include string-util.h for now */
+        *iovec = IOVEC_MAKE(s, s ? strlen(s) : 0);
+        return iovec;
+}
+
+#define IOVEC_MAKE_STRING(s) \
+        *iovec_make_string(&(struct iovec) {}, s)
+
+#define CONST_IOVEC_MAKE_STRING(s)              \
+        (const struct iovec) {                  \
+                .iov_base = (char*) s,          \
+                .iov_len = STRLEN(s),           \
+        }
+
+static inline void iovec_done(struct iovec *iovec) {
+        /* A _cleanup_() helper that frees the iov_base in the iovec */
+        assert(iovec);
+
+        iovec->iov_base = mfree(iovec->iov_base);
+        iovec->iov_len = 0;
+}
+
+static inline void iovec_done_erase(struct iovec *iovec) {
+        assert(iovec);
+
+        iovec->iov_base = erase_and_free(iovec->iov_base);
+        iovec->iov_len = 0;
+}
+
+static inline bool iovec_is_set(const struct iovec *iovec) {
+        /* Checks if the iovec points to a non-empty chunk of memory */
+        return iovec && iovec->iov_len > 0 && iovec->iov_base;
+}
+
+static inline bool iovec_is_valid(const struct iovec *iovec) {
+        /* Checks if the iovec is either NULL, empty or points to a valid bit of memory */
+        return !iovec || (iovec->iov_base || iovec->iov_len == 0);
+}
+
+char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value);
+char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value);
+
+void iovec_array_free(struct iovec *iovec, size_t n_iovec);
+
+static inline int iovec_memcmp(const struct iovec *a, const struct iovec *b) {
+
+        if (a == b)
+                return 0;
+
+        return memcmp_nn(a ? a->iov_base : NULL,
+                         a ? a->iov_len : 0,
+                         b ? b->iov_base : NULL,
+                         b ? b->iov_len : 0);
+}
+
+static inline struct iovec *iovec_memdup(const struct iovec *source, struct iovec *ret) {
+        assert(ret);
+
+        if (!iovec_is_set(source))
+                *ret = (struct iovec) {};
+        else {
+                void *p = memdup(source->iov_base, source->iov_len);
+                if (!p)
+                        return NULL;
+
+                *ret = IOVEC_MAKE(p, source->iov_len);
+        }
+
+        return ret;
+}
diff --git a/src/libnm-systemd-shared/src/basic/list.h b/src/libnm-systemd-shared/src/basic/list.h
index e4e5dff3..10e69541 100644
--- a/src/libnm-systemd-shared/src/basic/list.h
+++ b/src/libnm-systemd-shared/src/basic/list.h
@@ -192,6 +192,18 @@
                 _p;                                                     \
         })
 
+#define LIST_CLEAR(name, head, free_func)       \
+        _LIST_CLEAR(name, head, free_func, UNIQ_T(elem, UNIQ))
+
+/* Clear the list, destroying each element with free_func */
+#define _LIST_CLEAR(name, head, free_func, elem)        \
+        ({                                              \
+                typeof(head) elem;                      \
+                while ((elem = LIST_POP(name, head)))   \
+                        free_func(elem);                \
+                head;                                   \
+        })
+
 /* Now include "macro.h", because we want our definition of assert() which the macros above use. We include
  * it down here instead of up top, since macro.h pulls in log.h which in turn needs our own definitions. */
 #include "macro.h"
diff --git a/src/libnm-systemd-shared/src/basic/locale-util.c b/src/libnm-systemd-shared/src/basic/locale-util.c
index 2f3701eb..95648775 100644
--- a/src/libnm-systemd-shared/src/basic/locale-util.c
+++ b/src/libnm-systemd-shared/src/basic/locale-util.c
@@ -19,6 +19,7 @@
 #include "fileio.h"
 #include "hashmap.h"
 #include "locale-util.h"
+#include "missing_syscall.h"
 #include "path-util.h"
 #include "set.h"
 #include "string-table.h"
@@ -223,7 +224,7 @@ int get_locales(char ***ret) {
         locales = set_free(locales);
 
         r = getenv_bool("SYSTEMD_LIST_NON_UTF8_LOCALES");
-        if (r == -ENXIO || r == 0) {
+        if (IN_SET(r, -ENXIO, 0)) {
                 char **a, **b;
 
                 /* Filter out non-UTF-8 locales, because it's 2019, by default */
@@ -262,7 +263,10 @@ bool locale_is_valid(const char *name) {
         if (!filename_is_valid(name))
                 return false;
 
-        if (!string_is_safe(name))
+        /* Locales look like: ll_CC.ENC@variant, where ll and CC are alphabetic, ENC is alphanumeric with
+         * dashes, and variant seems to be alphabetic.
+         * See: https://www.gnu.org/software/gettext/manual/html_node/Locale-Names.html */
+        if (!in_charset(name, ALPHANUMERICAL "_.-@"))
                 return false;
 
         return true;
@@ -282,11 +286,6 @@ int locale_is_installed(const char *name) {
 
         return true;
 }
-
-void init_gettext(void) {
-        setlocale(LC_ALL, "");
-        textdomain(GETTEXT_PACKAGE);
-}
 #endif /* NM_IGNORED */
 
 bool is_locale_utf8(void) {
@@ -300,13 +299,19 @@ bool is_locale_utf8(void) {
         if (cached_answer >= 0)
                 goto out;
 
-        r = getenv_bool_secure("SYSTEMD_UTF8");
+        r = secure_getenv_bool("SYSTEMD_UTF8");
         if (r >= 0) {
                 cached_answer = r;
                 goto out;
         } else if (r != -ENXIO)
                 log_debug_errno(r, "Failed to parse $SYSTEMD_UTF8, ignoring: %m");
 
+        /* This function may be called from libsystemd, and setlocale() is not thread safe. Assuming yes. */
+        if (gettid() != raw_getpid()) {
+                cached_answer = true;
+                goto out;
+        }
+
         if (!setlocale(LC_ALL, "")) {
                 cached_answer = true;
                 goto out;
@@ -345,11 +350,7 @@ out:
 
 #if 0 /* NM_IGNORED */
 void locale_variables_free(char *l[_VARIABLE_LC_MAX]) {
-        if (!l)
-                return;
-
-        for (LocaleVariable i = 0; i < _VARIABLE_LC_MAX; i++)
-                l[i] = mfree(l[i]);
+        free_many_charp(l, _VARIABLE_LC_MAX);
 }
 
 void locale_variables_simplify(char *l[_VARIABLE_LC_MAX]) {
diff --git a/src/libnm-systemd-shared/src/basic/locale-util.h b/src/libnm-systemd-shared/src/basic/locale-util.h
index 8990cb6a..81fe8d10 100644
--- a/src/libnm-systemd-shared/src/basic/locale-util.h
+++ b/src/libnm-systemd-shared/src/basic/locale-util.h
@@ -33,9 +33,8 @@ int get_locales(char ***l);
 bool locale_is_valid(const char *name);
 int locale_is_installed(const char *name);
 
-#define _(String) gettext(String)
+#define _(String) dgettext(GETTEXT_PACKAGE, String)
 #define N_(String) String
-void init_gettext(void);
 
 bool is_locale_utf8(void);
 
diff --git a/src/libnm-systemd-shared/src/basic/lock-util.h b/src/libnm-systemd-shared/src/basic/lock-util.h
index e7744476..91b332f8 100644
--- a/src/libnm-systemd-shared/src/basic/lock-util.h
+++ b/src/libnm-systemd-shared/src/basic/lock-util.h
@@ -34,9 +34,12 @@ void unposix_unlockpp(int **fd);
         _cleanup_(unposix_unlockpp) _unused_ int *CONCATENATE(_cleanup_unposix_unlock_, UNIQ) = &(fd)
 
 typedef enum LockType {
+        LOCK_NONE, /* Don't lock the file descriptor. Useful if you need to conditionally lock a file. */
         LOCK_BSD,
         LOCK_POSIX,
         LOCK_UNPOSIX,
 } LockType;
 
 int lock_generic(int fd, LockType type, int operation);
+
+int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeout);
diff --git a/src/libnm-systemd-shared/src/basic/log.h b/src/libnm-systemd-shared/src/basic/log.h
index eb7b51cb..1b5bc655 100644
--- a/src/libnm-systemd-shared/src/basic/log.h
+++ b/src/libnm-systemd-shared/src/basic/log.h
@@ -467,6 +467,9 @@ void log_set_open_when_needed(bool b);
  * stderr, the console or kmsg */
 void log_set_prohibit_ipc(bool b);
 
+void log_set_assert_return_is_critical(bool b);
+bool log_get_assert_return_is_critical(void) _pure_;
+
 int log_dup_console(void);
 
 #if 0 /* NM_IGNORED */
@@ -521,7 +524,7 @@ typedef struct LogRateLimit {
         RateLimit ratelimit;
 } LogRateLimit;
 
-#define log_ratelimit_internal(_level, _error, _ratelimit, _format, _file, _line, _func, ...)        \
+#define log_ratelimit_internal(_level, _error, _ratelimit, _file, _line, _func, _format, ...)        \
 ({                                                                              \
         int _log_ratelimit_error = (_error);                                    \
         int _log_ratelimit_level = (_level);                                    \
@@ -545,7 +548,7 @@ typedef struct LogRateLimit {
         ({                                                              \
                 int _level = (level), _e = (error);                     \
                 _e = (log_get_max_level() >= LOG_PRI(_level))           \
-                        ? log_ratelimit_internal(_level, _e, _ratelimit, format, PROJECT_FILE, __LINE__, __func__, ##__VA_ARGS__) \
+                        ? log_ratelimit_internal(_level, _e, _ratelimit, PROJECT_FILE, __LINE__, __func__, format, ##__VA_ARGS__) \
                         : -ERRNO_VALUE(_e);                             \
                 _e < 0 ? _e : -ESTRPIPE;                                \
         })
diff --git a/src/libnm-systemd-shared/src/basic/macro.h b/src/libnm-systemd-shared/src/basic/macro.h
index ce7350cb..eec8cba6 100644
--- a/src/libnm-systemd-shared/src/basic/macro.h
+++ b/src/libnm-systemd-shared/src/basic/macro.h
@@ -14,7 +14,7 @@
 
 /* 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
+ * our primary use case 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__))
@@ -84,7 +84,7 @@
 #define REENABLE_WARNING
 #endif
 
-/* automake test harness */
+/* test harness */
 #define EXIT_TEST_SKIP 77
 
 /* builtins */
@@ -96,6 +96,13 @@
 #error "neither int nor long are four bytes long?!?"
 #endif
 
+static inline uint64_t u64_multiply_safe(uint64_t a, uint64_t b) {
+        if (_unlikely_(a != 0 && b > (UINT64_MAX / a)))
+                return 0; /* overflow */
+
+        return a * b;
+}
+
 /* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */
 static inline unsigned long ALIGN_POWER2(unsigned long u) {
 
@@ -198,7 +205,7 @@ static inline int __coverity_check_and_return__(int condition) {
 /* We override the glibc assert() here. */
 #undef assert
 #ifdef NDEBUG
-#define assert(expr) do {} while (false)
+#define assert(expr) ({ if (!(expr)) __builtin_unreachable(); })
 #else
 #define assert(expr) assert_message_se(expr, #expr)
 #endif
@@ -304,12 +311,6 @@ static inline int __coverity_check_and_return__(int condition) {
 /* Pointers range from NULL to POINTER_MAX */
 #define POINTER_MAX ((void*) UINTPTR_MAX)
 
-/* Iterates through a specified list of pointers. Accepts NULL pointers, but uses POINTER_MAX as internal marker for EOL. */
-#define FOREACH_POINTER(p, x, ...)                                                       \
-        for (typeof(p) *_l = (typeof(p)[]) { ({ p = x; }), ##__VA_ARGS__, POINTER_MAX }; \
-             p != (typeof(p)) POINTER_MAX;                                               \
-             p = *(++_l))
-
 #define _FOREACH_ARRAY(i, array, num, m, end)                           \
         for (typeof(array[0]) *i = (array), *end = ({                   \
                                 typeof(num) m = (num);                  \
@@ -319,31 +320,6 @@ static inline int __coverity_check_and_return__(int condition) {
 #define FOREACH_ARRAY(i, array, num)                                    \
         _FOREACH_ARRAY(i, array, num, UNIQ_T(m, UNIQ), UNIQ_T(end, UNIQ))
 
-#define DEFINE_TRIVIAL_DESTRUCTOR(name, type, func)             \
-        static inline void name(type *p) {                      \
-                func(p);                                        \
-        }
-
-/* When func() returns the void value (NULL, -1, …) of the appropriate type */
-#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func)                 \
-        static inline void func##p(type *p) {                   \
-                if (*p)                                         \
-                        *p = func(*p);                          \
-        }
-
-/* When func() doesn't return the appropriate type, set variable to empty afterwards.
- * The func() may be provided by a dynamically loaded shared library, hence add an assertion. */
-#define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(type, func, empty)     \
-        static inline void func##p(type *p) {                   \
-                if (*p != (empty)) {                            \
-                        DISABLE_WARNING_ADDRESS;                \
-                        assert(func);                           \
-                        REENABLE_WARNING;                       \
-                        func(*p);                               \
-                        *p = (empty);                           \
-                }                                               \
-        }
-
 #define _DEFINE_TRIVIAL_REF_FUNC(type, name, scope)             \
         scope type *name##_ref(type *p) {                       \
                 if (!p)                                         \
@@ -443,13 +419,13 @@ assert_cc(sizeof(dummy_t) == 0);
                 _q && _q > (base) ? &_q[-1] : NULL;      \
         })
 
-/* Iterate through each variadic arg. All must be the same type as 'entry' or must be implicitly
+/* Iterate through each argument passed. All must be the same type as 'entry' or must be implicitly
  * convertible. The iteration variable 'entry' must already be defined. */
-#define VA_ARGS_FOREACH(entry, ...)                                     \
-        _VA_ARGS_FOREACH(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), ##__VA_ARGS__)
-#define _VA_ARGS_FOREACH(entry, _entries_, _current_, ...)         \
-        for (typeof(entry) _entries_[] = { __VA_ARGS__ }, *_current_ = _entries_; \
-             ((long)(_current_ - _entries_) < (long)ELEMENTSOF(_entries_)) && ({ entry = *_current_; true; }); \
+#define FOREACH_ARGUMENT(entry, ...)                                     \
+        _FOREACH_ARGUMENT(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), UNIQ_T(_va_sentinel_, UNIQ), ##__VA_ARGS__)
+#define _FOREACH_ARGUMENT(entry, _entries_, _current_, _va_sentinel_, ...)      \
+        for (typeof(entry) _va_sentinel_[1] = {}, _entries_[] = { __VA_ARGS__ __VA_OPT__(,) _va_sentinel_[0] }, *_current_ = _entries_; \
+             ((long)(_current_ - _entries_) < (long)(ELEMENTSOF(_entries_) - 1)) && ({ entry = *_current_; true; }); \
              _current_++)
 
 #include "log.h"
diff --git a/src/libnm-systemd-shared/src/basic/memory-util.c b/src/libnm-systemd-shared/src/basic/memory-util.c
index c1e0a742..789e96a9 100644
--- a/src/libnm-systemd-shared/src/basic/memory-util.c
+++ b/src/libnm-systemd-shared/src/basic/memory-util.c
@@ -41,3 +41,19 @@ bool memeqbyte(uint8_t byte, const void *data, size_t length) {
         /* Now we know first 16 bytes match, memcmp() with self.  */
         return memcmp(data, p + 16, length) == 0;
 }
+
+void *memdup_reverse(const void *mem, size_t size) {
+        assert(mem);
+        assert(size != 0);
+
+        void *p = malloc(size);
+        if (!p)
+                return NULL;
+
+        uint8_t *p_dst = p;
+        const uint8_t *p_src = mem;
+        for (size_t i = 0, k = size; i < size; i++, k--)
+                p_dst[i] = p_src[k-1];
+
+        return p;
+}
diff --git a/src/libnm-systemd-shared/src/basic/memory-util.h b/src/libnm-systemd-shared/src/basic/memory-util.h
index d26a0918..294aed67 100644
--- a/src/libnm-systemd-shared/src/basic/memory-util.h
+++ b/src/libnm-systemd-shared/src/basic/memory-util.h
@@ -12,9 +12,12 @@
 #include "memory-util-fundamental.h"
 
 size_t page_size(void) _pure_;
-#define PAGE_ALIGN(l) ALIGN_TO((l), page_size())
-#define PAGE_ALIGN_DOWN(l) ((l) & ~(page_size() - 1))
-#define PAGE_OFFSET(l) ((l) & (page_size() - 1))
+#define PAGE_ALIGN(l)          ALIGN_TO(l, page_size())
+#define PAGE_ALIGN_U64(l)      ALIGN_TO_U64(l, page_size())
+#define PAGE_ALIGN_DOWN(l)     ALIGN_DOWN(l, page_size())
+#define PAGE_ALIGN_DOWN_U64(l) ALIGN_DOWN_U64(l, page_size())
+#define PAGE_OFFSET(l)         ALIGN_OFFSET(l, page_size())
+#define PAGE_OFFSET_U64(l)     ALIGN_OFFSET_U64(l, page_size())
 
 /* 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) {
@@ -47,13 +50,6 @@ static inline int memcmp_nn(const void *s1, size_t n1, const void *s2, size_t n2
             ?: CMP(n1, n2);
 }
 
-#define memzero(x,l)                                            \
-        ({                                                      \
-                size_t _l_ = (l);                               \
-                if (_l_ > 0)                                    \
-                        memset(x, 0, _l_);                      \
-        })
-
 #define zero(x) (memzero(&(x), sizeof(x)))
 
 bool memeqbyte(uint8_t byte, const void *data, size_t length);
@@ -112,36 +108,5 @@ static inline void erase_char(char *p) {
         explicit_bzero_safe(p, sizeof(char));
 }
 
-/* An automatic _cleanup_-like logic for destroy arrays (i.e. pointers + size) when leaving scope */
-typedef struct ArrayCleanup {
-        void **parray;
-        size_t *pn;
-        free_array_func_t pfunc;
-} ArrayCleanup;
-
-static inline void array_cleanup(const ArrayCleanup *c) {
-        assert(c);
-
-        assert(!c->parray == !c->pn);
-
-        if (!c->parray)
-                return;
-
-        if (*c->parray) {
-                assert(c->pfunc);
-                c->pfunc(*c->parray, *c->pn);
-                *c->parray = NULL;
-        }
-
-        *c->pn = 0;
-}
-
-#define CLEANUP_ARRAY(array, n, func)                                   \
-        _cleanup_(array_cleanup) _unused_ const ArrayCleanup CONCATENATE(_cleanup_array_, UNIQ) = { \
-                .parray = (void**) &(array),                            \
-                .pn = &(n),                                             \
-                .pfunc = (free_array_func_t) ({                         \
-                                void (*_f)(typeof(array[0]) *a, size_t b) = func; \
-                                _f;                                     \
-                        }),                                             \
-        }
+/* Makes a copy of the buffer with reversed order of bytes */
+void *memdup_reverse(const void *mem, size_t size);
diff --git a/src/libnm-systemd-shared/src/basic/missing_fcntl.h b/src/libnm-systemd-shared/src/basic/missing_fcntl.h
index 24b2dc31..3c85befd 100644
--- a/src/libnm-systemd-shared/src/basic/missing_fcntl.h
+++ b/src/libnm-systemd-shared/src/basic/missing_fcntl.h
@@ -69,9 +69,26 @@
 
 /* So O_LARGEFILE is generally implied by glibc, and defined to zero hence, because we only build in LFS
  * mode. However, when invoking fcntl(F_GETFL) the flag is ORed into the result anyway — glibc does not mask
- * it away. Which sucks. Let's define the actual value here, so that we can mask it ourselves. */
+ * it away. Which sucks. Let's define the actual value here, so that we can mask it ourselves.
+ *
+ * The precise definition is arch specific, so we use the values defined in the kernel (note that some
+ * are hexa and others are octal; duplicated as-is from the kernel definitions):
+ * - alpha, arm, arm64, m68k, mips, parisc, powerpc, sparc: each has a specific value;
+ * - others: they use the "generic" value (defined in include/uapi/asm-generic/fcntl.h) */
 #if O_LARGEFILE != 0
 #define RAW_O_LARGEFILE O_LARGEFILE
 #else
-#define RAW_O_LARGEFILE 0100000
+#if defined(__alpha__) || defined(__arm__) || defined(__aarch64__) || defined(__m68k__)
+#define RAW_O_LARGEFILE 0400000
+#elif defined(__mips__)
+#define RAW_O_LARGEFILE 0x2000
+#elif defined(__parisc__) || defined(__hppa__)
+#define RAW_O_LARGEFILE 000004000
+#elif defined(__powerpc__)
+#define RAW_O_LARGEFILE 0200000
+#elif defined(__sparc__)
+#define RAW_O_LARGEFILE 0x40000
+#else
+#define RAW_O_LARGEFILE 00100000
+#endif
 #endif
diff --git a/src/libnm-systemd-shared/src/basic/missing_socket.h b/src/libnm-systemd-shared/src/basic/missing_socket.h
index a4f6836f..ffda7cc6 100644
--- a/src/libnm-systemd-shared/src/basic/missing_socket.h
+++ b/src/libnm-systemd-shared/src/basic/missing_socket.h
@@ -7,7 +7,6 @@
 #if HAVE_LINUX_VM_SOCKETS_H
 #include <linux/vm_sockets.h>
 #else
-#define VMADDR_CID_ANY -1U
 struct sockaddr_vm {
         unsigned short svm_family;
         unsigned short svm_reserved1;
@@ -22,6 +21,26 @@ struct sockaddr_vm {
 #endif /* !HAVE_LINUX_VM_SOCKETS_H */
 #endif /* NM_IGNORED */
 
+#ifndef VMADDR_CID_ANY
+#define VMADDR_CID_ANY -1U
+#endif
+
+#ifndef VMADDR_CID_HYPERVISOR
+#define VMADDR_CID_HYPERVISOR 0U
+#endif
+
+#ifndef VMADDR_CID_LOCAL
+#define VMADDR_CID_LOCAL 1U
+#endif
+
+#ifndef VMADDR_CID_HOST
+#define VMADDR_CID_HOST 2U
+#endif
+
+#ifndef VMADDR_PORT_ANY
+#define VMADDR_PORT_ANY -1U
+#endif
+
 #ifndef AF_VSOCK
 #define AF_VSOCK 40
 #endif
@@ -34,6 +53,10 @@ struct sockaddr_vm {
 #define SO_PEERGROUPS 59
 #endif
 
+#ifndef SO_PEERPIDFD
+#define SO_PEERPIDFD 77
+#endif
+
 #ifndef SO_BINDTOIFINDEX
 #define SO_BINDTOIFINDEX 62
 #endif
diff --git a/src/libnm-systemd-shared/src/basic/missing_stat.h b/src/libnm-systemd-shared/src/basic/missing_stat.h
index 8b39d4f4..18a15ab0 100644
--- a/src/libnm-systemd-shared/src/basic/missing_stat.h
+++ b/src/libnm-systemd-shared/src/basic/missing_stat.h
@@ -9,7 +9,7 @@
 #include <linux/stat.h>
 #endif
 
-/* Thew newest definition we are aware of (fa2fcf4f1df1559a0a4ee0f46915b496cc2ebf60; 5.8) */
+/* The newest definition we are aware of (fa2fcf4f1df1559a0a4ee0f46915b496cc2ebf60; 5.8) */
 #define STATX_DEFINITION {                      \
         __u32 stx_mask;                         \
         __u32 stx_blksize;                      \
diff --git a/src/libnm-systemd-shared/src/basic/missing_syscall.h b/src/libnm-systemd-shared/src/basic/missing_syscall.h
index 610a7cef..149c5b48 100644
--- a/src/libnm-systemd-shared/src/basic/missing_syscall.h
+++ b/src/libnm-systemd-shared/src/basic/missing_syscall.h
@@ -34,6 +34,21 @@
 
 /* ======================================================================= */
 
+#if !HAVE_FCHMODAT2
+static inline int missing_fchmodat2(int dirfd, const char *path, mode_t mode, int flags) {
+#  ifdef __NR_fchmodat2
+        return syscall(__NR_fchmodat2, dirfd, path, mode, flags);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define fchmodat2 missing_fchmodat2
+#endif
+
+/* ======================================================================= */
+
 #if !HAVE_PIVOT_ROOT
 static inline int missing_pivot_root(const char *new_root, const char *put_old) {
         return syscall(__NR_pivot_root, new_root, put_old);
@@ -404,23 +419,14 @@ static inline int missing_execveat(int dirfd, const char *pathname,
 /* ======================================================================= */
 
 #if !HAVE_CLOSE_RANGE
-static inline int missing_close_range(int first_fd, int end_fd, unsigned flags) {
+static inline int missing_close_range(unsigned first_fd, unsigned end_fd, unsigned flags) {
 #  ifdef __NR_close_range
         /* Kernel-side the syscall expects fds as unsigned integers (just like close() actually), while
-         * userspace exclusively uses signed integers for fds. We don't know just yet how glibc is going to
-         * wrap this syscall, but let's assume it's going to be similar to what they do for close(),
-         * i.e. make the same unsigned → signed type change from the raw kernel syscall compared to the
-         * userspace wrapper. There's only one caveat for this: unlike for close() there's the special
-         * UINT_MAX fd value for the 'end_fd' argument. Let's safely map that to -1 here. And let's refuse
-         * any other negative values. */
-        if ((first_fd < 0) || (end_fd < 0 && end_fd != -1)) {
-                errno = -EBADF;
-                return -1;
-        }
-
+         * userspace exclusively uses signed integers for fds. glibc chose to expose it 1:1 however, hence we
+         * do so here too, even if we end up passing signed fds to it most of the time. */
         return syscall(__NR_close_range,
-                       (unsigned) first_fd,
-                       end_fd == -1 ? UINT_MAX : (unsigned) end_fd, /* Of course, the compiler should figure out that this is the identity mapping IRL */
+                       first_fd,
+                       end_fd,
                        flags);
 #  else
         errno = ENOSYS;
@@ -546,6 +552,10 @@ static inline int missing_open_tree(
 
 /* ======================================================================= */
 
+#ifndef MOVE_MOUNT_BENEATH
+#define MOVE_MOUNT_BENEATH 0x00000200
+#endif
+
 #if !HAVE_MOVE_MOUNT
 
 #ifndef MOVE_MOUNT_F_EMPTY_PATH
@@ -662,3 +672,17 @@ static inline ssize_t missing_getdents64(int fd, void *buffer, size_t length) {
 #  define getdents64 missing_getdents64
 #endif
 #endif /* NM_IGNORED */
+
+/* ======================================================================= */
+
+/* glibc does not provide clone() on ia64, only clone2(). Not only that, but it also doesn't provide a
+ * prototype, only the symbol in the shared library (it provides a prototype for clone(), but not the
+ * symbol in the shared library). */
+#if defined(__ia64__)
+int __clone2(int (*fn)(void *), void *stack_base, size_t stack_size, int flags, void *arg);
+#define HAVE_CLONE 0
+#else
+/* We know that everywhere else clone() is available, so we don't bother with a meson check (that takes time
+ * at build time) and just define it. Once the kernel drops ia64 support, we can drop this too. */
+#define HAVE_CLONE 1
+#endif
diff --git a/src/libnm-systemd-shared/src/basic/namespace-util.h b/src/libnm-systemd-shared/src/basic/namespace-util.h
new file mode 100644
index 00000000..34cbec3f
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/namespace-util.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/types.h>
+
+typedef enum NamespaceType {
+        NAMESPACE_CGROUP,
+        NAMESPACE_IPC,
+        NAMESPACE_NET,
+        NAMESPACE_MOUNT,
+        NAMESPACE_PID,
+        NAMESPACE_USER,
+        NAMESPACE_UTS,
+        NAMESPACE_TIME,
+        _NAMESPACE_TYPE_MAX,
+        _NAMESPACE_TYPE_INVALID = -EINVAL,
+} NamespaceType;
+
+extern const struct namespace_info {
+        const char *proc_name;
+        const char *proc_path;
+        unsigned int clone_flag;
+} namespace_info[_NAMESPACE_TYPE_MAX + 1];
+
+int namespace_open(
+                pid_t pid,
+                int *ret_pidns_fd,
+                int *ret_mntns_fd,
+                int *ret_netns_fd,
+                int *ret_userns_fd,
+                int *ret_root_fd);
+int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd);
+
+int fd_is_ns(int fd, unsigned long nsflag);
+
+int detach_mount_namespace(void);
+
+static inline bool userns_shift_range_valid(uid_t shift, uid_t range) {
+        /* Checks that the specified userns range makes sense, i.e. contains at least one UID, and the end
+         * doesn't overflow uid_t. */
+
+        assert_cc((uid_t) -1 > 0); /* verify that uid_t is unsigned */
+
+        if (range <= 0)
+                return false;
+
+        if (shift > (uid_t) -1 - range)
+                return false;
+
+        return true;
+}
+
+int userns_acquire(const char *uid_map, const char *gid_map);
+int netns_acquire(void);
+int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type);
+
+int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_range);
diff --git a/src/libnm-systemd-shared/src/basic/ordered-set.c b/src/libnm-systemd-shared/src/basic/ordered-set.c
index f402bb5b..ae50070f 100644
--- a/src/libnm-systemd-shared/src/basic/ordered-set.c
+++ b/src/libnm-systemd-shared/src/basic/ordered-set.c
@@ -93,13 +93,16 @@ void ordered_set_print(FILE *f, const char *field, OrderedSet *s) {
         bool space = false;
         char *p;
 
+        assert(f);
+        assert(field);
+
         if (ordered_set_isempty(s))
                 return;
 
         fputs(field, f);
 
         ORDERED_SET_FOREACH(p, s)
-                fputs_with_space(f, p, NULL, &space);
+                fputs_with_separator(f, p, NULL, &space);
 
         fputc('\n', f);
 }
diff --git a/src/libnm-systemd-shared/src/basic/parse-util.c b/src/libnm-systemd-shared/src/basic/parse-util.c
index 2b22039c..34a5375f 100644
--- a/src/libnm-systemd-shared/src/basic/parse-util.c
+++ b/src/libnm-systemd-shared/src/basic/parse-util.c
@@ -47,6 +47,24 @@ int parse_boolean(const char *v) {
 }
 
 #if 0 /* NM_IGNORED */
+int parse_tristate_full(const char *v, const char *third, int *ret) {
+        int r;
+
+        if (isempty(v) || streq_ptr(v, third)) { /* Empty string is always taken as the third/invalid/auto state */
+                if (ret)
+                        *ret = -1;
+        } else {
+                r = parse_boolean(v);
+                if (r < 0)
+                        return r;
+
+                if (ret)
+                        *ret = r;
+        }
+
+        return 0;
+}
+
 int parse_pid(const char *s, pid_t* ret_pid) {
         unsigned long ul = 0;
         pid_t pid;
@@ -110,8 +128,7 @@ int parse_ifindex(const char *s) {
 
 #if 0 /* NM_IGNORED */
 int parse_mtu(int family, const char *s, uint32_t *ret) {
-        uint64_t u;
-        size_t m;
+        uint64_t u, m;
         int r;
 
         r = parse_size(s, 1024, &u);
@@ -121,10 +138,16 @@ int parse_mtu(int family, const char *s, uint32_t *ret) {
         if (u > UINT32_MAX)
                 return -ERANGE;
 
-        if (family == AF_INET6)
+        switch (family) {
+        case AF_INET:
+                m = IPV4_MIN_MTU; /* This is 68 */
+                break;
+        case 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 */
+                break;
+        default:
+                m = 0;
+        }
 
         if (u < m)
                 return -ERANGE;
@@ -431,6 +454,21 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
         return 0;
 }
 
+int safe_atou_bounded(const char *s, unsigned min, unsigned max, unsigned *ret) {
+        unsigned v;
+        int r;
+
+        r = safe_atou(s, &v);
+        if (r < 0)
+                return r;
+
+        if (v < min || v > max)
+                return -ERANGE;
+
+        *ret = v;
+        return 0;
+}
+
 int safe_atoi(const char *s, int *ret_i) {
         unsigned base = 0;
         char *x = NULL;
@@ -660,7 +698,7 @@ int parse_ip_port(const char *s, uint16_t *ret) {
         return 0;
 }
 
-int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high) {
+int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high, bool allow_zero) {
         unsigned l, h;
         int r;
 
@@ -668,7 +706,10 @@ int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high) {
         if (r < 0)
                 return r;
 
-        if (l <= 0 || l > 65535 || h <= 0 || h > 65535)
+        if (l > 65535 || h > 65535)
+                return -EINVAL;
+
+        if (!allow_zero && (l == 0 || h == 0))
                 return -EINVAL;
 
         if (h < l)
@@ -754,4 +795,23 @@ int parse_loadavg_fixed_point(const char *s, loadavg_t *ret) {
 
         return store_loadavg_fixed_point(i, f, ret);
 }
+
+/* Limitations are described in https://www.netfilter.org/projects/nftables/manpage.html and
+ * https://bugzilla.netfilter.org/show_bug.cgi?id=1175 */
+bool nft_identifier_valid(const char *id) {
+        if (!id)
+                return false;
+
+        size_t len = strlen(id);
+        if (len == 0 || len > 31)
+                return false;
+
+        if (!ascii_isalpha(id[0]))
+                return false;
+
+        for (size_t i = 1; i < len; i++)
+                if (!ascii_isalpha(id[i]) && !ascii_isdigit(id[i]) && !IN_SET(id[i], '/', '\\', '_', '.'))
+                        return false;
+        return true;
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/parse-util.h b/src/libnm-systemd-shared/src/basic/parse-util.h
index c480407c..c12988ef 100644
--- a/src/libnm-systemd-shared/src/basic/parse-util.h
+++ b/src/libnm-systemd-shared/src/basic/parse-util.h
@@ -12,6 +12,10 @@
 typedef unsigned long loadavg_t;
 
 int parse_boolean(const char *v) _pure_;
+int parse_tristate_full(const char *v, const char *third, int *ret);
+static inline int parse_tristate(const char *v, int *ret) {
+        return parse_tristate_full(v, NULL, 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);
@@ -30,11 +34,12 @@ int parse_fd(const char *t);
 #define SAFE_ATO_MASK_FLAGS(base) ((base) & ~SAFE_ATO_ALL_FLAGS)
 
 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_atou_bounded(const char *s, unsigned min, unsigned max, unsigned *ret);
+
 int safe_atoi(const char *s, int *ret_i);
 int safe_atolli(const char *s, long long int *ret_i);
 
@@ -134,7 +139,7 @@ int parse_fractional_part_u(const char **s, size_t digits, unsigned *res);
 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_ip_port_range(const char *s, uint16_t *low, uint16_t *high, bool allow_zero);
 
 int parse_ip_prefix_length(const char *s, int *ret);
 
@@ -152,3 +157,5 @@ int parse_oom_score_adjust(const char *s, int *ret);
  * to a loadavg_t. */
 int store_loadavg_fixed_point(unsigned long i, unsigned long f, loadavg_t *ret);
 int parse_loadavg_fixed_point(const char *s, loadavg_t *ret);
+
+bool nft_identifier_valid(const char *id);
diff --git a/src/libnm-systemd-shared/src/basic/path-util.c b/src/libnm-systemd-shared/src/basic/path-util.c
index a2af9e0c..0e0f53d9 100644
--- a/src/libnm-systemd-shared/src/basic/path-util.c
+++ b/src/libnm-systemd-shared/src/basic/path-util.c
@@ -43,7 +43,7 @@ int path_split_and_make_absolute(const char *p, char ***ret) {
         return r;
 }
 
-char *path_make_absolute(const char *p, const char *prefix) {
+char* path_make_absolute(const char *p, const char *prefix) {
         assert(p);
 
         /* Makes every item in the list an absolute path by prepending
@@ -135,11 +135,9 @@ int path_make_relative(const char *from, const char *to, char **ret) {
                                         return -ENOMEM;
                         } else {
                                 /* 'to' is inside of 'from'. */
-                                result = strdup(t);
-                                if (!result)
-                                        return -ENOMEM;
-
-                                path_simplify(result);
+                                r = path_simplify_alloc(t, &result);
+                                if (r < 0)
+                                        return r;
 
                                 if (!path_is_valid(result))
                                         return -EINVAL;
@@ -221,6 +219,7 @@ int path_make_relative_parent(const char *from_child, const char *to, char **ret
 
         return path_make_relative(from, to, ret);
 }
+#endif /* NM_IGNORED */
 
 char* path_startswith_strv(const char *p, char **set) {
         STRV_FOREACH(s, set) {
@@ -234,6 +233,7 @@ char* path_startswith_strv(const char *p, char **set) {
         return NULL;
 }
 
+#if 0 /* NM_IGNORED */
 int path_strv_make_absolute_cwd(char **l) {
         int r;
 
@@ -255,7 +255,7 @@ int path_strv_make_absolute_cwd(char **l) {
         return 0;
 }
 
-char **path_strv_resolve(char **l, const char *root) {
+char** path_strv_resolve(char **l, const char *root) {
         unsigned k = 0;
         bool enomem = false;
         int r;
@@ -336,7 +336,7 @@ char **path_strv_resolve(char **l, const char *root) {
         return l;
 }
 
-char **path_strv_resolve_uniq(char **l, const char *root) {
+char** path_strv_resolve_uniq(char **l, const char *root) {
 
         if (strv_isempty(l))
                 return l;
@@ -348,9 +348,9 @@ char **path_strv_resolve_uniq(char **l, const char *root) {
 }
 #endif /* NM_IGNORED */
 
-char *path_simplify(char *path) {
-        bool add_slash = false;
-        char *f = ASSERT_PTR(path);
+char* path_simplify_full(char *path, PathSimplifyFlags flags) {
+        bool add_slash = false, keep_trailing_slash, absolute, beginning = true;
+        char *f = path;
         int r;
 
         /* Removes redundant inner and trailing slashes. Also removes unnecessary dots.
@@ -358,13 +358,17 @@ char *path_simplify(char *path) {
          *
          * ///foo//./bar/.   becomes /foo/bar
          * .//./foo//./bar/. becomes foo/bar
+         * /../foo/bar       becomes /foo/bar
+         * /../foo/bar/..    becomes /foo/bar/..
          */
 
         if (isempty(path))
                 return path;
 
-        if (path_is_absolute(path))
-                f++;
+        keep_trailing_slash = FLAGS_SET(flags, PATH_SIMPLIFY_KEEP_TRAILING_SLASH) && endswith(path, "/");
+
+        absolute = path_is_absolute(path);
+        f += absolute;  /* Keep leading /, if present. */
 
         for (const char *p = f;;) {
                 const char *e;
@@ -373,11 +377,17 @@ char *path_simplify(char *path) {
                 if (r == 0)
                         break;
 
+                if (r > 0 && absolute && beginning && path_startswith(e, ".."))
+                        /* If we're at the beginning of an absolute path, we can safely skip ".." */
+                        continue;
+
+                beginning = false;
+
                 if (add_slash)
                         *f++ = '/';
 
                 if (r < 0) {
-                        /* if path is invalid, then refuse to simplify remaining part. */
+                        /* if path is invalid, then refuse to simplify the remaining part. */
                         memmove(f, p, strlen(p) + 1);
                         return path;
                 }
@@ -392,11 +402,14 @@ char *path_simplify(char *path) {
         if (f == path)
                 *f++ = '.';
 
+        if (*(f-1) != '/' && keep_trailing_slash)
+                *f++ = '/';
+
         *f = '\0';
         return path;
 }
 
-char *path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) {
+char* path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) {
         assert(path);
         assert(prefix);
 
@@ -489,10 +502,6 @@ int path_compare(const char *a, const char *b) {
         }
 }
 
-bool path_equal_or_inode_same(const char *a, const char *b, int flags) {
-        return path_equal(a, b) || inode_same(a, b, flags) > 0;
-}
-
 int path_compare_filename(const char *a, const char *b) {
         _cleanup_free_ char *fa = NULL, *fb = NULL;
         int r, j, k;
@@ -661,7 +670,14 @@ static int find_executable_impl(const char *name, const char *root, char **ret_f
         return 0;
 }
 
-int find_executable_full(const char *name, const char *root, char **exec_search_path, bool use_path_envvar, char **ret_filename, int *ret_fd) {
+int find_executable_full(
+                const char *name,
+                const char *root,
+                char **exec_search_path,
+                bool use_path_envvar,
+                char **ret_filename,
+                int *ret_fd) {
+
         int last_error = -ENOENT, r = 0;
         const char *p = NULL;
 
@@ -812,7 +828,7 @@ int fsck_exists_for_fstype(const char *fstype) {
 }
 #endif /* NM_IGNORED */
 
-static const char *skip_slash_or_dot(const char *p) {
+static const char* skip_slash_or_dot(const char *p) {
         for (; !isempty(p); p++) {
                 if (*p == '/')
                         continue;
@@ -896,7 +912,7 @@ int path_find_first_component(const char **p, bool accept_dot_dot, const char **
         return len;
 }
 
-static const char *skip_slash_or_dot_backward(const char *path, const char *q) {
+static const char* skip_slash_or_dot_backward(const char *path, const char *q) {
         assert(path);
         assert(!q || q >= path);
 
@@ -1005,7 +1021,7 @@ int path_find_last_component(const char *path, bool accept_dot_dot, const char *
         return len;
 }
 
-const char *last_path_component(const char *path) {
+const char* last_path_component(const char *path) {
 
         /* Finds the last component of the path, preserving the optional trailing slash that signifies a directory.
          *
@@ -1126,17 +1142,19 @@ int path_extract_directory(const char *path, char **ret) {
         if (!path_is_valid(a))
                 return -EINVAL;
 
-        *ret = TAKE_PTR(a);
+        if (ret)
+                *ret = TAKE_PTR(a);
+
         return 0;
 }
 
-bool filename_is_valid(const char *p) {
+bool filename_part_is_valid(const char *p) {
         const char *e;
 
-        if (isempty(p))
-                return false;
+        /* Checks f the specified string is OK to be *part* of a filename. This is different from
+         * filename_is_valid() as "." and ".." and "" are OK by this call, but not by filename_is_valid(). */
 
-        if (dot_or_dot_dot(p)) /* Yes, in this context we consider "." and ".." invalid */
+        if (!p)
                 return false;
 
         e = strchrnul(p, '/');
@@ -1149,6 +1167,17 @@ bool filename_is_valid(const char *p) {
         return true;
 }
 
+bool filename_is_valid(const char *p) {
+
+        if (isempty(p))
+                return false;
+
+        if (dot_or_dot_dot(p)) /* Yes, in this context we consider "." and ".." invalid */
+                return false;
+
+        return filename_part_is_valid(p);
+}
+
 bool path_is_valid_full(const char *p, bool accept_dot_dot) {
         if (isempty(p))
                 return false;
@@ -1265,9 +1294,16 @@ bool hidden_or_backup_file(const char *filename) {
 bool is_device_path(const char *path) {
 
         /* Returns true for paths that likely refer to a device, either by path in sysfs or to something in
-         * /dev. */
+         * /dev. This accepts any path that starts with /dev/ or /sys/ and has something after that prefix.
+         * It does not actually resolve the path.
+         *
+         * Examples:
+         * /dev/sda, /dev/sda/foo, /sys/class, /dev/.., /sys/.., /./dev/foo → yes.
+         * /../dev/sda, /dev, /sys, /usr/path, /usr/../dev/sda → no.
+         */
 
-        return PATH_STARTSWITH_SET(path, "/dev/", "/sys/");
+        const char *p = PATH_STARTSWITH_SET(ASSERT_PTR(path), "/dev/", "/sys/");
+        return !isempty(p);
 }
 
 bool valid_device_node_path(const char *path) {
diff --git a/src/libnm-systemd-shared/src/basic/path-util.h b/src/libnm-systemd-shared/src/basic/path-util.h
index fee6e8ee..5bb51ff5 100644
--- a/src/libnm-systemd-shared/src/basic/path-util.h
+++ b/src/libnm-systemd-shared/src/basic/path-util.h
@@ -6,6 +6,7 @@
 #include <stddef.h>
 
 #include "macro.h"
+#include "stat-util.h"
 #include "string-util.h"
 #include "strv.h"
 #include "time-util.h"
@@ -25,24 +26,14 @@
 #  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 PATH_SBIN_BIN("/usr/local/") ":" PATH_SBIN_BIN("/usr/")
+#define DEFAULT_PATH_NULSTR PATH_SBIN_BIN_NULSTR("/usr/local/") PATH_SBIN_BIN_NULSTR("/usr/")
 #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 */
-
 #ifndef DEFAULT_USER_PATH
 #  define DEFAULT_USER_PATH DEFAULT_PATH
 #endif
+#endif /* NM_IGNORED */
 
 static inline bool is_path(const char *p) {
         if (!p) /* A NULL pointer is definitely not a path */
@@ -64,7 +55,7 @@ int safe_getcwd(char **ret);
 int path_make_absolute_cwd(const char *p, char **ret);
 int path_make_relative(const char *from, const char *to, char **ret);
 int path_make_relative_parent(const char *from_child, const char *to, char **ret);
-char *path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) _pure_;
+char* path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) _pure_;
 static inline char* path_startswith(const char *path, const char *prefix) {
         return path_startswith_full(path, prefix, true);
 }
@@ -79,13 +70,38 @@ static inline bool path_equal_filename(const char *a, const char *b) {
         return path_compare_filename(a, b) == 0;
 }
 
-bool path_equal_or_inode_same(const char *a, const char *b, int flags);
+static inline bool path_equal_or_inode_same(const char *a, const char *b, int flags) {
+        return path_equal(a, b) || inode_same(a, b, flags) > 0;
+}
 
 char* path_extend_internal(char **x, ...);
 #define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX)
 #define path_join(...) path_extend_internal(NULL, __VA_ARGS__, POINTER_MAX)
 
-char* path_simplify(char *path);
+typedef enum PathSimplifyFlags {
+        PATH_SIMPLIFY_KEEP_TRAILING_SLASH = 1 << 0,
+} PathSimplifyFlags;
+
+char* path_simplify_full(char *path, PathSimplifyFlags flags);
+static inline char* path_simplify(char *path) {
+        return path_simplify_full(path, 0);
+}
+
+static inline int path_simplify_alloc(const char *path, char **ret) {
+        assert(ret);
+
+        if (!path) {
+                *ret = NULL;
+                return 0;
+        }
+
+        char *t = strdup(path);
+        if (!t)
+                return -ENOMEM;
+
+        *ret = path_simplify(t);
+        return 0;
+}
 
 static inline bool path_equal_ptr(const char *a, const char *b) {
         return !!a == !!b && (!a || path_equal(a, b));
@@ -142,7 +158,7 @@ int fsck_exists_for_fstype(const char *fstype);
                 char *_p, *_n;                                          \
                 size_t _l;                                              \
                 while (_path[0] == '/' && _path[1] == '/')              \
-                        _path ++;                                       \
+                        _path++;                                        \
                 if (isempty(_root))                                     \
                         _ret = _path;                                   \
                 else {                                                  \
@@ -161,10 +177,11 @@ int fsck_exists_for_fstype(const char *fstype);
 
 int path_find_first_component(const char **p, bool accept_dot_dot, const char **ret);
 int path_find_last_component(const char *path, bool accept_dot_dot, const char **next, const char **ret);
-const char *last_path_component(const char *path);
+const char* last_path_component(const char *path);
 int path_extract_filename(const char *path, char **ret);
 int path_extract_directory(const char *path, char **ret);
 
+bool filename_part_is_valid(const char *p) _pure_;
 bool filename_is_valid(const char *p) _pure_;
 bool path_is_valid_full(const char *p, bool accept_dot_dot) _pure_;
 static inline bool path_is_valid(const char *p) {
@@ -197,7 +214,7 @@ static inline const char *skip_dev_prefix(const char *p) {
 }
 
 bool empty_or_root(const char *path);
-static inline const char *empty_to_root(const char *path) {
+static inline const char* empty_to_root(const char *path) {
         return isempty(path) ? "/" : path;
 }
 
diff --git a/src/libnm-systemd-shared/src/basic/pidref.h b/src/libnm-systemd-shared/src/basic/pidref.h
new file mode 100644
index 00000000..c440c8b0
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/pidref.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "macro.h"
+
+/* An embeddable structure carrying a reference to a process. Supposed to be used when tracking processes continuously. */
+typedef struct PidRef {
+        pid_t pid; /* always valid */
+        int fd;    /* only valid if pidfd are available in the kernel, and we manage to get an fd */
+} PidRef;
+
+#define PIDREF_NULL (const PidRef) { .fd = -EBADF }
+
+/* Turns a pid_t into a PidRef structure on-the-fly *without* acquiring a pidfd for it. (As opposed to
+ * pidref_set_pid() which does so *with* acquiring one, see below) */
+#define PIDREF_MAKE_FROM_PID(x) (PidRef) { .pid = (x), .fd = -EBADF }
+
+static inline bool pidref_is_set(const PidRef *pidref) {
+        return pidref && pidref->pid > 0;
+}
+
+static inline bool pidref_equal(const PidRef *a, const PidRef *b) {
+
+        if (pidref_is_set(a)) {
+                if (!pidref_is_set(b))
+                        return false;
+
+                return a->pid == b->pid;
+        }
+
+        return !pidref_is_set(b);
+}
+
+/* This turns a pid_t into a PidRef structure, and acquires a pidfd for it, if possible. (As opposed to
+ * PIDREF_MAKE_FROM_PID() above, which does not acquire a pidfd.) */
+int pidref_set_pid(PidRef *pidref, pid_t pid);
+int pidref_set_pidstr(PidRef *pidref, const char *pid);
+int pidref_set_pidfd(PidRef *pidref, int fd);
+int pidref_set_pidfd_take(PidRef *pidref, int fd); /* takes ownership of the passed pidfd on success*/
+int pidref_set_pidfd_consume(PidRef *pidref, int fd); /* takes ownership of the passed pidfd in both success and failure */
+int pidref_set_parent(PidRef *ret);
+static inline int pidref_set_self(PidRef *pidref) {
+        return pidref_set_pid(pidref, 0);
+}
+
+bool pidref_is_self(const PidRef *pidref);
+
+void pidref_done(PidRef *pidref);
+PidRef *pidref_free(PidRef *pidref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(PidRef*, pidref_free);
+
+int pidref_copy(const PidRef *pidref, PidRef *dest);
+int pidref_dup(const PidRef *pidref, PidRef **ret);
+
+int pidref_new_from_pid(pid_t pid, PidRef **ret);
+
+int pidref_kill(const PidRef *pidref, int sig);
+int pidref_kill_and_sigcont(const PidRef *pidref, int sig);
+int pidref_sigqueue(const PidRef *pidref, int sig, int value);
+
+int pidref_wait(const PidRef *pidref, siginfo_t *siginfo, int options);
+int pidref_wait_for_terminate(const PidRef *pidref, siginfo_t *ret);
+
+static inline void pidref_done_sigkill_wait(PidRef *pidref) {
+        if (!pidref_is_set(pidref))
+                return;
+
+        (void) pidref_kill(pidref, SIGKILL);
+        (void) pidref_wait_for_terminate(pidref, NULL);
+        pidref_done(pidref);
+}
+
+int pidref_verify(const PidRef *pidref);
+
+#define TAKE_PIDREF(p) TAKE_GENERIC((p), PidRef, PIDREF_NULL)
+
+extern const struct hash_ops pidref_hash_ops;
+extern const struct hash_ops pidref_hash_ops_free; /* Has destructor call for pidref_free(), i.e. expects heap allocated PidRef as keys */
diff --git a/src/libnm-systemd-shared/src/basic/prioq.c b/src/libnm-systemd-shared/src/basic/prioq.c
index 0af84bd2..b05b08da 100644
--- a/src/libnm-systemd-shared/src/basic/prioq.c
+++ b/src/libnm-systemd-shared/src/basic/prioq.c
@@ -215,7 +215,7 @@ static void remove_item(Prioq *q, struct prioq_item *i) {
         }
 }
 
-_pure_ static struct prioq_item* find_item(Prioq *q, void *data, unsigned *idx) {
+static struct prioq_item* find_item(Prioq *q, void *data, unsigned *idx) {
         struct prioq_item *i;
 
         assert(q);
diff --git a/src/libnm-systemd-shared/src/basic/process-util.c b/src/libnm-systemd-shared/src/basic/process-util.c
index 8601e0da..5dd9003d 100644
--- a/src/libnm-systemd-shared/src/basic/process-util.c
+++ b/src/libnm-systemd-shared/src/basic/process-util.c
@@ -7,6 +7,7 @@
 #include <limits.h>
 #include <linux/oom.h>
 #include <pthread.h>
+#include <spawn.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -26,6 +27,7 @@
 #include "alloc-util.h"
 #include "architecture.h"
 #include "argv-util.h"
+#include "dirent-util.h"
 #include "env-file.h"
 #include "env-util.h"
 #include "errno-util.h"
@@ -97,7 +99,7 @@ static int get_process_state(pid_t pid) {
         return (unsigned char) state;
 }
 
-int get_process_comm(pid_t pid, char **ret) {
+int pid_get_comm(pid_t pid, char **ret) {
         _cleanup_free_ char *escaped = NULL, *comm = NULL;
         int r;
 
@@ -135,15 +137,35 @@ int get_process_comm(pid_t pid, char **ret) {
         return 0;
 }
 
-static int get_process_cmdline_nulstr(
+int pidref_get_comm(const PidRef *pid, char **ret) {
+        _cleanup_free_ char *comm = NULL;
+        int r;
+
+        if (!pidref_is_set(pid))
+                return -ESRCH;
+
+        r = pid_get_comm(pid->pid, &comm);
+        if (r < 0)
+                return r;
+
+        r = pidref_verify(pid);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = TAKE_PTR(comm);
+        return 0;
+}
+
+static int pid_get_cmdline_nulstr(
                 pid_t pid,
                 size_t max_size,
                 ProcessCmdlineFlags flags,
                 char **ret,
                 size_t *ret_size) {
 
+        _cleanup_free_ char *t = NULL;
         const char *p;
-        char *t;
         size_t k;
         int r;
 
@@ -167,18 +189,17 @@ static int get_process_cmdline_nulstr(
                 return r;
 
         if (k == 0) {
-                t = mfree(t);
-
                 if (!(flags & PROCESS_CMDLINE_COMM_FALLBACK))
                         return -ENOENT;
 
                 /* Kernel threads have no argv[] */
                 _cleanup_free_ char *comm = NULL;
 
-                r = get_process_comm(pid, &comm);
+                r = pid_get_comm(pid, &comm);
                 if (r < 0)
                         return r;
 
+                free(t);
                 t = strjoin("[", comm, "]");
                 if (!t)
                         return -ENOMEM;
@@ -189,12 +210,15 @@ static int get_process_cmdline_nulstr(
                         t[max_size] = '\0';
         }
 
-        *ret = t;
-        *ret_size = k;
+        if (ret)
+                *ret = TAKE_PTR(t);
+        if (ret_size)
+                *ret_size = k;
+
         return r;
 }
 
-int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret) {
+int pid_get_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret) {
         _cleanup_free_ char *t = NULL;
         size_t k;
         char *ans;
@@ -202,7 +226,7 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags
         assert(pid >= 0);
         assert(ret);
 
-        /* Retrieve and format a commandline. See above for discussion of retrieval options.
+        /* Retrieve and format a command line. See above for discussion of retrieval options.
          *
          * There are two main formatting modes:
          *
@@ -216,7 +240,7 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags
          * Returns -ESRCH if the process doesn't exist, and -ENOENT if the process has no command line (and
          * PROCESS_CMDLINE_COMM_FALLBACK is not specified). Returns 0 and sets *line otherwise. */
 
-        int full = get_process_cmdline_nulstr(pid, max_columns, flags, &t, &k);
+        int full = pid_get_cmdline_nulstr(pid, max_columns, flags, &t, &k);
         if (full < 0)
                 return full;
 
@@ -259,7 +283,27 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags
         return 0;
 }
 
-int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret) {
+int pidref_get_cmdline(const PidRef *pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret) {
+        _cleanup_free_ char *s = NULL;
+        int r;
+
+        if (!pidref_is_set(pid))
+                return -ESRCH;
+
+        r = pid_get_cmdline(pid->pid, max_columns, flags, &s);
+        if (r < 0)
+                return r;
+
+        r = pidref_verify(pid);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = TAKE_PTR(s);
+        return 0;
+}
+
+int pid_get_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret) {
         _cleanup_free_ char *t = NULL;
         char **args;
         size_t k;
@@ -269,7 +313,7 @@ int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret)
         assert((flags & ~PROCESS_CMDLINE_COMM_FALLBACK) == 0);
         assert(ret);
 
-        r = get_process_cmdline_nulstr(pid, SIZE_MAX, flags, &t, &k);
+        r = pid_get_cmdline_nulstr(pid, SIZE_MAX, flags, &t, &k);
         if (r < 0)
                 return r;
 
@@ -281,6 +325,27 @@ int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret)
         return 0;
 }
 
+int pidref_get_cmdline_strv(const PidRef *pid, ProcessCmdlineFlags flags, char ***ret) {
+        _cleanup_strv_free_ char **args = NULL;
+        int r;
+
+        if (!pidref_is_set(pid))
+                return -ESRCH;
+
+        r = pid_get_cmdline_strv(pid->pid, flags, &args);
+        if (r < 0)
+                return r;
+
+        r = pidref_verify(pid);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = TAKE_PTR(args);
+
+        return 0;
+}
+
 int container_get_leader(const char *machine, pid_t *pid) {
         _cleanup_free_ char *s = NULL, *class = NULL;
         const char *p;
@@ -322,7 +387,34 @@ int container_get_leader(const char *machine, pid_t *pid) {
         return 0;
 }
 
-int is_kernel_thread(pid_t pid) {
+int namespace_get_leader(pid_t pid, NamespaceType type, pid_t *ret) {
+        int r;
+
+        assert(ret);
+
+        for (;;) {
+                pid_t ppid;
+
+                r = get_process_ppid(pid, &ppid);
+                if (r < 0)
+                        return r;
+
+                r = in_same_namespace(pid, ppid, type);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        /* If the parent and the child are not in the same
+                         * namespace, then the child is the leader we are
+                         * looking for. */
+                        *ret = pid;
+                        return 0;
+                }
+
+                pid = ppid;
+        }
+}
+
+int pid_is_kernel_thread(pid_t pid) {
         _cleanup_free_ char *line = NULL;
         unsigned long long flags;
         size_t l, i;
@@ -380,6 +472,23 @@ int is_kernel_thread(pid_t pid) {
         return !!(flags & PF_KTHREAD);
 }
 
+int pidref_is_kernel_thread(const PidRef *pid) {
+        int result, r;
+
+        if (!pidref_is_set(pid))
+                return -ESRCH;
+
+        result = pid_is_kernel_thread(pid->pid);
+        if (result < 0)
+                return result;
+
+        r = pidref_verify(pid); /* Verify that the PID wasn't reused since */
+        if (r < 0)
+                return r;
+
+        return result;
+}
+
 int get_process_capeff(pid_t pid, char **ret) {
         const char *p;
         int r;
@@ -405,7 +514,7 @@ static int get_process_link_contents(pid_t pid, const char *proc_file, char **re
         p = procfs_file_alloca(pid, proc_file);
 
         r = readlink_malloc(p, ret);
-        return r == -ENOENT ? -ESRCH : r;
+        return (r == -ENOENT && proc_mounted() > 0) ? -ESRCH : r;
 }
 
 int get_process_exe(pid_t pid, char **ret) {
@@ -449,16 +558,14 @@ static int get_process_id(pid_t pid, const char *field, uid_t *ret) {
                 _cleanup_free_ char *line = NULL;
                 char *l;
 
-                r = read_line(f, LONG_LINE_MAX, &line);
+                r = read_stripped_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 = startswith(line, field);
+                if (l) {
                         l += strspn(l, WHITESPACE);
 
                         l[strcspn(l, WHITESPACE)] = 0;
@@ -470,7 +577,8 @@ static int get_process_id(pid_t pid, const char *field, uid_t *ret) {
         return -EIO;
 }
 
-int get_process_uid(pid_t pid, uid_t *ret) {
+int pid_get_uid(pid_t pid, uid_t *ret) {
+        assert(ret);
 
         if (pid == 0 || pid == getpid_cached()) {
                 *ret = getuid();
@@ -480,6 +588,26 @@ int get_process_uid(pid_t pid, uid_t *ret) {
         return get_process_id(pid, "Uid:", ret);
 }
 
+int pidref_get_uid(const PidRef *pid, uid_t *ret) {
+        uid_t uid;
+        int r;
+
+        if (!pidref_is_set(pid))
+                return -ESRCH;
+
+        r = pid_get_uid(pid->pid, &uid);
+        if (r < 0)
+                return r;
+
+        r = pidref_verify(pid);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = uid;
+        return 0;
+}
+
 int get_process_gid(pid_t pid, gid_t *ret) {
 
         if (pid == 0 || pid == getpid_cached()) {
@@ -606,6 +734,82 @@ int get_process_ppid(pid_t pid, pid_t *ret) {
         return 0;
 }
 
+int pid_get_start_time(pid_t pid, uint64_t *ret) {
+        _cleanup_free_ char *line = NULL;
+        const char *p;
+        int r;
+
+        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;
+
+        /* 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++;
+
+        unsigned long llu;
+
+        if (sscanf(p, " "
+                   "%*c "  /* state */
+                   "%*u " /* ppid */
+                   "%*u " /* pgrp */
+                   "%*u " /* session */
+                   "%*u " /* tty_nr */
+                   "%*u " /* tpgid */
+                   "%*u " /* flags */
+                   "%*u " /* minflt */
+                   "%*u " /* cminflt */
+                   "%*u " /* majflt */
+                   "%*u " /* cmajflt */
+                   "%*u " /* utime */
+                   "%*u " /* stime */
+                   "%*u " /* cutime */
+                   "%*u " /* cstime */
+                   "%*i " /* priority */
+                   "%*i " /* nice */
+                   "%*u " /* num_threads */
+                   "%*u " /* itrealvalue */
+                   "%lu ", /* starttime */
+                   &llu) != 1)
+                return -EIO;
+
+        if (ret)
+                *ret = llu;
+
+        return 0;
+}
+
+int pidref_get_start_time(const PidRef *pid, uint64_t *ret) {
+        uint64_t t;
+        int r;
+
+        if (!pidref_is_set(pid))
+                return -ESRCH;
+
+        r = pid_get_start_time(pid->pid, ret ? &t : NULL);
+        if (r < 0)
+                return r;
+
+        r = pidref_verify(pid);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = t;
+
+        return 0;
+}
+
 int get_process_umask(pid_t pid, mode_t *ret) {
         _cleanup_free_ char *m = NULL;
         const char *p;
@@ -670,7 +874,7 @@ int wait_for_terminate_and_check(const char *name, pid_t pid, WaitFlags flags) {
         assert(pid > 1);
 
         if (!name) {
-                r = get_process_comm(pid, &buffer);
+                r = pid_get_comm(pid, &buffer);
                 if (r < 0)
                         log_debug_errno(r, "Failed to acquire process name of " PID_FMT ", ignoring: %m", pid);
                 else
@@ -824,7 +1028,7 @@ 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;
+        size_t sum = 0;
         int r;
 
         assert(pid >= 0);
@@ -859,9 +1063,9 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) {
         if (r < 0)
                 return r;
 
-        l = strlen(field);
         for (;;) {
                 _cleanup_free_ char *line = NULL;
+                const char *match;
 
                 if (sum > ENVIRONMENT_BLOCK_MAX) /* Give up searching eventually */
                         return -ENOBUFS;
@@ -874,8 +1078,9 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) {
 
                 sum += r;
 
-                if (strneq(line, field, l) && line[l] == '=') {
-                        value = strdup(line + l + 1);
+                match = startswith(line, field);
+                if (match && *match == '=') {
+                        value = strdup(match + 1);
                         if (!value)
                                 return -ENOMEM;
 
@@ -892,6 +1097,9 @@ int pid_is_my_child(pid_t pid) {
         pid_t ppid;
         int r;
 
+        if (pid < 0)
+                return -ESRCH;
+
         if (pid <= 1)
                 return false;
 
@@ -902,11 +1110,28 @@ int pid_is_my_child(pid_t pid) {
         return ppid == getpid_cached();
 }
 
-bool pid_is_unwaited(pid_t pid) {
+int pidref_is_my_child(const PidRef *pid) {
+        int r, result;
+
+        if (!pidref_is_set(pid))
+                return -ESRCH;
+
+        result = pid_is_my_child(pid->pid);
+        if (result < 0)
+                return result;
+
+        r = pidref_verify(pid);
+        if (r < 0)
+                return r;
+
+        return result;
+}
+
+int pid_is_unwaited(pid_t pid) {
         /* Checks whether a PID is still valid at all, including a zombie */
 
         if (pid < 0)
-                return false;
+                return -ESRCH;
 
         if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */
                 return true;
@@ -920,13 +1145,31 @@ bool pid_is_unwaited(pid_t pid) {
         return errno != ESRCH;
 }
 
-bool pid_is_alive(pid_t pid) {
+int pidref_is_unwaited(const PidRef *pid) {
+        int r;
+
+        if (!pidref_is_set(pid))
+                return -ESRCH;
+
+        if (pid->pid == 1 || pidref_is_self(pid))
+                return true;
+
+        r = pidref_kill(pid, 0);
+        if (r == -ESRCH)
+                return false;
+        if (r < 0)
+                return r;
+
+        return true;
+}
+
+int pid_is_alive(pid_t pid) {
         int r;
 
         /* Checks whether a PID is still valid and not a zombie */
 
         if (pid < 0)
-                return false;
+                return -ESRCH;
 
         if (pid <= 1) /* If we or PID 1 would be a zombie, this code would not be running */
                 return true;
@@ -935,10 +1178,33 @@ bool pid_is_alive(pid_t pid) {
                 return true;
 
         r = get_process_state(pid);
-        if (IN_SET(r, -ESRCH, 'Z'))
+        if (r == -ESRCH)
                 return false;
+        if (r < 0)
+                return r;
 
-        return true;
+        return r != 'Z';
+}
+
+int pidref_is_alive(const PidRef *pidref) {
+        int r, result;
+
+        if (!pidref_is_set(pidref))
+                return -ESRCH;
+
+        result = pid_is_alive(pidref->pid);
+        if (result < 0) {
+                assert(result != -ESRCH);
+                return result;
+        }
+
+        r = pidref_verify(pidref);
+        if (r == -ESRCH)
+                return false;
+        if (r < 0)
+                return r;
+
+        return result;
 }
 
 int pid_from_same_root_fs(pid_t pid) {
@@ -1043,7 +1309,7 @@ int opinionated_personality(unsigned long *ret) {
         if (current < 0)
                 return current;
 
-        if (((unsigned long) current & 0xffff) == PER_LINUX32)
+        if (((unsigned long) current & OPINIONATED_PERSONALITY_MASK) == PER_LINUX32)
                 *ret = PER_LINUX32;
         else
                 *ret = PER_LINUX;
@@ -1057,7 +1323,10 @@ void valgrind_summary_hack(void) {
                 pid_t pid;
                 pid = raw_clone(SIGCHLD);
                 if (pid < 0)
-                        log_emergency_errno(errno, "Failed to fork off valgrind helper: %m");
+                        log_struct_errno(
+                                LOG_EMERG, errno,
+                                "MESSAGE_ID=" SD_MESSAGE_VALGRIND_HELPER_FORK_STR,
+                                LOG_MESSAGE( "Failed to fork off valgrind helper: %m"));
                 else if (pid == 0)
                         exit(EXIT_SUCCESS);
                 else {
@@ -1104,7 +1373,7 @@ pid_t getpid_cached(void) {
          * https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=c579f48edba88380635ab98cb612030e3ed8691e
          */
 
-        __atomic_compare_exchange_n(
+        (void) __atomic_compare_exchange_n(
                         &cached_pid,
                         &current_value,
                         CACHED_PID_BUSY,
@@ -1159,10 +1428,55 @@ static void restore_sigsetp(sigset_t **ssp) {
                 (void) sigprocmask(SIG_SETMASK, *ssp, NULL);
 }
 
+pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata) {
+        size_t ps;
+        pid_t pid;
+        void *mystack;
+
+        /* A wrapper around glibc's clone() call that automatically sets up a "nested" stack. Only supports
+         * invocations without CLONE_VM, so that we can continue to use the parent's stack mapping.
+         *
+         * Note: glibc's clone() wrapper does not synchronize malloc() locks. This means that if the parent
+         * is threaded these locks will be in an undefined state in the child, and hence memory allocations
+         * are likely going to run into deadlocks. Hence: if you use this function make sure your parent is
+         * strictly single-threaded or your child never calls malloc(). */
+
+        assert((flags & (CLONE_VM|CLONE_PARENT_SETTID|CLONE_CHILD_SETTID|
+                         CLONE_CHILD_CLEARTID|CLONE_SETTLS)) == 0);
+
+        /* We allocate some space on the stack to use as the stack for the child (hence "nested"). Note that
+         * the net effect is that the child will have the start of its stack inside the stack of the parent,
+         * but since they are a CoW copy of each other that's fine. We allocate one page-aligned page. But
+         * since we don't want to deal with differences between systems where the stack grows backwards or
+         * forwards we'll allocate one more and place the stack address in the middle. Except that we also
+         * want it page aligned, hence we'll allocate one page more. Makes 3. */
+
+        ps = page_size();
+        mystack = alloca(ps*3);
+        mystack = (uint8_t*) mystack + ps; /* move pointer one page ahead since stacks usually grow backwards */
+        mystack = (void*) ALIGN_TO((uintptr_t) mystack, ps); /* align to page size (moving things further ahead) */
+
+#if HAVE_CLONE
+        pid = clone(fn, mystack, flags, userdata);
+#else
+        pid = __clone2(fn, mystack, ps, flags, userdata);
+#endif
+        if (pid < 0)
+                return -errno;
+
+        return pid;
+}
+
+static int fork_flags_to_signal(ForkFlags flags) {
+        return (flags & FORK_DEATHSIG_SIGTERM) ? SIGTERM :
+                (flags & FORK_DEATHSIG_SIGINT) ? SIGINT :
+                                                 SIGKILL;
+}
+
 int safe_fork_full(
                 const char *name,
                 const int stdio_fds[3],
-                const int except_fds[],
+                int except_fds[],
                 size_t n_except_fds,
                 ForkFlags flags,
                 pid_t *ret_pid) {
@@ -1170,9 +1484,12 @@ int safe_fork_full(
         pid_t original_pid, pid;
         sigset_t saved_ss, ss;
         _unused_ _cleanup_(restore_sigsetp) sigset_t *saved_ssp = NULL;
-        bool block_signals = false, block_all = false;
+        bool block_signals = false, block_all = false, intermediary = false;
         int prio, r;
 
+        assert(!FLAGS_SET(flags, FORK_DETACH) || !ret_pid);
+        assert(!FLAGS_SET(flags, FORK_DETACH|FORK_WAIT));
+
         /* 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. */
 
@@ -1185,9 +1502,10 @@ int safe_fork_full(
                 fflush(stderr); /* This one shouldn't be necessary, stderr should be unbuffered anyway, but let's better be safe than sorry */
         }
 
-        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. */
+        if (flags & (FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGINT)) {
+                /* 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. (Note that for
+                 * FORK_DEATHSIG_SIGKILL we don't bother, since it cannot be blocked anyway.) */
 
                 assert_se(sigfillset(&ss) >= 0);
                 block_signals = block_all = true;
@@ -1206,17 +1524,47 @@ int safe_fork_full(
                 saved_ssp = &saved_ss;
         }
 
-        if ((flags & (FORK_NEW_MOUNTNS|FORK_NEW_USERNS)) != 0)
+        if (FLAGS_SET(flags, FORK_DETACH)) {
+                assert(!FLAGS_SET(flags, FORK_WAIT));
+                assert(!ret_pid);
+
+                /* Fork off intermediary child if needed */
+
+                r = is_reaper_process();
+                if (r < 0)
+                        return log_full_errno(prio, r, "Failed to determine if we are a reaper process: %m");
+
+                if (!r) {
+                        /* Not a reaper process, hence do a double fork() so we are reparented to one */
+
+                        pid = fork();
+                        if (pid < 0)
+                                return log_full_errno(prio, errno, "Failed to fork off '%s': %m", strna(name));
+                        if (pid > 0) {
+                                log_debug("Successfully forked off intermediary '%s' as PID " PID_FMT ".", strna(name), pid);
+                                return 1; /* return in the parent */
+                        }
+
+                        intermediary = true;
+                }
+        }
+
+        if ((flags & (FORK_NEW_MOUNTNS|FORK_NEW_USERNS|FORK_NEW_NETNS)) != 0)
                 pid = raw_clone(SIGCHLD|
                                 (FLAGS_SET(flags, FORK_NEW_MOUNTNS) ? CLONE_NEWNS : 0) |
-                                (FLAGS_SET(flags, FORK_NEW_USERNS) ? CLONE_NEWUSER : 0));
+                                (FLAGS_SET(flags, FORK_NEW_USERNS) ? CLONE_NEWUSER : 0) |
+                                (FLAGS_SET(flags, FORK_NEW_NETNS) ? CLONE_NEWNET : 0));
         else
                 pid = fork();
         if (pid < 0)
                 return log_full_errno(prio, errno, "Failed to fork off '%s': %m", strna(name));
         if (pid > 0) {
-                /* We are in the parent process */
 
+                /* If we are in the intermediary process, exit now */
+                if (intermediary)
+                        _exit(EXIT_SUCCESS);
+
+                /* We are in the parent process */
                 log_debug("Successfully forked off '%s' as PID " PID_FMT ".", strna(name), pid);
 
                 if (flags & FORK_WAIT) {
@@ -1259,8 +1607,8 @@ int safe_fork_full(
                                        r, "Failed to rename process, ignoring: %m");
         }
 
-        if (flags & (FORK_DEATHSIG|FORK_DEATHSIG_SIGINT))
-                if (prctl(PR_SET_PDEATHSIG, (flags & FORK_DEATHSIG_SIGINT) ? SIGINT : SIGTERM) < 0) {
+        if (flags & (FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGINT|FORK_DEATHSIG_SIGKILL))
+                if (prctl(PR_SET_PDEATHSIG, fork_flags_to_signal(flags)) < 0) {
                         log_full_errno(prio, errno, "Failed to set death signal: %m");
                         _exit(EXIT_FAILURE);
                 }
@@ -1285,7 +1633,7 @@ int safe_fork_full(
                 }
         }
 
-        if (flags & FORK_DEATHSIG) {
+        if (flags & (FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGKILL|FORK_DEATHSIG_SIGINT)) {
                 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 */
@@ -1294,8 +1642,9 @@ int safe_fork_full(
                 if (ppid == 0)
                         /* Parent is in a different PID namespace. */;
                 else if (ppid != original_pid) {
-                        log_debug("Parent died early, raising SIGTERM.");
-                        (void) raise(SIGTERM);
+                        int sig = fork_flags_to_signal(flags);
+                        log_debug("Parent died early, raising %s.", signal_to_string(sig));
+                        (void) raise(sig);
                         _exit(EXIT_FAILURE);
                 }
         }
@@ -1328,6 +1677,9 @@ int safe_fork_full(
                                 log_full_errno(prio, r, "Failed to rearrange stdio fds: %m");
                                 _exit(EXIT_FAILURE);
                         }
+
+                        /* Turn off O_NONBLOCK on the fdio fds, in case it was left on */
+                        stdio_disable_nonblock();
                 } else {
                         r = make_null_stdio();
                         if (r < 0) {
@@ -1353,6 +1705,19 @@ int safe_fork_full(
                 }
         }
 
+        if (flags & FORK_PACK_FDS) {
+                /* FORK_CLOSE_ALL_FDS ensures that except_fds are the only FDs >= 3 that are
+                 * open, this is including the log. This is required by pack_fds, which will
+                 * get stuck in an infinite loop of any FDs other than except_fds are open. */
+                assert(FLAGS_SET(flags, FORK_CLOSE_ALL_FDS));
+
+                r = pack_fds(except_fds, n_except_fds);
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to pack file descriptors: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
         if (flags & FORK_CLOEXEC_OFF) {
                 r = fd_cloexec_many(except_fds, n_except_fds, false);
                 if (r < 0) {
@@ -1389,10 +1754,34 @@ int safe_fork_full(
         return 0;
 }
 
+int pidref_safe_fork_full(
+                const char *name,
+                const int stdio_fds[3],
+                int except_fds[],
+                size_t n_except_fds,
+                ForkFlags flags,
+                PidRef *ret_pid) {
+
+        pid_t pid;
+        int r, q;
+
+        assert(!FLAGS_SET(flags, FORK_WAIT));
+
+        r = safe_fork_full(name, stdio_fds, except_fds, n_except_fds, flags, &pid);
+        if (r < 0)
+                return r;
+
+        q = pidref_set_pid(ret_pid, pid);
+        if (q < 0) /* Let's not fail for this, no matter what, the process exists after all, and that's key */
+                *ret_pid = PIDREF_MAKE_FROM_PID(pid);
+
+        return r;
+}
+
 int namespace_fork(
                 const char *outer_name,
                 const char *inner_name,
-                const int except_fds[],
+                int except_fds[],
                 size_t n_except_fds,
                 ForkFlags flags,
                 int pidns_fd,
@@ -1411,7 +1800,7 @@ int namespace_fork(
         r = safe_fork_full(outer_name,
                            NULL,
                            except_fds, n_except_fds,
-                           (flags|FORK_DEATHSIG) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid);
+                           (flags|FORK_DEATHSIG_SIGINT|FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGKILL) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid);
         if (r < 0)
                 return r;
         if (r == 0) {
@@ -1633,6 +2022,212 @@ int get_process_threads(pid_t pid) {
         return n;
 }
 
+int is_reaper_process(void) {
+        int b = 0;
+
+        /* Checks if we are running in a reaper process, i.e. if we are expected to deal with processes
+         * reparented to us. This simply checks if we are PID 1 or if PR_SET_CHILD_SUBREAPER was called. */
+
+        if (getpid_cached() == 1)
+                return true;
+
+        if (prctl(PR_GET_CHILD_SUBREAPER, (unsigned long) &b, 0UL, 0UL, 0UL) < 0)
+                return -errno;
+
+        return b != 0;
+}
+
+int make_reaper_process(bool b) {
+
+        if (getpid_cached() == 1) {
+
+                if (!b)
+                        return -EINVAL;
+
+                return 0;
+        }
+
+        /* Some prctl()s insist that all 5 arguments are specified, others do not. Let's always specify all,
+         * to avoid any ambiguities */
+        if (prctl(PR_SET_CHILD_SUBREAPER, (unsigned long) b, 0UL, 0UL, 0UL) < 0)
+                return -errno;
+
+        return 0;
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(posix_spawnattr_t*, posix_spawnattr_destroy, NULL);
+
+int posix_spawn_wrapper(
+                const char *path,
+                char * const *argv,
+                char * const *envp,
+                const char *cgroup,
+                PidRef *ret_pidref) {
+
+        short flags = POSIX_SPAWN_SETSIGMASK|POSIX_SPAWN_SETSIGDEF;
+        posix_spawnattr_t attr;
+        sigset_t mask;
+        int r;
+
+        /* Forks and invokes 'path' with 'argv' and 'envp' using CLONE_VM and CLONE_VFORK, which means the
+         * caller will be blocked until the child either exits or exec's. The memory of the child will be
+         * fully shared with the memory of the parent, so that there are no copy-on-write or memory.max
+         * issues.
+         *
+         * Also, move the newly-created process into 'cgroup' through POSIX_SPAWN_SETCGROUP (clone3())
+         * if available. Note that CLONE_INTO_CGROUP is only supported on cgroup v2.
+         * returns 1: We're already in the right cgroup
+         *         0: 'cgroup' not specified or POSIX_SPAWN_SETCGROUP is not supported. The caller
+         *            needs to call 'cg_attach' on their own */
+
+        assert(path);
+        assert(argv);
+        assert(ret_pidref);
+
+        assert_se(sigfillset(&mask) >= 0);
+
+        r = posix_spawnattr_init(&attr);
+        if (r != 0)
+                return -r; /* These functions return a positive errno on failure */
+
+        /* Initialization needs to succeed before we can set up a destructor. */
+        _unused_ _cleanup_(posix_spawnattr_destroyp) posix_spawnattr_t *attr_destructor = &attr;
+
+#if HAVE_PIDFD_SPAWN
+        _cleanup_close_ int cgroup_fd = -EBADF;
+
+        if (cgroup) {
+                _cleanup_free_ char *resolved_cgroup = NULL;
+
+                r = cg_get_path_and_check(
+                                SYSTEMD_CGROUP_CONTROLLER,
+                                cgroup,
+                                /* suffix= */ NULL,
+                                &resolved_cgroup);
+                if (r < 0)
+                        return r;
+
+                cgroup_fd = open(resolved_cgroup, O_PATH|O_DIRECTORY|O_CLOEXEC);
+                if (cgroup_fd < 0)
+                        return -errno;
+
+                r = posix_spawnattr_setcgroup_np(&attr, cgroup_fd);
+                if (r != 0)
+                        return -r;
+
+                flags |= POSIX_SPAWN_SETCGROUP;
+        }
+#endif
+
+        r = posix_spawnattr_setflags(&attr, flags);
+        if (r != 0)
+                return -r;
+        r = posix_spawnattr_setsigmask(&attr, &mask);
+        if (r != 0)
+                return -r;
+
+#if HAVE_PIDFD_SPAWN
+        _cleanup_close_ int pidfd = -EBADF;
+
+        r = pidfd_spawn(&pidfd, path, NULL, &attr, argv, envp);
+        if (r == 0) {
+                r = pidref_set_pidfd_consume(ret_pidref, TAKE_FD(pidfd));
+                if (r < 0)
+                        return r;
+
+                return FLAGS_SET(flags, POSIX_SPAWN_SETCGROUP);
+        }
+        if (!(ERRNO_IS_NOT_SUPPORTED(r) || ERRNO_IS_PRIVILEGE(r)))
+                return -r;
+
+        /* Compiled on a newer host, or seccomp&friends blocking clone3()? Fallback, but need to change the
+         * flags to remove the cgroup one, which is what redirects to clone3() */
+        flags &= ~POSIX_SPAWN_SETCGROUP;
+        r = posix_spawnattr_setflags(&attr, flags);
+        if (r != 0)
+                return -r;
+#endif
+
+        pid_t pid;
+        r = posix_spawn(&pid, path, NULL, &attr, argv, envp);
+        if (r != 0)
+                return -r;
+
+        r = pidref_set_pid(ret_pidref, pid);
+        if (r < 0)
+                return r;
+
+        return 0; /* We did not use CLONE_INTO_CGROUP so return 0, the caller will have to move the child */
+}
+
+int proc_dir_open(DIR **ret) {
+        DIR *d;
+
+        assert(ret);
+
+        d = opendir("/proc");
+        if (!d)
+                return -errno;
+
+        *ret = d;
+        return 0;
+}
+
+int proc_dir_read(DIR *d, pid_t *ret) {
+        assert(d);
+
+        for (;;) {
+                struct dirent *de;
+
+                errno = 0;
+                de = readdir_no_dot(d);
+                if (!de) {
+                        if (errno != 0)
+                                return -errno;
+
+                        break;
+                }
+
+                if (!IN_SET(de->d_type, DT_DIR, DT_UNKNOWN))
+                        continue;
+
+                if (parse_pid(de->d_name, ret) >= 0)
+                        return 1;
+        }
+
+        if (ret)
+                *ret = 0;
+        return 0;
+}
+
+int proc_dir_read_pidref(DIR *d, PidRef *ret) {
+        int r;
+
+        assert(d);
+
+        for (;;) {
+                pid_t pid;
+
+                r = proc_dir_read(d, &pid);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                r = pidref_set_pid(ret, pid);
+                if (r == -ESRCH) /* gone by now? skip it */
+                        continue;
+                if (r < 0)
+                        return r;
+
+                return 1;
+        }
+
+        if (ret)
+                *ret = PIDREF_NULL;
+        return 0;
+}
+
 static const char *const sigchld_code_table[] = {
         [CLD_EXITED] = "exited",
         [CLD_KILLED] = "killed",
diff --git a/src/libnm-systemd-shared/src/basic/process-util.h b/src/libnm-systemd-shared/src/basic/process-util.h
index 5cf5c7c6..49350065 100644
--- a/src/libnm-systemd-shared/src/basic/process-util.h
+++ b/src/libnm-systemd-shared/src/basic/process-util.h
@@ -14,6 +14,7 @@
 #include "alloc-util.h"
 #include "format-util.h"
 #include "macro.h"
+#include "namespace-util.h"
 #include "time-util.h"
 
 #define procfs_file_alloca(pid, field)                                  \
@@ -38,21 +39,29 @@ typedef enum ProcessCmdlineFlags {
         PROCESS_CMDLINE_QUOTE_POSIX   = 1 << 3,
 } ProcessCmdlineFlags;
 
-int get_process_comm(pid_t pid, char **ret);
-int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret);
-int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret);
+int pid_get_comm(pid_t pid, char **ret);
+int pidref_get_comm(const PidRef *pid, char **ret);
+int pid_get_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret);
+int pidref_get_cmdline(const PidRef *pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret);
+int pid_get_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret);
+int pidref_get_cmdline_strv(const PidRef *pid, ProcessCmdlineFlags flags, char ***ret);
 int get_process_exe(pid_t pid, char **ret);
-int get_process_uid(pid_t pid, uid_t *ret);
+int pid_get_uid(pid_t pid, uid_t *ret);
+int pidref_get_uid(const PidRef *pid, uid_t *ret);
 int get_process_gid(pid_t pid, gid_t *ret);
 int get_process_capeff(pid_t pid, char **ret);
 int get_process_cwd(pid_t pid, char **ret);
 int get_process_root(pid_t pid, char **ret);
 int get_process_environ(pid_t pid, char **ret);
 int get_process_ppid(pid_t pid, pid_t *ret);
+int pid_get_start_time(pid_t pid, uint64_t *ret);
+int pidref_get_start_time(const PidRef* pid, uint64_t *ret);
 int get_process_umask(pid_t pid, mode_t *ret);
 
 int container_get_leader(const char *machine, pid_t *pid);
 
+int namespace_get_leader(pid_t pid, NamespaceType type, pid_t *ret);
+
 int wait_for_terminate(pid_t pid, siginfo_t *status);
 
 typedef enum WaitFlags {
@@ -74,13 +83,17 @@ void sigkill_nowaitp(pid_t *pid);
 
 int kill_and_sigcont(pid_t pid, int sig);
 
-int is_kernel_thread(pid_t pid);
+int pid_is_kernel_thread(pid_t pid);
+int pidref_is_kernel_thread(const PidRef *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_is_alive(pid_t pid);
+int pidref_is_alive(const PidRef *pidref);
+int pid_is_unwaited(pid_t pid);
+int pidref_is_unwaited(const PidRef *pidref);
 int pid_is_my_child(pid_t pid);
+int pidref_is_my_child(const PidRef *pidref);
 int pid_from_same_root_fs(pid_t pid);
 
 bool is_main_thread(void);
@@ -88,12 +101,17 @@ bool is_main_thread(void);
 bool oom_score_adjust_is_valid(int oa);
 
 #ifndef PERSONALITY_INVALID
-/* personality(7) documents that 0xffffffffUL is used for querying the
+/* personality(2) documents that 0xFFFFFFFFUL is used for querying the
  * current personality, hence let's use that here as error
  * indicator. */
-#define PERSONALITY_INVALID 0xffffffffLU
+#define PERSONALITY_INVALID 0xFFFFFFFFUL
 #endif
 
+/* The personality() syscall returns a 32-bit value where the top three bytes are reserved for flags that
+ * emulate historical or architectural quirks, and only the least significant byte reflects the actual
+ * personality we're interested in. */
+#define OPINIONATED_PERSONALITY_MASK 0xFFUL
+
 unsigned long personality_from_string(const char *p);
 const char *personality_to_string(unsigned long);
 
@@ -141,30 +159,41 @@ void reset_cached_pid(void);
 
 int must_be_root(void);
 
+pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata);
+
+/* 💣 Note that FORK_NEW_USERNS, FORK_NEW_MOUNTNS, or FORK_NEW_NETNS should not be called in threaded
+ * programs, because they cause us to use raw_clone() which does not synchronize the glibc malloc() locks,
+ * and thus will cause deadlocks if the parent uses threads and the child does memory allocations. Hence: if
+ * the parent is threaded these flags may not be used. These flags cannot be used if the parent uses threads
+ * or the child uses malloc(). 💣 */
 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 to SIGTERM */
+        FORK_DEATHSIG_SIGTERM   = 1 <<  2, /* Set PR_DEATHSIG in the child to SIGTERM */
         FORK_DEATHSIG_SIGINT    = 1 <<  3, /* Set PR_DEATHSIG in the child to SIGINT */
-        FORK_REARRANGE_STDIO    = 1 <<  4, /* Connect 0,1,2 to specified fds or /dev/null */
-        FORK_REOPEN_LOG         = 1 <<  5, /* Reopen log connection */
-        FORK_LOG                = 1 <<  6, /* Log above LOG_DEBUG log level about failures */
-        FORK_WAIT               = 1 <<  7, /* Wait until child exited */
-        FORK_NEW_MOUNTNS        = 1 <<  8, /* Run child in its own mount namespace */
-        FORK_MOUNTNS_SLAVE      = 1 <<  9, /* Make child's mount namespace MS_SLAVE */
-        FORK_PRIVATE_TMP        = 1 << 10, /* Mount new /tmp/ in the child (combine with FORK_NEW_MOUNTNS!) */
-        FORK_RLIMIT_NOFILE_SAFE = 1 << 11, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */
-        FORK_STDOUT_TO_STDERR   = 1 << 12, /* Make stdout a copy of stderr */
-        FORK_FLUSH_STDIO        = 1 << 13, /* fflush() stdout (and stderr) before forking */
-        FORK_NEW_USERNS         = 1 << 14, /* Run child in its own user namespace */
-        FORK_CLOEXEC_OFF        = 1 << 15, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */
-        FORK_KEEP_NOTIFY_SOCKET = 1 << 16, /* Unless this specified, $NOTIFY_SOCKET will be unset. */
+        FORK_DEATHSIG_SIGKILL   = 1 <<  4, /* Set PR_DEATHSIG in the child to SIGKILL */
+        FORK_REARRANGE_STDIO    = 1 <<  5, /* Connect 0,1,2 to specified fds or /dev/null */
+        FORK_REOPEN_LOG         = 1 <<  6, /* Reopen log connection */
+        FORK_LOG                = 1 <<  7, /* Log above LOG_DEBUG log level about failures */
+        FORK_WAIT               = 1 <<  8, /* Wait until child exited */
+        FORK_NEW_MOUNTNS        = 1 <<  9, /* Run child in its own mount namespace                               💣 DO NOT USE IN THREADED PROGRAMS! 💣 */
+        FORK_MOUNTNS_SLAVE      = 1 << 10, /* Make child's mount namespace MS_SLAVE */
+        FORK_PRIVATE_TMP        = 1 << 11, /* Mount new /tmp/ in the child (combine with FORK_NEW_MOUNTNS!) */
+        FORK_RLIMIT_NOFILE_SAFE = 1 << 12, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */
+        FORK_STDOUT_TO_STDERR   = 1 << 13, /* Make stdout a copy of stderr */
+        FORK_FLUSH_STDIO        = 1 << 14, /* fflush() stdout (and stderr) before forking */
+        FORK_NEW_USERNS         = 1 << 15, /* Run child in its own user namespace                                💣 DO NOT USE IN THREADED PROGRAMS! 💣 */
+        FORK_CLOEXEC_OFF        = 1 << 16, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */
+        FORK_KEEP_NOTIFY_SOCKET = 1 << 17, /* Unless this specified, $NOTIFY_SOCKET will be unset. */
+        FORK_DETACH             = 1 << 18, /* Double fork if needed to ensure PID1/subreaper is parent */
+        FORK_NEW_NETNS          = 1 << 19, /* Run child in its own network namespace                             💣 DO NOT USE IN THREADED PROGRAMS! 💣 */
+        FORK_PACK_FDS           = 1 << 20, /* Rearrange the passed FDs to be FD 3,4,5,etc. Updates the array in place (combine with FORK_CLOSE_ALL_FDS!) */
 } ForkFlags;
 
 int safe_fork_full(
                 const char *name,
                 const int stdio_fds[3],
-                const int except_fds[],
+                int except_fds[],
                 size_t n_except_fds,
                 ForkFlags flags,
                 pid_t *ret_pid);
@@ -173,7 +202,30 @@ static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) {
         return safe_fork_full(name, NULL, 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 pidref_safe_fork_full(
+                const char *name,
+                const int stdio_fds[3],
+                int except_fds[],
+                size_t n_except_fds,
+                ForkFlags flags,
+                PidRef *ret_pid);
+
+static inline int pidref_safe_fork(const char *name, ForkFlags flags, PidRef *ret_pid) {
+        return pidref_safe_fork_full(name, NULL, NULL, 0, flags, ret_pid);
+}
+
+int namespace_fork(
+                const char *outer_name,
+                const char *inner_name,
+                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 set_oom_score_adjust(int value);
 int get_oom_score_adjust(int *ret);
@@ -201,3 +253,17 @@ int setpriority_closest(int priority);
 _noreturn_ void freeze(void);
 
 int get_process_threads(pid_t pid);
+
+int is_reaper_process(void);
+int make_reaper_process(bool b);
+
+int posix_spawn_wrapper(
+                const char *path,
+                char * const *argv,
+                char * const *envp,
+                const char *cgroup,
+                PidRef *ret_pidref);
+
+int proc_dir_open(DIR **ret);
+int proc_dir_read(DIR *d, pid_t *ret);
+int proc_dir_read_pidref(DIR *d, PidRef *ret);
diff --git a/src/libnm-systemd-shared/src/basic/random-util.c b/src/libnm-systemd-shared/src/basic/random-util.c
index 934d5e25..c7b95516 100644
--- a/src/libnm-systemd-shared/src/basic/random-util.c
+++ b/src/libnm-systemd-shared/src/basic/random-util.c
@@ -6,7 +6,6 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/random.h>
-#include <pthread.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdlib.h>
@@ -28,6 +27,7 @@
 #include "missing_syscall.h"
 #include "missing_threads.h"
 #include "parse-util.h"
+#include "process-util.h"
 #include "random-util.h"
 #include "sha256.h"
 #include "time-util.h"
@@ -50,7 +50,7 @@ static void fallback_random_bytes(void *p, size_t n) {
                 .call_id = fallback_counter++,
                 .stamp_mono = now(CLOCK_MONOTONIC),
                 .stamp_real = now(CLOCK_REALTIME),
-                .pid = getpid(),
+                .pid = getpid_cached(),
                 .tid = gettid(),
         };
 
@@ -226,7 +226,7 @@ int random_write_entropy(int fd, const void *seed, size_t size, bool credit) {
                 if (ioctl(fd, RNDADDENTROPY, info) < 0)
                         return -errno;
         } else {
-                r = loop_write(fd, seed, size, false);
+                r = loop_write(fd, seed, size);
                 if (r < 0)
                         return r;
         }
diff --git a/src/libnm-systemd-shared/src/basic/ratelimit.h b/src/libnm-systemd-shared/src/basic/ratelimit.h
index bb7160a8..492ea3b4 100644
--- a/src/libnm-systemd-shared/src/basic/ratelimit.h
+++ b/src/libnm-systemd-shared/src/basic/ratelimit.h
@@ -12,6 +12,8 @@ typedef struct RateLimit {
         usec_t begin;
 } RateLimit;
 
+#define RATELIMIT_OFF (const RateLimit) { .interval = USEC_INFINITY, .burst = UINT_MAX }
+
 static inline void ratelimit_reset(RateLimit *rl) {
         rl->num = rl->begin = 0;
 }
diff --git a/src/libnm-systemd-shared/src/basic/signal-util.c b/src/libnm-systemd-shared/src/basic/signal-util.c
index 270d397d..95c86584 100644
--- a/src/libnm-systemd-shared/src/basic/signal-util.c
+++ b/src/libnm-systemd-shared/src/basic/signal-util.c
@@ -21,7 +21,7 @@ int reset_all_signal_handlers(void) {
                 .sa_handler = SIG_DFL,
                 .sa_flags = SA_RESTART,
         };
-        int r = 0;
+        int ret = 0, r;
 
         for (int sig = 1; sig < _NSIG; sig++) {
 
@@ -29,14 +29,14 @@ int reset_all_signal_handlers(void) {
                 if (IN_SET(sig, SIGKILL, SIGSTOP))
                         continue;
 
-                /* On Linux the first two RT signals are reserved by
-                 * glibc, and sigaction() will return EINVAL for them. */
-                if (sigaction(sig, &sa, NULL) < 0)
-                        if (errno != EINVAL && r >= 0)
-                                r = -errno;
+                /* On Linux the first two RT signals are reserved by glibc, and sigaction() will return
+                 * EINVAL for them. */
+                r = RET_NERRNO(sigaction(sig, &sa, NULL));
+                if (r != -EINVAL)
+                        RET_GATHER(ret, r);
         }
 
-        return r;
+        return ret;
 }
 
 int reset_signal_mask(void) {
@@ -60,10 +60,7 @@ int sigaction_many_internal(const struct sigaction *sa, ...) {
                 if (sig == 0)
                         continue;
 
-                if (sigaction(sig, sa, NULL) < 0) {
-                        if (r >= 0)
-                                r = -errno;
-                }
+                RET_GATHER(r, RET_NERRNO(sigaction(sig, sa, NULL)));
         }
 
         va_end(ap);
@@ -90,7 +87,7 @@ static int sigset_add_many_ap(sigset_t *ss, va_list ap) {
         return r;
 }
 
-int sigset_add_many(sigset_t *ss, ...) {
+int sigset_add_many_internal(sigset_t *ss, ...) {
         va_list ap;
         int r;
 
@@ -101,7 +98,7 @@ int sigset_add_many(sigset_t *ss, ...) {
         return r;
 }
 
-int sigprocmask_many(int how, sigset_t *old, ...) {
+int sigprocmask_many_internal(int how, sigset_t *old, ...) {
         va_list ap;
         sigset_t ss;
         int r;
@@ -116,46 +113,43 @@ int sigprocmask_many(int how, sigset_t *old, ...) {
         if (r < 0)
                 return r;
 
-        if (sigprocmask(how, &ss, old) < 0)
-                return -errno;
-
-        return 0;
+        return RET_NERRNO(sigprocmask(how, &ss, old));
 }
 
 static const char *const static_signal_table[] = {
-        [SIGHUP] = "HUP",
-        [SIGINT] = "INT",
-        [SIGQUIT] = "QUIT",
-        [SIGILL] = "ILL",
-        [SIGTRAP] = "TRAP",
-        [SIGABRT] = "ABRT",
-        [SIGBUS] = "BUS",
-        [SIGFPE] = "FPE",
-        [SIGKILL] = "KILL",
-        [SIGUSR1] = "USR1",
-        [SIGSEGV] = "SEGV",
-        [SIGUSR2] = "USR2",
-        [SIGPIPE] = "PIPE",
-        [SIGALRM] = "ALRM",
-        [SIGTERM] = "TERM",
+        [SIGHUP]    = "HUP",
+        [SIGINT]    = "INT",
+        [SIGQUIT]   = "QUIT",
+        [SIGILL]    = "ILL",
+        [SIGTRAP]   = "TRAP",
+        [SIGABRT]   = "ABRT",
+        [SIGBUS]    = "BUS",
+        [SIGFPE]    = "FPE",
+        [SIGKILL]   = "KILL",
+        [SIGUSR1]   = "USR1",
+        [SIGSEGV]   = "SEGV",
+        [SIGUSR2]   = "USR2",
+        [SIGPIPE]   = "PIPE",
+        [SIGALRM]   = "ALRM",
+        [SIGTERM]   = "TERM",
 #ifdef SIGSTKFLT
         [SIGSTKFLT] = "STKFLT",  /* Linux on SPARC doesn't know SIGSTKFLT */
 #endif
-        [SIGCHLD] = "CHLD",
-        [SIGCONT] = "CONT",
-        [SIGSTOP] = "STOP",
-        [SIGTSTP] = "TSTP",
-        [SIGTTIN] = "TTIN",
-        [SIGTTOU] = "TTOU",
-        [SIGURG] = "URG",
-        [SIGXCPU] = "XCPU",
-        [SIGXFSZ] = "XFSZ",
+        [SIGCHLD]   = "CHLD",
+        [SIGCONT]   = "CONT",
+        [SIGSTOP]   = "STOP",
+        [SIGTSTP]   = "TSTP",
+        [SIGTTIN]   = "TTIN",
+        [SIGTTOU]   = "TTOU",
+        [SIGURG]    = "URG",
+        [SIGXCPU]   = "XCPU",
+        [SIGXFSZ]   = "XFSZ",
         [SIGVTALRM] = "VTALRM",
-        [SIGPROF] = "PROF",
-        [SIGWINCH] = "WINCH",
-        [SIGIO] = "IO",
-        [SIGPWR] = "PWR",
-        [SIGSYS] = "SYS"
+        [SIGPROF]   = "PROF",
+        [SIGWINCH]  = "WINCH",
+        [SIGIO]     = "IO",
+        [SIGPWR]    = "PWR",
+        [SIGSYS]    = "SYS"
 };
 
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP(static_signal, int);
diff --git a/src/libnm-systemd-shared/src/basic/signal-util.h b/src/libnm-systemd-shared/src/basic/signal-util.h
index ad2ba841..8826fbeb 100644
--- a/src/libnm-systemd-shared/src/basic/signal-util.h
+++ b/src/libnm-systemd-shared/src/basic/signal-util.h
@@ -31,8 +31,11 @@ int sigaction_many_internal(const struct sigaction *sa, ...);
 #define sigaction_many(sa, ...)                                         \
         sigaction_many_internal(sa, __VA_ARGS__, -1)
 
-int sigset_add_many(sigset_t *ss, ...);
-int sigprocmask_many(int how, sigset_t *old, ...);
+int sigset_add_many_internal(sigset_t *ss, ...);
+#define sigset_add_many(...) sigset_add_many_internal(__VA_ARGS__, -1)
+
+int sigprocmask_many_internal(int how, sigset_t *old, ...);
+#define sigprocmask_many(...) sigprocmask_many_internal(__VA_ARGS__, -1)
 
 const char *signal_to_string(int i) _const_;
 int signal_from_string(const char *s) _pure_;
@@ -46,7 +49,7 @@ static inline void block_signals_reset(sigset_t *ss) {
 #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); \
+                assert_se(sigprocmask_many(SIG_BLOCK, &_t, __VA_ARGS__) >= 0);     \
                 _t;                                                                \
         })
 
diff --git a/src/libnm-systemd-shared/src/basic/siphash24.h b/src/libnm-systemd-shared/src/basic/siphash24.h
index e46f3cc5..f9867630 100644
--- a/src/libnm-systemd-shared/src/basic/siphash24.h
+++ b/src/libnm-systemd-shared/src/basic/siphash24.h
@@ -52,15 +52,16 @@ siphash24 (const void *in, size_t inlen, const uint8_t k[16])
 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))
+#define siphash24_compress_typesafe(in, state)                  \
+        siphash24_compress(&(in), sizeof(typeof(in)), (state))
 
 static inline void siphash24_compress_boolean(bool in, struct siphash *state) {
-        uint8_t i = in;
-
-        siphash24_compress(&i, sizeof i, state);
+        siphash24_compress_byte(in, state);
 }
 
 static inline void siphash24_compress_usec_t(usec_t in, struct siphash *state) {
-        siphash24_compress(&in, sizeof in, state);
+        uint64_t u = htole64(in);
+        siphash24_compress_typesafe(u, state);
 }
 
 static inline void siphash24_compress_safe(const void *in, size_t inlen, struct siphash *state) {
diff --git a/src/libnm-systemd-shared/src/basic/socket-util.c b/src/libnm-systemd-shared/src/basic/socket-util.c
index 9b411e07..df3e2c17 100644
--- a/src/libnm-systemd-shared/src/basic/socket-util.c
+++ b/src/libnm-systemd-shared/src/basic/socket-util.c
@@ -46,6 +46,11 @@
 #  define IDN_FLAGS 0
 #endif
 
+/* From the kernel's include/net/scm.h */
+#ifndef SCM_MAX_FD
+#  define SCM_MAX_FD 253
+#endif
+
 static const char* const socket_address_type_table[] = {
         [SOCK_STREAM] =    "Stream",
         [SOCK_DGRAM] =     "Datagram",
@@ -547,7 +552,7 @@ int sockaddr_pretty(
                         } else {
                                 if (path[path_len - 1] == '\0')
                                         /* We expect a terminating NUL and don't print it */
-                                        path_len --;
+                                        path_len--;
 
                                 p = cescape_length(path, path_len);
                         }
@@ -628,28 +633,33 @@ int getsockname_pretty(int fd, char **ret) {
         return sockaddr_pretty(&sa.sa, salen, false, true, ret);
 }
 
-int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret) {
+int socknameinfo_pretty(const struct sockaddr *sa, socklen_t salen, char **ret) {
+        char host[NI_MAXHOST];
         int r;
-        char host[NI_MAXHOST], *ret;
 
-        assert(_ret);
+        assert(sa);
+        assert(salen > sizeof(sa_family_t));
 
-        r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0, IDN_FLAGS);
+        r = getnameinfo(sa, salen, host, sizeof(host), /* service= */ NULL, /* service_len= */ 0, IDN_FLAGS);
         if (r != 0) {
-                int saved_errno = errno;
+                if (r == EAI_MEMORY)
+                        return log_oom_debug();
+                if (r == EAI_SYSTEM)
+                        log_debug_errno(errno, "getnameinfo() failed, ignoring: %m");
+                else
+                        log_debug("getnameinfo() failed, ignoring: %s", gai_strerror(r));
 
-                r = sockaddr_pretty(&sa->sa, salen, true, true, &ret);
-                if (r < 0)
-                        return r;
+                return sockaddr_pretty(sa, salen, /* translate_ipv6= */ true, /* include_port= */ true, ret);
+        }
 
-                log_debug_errno(saved_errno, "getnameinfo(%s) failed: %m", ret);
-        } else {
-                ret = strdup(host);
-                if (!ret)
+        if (ret) {
+                char *copy = strdup(host);
+                if (!copy)
                         return -ENOMEM;
+
+                *ret = copy;
         }
 
-        *_ret = ret;
         return 0;
 }
 
@@ -874,13 +884,11 @@ bool address_label_valid(const char *p) {
 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)
+        if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n) < 0)
                 return -errno;
 
         if (n != sizeof(struct ucred))
@@ -909,8 +917,10 @@ int getpeersec(int fd, char **ret) {
                 if (!s)
                         return -ENOMEM;
 
-                if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n) >= 0)
+                if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n) >= 0) {
+                        s[n] = 0;
                         break;
+                }
 
                 if (errno != ERANGE)
                         return -errno;
@@ -927,12 +937,16 @@ int getpeersec(int fd, char **ret) {
 }
 
 int getpeergroups(int fd, gid_t **ret) {
-        socklen_t n = sizeof(gid_t) * 64;
+        socklen_t n = sizeof(gid_t) * 64U;
         _cleanup_free_ gid_t *d = NULL;
 
         assert(fd >= 0);
         assert(ret);
 
+        long ngroups_max = sysconf(_SC_NGROUPS_MAX);
+        if (ngroups_max > 0)
+                n = MAX(n, sizeof(gid_t) * (socklen_t) ngroups_max);
+
         for (;;) {
                 d = malloc(n);
                 if (!d)
@@ -950,7 +964,7 @@ int getpeergroups(int fd, gid_t **ret) {
         assert_se(n % sizeof(gid_t) == 0);
         n /= sizeof(gid_t);
 
-        if ((socklen_t) (int) n != n)
+        if (n > INT_MAX)
                 return -E2BIG;
 
         *ret = TAKE_PTR(d);
@@ -958,6 +972,68 @@ int getpeergroups(int fd, gid_t **ret) {
         return (int) n;
 }
 
+int getpeerpidfd(int fd) {
+        socklen_t n = sizeof(int);
+        int pidfd = -EBADF;
+
+        assert(fd >= 0);
+
+        if (getsockopt(fd, SOL_SOCKET, SO_PEERPIDFD, &pidfd, &n) < 0)
+                return -errno;
+
+        if (n != sizeof(int))
+                return -EIO;
+
+        return pidfd;
+}
+
+ssize_t send_many_fds_iov_sa(
+                int transport_fd,
+                int *fds_array, size_t n_fds_array,
+                const struct iovec *iov, size_t iovlen,
+                const struct sockaddr *sa, socklen_t len,
+                int flags) {
+
+        _cleanup_free_ struct cmsghdr *cmsg = NULL;
+        struct msghdr mh = {
+                .msg_name = (struct sockaddr*) sa,
+                .msg_namelen = len,
+                .msg_iov = (struct iovec *)iov,
+                .msg_iovlen = iovlen,
+        };
+        ssize_t k;
+
+        assert(transport_fd >= 0);
+        assert(fds_array || n_fds_array == 0);
+
+        /* The kernel will reject sending more than SCM_MAX_FD FDs at once */
+        if (n_fds_array > SCM_MAX_FD)
+                return -E2BIG;
+
+        /* We need either an FD array or data to send. If there's nothing, return an error. */
+        if (n_fds_array == 0 && !iov)
+                return -EINVAL;
+
+        if (n_fds_array > 0) {
+                mh.msg_controllen = CMSG_SPACE(sizeof(int) * n_fds_array);
+                mh.msg_control = cmsg = malloc(mh.msg_controllen);
+                if (!cmsg)
+                        return -ENOMEM;
+
+                *cmsg = (struct cmsghdr) {
+                        .cmsg_len = CMSG_LEN(sizeof(int) * n_fds_array),
+                        .cmsg_level = SOL_SOCKET,
+                        .cmsg_type = SCM_RIGHTS,
+                };
+                memcpy(CMSG_DATA(cmsg), fds_array, sizeof(int) * n_fds_array);
+        }
+        k = sendmsg(transport_fd, &mh, MSG_NOSIGNAL | flags);
+        if (k < 0)
+                return (ssize_t) -errno;
+
+        return k;
+}
+
 ssize_t send_one_fd_iov_sa(
                 int transport_fd,
                 int fd,
@@ -1013,6 +1089,78 @@ int send_one_fd_sa(
         return (int) send_one_fd_iov_sa(transport_fd, fd, NULL, 0, sa, len, flags);
 }
 
+ssize_t receive_many_fds_iov(
+                int transport_fd,
+                struct iovec *iov, size_t iovlen,
+                int **ret_fds_array, size_t *ret_n_fds_array,
+                int flags) {
+
+        CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int) * SCM_MAX_FD)) control;
+        struct msghdr mh = {
+                .msg_control = &control,
+                .msg_controllen = sizeof(control),
+                .msg_iov = iov,
+                .msg_iovlen = iovlen,
+        };
+        _cleanup_free_ int *fds_array = NULL;
+        size_t n_fds_array = 0;
+        struct cmsghdr *cmsg;
+        ssize_t k;
+
+        assert(transport_fd >= 0);
+        assert(ret_fds_array);
+        assert(ret_n_fds_array);
+
+        /*
+         * Receive many FDs via @transport_fd. We don't care for the transport-type. We retrieve all the FDs
+         * at once. This is best used in combination with send_many_fds().
+         */
+
+        k = recvmsg_safe(transport_fd, &mh, MSG_CMSG_CLOEXEC | flags);
+        if (k < 0)
+                return k;
+
+        CMSG_FOREACH(cmsg, &mh)
+                if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+                        size_t n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+
+                        fds_array = GREEDY_REALLOC(fds_array, n_fds_array + n);
+                        if (!fds_array) {
+                                cmsg_close_all(&mh);
+                                return -ENOMEM;
+                        }
+
+                        memcpy(fds_array + n_fds_array, CMSG_TYPED_DATA(cmsg, int), sizeof(int) * n);
+                        n_fds_array += n;
+                }
+
+        if (n_fds_array == 0) {
+                cmsg_close_all(&mh);
+
+                /* If didn't receive an FD or any data, return an error. */
+                if (k == 0)
+                        return -EIO;
+        }
+
+        *ret_fds_array = TAKE_PTR(fds_array);
+        *ret_n_fds_array = n_fds_array;
+
+        return k;
+}
+
+int receive_many_fds(int transport_fd, int **ret_fds_array, size_t *ret_n_fds_array, int flags) {
+        ssize_t k;
+
+        k = receive_many_fds_iov(transport_fd, NULL, 0, ret_fds_array, ret_n_fds_array, flags);
+        if (k == 0)
+                return 0;
+
+        /* k must be negative, since receive_many_fds_iov() only returns a positive value if data was received
+         * through the iov. */
+        assert(k < 0);
+        return (int) k;
+}
+
 ssize_t receive_one_fd_iov(
                 int transport_fd,
                 struct iovec *iov, size_t iovlen,
@@ -1189,7 +1337,7 @@ void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf,
         assert(buf_len > 0);
 
         /* This is similar to cmsg_find_data(), but copy the found data to buf. This should be typically used
-         * when reading possibly unaligned data such as timestamp, as time_t is 64bit and size_t is 32bit on
+         * when reading possibly unaligned data such as timestamp, as time_t is 64-bit and size_t is 32-bit on
          * RISCV32. See issue #27241. */
 
         cmsg = cmsg_find(mh, level, type, CMSG_LEN(buf_len));
@@ -1532,6 +1680,50 @@ int socket_address_parse_unix(SocketAddress *ret_address, const char *s) {
 }
 
 #if 0 /* NM_IGNORED */
+int vsock_parse_port(const char *s, unsigned *ret) {
+        int r;
+
+        assert(ret);
+
+        if (!s)
+                return -EINVAL;
+
+        unsigned u;
+        r = safe_atou(s, &u);
+        if (r < 0)
+                return r;
+
+        /* Port 0 is apparently valid and not special in AF_VSOCK (unlike on IP). But VMADDR_PORT_ANY
+         * (UINT32_MAX) is. Hence refuse that. */
+
+        if (u == VMADDR_PORT_ANY)
+                return -EINVAL;
+
+        *ret = u;
+        return 0;
+}
+
+int vsock_parse_cid(const char *s, unsigned *ret) {
+        assert(ret);
+
+        if (!s)
+                return -EINVAL;
+
+        /* Parsed an AF_VSOCK "CID". This is a 32bit entity, and the usual type is "unsigned". We recognize
+         * the three special CIDs as strings, and otherwise parse the numeric CIDs. */
+
+        if (streq(s, "hypervisor"))
+                *ret = VMADDR_CID_HYPERVISOR;
+        else if (streq(s, "local"))
+                *ret = VMADDR_CID_LOCAL;
+        else if (streq(s, "host"))
+                *ret = VMADDR_CID_HOST;
+        else
+                return safe_atou(s, ret);
+
+        return 0;
+}
+
 int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) {
         /* AF_VSOCK socket in vsock:cid:port notation */
         _cleanup_free_ char *n = NULL;
@@ -1557,7 +1749,7 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) {
         if (!e)
                 return -EINVAL;
 
-        r = safe_atou(e+1, &port);
+        r = vsock_parse_port(e+1, &port);
         if (r < 0)
                 return r;
 
@@ -1568,15 +1760,15 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) {
         if (isempty(n))
                 cid = VMADDR_CID_ANY;
         else {
-                r = safe_atou(n, &cid);
+                r = vsock_parse_cid(n, &cid);
                 if (r < 0)
                         return r;
         }
 
         *ret_address = (SocketAddress) {
                 .sockaddr.vm = {
-                        .svm_cid = cid,
                         .svm_family = AF_VSOCK,
+                        .svm_cid = cid,
                         .svm_port = port,
                 },
                 .type = type,
@@ -1585,4 +1777,19 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) {
 
         return 0;
 }
+
+int vsock_get_local_cid(unsigned *ret) {
+        _cleanup_close_ int vsock_fd = -EBADF;
+
+        assert(ret);
+
+        vsock_fd = open("/dev/vsock", O_RDONLY|O_CLOEXEC);
+        if (vsock_fd < 0)
+                return log_debug_errno(errno, "Failed to open /dev/vsock: %m");
+
+        if (ioctl(vsock_fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, ret) < 0)
+                return log_debug_errno(errno, "Failed to query local AF_VSOCK CID: %m");
+
+        return 0;
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/socket-util.h b/src/libnm-systemd-shared/src/basic/socket-util.h
index 26f9636f..15c7d1c5 100644
--- a/src/libnm-systemd-shared/src/basic/socket-util.h
+++ b/src/libnm-systemd-shared/src/basic/socket-util.h
@@ -115,7 +115,7 @@ int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_
 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);
+int socknameinfo_pretty(const struct sockaddr *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_;
@@ -154,7 +154,30 @@ 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);
+int getpeerpidfd(int fd);
 
+ssize_t send_many_fds_iov_sa(
+                int transport_fd,
+                int *fds_array, size_t n_fds_array,
+                const struct iovec *iov, size_t iovlen,
+                const struct sockaddr *sa, socklen_t len,
+                int flags);
+static inline ssize_t send_many_fds_iov(
+                int transport_fd,
+                int *fds_array, size_t n_fds_array,
+                const struct iovec *iov, size_t iovlen,
+                int flags) {
+
+        return send_many_fds_iov_sa(transport_fd, fds_array, n_fds_array, iov, iovlen, NULL, 0, flags);
+}
+static inline int send_many_fds(
+                int transport_fd,
+                int *fds_array,
+                size_t n_fds_array,
+                int flags) {
+
+        return send_many_fds_iov_sa(transport_fd, fds_array, n_fds_array, NULL, 0, NULL, 0, flags);
+}
 ssize_t send_one_fd_iov_sa(
                 int transport_fd,
                 int fd,
@@ -169,6 +192,8 @@ int send_one_fd_sa(int transport_fd,
 #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 receive_many_fds_iov(int transport_fd, struct iovec *iov, size_t iovlen, int **ret_fds_array, size_t *ret_n_fds_array, int flags);
+int receive_many_fds(int transport_fd, int **ret_fds_array, size_t *ret_n_fds_array, int flags);
 
 ssize_t next_datagram_size_fd(int fd);
 
@@ -181,7 +206,7 @@ int flush_accept(int fd);
  * at compile time, that the requested type has a smaller or same alignment as 'struct cmsghdr', and one
  * during runtime, that the actual pointer matches the alignment too. This is supposed to catch cases such as
  * 'struct timeval' is embedded into 'struct cmsghdr' on architectures where the alignment of the former is 8
- * bytes (because of a 64bit time_t), but of the latter is 4 bytes (because size_t is 32bit), such as
+ * bytes (because of a 64-bit time_t), but of the latter is 4 bytes (because size_t is 32 bits), such as
  * riscv32. */
 #define CMSG_TYPED_DATA(cmsg, type)                                     \
         ({                                                              \
@@ -296,7 +321,7 @@ static inline int getsockopt_int(int fd, int level, int optname, int *ret) {
 int socket_bind_to_ifname(int fd, const char *ifname);
 int socket_bind_to_ifindex(int fd, int ifindex);
 
-/* Define a 64bit version of timeval/timespec in any case, even on 32bit userspace. */
+/* Define a 64-bit version of timeval/timespec in any case, even on 32-bit userspace. */
 struct timeval_large {
         uint64_t tvl_sec, tvl_usec;
 };
@@ -304,7 +329,7 @@ struct timespec_large {
         uint64_t tvl_sec, tvl_nsec;
 };
 
-/* glibc duplicates timespec/timeval on certain 32bit archs, once in 32bit and once in 64bit.
+/* glibc duplicates timespec/timeval on certain 32-bit arches, once in 32-bit and once in 64-bit.
  * See __convert_scm_timestamps() in glibc source code. Hence, we need additional buffer space for them
  * to prevent from recvmsg_safe() returning -EXFULL. */
 #define CMSG_SPACE_TIMEVAL                                              \
@@ -353,6 +378,14 @@ int socket_get_mtu(int fd, int af, size_t *ret);
 
 int connect_unix_path(int fd, int dir_fd, const char *path);
 
+static inline bool VSOCK_CID_IS_REGULAR(unsigned cid) {
+        /* 0, 1, 2, UINT32_MAX are special, refuse those */
+        return cid > 2 && cid < UINT32_MAX;
+}
+
+int vsock_parse_port(const char *s, unsigned *ret);
+int vsock_parse_cid(const char *s, unsigned *ret);
+
 /* Parses AF_UNIX and AF_VSOCK addresses. AF_INET[6] require some netlink calls, so it cannot be in
  * src/basic/ and is done from 'socket_local_address from src/shared/. Return -EPROTO in case of
  * protocol mismatch. */
@@ -365,3 +398,5 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s);
  * /proc/sys/net/core/somaxconn anyway, thus by setting this to unbounded we just make that sysctl file
  * authoritative. */
 #define SOMAXCONN_DELUXE INT_MAX
+
+int vsock_get_local_cid(unsigned *ret);
diff --git a/src/libnm-systemd-shared/src/basic/sort-util.h b/src/libnm-systemd-shared/src/basic/sort-util.h
index 52d611b8..9c818bd7 100644
--- a/src/libnm-systemd-shared/src/basic/sort-util.h
+++ b/src/libnm-systemd-shared/src/basic/sort-util.h
@@ -18,7 +18,7 @@ void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
         ({                                                              \
                 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]), (comparison_userdata_fn_t) _func_, userdata); \
+                (typeof((b)[0])*) xbsearch_r((const void*) _k, (b), (n), sizeof((b)[0]), (comparison_userdata_fn_t) _func_, userdata); \
         })
 
 /**
@@ -38,7 +38,7 @@ static inline void* bsearch_safe(const void *key, const void *base,
         ({                                                              \
                 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]), (comparison_fn_t) _func_); \
+                (typeof((b)[0])*) bsearch_safe((const void*) _k, (b), (n), sizeof((b)[0]), (comparison_fn_t) _func_); \
         })
 
 /**
@@ -61,7 +61,6 @@ static inline void _qsort_safe(void *base, size_t nmemb, size_t size, comparison
                 _qsort_safe((p), (n), sizeof((p)[0]), (comparison_fn_t) _func_); \
         })
 
-#if 0 /* NM_IGNORED */
 static inline void qsort_r_safe(void *base, size_t nmemb, size_t size, comparison_userdata_fn_t compar, void *userdata) {
         if (nmemb <= 1)
                 return;
@@ -75,6 +74,6 @@ static inline void qsort_r_safe(void *base, size_t nmemb, size_t size, compariso
                 int (*_func_)(const typeof((p)[0])*, const typeof((p)[0])*, typeof(userdata)) = func; \
                 qsort_r_safe((p), (n), sizeof((p)[0]), (comparison_userdata_fn_t) _func_, userdata); \
         })
-#endif /* NM_IGNORED */
 
 int cmp_int(const int *a, const int *b);
+int cmp_uint16(const uint16_t *a, const uint16_t *b);
diff --git a/src/libnm-systemd-shared/src/basic/stat-util.c b/src/libnm-systemd-shared/src/basic/stat-util.c
index a81ee468..4da3845b 100644
--- a/src/libnm-systemd-shared/src/basic/stat-util.c
+++ b/src/libnm-systemd-shared/src/basic/stat-util.c
@@ -27,46 +27,130 @@
 #include "stat-util.h"
 #include "string-util.h"
 
-#if 0 /* NM_IGNORED */
-int is_symlink(const char *path) {
-        struct stat info;
+static int verify_stat_at(
+                int fd,
+                const char *path,
+                bool follow,
+                int (*verify_func)(const struct stat *st),
+                bool verify) {
+        struct stat st;
+        int r;
 
-        assert(path);
+        assert(fd >= 0 || fd == AT_FDCWD);
+        assert(!isempty(path) || !follow);
+        assert(verify_func);
 
-        if (lstat(path, &info) < 0)
+        if (fstatat(fd, strempty(path), &st,
+                    (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0)
                 return -errno;
 
-        return !!S_ISLNK(info.st_mode);
+        r = verify_func(&st);
+        return verify ? r : r >= 0;
 }
-#endif /* NM_IGNORED */
 
-int is_dir_full(int atfd, const char* path, bool follow) {
-        struct stat st;
-        int r;
+int stat_verify_regular(const struct stat *st) {
+        assert(st);
 
-        assert(atfd >= 0 || atfd == AT_FDCWD);
-        assert(atfd >= 0 || path);
+        /* Checks whether the specified stat() structure refers to a regular file. If not returns an
+         * appropriate error code. */
 
-        if (path)
-                r = fstatat(atfd, path, &st, follow ? 0 : AT_SYMLINK_NOFOLLOW);
-        else
-                r = fstat(atfd, &st);
-        if (r < 0)
-                return -errno;
+        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 !!S_ISDIR(st.st_mode);
+        return 0;
 }
 
-#if 0 /* NM_IGNORED */
-int is_device_node(const char *path) {
-        struct stat info;
+int verify_regular_at(int fd, const char *path, bool follow) {
+        return verify_stat_at(fd, path, follow, stat_verify_regular, true);
+}
 
-        assert(path);
+int fd_verify_regular(int fd) {
+        assert(fd >= 0);
+        return verify_regular_at(fd, NULL, false);
+}
 
-        if (lstat(path, &info) < 0)
-                return -errno;
+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) {
+        assert(fd >= 0);
+        return verify_stat_at(fd, NULL, false, stat_verify_directory, true);
+}
+
+int is_dir_at(int fd, const char *path, bool follow) {
+        return verify_stat_at(fd, path, follow, stat_verify_directory, false);
+}
+
+int is_dir(const char *path, bool follow) {
+        assert(!isempty(path));
+        return is_dir_at(AT_FDCWD, path, follow);
+}
+
+int stat_verify_symlink(const struct stat *st) {
+        assert(st);
+
+        if (S_ISDIR(st->st_mode))
+                return -EISDIR;
+
+        if (!S_ISLNK(st->st_mode))
+                return -ENOLINK;
+
+        return 0;
+}
+
+int is_symlink(const char *path) {
+        assert(!isempty(path));
+        return verify_stat_at(AT_FDCWD, path, false, stat_verify_symlink, false);
+}
+
+int stat_verify_linked(const struct stat *st) {
+        assert(st);
+
+        if (st->st_nlink <= 0)
+                return -EIDRM; /* recognizable error. */
+
+        return 0;
+}
+
+int fd_verify_linked(int fd) {
+        assert(fd >= 0);
+        return verify_stat_at(fd, NULL, false, stat_verify_linked, true);
+}
+
+int stat_verify_device_node(const struct stat *st) {
+        assert(st);
+
+        if (S_ISLNK(st->st_mode))
+                return -ELOOP;
+
+        if (S_ISDIR(st->st_mode))
+                return -EISDIR;
+
+        if (!S_ISBLK(st->st_mode) && !S_ISCHR(st->st_mode))
+                return -ENOTTY;
+
+        return 0;
+}
 
-        return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode));
+#if 0 /* NM_IGNORED */
+int is_device_node(const char *path) {
+        assert(!isempty(path));
+        return verify_stat_at(AT_FDCWD, path, false, stat_verify_device_node, false);
 }
 
 int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) {
@@ -193,14 +277,12 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl
         struct stat a, b;
 
         assert(fda >= 0 || fda == AT_FDCWD);
-        assert(filea);
         assert(fdb >= 0 || fdb == AT_FDCWD);
-        assert(fileb);
 
-        if (fstatat(fda, filea, &a, flags) < 0)
+        if (fstatat(fda, strempty(filea), &a, flags) < 0)
                 return log_debug_errno(errno, "Cannot stat %s: %m", filea);
 
-        if (fstatat(fdb, fileb, &b, flags) < 0)
+        if (fstatat(fdb, strempty(fileb), &b, flags) < 0)
                 return log_debug_errno(errno, "Cannot stat %s: %m", fileb);
 
         return stat_inode_same(&a, &b);
@@ -270,72 +352,6 @@ int path_is_network_fs(const char *path) {
 }
 #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 verify_regular_at(int dir_fd, const char *path, bool follow) {
-        struct stat st;
-
-        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
-        assert(path);
-
-        if (fstatat(dir_fd, path, &st, (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0)
-                return -errno;
-
-        return stat_verify_regular(&st);
-}
-
-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);
-}
-#endif /* NM_IGNORED */
-
 int proc_mounted(void) {
         int r;
 
@@ -405,21 +421,35 @@ bool statx_mount_same(const struct new_statx *a, const struct new_statx *b) {
                 a->stx_dev_minor == b->stx_dev_minor;
 }
 
+static bool is_statx_fatal_error(int err, int flags) {
+        assert(err < 0);
+
+        /* If statx() is not supported or if we see EPERM (which might indicate seccomp filtering or so),
+         * let's do a fallback. Note that on EACCES we'll not fall back, since that is likely an indication of
+         * fs access issues, which we should propagate. */
+        if (ERRNO_IS_NOT_SUPPORTED(err) || err == -EPERM)
+                return false;
+
+        /* When unsupported flags are specified, glibc's fallback function returns -EINVAL.
+         * See statx_generic() in glibc. */
+        if (err != -EINVAL)
+                return true;
+
+        if ((flags & ~(AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | AT_STATX_SYNC_AS_STAT)) != 0)
+                return false; /* Unsupported flags are specified. Let's try to use our implementation. */
+
+        return true;
+}
+
 int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct statx *sx) {
         static bool avoid_statx = false;
         struct stat st;
+        int r;
 
         if (!avoid_statx) {
-                if (statx(dfd, path, flags, mask, sx) < 0) {
-                        if (!ERRNO_IS_NOT_SUPPORTED(errno) && errno != EPERM)
-                                return -errno;
-
-                        /* If statx() is not supported or if we see EPERM (which might indicate seccomp
-                         * filtering or so), let's do a fallback. Not that on EACCES we'll not fall back,
-                         * since that is likely an indication of fs access issues, which we should
-                         * propagate */
-                } else
-                        return 0;
+                r = RET_NERRNO(statx(dfd, path, flags, mask, sx));
+                if (r >= 0 || is_statx_fatal_error(r, flags))
+                        return r;
 
                 avoid_statx = true;
         }
@@ -468,7 +498,7 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *ret) {
         assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
         assert(ret);
 
-        fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY, /* xopen_flags = */ 0, /* mode = */ 0);
+        fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY);
         if (fd < 0)
                 return fd;
 
@@ -477,8 +507,8 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *ret) {
 
 #if 0 /* NM_IGNORED */
 void inode_hash_func(const struct stat *q, struct siphash *state) {
-        siphash24_compress(&q->st_dev, sizeof(q->st_dev), state);
-        siphash24_compress(&q->st_ino, sizeof(q->st_ino), state);
+        siphash24_compress_typesafe(q->st_dev, state);
+        siphash24_compress_typesafe(q->st_ino, state);
 }
 
 int inode_compare_func(const struct stat *a, const struct stat *b) {
@@ -503,6 +533,8 @@ const char* inode_type_to_string(mode_t m) {
                 return "reg";
         case S_IFDIR:
                 return "dir";
+        case S_IFLNK:
+                return "lnk";
         case S_IFCHR:
                 return "chr";
         case S_IFBLK:
@@ -515,4 +547,26 @@ const char* inode_type_to_string(mode_t m) {
 
         return NULL;
 }
+
+mode_t inode_type_from_string(const char *s) {
+        if (!s)
+                return MODE_INVALID;
+
+        if (streq(s, "reg"))
+                return S_IFREG;
+        if (streq(s, "dir"))
+                return S_IFDIR;
+        if (streq(s, "lnk"))
+                return S_IFLNK;
+        if (streq(s, "chr"))
+                return S_IFCHR;
+        if (streq(s, "blk"))
+                return S_IFBLK;
+        if (streq(s, "fifo"))
+                return S_IFIFO;
+        if (streq(s, "sock"))
+                return S_IFSOCK;
+
+        return MODE_INVALID;
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/stat-util.h b/src/libnm-systemd-shared/src/basic/stat-util.h
index ae0aaf8f..7eb951a7 100644
--- a/src/libnm-systemd-shared/src/basic/stat-util.h
+++ b/src/libnm-systemd-shared/src/basic/stat-util.h
@@ -12,15 +12,24 @@
 #include "macro.h"
 #include "missing_stat.h"
 #include "siphash24.h"
+#include "time-util.h"
 
+int stat_verify_regular(const struct stat *st);
+int verify_regular_at(int fd, const char *path, bool follow);
+int fd_verify_regular(int fd);
+
+int stat_verify_directory(const struct stat *st);
+int fd_verify_directory(int fd);
+int is_dir_at(int fd, const char *path, bool follow);
+int is_dir(const char *path, bool follow);
+
+int stat_verify_symlink(const struct stat *st);
 int is_symlink(const char *path);
-int is_dir_full(int atfd, const char *fname, bool follow);
-static inline int is_dir(const char *path, bool follow) {
-        return is_dir_full(AT_FDCWD, path, follow);
-}
-static inline int is_dir_fd(int fd) {
-        return is_dir_full(fd, NULL, false);
-}
+
+int stat_verify_linked(const struct stat *st);
+int fd_verify_linked(int fd);
+
+int stat_verify_device_node(const struct stat *st);
 int is_device_node(const char *path);
 
 int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup);
@@ -71,13 +80,6 @@ int path_is_network_fs(const char *path);
  */
 #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 verify_regular_at(int dir_fd, const char *path, bool follow);
-
-int stat_verify_directory(const struct stat *st);
-int fd_verify_directory(int fd);
-
 int proc_mounted(void);
 
 bool stat_inode_same(const struct stat *a, const struct stat *b);
@@ -109,8 +111,18 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *ret);
         } var
 #endif
 
+#if 0 /* NM_IGNORED */
+static inline usec_t statx_timestamp_load(const struct statx_timestamp *ts) {
+        return timespec_load(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec });
+}
+static inline nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) {
+        return timespec_load_nsec(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec });
+}
+#endif /* NM_IGNORED */
+
 void inode_hash_func(const struct stat *q, struct siphash *state);
 int inode_compare_func(const struct stat *a, const struct stat *b);
 extern const struct hash_ops inode_hash_ops;
 
 const char* inode_type_to_string(mode_t m);
+mode_t inode_type_from_string(const char *s);
diff --git a/src/libnm-systemd-shared/src/basic/string-util.c b/src/libnm-systemd-shared/src/basic/string-util.c
index 1afa49bb..59e65918 100644
--- a/src/libnm-systemd-shared/src/basic/string-util.c
+++ b/src/libnm-systemd-shared/src/basic/string-util.c
@@ -18,6 +18,7 @@
 #include "macro.h"
 #include "memory-util.h"
 #include "memstream-util.h"
+#include "path-util.h"
 #include "string-util.h"
 #include "strv.h"
 #include "terminal-util.h"
@@ -174,10 +175,15 @@ char *delete_trailing_chars(char *s, const char *bad) {
 }
 #endif /* NM_IGNORED */
 
-char *truncate_nl(char *s) {
+char *truncate_nl_full(char *s, size_t *ret_len) {
+        size_t n;
+
         assert(s);
 
-        s[strcspn(s, NEWLINE)] = 0;
+        n = strcspn(s, NEWLINE);
+        s[n] = '\0';
+        if (ret_len)
+                *ret_len = n;
         return s;
 }
 
@@ -294,6 +300,62 @@ static int write_ellipsis(char *buf, bool unicode) {
         return 3;
 }
 
+static size_t ansi_sequence_length(const char *s, size_t len) {
+        assert(s);
+
+        if (len < 2)
+                return 0;
+
+        if (s[0] != 0x1B)  /* ASCII 27, aka ESC, aka Ctrl-[ */
+                return 0;  /* Not the start of a sequence */
+
+        if (s[1] == 0x5B) { /* [, start of CSI sequence */
+                size_t i = 2;
+
+                if (i == len)
+                        return 0;
+
+                while (s[i] >= 0x30 && s[i] <= 0x3F) /* Parameter bytes */
+                        if (++i == len)
+                                return 0;
+                while (s[i] >= 0x20 && s[i] <= 0x2F) /* Intermediate bytes */
+                        if (++i == len)
+                                return 0;
+                if (s[i] >= 0x40 && s[i] <= 0x7E) /* Final byte */
+                        return i + 1;
+                return 0;  /* Bad sequence */
+
+        } else if (s[1] >= 0x40 && s[1] <= 0x5F) /* other non-CSI Fe sequence */
+                return 2;
+
+        return 0;  /* Bad escape? */
+}
+
+static bool string_has_ansi_sequence(const char *s, size_t len) {
+        const char *t = s;
+
+        while ((t = memchr(s, 0x1B, len - (t - s))))
+                if (ansi_sequence_length(t, len - (t - s)) > 0)
+                        return true;
+        return false;
+}
+
+static size_t previous_ansi_sequence(const char *s, size_t length, const char **ret_where) {
+        /* Locate the previous ANSI sequence and save its start in *ret_where and return length. */
+
+        for (size_t i = length - 2; i > 0; i--) {  /* -2 because at least two bytes are needed */
+                size_t slen = ansi_sequence_length(s + (i - 1), length - (i - 1));
+                if (slen == 0)
+                        continue;
+
+                *ret_where = s + (i - 1);
+                return slen;
+        }
+
+        *ret_where = NULL;
+        return 0;
+}
+
 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;
@@ -353,7 +415,6 @@ static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_le
 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
@@ -377,73 +438,116 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne
         if (new_length == 0)
                 return strdup("");
 
-        /* If no multibyte characters use ascii_ellipsize_mem for speed */
-        if (ascii_is_valid_n(s, old_length))
+        bool has_ansi_seq = string_has_ansi_sequence(s, old_length);
+
+        /* If no multibyte characters or ANSI sequences, use ascii_ellipsize_mem for speed */
+        if (!has_ansi_seq && ascii_is_valid_n(s, old_length))
                 return ascii_ellipsize_mem(s, old_length, new_length, percent);
 
-        x = ((new_length - 1) * percent) / 100;
+        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;
+        for (i = s; i < s + old_length; ) {
+                size_t slen = has_ansi_seq ? ansi_sequence_length(i, old_length - (i - s)) : 0;
+                if (slen > 0) {
+                        i += slen;
+                        continue;  /* ANSI sequences don't take up any space in output */
+                }
 
+                char32_t c;
                 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
+                int w = unichar_iswide(c) ? 2 : 1;
+                if (k + w > x)
                         break;
+
+                k += w;
+                i += r;
         }
 
-        for (j = s + old_length; j > i; ) {
+        const char *ansi_start = s + old_length;
+        size_t ansi_len = 0;
+
+        for (const char *t = j = s + old_length; t > i && k < new_length; ) {
                 char32_t c;
                 int w;
-                const char *jj;
+                const char *tt;
 
-                jj = utf8_prev_char(j);
-                r = utf8_encoded_to_unichar(jj, &c);
+                if (has_ansi_seq && ansi_start >= t)
+                        /* Figure out the previous ANSI sequence, if any */
+                        ansi_len = previous_ansi_sequence(s, t - s, &ansi_start);
+
+                /* If the sequence extends all the way to the current position, skip it. */
+                if (has_ansi_seq && ansi_len > 0 && ansi_start + ansi_len == t) {
+                        t = ansi_start;
+                        continue;
+                }
+
+                tt = utf8_prev_char(t);
+                r = utf8_encoded_to_unichar(tt, &c);
                 if (r < 0)
                         return NULL;
 
                 w = unichar_iswide(c) ? 2 : 1;
-                if (k + w <= new_length) {
-                        k += w;
-                        j = jj;
-                } else
+                if (k + w > new_length)
                         break;
+
+                k += w;
+                j = t = tt;  /* j should always point to the first "real" character */
         }
-        assert(i <= j);
 
-        /* we don't actually need to ellipsize */
-        if (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);
+        if (k >= new_length) {
+                /* Make space for ellipsis, if required and possible. We know that the edge character is not
+                 * part of an ANSI sequence (because then we'd skip it). If the last character we looked at
+                 * was wide, we don't need to make space. */
+                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 we have ANSI, allow the same length as the source string + ellipsis. It'd be too involved to
+         * figure out what exact space is needed. Strings with ANSI sequences are most likely to be fairly
+         * short anyway. */
+        size_t alloc_len = has_ansi_seq ? old_length + 3 + 1 : len + 3 + len2 + 1;
+
+        char *e = new(char, alloc_len);
         if (!e)
                 return NULL;
 
         /*
-        printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n",
+        printf("old_length=%zu new_length=%zu x=%zu len=%zu len2=%zu k=%zu\n",
                old_length, new_length, x, len, len2, k);
         */
 
-        memcpy(e, s, len);
+        memcpy_safe(e, s, len);
         write_ellipsis(e + len, true);
-        memcpy(e + len + 3, j, len2);
-        *(e + len + 3 + len2) = '\0';
+
+        char *dst = e + len + 3;
+
+        if (has_ansi_seq)
+                /* Copy over any ANSI sequences in full */
+                for (const char *p = s + len; p < j; ) {
+                        size_t slen = ansi_sequence_length(p, j - p);
+                        if (slen > 0) {
+                                dst = mempcpy(dst, p, slen);
+                                p += slen;
+                        } else
+                                p = utf8_next_char(p);
+                }
+
+        memcpy_safe(dst, j, len2);
+        dst[len2] = '\0';
 
         return e;
 }
@@ -521,6 +625,9 @@ char *cellescape(char *buf, size_t len, const char *s) {
 char* strshorten(char *s, size_t l) {
         assert(s);
 
+        if (l >= SIZE_MAX-1) /* Would not change anything */
+                return s;
+
         if (strnlen(s, l+1) > l)
                 s[l] = 0;
 
@@ -528,14 +635,23 @@ char* strshorten(char *s, size_t l) {
 }
 
 int strgrowpad0(char **s, size_t l) {
+        size_t sz;
+
         assert(s);
 
+        if (*s) {
+                sz = strlen(*s) + 1;
+                if (sz >= l) /* never shrink */
+                        return 0;
+        } else
+                sz = 0;
+
         char *q = realloc(*s, l);
         if (!q)
                 return -ENOMEM;
+
         *s = q;
 
-        size_t sz = strlen(*s);
         memzero(*s + sz, l - sz);
         return 0;
 }
@@ -893,6 +1009,33 @@ oom:
         return -ENOMEM;
 }
 
+char *strextendn(char **x, const char *s, size_t l) {
+        assert(x);
+        assert(s || l == 0);
+
+        if (l == SIZE_MAX)
+                l = strlen_ptr(s);
+        else if (l > 0)
+                l = strnlen(s, l); /* ignore trailing noise */
+
+        if (l > 0 || !*x) {
+                size_t q;
+                char *m;
+
+                q = strlen_ptr(*x);
+                m = realloc(*x, q + l + 1);
+                if (!m)
+                        return NULL;
+
+                memcpy_safe(m + q, s, l);
+                m[q + l] = 0;
+
+                *x = m;
+        }
+
+        return *x;
+}
+
 char *strrep(const char *s, unsigned n) {
         char *r, *p;
         size_t l;
@@ -1179,6 +1322,7 @@ bool streq_skip_trailing_chars(const char *s1, const char *s2, const char *ok) {
 
         return in_charset(s1, ok) && in_charset(s2, ok);
 }
+#endif /* NM_IGNORED */
 
 char *string_replace_char(char *str, char old_char, char new_char) {
         assert(str);
@@ -1250,6 +1394,7 @@ size_t strspn_from_end(const char *str, const char *accept) {
         return n;
 }
 
+#if 0 /* NM_IGNORED */
 char *strdupspn(const char *a, const char *accept) {
         if (isempty(a) || isempty(accept))
                 return strdup("");
@@ -1290,14 +1435,109 @@ char *find_line_startswith(const char *haystack, const char *needle) {
 }
 #endif /* NM_IGNORED */
 
-char *startswith_strv(const char *string, char **strv) {
-        char *found = NULL;
+bool version_is_valid(const char *s) {
+        if (isempty(s))
+                return false;
+
+        if (!filename_part_is_valid(s))
+                return false;
 
-        STRV_FOREACH(i, strv) {
-                found = startswith(string, *i);
-                if (found)
-                        break;
+        /* This is a superset of the characters used by semver. We additionally allow "," and "_". */
+        if (!in_charset(s, ALPHANUMERICAL ".,_-+"))
+                return false;
+
+        return true;
+}
+
+bool version_is_valid_versionspec(const char *s) {
+        if (!filename_part_is_valid(s))
+                return false;
+
+        if (!in_charset(s, ALPHANUMERICAL "-.~^"))
+                return false;
+
+        return true;
+}
+
+ssize_t strlevenshtein(const char *x, const char *y) {
+        _cleanup_free_ size_t *t0 = NULL, *t1 = NULL, *t2 = NULL;
+        size_t xl, yl;
+
+        /* This is inspired from the Linux kernel's Levenshtein implementation */
+
+        if (streq_ptr(x, y))
+                return 0;
+
+        xl = strlen_ptr(x);
+        if (xl > SSIZE_MAX)
+                return -E2BIG;
+
+        yl = strlen_ptr(y);
+        if (yl > SSIZE_MAX)
+                return -E2BIG;
+
+        if (isempty(x))
+                return yl;
+        if (isempty(y))
+                return xl;
+
+        t0 = new0(size_t, yl + 1);
+        if (!t0)
+                return -ENOMEM;
+        t1 = new0(size_t, yl + 1);
+        if (!t1)
+                return -ENOMEM;
+        t2 = new0(size_t, yl + 1);
+        if (!t2)
+                return -ENOMEM;
+
+        for (size_t i = 0; i <= yl; i++)
+                t1[i] = i;
+
+        for (size_t i = 0; i < xl; i++) {
+                t2[0] = i + 1;
+
+                for (size_t j = 0; j < yl; j++) {
+                        /* Substitution */
+                        t2[j+1] = t1[j] + (x[i] != y[j]);
+
+                        /* Swap */
+                        if (i > 0 && j > 0 && x[i-1] == y[j] && x[i] == y[j-1] && t2[j+1] > t0[j-1] + 1)
+                                t2[j+1] = t0[j-1] + 1;
+
+                        /* Deletion */
+                        if (t2[j+1] > t1[j+1] + 1)
+                                t2[j+1] = t1[j+1] + 1;
+
+                        /* Insertion */
+                        if (t2[j+1] > t2[j] + 1)
+                                t2[j+1] = t2[j] + 1;
+                }
+
+                size_t *dummy = t0;
+                t0 = t1;
+                t1 = t2;
+                t2 = dummy;
         }
 
-        return found;
+        return t1[yl];
+}
+
+char *strrstr(const char *haystack, const char *needle) {
+        /* Like strstr() but returns the last rather than the first occurrence of "needle" in "haystack". */
+
+        if (!haystack || !needle)
+                return NULL;
+
+        /* Special case: for the empty string we return the very last possible occurrence, i.e. *after* the
+         * last char, not before. */
+        if (*needle == 0)
+                return strchr(haystack, 0);
+
+        for (const char *p = strstr(haystack, needle), *q; p; p = q) {
+                q = strstr(p + 1, needle);
+                if (!q)
+                        return (char *) p;
+        }
+        return NULL;
 }
diff --git a/src/libnm-systemd-shared/src/basic/string-util.h b/src/libnm-systemd-shared/src/basic/string-util.h
index 4430910e..e162765a 100644
--- a/src/libnm-systemd-shared/src/basic/string-util.h
+++ b/src/libnm-systemd-shared/src/basic/string-util.h
@@ -22,6 +22,9 @@
 #define ALPHANUMERICAL      LETTERS DIGITS
 #define HEXDIGITS           DIGITS "abcdefABCDEF"
 #define LOWERCASE_HEXDIGITS DIGITS "abcdef"
+#define URI_RESERVED        ":/?#[]@!$&'()*+;="         /* [RFC3986] */
+#define URI_UNRESERVED      ALPHANUMERICAL "-._~"       /* [RFC3986] */
+#define URI_VALID           URI_RESERVED URI_UNRESERVED /* [RFC3986] */
 
 static inline char* strstr_ptr(const char *haystack, const char *needle) {
         if (!haystack || !needle)
@@ -65,6 +68,10 @@ static inline const char* enable_disable(bool b) {
         return b ? "enable" : "disable";
 }
 
+static inline const char* enabled_disabled(bool b) {
+        return b ? "enabled" : "disabled";
+}
+
 /* This macro's return pointer will have the "const" qualifier set or unset the same way as the input
  * pointer. */
 #define empty_to_null(p)                                \
@@ -121,7 +128,10 @@ char *strjoin_real(const char *x, ...) _sentinel_;
 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);
+char *truncate_nl_full(char *s, size_t *ret_len);
+static inline char *truncate_nl(char *s) {
+        return truncate_nl_full(s, NULL);
+}
 
 static inline char *skip_leading_chars(const char *s, const char *bad) {
         if (!s)
@@ -183,11 +193,24 @@ char *strextend_with_separator_internal(char **x, const char *separator, ...) _s
 #define strextend_with_separator(x, separator, ...) strextend_with_separator_internal(x, separator, __VA_ARGS__, NULL)
 #define strextend(x, ...) strextend_with_separator_internal(x, NULL, __VA_ARGS__, NULL)
 
+char *strextendn(char **x, const char *s, size_t l);
+
 int strextendf_with_separator(char **x, const char *separator, const char *format, ...) _printf_(3,4);
 #define strextendf(x, ...) strextendf_with_separator(x, NULL, __VA_ARGS__)
 
 char *strrep(const char *s, unsigned n);
 
+#define strrepa(s, n)                                           \
+        ({                                                      \
+                char *_d_, *_p_;                                \
+                size_t _len_ = strlen(s) * n;                   \
+                _p_ = _d_ = newa(char, _len_ + 1);              \
+                for (unsigned _i_ = 0; _i_ < n; _i_++)          \
+                        _p_ = stpcpy(_p_, s);                   \
+                *_p_ = 0;                                       \
+                _d_;                                            \
+        })
+
 int split_pair(const char *s, const char *sep, char **l, char **r);
 
 int free_and_strdup(char **p, const char *s);
@@ -268,7 +291,31 @@ char *strdupcspn(const char *a, const char *reject);
 
 char *find_line_startswith(const char *haystack, const char *needle);
 
-char *startswith_strv(const char *string, char **strv);
+bool version_is_valid(const char *s);
+
+bool version_is_valid_versionspec(const char *s);
+
+ssize_t strlevenshtein(const char *x, const char *y);
+
+static inline int strdup_or_null(const char *s, char **ret) {
+        char *c;
+
+        assert(ret);
+
+        /* This is a lot like strdup(), but is happy with NULL strings, and does not treat that as error, but
+         * copies the NULL value. */
+
+        if (!s) {
+                *ret = NULL;
+                return 0;
+        }
+
+        c = strdup(s);
+        if (!c)
+                return -ENOMEM;
+
+        *ret = c;
+        return 1;
+}
 
-#define STARTSWITH_SET(p, ...)                                  \
-        startswith_strv(p, STRV_MAKE(__VA_ARGS__))
+char *strrstr(const char *haystack, const char *needle);
diff --git a/src/libnm-systemd-shared/src/basic/strv.c b/src/libnm-systemd-shared/src/basic/strv.c
index 9ad53307..ed02b481 100644
--- a/src/libnm-systemd-shared/src/basic/strv.c
+++ b/src/libnm-systemd-shared/src/basic/strv.c
@@ -90,6 +90,15 @@ char** strv_free_erase(char **l) {
         return mfree(l);
 }
 
+void strv_free_many(char ***strvs, size_t n) {
+        assert(strvs || n == 0);
+
+        FOREACH_ARRAY (i, strvs, n)
+                strv_free(*i);
+
+        free(strvs);
+}
+
 char** strv_copy_n(char * const *l, size_t m) {
         _cleanup_strv_free_ char **result = NULL;
         char **k;
@@ -116,6 +125,22 @@ char** strv_copy_n(char * const *l, size_t m) {
         return TAKE_PTR(result);
 }
 
+int strv_copy_unless_empty(char * const *l, char ***ret) {
+        assert(ret);
+
+        if (strv_isempty(l)) {
+                *ret = NULL;
+                return 0;
+        }
+
+        char **copy = strv_copy(l);
+        if (!copy)
+                return -ENOMEM;
+
+        *ret = TAKE_PTR(copy);
+        return 1;
+}
+
 size_t strv_length(char * const *l) {
         size_t n = 0;
 
@@ -214,9 +239,7 @@ int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates) {
         return (int) i;
 
 rollback:
-        for (size_t j = 0; j < i; j++)
-                free(t[p + j]);
-
+        free_many_charp(t + p, i);
         t[p] = NULL;
         return -ENOMEM;
 }
@@ -340,7 +363,7 @@ int strv_split_colon_pairs(char ***t, const char *s) {
 
                 const char *p = tuple;
                 r = extract_many_words(&p, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS,
-                                       &first, &second, NULL);
+                                       &first, &second);
                 if (r < 0)
                         return r;
                 if (r == 0)
@@ -488,29 +511,31 @@ int strv_insert(char ***l, size_t position, char *value) {
         char **c;
         size_t n, m;
 
+        assert(l);
+
         if (!value)
                 return 0;
 
         n = strv_length(*l);
         position = MIN(position, n);
 
-        /* increase and check for overflow */
-        m = n + 2;
-        if (m < n)
+        /* check for overflow and increase*/
+        if (n > SIZE_MAX - 2)
                 return -ENOMEM;
+        m = n + 2;
 
-        c = new(char*, m);
+        c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(m), sizeof(char*));
         if (!c)
                 return -ENOMEM;
 
-        for (size_t i = 0; i < position; i++)
-                c[i] = (*l)[i];
+        if (n > position)
+                memmove(c + position + 1, c + position, (n - position) * sizeof(char*));
+
         c[position] = value;
-        for (size_t i = position; i < n; i++)
-                c[i+1] = (*l)[i];
-        c[n+1] = NULL;
+        c[n + 1] = NULL;
 
-        return free_and_replace(*l, c);
+        *l = c;
+        return 0;
 }
 
 int strv_consume_with_size(char ***l, size_t *n, char *value) {
@@ -571,39 +596,63 @@ int strv_extend_with_size(char ***l, size_t *n, const char *value) {
         return strv_consume_with_size(l, n, v);
 }
 
-int strv_extend_front(char ***l, const char *value) {
+int strv_extend_many_internal(char ***l, const char *value, ...) {
+        va_list ap;
         size_t n, m;
-        char *v, **c;
+        int r;
 
         assert(l);
 
-        /* Like strv_extend(), but prepends rather than appends the new entry */
+        m = n = strv_length(*l);
 
-        if (!value)
-                return 0;
+        r = 0;
+        va_start(ap, value);
+        for (const char *s = value; s != POINTER_MAX; s = va_arg(ap, const char*)) {
+                if (!s)
+                        continue;
 
-        n = strv_length(*l);
+                if (m > SIZE_MAX-1) { /* overflow */
+                        r = -ENOMEM;
+                        break;
+                }
+                m++;
+        }
+        va_end(ap);
 
-        /* Increase and overflow check. */
-        m = n + 2;
-        if (m < n)
+        if (r < 0)
+                return r;
+        if (m > SIZE_MAX-1)
                 return -ENOMEM;
 
-        v = strdup(value);
-        if (!v)
+        char **c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(m+1), sizeof(char*));
+        if (!c)
                 return -ENOMEM;
+        *l = c;
 
-        c = reallocarray(*l, m, sizeof(char*));
-        if (!c) {
-                free(v);
-                return -ENOMEM;
+        r = 0;
+        size_t i = n;
+        va_start(ap, value);
+        for (const char *s = value; s != POINTER_MAX; s = va_arg(ap, const char*)) {
+                if (!s)
+                        continue;
+
+                c[i] = strdup(s);
+                if (!c[i]) {
+                        r = -ENOMEM;
+                        break;
+                }
+                i++;
         }
+        va_end(ap);
 
-        memmove(c+1, c, n * sizeof(char*));
-        c[0] = v;
-        c[n+1] = NULL;
+        if (r < 0) {
+                /* rollback on error */
+                for (size_t j = n; j < i; j++)
+                        c[j] = mfree(c[j]);
+                return r;
+        }
 
-        *l = c;
+        c[i] = NULL;
         return 0;
 }
 
@@ -707,6 +756,26 @@ int strv_extendf(char ***l, const char *format, ...) {
         return strv_consume(l, x);
 }
 
+char* startswith_strv(const char *s, char * const *l) {
+        STRV_FOREACH(i, l) {
+                char *found = startswith(s, *i);
+                if (found)
+                        return found;
+        }
+
+        return NULL;
+}
+
+char* endswith_strv(const char *s, char * const *l) {
+        STRV_FOREACH(i, l) {
+                char *found = endswith(s, *i);
+                if (found)
+                        return found;
+        }
+
+        return NULL;
+}
+
 char** strv_reverse(char **l) {
         size_t n;
 
@@ -835,13 +904,15 @@ int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) {
         bool b = false;
         int r;
 
+        assert(f);
+
         /* 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);
+                r = fputs_with_separator(f, *s, separator, space);
                 if (r < 0)
                         return r;
         }
diff --git a/src/libnm-systemd-shared/src/basic/strv.h b/src/libnm-systemd-shared/src/basic/strv.h
index 544d46a3..91337b92 100644
--- a/src/libnm-systemd-shared/src/basic/strv.h
+++ b/src/libnm-systemd-shared/src/basic/strv.h
@@ -32,10 +32,14 @@ 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_free_many(char ***strvs, size_t n);
+
 char** strv_copy_n(char * const *l, size_t n);
 static inline char** strv_copy(char * const *l) {
         return strv_copy_n(l, SIZE_MAX);
 }
+int strv_copy_unless_empty(char * const *l, char ***ret);
+
 size_t strv_length(char * const *l) _pure_;
 
 int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates);
@@ -51,8 +55,10 @@ static inline int strv_extend(char ***l, const char *value) {
         return strv_extend_with_size(l, NULL, value);
 }
 
+int strv_extend_many_internal(char ***l, const char *value, ...);
+#define strv_extend_many(l, ...) strv_extend_many_internal(l, __VA_ARGS__, POINTER_MAX)
+
 int strv_extendf(char ***l, const char *format, ...) _printf_(2,3);
-int strv_extend_front(char ***l, const char *value);
 
 int strv_push_with_size(char ***l, size_t *n, char *value);
 static inline int strv_push(char ***l, char *value) {
@@ -157,6 +163,16 @@ static inline void strv_print(char * const *l) {
         strv_print_full(l, NULL);
 }
 
+char* startswith_strv(const char *s, char * const *l);
+
+#define STARTSWITH_SET(p, ...)                                  \
+        startswith_strv(p, STRV_MAKE(__VA_ARGS__))
+
+char* endswith_strv(const char *s, char * const *l);
+
+#define ENDSWITH_SET(p, ...)                                    \
+        endswith_strv(p, STRV_MAKE(__VA_ARGS__))
+
 #define strv_from_stdarg_alloca(first)                          \
         ({                                                      \
                 char **_l;                                      \
@@ -200,18 +216,6 @@ static inline void strv_print(char * const *l) {
                 _x && strv_contains_case(STRV_MAKE(__VA_ARGS__), _x); \
         })
 
-#define ENDSWITH_SET(p, ...)                                    \
-        ({                                                      \
-                const char *_p = (p);                           \
-                char *_found = NULL;                            \
-                STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) {      \
-                        _found = endswith(_p, *_i);             \
-                        if (_found)                             \
-                                break;                          \
-                }                                               \
-                _found;                                         \
-        })
-
 #define _FOREACH_STRING(uniq, x, y, ...)                                \
         for (const char *x, * const*UNIQ_T(l, uniq) = STRV_MAKE_CONST(({ x = y; }), ##__VA_ARGS__); \
              x;                                                         \
diff --git a/src/libnm-systemd-shared/src/basic/time-util.c b/src/libnm-systemd-shared/src/basic/time-util.c
index 092912b2..47f5bb5a 100644
--- a/src/libnm-systemd-shared/src/basic/time-util.c
+++ b/src/libnm-systemd-shared/src/basic/time-util.c
@@ -66,7 +66,7 @@ nsec_t now_nsec(clockid_t clock_id) {
         return timespec_load_nsec(&ts);
 }
 
-dual_timestamp* dual_timestamp_get(dual_timestamp *ts) {
+dual_timestamp* dual_timestamp_now(dual_timestamp *ts) {
         assert(ts);
 
         ts->realtime = now(CLOCK_REALTIME);
@@ -75,7 +75,7 @@ dual_timestamp* dual_timestamp_get(dual_timestamp *ts) {
         return ts;
 }
 
-triple_timestamp* triple_timestamp_get(triple_timestamp *ts) {
+triple_timestamp* triple_timestamp_now(triple_timestamp *ts) {
         assert(ts);
 
         ts->realtime = now(CLOCK_REALTIME);
@@ -158,6 +158,25 @@ triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u)
         return ts;
 }
 
+triple_timestamp* triple_timestamp_from_boottime(triple_timestamp *ts, usec_t u) {
+        usec_t nowb;
+
+        assert(ts);
+
+        if (u == USEC_INFINITY) {
+                ts->realtime = ts->monotonic = ts->boottime = u;
+                return ts;
+        }
+
+        nowb = now(CLOCK_BOOTTIME);
+
+        ts->boottime = u;
+        ts->monotonic = map_clock_usec_internal(u, nowb, now(CLOCK_MONOTONIC));
+        ts->realtime = map_clock_usec_internal(u, nowb, now(CLOCK_REALTIME));
+
+        return ts;
+}
+
 dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
         assert(ts);
 
@@ -330,7 +349,7 @@ char *format_timestamp_style(
                 if (l < (size_t) (1 + 1 + 1))
                         return NULL; /* not enough space for even the shortest of forms */
 
-                return snprintf_ok(buf, l, "@" USEC_FMT, t / USEC_PER_SEC);  /* round down µs → s */
+                return snprintf_ok(buf, l, "@" USEC_FMT, t / USEC_PER_SEC);  /* round down μs → s */
         }
 
         utc = IN_SET(style, TIMESTAMP_UTC, TIMESTAMP_US_UTC, TIMESTAMP_DATE);
@@ -618,7 +637,7 @@ char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
 #if 0 /* NM_IGNORED */
 static int parse_timestamp_impl(
                 const char *t,
-                size_t tz_offset,
+                size_t max_len,
                 bool utc,
                 int isdst,
                 long gmtoff,
@@ -655,8 +674,12 @@ static int parse_timestamp_impl(
 
         /* Allowed syntaxes:
          *
-         *   2012-09-22 16:34:22
+         *   2012-09-22 16:34:22.1[2[3[4[5[6]]]]]
+         *   2012-09-22 16:34:22  (µsec will be set to 0)
          *   2012-09-22 16:34     (seconds will be set to 0)
+         *   2012-09-22T16:34:22.1[2[3[4[5[6]]]]]
+         *   2012-09-22T16:34:22  (µsec will be set to 0)
+         *   2012-09-22T16: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)
@@ -670,17 +693,26 @@ static int parse_timestamp_impl(
          *
          * Note, on DST change, 00:00:00 may not exist and in that case the time part may be shifted.
          * E.g. "Sun 2023-03-13 America/Havana" is parsed as "Sun 2023-03-13 01:00:00 CDT".
+         *
+         * A simplified strptime-spelled RFC3339 ABNF looks like
+         *   "%Y-%m-%d" "T" "%H" ":" "%M" ":" "%S" [".%N"] ("Z" / (("+" / "-") "%H:%M"))
+         * We additionally allow no seconds and inherited timezone
+         * for symmetry with our other syntaxes and improved interactive usability:
+         *   "%Y-%m-%d" "T" "%H" ":" "%M" ":" ["%S" [".%N"]] ["Z" / (("+" / "-") "%H:%M")]
+         * RFC3339 defines time-secfrac to as "." 1*DIGIT, but we limit to 6 digits,
+         * since we're limited to 1µs resolution.
+         * We also accept "Sat 2012-09-22T16:34:22", RFC3339 warns against it.
          */
 
         assert(t);
 
-        if (tz_offset != SIZE_MAX) {
+        if (max_len != SIZE_MAX) {
                 /* If the input string contains timezone, then cut it here. */
 
-                if (tz_offset <= 1) /* timezone must be after a space. */
+                if (max_len == 0) /* Can't be the only field */
                         return -EINVAL;
 
-                t_alloc = strndup(t, tz_offset - 1);
+                t_alloc = strndup(t, max_len);
                 if (!t_alloc)
                         return -ENOMEM;
 
@@ -792,6 +824,7 @@ static int parse_timestamp_impl(
                         goto from_tm;
         }
 
+        /* Our "canonical" RFC3339 syntax variant */
         tm = copy;
         k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
         if (k) {
@@ -801,6 +834,16 @@ static int parse_timestamp_impl(
                         goto from_tm;
         }
 
+        /* RFC3339 syntax */
+        tm = copy;
+        k = strptime(t, "%Y-%m-%dT%H:%M:%S", &tm);
+        if (k) {
+                if (*k == '.')
+                        goto parse_usec;
+                else if (*k == 0)
+                        goto from_tm;
+        }
+
         /* Support OUTPUT_SHORT and OUTPUT_SHORT_PRECISE formats */
         tm = copy;
         k = strptime(t, "%b %d %H:%M:%S", &tm);
@@ -818,6 +861,7 @@ static int parse_timestamp_impl(
                 goto from_tm;
         }
 
+        /* Our "canonical" RFC3339 syntax variant without seconds */
         tm = copy;
         k = strptime(t, "%Y-%m-%d %H:%M", &tm);
         if (k && *k == 0) {
@@ -825,6 +869,14 @@ static int parse_timestamp_impl(
                 goto from_tm;
         }
 
+        /* RFC3339 syntax without seconds */
+        tm = copy;
+        k = strptime(t, "%Y-%m-%dT%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) {
@@ -927,13 +979,13 @@ static int parse_timestamp_maybe_with_tz(const char *t, size_t tz_offset, bool v
                         continue;
 
                 /* The specified timezone matches tzname[] of the local timezone. */
-                return parse_timestamp_impl(t, tz_offset, /* utc = */ false, /* isdst = */ j, /* gmtoff = */ 0, ret);
+                return parse_timestamp_impl(t, tz_offset - 1, /* utc = */ false, /* isdst = */ j, /* gmtoff = */ 0, ret);
         }
 
         /* If we know that the last word is a valid timezone (e.g. Asia/Tokyo), then simply drop the timezone
          * and parse the remaining string as a local time. If we know that the last word is not a timezone,
          * then assume that it is a part of the time and try to parse the whole string as a local time. */
-        return parse_timestamp_impl(t, valid_tz ? tz_offset : SIZE_MAX,
+        return parse_timestamp_impl(t, valid_tz ? tz_offset - 1 : SIZE_MAX,
                                     /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
 }
 
@@ -945,40 +997,50 @@ typedef struct ParseTimestampResult {
 int parse_timestamp(const char *t, usec_t *ret) {
         ParseTimestampResult *shared, tmp;
         const char *k, *tz, *current_tz;
-        size_t tz_offset;
+        size_t max_len, t_len;
         struct tm tm;
         int r;
 
         assert(t);
 
+        t_len = strlen(t);
+        if (t_len > 2 && t[t_len - 1] == 'Z' && t[t_len - 2] != ' ')  /* RFC3339-style welded UTC: "1985-04-12T23:20:50.52Z" */
+                return parse_timestamp_impl(t, t_len - 1, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret);
+
+        if (t_len > 7 && IN_SET(t[t_len - 6], '+', '-') && t[t_len - 7] != ' ') {  /* RFC3339-style welded offset: "1990-12-31T15:59:60-08:00" */
+                k = strptime(&t[t_len - 6], "%z", &tm);
+                if (k && *k == '\0')
+                        return parse_timestamp_impl(t, t_len - 6, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ tm.tm_gmtoff, ret);
+        }
+
         tz = strrchr(t, ' ');
         if (!tz)
-                return parse_timestamp_impl(t, /* tz_offset = */ SIZE_MAX, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
+                return parse_timestamp_impl(t, /* max_len = */ SIZE_MAX, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
 
+        max_len = tz - t;
         tz++;
-        tz_offset = tz - t;
 
         /* Shortcut, parse the string as UTC. */
         if (streq(tz, "UTC"))
-                return parse_timestamp_impl(t, tz_offset, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret);
+                return parse_timestamp_impl(t, max_len, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret);
 
         /* If the timezone is compatible with RFC-822/ISO 8601 (e.g. +06, or -03:00) then parse the string as
          * UTC and shift the result. Note, this must be earlier than the timezone check with tzname[], as
          * tzname[] may be in the same format. */
         k = strptime(tz, "%z", &tm);
         if (k && *k == '\0')
-                return parse_timestamp_impl(t, tz_offset, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ tm.tm_gmtoff, ret);
+                return parse_timestamp_impl(t, max_len, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ tm.tm_gmtoff, ret);
 
         /* If the last word is not a timezone file (e.g. Asia/Tokyo), then let's check if it matches
          * tzname[] of the local timezone, e.g. JST or CEST. */
         if (!timezone_is_valid(tz, LOG_DEBUG))
-                return parse_timestamp_maybe_with_tz(t, tz_offset, /* valid_tz = */ false, ret);
+                return parse_timestamp_maybe_with_tz(t, tz - t, /* valid_tz = */ false, ret);
 
         /* Shortcut. If the current $TZ is equivalent to the specified timezone, it is not necessary to fork
          * the process. */
         current_tz = getenv("TZ");
         if (current_tz && *current_tz == ':' && streq(current_tz + 1, tz))
-                return parse_timestamp_maybe_with_tz(t, tz_offset, /* valid_tz = */ true, ret);
+                return parse_timestamp_maybe_with_tz(t, tz - t, /* valid_tz = */ true, ret);
 
         /* Otherwise, to avoid polluting the current environment variables, let's fork the process and set
          * the specified timezone in the child process. */
@@ -987,7 +1049,7 @@ int parse_timestamp(const char *t, usec_t *ret) {
         if (shared == MAP_FAILED)
                 return negative_errno();
 
-        r = safe_fork("(sd-timestamp)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT, NULL);
+        r = safe_fork("(sd-timestamp)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_WAIT, NULL);
         if (r < 0) {
                 (void) munmap(shared, sizeof *shared);
                 return r;
@@ -1003,7 +1065,7 @@ int parse_timestamp(const char *t, usec_t *ret) {
                         _exit(EXIT_FAILURE);
                 }
 
-                shared->return_value = parse_timestamp_maybe_with_tz(t, tz_offset, /* valid_tz = */ true, &shared->usec);
+                shared->return_value = parse_timestamp_maybe_with_tz(t, tz - t, /* valid_tz = */ true, &shared->usec);
 
                 _exit(EXIT_SUCCESS);
         }
@@ -1051,7 +1113,8 @@ static const char* extract_multiplier(const char *p, usec_t *ret) {
                 { "y",       USEC_PER_YEAR   },
                 { "usec",    1ULL            },
                 { "us",      1ULL            },
-                { "µs",      1ULL            },
+                { "μs",      1ULL            }, /* U+03bc (aka GREEK SMALL LETTER MU) */
+                { "µs",      1ULL            }, /* U+b5 (aka MICRO SIGN) */
         };
 
         assert(p);
@@ -1229,7 +1292,8 @@ static const char* extract_nsec_multiplier(const char *p, nsec_t *ret) {
                 { "y",       NSEC_PER_YEAR   },
                 { "usec",    NSEC_PER_USEC   },
                 { "us",      NSEC_PER_USEC   },
-                { "µs",      NSEC_PER_USEC   },
+                { "μs",      NSEC_PER_USEC   }, /* U+03bc (aka GREEK LETTER MU) */
+                { "µs",      NSEC_PER_USEC   }, /* U+b5 (aka MICRO SIGN) */
                 { "nsec",    1ULL            },
                 { "ns",      1ULL            },
                 { "",        1ULL            }, /* default is nsec */
@@ -1370,7 +1434,7 @@ static int get_timezones_from_zone1970_tab(char ***ret) {
 
                 /* Line format is:
                  * 'country codes' 'coordinates' 'timezone' 'comments' */
-                r = extract_many_words(&p, NULL, 0, &cc, &co, &tz, NULL);
+                r = extract_many_words(&p, NULL, 0, &cc, &co, &tz);
                 if (r < 0)
                         continue;
 
@@ -1415,7 +1479,7 @@ static int get_timezones_from_tzdata_zi(char ***ret) {
                  * Link line format is:
                  * 'Link' 'target' 'alias'
                  * See 'man zic' for more detail. */
-                r = extract_many_words(&p, NULL, 0, &type, &f1, &f2, NULL);
+                r = extract_many_words(&p, NULL, 0, &type, &f1, &f2);
                 if (r < 0)
                         continue;
 
@@ -1458,7 +1522,7 @@ int get_timezones(char ***ret) {
         /* Always include UTC */
         r = strv_extend(&zones, "UTC");
         if (r < 0)
-                return -ENOMEM;
+                return r;
 
         strv_sort(zones);
         strv_uniq(zones);
@@ -1515,7 +1579,7 @@ int verify_timezone(const char *name, int log_level) {
 
         r = fd_verify_regular(fd);
         if (r < 0)
-                return log_full_errno(log_level, r, "Timezone file '%s' is not  a regular file: %m", t);
+                return log_full_errno(log_level, r, "Timezone file '%s' is not a regular file: %m", t);
 
         r = loop_read_exact(fd, buf, 4, false);
         if (r < 0)
@@ -1666,13 +1730,13 @@ int time_change_fd(void) {
         if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) >= 0)
                 return TAKE_FD(fd);
 
-        /* So apparently there are systems where time_t is 64bit, but the kernel actually doesn't support
-         * 64bit time_t. In that case configuring a timer to TIME_T_MAX will fail with EOPNOTSUPP or a
+        /* So apparently there are systems where time_t is 64-bit, but the kernel actually doesn't support
+         * 64-bit time_t. In that case configuring a timer to TIME_T_MAX will fail with EOPNOTSUPP or a
          * similar error. If that's the case let's try with INT32_MAX instead, maybe that works. It's a bit
          * of a black magic thing though, but what can we do?
          *
-         * We don't want this code on x86-64, hence let's conditionalize this for systems with 64bit time_t
-         * but where "long" is shorter than 64bit, i.e. 32bit archs.
+         * We don't want this code on x86-64, hence let's conditionalize this for systems with 64-bit time_t
+         * but where "long" is shorter than 64-bit, i.e. 32-bit archs.
          *
          * See: https://github.com/systemd/systemd/issues/14362 */
 
@@ -1708,9 +1772,9 @@ TimestampStyle timestamp_style_from_string(const char *s) {
         t = (TimestampStyle) string_table_lookup(timestamp_style_table, ELEMENTSOF(timestamp_style_table), s);
         if (t >= 0)
                 return t;
-        if (streq_ptr(s, "µs"))
+        if (STRPTR_IN_SET(s, "µs", "μs")) /* accept both µ symbols in unicode, i.e. micro symbol + Greek small letter mu. */
                 return TIMESTAMP_US;
-        if (streq_ptr(s, "µs+utc"))
+        if (STRPTR_IN_SET(s, "µs+utc", "μs+utc"))
                 return TIMESTAMP_US_UTC;
         return t;
 }
diff --git a/src/libnm-systemd-shared/src/basic/time-util.h b/src/libnm-systemd-shared/src/basic/time-util.h
index b49137d5..29373477 100644
--- a/src/libnm-systemd-shared/src/basic/time-util.h
+++ b/src/libnm-systemd-shared/src/basic/time-util.h
@@ -79,13 +79,14 @@ nsec_t now_nsec(clockid_t clock);
 
 usec_t map_clock_usec(usec_t from, clockid_t from_clock, clockid_t to_clock);
 
-dual_timestamp* dual_timestamp_get(dual_timestamp *ts);
+dual_timestamp* dual_timestamp_now(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(dual_timestamp *ts, usec_t u);
 
-triple_timestamp* triple_timestamp_get(triple_timestamp *ts);
+triple_timestamp* triple_timestamp_now(triple_timestamp *ts);
 triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u);
+triple_timestamp* triple_timestamp_from_boottime(triple_timestamp *ts, usec_t u);
 
 #define DUAL_TIMESTAMP_HAS_CLOCK(clock)                               \
         IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC)
@@ -211,10 +212,24 @@ static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) {
         return usec_sub_unsigned(timestamp, (usec_t) delta);
 }
 
+static inline int usleep_safe(usec_t usec) {
+        /* usleep() takes useconds_t that is (typically?) uint32_t. Also, usleep() may only support the
+         * range [0, 1000000]. See usleep(3). Let's override usleep() with clock_nanosleep().
+         *
+         * ⚠️ Note we are not using plain nanosleep() here, since that operates on CLOCK_REALTIME, not
+         *    CLOCK_MONOTONIC! */
+
+        if (usec == 0)
+                return 0;
+
+        // FIXME: use RET_NERRNO() macro here. Currently, this header cannot include errno-util.h.
+        return clock_nanosleep(CLOCK_MONOTONIC, 0, TIMESPEC_STORE(usec), NULL) < 0 ? -errno : 0;
+}
+
 /* 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_64BIT ((usec_t) 253402214399000000) /* Thu 9999-12-30 23:59:59 UTC */
-/* With a 32bit time_t we can't go beyond 2038...
+/* With a 32-bit time_t we can't go beyond 2038...
  * We parse timestamp with RFC-822/ISO 8601 (e.g. +06, or -03:00) as UTC, hence the upper bound must be off
  * by USEC_PER_DAY. See parse_timestamp() for more details. */
 #define USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT (((usec_t) INT32_MAX) * USEC_PER_SEC - USEC_PER_DAY)
diff --git a/src/libnm-systemd-shared/src/basic/tmpfile-util.h b/src/libnm-systemd-shared/src/basic/tmpfile-util.h
index 50904eca..8c917c06 100644
--- a/src/libnm-systemd-shared/src/basic/tmpfile-util.h
+++ b/src/libnm-systemd-shared/src/basic/tmpfile-util.h
@@ -29,7 +29,6 @@ static inline int open_tmpfile_linkable(const char *target, int flags, char **re
 }
 int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file);
 
-
 typedef enum LinkTmpfileFlags {
         LINK_TMPFILE_REPLACE = 1 << 0,
         LINK_TMPFILE_SYNC    = 1 << 1,
diff --git a/src/libnm-systemd-shared/src/basic/umask-util.h b/src/libnm-systemd-shared/src/basic/umask-util.h
index 6f0e1cc2..00417fa3 100644
--- a/src/libnm-systemd-shared/src/basic/umask-util.h
+++ b/src/libnm-systemd-shared/src/basic/umask-util.h
@@ -8,12 +8,12 @@
 #include "macro.h"
 
 static inline void umaskp(mode_t *u) {
-        umask(*u & 0777);
+        umask(*u);
 }
 
 #define _cleanup_umask_ _cleanup_(umaskp)
 
-/* We make use of the fact here that the umask() concept is using only the lower 9 bits of mode_t, although
+/* We make use of the fact here that the umask() syscall uses only the lower 9 bits of mode_t, although
  * mode_t has space for the file type in the bits further up. We simply OR in the file type mask S_IFMT to
  * distinguish the first and the second iteration of the WITH_UMASK() loop, so that we can run the first one,
  * and exit on the second. */
diff --git a/src/libnm-systemd-shared/src/basic/user-util.h b/src/libnm-systemd-shared/src/basic/user-util.h
index 8b829a9a..9d07ef31 100644
--- a/src/libnm-systemd-shared/src/basic/user-util.h
+++ b/src/libnm-systemd-shared/src/basic/user-util.h
@@ -42,8 +42,8 @@ typedef enum UserCredsFlags {
         USER_CREDS_CLEAN         = 1 << 2,  /* try to clean up shell and home fields with invalid data */
 } UserCredsFlags;
 
-int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home, const char **shell, UserCredsFlags flags);
-int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags);
+int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags);
+int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags);
 
 char* uid_to_name(uid_t uid);
 char* gid_to_name(gid_t gid);
@@ -57,7 +57,10 @@ int getgroups_alloc(gid_t** gids);
 int get_home_dir(char **ret);
 int get_shell(char **ret);
 
-int reset_uid_gid(void);
+int fully_set_uid_gid(uid_t uid, gid_t gid, const gid_t supplementary_gids[], size_t n_supplementary_gids);
+static inline int reset_uid_gid(void) {
+        return fully_set_uid_gid(0, 0, NULL, 0);
+}
 
 int take_etc_passwd_lock(const char *root);
 
@@ -69,13 +72,13 @@ int take_etc_passwd_lock(const char *root);
 
 /* If REMOUNT_IDMAPPING_HOST_ROOT is set for remount_idmap() we'll include a mapping here that maps the host
  * root user accessing the idmapped mount to the this user ID on the backing fs. This is the last valid UID in
- * the *signed* 32bit range. You might wonder why precisely use this specific UID for this purpose? Well, we
+ * the *signed* 32-bit range. You might wonder why precisely use this specific UID for this purpose? Well, we
  * definitely cannot use the first 0…65536 UIDs for that, since in most cases that's precisely the file range
  * we intend to map to some high UID range, and since UID mappings have to be bijective we thus cannot use
- * them at all. Furthermore the UID range beyond INT32_MAX (i.e. the range above the signed 32bit range) is
+ * them at all. Furthermore the UID range beyond INT32_MAX (i.e. the range above the signed 32-bit range) is
  * icky, since many APIs cannot use it (example: setfsuid() returns the old UID as signed integer). Following
- * our usual logic of assigning a 16bit UID range to each container, so that the upper 16bit of a 32bit UID
- * value indicate kind of a "container ID" and the lower 16bit map directly to the intended user you can read
+ * our usual logic of assigning a 16-bit UID range to each container, so that the upper 16-bit of a 32-bit UID
+ * value indicate kind of a "container ID" and the lower 16-bit map directly to the intended user you can read
  * this specific UID as the "nobody" user of the container with ID 0x7FFF, which is kinda nice. */
 #define UID_MAPPED_ROOT ((uid_t) (INT32_MAX-1))
 #define GID_MAPPED_ROOT ((gid_t) (INT32_MAX-1))
@@ -155,3 +158,9 @@ static inline bool hashed_password_is_locked_or_invalid(const char *password) {
  * Also see https://github.com/systemd/systemd/pull/24680#pullrequestreview-1439464325.
  */
 #define PASSWORD_UNPROVISIONED "!unprovisioned"
+
+int getpwuid_malloc(uid_t uid, struct passwd **ret);
+int getpwnam_malloc(const char *name, struct passwd **ret);
+
+int getgrnam_malloc(const char *name, struct group **ret);
+int getgrgid_malloc(gid_t gid, struct group **ret);
diff --git a/src/libnm-systemd-shared/src/basic/utf8.c b/src/libnm-systemd-shared/src/basic/utf8.c
index c8e39fe4..cf24e82f 100644
--- a/src/libnm-systemd-shared/src/basic/utf8.c
+++ b/src/libnm-systemd-shared/src/basic/utf8.c
@@ -92,7 +92,7 @@ int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar) {
         switch (len) {
         case 1:
                 *ret_unichar = (char32_t)str[0];
-                return 0;
+                return 1;
         case 2:
                 unichar = str[0] & 0x1f;
                 break;
@@ -121,15 +121,14 @@ int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar) {
         }
 
         *ret_unichar = unichar;
-
-        return 0;
+        return len;
 }
 
 bool utf8_is_printable_newline(const char* str, size_t length, bool allow_newline) {
         assert(str);
 
         for (const char *p = str; length > 0;) {
-                int encoded_len, r;
+                int encoded_len;
                 char32_t val;
 
                 encoded_len = utf8_encoded_valid_unichar(p, length);
@@ -137,8 +136,7 @@ bool utf8_is_printable_newline(const char* str, size_t length, bool allow_newlin
                         return false;
                 assert(encoded_len > 0 && (size_t) encoded_len <= length);
 
-                r = utf8_encoded_to_unichar(p, &val);
-                if (r < 0 ||
+                if (utf8_encoded_to_unichar(p, &val) < 0 ||
                     unichar_is_control(val) ||
                     (!allow_newline && val == '\n'))
                         return false;
@@ -398,11 +396,23 @@ char *utf16_to_utf8(const char16_t *s, size_t length /* bytes! */) {
         const uint8_t *f;
         char *r, *t;
 
+        if (length == 0)
+                return new0(char, 1);
+
         assert(s);
 
+        if (length == SIZE_MAX) {
+                length = char16_strlen(s);
+
+                if (length > SIZE_MAX/2)
+                        return NULL; /* overflow */
+
+                length *= 2;
+        }
+
         /* 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)
+        if (length > (SIZE_MAX - 1) / 2)
                 return NULL; /* overflow */
 
         r = new(char, length * 2 + 1);
@@ -472,8 +482,17 @@ char16_t *utf8_to_utf16(const char *s, size_t length) {
         char16_t *n, *p;
         int r;
 
+        if (length == 0)
+                return new0(char16_t, 1);
+
         assert(s);
 
+        if (length == SIZE_MAX)
+                length = strlen(s);
+
+        if (length > SIZE_MAX - 1)
+                return NULL; /* overflow */
+
         n = new(char16_t, length + 1);
         if (!n)
                 return NULL;
diff --git a/src/libnm-systemd-shared/src/basic/utf8.h b/src/libnm-systemd-shared/src/basic/utf8.h
index 4a06dd62..962312c5 100644
--- a/src/libnm-systemd-shared/src/basic/utf8.h
+++ b/src/libnm-systemd-shared/src/basic/utf8.h
@@ -38,7 +38,7 @@ 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!) */
+size_t char16_strlen(const char16_t *s); /* returns the number of 16-bit words in the string (not bytes!) */
 
 int utf8_encoded_valid_unichar(const char *str, size_t length);
 int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar);
diff --git a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h
index 89b83e7d..00f2a2b9 100644
--- a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h
+++ b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h
@@ -11,6 +11,42 @@
 #include <stddef.h>
 #include <stdint.h>
 
+/* Temporarily disable some warnings */
+#define DISABLE_WARNING_DEPRECATED_DECLARATIONS                         \
+        _Pragma("GCC diagnostic push");                                 \
+        _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
+
+#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 DISABLE_WARNING_TYPE_LIMITS                                     \
+        _Pragma("GCC diagnostic push");                                 \
+        _Pragma("GCC diagnostic ignored \"-Wtype-limits\"")
+
+#define DISABLE_WARNING_ADDRESS                                         \
+        _Pragma("GCC diagnostic push");                                 \
+        _Pragma("GCC diagnostic ignored \"-Waddress\"")
+
+#define REENABLE_WARNING                                                \
+        _Pragma("GCC diagnostic pop")
+
 #define _align_(x) __attribute__((__aligned__(x)))
 #define _alignas_(x) __attribute__((__aligned__(alignof(x))))
 #define _alignptr_ __attribute__((__aligned__(sizeof(void *))))
@@ -79,7 +115,7 @@
         _noreturn_ void efi_assert(const char *expr, const char *file, unsigned line, const char *function);
 
         #ifdef NDEBUG
-                #define assert(expr)
+                #define assert(expr) ({ if (!(expr)) __builtin_unreachable(); })
                 #define assert_not_reached() __builtin_unreachable()
         #else
                 #define assert(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __func__); })
@@ -129,6 +165,10 @@
                 __atomic_exchange_n(&(o), true, __ATOMIC_SEQ_CST); \
         })
 
+#define U64_KB UINT64_C(1024)
+#define U64_MB (UINT64_C(1024) * U64_KB)
+#define U64_GB (UINT64_C(1024) * U64_MB)
+
 #undef MAX
 #define MAX(a, b) __MAX(UNIQ, (a), UNIQ, (b))
 #define __MAX(aq, a, bq, b)                             \
@@ -224,6 +264,30 @@
                 })
 #endif /* NM_IGNORED */
 
+#define ADD_SAFE(ret, a, b) (!__builtin_add_overflow(a, b, ret))
+#define INC_SAFE(a, b) __INC_SAFE(UNIQ, a, b)
+#define __INC_SAFE(q, a, b)                                     \
+        ({                                                      \
+                const typeof(a) UNIQ_T(A, q) = (a);             \
+                ADD_SAFE(UNIQ_T(A, q), *UNIQ_T(A, q), b);       \
+        })
+
+#define SUB_SAFE(ret, a, b) (!__builtin_sub_overflow(a, b, ret))
+#define DEC_SAFE(a, b) __DEC_SAFE(UNIQ, a, b)
+#define __DEC_SAFE(q, a, b)                                     \
+        ({                                                      \
+                const typeof(a) UNIQ_T(A, q) = (a);             \
+                SUB_SAFE(UNIQ_T(A, q), *UNIQ_T(A, q), b);       \
+        })
+
+#define MUL_SAFE(ret, a, b) (!__builtin_mul_overflow(a, b, ret))
+#define MUL_ASSIGN_SAFE(a, b) __MUL_ASSIGN_SAFE(UNIQ, a, b)
+#define __MUL_ASSIGN_SAFE(q, a, b)                              \
+        ({                                                      \
+                const typeof(a) UNIQ_T(A, q) = (a);             \
+                MUL_SAFE(UNIQ_T(A, q), *UNIQ_T(A, q), b);       \
+        })
+
 #define LESS_BY(a, b) __LESS_BY(UNIQ, (a), UNIQ, (b))
 #define __LESS_BY(aq, a, bq, b)                         \
         ({                                              \
@@ -273,7 +337,7 @@
                 const typeof(y) UNIQ_T(A, q) = (y);                     \
                 const typeof(x) UNIQ_T(B, q) = DIV_ROUND_UP((x), UNIQ_T(A, q)); \
                 typeof(x) UNIQ_T(C, q);                                 \
-                __builtin_mul_overflow(UNIQ_T(B, q), UNIQ_T(A, q), &UNIQ_T(C, q)) ? (typeof(x)) -1 : UNIQ_T(C, q); \
+                MUL_SAFE(&UNIQ_T(C, q), UNIQ_T(B, q), UNIQ_T(A, q)) ? UNIQ_T(C, q) : (typeof(x)) -1; \
         })
 #define ROUND_UP(x, y) __ROUND_UP(UNIQ, (x), (y))
 
@@ -355,7 +419,40 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) {
         if (l > SIZE_MAX - (ali - 1))
                 return SIZE_MAX; /* indicate overflow */
 
-        return ((l + ali - 1) & ~(ali - 1));
+        return ((l + (ali - 1)) & ~(ali - 1));
+}
+
+static inline uint64_t ALIGN_TO_U64(uint64_t l, uint64_t ali) {
+        assert(ISPOWEROF2(ali));
+
+        if (l > UINT64_MAX - (ali - 1))
+                return UINT64_MAX; /* indicate overflow */
+
+        return ((l + (ali - 1)) & ~(ali - 1));
+}
+
+static inline size_t ALIGN_DOWN(size_t l, size_t ali) {
+        assert(ISPOWEROF2(ali));
+
+        return l & ~(ali - 1);
+}
+
+static inline uint64_t ALIGN_DOWN_U64(uint64_t l, uint64_t ali) {
+        assert(ISPOWEROF2(ali));
+
+        return l & ~(ali - 1);
+}
+
+static inline size_t ALIGN_OFFSET(size_t l, size_t ali) {
+        assert(ISPOWEROF2(ali));
+
+        return l & (ali - 1);
+}
+
+static inline uint64_t ALIGN_OFFSET_U64(uint64_t l, uint64_t ali) {
+        assert(ISPOWEROF2(ali));
+
+        return l & (ali - 1);
 }
 
 #define ALIGN2(l) ALIGN_TO(l, 2)
@@ -399,6 +496,42 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) {
 #define FLAGS_SET(v, flags) \
         ((~(v) & (flags)) == 0)
 
+/* A wrapper for 'func' to return void.
+ * Only useful when a void-returning function is required by some API. */
+#define DEFINE_TRIVIAL_DESTRUCTOR(name, type, func)             \
+        static inline void name(type *p) {                      \
+                func(p);                                        \
+        }
+
+/* When func() returns the void value (NULL, -1, …) of the appropriate type */
+#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func)                 \
+        static inline void func##p(type *p) {                   \
+                if (*p)                                         \
+                        *p = func(*p);                          \
+        }
+
+/* When func() doesn't return the appropriate type, set variable to empty afterwards.
+ * The func() may be provided by a dynamically loaded shared library, hence add an assertion. */
+#define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(type, func, empty)     \
+        static inline void func##p(type *p) {                   \
+                if (*p != (empty)) {                            \
+                        DISABLE_WARNING_ADDRESS;                \
+                        assert(func);                           \
+                        REENABLE_WARNING;                       \
+                        func(*p);                               \
+                        *p = (empty);                           \
+                }                                               \
+        }
+
+/* When func() doesn't return the appropriate type, and is also a macro, set variable to empty afterwards. */
+#define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(type, func, empty)       \
+        static inline void func##p(type *p) {                           \
+                if (*p != (empty)) {                                    \
+                        func(*p);                                       \
+                        *p = (empty);                                   \
+                }                                                       \
+        }
+
 /* Declare a flexible array usable in a union.
  * This is essentially a work-around for a pointless constraint in C99
  * and might go away in some future version of the standard.
@@ -410,3 +543,16 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) {
                 dummy_t __empty__ ## name;             \
                 type name[];                           \
         }
+
+/* Declares an ELF read-only string section that does not occupy memory at runtime. */
+#define DECLARE_NOALLOC_SECTION(name, text)   \
+        asm(".pushsection " name ",\"S\"\n\t" \
+            ".ascii " STRINGIFY(text) "\n\t"  \
+            ".zero 1\n\t"                     \
+            ".popsection\n")
+
+#ifdef SBAT_DISTRO
+        #define DECLARE_SBAT(text) DECLARE_NOALLOC_SECTION(".sbat", text)
+#else
+        #define DECLARE_SBAT(text)
+#endif
diff --git a/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h b/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h
index 78e2dbec..6870f54f 100644
--- a/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h
+++ b/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h
@@ -11,6 +11,12 @@
 
 #include "macro-fundamental.h"
 
+#define memzero(x, l)                                           \
+        ({                                                      \
+                size_t _l_ = (l);                               \
+                _l_ > 0 ? memset((x), 0, _l_) : (x);            \
+        })
+
 #if !SD_BOOT && HAVE_EXPLICIT_BZERO
 static inline void *explicit_bzero_safe(void *p, size_t l) {
         if (p && l > 0)
@@ -64,3 +70,39 @@ static inline void erase_varp(struct VarEraser *e) {
                 .p = (ptr),                                             \
                 .size = (sz),                                           \
         }
+
+typedef void (*free_array_func_t)(void *p, size_t n);
+
+/* An automatic _cleanup_-like logic for destroy arrays (i.e. pointers + size) when leaving scope */
+typedef struct ArrayCleanup {
+        void **parray;
+        size_t *pn;
+        free_array_func_t pfunc;
+} ArrayCleanup;
+
+static inline void array_cleanup(const ArrayCleanup *c) {
+        assert(c);
+
+        assert(!c->parray == !c->pn);
+
+        if (!c->parray)
+                return;
+
+        if (*c->parray) {
+                assert(c->pfunc);
+                c->pfunc(*c->parray, *c->pn);
+                *c->parray = NULL;
+        }
+
+        *c->pn = 0;
+}
+
+#define CLEANUP_ARRAY(array, n, func)                                   \
+        _cleanup_(array_cleanup) _unused_ const ArrayCleanup CONCATENATE(_cleanup_array_, UNIQ) = { \
+                .parray = (void**) &(array),                            \
+                .pn = &(n),                                             \
+                .pfunc = (free_array_func_t) ({                         \
+                                void (*_f)(typeof(array[0]) *a, size_t b) = func; \
+                                _f;                                     \
+                        }),                                             \
+        }
diff --git a/src/libnm-systemd-shared/src/fundamental/sha256.c b/src/libnm-systemd-shared/src/fundamental/sha256.c
index a4c6d627..84113aed 100644
--- a/src/libnm-systemd-shared/src/fundamental/sha256.c
+++ b/src/libnm-systemd-shared/src/fundamental/sha256.c
@@ -36,16 +36,9 @@
 
 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
 # define SWAP(n)                                                        \
-        (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
+        __builtin_bswap32(n)
 # define SWAP64(n)                              \
-        (((n) << 56)                            \
-         | (((n) & 0xff00) << 40)               \
-         | (((n) & 0xff0000) << 24)             \
-         | (((n) & 0xff000000) << 8)            \
-         | (((n) >> 8) & 0xff000000)            \
-         | (((n) >> 24) & 0xff0000)             \
-         | (((n) >> 40) & 0xff00)               \
-         | ((n) >> 56))
+        __builtin_bswap64(n)
 #else
 # define SWAP(n) (n)
 # define SWAP64(n) (n)
diff --git a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c
index 3a3e7f59..da810cb7 100644
--- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c
+++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c
@@ -35,14 +35,14 @@ sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) {
         return (sd_char*) s + l;
 }
 
-sd_char* endswith(const sd_char *s, const sd_char *postfix) {
+sd_char* endswith(const sd_char *s, const sd_char *suffix) {
         size_t sl, pl;
 
         assert(s);
-        assert(postfix);
+        assert(suffix);
 
         sl = strlen(s);
-        pl = strlen(postfix);
+        pl = strlen(suffix);
 
         if (pl == 0)
                 return (sd_char*) s + sl;
@@ -50,20 +50,20 @@ sd_char* endswith(const sd_char *s, const sd_char *postfix) {
         if (sl < pl)
                 return NULL;
 
-        if (strcmp(s + sl - pl, postfix) != 0)
+        if (!streq(s + sl - pl, suffix))
                 return NULL;
 
         return (sd_char*) s + sl - pl;
 }
 
-sd_char* endswith_no_case(const sd_char *s, const sd_char *postfix) {
+sd_char* endswith_no_case(const sd_char *s, const sd_char *suffix) {
         size_t sl, pl;
 
         assert(s);
-        assert(postfix);
+        assert(suffix);
 
         sl = strlen(s);
-        pl = strlen(postfix);
+        pl = strlen(suffix);
 
         if (pl == 0)
                 return (sd_char*) s + sl;
@@ -71,7 +71,7 @@ sd_char* endswith_no_case(const sd_char *s, const sd_char *postfix) {
         if (sl < pl)
                 return NULL;
 
-        if (strcasecmp(s + sl - pl, postfix) != 0)
+        if (!strcaseeq(s + sl - pl, suffix))
                 return NULL;
 
         return (sd_char*) s + sl - pl;
diff --git a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h
index 9019542b..419f1cc3 100644
--- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h
+++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h
@@ -59,8 +59,8 @@ static inline size_t strlen_ptr(const sd_char *s) {
 
 sd_char *startswith(const sd_char *s, const sd_char *prefix) _pure_;
 sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) _pure_;
-sd_char *endswith(const sd_char *s, const sd_char *postfix) _pure_;
-sd_char *endswith_no_case(const sd_char *s, const sd_char *postfix) _pure_;
+sd_char *endswith(const sd_char *s, const sd_char *suffix) _pure_;
+sd_char *endswith_no_case(const sd_char *s, const sd_char *suffix) _pure_;
 
 static inline bool isempty(const sd_char *a) {
         return !a || a[0] == '\0';
@@ -74,6 +74,10 @@ static inline const sd_char *yes_no(bool b) {
         return b ? STR_C("yes") : STR_C("no");
 }
 
+static inline const sd_char *on_off(bool b) {
+        return b ? STR_C("on") : STR_C("off");
+}
+
 static inline const sd_char* comparison_operator(int result) {
         return result < 0 ? STR_C("<") : result > 0 ? STR_C(">") : STR_C("==");
 }
diff --git a/src/libnm-systemd-shared/src/shared/dns-domain.c b/src/libnm-systemd-shared/src/shared/dns-domain.c
index 43f43197..a07eaa33 100644
--- a/src/libnm-systemd-shared/src/shared/dns-domain.c
+++ b/src/libnm-systemd-shared/src/shared/dns-domain.c
@@ -87,12 +87,9 @@ int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags f
                                         ((unsigned) (n[1] - '0') * 10) +
                                         ((unsigned) (n[2] - '0'));
 
-                                /* Don't allow anything that doesn't
-                                 * fit in 8bit. Note that we do allow
-                                 * control characters, as some servers
-                                 * (e.g. cloudflare) are happy to
-                                 * generate labels with them
-                                 * inside. */
+                                /* Don't allow anything that doesn't fit in 8 bits. Note that we do allow
+                                 * control characters, as some servers (e.g. cloudflare) are happy to
+                                 * generate labels with them inside. */
                                 if (k > 255)
                                         return -EINVAL;
 
@@ -213,7 +210,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
         char *q;
 
         /* DNS labels must be between 1 and 63 characters long. A
-         * zero-length label does not exist. See RFC 2182, Section
+         * zero-length label does not exist. See RFC 2181, Section
          * 11. */
 
         if (l <= 0 || l > DNS_LABEL_MAX)
@@ -302,14 +299,14 @@ int dns_label_escape_new(const char *p, size_t l, char **ret) {
 int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
         _cleanup_free_ uint32_t *input = NULL;
         size_t input_size, l;
-        bool contains_8bit = false;
+        bool contains_8_bit = false;
         char buffer[DNS_LABEL_MAX+1];
         int r;
 
         assert(encoded);
         assert(decoded);
 
-        /* Converts an U-label into an A-label */
+        /* Converts a U-label into an A-label */
 
         r = dlopen_idn();
         if (r < 0)
@@ -320,9 +317,9 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded
 
         for (const char *p = encoded; p < encoded + encoded_size; p++)
                 if ((uint8_t) *p > 127)
-                        contains_8bit = true;
+                        contains_8_bit = true;
 
-        if (!contains_8bit) {
+        if (!contains_8_bit) {
                 if (encoded_size > DNS_LABEL_MAX)
                         return -EINVAL;
 
@@ -361,7 +358,7 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
         size_t w;
         int r;
 
-        /* To be invoked after unescaping. Converts an A-label into an U-label. */
+        /* To be invoked after unescaping. Converts an A-label into a U-label. */
 
         assert(encoded);
         assert(decoded);
@@ -419,7 +416,7 @@ int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **_r
                 goto finish;
 
         for (;;) {
-                char label[DNS_LABEL_MAX];
+                char label[DNS_LABEL_MAX+1];
 
                 r = dns_label_unescape(&p, label, sizeof label, flags);
                 if (r < 0)
@@ -517,7 +514,7 @@ int dns_name_compare_func(const char *a, const char *b) {
         y = b + strlen(b);
 
         for (;;) {
-                char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
+                char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
 
                 if (x == NULL && y == NULL)
                         return 0;
@@ -553,7 +550,7 @@ int dns_name_equal(const char *x, const char *y) {
         assert(y);
 
         for (;;) {
-                char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
+                char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
 
                 r = dns_label_unescape(&x, la, sizeof la, 0);
                 if (r < 0)
@@ -584,7 +581,7 @@ int dns_name_endswith(const char *name, const char *suffix) {
         s = suffix;
 
         for (;;) {
-                char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
+                char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1];
 
                 r = dns_label_unescape(&n, ln, sizeof ln, 0);
                 if (r < 0)
@@ -622,7 +619,7 @@ int dns_name_startswith(const char *name, const char *prefix) {
         p = prefix;
 
         for (;;) {
-                char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX];
+                char ln[DNS_LABEL_MAX+1], lp[DNS_LABEL_MAX+1];
 
                 r = dns_label_unescape(&p, lp, sizeof lp, 0);
                 if (r < 0)
@@ -654,7 +651,7 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
         s = old_suffix;
 
         for (;;) {
-                char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
+                char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1];
 
                 if (!saved_before)
                         saved_before = n;
@@ -941,7 +938,7 @@ bool dns_srv_type_is_valid(const char *name) {
                 return false;
 
         for (;;) {
-                char label[DNS_LABEL_MAX];
+                char label[DNS_LABEL_MAX+1];
 
                 /* This more or less implements RFC 6335, Section 5.1 */
 
@@ -1239,7 +1236,7 @@ int dns_name_common_suffix(const char *a, const char *b, const char **ret) {
                 return m;
 
         for (;;) {
-                char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
+                char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
                 const char *x, *y;
 
                 if (k >= n || k >= m) {
@@ -1340,7 +1337,7 @@ int dns_name_apply_idna(const char *name, char **ret) {
         assert(ret);
 
         for (;;) {
-                char label[DNS_LABEL_MAX];
+                char label[DNS_LABEL_MAX+1];
 
                 r = dns_label_unescape(&name, label, sizeof label, 0);
                 if (r < 0)
@@ -1425,6 +1422,10 @@ bool dns_name_dont_resolve(const char *name) {
         if (dns_name_endswith(name, "invalid") > 0)
                 return true;
 
+        /* Never respond to some of the domains listed in RFC9476 */
+        if (dns_name_endswith(name, "alt") > 0)
+                return true;
+
         return false;
 }
 #endif /* NM_IGNORED */