diff --git a/man/networkctl.xml b/man/networkctl.xml index 9c7e35b4b00..9e2a65b879a 100644 --- a/man/networkctl.xml +++ b/man/networkctl.xml @@ -434,8 +434,9 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR) /run/, depending on whether is specified. 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. + with it. Additionally, the interface name can be suffixed with :network (default), + :link, or :netdev, 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, @@ -460,9 +461,10 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR) FILE|@DEVICE - Show network configuration files. This command honors the @ prefix in the - same way as edit. When no argument is specified, - networkd.conf5 + Show network configuration files. This command honors the @ prefix in a + similar way as edit, with support for an additional suffix :all + for showing all types of configuration files associated with the interface at once. When no argument + is specified, networkd.conf5 and its drop-in files will be shown. diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c index 9d11dce2e8f..a7d860c8bab 100644 --- a/src/libsystemd/sd-network/sd-network.c +++ b/src/libsystemd/sd-network/sd-network.c @@ -159,25 +159,37 @@ int sd_network_link_get_network_file(int ifindex, char **ret) { return network_link_get_string(ifindex, "NETWORK_FILE", ret); } -int sd_network_link_get_network_file_dropins(int ifindex, char ***ret) { - _cleanup_free_ char **sv = NULL, *joined = NULL; +int sd_network_link_get_netdev_file(int ifindex, char **ret) { + return network_link_get_string(ifindex, "NETDEV_FILE", ret); +} + +static int link_get_config_file_dropins_internal(int ifindex, const char *field, char ***ret) { + _cleanup_free_ char *s = NULL; int r; + assert(field); assert_return(ifindex > 0, -EINVAL); assert_return(ret, -EINVAL); - r = network_link_get_string(ifindex, "NETWORK_FILE_DROPINS", &joined); + r = network_link_get_string(ifindex, field, &s); if (r < 0) return r; - r = strv_split_full(&sv, joined, ":", EXTRACT_CUNESCAPE); + r = strv_split_full(ret, s, ":", EXTRACT_CUNESCAPE); if (r < 0) return r; - *ret = TAKE_PTR(sv); return 0; } +int sd_network_link_get_network_file_dropins(int ifindex, char ***ret) { + return link_get_config_file_dropins_internal(ifindex, "NETWORK_FILE_DROPINS", ret); +} + +int sd_network_link_get_netdev_file_dropins(int ifindex, char ***ret) { + return link_get_config_file_dropins_internal(ifindex, "NETDEV_FILE_DROPINS", ret); +} + int sd_network_link_get_operational_state(int ifindex, char **ret) { return network_link_get_string(ifindex, "OPER_STATE", ret); } diff --git a/src/network/netdev/netdev.c b/src/network/netdev/netdev.c index 2b411425ba1..cc32e6d1a6e 100644 --- a/src/network/netdev/netdev.c +++ b/src/network/netdev/netdev.c @@ -215,6 +215,7 @@ static NetDev *netdev_free(NetDev *netdev) { condition_free_list(netdev->conditions); free(netdev->filename); + strv_free(netdev->dropins); free(netdev->description); free(netdev->ifname); @@ -832,7 +833,9 @@ int netdev_load_one(Manager *manager, const char *filename) { NETDEV_VTABLE(netdev)->sections, config_item_perf_lookup, network_netdev_gperf_lookup, CONFIG_PARSE_WARN, - netdev, NULL, NULL); + netdev, + NULL, + &netdev->dropins); if (r < 0) return r; /* config_parse_many() logs internally. */ diff --git a/src/network/netdev/netdev.h b/src/network/netdev/netdev.h index 3bfecda6d89..d6b363d4f17 100644 --- a/src/network/netdev/netdev.h +++ b/src/network/netdev/netdev.h @@ -116,6 +116,7 @@ typedef struct NetDev { unsigned n_ref; char *filename; + char **dropins; LIST_HEAD(Condition, conditions); diff --git a/src/network/networkctl-config-file.c b/src/network/networkctl-config-file.c index 6d60d7eb99e..eeddfb22811 100644 --- a/src/network/networkctl-config-file.c +++ b/src/network/networkctl-config-file.c @@ -23,6 +23,7 @@ #include "path-util.h" #include "pretty-print.h" #include "selinux-util.h" +#include "string-table.h" #include "strv.h" #include "virt.h" @@ -31,6 +32,22 @@ typedef enum ReloadFlags { RELOAD_UDEVD = 1 << 1, } ReloadFlags; +typedef enum LinkConfigType { + CONFIG_NETWORK, + CONFIG_LINK, + CONFIG_NETDEV, + _CONFIG_MAX, + _CONFIG_INVALID = -EINVAL, +} LinkConfigType; + +static const char* const link_config_type_table[_CONFIG_MAX] = { + [CONFIG_NETWORK] = "network", + [CONFIG_LINK] = "link", + [CONFIG_NETDEV] = "netdev", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(link_config_type, LinkConfigType); + static int get_config_files_by_name( const char *name, bool allow_masked, @@ -115,28 +132,25 @@ static int get_dropin_by_name( } static int get_network_files_by_link( - sd_netlink **rtnl, const char *link, + int ifindex, + bool ignore_missing, char **ret_path, char ***ret_dropins) { _cleanup_strv_free_ char **dropins = NULL; _cleanup_free_ char *path = NULL; - int r, ifindex; + int r; - assert(rtnl); assert(link); + assert(ifindex > 0); 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); + return log_full_errno(ignore_missing ? LOG_DEBUG : LOG_ERR, 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); @@ -150,7 +164,40 @@ static int get_network_files_by_link( return 0; } -static int get_link_files_by_link(const char *link, char **ret_path, char ***ret_dropins) { +static int get_netdev_files_by_link( + const char *link, + int ifindex, + bool ignore_missing, + char **ret_path, + char ***ret_dropins) { + + _cleanup_strv_free_ char **dropins = NULL; + _cleanup_free_ char *path = NULL; + int r; + + assert(link); + assert(ifindex > 0); + assert(ret_path); + assert(ret_dropins); + + r = sd_network_link_get_netdev_file(ifindex, &path); + if (r == -ENODATA) + return log_full_errno(ignore_missing ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(ENOENT), + "Link '%s' has no associated netdev file.", link); + if (r < 0) + return log_error_errno(r, "Failed to get netdev file for link '%s': %m", link); + + r = sd_network_link_get_netdev_file_dropins(ifindex, &dropins); + if (r < 0 && r != -ENODATA) + return log_error_errno(r, "Failed to get netdev 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, bool ignore_missing, 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; @@ -167,7 +214,8 @@ static int get_link_files_by_link(const char *link, char **ret_path, char ***ret 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); + return log_full_errno(ignore_missing ? LOG_DEBUG : LOG_ERR, 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); @@ -191,62 +239,72 @@ static int get_link_files_by_link(const char *link, char **ret_path, char ***ret } static int get_config_files_by_link_config( - const char *link_config, + const char *ifname, + LinkConfigType type, + bool ignore_missing, sd_netlink **rtnl, char **ret_path, - char ***ret_dropins, - ReloadFlags *ret_reload) { + char ***ret_dropins) { - _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(ifname); + assert(type >= 0 && type < _CONFIG_MAX); assert(rtnl); assert(ret_path); assert(ret_dropins); - link_config_split = strv_split(link_config, ":"); - if (!link_config_split) - return log_oom(); + if (type == CONFIG_LINK) + return get_link_files_by_link(ifname, ignore_missing, ret_path, ret_dropins); - 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); + if (!networkd_is_running()) + return log_full_errno(ignore_missing ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(ESRCH), + "Cannot get network/netdev file for link if systemd-networkd is not running."); - ifname = link_config_split[0]; - type = n == 2 ? link_config_split[1] : "network"; + int ifindex = rtnl_resolve_interface_or_warn(rtnl, ifname); + if (ifindex < 0) + return ifindex; - 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."); + if (type == CONFIG_NETWORK) + r = get_network_files_by_link(ifname, ifindex, ignore_missing, ret_path, ret_dropins); + else if (type == CONFIG_NETDEV) + r = get_netdev_files_by_link(ifname, ifindex, ignore_missing, ret_path, ret_dropins); + else + assert_not_reached(); - r = get_network_files_by_link(rtnl, ifname, &path, &dropins); - if (r < 0) - return r; + return r; +} - reload = RELOAD_NETWORKD; - } else if (streq(type, "link")) { - r = get_link_files_by_link(ifname, &path, &dropins); - if (r < 0) - return r; +static int parse_link_config(const char *link_config, char **ret_ifname, LinkConfigType *ret_type) { + const char *p = ASSERT_PTR(link_config); + _cleanup_free_ char *ifname = NULL; + int r; - reload = RELOAD_UDEVD; - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid config type '%s' for link '%s'.", type, ifname); + assert(ret_ifname); + assert(ret_type); - *ret_path = TAKE_PTR(path); - *ret_dropins = TAKE_PTR(dropins); + r = extract_first_word(&p, &ifname, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r <= 0) + return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), + "Failed to extract link name from '%s': %m", link_config); - if (ret_reload) - *ret_reload = reload; + if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE | IFNAME_VALID_NUMERIC)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid link name: %s", ifname); + + LinkConfigType t; + + if (isempty(p)) + t = CONFIG_NETWORK; + else if (streq(p, "all")) + t = _CONFIG_MAX; + else { + t = link_config_type_from_string(p); + if (t < 0) + return log_error_errno(t, "Invalid config type '%s' for link '%s'.", p, ifname); + } + + *ret_ifname = TAKE_PTR(ifname); + *ret_type = t; return 0; } @@ -427,18 +485,29 @@ int verb_edit(int argc, char *argv[], void *userdata) { link_config = startswith(*name, "@"); if (link_config) { - ReloadFlags flags; + _cleanup_free_ char *ifname = NULL; + LinkConfigType type; - r = get_config_files_by_link_config(link_config, &rtnl, &path, &dropins, &flags); + r = parse_link_config(link_config, &ifname, &type); if (r < 0) return r; + if (type == _CONFIG_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Config type 'all' cannot be used with 'edit'."); - reload |= flags; + r = get_config_files_by_link_config(ifname, type, + /* ignore_missing = */ false, + &rtnl, + &path, &dropins); + if (r < 0) + return r; r = add_config_to_edit(&context, path, dropins); if (r < 0) return r; + reload |= type == CONFIG_LINK ? RELOAD_UDEVD : RELOAD_NETWORKD; + continue; } @@ -482,6 +551,66 @@ int verb_edit(int argc, char *argv[], void *userdata) { return reload_daemons(reload); } +static int cat_files_by_link_one( + const char *ifname, + LinkConfigType type, + sd_netlink **rtnl, + bool ignore_missing, + bool *first) { + + _cleanup_strv_free_ char **dropins = NULL; + _cleanup_free_ char *path = NULL; + int r; + + assert(ifname); + assert(type >= 0 && type < _CONFIG_MAX); + assert(rtnl); + assert(first); + + r = get_config_files_by_link_config(ifname, type, ignore_missing, rtnl, &path, &dropins); + if (ignore_missing && IN_SET(r, -ENOENT, -ESRCH)) + return 0; + if (r < 0) + return r; + + if (!*first) + putchar('\n'); + + r = cat_files(path, dropins, /* flags = */ CAT_FORMAT_HAS_SECTIONS); + if (r < 0) + return r; + + *first = false; + + return 0; +} + +static int cat_files_by_link_config(const char *link_config, sd_netlink **rtnl, bool *first) { + _cleanup_free_ char *ifname = NULL; + LinkConfigType type; + int r; + + assert(link_config); + assert(rtnl); + assert(first); + + r = parse_link_config(link_config, &ifname, &type); + if (r < 0) + return r; + + if (type == _CONFIG_MAX) { + for (LinkConfigType i = 0; i < _CONFIG_MAX; i++) { + r = cat_files_by_link_one(ifname, i, rtnl, /* ignore_missing = */ true, first); + if (r < 0) + return r; + } + + return 0; + } + + return cat_files_by_link_one(ifname, type, rtnl, /* ignore_missing = */ false, first); +} + int verb_cat(int argc, char *argv[], void *userdata) { _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; char **args = strv_skip(argv, 1); @@ -494,37 +623,37 @@ int verb_cat(int argc, char *argv[], void *userdata) { bool first = true; STRV_FOREACH(name, args) { - _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); + r = cat_files_by_link_config(link_config, &rtnl, &first); if (r < 0) - return RET_GATHER(ret, r); - } else { - r = get_config_files_by_name(*name, /* allow_masked = */ false, &path, &dropins); - if (r == -ENOENT) { - RET_GATHER(ret, log_error_errno(r, "Cannot find network config file '%s'.", *name)); - continue; - } - if (r == -ERFKILL) { - RET_GATHER(ret, log_debug_errno(r, "Network config '%s' is masked, ignoring.", *name)); - continue; - } - if (r < 0) { - log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); - return RET_GATHER(ret, r); - } + return r; + continue; } + _cleanup_strv_free_ char **dropins = NULL; + _cleanup_free_ char *path = NULL; + + r = get_config_files_by_name(*name, /* allow_masked = */ false, &path, &dropins); + if (r == -ENOENT) { + RET_GATHER(ret, log_error_errno(r, "Cannot find network config file '%s'.", *name)); + continue; + } + if (r == -ERFKILL) { + RET_GATHER(ret, log_debug_errno(r, "Network config '%s' is masked, ignoring.", *name)); + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + if (!first) putchar('\n'); r = cat_files(path, dropins, /* flags = */ CAT_FORMAT_HAS_SECTIONS); if (r < 0) - return RET_GATHER(ret, r); + return r; first = false; } diff --git a/src/network/networkctl-status-link.c b/src/network/networkctl-status-link.c index 14c7771d861..ae13eba9ae0 100644 --- a/src/network/networkctl-status-link.c +++ b/src/network/networkctl-status-link.c @@ -227,8 +227,10 @@ static int table_add_string_line(Table *table, const char *key, const char *valu return 0; } -static int format_dropins(char **dropins) { - STRV_FOREACH(d, dropins) { +static int format_config_files(char ***files, const char *main_config) { + assert(files); + + STRV_FOREACH(d, *files) { _cleanup_free_ char *s = NULL; int glyph = *(d + 1) == NULL ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH; @@ -239,6 +241,9 @@ static int format_dropins(char **dropins) { free_and_replace(*d, s); } + if (strv_prepend(files, main_config) < 0) + return log_oom(); + return 0; } @@ -250,8 +255,8 @@ static int link_status_one( const LinkInfo *info) { _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **sip = NULL, **search_domains = NULL, - **route_domains = NULL, **link_dropins = NULL, **network_dropins = NULL; - _cleanup_free_ char *t = NULL, *network = NULL, *iaid = NULL, *duid = NULL, *captive_portal = NULL, + **route_domains = NULL, **link_dropins = NULL, **network_dropins = NULL, **netdev_dropins = NULL; + _cleanup_free_ char *t = NULL, *network = NULL, *netdev = NULL, *iaid = NULL, *duid = NULL, *captive_portal = NULL, *setup_state = NULL, *operational_state = NULL, *online_state = NULL, *activation_policy = NULL; const char *driver = NULL, *path = NULL, *vendor = NULL, *model = NULL, *link = NULL, *on_color_operational, *off_color_operational, *on_color_setup, *off_color_setup, *on_color_online; @@ -282,6 +287,8 @@ static int link_status_one( (void) sd_network_link_get_captive_portal(info->ifindex, &captive_portal); (void) sd_network_link_get_network_file(info->ifindex, &network); (void) sd_network_link_get_network_file_dropins(info->ifindex, &network_dropins); + (void) sd_network_link_get_netdev_file(info->ifindex, &netdev); + (void) sd_network_link_get_netdev_file_dropins(info->ifindex, &netdev_dropins); (void) sd_network_link_get_carrier_bound_to(info->ifindex, &carrier_bound_to); (void) sd_network_link_get_carrier_bound_by(info->ifindex, &carrier_bound_by); (void) sd_network_link_get_activation_policy(info->ifindex, &activation_policy); @@ -312,19 +319,17 @@ static int link_status_one( (void) dhcp_lease_load(&lease, lease_file); - r = format_dropins(network_dropins); + r = format_config_files(&network_dropins, network); if (r < 0) return r; - if (strv_prepend(&network_dropins, network) < 0) - return log_oom(); - - r = format_dropins(link_dropins); + r = format_config_files(&link_dropins, link); if (r < 0) return r; - if (strv_prepend(&link_dropins, link) < 0) - return log_oom(); + r = format_config_files(&netdev_dropins, netdev); + if (r < 0) + return r; table = table_new_vertical(); if (!table) @@ -333,7 +338,15 @@ static int link_status_one( if (arg_full) table_set_width(table, 0); - /* unit files and basic states. */ + /* Config files and basic states. */ + if (netdev_dropins) { + r = table_add_many(table, + TABLE_FIELD, "NetDev File", + TABLE_STRV, netdev_dropins); + if (r < 0) + return table_log_add_error(r); + } + r = table_add_many(table, TABLE_FIELD, "Link File", TABLE_STRV, link_dropins ?: STRV_MAKE("n/a"), diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index fbe4fee17de..d89097b4285 100644 --- a/src/network/networkd-state-file.c +++ b/src/network/networkd-state-file.c @@ -277,10 +277,6 @@ int manager_save(Manager *m) { address_state = LINK_ADDRESS_STATE_OFF; LinkOnlineState online_state; size_t links_offline = 0, links_online = 0; - _cleanup_(unlink_and_freep) char *temp_path = NULL; - _cleanup_strv_free_ char **p = NULL; - _cleanup_fclose_ FILE *f = NULL; - Link *link; int r; assert(m); @@ -288,6 +284,7 @@ int manager_save(Manager *m) { if (isempty(m->state_file)) return 0; /* Do not update state file when running in test mode. */ + Link *link; HASHMAP_FOREACH(link, m->links_by_index) { if (link->flags & IFF_LOOPBACK) continue; @@ -336,20 +333,14 @@ int manager_save(Manager *m) { (links_offline > 0 ? LINK_ONLINE_STATE_PARTIAL : LINK_ONLINE_STATE_ONLINE) : (links_offline > 0 ? LINK_ONLINE_STATE_OFFLINE : _LINK_ONLINE_STATE_INVALID); - operstate_str = link_operstate_to_string(operstate); - assert(operstate_str); + operstate_str = ASSERT_PTR(link_operstate_to_string(operstate)); + carrier_state_str = ASSERT_PTR(link_carrier_state_to_string(carrier_state)); + address_state_str = ASSERT_PTR(link_address_state_to_string(address_state)); + ipv4_address_state_str = ASSERT_PTR(link_address_state_to_string(ipv4_address_state)); + ipv6_address_state_str = ASSERT_PTR(link_address_state_to_string(ipv6_address_state)); - carrier_state_str = link_carrier_state_to_string(carrier_state); - assert(carrier_state_str); - - address_state_str = link_address_state_to_string(address_state); - assert(address_state_str); - - ipv4_address_state_str = link_address_state_to_string(ipv4_address_state); - assert(ipv4_address_state_str); - - ipv6_address_state_str = link_address_state_to_string(ipv6_address_state); - assert(ipv6_address_state_str); + _cleanup_(unlink_and_freep) char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; r = fopen_temporary(m->state_file, &f, &temp_path); if (r < 0) @@ -386,6 +377,8 @@ int manager_save(Manager *m) { temp_path = mfree(temp_path); + _cleanup_strv_free_ char **p = NULL; + if (m->operational_state != operstate) { m->operational_state = operstate; if (strv_extend(&p, "OperationalState") < 0) @@ -568,9 +561,32 @@ static void link_save_domains(Link *link, FILE *f, OrderedSet *static_domains, U } } +static int serialize_config_files(FILE *f, const char *prefix, const char *main_config, char * const *dropins) { + assert(f); + assert(prefix); + assert(main_config); + + fprintf(f, "%s_FILE=%s\n", prefix, main_config); + + bool space = false; + + fprintf(f, "%s_FILE_DROPINS=\"", prefix); + STRV_FOREACH(d, dropins) { + _cleanup_free_ char *escaped = NULL; + + escaped = xescape(*d, ":"); + if (!escaped) + return -ENOMEM; + + fputs_with_separator(f, escaped, ":", &space); + } + fputs("\"\n", f); + + return 0; +} + static int link_save(Link *link) { - const char *admin_state, *oper_state, *carrier_state, *address_state, *ipv4_address_state, *ipv6_address_state, - *captive_portal; + const char *admin_state, *oper_state, *carrier_state, *address_state, *ipv4_address_state, *ipv6_address_state; _cleanup_(unlink_and_freep) char *temp_path = NULL; _cleanup_fclose_ FILE *f = NULL; int r; @@ -584,23 +600,12 @@ static int link_save(Link *link) { if (link->state == LINK_STATE_LINGER) return 0; - admin_state = link_state_to_string(link->state); - assert(admin_state); - - oper_state = link_operstate_to_string(link->operstate); - assert(oper_state); - - carrier_state = link_carrier_state_to_string(link->carrier_state); - assert(carrier_state); - - address_state = link_address_state_to_string(link->address_state); - assert(address_state); - - ipv4_address_state = link_address_state_to_string(link->ipv4_address_state); - assert(ipv4_address_state); - - ipv6_address_state = link_address_state_to_string(link->ipv6_address_state); - assert(ipv6_address_state); + admin_state = ASSERT_PTR(link_state_to_string(link->state)); + oper_state = ASSERT_PTR(link_operstate_to_string(link->operstate)); + carrier_state = ASSERT_PTR(link_carrier_state_to_string(link->carrier_state)); + address_state = ASSERT_PTR(link_address_state_to_string(link->address_state)); + ipv4_address_state = ASSERT_PTR(link_address_state_to_string(link->ipv4_address_state)); + ipv6_address_state = ASSERT_PTR(link_address_state_to_string(link->ipv6_address_state)); r = fopen_temporary(link->state_file, &f, &temp_path); if (r < 0) @@ -619,7 +624,7 @@ static int link_save(Link *link) { admin_state, oper_state, carrier_state, address_state, ipv4_address_state, ipv6_address_state); if (link->network) { - const char *online_state; + const char *online_state, *captive_portal; bool space = false; online_state = link_online_state_to_string(link->online_state); @@ -641,19 +646,15 @@ static int link_save(Link *link) { fprintf(f, "ACTIVATION_POLICY=%s\n", activation_policy_to_string(link->network->activation_policy)); - fprintf(f, "NETWORK_FILE=%s\n", link->network->filename); + r = serialize_config_files(f, "NETWORK", link->network->filename, link->network->dropins); + if (r < 0) + return r; - fputs("NETWORK_FILE_DROPINS=\"", f); - STRV_FOREACH(d, link->network->dropins) { - _cleanup_free_ char *escaped = NULL; - - escaped = xescape(*d, ":"); - if (!escaped) - return -ENOMEM; - - fputs_with_separator(f, escaped, ":", &space); + if (link->netdev) { + r = serialize_config_files(f, "NETDEV", link->netdev->filename, link->netdev->dropins); + if (r < 0) + return r; } - fputs("\"\n", f); /************************************************************/ @@ -778,8 +779,9 @@ static int link_save(Link *link) { if (!set_isempty(nta_anchors)) { const char *n; - fputs("DNSSEC_NTA=", f); space = false; + + fputs("DNSSEC_NTA=", f); SET_FOREACH(n, nta_anchors) fputs_with_separator(f, n, NULL, &space); fputc('\n', f); @@ -794,9 +796,7 @@ static int link_save(Link *link) { if (r < 0) return r; - fprintf(f, - "DHCP_LEASE=%s\n", - link->lease_file); + fprintf(f, "DHCP_LEASE=%s\n", link->lease_file); } else (void) unlink(link->lease_file); diff --git a/src/systemd/sd-network.h b/src/systemd/sd-network.h index d292719a3e5..d310d238d07 100644 --- a/src/systemd/sd-network.h +++ b/src/systemd/sd-network.h @@ -122,6 +122,12 @@ int sd_network_link_get_network_file(int ifindex, char **ret); /* Get paths to .network file dropins applied to link */ int sd_network_link_get_network_file_dropins(int ifindex, char ***ret); +/* Get path to .netdev file associated with link */ +int sd_network_link_get_netdev_file(int ifindex, char **ret); + +/* Get path to .netdev file dropins associated with link */ +int sd_network_link_get_netdev_file_dropins(int ifindex, char ***ret); + /* Get DNS entries for a given link. These are string representations of * IP addresses */ int sd_network_link_get_dns(int ifindex, char ***ret); diff --git a/test/networkd-test.py b/test/networkd-test.py index 47a3ad79632..a698627454b 100755 --- a/test/networkd-test.py +++ b/test/networkd-test.py @@ -1027,6 +1027,7 @@ DNS=127.0.0.1 subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface', 'dummy0', '--timeout=10']) out = subprocess.check_output(['networkctl', 'status', 'dummy0']) + self.assertIn(b'50-test.netdev', out) self.assertIn(b'50-test.network.d/dns.conf', out) for _ in range(50): diff --git a/test/units/TEST-74-AUX-UTILS.networkctl.sh b/test/units/TEST-74-AUX-UTILS.networkctl.sh index 8c62de9155a..0576d6c0556 100755 --- a/test/units/TEST-74-AUX-UTILS.networkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.networkctl.sh @@ -99,10 +99,18 @@ cmp "/usr/lib/systemd/network/$LINK_NAME" "/etc/systemd/network/$LINK_NAME" systemctl unmask systemd-networkd systemctl stop systemd-networkd (! networkctl cat @test2) +(! networkctl cat @test2:netdev) systemctl start systemd-networkd SYSTEMD_LOG_LEVEL=debug /usr/lib/systemd/systemd-networkd-wait-online -i test2:carrier --timeout 20 + networkctl cat @test2:network | cmp - <(networkctl cat "$NETWORK_NAME") +networkctl cat @test2:netdev | cmp - <(networkctl cat "$NETDEV_NAME") +for c in "$NETWORK_NAME" "$NETDEV_NAME"; do + assert_in "$(networkctl cat "$c" | head -n1)" "$(networkctl cat @test2:all)" +done + +(! networkctl edit @test2:all) EDITOR='cp' script -ec 'networkctl edit @test2 --drop-in test2.conf' /dev/null cmp "+4" "/etc/systemd/network/${NETWORK_NAME}.d/test2.conf" @@ -112,6 +120,9 @@ SYSTEMD_LOG_LEVEL=debug /usr/lib/systemd/systemd-networkd-wait-online -i test2:c ip_link="$(ip link show test2)" if systemctl --quiet is-active systemd-udevd; then + networkctl cat @test2:link | cmp - <(networkctl cat "$LINK_NAME") + assert_in "$(networkctl cat "$LINK_NAME" | head -n1)" "$(networkctl cat @test2:all)" + assert_in 'alias test_alias' "$ip_link" fi