]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkctl-config-file.c
network: make Reload bus method synchronous
[thirdparty/systemd.git] / src / network / networkctl-config-file.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <unistd.h>
4
5 #include "sd-daemon.h"
6 #include "sd-device.h"
7 #include "sd-netlink.h"
8 #include "sd-network.h"
9
10 #include "bus-error.h"
11 #include "bus-locator.h"
12 #include "bus-util.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"
20 #include "pager.h"
21 #include "path-lookup.h"
22 #include "path-util.h"
23 #include "pretty-print.h"
24 #include "selinux-util.h"
25 #include "strv.h"
26 #include "virt.h"
27
28 typedef enum ReloadFlags {
29 RELOAD_NETWORKD = 1 << 0,
30 RELOAD_UDEVD = 1 << 1,
31 } ReloadFlags;
32
33 static int get_config_files_by_name(
34 const char *name,
35 bool allow_masked,
36 char **ret_path,
37 char ***ret_dropins) {
38
39 _cleanup_free_ char *path = NULL;
40 int r;
41
42 assert(name);
43 assert(ret_path);
44
45 STRV_FOREACH(i, NETWORK_DIRS) {
46 _cleanup_free_ char *p = NULL;
47
48 p = path_join(*i, name);
49 if (!p)
50 return -ENOMEM;
51
52 r = RET_NERRNO(access(p, F_OK));
53 if (r >= 0) {
54 if (!allow_masked) {
55 r = null_or_empty_path(p);
56 if (r < 0)
57 return log_debug_errno(r,
58 "Failed to check if network config '%s' is masked: %m",
59 name);
60 if (r > 0)
61 return -ERFKILL;
62 }
63
64 path = TAKE_PTR(p);
65 break;
66 }
67
68 if (r != -ENOENT)
69 log_debug_errno(r, "Failed to determine whether '%s' exists, ignoring: %m", p);
70 }
71
72 if (!path)
73 return -ENOENT;
74
75 if (ret_dropins) {
76 _cleanup_free_ char *dropin_dirname = NULL;
77
78 dropin_dirname = strjoin(name, ".d");
79 if (!dropin_dirname)
80 return -ENOMEM;
81
82 r = conf_files_list_dropins(ret_dropins, dropin_dirname, /* root = */ NULL, NETWORK_DIRS);
83 if (r < 0)
84 return r;
85 }
86
87 *ret_path = TAKE_PTR(path);
88
89 return 0;
90 }
91
92 static int get_dropin_by_name(
93 const char *name,
94 char * const *dropins,
95 char **ret) {
96
97 assert(name);
98 assert(ret);
99
100 STRV_FOREACH(i, dropins)
101 if (path_equal_filename(*i, name)) {
102 _cleanup_free_ char *d = NULL;
103
104 d = strdup(*i);
105 if (!d)
106 return -ENOMEM;
107
108 *ret = TAKE_PTR(d);
109 return 1;
110 }
111
112 *ret = NULL;
113 return 0;
114 }
115
116 static int get_network_files_by_link(
117 sd_netlink **rtnl,
118 const char *link,
119 char **ret_path,
120 char ***ret_dropins) {
121
122 _cleanup_strv_free_ char **dropins = NULL;
123 _cleanup_free_ char *path = NULL;
124 int r, ifindex;
125
126 assert(rtnl);
127 assert(link);
128 assert(ret_path);
129 assert(ret_dropins);
130
131 ifindex = rtnl_resolve_interface_or_warn(rtnl, link);
132 if (ifindex < 0)
133 return ifindex;
134
135 r = sd_network_link_get_network_file(ifindex, &path);
136 if (r == -ENODATA)
137 return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
138 "Link '%s' has no associated network file.", link);
139 if (r < 0)
140 return log_error_errno(r, "Failed to get network file for link '%s': %m", link);
141
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);
145
146 *ret_path = TAKE_PTR(path);
147 *ret_dropins = TAKE_PTR(dropins);
148
149 return 0;
150 }
151
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;
157 int r;
158
159 assert(link);
160 assert(ret_path);
161 assert(ret_dropins);
162
163 r = sd_device_new_from_ifname(&device, link);
164 if (r < 0)
165 return log_error_errno(r, "Failed to create sd-device object for link '%s': %m", link);
166
167 r = sd_device_get_property_value(device, "ID_NET_LINK_FILE", &path);
168 if (r == -ENOENT)
169 return log_error_errno(r, "Link '%s' has no associated link file.", link);
170 if (r < 0)
171 return log_error_errno(r, "Failed to get link file for link '%s': %m", link);
172
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);
176 if (r >= 0) {
177 r = strv_split_full(&dropins_split, dropins, ":", EXTRACT_CUNESCAPE);
178 if (r < 0)
179 return log_error_errno(r, "Failed to parse link drop-ins for link '%s': %m", link);
180 }
181
182 p = strdup(path);
183 if (!p)
184 return log_oom();
185
186 *ret_path = TAKE_PTR(p);
187 *ret_dropins = TAKE_PTR(dropins_split);
188
189 return 0;
190 }
191
192 static int get_config_files_by_link_config(
193 const char *link_config,
194 sd_netlink **rtnl,
195 char **ret_path,
196 char ***ret_dropins,
197 ReloadFlags *ret_reload) {
198
199 _cleanup_strv_free_ char **dropins = NULL, **link_config_split = NULL;
200 _cleanup_free_ char *path = NULL;
201 const char *ifname, *type;
202 ReloadFlags reload;
203 size_t n;
204 int r;
205
206 assert(link_config);
207 assert(rtnl);
208 assert(ret_path);
209 assert(ret_dropins);
210
211 link_config_split = strv_split(link_config, ":");
212 if (!link_config_split)
213 return log_oom();
214
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.");
218 if (n > 2)
219 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid link config '%s'.", link_config);
220
221 ifname = link_config_split[0];
222 type = n == 2 ? link_config_split[1] : "network";
223
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.");
228
229 r = get_network_files_by_link(rtnl, ifname, &path, &dropins);
230 if (r < 0)
231 return r;
232
233 reload = RELOAD_NETWORKD;
234 } else if (streq(type, "link")) {
235 r = get_link_files_by_link(ifname, &path, &dropins);
236 if (r < 0)
237 return r;
238
239 reload = RELOAD_UDEVD;
240 } else
241 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
242 "Invalid config type '%s' for link '%s'.", type, ifname);
243
244 *ret_path = TAKE_PTR(path);
245 *ret_dropins = TAKE_PTR(dropins);
246
247 if (ret_reload)
248 *ret_reload = reload;
249
250 return 0;
251 }
252
253 static int add_config_to_edit(
254 EditFileContext *context,
255 const char *path,
256 char * const *dropins) {
257
258 _cleanup_free_ char *new_path = NULL, *dropin_path = NULL, *old_dropin = NULL;
259 _cleanup_strv_free_ char **comment_paths = NULL;
260 int r;
261
262 assert(context);
263 assert(path);
264
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);
270
271 if (path_startswith(path, "/usr") || arg_runtime != !!path_startswith(path, "/run")) {
272 _cleanup_free_ char *name = NULL;
273
274 r = path_extract_filename(path, &name);
275 if (r < 0)
276 return log_error_errno(r, "Failed to extract filename from '%s': %m", path);
277
278 new_path = path_join(NETWORK_DIRS[arg_runtime ? 1 : 0], name);
279 if (!new_path)
280 return log_oom();
281 }
282
283 if (!arg_drop_in)
284 return edit_files_add(context, new_path ?: path, path, NULL);
285
286 bool need_new_dropin;
287
288 r = get_dropin_by_name(arg_drop_in, dropins, &old_dropin);
289 if (r < 0)
290 return log_error_errno(r, "Failed to acquire drop-in '%s': %m", arg_drop_in);
291 if (r > 0) {
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);
296
297 need_new_dropin = path_startswith(old_dropin, "/usr") || arg_runtime != !!path_startswith(old_dropin, "/run");
298 } else
299 need_new_dropin = true;
300
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);
304 else {
305 /* No drop-in was found or an existing drop-in is in a different scope. Let's create a new
306 * drop-in file. */
307 dropin_path = strjoin(new_path ?: path, ".d/", arg_drop_in);
308 if (!dropin_path)
309 return log_oom();
310 }
311
312 comment_paths = strv_new(path);
313 if (!comment_paths)
314 return log_oom();
315
316 r = strv_extend_strv(&comment_paths, dropins, /* filter_duplicates = */ false);
317 if (r < 0)
318 return log_oom();
319
320 return edit_files_add(context, dropin_path, old_dropin, comment_paths);
321 }
322
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;
328 int r;
329
330 assert(bus);
331
332 r = bus_wait_for_jobs_new(bus, &w);
333 if (r < 0)
334 return log_error_errno(r, "Could not watch jobs: %m");
335
336 r = bus_call_method(bus,
337 bus_systemd_mgr,
338 "ReloadUnit",
339 &error,
340 &reply,
341 "ss",
342 "systemd-udevd.service",
343 "replace");
344 if (r < 0)
345 return log_error_errno(r, "Failed to reload systemd-udevd: %s", bus_error_message(&error, r));
346
347 r = sd_bus_message_read(reply, "o", &job_path);
348 if (r < 0)
349 return bus_log_parse_error(r);
350
351 r = bus_wait_for_jobs_one(w, job_path, /* flags = */ 0, NULL);
352 if (r == -ENOEXEC) {
353 log_debug("systemd-udevd is not running, skipping reload.");
354 return 0;
355 }
356 if (r < 0)
357 return log_error_errno(r, "Failed to reload systemd-udevd: %m");
358
359 return 1;
360 }
361
362 static int reload_daemons(ReloadFlags flags) {
363 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
364 int r, ret = 1;
365
366 if (arg_no_reload)
367 return 0;
368
369 if (flags == 0)
370 return 0;
371
372 if (!sd_booted() || running_in_chroot() > 0) {
373 log_debug("System is not booted with systemd or is running in chroot, skipping reload.");
374 return 0;
375 }
376
377 r = sd_bus_open_system(&bus);
378 if (r < 0)
379 return log_error_errno(r, "Failed to connect to system bus: %m");
380
381 if (FLAGS_SET(flags, RELOAD_UDEVD))
382 RET_GATHER(ret, udevd_reload(bus));
383
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;
387
388 r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL);
389 if (r < 0)
390 RET_GATHER(ret, log_error_errno(r, "Failed to reload systemd-networkd: %s", bus_error_message(&error, r)));
391 } else
392 log_debug("systemd-networkd is not running, skipping reload.");
393 }
394
395 return ret;
396 }
397
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,
403 };
404 _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
405 ReloadFlags reload = 0;
406 int r;
407
408 if (!on_tty())
409 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit network config files if not on a tty.");
410
411 r = mac_selinux_init();
412 if (r < 0)
413 return r;
414
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;
419
420 link_config = startswith(*name, "@");
421 if (link_config) {
422 ReloadFlags flags;
423
424 r = get_config_files_by_link_config(link_config, &rtnl, &path, &dropins, &flags);
425 if (r < 0)
426 return r;
427
428 reload |= flags;
429
430 r = add_config_to_edit(&context, path, dropins);
431 if (r < 0)
432 return r;
433
434 continue;
435 }
436
437 if (ENDSWITH_SET(*name, ".network", ".netdev"))
438 reload |= RELOAD_NETWORKD;
439 else if (endswith(*name, ".link"))
440 reload |= RELOAD_UDEVD;
441 else
442 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid network config name '%s'.", *name);
443
444 r = get_config_files_by_name(*name, /* allow_masked = */ false, &path, &dropins);
445 if (r == -ERFKILL)
446 return log_error_errno(r, "Network config '%s' is masked.", *name);
447 if (r == -ENOENT) {
448 if (arg_drop_in)
449 return log_error_errno(r, "Cannot find network config '%s'.", *name);
450
451 log_debug("No existing network config '%s' found, creating a new file.", *name);
452
453 path = path_join(NETWORK_DIRS[arg_runtime ? 1 : 0], *name);
454 if (!path)
455 return log_oom();
456
457 r = edit_files_add(&context, path, NULL, NULL);
458 if (r < 0)
459 return r;
460 continue;
461 }
462 if (r < 0)
463 return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name);
464
465 r = add_config_to_edit(&context, path, dropins);
466 if (r < 0)
467 return r;
468 }
469
470 r = do_edit_files_and_install(&context);
471 if (r < 0)
472 return r;
473
474 return reload_daemons(reload);
475 }
476
477 int verb_cat(int argc, char *argv[], void *userdata) {
478 _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
479 int r, ret = 0;
480
481 pager_open(arg_pager_flags);
482
483 bool first = true;
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;
488
489 link_config = startswith(*name, "@");
490 if (link_config) {
491 r = get_config_files_by_link_config(link_config, &rtnl, &path, &dropins, /* ret_reload = */ NULL);
492 if (r < 0)
493 return RET_GATHER(ret, r);
494 } else {
495 r = get_config_files_by_name(*name, /* allow_masked = */ false, &path, &dropins);
496 if (r == -ENOENT) {
497 RET_GATHER(ret, log_error_errno(r, "Cannot find network config file '%s'.", *name));
498 continue;
499 }
500 if (r == -ERFKILL) {
501 RET_GATHER(ret, log_debug_errno(r, "Network config '%s' is masked, ignoring.", *name));
502 continue;
503 }
504 if (r < 0) {
505 log_error_errno(r, "Failed to get the path of network config '%s': %m", *name);
506 return RET_GATHER(ret, r);
507 }
508 }
509
510 if (!first)
511 putchar('\n');
512
513 r = cat_files(path, dropins, /* flags = */ CAT_FORMAT_HAS_SECTIONS);
514 if (r < 0)
515 return RET_GATHER(ret, r);
516
517 first = false;
518 }
519
520 return ret;
521 }
522
523 int verb_mask(int argc, char *argv[], void *userdata) {
524 ReloadFlags flags = 0;
525 int r;
526
527 r = mac_selinux_init();
528 if (r < 0)
529 return r;
530
531 STRV_FOREACH(name, strv_skip(argv, 1)) {
532 _cleanup_free_ char *config_path = NULL, *symlink_path = NULL;
533 ReloadFlags reload;
534
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;
540 else
541 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid network config name '%s'.", *name);
542
543 r = get_config_files_by_name(*name, /* allow_masked = */ true, &config_path, /* ret_dropins = */ NULL);
544 if (r == -ENOENT)
545 log_warning("No existing network config '%s' found, proceeding anyway.", *name);
546 else if (r < 0)
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);
550 if (r < 0)
551 return log_error_errno(r,
552 "Failed to check if '%s' is masked: %m", config_path);
553 if (r > 0) {
554 log_debug("%s is already masked, skipping.", config_path);
555 continue;
556 }
557
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",
564 *name, config_path);
565 }
566
567 symlink_path = path_join(NETWORK_DIRS[arg_runtime ? 1 : 0], *name);
568 if (!symlink_path)
569 return log_oom();
570
571 (void) mkdir_parents_label(symlink_path, 0755);
572
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);
576
577 flags |= reload;
578 log_info("Successfully created symlink '%s' to /dev/null.", symlink_path);
579 }
580
581 return reload_daemons(flags);
582 }
583
584 int verb_unmask(int argc, char *argv[], void *userdata) {
585 ReloadFlags flags = 0;
586 int r;
587
588 STRV_FOREACH(name, strv_skip(argv, 1)) {
589 _cleanup_free_ char *path = NULL;
590 ReloadFlags reload;
591
592 if (ENDSWITH_SET(*name, ".network", ".netdev"))
593 reload = RELOAD_NETWORKD;
594 else if (endswith(*name, ".link"))
595 reload = RELOAD_UDEVD;
596 else
597 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid network config name '%s'.", *name);
598
599 r = get_config_files_by_name(*name, /* allow_masked = */ true, &path, /* ret_dropins = */ NULL);
600 if (r == -ENOENT) {
601 log_debug_errno(r, "Network configuration '%s' doesn't exist, skipping.", *name);
602 continue;
603 }
604 if (r < 0)
605 return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name);
606
607 r = null_or_empty_path(path);
608 if (r < 0)
609 return log_error_errno(r, "Failed to check if '%s' is masked: %m", path);
610 if (r == 0)
611 continue;
612
613 if (path_startswith(path, "/usr"))
614 return log_error_errno(r, "Cannot unmask network config under /usr/: %s", path);
615
616 if (unlink(path) < 0) {
617 if (errno == ENOENT)
618 continue;
619
620 return log_error_errno(errno, "Failed to remove '%s': %m", path);
621 }
622
623 flags |= reload;
624 log_info("Successfully removed masked network config '%s'.", path);
625 }
626
627 return reload_daemons(flags);
628 }