summary refs log tree commit diff
path: root/shared/nm-glib-aux/nm-shared-utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'shared/nm-glib-aux/nm-shared-utils.c')
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.c5789
1 files changed, 0 insertions, 5789 deletions
diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c
deleted file mode 100644
index e7cea23a..00000000
--- a/shared/nm-glib-aux/nm-shared-utils.c
+++ /dev/null
@@ -1,5789 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2016 Red Hat, Inc.
- */
-
-#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
-
-#include "nm-shared-utils.h"
-
-#include <pwd.h>
-#include <arpa/inet.h>
-#include <poll.h>
-#include <fcntl.h>
-#include <sys/syscall.h>
-#include <glib-unix.h>
-#include <net/if.h>
-#include <net/ethernet.h>
-#include <pthread.h>
-
-#include "c-list/src/c-list.h"
-#include "nm-errno.h"
-#include "nm-str-buf.h"
-
-G_STATIC_ASSERT(sizeof(NMEtherAddr) == 6);
-G_STATIC_ASSERT(_nm_alignof(NMEtherAddr) == 1);
-
-G_STATIC_ASSERT(sizeof(NMUtilsNamedEntry) == sizeof(const char *));
-G_STATIC_ASSERT(G_STRUCT_OFFSET(NMUtilsNamedValue, value_ptr) == sizeof(const char *));
-
-/*****************************************************************************/
-
-const char _nm_hexchar_table_lower[16] = "0123456789abcdef";
-const char _nm_hexchar_table_upper[16] = "0123456789ABCDEF";
-
-const void *const _NM_PTRARRAY_EMPTY[1] = {NULL};
-
-/*****************************************************************************/
-
-const NMIPAddr nm_ip_addr_zero = {};
-
-/* this initializes a struct in_addr/in6_addr and allows for untrusted
- * arguments (like unsuitable @addr_family or @src_len). It's almost safe
- * in the sense that it verifies input arguments strictly. Also, it
- * uses memcpy() to access @src, so alignment is not an issue.
- *
- * Only potential pitfalls:
- *
- * - it allows for @addr_family to be AF_UNSPEC. If that is the case (and the
- *   caller allows for that), the caller MUST provide @out_addr_family.
- * - when setting @dst to an IPv4 address, the trailing bytes are not touched.
- *   Meaning, if @dst is an NMIPAddr union, only the first bytes will be set.
- *   If that matter to you, clear @dst before. */
-gboolean
-nm_ip_addr_set_from_untrusted(int           addr_family,
-                              gpointer      dst,
-                              gconstpointer src,
-                              gsize         src_len,
-                              int *         out_addr_family)
-{
-    nm_assert(dst);
-
-    switch (addr_family) {
-    case AF_UNSPEC:
-        if (!out_addr_family) {
-            /* when the callers allow undefined @addr_family, they must provide
-             * an @out_addr_family argument. */
-            nm_assert_not_reached();
-            return FALSE;
-        }
-        switch (src_len) {
-        case sizeof(struct in_addr):
-            addr_family = AF_INET;
-            break;
-        case sizeof(struct in6_addr):
-            addr_family = AF_INET6;
-            break;
-        default:
-            return FALSE;
-        }
-        break;
-    case AF_INET:
-        if (src_len != sizeof(struct in_addr))
-            return FALSE;
-        break;
-    case AF_INET6:
-        if (src_len != sizeof(struct in6_addr))
-            return FALSE;
-        break;
-    default:
-        /* when the callers allow undefined @addr_family, they must provide
-         * an @out_addr_family argument. */
-        nm_assert(out_addr_family);
-        return FALSE;
-    }
-
-    nm_assert(src);
-
-    memcpy(dst, src, src_len);
-    NM_SET_OUT(out_addr_family, addr_family);
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-G_STATIC_ASSERT(ETH_ALEN == sizeof(struct ether_addr));
-G_STATIC_ASSERT(ETH_ALEN == 6);
-
-/*****************************************************************************/
-
-pid_t
-nm_utils_gettid(void)
-{
-    return (pid_t) syscall(SYS_gettid);
-}
-
-/* Used for asserting that this function is called on the main-thread.
- * The main-thread is determined by remembering the thread-id
- * of when the function was called the first time.
- *
- * When forking, the thread-id is again reset upon first call. */
-gboolean
-_nm_assert_on_main_thread(void)
-{
-    G_LOCK_DEFINE_STATIC(lock);
-    static pid_t seen_tid;
-    static pid_t seen_pid;
-    pid_t        tid;
-    pid_t        pid;
-    gboolean     success = FALSE;
-
-    tid = nm_utils_gettid();
-    nm_assert(tid != 0);
-
-    G_LOCK(lock);
-
-    if (G_LIKELY(tid == seen_tid)) {
-        /* we don't care about false positives (when the process forked, and the thread-id
-         * is accidentally re-used) . It's for assertions only. */
-        success = TRUE;
-    } else {
-        pid = getpid();
-        nm_assert(pid != 0);
-
-        if (seen_tid == 0 || seen_pid != pid) {
-            /* either this is the first time we call the function, or the process
-             * forked. In both cases, remember the thread-id. */
-            seen_tid = tid;
-            seen_pid = pid;
-            success  = TRUE;
-        }
-    }
-
-    G_UNLOCK(lock);
-
-    return success;
-}
-
-/*****************************************************************************/
-
-void
-nm_utils_strbuf_append_c(char **buf, gsize *len, char c)
-{
-    switch (*len) {
-    case 0:
-        return;
-    case 1:
-        (*buf)[0] = '\0';
-        *len      = 0;
-        (*buf)++;
-        return;
-    default:
-        (*buf)[0] = c;
-        (*buf)[1] = '\0';
-        (*len)--;
-        (*buf)++;
-        return;
-    }
-}
-
-void
-nm_utils_strbuf_append_bin(char **buf, gsize *len, gconstpointer str, gsize str_len)
-{
-    switch (*len) {
-    case 0:
-        return;
-    case 1:
-        if (str_len == 0) {
-            (*buf)[0] = '\0';
-            return;
-        }
-        (*buf)[0] = '\0';
-        *len      = 0;
-        (*buf)++;
-        return;
-    default:
-        if (str_len == 0) {
-            (*buf)[0] = '\0';
-            return;
-        }
-        if (str_len >= *len) {
-            memcpy(*buf, str, *len - 1);
-            (*buf)[*len - 1] = '\0';
-            *buf             = &(*buf)[*len];
-            *len             = 0;
-        } else {
-            memcpy(*buf, str, str_len);
-            *buf      = &(*buf)[str_len];
-            (*buf)[0] = '\0';
-            *len -= str_len;
-        }
-        return;
-    }
-}
-
-void
-nm_utils_strbuf_append_str(char **buf, gsize *len, const char *str)
-{
-    gsize src_len;
-
-    switch (*len) {
-    case 0:
-        return;
-    case 1:
-        if (!str || !*str) {
-            (*buf)[0] = '\0';
-            return;
-        }
-        (*buf)[0] = '\0';
-        *len      = 0;
-        (*buf)++;
-        return;
-    default:
-        if (!str || !*str) {
-            (*buf)[0] = '\0';
-            return;
-        }
-        src_len = g_strlcpy(*buf, str, *len);
-        if (src_len >= *len) {
-            *buf = &(*buf)[*len];
-            *len = 0;
-        } else {
-            *buf = &(*buf)[src_len];
-            *len -= src_len;
-        }
-        return;
-    }
-}
-
-void
-nm_utils_strbuf_append(char **buf, gsize *len, const char *format, ...)
-{
-    char *  p = *buf;
-    va_list args;
-    int     retval;
-
-    if (*len == 0)
-        return;
-
-    va_start(args, format);
-    retval = g_vsnprintf(p, *len, format, args);
-    va_end(args);
-
-    if ((gsize) retval >= *len) {
-        *buf = &p[*len];
-        *len = 0;
-    } else {
-        *buf = &p[retval];
-        *len -= retval;
-    }
-}
-
-/**
- * nm_utils_strbuf_seek_end:
- * @buf: the input/output buffer
- * @len: the input/output length of the buffer.
- *
- * Commonly, one uses nm_utils_strbuf_append*(), to incrementally
- * append strings to the buffer. However, sometimes we need to use
- * existing API to write to the buffer.
- * After doing so, we want to adjust the buffer counter.
- * Essentially,
- *
- *   g_snprintf (buf, len, ...);
- *   nm_utils_strbuf_seek_end (&buf, &len);
- *
- * is almost the same as
- *
- *   nm_utils_strbuf_append (&buf, &len, ...);
- *
- * The only difference is the behavior when the string got truncated:
- * nm_utils_strbuf_append() will recognize that and set the remaining
- * length to zero.
- *
- * In general, the behavior is:
- *
- *  - if *len is zero, do nothing
- *  - if the buffer contains a NUL byte within the first *len characters,
- *    the buffer is pointed to the NUL byte and len is adjusted. In this
- *    case, the remaining *len is always >= 1.
- *    In particular, that is also the case if the NUL byte is at the very last
- *    position ((*buf)[*len -1]). That happens, when the previous operation
- *    either fit the string exactly into the buffer or the string was truncated
- *    by g_snprintf(). The difference cannot be determined.
- *  - if the buffer contains no NUL bytes within the first *len characters,
- *    write NUL at the last position, set *len to zero, and point *buf past
- *    the NUL byte. This would happen with
- *
- *       strncpy (buf, long_str, len);
- *       nm_utils_strbuf_seek_end (&buf, &len).
- *
- *    where strncpy() does truncate the string and not NUL terminate it.
- *    nm_utils_strbuf_seek_end() would then NUL terminate it.
- */
-void
-nm_utils_strbuf_seek_end(char **buf, gsize *len)
-{
-    gsize l;
-    char *end;
-
-    nm_assert(len);
-    nm_assert(buf && *buf);
-
-    if (*len <= 1) {
-        if (*len == 1 && (*buf)[0])
-            goto truncate;
-        return;
-    }
-
-    end = memchr(*buf, 0, *len);
-    if (end) {
-        l = end - *buf;
-        nm_assert(l < *len);
-
-        *buf = end;
-        *len -= l;
-        return;
-    }
-
-truncate:
-    /* hm, no NUL character within len bytes.
-     * Just NUL terminate the array and consume them
-     * all. */
-    *buf += *len;
-    (*buf)[-1] = '\0';
-    *len       = 0;
-    return;
-}
-
-/*****************************************************************************/
-
-GBytes *
-nm_gbytes_get_empty(void)
-{
-    static GBytes *bytes = NULL;
-    GBytes *       b;
-
-again:
-    b = g_atomic_pointer_get(&bytes);
-    if (G_UNLIKELY(!b)) {
-        b = g_bytes_new_static("", 0);
-        if (!g_atomic_pointer_compare_and_exchange(&bytes, NULL, b)) {
-            g_bytes_unref(b);
-            goto again;
-        }
-    }
-    return b;
-}
-
-GBytes *
-nm_g_bytes_new_from_str(const char *str)
-{
-    gsize l;
-
-    if (!str)
-        return NULL;
-
-    /* the returned array is guaranteed to have a trailing '\0'
-     * character *after* the length. */
-
-    l = strlen(str);
-    return g_bytes_new_take(nm_memdup(str, l + 1u), l);
-}
-
-/**
- * nm_utils_gbytes_equals:
- * @bytes: (allow-none): a #GBytes array to compare. Note that
- *   %NULL is treated like an #GBytes array of length zero.
- * @mem_data: the data pointer with @mem_len bytes
- * @mem_len: the length of the data pointer
- *
- * Returns: %TRUE if @bytes contains the same data as @mem_data. As a
- *   special case, a %NULL @bytes is treated like an empty array.
- */
-gboolean
-nm_utils_gbytes_equal_mem(GBytes *bytes, gconstpointer mem_data, gsize mem_len)
-{
-    gconstpointer p;
-    gsize         l;
-
-    if (!bytes) {
-        /* as a special case, let %NULL GBytes compare identical
-         * to an empty array. */
-        return (mem_len == 0);
-    }
-
-    p = g_bytes_get_data(bytes, &l);
-    return l == mem_len
-           && (mem_len == 0 /* allow @mem_data to be %NULL */
-               || memcmp(p, mem_data, mem_len) == 0);
-}
-
-GVariant *
-nm_utils_gbytes_to_variant_ay(GBytes *bytes)
-{
-    const guint8 *p;
-    gsize         l;
-
-    if (!bytes) {
-        /* for convenience, accept NULL to return an empty variant */
-        return g_variant_new_array(G_VARIANT_TYPE_BYTE, NULL, 0);
-    }
-
-    p = g_bytes_get_data(bytes, &l);
-    return g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, p, l, 1);
-}
-
-/*****************************************************************************/
-
-#define _variant_singleton_get(create_variant)                                   \
-    ({                                                                           \
-        static GVariant *_singleton = NULL;                                      \
-        GVariant *       _v;                                                     \
-                                                                                 \
-again:                                                                           \
-        _v = g_atomic_pointer_get(&_singleton);                                  \
-        if (G_UNLIKELY(!_v)) {                                                   \
-            _v = (create_variant);                                               \
-            nm_assert(_v);                                                       \
-            nm_assert(g_variant_is_floating(_v));                                \
-            g_variant_ref_sink(_v);                                              \
-            if (!g_atomic_pointer_compare_and_exchange(&_singleton, NULL, _v)) { \
-                g_variant_unref(_v);                                             \
-                goto again;                                                      \
-            }                                                                    \
-        }                                                                        \
-        _v;                                                                      \
-    })
-
-GVariant *
-nm_g_variant_singleton_u_0(void)
-{
-    return _variant_singleton_get(g_variant_new_uint32(0));
-}
-
-/*****************************************************************************/
-
-GHashTable *
-nm_utils_strdict_clone(GHashTable *src)
-{
-    GHashTable *   dst;
-    GHashTableIter iter;
-    const char *   key;
-    const char *   val;
-
-    if (!src)
-        return NULL;
-
-    dst = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free);
-    g_hash_table_iter_init(&iter, src);
-    while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val))
-        g_hash_table_insert(dst, g_strdup(key), g_strdup(val));
-    return dst;
-}
-
-/* Convert a hash table with "char *" keys and values to an "a{ss}" GVariant.
- * The keys will be sorted asciibetically.
- * Returns a floating reference.
- */
-GVariant *
-nm_utils_strdict_to_variant_ass(GHashTable *strdict)
-{
-    gs_free NMUtilsNamedValue *values_free = NULL;
-    NMUtilsNamedValue          values_prepared[20];
-    const NMUtilsNamedValue *  values;
-    GVariantBuilder            builder;
-    guint                      i;
-    guint                      n;
-
-    values = nm_utils_named_values_from_strdict(strdict, &n, values_prepared, &values_free);
-
-    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}"));
-    for (i = 0; i < n; i++) {
-        g_variant_builder_add(&builder, "{ss}", values[i].name, values[i].value_str);
-    }
-    return g_variant_builder_end(&builder);
-}
-
-/*****************************************************************************/
-
-GVariant *
-nm_utils_strdict_to_variant_asv(GHashTable *strdict)
-{
-    gs_free NMUtilsNamedValue *values_free = NULL;
-    NMUtilsNamedValue          values_prepared[20];
-    const NMUtilsNamedValue *  values;
-    GVariantBuilder            builder;
-    guint                      i;
-    guint                      n;
-
-    values = nm_utils_named_values_from_strdict(strdict, &n, values_prepared, &values_free);
-
-    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
-    for (i = 0; i < n; i++) {
-        g_variant_builder_add(&builder,
-                              "{sv}",
-                              values[i].name,
-                              g_variant_new_string(values[i].value_str));
-    }
-    return g_variant_builder_end(&builder);
-}
-
-/*****************************************************************************/
-
-/**
- * nm_strquote:
- * @buf: the output buffer of where to write the quoted @str argument.
- * @buf_len: the size of @buf.
- * @str: (allow-none): the string to quote.
- *
- * Writes @str to @buf with quoting. The resulting buffer
- * is always NUL terminated, unless @buf_len is zero.
- * If @str is %NULL, it writes "(null)".
- *
- * If @str needs to be truncated, the closing quote is '^' instead
- * of '"'.
- *
- * This is similar to nm_strquote_a(), which however uses alloca()
- * to allocate a new buffer. Also, here @buf_len is the size of @buf,
- * while nm_strquote_a() has the number of characters to print. The latter
- * doesn't include the quoting.
- *
- * Returns: the input buffer with the quoted string.
- */
-const char *
-nm_strquote(char *buf, gsize buf_len, const char *str)
-{
-    const char *const buf0 = buf;
-
-    if (!str) {
-        nm_utils_strbuf_append_str(&buf, &buf_len, "(null)");
-        goto out;
-    }
-
-    if (G_UNLIKELY(buf_len <= 2)) {
-        switch (buf_len) {
-        case 2:
-            *(buf++) = '^';
-            /* fall-through */
-        case 1:
-            *(buf++) = '\0';
-            break;
-        }
-        goto out;
-    }
-
-    *(buf++) = '"';
-    buf_len--;
-
-    nm_utils_strbuf_append_str(&buf, &buf_len, str);
-
-    /* if the string was too long we indicate truncation with a
-     * '^' instead of a closing quote. */
-    if (G_UNLIKELY(buf_len <= 1)) {
-        switch (buf_len) {
-        case 1:
-            buf[-1] = '^';
-            break;
-        case 0:
-            buf[-2] = '^';
-            break;
-        default:
-            nm_assert_not_reached();
-            break;
-        }
-    } else {
-        nm_assert(buf_len >= 2);
-        *(buf++) = '"';
-        *(buf++) = '\0';
-    }
-
-out:
-    return buf0;
-}
-
-/*****************************************************************************/
-
-char _nm_utils_to_string_buffer[];
-
-void
-nm_utils_to_string_buffer_init(char **buf, gsize *len)
-{
-    if (!*buf) {
-        *buf = _nm_utils_to_string_buffer;
-        *len = sizeof(_nm_utils_to_string_buffer);
-    }
-}
-
-gboolean
-nm_utils_to_string_buffer_init_null(gconstpointer obj, char **buf, gsize *len)
-{
-    nm_utils_to_string_buffer_init(buf, len);
-    if (!obj) {
-        g_strlcpy(*buf, "(null)", *len);
-        return FALSE;
-    }
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-const char *
-nm_utils_flags2str(const NMUtilsFlags2StrDesc *descs,
-                   gsize                       n_descs,
-                   unsigned                    flags,
-                   char *                      buf,
-                   gsize                       len)
-{
-    gsize i;
-    char *p;
-
-#if NM_MORE_ASSERTS > 10
-    nm_assert(descs);
-    nm_assert(n_descs > 0);
-    for (i = 0; i < n_descs; i++) {
-        gsize j;
-
-        nm_assert(descs[i].name && descs[i].name[0]);
-        for (j = 0; j < i; j++)
-            nm_assert(descs[j].flag != descs[i].flag);
-    }
-#endif
-
-    nm_utils_to_string_buffer_init(&buf, &len);
-
-    if (!len)
-        return buf;
-
-    buf[0] = '\0';
-    p      = buf;
-    if (!flags) {
-        for (i = 0; i < n_descs; i++) {
-            if (!descs[i].flag) {
-                nm_utils_strbuf_append_str(&p, &len, descs[i].name);
-                break;
-            }
-        }
-        return buf;
-    }
-
-    for (i = 0; flags && i < n_descs; i++) {
-        if (descs[i].flag && NM_FLAGS_ALL(flags, descs[i].flag)) {
-            flags &= ~descs[i].flag;
-
-            if (buf[0] != '\0')
-                nm_utils_strbuf_append_c(&p, &len, ',');
-            nm_utils_strbuf_append_str(&p, &len, descs[i].name);
-        }
-    }
-    if (flags) {
-        if (buf[0] != '\0')
-            nm_utils_strbuf_append_c(&p, &len, ',');
-        nm_utils_strbuf_append(&p, &len, "0x%x", flags);
-    }
-    return buf;
-};
-
-/*****************************************************************************/
-
-/**
- * _nm_utils_ip4_prefix_to_netmask:
- * @prefix: a CIDR prefix
- *
- * Returns: the netmask represented by the prefix, in network byte order
- **/
-guint32
-_nm_utils_ip4_prefix_to_netmask(guint32 prefix)
-{
-    return prefix < 32 ? ~htonl(0xFFFFFFFFu >> prefix) : 0xFFFFFFFFu;
-}
-
-gconstpointer
-nm_utils_ipx_address_clear_host_address(int family, gpointer dst, gconstpointer src, guint8 plen)
-{
-    g_return_val_if_fail(dst, NULL);
-
-    switch (family) {
-    case AF_INET:
-        g_return_val_if_fail(plen <= 32, NULL);
-
-        if (!src) {
-            /* allow "self-assignment", by specifying %NULL as source. */
-            src = dst;
-        }
-
-        *((guint32 *) dst) = nm_utils_ip4_address_clear_host_address(*((guint32 *) src), plen);
-        break;
-    case AF_INET6:
-        nm_utils_ip6_address_clear_host_address(dst, src, plen);
-        break;
-    default:
-        g_return_val_if_reached(NULL);
-    }
-    return dst;
-}
-
-/* nm_utils_ip4_address_clear_host_address:
- * @addr: source ip6 address
- * @plen: prefix length of network
- *
- * returns: the input address, with the host address set to 0.
- */
-in_addr_t
-nm_utils_ip4_address_clear_host_address(in_addr_t addr, guint8 plen)
-{
-    return addr & _nm_utils_ip4_prefix_to_netmask(plen);
-}
-
-/* nm_utils_ip6_address_clear_host_address:
- * @dst: destination output buffer, will contain the network part of the @src address
- * @src: source ip6 address
- * @plen: prefix length of network
- *
- * Note: this function is self assignment safe, to update @src inplace, set both
- * @dst and @src to the same destination or set @src NULL.
- */
-const struct in6_addr *
-nm_utils_ip6_address_clear_host_address(struct in6_addr *      dst,
-                                        const struct in6_addr *src,
-                                        guint8                 plen)
-{
-    g_return_val_if_fail(plen <= 128, NULL);
-    g_return_val_if_fail(dst, NULL);
-
-    if (!src)
-        src = dst;
-
-    if (plen < 128) {
-        guint nbytes = plen / 8;
-        guint nbits  = plen % 8;
-
-        if (nbytes && dst != src)
-            memcpy(dst, src, nbytes);
-        if (nbits) {
-            dst->s6_addr[nbytes] = (src->s6_addr[nbytes] & (0xFF << (8 - nbits)));
-            nbytes++;
-        }
-        if (nbytes <= 15)
-            memset(&dst->s6_addr[nbytes], 0, 16 - nbytes);
-    } else if (src != dst)
-        *dst = *src;
-
-    return dst;
-}
-
-int
-nm_utils_ip6_address_same_prefix_cmp(const struct in6_addr *addr_a,
-                                     const struct in6_addr *addr_b,
-                                     guint8                 plen)
-{
-    int    nbytes;
-    guint8 va, vb, m;
-
-    if (plen >= 128)
-        NM_CMP_DIRECT_MEMCMP(addr_a, addr_b, sizeof(struct in6_addr));
-    else {
-        nbytes = plen / 8;
-        if (nbytes)
-            NM_CMP_DIRECT_MEMCMP(addr_a, addr_b, nbytes);
-
-        plen = plen % 8;
-        if (plen != 0) {
-            m  = ~((1 << (8 - plen)) - 1);
-            va = ((((const guint8 *) addr_a))[nbytes]) & m;
-            vb = ((((const guint8 *) addr_b))[nbytes]) & m;
-            NM_CMP_DIRECT(va, vb);
-        }
-    }
-    return 0;
-}
-
-/*****************************************************************************/
-
-guint32
-_nm_utils_ip4_get_default_prefix0(in_addr_t ip)
-{
-    /* The function is originally from ipcalc.c of Red Hat's initscripts. */
-    switch (ntohl(ip) >> 24) {
-    case 0 ... 127:
-        return 8; /* Class A */
-    case 128 ... 191:
-        return 16; /* Class B */
-    case 192 ... 223:
-        return 24; /* Class C */
-    }
-    return 0;
-}
-
-guint32
-_nm_utils_ip4_get_default_prefix(in_addr_t ip)
-{
-    return _nm_utils_ip4_get_default_prefix0(ip) ?: 24;
-}
-
-gboolean
-nm_utils_ip_is_site_local(int addr_family, const void *address)
-{
-    in_addr_t addr4;
-
-    switch (addr_family) {
-    case AF_INET:
-        /* RFC1918 private addresses
-         * 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 */
-        addr4 = ntohl(*((const in_addr_t *) address));
-        return (addr4 & 0xff000000) == 0x0a000000 || (addr4 & 0xfff00000) == 0xac100000
-               || (addr4 & 0xffff0000) == 0xc0a80000;
-    case AF_INET6:
-        return IN6_IS_ADDR_SITELOCAL(address);
-    default:
-        g_return_val_if_reached(FALSE);
-    }
-}
-
-/*****************************************************************************/
-
-static gboolean
-_parse_legacy_addr4(const char *text, in_addr_t *out_addr, GError **error)
-{
-    gs_free char * s_free = NULL;
-    struct in_addr a1;
-    guint8         bin[sizeof(a1)];
-    char *         s;
-    int            i;
-
-    if (inet_aton(text, &a1) != 1) {
-        g_set_error_literal(error,
-                            NM_UTILS_ERROR,
-                            NM_UTILS_ERROR_INVALID_ARGUMENT,
-                            "address invalid according to inet_aton()");
-        return FALSE;
-    }
-
-    /* OK, inet_aton() accepted the format. That's good, because we want
-     * to accept IPv4 addresses in octal format, like 255.255.000.000.
-     * That's what "legacy" means here. inet_pton() doesn't accept those.
-     *
-     * But inet_aton() also ignores trailing garbage and formats with fewer than
-     * 4 digits. That is just too crazy and we don't do that. Perform additional checks
-     * and reject some forms that inet_aton() accepted.
-     *
-     * Note that we still should (of course) accept everything that inet_pton()
-     * accepts. However this code never gets called if inet_pton() succeeds
-     * (see below, aside the assertion code). */
-
-    if (NM_STRCHAR_ANY(text, ch, (!(ch >= '0' && ch <= '9') && !NM_IN_SET(ch, '.', 'x')))) {
-        /* We only accepts '.', digits, and 'x' for "0x". */
-        g_set_error_literal(error,
-                            NM_UTILS_ERROR,
-                            NM_UTILS_ERROR_INVALID_ARGUMENT,
-                            "contains an invalid character");
-        return FALSE;
-    }
-
-    s = nm_memdup_maybe_a(300, text, strlen(text) + 1, &s_free);
-
-    for (i = 0; i < G_N_ELEMENTS(bin); i++) {
-        char * current_token = s;
-        gint32 v;
-
-        s = strchr(s, '.');
-        if (s) {
-            s[0] = '\0';
-            s++;
-        }
-
-        if ((i == G_N_ELEMENTS(bin) - 1) != (s == NULL)) {
-            /* Exactly for the last digit, we expect to have no more following token.
-             * But this isn't the case. Abort. */
-            g_set_error(error,
-                        NM_UTILS_ERROR,
-                        NM_UTILS_ERROR_INVALID_ARGUMENT,
-                        "wrong number of tokens (index %d, token '%s')",
-                        i,
-                        s);
-            return FALSE;
-        }
-
-        v = _nm_utils_ascii_str_to_int64(current_token, 0, 0, 0xFF, -1);
-        if (v == -1) {
-            int errsv = errno;
-
-            /* we do accept octal and hex (even with leading "0x"). But something
-             * about this token is wrong. */
-            g_set_error(error,
-                        NM_UTILS_ERROR,
-                        NM_UTILS_ERROR_INVALID_ARGUMENT,
-                        "invalid token '%s': %s (%d)",
-                        current_token,
-                        nm_strerror_native(errsv),
-                        errsv);
-            return FALSE;
-        }
-
-        bin[i] = v;
-    }
-
-    if (memcmp(bin, &a1, sizeof(bin)) != 0) {
-        /* our parsing did not agree with what inet_aton() gave. Something
-         * is wrong. Abort. */
-        g_set_error(
-            error,
-            NM_UTILS_ERROR,
-            NM_UTILS_ERROR_INVALID_ARGUMENT,
-            "inet_aton() result 0x%08x differs from computed value 0x%02hhx%02hhx%02hhx%02hhx",
-            a1.s_addr,
-            bin[0],
-            bin[1],
-            bin[2],
-            bin[3]);
-        return FALSE;
-    }
-
-    *out_addr = a1.s_addr;
-    return TRUE;
-}
-
-gboolean
-nm_utils_parse_inaddr_bin_full(int         addr_family,
-                               gboolean    accept_legacy,
-                               const char *text,
-                               int *       out_addr_family,
-                               gpointer    out_addr)
-{
-    NMIPAddr addrbin;
-
-    g_return_val_if_fail(text, FALSE);
-
-    if (addr_family == AF_UNSPEC) {
-        g_return_val_if_fail(!out_addr || out_addr_family, FALSE);
-        addr_family = strchr(text, ':') ? AF_INET6 : AF_INET;
-    } else
-        g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE);
-
-    if (inet_pton(addr_family, text, &addrbin) != 1) {
-        if (accept_legacy && addr_family == AF_INET
-            && _parse_legacy_addr4(text, &addrbin.addr4, NULL)) {
-            /* The address is in some legacy format which inet_aton() accepts, but not inet_pton().
-             * Most likely octal digits (leading zeros). We accept the address. */
-        } else
-            return FALSE;
-    }
-
-#if NM_MORE_ASSERTS > 10
-    if (addr_family == AF_INET) {
-        gs_free_error GError *error = NULL;
-        in_addr_t             a;
-
-        /* The legacy parser should accept everything that inet_pton() accepts too. Meaning,
-         * it should strictly parse *more* formats. And of course, parse it the same way. */
-        if (!_parse_legacy_addr4(text, &a, &error)) {
-            char buf[INET_ADDRSTRLEN];
-
-            g_error("unexpected assertion failure: could parse \"%s\" as %s, but not accepted by "
-                    "legacy parser: %s",
-                    text,
-                    _nm_utils_inet4_ntop(addrbin.addr4, buf),
-                    error->message);
-        }
-        nm_assert(addrbin.addr4 == a);
-    }
-#endif
-
-    NM_SET_OUT(out_addr_family, addr_family);
-    if (out_addr)
-        nm_ip_addr_set(addr_family, out_addr, &addrbin);
-    return TRUE;
-}
-
-gboolean
-nm_utils_parse_inaddr(int addr_family, const char *text, char **out_addr)
-{
-    NMIPAddr addrbin;
-    char     addrstr_buf[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
-
-    g_return_val_if_fail(text, FALSE);
-
-    if (addr_family == AF_UNSPEC)
-        addr_family = strchr(text, ':') ? AF_INET6 : AF_INET;
-    else
-        g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE);
-
-    if (inet_pton(addr_family, text, &addrbin) != 1)
-        return FALSE;
-
-    NM_SET_OUT(out_addr,
-               g_strdup(inet_ntop(addr_family, &addrbin, addrstr_buf, sizeof(addrstr_buf))));
-    return TRUE;
-}
-
-gboolean
-nm_utils_parse_inaddr_prefix_bin(int         addr_family,
-                                 const char *text,
-                                 int *       out_addr_family,
-                                 gpointer    out_addr,
-                                 int *       out_prefix)
-{
-    gs_free char *addrstr_free = NULL;
-    int           prefix       = -1;
-    const char *  slash;
-    const char *  addrstr;
-    NMIPAddr      addrbin;
-
-    g_return_val_if_fail(text, FALSE);
-
-    if (addr_family == AF_UNSPEC) {
-        g_return_val_if_fail(!out_addr || out_addr_family, FALSE);
-        addr_family = strchr(text, ':') ? AF_INET6 : AF_INET;
-    } else
-        g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE);
-
-    slash = strchr(text, '/');
-    if (slash)
-        addrstr = nm_strndup_a(300, text, slash - text, &addrstr_free);
-    else
-        addrstr = text;
-
-    if (inet_pton(addr_family, addrstr, &addrbin) != 1)
-        return FALSE;
-
-    if (slash) {
-        /* For IPv4, `ip addr add` supports the prefix-length as a netmask. We don't
-         * do that. */
-        prefix =
-            _nm_utils_ascii_str_to_int64(&slash[1], 10, 0, addr_family == AF_INET ? 32 : 128, -1);
-        if (prefix == -1)
-            return FALSE;
-    }
-
-    NM_SET_OUT(out_addr_family, addr_family);
-    if (out_addr)
-        nm_ip_addr_set(addr_family, out_addr, &addrbin);
-    NM_SET_OUT(out_prefix, prefix);
-    return TRUE;
-}
-
-gboolean
-nm_utils_parse_inaddr_prefix(int addr_family, const char *text, char **out_addr, int *out_prefix)
-{
-    NMIPAddr addrbin;
-    char     addrstr_buf[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
-
-    if (!nm_utils_parse_inaddr_prefix_bin(addr_family, text, &addr_family, &addrbin, out_prefix))
-        return FALSE;
-    NM_SET_OUT(out_addr,
-               g_strdup(inet_ntop(addr_family, &addrbin, addrstr_buf, sizeof(addrstr_buf))));
-    return TRUE;
-}
-
-gboolean
-nm_utils_parse_next_line(const char **inout_ptr,
-                         gsize *      inout_len,
-                         const char **out_line,
-                         gsize *      out_line_len)
-{
-    gboolean    eol_is_carriage_return;
-    const char *line_start;
-    gsize       line_len;
-
-    nm_assert(inout_ptr);
-    nm_assert(inout_len);
-    nm_assert(*inout_len == 0 || *inout_ptr);
-    nm_assert(out_line);
-    nm_assert(out_line_len);
-
-    if (G_UNLIKELY(*inout_len == 0))
-        return FALSE;
-
-    line_start = *inout_ptr;
-
-    eol_is_carriage_return = FALSE;
-    for (line_len = 0;; line_len++) {
-        if (line_len >= *inout_len) {
-            /* if we consumed the entire line, we place the pointer at
-             * one character after the end. */
-            *inout_ptr = &line_start[line_len];
-            *inout_len = 0;
-            goto done;
-        }
-        switch (line_start[line_len]) {
-        case '\r':
-            eol_is_carriage_return = TRUE;
-            /* fall-through*/
-        case '\0':
-        case '\n':
-            *inout_ptr = &line_start[line_len + 1];
-            *inout_len = *inout_len - line_len - 1u;
-            if (eol_is_carriage_return && *inout_len > 0 && (*inout_ptr)[0] == '\n') {
-                /* also consume "\r\n" as one. */
-                (*inout_len)--;
-                (*inout_ptr)++;
-            }
-            goto done;
-        }
-    }
-
-done:
-    *out_line     = line_start;
-    *out_line_len = line_len;
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-gboolean
-nm_utils_ipaddr_is_valid(int addr_family, const char *str_addr)
-{
-    nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6));
-
-    return str_addr && nm_utils_parse_inaddr_bin(addr_family, str_addr, NULL, NULL);
-}
-
-gboolean
-nm_utils_ipaddr_is_normalized(int addr_family, const char *str_addr)
-{
-    NMIPAddr addr;
-    char     sbuf[NM_UTILS_INET_ADDRSTRLEN];
-
-    nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6));
-
-    if (!str_addr)
-        return FALSE;
-
-    if (!nm_utils_parse_inaddr_bin(addr_family, str_addr, &addr_family, &addr))
-        return FALSE;
-
-    nm_utils_inet_ntop(addr_family, &addr, sbuf);
-    return nm_streq(sbuf, str_addr);
-}
-
-/*****************************************************************************/
-
-/**
- * nm_g_ascii_strtoll()
- * @nptr: the string to parse
- * @endptr: the pointer on the first invalid chars
- * @base: the base.
- *
- * This wraps g_ascii_strtoll() and should in almost all cases behave identical
- * to it.
- *
- * However, it seems there are situations where g_ascii_strtoll() might set
- * errno to some unexpected value EAGAIN. Possibly this is related to creating
- * the C locale during
- *
- *   #ifdef USE_XLOCALE
- *   return strtoll_l (nptr, endptr, base, get_C_locale ());
- *
- * This wrapper tries to workaround that condition.
- */
-gint64
-nm_g_ascii_strtoll(const char *nptr, char **endptr, guint base)
-{
-    int       try_count = 2;
-    gint64    v;
-    const int errsv_orig = errno;
-    int       errsv;
-
-    nm_assert(nptr);
-    nm_assert(base == 0u || (base >= 2u && base <= 36u));
-
-again:
-    errno = 0;
-    v     = g_ascii_strtoll(nptr, endptr, base);
-    errsv = errno;
-
-    if (errsv == 0) {
-        if (errsv_orig != 0)
-            errno = errsv_orig;
-        return v;
-    }
-
-    if (errsv == ERANGE && NM_IN_SET(v, G_MININT64, G_MAXINT64))
-        return v;
-
-    if (errsv == EINVAL && v == 0 && nptr && nptr[0] == '\0')
-        return v;
-
-    if (try_count-- > 0)
-        goto again;
-
-#if NM_MORE_ASSERTS
-    g_critical("g_ascii_strtoll() for \"%s\" failed with errno=%d (%s) and v=%" G_GINT64_FORMAT,
-               nptr,
-               errsv,
-               nm_strerror_native(errsv),
-               v);
-#endif
-
-    return v;
-}
-
-/* See nm_g_ascii_strtoll() */
-guint64
-nm_g_ascii_strtoull(const char *nptr, char **endptr, guint base)
-{
-    int       try_count = 2;
-    guint64   v;
-    const int errsv_orig = errno;
-    int       errsv;
-
-    nm_assert(nptr);
-    nm_assert(base == 0u || (base >= 2u && base <= 36u));
-
-again:
-    errno = 0;
-    v     = g_ascii_strtoull(nptr, endptr, base);
-    errsv = errno;
-
-    if (errsv == 0) {
-        if (errsv_orig != 0)
-            errno = errsv_orig;
-        return v;
-    }
-
-    if (errsv == ERANGE && NM_IN_SET(v, G_MAXUINT64))
-        return v;
-
-    if (errsv == EINVAL && v == 0 && nptr && nptr[0] == '\0')
-        return v;
-
-    if (try_count-- > 0)
-        goto again;
-
-#if NM_MORE_ASSERTS
-    g_critical("g_ascii_strtoull() for \"%s\" failed with errno=%d (%s) and v=%" G_GUINT64_FORMAT,
-               nptr,
-               errsv,
-               nm_strerror_native(errsv),
-               v);
-#endif
-
-    return v;
-}
-
-/* see nm_g_ascii_strtoll(). */
-double
-nm_g_ascii_strtod(const char *nptr, char **endptr)
-{
-    int    try_count = 2;
-    double v;
-    int    errsv;
-
-    nm_assert(nptr);
-
-again:
-    v     = g_ascii_strtod(nptr, endptr);
-    errsv = errno;
-
-    if (errsv == 0)
-        return v;
-
-    if (errsv == ERANGE)
-        return v;
-
-    if (try_count-- > 0)
-        goto again;
-
-#if NM_MORE_ASSERTS
-    g_critical("g_ascii_strtod() for \"%s\" failed with errno=%d (%s) and v=%f",
-               nptr,
-               errsv,
-               nm_strerror_native(errsv),
-               v);
-#endif
-
-    /* Not really much else to do. Return the parsed value and leave errno set
-     * to the unexpected value. */
-    return v;
-}
-
-/* _nm_utils_ascii_str_to_int64:
- *
- * A wrapper for g_ascii_strtoll, that checks whether the whole string
- * can be successfully converted to a number and is within a given
- * range. On any error, @fallback will be returned and %errno will be set
- * to a non-zero value. On success, %errno will be set to zero, check %errno
- * for errors. Any trailing or leading (ascii) white space is ignored and the
- * functions is locale independent.
- *
- * The function is guaranteed to return a value between @min and @max
- * (inclusive) or @fallback. Also, the parsing is rather strict, it does
- * not allow for any unrecognized characters, except leading and trailing
- * white space.
- **/
-gint64
-_nm_utils_ascii_str_to_int64(const char *str, guint base, gint64 min, gint64 max, gint64 fallback)
-{
-    gint64      v;
-    const char *s = NULL;
-
-    str = nm_str_skip_leading_spaces(str);
-    if (!str || !str[0]) {
-        errno = EINVAL;
-        return fallback;
-    }
-
-    errno = 0;
-    v     = nm_g_ascii_strtoll(str, (char **) &s, base);
-
-    if (errno != 0)
-        return fallback;
-
-    if (s[0] != '\0') {
-        s = nm_str_skip_leading_spaces(s);
-        if (s[0] != '\0') {
-            errno = EINVAL;
-            return fallback;
-        }
-    }
-    if (v > max || v < min) {
-        errno = ERANGE;
-        return fallback;
-    }
-
-    return v;
-}
-
-guint64
-_nm_utils_ascii_str_to_uint64(const char *str,
-                              guint       base,
-                              guint64     min,
-                              guint64     max,
-                              guint64     fallback)
-{
-    guint64     v;
-    const char *s = NULL;
-
-    if (str) {
-        while (g_ascii_isspace(str[0]))
-            str++;
-    }
-    if (!str || !str[0]) {
-        errno = EINVAL;
-        return fallback;
-    }
-
-    errno = 0;
-    v     = nm_g_ascii_strtoull(str, (char **) &s, base);
-
-    if (errno != 0)
-        return fallback;
-    if (s[0] != '\0') {
-        while (g_ascii_isspace(s[0]))
-            s++;
-        if (s[0] != '\0') {
-            errno = EINVAL;
-            return fallback;
-        }
-    }
-    if (v > max || v < min) {
-        errno = ERANGE;
-        return fallback;
-    }
-
-    if (v != 0 && str[0] == '-') {
-        /* As documented, g_ascii_strtoull() accepts negative values, and returns their
-         * absolute value. We don't. */
-        errno = ERANGE;
-        return fallback;
-    }
-
-    return v;
-}
-
-/*****************************************************************************/
-
-int
-nm_strcmp_with_data(gconstpointer a, gconstpointer b, gpointer user_data)
-{
-    const char *s1 = a;
-    const char *s2 = b;
-
-    return strcmp(s1, s2);
-}
-
-/* like nm_strcmp_p(), suitable for g_ptr_array_sort_with_data().
- * g_ptr_array_sort() just casts nm_strcmp_p() to a function of different
- * signature. I guess, in glib there are knowledgeable people that ensure
- * that this additional argument doesn't cause problems due to different ABI
- * for every architecture that glib supports.
- * For NetworkManager, we'd rather avoid such stunts.
- **/
-int
-nm_strcmp_p_with_data(gconstpointer a, gconstpointer b, gpointer user_data)
-{
-    const char *s1 = *((const char **) a);
-    const char *s2 = *((const char **) b);
-
-    return strcmp(s1, s2);
-}
-
-int
-nm_strcmp0_p_with_data(gconstpointer a, gconstpointer b, gpointer user_data)
-{
-    const char *s1 = *((const char **) a);
-    const char *s2 = *((const char **) b);
-
-    return nm_strcmp0(s1, s2);
-}
-
-int
-nm_strcmp_ascii_case_with_data(gconstpointer a, gconstpointer b, gpointer user_data)
-{
-    const char *s1 = a;
-    const char *s2 = b;
-
-    return g_ascii_strcasecmp(s1, s2);
-}
-
-int
-nm_cmp_uint32_p_with_data(gconstpointer p_a, gconstpointer p_b, gpointer user_data)
-{
-    const guint32 a = *((const guint32 *) p_a);
-    const guint32 b = *((const guint32 *) p_b);
-
-    if (a < b)
-        return -1;
-    if (a > b)
-        return 1;
-    return 0;
-}
-
-int
-nm_cmp_int2ptr_p_with_data(gconstpointer p_a, gconstpointer p_b, gpointer user_data)
-{
-    /* p_a and p_b are two pointers to a pointer, where the pointer is
-     * interpreted as a integer using GPOINTER_TO_INT().
-     *
-     * That is the case of a hash-table that uses GINT_TO_POINTER() to
-     * convert integers as pointers, and the resulting keys-as-array
-     * array. */
-    const int a = GPOINTER_TO_INT(*((gconstpointer *) p_a));
-    const int b = GPOINTER_TO_INT(*((gconstpointer *) p_b));
-
-    if (a < b)
-        return -1;
-    if (a > b)
-        return 1;
-    return 0;
-}
-
-/*****************************************************************************/
-
-const char *
-nm_utils_dbus_path_get_last_component(const char *dbus_path)
-{
-    if (dbus_path) {
-        dbus_path = strrchr(dbus_path, '/');
-        if (dbus_path)
-            return dbus_path + 1;
-    }
-    return NULL;
-}
-
-static gint64
-_dbus_path_component_as_num(const char *p)
-{
-    gint64 n;
-
-    /* no odd stuff. No leading zeros, only a non-negative, decimal integer.
-     *
-     * Otherwise, there would be multiple ways to encode the same number "10"
-     * and "010". That is just confusing. A number has no leading zeros,
-     * if it has, it's not a number (as far as we are concerned here). */
-    if (p[0] == '0') {
-        if (p[1] != '\0')
-            return -1;
-        else
-            return 0;
-    }
-    if (!(p[0] >= '1' && p[0] <= '9'))
-        return -1;
-    if (!NM_STRCHAR_ALL(&p[1], ch, (ch >= '0' && ch <= '9')))
-        return -1;
-    n = _nm_utils_ascii_str_to_int64(p, 10, 0, G_MAXINT64, -1);
-    nm_assert(n == -1 || nm_streq0(p, nm_sprintf_bufa(100, "%" G_GINT64_FORMAT, n)));
-    return n;
-}
-
-int
-nm_utils_dbus_path_cmp(const char *dbus_path_a, const char *dbus_path_b)
-{
-    const char *l_a, *l_b;
-    gsize       plen;
-    gint64      n_a, n_b;
-
-    /* compare function for two D-Bus paths. It behaves like
-     * strcmp(), except, if both paths have the same prefix,
-     * and both end in a (positive) number, then the paths
-     * will be sorted by number. */
-
-    NM_CMP_SELF(dbus_path_a, dbus_path_b);
-
-    /* if one or both paths have no slash (and no last component)
-     * compare the full paths directly. */
-    if (!(l_a = nm_utils_dbus_path_get_last_component(dbus_path_a))
-        || !(l_b = nm_utils_dbus_path_get_last_component(dbus_path_b)))
-        goto comp_full;
-
-    /* check if both paths have the same prefix (up to the last-component). */
-    plen = l_a - dbus_path_a;
-    if (plen != (l_b - dbus_path_b))
-        goto comp_full;
-    NM_CMP_RETURN(strncmp(dbus_path_a, dbus_path_b, plen));
-
-    n_a = _dbus_path_component_as_num(l_a);
-    n_b = _dbus_path_component_as_num(l_b);
-    if (n_a == -1 && n_b == -1)
-        goto comp_l;
-
-    /* both components must be convertible to a number. If they are not,
-     * (and only one of them is), then we must always strictly sort numeric parts
-     * after non-numeric components. If we wouldn't, we wouldn't have
-     * a total order.
-     *
-     * An example of a not total ordering would be:
-     *   "8"   < "010"  (numeric)
-     *   "0x"  < "8"    (lexical)
-     *   "0x"  > "010"  (lexical)
-     * We avoid this, by forcing that a non-numeric entry "0x" always sorts
-     * before numeric entries.
-     *
-     * Additionally, _dbus_path_component_as_num() would also reject "010" as
-     * not a valid number.
-     */
-    if (n_a == -1)
-        return -1;
-    if (n_b == -1)
-        return 1;
-
-    NM_CMP_DIRECT(n_a, n_b);
-    nm_assert(nm_streq(dbus_path_a, dbus_path_b));
-    return 0;
-
-comp_full:
-    NM_CMP_DIRECT_STRCMP0(dbus_path_a, dbus_path_b);
-    return 0;
-comp_l:
-    NM_CMP_DIRECT_STRCMP0(l_a, l_b);
-    nm_assert(nm_streq(dbus_path_a, dbus_path_b));
-    return 0;
-}
-
-/*****************************************************************************/
-
-typedef struct {
-    union {
-        guint8  table[256];
-        guint64 _dummy_for_alignment;
-    };
-} CharLookupTable;
-
-static void
-_char_lookup_table_set_one(CharLookupTable *lookup, char ch)
-{
-    lookup->table[(guint8) ch] = 1;
-}
-
-static void
-_char_lookup_table_set_all(CharLookupTable *lookup, const char *candidates)
-{
-    while (candidates[0] != '\0')
-        _char_lookup_table_set_one(lookup, (candidates++)[0]);
-}
-
-static void
-_char_lookup_table_init(CharLookupTable *lookup, const char *candidates)
-{
-    *lookup = (CharLookupTable){
-        .table = {0},
-    };
-    if (candidates)
-        _char_lookup_table_set_all(lookup, candidates);
-}
-
-static gboolean
-_char_lookup_has(const CharLookupTable *lookup, char ch)
-{
-    /* with some optimization levels, the compiler thinks this code
-     * might access uninitialized @lookup. It is not -- when you look at the
-     * callers of this function. */
-    NM_PRAGMA_WARNING_DISABLE("-Wmaybe-uninitialized")
-    nm_assert(lookup->table[(guint8) '\0'] == 0);
-    return lookup->table[(guint8) ch] != 0;
-    NM_PRAGMA_WARNING_REENABLE
-}
-
-static gboolean
-_char_lookup_has_all(const CharLookupTable *lookup, const char *candidates)
-{
-    if (candidates) {
-        while (candidates[0] != '\0') {
-            if (!_char_lookup_has(lookup, (candidates++)[0]))
-                return FALSE;
-        }
-    }
-    return TRUE;
-}
-
-/**
- * nm_utils_strsplit_set_full:
- * @str: the string to split.
- * @delimiters: the set of delimiters.
- * @flags: additional flags for controlling the operation.
- *
- * This is a replacement for g_strsplit_set() which avoids copying
- * each word once (the entire strv array), but instead copies it once
- * and all words point into that internal copy.
- *
- * Note that for @str %NULL and "", this always returns %NULL too. That differs
- * from g_strsplit_set(), which would return an empty strv array for "".
- * This never returns an empty array.
- *
- * Returns: %NULL if @str is %NULL or "".
- *   If @str only contains delimiters and %NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY
- *   is not set, it also returns %NULL.
- *   Otherwise, a %NULL terminated strv array containing the split words.
- *   (delimiter characters are removed).
- *   The strings to which the result strv array points to are allocated
- *   after the returned result itself. Don't free the strings themself,
- *   but free everything with g_free().
- *   It is however safe and allowed to modify the individual strings in-place,
- *   like "g_strstrip((char *) iter[0])".
- */
-const char **
-nm_utils_strsplit_set_full(const char *str, const char *delimiters, NMUtilsStrsplitSetFlags flags)
-{
-    const char **   ptr;
-    gsize           num_tokens;
-    gsize           i_token;
-    gsize           str_len_p1;
-    const char *    c_str;
-    char *          s;
-    CharLookupTable ch_lookup;
-    const gboolean  f_escaped = NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED);
-    const gboolean  f_allow_escaping =
-        f_escaped || NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING);
-    const gboolean f_preserve_empty =
-        NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY);
-    const gboolean f_strstrip = NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP);
-
-    if (!str)
-        return NULL;
-
-    if (!delimiters) {
-        nm_assert_not_reached();
-        delimiters = " \t\n";
-    }
-    _char_lookup_table_init(&ch_lookup, delimiters);
-
-    nm_assert(!f_allow_escaping || !_char_lookup_has(&ch_lookup, '\\'));
-
-    if (!f_preserve_empty) {
-        while (_char_lookup_has(&ch_lookup, str[0]))
-            str++;
-    }
-
-    if (!str[0]) {
-        /* We return %NULL here, also with NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY.
-         * That makes nm_utils_strsplit_set_full() with NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY
-         * different from g_strsplit_set(), which would in this case return an empty array.
-         * If you need to handle %NULL, and "" specially, then check the input string first. */
-        return NULL;
-    }
-
-#define _char_is_escaped(str_start, str_cur)              \
-    ({                                                    \
-        const char *const _str_start = (str_start);       \
-        const char *const _str_cur   = (str_cur);         \
-        const char *      _str_i     = (_str_cur);        \
-                                                          \
-        while (_str_i > _str_start && _str_i[-1] == '\\') \
-            _str_i--;                                     \
-        (((_str_cur - _str_i) % 2) != 0);                 \
-    })
-
-    num_tokens = 1;
-    c_str      = str;
-    while (TRUE) {
-        while (G_LIKELY(!_char_lookup_has(&ch_lookup, c_str[0]))) {
-            if (c_str[0] == '\0')
-                goto done1;
-            c_str++;
-        }
-
-        /* we assume escapings are not frequent. After we found
-         * this delimiter, check whether it was escaped by counting
-         * the backslashed before. */
-        if (f_allow_escaping && _char_is_escaped(str, c_str)) {
-            /* the delimiter is escaped. This was not an accepted delimiter. */
-            c_str++;
-            continue;
-        }
-
-        c_str++;
-
-        /* if we drop empty tokens, then we now skip over all consecutive delimiters. */
-        if (!f_preserve_empty) {
-            while (_char_lookup_has(&ch_lookup, c_str[0]))
-                c_str++;
-            if (c_str[0] == '\0')
-                break;
-        }
-
-        num_tokens++;
-    }
-
-done1:
-
-    nm_assert(c_str[0] == '\0');
-
-    str_len_p1 = (c_str - str) + 1;
-
-    nm_assert(str[str_len_p1 - 1] == '\0');
-
-    ptr = g_malloc((sizeof(const char *) * (num_tokens + 1)) + str_len_p1);
-    s   = (char *) &ptr[num_tokens + 1];
-    memcpy(s, str, str_len_p1);
-
-    i_token = 0;
-
-    while (TRUE) {
-        nm_assert(i_token < num_tokens);
-        ptr[i_token++] = s;
-
-        if (s[0] == '\0') {
-            nm_assert(f_preserve_empty);
-            goto done2;
-        }
-        nm_assert(f_preserve_empty || !_char_lookup_has(&ch_lookup, s[0]));
-
-        while (!_char_lookup_has(&ch_lookup, s[0])) {
-            if (G_UNLIKELY(s[0] == '\\' && f_allow_escaping)) {
-                s++;
-                if (s[0] == '\0')
-                    goto done2;
-                s++;
-            } else if (s[0] == '\0')
-                goto done2;
-            else
-                s++;
-        }
-
-        nm_assert(_char_lookup_has(&ch_lookup, s[0]));
-        s[0] = '\0';
-        s++;
-
-        if (!f_preserve_empty) {
-            while (_char_lookup_has(&ch_lookup, s[0]))
-                s++;
-            if (s[0] == '\0')
-                goto done2;
-        }
-    }
-
-done2:
-    nm_assert(i_token == num_tokens);
-    ptr[i_token] = NULL;
-
-    if (f_strstrip) {
-        gsize i;
-
-        i_token = 0;
-        for (i = 0; ptr[i]; i++) {
-            s = (char *) nm_str_skip_leading_spaces(ptr[i]);
-            if (s[0] != '\0') {
-                char *s_last;
-
-                s_last = &s[strlen(s) - 1];
-                while (s_last > s && g_ascii_isspace(s_last[0])
-                       && (!f_allow_escaping || !_char_is_escaped(s, s_last)))
-                    (s_last--)[0] = '\0';
-            }
-
-            if (!f_preserve_empty && s[0] == '\0')
-                continue;
-
-            ptr[i_token++] = s;
-        }
-
-        if (i_token == 0) {
-            g_free(ptr);
-            return NULL;
-        }
-        ptr[i_token] = NULL;
-    }
-
-    if (f_escaped) {
-        gsize i, j;
-
-        /* We no longer need ch_lookup for its original purpose. Modify it, so it
-         * can detect the delimiters, '\\', and (optionally) whitespaces. */
-        _char_lookup_table_set_one(&ch_lookup, '\\');
-        if (f_strstrip)
-            _char_lookup_table_set_all(&ch_lookup, NM_ASCII_SPACES);
-
-        for (i_token = 0; ptr[i_token]; i_token++) {
-            s = (char *) ptr[i_token];
-            j = 0;
-            for (i = 0; s[i] != '\0';) {
-                if (s[i] == '\\' && _char_lookup_has(&ch_lookup, s[i + 1]))
-                    i++;
-                s[j++] = s[i++];
-            }
-            s[j] = '\0';
-        }
-    }
-
-    nm_assert(ptr && ptr[0]);
-    return ptr;
-}
-
-/*****************************************************************************/
-
-const char *
-nm_utils_escaped_tokens_escape_full(const char *                    str,
-                                    const char *                    delimiters,
-                                    const char *                    delimiters_as_needed,
-                                    NMUtilsEscapedTokensEscapeFlags flags,
-                                    char **                         out_to_free)
-{
-    CharLookupTable ch_lookup;
-    CharLookupTable ch_lookup_as_needed;
-    gboolean        has_ch_lookup_as_needed = FALSE;
-    char *          ret;
-    gsize           str_len;
-    gsize           alloc_len;
-    gsize           n_escapes;
-    gsize           i, j;
-    gboolean        escape_leading_space;
-    gboolean        escape_trailing_space;
-    gboolean        escape_backslash_as_needed;
-
-    nm_assert(
-        !delimiters_as_needed
-        || (delimiters_as_needed[0]
-            && NM_FLAGS_HAS(flags,
-                            NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED)));
-
-    if (!str || str[0] == '\0') {
-        *out_to_free = NULL;
-        return str;
-    }
-
-    str_len = strlen(str);
-
-    _char_lookup_table_init(&ch_lookup, delimiters);
-    if (!delimiters || NM_FLAGS_HAS(flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_SPACES)) {
-        flags &= ~(NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE
-                   | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE);
-        _char_lookup_table_set_all(&ch_lookup, NM_ASCII_SPACES);
-    }
-
-    if (NM_FLAGS_HAS(flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS)) {
-        _char_lookup_table_set_one(&ch_lookup, '\\');
-        escape_backslash_as_needed = FALSE;
-    } else if (_char_lookup_has(&ch_lookup, '\\'))
-        escape_backslash_as_needed = FALSE;
-    else {
-        escape_backslash_as_needed =
-            NM_FLAGS_HAS(flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED);
-        if (escape_backslash_as_needed) {
-            if (NM_FLAGS_ANY(flags,
-                             NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE
-                                 | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE)
-                && !_char_lookup_has_all(&ch_lookup, NM_ASCII_SPACES)) {
-                /* ESCAPE_LEADING_SPACE and ESCAPE_TRAILING_SPACE implies that we escape backslash
-                 * before whitespaces. */
-                if (!has_ch_lookup_as_needed) {
-                    has_ch_lookup_as_needed = TRUE;
-                    _char_lookup_table_init(&ch_lookup_as_needed, NULL);
-                }
-                _char_lookup_table_set_all(&ch_lookup_as_needed, NM_ASCII_SPACES);
-            }
-            if (delimiters_as_needed && !_char_lookup_has_all(&ch_lookup, delimiters_as_needed)) {
-                if (!has_ch_lookup_as_needed) {
-                    has_ch_lookup_as_needed = TRUE;
-                    _char_lookup_table_init(&ch_lookup_as_needed, NULL);
-                }
-                _char_lookup_table_set_all(&ch_lookup_as_needed, delimiters_as_needed);
-            }
-        }
-    }
-
-    escape_leading_space =
-        NM_FLAGS_HAS(flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE)
-        && g_ascii_isspace(str[0]) && !_char_lookup_has(&ch_lookup, str[0]);
-    if (str_len == 1)
-        escape_trailing_space = FALSE;
-    else {
-        escape_trailing_space =
-            NM_FLAGS_HAS(flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE)
-            && g_ascii_isspace(str[str_len - 1]) && !_char_lookup_has(&ch_lookup, str[str_len - 1]);
-    }
-
-    n_escapes = 0;
-    for (i = 0; str[i] != '\0'; i++) {
-        if (_char_lookup_has(&ch_lookup, str[i]))
-            n_escapes++;
-        else if (str[i] == '\\' && escape_backslash_as_needed
-                 && (_char_lookup_has(&ch_lookup, str[i + 1]) || NM_IN_SET(str[i + 1], '\0', '\\')
-                     || (has_ch_lookup_as_needed
-                         && _char_lookup_has(&ch_lookup_as_needed, str[i + 1]))))
-            n_escapes++;
-    }
-    if (escape_leading_space)
-        n_escapes++;
-    if (escape_trailing_space)
-        n_escapes++;
-
-    if (n_escapes == 0u) {
-        *out_to_free = NULL;
-        return str;
-    }
-
-    alloc_len = str_len + n_escapes + 1u;
-    ret       = g_new(char, alloc_len);
-
-    j = 0;
-    i = 0;
-
-    if (escape_leading_space) {
-        ret[j++] = '\\';
-        ret[j++] = str[i++];
-    }
-    for (; str[i] != '\0'; i++) {
-        if (_char_lookup_has(&ch_lookup, str[i]))
-            ret[j++] = '\\';
-        else if (str[i] == '\\' && escape_backslash_as_needed
-                 && (_char_lookup_has(&ch_lookup, str[i + 1]) || NM_IN_SET(str[i + 1], '\0', '\\')
-                     || (has_ch_lookup_as_needed
-                         && _char_lookup_has(&ch_lookup_as_needed, str[i + 1]))))
-            ret[j++] = '\\';
-        ret[j++] = str[i];
-    }
-    if (escape_trailing_space) {
-        nm_assert(!_char_lookup_has(&ch_lookup, ret[j - 1]) && g_ascii_isspace(ret[j - 1]));
-        ret[j]     = ret[j - 1];
-        ret[j - 1] = '\\';
-        j++;
-    }
-
-    nm_assert(j == alloc_len - 1);
-    ret[j] = '\0';
-    nm_assert(strlen(ret) == j);
-
-    *out_to_free = ret;
-    return ret;
-}
-
-/**
- * nm_utils_escaped_tokens_options_split:
- * @str: the src string. This string will be modified in-place.
- *   The output values will point into @str.
- * @out_key: (allow-none): the returned output key. This will always be set to @str
- *   itself. @str will be modified to contain only the unescaped, truncated
- *   key name.
- * @out_val: returns the parsed (and unescaped) value or %NULL, if @str contains
- *   no '=' delimiter.
- *
- * Honors backslash escaping to parse @str as "key=value" pairs. Optionally, if no '='
- * is present, @out_val will be returned as %NULL. Backslash can be used to escape
- * '=', ',', '\\', and ascii whitespace. Other backslash sequences are taken verbatim.
- *
- * For keys, '=' obviously must be escaped. For values, that is optional because an
- * unescaped '=' is just taken verbatim. For example, in a key, the sequence "\\="
- * must be escaped as "\\\\\\=". For the value, that works too, but "\\\\=" is also
- * accepted.
- *
- * Unescaped Space around the key and value are also removed. Space in general must
- * not be escaped, unless they are at the beginning or the end of key/value.
- */
-void
-nm_utils_escaped_tokens_options_split(char *str, const char **out_key, const char **out_val)
-{
-    const char *val = NULL;
-    gsize       i;
-    gsize       j;
-    gsize       last_space_idx;
-    gboolean    last_space_has;
-
-    nm_assert(str);
-
-    i = 0;
-    while (g_ascii_isspace(str[i]))
-        i++;
-
-    j              = 0;
-    last_space_idx = 0;
-    last_space_has = FALSE;
-    while (str[i] != '\0') {
-        if (g_ascii_isspace(str[i])) {
-            if (!last_space_has) {
-                last_space_has = TRUE;
-                last_space_idx = j;
-            }
-        } else {
-            if (str[i] == '\\') {
-                if (NM_IN_SET(str[i + 1u], '\\', ',', '=') || g_ascii_isspace(str[i + 1u]))
-                    i++;
-            } else if (str[i] == '=') {
-                /* Encounter an unescaped '=' character. When we still parse the key, this
-                 * is the separator we were waiting for. If we are parsing the value,
-                 * we take the character verbatim. */
-                if (!val) {
-                    if (last_space_has) {
-                        str[last_space_idx] = '\0';
-                        j                   = last_space_idx + 1;
-                        last_space_has      = FALSE;
-                    } else
-                        str[j++] = '\0';
-                    val = &str[j];
-                    i++;
-                    while (g_ascii_isspace(str[i]))
-                        i++;
-                    continue;
-                }
-            }
-            last_space_has = FALSE;
-        }
-        str[j++] = str[i++];
-    }
-
-    if (last_space_has)
-        str[last_space_idx] = '\0';
-    else
-        str[j] = '\0';
-
-    *out_key = str;
-    *out_val = val;
-}
-
-/*****************************************************************************/
-
-/**
- * nm_utils_strsplit_quoted:
- * @str: the string to split (e.g. from /proc/cmdline).
- *
- * This basically does that systemd's extract_first_word() does
- * with the flags "EXTRACT_UNQUOTE | EXTRACT_RELAX". This is what
- * systemd uses to parse /proc/cmdline, and we do too.
- *
- * Splits the string. We have nm_utils_strsplit_set() which
- * supports a variety of flags. However, extending that already
- * complex code to also support quotation and escaping is hard.
- * Instead, add a naive implementation.
- *
- * Returns: (transfer full): the split string.
- */
-char **
-nm_utils_strsplit_quoted(const char *str)
-{
-    gs_unref_ptrarray GPtrArray *arr     = NULL;
-    gs_free char *               str_out = NULL;
-    CharLookupTable              ch_lookup;
-
-    nm_assert(str);
-
-    _char_lookup_table_init(&ch_lookup, NM_ASCII_WHITESPACES);
-
-    for (;;) {
-        char  quote;
-        gsize j;
-
-        while (_char_lookup_has(&ch_lookup, str[0]))
-            str++;
-
-        if (str[0] == '\0')
-            break;
-
-        if (!str_out)
-            str_out = g_new(char, strlen(str) + 1);
-
-        quote = '\0';
-        j     = 0;
-        for (;;) {
-            if (str[0] == '\\') {
-                str++;
-                if (str[0] == '\0')
-                    break;
-                str_out[j++] = str[0];
-                str++;
-                continue;
-            }
-            if (quote) {
-                if (str[0] == '\0')
-                    break;
-                if (str[0] == quote) {
-                    quote = '\0';
-                    str++;
-                    continue;
-                }
-                str_out[j++] = str[0];
-                str++;
-                continue;
-            }
-            if (str[0] == '\0')
-                break;
-            if (NM_IN_SET(str[0], '\'', '"')) {
-                quote = str[0];
-                str++;
-                continue;
-            }
-            if (_char_lookup_has(&ch_lookup, str[0])) {
-                str++;
-                break;
-            }
-            str_out[j++] = str[0];
-            str++;
-        }
-
-        if (!arr)
-            arr = g_ptr_array_new();
-        g_ptr_array_add(arr, g_strndup(str_out, j));
-    }
-
-    if (!arr)
-        return g_new0(char *, 1);
-
-    g_ptr_array_add(arr, NULL);
-
-    /* We want to return an optimally sized strv array, with no excess
-     * memory allocated. Hence, clone once more. */
-    return nm_memdup(arr->pdata, sizeof(char *) * arr->len);
-}
-
-/*****************************************************************************/
-
-/**
- * nm_utils_strv_find_first:
- * @list: the strv list to search
- * @len: the length of the list, or a negative value if @list is %NULL terminated.
- * @needle: the value to search for. The search is done using strcmp().
- *
- * Searches @list for @needle and returns the index of the first match (based
- * on strcmp()).
- *
- * For convenience, @list has type 'char**' instead of 'const char **'.
- *
- * Returns: index of first occurrence or -1 if @needle is not found in @list.
- */
-gssize
-nm_utils_strv_find_first(char **list, gssize len, const char *needle)
-{
-    gssize i;
-
-    if (len > 0) {
-        g_return_val_if_fail(list, -1);
-
-        if (!needle) {
-            /* if we search a list with known length, %NULL is a valid @needle. */
-            for (i = 0; i < len; i++) {
-                if (!list[i])
-                    return i;
-            }
-        } else {
-            for (i = 0; i < len; i++) {
-                if (list[i] && !strcmp(needle, list[i]))
-                    return i;
-            }
-        }
-    } else if (len < 0) {
-        g_return_val_if_fail(needle, -1);
-
-        if (list) {
-            for (i = 0; list[i]; i++) {
-                if (strcmp(needle, list[i]) == 0)
-                    return i;
-            }
-        }
-    }
-    return -1;
-}
-
-char **
-_nm_utils_strv_cleanup(char **  strv,
-                       gboolean strip_whitespace,
-                       gboolean skip_empty,
-                       gboolean skip_repeated)
-{
-    guint i, j;
-
-    if (!strv || !*strv)
-        return strv;
-
-    if (strip_whitespace) {
-        /* we only modify the strings pointed to by @strv if @strip_whitespace is
-         * requested. Otherwise, the strings themselves are untouched. */
-        for (i = 0; strv[i]; i++)
-            g_strstrip(strv[i]);
-    }
-    if (!skip_empty && !skip_repeated)
-        return strv;
-    j = 0;
-    for (i = 0; strv[i]; i++) {
-        if ((skip_empty && !*strv[i])
-            || (skip_repeated && nm_utils_strv_find_first(strv, j, strv[i]) >= 0))
-            g_free(strv[i]);
-        else
-            strv[j++] = strv[i];
-    }
-    strv[j] = NULL;
-    return strv;
-}
-
-/*****************************************************************************/
-
-GPtrArray *
-_nm_g_ptr_array_copy(GPtrArray *    array,
-                     GCopyFunc      func,
-                     gpointer       user_data,
-                     GDestroyNotify element_free_func)
-{
-    GPtrArray *new_array;
-    guint      i;
-
-    g_return_val_if_fail(array, NULL);
-
-    new_array = g_ptr_array_new_full(array->len, element_free_func);
-    for (i = 0; i < array->len; i++) {
-        g_ptr_array_add(new_array, func ? func(array->pdata[i], user_data) : array->pdata[i]);
-    }
-    return new_array;
-}
-
-/*****************************************************************************/
-
-int
-_nm_utils_ascii_str_to_bool(const char *str, int default_value)
-{
-    gs_free char *str_free = NULL;
-
-    if (!str)
-        return default_value;
-
-    str = nm_strstrip_avoid_copy_a(300, str, &str_free);
-    if (str[0] == '\0')
-        return default_value;
-
-    if (!g_ascii_strcasecmp(str, "true") || !g_ascii_strcasecmp(str, "yes")
-        || !g_ascii_strcasecmp(str, "on") || !g_ascii_strcasecmp(str, "1"))
-        return TRUE;
-
-    if (!g_ascii_strcasecmp(str, "false") || !g_ascii_strcasecmp(str, "no")
-        || !g_ascii_strcasecmp(str, "off") || !g_ascii_strcasecmp(str, "0"))
-        return FALSE;
-
-    return default_value;
-}
-
-/*****************************************************************************/
-
-NM_CACHED_QUARK_FCN("nm-manager-error-quark", nm_manager_error_quark);
-
-NM_CACHED_QUARK_FCN("nm-utils-error-quark", nm_utils_error_quark);
-
-void
-nm_utils_error_set_cancelled(GError **error, gboolean is_disposing, const char *instance_name)
-{
-    if (is_disposing) {
-        g_set_error(error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_CANCELLED_DISPOSING,
-                    "Disposing %s instance",
-                    instance_name && *instance_name ? instance_name : "source");
-    } else {
-        g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Request cancelled");
-    }
-}
-
-gboolean
-nm_utils_error_is_cancelled_or_disposing(GError *error)
-{
-    if (error) {
-        if (error->domain == G_IO_ERROR)
-            return NM_IN_SET(error->code, G_IO_ERROR_CANCELLED);
-        if (error->domain == NM_UTILS_ERROR)
-            return NM_IN_SET(error->code, NM_UTILS_ERROR_CANCELLED_DISPOSING);
-    }
-    return FALSE;
-}
-
-gboolean
-nm_utils_error_is_notfound(GError *error)
-{
-    if (error) {
-        if (error->domain == G_IO_ERROR)
-            return NM_IN_SET(error->code, G_IO_ERROR_NOT_FOUND);
-        if (error->domain == G_FILE_ERROR)
-            return NM_IN_SET(error->code, G_FILE_ERROR_NOENT);
-    }
-    return FALSE;
-}
-
-/*****************************************************************************/
-
-/**
- * nm_g_object_set_property:
- * @object: the target object
- * @property_name: the property name
- * @value: the #GValue to set
- * @error: (allow-none): optional error argument
- *
- * A reimplementation of g_object_set_property(), but instead
- * returning an error instead of logging a warning. All g_object_set*()
- * versions in glib require you to not pass invalid types or they will
- * log a g_warning() -- without reporting an error. We don't want that,
- * so we need to hack error checking around it.
- *
- * Returns: whether the value was successfully set.
- */
-gboolean
-nm_g_object_set_property(GObject *     object,
-                         const char *  property_name,
-                         const GValue *value,
-                         GError **     error)
-{
-    GParamSpec *                pspec;
-    nm_auto_unset_gvalue GValue tmp_value = G_VALUE_INIT;
-    GObjectClass *              klass;
-
-    g_return_val_if_fail(G_IS_OBJECT(object), FALSE);
-    g_return_val_if_fail(property_name != NULL, FALSE);
-    g_return_val_if_fail(G_IS_VALUE(value), FALSE);
-    g_return_val_if_fail(!error || !*error, FALSE);
-
-    /* g_object_class_find_property() does g_param_spec_get_redirect_target(),
-     * where we differ from a plain g_object_set_property(). */
-    pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(object), property_name);
-
-    if (!pspec) {
-        g_set_error(error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_UNKNOWN,
-                    _("object class '%s' has no property named '%s'"),
-                    G_OBJECT_TYPE_NAME(object),
-                    property_name);
-        return FALSE;
-    }
-    if (!(pspec->flags & G_PARAM_WRITABLE)) {
-        g_set_error(error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_UNKNOWN,
-                    _("property '%s' of object class '%s' is not writable"),
-                    pspec->name,
-                    G_OBJECT_TYPE_NAME(object));
-        return FALSE;
-    }
-    if ((pspec->flags & G_PARAM_CONSTRUCT_ONLY)) {
-        g_set_error(error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_UNKNOWN,
-                    _("construct property \"%s\" for object '%s' can't be set after construction"),
-                    pspec->name,
-                    G_OBJECT_TYPE_NAME(object));
-        return FALSE;
-    }
-
-    klass = g_type_class_peek(pspec->owner_type);
-    if (klass == NULL) {
-        g_set_error(error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_UNKNOWN,
-                    _("'%s::%s' is not a valid property name; '%s' is not a GObject subtype"),
-                    g_type_name(pspec->owner_type),
-                    pspec->name,
-                    g_type_name(pspec->owner_type));
-        return FALSE;
-    }
-
-    /* provide a copy to work from, convert (if necessary) and validate */
-    g_value_init(&tmp_value, pspec->value_type);
-    if (!g_value_transform(value, &tmp_value)) {
-        g_set_error(error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_UNKNOWN,
-                    _("unable to set property '%s' of type '%s' from value of type '%s'"),
-                    pspec->name,
-                    g_type_name(pspec->value_type),
-                    G_VALUE_TYPE_NAME(value));
-        return FALSE;
-    }
-    if (g_param_value_validate(pspec, &tmp_value) && !(pspec->flags & G_PARAM_LAX_VALIDATION)) {
-        gs_free char *contents = g_strdup_value_contents(value);
-
-        g_set_error(error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_UNKNOWN,
-                    _("value \"%s\" of type '%s' is invalid or out of range for property '%s' of "
-                      "type '%s'"),
-                    contents,
-                    G_VALUE_TYPE_NAME(value),
-                    pspec->name,
-                    g_type_name(pspec->value_type));
-        return FALSE;
-    }
-
-    g_object_set_property(object, property_name, &tmp_value);
-    return TRUE;
-}
-
-#define _set_property(object, property_name, gtype, gtype_set, value, error)          \
-    G_STMT_START                                                                      \
-    {                                                                                 \
-        nm_auto_unset_gvalue GValue gvalue = {0};                                     \
-                                                                                      \
-        g_value_init(&gvalue, gtype);                                                 \
-        gtype_set(&gvalue, (value));                                                  \
-        return nm_g_object_set_property((object), (property_name), &gvalue, (error)); \
-    }                                                                                 \
-    G_STMT_END
-
-gboolean
-nm_g_object_set_property_string(GObject *   object,
-                                const char *property_name,
-                                const char *value,
-                                GError **   error)
-{
-    _set_property(object, property_name, G_TYPE_STRING, g_value_set_string, value, error);
-}
-
-gboolean
-nm_g_object_set_property_string_static(GObject *   object,
-                                       const char *property_name,
-                                       const char *value,
-                                       GError **   error)
-{
-    _set_property(object, property_name, G_TYPE_STRING, g_value_set_static_string, value, error);
-}
-
-gboolean
-nm_g_object_set_property_string_take(GObject *   object,
-                                     const char *property_name,
-                                     char *      value,
-                                     GError **   error)
-{
-    _set_property(object, property_name, G_TYPE_STRING, g_value_take_string, value, error);
-}
-
-gboolean
-nm_g_object_set_property_boolean(GObject *   object,
-                                 const char *property_name,
-                                 gboolean    value,
-                                 GError **   error)
-{
-    _set_property(object, property_name, G_TYPE_BOOLEAN, g_value_set_boolean, !!value, error);
-}
-
-gboolean
-nm_g_object_set_property_char(GObject *   object,
-                              const char *property_name,
-                              gint8       value,
-                              GError **   error)
-{
-    /* glib says about G_TYPE_CHAR:
-     *
-     * The type designated by G_TYPE_CHAR is unconditionally an 8-bit signed integer.
-     *
-     * This is always a (signed!) char. */
-    _set_property(object, property_name, G_TYPE_CHAR, g_value_set_schar, value, error);
-}
-
-gboolean
-nm_g_object_set_property_uchar(GObject *   object,
-                               const char *property_name,
-                               guint8      value,
-                               GError **   error)
-{
-    _set_property(object, property_name, G_TYPE_UCHAR, g_value_set_uchar, value, error);
-}
-
-gboolean
-nm_g_object_set_property_int(GObject *object, const char *property_name, int value, GError **error)
-{
-    _set_property(object, property_name, G_TYPE_INT, g_value_set_int, value, error);
-}
-
-gboolean
-nm_g_object_set_property_int64(GObject *   object,
-                               const char *property_name,
-                               gint64      value,
-                               GError **   error)
-{
-    _set_property(object, property_name, G_TYPE_INT64, g_value_set_int64, value, error);
-}
-
-gboolean
-nm_g_object_set_property_uint(GObject *   object,
-                              const char *property_name,
-                              guint       value,
-                              GError **   error)
-{
-    _set_property(object, property_name, G_TYPE_UINT, g_value_set_uint, value, error);
-}
-
-gboolean
-nm_g_object_set_property_uint64(GObject *   object,
-                                const char *property_name,
-                                guint64     value,
-                                GError **   error)
-{
-    _set_property(object, property_name, G_TYPE_UINT64, g_value_set_uint64, value, error);
-}
-
-gboolean
-nm_g_object_set_property_flags(GObject *   object,
-                               const char *property_name,
-                               GType       gtype,
-                               guint       value,
-                               GError **   error)
-{
-    nm_assert(({
-        nm_auto_unref_gtypeclass GTypeClass *gtypeclass = g_type_class_ref(gtype);
-        G_IS_FLAGS_CLASS(gtypeclass);
-    }));
-    _set_property(object, property_name, gtype, g_value_set_flags, value, error);
-}
-
-gboolean
-nm_g_object_set_property_enum(GObject *   object,
-                              const char *property_name,
-                              GType       gtype,
-                              int         value,
-                              GError **   error)
-{
-    nm_assert(({
-        nm_auto_unref_gtypeclass GTypeClass *gtypeclass = g_type_class_ref(gtype);
-        G_IS_ENUM_CLASS(gtypeclass);
-    }));
-    _set_property(object, property_name, gtype, g_value_set_enum, value, error);
-}
-
-GParamSpec *
-nm_g_object_class_find_property_from_gtype(GType gtype, const char *property_name)
-{
-    nm_auto_unref_gtypeclass GObjectClass *gclass = NULL;
-
-    gclass = g_type_class_ref(gtype);
-    return g_object_class_find_property(gclass, property_name);
-}
-
-/*****************************************************************************/
-
-/**
- * nm_g_type_find_implementing_class_for_property:
- * @gtype: the GObject type which has a property @pname
- * @pname: the name of the property to look up
- *
- * This is only a helper function for printf debugging. It's not
- * used in actual code. Hence, the function just asserts that
- * @pname and @gtype arguments are suitable. It cannot fail.
- *
- * Returns: the most ancestor type of @gtype, that
- *   implements the property @pname. It means, it
- *   searches the type hierarchy to find the type
- *   that added @pname.
- */
-GType
-nm_g_type_find_implementing_class_for_property(GType gtype, const char *pname)
-{
-    nm_auto_unref_gtypeclass GObjectClass *klass = NULL;
-    GParamSpec *                           pspec;
-
-    g_return_val_if_fail(pname, G_TYPE_INVALID);
-
-    klass = g_type_class_ref(gtype);
-    g_return_val_if_fail(G_IS_OBJECT_CLASS(klass), G_TYPE_INVALID);
-
-    pspec = g_object_class_find_property(klass, pname);
-    g_return_val_if_fail(pspec, G_TYPE_INVALID);
-
-    gtype = G_TYPE_FROM_CLASS(klass);
-
-    while (TRUE) {
-        nm_auto_unref_gtypeclass GObjectClass *k = NULL;
-
-        k = g_type_class_ref(g_type_parent(gtype));
-
-        g_return_val_if_fail(G_IS_OBJECT_CLASS(k), G_TYPE_INVALID);
-
-        if (g_object_class_find_property(k, pname) != pspec)
-            return gtype;
-
-        gtype = G_TYPE_FROM_CLASS(k);
-    }
-}
-
-/*****************************************************************************/
-
-static void
-_str_buf_append_c_escape_octal(NMStrBuf *strbuf, char ch)
-{
-    nm_str_buf_append_c4(strbuf,
-                         '\\',
-                         '0' + ((char) ((((guchar) ch) >> 6) & 07)),
-                         '0' + ((char) ((((guchar) ch) >> 3) & 07)),
-                         '0' + ((char) ((((guchar) ch)) & 07)));
-}
-
-/**
- * nm_utils_buf_utf8safe_unescape:
- * @str: (allow-none): the string to unescape. The string itself is a NUL terminated
- *   ASCII string, that can have C-style backslash escape sequences (which
- *   are to be unescaped). Non-ASCII characters (e.g. UTF-8) are taken verbatim, so
- *   it doesn't care that this string is UTF-8. However, usually this is a UTF-8 encoded
- *   string.
- * @flags: flags for unescaping. The following flags are supported.
- *   %NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES performs a g_strstrip() on the input string,
- *   but preserving escaped spaces. For example, "a\\t " gives "a\t" (that is, the escaped space does
- *   not get stripped). Likewise, the invalid escape sequence "a\\  " results in "a " (stripping
- *   the unescaped space, but preserving the escaped one).
- * @out_len: (out): the length of the parsed string.
- * @to_free: (out): if @str requires unescaping, the function will clone the string. In
- *   that case, the allocated buffer will be returned here.
- *
- * See C-style escapes at https://en.wikipedia.org/wiki/Escape_sequences_in_C#Table_of_escape_sequences.
- * Note that hex escapes ("\\xhh") and unicode escapes ("\\uhhhh", "\\Uhhhhhhhh") are not supported.
- *
- * Also, this function is very similar to g_strcompress() but without issuing g_warning()
- * assertions and proper handling of "\\000" escape sequences.
- *
- * Invalid escape sequences (or non-UTF-8 input) are gracefully accepted. For example "\\ "
- * is an invalid escape sequence, in this case the backslash is removed and " " gets returned.
- *
- * The function never leaks secrets in memory.
- *
- * Returns: the unescaped buffer of length @out_len. If @str is %NULL, this returns %NULL
- *   and sets @out_len to 0. Otherwise, a non-%NULL binary buffer is returned with
- *   @out_len bytes. Note that the binary buffer is guaranteed to be NUL terminated. That
- *   is @result[@out_len] is NUL.
- *   Note that the result is binary, and may have embedded NUL characters and non-UTF-8.
- *   If the function can avoid cloning the input string, it will return a pointer inside
- *   the input @str. For example, if there is no backslash, no cloning is necessary. In that
- *   case, @to_free will be %NULL. Otherwise, @to_free is set to a newly allocated buffer
- *   containing the unescaped string and returned.
- */
-gconstpointer
-nm_utils_buf_utf8safe_unescape(const char *            str,
-                               NMUtilsStrUtf8SafeFlags flags,
-                               gsize *                 out_len,
-                               gpointer *              to_free)
-{
-    gboolean    strip_spaces = NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES);
-    NMStrBuf    strbuf;
-    const char *s;
-    gsize       len;
-
-    g_return_val_if_fail(to_free, NULL);
-    g_return_val_if_fail(out_len, NULL);
-
-    if (!str) {
-        *out_len = 0;
-        *to_free = NULL;
-        return NULL;
-    }
-
-    if (strip_spaces)
-        str = nm_str_skip_leading_spaces(str);
-
-    len = strlen(str);
-
-    s = memchr(str, '\\', len);
-    if (!s) {
-        if (strip_spaces && len > 0 && g_ascii_isspace(str[len - 1])) {
-            len--;
-            while (len > 0 && g_ascii_isspace(str[len - 1]))
-                len--;
-            *out_len = len;
-            return (*to_free = g_strndup(str, len));
-        }
-        *out_len = len;
-        *to_free = NULL;
-        return str;
-    }
-
-    nm_str_buf_init(&strbuf, len + 1u, FALSE);
-
-    nm_str_buf_append_len(&strbuf, str, s - str);
-    str = s;
-
-    for (;;) {
-        char  ch;
-        guint v;
-
-        nm_assert(str[0] == '\\');
-
-        ch = (++str)[0];
-
-        if (ch == '\0') {
-            /* error. Trailing '\\' */
-            break;
-        }
-
-        if (ch >= '0' && ch <= '9') {
-            v  = ch - '0';
-            ch = (++str)[0];
-            if (ch >= '0' && ch <= '7') {
-                v  = v * 8 + (ch - '0');
-                ch = (++str)[0];
-                if (ch >= '0' && ch <= '7') {
-                    /* technically, escape sequences larger than \3FF are out of range
-                     * and invalid. We don't check for that, and do the same as
-                     * g_strcompress(): silently clip the value with & 0xFF. */
-                    v = v * 8 + (ch - '0');
-                    ++str;
-                }
-            }
-            ch = v;
-        } else {
-            switch (ch) {
-            case 'b':
-                ch = '\b';
-                break;
-            case 'f':
-                ch = '\f';
-                break;
-            case 'n':
-                ch = '\n';
-                break;
-            case 'r':
-                ch = '\r';
-                break;
-            case 't':
-                ch = '\t';
-                break;
-            case 'v':
-                ch = '\v';
-                break;
-            default:
-                /* Here we handle "\\\\", but all other unexpected escape sequences are really a bug.
-                 * Take them literally, after removing the escape character */
-                break;
-            }
-            str++;
-        }
-
-        nm_str_buf_append_c(&strbuf, ch);
-
-        s = strchr(str, '\\');
-        if (!s) {
-            gsize l = strlen(str);
-
-            if (strip_spaces) {
-                while (l > 0 && g_ascii_isspace(str[l - 1]))
-                    l--;
-            }
-            nm_str_buf_append_len(&strbuf, str, l);
-            break;
-        }
-
-        nm_str_buf_append_len(&strbuf, str, s - str);
-        str = s;
-    }
-
-    /* assert that no reallocation was necessary. For one, unescaping should
-     * never result in a longer string than the input. Also, when unescaping
-     * secrets, we want to ensure that we don't leak secrets in memory. */
-    nm_assert(strbuf.allocated == len + 1u);
-
-    return (*to_free = nm_str_buf_finalize(&strbuf, out_len));
-}
-
-/**
- * nm_utils_buf_utf8safe_escape:
- * @buf: byte array, possibly in utf-8 encoding, may have NUL characters.
- * @buflen: the length of @buf in bytes, or -1 if @buf is a NUL terminated
- *   string.
- * @flags: #NMUtilsStrUtf8SafeFlags flags
- * @to_free: (out): return the pointer location of the string
- *   if a copying was necessary.
- *
- * Based on the assumption, that @buf contains UTF-8 encoded bytes,
- * this will return valid UTF-8 sequence, and invalid sequences
- * will be escaped with backslash (C escaping, like g_strescape()).
- * This is sanitize non UTF-8 characters. The result is valid
- * UTF-8.
- *
- * The operation can be reverted with nm_utils_buf_utf8safe_unescape().
- * Note that if, and only if @buf contains no NUL bytes, the operation
- * can also be reverted with g_strcompress().
- *
- * Depending on @flags, valid UTF-8 characters are not escaped at all
- * (except the escape character '\\'). This is the difference to g_strescape(),
- * which escapes all non-ASCII characters. This allows to pass on
- * valid UTF-8 characters as-is and can be directly shown to the user
- * as UTF-8 -- with exception of the backslash escape character,
- * invalid UTF-8 sequences, and other (depending on @flags).
- *
- * Returns: the escaped input buffer, as valid UTF-8. If no escaping
- *   is necessary, it returns the input @buf. Otherwise, an allocated
- *   string @to_free is returned which must be freed by the caller
- *   with g_free. The escaping can be reverted by g_strcompress().
- **/
-const char *
-nm_utils_buf_utf8safe_escape(gconstpointer           buf,
-                             gssize                  buflen,
-                             NMUtilsStrUtf8SafeFlags flags,
-                             char **                 to_free)
-{
-    const char *const str = buf;
-    const char *      p   = NULL;
-    const char *      s;
-    gboolean          nul_terminated = FALSE;
-    NMStrBuf          strbuf;
-
-    g_return_val_if_fail(to_free, NULL);
-
-    *to_free = NULL;
-
-    if (buflen == 0)
-        return NULL;
-
-    if (buflen < 0) {
-        if (!str)
-            return NULL;
-        buflen = strlen(str);
-        if (buflen == 0)
-            return str;
-        nul_terminated = TRUE;
-    }
-
-    if (g_utf8_validate(str, buflen, &p) && nul_terminated) {
-        /* note that g_utf8_validate() does not allow NUL character inside @str. Good.
-         * We can treat @str like a NUL terminated string. */
-        if (!NM_STRCHAR_ANY(
-                str,
-                ch,
-                (ch == '\\'
-                 || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) && ch < ' ')
-                 || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII)
-                     && ((guchar) ch) >= 127))))
-            return str;
-    }
-
-    nm_str_buf_init(&strbuf, buflen + 5, NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET));
-
-    s = str;
-    do {
-        buflen -= p - s;
-        nm_assert(buflen >= 0);
-
-        for (; s < p; s++) {
-            char ch = s[0];
-
-            nm_assert(ch);
-            if (ch == '\\')
-                nm_str_buf_append_c2(&strbuf, '\\', '\\');
-            else if ((NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) && ch < ' ')
-                     || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII)
-                         && ((guchar) ch) >= 127))
-                _str_buf_append_c_escape_octal(&strbuf, ch);
-            else
-                nm_str_buf_append_c(&strbuf, ch);
-        }
-
-        if (buflen <= 0)
-            break;
-
-        _str_buf_append_c_escape_octal(&strbuf, p[0]);
-
-        buflen--;
-        if (buflen == 0)
-            break;
-
-        s = &p[1];
-        (void) g_utf8_validate(s, buflen, &p);
-    } while (TRUE);
-
-    return (*to_free = nm_str_buf_finalize(&strbuf, NULL));
-}
-
-const char *
-nm_utils_buf_utf8safe_escape_bytes(GBytes *bytes, NMUtilsStrUtf8SafeFlags flags, char **to_free)
-{
-    gconstpointer p;
-    gsize         l;
-
-    if (bytes)
-        p = g_bytes_get_data(bytes, &l);
-    else {
-        p = NULL;
-        l = 0;
-    }
-
-    return nm_utils_buf_utf8safe_escape(p, l, flags, to_free);
-}
-
-char *
-nm_utils_buf_utf8safe_escape_cp(gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags)
-{
-    const char *s_const;
-    char *      s;
-
-    s_const = nm_utils_buf_utf8safe_escape(buf, buflen, flags, &s);
-    nm_assert(!s || s == s_const);
-    return s ?: g_strdup(s_const);
-}
-
-/*****************************************************************************/
-
-const char *
-nm_utils_str_utf8safe_unescape(const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free)
-{
-    const char *res;
-    gsize       len;
-
-    g_return_val_if_fail(to_free, NULL);
-
-    res = nm_utils_buf_utf8safe_unescape(str, flags, &len, (gpointer *) to_free);
-
-    nm_assert((!res && len == 0) || (strlen(res) <= len));
-
-    return res;
-}
-
-/**
- * nm_utils_str_utf8safe_escape:
- * @str: NUL terminated input string, possibly in utf-8 encoding
- * @flags: #NMUtilsStrUtf8SafeFlags flags
- * @to_free: (out): return the pointer location of the string
- *   if a copying was necessary.
- *
- * Returns the possible non-UTF-8 NUL terminated string @str
- * and uses backslash escaping (C escaping, like g_strescape())
- * to sanitize non UTF-8 characters. The result is valid
- * UTF-8.
- *
- * The operation can be reverted with g_strcompress() or
- * nm_utils_str_utf8safe_unescape().
- *
- * Depending on @flags, valid UTF-8 characters are not escaped at all
- * (except the escape character '\\'). This is the difference to g_strescape(),
- * which escapes all non-ASCII characters. This allows to pass on
- * valid UTF-8 characters as-is and can be directly shown to the user
- * as UTF-8 -- with exception of the backslash escape character,
- * invalid UTF-8 sequences, and other (depending on @flags).
- *
- * Returns: the escaped input string, as valid UTF-8. If no escaping
- *   is necessary, it returns the input @str. Otherwise, an allocated
- *   string @to_free is returned which must be freed by the caller
- *   with g_free. The escaping can be reverted by g_strcompress().
- **/
-const char *
-nm_utils_str_utf8safe_escape(const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free)
-{
-    return nm_utils_buf_utf8safe_escape(str, -1, flags, to_free);
-}
-
-/**
- * nm_utils_str_utf8safe_escape_cp:
- * @str: NUL terminated input string, possibly in utf-8 encoding
- * @flags: #NMUtilsStrUtf8SafeFlags flags
- *
- * Like nm_utils_str_utf8safe_escape(), except the returned value
- * is always a copy of the input and must be freed by the caller.
- *
- * Returns: the escaped input string in UTF-8 encoding. The returned
- *   value should be freed with g_free().
- *   The escaping can be reverted by g_strcompress().
- **/
-char *
-nm_utils_str_utf8safe_escape_cp(const char *str, NMUtilsStrUtf8SafeFlags flags)
-{
-    char *s;
-
-    nm_utils_str_utf8safe_escape(str, flags, &s);
-    return s ?: g_strdup(str);
-}
-
-char *
-nm_utils_str_utf8safe_unescape_cp(const char *str, NMUtilsStrUtf8SafeFlags flags)
-{
-    char *s;
-
-    str = nm_utils_str_utf8safe_unescape(str, flags, &s);
-    return s ?: g_strdup(str);
-}
-
-char *
-nm_utils_str_utf8safe_escape_take(char *str, NMUtilsStrUtf8SafeFlags flags)
-{
-    char *str_to_free;
-
-    nm_utils_str_utf8safe_escape(str, flags, &str_to_free);
-    if (str_to_free) {
-        g_free(str);
-        return str_to_free;
-    }
-    return str;
-}
-
-/*****************************************************************************/
-
-/* taken from systemd's fd_wait_for_event(). Note that the timeout
- * is here in nano-seconds, not micro-seconds. */
-int
-nm_utils_fd_wait_for_event(int fd, int event, gint64 timeout_nsec)
-{
-    struct pollfd pollfd = {
-        .fd     = fd,
-        .events = event,
-    };
-    struct timespec ts, *pts;
-    int             r;
-
-    if (timeout_nsec < 0)
-        pts = NULL;
-    else {
-        ts.tv_sec  = (time_t)(timeout_nsec / NM_UTILS_NSEC_PER_SEC);
-        ts.tv_nsec = (long int) (timeout_nsec % NM_UTILS_NSEC_PER_SEC);
-        pts        = &ts;
-    }
-
-    r = ppoll(&pollfd, 1, pts, NULL);
-    if (r < 0)
-        return -NM_ERRNO_NATIVE(errno);
-    if (r == 0)
-        return 0;
-    return pollfd.revents;
-}
-
-/* taken from systemd's loop_read() */
-ssize_t
-nm_utils_fd_read_loop(int fd, void *buf, size_t nbytes, bool do_poll)
-{
-    uint8_t *p = buf;
-    ssize_t  n = 0;
-
-    g_return_val_if_fail(fd >= 0, -EINVAL);
-    g_return_val_if_fail(buf, -EINVAL);
-
-    /* If called with nbytes == 0, let's call read() at least
-     * once, to validate the operation */
-
-    if (nbytes > (size_t) SSIZE_MAX)
-        return -EINVAL;
-
-    do {
-        ssize_t k;
-
-        k = read(fd, p, nbytes);
-        if (k < 0) {
-            int errsv = errno;
-
-            if (errsv == EINTR)
-                continue;
-
-            if (errsv == EAGAIN && do_poll) {
-                /* We knowingly ignore any return value here,
-                 * and expect that any error/EOF is reported
-                 * via read() */
-
-                (void) nm_utils_fd_wait_for_event(fd, POLLIN, -1);
-                continue;
-            }
-
-            return n > 0 ? n : -NM_ERRNO_NATIVE(errsv);
-        }
-
-        if (k == 0)
-            return n;
-
-        g_assert((size_t) k <= nbytes);
-
-        p += k;
-        nbytes -= k;
-        n += k;
-    } while (nbytes > 0);
-
-    return n;
-}
-
-/* taken from systemd's loop_read_exact() */
-int
-nm_utils_fd_read_loop_exact(int fd, void *buf, size_t nbytes, bool do_poll)
-{
-    ssize_t n;
-
-    n = nm_utils_fd_read_loop(fd, buf, nbytes, do_poll);
-    if (n < 0)
-        return (int) n;
-    if ((size_t) n != nbytes)
-        return -EIO;
-
-    return 0;
-}
-
-/*****************************************************************************/
-
-void
-nm_utils_named_value_clear_with_g_free(NMUtilsNamedValue *val)
-{
-    if (val) {
-        gs_free gpointer x_name  = NULL;
-        gs_free gpointer x_value = NULL;
-
-        x_name  = (gpointer) g_steal_pointer(&val->name);
-        x_value = g_steal_pointer(&val->value_ptr);
-    }
-}
-
-G_STATIC_ASSERT(G_STRUCT_OFFSET(NMUtilsNamedValue, name) == 0);
-
-NMUtilsNamedValue *
-nm_utils_named_values_from_strdict_full(GHashTable *        hash,
-                                        guint *             out_len,
-                                        GCompareDataFunc    compare_func,
-                                        gpointer            user_data,
-                                        NMUtilsNamedValue * provided_buffer,
-                                        guint               provided_buffer_len,
-                                        NMUtilsNamedValue **out_allocated_buffer)
-{
-    GHashTableIter     iter;
-    NMUtilsNamedValue *values;
-    guint              i, len;
-
-    nm_assert(provided_buffer_len == 0 || provided_buffer);
-    nm_assert(!out_allocated_buffer || !*out_allocated_buffer);
-
-    if (!hash || !(len = g_hash_table_size(hash))) {
-        NM_SET_OUT(out_len, 0);
-        return NULL;
-    }
-
-    if (provided_buffer_len >= len + 1) {
-        /* the buffer provided by the caller is large enough. Use it. */
-        values = provided_buffer;
-    } else {
-        /* allocate a new buffer. */
-        values = g_new(NMUtilsNamedValue, len + 1);
-        NM_SET_OUT(out_allocated_buffer, values);
-    }
-
-    i = 0;
-    g_hash_table_iter_init(&iter, hash);
-    while (g_hash_table_iter_next(&iter, (gpointer *) &values[i].name, &values[i].value_ptr))
-        i++;
-    nm_assert(i == len);
-    values[i].name      = NULL;
-    values[i].value_ptr = NULL;
-
-    if (compare_func)
-        nm_utils_named_value_list_sort(values, len, compare_func, user_data);
-
-    NM_SET_OUT(out_len, len);
-    return values;
-}
-
-gssize
-nm_utils_named_value_list_find(const NMUtilsNamedValue *arr,
-                               gsize                    len,
-                               const char *             name,
-                               gboolean                 sorted)
-{
-    gsize i;
-
-    nm_assert(name);
-
-#if NM_MORE_ASSERTS > 5
-    {
-        for (i = 0; i < len; i++) {
-            const NMUtilsNamedValue *v = &arr[i];
-
-            nm_assert(v->name);
-            if (sorted && i > 0)
-                nm_assert(strcmp(arr[i - 1].name, v->name) < 0);
-        }
-    }
-
-    nm_assert(!sorted || nm_utils_named_value_list_is_sorted(arr, len, FALSE, NULL, NULL));
-#endif
-
-    if (sorted) {
-        return nm_utils_array_find_binary_search(arr,
-                                                 sizeof(NMUtilsNamedValue),
-                                                 len,
-                                                 &name,
-                                                 nm_strcmp_p_with_data,
-                                                 NULL);
-    }
-    for (i = 0; i < len; i++) {
-        if (nm_streq(arr[i].name, name))
-            return i;
-    }
-    return ~((gssize) len);
-}
-
-gboolean
-nm_utils_named_value_list_is_sorted(const NMUtilsNamedValue *arr,
-                                    gsize                    len,
-                                    gboolean                 accept_duplicates,
-                                    GCompareDataFunc         compare_func,
-                                    gpointer                 user_data)
-{
-    gsize i;
-    int   c_limit;
-
-    if (len == 0)
-        return TRUE;
-
-    g_return_val_if_fail(arr, FALSE);
-
-    if (!compare_func)
-        compare_func = nm_strcmp_p_with_data;
-
-    c_limit = accept_duplicates ? 0 : -1;
-
-    for (i = 1; i < len; i++) {
-        int c;
-
-        c = compare_func(&arr[i - 1], &arr[i], user_data);
-        if (c > c_limit)
-            return FALSE;
-    }
-    return TRUE;
-}
-
-void
-nm_utils_named_value_list_sort(NMUtilsNamedValue *arr,
-                               gsize              len,
-                               GCompareDataFunc   compare_func,
-                               gpointer           user_data)
-{
-    if (len == 0)
-        return;
-
-    g_return_if_fail(arr);
-
-    if (len == 1)
-        return;
-
-    g_qsort_with_data(arr,
-                      len,
-                      sizeof(NMUtilsNamedValue),
-                      compare_func ?: nm_strcmp_p_with_data,
-                      user_data);
-}
-
-/*****************************************************************************/
-
-gpointer *
-nm_utils_hash_keys_to_array(GHashTable *     hash,
-                            GCompareDataFunc compare_func,
-                            gpointer         user_data,
-                            guint *          out_len)
-{
-    guint     len;
-    gpointer *keys;
-
-    /* by convention, we never return an empty array. In that
-     * case, always %NULL. */
-    if (!hash || g_hash_table_size(hash) == 0) {
-        NM_SET_OUT(out_len, 0);
-        return NULL;
-    }
-
-    keys = g_hash_table_get_keys_as_array(hash, &len);
-    if (len > 1 && compare_func) {
-        g_qsort_with_data(keys, len, sizeof(gpointer), compare_func, user_data);
-    }
-    NM_SET_OUT(out_len, len);
-    return keys;
-}
-
-gpointer *
-nm_utils_hash_values_to_array(GHashTable *     hash,
-                              GCompareDataFunc compare_func,
-                              gpointer         user_data,
-                              guint *          out_len)
-{
-    GHashTableIter iter;
-    gpointer       value;
-    gpointer *     arr;
-    guint          i, len;
-
-    if (!hash || (len = g_hash_table_size(hash)) == 0u) {
-        NM_SET_OUT(out_len, 0);
-        return NULL;
-    }
-
-    arr = g_new(gpointer, ((gsize) len) + 1);
-    i   = 0;
-    g_hash_table_iter_init(&iter, hash);
-    while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &value))
-        arr[i++] = value;
-
-    nm_assert(i == len);
-    arr[len] = NULL;
-
-    if (len > 1 && compare_func) {
-        g_qsort_with_data(arr, len, sizeof(gpointer), compare_func, user_data);
-    }
-
-    NM_SET_OUT(out_len, len);
-    return arr;
-}
-
-/*****************************************************************************/
-
-/**
- * nm_utils_hashtable_equal:
- * @a: one #GHashTable
- * @b: other #GHashTable
- * @treat_null_as_empty: if %TRUE, when either @a or @b is %NULL, it is
- *   treated like an empty hash. It means, a %NULL hash will compare equal
- *   to an empty hash.
- * @equal_func: the equality function, for comparing the values.
- *   If %NULL, the values are not compared. In that case, the function
- *   only checks, if both dictionaries have the same keys -- according
- *   to @b's key equality function.
- *   Note that the values of @a will be passed as first argument
- *   to @equal_func.
- *
- * Compares two hash tables, whether they have equal content.
- * This only makes sense, if @a and @b have the same key types and
- * the same key compare-function.
- *
- * Returns: %TRUE, if both dictionaries have the same content.
- */
-gboolean
-nm_utils_hashtable_equal(const GHashTable *a,
-                         const GHashTable *b,
-                         gboolean          treat_null_as_empty,
-                         GEqualFunc        equal_func)
-{
-    guint          n;
-    GHashTableIter iter;
-    gconstpointer  key, v_a, v_b;
-
-    if (a == b)
-        return TRUE;
-    if (!treat_null_as_empty) {
-        if (!a || !b)
-            return FALSE;
-    }
-
-    n = a ? g_hash_table_size((GHashTable *) a) : 0;
-    if (n != (b ? g_hash_table_size((GHashTable *) b) : 0))
-        return FALSE;
-
-    if (n > 0) {
-        g_hash_table_iter_init(&iter, (GHashTable *) a);
-        while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &v_a)) {
-            if (!g_hash_table_lookup_extended((GHashTable *) b, key, NULL, (gpointer *) &v_b))
-                return FALSE;
-            if (equal_func && !equal_func(v_a, v_b))
-                return FALSE;
-        }
-    }
-
-    return TRUE;
-}
-
-static gboolean
-_utils_hashtable_equal(GHashTable *     hash_a,
-                       GHashTable *     hash_b,
-                       GCompareDataFunc cmp_values,
-                       gpointer         user_data)
-{
-    GHashTableIter h;
-    gpointer       a_key;
-    gpointer       a_val;
-    gpointer       b_val;
-
-    nm_assert(hash_a);
-    nm_assert(hash_b);
-    nm_assert(hash_a != hash_b);
-    nm_assert(g_hash_table_size(hash_a) == g_hash_table_size(hash_b));
-
-    /* We rely on both hashes to have the same hash/equal function. Otherwise, we would have to iterate
-     * both hashes and check whether all keys/values are present in the respective other hash (which
-     * would be O(n^2), since we couldn't use the plain lookup function. That is not a useful thing
-     * for this function. */
-
-    g_hash_table_iter_init(&h, hash_a);
-    while (g_hash_table_iter_next(&h, &a_key, &a_val)) {
-        if (!g_hash_table_lookup_extended(hash_b, a_key, NULL, &b_val))
-            return FALSE;
-
-        if (!cmp_values) {
-            /* we accept %NULL compare function to indicate that we don't care about the key. */
-            continue;
-        }
-
-        if (cmp_values(a_val, b_val, user_data) != 0)
-            return FALSE;
-    }
-
-    return TRUE;
-}
-
-/**
- * nm_utils_hashtable_cmp_equal:
- * @a: (allow-none): the hash table or %NULL
- * @b: (allow-none): the other hash table or %NULL
- * @cmp_values: (allow-none): if %NULL, only the keys
- *   will be compared. Otherwise, this function is used to
- *   check whether all keys are equal.
- * @user_data: the argument for @cmp_values.
- *
- * It is required that both @a and @b have the same hash and equals
- * function.
- *
- * Returns: %TRUE, if both keys have the same keys and (if
- *   @cmp_values is given) all values are the same.
- */
-gboolean
-nm_utils_hashtable_cmp_equal(const GHashTable *a,
-                             const GHashTable *b,
-                             GCompareDataFunc  cmp_values,
-                             gpointer          user_data)
-{
-    GHashTable *hash_a = (GHashTable *) a;
-    GHashTable *hash_b = (GHashTable *) b;
-    gboolean    same;
-    guint       size;
-
-    if (hash_a == hash_b)
-        return TRUE;
-
-    if (!hash_a || !hash_b)
-        return FALSE;
-
-    size = g_hash_table_size(hash_a);
-    if (size != g_hash_table_size(hash_b))
-        return FALSE;
-
-    if (size == 0)
-        return TRUE;
-
-    same = _utils_hashtable_equal(hash_a, hash_b, cmp_values, user_data);
-
-#if NM_MORE_ASSERTS > 5
-    nm_assert(same == _utils_hashtable_equal(hash_b, hash_a, cmp_values, user_data));
-#endif
-
-    return same;
-}
-
-typedef struct {
-    gpointer key;
-    gpointer val;
-} HashTableCmpData;
-
-typedef struct {
-    GCompareDataFunc cmp_keys;
-    gpointer         user_data;
-} HashTableUserData;
-
-static int
-_hashtable_cmp_func(gconstpointer a, gconstpointer b, gpointer user_data)
-{
-    const HashTableUserData *d   = user_data;
-    const HashTableCmpData * d_a = *((const HashTableCmpData *const *) a);
-    const HashTableCmpData * d_b = *((const HashTableCmpData *const *) b);
-
-    NM_CMP_RETURN(d->cmp_keys(d_a, d_b, d->user_data));
-    return 0;
-}
-
-/**
- * nm_utils_hashtable_cmp:
- * @a: (allow-none): the hash to compare. May be %NULL.
- * @b: (allow-none): the other hash to compare. May be %NULL.
- * @do_fast_precheck: if %TRUE, assume that the hashes are equal
- *   and that it is worth calling nm_utils_hashtable_cmp_equal() first.
- *   That requires, that both hashes have the same equals function
- *   which is compatible with the @cmp_keys function.
- * @cmp_keys: the compare function for keys. Usually, the hash/equal function
- *   of both hashes corresponds to this function. If you set @do_fast_precheck
- *   to false, then this is not a requirement.
- * @cmp_values: (allow-none): if %NULL, only the keys are compared.
- *   Otherwise, the values must are also compared with this function.
- *
- * Both hashes must have keys/values of the same domain, so that
- * they can be effectively compared with @cmp_keys and @cmp_values.
- *
- * %NULL hashes compare equal to %NULL, but not to empty hashes.
- *
- * Returns: 0 if both hashes are equal, or -1 or 1 if one of the hashes
- *   sorts before/after.
- */
-int
-nm_utils_hashtable_cmp(const GHashTable *a,
-                       const GHashTable *b,
-                       gboolean          do_fast_precheck,
-                       GCompareDataFunc  cmp_keys,
-                       GCompareDataFunc  cmp_values,
-                       gpointer          user_data)
-{
-    GHashTable *hash_a                       = (GHashTable *) a;
-    GHashTable *hash_b                       = (GHashTable *) b;
-    gs_free HashTableCmpData *cmp_array_free = NULL;
-    HashTableCmpData *        cmp_array_a;
-    HashTableCmpData *        cmp_array_b;
-    GHashTableIter            h;
-    gpointer                  i_key;
-    gpointer                  i_val;
-    gsize                     size2;
-    guint                     size;
-    guint                     i;
-
-    nm_assert(cmp_keys);
-
-    NM_CMP_SELF(hash_a, hash_b);
-
-    size = g_hash_table_size(hash_a);
-
-    NM_CMP_DIRECT(size, g_hash_table_size(hash_b));
-
-    if (size == 0)
-        return 0;
-
-    if (do_fast_precheck) {
-        gboolean same;
-
-        /* we expect that the hashes are equal and the caller ensures us that they
-         * use the same hash/equal functions. Do a fast path check first...
-         *
-         * It's unclear whether this is worth it. The full comparison is O(n*ln(n)),
-         * while the fast check (using the hash lookup) is O(n). But then, the pre-check
-         * makes additional requirements on the hash's hash/equal functions -- the
-         * full comparison does not make such requirements. */
-        same = _utils_hashtable_equal(hash_a, hash_b, cmp_values, user_data);
-#if NM_MORE_ASSERTS > 5
-        nm_assert(same == _utils_hashtable_equal(hash_b, hash_a, cmp_values, user_data));
-#endif
-        if (same)
-            return 0;
-    }
-
-    size2 = ((gsize) size) * 2u;
-    if (size2 > 600u / sizeof(HashTableCmpData)) {
-        cmp_array_free = g_new(HashTableCmpData, size2);
-        cmp_array_a    = cmp_array_free;
-    } else
-        cmp_array_a = g_newa(HashTableCmpData, size2);
-    cmp_array_b = &cmp_array_a[size];
-
-    i = 0;
-    g_hash_table_iter_init(&h, hash_a);
-    while (g_hash_table_iter_next(&h, &i_key, &i_val)) {
-        nm_assert(i < size);
-        cmp_array_a[i++] = (HashTableCmpData){
-            .key = i_key,
-            .val = i_val,
-        };
-    }
-    nm_assert(i == size);
-
-    i = 0;
-    g_hash_table_iter_init(&h, hash_b);
-    while (g_hash_table_iter_next(&h, &i_key, &i_val)) {
-        nm_assert(i < size);
-        cmp_array_b[i++] = (HashTableCmpData){
-            .key = i_key,
-            .val = i_val,
-        };
-    }
-    nm_assert(i == size);
-
-    g_qsort_with_data(cmp_array_a,
-                      size,
-                      sizeof(HashTableCmpData),
-                      _hashtable_cmp_func,
-                      &((HashTableUserData){
-                          .cmp_keys  = cmp_keys,
-                          .user_data = user_data,
-                      }));
-
-    g_qsort_with_data(cmp_array_b,
-                      size,
-                      sizeof(HashTableCmpData),
-                      _hashtable_cmp_func,
-                      &((HashTableUserData){
-                          .cmp_keys  = cmp_keys,
-                          .user_data = user_data,
-                      }));
-
-    for (i = 0; i < size; i++) {
-        NM_CMP_RETURN(cmp_keys(cmp_array_a[i].key, cmp_array_b[i].key, user_data));
-    }
-
-    if (cmp_values) {
-        for (i = 0; i < size; i++) {
-            NM_CMP_RETURN(cmp_values(cmp_array_a[i].val, cmp_array_b[i].val, user_data));
-        }
-    }
-
-    /* the fast-precheck should have already told that the arrays are equal. */
-    nm_assert(!do_fast_precheck);
-
-    return 0;
-}
-
-char **
-nm_utils_strv_make_deep_copied(const char **strv)
-{
-    gsize i;
-
-    /* it takes a strv list, and copies each
-     * strings. Note that this updates @strv *in-place*
-     * and returns it. */
-
-    if (!strv)
-        return NULL;
-    for (i = 0; strv[i]; i++)
-        strv[i] = g_strdup(strv[i]);
-
-    return (char **) strv;
-}
-
-char **
-nm_utils_strv_make_deep_copied_n(const char **strv, gsize len)
-{
-    gsize i;
-
-    /* it takes a strv array with len elements, and copies each
-     * strings. Note that this updates @strv *in-place*
-     * and returns it. */
-
-    if (!strv)
-        return NULL;
-    for (i = 0; i < len; i++)
-        strv[i] = g_strdup(strv[i]);
-
-    return (char **) strv;
-}
-
-/**
- * @strv: the strv array to copy. It may be %NULL if @len
- *   is negative or zero (in which case %NULL will be returned).
- * @len: the length of strings in @str. If negative, strv is assumed
- *   to be a NULL terminated array.
- * @deep_copied: if %TRUE, clones the individual strings. In that case,
- *   the returned array must be freed with g_strfreev(). Otherwise, the
- *   strings themself are not copied. You must take care of who owns the
- *   strings yourself.
- *
- * Like g_strdupv(), with two differences:
- *
- * - accepts a @len parameter for non-null terminated strv array.
- *
- * - this never returns an empty strv array, but always %NULL if
- *   there are no strings.
- *
- * Note that if @len is non-negative, then it still must not
- * contain any %NULL pointers within the first @len elements.
- * Otherwise, you would leak elements if you try to free the
- * array with g_strfreev(). Allowing that would be error prone.
- *
- * Returns: (transfer full): a clone of the strv array. Always
- *   %NULL terminated. Depending on @deep_copied, the strings are
- *   cloned or not.
- */
-char **
-_nm_utils_strv_dup(const char *const *strv, gssize len, gboolean deep_copied)
-{
-    gsize  i, l;
-    char **v;
-
-    if (len < 0)
-        l = NM_PTRARRAY_LEN(strv);
-    else
-        l = len;
-    if (l == 0) {
-        /* this function never returns an empty strv array. If you
-         * need that, handle it yourself. */
-        return NULL;
-    }
-
-    v = g_new(char *, l + 1);
-    for (i = 0; i < l; i++) {
-        if (G_UNLIKELY(!strv[i])) {
-            /* NULL strings are not allowed. Clear the remainder of the array
-             * and return it (with assertion failure). */
-            l++;
-            for (; i < l; i++)
-                v[i] = NULL;
-            g_return_val_if_reached(v);
-        }
-
-        if (deep_copied)
-            v[i] = g_strdup(strv[i]);
-        else
-            v[i] = (char *) strv[i];
-    }
-    v[l] = NULL;
-    return v;
-}
-
-const char **
-_nm_utils_strv_dup_packed(const char *const *strv, gssize len)
-
-{
-    gs_free gsize *str_len_free = NULL;
-    gsize *        str_len;
-    const char **  result;
-    gsize          mem_len;
-    gsize          pre_len;
-    gsize          len2;
-    char *         sbuf;
-    gsize          i;
-
-    nm_assert(len >= -1);
-
-    if (G_LIKELY(len < 0)) {
-        if (!strv || !strv[0]) {
-            /* This function never returns an empty strv array. If you need that, handle it
-             * yourself. */
-            return NULL;
-        }
-        len2 = NM_PTRARRAY_LEN(strv);
-    } else {
-        if (len == 0)
-            return NULL;
-        len2 = len;
-    }
-
-    if (len2 > 300u / sizeof(gsize)) {
-        str_len_free = g_new(gsize, len2);
-        str_len      = str_len_free;
-    } else
-        str_len = g_newa(gsize, len2);
-
-    mem_len = 0;
-    for (i = 0; i < len2; i++) {
-        gsize l;
-
-        if (G_LIKELY(strv[i]))
-            l = strlen(strv[i]) + 1u;
-        else
-            l = 0;
-        str_len[i] = l;
-        mem_len += l;
-    }
-
-    pre_len = sizeof(const char *) * (len2 + 1u);
-
-    result = g_malloc(pre_len + mem_len);
-    sbuf   = &(((char *) result)[pre_len]);
-    for (i = 0; i < len2; i++) {
-        gsize l;
-
-        if (G_UNLIKELY(!strv[i])) {
-            /* Technically there is no problem with accepting NULL strings. But that
-             * does not really result in a strv array, and likely this only happens due
-             * to a bug. We want to catch such bugs by asserting.
-             *
-             * We clear the remainder of the buffer and fail with an assertion. */
-            len2++;
-            for (; i < len2; i++)
-                result[i] = NULL;
-            g_return_val_if_reached(result);
-        }
-
-        result[i] = sbuf;
-
-        l = str_len[i];
-        memcpy(sbuf, strv[i], l);
-        sbuf += l;
-    }
-    result[i] = NULL;
-    nm_assert(i == len2);
-    nm_assert(sbuf == (&((const char *) result)[pre_len]) + mem_len);
-
-    return result;
-}
-
-/*****************************************************************************/
-
-gssize
-nm_utils_ptrarray_find_binary_search(gconstpointer *  list,
-                                     gsize            len,
-                                     gconstpointer    needle,
-                                     GCompareDataFunc cmpfcn,
-                                     gpointer         user_data,
-                                     gssize *         out_idx_first,
-                                     gssize *         out_idx_last)
-{
-    gssize imin, imax, imid, i2min, i2max, i2mid;
-    int    cmp;
-
-    g_return_val_if_fail(list || !len, ~((gssize) 0));
-    g_return_val_if_fail(cmpfcn, ~((gssize) 0));
-
-    imin = 0;
-    if (len > 0) {
-        imax = len - 1;
-
-        while (imin <= imax) {
-            imid = imin + (imax - imin) / 2;
-
-            cmp = cmpfcn(list[imid], needle, user_data);
-            if (cmp == 0) {
-                /* we found a matching entry at index imid.
-                 *
-                 * Does the caller request the first/last index as well (in case that
-                 * there are multiple entries which compare equal). */
-
-                if (out_idx_first) {
-                    i2min = imin;
-                    i2max = imid + 1;
-                    while (i2min <= i2max) {
-                        i2mid = i2min + (i2max - i2min) / 2;
-
-                        cmp = cmpfcn(list[i2mid], needle, user_data);
-                        if (cmp == 0)
-                            i2max = i2mid - 1;
-                        else {
-                            nm_assert(cmp < 0);
-                            i2min = i2mid + 1;
-                        }
-                    }
-                    *out_idx_first = i2min;
-                }
-                if (out_idx_last) {
-                    i2min = imid + 1;
-                    i2max = imax;
-                    while (i2min <= i2max) {
-                        i2mid = i2min + (i2max - i2min) / 2;
-
-                        cmp = cmpfcn(list[i2mid], needle, user_data);
-                        if (cmp == 0)
-                            i2min = i2mid + 1;
-                        else {
-                            nm_assert(cmp > 0);
-                            i2max = i2mid - 1;
-                        }
-                    }
-                    *out_idx_last = i2min - 1;
-                }
-                return imid;
-            }
-
-            if (cmp < 0)
-                imin = imid + 1;
-            else
-                imax = imid - 1;
-        }
-    }
-
-    /* return the inverse of @imin. This is a negative number, but
-     * also is ~imin the position where the value should be inserted. */
-    imin = ~imin;
-    NM_SET_OUT(out_idx_first, imin);
-    NM_SET_OUT(out_idx_last, imin);
-    return imin;
-}
-
-/*****************************************************************************/
-
-/**
- * nm_utils_array_find_binary_search:
- * @list: the list to search. It must be sorted according to @cmpfcn ordering.
- * @elem_size: the size in bytes of each element in the list
- * @len: the number of elements in @list
- * @needle: the value that is searched
- * @cmpfcn: the compare function. The elements @list are passed as first
- *   argument to @cmpfcn, while @needle is passed as second. Usually, the
- *   needle is the same data type as inside the list, however, that is
- *   not necessary, as long as @cmpfcn takes care to cast the two arguments
- *   accordingly.
- * @user_data: optional argument passed to @cmpfcn
- *
- * Performs binary search for @needle in @list. On success, returns the
- * (non-negative) index where the compare function found the searched element.
- * On success, it returns a negative value. Note that the return negative value
- * is the bitwise inverse of the position where the element should be inserted.
- *
- * If the list contains multiple matching elements, an arbitrary index is
- * returned.
- *
- * Returns: the index to the element in the list, or the (negative, bitwise inverted)
- *   position where it should be.
- */
-gssize
-nm_utils_array_find_binary_search(gconstpointer    list,
-                                  gsize            elem_size,
-                                  gsize            len,
-                                  gconstpointer    needle,
-                                  GCompareDataFunc cmpfcn,
-                                  gpointer         user_data)
-{
-    gssize imin, imax, imid;
-    int    cmp;
-
-    g_return_val_if_fail(list || !len, ~((gssize) 0));
-    g_return_val_if_fail(cmpfcn, ~((gssize) 0));
-    g_return_val_if_fail(elem_size > 0, ~((gssize) 0));
-
-    imin = 0;
-    if (len == 0)
-        return ~imin;
-
-    imax = len - 1;
-
-    while (imin <= imax) {
-        imid = imin + (imax - imin) / 2;
-
-        cmp = cmpfcn(&((const char *) list)[elem_size * imid], needle, user_data);
-        if (cmp == 0)
-            return imid;
-
-        if (cmp < 0)
-            imin = imid + 1;
-        else
-            imax = imid - 1;
-    }
-
-    /* return the inverse of @imin. This is a negative number, but
-     * also is ~imin the position where the value should be inserted. */
-    return ~imin;
-}
-
-/*****************************************************************************/
-
-/**
- * nm_utils_get_start_time_for_pid:
- * @pid: the process identifier
- * @out_state: return the state character, like R, S, Z. See `man 5 proc`.
- * @out_ppid: parent process id
- *
- * Originally copied from polkit source (src/polkit/polkitunixprocess.c)
- * and adjusted.
- *
- * Returns: the timestamp when the process started (by parsing /proc/$PID/stat).
- * If an error occurs (e.g. the process does not exist), 0 is returned.
- *
- * The returned start time counts since boot, in the unit HZ (with HZ usually being (1/100) seconds)
- **/
-guint64
-nm_utils_get_start_time_for_pid(pid_t pid, char *out_state, pid_t *out_ppid)
-{
-    guint64              start_time;
-    char                 filename[256];
-    gs_free char *       contents = NULL;
-    size_t               length;
-    gs_free const char **tokens = NULL;
-    char *               p;
-    char                 state = ' ';
-    gint64               ppid  = 0;
-
-    start_time = 0;
-    contents   = NULL;
-
-    g_return_val_if_fail(pid > 0, 0);
-
-    G_STATIC_ASSERT_EXPR(sizeof(GPid) >= sizeof(pid_t));
-
-    nm_sprintf_buf(filename, "/proc/%" G_PID_FORMAT "/stat", (GPid) pid);
-
-    if (!g_file_get_contents(filename, &contents, &length, NULL))
-        goto fail;
-
-    /* start time is the token at index 19 after the '(process name)' entry - since only this
-     * field can contain the ')' character, search backwards for this to avoid malicious
-     * processes trying to fool us
-     */
-    p = strrchr(contents, ')');
-    if (!p)
-        goto fail;
-    p += 2; /* skip ') ' */
-    if (p - contents >= (int) length)
-        goto fail;
-
-    state = p[0];
-
-    tokens = nm_utils_strsplit_set(p, " ");
-
-    if (NM_PTRARRAY_LEN(tokens) < 20)
-        goto fail;
-
-    if (out_ppid) {
-        ppid = _nm_utils_ascii_str_to_int64(tokens[1], 10, 1, G_MAXINT, 0);
-        if (ppid == 0)
-            goto fail;
-    }
-
-    start_time = _nm_utils_ascii_str_to_int64(tokens[19], 10, 1, G_MAXINT64, 0);
-    if (start_time == 0)
-        goto fail;
-
-    NM_SET_OUT(out_state, state);
-    NM_SET_OUT(out_ppid, ppid);
-    return start_time;
-
-fail:
-    NM_SET_OUT(out_state, ' ');
-    NM_SET_OUT(out_ppid, 0);
-    return 0;
-}
-
-/*****************************************************************************/
-
-/**
- * _nm_utils_strv_sort:
- * @strv: pointer containing strings that will be sorted
- *   in-place, %NULL is allowed, unless @len indicates
- *   that there are more elements.
- * @len: the number of elements in strv. If negative,
- *   strv must be a NULL terminated array and the length
- *   will be calculated first. If @len is a positive
- *   number, @strv is allowed to contain %NULL strings
- *   too.
- *
- * Ascending sort of the array @strv inplace, using plain strcmp() string
- * comparison.
- */
-void
-_nm_utils_strv_sort(const char **strv, gssize len)
-{
-    GCompareDataFunc cmp;
-    gsize            l;
-
-    if (len < 0) {
-        l   = NM_PTRARRAY_LEN(strv);
-        cmp = nm_strcmp_p_with_data;
-    } else {
-        l   = len;
-        cmp = nm_strcmp0_p_with_data;
-    }
-
-    if (l <= 1)
-        return;
-
-    nm_assert(l <= (gsize) G_MAXINT);
-
-    g_qsort_with_data(strv, l, sizeof(const char *), cmp, NULL);
-}
-
-/**
- * _nm_utils_strv_cmp_n:
- * @strv1: a string array
- * @len1: the length of @strv1, or -1 for NULL terminated array.
- * @strv2: a string array
- * @len2: the length of @strv2, or -1 for NULL terminated array.
- *
- * Note that
- *   - len == -1 && strv == NULL
- * is treated like a %NULL argument and compares differently from
- * other arrays.
- *
- * Note that an empty array can be represented as
- *   - len == -1 &&  strv && !strv[0]
- *   - len ==  0 && !strv
- *   - len ==  0 &&  strv
- * These 3 forms all compare equal.
- * It also means, if length is 0, then it is permissible for strv to be %NULL.
- *
- * The strv arrays may contain %NULL strings (if len is positive).
- *
- * Returns: 0 if the arrays are equal (using strcmp).
- **/
-int
-_nm_utils_strv_cmp_n(const char *const *strv1, gssize len1, const char *const *strv2, gssize len2)
-{
-    gsize n, n2;
-
-    if (len1 < 0) {
-        if (!strv1)
-            return (len2 < 0 && !strv2) ? 0 : -1;
-        n = NM_PTRARRAY_LEN(strv1);
-    } else
-        n = len1;
-
-    if (len2 < 0) {
-        if (!strv2)
-            return 1;
-        n2 = NM_PTRARRAY_LEN(strv2);
-    } else
-        n2 = len2;
-
-    NM_CMP_DIRECT(n, n2);
-    for (; n > 0; n--, strv1++, strv2++)
-        NM_CMP_DIRECT_STRCMP0(*strv1, *strv2);
-    return 0;
-}
-
-/*****************************************************************************/
-
-/**
- * nm_utils_g_slist_find_str:
- * @list: the #GSList with NUL terminated strings to search
- * @needle: the needle string to look for.
- *
- * Search the list for @needle and return the first found match
- * (or %NULL if not found). Uses strcmp() for finding the first matching
- * element.
- *
- * Returns: the #GSList element with @needle as string value or
- *   %NULL if not found.
- */
-GSList *
-nm_utils_g_slist_find_str(const GSList *list, const char *needle)
-{
-    nm_assert(needle);
-
-    for (; list; list = list->next) {
-        nm_assert(list->data);
-        if (nm_streq(list->data, needle))
-            return (GSList *) list;
-    }
-    return NULL;
-}
-
-/**
- * nm_utils_g_slist_strlist_cmp:
- * @a: the left #GSList of strings
- * @b: the right #GSList of strings to compare.
- *
- * Compares two string lists. The data elements are compared with
- * strcmp(), allowing %NULL elements.
- *
- * Returns: 0, 1, or -1, depending on how the lists compare.
- */
-int
-nm_utils_g_slist_strlist_cmp(const GSList *a, const GSList *b)
-{
-    while (TRUE) {
-        if (!a)
-            return !b ? 0 : -1;
-        if (!b)
-            return 1;
-        NM_CMP_DIRECT_STRCMP0(a->data, b->data);
-        a = a->next;
-        b = b->next;
-    }
-}
-
-char *
-nm_utils_g_slist_strlist_join(const GSList *a, const char *separator)
-{
-    GString *str = NULL;
-
-    if (!a)
-        return NULL;
-
-    for (; a; a = a->next) {
-        if (!str)
-            str = g_string_new(NULL);
-        else
-            g_string_append(str, separator);
-        g_string_append(str, a->data);
-    }
-    return g_string_free(str, FALSE);
-}
-
-/*****************************************************************************/
-
-NMUtilsUserData *
-_nm_utils_user_data_pack(int nargs, gconstpointer *args)
-{
-    int       i;
-    gpointer *data;
-
-    nm_assert(nargs > 0);
-    nm_assert(args);
-
-    data = g_slice_alloc(((gsize) nargs) * sizeof(gconstpointer));
-    for (i = 0; i < nargs; i++)
-        data[i] = (gpointer) args[i];
-    return (NMUtilsUserData *) data;
-}
-
-void
-_nm_utils_user_data_unpack(NMUtilsUserData *user_data, int nargs, ...)
-{
-    gpointer *data = (gpointer *) user_data;
-    va_list   ap;
-    int       i;
-
-    nm_assert(data);
-    nm_assert(nargs > 0);
-
-    va_start(ap, nargs);
-    for (i = 0; i < nargs; i++) {
-        gpointer *dst;
-
-        dst = va_arg(ap, gpointer *);
-        nm_assert(dst);
-
-        *dst = data[i];
-    }
-    va_end(ap);
-
-    g_slice_free1(((gsize) nargs) * sizeof(gconstpointer), data);
-}
-
-/*****************************************************************************/
-
-typedef struct {
-    gpointer                    callback_user_data;
-    GCancellable *              cancellable;
-    GSource *                   source;
-    NMUtilsInvokeOnIdleCallback callback;
-    gulong                      cancelled_id;
-} InvokeOnIdleData;
-
-static gboolean
-_nm_utils_invoke_on_idle_cb_idle(gpointer user_data)
-{
-    InvokeOnIdleData *data = user_data;
-
-    nm_clear_g_signal_handler(data->cancellable, &data->cancelled_id);
-
-    data->callback(data->callback_user_data, data->cancellable);
-
-    nm_g_object_unref(data->cancellable);
-    g_source_destroy(data->source);
-    nm_g_slice_free(data);
-    return G_SOURCE_REMOVE;
-}
-
-static void
-_nm_utils_invoke_on_idle_cb_cancelled(GCancellable *cancellable, InvokeOnIdleData *data)
-{
-    /* on cancellation, we invoke the callback synchronously. */
-    nm_clear_g_signal_handler(data->cancellable, &data->cancelled_id);
-    nm_clear_g_source_inst(&data->source);
-    data->callback(data->callback_user_data, data->cancellable);
-    nm_g_object_unref(data->cancellable);
-    nm_g_slice_free(data);
-}
-
-static void
-_nm_utils_invoke_on_idle_start(gboolean                    use_timeout,
-                               guint                       timeout_msec,
-                               GCancellable *              cancellable,
-                               NMUtilsInvokeOnIdleCallback callback,
-                               gpointer                    callback_user_data)
-{
-    InvokeOnIdleData *data;
-    GSource *         source;
-
-    g_return_if_fail(callback);
-
-    data  = g_slice_new(InvokeOnIdleData);
-    *data = (InvokeOnIdleData){
-        .callback           = callback,
-        .callback_user_data = callback_user_data,
-        .cancellable        = nm_g_object_ref(cancellable),
-        .cancelled_id       = 0,
-    };
-
-    if (cancellable) {
-        if (g_cancellable_is_cancelled(cancellable)) {
-            /* the cancellable is already cancelled. We ignore the timeout
-             * and always schedule an idle action. */
-            use_timeout = FALSE;
-        } else {
-            /* if we are passed a non-cancelled cancellable, we register to the "cancelled"
-             * signal an invoke the callback synchronously (from the signal handler).
-             *
-             * We don't do that,
-             *  - if the cancellable is already cancelled (because we don't want to invoke
-             *    the callback synchronously from the caller).
-             *  - if we have no cancellable at hand. */
-            data->cancelled_id = g_signal_connect(cancellable,
-                                                  "cancelled",
-                                                  G_CALLBACK(_nm_utils_invoke_on_idle_cb_cancelled),
-                                                  data);
-        }
-    }
-
-    if (use_timeout) {
-        source = nm_g_timeout_source_new(timeout_msec,
-                                         G_PRIORITY_DEFAULT,
-                                         _nm_utils_invoke_on_idle_cb_idle,
-                                         data,
-                                         NULL);
-    } else {
-        source =
-            nm_g_idle_source_new(G_PRIORITY_DEFAULT, _nm_utils_invoke_on_idle_cb_idle, data, NULL);
-    }
-
-    /* use the current thread default context. */
-    g_source_attach(source, g_main_context_get_thread_default());
-
-    data->source = source;
-}
-
-void
-nm_utils_invoke_on_idle(GCancellable *              cancellable,
-                        NMUtilsInvokeOnIdleCallback callback,
-                        gpointer                    callback_user_data)
-{
-    _nm_utils_invoke_on_idle_start(FALSE, 0, cancellable, callback, callback_user_data);
-}
-
-void
-nm_utils_invoke_on_timeout(guint                       timeout_msec,
-                           GCancellable *              cancellable,
-                           NMUtilsInvokeOnIdleCallback callback,
-                           gpointer                    callback_user_data)
-{
-    _nm_utils_invoke_on_idle_start(TRUE, timeout_msec, cancellable, callback, callback_user_data);
-}
-
-/*****************************************************************************/
-
-int
-nm_utils_getpagesize(void)
-{
-    static volatile int val = 0;
-    long                l;
-    int                 v;
-
-    v = g_atomic_int_get(&val);
-
-    if (G_UNLIKELY(v == 0)) {
-        l = sysconf(_SC_PAGESIZE);
-
-        g_return_val_if_fail(l > 0 && l < G_MAXINT, 4 * 1024);
-
-        v = (int) l;
-        if (!g_atomic_int_compare_and_exchange(&val, 0, v)) {
-            v = g_atomic_int_get(&val);
-            g_return_val_if_fail(v > 0, 4 * 1024);
-        }
-    }
-
-    nm_assert(v > 0);
-#if NM_MORE_ASSERTS > 5
-    nm_assert(v == getpagesize());
-    nm_assert(v == sysconf(_SC_PAGESIZE));
-#endif
-
-    return v;
-}
-
-gboolean
-nm_utils_memeqzero(gconstpointer data, gsize length)
-{
-    const unsigned char *p = data;
-    int                  len;
-
-    /* Taken from https://github.com/rustyrussell/ccan/blob/9d2d2c49f053018724bcc6e37029da10b7c3d60d/ccan/mem/mem.c#L92,
-     * CC-0 licensed. */
-
-    /* Check first 16 bytes manually */
-    for (len = 0; len < 16; len++) {
-        if (!length)
-            return TRUE;
-        if (*p)
-            return FALSE;
-        p++;
-        length--;
-    }
-
-    /* Now we know that's zero, memcmp with self. */
-    return memcmp(data, p, length) == 0;
-}
-
-/**
- * nm_utils_bin2hexstr_full:
- * @addr: pointer of @length bytes. If @length is zero, this may
- *   also be %NULL.
- * @length: number of bytes in @addr. May also be zero, in which
- *   case this will return an empty string.
- * @delimiter: either '\0', otherwise the output string will have the
- *   given delimiter character between each two hex numbers.
- * @upper_case: if TRUE, use upper case ASCII characters for hex.
- * @out: if %NULL, the function will allocate a new buffer of
- *   either (@length*2+1) or (@length*3) bytes, depending on whether
- *   a @delimiter is specified. In that case, the allocated buffer will
- *   be returned and must be freed by the caller.
- *   If not %NULL, the buffer must already be preallocated and contain
- *   at least (@length*2+1) or (@length*3) bytes, depending on the delimiter.
- *   If @length is zero, then of course at least one byte will be allocated
- *   or @out (if given) must contain at least room for the trailing NUL byte.
- *
- * Returns: the binary value converted to a hex string. If @out is given,
- *   this always returns @out. If @out is %NULL, a newly allocated string
- *   is returned. This never returns %NULL, for buffers of length zero
- *   an empty string is returned.
- */
-char *
-nm_utils_bin2hexstr_full(gconstpointer addr,
-                         gsize         length,
-                         char          delimiter,
-                         gboolean      upper_case,
-                         char *        out)
-{
-    const guint8 *in     = addr;
-    const char *  LOOKUP = upper_case ? "0123456789ABCDEF" : "0123456789abcdef";
-    char *        out0;
-
-    if (out)
-        out0 = out;
-    else {
-        out0 = out =
-            g_new(char, length == 0 ? 1u : (delimiter == '\0' ? length * 2u + 1u : length * 3u));
-    }
-
-    /* @out must contain at least @length*3 bytes if @delimiter is set,
-     * otherwise, @length*2+1. */
-
-    if (length > 0) {
-        nm_assert(in);
-        for (;;) {
-            const guint8 v = *in++;
-
-            *out++ = LOOKUP[v >> 4];
-            *out++ = LOOKUP[v & 0x0F];
-            length--;
-            if (!length)
-                break;
-            if (delimiter)
-                *out++ = delimiter;
-        }
-    }
-
-    *out = '\0';
-    return out0;
-}
-
-guint8 *
-nm_utils_hexstr2bin_full(const char *hexstr,
-                         gboolean    allow_0x_prefix,
-                         gboolean    delimiter_required,
-                         gboolean    hexdigit_pairs_required,
-                         const char *delimiter_candidates,
-                         gsize       required_len,
-                         guint8 *    buffer,
-                         gsize       buffer_len,
-                         gsize *     out_len)
-{
-    const char *in            = hexstr;
-    guint8 *    out           = buffer;
-    gboolean    delimiter_has = TRUE;
-    guint8      delimiter     = '\0';
-    gsize       len;
-
-    nm_assert(hexstr);
-    nm_assert(buffer);
-    nm_assert(required_len > 0 || out_len);
-
-    if (allow_0x_prefix && in[0] == '0' && in[1] == 'x')
-        in += 2;
-
-    while (TRUE) {
-        const guint8 d1 = in[0];
-        guint8       d2;
-        int          i1, i2;
-
-        i1 = nm_utils_hexchar_to_int(d1);
-        if (i1 < 0)
-            goto fail;
-
-        /* If there's no leading zero (ie "aa:b:cc") then fake it */
-        d2 = in[1];
-        if (d2 && (i2 = nm_utils_hexchar_to_int(d2)) >= 0) {
-            *out++ = (i1 << 4) + i2;
-            d2     = in[2];
-            if (!d2)
-                break;
-            in += 2;
-        } else {
-            /* Fake leading zero */
-            *out++ = i1;
-            if (!d2) {
-                if (!delimiter_has || hexdigit_pairs_required) {
-                    /* when using no delimiter, there must be pairs of hex chars */
-                    goto fail;
-                }
-                break;
-            } else if (hexdigit_pairs_required)
-                goto fail;
-            in += 1;
-        }
-
-        if (--buffer_len == 0)
-            goto fail;
-
-        if (delimiter_has) {
-            if (d2 != delimiter) {
-                if (delimiter)
-                    goto fail;
-                if (delimiter_candidates) {
-                    while (delimiter_candidates[0]) {
-                        if (delimiter_candidates++[0] == d2)
-                            delimiter = d2;
-                    }
-                }
-                if (!delimiter) {
-                    if (delimiter_required)
-                        goto fail;
-                    delimiter_has = FALSE;
-                    continue;
-                }
-            }
-            in++;
-        }
-    }
-
-    len = out - buffer;
-    if (required_len == 0 || len == required_len) {
-        NM_SET_OUT(out_len, len);
-        return buffer;
-    }
-
-fail:
-    NM_SET_OUT(out_len, 0);
-    return NULL;
-}
-
-guint8 *
-nm_utils_hexstr2bin_alloc(const char *hexstr,
-                          gboolean    allow_0x_prefix,
-                          gboolean    delimiter_required,
-                          const char *delimiter_candidates,
-                          gsize       required_len,
-                          gsize *     out_len)
-{
-    guint8 *buffer;
-    gsize   buffer_len, len;
-
-    if (G_UNLIKELY(!hexstr)) {
-        NM_SET_OUT(out_len, 0);
-        g_return_val_if_fail(hexstr, NULL);
-    }
-
-    nm_assert(required_len > 0 || out_len);
-
-    if (allow_0x_prefix && hexstr[0] == '0' && hexstr[1] == 'x')
-        hexstr += 2;
-
-    if (!hexstr[0])
-        goto fail;
-
-    if (required_len > 0)
-        buffer_len = required_len;
-    else
-        buffer_len = strlen(hexstr) / 2 + 3;
-
-    buffer = g_malloc(buffer_len);
-
-    if (nm_utils_hexstr2bin_full(hexstr,
-                                 FALSE,
-                                 delimiter_required,
-                                 FALSE,
-                                 delimiter_candidates,
-                                 required_len,
-                                 buffer,
-                                 buffer_len,
-                                 &len)) {
-        NM_SET_OUT(out_len, len);
-        return buffer;
-    }
-
-    g_free(buffer);
-
-fail:
-    NM_SET_OUT(out_len, 0);
-    return NULL;
-}
-
-/*****************************************************************************/
-
-GVariant *
-nm_utils_gvariant_vardict_filter(GVariant *src,
-                                 gboolean (*filter_fcn)(const char *key,
-                                                        GVariant *  val,
-                                                        char **     out_key,
-                                                        GVariant ** out_val,
-                                                        gpointer    user_data),
-                                 gpointer user_data)
-{
-    GVariantIter    iter;
-    GVariantBuilder builder;
-    const char *    key;
-    GVariant *      val;
-
-    g_return_val_if_fail(src && g_variant_is_of_type(src, G_VARIANT_TYPE_VARDICT), NULL);
-    g_return_val_if_fail(filter_fcn, NULL);
-
-    g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
-
-    g_variant_iter_init(&iter, src);
-    while (g_variant_iter_next(&iter, "{&sv}", &key, &val)) {
-        _nm_unused gs_unref_variant GVariant *val_free = val;
-        gs_free char *                        key2     = NULL;
-        gs_unref_variant GVariant *val2                = NULL;
-
-        if (filter_fcn(key, val, &key2, &val2, user_data)) {
-            g_variant_builder_add(&builder, "{sv}", key2 ?: key, val2 ?: val);
-        }
-    }
-
-    return g_variant_builder_end(&builder);
-}
-
-static gboolean
-_gvariant_vardict_filter_drop_one(const char *key,
-                                  GVariant *  val,
-                                  char **     out_key,
-                                  GVariant ** out_val,
-                                  gpointer    user_data)
-{
-    return !nm_streq(key, user_data);
-}
-
-GVariant *
-nm_utils_gvariant_vardict_filter_drop_one(GVariant *src, const char *key)
-{
-    return nm_utils_gvariant_vardict_filter(src, _gvariant_vardict_filter_drop_one, (gpointer) key);
-}
-
-/*****************************************************************************/
-
-static gboolean
-debug_key_matches(const char *key, const char *token, guint length)
-{
-    /* may not call GLib functions: see note in g_parse_debug_string() */
-    for (; length; length--, key++, token++) {
-        char k = (*key == '_') ? '-' : g_ascii_tolower(*key);
-        char t = (*token == '_') ? '-' : g_ascii_tolower(*token);
-
-        if (k != t)
-            return FALSE;
-    }
-
-    return *key == '\0';
-}
-
-/**
- * nm_utils_parse_debug_string:
- * @string: the string to parse
- * @keys: the debug keys
- * @nkeys: number of entries in @keys
- *
- * Similar to g_parse_debug_string(), but does not special
- * case "help" or "all".
- *
- * Returns: the flags
- */
-guint
-nm_utils_parse_debug_string(const char *string, const GDebugKey *keys, guint nkeys)
-{
-    guint       i;
-    guint       result = 0;
-    const char *q;
-
-    if (string == NULL)
-        return 0;
-
-    while (*string) {
-        q = strpbrk(string, ":;, \t");
-        if (!q)
-            q = string + strlen(string);
-
-        for (i = 0; i < nkeys; i++) {
-            if (debug_key_matches(keys[i].key, string, q - string))
-                result |= keys[i].value;
-        }
-
-        string = q;
-        if (*string)
-            string++;
-    }
-
-    return result;
-}
-
-/*****************************************************************************/
-
-GSource *
-nm_g_idle_source_new(int            priority,
-                     GSourceFunc    func,
-                     gpointer       user_data,
-                     GDestroyNotify destroy_notify)
-{
-    GSource *source;
-
-    source = g_idle_source_new();
-    if (priority != G_PRIORITY_DEFAULT)
-        g_source_set_priority(source, priority);
-    g_source_set_callback(source, func, user_data, destroy_notify);
-    return source;
-}
-
-GSource *
-nm_g_timeout_source_new(guint          timeout_msec,
-                        int            priority,
-                        GSourceFunc    func,
-                        gpointer       user_data,
-                        GDestroyNotify destroy_notify)
-{
-    GSource *source;
-
-    source = g_timeout_source_new(timeout_msec);
-    if (priority != G_PRIORITY_DEFAULT)
-        g_source_set_priority(source, priority);
-    g_source_set_callback(source, func, user_data, destroy_notify);
-    return source;
-}
-
-GSource *
-nm_g_timeout_source_new_seconds(guint          timeout_sec,
-                                int            priority,
-                                GSourceFunc    func,
-                                gpointer       user_data,
-                                GDestroyNotify destroy_notify)
-{
-    GSource *source;
-
-    source = g_timeout_source_new_seconds(timeout_sec);
-    if (priority != G_PRIORITY_DEFAULT)
-        g_source_set_priority(source, priority);
-    g_source_set_callback(source, func, user_data, destroy_notify);
-    return source;
-}
-
-GSource *
-nm_g_unix_signal_source_new(int            signum,
-                            int            priority,
-                            GSourceFunc    handler,
-                            gpointer       user_data,
-                            GDestroyNotify notify)
-{
-    GSource *source;
-
-    source = g_unix_signal_source_new(signum);
-
-    if (priority != G_PRIORITY_DEFAULT)
-        g_source_set_priority(source, priority);
-    g_source_set_callback(source, handler, user_data, notify);
-    return source;
-}
-
-GSource *
-nm_g_unix_fd_source_new(int          fd,
-                        GIOCondition io_condition,
-                        int          priority,
-                        gboolean (*source_func)(int fd, GIOCondition condition, gpointer user_data),
-                        gpointer       user_data,
-                        GDestroyNotify destroy_notify)
-{
-    GSource *source;
-
-    source = g_unix_fd_source_new(fd, io_condition);
-
-    if (priority != G_PRIORITY_DEFAULT)
-        g_source_set_priority(source, priority);
-    g_source_set_callback(source, G_SOURCE_FUNC(source_func), user_data, destroy_notify);
-    return source;
-}
-
-/*****************************************************************************/
-
-#define _CTX_LOG(fmt, ...)                                                                       \
-    G_STMT_START                                                                                 \
-    {                                                                                            \
-        if (FALSE) {                                                                             \
-            gint64 _ts = g_get_monotonic_time() / 100;                                           \
-                                                                                                 \
-            g_printerr(">>>> [%" G_GINT64_FORMAT ".%05" G_GINT64_FORMAT "] [src:%p]: " fmt "\n", \
-                       _ts / 10000,                                                              \
-                       _ts % 10000,                                                              \
-                       (ctx_src),                                                                \
-                       ##__VA_ARGS__);                                                           \
-        }                                                                                        \
-    }                                                                                            \
-    G_STMT_END
-
-typedef struct {
-    int   fd;
-    guint events;
-    guint registered_events;
-    union {
-        int  one;
-        int *many;
-    } idx;
-    gpointer tag;
-    bool     stale : 1;
-    bool     has_many_idx : 1;
-} PollData;
-
-typedef struct {
-    GSource       source;
-    GMainContext *context;
-    GHashTable *  fds;
-    GPollFD *     fds_arr;
-    guint         fds_len;
-    int           max_priority;
-    bool          acquired : 1;
-} CtxIntegSource;
-
-static void
-_poll_data_free(gpointer user_data)
-{
-    PollData *poll_data = user_data;
-
-    if (poll_data->has_many_idx)
-        g_free(poll_data->idx.many);
-    nm_g_slice_free(poll_data);
-}
-
-static void
-_ctx_integ_source_reacquire(CtxIntegSource *ctx_src)
-{
-    if (G_LIKELY(ctx_src->acquired && g_main_context_is_owner(ctx_src->context)))
-        return;
-
-    /* the parent context now iterates on a different thread.
-     * We need to release and reacquire the inner context. */
-
-    if (ctx_src->acquired)
-        g_main_context_release(ctx_src->context);
-
-    if (G_UNLIKELY(!g_main_context_acquire(ctx_src->context))) {
-        /* Nobody is supposed to reacquire the context while we use it. This is a bug
-         * of the user. */
-        ctx_src->acquired = FALSE;
-        g_return_if_reached();
-    }
-    ctx_src->acquired = TRUE;
-}
-
-static gboolean
-_ctx_integ_source_prepare(GSource *source, int *out_timeout)
-{
-    CtxIntegSource *ctx_src = ((CtxIntegSource *) source);
-    int             max_priority;
-    int             timeout = -1;
-    gboolean        any_ready;
-    GHashTableIter  h_iter;
-    PollData *      poll_data;
-    gboolean        fds_changed;
-    GPollFD         new_fds_stack[300u / sizeof(GPollFD)];
-    gs_free GPollFD *new_fds_heap = NULL;
-    GPollFD *        new_fds;
-    guint            new_fds_len;
-    guint            new_fds_alloc;
-    guint            i;
-
-    _CTX_LOG("prepare...");
-
-    _ctx_integ_source_reacquire(ctx_src);
-
-    any_ready = g_main_context_prepare(ctx_src->context, &max_priority);
-
-    new_fds_alloc = NM_MAX(G_N_ELEMENTS(new_fds_stack), ctx_src->fds_len);
-
-    if (new_fds_alloc > G_N_ELEMENTS(new_fds_stack)) {
-        new_fds_heap = g_new(GPollFD, new_fds_alloc);
-        new_fds      = new_fds_heap;
-    } else
-        new_fds = new_fds_stack;
-
-    for (;;) {
-        int l;
-
-        nm_assert(new_fds_alloc <= (guint) G_MAXINT);
-
-        l = g_main_context_query(ctx_src->context,
-                                 max_priority,
-                                 &timeout,
-                                 new_fds,
-                                 (int) new_fds_alloc);
-        nm_assert(l >= 0);
-
-        new_fds_len = (guint) l;
-
-        if (G_LIKELY(new_fds_len <= new_fds_alloc))
-            break;
-
-        new_fds_alloc = new_fds_len;
-        g_free(new_fds_heap);
-        new_fds_heap = g_new(GPollFD, new_fds_alloc);
-        new_fds      = new_fds_heap;
-    }
-
-    fds_changed = FALSE;
-    if (new_fds_len != ctx_src->fds_len)
-        fds_changed = TRUE;
-    else {
-        for (i = 0; i < new_fds_len; i++) {
-            if (new_fds[i].fd != ctx_src->fds_arr[i].fd
-                || new_fds[i].events != ctx_src->fds_arr[i].events) {
-                fds_changed = TRUE;
-                break;
-            }
-        }
-    }
-
-    if (G_UNLIKELY(fds_changed)) {
-        g_free(ctx_src->fds_arr);
-        ctx_src->fds_len = new_fds_len;
-        if (G_LIKELY(new_fds == new_fds_stack) || new_fds_alloc != new_fds_len)
-            ctx_src->fds_arr = nm_memdup(new_fds, sizeof(*new_fds) * new_fds_len);
-        else
-            ctx_src->fds_arr = g_steal_pointer(&new_fds_heap);
-
-        g_hash_table_iter_init(&h_iter, ctx_src->fds);
-        while (g_hash_table_iter_next(&h_iter, (gpointer *) &poll_data, NULL))
-            poll_data->stale = TRUE;
-
-        for (i = 0; i < ctx_src->fds_len; i++) {
-            const GPollFD *fd = &ctx_src->fds_arr[i];
-
-            poll_data = g_hash_table_lookup(ctx_src->fds, &fd->fd);
-
-            if (G_UNLIKELY(!poll_data)) {
-                poll_data  = g_slice_new(PollData);
-                *poll_data = (PollData){
-                    .fd                = fd->fd,
-                    .idx.one           = i,
-                    .has_many_idx      = FALSE,
-                    .events            = fd->events,
-                    .registered_events = 0,
-                    .tag               = NULL,
-                    .stale             = FALSE,
-                };
-                g_hash_table_add(ctx_src->fds, poll_data);
-                nm_assert(poll_data == g_hash_table_lookup(ctx_src->fds, &fd->fd));
-                continue;
-            }
-
-            if (G_LIKELY(poll_data->stale)) {
-                if (poll_data->has_many_idx) {
-                    g_free(poll_data->idx.many);
-                    poll_data->has_many_idx = FALSE;
-                }
-                poll_data->events  = fd->events;
-                poll_data->idx.one = i;
-                poll_data->stale   = FALSE;
-                continue;
-            }
-
-            /* How odd. We have duplicate FDs. In fact, currently g_main_context_query() always
-             * coalesces the FDs and this cannot happen. However, that is not documented behavior,
-             * so we should not rely on that. So we need to keep a list of indexes... */
-            poll_data->events |= fd->events;
-            if (!poll_data->has_many_idx) {
-                int idx0;
-
-                idx0                    = poll_data->idx.one;
-                poll_data->has_many_idx = TRUE;
-                poll_data->idx.many     = g_new(int, 4);
-                poll_data->idx.many[0]  = 2; /* number allocated */
-                poll_data->idx.many[1]  = 2; /* number used */
-                poll_data->idx.many[2]  = idx0;
-                poll_data->idx.many[3]  = i;
-            } else {
-                if (poll_data->idx.many[0] == poll_data->idx.many[1]) {
-                    poll_data->idx.many[0] *= 2;
-                    poll_data->idx.many =
-                        g_realloc(poll_data->idx.many, sizeof(int) * (2 + poll_data->idx.many[0]));
-                }
-                poll_data->idx.many[2 + poll_data->idx.many[1]] = i;
-                poll_data->idx.many[1]++;
-            }
-        }
-
-        g_hash_table_iter_init(&h_iter, ctx_src->fds);
-        while (g_hash_table_iter_next(&h_iter, (gpointer *) &poll_data, NULL)) {
-            if (poll_data->stale) {
-                nm_assert(poll_data->tag);
-                nm_assert(poll_data->events == poll_data->registered_events);
-                _CTX_LOG("prepare: remove poll fd=%d, events=0x%x",
-                         poll_data->fd,
-                         poll_data->events);
-                g_source_remove_unix_fd(&ctx_src->source, poll_data->tag);
-                g_hash_table_iter_remove(&h_iter);
-                continue;
-            }
-            if (!poll_data->tag) {
-                _CTX_LOG("prepare: add poll fd=%d, events=0x%x", poll_data->fd, poll_data->events);
-                poll_data->registered_events = poll_data->events;
-                poll_data->tag               = g_source_add_unix_fd(&ctx_src->source,
-                                                      poll_data->fd,
-                                                      poll_data->registered_events);
-                continue;
-            }
-            if (poll_data->registered_events != poll_data->events) {
-                _CTX_LOG("prepare: update poll fd=%d, events=0x%x",
-                         poll_data->fd,
-                         poll_data->events);
-                poll_data->registered_events = poll_data->events;
-                g_source_modify_unix_fd(&ctx_src->source,
-                                        poll_data->tag,
-                                        poll_data->registered_events);
-            }
-        }
-    }
-
-    NM_SET_OUT(out_timeout, timeout);
-    ctx_src->max_priority = max_priority;
-
-    _CTX_LOG("prepare: done, any-ready=%d, timeout=%d, max-priority=%d",
-             any_ready,
-             timeout,
-             max_priority);
-
-    /* we always need to poll, because we have some file descriptors. */
-    return FALSE;
-}
-
-static gboolean
-_ctx_integ_source_check(GSource *source)
-{
-    CtxIntegSource *ctx_src = ((CtxIntegSource *) source);
-    GHashTableIter  h_iter;
-    gboolean        some_ready;
-    PollData *      poll_data;
-
-    nm_assert(ctx_src->context);
-
-    _CTX_LOG("check");
-
-    _ctx_integ_source_reacquire(ctx_src);
-
-    g_hash_table_iter_init(&h_iter, ctx_src->fds);
-    while (g_hash_table_iter_next(&h_iter, (gpointer *) &poll_data, NULL)) {
-        guint revents;
-
-        revents = g_source_query_unix_fd(&ctx_src->source, poll_data->tag);
-        if (G_UNLIKELY(poll_data->has_many_idx)) {
-            int  num   = poll_data->idx.many[1];
-            int *p_idx = &poll_data->idx.many[2];
-
-            for (; num > 0; num--, p_idx++)
-                ctx_src->fds_arr[*p_idx].revents = revents;
-        } else
-            ctx_src->fds_arr[poll_data->idx.one].revents = revents;
-    }
-
-    nm_assert(ctx_src->fds_len <= (guint) G_MAXINT);
-
-    some_ready = g_main_context_check(ctx_src->context,
-                                      ctx_src->max_priority,
-                                      ctx_src->fds_arr,
-                                      (int) ctx_src->fds_len);
-
-    _CTX_LOG("check (some-ready=%d)...", some_ready);
-
-    return some_ready;
-}
-
-static gboolean
-_ctx_integ_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
-{
-    CtxIntegSource *ctx_src = ((CtxIntegSource *) source);
-
-    nm_assert(ctx_src->context);
-
-    _ctx_integ_source_reacquire(ctx_src);
-
-    _CTX_LOG("dispatch");
-
-    g_main_context_dispatch(ctx_src->context);
-
-    return G_SOURCE_CONTINUE;
-}
-
-static void
-_ctx_integ_source_finalize(GSource *source)
-{
-    CtxIntegSource *ctx_src = ((CtxIntegSource *) source);
-    GHashTableIter  h_iter;
-    PollData *      poll_data;
-
-    g_return_if_fail(ctx_src->context);
-
-    _CTX_LOG("finalize...");
-
-    g_hash_table_iter_init(&h_iter, ctx_src->fds);
-    while (g_hash_table_iter_next(&h_iter, (gpointer *) &poll_data, NULL)) {
-        nm_assert(poll_data->tag);
-        _CTX_LOG("prepare: remove poll fd=%d, events=0x%x", poll_data->fd, poll_data->events);
-        g_source_remove_unix_fd(&ctx_src->source, poll_data->tag);
-        g_hash_table_iter_remove(&h_iter);
-    }
-
-    nm_clear_pointer(&ctx_src->fds, g_hash_table_unref);
-    nm_clear_g_free(&ctx_src->fds_arr);
-    ctx_src->fds_len = 0;
-
-    if (ctx_src->acquired) {
-        ctx_src->acquired = FALSE;
-        g_main_context_release(ctx_src->context);
-    }
-
-    nm_clear_pointer(&ctx_src->context, g_main_context_unref);
-}
-
-static GSourceFuncs ctx_integ_source_funcs = {
-    .prepare  = _ctx_integ_source_prepare,
-    .check    = _ctx_integ_source_check,
-    .dispatch = _ctx_integ_source_dispatch,
-    .finalize = _ctx_integ_source_finalize,
-};
-
-/**
- * nm_utils_g_main_context_create_integrate_source:
- * @inner_context: the inner context that will be integrated to an
- *   outer #GMainContext.
- *
- * By integrating the inner context with an outer context, when iterating the outer
- * context sources on the inner context will be dispatched. Note that while the
- * created source exists, the @inner_context will be acquired. The user gets restricted
- * what to do with the inner context. In particular while the inner context is integrated,
- * the user should not acquire the inner context again or explicitly iterate it. What
- * the user of course still can (and wants to) do is attaching new sources to the inner
- * context.
- *
- * Note that GSource has a priority. While each context dispatches events based on
- * their source's priorities, the outer context dispatches to the inner context
- * only with one priority (the priority of the created source). That is, the sources
- * from the two contexts are kept separate and are not sorted by their priorities.
- *
- * Returns: a newly created GSource that should be attached to the
- *   outer context.
- */
-GSource *
-nm_utils_g_main_context_create_integrate_source(GMainContext *inner_context)
-{
-    CtxIntegSource *ctx_src;
-
-    g_return_val_if_fail(inner_context, NULL);
-
-    if (!g_main_context_acquire(inner_context)) {
-        /* We require to acquire the context while it's integrated. We need to keep it acquired
-         * for the entire duration.
-         *
-         * This is also necessary because g_source_attach() only wakes up the context, if
-         * the context is currently acquired. */
-        g_return_val_if_reached(NULL);
-    }
-
-    ctx_src = (CtxIntegSource *) g_source_new(&ctx_integ_source_funcs, sizeof(CtxIntegSource));
-
-    g_source_set_name(&ctx_src->source, "ContextIntegrateSource");
-
-    ctx_src->context  = g_main_context_ref(inner_context);
-    ctx_src->fds      = g_hash_table_new_full(nm_pint_hash, nm_pint_equals, _poll_data_free, NULL);
-    ctx_src->fds_len  = 0;
-    ctx_src->fds_arr  = NULL;
-    ctx_src->acquired = TRUE;
-    ctx_src->max_priority = G_MAXINT;
-
-    _CTX_LOG("create new integ-source for %p", inner_context);
-
-    return &ctx_src->source;
-}
-
-/*****************************************************************************/
-
-void
-nm_utils_ifname_cpy(char *dst, const char *name)
-{
-    int i;
-
-    g_return_if_fail(dst);
-    g_return_if_fail(name && name[0]);
-
-    nm_assert(nm_utils_ifname_valid_kernel(name, NULL));
-
-    /* ensures NUL padding of the entire IFNAMSIZ buffer. */
-
-    for (i = 0; i < (int) IFNAMSIZ && name[i] != '\0'; i++)
-        dst[i] = name[i];
-
-    nm_assert(name[i] == '\0');
-
-    for (; i < (int) IFNAMSIZ; i++)
-        dst[i] = '\0';
-}
-
-/*****************************************************************************/
-
-gboolean
-nm_utils_ifname_valid_kernel(const char *name, GError **error)
-{
-    int i;
-
-    /* This function follows kernel's interface validation
-     * function dev_valid_name() in net/core/dev.c.
-     */
-
-    if (!name) {
-        g_set_error_literal(error,
-                            NM_UTILS_ERROR,
-                            NM_UTILS_ERROR_UNKNOWN,
-                            _("interface name is missing"));
-        return FALSE;
-    }
-
-    if (name[0] == '\0') {
-        g_set_error_literal(error,
-                            NM_UTILS_ERROR,
-                            NM_UTILS_ERROR_UNKNOWN,
-                            _("interface name is too short"));
-        return FALSE;
-    }
-
-    if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) {
-        g_set_error_literal(error,
-                            NM_UTILS_ERROR,
-                            NM_UTILS_ERROR_UNKNOWN,
-                            _("interface name is reserved"));
-        return FALSE;
-    }
-
-    for (i = 0; i < IFNAMSIZ; i++) {
-        char ch = name[i];
-
-        if (ch == '\0')
-            return TRUE;
-        if (NM_IN_SET(ch, '/', ':') || g_ascii_isspace(ch)) {
-            g_set_error_literal(error,
-                                NM_UTILS_ERROR,
-                                NM_UTILS_ERROR_UNKNOWN,
-                                _("interface name contains an invalid character"));
-            return FALSE;
-        }
-    }
-
-    g_set_error_literal(error,
-                        NM_UTILS_ERROR,
-                        NM_UTILS_ERROR_UNKNOWN,
-                        _("interface name is longer than 15 characters"));
-    return FALSE;
-}
-
-/*****************************************************************************/
-
-static gboolean
-_nm_utils_ifname_valid_kernel(const char *name, GError **error)
-{
-    if (!nm_utils_ifname_valid_kernel(name, error))
-        return FALSE;
-
-    if (strchr(name, '%')) {
-        /* Kernel's dev_valid_name() accepts (almost) any binary up to 15 chars.
-         * However, '%' is treated special as a format specifier. Try
-         *
-         *   ip link add 'dummy%dx' type dummy
-         *
-         * Don't allow that for "connection.interface-name", which either
-         * matches an existing netdev name (thus, it cannot have a '%') or
-         * is used to configure a name (in which case we don't want kernel
-         * to replace the format specifier). */
-        g_set_error_literal(error,
-                            NM_UTILS_ERROR,
-                            NM_UTILS_ERROR_UNKNOWN,
-                            _("'%%' is not allowed in interface names"));
-        return FALSE;
-    }
-
-    if (NM_IN_STRSET(name, "all", "default", "bonding_masters")) {
-        /* Certain names are not allowed. The "all" and "default" names are reserved
-         * due to their directories in "/proc/sys/net/ipv4/conf/" and "/proc/sys/net/ipv6/conf/".
-         *
-         * Also, there is "/sys/class/net/bonding_masters" file.
-         */
-        nm_utils_error_set(error,
-                           NM_UTILS_ERROR_UNKNOWN,
-                           _("'%s' is not allowed as interface name"),
-                           name);
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-static gboolean
-_nm_utils_ifname_valid_ovs(const char *name, GError **error)
-{
-    const char *ch;
-
-    /* OVS actually accepts a wider range of chars (all printable UTF-8 chars),
-     * NetworkManager restricts this to ASCII char as it's a safer option for
-     * now since OVS is not well documented on this matter.
-     **/
-    for (ch = name; *ch; ++ch) {
-        if (*ch == '\\' || *ch == '/' || !g_ascii_isgraph(*ch)) {
-            g_set_error_literal(error,
-                                NM_UTILS_ERROR,
-                                NM_UTILS_ERROR_UNKNOWN,
-                                _("interface name must be alphanumerical with "
-                                  "no forward or backward slashes"));
-            return FALSE;
-        }
-    };
-    return TRUE;
-}
-
-gboolean
-nm_utils_ifname_valid(const char *name, NMUtilsIfaceType type, GError **error)
-{
-    g_return_val_if_fail(!error || !(*error), FALSE);
-
-    if (!name || !(name[0])) {
-        g_set_error_literal(error,
-                            NM_UTILS_ERROR,
-                            NM_UTILS_ERROR_UNKNOWN,
-                            _("interface name must not be empty"));
-        return FALSE;
-    }
-
-    if (!g_utf8_validate(name, -1, NULL)) {
-        g_set_error_literal(error,
-                            NM_UTILS_ERROR,
-                            NM_UTILS_ERROR_UNKNOWN,
-                            _("interface name must be UTF-8 encoded"));
-        return FALSE;
-    }
-
-    switch (type) {
-    case NMU_IFACE_KERNEL:
-        return _nm_utils_ifname_valid_kernel(name, error);
-    case NMU_IFACE_OVS:
-        return _nm_utils_ifname_valid_ovs(name, error);
-    case NMU_IFACE_OVS_AND_KERNEL:
-        return _nm_utils_ifname_valid_kernel(name, error)
-               && _nm_utils_ifname_valid_ovs(name, error);
-    case NMU_IFACE_ANY:
-    {
-        gs_free_error GError *local = NULL;
-
-        if (_nm_utils_ifname_valid_kernel(name, error ? &local : NULL))
-            return TRUE;
-        if (_nm_utils_ifname_valid_ovs(name, NULL))
-            return TRUE;
-        if (error)
-            g_propagate_error(error, g_steal_pointer(&local));
-        return FALSE;
-    }
-    }
-
-    g_return_val_if_reached(FALSE);
-}
-
-/*****************************************************************************/
-
-void
-_nm_str_buf_ensure_size(NMStrBuf *strbuf, gsize new_size, gboolean reserve_exact)
-{
-    _nm_str_buf_assert(strbuf);
-
-    /* Currently, this only supports strictly growing the buffer. */
-    nm_assert(new_size > strbuf->_priv_allocated);
-
-    if (!reserve_exact) {
-        new_size = nm_utils_get_next_realloc_size(!strbuf->_priv_do_bzero_mem, new_size);
-    }
-
-    strbuf->_priv_str       = nm_secret_mem_realloc(strbuf->_priv_str,
-                                              strbuf->_priv_do_bzero_mem,
-                                              strbuf->_priv_allocated,
-                                              new_size);
-    strbuf->_priv_allocated = new_size;
-}
-
-void
-nm_str_buf_append_printf(NMStrBuf *strbuf, const char *format, ...)
-{
-    va_list args;
-    gsize   available;
-    int     l;
-
-    _nm_str_buf_assert(strbuf);
-
-    available = strbuf->_priv_allocated - strbuf->_priv_len;
-
-    nm_assert(available < G_MAXULONG);
-
-    va_start(args, format);
-    l = g_vsnprintf(&strbuf->_priv_str[strbuf->_priv_len], available, format, args);
-    va_end(args);
-
-    nm_assert(l >= 0);
-    nm_assert(l < G_MAXINT);
-
-    if ((gsize) l >= available) {
-        gsize l2;
-
-        if (l == 0)
-            return;
-
-        l2 = ((gsize) l) + 1u;
-
-        nm_str_buf_maybe_expand(strbuf, l2, FALSE);
-
-        va_start(args, format);
-        l = g_vsnprintf(&strbuf->_priv_str[strbuf->_priv_len], l2, format, args);
-        va_end(args);
-
-        nm_assert(l >= 0);
-        nm_assert((gsize) l == l2 - 1u);
-    }
-
-    strbuf->_priv_len += (gsize) l;
-}
-
-/*****************************************************************************/
-
-/**
- * nm_indirect_g_free:
- * @arg: a pointer to a pointer that is to be freed.
- *
- * This does the same as nm_clear_g_free(arg) (g_clear_pointer (arg, g_free)).
- * This is for example useful when you have a GArray with pointers and a
- * clear function to free them. g_array_set_clear_func()'s destroy notify
- * function gets a pointer to the array location, so we have to follow
- * the first pointer.
- */
-void
-nm_indirect_g_free(gpointer arg)
-{
-    gpointer *p = arg;
-
-    nm_clear_g_free(p);
-}
-
-/*****************************************************************************/
-
-static char *
-attribute_escape(const char *src, char c1, char c2)
-{
-    char *ret, *dest;
-
-    dest = ret = g_malloc(strlen(src) * 2 + 1);
-
-    while (*src) {
-        if (*src == c1 || *src == c2 || *src == '\\')
-            *dest++ = '\\';
-        *dest++ = *src++;
-    }
-    *dest++ = '\0';
-
-    return ret;
-}
-
-void
-_nm_utils_format_variant_attributes_full(GString *                            str,
-                                         const NMUtilsNamedValue *            values,
-                                         guint                                num_values,
-                                         const NMVariantAttributeSpec *const *spec,
-                                         char                                 attr_separator,
-                                         char                                 key_value_separator)
-{
-    const NMVariantAttributeSpec *const *s;
-    const char *                         name, *value;
-    GVariant *                           variant;
-    char *                               escaped;
-    char                                 buf[64];
-    char                                 sep = 0;
-    guint                                i;
-
-    for (i = 0; i < num_values; i++) {
-        name    = values[i].name;
-        variant = values[i].value_ptr;
-        value   = NULL;
-        s       = NULL;
-
-        if (spec) {
-            for (s = spec; *s; s++) {
-                if (nm_streq0((*s)->name, name))
-                    break;
-            }
-
-            if (!*s)
-                continue;
-        }
-
-        if (g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32))
-            value = nm_sprintf_buf(buf, "%u", g_variant_get_uint32(variant));
-        else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_INT32))
-            value = nm_sprintf_buf(buf, "%d", (int) g_variant_get_int32(variant));
-        else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT64))
-            value = nm_sprintf_buf(buf, "%" G_GUINT64_FORMAT, g_variant_get_uint64(variant));
-        else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BYTE))
-            value = nm_sprintf_buf(buf, "%hhu", g_variant_get_byte(variant));
-        else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN))
-            value = g_variant_get_boolean(variant) ? "true" : "false";
-        else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING))
-            value = g_variant_get_string(variant, NULL);
-        else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BYTESTRING)) {
-            /* FIXME: there is no guarantee that the byte array
-             * is valid UTF-8.*/
-            value = g_variant_get_bytestring(variant);
-        } else
-            continue;
-
-        if (sep)
-            g_string_append_c(str, sep);
-
-        escaped = attribute_escape(name, attr_separator, key_value_separator);
-        g_string_append(str, escaped);
-        g_free(escaped);
-
-        if (!s || !*s || !(*s)->no_value) {
-            g_string_append_c(str, key_value_separator);
-
-            escaped = attribute_escape(value, attr_separator, key_value_separator);
-            g_string_append(str, escaped);
-            g_free(escaped);
-        }
-
-        sep = attr_separator;
-    }
-}
-
-char *
-_nm_utils_format_variant_attributes(GHashTable *                         attributes,
-                                    const NMVariantAttributeSpec *const *spec,
-                                    char                                 attr_separator,
-                                    char                                 key_value_separator)
-{
-    gs_free NMUtilsNamedValue *values_free = NULL;
-    NMUtilsNamedValue          values_prepared[20];
-    const NMUtilsNamedValue *  values;
-    GString *                  str = NULL;
-    guint                      len;
-
-    g_return_val_if_fail(attr_separator, NULL);
-    g_return_val_if_fail(key_value_separator, NULL);
-
-    if (!attributes)
-        return NULL;
-
-    values = nm_utils_named_values_from_strdict(attributes, &len, values_prepared, &values_free);
-    if (len == 0)
-        return NULL;
-
-    str = g_string_new("");
-    _nm_utils_format_variant_attributes_full(str,
-                                             values,
-                                             len,
-                                             spec,
-                                             attr_separator,
-                                             key_value_separator);
-    return g_string_free(str, FALSE);
-}
-
-/*****************************************************************************/
-
-gboolean
-nm_utils_is_localhost(const char *name)
-{
-    static const char *const NAMES[] = {
-        "localhost",
-        "localhost4",
-        "localhost6",
-        "localhost.localdomain",
-        "localhost4.localdomain4",
-        "localhost6.localdomain6",
-    };
-    gsize name_len;
-    int   i;
-
-    if (!name)
-        return FALSE;
-
-    /* This tries to identify local host and domain names
-     * described in RFC6761 plus the redhatism of localdomain.
-     *
-     * Similar to systemd's is_localhost(). */
-
-    name_len = strlen(name);
-
-    if (name_len == 0)
-        return FALSE;
-
-    if (name[name_len - 1] == '.') {
-        /* one trailing dot is fine. Hide it. */
-        name_len--;
-    }
-
-    for (i = 0; i < (int) G_N_ELEMENTS(NAMES); i++) {
-        const char *n = NAMES[i];
-        gsize       l = strlen(n);
-        gsize       s;
-
-        if (name_len < l)
-            continue;
-
-        s = name_len - l;
-
-        if (g_ascii_strncasecmp(&name[s], n, l) != 0)
-            continue;
-
-        /* we accept the name if it is equal to one of the well-known names,
-         * or if it is some prefix, a '.' and the well-known name. */
-        if (s == 0)
-            return TRUE;
-        if (name[s - 1] == '.')
-            return TRUE;
-    }
-
-    return FALSE;
-}
-
-gboolean
-nm_utils_is_specific_hostname(const char *name)
-{
-    if (nm_str_is_empty(name))
-        return FALSE;
-
-    if (nm_streq(name, "(none)")) {
-        /* This is not a special hostname. Probably an artefact by somebody wrongly
-         * printing NULL. */
-        return FALSE;
-    }
-
-    if (nm_utils_is_localhost(name))
-        return FALSE;
-
-    /* FIXME: properly validate the hostname, like systemd's hostname_is_valid() */
-
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-/* taken from systemd's uid_to_name(). */
-char *
-nm_utils_uid_to_name(uid_t uid)
-{
-    gs_free char *buf_heap = NULL;
-    char          buf_stack[4096];
-    gsize         bufsize;
-    char *        buf;
-
-    bufsize = sizeof(buf_stack);
-    buf     = buf_stack;
-
-    for (;;) {
-        struct passwd  pwbuf;
-        struct passwd *pw = NULL;
-        int            r;
-
-        r = getpwuid_r(uid, &pwbuf, buf, bufsize, &pw);
-        if (r == 0 && pw)
-            return nm_strdup_not_empty(pw->pw_name);
-
-        if (r != ERANGE)
-            return NULL;
-
-        if (bufsize > G_MAXSIZE / 2u)
-            return NULL;
-
-        bufsize *= 2u;
-        g_free(buf_heap);
-        buf_heap = g_malloc(bufsize);
-        buf      = buf_heap;
-    }
-}
-
-/* taken from systemd's nss_user_record_by_name() */
-gboolean
-nm_utils_name_to_uid(const char *name, uid_t *out_uid)
-{
-    gs_free char *buf_heap = NULL;
-    char          buf_stack[4096];
-    gsize         bufsize;
-    char *        buf;
-
-    if (!name)
-        return nm_assert_unreachable_val(FALSE);
-
-    bufsize = sizeof(buf_stack);
-    buf     = buf_stack;
-
-    for (;;) {
-        struct passwd *result;
-        struct passwd  pwd;
-        int            r;
-
-        r = getpwnam_r(name, &pwd, buf, bufsize, &result);
-        if (r == 0) {
-            if (!result)
-                return FALSE;
-            NM_SET_OUT(out_uid, pwd.pw_uid);
-            return TRUE;
-        }
-
-        if (r != ERANGE)
-            return FALSE;
-
-        if (bufsize > G_MAXSIZE / 2u)
-            return FALSE;
-
-        bufsize *= 2u;
-        g_free(buf_heap);
-        buf_heap = g_malloc(bufsize);
-        buf      = buf_heap;
-    }
-}
-
-/*****************************************************************************/
-
-typedef struct {
-    CList          lst;
-    gpointer       tls_data;
-    GDestroyNotify destroy_notify;
-} TlsRegData;
-
-static pthread_key_t _tls_reg_key;
-
-static void
-_tls_reg_destroy(gpointer data)
-{
-    CList *     lst_head = data;
-    TlsRegData *entry;
-
-    if (!lst_head)
-        return;
-
-    /* For no strong reason are we destroying the elements in reverse
-     * order than they were added. It seems a bit more sensible (but shouldn't
-     * matter nor should you rely on that). */
-    while ((entry = c_list_last_entry(lst_head, TlsRegData, lst))) {
-        c_list_unlink_stale(&entry->lst);
-        entry->destroy_notify(entry->tls_data);
-        nm_g_slice_free(entry);
-    }
-
-    nm_g_slice_free(lst_head);
-}
-
-static void
-_tls_reg_make_key(void)
-{
-    if (pthread_key_create(&_tls_reg_key, _tls_reg_destroy) != 0)
-        g_return_if_reached();
-}
-
-/**
- * nm_utils_thread_local_register_destroy:
- * @tls_data: the thread local storage data that should be destroyed when the thread
- *   exits. This pointer will be "owned" by the current thread. There is no way
- *   to un-register the destruction.
- * @destroy_notify: the free function that will be called when the thread exits.
- *
- * If _nm_tread_local storage is heap allocated it requires freeing the pointer
- * when the thread exits. Use this function to register the pointer to be
- * released.
- *
- * This function does not change errno.
- */
-void
-nm_utils_thread_local_register_destroy(gpointer tls_data, GDestroyNotify destroy_notify)
-{
-    NM_AUTO_PROTECT_ERRNO(errsv);
-    static pthread_once_t key_once = PTHREAD_ONCE_INIT;
-    CList *               lst_head;
-    TlsRegData *          entry;
-
-    nm_assert(destroy_notify);
-
-    if (pthread_once(&key_once, _tls_reg_make_key) != 0)
-        g_return_if_reached();
-
-    if ((lst_head = pthread_getspecific(_tls_reg_key)) == NULL) {
-        lst_head = g_slice_new(CList);
-        c_list_init(lst_head);
-        if (pthread_setspecific(_tls_reg_key, lst_head) != 0)
-            g_return_if_reached();
-    }
-
-    entry                 = g_slice_new(TlsRegData);
-    entry->tls_data       = tls_data;
-    entry->destroy_notify = destroy_notify;
-    c_list_link_tail(lst_head, &entry->lst);
-}