about summary refs log tree commit diff
path: root/clients/cli
diff options
context:
space:
mode:
Diffstat (limited to 'clients/cli')
-rw-r--r--clients/cli/connections.c256
-rw-r--r--clients/cli/devices.c1
-rw-r--r--clients/cli/general.c4
-rw-r--r--clients/cli/meson.build2
-rw-r--r--clients/cli/settings.c261
-rw-r--r--clients/cli/settings.h13
-rw-r--r--clients/cli/utils.c3
7 files changed, 199 insertions, 341 deletions
diff --git a/clients/cli/connections.c b/clients/cli/connections.c
index 6db44f87..6ee3b49f 100644
--- a/clients/cli/connections.c
+++ b/clients/cli/connections.c
@@ -1276,12 +1276,18 @@ static void
 update_secrets_in_connection (NMRemoteConnection *remote, NMConnection *local)
 {
 	GetSecretsData data = { 0, };
+	GType setting_type;
 	int i;
 
 	data.local = local;
 	data.loop = g_main_loop_new (NULL, FALSE);
 
 	for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) {
+		setting_type = nm_meta_setting_infos[i].get_setting_gtype();
+		if (!nm_connection_get_setting (NM_CONNECTION (remote), setting_type))
+			continue;
+		if (!nm_meta_setting_info_editor_has_secrets (nm_meta_setting_info_editor_find_by_gtype (setting_type)))
+			continue;
 		data.setting_name = nm_meta_setting_infos[i].setting_name;
 		nm_remote_connection_get_secrets_async (remote,
 		                                        nm_meta_setting_infos[i].setting_name,
@@ -1522,15 +1528,13 @@ split_required_fields_for_con_show (const char *input,
                                     char **active_flds,
                                     GError **error)
 {
-	char **fields, **iter;
-	char *dot;
-	GString *str1, *str2;
-	gboolean found;
+	gs_free const char **fields = NULL;
+	const char *const*iter;
+	nm_auto_free_gstring GString *str1 = NULL;
+	nm_auto_free_gstring GString *str2 = NULL;
 	gboolean group_profile = FALSE;
 	gboolean group_active = FALSE;
-	gboolean success = TRUE;
-	gboolean is_all, is_common;
-	int i;
+	gboolean do_free;
 
 	if (!input) {
 		*profile_flds = NULL;
@@ -1541,25 +1545,30 @@ split_required_fields_for_con_show (const char *input,
 	str1 = g_string_new (NULL);
 	str2 = g_string_new (NULL);
 
-	/* Split supplied fields string */
-	fields = g_strsplit_set (input, ",", -1);
+	fields = nm_utils_strsplit_set_with_empty (input, ",");
 	for (iter = fields; iter && *iter; iter++) {
-		g_strstrip (*iter);
-		dot = strchr (*iter, '.');
+		char *s_mutable = (char *) (*iter);
+		char *dot;
+		gboolean is_all;
+		gboolean is_common;
+		gboolean found;
+		int i;
+
+		g_strstrip (s_mutable);
+		dot = strchr (s_mutable, '.');
 		if (dot)
 			*dot = '\0';
 
-		is_all = !dot && strcasecmp (*iter, "all") == 0;
-		is_common = !dot && strcasecmp (*iter, "common") == 0;
+		is_all = !dot && strcasecmp (s_mutable, "all") == 0;
+		is_common = !dot && strcasecmp (s_mutable, "common") == 0;
 
 		found = FALSE;
-
 		for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) {
 			if (   is_all || is_common
-			    || !strcasecmp (*iter, nm_meta_setting_infos[i].setting_name)) {
+			    || !strcasecmp (s_mutable, nm_meta_setting_infos[i].setting_name)) {
 				if (dot)
 					*dot = '.';
-				g_string_append (str1, *iter);
+				g_string_append (str1, s_mutable);
 				g_string_append_c (str1, ',');
 				found = TRUE;
 				break;
@@ -1567,12 +1576,13 @@ split_required_fields_for_con_show (const char *input,
 		}
 		if (found)
 			continue;
+
 		for (i = 0; nmc_fields_con_active_details_groups[i]; i++) {
 			if (   is_all || is_common
-			    || !strcasecmp (*iter, nmc_fields_con_active_details_groups[i]->name)) {
+			    || !strcasecmp (s_mutable, nmc_fields_con_active_details_groups[i]->name)) {
 				if (dot)
 					*dot = '.';
-				g_string_append (str2, *iter);
+				g_string_append (str2, s_mutable);
 				g_string_append_c (str2, ',');
 				found = TRUE;
 				break;
@@ -1581,55 +1591,49 @@ split_required_fields_for_con_show (const char *input,
 		if (!found) {
 			if (dot)
 				*dot = '.';
-			if (!strcasecmp (*iter, CON_SHOW_DETAIL_GROUP_PROFILE))
+			if (!strcasecmp (s_mutable, CON_SHOW_DETAIL_GROUP_PROFILE))
 				group_profile = TRUE;
-			else if (!strcasecmp (*iter, CON_SHOW_DETAIL_GROUP_ACTIVE))
+			else if (!strcasecmp (s_mutable, CON_SHOW_DETAIL_GROUP_ACTIVE))
 				group_active = TRUE;
 			else {
-				char *allowed1 = nm_meta_abstract_infos_get_names_str ((const NMMetaAbstractInfo *const*) nm_meta_setting_infos_editor_p (), NULL);
-				char *allowed2 = nm_meta_abstract_infos_get_names_str ((const NMMetaAbstractInfo *const*) nmc_fields_con_active_details_groups, NULL);
+				gs_free char *allowed1 = nm_meta_abstract_infos_get_names_str ((const NMMetaAbstractInfo *const*) nm_meta_setting_infos_editor_p (), NULL);
+				gs_free char *allowed2 = nm_meta_abstract_infos_get_names_str ((const NMMetaAbstractInfo *const*) nmc_fields_con_active_details_groups, NULL);
+
 				g_set_error (error, NMCLI_ERROR, 0, _("invalid field '%s'; allowed fields: %s and %s, or %s,%s"),
-				             *iter, allowed1, allowed2, CON_SHOW_DETAIL_GROUP_PROFILE, CON_SHOW_DETAIL_GROUP_ACTIVE);
-				g_free (allowed1);
-				g_free (allowed2);
-				success = FALSE;
-				break;
+				             s_mutable, allowed1, allowed2, CON_SHOW_DETAIL_GROUP_PROFILE, CON_SHOW_DETAIL_GROUP_ACTIVE);
+				return FALSE;
 			}
 		}
 	}
-	if (fields)
-		g_strfreev (fields);
 
 	/* Handle pseudo groups: profile, active */
-	if (success && group_profile) {
+	if (group_profile) {
 		if (str1->len > 0) {
 			g_set_error (error, NMCLI_ERROR, 0, _("'%s' has to be alone"),
 			             CON_SHOW_DETAIL_GROUP_PROFILE);
-			success = FALSE;
-		} else
-			g_string_assign (str1, "all,");
+			return FALSE;
+		}
+		g_string_assign (str1, "all,");
 	}
-	if (success && group_active) {
+	if (group_active) {
 		if (str2->len > 0) {
 			g_set_error (error, NMCLI_ERROR, 0, _("'%s' has to be alone"),
 			             CON_SHOW_DETAIL_GROUP_ACTIVE);
-			success = FALSE;
-		} else
-			g_string_assign (str2, "all,");
+			return FALSE;
+		}
+		g_string_assign (str2, "all,");
 	}
 
-	if (success) {
-		if (str1->len > 0)
-			g_string_truncate (str1, str1->len - 1);
-		if (str2->len > 0)
-			g_string_truncate (str2, str2->len - 1);
-		*profile_flds = g_string_free (str1, str1->len == 0);
-		*active_flds = g_string_free (str2, str2->len == 0);
-	} else {
-		g_string_free (str1, TRUE);
-		g_string_free (str2, TRUE);
-	}
-	return success;
+	if (str1->len > 0)
+		g_string_truncate (str1, str1->len - 1);
+	if (str2->len > 0)
+		g_string_truncate (str2, str2->len - 1);
+
+	do_free = (str1->len == 0);
+	*profile_flds = g_string_free (g_steal_pointer (&str1), do_free);
+	do_free = (str2->len == 0);
+	*active_flds = g_string_free (g_steal_pointer (&str2), do_free);
+	return TRUE;
 }
 
 typedef enum {
@@ -1870,7 +1874,7 @@ parse_preferred_connection_order (const char *order, GError **error)
 	gboolean inverse, unique;
 	int i;
 
-	strv = nm_utils_strsplit_set (order, ":", FALSE);
+	strv = nm_utils_strsplit_set (order, ":");
 	if (!strv) {
 		g_set_error (error, NMCLI_ERROR, 0,
 		             _("incorrect string '%s' of '--order' option"), order);
@@ -2674,7 +2678,7 @@ parse_passwords (const char *passwd_file, GError **error)
 		return NULL;
 	}
 
-	strv = nm_utils_strsplit_set (contents, "\r\n", FALSE);
+	strv = nm_utils_strsplit_set (contents, "\r\n");
 	for (iter = strv; *iter; iter++) {
 		gs_free char *iter_s = g_strdup (*iter);
 
@@ -3959,11 +3963,12 @@ set_property (NMClient *client,
               char modifier,
               GError **error)
 {
-	gs_free char *property_name = NULL, *value_free = NULL;
+	gs_free char *property_name = NULL;
+	gs_free_error GError *local = NULL;
 	NMSetting *setting;
-	GError *local = NULL;
 
-	g_assert (setting_name && setting_name[0]);
+	nm_assert (setting_name && setting_name[0]);
+	nm_assert (NM_IN_SET (modifier, '\0', '+', '-'));
 
 	setting = nm_connection_get_setting_by_name (connection, setting_name);
 	if (!setting) {
@@ -3977,48 +3982,26 @@ set_property (NMClient *client,
 		g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT,
 		             _("Error: invalid property '%s': %s."),
 		             property, local->message);
-		g_clear_error (&local);
 		return FALSE;
 	}
 
-	if (modifier != '-') {
-		/* Set/add value */
-		if (modifier != '+') {
-			/* We allow the existing property value to be passed as parameter,
-			 * so make a copy if we are going to free it.
-			 */
-			value = value_free = g_strdup (value);
-			nmc_setting_reset_property (setting, property_name, NULL);
-		}
-		if (!nmc_setting_set_property (client, setting, property_name, value, &local)) {
-			g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT,
-			             _("Error: failed to modify %s.%s: %s."),
-			             setting_name, property, local->message);
-			g_clear_error (&local);
-			return FALSE;
-		}
-	} else {
-		/* Remove value
-		 * - either empty: remove whole value
-		 * - or specified by index <0-n>: remove item at the index
-		 * - or option name: remove item with the option name
-		 */
-		if (value) {
-			unsigned long idx;
-
-			if (nmc_string_to_uint (value, TRUE, 0, G_MAXUINT32, &idx))
-				nmc_setting_remove_property_option (setting, property_name, NULL, idx, &local);
-			else
-				nmc_setting_remove_property_option (setting, property_name, value, 0, &local);
-			if (local) {
-				g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT,
-				             _("Error: failed to remove a value from %s.%s: %s."),
-				             setting_name, property,  local->message);
-				g_clear_error (&local);
-				return FALSE;
-			}
-		} else
-			nmc_setting_reset_property (setting, property_name, NULL);
+	if (!nmc_setting_set_property (client,
+	                               setting,
+	                               property_name,
+	                               (  (modifier == '-' && !value)
+	                                ? '\0'
+	                                : modifier),
+	                               value,
+	                               &local)) {
+		g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT,
+		             _("Error: failed to %s %s.%s: %s."),
+		             (  modifier != '-'
+		              ? "modify"
+		              : "remove a value from"),
+		             setting_name,
+		             property,
+		             local->message);
+		return FALSE;
 	}
 
 	/* Don't ask for this property in interactive mode. */
@@ -6904,7 +6887,6 @@ property_edit_submenu (NmCli *nmc,
 		gs_free char *cmd_property_user = NULL;
 		gs_free char *cmd_property_arg = NULL;
 		gs_free char *prop_val_user = NULL;
-		nm_auto_unset_gvalue GValue prop_g_value = G_VALUE_INIT;
 		gboolean removed;
 		gboolean dirty;
 
@@ -6954,24 +6936,17 @@ property_edit_submenu (NmCli *nmc,
 			} else
 				prop_val_user = g_strdup (cmd_property_arg);
 
-			/* nmc_setting_set_property() only adds new value, thus we have to
-			 * remove the original value and save it for error cases.
-			 */
-			if (cmdsub == NMC_EDITOR_SUB_CMD_SET) {
-				nmc_property_get_gvalue (curr_setting, prop_name, &prop_g_value);
-				nmc_property_set_default_value (curr_setting, prop_name);
-			}
-
-			set_result = nmc_setting_set_property (nmc->client, curr_setting, prop_name, prop_val_user, &tmp_err);
+			set_result = nmc_setting_set_property (nmc->client,
+			                                       curr_setting,
+			                                       prop_name,
+			                                         (cmdsub == NMC_EDITOR_SUB_CMD_SET)
+			                                       ? '\0'
+			                                       : '+',
+			                                       prop_val_user,
+			                                       &tmp_err);
 			if (!set_result) {
 				g_print (_("Error: failed to set '%s' property: %s\n"), prop_name, tmp_err->message);
 				g_clear_error (&tmp_err);
-				if (cmdsub == NMC_EDITOR_SUB_CMD_SET) {
-					/* Block change signals and restore original value */
-					g_signal_handlers_block_matched (curr_setting, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, NULL);
-					nmc_property_set_gvalue (curr_setting, prop_name, &prop_g_value);
-					g_signal_handlers_unblock_matched (curr_setting, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, NULL);
-				}
 			}
 			break;
 
@@ -6982,41 +6957,23 @@ property_edit_submenu (NmCli *nmc,
 			                              _("Edit '%s' value: "),
 			                              prop_name);
 
-			nmc_property_get_gvalue (curr_setting, prop_name, &prop_g_value);
-			nmc_property_set_default_value (curr_setting, prop_name);
-
-			if (!nmc_setting_set_property (nmc->client, curr_setting, prop_name, prop_val_user, &tmp_err)) {
+			if (!nmc_setting_set_property (nmc->client, curr_setting, prop_name, '\0', prop_val_user, &tmp_err)) {
 				g_print (_("Error: failed to set '%s' property: %s\n"), prop_name, tmp_err->message);
 				g_clear_error (&tmp_err);
-				g_signal_handlers_block_matched (curr_setting, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, NULL);
-				nmc_property_set_gvalue (curr_setting, prop_name, &prop_g_value);
-				g_signal_handlers_unblock_matched (curr_setting, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, NULL);
 			}
 			break;
 
 		case NMC_EDITOR_SUB_CMD_REMOVE:
-			if (cmd_property_arg) {
-				unsigned long val_int = G_MAXUINT32;
-				gs_free char *option = NULL;
-
-				if (!nmc_string_to_uint (cmd_property_arg, TRUE, 0, G_MAXUINT32, &val_int)) {
-					option = g_strdup (cmd_property_arg);
-					g_strstrip (option);
-				}
-
-				if (!nmc_setting_remove_property_option (curr_setting, prop_name,
-				                                         option,
-				                                         (guint32) val_int,
-				                                         &tmp_err)) {
-					g_print (_("Error: %s\n"), tmp_err->message);
-					g_clear_error (&tmp_err);
-				}
-			} else {
-				if (!nmc_setting_reset_property (curr_setting, prop_name, &tmp_err)) {
-					g_print (_("Error: failed to remove value of '%s': %s\n"), prop_name,
-					         tmp_err->message);
-					g_clear_error (&tmp_err);
-				}
+			if (!nmc_setting_set_property (nmc->client,
+			                               curr_setting,
+			                               prop_name,
+			                               (  cmd_property_arg
+			                                ? '-'
+			                                : '\0'),
+			                               cmd_property_arg,
+			                               &tmp_err)) {
+				g_print (_("Error: %s\n"), tmp_err->message);
+				g_clear_error (&tmp_err);
 			}
 			break;
 
@@ -7367,8 +7324,7 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
 					                              _("Enter '%s' value: "),
 					                              prop_name);
 
-					/* Set property value */
-					if (!nmc_setting_set_property (nmc->client, menu_ctx.curr_setting, prop_name, prop_val_user, &tmp_err)) {
+					if (!nmc_setting_set_property (nmc->client, menu_ctx.curr_setting, prop_name, '+', prop_val_user, &tmp_err)) {
 						g_print (_("Error: failed to set '%s' property: %s\n"), prop_name, tmp_err->message);
 						g_clear_error (&tmp_err);
 					}
@@ -7428,8 +7384,13 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
 					                          prop_name);
 				}
 
-				/* Set property value */
-				if (!nmc_setting_set_property (nmc->client, ss, prop_name, cmd_arg_v, &tmp_err)) {
+				/* setting a value in edit mode "appends". That seems unexpected behavior. */
+				if (!nmc_setting_set_property (nmc->client,
+				                               ss,
+				                               prop_name,
+				                               cmd_arg_v ? '+' : '\0',
+				                               cmd_arg_v,
+				                               &tmp_err)) {
 					g_print (_("Error: failed to set '%s' property: %s\n"),
 					         prop_name, tmp_err->message);
 					g_clear_error (&tmp_err);
@@ -7526,8 +7487,7 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
 					if (!prop_name)
 						break;
 
-					/* Delete property value */
-					if (!nmc_setting_reset_property (menu_ctx.curr_setting, prop_name, &tmp_err)) {
+					if (!nmc_setting_set_property (nmc->client, menu_ctx.curr_setting, prop_name, '\0', NULL, &tmp_err)) {
 						g_print (_("Error: failed to remove value of '%s': %s\n"), prop_name,
 						         tmp_err->message);
 						g_clear_error (&tmp_err);
@@ -7577,8 +7537,7 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
 
 					prop_name = is_property_valid (ss, cmd_arg_p, &tmp_err);
 					if (prop_name) {
-						/* Delete property value */
-						if (!nmc_setting_reset_property (ss, prop_name, &tmp_err)) {
+						if (!nmc_setting_set_property (nmc->client, ss, prop_name, '\0', NULL, &tmp_err)) {
 							g_print (_("Error: failed to remove value of '%s': %s\n"), prop_name,
 							         tmp_err->message);
 							g_clear_error (&tmp_err);
@@ -8148,6 +8107,9 @@ editor_init_existing_connection (NMConnection *connection)
 	NMSettingWireless *s_wireless;
 	NMSettingConnection *s_con;
 
+	/* FIXME: this approach of connecting handlers to do something is fundamentally
+	 * flawed. See the comment in nmc_setting_ip6_connect_handlers(). */
+
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	s_proxy = nm_connection_get_setting_proxy (connection);
@@ -8585,7 +8547,7 @@ delete_cb (GObject *con, GAsyncResult *result, gpointer user_data)
 		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 			return;
 		g_string_printf (info->nmc->return_text, _("Error: not all connections deleted."));
-		g_printerr (_("Error: Connection deletion failed: %s"),
+		g_printerr (_("Error: Connection deletion failed: %s\n"),
 		            error->message);
 		g_error_free (error);
 		info->nmc->return_value = NMC_RESULT_ERROR_CON_DEL;
diff --git a/clients/cli/devices.c b/clients/cli/devices.c
index 04a8c988..ad3a44c4 100644
--- a/clients/cli/devices.c
+++ b/clients/cli/devices.c
@@ -3888,6 +3888,7 @@ do_device_wifi_hotspot (NmCli *nmc, int argc, char **argv)
 	info->nmc = nmc;
 	info->device = device;
 	info->hotspot = TRUE;
+	info->create = TRUE;
 
 	nm_client_add_and_activate_connection_async (nmc->client,
 	                                             connection,
diff --git a/clients/cli/general.c b/clients/cli/general.c
index 2c22bdc9..d713426b 100644
--- a/clients/cli/general.c
+++ b/clients/cli/general.c
@@ -23,7 +23,7 @@
 
 #include <stdlib.h>
 
-#include "nm-common-macros.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
 
 #include "nm-client-utils.h"
 
@@ -1350,7 +1350,7 @@ do_overview (NmCli *nmc, int argc, char **argv)
 	g_print (_("Use \"nmcli device show\" to get complete information about known devices and\n"
 	           "\"nmcli connection show\" to get an overview on active connection profiles.\n"
 	           "\n"
-	           "Consult nmcli(1) and nmcli-examples(5) manual pages for complete usage details.\n"));
+	           "Consult nmcli(1) and nmcli-examples(7) manual pages for complete usage details.\n"));
 
 	return NMC_RESULT_SUCCESS;
 }
diff --git a/clients/cli/meson.build b/clients/cli/meson.build
index 11fe1cd1..396466a2 100644
--- a/clients/cli/meson.build
+++ b/clients/cli/meson.build
@@ -22,7 +22,7 @@ deps = [
   libnm_dep,
   libnmc_base_dep,
   libnmc_dep,
-  nm_core_dep,
+  libnm_core_dep,
   readline_dep,
 ]
 
diff --git a/clients/cli/settings.c b/clients/cli/settings.c
index a04c8eb6..2446cb08 100644
--- a/clients/cli/settings.c
+++ b/clients/cli/settings.c
@@ -24,7 +24,7 @@
 #include <stdlib.h>
 #include <arpa/inet.h>
 
-#include "nm-common-macros.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
 
 #include "nm-client-utils.h"
 #include "nm-vpn-helpers.h"
@@ -89,7 +89,7 @@ ipv4_addresses_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_dat
 static void
 ipv4_method_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data)
 {
-	static GValue value = G_VALUE_INIT;
+	static GPtrArray *old_value = NULL;
 	static gboolean answered = FALSE;
 	static gboolean answer = FALSE;
 
@@ -103,17 +103,17 @@ ipv4_method_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data)
 				answer = get_answer ("ipv4.addresses", NULL);
 			}
 			if (answer) {
-				if (G_IS_VALUE (&value))
-					g_value_unset (&value);
-				nmc_property_get_gvalue (NM_SETTING (object), NM_SETTING_IP_CONFIG_ADDRESSES, &value);
+				nm_clear_pointer (&old_value, g_ptr_array_unref);
+				g_object_get (object, NM_SETTING_IP_CONFIG_ADDRESSES, &old_value, NULL);
 				g_object_set (object, NM_SETTING_IP_CONFIG_ADDRESSES, NULL, NULL);
 			}
 		}
 	} else {
 		answered = FALSE;
-		if (G_IS_VALUE (&value)) {
-			nmc_property_set_gvalue (NM_SETTING (object), NM_SETTING_IP_CONFIG_ADDRESSES, &value);
-			g_value_unset (&value);
+		if (old_value) {
+			gs_unref_ptrarray GPtrArray *v = g_steal_pointer (&old_value);
+
+			g_object_set (object, NM_SETTING_IP_CONFIG_ADDRESSES, v, NULL);
 		}
 	}
 
@@ -142,6 +142,25 @@ ipv6_addresses_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_dat
 		}
 	} else {
 		answered = FALSE;
+		/* FIXME: editor_init_existing_connection() and registering handlers is not the
+		 *  right approach.
+		 *
+		 * This only happens to work because in nmcli's edit mode
+		 * tends to append addresses -- instead of setting them.
+		 * If we would change that (to behavior I'd expect), we'd get:
+		 *
+		 *   nmcli> set ipv6.addresses fc01::1:5/68
+		 *   Do you also want to set 'ipv6.method' to 'manual'? [yes]: y
+		 *   nmcli> set ipv6.addresses fc01::1:6/68
+		 *   Do you also want to set 'ipv6.method' to 'manual'? [yes]:
+		 *
+		 * That's because nmc_setting_set_property() calls set_fcn(). With modifier '\0'
+		 * (set), it would first clear all addresses before adding the address. Thereby
+		 * emitting multiple property changed signals.
+		 *
+		 * That can be avoided by freezing/thawing the signals, but this solution
+		 * here is ugly in general.
+		 */
 		if (!g_strcmp0 (nm_setting_ip_config_get_method (NM_SETTING_IP_CONFIG (object)), NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
 			g_object_set (object, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
 	}
@@ -152,7 +171,7 @@ ipv6_addresses_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_dat
 static void
 ipv6_method_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data)
 {
-	static GValue value = G_VALUE_INIT;
+	static GPtrArray *old_value = NULL;
 	static gboolean answered = FALSE;
 	static gboolean answer = FALSE;
 
@@ -166,17 +185,17 @@ ipv6_method_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data)
 				answer = get_answer ("ipv6.addresses", NULL);
 			}
 			if (answer) {
-				if (G_IS_VALUE (&value))
-					g_value_unset (&value);
-				nmc_property_get_gvalue (NM_SETTING (object), NM_SETTING_IP_CONFIG_ADDRESSES, &value);
+				nm_clear_pointer (&old_value, g_ptr_array_unref);
+				g_object_get (object, NM_SETTING_IP_CONFIG_ADDRESSES, &old_value, NULL);
 				g_object_set (object, NM_SETTING_IP_CONFIG_ADDRESSES, NULL, NULL);
 			}
 		}
 	} else {
 		answered = FALSE;
-		if (G_IS_VALUE (&value)) {
-			nmc_property_set_gvalue (NM_SETTING (object), NM_SETTING_IP_CONFIG_ADDRESSES, &value);
-			g_value_unset (&value);
+		if (old_value) {
+			gs_unref_ptrarray GPtrArray *v = g_steal_pointer (&old_value);
+
+			g_object_set (object, NM_SETTING_IP_CONFIG_ADDRESSES, v, NULL);
 		}
 	}
 
@@ -319,7 +338,7 @@ _set_fcn_precheck_connection_secondaries (NMClient *client,
 	char **iter;
 	gboolean modified = FALSE;
 
-	strv0 = nm_utils_strsplit_set (value, " \t,", FALSE);
+	strv0 = nm_utils_strsplit_set (value, " \t,");
 	if (!strv0)
 		return TRUE;
 
@@ -509,152 +528,73 @@ nmc_setting_get_property_parsable (NMSetting *setting, const char *prop, GError
 	return get_property_val (setting, prop, NM_META_ACCESSOR_GET_TYPE_PARSABLE, TRUE, error);
 }
 
-static gboolean
-_set_fcn_call (const NMMetaPropertyInfo *property_info,
-               NMSetting *setting,
-               const char *value,
-               GError **error)
-{
-	return property_info->property_type->set_fcn (property_info,
-	                                              nmc_meta_environment,
-	                                              nmc_meta_environment_arg,
-	                                              setting,
-	                                              value,
-	                                              error);
-}
-
-/*
- * Generic function for setting property value.
- *
- * Sets property=value in setting by calling specialized functions.
- * If value is NULL then default property value is set.
- *
- * Returns: TRUE on success; FALSE on failure and sets error
- */
 gboolean
-nmc_setting_set_property (NMClient *client, NMSetting *setting, const char *prop, const char *value, GError **error)
+nmc_setting_set_property (NMClient *client,
+                          NMSetting *setting,
+                          const char *prop,
+                          char modifier,
+                          const char *value,
+                          GError **error)
 {
 	const NMMetaPropertyInfo *property_info;
+	gs_free char *value_to_free = NULL;
+	gboolean success;
 
 	g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	g_return_val_if_fail (NM_IN_SET (modifier, '\0', '-', '+'), FALSE);
 
-	if ((property_info = nm_meta_property_info_find_by_setting (setting, prop))) {
-
-		if (!value) {
-			/* No value argument sets default value */
-			nmc_property_set_default_value (setting, prop);
-			return TRUE;
-		}
-
-		if (property_info->property_type->set_fcn) {
-			switch (property_info->setting_info->general->meta_type) {
-			case NM_META_SETTING_TYPE_CONNECTION:
-				if (nm_streq (property_info->property_name, NM_SETTING_CONNECTION_SECONDARIES)) {
-					gs_free char *value_coerced = NULL;
-
-					if (!_set_fcn_precheck_connection_secondaries (client, value, &value_coerced, error))
-						return FALSE;
+	if (!(property_info = nm_meta_property_info_find_by_setting (setting, prop)))
+		goto out_fail_read_only;
+	if (!property_info->property_type->set_fcn)
+		goto out_fail_read_only;
 
-					return _set_fcn_call (property_info,
-					                      setting,
-					                      value_coerced ?: value,
-					                      error);
-				}
-				break;
-			default:
-				break;
-			}
-			return _set_fcn_call (property_info,
-			                      setting,
-			                      value,
-			                      error);
-		}
+	if (   NM_IN_SET (modifier, '+', '-')
+	    && !value) {
+		/* nothing to do. */
+		return TRUE;
 	}
 
-	g_set_error_literal (error, 1, 0, _("the property can't be changed"));
-	return FALSE;
-}
-
-void
-nmc_property_set_default_value (NMSetting *setting, const char *prop)
-{
-	GValue value = G_VALUE_INIT;
-	GParamSpec *param_spec;
-
-	param_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (setting)), prop);
-	if (param_spec) {
-		g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (param_spec));
-		g_param_value_set_default (param_spec, &value);
-		g_object_set_property (G_OBJECT (setting), prop, &value);
+	if (   modifier == '-'
+	    && !property_info->property_type->set_supports_remove) {
+		/* The property is a plain property. It does not support '-'.
+		 *
+		 * Maybe we should fail, but just return silently. */
+		return TRUE;
 	}
-}
-
-/*
- * Generic function for resetting (single value) properties.
- *
- * The function resets the property value to the default one. It respects
- * nmcli restrictions for changing properties. So if 'set_func' is NULL,
- * resetting the value is denied.
- *
- * Returns: TRUE on success; FALSE on failure and sets error
- */
-gboolean
-nmc_setting_reset_property (NMSetting *setting, const char *prop, GError **error)
-{
-	const NMMetaPropertyInfo *property_info;
-
-	g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
-	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 
-	if ((property_info = nm_meta_property_info_find_by_setting (setting, prop))) {
-		if (property_info->property_type->set_fcn) {
-			nmc_property_set_default_value (setting, prop);
-			return TRUE;
+	if (value) {
+		switch (property_info->setting_info->general->meta_type) {
+		case NM_META_SETTING_TYPE_CONNECTION:
+			if (nm_streq (property_info->property_name, NM_SETTING_CONNECTION_SECONDARIES)) {
+				if (!_set_fcn_precheck_connection_secondaries (client, value, &value_to_free, error))
+					return FALSE;
+				if (value_to_free)
+					value = value_to_free;
+			}
+			break;
+		default:
+			break;
 		}
 	}
 
-	g_set_error_literal (error, 1, 0, _("the property can't be changed"));
+	g_object_freeze_notify (G_OBJECT (setting));
+	success = property_info->property_type->set_fcn (property_info,
+	                                                 nmc_meta_environment,
+	                                                 nmc_meta_environment_arg,
+	                                                 setting,
+	                                                 modifier,
+	                                                 value,
+	                                                 error);
+	g_object_thaw_notify (G_OBJECT (setting));
+	return success;
+
+out_fail_read_only:
+	nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, _("the property can't be changed"));
 	return FALSE;
 }
 
 /*
- * Generic function for removing items for collection-type properties.
- *
- * If 'option' is not NULL, it tries to remove it, otherwise 'idx' is used.
- * For single-value properties (not having specialized remove function) this
- * function does nothing and just returns TRUE.
- *
- * Returns: TRUE on success; FALSE on failure and sets error
- */
-gboolean
-nmc_setting_remove_property_option (NMSetting *setting,
-                                    const char *prop,
-                                    const char *option,
-                                    guint32 idx,
-                                    GError **error)
-{
-	const NMMetaPropertyInfo *property_info;
-
-	g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
-	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
-	if ((property_info = nm_meta_property_info_find_by_setting (setting, prop))) {
-		if (property_info->property_type->remove_fcn) {
-			return property_info->property_type->remove_fcn (property_info,
-			                                                 nmc_meta_environment,
-			                                                 nmc_meta_environment_arg,
-			                                                 setting,
-			                                                 option,
-			                                                 idx,
-			                                                 error);
-		}
-	}
-
-	return TRUE;
-}
-
-/*
  * Get valid property names for a setting.
  *
  * Returns: string array with the properties or NULL on failure.
@@ -749,41 +689,6 @@ nmc_setting_get_property_desc (NMSetting *setting, const char *prop)
 	                        nmcli_desc ?: "");
 }
 
-/*
- * Gets setting:prop property value and returns it in 'value'.
- * Caller is responsible for freeing the GValue resources using g_value_unset()
- */
-gboolean
-nmc_property_get_gvalue (NMSetting *setting, const char *prop, GValue *value)
-{
-	GParamSpec *param_spec;
-
-	param_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (setting)), prop);
-	if (param_spec) {
-		memset (value, 0, sizeof (GValue));
-		g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (param_spec));
-		g_object_get_property (G_OBJECT (setting), prop, value);
-		return TRUE;
-	}
-	return FALSE;
-}
-
-/*
- * Sets setting:prop property value from 'value'.
- */
-gboolean
-nmc_property_set_gvalue (NMSetting *setting, const char *prop, GValue *value)
-{
-	GParamSpec *param_spec;
-
-	param_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (setting)), prop);
-	if (param_spec && G_VALUE_TYPE (value) == G_PARAM_SPEC_VALUE_TYPE (param_spec)) {
-		g_object_set_property (G_OBJECT (setting), prop, value);
-		return TRUE;
-	}
-	return FALSE;
-}
-
 /*****************************************************************************/
 
 gboolean
diff --git a/clients/cli/settings.h b/clients/cli/settings.h
index 4e7e38df..1ff93685 100644
--- a/clients/cli/settings.h
+++ b/clients/cli/settings.h
@@ -45,20 +45,9 @@ char       *nmc_setting_get_property_parsable (NMSetting *setting,
 gboolean    nmc_setting_set_property (NMClient *client,
                                       NMSetting *setting,
                                       const char *prop,
+                                      char modifier,
                                       const char *val,
                                       GError **error);
-gboolean    nmc_setting_reset_property (NMSetting *setting,
-                                        const char *prop,
-                                        GError **error);
-gboolean    nmc_setting_remove_property_option (NMSetting *setting,
-                                                const char *prop,
-                                                const char *option,
-                                                guint32 idx,
-                                                GError **error);
-void nmc_property_set_default_value (NMSetting *setting, const char *prop);
-
-gboolean nmc_property_get_gvalue (NMSetting *setting, const char *prop, GValue *value);
-gboolean nmc_property_set_gvalue (NMSetting *setting, const char *prop, GValue *value);
 
 gboolean setting_details (const NmcConfig *nmc_config, NMSetting *setting, const char *one_prop);
 
diff --git a/clients/cli/utils.c b/clients/cli/utils.c
index 0e74ba88..a8b81279 100644
--- a/clients/cli/utils.c
+++ b/clients/cli/utils.c
@@ -508,7 +508,8 @@ nmc_string_to_arg_array (const char *line, const char *delim, gboolean unquote,
 	gs_free const char **arr0 = NULL;
 	char **arr;
 
-	arr0 = nm_utils_strsplit_set (line ?: "", delim ?: " \t", FALSE);
+	arr0 = nm_utils_strsplit_set (line ?: "",
+	                              delim ?: " \t");
 	if (!arr0)
 		arr = g_new0 (char *, 1);
 	else