summary refs log tree commit diff
path: root/src/settings/plugins/ifcfg-rh
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2015-07-14 19:38:58 +0200
committerMichael Biebl <biebl@debian.org>2015-07-14 19:38:58 +0200
commit50a58f0fabd8a34c1b6108a107e08abe3c1ccd24 (patch)
tree6790165f39daee79e2b6c6617483320613493367 /src/settings/plugins/ifcfg-rh
parentf408e27bccfacf347605a8d98649975a68f38a17 (diff)
Imported Upstream version 1.0.4 upstream/1.0.4
Diffstat (limited to 'src/settings/plugins/ifcfg-rh')
-rw-r--r--src/settings/plugins/ifcfg-rh/Makefile.in2
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-ifcfg-connection.c15
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-ifcfg-connection.h3
-rw-r--r--src/settings/plugins/ifcfg-rh/plugin.c59
-rw-r--r--src/settings/plugins/ifcfg-rh/reader.c48
-rw-r--r--src/settings/plugins/ifcfg-rh/reader.h3
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c78
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.h6
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/Makefile.in447
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/Makefile.in2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh-utils.c35
-rw-r--r--src/settings/plugins/ifcfg-rh/utils.c144
-rw-r--r--src/settings/plugins/ifcfg-rh/utils.h3
-rw-r--r--src/settings/plugins/ifcfg-rh/writer.c28
14 files changed, 602 insertions, 271 deletions
diff --git a/src/settings/plugins/ifcfg-rh/Makefile.in b/src/settings/plugins/ifcfg-rh/Makefile.in
index 89511983..0cd26960 100644
--- a/src/settings/plugins/ifcfg-rh/Makefile.in
+++ b/src/settings/plugins/ifcfg-rh/Makefile.in
@@ -269,6 +269,7 @@ ACLOCAL = @ACLOCAL@
 ALL_LINGUAS = @ALL_LINGUAS@
 AMTAR = @AMTAR@
 AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AM_TESTS_FD_REDIRECT = @AM_TESTS_FD_REDIRECT@
 AR = @AR@
 AUTOCONF = @AUTOCONF@
 AUTOHEADER = @AUTOHEADER@
@@ -382,6 +383,7 @@ LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
 LIPO = @LIPO@
 LN_S = @LN_S@
+LOG_DRIVER = @LOG_DRIVER@
 LTLIBICONV = @LTLIBICONV@
 LTLIBINTL = @LTLIBINTL@
 LTLIBOBJS = @LTLIBOBJS@
diff --git a/src/settings/plugins/ifcfg-rh/nm-ifcfg-connection.c b/src/settings/plugins/ifcfg-rh/nm-ifcfg-connection.c
index 94d22258..a18920c6 100644
--- a/src/settings/plugins/ifcfg-rh/nm-ifcfg-connection.c
+++ b/src/settings/plugins/ifcfg-rh/nm-ifcfg-connection.c
@@ -96,7 +96,7 @@ devtimeout_ready (gpointer user_data)
 }
 
 static void
