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