/* SPDX-License-Identifier: LGPL-2.1+ */ /* * Copyright (C) 2019 Red Hat, Inc. */ #define NM_VALUE_TYPE_DEFINE_FUNCTIONS #include "nm-default.h" #include "nm-team-utils.h" #include "nm-errors.h" #include "nm-utils-private.h" #include "nm-glib-aux/nm-json-aux.h" #include "nm-core-internal.h" #include "nm-setting-team.h" #include "nm-setting-team-port.h" /*****************************************************************************/ typedef enum { SET_FIELD_MODE_UNSET = 0, SET_FIELD_MODE_SET = 1, /* Sets the field as set, unless the field is at the default. * This is the case for API that is called from NMSettingTeam/NMSettingTeamPort. * This means, using libnm API to reset the value of a NMSetting to the default, * will mark the field as unset. * This is different from initializing the field when parsing JSON/GVariant. In * that case an explicitly set field (even set to the default value) will be remembered * to be set. */ SET_FIELD_MODE_SET_UNLESS_DEFAULT = 2, } SetFieldModeEnum; typedef enum { RESET_JSON_NO = FALSE, RESET_JSON_YES = TRUE, } ResetJsonEnum; /* we rely on "config" being the first. At various places we iterate over attribute types, * starting after "config".*/ G_STATIC_ASSERT(_NM_TEAM_ATTRIBUTE_0 == 0); G_STATIC_ASSERT(NM_TEAM_ATTRIBUTE_CONFIG == 1); static const char *const _valid_names_runner[] = { NM_SETTING_TEAM_RUNNER_BROADCAST, NM_SETTING_TEAM_RUNNER_ROUNDROBIN, NM_SETTING_TEAM_RUNNER_RANDOM, NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP, NM_SETTING_TEAM_RUNNER_LOADBALANCE, NM_SETTING_TEAM_RUNNER_LACP, NULL, }; static const char *const _valid_names_runner_hwaddr_policy[] = { NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_SAME_ALL, NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_BY_ACTIVE, NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_ONLY_ACTIVE, NULL, }; static const char *const _valid_names_runner_tx_balancer[] = { "basic", NULL, }; static const char *const _valid_names_runner_tx_hash[] = { "eth", "vlan", "ipv4", "ipv6", "ip", "l3", "l4", "tcp", "udp", "sctp", NULL, }; static const char *const _valid_names_runner_agg_select_policy[] = { "lacp_prio", "lacp_prio_stable", "bandwidth", "count", "port_config", NULL, }; typedef struct { NMTeamAttribute team_attr; const char *const *valid_runners; } RunnerCompatElem; static const RunnerCompatElem _runner_compat_lst[] = { { NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY, NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP), }, { NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LOADBALANCE, NM_SETTING_TEAM_RUNNER_LACP), }, { NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER, NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LOADBALANCE, NM_SETTING_TEAM_RUNNER_LACP), }, { NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL, NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LOADBALANCE, NM_SETTING_TEAM_RUNNER_LACP), }, { NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE, NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LACP), }, { NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE, NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LACP), }, { NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO, NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LACP), }, { NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS, NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LACP), }, { NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY, NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LACP), }, }; typedef struct { const char *const *js_keys; const char * property_name; NMValueTypUnion default_val; union { struct { gint32 min; gint32 max; } r_int32; struct { const char *const *valid_names; } r_string; } range; NMTeamAttribute team_attr; NMValueType value_type; guint8 field_offset; guint8 js_keys_len; bool for_master : 1; bool for_port : 1; bool has_range : 1; } TeamAttrData; #define TEAM_ATTR_IDX(_is_port, _team_attr) \ (((!(_is_port) || (_team_attr) < _NM_TEAM_ATTRIBUTE_START) \ ? (int) (_team_attr) \ : (((int) (_NM_TEAM_ATTRIBUTE_MASTER_NUM - _NM_TEAM_ATTRIBUTE_START)) \ + ((int) (_team_attr)))) \ - 1) #define TEAM_ATTR_IDX_CONFIG (TEAM_ATTR_IDX(FALSE, NM_TEAM_ATTRIBUTE_CONFIG)) static const TeamAttrData team_attr_datas[] = { #define _JS_KEYS(...) .js_keys = NM_MAKE_STRV(__VA_ARGS__), .js_keys_len = NM_NARG(__VA_ARGS__) #define _VAL_BOOL(_default) .default_val.v_bool = (_default) #define _VAL_INT32(_default) .default_val.v_int32 = (_default) #define _VAL_INT32_RANGE(_default, _min, _max) \ _VAL_INT32(_default), .has_range = TRUE, \ .range.r_int32 = { \ .min = _min, \ .max = _max, \ } #define _VAL_STRING() .default_val.v_string = NULL #define _VAL_STRING_RANGE(_valid_names) \ _VAL_STRING(), .has_range = TRUE, \ .range.r_string = { \ .valid_names = (_valid_names), \ } #define _VAL_UNSPEC() .default_val.v_string = (NULL) #define _INIT(_is_port, _team_attr, field, _value_type, _property_name, ...) \ [TEAM_ATTR_IDX(_is_port, _team_attr)] = { \ .for_master = (_team_attr) < _NM_TEAM_ATTRIBUTE_START || !(_is_port), \ .for_port = (_team_attr) < _NM_TEAM_ATTRIBUTE_START || (_is_port), \ .team_attr = (_team_attr), \ .field_offset = G_STRUCT_OFFSET(NMTeamSetting, _data_priv.field), \ .value_type = (_value_type), \ .property_name = ""_property_name \ "", \ __VA_ARGS__} _INIT(0, NM_TEAM_ATTRIBUTE_CONFIG, _js_str, NM_VALUE_TYPE_UNSPEC, NM_SETTING_TEAM_CONFIG, ), _INIT(0, NM_TEAM_ATTRIBUTE_LINK_WATCHERS, link_watchers, NM_VALUE_TYPE_UNSPEC, NM_SETTING_TEAM_LINK_WATCHERS, _JS_KEYS("link_watch"), _VAL_UNSPEC(), ), _INIT(0, NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT, master.notify_peers_count, NM_VALUE_TYPE_INT32, NM_SETTING_TEAM_NOTIFY_PEERS_COUNT, _JS_KEYS("notify_peers", "count"), _VAL_INT32_RANGE(-1, 0, G_MAXINT32), ), _INIT(0, NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL, master.notify_peers_interval, NM_VALUE_TYPE_INT32, NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL, _JS_KEYS("notify_peers", "interval"), _VAL_INT32_RANGE(-1, 0, G_MAXINT32), ), _INIT(0, NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT, master.mcast_rejoin_count, NM_VALUE_TYPE_INT32, NM_SETTING_TEAM_MCAST_REJOIN_COUNT, _JS_KEYS("mcast_rejoin", "count"), _VAL_INT32_RANGE(-1, 0, G_MAXINT32), ), _INIT(0, NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL, master.mcast_rejoin_interval, NM_VALUE_TYPE_INT32, NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL, _JS_KEYS("mcast_rejoin", "interval"), _VAL_INT32_RANGE(-1, 0, G_MAXINT32), ), _INIT(0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER, master.runner, NM_VALUE_TYPE_STRING, NM_SETTING_TEAM_RUNNER, _JS_KEYS("runner", "name"), _VAL_STRING_RANGE(_valid_names_runner), ), _INIT(0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY, master.runner_hwaddr_policy, NM_VALUE_TYPE_STRING, NM_SETTING_TEAM_RUNNER_HWADDR_POLICY, _JS_KEYS("runner", "hwaddr_policy"), _VAL_STRING_RANGE(_valid_names_runner_hwaddr_policy), ), _INIT(0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, master.runner_tx_hash, NM_VALUE_TYPE_UNSPEC, NM_SETTING_TEAM_RUNNER_TX_HASH, _JS_KEYS("runner", "tx_hash"), _VAL_UNSPEC(), ), _INIT(0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER, master.runner_tx_balancer, NM_VALUE_TYPE_STRING, NM_SETTING_TEAM_RUNNER_TX_BALANCER, _JS_KEYS("runner", "tx_balancer", "name"), _VAL_STRING_RANGE(_valid_names_runner_tx_balancer), ), _INIT(0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL, master.runner_tx_balancer_interval, NM_VALUE_TYPE_INT32, NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL, _JS_KEYS("runner", "tx_balancer", "balancing_interval"), _VAL_INT32_RANGE(-1, 0, G_MAXINT32), ), _INIT(0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE, master.runner_active, NM_VALUE_TYPE_BOOL, NM_SETTING_TEAM_RUNNER_ACTIVE, _JS_KEYS("runner", "active"), _VAL_BOOL(TRUE), ), _INIT(0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE, master.runner_fast_rate, NM_VALUE_TYPE_BOOL, NM_SETTING_TEAM_RUNNER_FAST_RATE, _JS_KEYS("runner", "fast_rate"), _VAL_BOOL(FALSE), ), _INIT(0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO, master.runner_sys_prio, NM_VALUE_TYPE_INT32, NM_SETTING_TEAM_RUNNER_SYS_PRIO, _JS_KEYS("runner", "sys_prio"), _VAL_INT32_RANGE(-1, 0, USHRT_MAX + 1), ), _INIT(0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS, master.runner_min_ports, NM_VALUE_TYPE_INT32, NM_SETTING_TEAM_RUNNER_MIN_PORTS, _JS_KEYS("runner", "min_ports"), _VAL_INT32_RANGE(-1, 1, UCHAR_MAX + 1), ), _INIT(0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY, master.runner_agg_select_policy, NM_VALUE_TYPE_STRING, NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY, _JS_KEYS("runner", "agg_select_policy"), _VAL_STRING_RANGE(_valid_names_runner_agg_select_policy), ), _INIT(1, NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID, port.queue_id, NM_VALUE_TYPE_INT32, NM_SETTING_TEAM_PORT_QUEUE_ID, _JS_KEYS("queue_id"), _VAL_INT32_RANGE(-1, 0, G_MAXINT32), ), _INIT(1, NM_TEAM_ATTRIBUTE_PORT_PRIO, port.prio, NM_VALUE_TYPE_INT32, NM_SETTING_TEAM_PORT_PRIO, _JS_KEYS("prio"), _VAL_INT32(0), ), _INIT(1, NM_TEAM_ATTRIBUTE_PORT_STICKY, port.sticky, NM_VALUE_TYPE_BOOL, NM_SETTING_TEAM_PORT_STICKY, _JS_KEYS("sticky"), _VAL_BOOL(FALSE), ), _INIT(1, NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO, port.lacp_prio, NM_VALUE_TYPE_INT32, NM_SETTING_TEAM_PORT_LACP_PRIO, _JS_KEYS("lacp_prio"), _VAL_INT32_RANGE(-1, 0, USHRT_MAX + 1), ), _INIT(1, NM_TEAM_ATTRIBUTE_PORT_LACP_KEY, port.lacp_key, NM_VALUE_TYPE_INT32, NM_SETTING_TEAM_PORT_LACP_KEY, _JS_KEYS("lacp_key"), _VAL_INT32_RANGE(-1, 0, USHRT_MAX + 1), ), #undef _INIT }; /*****************************************************************************/ typedef enum { LINK_WATCHER_ATTRIBUTE_NAME, LINK_WATCHER_ATTRIBUTE_DELAY_UP, LINK_WATCHER_ATTRIBUTE_DELAY_DOWN, LINK_WATCHER_ATTRIBUTE_INTERVAL, LINK_WATCHER_ATTRIBUTE_INIT_WAIT, LINK_WATCHER_ATTRIBUTE_MISSED_MAX, LINK_WATCHER_ATTRIBUTE_SOURCE_HOST, LINK_WATCHER_ATTRIBUTE_TARGET_HOST, LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE, LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE, LINK_WATCHER_ATTRIBUTE_VLANID, LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS, } LinkWatcherAttribute; #define _EXPECTED_LINK_WATCHER_ATTRIBUTES_ETHTOOL \ LINK_WATCHER_ATTRIBUTE_NAME, LINK_WATCHER_ATTRIBUTE_DELAY_UP, LINK_WATCHER_ATTRIBUTE_DELAY_DOWN #define _EXPECTED_LINK_WATCHER_ATTRIBUTES_NSNA_PING \ LINK_WATCHER_ATTRIBUTE_NAME, LINK_WATCHER_ATTRIBUTE_INTERVAL, \ LINK_WATCHER_ATTRIBUTE_INIT_WAIT, LINK_WATCHER_ATTRIBUTE_MISSED_MAX, \ LINK_WATCHER_ATTRIBUTE_TARGET_HOST #define _EXPECTED_LINK_WATCHER_ATTRIBUTES_ARP_PING \ LINK_WATCHER_ATTRIBUTE_NAME, LINK_WATCHER_ATTRIBUTE_INTERVAL, \ LINK_WATCHER_ATTRIBUTE_INIT_WAIT, LINK_WATCHER_ATTRIBUTE_MISSED_MAX, \ LINK_WATCHER_ATTRIBUTE_SOURCE_HOST, LINK_WATCHER_ATTRIBUTE_TARGET_HOST, \ LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE, LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE, \ LINK_WATCHER_ATTRIBUTE_VLANID, LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS typedef struct { const char * js_key; const char * dbus_name; NMValueTypUnion default_val; LinkWatcherAttribute link_watcher_attr; NMValueType value_type; } LinkWatcherAttrData; static const LinkWatcherAttrData link_watcher_attr_datas[] = { #define _INIT(_link_watcher_attr, _js_key, _dbus_name, _value_type, ...) \ [_link_watcher_attr] = {.link_watcher_attr = (_link_watcher_attr), \ .value_type = (_value_type), \ .js_key = (""_js_key \ ""), \ .dbus_name = (""_dbus_name \ ""), \ __VA_ARGS__} _INIT(LINK_WATCHER_ATTRIBUTE_NAME, "name", "name", NM_VALUE_TYPE_STRING, ), _INIT(LINK_WATCHER_ATTRIBUTE_DELAY_UP, "delay_up", "delay-up", NM_VALUE_TYPE_INT, ), _INIT(LINK_WATCHER_ATTRIBUTE_DELAY_DOWN, "delay_down", "delay-down", NM_VALUE_TYPE_INT, ), _INIT(LINK_WATCHER_ATTRIBUTE_INTERVAL, "interval", "interval", NM_VALUE_TYPE_INT, ), _INIT(LINK_WATCHER_ATTRIBUTE_INIT_WAIT, "init_wait", "init-wait", NM_VALUE_TYPE_INT, ), _INIT(LINK_WATCHER_ATTRIBUTE_MISSED_MAX, "missed_max", "missed-max", NM_VALUE_TYPE_INT, .default_val.v_int = 3, ), _INIT(LINK_WATCHER_ATTRIBUTE_SOURCE_HOST, "source_host", "source-host", NM_VALUE_TYPE_STRING, ), _INIT(LINK_WATCHER_ATTRIBUTE_TARGET_HOST, "target_host", "target-host", NM_VALUE_TYPE_STRING, ), _INIT(LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE, "validate_active", "validate-active", NM_VALUE_TYPE_BOOL, ), _INIT(LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE, "validate_inactive", "validate-inactive", NM_VALUE_TYPE_BOOL, ), _INIT(LINK_WATCHER_ATTRIBUTE_VLANID, "vlanid", "vlanid", NM_VALUE_TYPE_INT, .default_val.v_int = -1, ), _INIT(LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS, "send_always", "send-always", NM_VALUE_TYPE_BOOL, ), #undef _INIT }; /*****************************************************************************/ static const TeamAttrData *_team_attr_data_get(gboolean is_port, NMTeamAttribute team_attr); static gpointer _team_setting_get_field(const NMTeamSetting *self, const TeamAttrData *attr_data); static void _link_watcher_to_json(const NMTeamLinkWatcher *link_watcher, GString *gstr); /*****************************************************************************/ static void _team_attr_data_ASSERT(const TeamAttrData *attr_data) { #if NM_MORE_ASSERTS > 5 nm_assert(attr_data); if (attr_data->for_port) nm_assert(attr_data == _team_attr_data_get(TRUE, attr_data->team_attr)); if (attr_data->for_master) nm_assert(attr_data == _team_attr_data_get(FALSE, attr_data->team_attr)); nm_assert((attr_data - team_attr_datas) == TEAM_ATTR_IDX(attr_data->for_port, attr_data->team_attr)); nm_assert(attr_data->value_type > 0); nm_assert(attr_data->field_offset < sizeof(NMTeamSetting)); nm_assert(attr_data->js_keys_len == NM_PTRARRAY_LEN(attr_data->js_keys)); nm_assert(attr_data->property_name); { static int checked = 0; if (checked == 0) { checked = 1; for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; attr_data++) _team_attr_data_ASSERT(attr_data); } } #endif } static gboolean _team_attr_data_is_relevant(const TeamAttrData *attr_data, gboolean is_port) { return is_port ? attr_data->for_port : attr_data->for_master; } static const TeamAttrData * _team_attr_data_get(gboolean is_port, NMTeamAttribute team_attr) { const int idx = TEAM_ATTR_IDX(is_port, team_attr); nm_assert(idx >= 0 && idx < G_N_ELEMENTS(team_attr_datas)); nm_assert(team_attr_datas[idx].team_attr == team_attr); nm_assert(_team_attr_data_is_relevant(&team_attr_datas[idx], is_port)); return &team_attr_datas[idx]; } static const TeamAttrData * _team_attr_data_find_for_property_name(gboolean is_port, const char *property_name) { const TeamAttrData *attr_data; for (attr_data = team_attr_datas; attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; attr_data++) { if (_team_attr_data_is_relevant(attr_data, is_port) && nm_streq(property_name, attr_data->property_name)) return attr_data; } return NULL; } static int _team_attr_data_cmp(const TeamAttrData *attr_data, gboolean is_port, gconstpointer val_a, gconstpointer val_b) { const GPtrArray *v_ptrarray_a; const GPtrArray *v_ptrarray_b; guint len; _team_attr_data_ASSERT(attr_data); nm_assert(val_a); nm_assert(val_b); if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) NM_CMP_RETURN(nm_value_type_cmp(attr_data->value_type, val_a, val_b)); else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) { v_ptrarray_a = *((const GPtrArray *const *) val_a); v_ptrarray_b = *((const GPtrArray *const *) val_b); len = v_ptrarray_a ? v_ptrarray_a->len : 0u; NM_CMP_DIRECT(len, (v_ptrarray_b ? v_ptrarray_b->len : 0u)); if (len > 0) { NM_CMP_RETURN( nm_team_link_watchers_cmp((const NMTeamLinkWatcher *const *) v_ptrarray_a->pdata, (const NMTeamLinkWatcher *const *) v_ptrarray_b->pdata, len, FALSE)); } } else if (!is_port && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) { v_ptrarray_a = *((const GPtrArray *const *) val_a); v_ptrarray_b = *((const GPtrArray *const *) val_b); NM_CMP_RETURN( _nm_utils_strv_cmp_n(v_ptrarray_a ? (const char *const *) v_ptrarray_a->pdata : NULL, v_ptrarray_a ? v_ptrarray_a->len : 0u, v_ptrarray_b ? (const char *const *) v_ptrarray_b->pdata : NULL, v_ptrarray_b ? v_ptrarray_b->len : 0u)); } else nm_assert_not_reached(); return 0; } static gboolean _team_attr_data_equal(const TeamAttrData *attr_data, gboolean is_port, gconstpointer val_a, gconstpointer val_b) { return _team_attr_data_cmp(attr_data, is_port, val_a, val_b) == 0; } static void _team_attr_data_copy(const TeamAttrData *attr_data, gboolean is_port, gpointer dst, gconstpointer src) { GPtrArray * v_ptrarray_dst; const GPtrArray *v_ptrarray_src; GPtrArray * dst_array; guint i, len; if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) nm_value_type_copy(attr_data->value_type, dst, src); else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) { v_ptrarray_src = *((const GPtrArray *const *) src); v_ptrarray_dst = *((GPtrArray **) dst); len = (v_ptrarray_src ? v_ptrarray_src->len : 0u); if (len == 0) { if (v_ptrarray_dst) g_ptr_array_set_size(v_ptrarray_dst, 0); } else { dst_array = g_ptr_array_new_full(len, (GDestroyNotify) nm_team_link_watcher_unref); for (i = 0; i < len; i++) { if (v_ptrarray_src->pdata[i]) { nm_team_link_watcher_ref(v_ptrarray_src->pdata[i]); g_ptr_array_add(dst_array, v_ptrarray_src->pdata[i]); } } if (v_ptrarray_dst) g_ptr_array_unref(v_ptrarray_dst); *((GPtrArray **) dst) = dst_array; } } else if (!is_port && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) { v_ptrarray_src = *((const GPtrArray *const *) src); v_ptrarray_dst = *((GPtrArray **) dst); len = (v_ptrarray_src ? v_ptrarray_src->len : 0u); if (v_ptrarray_src && v_ptrarray_src->len > 0) { dst_array = g_ptr_array_new_full(v_ptrarray_src->len, g_free); for (i = 0; i < v_ptrarray_src->len; i++) g_ptr_array_add(dst_array, g_strdup(v_ptrarray_src->pdata[i])); } else dst_array = NULL; if (v_ptrarray_dst) g_ptr_array_unref(v_ptrarray_dst); *((GPtrArray **) dst) = dst_array; } else nm_assert_not_reached(); } static void _team_attr_data_to_json(const TeamAttrData *attr_data, gboolean is_port, GString * gstr, gconstpointer p_field) { guint i; _team_attr_data_ASSERT(attr_data); nm_assert(p_field); nm_json_gstr_append_obj_name(gstr, attr_data->js_keys[attr_data->js_keys_len - 1], '\0'); if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) { nm_value_type_to_json(attr_data->value_type, gstr, p_field); return; } if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) { const GPtrArray *v_ptrarray = *((const GPtrArray *const *) p_field); if (!v_ptrarray) g_string_append(gstr, "null"); else if (v_ptrarray->len == 0) g_string_append(gstr, "[ ]"); else if (v_ptrarray->len == 1) _link_watcher_to_json(v_ptrarray->pdata[0], gstr); else { g_string_append(gstr, "[ "); for (i = 0; i < v_ptrarray->len; i++) { if (i > 0) nm_json_gstr_append_delimiter(gstr); _link_watcher_to_json(v_ptrarray->pdata[i], gstr); } g_string_append(gstr, " ]"); } return; } if (!is_port && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) { const GPtrArray *v_ptrarray = *((const GPtrArray *const *) p_field); if (!v_ptrarray) g_string_append(gstr, "null"); else { g_string_append(gstr, "[ "); for (i = 0; i < v_ptrarray->len; i++) { if (i > 0) nm_json_gstr_append_delimiter(gstr); nm_json_gstr_append_string(gstr, v_ptrarray->pdata[i]); } g_string_append(gstr, i > 0 ? " ]" : "]"); } return; } nm_assert_not_reached(); } /*****************************************************************************/ static void _team_setting_ASSERT(const NMTeamSetting *self) { nm_assert(self); nm_assert(!self->d._js_str_need_synthetize || !self->d._js_str); #if NM_MORE_ASSERTS > 2 if (!self->d.strict_validated) { nm_assert(!self->d._js_str_need_synthetize); nm_assert(self->d._js_str); } nm_assert(self->d.link_watchers); nm_assert(self->d.is_port || !self->d.master.runner_tx_hash || self->d.master.runner_tx_hash->len > 0); #endif } static gboolean _team_setting_has_field(const NMTeamSetting *self, const TeamAttrData *attr_data) { _team_setting_ASSERT(self); return NM_FLAGS_ALL(self->d.has_fields_mask, nm_team_attribute_to_flags(attr_data->team_attr)); } static gboolean _team_setting_has_fields_any_v(const NMTeamSetting * self, const NMTeamAttribute *team_attrs, gsize n_team_attrs) { gsize i; for (i = 0; i < n_team_attrs; i++) { const TeamAttrData *attr_data = _team_attr_data_get(self->d.is_port, team_attrs[i]); if (_team_setting_has_field(self, attr_data)) return TRUE; } return FALSE; } #define _team_setting_has_fields_any(self, ...) \ _team_setting_has_fields_any_v((self), \ ((const NMTeamAttribute[]){__VA_ARGS__}), \ NM_NARG(__VA_ARGS__)) static void _team_setting_has_field_set(NMTeamSetting * self, const TeamAttrData *attr_data, SetFieldModeEnum set_field_mode) { guint32 mask = nm_team_attribute_to_flags(attr_data->team_attr); _team_setting_ASSERT(self); switch (set_field_mode) { case SET_FIELD_MODE_UNSET: goto do_unset; case SET_FIELD_MODE_SET: goto do_set; case SET_FIELD_MODE_SET_UNLESS_DEFAULT: if (_team_attr_data_equal(attr_data, self->d.is_port, _team_setting_get_field(self, attr_data), &attr_data->default_val)) goto do_unset; goto do_set; } nm_assert_not_reached(); do_unset: self->_data_priv.has_fields_mask &= ~mask; return; do_set: self->_data_priv.has_fields_mask |= mask; } static gpointer _team_setting_get_field(const NMTeamSetting *self, const TeamAttrData *attr_data) { _team_setting_ASSERT(self); _team_attr_data_ASSERT(attr_data); nm_assert(_team_attr_data_is_relevant(attr_data, self->d.is_port)); #if NM_MORE_ASSERTS > 5 if (attr_data->for_master && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO) nm_assert((gpointer)(((char *) self) + attr_data->field_offset) == &self->d.master.runner_sys_prio); #endif return (((char *) self) + attr_data->field_offset); } static guint32 _team_setting_attribute_changed(NMTeamSetting * self, const TeamAttrData *attr_data, gboolean changed, SetFieldModeEnum set_field_mode, ResetJsonEnum reset_json) { guint32 changed_flags; _team_setting_has_field_set(self, attr_data, set_field_mode); if (!reset_json) { return changed ? nm_team_attribute_to_flags(attr_data->team_attr) : 0u; } if (!changed) { /* a regular attribute was set, but the value did not change. * * If we previously were in non-strict mode, then * * - switch to strict-mode. Clearly the user set a regular attribute * and hence now we want to validate the setting. * * - clear the JSON string. We need to regenerate it. */ if (self->_data_priv.strict_validated) return 0; changed_flags = nm_team_attribute_to_flags(NM_TEAM_ATTRIBUTE_CONFIG); } else { changed_flags = nm_team_attribute_to_flags(attr_data->team_attr) | nm_team_attribute_to_flags(NM_TEAM_ATTRIBUTE_CONFIG); } nm_clear_g_free((char **) &self->_data_priv._js_str); self->_data_priv.strict_validated = TRUE; self->_data_priv._js_str_need_synthetize = TRUE; return changed_flags; } static guint32 _team_setting_attribute_changed_attr(NMTeamSetting * self, NMTeamAttribute team_attr, gboolean changed, SetFieldModeEnum set_field_mode, ResetJsonEnum reset_json) { return _team_setting_attribute_changed(self, _team_attr_data_get(self->d.is_port, team_attr), changed, set_field_mode, reset_json); } static gboolean _team_setting_field_to_json(const NMTeamSetting *self, GString * gstr, gboolean prepend_delimiter, const TeamAttrData * attr_data) { if (!_team_setting_has_field(self, attr_data)) return FALSE; if (prepend_delimiter) nm_json_gstr_append_delimiter(gstr); _team_attr_data_to_json(attr_data, self->d.is_port, gstr, _team_setting_get_field(self, attr_data)); return TRUE; } static gboolean _team_setting_fields_to_json_maybe(const NMTeamSetting * self, GString * gstr, gboolean prepend_delimiter, const NMTeamAttribute *team_attrs_lst, gsize team_attrs_lst_len) { gsize i; gboolean any_added = FALSE; for (i = 0; i < team_attrs_lst_len; i++) { if (_team_setting_field_to_json(self, gstr, prepend_delimiter, _team_attr_data_get(self->d.is_port, team_attrs_lst[i]))) { any_added = TRUE; prepend_delimiter = TRUE; } } return any_added; } static guint32 _team_setting_set(NMTeamSetting * self, gboolean modify, const bool * has_lst, const NMValueTypUnion *val_lst) { guint32 changed_flags = 0; const TeamAttrData *attr_data; nm_assert((!has_lst) == (!val_lst)); for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; attr_data++) { const NMValueTypUnion *p_val; gconstpointer p_field; gboolean has_field; if (!_team_attr_data_is_relevant(attr_data, self->d.is_port)) continue; has_field = (has_lst && has_lst[attr_data->team_attr]); p_val = has_field ? &val_lst[attr_data->team_attr] : &attr_data->default_val; p_field = _team_setting_get_field(self, attr_data); if (!_team_attr_data_equal(attr_data, self->d.is_port, p_val, p_field)) { if (modify) { _team_attr_data_copy(attr_data, self->d.is_port, (gpointer) p_field, p_val); } changed_flags |= nm_team_attribute_to_flags(attr_data->team_attr); } if (modify) { _team_setting_has_field_set(self, attr_data, has_field ? SET_FIELD_MODE_SET : SET_FIELD_MODE_UNSET); } } return changed_flags; } static guint32 _team_setting_check_default(const NMTeamSetting *self) { return _team_setting_set((NMTeamSetting *) self, FALSE, NULL, NULL); } static guint32 _team_setting_set_default(NMTeamSetting *self) { return _team_setting_set(self, TRUE, NULL, NULL); } /*****************************************************************************/ gconstpointer _nm_team_setting_value_get(const NMTeamSetting *self, NMTeamAttribute team_attr, NMValueType value_type) { const TeamAttrData *attr_data = _team_attr_data_get(self->d.is_port, team_attr); nm_assert(value_type == attr_data->value_type); nm_assert(_team_setting_has_field(self, attr_data) || _team_attr_data_equal(attr_data, self->d.is_port, _team_setting_get_field(self, attr_data), &attr_data->default_val)); return _team_setting_get_field(self, attr_data); } static guint32 _team_setting_value_set(NMTeamSetting * self, const TeamAttrData *attr_data, gconstpointer val, SetFieldModeEnum set_field_mode, ResetJsonEnum reset_json) { gpointer p_field; gboolean changed; nm_assert(self); _team_attr_data_ASSERT(attr_data); nm_assert(val); p_field = _team_setting_get_field(self, attr_data); changed = !_team_attr_data_equal(attr_data, self->d.is_port, p_field, val); if (changed) nm_value_type_copy(attr_data->value_type, p_field, val); return _team_setting_attribute_changed(self, attr_data, changed, set_field_mode, reset_json); } guint32 nm_team_setting_value_reset(NMTeamSetting * self, NMTeamAttribute team_attr, gboolean to_default /* or else unset */) { const TeamAttrData *attr_data; nm_assert(self); attr_data = _team_attr_data_get(self->d.is_port, team_attr); return _team_setting_value_set(self, attr_data, &attr_data->default_val, to_default ? SET_FIELD_MODE_SET : SET_FIELD_MODE_UNSET, RESET_JSON_YES); } guint32 _nm_team_setting_value_set(NMTeamSetting * self, NMTeamAttribute team_attr, NMValueType value_type, gconstpointer val) { const TeamAttrData *attr_data; nm_assert(self); attr_data = _team_attr_data_get(self->d.is_port, team_attr); nm_assert(value_type == attr_data->value_type); return _team_setting_value_set(self, attr_data, val, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES); } guint32 nm_team_setting_value_link_watchers_add(NMTeamSetting *self, const NMTeamLinkWatcher *link_watcher) { guint i; gboolean changed; for (i = 0; i < self->d.link_watchers->len; i++) { if (nm_team_link_watcher_equal(self->d.link_watchers->pdata[i], link_watcher)) { changed = FALSE; goto out; } } changed = TRUE; g_ptr_array_add((GPtrArray *) self->d.link_watchers, _nm_team_link_watcher_ref((NMTeamLinkWatcher *) link_watcher)); out: return _team_setting_attribute_changed_attr(self, NM_TEAM_ATTRIBUTE_LINK_WATCHERS, changed, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES); } guint32 nm_team_setting_value_link_watchers_remove_by_value(NMTeamSetting * self, const NMTeamLinkWatcher *link_watcher) { guint i; for (i = 0; i < self->d.link_watchers->len; i++) { if (nm_team_link_watcher_equal(self->d.link_watchers->pdata[i], link_watcher)) return nm_team_setting_value_link_watchers_remove(self, i); } return _team_setting_attribute_changed_attr(self, NM_TEAM_ATTRIBUTE_LINK_WATCHERS, FALSE, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES); } guint32 nm_team_setting_value_link_watchers_remove(NMTeamSetting *self, guint idx) { g_ptr_array_remove_index((GPtrArray *) self->d.link_watchers, idx); return _team_setting_attribute_changed_attr(self, NM_TEAM_ATTRIBUTE_LINK_WATCHERS, TRUE, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES); } static guint32 _team_setting_value_link_watchers_set_list(NMTeamSetting * self, const NMTeamLinkWatcher *const *arr, guint len, SetFieldModeEnum set_field_mode, ResetJsonEnum reset_json) { gboolean changed; if (self->d.link_watchers->len == len && nm_team_link_watchers_cmp( (const NMTeamLinkWatcher *const *) self->d.link_watchers->pdata, arr, len, FALSE) == 0) { changed = FALSE; goto out; } changed = TRUE; if (len == 0) g_ptr_array_set_size((GPtrArray *) self->d.link_watchers, 0); else { _nm_unused gs_unref_ptrarray GPtrArray *old_val_destroy = NULL; guint i; old_val_destroy = (GPtrArray *) g_steal_pointer(&self->_data_priv.link_watchers); self->_data_priv.link_watchers = g_ptr_array_new_with_free_func((GDestroyNotify) nm_team_link_watcher_unref); for (i = 0; i < len; i++) { if (arr[i]) { g_ptr_array_add((GPtrArray *) self->d.link_watchers, _nm_team_link_watcher_ref((NMTeamLinkWatcher *) arr[i])); } } } out: return _team_setting_attribute_changed_attr(self, NM_TEAM_ATTRIBUTE_LINK_WATCHERS, changed, set_field_mode, reset_json); } guint32 nm_team_setting_value_link_watchers_set_list(NMTeamSetting * self, const NMTeamLinkWatcher *const *arr, guint len) { return _team_setting_value_link_watchers_set_list(self, arr, len, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES); } /*****************************************************************************/ guint32 nm_team_setting_value_master_runner_tx_hash_add(NMTeamSetting *self, const char *txhash) { gboolean changed; guint i; if (!self->d.master.runner_tx_hash) self->_data_priv.master.runner_tx_hash = g_ptr_array_new_with_free_func(g_free); else { for (i = 0; i < self->d.master.runner_tx_hash->len; i++) { if (nm_streq(txhash, self->d.master.runner_tx_hash->pdata[i])) { changed = FALSE; goto out; } } } changed = TRUE; g_ptr_array_add((GPtrArray *) self->d.master.runner_tx_hash, g_strdup(txhash)); out: return _team_setting_attribute_changed_attr(self, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, changed, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES); } guint32 nm_team_setting_value_master_runner_tx_hash_remove(NMTeamSetting *self, guint idx) { g_ptr_array_remove_index((GPtrArray *) self->d.master.runner_tx_hash, idx); return _team_setting_attribute_changed_attr(self, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, TRUE, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES); } static guint32 _team_setting_value_master_runner_tx_hash_set_list(NMTeamSetting * self, const char *const *arr, guint len, SetFieldModeEnum set_field_mode, ResetJsonEnum reset_json) { _nm_unused gs_unref_ptrarray GPtrArray *old_val_destroy = NULL; gboolean changed; guint i; if (_nm_utils_strv_cmp_n(self->d.master.runner_tx_hash ? (const char *const *) self->d.master.runner_tx_hash->pdata : NULL, self->d.master.runner_tx_hash ? self->d.master.runner_tx_hash->len : 0u, arr, len) == 0) { changed = FALSE; goto out; } changed = TRUE; old_val_destroy = (GPtrArray *) g_steal_pointer(&self->_data_priv.master.runner_tx_hash); for (i = 0; i < len; i++) { if (!arr[i]) continue; if (!self->d.master.runner_tx_hash) self->_data_priv.master.runner_tx_hash = g_ptr_array_new_with_free_func(g_free); g_ptr_array_add((GPtrArray *) self->d.master.runner_tx_hash, g_strdup(arr[i])); } out: return _team_setting_attribute_changed_attr(self, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, changed, set_field_mode, reset_json); } guint32 nm_team_setting_value_master_runner_tx_hash_set_list(NMTeamSetting * self, const char *const *arr, guint len) { return _team_setting_value_master_runner_tx_hash_set_list(self, arr, len, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES); } /*****************************************************************************/ #define _LINK_WATCHER_ATTR_GET(args, link_watcher_attribute, _value_type) \ ({ \ const NMValueTypUnioMaybe *const _args = (args); \ \ nm_assert(link_watcher_attr_datas[(link_watcher_attribute)].value_type == (_value_type)); \ \ _args[(link_watcher_attribute)].has \ ? &_args[(link_watcher_attribute)].val \ : &link_watcher_attr_datas[(link_watcher_attribute)].default_val; \ }) #define _LINK_WATCHER_ATTR_GET_BOOL(args, link_watcher_attribute) \ (_LINK_WATCHER_ATTR_GET(args, link_watcher_attribute, NM_VALUE_TYPE_BOOL)->v_bool) #define _LINK_WATCHER_ATTR_GET_INT(args, link_watcher_attribute) \ (_LINK_WATCHER_ATTR_GET(args, link_watcher_attribute, NM_VALUE_TYPE_INT)->v_int) #define _LINK_WATCHER_ATTR_GET_STRING(args, link_watcher_attribute) \ (_LINK_WATCHER_ATTR_GET(args, link_watcher_attribute, NM_VALUE_TYPE_STRING)->v_string) #define _LINK_WATCHER_ATTR_SET(args, link_watcher_attribute, _value_type, c_type, val) \ ({ \ nm_assert(link_watcher_attr_datas[(link_watcher_attribute)].value_type == (_value_type)); \ \ NM_VALUE_TYP_UNIO_MAYBE_SET(&(args)[(link_watcher_attribute)], c_type, (val)); \ }) #define _LINK_WATCHER_ATTR_SET_BOOL(args, link_watcher_attribute, val) \ _LINK_WATCHER_ATTR_SET((args), (link_watcher_attribute), NM_VALUE_TYPE_BOOL, v_bool, (val)) #define _LINK_WATCHER_ATTR_SET_INT(args, link_watcher_attribute, val) \ _LINK_WATCHER_ATTR_SET((args), (link_watcher_attribute), NM_VALUE_TYPE_INT, v_int, (val)) #define _LINK_WATCHER_ATTR_SET_STRING(args, link_watcher_attribute, val) \ _LINK_WATCHER_ATTR_SET((args), (link_watcher_attribute), NM_VALUE_TYPE_STRING, v_string, (val)) static void _link_watcher_unpack(const NMTeamLinkWatcher *link_watcher, NMValueTypUnioMaybe args[static G_N_ELEMENTS(link_watcher_attr_datas)]) { const char * v_name = nm_team_link_watcher_get_name(link_watcher); NMTeamLinkWatcherArpPingFlags v_arp_ping_flags; memset(args, 0, sizeof(args[0]) * G_N_ELEMENTS(link_watcher_attr_datas)); _LINK_WATCHER_ATTR_SET_STRING(args, LINK_WATCHER_ATTRIBUTE_NAME, v_name); if (nm_streq(v_name, NM_TEAM_LINK_WATCHER_ETHTOOL)) { _LINK_WATCHER_ATTR_SET_INT(args, LINK_WATCHER_ATTRIBUTE_DELAY_UP, nm_team_link_watcher_get_delay_up(link_watcher)); _LINK_WATCHER_ATTR_SET_INT(args, LINK_WATCHER_ATTRIBUTE_DELAY_DOWN, nm_team_link_watcher_get_delay_down(link_watcher)); } else if (NM_IN_STRSET(v_name, NM_TEAM_LINK_WATCHER_NSNA_PING, NM_TEAM_LINK_WATCHER_ARP_PING)) { _LINK_WATCHER_ATTR_SET_INT(args, LINK_WATCHER_ATTRIBUTE_INIT_WAIT, nm_team_link_watcher_get_init_wait(link_watcher)); _LINK_WATCHER_ATTR_SET_INT(args, LINK_WATCHER_ATTRIBUTE_INTERVAL, nm_team_link_watcher_get_interval(link_watcher)); _LINK_WATCHER_ATTR_SET_INT(args, LINK_WATCHER_ATTRIBUTE_MISSED_MAX, nm_team_link_watcher_get_missed_max(link_watcher)); _LINK_WATCHER_ATTR_SET_STRING(args, LINK_WATCHER_ATTRIBUTE_TARGET_HOST, nm_team_link_watcher_get_target_host(link_watcher)); if (nm_streq(v_name, NM_TEAM_LINK_WATCHER_ARP_PING)) { v_arp_ping_flags = nm_team_link_watcher_get_flags(link_watcher); _LINK_WATCHER_ATTR_SET_INT(args, LINK_WATCHER_ATTRIBUTE_VLANID, nm_team_link_watcher_get_vlanid(link_watcher)); _LINK_WATCHER_ATTR_SET_STRING(args, LINK_WATCHER_ATTRIBUTE_SOURCE_HOST, nm_team_link_watcher_get_source_host(link_watcher)); _LINK_WATCHER_ATTR_SET_BOOL( args, LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE, NM_FLAGS_HAS(v_arp_ping_flags, NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE)); _LINK_WATCHER_ATTR_SET_BOOL( args, LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE, NM_FLAGS_HAS(v_arp_ping_flags, NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE)); _LINK_WATCHER_ATTR_SET_BOOL( args, LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS, NM_FLAGS_HAS(v_arp_ping_flags, NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS)); } } } static void _link_watcher_to_json(const NMTeamLinkWatcher *link_watcher, GString *gstr) { NMValueTypUnioMaybe args[G_N_ELEMENTS(link_watcher_attr_datas)]; int i; gboolean is_first = TRUE; if (!link_watcher) { g_string_append(gstr, "null"); return; } _link_watcher_unpack(link_watcher, args); g_string_append(gstr, "{ "); for (i = 0; i < (int) G_N_ELEMENTS(link_watcher_attr_datas); i++) { const NMValueTypUnioMaybe *p_val = &args[i]; const LinkWatcherAttrData *attr_data = &link_watcher_attr_datas[i]; if (!p_val->has) continue; if (nm_value_type_equal(attr_data->value_type, &attr_data->default_val, &p_val->val)) continue; if (is_first) is_first = FALSE; else nm_json_gstr_append_delimiter(gstr); nm_json_gstr_append_obj_name(gstr, attr_data->js_key, '\0'); nm_value_type_to_json(attr_data->value_type, gstr, &p_val->val); } g_string_append(gstr, " }"); } static NMTeamLinkWatcher * _link_watcher_from_json(const NMJsonVt * vt, const nm_json_t *root_js_obj, gboolean * out_unrecognized_content) { NMValueTypUnioMaybe args[G_N_ELEMENTS(link_watcher_attr_datas)] = {}; const char * j_key; nm_json_t * j_val; const char * v_name; NMTeamLinkWatcher * result = NULL; if (!nm_json_is_object(root_js_obj)) goto fail; nm_json_object_foreach (vt, (nm_json_t *) root_js_obj, j_key, j_val) { const LinkWatcherAttrData *attr_data = NULL; NMValueTypUnioMaybe * parse_result; if (j_key) { int i; for (i = 0; i < (int) G_N_ELEMENTS(link_watcher_attr_datas); i++) { if (nm_streq(link_watcher_attr_datas[i].js_key, j_key)) { attr_data = &link_watcher_attr_datas[i]; break; } } } if (!attr_data) { *out_unrecognized_content = TRUE; continue; } parse_result = &args[attr_data->link_watcher_attr]; if (parse_result->has) *out_unrecognized_content = TRUE; if (!nm_value_type_from_json(vt, attr_data->value_type, j_val, &parse_result->val)) *out_unrecognized_content = TRUE; else parse_result->has = TRUE; } #define _PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES(_parse_results, ...) \ ({ \ int _i; \ \ for (_i = 0; _i < (int) G_N_ELEMENTS((_parse_results)); _i++) { \ if ((_parse_results)[_i].has && !NM_IN_SET((LinkWatcherAttribute) _i, __VA_ARGS__)) \ break; \ } \ \ (_i == (int) G_N_ELEMENTS((_parse_results))); \ }) v_name = _LINK_WATCHER_ATTR_GET_STRING(args, LINK_WATCHER_ATTRIBUTE_NAME); if (nm_streq0(v_name, NM_TEAM_LINK_WATCHER_ETHTOOL)) { if (_PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES(args, _EXPECTED_LINK_WATCHER_ATTRIBUTES_ETHTOOL)) *out_unrecognized_content = TRUE; result = nm_team_link_watcher_new_ethtool( _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_DELAY_UP), _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_DELAY_DOWN), NULL); } else if (nm_streq0(v_name, NM_TEAM_LINK_WATCHER_NSNA_PING)) { if (_PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES(args, _EXPECTED_LINK_WATCHER_ATTRIBUTES_NSNA_PING)) *out_unrecognized_content = TRUE; result = nm_team_link_watcher_new_nsna_ping( _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_INIT_WAIT), _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_INTERVAL), _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_MISSED_MAX), _LINK_WATCHER_ATTR_GET_STRING(args, LINK_WATCHER_ATTRIBUTE_TARGET_HOST), NULL); } else if (nm_streq0(v_name, NM_TEAM_LINK_WATCHER_ARP_PING)) { NMTeamLinkWatcherArpPingFlags v_flags = NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE; if (_PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES(args, _EXPECTED_LINK_WATCHER_ATTRIBUTES_ARP_PING)) *out_unrecognized_content = TRUE; if (_LINK_WATCHER_ATTR_GET_BOOL(args, LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE)) v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE; if (_LINK_WATCHER_ATTR_GET_BOOL(args, LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE)) v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE; if (_LINK_WATCHER_ATTR_GET_BOOL(args, LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS)) v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS; result = nm_team_link_watcher_new_arp_ping2( _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_INIT_WAIT), _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_INTERVAL), _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_MISSED_MAX), _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_VLANID), _LINK_WATCHER_ATTR_GET_STRING(args, LINK_WATCHER_ATTRIBUTE_TARGET_HOST), _LINK_WATCHER_ATTR_GET_STRING(args, LINK_WATCHER_ATTRIBUTE_SOURCE_HOST), v_flags, NULL); } if (result) return result; fail: *out_unrecognized_content = TRUE; return NULL; } /*****************************************************************************/ static GVariant * _link_watcher_to_variant(const NMTeamLinkWatcher *link_watcher) { NMValueTypUnioMaybe args[G_N_ELEMENTS(link_watcher_attr_datas)]; GVariantBuilder builder; int i; if (!link_watcher) return NULL; _link_watcher_unpack(link_watcher, args); if (!args[LINK_WATCHER_ATTRIBUTE_NAME].has) return NULL; g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); for (i = 0; i < (int) G_N_ELEMENTS(link_watcher_attr_datas); i++) { const NMValueTypUnioMaybe *p_val = &args[i]; const LinkWatcherAttrData *attr_data = &link_watcher_attr_datas[i]; GVariant * v; if (!p_val->has) continue; if (nm_value_type_equal(attr_data->value_type, &attr_data->default_val, &p_val->val)) continue; if (attr_data->value_type == NM_VALUE_TYPE_INT) v = g_variant_new_int32(p_val->val.v_int); else { v = nm_value_type_to_variant(attr_data->value_type, &p_val->val); } if (!v) continue; nm_assert(g_variant_is_floating(v)); g_variant_builder_add(&builder, "{sv}", attr_data->dbus_name, v); } return g_variant_builder_end(&builder); } #define _LINK_WATCHER_ATTR_VARGET(variants, link_watcher_attribute, _value_type, c_type, _cmd) \ ({ \ GVariant *const *_variants = (variants); \ GVariant * _cc; \ \ nm_assert(link_watcher_attr_datas[(link_watcher_attribute)].value_type == (_value_type)); \ \ (_cc = _variants[(link_watcher_attribute)]) \ ? (_cmd) \ : link_watcher_attr_datas[(link_watcher_attribute)].default_val.c_type; \ }) #define _LINK_WATCHER_ATTR_VARGET_BOOL(variants, link_watcher_attribute) \ (_LINK_WATCHER_ATTR_VARGET(variants, \ link_watcher_attribute, \ NM_VALUE_TYPE_BOOL, \ v_bool, \ g_variant_get_boolean(_cc))) #define _LINK_WATCHER_ATTR_VARGET_INT(variants, link_watcher_attribute) \ (_LINK_WATCHER_ATTR_VARGET(variants, \ link_watcher_attribute, \ NM_VALUE_TYPE_INT, \ v_int, \ g_variant_get_int32(_cc))) #define _LINK_WATCHER_ATTR_VARGET_STRING(variants, link_watcher_attribute) \ (_LINK_WATCHER_ATTR_VARGET(variants, \ link_watcher_attribute, \ NM_VALUE_TYPE_STRING, \ v_string, \ g_variant_get_string(_cc, NULL))) static void _variants_list_link_watcher_unref_auto(GVariant *(*p_variants)[]) { int i; for (i = 0; i < (int) G_N_ELEMENTS(link_watcher_attr_datas); i++) nm_g_variant_unref((*p_variants)[i]); } static NMTeamLinkWatcher * _link_watcher_from_variant(GVariant *watcher_var, gboolean strict_parsing, GError **error) { nm_auto(_variants_list_link_watcher_unref_auto) GVariant *variants[G_N_ELEMENTS(link_watcher_attr_datas)] = { NULL, }; const char * v_key; GVariant * v_val; const char * v_name; GVariantIter iter; g_return_val_if_fail(g_variant_is_of_type(watcher_var, G_VARIANT_TYPE("a{sv}")), NULL); g_variant_iter_init(&iter, watcher_var); while (g_variant_iter_next(&iter, "{&sv}", &v_key, &v_val)) { _nm_unused gs_unref_variant GVariant *v_val_free = v_val; const LinkWatcherAttrData * attr_data = NULL; const GVariantType * variant_type; int i; for (i = 0; i < (int) G_N_ELEMENTS(link_watcher_attr_datas); i++) { if (nm_streq(link_watcher_attr_datas[i].dbus_name, v_key)) { attr_data = &link_watcher_attr_datas[i]; break; } } if (!attr_data) { if (strict_parsing) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("invalid D-Bus property \"%s\""), v_key); return NULL; } continue; } if (attr_data->value_type == NM_VALUE_TYPE_INT) variant_type = G_VARIANT_TYPE_INT32; else variant_type = nm_value_type_get_variant_type(attr_data->value_type); if (!g_variant_is_of_type(v_val, variant_type)) { if (strict_parsing) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("invalid D-Bus property \"%s\""), v_key); return NULL; } continue; } if (variants[attr_data->link_watcher_attr]) { if (strict_parsing) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("duplicate D-Bus property \"%s\""), v_key); return NULL; } g_variant_unref(variants[attr_data->link_watcher_attr]); } variants[attr_data->link_watcher_attr] = g_steal_pointer(&v_val_free); } #define _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES(_type, _variants, _error, ...) \ ({ \ int _i; \ gboolean _has_error = FALSE; \ \ for (_i = 0; _i < (int) G_N_ELEMENTS((_variants)); _i++) { \ if ((_variants)[_i] && !NM_IN_SET((LinkWatcherAttribute) _i, __VA_ARGS__)) { \ _has_error = TRUE; \ g_set_error(_error, \ NM_CONNECTION_ERROR, \ NM_CONNECTION_ERROR_INVALID_PROPERTY, \ _("invalid D-Bus property \"%s\" for \"%s\""), \ link_watcher_attr_datas[_i].dbus_name, \ _type); \ break; \ } \ } \ \ _has_error; \ }) v_name = _LINK_WATCHER_ATTR_VARGET_STRING(variants, LINK_WATCHER_ATTRIBUTE_NAME); if (nm_streq0(v_name, NM_TEAM_LINK_WATCHER_ETHTOOL)) { if (strict_parsing && _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES(v_name, variants, error, _EXPECTED_LINK_WATCHER_ATTRIBUTES_ETHTOOL)) return NULL; return nm_team_link_watcher_new_ethtool( _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_DELAY_UP), _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_DELAY_DOWN), strict_parsing ? error : NULL); } if (nm_streq0(v_name, NM_TEAM_LINK_WATCHER_NSNA_PING)) { if (strict_parsing && _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES(v_name, variants, error, _EXPECTED_LINK_WATCHER_ATTRIBUTES_NSNA_PING)) return NULL; return nm_team_link_watcher_new_nsna_ping( _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_INIT_WAIT), _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_INTERVAL), _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_MISSED_MAX), _LINK_WATCHER_ATTR_VARGET_STRING(variants, LINK_WATCHER_ATTRIBUTE_TARGET_HOST), strict_parsing ? error : NULL); } if (nm_streq0(v_name, NM_TEAM_LINK_WATCHER_ARP_PING)) { NMTeamLinkWatcherArpPingFlags v_flags = NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE; if (strict_parsing && _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES(v_name, variants, error, _EXPECTED_LINK_WATCHER_ATTRIBUTES_ARP_PING)) return NULL; if (_LINK_WATCHER_ATTR_VARGET_BOOL(variants, LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE)) v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE; if (_LINK_WATCHER_ATTR_VARGET_BOOL(variants, LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE)) v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE; if (_LINK_WATCHER_ATTR_VARGET_BOOL(variants, LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS)) v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS; return nm_team_link_watcher_new_arp_ping2( _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_INIT_WAIT), _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_INTERVAL), _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_MISSED_MAX), _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_VLANID), _LINK_WATCHER_ATTR_VARGET_STRING(variants, LINK_WATCHER_ATTRIBUTE_TARGET_HOST), _LINK_WATCHER_ATTR_VARGET_STRING(variants, LINK_WATCHER_ATTRIBUTE_SOURCE_HOST), v_flags, strict_parsing ? error : NULL); } if (strict_parsing) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("unknown link-watcher name \"%s\""), v_name); } return NULL; } /*****************************************************************************/ /** * _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 full): a new floating #GVariant representing link watchers. **/ GVariant * _nm_utils_team_link_watchers_to_variant(const GPtrArray *link_watchers) { GVariantBuilder builder; guint i; g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); if (link_watchers) { for (i = 0; i < link_watchers->len; i++) { g_variant_builder_add(&builder, "@a{sv}", _link_watcher_to_variant(link_watchers->pdata[i])); } } return g_variant_builder_end(&builder); } /** * _nm_utils_team_link_watchers_from_variant: * @value: a #GVariant of type 'aa{sv}' * @strict_parsing: whether to parse strictly or ignore everything invalid. * @error: error reason. * * 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. * * Note that if you provide an @error, then the function can only fail (and return %NULL) * or succeed (and not return %NULL). If you don't provide an @error, then the function * never returns %NULL. **/ GPtrArray * _nm_utils_team_link_watchers_from_variant(GVariant *value, gboolean strict_parsing, GError **error) { gs_unref_ptrarray GPtrArray *link_watchers = NULL; 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)) { _nm_unused gs_unref_variant GVariant *watcher_var_free = watcher_var; NMTeamLinkWatcher * watcher; watcher = _link_watcher_from_variant(watcher_var, strict_parsing, error); if (error && *error) return NULL; if (watcher) g_ptr_array_add(link_watchers, watcher); } return g_steal_pointer(&link_watchers); } /*****************************************************************************/ const char * nm_team_setting_config_get(const NMTeamSetting *self) { char *js_str; nm_assert(self); if (G_LIKELY(!self->d._js_str_need_synthetize)) return self->d._js_str; nm_assert(!self->d._js_str); nm_assert(self->d.strict_validated); if (_team_setting_check_default(self) == 0) { /* the default is set. We signal this as a NULL JSON string. * Nothing to do. */ js_str = NULL; } else { gboolean list_is_empty = TRUE; GString *gstr; gstr = g_string_new(NULL); g_string_append(gstr, "{ "); if (self->d.is_port) { static const NMTeamAttribute attr_lst_port[] = { NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID, NM_TEAM_ATTRIBUTE_PORT_PRIO, NM_TEAM_ATTRIBUTE_PORT_STICKY, NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO, NM_TEAM_ATTRIBUTE_PORT_LACP_KEY, }; if (_team_setting_fields_to_json_maybe(self, gstr, !list_is_empty, attr_lst_port, G_N_ELEMENTS(attr_lst_port))) list_is_empty = FALSE; } else { static const NMTeamAttribute attr_lst_runner_pt1[] = { NM_TEAM_ATTRIBUTE_MASTER_RUNNER, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, }; static const NMTeamAttribute attr_lst_runner_pt2[] = { NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL, }; static const NMTeamAttribute attr_lst_runner_pt3[] = { NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY, }; static const NMTeamAttribute attr_lst_notify_peers[] = { NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT, NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL, }; static const NMTeamAttribute attr_lst_mcast_rejoin[] = { NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT, NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL, }; if (_team_setting_has_fields_any_v(self, attr_lst_runner_pt1, G_N_ELEMENTS(attr_lst_runner_pt1)) || _team_setting_has_fields_any_v(self, attr_lst_runner_pt2, G_N_ELEMENTS(attr_lst_runner_pt2)) || _team_setting_has_fields_any_v(self, attr_lst_runner_pt3, G_N_ELEMENTS(attr_lst_runner_pt3))) { gboolean list_is_empty2 = TRUE; nm_assert(list_is_empty); nm_json_gstr_append_obj_name(gstr, "runner", '{'); if (_team_setting_fields_to_json_maybe(self, gstr, !list_is_empty2, attr_lst_runner_pt1, G_N_ELEMENTS(attr_lst_runner_pt1))) list_is_empty2 = FALSE; if (_team_setting_has_fields_any_v(self, attr_lst_runner_pt2, G_N_ELEMENTS(attr_lst_runner_pt2))) { if (!list_is_empty2) nm_json_gstr_append_delimiter(gstr); nm_json_gstr_append_obj_name(gstr, "tx_balancer", '{'); if (!_team_setting_fields_to_json_maybe(self, gstr, FALSE, attr_lst_runner_pt2, G_N_ELEMENTS(attr_lst_runner_pt2))) nm_assert_not_reached(); g_string_append(gstr, " }"); list_is_empty2 = FALSE; } if (_team_setting_fields_to_json_maybe(self, gstr, !list_is_empty2, attr_lst_runner_pt3, G_N_ELEMENTS(attr_lst_runner_pt3))) list_is_empty2 = FALSE; nm_assert(!list_is_empty2); g_string_append(gstr, " }"); list_is_empty = FALSE; } if (_team_setting_has_fields_any_v(self, attr_lst_notify_peers, G_N_ELEMENTS(attr_lst_notify_peers))) { if (!list_is_empty) nm_json_gstr_append_delimiter(gstr); nm_json_gstr_append_obj_name(gstr, "notify_peers", '{'); if (!_team_setting_fields_to_json_maybe(self, gstr, FALSE, attr_lst_notify_peers, G_N_ELEMENTS(attr_lst_notify_peers))) nm_assert_not_reached(); g_string_append(gstr, " }"); list_is_empty = FALSE; } if (_team_setting_has_fields_any_v(self, attr_lst_mcast_rejoin, G_N_ELEMENTS(attr_lst_mcast_rejoin))) { if (!list_is_empty) nm_json_gstr_append_delimiter(gstr); nm_json_gstr_append_obj_name(gstr, "mcast_rejoin", '{'); if (!_team_setting_fields_to_json_maybe(self, gstr, FALSE, attr_lst_mcast_rejoin, G_N_ELEMENTS(attr_lst_mcast_rejoin))) nm_assert_not_reached(); g_string_append(gstr, " }"); list_is_empty = FALSE; } } if (_team_setting_field_to_json( self, gstr, !list_is_empty, _team_attr_data_get(self->d.is_port, NM_TEAM_ATTRIBUTE_LINK_WATCHERS))) list_is_empty = FALSE; if (!list_is_empty) g_string_append(gstr, " }"); js_str = g_string_free(gstr, list_is_empty); } /* mutate the constant object. In C++ speak, these fields are "mutable". * That is because we construct the JSON string lazily/on-demand. */ *((char **) &self->_data_priv._js_str) = js_str; *((bool *) &self->_data_priv._js_str_need_synthetize) = FALSE; return self->d._js_str; } /*****************************************************************************/ static gboolean _attr_data_match_keys(const TeamAttrData *attr_data, const char *const *keys, guint8 n_keys) { guint8 i; _team_attr_data_ASSERT(attr_data); nm_assert(keys); nm_assert(n_keys > 0); nm_assert(({ gboolean all_non_null = TRUE; for (i = 0; i < n_keys; i++) all_non_null = all_non_null && keys[i] && keys[i][0] != '\0'; all_non_null; })); if (attr_data->js_keys_len < n_keys) return FALSE; for (i = 0; i < n_keys; i++) { if (!nm_streq(keys[i], attr_data->js_keys[i])) return FALSE; } return TRUE; } static const TeamAttrData * _attr_data_find_by_json_key(gboolean is_port, const char *const *keys, guint8 n_keys) { const TeamAttrData *attr_data; for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; attr_data++) { if (_team_attr_data_is_relevant(attr_data, is_port) && _attr_data_match_keys(attr_data, keys, n_keys)) return attr_data; } return NULL; } static void _js_parse_locate_keys(const NMJsonVt *vt, NMTeamSetting * self, nm_json_t * root_js_obj, nm_json_t * found_keys[static _NM_TEAM_ATTRIBUTE_NUM], gboolean * out_unrecognized_content) { const char *keys[3]; const char *cur_key1; const char *cur_key2; const char *cur_key3; nm_json_t * cur_val1; nm_json_t * cur_val2; nm_json_t * cur_val3; nm_assert(vt); #define _handle(_self, _cur_key, _cur_val, _keys, _level, _found_keys, _out_unrecognized_content) \ ({ \ const TeamAttrData *_attr_data; \ gboolean _handled = FALSE; \ \ (_keys)[(_level) -1] = (_cur_key); \ _attr_data = _attr_data_find_by_json_key((_self)->d.is_port, (_keys), (_level)); \ if (_attr_data && _attr_data->js_keys_len == (_level)) { \ if ((_found_keys)[_attr_data->team_attr]) \ *(_out_unrecognized_content) = TRUE; \ (_found_keys)[_attr_data->team_attr] = (_cur_val); \ _handled = TRUE; \ } else if (!_attr_data || !nm_json_is_object((_cur_val))) { \ *(_out_unrecognized_content) = TRUE; \ _handled = TRUE; \ } \ _handled; \ }) nm_json_object_foreach (vt, root_js_obj, cur_key1, cur_val1) { if (!_handle(self, cur_key1, cur_val1, keys, 1, found_keys, out_unrecognized_content)) { nm_json_object_foreach (vt, cur_val1, cur_key2, cur_val2) { if (!_handle(self, cur_key2, cur_val2, keys, 2, found_keys, out_unrecognized_content)) { nm_json_object_foreach (vt, cur_val2, cur_key3, cur_val3) { if (!_handle(self, cur_key3, cur_val3, keys, 3, found_keys, out_unrecognized_content)) *out_unrecognized_content = TRUE; } } } } } #undef _handle } static void _js_parse_unpack(const NMJsonVt *vt, gboolean is_port, nm_json_t * found_keys[static _NM_TEAM_ATTRIBUTE_NUM], bool out_has_lst[static _NM_TEAM_ATTRIBUTE_NUM], NMValueTypUnion out_val_lst[static _NM_TEAM_ATTRIBUTE_NUM], gboolean * out_unrecognized_content, GPtrArray ** out_ptr_array_link_watchers_free, GPtrArray ** out_ptr_array_master_runner_tx_hash_free) { const TeamAttrData *attr_data; nm_assert(vt); for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; attr_data++) { NMValueTypUnion *p_out_val; gboolean valid = FALSE; nm_json_t * arg_js_obj; if (!_team_attr_data_is_relevant(attr_data, is_port)) continue; nm_assert(!out_has_lst[attr_data->team_attr]); arg_js_obj = found_keys[attr_data->team_attr]; if (!arg_js_obj) continue; p_out_val = &out_val_lst[attr_data->team_attr]; if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) valid = nm_value_type_from_json(vt, attr_data->value_type, arg_js_obj, p_out_val); else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) { GPtrArray * link_watchers = NULL; NMTeamLinkWatcher *link_watcher; nm_assert(out_ptr_array_link_watchers_free && !*out_ptr_array_link_watchers_free); if (nm_json_is_array(arg_js_obj)) { gsize i, len; len = vt->nm_json_array_size(arg_js_obj); link_watchers = g_ptr_array_new_full(len, (GDestroyNotify) nm_team_link_watcher_unref); for (i = 0; i < len; i++) { link_watcher = _link_watcher_from_json(vt, vt->nm_json_array_get(arg_js_obj, i), out_unrecognized_content); if (link_watcher) g_ptr_array_add(link_watchers, link_watcher); } } else { link_watcher = _link_watcher_from_json(vt, arg_js_obj, out_unrecognized_content); if (link_watcher) { link_watchers = g_ptr_array_new_full(1, (GDestroyNotify) nm_team_link_watcher_unref); g_ptr_array_add(link_watchers, link_watcher); } } if (link_watchers) { valid = TRUE; p_out_val->v_ptrarray = link_watchers; *out_ptr_array_link_watchers_free = link_watchers; } } else if (!is_port && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) { GPtrArray *strv = NULL; nm_assert(out_ptr_array_master_runner_tx_hash_free && !*out_ptr_array_master_runner_tx_hash_free); if (nm_json_is_array(arg_js_obj)) { gsize i, len; len = vt->nm_json_array_size(arg_js_obj); if (len > 0) { strv = g_ptr_array_sized_new(len); for (i = 0; i < len; i++) { const char *v_string; if (nm_jansson_json_as_string(vt, vt->nm_json_array_get(arg_js_obj, i), &v_string) <= 0 || !v_string || v_string[0] == '\0') { /* we remember that there was some invalid content, but parts of the * list could still be parsed. */ *out_unrecognized_content = TRUE; continue; } g_ptr_array_add(strv, (char *) v_string); } } valid = TRUE; *out_ptr_array_master_runner_tx_hash_free = strv; } p_out_val->v_ptrarray = strv; } else nm_assert_not_reached(); out_has_lst[attr_data->team_attr] = valid; if (!valid) *out_unrecognized_content = TRUE; } } guint32 nm_team_setting_config_set(NMTeamSetting *self, const char *js_str) { const NMJsonVt *vt; guint32 changed_flags = 0; gboolean do_set_default = TRUE; gboolean new_js_str_invalid = FALSE; _team_setting_ASSERT(self); if (!js_str || js_str[0] == '\0') { changed_flags = _team_setting_set_default(self); if (changed_flags != 0 || !nm_streq0(js_str, self->d._js_str)) changed_flags |= nm_team_attribute_to_flags(NM_TEAM_ATTRIBUTE_CONFIG); nm_clear_g_free((char **) &self->_data_priv._js_str); self->_data_priv._js_str = g_strdup(js_str); self->_data_priv._js_str_need_synthetize = FALSE; self->_data_priv.strict_validated = TRUE; self->_data_priv.js_str_invalid = FALSE; return changed_flags; } if (self->d._js_str && nm_streq(js_str, self->d._js_str)) { if (!self->d.strict_validated) { /* setting the same JSON string twice in a row has no effect. */ return 0; } } else changed_flags |= nm_team_attribute_to_flags(NM_TEAM_ATTRIBUTE_CONFIG); if ((vt = nm_json_vt())) { nm_auto_decref_json nm_json_t *root_js_obj = NULL; root_js_obj = vt->nm_json_loads(js_str, 0, NULL); if (!root_js_obj || !nm_json_is_object(root_js_obj)) new_js_str_invalid = TRUE; else { gboolean unrecognized_content = FALSE; bool has_lst[_NM_TEAM_ATTRIBUTE_NUM] = { FALSE, }; NMValueTypUnion val_lst[_NM_TEAM_ATTRIBUTE_NUM]; nm_json_t * found_keys[_NM_TEAM_ATTRIBUTE_NUM] = { NULL, }; gs_unref_ptrarray GPtrArray *ptr_array_master_runner_tx_hash_free = NULL; gs_unref_ptrarray GPtrArray *ptr_array_link_watchers_free = NULL; _js_parse_locate_keys(vt, self, root_js_obj, found_keys, &unrecognized_content); _js_parse_unpack(vt, self->d.is_port, found_keys, has_lst, val_lst, &unrecognized_content, &ptr_array_link_watchers_free, &ptr_array_master_runner_tx_hash_free); do_set_default = FALSE; changed_flags |= _team_setting_set(self, TRUE, has_lst, val_lst); } } if (do_set_default) changed_flags |= _team_setting_set_default(self); self->_data_priv.strict_validated = FALSE; self->_data_priv._js_str_need_synthetize = FALSE; self->_data_priv.js_str_invalid = new_js_str_invalid; g_free((char *) self->_data_priv._js_str); self->_data_priv._js_str = g_strdup(js_str); return changed_flags; } /*****************************************************************************/ static void _team_setting_prefix_error_plain(gboolean is_port, const char *property_name, GError **error) { g_prefix_error(error, "%s.%s: ", is_port ? NM_SETTING_TEAM_PORT_SETTING_NAME : NM_SETTING_TEAM_SETTING_NAME, property_name); } static void _team_setting_prefix_error(const NMTeamSetting *self, const char * prop_name_master, const char * prop_name_port, GError ** error) { _team_setting_ASSERT(self); nm_assert(self->d.is_port ? (!!prop_name_port) : (!!prop_name_master)); _team_setting_prefix_error_plain(self->d.is_port, self->d.is_port ? prop_name_port : prop_name_master, error); } static gboolean _team_setting_verify_properties(const NMTeamSetting *self, GError **error) { const TeamAttrData *attr_data; guint i; for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; attr_data++) { if (!_team_attr_data_is_relevant(attr_data, self->d.is_port)) continue; if (!_team_setting_has_field(self, attr_data)) continue; if (attr_data->has_range) { gconstpointer p_field; p_field = _team_setting_get_field(self, attr_data); if (attr_data->value_type == NM_VALUE_TYPE_INT32) { gint32 v = *((const gint32 *) p_field); if (v < attr_data->range.r_int32.min || v > attr_data->range.r_int32.max) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("value out or range")); _team_setting_prefix_error_plain(self->d.is_port, attr_data->property_name, error); return FALSE; } } else if (attr_data->value_type == NM_VALUE_TYPE_STRING) { const char *v = *((const char *const *) p_field); if (nm_utils_strv_find_first((char **) attr_data->range.r_string.valid_names, -1, v) < 0) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("invalid value")); _team_setting_prefix_error_plain(self->d.is_port, attr_data->property_name, error); return FALSE; } } else nm_assert_not_reached(); } if (!self->d.is_port && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) { if (self->d.master.runner_tx_hash) { for (i = 0; i < self->d.master.runner_tx_hash->len; i++) { const char *val = self->d.master.runner_tx_hash->pdata[i]; if (!val || (nm_utils_strv_find_first((char **) _valid_names_runner_tx_hash, -1, val) < 0)) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("invalid runner-tx-hash")); _team_setting_prefix_error_plain(self->d.is_port, NM_SETTING_TEAM_RUNNER_TX_HASH, error); return FALSE; } } } } } if (!self->d.is_port) { for (i = 0; i < G_N_ELEMENTS(_runner_compat_lst); i++) { const RunnerCompatElem *e = &_runner_compat_lst[i]; nm_assert(NM_PTRARRAY_LEN(e->valid_runners) > 0); attr_data = _team_attr_data_get(FALSE, e->team_attr); if (!_team_setting_has_field(self, attr_data)) continue; if (self->d.master.runner && (nm_utils_strv_find_first((char **) e->valid_runners, -1, self->d.master.runner) >= 0)) continue; if (e->valid_runners[1] == NULL) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("%s is only allowed for runner %s"), attr_data->property_name, e->valid_runners[0]); } else { gs_free char *s = NULL; s = g_strjoinv(",", (char **) e->valid_runners); g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("%s is only allowed for runners %s"), attr_data->property_name, s); } _team_setting_prefix_error_plain(self->d.is_port, NM_SETTING_TEAM_RUNNER, error); return FALSE; } } else { gboolean has_lacp_attrs; gboolean has_activebackup_attrs; has_lacp_attrs = _team_setting_has_fields_any(self, NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO, NM_TEAM_ATTRIBUTE_PORT_LACP_KEY); has_activebackup_attrs = _team_setting_has_fields_any(self, NM_TEAM_ATTRIBUTE_PORT_PRIO, NM_TEAM_ATTRIBUTE_PORT_STICKY); if (has_lacp_attrs && has_activebackup_attrs) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("cannot set parameters for lacp and activebackup runners together")); _team_setting_prefix_error(self, NM_SETTING_TEAM_LINK_WATCHERS, NM_SETTING_TEAM_PORT_LINK_WATCHERS, error); return FALSE; } } for (i = 0; i < self->d.link_watchers->len; i++) { if (!self->d.link_watchers->pdata[i]) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("missing link watcher")); _team_setting_prefix_error(self, NM_SETTING_TEAM_LINK_WATCHERS, NM_SETTING_TEAM_PORT_LINK_WATCHERS, error); return FALSE; } } return TRUE; } static gboolean _team_setting_verify_config(const NMTeamSetting *self, GError **error) { const char *js_str; /* we always materialize the JSON string. That is because we want to validate the * string length of the resulting JSON. */ js_str = nm_team_setting_config_get(self); if (js_str) { if (strlen(js_str) > 1 * 1024 * 1024) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("team config exceeds size limit")); _team_setting_prefix_error(self, NM_SETTING_TEAM_CONFIG, NM_SETTING_TEAM_PORT_CONFIG, error); return FALSE; } if (!g_utf8_validate(js_str, -1, NULL)) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("team config is not valid UTF-8")); _team_setting_prefix_error(self, NM_SETTING_TEAM_CONFIG, NM_SETTING_TEAM_PORT_CONFIG, error); return FALSE; } if (self->d.js_str_invalid) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("invalid json")); _team_setting_prefix_error(self, NM_SETTING_TEAM_CONFIG, NM_SETTING_TEAM_PORT_CONFIG, error); return FALSE; } } return TRUE; } gboolean nm_team_setting_verify(const NMTeamSetting *self, GError **error) { if (self->d.strict_validated) { if (!_team_setting_verify_properties(self, error)) return FALSE; } return _team_setting_verify_config(self, error); } /*****************************************************************************/ int nm_team_setting_cmp(const NMTeamSetting *self_a, const NMTeamSetting *self_b, gboolean ignore_js_str) { const TeamAttrData *attr_data; NM_CMP_SELF(self_a, self_b); NM_CMP_FIELD_UNSAFE(self_a, self_b, d.is_port); for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; attr_data++) { if (!_team_attr_data_is_relevant(attr_data, self_a->d.is_port)) continue; NM_CMP_RETURN(_team_attr_data_cmp(attr_data, self_a->d.is_port, _team_setting_get_field(self_a, attr_data), _team_setting_get_field(self_b, attr_data))); } if (!ignore_js_str) { NM_CMP_DIRECT_STRCMP0(nm_team_setting_config_get(self_a), nm_team_setting_config_get(self_b)); } return 0; } guint32 nm_team_setting_reset(NMTeamSetting *self, const NMTeamSetting *src) { const TeamAttrData *attr_data; guint32 changed_flags; _team_setting_ASSERT(self); _team_setting_ASSERT(src); nm_assert(self->d.is_port == src->d.is_port); if (self == src) return 0; changed_flags = 0; for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; attr_data++) { if (!_team_attr_data_is_relevant(attr_data, self->d.is_port)) continue; if (_team_attr_data_equal(attr_data, self->d.is_port, _team_setting_get_field(self, attr_data), _team_setting_get_field(src, attr_data))) continue; _team_attr_data_copy(attr_data, self->d.is_port, _team_setting_get_field(self, attr_data), _team_setting_get_field(src, attr_data)); changed_flags |= nm_team_attribute_to_flags(attr_data->team_attr); } self->_data_priv.has_fields_mask = src->d.has_fields_mask; if (!nm_streq0(self->d._js_str, src->d._js_str)) { g_free((char *) self->_data_priv._js_str); self->_data_priv._js_str = g_strdup(src->d._js_str); changed_flags |= nm_team_attribute_to_flags(NM_TEAM_ATTRIBUTE_CONFIG); } else if (changed_flags != 0) changed_flags |= nm_team_attribute_to_flags(NM_TEAM_ATTRIBUTE_CONFIG); self->_data_priv._js_str_need_synthetize = src->d._js_str_need_synthetize; self->_data_priv.strict_validated = src->d.strict_validated; self->_data_priv.js_str_invalid = src->d.js_str_invalid; return changed_flags; } static void _variants_list_team_unref_auto(GVariant *(*p_variants)[]) { int i; for (i = 0; i < _NM_TEAM_ATTRIBUTE_NUM; i++) nm_g_variant_unref((*p_variants)[i]); } gboolean nm_team_setting_reset_from_dbus(NMTeamSetting * self, GVariant * setting_dict, GHashTable * keys, guint32 * out_changed, guint /* NMSettingParseFlags */ parse_flags, GError ** error) { nm_auto(_variants_list_team_unref_auto) GVariant *variants[_NM_TEAM_ATTRIBUTE_NUM] = { NULL, }; gs_unref_ptrarray GPtrArray *v_link_watchers = NULL; const TeamAttrData * attr_data; GVariantIter iter; const char * v_key; GVariant * v_val; const NMJsonVt * vt; *out_changed = 0; g_variant_iter_init(&iter, setting_dict); while (g_variant_iter_next(&iter, "{&sv}", &v_key, &v_val)) { _nm_unused gs_unref_variant GVariant *v_val_free = v_val; const GVariantType * variant_type = NULL; attr_data = _team_attr_data_find_for_property_name(self->d.is_port, v_key); if (!attr_data) { /* _nm_setting_new_from_dbus() already checks for unknown keys. Don't * do that here. */ continue; } if (keys) g_hash_table_remove(keys, v_key); if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) variant_type = nm_value_type_get_variant_type(attr_data->value_type); else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_CONFIG) variant_type = G_VARIANT_TYPE_STRING; else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) variant_type = G_VARIANT_TYPE("aa{sv}"); else if (!self->d.is_port && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) variant_type = G_VARIANT_TYPE_STRING_ARRAY; else nm_assert_not_reached(); if (!g_variant_is_of_type(v_val, variant_type)) { if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("invalid D-Bus type \"%s\""), g_variant_get_type_string(v_val)); _team_setting_prefix_error_plain(self->d.is_port, attr_data->property_name, error); return FALSE; } continue; } /* _nm_setting_new_from_dbus() already checks for duplicate keys. Don't * do that here. */ nm_g_variant_unref(variants[attr_data->team_attr]); variants[attr_data->team_attr] = g_steal_pointer(&v_val_free); } vt = nm_json_vt(); if (variants[NM_TEAM_ATTRIBUTE_LINK_WATCHERS]) { if (variants[NM_TEAM_ATTRIBUTE_CONFIG] && vt && !NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { /* we don't require the content of the "link-watchers" and we also * don't perform strict validation. No need to parse it. */ } else { gs_free_error GError *local = NULL; /* We might need the parsed v_link_watchers array below (because there is no JSON * "config" present or because we don't have json support). * * Or we might run with NM_SETTING_PARSE_FLAGS_STRICT. In that mode, we may not necessarily * require that the entire setting as a whole validates (if a JSON config is present and * we are not "strict_validated") , but we require that we can at least parse the link watchers * on their own. */ v_link_watchers = _nm_utils_team_link_watchers_from_variant( variants[NM_TEAM_ATTRIBUTE_LINK_WATCHERS], NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT), &local); if (local && NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("invalid link-watchers: %s"), local->message); _team_setting_prefix_error(self, NM_SETTING_TEAM_LINK_WATCHERS, NM_SETTING_TEAM_PORT_LINK_WATCHERS, error); return FALSE; } } } *out_changed |= nm_team_setting_config_set( self, variants[NM_TEAM_ATTRIBUTE_CONFIG] ? g_variant_get_string(variants[NM_TEAM_ATTRIBUTE_CONFIG], NULL) : NULL); if (vt && variants[NM_TEAM_ATTRIBUTE_CONFIG]) { /* for team settings, the JSON must be able to express all possible options. That means, * if the GVariant contains both the JSON "config" and other options, then the other options * are silently ignored. */ } else { guint32 extra_changed = 0u; for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; attr_data++) { NMValueTypUnion val; guint32 changed_flags = 0u; if (!_team_attr_data_is_relevant(attr_data, self->d.is_port)) continue; if (!variants[attr_data->team_attr]) continue; if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) { nm_value_type_get_from_variant(attr_data->value_type, &val, variants[attr_data->team_attr], FALSE); changed_flags = _team_setting_value_set(self, attr_data, &val, SET_FIELD_MODE_SET, RESET_JSON_NO); } else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) { changed_flags = _team_setting_value_link_watchers_set_list( self, v_link_watchers ? (const NMTeamLinkWatcher *const *) v_link_watchers->pdata : NULL, v_link_watchers ? v_link_watchers->len : 0u, SET_FIELD_MODE_SET, RESET_JSON_NO); } else if (!self->d.is_port && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) { gs_free const char **strv = NULL; gsize len; strv = g_variant_get_strv(variants[attr_data->team_attr], &len); changed_flags = _team_setting_value_master_runner_tx_hash_set_list( self, strv, NM_MIN(len, (gsize) G_MAXUINT), SET_FIELD_MODE_SET, RESET_JSON_NO); } else nm_assert_not_reached(); extra_changed |= changed_flags; } if (!variants[NM_TEAM_ATTRIBUTE_CONFIG]) { /* clear the JSON string so it can be regenerated. But only if we didn't set * it above. */ self->_data_priv.strict_validated = TRUE; self->_data_priv._js_str_need_synthetize = TRUE; } *out_changed |= extra_changed; } return TRUE; } /*****************************************************************************/ gboolean nm_team_setting_maybe_changed(NMSetting * source, const GParamSpec *const *obj_properties, guint32 changed_flags) { NMTeamAttribute team_attr; int count_flags; guint32 ch; if (changed_flags == 0u) return FALSE; count_flags = 0; for (ch = changed_flags; ch != 0u; ch >>= 1) { if (NM_FLAGS_HAS(ch, 0x1u)) count_flags++; } if (count_flags > 1) g_object_freeze_notify(G_OBJECT(source)); ch = changed_flags; for (team_attr = 0; team_attr < _NM_TEAM_ATTRIBUTE_NUM; team_attr++) { if (!NM_FLAGS_ANY(ch, nm_team_attribute_to_flags(team_attr))) continue; g_object_notify_by_pspec(G_OBJECT(source), (GParamSpec *) obj_properties[team_attr]); ch &= ~nm_team_attribute_to_flags(team_attr); if (ch == 0) break; } if (count_flags > 1) g_object_thaw_notify(G_OBJECT(source)); return TRUE; } /*****************************************************************************/ NMTeamSetting * _nm_setting_get_team_setting(struct _NMSetting *setting) { if (NM_IS_SETTING_TEAM(setting)) return _nm_setting_team_get_team_setting(NM_SETTING_TEAM(setting)); return _nm_setting_team_port_get_team_setting(NM_SETTING_TEAM_PORT(setting)); } static GVariant * _nm_team_settings_property_to_dbus(const NMSettInfoSetting * sett_info, guint property_idx, NMConnection * connection, NMSetting * setting, NMConnectionSerializationFlags flags, const NMConnectionSerializationOptions *options) { NMTeamSetting * self = _nm_setting_get_team_setting(setting); const TeamAttrData *attr_data = _team_attr_data_get(self->d.is_port, sett_info->property_infos[property_idx].param_spec->param_id); if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_CONFIG) { const char *config; if (self->d.strict_validated && !_nm_utils_is_manager_process) { /* if we are in strict validating mode on the client side, the JSON is generated * artificially. In this case, don't send the config via D-Bus to the server. * * This also will cause NetworkManager to strictly validate the settings. * If a JSON "config" is present, strict validation won't be performed. */ return NULL; } config = nm_team_setting_config_get(self); return config ? g_variant_new_string(config) : NULL; } if (!_team_setting_has_field(self, attr_data)) return NULL; if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) { return nm_value_type_to_variant(attr_data->value_type, _team_setting_get_field(self, attr_data)); } if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) return _nm_utils_team_link_watchers_to_variant(self->d.link_watchers); if (!self->d.is_port && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) { return g_variant_new_strv(self->d.master.runner_tx_hash ? (const char *const *) self->d.master.runner_tx_hash->pdata : NULL, self->d.master.runner_tx_hash ? self->d.master.runner_tx_hash->len : 0u); } nm_assert_not_reached(); return NULL; } static void _nm_team_settings_property_from_dbus_link_watchers(GVariant *dbus_value, GValue *prop_value) { g_value_take_boxed(prop_value, _nm_utils_team_link_watchers_from_variant(dbus_value, FALSE, NULL)); } const NMSettInfoPropertType nm_sett_info_propert_type_team_b = { .dbus_type = G_VARIANT_TYPE_BOOLEAN, .to_dbus_fcn = _nm_team_settings_property_to_dbus, }; const NMSettInfoPropertType nm_sett_info_propert_type_team_i = { .dbus_type = G_VARIANT_TYPE_INT32, .to_dbus_fcn = _nm_team_settings_property_to_dbus, }; const NMSettInfoPropertType nm_sett_info_propert_type_team_s = { .dbus_type = G_VARIANT_TYPE_STRING, .to_dbus_fcn = _nm_team_settings_property_to_dbus, }; const NMSettInfoPropertType nm_sett_info_propert_type_team_as = { .dbus_type = NM_G_VARIANT_TYPE("as"), .to_dbus_fcn = _nm_team_settings_property_to_dbus, }; const NMSettInfoPropertType nm_sett_info_propert_type_team_link_watchers = { .dbus_type = NM_G_VARIANT_TYPE("aa{sv}"), .to_dbus_fcn = _nm_team_settings_property_to_dbus, .gprop_from_dbus_fcn = _nm_team_settings_property_from_dbus_link_watchers, }; /*****************************************************************************/ NMTeamSetting * nm_team_setting_new(gboolean is_port, const char *js_str) { NMTeamSetting *self; gsize l; G_STATIC_ASSERT_EXPR(sizeof(*self) == sizeof(self->_data_priv)); G_STATIC_ASSERT_EXPR(sizeof(*self) == NM_CONST_MAX(nm_offsetofend(NMTeamSetting, d.master), nm_offsetofend(NMTeamSetting, d.port))); l = is_port ? nm_offsetofend(NMTeamSetting, d.port) : nm_offsetofend(NMTeamSetting, d.master); self = g_malloc0(l); self->_data_priv.is_port = is_port; self->_data_priv.strict_validated = TRUE; self->_data_priv._js_str_need_synthetize = FALSE; self->_data_priv.link_watchers = g_ptr_array_new_with_free_func((GDestroyNotify) nm_team_link_watcher_unref); _team_setting_ASSERT(self); nm_team_setting_config_set(self, js_str); _team_setting_ASSERT(self); return self; } void nm_team_setting_free(NMTeamSetting *self) { if (!self) return; _team_setting_ASSERT(self); if (!self->d.is_port) { nm_clear_pointer(((GPtrArray **) &self->_data_priv.master.runner_tx_hash), g_ptr_array_unref); g_free((char *) self->_data_priv.master.runner); g_free((char *) self->_data_priv.master.runner_hwaddr_policy); g_free((char *) self->_data_priv.master.runner_tx_balancer); g_free((char *) self->_data_priv.master.runner_agg_select_policy); } g_ptr_array_unref((GPtrArray *) self->_data_priv.link_watchers); g_free((char *) self->_data_priv._js_str); g_free(self); }