summary refs log tree commit diff
path: root/src/libnm-glib-aux
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2024-01-25 09:46:18 +0100
committerMichael Biebl <biebl@debian.org>2024-01-25 09:46:18 +0100
commit70e18d99b8e3e77bb37e218d7ac582130156f8ef (patch)
treed40c587e6d3f0e094ff558e415f1bb9803643214 /src/libnm-glib-aux
parentd4d8b2b91f7ba000d97a8b2aab48c85000c11314 (diff)
New upstream version 1.45.90 upstream/1.45.90
Diffstat (limited to 'src/libnm-glib-aux')
-rw-r--r--src/libnm-glib-aux/nm-dedup-multi.h2
-rw-r--r--src/libnm-glib-aux/nm-enum-utils.c129
-rw-r--r--src/libnm-glib-aux/nm-enum-utils.h12
-rw-r--r--src/libnm-glib-aux/nm-errno.c23
-rw-r--r--src/libnm-glib-aux/nm-glib.h380
-rw-r--r--src/libnm-glib-aux/nm-hash-utils.h29
-rw-r--r--src/libnm-glib-aux/nm-macros-internal.h26
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.c304
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.h367
-rw-r--r--src/libnm-glib-aux/nm-test-utils.h39
-rw-r--r--src/libnm-glib-aux/tests/test-shared-general.c286
11 files changed, 1049 insertions, 548 deletions
diff --git a/src/libnm-glib-aux/nm-dedup-multi.h b/src/libnm-glib-aux/nm-dedup-multi.h
index 87a2b815..ebc5d0a1 100644
--- a/src/libnm-glib-aux/nm-dedup-multi.h
+++ b/src/libnm-glib-aux/nm-dedup-multi.h
@@ -37,7 +37,7 @@ typedef enum _NMDedupMultiIdxMode {
 
 /*****************************************************************************/
 
-#define _NMDedupMultiObj_Align (MAX(_nm_alignof(void *), _nm_alignof(gint64)))
+#define _NMDedupMultiObj_Align (NM_MAX_CONST(_nm_alignof(void *), _nm_alignof(gint64)))
 
 struct _NMDedupMultiObj {
     union {
diff --git a/src/libnm-glib-aux/nm-enum-utils.c b/src/libnm-glib-aux/nm-enum-utils.c
index 2593a9cd..212c0021 100644
--- a/src/libnm-glib-aux/nm-enum-utils.c
+++ b/src/libnm-glib-aux/nm-enum-utils.c
@@ -320,53 +320,118 @@ _nm_utils_enum_from_str_full(GType                       type,
 const char **
 _nm_utils_enum_get_values(GType type, int from, int to)
 {
-    GTypeClass *klass;
-    GPtrArray  *array;
-    int         i;
+    int        i;
+    GArray    *values_full = _nm_utils_enum_get_values_full(type, from, to, NULL);
+    GPtrArray *values      = g_ptr_array_sized_new(values_full->len + 1);
+
+    for (i = 0; i < values_full->len; i++) {
+        NMUtilsEnumValueInfoFull *v = &nm_g_array_index(values_full, NMUtilsEnumValueInfoFull, i);
+        g_ptr_array_add(values, (gpointer) v->nick);
+    }
+
+    g_ptr_array_add(values, NULL);
+    g_array_unref(values_full);
+    return (const char **) g_ptr_array_free(values, FALSE);
+}
+
+static void
+_free_value_info_full(NMUtilsEnumValueInfoFull *value_info_full)
+{
+    g_free(value_info_full->aliases);
+}
+
+static void
+_init_value_info_full(NMUtilsEnumValueInfoFull *v, bool is_flag, const char *nick, int value)
+{
     char        sbuf[64];
+    const char *value_str = is_flag ? g_intern_string(nm_sprintf_buf(sbuf, "0x%x", value))
+                                    : g_intern_string(nm_sprintf_buf(sbuf, "%d", value));
 
-    klass = g_type_class_ref(type);
-    array = g_ptr_array_new();
+    v->nick      = _enum_is_valid_enum_nick(nick) ? nick : value_str;
+    v->aliases   = NULL;
+    v->value_str = value_str;
+    v->value     = value;
+}
+
+/**
+ * _nm_utils_enum_get_values_full:
+ * @type: the enum or flags type
+ * @from: lowest value to return
+ * @to:   highest value to return
+ * @value_infos: (nullable): additional value aliases
+ * 
+ * Get the enum or flags values within the given range, putting together the
+ * value, name and aliases of each of them.
+ *
+ * If @value_infos is NULL, no memory will be allocated and deallocated for the
+ * aliases and #NMUtilsEnumValueInfoFull:aliases will be NULL in the returned
+ * data.
+ *
+ * The caller is responsible of releasing the container, but not the contained
+ * data. Only #NMUtilsEnumValueInfoFull:aliases can be stolen (and set to NULL),
+ * and then the caller becomes the responsible to release it.
+ * 
+ * Return: (transfer container): an array of #NMUtilsEnumValueInfoFull.
+ */
+GArray *
+_nm_utils_enum_get_values_full(GType                       type,
+                               int                         from,
+                               int                         to,
+                               const NMUtilsEnumValueInfo *value_infos)
+{
+    NMUtilsEnumValueInfoFull v;
+    GArray                  *array;
+    int                      i;
+
+    nm_auto_unref_gtypeclass GTypeClass *klass = g_type_class_ref(type);
+    g_return_val_if_fail(G_IS_ENUM_CLASS(klass) || G_IS_FLAGS_CLASS(klass), NULL);
+
+    _ASSERT_enum_values_info(type, value_infos);
+
+    array = g_array_new(FALSE, FALSE, sizeof(NMUtilsEnumValueInfoFull));
 
     if (G_IS_ENUM_CLASS(klass)) {
         GEnumClass *enum_class = G_ENUM_CLASS(klass);
-        GEnumValue *enum_value;
 
         for (i = 0; i < enum_class->n_values; i++) {
-            enum_value = &enum_class->values[i];
-            if (enum_value->value >= from && enum_value->value <= to) {
-                if (_enum_is_valid_enum_nick(enum_value->value_nick))
-                    g_ptr_array_add(array, (gpointer) enum_value->value_nick);
-                else
-                    g_ptr_array_add(
-                        array,
-                        (gpointer) g_intern_string(nm_sprintf_buf(sbuf, "%d", enum_value->value)));
+            GEnumValue *enum_val = &enum_class->values[i];
+
+            if (enum_val->value >= from && enum_val->value <= to) {
+                _init_value_info_full(&v, FALSE, enum_val->value_nick, enum_val->value);
+                g_array_append_val(array, v);
             }
         }
-    } else if (G_IS_FLAGS_CLASS(klass)) {
+    } else {
         GFlagsClass *flags_class = G_FLAGS_CLASS(klass);
-        GFlagsValue *flags_value;
 
         for (i = 0; i < flags_class->n_values; i++) {
-            flags_value = &flags_class->values[i];
-            if (flags_value->value >= (guint) from && flags_value->value <= (guint) to) {
-                if (_enum_is_valid_flags_nick(flags_value->value_nick))
-                    g_ptr_array_add(array, (gpointer) flags_value->value_nick);
-                else
-                    g_ptr_array_add(
-                        array,
-                        (gpointer) g_intern_string(
-                            nm_sprintf_buf(sbuf, "0x%x", (unsigned) flags_value->value)));
+            GFlagsValue *flags_val = &flags_class->values[i];
+
+            if (flags_val->value >= (guint) from && flags_val->value <= (guint) to) {
+                _init_value_info_full(&v, TRUE, flags_val->value_nick, flags_val->value);
+                g_array_append_val(array, v);
             }
         }
-    } else {
-        g_type_class_unref(klass);
-        g_ptr_array_free(array, TRUE);
-        g_return_val_if_reached(NULL);
     }
 
-    g_type_class_unref(klass);
-    g_ptr_array_add(array, NULL);
+    if (value_infos) {
+        g_array_set_clear_func(array, (GDestroyNotify) _free_value_info_full);
+
+        for (i = 0; i < array->len; i++) {
+            NMUtilsEnumValueInfoFull *vi_full =
+                &nm_g_array_index(array, NMUtilsEnumValueInfoFull, i);
+            GPtrArray *aliases = g_ptr_array_new();
+
+            const NMUtilsEnumValueInfo *vi;
+            for (vi = value_infos; vi && vi->nick; vi++) {
+                if (vi->value == vi_full->value)
+                    g_ptr_array_add(aliases, (gpointer) vi->nick);
+            }
+
+            g_ptr_array_add(aliases, NULL);
+            vi_full->aliases = (const char **) g_ptr_array_free(aliases, FALSE);
+        }
+    }
 
-    return (const char **) g_ptr_array_free(array, FALSE);
+    return array;
 }
diff --git a/src/libnm-glib-aux/nm-enum-utils.h b/src/libnm-glib-aux/nm-enum-utils.h
index 6478cadf..a90e13fe 100644
--- a/src/libnm-glib-aux/nm-enum-utils.h
+++ b/src/libnm-glib-aux/nm-enum-utils.h
@@ -15,6 +15,13 @@ typedef struct _NMUtilsEnumValueInfo {
     int         value;
 } NMUtilsEnumValueInfo;
 
+typedef struct _NMUtilsEnumValueInfoFull {
+    const char  *nick;
+    const char **aliases;
+    const char  *value_str;
+    int          value;
+} NMUtilsEnumValueInfoFull;
+
 char    *_nm_utils_enum_to_str_full(GType                       type,
                                     int                         value,
                                     const char                 *sep,
@@ -27,6 +34,11 @@ gboolean _nm_utils_enum_from_str_full(GType                       type,
 
 const char **_nm_utils_enum_get_values(GType type, int from, int to);
 
+GArray *_nm_utils_enum_get_values_full(GType                       type,
+                                       int                         from,
+                                       int                         to,
+                                       const NMUtilsEnumValueInfo *value_infos);
+
 /*****************************************************************************/
 
 #endif /* __NM_ENUM_UTILS_H__ */
diff --git a/src/libnm-glib-aux/nm-errno.c b/src/libnm-glib-aux/nm-errno.c
index b76386a6..682bfea2 100644
--- a/src/libnm-glib-aux/nm-errno.c
+++ b/src/libnm-glib-aux/nm-errno.c
@@ -6,6 +6,7 @@
 #include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-errno.h"
+#include "libnm-std-aux/nm-std-utils.h"
 
 /*****************************************************************************/
 
@@ -100,26 +101,10 @@ nm_strerror(int nmerr)
 const char *
 nm_strerror_native_r(int errsv, char *buf, gsize buf_size)
 {
-    char *buf2;
+    NM_AUTO_PROTECT_ERRNO(errsv2);
+    const char *buf2;
 
-    nm_assert(buf);
-    nm_assert(buf_size > 0);
-
-#if (!defined(__GLIBC__) && !defined(__UCLIBC__)) || ((_POSIX_C_SOURCE >= 200112L) && !_GNU_SOURCE)
-    /* XSI-compliant */
-    {
-        int errno_saved = errno;
-
-        if (strerror_r(errsv, buf, buf_size) != 0) {
-            g_snprintf(buf, buf_size, "Unspecified errno %d", errsv);
-            errno = errno_saved;
-        }
-        buf2 = buf;
-    }
-#else
-    /* GNU-specific */
-    buf2 = strerror_r(errsv, buf, buf_size);
-#endif
+    buf2 = _nm_strerror_r(errsv, buf, buf_size);
 
     /* like g_strerror(), ensure that the error message is UTF-8. */
     if (!g_get_charset(NULL) && !g_utf8_validate(buf2, -1, NULL)) {
diff --git a/src/libnm-glib-aux/nm-glib.h b/src/libnm-glib-aux/nm-glib.h
index 9c90d429..cf76cf13 100644
--- a/src/libnm-glib-aux/nm-glib.h
+++ b/src/libnm-glib-aux/nm-glib.h
@@ -29,22 +29,6 @@
 
 /*****************************************************************************/
 
-static inline void
-__g_type_ensure(GType type)
-{
-#if !GLIB_CHECK_VERSION(2, 34, 0)
-    if (G_UNLIKELY(type == (GType) -1))
-        g_error("can't happen");
-#else
-    G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
-    g_type_ensure(type);
-    G_GNUC_END_IGNORE_DEPRECATIONS;
-#endif
-}
-#define g_type_ensure __g_type_ensure
-
-/*****************************************************************************/
-
 /* glib 2.58+ defines an improved, type-safe variant of g_clear_pointer(). Reimplement
  * that.
  *
@@ -81,71 +65,6 @@ __g_type_ensure(GType type)
 
 /*****************************************************************************/
 
-#if !GLIB_CHECK_VERSION(2, 34, 0)
-
-/* These are used to clean up the output of test programs; we can just let
- * them no-op in older glib.
- */
-#define g_test_expect_message(log_domain, log_level, pattern)
-#define g_test_assert_expected_messages()
-
-#else
-
-/* We build with -DGLIB_MAX_ALLOWED_VERSION set to 2.32 to make sure we don't
- * accidentally use new API that we shouldn't. But we don't want warnings for
- * the APIs that we emulate above.
- */
-
-#define g_test_expect_message(domain, level, format...) \
-    G_STMT_START                                        \
-    {                                                   \
-        G_GNUC_BEGIN_IGNORE_DEPRECATIONS                \
-        g_test_expect_message(domain, level, format);   \
-        G_GNUC_END_IGNORE_DEPRECATIONS                  \
-    }                                                   \
-    G_STMT_END
-
-#define g_test_assert_expected_messages_internal(domain, file, line, func)  \
-    G_STMT_START                                                            \
-    {                                                                       \
-        G_GNUC_BEGIN_IGNORE_DEPRECATIONS                                    \
-        g_test_assert_expected_messages_internal(domain, file, line, func); \
-        G_GNUC_END_IGNORE_DEPRECATIONS                                      \
-    }                                                                       \
-    G_STMT_END
-
-#endif
-
-/*****************************************************************************/
-
-#if GLIB_CHECK_VERSION(2, 35, 0)
-/* For glib >= 2.36, g_type_init() is deprecated.
- * But since 2.35.1 (7c42ab23b55c43ab96d0ac2124b550bf1f49c1ec) this function
- * does nothing. Replace the call with empty statement. */
-#define nm_g_type_init() \
-    G_STMT_START         \
-    {                    \
-        (void) 0;        \
-    }                    \
-    G_STMT_END
-#else
-#define nm_g_type_init() \
-    G_STMT_START         \
-    {                    \
-        g_type_init();   \
-    }                    \
-    G_STMT_END
-#endif
-
-/*****************************************************************************/
-
-/* g_test_initialized() is only available since glib 2.36. */
-#if !GLIB_CHECK_VERSION(2, 36, 0)
-#define g_test_initialized() (g_test_config_vars->test_initialized)
-#endif
-
-/*****************************************************************************/
-
 /* g_assert_cmpmem() is only available since glib 2.46. */
 #if !GLIB_CHECK_VERSION(2, 45, 7)
 #define g_assert_cmpmem(m1, l1, m2, l2)                                                 \
@@ -189,136 +108,6 @@ nm_glib_check_version(guint major, guint minor, guint micro)
 
 /*****************************************************************************/
 
-/* g_test_skip() is only available since glib 2.38. Add a compatibility wrapper. */
-static inline void
-__nmtst_g_test_skip(const char *msg)
-{
-#if GLIB_CHECK_VERSION(2, 38, 0)
-    G_GNUC_BEGIN_IGNORE_DEPRECATIONS
-    g_test_skip(msg);
-    G_GNUC_END_IGNORE_DEPRECATIONS
-#else
-    g_debug("%s", msg);
-#endif
-}
-#define g_test_skip __nmtst_g_test_skip
-
-/*****************************************************************************/
-
-/* g_test_add_data_func_full() is only available since glib 2.34. Add a compatibility wrapper. */
-static inline void
-__g_test_add_data_func_full(const char    *testpath,
-                            gpointer       test_data,
-                            GTestDataFunc  test_func,
-                            GDestroyNotify data_free_func)
-{
-#if GLIB_CHECK_VERSION(2, 34, 0)
-    G_GNUC_BEGIN_IGNORE_DEPRECATIONS
-    g_test_add_data_func_full(testpath, test_data, test_func, data_free_func);
-    G_GNUC_END_IGNORE_DEPRECATIONS
-#else
-    g_return_if_fail(testpath != NULL);
-    g_return_if_fail(testpath[0] == '/');
-    g_return_if_fail(test_func != NULL);
-
-    g_test_add_vtable(testpath,
-                      0,
-                      test_data,
-                      NULL,
-                      (GTestFixtureFunc) test_func,
-                      (GTestFixtureFunc) data_free_func);
-#endif
-}
-#define g_test_add_data_func_full __g_test_add_data_func_full
-
-/*****************************************************************************/
-
-static inline gboolean
-nm_g_hash_table_replace(GHashTable *hash, gpointer key, gpointer value)
-{
-    /* glib 2.40 added a return value indicating whether the key already existed
-     * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */
-#if GLIB_CHECK_VERSION(2, 40, 0)
-    return g_hash_table_replace(hash, key, value);
-#else
-    gboolean contained = g_hash_table_contains(hash, key);
-
-    g_hash_table_replace(hash, key, value);
-    return !contained;
-#endif
-}
-
-static inline gboolean
-nm_g_hash_table_insert(GHashTable *hash, gpointer key, gpointer value)
-{
-    /* glib 2.40 added a return value indicating whether the key already existed
-     * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */
-#if GLIB_CHECK_VERSION(2, 40, 0)
-    return g_hash_table_insert(hash, key, value);
-#else
-    gboolean contained = g_hash_table_contains(hash, key);
-
-    g_hash_table_insert(hash, key, value);
-    return !contained;
-#endif
-}
-
-static inline gboolean
-nm_g_hash_table_add(GHashTable *hash, gpointer key)
-{
-    /* glib 2.40 added a return value indicating whether the key already existed
-     * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */
-#if GLIB_CHECK_VERSION(2, 40, 0)
-    return g_hash_table_add(hash, key);
-#else
-    gboolean contained = g_hash_table_contains(hash, key);
-
-    g_hash_table_add(hash, key);
-    return !contained;
-#endif
-}
-
-/*****************************************************************************/
-
-#if !GLIB_CHECK_VERSION(2, 40, 0) || defined(NM_GLIB_COMPAT_H_TEST)
-static inline void
-_nm_g_ptr_array_insert(GPtrArray *array, int index_, gpointer data)
-{
-    g_return_if_fail(array);
-    g_return_if_fail(index_ >= -1);
-    g_return_if_fail(index_ <= (int) array->len);
-
-    g_ptr_array_add(array, data);
-
-    if (index_ != -1 && index_ != (int) (array->len - 1)) {
-        memmove(&(array->pdata[index_ + 1]),
-                &(array->pdata[index_]),
-                (array->len - index_ - 1) * sizeof(gpointer));
-        array->pdata[index_] = data;
-    }
-}
-#endif
-
-#if !GLIB_CHECK_VERSION(2, 40, 0)
-#define g_ptr_array_insert(array, index, data)      \
-    G_STMT_START                                    \
-    {                                               \
-        _nm_g_ptr_array_insert(array, index, data); \
-    }                                               \
-    G_STMT_END
-#else
-#define g_ptr_array_insert(array, index, data)  \
-    G_STMT_START                                \
-    {                                           \
-        G_GNUC_BEGIN_IGNORE_DEPRECATIONS        \
-        g_ptr_array_insert(array, index, data); \
-        G_GNUC_END_IGNORE_DEPRECATIONS          \
-    }                                           \
-    G_STMT_END
-#endif
-
-/*****************************************************************************/
-
 #if !GLIB_CHECK_VERSION(2, 54, 0)
 static inline gboolean
 g_ptr_array_find(GPtrArray *haystack, gconstpointer needle, guint *index_)
@@ -346,106 +135,6 @@ g_ptr_array_find(GPtrArray *haystack, gconstpointer needle, guint *index_)
 
 /*****************************************************************************/
 
-#if !GLIB_CHECK_VERSION(2, 40, 0)
-static inline gboolean
-_g_key_file_save_to_file(GKeyFile *key_file, const char *filename, GError **error)
-{
-    char    *contents;
-    gboolean success;
-    gsize    length;
-
-    g_return_val_if_fail(key_file != NULL, FALSE);
-    g_return_val_if_fail(filename != NULL, FALSE);
-    g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
-
-    contents = g_key_file_to_data(key_file, &length, NULL);
-    g_assert(contents != NULL);
-
-    success = g_file_set_contents(filename, contents, length, error);
-    g_free(contents);
-
-    return success;
-}
-#define g_key_file_save_to_file(key_file, filename, error) \
-    _g_key_file_save_to_file(key_file, filename, error)
-#else
-#define g_key_file_save_to_file(key_file, filename, error)             \
-    ({                                                                 \
-        gboolean _success;                                             \
-                                                                       \
-        G_GNUC_BEGIN_IGNORE_DEPRECATIONS                               \
-        _success = g_key_file_save_to_file(key_file, filename, error); \
-        G_GNUC_END_IGNORE_DEPRECATIONS                                 \
-        _success;                                                      \
-    })
-#endif
-
-/*****************************************************************************/
-
-#if GLIB_CHECK_VERSION(2, 36, 0)
-#define g_credentials_get_unix_pid(creds, error)                                        \
-    ({                                                                                  \
-        G_GNUC_BEGIN_IGNORE_DEPRECATIONS(g_credentials_get_unix_pid)((creds), (error)); \
-        G_GNUC_END_IGNORE_DEPRECATIONS                                                  \
-    })
-#else
-#define g_credentials_get_unix_pid(creds, error)                                          \
-    ({                                                                                    \
-        struct ucred *native_creds;                                                       \
-                                                                                          \
-        native_creds = g_credentials_get_native((creds), G_CREDENTIALS_TYPE_LINUX_UCRED); \
-        g_assert(native_creds);                                                           \
-        native_creds->pid;                                                                \
-    })
-#endif
-
-/*****************************************************************************/
-
-#if !GLIB_CHECK_VERSION(2, 40, 0) || defined(NM_GLIB_COMPAT_H_TEST)
-static inline gpointer *
-_nm_g_hash_table_get_keys_as_array(GHashTable *hash_table, guint *length)
-{
-    GHashTableIter iter;
-    gpointer       key, *ret;
-    guint          i = 0;
-
-    g_return_val_if_fail(hash_table, NULL);
-
-    ret = g_new0(gpointer, g_hash_table_size(hash_table) + 1);
-    g_hash_table_iter_init(&iter, hash_table);
-
-    while (g_hash_table_iter_next(&iter, &key, NULL))
-        ret[i++] = key;
-
-    ret[i] = NULL;
-
-    if (length)
-        *length = i;
-
-    return ret;
-}
-#endif
-#if !GLIB_CHECK_VERSION(2, 40, 0)
-#define g_hash_table_get_keys_as_array(hash_table, length) \
-    ({ _nm_g_hash_table_get_keys_as_array(hash_table, length); })
-#else
-#define g_hash_table_get_keys_as_array(hash_table, length)               \
-    ({                                                                   \
-        G_GNUC_BEGIN_IGNORE_DEPRECATIONS(g_hash_table_get_keys_as_array) \
-        ((hash_table), (length));                                        \
-        G_GNUC_END_IGNORE_DEPRECATIONS                                   \
-    })
-#endif
-
-/*****************************************************************************/
-
-#ifndef g_info
-/* g_info was only added with 2.39.2 */
-#define g_info(...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__)
-#endif
-
-/*****************************************************************************/
-
 #ifdef g_steal_pointer
 #undef g_steal_pointer
 #endif
@@ -486,69 +175,6 @@ _nm_g_strv_contains(const char *const *strv, const char *str)
 
 /*****************************************************************************/
 
-static inline GVariant *
-_nm_g_variant_new_take_string(char *string)
-{
-#if !GLIB_CHECK_VERSION(2, 36, 0)
-    GVariant *value;
-
-    g_return_val_if_fail(string != NULL, NULL);
-    g_return_val_if_fail(g_utf8_validate(string, -1, NULL), NULL);
-
-    value = g_variant_new_string(string);
-    g_free(string);
-    return value;
-#elif !GLIB_CHECK_VERSION(2, 38, 0)
-    GVariant *value;
-    GBytes   *bytes;
-
-    g_return_val_if_fail(string != NULL, NULL);
-    g_return_val_if_fail(g_utf8_validate(string, -1, NULL), NULL);
-
-    bytes = g_bytes_new_take(string, strlen(string) + 1);
-    value = g_variant_new_from_bytes(G_VARIANT_TYPE_STRING, bytes, TRUE);
-    g_bytes_unref(bytes);
-
-    return value;
-#else
-    G_GNUC_BEGIN_IGNORE_DEPRECATIONS
-    return g_variant_new_take_string(string);
-    G_GNUC_END_IGNORE_DEPRECATIONS
-#endif
-}
-#define g_variant_new_take_string _nm_g_variant_new_take_string
-
-/*****************************************************************************/
-
-#if !GLIB_CHECK_VERSION(2, 38, 0)
-_nm_printf(1, 2) static inline GVariant *_nm_g_variant_new_printf(const char *format_string, ...)
-{
-    char   *string;
-    va_list ap;
-
-    g_return_val_if_fail(format_string, NULL);
-
-    va_start(ap, format_string);
-    string = g_strdup_vprintf(format_string, ap);
-    va_end(ap);
-
-    return g_variant_new_take_string(string);
-}
-#define g_variant_new_printf(...) _nm_g_variant_new_printf(__VA_ARGS__)
-#else
-#define g_variant_new_printf(...)               \
-    ({                                          \
-        GVariant *_v;                           \
-                                                \
-        G_GNUC_BEGIN_IGNORE_DEPRECATIONS        \
-        _v = g_variant_new_printf(__VA_ARGS__); \
-        G_GNUC_END_IGNORE_DEPRECATIONS          \
-        _v;                                     \
-    })
-#endif
-
-/*****************************************************************************/
-
 /* Recent glib also casts the results to typeof(Obj), but only if
  *
  *  ( defined(g_has_typeof) && GLIB_VERSION_MAX_ALLOWED >= GLIB_VERSION_2_56 )
@@ -766,4 +392,10 @@ _nm_deprecated("Don't use this API") void _nm_forbidden_glib_api_n(gconstpointer
 
 /*****************************************************************************/
 
+/* Use either NM_MIN()/NM_MAX() or (if that doesn't work) use NM_MIN_CONST()/NM_MAX_CONST(). */
+#undef MIN
+#undef MAX
+
+/*****************************************************************************/
+
 #endif /* __NM_GLIB_H__ */
diff --git a/src/libnm-glib-aux/nm-hash-utils.h b/src/libnm-glib-aux/nm-hash-utils.h
index c1306200..703c00a4 100644
--- a/src/libnm-glib-aux/nm-hash-utils.h
+++ b/src/libnm-glib-aux/nm-hash-utils.h
@@ -14,6 +14,30 @@
 #define NM_HASH_SEED_16(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af) \
     ((const guint8[16]){a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af})
 
+struct _nm_packed _nm_hash_seed_16_u64_data {
+    guint64 s1;
+    guint64 s2;
+};
+
+G_STATIC_ASSERT(sizeof(struct _nm_hash_seed_16_u64_data) == 16);
+G_STATIC_ASSERT(sizeof(struct _nm_hash_seed_16_u64_data) == sizeof(guint64) * 2);
+
+/* c_siphash_init() has a seed of 16 bytes (NM_HASH_SEED_16()). That is
+ * cumbersome to use, because we usually just hardcode an arbitrarily chosen,
+ * fixed number.
+ *
+ * This macro takes a u64 (in host-endianness) and returns a 16 byte seed
+ * buffer. The number will be big endian encoded, to be architecture
+ * independent. */
+#define NM_HASH_SEED_16_U64(u64)                              \
+    ((const guint8 *) ((gpointer)                             \
+                       & ((struct _nm_hash_seed_16_u64_data){ \
+                           .s1 = htobe64((u64)),              \
+                           .s2 = 0,                           \
+                       })))
+
+/*****************************************************************************/
+
 void nm_hash_siphash42_init(CSipHash *h, guint static_seed);
 
 /* Siphash24 of binary buffer @arr and @len, using the randomized seed from
@@ -22,7 +46,7 @@ void nm_hash_siphash42_init(CSipHash *h, guint static_seed);
  * Note, that this is guaranteed to use siphash42 under the hood (contrary to
  * all other NMHash API, which leave this undefined). That matters at the point,
  * where the caller needs to be sure that a reasonably strong hashing algorithm
- * is used.  (Yes, NMHash is all about siphash24, but otherwise that is not promised
+ * is used.  (Yes, NMHash is all about siphash42, but otherwise that is not promised
  * anywhere).
  *
  * Another difference is, that this returns guint64 (not guint like other NMHash functions).
@@ -32,6 +56,9 @@ void nm_hash_siphash42_init(CSipHash *h, guint static_seed);
  * Then, why not use c_siphash_hash() directly? Because this also uses the randomized,
  * per-run hash-seed like nm_hash_init(). So, you get siphash24 with a random
  * seed (which is cached for the current run of the program).
+ *
+ * WARNING: the static_seed gets randomized like with nm_hash*(). If you want a reproducible
+ *   siphash42, use instead `c_siphash_hash(NM_HASH_SEED_16_U64(number), ptr, len)`.
  */
 static inline guint64
 nm_hash_siphash42(guint static_seed, const void *ptr, gsize n)
diff --git a/src/libnm-glib-aux/nm-macros-internal.h b/src/libnm-glib-aux/nm-macros-internal.h
index 9972dc44..a9253ed4 100644
--- a/src/libnm-glib-aux/nm-macros-internal.h
+++ b/src/libnm-glib-aux/nm-macros-internal.h
@@ -571,10 +571,10 @@ nm_str_realloc(char *str)
     }
 
 #define NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY(suffix, obj_type)                                 \
-    static inline void _nm_gobject_notify_together_impl##suffix(                              \
+    static inline void _nm_gobject_notify_together_full_v##suffix(                            \
         obj_type                     *obj,                                                    \
-        guint                         n,                                                      \
-        const _PropertyEnums##suffix *props)                                                  \
+        const _PropertyEnums##suffix *props,                                                  \
+        guint                         n)                                                      \
     {                                                                                         \
         GObject *const gobj        = (GObject *) obj;                                         \
         GParamSpec    *pspec_first = NULL;                                                    \
@@ -614,7 +614,7 @@ nm_str_realloc(char *str)
                                                                                               \
     _nm_unused static inline void _notify##suffix(obj_type *obj, _PropertyEnums##suffix prop) \
     {                                                                                         \
-        _nm_gobject_notify_together_impl##suffix(obj, 1, &prop);                              \
+        _nm_gobject_notify_together_full_v##suffix(obj, &prop, 1);                            \
     }                                                                                         \
     _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
 
@@ -631,15 +631,15 @@ nm_str_realloc(char *str)
 /* invokes _notify() for all arguments (of type _PropertyEnums). Note, that if
  * there are more than one prop arguments, this will involve a freeze/thaw
  * of GObject property notifications. */
-#define nm_gobject_notify_together_full(suffix, obj, ...)                            \
-    G_STMT_START                                                                     \
-    {                                                                                \
-        const _PropertyEnums##suffix _props[] = {__VA_ARGS__};                       \
-                                                                                     \
-        G_STATIC_ASSERT(G_N_ELEMENTS(_props) == NM_NARG(__VA_ARGS__));               \
-                                                                                     \
-        _nm_gobject_notify_together_impl##suffix(obj, G_N_ELEMENTS(_props), _props); \
-    }                                                                                \
+#define nm_gobject_notify_together_full(suffix, obj, ...)                              \
+    G_STMT_START                                                                       \
+    {                                                                                  \
+        const _PropertyEnums##suffix _props[] = {__VA_ARGS__};                         \
+                                                                                       \
+        G_STATIC_ASSERT(G_N_ELEMENTS(_props) == NM_NARG(__VA_ARGS__));                 \
+                                                                                       \
+        _nm_gobject_notify_together_full_v##suffix(obj, _props, G_N_ELEMENTS(_props)); \
+    }                                                                                  \
     G_STMT_END
 
 #define nm_gobject_notify_together(obj, ...) nm_gobject_notify_together_full(, obj, __VA_ARGS__)
diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c
index 34a3af20..7d623bd9 100644
--- a/src/libnm-glib-aux/nm-shared-utils.c
+++ b/src/libnm-glib-aux/nm-shared-utils.c
@@ -623,6 +623,149 @@ nm_g_variant_maybe_singleton_i(gint32 value)
 
 /*****************************************************************************/
 
+static int
+_variant_type_cmp(const GVariantType *type1, const GVariantType *type2)
+{
+    const char *string1;
+    const char *string2;
+    gsize       size;
+
+    NM_CMP_SELF(type1, type2);
+
+    size = g_variant_type_get_string_length(type1);
+
+    NM_CMP_DIRECT(size, g_variant_type_get_string_length(type2));
+
+    string1 = g_variant_type_peek_string(type1);
+    string2 = g_variant_type_peek_string(type2);
+
+    NM_CMP_DIRECT_MEMCMP(string1, string2, size);
+    return 0;
+}
+
+int
+nm_g_variant_type_cmp(const GVariantType *type1, const GVariantType *type2)
+{
+    int r;
+
+    r = _variant_type_cmp(type1, type2);
+    nm_assert((!!g_variant_type_equal(type1, type2)) == (r == 0));
+    return r;
+}
+
+/*****************************************************************************/
+
+typedef enum {
+    VARIANT_CMP_TYPE_VARIANT,
+    VARIANT_CMP_TYPE_STRDICT,
+    VARIANT_CMP_TYPE_VARDICT,
+} VariantCmpType;
+
+static int
+_variant_cmp_array(GVariant *value1, GVariant *value2, VariantCmpType type)
+{
+    gsize len;
+    gsize i;
+
+    len = g_variant_n_children(value1);
+
+    NM_CMP_DIRECT(len, g_variant_n_children(value2));
+
+    for (i = 0; i < len; i++) {
+        gs_unref_variant GVariant *child1 = g_variant_get_child_value(value1, i);
+        gs_unref_variant GVariant *child2 = g_variant_get_child_value(value2, i);
+        const char                *key1;
+        const char                *key2;
+        const char                *val1_str;
+        const char                *val2_str;
+
+        nm_assert(child1);
+        nm_assert(child2);
+
+        switch (type) {
+        case VARIANT_CMP_TYPE_VARIANT:
+            NM_CMP_RETURN(nm_g_variant_cmp(child1, child2));
+            break;
+        case VARIANT_CMP_TYPE_STRDICT:
+            g_variant_get(child1, "{&s&s}", &key1, &val1_str);
+            g_variant_get(child2, "{&s&s}", &key2, &val2_str);
+            NM_CMP_DIRECT_STRCMP(key1, key2);
+            NM_CMP_DIRECT_STRCMP(val1_str, val2_str);
+            break;
+        case VARIANT_CMP_TYPE_VARDICT:
+        {
+            gs_unref_variant GVariant *val1_var = NULL;
+            gs_unref_variant GVariant *val2_var = NULL;
+
+            g_variant_get(child1, "{&sv}", &key1, &val1_var);
+            g_variant_get(child2, "{&sv}", &key2, &val2_var);
+            NM_CMP_DIRECT_STRCMP(key1, key2);
+            NM_CMP_RETURN(nm_g_variant_cmp(val1_var, val2_var));
+            break;
+        }
+        }
+    }
+
+    return 0;
+}
+
+static int
+_variant_cmp_generic(GVariant *value1, GVariant *value2)
+{
+    gs_free char *str1 = NULL;
+    gs_free char *str2 = NULL;
+
+    /* This is like g_variant_equal(), which also resorts to pretty-printing
+     * the variants for comparison.
+     *
+     * Note that the variant types are already checked and equal. We thus don't
+     * need to include the type annotation. */
+    str1 = g_variant_print(value1, FALSE);
+    str2 = g_variant_print(value2, FALSE);
+
+    NM_CMP_DIRECT_STRCMP(str1, str2);
+    return 0;
+}
+
+static int
+_variant_cmp(GVariant *value1, GVariant *value2)
+{
+    const GVariantType *type;
+
+    NM_CMP_SELF(value1, value2);
+
+    type = g_variant_get_type(value1);
+
+    NM_CMP_RETURN(nm_g_variant_type_cmp(type, g_variant_get_type(value2)));
+
+    if (g_variant_type_is_basic(type))
+        NM_CMP_RETURN(g_variant_compare(value1, value2));
+    else if (g_variant_type_is_subtype_of(type, G_VARIANT_TYPE("a{ss}")))
+        NM_CMP_RETURN(_variant_cmp_array(value1, value2, VARIANT_CMP_TYPE_STRDICT));
+    else if (g_variant_type_is_subtype_of(type, G_VARIANT_TYPE("a{sv}")))
+        NM_CMP_RETURN(_variant_cmp_array(value1, value2, VARIANT_CMP_TYPE_VARDICT));
+    else if (g_variant_type_is_array(type) || g_variant_type_is_tuple(type))
+        NM_CMP_RETURN(_variant_cmp_array(value1, value2, VARIANT_CMP_TYPE_VARIANT));
+    else
+        NM_CMP_RETURN(_variant_cmp_generic(value1, value2));
+
+    return 0;
+}
+
+int
+nm_g_variant_cmp(GVariant *value1, GVariant *value2)
+{
+    int r;
+
+    r = _variant_cmp(value1, value2);
+
+    nm_assert((!!nm_g_variant_equal(value1, value2)) == (r == 0));
+
+    return r;
+}
+
+/*****************************************************************************/
+
 GHashTable *
 nm_strdict_clone(GHashTable *src)
 {
@@ -1937,7 +2080,7 @@ nm_utils_strsplit_quoted(const char *str)
     }
 
     if (!arr)
-        return g_new0(char *, 1);
+        return nm_strv_empty_new();
 
     /* We want to return an optimally sized strv array, with no excess
      * memory allocated. Hence, clone once more. */
@@ -1964,8 +2107,6 @@ nm_utils_strsplit_quoted(const char *str)
  * 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
@@ -1984,7 +2125,7 @@ _nm_strv_find_first(const char *const *list, gssize len, const char *needle)
             }
         } else {
             for (i = 0; i < len; i++) {
-                if (list[i] && !strcmp(needle, list[i]))
+                if (list[i] && nm_streq(needle, list[i]))
                     return i;
             }
         }
@@ -1993,7 +2134,7 @@ _nm_strv_find_first(const char *const *list, gssize len, const char *needle)
 
         if (list) {
             for (i = 0; list[i]; i++) {
-                if (strcmp(needle, list[i]) == 0)
+                if (nm_streq(needle, list[i]))
                     return i;
             }
         }
@@ -2078,7 +2219,7 @@ nm_strv_is_same_unordered(const char *const *strv1,
 }
 
 const char **
-nm_strv_cleanup_const(const char **strv, gboolean skip_empty, gboolean skip_repeated)
+nm_strv_cleanup_const(const char **strv, gboolean no_empty, gboolean no_duplicates)
 {
     gsize i;
     gsize j;
@@ -2086,13 +2227,12 @@ nm_strv_cleanup_const(const char **strv, gboolean skip_empty, gboolean skip_repe
     if (!strv || !*strv)
         return strv;
 
-    if (!skip_empty && !skip_repeated)
+    if (!no_empty && !no_duplicates)
         return strv;
 
     j = 0;
     for (i = 0; strv[i]; i++) {
-        if ((skip_empty && !*strv[i])
-            || (skip_repeated && nm_strv_find_first(strv, j, strv[i]) >= 0))
+        if ((no_empty && !*strv[i]) || (no_duplicates && nm_strv_contains(strv, j, strv[i])))
             continue;
         strv[j++] = strv[i];
     }
@@ -2101,7 +2241,7 @@ nm_strv_cleanup_const(const char **strv, gboolean skip_empty, gboolean skip_repe
 }
 
 char **
-nm_strv_cleanup(char **strv, gboolean strip_whitespace, gboolean skip_empty, gboolean skip_repeated)
+nm_strv_cleanup(char **strv, gboolean strip_whitespace, gboolean no_empty, gboolean no_duplicates)
 {
     gsize i;
     gsize j;
@@ -2115,12 +2255,11 @@ nm_strv_cleanup(char **strv, gboolean strip_whitespace, gboolean skip_empty, gbo
         for (i = 0; strv[i]; i++)
             g_strstrip(strv[i]);
     }
-    if (!skip_empty && !skip_repeated)
+    if (!no_empty && !no_duplicates)
         return strv;
     j = 0;
     for (i = 0; strv[i]; i++) {
-        if ((skip_empty && !*strv[i])
-            || (skip_repeated && nm_strv_find_first(strv, j, strv[i]) >= 0))
+        if ((no_empty && !*strv[i]) || (no_duplicates && nm_strv_contains(strv, j, strv[i])))
             g_free(strv[i]);
         else
             strv[j++] = strv[i];
@@ -2221,6 +2360,44 @@ nm_utils_error_is_notfound(GError *error)
 
 /*****************************************************************************/
 
+void
+nm_gobject_notify_together_by_pspec_v(gpointer                 obj,
+                                      const GParamSpec *const *param_specs,
+                                      gsize                    param_specs_len)
+{
+    GObject *const    gobj        = obj;
+    gboolean          frozen      = FALSE;
+    const GParamSpec *pspec_first = NULL;
+
+    nm_assert(G_IS_OBJECT(gobj));
+    nm_assert(param_specs_len > 0);
+
+    while (param_specs_len-- > 0) {
+        const GParamSpec *pspec = (param_specs++)[0];
+
+        if (!pspec)
+            continue;
+
+        if (!frozen) {
+            if (!pspec_first) {
+                pspec_first = pspec;
+                continue;
+            }
+            frozen = TRUE;
+            g_object_freeze_notify(gobj);
+            g_object_notify_by_pspec(gobj, (GParamSpec *) pspec_first);
+        }
+        g_object_notify_by_pspec(gobj, (GParamSpec *) pspec);
+    }
+
+    if (frozen)
+        g_object_thaw_notify(gobj);
+    else if (pspec_first)
+        g_object_notify_by_pspec(gobj, (GParamSpec *) pspec_first);
+}
+
+/*****************************************************************************/
+
 /**
  * nm_g_object_set_property:
  * @object: the target object
@@ -3584,6 +3761,9 @@ nm_strv_make_deep_copied_n(const char **strv, gsize len)
  *   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.
+ * @preserved_empty: affects how to handle if the strv array is empty (length 0).
+ *   If TRUE, results in a non-NULL, empty, allocated strv array. If FALSE,
+ *   returns NULL instead of an empty strv array.
  *
  * Like g_strdupv(), with two differences:
  *
@@ -3602,7 +3782,10 @@ nm_strv_make_deep_copied_n(const char **strv, gsize len)
  *   cloned or not.
  */
 char **
-_nm_strv_dup(const char *const *strv, gssize len, gboolean deep_copied)
+_nm_strv_dup_full(const char *const *strv,
+                  gssize             len,
+                  gboolean           deep_copied,
+                  gboolean           preserve_empty)
 {
     gsize  i, l;
     char **v;
@@ -3611,13 +3794,16 @@ _nm_strv_dup(const char *const *strv, gssize len, gboolean deep_copied)
         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. */
+
+    if (l == 0 && !preserve_empty) {
+        /* An empty strv array is not returned (as requested by
+         * !preserved_empty). Instead, return NULL. */
         return NULL;
     }
 
-    v = g_new(char *, l + 1);
+    nm_assert(l < G_MAXSIZE);
+
+    v = g_new(char *, l + 1u);
     for (i = 0; i < l; i++) {
         if (G_UNLIKELY(!strv[i])) {
             /* NULL strings are not allowed. Clear the remainder of the array
@@ -5775,40 +5961,81 @@ nm_utils_is_specific_hostname(const char *name)
 
 /*****************************************************************************/
 
-/* taken from systemd's uid_to_name(). */
-char *
-nm_utils_uid_to_name(uid_t uid)
+typedef struct {
+    struct passwd pw;
+    _nm_alignas(max_align_t) char buf[];
+} GetPwuidData;
+
+/**
+ * nm_getpwuid:
+ * uid: the user Id to look up
+ *
+ * Calls getpwuid_r() to lookup the passwd entry. See the manual.
+ * Allocates and returns a suitable buffer.
+ *
+ * The returned buffer is likely much large than required. You don't want to
+ * keep this buffer around for longer than necessary.
+ *
+ * Returns: (transfer full): the passwd entry, if found or NULL on error
+ *   or if the entry was not found.
+ */
+struct passwd *
+nm_getpwuid(uid_t uid)
 {
-    gs_free char *buf_heap = NULL;
-    char          buf_stack[4096];
-    gsize         bufsize;
-    char         *buf;
+    gs_free GetPwuidData *data = NULL;
+    gsize                 bufsize;
+    const gsize           OFFSET = G_STRUCT_OFFSET(GetPwuidData, buf);
+    long int              size_max;
 
-    bufsize = sizeof(buf_stack);
-    buf     = buf_stack;
+    size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
+    if (size_max > 0 && ((unsigned long int) size_max < G_MAXSIZE - OFFSET))
+        bufsize = size_max;
+    else
+        bufsize = 4096;
 
     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 (bufsize >= G_MAXSIZE - OFFSET)
+            return NULL;
+
+        nm_clear_g_free(&data);
+        data = g_malloc(OFFSET + bufsize);
+
+        r = getpwuid_r(uid, &data->pw, data->buf, bufsize, &pw);
+        if (r == 0) {
+            if (!pw)
+                return NULL;
+            nm_assert(pw == (gpointer) data);
+            return (gpointer) g_steal_pointer(&data);
+        }
 
         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;
     }
 }
 
+const char *
+nm_passwd_name(const struct passwd *pw)
+{
+    /* Normalize "pw->pw_name" and return it. */
+    return pw ? nm_str_not_empty(pw->pw_name) : NULL;
+}
+
+char *
+nm_utils_uid_to_name(uid_t uid)
+{
+    gs_free struct passwd *pw = NULL;
+
+    pw = nm_getpwuid(uid);
+    return g_strdup(nm_passwd_name(pw));
+}
+
 /* taken from systemd's nss_user_record_by_name() */
 gboolean
 nm_utils_name_to_uid(const char *name, uid_t *out_uid)
@@ -5886,6 +6113,9 @@ nm_utils_exp10(gint16 ex)
 gboolean
 _nm_utils_is_empty_ssid_arr(const guint8 *ssid, gsize len)
 {
+    if (len == 0)
+        return TRUE;
+
     /* Single white space is for Linksys APs */
     if (len == 1 && ssid[0] == ' ')
         return TRUE;
@@ -6925,10 +7155,10 @@ _poll_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
     else
         wait_ms = 0;
     if (poll_task_data->sleep_timeout_ms > 0)
-        wait_ms = MAX(wait_ms, poll_task_data->sleep_timeout_ms);
+        wait_ms = NM_MAX(wait_ms, poll_task_data->sleep_timeout_ms);
 
     poll_task_data->source_next_poll =
-        nm_g_source_attach(nm_g_timeout_source_new(MAX(1, wait_ms),
+        nm_g_source_attach(nm_g_timeout_source_new(NM_MAX(1, wait_ms),
                                                    G_PRIORITY_DEFAULT,
                                                    _poll_start_cb,
                                                    poll_task_data,
diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h
index e37cf5e6..ea38e083 100644
--- a/src/libnm-glib-aux/nm-shared-utils.h
+++ b/src/libnm-glib-aux/nm-shared-utils.h
@@ -97,6 +97,7 @@ typedef enum _nm_packed {
     /* No type, empty value */
     NM_PORT_KIND_NONE,
     NM_PORT_KIND_BOND,
+    NM_PORT_KIND_BRIDGE,
 } NMPortKind;
 
 /*****************************************************************************/
@@ -157,6 +158,7 @@ typedef enum {
 #define _NM_LINK_TYPE_SW_MASTER_FIRST NM_LINK_TYPE_BRIDGE
     NM_LINK_TYPE_BRIDGE,
     NM_LINK_TYPE_BOND,
+    NM_LINK_TYPE_HSR,
     NM_LINK_TYPE_TEAM,
 #define _NM_LINK_TYPE_SW_MASTER_LAST NM_LINK_TYPE_TEAM
 
@@ -331,9 +333,10 @@ gboolean nm_utils_memeqzero(gconstpointer data, gsize length);
 
 extern const void *const _NM_PTRARRAY_EMPTY[1];
 
-#define NM_PTRARRAY_EMPTY(type) ((type const *) _NM_PTRARRAY_EMPTY)
-#define NM_STRV_EMPTY()         ((char **) _NM_PTRARRAY_EMPTY)
-#define NM_STRV_EMPTY_CC()      NM_PTRARRAY_EMPTY(const char *)
+#define NM_PTRARRAY_EMPTY(type)     ((type const *) _NM_PTRARRAY_EMPTY)
+#define NM_STRV_EMPTY()             ((char **) _NM_PTRARRAY_EMPTY)
+#define NM_STRV_EMPTY_CC()          NM_PTRARRAY_EMPTY(const char *)
+#define NM_PTRARRAY_EMPTY_NEW(type) (g_new0(type, 1))
 
 static inline void
 nm_strbuf_init(char *buf, gsize len, char **p_buf_ptr, gsize *p_buf_len)
@@ -528,12 +531,10 @@ gssize _nm_strv_find_first(const char *const *list, gssize len, const char *need
 
 gboolean nm_strv_has_duplicate(const char *const *list, gssize len, gboolean is_sorted);
 
-const char **nm_strv_cleanup_const(const char **strv, gboolean skip_empty, gboolean skip_repeated);
+const char **nm_strv_cleanup_const(const char **strv, gboolean no_empty, gboolean no_duplicates);
 
-char **nm_strv_cleanup(char   **strv,
-                       gboolean strip_whitespace,
-                       gboolean skip_empty,
-                       gboolean skip_repeated);
+char **
+nm_strv_cleanup(char **strv, gboolean strip_whitespace, gboolean no_empty, gboolean no_duplicates);
 
 gboolean nm_strv_is_same_unordered(const char *const *strv1,
                                    gssize             len1,
@@ -1114,6 +1115,21 @@ nm_utils_error_set_literal(GError **error, int error_code, const char *literal)
 
 /*****************************************************************************/
 
+void nm_gobject_notify_together_by_pspec_v(gpointer                 obj,
+                                           const GParamSpec *const *param_specs,
+                                           gsize                    param_specs_len);
+
+#define nm_gobject_notify_together_by_pspec(obj, ...)                           \
+    G_STMT_START                                                                \
+    {                                                                           \
+        const GParamSpec *const _arr[] = {__VA_ARGS__};                         \
+                                                                                \
+        G_STATIC_ASSERT(NM_NARG(__VA_ARGS__) == G_N_ELEMENTS(_arr));            \
+                                                                                \
+        nm_gobject_notify_together_by_pspec_v((obj), _arr, G_N_ELEMENTS(_arr)); \
+    }                                                                           \
+    G_STMT_END
+
 gboolean nm_g_object_set_property(GObject      *object,
                                   const char   *property_name,
                                   const GValue *value,
@@ -1402,6 +1418,10 @@ nm_g_variant_builder_add_sv_str(GVariantBuilder *builder, const char *key, const
     nm_g_variant_builder_add_sv(builder, key, g_variant_new_string(str));
 }
 
+int nm_g_variant_type_cmp(const GVariantType *type1, const GVariantType *type2);
+
+int nm_g_variant_cmp(GVariant *value1, GVariant *value2);
+
 static inline void
 nm_g_source_destroy_and_unref(GSource *source)
 {
@@ -1851,6 +1871,8 @@ int nm_utils_hashtable_cmp(const GHashTable *a,
                            GCompareDataFunc  cmp_values,
                            gpointer          user_data);
 
+#define nm_strv_empty_new() NM_PTRARRAY_EMPTY_NEW(char *)
+
 char **nm_strv_make_deep_copied(const char **strv);
 
 char **nm_strv_make_deep_copied_n(const char **strv, gsize len);
@@ -1858,13 +1880,18 @@ char **nm_strv_make_deep_copied_n(const char **strv, gsize len);
 static inline char **
 nm_strv_make_deep_copied_nonnull(const char **strv)
 {
-    return nm_strv_make_deep_copied(strv) ?: g_new0(char *, 1);
+    return nm_strv_make_deep_copied(strv) ?: nm_strv_empty_new();
 }
 
-char **_nm_strv_dup(const char *const *strv, gssize len, gboolean deep_copied);
+char **_nm_strv_dup_full(const char *const *strv,
+                         gssize             len,
+                         gboolean           deep_copied,
+                         gboolean           preserve_empty);
 
-#define nm_strv_dup(strv, len, deep_copied) \
-    _nm_strv_dup(NM_CAST_STRV_CC(strv), (len), (deep_copied))
+#define nm_strv_dup_full(strv, len, deep_copied, preserve_empty) \
+    _nm_strv_dup_full(NM_CAST_STRV_CC(strv), (len), (deep_copied), (preserve_empty))
+
+#define nm_strv_dup(strv, len, deep_copied) nm_strv_dup_full((strv), (len), (deep_copied), FALSE)
 
 const char **_nm_strv_dup_packed(const char *const *strv, gssize len);
 
@@ -1942,7 +1969,27 @@ nm_g_array_unref(GArray *arr)
  * When accessing index zero, then this returns NULL if-and-only-if
  * "arr" is NULL or "arr->data" is NULL. In all other cases, this
  * returns the pointer &((Type*) arr->data)[idx]. Note that the pointer
- * may not be followed, if "idx" is equal to "arr->len". */
+ * may not be followed, if "idx" is equal to "arr->len".
+ *
+ * The reason to allow access one element past the length is the
+ * following usage:
+ *
+ *    ptr = nm_g_array_index_p(arr, Type, 0);
+ *    end = nm_g_array_index_p(arr, Type, length);
+ *    for (; ptr < end; ptr++) { ... }
+ *
+ * Another usage is to get a buffer, if the length might be zero. If
+ * length is zero, you cannot dereference the pointer, but it can be convenient
+ * to not require special casing:
+ *
+ *    // length might be zero.
+ *    nm_memdup(nm_g_array_index_p(arr, Type, length), sizeof(Type) * length);
+ *
+ * Note that in C, it's valid point one past the end of an array. So getting
+ * a pointer at index "length" is valid, and what nm_g_array_index_p() allows.
+ * If you don't need that, nm_g_array_index() is usually preferable,
+ * because it asserts against access at index "length".
+ */
 #define nm_g_array_index_p(arr, Type, idx)                                                       \
     ({                                                                                           \
         const GArray *const _arr_55 = (arr);                                                     \
@@ -2509,6 +2556,21 @@ nm_strv_ptrarray_get_unsafe(GPtrArray *arr, guint *out_len)
     return (const char *const *) arr->pdata;
 }
 
+static inline char **
+nm_strv_ptrarray_to_strv_full(const GPtrArray *a, gboolean not_null)
+{
+    if (!a)
+        return not_null ? nm_strv_empty_new() : NULL;
+    return nm_strv_dup_full((const char *const *) a->pdata, a->len, TRUE, TRUE);
+}
+
+static inline char **
+nm_strv_ptrarray_to_strv(const GPtrArray *a)
+{
+    /* Returns never NULL ("not_null"!) */
+    return nm_strv_ptrarray_to_strv_full(a, TRUE);
+}
+
 static inline GPtrArray *
 nm_strv_ptrarray_clone(const GPtrArray *src, gboolean null_if_empty)
 {
@@ -2977,100 +3039,248 @@ nm_strvarray_ensure(GArray **p)
         *p = g_array_new(TRUE, FALSE, sizeof(char *));
         g_array_set_clear_func(*p, nm_indirect_g_free);
     } else
-        nm_assert(g_array_get_element_size(*p) == sizeof(char *));
+        nm_assert(sizeof(char *) == g_array_get_element_size(*p));
 
     return *p;
 }
 
 static inline void
-nm_strvarray_add(GArray *array, const char *str)
+nm_strvarray_add_take(GArray *array, char *str)
 {
-    char *s;
-
     nm_assert(array);
-    nm_assert(g_array_get_element_size(array) == sizeof(char *));
+    nm_assert(sizeof(char *) == g_array_get_element_size(array));
 
-    s = g_strdup(str);
-    g_array_append_val(array, s);
+    /* The array is used as a NULL terminated strv array. Adding NULL is most
+     * likely a bug. Assert against it. */
+    nm_assert(str);
+
+    g_array_append_val(array, str);
+}
+
+static inline void
+nm_strvarray_add(GArray *array, const char *str)
+{
+    nm_strvarray_add_take(array, g_strdup(str));
 }
 
 static inline const char *
-nm_strvarray_get_idx(GArray *array, guint idx)
+nm_strvarray_get_idx(const GArray *array, guint idx)
 {
     return nm_g_array_index(array, const char *, idx);
 }
 
+/* nm_strvarray_get_idxnull_or_greturn() permits access at `len`,
+ * returning NULL. If the access is out of bounds, the assertion
+ * will fail (and also return NULL). */
+#define nm_strvarray_get_idxnull_or_greturn(arr, idx)           \
+    ({                                                          \
+        GArray *_arr = (arr);                                   \
+        gsize   _idx = (idx);                                   \
+        guint   _len = nm_g_array_len(_arr);                    \
+                                                                \
+        g_return_val_if_fail(_idx <= _len, NULL);               \
+                                                                \
+        _idx == _len ? NULL : nm_strvarray_get_idx(_arr, _idx); \
+    })
+
+/**
+ * nm_strvarray_get_strv_full:
+ * @arr: the strvarray.
+ * @length: (out) (nullable): optionally return the length of the result.
+ * @not_null: if true and @arr is NULL, return NM_STRV_EMPTY_CC() (otherwise NULL).
+ * @preserve_empty: if true and the array is empty, return an empty
+ *   strv array. Otherwise, return NULL.
+ *
+ * If "arr" is NULL, this returns NULL, unless "not_null" is true (in which
+ *   case the static NM_STRV_EMPTY_CC() is returned).
+ * If "arr" is empty, it depends on:
+ *   - if "preserve_empty" or "not_null", then the resulting strv array is the empty "arr".
+ *   - otherwise NULL is returned.
+ * Otherwise, returns the non-empty, non-deep-cloned strv array.
+ *
+ * Like nm_strvarray_get_strv_full_dup(), but the strings are not cloned.
+ *
+ * Returns: (transfer none): a strv list or NULL.
+ */
 static inline const char *const *
-nm_strvarray_get_strv_non_empty(GArray *arr, guint *length)
+nm_strvarray_get_strv_full(const GArray *arr,
+                           guint        *length,
+                           gboolean      not_null,
+                           gboolean      preserve_empty)
 {
-    nm_assert(!arr || g_array_get_element_size(arr) == sizeof(char *));
-
-    if (!arr || arr->len == 0) {
+    if (!arr) {
         NM_SET_OUT(length, 0);
-        return NULL;
+        return not_null ? NM_STRV_EMPTY_CC() : NULL;
     }
 
+    nm_assert(sizeof(char *) == g_array_get_element_size((GArray *) arr));
+
     NM_SET_OUT(length, arr->len);
+
+    if (arr->len == 0 && !(preserve_empty || not_null))
+        return NULL;
+
     return &g_array_index(arr, const char *, 0);
 }
 
+/**
+ * nm_strvarray_get_strv_full_dup:
+ * @arr: the strvarray.
+ * @length: (out) (nullable): optionally return the length of the result.
+ * @not_null: if true, never return NULL but allocate an empty strv array.
+ * @preserve_empty: if true and the array is empty, return an empty
+ *   strv array. Otherwise, return NULL.
+ *
+ * If "arr" is NULL, this returns NULL, unless "not_null" is true (in which case
+ *   am empty strv array is allocated.
+ * If "arr" is empty, it depends on:
+ *   - if "preserve_empty" || "not_null", then the resulting strv array is allocated (and empty).
+ *   - otherwise, NULL is returned.
+ * Otherwise, return the non-empty, deep-cloned strv array.
+ *
+ * Like nm_strvarray_get_strv_full(), but the strings are cloned.
+ *
+ * Returns: (transfer full): a deep-cloned strv list or NULL.
+ */
 static inline char **
-nm_strvarray_get_strv_non_empty_dup(GArray *arr, guint *length)
+nm_strvarray_get_strv_full_dup(const GArray *arr,
+                               guint        *length,
+                               gboolean      not_null,
+                               gboolean      preserve_empty)
 {
-    const char *const *strv;
+    if (!arr) {
+        NM_SET_OUT(length, 0);
+        return not_null ? nm_strv_empty_new() : NULL;
+    }
 
-    nm_assert(!arr || g_array_get_element_size(arr) == sizeof(char *));
+    nm_assert(sizeof(char *) == g_array_get_element_size((GArray *) arr));
 
-    if (!arr || arr->len == 0) {
-        NM_SET_OUT(length, 0);
+    NM_SET_OUT(length, arr->len);
+
+    if (arr->len == 0) {
+        if (preserve_empty || not_null)
+            return nm_strv_empty_new();
         return NULL;
     }
 
-    NM_SET_OUT(length, arr->len);
-    strv = &g_array_index(arr, const char *, 0);
-    return nm_strv_dup(strv, arr->len, TRUE);
+    return nm_strv_dup(&g_array_index(arr, const char *, 0), arr->len, TRUE);
 }
 
+/**
+ * nm_strvarray_get_strv_notnull:
+ * @arr: the strvarray.
+ * @length: (out) (nullable): optionally return the length of the result.
+ *
+ * This never returns NULL. If @arr is NULL, this returns NM_STRV_EMPTY_CC().
+ *
+ * Like nm_strvarray_get_strv_notempty(), but never returns NULL.
+ *
+ * Returns: (transfer none): a pointer to the strv list in @arr or NM_STRV_EMPTY_CC().
+ */
 static inline const char *const *
-nm_strvarray_get_strv(GArray **arr, guint *length)
+nm_strvarray_get_strv_notnull(const GArray *arr, guint *length)
 {
-    if (!*arr) {
-        NM_SET_OUT(length, 0);
-        return (const char *const *) arr;
-    }
+    return nm_strvarray_get_strv_full(arr, length, TRUE, TRUE);
+}
 
-    nm_assert(g_array_get_element_size(*arr) == sizeof(char *));
+/**
+ * nm_strvarray_get_strv_notempty:
+ * @arr: the strvarray.
+ * @length: (out) (nullable): optionally return the length of the result.
+ *
+ * This never returns an empty strv array. If @arr is NULL or empty, this
+ * returns NULL.
+ *
+ * Like nm_strvarray_get_strv_notempty_dup(), but does not clone strings.
+ *
+ * Returns: (transfer none): a pointer to the strv list in @arr or NULL.
+ */
+static inline const char *const *
+nm_strvarray_get_strv_notempty(const GArray *arr, guint *length)
+{
+    return nm_strvarray_get_strv_full(arr, length, FALSE, FALSE);
+}
 
-    NM_SET_OUT(length, (*arr)->len);
-    return &g_array_index(*arr, const char *, 0);
+/**
+ * nm_strvarray_get_strv_notempty_dup:
+ * @arr: the strvarray.
+ * @length: (out) (nullable): optionally return the length of the result.
+ *
+ * This never returns an empty strv array. If @arr is NULL or empty, this
+ * returns NULL.
+ *
+ * Like nm_strvarray_get_strv_notempty(), but clones strings.
+ *
+ * Returns: (transfer full): a deep-cloned strv list or NULL.
+ */
+static inline char **
+nm_strvarray_get_strv_notempty_dup(const GArray *arr, guint *length)
+{
+    return nm_strvarray_get_strv_full_dup(arr, length, FALSE, FALSE);
 }
 
+/**
+ * nm_strvarray_set_strv_full:
+ * @array: a pointer to the array to set.
+ * @strv: the strv array. May be NULL.
+ * @preserve_empty: how to treat if strv is empty (strv[0]==NULL).
+ *
+ * The old array will be freed (in a way so that the function is self-assignment
+ * safe).
+ *
+ * If "strv" is NULL, then the resulting GArray is NULL.
+ * If "strv" is empty, then it depends on "preserve_empty":
+ *   - if "preserve_empty", then the resulting GArray is allocated (and empty).
+ *   - if "!preserve_empty", then the resulting GArray is NULL.
+ * If "strv" is not empty, a GArray gets allocated and the strv array deep-cloned.
+ */
 static inline void
-nm_strvarray_set_strv(GArray **array, const char *const *strv)
+nm_strvarray_set_strv_full(GArray **array, const char *const *strv, gboolean preserve_empty)
 {
     gs_unref_array GArray *array_old = NULL;
 
     array_old = g_steal_pointer(array);
 
-    nm_assert(!array_old || g_array_get_element_size(array_old) == sizeof(char *));
+    nm_assert(!array_old || sizeof(char *) == g_array_get_element_size(array_old));
+
+    if (!strv)
+        return;
 
-    if (!strv || !strv[0])
+    if (!strv[0] && !preserve_empty) {
+        /* An empty strv array is treated like NULL. Don't allocate a GArray. */
         return;
+    }
 
     nm_strvarray_ensure(array);
     for (; strv[0]; strv++)
         nm_strvarray_add(*array, strv[0]);
 }
 
+/**
+ * nm_strvarray_set_strv:
+ * @array: a pointer to the array to set.
+ * @strv: the strv array. May be NULL.
+ *
+ * The old array will be freed (in a way so that the function is self-assignment
+ * safe).
+ *
+ * Note that this will never initialize an empty GArray. If strv is NULL or
+ * empty, the @array pointer will be set to NULL. */
+static inline void
+nm_strvarray_set_strv(GArray **array, const char *const *strv)
+{
+    nm_strvarray_set_strv_full(array, strv, FALSE);
+}
+
 static inline gssize
-nm_strvarray_find_first(GArray *strv, const char *needle)
+nm_strvarray_find_first(const GArray *strv, const char *needle)
 {
     guint i;
 
     nm_assert(needle);
 
     if (strv) {
-        nm_assert(g_array_get_element_size(strv) == sizeof(char *));
+        nm_assert(sizeof(char *) == g_array_get_element_size((GArray *) strv));
         for (i = 0; i < strv->len; i++) {
             if (nm_streq(needle, g_array_index(strv, const char *, i)))
                 return i;
@@ -3079,6 +3289,8 @@ nm_strvarray_find_first(GArray *strv, const char *needle)
     return -1;
 }
 
+#define nm_strvarray_contains(strv, needle) (nm_strvarray_find_first((strv), (needle)) >= 0)
+
 static inline gboolean
 nm_strvarray_remove_first(GArray *strv, const char *needle)
 {
@@ -3093,11 +3305,42 @@ nm_strvarray_remove_first(GArray *strv, const char *needle)
     return TRUE;
 }
 
+#define nm_strvarray_remove_index(strv, idx)                          \
+    G_STMT_START                                                      \
+    {                                                                 \
+        GArray *const _strv = (strv);                                 \
+        typeof(idx)   _idx  = (idx);                                  \
+                                                                      \
+        nm_assert(_strv);                                             \
+        nm_assert((uintmax_t) _idx < _strv->len);                     \
+        nm_assert(sizeof(char *) == g_array_get_element_size(_strv)); \
+                                                                      \
+        g_array_remove_index(_strv, (guint) _idx);                    \
+    }                                                                 \
+    G_STMT_END
+
+static inline void
+nm_strvarray_ensure_and_add(GArray **p, const char *str)
+{
+    nm_strvarray_add(nm_strvarray_ensure(p), str);
+}
+
+static inline gboolean
+nm_strvarray_ensure_and_add_unique(GArray **p, const char *str)
+{
+    nm_assert(p);
+
+    if (nm_strvarray_contains(*p, str))
+        return FALSE;
+    nm_strvarray_add(nm_strvarray_ensure(p), str);
+    return TRUE;
+}
+
 static inline int
 nm_strvarray_cmp(const GArray *a, const GArray *b)
 {
-    nm_assert(!a || sizeof(const char *const *) == g_array_get_element_size((GArray *) a));
-    nm_assert(!b || sizeof(const char *const *) == g_array_get_element_size((GArray *) b));
+    nm_assert(!a || sizeof(char *) == g_array_get_element_size((GArray *) a));
+    nm_assert(!b || sizeof(char *) == g_array_get_element_size((GArray *) b));
 
     NM_CMP_SELF(a, b);
 
@@ -3109,7 +3352,7 @@ nm_strvarray_cmp(const GArray *a, const GArray *b)
 static inline int
 _nm_strvarray_cmp_strv(const GArray *strv, const char *const *ss, gsize ss_len)
 {
-    nm_assert(!strv || sizeof(const char *const *) == g_array_get_element_size((GArray *) strv));
+    nm_assert(!strv || sizeof(char *) == g_array_get_element_size((GArray *) strv));
 
     return nm_strv_cmp_n(nm_g_array_data(strv), strv ? ((gssize) strv->len) : -1, ss, ss_len);
 }
@@ -3119,6 +3362,24 @@ _nm_strvarray_cmp_strv(const GArray *strv, const char *const *ss, gsize ss_len)
 #define nm_strvarray_equal_strv(strv, ss, ss_len) \
     (nm_strvarray_cmp_strv((strv), (ss), (ss_len)) == 0)
 
+static inline gboolean
+nm_strvarray_clear(GArray **array)
+{
+    gboolean cleared = FALSE;
+
+    nm_assert(array);
+    nm_assert(!*array || sizeof(char *) == g_array_get_element_size(*array));
+
+    if (*array) {
+        /* We always clear the GArray, but we return TRUE only if the
+         * array was non-empty before. */
+        if ((*array)->len > 0)
+            cleared = TRUE;
+        nm_clear_pointer(array, g_array_unref);
+    }
+    return cleared;
+}
+
 /*****************************************************************************/
 
 struct _NMVariantAttributeSpec {
@@ -3163,6 +3424,12 @@ gboolean nm_utils_is_localhost(const char *name);
 
 gboolean nm_utils_is_specific_hostname(const char *name);
 
+struct passwd;
+
+struct passwd *nm_getpwuid(uid_t uid);
+
+const char *nm_passwd_name(const struct passwd *pw);
+
 char    *nm_utils_uid_to_name(uid_t uid);
 gboolean nm_utils_name_to_uid(const char *name, uid_t *out_uid);
 
diff --git a/src/libnm-glib-aux/nm-test-utils.h b/src/libnm-glib-aux/nm-test-utils.h
index de6fd0f1..2a6a5d3a 100644
--- a/src/libnm-glib-aux/nm-test-utils.h
+++ b/src/libnm-glib-aux/nm-test-utils.h
@@ -134,7 +134,7 @@
     ({                                                                                        \
         gboolean     _not_expired             = TRUE;                                         \
         const gint64 nmtst_wait_start_us      = g_get_monotonic_time();                       \
-        const gint64 nmtst_wait_duration_us   = (max_wait_ms) *1000L;                         \
+        const gint64 nmtst_wait_duration_us   = (max_wait_ms) * 1000L;                        \
         const gint64 nmtst_wait_end_us        = nmtst_wait_start_us + nmtst_wait_duration_us; \
         gint64       _nmtst_wait_remaining_us = nmtst_wait_duration_us;                       \
         int          _nmtst_wait_iteration    = 0;                                            \
@@ -497,8 +497,6 @@ __nmtst_init(int        *argc,
 
     __nmtst_internal.assert_logging = !!assert_logging;
 
-    nm_g_type_init();
-
     is_debug = g_test_verbose();
 
     nmtst_debug = g_getenv("NMTST_DEBUG");
@@ -1228,7 +1226,7 @@ nmtst_rand_perm_strv(const char *const *strv)
     /* this returns a (scrambled) SHALLOW copy of the strv array! */
 
     n   = NM_PTRARRAY_LEN(strv);
-    res = (const char **) (nm_strv_dup(strv, n, FALSE) ?: g_new0(char *, 1));
+    res = (const char **) (nm_strv_dup(strv, n, FALSE) ?: nm_strv_empty_new());
     nmtst_rand_perm(NULL, res, res, sizeof(char *), n);
     return res;
 }
@@ -1803,7 +1801,7 @@ nmtst_inet_from_string(int addr_family, const char *str)
 static inline const char *
 nmtst_inet_to_string(int addr_family, gconstpointer addr)
 {
-    static _nm_thread_local char buf[NM_CONST_MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)];
+    static _nm_thread_local char buf[NM_MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)];
 
     g_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
     g_assert(addr);
@@ -2568,20 +2566,21 @@ nmtst_assert_connection_unnormalizable(NMConnection *con,
     g_clear_error(&error);
 }
 
-static inline void
-nmtst_assert_setting_verifies(NMSetting *setting)
-{
-    /* assert that the setting verifies without an error */
-
-    GError  *error = NULL;
-    gboolean success;
-
-    g_assert(NM_IS_SETTING(setting));
-
-    success = nm_setting_verify(setting, NULL, &error);
-    g_assert_no_error(error);
-    g_assert(success);
-}
+#define nmtst_assert_setting_verifies(setting)                  \
+    G_STMT_START                                                \
+    {                                                           \
+        NMSetting *_setting = NM_SETTING(setting);              \
+        GError    *_error   = NULL;                             \
+        gboolean   _success;                                    \
+                                                                \
+        /* assert that the setting verifies without an error */ \
+                                                                \
+        g_assert(NM_IS_SETTING(_setting));                      \
+                                                                \
+        _success = nm_setting_verify(_setting, NULL, &_error);  \
+        nmtst_assert_success(_success, _error);                 \
+    }                                                           \
+    G_STMT_END
 
 #if defined(__NM_SIMPLE_CONNECTION_H__) && NM_CHECK_VERSION(1, 10, 0) \
     && (!defined(NM_VERSION_MAX_ALLOWED) || NM_VERSION_MAX_ALLOWED >= NM_VERSION_1_10)
@@ -2605,7 +2604,7 @@ _nmtst_assert_connection_has_settings(NMConnection *connection,
 
     va_start(ap, has_at_most);
     while ((name = va_arg(ap, const char *))) {
-        if (!nm_g_hash_table_add(names, (gpointer) name))
+        if (!g_hash_table_add(names, (gpointer) name))
             g_assert_not_reached();
         g_ptr_array_add(names_arr, (gpointer) name);
     }
diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c
index 84d87d48..b19ac1ce 100644
--- a/src/libnm-glib-aux/tests/test-shared-general.c
+++ b/src/libnm-glib-aux/tests/test-shared-general.c
@@ -5,6 +5,8 @@
 
 #include "libnm-glib-aux/nm-default-glib-i18n-prog.h"
 
+#include <pwd.h>
+
 #include "libnm-std-aux/unaligned.h"
 #include "libnm-glib-aux/nm-random-utils.h"
 #include "libnm-glib-aux/nm-str-buf.h"
@@ -32,6 +34,13 @@ G_STATIC_ASSERT(_nm_alignof(NMEtherAddr) <= _nm_alignof(NMIPAddr));
 
 /*****************************************************************************/
 
+G_STATIC_ASSERT(_NM_INT_IS_SIGNED(1));
+G_STATIC_ASSERT(!_NM_INT_IS_SIGNED(1u));
+G_STATIC_ASSERT(_NM_INT_SAME_SIGNEDNESS((short) 1, 1l));
+G_STATIC_ASSERT(!_NM_INT_SAME_SIGNEDNESS((unsigned short) 1, 1l));
+
+/*****************************************************************************/
+
 static void
 test_nm_static_assert(void)
 {
@@ -58,6 +67,20 @@ test_nm_static_assert(void)
 /*****************************************************************************/
 
 static void
+test_max(void)
+{
+    /* Check that NM_MAX() of constant expressions is itself a constant. We
+     * build with -Wvla, so this is a constant! */
+    char buf1[NM_MAX(55, 40)];
+    char buf2[NM_MAX(1, NM_MAX(40, 55))];
+
+    G_STATIC_ASSERT(sizeof(buf1) == 55);
+    G_STATIC_ASSERT(sizeof(buf2) == 55);
+}
+
+/*****************************************************************************/
+
+static void
 test_gpid(void)
 {
     const int *int_ptr;
@@ -427,7 +450,7 @@ _strv_cmp_fuzz_input(const char *const  *in,
         if (l < 0)
             ss = g_strdupv((char **) in);
         else if (l == 0) {
-            ss = nmtst_get_rand_bool() ? NULL : g_new0(char *, 1);
+            ss = nmtst_get_rand_bool() ? NULL : nm_strv_empty_new();
         } else {
             ss = nm_memdup(in, sizeof(const char *) * l);
             for (i = 0; i < (gsize) l; i++)
@@ -2573,6 +2596,258 @@ test_nm_prioq(void)
 
 /*****************************************************************************/
 
+static const char *
+_getpwuid_name(uid_t uid)
+{
+    static struct passwd *pw;
+
+    pw = getpwuid(uid);
+    return pw ? nm_str_not_empty(pw->pw_name) : NULL;
+}
+
+static void
+test_uid_to_name(void)
+{
+    int i;
+
+    for (i = 0; i < 20; i++) {
+        gs_free char          *name = NULL;
+        gs_free struct passwd *pw   = NULL;
+        uid_t                  uid;
+
+        if (i < 5)
+            uid = i;
+        else
+            uid = nmtst_get_rand_uint32() % 2000u;
+
+        name = nm_utils_uid_to_name(uid);
+        g_assert_cmpstr(name, ==, _getpwuid_name(uid));
+
+        pw = nm_getpwuid(uid);
+        g_assert_cmpstr(name, ==, nm_passwd_name(pw));
+    }
+}
+
+/*****************************************************************************/
+
+static void
+compare_ints(void)
+{
+    GVariant *value1, *value2;
+
+    value1 = g_variant_new_int32(5);
+    value2 = g_variant_new_int32(5);
+    g_assert(nm_g_variant_cmp(value1, value2) == 0);
+
+    g_variant_unref(value2);
+    value2 = g_variant_new_int32(10);
+    g_assert(nm_g_variant_cmp(value1, value2) < 0);
+
+    g_variant_unref(value2);
+    value2 = g_variant_new_int32(-1);
+    g_assert(nm_g_variant_cmp(value1, value2) > 0);
+
+    g_variant_unref(value1);
+    g_variant_unref(value2);
+}
+
+static void
+compare_strings(void)
+{
+    GVariant   *value1, *value2;
+    const char *str1 = "hello";
+    const char *str2 = "world";
+
+    value1 = g_variant_new_string(str1);
+    value2 = g_variant_new_string(str1);
+    g_assert(nm_g_variant_cmp(value1, value2) == 0);
+
+    g_variant_unref(value2);
+    value2 = g_variant_new_string(str2);
+    g_assert(nm_g_variant_cmp(value1, value2) < 0);
+
+    g_assert(nm_g_variant_cmp(value2, value1) > 0);
+
+    g_variant_unref(value1);
+    g_variant_unref(value2);
+}
+
+static void
+compare_strv(void)
+{
+    GVariant         *value1, *value2;
+    const char *const strv1[] = {"foo", "bar", "baz", NULL};
+    const char *const strv2[] = {"foo", "bar", "bar", NULL};
+    const char *const strv3[] = {"foo", "bar", NULL};
+    const char *const strv4[] = {"foo", "bar", "baz", "bam", NULL};
+
+    value1 = g_variant_new_strv(strv1, -1);
+    value2 = g_variant_new_strv(strv1, -1);
+    g_assert(nm_g_variant_cmp(value1, value2) == 0);
+
+    g_variant_unref(value2);
+    value2 = g_variant_new_strv(strv2, -1);
+    g_assert(nm_g_variant_cmp(value1, value2) != 0);
+
+    g_variant_unref(value2);
+    value2 = g_variant_new_strv(strv3, -1);
+    g_assert(nm_g_variant_cmp(value1, value2) != 0);
+
+    g_variant_unref(value2);
+    value2 = g_variant_new_strv(strv4, -1);
+    g_assert(nm_g_variant_cmp(value1, value2) != 0);
+
+    g_variant_unref(value1);
+    g_variant_unref(value2);
+}
+
+static void
+compare_arrays(void)
+{
+    GVariant *value1, *value2;
+    guint32   array[] = {0, 1, 2, 3, 4};
+
+    value1 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
+                                       array,
+                                       G_N_ELEMENTS(array),
+                                       sizeof(guint32));
+    value2 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
+                                       array,
+                                       G_N_ELEMENTS(array),
+                                       sizeof(guint32));
+
+    g_assert(nm_g_variant_cmp(value1, value2) == 0);
+
+    g_variant_unref(value2);
+    value2 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
+                                       array + 1,
+                                       G_N_ELEMENTS(array) - 1,
+                                       sizeof(guint32));
+    g_assert(nm_g_variant_cmp(value1, value2) != 0);
+
+    array[0] = 7;
+    g_variant_unref(value2);
+    value2 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
+                                       array,
+                                       G_N_ELEMENTS(array),
+                                       sizeof(guint32));
+    g_assert(nm_g_variant_cmp(value1, value2) != 0);
+
+    g_variant_unref(value1);
+    g_variant_unref(value2);
+}
+
+static void
+compare_str_hash(void)
+{
+    GVariant       *value1, *value2;
+    GVariantBuilder builder;
+
+    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}"));
+    g_variant_builder_add(&builder, "{ss}", "key1", "hello");
+    g_variant_builder_add(&builder, "{ss}", "key2", "world");
+    g_variant_builder_add(&builder, "{ss}", "key3", "!");
+    value1 = g_variant_builder_end(&builder);
+
+    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}"));
+    g_variant_builder_add(&builder, "{ss}", "key3", "!");
+    g_variant_builder_add(&builder, "{ss}", "key2", "world");
+    g_variant_builder_add(&builder, "{ss}", "key1", "hello");
+    value2 = g_variant_builder_end(&builder);
+
+    g_assert(nm_g_variant_cmp(value1, value2) != 0);
+
+    g_variant_unref(value1);
+    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+    g_variant_builder_add(&builder, "{sv}", "key1", g_variant_new_string("hello"));
+    g_variant_builder_add(&builder, "{sv}", "key2", g_variant_new_string("world"));
+    g_variant_builder_add(&builder, "{sv}", "key3", g_variant_new_string("!"));
+    value1 = g_variant_builder_end(&builder);
+
+    g_variant_unref(value2);
+    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+    g_variant_builder_add(&builder, "{sv}", "key1", g_variant_new_string("hello"));
+    g_variant_builder_add(&builder, "{sv}", "key2", g_variant_new_string("world"));
+    g_variant_builder_add(&builder, "{sv}", "key3", g_variant_new_string("!"));
+    value2 = g_variant_builder_end(&builder);
+
+    g_assert(nm_g_variant_cmp(value1, value2) == 0);
+
+    g_variant_unref(value2);
+    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}"));
+    g_variant_builder_add(&builder, "{ss}", "key1", "hello");
+    g_variant_builder_add(&builder, "{ss}", "key3", "!");
+    value2 = g_variant_builder_end(&builder);
+
+    g_assert(nm_g_variant_cmp(value1, value2) != 0);
+    g_assert(nm_g_variant_cmp(value2, value1) != 0);
+
+    g_variant_unref(value2);
+    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}"));
+    g_variant_builder_add(&builder, "{ss}", "key1", "hello");
+    g_variant_builder_add(&builder, "{ss}", "key2", "moon");
+    g_variant_builder_add(&builder, "{ss}", "key3", "!");
+    value2 = g_variant_builder_end(&builder);
+
+    g_assert(nm_g_variant_cmp(value1, value2) != 0);
+
+    g_variant_unref(value1);
+    g_variant_unref(value2);
+}
+
+static void
+compare_ip6_addresses(void)
+{
+    GVariant       *value1, *value2;
+    struct in6_addr addr1;
+    struct in6_addr addr2;
+    struct in6_addr addr3;
+    guint32         prefix1 = 64;
+    guint32         prefix2 = 64;
+    guint32         prefix3 = 0;
+
+    inet_pton(AF_INET6, "1:2:3:4:5:6:7:8", &addr1);
+    inet_pton(AF_INET6, "ffff:2:3:4:5:6:7:8", &addr2);
+    inet_pton(AF_INET6, "::", &addr3);
+
+    value1 = g_variant_new(
+        "(@ayu@ay)",
+        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr1.s6_addr, 16, 1),
+        prefix1,
+        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1));
+
+    value2 = g_variant_new(
+        "(@ayu@ay)",
+        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr1.s6_addr, 16, 1),
+        prefix1,
+        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1));
+
+    g_assert(nm_g_variant_cmp(value1, value2) == 0);
+
+    g_variant_unref(value2);
+    value2 = g_variant_new(
+        "(@ayu@ay)",
+        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr2.s6_addr, 16, 1),
+        prefix2,
+        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1));
+
+    g_assert(nm_g_variant_cmp(value1, value2) != 0);
+
+    g_variant_unref(value2);
+    value2 = g_variant_new(
+        "(@ayu@ay)",
+        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1),
+        prefix3,
+        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1));
+
+    g_assert(nm_g_variant_cmp(value1, value2) != 0);
+
+    g_variant_unref(value1);
+    g_variant_unref(value2);
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE();
 
 int
@@ -2581,6 +2856,7 @@ main(int argc, char **argv)
     nmtst_init(&argc, &argv, TRUE);
 
     g_test_add_func("/general/test_nm_static_assert", test_nm_static_assert);
+    g_test_add_func("/general/test_max", test_max);
     g_test_add_func("/general/test_gpid", test_gpid);
     g_test_add_func("/general/test_monotonic_timestamp", test_monotonic_timestamp);
     g_test_add_func("/general/test_timespect_to", test_timespect_to);
@@ -2623,6 +2899,14 @@ main(int argc, char **argv)
     g_test_add_func("/general/test_garray", test_garray);
     g_test_add_func("/general/test_nm_prioq", test_nm_prioq);
     g_test_add_func("/general/test_nm_random", test_nm_random);
+    g_test_add_func("/general/test_uid_to_name", test_uid_to_name);
+
+    g_test_add_func("/libnm/compare/ints", compare_ints);
+    g_test_add_func("/libnm/compare/strings", compare_strings);
+    g_test_add_func("/libnm/compare/strv", compare_strv);
+    g_test_add_func("/libnm/compare/arrays", compare_arrays);
+    g_test_add_func("/libnm/compare/str_hash", compare_str_hash);
+    g_test_add_func("/libnm/compare/ip6_addresses", compare_ip6_addresses);
 
     return g_test_run();
 }