| /* |
| * strset.c - string set handling |
| * |
| * Implementation of local cache of ethtool string sets. |
| */ |
| |
| #include <errno.h> |
| #include <string.h> |
| |
| #include "../internal.h" |
| #include "netlink.h" |
| #include "nlsock.h" |
| #include "msgbuff.h" |
| |
| struct stringset { |
| const char **strings; |
| void *raw_data; |
| unsigned int count; |
| bool loaded; |
| }; |
| |
| struct perdev_strings { |
| int ifindex; |
| char devname[ALTIFNAMSIZ]; |
| struct stringset strings[ETH_SS_COUNT]; |
| struct perdev_strings *next; |
| }; |
| |
| /* universal string sets */ |
| static struct stringset global_strings[ETH_SS_COUNT]; |
| /* linked list of string sets related to network devices */ |
| static struct perdev_strings *device_strings; |
| |
| static void drop_stringset(struct stringset *set) |
| { |
| if (!set) |
| return; |
| |
| free(set->strings); |
| free(set->raw_data); |
| memset(set, 0, sizeof(*set)); |
| } |
| |
| static int import_stringset(struct stringset *dest, const struct nlattr *nest) |
| { |
| const struct nlattr *tb_stringset[ETHTOOL_A_STRINGSET_MAX + 1] = {}; |
| DECLARE_ATTR_TB_INFO(tb_stringset); |
| const struct nlattr *string; |
| unsigned int size; |
| unsigned int count; |
| unsigned int idx; |
| int ret; |
| |
| ret = mnl_attr_parse_nested(nest, attr_cb, &tb_stringset_info); |
| if (ret < 0) |
| return ret; |
| if (!tb_stringset[ETHTOOL_A_STRINGSET_ID] || |
| !tb_stringset[ETHTOOL_A_STRINGSET_COUNT] || |
| !tb_stringset[ETHTOOL_A_STRINGSET_STRINGS]) |
| return -EFAULT; |
| idx = mnl_attr_get_u32(tb_stringset[ETHTOOL_A_STRINGSET_ID]); |
| if (idx >= ETH_SS_COUNT) |
| return 0; |
| if (dest[idx].loaded) |
| drop_stringset(&dest[idx]); |
| dest[idx].loaded = true; |
| count = mnl_attr_get_u32(tb_stringset[ETHTOOL_A_STRINGSET_COUNT]); |
| if (count == 0) |
| return 0; |
| |
| size = mnl_attr_get_len(tb_stringset[ETHTOOL_A_STRINGSET_STRINGS]); |
| ret = -ENOMEM; |
| dest[idx].raw_data = malloc(size); |
| if (!dest[idx].raw_data) |
| goto err; |
| memcpy(dest[idx].raw_data, tb_stringset[ETHTOOL_A_STRINGSET_STRINGS], |
| size); |
| dest[idx].strings = calloc(count, sizeof(dest[idx].strings[0])); |
| if (!dest[idx].strings) |
| goto err; |
| dest[idx].count = count; |
| |
| nest = dest[idx].raw_data; |
| mnl_attr_for_each_nested(string, nest) { |
| const struct nlattr *tb[ETHTOOL_A_STRING_MAX + 1] = {}; |
| DECLARE_ATTR_TB_INFO(tb); |
| unsigned int i; |
| |
| if (mnl_attr_get_type(string) != ETHTOOL_A_STRINGS_STRING) |
| continue; |
| ret = mnl_attr_parse_nested(string, attr_cb, &tb_info); |
| if (ret < 0) |
| goto err; |
| ret = -EFAULT; |
| if (!tb[ETHTOOL_A_STRING_INDEX] || !tb[ETHTOOL_A_STRING_VALUE]) |
| goto err; |
| |
| i = mnl_attr_get_u32(tb[ETHTOOL_A_STRING_INDEX]); |
| if (i >= count) |
| goto err; |
| dest[idx].strings[i] = |
| mnl_attr_get_payload(tb[ETHTOOL_A_STRING_VALUE]); |
| } |
| |
| return 0; |
| err: |
| drop_stringset(&dest[idx]); |
| return ret; |
| } |
| |
| static struct perdev_strings *get_perdev_by_ifindex(int ifindex) |
| { |
| struct perdev_strings *perdev = device_strings; |
| |
| while (perdev && perdev->ifindex != ifindex) |
| perdev = perdev->next; |
| if (perdev) |
| return perdev; |
| |
| /* not found, allocate and insert into list */ |
| perdev = calloc(sizeof(*perdev), 1); |
| if (!perdev) |
| return NULL; |
| perdev->ifindex = ifindex; |
| perdev->next = device_strings; |
| device_strings = perdev; |
| |
| return perdev; |
| } |
| |
| static int strset_reply_cb(const struct nlmsghdr *nlhdr, void *data) |
| { |
| const struct nlattr *tb[ETHTOOL_A_STRSET_MAX + 1] = {}; |
| DECLARE_ATTR_TB_INFO(tb); |
| struct nl_context *nlctx = data; |
| char devname[ALTIFNAMSIZ] = ""; |
| struct stringset *dest; |
| struct nlattr *attr; |
| int ifindex = 0; |
| int ret; |
| |
| ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info); |
| if (ret < 0) |
| return ret; |
| if (tb[ETHTOOL_A_STRSET_HEADER]) { |
| ret = get_dev_info(tb[ETHTOOL_A_STRSET_HEADER], &ifindex, |
| devname); |
| if (ret < 0) |
| return MNL_CB_OK; |
| nlctx->devname = devname; |
| } |
| if (ifindex && !dev_ok(nlctx)) |
| return MNL_CB_OK; |
| |
| if (ifindex) { |
| struct perdev_strings *perdev; |
| |
| perdev = get_perdev_by_ifindex(ifindex); |
| if (!perdev) |
| return MNL_CB_OK; |
| copy_devname(perdev->devname, devname); |
| dest = perdev->strings; |
| } else { |
| dest = global_strings; |
| } |
| |
| if (!tb[ETHTOOL_A_STRSET_STRINGSETS]) |
| return MNL_CB_OK; |
| mnl_attr_for_each_nested(attr, tb[ETHTOOL_A_STRSET_STRINGSETS]) { |
| if (mnl_attr_get_type(attr) == |
| ETHTOOL_A_STRINGSETS_STRINGSET) |
| import_stringset(dest, attr); |
| } |
| |
| return MNL_CB_OK; |
| } |
| |
| static int fill_stringset_id(struct nl_msg_buff *msgbuff, unsigned int type) |
| { |
| struct nlattr *nest_sets; |
| struct nlattr *nest_set; |
| |
| nest_sets = ethnla_nest_start(msgbuff, ETHTOOL_A_STRSET_STRINGSETS); |
| if (!nest_sets) |
| return -EMSGSIZE; |
| nest_set = ethnla_nest_start(msgbuff, ETHTOOL_A_STRINGSETS_STRINGSET); |
| if (!nest_set) |
| goto err; |
| if (ethnla_put_u32(msgbuff, ETHTOOL_A_STRINGSET_ID, type)) |
| goto err; |
| ethnla_nest_end(msgbuff, nest_set); |
| ethnla_nest_end(msgbuff, nest_sets); |
| return 0; |
| |
| err: |
| ethnla_nest_cancel(msgbuff, nest_sets); |
| return -EMSGSIZE; |
| } |
| |
| static int stringset_load_request(struct nl_socket *nlsk, const char *devname, |
| int type, bool is_dump) |
| { |
| struct nl_msg_buff *msgbuff = &nlsk->msgbuff; |
| int ret; |
| |
| ret = msg_init(nlsk->nlctx, msgbuff, ETHTOOL_MSG_STRSET_GET, |
| NLM_F_REQUEST | NLM_F_ACK | (is_dump ? NLM_F_DUMP : 0)); |
| if (ret < 0) |
| return ret; |
| if (ethnla_fill_header(msgbuff, ETHTOOL_A_STRSET_HEADER, devname, 0)) |
| return -EMSGSIZE; |
| if (type >= 0) { |
| ret = fill_stringset_id(msgbuff, type); |
| if (ret < 0) |
| return ret; |
| } |
| |
| ret = nlsock_send_get_request(nlsk, strset_reply_cb); |
| return ret; |
| } |
| |
| /* interface */ |
| |
| const struct stringset *global_stringset(unsigned int type, |
| struct nl_socket *nlsk) |
| { |
| int ret; |
| |
| if (type >= ETH_SS_COUNT) |
| return NULL; |
| if (global_strings[type].loaded) |
| return &global_strings[type]; |
| ret = stringset_load_request(nlsk, NULL, type, false); |
| return ret < 0 ? NULL : &global_strings[type]; |
| } |
| |
| const struct stringset *perdev_stringset(const char *devname, unsigned int type, |
| struct nl_socket *nlsk) |
| { |
| const struct perdev_strings *p; |
| int ret; |
| |
| if (type >= ETH_SS_COUNT) |
| return NULL; |
| for (p = device_strings; p; p = p->next) |
| if (!strcmp(p->devname, devname)) |
| return &p->strings[type]; |
| |
| ret = stringset_load_request(nlsk, devname, type, false); |
| if (ret < 0) |
| return NULL; |
| for (p = device_strings; p; p = p->next) |
| if (!strcmp(p->devname, devname)) |
| return &p->strings[type]; |
| |
| return NULL; |
| } |
| |
| unsigned int get_count(const struct stringset *set) |
| { |
| return set->count; |
| } |
| |
| const char *get_string(const struct stringset *set, unsigned int idx) |
| { |
| if (!set || idx >= set->count) |
| return NULL; |
| return set->strings[idx]; |
| } |
| |
| int preload_global_strings(struct nl_socket *nlsk) |
| { |
| return stringset_load_request(nlsk, NULL, -1, false); |
| } |
| |
| int preload_perdev_strings(struct nl_socket *nlsk, const char *dev) |
| { |
| return stringset_load_request(nlsk, dev, -1, !dev); |
| } |
| |
| void cleanup_all_strings(void) |
| { |
| struct perdev_strings *perdev; |
| unsigned int i; |
| |
| for (i = 0; i < ETH_SS_COUNT; i++) |
| drop_stringset(&global_strings[i]); |
| |
| perdev = device_strings; |
| while (perdev) { |
| device_strings = perdev->next; |
| for (i = 0; i < ETH_SS_COUNT; i++) |
| drop_stringset(&perdev->strings[i]); |
| free(perdev); |
| perdev = device_strings; |
| } |
| } |