1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-22 17:35:35 +03:00

networkctl: support editing netdev files by link and cat ":all"

Also, don't abuse RET_GATHER in verb_cat(), where the failures
are most likely unrelated to each other.

Closes #34281
This commit is contained in:
Mike Yuan 2024-09-08 18:09:35 +02:00
parent c9837c17d5
commit 0d3787deac
No known key found for this signature in database
GPG Key ID: 417471C0A40F58B3
3 changed files with 219 additions and 77 deletions

View File

@ -434,8 +434,9 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR)
<filename>/run/</filename>, depending on whether <option>--runtime</option> is specified. <filename>/run/</filename>, depending on whether <option>--runtime</option> is specified.
Specially, if the name is prefixed by <literal>@</literal>, it will be treated as Specially, if the name is prefixed by <literal>@</literal>, it will be treated as
a network interface, and editing will be performed on the network config files associated a network interface, and editing will be performed on the network config files associated
with it. Additionally, the interface name can be suffixed with <literal>:network</literal> (default) with it. Additionally, the interface name can be suffixed with <literal>:network</literal> (default),
or <literal>:link</literal>, in order to choose the type of network config to operate on.</para> <literal>:link</literal>, or <literal>:netdev</literal>, in order to choose the type of network config
to operate on.</para>
<para>If <option>--drop-in=</option> is specified, edit the drop-in file instead of <para>If <option>--drop-in=</option> is specified, edit the drop-in file instead of
the main configuration file. Unless <option>--no-reload</option> is specified, the main configuration file. Unless <option>--no-reload</option> is specified,
@ -460,9 +461,10 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR)
<optional><replaceable>FILE</replaceable>|<replaceable>@DEVICE</replaceable></optional> <optional><replaceable>FILE</replaceable>|<replaceable>@DEVICE</replaceable></optional>
</term> </term>
<listitem> <listitem>
<para>Show network configuration files. This command honors the <literal>@</literal> prefix in the <para>Show network configuration files. This command honors the <literal>@</literal> prefix in a
same way as <command>edit</command>. When no argument is specified, similar way as <command>edit</command>, with support for an additional suffix <literal>:all</literal>
<citerefentry><refentrytitle>networkd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> for showing all types of configuration files associated with the interface at once. When no argument
is specified, <citerefentry><refentrytitle>networkd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
and its drop-in files will be shown.</para> and its drop-in files will be shown.</para>
<xi:include href="version-info.xml" xpointer="v254"/> <xi:include href="version-info.xml" xpointer="v254"/>

View File

