summary refs log tree commit diff
path: root/shared/nm-utils/nm-shared-utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'shared/nm-utils/nm-shared-utils.c')
-rw-r--r--shared/nm-utils/nm-shared-utils.c720
1 files changed, 667 insertions, 53 deletions
diff --git a/shared/nm-utils/nm-shared-utils.c b/shared/nm-utils/nm-shared-utils.c
index d0019c11..ba38237b 100644
--- a/shared/nm-utils/nm-shared-utils.c
+++ b/shared/nm-utils/nm-shared-utils.c
@@ -97,7 +97,7 @@ nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...)
 {
 	char *p = *buf;
 	va_list args;
-	gint retval;
+	int retval;
 
 	if (*len == 0)
 		return;
@@ -106,7 +106,7 @@ nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...)
 	retval = g_vsnprintf (p, *len, format, args);
 	va_end (args);
 
-	if (retval >= *len) {
+	if ((gsize) retval >= *len) {
 		*buf = &p[*len];
 		*len = 0;
 	} else {
@@ -115,6 +115,131 @@ nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...)
 	}
 }
 
+/**
+ * nm_utils_strbuf_seek_end:
+ * @buf: the input/output buffer
+ * @len: the input/output lenght of the buffer.
+ *
+ * Commonly, one uses nm_utils_strbuf_append*(), to incrementally
+ * append strings to the buffer. However, sometimes we need to use
+ * existing API to write to the buffer.
+ * After doing so, we want to adjust the buffer counter.
+ * Essentially,
+ *
+ *   g_snprintf (buf, len, ...);
+ *   nm_utils_strbuf_seek_end (&buf, &len);
+ *
+ * is almost the same as
+ *
+ *   nm_utils_strbuf_append (&buf, &len, ...);
+ *
+ * The only difference is the behavior when the string got truncated:
+ * nm_utils_strbuf_append() will recognize that and set the remaining
+ * length to zero.
+ *
+ * In general, the behavior is:
+ *
+ *  - if *len is zero, do nothing
+ *  - if the buffer contains a NUL byte within the first *len characters,
+ *    the buffer is pointed to the NUL byte and len is adjusted. In this
+ *    case, the remaining *len is always >= 1.
+ *    In particular, that is also the case if the NUL byte is at the very last
+ *    position ((*buf)[*len -1]). That happens, when the previous operation
+ *    either fit the string exactly into the buffer or the string was truncated
+ *    by g_snprintf(). The difference cannot be determined.
+ *  - if the buffer contains no NUL bytes within the first *len characters,
+ *    write NUL at the last position, set *len to zero, and point *buf past
+ *    the NUL byte. This would happen with
+ *
+ *       strncpy (buf, long_str, len);
+ *       nm_utils_strbuf_seek_end (&buf, &len).
+ *
+ *    where strncpy() does truncate the string and not NUL terminate it.
+ *    nm_utils_strbuf_seek_end() would then NUL terminate it.
+ */
+void
+nm_utils_strbuf_seek_end (char **buf, gsize *len)
+{
+	gsize l;
+	char *end;
+
+	nm_assert (len);
+	nm_assert (buf && *buf);
+
+	if (*len <= 1) {
+		if (   *len == 1
+		    && (*buf)[0])
+			goto truncate;
+		return;
+	}
+
+	end = memchr (*buf, 0, *len);
+	if (end) {
+		l = end - *buf;
+		nm_assert (l < *len);
+
+		*buf = end;
+		*len -= l;
+		return;
+	}
+
+truncate:
+	/* hm, no NUL character within len bytes.
+	 * Just NUL terminate the array and consume them
+	 * all. */
+	*buf += *len;
+	(*buf)[-1] = '\0';
+	*len = 0;
+	return;
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_utils_gbytes_equals:
+ * @bytes: (allow-none): a #GBytes array to compare. Note that
+ *   %NULL is treated like an #GBytes array of length zero.
+ * @mem_data: the data pointer with @mem_len bytes
+ * @mem_len: the length of the data pointer
+ *
+ * Returns: %TRUE if @bytes contains the same data as @mem_data. As a
+ *   special case, a %NULL @bytes is treated like an empty array.
+ */
+gboolean
+nm_utils_gbytes_equal_mem (GBytes *bytes,
+                           gconstpointer mem_data,
+                           gsize mem_len)
+{
+	gconstpointer p;
+	gsize l;
+
+	if (!bytes) {
+		/* as a special case, let %NULL GBytes compare idential
+		 * to an empty array. */
+		return (mem_len == 0);
+	}
+
+	p = g_bytes_get_data (bytes, &l);
+	return    l == mem_len
+	       && (   mem_len == 0 /* allow @mem_data to be %NULL */
+	           || memcmp (p, mem_data, mem_len) == 0);
+}
+
+GVariant *
+nm_utils_gbytes_to_variant_ay (GBytes *bytes)
+{
+	const guint8 *p;
+	gsize l;
+
+	if (!bytes) {
+		/* for convenience, accept NULL to return an empty variant */
+		return g_variant_new_array (G_VARIANT_TYPE_BYTE, NULL, 0);
+	}
+
+	p = g_bytes_get_data (bytes, &l);
+	return g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, p, l, 1);
+}
+
 /*****************************************************************************/
 
 /**
@@ -497,6 +622,50 @@ _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 ma
 	return v;
 }
 
+guint64
+_nm_utils_ascii_str_to_uint64 (const char *str, guint base, guint64 min, guint64 max, guint64 fallback)
+{
+	guint64 v;
+	const char *s = NULL;
+
+	if (str) {
+		while (g_ascii_isspace (str[0]))
+			str++;
+	}
+	if (!str || !str[0]) {
+		errno = EINVAL;
+		return fallback;
+	}
+
+	errno = 0;
+	v = g_ascii_strtoull (str, (char **) &s, base);
+
+	if (errno != 0)
+		return fallback;
+	if (s[0] != '\0') {
+		while (g_ascii_isspace (s[0]))
+			s++;
+		if (s[0] != '\0') {
+			errno = EINVAL;
+			return fallback;
+		}
+	}
+	if (v > max || v < min) {
+		errno = ERANGE;
+		return fallback;
+	}
+
+	if (   v != 0
+	    && str[0] == '-') {
+		/* I don't know why, but g_ascii_strtoull() accepts minus signs ("-2" gives 18446744073709551614).
+		 * For "-0" that is OK, but otherwise not. */
+		errno = ERANGE;
+		return fallback;
+	}
+
+	return v;
+}
+
 /*****************************************************************************/
 
 /* like nm_strcmp_p(), suitable for g_ptr_array_sort_with_data().
@@ -656,6 +825,7 @@ comp_l:
  * @str: the string to split.
  * @delimiters: the set of delimiters. If %NULL, defaults to " \t\n",
  *   like bash's $IFS.
+ * @allow_escaping: whether delimiters can be escaped by a backslash
  *
  * This is a replacement for g_strsplit_set() which avoids copying
  * each word once (the entire strv array), but instead copies it once
@@ -664,6 +834,10 @@ comp_l:
  * Another difference from g_strsplit_set() is that this never returns
  * empty words. Multiple delimiters are combined and treated as one.
  *
+ * If @allow_escaping is %TRUE, delimiters prefixed by a backslash are
+ * not treated as a separator. Such delimiters and their escape
+ * character are copied to the current word without unescaping them.
+ *
  * Returns: %NULL if @str is %NULL or contains only delimiters.
  *   Otherwise, a %NULL terminated strv array containing non-empty
  *   words, split at the delimiter characters (delimiter characters
@@ -673,7 +847,7 @@ comp_l:
  *   but free everything with g_free().
  */
 const char **
