summary refs log tree commit diff
path: root/shared/systemd/src
diff options
context:
space:
mode:
Diffstat (limited to 'shared/systemd/src')
-rw-r--r--shared/systemd/src/basic/alloc-util.c25
-rw-r--r--shared/systemd/src/basic/alloc-util.h6
-rw-r--r--shared/systemd/src/basic/env-file.c3
-rw-r--r--shared/systemd/src/basic/env-util.c14
-rw-r--r--shared/systemd/src/basic/env-util.h7
-rw-r--r--shared/systemd/src/basic/errno-util.h19
-rw-r--r--shared/systemd/src/basic/escape.c73
-rw-r--r--shared/systemd/src/basic/escape.h6
-rw-r--r--shared/systemd/src/basic/extract-word.c4
-rw-r--r--shared/systemd/src/basic/extract-word.h2
-rw-r--r--shared/systemd/src/basic/fileio.c131
-rw-r--r--shared/systemd/src/basic/fileio.h7
-rw-r--r--shared/systemd/src/basic/format-util.c72
-rw-r--r--shared/systemd/src/basic/format-util.h83
-rw-r--r--shared/systemd/src/basic/fs-util.c128
-rw-r--r--shared/systemd/src/basic/fs-util.h8
-rw-r--r--shared/systemd/src/basic/hash-funcs.c3
-rw-r--r--shared/systemd/src/basic/hash-funcs.h1
-rw-r--r--shared/systemd/src/basic/hashmap.c39
-rw-r--r--shared/systemd/src/basic/hashmap.h4
-rw-r--r--shared/systemd/src/basic/hexdecoct.c25
-rw-r--r--shared/systemd/src/basic/hexdecoct.h5
-rw-r--r--shared/systemd/src/basic/in-addr-util.c30
-rw-r--r--shared/systemd/src/basic/in-addr-util.h2
-rw-r--r--shared/systemd/src/basic/io-util.c84
-rw-r--r--shared/systemd/src/basic/io-util.h17
-rw-r--r--shared/systemd/src/basic/log.h27
-rw-r--r--shared/systemd/src/basic/macro.h24
-rw-r--r--shared/systemd/src/basic/memory-util.h15
-rw-r--r--shared/systemd/src/basic/parse-util.c63
-rw-r--r--shared/systemd/src/basic/parse-util.h6
-rw-r--r--shared/systemd/src/basic/path-util.c151
-rw-r--r--shared/systemd/src/basic/path-util.h37
-rw-r--r--shared/systemd/src/basic/process-util.c280
-rw-r--r--shared/systemd/src/basic/process-util.h12
-rw-r--r--shared/systemd/src/basic/random-util.c191
-rw-r--r--shared/systemd/src/basic/random-util.h4
-rw-r--r--shared/systemd/src/basic/set.h20
-rw-r--r--shared/systemd/src/basic/siphash24.h1
-rw-r--r--shared/systemd/src/basic/socket-util.c33
-rw-r--r--shared/systemd/src/basic/socket-util.h3
-rw-r--r--shared/systemd/src/basic/string-util.c50
-rw-r--r--shared/systemd/src/basic/string-util.h26
-rw-r--r--shared/systemd/src/basic/strv.c69
-rw-r--r--shared/systemd/src/basic/strv.h5
-rw-r--r--shared/systemd/src/basic/strxcpyx.c118
-rw-r--r--shared/systemd/src/basic/strxcpyx.h14
-rw-r--r--shared/systemd/src/basic/time-util.c28
-rw-r--r--shared/systemd/src/basic/time-util.h14
-rw-r--r--shared/systemd/src/basic/tmpfile-util.c19
-rw-r--r--shared/systemd/src/basic/umask-util.h20
-rw-r--r--shared/systemd/src/basic/utf8.c76
-rw-r--r--shared/systemd/src/basic/utf8.h5
-rw-r--r--shared/systemd/src/shared/dns-domain.c1387
-rw-r--r--shared/systemd/src/shared/dns-domain.h114
55 files changed, 2914 insertions, 696 deletions
diff --git a/shared/systemd/src/basic/alloc-util.c b/shared/systemd/src/basic/alloc-util.c
index 92b350bc..c97d8700 100644
--- a/shared/systemd/src/basic/alloc-util.c
+++ b/shared/systemd/src/basic/alloc-util.c
@@ -66,8 +66,31 @@ void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) {
         if (!q)
                 return NULL;
 
+        if (size > 0) {
+                size_t bn;
+
+                /* Adjust for the 64 byte minimum */
+                newalloc = a / size;
+
+                bn = malloc_usable_size(q) / size;
+                if (bn > newalloc) {
+                        void *qq;
+
+                        /* The actual size allocated is larger than what we asked for. Let's call realloc() again to
+                         * take possession of the extra space. This should be cheap, since libc doesn't have to move
+                         * the memory for this. */
+
+                        qq = realloc(q, bn * size);
+                        if (_likely_(qq)) {
+                                *p = qq;
+                                *allocated = bn;
+                                return qq;
+                        }
+                }
+        }
+
         *p = q;
-        *allocated = _unlikely_(size == 0) ? newalloc : malloc_usable_size(q) / size;
+        *allocated = newalloc;
         return q;
 }
 
diff --git a/shared/systemd/src/basic/alloc-util.h b/shared/systemd/src/basic/alloc-util.h
index 9b20be47..64d9e003 100644
--- a/shared/systemd/src/basic/alloc-util.h
+++ b/shared/systemd/src/basic/alloc-util.h
@@ -58,7 +58,7 @@ static inline void *mfree(void *memory) {
         })
 
 void* memdup(const void *p, size_t l) _alloc_(2);
-void* memdup_suffix0(const void *p, size_t l) _alloc_(2);
+void* memdup_suffix0(const void *p, size_t l); /* We can't use _alloc_() here, since we return a buffer one byte larger than the specified size */
 
 #define memdupa(p, l)                           \
         ({                                      \
@@ -112,7 +112,9 @@ _alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, si
         return memdup(p, size * need);
 }
 
-_alloc_(2, 3) static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t need) {
+/* Note that we can't decorate this function with _alloc_() since the returned memory area is one byte larger
+ * than the product of its parameters. */
+static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t need) {
         if (size_multiply_overflow(size, need))
                 return NULL;
 
diff --git a/shared/systemd/src/basic/env-file.c b/shared/systemd/src/basic/env-file.c
index 4a0f9c39..5860b050 100644
--- a/shared/systemd/src/basic/env-file.c
+++ b/shared/systemd/src/basic/env-file.c
@@ -2,8 +2,6 @@
 
 #include "nm-sd-adapt-shared.h"
 
-#include <stdio_ext.h>
-
 #include "alloc-util.h"
 #include "env-file.h"
 #include "env-util.h"
@@ -548,7 +546,6 @@ int write_env_file(const char *fname, char **l) {
         if (r < 0)
                 return r;
 
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
         (void) fchmod_umask(fileno(f), 0644);
 
         STRV_FOREACH(i, l)
diff --git a/shared/systemd/src/basic/env-util.c b/shared/systemd/src/basic/env-util.c
index dc10362d..cd9d3176 100644
--- a/shared/systemd/src/basic/env-util.c
+++ b/shared/systemd/src/basic/env-util.c
@@ -75,7 +75,7 @@ bool env_value_is_valid(const char *e) {
          * either. Discounting the shortest possible variable name of
          * length 1, the equal sign and trailing NUL this hence leaves
          * ARG_MAX-3 as longest possible variable value. */
-        if (strlen(e) > (size_t) sysconf(_SC_ARG_MAX) - 3)
+        if (strlen(e) > sc_arg_max() - 3)
                 return false;
 
         return true;
@@ -98,7 +98,7 @@ bool env_assignment_is_valid(const char *e) {
          * be > ARG_MAX, hence the individual variable assignments
          * cannot be either, but let's leave room for one trailing NUL
          * byte. */
-        if (strlen(e) > (size_t) sysconf(_SC_ARG_MAX) - 1)
+        if (strlen(e) > sc_arg_max() - 1)
                 return false;
 
         return true;
@@ -570,7 +570,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);
 
-                                k = strappend(r, t);
+                                k = strjoin(r, t);
                                 if (!k)
                                         return NULL;
 
@@ -626,7 +626,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);
 
-                                k = strappend(r, t);
+                                k = strjoin(r, t);
                                 if (!k)
                                         return NULL;
 
@@ -645,7 +645,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);
 
-                                k = strappend(r, t);
+                                k = strjoin(r, t);
                                 if (!k)
                                         return NULL;
 
@@ -664,7 +664,7 @@ 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 strappend(r, t);
+                return strjoin(r, t);
         } else
                 return strnappend(r, word, e-word);
 }
