From 6c6930077cdc705b3c2f5b28aa0af7c49e16fade Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Mon, 3 Apr 2023 02:20:32 +0800 Subject: [PATCH 1/7] networkctl: check netns only if networkd is running --- src/network/networkctl.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 9b21afe405b..5ef3cbabec8 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -120,12 +120,12 @@ static int acquire_bus(sd_bus **ret) { if (r < 0) return log_error_errno(r, "Failed to connect system bus: %m"); - r = check_netns_match(bus); - if (r < 0) - return r; - - if (!networkd_is_running()) - fprintf(stderr, "WARNING: systemd-networkd is not running, output will be incomplete.\n\n"); + if (networkd_is_running()) { + r = check_netns_match(bus); + if (r < 0) + return r; + } else + log_warning("systemd-networkd is not running, output might be incomplete."); *ret = TAKE_PTR(bus); return 0; From 43449ca327530daaa0ed3ba22208008669cdb701 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Mon, 10 Apr 2023 07:31:24 +0800 Subject: [PATCH 2/7] networkctl: add missing asserts --- src/network/networkctl.c | 73 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 6 deletions(-) diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 5ef3cbabec8..95cf064aa91 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -86,6 +86,8 @@ static int check_netns_match(sd_bus *bus) { uint64_t id; int r; + assert(bus); + r = bus_get_property_trivial(bus, bus_network_mgr, "NamespaceId", &error, 't', &id); if (r < 0) { log_debug_errno(r, "Failed to query network namespace of networkd, ignoring: %s", bus_error_message(&error, r)); @@ -137,6 +139,9 @@ static int get_description(sd_bus *bus, JsonVariant **ret) { const char *text; int r; + assert(bus); + assert(ret); + 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)); @@ -156,6 +161,8 @@ static int dump_manager_description(sd_bus *bus) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; int r; + assert(bus); + r = get_description(bus, &v); if (r < 0) return r; @@ -164,13 +171,16 @@ static int dump_manager_description(sd_bus *bus) { return 0; } -static int dump_link_description(sd_bus *bus, char **patterns) { +static int dump_link_description(sd_bus *bus, char * const *patterns) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_free_ bool *matched_patterns = NULL; JsonVariant *i; size_t c = 0; int r; + assert(bus); + assert(patterns); + r = get_description(bus, &v); if (r < 0) return r; @@ -229,7 +239,12 @@ static int dump_link_description(sd_bus *bus, char **patterns) { return 0; } -static void operational_state_to_color(const char *name, const char *state, const char **on, const char **off) { +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") || (streq_ptr(name, "lo") && streq_ptr(state, "carrier"))) { if (on) @@ -539,7 +554,12 @@ static int decode_netdev(sd_netlink_message *m, LinkInfo *info) { return 0; } -static int decode_link(sd_netlink_message *m, LinkInfo *info, char **patterns, bool matched_patterns[]) { +static int decode_link( + sd_netlink_message *m, + LinkInfo *info, + char * const *patterns, + bool matched_patterns[]) { + _cleanup_strv_free_ char **altnames = NULL; const char *name, *qdisc; int ifindex, r; @@ -669,6 +689,14 @@ static int link_get_property( char ifindex_str[DECIMAL_STR_MAX(int)]; int r; + assert(bus); + assert(link); + assert(link->ifindex >= 0); + assert(error); + assert(reply); + assert(iface); + assert(propname); + xsprintf(ifindex_str, "%i", link->ifindex); r = sd_bus_path_encode("/org/freedesktop/network1/link", ifindex_str, &path); @@ -683,6 +711,9 @@ static int acquire_link_bitrates(sd_bus *bus, LinkInfo *link) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; + assert(bus); + assert(link); + r = link_get_property(bus, link, &error, &reply, "org.freedesktop.network1.Link", "BitRates"); if (r < 0) { bool quiet = sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY, @@ -710,7 +741,11 @@ static int acquire_link_bitrates(sd_bus *bus, LinkInfo *link) { } static void acquire_ether_link_info(int *fd, LinkInfo *link) { - if (ethtool_get_link_info(fd, link->name, + assert(fd); + assert(link); + + if (ethtool_get_link_info(fd, + link->name, &link->autonegotiation, &link->speed, &link->duplex, @@ -723,6 +758,8 @@ static void acquire_wlan_link_info(LinkInfo *link) { const char *type = NULL; int r, k = 0; + assert(link); + if (link->sd_device) (void) sd_device_get_devtype(link->sd_device, &type); if (!streq_ptr(type, "wlan")) @@ -749,7 +786,7 @@ static void acquire_wlan_link_info(LinkInfo *link) { link->has_wlan_link_info = r > 0 || k > 0; } -static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char **patterns, LinkInfo **ret) { +static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char * const *patterns, LinkInfo **ret) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; _cleanup_(link_info_array_freep) LinkInfo *links = NULL; _cleanup_free_ bool *matched_patterns = NULL; @@ -1056,6 +1093,8 @@ static int get_gateway_description( static int dump_list(Table *table, const char *prefix, char * const *l) { int r; + assert(table); + if (strv_isempty(l)) return 0; @@ -1239,6 +1278,9 @@ static int open_lldp_neighbors(int ifindex, FILE **ret) { _cleanup_fclose_ FILE *f = NULL; char p[STRLEN("/run/systemd/netif/lldp/") + DECIMAL_STR_MAX(int)]; + assert(ifindex >= 0); + assert(ret); + xsprintf(p, "/run/systemd/netif/lldp/%i", ifindex); f = fopen(p, "re"); @@ -1330,6 +1372,11 @@ static int dump_dhcp_leases(Table *table, const char *prefix, sd_bus *bus, const _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; + assert(table); + assert(prefix); + assert(bus); + assert(link); + r = link_get_property(bus, link, &error, &reply, "org.freedesktop.network1.DHCPServer", "Leases"); if (r < 0) { bool quiet = sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY); @@ -1417,6 +1464,7 @@ static int dump_dhcp_leases(Table *table, const char *prefix, sd_bus *bus, const static int dump_ifindexes(Table *table, const char *prefix, const int *ifindexes) { int r; + assert(table); assert(prefix); if (!ifindexes || ifindexes[0] <= 0) @@ -1449,6 +1497,9 @@ static int dump_ifindexes(Table *table, const char *prefix, const int *ifindexes static int dump_statistics(Table *table, const LinkInfo *info) { int r; + assert(table); + assert(info); + if (!arg_stats) return 0; @@ -1612,6 +1663,7 @@ static int link_status_one( TableCell *cell; int r; + assert(bus); assert(rtnl); assert(info); @@ -2629,6 +2681,7 @@ static int link_delete_send_message(sd_netlink *rtnl, int index) { int r; assert(rtnl); + assert(index >= 0); r = sd_rtnl_message_new_link(rtnl, &req, RTM_DELLINK, index); if (r < 0) @@ -2646,6 +2699,7 @@ static int link_up_down_send_message(sd_netlink *rtnl, char *command, int index) int r; assert(rtnl); + assert(index >= 0); r = sd_rtnl_message_new_link(rtnl, &req, RTM_SETLINK, index); if (r < 0) @@ -2739,6 +2793,10 @@ static int link_renew_one(sd_bus *bus, int index, const char *name) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; + assert(bus); + assert(index >= 0); + assert(name); + r = bus_call_method(bus, bus_network_mgr, "RenewLink", &error, NULL, "i", index); if (r < 0) return log_error_errno(r, "Failed to renew dynamic configuration of interface %s: %s", @@ -2773,6 +2831,10 @@ static int link_force_renew_one(sd_bus *bus, int index, const char *name) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; + assert(bus); + assert(index >= 0); + assert(name); + r = bus_call_method(bus, bus_network_mgr, "ForceRenewLink", &error, NULL, "i", index); if (r < 0) return log_error_errno(r, "Failed to force renew dynamic configuration of interface %s: %s", @@ -2900,7 +2962,6 @@ static int help(void) { } static int parse_argv(int argc, char *argv[]) { - enum { ARG_VERSION = 0x100, ARG_NO_PAGER, From 8dfc3bf597a0a663f7e429ea79fa08e3ba84e8ca Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Sat, 15 Apr 2023 02:04:34 +0800 Subject: [PATCH 3/7] networkctl: fix a typo in log message --- src/network/networkctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 95cf064aa91..0e2f59bd4f3 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -120,7 +120,7 @@ static int acquire_bus(sd_bus **ret) { r = sd_bus_open_system(&bus); if (r < 0) - return log_error_errno(r, "Failed to connect system bus: %m"); + return log_error_errno(r, "Failed to connect to system bus: %m"); if (networkd_is_running()) { r = check_netns_match(bus); From d260875f78ecdaad82e99ea298fb71b0a5daf9d3 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Sun, 9 Apr 2023 20:39:12 +0800 Subject: [PATCH 4/7] networkctl: mark some verbs as online only --- src/network/networkctl.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 0e2f59bd4f3..9cd4074fb8e 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -3042,17 +3042,17 @@ static int parse_argv(int argc, char *argv[]) { static int networkctl_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT, list_links }, - { "status", VERB_ANY, VERB_ANY, 0, link_status }, - { "lldp", VERB_ANY, VERB_ANY, 0, link_lldp_status }, - { "label", 1, 1, 0, list_address_labels }, - { "delete", 2, VERB_ANY, 0, link_delete }, - { "up", 2, VERB_ANY, 0, link_up_down }, - { "down", 2, VERB_ANY, 0, link_up_down }, - { "renew", 2, VERB_ANY, 0, link_renew }, - { "forcerenew", 2, VERB_ANY, 0, link_force_renew }, - { "reconfigure", 2, VERB_ANY, 0, verb_reconfigure }, - { "reload", 1, 1, 0, verb_reload }, + { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, list_links }, + { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, link_status }, + { "lldp", VERB_ANY, VERB_ANY, 0, link_lldp_status }, + { "label", 1, 1, 0, list_address_labels }, + { "delete", 2, VERB_ANY, 0, link_delete }, + { "up", 2, VERB_ANY, 0, link_up_down }, + { "down", 2, VERB_ANY, 0, link_up_down }, + { "renew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_renew }, + { "forcerenew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_force_renew }, + { "reconfigure", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_reconfigure }, + { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, {} }; From 35c0e3444d7587e4e48d2590df78da682f7db0de Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Sat, 13 May 2023 02:38:41 +0800 Subject: [PATCH 5/7] conf-parser: move config_get_dropin_files to conf-files --- src/basic/conf-files.c | 22 ++++++++++++++++++++++ src/basic/conf-files.h | 5 +++++ src/shared/conf-parser.c | 26 ++------------------------ 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index c31fe79ebda..a56f82f8a3e 100644 --- a/src/basic/conf-files.c +++ b/src/basic/conf-files.c @@ -350,3 +350,25 @@ int conf_files_list_with_replacement( return 0; } + +int conf_files_list_dropins( + char ***ret, + const char *dropin_dirname, + const char *root, + const char * const *dirs) { + + _cleanup_strv_free_ char **dropin_dirs = NULL; + const char *suffix; + int r; + + assert(ret); + assert(dropin_dirname); + assert(dirs); + + suffix = strjoina("/", dropin_dirname); + r = strv_extend_strv_concat(&dropin_dirs, (char**) dirs, suffix); + if (r < 0) + return r; + + return conf_files_list_strv(ret, ".conf", root, 0, (const char* const*) dropin_dirs); +} diff --git a/src/basic/conf-files.h b/src/basic/conf-files.h index 547c2fc137f..566cc8fda46 100644 --- a/src/basic/conf-files.h +++ b/src/basic/conf-files.h @@ -24,3 +24,8 @@ int conf_files_list_with_replacement( const char *replacement, char ***files, char **replace_file); +int conf_files_list_dropins( + char ***ret, + const char *dropin_dirname, + const char *root, + const char * const *dirs); diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index 21ba7f69149..10bef002b01 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -581,28 +581,6 @@ int config_parse_config_file( sections, lookup, table, flags, userdata, NULL); } -static int config_get_dropin_files( - const char* const* conf_file_dirs, - const char *dropin_dirname, - const char *root, - char ***ret) { - - _cleanup_strv_free_ char **dropin_dirs = NULL; - const char *suffix; - int r; - - assert(conf_file_dirs); - assert(dropin_dirname); - assert(ret); - - suffix = strjoina("/", dropin_dirname); - r = strv_extend_strv_concat(&dropin_dirs, (char**) conf_file_dirs, suffix); - if (r < 0) - return r; - - return conf_files_list_strv(ret, ".conf", root, 0, (const char* const*) dropin_dirs); -} - /* Parse each config file in the directories specified as strv. */ int config_parse_many( const char* const* conf_files, @@ -625,7 +603,7 @@ int config_parse_many( assert(sections); assert(table); - r = config_get_dropin_files(conf_file_dirs, dropin_dirname, root, &files); + r = conf_files_list_dropins(&files, dropin_dirname, root, conf_file_dirs); if (r < 0) return r; @@ -661,7 +639,7 @@ static int dropins_get_stats_by_path( if (!strextend(&dropin_dirname, ".d")) return -ENOMEM; - r = config_get_dropin_files(conf_file_dirs, dropin_dirname, /* root = */ NULL, &files); + r = conf_files_list_dropins(&files, dropin_dirname, /* root = */ NULL, conf_file_dirs); if (r < 0) return r; From 96bab8fd63d29a8e3a905bb75167862fc0e5e0f1 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Sat, 1 Apr 2023 19:44:29 +0800 Subject: [PATCH 6/7] networkctl: add verb edit and cat to operate on network configs This adds two verbs, edit and cat, to networkctl for operating on network configs (namely .network, .netdev and .link files). Specially, if the config name is prefixed by @, it will be treated as network interface name, and operations will be performed on config files associated with the link. Closes #26906 --- man/networkctl.xml | 51 ++++ src/network/networkctl.c | 510 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 560 insertions(+), 1 deletion(-) diff --git a/man/networkctl.xml b/man/networkctl.xml index 94ec3dc6312..497d88a15f8 100644 --- a/man/networkctl.xml +++ b/man/networkctl.xml @@ -355,6 +355,38 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR) which match the file are reconfigured. + + + edit + FILE|@DEVICE… + + Edit network configuration files, which include .network, + .netdev, and .link files. If no network config file + matching the given name is found, a new one will be created under /etc/. + Specially, if the name is prefixed by @, it will be treated as + a network interface, and editing will be performed on the network config files associated + with it. Additionally, the interface name can be suffixed with :network (default) + or :link, in order to choose the type of network config to operate on. + + If is specified, edit the drop-in file instead of + the main configuration file. Unless is specified, + systemd-networkd will be reloaded after the edit of the + .network or .netdev files finishes. + The same applies for .link files and systemd-udevd. + Note that the changed link settings are not automatically applied after reloading. + To achieve that, trigger uevents for the corresponding interface. Refer to + systemd.link5 + for more information. + + + + + cat + FILE|@DEVICE… + + Show network configuration files. This command honors + the @ prefix in the same way as edit. + @@ -405,6 +437,25 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR) + + + NAME + + + When used with edit, edit the drop-in file NAME + instead of the main configuration file. + + + + + + + + When used with edit, systemd-networkd + or systemd-udevd will not be reloaded after the editing finishes. + + + diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 9cd4074fb8e..97e6cc7f9ed 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -26,7 +26,10 @@ #include "bus-common-errors.h" #include "bus-error.h" #include "bus-locator.h" +#include "bus-wait-for-jobs.h" +#include "conf-files.h" #include "device-util.h" +#include "edit-util.h" #include "escape.h" #include "ether-addr-util.h" #include "ethtool-util.h" @@ -50,6 +53,8 @@ #include "pager.h" #include "parse-argument.h" #include "parse-util.h" +#include "path-lookup.h" +#include "path-util.h" #include "pretty-print.h" #include "set.h" #include "socket-netlink.h" @@ -64,6 +69,7 @@ #include "terminal-util.h" #include "unit-def.h" #include "verbs.h" +#include "virt.h" #include "wifi-util.h" /* Kernel defines MODULE_NAME_LEN as 64 - sizeof(unsigned long). So, 64 is enough. */ @@ -74,12 +80,16 @@ static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; +static bool arg_no_reload = false; static bool arg_all = false; static bool arg_stats = false; static bool arg_full = false; static unsigned arg_lines = 10; +static char *arg_drop_in = NULL; static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; +STATIC_DESTRUCTOR_REGISTER(arg_drop_in, freep); + static int check_netns_match(sd_bus *bus) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; struct stat st; @@ -109,7 +119,22 @@ static int check_netns_match(sd_bus *bus) { } static bool networkd_is_running(void) { - return access("/run/systemd/netif/state", F_OK) >= 0; + static int cached = -1; + int r; + + if (cached < 0) { + r = access("/run/systemd/netif/state", F_OK); + if (r < 0) { + if (errno != ENOENT) + log_debug_errno(errno, + "Failed to determine whether networkd is running, assuming it's not: %m"); + + cached = false; + } else + cached = true; + } + + return cached; } static int acquire_bus(sd_bus **ret) { @@ -2919,6 +2944,457 @@ static int verb_reconfigure(int argc, char *argv[], void *userdata) { return 0; } +typedef enum ReloadFlags { + RELOAD_NETWORKD = 1 << 0, + RELOAD_UDEVD = 1 << 1, +} ReloadFlags; + +static int get_config_files_by_name(const char *name, char **ret_path, char ***ret_dropins) { + _cleanup_free_ char *path = NULL; + int r; + + assert(name); + assert(ret_path); + + STRV_FOREACH(i, NETWORK_DIRS) { + _cleanup_free_ char *p = NULL; + + p = path_join(*i, name); + if (!p) + return -ENOMEM; + + r = RET_NERRNO(access(p, F_OK)); + if (r >= 0) { + path = TAKE_PTR(p); + break; + } + + if (r != -ENOENT) + log_debug_errno(r, "Failed to determine whether '%s' exists, ignoring: %m", p); + } + + if (!path) + return -ENOENT; + + if (ret_dropins) { + _cleanup_free_ char *dropin_dirname = NULL; + + dropin_dirname = strjoin(name, ".d"); + if (!dropin_dirname) + return -ENOMEM; + + r = conf_files_list_dropins(ret_dropins, dropin_dirname, /* root = */ NULL, NETWORK_DIRS); + if (r < 0) + return r; + } + + *ret_path = TAKE_PTR(path); + + return 0; +} + +static int get_dropin_by_name( + const char *name, + char * const *dropins, + char **ret) { + + assert(name); + assert(dropins); + assert(ret); + + STRV_FOREACH(i, dropins) + if (path_equal_filename(*i, name)) { + _cleanup_free_ char *d = NULL; + + d = strdup(*i); + if (!d) + return -ENOMEM; + + *ret = TAKE_PTR(d); + return 1; + } + + *ret = NULL; + return 0; +} + +static int get_network_files_by_link( + sd_netlink **rtnl, + const char *link, + char **ret_path, + char ***ret_dropins) { + + _cleanup_strv_free_ char **dropins = NULL; + _cleanup_free_ char *path = NULL; + int r, ifindex; + + assert(rtnl); + assert(link); + assert(ret_path); + assert(ret_dropins); + + ifindex = rtnl_resolve_interface_or_warn(rtnl, link); + if (ifindex < 0) + return ifindex; + + r = sd_network_link_get_network_file(ifindex, &path); + if (r == -ENODATA) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "Link '%s' has no associated network file.", link); + if (r < 0) + return log_error_errno(r, "Failed to get network file for link '%s': %m", link); + + r = sd_network_link_get_network_file_dropins(ifindex, &dropins); + if (r < 0 && r != -ENODATA) + return log_error_errno(r, "Failed to get network drop-ins for link '%s': %m", link); + + *ret_path = TAKE_PTR(path); + *ret_dropins = TAKE_PTR(dropins); + + return 0; +} + +static int get_link_files_by_link(const char *link, char **ret_path, char ***ret_dropins) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + _cleanup_strv_free_ char **dropins_split = NULL; + _cleanup_free_ char *p = NULL; + const char *path, *dropins; + int r; + + assert(link); + assert(ret_path); + assert(ret_dropins); + + r = sd_device_new_from_ifname(&device, link); + if (r < 0) + return log_error_errno(r, "Failed to create sd-device object for link '%s': %m", link); + + r = sd_device_get_property_value(device, "ID_NET_LINK_FILE", &path); + if (r == -ENOENT) + return log_error_errno(r, "Link '%s' has no associated link file.", link); + if (r < 0) + return log_error_errno(r, "Failed to get link file for link '%s': %m", link); + + r = sd_device_get_property_value(device, "ID_NET_LINK_FILE_DROPINS", &dropins); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to get link drop-ins for link '%s': %m", link); + if (r >= 0) { + r = strv_split_full(&dropins_split, dropins, ":", EXTRACT_CUNESCAPE); + if (r < 0) + return log_error_errno(r, "Failed to parse link drop-ins for link '%s': %m", link); + } + + p = strdup(path); + if (!p) + return log_oom(); + + *ret_path = TAKE_PTR(p); + *ret_dropins = TAKE_PTR(dropins_split); + + return 0; +} + +static int get_config_files_by_link_config( + const char *link_config, + sd_netlink **rtnl, + char **ret_path, + char ***ret_dropins, + ReloadFlags *ret_reload) { + + _cleanup_strv_free_ char **dropins = NULL, **link_config_split = NULL; + _cleanup_free_ char *path = NULL; + const char *ifname, *type; + ReloadFlags reload; + size_t n; + int r; + + assert(link_config); + assert(rtnl); + assert(ret_path); + assert(ret_dropins); + + link_config_split = strv_split(link_config, ":"); + if (!link_config_split) + return log_oom(); + + n = strv_length(link_config_split); + if (n == 0 || isempty(link_config_split[0])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No link name is given."); + if (n > 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid link config '%s'.", link_config); + + ifname = link_config_split[0]; + type = n == 2 ? link_config_split[1] : "network"; + + if (streq(type, "network")) { + if (!networkd_is_running()) + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), + "Cannot get network file for link if systemd-networkd is not running."); + + r = get_network_files_by_link(rtnl, ifname, &path, &dropins); + if (r < 0) + return r; + + reload = RELOAD_NETWORKD; + } else if (streq(type, "link")) { + r = get_link_files_by_link(ifname, &path, &dropins); + if (r < 0) + return r; + + reload = RELOAD_UDEVD; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid config type '%s' for link '%s'.", type, ifname); + + *ret_path = TAKE_PTR(path); + *ret_dropins = TAKE_PTR(dropins); + + if (ret_reload) + *ret_reload = reload; + + return 0; +} + +static int add_config_to_edit( + EditFileContext *context, + const char *path, + char * const *dropins) { + + _cleanup_free_ char *new_path = NULL, *dropin_path = NULL, *old_dropin = NULL; + _cleanup_strv_free_ char **comment_paths = NULL; + int r; + + assert(context); + assert(path); + assert(!arg_drop_in || dropins); + + if (path_startswith(path, "/usr")) { + _cleanup_free_ char *name = NULL; + + r = path_extract_filename(path, &name); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from '%s': %m", path); + + new_path = path_join(NETWORK_DIRS[0], name); + if (!new_path) + return log_oom(); + } + + if (!arg_drop_in) + return edit_files_add(context, new_path ?: path, path, NULL); + + r = get_dropin_by_name(arg_drop_in, dropins, &old_dropin); + if (r < 0) + return log_error_errno(r, "Failed to acquire drop-in '%s': %m", arg_drop_in); + + if (r > 0 && !path_startswith(old_dropin, "/usr")) + /* An existing drop-in is found and not in /usr/. Let's edit it directly. */ + dropin_path = TAKE_PTR(old_dropin); + else { + /* No drop-in was found or an existing drop-in resides in /usr/. Let's create + * a new drop-in file. */ + dropin_path = strjoin(new_path ?: path, ".d/", arg_drop_in); + if (!dropin_path) + return log_oom(); + } + + comment_paths = strv_new(path); + if (!comment_paths) + return log_oom(); + + r = strv_extend_strv(&comment_paths, dropins, /* filter_duplicates = */ false); + if (r < 0) + return log_oom(); + + return edit_files_add(context, dropin_path, old_dropin, comment_paths); +} + +static int udevd_reload(sd_bus *bus) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + const char *job_path; + int r; + + assert(bus); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + + r = bus_call_method(bus, + bus_systemd_mgr, + "ReloadUnit", + &error, + &reply, + "ss", + "systemd-udevd.service", + "replace"); + if (r < 0) + return log_error_errno(r, "Failed to reload systemd-udevd: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &job_path); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, job_path, /* quiet = */ true, NULL); + if (r == -ENOEXEC) { + log_debug("systemd-udevd is not running, skipping reload."); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to reload systemd-udevd: %m"); + + return 1; +} + +static int verb_edit(int argc, char *argv[], void *userdata) { + _cleanup_(edit_file_context_done) EditFileContext context = { + .marker_start = DROPIN_MARKER_START, + .marker_end = DROPIN_MARKER_END, + .remove_parent = !!arg_drop_in, + }; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + ReloadFlags reload = 0; + int r; + + if (!on_tty()) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit network config files if not on a tty."); + + r = mac_selinux_init(); + if (r < 0) + return r; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_strv_free_ char **dropins = NULL; + _cleanup_free_ char *path = NULL; + const char *link_config; + + link_config = startswith(*name, "@"); + if (link_config) { + ReloadFlags flags; + + r = get_config_files_by_link_config(link_config, &rtnl, &path, &dropins, &flags); + if (r < 0) + return r; + + reload |= flags; + + r = add_config_to_edit(&context, path, dropins); + if (r < 0) + return r; + + continue; + } + + if (ENDSWITH_SET(*name, ".network", ".netdev")) + reload |= RELOAD_NETWORKD; + else if (endswith(*name, ".link")) + reload |= RELOAD_UDEVD; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid network config name '%s'.", *name); + + r = get_config_files_by_name(*name, &path, &dropins); + if (r == -ENOENT) { + if (arg_drop_in) + return log_error_errno(r, "Cannot find network config '%s'.", *name); + + log_debug("No existing network config '%s' found, creating a new file.", *name); + + path = path_join(NETWORK_DIRS[0], *name); + if (!path) + return log_oom(); + + r = edit_files_add(&context, path, NULL, NULL); + if (r < 0) + return r; + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + + r = add_config_to_edit(&context, path, dropins); + if (r < 0) + return r; + } + + r = do_edit_files_and_install(&context); + if (r < 0) + return r; + + if (arg_no_reload) + return 0; + + if (!sd_booted() || running_in_chroot() > 0) { + log_debug("System is not booted with systemd or is running in chroot, skipping reload."); + return 0; + } + + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to system bus: %m"); + + if (FLAGS_SET(reload, RELOAD_UDEVD)) { + r = udevd_reload(bus); + if (r < 0) + return r; + } + + if (FLAGS_SET(reload, RELOAD_NETWORKD)) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + if (!networkd_is_running()) { + log_debug("systemd-networkd is not running, skipping reload."); + return 0; + } + + r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to reload systemd-networkd: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static int verb_cat(int argc, char *argv[], void *userdata) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int r, ret = 0; + + pager_open(arg_pager_flags); + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_strv_free_ char **dropins = NULL; + _cleanup_free_ char *path = NULL; + const char *link_config; + + link_config = startswith(*name, "@"); + if (link_config) { + r = get_config_files_by_link_config(link_config, &rtnl, &path, &dropins, /* ret_reload = */ NULL); + if (r < 0) + return ret < 0 ? ret : r; + } else { + r = get_config_files_by_name(*name, &path, &dropins); + if (r == -ENOENT) { + log_error_errno(r, "Cannot find network config file '%s'.", *name); + ret = ret < 0 ? ret : r; + continue; + } + if (r < 0) { + log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + return ret < 0 ? ret : r; + } + } + + r = cat_files(path, dropins, /* flags = */ 0); + if (r < 0) + return ret < 0 ? ret : r; + } + + return ret; +} + static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -2941,6 +3417,8 @@ static int help(void) { " forcerenew DEVICES... Trigger DHCP reconfiguration of all connected clients\n" " reconfigure DEVICES... Reconfigure interfaces\n" " reload Reload .network and .netdev files\n" + " edit FILES|DEVICES... Edit network configuration files\n" + " cat FILES|DEVICES... Show network configuration files\n" "\nOptions:\n" " -h --help Show this help\n" " --version Show package version\n" @@ -2952,6 +3430,9 @@ static int help(void) { " -n --lines=INTEGER Number of journal entries to show\n" " --json=pretty|short|off\n" " Generate JSON output\n" + " --no-reload Do not reload systemd-networkd or systemd-udevd\n" + " after editing network config\n" + " --drop-in=NAME Edit specified drop-in instead of main config file\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -2967,6 +3448,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_NO_PAGER, ARG_NO_LEGEND, ARG_JSON, + ARG_NO_RELOAD, + ARG_DROP_IN, }; static const struct option options[] = { @@ -2979,6 +3462,8 @@ static int parse_argv(int argc, char *argv[]) { { "full", no_argument, NULL, 'l' }, { "lines", required_argument, NULL, 'n' }, { "json", required_argument, NULL, ARG_JSON }, + { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, + { "drop-in", required_argument, NULL, ARG_DROP_IN }, {} }; @@ -3005,6 +3490,27 @@ static int parse_argv(int argc, char *argv[]) { arg_legend = false; break; + case ARG_NO_RELOAD: + arg_no_reload = true; + break; + + case ARG_DROP_IN: + if (isempty(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty drop-in file name."); + + if (!endswith(optarg, ".conf")) + arg_drop_in = strjoin(optarg, ".conf"); + else + arg_drop_in = strdup(optarg); + if (!arg_drop_in) + return log_oom(); + + if (!filename_is_valid(arg_drop_in)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid drop-in file name '%s'.", arg_drop_in); + + break; + case 'a': arg_all = true; break; @@ -3053,6 +3559,8 @@ static int networkctl_main(int argc, char *argv[]) { { "forcerenew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_force_renew }, { "reconfigure", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_reconfigure }, { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, + { "edit", 2, VERB_ANY, 0, verb_edit }, + { "cat", 2, VERB_ANY, 0, verb_cat }, {} }; From a3d975b24040db148126763f98bccc7ebd731553 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Mon, 10 Apr 2023 18:45:00 +0800 Subject: [PATCH 7/7] test: add tests for networkctl edit/cat --- test/TEST-74-AUX-UTILS/test.sh | 1 + test/units/testsuite-74.networkctl.sh | 86 +++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100755 test/units/testsuite-74.networkctl.sh diff --git a/test/TEST-74-AUX-UTILS/test.sh b/test/TEST-74-AUX-UTILS/test.sh index 1e360658ed5..f033ec469f3 100755 --- a/test/TEST-74-AUX-UTILS/test.sh +++ b/test/TEST-74-AUX-UTILS/test.sh @@ -3,6 +3,7 @@ set -e TEST_DESCRIPTION="Tests for auxiliary utilities" +NSPAWN_ARGUMENTS="--private-network" # shellcheck source=test/test-functions . "${TEST_BASE_DIR:?}/test-functions" diff --git a/test/units/testsuite-74.networkctl.sh b/test/units/testsuite-74.networkctl.sh new file mode 100755 index 00000000000..63c9ff13d44 --- /dev/null +++ b/test/units/testsuite-74.networkctl.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2016 +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +at_exit() { + systemctl stop systemd-networkd + + if [[ -v NETWORK_NAME && -v NETDEV_NAME && -v LINK_NAME ]]; then + rm -fvr {/usr/lib,/etc}/systemd/network/"$NETWORK_NAME" "/usr/lib/systemd/network/$NETDEV_NAME" \ + {/usr/lib,/etc}/systemd/network/"$LINK_NAME" "/etc/systemd/network/${NETWORK_NAME}.d" \ + "new" "+4" + fi +} + +trap at_exit EXIT + +export NETWORK_NAME="networkctl-test-$RANDOM.network" +export NETDEV_NAME="networkctl-test-$RANDOM.netdev" +export LINK_NAME="90-networkctl-test-$RANDOM.link" +cat >"/usr/lib/systemd/network/$NETWORK_NAME" <new <"+4" <"/usr/lib/systemd/network/$NETDEV_NAME" <"/usr/lib/systemd/network/$LINK_NAME" <