@ -23,6 +23,7 @@
#include "path-util.h" #include "path-util.h"
#include "pretty-print.h" #include "pretty-print.h"
#include "selinux-util.h" #include "selinux-util.h"
#include "string-table.h"
#include "strv.h" #include "strv.h"
#include "virt.h" #include "virt.h"
@ -31,6 +32,22 @@ typedef enum ReloadFlags {
RELOAD_UDEVD = 1 << 1, RELOAD_UDEVD = 1 << 1,
} ReloadFlags; } 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( static int get_config_files_by_name(
const char *name, const char *name,
bool allow_masked, bool allow_masked,
@ -115,28 +132,25 @@ static int get_dropin_by_name(
} }
static int get_network_files_by_link( static int get_network_files_by_link(
sd_netlink **rtnl,
const char *link, const char *link,
int ifindex,
bool ignore_missing,
char **ret_path, char **ret_path,
char ***ret_dropins) { char ***ret_dropins) {
_cleanup_strv_free_ char **dropins = NULL; _cleanup_strv_free_ char **dropins = NULL;
_cleanup_free_ char *path = NULL; _cleanup_free_ char *path = NULL;
int r, ifindex; int r;
assert(rtnl);
assert(link); assert(link);
assert(ifindex > 0);
assert(ret_path); assert(ret_path);
assert(ret_dropins); 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); r = sd_network_link_get_network_file(ifindex, &path);
if (r == -ENODATA) if (r == -ENODATA)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), return log_full_errno(ignore_missing ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(ENOENT),
"Link '%s' has no associated network file.", link); "Link '%s' has no associated network file.", link);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to get network file for link '%s': %m", link); 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; 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_(sd_device_unrefp) sd_device *device = NULL;
_cleanup_strv_free_ char **dropins_split = NULL; _cleanup_strv_free_ char **dropins_split = NULL;
_cleanup_free_ char *p = 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); r = sd_device_get_property_value(device, "ID_NET_LINK_FILE", &path);
if (r == -ENOENT) 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) if (r < 0)
return log_error_errno(r, "Failed to get link file for link '%s': %m", link); 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( static int get_config_files_by_link_config(
const char *link_config, const char *ifname,
LinkConfigType type,
bool ignore_missing,
sd_netlink **rtnl, sd_netlink **rtnl,
char **ret_path, char **ret_path,
char ***ret_dropins, 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; int r;
assert(link_config); assert(ifname);
assert(type >= 0 && type < _CONFIG_MAX);
assert(rtnl); assert(rtnl);
assert(ret_path); assert(ret_path);
assert(ret_dropins); assert(ret_dropins);
link_config_split = strv_split(link_config, ":"); if (type == CONFIG_LINK)
if (!link_config_split) return get_link_files_by_link(ifname, ignore_missing, ret_path, ret_dropins);
return log_oom();
n = strv_length(link_config_split); if (!networkd_is_running())
if (n == 0 || isempty(link_config_split[0])) return log_full_errno(ignore_missing ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(ESRCH),
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No link name is given."); "Cannot get network/netdev file for link if systemd-networkd is not running.");
if (n > 2)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid link config '%s'.", link_config);
ifname = link_config_split[0]; int ifindex = rtnl_resolve_interface_or_warn(rtnl, ifname);
type = n == 2 ? link_config_split[1] : "network"; if (ifindex < 0)
return ifindex;
if (streq(type, "network")) { if (type == CONFIG_NETWORK)
if (!networkd_is_running()) r = get_network_files_by_link(ifname, ifindex, ignore_missing, ret_path, ret_dropins);
return log_error_errno(SYNTHETIC_ERRNO(ESRCH), else if (type == CONFIG_NETDEV)
"Cannot get network file for link if systemd-networkd is not running."); 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); return r;
if (r < 0) }
return r;
reload = RELOAD_NETWORKD; static int parse_link_config(const char *link_config, char **ret_ifname, LinkConfigType *ret_type) {
} else if (streq(type, "link")) { const char *p = ASSERT_PTR(link_config);
r = get_link_files_by_link(ifname, &path, &dropins); _cleanup_free_ char *ifname = NULL;
if (r < 0) int r;
return r;
reload = RELOAD_UDEVD; assert(ret_ifname);
} else assert(ret_type);
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid config type '%s' for link '%s'.", type, ifname);
*ret_path = TAKE_PTR(path); r = extract_first_word(&p, &ifname, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
*ret_dropins = TAKE_PTR(dropins); 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) if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE | IFNAME_VALID_NUMERIC))
*ret_reload = reload; 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; return 0;
} }
@ -427,18 +485,29 @@ int verb_edit(int argc, char *argv[], void *userdata) {
link_config = startswith(*name, "@"); link_config = startswith(*name, "@");
if (link_config) { 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) if (r < 0)
return r; 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); r = add_config_to_edit(&context, path, dropins);
if (r < 0) if (r < 0)
return r; return r;
reload |= type == CONFIG_LINK ? RELOAD_UDEVD : RELOAD_NETWORKD;
continue; continue;
} }
@ -482,6 +551,66 @@ int verb_edit(int argc, char *argv[], void *userdata) {
return reload_daemons(reload); 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) { int verb_cat(int argc, char *argv[], void *userdata) {
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
char **args = strv_skip(argv, 1); char **args = strv_skip(argv, 1);
@ -494,37 +623,37 @@ int verb_cat(int argc, char *argv[], void *userdata) {
bool first = true; bool first = true;
STRV_FOREACH(name, args) { STRV_FOREACH(name, args) {
_cleanup_strv_free_ char **dropins = NULL;
_cleanup_free_ char *path = NULL;
const char *link_config; const char *link_config;
link_config = startswith(*name, "@"); link_config = startswith(*name, "@");
if (link_config) { 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) if (r < 0)
return RET_GATHER(ret, r); return r;
} else { continue;
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);
}
} }
_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) if (!first)
putchar('\n'); putchar('\n');
r = cat_files(path, dropins, /* flags = */ CAT_FORMAT_HAS_SECTIONS); r = cat_files(path, dropins, /* flags = */ CAT_FORMAT_HAS_SECTIONS);
if (r < 0) if (r < 0)
return RET_GATHER(ret, r); return r;
first = false; first = false;
} }

View File

@ -99,10 +99,18 @@ cmp "/usr/lib/systemd/network/$LINK_NAME" "/etc/systemd/network/$LINK_NAME"
systemctl unmask systemd-networkd systemctl unmask systemd-networkd
systemctl stop systemd-networkd systemctl stop systemd-networkd
(! networkctl cat @test2) (! networkctl cat @test2)
(! networkctl cat @test2:netdev)
systemctl start systemd-networkd systemctl start systemd-networkd
SYSTEMD_LOG_LEVEL=debug /usr/lib/systemd/systemd-networkd-wait-online -i test2:carrier --timeout 20 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: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 EDITOR='cp' script -ec 'networkctl edit @test2 --drop-in test2.conf' /dev/null
cmp "+4" "/etc/systemd/network/${NETWORK_NAME}.d/test2.conf" 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)" ip_link="$(ip link show test2)"
if systemctl --quiet is-active systemd-udevd; then 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" assert_in 'alias test_alias' "$ip_link"
fi fi