1
0
mirror of https://github.com/systemd/systemd.git synced 2024-10-27 01:55:22 +03:00

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
This commit is contained in:
Mike Yuan 2023-04-01 19:44:29 +08:00
parent 35c0e3444d
commit 96bab8fd63
No known key found for this signature in database
GPG Key ID: 417471C0A40F58B3
2 changed files with 560 additions and 1 deletions

View File

@ -355,6 +355,38 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR)
which match the file are reconfigured.</para></listitem>
</varlistentry>
<varlistentry>
<term>
<command>edit</command>
<replaceable>FILE</replaceable>|<replaceable>@DEVICE</replaceable>
</term>
<listitem><para>Edit network configuration files, which include <filename>.network</filename>,
<filename>.netdev</filename>, and <filename>.link</filename> files. If no network config file
matching the given name is found, a new one will be created under <filename>/etc/</filename>.
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
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>
<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,
<command>systemd-networkd</command> will be reloaded after the edit of the
<filename>.network</filename> or <filename>.netdev</filename> files finishes.
The same applies for <filename>.link</filename> files and <command>systemd-udevd</command>.
Note that the changed link settings are not automatically applied after reloading.
To achieve that, trigger uevents for the corresponding interface. Refer to
<citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more information.</para></listitem>
</varlistentry>
<varlistentry>
<term>
<command>cat</command>
<replaceable>FILE</replaceable>|<replaceable>@DEVICE</replaceable>
</term>
<listitem><para>Show network configuration files. This command honors
the <literal>@</literal> prefix in the same way as <command>edit</command>.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -405,6 +437,25 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR)
</listitem>
</varlistentry>
<varlistentry>
<term><option>--drop-in=</option></term>
<replaceable>NAME</replaceable>
<listitem>
<para>When used with <command>edit</command>, edit the drop-in file <replaceable>NAME</replaceable>
instead of the main configuration file.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--no-reload</option></term>
<listitem>
<para>When used with <command>edit</command>, <command>systemd-networkd</command>
or <command>systemd-udevd</command> will not be reloaded after the editing finishes.</para>
</listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="json" />
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />

View File

@ -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 },
{}
};