@@ -691,7 +691,7 @@ char **replace_env_argv(char **argv, char **env) {
                         if (e) {
                                 int r;
 
-                                r = strv_split_extract(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_QUOTES);
+                                r = strv_split_extract(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_UNQUOTE);
                                 if (r < 0) {
                                         ret[k] = NULL;
                                         strv_free(ret);
diff --git a/shared/systemd/src/basic/env-util.h b/shared/systemd/src/basic/env-util.h
index d54f9965..92802ed7 100644
--- a/shared/systemd/src/basic/env-util.h
+++ b/shared/systemd/src/basic/env-util.h
@@ -4,10 +4,17 @@
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdio.h>
+#include <unistd.h>
 
 #include "macro.h"
 #include "string.h"
 
+static inline size_t sc_arg_max(void) {
+        long l = sysconf(_SC_ARG_MAX);
+        assert(l > 0);
+        return (size_t) l;
+}
+
 bool env_name_is_valid(const char *e);
 bool env_value_is_valid(const char *e);
 bool env_assignment_is_valid(const char *e);
diff --git a/shared/systemd/src/basic/errno-util.h b/shared/systemd/src/basic/errno-util.h
index d7a5ea77..6053cde6 100644
--- a/shared/systemd/src/basic/errno-util.h
+++ b/shared/systemd/src/basic/errno-util.h
@@ -1,6 +1,9 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
+#include <stdlib.h>
+#include <string.h>
+
 #include "macro.h"
 
 static inline void _reset_errno_(int *saved_errno) {
@@ -28,6 +31,22 @@ static inline int negative_errno(void) {
         return -errno;
 }
 
+static inline const char *strerror_safe(int error) {
+        /* 'safe' here does NOT mean thread safety. */
+        return strerror(abs(error));
+}
+
+static inline int errno_or_else(int fallback) {
+        /* To be used when invoking library calls where errno handling is not defined clearly: we return
+         * errno if it is set, and the specified error otherwise. The idea is that the caller initializes
+         * errno to zero before doing an API call, and then uses this helper to retrieve a somewhat useful
+         * error code */
+        if (errno > 0)
+                return -errno;
+
+        return -abs(fallback);
+}
+
 /* Hint #1: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5.
  *
  * Hint #2: The kernel sends e.g., EHOSTUNREACH or ENONET to userspace in some ICMP error cases.  See the
diff --git a/shared/systemd/src/basic/escape.c b/shared/systemd/src/basic/escape.c
index 8f7a1b33..06d823c7 100644
--- a/shared/systemd/src/basic/escape.c
+++ b/shared/systemd/src/basic/escape.c
@@ -370,34 +370,81 @@ int cunescape(const char *s, UnescapeFlags flags, char **ret) {
         return cunescape_length(s, strlen(s), flags, ret);
 }
 
-char *xescape(const char *s, const char *bad) {
-        char *r, *t;
+#if 0 /* NM_IGNORED */
+char *xescape_full(const char *s, const char *bad, size_t console_width, bool eight_bits) {
+        char *ans, *t, *prev, *prev2;
         const char *f;
 
-        /* Escapes all chars in bad, in addition to \ and all special
-         * chars, in \xFF style escaping. May be reversed with
-         * cunescape(). */
+        /* Escapes all chars in bad, in addition to \ and all special chars, in \xFF style escaping. May be
+         * reversed with cunescape(). If eight_bits is true, characters >= 127 are let through unchanged.
+         * This corresponds to non-ASCII printable characters in pre-unicode encodings.
+         *
+         * If console_width is reached, output is truncated and "..." is appended. */
 
-        r = new(char, strlen(s) * 4 + 1);
-        if (!r)
+        if (console_width == 0)
+                return strdup("");
+
+        ans = new(char, MIN(strlen(s), console_width) * 4 + 1);
+        if (!ans)
                 return NULL;
 
-        for (f = s, t = r; *f; f++) {
+        memset(ans, '_', MIN(strlen(s), console_width) * 4);
+        ans[MIN(strlen(s), console_width) * 4] = 0;
+
+        for (f = s, t = prev = prev2 = ans; ; f++) {
+                char *tmp_t = t;
+
+                if (!*f) {
+                        *t = 0;
+                        return ans;
+                }
+
+                if ((unsigned char) *f < ' ' || (!eight_bits && (unsigned char) *f >= 127) ||
+                    *f == '\\' || strchr(bad, *f)) {
+                        if ((size_t) (t - ans) + 4 > console_width)
+                                break;
 
-                if ((*f < ' ') || (*f >= 127) ||
-                    (*f == '\\') || strchr(bad, *f)) {
                         *(t++) = '\\';
                         *(t++) = 'x';
                         *(t++) = hexchar(*f >> 4);
                         *(t++) = hexchar(*f);
-                } else
+                } else {
+                        if ((size_t) (t - ans) + 1 > console_width)
+                                break;
+
                         *(t++) = *f;
+                }
+
+                /* We might need to go back two cycles to fit three dots, so remember two positions */
+                prev2 = prev;
+                prev = tmp_t;
         }
 
-        *t = 0;
+        /* We can just write where we want, since chars are one-byte */
+        size_t c = MIN(console_width, 3u); /* If the console is too narrow, write fewer dots */
+        size_t off;
+        if (console_width - c >= (size_t) (t - ans))
+                off = (size_t) (t - ans);
+        else if (console_width - c >= (size_t) (prev - ans))
+                off = (size_t) (prev - ans);
+        else if (console_width - c >= (size_t) (prev2 - ans))
+                off = (size_t) (prev2 - ans);
+        else
+                off = console_width - c;
+        assert(off <= (size_t) (t - ans));
 
-        return r;
+        memcpy(ans + off, "...", c);
+        ans[off + c] = '\0';
+        return ans;
+}
+
+char *escape_non_printable_full(const char *str, size_t console_width, bool eight_bit) {
+        if (eight_bit)
+                return xescape_full(str, "", console_width, true);
+        else
+                return utf8_escape_non_printable_full(str, console_width);
 }
+#endif /* NM_IGNORED */
 
 char *octescape(const char *s, size_t len) {
         char *r, *t;
diff --git a/shared/systemd/src/basic/escape.h b/shared/systemd/src/basic/escape.h
index 51562099..b26054c5 100644
--- a/shared/systemd/src/basic/escape.h
+++ b/shared/systemd/src/basic/escape.h
@@ -46,8 +46,12 @@ int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **r
 int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret);
 int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit);
 
-char *xescape(const char *s, const char *bad);
+char *xescape_full(const char *s, const char *bad, size_t console_width, bool eight_bits);
+static inline char *xescape(const char *s, const char *bad) {
+        return xescape_full(s, bad, SIZE_MAX, false);
+}
 char *octescape(const char *s, size_t len);
+char *escape_non_printable_full(const char *str, size_t console_width, bool eight_bit);
 
 char *shell_escape(const char *s, const char *bad);
 char* shell_maybe_quote(const char *s, EscapeStyle style);
diff --git a/shared/systemd/src/basic/extract-word.c b/shared/systemd/src/basic/extract-word.c
index 782c868b..15cbaafb 100644
--- a/shared/systemd/src/basic/extract-word.c
+++ b/shared/systemd/src/basic/extract-word.c
@@ -30,6 +30,8 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
 
         assert(p);
         assert(ret);
+        /* Those two don't make sense together. */
+        assert(!FLAGS_SET(flags, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE));
 
         /* Bail early if called after last value or with no input */
         if (!*p)
@@ -137,7 +139,7 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
                         for (;; (*p)++, c = **p) {
                                 if (c == 0)
                                         goto finish_force_terminate;
-                                else if (IN_SET(c, '\'', '"') && (flags & EXTRACT_QUOTES)) {
+                                else if (IN_SET(c, '\'', '"') && (flags & EXTRACT_UNQUOTE)) {
                                         quote = c;
                                         break;
                                 } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
diff --git a/shared/systemd/src/basic/extract-word.h b/shared/systemd/src/basic/extract-word.h
index 705ebbe9..e2d43389 100644
--- a/shared/systemd/src/basic/extract-word.h
+++ b/shared/systemd/src/basic/extract-word.h
@@ -7,7 +7,7 @@ typedef enum ExtractFlags {
         EXTRACT_RELAX                    = 1 << 0,
         EXTRACT_CUNESCAPE                = 1 << 1,
         EXTRACT_CUNESCAPE_RELAX          = 1 << 2,
-        EXTRACT_QUOTES                   = 1 << 3,
+        EXTRACT_UNQUOTE                  = 1 << 3,
         EXTRACT_DONT_COALESCE_SEPARATORS = 1 << 4,
         EXTRACT_RETAIN_ESCAPE            = 1 << 5,
 } ExtractFlags;
diff --git a/shared/systemd/src/basic/fileio.c b/shared/systemd/src/basic/fileio.c
index 0dfb4574..bd2afbbe 100644
--- a/shared/systemd/src/basic/fileio.c
+++ b/shared/systemd/src/basic/fileio.c
@@ -23,6 +23,7 @@
 #include "log.h"
 #include "macro.h"
 #include "missing.h"
+#include "mkdir.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "stdio-util.h"
@@ -31,6 +32,52 @@
 
 #define READ_FULL_BYTES_MAX (4U*1024U*1024U)
 
+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);
+
+        FILE *f = fdopen(fd, options);
+        if (!f)
+                return -errno;
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        *ret = f;
+        return 0;
+}
+
+FILE* open_memstream_unlocked(char **ptr, size_t *sizeloc) {
+        FILE *f = open_memstream(ptr, sizeloc);
+        if (!f)
+                return NULL;
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        return f;
+}
+
+FILE* fmemopen_unlocked(void *buf, size_t size, const char *mode) {
+        FILE *f = fmemopen(buf, size, mode);
+        if (!f)
+                return NULL;
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        return f;
+}
+
 #if 0 /* NM_IGNORED */
 int write_string_stream_ts(
                 FILE *f,
@@ -98,7 +145,6 @@ static int write_string_file_atomic(
         if (r < 0)
                 return r;
 
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
         (void) fchmod_umask(fileno(f), 0644);
 
         r = write_string_stream_ts(f, line, flags, ts);
@@ -132,6 +178,12 @@ int write_string_file_ts(
         /* We don't know how to verify whether the file contents was already on-disk. */
         assert(!((flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE) && (flags & WRITE_STRING_FILE_SYNC)));
 
+        if (flags & WRITE_STRING_FILE_MKDIR_0755) {
+                r = mkdir_parents(fn, 0755);
+                if (r < 0)
+                        return r;
+        }
+
         if (flags & WRITE_STRING_FILE_ATOMIC) {
                 assert(flags & WRITE_STRING_FILE_CREATE);
 
@@ -144,11 +196,9 @@ int write_string_file_ts(
                 assert(!ts);
 
         if (flags & WRITE_STRING_FILE_CREATE) {
-                f = fopen(fn, "we");
-                if (!f) {
-                        r = -errno;
+                r = fopen_unlocked(fn, "we", &f);
+                if (r < 0)
                         goto fail;
-                }
         } else {
                 int fd;
 
@@ -160,16 +210,13 @@ int write_string_file_ts(
                         goto fail;
                 }
 
-                f = fdopen(fd, "w");
-                if (!f) {
-                        r = -errno;
+                r = fdopen_unlocked(fd, "w", &f);
+                if (r < 0) {
                         safe_close(fd);
                         goto fail;
                 }
         }
 
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
         if (flags & WRITE_STRING_FILE_DISABLE_BUFFER)
                 setvbuf(f, NULL, _IONBF, 0);
 
@@ -216,15 +263,14 @@ int write_string_filef(
 
 int read_one_line_file(const char *fn, char **line) {
         _cleanup_fclose_ FILE *f = NULL;
+        int r;
 
         assert(fn);
         assert(line);
 
-        f = fopen(fn, "re");
-        if (!f)
-                return -errno;
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+        r = fopen_unlocked(fn, "re", &f);
+        if (r < 0)
+                return r;
 
         return read_line(f, LONG_LINE_MAX, line);
 }
@@ -233,6 +279,7 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *buf = NULL;
         size_t l, k;
+        int r;
 
         assert(fn);
         assert(blob);
@@ -246,17 +293,15 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
         if (!buf)
                 return -ENOMEM;
 
-        f = fopen(fn, "re");
-        if (!f)
-                return -errno;
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+        r = fopen_unlocked(fn, "re", &f);
+        if (r < 0)
+                return r;
 
         /* We try to read one byte more than we need, so that we know whether we hit eof */
         errno = 0;
         k = fread(buf, 1, l + accept_extra_nl + 1, f);
         if (ferror(f))
-                return errno > 0 ? -errno : -EIO;
+                return errno_or_else(EIO);
 
         if (k != l && k != l + accept_extra_nl)
                 return 0;
@@ -283,7 +328,8 @@ int read_full_stream_full(
 
         assert(f);
         assert(ret_contents);
-        assert(!(flags & READ_FULL_FILE_UNBASE64) || ret_size);
+        assert(!FLAGS_SET(flags, READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX));
+        assert(!(flags & (READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)) || ret_size);
 
         n_next = LINE_MAX; /* Start size */
 
@@ -324,6 +370,7 @@ int read_full_stream_full(
                         }
                         memcpy_safe(t, buf, n);
                         explicit_bzero_safe(buf, n);
+                        buf = mfree(buf);
                 } else {
                         t = realloc(buf, n_next + 1);
                         if (!t)
@@ -339,7 +386,7 @@ int read_full_stream_full(
                         l += k;
 
                 if (ferror(f)) {
-                        r = errno > 0 ? -errno : -EIO;
+                        r = errno_or_else(EIO);
                         goto finalize;
                 }
 
@@ -360,9 +407,12 @@ int read_full_stream_full(
                 n_next = MIN(n * 2, READ_FULL_BYTES_MAX);
         }
 
-        if (flags & READ_FULL_FILE_UNBASE64) {
+        if (flags & (READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)) {
                 buf[l++] = 0;
-                r = unbase64mem_full(buf, l, flags & READ_FULL_FILE_SECURE, (void **) ret_contents, ret_size);
+                if (flags & READ_FULL_FILE_UNBASE64)
+                        r = unbase64mem_full(buf, l, flags & READ_FULL_FILE_SECURE, (void **) ret_contents, ret_size);
+                else
+                        r = unhexmem_full(buf, l, flags & READ_FULL_FILE_SECURE, (void **) ret_contents, ret_size);
                 goto finalize;
         }
 
@@ -394,15 +444,14 @@ finalize:
 
 int read_full_file_full(const char *filename, ReadFullFileFlags flags, char **contents, size_t *size) {
         _cleanup_fclose_ FILE *f = NULL;
+        int r;
 
         assert(filename);
         assert(contents);
 
-        f = fopen(filename, "re");
-        if (!f)
-                return -errno;
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+        r = fopen_unlocked(filename, "re", &f);
+        if (r < 0)
+                return r;
 
         return read_full_stream_full(f, filename, flags, contents, size);
 }
@@ -547,10 +596,7 @@ static int search_and_fopen_internal(const char *path, const char *mode, const c
                 _cleanup_free_ char *p = NULL;
                 FILE *f;
 
-                if (root)
-                        p = strjoin(root, *i, "/", path);
-                else
-                        p = strjoin(*i, "/", path);
+                p = path_join(root, *i, path);
                 if (!p)
                         return -ENOMEM;
 
@@ -623,7 +669,7 @@ int fflush_and_check(FILE *f) {
         fflush(f);
 
         if (ferror(f))
-                return errno > 0 ? -errno : -EIO;
+                return errno_or_else(EIO);
 
         return 0;
 }
@@ -741,7 +787,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, funlockfile);
 int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) {
         size_t n = 0, allocated = 0, count = 0;
         _cleanup_free_ char *buffer = NULL;
-        int r;
+        int r, tty = -1;
 
         assert(f);
 
@@ -816,6 +862,17 @@ int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) {
                         count++;
 
                         if (eol != EOL_NONE) {
+                                /* If we are on a tty, we can't wait for more input. But we expect only
+                                 * \n as the single EOL marker, so there is no need to wait. We check
+                                 * this condition last to avoid isatty() check if not necessary. */
+
+                                if (tty < 0)
+                                        tty = isatty(fileno(f));
+                                if (tty > 0)
+                                        break;
+                        }
+
+                        if (eol != EOL_NONE) {
                                 previous_eol |= eol;
                                 continue;
                         }
@@ -853,7 +910,7 @@ int safe_fgetc(FILE *f, char *ret) {
         k = fgetc(f);
         if (k == EOF) {
                 if (ferror(f))
-                        return errno > 0 ? -errno : -EIO;
+                        return errno_or_else(EIO);
 
                 if (ret)
                         *ret = 0;
diff --git a/shared/systemd/src/basic/fileio.h b/shared/systemd/src/basic/fileio.h
index 760e7386..05f6c89d 100644
--- a/shared/systemd/src/basic/fileio.h
+++ b/shared/systemd/src/basic/fileio.h
@@ -21,6 +21,7 @@ typedef enum {
         WRITE_STRING_FILE_SYNC              = 1 << 4,
         WRITE_STRING_FILE_DISABLE_BUFFER    = 1 << 5,
         WRITE_STRING_FILE_NOFOLLOW          = 1 << 6,
+        WRITE_STRING_FILE_MKDIR_0755        = 1 << 7,
 
         /* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one
            more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file()
@@ -31,8 +32,14 @@ typedef enum {
 typedef enum {
         READ_FULL_FILE_SECURE   = 1 << 0,
         READ_FULL_FILE_UNBASE64 = 1 << 1,
+        READ_FULL_FILE_UNHEX    = 1 << 2,
 } ReadFullFileFlags;
 
+int fopen_unlocked(const char *path, const char *options, FILE **ret);
+int fdopen_unlocked(int fd, const char *options, FILE **ret);
+FILE* open_memstream_unlocked(char **ptr, size_t *sizeloc);
+FILE* fmemopen_unlocked(void *buf, size_t size, const char *mode);
+
 int write_string_stream_ts(FILE *f, const char *line, WriteStringFileFlags flags, struct timespec *ts);
 static inline int write_string_stream(FILE *f, const char *line, WriteStringFileFlags flags) {
         return write_string_stream_ts(f, line, flags, NULL);
diff --git a/shared/systemd/src/basic/format-util.c b/shared/systemd/src/basic/format-util.c
new file mode 100644
index 00000000..7a3e735b
--- /dev/null
+++ b/shared/systemd/src/basic/format-util.c
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <stdio.h>
+
+#include "format-util.h"
+#include "memory-util.h"
+
+char *format_ifname(int ifindex, char buf[static IF_NAMESIZE + 1]) {
+        /* Buffer is always cleared */
+        memzero(buf, IF_NAMESIZE + 1);
+        return if_indextoname(ifindex, buf);
+}
+
+char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) {
+        typedef struct {
+                const char *suffix;
+                uint64_t factor;
+        } suffix_table;
+        static const suffix_table table_iec[] = {
+                { "E", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
+                { "P", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
+                { "T", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
+                { "G", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
+                { "M", UINT64_C(1024)*UINT64_C(1024) },
+                { "K", UINT64_C(1024) },
+        }, table_si[] = {
+                { "E", UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000) },
+                { "P", UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000) },
+                { "T", UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000) },
+                { "G", UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000) },
+                { "M", UINT64_C(1000)*UINT64_C(1000) },
+                { "K", UINT64_C(1000) },
+        };
+        const suffix_table *table;
+        size_t n, i;
+
+        assert_cc(ELEMENTSOF(table_iec) == ELEMENTSOF(table_si));
+
+        if (t == (uint64_t) -1)
+                return NULL;
+
+        table = flag & FORMAT_BYTES_USE_IEC ? table_iec : table_si;
+        n = ELEMENTSOF(table_iec);
+
+        for (i = 0; i < n; i++)
+                if (t >= table[i].factor) {
+                        if (flag & FORMAT_BYTES_BELOW_POINT) {
+                                snprintf(buf, l,
+                                         "%" PRIu64 ".%" PRIu64 "%s",
+                                         t / table[i].factor,
+                                         i != n - 1 ?
+                                         (t / table[i + 1].factor * UINT64_C(10) / table[n - 1].factor) % UINT64_C(10):
+                                         (t * UINT64_C(10) / table[i].factor) % UINT64_C(10),
+                                         table[i].suffix);
+                        } else
+                                snprintf(buf, l,
+                                         "%" PRIu64 "%s",
+                                         t / table[i].factor,
+                                         table[i].suffix);
+
+                        goto finish;
+                }
+
+        snprintf(buf, l, "%" PRIu64 "%s", t, flag & FORMAT_BYTES_TRAILING_B ? "B" : "");
+
+finish:
+        buf[l-1] = 0;
+        return buf;
+
+}
diff --git a/shared/systemd/src/basic/format-util.h b/shared/systemd/src/basic/format-util.h
new file mode 100644
index 00000000..e0d184a5
--- /dev/null
+++ b/shared/systemd/src/basic/format-util.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <inttypes.h>
+#include <net/if.h>
+#include <stdbool.h>
+
+#if SIZEOF_PID_T == 4
+#  define PID_PRI PRIi32
+#elif SIZEOF_PID_T == 2
+#  define PID_PRI PRIi16
+#else
+#  error Unknown pid_t size
+#endif
+#define PID_FMT "%" PID_PRI
+
+#if SIZEOF_UID_T == 4
+#  define UID_FMT "%" PRIu32
+#elif SIZEOF_UID_T == 2
+#  define UID_FMT "%" PRIu16
+#else
+#  error Unknown uid_t size
+#endif
+
+#if SIZEOF_GID_T == 4
+#  define GID_FMT "%" PRIu32
+#elif SIZEOF_GID_T == 2
+#  define GID_FMT "%" PRIu16
+#else
+#  error Unknown gid_t size
+#endif
+
+#if SIZEOF_TIME_T == 8
+#  define PRI_TIME PRIi64
+#elif SIZEOF_TIME_T == 4
+#  define PRI_TIME "li"
+#else
+#  error Unknown time_t size
+#endif
+
+#if defined __x86_64__ && defined __ILP32__
+#  define PRI_TIMEX PRIi64
+#else
+#  define PRI_TIMEX "li"
+#endif
+
+#if SIZEOF_RLIM_T == 8
+#  define RLIM_FMT "%" PRIu64
+#elif SIZEOF_RLIM_T == 4
+#  define RLIM_FMT "%" PRIu32
+#else
+#  error Unknown rlim_t size
+#endif
+
+#if SIZEOF_DEV_T == 8
+#  define DEV_FMT "%" PRIu64
+#elif SIZEOF_DEV_T == 4
+#  define DEV_FMT "%" PRIu32
+#else
+#  error Unknown dev_t size
+#endif
+
+#if SIZEOF_INO_T == 8
+#  define INO_FMT "%" PRIu64
+#elif SIZEOF_INO_T == 4
+#  define INO_FMT "%" PRIu32
+#else
+#  error Unknown ino_t size
+#endif
+
+char *format_ifname(int ifindex, char buf[static IF_NAMESIZE + 1]);
+
+typedef enum {
+        FORMAT_BYTES_USE_IEC     = 1 << 0,
+        FORMAT_BYTES_BELOW_POINT = 1 << 1,
+        FORMAT_BYTES_TRAILING_B  = 1 << 2,
+} FormatBytesFlag;
+
+#define FORMAT_BYTES_MAX 8
+char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag);
+static inline char *format_bytes(char *buf, size_t l, uint64_t t) {
+        return format_bytes_full(buf, l, t, FORMAT_BYTES_USE_IEC | FORMAT_BYTES_BELOW_POINT | FORMAT_BYTES_TRAILING_B);
+}
diff --git a/shared/systemd/src/basic/fs-util.c b/shared/systemd/src/basic/fs-util.c
index be85eef1..56385fa2 100644
--- a/shared/systemd/src/basic/fs-util.c
+++ b/shared/systemd/src/basic/fs-util.c
@@ -218,113 +218,65 @@ 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) {
-        char fd_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
         _cleanup_close_ int fd = -1;
-        bool st_valid = false;
-        struct stat st;
-        int r;
 
         assert(path);
 
-        /* Under the assumption that we are running privileged we first change the access mode and only then
-         * hand out ownership to avoid a window where access is too open. */
-
         fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW); /* Let's acquire an O_PATH fd, as precaution to change
                                                        * mode/owner on the same file */
         if (fd < 0)
                 return -errno;
 
-        xsprintf(fd_path, "/proc/self/fd/%i", fd);
-
-        if (mode != MODE_INVALID) {
-                if ((mode & S_IFMT) != 0) {
-
-                        if (stat(fd_path, &st) < 0)
-                                return -errno;
-
-                        if ((mode & S_IFMT) != (st.st_mode & S_IFMT))
-                                return -EINVAL;
-
-                        st_valid = true;
-                }
-
-                if (chmod(fd_path, mode & 07777) < 0) {
-                        r = -errno;
-
-                        if (!st_valid && stat(fd_path, &st) < 0)
-                                return -errno;
-
-                        if ((mode & 07777) != (st.st_mode & 07777))
-                                return r;
-
-                        st_valid = true;
-                }
-        }
-
-        if (uid != UID_INVALID || gid != GID_INVALID) {
-                if (chown(fd_path, uid, gid) < 0) {
-                        r = -errno;
-
-                        if (!st_valid && stat(fd_path, &st) < 0)
-                                return -errno;
-
-                        if (uid != UID_INVALID && st.st_uid != uid)
-                                return r;
-                        if (gid != GID_INVALID && st.st_gid != gid)
-                                return r;
-                }
-        }
-
-        return 0;
+        return fchmod_and_chown(fd, mode, uid, gid);
 }
 
 int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid) {
-        bool st_valid = false;
+        bool do_chown, do_chmod;
         struct stat st;
-        int r;
 
-        /* Under the assumption that we are running privileged we first change the access mode and only then hand out
-         * ownership to avoid a window where access is too open. */
+        /* Change ownership and access mode of the specified fd. Tries to do so safely, ensuring that at no
+         * point in time the access mode is above the old access mode under the old ownership or the new
+         * access mode under the new ownership. Note: this call tries hard to leave the access mode
+         * unaffected if the uid/gid is changed, i.e. it undoes implicit suid/sgid dropping the kernel does
+         * on chown().
+         *
+         * This call is happy with O_PATH fds. */
 
-        if (mode != MODE_INVALID) {
-                if ((mode & S_IFMT) != 0) {
+        if (fstat(fd, &st) < 0)
+                return -errno;
 
-                        if (fstat(fd, &st) < 0)
-                                return -errno;
+        do_chown =
+                (uid != UID_INVALID && st.st_uid != uid) ||
+                (gid != GID_INVALID && st.st_gid != gid);
 
-                        if ((mode & S_IFMT) != (st.st_mode & S_IFMT))
-                                return -EINVAL;
+        do_chmod =
+                !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */
+                ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) ||
+                 do_chown); /* If we change ownership, make sure we reset the mode afterwards, since chown()
+                             * modifies the access mode too */
 
-                        st_valid = true;
-                }
+        if (mode == MODE_INVALID)
+                mode = st.st_mode; /* If we only shall do a chown(), save original mode, since chown() might break it. */
+        else if ((mode & S_IFMT) != 0 && ((mode ^ st.st_mode) & S_IFMT) != 0)
+                return -EINVAL; /* insist on the right file type if it was specified */
 
-                if (fchmod(fd, mode & 07777) < 0) {
-                        r = -errno;
+        if (do_chown && do_chmod) {
+                mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */
 
-                        if (!st_valid && fstat(fd, &st) < 0)
+                if (((minimal ^ st.st_mode) & 07777) != 0)
+                        if (fchmod_opath(fd, minimal & 07777) < 0)
                                 return -errno;
-
-                        if ((mode & 07777) != (st.st_mode & 07777))
-                                return r;
-
-                        st_valid = true;
-                }
         }
 
-        if (uid != UID_INVALID || gid != GID_INVALID)
-                if (fchown(fd, uid, gid) < 0) {
-                        r = -errno;
-
-                        if (!st_valid && fstat(fd, &st) < 0)
-                                return -errno;
+        if (do_chown)
+                if (fchownat(fd, "", uid, gid, AT_EMPTY_PATH) < 0)
+                        return -errno;
 
-                        if (uid != UID_INVALID && st.st_uid != uid)
-                                return r;
-                        if (gid != GID_INVALID && st.st_gid != gid)
-                                return r;
-                }
+        if (do_chmod)
+                if (fchmod_opath(fd, mode & 07777) < 0)
+                        return -errno;
 
-        return 0;
+        return do_chown || do_chmod;
 }
 #endif /* NM_IGNORED */
 
@@ -411,13 +363,7 @@ int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gi
          * something fchown(), fchmod(), futimensat() don't allow. */
         xsprintf(fdpath, "/proc/self/fd/%i", fd);
 
-        if (mode != MODE_INVALID)
-                if (chmod(fdpath, mode) < 0)
-                        ret = -errno;
-
-        if (uid_is_valid(uid) || gid_is_valid(gid))
-                if (chown(fdpath, uid, gid) < 0 && ret >= 0)
-                        ret = -errno;
+        ret = fchmod_and_chown(fd, mode, uid, gid);
 
         if (stamp != USEC_INFINITY) {
                 struct timespec ts[2];
@@ -1043,9 +989,9 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
 
                                 /* Prefix what's left to do with what we just read, and start the loop again, but
                                  * remain in the current directory. */
-                                joined = strjoin(destination, todo);
+                                joined = path_join(destination, todo);
                         } else
-                                joined = strjoin("/", destination, todo);
+                                joined = path_join("/", destination, todo);
                         if (!joined)
                                 return -ENOMEM;
 
diff --git a/shared/systemd/src/basic/fs-util.h b/shared/systemd/src/basic/fs-util.h
index b9651205..c5527cc4 100644
--- a/shared/systemd/src/basic/fs-util.h
+++ b/shared/systemd/src/basic/fs-util.h
@@ -7,12 +7,20 @@
 #include <stdbool.h>
 #include <stdint.h>
 #include <sys/inotify.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 
 #include "errno-util.h"
 #include "time-util.h"
 
+#define MODE_INVALID ((mode_t) -1)
+
+/* The following macros add 1 when converting things, since 0 is a valid mode, while the pointer
+ * NULL is special */
+#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);
diff --git a/shared/systemd/src/basic/hash-funcs.c b/shared/systemd/src/basic/hash-funcs.c
index ec4f1de6..03695098 100644
--- a/shared/systemd/src/basic/hash-funcs.c
+++ b/shared/systemd/src/basic/hash-funcs.c
@@ -13,6 +13,9 @@ void string_hash_func(const char *p, struct siphash *state) {
 
 #if 0 /* NM_IGNORED */
 DEFINE_HASH_OPS(string_hash_ops, char, string_hash_func, string_compare_func);
+DEFINE_HASH_OPS_FULL(string_hash_ops_free_free,
+                     char, string_hash_func, string_compare_func, free,
+                     char, free);
 
 void path_hash_func(const char *q, struct siphash *state) {
         size_t n;
diff --git a/shared/systemd/src/basic/hash-funcs.h b/shared/systemd/src/basic/hash-funcs.h
index 3d2ae4b5..0d2d4283 100644
--- a/shared/systemd/src/basic/hash-funcs.h
+++ b/shared/systemd/src/basic/hash-funcs.h
@@ -76,6 +76,7 @@ struct hash_ops {
 void string_hash_func(const char *p, struct siphash *state);
 #define string_compare_func strcmp
 extern const struct hash_ops string_hash_ops;
+extern const struct hash_ops string_hash_ops_free_free;
 
 void path_hash_func(const char *p, struct siphash *state);
 int path_compare_func(const char *a, const char *b) _pure_;
diff --git a/shared/systemd/src/basic/hashmap.c b/shared/systemd/src/basic/hashmap.c
index 9418dbd8..b1ae08cd 100644
--- a/shared/systemd/src/basic/hashmap.c
+++ b/shared/systemd/src/basic/hashmap.c
@@ -13,6 +13,7 @@
 #include "macro.h"
 #include "memory-util.h"
 #include "mempool.h"
+#include "missing.h"
 #include "process-util.h"
 #include "random-util.h"
 #include "set.h"
@@ -287,7 +288,11 @@ _destructor_ static void cleanup_pools(void) {
         /* The pool is only allocated by the main thread, but the memory can
          * be passed to other threads. Let's clean up if we are the main thread
          * and no other threads are live. */
-        if (!is_main_thread())
+        /* 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. */
+        if (getpid() != gettid())
                 return;
 
         r = get_proc_field("/proc/self/status", "Threads", WHITESPACE, &t);
@@ -730,8 +735,8 @@ bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const v
         return true;
 }
 
-bool set_iterate(Set *s, Iterator *i, void **value) {
-        return internal_hashmap_iterate(HASHMAP_BASE(s), i, value, NULL);
+bool set_iterate(const Set *s, Iterator *i, void **value) {
+        return internal_hashmap_iterate(HASHMAP_BASE((Set*) s), i, value, NULL);
 }
 
 #define HASHMAP_FOREACH_IDX(idx, h, i) \
@@ -1765,6 +1770,34 @@ int set_consume(Set *s, void *value) {
         return r;
 }
 
+#if 0 /* NM_IGNORED */
+int hashmap_put_strdup(Hashmap **h, const char *k, const char *v) {
+        int r;
+
+        r = hashmap_ensure_allocated(h, &string_hash_ops_free_free);
+        if (r < 0)
+                return r;
+
+        _cleanup_free_ char *kdup = NULL, *vdup = NULL;
+        kdup = strdup(k);
+        vdup = strdup(v);
+        if (!kdup || !vdup)
+                return -ENOMEM;
+
+        r = hashmap_put(*h, kdup, vdup);
+        if (r < 0) {
+                if (r == -EEXIST && streq(v, hashmap_get(*h, kdup)))
+                        return 0;
+                return r;
+        }
+
+        assert(r > 0); /* 0 would mean vdup is already in the hashmap, which cannot be */
+        kdup = vdup = NULL;
+
+        return 0;
+}
+#endif /* NM_IGNORED */
+
 int set_put_strdup(Set *s, const char *p) {
         char *c;
 
diff --git a/shared/systemd/src/basic/hashmap.h b/shared/systemd/src/basic/hashmap.h
index 41c8adb1..65adc925 100644
--- a/shared/systemd/src/basic/hashmap.h
+++ b/shared/systemd/src/basic/hashmap.h
@@ -76,7 +76,7 @@ typedef struct {
 
 #if ENABLE_DEBUG_HASHMAP
 # define HASHMAP_DEBUG_PARAMS , const char *func, const char *file, int line
-# define HASHMAP_DEBUG_SRC_ARGS   , __func__, __FILE__, __LINE__
+# define HASHMAP_DEBUG_SRC_ARGS   , __func__, PROJECT_FILE, __LINE__
 # define HASHMAP_DEBUG_PASS_ARGS   , func, file, line
 #else
 # define HASHMAP_DEBUG_PARAMS
@@ -147,6 +147,8 @@ static inline int ordered_hashmap_put(OrderedHashmap *h, const void *key, void *
         return hashmap_put(PLAIN_HASHMAP(h), key, value);
 }
 
+int hashmap_put_strdup(Hashmap **h, const char *k, const char *v);
+
 int hashmap_update(Hashmap *h, const void *key, void *value);
 static inline int ordered_hashmap_update(OrderedHashmap *h, const void *key, void *value) {
         return hashmap_update(PLAIN_HASHMAP(h), key, value);
diff --git a/shared/systemd/src/basic/hexdecoct.c b/shared/systemd/src/basic/hexdecoct.c
index c81c09e8..36174ec3 100644
--- a/shared/systemd/src/basic/hexdecoct.c
+++ b/shared/systemd/src/basic/hexdecoct.c
@@ -110,10 +110,12 @@ static int unhex_next(const char **p, size_t *l) {
         return r;
 }
 
-int unhexmem(const char *p, size_t l, 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(ret);
         assert(ret_len);
@@ -123,7 +125,8 @@ int unhexmem(const char *p, size_t l, void **ret, size_t *ret_len) {
                 l = strlen(p);
 
         /* Note that the calculation of memory size is an upper boundary, as we ignore whitespace while decoding */
-        buf = malloc((l + 1) / 2 + 1);
+        buf_size = (l + 1) / 2 + 1;
+        buf = malloc(buf_size);
         if (!buf)
                 return -ENOMEM;
 
@@ -133,12 +136,16 @@ int unhexmem(const char *p, size_t l, void **ret, size_t *ret_len) {
                 a = unhex_next(&x, &l);
                 if (a == -EPIPE) /* End of string */
                         break;
-                if (a < 0)
-                        return a;
+                if (a < 0) {
+                        r = a;
+                        goto on_failure;
+                }
 
                 b = unhex_next(&x, &l);
-                if (b < 0)
-                        return b;
+                if (b < 0) {
+                        r = b;
+                        goto on_failure;
+                }
 
                 *(z++) = (uint8_t) a << 4 | (uint8_t) b;
         }
@@ -149,6 +156,12 @@ int unhexmem(const char *p, size_t l, void **ret, size_t *ret_len) {
         *ret = TAKE_PTR(buf);
 
         return 0;
+
+on_failure:
+        if (secure)
+                explicit_bzero_safe(buf, buf_size);
+
+        return r;
 }
 
 #if 0 /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/hexdecoct.h b/shared/systemd/src/basic/hexdecoct.h
index fa6013ee..dfdff1e9 100644
--- a/shared/systemd/src/basic/hexdecoct.h
+++ b/shared/systemd/src/basic/hexdecoct.h
@@ -18,7 +18,10 @@ char hexchar(int x) _const_;
 int unhexchar(char c) _const_;
 
 char *hexmem(const void *p, size_t l);
-int unhexmem(const char *p, size_t l, void **mem, size_t *len);
+int unhexmem_full(const char *p, size_t l, bool secure, void **mem, size_t *len);
+static inline int unhexmem(const char *p, size_t l, void **mem, size_t *len) {
+        return unhexmem_full(p, l, false, mem, len);
+}
 
 char base32hexchar(int x) _const_;
 int unbase32hexchar(char c) _const_;
diff --git a/shared/systemd/src/basic/in-addr-util.c b/shared/systemd/src/basic/in-addr-util.c
index 5899f62f..91d687c2 100644
--- a/shared/systemd/src/basic/in-addr-util.c
+++ b/shared/systemd/src/basic/in-addr-util.c
@@ -11,6 +11,7 @@
 #include <stdlib.h>
 
 #include "alloc-util.h"
+#include "errno-util.h"
 #include "in-addr-util.h"
 #include "macro.h"
 #include "parse-util.h"
@@ -93,12 +94,19 @@ int in_addr_is_localhost(int family, const union in_addr_union *u) {
         return -EAFNOSUPPORT;
 }
 
+bool in4_addr_equal(const struct in_addr *a, const struct in_addr *b) {
+        assert(a);
+        assert(b);
+
+        return a->s_addr == b->s_addr;
+}
+
 int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b) {
         assert(a);
         assert(b);
 
         if (family == AF_INET)
-                return a->in.s_addr == b->in.s_addr;
+                return in4_addr_equal(&a->in, &b->in);
 
         if (family == AF_INET6)
                 return
@@ -319,7 +327,7 @@ int in_addr_to_string(int family, const union in_addr_union *u, char **ret) {
 
         errno = 0;
         if (!inet_ntop(family, u, x, l))
-                return errno > 0 ? -errno : -EINVAL;
+                return errno_or_else(EINVAL);
 
         *ret = TAKE_PTR(x);
         return 0;
@@ -350,7 +358,7 @@ int in_addr_prefix_to_string(int family, const union in_addr_union *u, unsigned
 
         errno = 0;
         if (!inet_ntop(family, u, x, l))
-                return errno > 0 ? -errno : -EINVAL;
+                return errno_or_else(EINVAL);
 
         p = x + strlen(x);
         l -= strlen(x);
@@ -390,7 +398,7 @@ int in_addr_ifindex_to_string(int family, const union in_addr_union *u, int ifin
 
         errno = 0;
         if (!inet_ntop(family, u, x, l))
-                return errno > 0 ? -errno : -EINVAL;
+                return errno_or_else(EINVAL);
 
         sprintf(strchr(x, 0), "%%%i", ifindex);
 
@@ -410,7 +418,7 @@ int in_addr_from_string(int family, const char *s, union in_addr_union *ret) {
 
         errno = 0;
         if (inet_pton(family, s, ret ?: &buffer) <= 0)
-                return errno > 0 ? -errno : -EINVAL;
+                return errno_or_else(EINVAL);
 
         return 0;
 }
@@ -747,4 +755,16 @@ static int in_addr_data_compare_func(const struct in_addr_data *x, const struct
 }
 
 DEFINE_HASH_OPS(in_addr_data_hash_ops, struct in_addr_data, in_addr_data_hash_func, in_addr_data_compare_func);
+
+static void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state) {
+        assert(addr);
+
+        siphash24_compress(addr, sizeof(*addr), state);
+}
+
+static 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);
 #endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/in-addr-util.h b/shared/systemd/src/basic/in-addr-util.h
index a6a685b9..28afc7d8 100644
--- a/shared/systemd/src/basic/in-addr-util.h
+++ b/shared/systemd/src/basic/in-addr-util.h
@@ -32,6 +32,7 @@ int in_addr_is_localhost(int family, const union in_addr_union *u);
 
 bool in4_addr_is_non_local(const struct in_addr *a);
 
+bool in4_addr_equal(const struct in_addr *a, const struct in_addr *b);
 int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b);
 int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen);
 int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen);
@@ -72,3 +73,4 @@ static inline size_t FAMILY_ADDRESS_SIZE(int family) {
 #define IN_ADDR_NULL ((union in_addr_union) { .in6 = {} })
 
 extern const struct hash_ops in_addr_data_hash_ops;
+extern const struct hash_ops in6_addr_hash_ops;
diff --git a/shared/systemd/src/basic/io-util.c b/shared/systemd/src/basic/io-util.c
index 3f47eff5..9669c463 100644
--- a/shared/systemd/src/basic/io-util.c
+++ b/shared/systemd/src/basic/io-util.c
@@ -264,9 +264,91 @@ ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) {
 char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value) {
         char *x;
 
-        x = strappend(field, value);
+        x = strjoin(field, value);
         if (x)
                 iovec[(*n_iovec)++] = IOVEC_MAKE_STRING(x);
         return x;
 }
+
+char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value) {
+        char *x;
+
+        x = set_iovec_string_field(iovec, n_iovec, field, value);
+        free(value);
+        return x;
+}
+
+struct iovec_wrapper *iovw_new(void) {
+        return malloc0(sizeof(struct iovec_wrapper));
+}
+
+void iovw_free_contents(struct iovec_wrapper *iovw, bool free_vectors) {
+        if (free_vectors)
+                for (size_t i = 0; i < iovw->count; i++)
+                        free(iovw->iovec[i].iov_base);
+
+        iovw->iovec = mfree(iovw->iovec);
+        iovw->count = 0;
+        iovw->size_bytes = 0;
+}
+
+struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw) {
+        iovw_free_contents(iovw, true);
+
+        return mfree(iovw);
+}
+
+struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw) {
+        iovw_free_contents(iovw, false);
+
+        return mfree(iovw);
+}
+
+int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) {
+        if (iovw->count >= IOV_MAX)
+                return -E2BIG;
+
+        if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1))
+                return log_oom();
+
+        iovw->iovec[iovw->count++] = IOVEC_MAKE(data, len);
+        return 0;
+}
+
+int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const char *value) {
+        _cleanup_free_ char *x = NULL;
+        int r;
+
+        x = strjoin(field, value);
+        if (!x)
+                return log_oom();
+
+        r = iovw_put(iovw, x, strlen(x));
+        if (r >= 0)
+                TAKE_PTR(x);
+
+        return r;
+}
+
+int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value) {
+        _cleanup_free_ _unused_ char *free_ptr = value;
+
+        return iovw_put_string_field(iovw, field, value);
+}
+
+void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new) {
+        size_t i;
+
+        for (i = 0; i < iovw->count; i++)
+                iovw->iovec[i].iov_base = (char *)iovw->iovec[i].iov_base - old + new;
+}
+
+size_t iovw_size(struct iovec_wrapper *iovw) {
+        size_t n = 0, i;
+
+        for (i = 0; i < iovw->count; i++)
+                n += iovw->iovec[i].iov_len;
+
+        return n;
+}
 #endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/io-util.h b/shared/systemd/src/basic/io-util.h
index 792a64ad..719e19e8 100644
--- a/shared/systemd/src/basic/io-util.h
+++ b/shared/systemd/src/basic/io-util.h
@@ -73,3 +73,20 @@ static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) {
 #define IOVEC_MAKE_STRING(string) (struct iovec) IOVEC_INIT_STRING(string)
 
 char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value);
+char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value);
+
+struct iovec_wrapper {
+        struct iovec *iovec;
+        size_t count;
+        size_t size_bytes;
+};
+
+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);
+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);
diff --git a/shared/systemd/src/basic/log.h b/shared/systemd/src/basic/log.h
index 98c5f4d7..b81c93a5 100644
--- a/shared/systemd/src/basic/log.h
+++ b/shared/systemd/src/basic/log.h
@@ -75,6 +75,12 @@ int log_get_max_level_realm(LogRealm realm) _pure_;
  * for the application itself.
  */
 
+#if 0 /* NM_IGNORED */
+assert_cc(STRLEN(__FILE__) > STRLEN(RELATIVE_SOURCE_PATH) + 1);
+#define PROJECT_FILE (__FILE__ + STRLEN(RELATIVE_SOURCE_PATH) + 1)
+#endif /* NM_IGNORED */
+#define PROJECT_FILE __FILE__
+
 int log_open(void);
 void log_close(void);
 void log_forget_fds(void);
@@ -201,7 +207,6 @@ _noreturn_ void log_assert_failed_realm(
 #define log_assert_failed(text, ...) \
         log_assert_failed_realm(LOG_REALM, (text), __VA_ARGS__)
 
-
 _noreturn_ void log_assert_failed_unreachable_realm(
                 LogRealm realm,
                 const char *text,
@@ -221,7 +226,7 @@ void log_assert_failed_return_realm(
         log_assert_failed_return_realm(LOG_REALM, (text), __VA_ARGS__)
 
 #define log_dispatch(level, error, buffer)                              \
-        log_dispatch_internal(level, error, __FILE__, __LINE__, __func__, NULL, NULL, NULL, NULL, buffer)
+        log_dispatch_internal(level, error, PROJECT_FILE, __LINE__, __func__, NULL, NULL, NULL, NULL, buffer)
 #endif /* NM_IGNORED */
 
 /* Logging with level */
@@ -230,7 +235,7 @@ void log_assert_failed_return_realm(
                 int _level = (level), _e = (error), _realm = (realm);   \
                 (log_get_max_level_realm(_realm) >= LOG_PRI(_level))    \
                         ? log_internal_realm(LOG_REALM_PLUS_LEVEL(_realm, _level), _e, \
-                                             __FILE__, __LINE__, __func__, __VA_ARGS__) \
+                                             PROJECT_FILE, __LINE__, __func__, __VA_ARGS__) \
                         : -ERRNO_VALUE(_e);                             \
         })
 
@@ -266,20 +271,20 @@ int log_emergency_level(void);
 /* Structured logging */
 #define log_struct_errno(level, error, ...) \
         log_struct_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \
-                            error, __FILE__, __LINE__, __func__, __VA_ARGS__, NULL)
+                            error, PROJECT_FILE, __LINE__, __func__, __VA_ARGS__, NULL)
 #define log_struct(level, ...) log_struct_errno(level, 0, __VA_ARGS__)
 
 #define log_struct_iovec_errno(level, error, iovec, n_iovec)            \
         log_struct_iovec_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \
-                                  error, __FILE__, __LINE__, __func__, iovec, n_iovec)
+                                  error, PROJECT_FILE, __LINE__, __func__, iovec, n_iovec)
 #define log_struct_iovec(level, iovec, n_iovec) log_struct_iovec_errno(level, 0, iovec, n_iovec)
 
 /* This modifies the buffer passed! */
 #define log_dump(level, buffer) \
         log_dump_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \
-                          0, __FILE__, __LINE__, __func__, buffer)
+                          0, PROJECT_FILE, __LINE__, __func__, buffer)
 
