1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "sd-netlink.h"
8 #include "sd-network.h"
10 #include "bus-error.h"
11 #include "bus-locator.h"
13 #include "bus-wait-for-jobs.h"
14 #include "conf-files.h"
15 #include "edit-util.h"
16 #include "mkdir-label.h"
17 #include "netlink-util.h"
18 #include "networkctl.h"
19 #include "networkctl-config-file.h"
21 #include "path-lookup.h"
22 #include "path-util.h"
23 #include "pretty-print.h"
24 #include "selinux-util.h"
28 typedef enum ReloadFlags
{
29 RELOAD_NETWORKD
= 1 << 0,
30 RELOAD_UDEVD
= 1 << 1,
33 static int get_config_files_by_name(
37 char ***ret_dropins
) {
39 _cleanup_free_
char *path
= NULL
;
45 STRV_FOREACH(i
, NETWORK_DIRS
) {
46 _cleanup_free_
char *p
= NULL
;
48 p
= path_join(*i
, name
);
52 r
= RET_NERRNO(access(p
, F_OK
));
55 r
= null_or_empty_path(p
);
57 return log_debug_errno(r
,
58 "Failed to check if network config '%s' is masked: %m",
69 log_debug_errno(r
, "Failed to determine whether '%s' exists, ignoring: %m", p
);
76 _cleanup_free_
char *dropin_dirname
= NULL
;
78 dropin_dirname
= strjoin(name
, ".d");
82 r
= conf_files_list_dropins(ret_dropins
, dropin_dirname
, /* root = */ NULL
, NETWORK_DIRS
);
87 *ret_path
= TAKE_PTR(path
);
92 static int get_dropin_by_name(
94 char * const *dropins
,
100 STRV_FOREACH(i
, dropins
)
101 if (path_equal_filename(*i
, name
)) {
102 _cleanup_free_
char *d
= NULL
;
116 static int get_network_files_by_link(
120 char ***ret_dropins
) {
122 _cleanup_strv_free_
char **dropins
= NULL
;
123 _cleanup_free_
char *path
= NULL
;
131 ifindex
= rtnl_resolve_interface_or_warn(rtnl
, link
);
135 r
= sd_network_link_get_network_file(ifindex
, &path
);
137 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
),
138 "Link '%s' has no associated network file.", link
);
140 return log_error_errno(r
, "Failed to get network file for link '%s': %m", link
);
142 r
= sd_network_link_get_network_file_dropins(ifindex
, &dropins
);
143 if (r
< 0 && r
!= -ENODATA
)
144 return log_error_errno(r
, "Failed to get network drop-ins for link '%s': %m", link
);
146 *ret_path
= TAKE_PTR(path
);
147 *ret_dropins
= TAKE_PTR(dropins
);
152 static int get_link_files_by_link(const char *link
, char **ret_path
, char ***ret_dropins
) {
153 _cleanup_(sd_device_unrefp
) sd_device
*device
= NULL
;
154 _cleanup_strv_free_
char **dropins_split
= NULL
;
155 _cleanup_free_
char *p
= NULL
;
156 const char *path
, *dropins
;
163 r
= sd_device_new_from_ifname(&device
, link
);
165 return log_error_errno(r
, "Failed to create sd-device object for link '%s': %m", link
);
167 r
= sd_device_get_property_value(device
, "ID_NET_LINK_FILE", &path
);
169 return log_error_errno(r
, "Link '%s' has no associated link file.", link
);
171 return log_error_errno(r
, "Failed to get link file for link '%s': %m", link
);
173 r
= sd_device_get_property_value(device
, "ID_NET_LINK_FILE_DROPINS", &dropins
);
174 if (r
< 0 && r
!= -ENOENT
)
175 return log_error_errno(r
, "Failed to get link drop-ins for link '%s': %m", link
);
177 r
= strv_split_full(&dropins_split
, dropins
, ":", EXTRACT_CUNESCAPE
);
179 return log_error_errno(r
, "Failed to parse link drop-ins for link '%s': %m", link
);
186 *ret_path
= TAKE_PTR(p
);
187 *ret_dropins
= TAKE_PTR(dropins_split
);
192 static int get_config_files_by_link_config(
193 const char *link_config
,
197 ReloadFlags
*ret_reload
) {
199 _cleanup_strv_free_
char **dropins
= NULL
, **link_config_split
= NULL
;
200 _cleanup_free_
char *path
= NULL
;
201 const char *ifname
, *type
;
211 link_config_split
= strv_split(link_config
, ":");
212 if (!link_config_split
)
215 n
= strv_length(link_config_split
);
216 if (n
== 0 || isempty(link_config_split
[0]))
217 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "No link name is given.");
219 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid link config '%s'.", link_config
);
221 ifname
= link_config_split
[0];
222 type
= n
== 2 ? link_config_split
[1] : "network";
224 if (streq(type
, "network")) {
225 if (!networkd_is_running())
226 return log_error_errno(SYNTHETIC_ERRNO(ESRCH
),
227 "Cannot get network file for link if systemd-networkd is not running.");
229 r
= get_network_files_by_link(rtnl
, ifname
, &path
, &dropins
);
233 reload
= RELOAD_NETWORKD
;
234 } else if (streq(type
, "link")) {
235 r
= get_link_files_by_link(ifname
, &path
, &dropins
);
239 reload
= RELOAD_UDEVD
;
241 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
242 "Invalid config type '%s' for link '%s'.", type
, ifname
);
244 *ret_path
= TAKE_PTR(path
);
245 *ret_dropins
= TAKE_PTR(dropins
);
248 *ret_reload
= reload
;
253 static int add_config_to_edit(
254 EditFileContext
*context
,
256 char * const *dropins
) {
258 _cleanup_free_
char *new_path
= NULL
, *dropin_path
= NULL
, *old_dropin
= NULL
;
259 _cleanup_strv_free_
char **comment_paths
= NULL
;
265 /* If we're supposed to edit main config file in /run/, but a config with the same name is present
266 * under /etc/, we bail out since the one in /etc/ always overrides that in /run/. */
267 if (arg_runtime
&& !arg_drop_in
&& path_startswith(path
, "/etc"))
268 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
269 "Cannot edit runtime config file: overridden by %s", path
);
271 if (path_startswith(path
, "/usr") || arg_runtime
!= !!path_startswith(path
, "/run")) {
272 _cleanup_free_
char *name
= NULL
;
274 r
= path_extract_filename(path
, &name
);
276 return log_error_errno(r
, "Failed to extract filename from '%s': %m", path
);
278 new_path
= path_join(NETWORK_DIRS
[arg_runtime
? 1 : 0], name
);
284 return edit_files_add(context
, new_path
?: path
, path
, NULL
);
286 bool need_new_dropin
;
288 r
= get_dropin_by_name(arg_drop_in
, dropins
, &old_dropin
);
290 return log_error_errno(r
, "Failed to acquire drop-in '%s': %m", arg_drop_in
);
292 /* See the explanation above */
293 if (arg_runtime
&& path_startswith(old_dropin
, "/etc"))
294 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
295 "Cannot edit runtime config file: overridden by %s", old_dropin
);
297 need_new_dropin
= path_startswith(old_dropin
, "/usr") || arg_runtime
!= !!path_startswith(old_dropin
, "/run");
299 need_new_dropin
= true;
301 if (!need_new_dropin
)
302 /* An existing drop-in is found in the correct scope. Let's edit it directly. */
303 dropin_path
= TAKE_PTR(old_dropin
);
305 /* No drop-in was found or an existing drop-in is in a different scope. Let's create a new
307 dropin_path
= strjoin(new_path
?: path
, ".d/", arg_drop_in
);
312 comment_paths
= strv_new(path
);
316 r
= strv_extend_strv(&comment_paths
, dropins
, /* filter_duplicates = */ false);
320 return edit_files_add(context
, dropin_path
, old_dropin
, comment_paths
);
323 static int udevd_reload(sd_bus
*bus
) {
324 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
325 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
326 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*w
= NULL
;
327 const char *job_path
;
332 r
= bus_wait_for_jobs_new(bus
, &w
);
334 return log_error_errno(r
, "Could not watch jobs: %m");
336 r
= bus_call_method(bus
,
342 "systemd-udevd.service",
345 return log_error_errno(r
, "Failed to reload systemd-udevd: %s", bus_error_message(&error
, r
));
347 r
= sd_bus_message_read(reply
, "o", &job_path
);
349 return bus_log_parse_error(r
);
351 r
= bus_wait_for_jobs_one(w
, job_path
, /* flags = */ 0, NULL
);
353 log_debug("systemd-udevd is not running, skipping reload.");
357 return log_error_errno(r
, "Failed to reload systemd-udevd: %m");
362 static int reload_daemons(ReloadFlags flags
) {
363 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
372 if (!sd_booted() || running_in_chroot() > 0) {
373 log_debug("System is not booted with systemd or is running in chroot, skipping reload.");
377 r
= sd_bus_open_system(&bus
);
379 return log_error_errno(r
, "Failed to connect to system bus: %m");
381 if (FLAGS_SET(flags
, RELOAD_UDEVD
))
382 RET_GATHER(ret
, udevd_reload(bus
));
384 if (FLAGS_SET(flags
, RELOAD_NETWORKD
)) {
385 if (networkd_is_running()) {
386 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
388 r
= bus_call_method(bus
, bus_network_mgr
, "Reload", &error
, NULL
, NULL
);
390 RET_GATHER(ret
, log_error_errno(r
, "Failed to reload systemd-networkd: %s", bus_error_message(&error
, r
)));
392 log_debug("systemd-networkd is not running, skipping reload.");
398 int verb_edit(int argc
, char *argv
[], void *userdata
) {
399 _cleanup_(edit_file_context_done
) EditFileContext context
= {
400 .marker_start
= DROPIN_MARKER_START
,
401 .marker_end
= DROPIN_MARKER_END
,
402 .remove_parent
= !!arg_drop_in
,
404 _cleanup_(sd_netlink_unrefp
) sd_netlink
*rtnl
= NULL
;
405 ReloadFlags reload
= 0;
409 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Cannot edit network config files if not on a tty.");
411 r
= mac_selinux_init();
415 STRV_FOREACH(name
, strv_skip(argv
, 1)) {
416 _cleanup_strv_free_
char **dropins
= NULL
;
417 _cleanup_free_
char *path
= NULL
;
418 const char *link_config
;
420 link_config
= startswith(*name
, "@");
424 r
= get_config_files_by_link_config(link_config
, &rtnl
, &path
, &dropins
, &flags
);
430 r
= add_config_to_edit(&context
, path
, dropins
);
437 if (ENDSWITH_SET(*name
, ".network", ".netdev"))
438 reload
|= RELOAD_NETWORKD
;
439 else if (endswith(*name
, ".link"))
440 reload
|= RELOAD_UDEVD
;
442 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid network config name '%s'.", *name
);
444 r
= get_config_files_by_name(*name
, /* allow_masked = */ false, &path
, &dropins
);
446 return log_error_errno(r
, "Network config '%s' is masked.", *name
);
449 return log_error_errno(r
, "Cannot find network config '%s'.", *name
);
451 log_debug("No existing network config '%s' found, creating a new file.", *name
);
453 path
= path_join(NETWORK_DIRS
[arg_runtime
? 1 : 0], *name
);
457 r
= edit_files_add(&context
, path
, NULL
, NULL
);
463 return log_error_errno(r
, "Failed to get the path of network config '%s': %m", *name
);
465 r
= add_config_to_edit(&context
, path
, dropins
);
470 r
= do_edit_files_and_install(&context
);
474 return reload_daemons(reload
);
477 int verb_cat(int argc
, char *argv
[], void *userdata
) {
478 _cleanup_(sd_netlink_unrefp
) sd_netlink
*rtnl
= NULL
;
481 pager_open(arg_pager_flags
);
484 STRV_FOREACH(name
, strv_skip(argv
, 1)) {
485 _cleanup_strv_free_
char **dropins
= NULL
;
486 _cleanup_free_
char *path
= NULL
;
487 const char *link_config
;
489 link_config
= startswith(*name
, "@");
491 r
= get_config_files_by_link_config(link_config
, &rtnl
, &path
, &dropins
, /* ret_reload = */ NULL
);
493 return RET_GATHER(ret
, r
);
495 r
= get_config_files_by_name(*name
, /* allow_masked = */ false, &path
, &dropins
);
497 RET_GATHER(ret
, log_error_errno(r
, "Cannot find network config file '%s'.", *name
));
501 RET_GATHER(ret
, log_debug_errno(r
, "Network config '%s' is masked, ignoring.", *name
));
505 log_error_errno(r
, "Failed to get the path of network config '%s': %m", *name
);
506 return RET_GATHER(ret
, r
);
513 r
= cat_files(path
, dropins
, /* flags = */ CAT_FORMAT_HAS_SECTIONS
);
515 return RET_GATHER(ret
, r
);
523 int verb_mask(int argc
, char *argv
[], void *userdata
) {
524 ReloadFlags flags
= 0;
527 r
= mac_selinux_init();
531 STRV_FOREACH(name
, strv_skip(argv
, 1)) {
532 _cleanup_free_
char *config_path
= NULL
, *symlink_path
= NULL
;
535 /* We update the real 'flags' at last, since the operation can be skipped. */
536 if (ENDSWITH_SET(*name
, ".network", ".netdev"))
537 reload
= RELOAD_NETWORKD
;
538 else if (endswith(*name
, ".link"))
539 reload
= RELOAD_UDEVD
;
541 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid network config name '%s'.", *name
);
543 r
= get_config_files_by_name(*name
, /* allow_masked = */ true, &config_path
, /* ret_dropins = */ NULL
);
545 log_warning("No existing network config '%s' found, proceeding anyway.", *name
);
547 return log_error_errno(r
, "Failed to get the path of network config '%s': %m", *name
);
548 else if (!path_startswith(config_path
, "/usr")) {
549 r
= null_or_empty_path(config_path
);
551 return log_error_errno(r
,
552 "Failed to check if '%s' is masked: %m", config_path
);
554 log_debug("%s is already masked, skipping.", config_path
);
558 /* At this point, we have found a config under mutable dir (/run/ or /etc/),
559 * so masking through /run/ (--runtime) is not possible. If it's under /etc/,
560 * then it doesn't work without --runtime either. */
561 if (arg_runtime
|| path_startswith(config_path
, "/etc"))
562 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
563 "Cannot mask network config %s: %s exists",
567 symlink_path
= path_join(NETWORK_DIRS
[arg_runtime
? 1 : 0], *name
);
571 (void) mkdir_parents_label(symlink_path
, 0755);
573 if (symlink("/dev/null", symlink_path
) < 0)
574 return log_error_errno(errno
,
575 "Failed to create symlink '%s' to /dev/null: %m", symlink_path
);
578 log_info("Successfully created symlink '%s' to /dev/null.", symlink_path
);
581 return reload_daemons(flags
);
584 int verb_unmask(int argc
, char *argv
[], void *userdata
) {
585 ReloadFlags flags
= 0;
588 STRV_FOREACH(name
, strv_skip(argv
, 1)) {
589 _cleanup_free_
char *path
= NULL
;
592 if (ENDSWITH_SET(*name
, ".network", ".netdev"))
593 reload
= RELOAD_NETWORKD
;
594 else if (endswith(*name
, ".link"))
595 reload
= RELOAD_UDEVD
;
597 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid network config name '%s'.", *name
);
599 r
= get_config_files_by_name(*name
, /* allow_masked = */ true, &path
, /* ret_dropins = */ NULL
);
601 log_debug_errno(r
, "Network configuration '%s' doesn't exist, skipping.", *name
);
605 return log_error_errno(r
, "Failed to get the path of network config '%s': %m", *name
);
607 r
= null_or_empty_path(path
);
609 return log_error_errno(r
, "Failed to check if '%s' is masked: %m", path
);
613 if (path_startswith(path
, "/usr"))
614 return log_error_errno(r
, "Cannot unmask network config under /usr/: %s", path
);
616 if (unlink(path
) < 0) {
620 return log_error_errno(errno
, "Failed to remove '%s': %m", path
);
624 log_info("Successfully removed masked network config '%s'.", path
);
627 return reload_daemons(flags
);