about summary refs log tree commit diff
path: root/shared/nm-utils
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2017-05-11 14:55:55 +0200
committerMichael Biebl <biebl@debian.org>2017-05-11 14:55:55 +0200
commitc333f062ddcba9b35330647bf6cbd0a07f2d786e (patch)
tree257c3a0c74c09f4ad2328eab5b932806405f0c1c /shared/nm-utils
parenta222e56e103f949b148a6942e385ccca2c26d9f3 (diff)
New upstream version 1.8.0 upstream/1.8.0
Diffstat (limited to 'shared/nm-utils')
-rw-r--r--shared/nm-utils/nm-glib.h8
-rw-r--r--shared/nm-utils/nm-macros-internal.h160
-rw-r--r--shared/nm-utils/nm-shared-utils.c50
-rw-r--r--shared/nm-utils/nm-shared-utils.h4
-rw-r--r--shared/nm-utils/nm-test-utils.h151
-rw-r--r--shared/nm-utils/nm-udev-utils.c292
-rw-r--r--shared/nm-utils/nm-udev-utils.h48
7 files changed, 608 insertions, 105 deletions
diff --git a/shared/nm-utils/nm-glib.h b/shared/nm-utils/nm-glib.h
index 03251ca0..dd18756a 100644
--- a/shared/nm-utils/nm-glib.h
+++ b/shared/nm-utils/nm-glib.h
@@ -134,7 +134,7 @@ __g_type_ensure (GType type)
 
 /* Rumtime check for glib version. First do a compile time check which
  * (if satisfied) shortcuts the runtime check. */
-inline static gboolean
+static inline gboolean
 nm_glib_check_version (guint major, guint minor, guint micro)
 {
 	return    GLIB_CHECK_VERSION (major, minor, micro)
@@ -147,7 +147,7 @@ nm_glib_check_version (guint major, guint minor, guint micro)
 }
 
 /* g_test_skip() is only available since glib 2.38. Add a compatibility wrapper. */