-#define log_oom() log_oom_internal(LOG_REALM, __FILE__, __LINE__, __func__)
+#define log_oom() log_oom_internal(LOG_REALM, PROJECT_FILE, __LINE__, __func__)
 
 bool log_on_console(void) _pure_;
 
@@ -307,6 +312,7 @@ void log_set_prohibit_ipc(bool b);
 
 int log_dup_console(void);
 
+#if 0 /* NM_IGNORED */
 int log_syntax_internal(
                 const char *unit,
                 int level,
@@ -317,6 +323,9 @@ int log_syntax_internal(
                 int line,
                 const char *func,
                 const char *format, ...) _printf_(9, 10);
+#endif /* NM_IGNORED */
+#define log_syntax_internal(unit, level, config_file, config_line, error, file, line, func, format, ...) \
+    log_internal_realm((level), (error), file, (line), (func), "syntax[%s]: "format, (config_file), __VA_ARGS__) \
 
 int log_syntax_invalid_utf8_internal(
                 const char *unit,
@@ -332,7 +341,7 @@ int log_syntax_invalid_utf8_internal(
         ({                                                              \
                 int _level = (level), _e = (error);                     \
                 (log_get_max_level() >= LOG_PRI(_level))                \
-                        ? log_internal_realm(_level, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \
+                        ? log_syntax_internal(unit, _level, config_file, config_line, _e, PROJECT_FILE, __LINE__, __func__, __VA_ARGS__) \
                         : -ERRNO_VALUE(_e);                             \
         })
 
@@ -340,7 +349,7 @@ int log_syntax_invalid_utf8_internal(
         ({                                                              \
                 int _level = (level);                                   \
                 (log_get_max_level() >= LOG_PRI(_level))                \
-                        ? log_syntax_invalid_utf8_internal(unit, _level, config_file, config_line, __FILE__, __LINE__, __func__, rvalue) \
+                        ? log_syntax_invalid_utf8_internal(unit, _level, config_file, config_line, PROJECT_FILE, __LINE__, __func__, rvalue) \
                         : -EINVAL;                                      \
         })
 
diff --git a/shared/systemd/src/basic/macro.h b/shared/systemd/src/basic/macro.h
index 6a1fdab5..43c51326 100644
--- a/shared/systemd/src/basic/macro.h
+++ b/shared/systemd/src/basic/macro.h
@@ -106,6 +106,15 @@
         _Pragma("GCC diagnostic push");                                 \
         _Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"")
 
+#if HAVE_WSTRINGOP_TRUNCATION
+#  define DISABLE_WARNING_STRINGOP_TRUNCATION                           \
+        _Pragma("GCC diagnostic push");                                 \
+        _Pragma("GCC diagnostic ignored \"-Wstringop-truncation\"")
+#else
+#  define DISABLE_WARNING_STRINGOP_TRUNCATION                           \
+        _Pragma("GCC diagnostic push")
+#endif
+
 #define REENABLE_WARNING                                                \
         _Pragma("GCC diagnostic pop")
 #else
@@ -324,12 +333,12 @@ static inline int __coverity_check__(int condition) {
 #define assert_message_se(expr, message)                                \
         do {                                                            \
                 if (_unlikely_(!(expr)))                                \
-                        log_assert_failed(message, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+                        log_assert_failed(message, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__); \
         } while (false)
 
 #define assert_log(expr, message) ((_likely_(expr))                     \
         ? (true)                                                        \
-        : (log_assert_failed_return(message, __FILE__, __LINE__, __PRETTY_FUNCTION__), false))
+        : (log_assert_failed_return(message, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__), false))
 
 #endif  /* __COVERITY__ */
 
@@ -344,18 +353,16 @@ static inline int __coverity_check__(int condition) {
 #endif
 
 #define assert_not_reached(t)                                           \
-        do {                                                            \
-                log_assert_failed_unreachable(t, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
-        } while (false)
+        log_assert_failed_unreachable(t, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__)
 
 #if defined(static_assert)
 #define assert_cc(expr)                                                 \
-        static_assert(expr, #expr);
+        static_assert(expr, #expr)
 #else
 #define assert_cc(expr)                                                 \
         struct CONCATENATE(_assert_struct_, __COUNTER__) {              \
                 char x[(expr) ? 0 : -1];                                \
-        };
+        }
 #endif
 
 #define assert_return(expr, r)                                          \
@@ -464,7 +471,8 @@ static inline int __coverity_check__(int condition) {
                  * type for the array, in the hope that checkers such as ubsan don't complain that the initializers for \
                  * the array are not representable by the base type. Ideally we'd use typeof(x) as base type, but that  \
                  * doesn't work, as we want to use this on bitfields and gcc refuses typeof() on bitfields.) */         \
-                assert_cc((sizeof((long double[]){__VA_ARGS__})/sizeof(long double)) <= 20); \
+                static const long double __assert_in_set[] _unused_ = { __VA_ARGS__ }; \
+                assert_cc(ELEMENTSOF(__assert_in_set) <= 20); \
                 switch(x) {                     \
                 FOR_EACH_MAKE_CASE(__VA_ARGS__) \
                         _found = true;          \
diff --git a/shared/systemd/src/basic/memory-util.h b/shared/systemd/src/basic/memory-util.h
index 915c24a5..9cb8ac3c 100644
--- a/shared/systemd/src/basic/memory-util.h
+++ b/shared/systemd/src/basic/memory-util.h
@@ -2,6 +2,7 @@
 #pragma once
 
 #include <inttypes.h>
+#include <malloc.h>
 #include <stdbool.h>
 #include <string.h>
 #include <sys/types.h>
@@ -37,8 +38,8 @@ static inline int memcmp_nn(const void *s1, size_t n1, const void *s2, size_t n2
 #define memzero(x,l)                                            \
         ({                                                      \
                 size_t _l_ = (l);                               \
-                void *_x_ = (x);                                \
-                _l_ == 0 ? _x_ : memset(_x_, 0, _l_);           \
+                if (_l_ > 0)                                    \
+                        memset(x, 0, _l_);                      \
         })
 
 #define zero(x) (memzero(&(x), sizeof(x)))
@@ -78,6 +79,16 @@ static inline void* explicit_bzero_safe(void *p, size_t l) {
 void *explicit_bzero_safe(void *p, size_t l);
 #endif
 
+static inline void erase_and_freep(void *p) {
+        void *ptr = *(void**) p;
+
+        if (ptr) {
+                size_t l = malloc_usable_size(ptr);
+                explicit_bzero_safe(ptr, l);
+                free(ptr);
+        }
+}
+
 /* Use with _cleanup_ to erase a single 'char' when leaving scope */
 static inline void erase_char(char *p) {
         explicit_bzero_safe(p, sizeof(char));
diff --git a/shared/systemd/src/basic/parse-util.c b/shared/systemd/src/basic/parse-util.c
index 02ef426f..76ef6e09 100644
--- a/shared/systemd/src/basic/parse-util.c
+++ b/shared/systemd/src/basic/parse-util.c
@@ -6,6 +6,7 @@
 #include <inttypes.h>
 #include <linux/oom.h>
 #include <locale.h>
+#include <net/if.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -86,6 +87,9 @@ int parse_mode(const char *s, mode_t *ret) {
 int parse_ifindex(const char *s, int *ret) {
         int ifi, r;
 
+        assert(s);
+        assert(ret);
+
         r = safe_atoi(s, &ifi);
         if (r < 0)
                 return r;
@@ -96,6 +100,24 @@ int parse_ifindex(const char *s, int *ret) {
         return 0;
 }
 
+int parse_ifindex_or_ifname(const char *s, int *ret) {
+        int r;
+
+        assert(s);
+        assert(ret);
+
+        r = parse_ifindex(s, ret);
+        if (r >= 0)
+                return r;
+
+        r = (int) if_nametoindex(s);
+        if (r <= 0)
+                return -errno;
+
+        *ret = r;
+        return 0;
+}
+
 int parse_mtu(int family, const char *s, uint32_t *ret) {
         uint64_t u;
         size_t m;
@@ -342,47 +364,6 @@ int parse_syscall_and_errno(const char *in, char **name, int *error) {
 
         return 0;
 }
-
-char *format_bytes(char *buf, size_t l, uint64_t t) {
-        unsigned i;
-
-        /* This only does IEC units so far */
-
-        static const struct {
-                const char *suffix;
-                uint64_t factor;
-        } table[] = {
-                { "E", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
-                { "P", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
-                { "T", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
-                { "G", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
-                { "M", UINT64_C(1024)*UINT64_C(1024) },
-                { "K", UINT64_C(1024) },
-        };
-
-        if (t == (uint64_t) -1)
-                return NULL;
-
-        for (i = 0; i < ELEMENTSOF(table); i++) {
-
-                if (t >= table[i].factor) {
-                        snprintf(buf, l,
-                                 "%" PRIu64 ".%" PRIu64 "%s",
-                                 t / table[i].factor,
-                                 ((t*UINT64_C(10)) / table[i].factor) % UINT64_C(10),
-                                 table[i].suffix);
-
-                        goto finish;
-                }
-        }
-
-        snprintf(buf, l, "%" PRIu64 "B", t);
-
-finish:
-        buf[l-1] = 0;
-        return buf;
-
-}
 #endif /* NM_IGNORED */
 
 int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
diff --git a/shared/systemd/src/basic/parse-util.h b/shared/systemd/src/basic/parse-util.h
index e47641b4..3a70b792 100644
--- a/shared/systemd/src/basic/parse-util.h
+++ b/shared/systemd/src/basic/parse-util.h
@@ -9,13 +9,12 @@
 
 #include "macro.h"
 
-#define MODE_INVALID ((mode_t) -1)
-
 int parse_boolean(const char *v) _pure_;
 int parse_dev(const char *s, dev_t *ret);
 int parse_pid(const char *s, pid_t* ret_pid);
 int parse_mode(const char *s, mode_t *ret);
 int parse_ifindex(const char *s, int *ret);
+int parse_ifindex_or_ifname(const char *s, int *ret);
 int parse_mtu(int family, const char *s, uint32_t *ret);
 
 int parse_size(const char *t, uint64_t base, uint64_t *size);
@@ -23,9 +22,6 @@ int parse_range(const char *t, unsigned *lower, unsigned *upper);
 int parse_errno(const char *t);
 int parse_syscall_and_errno(const char *in, char **name, int *error);
 
-#define FORMAT_BYTES_MAX 8
-char *format_bytes(char *buf, size_t l, uint64_t t);
-
 int safe_atou_full(const char *s, unsigned base, unsigned *ret_u);
 
 static inline int safe_atou(const char *s, unsigned *ret_u) {
diff --git a/shared/systemd/src/basic/path-util.c b/shared/systemd/src/basic/path-util.c
index 29955a36..e39656bc 100644
--- a/shared/systemd/src/basic/path-util.c
+++ b/shared/systemd/src/basic/path-util.c
@@ -71,10 +71,7 @@ char *path_make_absolute(const char *p, const char *prefix) {
         if (path_is_absolute(p) || isempty(prefix))
                 return strdup(p);
 
-        if (endswith(prefix, "/"))
-                return strjoin(prefix, p);
-        else
-                return strjoin(prefix, "/", p);
+        return path_join(prefix, p);
 }
 
 int safe_getcwd(char **ret) {
@@ -213,6 +210,18 @@ int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
         return 0;
 }
 
+char* path_startswith_strv(const char *p, char **set) {
+        char **s, *t;
+
+        STRV_FOREACH(s, set) {
+                t = path_startswith(p, *s);
+                if (t)
+                        return t;
+        }
+
+        return NULL;
+}
+
 int path_strv_make_absolute_cwd(char **l) {
         char **s;
         int r;
@@ -259,7 +268,7 @@ char **path_strv_resolve(char **l, const char *root) {
 
                 if (root) {
                         orig = *s;
-                        t = prefix_root(root, orig);
+                        t = path_join(root, orig);
                         if (!t) {
                                 enomem = true;
                                 continue;
@@ -389,6 +398,54 @@ char *path_simplify(char *path, bool kill_dots) {
         return path;
 }
 
+#if 0 /* NM_IGNORED */
+int path_simplify_and_warn(
+                char *path,
+                unsigned flag,
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *lvalue) {
+
+        bool fatal = flag & PATH_CHECK_FATAL;
+
+        assert(!FLAGS_SET(flag, PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE));
+
+        if (!utf8_is_valid(path))
+                return log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, path);
+
+        if (flag & (PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)) {
+                bool absolute;
+
+                absolute = path_is_absolute(path);
+
+                if (!absolute && (flag & PATH_CHECK_ABSOLUTE))
+                        return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
+                                          "%s= path is not absolute%s: %s",
+                                          lvalue, fatal ? "" : ", ignoring", path);
+
+                if (absolute && (flag & PATH_CHECK_RELATIVE))
+                        return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
+                                          "%s= path is absolute%s: %s",
+                                          lvalue, fatal ? "" : ", ignoring", path);
+        }
+
+        path_simplify(path, true);
+
+        if (!path_is_valid(path))
+                return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
+                                  "%s= path has invalid length (%zu bytes)%s.",
+                                  lvalue, strlen(path), fatal ? "" : ", ignoring");
+
+        if (!path_is_normalized(path))
+                return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
+                                  "%s= path is not normalized%s: %s",
+                                  lvalue, fatal ? "" : ", ignoring", path);
+
+        return 0;
+}
+#endif /* NM_IGNORED */
+
 char* path_startswith(const char *path, const char *prefix) {
         assert(path);
         assert(prefix);
@@ -586,7 +643,7 @@ int find_binary(const char *name, char **ret) {
                 if (!path_is_absolute(element))
                         continue;
 
-                j = strjoin(element, "/", name);
+                j = path_join(element, name);
                 if (!j)
                         return -ENOMEM;
 
@@ -691,40 +748,6 @@ int mkfs_exists(const char *fstype) {
         return binary_is_good(mkfs);
 }
 
-char *prefix_root(const char *root, const char *path) {
-        char *n, *p;
-        size_t l;
-
-        /* If root is passed, prefixes path with it. Otherwise returns
-         * it as is. */
-
-        assert(path);
-
-        /* First, drop duplicate prefixing slashes from the path */
-        while (path[0] == '/' && path[1] == '/')
-                path++;
-
-        if (empty_or_root(root))
-                return strdup(path);
-
-        l = strlen(root) + 1 + strlen(path) + 1;
-
-        n = new(char, l);
-        if (!n)
-                return NULL;
-
-        p = stpcpy(n, root);
-
-        while (p > n && p[-1] == '/')
-                p--;
-
-        if (path[0] != '/')
-                *(p++) = '/';
-
-        strcpy(p, path);
-        return n;
-}
-
 int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg) {
         char *p;
         int r;
@@ -1034,7 +1057,7 @@ int systemd_installation_has_version(const char *root, unsigned minimal_version)
                 _cleanup_free_ char *path = NULL;
                 char *c, **name;
 
-                path = prefix_root(root, pattern);
+                path = path_join(root, pattern);
                 if (!path)
                         return -ENOMEM;
 
@@ -1104,50 +1127,4 @@ bool empty_or_root(const char *root) {
 
         return root[strspn(root, "/")] == 0;
 }
-
-int path_simplify_and_warn(
-                char *path,
-                unsigned flag,
-                const char *unit,
-                const char *filename,
-                unsigned line,
-                const char *lvalue) {
-
-        bool fatal = flag & PATH_CHECK_FATAL;
-
-        assert(!FLAGS_SET(flag, PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE));
-
-        if (!utf8_is_valid(path))
-                return log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, path);
-
-        if (flag & (PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)) {
-                bool absolute;
-
-                absolute = path_is_absolute(path);
-
-                if (!absolute && (flag & PATH_CHECK_ABSOLUTE))
-                        return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
-                                          "%s= path is not absolute%s: %s",
-                                          lvalue, fatal ? "" : ", ignoring", path);
-
-                if (absolute && (flag & PATH_CHECK_RELATIVE))
-                        return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
-                                          "%s= path is absolute%s: %s",
-                                          lvalue, fatal ? "" : ", ignoring", path);
-        }
-
-        path_simplify(path, true);
-
-        if (!path_is_valid(path))
-                return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
-                                  "%s= path has invalid length (%zu bytes)%s.",
-                                  lvalue, strlen(path), fatal ? "" : ", ignoring");
-
-        if (!path_is_normalized(path))
-                return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
-                                  "%s= path is not normalized%s: %s",
-                                  lvalue, fatal ? "" : ", ignoring", path);
-
-        return 0;
-}
 #endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/path-util.h b/shared/systemd/src/basic/path-util.h
index 5204adaa..cd6216bb 100644
--- a/shared/systemd/src/basic/path-util.h
+++ b/shared/systemd/src/basic/path-util.h
@@ -56,6 +56,14 @@ char* path_join_internal(const char *first, ...);
 
 char* path_simplify(char *path, bool kill_dots);
 
+enum {
+        PATH_CHECK_FATAL    = 1 << 0,  /* If not set, then error message is appended with 'ignoring'. */
+        PATH_CHECK_ABSOLUTE = 1 << 1,
+        PATH_CHECK_RELATIVE = 1 << 2,
+};
+
+int path_simplify_and_warn(char *path, unsigned flag, const char *unit, const char *filename, unsigned line, const char *lvalue);
+
 static inline bool path_equal_ptr(const char *a, const char *b) {
         return !!a == !!b && (!a || path_equal(a, b));
 }
@@ -73,17 +81,8 @@ static inline bool path_equal_ptr(const char *a, const char *b) {
                 _found;                                         \
         })
 
-#define PATH_STARTSWITH_SET(p, ...)                             \
-        ({                                                      \
-                const char *_p = (p);                           \
-                char *_found = NULL, **_i;                      \
-                STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) {      \
-                        _found = path_startswith(_p, *_i);      \
-                        if (_found)                             \
-                                break;                          \
-                }                                               \
-                _found;                                         \
-        })
+char* path_startswith_strv(const char *p, char **set);
+#define PATH_STARTSWITH_SET(p, ...) path_startswith_strv(p, STRV_MAKE(__VA_ARGS__))
 
 int path_strv_make_absolute_cwd(char **l);
 char** path_strv_resolve(char **l, const char *root);
@@ -118,10 +117,8 @@ int mkfs_exists(const char *fstype);
              _slash && ((*_slash = 0), true);                           \
              _slash = strrchr((prefix), '/'))
 
-char *prefix_root(const char *root, const char *path);
-
-/* Similar to prefix_root(), but returns an alloca() buffer, or
- * possibly a const pointer into the path parameter */
+/* 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. */
 #define prefix_roota(root, path)                                        \
         ({                                                              \
                 const char* _path = (path), *_root = (root), *_ret;     \
@@ -129,7 +126,7 @@ char *prefix_root(const char *root, const char *path);
                 size_t _l;                                              \
                 while (_path[0] == '/' && _path[1] == '/')              \
                         _path ++;                                       \
-                if (empty_or_root(_root))                               \
+                if (isempty(_root))                                     \
                         _ret = _path;                                   \
                 else {                                                  \
                         _l = strlen(_root) + 1 + strlen(_path) + 1;     \
@@ -182,11 +179,3 @@ bool empty_or_root(const char *root);
 static inline const char *empty_to_root(const char *path) {
         return isempty(path) ? "/" : path;
 }
-
-enum {
-        PATH_CHECK_FATAL    = 1 << 0,  /* If not set, then error message is appended with 'ignoring'. */
-        PATH_CHECK_ABSOLUTE = 1 << 1,
-        PATH_CHECK_RELATIVE = 1 << 2,
-};
-
-int path_simplify_and_warn(char *path, unsigned flag, const char *unit, const char *filename, unsigned line, const char *lvalue);
diff --git a/shared/systemd/src/basic/process-util.c b/shared/systemd/src/basic/process-util.c
index 7431be3e..317815fe 100644
--- a/shared/systemd/src/basic/process-util.c
+++ b/shared/systemd/src/basic/process-util.c
@@ -6,11 +6,9 @@
 #include <errno.h>
 #include <limits.h>
 #include <linux/oom.h>
-#include <sched.h>
 #include <signal.h>
 #include <stdbool.h>
 #include <stdio.h>
-#include <stdio_ext.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/mman.h>
@@ -30,10 +28,12 @@
 #include "alloc-util.h"
 #include "architecture.h"
 #include "escape.h"
+#include "env-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
 #include "ioprio.h"
+#include "locale-util.h"
 #include "log.h"
 #include "macro.h"
 #include "memory-util.h"
@@ -48,9 +48,16 @@
 #include "string-util.h"
 #include "terminal-util.h"
 #include "user-util.h"
+#include "utf8.h"
 
 #if 0 /* NM_IGNORED */
-int get_process_state(pid_t pid) {
+
+/* The kernel limits userspace processes to TASK_COMM_LEN (16 bytes), but allows higher values for its own
+ * workers, e.g. "kworker/u9:3-kcryptd/253:0". Let's pick a fixed smallish limit that will work for the kernel.
+ */
+#define COMM_MAX_LEN 128
+
+static int get_process_state(pid_t pid) {
         const char *p;
         char state;
         int r;
@@ -86,7 +93,7 @@ int get_process_comm(pid_t pid, char **ret) {
         assert(ret);
         assert(pid >= 0);
 
-        escaped = new(char, TASK_COMM_LEN);
+        escaped = new(char, COMM_MAX_LEN);
         if (!escaped)
                 return -ENOMEM;
 
@@ -99,167 +106,92 @@ int get_process_comm(pid_t pid, char **ret) {
                 return r;
 
         /* Escape unprintable characters, just in case, but don't grow the string beyond the underlying size */
-        cellescape(escaped, TASK_COMM_LEN, comm);
+        cellescape(escaped, COMM_MAX_LEN, comm);
 
         *ret = TAKE_PTR(escaped);
         return 0;
 }
 
-int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) {
+int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **line) {
         _cleanup_fclose_ FILE *f = NULL;
-        bool space = false;
-        char *k;
-        _cleanup_free_ char *ans = NULL;
+        _cleanup_free_ char *t = NULL, *ans = NULL;
         const char *p;
-        int c;
+        int r;
+        size_t k;
+
+        /* This is supposed to be a safety guard against runaway command lines. */
+        size_t max_length = sc_arg_max();
 
         assert(line);
         assert(pid >= 0);
 
-        /* Retrieves a process' command line. Replaces unprintable characters while doing so by whitespace (coalescing
-         * multiple sequential ones into one). If max_length is != 0 will return a string of the specified size at most
-         * (the trailing NUL byte does count towards the length here!), abbreviated with a "..." ellipsis. If
-         * comm_fallback is true and the process has no command line set (the case for kernel threads), or has a
-         * command line that resolves to the empty string will return the "comm" name of the process instead.
+        /* Retrieves a process' command line. Replaces non-utf8 bytes by replacement character (�). If
+         * max_columns is != -1 will return a string of the specified console width at most, abbreviated with
+         * an ellipsis. If PROCESS_CMDLINE_COMM_FALLBACK is specified in flags and the process has no command
+         * line set (the case for kernel threads), or has a command line that resolves to the empty string
+         * will return the "comm" name of the process instead. This will use at most _SC_ARG_MAX bytes of
+         * input data.
          *
          * Returns -ESRCH if the process doesn't exist, and -ENOENT if the process has no command line (and
          * comm_fallback is false). Returns 0 and sets *line otherwise. */
 
         p = procfs_file_alloca(pid, "cmdline");
+        r = fopen_unlocked(p, "re", &f);
+        if (r == -ENOENT)
+                return -ESRCH;
+        if (r < 0)
+                return r;
 
-        f = fopen(p, "re");
-        if (!f) {
-                if (errno == ENOENT)
-                        return -ESRCH;
-                return -errno;
-        }
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
-        if (max_length == 0) {
-                /* This is supposed to be a safety guard against runaway command lines. */
-                long l = sysconf(_SC_ARG_MAX);
-                assert(l > 0);
-                max_length = l;
-        }
-
-        if (max_length == 1) {
+        /* We assume that each four-byte character uses one or two columns. If we ever check for combining
+         * characters, this assumption will need to be adjusted. */
+        if ((size_t) 4 * max_columns + 1 < max_columns)
+                max_length = MIN(max_length, (size_t) 4 * max_columns + 1);
 
-                /* If there's only room for one byte, return the empty string */
-                ans = new0(char, 1);
-                if (!ans)
-                        return -ENOMEM;
+        t = new(char, max_length);
+        if (!t)
+                return -ENOMEM;
 
-                *line = TAKE_PTR(ans);
-                return 0;
+        k = fread(t, 1, max_length, f);
+        if (k > 0) {
+                /* Arguments are separated by NULs. Let's replace those with spaces. */
+                for (size_t i = 0; i < k - 1; i++)
+                        if (t[i] == '\0')
+                                t[i] = ' ';
 
+                t[k] = '\0'; /* Normally, t[k] is already NUL, so this is just a guard in case of short read */
         } else {
-                bool dotdotdot = false;
-                size_t left;
-
-                ans = new(char, max_length);
-                if (!ans)
-                        return -ENOMEM;
-
-                k = ans;
-                left = max_length;
-                while ((c = getc(f)) != EOF) {
-
-                        if (isprint(c)) {
-
-                                if (space) {
-                                        if (left <= 2) {
-                                                dotdotdot = true;
-                                                break;
-                                        }
-
-                                        *(k++) = ' ';
-                                        left--;
-                                        space = false;
-                                }
-
-                                if (left <= 1) {
-                                        dotdotdot = true;
-                                        break;
-                                }
-
-                                *(k++) = (char) c;
-                                left--;
-                        } else if (k > ans)
-                                space = true;
-                }
-
-                if (dotdotdot) {
-                        if (max_length <= 4) {
-                                k = ans;
-                                left = max_length;
-                        } else {
-                                k = ans + max_length - 4;
-                                left = 4;
-
-                                /* Eat up final spaces */
-                                while (k > ans && isspace(k[-1])) {
-                                        k--;
-                                        left++;
-                                }
-                        }
-
-                        strncpy(k, "...", left-1);
-                        k[left-1] = 0;
-                } else
-                        *k = 0;
-        }
-
-        /* Kernel threads have no argv[] */
-        if (isempty(ans)) {
-                _cleanup_free_ char *t = NULL;
-                int h;
-
-                ans = mfree(ans);
+                /* We only treat getting nothing as an error. We *could* also get an error after reading some
+                 * data, but we ignore that case, as such an error is rather unlikely and we prefer to get
+                 * some data rather than none. */
+                if (ferror(f))
+                        return -errno;
 
-                if (!comm_fallback)
+                if (!(flags & PROCESS_CMDLINE_COMM_FALLBACK))
                         return -ENOENT;
 
-                h = get_process_comm(pid, &t);
-                if (h < 0)
-                        return h;
+                /* Kernel threads have no argv[] */
+                _cleanup_free_ char *t2 = NULL;
 
-                size_t l = strlen(t);
-
-                if (l + 3 <= max_length) {
-                        ans = strjoin("[", t, "]");
-                        if (!ans)
-                                return -ENOMEM;
+                r = get_process_comm(pid, &t2);
+                if (r < 0)
+                        return r;
 
-                } else if (max_length <= 6) {
-                        ans = new(char, max_length);
-                        if (!ans)
-                                return -ENOMEM;
+                mfree(t);
+                t = strjoin("[", t2, "]");
+                if (!t)
+                        return -ENOMEM;
+        }
 
-                        memcpy(ans, "[...]", max_length-1);
-                        ans[max_length-1] = 0;
-                } else {
-                        t[max_length - 6] = 0;
+        delete_trailing_chars(t, WHITESPACE);
 
-                        /* Chop off final spaces */
-                        delete_trailing_chars(t, WHITESPACE);
+        bool eight_bit = (flags & PROCESS_CMDLINE_USE_LOCALE) && !is_locale_utf8();
 
-                        ans = strjoin("[", t, "...]");
-                        if (!ans)
-                                return -ENOMEM;
-                }
-
-                *line = TAKE_PTR(ans);
-                return 0;
-        }
-
-        k = realloc(ans, strlen(ans) + 1);
-        if (!k)
+        ans = escape_non_printable_full(t, max_columns, eight_bit);
+        if (!ans)
                 return -ENOMEM;
 
-        ans = NULL;
-        *line = k;
-
+        (void) str_realloc(&ans);
+        *line = TAKE_PTR(ans);
         return 0;
 }
 
@@ -291,7 +223,7 @@ int rename_process(const char name[]) {
          * can use PR_SET_NAME, which sets the thread name for the calling thread. */
         if (prctl(PR_SET_NAME, name) < 0)
                 log_debug_errno(errno, "PR_SET_NAME failed: %m");
-        if (l >= TASK_COMM_LEN) /* Linux process names can be 15 chars at max */
+        if (l >= TASK_COMM_LEN) /* Linux userspace process names can be 15 chars at max */
                 truncated = true;
 
         /* Second step, change glibc's ID of the process name. */
@@ -518,14 +450,11 @@ static int get_process_id(pid_t pid, const char *field, uid_t *uid) {
                 return -EINVAL;
 
         p = procfs_file_alloca(pid, "status");
-        f = fopen(p, "re");
-        if (!f) {
-                if (errno == ENOENT)
-                        return -ESRCH;
-                return -errno;
-        }
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+        r = fopen_unlocked(p, "re", &f);
+        if (r == -ENOENT)
+                return -ESRCH;
+        if (r < 0)
+                return r;
 
         for (;;) {
                 _cleanup_free_ char *line = NULL;
@@ -607,14 +536,11 @@ int get_process_environ(pid_t pid, char **env) {
 
         p = procfs_file_alloca(pid, "environ");
 
-        f = fopen(p, "re");
-        if (!f) {
-                if (errno == ENOENT)
-                        return -ESRCH;
-                return -errno;
-        }
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+        r = fopen_unlocked(p, "re", &f);
+        if (r == -ENOENT)
+                return -ESRCH;
+        if (r < 0)
+                return r;
 
         for (;;) {
                 char c;
@@ -900,15 +826,11 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) {
 
         path = procfs_file_alloca(pid, "environ");
 
-        f = fopen(path, "re");
-        if (!f) {
-                if (errno == ENOENT)
-                        return -ESRCH;
-
-                return -errno;
-        }
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+        r = fopen_unlocked(path, "re", &f);
+        if (r == -ENOENT)
+                return -ESRCH;
+        if (r < 0)
+                return r;
 
         l = strlen(field);
         for (;;) {
@@ -1558,45 +1480,11 @@ int set_oom_score_adjust(int value) {
                                  WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_DISABLE_BUFFER);
 }
 
-int cpus_in_affinity_mask(void) {
-        size_t n = 16;
-        int r;
-
-        for (;;) {
-                cpu_set_t *c;
-
-                c = CPU_ALLOC(n);
-                if (!c)
-                        return -ENOMEM;
-
-                if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), c) >= 0) {
-                        int k;
-
-                        k = CPU_COUNT_S(CPU_ALLOC_SIZE(n), c);
-                        CPU_FREE(c);
-
-                        if (k <= 0)
-                                return -EINVAL;
-
-                        return k;
-                }
-
-                r = -errno;
-                CPU_FREE(c);
-
-                if (r != -EINVAL)
-                        return r;
-                if (n > SIZE_MAX/2)
-                        return -ENOMEM;
-                n *= 2;
-        }
-}
-
 static const char *const ioprio_class_table[] = {
         [IOPRIO_CLASS_NONE] = "none",
         [IOPRIO_CLASS_RT] = "realtime",
         [IOPRIO_CLASS_BE] = "best-effort",
-        [IOPRIO_CLASS_IDLE] = "idle"
+        [IOPRIO_CLASS_IDLE] = "idle",
 };
 
 DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, IOPRIO_N_CLASSES);
@@ -1617,7 +1505,7 @@ static const char* const sched_policy_table[] = {
         [SCHED_BATCH] = "batch",
         [SCHED_IDLE] = "idle",
         [SCHED_FIFO] = "fifo",
-        [SCHED_RR] = "rr"
+        [SCHED_RR] = "rr",
 };
 
 DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX);
diff --git a/shared/systemd/src/basic/process-util.h b/shared/systemd/src/basic/process-util.h
index 3933bee6..20f663e2 100644
--- a/shared/systemd/src/basic/process-util.h
+++ b/shared/systemd/src/basic/process-util.h
@@ -31,9 +31,13 @@
                 _r_;                                                    \
         })
 
-int get_process_state(pid_t pid);
+typedef enum ProcessCmdlineFlags {
+        PROCESS_CMDLINE_COMM_FALLBACK = 1 << 0,
+        PROCESS_CMDLINE_USE_LOCALE    = 1 << 1,
+} ProcessCmdlineFlags;
+
 int get_process_comm(pid_t pid, char **name);
-int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line);
+int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **line);
 int get_process_exe(pid_t pid, char **name);
 int get_process_uid(pid_t pid, uid_t *uid);
 int get_process_gid(pid_t pid, gid_t *gid);
@@ -187,7 +191,7 @@ int set_oom_score_adjust(int value);
 #error "Unknown pid_t size"
 #endif
 
-assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX)
+assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX);
 
 /* Like TAKE_PTR() but for child PIDs, resetting them to 0 */
 #define TAKE_PID(pid)                           \
@@ -196,5 +200,3 @@ assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX)
                 (pid) = 0;                      \
                 _pid_;                          \
         })
-
-int cpus_in_affinity_mask(void);
diff --git a/shared/systemd/src/basic/random-util.c b/shared/systemd/src/basic/random-util.c
index b8b45958..c70871bf 100644
--- a/shared/systemd/src/basic/random-util.c
+++ b/shared/systemd/src/basic/random-util.c
@@ -30,13 +30,75 @@
 #include "io-util.h"
 #include "missing.h"
 #include "random-util.h"
+#include "siphash24.h"
 #include "time-util.h"
 
 int rdrand(unsigned long *ret) {
 
+        /* So, you are a "security researcher", and you wonder why we bother with using raw RDRAND here,
+         * instead of sticking to /dev/urandom or getrandom()?
+         *
+         * Here's why: early boot. On Linux, during early boot the random pool that backs /dev/urandom and
+         * getrandom() is generally not initialized yet. It is very common that initialization of the random
+         * pool takes a longer time (up to many minutes), in particular on embedded devices that have no
+         * explicit hardware random generator, as well as in virtualized environments such as major cloud
+         * installations that do not provide virtio-rng or a similar mechanism.
+         *
+         * In such an environment using getrandom() synchronously means we'd block the entire system boot-up
+         * until the pool is initialized, i.e. *very* long. Using getrandom() asynchronously (GRND_NONBLOCK)
+         * would mean acquiring randomness during early boot would simply fail. Using /dev/urandom would mean
+         * generating many kmsg log messages about our use of it before the random pool is properly
+         * initialized. Neither of these outcomes is desirable.
+         *
+         * Thus, for very specific purposes we use RDRAND instead of either of these three options. RDRAND
+         * provides us quickly and relatively reliably with random values, without having to delay boot,
+         * without triggering warning messages in kmsg.
+         *
+         * Note that we use RDRAND only under very specific circumstances, when the requirements on the
+         * quality of the returned entropy permit it. Specifically, here are some cases where we *do* use
+         * RDRAND:
+         *
+         *         • UUID generation: UUIDs are supposed to be universally unique but are not cryptographic
+         *           key material. The quality and trust level of RDRAND should hence be OK: UUIDs should be
+         *           generated in a way that is reliably unique, but they do not require ultimate trust into
+         *           the entropy generator. systemd generates a number of UUIDs during early boot, including
+         *           'invocation IDs' for every unit spawned that identify the specific invocation of the
+         *           service globally, and a number of others. Other alternatives for generating these UUIDs
+         *           have been considered, but don't really work: for example, hashing uuids from a local
+         *           system identifier combined with a counter falls flat because during early boot disk
+         *           storage is not yet available (think: initrd) and thus a system-specific ID cannot be
+         *           stored or retrieved yet.
+         *
+         *         • Hash table seed generation: systemd uses many hash tables internally. Hash tables are
+         *           generally assumed to have O(1) access complexity, but can deteriorate to prohibitive
+         *           O(n) access complexity if an attacker manages to trigger a large number of hash
+         *           collisions. Thus, systemd (as any software employing hash tables should) uses seeded
+         *           hash functions for its hash tables, with a seed generated randomly. The hash tables
+         *           systemd employs watch the fill level closely and reseed if necessary. This allows use of
+         *           a low quality RNG initially, as long as it improves should a hash table be under attack:
+         *           the attacker after all needs to to trigger many collisions to exploit it for the purpose
+         *           of DoS, but if doing so improves the seed the attack surface is reduced as the attack
+         *           takes place.
+         *
+         * Some cases where we do NOT use RDRAND are:
+         *
+         *         • Generation of cryptographic key material 🔑
+         *
+         *         • Generation of cryptographic salt values 🧂
+         *
+         * This function returns:
+         *
+         *         -EOPNOTSUPP → RDRAND is not available on this system 😔
+         *         -EAGAIN     → The operation failed this time, but is likely to work if you try again a few
+         *                       times ♻
+         *         -EUCLEAN    → We got some random value, but it looked strange, so we refused using it.
+         *                       This failure might or might not be temporary. 😕
+         */
+
 #if defined(__i386__) || defined(__x86_64__)
         static int have_rdrand = -1;
-        unsigned char err;
+        unsigned long v;
+        uint8_t success;
 
         if (have_rdrand < 0) {
                 uint32_t eax, ebx, ecx, edx;
@@ -47,7 +109,12 @@ int rdrand(unsigned long *ret) {
                         return -EOPNOTSUPP;
                 }
 
-                have_rdrand = !!(ecx & (1U << 30));
+/* Compat with old gcc where bit_RDRND didn't exist yet */
+#ifndef bit_RDRND
+#define bit_RDRND (1U << 30)
+#endif
+
+                have_rdrand = !!(ecx & bit_RDRND);
         }
 
         if (have_rdrand == 0)
@@ -55,12 +122,24 @@ int rdrand(unsigned long *ret) {
 
         asm volatile("rdrand %0;"
                      "setc %1"
-                     : "=r" (*ret),
-                       "=qm" (err));
-        msan_unpoison(&err, sizeof(err));
-        if (!err)
+                     : "=r" (v),
+                       "=qm" (success));
+        msan_unpoison(&success, sizeof(success));
+        if (!success)
                 return -EAGAIN;
 
+        /* Apparently on some AMD CPUs RDRAND will sometimes (after a suspend/resume cycle?) report success
+         * via the carry flag but nonetheless return the same fixed value -1 in all cases. This appears to be
+         * a bad bug in the CPU or firmware. Let's deal with that and work-around this by explicitly checking
+         * for this special value (and also 0, just to be sure) and filtering it out. This is a work-around
+         * only however and something AMD really should fix properly. The Linux kernel should probably work
+         * around this issue by turning off RDRAND altogether on those CPUs. See:
+         * https://github.com/systemd/systemd/issues/11810 */
+        if (v == 0 || v == ULONG_MAX)
+                return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN),
+                                       "RDRAND returned suspicious value %lx, assuming bad hardware RNG, not using value.", v);
+
+        *ret = v;
         return 0;
 #else
         return -EOPNOTSUPP;
@@ -73,21 +152,32 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) {
         bool got_some = false;
         int r;
 
-        /* Gathers some randomness from the kernel (or the CPU if the RANDOM_ALLOW_RDRAND flag is set). This call won't
-         * block, unless the RANDOM_BLOCK flag is set. If RANDOM_DONT_DRAIN is set, an error is returned if the random
-         * pool is not initialized. Otherwise it will always return some data from the kernel, regardless of whether
-         * the random pool is fully initialized or not. */
+        /* Gathers some high-quality randomness from the kernel (or potentially mid-quality randomness from
+         * the CPU if the RANDOM_ALLOW_RDRAND flag is set). This call won't block, unless the RANDOM_BLOCK
+         * flag is set. If RANDOM_MAY_FAIL is set, an error is returned if the random pool is not
+         * initialized. Otherwise it will always return some data from the kernel, regardless of whether the
+         * random pool is fully initialized or not. If RANDOM_EXTEND_WITH_PSEUDO is set, and some but not
+         * enough better quality randomness could be acquired, the rest is filled up with low quality
+         * randomness.
+         *
+         * Of course, when creating cryptographic key material you really shouldn't use RANDOM_ALLOW_DRDRAND
+         * or even RANDOM_EXTEND_WITH_PSEUDO.
+         *
+         * When generating UUIDs it's fine to use RANDOM_ALLOW_RDRAND but not OK to use
+         * RANDOM_EXTEND_WITH_PSEUDO. In fact RANDOM_EXTEND_WITH_PSEUDO is only really fine when invoked via
+         * an "all bets are off" wrapper, such as random_bytes(), see below. */
 
         if (n == 0)
                 return 0;
 
         if (FLAGS_SET(flags, RANDOM_ALLOW_RDRAND))
-                /* Try x86-64' RDRAND intrinsic if we have it. We only use it if high quality randomness is not
-                 * required, as we don't trust it (who does?). Note that we only do a single iteration of RDRAND here,
-                 * even though the Intel docs suggest calling this in a tight loop of 10 invocations or so. That's
-                 * because we don't really care about the quality here. We generally prefer using RDRAND if the caller
-                 * allows us too, since this way we won't drain the kernel randomness pool if we don't need it, as the
-                 * pool's entropy is scarce. */
+                /* Try x86-64' RDRAND intrinsic if we have it. We only use it if high quality randomness is
+                 * not required, as we don't trust it (who does?). Note that we only do a single iteration of
+                 * RDRAND here, even though the Intel docs suggest calling this in a tight loop of 10
+                 * invocations or so. That's because we don't really care about the quality here. We
+                 * generally prefer using RDRAND if the caller allows us to, since this way we won't upset
+                 * the kernel's random subsystem by accessing it before the pool is initialized (after all it
+                 * will kmsg log about every attempt to do so)..*/
                 for (;;) {
                         unsigned long u;
                         size_t m;
@@ -162,12 +252,13 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) {
                                 break;
 
                         } else if (errno == EAGAIN) {
-                                /* The kernel has no entropy whatsoever. Let's remember to use the syscall the next
-                                 * time again though.
+                                /* The kernel has no entropy whatsoever. Let's remember to use the syscall
+                                 * the next time again though.
                                  *
-                                 * If RANDOM_DONT_DRAIN is set, return an error so that random_bytes() can produce some
-                                 * pseudo-random bytes instead. Otherwise, fall back to /dev/urandom, which we know is empty,
-                                 * but the kernel will produce some bytes for us on a best-effort basis. */
+                                 * If RANDOM_MAY_FAIL is set, return an error so that random_bytes() can
+                                 * produce some pseudo-random bytes instead. Otherwise, fall back to
+                                 * /dev/urandom, which we know is empty, but the kernel will produce some
+                                 * bytes for us on a best-effort basis. */
                                 have_syscall = true;
 
                                 if (got_some && FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) {
@@ -176,7 +267,7 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) {
                                         return 0;
                                 }
 
-                                if (FLAGS_SET(flags, RANDOM_DONT_DRAIN))
+                                if (FLAGS_SET(flags, RANDOM_MAY_FAIL))
                                         return -ENODATA;
 
                                 /* Use /dev/urandom instead */
@@ -205,14 +296,19 @@ void initialize_srand(void) {
                 return;
 
 #if HAVE_SYS_AUXV_H
-        /* The kernel provides us with 16 bytes of entropy in auxv, so let's
-         * try to make use of that to seed the pseudo-random generator. It's
-         * better than nothing... */
+        /* The kernel provides us with 16 bytes of entropy in auxv, so let's try to make use of that to seed
+         * the pseudo-random generator. It's better than nothing... But let's first hash it to make it harder
+         * to recover the original value by watching any pseudo-random bits we generate. After all the
+         * AT_RANDOM data might be used by other stuff too (in particular: ASLR), and we probably shouldn't
+         * leak the seed for that. */
 
-        auxv = (const void*) getauxval(AT_RANDOM);
+        auxv = ULONG_TO_PTR(getauxval(AT_RANDOM));
         if (auxv) {
-                assert_cc(sizeof(x) <= 16);
-                memcpy(&x, auxv, sizeof(x));
+                static const uint8_t auxval_hash_key[16] = {
+                        0x92, 0x6e, 0xfe, 0x1b, 0xcf, 0x00, 0x52, 0x9c, 0xcc, 0x42, 0xcf, 0xdc, 0x94, 0x1f, 0x81, 0x0f
+                };
+
+                x = (unsigned) siphash24(auxv, 16, auxval_hash_key);
         } else
 #endif
                 x = 0;
@@ -238,6 +334,11 @@ void initialize_srand(void) {
 void pseudo_random_bytes(void *p, size_t n) {
         uint8_t *q;
 
+        /* This returns pseudo-random data using libc's rand() function. You probably never want to call this
+         * directly, because why would you use this if you can get better stuff cheaply? Use random_bytes()
+         * instead, see below: it will fall back to this function if there's nothing better to get, but only
+         * then. */
+
         initialize_srand();
 
         for (q = p; q < (uint8_t*) p + n; q += RAND_STEP) {
@@ -259,7 +360,39 @@ void pseudo_random_bytes(void *p, size_t n) {
 
 void random_bytes(void *p, size_t n) {
 
-        if (genuine_random_bytes(p, n, RANDOM_EXTEND_WITH_PSEUDO|RANDOM_DONT_DRAIN|RANDOM_ALLOW_RDRAND) >= 0)
+        /* This returns high quality randomness if we can get it cheaply. If we can't because for some reason
+         * it is not available we'll try some crappy fallbacks.
+         *
+         * What this function will do:
+         *
+         *         • This function will preferably use the CPU's RDRAND operation, if it is available, in
+         *           order to return "mid-quality" random values cheaply.
+         *
+         *         • Use getrandom() with GRND_NONBLOCK, to return high-quality random values if they are
+         *           cheaply available.
+         *
+         *         • This function will return pseudo-random data, generated via libc rand() if nothing
+         *           better is available.
+         *
+         *         • This function will work fine in early boot
+         *
+         *         • This function will always succeed
+         *
+         * What this function won't do:
+         *
+         *         • This function will never fail: it will give you randomness no matter what. It might not
+         *           be high quality, but it will return some, possibly generated via libc's rand() call.
+         *
+         *         • This function will never block: if the only way to get good randomness is a blocking,
+         *           synchronous getrandom() we'll instead provide you with pseudo-random data.
+         *
+         * This function is hence great for things like seeding hash tables, generating random numeric UNIX
+         * user IDs (that are checked for collisions before use) and such.
+         *
+         * This function is hence not useful for generating UUIDs or cryptographic key material.
+         */
+
+        if (genuine_random_bytes(p, n, RANDOM_EXTEND_WITH_PSEUDO|RANDOM_MAY_FAIL|RANDOM_ALLOW_RDRAND) >= 0)
                 return;
 
         /* If for some reason some user made /dev/urandom unavailable to us, or the kernel has no entropy, use a PRNG instead. */
diff --git a/shared/systemd/src/basic/random-util.h b/shared/systemd/src/basic/random-util.h
index 3e8c288d..148b6c78 100644
--- a/shared/systemd/src/basic/random-util.h
+++ b/shared/systemd/src/basic/random-util.h
@@ -8,11 +8,11 @@
 typedef enum RandomFlags {
         RANDOM_EXTEND_WITH_PSEUDO = 1 << 0, /* If we can't get enough genuine randomness, but some, fill up the rest with pseudo-randomness */
         RANDOM_BLOCK              = 1 << 1, /* Rather block than return crap randomness (only if the kernel supports that) */
-        RANDOM_DONT_DRAIN         = 1 << 2, /* If we can't get any randomness at all, return early with -EAGAIN */
+        RANDOM_MAY_FAIL           = 1 << 2, /* If we can't get any randomness at all, return early with -ENODATA */
         RANDOM_ALLOW_RDRAND       = 1 << 3, /* Allow usage of the CPU RNG */
 } RandomFlags;
 
-int genuine_random_bytes(void *p, size_t n, RandomFlags flags); /* returns "genuine" randomness, optionally filled upwith pseudo random, if not enough is available */
+int genuine_random_bytes(void *p, size_t n, RandomFlags flags); /* returns "genuine" randomness, optionally filled up with pseudo random, if not enough is available */
 void pseudo_random_bytes(void *p, size_t n);                    /* returns only pseudo-randommess (but possibly seeded from something better) */
 void random_bytes(void *p, size_t n);                           /* returns genuine randomness if cheaply available, and pseudo randomness if not. */
 
diff --git a/shared/systemd/src/basic/set.h b/shared/systemd/src/basic/set.h
index 2a80632b..2bb26c68 100644
--- a/shared/systemd/src/basic/set.h
+++ b/shared/systemd/src/basic/set.h
@@ -28,13 +28,13 @@ int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHM
 int set_put(Set *s, const void *key);
 /* no set_update */
 /* no set_replace */
-static inline void *set_get(Set *s, void *key) {
-        return internal_hashmap_get(HASHMAP_BASE(s), key);
+static inline void *set_get(const Set *s, void *key) {
+        return internal_hashmap_get(HASHMAP_BASE((Set *) s), key);
 }
 /* no set_get2 */
 
-static inline bool set_contains(Set *s, const void *key) {
-        return internal_hashmap_contains(HASHMAP_BASE(s), key);
+static inline bool set_contains(const Set *s, const void *key) {
+        return internal_hashmap_contains(HASHMAP_BASE((Set *) s), key);
 }
 
 static inline void *set_remove(Set *s, const void *key) {
@@ -59,19 +59,19 @@ static inline int set_move_one(Set *s, Set *other, const void *key) {
         return internal_hashmap_move_one(HASHMAP_BASE(s), HASHMAP_BASE(other), key);
 }
 
-static inline unsigned set_size(Set *s) {
-        return internal_hashmap_size(HASHMAP_BASE(s));
+static inline unsigned set_size(const Set *s) {
+        return internal_hashmap_size(HASHMAP_BASE((Set *) s));
 }
 
-static inline bool set_isempty(Set *s) {
+static inline bool set_isempty(const Set *s) {
         return set_size(s) == 0;
 }
 
-static inline unsigned set_buckets(Set *s) {
-        return internal_hashmap_buckets(HASHMAP_BASE(s));
+static inline unsigned set_buckets(const Set *s) {
+        return internal_hashmap_buckets(HASHMAP_BASE((Set *) s));
 }
 
-bool set_iterate(Set *s, Iterator *i, void **value);
+bool set_iterate(const Set *s, Iterator *i, void **value);
 
 static inline void set_clear(Set *s) {
         internal_hashmap_clear(HASHMAP_BASE(s), NULL, NULL);
diff --git a/shared/systemd/src/basic/siphash24.h b/shared/systemd/src/basic/siphash24.h
index be1d3e00..c4e919df 100644
--- a/shared/systemd/src/basic/siphash24.h
+++ b/shared/systemd/src/basic/siphash24.h
@@ -47,6 +47,7 @@ siphash24 (const void *in, size_t inlen, const uint8_t k[16])
 
 void siphash24_init(struct siphash *state, const uint8_t k[static 16]);
 void siphash24_compress(const void *in, size_t inlen, struct siphash *state);
+void siphash24_compress_boolean(bool in, struct siphash *state);
 #define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state))
 
 uint64_t siphash24_finalize(struct siphash *state);
diff --git a/shared/systemd/src/basic/socket-util.c b/shared/systemd/src/basic/socket-util.c
index b98b0461..b822ed03 100644
--- a/shared/systemd/src/basic/socket-util.c
+++ b/shared/systemd/src/basic/socket-util.c
@@ -81,7 +81,7 @@ int socket_address_parse(SocketAddress *a, const char *s) {
 
                 errno = 0;
                 if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0)
-                        return errno > 0 ? -errno : -EINVAL;
+                        return errno_or_else(EINVAL);
 
                 e++;
                 if (*e != ':')
@@ -1224,17 +1224,34 @@ fallback:
 }
 
 #if 0 /* NM_IGNORED */
+/* Put a limit on how many times will attempt to call accept4(). We loop
+ * only on "transient" errors, but let's make sure we don't loop forever. */
+#define MAX_FLUSH_ITERATIONS 1024
+
 int flush_accept(int fd) {
 
         struct pollfd pollfd = {
                 .fd = fd,
                 .events = POLLIN,
         };
-        int r;
+        int r, b;
+        socklen_t l = sizeof(b);
 
-        /* Similar to flush_fd() but flushes all incoming connection by accepting them and immediately closing them. */
+        /* Similar to flush_fd() but flushes all incoming connections by accepting and immediately closing
+         * them. */
 
-        for (;;) {
+        if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &b, &l) < 0)
+                return -errno;
+
+        assert(l == sizeof(b));
+        if (!b) /* Let's check if this socket accepts connections before calling accept(). accept4() can
+                 * return EOPNOTSUPP if the fd is not a listening socket, which we should treat as a fatal
+                 * error, or in case the incoming TCP connection triggered a network issue, which we want to
+                 * treat as a transient error. Thus, let's rule out the first reason for EOPNOTSUPP early, so
+                 * we can loop safely on transient errors below. */
+                return -ENOTTY;
+
+        for (unsigned iteration = 0;; iteration++) {
                 int cfd;
 
                 r = poll(&pollfd, 1, 0);
@@ -1247,6 +1264,10 @@ int flush_accept(int fd) {
                 if (r == 0)
                         return 0;
 
+                if (iteration >= MAX_FLUSH_ITERATIONS)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EBUSY),
+                                               "Failed to flush connections within " STRINGIFY(MAX_FLUSH_ITERATIONS) " iterations.");
+
                 cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
                 if (cfd < 0) {
                         if (errno == EAGAIN)
@@ -1374,7 +1395,7 @@ int socket_bind_to_ifname(int fd, const char *ifname) {
 }
 
 int socket_bind_to_ifindex(int fd, int ifindex) {
-        char ifname[IFNAMSIZ] = "";
+        char ifname[IF_NAMESIZE + 1];
 
         assert(fd >= 0);
 
@@ -1392,7 +1413,7 @@ int socket_bind_to_ifindex(int fd, int ifindex) {
                 return -errno;
 
         /* Fall back to SO_BINDTODEVICE on kernels < 5.0 which didn't have SO_BINDTOIFINDEX */
-        if (!if_indextoname(ifindex, ifname))
+        if (!format_ifname(ifindex, ifname))
                 return -errno;
 
         return socket_bind_to_ifname(fd, ifname);
diff --git a/shared/systemd/src/basic/socket-util.h b/shared/systemd/src/basic/socket-util.h
index 15443f1e..a0886e0e 100644
--- a/shared/systemd/src/basic/socket-util.h
+++ b/shared/systemd/src/basic/socket-util.h
@@ -3,12 +3,13 @@
 
 #include <inttypes.h>
 #include <linux/netlink.h>
+#include <linux/if_ether.h>
 #include <linux/if_infiniband.h>
 #include <linux/if_packet.h>
-#include <netinet/ether.h>
 #include <netinet/in.h>
 #include <stdbool.h>
 #include <stddef.h>
+#include <string.h>
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <sys/un.h>
diff --git a/shared/systemd/src/basic/string-util.c b/shared/systemd/src/basic/string-util.c
index df519976..2d34603e 100644
--- a/shared/systemd/src/basic/string-util.c
+++ b/shared/systemd/src/basic/string-util.c
@@ -6,7 +6,6 @@
 #include <stdarg.h>
 #include <stdint.h>
 #include <stdio.h>
-#include <stdio_ext.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -209,10 +208,6 @@ char *strnappend(const char *s, const char *suffix, size_t b) {
         return r;
 }
 
-char *strappend(const char *s, const char *suffix) {
-        return strnappend(s, suffix, strlen_ptr(suffix));
-}
-
 #if 0 /* NM_IGNORED */
 char *strjoin_real(const char *x, ...) {
         va_list ap;
@@ -736,10 +731,18 @@ char *strreplace(const char *text, const char *old_string, const char *new_strin
         return ret;
 }
 
-static void advance_offsets(ssize_t diff, size_t offsets[static 2], size_t shift[static 2], size_t size) {
+#if 0 /* NM_IGNORED */
+static void advance_offsets(
+                ssize_t diff,
+                size_t offsets[2], /* note: we can't use [static 2] here, since this may be NULL */
+                size_t shift[static 2],
+                size_t size) {
+
         if (!offsets)
                 return;
 
+        assert(shift);
+
         if ((size_t) diff < offsets[0])
                 shift[0] += size;
         if ((size_t) diff < offsets[1])
@@ -767,23 +770,20 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
          * 2. Strips ANSI color sequences (a subset of CSI), i.e. ESC '[' … 'm' sequences
          * 3. Strips ANSI operating system sequences (CSO), i.e. ESC ']' … BEL sequences
          *
-         * Everything else will be left as it is. In particular other ANSI sequences are left as they are, as are any
-         * other special characters. Truncated ANSI sequences are left-as is too. This call is supposed to suppress the
-         * most basic formatting noise, but nothing else.
+         * Everything else will be left as it is. In particular other ANSI sequences are left as they are, as
+         * are any other special characters. Truncated ANSI sequences are left-as is too. This call is
+         * supposed to suppress the most basic formatting noise, but nothing else.
          *
          * Why care for CSO sequences? Well, to undo what terminal_urlify() and friends generate. */
 
         isz = _isz ? *_isz : strlen(*ibuf);
 
-        f = open_memstream(&obuf, &osz);
+        /* 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);
         if (!f)
                 return NULL;
 
-        /* Note we turn off internal locking on f for performance reasons.  It's safe to do so since we created f here
-         * and it doesn't leave our scope. */
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
         for (i = *ibuf; i < *ibuf + isz + 1; i++) {
 
                 switch (state) {
@@ -858,8 +858,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
 
         fclose(f);
 
-        free(*ibuf);
-        *ibuf = obuf;
+        free_and_replace(*ibuf, obuf);
 
         if (_isz)
                 *_isz = osz;
@@ -869,10 +868,9 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
                 highlight[1] += shift[1];
         }
 
-        return obuf;
+        return *ibuf;
 }
 
-#if 0 /* NM_IGNORED */
 char *strextend_with_separator(char **x, const char *separator, ...) {
         bool need_separator;
         size_t f, l, l_separator;
@@ -1044,20 +1042,6 @@ int free_and_strndup(char **p, const char *s, size_t l) {
         return 1;
 }
 
-char* string_erase(char *x) {
-        if (!x)
-                return NULL;
-
-        /* A delicious drop of snake-oil! To be called on memory where
-         * we stored passphrases or so, after we used them. */
-        explicit_bzero_safe(x, strlen(x));
-        return x;
-}
-
-char *string_free_erase(char *s) {
-        return mfree(string_erase(s));
-}
-
 bool string_is_safe(const char *p) {
         const char *t;
 
diff --git a/shared/systemd/src/basic/string-util.h b/shared/systemd/src/basic/string-util.h
index b23f4c83..76767afc 100644
--- a/shared/systemd/src/basic/string-util.h
+++ b/shared/systemd/src/basic/string-util.h
@@ -108,7 +108,6 @@ const char* split(const char **state, size_t *l, const char *separator, SplitFla
 #define _FOREACH_WORD(word, length, s, separator, flags, state)         \
         for ((state) = (s), (word) = split(&(state), &(length), (separator), (flags)); (word); (word) = split(&(state), &(length), (separator), (flags)))
 
-char *strappend(const char *s, const char *suffix);
 char *strnappend(const char *s, const char *suffix, size_t length);
 
 char *strjoin_real(const char *x, ...) _sentinel_;
@@ -197,12 +196,6 @@ static inline int free_and_strdup_warn(char **p, const char *s) {
 }
 int free_and_strndup(char **p, const char *s, size_t l);
 
-char *string_erase(char *x);
-
-char *string_free_erase(char *s);
-DEFINE_TRIVIAL_CLEANUP_FUNC(char *, string_free_erase);
-#define _cleanup_string_free_erase_ _cleanup_(string_free_erasep)
-
 bool string_is_safe(const char *p) _pure_;
 
 static inline size_t strlen_ptr(const char *s) {
@@ -212,6 +205,12 @@ static inline size_t strlen_ptr(const char *s) {
         return strlen(s);
 }
 
+DISABLE_WARNING_STRINGOP_TRUNCATION;
+static inline void strncpy_exact(char *buf, const char *src, size_t buf_len) {
+        strncpy(buf, src, buf_len);
+}
+REENABLE_WARNING;
+
 /* Like startswith(), but operates on arbitrary memory blocks */
 static inline void *memory_startswith(const void *p, size_t sz, const char *token) {
         size_t n;
@@ -251,3 +250,16 @@ static inline void *memory_startswith_no_case(const void *p, size_t sz, const ch
 
         return (uint8_t*) p + n;
 }
+
+static inline char* str_realloc(char **p) {
+        /* Reallocate *p to actual size */
+
+        if (!*p)
+                return NULL;
+
+        char *t = realloc(*p, strlen(*p) + 1);
+        if (!t)
+                return NULL;
+
+        return (*p = t);
+}
diff --git a/shared/systemd/src/basic/strv.c b/shared/systemd/src/basic/strv.c
index 8de40b9d..ba23178a 100644
--- a/shared/systemd/src/basic/strv.c
+++ b/shared/systemd/src/basic/strv.c
@@ -13,6 +13,7 @@
 #include "escape.h"
 #include "extract-word.h"
 #include "fileio.h"
+#include "memory-util.h"
 #include "nulstr-util.h"
 #include "sort-util.h"
 #include "string-util.h"
@@ -80,9 +81,9 @@ char **strv_free_erase(char **l) {
         char **i;
 
         STRV_FOREACH(i, l)
-                string_erase(*i);
+                erase_and_freep(i);
 
-        return strv_free(l);
+        return mfree(l);
 }
 
 char **strv_copy(char * const *l) {
@@ -227,6 +228,7 @@ rollback:
         return -ENOMEM;
 }
 
+#if 0 /* NM_IGNORED */
 int strv_extend_strv_concat(char ***a, char **b, const char *suffix) {
         int r;
         char **s;
@@ -234,7 +236,7 @@ int strv_extend_strv_concat(char ***a, char **b, const char *suffix) {
         STRV_FOREACH(s, b) {
                 char *v;
 
-                v = strappend(*s, suffix);
+                v = strjoin(*s, suffix);
                 if (!v)
                         return -ENOMEM;
 
@@ -247,6 +249,7 @@ int strv_extend_strv_concat(char ***a, char **b, const char *suffix) {
 
         return 0;
 }
+#endif /* NM_IGNORED */
 
 char **strv_split_full(const char *s, const char *separator, SplitFlags flags) {
         const char *word, *state;
@@ -896,3 +899,63 @@ int fputstrv(FILE *f, char **l, const char *separator, bool *space) {
 
         return 0;
 }
+
+static int string_strv_hashmap_put_internal(Hashmap *h, const char *key, const char *value) {
+        char **l;
+        int r;
+
+        l = hashmap_get(h, key);
+        if (l) {
+                /* A list for this key already exists, let's append to it if it is not listed yet */
+                if (strv_contains(l, value))
+                        return 0;
+
+                r = strv_extend(&l, value);
+                if (r < 0)
+                        return r;
+
+                assert_se(hashmap_update(h, key, l) >= 0);
+        } else {
+                /* No list for this key exists yet, create one */
+                _cleanup_strv_free_ char **l2 = NULL;
+                _cleanup_free_ char *t = NULL;
+
+                t = strdup(key);
+                if (!t)
+                        return -ENOMEM;
+
+                r = strv_extend(&l2, value);
+                if (r < 0)
+                        return r;
+
+                r = hashmap_put(h, t, l2);
+                if (r < 0)
+                        return r;
+                TAKE_PTR(t);
+                TAKE_PTR(l2);
+        }
+
+        return 1;
+}
+
+int string_strv_hashmap_put(Hashmap **h, const char *key, const char *value) {
+        int r;
+
+        r = hashmap_ensure_allocated(h, &string_strv_hash_ops);
+        if (r < 0)
+                return r;
+
+        return string_strv_hashmap_put_internal(*h, key, value);
+}
+
+int string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value) {
+        int r;
+
+        r = ordered_hashmap_ensure_allocated(h, &string_strv_hash_ops);
+        if (r < 0)
+                return r;
+
+        return string_strv_hashmap_put_internal(PLAIN_HASHMAP(*h), key, value);
+}
+
+DEFINE_HASH_OPS_FULL(string_strv_hash_ops, char, string_hash_func, string_compare_func, free, char*, strv_free);
diff --git a/shared/systemd/src/basic/strv.h b/shared/systemd/src/basic/strv.h
index aa5f95ab..e80964ac 100644
--- a/shared/systemd/src/basic/strv.h
+++ b/shared/systemd/src/basic/strv.h
@@ -9,6 +9,7 @@
 
 #include "alloc-util.h"
 #include "extract-word.h"
+#include "hashmap.h"
 #include "macro.h"
 #include "string-util.h"
 
@@ -188,3 +189,7 @@ int fputstrv(FILE *f, char **l, const char *separator, bool *space);
                 (b) = NULL;                     \
                 0;                              \
         })
+
+extern const struct hash_ops string_strv_hash_ops;
+int string_strv_hashmap_put(Hashmap **h, const char *key, const char *value);
+int string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value);
diff --git a/shared/systemd/src/basic/strxcpyx.c b/shared/systemd/src/basic/strxcpyx.c
new file mode 100644
index 00000000..301e6899
--- /dev/null
+++ b/shared/systemd/src/basic/strxcpyx.c
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+/*
+ * Concatenates/copies strings. In any case, terminates in all cases
+ * with '\0' and moves the @dest pointer forward to the added '\0'.
+ * Returns the remaining size, and 0 if the string was truncated.
+ *
+ * Due to the intended usage, these helpers silently noop invocations
+ * having zero size.  This is technically an exception to the above
+ * statement "terminates in all cases".  It's unexpected for such calls to
+ * occur outside of a loop where this is the preferred behavior.
+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "strxcpyx.h"
+
+size_t strnpcpy(char **dest, size_t size, const char *src, size_t len) {
+        assert(dest);
+        assert(src);
+
+        if (size == 0)
+                return 0;
+
+        if (len >= size) {
+                if (size > 1)
+                        *dest = mempcpy(*dest, src, size-1);
+                size = 0;
+        } else if (len > 0) {
+                *dest = mempcpy(*dest, src, len);
+                size -= len;
+        }
+
+        *dest[0] = '\0';
+        return size;
+}
+
+size_t strpcpy(char **dest, size_t size, const char *src) {
+        assert(dest);
+        assert(src);
+
+        return strnpcpy(dest, size, src, strlen(src));
+}
+
+size_t strpcpyf(char **dest, size_t size, const char *src, ...) {
+        va_list va;
+        int i;
+
+        assert(dest);
+        assert(src);
+
+        if (size == 0)
+                return 0;
+
+        va_start(va, src);
+        i = vsnprintf(*dest, size, src, va);
+        if (i < (int)size) {
+                *dest += i;
+                size -= i;
+        } else
+                size = 0;
+        va_end(va);
+        return size;
+}
+
+size_t strpcpyl(char **dest, size_t size, const char *src, ...) {
+        va_list va;
+
+        assert(dest);
+        assert(src);
+
+        va_start(va, src);
+        do {
+                size = strpcpy(dest, size, src);
+                src = va_arg(va, char *);
+        } while (src);
+        va_end(va);
+        return size;
+}
+
+size_t strnscpy(char *dest, size_t size, const char *src, size_t len) {
+        char *s;
+
+        assert(dest);
+        assert(src);
+
+        s = dest;
+        return strnpcpy(&s, size, src, len);
+}
+
+size_t strscpy(char *dest, size_t size, const char *src) {
+        assert(dest);
+        assert(src);
+
+        return strnscpy(dest, size, src, strlen(src));
+}
+
+size_t strscpyl(char *dest, size_t size, const char *src, ...) {
+        va_list va;
+        char *s;
+
+        assert(dest);
+        assert(src);
+
+        va_start(va, src);
+        s = dest;
+        do {
+                size = strpcpy(&s, size, src);
+                src = va_arg(va, char *);
+        } while (src);
+        va_end(va);
+
+        return size;
+}
diff --git a/shared/systemd/src/basic/strxcpyx.h b/shared/systemd/src/basic/strxcpyx.h
new file mode 100644
index 00000000..9b668412
--- /dev/null
+++ b/shared/systemd/src/basic/strxcpyx.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stddef.h>
+
+#include "macro.h"
+
+size_t strnpcpy(char **dest, size_t size, const char *src, size_t len);
+size_t strpcpy(char **dest, size_t size, const char *src);
+size_t strpcpyf(char **dest, size_t size, const char *src, ...) _printf_(3, 4);
+size_t strpcpyl(char **dest, size_t size, const char *src, ...) _sentinel_;
+size_t strnscpy(char *dest, size_t size, const char *src, size_t len);
+size_t strscpy(char *dest, size_t size, const char *src);
+size_t strscpyl(char *dest, size_t size, const char *src, ...) _sentinel_;
diff --git a/shared/systemd/src/basic/time-util.c b/shared/systemd/src/basic/time-util.c
index 14b17bfc..aa790023 100644
--- a/shared/systemd/src/basic/time-util.c
+++ b/shared/systemd/src/basic/time-util.c
@@ -270,13 +270,12 @@ static char *format_timestamp_internal(
 
         assert(buf);
 
-        if (l <
-            3 +                  /* week day */
-            1 + 10 +             /* space and date */
-            1 + 8 +              /* space and time */
-            (us ? 1 + 6 : 0) +   /* "." and microsecond part */
-            1 + 1 +              /* space and shortest possible zone */
-            1)
+        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 (t <= 0 || t == USEC_INFINITY)
                 return NULL; /* Timestamp is unset */
@@ -580,7 +579,6 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
          */
 
         assert(t);
-        assert(usec);
 
         if (t[0] == '@' && !with_tz)
                 return parse_sec(t + 1, usec);
@@ -808,8 +806,8 @@ finish:
         else
                 return -EINVAL;
 
-        *usec = ret;
-
+        if (usec)
+                *usec = ret;
         return 0;
 }
 
@@ -866,7 +864,7 @@ int parse_timestamp(const char *t, usec_t *usec) {
         if (munmap(shared, sizeof *shared) != 0)
                 return negative_errno();
 
-        if (tmp.return_value == 0)
+        if (tmp.return_value == 0 && usec)
                 *usec = tmp.usec;
 
         return tmp.return_value;
@@ -928,7 +926,6 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
         bool something = false;
 
         assert(t);
-        assert(usec);
         assert(default_unit > 0);
 
         p = t;
@@ -940,7 +937,8 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
                 if (*s != 0)
                         return -EINVAL;
 
-                *usec = USEC_INFINITY;
+                if (usec)
+                        *usec = USEC_INFINITY;
                 return 0;
         }
 
@@ -1012,8 +1010,8 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
                 }
         }
 
-        *usec = r;
-
+        if (usec)
+                *usec = r;
         return 0;
 }
 
diff --git a/shared/systemd/src/basic/time-util.h b/shared/systemd/src/basic/time-util.h
index a238f691..e3a529d9 100644
--- a/shared/systemd/src/basic/time-util.h
+++ b/shared/systemd/src/basic/time-util.h
@@ -81,15 +81,19 @@ triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u)
 #define TRIPLE_TIMESTAMP_HAS_CLOCK(clock)                               \
         IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM)
 
+static inline bool timestamp_is_set(usec_t timestamp) {
+        return timestamp > 0 && timestamp != USEC_INFINITY;
+}
+
 static inline bool dual_timestamp_is_set(const dual_timestamp *ts) {
-        return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) ||
-                (ts->monotonic > 0 && ts->monotonic != USEC_INFINITY));
+        return timestamp_is_set(ts->realtime) ||
+               timestamp_is_set(ts->monotonic);
 }
 
 static inline bool triple_timestamp_is_set(const triple_timestamp *ts) {
-        return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) ||
-                (ts->monotonic > 0 && ts->monotonic != USEC_INFINITY) ||
-                (ts->boottime > 0 && ts->boottime != USEC_INFINITY));
+        return timestamp_is_set(ts->realtime) ||
+               timestamp_is_set(ts->monotonic) ||
+               timestamp_is_set(ts->boottime);
 }
 
 usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock);
diff --git a/shared/systemd/src/basic/tmpfile-util.c b/shared/systemd/src/basic/tmpfile-util.c
index 019121cb..c02ce3df 100644
--- a/shared/systemd/src/basic/tmpfile-util.c
+++ b/shared/systemd/src/basic/tmpfile-util.c
@@ -2,10 +2,12 @@
 
 #include "nm-sd-adapt-shared.h"
 
+#include <stdio.h>
 #include <sys/mman.h>
 
 #include "alloc-util.h"
 #include "fd-util.h"
+#include "fileio.h"
 #include "fs-util.h"
 #include "hexdecoct.h"
 #include "macro.h"
@@ -39,12 +41,15 @@ int fopen_temporary(const char *path, FILE **_f, char **_temp_path) {
                 return -errno;
         }
 
-        f = fdopen(fd, "w");
-        if (!f) {
-                unlink_noerrno(t);
+        /* This assumes that returned FILE object is short-lived and used within the same single-threaded
+         * context and never shared externally, hence locking is not necessary. */
+
+        r = fdopen_unlocked(fd, "w", &f);
+        if (r < 0) {
+                unlink(t);
                 free(t);
                 safe_close(fd);
-                return -errno;
+                return r;
         }
 
         *_f = f;
@@ -55,13 +60,11 @@ int fopen_temporary(const char *path, FILE **_f, char **_temp_path) {
 
 /* This is much like mkostemp() but is subject to umask(). */
 int mkostemp_safe(char *pattern) {
-        _cleanup_umask_ mode_t u = 0;
+        _unused_ _cleanup_umask_ mode_t u = umask(0077);
         int fd;
 
         assert(pattern);
 
-        u = umask(077);
-
         fd = mkostemp(pattern, O_CLOEXEC);
         if (fd < 0)
                 return -errno;
@@ -322,7 +325,7 @@ int mkdtemp_malloc(const char *template, char **ret) {
                 if (r < 0)
                         return r;
 
-                p = strjoin(tmp, "/XXXXXX");
+                p = path_join(tmp, "XXXXXX");
         }
         if (!p)
                 return -ENOMEM;
diff --git a/shared/systemd/src/basic/umask-util.h b/shared/systemd/src/basic/umask-util.h
index e964292e..cad74517 100644
--- a/shared/systemd/src/basic/umask-util.h
+++ b/shared/systemd/src/basic/umask-util.h
@@ -8,21 +8,19 @@
 #include "macro.h"
 
 static inline void umaskp(mode_t *u) {
-        umask(*u);
+        umask(*u & 0777);
 }
 
 #define _cleanup_umask_ _cleanup_(umaskp)
 
-struct _umask_struct_ {
-        mode_t mask;
-        bool quit;
-};
+/* 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. */
 
-static inline void _reset_umask_(struct _umask_struct_ *s) {
-        umask(s->mask);
-};
+assert_cc((S_IFMT & 0777) == 0);
 
 #define RUN_WITH_UMASK(mask)                                            \
-        for (_cleanup_(_reset_umask_) struct _umask_struct_ _saved_umask_ = { umask(mask), false }; \
-             !_saved_umask_.quit ;                                      \
-             _saved_umask_.quit = true)
+        for (_cleanup_umask_ mode_t _saved_umask_ = umask(mask) | S_IFMT; \
+             FLAGS_SET(_saved_umask_, S_IFMT);                          \
+             _saved_umask_ &= 0777)
diff --git a/shared/systemd/src/basic/utf8.c b/shared/systemd/src/basic/utf8.c
index f1c6ac1f..3c51fa1f 100644
--- a/shared/systemd/src/basic/utf8.c
+++ b/shared/systemd/src/basic/utf8.c
@@ -34,6 +34,7 @@
 #include "gunicode.h"
 #include "hexdecoct.h"
 #include "macro.h"
+#include "string-util.h"
 #include "utf8.h"
 
 bool unichar_is_valid(char32_t ch) {
@@ -198,47 +199,94 @@ char *utf8_escape_invalid(const char *str) {
         }
 
         *s = '\0';
-
+        (void) str_realloc(&p);
         return p;
 }
 
 #if 0 /* NM_IGNORED */
-char *utf8_escape_non_printable(const char *str) {
-        char *p, *s;
+static int utf8_char_console_width(const char *str) {
+        char32_t c;
+        int r;
+
+        r = utf8_encoded_to_unichar(str, &c);
+        if (r < 0)
+                return r;
+
+        /* TODO: we should detect combining characters */
+
+        return unichar_iswide(c) ? 2 : 1;
+}
+
+char *utf8_escape_non_printable_full(const char *str, size_t console_width) {
+        char *p, *s, *prev_s;
+        size_t n = 0; /* estimated print width */
 
         assert(str);
 
-        p = s = malloc(strlen(str) * 4 + 1);
+        if (console_width == 0)
+                return strdup("");
+
+        p = s = prev_s = malloc(strlen(str) * 4 + 1);
         if (!p)
                 return NULL;
 
-        while (*str) {
+        for (;;) {
                 int len;
+                char *saved_s = s;
+
+                if (!*str) /* done! */
+                        goto finish;
 
                 len = utf8_encoded_valid_unichar(str, (size_t) -1);
                 if (len > 0) {
                         if (utf8_is_printable(str, len)) {
+                                int w;
+
+                                w = utf8_char_console_width(str);
+                                assert(w >= 0);
+                                if (n + w > console_width)
+                                        goto truncation;
+
                                 s = mempcpy(s, str, len);
                                 str += len;
+                                n += w;
+
                         } else {
-                                while (len > 0) {
+                                for (; len > 0; len--) {
+                                        if (n + 4 > console_width)
+                                                goto truncation;
+
                                         *(s++) = '\\';
                                         *(s++) = 'x';
                                         *(s++) = hexchar((int) *str >> 4);
                                         *(s++) = hexchar((int) *str);
 
                                         str += 1;
-                                        len--;
+                                        n += 4;
                                 }
                         }
                 } else {
-                        s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER);
+                        if (n + 1 > console_width)
+                                goto truncation;
+
+                        s = mempcpy(s, UTF8_REPLACEMENT_CHARACTER, strlen(UTF8_REPLACEMENT_CHARACTER));
                         str += 1;
+                        n += 1;
                 }
+
+                prev_s = saved_s;
         }
 
-        *s = '\0';
+ truncation:
+        /* Try to go back one if we don't have enough space for the ellipsis */
+        if (n + 1 >= console_width)
+                s = prev_s;
+
+        s = mempcpy(s, "…", strlen("…"));
 
+ finish:
+        *s = '\0';
+        (void) str_realloc(&p);
         return p;
 }
 #endif /* NM_IGNORED */
@@ -532,15 +580,15 @@ size_t utf8_console_width(const char *str) {
         /* Returns the approximate width a string will take on screen when printed on a character cell
          * terminal/console. */
 
-        while (*str != 0) {
-                char32_t c;
+        while (*str) {
+                int w;
 
-                if (utf8_encoded_to_unichar(str, &c) < 0)
+                w = utf8_char_console_width(str);
+                if (w < 0)
                         return (size_t) -1;
 
+                n += w;
                 str = utf8_next_char(str);
-
-                n += unichar_iswide(c) ? 2 : 1;
         }
 
         return n;
diff --git a/shared/systemd/src/basic/utf8.h b/shared/systemd/src/basic/utf8.h
index 6df70921..62e99b72 100644
--- a/shared/systemd/src/basic/utf8.h
+++ b/shared/systemd/src/basic/utf8.h
@@ -22,7 +22,10 @@ bool utf8_is_printable_newline(const char* str, size_t length, bool newline) _pu
 #define utf8_is_printable(str, length) utf8_is_printable_newline(str, length, true)
 
 char *utf8_escape_invalid(const char *s);
-char *utf8_escape_non_printable(const char *str);
+char *utf8_escape_non_printable_full(const char *str, size_t console_width);
+static inline char *utf8_escape_non_printable(const char *str) {
+        return utf8_escape_non_printable_full(str, (size_t) -1);
+}
 
 size_t utf8_encode_unichar(char *out_utf8, char32_t g);
 size_t utf16_encode_unichar(char16_t *out, char32_t c);
diff --git a/shared/systemd/src/shared/dns-domain.c b/shared/systemd/src/shared/dns-domain.c
new file mode 100644
index 00000000..92543ebe
--- /dev/null
+++ b/shared/systemd/src/shared/dns-domain.c
@@ -0,0 +1,1387 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#if 0 /* NM_IGNORED */
+#if HAVE_LIBIDN2
+#  include <idn2.h>
+#elif HAVE_LIBIDN
+#  include <idna.h>
+#  include <stringprep.h>
+#endif
+#endif
+
+#include <endian.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include "alloc-util.h"
+#include "dns-domain.h"
+#include "hashmap.h"
+#include "hexdecoct.h"
+#include "hostname-util.h"
+#include "in-addr-util.h"
+#include "macro.h"
+#include "parse-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "utf8.h"
+
+int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags flags) {
+        const char *n;
+        char *d, last_char = 0;
+        int r = 0;
+
+        assert(name);
+        assert(*name);
+
+        n = *name;
+        d = dest;
+
+        for (;;) {
+                if (IN_SET(*n, 0, '.')) {
+                        if (FLAGS_SET(flags, DNS_LABEL_LDH) && last_char == '-')
+                                /* Trailing dash */
+                                return -EINVAL;
+
+                        if (*n == '.')
+                                n++;
+                        break;
+                }
+
+                if (r >= DNS_LABEL_MAX)
+                        return -EINVAL;
+
+                if (sz <= 0)
+                        return -ENOBUFS;
+
+                if (*n == '\\') {
+                        /* Escaped character */
+                        if (FLAGS_SET(flags, DNS_LABEL_NO_ESCAPES))
+                                return -EINVAL;
+
+                        n++;
+
+                        if (*n == 0)
+                                /* Ending NUL */
+                                return -EINVAL;
+
+                        else if (IN_SET(*n, '\\', '.')) {
+                                /* Escaped backslash or dot */
+
+                                if (FLAGS_SET(flags, DNS_LABEL_LDH))
+                                        return -EINVAL;
+
+                                last_char = *n;
+                                if (d)
+                                        *(d++) = *n;
+                                sz--;
+                                r++;
+                                n++;
+
+                        } else if (n[0] >= '0' && n[0] <= '9') {
+                                unsigned k;
+
+                                /* Escaped literal ASCII character */
+
+                                if (!(n[1] >= '0' && n[1] <= '9') ||
+                                    !(n[2] >= '0' && n[2] <= '9'))
+                                        return -EINVAL;
+
+                                k = ((unsigned) (n[0] - '0') * 100) +
+                                        ((unsigned) (n[1] - '0') * 10) +
+                                        ((unsigned) (n[2] - '0'));
+
+                                /* Don't allow anything that doesn't
+                                 * fit in 8bit. Note that we do allow
+                                 * control characters, as some servers
+                                 * (e.g. cloudflare) are happy to
+                                 * generate labels with them
+                                 * inside. */
+                                if (k > 255)
+                                        return -EINVAL;
+
+                                if (FLAGS_SET(flags, DNS_LABEL_LDH) &&
+                                    !valid_ldh_char((char) k))
+                                        return -EINVAL;
+
+                                last_char = (char) k;
+                                if (d)
+                                        *(d++) = (char) k;
+                                sz--;
+                                r++;
+
+                                n += 3;
+                        } else
+                                return -EINVAL;
+
+                } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) {
+
+                        /* Normal character */
+
+                        if (FLAGS_SET(flags, DNS_LABEL_LDH)) {
+                                if (!valid_ldh_char(*n))
+                                        return -EINVAL;
+                                if (r == 0 && *n == '-')
+                                        /* Leading dash */
+                                        return -EINVAL;
+                        }
+
+                        last_char = *n;
+                        if (d)
+                                *(d++) = *n;
+                        sz--;
+                        r++;
+                        n++;
+                } else
+                        return -EINVAL;
+        }
+
+        /* Empty label that is not at the end? */
+        if (r == 0 && *n)
+                return -EINVAL;
+
+        /* More than one trailing dot? */
+        if (*n == '.')
+                return -EINVAL;
+
+        if (sz >= 1 && d)
+                *d = 0;
+
+        *name = n;
+        return r;
+}
+
+#if 0 /* NM_IGNORED */
+/* @label_terminal: terminal character of a label, updated to point to the terminal character of
+ *                  the previous label (always skipping one dot) or to NULL if there are no more
+ *                  labels. */
+int dns_label_unescape_suffix(const char *name, const char **label_terminal, char *dest, size_t sz) {
+        const char *terminal;
+        int r;
+
+        assert(name);
+        assert(label_terminal);
+        assert(dest);
+
+        /* no more labels */
+        if (!*label_terminal) {
+                if (sz >= 1)
+                        *dest = 0;
+
+                return 0;
+        }
+
+        terminal = *label_terminal;
+        assert(IN_SET(*terminal, 0, '.'));
+
+        /* Skip current terminal character (and accept domain names ending it ".") */
+        if (*terminal == 0)
+                terminal--;
+        if (terminal >= name && *terminal == '.')
+                terminal--;
+
+        /* Point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */
+        for (;;) {
+                if (terminal < name) {
+                        /* Reached the first label, so indicate that there are no more */
+                        terminal = NULL;
+                        break;
+                }
+
+                /* Find the start of the last label */
+                if (*terminal == '.') {
+                        const char *y;
+                        unsigned slashes = 0;
+
+                        for (y = terminal - 1; y >= name && *y == '\\'; y--)
+                                slashes++;
+
+                        if (slashes % 2 == 0) {
+                                /* The '.' was not escaped */
+                                name = terminal + 1;
+                                break;
+                        } else {
+                                terminal = y;
+                                continue;
+                        }
+                }
+
+                terminal--;
+        }
+
+        r = dns_label_unescape(&name, dest, sz, 0);
+        if (r < 0)
+                return r;
+
+        *label_terminal = terminal;
+
+        return r;
+}
+#endif /* NM_IGNORED */
+
+int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
+        char *q;
+
+        /* DNS labels must be between 1 and 63 characters long. A
+         * zero-length label does not exist. See RFC 2182, Section
+         * 11. */
+
+        if (l <= 0 || l > DNS_LABEL_MAX)
+                return -EINVAL;
+        if (sz < 1)
+                return -ENOBUFS;
+
+        assert(p);
+        assert(dest);
+
+        q = dest;
+        while (l > 0) {
+
+                if (IN_SET(*p, '.', '\\')) {
+
+                        /* Dot or backslash */
+
+                        if (sz < 3)
+                                return -ENOBUFS;
+
+                        *(q++) = '\\';
+                        *(q++) = *p;
+
+                        sz -= 2;
+
+                } else if (IN_SET(*p, '_', '-') ||
+                           (*p >= '0' && *p <= '9') ||
+                           (*p >= 'a' && *p <= 'z') ||
+                           (*p >= 'A' && *p <= 'Z')) {
+
+                        /* Proper character */
+
+                        if (sz < 2)
+                                return -ENOBUFS;
+
+                        *(q++) = *p;
+                        sz -= 1;
+
+                } else {
+
+                        /* Everything else */
+
+                        if (sz < 5)
+                                return -ENOBUFS;
+
+                        *(q++) = '\\';
+                        *(q++) = '0' + (char) ((uint8_t) *p / 100);
+                        *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10);
+                        *(q++) = '0' + (char) ((uint8_t) *p % 10);
+
+                        sz -= 4;
+                }
+
+                p++;
+                l--;
+        }
+
+        *q = 0;
+        return (int) (q - dest);
+}
+
+#if 0 /* NM_IGNORED */
+int dns_label_escape_new(const char *p, size_t l, char **ret) {
+        _cleanup_free_ char *s = NULL;
+        int r;
+
+        assert(p);
+        assert(ret);
+
+        if (l <= 0 || l > DNS_LABEL_MAX)
+                return -EINVAL;
+
+        s = new(char, DNS_LABEL_ESCAPED_MAX);
+        if (!s)
+                return -ENOMEM;
+
+        r = dns_label_escape(p, l, s, DNS_LABEL_ESCAPED_MAX);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(s);
+
+        return r;
+}
+
+#if HAVE_LIBIDN
+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];
+
+        assert(encoded);
+        assert(decoded);
+
+        /* Converts an U-label into an A-label */
+
+        if (encoded_size <= 0)
+                return -EINVAL;
+
+        for (p = encoded; p < encoded + encoded_size; p++)
+                if ((uint8_t) *p > 127)
+                        contains_8bit = true;
+
+        if (!contains_8bit) {
+                if (encoded_size > DNS_LABEL_MAX)
+                        return -EINVAL;
+
+                return 0;
+        }
+
+        input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
+        if (!input)
+                return -ENOMEM;
+
+        if (idna_to_ascii_4i(input, input_size, buffer, 0) != 0)
+                return -EINVAL;
+
+        l = strlen(buffer);
+
+        /* Verify that the result is not longer than one DNS label. */
+        if (l <= 0 || l > DNS_LABEL_MAX)
+                return -EINVAL;
+        if (l > decoded_max)
+                return -ENOBUFS;
+
+        memcpy(decoded, buffer, l);
+
+        /* If there's room, append a trailing NUL byte, but only then */
+        if (decoded_max > l)
+                decoded[l] = 0;
+
+        return (int) l;
+}
+
+int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
+        size_t input_size, output_size;
+        _cleanup_free_ uint32_t *input = NULL;
+        _cleanup_free_ char *result = NULL;
+        uint32_t *output = NULL;
+        size_t w;
+
+        /* To be invoked after unescaping. Converts an A-label into an U-label. */
+
+        assert(encoded);
+        assert(decoded);
+
+        if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX)
+                return -EINVAL;
+
+        if (!memory_startswith(encoded, encoded_size, IDNA_ACE_PREFIX))
+                return 0;
+
+        input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
+        if (!input)
+                return -ENOMEM;
+
+        output_size = input_size;
+        output = newa(uint32_t, output_size);
+
+        idna_to_unicode_44i(input, input_size, output, &output_size, 0);
+
+        result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w);
+        if (!result)
+                return -ENOMEM;
+        if (w <= 0)
+                return -EINVAL;
+        if (w > decoded_max)
+                return -ENOBUFS;
+
+        memcpy(decoded, result, w);
+
+        /* Append trailing NUL byte if there's space, but only then. */
+        if (decoded_max > w)
+                decoded[w] = 0;
+
+        return w;
+}
+#endif
+#endif /* NM_IGNORED */
+
+int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **_ret) {
+        _cleanup_free_ char *ret = NULL;
+        size_t n = 0, allocated = 0;
+        const char *p;
+        bool first = true;
+        int r;
+
+        if (a)
+                p = a;
+        else if (b)
+                p = TAKE_PTR(b);
+        else
+                goto finish;
+
+        for (;;) {
+                char label[DNS_LABEL_MAX];
+
+                r = dns_label_unescape(&p, label, sizeof label, flags);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        if (*p != 0)
+                                return -EINVAL;
+
+                        if (b) {
+                                /* Now continue with the second string, if there is one */
+                                p = TAKE_PTR(b);
+                                continue;
+                        }
+
+                        break;
+                }
+
+                if (_ret) {
+                        if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+                                return -ENOMEM;
+
+                        r = dns_label_escape(label, r, ret + n + !first, DNS_LABEL_ESCAPED_MAX);
+                        if (r < 0)
+                                return r;
+
+                        if (!first)
+                                ret[n] = '.';
+                } else {
+                        char escaped[DNS_LABEL_ESCAPED_MAX];
+
+                        r = dns_label_escape(label, r, escaped, sizeof(escaped));
+                        if (r < 0)
+                                return r;
+                }
+
+                if (!first)
+                        n++;
+                else
+                        first = false;
+
+                n += r;
+        }
+
+finish:
+        if (n > DNS_HOSTNAME_MAX)
+                return -EINVAL;
+
+        if (_ret) {
+                if (n == 0) {
+                        /* Nothing appended? If so, generate at least a single dot, to indicate the DNS root domain */
+                        if (!GREEDY_REALLOC(ret, allocated, 2))
+                                return -ENOMEM;
+
+                        ret[n++] = '.';
+                } else {
+                        if (!GREEDY_REALLOC(ret, allocated, n + 1))
+                                return -ENOMEM;
+                }
+
+                ret[n] = 0;
+                *_ret = TAKE_PTR(ret);
+        }
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+void dns_name_hash_func(const char *p, struct siphash *state) {
+        int r;
+
+        assert(p);
+
+        for (;;) {
+                char label[DNS_LABEL_MAX+1];
+
+                r = dns_label_unescape(&p, label, sizeof label, 0);
+                if (r < 0)
+                        break;
+                if (r == 0)
+                        break;
+
+                ascii_strlower_n(label, r);
+                siphash24_compress(label, r, state);
+                siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */
+        }
+
+        /* enforce that all names are terminated by the empty label */
+        string_hash_func("", state);
+}
+
+int dns_name_compare_func(const char *a, const char *b) {
+        const char *x, *y;
+        int r, q;
+
+        assert(a);
+        assert(b);
+
+        x = a + strlen(a);
+        y = b + strlen(b);
+
+        for (;;) {
+                char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
+
+                if (x == NULL && y == NULL)
+                        return 0;
+
+                r = dns_label_unescape_suffix(a, &x, la, sizeof(la));
+                q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb));
+                if (r < 0 || q < 0)
+                        return CMP(r, q);
+
+                r = ascii_strcasecmp_nn(la, r, lb, q);
+                if (r != 0)
+                        return r;
+        }
+}
+
+DEFINE_HASH_OPS(dns_name_hash_ops, char, dns_name_hash_func, dns_name_compare_func);
+
+int dns_name_equal(const char *x, const char *y) {
+        int r, q;
+
+        assert(x);
+        assert(y);
+
+        for (;;) {
+                char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
+
+                r = dns_label_unescape(&x, la, sizeof la, 0);
+                if (r < 0)
+                        return r;
+
+                q = dns_label_unescape(&y, lb, sizeof lb, 0);
+                if (q < 0)
+                        return q;
+
+                if (r != q)
+                        return false;
+                if (r == 0)
+                        return true;
+
+                if (ascii_strcasecmp_n(la, lb, r) != 0)
+                        return false;
+        }
+}
+
+int dns_name_endswith(const char *name, const char *suffix) {
+        const char *n, *s, *saved_n = NULL;
+        int r, q;
+
+        assert(name);
+        assert(suffix);
+
+        n = name;
+        s = suffix;
+
+        for (;;) {
+                char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
+
+                r = dns_label_unescape(&n, ln, sizeof ln, 0);
+                if (r < 0)
+                        return r;
+
+                if (!saved_n)
+                        saved_n = n;
+
+                q = dns_label_unescape(&s, ls, sizeof ls, 0);
+                if (q < 0)
+                        return q;
+
+                if (r == 0 && q == 0)
+                        return true;
+                if (r == 0 && saved_n == n)
+                        return false;
+
+                if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) {
+
+                        /* Not the same, let's jump back, and try with the next label again */
+                        s = suffix;
+                        n = TAKE_PTR(saved_n);
+                }
+        }
+}
+
+int dns_name_startswith(const char *name, const char *prefix) {
+        const char *n, *p;
+        int r, q;
+
+        assert(name);
+        assert(prefix);
+
+        n = name;
+        p = prefix;
+
+        for (;;) {
+                char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX];
+
+                r = dns_label_unescape(&p, lp, sizeof lp, 0);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return true;
+
+                q = dns_label_unescape(&n, ln, sizeof ln, 0);
+                if (q < 0)
+                        return q;
+
+                if (r != q)
+                        return false;
+                if (ascii_strcasecmp_n(ln, lp, r) != 0)
+                        return false;
+        }
+}
+
+int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) {
+        const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix;
+        int r, q;
+
+        assert(name);
+        assert(old_suffix);
+        assert(new_suffix);
+        assert(ret);
+
+        n = name;
+        s = old_suffix;
+
+        for (;;) {
+                char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
+
+                if (!saved_before)
+                        saved_before = n;
+
+                r = dns_label_unescape(&n, ln, sizeof ln, 0);
+                if (r < 0)
+                        return r;
+
+                if (!saved_after)
+                        saved_after = n;
+
+                q = dns_label_unescape(&s, ls, sizeof ls, 0);
+                if (q < 0)
+                        return q;
+
+                if (r == 0 && q == 0)
+                        break;
+                if (r == 0 && saved_after == n) {
+                        *ret = NULL; /* doesn't match */
+                        return 0;
+                }
+
+                if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) {
+
+                        /* Not the same, let's jump back, and try with the next label again */
+                        s = old_suffix;
+                        n = TAKE_PTR(saved_after);
+                        saved_before = NULL;
+                }
+        }
+
+        /* Found it! Now generate the new name */
+        prefix = strndupa(name, saved_before - name);
+
+        r = dns_name_concat(prefix, new_suffix, 0, ret);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+int dns_name_between(const char *a, const char *b, const char *c) {
+        /* Determine if b is strictly greater than a and strictly smaller than c.
+           We consider the order of names to be circular, so that if a is
+           strictly greater than c, we consider b to be between them if it is
+           either greater than a or smaller than c. This is how the canonical
+           DNS name order used in NSEC records work. */
+
+        if (dns_name_compare_func(a, c) < 0)
+                /*
+                   a and c are properly ordered:
+                   a<---b--->c
+                */
+                return dns_name_compare_func(a, b) < 0 &&
+                       dns_name_compare_func(b, c) < 0;
+        else
+                /*
+                   a and c are equal or 'reversed':
+                   <--b--c         a----->
+                   or:
+                   <-----c         a--b-->
+                */
+                return dns_name_compare_func(b, c) < 0 ||
+                       dns_name_compare_func(a, b) < 0;
+}
+
+int dns_name_reverse(int family, const union in_addr_union *a, char **ret) {
+        const uint8_t *p;
+        int r;
+
+        assert(a);
+        assert(ret);
+
+        p = (const uint8_t*) a;
+
+        if (family == AF_INET)
+                r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]);
+        else if (family == AF_INET6)
+                r = asprintf(ret, "%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.ip6.arpa",
+                             hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4),
+                             hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4),
+                             hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4),
+                             hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4),
+                             hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4),
+                             hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4),
+                             hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4),
+                             hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4));
+        else
+                return -EAFNOSUPPORT;
+        if (r < 0)
+                return -ENOMEM;
+
+        return 0;
+}
+
+int dns_name_address(const char *p, int *family, union in_addr_union *address) {
+        int r;
+
+        assert(p);
+        assert(family);
+        assert(address);
+
+        r = dns_name_endswith(p, "in-addr.arpa");
+        if (r < 0)
+                return r;
+        if (r > 0) {
+                uint8_t a[4];
+                unsigned i;
+
+                for (i = 0; i < ELEMENTSOF(a); i++) {
+                        char label[DNS_LABEL_MAX+1];
+
+                        r = dns_label_unescape(&p, label, sizeof label, 0);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                return -EINVAL;
+                        if (r > 3)
+                                return -EINVAL;
+
+                        r = safe_atou8(label, &a[i]);
+                        if (r < 0)
+                                return r;
+                }
+
+                r = dns_name_equal(p, "in-addr.arpa");
+                if (r <= 0)
+                        return r;
+
+                *family = AF_INET;
+                address->in.s_addr = htobe32(((uint32_t) a[3] << 24) |
+                                             ((uint32_t) a[2] << 16) |
+                                             ((uint32_t) a[1] << 8) |
+                                              (uint32_t) a[0]);
+
+                return 1;
+        }
+
+        r = dns_name_endswith(p, "ip6.arpa");
+        if (r < 0)
+                return r;
+        if (r > 0) {
+                struct in6_addr a;
+                unsigned i;
+
+                for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) {
+                        char label[DNS_LABEL_MAX+1];
+                        int x, y;
+
+                        r = dns_label_unescape(&p, label, sizeof label, 0);
+                        if (r <= 0)
+                                return r;
+                        if (r != 1)
+                                return -EINVAL;
+                        x = unhexchar(label[0]);
+                        if (x < 0)
+                                return -EINVAL;
+
+                        r = dns_label_unescape(&p, label, sizeof label, 0);
+                        if (r <= 0)
+                                return r;
+                        if (r != 1)
+                                return -EINVAL;
+                        y = unhexchar(label[0]);
+                        if (y < 0)
+                                return -EINVAL;
+
+                        a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x;
+                }
+
+                r = dns_name_equal(p, "ip6.arpa");
+                if (r <= 0)
+                        return r;
+
+                *family = AF_INET6;
+                address->in6 = a;
+                return 1;
+        }
+
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+bool dns_name_is_root(const char *name) {
+
+        assert(name);
+
+        /* There are exactly two ways to encode the root domain name:
+         * as empty string, or with a single dot. */
+
+        return STR_IN_SET(name, "", ".");
+}
+
+bool dns_name_is_single_label(const char *name) {
+        int r;
+
+        assert(name);
+
+        r = dns_name_parent(&name);
+        if (r <= 0)
+                return false;
+
+        return dns_name_is_root(name);
+}
+
+/* Encode a domain name according to RFC 1035 Section 3.1, without compression */
+int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical) {
+        uint8_t *label_length, *out;
+        int r;
+
+        assert(domain);
+        assert(buffer);
+
+        out = buffer;
+
+        do {
+                /* Reserve a byte for label length */
+                if (len <= 0)
+                        return -ENOBUFS;
+                len--;
+                label_length = out;
+                out++;
+
+                /* Convert and copy a single label. Note that
+                 * dns_label_unescape() returns 0 when it hits the end
+                 * of the domain name, which we rely on here to encode
+                 * the trailing NUL byte. */
+                r = dns_label_unescape(&domain, (char *) out, len, 0);
+                if (r < 0)
+                        return r;
+
+                /* Optionally, output the name in DNSSEC canonical
+                 * format, as described in RFC 4034, section 6.2. Or
+                 * in other words: in lower-case. */
+                if (canonical)
+                        ascii_strlower_n((char*) out, (size_t) r);
+
+                /* Fill label length, move forward */
+                *label_length = r;
+                out += r;
+                len -= r;
+
+        } while (r != 0);
+
+        /* Verify the maximum size of the encoded name. The trailing
+         * dot + NUL byte account are included this time, hence
+         * compare against DNS_HOSTNAME_MAX + 2 (which is 255) this
+         * time. */
+        if (out - buffer > DNS_HOSTNAME_MAX + 2)
+                return -EINVAL;
+
+        return out - buffer;
+}
+
+#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 */
+                return false;
+
+        if (label[0] != '_') /* First label char needs to be underscore */
+                return false;
+
+        /* Second char must be a letter */
+        if (!(label[1] >= 'A' && label[1] <= 'Z') &&
+            !(label[1] >= 'a' && label[1] <= 'z'))
+                return false;
+
+        /* Third and further chars must be alphanumeric or a hyphen */
+        for (k = 2; k < n; k++) {
+                if (!(label[k] >= 'A' && label[k] <= 'Z') &&
+                    !(label[k] >= 'a' && label[k] <= 'z') &&
+                    !(label[k] >= '0' && label[k] <= '9') &&
+                    label[k] != '-')
+                        return false;
+        }
+
+        return true;
+}
+
+bool dns_srv_type_is_valid(const char *name) {
+        unsigned c = 0;
+        int r;
+
+        if (!name)
+                return false;
+
+        for (;;) {
+                char label[DNS_LABEL_MAX];
+
+                /* This more or less implements RFC 6335, Section 5.1 */
+
+                r = dns_label_unescape(&name, label, sizeof label, 0);
+                if (r < 0)
+                        return false;
+                if (r == 0)
+                        break;
+
+                if (c >= 2)
+                        return false;
+
+                if (!srv_type_label_is_valid(label, r))
+                        return false;
+
+                c++;
+        }
+
+        return c == 2; /* exactly two labels */
+}
+
+bool dnssd_srv_type_is_valid(const char *name) {
+        return dns_srv_type_is_valid(name) &&
+                ((dns_name_endswith(name, "_tcp") > 0) ||
+                 (dns_name_endswith(name, "_udp") > 0)); /* Specific to DNS-SD. RFC 6763, Section 7 */
+}
+
+bool dns_service_name_is_valid(const char *name) {
+        size_t l;
+
+        /* This more or less implements RFC 6763, Section 4.1.1 */
+
+        if (!name)
+                return false;
+
+        if (!utf8_is_valid(name))
+                return false;
+
+        if (string_has_cc(name, NULL))
+                return false;
+
+        l = strlen(name);
+        if (l <= 0)
+                return false;
+        if (l > 63)
+                return false;
+
+        return true;
+}
+
+int dns_service_join(const char *name, const char *type, const char *domain, char **ret) {
+        char escaped[DNS_LABEL_ESCAPED_MAX];
+        _cleanup_free_ char *n = NULL;
+        int r;
+
+        assert(type);
+        assert(domain);
+        assert(ret);
+
+        if (!dns_srv_type_is_valid(type))
+                return -EINVAL;
+
+        if (!name)
+                return dns_name_concat(type, domain, 0, ret);
+
+        if (!dns_service_name_is_valid(name))
+                return -EINVAL;
+
+        r = dns_label_escape(name, strlen(name), escaped, sizeof(escaped));
+        if (r < 0)
+                return r;
+
+        r = dns_name_concat(type, domain, 0, &n);
+        if (r < 0)
+                return r;
+
+        return dns_name_concat(escaped, n, 0, ret);
+}
+
+static bool dns_service_name_label_is_valid(const char *label, size_t n) {
+        char *s;
+
+        assert(label);
+
+        if (memchr(label, 0, n))
+                return false;
+
+        s = strndupa(label, n);
+        return dns_service_name_is_valid(s);
+}
+
+int dns_service_split(const char *joined, char **_name, char **_type, char **_domain) {
+        _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
+        const char *p = joined, *q = NULL, *d = NULL;
+        char a[DNS_LABEL_MAX], b[DNS_LABEL_MAX], c[DNS_LABEL_MAX];
+        int an, bn, cn, r;
+        unsigned x = 0;
+
+        assert(joined);
+
+        /* Get first label from the full name */
+        an = dns_label_unescape(&p, a, sizeof(a), 0);
+        if (an < 0)
+                return an;
+
+        if (an > 0) {
+                x++;
+
+                /* If there was a first label, try to get the second one */
+                bn = dns_label_unescape(&p, b, sizeof(b), 0);
+                if (bn < 0)
+                        return bn;
+
+                if (bn > 0) {
+                        x++;
+
+                        /* If there was a second label, try to get the third one */
+                        q = p;
+                        cn = dns_label_unescape(&p, c, sizeof(c), 0);
+                        if (cn < 0)
+                                return cn;
+
+                        if (cn > 0)
+                                x++;
+                } else
+                        cn = 0;
+        } else
+                an = 0;
+
+        if (x >= 2 && srv_type_label_is_valid(b, bn)) {
+
+                if (x >= 3 && srv_type_label_is_valid(c, cn)) {
+
+                        if (dns_service_name_label_is_valid(a, an)) {
+                                /* OK, got <name> . <type> . <type2> . <domain> */
+
+                                name = strndup(a, an);
+                                if (!name)
+                                        return -ENOMEM;
+
+                                type = strjoin(b, ".", c);
+                                if (!type)
+                                        return -ENOMEM;
+
+                                d = p;
+                                goto finish;
+                        }
+
+                } else if (srv_type_label_is_valid(a, an)) {
+
+                        /* OK, got <type> . <type2> . <domain> */
+
+                        name = NULL;
+
+                        type = strjoin(a, ".", b);
+                        if (!type)
+                                return -ENOMEM;
+
+                        d = q;
+                        goto finish;
+                }
+        }
+
+        name = NULL;
+        type = NULL;
+        d = joined;
+
+finish:
+        r = dns_name_normalize(d, 0, &domain);
+        if (r < 0)
+                return r;
+
+        if (_domain)
+                *_domain = TAKE_PTR(domain);
+
+        if (_type)
+                *_type = TAKE_PTR(type);
+
+        if (_name)
+                *_name = TAKE_PTR(name);
+
+        return 0;
+}
+
+static int dns_name_build_suffix_table(const char *name, const char *table[]) {
+        const char *p;
+        unsigned n = 0;
+        int r;
+
+        assert(name);
+        assert(table);
+
+        p = name;
+        for (;;) {
+                if (n > DNS_N_LABELS_MAX)
+                        return -EINVAL;
+
+                table[n] = p;
+                r = dns_name_parent(&p);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                n++;
+        }
+
+        return (int) n;
+}
+
+int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
+        const char* labels[DNS_N_LABELS_MAX+1];
+        int n;
+
+        assert(name);
+        assert(ret);
+
+        n = dns_name_build_suffix_table(name, labels);
+        if (n < 0)
+                return n;
+
+        if ((unsigned) n < n_labels)
+                return -EINVAL;
+
+        *ret = labels[n - n_labels];
+        return (int) (n - n_labels);
+}
+
+int dns_name_skip(const char *a, unsigned n_labels, const char **ret) {
+        int r;
+
+        assert(a);
+        assert(ret);
+
+        for (; n_labels > 0; n_labels--) {
+                r = dns_name_parent(&a);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        *ret = "";
+                        return 0;
+                }
+        }
+
+        *ret = a;
+        return 1;
+}
+
+int dns_name_count_labels(const char *name) {
+        unsigned n = 0;
+        const char *p;
+        int r;
+
+        assert(name);
+
+        p = name;
+        for (;;) {
+                r = dns_name_parent(&p);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                if (n >= DNS_N_LABELS_MAX)
+                        return -EINVAL;
+
+                n++;
+        }
+
+        return (int) n;
+}
+
+int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) {
+        int r;
+
+        assert(a);
+        assert(b);
+
+        r = dns_name_skip(a, n_labels, &a);
+        if (r <= 0)
+                return r;
+
+        return dns_name_equal(a, b);
+}
+
+int dns_name_common_suffix(const char *a, const char *b, const char **ret) {
+        const char *a_labels[DNS_N_LABELS_MAX+1], *b_labels[DNS_N_LABELS_MAX+1];
+        int n = 0, m = 0, k = 0, r, q;
+
+        assert(a);
+        assert(b);
+        assert(ret);
+
+        /* Determines the common suffix of domain names a and b */
+
+        n = dns_name_build_suffix_table(a, a_labels);
+        if (n < 0)
+                return n;
+
+        m = dns_name_build_suffix_table(b, b_labels);
+        if (m < 0)
+                return m;
+
+        for (;;) {
+                char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
+                const char *x, *y;
+
+                if (k >= n || k >= m) {
+                        *ret = a_labels[n - k];
+                        return 0;
+                }
+
+                x = a_labels[n - 1 - k];
+                r = dns_label_unescape(&x, la, sizeof la, 0);
+                if (r < 0)
+                        return r;
+
+                y = b_labels[m - 1 - k];
+                q = dns_label_unescape(&y, lb, sizeof lb, 0);
+                if (q < 0)
+                        return q;
+
+                if (r != q || ascii_strcasecmp_n(la, lb, r) != 0) {
+                        *ret = a_labels[n - k];
+                        return 0;
+                }
+
+                k++;
+        }
+}
+
+int dns_name_apply_idna(const char *name, char **ret) {
+        /* Return negative on error, 0 if not implemented, positive on success. */
+
+#if HAVE_LIBIDN2
+        int r;
+        _cleanup_free_ char *t = NULL;
+
+        assert(name);
+        assert(ret);
+
+        r = idn2_lookup_u8((uint8_t*) name, (uint8_t**) &t,
+                           IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
+        log_debug("idn2_lookup_u8: %s → %s", name, t);
+        if (r == IDN2_OK) {
+                if (!startswith(name, "xn--")) {
+                        _cleanup_free_ char *s = NULL;
+
+                        r = idn2_to_unicode_8z8z(t, &s, 0);
+                        if (r != IDN2_OK) {
+                                log_debug("idn2_to_unicode_8z8z(\"%s\") failed: %d/%s",
+                                          t, r, idn2_strerror(r));
+                                return 0;
+                        }
+
+                        if (!streq_ptr(name, s)) {
+                                log_debug("idn2 roundtrip failed: \"%s\" → \"%s\" → \"%s\", ignoring.",
+                                          name, t, s);
+                                return 0;
+                        }
+                }
+
+                *ret = TAKE_PTR(t);
+
+                return 1; /* *ret has been written */
+        }
+
+        log_debug("idn2_lookup_u8(\"%s\") failed: %d/%s", name, r, idn2_strerror(r));
+        if (r == IDN2_2HYPHEN)
+                /* The name has two hyphens — forbidden by IDNA2008 in some cases */
+                return 0;
+        if (IN_SET(r, IDN2_TOO_BIG_DOMAIN, IDN2_TOO_BIG_LABEL))
+                return -ENOSPC;
+        return -EINVAL;
+#elif HAVE_LIBIDN
+        _cleanup_free_ char *buf = NULL;
+        size_t n = 0, allocated = 0;
+        bool first = true;
+        int r, q;
+
+        assert(name);
+        assert(ret);
+
+        for (;;) {
+                char label[DNS_LABEL_MAX];
+
+                r = dns_label_unescape(&name, label, sizeof label, 0);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                q = dns_label_apply_idna(label, r, label, sizeof label);
+                if (q < 0)
+                        return q;
+                if (q > 0)
+                        r = q;
+
+                if (!GREEDY_REALLOC(buf, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+                        return -ENOMEM;
+
+                r = dns_label_escape(label, r, buf + n + !first, DNS_LABEL_ESCAPED_MAX);
+                if (r < 0)
+                        return r;
+
+                if (first)
+                        first = false;
+                else
+                        buf[n++] = '.';
+
+                n += r;
+        }
+
+        if (n > DNS_HOSTNAME_MAX)
+                return -EINVAL;
+
+        if (!GREEDY_REALLOC(buf, allocated, n + 1))
+                return -ENOMEM;
+
+        buf[n] = 0;
+        *ret = TAKE_PTR(buf);
+
+        return 1;
+#else
+        return 0;
+#endif
+}
+
+int dns_name_is_valid_or_address(const char *name) {
+        /* Returns > 0 if the specified name is either a valid IP address formatted as string or a valid DNS name */
+
+        if (isempty(name))
+                return 0;
+
+        if (in_addr_from_string_auto(name, NULL, NULL) >= 0)
+                return 1;
+
+        return dns_name_is_valid(name);
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/shared/dns-domain.h b/shared/systemd/src/shared/dns-domain.h
new file mode 100644
index 00000000..88b3eb11
--- /dev/null
+++ b/shared/systemd/src/shared/dns-domain.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "hashmap.h"
+#include "in-addr-util.h"
+
+/* Length of a single label, with all escaping removed, excluding any trailing dot or NUL byte */
+#define DNS_LABEL_MAX 63
+
+/* Worst case length of a single label, with all escaping applied and room for a trailing NUL byte. */
+#define DNS_LABEL_ESCAPED_MAX (DNS_LABEL_MAX*4+1)
+
+/* Maximum length of a full hostname, consisting of a series of unescaped labels, and no trailing dot or NUL byte */
+#define DNS_HOSTNAME_MAX 253
+
+/* Maximum length of a full hostname, on the wire, including the final NUL byte */
+#define DNS_WIRE_FORMAT_HOSTNAME_MAX 255
+
+/* Maximum number of labels per valid hostname */
+#define DNS_N_LABELS_MAX 127
+
+typedef enum DNSLabelFlags {
+        DNS_LABEL_LDH        = 1 << 0, /* Follow the "LDH" rule — only letters, digits, and internal hyphens. */
+        DNS_LABEL_NO_ESCAPES = 1 << 1, /* Do not treat backslashes specially */
+} DNSLabelFlags;
+
+int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags flags);
+int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz);
+int dns_label_escape(const char *p, size_t l, char *dest, size_t sz);
+int dns_label_escape_new(const char *p, size_t l, char **ret);
+
+static inline int dns_name_parent(const char **name) {
+        return dns_label_unescape(name, NULL, DNS_LABEL_MAX, 0);
+}
+
+#if 0 /* NM_IGNORED */
+#if HAVE_LIBIDN
+int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max);
+int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max);
+#endif
+#endif /* NM_IGNORED */
+
+int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **ret);
+
+static inline int dns_name_normalize(const char *s, DNSLabelFlags flags, char **ret) {
+        /* dns_name_concat() normalizes as a side-effect */
+        return dns_name_concat(s, NULL, flags, ret);
+}
+
+static inline int dns_name_is_valid(const char *s) {
+        int r;
+
+        /* dns_name_normalize() verifies as a side effect */
+        r = dns_name_normalize(s, 0, NULL);
+        if (r == -EINVAL)
+                return 0;
+        if (r < 0)
+                return r;
+        return 1;
+}
+
+static inline int dns_name_is_valid_ldh(const char *s) {
+        int r;
+
+        r = dns_name_concat(s, NULL, DNS_LABEL_LDH|DNS_LABEL_NO_ESCAPES, NULL);
+        if (r == -EINVAL)
+                return 0;
+        if (r < 0)
+                return r;
+        return 1;
+}
+
+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;
+
+int dns_name_between(const char *a, const char *b, const char *c);
+int dns_name_equal(const char *x, const char *y);
+int dns_name_endswith(const char *name, const char *suffix);
+int dns_name_startswith(const char *name, const char *prefix);
+
+int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret);
+
+int dns_name_reverse(int family, const union in_addr_union *a, char **ret);
+int dns_name_address(const char *p, int *family, union in_addr_union *a);
+
+bool dns_name_is_root(const char *name);
+bool dns_name_is_single_label(const char *name);
+
+int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical);
+
+bool dns_srv_type_is_valid(const char *name);
+bool dnssd_srv_type_is_valid(const char *name);
+bool dns_service_name_is_valid(const char *name);
+
+int dns_service_join(const char *name, const char *type, const char *domain, char **ret);
+int dns_service_split(const char *joined, char **name, char **type, char **domain);
+
+int dns_name_suffix(const char *name, unsigned n_labels, const char **ret);
+int dns_name_count_labels(const char *name);
+
+int dns_name_skip(const char *a, unsigned n_labels, const char **ret);
+int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b);
+
+int dns_name_common_suffix(const char *a, const char *b, const char **ret);
+
+int dns_name_apply_idna(const char *name, char **ret);
+
+int dns_name_is_valid_or_address(const char *name);