about summary refs log tree commit diff
path: root/src/libnm-glib-aux/nm-shared-utils.c
diff options
context:
space:
mode:
authorJeremy Bicha <jeremy.bicha@canonical.com>2022-08-18 08:31:29 -0400
committerJeremy Bicha <jeremy.bicha@canonical.com>2022-08-18 08:31:29 -0400
commitb0887dd4d035acc0d84aa859748d36891548eaaf (patch)
treec4dc0dae50954f3c8fb600aa9e7cfaabeb80deb0 /src/libnm-glib-aux/nm-shared-utils.c
parent1a62dcfdc0470be37743914da69fe0b0677c6bfb (diff)
parent4741f1a52215c7ba466140912084d6906185bef3 (diff)
Merge branch 'debian/master' into ubuntu/master
Diffstat (limited to 'src/libnm-glib-aux/nm-shared-utils.c')
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.c743
1 files changed, 674 insertions, 69 deletions
diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c
index e6ee04d7..624f9a3e 100644
--- a/src/libnm-glib-aux/nm-shared-utils.c
+++ b/src/libnm-glib-aux/nm-shared-utils.c
@@ -44,6 +44,15 @@ G_STATIC_ASSERT(_nm_alignof(struct in_addr) <= _nm_alignof(NMIPAddr));
 G_STATIC_ASSERT(_nm_alignof(struct in6_addr) <= _nm_alignof(NMIPAddr));
 G_STATIC_ASSERT(_nm_alignof(NMEtherAddr) <= _nm_alignof(NMIPAddr));
 
+int
+nm_ip_addr_cmp_for_sort(gconstpointer a, gconstpointer b, gpointer user_data)
+{
+    /* This is a compare function that can be used for sorting IP addresses.
+     * Essentially, it calls memcmp(). @user_data must be GINT_TO_POINTER(addr_family).
+     * @a and @b must be either pointers to in_addr_t, struct in6_addr or NMIPAddr. */
+    return nm_ip_addr_cmp(GPOINTER_TO_INT(user_data), a, b);
+}
+
 /* 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
@@ -137,6 +146,18 @@ G_STATIC_ASSERT(ETH_ALEN == sizeof(NMEtherAddr));
 
 G_STATIC_ASSERT(_nm_alignof(struct ether_addr) <= _nm_alignof(NMEtherAddr));
 
+NMEtherAddr *
+nm_ether_addr_from_string(NMEtherAddr *addr, const char *str)
+{
+    nm_assert(addr);
+
+    if (!str || !_nm_utils_hwaddr_aton_exact(str, addr, ETH_ALEN)) {
+        *addr = NM_ETHER_ADDR_INIT(0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+        return NULL;
+    }
+
+    return addr;
+}
 /*****************************************************************************/
 
 /**
@@ -994,12 +1015,22 @@ nm_utils_ip_is_site_local(int addr_family, const void *address)
         return (addr4 & 0xff000000) == 0x0a000000 || (addr4 & 0xfff00000) == 0xac100000
                || (addr4 & 0xffff0000) == 0xc0a80000;
     case AF_INET6:
+        /* IN6_IS_ADDR_SITELOCAL() is for deprecated fec0::/10 addresses (see rfc3879, 4.).
+         * Note that for unique local IPv6 addresses (ULA, fc00::/7) this returns false,
+         * which may or may not be a bug. */
         return IN6_IS_ADDR_SITELOCAL(address);
     default:
         g_return_val_if_reached(FALSE);
     }
 }
 
+gboolean
+nm_utils_ip6_is_ula(const struct in6_addr *address)
+{
+    /* Unique local IPv6 address (ULA) fc00::/7 */
+    return (address->s6_addr32[0] & htonl(0xfe000000u)) == htonl(0xfc000000u);
+}
+
 /*****************************************************************************/
 
 static gboolean