-link_changed (NMPlatform *platform, int ifindex, NMPlatformLink *link,
+link_changed (NMPlatform *platform, NMPObjectType *obj_type, int ifindex, const NMPlatformLink *link,
               NMPlatformSignalChangeType change_type, NMPlatformReason reason,
               NMConnection *self)
 {
@@ -164,7 +164,7 @@ nm_ifcfg_connection_check_devtimeout (NMIfcfgConnection *self)
 	if (!devtimeout)
 		return;
 
-	if (nm_platform_link_get_ifindex (ifname) != 0)
+	if (nm_platform_link_get_ifindex (NM_PLATFORM_GET, ifname) != 0)
 		return;
 
 	/* ONBOOT=yes, DEVICE and DEVTIMEOUT are set, but device is not present */
@@ -201,7 +201,8 @@ files_changed_cb (NMInotifyHelper *ih,
 NMIfcfgConnection *
 nm_ifcfg_connection_new (NMConnection *source,
                          const char *full_path,
-                         GError **error)
+                         GError **error,
+                         gboolean *out_ignore_error)
 {
 	GObject *object;
 	NMConnection *tmp;
@@ -211,13 +212,17 @@ nm_ifcfg_connection_new (NMConnection *source,
 
 	g_assert (source || full_path);
 
+	if (out_ignore_error)
+		*out_ignore_error = FALSE;
+
 	/* If we're given a connection already, prefer that instead of re-reading */
 	if (source)
 		tmp = g_object_ref (source);
 	else {
 		tmp = connection_from_file (full_path,
 		                            &unhandled_spec,
-		                            error);
+		                            error,
+		                            out_ignore_error);
 		if (!tmp)
 			return NULL;
 
@@ -376,7 +381,7 @@ commit_changes (NMSettingsConnection *connection,
 	 */
 	filename = nm_settings_connection_get_filename (connection);
 	if (filename) {
-		reread = connection_from_file (filename, NULL, NULL);
+		reread = connection_from_file (filename, NULL, NULL, NULL);
 		if (reread) {
 			same = nm_connection_compare (NM_CONNECTION (connection),
 			                              reread,
diff --git a/src/settings/plugins/ifcfg-rh/nm-ifcfg-connection.h b/src/settings/plugins/ifcfg-rh/nm-ifcfg-connection.h
index 328e58f5..44e02987 100644
--- a/src/settings/plugins/ifcfg-rh/nm-ifcfg-connection.h
+++ b/src/settings/plugins/ifcfg-rh/nm-ifcfg-connection.h
@@ -48,7 +48,8 @@ GType nm_ifcfg_connection_get_type (void);
 
 NMIfcfgConnection *nm_ifcfg_connection_new (NMConnection *source,
                                             const char *full_path,
-                                            GError **error);
+                                            GError **error,
+                                            gboolean *out_ignore_error);
 
 const char *nm_ifcfg_connection_get_unmanaged_spec (NMIfcfgConnection *self);
 const char *nm_ifcfg_connection_get_unrecognized_spec (NMIfcfgConnection *self);
diff --git a/src/settings/plugins/ifcfg-rh/plugin.c b/src/settings/plugins/ifcfg-rh/plugin.c
index b7efa864..1a3f791a 100644
--- a/src/settings/plugins/ifcfg-rh/plugin.c
+++ b/src/settings/plugins/ifcfg-rh/plugin.c
@@ -169,7 +169,8 @@ remove_connection (SCPluginIfcfg *self, NMIfcfgConnection *connection)
 
 	g_object_ref (connection);
 	g_hash_table_remove (priv->connections, nm_connection_get_uuid (NM_CONNECTION (connection)));
-	nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection));
+	if (!unmanaged && !unrecognized)
+		nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection));
 	g_object_unref (connection);
 
 	/* Emit changes _after_ removing the connection */
@@ -213,6 +214,7 @@ update_connection (SCPluginIfcfg *self,
 	const char *new_unrecognized = NULL, *old_unrecognized = NULL;
 	gboolean unmanaged_changed = FALSE, unrecognized_changed = FALSE;
 	const char *uuid;
+	gboolean ignore_error = FALSE;
 
 	g_return_val_if_fail (!source || NM_IS_CONNECTION (source), NULL);
 	g_return_val_if_fail (full_path || source, NULL);
@@ -222,13 +224,16 @@ update_connection (SCPluginIfcfg *self,
 
 	/* Create a NMIfcfgConnection instance, either by reading from @full_path or
 	 * based on @source. */
-	connection_new = nm_ifcfg_connection_new (source, full_path, error);
+	connection_new = nm_ifcfg_connection_new (source, full_path, &local, &ignore_error);
 	if (!connection_new) {
 		/* Unexpected failure. Probably the file is invalid? */
 		if (   connection
 		    && !protect_existing_connection
 		    && (!protected_connections || !g_hash_table_contains (protected_connections, connection)))
 			remove_connection (self, connection);
+		if (!source && !ignore_error)
+			_LOGW ("loading \"%s\" fails: %s", full_path, local ? local->message : "(unknown reason)");
+		g_propagate_error (error, local);
 		return NULL;
 	}
 
@@ -421,25 +426,13 @@ ifcfg_dir_changed (GFileMonitor *monitor,
                    gpointer user_data)
 {
 	SCPluginIfcfg *plugin = SC_PLUGIN_IFCFG (user_data);
-	char *path, *base, *ifcfg_path;
+	char *path, *ifcfg_path;
 	NMIfcfgConnection *connection;
 
 	path = g_file_get_path (file);
-	if (utils_should_ignore_file (path, FALSE)) {
-		g_free (path);
-		return;
-	}
-
-	_LOGD ("ifcfg_dir_changed(%s) = %d", path, event_type);
 
-	base = g_file_get_basename (file);
-	if (utils_is_ifcfg_alias_file (base, NULL)) {
-		/* Alias file changed. Get the base ifcfg file from it */
-		ifcfg_path = utils_get_ifcfg_from_alias (path);
-	} else {
-		/* Given any ifcfg, keys, or routes file, get the ifcfg file path */
-		ifcfg_path = utils_get_ifcfg_path (path);
-	}
+	ifcfg_path = utils_detect_ifcfg_path (path, FALSE);
+	_LOGD ("ifcfg_dir_changed(%s) = %d // %s", path, event_type, ifcfg_path ? ifcfg_path : "(none)");
 	if (ifcfg_path) {
 		connection = find_by_path (plugin, ifcfg_path);
 		switch (event_type) {
@@ -458,7 +451,6 @@ ifcfg_dir_changed (GFileMonitor *monitor,
 		g_free (ifcfg_path);
 	}
 	g_free (path);
-	g_free (base);
 }
 
 static void
@@ -542,18 +534,14 @@ read_connections (SCPluginIfcfg *plugin)
 
 	filenames = g_ptr_array_new_with_free_func (g_free);
 	while ((item = g_dir_read_name (dir))) {
-		char *full_path;
-
-		if (utils_should_ignore_file (item, TRUE))
-			continue;
-		if (utils_is_ifcfg_alias_file (item, NULL))
-			continue;
+		char *full_path, *real_path;
 
 		full_path = g_build_filename (IFCFG_DIR, item, NULL);
-		if (!utils_get_ifcfg_name (full_path, TRUE))
-			g_free (full_path);
-		else
-			g_ptr_array_add (filenames, full_path);
+		real_path = utils_detect_ifcfg_path (full_path, TRUE);
+
+		if (real_path)
+			g_ptr_array_add (filenames, real_path);
+		g_free (full_path);
 	}
 	g_dir_close (dir);
 
@@ -625,20 +613,25 @@ load_connection (NMSystemConfigInterface *config,
 	SCPluginIfcfg *plugin = SC_PLUGIN_IFCFG (config);
 	NMIfcfgConnection *connection;
 	int dir_len = strlen (IFCFG_DIR);
+	char *ifcfg_path;
 
 	if (   strncmp (filename, IFCFG_DIR, dir_len) != 0
 	    || filename[dir_len] != '/'
 	    || strchr (filename + dir_len + 1, '/') != NULL)
 		return FALSE;
 
-	if (utils_should_ignore_file (filename + dir_len + 1, TRUE))
+	/* get the real ifcfg-path. This allows us to properly
+	 * handle load command using a route-* file etc. */
+	ifcfg_path = utils_detect_ifcfg_path (filename, FALSE);
+	if (!ifcfg_path)
 		return FALSE;
 
-	connection = find_by_path (plugin, filename);
-	update_connection (plugin, NULL, filename, connection, TRUE, NULL, NULL);
+	connection = find_by_path (plugin, ifcfg_path);
+	update_connection (plugin, NULL, ifcfg_path, connection, TRUE, NULL, NULL);
 	if (!connection)
-		connection = find_by_path (plugin, filename);
+		connection = find_by_path (plugin, ifcfg_path);
 
+	g_free (ifcfg_path);
 	return (connection != NULL);
 }
 
@@ -1139,7 +1132,7 @@ nm_system_config_factory (void)
 			dbus_g_connection_register_g_object (priv->bus,
 			                                     DBUS_OBJECT_PATH,
 			                                     G_OBJECT (singleton));
-		_LOGI ("Acquired D-Bus service %s", DBUS_SERVICE_NAME);
+		_LOGD ("Acquired D-Bus service %s", DBUS_SERVICE_NAME);
 	} else
 		g_object_ref (singleton);
 
diff --git a/src/settings/plugins/ifcfg-rh/reader.c b/src/settings/plugins/ifcfg-rh/reader.c
index 41439f61..ec874167 100644
--- a/src/settings/plugins/ifcfg-rh/reader.c
+++ b/src/settings/plugins/ifcfg-rh/reader.c
@@ -53,7 +53,6 @@
 #include <nm-utils.h>
 
 #include "nm-platform.h"
-#include "nm-posix-signals.h"
 #include "NetworkManagerUtils.h"
 #include "nm-logging.h"
 #include "gsystem-local-alloc.h"
@@ -178,6 +177,8 @@ make_connection_setting (const char *file,
 	                                      NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MIN,
 	                                      NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MAX,
 	                                      NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_DEFAULT),
+	              NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES,
+	              svTrueValue (ifcfg, "AUTOCONNECT_SLAVES", NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT),
 	              NULL);
 
 	value = svGetValue (ifcfg, "USERS", FALSE);
@@ -507,7 +508,7 @@ read_one_ip4_route (shvarFile *ifcfg,
 	/* Metric */
 	value = svGetValue (ifcfg, metric_tag, FALSE);
 	if (value) {
-		metric = nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, -1);
+		metric = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, -1);
 		if (metric < 0) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Invalid IP4 route metric '%s'", value);
@@ -1153,16 +1154,14 @@ read_aliases (NMSettingIPConfig *s_ip4, const char *filename, const char *networ
 	GDir *dir;
 	char *dirname, *base;
 	shvarFile *parsed;
-	NMIPAddress *base_addr;
+	NMIPAddress *base_addr = NULL;
 	GError *err = NULL;
 
 	g_return_if_fail (s_ip4 != NULL);
 	g_return_if_fail (filename != NULL);
 
-	if (nm_setting_ip_config_get_num_addresses (s_ip4) == 0)
-		return;
-
-	base_addr = nm_setting_ip_config_get_address (s_ip4, 0);
+	if (nm_setting_ip_config_get_num_addresses (s_ip4) > 0)
+		base_addr = nm_setting_ip_config_get_address (s_ip4, 0);
 
 	dirname = g_path_get_dirname (filename);
 	g_return_if_fail (dirname != NULL);
@@ -3351,7 +3350,7 @@ make_wireless_setting (shvarFile *ifcfg,
 	value = svGetValue (ifcfg, "CHANNEL", FALSE);
 	if (value) {
 		errno = 0;
-		chan = nm_utils_ascii_str_to_int64 (value, 10, 1, 196, 0);
+		chan = _nm_utils_ascii_str_to_int64 (value, 10, 1, 196, 0);
 		if (errno || (chan == 0)) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Invalid wireless channel '%s'", value);
@@ -4335,11 +4334,11 @@ is_wifi_device (const char *name, shvarFile *parsed)
 	g_return_val_if_fail (name != NULL, FALSE);
 	g_return_val_if_fail (parsed != NULL, FALSE);
 
-	ifindex = nm_platform_link_get_ifindex (name);
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, name);
 	if (ifindex == 0)
 		return FALSE;
 
-	return nm_platform_link_get_type (ifindex) == NM_LINK_TYPE_WIFI;
+	return nm_platform_link_get_type (NM_PLATFORM_GET, ifindex) == NM_LINK_TYPE_WIFI;
 }
 
 static void
@@ -4433,13 +4432,10 @@ make_vlan_setting (shvarFile *ifcfg,
 			/* Grab VLAN ID from interface name; this takes precedence over the
 			 * separate VLAN_ID property for backwards compat.
 			 */
-			vlan_id = (gint) g_ascii_strtoll (p, &end, 10);
-			if (vlan_id < 0 || vlan_id > 4095 || end == p || *end) {
-				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				             "Failed to determine VLAN ID from DEVICE '%s'",
-				             iface_name);
-				goto error;
-			}
+
+			gint device_vlan_id = (gint) g_ascii_strtoll (p, &end, 10);
+			if (device_vlan_id >= 0 && device_vlan_id <= 4095 && end != p && !*end)
+				vlan_id = device_vlan_id;
 		}
 	}
 
@@ -4679,7 +4675,8 @@ connection_from_file_full (const char *filename,
 
 		connection = create_unhandled_connection (filename, parsed, "unmanaged", out_unhandled);
 		if (!connection)
-			PARSE_WARNING ("NM_CONTROLLED was false but device was not uniquely identified; device will be managed");
+			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+			             "NM_CONTROLLED was false but device was not uniquely identified; device will be managed");
 		goto done;
 	}
 
@@ -4841,18 +4838,13 @@ done:
 NMConnection *
 connection_from_file (const char *filename,
                       char **out_unhandled,
-                      GError **error)
+                      GError **error,
+                      gboolean *out_ignore_error)
 {
-	gboolean ignore_error = FALSE;
-	NMConnection *conn;
-
-	conn = connection_from_file_full (filename, NULL, NULL,
+	return connection_from_file_full (filename, NULL, NULL,
 	                                  out_unhandled,
 	                                  error,
-	                                  &ignore_error);
-	if (error && *error && !ignore_error)
-		PARSE_WARNING ("%s", (*error)->message);
-	return conn;
+	                                  out_ignore_error);
 }
 
 NMConnection *
@@ -4885,7 +4877,7 @@ devtimeout_from_file (const char *filename)
 
 	devtimeout_str = svGetValue (ifcfg, "DEVTIMEOUT", FALSE);
 	if (devtimeout_str) {
-		devtimeout = nm_utils_ascii_str_to_int64 (devtimeout_str, 10, 0, G_MAXUINT, 0);
+		devtimeout = _nm_utils_ascii_str_to_int64 (devtimeout_str, 10, 0, G_MAXUINT, 0);
 		g_free (devtimeout_str);
 	} else
 		devtimeout = 0;
diff --git a/src/settings/plugins/ifcfg-rh/reader.h b/src/settings/plugins/ifcfg-rh/reader.h
index 70e9ce4e..2096ffc4 100644
--- a/src/settings/plugins/ifcfg-rh/reader.h
+++ b/src/settings/plugins/ifcfg-rh/reader.h
@@ -28,7 +28,8 @@
 
 NMConnection *connection_from_file (const char *filename,
                                     char **out_unhandled,
-                                    GError **error);
+                                    GError **error,
+                                    gboolean *out_ignore_error);
 
 char *uuid_from_file (const char *filename);
 
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index 5f3d173b..4a5ca1d1 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -36,7 +36,8 @@
 
 #include "shvar.h"
 
-#include "NetworkManagerUtils.h"
+#include "gsystem-local-alloc.h"
+#include "nm-core-internal.h"
 #include "nm-logging.h"
 
 #define PARSE_WARNING(msg...) nm_log_warn (LOGD_SETTINGS, "    " msg)
@@ -206,8 +207,8 @@ static const char escapees[] = "\"'\\$~`";		/* must be escaped */
 static const char spaces[] = " \t|&;()<>";		/* only require "" */
 static const char newlines[] = "\n\r";			/* will be removed */
 
-char *
-svEscape (const char *s)
+const char *
+svEscape (const char *s, char **to_free)
 {
 	char *new;
 	int i, j, mangle = 0, space = 0, newline = 0;
@@ -223,8 +224,10 @@ svEscape (const char *s)
 		if (strchr (newlines, s[i]))
 			newline++;
 	}
-	if (!mangle && !space && !newline)
-		return strdup (s);
+	if (!mangle && !space && !newline) {
+		*to_free = NULL;
+		return s;
+	}
 
 	newlen = slen + mangle - newline + 3;	/* 3 is extra ""\0 */
 	new = g_malloc (newlen);
@@ -243,6 +246,7 @@ svEscape (const char *s)
 	new[j++] = '\0';
 	g_assert (j == slen + mangle - newline + 3);
 
+	*to_free = new;
 	return new;
 }
 
