]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/gpt-auto-generator/gpt-auto-generator.c
log: remove LOG_TARGET_SAFE pseudo log target
[thirdparty/systemd.git] / src / gpt-auto-generator / gpt-auto-generator.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2013 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <blkid.h>
22 #include <stdlib.h>
23 #include <sys/statfs.h>
24 #include <unistd.h>
25
26 #include "libudev.h"
27 #include "sd-id128.h"
28
29 #include "alloc-util.h"
30 #include "blkid-util.h"
31 #include "blockdev-util.h"
32 #include "btrfs-util.h"
33 #include "dirent-util.h"
34 #include "dissect-image.h"
35 #include "efivars.h"
36 #include "fd-util.h"
37 #include "fileio.h"
38 #include "fstab-util.h"
39 #include "generator.h"
40 #include "gpt.h"
41 #include "missing.h"
42 #include "mkdir.h"
43 #include "mount-util.h"
44 #include "parse-util.h"
45 #include "path-util.h"
46 #include "proc-cmdline.h"
47 #include "special.h"
48 #include "specifier.h"
49 #include "stat-util.h"
50 #include "string-util.h"
51 #include "udev-util.h"
52 #include "unit-name.h"
53 #include "util.h"
54 #include "virt.h"
55
56 static const char *arg_dest = "/tmp";
57 static bool arg_enabled = true;
58 static bool arg_root_enabled = true;
59 static bool arg_root_rw = false;
60
61 static int add_cryptsetup(const char *id, const char *what, bool rw, bool require, char **device) {
62 _cleanup_free_ char *e = NULL, *n = NULL, *p = NULL, *d = NULL, *id_escaped = NULL, *what_escaped = NULL;
63 _cleanup_fclose_ FILE *f = NULL;
64 char *ret;
65 int r;
66
67 assert(id);
68 assert(what);
69
70 r = unit_name_from_path(what, ".device", &d);
71 if (r < 0)
72 return log_error_errno(r, "Failed to generate unit name: %m");
73
74 e = unit_name_escape(id);
75 if (!e)
76 return log_oom();
77
78 r = unit_name_build("systemd-cryptsetup", e, ".service", &n);
79 if (r < 0)
80 return log_error_errno(r, "Failed to generate unit name: %m");
81
82 id_escaped = specifier_escape(id);
83 if (!id_escaped)
84 return log_oom();
85
86 what_escaped = specifier_escape(what);
87 if (!what_escaped)
88 return log_oom();
89
90 p = strjoin(arg_dest, "/", n);
91 if (!p)
92 return log_oom();
93
94 f = fopen(p, "wxe");
95 if (!f)
96 return log_error_errno(errno, "Failed to create unit file %s: %m", p);
97
98 fprintf(f,
99 "# Automatically generated by systemd-gpt-auto-generator\n\n"
100 "[Unit]\n"
101 "Description=Cryptography Setup for %%I\n"
102 "Documentation=man:systemd-gpt-auto-generator(8) man:systemd-cryptsetup@.service(8)\n"
103 "DefaultDependencies=no\n"
104 "Conflicts=umount.target\n"
105 "BindsTo=dev-mapper-%%i.device %s\n"
106 "Before=umount.target cryptsetup.target\n"
107 "After=%s\n"
108 "IgnoreOnIsolate=true\n"
109 "[Service]\n"
110 "Type=oneshot\n"
111 "RemainAfterExit=yes\n"
112 "TimeoutSec=0\n" /* the binary handles timeouts anyway */
113 "KeyringMode=shared\n" /* make sure we can share cached keys among instances */
114 "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '' '%s'\n"
115 "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n",
116 d, d,
117 id_escaped, what_escaped, rw ? "" : "read-only",
118 id_escaped);
119
120 r = fflush_and_check(f);
121 if (r < 0)
122 return log_error_errno(r, "Failed to write file %s: %m", p);
123
124 r = generator_add_symlink(arg_dest, d, "wants", n);
125 if (r < 0)
126 return r;
127
128 if (require) {
129 const char *dmname;
130
131 r = generator_add_symlink(arg_dest, "cryptsetup.target", "requires", n);
132 if (r < 0)
133 return r;
134
135 dmname = strjoina("dev-mapper-", e, ".device");
136 r = generator_add_symlink(arg_dest, dmname, "requires", n);
137 if (r < 0)
138 return r;
139 }
140
141 free(p);
142 p = strjoin(arg_dest, "/dev-mapper-", e, ".device.d/50-job-timeout-sec-0.conf");
143 if (!p)
144 return log_oom();
145
146 mkdir_parents_label(p, 0755);
147 r = write_string_file(p,
148 "# Automatically generated by systemd-gpt-auto-generator\n\n"
149 "[Unit]\n"
150 "JobTimeoutSec=0\n",
151 WRITE_STRING_FILE_CREATE); /* the binary handles timeouts anyway */
152 if (r < 0)
153 return log_error_errno(r, "Failed to write device drop-in: %m");
154
155 ret = strappend("/dev/mapper/", id);
156 if (!ret)
157 return log_oom();
158
159 if (device)
160 *device = ret;
161 return 0;
162 }
163
164 static int add_mount(
165 const char *id,
166 const char *what,
167 const char *where,
168 const char *fstype,
169 bool rw,
170 const char *options,
171 const char *description,
172 const char *post) {
173
174 _cleanup_free_ char *unit = NULL, *crypto_what = NULL, *p = NULL;
175 _cleanup_fclose_ FILE *f = NULL;
176 int r;
177
178 /* Note that we don't apply specifier escaping on the input strings here, since we know they are not configured
179 * externally, but all originate from our own sources here, and hence we know they contain no % characters that
180 * could potentially be understood as specifiers. */
181
182 assert(id);
183 assert(what);
184 assert(where);
185 assert(description);
186
187 log_debug("Adding %s: %s %s", where, what, strna(fstype));
188
189 if (streq_ptr(fstype, "crypto_LUKS")) {
190
191 r = add_cryptsetup(id, what, rw, true, &crypto_what);
192 if (r < 0)
193 return r;
194
195 what = crypto_what;
196 fstype = NULL;
197 }
198
199 r = unit_name_from_path(where, ".mount", &unit);
200 if (r < 0)
201 return log_error_errno(r, "Failed to generate unit name: %m");
202
203 p = strjoin(arg_dest, "/", unit);
204 if (!p)
205 return log_oom();
206
207 f = fopen(p, "wxe");
208 if (!f)
209 return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
210
211 fprintf(f,
212 "# Automatically generated by systemd-gpt-auto-generator\n\n"
213 "[Unit]\n"
214 "Description=%s\n"
215 "Documentation=man:systemd-gpt-auto-generator(8)\n",
216 description);
217
218 if (post)
219 fprintf(f, "Before=%s\n", post);
220
221 r = generator_write_fsck_deps(f, arg_dest, what, where, fstype);
222 if (r < 0)
223 return r;
224
225 fprintf(f,
226 "\n"
227 "[Mount]\n"
228 "What=%s\n"
229 "Where=%s\n",
230 what, where);
231
232 if (fstype)
233 fprintf(f, "Type=%s\n", fstype);
234
235 if (options)
236 fprintf(f, "Options=%s,%s\n", options, rw ? "rw" : "ro");
237 else
238 fprintf(f, "Options=%s\n", rw ? "rw" : "ro");
239
240 r = fflush_and_check(f);
241 if (r < 0)
242 return log_error_errno(r, "Failed to write unit file %s: %m", p);
243
244 if (post)
245 return generator_add_symlink(arg_dest, post, "requires", unit);
246 return 0;
247 }
248
249 static bool path_is_busy(const char *where) {
250 int r;
251
252 /* already a mountpoint; generators run during reload */
253 r = path_is_mount_point(where, NULL, AT_SYMLINK_FOLLOW);
254 if (r > 0)
255 return false;
256
257 /* the directory might not exist on a stateless system */
258 if (r == -ENOENT)
259 return false;
260
261 if (r < 0)
262 return true;
263
264 /* not a mountpoint but it contains files */
265 if (dir_is_empty(where) <= 0)
266 return true;
267
268 return false;
269 }
270
271 static int add_partition_mount(
272 DissectedPartition *p,
273 const char *id,
274 const char *where,
275 const char *description) {
276
277 assert(p);
278
279 if (path_is_busy(where)) {
280 log_debug("%s already populated, ignoring.", where);
281 return 0;
282 }
283
284 return add_mount(
285 id,
286 p->node,
287 where,
288 p->fstype,
289 p->rw,
290 NULL,
291 description,
292 SPECIAL_LOCAL_FS_TARGET);
293 }
294
295 static int add_swap(const char *path) {
296 _cleanup_free_ char *name = NULL, *unit = NULL;
297 _cleanup_fclose_ FILE *f = NULL;
298 int r;
299
300 assert(path);
301
302 /* Disable the swap auto logic if at least one swap is defined in /etc/fstab, see #6192. */
303 r = fstab_has_fstype("swap");
304 if (r < 0)
305 return log_error_errno(r, "Failed to parse fstab: %m");
306 if (r > 0) {
307 log_debug("swap specified in fstab, ignoring.");
308 return 0;
309 }
310
311 log_debug("Adding swap: %s", path);
312
313 r = unit_name_from_path(path, ".swap", &name);
314 if (r < 0)
315 return log_error_errno(r, "Failed to generate unit name: %m");
316
317 unit = strjoin(arg_dest, "/", name);
318 if (!unit)
319 return log_oom();
320
321 f = fopen(unit, "wxe");
322 if (!f)
323 return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
324
325 fprintf(f,
326 "# Automatically generated by systemd-gpt-auto-generator\n\n"
327 "[Unit]\n"
328 "Description=Swap Partition\n"
329 "Documentation=man:systemd-gpt-auto-generator(8)\n\n"
330 "[Swap]\n"
331 "What=%s\n",
332 path);
333
334 r = fflush_and_check(f);
335 if (r < 0)
336 return log_error_errno(r, "Failed to write unit file %s: %m", unit);
337
338 return generator_add_symlink(arg_dest, SPECIAL_SWAP_TARGET, "wants", name);
339 }
340
341 #if ENABLE_EFI
342 static int add_automount(
343 const char *id,
344 const char *what,
345 const char *where,
346 const char *fstype,
347 bool rw,
348 const char *options,
349 const char *description,
350 usec_t timeout) {
351
352 _cleanup_free_ char *unit = NULL;
353 _cleanup_free_ char *opt, *p = NULL;
354 _cleanup_fclose_ FILE *f = NULL;
355 int r;
356
357 assert(id);
358 assert(where);
359 assert(description);
360
361 if (options)
362 opt = strjoin(options, ",noauto");
363 else
364 opt = strdup("noauto");
365 if (!opt)
366 return log_oom();
367
368 r = add_mount(id,
369 what,
370 where,
371 fstype,
372 rw,
373 opt,
374 description,
375 NULL);
376 if (r < 0)
377 return r;
378
379 r = unit_name_from_path(where, ".automount", &unit);
380 if (r < 0)
381 return log_error_errno(r, "Failed to generate unit name: %m");
382
383 p = strjoin(arg_dest, "/", unit);
384 if (!p)
385 return log_oom();
386
387 f = fopen(p, "wxe");
388 if (!f)
389 return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
390
391 fprintf(f,
392 "# Automatically generated by systemd-gpt-auto-generator\n\n"
393 "[Unit]\n"
394 "Description=%s\n"
395 "Documentation=man:systemd-gpt-auto-generator(8)\n"
396 "[Automount]\n"
397 "Where=%s\n"
398 "TimeoutIdleSec="USEC_FMT"\n",
399 description,
400 where,
401 timeout / USEC_PER_SEC);
402
403 r = fflush_and_check(f);
404 if (r < 0)
405 return log_error_errno(r, "Failed to write unit file %s: %m", p);
406
407 return generator_add_symlink(arg_dest, SPECIAL_LOCAL_FS_TARGET, "wants", unit);
408 }
409
410 static int add_esp(DissectedPartition *p) {
411 const char *esp;
412 int r;
413
414 assert(p);
415
416 if (in_initrd()) {
417 log_debug("In initrd, ignoring the ESP.");
418 return 0;
419 }
420
421 /* If /efi exists we'll use that. Otherwise we'll use /boot, as that's usually the better choice */
422 esp = access("/efi/", F_OK) >= 0 ? "/efi" : "/boot";
423
424 /* We create an .automount which is not overridden by the .mount from the fstab generator. */
425 r = fstab_is_mount_point(esp);
426 if (r < 0)
427 return log_error_errno(r, "Failed to parse fstab: %m");
428 if (r > 0) {
429 log_debug("%s specified in fstab, ignoring.", esp);
430 return 0;
431 }
432
433 if (path_is_busy(esp)) {
434 log_debug("%s already populated, ignoring.", esp);
435 return 0;
436 }
437
438 if (is_efi_boot()) {
439 sd_id128_t loader_uuid;
440
441 /* If this is an EFI boot, be extra careful, and only mount the ESP if it was the ESP used for booting. */
442
443 r = efi_loader_get_device_part_uuid(&loader_uuid);
444 if (r == -ENOENT) {
445 log_debug("EFI loader partition unknown.");
446 return 0;
447 }
448 if (r < 0)
449 return log_error_errno(r, "Failed to read ESP partition UUID: %m");
450
451 if (!sd_id128_equal(p->uuid, loader_uuid)) {
452 log_debug("Partition for %s does not appear to be the partition we are booted from.", esp);
453 return 0;
454 }
455 } else
456 log_debug("Not an EFI boot, skipping ESP check.");
457
458 return add_automount("boot",
459 p->node,
460 esp,
461 p->fstype,
462 true,
463 "umask=0077",
464 "EFI System Partition Automount",
465 120 * USEC_PER_SEC);
466 }
467 #else
468 static int add_esp(DissectedPartition *p) {
469 return 0;
470 }
471 #endif
472
473 static int open_parent(dev_t devnum, int *ret) {
474 _cleanup_udev_device_unref_ struct udev_device *d = NULL;
475 _cleanup_udev_unref_ struct udev *udev = NULL;
476 const char *name, *devtype, *node;
477 struct udev_device *parent;
478 dev_t pn;
479 int fd;
480
481 assert(ret);
482
483 udev = udev_new();
484 if (!udev)
485 return log_oom();
486
487 d = udev_device_new_from_devnum(udev, 'b', devnum);
488 if (!d)
489 return log_oom();
490
491 name = udev_device_get_devnode(d);
492 if (!name)
493 name = udev_device_get_syspath(d);
494 if (!name) {
495 log_debug("Device %u:%u does not have a name, ignoring.", major(devnum), minor(devnum));
496 goto not_found;
497 }
498
499 parent = udev_device_get_parent(d);
500 if (!parent) {
501 log_debug("%s: not a partitioned device, ignoring.", name);
502 goto not_found;
503 }
504
505 /* Does it have a devtype? */
506 devtype = udev_device_get_devtype(parent);
507 if (!devtype) {
508 log_debug("%s: parent doesn't have a device type, ignoring.", name);
509 goto not_found;
510 }
511
512 /* Is this a disk or a partition? We only care for disks... */
513 if (!streq(devtype, "disk")) {
514 log_debug("%s: parent isn't a raw disk, ignoring.", name);
515 goto not_found;
516 }
517
518 /* Does it have a device node? */
519 node = udev_device_get_devnode(parent);
520 if (!node) {
521 log_debug("%s: parent device does not have device node, ignoring.", name);
522 goto not_found;
523 }
524
525 log_debug("%s: root device %s.", name, node);
526
527 pn = udev_device_get_devnum(parent);
528 if (major(pn) == 0) {
529 log_debug("%s: parent device is not a proper block device, ignoring.", name);
530 goto not_found;
531 }
532
533 fd = open(node, O_RDONLY|O_CLOEXEC|O_NOCTTY);
534 if (fd < 0)
535 return log_error_errno(errno, "Failed to open %s: %m", node);
536
537 *ret = fd;
538 return 1;
539
540 not_found:
541 *ret = -1;
542 return 0;
543 }
544
545 static int enumerate_partitions(dev_t devnum) {
546
547 _cleanup_close_ int fd = -1;
548 _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
549 int r, k;
550
551 r = open_parent(devnum, &fd);
552 if (r <= 0)
553 return r;
554
555 r = dissect_image(fd, NULL, 0, DISSECT_IMAGE_GPT_ONLY, &m);
556 if (r == -ENOPKG) {
557 log_debug_errno(r, "No suitable partition table found, ignoring.");
558 return 0;
559 }
560 if (r < 0)
561 return log_error_errno(r, "Failed to dissect: %m");
562
563 if (m->partitions[PARTITION_SWAP].found) {
564 k = add_swap(m->partitions[PARTITION_SWAP].node);
565 if (k < 0)
566 r = k;
567 }
568
569 if (m->partitions[PARTITION_ESP].found) {
570 k = add_esp(m->partitions + PARTITION_ESP);
571 if (k < 0)
572 r = k;
573 }
574
575 if (m->partitions[PARTITION_HOME].found) {
576 k = add_partition_mount(m->partitions + PARTITION_HOME, "home", "/home", "Home Partition");
577 if (k < 0)
578 r = k;
579 }
580
581 if (m->partitions[PARTITION_SRV].found) {
582 k = add_partition_mount(m->partitions + PARTITION_SRV, "srv", "/srv", "Server Data Partition");
583 if (k < 0)
584 r = k;
585 }
586
587 return r;
588 }
589
590 static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
591 int r;
592
593 assert(key);
594
595 if (STR_IN_SET(key, "systemd.gpt_auto", "rd.systemd.gpt_auto")) {
596
597 r = value ? parse_boolean(value) : 1;
598 if (r < 0)
599 log_warning("Failed to parse gpt-auto switch \"%s\". Ignoring.", value);
600 else
601 arg_enabled = r;
602
603 } else if (streq(key, "root")) {
604
605 if (proc_cmdline_value_missing(key, value))
606 return 0;
607
608 /* Disable root disk logic if there's a root= value
609 * specified (unless it happens to be "gpt-auto") */
610
611 arg_root_enabled = streq(value, "gpt-auto");
612
613 } else if (streq(key, "roothash")) {
614
615 if (proc_cmdline_value_missing(key, value))
616 return 0;
617
618 /* Disable root disk logic if there's roothash= defined (i.e. verity enabled) */
619
620 arg_root_enabled = false;
621
622 } else if (streq(key, "rw") && !value)
623 arg_root_rw = true;
624 else if (streq(key, "ro") && !value)
625 arg_root_rw = false;
626
627 return 0;
628 }
629
630 #if ENABLE_EFI
631 static int add_root_cryptsetup(void) {
632
633 /* If a device /dev/gpt-auto-root-luks appears, then make it pull in systemd-cryptsetup-root.service, which
634 * sets it up, and causes /dev/gpt-auto-root to appear which is all we are looking for. */
635
636 return add_cryptsetup("root", "/dev/gpt-auto-root-luks", true, false, NULL);
637 }
638 #endif
639
640 static int add_root_mount(void) {
641
642 #if ENABLE_EFI
643 int r;
644
645 if (!is_efi_boot()) {
646 log_debug("Not a EFI boot, not creating root mount.");
647 return 0;
648 }
649
650 r = efi_loader_get_device_part_uuid(NULL);
651 if (r == -ENOENT) {
652 log_debug("EFI loader partition unknown, exiting.");
653 return 0;
654 } else if (r < 0)
655 return log_error_errno(r, "Failed to read ESP partition UUID: %m");
656
657 /* OK, we have an ESP partition, this is fantastic, so let's
658 * wait for a root device to show up. A udev rule will create
659 * the link for us under the right name. */
660
661 if (in_initrd()) {
662 r = generator_write_initrd_root_device_deps(arg_dest, "/dev/gpt-auto-root");
663 if (r < 0)
664 return 0;
665
666 r = add_root_cryptsetup();
667 if (r < 0)
668 return r;
669 }
670
671 return add_mount(
672 "root",
673 "/dev/gpt-auto-root",
674 in_initrd() ? "/sysroot" : "/",
675 NULL,
676 arg_root_rw,
677 NULL,
678 "Root Partition",
679 in_initrd() ? SPECIAL_INITRD_ROOT_FS_TARGET : SPECIAL_LOCAL_FS_TARGET);
680 #else
681 return 0;
682 #endif
683 }
684
685 static int add_mounts(void) {
686 dev_t devno;
687 int r;
688
689 r = get_block_device_harder("/", &devno);
690 if (r < 0)
691 return log_error_errno(r, "Failed to determine block device of root file system: %m");
692 if (r == 0) {
693 r = get_block_device_harder("/usr", &devno);
694 if (r < 0)
695 return log_error_errno(r, "Failed to determine block device of /usr file system: %m");
696 if (r == 0) {
697 log_debug("Neither root nor /usr file system are on a (single) block device.");
698 return 0;
699 }
700 }
701
702 return enumerate_partitions(devno);
703 }
704
705 int main(int argc, char *argv[]) {
706 int r, k;
707
708 if (argc > 1 && argc != 4) {
709 log_error("This program takes three or no arguments.");
710 return EXIT_FAILURE;
711 }
712
713 if (argc > 1)
714 arg_dest = argv[3];
715
716 log_set_prohibit_ipc(true);
717 log_set_target(LOG_TARGET_AUTO);
718 log_parse_environment();
719 log_open();
720
721 umask(0022);
722
723 if (detect_container() > 0) {
724 log_debug("In a container, exiting.");
725 return EXIT_SUCCESS;
726 }
727
728 r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0);
729 if (r < 0)
730 log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
731
732 if (!arg_enabled) {
733 log_debug("Disabled, exiting.");
734 return EXIT_SUCCESS;
735 }
736
737 if (arg_root_enabled)
738 r = add_root_mount();
739 else
740 r = 0;
741
742 if (!in_initrd()) {
743 k = add_mounts();
744 if (k < 0)
745 r = k;
746 }
747
748 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
749 }