@@ -2288,9 +2319,11 @@ nm_utils_escaped_tokens_options_split(char *str, const char **out_key, const cha
 char **
 nm_utils_strsplit_quoted(const char *str)
 {
-    gs_unref_ptrarray GPtrArray *arr     = NULL;
-    gs_free char                *str_out = NULL;
-    CharLookupTable              ch_lookup;
+    char          **arr       = NULL;
+    gsize           arr_len   = 0;
+    gsize           arr_alloc = 0;
+    gs_free char   *str_out   = NULL;
+    CharLookupTable ch_lookup;
 
     nm_assert(str);
 
@@ -2347,19 +2380,32 @@ nm_utils_strsplit_quoted(const char *str)
             str++;
         }
 
-        if (!arr)
-            arr = g_ptr_array_new();
-        g_ptr_array_add(arr, g_strndup(str_out, j));
+        if (arr_len >= arr_alloc) {
+            if (arr_alloc == 0)
+                arr_alloc = 4;
+            else
+                arr_alloc *= 2;
+            arr = g_realloc(arr, sizeof(char *) * arr_alloc);
+        }
+
+        arr[arr_len++] = 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);
+
+    if (arr_len + 1u != arr_alloc) {
+        gs_free char **arr_old = arr;
+
+        arr = g_new(char *, arr_len + 1u);
+        memcpy(arr, arr_old, sizeof(char *) * arr_len);
+    }
+
+    arr[arr_len] = NULL;
+    return arr;
 }
 
 /*****************************************************************************/
@@ -3008,7 +3054,7 @@ nm_utils_buf_utf8safe_unescape(const char             *str,
         return str;
     }
 
-    nm_str_buf_init(&strbuf, len + 1u, FALSE);
+    strbuf = NM_STR_BUF_INIT(len + 1u, FALSE);
 
     nm_str_buf_append_len(&strbuf, str, s - str);
     str = s;
@@ -3175,7 +3221,7 @@ nm_utils_buf_utf8safe_escape(gconstpointer           buf,
             return str;
     }
 
-    nm_str_buf_init(&strbuf, buflen + 5, NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET));
+    strbuf = NM_STR_BUF_INIT(buflen + 5, NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET));
 
     s = str;
     do {
@@ -3347,6 +3393,8 @@ nm_utils_fd_wait_for_event(int fd, int event, gint64 timeout_nsec)
     struct timespec ts, *pts;
     int             r;
 
+    nm_assert(fd >= 0);
+
     if (timeout_nsec < 0)
         pts = NULL;
     else {
@@ -3360,6 +3408,13 @@ nm_utils_fd_wait_for_event(int fd, int event, gint64 timeout_nsec)
         return -NM_ERRNO_NATIVE(errno);
     if (r == 0)
         return 0;
+
+    nm_assert(r == 1);
+    nm_assert(pollfd.revents > 0);
+
+    if (pollfd.revents & POLLNVAL)
+        return nm_assert_unreachable_val(-EBADF);
+
     return pollfd.revents;
 }
 
@@ -3442,51 +3497,6 @@ nm_utils_named_value_clear_with_g_free(NMUtilsNamedValue *val)
 
 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,
@@ -3636,6 +3646,52 @@ nm_utils_hash_values_to_array(GHashTable      *hash,
     return arr;
 }
 
+NMUtilsNamedValue *
+nm_utils_hash_to_array_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              len;
+    guint              i;
+
+    nm_assert(provided_buffer_len == 0 || provided_buffer);
+    nm_assert(!out_allocated_buffer || !*out_allocated_buffer);
+
+    if (!hash || ((len = g_hash_table_size(hash)) == 0)) {
+        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, &values[i].name_ptr, &values[i].value_ptr))
+        i++;
+    nm_assert(i == len);
+    values[i].name_ptr  = NULL;
+    values[i].value_ptr = NULL;
+
+    if (compare_func && len > 1)
+        g_qsort_with_data(values, len, sizeof(NMUtilsNamedValue), compare_func, user_data);
+
+    NM_SET_OUT(out_len, len);
+    return values;
+}
+
 /*****************************************************************************/
 
 /**
@@ -5850,17 +5906,29 @@ _nm_str_buf_ensure_size(NMStrBuf *strbuf, gsize new_size, gboolean 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);
+    if (strbuf->_priv_malloced) {
+        strbuf->_priv_str = nm_secret_mem_realloc(strbuf->_priv_str,
+                                                  strbuf->_priv_do_bzero_mem,
+                                                  strbuf->_priv_allocated,
+                                                  new_size);
+    } else {
+        char *old = strbuf->_priv_str;
+
+        strbuf->_priv_str = g_malloc(new_size);
+        if (strbuf->_priv_len > 0) {
+            memcpy(strbuf->_priv_str, old, strbuf->_priv_len);
+            if (strbuf->_priv_do_bzero_mem)
+                nm_explicit_bzero(old, strbuf->_priv_len);
+        }
+        strbuf->_priv_malloced = TRUE;
+    }
     strbuf->_priv_allocated = new_size;
 }
 
 void
-nm_str_buf_append_printf(NMStrBuf *strbuf, const char *format, ...)
+nm_str_buf_append_printfv(NMStrBuf *strbuf, const char *format, va_list args)
 {
-    va_list args;
+    va_list args_copy;
     gsize   available;
     int     l;
 
@@ -5870,12 +5938,12 @@ nm_str_buf_append_printf(NMStrBuf *strbuf, const char *format, ...)
 
     nm_assert(available < G_MAXULONG);
 
-    va_start(args, format);
+    va_copy(args_copy, args);
     l = g_vsnprintf(strbuf->_priv_allocated > 0 ? &strbuf->_priv_str[strbuf->_priv_len] : NULL,
                     available,
                     format,
-                    args);
-    va_end(args);
+                    args_copy);
+    va_end(args_copy);
 
     nm_assert(l >= 0);
     nm_assert(l < G_MAXINT);
@@ -5890,9 +5958,9 @@ nm_str_buf_append_printf(NMStrBuf *strbuf, const char *format, ...)
 
         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);
+        va_copy(args_copy, args);
+        l = g_vsnprintf(&strbuf->_priv_str[strbuf->_priv_len], l2, format, args_copy);
+        va_end(args_copy);
 
         nm_assert(l >= 0);
         nm_assert((gsize) l == l2 - 1u);
@@ -6594,7 +6662,7 @@ nm_utils_validate_hostname(const char *hostname)
     if (dot)
         return FALSE;
 
-    return (p - hostname <= HOST_NAME_MAX);
+    return (p - hostname <= NM_HOST_NAME_MAX);
 }
 
 /*****************************************************************************/
