summary refs log tree commit diff
path: root/shared/nm-glib-aux
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2020-06-28 18:58:14 +0200
committerMichael Biebl <biebl@debian.org>2020-06-28 18:58:14 +0200
commita54ac63bbf9b2c71026ac9028a8ffaf186cf3c82 (patch)
tree6a32883bd916c4096357b35298beff6db8bcd0b5 /shared/nm-glib-aux
parent45e8e1149027529194982212c804c0468aa01d98 (diff)
New upstream version 1.25.90 upstream/1.25.90
Diffstat (limited to 'shared/nm-glib-aux')
-rw-r--r--shared/nm-glib-aux/nm-glib.h12
-rw-r--r--shared/nm-glib-aux/nm-macros-internal.h46
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.c190
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.h181
4 files changed, 406 insertions, 23 deletions
diff --git a/shared/nm-glib-aux/nm-glib.h b/shared/nm-glib-aux/nm-glib.h
index 7a5b8edd..2df51795 100644
--- a/shared/nm-glib-aux/nm-glib.h
+++ b/shared/nm-glib-aux/nm-glib.h
@@ -382,9 +382,8 @@ _nm_g_hash_table_get_keys_as_array (GHashTable *hash_table,
 
 /*****************************************************************************/
 
-#if !GLIB_CHECK_VERSION(2, 44, 0)
 static inline gpointer
-g_steal_pointer (gpointer pp)
+_nm_g_steal_pointer (gpointer pp)
 {
 	gpointer *ptr = (gpointer *) pp;
 	gpointer ref;
@@ -394,13 +393,20 @@ g_steal_pointer (gpointer pp)
 
 	return ref;
 }
+
+#if !GLIB_CHECK_VERSION(2, 44, 0)
+static inline gpointer
+g_steal_pointer (gpointer pp)
+{
+	return _nm_g_steal_pointer (pp);
+}
 #endif
 
 #ifdef g_steal_pointer
 #undef g_steal_pointer
 #endif
 #define g_steal_pointer(pp) \
-	((typeof (*(pp))) g_steal_pointer (pp))
+	((typeof (*(pp))) _nm_g_steal_pointer (pp))
 
 /*****************************************************************************/
 
diff --git a/shared/nm-glib-aux/nm-macros-internal.h b/shared/nm-glib-aux/nm-macros-internal.h
index d5a8513e..f56ed856 100644
--- a/shared/nm-glib-aux/nm-macros-internal.h
+++ b/shared/nm-glib-aux/nm-macros-internal.h
@@ -630,6 +630,14 @@ NM_G_ERROR_MSG (GError *error)
 		NM_CONSTCAST_FULL (type, (obj), _obj, GObject, ##__VA_ARGS__); \
 	})
 
+#define NM_ENSURE_NOT_NULL(ptr) \
+	({ \
+		typeof (ptr) _ptr = (ptr); \
+		\
+		nm_assert (_ptr != NULL); \
+		_ptr; \
+	})
+
 #if _NM_CC_SUPPORT_GENERIC
 /* returns @value, if the type of @value matches @type.
  * This requires support for C11 _Generic(). If no support is
@@ -929,23 +937,38 @@ nm_streq0 (const char *s1, const char *s2)
 
 #define NM_STR_HAS_PREFIX(str, prefix) \
 	({ \
-		const char *const _str = (str); \
+		const char *const _str_has_prefix = (str); \
 		\
-		_str && (strncmp ((str), ""prefix"", NM_STRLEN (prefix)) == 0); \
+		nm_assert (strlen (prefix) == NM_STRLEN (prefix)); \
+		\
+		   _str_has_prefix \
+		&& (strncmp (_str_has_prefix, ""prefix"", NM_STRLEN (prefix)) == 0); \
 	})
 
 #define NM_STR_HAS_SUFFIX(str, suffix) \
 	({ \
-		const char *_str; \
+		const char *const _str_has_suffix = (str); \
 		gsize _l; \
 		\
-		(   (_str = (str)) \
-		 && ((_l = strlen (_str)) >= NM_STRLEN (suffix)) \
-		 && (memcmp (&_str[_l - NM_STRLEN (suffix)], \
+		nm_assert (strlen (suffix) == NM_STRLEN (suffix)); \
+		\
+		(   _str_has_suffix \
+		 && ((_l = strlen (_str_has_suffix)) >= NM_STRLEN (suffix)) \
+		 && (memcmp (&_str_has_suffix[_l - NM_STRLEN (suffix)], \
 		             ""suffix"", \
 		             NM_STRLEN (suffix)) == 0)); \
 	})
 
+/* whether @str starts with the string literal @prefix and is followed by
+ * some other text. It is like NM_STR_HAS_PREFIX() && !nm_streq() together. */
+#define NM_STR_HAS_PREFIX_WITH_MORE(str, prefix) \
+	({ \
+		const char *const _str_has_prefix_with_more = (str); \
+		\
+		   NM_STR_HAS_PREFIX (_str_has_prefix_with_more, ""prefix"") \
+		&& _str_has_prefix_with_more[NM_STRLEN (prefix)] != '\0'; \
+	})
+
 /*****************************************************************************/
 
 static inline GString *
