summary refs log tree commit diff
path: root/shared/nm-glib-aux
diff options
context:
space:
mode:
Diffstat (limited to 'shared/nm-glib-aux')
-rw-r--r--shared/nm-glib-aux/nm-c-list.h103
-rw-r--r--shared/nm-glib-aux/nm-dbus-aux.c69
-rw-r--r--shared/nm-glib-aux/nm-dbus-aux.h102
-rw-r--r--shared/nm-glib-aux/nm-dedup-multi.c1
-rw-r--r--shared/nm-glib-aux/nm-dedup-multi.h1
-rw-r--r--shared/nm-glib-aux/nm-enum-utils.c1
-rw-r--r--shared/nm-glib-aux/nm-enum-utils.h1
-rw-r--r--shared/nm-glib-aux/nm-glib.h13
-rw-r--r--shared/nm-glib-aux/nm-hash-utils.c39
-rw-r--r--shared/nm-glib-aux/nm-hash-utils.h26
-rw-r--r--shared/nm-glib-aux/nm-io-utils.c24
-rw-r--r--shared/nm-glib-aux/nm-io-utils.h5
-rw-r--r--shared/nm-glib-aux/nm-jansson.h101
-rw-r--r--shared/nm-glib-aux/nm-json-aux.c149
-rw-r--r--shared/nm-glib-aux/nm-json-aux.h83
-rw-r--r--shared/nm-glib-aux/nm-keyfile-aux.c413
-rw-r--r--shared/nm-glib-aux/nm-keyfile-aux.h78
-rw-r--r--shared/nm-glib-aux/nm-logging-fwd.h27
-rw-r--r--shared/nm-glib-aux/nm-macros-internal.h159
-rw-r--r--shared/nm-glib-aux/nm-obj.h1
-rw-r--r--shared/nm-glib-aux/nm-random-utils.c1
-rw-r--r--shared/nm-glib-aux/nm-random-utils.h1
-rw-r--r--shared/nm-glib-aux/nm-secret-utils.c1
-rw-r--r--shared/nm-glib-aux/nm-secret-utils.h1
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.c371
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.h82
-rw-r--r--shared/nm-glib-aux/nm-time-utils.c38
-rw-r--r--shared/nm-glib-aux/nm-time-utils.h25
-rw-r--r--shared/nm-glib-aux/nm-value-type.h208
29 files changed, 2027 insertions, 97 deletions
diff --git a/shared/nm-glib-aux/nm-c-list.h b/shared/nm-glib-aux/nm-c-list.h
index 5c73f574..7512730d 100644
--- a/shared/nm-glib-aux/nm-c-list.h
+++ b/shared/nm-glib-aux/nm-c-list.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
@@ -33,6 +32,8 @@
 		_what && c_list_contains (list, &_what->member); \
 	})
 
+/*****************************************************************************/
+
 typedef struct {
 	CList lst;
 	void *data;
@@ -48,21 +49,34 @@ nm_c_list_elem_new_stale (void *data)
 	return elem;
 }
 
-static inline void *
-nm_c_list_elem_get (CList *lst)
+static inline gboolean
+nm_c_list_elem_free_full (NMCListElem *elem, GDestroyNotify free_fcn)
 {
-	if (!lst)
-		return NULL;
-	return c_list_entry (lst, NMCListElem, lst)->data;
+	if (!elem)
+		return FALSE;
+	c_list_unlink_stale (&elem->lst);
+	if (free_fcn)
+		free_fcn (elem->data);
+	g_slice_free (NMCListElem, elem);
+	return TRUE;
 }
 
-static inline void
+static inline gboolean
 nm_c_list_elem_free (NMCListElem *elem)
 {
-	if (elem) {
-		c_list_unlink_stale (&elem->lst);
-		g_slice_free (NMCListElem, elem);
-	}
+	return nm_c_list_elem_free_full (elem, NULL);
+}
+
+static inline void *
+nm_c_list_elem_free_steal (NMCListElem *elem)
+{
+	gpointer data;
+
+	if (!elem)
+		return NULL;
+	data = elem->data;
+	nm_c_list_elem_free_full (elem, NULL);
+	return data;
 }
 
 static inline void