-inline static void
+static inline void
 __nmtst_g_test_skip (const gchar *msg)
 {
 #if GLIB_CHECK_VERSION (2, 38, 0)
@@ -162,7 +162,7 @@ __nmtst_g_test_skip (const gchar *msg)
 
 
 /* g_test_add_data_func_full() is only available since glib 2.34. Add a compatibility wrapper. */
-inline static void
+static inline void
 __g_test_add_data_func_full (const char     *testpath,
                              gpointer        test_data,
                              GTestDataFunc   test_func,
@@ -278,7 +278,7 @@ _nm_g_ptr_array_insert (GPtrArray *array,
 
 
 #if !GLIB_CHECK_VERSION (2, 40, 0)
-inline static gboolean
+static inline gboolean
 _g_key_file_save_to_file (GKeyFile     *key_file,
                           const gchar  *filename,
                           GError      **error)
diff --git a/shared/nm-utils/nm-macros-internal.h b/shared/nm-utils/nm-macros-internal.h
index 70476b7e..6ca66d29 100644
--- a/shared/nm-utils/nm-macros-internal.h
+++ b/shared/nm-utils/nm-macros-internal.h
@@ -106,12 +106,14 @@ _nm_auto_protect_errno (int *p_saved_errno)
 #define __NM_UTILS_MACRO_REST_HELPER_ONE(first)
 #define __NM_UTILS_MACRO_REST_HELPER_TWOORMORE(first, ...)    , __VA_ARGS__
 #define __NM_UTILS_MACRO_REST_NUM(...) \
-    __NM_UTILS_MACRO_REST_SELECT_20TH(__VA_ARGS__, \
+    __NM_UTILS_MACRO_REST_SELECT_30TH(__VA_ARGS__, \
+                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
+                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
                 TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
                 TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
                 TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
                 TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway)
-#define __NM_UTILS_MACRO_REST_SELECT_20TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, ...) a20
+#define __NM_UTILS_MACRO_REST_SELECT_30TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, ...) a30
 
 /*****************************************************************************/
 
@@ -178,7 +180,7 @@ _nm_auto_protect_errno (int *p_saved_errno)
 
 /**
  * NM_G_ERROR_MSG:
- * @error: (allow none): the #GError instance
+ * @error: (allow-none): the #GError instance
  *
  * All functions must follow the convention that when they
  * return a failure, they must also set the GError to a valid
@@ -233,21 +235,34 @@ NM_G_ERROR_MSG (GError *error)
 #define _NM_IN_SET_EVAL_16(op, _x, y, ...)      (_x == (y)) op _NM_IN_SET_EVAL_15 (op, _x, __VA_ARGS__)
 
 #define _NM_IN_SET_EVAL_N2(op, _x, n, ...)      (_NM_IN_SET_EVAL_##n(op, _x, __VA_ARGS__))
-#define _NM_IN_SET_EVAL_N(op, x, n, ...)                            \
+#define _NM_IN_SET_EVAL_N(op, type, x, n, ...)                      \
     ({                                                              \
-        typeof(x) _x = (x);                                         \
+        type _x = (x);                                              \
+                                                                    \
+        /* trigger a -Wenum-compare warning */                      \
+        nm_assert (TRUE || _x == (x));                              \
+                                                                    \
         !!_NM_IN_SET_EVAL_N2(op, _x, n, __VA_ARGS__);               \
     })
 
+#define _NM_IN_SET(op, type, x, ...)        _NM_IN_SET_EVAL_N(op, type, x, NM_NARG (__VA_ARGS__), __VA_ARGS__)
+
 /* Beware that this does short-circuit evaluation (use "||" instead of "|")
  * which has a possibly unexpected non-function-like behavior.
  * Use NM_IN_SET_SE if you need all arguments to be evaluted. */
-#define NM_IN_SET(x, ...)               _NM_IN_SET_EVAL_N(||, x, NM_NARG (__VA_ARGS__), __VA_ARGS__)
+#define NM_IN_SET(x, ...)                   _NM_IN_SET(||, typeof (x), x, __VA_ARGS__)
 
 /* "SE" stands for "side-effect". Contrary to NM_IN_SET(), this does not do
  * short-circuit evaluation, which can make a difference if the arguments have
  * side-effects. */
-#define NM_IN_SET_SE(x, ...)            _NM_IN_SET_EVAL_N(|, x, NM_NARG (__VA_ARGS__), __VA_ARGS__)
+#define NM_IN_SET_SE(x, ...)                _NM_IN_SET(|,  typeof (x), x, __VA_ARGS__)
+
+/* the *_TYPED forms allow to explicitly select the type of "x". This is useful
+ * if "x" doesn't support typeof (bitfields) or you want to gracefully convert
+ * a type using automatic type conversion rules (but not forcing the conversion
+ * with a cast). */
+#define NM_IN_SET_TYPED(type, x, ...)       _NM_IN_SET(||, type,       x, __VA_ARGS__)
+#define NM_IN_SET_SE_TYPED(type, x, ...)    _NM_IN_SET(|,  type,       x, __VA_ARGS__)
 
 /*****************************************************************************/
 
@@ -278,8 +293,8 @@ _NM_IN_STRSET_streq (const char *x, const char *s)
 #define _NM_IN_STRSET_EVAL_N(op, x, n, ...)                       \
     ({                                                            \
         const char *_x = (x);                                     \
-        (   ((_x == NULL) && _NM_IN_SET_EVAL_N2    (op, (const char *) NULL, n, __VA_ARGS__)) \
-         || ((_x != NULL) && _NM_IN_STRSET_EVAL_N2 (op, _x,                  n, __VA_ARGS__)) \
+        (   ((_x == NULL) && _NM_IN_SET_EVAL_N2    (op, ((const char *) NULL), n, __VA_ARGS__)) \
+         || ((_x != NULL) && _NM_IN_STRSET_EVAL_N2 (op, _x,                    n, __VA_ARGS__)) \
         ); \
     })
 
@@ -341,6 +356,40 @@ _NM_IN_STRSET_streq (const char *x, const char *s)
 
 /*****************************************************************************/
 
+/* NM_CACHED_QUARK() returns the GQuark for @string, but caches
+ * it in a static variable to speed up future lookups.
+ *
+ * @string must be a string literal.
+ */
+#define NM_CACHED_QUARK(string) \
+	({ \
+		static GQuark _nm_cached_quark = 0; \
+		\
+		(G_LIKELY (_nm_cached_quark != 0) \
+			? _nm_cached_quark \
+			: (_nm_cached_quark = g_quark_from_static_string (""string""))); \
+	})
+
+/* NM_CACHED_QUARK_FCN() is essentially the same as G_DEFINE_QUARK
+ * with two differences:
+ * - @string must be a quited string-literal
+ * - @fcn must be the full function name, while G_DEFINE_QUARK() appends
+ *   "_quark" to the function name.
+ * Both properties of G_DEFINE_QUARK() are non favorable, because you can no
+ * longer grep for string/fcn -- unless you are aware that you are searching
+ * for G_DEFINE_QUARK() and omit quotes / append _quark(). With NM_CACHED_QUARK_FCN(),
+ * ctags/cscope can locate the use of @fcn (though it doesn't recognize that
+ * NM_CACHED_QUARK_FCN() defines it).
+ */
+#define NM_CACHED_QUARK_FCN(string, fcn) \
+GQuark \
+fcn (void) \
+{ \
+	return NM_CACHED_QUARK (string); \
+}
+
+/*****************************************************************************/
+
 #define nm_streq(s1, s2)  (strcmp (s1, s2) == 0)
 #define nm_streq0(s1, s2) (g_strcmp0 (s1, s2) == 0)
 
@@ -435,6 +484,9 @@ _notify (obj_type *obj, _PropertyEnums prop) \
 
 /*****************************************************************************/
 
+/* these are implemented as a macro, because they accept self
+ * as both (type*) and (const type*), and return a const
+ * private pointer accordingly. */
 #define __NM_GET_PRIVATE(self, type, is_check, result_cmd) \
 	({ \
 		/* preserve the const-ness of self. Unfortunately, that
@@ -442,7 +494,7 @@ _notify (obj_type *obj, _PropertyEnums prop) \
 		typeof (self) _self = (self); \
 		\
 		/* Get compiler error if variable is of wrong type */ \
-		_nm_unused const type *_self2 = (_self); \
+		_nm_unused const type *const _self2 = (_self); \
 		\
 		nm_assert (is_check (_self)); \
 		( result_cmd ); \
@@ -451,6 +503,21 @@ _notify (obj_type *obj, _PropertyEnums prop) \
 #define _NM_GET_PRIVATE(self, type, is_check)     __NM_GET_PRIVATE(self, type, is_check, &_self->_priv)
 #define _NM_GET_PRIVATE_PTR(self, type, is_check) __NM_GET_PRIVATE(self, type, is_check,  _self->_priv)
 
+#define __NM_GET_PRIVATE_VOID(self, type, is_check, result_cmd) \
+	({ \
+		/* (self) can be any non-const pointer. It will be cast to "type *".
+		 * We don't explicitly cast but assign first to (void *) which
+		 * will fail if @self is pointing to const. */ \
+		void *const _self1 = (self); \
+		type *const _self = _self1; \
+		\
+		nm_assert (is_check (_self)); \
+		( result_cmd ); \
+	})
+
+#define _NM_GET_PRIVATE_VOID(self, type, is_check)     __NM_GET_PRIVATE_VOID(self, type, is_check, &_self->_priv)
+#define _NM_GET_PRIVATE_PTR_VOID(self, type, is_check) __NM_GET_PRIVATE_VOID(self, type, is_check,  _self->_priv)
+
 /*****************************************************************************/
 
 static inline gpointer
@@ -543,16 +610,16 @@ nm_clear_g_cancellable (GCancellable **cancellable)
 
 /* Determine whether @x is a power of two (@x being an integer type).
  * For the special cases @x equals zero or one, it also returns true.
- * For negative @x, always returns FALSE. That only applies, if the data
- * type of @x is signed. */
+ * In case @x being a signed type, for negative @x always return FALSE. */
 #define nm_utils_is_power_of_two(x) ({ \
 		typeof(x) __x = (x); \
 		\
 		/* Check if the value is negative. In that case, return FALSE.
 		 * The first expression is a compile time constant, depending on whether
 		 * the type is signed. The second expression is a clumsy way for (__x >= 0),
-		 * which causes a compiler warning for unsigned types. */ \
-		    ( ( ((typeof(__x)) -1) > ((typeof(__x)) 0) ) || (__x > 0) || (__x == 0) ) \
+		 * which otherwise causes a compiler warning for unsigned types. */ \
+		    (    (((typeof(__x)) -1) > ((typeof(__x)) 0)) \
+		      || (__x > 0 || __x == 0) ) \
 		 && ((__x & (__x - 1)) == 0); \
 	})
 
@@ -561,7 +628,7 @@ nm_clear_g_cancellable (GCancellable **cancellable)
 /* check if @flags has exactly one flag (@check) set. You should call this
  * only with @check being a compile time constant and a power of two. */
 #define NM_FLAGS_HAS(flags, check)  \
-    ( (G_STATIC_ASSERT_EXPR ( ((check) != 0) && ((check) & ((check)-1)) == 0 )), (NM_FLAGS_ANY ((flags), (check))) )
+    ( G_STATIC_ASSERT_EXPR ((check) > 0 && ((check) & ((check) - 1)) == 0), NM_FLAGS_ANY ((flags), (check)) )
 
 #define NM_FLAGS_ANY(flags, check)  ( ( ((flags) & (check)) != 0       ) ? TRUE : FALSE )
 #define NM_FLAGS_ALL(flags, check)  ( ( ((flags) & (check)) == (check) ) ? TRUE : FALSE )
@@ -714,13 +781,15 @@ nm_cmp_uint32_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_d
 /*****************************************************************************/
 
 static inline guint
-nm_encode_version (guint major, guint minor, guint micro) {
+nm_encode_version (guint major, guint minor, guint micro)
+{
 	/* analog to the preprocessor macro NM_ENCODE_VERSION(). */
 	return (major << 16) | (minor << 8) | micro;
 }
 
 static inline void
-nm_decode_version (guint version, guint *major, guint *minor, guint *micro) {
+nm_decode_version (guint version, guint *major, guint *minor, guint *micro)
+{
 	*major = (version & 0xFFFF0000u) >> 16;
 	*minor = (version & 0x0000FF00u) >>  8;
 	*micro = (version & 0x000000FFu);
@@ -743,35 +812,33 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro) {
 /*****************************************************************************/
 
 /* if @str is NULL, return "(null)". Otherwise, allocate a buffer using
- * alloca() of size @bufsize and fill it with @str. @str will be quoted with
- * single quote, and in case @str is too long, the final quote will be '^'. */
-#define nm_strquote_a(bufsize, str) \
+ * alloca() of and fill it with @str. @str will be quoted with double quote.
+ * If @str is longer then @trunc_at, the string is truncated and the closing
+ * quote is instead '^' to indicate truncation.
+ *
+ * Thus, the maximum stack allocated buffer will be @trunc_at+3. */
+#define nm_strquote_a(trunc_at, str) \
 	({ \
-		G_STATIC_ASSERT ((bufsize) >= 6); \
-		const gsize _BUFSIZE = (bufsize); \
-		const char *const _s = (str); \
-		char *_r; \
-		gsize _l; \
-		gboolean _truncated; \
-		\
-		nm_assert (_BUFSIZE >= 6); \
+		const char *const _str = (str); \
 		\
-		if (_s) { \
-			_l = strlen (_s) + 3; \
-			if ((_truncated = (_BUFSIZE < _l))) \
-				_l = _BUFSIZE; \
-			\
-			_r = g_alloca (_l); \
-			_r[0] = '\''; \
-			memcpy (&_r[1], _s, _l - 3); \
-			_r[_l - 2] = _truncated ? '^' : '\''; \
-			_r[_l - 1] = '\0'; \
-		} else \
-			_r = "(null)"; \
-		_r; \
+		(_str \
+			? ({ \
+				const gsize _trunc_at = (trunc_at); \
+				const gsize _strlen_trunc = NM_MIN (strlen (_str), _trunc_at); \
+				char *_buf; \
+				\
+				_buf = g_alloca (_strlen_trunc + 3); \
+				_buf[0] = '"'; \
+				memcpy (&_buf[1], _str, _strlen_trunc); \
+				_buf[_strlen_trunc + 1] = _str[_strlen_trunc] ? '^' : '"'; \
+				_buf[_strlen_trunc + 2] = '\0'; \
+				_buf; \
+			}) \
+			: "(null)"); \
 	})
 
-#define nm_sprintf_buf(buf, format, ...) ({ \
+#define nm_sprintf_buf(buf, format, ...) \
+	({ \
 		char * _buf = (buf); \
 		int _buf_len; \
 		\
@@ -788,12 +855,12 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro) {
 	({ \
 		char *_buf; \
 		int _buf_len; \
+		typeof (n_elements) _n_elements = (n_elements); \
 		\
-		G_STATIC_ASSERT (sizeof (char[MAX ((n_elements), 1)]) == (n_elements)); \
-		_buf = g_alloca (n_elements); \
-		_buf_len = g_snprintf (_buf, (n_elements), \
+		_buf = g_alloca (_n_elements); \
+		_buf_len = g_snprintf (_buf, _n_elements, \
 		                       ""format"", ##__VA_ARGS__); \
-		nm_assert (_buf_len < (n_elements)); \
+		nm_assert (_buf_len < _n_elements); \
 		_buf; \
 	})
 
@@ -870,7 +937,6 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro) {
 #define _G_BOOLEAN_EXPR(expr) __NM_G_BOOLEAN_EXPR_IMPL (NM_UNIQ, expr)
 #endif
 
-
 /*****************************************************************************/
 
 #endif /* __NM_MACROS_INTERNAL_H__ */
diff --git a/shared/nm-utils/nm-shared-utils.c b/shared/nm-utils/nm-shared-utils.c
index 38bb8181..413526dc 100644
--- a/shared/nm-utils/nm-shared-utils.c
+++ b/shared/nm-utils/nm-shared-utils.c
@@ -158,6 +158,54 @@ _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 ma
 
 /*****************************************************************************/
 
+/**
+ * nm_utils_strv_find_first:
+ * @list: the strv list to search
+ * @len: the length of the list, or a negative value if @list is %NULL terminated.
+ * @needle: the value to search for. The search is done using strcmp().
+ *
+ * Searches @list for @needle and returns the index of the first match (based
+ * on strcmp()).
+ *
+ * For convenience, @list has type 'char**' instead of 'const char **'.
+ *
+ * Returns: index of first occurrence or -1 if @needle is not found in @list.
+ */
+gssize
+nm_utils_strv_find_first (char **list, gssize len, const char *needle)
+{
+	gssize i;
+
+	if (len > 0) {
+		g_return_val_if_fail (list, -1);
+
+		if (!needle) {
+			/* if we search a list with known length, %NULL is a valid @needle. */
+			for (i = 0; i < len; i++) {
+				if (!list[i])
+					return i;
+			}
+		} else {
+			for (i = 0; i < len; i++) {
+				if (list[i] && !strcmp (needle, list[i]))
+					return i;
+			}
+		}
+	} else if (len < 0) {
+		g_return_val_if_fail (needle, -1);
+
+		if (list) {
+			for (i = 0; list[i]; i++) {
+				if (strcmp (needle, list[i]) == 0)
+					return i;
+			}
+		}
+	}
+	return -1;
+}
+
+/*****************************************************************************/
+
 gint
 _nm_utils_ascii_str_to_bool (const char *str,
                              gint default_value)
@@ -192,7 +240,7 @@ _nm_utils_ascii_str_to_bool (const char *str,
 
 /*****************************************************************************/
 
-G_DEFINE_QUARK (nm-utils-error-quark, nm_utils_error)
+NM_CACHED_QUARK_FCN ("nm-utils-error-quark", nm_utils_error_quark)
 
 void
 nm_utils_error_set_cancelled (GError **error,
diff --git a/shared/nm-utils/nm-shared-utils.h b/shared/nm-utils/nm-shared-utils.h
index 5d8a3a86..f1f9f518 100644
--- a/shared/nm-utils/nm-shared-utils.h
+++ b/shared/nm-utils/nm-shared-utils.h
@@ -43,6 +43,10 @@ void nm_utils_strbuf_append_str (char **buf, gsize *len, const char *str);
 
 /*****************************************************************************/
 
+gssize nm_utils_strv_find_first (char **list, gssize len, const char *needle);
+
+/*****************************************************************************/
+
 gint64 _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback);
 
 gint _nm_utils_ascii_str_to_bool (const char *str,
diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h
index 7be7e254..0dfdfce6 100644
--- a/shared/nm-utils/nm-test-utils.h
+++ b/shared/nm-utils/nm-test-utils.h
@@ -198,7 +198,7 @@ _nmtst_exit (void) \
 }
 
 
-inline static gboolean
+static inline gboolean
 nmtst_initialized (void)
 {
 	return !!__nmtst_internal.rand0;
@@ -219,7 +219,7 @@ nmtst_initialized (void)
  *
  * The caller must g_free() the returned argv array.
  **/
-inline static char **
+static inline char **
 nmtst_str_split (char *str, const char *delimiters)
 {
 	const char *d;
@@ -265,7 +265,7 @@ BREAK_INNER_LOOPS:
 
 /* free instances allocated by nmtst (especially nmtst_init()) on shutdown
  * to release memory. After nmtst_free(), the test is uninitialized again. */
-inline static void
+static inline void
 nmtst_free (void)
 {
 	if (!nmtst_initialized ())
@@ -280,7 +280,7 @@ nmtst_free (void)
 	memset (&__nmtst_internal, 0, sizeof (__nmtst_internal));
 }
 
-inline static void
+static inline void
 __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_level, const char *log_domains, gboolean *out_set_logging)
 {
 	const char *nmtst_debug;
@@ -592,21 +592,21 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_
 }
 
 #ifndef _NMTST_INSIDE_CORE
-inline static void
+static inline void
 nmtst_init (int *argc, char ***argv, gboolean assert_logging)
 {
 	__nmtst_init (argc, argv, assert_logging, NULL, NULL, NULL);
 }
 #endif
 
-inline static gboolean
+static inline gboolean
 nmtst_is_debug (void)
 {
 	g_assert (nmtst_initialized ());
 	return __nmtst_internal.is_debug;
 }
 
-inline static gboolean
+static inline gboolean
 nmtst_test_quick (void)
 {
 	g_assert (nmtst_initialized ());
@@ -656,7 +656,7 @@ struct _NmtstTestData {
 	gpointer args[1];
 };
 
-inline static void
+static inline void
 _nmtst_test_data_unpack (const NmtstTestData *test_data, gsize n_args, ...)
 {
 	gsize i;
@@ -677,7 +677,7 @@ _nmtst_test_data_unpack (const NmtstTestData *test_data, gsize n_args, ...)
 }
 #define nmtst_test_data_unpack(test_data, ...) _nmtst_test_data_unpack(test_data, NM_NARG (__VA_ARGS__), ##__VA_ARGS__)
 
-inline static void
+static inline void
 _nmtst_test_data_free (gpointer data)
 {
 	NmtstTestData *test_data = data;
@@ -691,7 +691,7 @@ _nmtst_test_data_free (gpointer data)
 	g_free (test_data);
 }
 
-inline static void
+static inline void
 _nmtst_add_test_func_full (const char *testpath, GTestDataFunc test_func, NmtstTestDataRelease fcn_release, gsize n_args, ...)
 {
 	gsize i;
@@ -719,14 +719,14 @@ _nmtst_add_test_func_full (const char *testpath, GTestDataFunc test_func, NmtstT
 
 /*****************************************************************************/
 
-inline static GRand *
+static inline GRand *
 nmtst_get_rand0 (void)
 {
 	g_assert (nmtst_initialized ());
 	return __nmtst_internal.rand0;
 }
 
-inline static GRand *
+static inline GRand *
 nmtst_get_rand (void)
 {
 	g_assert (nmtst_initialized ());
@@ -757,13 +757,13 @@ nmtst_get_rand (void)
 	return __nmtst_internal.rand;
 }
 
-inline static guint32
+static inline guint32
 nmtst_get_rand_int (void)
 {
 	return g_rand_int (nmtst_get_rand ());
 }
 
-inline static gpointer
+static inline gpointer
 nmtst_rand_buf (GRand *rand, gpointer buffer, gsize buffer_length)
 {
 	guint32 v;
@@ -791,7 +791,7 @@ nmtst_rand_buf (GRand *rand, gpointer buffer, gsize buffer_length)
 	return buffer;
 }
 
-inline static void *
+static inline void *
 nmtst_rand_perm (GRand *rand, void *dst, const void *src, gsize elmt_size, gsize n_elmt)
 {
 	gsize i, j;
@@ -832,7 +832,7 @@ nmtst_rand_perm (GRand *rand, void *dst, const void *src, gsize elmt_size, gsize
 	return dst;
 }
 
-inline static GSList *
+static inline GSList *
 nmtst_rand_perm_gslist (GRand *rand, GSList *list)
 {
 	GSList *result;
@@ -859,7 +859,7 @@ nmtst_rand_perm_gslist (GRand *rand, GSList *list)
 
 /*****************************************************************************/
 
-inline static gboolean
+static inline gboolean
 _nmtst_main_loop_run_timeout (gpointer user_data)
 {
 	GMainLoop **p_loop = user_data;
@@ -873,7 +873,7 @@ _nmtst_main_loop_run_timeout (gpointer user_data)
 	return G_SOURCE_REMOVE;
 }
 
-inline static gboolean
+static inline gboolean
 nmtst_main_loop_run (GMainLoop *loop, int timeout_ms)
 {
 	GSource *source = NULL;
@@ -894,7 +894,7 @@ nmtst_main_loop_run (GMainLoop *loop, int timeout_ms)
 	return loopx != NULL;
 }
 
-inline static void
+static inline void
 _nmtst_main_loop_quit_on_notify (GObject *object, GParamSpec *pspec, gpointer user_data)
 {
 	GMainLoop *loop = user_data;
@@ -908,14 +908,14 @@ _nmtst_main_loop_quit_on_notify (GObject *object, GParamSpec *pspec, gpointer us
 
 /*****************************************************************************/
 
-inline static const char *
+static inline const char *
 nmtst_get_sudo_cmd (void)
 {
 	g_assert (nmtst_initialized ());
 	return __nmtst_internal.sudo_cmd;
 }
 
-inline static void
+static inline void
 nmtst_reexec_sudo (void)
 {
 	char *str;
@@ -945,7 +945,7 @@ nmtst_reexec_sudo (void)
 
 /*****************************************************************************/
 
-inline static gsize
+static inline gsize
 nmtst_find_all_indexes (gpointer *elements,
                         gsize n_elements,
                         gpointer *needles,
@@ -987,7 +987,7 @@ next:
 /*****************************************************************************/
 
 #define __define_nmtst_static(NUM,SIZE) \
-inline static const char * \
+static inline const char * \
 nmtst_static_##SIZE##_##NUM (const char *str) \
 { \
 	gsize l; \
@@ -1008,7 +1008,7 @@ __define_nmtst_static(03, 1024)
 	gs_free char *_nmtst_hidden_##uuid = nm_utils_uuid_generate (); \
 	const char *const uuid = _nmtst_hidden_##uuid
 
-inline static const char *
+static inline const char *
 nmtst_uuid_generate (void)
 {
 	static char u[37];
@@ -1039,7 +1039,7 @@ nmtst_uuid_generate (void)
 			g_error ("%s:%d: Expects \"%s\" but got \"%s\"", __FILE__, __LINE__, __substr, __str); \
 	} G_STMT_END
 
-inline static guint32
+static inline guint32
 nmtst_inet4_from_string (const char *str)
 {
 	guint32 addr;
@@ -1055,7 +1055,7 @@ nmtst_inet4_from_string (const char *str)
 	return addr;
 }
 
-inline static const struct in6_addr *
+static inline const struct in6_addr *
 nmtst_inet6_from_string (const char *str)
 {
 	static struct in6_addr addr;
@@ -1071,7 +1071,7 @@ nmtst_inet6_from_string (const char *str)
 	return &addr;
 }
 
-inline static void
+static inline void
 _nmtst_assert_ip4_address (const char *file, int line, in_addr_t addr, const char *str_expected)
 {
 	if (nmtst_inet4_from_string (str_expected) != addr) {
@@ -1084,7 +1084,7 @@ _nmtst_assert_ip4_address (const char *file, int line, in_addr_t addr, const cha
 }
 #define nmtst_assert_ip4_address(addr, str_expected) _nmtst_assert_ip4_address (__FILE__, __LINE__, addr, str_expected)
 
-inline static void
+static inline void
 _nmtst_assert_ip6_address (const char *file, int line, const struct in6_addr *addr, const char *str_expected)
 {
 	struct in6_addr any = in6addr_any;
@@ -1104,9 +1104,9 @@ _nmtst_assert_ip6_address (const char *file, int line, const struct in6_addr *ad
 
 #define nmtst_spawn_sync(working_directory, standard_out, standard_err, assert_exit_status, ...) \
 	__nmtst_spawn_sync (working_directory, standard_out, standard_err, assert_exit_status, ##__VA_ARGS__, NULL)
-inline static gint
+static inline gint
 __nmtst_spawn_sync (const char *working_directory, char **standard_out, char **standard_err, int assert_exit_status, ...) G_GNUC_NULL_TERMINATED;
-inline static gint
+static inline gint
 __nmtst_spawn_sync (const char *working_directory, char **standard_out, char **standard_err, int assert_exit_status, ...)
 {
 	gint exit_status = 0;
@@ -1154,7 +1154,7 @@ __nmtst_spawn_sync (const char *working_directory, char **standard_out, char **s
 
 /*****************************************************************************/
 
-inline static char *
+static inline char *
 nmtst_file_resolve_relative_path (const char *rel, const char *cwd)
 {
 	gs_free char *cwd_free = NULL;
@@ -1169,7 +1169,7 @@ nmtst_file_resolve_relative_path (const char *rel, const char *cwd)
 	return g_build_filename (cwd, rel, NULL);
 }
 
-inline static char *
+static inline char *
 nmtst_file_get_contents (const char *filename)
 {
 	GError *error = NULL;
@@ -1185,7 +1185,7 @@ nmtst_file_get_contents (const char *filename)
 
 /*****************************************************************************/
 
-inline static void
+static inline void
 nmtst_file_unlink_if_exists (const char *name)
 {
 	int errsv;
@@ -1199,7 +1199,7 @@ nmtst_file_unlink_if_exists (const char *name)
 	}
 }
 
-inline static void
+static inline void
 nmtst_file_unlink (const char *name)
 {
 	int errsv;
@@ -1212,7 +1212,7 @@ nmtst_file_unlink (const char *name)
 	}
 }
 
-inline static void
+static inline void
 _nmtst_auto_unlinkfile (char **p_name)
 {
 	if (*p_name) {
@@ -1225,7 +1225,7 @@ _nmtst_auto_unlinkfile (char **p_name)
 
 /*****************************************************************************/
 
-inline static void
+static inline void
 _nmtst_assert_resolve_relative_path_equals (const char *f1, const char *f2, const char *file, int line)
 {
 	gs_free char *p1 = NULL, *p2 = NULL;
@@ -1244,7 +1244,7 @@ _nmtst_assert_resolve_relative_path_equals (const char *f1, const char *f2, cons
 /*****************************************************************************/
 
 #ifdef NM_SETTING_IP_CONFIG_H
-inline static void
+static inline void
 nmtst_setting_ip_config_add_address (NMSettingIPConfig *s_ip,
                                      const char *address,
                                      guint prefix)
@@ -1267,7 +1267,7 @@ nmtst_setting_ip_config_add_address (NMSettingIPConfig *s_ip,
 	nm_ip_address_unref (addr);
 }
 
-inline static void
+static inline void
 nmtst_setting_ip_config_add_route (NMSettingIPConfig *s_ip,
                                    const char *dest,
                                    guint prefix,
@@ -1291,11 +1291,55 @@ nmtst_setting_ip_config_add_route (NMSettingIPConfig *s_ip,
 	g_assert (nm_setting_ip_config_add_route (s_ip, route));
 	nm_ip_route_unref (route);
 }
+
+static inline void
+nmtst_assert_route_attribute_string (NMIPRoute *route, const char *name, const char *value)
+{
+	GVariant *variant;
+
+	variant = nm_ip_route_get_attribute (route, name);
+	g_assert (variant);
+	g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING));
+	g_assert_cmpstr (g_variant_get_string (variant, NULL), ==, value);
+}
+
+static inline void
+nmtst_assert_route_attribute_byte (NMIPRoute *route, const char *name, guchar value)
+{
+	GVariant *variant;
+
+	variant = nm_ip_route_get_attribute (route, name);
+	g_assert (variant);
+	g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_BYTE));
+	g_assert_cmpint (g_variant_get_byte (variant), ==, value);
+}
+
+static inline void
+nmtst_assert_route_attribute_uint32 (NMIPRoute *route, const char *name, guint32 value)
+{
+	GVariant *variant;
+
+	variant = nm_ip_route_get_attribute (route, name);
+	g_assert (variant);
+	g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT32));
+	g_assert_cmpint (g_variant_get_uint32 (variant), ==, value);
+}
+
+static inline void
+nmtst_assert_route_attribute_boolean (NMIPRoute *route, const char *name, gboolean value)
+{
+	GVariant *variant;
+
+	variant = nm_ip_route_get_attribute (route, name);
+	g_assert (variant);
+	g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_BOOLEAN));
+	g_assert_cmpint (g_variant_get_boolean (variant), ==, value);
+}
 #endif /* NM_SETTING_IP_CONFIG_H */
 
 #if (defined(__NM_SIMPLE_CONNECTION_H__) && defined(__NM_SETTING_CONNECTION_H__)) || (defined(NM_CONNECTION_H))
 
-inline static NMConnection *
+static inline NMConnection *
 nmtst_clone_connection (NMConnection *connection)
 {
 	g_assert (NM_IS_CONNECTION (connection));
@@ -1307,7 +1351,7 @@ nmtst_clone_connection (NMConnection *connection)
 #endif
 }
 
-inline static NMConnection *
+static inline NMConnection *
 nmtst_create_minimal_connection (const char *id, const char *uuid, const char *type, NMSettingConnection **out_s_con)
 {
 	NMConnection *con;
@@ -1360,7 +1404,7 @@ nmtst_create_minimal_connection (const char *id, const char *uuid, const char *t
 	return con;
 }
 
-inline static gboolean
+static inline gboolean
 _nmtst_connection_normalize_v (NMConnection *connection, va_list args)
 {
 	GError *error = NULL;
@@ -1390,7 +1434,7 @@ _nmtst_connection_normalize_v (NMConnection *connection, va_list args)
 	return was_modified;
 }
 
-inline static gboolean
+static inline gboolean
 _nmtst_connection_normalize (NMConnection *connection, ...)
 {
 	gboolean was_modified;
@@ -1405,7 +1449,7 @@ _nmtst_connection_normalize (NMConnection *connection, ...)
 #define nmtst_connection_normalize(connection, ...) \
     _nmtst_connection_normalize(connection, ##__VA_ARGS__, NULL)
 
-inline static NMConnection *
+static inline NMConnection *
 _nmtst_connection_duplicate_and_normalize (NMConnection *connection, ...)
 {
 	gboolean was_modified;
@@ -1422,7 +1466,7 @@ _nmtst_connection_duplicate_and_normalize (NMConnection *connection, ...)
 #define nmtst_connection_duplicate_and_normalize(connection, ...) \
     _nmtst_connection_duplicate_and_normalize(connection, ##__VA_ARGS__, NULL)
 
-inline static void
+static inline void
 nmtst_assert_connection_equals (NMConnection *a, gboolean normalize_a, NMConnection *b, gboolean normalize_b)
 {
 	gboolean compare;
@@ -1481,7 +1525,7 @@ nmtst_assert_connection_equals (NMConnection *a, gboolean normalize_a, NMConnect
 	g_assert (compare);
 }
 
-inline static void
+static inline void
 nmtst_assert_connection_verifies (NMConnection *con)
 {
 	/* assert that the connection does verify, it might be normaliziable or not */
@@ -1495,7 +1539,7 @@ nmtst_assert_connection_verifies (NMConnection *con)
 	g_assert (success);
 }
 
-inline static void
+static inline void
 nmtst_assert_connection_verifies_without_normalization (NMConnection *con)
 {
 	/* assert that the connection verifies and does not need any normalization */
@@ -1515,7 +1559,7 @@ nmtst_assert_connection_verifies_without_normalization (NMConnection *con)
 	g_assert (!was_modified);
 }
 
-inline static void
+static inline void
 nmtst_assert_connection_verifies_and_normalizable (NMConnection *con)
 {
 	/* assert that the connection does verify, but normalization still modifies it */
@@ -1537,7 +1581,7 @@ nmtst_assert_connection_verifies_and_normalizable (NMConnection *con)
 	nmtst_assert_connection_verifies_without_normalization (clone);
 }
 
-inline static void
+static inline void
 nmtst_assert_connection_verifies_after_normalization (NMConnection *con,
                                                       GQuark expect_error_domain,
                                                       gint expect_error_code)
@@ -1564,7 +1608,7 @@ nmtst_assert_connection_verifies_after_normalization (NMConnection *con,
 	nmtst_assert_connection_verifies_without_normalization (clone);
 }
 
-inline static void
+static inline void
 nmtst_assert_connection_unnormalizable (NMConnection *con,
                                         GQuark expect_error_domain,
                                         gint expect_error_code)
@@ -1591,7 +1635,7 @@ nmtst_assert_connection_unnormalizable (NMConnection *con,
 	g_clear_error (&error);
 }
 
-inline static void
+static inline void
 nmtst_assert_setting_verifies (NMSetting *setting)
 {
 	/* assert that the setting verifies without an error */
@@ -1606,7 +1650,7 @@ nmtst_assert_setting_verifies (NMSetting *setting)
 	g_assert (success);
 }
 
-inline static void
+static inline void
 nmtst_assert_setting_verify_fails (NMSetting *setting,
                                    GQuark expect_error_domain,
                                    gint expect_error_code)
@@ -1661,9 +1705,10 @@ nmtst_assert_hwaddr_equals (gconstpointer hwaddr1, gssize hwaddr1_len, const cha
     nmtst_assert_hwaddr_equals (hwaddr1, hwaddr1_len, expected, __FILE__, __LINE__)
 #endif
 
+
 #if defined(__NM_SIMPLE_CONNECTION_H__) && defined(__NM_SETTING_CONNECTION_H__) && defined(__NM_KEYFILE_INTERNAL_H__)
 
-inline static NMConnection *
+static inline NMConnection *
 nmtst_create_connection_from_keyfile (const char *keyfile_str, const char *keyfile_name, const char *base_dir)
 {
 	GKeyFile *keyfile;
@@ -1693,7 +1738,7 @@ nmtst_create_connection_from_keyfile (const char *keyfile_str, const char *keyfi
 
 #ifdef __NM_CONNECTION_H__
 
-inline static GVariant *
+static inline GVariant *
 _nmtst_variant_new_vardict (int dummy, ...)
 {
 	GVariantBuilder builder;
diff --git a/shared/nm-utils/nm-udev-utils.c b/shared/nm-utils/nm-udev-utils.c
new file mode 100644
index 00000000..5552d592
--- /dev/null
+++ b/shared/nm-utils/nm-udev-utils.c
@@ -0,0 +1,292 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-udev-utils.c - udev utils functions
+ *
+ * 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
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-udev-utils.h"
+
+#include <libudev.h>
+
+struct _NMPUdevClient {
+	char **subsystems;
+	GSource *watch_source;
+	struct udev *udev;
+	struct udev_monitor *monitor;
+	NMUdevClientEvent event_handler;
+	gpointer event_user_data;
+};
+
+/*****************************************************************************/
+
+gboolean
+nm_udev_utils_property_as_boolean (const char *uproperty)
+{
+	/* taken from g_udev_device_get_property_as_boolean() */
+
+	if (uproperty) {
+		if (   strcmp (uproperty, "1") == 0
+		    || g_ascii_strcasecmp (uproperty, "true") == 0)
+			return TRUE;
+	}
+	return FALSE;
+}
+
+const char *
+nm_udev_utils_property_decode (const char *uproperty, char **to_free)
+{
+	const char *p;
+	char *unescaped = NULL;
+	char *n = NULL;
+
+	if (!uproperty) {
+		*to_free = NULL;
+		return NULL;
+	}
+
+	p = uproperty;
+	while (*p) {
+		int a, b;
+
+		if (   p[0] == '\\'
+		    && p[1] == 'x'
+		    && (a = g_ascii_xdigit_value (p[2])) >= 0
+		    && (b = g_ascii_xdigit_value (p[3])) >= 0) {
+			if (!unescaped) {
+				gssize l = p - uproperty;
+
+				unescaped = g_malloc (l + strlen (p) + 1 - 3);
+				memcpy (unescaped, uproperty, l);
+				n = &unescaped[l];
+			}
+			*n++ = (a << 4) | b;
+			p += 4;
+		} else {
+			if (n)
+				*n++ = *p;
+			p++;
+		}
+	}
+
+	if (!unescaped) {
+		*to_free = NULL;
+		return uproperty;
+	}
+
+	return (*to_free = unescaped);
+}
+
+char *
+nm_udev_utils_property_decode_cp (const char *uproperty)
+{
+	char *cpy;
+
+	uproperty = nm_udev_utils_property_decode (uproperty, &cpy);
+	return cpy ?: g_strdup (uproperty);
+}
+
+/*****************************************************************************/
+
+static void
+_subsystem_split (const char *subsystem_full,
+                  const char **out_subsystem,
+                  const char **out_devtype,
+                  char **to_free)
+{
+	char *tmp, *s;
+
+	nm_assert (subsystem_full);
+	nm_assert (out_subsystem);
+	nm_assert (out_devtype);
+	nm_assert (to_free);
+
+	s = strstr (subsystem_full, "/");
+	if (s) {
+		tmp = g_strdup (subsystem_full);
+		s = &tmp[s - subsystem_full];
+		*s = '\0';
+		*out_subsystem = tmp;
+		*out_devtype = &s[1];
+		*to_free = tmp;
+	} else {
+		*out_subsystem = subsystem_full;
+		*out_devtype = NULL;
+		*to_free = NULL;
+	}
+}
+
+static struct udev_enumerate *
+nm_udev_utils_enumerate (struct udev *uclient,
+                         const char *const*subsystems)
+{
+	struct udev_enumerate *enumerate;
+	guint n;
+
+	enumerate = udev_enumerate_new (uclient);
+
+	if (subsystems) {
+		for (n = 0; subsystems[n]; n++) {
+			const char *subsystem;
+			const char *devtype;
+			gs_free char *to_free;
+
+			_subsystem_split (subsystems[n], &subsystem, &devtype, &to_free);
+
+			udev_enumerate_add_match_subsystem (enumerate, subsystem);
+
+			if (devtype != NULL)
+				udev_enumerate_add_match_property (enumerate, "DEVTYPE", devtype);
+		}
+	}
+
+	return enumerate;
+}
+
+struct udev *
+nm_udev_client_get_udev (NMUdevClient *self)
+{
+	g_return_val_if_fail (self, NULL);
+
+	return self->udev;
+}
+
+struct udev_enumerate *
+nm_udev_client_enumerate_new (NMUdevClient *self)
+{
+	g_return_val_if_fail (self, NULL);
+
+	return nm_udev_utils_enumerate (self->udev, (const char *const*) self->subsystems);
+}
+
+/*****************************************************************************/
+
+static gboolean
+monitor_event (GIOChannel *source,
+               GIOCondition condition,
+               gpointer user_data)
+{
+	NMUdevClient *self = user_data;
+	struct udev_device *udevice;
+
+	if (!self->monitor)
+		goto out;
+
+	udevice = udev_monitor_receive_device (self->monitor);
+	if (udevice == NULL)
+		goto out;
+
+	self->event_handler (self,
+	                     udevice,
+	                     self->event_user_data);
+	udev_device_unref (udevice);
+
+out:
+	return TRUE;
+}
+
+/**
+ * nm_udev_client_new:
+ * @subsystems: the subsystems
+ * @event_handler: callback for events
+ * @event_user_data: user-data for @event_handler
+ *
+ * Basically, it is g_udev_client_new(), and most notably
+ * g_udev_client_constructed().
+ *
+ * Returns: a new NMUdevClient instance.
+ */
+NMUdevClient *
+nm_udev_client_new (const char *const*subsystems,
+                    NMUdevClientEvent event_handler,
+                    gpointer event_user_data)
+{
+	NMUdevClient *self;
+	GIOChannel *channel;
+	guint n;
+
+	self = g_slice_new0 (NMUdevClient);
+
+	self->event_handler = event_handler;
+	self->event_user_data = event_user_data;
+	self->subsystems = subsystems && subsystems[0] ? g_strdupv ((char **) subsystems) : NULL;
+
+	self->udev = udev_new ();
+	if (!self->udev)
+		goto fail;
+
+	/* connect to event source */
+	if (self->event_handler) {
+		self->monitor = udev_monitor_new_from_netlink (self->udev, "udev");
+		if (!self->monitor)
+			goto fail;
+
+		if (self->subsystems) {
+			/* install subsystem filters to only wake up for certain events */
+			for (n = 0; self->subsystems[n]; n++) {
+				if (self->monitor) {
+					gs_free char *to_free;
+					const char *subsystem;
+					const char *devtype;
+
+					_subsystem_split (self->subsystems[n], &subsystem, &devtype, &to_free);
+					udev_monitor_filter_add_match_subsystem_devtype (self->monitor, subsystem, devtype);
+				}
+			}
+
+			/* listen to events, and buffer them */
+			if (self->monitor) {
+				udev_monitor_enable_receiving (self->monitor);
+				channel = g_io_channel_unix_new (udev_monitor_get_fd (self->monitor));
+				self->watch_source = g_io_create_watch (channel, G_IO_IN);
+				g_io_channel_unref (channel);
+				g_source_set_callback (self->watch_source, (GSourceFunc) monitor_event, self, NULL);
+				g_source_attach (self->watch_source, g_main_context_get_thread_default ());
+				g_source_unref (self->watch_source);
+			}
+		}
+	}
+
+	return self;
+
+fail:
+	return nm_udev_client_unref (self);
+}
+
+NMUdevClient *
+nm_udev_client_unref (NMUdevClient *self)
+{
+	if (!self)
+		return NULL;
+
+	if (self->watch_source) {
+		g_source_destroy (self->watch_source);
+		self->watch_source = NULL;
+	}
+
+	udev_monitor_unref (self->monitor);
+	self->monitor = NULL;
+	udev_unref (self->udev);
+	self->udev = NULL;
+
+	g_strfreev (self->subsystems);
+
+	g_slice_free (NMUdevClient, self);
+
+	return NULL;
+}
diff --git a/shared/nm-utils/nm-udev-utils.h b/shared/nm-utils/nm-udev-utils.h
new file mode 100644
index 00000000..911e8a27
--- /dev/null
+++ b/shared/nm-utils/nm-udev-utils.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-udev-utils.h - udev utils functions
+ *
+ * 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
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#ifndef __NM_UDEV_UTILS_H__
+#define __NM_UDEV_UTILS_H__
+
+struct udev;
+struct udev_device;
+struct udev_enumerate;
+
+gboolean nm_udev_utils_property_as_boolean (const char *uproperty);
+const char *nm_udev_utils_property_decode    (const char *uproperty, char **to_free);
+char       *nm_udev_utils_property_decode_cp (const char *uproperty);
+
+typedef struct _NMPUdevClient NMUdevClient;
+
+typedef void (*NMUdevClientEvent) (NMUdevClient *udev_client,
+                                   struct udev_device *udevice,
+                                   gpointer event_user_data);
+
+NMUdevClient *nm_udev_client_new (const char *const*subsystems,
+                                  NMUdevClientEvent event_handler,
+                                  gpointer event_user_data);
+
+NMUdevClient *nm_udev_client_unref (NMUdevClient *self);
+
+struct udev *nm_udev_client_get_udev (NMUdevClient *self);
+
+struct udev_enumerate *nm_udev_client_enumerate_new (NMUdevClient *self);
+
+#endif /* __NM_UDEV_UTILS_H__ */