@@ -966,16 +989,23 @@ nm_gstring_add_space_delimiter (GString *str)
 	return str;
 }
 
+static inline gboolean
+nm_str_is_empty (const char *str)
+{
+	/* %NULL is also accepted, and also "empty". */
+	return !str || !str[0];
+}
+
 static inline const char *
 nm_str_not_empty (const char *str)
 {
-	return str && str[0] ? str : NULL;
+	return !nm_str_is_empty (str) ? str : NULL;
 }
 
 static inline char *
 nm_strdup_not_empty (const char *str)
 {
-	return str && str[0] ? g_strdup (str) : NULL;
+	return !nm_str_is_empty (str) ? g_strdup (str) : NULL;
 }
 
 static inline char *
diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c
index 5dd099a7..39e98a31 100644
--- a/shared/nm-glib-aux/nm-shared-utils.c
+++ b/shared/nm-glib-aux/nm-shared-utils.c
@@ -13,10 +13,14 @@
 #include <sys/syscall.h>
 #include <glib-unix.h>
 #include <net/if.h>
+#include <net/ethernet.h>
 
 #include "nm-errno.h"
 #include "nm-str-buf.h"
 
+G_STATIC_ASSERT (sizeof (NMUtilsNamedEntry) == sizeof (const char *));
+G_STATIC_ASSERT (G_STRUCT_OFFSET (NMUtilsNamedValue, value_ptr) == sizeof (const char *));
+
 /*****************************************************************************/
 
 const void *const _NM_PTRARRAY_EMPTY[1] = { NULL };
@@ -85,6 +89,11 @@ nm_ip_addr_set_from_untrusted (int addr_family,
 
 /*****************************************************************************/
 
+G_STATIC_ASSERT (ETH_ALEN == sizeof (struct ether_addr));
+G_STATIC_ASSERT (ETH_ALEN == 6);
+
+/*****************************************************************************/
+
 gsize
 nm_utils_get_next_realloc_size (gboolean true_realloc, gsize requested)
 {
@@ -2456,11 +2465,12 @@ _str_buf_append_c_escape_octal (NMStrBuf *strbuf,
 }
 
 gconstpointer
-nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_free)
+nm_utils_buf_utf8safe_unescape (const char *str, NMUtilsStrUtf8SafeFlags flags, gsize *out_len, gpointer *to_free)
 {
+	gboolean strip_spaces = NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES);
 	NMStrBuf strbuf;
-	gsize len;
 	const char *s;
+	gsize len;
 
 	g_return_val_if_fail (to_free, NULL);
 	g_return_val_if_fail (out_len, NULL);
@@ -2471,16 +2481,29 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr
 		return NULL;
 	}
 
+	if (strip_spaces)
+		str = nm_str_skip_leading_spaces (str);
+
 	len = strlen (str);
 
 	s = memchr (str, '\\', len);
 	if (!s) {
+		if (   strip_spaces
+		    && len > 0
+		    && g_ascii_isspace (str[len - 1])) {
+			len--;
+			while (   len > 0
+			       && g_ascii_isspace (str[len - 1]))
+				len--;
+			*out_len = len;
+			return (*to_free = g_strndup (str, len));
+		}
 		*out_len = len;
 		*to_free = NULL;
 		return str;
 	}
 
-	nm_str_buf_init (&strbuf, len, FALSE);
+	nm_str_buf_init (&strbuf, len + 1u, FALSE);
 
 	nm_str_buf_append_len (&strbuf, str, s - str);
 	str = s;
