]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nspawn/nspawn-settings.c
nspawn: add a new --cpu-affinity= switch
[thirdparty/systemd.git] / src / nspawn / nspawn-settings.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2015 Lennart Poettering
6 ***/
7
8 #include "alloc-util.h"
9 #include "cap-list.h"
10 #include "conf-parser.h"
11 #include "cpu-set-util.h"
12 #include "hostname-util.h"
13 #include "nspawn-network.h"
14 #include "nspawn-settings.h"
15 #include "parse-util.h"
16 #include "process-util.h"
17 #include "rlimit-util.h"
18 #include "socket-util.h"
19 #include "string-util.h"
20 #include "strv.h"
21 #include "user-util.h"
22 #include "util.h"
23
24 int settings_load(FILE *f, const char *path, Settings **ret) {
25 _cleanup_(settings_freep) Settings *s = NULL;
26 int r;
27
28 assert(path);
29 assert(ret);
30
31 s = new0(Settings, 1);
32 if (!s)
33 return -ENOMEM;
34
35 s->start_mode = _START_MODE_INVALID;
36 s->personality = PERSONALITY_INVALID;
37 s->userns_mode = _USER_NAMESPACE_MODE_INVALID;
38 s->uid_shift = UID_INVALID;
39 s->uid_range = UID_INVALID;
40 s->no_new_privileges = -1;
41
42 s->read_only = -1;
43 s->volatile_mode = _VOLATILE_MODE_INVALID;
44 s->userns_chown = -1;
45
46 s->private_network = -1;
47 s->network_veth = -1;
48
49 r = config_parse(NULL, path, f,
50 "Exec\0"
51 "Network\0"
52 "Files\0",
53 config_item_perf_lookup, nspawn_gperf_lookup,
54 CONFIG_PARSE_WARN,
55 s);
56 if (r < 0)
57 return r;
58
59 /* Make sure that if userns_mode is set, userns_chown is set to something appropriate, and vice versa. Either
60 * both fields shall be initialized or neither. */
61 if (s->userns_mode == USER_NAMESPACE_PICK)
62 s->userns_chown = true;
63 else if (s->userns_mode != _USER_NAMESPACE_MODE_INVALID && s->userns_chown < 0)
64 s->userns_chown = false;
65
66 if (s->userns_chown >= 0 && s->userns_mode == _USER_NAMESPACE_MODE_INVALID)
67 s->userns_mode = USER_NAMESPACE_NO;
68
69 *ret = TAKE_PTR(s);
70
71 return 0;
72 }
73
74 Settings* settings_free(Settings *s) {
75
76 if (!s)
77 return NULL;
78
79 strv_free(s->parameters);
80 strv_free(s->environment);
81 free(s->user);
82 free(s->pivot_root_new);
83 free(s->pivot_root_old);
84 free(s->working_directory);
85 strv_free(s->syscall_whitelist);
86 strv_free(s->syscall_blacklist);
87 rlimit_free_all(s->rlimit);
88 free(s->hostname);
89 s->cpuset = cpu_set_mfree(s->cpuset);
90
91 strv_free(s->network_interfaces);
92 strv_free(s->network_macvlan);
93 strv_free(s->network_ipvlan);
94 strv_free(s->network_veth_extra);
95 free(s->network_bridge);
96 free(s->network_zone);
97 expose_port_free_all(s->expose_ports);
98
99 custom_mount_free_all(s->custom_mounts, s->n_custom_mounts);
100 return mfree(s);
101 }
102
103 bool settings_private_network(Settings *s) {
104 assert(s);
105
106 return
107 s->private_network > 0 ||
108 s->network_veth > 0 ||
109 s->network_bridge ||
110 s->network_zone ||
111 s->network_interfaces ||
112 s->network_macvlan ||
113 s->network_ipvlan ||
114 s->network_veth_extra;
115 }
116
117 bool settings_network_veth(Settings *s) {
118 assert(s);
119
120 return
121 s->network_veth > 0 ||
122 s->network_bridge ||
123 s->network_zone;
124 }
125
126 DEFINE_CONFIG_PARSE_ENUM(config_parse_volatile_mode, volatile_mode, VolatileMode, "Failed to parse volatile mode");
127
128 int config_parse_expose_port(
129 const char *unit,
130 const char *filename,
131 unsigned line,
132 const char *section,
133 unsigned section_line,
134 const char *lvalue,
135 int ltype,
136 const char *rvalue,
137 void *data,
138 void *userdata) {
139
140 Settings *s = data;
141 int r;
142
143 assert(filename);
144 assert(lvalue);
145 assert(rvalue);
146
147 r = expose_port_parse(&s->expose_ports, rvalue);
148 if (r == -EEXIST) {
149 log_syntax(unit, LOG_ERR, filename, line, r, "Duplicate port specification, ignoring: %s", rvalue);
150 return 0;
151 }
152 if (r < 0) {
153 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse host port %s: %m", rvalue);
154 return 0;
155 }
156
157 return 0;
158 }
159
160 int config_parse_capability(
161 const char *unit,
162 const char *filename,
163 unsigned line,
164 const char *section,
165 unsigned section_line,
166 const char *lvalue,
167 int ltype,
168 const char *rvalue,
169 void *data,
170 void *userdata) {
171
172 uint64_t u = 0, *result = data;
173 int r;
174
175 assert(filename);
176 assert(lvalue);
177 assert(rvalue);
178
179 for (;;) {
180 _cleanup_free_ char *word = NULL;
181 int cap;
182
183 r = extract_first_word(&rvalue, &word, NULL, 0);
184 if (r < 0) {
185 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract capability string, ignoring: %s", rvalue);
186 return 0;
187 }
188 if (r == 0)
189 break;
190
191 cap = capability_from_name(word);
192 if (cap < 0) {
193 log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability, ignoring: %s", word);
194 continue;
195 }
196
197 u |= UINT64_C(1) << cap;
198 }
199
200 if (u == 0)
201 return 0;
202
203 *result |= u;
204 return 0;
205 }
206
207 int config_parse_id128(
208 const char *unit,
209 const char *filename,
210 unsigned line,
211 const char *section,
212 unsigned section_line,
213 const char *lvalue,
214 int ltype,
215 const char *rvalue,
216 void *data,
217 void *userdata) {
218
219 sd_id128_t t, *result = data;
220 int r;
221
222 assert(filename);
223 assert(lvalue);
224 assert(rvalue);
225
226 r = sd_id128_from_string(rvalue, &t);
227 if (r < 0) {
228 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse 128bit ID/UUID, ignoring: %s", rvalue);
229 return 0;
230 }
231
232 *result = t;
233 return 0;
234 }
235
236 int config_parse_pivot_root(
237 const char *unit,
238 const char *filename,
239 unsigned line,
240 const char *section,
241 unsigned section_line,
242 const char *lvalue,
243 int ltype,
244 const char *rvalue,
245 void *data,
246 void *userdata) {
247
248 Settings *settings = data;
249 int r;
250
251 assert(filename);
252 assert(lvalue);
253 assert(rvalue);
254
255 r = pivot_root_parse(&settings->pivot_root_new, &settings->pivot_root_old, rvalue);
256 if (r < 0) {
257 log_syntax(unit, LOG_ERR, filename, line, r, "Invalid pivot root mount specification %s: %m", rvalue);
258 return 0;
259 }
260
261 return 0;
262 }
263
264 int config_parse_bind(
265 const char *unit,
266 const char *filename,
267 unsigned line,
268 const char *section,
269 unsigned section_line,
270 const char *lvalue,
271 int ltype,
272 const char *rvalue,
273 void *data,
274 void *userdata) {
275
276 Settings *settings = data;
277 int r;
278
279 assert(filename);
280 assert(lvalue);
281 assert(rvalue);
282
283 r = bind_mount_parse(&settings->custom_mounts, &settings->n_custom_mounts, rvalue, ltype);
284 if (r < 0) {
285 log_syntax(unit, LOG_ERR, filename, line, r, "Invalid bind mount specification %s: %m", rvalue);
286 return 0;
287 }
288
289 return 0;
290 }
291
292 int config_parse_tmpfs(
293 const char *unit,
294 const char *filename,
295 unsigned line,
296 const char *section,
297 unsigned section_line,
298 const char *lvalue,
299 int ltype,
300 const char *rvalue,
301 void *data,
302 void *userdata) {
303
304 Settings *settings = data;
305 int r;
306
307 assert(filename);
308 assert(lvalue);
309 assert(rvalue);
310
311 r = tmpfs_mount_parse(&settings->custom_mounts, &settings->n_custom_mounts, rvalue);
312 if (r < 0) {
313 log_syntax(unit, LOG_ERR, filename, line, r, "Invalid temporary file system specification %s: %m", rvalue);
314 return 0;
315 }
316
317 return 0;
318 }
319
320 int config_parse_overlay(
321 const char *unit,
322 const char *filename,
323 unsigned line,
324 const char *section,
325 unsigned section_line,
326 const char *lvalue,
327 int ltype,
328 const char *rvalue,
329 void *data,
330 void *userdata) {
331
332 Settings *settings = data;
333 int r;
334
335 assert(filename);
336 assert(lvalue);
337 assert(rvalue);
338
339 r = overlay_mount_parse(&settings->custom_mounts, &settings->n_custom_mounts, rvalue, ltype);
340 if (r < 0)
341 log_syntax(unit, LOG_ERR, filename, line, r, "Invalid overlay file system specification %s, ignoring: %m", rvalue);
342
343 return 0;
344 }
345
346 int config_parse_veth_extra(
347 const char *unit,
348 const char *filename,
349 unsigned line,
350 const char *section,
351 unsigned section_line,
352 const char *lvalue,
353 int ltype,
354 const char *rvalue,
355 void *data,
356 void *userdata) {
357
358 Settings *settings = data;
359 int r;
360
361 assert(filename);
362 assert(lvalue);
363 assert(rvalue);
364
365 r = veth_extra_parse(&settings->network_veth_extra, rvalue);
366 if (r < 0) {
367 log_syntax(unit, LOG_ERR, filename, line, r, "Invalid extra virtual Ethernet link specification %s: %m", rvalue);
368 return 0;
369 }
370
371 return 0;
372 }
373
374 int config_parse_network_zone(
375 const char *unit,
376 const char *filename,
377 unsigned line,
378 const char *section,
379 unsigned section_line,
380 const char *lvalue,
381 int ltype,
382 const char *rvalue,
383 void *data,
384 void *userdata) {
385
386 Settings *settings = data;
387 _cleanup_free_ char *j = NULL;
388
389 assert(filename);
390 assert(lvalue);
391 assert(rvalue);
392
393 j = strappend("vz-", rvalue);
394 if (!ifname_valid(j)) {
395 log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid network zone name %s, ignoring: %m", rvalue);
396 return 0;
397 }
398
399 free_and_replace(settings->network_zone, j);
400
401 return 0;
402 }
403
404 int config_parse_boot(
405 const char *unit,
406 const char *filename,
407 unsigned line,
408 const char *section,
409 unsigned section_line,
410 const char *lvalue,
411 int ltype,
412 const char *rvalue,
413 void *data,
414 void *userdata) {
415
416 Settings *settings = data;
417 int r;
418
419 assert(filename);
420 assert(lvalue);
421 assert(rvalue);
422
423 r = parse_boolean(rvalue);
424 if (r < 0) {
425 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse Boot= parameter %s, ignoring: %m", rvalue);
426 return 0;
427 }
428
429 if (r > 0) {
430 if (settings->start_mode == START_PID2)
431 goto conflict;
432
433 settings->start_mode = START_BOOT;
434 } else {
435 if (settings->start_mode == START_BOOT)
436 goto conflict;
437
438 if (settings->start_mode < 0)
439 settings->start_mode = START_PID1;
440 }
441
442 return 0;
443
444 conflict:
445 log_syntax(unit, LOG_ERR, filename, line, r, "Conflicting Boot= or ProcessTwo= setting found. Ignoring.");
446 return 0;
447 }
448
449 int config_parse_pid2(
450 const char *unit,
451 const char *filename,
452 unsigned line,
453 const char *section,
454 unsigned section_line,
455 const char *lvalue,
456 int ltype,
457 const char *rvalue,
458 void *data,
459 void *userdata) {
460
461 Settings *settings = data;
462 int r;
463
464 assert(filename);
465 assert(lvalue);
466 assert(rvalue);
467
468 r = parse_boolean(rvalue);
469 if (r < 0) {
470 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse ProcessTwo= parameter %s, ignoring: %m", rvalue);
471 return 0;
472 }
473
474 if (r > 0) {
475 if (settings->start_mode == START_BOOT)
476 goto conflict;
477
478 settings->start_mode = START_PID2;
479 } else {
480 if (settings->start_mode == START_PID2)
481 goto conflict;
482
483 if (settings->start_mode < 0)
484 settings->start_mode = START_PID1;
485 }
486
487 return 0;
488
489 conflict:
490 log_syntax(unit, LOG_ERR, filename, line, r, "Conflicting Boot= or ProcessTwo= setting found. Ignoring.");
491 return 0;
492 }
493
494 int config_parse_private_users(
495 const char *unit,
496 const char *filename,
497 unsigned line,
498 const char *section,
499 unsigned section_line,
500 const char *lvalue,
501 int ltype,
502 const char *rvalue,
503 void *data,
504 void *userdata) {
505
506 Settings *settings = data;
507 int r;
508
509 assert(filename);
510 assert(lvalue);
511 assert(rvalue);
512
513 r = parse_boolean(rvalue);
514 if (r == 0) {
515 /* no: User namespacing off */
516 settings->userns_mode = USER_NAMESPACE_NO;
517 settings->uid_shift = UID_INVALID;
518 settings->uid_range = UINT32_C(0x10000);
519 } else if (r > 0) {
520 /* yes: User namespacing on, UID range is read from root dir */
521 settings->userns_mode = USER_NAMESPACE_FIXED;
522 settings->uid_shift = UID_INVALID;
523 settings->uid_range = UINT32_C(0x10000);
524 } else if (streq(rvalue, "pick")) {
525 /* pick: User namespacing on, UID range is picked randomly */
526 settings->userns_mode = USER_NAMESPACE_PICK;
527 settings->uid_shift = UID_INVALID;
528 settings->uid_range = UINT32_C(0x10000);
529 } else {
530 const char *range, *shift;
531 uid_t sh, rn;
532
533 /* anything else: User namespacing on, UID range is explicitly configured */
534
535 range = strchr(rvalue, ':');
536 if (range) {
537 shift = strndupa(rvalue, range - rvalue);
538 range++;
539
540 r = safe_atou32(range, &rn);
541 if (r < 0 || rn <= 0) {
542 log_syntax(unit, LOG_ERR, filename, line, r, "UID/GID range invalid, ignoring: %s", range);
543 return 0;
544 }
545 } else {
546 shift = rvalue;
547 rn = UINT32_C(0x10000);
548 }
549
550 r = parse_uid(shift, &sh);
551 if (r < 0) {
552 log_syntax(unit, LOG_ERR, filename, line, r, "UID/GID shift invalid, ignoring: %s", range);
553 return 0;
554 }
555
556 settings->userns_mode = USER_NAMESPACE_FIXED;
557 settings->uid_shift = sh;
558 settings->uid_range = rn;
559 }
560
561 return 0;
562 }
563
564 int config_parse_syscall_filter(
565 const char *unit,
566 const char *filename,
567 unsigned line,
568 const char *section,
569 unsigned section_line,
570 const char *lvalue,
571 int ltype,
572 const char *rvalue,
573 void *data,
574 void *userdata) {
575
576 Settings *settings = data;
577 bool negative;
578 const char *items;
579 int r;
580
581 assert(filename);
582 assert(lvalue);
583 assert(rvalue);
584
585 negative = rvalue[0] == '~';
586 items = negative ? rvalue + 1 : rvalue;
587
588 for (;;) {
589 _cleanup_free_ char *word = NULL;
590
591 r = extract_first_word(&items, &word, NULL, 0);
592 if (r == 0)
593 break;
594 if (r == -ENOMEM)
595 return log_oom();
596 if (r < 0) {
597 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse SystemCallFilter= parameter %s, ignoring: %m", rvalue);
598 return 0;
599 }
600
601 if (negative)
602 r = strv_extend(&settings->syscall_blacklist, word);
603 else
604 r = strv_extend(&settings->syscall_whitelist, word);
605 if (r < 0)
606 return log_oom();
607 }
608
609 return 0;
610 }
611
612 int config_parse_hostname(
613 const char *unit,
614 const char *filename,
615 unsigned line,
616 const char *section,
617 unsigned section_line,
618 const char *lvalue,
619 int ltype,
620 const char *rvalue,
621 void *data,
622 void *userdata) {
623
624 char **s = data;
625
626 assert(rvalue);
627 assert(s);
628
629 if (!hostname_is_valid(rvalue, false)) {
630 log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid hostname, ignoring: %s", rvalue);
631 return 0;
632 }
633
634 if (free_and_strdup(s, empty_to_null(rvalue)) < 0)
635 return log_oom();
636
637 return 0;
638 }
639
640 int config_parse_oom_score_adjust(
641 const char *unit,
642 const char *filename,
643 unsigned line,
644 const char *section,
645 unsigned section_line,
646 const char *lvalue,
647 int ltype,
648 const char *rvalue,
649 void *data,
650 void *userdata) {
651
652 Settings *settings = data;
653 int oa, r;
654
655 assert(rvalue);
656 assert(settings);
657
658 if (isempty(rvalue)) {
659 settings->oom_score_adjust_set = false;
660 return 0;
661 }
662
663 r = parse_oom_score_adjust(rvalue, &oa);
664 if (r == -ERANGE) {
665 log_syntax(unit, LOG_ERR, filename, line, r, "OOM score adjust value out of range, ignoring: %s", rvalue);
666 return 0;
667 }
668 if (r < 0) {
669 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse the OOM score adjust value, ignoring: %s", rvalue);
670 return 0;
671 }
672
673 settings->oom_score_adjust = oa;
674 settings->oom_score_adjust_set = true;
675
676 return 0;
677 }
678
679 int config_parse_cpu_affinity(
680 const char *unit,
681 const char *filename,
682 unsigned line,
683 const char *section,
684 unsigned section_line,
685 const char *lvalue,
686 int ltype,
687 const char *rvalue,
688 void *data,
689 void *userdata) {
690
691 _cleanup_cpu_free_ cpu_set_t *cpuset = NULL;
692 Settings *settings = data;
693 int ncpus;
694
695 assert(rvalue);
696 assert(settings);
697
698 ncpus = parse_cpu_set_and_warn(rvalue, &cpuset, unit, filename, line, lvalue);
699 if (ncpus < 0)
700 return ncpus;
701
702 if (ncpus == 0) {
703 /* An empty assignment resets the CPU list */
704 settings->cpuset = cpu_set_mfree(settings->cpuset);
705 settings->cpuset_ncpus = 0;
706 return 0;
707 }
708
709 if (!settings->cpuset) {
710 settings->cpuset = TAKE_PTR(cpuset);
711 settings->cpuset_ncpus = (unsigned) ncpus;
712 return 0;
713 }
714
715 if (settings->cpuset_ncpus < (unsigned) ncpus) {
716 CPU_OR_S(CPU_ALLOC_SIZE(settings->cpuset_ncpus), cpuset, settings->cpuset, cpuset);
717 CPU_FREE(settings->cpuset);
718 settings->cpuset = TAKE_PTR(cpuset);
719 settings->cpuset_ncpus = (unsigned) ncpus;
720 return 0;
721 }
722
723 CPU_OR_S(CPU_ALLOC_SIZE((unsigned) ncpus), settings->cpuset, settings->cpuset, cpuset);
724
725 return 0;
726 }