mirror of
https://github.com/systemd/systemd.git
synced 2026-08-05 07:30:30 +00:00
Merge pull request #27157 from YHNdnzj/networkctl-edit
networkctl: add verb edit and cat to operate on network configs
This commit is contained in:
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 },
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
86
test/units/testsuite-74.networkctl.sh
Executable file
86
test/units/testsuite-74.networkctl.sh
Executable file
@@ -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" <<EOF
|
||||
[Match]
|
||||
Name=test
|
||||
EOF
|
||||
|
||||
# Test files
|
||||
networkctl cat "$NETWORK_NAME" | tail -n +2 | cmp - "/usr/lib/systemd/network/$NETWORK_NAME"
|
||||
|
||||
cat >new <<EOF
|
||||
[Match]
|
||||
Name=test2
|
||||
EOF
|
||||
|
||||
EDITOR='mv new' script -ec 'networkctl edit "$NETWORK_NAME"' /dev/null
|
||||
printf '%s\n' '[Match]' 'Name=test2' | cmp - "/etc/systemd/network/$NETWORK_NAME"
|
||||
|
||||
cat >"+4" <<EOF
|
||||
[Network]
|
||||
DHCP=yes
|
||||
EOF
|
||||
|
||||
EDITOR='cp' script -ec 'networkctl edit "$NETWORK_NAME" --drop-in test' /dev/null
|
||||
cmp "+4" "/etc/systemd/network/${NETWORK_NAME}.d/test.conf"
|
||||
|
||||
networkctl cat "$NETWORK_NAME" | grep '^# ' |
|
||||
cmp - <(printf '%s\n' "# /etc/systemd/network/$NETWORK_NAME" "# /etc/systemd/network/${NETWORK_NAME}.d/test.conf")
|
||||
|
||||
cat >"/usr/lib/systemd/network/$NETDEV_NAME" <<EOF
|
||||
[NetDev]
|
||||
Name=test2
|
||||
Kind=dummy
|
||||
EOF
|
||||
|
||||
networkctl cat "$NETDEV_NAME"
|
||||
|
||||
cat >"/usr/lib/systemd/network/$LINK_NAME" <<EOF
|
||||
[Match]
|
||||
OriginalName=test2
|
||||
|
||||
[Link]
|
||||
Alias=test_alias
|
||||
EOF
|
||||
|
||||
SYSTEMD_LOG_LEVEL=debug EDITOR='true' script -ec 'networkctl edit "$LINK_NAME"' /dev/null
|
||||
cmp "/usr/lib/systemd/network/$LINK_NAME" "/etc/systemd/network/$LINK_NAME"
|
||||
|
||||
# Test links
|
||||
systemctl unmask systemd-networkd
|
||||
systemctl stop systemd-networkd
|
||||
(! networkctl cat @test2)
|
||||
|
||||
systemctl start systemd-networkd
|
||||
sleep 1
|
||||
networkctl cat @test2:network | cmp - <(networkctl cat "$NETWORK_NAME")
|
||||
|
||||
EDITOR='cp' script -ec 'networkctl edit @test2 --drop-in test2.conf' /dev/null
|
||||
cmp "+4" "/etc/systemd/network/${NETWORK_NAME}.d/test2.conf"
|
||||
|
||||
ip_link="$(ip link show test2)"
|
||||
if systemctl --quiet is-active systemd-udevd; then
|
||||
assert_in 'alias test_alias' "$ip_link"
|
||||
fi
|
||||
Reference in New Issue
Block a user