@@ -2494,7 +2517,7 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr
 		ch = (++str)[0];
 
 		if (ch == '\0') {
-			// error. Trailing '\\'
+			/* error. Trailing '\\' */
 			break;
 		}
 
@@ -2533,7 +2556,14 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr
 
 		s = strchr (str, '\\');
 		if (!s) {
-			nm_str_buf_append (&strbuf, str);
+			gsize l = strlen (str);
+
+			if (strip_spaces) {
+				while (   l > 0
+				       && g_ascii_isspace (str[l - 1]))
+					l--;
+			}
+			nm_str_buf_append_len (&strbuf, str, l);
 			break;
 		}
 
@@ -2541,6 +2571,11 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr
 		str = s;
 	}
 
+	/* assert that no reallocation was necessary. For one, unescaping should
+	 * never result in a longer string than the input. Also, when unescaping
+	 * secrets, we want to ensure that we don't leak secrets in memory. */
+	nm_assert (strbuf.allocated == len + 1u);
+
 	return (*to_free = nm_str_buf_finalize (&strbuf,
 	                                        out_len));
 }
@@ -2670,16 +2705,33 @@ nm_utils_buf_utf8safe_escape_bytes (GBytes *bytes, NMUtilsStrUtf8SafeFlags flags
 	return nm_utils_buf_utf8safe_escape (p, l, flags, to_free);
 }
 
+char *
+nm_utils_buf_utf8safe_escape_cp (gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags)
+{
+	const char *s_const;
+	char *s;
+
+	s_const = nm_utils_buf_utf8safe_escape (buf, buflen, flags, &s);
+	nm_assert (!s || s == s_const);
+	return s ?: g_strdup (s_const);
+}
+
 /*****************************************************************************/
 
 const char *