@@ -6723,3 +6791,540 @@ nm_g_main_context_can_acquire(GMainContext *context)
     g_main_context_release(context);
     return TRUE;
 }
+
+/*****************************************************************************/
+
+int
+nm_unbase64char(char c)
+{
+    /* copied from systemd's unbase64char():
+     * https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/basic/hexdecoct.c#L539 */
+
+    switch (c) {
+    case 'A' ... 'Z':
+        return c - 'A';
+    case 'a' ... 'z':
+        return (c - 'a') + ('Z' - 'A' + 1);
+    case '0' ... '9':
+        return (c - '0') + (('Z' - 'A' + 1) + ('z' - 'a' + 1));
+    case '+':
+        return ('Z' - 'A' + 1) + ('z' - 'a' + 1) + ('9' - '0' + 1);
+    case '/':
+        return ('Z' - 'A' + 1) + ('z' - 'a' + 1) + ('9' - '0' + 1) + 1;
+    case '=':
+        /* The padding is a different kind of base64 character. Return
+         * a special error code for it. */
+        return -ERANGE;
+    default:
+        return -EINVAL;
+    }
+}
+
+static int
+unbase64_next(const char **p, size_t *l)
+{
+    int ret;
+
+    nm_assert(p);
+    nm_assert(l);
+
+    /* copied from systemd's unbase64_next():
+     * https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/basic/hexdecoct.c#L709 */
+
+    /* Find the next non-whitespace character, and decode it. If we find padding, we return it as INT_MAX. We
+     * greedily skip all preceding and all following whitespace. */
+
+    for (;;) {
+        if (*l == 0)
+            return -EPIPE;
+
+        if (!nm_ascii_is_whitespace(**p))
+            break;
+
+        /* Skip leading whitespace */
+        (*p)++;
+        (*l)--;
+    }
+
+    ret = nm_unbase64char(**p);
+    if (ret < 0) {
+        nm_assert(NM_IN_SET(ret, -EINVAL, -ERANGE));
+        if (ret != -ERANGE)
+            return ret;
+    }
+
+    for (;;) {
+        (*p)++;
+        (*l)--;
+
+        if (*l == 0)
+            break;
+        if (!nm_ascii_is_whitespace(**p))
+            break;
+
+        /* Skip following whitespace */
+    }
+
+    nm_assert(ret == -ERANGE || ret >= 0);
+    return ret;
+}
+
+/**
+ * nm_unbase64mem_full:
+ * @p: a valid base64 string. Whitespace is ignored, but invalid encodings
+ *   will cause the function to fail.
+ * @l: the length of @p. @p is not treated as NUL terminated string but
+ *   merely as a buffer of ascii characters.
+ * @secure: whether the temporary memory will be cleared to avoid leaving
+ *   secrets in memory (see also nm_explicit_bzero()).
+ * @mem: (transfer full): the decoded buffer on success.
+ * @len: the length of @mem on success.
+ *
+ * glib provides g_base64_decode(), but that does not report any errors
+ * from invalid encodings. Our own implementation (based on systemd code)
+ * rejects invalid inputs.
+ *
+ * Returns: a non-negative code on success. Invalid encoding let the
+ *   function fail.
+ */
+int
+nm_unbase64mem_full(const char *p, gsize l, gboolean secure, guint8 **ret, gsize *ret_size)
+{
+    gs_free uint8_t *buf = NULL;
+    const char      *x;
+    guint8          *z;
+    gsize            len;
+    int              r;
+
+    /* copied from systemd's unbase64mem_full():
+     * https://github.com/systemd/systemd/blob/688efe7703328c5a0251fafac55757b8864a9f9a/src/basic/hexdecoct.c#L751 */
+
+    nm_assert(p || l == 0);
+
+    if (l == G_MAXSIZE)
+        l = strlen(p);
+
+    /* A group of four input bytes needs three output bytes, in case of padding we need to add two or three extra
+     * bytes. Note that this calculation is an upper boundary, as we ignore whitespace while decoding */
+    len = (l / 4) * 3 + (l % 4 != 0 ? (l % 4) - 1 : 0);
+
+    buf = g_malloc(len + 1);
+
+    for (x = p, z = buf;;) {
+        int a; /* a == 00XXXXXX */
+        int b; /* b == 00YYYYYY */
+        int c; /* c == 00ZZZZZZ */
+        int d; /* d == 00WWWWWW */
+
+        a = unbase64_next(&x, &l);
+        if (a < 0) {
+            if (a == -EPIPE) /* End of string */
+                break;
+            if (a == -ERANGE) { /* Padding is not allowed at the beginning of a 4ch block */
+                r = -EINVAL;
+                goto on_failure;
+            }
+            r = a;
+            goto on_failure;
+        }
+
+        b = unbase64_next(&x, &l);
+        if (b < 0) {
+            if (b == -ERANGE) {
+                /* Padding is not allowed at the second character of a 4ch block either */
+                r = -EINVAL;
+                goto on_failure;
+            }
+            r = b;
+            goto on_failure;
+        }
+
+        c = unbase64_next(&x, &l);
+        if (c < 0) {
+            if (c != -ERANGE) {
+                r = c;
+                goto on_failure;
+            }
+        }
+
+        d = unbase64_next(&x, &l);
+        if (d < 0) {
+            if (d != -ERANGE) {
+                r = d;
+                goto on_failure;
+            }
+        }
+
+        if (c == -ERANGE) { /* Padding at the third character */
+
+            if (d != -ERANGE) { /* If the third character is padding, the fourth must be too */
+                r = -EINVAL;
+                goto on_failure;
+            }
+
+            /* b == 00YY0000 */
+            if (b & 15) {
+                r = -EINVAL;
+                goto on_failure;
+            }
+
+            if (l > 0) { /* Trailing rubbish? */
+                r = -ENAMETOOLONG;
+                goto on_failure;
+            }
+
+            *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */
+            break;
+        }
+
+        if (d == -ERANGE) {
+            /* c == 00ZZZZ00 */
+            if (c & 3) {
+                r = -EINVAL;
+                goto on_failure;
+            }
+
+            if (l > 0) { /* Trailing rubbish? */
+                r = -ENAMETOOLONG;
+                goto on_failure;
+            }
+
+            *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
+            *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
+            break;
+        }
+
+        *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
+        *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
+        *(z++) = (uint8_t) c << 6 | (uint8_t) d;      /* ZZWWWWWW */
+    }
+
+    *z = '\0';
+
+    NM_SET_OUT(ret_size, (gsize) (z - buf));
+    NM_SET_OUT(ret, g_steal_pointer(&buf));
+    return 0;
+
+on_failure:
+    if (secure)
+        nm_explicit_bzero(buf, len);
+    return r;
+}
+
+/*****************************************************************************/
+
+static const char *
+skip_slash_or_dot(const char *p)
+{
+    for (; !nm_str_is_empty(p);) {
+        if (p[0] == '/') {
+            p += 1;
+            continue;
+        }
+        if (p[0] == '.' && p[1] == '/') {
+            p += 2;
+            continue;
+        }
+        break;
+    }
+    return p;
+}
+
+int
+nm_path_find_first_component(const char **p, gboolean accept_dot_dot, const char **ret)
+{
+    const char *q, *first, *end_first, *next;
+    size_t      len;
+
+    /* Copied from systemd's path_compare()
+     * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.c#L809 */
+
+    nm_assert(p);
+
+    /* When a path is input, then returns the pointer to the first component and its length, and
+     * move the input pointer to the next component or nul. This skips both over any '/'
+     * immediately *before* and *after* the first component before returning.
+     *
+     * Examples
+     *   Input:  p: "//.//aaa///bbbbb/cc"
+     *   Output: p: "bbbbb///cc"
+     *           ret: "aaa///bbbbb/cc"
+     *           return value: 3 (== strlen("aaa"))
+     *
+     *   Input:  p: "aaa//"
+     *   Output: p: (pointer to NUL)
+     *           ret: "aaa//"
+     *           return value: 3 (== strlen("aaa"))
+     *
+     *   Input:  p: "/", ".", ""
+     *   Output: p: (pointer to NUL)
+     *           ret: NULL
+     *           return value: 0
+     *
+     *   Input:  p: NULL
+     *   Output: p: NULL
+     *           ret: NULL
+     *           return value: 0
+     *
+     *   Input:  p: "(too long component)"
+     *   Output: return value: -EINVAL
+     *
+     *   (when accept_dot_dot is false)
+     *   Input:  p: "//..//aaa///bbbbb/cc"
+     *   Output: return value: -EINVAL
+     */
+
+    q = *p;
+
+    first = skip_slash_or_dot(q);
+    if (nm_str_is_empty(first)) {
+        *p = first;
+        if (ret)
+            *ret = NULL;
+        return 0;
+    }
+    if (nm_streq(first, ".")) {
+        *p = first + 1;
+        if (ret)
+            *ret = NULL;
+        return 0;
+    }
+
+    end_first = strchrnul(first, '/');
+    len       = end_first - first;
+
+    if (len > NAME_MAX)
+        return -EINVAL;
+    if (!accept_dot_dot && len == 2 && first[0] == '.' && first[1] == '.')
+        return -EINVAL;
+
+    next = skip_slash_or_dot(end_first);
+
+    *p = next + (nm_streq(next, ".") ? 1 : 0);
+    if (ret)
+        *ret = first;
+    return len;
+}
+
+int
+nm_path_compare(const char *a, const char *b)
+{
+    /* Copied from systemd's path_compare()
+     * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.c#L415 */
+
+    /* Order NULL before non-NULL */
+    NM_CMP_SELF(a, b);
+
+    /* A relative path and an absolute path must not compare as equal.
+     * Which one is sorted before the other does not really matter.
+     * Here a relative path is ordered before an absolute path. */
+    NM_CMP_DIRECT(nm_path_is_absolute(a), nm_path_is_absolute(b));
+
+    for (;;) {
+        const char *aa, *bb;
+        int         j, k;
+
+        j = nm_path_find_first_component(&a, TRUE, &aa);
+        k = nm_path_find_first_component(&b, TRUE, &bb);
+
+        if (j < 0 || k < 0) {
+            /* When one of paths is invalid, order invalid path after valid one. */
+            NM_CMP_DIRECT(j < 0, k < 0);
+
+            /* fallback to use strcmp() if both paths are invalid. */
+            NM_CMP_DIRECT_STRCMP(a, b);
+            return 0;
+        }
+
+        /* Order prefixes first: "/foo" before "/foo/bar" */
+        if (j == 0) {
+            if (k == 0)
+                return 0;
+            return -1;
+        }
+        if (k == 0)
+            return 1;
+
+        /* Alphabetical sort: "/foo/aaa" before "/foo/b" */
+        NM_CMP_DIRECT_MEMCMP(aa, bb, NM_MIN(j, k));
+
+        /* Sort "/foo/a" before "/foo/aaa" */
+        NM_CMP_DIRECT(j, k);
+    }
+}
+
+char *
+nm_path_startswith_full(const char *path, const char *prefix, gboolean accept_dot_dot)
+{
+    /* Copied from systemd's path_startswith_full()
+     * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.c#L375 */
+
+    nm_assert(path);
+    nm_assert(prefix);
+
+    /* Returns a pointer to the start of the first component after the parts matched by
+     * the prefix, iff
+     * - both paths are absolute or both paths are relative,
+     * and
+     * - each component in prefix in turn matches a component in path at the same position.
+     * An empty string will be returned when the prefix and path are equivalent.
+     *
+     * Returns NULL otherwise.
+     */
+
+    if ((path[0] == '/') != (prefix[0] == '/'))
+        return NULL;
+
+    for (;;) {
+        const char *p, *q;
+        int         r, k;
+
+        r = nm_path_find_first_component(&path, accept_dot_dot, &p);
+        if (r < 0)
+            return NULL;
+
+        k = nm_path_find_first_component(&prefix, accept_dot_dot, &q);
+        if (k < 0)
+            return NULL;
+
+        if (k == 0)
+            return (char *) (p ?: path);
+
+        if (r != k)
+            return NULL;
+
+        if (strncmp(p, q, r) != 0)
+            return NULL;
+    }
+}
+
+char *
+nm_path_simplify(char *path)
+{
+    bool  add_slash = false;
+    char *f         = path;
+    int   r;
+
+    /* Copied from systemd's path_simplify()
+     * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.c#L325 */
+
+    nm_assert(path);
+
+    /* Removes redundant inner and trailing slashes. Also removes unnecessary dots.
+     * Modifies the passed string in-place.
+     *
+     * ///foo//./bar/.   becomes /foo/bar
+     * .//./foo//./bar/. becomes foo/bar
+     */
+
+    if (path[0] == '\0')
+        return path;
+
+    if (nm_path_is_absolute(path))
+        f++;
+
+    for (const char *p = f;;) {
+        const char *e;
+
+        r = nm_path_find_first_component(&p, TRUE, &e);
+        if (r == 0)
+            break;
+
+        if (add_slash)
+            *f++ = '/';
+
+        if (r < 0) {
+            /* if path is invalid, then refuse to simplify remaining part. */
+            memmove(f, p, strlen(p) + 1);
+            return path;
+        }
+
+        memmove(f, e, r);
+        f += r;
+
+        add_slash = TRUE;
+    }
+
+    /* Special rule, if we stripped everything, we need a "." for the current directory. */
+    if (f == path)
+        *f++ = '.';
+
+    *f = '\0';
+    return path;
+}
+
+/*****************************************************************************/
+
+static gboolean
+valid_ldh_char(char c)
+{
+    /* "LDH" → "Letters, digits, hyphens", as per RFC 5890, Section 2.3.1 */
+
+    return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-';
+}
+
+/**
+ * nm_hostname_is_valid:
+ * @s: the hostname to check.
+ * @trailing_dot: Accept trailing dot on multi-label names.
+ *
+ * Return: %TRUE if valid.
+ */
+gboolean
+nm_hostname_is_valid(const char *s, gboolean trailing_dot)
+{
+    unsigned    n_dots = 0;
+    const char *p;
+    gboolean    dot;
+    gboolean    hyphen;
+
+    /* Copied from systemd's hostname_is_valid()
+     * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/hostname-util.c#L85 */
+
+    /* Check if s looks like a valid hostname or FQDN. This does not do full DNS validation, but only
+     * checks if the name is composed of allowed characters and the length is not above the maximum
+     * allowed by Linux (c.f. dns_name_is_valid()). A trailing dot is allowed if
+     * VALID_HOSTNAME_TRAILING_DOT flag is set and at least two components are present in the name. Note
+     * that due to the restricted charset and length this call is substantially more conservative than
+     * dns_name_is_valid(). Doesn't accept empty hostnames, hostnames with leading dots, and hostnames
+     * with multiple dots in a sequence. Doesn't allow hyphens at the beginning or end of label. */
+
+    if (nm_str_is_empty(s))
+        return FALSE;
+
+    for (p = s, dot = hyphen = TRUE; *p; p++)
+        if (*p == '.') {
+            if (dot || hyphen)
+                return FALSE;
+
+            dot    = TRUE;
+            hyphen = FALSE;
+            n_dots++;
+
+        } else if (*p == '-') {
+            if (dot)
+                return FALSE;
+
+            dot    = FALSE;
+            hyphen = TRUE;
+
+        } else {
+            if (!valid_ldh_char(*p))
+                return FALSE;
+
+            dot    = FALSE;
+            hyphen = FALSE;
+        }
+
+    if (dot && (n_dots < 2 || !trailing_dot))
+        return FALSE;
+    if (hyphen)
+        return FALSE;
+
+    /* Note that HOST_NAME_MAX is 64 on Linux, but DNS allows domain names up to
+     * 255 characters */
+    if (p - s > NM_HOST_NAME_MAX)
+        return FALSE;
+
+    return TRUE;
+}