diff options
Diffstat (limited to 'libnm-core/nm-utils.c')
| -rw-r--r-- | libnm-core/nm-utils.c | 1625 |
1 files changed, 1533 insertions, 92 deletions
diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c index bc955834..6cc92ec8 100644 --- a/libnm-core/nm-utils.c +++ b/libnm-core/nm-utils.c @@ -16,7 +16,7 @@ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * - * Copyright 2005 - 2014 Red Hat, Inc. + * Copyright 2005 - 2017 Red Hat, Inc. */ #include "nm-default.h" @@ -33,6 +33,7 @@ #include <gmodule.h> #include <sys/stat.h> #include <net/if.h> +#include <linux/pkt_sched.h> #if WITH_JANSSON #include <jansson.h> @@ -494,17 +495,45 @@ _nm_utils_strdict_to_dbus (const GValue *prop_value) { GHashTable *hash; GHashTableIter iter; - gpointer key, value; + const char *key, *value; GVariantBuilder builder; + guint i, len; g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}")); + hash = g_value_get_boxed (prop_value); - if (hash) { - g_hash_table_iter_init (&iter, hash); - while (g_hash_table_iter_next (&iter, &key, &value)) - g_variant_builder_add (&builder, "{ss}", key, value); + if (!hash) + goto out; + len = g_hash_table_size (hash); + if (!len) + goto out; + + g_hash_table_iter_init (&iter, hash); + if (!g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) + nm_assert_not_reached (); + + if (len == 1) + g_variant_builder_add (&builder, "{ss}", key, value); + else { + gs_free NMUtilsNamedValue *idx = NULL; + + idx = g_new (NMUtilsNamedValue, len); + i = 0; + do { + idx[i].name = key; + idx[i].value_str = value; + i++; + } while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)); + nm_assert (i == len); + + g_qsort_with_data (idx, len, sizeof (idx[0]), + nm_utils_named_entry_cmp_with_data, NULL); + + for (i = 0; i < len; i++) + g_variant_builder_add (&builder, "{ss}", idx[i].name, idx[i].value_str); } +out: return g_variant_builder_end (&builder); } @@ -822,41 +851,6 @@ _nm_utils_strv_equal (char **strv1, char **strv2) return !*strv1 && !*strv2; } -/** - * _nm_utils_strsplit_set: - * @str: string to split - * @delimiters: string of delimiter characters - * @max_tokens: the maximum number of tokens to split string into. When it is - * less than 1, the @str is split completely. - * - * Utility function for splitting string into a string array. It is a wrapper - * for g_strsplit_set(), but it also removes empty strings from the vector as - * they are not useful in most cases. - * - * Returns: (transfer full): a newly allocated NULL-terminated array of strings. - * The caller must free the returned array with g_strfreev(). - **/ -char ** -_nm_utils_strsplit_set (const char *str, const char *delimiters, int max_tokens) -{ - char **result; - uint i; - uint j; - - result = g_strsplit_set (str, delimiters, max_tokens); - - /* remove empty strings */ - for (i = 0; result && result[i]; i++) { - if (*result[i] == '\0') { - g_free (result[i]); - for (j = i; result[j]; j++) - result[j] = result[j + 1]; - i--; - } - } - return result; -} - static gboolean device_supports_ap_ciphers (guint32 dev_caps, guint32 ap_flags, @@ -2070,6 +2064,550 @@ next: /*****************************************************************************/ +static void +_string_append_tc_handle (GString *string, guint32 handle) +{ + g_string_append_printf (string, "%x:", TC_H_MAJ (handle) >> 16); + if (TC_H_MIN (handle) != TC_H_UNSPEC) + g_string_append_printf (string, "%x", TC_H_MIN (handle)); +} + +/** + * _nm_utils_string_append_tc_parent: + * @string: the string to write the parent handle to + * @prefix: optional prefix for the numeric handle + * @parent: the parent handle + * + * This is used to either write out the parent handle to the tc qdisc string + * or to pretty-format (use symbolic name for root) the key in keyfile. + * The presence of prefix determnines which one is the case. + * + * Private API due to general uglyness and overall uselessness for anything + * sensible. + */ +void +_nm_utils_string_append_tc_parent (GString *string, const char *prefix, guint32 parent) +{ + if (parent == TC_H_ROOT) { + g_string_append (string, "root"); + } else { + if (prefix) { + if (parent == TC_H_INGRESS) + return; + g_string_append_printf (string, "%s ", prefix); + } + _string_append_tc_handle (string, parent); + } + + if (prefix) + g_string_append_c (string, ' '); +} + +/** + * _nm_utils_parse_tc_handle: + * @str: the string representation of a qdisc handle + * @error: location of the error + * + * Parses tc style handle number into a numeric representation. + * Don't use this, use nm_utils_tc_qdisc_from_str() instead. + */ +guint32 +_nm_utils_parse_tc_handle (const char *str, GError **error) +{ + gint64 maj, min; + char *sep; + + maj = g_ascii_strtoll (str, &sep, 0x10); + if (*sep == ':') + min = g_ascii_strtoll (&sep[1], &sep, 0x10); + else + min = 0; + + if (*sep != '\0' || maj <= 0 || maj > 0xffff || min < 0 || min > 0xffff) { + g_set_error (error, 1, 0, _("'%s' is not a valid handle."), str); + return TC_H_UNSPEC; + } + + return TC_H_MAKE (maj << 16, min); +} + +#define TC_ATTR_SPEC_PTR(name, type, no_value, consumes_rest, str_type) \ + &(NMVariantAttributeSpec) { name, type, FALSE, FALSE, no_value, consumes_rest, str_type } + +static const NMVariantAttributeSpec * const tc_object_attribute_spec[] = { + TC_ATTR_SPEC_PTR ("root", G_VARIANT_TYPE_BOOLEAN, TRUE, FALSE, 0 ), + TC_ATTR_SPEC_PTR ("parent", G_VARIANT_TYPE_STRING, FALSE, FALSE, 'a' ), + TC_ATTR_SPEC_PTR ("handle", G_VARIANT_TYPE_STRING, FALSE, FALSE, 'a' ), + TC_ATTR_SPEC_PTR ("kind", G_VARIANT_TYPE_STRING, TRUE, FALSE, 'a' ), + TC_ATTR_SPEC_PTR ("", G_VARIANT_TYPE_STRING, TRUE, TRUE, 'a' ), + NULL, +}; + +/*****************************************************************************/ + +/** + * _nm_utils_string_append_tc_qdisc_rest: + * @string: the string to write the formatted qdisc to + * @qdisc: the %NMTCQdisc + * + * This formats the rest of the qdisc string but the parent. Useful to format + * the keyfile value and nowhere else. + * Use nm_utils_tc_qdisc_to_str() that also includes the parent instead. + */ +void +_nm_utils_string_append_tc_qdisc_rest (GString *string, NMTCQdisc *qdisc) +{ + guint32 handle = nm_tc_qdisc_get_handle (qdisc); + const char *kind = nm_tc_qdisc_get_kind (qdisc); + + if (handle != TC_H_UNSPEC && strcmp (kind, "ingress") != 0) { + g_string_append (string, "handle "); + _string_append_tc_handle (string, handle); + g_string_append_c (string, ' '); + } + + g_string_append (string, kind); +} + +/** + * nm_utils_tc_qdisc_to_str: + * @qdisc: the %NMTCQdisc + * @error: location of the error + * + * Turns the %NMTCQdisc into a tc style string representation of the queueing + * discipline. + * + * Returns: formatted string or %NULL + * + * Since: 1.10.2 + */ +char * +nm_utils_tc_qdisc_to_str (NMTCQdisc *qdisc, GError **error) +{ + GString *string; + + string = g_string_sized_new (60); + + _nm_utils_string_append_tc_parent (string, "parent", + nm_tc_qdisc_get_parent (qdisc)); + _nm_utils_string_append_tc_qdisc_rest (string, qdisc); + + return g_string_free (string, FALSE); +} + + +static gboolean +_tc_read_common_opts (const char *str, + guint32 *handle, + guint32 *parent, + char **kind, + char **rest, + GError **error) +{ + gs_unref_hashtable GHashTable *ht = NULL; + GVariant *variant; + + ht = nm_utils_parse_variant_attributes (str, + ' ', ' ', FALSE, + tc_object_attribute_spec, + error); + if (!ht) + return FALSE; + + if (g_hash_table_contains (ht, "root")) + *parent = TC_H_ROOT; + + variant = g_hash_table_lookup (ht, "parent"); + if (variant) { + if (*parent != TC_H_UNSPEC) { + g_set_error (error, 1, 0, + _("'%s' unexpected: parent already specified."), + g_variant_get_string (variant, NULL)); + return FALSE; + } + *parent = _nm_utils_parse_tc_handle (g_variant_get_string (variant, NULL), error); + if (*parent == TC_H_UNSPEC) + return FALSE; + } + + variant = g_hash_table_lookup (ht, "handle"); + if (variant) { + *handle = _nm_utils_parse_tc_handle (g_variant_get_string (variant, NULL), error); + if (*handle == TC_H_UNSPEC) + return FALSE; + if (TC_H_MIN (*handle)) { + g_set_error (error, 1, 0, + _("invalid handle: '%s'"), + g_variant_get_string (variant, NULL)); + return FALSE; + } + } + + variant = g_hash_table_lookup (ht, "kind"); + if (variant) { + *kind = g_variant_dup_string (variant, NULL); + if (strcmp (*kind, "ingress") == 0) { + if (*parent == TC_H_UNSPEC) + *parent = TC_H_INGRESS; + if (*handle == TC_H_UNSPEC) + *handle = TC_H_MAKE (TC_H_INGRESS, 0); + } + } + + if (*parent == TC_H_UNSPEC) { + if (*kind) { + g_free (*kind); + *kind = NULL; + } + g_set_error_literal (error, 1, 0, _("parent not specified.")); + return FALSE; + } + + variant = g_hash_table_lookup (ht, ""); + if (variant) + *rest = g_variant_dup_string (variant, NULL); + + return TRUE; +} + +/** + * nm_utils_tc_qdisc_from_str: + * @str: the string representation of a qdisc + * @error: location of the error + * + * Parces the tc style string qdisc representation of the queueing + * discipline to a %NMTCQdisc instance. Supports a subset of the tc language. + * + * Returns: the %NMTCQdisc or %NULL + * + * Since: 1.10.2 + */ +NMTCQdisc * +nm_utils_tc_qdisc_from_str (const char *str, GError **error) +{ + guint32 handle = TC_H_UNSPEC; + guint32 parent = TC_H_UNSPEC; + gs_free char *kind = NULL; + gs_free char *rest = NULL; + NMTCQdisc *qdisc = NULL; + gs_unref_hashtable GHashTable *ht = NULL; + + nm_assert (str); + nm_assert (!error || !*error); + + ht = nm_utils_parse_variant_attributes (str, + ' ', ' ', FALSE, + tc_object_attribute_spec, + error); + if (!ht) + return NULL; + + if (!_tc_read_common_opts (str, &handle, &parent, &kind, &rest, error)) + return NULL; + + if (rest) { + g_set_error (error, 1, 0, _("unsupported qdisc option: '%s'."), rest); + return NULL; + } + + qdisc = nm_tc_qdisc_new (kind, parent, error); + if (!qdisc) + return NULL; + + nm_tc_qdisc_set_handle (qdisc, handle); + + return qdisc; +} +/*****************************************************************************/ + +static const NMVariantAttributeSpec * const tc_action_simple_attribute_spec[] = { + TC_ATTR_SPEC_PTR ("sdata", G_VARIANT_TYPE_BYTESTRING, FALSE, FALSE, 0 ), + NULL, +}; + +static const NMVariantAttributeSpec * const tc_action_attribute_spec[] = { + TC_ATTR_SPEC_PTR ("kind", G_VARIANT_TYPE_STRING, TRUE, FALSE, 'a' ), + TC_ATTR_SPEC_PTR ("", G_VARIANT_TYPE_STRING, TRUE, TRUE, 'a' ), + NULL, +}; + +static gboolean +_string_append_tc_action (GString *string, NMTCAction *action, GError **error) +{ + gs_unref_hashtable GHashTable *ht = NULL; + const char *kind = nm_tc_action_get_kind (action); + gs_strfreev char **attr_names = NULL; + gs_free char *str = NULL; + int i; + + ht = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, NULL); + + g_string_append (string, kind); + + attr_names = nm_tc_action_get_attribute_names (action); + for (i = 0; attr_names[i]; i++) { + g_hash_table_insert (ht, attr_names[i], + nm_tc_action_get_attribute (action, attr_names[i])); + } + + if (i) { + str = nm_utils_format_variant_attributes (ht, ' ', ' '); + g_string_append_c (string, ' '); + g_string_append (string, str); + } + + return TRUE; +} + +/** + * nm_utils_tc_action_to_str: + * @action: the %NMTCAction + * @error: location of the error + * + * Turns the %NMTCAction into a tc style string representation of the queueing + * discipline. + * + * Returns: formatted string or %NULL + * + * Since: 1.10.2 + */ +char * +nm_utils_tc_action_to_str (NMTCAction *action, GError **error) +{ + GString *string; + + string = g_string_sized_new (60); + if (!_string_append_tc_action (string, action, error)) { + g_string_free (string, TRUE); + return NULL; + } + + return g_string_free (string, FALSE); +} + +/** + * nm_utils_tc_action_from_str: + * @str: the string representation of a action + * @error: location of the error + * + * Parces the tc style string action representation of the queueing + * discipline to a %NMTCAction instance. Supports a subset of the tc language. + * + * Returns: the %NMTCAction or %NULL + * + * Since: 1.10.2 + */ +NMTCAction * +nm_utils_tc_action_from_str (const char *str, GError **error) +{ + const char *kind = NULL; + const char *rest = NULL; + NMTCAction *action = NULL; + gs_unref_hashtable GHashTable *ht = NULL; + gs_unref_hashtable GHashTable *options = NULL; + GVariant *variant; + const NMVariantAttributeSpec * const *attrs; + + nm_assert (str); + nm_assert (!error || !*error); + + ht = nm_utils_parse_variant_attributes (str, + ' ', ' ', FALSE, + tc_action_attribute_spec, + error); + if (!ht) + return FALSE; + + variant = g_hash_table_lookup (ht, "kind"); + if (variant) { + kind = g_variant_get_string (variant, NULL); + } else { + g_set_error_literal (error, 1, 0, _("action name missing.")); + return NULL; + } + + kind = g_variant_get_string (variant, NULL); + if (strcmp (kind, "simple") == 0) + attrs = tc_action_simple_attribute_spec; + else + attrs = NULL; + + variant = g_hash_table_lookup (ht, ""); + if (variant) + rest = g_variant_get_string (variant, NULL); + + action = nm_tc_action_new (kind, error); + if (!action) + return NULL; + + if (rest) { + GHashTableIter iter; + gpointer key, value; + + if (!attrs) { + nm_tc_action_unref (action); + g_set_error (error, 1, 0, _("unsupported action option: '%s'."), rest); + return NULL; + } + + options = nm_utils_parse_variant_attributes (rest, + ' ', ' ', FALSE, + attrs, + error); + if (!options) { + nm_tc_action_unref (action); + return NULL; + } + + g_hash_table_iter_init (&iter, options); + while (g_hash_table_iter_next (&iter, &key, &value)) + nm_tc_action_set_attribute (action, key, g_variant_ref_sink (value)); + } + + return action; +} + +/*****************************************************************************/ + +/** + * _nm_utils_string_append_tc_tfilter_rest: + * @string: the string to write the formatted tfilter to + * @tfilter: the %NMTCTfilter + * + * This formats the rest of the tfilter string but the parent. Useful to format + * the keyfile value and nowhere else. + * Use nm_utils_tc_tfilter_to_str() that also includes the parent instead. + */ +gboolean +_nm_utils_string_append_tc_tfilter_rest (GString *string, NMTCTfilter *tfilter, GError **error) +{ + guint32 handle = nm_tc_tfilter_get_handle (tfilter); + const char *kind = nm_tc_tfilter_get_kind (tfilter); + NMTCAction *action; + + if (handle != TC_H_UNSPEC) { + g_string_append (string, "handle "); + _string_append_tc_handle (string, handle); + g_string_append_c (string, ' '); + } + + g_string_append (string, kind); + + action = nm_tc_tfilter_get_action (tfilter); + if (action) { + g_string_append (string, " action "); + if (!_string_append_tc_action (string, action, error)) + return FALSE; + } + + return TRUE; +} + +/** + * nm_utils_tc_tfilter_to_str: + * @tfilter: the %NMTCTfilter + * @error: location of the error + * + * Turns the %NMTCTfilter into a tc style string representation of the queueing + * discipline. + * + * Returns: formatted string or %NULL + * + * Since: 1.10.2 + */ +char * +nm_utils_tc_tfilter_to_str (NMTCTfilter *tfilter, GError **error) +{ + GString *string; + + string = g_string_sized_new (60); + + _nm_utils_string_append_tc_parent (string, "parent", + nm_tc_tfilter_get_parent (tfilter)); + if (!_nm_utils_string_append_tc_tfilter_rest (string, tfilter, error)) { + g_string_free (string, TRUE); + return NULL; + } + + return g_string_free (string, FALSE); +} + +static const NMVariantAttributeSpec * const tc_tfilter_attribute_spec[] = { + TC_ATTR_SPEC_PTR ("action", G_VARIANT_TYPE_BOOLEAN, TRUE, FALSE, 0 ), + TC_ATTR_SPEC_PTR ("", G_VARIANT_TYPE_STRING, TRUE, TRUE, 'a' ), + NULL, +}; + +/** + * nm_utils_tc_tfilter_from_str: + * @str: the string representation of a tfilter + * @error: location of the error + * + * Parces the tc style string tfilter representation of the queueing + * discipline to a %NMTCTfilter instance. Supports a subset of the tc language. + * + * Returns: the %NMTCTfilter or %NULL + * + * Since: 1.10.2 + */ +NMTCTfilter * +nm_utils_tc_tfilter_from_str (const char *str, GError **error) +{ + guint32 handle = TC_H_UNSPEC; + guint32 parent = TC_H_UNSPEC; + gs_free char *kind = NULL; + gs_free char *rest = NULL; + NMTCAction *action = NULL; + const char *extra_opts = NULL; + NMTCTfilter *tfilter = NULL; + gs_unref_hashtable GHashTable *ht = NULL; + GVariant *variant; + + nm_assert (str); + nm_assert (!error || !*error); + + if (!_tc_read_common_opts (str, &handle, &parent, &kind, &rest, error)) + return NULL; + + if (rest) { + ht = nm_utils_parse_variant_attributes (rest, + ' ', ' ', FALSE, + tc_tfilter_attribute_spec, + error); + if (!ht) + return NULL; + + variant = g_hash_table_lookup (ht, ""); + if (variant) + extra_opts = g_variant_get_string (variant, NULL); + + if (g_hash_table_contains (ht, "action")) { + action = nm_utils_tc_action_from_str (extra_opts, error); + if (!action) { + g_prefix_error (error, _("invalid action: ")); + return NULL; + } + } else { + g_set_error (error, 1, 0, _("unsupported tfilter option: '%s'."), rest); + return NULL; + } + } + + tfilter = nm_tc_tfilter_new (kind, parent, error); + if (!tfilter) + return NULL; + + nm_tc_tfilter_set_handle (tfilter, handle); + if (action) { + nm_tc_tfilter_set_action (tfilter, action); + nm_tc_action_unref (action); + } + + return tfilter; +} + +/*****************************************************************************/ + /** * nm_utils_uuid_generate_buf_: * @buf: input buffer, must contain at least 37 bytes @@ -3762,6 +4300,22 @@ nm_utils_is_uuid (const char *str) static char _nm_utils_inet_ntop_buffer[NM_UTILS_INET_ADDRSTRLEN]; +const char * +nm_utils_inet_ntop (int addr_family, gconstpointer addr, char *dst) +{ + const char *s; + + nm_assert_addr_family (addr_family); + nm_assert (addr); + + s = inet_ntop (addr_family, + addr, + dst ? dst : _nm_utils_inet_ntop_buffer, + addr_family == AF_INET6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN); + nm_assert (s); + return s; +} + /** * nm_utils_inet4_ntop: (skip) * @inaddr: the address that should be converted to string. @@ -4292,6 +4846,446 @@ const char **nm_utils_enum_get_values (GType type, gint from, gint to) /*****************************************************************************/ #if WITH_JANSSON + +/* Added in Jansson v2.3 (released Jan 27 2012) */ +#ifndef json_object_foreach +#define json_object_foreach(object, key, value) \ + for(key = json_object_iter_key(json_object_iter(object)); \ + key && (value = json_object_iter_value(json_object_iter_at (object, key) )); \ + key = json_object_iter_key(json_object_iter_next(object, json_object_iter_at (object, key)))) +#endif + +/* Added in Jansson v2.4 (released Sep 23 2012), but travis.ci has v2.2. */ +#ifndef json_boolean +#define json_boolean(val) ((val) ? json_true() : json_false()) +#endif + +/* Added in Jansson v2.5 (released Sep 19 2013), but travis.ci has v2.2. */ +#ifndef json_array_foreach +#define json_array_foreach(array, index, value) \ + for (index = 0; \ + index < json_array_size(array) && (value = json_array_get(array, index)); \ + index++) +#endif + +/* Added in Jansson v2.7 */ +#ifndef json_boolean_value +#define json_boolean_value json_is_true +#endif + +static void +_json_add_object (json_t *json, + const char *key1, + const char *key2, + const char *key3, + json_t *value) +{ + json_t *json_element, *json_link; + + json_element = json_object_get (json, key1); + if (!json_element) { + json_element = value; + if (key2) { + if (key3) { + json_element = json_object (); + json_object_set_new (json_element, key3, value); + } + json_link = json_object (); + json_object_set_new (json_link, key2, json_element); + json_element = json_link; + } + json_object_set_new (json, key1, json_element); + return; + } + + if (!key2) + goto key_already_there; + + json_link = json_element; + json_element = json_object_get (json_element, key2); + if (!json_element) { + json_element = value; + if (key3) { + json_element = json_object (); + json_object_set_new (json_element, key3, value); + } + json_object_set_new (json_link, key2, json_element); + return; + } + + if (!key3) + goto key_already_there; + + json_link = json_element; + json_element = json_object_get (json_element, key3); + if (!json_element) { + json_object_set_new (json_link, key3, value); + return; + } + +key_already_there: + json_decref (value); +} + +/* + * Removes the specified key1[.key2.key3] from json. + * Returns TRUE if json has been modified, FALSE otherwise. */ +static gboolean +_json_del_object (json_t *json, + const char *key1, + const char *key2, + const char *key3) +{ + json_t *json_element = json; + json_t *json_link = NULL; + const char *iter_key = key1; + + if (key2) { + json_link = json; + json_element = json_object_get (json, key1); + if (!json_element) + return FALSE; + iter_key = key2; + } + if (key3) { + json_link = json_element; + json_element = json_object_get (json_element, key2); + if (!json_element) + return FALSE; + iter_key = key3; + } + + if (json_object_del (json_element, iter_key) != 0) + return FALSE; + + /* 1st level key only */ + if (!json_link) + return TRUE; + + if (json_object_size (json_element) == 0) + json_object_del (json_link, (key3 ? key2 : key1)); + + if (key3 && json_object_size (json_link) == 0) + json_object_del (json, key1); + + return TRUE; +} + +/* Adds in place to json the defaults for missing properties; + * the "add_implicit" allows to add to the json also the default + * values used but not shown with teamdctl */ +static void +_json_team_add_defaults (json_t *json, + gboolean port_config, + gboolean add_implicit) +{ + json_t *json_element; + const char *runner = NULL; + + if (port_config) { + _json_add_object (json, "link_watch", "name", NULL, + json_string (NM_TEAM_LINK_WATCHER_ETHTOOL)); + return; + } + + /* Retrieve runner or add default one */ + json_element = json_object_get (json, "runner"); + if (json_element) { + runner = json_string_value (json_object_get (json_element, "name")); + } else { + json_element = json_object (); + json_object_set_new (json, "runner", json_element); + } + if (!runner) { + runner = NM_SETTING_TEAM_RUNNER_DEFAULT; + json_object_set_new (json_element, "name", json_string (runner)); + } + + + if (nm_streq (runner, NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP)) { + _json_add_object (json, "notify_peers", "count", NULL, + json_integer (NM_SETTING_TEAM_NOTIFY_PEERS_COUNT_ACTIVEBACKUP_DEFAULT)); + _json_add_object (json, "mcast_rejoin", "count", NULL, + json_integer (NM_SETTING_TEAM_NOTIFY_MCAST_COUNT_ACTIVEBACKUP_DEFAULT)); + } else if ( nm_streq (runner, NM_SETTING_TEAM_RUNNER_LOADBALANCE) + || nm_streq (runner, NM_SETTING_TEAM_RUNNER_LACP)) { + json_element = json_array (); + json_array_append_new (json_element, json_string ("eth")); + json_array_append_new (json_element, json_string ("ipv4")); + json_array_append_new (json_element, json_string ("ipv6")); + _json_add_object (json, "runner", "tx_hash", NULL, json_element); + } + + if (!add_implicit) + return; + + if (nm_streq (runner, NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP)) + _json_add_object (json, "runner", "hwaddr_policy", NULL, json_string ("same_all")); + else if (NM_IN_STRSET (runner, + NM_SETTING_TEAM_RUNNER_LOADBALANCE, + NM_SETTING_TEAM_RUNNER_LACP)) { + _json_add_object (json, "runner", "tx_balancer", "balancing_interval", + json_integer (NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL_DEFAULT)); + if (nm_streq (runner, NM_SETTING_TEAM_RUNNER_LACP)) { + _json_add_object (json, "runner", "active", NULL, json_boolean (TRUE)); + _json_add_object (json, "runner", "sys_prio", NULL, + json_integer (NM_SETTING_TEAM_RUNNER_SYS_PRIO_DEFAULT)); + _json_add_object (json, "runner", "min_ports", NULL, json_integer (0)); + _json_add_object (json, "runner", "agg_select_policy", NULL, + json_string (NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY_DEFAULT)); + } + } +} + +static json_t * +_json_find_object (json_t *json, + const char *key1, + const char *key2, + const char *key3) +{ + json_t *json_element; + + if (!key1) + return NULL; + json_element = json_object_get (json, key1); + if (!key2 || !json_element) + return json_element; + + json_element = json_object_get (json_element, key2); + if (!key3 || !json_element) + return json_element; + + json_element = json_object_get (json_element, key3); + return json_element; +} + +static inline void +_json_delete_object_on_int_match (json_t *json, + const char *key1, + const char *key2, + const char *key3, + int val) +{ + json_t *json_element; + + json_element = _json_find_object (json, key1, key2, key3); + if (!json_element || !json_is_integer (json_element)) + return; + if (json_integer_value (json_element) == val) + _json_del_object (json, key1, key2, key3); +} + +static inline void +_json_delete_object_on_bool_match (json_t *json, + const char *key1, + const char *key2, + const char *key3, + gboolean val) +{ + json_t *json_element; + + json_element = _json_find_object (json, key1, key2, key3); + if (!json_element || !json_is_boolean (json_element)) + return; + if (json_boolean_value (json_element) == val) + _json_del_object (json, key1, key2, key3); +} + +static inline void +_json_delete_object_on_string_match (json_t *json, + const char *key1, + const char *key2, + const char *key3, + const char *val) +{ + json_t *json_element; + + json_element = _json_find_object (json, key1, key2, key3); + if (!json_element || !json_is_string (json_element)) + return; + if (nm_streq0 (json_string_value (json_element), val)) + _json_del_object (json, key1, key2, key3); +} + +static void +_json_team_normalize_defaults (json_t *json, gboolean reset) +{ + json_t *json_element; + const char *runner = NM_SETTING_TEAM_RUNNER_DEFAULT; + int notify_peers_count = 0, notify_peers_interval = 0; + int mcast_rejoin_count = 0, mcast_rejoin_interval = 0; + int runner_tx_balancer_interval = -1; + gboolean runner_active = FALSE, runner_fast_rate = FALSE; + int runner_sys_prio = -1, runner_min_ports = -1; + + json_element = _json_find_object (json, "runner", "name", NULL); + if (json_element) { + runner = json_string_value (json_element); + _json_delete_object_on_string_match (json, "runner", "name", NULL, + NM_SETTING_TEAM_RUNNER_DEFAULT); + } + + /* the runner changed: clear all the properties. Then team.config will be saved + * and reloaded triggering the reset of the values through _nm_utils_team_config_get + */ + if (reset) { + _json_del_object (json, "notify_peers", "count", NULL); + _json_del_object (json, "notify_peers", "interval", NULL); + _json_del_object (json, "mcast_rejoin", "count", NULL); + _json_del_object (json, "mcast_rejoin", "interval", NULL); + _json_del_object (json, "runner", "hwaddr_policy", NULL); + _json_del_object (json, "runner", "tx_hash", NULL); + _json_del_object (json, "runner", "tx_balancer", "name"); + _json_del_object (json, "runner", "tx_balancer", "balancing_interval"); + _json_del_object (json, "runner", "active", NULL); + _json_del_object (json, "runner", "fast_rate", NULL); + _json_del_object (json, "runner", "sys_prio", NULL); + _json_del_object (json, "runner", "min_ports", NULL); + _json_del_object (json, "runner", "agg_select_policy", NULL); + return; + } + + if (nm_streq (runner, NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP)) { + notify_peers_count = 1; + mcast_rejoin_count = 1; + _json_delete_object_on_string_match (json, "runner", "hwaddr_policy", NULL, + NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_DEFAULT); + } else if (nm_streq (runner, NM_SETTING_TEAM_RUNNER_LACP)) { + runner_tx_balancer_interval = 50; + runner_active = TRUE; + runner_sys_prio = 255; + runner_min_ports = 0; + _json_delete_object_on_string_match (json, "runner", "agg_select_policy", NULL, + NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY_DEFAULT); + } else if (nm_streq (runner, NM_SETTING_TEAM_RUNNER_LOADBALANCE)) + runner_tx_balancer_interval = 50; + + _json_delete_object_on_int_match (json, "notify_peers", "count", NULL, notify_peers_count); + _json_delete_object_on_int_match (json, "notify_peers", "interval", NULL, notify_peers_interval); + _json_delete_object_on_int_match (json, "mcast_rejoin", "count", NULL, mcast_rejoin_count); + _json_delete_object_on_int_match (json, "macst_rejoin", "interval", NULL, mcast_rejoin_interval); + _json_delete_object_on_int_match (json, "runner", "tx_balancer", "balancing_interval", + runner_tx_balancer_interval); + _json_delete_object_on_int_match (json, "runner", "sys_prio", NULL, runner_sys_prio); + _json_delete_object_on_int_match (json, "runner", "min_ports", NULL, runner_min_ports); + _json_delete_object_on_bool_match (json, "runner", "active", NULL, runner_active); + _json_delete_object_on_bool_match (json, "runner", "active", NULL, runner_active); + _json_delete_object_on_bool_match (json, "runner", "fast_rate", NULL, runner_fast_rate); +} + +static NMTeamLinkWatcher * +_nm_utils_team_link_watcher_from_json (json_t *json_element) +{ + const char *j_key; + json_t *j_val; + gs_free char *name = NULL, *target_host = NULL, *source_host = NULL; + int val1 = 0, val2 = 0, val3 = 3; + NMTeamLinkWatcherArpPingFlags flags = 0; + + g_return_val_if_fail (json_element, NULL); + + json_object_foreach (json_element, j_key, j_val) { + if (nm_streq (j_key, "name")) + name = strdup (json_string_value (j_val)); + else if (nm_streq (j_key, "target_host")) + target_host = strdup (json_string_value (j_val)); + else if (nm_streq (j_key, "source_host")) + source_host = strdup (json_string_value (j_val)); + else if (NM_IN_STRSET (j_key, "delay_up", "init_wait")) + val1 = json_integer_value (j_val); + else if (NM_IN_STRSET (j_key, "delay_down", "interval")) + val2 = json_integer_value (j_val); + else if (nm_streq (j_key, "missed_max")) + val3 = json_integer_value (j_val); + else if (nm_streq (j_key, "validate_active")) { + if (json_is_true (j_val)) + flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE; + } else if (nm_streq (j_key, "validate_inactive")) { + if (json_is_true (j_val)) + flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE; + } else if (nm_streq (j_key, "send_always")) { + if (json_is_true (j_val)) + flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS; + } + } + + if (nm_streq0 (name, NM_TEAM_LINK_WATCHER_ETHTOOL)) + return nm_team_link_watcher_new_ethtool (val1, val2, NULL); + else if (nm_streq0 (name, NM_TEAM_LINK_WATCHER_NSNA_PING)) + return nm_team_link_watcher_new_nsna_ping (val1, val2, val3, target_host, NULL); + else if (nm_streq0 (name, NM_TEAM_LINK_WATCHER_ARP_PING)) { + return nm_team_link_watcher_new_arp_ping (val1, val2, val3, target_host, + source_host, flags, NULL); + } else + return NULL; +} + +static json_t * +_nm_utils_team_link_watcher_to_json (NMTeamLinkWatcher *watcher) +{ + const char *name; + int int_val; + const char *str_val; + NMTeamLinkWatcherArpPingFlags flags = 0; + json_t *json_element; + + g_return_val_if_fail (watcher, NULL); + + json_element = json_object (); + name = nm_team_link_watcher_get_name (watcher); + if (!name) + goto fail; + + json_object_set_new (json_element, "name", json_string (name)); + + if (nm_streq (name, NM_TEAM_LINK_WATCHER_ETHTOOL)) { + int_val = nm_team_link_watcher_get_delay_up (watcher); + if (int_val) + json_object_set_new (json_element, "delay_up", json_integer (int_val)); + int_val = nm_team_link_watcher_get_delay_down (watcher); + if (int_val) + json_object_set_new (json_element, "delay_down", json_integer (int_val)); + return json_element; + } + + int_val = nm_team_link_watcher_get_init_wait (watcher); + if (int_val) + json_object_set_new (json_element, "init_wait", json_integer (int_val)); + int_val = nm_team_link_watcher_get_interval (watcher); + if (int_val) + json_object_set_new (json_element, "interval", json_integer (int_val)); + int_val = nm_team_link_watcher_get_missed_max (watcher); + if (int_val != 3) + json_object_set_new (json_element, "missed_max", json_integer (int_val)); + str_val = nm_team_link_watcher_get_target_host (watcher); + if (!str_val) + goto fail; + json_object_set_new (json_element, "target_host", json_string (str_val)); + + if (nm_streq (name, NM_TEAM_LINK_WATCHER_NSNA_PING)) + return json_element; + + str_val = nm_team_link_watcher_get_source_host (watcher); + if (!str_val) + goto fail; + json_object_set_new (json_element, "source_host", json_string (str_val)); + + flags = nm_team_link_watcher_get_flags (watcher); + if (flags & NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE) + json_object_set_new (json_element, "validate_active", json_string ("true")); + if (flags & NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE) + json_object_set_new (json_element, "validate_inactive", json_string ("true")); + if (flags & NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS) + json_object_set_new (json_element, "send_always", json_string ("true")); + + return json_element; + +fail: + json_decref (json_element); + return NULL; +} + + /** * nm_utils_is_json_object: * @str: the JSON string to test @@ -4360,9 +5354,8 @@ _nm_utils_team_config_equal (const char *conf1, gboolean port_config) { json_t *json1 = NULL, *json2 = NULL, *json; - json_t *array, *name; gs_free char *dump1 = NULL, *dump2 = NULL; - json_t *value, *property; + json_t *value; json_error_t jerror; const char *key; gboolean ret; @@ -4386,33 +5379,8 @@ _nm_utils_team_config_equal (const char *conf1, * configuration. Add them with the default value if necessary, depending * on the configuration type. */ - for (i = 0, json = json1; i < 2; i++, json = json2) { - if (port_config) { - property = json_object_get (json, "link_watch"); - if (!property) { - property = json_object (); - json_object_set_new (property, "name", json_string ("ethtool")); - json_object_set_new (json, "link_watch", property); - } - } else { - property = json_object_get (json, "runner"); - if (!property) { - property = json_object (); - json_object_set_new (property, "name", json_string ("roundrobin")); - json_object_set_new (json, "runner", property); - } else if ( (name = json_object_get (property, "name")) - && NM_IN_STRSET (json_string_value (name), "lacp", "loadbalance")) { - /* Add default tx_hash when missing */ - if (!json_object_get (property, "tx_hash")) { - array = json_array (); - json_array_append_new (array, json_string ("eth")); - json_array_append_new (array, json_string ("ipv4")); - json_array_append_new (array, json_string ("ipv6")); - json_object_set_new (property, "tx_hash", array); - } - } - } - } + for (i = 0, json = json1; i < 2; i++, json = json2) + _json_team_add_defaults (json, port_config, FALSE); /* Only consider a given subset of nodes, others can change depending on * current state */ @@ -4437,6 +5405,239 @@ out: return ret; } + +GValue * +_nm_utils_team_config_get (const char *conf, + const char *key, + const char *key2, + const char *key3, + gboolean port_config) +{ + json_t *json; + json_t *json_element; + GValue *value = NULL; + json_error_t jerror; + + if (!key) + return NULL; + + json = json_loads (conf ?: "{}", JSON_REJECT_DUPLICATES, &jerror); + + /* Invalid json in conf */ + if (!json) + return NULL; + + /* Some properties are added by teamd when missing from the initial + * configuration. Add them with the default value if necessary, depending + * on the configuration type. + * Skip this for port config, as some properties change on the basis of the + * runner specified in the master connection... but we don't want to check + * against properties in another connection. Moreover, for team-port we have + * the link-watchers property only here: and for this compound property it is + * fine to show the default value only if explicitly set. + */ + if (!port_config) + _json_team_add_defaults (json, port_config, TRUE); + + /* Now search the property to retrieve */ + json_element = json_object_get (json, key); + if (json_element && key2) + json_element = json_object_get (json_element, key2); + if (json_element && key3) + json_element = json_object_get (json_element, key3); + + if (json_element) { + value = g_new0 (GValue, 1); + if (json_is_string (json_element)) { + g_value_init (value, G_TYPE_STRING); + g_value_set_string (value, json_string_value (json_element)); + } else if (json_is_integer (json_element)) { + g_value_init (value, G_TYPE_INT); + g_value_set_int (value, json_integer_value (json_element)); + } else if (json_is_boolean (json_element)) { + g_value_init (value, G_TYPE_BOOLEAN); + g_value_set_boolean (value, json_boolean_value (json_element)); + } else if (nm_streq (key, "link_watch")) { + NMTeamLinkWatcher *watcher; + GPtrArray *data = g_ptr_array_new_with_free_func + ((GDestroyNotify) nm_team_link_watcher_unref); + + if (json_is_array (json_element)) { + json_t *j_watcher; + int index; + + json_array_foreach (json_element, index, j_watcher) { + watcher = _nm_utils_team_link_watcher_from_json (j_watcher); + if (watcher) + g_ptr_array_add (data, watcher); + } + } else { + watcher = _nm_utils_team_link_watcher_from_json (json_element); + if (watcher) + g_ptr_array_add (data, watcher); + } + if (data->len) { + g_value_init (value, G_TYPE_PTR_ARRAY); + g_value_take_boxed (value, data); + } else + g_ptr_array_free (data, TRUE); + + } else if (json_is_array (json_element)) { + GPtrArray *data = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free); + json_t *str_element; + int index; + + json_array_foreach (json_element, index, str_element) { + if (json_is_string (str_element)) + g_ptr_array_add (data, g_strdup (json_string_value (str_element))); + } + if (data->len) { + g_value_init (value, G_TYPE_STRV); + g_value_take_boxed (value, _nm_utils_ptrarray_to_strv (data)); + } + g_ptr_array_free (data, TRUE); + } else { + g_assert_not_reached (); + g_free (value); + value = NULL; + } + } + + if (json) + json_decref (json); + + return value; +} + +/* if conf is updated in place returns TRUE */ +gboolean +_nm_utils_team_config_set (char **conf, + const char *key, + const char *key2, + const char *key3, + const GValue *value) +{ + json_t *json, *json_element, *json_link, *json_value = NULL; + json_error_t jerror; + gboolean updated = FALSE; + char **strv; + GPtrArray *array; + const char *iter_key = key; + int i; + NMTeamLinkWatcher *watcher; + + g_return_val_if_fail (key, FALSE); + + json = json_loads (*conf?: "{}", JSON_REJECT_DUPLICATES, &jerror); + if (!json) + return FALSE; + + /* no new value? delete element */ + if (!value) { + updated = _json_del_object (json, key, key2, key3); + goto done; + } + + /* insert new value */ + updated = TRUE; + if (G_VALUE_HOLDS_STRING (value)) + json_value = json_string (g_value_get_string (value)); + else if (G_VALUE_HOLDS_INT (value)) + json_value = json_integer (g_value_get_int (value)); + else if (G_VALUE_HOLDS_BOOLEAN (value)) + json_value = json_boolean (g_value_get_boolean (value)); + else if (G_VALUE_HOLDS_BOXED (value)) { + if (nm_streq (key, "link_watch")) { + array = g_value_get_boxed (value); + if (!array || !array->len) { + updated = FALSE; + goto done; + } + + /* + * json_value: will hold the final link_watcher json (array) object + * json_element: is the next link_watcher to append to json_value + * json_link: used to transit the json_value from a single link_watcher + * object to an array of link watcher objects + */ + json_value = NULL; + for (i = 0; i < array->len; i++) { + watcher = array->pdata[i]; + json_element = _nm_utils_team_link_watcher_to_json (watcher); + if (!json_element) + continue; + if (!json_value) { + json_value = json_element; + continue; + } + if (!json_is_array (json_value)) { + json_link = json_value; + json_value = json_array (); + json_array_append_new (json_value, json_link); + } + json_array_append_new (json_value, json_element); + } + } else if ( nm_streq (key, "runner") + && nm_streq0 (key2, "tx_hash")) { + strv = g_value_get_boxed (value); + if (!strv) { + updated = FALSE; + goto done; + } + json_value = json_array (); + for (i = 0; strv[i]; i++) + json_array_append_new (json_value, json_string (strv[i])); + } else { + updated = FALSE; + goto done; + } + } else { /* G_VALUE_HOLDS_? */ + g_assert_not_reached (); + updated = FALSE; + goto done; + } + + /* Simplest case: first level key only */ + json_element = json; + json_link = NULL; + + if (key2) { + json_link = json; + json_element = json_object_get (json, iter_key); + if (!json_element) { + json_element = json_object (); + json_object_set_new (json_link, iter_key, json_element); + } + iter_key = key2; + } + if (key3) { + json_link = json_element; + json_element = json_object_get (json_link, iter_key); + if (!json_element) { + json_element = json_object (); + json_object_set_new (json_link, iter_key, json_element); + } + iter_key = key3; + } + + json_object_set_new (json_element, iter_key, json_value); + +done: + if (updated) { + _json_team_normalize_defaults (json, ( nm_streq0 (key, "runner") + && nm_streq0 (key2, "name"))); + g_free (*conf); + *conf = json_dumps (json, JSON_PRESERVE_ORDER); + /* Don't save an empty config */ + if (nm_streq0 (*conf, "{}")) { + g_free (*conf); + *conf = NULL; + } + } + json_decref (json); + return updated; +} + #else /* WITH_JANSSON */ gboolean @@ -4491,8 +5692,224 @@ _nm_utils_team_config_equal (const char *conf1, { return nm_streq0 (conf1, conf2); } + +GValue * +_nm_utils_team_config_get (const char *conf, + const char *key, + const char *key2, + const char *key3, + gboolean port_config) +{ + return NULL; +} + +gboolean +_nm_utils_team_config_set (char **conf, + const char *key, + const char *key2, + const char *key3, + const GValue *value) +{ + return FALSE; +} #endif +/** + * _nm_utils_team_link_watchers_to_variant: + * @link_watchers: (element-type NMTeamLinkWatcher): array of #NMTeamLinkWatcher + * + * Utility function to convert a #GPtrArray of #NMTeamLinkWatcher objects + * representing link watcher configuration for team devices into a #GVariant + * of type 'aa{sv}' representing an array of link watchers. + * + * Returns: (transfer none): a new floating #GVariant representing link watchers. + **/ +GVariant * +_nm_utils_team_link_watchers_to_variant (GPtrArray *link_watchers) +{ + GVariantBuilder builder; + int i; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}")); + + if (!link_watchers) + goto end; + + for (i = 0; i < link_watchers->len; i++) { + NMTeamLinkWatcher *watcher = link_watchers->pdata[i]; + GVariantBuilder watcher_builder; + const char *name; + int int_val; + NMTeamLinkWatcherArpPingFlags flags; + + g_variant_builder_init (&watcher_builder, G_VARIANT_TYPE ("a{sv}")); + + name = nm_team_link_watcher_get_name (watcher); + g_variant_builder_add (&watcher_builder, "{sv}", + "name", + g_variant_new_string (name)); + + if nm_streq (name, NM_TEAM_LINK_WATCHER_ETHTOOL) { + int_val = nm_team_link_watcher_get_delay_up (watcher); + if (int_val) { + g_variant_builder_add (&watcher_builder, "{sv}", + "delay-up", + g_variant_new_int32 (int_val)); + } + int_val = nm_team_link_watcher_get_delay_down (watcher); + if (int_val) { + g_variant_builder_add (&watcher_builder, "{sv}", + "delay-down", + g_variant_new_int32 (int_val)); + } + g_variant_builder_add (&builder, "a{sv}", &watcher_builder); + continue; + } + + /* Common properties for arp_ping and nsna_ping link watchers */ + int_val = nm_team_link_watcher_get_init_wait (watcher); + if (int_val) { + g_variant_builder_add (&watcher_builder, "{sv}", + "init-wait", + g_variant_new_int32 (int_val)); + } + int_val = nm_team_link_watcher_get_interval (watcher); + if (int_val) { + g_variant_builder_add (&watcher_builder, "{sv}", + "interval", + g_variant_new_int32 (int_val)); + } + int_val = nm_team_link_watcher_get_missed_max (watcher); + if (int_val != 3) { + g_variant_builder_add (&watcher_builder, "{sv}", + "missed-max", + g_variant_new_int32 (int_val)); + } + g_variant_builder_add (&watcher_builder, "{sv}", + "target-host", + g_variant_new_string (nm_team_link_watcher_get_target_host (watcher))); + + if nm_streq (name, NM_TEAM_LINK_WATCHER_NSNA_PING) { + g_variant_builder_add (&builder, "a{sv}", &watcher_builder); + continue; + } + + /* arp_ping watcher only */ + g_variant_builder_add (&watcher_builder, "{sv}", + "source-host", + g_variant_new_string (nm_team_link_watcher_get_source_host (watcher))); + flags = nm_team_link_watcher_get_flags (watcher); + if (flags & NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE) { + g_variant_builder_add (&watcher_builder, "{sv}", + "validate-active", + g_variant_new_boolean (TRUE)); + } + if (flags & NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE) { + g_variant_builder_add (&watcher_builder, "{sv}", + "validate-inactive", + g_variant_new_boolean (TRUE)); + } + if (flags & NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS) { + g_variant_builder_add (&watcher_builder, "{sv}", + "send-always", + g_variant_new_boolean (TRUE)); + } + g_variant_builder_add (&builder, "a{sv}", &watcher_builder); + } +end: + return g_variant_builder_end (&builder); +} + +/** + * _nm_utils_team_link_watchers_from_variant: + * @value: a #GVariant of type 'aa{sv}' + * + * Utility function to convert a #GVariant representing a list of team link + * watchers int a #GPtrArray of #NMTeamLinkWatcher objects. + * + * Returns: (transfer full) (element-type NMTeamLinkWatcher): a newly allocated + * #GPtrArray of #NMTeamLinkWatcher objects. + **/ +GPtrArray * +_nm_utils_team_link_watchers_from_variant (GVariant *value) +{ + GPtrArray *link_watchers; + GVariantIter iter; + GVariant *watcher_var; + + g_return_val_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE ("aa{sv}")), NULL); + + link_watchers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_team_link_watcher_unref); + g_variant_iter_init (&iter, value); + + while (g_variant_iter_next (&iter, "@a{sv}", &watcher_var)) { + NMTeamLinkWatcher *watcher; + const char *name; + int val1, val2, val3 = 0; + const char *target_host = NULL, *source_host = NULL; + gboolean bval; + NMTeamLinkWatcherArpPingFlags flags = NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE; + GError *error = NULL; + + if (!g_variant_lookup (watcher_var, "name", "&s", &name)) + goto next; + if (!NM_IN_STRSET (name, + NM_TEAM_LINK_WATCHER_ETHTOOL, + NM_TEAM_LINK_WATCHER_ARP_PING, + NM_TEAM_LINK_WATCHER_NSNA_PING)) { + goto next; + } + + if (nm_streq (name, NM_TEAM_LINK_WATCHER_ETHTOOL)) { + if (!g_variant_lookup (watcher_var, "delay-up", "i", &val1)) + val1 = 0; + if (!g_variant_lookup (watcher_var, "delay-down", "i", &val2)) + val2 = 0; + watcher = nm_team_link_watcher_new_ethtool (val1, val2, &error); + } else { + if (!g_variant_lookup (watcher_var, "target-host", "&s", &target_host)) + goto next; + if (!g_variant_lookup (watcher_var, "init_wait", "i", &val1)) + val1 = 0; + if (!g_variant_lookup (watcher_var, "interval", "i", &val2)) + val2 = 0; + if (!g_variant_lookup (watcher_var, "missed-max", "i", &val3)) + val3 = 3; + if nm_streq (name, NM_TEAM_LINK_WATCHER_ARP_PING) { + if (!g_variant_lookup (watcher_var, "source-host", "&s", &source_host)) + goto next; + if (!g_variant_lookup (watcher_var, "validate-active", "b", &bval)) + bval = FALSE; + if (bval) + flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE; + if (!g_variant_lookup (watcher_var, "validate-inactive", "b", &bval)) + bval = FALSE; + if (bval) + flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE; + if (!g_variant_lookup (watcher_var, "send-always", "b", &bval)) + bval = FALSE; + if (bval) + flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS; + watcher = nm_team_link_watcher_new_arp_ping (val1, val2, val3, + target_host, source_host, + flags, &error); + } else + watcher = nm_team_link_watcher_new_nsna_ping (val1, val2, val3, + target_host, &error); + } + if (!watcher) { + g_clear_error (&error); + goto next; + } + + g_ptr_array_add (link_watchers, watcher); +next: + g_variant_unref (watcher_var); + } + + return link_watchers; +} + static char * attribute_escape (const char *src, char c1, char c2) { @@ -4574,8 +5991,10 @@ nm_utils_parse_variant_attributes (const char *string, if (*ptr == '\\') { ptr++; if (!*ptr) { - g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED, - _("unterminated escape sequence")); + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("unterminated escape sequence")); return NULL; } goto next; @@ -4592,8 +6011,10 @@ nm_utils_parse_variant_attributes (const char *string, if (*sep == '\\') { sep++; if (!*sep) { - g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED, - _("unterminated escape sequence")); + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("unterminated escape sequence")); return NULL; } } @@ -4601,18 +6022,16 @@ nm_utils_parse_variant_attributes (const char *string, break; } - if (*sep != key_value_separator) { - g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED, - _("missing key-value separator '%c'"), key_value_separator); - return NULL; - } - name = attribute_unescape (start, sep); - value = attribute_unescape (sep + 1, ptr); for (s = spec; *s; s++) { + if (g_hash_table_contains (ht, (*s)->name)) + continue; if (nm_streq (name, (*s)->name)) break; + if ( (*s)->no_value + && g_variant_type_equal ((*s)->type, G_VARIANT_TYPE_STRING)) + break; } if (!*s) { @@ -4625,12 +6044,33 @@ nm_utils_parse_variant_attributes (const char *string, } } + if ((*s)->no_value) { + if ((*s)->consumes_rest) { + value = g_strdup (start); + ptr = strchr (start, '\0'); + } else { + value = g_steal_pointer (&name); + } + } else { + if (*sep != key_value_separator) { + g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED, + _("missing key-value separator '%c' after '%s'"), key_value_separator, name); + return NULL; + } + + /* The attribute and key/value separators are the same. Look for the next one. */ + if (ptr == sep) + goto next; + + value = attribute_unescape (sep + 1, ptr); + } + if (g_variant_type_equal ((*s)->type, G_VARIANT_TYPE_UINT32)) { gint64 num = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, -1); if (num == -1) { g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED, - _("invalid uint32 value '%s' for attribute '%s'"), value, name); + _("invalid uint32 value '%s' for attribute '%s'"), value, (*s)->name); return NULL; } variant = g_variant_new_uint32 (num); @@ -4639,33 +6079,32 @@ nm_utils_parse_variant_attributes (const char *string, if (num == -1) { g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED, - _("invalid uint8 value '%s' for attribute '%s'"), value, name); + _("invalid uint8 value '%s' for attribute '%s'"), value, (*s)->name); return NULL; } variant = g_variant_new_byte ((guchar) num); } else if (g_variant_type_equal ((*s)->type, G_VARIANT_TYPE_BOOLEAN)) { - gboolean b; + int b; - if (nm_streq (value, "true")) - b = TRUE; - else if (nm_streq (value, "false")) - b = FALSE; - else { + b = (*s)->no_value ? TRUE :_nm_utils_ascii_str_to_bool (value, -1); + if (b == -1) { g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED, - _("invalid boolean value '%s' for attribute '%s'"), value, name); + _("invalid boolean value '%s' for attribute '%s'"), value, (*s)->name); return NULL; } variant = g_variant_new_boolean (b); } else if (g_variant_type_equal ((*s)->type, G_VARIANT_TYPE_STRING)) { variant = g_variant_new_take_string (g_steal_pointer (&value)); + } else if (g_variant_type_equal ((*s)->type, G_VARIANT_TYPE_BYTESTRING)) { + variant = g_variant_new_bytestring (value); } else { g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED, - _("unsupported attribute '%s' of type '%s'"), name, + _("unsupported attribute '%s' of type '%s'"), (*s)->name, (char *) (*s)->type); return NULL; } - g_hash_table_insert (ht, g_steal_pointer (&name), variant); + g_hash_table_insert (ht, g_strdup ((*s)->name), variant); start = NULL; } next: @@ -4726,6 +6165,8 @@ nm_utils_format_variant_attributes (GHashTable *attributes, value = g_variant_get_boolean (variant) ? "true" : "false"; else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) value = g_variant_get_string (variant, NULL); + else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BYTESTRING)) + value = g_variant_get_bytestring (variant); else continue; |