-nm_utils_str_utf8safe_unescape (const char *str, char **to_free)
+nm_utils_str_utf8safe_unescape (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free)
 {
+	const char *res;
 	gsize len;
 
 	g_return_val_if_fail (to_free, NULL);
 
-	return nm_utils_buf_utf8safe_unescape (str, &len, (gpointer *) to_free);
+	res = nm_utils_buf_utf8safe_unescape (str, flags, &len, (gpointer *) to_free);
+
+	nm_assert (   (!res && len == 0)
+	           || (strlen (res) <= len));
+
+	return res;
 }
 
 /**
@@ -2737,11 +2789,11 @@ nm_utils_str_utf8safe_escape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags)
 }
 
 char *
-nm_utils_str_utf8safe_unescape_cp (const char *str)
+nm_utils_str_utf8safe_unescape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags)
 {
 	char *s;
 
-	str = nm_utils_str_utf8safe_unescape (str, &s);
+	str = nm_utils_str_utf8safe_unescape (str, flags, &s);
 	return s ?: g_strdup (str);
 }
 
@@ -4817,3 +4869,123 @@ nm_str_buf_append_printf (NMStrBuf *strbuf,
 
 	strbuf->_priv_len += (gsize) l;
 }
+
+/*****************************************************************************/
+
+/**
+ * nm_indirect_g_free:
+ * @arg: a pointer to a pointer that is to be freed.
+ *
+ * This does the same as nm_clear_g_free(arg) (g_clear_pointer (arg, g_free)).
+ * This is for example useful when you have a GArray with pointers and a
+ * clear function to free them. g_array_set_clear_func()'s destroy notify
+ * function gets a pointer to the array location, so we have to follow
+ * the first pointer.
+ */
+void
+nm_indirect_g_free (gpointer arg)
+{
+	gpointer *p = arg;
+
+	nm_clear_g_free (p);
+}
+
+/*****************************************************************************/
+
+static char *
+attribute_escape (const char *src, char c1, char c2)
+{
+	char *ret, *dest;
+
+	dest = ret = g_malloc (strlen (src) * 2 + 1);
+
+	while (*src) {
+		if (*src == c1 || *src == c2 || *src == '\\')
+			*dest++ = '\\';
+		*dest++ = *src++;
+	}
+	*dest++ = '\0';
+
+	return ret;
+}
+
+void
+_nm_utils_format_variant_attributes_full (GString *str,
+                                          const NMUtilsNamedValue *values,
+                                          guint num_values,
+                                          char attr_separator,
+                                          char key_value_separator)
+{
+	const char *name, *value;
+	GVariant *variant;
+	char *escaped;
+	char buf[64];
+	char sep = 0;
+	guint i;
+
+	for (i = 0; i < num_values; i++) {
+		name = values[i].name;
+		variant = (GVariant *) values[i].value_ptr;
+		value = NULL;
+
+		if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT32))
+			value = nm_sprintf_buf (buf, "%u", g_variant_get_uint32 (variant));
+		else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_INT32))
+			value = nm_sprintf_buf (buf, "%d", (int) g_variant_get_int32 (variant));
+		else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT64))
+			value = nm_sprintf_buf (buf, "%"G_GUINT64_FORMAT, g_variant_get_uint64 (variant));
+		else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BYTE))
+			value = nm_sprintf_buf (buf, "%hhu", g_variant_get_byte (variant));
+		else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BOOLEAN))
+			value = g_variant_get_boolean (variant) ? "true" : "false";
+		else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING))
+			value = g_variant_get_string (variant, NULL);
+		else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BYTESTRING)) {
+			/* FIXME: there is no guarantee that the byte array
+			 * is valid UTF-8.*/
+			value = g_variant_get_bytestring (variant);
+		} else
+			continue;
+
+		if (sep)
+			g_string_append_c (str, sep);
+
+		escaped = attribute_escape (name, attr_separator, key_value_separator);
+		g_string_append (str, escaped);
+		g_free (escaped);
+
+		g_string_append_c (str, key_value_separator);
+
+		escaped = attribute_escape (value, attr_separator, key_value_separator);
+		g_string_append (str, escaped);
+		g_free (escaped);
+
+		sep = attr_separator;
+	}
+}
+
+char *
+_nm_utils_format_variant_attributes (GHashTable *attributes,
+                                     char attr_separator,
+                                     char key_value_separator)
+{
+	GString *str = NULL;
+	gs_free NMUtilsNamedValue *values = NULL;
+	guint len;
+
+	g_return_val_if_fail (attr_separator, NULL);
+	g_return_val_if_fail (key_value_separator, NULL);
+
+	if (!attributes || !g_hash_table_size (attributes))
+		return NULL;
+
+	values = nm_utils_named_values_from_str_dict (attributes, &len);
+
+	str = g_string_new ("");
+	_nm_utils_format_variant_attributes_full (str,
+	                                          values,
+	                                          len,
+	                                          attr_separator,
+	                                          key_value_separator);
+	return g_string_free (str, FALSE);
+}
diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h
index d5990c2d..eeb8be5b 100644
--- a/shared/nm-glib-aux/nm-shared-utils.h
+++ b/shared/nm-glib-aux/nm-shared-utils.h
@@ -178,6 +178,24 @@ nm_ip4_addr_is_localhost (in_addr_t addr4)
 
 /*****************************************************************************/
 
+struct ether_addr;
+
+static inline int
+nm_utils_ether_addr_cmp (const struct ether_addr *a1, const struct ether_addr *a2)
+{
+	nm_assert (a1);
+	nm_assert (a2);
+	return memcmp (a1, a2, 6 /*ETH_ALEN*/);
+}
+
+static inline gboolean
+nm_utils_ether_addr_equal (const struct ether_addr *a1, const struct ether_addr *a2)
+{
+	return nm_utils_ether_addr_cmp (a1, a2) == 0;
+}
+
+/*****************************************************************************/
+
 #define NM_UTILS_INET_ADDRSTRLEN INET6_ADDRSTRLEN
 
 static inline const char *
@@ -926,6 +944,21 @@ _nm_g_slice_free_fcn_define (32)
 
 /*****************************************************************************/
 
