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