| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include "device-private.h" |
| #include "hexdecoct.h" |
| #include "netlink-util.h" |
| #include "strv.h" |
| #include "udev-netlink.h" |
| |
| void link_info_clear(LinkInfo *info) { |
| if (!info) |
| return; |
| |
| info->ifname = mfree(info->ifname); |
| info->ifalias = mfree(info->ifalias); |
| info->phys_port_id = mfree(info->phys_port_id); |
| info->phys_switch_id = mfree(info->phys_switch_id); |
| info->phys_port_name = mfree(info->phys_port_name); |
| } |
| |
| int link_info_get(sd_netlink **rtnl, int ifindex, LinkInfo *ret) { |
| _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; |
| _cleanup_(link_info_clear) LinkInfo info = LINK_INFO_NULL; |
| uint16_t nlmsg_type, max_attr; |
| int r; |
| |
| assert(rtnl); |
| assert(ifindex > 0); |
| assert(ret); |
| |
| if (!*rtnl) { |
| r = sd_netlink_open(rtnl); |
| if (r < 0) |
| return r; |
| } |
| |
| r = sd_rtnl_message_new_link(*rtnl, &message, RTM_GETLINK, ifindex); |
| if (r < 0) |
| return r; |
| |
| r = sd_netlink_call(*rtnl, message, 0, &reply); |
| if (r == -EINVAL) |
| return -ENODEV; /* The device does not exist */ |
| if (r < 0) |
| return r; |
| |
| r = sd_netlink_message_get_type(reply, &nlmsg_type); |
| if (r < 0) |
| return r; |
| if (nlmsg_type != RTM_NEWLINK) |
| return -ENXIO; |
| |
| r = sd_rtnl_message_link_get_ifindex(reply, &info.ifindex); |
| if (r < 0) |
| return r; |
| if (info.ifindex != ifindex) |
| return -ENXIO; |
| |
| r = sd_rtnl_message_link_get_type(reply, &info.iftype); |
| if (r < 0) |
| return r; |
| |
| r = netlink_message_read_hw_addr(reply, IFLA_ADDRESS, &info.hw_addr); |
| if (r < 0 && r != -ENODATA) |
| return r; |
| |
| r = netlink_message_read_hw_addr(reply, IFLA_BROADCAST, &info.broadcast); |
| if (r < 0 && r != -ENODATA) |
| return r; |
| |
| r = sd_netlink_message_read_string_strdup(reply, IFLA_IFNAME, &info.ifname); |
| if (r < 0) |
| return r; |
| |
| r = sd_netlink_message_read_u32(reply, IFLA_MTU, &info.mtu); |
| if (r < 0) |
| return r; |
| |
| r = sd_netlink_message_read_u32(reply, IFLA_LINK, &info.iflink); |
| if (r == -ENODATA) |
| info.iflink = info.ifindex; |
| else if (r < 0) |
| return r; |
| |
| r = sd_netlink_message_read_u8(reply, IFLA_LINKMODE, &info.link_mode); |
| if (r < 0) |
| return r; |
| |
| r = sd_netlink_message_read_string_strdup(reply, IFLA_IFALIAS, &info.ifalias); |
| if (r < 0 && r != -ENODATA) |
| return r; |
| |
| r = sd_netlink_message_read_u32(reply, IFLA_GROUP, &info.group); |
| if (r < 0) |
| return r; |
| |
| r = sd_netlink_message_read_data(reply, IFLA_PHYS_PORT_ID, |
| &info.phys_port_id_len, (void**) &info.phys_port_id); |
| if (r < 0 && r != -ENODATA) |
| return r; |
| |
| r = sd_netlink_message_read_data(reply, IFLA_PHYS_SWITCH_ID, |
| &info.phys_switch_id_len, (void**) &info.phys_switch_id); |
| if (r < 0 && r != -ENODATA) |
| return r; |
| |
| r = sd_netlink_message_read_string_strdup(reply, IFLA_PHYS_PORT_NAME, &info.phys_port_name); |
| if (r < 0 && r != -ENODATA) |
| return r; |
| |
| r = sd_netlink_message_get_max_attribute(reply, &max_attr); |
| if (r < 0) |
| return r; |
| |
| info.phys_port_id_supported = max_attr >= IFLA_PHYS_PORT_ID; |
| info.phys_switch_id_supported = max_attr >= IFLA_PHYS_SWITCH_ID; |
| info.phys_port_name_supported = max_attr >= IFLA_PHYS_PORT_NAME; |
| |
| *ret = info; |
| info = LINK_INFO_NULL; |
| return 0; |
| } |
| |
| static int cache_unsigned(sd_device *device, const char *attr, uint64_t val) { |
| _cleanup_free_ char *str = NULL; |
| int r; |
| |
| assert(device); |
| assert(attr); |
| |
| if (device_get_cached_sysattr_value(device, attr, NULL) != -ESTALE) |
| return 0; |
| |
| if (asprintf(&str, "%"PRIu64, val) < 0) |
| return -ENOMEM; |
| |
| r = device_cache_sysattr_value(device, attr, str); |
| if (r < 0) |
| return r; |
| |
| TAKE_PTR(str); |
| return 0; |
| } |
| |
| static int cache_hw_addr(sd_device *device, const char *attr, const struct hw_addr_data *hw_addr) { |
| _cleanup_free_ char *str = NULL; |
| int r; |
| |
| assert(device); |
| assert(attr); |
| assert(hw_addr); |
| |
| if (device_get_cached_sysattr_value(device, attr, NULL) != -ESTALE) |
| return 0; |
| |
| str = new(char, HW_ADDR_TO_STRING_MAX); |
| if (!str) |
| return -ENOMEM; |
| |
| r = device_cache_sysattr_value(device, attr, hw_addr_to_string(hw_addr, str)); |
| if (r < 0) |
| return r; |
| |
| TAKE_PTR(str); |
| return 0; |
| } |
| |
| static int cache_binary(sd_device *device, const char *attr, size_t len, const uint8_t *data) { |
| _cleanup_free_ char *str = NULL; |
| int r; |
| |
| assert(device); |
| assert(attr); |
| |
| if (device_get_cached_sysattr_value(device, attr, NULL) != -ESTALE) |
| return 0; |
| |
| if (data) { |
| size_t j = 0; |
| |
| str = new(char, len * 2 + 1); |
| if (!str) |
| return -ENOMEM; |
| |
| for (size_t i = 0; i < len; i++) { |
| str[j++] = hexchar(data[i] >> 4); |
| str[j++] = hexchar(data[i] & 0x0f); |
| } |
| |
| str[j] = '\0'; |
| } |
| |
| r = device_cache_sysattr_value(device, attr, str); |
| if (r < 0) |
| return r; |
| |
| TAKE_PTR(str); |
| return 0; |
| } |
| |
| static int cache_string(sd_device *device, const char *attr, const char *val) { |
| _cleanup_free_ char *str = NULL; |
| int r; |
| |
| assert(device); |
| assert(attr); |
| |
| if (device_get_cached_sysattr_value(device, attr, NULL) != -ESTALE) |
| return 0; |
| |
| if (val) { |
| str = strdup(val); |
| if (!str) |
| return -ENOMEM; |
| } |
| |
| r = device_cache_sysattr_value(device, attr, str); |
| if (r < 0) |
| return r; |
| |
| TAKE_PTR(str); |
| return 0; |
| } |
| |
| int device_cache_sysattr_from_link_info(sd_device *device, LinkInfo *info) { |
| int ifindex, r; |
| |
| assert(device); |
| assert(info); |
| |
| r = sd_device_get_ifindex(device, &ifindex); |
| if (r < 0) |
| return r; |
| |
| if (ifindex != info->ifindex) |
| return -EINVAL; |
| |
| r = cache_unsigned(device, "type", info->iftype); |
| if (r < 0) |
| return r; |
| |
| r = cache_unsigned(device, "addr_len", info->hw_addr.length); |
| if (r < 0) |
| return r; |
| |
| r = cache_hw_addr(device, "address", &info->hw_addr); |
| if (r < 0) |
| return r; |
| |
| r = cache_hw_addr(device, "broadcast", &info->broadcast); |
| if (r < 0) |
| return r; |
| |
| r = cache_unsigned(device, "mtu", info->mtu); |
| if (r < 0) |
| return r; |
| |
| r = cache_unsigned(device, "iflink", info->iflink); |
| if (r < 0) |
| return r; |
| |
| r = cache_unsigned(device, "link_mode", info->link_mode); |
| if (r < 0) |
| return r; |
| |
| r = cache_string(device, "ifalias", strempty(info->ifalias)); |
| if (r < 0) |
| return r; |
| |
| r = cache_unsigned(device, "netdev_group", info->group); |
| if (r < 0) |
| return r; |
| |
| if (info->phys_port_id_supported) { |
| r = cache_binary(device, "phys_port_id", info->phys_port_id_len, info->phys_port_id); |
| if (r < 0) |
| return r; |
| } |
| |
| if (info->phys_switch_id_supported) { |
| r = cache_binary(device, "phys_switch_id", info->phys_switch_id_len, info->phys_switch_id); |
| if (r < 0) |
| return r; |
| } |
| |
| if (info->phys_port_name_supported) { |
| r = cache_string(device, "phys_port_name", info->phys_port_name); |
| if (r < 0) |
| return r; |
| } |
| |
| return 0; |
| } |
| |
| int device_get_sysattr_value_maybe_from_netlink( |
| sd_device *device, |
| sd_netlink **rtnl, |
| const char *sysattr, |
| const char **ret_value) { |
| |
| _cleanup_(link_info_clear) LinkInfo info = LINK_INFO_NULL; |
| int ifindex, r; |
| |
| assert(device); |
| assert(rtnl); |
| assert(sysattr); |
| |
| if (sd_device_get_ifindex(device, &ifindex) < 0) |
| return sd_device_get_sysattr_value(device, sysattr, ret_value); |
| |
| if (!STR_IN_SET(sysattr, |
| "type", "addr_len", "address", "broadcast", "mtu", "iflink", "linkmode", |
| "ifalias", "group", "phys_port_id", "phys_switch_id", "phys_port_name")) |
| return sd_device_get_sysattr_value(device, sysattr, ret_value); |
| |
| r = device_get_cached_sysattr_value(device, sysattr, ret_value); |
| if (r != -ESTALE) |
| return r; |
| |
| r = link_info_get(rtnl, ifindex, &info); |
| if (r < 0) |
| return r; |
| |
| r = device_cache_sysattr_from_link_info(device, &info); |
| if (r < 0) |
| return r; |
| |
| /* Do not use device_get_cached_sysattr_value() here, as kernel may not support |
| * IFLA_PHYS_PORT_NAME, and in that case we need to read the value from sysfs. */ |
| return sd_device_get_sysattr_value(device, sysattr, ret_value); |
| } |