+/* Like g_error_matches() however:
+ * - as macro it is always inlined.
+ * - the @domain is usually a error quark getter function that cannot
+ *   be inlined. This macro calls the getter only if there is an error (lazy).
+ * - accept a list of allowed codes, instead of only one.
+ */
+#define nm_g_error_matches(error, err_domain, ...) \
+	({ \
+		const GError *const _error = (error); \
+		\
+		   _error \
+		&& _error->domain == (err_domain) \
+		&& NM_IN_SET (_error->code, __VA_ARGS__); \
+	})
+
 static inline void
 nm_g_set_error_take (GError **error, GError *error_take)
 {
@@ -1143,6 +1176,25 @@ GParamSpec *nm_g_object_class_find_property_from_gtype (GType gtype,
 
 /*****************************************************************************/
 
+#define _NM_G_PARAM_SPEC_CAST(param_spec, _value_type, _c_type) \
+	({ \
+		const GParamSpec *const _param_spec = (param_spec); \
+		\
+		nm_assert (   !_param_spec \
+		           || _param_spec->value_type == (_value_type)); \
+		((const _c_type *) _param_spec); \
+	})
+
+#define NM_G_PARAM_SPEC_CAST_BOOLEAN(param_spec) _NM_G_PARAM_SPEC_CAST (param_spec, G_TYPE_BOOLEAN, GParamSpecBoolean)
+#define NM_G_PARAM_SPEC_CAST_UINT(param_spec)    _NM_G_PARAM_SPEC_CAST (param_spec, G_TYPE_UINT,    GParamSpecUInt)
+#define NM_G_PARAM_SPEC_CAST_UINT64(param_spec)  _NM_G_PARAM_SPEC_CAST (param_spec, G_TYPE_UINT64,  GParamSpecUInt64)
+
+#define NM_G_PARAM_SPEC_GET_DEFAULT_BOOLEAN(param_spec) (NM_G_PARAM_SPEC_CAST_BOOLEAN (NM_ENSURE_NOT_NULL (param_spec))->default_value)
+#define NM_G_PARAM_SPEC_GET_DEFAULT_UINT(param_spec)    (NM_G_PARAM_SPEC_CAST_UINT    (NM_ENSURE_NOT_NULL (param_spec))->default_value)
+#define NM_G_PARAM_SPEC_GET_DEFAULT_UINT64(param_spec)  (NM_G_PARAM_SPEC_CAST_UINT64  (NM_ENSURE_NOT_NULL (param_spec))->default_value)
+
+/*****************************************************************************/
+
 GType nm_g_type_find_implementing_class_for_property (GType gtype,
                                                       const char *pname);
 
@@ -1150,20 +1202,39 @@ GType nm_g_type_find_implementing_class_for_property (GType gtype,
 
 typedef enum {
 	NM_UTILS_STR_UTF8_SAFE_FLAG_NONE                = 0,
+
+	/* This flag only has an effect during escaping. */
 	NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL         = 0x0001,
+
+	/* This flag only has an effect during escaping. */
 	NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII    = 0x0002,
+
+	/* This flag only has an effect during escaping to ensure we
+	 * don't leak secrets in memory. Note that during unescape we
+	 * know the maximum result size from the beginning, and no
+	 * reallocation happens. Thus, unescape always avoids leaking
+	 * secrets already. */
 	NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET              = 0x0004,
+
+	/* This flag only has an effect during unescaping. It means
+	 * that non-escaped whitespaces (g_ascii_isspace()) will be
+	 * stripped from the front and end of the string. Note that
+	 * this flag is only useful for gracefully accepting user input
+	 * with spaces. With this flag, escape and unescape may no longer
+	 * yield the original input. */
+	NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES    = 0x0008,
 } NMUtilsStrUtf8SafeFlags;
 
 const char *nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags, char **to_free);
+char *nm_utils_buf_utf8safe_escape_cp (gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags);
 const char *nm_utils_buf_utf8safe_escape_bytes (GBytes *bytes, NMUtilsStrUtf8SafeFlags flags, char **to_free);
-gconstpointer nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_free);
+gconstpointer nm_utils_buf_utf8safe_unescape (const char *str, NMUtilsStrUtf8SafeFlags flags, gsize *out_len, gpointer *to_free);
 
 const char *nm_utils_str_utf8safe_escape   (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free);
-const char *nm_utils_str_utf8safe_unescape (const char *str, char **to_free);
+const char *nm_utils_str_utf8safe_unescape (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free);
 
 char *nm_utils_str_utf8safe_escape_cp   (const char *str, NMUtilsStrUtf8SafeFlags flags);
-char *nm_utils_str_utf8safe_unescape_cp (const char *str);
+char *nm_utils_str_utf8safe_unescape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags);
 
 char *nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flags);
 
@@ -1211,6 +1282,30 @@ nm_g_variant_is_of_type (GVariant *value,
 }
 
 static inline void
