summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2017-01-25 21:09:50 +0100
committerMichael Biebl <biebl@debian.org>2017-01-25 21:09:50 +0100
commit7f4dff3943c64e1d0e5d13b2ac915088dde165e6 (patch)
treeabfbc3a4102d3bba349b3e25265c2213ad636954 /src
parentd462f64d6044349b0a4e3581cf2c97bbab7aec97 (diff)
New upstream version 1.6.0 upstream/1.6.0
Diffstat (limited to 'src')
-rw-r--r--src/devices/nm-device.c70
-rw-r--r--src/nm-active-connection.c17
-rw-r--r--src/nm-core-utils.c65
-rw-r--r--src/nm-exported-object.c47
-rw-r--r--src/nm-exported-object.h2
-rw-r--r--src/nm-manager.c7
-rw-r--r--src/settings/nm-settings.c6
-rw-r--r--src/tests/test-general.c106
8 files changed, 219 insertions, 101 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index b614894e..1fcf965c 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -281,8 +281,10 @@ typedef struct _NMDevicePrivate {
 	guint32         ip4_address;
 
 	NMActRequest *  queued_act_request;
-	bool            queued_act_request_is_waiting_for_carrier;
-	NMActRequest *  act_request;
+	bool            queued_act_request_is_waiting_for_carrier:1;
+	bool            act_request_public:1;
+	NMActRequest   *act_request;
+	gulong          act_request_id;
 	ActivationHandleData act_handle4; /* for layer2 and IPv4. */
 	ActivationHandleData act_handle6;
 	guint           recheck_assume_id;
@@ -4207,6 +4209,7 @@ activate_stage1_device_prepare (NMDevice *self)
 	_set_ip_state (self, AF_INET6, IP_NONE);
 
 	/* Notify the new ActiveConnection along with the state change */
+	priv->act_request_public = TRUE;
 	_notify (self, PROP_ACTIVE_CONNECTION);
 
 	nm_device_state_changed (self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE);
@@ -6669,8 +6672,9 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 		NMSettingIPConfig *s_ip6;
 
 		s_ip6 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
-		if (s_ip6 && nm_streq0 (nm_setting_ip_config_get_method (s_ip6),
-		                        NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
+		if (   s_ip6
+		    && !NM_IN_STRSET (nm_setting_ip_config_get_method (s_ip6),
+		                      NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
 			/* the interface has IPv6 enabled. The MTU with IPv6 cannot be smaller
 			 * then 1280.
 			 *
@@ -8122,20 +8126,46 @@ nm_device_activate_ip6_state_done (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_DONE;
 }
 
+/*****************************************************************************/
+
 static void
-clear_act_request (NMDevice *self)
+act_request_set_cb (NMActRequest *act_request,
+                    GParamSpec *pspec,
+                    NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	_notify (self, PROP_ACTIVE_CONNECTION);
+}
 
-	if (!priv->act_request)
+static void
+act_request_set (NMDevice *self, NMActRequest *act_request)
+{
+	NMDevicePrivate *priv;
+	gs_unref_object NMActRequest *old_act_requst = NULL;
+
+	nm_assert (NM_IS_DEVICE (self));
+	nm_assert (!act_request || NM_IS_ACT_REQUEST (act_request));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (   !priv->act_request_public
+	    && priv->act_request == act_request)
 		return;
 
-	nm_active_connection_set_default (NM_ACTIVE_CONNECTION (priv->act_request), FALSE);
+	/* always clear the public flag. The few callers that set a new @act_request
+	 * don't want that the property is public yet.  */
+	priv->act_request_public = FALSE;
+
+	nm_clear_g_signal_handler (priv->act_request, &priv->act_request_id);
 
-	priv->master_ready_handled = FALSE;
-	nm_clear_g_signal_handler (priv->act_request, &priv->master_ready_id);
+	old_act_requst = priv->act_request;
+	priv->act_request = nm_g_object_ref (act_request);
 
-	g_clear_object (&priv->act_request);
+	if (act_request) {
+		priv->act_request_id = g_signal_connect (act_request,
+		                                         "notify::"NM_EXPORTED_OBJECT_PATH,
+		                                         G_CALLBACK (act_request_set_cb),
+		                                         self);
+	}
 	_notify (self, PROP_ACTIVE_CONNECTION);
 }
 
@@ -8922,10 +8952,7 @@ _device_activate (NMDevice *self, NMActRequest *req)
 
 	delete_on_deactivate_unschedule (self);
 
-	/* note: don't notify D-Bus of the new AC here, but do it later when
-	 * changing state to PREPARE so that the two properties change together.
-	 */
-	priv->act_request = g_object_ref (req);
+	act_request_set (self, req);
 
 	nm_device_activate_schedule_stage1_device_prepare (self);
 	return TRUE;
@@ -11409,7 +11436,14 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 
 	priv->needs_ip6_subnet = FALSE;
 
-	clear_act_request (self);
+	if (priv->act_request) {
+		nm_active_connection_set_default (NM_ACTIVE_CONNECTION (priv->act_request), FALSE);
+
+		priv->master_ready_handled = FALSE;
+		nm_clear_g_signal_handler (priv->act_request, &priv->master_ready_id);
+
+		act_request_set (self, NULL);
+	}
 
 	/* Clear legacy IPv4 address property */
 	if (priv->ip4_address) {
@@ -11863,7 +11897,7 @@ _set_state_full (NMDevice *self,
 		g_cancellable_cancel (priv->deactivating_cancellable);
 
 	/* Cache the activation request for the dispatcher */
-	req = priv->act_request ? g_object_ref (priv->act_request) : NULL;
+	req = nm_g_object_ref (priv->act_request);
 
 	if (state <= NM_DEVICE_STATE_UNAVAILABLE) {
 		if (available_connections_del_all (self))
@@ -13298,7 +13332,7 @@ get_property (GObject *object, guint prop_id,
 		                      g_variant_new ("(uu)", priv->state, priv->state_reason));
 		break;
 	case PROP_ACTIVE_CONNECTION:
-		nm_utils_g_value_set_object_path (value, priv->act_request);
+		nm_utils_g_value_set_object_path (value, priv->act_request_public ? priv->act_request : NULL);
 		break;
 	case PROP_DEVICE_TYPE:
 		g_value_set_uint (value, priv->type);
diff --git a/src/nm-active-connection.c b/src/nm-active-connection.c
index 17fb82e5..38aadd27 100644
--- a/src/nm-active-connection.c
+++ b/src/nm-active-connection.c
@@ -157,6 +157,21 @@ _settings_connection_updated (NMSettingsConnection *connection,
 }
 
 static void
+_settings_connection_removed (NMSettingsConnection *connection,
+                              gpointer user_data)
+{
+	NMActiveConnection *self = user_data;
+
+	/* Our settings connection is about to drop off. The next active connection
+	 * cleanup is going to tear us down (at least until we grow the capability to
+	 * re-link; in that case we'd just clean the references to the old connection here).
+	 * Let's remove ourselves from the bus so that we're not exposed with a dangling
+	 * reference to the setting connection once it's gone. */
+	if (nm_exported_object_is_exported (NM_EXPORTED_OBJECT (self)))
+		nm_exported_object_unexport (NM_EXPORTED_OBJECT (self));
+}
+
+static void
 _set_settings_connection (NMActiveConnection *self, NMSettingsConnection *connection)
 {
 	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
@@ -165,11 +180,13 @@ _set_settings_connection (NMActiveConnection *self, NMSettingsConnection *connec
 		return;
 	if (priv->settings_connection) {
 		g_signal_handlers_disconnect_by_func (priv->settings_connection, _settings_connection_updated, self);
+		g_signal_handlers_disconnect_by_func (priv->settings_connection, _settings_connection_removed, self);
 		g_clear_object (&priv->settings_connection);
 	}
 	if (connection) {
 		priv->settings_connection = g_object_ref (connection);
 		g_signal_connect (connection, NM_SETTINGS_CONNECTION_UPDATED_INTERNAL, (GCallback) _settings_connection_updated, self);
+		g_signal_connect (connection, NM_SETTINGS_CONNECTION_REMOVED, (GCallback) _settings_connection_removed, self);
 	}
 }
 
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index 79f9e117..631540a1 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -1232,67 +1232,64 @@ typedef struct {
 } MatchDeviceData;
 
 static gboolean
-match_device_s390_subchannels_parse (const char *s390_subchannels, guint32 *a, guint32 *b, guint32 *c)
+match_device_s390_subchannels_parse (const char *s390_subchannels, guint32 *out_a, guint32 *out_b, guint32 *out_c)
 {
-	const int BUFSIZE = 10;
-	long unsigned int tmp;
+	const int BUFSIZE = 30;
 	char buf[BUFSIZE + 1];
-	const char *p = s390_subchannels;
-	int i = 0;
+	guint i = 0;
 	char *pa = NULL, *pb = NULL, *pc = NULL;
+	gint64 a, b, c;
 
 	nm_assert (s390_subchannels);
-	nm_assert (a != NULL);
-	nm_assert (*a == 0);
-	nm_assert (b != NULL);
-	nm_assert (*b == 0);
-	nm_assert (c != NULL);
-	nm_assert (*c == 0);
-
-	/* sanity check */
+	nm_assert (out_a);
+	nm_assert (out_b);
+	nm_assert (out_c);
+
 	if (!g_ascii_isxdigit (s390_subchannels[0]))
 		return FALSE;
 
 	/* Get the first channel */
-	while (*p && (*p != ',')) {
-		if (!g_ascii_isxdigit (*p) && (*p != '.'))
+	for (i = 0; s390_subchannels[i]; i++) {
+		char ch = s390_subchannels[i];
+
+		if (!g_ascii_isxdigit (ch) && ch != '.') {
+			if (ch == ',') {
+				/* FIXME: currently we consider the first channel and ignore
+				 * everything after the first ',' separator. Maybe we should
+				 * validate all present channels? */
+				break;
+			}
 			return FALSE;  /* Invalid chars */
+		}
 		if (i >= BUFSIZE)
 			return FALSE;  /* Too long to be a subchannel */
-		buf[i++] = *p++;
+		buf[i] = ch;
 	}
 	buf[i] = '\0';
 
 	/* and grab each of its elements, there should be 3 */
 	pa = &buf[0];
-	pb = strchr (buf, '.');
+	pb = strchr (pa, '.');
 	if (pb)
 		pc = strchr (pb + 1, '.');
-	if (!pa || !pb || !pc)
+	if (!pb || !pc)
 		return FALSE;
-
-	/* Split the string */
 	*pb++ = '\0';
 	*pc++ = '\0';
 
-	errno = 0;
-	tmp = strtoul (pa, NULL, 16);
-	if (errno)
+	a = _nm_utils_ascii_str_to_int64 (pa, 16, 0, G_MAXUINT32, -1);
+	if (a == -1)
 		return FALSE;
-	*a = (guint32) tmp;
-
-	errno = 0;
-	tmp = strtoul (pb, NULL, 16);
-	if (errno)
+	b = _nm_utils_ascii_str_to_int64 (pb, 16, 0, G_MAXUINT32, -1);
+	if (b == -1)
 		return FALSE;
-	*b = (guint32) tmp;
-
-	errno = 0;
-	tmp = strtoul (pc, NULL, 16);
-	if (errno)
+	c = _nm_utils_ascii_str_to_int64 (pc, 16, 0, G_MAXUINT32, -1);
+	if (c == -1)
 		return FALSE;
-	*c = (guint32) tmp;
 
+	*out_a = (guint32) a;
+	*out_b = (guint32) b;
+	*out_c = (guint32) c;
 	return TRUE;
 }
 
diff --git a/src/nm-exported-object.c b/src/nm-exported-object.c
index a53a3123..cd0789ee 100644
--- a/src/nm-exported-object.c
+++ b/src/nm-exported-object.c
@@ -35,8 +35,16 @@
 #define _ASSERT_NO_EARLY_EXPORT
 #endif
 
+/*****************************************************************************/
+
 static gboolean quitting = FALSE;
 
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMExportedObject,
+	PROP_PATH,
+);
+
 typedef struct {
 	GDBusInterfaceSkeleton *interface;
 	guint property_changed_signal_id;
@@ -677,6 +685,8 @@ nm_exported_object_export (NMExportedObject *self)
 
 	nm_bus_manager_register_object (priv->bus_mgr, (GDBusObjectSkeleton *) self);
 
+	_notify (self, PROP_PATH);
+
 	return priv->path;
 }
 
@@ -715,6 +725,8 @@ nm_exported_object_unexport (NMExportedObject *self)
 	g_clear_pointer (&priv->path, g_free);
 
 	nm_clear_g_source (&priv->notify_idle_id);
+
+	_notify (self, PROP_PATH);
 }
 
 /*****************************************************************************/
@@ -959,6 +971,23 @@ vtype_found:
 /*****************************************************************************/
 
 static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMExportedObject *self = NM_EXPORTED_OBJECT (object);
+	NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
+
+	switch (prop_id) {
+	case PROP_PATH:
+		g_value_set_string (value, priv->path);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
 nm_exported_object_init (NMExportedObject *self)
 {
 	NMExportedObjectPrivate *priv;
@@ -987,7 +1016,8 @@ constructed (GObject *object)
 static void
 dispose (GObject *object)
 {
-	NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (NM_EXPORTED_OBJECT (object));
+	NMExportedObject *self = NM_EXPORTED_OBJECT (object);
+	NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
 
 	/* Objects should have already been unexported by their owner, unless
 	 * we are quitting, where many objects stick around until exit.
@@ -995,10 +1025,10 @@ dispose (GObject *object)
 	if (!quitting) {
 		if (priv->path) {
 			g_warn_if_reached ();
-			nm_exported_object_unexport (NM_EXPORTED_OBJECT (object));
+			nm_exported_object_unexport (self);
 		}
-	} else
-		g_clear_pointer (&priv->path, g_free);
+	} else if (nm_clear_g_free (&priv->path))
+		_notify (self, PROP_PATH);
 
 	nm_clear_g_source (&priv->notify_idle_id);
 
@@ -1015,4 +1045,13 @@ nm_exported_object_class_init (NMExportedObjectClass *klass)
 	object_class->constructed = constructed;
 	object_class->notify = nm_exported_object_notify;
 	object_class->dispose = dispose;
+	object_class->get_property = get_property;
+
+	obj_properties[PROP_PATH] =
+	    g_param_spec_string (NM_EXPORTED_OBJECT_PATH, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 }
diff --git a/src/nm-exported-object.h b/src/nm-exported-object.h
index 7d348129..47559a20 100644
--- a/src/nm-exported-object.h
+++ b/src/nm-exported-object.h
@@ -49,6 +49,8 @@ void nm_exported_object_skeleton_release (GDBusInterfaceSkeleton *interface);
 #define NM_IS_EXPORTED_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_EXPORTED_OBJECT))
 #define NM_EXPORTED_OBJECT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_EXPORTED_OBJECT, NMExportedObjectClass))
 
+#define NM_EXPORTED_OBJECT_PATH "path"
+
 struct _NMExportedObjectPrivate;
 
 struct _NMExportedObject {
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 867e0db6..0c1d36e1 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -3122,6 +3122,13 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 			}
 		}
 
+		/* Now that we're activating a slave for that master, make sure the master just
+		 * decides to go unmanaged while we're activating (perhaps because other slaves
+		 * go away leaving him with no kids).
+		 */
+		nm_device_set_unmanaged_by_flags (master_device, NM_UNMANAGED_EXTERNAL_DOWN,
+		                                  NM_UNMAN_FLAG_OP_FORGET, NM_DEVICE_STATE_REASON_USER_REQUESTED);
+
 		nm_active_connection_set_master (active, master_ac);
 		_LOGD (LOGD_CORE, "Activation of '%s' depends on active connection %p %s",
 		       nm_settings_connection_get_id (connection),
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 6a920d5c..35790a12 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -1112,8 +1112,10 @@ claim_connection (NMSettings *self, NMSettingsConnection *connection)
 	openconnect_migrate_hack (NM_CONNECTION (connection));
 
 	g_object_ref (self);
-	g_signal_connect (connection, NM_SETTINGS_CONNECTION_REMOVED,
-	                  G_CALLBACK (connection_removed), self);
+	/* This one unexports the connection, it needs to run late to give the active
+	 * connection a chance to deal with its reference to this settings connection. */
+	g_signal_connect_after (connection, NM_SETTINGS_CONNECTION_REMOVED,
+	                        G_CALLBACK (connection_removed), self);
 	g_signal_connect (connection, NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
 	                  G_CALLBACK (connection_updated), self);
 	g_signal_connect (connection, "notify::" NM_SETTINGS_CONNECTION_VISIBLE,
diff --git a/src/tests/test-general.c b/src/tests/test-general.c
index add12233..53c9c0d8 100644
--- a/src/tests/test-general.c
+++ b/src/tests/test-general.c
@@ -955,47 +955,38 @@ test_connection_sort_autoconnect_priority (void)
 
 /*****************************************************************************/
 
-static NMMatchSpecMatchType
-_test_match_device_interface (const GSList *specs, const char *interface_name)
-{
-	return nm_match_spec_device (specs, interface_name, NULL, NULL, NULL);
-}
+#define MATCH_S390 "S390:"
 
-static const char *_test_match_spec_all[] = {
-	"e",
-	"em",
-	"em*",
-	"em\\",
-	"em\\*",
-	"em\\1",
-	"em\\11",
-	"em\\2",
-	"em1",
-	"em11",
-	"em2",
-	"=em*",
-	NULL
-};
-
-static gboolean
-_test_match_spec_contains (const char **matches, const char *match)
+static NMMatchSpecMatchType
+_test_match_spec_device (const GSList *specs, const char *match_str)
 {
-	guint i;
-
-	for (i = 0; matches && matches[i]; i++) {
-		if (strcmp (match, matches[i]) == 0)
-			return TRUE;
-	}
-	return FALSE;
+	if (match_str && g_str_has_prefix (match_str, MATCH_S390))
+		return nm_match_spec_device (specs, NULL, NULL, NULL, &match_str[NM_STRLEN (MATCH_S390)]);
+	return nm_match_spec_device (specs, match_str, NULL, NULL, NULL);
 }
 
 static void
-_do_test_match_spec_device (const char *spec_str, const char **matches, const char **neg_matches)
+_do_test_match_spec_device (const char *spec_str, const char **matches, const char **no_matches, const char **neg_matches)
 {
-	const char *m;
 	GSList *specs, *specs_randperm = NULL, *specs_resplit, *specs_i, *specs_j;
 	guint i;
 	gs_free char *specs_joined = NULL;
+	const char *s;
+	static const char *no_matches_default[] = {
+		"e",
+		"em",
+		"em*",
+		"em\\",
+		"em\\*",
+		"em\\1",
+		"em\\11",
+		"em\\2",
+		"em1",
+		"em11",
+		"em2",
+		"=em*",
+		NULL
+	};
 
 	g_assert (spec_str);
 
@@ -1019,20 +1010,25 @@ _do_test_match_spec_device (const char *spec_str, const char **matches, const ch
 	specs_randperm = nmtst_rand_perm_gslist (NULL, g_slist_copy (specs));
 
 	for (i = 0; matches && matches[i]; i++) {
-		g_assert (_test_match_device_interface (specs, matches[i]) == NM_MATCH_SPEC_MATCH);
-		g_assert (_test_match_device_interface (specs_randperm, matches[i]) == NM_MATCH_SPEC_MATCH);
+		g_assert (_test_match_spec_device (specs, matches[i]) == NM_MATCH_SPEC_MATCH);
+		g_assert (_test_match_spec_device (specs_randperm, matches[i]) == NM_MATCH_SPEC_MATCH);
 	}
 	for (i = 0; neg_matches && neg_matches[i]; i++) {
-		g_assert (_test_match_device_interface (specs, neg_matches[i]) == NM_MATCH_SPEC_NEG_MATCH);
-		g_assert (_test_match_device_interface (specs_randperm, neg_matches[i]) == NM_MATCH_SPEC_NEG_MATCH);
+		g_assert (_test_match_spec_device (specs, neg_matches[i]) == NM_MATCH_SPEC_NEG_MATCH);
+		g_assert (_test_match_spec_device (specs_randperm, neg_matches[i]) == NM_MATCH_SPEC_NEG_MATCH);
 	}
-	for (i = 0; (m = _test_match_spec_all[i]); i++) {
-		if (_test_match_spec_contains (matches, m))
-			continue;
-		if (_test_match_spec_contains (neg_matches, m))
-			continue;
-		g_assert (_test_match_device_interface (specs, m) == NM_MATCH_SPEC_NO_MATCH);
-		g_assert (_test_match_device_interface (specs_randperm, m) == NM_MATCH_SPEC_NO_MATCH);
+	for (i = 0; no_matches && no_matches[i]; i++) {
+		g_assert (_test_match_spec_device (specs, no_matches[i]) == NM_MATCH_SPEC_NO_MATCH);
+		g_assert (_test_match_spec_device (specs_randperm, no_matches[i]) == NM_MATCH_SPEC_NO_MATCH);
+	}
+	if (!no_matches) {
+		for (i = 0; (s = no_matches_default[i]); i++) {
+			if (   (matches && g_strv_contains (matches, s))
+			    || (neg_matches && g_strv_contains (neg_matches, s)))
+				continue;
+			g_assert (_test_match_spec_device (specs, s) == NM_MATCH_SPEC_NO_MATCH);
+			g_assert (_test_match_spec_device (specs_randperm, s) == NM_MATCH_SPEC_NO_MATCH);
+		}
 	}
 
 	g_slist_free (specs_randperm);
@@ -1045,49 +1041,73 @@ test_match_spec_device (void)
 #define S(...) ((const char *[]) { __VA_ARGS__, NULL } )
 	_do_test_match_spec_device ("em1",
 	                            S ("em1"),
+	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("em1,em2",
 	                            S ("em1", "em2"),
+	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("em1,em2,interface-name:em2",
 	                            S ("em1", "em2"),
+	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("interface-name:em1",
 	                            S ("em1"),
+	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("interface-name:em*",
 	                            S ("em", "em*", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em1", "em11", "em2", "em3"),
+	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("interface-name:em\\*",
 	                            S ("em\\", "em\\*", "em\\1", "em\\11", "em\\2"),
+	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("interface-name:~em\\*",
 	                            S ("em\\", "em\\*", "em\\1", "em\\11", "em\\2"),
+	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("interface-name:=em*",
 	                            S ("em*"),
+	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("interface-name:em*,except:interface-name:em1*",
 	                            S ("em", "em*", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em2", "em3"),
+	                            NULL,
 	                            S ("em1", "em11"));
 	_do_test_match_spec_device ("interface-name:em*,except:interface-name:=em*",
 	                            S ("em", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em1", "em11", "em2", "em3"),
+	                            NULL,
 	                            S ("em*"));
 	_do_test_match_spec_device ("aa,bb,cc\\,dd,e,,",
 	                            S ("aa", "bb", "cc,dd", "e"),
+	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("aa;bb;cc\\;dd;e,;",
 	                            S ("aa", "bb", "cc;dd", "e"),
+	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("interface-name:em\\;1,em\\,2,\\,,\\\\,,em\\\\x",
 	                            S ("em;1", "em,2", ",", "\\", "em\\x"),
+	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("\\s\\s,\\sinterface-name:a,\\s,",
 	                            S ("  ", " ", " interface-name:a"),
+	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device (" aa ;  bb   ; cc\\;dd  ;e , ; \t\\t  , ",
 	                            S ("aa", "bb", "cc;dd", "e", "\t"),
+	                            NULL,
+	                            NULL);
+
+	_do_test_match_spec_device ("s390-subchannels:0.0.1000\\,0.0.1001",
+	                            S (MATCH_S390"0.0.1000", MATCH_S390"0.0.1000,deadbeef", MATCH_S390"0.0.1000,0.0.1001", MATCH_S390"0.0.1000,0.0.1002"),
+	                            S (MATCH_S390"0.0.1001"),
 	                            NULL);
+	_do_test_match_spec_device ("*,except:s390-subchannels:0.0.1000\\,0.0.1001",
+	                            NULL,
+	                            S (NULL),
+	                            S (MATCH_S390"0.0.1000", MATCH_S390"0.0.1000,deadbeef", MATCH_S390"0.0.1000,0.0.1001", MATCH_S390"0.0.1000,0.0.1002"));
 #undef S
 }