@@ -70,22 +84,53 @@ nm_c_list_elem_free_all (CList *head, GDestroyNotify free_fcn)
 {
 	NMCListElem *elem;
 
-	while ((elem = c_list_first_entry (head, NMCListElem, lst))) {
-		if (free_fcn)
-			free_fcn (elem->data);
-		c_list_unlink_stale (&elem->lst);
-		g_slice_free (NMCListElem, elem);
+	while ((elem = c_list_first_entry (head, NMCListElem, lst)))
+		nm_c_list_elem_free_full (elem, free_fcn);
+}
+
+/**
+ * nm_c_list_elem_find_first:
+ * @head: the @CList head of a list containing #NMCListElem elements.
+ *   Note that the head is not itself part of the list.
+ * @needle: the needle pointer.
+ *
+ * Iterates the list and returns the first #NMCListElem with the matching @needle,
+ * using pointer equality.
+ *
+ * Returns: the found list element or %NULL if not found.
+ */
+static inline NMCListElem *
+nm_c_list_elem_find_first (CList *head, gconstpointer needle)
+{
+	NMCListElem *elem;
+
+	c_list_for_each_entry (elem, head, lst) {
+		if (elem->data == needle)
+			return elem;
 	}
+	return NULL;
 }
 
 /*****************************************************************************/
 
+/**
+ * nm_c_list_move_before:
+ * @lst: the list element to which @elem will be prepended.
+ * @elem: the list element to move.
+ *
+ * This unlinks @elem from the current list and linkes it before
+ * @lst. This is like c_list_link_before(), except that @elem must
+ * be initialized and linked. Note that @elem may be linked in @lst
+ * or in another list. In both cases it gets moved.
+ *
+ * Returns: %TRUE if there were any changes. %FALSE if elem was already
+ *   linked at the right place.
+ */
 static inline gboolean
 nm_c_list_move_before (CList *lst, CList *elem)
 {
 	nm_assert (lst);
 	nm_assert (elem);
-	nm_assert (c_list_contains (lst, elem));
 
 	if (   lst != elem
 	    && lst->prev != elem) {
@@ -97,12 +142,24 @@ nm_c_list_move_before (CList *lst, CList *elem)
 }
 #define nm_c_list_move_tail(lst, elem) nm_c_list_move_before (lst, elem)
 
+/**
+ * nm_c_list_move_after:
+ * @lst: the list element to which @elem will be prepended.
+ * @elem: the list element to move.
+ *
+ * This unlinks @elem from the current list and linkes it after
+ * @lst. This is like c_list_link_after(), except that @elem must
+ * be initialized and linked. Note that @elem may be linked in @lst
+ * or in another list. In both cases it gets moved.
+ *
+ * Returns: %TRUE if there were any changes. %FALSE if elem was already
+ *   linked at the right place.
+ */
 static inline gboolean
 nm_c_list_move_after (CList *lst, CList *elem)
 {
 	nm_assert (lst);
 	nm_assert (elem);
-	nm_assert (c_list_contains (lst, elem));
 
 	if (   lst != elem
 	    && lst->next != elem) {
@@ -114,4 +171,14 @@ nm_c_list_move_after (CList *lst, CList *elem)
 }
 #define nm_c_list_move_front(lst, elem) nm_c_list_move_after (lst, elem)
 
+#define nm_c_list_free_all(lst, type, member, destroy_fcn) \
+	G_STMT_START { \
+		CList *const _lst = (lst); \
+		type *_elem; \
+		\
+		while ((_elem = c_list_first_entry (_lst, type, member))) { \
+			destroy_fcn (_elem); \
+		} \
+	} G_STMT_END
+
 #endif /* __NM_C_LIST_H__ */
diff --git a/shared/nm-glib-aux/nm-dbus-aux.c b/shared/nm-glib-aux/nm-dbus-aux.c
new file mode 100644
index 00000000..083c4fee
--- /dev/null
+++ b/shared/nm-glib-aux/nm-dbus-aux.c
@@ -0,0 +1,69 @@
+/* NetworkManager -- Network link manager
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2019 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-dbus-aux.h"
+
+/*****************************************************************************/
+
+static void
+_nm_dbus_connection_call_get_name_owner_cb (GObject *source,
+                                            GAsyncResult *res,
+                                            gpointer user_data)
+{
+	gs_unref_variant GVariant *ret = NULL;
+	gs_free_error GError *error = NULL;
+	const char *owner = NULL;
+	gpointer orig_user_data;
+	NMDBusConnectionCallGetNameOwnerCb callback;
+
+	nm_utils_user_data_unpack (user_data, &orig_user_data, &callback);
+
+	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &error);
+	if (ret)
+		g_variant_get (ret, "(&s)", &owner);
+
+	callback (owner, error, orig_user_data);
+}
+
+void
+nm_dbus_connection_call_get_name_owner (GDBusConnection *dbus_connection,
+                                        const char *service_name,
+                                        int timeout_msec,
+                                        GCancellable *cancellable,
+                                        NMDBusConnectionCallGetNameOwnerCb callback,
+                                        gpointer user_data)
+{
+	nm_assert (callback);
+
+	g_dbus_connection_call (dbus_connection,
+	                        DBUS_SERVICE_DBUS,
+	                        DBUS_PATH_DBUS,
+	                        DBUS_INTERFACE_DBUS,
+	                        "GetNameOwner",
+	                        g_variant_new ("(s)", service_name),
+	                        G_VARIANT_TYPE ("(s)"),
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        timeout_msec,
+	                        cancellable,
+	                        _nm_dbus_connection_call_get_name_owner_cb,
+	                        nm_utils_user_data_pack (user_data, callback));
+}
diff --git a/shared/nm-glib-aux/nm-dbus-aux.h b/shared/nm-glib-aux/nm-dbus-aux.h
new file mode 100644
index 00000000..271a7d9c
--- /dev/null
+++ b/shared/nm-glib-aux/nm-dbus-aux.h
@@ -0,0 +1,102 @@
+/* NetworkManager -- Network link manager
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_DBUS_AUX_H__
+#define __NM_DBUS_AUX_H__
+
+#include "nm-std-aux/nm-dbus-compat.h"
+
+/*****************************************************************************/
+
+static inline gboolean
+nm_clear_g_dbus_connection_signal (GDBusConnection *dbus_connection,
+                                   guint *id)
+{
+	guint v;
+
+	if (   id
+	    && (v = *id)) {
+		*id = 0;
+		g_dbus_connection_signal_unsubscribe (dbus_connection, v);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/*****************************************************************************/
+
+static inline void
+nm_dbus_connection_call_start_service_by_name (GDBusConnection *dbus_connection,
+                                               const char *name,
+                                               int timeout_msec,
+                                               GCancellable *cancellable,
+                                               GAsyncReadyCallback  callback,
+                                               gpointer user_data)
+{
+	g_dbus_connection_call (dbus_connection,
+	                        DBUS_SERVICE_DBUS,
+	                        DBUS_PATH_DBUS,
+	                        DBUS_INTERFACE_DBUS,
+	                        "StartServiceByName",
+	                        g_variant_new ("(su)", name, 0u),
+	                        G_VARIANT_TYPE ("(u)"),
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        timeout_msec,
+	                        cancellable,
+	                        callback,
+	                        user_data);
+}
+
+/*****************************************************************************/
+
+static inline guint
+nm_dbus_connection_signal_subscribe_name_owner_changed (GDBusConnection *dbus_connection,
+                                                        const char *service_name,
+                                                        GDBusSignalCallback callback,
+                                                        gpointer user_data,
+                                                        GDestroyNotify user_data_free_func)
+
+{
+	return g_dbus_connection_signal_subscribe (dbus_connection,
+	                                           DBUS_SERVICE_DBUS,
+	                                           DBUS_INTERFACE_DBUS,
+	                                           "NameOwnerChanged",
+	                                           DBUS_PATH_DBUS,
+	                                           service_name,
+	                                           G_DBUS_SIGNAL_FLAGS_NONE,
+	                                           callback,
+	                                           user_data,
+	                                           user_data_free_func);
+}
+
+typedef void (*NMDBusConnectionCallGetNameOwnerCb) (const char *name_owner,
+                                                    GError *error,
+                                                    gpointer user_data);
+
+void nm_dbus_connection_call_get_name_owner (GDBusConnection *dbus_connection,
+                                              const char *service_name,
+                                              int timeout_msec,
+                                              GCancellable *cancellable,
+                                              NMDBusConnectionCallGetNameOwnerCb callback,
+                                              gpointer user_data);
+
+/*****************************************************************************/
+
+#endif /* __NM_DBUS_AUX_H__ */
diff --git a/shared/nm-glib-aux/nm-dedup-multi.c b/shared/nm-glib-aux/nm-dedup-multi.c
index 5bdc3e3c..345062ca 100644
--- a/shared/nm-glib-aux/nm-dedup-multi.c
+++ b/shared/nm-glib-aux/nm-dedup-multi.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
diff --git a/shared/nm-glib-aux/nm-dedup-multi.h b/shared/nm-glib-aux/nm-dedup-multi.h
index 82c6f1e9..ca15c516 100644
--- a/shared/nm-glib-aux/nm-dedup-multi.h
+++ b/shared/nm-glib-aux/nm-dedup-multi.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
diff --git a/shared/nm-glib-aux/nm-enum-utils.c b/shared/nm-glib-aux/nm-enum-utils.c
index a4f6e809..b16267a5 100644
--- a/shared/nm-glib-aux/nm-enum-utils.c
+++ b/shared/nm-glib-aux/nm-enum-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
diff --git a/shared/nm-glib-aux/nm-enum-utils.h b/shared/nm-glib-aux/nm-enum-utils.h
index 1827fdf4..20db07cc 100644
--- a/shared/nm-glib-aux/nm-enum-utils.h
+++ b/shared/nm-glib-aux/nm-enum-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
diff --git a/shared/nm-glib-aux/nm-glib.h b/shared/nm-glib-aux/nm-glib.h
index e941e067..bdb7ea5b 100644
--- a/shared/nm-glib-aux/nm-glib.h
+++ b/shared/nm-glib-aux/nm-glib.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -523,10 +522,18 @@ _nm_g_variant_new_printf (const char *format_string, ...)
 
 /*****************************************************************************/
 
-#if !GLIB_CHECK_VERSION (2, 56, 0)
+/* Recent glib also casts the results to typeof(Obj), but only if
+ *
+ *  ( defined(g_has_typeof) && GLIB_VERSION_MAX_ALLOWED >= GLIB_VERSION_2_56 )
+ *
+ * Since we build NetworkManager with older GLIB_VERSION_MAX_ALLOWED, it's
+ * not taking effect.
+ *
+ * Override this. */
+#undef g_object_ref
+#undef g_object_ref_sink
 #define g_object_ref(Obj)      ((typeof(Obj)) g_object_ref (Obj))
 #define g_object_ref_sink(Obj) ((typeof(Obj)) g_object_ref_sink (Obj))
-#endif
 
 /*****************************************************************************/
 
diff --git a/shared/nm-glib-aux/nm-hash-utils.c b/shared/nm-glib-aux/nm-hash-utils.c
index 6e728e6b..a6158269 100644
--- a/shared/nm-glib-aux/nm-hash-utils.c
+++ b/shared/nm-glib-aux/nm-hash-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
@@ -45,7 +44,10 @@ _get_hash_key_init (void)
 	 * to use it as guint* or guint64* pointer. */
 	static union {
 		guint8 v8[HASH_KEY_SIZE];
-	} g_arr _nm_alignas (guint64);
+		guint _align_as_uint;
+		guint32 _align_as_uint32;
+		guint64 _align_as_uint64;
+	} g_arr;
 	const guint8 *g;
 	union {
 		guint8 v8[HASH_KEY_SIZE];
@@ -125,14 +127,17 @@ void
 nm_hash_siphash42_init (CSipHash *h, guint static_seed)
 {
 	const guint8 *g;
-	guint seed[HASH_KEY_SIZE_GUINT];
+	union {
+		guint64 _align_as_uint64;
+		guint arr[HASH_KEY_SIZE_GUINT];
+	} seed;
 
 	nm_assert (h);
 
 	g = _get_hash_key ();
-	memcpy (seed, g, HASH_KEY_SIZE);
-	seed[0] ^= static_seed;
-	c_siphash_init (h, (const guint8 *) seed);
+	memcpy (&seed, g, HASH_KEY_SIZE);
+	seed.arr[0] ^= static_seed;
+	c_siphash_init (h, (const guint8 *) &seed);
 }
 
 guint
@@ -194,3 +199,25 @@ nm_pstr_equal (gconstpointer a, gconstpointer b)
 	           && s2
 	           && nm_streq0 (*s1, *s2));
 }
+
+guint
+nm_pdirect_hash (gconstpointer p)
+{
+	const void *const*s = p;
+
+	if (!s)
+		return nm_hash_static (1852748873u);
+	return nm_direct_hash (*s);
+}
+
+gboolean
+nm_pdirect_equal (gconstpointer a, gconstpointer b)
+{
+	const void *const*s1 = a;
+	const void *const*s2 = b;
+
+	return    (s1 == s2)
+	       || (   s1
+	           && s2
+	           && *s1 == *s2);
+}
diff --git a/shared/nm-glib-aux/nm-hash-utils.h b/shared/nm-glib-aux/nm-hash-utils.h
index 3f622f99..f13e0b6d 100644
--- a/shared/nm-glib-aux/nm-hash-utils.h
+++ b/shared/nm-glib-aux/nm-hash-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
@@ -34,7 +33,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 hasing algorithm
+ * 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
  * anywhere).
  *
@@ -291,7 +290,16 @@ gboolean nm_pstr_equal (gconstpointer a, gconstpointer b);
 
 /*****************************************************************************/
 
-#define NM_HASH_OBFUSCATE_PTR_FMT "%016llx"
+/* this hashes/compares the pointer value that we point to. Basically,
+ * (((const void *const*) a) == ((const void *const*) b)). */
+
+guint nm_pdirect_hash (gconstpointer p);
+
+gboolean nm_pdirect_equal (gconstpointer a, gconstpointer b);
+
+/*****************************************************************************/
+
+#define NM_HASH_OBFUSCATE_PTR_FMT "%016" G_GINT64_MODIFIER "x"
 
 /* sometimes we want to log a pointer directly, for providing context/information about
  * the message that get logged. Logging pointer values directly defeats ASLR, so we should
@@ -307,9 +315,19 @@ gboolean nm_pstr_equal (gconstpointer a, gconstpointer b);
 		\
 		nm_hash_init (&_h, (static_seed)); \
 		nm_hash_update_val (&_h, _val_obf_ptr); \
-		(unsigned long long) nm_hash_complete_u64 (&_h); \
+		nm_hash_complete_u64 (&_h); \
 	})
 
+/* if you want to log obfuscated pointer for a certain context (like, NMPRuleManager
+ * logging user-tags), then you are advised to use nm_hash_obfuscate_ptr() with your
+ * own, unique static-seed.
+ *
+ * However, for example the singleton constructors log the obfuscated pointer values
+ * for all singletons, so they must all be obfuscated with the same seed. So, this
+ * macro uses a particular static seed that should be used by when comparing pointer
+ * values in a global context. */
+#define NM_HASH_OBFUSCATE_PTR(ptr) (nm_hash_obfuscate_ptr (1678382159u, ptr))
+
 /*****************************************************************************/
 
 #endif /* __NM_HASH_UTILS_H__ */
diff --git a/shared/nm-glib-aux/nm-io-utils.c b/shared/nm-glib-aux/nm-io-utils.c
index 51312748..23133ec5 100644
--- a/shared/nm-glib-aux/nm-io-utils.c
+++ b/shared/nm-glib-aux/nm-io-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
@@ -437,3 +436,26 @@ nm_utils_file_set_contents (const char *filename,
 
 	return TRUE;
 }
+
+/**
+ * nm_utils_file_stat:
+ * @filename: the filename to stat.
+ * @out_st: (allow-none) (out): if given, this will be passed to stat().
+ *
+ * Just wraps stat() and gives the errno number as function result instead
+ * of setting the errno (though, errno is also set). It's only for convenience
+ * with
+ *
+ *    if (nm_utils_file_stat (filename, NULL) == -ENOENT) {
+ *    }
+ *
+ * Returns: 0 on success a negative errno on failure. */
+int
+nm_utils_file_stat (const char *filename, struct stat *out_st)
+{
+	struct stat st;
+
+	if (stat (filename, out_st ?: &st) != 0)
+		return -NM_ERRNO_NATIVE (errno);
+	return 0;
+}
diff --git a/shared/nm-glib-aux/nm-io-utils.h b/shared/nm-glib-aux/nm-io-utils.h
index dc72a2a6..121fc481 100644
--- a/shared/nm-glib-aux/nm-io-utils.h
+++ b/shared/nm-glib-aux/nm-io-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
@@ -60,4 +59,8 @@ gboolean nm_utils_file_set_contents (const char *filename,
                                      mode_t mode,
                                      GError **error);
 
+struct stat;
+
+int nm_utils_file_stat (const char *filename, struct stat *out_st);
+
 #endif /* __NM_IO_UTILS_H__ */
diff --git a/shared/nm-glib-aux/nm-jansson.h b/shared/nm-glib-aux/nm-jansson.h
index 5a73231f..d4642319 100644
--- a/shared/nm-glib-aux/nm-jansson.h
+++ b/shared/nm-glib-aux/nm-jansson.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -44,6 +43,106 @@
 NM_AUTO_DEFINE_FCN0 (json_t *, _nm_auto_decref_json, json_decref)
 #define nm_auto_decref_json nm_auto(_nm_auto_decref_json)
 
+/*****************************************************************************/
+
+static inline int
+nm_jansson_json_as_bool (const json_t *elem,
+                         bool *out_val)
+{
+	if (!elem)
+		return 0;
+
+	if (!json_is_boolean (elem))
+		return -EINVAL;
+
+	NM_SET_OUT (out_val, json_boolean_value (elem));
+	return 1;
+}
+
+static inline int
+nm_jansson_json_as_int32 (const json_t *elem,
+                          gint32 *out_val)
+{
+	json_int_t v;
+
+	if (!elem)
+		return 0;
+
+	if (!json_is_integer (elem))
+		return -EINVAL;
+
+	v = json_integer_value (elem);
+	if (   v < (gint64) G_MININT32
+	    || v > (gint64) G_MAXINT32)
+		return -ERANGE;
+
+	NM_SET_OUT (out_val, v);
+	return 1;
+}
+
+static inline int
+nm_jansson_json_as_int (const json_t *elem,
+                        int *out_val)
+{
+	json_int_t v;
+
+	if (!elem)
+		return 0;
+
+	if (!json_is_integer (elem))
+		return -EINVAL;
+
+	v = json_integer_value (elem);
+	if (   v < (gint64) G_MININT
+	    || v > (gint64) G_MAXINT)
+		return -ERANGE;
+
+	NM_SET_OUT (out_val, v);
+	return 1;
+}
+
+static inline int
+nm_jansson_json_as_string (const json_t *elem,
+                           const char **out_val)
+{
+	if (!elem)
+		return 0;
+
+	if (!json_is_string (elem))
+		return -EINVAL;
+
+	NM_SET_OUT (out_val, json_string_value (elem));
+	return 1;
+}
+
+/*****************************************************************************/
+
+#ifdef NM_VALUE_TYPE_DEFINE_FUNCTIONS
+#include "nm-value-type.h"
+static inline gboolean
+nm_value_type_from_json (NMValueType value_type,
+                         const json_t *elem,
+                         gpointer out_val)
+{
+	switch (value_type) {
+	case NM_VALUE_TYPE_BOOL:   return (nm_jansson_json_as_bool   (elem, out_val) > 0);
+	case NM_VALUE_TYPE_INT32:  return (nm_jansson_json_as_int32  (elem, out_val) > 0);
+	case NM_VALUE_TYPE_INT:    return (nm_jansson_json_as_int    (elem, out_val) > 0);
+
+	/* warning: this overwrites/leaks the previous value. You better have *out_val
+	 * point to uninitialized memory or NULL. */
+	case NM_VALUE_TYPE_STRING: return (nm_jansson_json_as_string (elem, out_val) > 0);
+
+	case NM_VALUE_TYPE_UNSPEC:
+		break;
+	}
+	nm_assert_not_reached ();
+	return FALSE;
+}
+#endif
+
+/*****************************************************************************/
+
 #endif /* WITH_JANSON */
 
 #endif  /* __NM_JANSSON_H__ */
diff --git a/shared/nm-glib-aux/nm-json-aux.c b/shared/nm-glib-aux/nm-json-aux.c
new file mode 100644
index 00000000..6f04ef2b
--- /dev/null
+++ b/shared/nm-glib-aux/nm-json-aux.c
@@ -0,0 +1,149 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2019 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-json-aux.h"
+
+/*****************************************************************************/
+
+static void
+_gstr_append_string_len (GString *gstr,
+                         const char *str,
+                         gsize len)
+{
+	g_string_append_c (gstr, '\"');
+
+	while (len > 0) {
+		gsize n;
+		const char *end;
+		gboolean valid;
+
+		nm_assert (len > 0);
+
+		valid = g_utf8_validate (str, len, &end);
+
+		nm_assert (   end
+		           && end >= str
+		           && end <= &str[len]);
+
+		if (end > str) {
+			const char *s;
+
+			for (s = str; s < end; s++) {
+				nm_assert (s[0] != '\0');
+
+				if (s[0] < 0x20) {
+					const char *text;
+
+					switch (s[0]) {
+					case '\\': text = "\\\\"; break;
+					case '\"': text = "\\\""; break;
+					case '\b': text = "\\b";  break;
+					case '\f': text = "\\f";  break;
+					case '\n': text = "\\n";  break;
+					case '\r': text = "\\r";  break;
+					case '\t': text = "\\t";  break;
+					default:
+						g_string_append_printf (gstr, "\\u%04X", (guint) s[0]);
+						continue;
+					}
+					g_string_append (gstr, text);
+					continue;
+				}
+
+				if (NM_IN_SET (s[0], '\\', '\"'))
+					g_string_append_c (gstr, '\\');
+				g_string_append_c (gstr, s[0]);
+			}
+		} else
+			nm_assert (!valid);
+
+		if (valid) {
+			nm_assert (end == &str[len]);
+			break;
+		}
+
+		nm_assert (end < &str[len]);
+
+		if (end[0] == '\0') {
+			/* there is a NUL byte in the string. Technically this is valid UTF-8, so we
+			 * encode it there. However, this will likely result in a truncated string when
+			 * parsing. */
+			g_string_append (gstr, "\\u0000");
+		} else {
+			/* the character is not valid UTF-8. There is nothing we can do about it, because
+			 * JSON can only contain UTF-8 and even the escape sequences can only escape Unicode
+			 * codepoints (but not binary).
+			 *
+			 * The argument is not a a string (in any known encoding), hence we cannot represent
+			 * it as a JSON string (which are unicode strings).
+			 *
+			 * Print an underscore instead of the invalid char :) */
+			g_string_append_c (gstr, '_');
+		}
+
+		n = str - end;
+		nm_assert (n < len);
+		n++;
+		str += n;
+		len -= n;
+	}
+
+	g_string_append_c (gstr, '\"');
+}
+
+void
+nm_json_aux_gstr_append_string_len (GString *gstr,
+                                    const char *str,
+                                    gsize n)
+{
+	g_return_if_fail (gstr);
+
+	_gstr_append_string_len (gstr, str, n);
+}
+
+void
+nm_json_aux_gstr_append_string (GString *gstr,
+                                const char *str)
+{
+	g_return_if_fail (gstr);
+
+	if (!str)
+		g_string_append (gstr, "null");
+	else
+		_gstr_append_string_len (gstr, str, strlen (str));
+}
+
+void
+nm_json_aux_gstr_append_obj_name (GString *gstr,
+                                  const char *key,
+                                  char start_container)
+{
+	g_return_if_fail (gstr);
+	g_return_if_fail (key);
+
+	nm_json_aux_gstr_append_string (gstr, key);
+
+	if (start_container != '\0') {
+		nm_assert (NM_IN_SET (start_container, '[', '{'));
+		g_string_append_printf (gstr, ": %c ", start_container);
+	} else
+		g_string_append (gstr, ": ");
+}
diff --git a/shared/nm-glib-aux/nm-json-aux.h b/shared/nm-glib-aux/nm-json-aux.h
new file mode 100644
index 00000000..19d43ce4
--- /dev/null
+++ b/shared/nm-glib-aux/nm-json-aux.h
@@ -0,0 +1,83 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_JSON_AUX_H__
+#define __NM_JSON_AUX_H__
+
+/*****************************************************************************/
+
+static inline GString *
+nm_json_aux_gstr_append_delimiter (GString *gstr)
+{
+	g_string_append (gstr, ", ");
+	return gstr;
+}
+
+void nm_json_aux_gstr_append_string_len (GString *gstr,
+                                         const char *str,
+                                         gsize n);
+
+void nm_json_aux_gstr_append_string (GString *gstr,
+                                     const char *str);
+
+static inline void
+nm_json_aux_gstr_append_bool (GString *gstr,
+                              gboolean v)
+{
+	g_string_append (gstr, v ? "true" : "false");
+}
+
+static inline void
+nm_json_aux_gstr_append_int64 (GString *gstr,
+                               gint64 v)
+{
+	g_string_append_printf (gstr, "%"G_GINT64_FORMAT, v);
+}
+
+void nm_json_aux_gstr_append_obj_name (GString *gstr,
+                                       const char *key,
+                                       char start_container);
+
+/*****************************************************************************/
+
+#ifdef NM_VALUE_TYPE_DEFINE_FUNCTIONS
+#include "nm-value-type.h"
+static inline void
+nm_value_type_to_json (NMValueType value_type,
+                       GString *gstr,
+                       gconstpointer p_field)
+{
+	nm_assert (p_field);
+	nm_assert (gstr);
+
+	switch (value_type) {
+	case NM_VALUE_TYPE_BOOL:   nm_json_aux_gstr_append_bool   (gstr, *((const bool        *) p_field)); return;
+	case NM_VALUE_TYPE_INT32:  nm_json_aux_gstr_append_int64  (gstr, *((const gint32      *) p_field)); return;
+	case NM_VALUE_TYPE_INT:    nm_json_aux_gstr_append_int64  (gstr, *((const int         *) p_field)); return;
+	case NM_VALUE_TYPE_STRING: nm_json_aux_gstr_append_string (gstr, *((const char *const *) p_field)); return;
+	case NM_VALUE_TYPE_UNSPEC:
+		break;
+	}
+	nm_assert_not_reached ();
+}
+#endif
+
+/*****************************************************************************/
+
+#endif  /* __NM_JSON_AUX_H__ */
diff --git a/shared/nm-glib-aux/nm-keyfile-aux.c b/shared/nm-glib-aux/nm-keyfile-aux.c
new file mode 100644
index 00000000..0257bcca
--- /dev/null
+++ b/shared/nm-glib-aux/nm-keyfile-aux.c
@@ -0,0 +1,413 @@
+/* NetworkManager -- Network link manager
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2019 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-keyfile-aux.h"
+
+#include <syslog.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "nm-io-utils.h"
+
+/*****************************************************************************/
+
+struct _NMKeyFileDB {
+	NMKeyFileDBLogFcn log_fcn;
+	NMKeyFileDBGotDirtyFcn got_dirty_fcn;
+	gpointer user_data;
+	const char *group_name;
+	GKeyFile *kf;
+	guint ref_count;
+
+	bool is_started:1;
+	bool dirty:1;
+	bool destroyed:1;
+
+	char filename[];
+};
+
+#define _NMLOG(self, \
+               syslog_level, \
+               fmt, \
+               ...) \
+	G_STMT_START { \
+		NMKeyFileDB *_self = (self); \
+		\
+		nm_assert (_self); \
+		nm_assert (!_self->destroyed); \
+		\
+		if (_self->log_fcn) { \
+			_self->log_fcn (_self, \
+			                (syslog_level), \
+			                _self->user_data, \
+			                ""fmt"", \
+			                ##__VA_ARGS__); \
+		}; \
+	} G_STMT_END
+
+#define _LOGD(...) _NMLOG (self, LOG_DEBUG, __VA_ARGS__)
+
+static gboolean
+_IS_KEY_FILE_DB (NMKeyFileDB *self, gboolean require_is_started, gboolean allow_destroyed)
+{
+	if (self == NULL)
+		return FALSE;
+	if (self->ref_count <= 0) {
+		nm_assert_not_reached ();
+		return FALSE;
+	}
+	if (   require_is_started
+	    && !self->is_started)
+		return FALSE;
+	if (   !allow_destroyed
+	    && self->destroyed)
+		return FALSE;
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+NMKeyFileDB *
+nm_key_file_db_new (const char *filename,
+                    const char *group_name,
+                    NMKeyFileDBLogFcn log_fcn,
+                    NMKeyFileDBGotDirtyFcn got_dirty_fcn,
+                    gpointer user_data)
+{
+	NMKeyFileDB *self;
+	gsize l_filename;
+	gsize l_group;
+
+	g_return_val_if_fail (filename && filename[0], NULL);
+	g_return_val_if_fail (group_name && group_name[0], NULL);
+
+	l_filename = strlen (filename);
+	l_group = strlen (group_name);
+
+	self = g_malloc0 (sizeof (NMKeyFileDB) + l_filename + 1 + l_group + 1);
+	self->ref_count = 1;
+	self->log_fcn = log_fcn;
+	self->got_dirty_fcn = got_dirty_fcn;
+	self->user_data = user_data;
+	self->kf = g_key_file_new ();
+	g_key_file_set_list_separator (self->kf, ',');
+	memcpy (self->filename, filename, l_filename + 1);
+	self->group_name = &self->filename[l_filename + 1];
+	memcpy ((char *) self->group_name, group_name, l_group + 1);
+
+	return self;
+}
+
+NMKeyFileDB *
+nm_key_file_db_ref (NMKeyFileDB *self)
+{
+	if (!self)
+		return NULL;
+
+	g_return_val_if_fail (_IS_KEY_FILE_DB (self, FALSE, TRUE), NULL);
+
+	nm_assert (self->ref_count <= G_MAXUINT);
+	self->ref_count++;
+	return self;
+}
+
+void
+nm_key_file_db_unref (NMKeyFileDB *self)
+{
+	if (!self)
+		return;
+
+	g_return_if_fail (_IS_KEY_FILE_DB (self, FALSE, TRUE));
+
+	if (--self->ref_count > 0)
+		return;
+
+	g_key_file_unref (self->kf);
+
+	g_free (self);
+}
+
+/* destroy() is like unref, but it also makes the instance unusable.
+ * All changes afterwards fail with an assertion.
+ *
+ * The point is that NMKeyFileDB is ref-counted in principle. But there
+ * is a primary owner who also provides the log_fcn().
+ *
+ * When the primary owner goes out of scope and gives up the reference, it does
+ * not want to receive any log notifications anymore.
+ *
+ * The way NMKeyFileDB is intended to be used is in a very strict context:
+ * NMSettings owns the NMKeyFileDB instance and receives logging notifications.
+ * It's also the last one to persist the data to disk. Afterwards, no other user
+ * is supposed to be around and do anything with NMKeyFileDB. But since NMKeyFileDB
+ * is ref-counted it's hard to ensure that this is truly honored. So we start
+ * asserting at that point.
+ */
+void
+nm_key_file_db_destroy (NMKeyFileDB *self)
+{
+	if (!self)
+		return;
+
+	g_return_if_fail (_IS_KEY_FILE_DB (self, FALSE, FALSE));
+	g_return_if_fail (!self->destroyed);
+
+	self->destroyed = TRUE;
+	nm_key_file_db_unref (self);
+}
+
+/*****************************************************************************/
+
+/* nm_key_file_db_start() is supposed to be called right away, after creating the
+ * instance.
+ *
+ * It's not done as separate step after nm_key_file_db_new(), because we want to log,
+ * and the log_fcn returns the self pointer (which we should not expose before
+ * nm_key_file_db_new() returns. */
+void
+nm_key_file_db_start (NMKeyFileDB *self)
+{
+	int r;
+	gs_free char *contents = NULL;
+	gsize contents_len;
+	gs_free_error GError *error = NULL;
+
+	g_return_if_fail (_IS_KEY_FILE_DB (self, FALSE, FALSE));
+	g_return_if_fail (!self->is_started);
+
+	self->is_started = TRUE;
+
+	r = nm_utils_file_get_contents (-1,
+	                                self->filename,
+	                                20*1024*1024,
+	                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+	                                &contents,
+	                                &contents_len,
+	                                &error);
+	if (r < 0) {
+		_LOGD ("failed to read \"%s\": %s", self->filename, error->message);
+		return;
+	}
+
+	if (!g_key_file_load_from_data (self->kf,
+	                                contents,
+	                                contents_len,
+	                                G_KEY_FILE_KEEP_COMMENTS,
+	                                &error)) {
+		_LOGD ("failed to load keyfile \"%s\": %s", self->filename, error->message);
+		return;
+	}
+
+	_LOGD ("loaded keyfile-db for \"%s\"", self->filename);
+}
+
+/*****************************************************************************/
+
+const char *
+nm_key_file_db_get_filename (NMKeyFileDB *self)
+{
+	g_return_val_if_fail (_IS_KEY_FILE_DB (self, FALSE, TRUE), NULL);
+
+	return self->filename;
+}
+
+gboolean
+nm_key_file_db_is_dirty (NMKeyFileDB *self)
+{
+	g_return_val_if_fail (_IS_KEY_FILE_DB (self, FALSE, TRUE), FALSE);
+
+	return self->dirty;
+}
+
+/*****************************************************************************/
+
+char *
+nm_key_file_db_get_value (NMKeyFileDB *self,
+                          const char *key)
+{
+	g_return_val_if_fail (_IS_KEY_FILE_DB (self, TRUE, TRUE), NULL);
+
+	return g_key_file_get_value (self->kf, self->group_name, key, NULL);
+}
+
+char **
+nm_key_file_db_get_string_list (NMKeyFileDB *self,
+                                const char *key,
+                                gsize *out_len)
+{
+	g_return_val_if_fail (_IS_KEY_FILE_DB (self, TRUE, TRUE), NULL);
+
+	return g_key_file_get_string_list (self->kf, self->group_name, key, out_len, NULL);
+}
+
+/*****************************************************************************/
+
+static void
+_got_dirty (NMKeyFileDB *self,
+            const char *key)
+{
+	nm_assert (_IS_KEY_FILE_DB (self, TRUE, FALSE));
+	nm_assert (!self->dirty);
+
+	_LOGD ("updated entry for %s.%s", self->group_name, key);
+
+	self->dirty = TRUE;
+	if (self->got_dirty_fcn)
+		self->got_dirty_fcn (self, self->user_data);
+}
+
+/*****************************************************************************/
+
+void
+nm_key_file_db_remove_key (NMKeyFileDB *self,
+                           const char *key)
+{
+	gboolean got_dirty = FALSE;
+
+	g_return_if_fail (_IS_KEY_FILE_DB (self, TRUE, FALSE));
+
+	if (!key)
+		return;
+
+	if (!self->dirty) {
+		gs_free_error GError *error = NULL;
+
+		g_key_file_has_key (self->kf, self->group_name, key, &error);
+		got_dirty = (error != NULL);
+	}
+	g_key_file_remove_key (self->kf, self->group_name, key, NULL);
+
+	if (got_dirty)
+		_got_dirty (self, key);
+}
+
+void
+nm_key_file_db_set_value (NMKeyFileDB *self,
+                          const char *key,
+                          const char *value)
+{
+	gs_free char *old_value = NULL;
+	gboolean got_dirty = FALSE;
+
+	g_return_if_fail (_IS_KEY_FILE_DB (self, TRUE, FALSE));
+	g_return_if_fail (key);
+
+	if (!value) {
+		nm_key_file_db_remove_key (self, key);
+		return;
+	}
+
+	if (!self->dirty) {
+		gs_free_error GError *error = NULL;
+
+		old_value = g_key_file_get_value (self->kf, self->group_name, key, &error);
+		if (error)
+			got_dirty = TRUE;
+	}
+
+	g_key_file_set_value (self->kf, self->group_name, key, value);
+
+	if (   !self->dirty
+	    && !got_dirty) {
+		gs_free_error GError *error = NULL;
+		gs_free char *new_value = NULL;
+
+		new_value = g_key_file_get_value (self->kf, self->group_name, key, &error);
+		if (   error
+		    || !new_value
+		    || !nm_streq0 (old_value, new_value))
+			got_dirty = TRUE;
+	}
+
+	if (got_dirty)
+		_got_dirty (self, key);
+}
+
+void
+nm_key_file_db_set_string_list (NMKeyFileDB *self,
+                                const char *key,
+                                const char *const*value,
+                                gssize len)
+{
+	gs_free char *old_value = NULL;
+	gboolean got_dirty = FALSE;;
+
+	g_return_if_fail (_IS_KEY_FILE_DB (self, TRUE, FALSE));
+	g_return_if_fail (key);
+
+	if (!value) {
+		nm_key_file_db_remove_key (self, key);
+		return;
+	}
+
+	if (!self->dirty) {
+		gs_free_error GError *error = NULL;
+
+		old_value = g_key_file_get_value (self->kf, self->group_name, key, &error);
+		if (error)
+			got_dirty = TRUE;
+	}
+
+	if (len < 0)
+		len = NM_PTRARRAY_LEN (value);
+
+	g_key_file_set_string_list (self->kf, self->group_name, key, value, len);
+
+	if (   !self->dirty
+	    && !got_dirty) {
+		gs_free_error GError *error = NULL;
+		gs_free char *new_value = NULL;
+
+		new_value = g_key_file_get_value (self->kf, self->group_name, key, &error);
+		if (   error
+		    || !new_value
+		    || !nm_streq0 (old_value, new_value))
+			got_dirty = TRUE;
+	}
+
+	if (got_dirty)
+		_got_dirty (self, key);
+}
+
+/*****************************************************************************/
+
+void
+nm_key_file_db_to_file (NMKeyFileDB *self,
+                        gboolean force)
+{
+	gs_free_error GError *error = NULL;
+
+	g_return_if_fail (_IS_KEY_FILE_DB (self, TRUE, FALSE));
+
+	if (   !force
+	    && !self->dirty)
+		return;
+
+	self->dirty = FALSE;
+
+	if (!g_key_file_save_to_file (self->kf,
+	                              self->filename,
+	                              &error)) {
+		_LOGD ("failure to write keyfile \"%s\": %s", self->filename, error->message);
+	} else
+		_LOGD ("write keyfile: \"%s\"", self->filename);
+}
diff --git a/shared/nm-glib-aux/nm-keyfile-aux.h b/shared/nm-glib-aux/nm-keyfile-aux.h
new file mode 100644
index 00000000..8563f4d1
--- /dev/null
+++ b/shared/nm-glib-aux/nm-keyfile-aux.h
@@ -0,0 +1,78 @@
+/* NetworkManager -- Network link manager
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_KEYFILE_AUX_H__
+#define __NM_KEYFILE_AUX_H__
+
+/*****************************************************************************/
+
+typedef struct _NMKeyFileDB NMKeyFileDB;
+
+typedef void (*NMKeyFileDBLogFcn) (NMKeyFileDB *self,
+                                   int syslog_level,
+                                   gpointer user_data,
+                                   const char *fmt,
+                                   ...) G_GNUC_PRINTF (4, 5);
+
+typedef void (*NMKeyFileDBGotDirtyFcn) (NMKeyFileDB *self,
+                                        gpointer user_data);
+
+NMKeyFileDB *nm_key_file_db_new (const char *filename,
+                                 const char *group,
+                                 NMKeyFileDBLogFcn log_fcn,
+                                 NMKeyFileDBGotDirtyFcn got_dirty_fcn,
+                                 gpointer user_data);
+
+void nm_key_file_db_start (NMKeyFileDB *self);
+
+NMKeyFileDB *nm_key_file_db_ref (NMKeyFileDB *self);
+void nm_key_file_db_unref (NMKeyFileDB *self);
+
+void nm_key_file_db_destroy (NMKeyFileDB *self);
+
+const char *nm_key_file_db_get_filename (NMKeyFileDB *self);
+
+gboolean nm_key_file_db_is_dirty (NMKeyFileDB *self);
+
+char *nm_key_file_db_get_value (NMKeyFileDB *self,
+                                const char *key);
+
+char **nm_key_file_db_get_string_list (NMKeyFileDB *self,
+                                       const char *key,
+                                       gsize *out_len);
+
+void nm_key_file_db_remove_key (NMKeyFileDB *self,
+                                const char *key);
+
+void nm_key_file_db_set_value (NMKeyFileDB *self,
+                               const char *key,
+                               const char *value);
+
+void nm_key_file_db_set_string_list (NMKeyFileDB *self,
+                                     const char *key,
+                                     const char *const*value,
+                                     gssize len);
+
+void nm_key_file_db_to_file (NMKeyFileDB *self,
+                             gboolean force);
+
+/*****************************************************************************/
+
+#endif /* __NM_KEYFILE_AUX_H__ */
diff --git a/shared/nm-glib-aux/nm-logging-fwd.h b/shared/nm-glib-aux/nm-logging-fwd.h
index 900dfff8..c60a20b5 100644
--- a/shared/nm-glib-aux/nm-logging-fwd.h
+++ b/shared/nm-glib-aux/nm-logging-fwd.h
@@ -110,4 +110,31 @@ void _nm_log_impl (const char *file,
                    const char *fmt,
                    ...) _nm_printf (10, 11);
 
+static inline NMLogLevel
+nm_log_level_from_syslog (int syslog_level)
+{
+	switch (syslog_level) {
+	case 0 /* LOG_EMERG */   : return LOGL_ERR;
+	case 1 /* LOG_ALERT */   : return LOGL_ERR;
+	case 2 /* LOG_CRIT */    : return LOGL_ERR;
+	case 3 /* LOG_ERR */     : return LOGL_ERR;
+	case 4 /* LOG_WARNING */ : return LOGL_WARN;
+	case 5 /* LOG_NOTICE */  : return LOGL_INFO;
+	case 6 /* LOG_INFO */    : return LOGL_DEBUG;
+	case 7 /* LOG_DEBUG */   : return LOGL_TRACE;
+	default:
+		return syslog_level >= 0 ? LOGL_TRACE : LOGL_ERR;
+	}
+}
+
+/*****************************************************************************/
+
+struct timespec;
+
+/* this function must be implemented to handle the notification when
+ * the first monotonic-timestamp is fetched. */
+extern void _nm_utils_monotonic_timestamp_initialized (const struct timespec *tp,
+                                                       gint64 offset_sec,
+                                                       gboolean is_boottime);
+
 #endif /* __NM_LOGGING_DEFINES_H__ */
diff --git a/shared/nm-glib-aux/nm-macros-internal.h b/shared/nm-glib-aux/nm-macros-internal.h
index 2e46cd2d..9502c442 100644
--- a/shared/nm-glib-aux/nm-macros-internal.h
+++ b/shared/nm-glib-aux/nm-macros-internal.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
@@ -635,8 +634,14 @@ NM_G_ERROR_MSG (GError *error)
  * It's useful to check the let the compiler ensure that @value is
  * of a certain type. */
 #define _NM_ENSURE_TYPE(type, value) (_Generic ((value), type: (value)))
+#define _NM_ENSURE_TYPE_CONST(type, value) (_Generic ((value), \
+                                                      const type      : ((const type) (value)), \
+                                                      const type const: ((const type) (value)), \
+                                                            type      : ((const type) (value)), \
+                                                            type const: ((const type) (value))))
 #else
 #define _NM_ENSURE_TYPE(type, value) (value)
