From 170e88c8e3f321590688e4cd68e829ef6abe7101 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 12 May 2021 20:58:06 +0900 Subject: [PATCH 1/7] network: move and rename network_get() -> link_get_network() This also drops test_network_get(). But it tests almost nothing. So, it should be ok to not re-implement something like test_link_get_network(). --- src/network/networkd-link.c | 71 ++++++++++++++++++++++++++-------- src/network/networkd-network.c | 37 ------------------ src/network/networkd-network.h | 5 --- src/network/test-network.c | 32 ++------------- 4 files changed, 59 insertions(+), 86 deletions(-) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 152ac05e7c..7c24138a4a 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -2226,6 +2226,55 @@ static int link_configure_continue(Link *link) { return link_enter_join_netdev(link); } +static int link_get_network(Link *link, Network **ret) { + Network *network; + + assert(link); + assert(link->manager); + assert(ret); + + ORDERED_HASHMAP_FOREACH(network, link->manager->networks) { + bool warn = false; + + if (!net_match_config( + &network->match, + link->sd_device, + &link->hw_addr.addr.ether, + &link->permanent_mac, + link->driver, + link->iftype, + link->ifname, + link->alternative_names, + link->wlan_iftype, + link->ssid, + &link->bssid)) + continue; + + if (network->match.ifname && link->sd_device) { + uint8_t name_assign_type = NET_NAME_UNKNOWN; + const char *attr; + + if (sd_device_get_sysattr_value(link->sd_device, "name_assign_type", &attr) >= 0) + (void) safe_atou8(attr, &name_assign_type); + + warn = name_assign_type == NET_NAME_ENUM; + } + + log_link_full(link, warn ? LOG_WARNING : LOG_DEBUG, + "found matching network '%s'%s.", + network->filename, + warn ? ", based on potentially unpredictable interface name" : ""); + + if (network->unmanaged) + return -ENOENT; + + *ret = network; + return 0; + } + + return -ENOENT; +} + static int link_reconfigure_internal(Link *link, sd_netlink_message *m, bool force) { _cleanup_strv_free_ char **s = NULL; Network *network; @@ -2243,17 +2292,12 @@ static int link_reconfigure_internal(Link *link, sd_netlink_message *m, bool for strv_free_and_replace(link->alternative_names, s); - r = network_get(link->manager, link->iftype, link->sd_device, - link->ifname, link->alternative_names, link->driver, - &link->hw_addr.addr.ether, &link->permanent_mac, - link->wlan_iftype, link->ssid, &link->bssid, &network); + r = link_get_network(link, &network); if (r == -ENOENT) { link_enter_unmanaged(link); return 0; - } else if (r == 0 && network->unmanaged) { - link_enter_unmanaged(link); - return 0; - } else if (r < 0) + } + if (r < 0) return r; if (link->network == network && !force) @@ -2377,17 +2421,12 @@ static int link_initialized_and_synced(Link *link) { if (r < 0) return r; - r = network_get(link->manager, link->iftype, link->sd_device, - link->ifname, link->alternative_names, link->driver, - &link->hw_addr.addr.ether, &link->permanent_mac, - link->wlan_iftype, link->ssid, &link->bssid, &network); + r = link_get_network(link, &network); if (r == -ENOENT) { link_enter_unmanaged(link); return 0; - } else if (r == 0 && network->unmanaged) { - link_enter_unmanaged(link); - return 0; - } else if (r < 0) + } + if (r < 0) return r; if (link->flags & IFF_LOOPBACK) { diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 8b236342b5..482c31064f 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -646,43 +646,6 @@ int network_get_by_name(Manager *manager, const char *name, Network **ret) { return 0; } -int network_get(Manager *manager, unsigned short iftype, sd_device *device, - const char *ifname, char * const *alternative_names, const char *driver, - const struct ether_addr *mac, const struct ether_addr *permanent_mac, - enum nl80211_iftype wlan_iftype, const char *ssid, const struct ether_addr *bssid, - Network **ret) { - Network *network; - - assert(manager); - assert(ret); - - ORDERED_HASHMAP_FOREACH(network, manager->networks) - if (net_match_config(&network->match, device, mac, permanent_mac, driver, iftype, - ifname, alternative_names, wlan_iftype, ssid, bssid)) { - if (network->match.ifname && device) { - const char *attr; - uint8_t name_assign_type = NET_NAME_UNKNOWN; - - if (sd_device_get_sysattr_value(device, "name_assign_type", &attr) >= 0) - (void) safe_atou8(attr, &name_assign_type); - - if (name_assign_type == NET_NAME_ENUM) - log_warning("%s: found matching network '%s', based on potentially unpredictable ifname", - ifname, network->filename); - else - log_debug("%s: found matching network '%s'", ifname, network->filename); - } else - log_debug("%s: found matching network '%s'", ifname, network->filename); - - *ret = network; - return 0; - } - - *ret = NULL; - - return -ENOENT; -} - bool network_has_static_ipv6_configurations(Network *network) { Address *address; Route *route; diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index c094065a83..e27a27cd02 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -341,11 +341,6 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi int network_verify(Network *network); int network_get_by_name(Manager *manager, const char *name, Network **ret); -int network_get(Manager *manager, unsigned short iftype, sd_device *device, - const char *ifname, char * const *alternative_names, const char *driver, - const struct ether_addr *mac, const struct ether_addr *permanent_mac, - enum nl80211_iftype wlan_iftype, const char *ssid, const struct ether_addr *bssid, - Network **ret); void network_apply_anonymize_if_set(Network *network); bool network_has_static_ipv6_configurations(Network *network); diff --git a/src/network/test-network.c b/src/network/test-network.c index 25ff3a33a8..09e7b99d23 100644 --- a/src/network/test-network.c +++ b/src/network/test-network.c @@ -168,23 +168,6 @@ static int test_load_config(Manager *manager) { return 0; } -static void test_network_get(Manager *manager, sd_device *loopback) { - Network *network; - const struct ether_addr mac = ETHER_ADDR_NULL; - int r; - - /* Let's hope that the test machine does not have a .network file that applies to loopback device… - * But it is still possible, so let's allow that case too. */ - r = network_get(manager, 0, loopback, "lo", NULL, NULL, &mac, &mac, 0, NULL, NULL, &network); - if (r == -ENOENT) - /* The expected case */ - assert_se(!network); - else if (r >= 0) - assert_se(network); - else - assert_not_reached("bad error!"); -} - static void test_address_equality(void) { _cleanup_(address_freep) Address *a1 = NULL, *a2 = NULL; @@ -277,8 +260,7 @@ static void test_dhcp_hostname_shorten_overlong(void) { int main(void) { _cleanup_(manager_freep) Manager *manager = NULL; - _cleanup_(sd_device_unrefp) sd_device *loopback = NULL; - int ifindex, r; + int r; test_setup_logging(LOG_INFO); @@ -293,15 +275,9 @@ int main(void) { r = test_load_config(manager); if (r == -EPERM) - return log_tests_skipped("Cannot load configuration"); - assert_se(r == 0); - - assert_se(sd_device_new_from_syspath(&loopback, "/sys/class/net/lo") >= 0); - assert_se(loopback); - assert_se(sd_device_get_ifindex(loopback, &ifindex) >= 0); - assert_se(ifindex == 1); - - test_network_get(manager, loopback); + log_debug("Cannot load configuration, ignoring."); + else + assert_se(r == 0); assert_se(manager_enumerate(manager) >= 0); return 0; From 1a3caa49d757ed0b90f86d0f1c3eee55a1588e61 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 12 May 2021 21:07:14 +0900 Subject: [PATCH 2/7] udev,network: make link_get_type_string() return negative errno on failure And make net_match_config() propagate the error. --- src/libsystemd/sd-network/network-util.c | 19 +++++++++++++------ src/libsystemd/sd-network/network-util.h | 2 +- src/network/networkctl.c | 8 ++++++-- src/network/networkd-link.c | 8 ++++++-- src/shared/net-condition.c | 7 +++++-- src/shared/net-condition.h | 2 +- src/udev/net/link-config.c | 22 +++++++++++++--------- 7 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/libsystemd/sd-network/network-util.c b/src/libsystemd/sd-network/network-util.c index c1d3a9f6a2..4570098388 100644 --- a/src/libsystemd/sd-network/network-util.c +++ b/src/libsystemd/sd-network/network-util.c @@ -119,24 +119,31 @@ int parse_operational_state_range(const char *str, LinkOperationalStateRange *ou return 0; } -char *link_get_type_string(sd_device *device, unsigned short iftype) { +int link_get_type_string(sd_device *device, unsigned short iftype, char **ret) { const char *t; char *p; if (device && sd_device_get_devtype(device, &t) >= 0 && - !isempty(t)) - return strdup(t); + !isempty(t)) { + p = strdup(t); + if (!p) + return -ENOMEM; + + *ret = p; + return 0; + } t = arphrd_to_name(iftype); if (!t) - return NULL; + return -ENOENT; p = strdup(t); if (!p) - return NULL; + return -ENOMEM; - return ascii_strlower(p); + *ret = ascii_strlower(p); + return 0; } const char *net_get_name_persistent(sd_device *device) { diff --git a/src/libsystemd/sd-network/network-util.h b/src/libsystemd/sd-network/network-util.h index 3bd10c42de..d33f42cfb0 100644 --- a/src/libsystemd/sd-network/network-util.h +++ b/src/libsystemd/sd-network/network-util.h @@ -76,6 +76,6 @@ typedef struct LinkOperationalStateRange { int parse_operational_state_range(const char *str, LinkOperationalStateRange *out); -char *link_get_type_string(sd_device *device, unsigned short iftype); +int link_get_type_string(sd_device *device, unsigned short iftype, char **ret); int net_get_unique_predictable_data(sd_device *device, bool use_sysname, uint64_t *result); const char *net_get_name_persistent(sd_device *device); diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 52abcddd64..d60b75a045 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -715,7 +715,9 @@ static int list_links(int argc, char *argv[], void *userdata) { setup_state = strdup("unmanaged"); setup_state_to_color(setup_state, &on_color_setup, NULL); - t = link_get_type_string(links[i].sd_device, links[i].iftype); + r = link_get_type_string(links[i].sd_device, links[i].iftype, &t); + if (r == -ENOMEM) + return log_oom(); r = table_add_many(table, TABLE_INT, links[i].ifindex, @@ -1436,7 +1438,9 @@ static int link_status_one( (void) sd_device_get_property_value(info->sd_device, "ID_MODEL", &model); } - t = link_get_type_string(info->sd_device, info->iftype); + r = link_get_type_string(info->sd_device, info->iftype, &t); + if (r == -ENOMEM) + return log_oom(); (void) sd_network_link_get_network_file(info->ifindex, &network); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 7c24138a4a..9d30e16b0a 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -2228,6 +2228,7 @@ static int link_configure_continue(Link *link) { static int link_get_network(Link *link, Network **ret) { Network *network; + int r; assert(link); assert(link->manager); @@ -2236,7 +2237,7 @@ static int link_get_network(Link *link, Network **ret) { ORDERED_HASHMAP_FOREACH(network, link->manager->networks) { bool warn = false; - if (!net_match_config( + r = net_match_config( &network->match, link->sd_device, &link->hw_addr.addr.ether, @@ -2247,7 +2248,10 @@ static int link_get_network(Link *link, Network **ret) { link->alternative_names, link->wlan_iftype, link->ssid, - &link->bssid)) + &link->bssid); + if (r < 0) + return r; + if (r == 0) continue; if (network->match.ifname && link->sd_device) { diff --git a/src/shared/net-condition.c b/src/shared/net-condition.c index 2479a5672c..4742f37447 100644 --- a/src/shared/net-condition.c +++ b/src/shared/net-condition.c @@ -134,7 +134,7 @@ static const char *const wifi_iftype_table[NL80211_IFTYPE_MAX+1] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(wifi_iftype, enum nl80211_iftype); -bool net_match_config( +int net_match_config( const NetMatch *match, sd_device *device, const struct ether_addr *mac, @@ -149,10 +149,13 @@ bool net_match_config( _cleanup_free_ char *iftype_str = NULL; const char *path = NULL; + int r; assert(match); - iftype_str = link_get_type_string(device, iftype); + r = link_get_type_string(device, iftype, &iftype_str); + if (r == -ENOMEM) + return r; if (device) { const char *mac_str; diff --git a/src/shared/net-condition.h b/src/shared/net-condition.h index 8d85fc8a7f..d61537e5e3 100644 --- a/src/shared/net-condition.h +++ b/src/shared/net-condition.h @@ -26,7 +26,7 @@ typedef struct NetMatch { void net_match_clear(NetMatch *match); bool net_match_is_empty(const NetMatch *match); -bool net_match_config( +int net_match_config( const NetMatch *match, sd_device *device, const struct ether_addr *mac, diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index 87afe8383a..3685c9d228 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -273,16 +273,20 @@ int link_config_get(link_config_ctx *ctx, sd_device *device, link_config **ret) (void) link_unsigned_attribute(device, "name_assign_type", &name_assign_type); LIST_FOREACH(links, link, ctx->links) { - if (net_match_config(&link->match, device, NULL, &permanent_mac, NULL, iftype, NULL, NULL, 0, NULL, NULL)) { - if (link->match.ifname && !strv_contains(link->match.ifname, "*") && name_assign_type == NET_NAME_ENUM) - log_device_warning(device, "Config file %s is applied to device based on potentially unpredictable interface name.", - link->filename); - else - log_device_debug(device, "Config file %s is applied", link->filename); + r = net_match_config(&link->match, device, NULL, &permanent_mac, NULL, iftype, NULL, NULL, 0, NULL, NULL); + if (r < 0) + return r; + if (r == 0) + continue; - *ret = link; - return 0; - } + if (link->match.ifname && !strv_contains(link->match.ifname, "*") && name_assign_type == NET_NAME_ENUM) + log_device_warning(device, "Config file %s is applied to device based on potentially unpredictable interface name.", + link->filename); + else + log_device_debug(device, "Config file %s is applied", link->filename); + + *ret = link; + return 0; } return -ENOENT; From 54a16efe18289126467c802651dffb9a1c4bf703 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 12 May 2021 22:11:09 +0900 Subject: [PATCH 3/7] network: introduce Describe() method for manager and links --- src/network/meson.build | 2 + src/network/networkd-json.c | 135 +++++++++++++++++++++++++++++ src/network/networkd-json.h | 10 +++ src/network/networkd-link-bus.c | 35 ++++++++ src/network/networkd-link-bus.h | 1 + src/network/networkd-manager-bus.c | 44 ++++++++++ 6 files changed, 227 insertions(+) create mode 100644 src/network/networkd-json.c create mode 100644 src/network/networkd-json.h diff --git a/src/network/meson.build b/src/network/meson.build index a8b9232e64..7b6a4b7c7a 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -79,6 +79,8 @@ sources = files(''' networkd-ipv4ll.h networkd-ipv6-proxy-ndp.c networkd-ipv6-proxy-ndp.h + networkd-json.c + networkd-json.h networkd-link-bus.c networkd-link-bus.h networkd-link.c diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c new file mode 100644 index 0000000000..7a7fc24ea6 --- /dev/null +++ b/src/network/networkd-json.c @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "network-util.h" +#include "networkd-json.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "sort-util.h" + +static int network_build_json(Network *network, JsonVariant **ret) { + assert(network); + assert(ret); + + return json_build(ret, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("NetworkFile", JSON_BUILD_STRING(network->filename)))); +} + +static int device_build_json(sd_device *device, JsonVariant **ret) { + const char *link = NULL, *path = NULL, *vendor = NULL, *model = NULL; + + assert(device); + assert(ret); + + (void) sd_device_get_property_value(device, "ID_NET_LINK_FILE", &link); + (void) sd_device_get_property_value(device, "ID_PATH", &path); + + if (sd_device_get_property_value(device, "ID_VENDOR_FROM_DATABASE", &vendor) < 0) + (void) sd_device_get_property_value(device, "ID_VENDOR", &vendor); + + if (sd_device_get_property_value(device, "ID_MODEL_FROM_DATABASE", &model) < 0) + (void) sd_device_get_property_value(device, "ID_MODEL", &model); + + return json_build(ret, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("LinkFile", JSON_BUILD_STRING(link)), + JSON_BUILD_PAIR("Path", JSON_BUILD_STRING(path)), + JSON_BUILD_PAIR("Vendor", JSON_BUILD_STRING(vendor)), + JSON_BUILD_PAIR("Model", JSON_BUILD_STRING(model)))); +} + +int link_build_json(Link *link, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_free_ char *type = NULL; + int r; + + assert(link); + assert(ret); + + r = link_get_type_string(link->sd_device, link->iftype, &type); + if (r == -ENOMEM) + return r; + + r = json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("Index", JSON_BUILD_INTEGER(link->ifindex)), + JSON_BUILD_PAIR("Name", JSON_BUILD_STRING(link->ifname)), + JSON_BUILD_PAIR("AlternativeNames", JSON_BUILD_STRV(link->alternative_names)), + JSON_BUILD_PAIR("Type", JSON_BUILD_STRING(type)), + JSON_BUILD_PAIR("Driver", JSON_BUILD_STRING(link->driver)), + JSON_BUILD_PAIR("SetupState", JSON_BUILD_STRING(link_state_to_string(link->state))), + JSON_BUILD_PAIR("OperationalState", JSON_BUILD_STRING(link_operstate_to_string(link->operstate))), + JSON_BUILD_PAIR("CarrierState", JSON_BUILD_STRING(link_carrier_state_to_string(link->carrier_state))), + JSON_BUILD_PAIR("AddressState", JSON_BUILD_STRING(link_address_state_to_string(link->address_state))), + JSON_BUILD_PAIR("IPv4AddressState", JSON_BUILD_STRING(link_address_state_to_string(link->ipv4_address_state))), + JSON_BUILD_PAIR("IPv6AddressState", JSON_BUILD_STRING(link_address_state_to_string(link->ipv6_address_state))))); + if (r < 0) + return r; + + if (link->network) { + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + + r = network_build_json(link->network, &w); + if (r < 0) + return r; + + r = json_variant_merge(&v, w); + if (r < 0) + return r; + } + + if (link->sd_device) { + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + + r = device_build_json(link->sd_device, &w); + if (r < 0) + return r; + + r = json_variant_merge(&v, w); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +static int link_json_compare(JsonVariant * const *a, JsonVariant * const *b) { + intmax_t index_a, index_b; + + assert(a && *a); + assert(b && *b); + + index_a = json_variant_integer(json_variant_by_key(*a, "Index")); + index_b = json_variant_integer(json_variant_by_key(*b, "Index")); + + return CMP(index_a, index_b); +} + +int manager_build_json(Manager *manager, JsonVariant **ret) { + JsonVariant **elements; + Link *link; + size_t n = 0; + int r; + + assert(manager); + assert(ret); + + elements = new(JsonVariant*, hashmap_size(manager->links)); + if (!elements) + return -ENOMEM; + + HASHMAP_FOREACH(link, manager->links) { + r = link_build_json(link, elements + n); + if (r < 0) + goto finalize; + n++; + } + + typesafe_qsort(elements, n, link_json_compare); + + r = json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("Interfaces", JSON_BUILD_VARIANT_ARRAY(elements, n)))); + +finalize: + json_variant_unref_many(elements, n); + free(elements); + return r; +} diff --git a/src/network/networkd-json.h b/src/network/networkd-json.h new file mode 100644 index 0000000000..25018fa097 --- /dev/null +++ b/src/network/networkd-json.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "json.h" + +typedef struct Link Link; +typedef struct Manager Manager; + +int link_build_json(Link *link, JsonVariant **ret); +int manager_build_json(Manager *manager, JsonVariant **ret); diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c index a999b05845..f2f47c52b0 100644 --- a/src/network/networkd-link-bus.c +++ b/src/network/networkd-link-bus.c @@ -10,6 +10,7 @@ #include "bus-message-util.h" #include "bus-polkit.h" #include "dns-domain.h" +#include "networkd-json.h" #include "networkd-link-bus.h" #include "networkd-link.h" #include "networkd-manager.h" @@ -678,6 +679,35 @@ int bus_link_method_reconfigure(sd_bus_message *message, void *userdata, sd_bus_ return sd_bus_reply_method_return(message, NULL); } +int bus_link_method_describe(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_free_ char *text = NULL; + Link *link = userdata; + int r; + + assert(message); + assert(link); + + r = link_build_json(link, &v); + if (r < 0) + return log_link_error_errno(link, r, "Failed to build JSON data: %m"); + + r = json_variant_format(v, 0, &text); + if (r < 0) + return log_link_error_errno(link, r, "Failed to format JSON data: %m"); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", text); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + const sd_bus_vtable link_vtable[] = { SD_BUS_VTABLE_START(0), @@ -764,6 +794,11 @@ const sd_bus_vtable link_vtable[] = { SD_BUS_NO_RESULT, bus_link_method_reconfigure, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("Describe", + SD_BUS_NO_ARGS, + SD_BUS_RESULT("s", json), + bus_link_method_describe, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END }; diff --git a/src/network/networkd-link-bus.h b/src/network/networkd-link-bus.h index 45594dfac9..ddc3fcb39d 100644 --- a/src/network/networkd-link-bus.h +++ b/src/network/networkd-link-bus.h @@ -34,3 +34,4 @@ int bus_link_method_revert_dns(sd_bus_message *message, void *userdata, sd_bus_e int bus_link_method_renew(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_link_method_force_renew(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_link_method_reconfigure(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_describe(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/network/networkd-manager-bus.c b/src/network/networkd-manager-bus.c index 0acaeb2bed..f66d7d1612 100644 --- a/src/network/networkd-manager-bus.c +++ b/src/network/networkd-manager-bus.c @@ -8,6 +8,7 @@ #include "bus-common-errors.h" #include "bus-message-util.h" #include "bus-polkit.h" +#include "networkd-json.h" #include "networkd-link-bus.h" #include "networkd-link.h" #include "networkd-manager-bus.h" @@ -229,6 +230,39 @@ static int bus_method_reload(sd_bus_message *message, void *userdata, sd_bus_err return sd_bus_reply_method_return(message, NULL); } +static int bus_method_describe_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_describe, error); +} + +static int bus_method_describe(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_free_ char *text = NULL; + Manager *manager = userdata; + int r; + + assert(message); + assert(manager); + + r = manager_build_json(manager, &v); + if (r < 0) + return log_error_errno(r, "Failed to build JSON data: %m"); + + r = json_variant_format(v, 0, &text); + if (r < 0) + return log_error_errno(r, "Failed to format JSON data: %m"); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", text); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + const sd_bus_vtable manager_vtable[] = { SD_BUS_VTABLE_START(0), @@ -333,6 +367,16 @@ const sd_bus_vtable manager_vtable[] = { SD_BUS_NO_RESULT, bus_method_reload, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("DescribeLink", + SD_BUS_ARGS("i", ifindex), + SD_BUS_RESULT("s", json), + bus_method_describe_link, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("Describe", + SD_BUS_NO_ARGS, + SD_BUS_RESULT("s", json), + bus_method_describe, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END }; From 484f22dd66a1e5187c0d6aac30ba56fd3bd856bb Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 13 May 2021 01:12:58 +0900 Subject: [PATCH 4/7] networkctl: use table_set_empty_string() --- src/network/networkctl.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/network/networkctl.c b/src/network/networkctl.c index d60b75a045..86f8526309 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -692,6 +692,8 @@ static int list_links(int argc, char *argv[], void *userdata) { table_set_width(table, 0); table_set_header(table, arg_legend); + if (table_set_empty_string(table, "n/a") < 0) + return log_oom(); assert_se(cell = table_get_cell(table, 0, 0)); (void) table_set_minimum_width(table, cell, 3); @@ -722,10 +724,10 @@ static int list_links(int argc, char *argv[], void *userdata) { r = table_add_many(table, TABLE_INT, links[i].ifindex, TABLE_STRING, links[i].name, - TABLE_STRING, strna(t), - TABLE_STRING, strna(operational_state), + TABLE_STRING, t, + TABLE_STRING, operational_state, TABLE_SET_COLOR, on_color_operational, - TABLE_STRING, strna(setup_state), + TABLE_STRING, setup_state, TABLE_SET_COLOR, on_color_setup); if (r < 0) return table_log_add_error(r); From fec7a9e0ace8da8a7562f821c37f868c78efdb96 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 14 May 2021 09:05:20 +0900 Subject: [PATCH 5/7] json: make JSON_VARIANT_ARRAY/OBJECT_FOREACH() nestable --- src/shared/json.h | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/shared/json.h b/src/shared/json.h index 148701a54f..c679f8f1ab 100644 --- a/src/shared/json.h +++ b/src/shared/json.h @@ -146,22 +146,26 @@ struct json_variant_foreach_state { size_t idx; }; +#define _JSON_VARIANT_ARRAY_FOREACH(i, v, state) \ + for (struct json_variant_foreach_state state = { (v), 0 }; \ + json_variant_is_array(state.variant) && \ + state.idx < json_variant_elements(state.variant) && \ + ({ i = json_variant_by_index(state.variant, state.idx); \ + true; }); \ + state.idx++) #define JSON_VARIANT_ARRAY_FOREACH(i, v) \ - for (struct json_variant_foreach_state _state = { (v), 0 }; \ - json_variant_is_array(_state.variant) && \ - _state.idx < json_variant_elements(_state.variant) && \ - ({ i = json_variant_by_index(_state.variant, _state.idx); \ - true; }); \ - _state.idx++) + _JSON_VARIANT_ARRAY_FOREACH(i, v, UNIQ_T(state, UNIQ)) -#define JSON_VARIANT_OBJECT_FOREACH(k, e, v) \ - for (struct json_variant_foreach_state _state = { (v), 0 }; \ - json_variant_is_object(_state.variant) && \ - _state.idx < json_variant_elements(_state.variant) && \ - ({ k = json_variant_string(json_variant_by_index(_state.variant, _state.idx)); \ - e = json_variant_by_index(_state.variant, _state.idx + 1); \ +#define _JSON_VARIANT_OBJECT_FOREACH(k, e, v, state) \ + for (struct json_variant_foreach_state state = { (v), 0 }; \ + json_variant_is_object(state.variant) && \ + state.idx < json_variant_elements(state.variant) && \ + ({ k = json_variant_string(json_variant_by_index(state.variant, state.idx)); \ + e = json_variant_by_index(state.variant, state.idx + 1); \ true; }); \ - _state.idx += 2) + state.idx += 2) +#define JSON_VARIANT_OBJECT_FOREACH(k, e, v) \ + _JSON_VARIANT_OBJECT_FOREACH(k, e, v, UNIQ_T(state, UNIQ)) int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column); From bfc2b05e59495db8a1d14b4260a91a97ea3343f8 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 13 May 2021 00:22:14 +0900 Subject: [PATCH 6/7] networkctl: introduce --json option for "status" and "list" commands When `--json` option is specified, "status" and "list" commands gives the same information, as originally "list" just gives partial information of "status" in different format. --- man/networkctl.xml | 1 + shell-completion/bash/networkctl | 15 +++- shell-completion/zsh/_networkctl | 8 ++ src/network/networkctl.c | 131 ++++++++++++++++++++++++++++++- 4 files changed, 153 insertions(+), 2 deletions(-) diff --git a/man/networkctl.xml b/man/networkctl.xml index ee204a60c5..19095164ce 100644 --- a/man/networkctl.xml +++ b/man/networkctl.xml @@ -367,6 +367,7 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR) + diff --git a/shell-completion/bash/networkctl b/shell-completion/bash/networkctl index ab2a5f7015..1e4d0a2aae 100644 --- a/shell-completion/bash/networkctl +++ b/shell-completion/bash/networkctl @@ -33,7 +33,7 @@ _networkctl() { local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword local -A OPTS=( [STANDALONE]='-a --all -h --help --version --no-pager --no-legend -s --stats -l --full' - [ARG]='-n --lines' + [ARG]='-n --lines --json' ) local -A VERBS=( @@ -51,6 +51,19 @@ _networkctl() { fi done + if __contains_word "$prev" ${OPTS[ARG]}; then + case $prev in + --json) + comps=$(networkctl --json=help | sort 2>/dev/null) + ;; + *) + return 0 + ;; + esac + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + fi + if [[ "$cur" = -* ]]; then COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) return 0 diff --git a/shell-completion/zsh/_networkctl b/shell-completion/zsh/_networkctl index 165c5411be..273e7d94a8 100644 --- a/shell-completion/zsh/_networkctl +++ b/shell-completion/zsh/_networkctl @@ -39,10 +39,18 @@ fi } +(( $+functions[_networkctl_get_json] )) || _networkctl_get_json() +{ + local -a _json_forms + _json_forms=( $(networkctl --json=help 2>/dev/null) ) + _values 'format' $_json_forms +} + _arguments \ {-a,--all}'[Show all links with status]' \ '--no-pager[Do not pipe output into a pager]' \ '--no-legend[Do not print the column headers]' \ {-h,--help}'[Show this help]' \ '--version[Show package version]' \ + '--json[Shows output formatted as JSON]:format:_networkctl_get_json' \ '*::networkctl commands:_networkctl_commands' diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 86f8526309..7953a52b9d 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -46,6 +46,7 @@ #include "network-internal.h" #include "network-util.h" #include "pager.h" +#include "parse-argument.h" #include "parse-util.h" #include "pretty-print.h" #include "set.h" @@ -75,6 +76,110 @@ static bool arg_all = false; static bool arg_stats = false; static bool arg_full = false; static unsigned arg_lines = 10; +static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; + +static int get_description(JsonVariant **ret) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + const char *text = NULL; + int r; + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect system bus: %m"); + + r = bus_call_method(bus, bus_network_mgr, "Describe", &error, &reply, NULL); + if (r < 0) + return log_error_errno(r, "Failed to get description: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "s", &text); + if (r < 0) + return bus_log_parse_error(r); + + r = json_parse(text, 0, ret, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse JSON: %m"); + + return 0; +} + +static int dump_manager_description(void) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + int r; + + r = get_description(&v); + if (r < 0) + return r; + + json_variant_dump(v, arg_json_format_flags, NULL, NULL); + return 0; +} + +static int dump_link_description(char **patterns) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_free_ bool *matched_patterns = NULL; + JsonVariant *i; + size_t c = 0; + int r; + + r = get_description(&v); + if (r < 0) + return r; + + matched_patterns = new0(bool, strv_length(patterns)); + if (!matched_patterns) + return log_oom(); + + JSON_VARIANT_ARRAY_FOREACH(i, json_variant_by_key(v, "Interfaces")) { + char ifindex_str[DECIMAL_STR_MAX(intmax_t)]; + const char *name; + intmax_t index; + size_t pos; + + name = json_variant_string(json_variant_by_key(i, "Name")); + index = json_variant_integer(json_variant_by_key(i, "Index")); + xsprintf(ifindex_str, "%ji", index); + + if (!strv_fnmatch_full(patterns, ifindex_str, 0, &pos) && + !strv_fnmatch_full(patterns, name, 0, &pos)) { + bool match = false; + JsonVariant *a; + + JSON_VARIANT_ARRAY_FOREACH(a, json_variant_by_key(i, "AlternativeNames")) + if (strv_fnmatch_full(patterns, json_variant_string(a), 0, &pos)) { + match = true; + break; + } + + if (!match) + continue; + } + + matched_patterns[pos] = true; + json_variant_dump(i, arg_json_format_flags, NULL, NULL); + c++; + } + + /* Look if we matched all our arguments that are not globs. It is OK for a glob to match + * nothing, but not for an exact argument. */ + for (size_t pos = 0; pos < strv_length(patterns); pos++) { + if (matched_patterns[pos]) + continue; + + if (string_is_glob(patterns[pos])) + log_debug("Pattern \"%s\" doesn't match any interface, ignoring.", + patterns[pos]); + else + return log_error_errno(SYNTHETIC_ERRNO(ENODEV), + "Interface \"%s\" not found.", patterns[pos]); + } + + if (c == 0) + log_warning("No interfaces matched."); + + return 0; +} static void operational_state_to_color(const char *name, const char *state, const char **on, const char **off) { if (STRPTR_IN_SET(state, "routable", "enslaved") || @@ -674,6 +779,13 @@ static int list_links(int argc, char *argv[], void *userdata) { TableCell *cell; int c, r; + if (arg_json_format_flags != JSON_FORMAT_OFF) { + if (arg_all || argc <= 1) + return dump_manager_description(); + else + return dump_link_description(strv_skip(argv, 1)); + } + r = sd_netlink_open(&rtnl); if (r < 0) return log_error_errno(r, "Failed to connect to netlink: %m"); @@ -2238,6 +2350,13 @@ static int link_status(int argc, char *argv[], void *userdata) { _cleanup_(link_info_array_freep) LinkInfo *links = NULL; int r, c; + if (arg_json_format_flags != JSON_FORMAT_OFF) { + if (arg_all || argc <= 1) + return dump_manager_description(); + else + return dump_link_description(strv_skip(argv, 1)); + } + (void) pager_open(arg_pager_flags); r = sd_bus_open_system(&bus); @@ -2728,6 +2847,8 @@ static int help(void) { " -s --stats Show detailed link statics\n" " -l --full Do not ellipsize output\n" " -n --lines=INTEGER Number of journal entries to show\n" + " --json=pretty|short|off\n" + " Generate JSON output\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -2743,6 +2864,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_VERSION = 0x100, ARG_NO_PAGER, ARG_NO_LEGEND, + ARG_JSON, }; static const struct option options[] = { @@ -2754,10 +2876,11 @@ static int parse_argv(int argc, char *argv[]) { { "stats", no_argument, NULL, 's' }, { "full", no_argument, NULL, 'l' }, { "lines", required_argument, NULL, 'n' }, + { "json", required_argument, NULL, ARG_JSON }, {} }; - int c; + int c, r; assert(argc >= 0); assert(argv); @@ -2798,6 +2921,12 @@ static int parse_argv(int argc, char *argv[]) { "Failed to parse lines '%s'", optarg); break; + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) + return r; + break; + case '?': return -EINVAL; From f8cd3f610fc0dba38f23c8e2e98bab83f38b4e72 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 13 May 2021 01:37:41 +0900 Subject: [PATCH 7/7] shell-completion: support --json option for hostnamectl --- shell-completion/bash/hostnamectl | 31 +++++++++++++++++++++++++++++-- shell-completion/zsh/_hostnamectl | 8 ++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/shell-completion/bash/hostnamectl b/shell-completion/bash/hostnamectl index 5c8d3975d7..af4339111e 100644 --- a/shell-completion/bash/hostnamectl +++ b/shell-completion/bash/hostnamectl @@ -25,11 +25,38 @@ __contains_word () { done } +__get_machines() { + local a b + machinectl list --full --no-legend --no-pager 2>/dev/null | + { while read a b; do echo " $a"; done; }; +} + _hostnamectl() { local i verb comps local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} - local OPTS='-h --help --version --transient --static --pretty - --no-ask-password -H --host -M --machine' + local -A OPTS=( + [STANDALONE]='-h --help --version --transient --static --pretty --no-ask-password' + [ARG]='-H --host -M --machine --json' + ) + + if __contains_word "$prev" ${OPTS[ARG]} ${OPTS[ARGUNKNOWN]}; then + case $prev in + --host|-H) + comps=$(compgen -A hostname) + ;; + --machine|-M) + comps=$( __get_machines ) + ;; + --json) + comps=$( hostnamectl --json=help 2>/dev/null ) + ;; + *) + return 0 + ;; + esac + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + fi if [[ $cur = -* ]]; then COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) diff --git a/shell-completion/zsh/_hostnamectl b/shell-completion/zsh/_hostnamectl index 2a6bf4d125..2f1f51005d 100644 --- a/shell-completion/zsh/_hostnamectl +++ b/shell-completion/zsh/_hostnamectl @@ -75,6 +75,13 @@ _hostnamectl_commands() { fi } +(( $+functions[_hostnamectl_get_json] )) || _hostnamectl_get_json() +{ + local -a _json_forms + _json_forms=( $(hostnamectl --json=help 2>/dev/null) ) + _values 'format' $_json_forms +} + _arguments -s \ {-h,--help}'[Show this help]' \ '--version[Show package version]' \ @@ -84,4 +91,5 @@ _arguments -s \ '--no-ask-password[Do not prompt for password]' \ {-H+,--host=}'[Operate on remote host]:userathost:_sd_hosts_or_user_at_host' \ {-M+,--machine=}'[Operate on local container]:machines:_sd_machines' \ + '--json[Shows output formatted as JSON]:format:_hostnamectl_get_json' \ '*::hostnamectl commands:_hostnamectl_commands'