summary refs log tree commit diff
path: root/src/libnm-systemd-shared
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-systemd-shared')
-rw-r--r--src/libnm-systemd-shared/meson.build2
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/argv-util.h (renamed from src/libnm-systemd-shared/sd-adapt-shared/def.h)0
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/chase.h3
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/mallinfo-util.h3
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/memstream-util.h3
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/mountpoint-util.h3
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h10
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/psi-util.h3
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/unaligned-fundamental.h3
-rw-r--r--src/libnm-systemd-shared/src/basic/alloc-util.c4
-rw-r--r--src/libnm-systemd-shared/src/basic/alloc-util.h40
-rw-r--r--src/libnm-systemd-shared/src/basic/cgroup-util.h7
-rw-r--r--src/libnm-systemd-shared/src/basic/constants.h112
-rw-r--r--src/libnm-systemd-shared/src/basic/env-file.c109
-rw-r--r--src/libnm-systemd-shared/src/basic/env-file.h14
-rw-r--r--src/libnm-systemd-shared/src/basic/env-util.c117
-rw-r--r--src/libnm-systemd-shared/src/basic/env-util.h2
-rw-r--r--src/libnm-systemd-shared/src/basic/escape.c56
-rw-r--r--src/libnm-systemd-shared/src/basic/fd-util.c253
-rw-r--r--src/libnm-systemd-shared/src/basic/fd-util.h47
-rw-r--r--src/libnm-systemd-shared/src/basic/fileio.c285
-rw-r--r--src/libnm-systemd-shared/src/basic/fileio.h48
-rw-r--r--src/libnm-systemd-shared/src/basic/fs-util.c260
-rw-r--r--src/libnm-systemd-shared/src/basic/fs-util.h23
-rw-r--r--src/libnm-systemd-shared/src/basic/glyph-util.c22
-rw-r--r--src/libnm-systemd-shared/src/basic/glyph-util.h1
-rw-r--r--src/libnm-systemd-shared/src/basic/hash-funcs.h2
-rw-r--r--src/libnm-systemd-shared/src/basic/hashmap.c52
-rw-r--r--src/libnm-systemd-shared/src/basic/hashmap.h3
-rw-r--r--src/libnm-systemd-shared/src/basic/hexdecoct.c229
-rw-r--r--src/libnm-systemd-shared/src/basic/hexdecoct.h10
-rw-r--r--src/libnm-systemd-shared/src/basic/hostname-util.c4
-rw-r--r--src/libnm-systemd-shared/src/basic/hostname-util.h8
-rw-r--r--src/libnm-systemd-shared/src/basic/in-addr-util.c34
-rw-r--r--src/libnm-systemd-shared/src/basic/in-addr-util.h5
-rw-r--r--src/libnm-systemd-shared/src/basic/io-util.c28
-rw-r--r--src/libnm-systemd-shared/src/basic/io-util.h22
-rw-r--r--src/libnm-systemd-shared/src/basic/label.c32
-rw-r--r--src/libnm-systemd-shared/src/basic/label.h14
-rw-r--r--src/libnm-systemd-shared/src/basic/list.h78
-rw-r--r--src/libnm-systemd-shared/src/basic/locale-util.c14
-rw-r--r--src/libnm-systemd-shared/src/basic/lock-util.h42
-rw-r--r--src/libnm-systemd-shared/src/basic/log.h149
-rw-r--r--src/libnm-systemd-shared/src/basic/macro.h80
-rw-r--r--src/libnm-systemd-shared/src/basic/memory-util.c19
-rw-r--r--src/libnm-systemd-shared/src/basic/memory-util.h46
-rw-r--r--src/libnm-systemd-shared/src/basic/mempool.c124
-rw-r--r--src/libnm-systemd-shared/src/basic/mempool.h8
-rw-r--r--src/libnm-systemd-shared/src/basic/missing_fcntl.h17
-rw-r--r--src/libnm-systemd-shared/src/basic/missing_syscall.h124
-rw-r--r--src/libnm-systemd-shared/src/basic/missing_threads.h15
-rw-r--r--src/libnm-systemd-shared/src/basic/origin-id.h36
-rw-r--r--src/libnm-systemd-shared/src/basic/parse-util.c39
-rw-r--r--src/libnm-systemd-shared/src/basic/parse-util.h2
-rw-r--r--src/libnm-systemd-shared/src/basic/path-util.c127
-rw-r--r--src/libnm-systemd-shared/src/basic/path-util.h14
-rw-r--r--src/libnm-systemd-shared/src/basic/prioq.c5
-rw-r--r--src/libnm-systemd-shared/src/basic/prioq.h2
-rw-r--r--src/libnm-systemd-shared/src/basic/process-util.c402
-rw-r--r--src/libnm-systemd-shared/src/basic/process-util.h46
-rw-r--r--src/libnm-systemd-shared/src/basic/random-util.c9
-rw-r--r--src/libnm-systemd-shared/src/basic/random-util.h1
-rw-r--r--src/libnm-systemd-shared/src/basic/ratelimit.c41
-rw-r--r--src/libnm-systemd-shared/src/basic/ratelimit.h3
-rw-r--r--src/libnm-systemd-shared/src/basic/signal-util.c19
-rw-r--r--src/libnm-systemd-shared/src/basic/signal-util.h2
-rw-r--r--src/libnm-systemd-shared/src/basic/socket-util.c190
-rw-r--r--src/libnm-systemd-shared/src/basic/socket-util.h41
-rw-r--r--src/libnm-systemd-shared/src/basic/stat-util.c137
-rw-r--r--src/libnm-systemd-shared/src/basic/stat-util.h26
-rw-r--r--src/libnm-systemd-shared/src/basic/stdio-util.h23
-rw-r--r--src/libnm-systemd-shared/src/basic/string-table.h1
-rw-r--r--src/libnm-systemd-shared/src/basic/string-util.c119
-rw-r--r--src/libnm-systemd-shared/src/basic/string-util.h47
-rw-r--r--src/libnm-systemd-shared/src/basic/strv.c161
-rw-r--r--src/libnm-systemd-shared/src/basic/strv.h45
-rw-r--r--src/libnm-systemd-shared/src/basic/time-util.c487
-rw-r--r--src/libnm-systemd-shared/src/basic/time-util.h45
-rw-r--r--src/libnm-systemd-shared/src/basic/tmpfile-util.c198
-rw-r--r--src/libnm-systemd-shared/src/basic/tmpfile-util.h32
-rw-r--r--src/libnm-systemd-shared/src/basic/umask-util.h6
-rw-r--r--src/libnm-systemd-shared/src/basic/user-util.h13
-rw-r--r--src/libnm-systemd-shared/src/basic/util.c178
-rw-r--r--src/libnm-systemd-shared/src/fundamental/logarithm.h (renamed from src/libnm-systemd-shared/src/basic/util.h)37
-rw-r--r--src/libnm-systemd-shared/src/fundamental/macro-fundamental.h140
-rw-r--r--src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h66
-rw-r--r--src/libnm-systemd-shared/src/fundamental/sha256.c28
-rw-r--r--src/libnm-systemd-shared/src/fundamental/sha256.h8
-rw-r--r--src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c4
-rw-r--r--src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h13
-rw-r--r--src/libnm-systemd-shared/src/shared/dns-domain.c32
-rw-r--r--src/libnm-systemd-shared/src/shared/dns-domain.h5
92 files changed, 3530 insertions, 1889 deletions
diff --git a/src/libnm-systemd-shared/meson.build b/src/libnm-systemd-shared/meson.build
index d8e7a31a..b32bd7f6 100644
--- a/src/libnm-systemd-shared/meson.build
+++ b/src/libnm-systemd-shared/meson.build
@@ -22,6 +22,7 @@ libnm_systemd_shared = static_library(
     'src/basic/in-addr-util.c',
     'src/basic/inotify-util.c',
     'src/basic/io-util.c',
+    'src/basic/label.c',
     'src/basic/locale-util.c',
     'src/basic/memory-util.c',
     'src/basic/mempool.c',
@@ -42,7 +43,6 @@ libnm_systemd_shared = static_library(
     'src/basic/time-util.c',
     'src/basic/tmpfile-util.c',
     'src/basic/utf8.c',
-    'src/basic/util.c',
     'src/fundamental/sha256.c',
     'src/fundamental/string-util-fundamental.c',
     'src/shared/dns-domain.c',
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/def.h b/src/libnm-systemd-shared/sd-adapt-shared/argv-util.h
index 637892c2..637892c2 100644
--- a/src/libnm-systemd-shared/sd-adapt-shared/def.h
+++ b/src/libnm-systemd-shared/sd-adapt-shared/argv-util.h
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/chase.h b/src/libnm-systemd-shared/sd-adapt-shared/chase.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/libnm-systemd-shared/sd-adapt-shared/chase.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/mallinfo-util.h b/src/libnm-systemd-shared/sd-adapt-shared/mallinfo-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/libnm-systemd-shared/sd-adapt-shared/mallinfo-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/memstream-util.h b/src/libnm-systemd-shared/sd-adapt-shared/memstream-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/libnm-systemd-shared/sd-adapt-shared/memstream-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/mountpoint-util.h b/src/libnm-systemd-shared/sd-adapt-shared/mountpoint-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/libnm-systemd-shared/sd-adapt-shared/mountpoint-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h b/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h
index ee5a0a24..de6edd02 100644
--- a/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h
+++ b/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h
@@ -28,12 +28,14 @@
 
 /*****************************************************************************/
 
-#ifndef VALGRIND
-#define VALGRIND 0
-#endif
+#define HAVE_VALGRIND_VALGRIND_H 0
 
 #define ENABLE_DEBUG_HASHMAP 0
 
+#define SD_BOOT 0
+
+#define HAVE_SPLIT_USR 0
+
 /*****************************************************************************
  * The remainder of the header is only enabled when building the systemd code
  * itself.
@@ -45,7 +47,7 @@
 #include <sys/ioctl.h>
 #include <pthread.h>
 
-#define ENABLE_GSHADOW FALSE
+#define ENABLE_GSHADOW 0
 
 #define HAVE_SECCOMP 0
 
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/psi-util.h b/src/libnm-systemd-shared/sd-adapt-shared/psi-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/libnm-systemd-shared/sd-adapt-shared/psi-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/unaligned-fundamental.h b/src/libnm-systemd-shared/sd-adapt-shared/unaligned-fundamental.h
new file mode 100644
index 00000000..97d1609d
--- /dev/null
+++ b/src/libnm-systemd-shared/sd-adapt-shared/unaligned-fundamental.h
@@ -0,0 +1,3 @@
+#pragma once
+
+#include "libnm-std-aux/unaligned-fundamental.h"
diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.c b/src/libnm-systemd-shared/src/basic/alloc-util.c
index e252b6ab..c07ab589 100644
--- a/src/libnm-systemd-shared/src/basic/alloc-util.c
+++ b/src/libnm-systemd-shared/src/basic/alloc-util.c
@@ -104,3 +104,7 @@ void* greedy_realloc0(
 
         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 b38db7d4..9a62381d 100644
--- a/src/libnm-systemd-shared/src/basic/alloc-util.h
+++ b/src/libnm-systemd-shared/src/basic/alloc-util.h
@@ -2,6 +2,7 @@
 #pragma once
 
 #include <alloca.h>
+#include <malloc.h>
 #include <stddef.h>
 #include <stdlib.h>
 #include <string.h>
@@ -14,6 +15,7 @@
 
 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. */
@@ -184,17 +186,35 @@ void* greedy_realloc0(void **p, size_t need, size_t size);
 #  define msan_unpoison(r, s)
 #endif
 
-/* This returns the number of usable bytes in a malloc()ed region as per malloc_usable_size(), in a way that
- * is compatible with _FORTIFY_SOURCES. If _FORTIFY_SOURCES is used many memory operations will take the
- * object size as returned by __builtin_object_size() into account. Hence, let's return the smaller size of
- * malloc_usable_size() and __builtin_object_size() here, so that we definitely operate in safe territory by
- * both the compiler's and libc's standards. Note that __builtin_object_size() evaluates to SIZE_MAX if the
- * size cannot be determined, hence the MIN() expression should be safe with dynamically sized memory,
- * too. Moreover, when NULL is passed malloc_usable_size() is documented to return zero, and
- * __builtin_object_size() returns SIZE_MAX too, hence we also return a sensible value of 0 in this corner
- * case. */
+/* Dummy allocator to tell the compiler that the new size of p is newsize. The implementation returns the
+ * pointer as is; the only reason for its existence is as a conduit for the _alloc_ attribute.  This must not
+ * be inlined (hence a non-static function with _noinline_ because LTO otherwise tries to inline it) because
+ * gcc then loses the attributes on the function.
+ * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96503 */
+void *expand_to_usable(void *p, size_t newsize) _alloc_(2) _returns_nonnull_ _noinline_;
+
+static inline size_t malloc_sizeof_safe(void **xp) {
+        if (_unlikely_(!xp || !*xp))
+                return 0;
+
+        size_t sz = malloc_usable_size(*xp);
+        *xp = expand_to_usable(*xp, sz);
+        /* GCC doesn't see the _returns_nonnull_ when built with ubsan, so yet another hint to make it doubly
+         * clear that expand_to_usable won't return NULL.
+         * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79265 */
+        if (!*xp)
+                assert_not_reached();
+        return sz;
+}
+
+/* This returns the number of usable bytes in a malloc()ed region as per malloc_usable_size(), which may
+ * return a value larger than the size that was actually allocated. Access to that additional memory is
+ * discouraged because it violates the C standard; a compiler cannot see that this as valid. To help the
+ * compiler out, the MALLOC_SIZEOF_SAFE macro 'allocates' the usable size using a dummy allocator function
+ * expand_to_usable. There is a possibility of malloc_usable_size() returning different values during the
+ * lifetime of an object, which may cause problems, but the glibc allocator does not do that at the moment. */
 #define MALLOC_SIZEOF_SAFE(x) \
-        MIN(malloc_usable_size(x), __builtin_object_size(x, 0))
+        malloc_sizeof_safe((void**) &__builtin_choose_expr(__builtin_constant_p(x), (void*) { NULL }, (x)))
 
 /* Inspired by ELEMENTSOF() but operates on malloc()'ed memory areas: typesafely returns the number of items
  * that fit into the specified memory block */
diff --git a/src/libnm-systemd-shared/src/basic/cgroup-util.h b/src/libnm-systemd-shared/src/basic/cgroup-util.h
index df6d5b7b..9b30ae03 100644
--- a/src/libnm-systemd-shared/src/basic/cgroup-util.h
+++ b/src/libnm-systemd-shared/src/basic/cgroup-util.h
@@ -9,7 +9,7 @@
 #include <sys/statfs.h>
 #include <sys/types.h>
 
-#include "def.h"
+#include "constants.h"
 #include "set.h"
 
 #define SYSTEMD_CGROUP_CONTROLLER_LEGACY "name=systemd"
@@ -239,7 +239,6 @@ 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_set_access(const char *controller, const char *path, uid_t uid, gid_t gid);
 int cg_get_owner(const char *controller, 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);
@@ -261,6 +260,7 @@ 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);
@@ -279,7 +279,8 @@ int cg_pid_get_user_slice(pid_t pid, char **slice);
 
 int cg_path_decode_unit(const char *cgroup, char **unit);
 
-char *cg_escape(const char *p);
+bool cg_needs_escape(const char *p);
+int cg_escape(const char *p, char **ret);
 char *cg_unescape(const char *p) _pure_;
 
 bool cg_controller_is_valid(const char *p);
diff --git a/src/libnm-systemd-shared/src/basic/constants.h b/src/libnm-systemd-shared/src/basic/constants.h
new file mode 100644
index 00000000..3f96786d
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/constants.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#if !defined(HAS_FEATURE_MEMORY_SANITIZER)
+#  if defined(__has_feature)
+#    if __has_feature(memory_sanitizer)
+#      define HAS_FEATURE_MEMORY_SANITIZER 1
+#    endif
+#  endif
+#  if !defined(HAS_FEATURE_MEMORY_SANITIZER)
+#    define HAS_FEATURE_MEMORY_SANITIZER 0
+#  endif
+#endif
+
+#if !defined(HAS_FEATURE_ADDRESS_SANITIZER)
+#  ifdef __SANITIZE_ADDRESS__
+#      define HAS_FEATURE_ADDRESS_SANITIZER 1
+#  elif defined(__has_feature)
+#    if __has_feature(address_sanitizer)
+#      define HAS_FEATURE_ADDRESS_SANITIZER 1
+#    endif
+#  endif
+#  if !defined(HAS_FEATURE_ADDRESS_SANITIZER)
+#    define HAS_FEATURE_ADDRESS_SANITIZER 0
+#  endif
+#endif
+
+#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC)
+
+/* Many different things, but also system unit start/stop */
+#define DEFAULT_TIMEOUT_USEC (DEFAULT_TIMEOUT_SEC*USEC_PER_SEC)
+/* User unit start/stop */
+#define DEFAULT_USER_TIMEOUT_USEC (DEFAULT_USER_TIMEOUT_SEC*USEC_PER_SEC)
+/* Timeout for user confirmation on the console */
+#define DEFAULT_CONFIRM_USEC (30*USEC_PER_SEC)
+
+/* We use an extra-long timeout for the reload. This is because a reload or reexec means generators are rerun
+ * which are timed out after DEFAULT_TIMEOUT_USEC. Let's use twice that time here, so that the generators can
+ * have their timeout, and for everything else there's the same time budget in place. */
+#define DAEMON_RELOAD_TIMEOUT_SEC (DEFAULT_TIMEOUT_USEC * 2)
+
+#define DEFAULT_START_LIMIT_INTERVAL (10*USEC_PER_SEC)
+#define DEFAULT_START_LIMIT_BURST 5
+
+/* Wait for 1.5 seconds at maximum for freeze operation */
+#define FREEZE_TIMEOUT (1500 * USEC_PER_MSEC)
+
+/* The default time after which exit-on-idle services exit. This
+ * should be kept lower than the watchdog timeout, because otherwise
+ * the watchdog pings will keep the loop busy. */
+#define DEFAULT_EXIT_USEC (30*USEC_PER_SEC)
+
+/* The default value for the net.unix.max_dgram_qlen sysctl */
+#define DEFAULT_UNIX_MAX_DGRAM_QLEN 512
+
+#define SIGNALS_CRASH_HANDLER SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT
+#define SIGNALS_IGNORE SIGPIPE
+
+#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)
+
+#define CONF_PATHS_USR(n)                       \
+        "/etc/" n,                              \
+        "/run/" n,                              \
+        "/usr/local/lib/" n,                    \
+        "/usr/lib/" n
+
+#define CONF_PATHS(n)                           \
+        CONF_PATHS_USR(n)                       \
+        _CONF_PATHS_SPLIT_USR(n)
+
+#define CONF_PATHS_USR_STRV(n)                  \
+        STRV_MAKE(CONF_PATHS_USR(n))
+
+#define CONF_PATHS_STRV(n)                      \
+        STRV_MAKE(CONF_PATHS(n))
+
+/* The limit for PID 1 itself (which is not inherited to children) */
+#define HIGH_RLIMIT_MEMLOCK (1024ULL*1024ULL*64ULL)
+
+/* Since kernel 5.16 the kernel default limit was raised to 8M. Let's adjust things on old kernels too, and
+ * 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"
+/* 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 KERNEL_BASELINE_VERSION "4.15"
diff --git a/src/libnm-systemd-shared/src/basic/env-file.c b/src/libnm-systemd-shared/src/basic/env-file.c
index 266e4e79..db270bed 100644
--- a/src/libnm-systemd-shared/src/basic/env-file.c
+++ b/src/libnm-systemd-shared/src/basic/env-file.c
@@ -14,11 +14,17 @@
 #include "tmpfile-util.h"
 #include "utf8.h"
 
+typedef int (*push_env_func_t)(
+                const char *filename,
+                unsigned line,
+                const char *key,
+                char *value,
+                void *userdata);
+
 static int parse_env_file_internal(
                 FILE *f,
                 const char *fname,
-                int (*push) (const char *filename, unsigned line,
-                             const char *key, char *value, void *userdata),
+                push_env_func_t push,
                 void *userdata) {
 
         size_t n_key = 0, n_value = 0, last_value_whitespace = SIZE_MAX, last_key_whitespace = SIZE_MAX;
@@ -39,6 +45,9 @@ static int parse_env_file_internal(
                 COMMENT_ESCAPE
         } state = PRE_KEY;
 
+        assert(f || fname);
+        assert(push);
+
         if (f)
                 r = read_full_stream(f, &contents, NULL);
         else
@@ -276,6 +285,8 @@ static int check_utf8ness_and_warn(
                 const char *filename, unsigned line,
                 const char *key, char *value) {
 
+        assert(key);
+
         if (!utf8_is_valid(key)) {
                 _cleanup_free_ char *p = NULL;
 
@@ -306,6 +317,8 @@ static int parse_env_file_push(
         va_list aq, *ap = userdata;
         int r;
 
+        assert(key);
+
         r = check_utf8ness_and_warn(filename, line, key, value);
         if (r < 0)
                 return r;
@@ -319,8 +332,7 @@ static int parse_env_file_push(
 
                 if (streq(key, k)) {
                         va_end(aq);
-                        free(*v);
-                        *v = value;
+                        free_and_replace(*v, value);
 
                         return 1;
                 }
@@ -340,6 +352,26 @@ int parse_env_filev(
         int r;
         va_list aq;
 
+        assert(f || fname);
+
+        va_copy(aq, ap);
+        r = parse_env_file_internal(f, fname, parse_env_file_push, &aq);
+        va_end(aq);
+        return r;
+}
+
+#if 0 /* NM_IGNORED */
+int parse_env_file_fdv(int fd, const char *fname, va_list ap) {
+        _cleanup_fclose_ FILE *f = NULL;
+        va_list aq;
+        int r;
+
+        assert(fd >= 0);
+
+        r = fdopen_independent(fd, "re", &f);
+        if (r < 0)
+                return r;
+
         va_copy(aq, ap);
         r = parse_env_file_internal(f, fname, parse_env_file_push, &aq);
         va_end(aq);
@@ -354,6 +386,8 @@ int parse_env_file_sentinel(
         va_list ap;
         int r;
 
+        assert(f || fname);
+
         va_start(ap, fname);
         r = parse_env_filev(f, fname, ap);
         va_end(ap);
@@ -361,15 +395,34 @@ int parse_env_file_sentinel(
         return r;
 }
 
-#if 0 /* NM_IGNORED */
+int parse_env_file_fd_sentinel(
+                int fd,
+                const char *fname, /* only used for logging */
+                ...) {
+
+        va_list ap;
+        int r;
+
+        assert(fd >= 0);
+
+        va_start(ap, fname);
+        r = parse_env_file_fdv(fd, fname, ap);
+        va_end(ap);
+
+        return r;
+}
+
 static int load_env_file_push(
                 const char *filename, unsigned line,
                 const char *key, char *value,
                 void *userdata) {
+
         char ***m = userdata;
         char *p;
         int r;
 
+        assert(key);
+
         r = check_utf8ness_and_warn(filename, line, key, value);
         if (r < 0)
                 return r;
@@ -386,15 +439,18 @@ static int load_env_file_push(
         return 0;
 }
 
-int load_env_file(FILE *f, const char *fname, char ***rl) {
+int load_env_file(FILE *f, const char *fname, char ***ret) {
         _cleanup_strv_free_ char **m = NULL;
         int r;
 
+        assert(f || fname);
+        assert(ret);
+
         r = parse_env_file_internal(f, fname, load_env_file_push, &m);
         if (r < 0)
                 return r;
 
-        *rl = TAKE_PTR(m);
+        *ret = TAKE_PTR(m);
         return 0;
 }
 
@@ -406,6 +462,8 @@ static int load_env_file_push_pairs(
         char ***m = ASSERT_PTR(userdata);
         int r;
 
+        assert(key);
+
         r = check_utf8ness_and_warn(filename, line, key, value);
         if (r < 0)
                 return r;
@@ -429,18 +487,34 @@ static int load_env_file_push_pairs(
                 return strv_extend(m, "");
 }
 
-int load_env_file_pairs(FILE *f, const char *fname, char ***rl) {
+int load_env_file_pairs(FILE *f, const char *fname, char ***ret) {
         _cleanup_strv_free_ char **m = NULL;
         int r;
 
+        assert(f || fname);
+        assert(ret);
+
         r = parse_env_file_internal(f, fname, load_env_file_push_pairs, &m);
         if (r < 0)
                 return r;
 
-        *rl = TAKE_PTR(m);
+        *ret = TAKE_PTR(m);
         return 0;
 }
 
+int load_env_file_pairs_fd(int fd, const char *fname, char ***ret) {
+        _cleanup_fclose_ FILE *f = NULL;
+        int r;
+
+        assert(fd >= 0);
+
+        r = fdopen_independent(fd, "re", &f);
+        if (r < 0)
+                return r;
+
+        return load_env_file_pairs(f, fname, ret);
+}
+
 static int merge_env_file_push(
                 const char *filename, unsigned line,
                 const char *key, char *value,
@@ -449,6 +523,8 @@ static int merge_env_file_push(
         char ***env = ASSERT_PTR(userdata);
         char *expanded_value;
 
+        assert(key);
+
         if (!value) {
                 log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename), line, key);
                 return 0;
@@ -479,6 +555,9 @@ int merge_env_file(
                 FILE *f,
                 const char *fname) {
 
+        assert(env);
+        assert(f || fname);
+
         /* NOTE: this function supports braceful and braceless variable expansions,
          * plus "extended" substitutions, unlike other exported parsing functions.
          */
@@ -489,6 +568,9 @@ int merge_env_file(
 static void write_env_var(FILE *f, const char *v) {
         const char *p;
 
+        assert(f);
+        assert(v);
+
         p = strchr(v, '=');
         if (!p) {
                 /* Fallback */
@@ -517,14 +599,15 @@ static void write_env_var(FILE *f, const char *v) {
         fputc_unlocked('\n', f);
 }
 
-int write_env_file(const char *fname, char **l) {
+int write_env_file_at(int dir_fd, const char *fname, char **l) {
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *p = NULL;
         int r;
 
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
         assert(fname);
 
-        r = fopen_temporary(fname, &f, &p);
+        r = fopen_temporary_at(dir_fd, fname, &f, &p);
         if (r < 0)
                 return r;
 
@@ -535,13 +618,13 @@ int write_env_file(const char *fname, char **l) {
 
         r = fflush_and_check(f);
         if (r >= 0) {
-                if (rename(p, fname) >= 0)
+                if (renameat(dir_fd, p, dir_fd, fname) >= 0)
                         return 0;
 
                 r = -errno;
         }
 
-        (void) unlink(p);
+        (void) unlinkat(dir_fd, p, 0);
         return r;
 }
 #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 de475885..2465eedd 100644
--- a/src/libnm-systemd-shared/src/basic/env-file.h
+++ b/src/libnm-systemd-shared/src/basic/env-file.h
@@ -1,17 +1,25 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <fcntl.h>
 #include <stdarg.h>
 #include <stdio.h>
 
 #include "macro.h"
 
 int parse_env_filev(FILE *f, const char *fname, va_list ap);
+int parse_env_file_fdv(int fd, const char *fname, va_list ap);
 int parse_env_file_sentinel(FILE *f, const char *fname, ...) _sentinel_;
 #define parse_env_file(f, fname, ...) parse_env_file_sentinel(f, fname, __VA_ARGS__, NULL)
-int load_env_file(FILE *f, const char *fname, char ***l);
-int load_env_file_pairs(FILE *f, const char *fname, char ***l);
+int parse_env_file_fd_sentinel(int fd, const char *fname, ...) _sentinel_;
+#define parse_env_file_fd(fd, fname, ...) parse_env_file_fd_sentinel(fd, fname, __VA_ARGS__, NULL)
+int load_env_file(FILE *f, const char *fname, char ***ret);
+int load_env_file_pairs(FILE *f, const char *fname, char ***ret);
+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(const char *fname, char **l);
+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);
+}
diff --git a/src/libnm-systemd-shared/src/basic/env-util.c b/src/libnm-systemd-shared/src/basic/env-util.c
index f58d86a7..fa2753bc 100644
--- a/src/libnm-systemd-shared/src/basic/env-util.c
+++ b/src/libnm-systemd-shared/src/basic/env-util.c
@@ -139,20 +139,20 @@ bool strv_env_name_or_assignment_is_valid(char **l) {
         return true;
 }
 
-static int env_append(char **r, char ***k, char **a) {
-        assert(r);
+static int env_append(char **e, char ***k, char **a) {
+        assert(e);
         assert(k);
-        assert(*k >= r);
+        assert(*k >= e);
 
         if (!a)
                 return 0;
 
-        /* Expects the following arguments: 'r' shall point to the beginning of an strv we are going to append to, 'k'
+        /* Expects the following arguments: 'e' shall point to the beginning of an strv we are going to append to, 'k'
          * to a pointer pointing to the NULL entry at the end of the same array. 'a' shall point to another strv.
          *
-         * This call adds every entry of 'a' to 'r', either overriding an existing matching entry, or appending to it.
+         * This call adds every entry of 'a' to 'e', either overriding an existing matching entry, or appending to it.
          *
-         * This call assumes 'r' has enough pre-allocated space to grow by all of 'a''s items. */
+         * This call assumes 'e' has enough pre-allocated space to grow by all of 'a''s items. */
 
         for (; *a; a++) {
                 char **j, *c;
@@ -162,7 +162,7 @@ static int env_append(char **r, char ***k, char **a) {
                 if ((*a)[n] == '=')
                         n++;
 
-                for (j = r; j < *k; j++)
+                for (j = e; j < *k; j++)
                         if (strneq(*j, *a, n))
                                 break;
 
@@ -269,7 +269,7 @@ static bool env_entry_has_name(const char *entry, const char *name) {
 
 char **strv_env_delete(char **x, size_t n_lists, ...) {
         size_t n, i = 0;
-        char **r;
+        _cleanup_strv_free_ char **t = NULL;
         va_list ap;
 
         /* Deletes every entry from x that is mentioned in the other
@@ -277,8 +277,8 @@ char **strv_env_delete(char **x, size_t n_lists, ...) {
 
         n = strv_length(x);
 
-        r = new(char*, n+1);
-        if (!r)
+        t = new(char*, n+1);
+        if (!t)
                 return NULL;
 
         STRV_FOREACH(k, x) {
@@ -293,11 +293,9 @@ char **strv_env_delete(char **x, size_t n_lists, ...) {
                 }
                 va_end(ap);
 
-                r[i] = strdup(*k);
-                if (!r[i]) {
-                        strv_free(r);
+                t[i] = strdup(*k);
+                if (!t[i])
                         return NULL;
-                }
 
                 i++;
                 continue;
@@ -306,11 +304,11 @@ char **strv_env_delete(char **x, size_t n_lists, ...) {
                 va_end(ap);
         }
 
-        r[i] = NULL;
+        t[i] = NULL;
 
         assert(i <= n);
 
-        return r;
+        return TAKE_PTR(t);
 }
 
 char **strv_env_unset(char **l, const char *p) {
@@ -462,6 +460,48 @@ int strv_env_assign(char ***l, const char *key, const char *value) {
         return strv_env_replace_consume(l, p);
 }
 
+int _strv_env_assign_many(char ***l, ...) {
+        va_list ap;
+        int r;
+
+        assert(l);
+
+        va_start(ap, l);
+        for (;;) {
+                const char *key, *value;
+
+                key = va_arg(ap, const char *);
+                if (!key)
+                        break;
+
+                if (!env_name_is_valid(key)) {
+                        va_end(ap);
+                        return -EINVAL;
+                }
+
+                value = va_arg(ap, const char *);
+                if (!value) {
+                        strv_env_unset(*l, key);
+                        continue;
+                }
+
+                char *p = strjoin(key, "=", value);
+                if (!p) {
+                        va_end(ap);
+                        return -ENOMEM;
+                }
+
+                r = strv_env_replace_consume(l, p);
+                if (r < 0) {
+                        va_end(ap);
+                        return r;
+                }
+        }
+        va_end(ap);
+
+        return 0;
+}
+
 char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) {
         assert(name);
 
@@ -488,6 +528,7 @@ char *strv_env_get(char **l, const char *name) {
 
         return strv_env_get_n(l, name, strlen(name), 0);
 }
+#endif /* NM_IGNORED */
 
 char *strv_env_pairs_get(char **l, const char *name) {
         char *result = NULL;
@@ -501,6 +542,7 @@ char *strv_env_pairs_get(char **l, const char *name) {
         return result;
 }
 
+#if 0 /* NM_IGNORED */
 char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) {
         int k = 0;
 
@@ -549,7 +591,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
 
         const char *e, *word = format, *test_value = NULL; /* test_value is initialized to appease gcc */
         char *k;
-        _cleanup_free_ char *r = NULL;
+        _cleanup_free_ char *s = NULL;
         size_t i, len = 0; /* len is initialized to appease gcc */
         int nest = 0;
 
@@ -565,31 +607,31 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
 
                 case CURLY:
                         if (*e == '{') {
-                                k = strnappend(r, word, e-word-1);
+                                k = strnappend(s, word, e-word-1);
                                 if (!k)
                                         return NULL;
 
-                                free_and_replace(r, k);
+                                free_and_replace(s, k);
 
                                 word = e-1;
                                 state = VARIABLE;
                                 nest++;
                         } else if (*e == '$') {
-                                k = strnappend(r, word, e-word);
+                                k = strnappend(s, word, e-word);
                                 if (!k)
                                         return NULL;
 
-                                free_and_replace(r, k);
+                                free_and_replace(s, k);
 
                                 word = e+1;
                                 state = WORD;
 
                         } else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_BASH_ENV_NAME_CHARS, *e)) {
-                                k = strnappend(r, word, e-word-1);
+                                k = strnappend(s, word, e-word-1);
                                 if (!k)
                                         return NULL;
 
-                                free_and_replace(r, k);
+                                free_and_replace(s, k);
 
                                 word = e-1;
                                 state = VARIABLE_RAW;
@@ -604,7 +646,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
 
                                 t = strv_env_get_n(env, word+2, e-word-2, flags);
 
-                                if (!strextend(&r, t))
+                                if (!strextend(&s, t))
                                         return NULL;
 
                                 word = e+1;
@@ -657,7 +699,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
                                 else if (!t && state == DEFAULT_VALUE)
                                         t = v = replace_env_n(test_value, e-test_value, env, flags);
 
-                                if (!strextend(&r, t))
+                                if (!strextend(&s, t))
                                         return NULL;
 
                                 word = e+1;
@@ -673,7 +715,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
 
                                 t = strv_env_get_n(env, word+1, e-word-1, flags);
 
-                                if (!strextend(&r, t))
+                                if (!strextend(&s, t))
                                         return NULL;
 
                                 word = e--;
@@ -689,13 +731,13 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
                 assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
 
                 t = strv_env_get_n(env, word+1, e-word-1, flags);
-                return strjoin(r, t);
+                return strjoin(s, t);
         } else
-                return strnappend(r, word, e-word);
+                return strnappend(s, word, e-word);
 }
 
 char **replace_env_argv(char **argv, char **env) {
-        char **ret;
+        _cleanup_strv_free_ char **ret = NULL;
         size_t k = 0, l = 0;
 
         l = strv_length(argv);
@@ -709,7 +751,8 @@ char **replace_env_argv(char **argv, char **env) {
                 /* If $FOO appears as single word, replace it by the split up variable */
                 if ((*i)[0] == '$' && !IN_SET((*i)[1], '{', '$')) {
                         char *e;
-                        char **w, **m = NULL;
+                        char **w;
+                        _cleanup_strv_free_ char **m = NULL;
                         size_t q;
 
                         e = strv_env_get(env, *i+1);
@@ -719,11 +762,9 @@ char **replace_env_argv(char **argv, char **env) {
                                 r = strv_split_full(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_UNQUOTE);
                                 if (r < 0) {
                                         ret[k] = NULL;
-                                        strv_free(ret);
                                         return NULL;
                                 }
-                        } else
-                                m = NULL;
+                        }
 
                         q = strv_length(m);
                         l = l + q - 1;
@@ -731,15 +772,13 @@ char **replace_env_argv(char **argv, char **env) {
                         w = reallocarray(ret, l + 1, sizeof(char *));
                         if (!w) {
                                 ret[k] = NULL;
-                                strv_free(ret);
-                                strv_free(m);
                                 return NULL;
                         }
 
                         ret = w;
                         if (m) {
                                 memcpy(ret + k, m, q * sizeof(char*));
-                                free(m);
+                                m = mfree(m);
                         }
 
                         k += q;
@@ -748,15 +787,13 @@ char **replace_env_argv(char **argv, char **env) {
 
                 /* If ${FOO} appears as part of a word, replace it by the variable as-is */
                 ret[k] = replace_env(*i, env, 0);
-                if (!ret[k]) {
-                        strv_free(ret);
+                if (!ret[k])
                         return NULL;
-                }
                 k++;
         }
 
         ret[k] = NULL;
-        return ret;
+        return TAKE_PTR(ret);
 }
 #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 b927ac7a..b0ff5a11 100644
--- a/src/libnm-systemd-shared/src/basic/env-util.h
+++ b/src/libnm-systemd-shared/src/basic/env-util.h
@@ -49,6 +49,8 @@ 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_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_;
diff --git a/src/libnm-systemd-shared/src/basic/escape.c b/src/libnm-systemd-shared/src/basic/escape.c
index cc317dfb..6d2c1d4d 100644
--- a/src/libnm-systemd-shared/src/basic/escape.c
+++ b/src/libnm-systemd-shared/src/basic/escape.c
@@ -448,44 +448,51 @@ char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFl
 }
 
 char* octescape(const char *s, size_t len) {
-        char *r, *t;
-        const char *f;
+        char *buf, *t;
 
-        /* Escapes all chars in bad, in addition to \ and " chars,
-         * in \nnn style escaping. */
+        /* Escapes all chars in bad, in addition to \ and " chars, in \nnn style escaping. */
 
-        r = new(char, len * 4 + 1);
-        if (!r)
+        assert(s || len == 0);
+
+        t = buf = new(char, len * 4 + 1);
+        if (!buf)
                 return NULL;
 
-        for (f = s, t = r; f < s + len; f++) {
+        for (size_t i = 0; i < len; i++) {
+                uint8_t u = (uint8_t) s[i];
 
-                if (*f < ' ' || *f >= 127 || IN_SET(*f, '\\', '"')) {
+                if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"')) {
                         *(t++) = '\\';
-                        *(t++) = '0' + (*f >> 6);
-                        *(t++) = '0' + ((*f >> 3) & 8);
-                        *(t++) = '0' + (*f & 8);
+                        *(t++) = '0' + (u >> 6);
+                        *(t++) = '0' + ((u >> 3) & 7);
+                        *(t++) = '0' + (u & 7);
                 } else
-                        *(t++) = *f;
+                        *(t++) = u;
         }
 
         *t = 0;
-
-        return r;
-
+        return buf;
 }
 
 static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad) {
         assert(bad);
+        assert(t);
+        assert(s);
 
-        for (; *s; s++)
-                if (char_is_cc(*s))
-                        t += cescape_char(*s, t);
-                else {
+        while (*s) {
+                int l = utf8_encoded_valid_unichar(s, SIZE_MAX);
+
+                if (char_is_cc(*s) || l < 0)
+                        t += cescape_char(*(s++), t);
+                else if (l == 1) {
                         if (*s == '\\' || strchr(bad, *s))
                                 *(t++) = '\\';
-                        *(t++) = *s;
+                        *(t++) = *(s++);
+                } else {
+                        t = mempcpy(t, s, l);
+                        s += l;
                 }
+        }
 
         return t;
 }
@@ -514,11 +521,16 @@ char* shell_maybe_quote(const char *s, ShellEscapeFlags flags) {
         if (FLAGS_SET(flags, SHELL_ESCAPE_EMPTY) && isempty(s))
                 return strdup("\"\""); /* We don't use $'' here in the POSIX mode. "" is fine too. */
 
-        for (p = s; *p; p++)
-                if (char_is_cc(*p) ||
+        for (p = s; *p; ) {
+                int l = utf8_encoded_valid_unichar(p, SIZE_MAX);
+
+                if (char_is_cc(*p) || l < 0 ||
                     strchr(WHITESPACE SHELL_NEED_QUOTES, *p))
                         break;
 
+                p += l;
+        }
+
         if (!*p)
                 return strdup(s);
 
diff --git a/src/libnm-systemd-shared/src/basic/fd-util.c b/src/libnm-systemd-shared/src/basic/fd-util.c
index 61a5bb78..a0e2f4eb 100644
--- a/src/libnm-systemd-shared/src/basic/fd-util.c
+++ b/src/libnm-systemd-shared/src/basic/fd-util.c
@@ -23,6 +23,7 @@
 #include "missing_fcntl.h"
 #include "missing_fs.h"
 #include "missing_syscall.h"
+#include "mountpoint-util.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "process-util.h"
@@ -31,7 +32,6 @@
 #include "stat-util.h"
 #include "stdio-util.h"
 #include "tmpfile-util.h"
-#include "util.h"
 
 /* The maximum number of iterations in the loop to close descriptors in the fallback case
  * when /proc/self/fd/ is inaccessible. */
@@ -59,11 +59,9 @@ int close_nointr(int fd) {
 }
 
 int safe_close(int fd) {
-
         /*
-         * Like close_nointr() but cannot fail. Guarantees errno is
-         * unchanged. Is a NOP with negative fds passed, and returns
-         * -1, so that it can be used in this syntax:
+         * Like close_nointr() but cannot fail. Guarantees errno is unchanged. Is a noop for negative fds,
+         * and returns -EBADF, so that it can be used in this syntax:
          *
          * fd = safe_close(fd);
          */
@@ -79,7 +77,7 @@ int safe_close(int fd) {
                 assert_se(close_nointr(fd) != -EBADF);
         }
 
-        return -1;
+        return -EBADF;
 }
 
 void safe_close_pair(int p[static 2]) {
@@ -177,12 +175,35 @@ 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;
+
+        assert(n_fds == 0 || fds);
+
+        for (size_t i = 0; i < n_fds; i++) {
+                if (fds[i] < 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 */
+        }
+
+        return ret;
+}
+
 _pure_ static bool fd_in_set(int fd, const int fdset[], size_t n_fdset) {
         assert(n_fdset == 0 || fdset);
 
-        for (size_t i = 0; i < n_fdset; i++)
+        for (size_t i = 0; i < n_fdset; i++) {
+                if (fdset[i] < 0)
+                        continue;
+
                 if (fdset[i] == fd)
                         return true;
+        }
 
         return false;
 }
@@ -229,7 +250,7 @@ static int close_all_fds_frugal(const int except[], size_t n_except) {
                                        "Refusing to loop over %d potential fds.",
                                        max_fd);
 
-        for (int fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -1) {
+        for (int fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -EBADF) {
                 int q;
 
                 if (fd_in_set(fd, except, n_except))
@@ -255,6 +276,10 @@ static int close_all_fds_special_case(const int except[], size_t n_except) {
         if (!have_close_range)
                 return 0;
 
+        if (n_except == 1 && except[0] < 0) /* Minor optimization: if we only got one fd, and it's invalid,
+                                             * we got none */
+                n_except = 0;
+
         switch (n_except) {
 
         case 0:
@@ -389,12 +414,13 @@ int close_all_fds(const int except[], size_t n_except) {
                 return close_all_fds_frugal(except, n_except); /* ultimate fallback if /proc/ is not available */
 
         FOREACH_DIRENT(de, d, return -errno) {
-                int fd = -1, q;
+                int fd = -EBADF, q;
 
                 if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN))
                         continue;
 
-                if (safe_atoi(de->d_name, &fd) < 0)
+                fd = parse_fd(de->d_name);
+                if (fd < 0)
                         /* Let's better ignore this, just in case */
                         continue;
 
@@ -479,7 +505,8 @@ void cmsg_close_all(struct msghdr *mh) {
 
         CMSG_FOREACH(cmsg, mh)
                 if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
-                        close_many((int*) ((void*) CMSG_DATA(cmsg)), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int));
+                        close_many(CMSG_TYPED_DATA(cmsg, int),
+                                   (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int));
 }
 
 bool fdname_is_valid(const char *s) {
@@ -513,6 +540,11 @@ bool fdname_is_valid(const char *s) {
 int fd_get_path(int fd, char **ret) {
         int r;
 
+        assert(fd >= 0 || fd == AT_FDCWD);
+
+        if (fd == AT_FDCWD)
+                return safe_getcwd(ret);
+
         r = readlink_malloc(FORMAT_PROC_FD_PATH(fd), ret);
         if (r == -ENOENT) {
                 /* ENOENT can mean two things: that the fd does not exist or that /proc is not mounted. Let's make
@@ -611,25 +643,23 @@ int fd_move_above_stdio(int fd) {
 
 #if 0 /* NM_IGNORED */
 int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd) {
-
-        int fd[3] = { /* Put together an array of fds we work on */
-                original_input_fd,
-                original_output_fd,
-                original_error_fd
-        };
-
-        int r, i,
-                null_fd = -1,                /* if we open /dev/null, we store the fd to it here */
-                copy_fd[3] = { -1, -1, -1 }; /* This contains all fds we duplicate here temporarily, and hence need to close at the end */
+        int fd[3] = { original_input_fd,             /* Put together an array of fds we work on */
+                      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
+                                                      * temporarily, and hence need to close at the end. */
+            r;
         bool null_readable, null_writable;
 
-        /* Sets up stdin, stdout, stderr with the three file descriptors passed in. If any of the descriptors is
-         * specified as -1 it will be connected with /dev/null instead. If any of the file descriptors is passed as
-         * itself (e.g. stdin as STDIN_FILENO) it is left unmodified, but the O_CLOEXEC bit is turned off should it be
-         * on.
+        /* Sets up stdin, stdout, stderr with the three file descriptors passed in. If any of the descriptors
+         * is specified as -EBADF it will be connected with /dev/null instead. If any of the file descriptors
+         * is passed as itself (e.g. stdin as STDIN_FILENO) it is left unmodified, but the O_CLOEXEC bit is
+         * turned off should it be on.
          *
-         * Note that if any of the passed file descriptors are > 2 they will be closed — both on success and on
-         * failure! Thus, callers should assume that when this function returns the input fds are invalidated.
+         * Note that if any of the passed file descriptors are > 2 they will be closed — both on success and
+         * on failure! Thus, callers should assume that when this function returns the input fds are
+         * invalidated.
          *
          * Note that when this function fails stdin/stdout/stderr might remain half set up!
          *
@@ -665,7 +695,7 @@ int rearrange_stdio(int original_input_fd, int original_output_fd, int original_
         }
 
         /* Let's assemble fd[] with the fds to install in place of stdin/stdout/stderr */
-        for (i = 0; i < 3; i++) {
+        for (int i = 0; i < 3; i++) {
 
                 if (fd[i] < 0)
                         fd[i] = null_fd;        /* A negative parameter means: connect this one to /dev/null */
@@ -681,10 +711,10 @@ int rearrange_stdio(int original_input_fd, int original_output_fd, int original_
                 }
         }
 
-        /* At this point we now have the fds to use in fd[], and they are all above the stdio range, so that we
-         * have freedom to move them around. If the fds already were at the right places then the specific fds are
-         * -1. Let's now move them to the right places. This is the point of no return. */
-        for (i = 0; i < 3; i++) {
+        /* At this point we now have the fds to use in fd[], and they are all above the stdio range, so that
+         * we have freedom to move them around. If the fds already were at the right places then the specific
+         * fds are -EBADF. Let's now move them to the right places. This is the point of no return. */
+        for (int i = 0; i < 3; i++) {
 
                 if (fd[i] == i) {
 
@@ -715,7 +745,7 @@ finish:
                 safe_close_above_stdio(original_error_fd);
 
         /* Close the copies we moved > 2 */
-        for (i = 0; i < 3; i++)
+        for (int i = 0; i < 3; i++)
                 safe_close(copy_fd[i]);
 
         /* Close our null fd, if it's > 2 */
@@ -723,27 +753,47 @@ finish:
 
         return r;
 }
+#endif /* NM_IGNORED */
 
 int fd_reopen(int fd, int flags) {
         int new_fd, r;
 
+        assert(fd >= 0 || fd == AT_FDCWD);
+
         /* Reopens the specified fd with new flags. This is useful for convert an O_PATH fd into a regular one, or to
          * turn O_RDWR fds into O_RDONLY fds.
          *
          * This doesn't work on sockets (since they cannot be open()ed, ever).
          *
-         * This implicitly resets the file read index to 0. */
-
-        if (FLAGS_SET(flags, O_DIRECTORY)) {
+         * This implicitly resets the file read index to 0.
+         *
+         * If AT_FDCWD is specified as file descriptor gets an fd to the current cwd.
+         *
+         * If the specified file descriptor refers to a symlink via O_PATH, then this function cannot be used
+         * to follow that symlink. Because we cannot have non-O_PATH fds to symlinks reopening it without
+         * O_PATH will always result in -ELOOP. Or in other words: if you have an O_PATH fd to a symlink you
+         * can reopen it only if you pass O_PATH again. */
+
+        if (FLAGS_SET(flags, O_NOFOLLOW))
+                /* O_NOFOLLOW is not allowed in fd_reopen(), because after all this is primarily implemented
+                 * via a symlink-based interface in /proc/self/fd. Let's refuse this here early. Note that
+                 * the kernel would generate ELOOP here too, hence this manual check is mostly redundant –
+                 * the only reason we add it here is so that the O_DIRECTORY special case (see below) behaves
+                 * the same way as the non-O_DIRECTORY case. */
+                return -ELOOP;
+
+        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);
+                new_fd = openat(fd, ".", flags | O_DIRECTORY);
                 if (new_fd < 0)
                         return -errno;
 
                 return new_fd;
         }
 
+        assert(fd >= 0);
+
         new_fd = open(FORMAT_PROC_FD_PATH(fd), flags);
         if (new_fd < 0) {
                 if (errno != ENOENT)
@@ -761,6 +811,50 @@ int fd_reopen(int fd, int flags) {
         return new_fd;
 }
 
+#if 0 /* NM_IGNORED */
+int fd_reopen_condition(
+                int fd,
+                int flags,
+                int mask,
+                int *ret_new_fd) {
+
+        int r, new_fd;
+
+        assert(fd >= 0);
+
+        /* 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
+         * needed, but only then. */
+
+        r = fcntl(fd, F_GETFL);
+        if (r < 0)
+                return -errno;
+
+        if ((r & mask) == (flags & mask)) {
+                *ret_new_fd = -EBADF;
+                return fd;
+        }
+
+        new_fd = fd_reopen(fd, flags);
+        if (new_fd < 0)
+                return new_fd;
+
+        *ret_new_fd = new_fd;
+        return new_fd;
+}
+
+int fd_is_opath(int fd) {
+        int r;
+
+        assert(fd >= 0);
+
+        r = fcntl(fd, F_GETFL);
+        if (r < 0)
+                return -errno;
+
+        return FLAGS_SET(r, O_PATH);
+}
+
 int read_nr_open(void) {
         _cleanup_free_ char *nr_open = NULL;
         int r;
@@ -805,4 +899,89 @@ int fd_get_diskseq(int fd, uint64_t *ret) {
 
         return 0;
 }
+
+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;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+
+        if (!isempty(path)) {
+                fd = openat(dir_fd, path, O_PATH|O_CLOEXEC);
+                if (fd < 0)
+                        return -errno;
+
+                dir_fd = fd;
+        }
+
+        r = statx_fallback(dir_fd, ".", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st.sx);
+        if (r == -ENOTDIR)
+                return false;
+        if (r < 0)
+                return r;
+
+        r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.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))
+                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. */
+
+        if (!FLAGS_SET(st.nsx.stx_mask, STATX_MNT_ID)) {
+                int mntid;
+
+                r = path_get_mnt_id_at(dir_fd, "", &mntid);
+                if (r == -ENOSYS)
+                        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;
+        }
+
+        if (!FLAGS_SET(pst.nsx.stx_mask, STATX_MNT_ID)) {
+                int mntid;
+
+                r = path_get_mnt_id_at(dir_fd, "..", &mntid);
+                if (r == -ENOSYS)
+                        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;
+        }
+
+        return statx_mount_same(&st.nsx, &pst.nsx);
+}
+
+const char *accmode_to_string(int flags) {
+        switch (flags & O_ACCMODE) {
+        case O_RDONLY:
+                return "ro";
+        case O_WRONLY:
+                return "wo";
+        case O_RDWR:
+                return "rw";
+        default:
+                return NULL;
+        }
+}
 #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 d9896e27..c870a1b8 100644
--- a/src/libnm-systemd-shared/src/basic/fd-util.h
+++ b/src/libnm-systemd-shared/src/basic/fd-util.h
@@ -2,6 +2,7 @@
 #pragma once
 
 #include <dirent.h>
+#include <fcntl.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <sys/socket.h>
@@ -15,14 +16,15 @@
 /* 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 }
 
 int close_nointr(int fd);
 int safe_close(int fd);
 void safe_close_pair(int p[static 2]);
 
 static inline int safe_close_above_stdio(int fd) {
-        if (fd < 3) /* Don't close stdin/stdout/stderr, but still invalidate the fd by returning -1 */
-                return -1;
+        if (fd < 3) /* Don't close stdin/stdout/stderr, but still invalidate the fd by returning -EBADF. */
+                return -EBADF;
 
         return safe_close(fd);
 }
@@ -56,6 +58,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL);
 
 int fd_nonblock(int fd, bool nonblock);
 int fd_cloexec(int fd, bool cloexec);
+int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec);
 
 int get_max_fd(void);
 
@@ -72,30 +75,16 @@ int fd_get_path(int fd, char **ret);
 
 int move_fd(int from, int to, int cloexec);
 
-enum {
-        ACQUIRE_NO_DEV_NULL = 1 << 0,
-        ACQUIRE_NO_MEMFD    = 1 << 1,
-        ACQUIRE_NO_PIPE     = 1 << 2,
-        ACQUIRE_NO_TMPFILE  = 1 << 3,
-        ACQUIRE_NO_REGULAR  = 1 << 4,
-};
-
 int fd_move_above_stdio(int fd);
 
 int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd);
 
 static inline int make_null_stdio(void) {
-        return rearrange_stdio(-1, -1, -1);
+        return rearrange_stdio(-EBADF, -EBADF, -EBADF);
 }
 
-/* Like TAKE_PTR() but for file descriptors, resetting them to -1 */
-#define TAKE_FD(fd)                             \
-        ({                                      \
-                int *_fd_ = &(fd);              \
-                int _ret_ = *_fd_;              \
-                *_fd_ = -1;                     \
-                _ret_;                          \
-        })
+/* Like TAKE_PTR() but for file descriptors, resetting them to -EBADF */
+#define TAKE_FD(fd) TAKE_GENERIC(fd, int, -EBADF)
 
 /* Like free_and_replace(), but for file descriptors */
 #define close_and_replace(a, b)                 \
@@ -107,9 +96,19 @@ 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 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 dir_fd_is_root(int dir_fd) {
+        return path_is_root_at(dir_fd, NULL);
+}
+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);
+}
+
 /* 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,3 +122,13 @@ 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))
+
+const char *accmode_to_string(int flags);
+
+/* Like ASSERT_PTR, but for fds */
+#define ASSERT_FD(fd)                           \
+        ({                                      \
+                int _fd_ = (fd);                \
+                assert(_fd_ >= 0);              \
+                _fd_;                           \
+        })
diff --git a/src/libnm-systemd-shared/src/basic/fileio.c b/src/libnm-systemd-shared/src/basic/fileio.c
index d996bc02..908a0309 100644
--- a/src/libnm-systemd-shared/src/basic/fileio.c
+++ b/src/libnm-systemd-shared/src/basic/fileio.c
@@ -15,7 +15,7 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
@@ -23,6 +23,7 @@
 #include "log.h"
 #include "macro.h"
 #include "mkdir.h"
+#include "nulstr-util.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "socket-util.h"
@@ -45,19 +46,6 @@
  * can detect EOFs. */
 #define READ_VIRTUAL_BYTES_MAX (4U*1024U*1024U - 2U)
 
-int fopen_unlocked(const char *path, const char *options, FILE **ret) {
-        assert(ret);
-
-        FILE *f = fopen(path, options);
-        if (!f)
-                return -errno;
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
-        *ret = f;
-        return 0;
-}
-
 int fdopen_unlocked(int fd, const char *options, FILE **ret) {
         assert(ret);
 
@@ -80,7 +68,7 @@ int take_fdopen_unlocked(int *fd, const char *options, FILE **ret) {
         if (r < 0)
                 return r;
 
-        *fd = -1;
+        *fd = -EBADF;
 
         return 0;
 }
@@ -92,7 +80,7 @@ FILE* take_fdopen(int *fd, const char *options) {
         if (!f)
                 return NULL;
 
-        *fd = -1;
+        *fd = -EBADF;
 
         return f;
 }
@@ -104,7 +92,7 @@ DIR* take_fdopendir(int *dfd) {
         if (!d)
                 return NULL;
 
-        *dfd = -1;
+        *dfd = -EBADF;
 
         return d;
 }
@@ -137,7 +125,7 @@ int write_string_stream_ts(
                 const struct timespec *ts) {
 
         bool needs_nl;
-        int r, fd = -1;
+        int r, fd = -EBADF;
 
         assert(f);
         assert(line);
@@ -212,7 +200,8 @@ int write_string_stream_ts(
         return 0;
 }
 
-static int write_string_file_atomic(
+static int write_string_file_atomic_at(
+                int dir_fd,
                 const char *fn,
                 const char *line,
                 WriteStringFileFlags flags,
@@ -228,7 +217,7 @@ static int write_string_file_atomic(
         /* Note that we'd really like to use O_TMPFILE here, but can't really, since we want replacement
          * semantics here, and O_TMPFILE can't offer that. i.e. rename() replaces but linkat() doesn't. */
 
-        r = fopen_temporary(fn, &f, &p);
+        r = fopen_temporary_at(dir_fd, fn, &f, &p);
         if (r < 0)
                 return r;
 
@@ -240,7 +229,7 @@ static int write_string_file_atomic(
         if (r < 0)
                 goto fail;
 
-        if (rename(p, fn) < 0) {
+        if (renameat(dir_fd, p, dir_fd, fn) < 0) {
                 r = -errno;
                 goto fail;
         }
@@ -255,18 +244,20 @@ static int write_string_file_atomic(
         return 0;
 
 fail:
-        (void) unlink(p);
+        (void) unlinkat(dir_fd, p, 0);
         return r;
 }
 
-int write_string_file_ts(
+int write_string_file_ts_at(
+                int dir_fd,
                 const char *fn,
                 const char *line,
                 WriteStringFileFlags flags,
                 const struct timespec *ts) {
 
         _cleanup_fclose_ FILE *f = NULL;
-        int q, r, fd;
+        _cleanup_close_ int fd = -EBADF;
+        int q, r;
 
         assert(fn);
         assert(line);
@@ -275,7 +266,7 @@ int write_string_file_ts(
         assert(!((flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE) && (flags & WRITE_STRING_FILE_SYNC)));
 
         if (flags & WRITE_STRING_FILE_MKDIR_0755) {
-                r = mkdir_parents(fn, 0755);
+                r = mkdirat_parents(dir_fd, fn, 0755);
                 if (r < 0)
                         return r;
         }
@@ -283,7 +274,7 @@ int write_string_file_ts(
         if (flags & WRITE_STRING_FILE_ATOMIC) {
                 assert(flags & WRITE_STRING_FILE_CREATE);
 
-                r = write_string_file_atomic(fn, line, flags, ts);
+                r = write_string_file_atomic_at(dir_fd, fn, line, flags, ts);
                 if (r < 0)
                         goto fail;
 
@@ -292,22 +283,20 @@ int write_string_file_ts(
                 assert(!ts);
 
         /* We manually build our own version of fopen(..., "we") that works without O_CREAT and with O_NOFOLLOW if needed. */
-        fd = open(fn, O_CLOEXEC|O_NOCTTY |
-                  (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) |
-                  (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));
+        fd = openat(dir_fd, fn, O_CLOEXEC|O_NOCTTY |
+                    (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) |
+                    (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));
         if (fd < 0) {
                 r = -errno;
                 goto fail;
         }
 
-        r = fdopen_unlocked(fd, "w", &f);
-        if (r < 0) {
-                safe_close(fd);
+        r = take_fdopen_unlocked(&fd, "w", &f);
+        if (r < 0)
                 goto fail;
-        }
 
         if (flags & WRITE_STRING_FILE_DISABLE_BUFFER)
                 setvbuf(f, NULL, _IONBF, 0);
@@ -353,21 +342,22 @@ int write_string_filef(
         return write_string_file(fn, p, flags);
 }
 
-int read_one_line_file(const char *fn, char **line) {
+int read_one_line_file_at(int dir_fd, const char *filename, char **ret) {
         _cleanup_fclose_ FILE *f = NULL;
         int r;
 
-        assert(fn);
-        assert(line);
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(filename);
+        assert(ret);
 
-        r = fopen_unlocked(fn, "re", &f);
+        r = fopen_unlocked_at(dir_fd, filename, "re", 0, &f);
         if (r < 0)
                 return r;
 
-        return read_line(f, LONG_LINE_MAX, line);
+        return read_line(f, LONG_LINE_MAX, ret);
 }
 
-int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
+int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl) {
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *buf = NULL;
         size_t l, k;
@@ -385,7 +375,7 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
         if (!buf)
                 return -ENOMEM;
 
-        r = fopen_unlocked(fn, "re", &f);
+        r = fopen_unlocked_at(dir_fd, fn, "re", 0, &f);
         if (r < 0)
                 return r;
 
@@ -504,7 +494,7 @@ int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *r
                  * at least one more byte to be able to distinguish EOF from truncation. */
                 if (max_size != SIZE_MAX && n > max_size) {
                         n = size; /* Make sure we never use more than what we sized the buffer for (so that
-                                   * we have one free byte in it for the trailing NUL we add below).*/
+                                   * we have one free byte in it for the trailing NUL we add below). */
                         truncated = true;
                         break;
                 }
@@ -558,7 +548,7 @@ int read_virtual_file_at(
                 char **ret_contents,
                 size_t *ret_size) {
 
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
 
@@ -760,62 +750,19 @@ int read_full_file_full(
                 size_t *ret_size) {
 
         _cleanup_fclose_ FILE *f = NULL;
+        XfopenFlags xflags = XFOPEN_UNLOCKED;
         int r;
 
         assert(filename);
         assert(ret_contents);
 
-        r = xfopenat(dir_fd, filename, "re", 0, &f);
-        if (r < 0) {
-                _cleanup_close_ int sk = -1;
-
-                /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
-                if (r != -ENXIO)
-                        return r;
-
-                /* If this is enabled, let's try to connect to it */
-                if (!FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET))
-                        return -ENXIO;
-
-                /* Seeking is not supported on AF_UNIX sockets */
-                if (offset != UINT64_MAX)
-                        return -ENXIO;
+        if (FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET) && /* If this is enabled, let's try to connect to it */
+            offset == UINT64_MAX)                              /* Seeking is not supported on AF_UNIX sockets */
+                xflags |= XFOPEN_SOCKET;
 
-                sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
-                if (sk < 0)
-                        return -errno;
-
-                if (bind_name) {
-                        /* If the caller specified a socket name to bind to, do so before connecting. This is
-                         * useful to communicate some minor, short meta-information token from the client to
-                         * the server. */
-                        union sockaddr_union bsa;
-
-                        r = sockaddr_un_set_path(&bsa.un, bind_name);
-                        if (r < 0)
-                                return r;
-
-                        if (bind(sk, &bsa.sa, r) < 0)
-                                return -errno;
-                }
-
-                r = connect_unix_path(sk, dir_fd, filename);
-                if (IN_SET(r, -ENOTSOCK, -EINVAL)) /* propagate original error if this is not a socket after all */
-                        return -ENXIO;
-                if (r < 0)
-                        return r;
-
-                if (shutdown(sk, SHUT_WR) < 0)
-                        return -errno;
-
-                f = fdopen(sk, "r");
-                if (!f)
-                        return -errno;
-
-                TAKE_FD(sk);
-        }
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+        r = xfopenat_full(dir_fd, filename, "re", 0, xflags, bind_name, &f);
+        if (r < 0)
+                return r;
 
         return read_full_stream_full(f, filename, offset, size, flags, ret_contents, ret_size);
 }
@@ -851,7 +798,6 @@ int executable_is_script(const char *path, char **interpreter) {
         *interpreter = ans;
         return 1;
 }
-#endif /* NM_IGNORED */
 
 /**
  * Retrieve one field from a file like /proc/self/status.  pattern
@@ -864,7 +810,6 @@ int executable_is_script(const char *path, char **interpreter) {
 int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field) {
         _cleanup_free_ char *status = NULL;
         char *t, *f;
-        size_t len;
         int r;
 
         assert(terminator);
@@ -916,9 +861,7 @@ int get_proc_field(const char *filename, const char *pattern, const char *termin
                         t--;
         }
 
-        len = strcspn(t, terminator);
-
-        f = strndup(t, len);
+        f = strdupcspn(t, terminator);
         if (!f)
                 return -ENOMEM;
 
@@ -927,8 +870,7 @@ int get_proc_field(const char *filename, const char *pattern, const char *termin
 }
 
 DIR *xopendirat(int fd, const char *name, int flags) {
-        int nfd;
-        DIR *d;
+        _cleanup_close_ int nfd = -EBADF;
 
         assert(!(flags & O_CREAT));
 
@@ -939,14 +881,9 @@ DIR *xopendirat(int fd, const char *name, int flags) {
         if (nfd < 0)
                 return NULL;
 
-        d = fdopendir(nfd);
-        if (!d) {
-                safe_close(nfd);
-                return NULL;
-        }
-
-        return d;
+        return take_fdopendir(&nfd);
 }
+#endif /* NM_IGNORED */
 
 int fopen_mode_to_flags(const char *mode) {
         const char *p;
@@ -994,38 +931,144 @@ int fopen_mode_to_flags(const char *mode) {
         return flags;
 }
 
-int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret) {
+static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
         FILE *f;
 
         /* A combination of fopen() with openat() */
 
-        if (dir_fd == AT_FDCWD && flags == 0) {
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(mode);
+        assert(ret);
+
+        if (dir_fd == AT_FDCWD && open_flags == 0)
                 f = fopen(path, mode);
-                if (!f)
-                        return -errno;
-        } else {
-                int fd, mode_flags;
+        else {
+                _cleanup_close_ int fd = -EBADF;
+                int mode_flags;
 
                 mode_flags = fopen_mode_to_flags(mode);
                 if (mode_flags < 0)
                         return mode_flags;
 
-                fd = openat(dir_fd, path, mode_flags | flags);
+                fd = openat(dir_fd, path, mode_flags | open_flags);
                 if (fd < 0)
                         return -errno;
 
-                f = fdopen(fd, mode);
-                if (!f) {
-                        safe_close(fd);
+                f = take_fdopen(&fd, mode);
+        }
+        if (!f)
+                return -errno;
+
+        *ret = f;
+        return 0;
+}
+
+static int xfopenat_unix_socket(int dir_fd, const char *path, const char *bind_name, FILE **ret) {
+        _cleanup_close_ int sk = -EBADF;
+        FILE *f;
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(ret);
+
+        sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+        if (sk < 0)
+                return -errno;
+
+        if (bind_name) {
+                /* If the caller specified a socket name to bind to, do so before connecting. This is
+                 * useful to communicate some minor, short meta-information token from the client to
+                 * the server. */
+                union sockaddr_union bsa;
+
+                r = sockaddr_un_set_path(&bsa.un, bind_name);
+                if (r < 0)
+                        return r;
+
+                if (bind(sk, &bsa.sa, r) < 0)
                         return -errno;
-                }
         }
 
+        r = connect_unix_path(sk, dir_fd, path);
+        if (r < 0)
+                return r;
+
+        if (shutdown(sk, SHUT_WR) < 0)
+                return -errno;
+
+        f = take_fdopen(&sk, "r");
+        if (!f)
+                return -errno;
+
+        *ret = f;
+        return 0;
+}
+
+int xfopenat_full(
+                int dir_fd,
+                const char *path,
+                const char *mode,
+                int open_flags,
+                XfopenFlags flags,
+                const char *bind_name,
+                FILE **ret) {
+
+        FILE *f = NULL;  /* avoid false maybe-uninitialized warning */
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(mode);
+        assert(ret);
+
+        r = xfopenat_regular(dir_fd, path, mode, open_flags, &f);
+        if (r == -ENXIO && FLAGS_SET(flags, XFOPEN_SOCKET)) {
+                /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
+                r = xfopenat_unix_socket(dir_fd, path, bind_name, &f);
+                if (IN_SET(r, -ENOTSOCK, -EINVAL))
+                        return -ENXIO; /* propagate original error if this is not a socket after all */
+        }
+        if (r < 0)
+                return r;
+
+        if (FLAGS_SET(flags, XFOPEN_UNLOCKED))
+                (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
         *ret = f;
         return 0;
 }
 
 #if 0 /* NM_IGNORED */
+int fdopen_independent(int fd, const char *mode, FILE **ret) {
+        _cleanup_close_ int copy_fd = -EBADF;
+        _cleanup_fclose_ FILE *f = NULL;
+        int mode_flags;
+
+        assert(fd >= 0);
+        assert(mode);
+        assert(ret);
+
+        /* A combination of fdopen() + fd_reopen(). i.e. reopens the inode the specified fd points to and
+         * returns a FILE* for it */
+
+        mode_flags = fopen_mode_to_flags(mode);
+        if (mode_flags < 0)
+                return mode_flags;
+
+        copy_fd = fd_reopen(fd, mode_flags);
+        if (copy_fd < 0)
+                return copy_fd;
+
+        f = take_fdopen(&copy_fd, mode);
+        if (!f)
+                return -errno;
+
+        *ret = TAKE_PTR(f);
+        return 0;
+}
+
 static int search_and_fopen_internal(
                 const char *path,
                 const char *mode,
@@ -1256,7 +1299,7 @@ typedef enum EndOfLineMarker {
 
 static EndOfLineMarker categorize_eol(char c, ReadLineFlags flags) {
 
-        if (!IN_SET(flags, READ_LINE_ONLY_NUL)) {
+        if (!FLAGS_SET(flags, READ_LINE_ONLY_NUL)) {
                 if (c == '\n')
                         return EOL_TEN;
                 if (c == '\r')
diff --git a/src/libnm-systemd-shared/src/basic/fileio.h b/src/libnm-systemd-shared/src/basic/fileio.h
index 9151d823..769bf394 100644
--- a/src/libnm-systemd-shared/src/basic/fileio.h
+++ b/src/libnm-systemd-shared/src/basic/fileio.h
@@ -43,7 +43,6 @@ typedef enum {
         READ_FULL_FILE_FAIL_WHEN_LARGER    = 1 << 5, /* fail loading if file is larger than specified size */
 } ReadFullFileFlags;
 
-int fopen_unlocked(const char *path, const char *options, FILE **ret);
 int fdopen_unlocked(int fd, const char *options, FILE **ret);
 int take_fdopen_unlocked(int *fd, const char *options, FILE **ret);
 FILE* take_fdopen(int *fd, const char *options);
@@ -55,15 +54,27 @@ int write_string_stream_ts(FILE *f, const char *line, WriteStringFileFlags flags
 static inline int write_string_stream(FILE *f, const char *line, WriteStringFileFlags flags) {
         return write_string_stream_ts(f, line, flags, NULL);
 }
-int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, const struct timespec *ts);
+int write_string_file_ts_at(int dir_fd, const char *fn, const char *line, WriteStringFileFlags flags, const struct timespec *ts);
+static inline int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, const struct timespec *ts) {
+        return write_string_file_ts_at(AT_FDCWD, fn, line, flags, ts);
+}
+static inline int write_string_file_at(int dir_fd, const char *fn, const char *line, WriteStringFileFlags flags) {
+        return write_string_file_ts_at(dir_fd, fn, line, flags, NULL);
+}
 static inline int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) {
         return write_string_file_ts(fn, line, flags, NULL);
 }
 
 int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4);
 
-int read_one_line_file(const char *filename, char **line);
+int read_one_line_file_at(int dir_fd, const char *filename, char **ret);
+static inline int read_one_line_file(const char *filename, char **ret) {
+        return read_one_line_file_at(AT_FDCWD, filename, ret);
+}
 int read_full_file_full(int dir_fd, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, const char *bind_name, char **ret_contents, size_t *ret_size);
+static inline int read_full_file_at(int dir_fd, const char *filename, char **ret_contents, size_t *ret_size) {
+        return read_full_file_full(dir_fd, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size);
+}
 static inline int read_full_file(const char *filename, char **ret_contents, size_t *ret_size) {
         return read_full_file_full(AT_FDCWD, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size);
 }
@@ -82,14 +93,41 @@ static inline int read_full_stream(FILE *f, char **ret_contents, size_t *ret_siz
         return read_full_stream_full(f, NULL, UINT64_MAX, SIZE_MAX, 0, ret_contents, ret_size);
 }
 
-int verify_file(const char *fn, const char *blob, bool accept_extra_nl);
+int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl);
+static inline int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
+        return verify_file_at(AT_FDCWD, fn, blob, accept_extra_nl);
+}
 
 int executable_is_script(const char *path, char **interpreter);
 
 int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field);
 
 DIR *xopendirat(int dirfd, const char *name, int flags);
-int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret);
+
+typedef enum XfopenFlags {
+        XFOPEN_UNLOCKED = 1 << 0, /* call __fsetlocking(FSETLOCKING_BYCALLER) after opened */
+        XFOPEN_SOCKET   = 1 << 1, /* also try to open unix socket */
+} XfopenFlags;
+
+int xfopenat_full(
+                int dir_fd,
+                const char *path,
+                const char *mode,
+                int open_flags,
+                XfopenFlags flags,
+                const char *bind_name,
+                FILE **ret);
+static inline int xfopenat(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
+        return xfopenat_full(dir_fd, path, mode, open_flags, 0, NULL, ret);
+}
+static inline int fopen_unlocked_at(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
+        return xfopenat_full(dir_fd, path, mode, open_flags, XFOPEN_UNLOCKED, NULL, ret);
+}
+static inline int fopen_unlocked(const char *path, const char *mode, FILE **ret) {
+        return fopen_unlocked_at(AT_FDCWD, path, mode, 0, 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);
diff --git a/src/libnm-systemd-shared/src/basic/fs-util.c b/src/libnm-systemd-shared/src/basic/fs-util.c
index 23f8d403..32fd849d 100644
--- a/src/libnm-systemd-shared/src/basic/fs-util.c
+++ b/src/libnm-systemd-shared/src/basic/fs-util.c
@@ -5,6 +5,7 @@
 #include <errno.h>
 #include <stddef.h>
 #include <stdlib.h>
+#include <sys/file.h>
 #include <linux/falloc.h>
 #include <linux/magic.h>
 #include <unistd.h>
@@ -15,6 +16,8 @@
 #include "fileio.h"
 #include "fs-util.h"
 #include "hostname-util.h"
+#include "label.h"
+#include "lock-util.h"
 #include "log.h"
 #include "macro.h"
 #include "missing_fcntl.h"
@@ -34,12 +37,6 @@
 #include "tmpfile-util.h"
 #include "umask-util.h"
 #include "user-util.h"
-#include "util.h"
-
-int unlink_noerrno(const char *path) {
-        PROTECT_ERRNO;
-        return RET_NERRNO(unlink(path));
-}
 
 #if 0 /* NM_IGNORED */
 int rmdir_parents(const char *path, const char *stop) {
@@ -180,37 +177,41 @@ int readlink_value(const char *p, char **ret) {
         return 0;
 }
 
-int readlink_and_make_absolute(const char *p, char **r) {
+int readlink_and_make_absolute(const char *p, char **ret) {
         _cleanup_free_ char *target = NULL;
-        char *k;
-        int j;
+        int r;
 
         assert(p);
-        assert(r);
-
-        j = readlink_malloc(p, &target);
-        if (j < 0)
-                return j;
+        assert(ret);
 
-        k = file_in_same_dir(p, target);
-        if (!k)
-                return -ENOMEM;
+        r = readlink_malloc(p, &target);
+        if (r < 0)
+                return r;
 
-        *r = k;
-        return 0;
+        return file_in_same_dir(p, target, ret);
 }
 
-int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
-        _cleanup_close_ int fd = -1;
+int chmod_and_chown_at(int dir_fd, const char *path, mode_t mode, uid_t uid, gid_t gid) {
+        _cleanup_close_ int fd = -EBADF;
 
-        assert(path);
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
 
-        fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW); /* Let's acquire an O_PATH fd, as precaution to change
-                                                       * mode/owner on the same file */
-        if (fd < 0)
-                return -errno;
+        if (path) {
+                /* Let's acquire an O_PATH fd, as precaution to change mode/owner on the same file */
+                fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
+                if (fd < 0)
+                        return -errno;
+                dir_fd = fd;
 
-        return fchmod_and_chown(fd, mode, uid, gid);
+        } else if (dir_fd == AT_FDCWD) {
+                /* Let's acquire an O_PATH fd of the current directory */
+                fd = openat(dir_fd, ".", O_PATH|O_CLOEXEC|O_NOFOLLOW|O_DIRECTORY);
+                if (fd < 0)
+                        return -errno;
+                dir_fd = fd;
+        }
+
+        return fchmod_and_chown(dir_fd, mode, uid, gid);
 }
 
 int fchmod_and_chown_with_fallback(int fd, const char *path, mode_t mode, uid_t uid, gid_t gid) {
@@ -308,7 +309,7 @@ int fchmod_opath(int fd, mode_t m) {
 }
 
 int futimens_opath(int fd, const struct timespec ts[2]) {
-        /* Similar to fchmod_path() but for futimens() */
+        /* Similar to fchmod_opath() but for futimens() */
 
         if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, 0) < 0) {
                 if (errno != ENOENT)
@@ -356,7 +357,7 @@ int fd_warn_permissions(const char *path, int fd) {
 }
 
 int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         int r, ret;
 
         assert(path);
@@ -680,11 +681,11 @@ void unlink_tempfilep(char (*p)[]) {
          * successfully created. We ignore both the rare case where the
          * original suffix is used and unlink failures. */
         if (!endswith(*p, ".XXXXXX"))
-                (void) unlink_noerrno(*p);
+                (void) unlink(*p);
 }
 
 int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
-        _cleanup_close_ int truncate_fd = -1;
+        _cleanup_close_ int truncate_fd = -EBADF;
         struct stat st;
         off_t l, bs;
 
@@ -778,7 +779,7 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
          * punch-hole/truncate this to release the disk space. */
 
         bs = MAX(st.st_blksize, 512);
-        l = DIV_ROUND_UP(st.st_size, bs) * bs; /* Round up to next block size */
+        l = ROUND_UP(st.st_size, bs); /* Round up to next block size */
 
         if (fallocate(truncate_fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, 0, l) >= 0)
                 return 0; /* Successfully punched a hole! 😊 */
@@ -792,12 +793,23 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
         return 0;
 }
 
-int open_parent(const char *path, int flags, mode_t mode) {
+int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode) {
         _cleanup_free_ char *parent = NULL;
         int r;
 
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+
         r = path_extract_directory(path, &parent);
-        if (r < 0)
+        if (r == -EDESTADDRREQ) {
+                parent = strdup(".");
+                if (!parent)
+                        return -ENOMEM;
+        } else if (r == -EADDRNOTAVAIL) {
+                parent = strdup(path);
+                if (!parent)
+                        return -ENOMEM;
+        } else if (r < 0)
                 return r;
 
         /* Let's insist on O_DIRECTORY since the parent of a file or directory is a directory. Except if we open an
@@ -808,7 +820,7 @@ int open_parent(const char *path, int flags, mode_t mode) {
         else if (!FLAGS_SET(flags, O_TMPFILE))
                 flags |= O_DIRECTORY|O_RDONLY;
 
-        return RET_NERRNO(open(parent, flags, mode));
+        return RET_NERRNO(openat(dir_fd, parent, flags, mode));
 }
 #endif /* NM_IGNORED */
 
@@ -816,14 +828,14 @@ int conservative_renameat(
                 int olddirfd, const char *oldpath,
                 int newdirfd, const char *newpath) {
 
-        _cleanup_close_ int old_fd = -1, new_fd = -1;
+        _cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF;
         struct stat old_stat, new_stat;
 
-        /* Renames the old path to thew new path, much like renameat() — except if both are regular files and
+        /* Renames the old path to the new path, much like renameat() — except if both are regular files and
          * have the exact same contents and basic file attributes already. In that case remove the new file
          * instead. This call is useful for reducing inotify wakeups on files that are updated but don't
          * actually change. This function is written in a style that we rather rename too often than suppress
-         * too much. i.e. whenever we are in doubt we rather rename than fail. After all reducing inotify
+         * too much. I.e. whenever we are in doubt, we rather rename than fail. After all reducing inotify
          * events is an optimization only, not more. */
 
         old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
@@ -912,7 +924,7 @@ int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size) {
 
         /* On EINTR try a couple of times more, but protect against busy looping
          * (not more than 16 times per 10s) */
-        rl = (RateLimit) { 10 * USEC_PER_SEC, 16 };
+        rl = (const RateLimit) { 10 * USEC_PER_SEC, 16 };
         while (ratelimit_below(&rl)) {
                 r = posix_fallocate(fd, offset, size);
                 if (r != EINTR)
@@ -998,9 +1010,8 @@ int parse_cifs_service(
 }
 
 int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
-        _cleanup_close_ int fd = -1, parent_fd = -1;
-        _cleanup_free_ char *fname = NULL;
-        bool made;
+        _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
+        _cleanup_free_ char *fname = NULL, *parent = NULL;
         int r;
 
         /* Creates a directory with mkdirat() and then opens it, in the "most atomic" fashion we can
@@ -1015,19 +1026,13 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
         /* Note that O_DIRECTORY|O_NOFOLLOW is implied, but we allow specifying it anyway. The following
          * flags actually make sense to specify: O_CLOEXEC, O_EXCL, O_NOATIME, O_PATH */
 
-        if (isempty(path))
-                return -EINVAL;
-
-        if (!filename_is_valid(path)) {
-                _cleanup_free_ char *parent = NULL;
-
-                /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
-                 * that we can pin it, and operate below it. */
-
-                r = path_extract_directory(path, &parent);
-                if (r < 0)
+        /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
+         * that we can pin it, and operate below it. */
+        r = path_extract_directory(path, &parent);
+        if (r < 0) {
+                if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL))
                         return r;
-
+        } else {
                 r = path_extract_filename(path, &fname);
                 if (r < 0)
                         return r;
@@ -1040,33 +1045,11 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
                 path = fname;
         }
 
-        r = RET_NERRNO(mkdirat(dirfd, path, mode));
-        if (r == -EEXIST) {
-                if (FLAGS_SET(flags, O_EXCL))
-                        return -EEXIST;
-
-                made = false;
-        } else if (r < 0)
-                return r;
-        else
-                made = true;
-
-        fd = RET_NERRNO(openat(dirfd, path, (flags & ~O_EXCL)|O_DIRECTORY|O_NOFOLLOW));
-        if (fd < 0) {
-                if (fd == -ENOENT)  /* We got ENOENT? then someone else immediately removed it after we
-                                     * created it. In that case let's return immediately without unlinking
-                                     * anything, because there simply isn't anything to unlink anymore. */
-                        return -ENOENT;
-                if (fd == -ELOOP)   /* is a symlink? exists already → created by someone else, don't unlink */
-                        return -EEXIST;
-                if (fd == -ENOTDIR) /* not a directory? exists already → created by someone else, don't unlink */
-                        return -EEXIST;
-
-                if (made)
-                        (void) unlinkat(dirfd, path, AT_REMOVEDIR);
-
+        fd = xopenat(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, /* xopen_flags = */ 0, mode);
+        if (IN_SET(fd, -ELOOP, -ENOTDIR))
+                return -EEXIST;
+        if (fd < 0)
                 return fd;
-        }
 
         return TAKE_FD(fd);
 }
@@ -1117,3 +1100,120 @@ int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, b
                         return -EEXIST;
         }
 }
+
+int xopenat(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);
+
+        if (isempty(path)) {
+                assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
+                return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW);
+        }
+
+        if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
+                r = label_ops_pre(dir_fd, path, FLAGS_SET(open_flags, O_DIRECTORY) ? S_IFDIR : S_IFREG);
+                if (r < 0)
+                        return r;
+        }
+
+        if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
+                r = RET_NERRNO(mkdirat(dir_fd, path, mode));
+                if (r == -EEXIST) {
+                        if (FLAGS_SET(open_flags, O_EXCL))
+                                return -EEXIST;
+
+                        made = false;
+                } else if (r < 0)
+                        return r;
+                else
+                        made = true;
+
+                if (FLAGS_SET(xopen_flags, XO_LABEL)) {
+                        r = label_ops_post(dir_fd, path);
+                        if (r < 0)
+                                return r;
+                }
+
+                open_flags &= ~(O_EXCL|O_CREAT);
+                xopen_flags &= ~XO_LABEL;
+        }
+
+        fd = RET_NERRNO(openat(dir_fd, path, open_flags, mode));
+        if (fd < 0) {
+                if (IN_SET(fd,
+                           /* We got ENOENT? then someone else immediately removed it after we
+                           * created it. In that case let's return immediately without unlinking
+                           * anything, because there simply isn't anything to unlink anymore. */
+                           -ENOENT,
+                           /* is a symlink? exists already → created by someone else, don't unlink */
+                           -ELOOP,
+                           /* not a directory? exists already → created by someone else, don't unlink */
+                           -ENOTDIR))
+                        return fd;
+
+                if (made)
+                        (void) unlinkat(dir_fd, path, AT_REMOVEDIR);
+
+                return fd;
+        }
+
+        if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
+                r = label_ops_post(dir_fd, path);
+                if (r < 0)
+                        return r;
+        }
+
+        return TAKE_FD(fd);
+}
+
+#if 0 /* NM_IGNORED */
+int xopenat_lock(
+                int dir_fd,
+                const char *path,
+                int open_flags,
+                XOpenFlags xopen_flags,
+                mode_t mode,
+                LockType locktype,
+                int operation) {
+
+        _cleanup_close_ int fd = -EBADF;
+        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)
+                return -EBADF;
+
+        for (;;) {
+                struct stat st;
+
+                fd = xopenat(dir_fd, path, open_flags, xopen_flags, mode);
+                if (fd < 0)
+                        return fd;
+
+                r = lock_generic(fd, locktype, operation);
+                if (r < 0)
+                        return r;
+
+                /* If we acquired the lock, let's check if the file/directory still exists in the file
+                 * system. If not, then the previous exclusive owner removed it and then closed it. In such a
+                 * case our acquired lock is worthless, hence try again. */
+
+                if (fstat(fd, &st) < 0)
+                        return -errno;
+                if (st.st_nlink > 0)
+                        break;
+
+                fd = safe_close(fd);
+        }
+
+        return TAKE_FD(fd);
+}
+#endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/fs-util.h b/src/libnm-systemd-shared/src/basic/fs-util.h
index c4dffc48..a19836d1 100644
--- a/src/libnm-systemd-shared/src/basic/fs-util.h
+++ b/src/libnm-systemd-shared/src/basic/fs-util.h
@@ -12,6 +12,7 @@
 
 #include "alloc-util.h"
 #include "errno-util.h"
+#include "lock-util.h"
 #include "time-util.h"
 #include "user-util.h"
 
@@ -22,8 +23,6 @@
 #define PTR_TO_MODE(p) ((mode_t) ((uintptr_t) (p)-1))
 #define MODE_TO_PTR(u) ((void *) ((uintptr_t) (u)+1))
 
-int unlink_noerrno(const char *path);
-
 int rmdir_parents(const char *path, const char *stop);
 
 int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
@@ -33,7 +32,10 @@ int readlink_malloc(const char *p, char **r);
 int readlink_value(const char *p, char **ret);
 int readlink_and_make_absolute(const char *p, char **r);
 
-int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid);
+int chmod_and_chown_at(int dir_fd, const char *path, mode_t mode, uid_t uid, gid_t gid);
+static inline int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
+        return chmod_and_chown_at(AT_FDCWD, path, mode, uid, gid);
+}
 int fchmod_and_chown_with_fallback(int fd, const char *path, mode_t mode, uid_t uid, gid_t gid);
 static inline int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid) {
         return fchmod_and_chown_with_fallback(fd, NULL, mode, uid, gid); /* no fallback */
@@ -96,7 +98,7 @@ static inline char* unlink_and_free(char *p) {
         if (!p)
                 return NULL;
 
-        (void) unlink_noerrno(p);
+        (void) unlink(p);
         return mfree(p);
 }
 DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free);
@@ -112,7 +114,10 @@ typedef enum UnlinkDeallocateFlags {
 
 int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags);
 
-int open_parent(const char *path, int flags, mode_t mode);
+int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode);
+static inline int open_parent(const char *path, int flags, mode_t mode) {
+        return open_parent_at(AT_FDCWD, path, flags, mode);
+}
 
 int conservative_renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
 static inline int conservative_rename(const char *oldpath, const char *newpath) {
@@ -126,3 +131,11 @@ int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char
 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,
+} XOpenFlags;
+
+int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode);
+
+int xopenat_lock(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode, LockType locktype, int operation);
diff --git a/src/libnm-systemd-shared/src/basic/glyph-util.c b/src/libnm-systemd-shared/src/basic/glyph-util.c
index 5289cffe..1ea8a645 100644
--- a/src/libnm-systemd-shared/src/basic/glyph-util.c
+++ b/src/libnm-systemd-shared/src/basic/glyph-util.c
@@ -11,15 +11,15 @@ bool emoji_enabled(void) {
         static int cached_emoji_enabled = -1;
 
         if (cached_emoji_enabled < 0) {
-                int val;
-
-                val = getenv_bool("SYSTEMD_EMOJI");
-                if (val < 0)
-                        cached_emoji_enabled =
-                                is_locale_utf8() &&
-                                !STRPTR_IN_SET(getenv("TERM"), "dumb", "linux");
-                else
-                        cached_emoji_enabled = val;
+                int val = getenv_bool("SYSTEMD_EMOJI");
+                if (val >= 0)
+                        return (cached_emoji_enabled = val);
+
+                const char *term = getenv("TERM");
+                if (!term || STR_IN_SET(term, "dumb", "linux"))
+                        return (cached_emoji_enabled = false);
+
+                cached_emoji_enabled = is_locale_utf8();
         }
 
         return cached_emoji_enabled;
@@ -73,6 +73,7 @@ const char *special_glyph(SpecialGlyph code) {
                         [SPECIAL_GLYPH_RECYCLING]               = "~",
                         [SPECIAL_GLYPH_DOWNLOAD]                = "\\",
                         [SPECIAL_GLYPH_SPARKLES]                = "*",
+                        [SPECIAL_GLYPH_WARNING_SIGN]            = "!",
                 },
 
                 /* UTF-8 */
@@ -126,10 +127,11 @@ const char *special_glyph(SpecialGlyph code) {
                         /* This emoji is a single character cell glyph in Unicode, and two in ASCII */
                         [SPECIAL_GLYPH_TOUCH]                   = u8"👆",       /* actually called: BACKHAND INDEX POINTING UP */
 
-                        /* These three emojis are single character cell glyphs in Unicode and also in ASCII. */
+                        /* These four emojis are single character cell glyphs in Unicode and also in ASCII. */
                         [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_WARNING_SIGN]            = u8"⚠️",
                 },
         };
 
diff --git a/src/libnm-systemd-shared/src/basic/glyph-util.h b/src/libnm-systemd-shared/src/basic/glyph-util.h
index 621d7a85..b6463962 100644
--- a/src/libnm-systemd-shared/src/basic/glyph-util.h
+++ b/src/libnm-systemd-shared/src/basic/glyph-util.h
@@ -44,6 +44,7 @@ typedef enum SpecialGlyph {
         SPECIAL_GLYPH_RECYCLING,
         SPECIAL_GLYPH_DOWNLOAD,
         SPECIAL_GLYPH_SPARKLES,
+        SPECIAL_GLYPH_WARNING_SIGN,
         _SPECIAL_GLYPH_MAX,
         _SPECIAL_GLYPH_INVALID = -EINVAL,
 } SpecialGlyph;
diff --git a/src/libnm-systemd-shared/src/basic/hash-funcs.h b/src/libnm-systemd-shared/src/basic/hash-funcs.h
index c14302ec..be642892 100644
--- a/src/libnm-systemd-shared/src/basic/hash-funcs.h
+++ b/src/libnm-systemd-shared/src/basic/hash-funcs.h
@@ -102,7 +102,7 @@ extern const struct hash_ops uint64_hash_ops;
 /* On some archs dev_t is 32bit, and on others 64bit. And sometimes it's 64bit on 32bit archs, and sometimes 32bit on
  * 64bit archs. Yuck! */
 #if SIZEOF_DEV_T != 8
-void devt_hash_func(const dev_t *p, struct siphash *state) _pure_;
+void devt_hash_func(const dev_t *p, struct siphash *state);
 #else
 #define devt_hash_func uint64_hash_func
 #endif
diff --git a/src/libnm-systemd-shared/src/basic/hashmap.c b/src/libnm-systemd-shared/src/basic/hashmap.c
index d0b950e0..356200cf 100644
--- a/src/libnm-systemd-shared/src/basic/hashmap.c
+++ b/src/libnm-systemd-shared/src/basic/hashmap.c
@@ -7,10 +7,14 @@
 #include <pthread.h>
 #include <stdint.h>
 #include <stdlib.h>
+#if HAVE_VALGRIND_VALGRIND_H
+#  include <valgrind/valgrind.h>
+#endif
 
 #include "alloc-util.h"
 #include "fileio.h"
 #include "hashmap.h"
+#include "logarithm.h"
 #include "macro.h"
 #include "memory-util.h"
 #include "mempool.h"
@@ -275,29 +279,34 @@ static _used_ const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX
         },
 };
 
-#if VALGRIND
-_destructor_ static void cleanup_pools(void) {
-        _cleanup_free_ char *t = NULL;
+#if 0 /* NM_IGNORED */
+void hashmap_trim_pools(void) {
         int r;
 
-        /* Be nice to valgrind */
+        /* The pool is only allocated by the main thread, but the memory can be passed to other
+         * threads. Let's clean up if we are the main thread and no other threads are live. */
 
-        /* The pool is only allocated by the main thread, but the memory can
-         * be passed to other threads. Let's clean up if we are the main thread
-         * and no other threads are live. */
-        /* We build our own is_main_thread() here, which doesn't use C11
-         * TLS based caching of the result. That's because valgrind apparently
-         * doesn't like malloc() (which C11 TLS internally uses) to be called
-         * from a GCC destructors. */
+        /* We build our own is_main_thread() here, which doesn't use C11 TLS based caching of the
+         * result. That's because valgrind apparently doesn't like TLS to be used from a GCC destructor. */
         if (getpid() != gettid())
-                return;
+                return (void) log_debug("Not cleaning up memory pools, not in main thread.");
 
-        r = get_proc_field("/proc/self/status", "Threads", WHITESPACE, &t);
-        if (r < 0 || !streq(t, "1"))
-                return;
+        r = get_process_threads(0);
+        if (r < 0)
+                return (void) log_debug_errno(r, "Failed to determine number of threads, not cleaning up memory pools: %m");
+        if (r != 1)
+                return (void) log_debug("Not cleaning up memory pools, running in multi-threaded process.");
+
+        mempool_trim(&hashmap_pool);
+        mempool_trim(&ordered_hashmap_pool);
+}
+#endif /* NM_IGNORED */
 
-        mempool_drop(&hashmap_pool);
-        mempool_drop(&ordered_hashmap_pool);
+#if HAVE_VALGRIND_VALGRIND_H
+_destructor_ static void cleanup_pools(void) {
+        /* Be nice to valgrind */
+        if (RUNNING_ON_VALGRIND)
+                hashmap_trim_pools();
 }
 #endif
 
@@ -374,8 +383,9 @@ static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) {
 }
 
 static struct hashmap_base_entry* bucket_at(HashmapBase *h, unsigned idx) {
-        return (struct hashmap_base_entry*) (void *)
-                ((uint8_t*) storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size);
+        return CAST_ALIGN_PTR(
+                        struct hashmap_base_entry,
+                        (uint8_t *) storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size);
 }
 
 static struct plain_hashmap_entry* plain_bucket_at(Hashmap *h, unsigned idx) {
@@ -774,7 +784,7 @@ static struct HashmapBase* hashmap_base_new(const struct hash_ops *hash_ops, enu
         HashmapBase *h;
         const struct hashmap_type_info *hi = &hashmap_type_info[type];
 
-        bool use_pool = mempool_enabled && mempool_enabled();
+        bool use_pool = mempool_enabled && mempool_enabled();  /* mempool_enabled is a weak symbol */
 
         h = use_pool ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size);
         if (!h)
@@ -1753,7 +1763,7 @@ HashmapBase* _hashmap_copy(HashmapBase *h  HASHMAP_DEBUG_PARAMS) {
         }
 
         if (r < 0)
-                return _hashmap_free(copy, false, false);
+                return _hashmap_free(copy, NULL, NULL);
 
         return copy;
 }
diff --git a/src/libnm-systemd-shared/src/basic/hashmap.h b/src/libnm-systemd-shared/src/basic/hashmap.h
index 91b3fe86..68d9b81c 100644
--- a/src/libnm-systemd-shared/src/basic/hashmap.h
+++ b/src/libnm-systemd-shared/src/basic/hashmap.h
@@ -7,7 +7,6 @@
 
 #include "hash-funcs.h"
 #include "macro.h"
-#include "util.h"
 
 /*
  * A hash table implementation. As a minor optimization a NULL hashmap object
@@ -444,3 +443,5 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free);
 DEFINE_TRIVIAL_CLEANUP_FUNC(IteratedCache*, iterated_cache_free);
 
 #define _cleanup_iterated_cache_free_ _cleanup_(iterated_cache_freep)
+
+void hashmap_trim_pools(void);
diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.c b/src/libnm-systemd-shared/src/basic/hexdecoct.c
index 570da688..d41d2ea0 100644
--- a/src/libnm-systemd-shared/src/basic/hexdecoct.c
+++ b/src/libnm-systemd-shared/src/basic/hexdecoct.c
@@ -61,11 +61,13 @@ char *hexmem(const void *p, size_t l) {
         const uint8_t *x;
         char *r, *z;
 
+        assert(p || l == 0);
+
         z = r = new(char, l * 2 + 1);
         if (!r)
                 return NULL;
 
-        for (x = p; x < (const uint8_t*) p + l; x++) {
+        for (x = p; x && x < (const uint8_t*) p + l; x++) {
                 *(z++) = hexchar(*x >> 4);
                 *(z++) = hexchar(*x & 15);
         }
@@ -110,12 +112,17 @@ static int unhex_next(const char **p, size_t *l) {
         return r;
 }
 
-int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_len) {
+int unhexmem_full(
+                const char *p,
+                size_t l,
+                bool secure,
+                void **ret,
+                size_t *ret_len) {
+
         _cleanup_free_ uint8_t *buf = NULL;
         size_t buf_size;
         const char *x;
         uint8_t *z;
-        int r;
 
         assert(p || l == 0);
 
@@ -128,22 +135,20 @@ int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_
         if (!buf)
                 return -ENOMEM;
 
+        CLEANUP_ERASE_PTR(secure ? &buf : NULL, buf_size);
+
         for (x = p, z = buf;;) {
                 int a, b;
 
                 a = unhex_next(&x, &l);
                 if (a == -EPIPE) /* End of string */
                         break;
-                if (a < 0) {
-                        r = a;
-                        goto on_failure;
-                }
+                if (a < 0)
+                        return a;
 
                 b = unhex_next(&x, &l);
-                if (b < 0) {
-                        r = b;
-                        goto on_failure;
-                }
+                if (b < 0)
+                        return b;
 
                 *(z++) = (uint8_t) a << 4 | (uint8_t) b;
         }
@@ -156,12 +161,6 @@ int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_
                 *ret = TAKE_PTR(buf);
 
         return 0;
-
-on_failure:
-        if (secure)
-                explicit_bzero_safe(buf, buf_size);
-
-        return r;
 }
 
 #if 0 /* NM_IGNORED */
@@ -591,121 +590,136 @@ ssize_t base64mem_full(
                 const void *p,
                 size_t l,
                 size_t line_break,
-                char **out) {
+                char **ret) {
 
         const uint8_t *x;
-        char *r, *z;
+        char *b, *z;
         size_t m;
 
         assert(p || l == 0);
-        assert(out);
         assert(line_break > 0);
+        assert(ret);
 
         /* three input bytes makes four output bytes, padding is added so we must round up */
         m = 4 * (l + 2) / 3 + 1;
-
         if (line_break != SIZE_MAX)
                 m += m / line_break;
 
-        z = r = malloc(m);
-        if (!r)
+        z = b = malloc(m);
+        if (!b)
                 return -ENOMEM;
 
-        for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) {
+        for (x = p; x && x < (const uint8_t*) p + (l / 3) * 3; x += 3) {
                 /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char(x[0] >> 2);                    /* 00XXXXXX */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4);  /* 00XXYYYY */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char(x[2] & 63);                    /* 00ZZZZZZ */
         }
 
         switch (l % 3) {
         case 2:
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char(x[0] >> 2);                   /* 00XXXXXX */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char((x[1] & 15) << 2);            /* 00YYYY00 */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = '=';
-
                 break;
+
         case 1:
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char(x[0] >> 2);        /* 00XXXXXX */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char((x[0] & 3) << 4);  /* 00XX0000 */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = '=';
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = '=';
-
                 break;
         }
 
         *z = 0;
-        *out = r;
-        assert(z >= r); /* Let static analyzers know that the answer is non-negative. */
-        return z - r;
+        *ret = b;
+
+        assert(z >= b); /* Let static analyzers know that the answer is non-negative. */
+        return z - b;
 }
 
-static int base64_append_width(
-                char **prefix, int plen,
-                char sep, int indent,
-                const void *p, size_t l,
-                int width) {
+static ssize_t base64_append_width(
+                char **prefix,
+                size_t plen,
+                char sep,
+                size_t indent,
+                const void *p,
+                size_t l,
+                size_t width) {
 
         _cleanup_free_ char *x = NULL;
         char *t, *s;
-        ssize_t len, avail, line, lines;
+        size_t lines;
+        ssize_t len;
+
+        assert(prefix);
+        assert(*prefix || plen == 0);
+        assert(p || l == 0);
 
         len = base64mem(p, l, &x);
-        if (len <= 0)
+        if (len < 0)
                 return len;
+        if (len == 0)
+                return plen;
 
         lines = DIV_ROUND_UP(len, width);
 
-        if ((size_t) plen >= SSIZE_MAX - 1 - 1 ||
+        if (plen >= SSIZE_MAX - 1 - 1 ||
             lines > (SSIZE_MAX - plen - 1 - 1) / (indent + width + 1))
                 return -ENOMEM;
 
-        t = realloc(*prefix, (ssize_t) plen + 1 + 1 + (indent + width + 1) * lines);
+        t = realloc(*prefix, plen + 1 + 1 + (indent + width + 1) * lines);
         if (!t)
                 return -ENOMEM;
 
-        t[plen] = sep;
+        s = t + plen;
+        for (size_t line = 0; line < lines; line++) {
+                size_t act = MIN(width, (size_t) len);
 
-        for (line = 0, s = t + plen + 1, avail = len; line < lines; line++) {
-                int act = MIN(width, avail);
+                if (line > 0)
+                        sep = '\n';
 
-                if (line > 0 || sep == '\n') {
-                        memset(s, ' ', indent);
-                        s += indent;
+                if (s > t) {
+                        *s++ = sep;
+                        if (sep == '\n')
+                                s = mempset(s, ' ', indent);
                 }
 
                 s = mempcpy(s, x + width * line, act);
-                *(s++) = line < lines - 1 ? '\n' : '\0';
-                avail -= act;
+                len -= act;
         }
-        assert(avail == 0);
+        assert(len == 0);
 
+        *s = '\0';
         *prefix = t;
-        return 0;
+        return s - t;
 }
 
-int base64_append(
-                char **prefix, int plen,
-                const void *p, size_t l,
-                int indent, int width) {
+ssize_t base64_append(
+                char **prefix,
+                size_t plen,
+                const void *p,
+                size_t l,
+                size_t indent,
+                size_t width) {
 
         if (plen > width / 2 || plen + indent > width)
                 /* leave indent on the left, keep last column free */
-                return base64_append_width(prefix, plen, '\n', indent, p, l, width - indent - 1);
+                return base64_append_width(prefix, plen, '\n', indent, p, l, width - indent);
         else
                 /* leave plen on the left, keep last column free */
                 return base64_append_width(prefix, plen, ' ', plen + 1, p, l, width - plen - 1);
@@ -754,12 +768,17 @@ static int unbase64_next(const char **p, size_t *l) {
         return ret;
 }
 
-int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_size) {
+int unbase64mem_full(
+                const char *p,
+                size_t l,
+                bool secure,
+                void **ret,
+                size_t *ret_size) {
+
         _cleanup_free_ uint8_t *buf = NULL;
         const char *x;
         uint8_t *z;
         size_t len;
-        int r;
 
         assert(p || l == 0);
 
@@ -774,60 +793,44 @@ int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *r
         if (!buf)
                 return -ENOMEM;
 
+        CLEANUP_ERASE_PTR(secure ? &buf : NULL, len);
+
         for (x = p, z = buf;;) {
                 int a, b, c, d; /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */
 
                 a = unbase64_next(&x, &l);
                 if (a == -EPIPE) /* End of string */
                         break;
-                if (a < 0) {
-                        r = a;
-                        goto on_failure;
-                }
-                if (a == INT_MAX) { /* Padding is not allowed at the beginning of a 4ch block */
-                        r = -EINVAL;
-                        goto on_failure;
-                }
+                if (a < 0)
+                        return a;
+                if (a == INT_MAX) /* Padding is not allowed at the beginning of a 4ch block */
+                        return -EINVAL;
 
                 b = unbase64_next(&x, &l);
-                if (b < 0) {
-                        r = b;
-                        goto on_failure;
-                }
-                if (b == INT_MAX) { /* Padding is not allowed at the second character of a 4ch block either */
-                        r = -EINVAL;
-                        goto on_failure;
-                }
+                if (b < 0)
+                        return b;
+                if (b == INT_MAX) /* Padding is not allowed at the second character of a 4ch block either */
+                        return -EINVAL;
 
                 c = unbase64_next(&x, &l);
-                if (c < 0) {
-                        r = c;
-                        goto on_failure;
-                }
+                if (c < 0)
+                        return c;
 
                 d = unbase64_next(&x, &l);
-                if (d < 0) {
-                        r = d;
-                        goto on_failure;
-                }
+                if (d < 0)
+                        return d;
 
                 if (c == INT_MAX) { /* Padding at the third character */
 
-                        if (d != INT_MAX) { /* If the third character is padding, the fourth must be too */
-                                r = -EINVAL;
-                                goto on_failure;
-                        }
+                        if (d != INT_MAX) /* If the third character is padding, the fourth must be too */
+                                return -EINVAL;
 
                         /* b == 00YY0000 */
-                        if (b & 15) {
-                                r = -EINVAL;
-                                goto on_failure;
-                        }
+                        if (b & 15)
+                                return -EINVAL;
 
-                        if (l > 0) { /* Trailing rubbish? */
-                                r = -ENAMETOOLONG;
-                                goto on_failure;
-                        }
+                        if (l > 0) /* Trailing rubbish? */
+                                return -ENAMETOOLONG;
 
                         *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */
                         break;
@@ -835,15 +838,11 @@ int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *r
 
                 if (d == INT_MAX) {
                         /* c == 00ZZZZ00 */
-                        if (c & 3) {
-                                r = -EINVAL;
-                                goto on_failure;
-                        }
+                        if (c & 3)
+                                return -EINVAL;
 
-                        if (l > 0) { /* Trailing rubbish? */
-                                r = -ENAMETOOLONG;
-                                goto on_failure;
-                        }
+                        if (l > 0) /* Trailing rubbish? */
+                                return -ENAMETOOLONG;
 
                         *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
                         *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
@@ -857,18 +856,14 @@ int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *r
 
         *z = 0;
 
+        assert((size_t) (z - buf) <= len);
+
         if (ret_size)
                 *ret_size = (size_t) (z - buf);
         if (ret)
                 *ret = TAKE_PTR(buf);
 
         return 0;
-
-on_failure:
-        if (secure)
-                explicit_bzero_safe(buf, len);
-
-        return r;
 }
 
 #if 0 /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.h b/src/libnm-systemd-shared/src/basic/hexdecoct.h
index 5218f786..319b21a1 100644
--- a/src/libnm-systemd-shared/src/basic/hexdecoct.h
+++ b/src/libnm-systemd-shared/src/basic/hexdecoct.h
@@ -38,9 +38,13 @@ static inline ssize_t base64mem(const void *p, size_t l, char **ret) {
         return base64mem_full(p, l, SIZE_MAX, ret);
 }
 
-int base64_append(char **prefix, int plen,
-                  const void *p, size_t l,
-                  int margin, int width);
+ssize_t base64_append(
+                char **prefix,
+                size_t plen,
+                const void *p,
+                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);
diff --git a/src/libnm-systemd-shared/src/basic/hostname-util.c b/src/libnm-systemd-shared/src/basic/hostname-util.c
index 1e94ce13..26e196f3 100644
--- a/src/libnm-systemd-shared/src/basic/hostname-util.c
+++ b/src/libnm-systemd-shared/src/basic/hostname-util.c
@@ -65,7 +65,7 @@ int gethostname_full(GetHostnameFlags flags, char **ret) {
         }
 
         if (FLAGS_SET(flags, GET_HOSTNAME_SHORT))
-                buf = strndup(s, strcspn(s, "."));
+                buf = strdupcspn(s, ".");
         else
                 buf = strdup(s);
         if (!buf)
@@ -197,6 +197,7 @@ bool is_localhost(const char *hostname) {
                 endswith_no_case(hostname, ".localhost.localdomain.");
 }
 
+#if 0 /* NM_IGNORED */
 int get_pretty_hostname(char **ret) {
         _cleanup_free_ char *n = NULL;
         int r;
@@ -213,3 +214,4 @@ int get_pretty_hostname(char **ret) {
         *ret = TAKE_PTR(n);
         return 0;
 }
+#endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/hostname-util.h b/src/libnm-systemd-shared/src/basic/hostname-util.h
index a00b8523..bcac3d9f 100644
--- a/src/libnm-systemd-shared/src/basic/hostname-util.h
+++ b/src/libnm-systemd-shared/src/basic/hostname-util.h
@@ -60,4 +60,12 @@ static inline bool is_outbound_hostname(const char *hostname) {
         return STRCASE_IN_SET(hostname, "_outbound", "_outbound.");
 }
 
+static inline bool is_dns_stub_hostname(const char *hostname) {
+        return STRCASE_IN_SET(hostname, "_localdnsstub", "_localdnsstub.");
+}
+
+static inline bool is_dns_proxy_stub_hostname(const char *hostname) {
+        return STRCASE_IN_SET(hostname, "_localdnsproxy", "_localdnsproxy.");
+}
+
 int get_pretty_hostname(char **ret);
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 b25c803b..b863aec3 100644
--- a/src/libnm-systemd-shared/src/basic/in-addr-util.c
+++ b/src/libnm-systemd-shared/src/basic/in-addr-util.c
@@ -13,13 +13,13 @@
 #include "alloc-util.h"
 #include "errno-util.h"
 #include "in-addr-util.h"
+#include "logarithm.h"
 #include "macro.h"
 #include "parse-util.h"
 #include "random-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
 #include "strxcpyx.h"
-#include "util.h"
 
 bool in4_addr_is_null(const struct in_addr *a) {
         assert(a);
@@ -907,14 +907,6 @@ int in_addr_prefix_from_string_auto_internal(
                         break;
                 case PREFIXLEN_REFUSE:
                         return -ENOANO; /* To distinguish this error from others. */
-                case PREFIXLEN_LEGACY:
-                        if (family == AF_INET) {
-                                r = in4_addr_default_prefixlen(&buffer.in, &k);
-                                if (r < 0)
-                                        return r;
-                        } else
-                                k = 0;
-                        break;
                 default:
                         assert_not_reached();
                 }
@@ -930,7 +922,7 @@ int in_addr_prefix_from_string_auto_internal(
 
 }
 
-static void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state) {
+void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state) {
         assert(a);
         assert(state);
 
@@ -938,7 +930,7 @@ static void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash
         siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state);
 }
 
-static int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y) {
+int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y) {
         int r;
 
         assert(x);
@@ -951,7 +943,18 @@ static int in_addr_data_compare_func(const struct in_addr_data *x, const struct
         return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family));
 }
 
-DEFINE_HASH_OPS(in_addr_data_hash_ops, struct in_addr_data, in_addr_data_hash_func, in_addr_data_compare_func);
+DEFINE_HASH_OPS(
+        in_addr_data_hash_ops,
+        struct in_addr_data,
+        in_addr_data_hash_func,
+        in_addr_data_compare_func);
+
+DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+        in_addr_data_hash_ops_free,
+        struct in_addr_data,
+        in_addr_data_hash_func,
+        in_addr_data_compare_func,
+        free);
 
 void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state) {
         assert(addr);
@@ -967,7 +970,12 @@ int in6_addr_compare_func(const struct in6_addr *a, const struct in6_addr *b) {
         return memcmp(a, b, sizeof(*a));
 }
 
-DEFINE_HASH_OPS(in6_addr_hash_ops, struct in6_addr, in6_addr_hash_func, in6_addr_compare_func);
+DEFINE_HASH_OPS(
+        in6_addr_hash_ops,
+        struct in6_addr,
+        in6_addr_hash_func,
+        in6_addr_compare_func);
+
 DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(
         in6_addr_hash_ops_free,
         struct in6_addr,
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 19fa35f1..200b9eb6 100644
--- a/src/libnm-systemd-shared/src/basic/in-addr-util.h
+++ b/src/libnm-systemd-shared/src/basic/in-addr-util.h
@@ -8,7 +8,6 @@
 
 #include "hash-funcs.h"
 #include "macro.h"
-#include "util.h"
 
 union in_addr_union {
         struct in_addr in;
@@ -154,7 +153,6 @@ int in_addr_prefix_from_string(const char *p, int family, union in_addr_union *r
 typedef enum InAddrPrefixLenMode {
         PREFIXLEN_FULL,   /* Default to prefixlen of address size, 32 for IPv4 or 128 for IPv6, if not specified. */
         PREFIXLEN_REFUSE, /* Fail with -ENOANO if prefixlen is not specified. */
-        PREFIXLEN_LEGACY, /* Default to legacy default prefixlen calculation from address if not specified. */
 } InAddrPrefixLenMode;
 
 int in_addr_prefix_from_string_auto_internal(const char *p, InAddrPrefixLenMode mode, int *ret_family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen);
@@ -178,10 +176,13 @@ 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_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);
 int in6_addr_compare_func(const struct in6_addr *a, const struct in6_addr *b);
 
 extern const struct hash_ops in_addr_data_hash_ops;
+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;
 
diff --git a/src/libnm-systemd-shared/src/basic/io-util.c b/src/libnm-systemd-shared/src/basic/io-util.c
index fc65a5e7..0c480091 100644
--- a/src/libnm-systemd-shared/src/basic/io-util.c
+++ b/src/libnm-systemd-shared/src/basic/io-util.c
@@ -167,6 +167,21 @@ int ppoll_usec(struct pollfd *fds, size_t nfds, usec_t timeout) {
 
         assert(fds || nfds == 0);
 
+        /* This is a wrapper around ppoll() that does primarily two things:
+         *
+         *  ✅ Takes a usec_t instead of a struct timespec
+         *
+         *  ✅ Guarantees that if an invalid fd is specified we return EBADF (i.e. converts POLLNVAL to
+         *     EBADF). This is done because EBADF is a programming error usually, and hence should bubble up
+         *     as error, and not be eaten up as non-error POLLNVAL event.
+         *
+         *  ⚠️ ⚠️ ⚠️ Note that this function does not add any special handling for EINTR. Don't forget
+         *  poll()/ppoll() will return with EINTR on any received signal always, there is no automatic
+         *  restarting via SA_RESTART available. Thus, typically you want to handle EINTR not as an error,
+         *  but just as reason to restart things, under the assumption you use a more appropriate mechanism
+         *  to handle signals, such as signalfd() or signal handlers. ⚠️ ⚠️ ⚠️
+         */
+
         if (nfds == 0)
                 return 0;
 
@@ -194,6 +209,9 @@ int fd_wait_for_event(int fd, int event, usec_t timeout) {
         };
         int r;
 
+        /* ⚠️ ⚠️ ⚠️ Keep in mind you almost certainly want to handle -EINTR gracefully in the caller, see
+         * ppoll_usec() above! ⚠️ ⚠️ ⚠️ */
+
         r = ppoll_usec(&pollfd, 1, timeout);
         if (r <= 0)
                 return r;
@@ -351,4 +369,14 @@ size_t iovw_size(struct iovec_wrapper *iovw) {
 
         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 39728e06..3ad82679 100644
--- a/src/libnm-systemd-shared/src/basic/io-util.h
+++ b/src/libnm-systemd-shared/src/basic/io-util.h
@@ -74,10 +74,13 @@ static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) {
 
 }
 
-#define IOVEC_INIT(base, len) { .iov_base = (base), .iov_len = (len) }
-#define IOVEC_MAKE(base, len) (struct iovec) IOVEC_INIT(base, len)
-#define IOVEC_INIT_STRING(string) IOVEC_INIT((char*) string, strlen(string))
-#define IOVEC_MAKE_STRING(string) (struct iovec) IOVEC_INIT_STRING(string)
+#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);
@@ -91,8 +94,19 @@ 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/label.c b/src/libnm-systemd-shared/src/basic/label.c
new file mode 100644
index 00000000..a08a238f
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/label.c
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <stddef.h>
+
+#include "label.h"
+
+static const LabelOps *label_ops = NULL;
+
+int label_ops_set(const LabelOps *ops) {
+        if (label_ops)
+                return -EBUSY;
+
+        label_ops = ops;
+        return 0;
+}
+
+int label_ops_pre(int dir_fd, const char *path, mode_t mode) {
+        if (!label_ops || !label_ops->pre)
+                return 0;
+
+        return label_ops->pre(dir_fd, path, mode);
+}
+
+int label_ops_post(int dir_fd, const char *path) {
+        if (!label_ops || !label_ops->post)
+                return 0;
+
+        return label_ops->post(dir_fd, path);
+}
diff --git a/src/libnm-systemd-shared/src/basic/label.h b/src/libnm-systemd-shared/src/basic/label.h
new file mode 100644
index 00000000..9644e435
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/label.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/types.h>
+
+typedef struct LabelOps {
+        int (*pre)(int dir_fd, const char *path, mode_t mode);
+        int (*post)(int dir_fd, const char *path);
+} LabelOps;
+
+int label_ops_set(const LabelOps *label_ops);
+
+int label_ops_pre(int dir_fd, const char *path, mode_t mode);
+int label_ops_post(int dir_fd, const char *path);
diff --git a/src/libnm-systemd-shared/src/basic/list.h b/src/libnm-systemd-shared/src/basic/list.h
index ca300396..e4e5dff3 100644
--- a/src/libnm-systemd-shared/src/basic/list.h
+++ b/src/libnm-systemd-shared/src/basic/list.h
@@ -1,8 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
-#include "macro.h"
-
 /* The head of the linked list. Use this in the structure that shall
  * contain the head of the linked list */
 #define LIST_HEAD(t,name)                                               \
@@ -28,26 +26,27 @@
 
 /* Prepend an item to the list */
 #define LIST_PREPEND(name,head,item)                                    \
-        do {                                                            \
+        ({                                                              \
                 typeof(*(head)) **_head = &(head), *_item = (item);     \
                 assert(_item);                                          \
                 if ((_item->name##_next = *_head))                      \
                         _item->name##_next->name##_prev = _item;        \
                 _item->name##_prev = NULL;                              \
                 *_head = _item;                                         \
-        } while (false)
+                _item;                                                  \
+        })
 
 /* Append an item to the list */
 #define LIST_APPEND(name,head,item)                                     \
-        do {                                                            \
+        ({                                                              \
                 typeof(*(head)) **_hhead = &(head), *_tail;             \
-                LIST_FIND_TAIL(name, *_hhead, _tail);                   \
+                _tail = LIST_FIND_TAIL(name, *_hhead);                  \
                 LIST_INSERT_AFTER(name, *_hhead, _tail, item);          \
-        } while (false)
+        })
 
 /* Remove an item from the list */
 #define LIST_REMOVE(name,head,item)                                     \
-        do {                                                            \
+        ({                                                              \
                 typeof(*(head)) **_head = &(head), *_item = (item);     \
                 assert(_item);                                          \
                 if (_item->name##_next)                                 \
@@ -59,37 +58,30 @@
                         *_head = _item->name##_next;                    \
                 }                                                       \
                 _item->name##_next = _item->name##_prev = NULL;         \
-        } while (false)
+                _item;                                                  \
+        })
 
 /* Find the head of the list */
-#define LIST_FIND_HEAD(name,item,head)                                  \
-        do {                                                            \
+#define LIST_FIND_HEAD(name,item)                                       \
+        ({                                                              \
                 typeof(*(item)) *_item = (item);                        \
-                if (!_item)                                             \
-                        (head) = NULL;                                  \
-                else {                                                  \
-                        while (_item->name##_prev)                      \
-                                _item = _item->name##_prev;             \
-                        (head) = _item;                                 \
-                }                                                       \
-        } while (false)
+                while (_item && _item->name##_prev)                     \
+                        _item = _item->name##_prev;                     \
+                _item;                                                  \
+        })
 
 /* Find the tail of the list */
-#define LIST_FIND_TAIL(name,item,tail)                                  \
-        do {                                                            \
+#define LIST_FIND_TAIL(name,item)                                       \
+        ({                                                              \
                 typeof(*(item)) *_item = (item);                        \
-                if (!_item)                                             \
-                        (tail) = NULL;                                  \
-                else {                                                  \
-                        while (_item->name##_next)                      \
-                                _item = _item->name##_next;             \
-                        (tail) = _item;                                 \
-                }                                                       \
-        } while (false)
+                while (_item && _item->name##_next)                     \
+                        _item = _item->name##_next;                     \
+                _item;                                                  \
+        })
 
 /* Insert an item after another one (a = where, b = what) */
 #define LIST_INSERT_AFTER(name,head,a,b)                                \
-        do {                                                            \
+        ({                                                              \
                 typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \
                 assert(_b);                                             \
                 if (!_a) {                                              \
@@ -103,11 +95,12 @@
                         _b->name##_prev = _a;                           \
                         _a->name##_next = _b;                           \
                 }                                                       \
-        } while (false)
+                _b;                                                     \
+        })
 
 /* Insert an item before another one (a = where, b = what) */
 #define LIST_INSERT_BEFORE(name,head,a,b)                               \
-        do {                                                            \
+        ({                                                              \
                 typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \
                 assert(_b);                                             \
                 if (!_a) {                                              \
@@ -131,10 +124,14 @@
                         _b->name##_next = _a;                           \
                         _a->name##_prev = _b;                           \
                 }                                                       \
-        } while (false)
+                _b;                                                     \
+        })
 
-#define LIST_JUST_US(name,item)                                         \
-        (!(item)->name##_prev && !(item)->name##_next)
+#define LIST_JUST_US(name, item)                                        \
+        ({                                                              \
+                typeof(*(item)) *_item = (item);                        \
+                !(_item)->name##_prev && !(_item)->name##_next;         \
+        })
 
 /* The type of the iterator 'i' is automatically determined by the type of 'head', and declared in the
  * loop. Hence, do not declare the same variable in the outer scope. Sometimes, we set 'head' through
@@ -172,18 +169,19 @@
 
 /* Join two lists tail to head: a->b, c->d to a->b->c->d and de-initialise second list */
 #define LIST_JOIN(name,a,b)                                             \
-        do {                                                            \
+        ({                                                              \
                 assert(b);                                              \
                 if (!(a))                                               \
                         (a) = (b);                                      \
                 else {                                                  \
                         typeof(*(a)) *_head = (b), *_tail;              \
-                        LIST_FIND_TAIL(name, (a), _tail);               \
+                        _tail = LIST_FIND_TAIL(name, (a));              \
                         _tail->name##_next = _head;                     \
                         _head->name##_prev = _tail;                     \
                 }                                                       \
                 (b) = NULL;                                             \
-        } while (false)
+                a;                                                      \
+        })
 
 #define LIST_POP(name, a)                                               \
         ({                                                              \
@@ -193,3 +191,7 @@
                         LIST_REMOVE(name, *_a, _p);                     \
                 _p;                                                     \
         })
+
+/* 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 ae567345..2f3701eb 100644
--- a/src/libnm-systemd-shared/src/basic/locale-util.c
+++ b/src/libnm-systemd-shared/src/basic/locale-util.c
@@ -12,7 +12,7 @@
 #include <sys/mman.h>
 #include <sys/stat.h>
 
-#include "def.h"
+#include "constants.h"
 #include "dirent-util.h"
 #include "env-util.h"
 #include "fd-util.h"
@@ -98,7 +98,7 @@ static int add_locales_from_archive(Set *locales) {
         const struct locarhead *h;
         const struct namehashent *e;
         const void *p = MAP_FAILED;
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         size_t sz = 0;
         struct stat st;
         int r;
@@ -290,8 +290,9 @@ void init_gettext(void) {
 #endif /* NM_IGNORED */
 
 bool is_locale_utf8(void) {
-        const char *set;
         static int cached_answer = -1;
+        const char *set;
+        int r;
 
         /* Note that we default to 'true' here, since today UTF8 is
          * pretty much supported everywhere. */
@@ -299,6 +300,13 @@ bool is_locale_utf8(void) {
         if (cached_answer >= 0)
                 goto out;
 
+        r = getenv_bool_secure("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");
+
         if (!setlocale(LC_ALL, "")) {
                 cached_answer = true;
                 goto out;
diff --git a/src/libnm-systemd-shared/src/basic/lock-util.h b/src/libnm-systemd-shared/src/basic/lock-util.h
new file mode 100644
index 00000000..e7744476
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/lock-util.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <fcntl.h>
+
+typedef struct LockFile {
+        int dir_fd;
+        char *path;
+        int fd;
+        int operation;
+} LockFile;
+
+int make_lock_file_at(int dir_fd, const char *p, int operation, LockFile *ret);
+static inline int make_lock_file(const char *p, int operation, LockFile *ret) {
+        return make_lock_file_at(AT_FDCWD, p, operation, ret);
+}
+int make_lock_file_for(const char *p, int operation, LockFile *ret);
+void release_lock_file(LockFile *f);
+
+#define LOCK_FILE_INIT { .dir_fd = -EBADF, .fd = -EBADF }
+
+/* POSIX locks with the same interface as flock(). */
+int posix_lock(int fd, int operation);
+void posix_unlockpp(int **fd);
+
+#define CLEANUP_POSIX_UNLOCK(fd)                                   \
+        _cleanup_(posix_unlockpp) _unused_ int *CONCATENATE(_cleanup_posix_unlock_, UNIQ) = &(fd)
+
+/* Open File Description locks with the same interface as flock(). */
+int unposix_lock(int fd, int operation);
+void unposix_unlockpp(int **fd);
+
+#define CLEANUP_UNPOSIX_UNLOCK(fd)                                   \
+        _cleanup_(unposix_unlockpp) _unused_ int *CONCATENATE(_cleanup_unposix_unlock_, UNIQ) = &(fd)
+
+typedef enum LockType {
+        LOCK_BSD,
+        LOCK_POSIX,
+        LOCK_UNPOSIX,
+} LockType;
+
+int lock_generic(int fd, LockType type, int operation);
diff --git a/src/libnm-systemd-shared/src/basic/log.h b/src/libnm-systemd-shared/src/basic/log.h
index a6adacf6..eb7b51cb 100644
--- a/src/libnm-systemd-shared/src/basic/log.h
+++ b/src/libnm-systemd-shared/src/basic/log.h
@@ -7,8 +7,10 @@
 #include <string.h>
 #include <syslog.h>
 
+#include "list.h"
 #include "macro.h"
 #include "ratelimit.h"
+#include "stdio-util.h"
 
 /* Some structures we reference but don't want to pull in headers for */
 struct iovec;
@@ -49,8 +51,10 @@ static inline void clear_log_syntax_callback(dummy_t *dummy) {
 const char *log_target_to_string(LogTarget target) _const_;
 LogTarget log_target_from_string(const char *s) _pure_;
 void log_set_target(LogTarget target);
+void log_set_target_and_open(LogTarget target);
 int log_set_target_from_string(const char *e);
 LogTarget log_get_target(void) _pure_;
+void log_settle_target(void);
 
 void log_set_max_level(int level);
 int log_set_max_level_from_string(const char *e);
@@ -432,6 +436,7 @@ int log_emergency_level(void);
 
 #define log_oom() log_oom_internal(LOG_ERR, PROJECT_FILE, __LINE__, __func__)
 #define log_oom_debug() log_oom_internal(LOG_DEBUG, PROJECT_FILE, __LINE__, __func__)
+#define log_oom_warning() log_oom_internal(LOG_WARNING, PROJECT_FILE, __LINE__, __func__)
 
 bool log_on_console(void) _pure_;
 
@@ -516,15 +521,12 @@ typedef struct LogRateLimit {
         RateLimit ratelimit;
 } LogRateLimit;
 
-#define log_ratelimit_internal(_level, _error, _format, _file, _line, _func, ...)        \
+#define log_ratelimit_internal(_level, _error, _ratelimit, _format, _file, _line, _func, ...)        \
 ({                                                                              \
         int _log_ratelimit_error = (_error);                                    \
         int _log_ratelimit_level = (_level);                                    \
         static LogRateLimit _log_ratelimit = {                                  \
-                .ratelimit = {                                                  \
-                        .interval = 1 * USEC_PER_SEC,                           \
-                        .burst = 1,                                             \
-                },                                                              \
+                .ratelimit = (_ratelimit),                                      \
         };                                                                      \
         unsigned _num_dropped_errors = ratelimit_num_dropped(&_log_ratelimit.ratelimit); \
         if (_log_ratelimit_error != _log_ratelimit.error || _log_ratelimit_level != _log_ratelimit.level) { \
@@ -532,18 +534,145 @@ typedef struct LogRateLimit {
                 _log_ratelimit.error = _log_ratelimit_error;                    \
                 _log_ratelimit.level = _log_ratelimit_level;                    \
         }                                                                       \
-        if (ratelimit_below(&_log_ratelimit.ratelimit))                         \
+        if (log_get_max_level() == LOG_DEBUG || ratelimit_below(&_log_ratelimit.ratelimit)) \
                 _log_ratelimit_error = _num_dropped_errors > 0                  \
-                ? log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format " (Dropped %u similar message(s))", __VA_ARGS__, _num_dropped_errors) \
-                : log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format, __VA_ARGS__); \
+                ? log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format " (Dropped %u similar message(s))", ##__VA_ARGS__, _num_dropped_errors) \
+                : log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format, ##__VA_ARGS__); \
         _log_ratelimit_error;                                                   \
 })
 
-#define log_ratelimit_full_errno(level, error, format, ...)             \
+#define log_ratelimit_full_errno(level, error, _ratelimit, format, ...)             \
         ({                                                              \
                 int _level = (level), _e = (error);                     \
                 _e = (log_get_max_level() >= LOG_PRI(_level))           \
-                        ? log_ratelimit_internal(_level, _e, format, PROJECT_FILE, __LINE__, __func__, __VA_ARGS__) \
+                        ? log_ratelimit_internal(_level, _e, _ratelimit, format, PROJECT_FILE, __LINE__, __func__, ##__VA_ARGS__) \
                         : -ERRNO_VALUE(_e);                             \
                 _e < 0 ? _e : -ESTRPIPE;                                \
         })
+
+#define log_ratelimit_full(level, _ratelimit, format, ...)                          \
+        log_ratelimit_full_errno(level, 0, _ratelimit, format, ##__VA_ARGS__)
+
+/* Normal logging */
+#define log_ratelimit_info(...)      log_ratelimit_full(LOG_INFO,    __VA_ARGS__)
+#define log_ratelimit_notice(...)    log_ratelimit_full(LOG_NOTICE,  __VA_ARGS__)
+#define log_ratelimit_warning(...)   log_ratelimit_full(LOG_WARNING, __VA_ARGS__)
+#define log_ratelimit_error(...)     log_ratelimit_full(LOG_ERR,     __VA_ARGS__)
+#define log_ratelimit_emergency(...) log_ratelimit_full(log_emergency_level(), __VA_ARGS__)
+
+/* Logging triggered by an errno-like error */
+#define log_ratelimit_info_errno(error, ...)      log_ratelimit_full_errno(LOG_INFO,    error, __VA_ARGS__)
+#define log_ratelimit_notice_errno(error, ...)    log_ratelimit_full_errno(LOG_NOTICE,  error, __VA_ARGS__)
+#define log_ratelimit_warning_errno(error, ...)   log_ratelimit_full_errno(LOG_WARNING, error, __VA_ARGS__)
+#define log_ratelimit_error_errno(error, ...)     log_ratelimit_full_errno(LOG_ERR,     error, __VA_ARGS__)
+#define log_ratelimit_emergency_errno(error, ...) log_ratelimit_full_errno(log_emergency_level(), error, __VA_ARGS__)
+
+const char *_log_set_prefix(const char *prefix, bool force);
+static inline const char *_log_unset_prefixp(const char **p) {
+        assert(p);
+        _log_set_prefix(*p, true);
+        return NULL;
+}
+
+#define LOG_SET_PREFIX(prefix) \
+        _cleanup_(_log_unset_prefixp) _unused_ const char *CONCATENATE(_cleanup_log_unset_prefix_, UNIQ) = _log_set_prefix(prefix, false);
+
+/*
+ * The log context allows attaching extra metadata to log messages written to the journal via log.h. We keep
+ * track of a thread local log context onto which we can push extra metadata fields that should be logged.
+ *
+ * LOG_CONTEXT_PUSH() will add the provided field to the log context and will remove it again when the
+ * current block ends. LOG_CONTEXT_PUSH_STRV() will do the same but for all fields in the given strv.
+ * LOG_CONTEXT_PUSHF() is like LOG_CONTEXT_PUSH() but takes a format string and arguments.
+ *
+ * Using the macros is as simple as putting them anywhere inside a block to add a field to all following log
+ * messages logged from inside that block.
+ *
+ * void myfunction(...) {
+ *         ...
+ *
+ *         LOG_CONTEXT_PUSHF("MYMETADATA=%s", "abc");
+ *
+ *         // Every journal message logged will now have the MYMETADATA=abc
+ *         // field included.
+ * }
+ *
+ * One special case to note is async code, where we use callbacks that are invoked to continue processing
+ * when some event occurs. For async code, there's usually an associated "userdata" struct containing all the
+ * information associated with the async operation. In this "userdata" struct, we can store a log context
+ * allocated with log_context_new() and freed with log_context_free(). We can then add and remove fields to
+ * the `fields` member of the log context object and all those fields will be logged along with each log
+ * message.
+ */
+
+typedef struct LogContext LogContext;
+
+bool log_context_enabled(void);
+
+LogContext* log_context_new(const char *key, const char *value);
+LogContext* log_context_new_strv(char **fields, bool owned);
+LogContext* log_context_new_iov(struct iovec *input_iovec, size_t n_input_iovec, bool owned);
+
+/* Same as log_context_new(), but frees the given fields strv/iovec on failure. */
+LogContext* log_context_new_strv_consume(char **fields);
+LogContext* log_context_new_iov_consume(struct iovec *input_iovec, size_t n_input_iovec);
+
+LogContext *log_context_ref(LogContext *c);
+LogContext *log_context_unref(LogContext *c);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_unref);
+
+/* Returns the number of attached log context objects. */
+size_t log_context_num_contexts(void);
+/* Returns the number of fields in all attached log contexts. */
+size_t log_context_num_fields(void);
+
+#define LOG_CONTEXT_PUSH(...) \
+        LOG_CONTEXT_PUSH_STRV(STRV_MAKE(__VA_ARGS__))
+
+#define LOG_CONTEXT_PUSHF(...) \
+        LOG_CONTEXT_PUSH(snprintf_ok((char[LINE_MAX]) {}, LINE_MAX, __VA_ARGS__))
+
+#define _LOG_CONTEXT_PUSH_KEY_VALUE(key, value, c) \
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new(key, value);
+
+#define LOG_CONTEXT_PUSH_KEY_VALUE(key, value) \
+        _LOG_CONTEXT_PUSH_KEY_VALUE(key, value, UNIQ_T(c, UNIQ))
+
+#define _LOG_CONTEXT_PUSH_STRV(strv, c) \
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv(strv, /*owned=*/ false);
+
+#define LOG_CONTEXT_PUSH_STRV(strv) \
+        _LOG_CONTEXT_PUSH_STRV(strv, UNIQ_T(c, UNIQ))
+
+#define _LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec, c) \
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_iov(input_iovec, n_input_iovec, /*owned=*/ false);
+
+#define LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec) \
+        _LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec, UNIQ_T(c, UNIQ))
+
+/* LOG_CONTEXT_CONSUME_STR()/LOG_CONTEXT_CONSUME_STRV()/LOG_CONTEXT_CONSUME_IOV() are identical to
+ * LOG_CONTEXT_PUSH_STR()/LOG_CONTEXT_PUSH_STRV()/LOG_CONTEXT_PUSH_IOV() except they take ownership of the
+ * given str/strv argument.
+ */
+
+#define _LOG_CONTEXT_CONSUME_STR(s, c, strv) \
+        _unused_ _cleanup_strv_free_ strv = strv_new(s);                                                \
+        if (!strv)                                                                                      \
+                free(s);                                                                                \
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv_consume(TAKE_PTR(strv))
+
+#define LOG_CONTEXT_CONSUME_STR(s) \
+        _LOG_CONTEXT_CONSUME_STR(s, UNIQ_T(c, UNIQ), UNIQ_T(sv, UNIQ))
+
+#define _LOG_CONTEXT_CONSUME_STRV(strv, c) \
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv_consume(strv);
+
+#define LOG_CONTEXT_CONSUME_STRV(strv) \
+        _LOG_CONTEXT_CONSUME_STRV(strv, UNIQ_T(c, UNIQ))
+
+#define _LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec, c) \
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_iov_consume(input_iovec, n_input_iovec);
+
+#define LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec) \
+        _LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec, UNIQ_T(c, UNIQ))
diff --git a/src/libnm-systemd-shared/src/basic/macro.h b/src/libnm-systemd-shared/src/basic/macro.h
index 7380da10..ce7350cb 100644
--- a/src/libnm-systemd-shared/src/basic/macro.h
+++ b/src/libnm-systemd-shared/src/basic/macro.h
@@ -9,32 +9,9 @@
 #include <sys/sysmacros.h>
 #include <sys/types.h>
 
+#include "constants.h"
 #include "macro-fundamental.h"
 
-#if !defined(HAS_FEATURE_MEMORY_SANITIZER)
-#  if defined(__has_feature)
-#    if __has_feature(memory_sanitizer)
-#      define HAS_FEATURE_MEMORY_SANITIZER 1
-#    endif
-#  endif
-#  if !defined(HAS_FEATURE_MEMORY_SANITIZER)
-#    define HAS_FEATURE_MEMORY_SANITIZER 0
-#  endif
-#endif
-
-#if !defined(HAS_FEATURE_ADDRESS_SANITIZER)
-#  ifdef __SANITIZE_ADDRESS__
-#      define HAS_FEATURE_ADDRESS_SANITIZER 1
-#  elif defined(__has_feature)
-#    if __has_feature(address_sanitizer)
-#      define HAS_FEATURE_ADDRESS_SANITIZER 1
-#    endif
-#  endif
-#  if !defined(HAS_FEATURE_ADDRESS_SANITIZER)
-#    define HAS_FEATURE_ADDRESS_SANITIZER 0
-#  endif
-#endif
-
 /* Note: on GCC "no_sanitize_address" is a function attribute only, on llvm it may also be applied to global
  * variables. We define a specific macro which knows this. Note that on GCC we don't need this decorator so much, since
  * our primary usecase for this attribute is registration structures placed in named ELF sections which shall not be
@@ -88,10 +65,14 @@
         _Pragma("GCC diagnostic push")
 #endif
 
-#define DISABLE_WARNING_TYPE_LIMITS \
+#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")
 #else
@@ -203,12 +184,12 @@ static inline int __coverity_check_and_return__(int condition) {
 #define assert_message_se(expr, message)                                \
         do {                                                            \
                 if (_unlikely_(!(expr)))                                \
-                        log_assert_failed(message, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__); \
+                        log_assert_failed(message, PROJECT_FILE, __LINE__, __func__); \
         } while (false)
 
 #define assert_log(expr, message) ((_likely_(expr))                     \
         ? (true)                                                        \
-        : (log_assert_failed_return(message, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__), false))
+        : (log_assert_failed_return(message, PROJECT_FILE, __LINE__, __func__), false))
 
 #endif  /* __COVERITY__ */
 
@@ -223,7 +204,7 @@ static inline int __coverity_check_and_return__(int condition) {
 #endif
 
 #define assert_not_reached()                                            \
-        log_assert_failed_unreachable(PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__)
+        log_assert_failed_unreachable(PROJECT_FILE, __LINE__, __func__)
 
 #define assert_return(expr, r)                                          \
         do {                                                            \
@@ -276,6 +257,11 @@ static inline int __coverity_check_and_return__(int condition) {
 #define char_array_0(x) x[sizeof(x)-1] = 0;
 
 #define sizeof_field(struct_type, member) sizeof(((struct_type *) 0)->member)
+#define endoffsetof_field(struct_type, member) (offsetof(struct_type, member) + sizeof_field(struct_type, member))
+
+/* Maximum buffer size needed for formatting an unsigned integer type as hex, including space for '0x'
+ * prefix and trailing NUL suffix. */
+#define HEXADECIMAL_STR_MAX(type) (2 + sizeof(type) * 2 + 1)
 
 /* Returns the number of chars needed to format variables of the specified type as a decimal string. Adds in
  * extra space for a negative '-' prefix for signed types. Includes space for the trailing NUL. */
@@ -324,19 +310,14 @@ static inline int __coverity_check_and_return__(int condition) {
              p != (typeof(p)) POINTER_MAX;                                               \
              p = *(++_l))
 
-/* Define C11 thread_local attribute even on older gcc compiler
- * version */
-#ifndef thread_local
-/*
- * Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__
- * see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769
- */
-#if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16))
-#define thread_local _Thread_local
-#else
-#define thread_local __thread
-#endif
-#endif
+#define _FOREACH_ARRAY(i, array, num, m, end)                           \
+        for (typeof(array[0]) *i = (array), *end = ({                   \
+                                typeof(num) m = (num);                  \
+                                (i && m > 0) ? i + m : NULL;            \
+                        }); end && i < end; i++)
+
+#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) {                      \
@@ -350,10 +331,14 @@ static inline int __coverity_check_and_return__(int condition) {
                         *p = func(*p);                          \
         }
 
-/* When func() doesn't return the appropriate type, set variable to empty afterwards */
+/* 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);                           \
                 }                                               \
@@ -442,7 +427,7 @@ typedef struct {
 
 assert_cc(sizeof(dummy_t) == 0);
 
-/* A little helper for subtracting 1 off a pointer in a safe UB-free way. This is intended to be used for for
+/* A little helper for subtracting 1 off a pointer in a safe UB-free way. This is intended to be used for
  * loops that count down from a high pointer until some base. A naive loop would implement this like this:
  *
  * for (p = end-1; p >= base; p--) …
@@ -458,4 +443,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
+ * 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; }); \
+             _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 2d41a26a..c1e0a742 100644
--- a/src/libnm-systemd-shared/src/basic/memory-util.c
+++ b/src/libnm-systemd-shared/src/basic/memory-util.c
@@ -5,6 +5,7 @@
 #include <unistd.h>
 
 #include "memory-util.h"
+#include "missing_threads.h"
 
 size_t page_size(void) {
         static thread_local size_t pgsz = 0;
@@ -40,21 +41,3 @@ 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;
 }
-
-#if !HAVE_EXPLICIT_BZERO
-/*
- * The pointer to memset() is volatile so that compiler must de-reference the pointer and can't assume that
- * it points to any function in particular (such as memset(), which it then might further "optimize"). This
- * approach is inspired by openssl's crypto/mem_clr.c.
- */
-typedef void *(*memset_t)(void *,int,size_t);
-
-static volatile memset_t memset_func = memset;
-
-void* explicit_bzero_safe(void *p, size_t l) {
-        if (l > 0)
-                memset_func(p, '\0', l);
-
-        return p;
-}
-#endif
diff --git a/src/libnm-systemd-shared/src/basic/memory-util.h b/src/libnm-systemd-shared/src/basic/memory-util.h
index 6e3280b9..d26a0918 100644
--- a/src/libnm-systemd-shared/src/basic/memory-util.h
+++ b/src/libnm-systemd-shared/src/basic/memory-util.h
@@ -9,6 +9,7 @@
 
 #include "alloc-util.h"
 #include "macro.h"
+#include "memory-util-fundamental.h"
 
 size_t page_size(void) _pure_;
 #define PAGE_ALIGN(l) ALIGN_TO((l), page_size())
@@ -91,17 +92,6 @@ static inline void *mempmem_safe(const void *haystack, size_t haystacklen, const
         return (uint8_t*) p + needlelen;
 }
 
-#if HAVE_EXPLICIT_BZERO
-static inline void* explicit_bzero_safe(void *p, size_t l) {
-        if (l > 0)
-                explicit_bzero(p, l);
-
-        return p;
-}
-#else
-void *explicit_bzero_safe(void *p, size_t l);
-#endif
-
 static inline void* erase_and_free(void *p) {
         size_t l;
 
@@ -121,3 +111,37 @@ static inline void erase_and_freep(void *p) {
 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;                                     \
+                        }),                                             \
+        }
diff --git a/src/libnm-systemd-shared/src/basic/mempool.c b/src/libnm-systemd-shared/src/basic/mempool.c
index 53a719e0..e467d706 100644
--- a/src/libnm-systemd-shared/src/basic/mempool.c
+++ b/src/libnm-systemd-shared/src/basic/mempool.c
@@ -5,6 +5,7 @@
 #include <stdint.h>
 #include <stdlib.h>
 
+#include "format-util.h"
 #include "macro.h"
 #include "memory-util.h"
 #include "mempool.h"
@@ -15,21 +16,26 @@ struct pool {
         size_t n_used;
 };
 
+static void* pool_ptr(struct pool *p) {
+        return ((uint8_t*) ASSERT_PTR(p)) + ALIGN(sizeof(struct pool));
+}
+
 void* mempool_alloc_tile(struct mempool *mp) {
         size_t i;
 
         /* When a tile is released we add it to the list and simply
          * place the next pointer at its offset 0. */
 
+        assert(mp);
         assert(mp->tile_size >= sizeof(void*));
         assert(mp->at_least > 0);
 
         if (mp->freelist) {
-                void *r;
+                void *t;
 
-                r = mp->freelist;
-                mp->freelist = * (void**) mp->freelist;
-                return r;
+                t = mp->freelist;
+                mp->freelist = *(void**) mp->freelist;
+                return t;
         }
 
         if (_unlikely_(!mp->first_pool) ||
@@ -55,7 +61,7 @@ void* mempool_alloc_tile(struct mempool *mp) {
 
         i = mp->first_pool->n_used++;
 
-        return ((uint8_t*) mp->first_pool) + ALIGN(sizeof(struct pool)) + i*mp->tile_size;
+        return (uint8_t*) pool_ptr(mp->first_pool) + i*mp->tile_size;
 }
 
 void* mempool_alloc0_tile(struct mempool *mp) {
@@ -67,19 +73,105 @@ void* mempool_alloc0_tile(struct mempool *mp) {
         return p;
 }
 
-void mempool_free_tile(struct mempool *mp, void *p) {
-        * (void**) p = mp->freelist;
+void* mempool_free_tile(struct mempool *mp, void *p) {
+        assert(mp);
+
+        if (!p)
+                return NULL;
+
+        *(void**) p = mp->freelist;
         mp->freelist = p;
+
+        return NULL;
+}
+
+static bool pool_contains(struct mempool *mp, struct pool *p, void *ptr) {
+        size_t off;
+        void *a;
+
+        assert(mp);
+        assert(p);
+
+        if (!ptr)
+                return false;
+
+        a = pool_ptr(p);
+        if ((uint8_t*) ptr < (uint8_t*) a)
+                return false;
+
+        off = (uint8_t*) ptr - (uint8_t*) a;
+        if (off >= mp->tile_size * p->n_tiles)
+                return false;
+
+        assert(off % mp->tile_size == 0);
+        return true;
+}
+
+static bool pool_is_unused(struct mempool *mp, struct pool *p) {
+        assert(mp);
+        assert(p);
+
+        if (p->n_used == 0)
+                return true;
+
+        /* Check if all tiles in this specific pool are in the freelist. */
+        size_t n = 0;
+        void *i = mp->freelist;
+        while (i) {
+                if (pool_contains(mp, p, i))
+                        n++;
+
+                i = *(void**) i;
+        }
+
+        assert(n <= p->n_used);
+
+        return n == p->n_used;
+}
+
+static void pool_unlink(struct mempool *mp, struct pool *p) {
+        size_t m = 0;
+
+        assert(mp);
+        assert(p);
+
+        if (p->n_used == 0)
+                return;
+
+        void **i = &mp->freelist;
+        while (*i) {
+                void *d = *i;
+
+                if (pool_contains(mp, p, d)) {
+                        *i = *(void**) d;
+                        m++;
+
+                        if (m == p->n_used)
+                                break;
+                } else
+                        i = (void**) d;
+        }
 }
 
-#if VALGRIND
-void mempool_drop(struct mempool *mp) {
-        struct pool *p = mp->first_pool;
-        while (p) {
-                struct pool *n;
-                n = p->next;
-                free(p);
-                p = n;
+void mempool_trim(struct mempool *mp) {
+        size_t trimmed = 0, left = 0;
+
+        assert(mp);
+
+        struct pool **p = &mp->first_pool;
+        while (*p) {
+                struct pool *d = *p;
+
+                if (pool_is_unused(mp, d)) {
+                        trimmed += d->n_tiles * mp->tile_size;
+                        pool_unlink(mp, d);
+                        *p = d->next;
+                        free(d);
+                } else {
+                        left += d->n_tiles * mp->tile_size;
+                        p = &d->next;
+                }
         }
+
+        log_debug("Trimmed %s from memory pool %p. (%s left)", FORMAT_BYTES(trimmed), mp, FORMAT_BYTES(left));
 }
-#endif
diff --git a/src/libnm-systemd-shared/src/basic/mempool.h b/src/libnm-systemd-shared/src/basic/mempool.h
index 539ccbdf..ba588af4 100644
--- a/src/libnm-systemd-shared/src/basic/mempool.h
+++ b/src/libnm-systemd-shared/src/basic/mempool.h
@@ -10,12 +10,12 @@ struct mempool {
         struct pool *first_pool;
         void *freelist;
         size_t tile_size;
-        unsigned at_least;
+        size_t at_least;
 };
 
 void* mempool_alloc_tile(struct mempool *mp);
 void* mempool_alloc0_tile(struct mempool *mp);
-void mempool_free_tile(struct mempool *mp, void *p);
+void* mempool_free_tile(struct mempool *mp, void *p);
 
 #define DEFINE_MEMPOOL(pool_name, tile_type, alloc_at_least) \
 static struct mempool pool_name = { \
@@ -25,6 +25,4 @@ static struct mempool pool_name = { \
 
 __attribute__((weak)) bool mempool_enabled(void);
 
-#if VALGRIND
-void mempool_drop(struct mempool *mp);
-#endif
+void mempool_trim(struct mempool *mp);
diff --git a/src/libnm-systemd-shared/src/basic/missing_fcntl.h b/src/libnm-systemd-shared/src/basic/missing_fcntl.h
index 00937d2a..24b2dc31 100644
--- a/src/libnm-systemd-shared/src/basic/missing_fcntl.h
+++ b/src/libnm-systemd-shared/src/basic/missing_fcntl.h
@@ -25,6 +25,14 @@
 #define F_SEAL_WRITE    0x0008  /* prevent writes */
 #endif
 
+#ifndef F_SEAL_FUTURE_WRITE
+#define F_SEAL_FUTURE_WRITE 0x0010 /* prevent future writes while mapped */
+#endif
+
+#ifndef F_SEAL_EXEC
+#define F_SEAL_EXEC     0x0020  /* prevent chmod modifying exec bits */
+#endif
+
 #ifndef F_OFD_GETLK
 #define F_OFD_GETLK     36
 #define F_OFD_SETLK     37
@@ -58,3 +66,12 @@
 #ifndef O_TMPFILE
 #define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
 #endif
+
+/* 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. */
+#if O_LARGEFILE != 0
+#define RAW_O_LARGEFILE O_LARGEFILE
+#else
+#define RAW_O_LARGEFILE 0100000
+#endif
diff --git a/src/libnm-systemd-shared/src/basic/missing_syscall.h b/src/libnm-systemd-shared/src/basic/missing_syscall.h
index 107f4b01..610a7cef 100644
--- a/src/libnm-systemd-shared/src/basic/missing_syscall.h
+++ b/src/libnm-systemd-shared/src/basic/missing_syscall.h
@@ -370,6 +370,20 @@ static inline int missing_rt_sigqueueinfo(pid_t tgid, int sig, siginfo_t *info)
 /* ======================================================================= */
 
 #if 0 /* NM_IGNORED */
+#if !HAVE_RT_TGSIGQUEUEINFO
+static inline int missing_rt_tgsigqueueinfo(pid_t tgid, pid_t tid, int sig, siginfo_t *info) {
+#  if defined __NR_rt_tgsigqueueinfo && __NR_rt_tgsigqueueinfo >= 0
+        return syscall(__NR_rt_tgsigqueueinfo, tgid, tid, sig, info);
+#  else
+#    error "__NR_rt_tgsigqueueinfo not defined"
+#  endif
+}
+
+#  define rt_tgsigqueueinfo missing_rt_tgsigqueueinfo
+#endif
+
+/* ======================================================================= */
+
 #if !HAVE_EXECVEAT
 static inline int missing_execveat(int dirfd, const char *pathname,
                                    char *const argv[], char *const envp[],
@@ -419,44 +433,6 @@ static inline int missing_close_range(int first_fd, int end_fd, unsigned flags)
 
 /* ======================================================================= */
 
-#if !HAVE_EPOLL_PWAIT2
-
-/* Defined to be equivalent to the kernel's _NSIG_WORDS, i.e. the size of the array of longs that is
- * encapsulated by sigset_t. */
-#define KERNEL_NSIG_WORDS (64 / (sizeof(long) * 8))
-#define KERNEL_NSIG_BYTES (KERNEL_NSIG_WORDS * sizeof(long))
-
-struct epoll_event;
-
-static inline int missing_epoll_pwait2(
-                int fd,
-                struct epoll_event *events,
-                int maxevents,
-                const struct timespec *timeout,
-                const sigset_t *sigset) {
-
-#  if defined(__NR_epoll_pwait2) && HAVE_LINUX_TIME_TYPES_H
-        if (timeout) {
-                /* Convert from userspace timespec to kernel timespec */
-                struct __kernel_timespec ts = {
-                        .tv_sec = timeout->tv_sec,
-                        .tv_nsec = timeout->tv_nsec,
-                };
-
-                return syscall(__NR_epoll_pwait2, fd, events, maxevents, &ts, sigset, sigset ? KERNEL_NSIG_BYTES : 0);
-        } else
-                return syscall(__NR_epoll_pwait2, fd, events, maxevents, NULL, sigset, sigset ? KERNEL_NSIG_BYTES : 0);
-#  else
-        errno = ENOSYS;
-        return -1;
-#  endif
-}
-
-#  define epoll_pwait2 missing_epoll_pwait2
-#endif
-
-/* ======================================================================= */
-
 #if !HAVE_MOUNT_SETATTR
 
 #if !HAVE_STRUCT_MOUNT_ATTR
@@ -600,6 +576,78 @@ static inline int missing_move_mount(
 
 /* ======================================================================= */
 
+#if !HAVE_FSOPEN
+
+#ifndef FSOPEN_CLOEXEC
+#define FSOPEN_CLOEXEC 0x00000001
+#endif
+
+static inline int missing_fsopen(const char *fsname, unsigned flags) {
+#  if defined __NR_fsopen && __NR_fsopen >= 0
+        return syscall(__NR_fsopen, fsname, flags);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define fsopen missing_fsopen
+#endif
+
+/* ======================================================================= */
+
+#if !HAVE_FSCONFIG
+
+#ifndef FSCONFIG_SET_FLAG
+#define FSCONFIG_SET_FLAG 0 /* Set parameter, supplying no value */
+#endif
+
+#ifndef FSCONFIG_SET_STRING
+#define FSCONFIG_SET_STRING 1 /* Set parameter, supplying a string value */
+#endif
+
+#ifndef FSCONFIG_SET_FD
+#define FSCONFIG_SET_FD 5 /* Set parameter, supplying an object by fd */
+#endif
+
+#ifndef FSCONFIG_CMD_CREATE
+#define FSCONFIG_CMD_CREATE 6 /* Invoke superblock creation */
+#endif
+
+static inline int missing_fsconfig(int fd, unsigned cmd, const char *key, const void *value, int aux) {
+#  if defined __NR_fsconfig && __NR_fsconfig >= 0
+        return syscall(__NR_fsconfig, fd, cmd, key, value, aux);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define fsconfig missing_fsconfig
+#endif
+
+/* ======================================================================= */
+
+#if !HAVE_FSMOUNT
+
+#ifndef FSMOUNT_CLOEXEC
+#define FSMOUNT_CLOEXEC 0x00000001
+#endif
+
+static inline int missing_fsmount(int fd, unsigned flags, unsigned ms_flags) {
+#  if defined __NR_fsmount && __NR_fsmount >= 0
+        return syscall(__NR_fsmount, fd, flags, ms_flags);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define fsmount missing_fsmount
+#endif
+
+/* ======================================================================= */
+
 #if !HAVE_GETDENTS64
 
 static inline ssize_t missing_getdents64(int fd, void *buffer, size_t length) {
diff --git a/src/libnm-systemd-shared/src/basic/missing_threads.h b/src/libnm-systemd-shared/src/basic/missing_threads.h
new file mode 100644
index 00000000..fb3b7224
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/missing_threads.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/* If threads.h doesn't exist, then define our own thread_local to match C11's thread_local. */
+#if HAVE_THREADS_H
+#  include <threads.h>
+#elif !(defined(thread_local))
+/* Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__
+ * see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769 */
+#  if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16))
+#    define thread_local _Thread_local
+#  else
+#    define thread_local __thread
+#  endif
+#endif
diff --git a/src/libnm-systemd-shared/src/basic/origin-id.h b/src/libnm-systemd-shared/src/basic/origin-id.h
new file mode 100644
index 00000000..c55b0a36
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/origin-id.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <pthread.h>
+
+#include "random-util.h"
+
+/* This pattern needs to be repeated exactly in multiple modules, so macro it.
+ * To ensure an object is not passed into a different module (e.g.: when two shared objects statically
+ * linked to libsystemd get loaded in the same process, and the object created by one is passed to the
+ * other, see https://github.com/systemd/systemd/issues/27216), create a random static global random
+ * (mixed with PID, so that we can also check for reuse after fork) that is stored in the object and
+ * checked by public API on use. */
+#define _DEFINE_ORIGIN_ID_HELPERS(type, name, scope)                  \
+static uint64_t origin_id;                                            \
+                                                                      \
+static void origin_id_initialize(void) {                              \
+        origin_id = random_u64();                                     \
+}                                                                     \
+                                                                      \
+static uint64_t origin_id_query(void) {                               \
+        static pthread_once_t once = PTHREAD_ONCE_INIT;               \
+        assert_se(pthread_once(&once, origin_id_initialize) == 0);    \
+        return origin_id ^ getpid_cached();                           \
+}                                                                     \
+                                                                      \
+scope bool name##_origin_changed(type *p) {                           \
+        assert(p);                                                    \
+        return p->origin_id != origin_id_query();                     \
+}
+
+#define DEFINE_ORIGIN_ID_HELPERS(type, name)                          \
+        _DEFINE_ORIGIN_ID_HELPERS(type, name,);
+
+#define DEFINE_PRIVATE_ORIGIN_ID_HELPERS(type, name)                  \
+        _DEFINE_ORIGIN_ID_HELPERS(type, name, static);
diff --git a/src/libnm-systemd-shared/src/basic/parse-util.c b/src/libnm-systemd-shared/src/basic/parse-util.c
index 968f01c6..2b22039c 100644
--- a/src/libnm-systemd-shared/src/basic/parse-util.c
+++ b/src/libnm-systemd-shared/src/basic/parse-util.c
@@ -53,7 +53,6 @@ int parse_pid(const char *s, pid_t* ret_pid) {
         int r;
 
         assert(s);
-        assert(ret_pid);
 
         r = safe_atolu(s, &ul);
         if (r < 0)
@@ -67,7 +66,8 @@ int parse_pid(const char *s, pid_t* ret_pid) {
         if (!pid_is_valid(pid))
                 return -ERANGE;
 
-        *ret_pid = pid;
+        if (ret_pid)
+                *ret_pid = pid;
         return 0;
 }
 
@@ -261,6 +261,26 @@ int parse_size(const char *t, uint64_t base, uint64_t *size) {
         return 0;
 }
 
+int parse_sector_size(const char *t, uint64_t *ret) {
+        int r;
+
+        assert(t);
+        assert(ret);
+
+        uint64_t ss;
+
+        r = safe_atou64(t, &ss);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse sector size parameter %s", t);
+        if (ss < 512 || ss > 4096) /* Allow up to 4K due to dm-crypt support and 4K alignment by the homed LUKS backend */
+                return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Sector size not between 512 and 4096: %s", t);
+        if (!ISPOWEROF2(ss))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Sector size not power of 2: %s", t);
+
+        *ret = ss;
+        return 0;
+}
+
 int parse_range(const char *t, unsigned *lower, unsigned *upper) {
         _cleanup_free_ char *word = NULL;
         unsigned l, u;
@@ -319,6 +339,21 @@ int parse_errno(const char *t) {
 }
 #endif /* NM_IGNORED */
 
+int parse_fd(const char *t) {
+        int r, fd;
+
+        assert(t);
+
+        r = safe_atoi(t, &fd);
+        if (r < 0)
+                return r;
+
+        if (fd < 0)
+                return -EBADF;
+
+        return fd;
+}
+
 static const char *mangle_base(const char *s, unsigned *base) {
         const char *k;
 
diff --git a/src/libnm-systemd-shared/src/basic/parse-util.h b/src/libnm-systemd-shared/src/basic/parse-util.h
index 8d8d5232..c480407c 100644
--- a/src/libnm-systemd-shared/src/basic/parse-util.h
+++ b/src/libnm-systemd-shared/src/basic/parse-util.h
@@ -18,8 +18,10 @@ int parse_ifindex(const char *s);
 int parse_mtu(int family, const char *s, uint32_t *ret);
 
 int parse_size(const char *t, uint64_t base, uint64_t *size);
+int parse_sector_size(const char *t, uint64_t *ret);
 int parse_range(const char *t, unsigned *lower, unsigned *upper);
 int parse_errno(const char *t);
+int parse_fd(const char *t);
 
 #define SAFE_ATO_REFUSE_PLUS_MINUS (1U << 30)
 #define SAFE_ATO_REFUSE_LEADING_ZERO (1U << 29)
diff --git a/src/libnm-systemd-shared/src/basic/path-util.c b/src/libnm-systemd-shared/src/basic/path-util.c
index b45bb0b5..a2af9e0c 100644
--- a/src/libnm-systemd-shared/src/basic/path-util.c
+++ b/src/libnm-systemd-shared/src/basic/path-util.c
@@ -10,7 +10,7 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "extract-word.h"
 #include "fd-util.h"
 #include "fs-util.h"
@@ -25,7 +25,7 @@
 
 #if 0 /* NM_IGNORED */
 int path_split_and_make_absolute(const char *p, char ***ret) {
-        char **l;
+        _cleanup_strv_free_ char **l = NULL;
         int r;
 
         assert(p);
@@ -36,12 +36,10 @@ int path_split_and_make_absolute(const char *p, char ***ret) {
                 return -ENOMEM;
 
         r = path_strv_make_absolute_cwd(l);
-        if (r < 0) {
-                strv_free(l);
+        if (r < 0)
                 return r;
-        }
 
-        *ret = l;
+        *ret = TAKE_PTR(l);
         return r;
 }
 
@@ -288,7 +286,7 @@ char **path_strv_resolve(char **l, const char *root) {
                 } else
                         t = *s;
 
-                r = chase_symlinks(t, root, 0, &u, NULL);
+                r = chase(t, root, 0, &u, NULL);
                 if (r == -ENOENT) {
                         if (root) {
                                 u = TAKE_PTR(orig);
@@ -491,29 +489,37 @@ int path_compare(const char *a, const char *b) {
         }
 }
 
-bool path_equal_or_files_same(const char *a, const char *b, int flags) {
-        return path_equal(a, b) || files_same(a, b, flags) > 0;
+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;
 }
 
-bool path_equal_filename(const char *a, const char *b) {
-        _cleanup_free_ char *a_basename = NULL, *b_basename = NULL;
-        int r;
+int path_compare_filename(const char *a, const char *b) {
+        _cleanup_free_ char *fa = NULL, *fb = NULL;
+        int r, j, k;
 
-        assert(a);
-        assert(b);
+        /* Order NULL before non-NULL */
+        r = CMP(!!a, !!b);
+        if (r != 0)
+                return r;
 
-        r = path_extract_filename(a, &a_basename);
-        if (r < 0) {
-                log_debug_errno(r, "Failed to parse basename of %s: %m", a);
-                return false;
-        }
-        r = path_extract_filename(b, &b_basename);
-        if (r < 0) {
-                log_debug_errno(r, "Failed to parse basename of %s: %m", b);
-                return false;
-        }
+        j = path_extract_filename(a, &fa);
+        k = path_extract_filename(b, &fb);
+
+        /* When one of paths is "." or root, then order it earlier. */
+        r = CMP(j != -EADDRNOTAVAIL, k != -EADDRNOTAVAIL);
+        if (r != 0)
+                return r;
+
+        /* When one of paths is invalid (or we get OOM), order invalid path after valid one. */
+        r = CMP(j < 0, k < 0);
+        if (r != 0)
+                return r;
 
-        return path_equal(a_basename, b_basename);
+        /* fallback to use strcmp() if both paths are invalid. */
+        if (j < 0)
+                return strcmp(a, b);
+
+        return strcmp(fa, fb);
 }
 
 char* path_extend_internal(char **x, ...) {
@@ -523,17 +529,17 @@ char* path_extend_internal(char **x, ...) {
         va_list ap;
         bool slash;
 
-        /* Joins all listed strings until the sentinel and places a "/" between them unless the strings end/begin
-         * already with one so that it is unnecessary. Note that slashes which are already duplicate won't be
-         * removed. The string returned is hence always equal to or longer than the sum of the lengths of each
-         * individual string.
+        /* Joins all listed strings until the sentinel and places a "/" between them unless the strings
+         * end/begin already with one so that it is unnecessary. Note that slashes which are already
+         * duplicate won't be removed. The string returned is hence always equal to or longer than the sum of
+         * the lengths of the individual strings.
          *
          * The first argument may be an already allocated string that is extended via realloc() if
          * non-NULL. path_extend() and path_join() are macro wrappers around this function, making use of the
          * first parameter to distinguish the two operations.
          *
-         * Note: any listed empty string is simply skipped. This can be useful for concatenating strings of which some
-         * are optional.
+         * Note: any listed empty string is simply skipped. This can be useful for concatenating strings of
+         * which some are optional.
          *
          * Examples:
          *
@@ -592,7 +598,7 @@ char* path_extend_internal(char **x, ...) {
 
 #if 0 /* NM_IGNORED */
 static int check_x_access(const char *path, int *ret_fd) {
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         int r;
 
         /* We need to use O_PATH because there may be executables for which we have only exec
@@ -620,22 +626,19 @@ static int check_x_access(const char *path, int *ret_fd) {
 }
 
 static int find_executable_impl(const char *name, const char *root, char **ret_filename, int *ret_fd) {
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         _cleanup_free_ char *path_name = NULL;
         int r;
 
         assert(name);
 
-        /* Function chase_symlinks() is invoked only when root is not NULL, as using it regardless of
+        /* Function chase() is invoked only when root is not NULL, as using it regardless of
          * root value would alter the behavior of existing callers for example: /bin/sleep would become
          * /usr/bin/sleep when find_executables is called. Hence, this function should be invoked when
          * needed to avoid unforeseen regression or other complicated changes. */
         if (root) {
-                r = chase_symlinks(name,
-                                   root,
-                                   CHASE_PREFIX_ROOT,
-                                   &path_name,
-                                   /* ret_fd= */ NULL); /* prefix root to name in case full paths are not specified */
+                 /* prefix root to name in case full paths are not specified */
+                r = chase(name, root, CHASE_PREFIX_ROOT, &path_name, /* ret_fd= */ NULL);
                 if (r < 0)
                         return r;
 
@@ -902,6 +905,8 @@ static const char *skip_slash_or_dot_backward(const char *path, const char *q) {
                         continue;
                 if (q > path && strneq(q - 1, "/.", 2))
                         continue;
+                if (q == path && *q == '.')
+                        continue;
                 break;
         }
         return q;
@@ -926,6 +931,12 @@ int path_find_last_component(const char *path, bool accept_dot_dot, const char *
         *           ret: "bbbbb/cc//././"
         *           return value: 5 (== strlen("bbbbb"))
         *
+        *   Input:  path: "//.//aaa///bbbbb/cc//././"
+        *           next: "///bbbbb/cc//././"
+        *   Output: next: "//.//aaa///bbbbb/cc//././" (next == path)
+        *           ret: "aaa///bbbbb/cc//././"
+        *           return value: 3 (== strlen("aaa"))
+        *
         *   Input:  path: "/", ".", "", or NULL
         *   Output: next: equivalent to path
         *           ret: NULL
@@ -1171,31 +1182,35 @@ bool path_is_normalized(const char *p) {
 }
 
 #if 0 /* NM_IGNORED */
-char *file_in_same_dir(const char *path, const char *filename) {
-        char *e, *ret;
-        size_t k;
+int file_in_same_dir(const char *path, const char *filename, char **ret) {
+        _cleanup_free_ char *b = NULL;
+        int r;
 
         assert(path);
         assert(filename);
+        assert(ret);
 
-        /* This removes the last component of path and appends
-         * filename, unless the latter is absolute anyway or the
-         * former isn't */
+        /* This removes the last component of path and appends filename, unless the latter is absolute anyway
+         * or the former isn't */
 
         if (path_is_absolute(filename))
-                return strdup(filename);
-
-        e = strrchr(path, '/');
-        if (!e)
-                return strdup(filename);
+                b = strdup(filename);
+        else {
+                _cleanup_free_ char *dn = NULL;
 
-        k = strlen(filename);
-        ret = new(char, (e + 1 - path) + k + 1);
-        if (!ret)
-                return NULL;
+                r = path_extract_directory(path, &dn);
+                if (r == -EDESTADDRREQ) /* no path prefix */
+                        b = strdup(filename);
+                else if (r < 0)
+                        return r;
+                else
+                        b = path_join(dn, filename);
+        }
+        if (!b)
+                return -ENOMEM;
 
-        memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1);
-        return ret;
+        *ret = TAKE_PTR(b);
+        return 0;
 }
 
 bool hidden_or_backup_file(const char *filename) {
diff --git a/src/libnm-systemd-shared/src/basic/path-util.h b/src/libnm-systemd-shared/src/basic/path-util.h
index 949fdc86..fee6e8ee 100644
--- a/src/libnm-systemd-shared/src/basic/path-util.h
+++ b/src/libnm-systemd-shared/src/basic/path-util.h
@@ -68,15 +68,18 @@ char *path_startswith_full(const char *path, const char *prefix, bool accept_dot
 static inline char* path_startswith(const char *path, const char *prefix) {
         return path_startswith_full(path, prefix, true);
 }
-int path_compare(const char *a, const char *b) _pure_;
 
+int path_compare(const char *a, const char *b) _pure_;
 static inline bool path_equal(const char *a, const char *b) {
         return path_compare(a, b) == 0;
 }
 
-bool path_equal_or_files_same(const char *a, const char *b, int flags);
-/* Compares only the last portion of the input paths, ie: the filenames */
-bool path_equal_filename(const char *a, const char *b);
+int path_compare_filename(const char *a, const char *b);
+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);
 
 char* path_extend_internal(char **x, ...);
 #define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX)
@@ -132,6 +135,7 @@ int fsck_exists_for_fstype(const char *fstype);
 
 /* Similar to path_join(), but only works for two components, and only the first one may be NULL and returns
  * an alloca() buffer, or possibly a const pointer into the path parameter. */
+/* DEPRECATED: use path_join() instead */
 #define prefix_roota(root, path)                                        \
         ({                                                              \
                 const char* _path = (path), *_root = (root), *_ret;     \
@@ -171,7 +175,7 @@ static inline bool path_is_safe(const char *p) {
 }
 bool path_is_normalized(const char *p) _pure_;
 
-char *file_in_same_dir(const char *path, const char *filename);
+int file_in_same_dir(const char *path, const char *filename, char **ret);
 
 bool hidden_or_backup_file(const char *filename) _pure_;
 
diff --git a/src/libnm-systemd-shared/src/basic/prioq.c b/src/libnm-systemd-shared/src/basic/prioq.c
index 2f8bdf0f..0af84bd2 100644
--- a/src/libnm-systemd-shared/src/basic/prioq.c
+++ b/src/libnm-systemd-shared/src/basic/prioq.c
@@ -255,7 +255,7 @@ int prioq_remove(Prioq *q, void *data, unsigned *idx) {
         return 1;
 }
 
-int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) {
+void prioq_reshuffle(Prioq *q, void *data, unsigned *idx) {
         struct prioq_item *i;
         unsigned k;
 
@@ -263,12 +263,11 @@ int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) {
 
         i = find_item(q, data, idx);
         if (!i)
-                return 0;
+                return;
 
         k = i - q->items;
         k = shuffle_down(q, k);
         shuffle_up(q, k);
-        return 1;
 }
 
 void *prioq_peek_by_index(Prioq *q, unsigned idx) {
diff --git a/src/libnm-systemd-shared/src/basic/prioq.h b/src/libnm-systemd-shared/src/basic/prioq.h
index 508db880..f66562f3 100644
--- a/src/libnm-systemd-shared/src/basic/prioq.h
+++ b/src/libnm-systemd-shared/src/basic/prioq.h
@@ -18,7 +18,7 @@ int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func);
 int prioq_put(Prioq *q, void *data, unsigned *idx);
 int prioq_ensure_put(Prioq **q, compare_func_t compare_func, void *data, unsigned *idx);
 int prioq_remove(Prioq *q, void *data, unsigned *idx);
-int prioq_reshuffle(Prioq *q, void *data, unsigned *idx);
+void prioq_reshuffle(Prioq *q, void *data, unsigned *idx);
 
 void *prioq_peek_by_index(Prioq *q, unsigned idx) _pure_;
 static inline void *prioq_peek(Prioq *q) {
diff --git a/src/libnm-systemd-shared/src/basic/process-util.c b/src/libnm-systemd-shared/src/basic/process-util.c
index 428323ef..8601e0da 100644
--- a/src/libnm-systemd-shared/src/basic/process-util.c
+++ b/src/libnm-systemd-shared/src/basic/process-util.c
@@ -10,7 +10,6 @@
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <sys/mman.h>
 #include <sys/mount.h>
 #include <sys/personality.h>
 #include <sys/prctl.h>
@@ -26,19 +25,26 @@
 
 #include "alloc-util.h"
 #include "architecture.h"
+#include "argv-util.h"
+#include "env-file.h"
 #include "env-util.h"
 #include "errno-util.h"
 #include "escape.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
+#include "hostname-util.h"
 #include "locale-util.h"
 #include "log.h"
 #include "macro.h"
 #include "memory-util.h"
 #include "missing_sched.h"
 #include "missing_syscall.h"
+#include "missing_threads.h"
+#include "mountpoint-util.h"
 #include "namespace-util.h"
+#include "nulstr-util.h"
+#include "parse-util.h"
 #include "path-util.h"
 #include "process-util.h"
 #include "raw-clone.h"
@@ -222,18 +228,12 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags
 
                 _cleanup_strv_free_ char **args = NULL;
 
-                args = strv_parse_nulstr(t, k);
+                /* Drop trailing NULs, otherwise strv_parse_nulstr() adds additional empty strings at the end.
+                 * See also issue #21186. */
+                args = strv_parse_nulstr_full(t, k, /* drop_trailing_nuls = */ true);
                 if (!args)
                         return -ENOMEM;
 
-                /* Drop trailing empty strings. See issue #21186. */
-                STRV_FOREACH_BACKWARDS(p, args) {
-                        if (!isempty(*p))
-                                break;
-
-                        *p = mfree(*p);
-                }
-
                 ans = quote_command_line(args, shflags);
                 if (!ans)
                         return -ENOMEM;
@@ -259,149 +259,67 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags
         return 0;
 }
 
-static int update_argv(const char name[], size_t l) {
-        static int can_do = -1;
-
-        if (can_do == 0)
-                return 0;
-        can_do = false; /* We'll set it to true only if the whole process works */
-
-        /* Let's not bother with this if we don't have euid == 0. Strictly speaking we should check for the
-         * CAP_SYS_RESOURCE capability which is independent of the euid. In our own code the capability generally is
-         * present only for euid == 0, hence let's use this as quick bypass check, to avoid calling mmap() if
-         * PR_SET_MM_ARG_{START,END} fails with EPERM later on anyway. After all geteuid() is dead cheap to call, but
-         * mmap() is not. */
-        if (geteuid() != 0)
-                return log_debug_errno(SYNTHETIC_ERRNO(EPERM),
-                                       "Skipping PR_SET_MM, as we don't have privileges.");
-
-        static size_t mm_size = 0;
-        static char *mm = NULL;
+int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret) {
+        _cleanup_free_ char *t = NULL;
+        char **args;
+        size_t k;
         int r;
 
-        if (mm_size < l+1) {
-                size_t nn_size;
-                char *nn;
-
-                nn_size = PAGE_ALIGN(l+1);
-                nn = mmap(NULL, nn_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
-                if (nn == MAP_FAILED)
-                        return log_debug_errno(errno, "mmap() failed: %m");
-
-                strncpy(nn, name, nn_size);
-
-                /* Now, let's tell the kernel about this new memory */
-                if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
-                        if (ERRNO_IS_PRIVILEGE(errno))
-                                return log_debug_errno(errno, "PR_SET_MM_ARG_START failed: %m");
-
-                        /* HACK: prctl() API is kind of dumb on this point.  The existing end address may already be
-                         * below the desired start address, in which case the kernel may have kicked this back due
-                         * to a range-check failure (see linux/kernel/sys.c:validate_prctl_map() to see this in
-                         * action).  The proper solution would be to have a prctl() API that could set both start+end
-                         * simultaneously, or at least let us query the existing address to anticipate this condition
-                         * and respond accordingly.  For now, we can only guess at the cause of this failure and try
-                         * a workaround--which will briefly expand the arg space to something potentially huge before
-                         * resizing it to what we want. */
-                        log_debug_errno(errno, "PR_SET_MM_ARG_START failed, attempting PR_SET_MM_ARG_END hack: %m");
-
-                        if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0) {
-                                r = log_debug_errno(errno, "PR_SET_MM_ARG_END hack failed, proceeding without: %m");
-                                (void) munmap(nn, nn_size);
-                                return r;
-                        }
-
-                        if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0)
-                                return log_debug_errno(errno, "PR_SET_MM_ARG_START still failed, proceeding without: %m");
-                } else {
-                        /* And update the end pointer to the new end, too. If this fails, we don't really know what
-                         * to do, it's pretty unlikely that we can rollback, hence we'll just accept the failure,
-                         * and continue. */
-                        if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0)
-                                log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
-                }
-
-                if (mm)
-                        (void) munmap(mm, mm_size);
+        assert(pid >= 0);
+        assert((flags & ~PROCESS_CMDLINE_COMM_FALLBACK) == 0);
+        assert(ret);
 
-                mm = nn;
-                mm_size = nn_size;
-        } else {
-                strncpy(mm, name, mm_size);
+        r = get_process_cmdline_nulstr(pid, SIZE_MAX, flags, &t, &k);
+        if (r < 0)
+                return r;
 
-                /* Update the end pointer, continuing regardless of any failure. */
-                if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) mm + l + 1, 0, 0) < 0)
-                        log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
-        }
+        args = strv_parse_nulstr_full(t, k, /* drop_trailing_nuls = */ true);
+        if (!args)
+                return -ENOMEM;
 
-        can_do = true;
+        *ret = args;
         return 0;
 }
 
-int rename_process(const char name[]) {
-        bool truncated = false;
-
-        /* This is a like a poor man's setproctitle(). It changes the comm field, argv[0], and also the glibc's
-         * internally used name of the process. For the first one a limit of 16 chars applies; to the second one in
-         * many cases one of 10 (i.e. length of "/sbin/init") — however if we have CAP_SYS_RESOURCES it is unbounded;
-         * to the third one 7 (i.e. the length of "systemd". If you pass a longer string it will likely be
-         * truncated.
-         *
-         * Returns 0 if a name was set but truncated, > 0 if it was set but not truncated. */
-
-        if (isempty(name))
-                return -EINVAL; /* let's not confuse users unnecessarily with an empty name */
-
-        if (!is_main_thread())
-                return -EPERM; /* Let's not allow setting the process name from other threads than the main one, as we
-                                * cache things without locking, and we make assumptions that PR_SET_NAME sets the
-                                * process name that isn't correct on any other threads */
-
-        size_t l = strlen(name);
-
-        /* First step, change the comm field. The main thread's comm is identical to the process comm. This means we
-         * can use PR_SET_NAME, which sets the thread name for the calling thread. */
-        if (prctl(PR_SET_NAME, name) < 0)
-                log_debug_errno(errno, "PR_SET_NAME failed: %m");
-        if (l >= TASK_COMM_LEN) /* Linux userspace process names can be 15 chars at max */
-                truncated = true;
+int container_get_leader(const char *machine, pid_t *pid) {
+        _cleanup_free_ char *s = NULL, *class = NULL;
+        const char *p;
+        pid_t leader;
+        int r;
 
-        /* Second step, change glibc's ID of the process name. */
-        if (program_invocation_name) {
-                size_t k;
+        assert(machine);
+        assert(pid);
 
-                k = strlen(program_invocation_name);
-                strncpy(program_invocation_name, name, k);
-                if (l > k)
-                        truncated = true;
+        if (streq(machine, ".host")) {
+                *pid = 1;
+                return 0;
         }
 
-        /* Third step, completely replace the argv[] array the kernel maintains for us. This requires privileges, but
-         * has the advantage that the argv[] array is exactly what we want it to be, and not filled up with zeros at
-         * the end. This is the best option for changing /proc/self/cmdline. */
-        (void) update_argv(name, l);
-
-        /* Fourth step: in all cases we'll also update the original argv[], so that our own code gets it right too if
-         * it still looks here */
-        if (saved_argc > 0) {
-                if (saved_argv[0]) {
-                        size_t k;
-
-                        k = strlen(saved_argv[0]);
-                        strncpy(saved_argv[0], name, k);
-                        if (l > k)
-                                truncated = true;
-                }
+        if (!hostname_is_valid(machine, 0))
+                return -EINVAL;
 
-                for (int i = 1; i < saved_argc; i++) {
-                        if (!saved_argv[i])
-                                break;
+        p = strjoina("/run/systemd/machines/", machine);
+        r = parse_env_file(NULL, p,
+                           "LEADER", &s,
+                           "CLASS", &class);
+        if (r == -ENOENT)
+                return -EHOSTDOWN;
+        if (r < 0)
+                return r;
+        if (!s)
+                return -EIO;
 
-                        memzero(saved_argv[i], strlen(saved_argv[i]));
-                }
-        }
+        if (!streq_ptr(class, "container"))
+                return -EIO;
 
-        return !truncated;
+        r = parse_pid(s, &leader);
+        if (r < 0)
+                return r;
+        if (leader <= 1)
+                return -EIO;
+
+        *pid = leader;
+        return 0;
 }
 
 int is_kernel_thread(pid_t pid) {
@@ -701,6 +619,8 @@ int get_process_umask(pid_t pid, mode_t *ret) {
         r = get_proc_field(p, "Umask", WHITESPACE, &m);
         if (r == -ENOENT)
                 return -ESRCH;
+        if (r < 0)
+                return r;
 
         return parse_mode(m, ret);
 }
@@ -870,6 +790,23 @@ void sigterm_wait(pid_t pid) {
         (void) wait_for_terminate(pid, NULL);
 }
 
+void sigkill_nowait(pid_t pid) {
+        assert(pid > 1);
+
+        (void) kill(pid, SIGKILL);
+}
+
+void sigkill_nowaitp(pid_t *pid) {
+        PROTECT_ERRNO;
+
+        if (!pid)
+                return;
+        if (*pid <= 1)
+                return;
+
+        sigkill_nowait(*pid);
+}
+
 int kill_and_sigcont(pid_t pid, int sig) {
         int r;
 
@@ -1015,7 +952,7 @@ int pid_from_same_root_fs(pid_t pid) {
 
         root = procfs_file_alloca(pid, "root");
 
-        return files_same(root, "/proc/1/root", 0);
+        return inode_same(root, "/proc/1/root", 0);
 }
 #endif /* NM_IGNORED */
 
@@ -1224,6 +1161,7 @@ static void restore_sigsetp(sigset_t **ssp) {
 
 int safe_fork_full(
                 const char *name,
+                const int stdio_fds[3],
                 const int except_fds[],
                 size_t n_except_fds,
                 ForkFlags flags,
@@ -1275,7 +1213,7 @@ int safe_fork_full(
         else
                 pid = fork();
         if (pid < 0)
-                return log_full_errno(prio, errno, "Failed to fork: %m");
+                return log_full_errno(prio, errno, "Failed to fork off '%s': %m", strna(name));
         if (pid > 0) {
                 /* We are in the parent process */
 
@@ -1311,6 +1249,7 @@ int safe_fork_full(
                 /* Close the logs if requested, before we log anything. And make sure we reopen it if needed. */
                 log_close();
                 log_set_open_when_needed(true);
+                log_settle_target();
         }
 
         if (name) {
@@ -1362,15 +1301,47 @@ int safe_fork_full(
         }
 
         if (FLAGS_SET(flags, FORK_NEW_MOUNTNS | FORK_MOUNTNS_SLAVE)) {
-
                 /* Optionally, make sure we never propagate mounts to the host. */
-
                 if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) {
                         log_full_errno(prio, errno, "Failed to remount root directory as MS_SLAVE: %m");
                         _exit(EXIT_FAILURE);
                 }
         }
 
+        if (FLAGS_SET(flags, FORK_PRIVATE_TMP)) {
+                assert(FLAGS_SET(flags, FORK_NEW_MOUNTNS));
+
+                /* Optionally, overmount new tmpfs instance on /tmp/. */
+                r = mount_nofollow("tmpfs", "/tmp", "tmpfs",
+                                   MS_NOSUID|MS_NODEV,
+                                   "mode=01777" TMPFS_LIMITS_RUN);
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to overmount /tmp/: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
+        if (flags & FORK_REARRANGE_STDIO) {
+                if (stdio_fds) {
+                        r = rearrange_stdio(stdio_fds[0], stdio_fds[1], stdio_fds[2]);
+                        if (r < 0) {
+                                log_full_errno(prio, r, "Failed to rearrange stdio fds: %m");
+                                _exit(EXIT_FAILURE);
+                        }
+                } else {
+                        r = make_null_stdio();
+                        if (r < 0) {
+                                log_full_errno(prio, r, "Failed to connect stdin/stdout to /dev/null: %m");
+                                _exit(EXIT_FAILURE);
+                        }
+                }
+        } else if (flags & FORK_STDOUT_TO_STDERR) {
+                if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0) {
+                        log_full_errno(prio, errno, "Failed to connect stdout to stderr: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
         if (flags & FORK_CLOSE_ALL_FDS) {
                 /* Close the logs here in case it got reopened above, as close_all_fds() would close them for us */
                 log_close();
@@ -1382,30 +1353,32 @@ int safe_fork_full(
                 }
         }
 
+        if (flags & FORK_CLOEXEC_OFF) {
+                r = fd_cloexec_many(except_fds, n_except_fds, false);
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to turn off O_CLOEXEC on file descriptors: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
         /* When we were asked to reopen the logs, do so again now */
         if (flags & FORK_REOPEN_LOG) {
                 log_open();
                 log_set_open_when_needed(false);
         }
 
-        if (flags & FORK_NULL_STDIO) {
-                r = make_null_stdio();
+        if (flags & FORK_RLIMIT_NOFILE_SAFE) {
+                r = rlimit_nofile_safe();
                 if (r < 0) {
-                        log_full_errno(prio, r, "Failed to connect stdin/stdout to /dev/null: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-        } else if (flags & FORK_STDOUT_TO_STDERR) {
-                if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0) {
-                        log_full_errno(prio, errno, "Failed to connect stdout to stderr: %m");
+                        log_full_errno(prio, r, "Failed to lower RLIMIT_NOFILE's soft limit to 1K: %m");
                         _exit(EXIT_FAILURE);
                 }
         }
 
-        if (flags & FORK_RLIMIT_NOFILE_SAFE) {
-                r = rlimit_nofile_safe();
+        if (!FLAGS_SET(flags, FORK_KEEP_NOTIFY_SOCKET)) {
+                r = RET_NERRNO(unsetenv("NOTIFY_SOCKET"));
                 if (r < 0) {
-                        log_full_errno(prio, r, "Failed to lower RLIMIT_NOFILE's soft limit to 1K: %m");
+                        log_full_errno(prio, r, "Failed to unset $NOTIFY_SOCKET: %m");
                         _exit(EXIT_FAILURE);
                 }
         }
@@ -1435,7 +1408,10 @@ int namespace_fork(
          * process. This ensures that we are fully a member of the destination namespace, with pidns an all, so that
          * /proc/self/fd works correctly. */
 
-        r = safe_fork_full(outer_name, except_fds, n_except_fds, (flags|FORK_DEATHSIG) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid);
+        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);
         if (r < 0)
                 return r;
         if (r == 0) {
@@ -1450,7 +1426,10 @@ int namespace_fork(
                 }
 
                 /* We mask a few flags here that either make no sense for the grandchild, or that we don't have to do again */
-                r = safe_fork_full(inner_name, except_fds, n_except_fds, flags & ~(FORK_WAIT|FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_NULL_STDIO), &pid);
+                r = safe_fork_full(inner_name,
+                                   NULL,
+                                   except_fds, n_except_fds,
+                                   flags & ~(FORK_WAIT|FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_REARRANGE_STDIO), &pid);
                 if (r < 0)
                         _exit(EXIT_FAILURE);
                 if (r == 0) {
@@ -1503,6 +1482,15 @@ int pidfd_get_pid(int fd, pid_t *ret) {
         char *p;
         int r;
 
+        /* Converts a pidfd into a pid. Well known errors:
+         *
+         *    -EBADF   → fd invalid
+         *    -ENOSYS  → /proc/ not mounted
+         *    -ENOTTY  → fd valid, but not a pidfd
+         *    -EREMOTE → fd valid, but pid is in another namespace we cannot translate to the local one
+         *    -ESRCH   → fd valid, but process is already reaped
+         */
+
         if (fd < 0)
                 return -EBADF;
 
@@ -1510,25 +1498,39 @@ int pidfd_get_pid(int fd, pid_t *ret) {
 
         r = read_full_virtual_file(path, &fdinfo, NULL);
         if (r == -ENOENT) /* if fdinfo doesn't exist we assume the process does not exist */
-                return -ESRCH;
+                return proc_mounted() > 0 ? -EBADF : -ENOSYS;
         if (r < 0)
                 return r;
 
-        p = startswith(fdinfo, "Pid:");
-        if (!p) {
-                p = strstr(fdinfo, "\nPid:");
-                if (!p)
-                        return -ENOTTY; /* not a pidfd? */
-
-                p += 5;
-        }
+        p = find_line_startswith(fdinfo, "Pid:");
+        if (!p)
+                return -ENOTTY; /* not a pidfd? */
 
         p += strspn(p, WHITESPACE);
         p[strcspn(p, WHITESPACE)] = 0;
 
+        if (streq(p, "0"))
+                return -EREMOTE; /* PID is in foreign PID namespace? */
+        if (streq(p, "-1"))
+                return -ESRCH;   /* refers to reaped process? */
+
         return parse_pid(p, ret);
 }
 
+int pidfd_verify_pid(int pidfd, pid_t pid) {
+        pid_t current_pid;
+        int r;
+
+        assert(pidfd >= 0);
+        assert(pid > 0);
+
+        r = pidfd_get_pid(pidfd, &current_pid);
+        if (r < 0)
+                return r;
+
+        return current_pid != pid ? -ESRCH : 0;
+}
+
 static int rlimit_to_nice(rlim_t limit) {
         if (limit <= 1)
                 return PRIO_MAX-1; /* i.e. 19 */
@@ -1585,40 +1587,6 @@ int setpriority_closest(int priority) {
         return 0;
 }
 
-bool invoked_as(char *argv[], const char *token) {
-        if (!argv || isempty(argv[0]))
-                return false;
-
-        if (isempty(token))
-                return false;
-
-        return strstr(last_path_component(argv[0]), token);
-}
-
-bool invoked_by_systemd(void) {
-        int r;
-
-        /* If the process is directly executed by PID1 (e.g. ExecStart= or generator), systemd-importd,
-         * or systemd-homed, then $SYSTEMD_EXEC_PID= is set, and read the command line. */
-        const char *e = getenv("SYSTEMD_EXEC_PID");
-        if (!e)
-                return false;
-
-        if (streq(e, "*"))
-                /* For testing. */
-                return true;
-
-        pid_t p;
-        r = parse_pid(e, &p);
-        if (r < 0) {
-                /* We know that systemd sets the variable correctly. Something else must have set it. */
-                log_debug_errno(r, "Failed to parse \"SYSTEMD_EXEC_PID=%s\", ignoring: %m", e);
-                return false;
-        }
-
-        return getpid_cached() == p;
-}
-
 _noreturn_ void freeze(void) {
         log_close();
 
@@ -1640,29 +1608,29 @@ _noreturn_ void freeze(void) {
                 pause();
 }
 
-bool argv_looks_like_help(int argc, char **argv) {
-        char **l;
+int get_process_threads(pid_t pid) {
+        _cleanup_free_ char *t = NULL;
+        const char *p;
+        int n, r;
 
-        /* Scans the command line for indications the user asks for help. This is supposed to be called by
-         * tools that do not implement getopt() style command line parsing because they are not primarily
-         * user-facing. Detects four ways of asking for help:
-         *
-         * 1. Passing zero arguments
-         * 2. Passing "help" as first argument
-         * 3. Passing --help as any argument
-         * 4. Passing -h as any argument
-         */
+        if (pid < 0)
+                return -EINVAL;
 
-        if (argc <= 1)
-                return true;
+        p = procfs_file_alloca(pid, "status");
 
-        if (streq_ptr(argv[1], "help"))
-                return true;
+        r = get_proc_field(p, "Threads", WHITESPACE, &t);
+        if (r == -ENOENT)
+                return proc_mounted() == 0 ? -ENOSYS : -ESRCH;
+        if (r < 0)
+                return r;
 
-        l = strv_skip(argv, 1);
+        r = safe_atoi(t, &n);
+        if (r < 0)
+                return r;
+        if (n < 0)
+                return -EINVAL;
 
-        return strv_contains(l, "--help") ||
-                strv_contains(l, "-h");
+        return n;
 }
 
 static const char *const sigchld_code_table[] = {
diff --git a/src/libnm-systemd-shared/src/basic/process-util.h b/src/libnm-systemd-shared/src/basic/process-util.h
index 962f12d1..5cf5c7c6 100644
--- a/src/libnm-systemd-shared/src/basic/process-util.h
+++ b/src/libnm-systemd-shared/src/basic/process-util.h
@@ -40,6 +40,7 @@ typedef enum 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 get_process_exe(pid_t pid, char **ret);
 int get_process_uid(pid_t pid, uid_t *ret);
 int get_process_gid(pid_t pid, gid_t *ret);
@@ -50,6 +51,8 @@ int get_process_environ(pid_t pid, char **ret);
 int get_process_ppid(pid_t pid, pid_t *ret);
 int get_process_umask(pid_t pid, mode_t *ret);
 
+int container_get_leader(const char *machine, pid_t *pid);
+
 int wait_for_terminate(pid_t pid, siginfo_t *status);
 
 typedef enum WaitFlags {
@@ -66,10 +69,11 @@ int wait_for_terminate_with_timeout(pid_t pid, usec_t timeout);
 void sigkill_wait(pid_t pid);
 void sigkill_waitp(pid_t *pid);
 void sigterm_wait(pid_t pid);
+void sigkill_nowait(pid_t pid);
+void sigkill_nowaitp(pid_t *pid);
 
 int kill_and_sigcont(pid_t pid, int sig);
 
-int rename_process(const char name[]);
 int is_kernel_thread(pid_t pid);
 
 int getenv_for_pid(pid_t pid, const char *field, char **_value);
@@ -142,22 +146,31 @@ typedef enum ForkFlags {
         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_SIGINT    = 1 <<  3, /* Set PR_DEATHSIG in the child to SIGINT */
-        FORK_NULL_STDIO         = 1 <<  4, /* Connect 0,1,2 to /dev/null */
+        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_RLIMIT_NOFILE_SAFE = 1 << 10, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */
-        FORK_STDOUT_TO_STDERR   = 1 << 11, /* Make stdout a copy of stderr */
-        FORK_FLUSH_STDIO        = 1 << 12, /* fflush() stdout (and stderr) before forking */
-        FORK_NEW_USERNS         = 1 << 13, /* Run child in its own user namespace */
+        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. */
 } ForkFlags;
 
-int safe_fork_full(const char *name, const int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid);
+int safe_fork_full(
+                const char *name,
+                const int stdio_fds[3],
+                const int except_fds[],
+                size_t n_except_fds,
+                ForkFlags flags,
+                pid_t *ret_pid);
 
 static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) {
-        return safe_fork_full(name, NULL, 0, flags, ret_pid);
+        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);
@@ -177,23 +190,14 @@ int get_oom_score_adjust(int *ret);
 
 assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX);
 
-/* Like TAKE_PTR() but for child PIDs, resetting them to 0 */
-#define TAKE_PID(pid)                           \
-        ({                                      \
-                pid_t *_ppid_ = &(pid);         \
-                pid_t _pid_ = *_ppid_;          \
-                *_ppid_ = 0;                    \
-                _pid_;                          \
-        })
+/* Like TAKE_PTR() but for pid_t, resetting them to 0 */
+#define TAKE_PID(pid) TAKE_GENERIC(pid, pid_t, 0)
 
 int pidfd_get_pid(int fd, pid_t *ret);
+int pidfd_verify_pid(int pidfd, pid_t pid);
 
 int setpriority_closest(int priority);
 
-bool invoked_as(char *argv[], const char *token);
-
-bool invoked_by_systemd(void);
-
 _noreturn_ void freeze(void);
 
-bool argv_looks_like_help(int argc, char **argv);
+int get_process_threads(pid_t pid);
diff --git a/src/libnm-systemd-shared/src/basic/random-util.c b/src/libnm-systemd-shared/src/basic/random-util.c
index 9e1a746f..934d5e25 100644
--- a/src/libnm-systemd-shared/src/basic/random-util.c
+++ b/src/libnm-systemd-shared/src/basic/random-util.c
@@ -26,6 +26,7 @@
 #include "io-util.h"
 #include "missing_random.h"
 #include "missing_syscall.h"
+#include "missing_threads.h"
 #include "parse-util.h"
 #include "random-util.h"
 #include "sha256.h"
@@ -77,7 +78,7 @@ static void fallback_random_bytes(void *p, size_t n) {
 
 void random_bytes(void *p, size_t n) {
         static bool have_getrandom = true, have_grndinsecure = true;
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         if (n == 0)
                 return;
@@ -119,7 +120,7 @@ void random_bytes(void *p, size_t n) {
 
 int crypto_random_bytes(void *p, size_t n) {
         static bool have_getrandom = true, seen_initialized = false;
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         if (n == 0)
                 return 0;
@@ -147,7 +148,7 @@ int crypto_random_bytes(void *p, size_t n) {
         }
 
         if (!seen_initialized) {
-                _cleanup_close_ int ready_fd = -1;
+                _cleanup_close_ int ready_fd = -EBADF;
                 int r;
 
                 ready_fd = open("/dev/random", O_RDONLY|O_CLOEXEC|O_NOCTTY);
@@ -190,7 +191,7 @@ size_t random_pool_size(void) {
 }
 
 int random_write_entropy(int fd, const void *seed, size_t size, bool credit) {
-        _cleanup_close_ int opened_fd = -1;
+        _cleanup_close_ int opened_fd = -EBADF;
         int r;
 
         assert(seed || size == 0);
diff --git a/src/libnm-systemd-shared/src/basic/random-util.h b/src/libnm-systemd-shared/src/basic/random-util.h
index 2d998072..b1a4d109 100644
--- a/src/libnm-systemd-shared/src/basic/random-util.h
+++ b/src/libnm-systemd-shared/src/basic/random-util.h
@@ -23,6 +23,7 @@ static inline uint32_t random_u32(void) {
 /* Some limits on the pool sizes when we deal with the kernel random pool */
 #define RANDOM_POOL_SIZE_MIN 32U
 #define RANDOM_POOL_SIZE_MAX (10U*1024U*1024U)
+#define RANDOM_EFI_SEED_SIZE 32U
 
 size_t random_pool_size(void);
 
diff --git a/src/libnm-systemd-shared/src/basic/ratelimit.c b/src/libnm-systemd-shared/src/basic/ratelimit.c
index 3ccec119..a28c8122 100644
--- a/src/libnm-systemd-shared/src/basic/ratelimit.c
+++ b/src/libnm-systemd-shared/src/basic/ratelimit.c
@@ -22,26 +22,41 @@ bool ratelimit_below(RateLimit *r) {
 
         if (r->begin <= 0 ||
             usec_sub_unsigned(ts, r->begin) > r->interval) {
-                r->begin = ts;
-
-                /* Reset counter */
-                r->num = 0;
-                goto good;
+                r->begin = ts;  /* Start a new time window */
+                r->num = 1;     /* Reset counter */
+                return true;
         }
 
-        if (r->num < r->burst)
-                goto good;
-
-        r->num++;
-        return false;
+        if (_unlikely_(r->num == UINT_MAX))
+                return false;
 
-good:
         r->num++;
-        return true;
+        return r->num <= r->burst;
 }
 
 unsigned ratelimit_num_dropped(RateLimit *r) {
         assert(r);
 
-        return r->num > r->burst ? r->num - r->burst : 0;
+        if (r->num == UINT_MAX) /* overflow, return as special case */
+                return UINT_MAX;
+
+        return LESS_BY(r->num, r->burst);
+}
+
+usec_t ratelimit_end(const RateLimit *rl) {
+        assert(rl);
+
+        if (rl->begin == 0)
+                return 0;
+
+        return usec_add(rl->begin, rl->interval);
+}
+
+usec_t ratelimit_left(const RateLimit *rl) {
+        assert(rl);
+
+        if (rl->begin == 0)
+                return 0;
+
+        return usec_sub_unsigned(ratelimit_end(rl), now(CLOCK_MONOTONIC));
 }
diff --git a/src/libnm-systemd-shared/src/basic/ratelimit.h b/src/libnm-systemd-shared/src/basic/ratelimit.h
index 22361898..bb7160a8 100644
--- a/src/libnm-systemd-shared/src/basic/ratelimit.h
+++ b/src/libnm-systemd-shared/src/basic/ratelimit.h
@@ -23,3 +23,6 @@ static inline bool ratelimit_configured(RateLimit *rl) {
 bool ratelimit_below(RateLimit *r);
 
 unsigned ratelimit_num_dropped(RateLimit *r);
+
+usec_t ratelimit_end(const RateLimit *rl);
+usec_t ratelimit_left(const RateLimit *rl);
diff --git a/src/libnm-systemd-shared/src/basic/signal-util.c b/src/libnm-systemd-shared/src/basic/signal-util.c
index 6d8c9920..270d397d 100644
--- a/src/libnm-systemd-shared/src/basic/signal-util.c
+++ b/src/libnm-systemd-shared/src/basic/signal-util.c
@@ -7,6 +7,8 @@
 
 #include "errno-util.h"
 #include "macro.h"
+#include "missing_syscall.h"
+#include "missing_threads.h"
 #include "parse-util.h"
 #include "signal-util.h"
 #include "stdio-util.h"
@@ -285,4 +287,21 @@ int pop_pending_signal_internal(int sig, ...) {
 
         return r; /* Returns the signal popped */
 }
+
+void propagate_signal(int sig, siginfo_t *siginfo) {
+        pid_t p;
+
+        /* To be called from a signal handler. Will raise the same signal again, in our process + in our threads.
+         *
+         * Note that we use raw_getpid() instead of getpid_cached(). We might have forked with raw_clone()
+         * earlier (see PID 1), and hence let's go to the raw syscall here. In particular as this is not
+         * performance sensitive code.
+         *
+         * Note that we use kill() rather than raise() as fallback, for similar reasons. */
+
+        p = raw_getpid();
+
+        if (rt_tgsigqueueinfo(p, gettid(), sig, siginfo) < 0)
+                assert_se(kill(p, sig) >= 0);
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/signal-util.h b/src/libnm-systemd-shared/src/basic/signal-util.h
index 36372c19..ad2ba841 100644
--- a/src/libnm-systemd-shared/src/basic/signal-util.h
+++ b/src/libnm-systemd-shared/src/basic/signal-util.h
@@ -65,3 +65,5 @@ int signal_is_blocked(int sig);
 
 int pop_pending_signal_internal(int sig, ...);
 #define pop_pending_signal(...) pop_pending_signal_internal(__VA_ARGS__, -1)
+
+void propagate_signal(int sig, siginfo_t *siginfo);
diff --git a/src/libnm-systemd-shared/src/basic/socket-util.c b/src/libnm-systemd-shared/src/basic/socket-util.c
index c155e352..9b411e07 100644
--- a/src/libnm-systemd-shared/src/basic/socket-util.c
+++ b/src/libnm-systemd-shared/src/basic/socket-util.c
@@ -228,7 +228,7 @@ bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) {
                         return false;
 
                 if (a->sockaddr.un.sun_path[0]) {
-                        if (!path_equal_or_files_same(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, 0))
+                        if (!path_equal_or_inode_same(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, 0))
                                 return false;
                 } else {
                         if (a->size != b->size)
@@ -1054,9 +1054,9 @@ ssize_t receive_one_fd_iov(
         }
 
         if (found)
-                *ret_fd = *(int*) CMSG_DATA(found);
+                *ret_fd = *CMSG_TYPED_DATA(found, int);
         else
-                *ret_fd = -1;
+                *ret_fd = -EBADF;
 
         return k;
 }
@@ -1181,6 +1181,24 @@ struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t leng
         return NULL;
 }
 
+void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, size_t buf_len) {
+        struct cmsghdr *cmsg;
+
+        assert(mh);
+        assert(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
+         * RISCV32. See issue #27241. */
+
+        cmsg = cmsg_find(mh, level, type, CMSG_LEN(buf_len));
+        if (!cmsg)
+                return NULL;
+
+        return memcpy_safe(buf, CMSG_DATA(cmsg), buf_len);
+}
+
 #if 0 /* NM_IGNORED */
 int socket_ioctl_fd(void) {
         int fd;
@@ -1320,7 +1338,7 @@ ssize_t recvmsg_safe(int sockfd, struct msghdr *msg, int flags) {
 }
 
 #if 0 /* NM_IGNORED */
-int socket_get_family(int fd, int *ret) {
+int socket_get_family(int fd) {
         int af;
         socklen_t sl = sizeof(af);
 
@@ -1334,12 +1352,11 @@ int socket_get_family(int fd, int *ret) {
 }
 
 int socket_set_recvpktinfo(int fd, int af, bool b) {
-        int r;
 
         if (af == AF_UNSPEC) {
-                r = socket_get_family(fd, &af);
-                if (r < 0)
-                        return r;
+                af = socket_get_family(fd);
+                if (af < 0)
+                        return af;
         }
 
         switch (af) {
@@ -1363,12 +1380,11 @@ int socket_set_recvpktinfo(int fd, int af, bool b) {
 
 int socket_set_unicast_if(int fd, int af, int ifi) {
         be32_t ifindex_be = htobe32(ifi);
-        int r;
 
         if (af == AF_UNSPEC) {
-                r = socket_get_family(fd, &af);
-                if (r < 0)
-                        return r;
+                af = socket_get_family(fd);
+                if (af < 0)
+                        return af;
         }
 
         switch (af) {
@@ -1385,12 +1401,10 @@ int socket_set_unicast_if(int fd, int af, int ifi) {
 }
 
 int socket_set_option(int fd, int af, int opt_ipv4, int opt_ipv6, int val) {
-        int r;
-
         if (af == AF_UNSPEC) {
-                r = socket_get_family(fd, &af);
-                if (r < 0)
-                        return r;
+                af = socket_get_family(fd);
+                if (af < 0)
+                        return af;
         }
 
         switch (af) {
@@ -1410,9 +1424,9 @@ int socket_get_mtu(int fd, int af, size_t *ret) {
         int mtu, r;
 
         if (af == AF_UNSPEC) {
-                r = socket_get_family(fd, &af);
-                if (r < 0)
-                        return r;
+                af = socket_get_family(fd);
+                if (af < 0)
+                        return af;
         }
 
         switch (af) {
@@ -1439,50 +1453,136 @@ int socket_get_mtu(int fd, int af, size_t *ret) {
 }
 #endif /* NM_IGNORED */
 
-int connect_unix_path(int fd, int dir_fd, const char *path) {
-        _cleanup_close_ int inode_fd = -1;
+static int connect_unix_path_simple(int fd, const char *path) {
         union sockaddr_union sa = {
                 .un.sun_family = AF_UNIX,
         };
-        size_t path_len;
-        socklen_t salen;
+        size_t l;
 
         assert(fd >= 0);
-        assert(dir_fd == AT_FDCWD || dir_fd >= 0);
         assert(path);
 
+        l = strlen(path);
+        assert(l > 0);
+        assert(l < sizeof(sa.un.sun_path));
+
+        memcpy(sa.un.sun_path, path, l + 1);
+        return RET_NERRNO(connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + l + 1));
+}
+
+static int connect_unix_inode(int fd, int inode_fd) {
+        assert(fd >= 0);
+        assert(inode_fd >= 0);
+
+        return connect_unix_path_simple(fd, FORMAT_PROC_FD_PATH(inode_fd));
+}
+
+int connect_unix_path(int fd, int dir_fd, const char *path) {
+        _cleanup_close_ int inode_fd = -EBADF;
+
+        assert(fd >= 0);
+        assert(dir_fd == AT_FDCWD || dir_fd >= 0);
+
         /* Connects to the specified AF_UNIX socket in the file system. Works around the 108 byte size limit
          * in sockaddr_un, by going via O_PATH if needed. This hence works for any kind of path. */
 
-        path_len = strlen(path);
+        if (!path)
+                return connect_unix_inode(fd, dir_fd); /* If no path is specified, then dir_fd refers to the socket inode to connect to. */
 
         /* Refuse zero length path early, to make sure AF_UNIX stack won't mistake this for an abstract
          * namespace path, since first char is NUL */
-        if (path_len <= 0)
+        if (isempty(path))
                 return -EINVAL;
 
-        if (dir_fd == AT_FDCWD && path_len < sizeof(sa.un.sun_path)) {
-                memcpy(sa.un.sun_path, path, path_len + 1);
-                salen = offsetof(struct sockaddr_un, sun_path) + path_len + 1;
-        } else {
-                const char *proc;
-                size_t proc_len;
+        /* Shortcut for the simple case */
+        if (dir_fd == AT_FDCWD && strlen(path) < sizeof_field(struct sockaddr_un, sun_path))
+                return connect_unix_path_simple(fd, path);
 
-                /* If dir_fd is specified, then we need to go the indirect O_PATH route, because connectat()
-                 * does not exist. If the path is too long, we also need to take the indirect route, since we
-                 * can't fit this into a sockaddr_un directly. */
+        /* If dir_fd is specified, then we need to go the indirect O_PATH route, because connectat() does not
+         * exist. If the path is too long, we also need to take the indirect route, since we can't fit this
+         * into a sockaddr_un directly. */
 
-                inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC);
-                if (inode_fd < 0)
-                        return -errno;
+        inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC);
+        if (inode_fd < 0)
+                return -errno;
+
+        return connect_unix_inode(fd, inode_fd);
+}
+
+int socket_address_parse_unix(SocketAddress *ret_address, const char *s) {
+        struct sockaddr_un un;
+        int r;
+
+        assert(ret_address);
+        assert(s);
+
+        if (!IN_SET(*s, '/', '@'))
+                return -EPROTO;
+
+        r = sockaddr_un_set_path(&un, s);
+        if (r < 0)
+                return r;
+
+        *ret_address = (SocketAddress) {
+                .sockaddr.un = un,
+                .size = r,
+        };
 
-                proc = FORMAT_PROC_FD_PATH(inode_fd);
-                proc_len = strlen(proc);
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) {
+        /* AF_VSOCK socket in vsock:cid:port notation */
+        _cleanup_free_ char *n = NULL;
+        char *e, *cid_start;
+        unsigned port, cid;
+        int type, r;
+
+        assert(ret_address);
+        assert(s);
+
+        if ((cid_start = startswith(s, "vsock:")))
+                type = 0;
+        else if ((cid_start = startswith(s, "vsock-dgram:")))
+                type = SOCK_DGRAM;
+        else if ((cid_start = startswith(s, "vsock-seqpacket:")))
+                type = SOCK_SEQPACKET;
+        else if ((cid_start = startswith(s, "vsock-stream:")))
+                type = SOCK_STREAM;
+        else
+                return -EPROTO;
 
-                assert(proc_len < sizeof(sa.un.sun_path));
-                memcpy(sa.un.sun_path, proc, proc_len + 1);
-                salen = offsetof(struct sockaddr_un, sun_path) + proc_len + 1;
+        e = strchr(cid_start, ':');
+        if (!e)
+                return -EINVAL;
+
+        r = safe_atou(e+1, &port);
+        if (r < 0)
+                return r;
+
+        n = strndup(cid_start, e - cid_start);
+        if (!n)
+                return -ENOMEM;
+
+        if (isempty(n))
+                cid = VMADDR_CID_ANY;
+        else {
+                r = safe_atou(n, &cid);
+                if (r < 0)
+                        return r;
         }
 
-        return RET_NERRNO(connect(fd, &sa.sa, salen));
+        *ret_address = (SocketAddress) {
+                .sockaddr.vm = {
+                        .svm_cid = cid,
+                        .svm_family = AF_VSOCK,
+                        .svm_port = port,
+                },
+                .type = type,
+                .size = sizeof(struct sockaddr_vm),
+        };
+
+        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 cf9a0b35..26f9636f 100644
--- a/src/libnm-systemd-shared/src/basic/socket-util.h
+++ b/src/libnm-systemd-shared/src/basic/socket-util.h
@@ -177,15 +177,29 @@ int flush_accept(int fd);
 #define CMSG_FOREACH(cmsg, mh)                                          \
         for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg)))
 
+/* Returns the cmsghdr's data pointer, but safely cast to the specified type. Does two alignment checks: one
+ * 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
+ * riscv32. */
+#define CMSG_TYPED_DATA(cmsg, type)                                     \
+        ({                                                              \
+                struct cmsghdr *_cmsg = (cmsg);                         \
+                assert_cc(alignof(type) <= alignof(struct cmsghdr));    \
+                _cmsg ? CAST_ALIGN_PTR(type, CMSG_DATA(_cmsg)) : (type*) NULL; \
+        })
+
 struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length);
+void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, size_t buf_len);
 
 /* Type-safe, dereferencing version of cmsg_find() */
-#define CMSG_FIND_DATA(mh, level, type, ctype) \
-        ({                                                            \
-                struct cmsghdr *_found;                               \
-                _found = cmsg_find(mh, level, type, CMSG_LEN(sizeof(ctype))); \
-                (ctype*) (_found ? CMSG_DATA(_found) : NULL);         \
-        })
+#define CMSG_FIND_DATA(mh, level, type, ctype)                          \
+        CMSG_TYPED_DATA(cmsg_find(mh, level, type, CMSG_LEN(sizeof(ctype))), ctype)
+
+/* Type-safe version of cmsg_find_and_copy_data() */
+#define CMSG_FIND_AND_COPY_DATA(mh, level, type, ctype)             \
+        (ctype*) cmsg_find_and_copy_data(mh, level, type, &(ctype){}, sizeof(ctype))
 
 /* Resolves to a type that can carry cmsghdr structures. Make sure things are properly aligned, i.e. the type
  * itself is placed properly in memory and the size is also aligned to what's appropriate for "cmsghdr"
@@ -306,7 +320,7 @@ struct timespec_large {
 
 ssize_t recvmsg_safe(int sockfd, struct msghdr *msg, int flags);
 
-int socket_get_family(int fd, int *ret);
+int socket_get_family(int fd);
 int socket_set_recvpktinfo(int fd, int af, bool b);
 int socket_set_unicast_if(int fd, int af, int ifi);
 
@@ -338,3 +352,16 @@ int socket_get_mtu(int fd, int af, size_t *ret);
 #define UCRED_INVALID { .pid = 0, .uid = UID_INVALID, .gid = GID_INVALID }
 
 int connect_unix_path(int fd, int dir_fd, const char *path);
+
+/* 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. */
+int socket_address_parse_unix(SocketAddress *ret_address, const char *s);
+int socket_address_parse_vsock(SocketAddress *ret_address, const char *s);
+
+/* libc's SOMAXCONN is defined to 128 or 4096 (at least on glibc). But actually, the value can be much
+ * larger. In our codebase we want to set it to the max usually, since noawadays socket memory is properly
+ * tracked by memcg, and hence we don't need to enforce extra limits here. Moreover, the kernel caps it to
+ * /proc/sys/net/core/somaxconn anyway, thus by setting this to unbounded we just make that sysctl file
+ * authoritative. */
+#define SOMAXCONN_DELUXE INT_MAX
diff --git a/src/libnm-systemd-shared/src/basic/stat-util.c b/src/libnm-systemd-shared/src/basic/stat-util.c
index 5a142263..a81ee468 100644
--- a/src/libnm-systemd-shared/src/basic/stat-util.c
+++ b/src/libnm-systemd-shared/src/basic/stat-util.c
@@ -10,13 +10,14 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "dirent-util.h"
 #include "errno-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "filesystems.h"
 #include "fs-util.h"
+#include "hash-funcs.h"
 #include "macro.h"
 #include "missing_fs.h"
 #include "missing_magic.h"
@@ -69,7 +70,7 @@ int is_device_node(const char *path) {
 }
 
 int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) {
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         struct dirent *buf;
         size_t m;
 
@@ -149,56 +150,58 @@ int null_or_empty_path_with_root(const char *fn, const char *root) {
         if (path_equal_ptr(path_startswith(fn, root ?: "/"), "dev/null"))
                 return true;
 
-        r = chase_symlinks_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st, NULL);
+        r = chase_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st);
         if (r < 0)
                 return r;
 
         return null_or_empty(&st);
 }
 
-int null_or_empty_fd(int fd) {
-        struct stat st;
+static int fd_is_read_only_fs(int fd) {
+        struct statvfs st;
 
         assert(fd >= 0);
 
-        if (fstat(fd, &st) < 0)
+        if (fstatvfs(fd, &st) < 0)
                 return -errno;
 
-        return null_or_empty(&st);
+        if (st.f_flag & ST_RDONLY)
+                return true;
+
+        /* On NFS, fstatvfs() might not reflect whether we can actually write to the remote share. Let's try
+         * again with access(W_OK) which is more reliable, at least sometimes. */
+        if (access_fd(fd, W_OK) == -EROFS)
+                return true;
+
+        return false;
 }
 
 int path_is_read_only_fs(const char *path) {
-        struct statvfs st;
+        _cleanup_close_ int fd = -EBADF;
 
         assert(path);
 
-        if (statvfs(path, &st) < 0)
+        fd = open(path, O_CLOEXEC | O_PATH);
+        if (fd < 0)
                 return -errno;
 
-        if (st.f_flag & ST_RDONLY)
-                return true;
-
-        /* On NFS, statvfs() might not reflect whether we can actually
-         * write to the remote share. Let's try again with
-         * access(W_OK) which is more reliable, at least sometimes. */
-        if (access(path, W_OK) < 0 && errno == EROFS)
-                return true;
-
-        return false;
+        return fd_is_read_only_fs(fd);
 }
 #endif /* NM_IGNORED */
 
-int files_same(const char *filea, const char *fileb, int flags) {
+int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int flags) {
         struct stat a, b;
 
+        assert(fda >= 0 || fda == AT_FDCWD);
         assert(filea);
+        assert(fdb >= 0 || fdb == AT_FDCWD);
         assert(fileb);
 
-        if (fstatat(AT_FDCWD, filea, &a, flags) < 0)
-                return -errno;
+        if (fstatat(fda, filea, &a, flags) < 0)
+                return log_debug_errno(errno, "Cannot stat %s: %m", filea);
 
-        if (fstatat(AT_FDCWD, fileb, &b, flags) < 0)
-                return -errno;
+        if (fstatat(fdb, fileb, &b, flags) < 0)
+                return log_debug_errno(errno, "Cannot stat %s: %m", fileb);
 
         return stat_inode_same(&a, &b);
 }
@@ -210,22 +213,13 @@ bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) {
         return F_TYPE_EQUAL(s->f_type, magic_value);
 }
 
-#if 0 /* NM_IGNORED */
-int fd_is_fs_type(int fd, statfs_f_type_t magic_value) {
-        struct statfs s;
-
-        if (fstatfs(fd, &s) < 0)
-                return -errno;
-
-        return is_fs_type(&s, magic_value);
-}
-#endif /* NM_IGNORED */
-
-int path_is_fs_type(const char *path, statfs_f_type_t magic_value) {
+int is_fs_type_at(int dir_fd, const char *path, statfs_f_type_t magic_value) {
         struct statfs s;
+        int r;
 
-        if (statfs(path, &s) < 0)
-                return -errno;
+        r = xstatfsat(dir_fd, path, &s);
+        if (r < 0)
+                return r;
 
         return is_fs_type(&s, magic_value);
 }
@@ -306,6 +300,18 @@ int fd_verify_regular(int fd) {
 }
 
 #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);
 
@@ -455,3 +461,58 @@ int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct s
         return 0;
 }
 #endif /* NM_IGNORED */
+
+int xstatfsat(int dir_fd, const char *path, struct statfs *ret) {
+        _cleanup_close_ int fd = -EBADF;
+
+        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);
+        if (fd < 0)
+                return fd;
+
+        return RET_NERRNO(fstatfs(fd, 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);
+}
+
+int inode_compare_func(const struct stat *a, const struct stat *b) {
+        int r;
+
+        r = CMP(a->st_dev, b->st_dev);
+        if (r != 0)
+                return r;
+
+        return CMP(a->st_ino, b->st_ino);
+}
+
+DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free);
+
+const char* inode_type_to_string(mode_t m) {
+
+        /* Returns a short string for the inode type. We use the same name as the underlying macros for each
+         * inode type. */
+
+        switch (m & S_IFMT) {
+        case S_IFREG:
+                return "reg";
+        case S_IFDIR:
+                return "dir";
+        case S_IFCHR:
+                return "chr";
+        case S_IFBLK:
+                return "blk";
+        case S_IFIFO:
+                return "fifo";
+        case S_IFSOCK:
+                return "sock";
+        }
+
+        return NULL;
+}
+#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 f9519d8c..ae0aaf8f 100644
--- a/src/libnm-systemd-shared/src/basic/stat-util.h
+++ b/src/libnm-systemd-shared/src/basic/stat-util.h
@@ -11,6 +11,7 @@
 
 #include "macro.h"
 #include "missing_stat.h"
+#include "siphash24.h"
 
 int is_symlink(const char *path);
 int is_dir_full(int atfd, const char *fname, bool follow);
@@ -29,7 +30,6 @@ static inline int dir_is_empty(const char *path, bool ignore_hidden_or_backup) {
 
 bool null_or_empty(struct stat *st) _pure_;
 int null_or_empty_path_with_root(const char *fn, const char *root);
-int null_or_empty_fd(int fd);
 
 static inline int null_or_empty_path(const char *fn) {
         return null_or_empty_path_with_root(fn, NULL);
@@ -37,15 +37,24 @@ static inline int null_or_empty_path(const char *fn) {
 
 int path_is_read_only_fs(const char *path);
 
-int files_same(const char *filea, const char *fileb, int flags);
+int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int flags);
+
+static inline int inode_same(const char *filea, const char *fileb, int flags) {
+        return inode_same_at(AT_FDCWD, filea, AT_FDCWD, fileb, flags);
+}
 
 /* The .f_type field of struct statfs is really weird defined on
  * different archs. Let's give its type a name. */
 typedef typeof(((struct statfs*)NULL)->f_type) statfs_f_type_t;
 
 bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) _pure_;
-int fd_is_fs_type(int fd, statfs_f_type_t magic_value);
-int path_is_fs_type(const char *path, statfs_f_type_t magic_value);
+int is_fs_type_at(int dir_fd, const char *path, statfs_f_type_t magic_value);
+static inline int fd_is_fs_type(int fd, statfs_f_type_t magic_value) {
+        return is_fs_type_at(fd, NULL, magic_value);
+}
+static inline int path_is_fs_type(const char *path, statfs_f_type_t magic_value) {
+        return is_fs_type_at(AT_FDCWD, path, magic_value);
+}
 
 bool is_temporary_fs(const struct statfs *s) _pure_;
 bool is_network_fs(const struct statfs *s) _pure_;
@@ -64,6 +73,7 @@ int path_is_network_fs(const char *path);
 
 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);
@@ -78,6 +88,8 @@ bool statx_mount_same(const struct new_statx *a, const struct new_statx *b);
 
 int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct statx *sx);
 
+int xstatfsat(int dir_fd, const char *path, struct statfs *ret);
+
 #if HAS_FEATURE_MEMORY_SANITIZER
 #  warning "Explicitly initializing struct statx, to work around msan limitation. Please remove as soon as msan has been updated to not require this."
 #  define STRUCT_STATX_DEFINE(var)              \
@@ -96,3 +108,9 @@ int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct s
                 struct new_statx nsx;           \
         } var
 #endif
+
+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);
diff --git a/src/libnm-systemd-shared/src/basic/stdio-util.h b/src/libnm-systemd-shared/src/basic/stdio-util.h
index 07cbfe9d..d0b25fdf 100644
--- a/src/libnm-systemd-shared/src/basic/stdio-util.h
+++ b/src/libnm-systemd-shared/src/basic/stdio-util.h
@@ -9,15 +9,20 @@
 #include <sys/types.h>
 
 #include "macro.h"
-#include "memory-util.h"
 
-#define snprintf_ok(buf, len, fmt, ...)                                 \
-        ({                                                              \
-                char *_buf = (buf);                                     \
-                size_t _len = (len);                                    \
-                int _snpf = snprintf(_buf, _len, (fmt), ##__VA_ARGS__); \
-                _snpf >= 0 && (size_t) _snpf < _len ? _buf : NULL;      \
-        })
+_printf_(3, 4)
+static inline char *snprintf_ok(char *buf, size_t len, const char *format, ...) {
+        va_list ap;
+        int r;
+
+        va_start(ap, format);
+        DISABLE_WARNING_FORMAT_NONLITERAL;
+        r = vsnprintf(buf, len, format, ap);
+        REENABLE_WARNING;
+        va_end(ap);
+
+        return r >= 0 && (size_t) r < len ? buf : NULL;
+}
 
 #define xsprintf(buf, fmt, ...) \
         assert_message_se(snprintf_ok(buf, ELEMENTSOF(buf), fmt, ##__VA_ARGS__), "xsprintf: " #buf "[] must be big enough")
@@ -28,7 +33,7 @@ do {                                                                    \
         size_t _i, _k;                                                  \
         /* See https://github.com/google/sanitizers/issues/992 */       \
         if (HAS_FEATURE_MEMORY_SANITIZER)                               \
-                zero(_argtypes);                                        \
+                memset(_argtypes, 0, sizeof(_argtypes));                \
         _k = parse_printf_format((format), ELEMENTSOF(_argtypes), _argtypes); \
         assert(_k < ELEMENTSOF(_argtypes));                             \
         for (_i = 0; _i < _k; _i++) {                                   \
diff --git a/src/libnm-systemd-shared/src/basic/string-table.h b/src/libnm-systemd-shared/src/basic/string-table.h
index e3a26a62..3be70dfa 100644
--- a/src/libnm-systemd-shared/src/basic/string-table.h
+++ b/src/libnm-systemd-shared/src/basic/string-table.h
@@ -95,6 +95,7 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
 #define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max)         \
         _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,)  \
         _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,)
+#define DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_FALLBACK(name,type,max) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,)
 
 #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max) \
         _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,static)
diff --git a/src/libnm-systemd-shared/src/basic/string-util.c b/src/libnm-systemd-shared/src/basic/string-util.c
index 9b2ee879..1afa49bb 100644
--- a/src/libnm-systemd-shared/src/basic/string-util.c
+++ b/src/libnm-systemd-shared/src/basic/string-util.c
@@ -11,16 +11,17 @@
 #include "alloc-util.h"
 #include "escape.h"
 #include "extract-word.h"
+#include "fd-util.h"
 #include "fileio.h"
 #include "gunicode.h"
 #include "locale-util.h"
 #include "macro.h"
 #include "memory-util.h"
+#include "memstream-util.h"
 #include "string-util.h"
 #include "strv.h"
 #include "terminal-util.h"
 #include "utf8.h"
-#include "util.h"
 
 char* first_word(const char *s, const char *word) {
         size_t sl, wl;
@@ -611,8 +612,8 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
                 STATE_CSI,
                 STATE_CSO,
         } state = STATE_OTHER;
-        char *obuf = NULL;
-        size_t osz = 0, isz, shift[2] = {}, n_carriage_returns = 0;
+        _cleanup_(memstream_done) MemStream m = {};
+        size_t isz, shift[2] = {}, n_carriage_returns = 0;
         FILE *f;
 
         assert(ibuf);
@@ -636,7 +637,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
 
         /* Note we turn off internal locking on f for performance reasons. It's safe to do so since we
          * created f here and it doesn't leave our scope. */
-        f = open_memstream_unlocked(&obuf, &osz);
+        f = memstream_init(&m);
         if (!f)
                 return NULL;
 
@@ -721,17 +722,12 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
                 }
         }
 
-        if (fflush_and_check(f) < 0) {
-                fclose(f);
-                return mfree(obuf);
-        }
-        fclose(f);
+        char *obuf;
+        if (memstream_finalize(&m, &obuf, _isz) < 0)
+                return NULL;
 
         free_and_replace(*ibuf, obuf);
 
-        if (_isz)
-                *_isz = osz;
-
         if (highlight) {
                 highlight[0] += shift[0];
                 highlight[1] += shift[1];
@@ -739,6 +735,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
 
         return *ibuf;
 }
+#endif /* NM_IGNORED */
 
 char *strextend_with_separator_internal(char **x, const char *separator, ...) {
         size_t f, l, l_separator;
@@ -809,7 +806,6 @@ char *strextend_with_separator_internal(char **x, const char *separator, ...) {
 
         return p;
 }
-#endif /* NM_IGNORED */
 
 int strextendf_with_separator(char **x, const char *separator, const char *format, ...) {
         size_t m, a, l_separator;
@@ -964,8 +960,7 @@ int free_and_strdup(char **p, const char *s) {
         } else
                 t = NULL;
 
-        free(*p);
-        *p = t;
+        free_and_replace(*p, t);
 
         return 1;
 }
@@ -1197,6 +1192,49 @@ char *string_replace_char(char *str, char old_char, char new_char) {
         return str;
 }
 
+int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret) {
+        char *b;
+
+        assert(s || n == 0);
+        assert(mode >= 0);
+        assert(mode < _MAKE_CSTRING_MODE_MAX);
+
+        /* Converts a sized character buffer into a NUL-terminated NUL string, refusing if there are embedded
+         * NUL bytes. Whether to expect a trailing NUL byte can be specified via 'mode' */
+
+        if (n == 0) {
+                if (mode == MAKE_CSTRING_REQUIRE_TRAILING_NUL)
+                        return -EINVAL;
+
+                if (!ret)
+                        return 0;
+
+                b = new0(char, 1);
+        } else {
+                const char *nul;
+
+                nul = memchr(s, 0, n);
+                if (nul) {
+                        if (nul < s + n - 1 || /* embedded NUL? */
+                            mode == MAKE_CSTRING_REFUSE_TRAILING_NUL)
+                                return -EINVAL;
+
+                        n--;
+                } else if (mode == MAKE_CSTRING_REQUIRE_TRAILING_NUL)
+                        return -EINVAL;
+
+                if (!ret)
+                        return 0;
+
+                b = memdup_suffix0(s, n);
+        }
+        if (!b)
+                return -ENOMEM;
+
+        *ret = b;
+        return 0;
+}
+
 size_t strspn_from_end(const char *str, const char *accept) {
         size_t n = 0;
 
@@ -1211,4 +1249,55 @@ size_t strspn_from_end(const char *str, const char *accept) {
 
         return n;
 }
+
+char *strdupspn(const char *a, const char *accept) {
+        if (isempty(a) || isempty(accept))
+                return strdup("");
+
+        return strndup(a, strspn(a, accept));
+}
+
+char *strdupcspn(const char *a, const char *reject) {
+        if (isempty(a))
+                return strdup("");
+        if (isempty(reject))
+                return strdup(a);
+
+        return strndup(a, strcspn(a, reject));
+}
+
+char *find_line_startswith(const char *haystack, const char *needle) {
+        char *p;
+
+        assert(haystack);
+        assert(needle);
+
+        /* Finds the first line in 'haystack' that starts with the specified string. Returns a pointer to the
+         * first character after it */
+
+        p = strstr(haystack, needle);
+        if (!p)
+                return NULL;
+
+        if (p > haystack)
+                while (p[-1] != '\n') {
+                        p = strstr(p + 1, needle);
+                        if (!p)
+                                return NULL;
+                }
+
+        return p + strlen(needle);
+}
 #endif /* NM_IGNORED */
+
+char *startswith_strv(const char *string, char **strv) {
+        char *found = NULL;
+
+        STRV_FOREACH(i, strv) {
+                found = startswith(string, *i);
+                if (found)
+                        break;
+        }
+
+        return found;
+}
diff --git a/src/libnm-systemd-shared/src/basic/string-util.h b/src/libnm-systemd-shared/src/basic/string-util.h
index 46681ced..4430910e 100644
--- a/src/libnm-systemd-shared/src/basic/string-util.h
+++ b/src/libnm-systemd-shared/src/basic/string-util.h
@@ -29,6 +29,18 @@ static inline char* strstr_ptr(const char *haystack, const char *needle) {
         return strstr(haystack, needle);
 }
 
+static inline char *strstrafter(const char *haystack, const char *needle) {
+        char *p;
+
+        /* Returns NULL if not found, or pointer to first character after needle if found */
+
+        p = strstr_ptr(haystack, needle);
+        if (!p)
+                return NULL;
+
+        return p + strlen(needle);
+}
+
 static inline const char* strnull(const char *s) {
         return s ?: "(null)";
 }
@@ -53,9 +65,13 @@ static inline const char* enable_disable(bool b) {
         return b ? "enable" : "disable";
 }
 
-static inline const char *empty_to_null(const char *p) {
-        return isempty(p) ? NULL : p;
-}
+/* 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)                                \
+        ({                                              \
+                const char *_p = (p);                   \
+                (typeof(p)) (isempty(_p) ? NULL : _p);  \
+        })
 
 static inline const char *empty_to_na(const char *p) {
         return isempty(p) ? "n/a" : p;
@@ -74,6 +90,11 @@ static inline bool empty_or_dash(const char *str) {
 static inline const char *empty_or_dash_to_null(const char *p) {
         return empty_or_dash(p) ? NULL : p;
 }
+#define empty_or_dash_to_null(p)                                \
+        ({                                                      \
+                const char *_p = (p);                           \
+                (typeof(p)) (empty_or_dash(_p) ? NULL : _p);    \
+        })
 
 char *first_word(const char *s, const char *word) _pure_;
 
@@ -230,4 +251,24 @@ bool streq_skip_trailing_chars(const char *s1, const char *s2, const char *ok);
 
 char *string_replace_char(char *str, char old_char, char new_char);
 
+typedef enum MakeCStringMode {
+        MAKE_CSTRING_REFUSE_TRAILING_NUL,
+        MAKE_CSTRING_ALLOW_TRAILING_NUL,
+        MAKE_CSTRING_REQUIRE_TRAILING_NUL,
+        _MAKE_CSTRING_MODE_MAX,
+        _MAKE_CSTRING_MODE_INVALID = -1,
+} MakeCStringMode;
+
+int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret);
+
 size_t strspn_from_end(const char *str, const char *accept);
+
+char *strdupspn(const char *a, const char *accept);
+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);
+
+#define STARTSWITH_SET(p, ...)                                  \
+        startswith_strv(p, STRV_MAKE(__VA_ARGS__))
diff --git a/src/libnm-systemd-shared/src/basic/strv.c b/src/libnm-systemd-shared/src/basic/strv.c
index 48ca8575..9ad53307 100644
--- a/src/libnm-systemd-shared/src/basic/strv.c
+++ b/src/libnm-systemd-shared/src/basic/strv.c
@@ -9,6 +9,7 @@
 #include <stdlib.h>
 
 #include "alloc-util.h"
+#include "env-util.h"
 #include "escape.h"
 #include "extract-word.h"
 #include "fileio.h"
@@ -65,6 +66,16 @@ char* strv_find_startswith(char * const *l, const char *name) {
         return NULL;
 }
 
+char* strv_find_first_field(char * const *needles, char * const *haystack) {
+        STRV_FOREACH(k, needles) {
+                char *value = strv_env_pairs_get((char **)haystack, *k);
+                if (value)
+                        return value;
+        }
+
+        return NULL;
+}
+
 char** strv_free(char **l) {
         STRV_FOREACH(k, l)
                 free(*k);
@@ -79,20 +90,26 @@ char** strv_free_erase(char **l) {
         return mfree(l);
 }
 
-char** strv_copy(char * const *l) {
+char** strv_copy_n(char * const *l, size_t m) {
         _cleanup_strv_free_ char **result = NULL;
         char **k;
 
-        result = new(char*, strv_length(l) + 1);
+        result = new(char*, MIN(strv_length(l), m) + 1);
         if (!result)
                 return NULL;
 
         k = result;
         STRV_FOREACH(i, l) {
+                if (m == 0)
+                        break;
+
                 *k = strdup(*i);
                 if (!*k)
                         return NULL;
                 k++;
+
+                if (m != SIZE_MAX)
+                        m--;
         }
 
         *k = NULL;
@@ -629,123 +646,6 @@ char** strv_remove(char **l, const char *s) {
         return l;
 }
 
-char** strv_parse_nulstr(const char *s, size_t l) {
-        /* l is the length of the input data, which will be split at NULs into
-         * elements of the resulting strv. Hence, the number of items in the resulting strv
-         * will be equal to one plus the number of NUL bytes in the l bytes starting at s,
-         * unless s[l-1] is NUL, in which case the final empty string is not stored in
-         * the resulting strv, and length is equal to the number of NUL bytes.
-         *
-         * Note that contrary to a normal nulstr which cannot contain empty strings, because
-         * the input data is terminated by any two consequent NUL bytes, this parser accepts
-         * empty strings in s.
-         */
-
-        size_t c = 0, i = 0;
-        char **v;
-
-        assert(s || l <= 0);
-
-        if (l <= 0)
-                return new0(char*, 1);
-
-        for (const char *p = s; p < s + l; p++)
-                if (*p == 0)
-                        c++;
-
-        if (s[l-1] != 0)
-                c++;
-
-        v = new0(char*, c+1);
-        if (!v)
-                return NULL;
-
-        for (const char *p = s; p < s + l; ) {
-                const char *e;
-
-                e = memchr(p, 0, s + l - p);
-
-                v[i] = strndup(p, e ? e - p : s + l - p);
-                if (!v[i]) {
-                        strv_free(v);
-                        return NULL;
-                }
-
-                i++;
-
-                if (!e)
-                        break;
-
-                p = e + 1;
-        }
-
-        assert(i == c);
-
-        return v;
-}
-
-#if 0 /* NM_IGNORED */
-char** strv_split_nulstr(const char *s) {
-        const char *i;
-        char **r = NULL;
-
-        NULSTR_FOREACH(i, s)
-                if (strv_extend(&r, i) < 0) {
-                        strv_free(r);
-                        return NULL;
-                }
-
-        if (!r)
-                return strv_new(NULL);
-
-        return r;
-}
-#endif /* NM_IGNORED */
-
-int strv_make_nulstr(char * const *l, char **ret, size_t *ret_size) {
-        /* A valid nulstr with two NULs at the end will be created, but
-         * q will be the length without the two trailing NULs. Thus the output
-         * string is a valid nulstr and can be iterated over using NULSTR_FOREACH,
-         * and can also be parsed by strv_parse_nulstr as long as the length
-         * is provided separately.
-         */
-
-        _cleanup_free_ char *m = NULL;
-        size_t n = 0;
-
-        assert(ret);
-        assert(ret_size);
-
-        STRV_FOREACH(i, l) {
-                size_t z;
-
-                z = strlen(*i);
-
-                if (!GREEDY_REALLOC(m, n + z + 2))
-                        return -ENOMEM;
-
-                memcpy(m + n, *i, z + 1);
-                n += z + 1;
-        }
-
-        if (!m) {
-                m = new0(char, 1);
-                if (!m)
-                        return -ENOMEM;
-                n = 1;
-        } else
-                /* make sure there is a second extra NUL at the end of resulting nulstr */
-                m[n] = '\0';
-
-        assert(n > 0);
-        *ret = m;
-        *ret_size = n - 1;
-
-        m = NULL;
-
-        return 0;
-}
-
 bool strv_overlap(char * const *a, char * const *b) {
         STRV_FOREACH(i, a)
                 if (strv_contains(b, *i))
@@ -787,9 +687,9 @@ int strv_compare(char * const *a, char * const *b) {
         return 0;
 }
 
-void strv_print(char * const *l) {
+void strv_print_full(char * const *l, const char *prefix) {
         STRV_FOREACH(s, l)
-                puts(*s);
+                printf("%s%s\n", strempty(prefix), *s);
 }
 
 int strv_extendf(char ***l, const char *format, ...) {
@@ -832,8 +732,7 @@ char** strv_shell_escape(char **l, const char *bad) {
                 if (!v)
                         return NULL;
 
-                free(*s);
-                *s = v;
+                free_and_replace(*s, v);
         }
 
         return l;
@@ -916,6 +815,22 @@ rollback:
         return -ENOMEM;
 }
 
+int strv_extend_assignment(char ***l, const char *lhs, const char *rhs) {
+        char *j;
+
+        assert(l);
+        assert(lhs);
+
+        if (!rhs) /* value is optional, in which case we suppress the field */
+                return 0;
+
+        j = strjoin(lhs, "=", rhs);
+        if (!j)
+                return -ENOMEM;
+
+        return strv_consume(l, j);
+}
+
 int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) {
         bool b = false;
         int r;
diff --git a/src/libnm-systemd-shared/src/basic/strv.h b/src/libnm-systemd-shared/src/basic/strv.h
index d6f5ac6b..544d46a3 100644
--- a/src/libnm-systemd-shared/src/basic/strv.h
+++ b/src/libnm-systemd-shared/src/basic/strv.h
@@ -17,6 +17,9 @@ char* strv_find(char * const *l, const char *name) _pure_;
 char* strv_find_case(char * const *l, const char *name) _pure_;
 char* strv_find_prefix(char * const *l, const char *name) _pure_;
 char* strv_find_startswith(char * const *l, const char *name) _pure_;
+/* Given two vectors, the first a list of keys and the second a list of key-value pairs, returns the value
+ * of the first key from the first vector that is found in the second vector. */
+char* strv_find_first_field(char * const *needles, char * const *haystack) _pure_;
 
 #define strv_contains(l, s) (!!strv_find((l), (s)))
 #define strv_contains_case(l, s) (!!strv_find_case((l), (s)))
@@ -29,7 +32,10 @@ char** strv_free_erase(char **l);
 DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase);
 #define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep)
 
-char** strv_copy(char * const *l);
+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);
+}
 size_t strv_length(char * const *l) _pure_;
 
 int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates);
@@ -45,7 +51,7 @@ static inline int strv_extend(char ***l, const char *value) {
         return strv_extend_with_size(l, NULL, value);
 }
 
-int strv_extendf(char ***l, const char *format, ...) _printf_(2,0);
+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);
@@ -84,7 +90,7 @@ char** strv_new_ap(const char *x, va_list ap);
 #define STRV_IGNORE ((const char *) POINTER_MAX)
 
 static inline const char* STRV_IFNOTNULL(const char *x) {
-        return x ? x : STRV_IGNORE;
+        return x ?: STRV_IGNORE;
 }
 
 static inline bool strv_isempty(char * const *l) {
@@ -124,20 +130,6 @@ static inline char *strv_join(char * const *l, const char *separator) {
         return strv_join_full(l, separator, NULL, false);
 }
 
-char** strv_parse_nulstr(const char *s, size_t l);
-char** strv_split_nulstr(const char *s);
-int strv_make_nulstr(char * const *l, char **p, size_t *n);
-
-static inline int strv_from_nulstr(char ***a, const char *nulstr) {
-        char **t;
-
-        t = strv_split_nulstr(nulstr);
-        if (!t)
-                return -ENOMEM;
-        *a = t;
-        return 0;
-}
-
 bool strv_overlap(char * const *a, char * const *b) _pure_;
 
 #define _STRV_FOREACH_BACKWARDS(s, l, h, i)                             \
@@ -160,7 +152,10 @@ bool strv_overlap(char * const *a, char * const *b) _pure_;
         _STRV_FOREACH_PAIR(x, y, l, UNIQ_T(i, UNIQ))
 
 char** strv_sort(char **l);
-void strv_print(char * const *l);
+void strv_print_full(char * const *l, const char *prefix);
+static inline void strv_print(char * const *l) {
+        strv_print_full(l, NULL);
+}
 
 #define strv_from_stdarg_alloca(first)                          \
         ({                                                      \
@@ -205,18 +200,6 @@ void strv_print(char * const *l);
                 _x && strv_contains_case(STRV_MAKE(__VA_ARGS__), _x); \
         })
 
-#define STARTSWITH_SET(p, ...)                                  \
-        ({                                                      \
-                const char *_p = (p);                           \
-                char *_found = NULL;                            \
-                STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) {      \
-                        _found = startswith(_p, *_i);           \
-                        if (_found)                             \
-                                break;                          \
-                }                                               \
-                _found;                                         \
-        })
-
 #define ENDSWITH_SET(p, ...)                                    \
         ({                                                      \
                 const char *_p = (p);                           \
@@ -255,6 +238,8 @@ char** strv_skip(char **l, size_t n);
 
 int strv_extend_n(char ***l, const char *value, size_t n);
 
+int strv_extend_assignment(char ***l, const char *lhs, const char *rhs);
+
 int fputstrv(FILE *f, char * const *l, const char *separator, bool *space);
 
 #define strv_free_and_replace(a, b)             \
diff --git a/src/libnm-systemd-shared/src/basic/time-util.c b/src/libnm-systemd-shared/src/basic/time-util.c
index cf556a73..092912b2 100644
--- a/src/libnm-systemd-shared/src/basic/time-util.c
+++ b/src/libnm-systemd-shared/src/basic/time-util.c
@@ -19,6 +19,7 @@
 #include "io-util.h"
 #include "log.h"
 #include "macro.h"
+#include "missing_threads.h"
 #include "missing_timerfd.h"
 #include "parse-util.h"
 #include "path-util.h"
@@ -173,6 +174,8 @@ dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
 dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) {
         usec_t nowm;
 
+        assert(ts);
+
         if (u == USEC_INFINITY) {
                 ts->realtime = ts->monotonic = USEC_INFINITY;
                 return ts;
@@ -185,6 +188,7 @@ dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) {
 }
 
 usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) {
+        assert(ts);
 
         switch (clock) {
 
@@ -230,7 +234,7 @@ nsec_t timespec_load_nsec(const struct timespec *ts) {
         return (nsec_t) ts->tv_sec * NSEC_PER_SEC + (nsec_t) ts->tv_nsec;
 }
 
-struct timespec *timespec_store(struct timespec *ts, usec_t u)  {
+struct timespec *timespec_store(struct timespec *ts, usec_t u) {
         assert(ts);
 
         if (u == USEC_INFINITY ||
@@ -246,7 +250,7 @@ struct timespec *timespec_store(struct timespec *ts, usec_t u)  {
         return ts;
 }
 
-struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n)  {
+struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n) {
         assert(ts);
 
         if (n == NSEC_INFINITY ||
@@ -311,54 +315,48 @@ char *format_timestamp_style(
         };
 
         struct tm tm;
+        bool utc, us;
         time_t sec;
         size_t n;
-        bool utc = false, us = false;
-        int r;
 
         assert(buf);
+        assert(style >= 0);
+        assert(style < _TIMESTAMP_STYLE_MAX);
 
-        switch (style) {
-                case TIMESTAMP_PRETTY:
-                case TIMESTAMP_UNIX:
-                        break;
-                case TIMESTAMP_US:
-                        us = true;
-                        break;
-                case TIMESTAMP_UTC:
-                        utc = true;
-                        break;
-                case TIMESTAMP_US_UTC:
-                        us = true;
-                        utc = true;
-                        break;
-                default:
-                        return NULL;
-        }
-
-        if (l < (size_t) (3 +                  /* week day */
-                          1 + 10 +             /* space and date */
-                          1 + 8 +              /* space and time */
-                          (us ? 1 + 6 : 0) +   /* "." and microsecond part */
-                          1 + 1 +              /* space and shortest possible zone */
-                          1))
-                return NULL; /* Not enough space even for the shortest form. */
         if (!timestamp_is_set(t))
                 return NULL; /* Timestamp is unset */
 
         if (style == TIMESTAMP_UNIX) {
-                r = snprintf(buf, l, "@" USEC_FMT, t / USEC_PER_SEC);  /* round down µs → s */
-                if (r < 0 || (size_t) r >= l)
-                        return NULL; /* Doesn't fit */
+                if (l < (size_t) (1 + 1 + 1))
+                        return NULL; /* not enough space for even the shortest of forms */
 
-                return buf;
+                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);
+        us = IN_SET(style, TIMESTAMP_US, TIMESTAMP_US_UTC);
+
+        if (l < (size_t) (3 +                   /* week day */
+                          1 + 10 +              /* space and date */
+                          style == TIMESTAMP_DATE ? 0 :
+                          (1 + 8 +              /* space and time */
+                           (us ? 1 + 6 : 0) +   /* "." and microsecond part */
+                           1 + (utc ? 3 : 1)) + /* space and shortest possible zone */
+                          1))
+                return NULL; /* Not enough space even for the shortest form. */
+
         /* Let's not format times with years > 9999 */
         if (t > USEC_TIMESTAMP_FORMATTABLE_MAX) {
-                assert(l >= STRLEN("--- XXXX-XX-XX XX:XX:XX") + 1);
-                strcpy(buf, "--- XXXX-XX-XX XX:XX:XX");
-                return buf;
+                static const char* const xxx[_TIMESTAMP_STYLE_MAX] = {
+                        [TIMESTAMP_PRETTY] = "--- XXXX-XX-XX XX:XX:XX",
+                        [TIMESTAMP_US]     = "--- XXXX-XX-XX XX:XX:XX.XXXXXX",
+                        [TIMESTAMP_UTC]    = "--- XXXX-XX-XX XX:XX:XX UTC",
+                        [TIMESTAMP_US_UTC] = "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC",
+                        [TIMESTAMP_DATE]   = "--- XXXX-XX-XX",
+                };
+
+                assert(l >= strlen(xxx[style]) + 1);
+                return strcpy(buf, xxx[style]);
         }
 
         sec = (time_t) (t / USEC_PER_SEC); /* Round down */
@@ -370,6 +368,14 @@ char *format_timestamp_style(
         assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays));
         memcpy(buf, weekdays[tm.tm_wday], 4);
 
+        if (style == TIMESTAMP_DATE) {
+                /* Special format string if only date should be shown. */
+                if (strftime(buf + 3, l - 3, " %Y-%m-%d", &tm) <= 0)
+                        return NULL; /* Doesn't fit */
+
+                return buf;
+        }
+
         /* Add the main components */
         if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0)
                 return NULL; /* Doesn't fit */
@@ -419,27 +425,29 @@ char *format_timestamp_style(
         return buf;
 }
 
-char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
+char* format_timestamp_relative_full(char *buf, size_t l, usec_t t, clockid_t clock, bool implicit_left) {
         const char *s;
         usec_t n, d;
 
+        assert(buf);
+
         if (!timestamp_is_set(t))
                 return NULL;
 
-        n = now(CLOCK_REALTIME);
+        n = now(clock);
         if (n > t) {
                 d = n - t;
-                s = "ago";
+                s = " ago";
         } else {
                 d = t - n;
-                s = "left";
+                s = implicit_left ? "" : " left";
         }
 
         if (d >= USEC_PER_YEAR) {
                 usec_t years = d / USEC_PER_YEAR;
                 usec_t months = (d % USEC_PER_YEAR) / USEC_PER_MONTH;
 
-                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s",
+                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
                                 years,
                                 years == 1 ? "year" : "years",
                                 months,
@@ -449,7 +457,7 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
                 usec_t months = d / USEC_PER_MONTH;
                 usec_t days = (d % USEC_PER_MONTH) / USEC_PER_DAY;
 
-                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s",
+                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
                                 months,
                                 months == 1 ? "month" : "months",
                                 days,
@@ -459,39 +467,39 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
                 usec_t weeks = d / USEC_PER_WEEK;
                 usec_t days = (d % USEC_PER_WEEK) / USEC_PER_DAY;
 
-                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s",
+                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
                                 weeks,
                                 weeks == 1 ? "week" : "weeks",
                                 days,
                                 days == 1 ? "day" : "days",
                                 s);
         } else if (d >= 2*USEC_PER_DAY)
-                (void) snprintf(buf, l, USEC_FMT " days %s", d / USEC_PER_DAY, s);
+                (void) snprintf(buf, l, USEC_FMT " days%s", d / USEC_PER_DAY,s);
         else if (d >= 25*USEC_PER_HOUR)
-                (void) snprintf(buf, l, "1 day " USEC_FMT "h %s",
+                (void) snprintf(buf, l, "1 day " USEC_FMT "h%s",
                                 (d - USEC_PER_DAY) / USEC_PER_HOUR, s);
         else if (d >= 6*USEC_PER_HOUR)
-                (void) snprintf(buf, l, USEC_FMT "h %s",
+                (void) snprintf(buf, l, USEC_FMT "h%s",
                                 d / USEC_PER_HOUR, s);
         else if (d >= USEC_PER_HOUR)
-                (void) snprintf(buf, l, USEC_FMT "h " USEC_FMT "min %s",
+                (void) snprintf(buf, l, USEC_FMT "h " USEC_FMT "min%s",
                                 d / USEC_PER_HOUR,
                                 (d % USEC_PER_HOUR) / USEC_PER_MINUTE, s);
         else if (d >= 5*USEC_PER_MINUTE)
-                (void) snprintf(buf, l, USEC_FMT "min %s",
+                (void) snprintf(buf, l, USEC_FMT "min%s",
                                 d / USEC_PER_MINUTE, s);
         else if (d >= USEC_PER_MINUTE)
-                (void) snprintf(buf, l, USEC_FMT "min " USEC_FMT "s %s",
+                (void) snprintf(buf, l, USEC_FMT "min " USEC_FMT "s%s",
                                 d / USEC_PER_MINUTE,
                                 (d % USEC_PER_MINUTE) / USEC_PER_SEC, s);
         else if (d >= USEC_PER_SEC)
-                (void) snprintf(buf, l, USEC_FMT "s %s",
+                (void) snprintf(buf, l, USEC_FMT "s%s",
                                 d / USEC_PER_SEC, s);
         else if (d >= USEC_PER_MSEC)
-                (void) snprintf(buf, l, USEC_FMT "ms %s",
+                (void) snprintf(buf, l, USEC_FMT "ms%s",
                                 d / USEC_PER_MSEC, s);
         else if (d > 0)
-                (void) snprintf(buf, l, USEC_FMT"us %s",
+                (void) snprintf(buf, l, USEC_FMT"us%s",
                                 d, s);
         else
                 (void) snprintf(buf, l, "now");
@@ -501,7 +509,7 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
 }
 #endif /* NM_IGNORED */
 
-char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
+char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
         static const struct {
                 const char *suffix;
                 usec_t usec;
@@ -608,7 +616,14 @@ 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, usec_t *usec, bool with_tz) {
+static int parse_timestamp_impl(
+                const char *t,
+                size_t tz_offset,
+                bool utc,
+                int isdst,
+                long gmtoff,
+                usec_t *ret) {
+
         static const struct {
                 const char *name;
                 const int nr;
@@ -629,12 +644,14 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
                 { "Sat",       6 },
         };
 
-        const char *k, *utc = NULL, *tzn = NULL;
+        _cleanup_free_ char *t_alloc = NULL;
+        usec_t usec, plus = 0, minus = 0;
+        bool with_tz = false;
+        int r, weekday = -1;
+        unsigned fractional = 0;
+        const char *k;
         struct tm tm, copy;
-        time_t x;
-        usec_t x_usec, plus = 0, minus = 0, ret;
-        int r, weekday = -1, dst = -1;
-        size_t i;
+        time_t sec;
 
         /* Allowed syntaxes:
          *
@@ -650,103 +667,96 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
          *   +5min
          *   -5days
          *   @2147483647          (seconds since epoch)
+         *
+         * 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".
          */
 
         assert(t);
 
+        if (tz_offset != SIZE_MAX) {
+                /* If the input string contains timezone, then cut it here. */
+
+                if (tz_offset <= 1) /* timezone must be after a space. */
+                        return -EINVAL;
+
+                t_alloc = strndup(t, tz_offset - 1);
+                if (!t_alloc)
+                        return -ENOMEM;
+
+                t = t_alloc;
+                with_tz = true;
+        }
+
+        if (utc) {
+                /* glibc accepts gmtoff more than 24 hours, but we refuse it. */
+                if ((usec_t) labs(gmtoff) * USEC_PER_SEC > USEC_PER_DAY)
+                        return -EINVAL;
+        } else {
+                if (gmtoff != 0)
+                        return -EINVAL;
+        }
+
         if (t[0] == '@' && !with_tz)
-                return parse_sec(t + 1, usec);
+                return parse_sec(t + 1, ret);
 
-        ret = now(CLOCK_REALTIME);
+        usec = now(CLOCK_REALTIME);
 
         if (!with_tz) {
                 if (streq(t, "now"))
                         goto finish;
 
-                else if (t[0] == '+') {
+                if (t[0] == '+') {
                         r = parse_sec(t+1, &plus);
                         if (r < 0)
                                 return r;
 
                         goto finish;
+                }
 
-                } else if (t[0] == '-') {
+                if (t[0] == '-') {
                         r = parse_sec(t+1, &minus);
                         if (r < 0)
                                 return r;
 
                         goto finish;
+                }
+
+                if ((k = endswith(t, " ago"))) {
+                        _cleanup_free_ char *buf = NULL;
 
-                } else if ((k = endswith(t, " ago"))) {
-                        t = strndupa_safe(t, k - t);
+                        buf = strndup(t, k - t);
+                        if (!buf)
+                                return -ENOMEM;
 
-                        r = parse_sec(t, &minus);
+                        r = parse_sec(buf, &minus);
                         if (r < 0)
                                 return r;
 
                         goto finish;
+                }
+
+                if ((k = endswith(t, " left"))) {
+                        _cleanup_free_ char *buf = NULL;
 
-                } else if ((k = endswith(t, " left"))) {
-                        t = strndupa_safe(t, k - t);
+                        buf = strndup(t, k - t);
+                        if (!buf)
+                                return -ENOMEM;
 
-                        r = parse_sec(t, &plus);
+                        r = parse_sec(buf, &plus);
                         if (r < 0)
                                 return r;
 
                         goto finish;
                 }
-
-                /* See if the timestamp is suffixed with UTC */
-                utc = endswith_no_case(t, " UTC");
-                if (utc)
-                        t = strndupa_safe(t, utc - t);
-                else {
-                        const char *e = NULL;
-                        int j;
-
-                        tzset();
-
-                        /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note
-                         * that we only support the local timezones here, nothing else. Not because we
-                         * wouldn't want to, but simply because there are no nice APIs available to cover
-                         * this. By accepting the local time zone strings, we make sure that all timestamps
-                         * written by format_timestamp() can be parsed correctly, even though we don't
-                         * support arbitrary timezone specifications. */
-
-                        for (j = 0; j <= 1; j++) {
-
-                                if (isempty(tzname[j]))
-                                        continue;
-
-                                e = endswith_no_case(t, tzname[j]);
-                                if (!e)
-                                        continue;
-                                if (e == t)
-                                        continue;
-                                if (e[-1] != ' ')
-                                        continue;
-
-                                break;
-                        }
-
-                        if (IN_SET(j, 0, 1)) {
-                                /* Found one of the two timezones specified. */
-                                t = strndupa_safe(t, e - t - 1);
-                                dst = j;
-                                tzn = tzname[j];
-                        }
-                }
         }
 
-        x = (time_t) (ret / USEC_PER_SEC);
-        x_usec = 0;
+        sec = (time_t) (usec / USEC_PER_SEC);
 
-        if (!localtime_or_gmtime_r(&x, &tm, utc))
+        if (!localtime_or_gmtime_r(&sec, &tm, utc))
                 return -EINVAL;
 
-        tm.tm_isdst = dst;
-        if (!with_tz && tzn)
-                tm.tm_zone = tzn;
+        tm.tm_isdst = isdst;
 
         if (streq(t, "today")) {
                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
@@ -763,18 +773,13 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
                 goto from_tm;
         }
 
-        for (i = 0; i < ELEMENTSOF(day_nr); i++) {
-                size_t skip;
-
-                if (!startswith_no_case(t, day_nr[i].name))
-                        continue;
-
-                skip = strlen(day_nr[i].name);
-                if (t[skip] != ' ')
+        for (size_t i = 0; i < ELEMENTSOF(day_nr); i++) {
+                k = startswith_no_case(t, day_nr[i].name);
+                if (!k || *k != ' ')
                         continue;
 
                 weekday = day_nr[i].nr;
-                t += skip + 1;
+                t = k + 1;
                 break;
         }
 
@@ -853,65 +858,130 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
         return -EINVAL;
 
 parse_usec:
-        {
-                unsigned add;
-
-                k++;
-                r = parse_fractional_part_u(&k, 6, &add);
-                if (r < 0)
-                        return -EINVAL;
-
-                if (*k)
-                        return -EINVAL;
-
-                x_usec = add;
-        }
+        k++;
+        r = parse_fractional_part_u(&k, 6, &fractional);
+        if (r < 0)
+                return -EINVAL;
+        if (*k != '\0')
+                return -EINVAL;
 
 from_tm:
+        assert(plus == 0);
+        assert(minus == 0);
+
         if (weekday >= 0 && tm.tm_wday != weekday)
                 return -EINVAL;
 
-        x = mktime_or_timegm(&tm, utc);
-        if (x < 0)
-                return -EINVAL;
+        if (gmtoff < 0) {
+                plus = -gmtoff * USEC_PER_SEC;
+
+                /* If gmtoff is negative, the string may be too old to be parsed as UTC.
+                 * E.g. 1969-12-31 23:00:00 -06 == 1970-01-01 05:00:00 UTC
+                 * We assumed that gmtoff is in the range of -24:00…+24:00, hence the only date we need to
+                 * handle here is 1969-12-31. So, let's shift the date with one day, then subtract the shift
+                 * later. */
+                if (tm.tm_year == 69 && tm.tm_mon == 11 && tm.tm_mday == 31) {
+                        /* Thu 1970-01-01-00:00:00 */
+                        tm.tm_year = 70;
+                        tm.tm_mon = 0;
+                        tm.tm_mday = 1;
+                        tm.tm_wday = 4;
+                        tm.tm_yday = 0;
+                        minus = USEC_PER_DAY;
+                }
+        } else
+                minus = gmtoff * USEC_PER_SEC;
 
-        ret = (usec_t) x * USEC_PER_SEC + x_usec;
-        if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX)
+        sec = mktime_or_timegm(&tm, utc);
+        if (sec < 0)
                 return -EINVAL;
 
+        usec = usec_add(sec * USEC_PER_SEC, fractional);
+
 finish:
-        if (ret + plus < ret) /* overflow? */
-                return -EINVAL;
-        ret += plus;
-        if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX)
+        usec = usec_add(usec, plus);
+
+        if (usec < minus)
                 return -EINVAL;
 
-        if (ret >= minus)
-                ret -= minus;
-        else
+        usec = usec_sub_unsigned(usec, minus);
+
+        if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
                 return -EINVAL;
 
-        if (usec)
-                *usec = ret;
+        if (ret)
+                *ret = usec;
         return 0;
 }
 
+static int parse_timestamp_maybe_with_tz(const char *t, size_t tz_offset, bool valid_tz, usec_t *ret) {
+        assert(t);
+
+        tzset();
+
+        for (int j = 0; j <= 1; j++) {
+                if (isempty(tzname[j]))
+                        continue;
+
+                if (!streq(t + tz_offset, tzname[j]))
+                        continue;
+
+                /* The specified timezone matches tzname[] of the local timezone. */
+                return parse_timestamp_impl(t, tz_offset, /* 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,
+                                    /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
+}
+
 typedef struct ParseTimestampResult {
         usec_t usec;
         int return_value;
 } ParseTimestampResult;
 
-int parse_timestamp(const char *t, usec_t *usec) {
-        char *last_space, *tz = NULL;
+int parse_timestamp(const char *t, usec_t *ret) {
         ParseTimestampResult *shared, tmp;
+        const char *k, *tz, *current_tz;
+        size_t tz_offset;
+        struct tm tm;
         int r;
 
-        last_space = strrchr(t, ' ');
-        if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG))
-                tz = last_space + 1;
+        assert(t);
+
+        tz = strrchr(t, ' ');
+        if (!tz)
+                return parse_timestamp_impl(t, /* tz_offset = */ SIZE_MAX, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
+
+        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);
 
-        if (!tz || endswith_no_case(t, " UTC"))
-                return parse_timestamp_impl(t, usec, false);
+        /* 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);
+
+        /* 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);
+
+        /* 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);
+
+        /* Otherwise, to avoid polluting the current environment variables, let's fork the process and set
+         * the specified timezone in the child process. */
 
         shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
         if (shared == MAP_FAILED)
@@ -923,8 +993,7 @@ int parse_timestamp(const char *t, usec_t *usec) {
                 return r;
         }
         if (r == 0) {
-                bool with_tz = true;
-                char *colon_tz;
+                const char *colon_tz;
 
                 /* tzset(3) says $TZ should be prefixed with ":" if we reference timezone files */
                 colon_tz = strjoina(":", tz);
@@ -934,17 +1003,7 @@ int parse_timestamp(const char *t, usec_t *usec) {
                         _exit(EXIT_FAILURE);
                 }
 
-                tzset();
-
-                /* If there is a timezone that matches the tzname fields, leave the parsing to the implementation.
-                 * Otherwise just cut it off. */
-                with_tz = !STR_IN_SET(tz, tzname[0], tzname[1]);
-
-                /* Cut off the timezone if we don't need it. */
-                if (with_tz)
-                        t = strndupa_safe(t, last_space - t);
-
-                shared->return_value = parse_timestamp_impl(t, &shared->usec, with_tz);
+                shared->return_value = parse_timestamp_maybe_with_tz(t, tz_offset, /* valid_tz = */ true, &shared->usec);
 
                 _exit(EXIT_SUCCESS);
         }
@@ -953,13 +1012,13 @@ int parse_timestamp(const char *t, usec_t *usec) {
         if (munmap(shared, sizeof *shared) != 0)
                 return negative_errno();
 
-        if (tmp.return_value == 0 && usec)
-                *usec = tmp.usec;
+        if (tmp.return_value == 0 && ret)
+                *ret = tmp.usec;
 
         return tmp.return_value;
 }
 
-static const char* extract_multiplier(const char *p, usec_t *multiplier) {
+static const char* extract_multiplier(const char *p, usec_t *ret) {
         static const struct {
                 const char *suffix;
                 usec_t usec;
@@ -995,12 +1054,15 @@ static const char* extract_multiplier(const char *p, usec_t *multiplier) {
                 { "µs",      1ULL            },
         };
 
+        assert(p);
+        assert(ret);
+
         for (size_t i = 0; i < ELEMENTSOF(table); i++) {
                 char *e;
 
                 e = startswith(p, table[i].suffix);
                 if (e) {
-                        *multiplier = table[i].usec;
+                        *ret = table[i].usec;
                         return e;
                 }
         }
@@ -1008,9 +1070,9 @@ static const char* extract_multiplier(const char *p, usec_t *multiplier) {
         return p;
 }
 
-int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
+int parse_time(const char *t, usec_t *ret, usec_t default_unit) {
         const char *p, *s;
-        usec_t r = 0;
+        usec_t usec = 0;
         bool something = false;
 
         assert(t);
@@ -1025,8 +1087,8 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
                 if (*s != 0)
                         return -EINVAL;
 
-                if (usec)
-                        *usec = USEC_INFINITY;
+                if (ret)
+                        *ret = USEC_INFINITY;
                 return 0;
         }
 
@@ -1073,10 +1135,10 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
                         return -ERANGE;
 
                 k = (usec_t) l * multiplier;
-                if (k >= USEC_INFINITY - r)
+                if (k >= USEC_INFINITY - usec)
                         return -ERANGE;
 
-                r += k;
+                usec += k;
 
                 something = true;
 
@@ -1086,10 +1148,10 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
 
                         for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) {
                                 k = (usec_t) (*b - '0') * m;
-                                if (k >= USEC_INFINITY - r)
+                                if (k >= USEC_INFINITY - usec)
                                         return -ERANGE;
 
-                                r += k;
+                                usec += k;
                         }
 
                         /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */
@@ -1098,13 +1160,13 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
                 }
         }
 
-        if (usec)
-                *usec = r;
+        if (ret)
+                *ret = usec;
         return 0;
 }
 
-int parse_sec(const char *t, usec_t *usec) {
-        return parse_time(t, usec, USEC_PER_SEC);
+int parse_sec(const char *t, usec_t *ret) {
+        return parse_time(t, ret, USEC_PER_SEC);
 }
 
 int parse_sec_fix_0(const char *t, usec_t *ret) {
@@ -1123,6 +1185,9 @@ int parse_sec_fix_0(const char *t, usec_t *ret) {
 }
 
 int parse_sec_def_infinity(const char *t, usec_t *ret) {
+        assert(t);
+        assert(ret);
+
         t += strspn(t, WHITESPACE);
         if (isempty(t)) {
                 *ret = USEC_INFINITY;
@@ -1131,7 +1196,7 @@ int parse_sec_def_infinity(const char *t, usec_t *ret) {
         return parse_sec(t, ret);
 }
 
-static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) {
+static const char* extract_nsec_multiplier(const char *p, nsec_t *ret) {
         static const struct {
                 const char *suffix;
                 nsec_t nsec;
@@ -1171,12 +1236,15 @@ static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) {
         };
         size_t i;
 
+        assert(p);
+        assert(ret);
+
         for (i = 0; i < ELEMENTSOF(table); i++) {
                 char *e;
 
                 e = startswith(p, table[i].suffix);
                 if (e) {
-                        *multiplier = table[i].nsec;
+                        *ret = table[i].nsec;
                         return e;
                 }
         }
@@ -1184,13 +1252,13 @@ static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) {
         return p;
 }
 
-int parse_nsec(const char *t, nsec_t *nsec) {
+int parse_nsec(const char *t, nsec_t *ret) {
         const char *p, *s;
-        nsec_t r = 0;
+        nsec_t nsec = 0;
         bool something = false;
 
         assert(t);
-        assert(nsec);
+        assert(ret);
 
         p = t;
 
@@ -1201,7 +1269,7 @@ int parse_nsec(const char *t, nsec_t *nsec) {
                 if (*s != 0)
                         return -EINVAL;
 
-                *nsec = NSEC_INFINITY;
+                *ret = NSEC_INFINITY;
                 return 0;
         }
 
@@ -1248,10 +1316,10 @@ int parse_nsec(const char *t, nsec_t *nsec) {
                         return -ERANGE;
 
                 k = (nsec_t) l * multiplier;
-                if (k >= NSEC_INFINITY - r)
+                if (k >= NSEC_INFINITY - nsec)
                         return -ERANGE;
 
-                r += k;
+                nsec += k;
 
                 something = true;
 
@@ -1261,10 +1329,10 @@ int parse_nsec(const char *t, nsec_t *nsec) {
 
                         for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) {
                                 k = (nsec_t) (*b - '0') * m;
-                                if (k >= NSEC_INFINITY - r)
+                                if (k >= NSEC_INFINITY - nsec)
                                         return -ERANGE;
 
-                                r += k;
+                                nsec += k;
                         }
 
                         /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */
@@ -1273,7 +1341,7 @@ int parse_nsec(const char *t, nsec_t *nsec) {
                 }
         }
 
-        *nsec = r;
+        *ret = nsec;
 
         return 0;
 }
@@ -1324,6 +1392,8 @@ static int get_timezones_from_tzdata_zi(char ***ret) {
         _cleanup_strv_free_ char **zones = NULL;
         int r;
 
+        assert(ret);
+
         f = fopen("/usr/share/zoneinfo/tzdata.zi", "re");
         if (!f)
                 return -errno;
@@ -1401,7 +1471,7 @@ int get_timezones(char ***ret) {
 int verify_timezone(const char *name, int log_level) {
         bool slash = false;
         const char *p, *t;
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         char buf[4];
         int r;
 
@@ -1483,6 +1553,8 @@ int get_timezone(char **ret) {
         char *z;
         int r;
 
+        assert(ret);
+
         r = readlink_malloc("/etc/localtime", &t);
         if (r == -ENOENT) {
                 /* If the symlink does not exist, assume "UTC", like glibc does */
@@ -1512,10 +1584,15 @@ int get_timezone(char **ret) {
 }
 
 time_t mktime_or_timegm(struct tm *tm, bool utc) {
+        assert(tm);
+
         return utc ? timegm(tm) : mktime(tm);
 }
 
 struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) {
+        assert(t);
+        assert(tm);
+
         return utc ? gmtime_r(t, tm) : localtime_r(t, tm);
 }
 
@@ -1575,7 +1652,7 @@ int time_change_fd(void) {
                 .it_value.tv_sec = TIME_T_MAX,
         };
 
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX));
 
@@ -1615,14 +1692,14 @@ int time_change_fd(void) {
 
 static const char* const timestamp_style_table[_TIMESTAMP_STYLE_MAX] = {
         [TIMESTAMP_PRETTY] = "pretty",
-        [TIMESTAMP_US] = "us",
-        [TIMESTAMP_UTC] = "utc",
+        [TIMESTAMP_US]     = "us",
+        [TIMESTAMP_UTC]    = "utc",
         [TIMESTAMP_US_UTC] = "us+utc",
-        [TIMESTAMP_UNIX] = "unix",
+        [TIMESTAMP_UNIX]   = "unix",
 };
 
 /* Use the macro for enum → string to allow for aliases */
-_DEFINE_STRING_TABLE_LOOKUP_TO_STRING(timestamp_style, TimestampStyle,);
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(timestamp_style, TimestampStyle);
 
 /* For the string → enum mapping we use the generic implementation, but also support two aliases */
 TimestampStyle timestamp_style_from_string(const char *s) {
diff --git a/src/libnm-systemd-shared/src/basic/time-util.h b/src/libnm-systemd-shared/src/basic/time-util.h
index c98f95a5..b49137d5 100644
--- a/src/libnm-systemd-shared/src/basic/time-util.h
+++ b/src/libnm-systemd-shared/src/basic/time-util.h
@@ -35,6 +35,7 @@ typedef enum TimestampStyle {
         TIMESTAMP_UTC,
         TIMESTAMP_US_UTC,
         TIMESTAMP_UNIX,
+        TIMESTAMP_DATE,
         _TIMESTAMP_STYLE_MAX,
         _TIMESTAMP_STYLE_INVALID = -EINVAL,
 } TimestampStyle;
@@ -65,7 +66,6 @@ typedef enum TimestampStyle {
 /* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this
  * to 6. Let's rely on that. */
 #define FORMAT_TIMESTAMP_MAX (3U+1U+10U+1U+8U+1U+6U+1U+6U+1U)
-#define FORMAT_TIMESTAMP_WIDTH 28U /* when outputting, assume this width */
 #define FORMAT_TIMESTAMP_RELATIVE_MAX 256U
 #define FORMAT_TIMESPAN_MAX 64U
 
@@ -123,10 +123,19 @@ struct timeval* timeval_store(struct timeval *tv, usec_t u);
 #define TIMEVAL_STORE(u) timeval_store(&(struct timeval) {}, (u))
 
 char* format_timestamp_style(char *buf, size_t l, usec_t t, TimestampStyle style) _warn_unused_result_;
-char* format_timestamp_relative(char *buf, size_t l, usec_t t) _warn_unused_result_;
+char* format_timestamp_relative_full(char *buf, size_t l, usec_t t, clockid_t clock, bool implicit_left) _warn_unused_result_;
 char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) _warn_unused_result_;
 
 _warn_unused_result_
+static inline char* format_timestamp_relative(char *buf, size_t l, usec_t t) {
+        return format_timestamp_relative_full(buf, l, t, CLOCK_REALTIME, /* implicit_left = */ false);
+}
+_warn_unused_result_
+static inline char* format_timestamp_relative_monotonic(char *buf, size_t l, usec_t t) {
+        return format_timestamp_relative_full(buf, l, t, CLOCK_MONOTONIC, /* implicit_left = */ false);
+}
+
+_warn_unused_result_
 static inline char* format_timestamp(char *buf, size_t l, usec_t t) {
         return format_timestamp_style(buf, l, t, TIMESTAMP_PRETTY);
 }
@@ -137,19 +146,21 @@ static inline char* format_timestamp(char *buf, size_t l, usec_t t) {
 #define FORMAT_TIMESTAMP(t) format_timestamp((char[FORMAT_TIMESTAMP_MAX]){}, FORMAT_TIMESTAMP_MAX, t)
 #define FORMAT_TIMESTAMP_RELATIVE(t)                                    \
         format_timestamp_relative((char[FORMAT_TIMESTAMP_RELATIVE_MAX]){}, FORMAT_TIMESTAMP_RELATIVE_MAX, t)
+#define FORMAT_TIMESTAMP_RELATIVE_MONOTONIC(t)                          \
+        format_timestamp_relative_monotonic((char[FORMAT_TIMESTAMP_RELATIVE_MAX]){}, FORMAT_TIMESTAMP_RELATIVE_MAX, t)
 #define FORMAT_TIMESPAN(t, accuracy) format_timespan((char[FORMAT_TIMESPAN_MAX]){}, FORMAT_TIMESPAN_MAX, t, accuracy)
 #define FORMAT_TIMESTAMP_STYLE(t, style) \
         format_timestamp_style((char[FORMAT_TIMESTAMP_MAX]){}, FORMAT_TIMESTAMP_MAX, t, style)
 
-int parse_timestamp(const char *t, usec_t *usec);
+int parse_timestamp(const char *t, usec_t *ret);
 
-int parse_sec(const char *t, usec_t *usec);
-int parse_sec_fix_0(const char *t, usec_t *usec);
-int parse_sec_def_infinity(const char *t, usec_t *usec);
-int parse_time(const char *t, usec_t *usec, usec_t default_unit);
-int parse_nsec(const char *t, nsec_t *nsec);
+int parse_sec(const char *t, usec_t *ret);
+int parse_sec_fix_0(const char *t, usec_t *ret);
+int parse_sec_def_infinity(const char *t, usec_t *ret);
+int parse_time(const char *t, usec_t *ret, usec_t default_unit);
+int parse_nsec(const char *t, nsec_t *ret);
 
-int get_timezones(char ***l);
+int get_timezones(char ***ret);
 int verify_timezone(const char *name, int log_level);
 static inline bool timezone_is_valid(const char *name, int log_level) {
         return verify_timezone(name, log_level) >= 0;
@@ -159,7 +170,7 @@ bool clock_supported(clockid_t clock);
 
 usec_t usec_shift_clock(usec_t, clockid_t from, clockid_t to);
 
-int get_timezone(char **timezone);
+int get_timezone(char **ret);
 
 time_t mktime_or_timegm(struct tm *tm, bool utc);
 struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc);
@@ -200,13 +211,17 @@ static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) {
         return usec_sub_unsigned(timestamp, (usec_t) delta);
 }
 
+/* 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...
+ * 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)
 #if SIZEOF_TIME_T == 8
-  /* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit
-   * year territory. However, since we want to stay away from this in all timezones we take one day off. */
-#  define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 253402214399000000)
+#  define USEC_TIMESTAMP_FORMATTABLE_MAX USEC_TIMESTAMP_FORMATTABLE_MAX_64BIT
 #elif SIZEOF_TIME_T == 4
-/* With a 32bit time_t we can't go beyond 2038... */
-#  define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000)
+#  define USEC_TIMESTAMP_FORMATTABLE_MAX USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT
 #else
 #  error "Yuck, time_t is neither 4 nor 8 bytes wide?"
 #endif
diff --git a/src/libnm-systemd-shared/src/basic/tmpfile-util.c b/src/libnm-systemd-shared/src/basic/tmpfile-util.c
index 2a27f86b..a66ee82d 100644
--- a/src/libnm-systemd-shared/src/basic/tmpfile-util.c
+++ b/src/libnm-systemd-shared/src/basic/tmpfile-util.c
@@ -16,34 +16,22 @@
 #include "path-util.h"
 #include "process-util.h"
 #include "random-util.h"
+#include "stat-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
+#include "sync-util.h"
 #include "tmpfile-util.h"
 #include "umask-util.h"
 
-int fopen_temporary(const char *path, FILE **ret_f, char **ret_temp_path) {
+static int fopen_temporary_internal(int dir_fd, const char *path, FILE **ret_file) {
         _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_free_ char *t = NULL;
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         int r;
 
-        if (path) {
-                r = tempfn_xxxxxx(path, NULL, &t);
-                if (r < 0)
-                        return r;
-        } else {
-                const char *d;
-
-                r = tmp_dir(&d);
-                if (r < 0)
-                        return r;
-
-                t = path_join(d, "XXXXXX");
-                if (!t)
-                        return -ENOMEM;
-        }
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
 
-        fd = mkostemp_safe(t);
+        fd = openat(dir_fd, path, O_CLOEXEC|O_NOCTTY|O_RDWR|O_CREAT|O_EXCL, 0600);
         if (fd < 0)
                 return -errno;
 
@@ -52,15 +40,59 @@ int fopen_temporary(const char *path, FILE **ret_f, char **ret_temp_path) {
 
         r = take_fdopen_unlocked(&fd, "w", &f);
         if (r < 0) {
-                (void) unlink(t);
+                (void) unlinkat(dir_fd, path, 0);
+                return r;
+        }
+
+        if (ret_file)
+                *ret_file = TAKE_PTR(f);
+
+        return 0;
+}
+
+int fopen_temporary_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+
+        r = tempfn_random(path, NULL, &t);
+        if (r < 0)
                 return r;
+
+        r = fopen_temporary_internal(dir_fd, t, ret_file);
+        if (r < 0)
+                return r;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(t);
+
+        return 0;
+}
+
+int fopen_temporary_child_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+
+        if (!path) {
+                r = tmp_dir(&path);
+                if (r < 0)
+                        return r;
         }
 
-        if (ret_f)
-                *ret_f = TAKE_PTR(f);
+        r = tempfn_random_child(path, NULL, &t);
+        if (r < 0)
+                return r;
 
-        if (ret_temp_path)
-                *ret_temp_path = TAKE_PTR(t);
+        r = fopen_temporary_internal(dir_fd, t, ret_file);
+        if (r < 0)
+                return r;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(t);
 
         return 0;
 }
@@ -74,7 +106,7 @@ int mkostemp_safe(char *pattern) {
 
 #if 0 /* NM_IGNORED */
 int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) {
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         FILE *f;
 
         fd = mkostemp_safe(pattern);
@@ -171,7 +203,6 @@ int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
         return tempfn_build(p, extra, "XXXXXX", /* child = */ false, ret);
 }
 
-#if 0 /* NM_IGNORED */
 int tempfn_random(const char *p, const char *extra, char **ret) {
         _cleanup_free_ char *s = NULL;
 
@@ -216,6 +247,7 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) {
         return tempfn_build(p, extra, s, /* child = */ true, ret);
 }
 
+#if 0 /* NM_IGNORED */
 int open_tmpfile_unlinkable(const char *directory, int flags) {
         char *p;
         int fd, r;
@@ -246,7 +278,7 @@ int open_tmpfile_unlinkable(const char *directory, int flags) {
         return fd;
 }
 
-int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
+int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **ret_path) {
         _cleanup_free_ char *tmp = NULL;
         int r, fd;
 
@@ -260,7 +292,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
          * which case "ret_path" will be returned as NULL. If not possible the temporary path name used is returned in
          * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */
 
-        fd = open_parent(target, O_TMPFILE|flags, 0640);
+        fd = open_parent_at(dir_fd, target, O_TMPFILE|flags, 0640);
         if (fd >= 0) {
                 *ret_path = NULL;
                 return fd;
@@ -272,7 +304,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
         if (r < 0)
                 return r;
 
-        fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
+        fd = openat(dir_fd, tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
         if (fd < 0)
                 return -errno;
 
@@ -284,7 +316,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
 int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file) {
         _cleanup_free_ char *path = NULL;
         _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         assert(target);
         assert(ret_file);
@@ -303,24 +335,84 @@ int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE
         return 0;
 }
 
-int link_tmpfile(int fd, const char *path, const char *target) {
+static int link_fd(int fd, int newdirfd, const char *newpath) {
+        int r;
+
+        assert(fd >= 0);
+        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
+        assert(newpath);
+
+        /* Try symlinking via /proc/fd/ first. */
+        r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW));
+        if (r != -ENOENT)
+                return r;
+
+        /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a
+         * more recent kernel, but does not require /proc/ mounted) */
+        if (proc_mounted() != 0)
+                return r;
+
+        return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
+}
+
+int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, LinkTmpfileFlags flags) {
+        _cleanup_free_ char *tmp = NULL;
+        int r;
+
         assert(fd >= 0);
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
         assert(target);
 
-        /* Moves a temporary file created with open_tmpfile() above into its final place. if "path" is NULL an fd
-         * created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE is not supported
-         * on the directory, and renameat2() is used instead.
-         *
-         * Note that in both cases we will not replace existing files. This is because linkat() does not support this
-         * operation currently (renameat2() does), and there is no nice way to emulate this. */
+        /* Moves a temporary file created with open_tmpfile() above into its final place. If "path" is NULL
+         * an fd created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE
+         * is not supported on the directory, and renameat2() is used instead. */
+
+        if (FLAGS_SET(flags, LINK_TMPFILE_SYNC) && fsync(fd) < 0)
+                return -errno;
+
+        if (path) {
+                if (FLAGS_SET(flags, LINK_TMPFILE_REPLACE))
+                        r = RET_NERRNO(renameat(dir_fd, path, dir_fd, target));
+                else
+                        r = rename_noreplace(dir_fd, path, dir_fd, target);
+                if (r < 0)
+                        return r;
+        } else {
+
+                r = link_fd(fd, dir_fd, target);
+                if (r != -EEXIST || !FLAGS_SET(flags, LINK_TMPFILE_REPLACE))
+                        return r;
+
+                /* So the target already exists and we were asked to replace it. That sucks a bit, since the kernel's
+                 * linkat() logic does not allow that. We work-around this by linking the file to a random name
+                 * first, and then renaming that to the final name. This reintroduces the race O_TMPFILE kinda is
+                 * trying to fix, but at least the vulnerability window (i.e. where the file is linked into the file
+                 * system under a temporary name) is very short. */
+
+                r = tempfn_random(target, NULL, &tmp);
+                if (r < 0)
+                        return r;
+
+                if (link_fd(fd, dir_fd, tmp) < 0)
+                        return -EEXIST; /* propagate original error */
+
+                r = RET_NERRNO(renameat(dir_fd, tmp, dir_fd, target));
+                if (r < 0) {
+                        (void) unlinkat(dir_fd, tmp, 0);
+                        return r;
+                }
+        }
 
-        if (path)
-                return rename_noreplace(AT_FDCWD, path, AT_FDCWD, target);
+        if (FLAGS_SET(flags, LINK_TMPFILE_SYNC)) {
+                r = fsync_full(fd);
+                if (r < 0)
+                        return r;
+        }
 
-        return RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), AT_FDCWD, target, AT_SYMLINK_FOLLOW));
+        return 0;
 }
 
-int flink_tmpfile(FILE *f, const char *path, const char *target) {
+int flink_tmpfile(FILE *f, const char *path, const char *target, LinkTmpfileFlags flags) {
         int fd, r;
 
         assert(f);
@@ -330,11 +422,11 @@ int flink_tmpfile(FILE *f, const char *path, const char *target) {
         if (fd < 0) /* Not all FILE* objects encapsulate fds */
                 return -EBADF;
 
-        r = fflush_sync_and_check(f);
+        r = fflush_and_check(f);
         if (r < 0)
                 return r;
 
-        return link_tmpfile(fd, path, target);
+        return link_tmpfile(fd, path, target, flags);
 }
 
 int mkdtemp_malloc(const char *template, char **ret) {
@@ -363,4 +455,24 @@ int mkdtemp_malloc(const char *template, char **ret) {
         *ret = TAKE_PTR(p);
         return 0;
 }
+
+int mkdtemp_open(const char *template, int flags, char **ret) {
+        _cleanup_free_ char *p = NULL;
+        int fd, r;
+
+        r = mkdtemp_malloc(template, &p);
+        if (r < 0)
+                return r;
+
+        fd = RET_NERRNO(open(p, O_DIRECTORY|O_CLOEXEC|flags));
+        if (fd < 0) {
+                (void) rmdir(p);
+                return fd;
+        }
+
+        if (ret)
+                *ret = TAKE_PTR(p);
+
+        return fd;
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/tmpfile-util.h b/src/libnm-systemd-shared/src/basic/tmpfile-util.h
index 610cbaf8..50904eca 100644
--- a/src/libnm-systemd-shared/src/basic/tmpfile-util.h
+++ b/src/libnm-systemd-shared/src/basic/tmpfile-util.h
@@ -1,9 +1,20 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <fcntl.h>
+#include <stdbool.h>
 #include <stdio.h>
 
-int fopen_temporary(const char *path, FILE **_f, char **_temp_path);
+int fopen_temporary_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path);
+static inline int fopen_temporary(const char *path, FILE **ret_file, char **ret_path) {
+        return fopen_temporary_at(AT_FDCWD, path, ret_file, ret_path);
+}
+
+int fopen_temporary_child_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path);
+static inline int fopen_temporary_child(const char *path, FILE **ret_file, char **ret_path) {
+        return fopen_temporary_child_at(AT_FDCWD, path, ret_file, ret_path);
+}
+
 int mkostemp_safe(char *pattern);
 int fmkostemp_safe(char *pattern, const char *mode, FILE**_f);
 
@@ -12,10 +23,23 @@ int tempfn_random(const char *p, const char *extra, char **ret);
 int tempfn_random_child(const char *p, const char *extra, char **ret);
 
 int open_tmpfile_unlinkable(const char *directory, int flags);
-int open_tmpfile_linkable(const char *target, int flags, char **ret_path);
+int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **ret_path);
+static inline int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
+        return open_tmpfile_linkable_at(AT_FDCWD, target, flags, ret_path);
+}
 int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file);
 
-int link_tmpfile(int fd, const char *path, const char *target);
-int flink_tmpfile(FILE *f, const char *path, const char *target);
+
+typedef enum LinkTmpfileFlags {
+        LINK_TMPFILE_REPLACE = 1 << 0,
+        LINK_TMPFILE_SYNC    = 1 << 1,
+} LinkTmpfileFlags;
+
+int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, LinkTmpfileFlags flags);
+static inline int link_tmpfile(int fd, const char *path, const char *target, LinkTmpfileFlags flags) {
+        return link_tmpfile_at(fd, AT_FDCWD, path, target, flags);
+}
+int flink_tmpfile(FILE *f, const char *path, const char *target, LinkTmpfileFlags flags);
 
 int mkdtemp_malloc(const char *template, char **ret);
+int mkdtemp_open(const char *template, int flags, char **ret);
diff --git a/src/libnm-systemd-shared/src/basic/umask-util.h b/src/libnm-systemd-shared/src/basic/umask-util.h
index 90d18f70..6f0e1cc2 100644
--- a/src/libnm-systemd-shared/src/basic/umask-util.h
+++ b/src/libnm-systemd-shared/src/basic/umask-util.h
@@ -15,12 +15,12 @@ static inline void umaskp(mode_t *u) {
 
 /* We make use of the fact here that the umask() concept is using 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 RUN_WITH_UMASK() loop, so that we can run the first
- * one, and exit on the second. */
+ * 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. */
 
 assert_cc((S_IFMT & 0777) == 0);
 
-#define RUN_WITH_UMASK(mask)                                            \
+#define WITH_UMASK(mask)                                            \
         for (_cleanup_umask_ mode_t _saved_umask_ = umask(mask) | S_IFMT; \
              FLAGS_SET(_saved_umask_, S_IFMT);                          \
              _saved_umask_ &= 0777)
diff --git a/src/libnm-systemd-shared/src/basic/user-util.h b/src/libnm-systemd-shared/src/basic/user-util.h
index a08683bc..8b829a9a 100644
--- a/src/libnm-systemd-shared/src/basic/user-util.h
+++ b/src/libnm-systemd-shared/src/basic/user-util.h
@@ -80,7 +80,8 @@ int take_etc_passwd_lock(const char *root);
 #define UID_MAPPED_ROOT ((uid_t) (INT32_MAX-1))
 #define GID_MAPPED_ROOT ((gid_t) (INT32_MAX-1))
 
-#define ETC_PASSWD_LOCK_PATH "/etc/.pwd.lock"
+#define ETC_PASSWD_LOCK_FILENAME ".pwd.lock"
+#define ETC_PASSWD_LOCK_PATH "/etc/" ETC_PASSWD_LOCK_FILENAME
 
 /* The following macros add 1 when converting things, since UID 0 is a valid UID, while the pointer
  * NULL is special */
@@ -102,7 +103,7 @@ typedef enum ValidUserFlags {
 
 bool valid_user_group_name(const char *u, ValidUserFlags flags);
 bool valid_gecos(const char *d);
-char *mangle_gecos(const char *d);
+char* mangle_gecos(const char *d);
 bool valid_home(const char *p);
 
 static inline bool valid_shell(const char *p) {
@@ -130,11 +131,12 @@ int putsgent_sane(const struct sgrp *sg, FILE *stream);
 #endif
 
 bool is_nologin_shell(const char *shell);
+const char* default_root_shell_at(int rfd);
 const char* default_root_shell(const char *root);
 
 int is_this_me(const char *username);
 
-const char *get_home_root(void);
+const char* get_home_root(void);
 
 static inline bool hashed_password_is_locked_or_invalid(const char *password) {
         return password && password[0] != '$';
@@ -148,3 +150,8 @@ static inline bool hashed_password_is_locked_or_invalid(const char *password) {
 
 /* A password indicating "hey, no password required for login" */
 #define PASSWORD_NONE ""
+
+/* Used by sysusers to indicate that the password should be filled in by firstboot.
+ * Also see https://github.com/systemd/systemd/pull/24680#pullrequestreview-1439464325.
+ */
+#define PASSWORD_UNPROVISIONED "!unprovisioned"
diff --git a/src/libnm-systemd-shared/src/basic/util.c b/src/libnm-systemd-shared/src/basic/util.c
deleted file mode 100644
index 3425d0f0..00000000
--- a/src/libnm-systemd-shared/src/basic/util.c
+++ /dev/null
@@ -1,178 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-
-#include "nm-sd-adapt-shared.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/mman.h>
-
-#include "alloc-util.h"
-#include "build.h"
-#include "env-file.h"
-#include "env-util.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "hostname-util.h"
-#include "log.h"
-#include "macro.h"
-#include "parse-util.h"
-#include "stat-util.h"
-#include "string-util.h"
-#include "util.h"
-#include "virt.h"
-
-#if 0 /* NM_IGNORED */
-int saved_argc = 0;
-char **saved_argv = NULL;
-static int saved_in_initrd = -1;
-
-bool kexec_loaded(void) {
-       _cleanup_free_ char *s = NULL;
-
-       if (read_one_line_file("/sys/kernel/kexec_loaded", &s) < 0)
-               return false;
-
-       return s[0] == '1';
-}
-
-int prot_from_flags(int flags) {
-
-        switch (flags & O_ACCMODE) {
-
-        case O_RDONLY:
-                return PROT_READ;
-
-        case O_WRONLY:
-                return PROT_WRITE;
-
-        case O_RDWR:
-                return PROT_READ|PROT_WRITE;
-
-        default:
-                return -EINVAL;
-        }
-}
-
-bool in_initrd(void) {
-        int r;
-        const char *e;
-        bool lenient = false;
-
-        if (saved_in_initrd >= 0)
-                return saved_in_initrd;
-
-        /* We have two checks here:
-         *
-         * 1. the flag file /etc/initrd-release must exist
-         * 2. the root file system must be a memory file system
-         *
-         * The second check is extra paranoia, since misdetecting an
-         * initrd can have bad consequences due the initrd
-         * emptying when transititioning to the main systemd.
-         *
-         * If env var $SYSTEMD_IN_INITRD is not set or set to "auto",
-         * both checks are used. If it's set to "lenient", only check
-         * 1 is used. If set to a boolean value, then the boolean
-         * value is returned.
-         */
-
-        e = secure_getenv("SYSTEMD_IN_INITRD");
-        if (e) {
-                if (streq(e, "lenient"))
-                        lenient = true;
-                else if (!streq(e, "auto")) {
-                        r = parse_boolean(e);
-                        if (r >= 0) {
-                                saved_in_initrd = r > 0;
-                                return saved_in_initrd;
-                        }
-                        log_debug_errno(r, "Failed to parse $SYSTEMD_IN_INITRD, ignoring: %m");
-                }
-        }
-
-        if (!lenient) {
-                r = path_is_temporary_fs("/");
-                if (r < 0)
-                        log_debug_errno(r, "Couldn't determine if / is a temporary file system: %m");
-
-                saved_in_initrd = r > 0;
-        }
-
-        r = access("/etc/initrd-release", F_OK);
-        if (r >= 0) {
-                if (saved_in_initrd == 0)
-                        log_debug("/etc/initrd-release exists, but it's not an initrd.");
-                else
-                        saved_in_initrd = 1;
-        } else {
-                if (errno != ENOENT)
-                        log_debug_errno(errno, "Failed to test if /etc/initrd-release exists: %m");
-                saved_in_initrd = 0;
-        }
-
-        return saved_in_initrd;
-}
-
-void in_initrd_force(bool value) {
-        saved_in_initrd = value;
-}
-
-int container_get_leader(const char *machine, pid_t *pid) {
-        _cleanup_free_ char *s = NULL, *class = NULL;
-        const char *p;
-        pid_t leader;
-        int r;
-
-        assert(machine);
-        assert(pid);
-
-        if (streq(machine, ".host")) {
-                *pid = 1;
-                return 0;
-        }
-
-        if (!hostname_is_valid(machine, 0))
-                return -EINVAL;
-
-        p = strjoina("/run/systemd/machines/", machine);
-        r = parse_env_file(NULL, p,
-                           "LEADER", &s,
-                           "CLASS", &class);
-        if (r == -ENOENT)
-                return -EHOSTDOWN;
-        if (r < 0)
-                return r;
-        if (!s)
-                return -EIO;
-
-        if (!streq_ptr(class, "container"))
-                return -EIO;
-
-        r = parse_pid(s, &leader);
-        if (r < 0)
-                return r;
-        if (leader <= 1)
-                return -EIO;
-
-        *pid = leader;
-        return 0;
-}
-
-int version(void) {
-        printf("systemd " STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")\n%s\n",
-               systemd_features);
-        return 0;
-}
-
-/* Turn off core dumps but only if we're running outside of a container. */
-void disable_coredumps(void) {
-        int r;
-
-        if (detect_container() > 0)
-                return;
-
-        r = write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", WRITE_STRING_FILE_DISABLE_BUFFER);
-        if (r < 0)
-                log_debug_errno(r, "Failed to turn off coredumps, ignoring: %m");
-}
-#endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/util.h b/src/libnm-systemd-shared/src/fundamental/logarithm.h
index 368e3e4f..5f742951 100644
--- a/src/libnm-systemd-shared/src/basic/util.h
+++ b/src/libnm-systemd-shared/src/fundamental/logarithm.h
@@ -3,29 +3,6 @@
 
 #include <stdint.h>
 
-#include "macro.h"
-
-extern int saved_argc;
-extern char **saved_argv;
-
-static inline void save_argc_argv(int argc, char **argv) {
-
-        /* Protect against CVE-2021-4034 style attacks */
-        assert_se(argc > 0);
-        assert_se(argv);
-        assert_se(argv[0]);
-
-        saved_argc = argc;
-        saved_argv = argv;
-}
-
-bool kexec_loaded(void);
-
-int prot_from_flags(int flags) _const_;
-
-bool in_initrd(void);
-void in_initrd_force(bool value);
-
 /* Note: log2(0) == log2(1) == 0 here and below. */
 
 #define CONST_LOG2ULL(x) ((x) > 1 ? (unsigned) __builtin_clzll(x) ^ 63U : 0)
@@ -55,6 +32,14 @@ static inline unsigned u32ctz(uint32_t n) {
 #endif
 }
 
+#define popcount(n)                                             \
+        _Generic((n),                                           \
+                 unsigned char: __builtin_popcount(n),          \
+                 unsigned short: __builtin_popcount(n),         \
+                 unsigned: __builtin_popcount(n),               \
+                 unsigned long: __builtin_popcountl(n),         \
+                 unsigned long long: __builtin_popcountll(n))
+
 #define CONST_LOG2U(x) ((x) > 1 ? __SIZEOF_INT__ * 8 - __builtin_clz(x) - 1 : 0)
 #define NONCONST_LOG2U(x) ({                                             \
                 unsigned _x = (x);                                       \
@@ -80,9 +65,3 @@ static inline unsigned log2u_round_up(unsigned x) {
 
         return log2u(x - 1) + 1;
 }
-
-int container_get_leader(const char *machine, pid_t *pid);
-
-int version(void);
-
-void disable_coredumps(void);
diff --git a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h
index 0c69d98a..89b83e7d 100644
--- a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h
+++ b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h
@@ -1,17 +1,18 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
-#ifndef SD_BOOT
+#if !SD_BOOT
 #  include <assert.h>
 #endif
 
 #include <limits.h>
+#include <stdalign.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 
 #define _align_(x) __attribute__((__aligned__(x)))
-#define _alignas_(x) __attribute__((__aligned__(__alignof__(x))))
+#define _alignas_(x) __attribute__((__aligned__(alignof(x))))
 #define _alignptr_ __attribute__((__aligned__(sizeof(void *))))
 #define _cleanup_(x) __attribute__((__cleanup__(x)))
 #define _const_ __attribute__((__const__))
@@ -20,6 +21,7 @@
 #define _hidden_ __attribute__((__visibility__("hidden")))
 #define _likely_(x) (__builtin_expect(!!(x), 1))
 #define _malloc_ __attribute__((__malloc__))
+#define _noinline_ __attribute__((noinline))
 #define _noreturn_ _Noreturn
 #define _packed_ __attribute__((__packed__))
 #define _printf_(a, b) __attribute__((__format__(printf, a, b)))
@@ -36,6 +38,13 @@
 #define _weak_ __attribute__((__weak__))
 #define _weakref_(x) __attribute__((__weakref__(#x)))
 
+/* !NM_IGNORED */
+#if __GNUC__ <= 4
+#undef _returns_nonnull_
+#define _returns_nonnull_
+#endif
+/* !NM_IGNORED */
+
 #ifdef __clang__
 #  define _alloc_(...)
 #else
@@ -66,18 +75,18 @@
 #define XCONCATENATE(x, y) x ## y
 #define CONCATENATE(x, y) XCONCATENATE(x, y)
 
-#ifdef SD_BOOT
+#if SD_BOOT
         _noreturn_ void efi_assert(const char *expr, const char *file, unsigned line, const char *function);
 
         #ifdef NDEBUG
                 #define assert(expr)
                 #define assert_not_reached() __builtin_unreachable()
         #else
-                #define assert(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); })
-                #define assert_not_reached() efi_assert("Code should not be reached", __FILE__, __LINE__, __PRETTY_FUNCTION__)
+                #define assert(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __func__); })
+                #define assert_not_reached() efi_assert("Code should not be reached", __FILE__, __LINE__, __func__)
         #endif
         #define static_assert _Static_assert
-        #define assert_se(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); })
+        #define assert_se(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __func__); })
 #endif
 
 /* This passes the argument through after (if asserts are enabled) checking that it is not null. */
@@ -106,7 +115,6 @@
 
 #define assert_cc(expr) static_assert(expr, #expr)
 
-
 #define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq))
 #define UNIQ __COUNTER__
 
@@ -259,47 +267,54 @@
                 (UNIQ_T(X, xq) / UNIQ_T(Y, yq) + !!(UNIQ_T(X, xq) % UNIQ_T(Y, yq))); \
         })
 
-#define CASE_F(X) case X:
-#define CASE_F_1(CASE, X) CASE_F(X)
-#define CASE_F_2(CASE, X, ...)  CASE(X) CASE_F_1(CASE, __VA_ARGS__)
-#define CASE_F_3(CASE, X, ...)  CASE(X) CASE_F_2(CASE, __VA_ARGS__)
-#define CASE_F_4(CASE, X, ...)  CASE(X) CASE_F_3(CASE, __VA_ARGS__)
-#define CASE_F_5(CASE, X, ...)  CASE(X) CASE_F_4(CASE, __VA_ARGS__)
-#define CASE_F_6(CASE, X, ...)  CASE(X) CASE_F_5(CASE, __VA_ARGS__)
-#define CASE_F_7(CASE, X, ...)  CASE(X) CASE_F_6(CASE, __VA_ARGS__)
-#define CASE_F_8(CASE, X, ...)  CASE(X) CASE_F_7(CASE, __VA_ARGS__)
-#define CASE_F_9(CASE, X, ...)  CASE(X) CASE_F_8(CASE, __VA_ARGS__)
-#define CASE_F_10(CASE, X, ...) CASE(X) CASE_F_9(CASE, __VA_ARGS__)
-#define CASE_F_11(CASE, X, ...) CASE(X) CASE_F_10(CASE, __VA_ARGS__)
-#define CASE_F_12(CASE, X, ...) CASE(X) CASE_F_11(CASE, __VA_ARGS__)
-#define CASE_F_13(CASE, X, ...) CASE(X) CASE_F_12(CASE, __VA_ARGS__)
-#define CASE_F_14(CASE, X, ...) CASE(X) CASE_F_13(CASE, __VA_ARGS__)
-#define CASE_F_15(CASE, X, ...) CASE(X) CASE_F_14(CASE, __VA_ARGS__)
-#define CASE_F_16(CASE, X, ...) CASE(X) CASE_F_15(CASE, __VA_ARGS__)
-#define CASE_F_17(CASE, X, ...) CASE(X) CASE_F_16(CASE, __VA_ARGS__)
-#define CASE_F_18(CASE, X, ...) CASE(X) CASE_F_17(CASE, __VA_ARGS__)
-#define CASE_F_19(CASE, X, ...) CASE(X) CASE_F_18(CASE, __VA_ARGS__)
-#define CASE_F_20(CASE, X, ...) CASE(X) CASE_F_19(CASE, __VA_ARGS__)
+/* Rounds up x to the next multiple of y. Resolves to typeof(x) -1 in case of overflow */
+#define __ROUND_UP(q, x, y)                                             \
+        ({                                                              \
+                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); \
+        })
+#define ROUND_UP(x, y) __ROUND_UP(UNIQ, (x), (y))
+
+#define  CASE_F_1(X)      case X:
+#define  CASE_F_2(X, ...) case X:  CASE_F_1( __VA_ARGS__)
+#define  CASE_F_3(X, ...) case X:  CASE_F_2( __VA_ARGS__)
+#define  CASE_F_4(X, ...) case X:  CASE_F_3( __VA_ARGS__)
+#define  CASE_F_5(X, ...) case X:  CASE_F_4( __VA_ARGS__)
+#define  CASE_F_6(X, ...) case X:  CASE_F_5( __VA_ARGS__)
+#define  CASE_F_7(X, ...) case X:  CASE_F_6( __VA_ARGS__)
+#define  CASE_F_8(X, ...) case X:  CASE_F_7( __VA_ARGS__)
+#define  CASE_F_9(X, ...) case X:  CASE_F_8( __VA_ARGS__)
+#define CASE_F_10(X, ...) case X:  CASE_F_9( __VA_ARGS__)
+#define CASE_F_11(X, ...) case X: CASE_F_10( __VA_ARGS__)
+#define CASE_F_12(X, ...) case X: CASE_F_11( __VA_ARGS__)
+#define CASE_F_13(X, ...) case X: CASE_F_12( __VA_ARGS__)
+#define CASE_F_14(X, ...) case X: CASE_F_13( __VA_ARGS__)
+#define CASE_F_15(X, ...) case X: CASE_F_14( __VA_ARGS__)
+#define CASE_F_16(X, ...) case X: CASE_F_15( __VA_ARGS__)
+#define CASE_F_17(X, ...) case X: CASE_F_16( __VA_ARGS__)
+#define CASE_F_18(X, ...) case X: CASE_F_17( __VA_ARGS__)
+#define CASE_F_19(X, ...) case X: CASE_F_18( __VA_ARGS__)
+#define CASE_F_20(X, ...) case X: CASE_F_19( __VA_ARGS__)
 
 #define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME
 #define FOR_EACH_MAKE_CASE(...) \
         GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \
                                CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \
-                   (CASE_F,__VA_ARGS__)
+                   (__VA_ARGS__)
 
-#define IN_SET(x, ...)                                                  \
+#define IN_SET(x, first, ...)                                           \
         ({                                                              \
                 bool _found = false;                                    \
-                /* If the build breaks in the line below, you need to extend the case macros. (We use "long double" as  \
-                 * type for the array, in the hope that checkers such as ubsan don't complain that the initializers for \
-                 * the array are not representable by the base type. Ideally we'd use typeof(x) as base type, but that  \
-                 * doesn't work, as we want to use this on bitfields and gcc refuses typeof() on bitfields.) */         \
-                static const long double __assert_in_set[] _unused_ = { __VA_ARGS__ }; \
+                /* If the build breaks in the line below, you need to extend the case macros. We use typeof(+x) \
+                 * here to widen the type of x if it is a bit-field as this would otherwise be illegal. */      \
+                static const typeof(+x) __assert_in_set[] _unused_ = { first, __VA_ARGS__ }; \
                 assert_cc(ELEMENTSOF(__assert_in_set) <= 20);           \
                 switch (x) {                                            \
-                FOR_EACH_MAKE_CASE(__VA_ARGS__)                         \
+                FOR_EACH_MAKE_CASE(first, __VA_ARGS__)                  \
                         _found = true;                                  \
-                       break;                                           \
+                        break;                                          \
                 default:                                                \
                         break;                                          \
                 }                                                       \
@@ -308,13 +323,18 @@
 
 /* Takes inspiration from Rust's Option::take() method: reads and returns a pointer, but at the same time
  * resets it to NULL. See: https://doc.rust-lang.org/std/option/enum.Option.html#method.take */
-#define TAKE_PTR(ptr)                           \
-        ({                                      \
-                typeof(ptr) *_pptr_ = &(ptr);   \
-                typeof(ptr) _ptr_ = *_pptr_;    \
-                *_pptr_ = NULL;                 \
-                _ptr_;                          \
+#define TAKE_GENERIC(var, type, nullvalue)                       \
+        ({                                                       \
+                type *_pvar_ = &(var);                           \
+                type _var_ = *_pvar_;                            \
+                type _nullvalue_ = nullvalue;                    \
+                *_pvar_ = _nullvalue_;                           \
+                _var_;                                           \
         })
+#define TAKE_PTR_TYPE(ptr, type) TAKE_GENERIC(ptr, type, NULL)
+#define TAKE_PTR(ptr) TAKE_PTR_TYPE(ptr, typeof(ptr))
+#define TAKE_STRUCT_TYPE(s, type) TAKE_GENERIC(s, type, {})
+#define TAKE_STRUCT(s) TAKE_STRUCT_TYPE(s, typeof(s))
 
 /*
  * STRLEN - return the length of a string literal, minus the trailing NUL byte.
@@ -338,13 +358,19 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) {
         return ((l + ali - 1) & ~(ali - 1));
 }
 
+#define ALIGN2(l) ALIGN_TO(l, 2)
 #define ALIGN4(l) ALIGN_TO(l, 4)
 #define ALIGN8(l) ALIGN_TO(l, 8)
-#ifndef SD_BOOT
-/* libefi also provides ALIGN, and we do not use them in sd-boot explicitly. */
+#define ALIGN2_PTR(p) ((void*) ALIGN2((uintptr_t) p))
+#define ALIGN4_PTR(p) ((void*) ALIGN4((uintptr_t) p))
+#define ALIGN8_PTR(p) ((void*) ALIGN8((uintptr_t) p))
 #define ALIGN(l)  ALIGN_TO(l, sizeof(void*))
 #define ALIGN_PTR(p) ((void*) ALIGN((uintptr_t) (p)))
-#endif
+
+/* Checks if the specified pointer is aligned as appropriate for the specific type */
+#define IS_ALIGNED16(p) (((uintptr_t) p) % alignof(uint16_t) == 0)
+#define IS_ALIGNED32(p) (((uintptr_t) p) % alignof(uint32_t) == 0)
+#define IS_ALIGNED64(p) (((uintptr_t) p) % alignof(uint64_t) == 0)
 
 /* Same as ALIGN_TO but callable in constant contexts. */
 #define CONST_ALIGN_TO(l, ali)                                         \
@@ -356,9 +382,31 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) {
                 ((l) + (ali) - 1) & ~((ali) - 1),                      \
                 VOID_0)
 
+/* Similar to ((t *) (void *) (p)) to cast a pointer. The macro asserts that the pointer has a suitable
+ * alignment for type "t". This exists for places where otherwise "-Wcast-align=strict" would issue a
+ * warning or if you want to assert that the cast gives a pointer of suitable alignment. */
+#define CAST_ALIGN_PTR(t, p)                                    \
+        ({                                                      \
+                const void *_p = (p);                           \
+                assert(((uintptr_t) _p) % alignof(t) == 0); \
+                (t *) _p;                                       \
+        })
+
 #define UPDATE_FLAG(orig, flag, b)                      \
         ((b) ? ((orig) | (flag)) : ((orig) & ~(flag)))
 #define SET_FLAG(v, flag, b) \
         (v) = UPDATE_FLAG(v, flag, b)
 #define FLAGS_SET(v, flags) \
         ((~(v) & (flags)) == 0)
+
+/* 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.
+ *
+ * See https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=3080ea5553cc909b000d1f1d964a9041962f2c5b
+ */
+#define DECLARE_FLEX_ARRAY(type, name)                 \
+        struct {                                       \
+                dummy_t __empty__ ## name;             \
+                type name[];                           \
+        }
diff --git a/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h b/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h
new file mode 100644
index 00000000..78e2dbec
--- /dev/null
+++ b/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stddef.h>
+
+#if SD_BOOT
+#  include "efi-string.h"
+#else
+#  include <string.h>
+#endif
+
+#include "macro-fundamental.h"
+
+#if !SD_BOOT && HAVE_EXPLICIT_BZERO
+static inline void *explicit_bzero_safe(void *p, size_t l) {
+        if (p && l > 0)
+                explicit_bzero(p, l);
+
+        return p;
+}
+#else
+static inline void *explicit_bzero_safe(void *p, size_t l) {
+        if (p && l > 0) {
+                memset(p, 0, l);
+                __asm__ __volatile__("" : : "r"(p) : "memory");
+        }
+        return p;
+}
+#endif
+
+struct VarEraser {
+        /* NB: This is a pointer to memory to erase in case of CLEANUP_ERASE(). Pointer to pointer to memory
+         * to erase in case of CLEANUP_ERASE_PTR() */
+        void *p;
+        size_t size;
+};
+
+static inline void erase_var(struct VarEraser *e) {
+        explicit_bzero_safe(e->p, e->size);
+}
+
+/* Mark var to be erased when leaving scope. */
+#define CLEANUP_ERASE(var)                                              \
+        _cleanup_(erase_var) _unused_ struct VarEraser CONCATENATE(_eraser_, UNIQ) = { \
+                .p = &(var),                                            \
+                .size = sizeof(var),                                    \
+        }
+
+static inline void erase_varp(struct VarEraser *e) {
+
+        /* Very similar to erase_var(), but assumes `p` is a pointer to a pointer whose memory shall be destructed. */
+        if (!e->p)
+                return;
+
+        explicit_bzero_safe(*(void**) e->p, e->size);
+}
+
+/* Mark pointer so that memory pointed to is erased when leaving scope. Note: this takes a pointer to the
+ * specified pointer, instead of just a copy of it. This is to allow callers to invalidate the pointer after
+ * use, if they like, disabling our automatic erasure (for example because they succeeded with whatever they
+ * wanted to do and now intend to return the allocated buffer to their caller without it being erased). */
+#define CLEANUP_ERASE_PTR(ptr, sz)                                      \
+        _cleanup_(erase_varp) _unused_ struct VarEraser CONCATENATE(_eraser_, UNIQ) = { \
+                .p = (ptr),                                             \
+                .size = (sz),                                           \
+        }
diff --git a/src/libnm-systemd-shared/src/fundamental/sha256.c b/src/libnm-systemd-shared/src/fundamental/sha256.c
index b4c36ab3..a4c6d627 100644
--- a/src/libnm-systemd-shared/src/fundamental/sha256.c
+++ b/src/libnm-systemd-shared/src/fundamental/sha256.c
@@ -24,7 +24,7 @@
    <https://www.gnu.org/licenses/>.  */
 
 #include <stdbool.h>
-#ifdef SD_BOOT
+#if SD_BOOT
 #  include "efi-string.h"
 #else
 #  include <string.h>
@@ -32,6 +32,7 @@
 
 #include "macro-fundamental.h"
 #include "sha256.h"
+#include "unaligned-fundamental.h"
 
 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
 # define SWAP(n)                                                        \
@@ -50,14 +51,6 @@
 # define SWAP64(n) (n)
 #endif
 
-/* The condition below is from glibc's string/string-inline.c.
- * See definition of _STRING_INLINE_unaligned. */
-#if !defined(__mc68020__) && !defined(__s390__) && !defined(__i386__)
-#  define UNALIGNED_P(p) (((uintptr_t) p) % __alignof__(uint32_t) != 0)
-#else
-#  define UNALIGNED_P(p) false
-#endif
-
 /* This array contains the bytes used to pad the buffer to the next
    64-byte boundary.  (FIPS 180-2:5.1.1)  */
 static const uint8_t fillbuf[64] = {
@@ -130,11 +123,7 @@ uint8_t *sha256_finish_ctx(struct sha256_ctx *ctx, uint8_t resbuf[static SHA256_
 
         /* Put result from CTX in first 32 bytes following RESBUF.  */
         for (size_t i = 0; i < 8; ++i)
-                if (UNALIGNED_P(resbuf))
-                        memcpy(resbuf + i * sizeof(uint32_t), (uint32_t[]) { SWAP(ctx->H[i]) }, sizeof(uint32_t));
-                else
-                        ((uint32_t *) (void *) resbuf)[i] = SWAP(ctx->H[i]);
-
+                unaligned_write_ne32(resbuf + i * sizeof(uint32_t), SWAP(ctx->H[i]));
         return resbuf;
 }
 
@@ -167,18 +156,17 @@ void sha256_process_bytes(const void *buffer, size_t len, struct sha256_ctx *ctx
 
         /* Process available complete blocks.  */
         if (len >= 64) {
-                if (UNALIGNED_P(buffer))
+                if (IS_ALIGNED32(buffer)) {
+                        sha256_process_block(buffer, len & ~63, ctx);
+                        buffer = (const char *) buffer + (len & ~63);
+                        len &= 63;
+                } else
                         while (len > 64) {
                                 memcpy(ctx->buffer, buffer, 64);
                                 sha256_process_block(ctx->buffer, 64, ctx);
                                 buffer = (const char *) buffer + 64;
                                 len -= 64;
                         }
-                else {
-                        sha256_process_block(buffer, len & ~63, ctx);
-                        buffer = (const char *) buffer + (len & ~63);
-                        len &= 63;
-                }
         }
 
         /* Move remaining bytes into internal buffer.  */
diff --git a/src/libnm-systemd-shared/src/fundamental/sha256.h b/src/libnm-systemd-shared/src/fundamental/sha256.h
index 31790c2e..dbb08e35 100644
--- a/src/libnm-systemd-shared/src/fundamental/sha256.h
+++ b/src/libnm-systemd-shared/src/fundamental/sha256.h
@@ -1,7 +1,8 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
-#include "stdint.h"
+#include <stddef.h>
+#include <stdint.h>
 
 #define SHA256_DIGEST_SIZE 32
 
@@ -28,6 +29,11 @@ void sha256_init_ctx(struct sha256_ctx *ctx);
 uint8_t *sha256_finish_ctx(struct sha256_ctx *ctx, uint8_t resbuf[static SHA256_DIGEST_SIZE]);
 void sha256_process_bytes(const void *buffer, size_t len, struct sha256_ctx *ctx);
 
+static inline void sha256_process_bytes_and_size(const void *buffer, size_t len, struct sha256_ctx *ctx) {
+        sha256_process_bytes(&len, sizeof(len), ctx);
+        sha256_process_bytes(buffer, len, ctx);
+}
+
 uint8_t* sha256_direct(const void *buffer, size_t sz, uint8_t result[static SHA256_DIGEST_SIZE]);
 
 #define SHA256_DIRECT(buffer, sz) sha256_direct(buffer, sz, (uint8_t[SHA256_DIGEST_SIZE]) {})
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 33f2d860..3a3e7f59 100644
--- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c
+++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c
@@ -2,7 +2,7 @@
 
 #include "nm-sd-adapt-shared.h"
 
-#ifndef SD_BOOT
+#if !SD_BOOT
 #  include <ctype.h>
 #endif
 
@@ -22,7 +22,6 @@ sd_char *startswith(const sd_char *s, const sd_char *prefix) {
         return (sd_char*) s + l;
 }
 
-#ifndef SD_BOOT
 sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) {
         size_t l;
 
@@ -35,7 +34,6 @@ sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) {
 
         return (sd_char*) s + l;
 }
-#endif
 
 sd_char* endswith(const sd_char *s, const sd_char *postfix) {
         size_t 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 ecf32e51..9019542b 100644
--- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h
+++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h
@@ -1,9 +1,8 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
-#ifdef SD_BOOT
-#  include <efi.h>
-#  include <efilib.h>
+#if SD_BOOT
+#  include "efi.h"
 #  include "efi-string.h"
 #else
 #  include <string.h>
@@ -11,7 +10,7 @@
 
 #include "macro-fundamental.h"
 
-#ifdef SD_BOOT
+#if SD_BOOT
 #  define strlen strlen16
 #  define strcmp strcmp16
 #  define strncmp strncmp16
@@ -59,9 +58,7 @@ static inline size_t strlen_ptr(const sd_char *s) {
 }
 
 sd_char *startswith(const sd_char *s, const sd_char *prefix) _pure_;
-#ifndef SD_BOOT
 sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) _pure_;
-#endif
 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_;
 
@@ -110,6 +107,10 @@ static inline bool ascii_isdigit(sd_char a) {
         return a >= '0' && a <= '9';
 }
 
+static inline bool ascii_ishex(sd_char a) {
+        return ascii_isdigit(a) || (a >= 'a' && a <= 'f') || (a >= 'A' && a <= 'F');
+}
+
 static inline bool ascii_isalpha(sd_char a) {
         /* A pure ASCII, locale independent version of isalpha() */
         return (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z');
diff --git a/src/libnm-systemd-shared/src/shared/dns-domain.c b/src/libnm-systemd-shared/src/shared/dns-domain.c
index 7bba3b18..43f43197 100644
--- a/src/libnm-systemd-shared/src/shared/dns-domain.c
+++ b/src/libnm-systemd-shared/src/shared/dns-domain.c
@@ -302,7 +302,6 @@ 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;
-        const char *p;
         bool contains_8bit = false;
         char buffer[DNS_LABEL_MAX+1];
         int r;
@@ -319,7 +318,7 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded
         if (encoded_size <= 0)
                 return -EINVAL;
 
-        for (p = encoded; p < encoded + encoded_size; p++)
+        for (const char *p = encoded; p < encoded + encoded_size; p++)
                 if ((uint8_t) *p > 127)
                         contains_8bit = true;
 
@@ -534,7 +533,18 @@ int dns_name_compare_func(const char *a, const char *b) {
         }
 }
 
-DEFINE_HASH_OPS(dns_name_hash_ops, char, dns_name_hash_func, dns_name_compare_func);
+DEFINE_HASH_OPS(
+        dns_name_hash_ops,
+        char,
+        dns_name_hash_func,
+        dns_name_compare_func);
+
+DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+        dns_name_hash_ops_free,
+        char,
+        dns_name_hash_func,
+        dns_name_compare_func,
+        free);
 
 int dns_name_equal(const char *x, const char *y) {
         int r, q;
@@ -752,9 +762,8 @@ int dns_name_address(const char *p, int *ret_family, union in_addr_union *ret_ad
                 return r;
         if (r > 0) {
                 uint8_t a[4];
-                unsigned i;
 
-                for (i = 0; i < ELEMENTSOF(a); i++) {
+                for (size_t i = 0; i < ELEMENTSOF(a); i++) {
                         char label[DNS_LABEL_MAX+1];
 
                         r = dns_label_unescape(&p, label, sizeof label, 0);
@@ -788,9 +797,8 @@ int dns_name_address(const char *p, int *ret_family, union in_addr_union *ret_ad
                 return r;
         if (r > 0) {
                 struct in6_addr a;
-                unsigned i;
 
-                for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) {
+                for (size_t i = 0; i < ELEMENTSOF(a.s6_addr); i++) {
                         char label[DNS_LABEL_MAX+1];
                         int x, y;
 
@@ -832,7 +840,6 @@ int dns_name_address(const char *p, int *ret_family, union in_addr_union *ret_ad
 #endif /* NM_IGNORED */
 
 bool dns_name_is_root(const char *name) {
-
         assert(name);
 
         /* There are exactly two ways to encode the root domain name:
@@ -904,8 +911,6 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, boo
 
 #if 0 /* NM_IGNORED */
 static bool srv_type_label_is_valid(const char *label, size_t n) {
-        size_t k;
-
         assert(label);
 
         if (n < 2) /* Label needs to be at least 2 chars long */
@@ -919,12 +924,11 @@ static bool srv_type_label_is_valid(const char *label, size_t n) {
                 return false;
 
         /* Third and further chars must be alphanumeric or a hyphen */
-        for (k = 2; k < n; k++) {
+        for (size_t k = 2; k < n; k++)
                 if (!ascii_isalpha(label[k]) &&
                     !ascii_isdigit(label[k]) &&
                     label[k] != '-')
                         return false;
-        }
 
         return true;
 }
@@ -1120,14 +1124,12 @@ finish:
 }
 
 static int dns_name_build_suffix_table(const char *name, const char *table[]) {
-        const char *p;
+        const char *p = ASSERT_PTR(name);
         unsigned n = 0;
         int r;
 
-        assert(name);
         assert(table);
 
-        p = name;
         for (;;) {
                 if (n > DNS_N_LABELS_MAX)
                         return -EINVAL;
diff --git a/src/libnm-systemd-shared/src/shared/dns-domain.h b/src/libnm-systemd-shared/src/shared/dns-domain.h
index 828e924d..a9449410 100644
--- a/src/libnm-systemd-shared/src/shared/dns-domain.h
+++ b/src/libnm-systemd-shared/src/shared/dns-domain.h
@@ -62,13 +62,10 @@ static inline int dns_name_is_valid_ldh(const char *s) {
         return 1;
 }
 
-static inline bool dns_name_is_empty(const char *s) {
-        return isempty(s) || streq(s, ".");
-}
-
 void dns_name_hash_func(const char *s, struct siphash *state);
 int dns_name_compare_func(const char *a, const char *b);
 extern const struct hash_ops dns_name_hash_ops;
+extern const struct hash_ops dns_name_hash_ops_free;
 
 int dns_name_between(const char *a, const char *b, const char *c);
 int dns_name_equal(const char *x, const char *y);