-nm_utils_strsplit_set (const char *str, const char *delimiters)
+nm_utils_strsplit_set (const char *str, const char *delimiters, gboolean allow_escaping)
 {
 	const char **ptr, **ptr0;
 	gsize alloc_size, plen, i;
@@ -681,6 +855,7 @@ nm_utils_strsplit_set (const char *str, const char *delimiters)
 	char *s0;
 	char *s;
 	guint8 delimiters_table[256];
+	gboolean escaped = FALSE;
 
 	if (!str)
 		return NULL;
@@ -692,13 +867,23 @@ nm_utils_strsplit_set (const char *str, const char *delimiters)
 	for (i = 0; delimiters[i]; i++)
 		delimiters_table[(guint8) delimiters[i]] = 1;
 
-#define _is_delimiter(ch, delimiters_table) \
-	((delimiters_table)[(guint8) (ch)] != 0)
+#define _is_delimiter(ch, delimiters_table, allow_esc, esc) \
+	((delimiters_table)[(guint8) (ch)] != 0 && (!allow_esc || !esc))
+
+#define next_char(p, esc) \
+	G_STMT_START { \
+		if (esc) \
+			esc = FALSE; \
+		else \
+			esc = p[0] == '\\'; \
+		p++; \
+	} G_STMT_END
 
 	/* skip initial delimiters, and return of the remaining string is
 	 * empty. */
-	while (_is_delimiter (str[0], delimiters_table))
-		str++;
+	while (_is_delimiter (str[0], delimiters_table, allow_escaping, escaped))
+		next_char (str, escaped);
+
 	if (!str[0])
 		return NULL;
 
@@ -730,20 +915,20 @@ nm_utils_strsplit_set (const char *str, const char *delimiters)
 
 		ptr[plen++] = s;
 
-		nm_assert (s[0] && !_is_delimiter (s[0], delimiters_table));
+		nm_assert (s[0] && !_is_delimiter (s[0], delimiters_table, allow_escaping, escaped));
 
 		while (TRUE) {
-			s++;
-			if (_is_delimiter (s[0], delimiters_table))
+			next_char (s, escaped);
+			if (_is_delimiter (s[0], delimiters_table, allow_escaping, escaped))
 				break;
 			if (s[0] == '\0')
 				goto done;
 		}
 
 		s[0] = '\0';
-		s++;
-		while (_is_delimiter (s[0], delimiters_table))
-			s++;
+		next_char (s, escaped);
+		while (_is_delimiter (s[0], delimiters_table, allow_escaping, escaped))
+			next_char (s, escaped);
 		if (s[0] == '\0')
 			break;
 	}
