about summary refs log tree commit diff
path: root/src/libnm-glib-aux/nm-shared-utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-glib-aux/nm-shared-utils.c')
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.c304
1 files changed, 267 insertions, 37 deletions
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,