+#define _NM_ENSURE_TYPE_CONST(type, value) ((const type) (value))
 #endif
 
 #if _NM_CC_SUPPORT_GENERIC
@@ -865,6 +870,28 @@ fcn (void) \
 
 /*****************************************************************************/
 
+static inline int
+nm_strcmp0 (const char *s1, const char *s2)
+{
+	int c;
+
+	/* like g_strcmp0(), but this is inlinable.
+	 *
+	 * Also, it is guaranteed to return either -1, 0, or 1. */
+	if (s1 == s2)
+		return 0;
+	if (!s1)
+		return -1;
+	if (!s2)
+		return 1;
+	c = strcmp (s1, s2);
+	if (c < 0)
+		return -1;
+	if (c > 0)
+		return 1;
+	return 0;
+}
+
 static inline gboolean
 nm_streq (const char *s1, const char *s2)
 {
@@ -879,14 +906,19 @@ nm_streq0 (const char *s1, const char *s2)
 }
 
 #define NM_STR_HAS_PREFIX(str, prefix) \
-	(strncmp ((str), ""prefix"", NM_STRLEN (prefix)) == 0)
+	({ \
+		const char *const _str = (str); \
+		\
+		_str && (strncmp ((str), ""prefix"", NM_STRLEN (prefix)) == 0); \
+	})
 
 #define NM_STR_HAS_SUFFIX(str, suffix) \
 	({ \
-		const char *_str = (str); \
-		gsize _l = strlen (_str); \
+		const char *_str; \
+		gsize _l; \
 		\
-		(   (_l >= NM_STRLEN (suffix)) \
+		(   (_str = (str)) \
+		 && ((_l = strlen (_str)) >= NM_STRLEN (suffix)) \
 		 && (memcmp (&_str[_l - NM_STRLEN (suffix)], \
 		             ""suffix"", \
 		             NM_STRLEN (suffix)) == 0)); \
@@ -989,10 +1021,9 @@ typedef enum { \
 } _PropertyEnums; \
 static GParamSpec *obj_properties[_PROPERTY_ENUMS_LAST] = { NULL, }
 
-#define NM_GOBJECT_PROPERTIES_DEFINE(obj_type, ...) \
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (__VA_ARGS__); \
+#define NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY(obj_type, obj_properties, property_enums_type, prop_0) \
 static inline void \
-_nm_gobject_notify_together_impl (obj_type *obj, guint n, const _PropertyEnums *props) \
+_nm_gobject_notify_together_impl (obj_type *obj, guint n, const property_enums_type *props) \
 { \
 	const gboolean freeze_thaw = (n > 1); \
 	\
@@ -1002,9 +1033,9 @@ _nm_gobject_notify_together_impl (obj_type *obj, guint n, const _PropertyEnums *
 	if (freeze_thaw) \
 		g_object_freeze_notify ((GObject *) obj); \
 	while (n-- > 0) { \
-		const _PropertyEnums prop = *props++; \
+		const property_enums_type prop = *props++; \
 		\
-		if (prop != PROP_0) { \
+		if (prop != prop_0) { \
 			nm_assert ((gsize) prop < G_N_ELEMENTS (obj_properties)); \
 			nm_assert (obj_properties[prop]); \
 			g_object_notify_by_pspec ((GObject *) obj, obj_properties[prop]); \
@@ -1015,11 +1046,15 @@ _nm_gobject_notify_together_impl (obj_type *obj, guint n, const _PropertyEnums *
 } \
 \
 static inline void \
-_notify (obj_type *obj, _PropertyEnums prop) \
+_notify (obj_type *obj, property_enums_type prop) \
 { \
 	_nm_gobject_notify_together_impl (obj, 1, &prop); \
 } \
 
+#define NM_GOBJECT_PROPERTIES_DEFINE(obj_type, ...) \
+NM_GOBJECT_PROPERTIES_DEFINE_BASE (__VA_ARGS__); \
+NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY (obj_type, obj_properties, _PropertyEnums, PROP_0)
+
 /* 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. */
@@ -1130,6 +1165,30 @@ nm_g_object_unref (gpointer obj)
 #define nm_clear_g_object(pp) \
 	nm_clear_pointer (pp, g_object_unref)
 
+/**
+ * nm_clear_error:
+ * @err: a pointer to pointer to a #GError.
+ *
+ * This is like g_clear_error(). The only difference is
+ * that this is an inline function.
+ */
+static inline void
+nm_clear_error (GError **err)
+{
+	if (err && *err) {
+		g_error_free (*err);
+		*err = NULL;
+	}
+}
+
+/* Patch g_clear_error() to use nm_clear_error(), which is inlineable
+ * and visible to the compiler. For example gs_free_error attribute only
+ * frees the error after checking that it's not %NULL. So, in many cases
+ * the compiler knows that gs_free_error has no effect and can optimize
+ * the call away. By making g_clear_error() inlineable, we give the compiler
+ * more chance to detect that the function actually has no effect. */
+#define g_clear_error(ptr) nm_clear_error(ptr)
+
 static inline gboolean
 nm_clear_g_source (guint *id)
 {
@@ -1219,6 +1278,14 @@ nm_g_variant_ref (GVariant *v)
 	return v;
 }
 
+static inline GVariant *
+nm_g_variant_ref_sink (GVariant *v)
+{
+	if (v)
+		g_variant_ref_sink (v);
+	return v;
+}
+
 static inline void
 nm_g_variant_unref (GVariant *v)
 {
@@ -1226,6 +1293,14 @@ nm_g_variant_unref (GVariant *v)
 		g_variant_unref (v);
 }
 
+static inline GVariant *
+nm_g_variant_take_ref (GVariant *v)
+{
+	if (v)
+		g_variant_take_ref (v);
+	return v;
+}
+
 /*****************************************************************************/
 
 /* Determine whether @x is a power of two (@x being an integer type).
@@ -1494,6 +1569,11 @@ nm_strcmp_p (gconstpointer a, gconstpointer b)
 
 /*****************************************************************************/
 
+#define nm_g_slice_free(ptr) \
+	g_slice_free (typeof (*(ptr)), ptr)
+
+/*****************************************************************************/
+
 /* like g_memdup(). The difference is that the @size argument is of type
  * gsize, while g_memdup() has type guint. Since, the size of container types
  * like GArray is guint as well, this means trying to g_memdup() an
@@ -1523,15 +1603,72 @@ nm_memdup (gconstpointer data, gsize size)
 	return p;
 }
 
+#define nm_malloc_maybe_a(alloca_maxlen, bytes, to_free) \
+	({ \
+		const gsize _bytes = (bytes); \
+		typeof (to_free) _to_free = (to_free); \
+		typeof (*_to_free) _ptr; \
+		\
+		G_STATIC_ASSERT_EXPR ((alloca_maxlen) <= 500); \
+		nm_assert (_to_free && !*_to_free); \
+		\
+		if (_bytes <= (alloca_maxlen)) { \
+			_ptr = g_alloca (_bytes); \
+		} else { \
+			_ptr = g_malloc (_bytes); \
+			*_to_free = _ptr; \
+		}; \
+		\
+		_ptr; \
+	})
+
+#define nm_malloc0_maybe_a(alloca_maxlen, bytes, to_free) \
+	({ \
+		const gsize _bytes = (bytes); \
+		typeof (to_free) _to_free = (to_free); \
+		typeof (*_to_free) _ptr; \
+		\
+		G_STATIC_ASSERT_EXPR ((alloca_maxlen) <= 500); \
+		nm_assert (_to_free && !*_to_free); \
+		\
+		if (_bytes <= (alloca_maxlen)) { \
+			_ptr = g_alloca (_bytes); \
+			memset (_ptr, 0, _bytes); \
+		} else { \
+			_ptr = g_malloc0 (_bytes); \
+			*_to_free = _ptr; \
+		}; \
+		\
+		_ptr; \
+	})
+
+#define nm_memdup_maybe_a(alloca_maxlen, data, size, to_free) \
+	({ \
+		const gsize _size = (size); \
+		typeof (to_free) _to_free_md = (to_free); \
+		typeof (*_to_free_md) _ptr_md = NULL; \
+		\
+		nm_assert (_to_free_md && !*_to_free_md); \
+		\
+		if (_size > 0u) { \
+			_ptr_md = nm_malloc_maybe_a ((alloca_maxlen), _size, _to_free_md); \
+			memcpy (_ptr_md, (data), _size); \
+		} \
+		\
+		_ptr_md; \
+	})
+
 static inline char *
 _nm_strndup_a_step (char *s, const char *str, gsize len)
 {
 	NM_PRAGMA_WARNING_DISABLE ("-Wstringop-truncation");
+	NM_PRAGMA_WARNING_DISABLE ("-Wstringop-overflow");
 	if (len > 0)
 		strncpy (s, str, len);
 	s[len] = '\0';
 	return s;
 	NM_PRAGMA_WARNING_REENABLE;
+	NM_PRAGMA_WARNING_REENABLE;
 }
 
 /* Similar to g_strndup(), however, if the string (including the terminating
diff --git a/shared/nm-glib-aux/nm-obj.h b/shared/nm-glib-aux/nm-obj.h
index 4edd1f3e..06016bdd 100644
--- a/shared/nm-glib-aux/nm-obj.h
+++ b/shared/nm-glib-aux/nm-obj.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
diff --git a/shared/nm-glib-aux/nm-random-utils.c b/shared/nm-glib-aux/nm-random-utils.c
index d7c7da42..f56f8b99 100644
--- a/shared/nm-glib-aux/nm-random-utils.c
+++ b/shared/nm-glib-aux/nm-random-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
diff --git a/shared/nm-glib-aux/nm-random-utils.h b/shared/nm-glib-aux/nm-random-utils.h
index 15a118d3..8e134ee9 100644
--- a/shared/nm-glib-aux/nm-random-utils.h
+++ b/shared/nm-glib-aux/nm-random-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
diff --git a/shared/nm-glib-aux/nm-secret-utils.c b/shared/nm-glib-aux/nm-secret-utils.c
index 81f8b5ae..aeb88877 100644
--- a/shared/nm-glib-aux/nm-secret-utils.c
+++ b/shared/nm-glib-aux/nm-secret-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
diff --git a/shared/nm-glib-aux/nm-secret-utils.h b/shared/nm-glib-aux/nm-secret-utils.h
index 034ef7bd..0fd1ac8b 100644
--- a/shared/nm-glib-aux/nm-secret-utils.h
+++ b/shared/nm-glib-aux/nm-secret-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c
index cf08a77f..c8a253a6 100644
--- a/shared/nm-glib-aux/nm-shared-utils.c
+++ b/shared/nm-glib-aux/nm-shared-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
@@ -734,10 +733,7 @@ _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 ma
 	gint64 v;
 	const char *s = NULL;
 
-	if (str) {
-		while (g_ascii_isspace (str[0]))
-			str++;
-	}
+	str = nm_str_skip_leading_spaces (str);
 	if (!str || !str[0]) {
 		errno = EINVAL;
 		return fallback;
@@ -748,9 +744,9 @@ _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 ma
 
 	if (errno != 0)
 		return fallback;
+
 	if (s[0] != '\0') {
-		while (g_ascii_isspace (s[0]))
-			s++;
+		s = nm_str_skip_leading_spaces (s);
 		if (s[0] != '\0') {
 			errno = EINVAL;
 			return fallback;
@@ -810,6 +806,15 @@ _nm_utils_ascii_str_to_uint64 (const char *str, guint base, guint64 min, guint64
 
 /*****************************************************************************/
 
+int
+nm_strcmp_with_data (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+	const char *s1 = a;
+	const char *s2 = b;
+
+	return strcmp (s1, s2);
+}
+
 /* like nm_strcmp_p(), suitable for g_ptr_array_sort_with_data().
  * g_ptr_array_sort() just casts nm_strcmp_p() to a function of different
  * signature. I guess, in glib there are knowledgeable people that ensure
@@ -827,6 +832,15 @@ nm_strcmp_p_with_data (gconstpointer a, gconstpointer b, gpointer user_data)
 }
 
 int
+nm_strcmp0_p_with_data (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+	const char *s1 = *((const char **) a);
+	const char *s2 = *((const char **) b);
+
+	return nm_strcmp0 (s1, s2);
+}
+
+int
 nm_cmp_uint32_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data)
 {
 	const guint32 a = *((const guint32 *) p_a);
@@ -1350,6 +1364,8 @@ _nm_utils_strv_cleanup (char **strv,
 		return strv;
 
 	if (strip_whitespace) {
+		/* we only modify the strings pointed to by @strv if @strip_whitespace is
+		 * requested. Otherwise, the strings themselves are untouched. */
 		for (i = 0; strv[i]; i++)
 			g_strstrip (strv[i]);
 	}
@@ -2130,6 +2146,8 @@ nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll)
 	return 0;
 }
 
+/*****************************************************************************/
+
 NMUtilsNamedValue *
 nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len)
 {
@@ -2154,15 +2172,106 @@ nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len)
 	values[i].name = NULL;
 	values[i].value_ptr = NULL;
 
-	if (len > 1) {
-		g_qsort_with_data (values, len, sizeof (values[0]),
-		                   nm_utils_named_entry_cmp_with_data, NULL);
-	}
+	nm_utils_named_value_list_sort (values, len, NULL, NULL);
 
 	NM_SET_OUT (out_len, len);
 	return values;
 }
 
+gssize
+nm_utils_named_value_list_find (const NMUtilsNamedValue *arr,
+                                gsize len,
+                                const char *name,
+                                gboolean sorted)
+{
+	gsize i;
+
+	nm_assert (name);
+
+#if NM_MORE_ASSERTS > 5
+	{
+		for (i = 0; i < len; i++) {
+			const NMUtilsNamedValue *v = &arr[i];
+
+			nm_assert (v->name);
+			if (   sorted
+			    && i > 0)
+				nm_assert (strcmp (arr[i - 1].name, v->name) < 0);
+		}
+	}
+
+	nm_assert (   !sorted
+	           || nm_utils_named_value_list_is_sorted (arr, len, FALSE, NULL, NULL));
+#endif
+
+	if (sorted) {
+		return nm_utils_array_find_binary_search (arr,
+		                                          sizeof (NMUtilsNamedValue),
+		                                          len,
+		                                          &name,
+		                                          nm_strcmp_p_with_data,
+		                                          NULL);
+	}
+	for (i = 0; i < len; i++) {
+		if (nm_streq (arr[i].name, name))
+			return i;
+	}
+	return ~((gssize) len);
+}
+
+gboolean
+nm_utils_named_value_list_is_sorted (const NMUtilsNamedValue *arr,
+                                     gsize len,
+                                     gboolean accept_duplicates,
+                                     GCompareDataFunc compare_func,
+                                     gpointer user_data)
+{
+	gsize i;
+	int c_limit;
+
+	if (len == 0)
+		return TRUE;
+
+	g_return_val_if_fail (arr, FALSE);
+
+	if (!compare_func)
+		compare_func = nm_strcmp_p_with_data;
+
+	c_limit = accept_duplicates ? 0 : -1;
+
+	for (i = 1; i < len; i++) {
+		int c;
+
+		c = compare_func (&arr[i - 1], &arr[i], user_data);
+		if (c > c_limit)
+			return FALSE;
+	}
+	return TRUE;
+}
+
+void
+nm_utils_named_value_list_sort (NMUtilsNamedValue *arr,
+                                gsize len,
+                                GCompareDataFunc compare_func,
+                                gpointer user_data)
+{
+	if (len == 0)
+		return;
+
+	g_return_if_fail (arr);
+
+	if (len == 1)
+		return;
+
+	g_qsort_with_data (arr,
+	                   len,
+	                   sizeof (NMUtilsNamedValue),
+	                   compare_func ?: nm_strcmp_p_with_data,
+	                   user_data);
+}
+
+/*****************************************************************************/
+
 gpointer *
 nm_utils_hash_keys_to_array (GHashTable *hash,
                              GCompareDataFunc compare_func,
@@ -2193,12 +2302,41 @@ nm_utils_hash_keys_to_array (GHashTable *hash,
 	return keys;
 }
 
+gboolean
+nm_utils_hashtable_same_keys (const GHashTable *a,
+                              const GHashTable *b)
+{
+	GHashTableIter h;
+	const char *k;
+
+	if (a == b)
+		return TRUE;
+	if (!a || !b)
+		return FALSE;
+	if (g_hash_table_size ((GHashTable *) a) != g_hash_table_size ((GHashTable *) b))
+		return FALSE;
+
+	g_hash_table_iter_init (&h, (GHashTable *) a);
+	while (g_hash_table_iter_next (&h, (gpointer) &k, NULL)) {
+		if (!g_hash_table_contains ((GHashTable *) b, k))
+			return FALSE;
+	}
+
+#if NM_MORE_ASSERTS > 5
+	g_hash_table_iter_init (&h, (GHashTable *) b);
+	while (g_hash_table_iter_next (&h, (gpointer) &k, NULL))
+		nm_assert (g_hash_table_contains ((GHashTable *) a, k));
+#endif
+
+	return TRUE;
+}
+
 char **
 nm_utils_strv_make_deep_copied (const char **strv)
 {
 	gsize i;
 
-	/* it takes a strv dictionary, and copies each
+	/* it takes a strv list, and copies each
 	 * strings. Note that this updates @strv *in-place*
 	 * and returns it. */
 
@@ -2210,6 +2348,79 @@ nm_utils_strv_make_deep_copied (const char **strv)
 	return (char **) strv;
 }
 
+char **
+nm_utils_strv_make_deep_copied_n (const char **strv, gsize len)
+{
+	gsize i;
+
+	/* it takes a strv array with len elements, and copies each
+	 * strings. Note that this updates @strv *in-place*
+	 * and returns it. */
+
+	if (!strv)
+		return NULL;
+	for (i = 0; i < len; i++)
+		strv[i] = g_strdup (strv[i]);
+
+	return (char **) strv;
+}
+
+/**
+ * @strv: the strv array to copy. It may be %NULL if @len
+ *   is negative or zero (in which case %NULL will be returned).
+ * @len: the length of strings in @str. If negative, strv is assumed
+ *   to be a NULL terminated array.
+ *
+ * Like g_strdupv(), with two differences:
+ *
+ * - accepts a @len parameter for non-null terminated strv array.
+ *
+ * - this never returns an empty strv array, but always %NULL if
+ *   there are no strings.
+ *
+ * Note that if @len is non-negative, then it still must not
+ * contain any %NULL pointers within the first @len elements.
+ * Otherwise you would leak elements if you try to free the
+ * array with g_strfreev(). Allowing that would be error prone.
+ *
+ * Returns: (transfer full): a clone of the strv array. Always
+ *   %NULL terminated.
+ */
+char **
+nm_utils_strv_dup (gpointer strv, gssize len)
+{
+	gsize i, l;
+	char **v;
+	const char *const *const src = strv;
+
+	if (len < 0)
+		l = NM_PTRARRAY_LEN (src);
+	else
+		l = len;
+	if (l == 0) {
+		/* this function never returns an empty strv array. If you
+		 * need that, handle it yourself. */
+		return NULL;
+	}
+
+	v = g_new (char *, l + 1);
+	for (i = 0; i < l; i++) {
+
+		if (G_UNLIKELY (!src[i])) {
+			/* NULL strings are not allowed. Clear the remainder of the array
+			 * and return it (with assertion failure). */
+			l++;
+			for (; i < l; i++)
+				v[i] = NULL;
+			g_return_val_if_reached (v);
+		}
+
+		v[i] = g_strdup (src[i]);
+	}
+	v[l] = NULL;
+	return v;
+}
+
 /*****************************************************************************/
 
 gssize
@@ -2499,8 +2710,8 @@ fail:
  * @len: the number of elements in strv. If negative,
  *   strv must be a NULL terminated array and the length
  *   will be calculated first. If @len is a positive
- *   number, all first @len elements in @strv must be
- *   non-NULL, valid strings.
+ *   number, @strv is allowed to contain %NULL strings
+ *   too.
  *
  * Ascending sort of the array @strv inplace, using plain strcmp() string
  * comparison.
@@ -2508,9 +2719,16 @@ fail:
 void
 _nm_utils_strv_sort (const char **strv, gssize len)
 {
+	GCompareDataFunc cmp;
 	gsize l;
 
-	l = len < 0 ? (gsize) NM_PTRARRAY_LEN (strv) : (gsize) len;
+	if (len < 0) {
+		l = NM_PTRARRAY_LEN (strv);
+		cmp = nm_strcmp_p_with_data;
+	} else {
+		l = len;
+		cmp = nm_strcmp0_p_with_data;
+	}
 
 	if (l <= 1)
 		return;
@@ -2520,7 +2738,7 @@ _nm_utils_strv_sort (const char **strv, gssize len)
 	g_qsort_with_data (strv,
 	                   l,
 	                   sizeof (const char *),
-	                   nm_strcmp_p_with_data,
+	                   cmp,
 	                   NULL);
 }
 
@@ -2577,6 +2795,53 @@ _nm_utils_strv_cmp_n (const char *const*strv1,
 
 /*****************************************************************************/
 
+/**
+ * nm_utils_g_slist_find_str:
+ * @list: the #GSList with NUL terminated strings to search
+ * @needle: the needle string to look for.
+ *
+ * Search the list for @needle and return the first found match
+ * (or %NULL if not found). Uses strcmp() for finding the first matching
+ * element.
+ *
+ * Returns: the #GSList element with @needle as string value or
+ *   %NULL if not found.
+ */
+GSList *
+nm_utils_g_slist_find_str (const GSList *list,
+                           const char *needle)
+{
+	nm_assert (needle);
+
+	for (; list; list = list->next) {
+		nm_assert (list->data);
+		if (nm_streq (list->data, needle))
+			return (GSList *) list;
+	}
+	return NULL;
+}
+
+/**
+ * nm_utils_g_slist_strlist_cmp:
+ * @a: the left #GSList of strings
+ * @b: the right #GSList of strings to compare.
+ *
+ * Compares two string lists. The data elements are compared with
+ * strcmp(), alloing %NULL elements.
+ *
+ * Returns: 0, 1, or -1, depending on how the lists compare.
+ */
+int
+nm_utils_g_slist_strlist_cmp (const GSList *a, const GSList *b)
+{
+	for (; a && b; a = a->next, b = b->next)
+		NM_CMP_DIRECT_STRCMP0 (a->data, b->data);
+	NM_CMP_SELF (a, b);
+	return 0;
+}
+
+/*****************************************************************************/
+
 gpointer
 _nm_utils_user_data_pack (int nargs, gconstpointer *args)
 {
@@ -2753,10 +3018,13 @@ nm_utils_memeqzero (gconstpointer data, gsize length)
  *   be returned and must be freed by the caller.
  *   If not %NULL, the buffer must already be preallocated and contain
  *   at least (@length*2+1) or (@length*3) bytes, depending on the delimiter.
+ *   If @length is zero, then of course at least one byte will be allocated
+ *   or @out (if given) must contain at least room for the trailing NUL byte.
  *
  * Returns: the binary value converted to a hex string. If @out is given,
  *   this always returns @out. If @out is %NULL, a newly allocated string
- *   is returned.
+ *   is returned. This never returns %NULL, for buffers of length zero
+ *   an empty string is returend.
  */
 char *
 nm_utils_bin2hexstr_full (gconstpointer addr,
@@ -2772,9 +3040,11 @@ nm_utils_bin2hexstr_full (gconstpointer addr,
 	if (out)
 		out0 = out;
 	else {
-		out0 = out = g_new (char, delimiter == '\0'
-		                          ? length * 2 + 1
-		                          : length * 3);
+		out0 = out = g_new (char, length == 0
+		                          ? 1u
+		                          : (  delimiter == '\0'
+		                             ? length * 2u + 1u
+		                             : length * 3u));
 	}
 
 	/* @out must contain at least @length*3 bytes if @delimiter is set,
@@ -2939,3 +3209,64 @@ fail:
 	NM_SET_OUT (out_len, 0);
 	return NULL;
 }
+
+/*****************************************************************************/
+
+GVariant *
+nm_utils_gvariant_vardict_filter (GVariant *src,
+                                  gboolean (*filter_fcn) (const char *key,
+                                                          GVariant *val,
+                                                          char **out_key,
+                                                          GVariant **out_val,
+                                                          gpointer user_data),
+                                  gpointer user_data)
+{
+	GVariantIter iter;
+	GVariantBuilder builder;
+	const char *key;
+	GVariant *val;
+
+	g_return_val_if_fail (src && g_variant_is_of_type (src, G_VARIANT_TYPE_VARDICT), NULL);
+	g_return_val_if_fail (filter_fcn, NULL);
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+
+	g_variant_iter_init (&iter, src);
+	while (g_variant_iter_next (&iter, "{&sv}", &key, &val)) {
+		_nm_unused gs_unref_variant GVariant *val_free = val;
+		gs_free char *key2 = NULL;
+		gs_unref_variant GVariant *val2 = NULL;
+
+		if (filter_fcn (key,
+		                val,
+		                &key2,
+		                &val2,
+		                user_data)) {
+			g_variant_builder_add (&builder,
+			                       "{sv}",
+			                       key2 ?: key,
+			                       val2 ?: val);
+		}
+	}
+
+	return g_variant_builder_end (&builder);
+}
+
+static gboolean
+_gvariant_vardict_filter_drop_one (const char *key,
+                                   GVariant *val,
+                                   char **out_key,
+                                   GVariant **out_val,
+                                   gpointer user_data)
+{
+	return !nm_streq (key, user_data);
+}
+
+GVariant *
+nm_utils_gvariant_vardict_filter_drop_one (GVariant *src,
+                                           const char *key)
+{
+	return nm_utils_gvariant_vardict_filter (src,
+	                                         _gvariant_vardict_filter_drop_one,
+	                                         (gpointer) key);
+}
diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h
index af3c2f83..d9c430d4 100644
--- a/shared/nm-glib-aux/nm-shared-utils.h
+++ b/shared/nm-glib-aux/nm-shared-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
@@ -171,6 +170,13 @@ nm_ip4_addr_is_localhost (in_addr_t addr4)
             return _cc < 0 ? -1 : 1; \
     } G_STMT_END
 
+#define NM_CMP_RETURN_DIRECT(c) \
+    G_STMT_START { \
+        const int _cc = (c); \
+        if (_cc) \
+            return _cc; \
+    } G_STMT_END
+
 #define NM_CMP_SELF(a, b) \
     G_STMT_START { \
         typeof (a) _a = (a); \
@@ -196,8 +202,11 @@ nm_ip4_addr_is_localhost (in_addr_t addr4)
 #define NM_CMP_DIRECT_MEMCMP(a, b, size) \
     NM_CMP_RETURN (memcmp ((a), (b), (size)))
 
+#define NM_CMP_DIRECT_STRCMP(a, b) \
+    NM_CMP_RETURN_DIRECT (strcmp ((a), (b)))
+
 #define NM_CMP_DIRECT_STRCMP0(a, b) \
-    NM_CMP_RETURN (g_strcmp0 ((a), (b)))
+    NM_CMP_RETURN_DIRECT (nm_strcmp0 ((a), (b)))
 
 #define NM_CMP_DIRECT_IN6ADDR(a, b) \
     G_STMT_START { \
@@ -229,16 +238,16 @@ nm_ip4_addr_is_localhost (in_addr_t addr4)
         const char *_b = ((b)->field); \
         \
         if (_a != _b) { \
-            NM_CMP_RETURN (g_strcmp0 (_a, _b)); \
+            NM_CMP_RETURN_DIRECT (nm_strcmp0 (_a, _b)); \
         } \
     } G_STMT_END
 
 #define NM_CMP_FIELD_STR0(a, b, field) \
-    NM_CMP_RETURN (g_strcmp0 (((a)->field), ((b)->field)))
+    NM_CMP_RETURN_DIRECT (nm_strcmp0 (((a)->field), ((b)->field)))
 
 #define NM_CMP_FIELD_MEMCMP_LEN(a, b, field, len) \
     NM_CMP_RETURN (memcmp (&((a)->field), &((b)->field), \
-                           MIN (len, sizeof ((a)->field))))
+                           NM_MIN (len, sizeof ((a)->field))))
 
 #define NM_CMP_FIELD_MEMCMP(a, b, field) \
     NM_CMP_RETURN (memcmp (&((a)->field), \
@@ -305,6 +314,20 @@ GVariant *nm_utils_gbytes_to_variant_ay (GBytes *bytes);
 
 /*****************************************************************************/
 
+GVariant *nm_utils_gvariant_vardict_filter (GVariant *src,
+                                            gboolean (*filter_fcn) (const char *key,
+                                                                    GVariant *val,
+                                                                    char **out_key,
+                                                                    GVariant **out_val,
+                                                                    gpointer user_data),
+                                            gpointer user_data);
+
+GVariant *
+nm_utils_gvariant_vardict_filter_drop_one (GVariant *src,
+                                           const char *key);
+
+/*****************************************************************************/
+
 static inline int
 nm_utils_hexchar_to_int (char ch)
 {
@@ -693,6 +716,8 @@ _nm_g_slice_free_fcn_define (16)
  * @NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY: the profile is currently not
  *   available/compatible with the device, but this may be only temporary.
  *
+ * @NM_UTILS_ERROR_SETTING_MISSING: the setting is missing
+ *
  * @NM_UTILS_ERROR_INVALID_ARGUMENT: invalid argument.
  */
 typedef enum {
@@ -715,6 +740,8 @@ typedef enum {
 	NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE,
 	NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
 
+	NM_UTILS_ERROR_SETTING_MISSING,
+
 } NMUtilsError;
 
 #define NM_UTILS_ERROR (nm_utils_error_quark ())
@@ -735,7 +762,13 @@ nm_utils_error_set_literal (GError **error, int error_code, const char *literal)
 }
 
 #define nm_utils_error_set(error, error_code, ...) \
-	g_set_error ((error), NM_UTILS_ERROR, error_code, __VA_ARGS__)
+	G_STMT_START { \
+		if (NM_NARG (__VA_ARGS__) == 1) { \
+			g_set_error_literal ((error), NM_UTILS_ERROR, (error_code), _NM_UTILS_MACRO_FIRST (__VA_ARGS__)); \
+		} else { \
+			g_set_error ((error), NM_UTILS_ERROR, (error_code), __VA_ARGS__); \
+		} \
+	} G_STMT_END
 
 #define nm_utils_error_set_errno(error, errsv, fmt, ...) \
 	G_STMT_START { \
@@ -885,7 +918,9 @@ nm_utf8_collate0 (const char *a, const char *b)
 	return g_utf8_collate (a, b);
 }
 
+int nm_strcmp_with_data (gconstpointer a, gconstpointer b, gpointer user_data);
 int nm_strcmp_p_with_data (gconstpointer a, gconstpointer b, gpointer user_data);
+int nm_strcmp0_p_with_data (gconstpointer a, gconstpointer b, gpointer user_data);
 int nm_cmp_uint32_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data);
 int nm_cmp_int2ptr_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data);
 
@@ -906,11 +941,26 @@ typedef struct {
 	};
 } NMUtilsNamedValue;
 
-#define nm_utils_named_entry_cmp           nm_strcmp_p
-#define nm_utils_named_entry_cmp_with_data nm_strcmp_p_with_data
-
 NMUtilsNamedValue *nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len);
 
+gssize nm_utils_named_value_list_find (const NMUtilsNamedValue *arr,
+                                       gsize len,
+                                       const char *name,
+                                       gboolean sorted);
+
+gboolean nm_utils_named_value_list_is_sorted (const NMUtilsNamedValue *arr,
+                                              gsize len,
+                                              gboolean accept_duplicates,
+                                              GCompareDataFunc compare_func,
+                                              gpointer user_data);
+
+void nm_utils_named_value_list_sort (NMUtilsNamedValue *arr,
+                                     gsize len,
+                                     GCompareDataFunc compare_func,
+                                     gpointer user_data);
+
+/*****************************************************************************/
+
 gpointer *nm_utils_hash_keys_to_array (GHashTable *hash,
                                        GCompareDataFunc compare_func,
                                        gpointer user_data,
@@ -927,14 +977,28 @@ nm_utils_strdict_get_keys (const GHashTable *hash,
 	                                                    out_length);
 }
 
+gboolean nm_utils_hashtable_same_keys (const GHashTable *a,
+                                       const GHashTable *b);
+
 char **nm_utils_strv_make_deep_copied (const char **strv);
 
+char **nm_utils_strv_make_deep_copied_n (const char **strv, gsize len);
+
 static inline char **
 nm_utils_strv_make_deep_copied_nonnull (const char **strv)
 {
 	return nm_utils_strv_make_deep_copied (strv) ?: g_new0 (char *, 1);
 }
 
+char **nm_utils_strv_dup (gpointer strv, gssize len);
+
+/*****************************************************************************/
+
+GSList *nm_utils_g_slist_find_str (const GSList *list,
+                                   const char *needle);
+
+int nm_utils_g_slist_strlist_cmp (const GSList *a, const GSList *b);
+
 /*****************************************************************************/
 
 gssize nm_utils_ptrarray_find_binary_search (gconstpointer *list,
diff --git a/shared/nm-glib-aux/nm-time-utils.c b/shared/nm-glib-aux/nm-time-utils.c
index ae526c34..7735f29d 100644
--- a/shared/nm-glib-aux/nm-time-utils.c
+++ b/shared/nm-glib-aux/nm-time-utils.c
@@ -22,6 +22,8 @@
 
 #include "nm-time-utils.h"
 
+#include "nm-logging-fwd.h"
+
 /*****************************************************************************/
 
 typedef struct {
@@ -229,15 +231,15 @@ nm_utils_get_monotonic_timestamp_s (void)
 /**
  * nm_utils_monotonic_timestamp_as_boottime:
  * @timestamp: the monotonic-timestamp that should be converted into CLOCK_BOOTTIME.
- * @timestamp_ns_per_tick: How many nano seconds make one unit of @timestamp? E.g. if
- * @timestamp is in unit seconds, pass %NM_UTILS_NS_PER_SECOND; @timestamp in nano
- * seconds, pass 1; @timestamp in milli seconds, pass %NM_UTILS_NS_PER_SECOND/1000; etc.
+ * @timestamp_ns_per_tick: How many nanoseconds make one unit of @timestamp? E.g. if
+ *   @timestamp is in unit seconds, pass %NM_UTILS_NS_PER_SECOND; if @timestamp is
+ *   in nanoseconds, pass 1; if @timestamp is in milliseconds, pass %NM_UTILS_NS_PER_SECOND/1000.
  *
  * Returns: the monotonic-timestamp as CLOCK_BOOTTIME, as returned by clock_gettime().
- * The unit is the same as the passed in @timestamp basd on @timestamp_ns_per_tick.
- * E.g. if you passed @timestamp in as seconds, it will return boottime in seconds.
- * If @timestamp is a non-positive, it returns -1. Note that a (valid) monotonic-timestamp
- * is always positive.
+ *   The unit is the same as the passed in @timestamp based on @timestamp_ns_per_tick.
+ *   E.g. if you passed @timestamp in as seconds, it will return boottime in seconds.
+ *   If @timestamp is non-positive, it returns -1. Note that a (valid) monotonic-timestamp
+ *   is always positive.
  *
  * On older kernels that don't support CLOCK_BOOTTIME, the returned time is instead CLOCK_MONOTONIC.
  **/
@@ -263,6 +265,8 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_
 
 	p = _t_get_global_state ();
 
+	nm_assert (p->offset_sec <= 0);
+
 	/* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */
 	offset = p->offset_sec * (NM_UTILS_NS_PER_SECOND / timestamp_ns_per_tick);
 
@@ -271,3 +275,23 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_
 
 	return timestamp - offset;
 }
+
+gint64
+nm_utils_clock_gettime_ns (clockid_t clockid)
+{
+	struct timespec tp;
+
+	if (clock_gettime (clockid, &tp) != 0)
+		return -NM_ERRNO_NATIVE (errno);
+	return nm_utils_timespec_to_ns (&tp);
+}
+
+gint64
+nm_utils_clock_gettime_ms (clockid_t clockid)
+{
+	struct timespec tp;
+
+	if (clock_gettime (clockid, &tp) != 0)
+		return -NM_ERRNO_NATIVE (errno);
+	return nm_utils_timespec_to_ms (&tp);
+}
diff --git a/shared/nm-glib-aux/nm-time-utils.h b/shared/nm-glib-aux/nm-time-utils.h
index 7e4f4f25..52d6637d 100644
--- a/shared/nm-glib-aux/nm-time-utils.h
+++ b/shared/nm-glib-aux/nm-time-utils.h
@@ -21,6 +21,22 @@
 #ifndef __NM_TIME_UTILS_H__
 #define __NM_TIME_UTILS_H__
 
+#include <time.h>
+
+static inline gint64
+nm_utils_timespec_to_ns (const struct timespec *ts)
+{
+	return   (((gint64) ts->tv_sec) * ((gint64) NM_UTILS_NS_PER_SECOND))
+	       +  ((gint64) ts->tv_nsec);
+}
+
+static inline gint64
+nm_utils_timespec_to_ms (const struct timespec *ts)
+{
+	return   (((gint64) ts->tv_sec)  * ((gint64) 1000))
+	       + (((gint64) ts->tv_nsec) / ((gint64) NM_UTILS_NS_PER_SECOND / 1000));
+}
+
 gint64 nm_utils_get_monotonic_timestamp_ns (void);
 gint64 nm_utils_get_monotonic_timestamp_us (void);
 gint64 nm_utils_get_monotonic_timestamp_ms (void);
@@ -34,12 +50,7 @@ nm_utils_get_monotonic_timestamp_ns_cached (gint64 *cache_now)
 	       ?: (*cache_now = nm_utils_get_monotonic_timestamp_ns ());
 }
 
-struct timespec;
-
-/* this function must be implemented to handle the notification when
- * the first monotonic-timestamp is fetched. */
-extern void _nm_utils_monotonic_timestamp_initialized (const struct timespec *tp,
-                                                       gint64 offset_sec,
-                                                       gboolean is_boottime);
+gint64 nm_utils_clock_gettime_ns (clockid_t clockid);
+gint64 nm_utils_clock_gettime_ms (clockid_t clockid);
 
 #endif /* __NM_TIME_UTILS_H__ */
diff --git a/shared/nm-glib-aux/nm-value-type.h b/shared/nm-glib-aux/nm-value-type.h
new file mode 100644
index 00000000..b4d6898f
--- /dev/null
+++ b/shared/nm-glib-aux/nm-value-type.h
@@ -0,0 +1,208 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_VALUE_TYPE_H__
+#define __NM_VALUE_TYPE_H__
+
+typedef enum {
+	NM_VALUE_TYPE_UNSPEC = 1,
+	NM_VALUE_TYPE_BOOL   = 2,
+	NM_VALUE_TYPE_INT32  = 3,
+	NM_VALUE_TYPE_INT    = 4,
+	NM_VALUE_TYPE_STRING = 5,
+} NMValueType;
+
+/*****************************************************************************/
+
+#ifdef NM_VALUE_TYPE_DEFINE_FUNCTIONS
+
+typedef union {
+	bool             v_bool;
+	gint32           v_int32;
+	int              v_int;
+	const char      *v_string;
+
+	/* for convenience, also let the union contain other pointer types. These are
+	 * for NM_VALUE_TYPE_UNSPEC. */
+	gconstpointer   *v_ptr;
+	const GPtrArray *v_ptrarray;
+
+} NMValueTypUnion;
+
+/* Set the NMValueTypUnion. You can also assign the member directly.
+ * The only purpose of this is that it also returns a pointer to the
+ * union. So, you can do
+ *
+ *   ptr = NM_VALUE_TYP_UNION_SET (&value_typ_union_storage, v_bool, TRUE);
+ */
+#define NM_VALUE_TYP_UNION_SET(_arg, _type, _val) \
+	({ \
+		NMValueTypUnion *const _arg2 = (_arg); \
+		\
+		*_arg2 = (NMValueTypUnion) { \
+			._type = (_val), \
+		}; \
+		_arg2; \
+	})
+
+typedef struct {
+	bool has;
+	NMValueTypUnion val;
+} NMValueTypUnioMaybe;
+
+#define NM_VALUE_TYP_UNIO_MAYBE_SET(_arg, _type, _val) \
+	({ \
+		NMValueTypUnioMaybe *const _arg2 = (_arg); \
+		\
+		*_arg2 = (NMValueTypUnioMaybe) { \
+			.has       = TRUE, \
+			.val._type = (_val), \
+		}; \
+		_arg2; \
+	})
+
+/*****************************************************************************/
+
+static inline int
+nm_value_type_cmp (NMValueType value_type,
+                   gconstpointer p_a,
+                   gconstpointer p_b)
+{
+	switch (value_type) {
+	case NM_VALUE_TYPE_BOOL:   NM_CMP_DIRECT (*((const bool   *) p_a), *((const bool   *) p_b)); return 0;
+	case NM_VALUE_TYPE_INT32:  NM_CMP_DIRECT (*((const gint32 *) p_a), *((const gint32 *) p_b)); return 0;
+	case NM_VALUE_TYPE_INT:    NM_CMP_DIRECT (*((const int    *) p_a), *((const int    *) p_b)); return 0;
+	case NM_VALUE_TYPE_STRING: return nm_strcmp0 (*((const char *const*) p_a), *((const char *const*) p_b));
+	case NM_VALUE_TYPE_UNSPEC:
+		break;
+	}
+	nm_assert_not_reached ();
+	return 0;
+}
+
+static inline gboolean
+nm_value_type_equal (NMValueType value_type,
+                     gconstpointer p_a,
+                     gconstpointer p_b)
+{
+	return nm_value_type_cmp (value_type, p_a, p_b) == 0;
+}
+
+static inline void
+nm_value_type_copy (NMValueType value_type,
+                    gpointer dst,
+                    gconstpointer src)
+{
+	switch (value_type) {
+	case NM_VALUE_TYPE_BOOL:   (*((bool   *) dst) = *((const bool   *) src)); return;
+	case NM_VALUE_TYPE_INT32:  (*((gint32 *) dst) = *((const gint32 *) src)); return;
+	case NM_VALUE_TYPE_INT:    (*((int    *) dst) = *((const int    *) src)); return;
+	case NM_VALUE_TYPE_STRING:
+		/* self assignment safe! */
+		if (*((char **) dst) != *((const char *const*) src)) {
+			g_free (*((char **) dst));
+			*((char **) dst) = g_strdup (*((const char *const*) src));
+		}
+		return;
+	case NM_VALUE_TYPE_UNSPEC:
+		break;
+	}
+	nm_assert_not_reached ();
+}
+
+static inline void
+nm_value_type_get_from_variant (NMValueType value_type,
+                                gpointer dst,
+                                GVariant *variant,
+                                gboolean clone)
+{
+	switch (value_type) {
+	case NM_VALUE_TYPE_BOOL:   *((bool   *) dst) = g_variant_get_boolean (variant); return;
+	case NM_VALUE_TYPE_INT32:  *((gint32 *) dst) = g_variant_get_int32 (variant);   return;
+	case NM_VALUE_TYPE_STRING:
+		if (clone) {
+			g_free (*((char **) dst));
+			*((char **) dst) = g_variant_dup_string (variant, NULL);
+		} else {
+			/* we don't clone the string, nor free the previous value. */
+			*((const char **) dst) = g_variant_get_string (variant, NULL);
+		}
+		return;
+
+	case NM_VALUE_TYPE_INT:
+		/* "int" also does not have a define variant type, because it's not
+		 * clear how many bits we would need. */
+
+		/* fall-through */
+	case NM_VALUE_TYPE_UNSPEC:
+		break;
+	}
+	nm_assert_not_reached ();
+}
+
+static inline GVariant *
+nm_value_type_to_variant (NMValueType value_type,
+                          gconstpointer src)
+{
+	const char *v_string;
+
+	switch (value_type) {
+	case NM_VALUE_TYPE_BOOL:   return g_variant_new_boolean (*((const bool   *) src));
+	case NM_VALUE_TYPE_INT32:  return g_variant_new_int32   (*((const gint32 *) src));;
+	case NM_VALUE_TYPE_STRING:
+		v_string = *((const char *const*) src);
+		return v_string ? g_variant_new_string (v_string) : NULL;
+
+	case NM_VALUE_TYPE_INT:
+		/* "int" also does not have a define variant type, because it's not
+		 * clear how many bits we would need. */
+
+		/* fall-through */
+	case NM_VALUE_TYPE_UNSPEC:
+		break;
+	}
+	nm_assert_not_reached ();
+	return NULL;
+}
+
+static inline const GVariantType *
+nm_value_type_get_variant_type (NMValueType value_type)
+{
+	switch (value_type) {
+	case NM_VALUE_TYPE_BOOL:   return G_VARIANT_TYPE_BOOLEAN;
+	case NM_VALUE_TYPE_INT32:  return G_VARIANT_TYPE_INT32;
+	case NM_VALUE_TYPE_STRING: return G_VARIANT_TYPE_STRING;
+
+	case NM_VALUE_TYPE_INT:
+		/* "int" also does not have a define variant type, because it's not
+		 * clear how many bits we would need. */
+
+		/* fall-through */
+	case NM_VALUE_TYPE_UNSPEC:
+		break;
+	}
+	nm_assert_not_reached ();
+	return NULL;
+}
+
+/*****************************************************************************/
+
+#endif /* NM_VALUE_TYPE_DEFINE_FUNCTIONS */
+
+#endif  /* __NM_VALUE_TYPE_H__ */