@@ -253,6 +257,22 @@ svEscape (const char *s)
 char *
 svGetValue (shvarFile *s, const char *key, gboolean verbatim)
 {
+	char *value;
+
+	value = svGetValueFull (s, key, verbatim);
+	if (value && !*value) {
+		g_free (value);
+		return NULL;
+	}
+	return value;
+}
+
+/* svGetValueFull() is identical to svGetValue() except that
+ * svGetValue() will never return an empty value (but %NULL instead).
+ * svGetValueFull() will return empty values if that is the value for the @key. */
+char *
+svGetValueFull (shvarFile *s, const char *key, gboolean verbatim)
+{
 	char *value = NULL;
 	char *line;
 	char *keyString;
@@ -276,12 +296,7 @@ svGetValue (shvarFile *s, const char *key, gboolean verbatim)
 	}
 	g_free (keyString);
 
-	if (value && value[0]) {
-		return value;
-	} else {
-		g_free (value);
-		return NULL;
-	}
+	return value;
 }
 
 /* return TRUE if <key> resolves to any truth value (e.g. "yes", "y", "true")
@@ -321,7 +336,7 @@ svTrueValue (shvarFile *s, const char *key, gboolean def)
  * @max: the maximum for range-check
  * @fallback: the fallback value in any error case
  *
- * Reads a value @key and converts it to an integer using nm_utils_ascii_str_to_int64().
+ * Reads a value @key and converts it to an integer using _nm_utils_ascii_str_to_int64().
  * In case of error, @errno will be set and @fallback returned. */
 gint64
 svGetValueInt64 (shvarFile *s, const char *key, guint base, gint64 min, gint64 max, gint64 fallback)
