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/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/network/networkctl.c b/src/network/networkctl.c index 9b21afe405b..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,18 +80,24 @@ 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; 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)); @@ -107,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) { @@ -118,14 +145,14 @@ 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"); - 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; @@ -137,6 +164,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 +186,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 +196,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 +264,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 +579,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 +714,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 +736,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 +766,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 +783,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 +811,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 +1118,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 +1303,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 +1397,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 +1489,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 +1522,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 +1688,7 @@ static int link_status_one( TableCell *cell; int r; + assert(bus); assert(rtnl); assert(info); @@ -2629,6 +2706,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 +2724,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 +2818,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 +2856,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", @@ -2857,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; @@ -2879,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" @@ -2890,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(), @@ -2900,12 +3443,13 @@ static int help(void) { } static int parse_argv(int argc, char *argv[]) { - enum { ARG_VERSION = 0x100, ARG_NO_PAGER, ARG_NO_LEGEND, ARG_JSON, + ARG_NO_RELOAD, + ARG_DROP_IN, }; static const struct option options[] = { @@ -2918,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 }, {} }; @@ -2944,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; @@ -2981,17 +3548,19 @@ 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 }, + { "edit", 2, VERB_ANY, 0, verb_edit }, + { "cat", 2, VERB_ANY, 0, verb_cat }, {} }; 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; 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" <