diff options
author | Yu Watanabe <watanabe.yu+github@gmail.com> | 2023-12-20 13:00:36 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-20 13:00:36 +0100 |
commit | 8f876e8d98821ea7afbb0605ac6705874337099a (patch) | |
tree | 682c7f0421db0d169ecc8a54504ea842d55a020b | |
parent | Merge pull request #30534 from yuwata/man-page-update-and-fix-typo (diff) | |
parent | networkctl: introduce verb mask and unmask (diff) | |
download | systemd-8f876e8d98821ea7afbb0605ac6705874337099a.tar.xz systemd-8f876e8d98821ea7afbb0605ac6705874337099a.zip |
Merge pull request #30525 from YHNdnzj/networkctl-mask
networkctl: introduce verb mask and unmask
-rw-r--r-- | man/networkctl.xml | 42 | ||||
-rw-r--r-- | src/network/meson.build | 5 | ||||
-rw-r--r-- | src/network/networkctl-config-file.c | 628 | ||||
-rw-r--r-- | src/network/networkctl-config-file.h | 8 | ||||
-rw-r--r-- | src/network/networkctl.c | 499 | ||||
-rw-r--r-- | src/network/networkctl.h | 23 | ||||
-rw-r--r-- | src/shared/install.c | 17 | ||||
-rwxr-xr-x | test/units/testsuite-74.networkctl.sh | 19 |
8 files changed, 744 insertions, 497 deletions
diff --git a/man/networkctl.xml b/man/networkctl.xml index 3a2dc09ecc..1a03e9e11d 100644 --- a/man/networkctl.xml +++ b/man/networkctl.xml @@ -461,6 +461,40 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR) <xi:include href="version-info.xml" xpointer="v254"/></listitem> </varlistentry> + + <varlistentry> + <term> + <command>mask</command> + <replaceable>FILE</replaceable>… + </term> + <listitem><para>Mask network configuration files, which include <filename>.network</filename>, + <filename>.netdev</filename>, and <filename>.link</filename> files. A symlink of the given name will + be created under <filename>/etc/</filename> or <filename>/run/</filename>, depending on + whether <option>--runtime</option> is specified, that points to <filename>/dev/null</filename>. + If a non-empty config file with the specified name exists under the target directory or a directory + with higher priority (e.g. <option>--runtime</option> is used while an existing config resides + in <filename>/etc/</filename>), the operation is aborted.</para> + + <para>This command honors <option>--no-reload</option> in the same way as <command>edit</command>. + </para> + + <xi:include href="version-info.xml" xpointer="v256"/></listitem> + </varlistentry> + + <varlistentry> + <term> + <command>unmask</command> + <replaceable>FILE</replaceable>… + </term> + <listitem><para>Unmask network configuration files, i.e. reverting the effect of <command>mask</command>. + Note that this command operates regardless of the scope of the directory, i.e. <option>--runtime</option> + is of no effect.</para> + + <para>This command honors <option>--no-reload</option> in the same way as <command>edit</command> + and <command>mask</command>.</para> + + <xi:include href="version-info.xml" xpointer="v256"/></listitem> + </varlistentry> </variablelist> </refsect1> @@ -534,11 +568,11 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR) <term><option>--no-reload</option></term> <listitem> - <para>When used with <command>edit</command>, + <para>When used with <command>edit</command>, <command>mask</command>, or <command>unmask</command>, <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> or <citerefentry><refentrytitle>systemd-udevd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> - will not be reloaded after the editing finishes.</para> + will not be reloaded after the operation finishes.</para> <xi:include href="version-info.xml" xpointer="v254"/> </listitem> @@ -547,8 +581,8 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR) <term><option>--runtime</option></term> <listitem> - <para>When used with <command>edit</command>, edit the file under <filename>/run/</filename> - instead of <filename>/etc/</filename>.</para> + <para>When used with <command>edit</command> or <command>mask</command>, + operate on the file under <filename>/run/</filename> instead of <filename>/etc/</filename>.</para> <xi:include href="version-info.xml" xpointer="v256"/> </listitem> diff --git a/src/network/meson.build b/src/network/meson.build index 5c05eba095..3d692abf44 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -109,7 +109,10 @@ systemd_networkd_wait_online_sources = files( 'wait-online/wait-online.c', ) -networkctl_sources = files('networkctl.c') +networkctl_sources = files( + 'networkctl.c', + 'networkctl-config-file.c' +) network_generator_sources = files( 'generator/main.c', diff --git a/src/network/networkctl-config-file.c b/src/network/networkctl-config-file.c new file mode 100644 index 0000000000..670f1c2fd7 --- /dev/null +++ b/src/network/networkctl-config-file.c @@ -0,0 +1,628 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "sd-daemon.h" +#include "sd-device.h" +#include "sd-netlink.h" +#include "sd-network.h" + +#include "bus-error.h" +#include "bus-locator.h" +#include "bus-util.h" +#include "bus-wait-for-jobs.h" +#include "conf-files.h" +#include "edit-util.h" +#include "mkdir-label.h" +#include "netlink-util.h" +#include "networkctl.h" +#include "networkctl-config-file.h" +#include "pager.h" +#include "path-lookup.h" +#include "path-util.h" +#include "pretty-print.h" +#include "selinux-util.h" +#include "strv.h" +#include "virt.h" + +typedef enum ReloadFlags { + RELOAD_NETWORKD = 1 << 0, + RELOAD_UDEVD = 1 << 1, +} ReloadFlags; + +static int get_config_files_by_name( + const char *name, + bool allow_masked, + 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) { + if (!allow_masked) { + r = null_or_empty_path(p); + if (r < 0) + return log_debug_errno(r, + "Failed to check if network config '%s' is masked: %m", + name); + if (r > 0) + return -ERFKILL; + } + + 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(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); + + /* 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: overridden 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[arg_runtime ? 1 : 0], name); + if (!new_path) + return log_oom(); + } + + 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: overridden by %s", old_dropin); + + 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 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(); + } + + 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, /* flags = */ 0, 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 reload_daemons(ReloadFlags flags) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r, ret = 1; + + if (arg_no_reload) + return 0; + + if (flags == 0) + 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; + } + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to system bus: %m"); + + if (FLAGS_SET(flags, RELOAD_UDEVD)) + RET_GATHER(ret, udevd_reload(bus)); + + if (FLAGS_SET(flags, RELOAD_NETWORKD)) { + if (networkd_is_running()) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL); + if (r < 0) + RET_GATHER(ret, log_error_errno(r, "Failed to reload systemd-networkd: %s", bus_error_message(&error, r))); + } else + log_debug("systemd-networkd is not running, skipping reload."); + } + + return ret; +} + +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, /* allow_masked = */ false, &path, &dropins); + if (r == -ERFKILL) + return log_error_errno(r, "Network config '%s' is masked.", *name); + 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[arg_runtime ? 1 : 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; + + return reload_daemons(reload); +} + +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); + + bool first = true; + 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_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); + } + } + + if (!first) + putchar('\n'); + + r = cat_files(path, dropins, /* flags = */ CAT_FORMAT_HAS_SECTIONS); + if (r < 0) + return RET_GATHER(ret, r); + + first = false; + } + + return ret; +} + +int verb_mask(int argc, char *argv[], void *userdata) { + ReloadFlags flags = 0; + int r; + + r = mac_selinux_init(); + if (r < 0) + return r; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_free_ char *config_path = NULL, *symlink_path = NULL; + ReloadFlags reload; + + /* We update the real 'flags' at last, since the operation can be skipped. */ + 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, /* allow_masked = */ true, &config_path, /* ret_dropins = */ NULL); + if (r == -ENOENT) + log_warning("No existing network config '%s' found, proceeding anyway.", *name); + else if (r < 0) + return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + else if (!path_startswith(config_path, "/usr")) { + r = null_or_empty_path(config_path); + if (r < 0) + return log_error_errno(r, + "Failed to check if '%s' is masked: %m", config_path); + if (r > 0) { + log_debug("%s is already masked, skipping.", config_path); + continue; + } + + /* At this point, we have found a config under mutable dir (/run/ or /etc/), + * so masking through /run/ (--runtime) is not possible. If it's under /etc/, + * then it doesn't work without --runtime either. */ + if (arg_runtime || path_startswith(config_path, "/etc")) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Cannot mask network config %s: %s exists", + *name, config_path); + } + + symlink_path = path_join(NETWORK_DIRS[arg_runtime ? 1 : 0], *name); + if (!symlink_path) + return log_oom(); + + (void) mkdir_parents_label(symlink_path, 0755); + + if (symlink("/dev/null", symlink_path) < 0) + return log_error_errno(errno, + "Failed to create symlink '%s' to /dev/null: %m", symlink_path); + + flags |= reload; + log_info("Successfully created symlink '%s' to /dev/null.", symlink_path); + } + + return reload_daemons(flags); +} + +int verb_unmask(int argc, char *argv[], void *userdata) { + ReloadFlags flags = 0; + int r; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_free_ char *path = NULL; + ReloadFlags reload; + + 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, /* allow_masked = */ true, &path, /* ret_dropins = */ NULL); + if (r == -ENOENT) { + log_debug_errno(r, "Network configuration '%s' doesn't exist, skipping.", *name); + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + + r = null_or_empty_path(path); + if (r < 0) + return log_error_errno(r, "Failed to check if '%s' is masked: %m", path); + if (r == 0) + continue; + + if (path_startswith(path, "/usr")) + return log_error_errno(r, "Cannot unmask network config under /usr/: %s", path); + + if (unlink(path) < 0) { + if (errno == ENOENT) + continue; + + return log_error_errno(errno, "Failed to remove '%s': %m", path); + } + + flags |= reload; + log_info("Successfully removed masked network config '%s'.", path); + } + + return reload_daemons(flags); +} diff --git a/src/network/networkctl-config-file.h b/src/network/networkctl-config-file.h new file mode 100644 index 0000000000..38210a8093 --- /dev/null +++ b/src/network/networkctl-config-file.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int verb_edit(int argc, char *argv[], void *userdata); +int verb_cat(int argc, char *argv[], void *userdata); + +int verb_mask(int argc, char *argv[], void *userdata); +int verb_unmask(int argc, char *argv[], void *userdata); diff --git a/src/network/networkctl.c b/src/network/networkctl.c index f67755005c..cf9d17c8b2 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -26,10 +26,7 @@ #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" @@ -51,6 +48,8 @@ #include "netlink-util.h" #include "network-internal.h" #include "network-util.h" +#include "networkctl.h" +#include "networkctl-config-file.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -72,7 +71,6 @@ #include "udev-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. */ @@ -81,16 +79,16 @@ /* use 128 kB for receive socket kernel queue, we shouldn't need more here */ #define RCVBUF_SIZE (128*1024) -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 bool arg_runtime = false; -static unsigned arg_lines = 10; -static char *arg_drop_in = NULL; -static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; +PagerFlags arg_pager_flags = 0; +bool arg_legend = true; +bool arg_no_reload = false; +bool arg_all = false; +bool arg_stats = false; +bool arg_full = false; +bool arg_runtime = false; +unsigned arg_lines = 10; +char *arg_drop_in = NULL; +JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; STATIC_DESTRUCTOR_REGISTER(arg_drop_in, freep); @@ -122,7 +120,7 @@ static int check_netns_match(sd_bus *bus) { return 0; } -static bool networkd_is_running(void) { +bool networkd_is_running(void) { static int cached = -1; int r; @@ -141,7 +139,7 @@ static bool networkd_is_running(void) { return cached; } -static int acquire_bus(sd_bus **ret) { +int acquire_bus(sd_bus **ret) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -2855,471 +2853,6 @@ 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(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); - - /* 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: overridden 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[arg_runtime ? 1 : 0], name); - if (!new_path) - return log_oom(); - } - - 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: overridden by %s", old_dropin); - - 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 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(); - } - - 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, /* flags = */ 0, 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[arg_runtime ? 1 : 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_GATHER(ret, r); - } else { - r = get_config_files_by_name(*name, &path, &dropins); - if (r == -ENOENT) { - 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_GATHER(ret, r); - } - } - - r = cat_files(path, dropins, /* flags = */ CAT_FORMAT_HAS_SECTIONS); - if (r < 0) - return RET_GATHER(ret, r); - } - - return ret; -} - static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -3344,6 +2877,8 @@ static int help(void) { " reload Reload .network and .netdev files\n" " edit FILES|DEVICES... Edit network configuration files\n" " cat FILES|DEVICES... Show network configuration files\n" + " mask FILES... Mask network configuration files\n" + " unmask FILES... Unmask network configuration files\n" "\nOptions:\n" " -h --help Show this help\n" " --version Show package version\n" @@ -3500,6 +3035,8 @@ static int networkctl_main(int argc, char *argv[]) { { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, { "edit", 2, VERB_ANY, 0, verb_edit }, { "cat", 2, VERB_ANY, 0, verb_cat }, + { "mask", 2, VERB_ANY, 0, verb_mask }, + { "unmask", 2, VERB_ANY, 0, verb_unmask }, {} }; diff --git a/src/network/networkctl.h b/src/network/networkctl.h new file mode 100644 index 0000000000..46b44f7975 --- /dev/null +++ b/src/network/networkctl.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <stdbool.h> + +#include "sd-bus.h" + +#include "output-mode.h" +#include "pager.h" + +extern PagerFlags arg_pager_flags; +extern bool arg_legend; +extern bool arg_no_reload; +extern bool arg_all; +extern bool arg_stats; +extern bool arg_full; +extern bool arg_runtime; +extern unsigned arg_lines; +extern char *arg_drop_in; +extern JsonFormatFlags arg_json_format_flags; + +bool networkd_is_running(void); +int acquire_bus(sd_bus **ret); diff --git a/src/shared/install.c b/src/shared/install.c index 97707e50b4..b5f386b5a1 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -2271,13 +2271,13 @@ int unit_file_mask( if (!config_path) return -ENXIO; + r = 0; + STRV_FOREACH(name, names) { _cleanup_free_ char *path = NULL; - int q; if (!unit_name_is_valid(*name, UNIT_NAME_ANY)) { - if (r == 0) - r = -EINVAL; + RET_GATHER(r, -EINVAL); continue; } @@ -2285,9 +2285,7 @@ int unit_file_mask( if (!path) return -ENOMEM; - q = create_symlink(&lp, "/dev/null", path, flags & UNIT_FILE_FORCE, changes, n_changes); - if (q < 0 && r >= 0) - r = q; + RET_GATHER(r, create_symlink(&lp, "/dev/null", path, flags & UNIT_FILE_FORCE, changes, n_changes)); } return r; @@ -2383,8 +2381,7 @@ int unit_file_unmask( if (!dry_run && unlink(path) < 0) { if (errno != ENOENT) { - if (r >= 0) - r = -errno; + RET_GATHER(r, -errno); install_changes_add(changes, n_changes, -errno, path, NULL); } @@ -2401,9 +2398,7 @@ int unit_file_unmask( return q; } - q = remove_marked_symlinks(remove_symlinks_to, config_path, &lp, dry_run, changes, n_changes); - if (r >= 0) - r = q; + RET_GATHER(r, remove_marked_symlinks(remove_symlinks_to, config_path, &lp, dry_run, changes, n_changes)); return r; } diff --git a/test/units/testsuite-74.networkctl.sh b/test/units/testsuite-74.networkctl.sh index b857abcf9a..06a3c39e77 100755 --- a/test/units/testsuite-74.networkctl.sh +++ b/test/units/testsuite-74.networkctl.sh @@ -28,6 +28,16 @@ Name=test EOF # Test files + +networkctl mask --runtime "donotexist.network" +assert_eq "$(readlink /run/systemd/network/donotexist.network)" "/dev/null" +networkctl unmask "donotexist.network" # unmask should work even without --runtime +[[ ! -e /run/systemd/network/donotexist.network ]] + +touch /usr/lib/systemd/network/donotexist.network +(! networkctl unmask "donotexist.network") +rm /usr/lib/systemd/network/donotexist.network + networkctl cat "$NETWORK_NAME" | tail -n +2 | cmp - "/usr/lib/systemd/network/$NETWORK_NAME" cat >new <<EOF @@ -36,11 +46,20 @@ Name=test2 EOF EDITOR='mv new' script -ec 'networkctl edit --runtime "$NETWORK_NAME"' /dev/null +(! networkctl mask --runtime "$NETWORK_NAME") printf '%s\n' '[Match]' 'Name=test2' | cmp - "/run/systemd/network/$NETWORK_NAME" +networkctl mask "$NETWORK_NAME" +assert_eq "$(readlink "/etc/systemd/network/$NETWORK_NAME")" "/dev/null" +(! networkctl edit "$NETWORK_NAME") +(! networkctl edit --runtime "$NETWORK_NAME") +(! networkctl cat "$NETWORK_NAME") +networkctl unmask "$NETWORK_NAME" + EDITOR='true' script -ec 'networkctl edit "$NETWORK_NAME"' /dev/null printf '%s\n' '[Match]' 'Name=test2' | cmp - "/etc/systemd/network/$NETWORK_NAME" +(! networkctl mask "$NETWORK_NAME") (! EDITOR='true' script -ec 'networkctl edit --runtime "$NETWORK_NAME"' /dev/null) cat >"+4" <<EOF |