@@ -330,13 +345,13 @@ svGetValueInt64 (shvarFile *s, const char *key, guint base, gint64 min, gint64 m
 	gint64 result;
 	int errsv;
 
-	tmp = svGetValue (s, key, FALSE);
+	tmp = svGetValueFull (s, key, FALSE);
 	if (!tmp) {
 		errno = 0;
 		return fallback;
 	}
 
-	result = nm_utils_ascii_str_to_int64 (tmp, base, min, max, fallback);
+	result = _nm_utils_ascii_str_to_int64 (tmp, base, min, max, fallback);
 	errsv = errno;
 	if (errsv != 0)
 		PARSE_WARNING ("Error reading '%s' value '%s' as integer (%d)", key, tmp, errsv);
@@ -354,20 +369,30 @@ svGetValueInt64 (shvarFile *s, const char *key, guint base, gint64 min, gint64 m
 void
 svSetValue (shvarFile *s, const char *key, const char *value, gboolean verbatim)
 {
-	char *newval = NULL, *oldval = NULL;
+	svSetValueFull (s, key, value && value[0] ? value : NULL, verbatim);
+}
+
+/* Same as svSetValue() but it preserves empty @value -- contrary to
+ * svSetValue() for which "" effectively means to remove the value. */
+void
+svSetValueFull (shvarFile *s, const char *key, const char *value, gboolean verbatim)
+{
+	gs_free char *newval_free = NULL;
+	gs_free char *oldval = NULL;
+	const char *newval;
 	char *keyValue;
 
 	g_return_if_fail (s != NULL);
 	g_return_if_fail (key != NULL);
 	/* value may be NULL */
 
-	if (value)
-		newval = verbatim ? g_strdup (value) : svEscape (value);
-	keyValue = g_strdup_printf ("%s=%s", key, newval ? newval : "");
-
-	oldval = svGetValue (s, key, FALSE);
+	if (!value || verbatim)
+		newval = value;
+	else
+		newval = svEscape (value, &newval_free);
+	oldval = svGetValueFull (s, key, FALSE);
 
-	if (!newval || !newval[0]) {
+	if (!newval) {
 		/* delete value */
 		if (oldval) {
 			/* delete line */
@@ -376,15 +401,15 @@ svSetValue (shvarFile *s, const char *key, const char *value, gboolean verbatim)
 			g_list_free_1 (s->current);
 			s->modified = TRUE;
 		}
-		g_free (keyValue);
-		goto end;
+		return;
 	}
 
+	keyValue = g_strdup_printf ("%s=%s", key, newval);
 	if (!oldval) {
 		/* append line */
 		s->lineList = g_list_append (s->lineList, keyValue);
 		s->modified = TRUE;
-		goto end;
+		return;
 	}
 
 	if (strcmp (oldval, newval) != 0) {
@@ -397,11 +422,6 @@ svSetValue (shvarFile *s, const char *key, const char *value, gboolean verbatim)
 		s->modified = TRUE;
 	} else
 		g_free (keyValue);
-
- end:
-	g_free (newval);
-	g_free (oldval);
-	return;
 }
 
 /* Write the current contents iff modified.  Returns FALSE on error
diff --git a/src/settings/plugins/ifcfg-rh/shvar.h b/src/settings/plugins/ifcfg-rh/shvar.h
index b2a2f263..4902541b 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.h
+++ b/src/settings/plugins/ifcfg-rh/shvar.h
@@ -56,6 +56,7 @@ shvarFile *svOpenFile (const char *name, GError **error);
  * be freed by the caller.
  */
 char *svGetValue (shvarFile *s, const char *key, gboolean verbatim);
+char *svGetValueFull (shvarFile *s, const char *key, gboolean verbatim);
 
 /* return TRUE if <key> resolves to any truth value (e.g. "yes", "y", "true")
  * return FALSE if <key> resolves to any non-truth value (e.g. "no", "n", "false")
@@ -71,6 +72,7 @@ gint64 svGetValueInt64 (shvarFile *s, const char *key, guint base, gint64 min, g
  * to the top of the file.
  */
 void svSetValue (shvarFile *s, const char *key, const char *value, gboolean verbatim);
+void svSetValueFull (shvarFile *s, const char *key, const char *value, gboolean verbatim);
 
 
 /* Write the current contents iff modified.  Returns FALSE on error
@@ -84,8 +86,8 @@ gboolean svWriteFile (shvarFile *s, int mode, GError **error);
 /* Close the file descriptor (if open) and free the shvarFile. */
 void svCloseFile (shvarFile *s);
 
-/* Return a new escaped string */
-char *svEscape (const char *s);
+/* Return @s unmodified or an escaped string */
+const char *svEscape (const char *s, char **to_free);
 
 /* Unescape a string in-place */
 void svUnescape (char *s);
diff --git a/src/settings/plugins/ifcfg-rh/tests/Makefile.in b/src/settings/plugins/ifcfg-rh/tests/Makefile.in
index d3c838a7..d0832e47 100644
--- a/src/settings/plugins/ifcfg-rh/tests/Makefile.in
+++ b/src/settings/plugins/ifcfg-rh/tests/Makefile.in
@@ -192,7 +192,7 @@ am__recursive_targets = \
   $(RECURSIVE_CLEAN_TARGETS) \
   $(am__extra_recursive_targets)
 AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
-	distdir
+	check recheck distdir
 am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
 # Read a list of newline-separated strings from the standard input,
 # and print each of them once, without duplicates.  Input order is
@@ -234,9 +234,190 @@ am__tty_colors = { \
     std=''; \
   fi; \
 }
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+    $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+    *) f=$$p;; \
+  esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+  srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+  for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+  for p in $$list; do echo "$$p $$p"; done | \
+  sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+  $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+    if (++n[$$2] == $(am__install_max)) \
+      { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+    END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+  sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+  sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+  test -z "$$files" \
+    || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+    || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+         $(am__cd) "$$dir" && rm -f $$files; }; \
+  }
+am__recheck_rx = ^[ 	]*:recheck:[ 	]*
+am__global_test_result_rx = ^[ 	]*:global-test-result:[ 	]*
+am__copy_in_global_log_rx = ^[ 	]*:copy-in-global-log:[ 	]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+  recheck = 1; \
+  while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+    { \
+      if (rc < 0) \
+        { \
+          if ((getline line2 < ($$0 ".log")) < 0) \
+	    recheck = 0; \
+          break; \
+        } \
+      else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+        { \
+          recheck = 0; \
+          break; \
+        } \
+      else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+        { \
+          break; \
+        } \
+    }; \
+  if (recheck) \
+    print $$0; \
+  close ($$0 ".trs"); \
+  close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+  print "fatal: making $@: " msg | "cat >&2"; \
+  exit 1; \
+} \
+function rst_section(header) \
+{ \
+  print header; \
+  len = length(header); \
+  for (i = 1; i <= len; i = i + 1) \
+    printf "="; \
+  printf "\n\n"; \
+} \
+{ \
+  copy_in_global_log = 1; \
+  global_test_result = "RUN"; \
+  while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+    { \
+      if (rc < 0) \
+         fatal("failed to read from " $$0 ".trs"); \
+      if (line ~ /$(am__global_test_result_rx)/) \
+        { \
+          sub("$(am__global_test_result_rx)", "", line); \
+          sub("[ 	]*$$", "", line); \
+          global_test_result = line; \
+        } \
+      else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+        copy_in_global_log = 0; \
+    }; \
+  if (copy_in_global_log) \
+    { \
+      rst_section(global_test_result ": " $$0); \
+      while ((rc = (getline line < ($$0 ".log"))) != 0) \
+      { \
+        if (rc < 0) \
+          fatal("failed to read from " $$0 ".log"); \
+        print line; \
+      }; \
+      printf "\n"; \
+    }; \
+  close ($$0 ".trs"); \
+  close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/   &   /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this.  Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+  --color-tests "$$am__color_tests" \
+  --enable-hard-errors "$$am__enable_hard_errors" \
+  --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test.  Creates the
+# directory for the log if needed.  Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log.  Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT.  Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup);					\
+$(am__vpath_adj_setup) $(am__vpath_adj)			\
+$(am__tty_colors);					\
+srcdir=$(srcdir); export srcdir;			\
+case "$@" in						\
+  */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;;	\
+    *) am__odir=.;; 					\
+esac;							\
+test "x$$am__odir" = x"." || test -d "$$am__odir" 	\
+  || $(MKDIR_P) "$$am__odir" || exit $$?;		\
+if test -f "./$$f"; then dir=./;			\
+elif test -f "$$f"; then dir=;				\
+else dir="$(srcdir)/"; fi;				\
+tst=$$dir$$f; log='$@'; 				\
+if test -n '$(DISABLE_HARD_ERRORS)'; then		\
+  am__enable_hard_errors=no; 				\
+else							\
+  am__enable_hard_errors=yes; 				\
+fi; 							\
+case " $(XFAIL_TESTS) " in				\
+  *[\ \	]$$f[\ \	]* | *[\ \	]$$dir$$f[\ \	]*) \
+    am__expect_failure=yes;;				\
+  *)							\
+    am__expect_failure=no;;				\
+esac; 							\
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed).  The result is saved in the shell variable
+# '$bases'.  This honors runtime overriding of TESTS and TEST_LOGS.  Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+  bases='$(TEST_LOGS)'; \
+  bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+  bases=`echo $$bases`
+RECHECK_LOGS = $(TEST_LOGS)
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+  case '$@' in \
+    */*) \
+      case '$*' in \
+        */*) b='$*';; \
+          *) b=`echo '$@' | sed 's/\.log$$//'`; \
+       esac;; \
+    *) \
+      b='$*';; \
+  esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/build-aux/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+	$(TEST_LOG_FLAGS)
 DIST_SUBDIRS = network-scripts
 am__DIST_COMMON = $(srcdir)/Makefile.in \
