libnm-core: allow strict and relaxed error behavior for _nm_setting_new_from_dbus()
[NetworkManager.git] / libnm-core / nm-setting.c
index fec645b..0c41bd8 100644 (file)
@@ -774,6 +774,7 @@ _nm_setting_to_dbus (NMSetting *setting, NMConnection *connection, NMConnectionS
  *   mapping property names to values
  * @connection_dict: the #GVariant containing an %NM_VARIANT_TYPE_CONNECTION
  *   dictionary mapping setting names to dictionaries.
+ * @parse_flags: flags to determine behavior during parsing.
  * @error: location to store error, or %NULL
  *
  * Creates a new #NMSetting object and populates that object with the properties
@@ -790,16 +791,20 @@ NMSetting *
 _nm_setting_new_from_dbus (GType setting_type,
                            GVariant *setting_dict,
                            GVariant *connection_dict,
+                           NMSettingParseFlags parse_flags,
                            GError **error)
 {
-       NMSetting *setting;
+       gs_unref_object NMSetting *setting = NULL;
+       gs_unref_hashtable GHashTable *keys = NULL;
        const NMSettingProperty *properties;
-       guint n_properties;
-       guint i;
+       guint i, n_properties;
 
        g_return_val_if_fail (G_TYPE_IS_INSTANTIATABLE (setting_type), NULL);
        g_return_val_if_fail (g_variant_is_of_type (setting_dict, NM_VARIANT_TYPE_SETTING), NULL);
 
+       nm_assert (!NM_FLAGS_ANY (parse_flags, ~NM_SETTING_PARSE_FLAGS_ALL));
+       nm_assert (!NM_FLAGS_ALL (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT | NM_SETTING_PARSE_FLAGS_BEST_EFFORT));
+
        /* connection_dict is not technically optional, but some tests in test-general
         * don't bother with it in cases where they know it's not needed.
         */
@@ -813,19 +818,49 @@ _nm_setting_new_from_dbus (GType setting_type,
         */
        setting = (NMSetting *) g_object_new (setting_type, NULL);
 
+       if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
+               GVariantIter iter;
+               GVariant *entry, *entry_key;
+               char *key;
+
+               keys = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+               g_variant_iter_init (&iter, setting_dict);
+               while ((entry = g_variant_iter_next_value (&iter))) {
+                       entry_key = g_variant_get_child_value (entry, 0);
+                       key = g_strdup (g_variant_get_string (entry_key, NULL));
+                       g_variant_unref (entry_key);
+                       g_variant_unref (entry);
+
+                       if (!nm_g_hash_table_add (keys, key)) {
+                               g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
+                                            _("duplicate property"));
+                               g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), key);
+                               return NULL;
+                       }
+               }
+       }
+
        properties = nm_setting_class_get_properties (NM_SETTING_GET_CLASS (setting), &n_properties);
        for (i = 0; i < n_properties; i++) {
                const NMSettingProperty *property = &properties[i];
-               GVariant *value;
+               gs_unref_variant GVariant *value = NULL;
+               gs_free_error GError *local = NULL;
 
                if (property->param_spec && !(property->param_spec->flags & G_PARAM_WRITABLE))
                        continue;
 
                value = g_variant_lookup_value (setting_dict, property->name, NULL);
 
+               if (value && keys)
+                       g_hash_table_remove (keys, property->name);
+
                if (value && property->set_func) {
+
                        if (!g_variant_type_equal (g_variant_get_type (value), property->dbus_type)) {
-                       property_type_error:
+                               /* for backward behavior, fail unless best-effort is chosen. */
+                               if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_BEST_EFFORT))
+                                       continue;
                                g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
                                             _("can't set property of type '%s' from value of type '%s'"),
                                             property->dbus_type ?
@@ -834,36 +869,83 @@ _nm_setting_new_from_dbus (GType setting_type,
                                                     g_type_name (property->param_spec->value_type) : "(unknown)",
                                             g_variant_get_type_string (value));
                                g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property->name);
-
-                               g_variant_unref (value);
-                               g_object_unref (setting);
                                return NULL;
                        }
 
-                       property->set_func (setting,
-                                           connection_dict,
-                                           property->name,
-                                           value);
+                       if (!property->set_func (setting,
+                                                connection_dict,
+                                                property->name,
+                                                value,
+                                                parse_flags,
+                                                &local)) {
+                               if (!NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
+                                       continue;
+                               g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                            _("failed to set property: %s"),
+                                            local->message);
+                               g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property->name);
+                               return NULL;
+                       }
                } else if (!value && property->not_set_func) {
-                       property->not_set_func (setting,
-                                               connection_dict,
-                                               property->name);
+                       if (!property->not_set_func (setting,
+                                                    connection_dict,
+                                                    property->name,
+                                                    parse_flags,
+                                                    &local)) {
+                               if (!NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
+                                       continue;
+                               g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                            _("failed to set property: %s"),
+                                            local->message);
+                               g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property->name);
+                               return NULL;
+                       }
                } else if (value && property->param_spec) {
-                       GValue object_value = { 0, };
+                       nm_auto_unset_gvalue GValue object_value = G_VALUE_INIT;
 
                        g_value_init (&object_value, property->param_spec->value_type);
-                       if (!set_property_from_dbus (property, value, &object_value))
-                               goto property_type_error;
+                       if (!set_property_from_dbus (property, value, &object_value)) {
+                               /* for backward behavior, fail unless best-effort is chosen. */
+                               if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_BEST_EFFORT))
+                                       continue;
+                               g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                            _("can't set property of type '%s' from value of type '%s'"),
+                                            property->dbus_type ?
+                                                g_variant_type_peek_string (property->dbus_type) :
+                                                property->param_spec ?
+                                                    g_type_name (property->param_spec->value_type) : "(unknown)",
+                                            g_variant_get_type_string (value));
+                               g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property->name);
+                               return NULL;
+                       }
 
-                       g_object_set_property (G_OBJECT (setting), property->param_spec->name, &object_value);
-                       g_value_unset (&object_value);
+                       if (!nm_g_object_set_property (G_OBJECT (setting), property->param_spec->name, &object_value, &local)) {
+                               if (!NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
+                                       continue;
+                               g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                            _("can not set property: %s"),
+                                            local->message);
+                               g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property->name);
+                               return NULL;
+                       }
                }
+       }
 
-               if (value)
-                       g_variant_unref (value);
+       if (   NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)
+           && g_hash_table_size (keys) > 0) {
+               GHashTableIter iter;
+               const char *key;
+
+               g_hash_table_iter_init (&iter, keys);
+               if (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL)) {
+                       g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                    _("unknown property"));
+                       g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), key);
+                       return NULL;
+               }
        }
 
-       return setting;
+       return nm_unauto (&setting);
 }
 
 /**