@@ -840,9 +1025,9 @@ _nm_utils_strv_cleanup (char **strv,
 
 /*****************************************************************************/
 
-gint
+int
 _nm_utils_ascii_str_to_bool (const char *str,
-                             gint default_value)
+                             int default_value)
 {
 	gsize len;
 	char *s = NULL;
@@ -924,7 +1109,7 @@ nm_utils_error_is_cancelled (GError *error,
  */
 gboolean
 nm_g_object_set_property (GObject *object,
-                          const gchar  *property_name,
+                          const char   *property_name,
                           const GValue *value,
                           GError **error)
 {
@@ -999,7 +1184,7 @@ nm_g_object_set_property (GObject *object,
 
 gboolean
 nm_g_object_set_property_boolean (GObject *object,
-                                  const gchar  *property_name,
+                                  const char   *property_name,
                                   gboolean value,
                                   GError **error)
 {
@@ -1012,7 +1197,7 @@ nm_g_object_set_property_boolean (GObject *object,
 
 gboolean
 nm_g_object_set_property_uint (GObject *object,
-                               const gchar  *property_name,
+                               const char   *property_name,
                                guint value,
                                GError **error)
 {
@@ -1044,20 +1229,112 @@ _str_append_escape (GString *s, char ch)
 	g_string_append_c (s, '0' + ( ((guchar) ch)       & 07));
 }
 
+gconstpointer
+nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_free)
+{
+	GString *gstr;
+	gsize len;
+	const char *s;
+
+	g_return_val_if_fail (to_free, NULL);
+	g_return_val_if_fail (out_len, NULL);
+
+	if (!str) {
+		*out_len = 0;
+		*to_free = NULL;
+		return NULL;
+	}
+
+	len = strlen (str);
+
+	s = memchr (str, '\\', len);
+	if (!s) {
+		*out_len = len;
+		*to_free = NULL;
+		return str;
+	}
+
+	gstr = g_string_new_len (NULL, len);
+
+	g_string_append_len (gstr, str, s - str);
+	str = s;
+
+	for (;;) {
+		char ch;
+		guint v;
+
+		nm_assert (str[0] == '\\');
+
+		ch = (++str)[0];
+
+		if (ch == '\0') {
+			// error. Trailing '\\'
+			break;
+		}
+
+		if (ch >= '0' && ch <= '9') {
+			v = ch - '0';
+			ch = (++str)[0];
+			if (ch >= '0' && ch <= '7') {
+				v = v * 8 + (ch - '0');
+				ch = (++str)[0];
+				if (ch >= '0' && ch <= '7') {
+					v = v * 8 + (ch - '0');
+					++str;
+				}
+			}
+			ch = v;
+		} else {
+			switch (ch) {
+			case 'b': ch = '\b'; break;
+			case 'f': ch = '\f'; break;
+			case 'n': ch = '\n'; break;
+			case 'r': ch = '\r'; break;
+			case 't': ch = '\t'; break;
+			case 'v': ch = '\v'; break;
+			default:
+				/* Here we handle "\\\\", but all other unexpected escape sequences are really a bug.
+				 * Take them literally, after removing the escape character */
+				break;
+			}
+			str++;
+		}
+
+		g_string_append_c (gstr, ch);
+
+		s = strchr (str, '\\');
+		if (!s) {
+			g_string_append (gstr, str);
+			break;
+		}
+
+		g_string_append_len (gstr, str, s - str);
+		str = s;
+	}
+
+	*out_len = gstr->len;
+	*to_free = gstr->str;
+	return g_string_free (gstr, FALSE);
+}
+
 /**
- * nm_utils_str_utf8safe_escape:
- * @str: NUL terminated input string, possibly in utf-8 encoding
+ * nm_utils_buf_utf8safe_escape:
+ * @buf: byte array, possibly in utf-8 encoding, may have NUL characters.
+ * @buflen: the length of @buf in bytes, or -1 if @buf is a NUL terminated
+ *   string.
  * @flags: #NMUtilsStrUtf8SafeFlags flags
  * @to_free: (out): return the pointer location of the string
  *   if a copying was necessary.
  *
- * Returns the possible non-UTF-8 NUL terminated string @str
- * and uses backslash escaping (C escaping, like g_strescape())
- * to sanitize non UTF-8 characters. The result is valid
+ * Based on the assumption, that @buf contains UTF-8 encoded bytes,
+ * this will return valid UTF-8 sequence, and invalid sequences
+ * will be escaped with backslash (C escaping, like g_strescape()).
+ * This is sanitize non UTF-8 characters. The result is valid
  * UTF-8.
  *
- * The operation can be reverted with g_strcompress() or
- * nm_utils_str_utf8safe_unescape().
+ * The operation can be reverted with nm_utils_buf_utf8safe_unescape().
+ * Note that if, and only if @buf contains no NUL bytes, the operation
+ * can also be reverted with g_strcompress().
  *
  * Depending on @flags, valid UTF-8 characters are not escaped at all
  * (except the escape character '\\'). This is the difference to g_strescape(),
@@ -1066,62 +1343,106 @@ _str_append_escape (GString *s, char ch)
  * as UTF-8 -- with exception of the backslash escape character,
  * invalid UTF-8 sequences, and other (depending on @flags).
  *
- * Returns: the escaped input string, as valid UTF-8. If no escaping
- *   is necessary, it returns the input @str. Otherwise, an allocated
+ * Returns: the escaped input buffer, as valid UTF-8. If no escaping
+ *   is necessary, it returns the input @buf. Otherwise, an allocated
  *   string @to_free is returned which must be freed by the caller
  *   with g_free. The escaping can be reverted by g_strcompress().
  **/
 const char *
-nm_utils_str_utf8safe_escape (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free)
+nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags, char **to_free)
 {
+	const char *const str = buf;
 	const char *p = NULL;
-	GString *s;
+	const char *s;
+	gboolean nul_terminated = FALSE;
+	GString *gstr;
 
 	g_return_val_if_fail (to_free, NULL);
 
 	*to_free = NULL;
-	if (!str || !str[0])
-		return str;
 
-	if (   g_utf8_validate (str, -1, &p)
-	    && !NM_STRCHAR_ANY (str, ch,
-	                        (   ch == '\\' \
-	                         || (   NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \
-	                             && ch < ' ') \
-	                         || (   NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \
-	                             && ((guchar) ch) >= 127))))
-		return str;
+	if (buflen == 0)
+		return NULL;
+
+	if (buflen < 0) {
+		if (!str)
+			return NULL;
+		buflen = strlen (str);
+		if (buflen == 0)
+			return str;
+		nul_terminated = TRUE;
+	}
+
+	if (   g_utf8_validate (str, buflen, &p)
+	    && nul_terminated) {
+		/* note that g_utf8_validate() does not allow NUL character inside @str. Good.
+		 * We can treat @str like a NUL terminated string. */
+		if (!NM_STRCHAR_ANY (str, ch,
+		                        (   ch == '\\' \
+		                         || (   NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \
+		                             && ch < ' ') \
+		                         || (   NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \
+		                             && ((guchar) ch) >= 127))))
+			return str;
+	}
 
-	s = g_string_sized_new ((p - str) + strlen (p) + 5);
+	gstr = g_string_sized_new (buflen + 5);
 
+	s = str;
 	do {
-		for (; str < p; str++) {
-			char ch = str[0];
+		buflen -= p - s;
+		nm_assert (buflen >= 0);
+
+		for (; s < p; s++) {
+			char ch = s[0];
 
 			if (ch == '\\')
-				g_string_append (s, "\\\\");
+				g_string_append (gstr, "\\\\");
 			else if (   (   NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \
 			             && ch < ' ') \
 			         || (   NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \
 			             && ((guchar) ch) >= 127))
-				_str_append_escape (s, ch);
+				_str_append_escape (gstr, ch);
 			else
-				g_string_append_c (s, ch);
+				g_string_append_c (gstr, ch);
 		}
 
-		if (p[0] == '\0')
+		if (buflen <= 0)
+			break;
+
+		_str_append_escape (gstr, p[0]);
+
+		buflen--;
+		if (buflen == 0)
 			break;
-		_str_append_escape (s, p[0]);
 
-		str = &p[1];
-		g_utf8_validate (str, -1, &p);
+		s = &p[1];
+		g_utf8_validate (s, buflen, &p);
 	} while (TRUE);
 
-	*to_free = g_string_free (s, FALSE);
+	*to_free = g_string_free (gstr, FALSE);
 	return *to_free;
 }
 
 const char *
+nm_utils_buf_utf8safe_escape_bytes (GBytes *bytes, NMUtilsStrUtf8SafeFlags flags, char **to_free)
+{
+	gconstpointer p;
+	gsize l;
+
+	if (bytes)
+		p = g_bytes_get_data (bytes, &l);
+	else {
+		p = NULL;
+		l = 0;
+	}
+
+	return nm_utils_buf_utf8safe_escape (p, l, flags, to_free);
+}
+
+/*****************************************************************************/
+
+const char *
 nm_utils_str_utf8safe_unescape (const char *str, char **to_free)
 {
 	g_return_val_if_fail (to_free, NULL);
@@ -1134,6 +1455,39 @@ nm_utils_str_utf8safe_unescape (const char *str, char **to_free)
 }
 
 /**
+ * nm_utils_str_utf8safe_escape:
+ * @str: NUL terminated input string, possibly in utf-8 encoding
+ * @flags: #NMUtilsStrUtf8SafeFlags flags
+ * @to_free: (out): return the pointer location of the string
+ *   if a copying was necessary.
+ *
+ * Returns the possible non-UTF-8 NUL terminated string @str
+ * and uses backslash escaping (C escaping, like g_strescape())
+ * to sanitize non UTF-8 characters. The result is valid
+ * UTF-8.
+ *
+ * The operation can be reverted with g_strcompress() or
+ * nm_utils_str_utf8safe_unescape().
+ *
+ * Depending on @flags, valid UTF-8 characters are not escaped at all
+ * (except the escape character '\\'). This is the difference to g_strescape(),
+ * which escapes all non-ASCII characters. This allows to pass on
+ * valid UTF-8 characters as-is and can be directly shown to the user
+ * as UTF-8 -- with exception of the backslash escape character,
+ * invalid UTF-8 sequences, and other (depending on @flags).
+ *
+ * Returns: the escaped input string, as valid UTF-8. If no escaping
+ *   is necessary, it returns the input @str. Otherwise, an allocated
+ *   string @to_free is returned which must be freed by the caller
+ *   with g_free. The escaping can be reverted by g_strcompress().
+ **/
+const char *
+nm_utils_str_utf8safe_escape (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free)
+{
+	return nm_utils_buf_utf8safe_escape (str, -1, flags, to_free);
+}
+
+/**
  * nm_utils_str_utf8safe_escape_cp:
  * @str: NUL terminated input string, possibly in utf-8 encoding
  * @flags: #NMUtilsStrUtf8SafeFlags flags
@@ -1350,6 +1704,209 @@ nm_utils_strv_make_deep_copied (const char **strv)
 
 /*****************************************************************************/
 
+gssize
+nm_utils_ptrarray_find_binary_search (gconstpointer *list,
+                                      gsize len,
+                                      gconstpointer needle,
+                                      GCompareDataFunc cmpfcn,
+                                      gpointer user_data,
+                                      gssize *out_idx_first,
+                                      gssize *out_idx_last)
+{
+	gssize imin, imax, imid, i2min, i2max, i2mid;
+	int cmp;
+
+	g_return_val_if_fail (list || !len, ~((gssize) 0));
+	g_return_val_if_fail (cmpfcn, ~((gssize) 0));
+
+	imin = 0;
+	if (len > 0) {
+		imax = len - 1;
+
+		while (imin <= imax) {
+			imid = imin + (imax - imin) / 2;
+
+			cmp = cmpfcn (list[imid], needle, user_data);
+			if (cmp == 0) {
+				/* we found a matching entry at index imid.
+				 *
+				 * Does the caller request the first/last index as well (in case that
+				 * there are multiple entries which compare equal). */
+
+				if (out_idx_first) {
+					i2min = imin;
+					i2max = imid + 1;
+					while (i2min <= i2max) {
+						i2mid = i2min + (i2max - i2min) / 2;
+
+						cmp = cmpfcn (list[i2mid], needle, user_data);
+						if (cmp == 0)
+							i2max = i2mid -1;
+						else {
+							nm_assert (cmp < 0);
+							i2min = i2mid + 1;
+						}
+					}
+					*out_idx_first = i2min;
+				}
+				if (out_idx_last) {
+					i2min = imid + 1;
+					i2max = imax;
+					while (i2min <= i2max) {
+						i2mid = i2min + (i2max - i2min) / 2;
+
+						cmp = cmpfcn (list[i2mid], needle, user_data);
+						if (cmp == 0)
+							i2min = i2mid + 1;
+						else {
+							nm_assert (cmp > 0);
+							i2max = i2mid - 1;
+						}
+					}
+					*out_idx_last = i2min - 1;
+				}
+				return imid;
+			}
+
+			if (cmp < 0)
+				imin = imid + 1;
+			else
+				imax = imid - 1;
+		}
+	}
+
+	/* return the inverse of @imin. This is a negative number, but
+	 * also is ~imin the position where the value should be inserted. */
+	imin = ~imin;
+	NM_SET_OUT (out_idx_first, imin);
+	NM_SET_OUT (out_idx_last, imin);
+	return imin;
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_utils_array_find_binary_search:
+ * @list: the list to search. It must be sorted according to @cmpfcn ordering.
+ * @elem_size: the size in bytes of each element in the list
+ * @len: the number of elements in @list
+ * @needle: the value that is searched
+ * @cmpfcn: the compare function. The elements @list are passed as first
+ *   argument to @cmpfcn, while @needle is passed as second. Usually, the
+ *   needle is the same data type as inside the list, however, that is
+ *   not necessary, as long as @cmpfcn takes care to cast the two arguments
+ *   accordingly.
+ * @user_data: optional argument passed to @cmpfcn
+ *
+ * Performs binary search for @needle in @list. On success, returns the
+ * (non-negative) index where the compare function found the searched element.
+ * On success, it returns a negative value. Note that the return negative value
+ * is the bitwise inverse of the position where the element should be inserted.
+ *
+ * If the list contains multiple matching elements, an arbitrary index is
+ * returned.
+ *
+ * Returns: the index to the element in the list, or the (negative, bitwise inverted)
+ *   position where it should be.
+ */
+gssize
+nm_utils_array_find_binary_search (gconstpointer list,
+                                   gsize elem_size,
+                                   gsize len,
+                                   gconstpointer needle,
+                                   GCompareDataFunc cmpfcn,
+                                   gpointer user_data)
+{
+	gssize imin, imax, imid;
+	int cmp;
+
+	g_return_val_if_fail (list || !len, ~((gssize) 0));
+	g_return_val_if_fail (cmpfcn, ~((gssize) 0));
+	g_return_val_if_fail (elem_size > 0, ~((gssize) 0));
+
+	imin = 0;
+	if (len == 0)
+		return ~imin;
+
+	imax = len - 1;
+
+	while (imin <= imax) {
+		imid = imin + (imax - imin) / 2;
+
+		cmp = cmpfcn (&((const char *) list)[elem_size * imid], needle, user_data);
+		if (cmp == 0)
+			return imid;
+
+		if (cmp < 0)
+			imin = imid + 1;
+		else
+			imax = imid - 1;
+	}
+
+	/* return the inverse of @imin. This is a negative number, but
+	 * also is ~imin the position where the value should be inserted. */
+	return ~imin;
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_utils_hash_table_equal:
+ * @a: one #GHashTable
+ * @b: other #GHashTable
+ * @treat_null_as_empty: if %TRUE, when either @a or @b is %NULL, it is
+ *   treated like an empty hash. It means, a %NULL hash will compare equal
+ *   to an empty hash.
+ * @equal_func: the equality function, for comparing the values.
+ *   If %NULL, the values are not compared. In that case, the function
+ *   only checks, if both dictionaries have the same keys -- according
+ *   to @b's key equality function.
+ *   Note that the values of @a will be passed as first argument
+ *   to @equal_func.
+ *
+ * Compares two hash tables, whether they have equal content.
+ * This only makes sense, if @a and @b have the same key types and
+ * the same key compare-function.
+ *
+ * Returns: %TRUE, if both dictionaries have the same content.
+ */
+gboolean
+nm_utils_hash_table_equal (const GHashTable *a,
+                           const GHashTable *b,
+                           gboolean treat_null_as_empty,
+                           NMUtilsHashTableEqualFunc equal_func)
+{
+	guint n;
+	GHashTableIter iter;
+	gconstpointer key, v_a, v_b;
+
+	if (a == b)
+		return TRUE;
+	if (!treat_null_as_empty) {
+		if (!a || !b)
+			return FALSE;
+	}
+
+	n = a ? g_hash_table_size ((GHashTable *) a) : 0;
+	if (n != (b ? g_hash_table_size ((GHashTable *) b) : 0))
+		return FALSE;
+
+	if (n > 0) {
+		g_hash_table_iter_init (&iter, (GHashTable *) a);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &v_a)) {
+			if (!g_hash_table_lookup_extended ((GHashTable *) b, key, NULL, (gpointer *) &v_b))
+				return FALSE;
+			if (   equal_func
+			    && !equal_func (v_a, v_b))
+				return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
 /**
  * nm_utils_get_start_time_for_pid:
  * @pid: the process identifier
@@ -1369,10 +1926,10 @@ nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid)
 {
 	guint64 start_time;
 	char filename[256];
-	gs_free gchar *contents = NULL;
+	gs_free char *contents = NULL;
 	size_t length;
 	gs_free const char **tokens = NULL;
-	gchar *p;
+	char *p;
 	char state = ' ';
 	gint64 ppid = 0;
 
@@ -1399,7 +1956,7 @@ nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid)
 
 	state = p[0];
 
-	tokens = nm_utils_strsplit_set (p, " ");
+	tokens = nm_utils_strsplit_set (p, " ", FALSE);
 
 	if (NM_PTRARRAY_LEN (tokens) < 20)
 		goto fail;
@@ -1499,3 +2056,60 @@ _nm_utils_user_data_unpack (gpointer user_data, int nargs, ...)
 
 	g_slice_free1 (((gsize) nargs) * sizeof (gconstpointer), user_data);
 }
+
+/*****************************************************************************/
+
+#define IS_SPACE(c) NM_IN_SET ((c), ' ', '\t')
+
+const char *
+_nm_utils_escape_spaces (const char *str, char **to_free)
+{
+	const char *ptr = str;
+	char *ret, *r;
+
+	*to_free = NULL;
+
+	if (!str)
+		return NULL;
+
+	while (TRUE) {
+		if (!*ptr)
+			return str;
+		if (IS_SPACE (*ptr))
+			break;
+		ptr++;
+	}
+
+	ptr = str;
+	ret = g_new (char, strlen (str) * 2 + 1);
+	r = ret;
+	*to_free = ret;
+	while (*ptr) {
+		if (IS_SPACE (*ptr))
+			*r++ = '\\';
+		*r++ = *ptr++;
+	}
+	*r = '\0';
+
+	return ret;
+}
+
+char *
+_nm_utils_unescape_spaces (char *str)
+{
+	guint i, j = 0;
+
+	if (!str)
+		return NULL;
+
+	for (i = 0; str[i]; i++) {
+		if (str[i] == '\\' && IS_SPACE (str[i+1]))
+			i++;
+		str[j++] = str[i];
+	}
+	str[j] = '\0';
+
+	return str;
+}
+
+#undef IS_SPACE