+nm_g_variant_builder_add_sv (GVariantBuilder *builder, const char *key, GVariant *val)
+{
+	g_variant_builder_add (builder, "{sv}", key, val);
+}
+
+static inline void
+nm_g_variant_builder_add_sv_bytearray (GVariantBuilder *builder, const char *key, const guint8 *arr, gsize len)
+{
+	g_variant_builder_add (builder, "{sv}", key, g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, arr, len, 1));
+}
+
+static inline void
+nm_g_variant_builder_add_sv_uint32 (GVariantBuilder *builder, const char *key, guint32 val)
+{
+	nm_g_variant_builder_add_sv (builder, key, g_variant_new_uint32 (val));
+}
+
+static inline void
+nm_g_variant_builder_add_sv_str (GVariantBuilder *builder, const char *key, const char *str)
+{
+	nm_g_variant_builder_add_sv (builder, key, g_variant_new_string (str));
+}
+
+static inline void
 nm_g_source_destroy_and_unref (GSource *source)
 {
 	g_source_destroy (source);
@@ -1355,6 +1450,8 @@ typedef struct {
 	};
 } NMUtilsNamedValue;
 
+#define NM_UTILS_NAMED_VALUE_INIT(n, v)     { .name = (n), .value_ptr = (v) }
+
 NMUtilsNamedValue *nm_utils_named_values_from_str_dict_with_sort (GHashTable *hash,
                                                                   guint *out_len,
                                                                   GCompareDataFunc compare_func,
@@ -1436,6 +1533,22 @@ char *nm_utils_g_slist_strlist_join (const GSList *a, const char *separator);
 /*****************************************************************************/
 
 static inline guint
+nm_g_array_len (const GArray *arr)
+{
+	return arr ? arr->len : 0u;
+}
+
+/*****************************************************************************/
+
+static inline guint
+nm_g_ptr_array_len (const GPtrArray *arr)
+{
+	return arr ? arr->len : 0u;
+}
+
+/*****************************************************************************/
+
+static inline guint
 nm_g_hash_table_size (GHashTable *hash)
 {
 	return hash ? g_hash_table_size (hash) : 0u;
@@ -1848,6 +1961,8 @@ nm_utils_strdup_reset (char **dst, const char *src)
 	return TRUE;
 }
 
+void nm_indirect_g_free (gpointer arg);
+
 /*****************************************************************************/
 
 /* nm_utils_get_next_realloc_size() is used to grow buffers exponentially, when
@@ -1887,4 +2002,64 @@ gboolean nm_utils_ifname_valid (const char* name,
                                 NMUtilsIfaceType type,
                                 GError **error);
 
+/*****************************************************************************/
+
+static inline GArray *
+nm_strvarray_ensure (GArray **p)
+{
+	if (!*p) {
+		*p = g_array_new (TRUE, FALSE, sizeof (char *));
+		g_array_set_clear_func (*p, nm_indirect_g_free);
+	}
+	return *p;
+}
+
+static inline void
+nm_strvarray_add (GArray *array, const char *str)
+{
+	char *s;
+
+	s = g_strdup (str);
+	g_array_append_val (array, s);
+}
+
+static inline const char *const*
+nm_strvarray_get_strv (GArray **arr, guint *length)
+{
+	if (!*arr) {
+		NM_SET_OUT (length, 0);
+		return (const char *const*) arr;
+	}
+
+	NM_SET_OUT (length, (*arr)->len);
+	return &g_array_index (*arr, const char *, 0);
+}
+
+static inline void
+nm_strvarray_set_strv (GArray **array, const char *const*strv)
+{
+	gs_unref_array GArray *array_old = NULL;
+
+	array_old = g_steal_pointer (array);
+
+	if (!strv || !strv[0])
+		return;
+
+	nm_strvarray_ensure (array);
+	for (; strv[0]; strv++)
+		nm_strvarray_add (*array, strv[0]);
+}
+
+/*****************************************************************************/
+
+void _nm_utils_format_variant_attributes_full (GString *str,
+                                               const NMUtilsNamedValue *values,
+                                               guint num_values,
+                                               char attr_separator,
+                                               char key_value_separator);
+
+char *_nm_utils_format_variant_attributes (GHashTable *attributes,
+                                           char attr_separator,
+                                           char key_value_separator);
+
 #endif /* __NM_SHARED_UTILS_H__ */