diff --git a/man/networkctl.xml b/man/networkctl.xml
index 68b1d97f09c..3a2dc09eccb 100644
--- a/man/networkctl.xml
+++ b/man/networkctl.xml
@@ -430,7 +430,8 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR)
Edit network configuration files, which include .network,
.netdev, and .link files. If no network config file
- matching the given name is found, a new one will be created under /etc/.
+ matching the given name is found, a new one will be created under /etc/ or
+ /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)
@@ -541,6 +542,17 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR)
+
+
+
+
+
+ When used with edit, edit the file under /run/
+ instead of /etc/.
+
+
+
+
diff --git a/src/network/networkctl.c b/src/network/networkctl.c
index ec31e8eb444..939b590a6d0 100644
--- a/src/network/networkctl.c
+++ b/src/network/networkctl.c
@@ -87,6 +87,7 @@ static bool arg_no_reload = false;
static bool arg_all = false;
static bool arg_stats = false;
static bool arg_full = false;
+static bool arg_runtime = false;
static unsigned arg_lines = 10;
static char *arg_drop_in = NULL;
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
@@ -2741,23 +2742,25 @@ static int link_renew_one(sd_bus *bus, int index, const char *name) {
static int link_renew(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
- int index, k = 0, r;
+ int r;
r = acquire_bus(&bus);
if (r < 0)
return r;
+ r = 0;
+
for (int i = 1; i < argc; i++) {
+ int index;
+
index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
if (index < 0)
return index;
- r = link_renew_one(bus, index, argv[i]);
- if (r < 0 && k >= 0)
- k = r;
+ RET_GATHER(r, link_renew_one(bus, index, argv[i]));
}
- return k;
+ return r;
}
static int link_force_renew_one(sd_bus *bus, int index, const char *name) {
@@ -2907,7 +2910,6 @@ static int get_dropin_by_name(
char **ret) {
assert(name);
- assert(dropins);
assert(ret);
STRV_FOREACH(i, dropins)
@@ -3074,16 +3076,21 @@ static int add_config_to_edit(
assert(context);
assert(path);
- assert(!arg_drop_in || dropins);
- if (path_startswith(path, "/usr")) {
+ /* If we're supposed to edit main config file in /run/, but a config with the same name is present
+ * under /etc/, we bail out since the one in /etc/ always overrides that in /run/. */
+ if (arg_runtime && !arg_drop_in && path_startswith(path, "/etc"))
+ return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
+ "Cannot edit runtime config file: overriden by %s", path);
+
+ if (path_startswith(path, "/usr") || arg_runtime != !!path_startswith(path, "/run")) {
_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);
+ new_path = path_join(NETWORK_DIRS[arg_runtime ? 1 : 0], name);
if (!new_path)
return log_oom();
}
@@ -3091,16 +3098,27 @@ static int add_config_to_edit(
if (!arg_drop_in)
return edit_files_add(context, new_path ?: path, path, NULL);
+ bool need_new_dropin;
+
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) {
+ /* See the explanation above */
+ if (arg_runtime && path_startswith(old_dropin, "/etc"))
+ return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
+ "Cannot edit runtime config file: overriden by %s", old_dropin);
- if (r > 0 && !path_startswith(old_dropin, "/usr"))
- /* An existing drop-in is found and not in /usr/. Let's edit it directly. */
+ need_new_dropin = path_startswith(old_dropin, "/usr") || arg_runtime != !!path_startswith(old_dropin, "/run");
+ } else
+ need_new_dropin = true;
+
+ if (!need_new_dropin)
+ /* An existing drop-in is found in the correct scope. 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. */
+ /* No drop-in was found or an existing drop-in is in a different scope. Let's create a new
+ * drop-in file. */
dropin_path = strjoin(new_path ?: path, ".d/", arg_drop_in);
if (!dropin_path)
return log_oom();
@@ -3209,7 +3227,7 @@ static int verb_edit(int argc, char *argv[], void *userdata) {
log_debug("No existing network config '%s' found, creating a new file.", *name);
- path = path_join(NETWORK_DIRS[0], *name);
+ path = path_join(NETWORK_DIRS[arg_runtime ? 1 : 0], *name);
if (!path)
return log_oom();
@@ -3281,23 +3299,22 @@ static int verb_cat(int argc, char *argv[], void *userdata) {
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;
+ return RET_GATHER(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;
+ RET_GATHER(ret, log_error_errno(r, "Cannot find network config file '%s'.", *name));
continue;
}
if (r < 0) {
log_error_errno(r, "Failed to get the path of network config '%s': %m", *name);
- return ret < 0 ? ret : r;
+ return RET_GATHER(ret, r);
}
}
r = cat_files(path, dropins, /* flags = */ CAT_FORMAT_HAS_SECTIONS);
if (r < 0)
- return ret < 0 ? ret : r;
+ return RET_GATHER(ret, r);
}
return ret;
@@ -3341,6 +3358,7 @@ static int help(void) {
" --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"
+ " --runtime Edit runtime config files\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
@@ -3358,6 +3376,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_JSON,
ARG_NO_RELOAD,
ARG_DROP_IN,
+ ARG_RUNTIME,
};
static const struct option options[] = {
@@ -3372,6 +3391,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "json", required_argument, NULL, ARG_JSON },
{ "no-reload", no_argument, NULL, ARG_NO_RELOAD },
{ "drop-in", required_argument, NULL, ARG_DROP_IN },
+ { "runtime", no_argument, NULL, ARG_RUNTIME },
{}
};
@@ -3402,6 +3422,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_no_reload = true;
break;
+ case ARG_RUNTIME:
+ arg_runtime = true;
+ break;
+
case ARG_DROP_IN:
if (isempty(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty drop-in file name.");
diff --git a/test/units/testsuite-74.networkctl.sh b/test/units/testsuite-74.networkctl.sh
index 0a687af6e4d..b857abcf9a6 100755
--- a/test/units/testsuite-74.networkctl.sh
+++ b/test/units/testsuite-74.networkctl.sh
@@ -11,7 +11,7 @@ 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" \
+ rm -fvr {/usr/lib,/etc,/run}/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
@@ -35,9 +35,14 @@ cat >new <"+4" <