-	$(top_srcdir)/build-aux/depcomp
+	$(top_srcdir)/build-aux/depcomp \
+	$(top_srcdir)/build-aux/test-driver
 DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
 am__relativize = \
   dir0=`pwd`; \
@@ -267,6 +448,7 @@ ACLOCAL = @ACLOCAL@
 ALL_LINGUAS = @ALL_LINGUAS@
 AMTAR = @AMTAR@
 AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AM_TESTS_FD_REDIRECT = @AM_TESTS_FD_REDIRECT@
 AR = @AR@
 AUTOCONF = @AUTOCONF@
 AUTOHEADER = @AUTOHEADER@
@@ -380,6 +562,7 @@ LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
 LIPO = @LIPO@
 LN_S = @LN_S@
+LOG_DRIVER = @LOG_DRIVER@
 LTLIBICONV = @LTLIBICONV@
 LTLIBINTL = @LTLIBINTL@
 LTLIBOBJS = @LTLIBOBJS@
@@ -568,7 +751,7 @@ with_valgrind = @with_valgrind@
 all: all-recursive
 
 .SUFFIXES:
-.SUFFIXES: .c .lo .o .obj
+.SUFFIXES: .c .lo .log .o .obj .test .test$(EXEEXT) .trs
 $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__configure_deps)
 	@for dep in $?; do \
 	  case '$(am__configure_deps)' in \
@@ -811,98 +994,175 @@ cscopelist-am: $(am__tagged_files)
 distclean-tags:
 	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
 
-check-TESTS: $(TESTS)
-	@failed=0; all=0; xfail=0; xpass=0; skip=0; \
-	srcdir=$(srcdir); export srcdir; \
-	list=' $(TESTS) '; \
-	$(am__tty_colors); \
-	if test -n "$$list"; then \
-	  for tst in $$list; do \
-	    if test -f ./$$tst; then dir=./; \
-	    elif test -f $$tst; then dir=; \
-	    else dir="$(srcdir)/"; fi; \
-	    if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
-	      all=`expr $$all + 1`; \
-	      case " $(XFAIL_TESTS) " in \
-	      *[\ \	]$$tst[\ \	]*) \
-		xpass=`expr $$xpass + 1`; \
-		failed=`expr $$failed + 1`; \
-		col=$$red; res=XPASS; \
-	      ;; \
-	      *) \
-		col=$$grn; res=PASS; \
-	      ;; \
-	      esac; \
-	    elif test $$? -ne 77; then \
-	      all=`expr $$all + 1`; \
-	      case " $(XFAIL_TESTS) " in \
-	      *[\ \	]$$tst[\ \	]*) \
-		xfail=`expr $$xfail + 1`; \
-		col=$$lgn; res=XFAIL; \
-	      ;; \
-	      *) \
-		failed=`expr $$failed + 1`; \
-		col=$$red; res=FAIL; \
-	      ;; \
-	      esac; \
-	    else \
-	      skip=`expr $$skip + 1`; \
-	      col=$$blu; res=SKIP; \
-	    fi; \
-	    echo "$${col}$$res$${std}: $$tst"; \
-	  done; \
-	  if test "$$all" -eq 1; then \
-	    tests="test"; \
-	    All=""; \
-	  else \
-	    tests="tests"; \
-	    All="All "; \
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'.  Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+	rm -f $< $@
+	$(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+	@:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+	@$(am__set_TESTS_bases); \
+	am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+	redo_bases=`for i in $$bases; do \
+	              am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+	            done`; \
+	if test -n "$$redo_bases"; then \
+	  redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+	  redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+	  if $(am__make_dryrun); then :; else \
+	    rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
 	  fi; \
-	  if test "$$failed" -eq 0; then \
-	    if test "$$xfail" -eq 0; then \
-	      banner="$$All$$all $$tests passed"; \
-	    else \
-	      if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
-	      banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
-	    fi; \
-	  else \
-	    if test "$$xpass" -eq 0; then \
-	      banner="$$failed of $$all $$tests failed"; \
+	fi; \
+	if test -n "$$am__remaking_logs"; then \
+	  echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+	       "recursion detected" >&2; \
+	elif test -n "$$redo_logs"; then \
+	  am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+	fi; \
+	if $(am__make_dryrun); then :; else \
+	  st=0;  \
+	  errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+	  for i in $$redo_bases; do \
+	    test -f $$i.trs && test -r $$i.trs \
+	      || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+	    test -f $$i.log && test -r $$i.log \
+	      || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+	  done; \
+	  test $$st -eq 0 || exit 1; \
+	fi
+	@$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+	ws='[ 	]'; \
+	results=`for b in $$bases; do echo $$b.trs; done`; \
+	test -n "$$results" || results=/dev/null; \
+	all=`  grep "^$$ws*:test-result:"           $$results | wc -l`; \
+	pass=` grep "^$$ws*:test-result:$$ws*PASS"  $$results | wc -l`; \
+	fail=` grep "^$$ws*:test-result:$$ws*FAIL"  $$results | wc -l`; \
+	skip=` grep "^$$ws*:test-result:$$ws*SKIP"  $$results | wc -l`; \
+	xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+	xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+	error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+	if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+	  success=true; \
+	else \
+	  success=false; \
+	fi; \
+	br='==================='; br=$$br$$br$$br$$br; \
+	result_count () \
+	{ \
+	    if test x"$$1" = x"--maybe-color"; then \
+	      maybe_colorize=yes; \
+	    elif test x"$$1" = x"--no-color"; then \
+	      maybe_colorize=no; \
 	    else \
-	      if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
-	      banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+	      echo "$@: invalid 'result_count' usage" >&2; exit 4; \
 	    fi; \
-	  fi; \
-	  dashes="$$banner"; \
-	  skipped=""; \
-	  if test "$$skip" -ne 0; then \
-	    if test "$$skip" -eq 1; then \
-	      skipped="($$skip test was not run)"; \
+	    shift; \
+	    desc=$$1 count=$$2; \
+	    if test $$maybe_colorize = yes && test $$count -gt 0; then \
+	      color_start=$$3 color_end=$$std; \
 	    else \
-	      skipped="($$skip tests were not run)"; \
+	      color_start= color_end=; \
 	    fi; \
-	    test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
-	      dashes="$$skipped"; \
-	  fi; \
-	  report=""; \
-	  if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
-	    report="Please report to $(PACKAGE_BUGREPORT)"; \
-	    test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
-	      dashes="$$report"; \
-	  fi; \
-	  dashes=`echo "$$dashes" | sed s/./=/g`; \
-	  if test "$$failed" -eq 0; then \
-	    col="$$grn"; \
-	  else \
-	    col="$$red"; \
-	  fi; \
-	  echo "$${col}$$dashes$${std}"; \
-	  echo "$${col}$$banner$${std}"; \
-	  test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
-	  test -z "$$report" || echo "$${col}$$report$${std}"; \
-	  echo "$${col}$$dashes$${std}"; \
-	  test "$$failed" -eq 0; \
-	else :; fi
+	    echo "$${color_start}# $$desc $$count$${color_end}"; \
+	}; \
+	create_testsuite_report () \
+	{ \
+	  result_count $$1 "TOTAL:" $$all   "$$brg"; \
+	  result_count $$1 "PASS: " $$pass  "$$grn"; \
+	  result_count $$1 "SKIP: " $$skip  "$$blu"; \
+	  result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+	  result_count $$1 "FAIL: " $$fail  "$$red"; \
+	  result_count $$1 "XPASS:" $$xpass "$$red"; \
+	  result_count $$1 "ERROR:" $$error "$$mgn"; \
+	}; \
+	{								\
+	  echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" |	\
+	    $(am__rst_title);						\
+	  create_testsuite_report --no-color;				\
+	  echo;								\
+	  echo ".. contents:: :depth: 2";				\
+	  echo;								\
+	  for b in $$bases; do echo $$b; done				\
+	    | $(am__create_global_log);					\
+	} >$(TEST_SUITE_LOG).tmp || exit 1;				\
+	mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG);			\
+	if $$success; then						\
+	  col="$$grn";							\
+	 else								\
+	  col="$$red";							\
+	  test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG);		\
+	fi;								\
+	echo "$${col}$$br$${std}"; 					\
+	echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}";	\
+	echo "$${col}$$br$${std}"; 					\
+	create_testsuite_report --maybe-color;				\
+	echo "$$col$$br$$std";						\
+	if $$success; then :; else					\
+	  echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}";		\
+	  if test -n "$(PACKAGE_BUGREPORT)"; then			\
+	    echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}";	\
+	  fi;								\
+	  echo "$$col$$br$$std";					\
+	fi;								\
+	$$success || exit 1
+
+check-TESTS:
+	@list='$(RECHECK_LOGS)';           test -z "$$list" || rm -f $$list
+	@list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+	@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+	@set +e; $(am__set_TESTS_bases); \
+	log_list=`for i in $$bases; do echo $$i.log; done`; \
+	trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+	log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+	$(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+	exit $$?;
+recheck: all 
+	@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+	@set +e; $(am__set_TESTS_bases); \
+	bases=`for i in $$bases; do echo $$i; done \
+	         | $(am__list_recheck_tests)` || exit 1; \
+	log_list=`for i in $$bases; do echo $$i.log; done`; \
+	log_list=`echo $$log_list`; \
+	$(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+	        am__force_recheck=am--force-recheck \
+	        TEST_LOGS="$$log_list"; \
+	exit $$?
+test-ifcfg-rh-utils.log: test-ifcfg-rh-utils$(EXEEXT)
+	@p='test-ifcfg-rh-utils$(EXEEXT)'; \
+	b='test-ifcfg-rh-utils'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-ifcfg-rh.log: test-ifcfg-rh$(EXEEXT)
+	@p='test-ifcfg-rh$(EXEEXT)'; \
+	b='test-ifcfg-rh'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+	@p='$<'; \
+	$(am__set_b); \
+	$(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@	@p='$<'; \
+@am__EXEEXT_TRUE@	$(am__set_b); \
+@am__EXEEXT_TRUE@	$(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@	--log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@	$(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@	"$$tst" $(AM_TESTS_FD_REDIRECT)
 
 distdir: $(DISTFILES)
 	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
@@ -985,6 +1245,9 @@ install-strip:
 	    "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
 	fi
 mostlyclean-generic:
+	-test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+	-test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+	-test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
 
 clean-generic:
 
@@ -1080,8 +1343,8 @@ uninstall-am:
 	install-strip installcheck installcheck-am installdirs \
 	installdirs-am maintainer-clean maintainer-clean-generic \
 	mostlyclean mostlyclean-compile mostlyclean-generic \
-	mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
-	uninstall-am
+	mostlyclean-libtool pdf pdf-am ps ps-am recheck tags tags-am \
+	uninstall uninstall-am
 
 .PRECIOUS: Makefile
 
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/Makefile.in b/src/settings/plugins/ifcfg-rh/tests/network-scripts/Makefile.in
index 58798f77..ef06c5ef 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/Makefile.in
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/Makefile.in
@@ -135,6 +135,7 @@ ACLOCAL = @ACLOCAL@
 ALL_LINGUAS = @ALL_LINGUAS@
 AMTAR = @AMTAR@
 AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AM_TESTS_FD_REDIRECT = @AM_TESTS_FD_REDIRECT@
 AR = @AR@
 AUTOCONF = @AUTOCONF@
 AUTOHEADER = @AUTOHEADER@
@@ -248,6 +249,7 @@ LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
 LIPO = @LIPO@
 LN_S = @LN_S@
+LOG_DRIVER = @LOG_DRIVER@
 LTLIBICONV = @LTLIBICONV@
 LTLIBINTL = @LTLIBINTL@
 LTLIBOBJS = @LTLIBOBJS@
diff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh-utils.c b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh-utils.c
index 13f1126e..68f236af 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh-utils.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh-utils.c
@@ -115,15 +115,9 @@ test_ignored (const char *desc, const char *path, gboolean expected_ignored)
 	ASSERT (result == expected_ignored, desc, "unexpected ignore result for path '%s'", path);
 }
 
-NMTST_DEFINE ();
-
-int main (int argc, char **argv)
+static void
+test_name (void)
 {
-	char *base;
-
-	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
-
-	/* The tests */
 	test_get_ifcfg_name ("get-ifcfg-name-bad", "/foo/bar/adfasdfadf", FALSE, NULL);
 	test_get_ifcfg_name ("get-ifcfg-name-good", "/foo/bar/ifcfg-FooBar", FALSE, "FooBar");
 	test_get_ifcfg_name ("get-ifcfg-name-keys", "/foo/bar/keys-BlahLbah", FALSE, "BlahLbah");
@@ -137,7 +131,11 @@ int main (int argc, char **argv)
 	test_get_ifcfg_name ("get-ifcfg-name-bad2-ifcfg", "/foo/bar/asdfasifcfg-Foobar", FALSE, NULL);
 	test_get_ifcfg_name ("get-ifcfg-name-bad2-keys", "/foo/bar/asdfaskeys-Foobar", FALSE, NULL);
 	test_get_ifcfg_name ("get-ifcfg-name-bad2-route", "/foo/bar/asdfasroute-Foobar", FALSE, NULL);
+}
 
+static void
+test_path (void)
+{
 	test_get_ifcfg_path ("ifcfg-path-bad", "/foo/bar/adfasdfasdf", NULL);
 	test_get_ifcfg_path ("ifcfg-path-from-keys-no-path", "keys-BlahBlah", "ifcfg-BlahBlah");
 	test_get_ifcfg_path ("ifcfg-path-from-keys", "/foo/bar/keys-BlahBlah", "/foo/bar/ifcfg-BlahBlah");
@@ -152,7 +150,11 @@ int main (int argc, char **argv)
 	test_get_route_path ("route-path-from-ifcfg-no-path", "ifcfg-FooBar", "route-FooBar");
 	test_get_route_path ("route-path-from-ifcfg", "/foo/bar/ifcfg-FooBar", "/foo/bar/route-FooBar");
 	test_get_route_path ("route-path-from-keys", "/foo/bar/keys-FooBar", "/foo/bar/route-FooBar");
+}
 
+static void
+test_ignore (void)
+{
 	test_ignored ("ignored-ifcfg", "ifcfg-FooBar", FALSE);
 	test_ignored ("ignored-keys", "keys-FooBar", FALSE);
 	test_ignored ("ignored-route", "route-FooBar", FALSE);
@@ -163,10 +165,19 @@ int main (int argc, char **argv)
 	test_ignored ("ignored-rpmnew", "ifcfg-FooBar" RPMNEW_TAG, TRUE);
 	test_ignored ("ignored-augnew", "ifcfg-FooBar" AUGNEW_TAG, TRUE);
 	test_ignored ("ignored-augtmp", "ifcfg-FooBar" AUGTMP_TAG, TRUE);
+}
+
+NMTST_DEFINE ();
+
+int main (int argc, char **argv)
+{
+	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
+
+	/* The tests */
+	g_test_add_func ("/settings/plugins/ifcfg-rh/name", test_name);
+	g_test_add_func ("/settings/plugins/ifcfg-rh/path", test_path);
+	g_test_add_func ("/settings/plugins/ifcfg-rh/ignore", test_ignore);
 
-	base = g_path_get_basename (argv[0]);
-	fprintf (stdout, "%s: SUCCESS\n", base);
-	g_free (base);
-	return 0;
+	return g_test_run ();
 }
 
diff --git a/src/settings/plugins/ifcfg-rh/utils.c b/src/settings/plugins/ifcfg-rh/utils.c
index 4b7cb43d..a793288d 100644
--- a/src/settings/plugins/ifcfg-rh/utils.c
+++ b/src/settings/plugins/ifcfg-rh/utils.c
@@ -25,6 +25,9 @@
 #include <string.h>
 
 #include "nm-core-internal.h"
+#include "nm-macros-internal.h"
+#include "NetworkManagerUtils.h"
+#include "gsystem-local-alloc.h"
 
 #include "utils.h"
 #include "shvar.h"
@@ -149,41 +152,34 @@ check_suffix (const char *base, const char *tag)
 gboolean
 utils_should_ignore_file (const char *filename, gboolean only_ifcfg)
 {
-	char *base;
-	gboolean ignore = TRUE;
-	gboolean is_ifcfg = FALSE;
-	gboolean is_other = FALSE;
+	gs_free char *base = NULL;
 
 	g_return_val_if_fail (filename != NULL, TRUE);
 
 	base = g_path_get_basename (filename);
-	g_return_val_if_fail (base != NULL, TRUE);
 
 	/* Only handle ifcfg, keys, and routes files */
-	if (!strncmp (base, IFCFG_TAG, strlen (IFCFG_TAG)))
-		is_ifcfg = TRUE;
-
-	if (only_ifcfg == FALSE) {
-		if (   !strncmp (base, KEYS_TAG, strlen (KEYS_TAG))
-		    || !strncmp (base, ROUTE_TAG, strlen (ROUTE_TAG))
-		    || !strncmp (base, ROUTE6_TAG, strlen (ROUTE6_TAG)))
-				is_other = TRUE;
+	if (strncmp (base, IFCFG_TAG, strlen (IFCFG_TAG)) != 0) {
+		if (only_ifcfg)
+			return TRUE;
+		else if (   strncmp (base, KEYS_TAG, strlen (KEYS_TAG)) != 0
+		         && strncmp (base, ROUTE_TAG, strlen (ROUTE_TAG)) != 0
+		         && strncmp (base, ROUTE6_TAG, strlen (ROUTE6_TAG)) != 0)
+			return TRUE;
 	}
 
 	/* But not those that have certain suffixes */
-	if (   (is_ifcfg || is_other)
-	    && !check_suffix (base, BAK_TAG)
-	    && !check_suffix (base, TILDE_TAG)
-	    && !check_suffix (base, ORIG_TAG)
-	    && !check_suffix (base, REJ_TAG)
-	    && !check_suffix (base, RPMNEW_TAG)
-	    && !check_suffix (base, AUGNEW_TAG)
-	    && !check_suffix (base, AUGTMP_TAG)
-	    && !check_rpm_temp_suffix (base))
-		ignore = FALSE;
-
-	g_free (base);
-	return ignore;
+	if (   check_suffix (base, BAK_TAG)
+	    || check_suffix (base, TILDE_TAG)
+	    || check_suffix (base, ORIG_TAG)
+	    || check_suffix (base, REJ_TAG)
+	    || check_suffix (base, RPMNEW_TAG)
+	    || check_suffix (base, AUGNEW_TAG)
+	    || check_suffix (base, AUGTMP_TAG)
+	    || check_rpm_temp_suffix (base))
+		return TRUE;
+
+	return FALSE;
 }
 
 char *
@@ -205,34 +201,43 @@ utils_cert_path (const char *parent, const char *suffix)
 const char *
 utils_get_ifcfg_name (const char *file, gboolean only_ifcfg)
 {
-	const char *name = NULL, *start = NULL;
-	char *base;
+	const char *name;
 
 	g_return_val_if_fail (file != NULL, NULL);
 
-	base = g_path_get_basename (file);
-	if (!base)
+	name = strrchr (file, '/');
+	if (!name)
+		name = file;
+	else
+		name++;
+	if (!*name)
 		return NULL;
 
-	/* Find the point in 'file' where 'base' starts.  We use 'file' since it's
-	 * const and thus will survive after we free 'base'.
-	 */
-	start = file + strlen (file) - strlen (base);
-	g_assert (strcmp (start, base) == 0);
-	g_free (base);
-
-	if (!strncmp (start, IFCFG_TAG, strlen (IFCFG_TAG)))
-		name = start + strlen (IFCFG_TAG);
-	else if (only_ifcfg == FALSE)  {
-		if (!strncmp (start, KEYS_TAG, strlen (KEYS_TAG)))
-			name = start + strlen (KEYS_TAG);
-		else if (!strncmp (start, ROUTE_TAG, strlen (ROUTE_TAG)))
-			name = start + strlen (ROUTE_TAG);
-		else if (!strncmp (start, ROUTE6_TAG, strlen (ROUTE6_TAG)))
-			name = start + strlen (ROUTE6_TAG);
+#define MATCH_TAG_AND_RETURN(name, TAG) \
+	G_STMT_START { \
+		if (strncmp (name, TAG, STRLEN (TAG)) == 0) { \
+			name += STRLEN (TAG); \
+			if (name[0] == '\0') \
+				return NULL; \
+			else \
+				return name; \
+		} \
+	} G_STMT_END
+
+	/* Do not detect alias files and return 'eth0:0' instead of 'eth0'.
+	 * Unfortunately, we cannot be sure that our files don't contain colons,
+	 * so we cannot reject files with colons.
+	 *
+	 * Instead, you must not call utils_get_ifcfg_name() with an alias file
+	 * or files that are ignored. */
+	MATCH_TAG_AND_RETURN (name, IFCFG_TAG);
+	if (!only_ifcfg) {
+		MATCH_TAG_AND_RETURN (name, KEYS_TAG);
+		MATCH_TAG_AND_RETURN (name, ROUTE_TAG);
+		MATCH_TAG_AND_RETURN (name, ROUTE6_TAG);
 	}
 
-	return name;
+	return NULL;
 }
 
 /* Used to get any ifcfg/extra file path from any other ifcfg/extra path
@@ -420,26 +425,43 @@ utils_is_ifcfg_alias_file (const char *alias, const char *ifcfg)
 }
 
 char *
-utils_get_ifcfg_from_alias (const char *alias)
+utils_detect_ifcfg_path (const char *path, gboolean only_ifcfg)
 {
-	char *base, *ptr, *ifcfg = NULL;
+	gs_free char *base = NULL;
+	char *ptr, *ifcfg = NULL;
 
-	g_return_val_if_fail (alias != NULL, NULL);
+	g_return_val_if_fail (path != NULL, NULL);
 
-	base = g_path_get_basename (alias);
-	g_return_val_if_fail (base != NULL, NULL);
+	if (utils_should_ignore_file (path, only_ifcfg))
+		return NULL;
 
-	if (utils_is_ifcfg_alias_file (base, NULL)) {
-		ifcfg = g_strdup (alias);
-		ptr = strrchr (ifcfg, ':');
-		if (ptr)
-			*ptr = '\0';
-		else {
+	base = g_path_get_basename (path);
+
+	if (strncmp (base, IFCFG_TAG, STRLEN (IFCFG_TAG)) == 0) {
+		if (base[STRLEN (IFCFG_TAG)] == '\0')
+			return NULL;
+		if (utils_is_ifcfg_alias_file (base, NULL)) {
+			ifcfg = g_strdup (path);
+			ptr = strrchr (ifcfg, ':');
+			if (ptr && ptr > ifcfg) {
+				*ptr = '\0';
+				if (g_file_test (ifcfg, G_FILE_TEST_EXISTS)) {
+					/* the file has a colon, so it is probably an alias.
+					 * To be ~more~ certain that this is an alias file,
+					 * check whether a corresponding base file exists. */
+					if (only_ifcfg) {
+						g_free (ifcfg);
+						return NULL;
+					}
+					return ifcfg;
+				}
+			}
 			g_free (ifcfg);
-			ifcfg = NULL;
 		}
+		return g_strdup (path);
 	}
 
-	g_free (base);
-	return ifcfg;
+	if (only_ifcfg)
+		return NULL;
+	return utils_get_ifcfg_path (path);
 }
diff --git a/src/settings/plugins/ifcfg-rh/utils.h b/src/settings/plugins/ifcfg-rh/utils.h
index 445437c4..547bfcb2 100644
--- a/src/settings/plugins/ifcfg-rh/utils.h
+++ b/src/settings/plugins/ifcfg-rh/utils.h
@@ -59,7 +59,8 @@ gboolean utils_has_complex_routes (const char *filename);
 gboolean utils_ignore_ip_config (NMConnection *connection);
 
 gboolean utils_is_ifcfg_alias_file (const char *alias, const char *ifcfg);
-char *utils_get_ifcfg_from_alias (const char *alias);
+
+char *utils_detect_ifcfg_path (const char *path, gboolean only_ifcfg);
 
 #endif  /* _UTILS_H_ */
 
diff --git a/src/settings/plugins/ifcfg-rh/writer.c b/src/settings/plugins/ifcfg-rh/writer.c
index d2b0c970..52bf51d1 100644
--- a/src/settings/plugins/ifcfg-rh/writer.c
+++ b/src/settings/plugins/ifcfg-rh/writer.c
@@ -888,20 +888,22 @@ write_wireless_setting (NMConnection *connection,
 		svSetValue (ifcfg, "ESSID", str->str, TRUE);
 		g_string_free (str, TRUE);
 	} else {
+		const char *tmp_escaped;
+
 		/* Printable SSIDs always get quoted */
 		memset (buf, 0, sizeof (buf));
 		memcpy (buf, ssid_data, ssid_len);
-		tmp = svEscape (buf);
+		tmp_escaped = svEscape (buf, &tmp);
 
 		/* svEscape will usually quote the string, but just for consistency,
 		 * if svEscape doesn't quote the ESSID, we quote it ourselves.
 		 */
-		if (tmp[0] != '"' && tmp[strlen (tmp) - 1] != '"') {
-			tmp2 = g_strdup_printf ("\"%s\"", tmp);
+		if (tmp_escaped[0] != '"' && tmp_escaped[strlen (tmp_escaped) - 1] != '"') {
+			tmp2 = g_strdup_printf ("\"%s\"", tmp_escaped);
 			svSetValue (ifcfg, "ESSID", tmp2, TRUE);
 			g_free (tmp2);
 		} else
-			svSetValue (ifcfg, "ESSID", tmp, TRUE);
+			svSetValue (ifcfg, "ESSID", tmp_escaped, TRUE);
 		g_free (tmp);
 	}
 
@@ -1665,7 +1667,7 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 {
 	guint32 n, i;
 	GString *str;
-	const char *master;
+	const char *master, *type;
 	char *tmp;
 	gint i_int;
 
@@ -1682,6 +1684,20 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	svSetValue (ifcfg, "AUTOCONNECT_PRIORITY", tmp, FALSE);
 	g_free (tmp);
 
+	/* Only save the value for master connections */
+	svSetValue (ifcfg, "AUTOCONNECT_SLAVES", NULL, FALSE);
+	type = nm_setting_connection_get_connection_type (s_con);
+	if (   !g_strcmp0 (type, NM_SETTING_BOND_SETTING_NAME)
+	    || !g_strcmp0 (type, NM_SETTING_TEAM_SETTING_NAME)
+	    || !g_strcmp0 (type, NM_SETTING_BRIDGE_SETTING_NAME)) {
+		NMSettingConnectionAutoconnectSlaves autoconnect_slaves;
+		autoconnect_slaves = nm_setting_connection_get_autoconnect_slaves (s_con);
+		svSetValue (ifcfg, "AUTOCONNECT_SLAVES",
+		            autoconnect_slaves == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES ? "yes" :
+		            autoconnect_slaves == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO ? "no" : NULL,
+		            FALSE);
+	}
+
 	/* Permissions */
 	svSetValue (ifcfg, "USERS", NULL, FALSE);
 	n = nm_setting_connection_get_num_permissions (s_con);
@@ -2448,7 +2464,7 @@ escape_id (const char *id)
 	while (*p) {
 		if (*p == ' ')
 			*p = '_';
-		else if (strchr ("\\][|/=()!", *p))
+		else if (strchr ("\\][|/=()!:", *p))
 			*p = '-';
 		p++;
 	}