]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/boot/bootctl.c
bootctl: modernization
[thirdparty/systemd.git] / src / boot / bootctl.c
CommitLineData
7b4d7cc0
KS
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
0974a682
KS
6 Copyright 2013-2015 Kay Sievers
7 Copyright 2013 Lennart Poettering
7b4d7cc0
KS
8
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
13
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21***/
22
0974a682 23#include <stdio.h>
7b4d7cc0 24#include <getopt.h>
0974a682
KS
25#include <stdlib.h>
26#include <assert.h>
27#include <sys/statfs.h>
28#include <sys/stat.h>
29#include <errno.h>
7b4d7cc0 30#include <string.h>
0974a682
KS
31#include <unistd.h>
32#include <sys/mman.h>
33#include <dirent.h>
34#include <ctype.h>
35#include <limits.h>
36#include <ftw.h>
37#include <stdbool.h>
38#include <blkid/blkid.h>
7b4d7cc0 39
0974a682 40#include "efivars.h"
7b4d7cc0
KS
41#include "build.h"
42#include "util.h"
c6878637 43#include "rm-rf.h"
d3226d77 44#include "blkid-util.h"
7b4d7cc0 45
0974a682
KS
46static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) {
47 struct statfs sfs;
48 struct stat st, st2;
d3226d77
ZJS
49 _cleanup_free_ char *t = NULL;
50 _cleanup_blkid_free_probe_ blkid_probe b = NULL;
0974a682 51 int r;
d3226d77 52 const char *v, *t2;
0974a682 53
d3226d77
ZJS
54 if (statfs(p, &sfs) < 0)
55 return log_error_errno(errno, "Failed to check file system type of \"%s\": %m", p);
0974a682
KS
56
57 if (sfs.f_type != 0x4d44) {
d3226d77 58 log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p);
0974a682
KS
59 return -ENODEV;
60 }
61
d3226d77
ZJS
62 if (stat(p, &st) < 0)
63 return log_error_errno(errno, "Failed to determine block device node of \"%s\": %m", p);
0974a682
KS
64
65 if (major(st.st_dev) == 0) {
d3226d77 66 log_error("Block device node of %p is invalid.", p);
0974a682
KS
67 return -ENODEV;
68 }
69
d3226d77
ZJS
70 t2 = strjoina(p, "/..");
71 r = stat(t2, &st2);
72 if (r < 0)
73 return log_error_errno(errno, "Failed to determine block device node of parent of \"%s\": %m", p);
0974a682
KS
74
75 if (st.st_dev == st2.st_dev) {
d3226d77 76 log_error("Directory \"%s\" is not the root of the EFI System Partition (ESP) file system.", p);
0974a682
KS
77 return -ENODEV;
78 }
79
80 r = asprintf(&t, "/dev/block/%u:%u", major(st.st_dev), minor(st.st_dev));
d3226d77
ZJS
81 if (r < 0)
82 return log_oom();
0974a682
KS
83
84 errno = 0;
85 b = blkid_new_probe_from_filename(t);
0974a682 86 if (!b) {
d3226d77
ZJS
87 if (errno == 0)
88 return log_oom();
0974a682 89
d3226d77 90 return log_error_errno(errno, "Failed to open file system \"%s\": %m", p);
0974a682
KS
91 }
92
93 blkid_probe_enable_superblocks(b, 1);
94 blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
95 blkid_probe_enable_partitions(b, 1);
96 blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
97
98 errno = 0;
99 r = blkid_do_safeprobe(b);
100 if (r == -2) {
d3226d77
ZJS
101 log_error("File system \"%s\" is ambigious.", p);
102 return -ENODEV;
0974a682 103 } else if (r == 1) {
d3226d77
ZJS
104 log_error("File system \"%s\" does not contain a label.", p);
105 return -ENODEV;
0974a682
KS
106 } else if (r != 0) {
107 r = errno ? -errno : -EIO;
d3226d77 108 return log_error_errno(r, "Failed to probe file system \"%s\": %m", p);
0974a682
KS
109 }
110
111 errno = 0;
112 r = blkid_probe_lookup_value(b, "TYPE", &v, NULL);
113 if (r != 0) {
114 r = errno ? -errno : -EIO;
d3226d77 115 return log_error_errno(r, "Failed to probe file system type \"%s\": %m", p);
0974a682
KS
116 }
117
d3226d77
ZJS
118 if (!streq(v, "vfat")) {
119 log_error("File system \"%s\" is not FAT.", p);
120 return -ENODEV;
0974a682
KS
121 }
122
123 errno = 0;
124 r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
125 if (r != 0) {
126 r = errno ? -errno : -EIO;
d3226d77 127 return log_error_errno(r, "Failed to probe partition scheme \"%s\": %m", p);
0974a682
KS
128 }
129
d3226d77
ZJS
130 if (!streq(v, "gpt")) {
131 log_error("File system \"%s\" is not on a GPT partition table.", p);
132 return -ENODEV;
0974a682
KS
133 }
134
135 errno = 0;
136 r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
137 if (r != 0) {
138 r = errno ? -errno : -EIO;
d3226d77 139 return log_error_errno(r, "Failed to probe partition type UUID \"%s\": %m", p);
0974a682
KS
140 }
141
d3226d77
ZJS
142 if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) {
143 log_error("File system \"%s\" has wrong type for an EFI System Partition (ESP).", p);
144 return -ENODEV;
0974a682
KS
145 }
146
147 errno = 0;
148 r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
149 if (r != 0) {
150 r = errno ? -errno : -EIO;
d3226d77 151 return log_error_errno(r, "Failed to probe partition entry UUID \"%s\": %m", p);
0974a682 152 }
3a4efbff
TA
153
154 r = sd_id128_from_string(v, uuid);
155 if (r < 0) {
d3226d77
ZJS
156 log_error("Partition \"%s\" has invalid UUID \"%s\".", p, v);
157 return -EIO;
3a4efbff 158 }
0974a682
KS
159
160 errno = 0;
161 r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL);
162 if (r != 0) {
163 r = errno ? -errno : -EIO;
d3226d77 164 return log_error_errno(r, "Failed to probe partition number \"%s\": m", p);
0974a682
KS
165 }
166 *part = strtoul(v, NULL, 10);
167
168 errno = 0;
169 r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL);
170 if (r != 0) {
171 r = errno ? -errno : -EIO;
d3226d77 172 return log_error_errno(r, "Failed to probe partition offset \"%s\": %m", p);
0974a682
KS
173 }
174 *pstart = strtoul(v, NULL, 10);
175
176 errno = 0;
177 r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL);
178 if (r != 0) {
179 r = errno ? -errno : -EIO;
d3226d77 180 return log_error_errno(r, "Failed to probe partition size \"%s\": %m", p);
0974a682
KS
181 }
182 *psize = strtoul(v, NULL, 10);
183
0974a682 184 return 0;
7b4d7cc0
KS
185}
186
e7dd673d 187/* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */
d3226d77 188static int get_file_version(int fd, char **v) {
0974a682
KS
189 struct stat st;
190 char *buf;
191 const char *s, *e;
192 char *x = NULL;
193 int r = 0;
7b4d7cc0 194
d3226d77 195 assert(fd >= 0);
0974a682 196 assert(v);
7b4d7cc0 197
d3226d77 198 if (fstat(fd, &st) < 0)
0974a682 199 return -errno;
7b4d7cc0 200
0974a682
KS
201 if (st.st_size < 27)
202 return 0;
eb9da376 203
d3226d77 204 buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
0974a682
KS
205 if (buf == MAP_FAILED)
206 return -errno;
7b4d7cc0 207
0974a682
KS
208 s = memmem(buf, st.st_size - 8, "#### LoaderInfo: ", 17);
209 if (!s)
210 goto finish;
211 s += 17;
7b4d7cc0 212
0974a682
KS
213 e = memmem(s, st.st_size - (s - buf), " ####", 5);
214 if (!e || e - s < 3) {
d3226d77 215 log_error("Malformed version string.");
0974a682
KS
216 r = -EINVAL;
217 goto finish;
218 }
7b4d7cc0 219
0974a682
KS
220 x = strndup(s, e - s);
221 if (!x) {
d3226d77 222 r = log_oom();
0974a682
KS
223 goto finish;
224 }
225 r = 1;
7b4d7cc0 226
0974a682
KS
227finish:
228 munmap(buf, st.st_size);
229 *v = x;
230 return r;
231}
7b4d7cc0 232
0974a682 233static int enumerate_binaries(const char *esp_path, const char *path, const char *prefix) {
d3226d77
ZJS
234 char *p;
235 _cleanup_closedir_ DIR *d = NULL;
0974a682 236 struct dirent *de;
0974a682
KS
237 int r = 0, c = 0;
238
d3226d77 239 p = strjoina(esp_path, "/", path);
0974a682
KS
240 d = opendir(p);
241 if (!d) {
d3226d77
ZJS
242 if (errno == ENOENT)
243 return 0;
7b4d7cc0 244
d3226d77 245 return log_error_errno(errno, "Failed to read \"%s\": %m", p);
0974a682
KS
246 }
247
248 while ((de = readdir(d))) {
d3226d77
ZJS
249 _cleanup_close_ int fd = -1;
250 _cleanup_free_ char *v = NULL;
0974a682
KS
251
252 if (de->d_name[0] == '.')
253 continue;
254
d3226d77 255 if (!endswith_no_case(de->d_name, ".efi"))
0974a682
KS
256 continue;
257
d3226d77 258 if (prefix && !startswith_no_case(de->d_name, prefix))
0974a682
KS
259 continue;
260
d3226d77
ZJS
261 fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC);
262 if (fd < 0)
263 return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name);
0974a682 264
d3226d77 265 r = get_file_version(fd, &v);
0974a682 266 if (r < 0)
d3226d77 267 return r;
0974a682
KS
268 if (r > 0)
269 printf(" File: └─/%s/%s (%s)\n", path, de->d_name, v);
270 else
271 printf(" File: └─/%s/%s\n", path, de->d_name);
0974a682 272 c++;
0974a682
KS
273 }
274
d3226d77 275 return c;
7b4d7cc0
KS
276}
277
0974a682
KS
278static int status_binaries(const char *esp_path, sd_id128_t partition) {
279 int r;
280
281 printf("Boot Loader Binaries:\n");
282
283 printf(" ESP: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", SD_ID128_FORMAT_VAL(partition));
284
285 r = enumerate_binaries(esp_path, "EFI/systemd", NULL);
286 if (r == 0)
d3226d77 287 log_error("systemd-boot not installed in ESP.");
0974a682
KS
288 else if (r < 0)
289 return r;
290
291 r = enumerate_binaries(esp_path, "EFI/Boot", "boot");
292 if (r == 0)
d3226d77 293 log_error("No default/fallback boot loader installed in ESP.");
0974a682
KS
294 else if (r < 0)
295 return r;
296
0974a682
KS
297 return 0;
298}
299
300static int print_efi_option(uint16_t id, bool in_order) {
7cb0f263
TA
301 _cleanup_free_ char *title = NULL;
302 _cleanup_free_ char *path = NULL;
0974a682
KS
303 sd_id128_t partition;
304 bool active;
305 int r = 0;
306
307 r = efi_get_boot_option(id, &title, &partition, &path, &active);
308 if (r < 0)
7cb0f263 309 return r;
7b4d7cc0 310
0974a682
KS
311 /* print only configured entries with partition information */
312 if (!path || sd_id128_equal(partition, SD_ID128_NULL))
313 return 0;
314
315 efi_tilt_backslashes(path);
316
317 printf(" Title: %s\n", strna(title));
318 printf(" ID: 0x%04X\n", id);
319 printf(" Status: %sactive%s\n", active ? "" : "in", in_order ? ", boot-order" : "");
320 printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", SD_ID128_FORMAT_VAL(partition));
321 printf(" File: └─%s\n", path);
322 printf("\n");
323
7cb0f263 324 return 0;
0974a682
KS
325}
326
327static int status_variables(void) {
328 int n_options, n_order;
d3226d77
ZJS
329 _cleanup_free_ uint16_t *options = NULL, *order = NULL;
330 int i;
0974a682
KS
331
332 if (!is_efi_boot()) {
d3226d77 333 log_notice("Not booted with EFI, not showing EFI variables.");
0974a682
KS
334 return 0;
335 }
336
337 n_options = efi_get_boot_options(&options);
d3226d77
ZJS
338 if (n_options == -ENOENT)
339 return log_error_errno(ENOENT, "Failed to access EFI variables, efivarfs"
340 " needs to be available at /sys/firmware/efi/efivars/.");
341 else if (n_options < 0)
342 return log_error_errno(n_options, "Failed to read EFI boot entries: %m");
0974a682 343
0974a682 344 n_order = efi_get_boot_order(&order);
d3226d77 345 if (n_order == -ENOENT)
0974a682 346 n_order = 0;
d3226d77
ZJS
347 else if (n_order < 0)
348 return log_error_errno(n_order, "Failed to read EFI boot order.");
0974a682
KS
349
350 /* print entries in BootOrder first */
d3226d77 351 printf("Boot Loader Entries in EFI Variables:\n");
0974a682
KS
352 for (i = 0; i < n_order; i++)
353 print_efi_option(order[i], true);
354
355 /* print remaining entries */
356 for (i = 0; i < n_options; i++) {
357 int j;
0974a682
KS
358
359 for (j = 0; j < n_order; j++)
d3226d77
ZJS
360 if (options[i] == order[j])
361 goto next;
0974a682
KS
362
363 print_efi_option(options[i], false);
d3226d77
ZJS
364 next:
365 continue;
0974a682
KS
366 }
367
d3226d77 368 return 0;
0974a682
KS
369}
370
371static int compare_product(const char *a, const char *b) {
372 size_t x, y;
373
374 assert(a);
375 assert(b);
376
377 x = strcspn(a, " ");
378 y = strcspn(b, " ");
379 if (x != y)
380 return x < y ? -1 : x > y ? 1 : 0;
381
382 return strncmp(a, b, x);
383}
384
385static int compare_version(const char *a, const char *b) {
386 assert(a);
387 assert(b);
388
389 a += strcspn(a, " ");
390 a += strspn(a, " ");
391 b += strcspn(b, " ");
392 b += strspn(b, " ");
393
394 return strverscmp(a, b);
395}
396
d3226d77
ZJS
397static int version_check(int fd, const char *from, const char *to) {
398 _cleanup_free_ char *a = NULL, *b = NULL;
399 _cleanup_close_ int fd2 = -1;
0974a682
KS
400 int r;
401
d3226d77 402 assert(fd >= 0);
0974a682
KS
403 assert(from);
404 assert(to);
405
d3226d77 406 r = get_file_version(fd, &a);
0974a682 407 if (r < 0)
d3226d77 408 return r;
0974a682 409 if (r == 0) {
d3226d77
ZJS
410 log_error("Source file \"%s\" does not carry version information!", from);
411 return -EINVAL;
0974a682
KS
412 }
413
d3226d77
ZJS
414 fd2 = open(to, O_RDONLY|O_CLOEXEC);
415 if (fd2 < 0) {
416 if (errno == ENOENT)
417 return 0;
0974a682 418
d3226d77 419 return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", to);
0974a682
KS
420 }
421
d3226d77 422 r = get_file_version(fd2, &b);
0974a682 423 if (r < 0)
d3226d77 424 return r;
0974a682 425 if (r == 0 || compare_product(a, b) != 0) {
d3226d77
ZJS
426 log_notice("Skipping \"%s\", since it's owned by another boot loader.", to);
427 return -EEXIST;
0974a682
KS
428 }
429
430 if (compare_version(a, b) < 0) {
d3226d77
ZJS
431 log_warning("Skipping \"%s\", since a newer boot loader version exists already.", to);
432 return -ESTALE;
0974a682
KS
433 }
434
d3226d77 435 return 0;
0974a682
KS
436}
437
438static int copy_file(const char *from, const char *to, bool force) {
d3226d77
ZJS
439 _cleanup_fclose_ FILE *f = NULL, *g = NULL;
440 char *p;
0974a682
KS
441 int r;
442 struct timespec t[2];
443 struct stat st;
444
445 assert(from);
446 assert(to);
447
448 f = fopen(from, "re");
d3226d77
ZJS
449 if (!f)
450 return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", from);
0974a682
KS
451
452 if (!force) {
453 /* If this is an update, then let's compare versions first */
d3226d77 454 r = version_check(fileno(f), from, to);
0974a682 455 if (r < 0)
d3226d77 456 return r;
0974a682
KS
457 }
458
d3226d77 459 p = strjoina(to, "~");
0974a682
KS
460 g = fopen(p, "wxe");
461 if (!g) {
462 /* Directory doesn't exist yet? Then let's skip this... */
d3226d77
ZJS
463 if (!force && errno == ENOENT)
464 return 0;
0974a682 465
d3226d77 466 return log_error_errno(errno, "Failed to open \"%s\" for writing: %m", to);
0974a682
KS
467 }
468
469 rewind(f);
470 do {
471 size_t k;
472 uint8_t buf[32*1024];
473
474 k = fread(buf, 1, sizeof(buf), f);
475 if (ferror(f)) {
d3226d77
ZJS
476 r = log_error_errno(EIO, "Failed to read \"%s\": %m", from);
477 goto error;
0974a682 478 }
d3226d77 479
0974a682
KS
480 if (k == 0)
481 break;
482
483 fwrite(buf, 1, k, g);
484 if (ferror(g)) {
d3226d77
ZJS
485 r = log_error_errno(EIO, "Failed to write \"%s\": %m", to);
486 goto error;
0974a682
KS
487 }
488 } while (!feof(f));
489
490 fflush(g);
491 if (ferror(g)) {
d3226d77
ZJS
492 r = log_error_errno(EIO, "Failed to write \"%s\": %m", to);
493 goto error;
0974a682
KS
494 }
495
496 r = fstat(fileno(f), &st);
497 if (r < 0) {
d3226d77
ZJS
498 r = log_error_errno(errno, "Failed to get file timestamps of \"%s\": %m", from);
499 goto error;
0974a682
KS
500 }
501
502 t[0] = st.st_atim;
503 t[1] = st.st_mtim;
504
505 r = futimens(fileno(g), t);
506 if (r < 0) {
d3226d77
ZJS
507 r = log_error_errno(errno, "Failed to set file timestamps on \"%s\": %m", p);
508 goto error;
0974a682
KS
509 }
510
511 if (rename(p, to) < 0) {
d3226d77
ZJS
512 r = log_error_errno(errno, "Failed to rename \"%s\" to \"%s\": %m", p, to);
513 goto error;
0974a682
KS
514 }
515
d3226d77
ZJS
516 log_info("Copied \"%s\" to \"%s\".", from, to);
517 return 0;
0974a682 518
d3226d77
ZJS
519error:
520 unlink(p);
0974a682
KS
521 return r;
522}
523
524static char* strupper(char *s) {
525 char *p;
526
527 for (p = s; *p; p++)
528 *p = toupper(*p);
529
530 return s;
531}
532
533static int mkdir_one(const char *prefix, const char *suffix) {
534 char *p;
535
d3226d77 536 p = strjoina(prefix, "/", suffix);
0974a682 537 if (mkdir(p, 0700) < 0) {
d3226d77
ZJS
538 if (errno != EEXIST)
539 return log_error_errno(errno, "Failed to create \"%s\": %m", p);
0974a682 540 } else
d3226d77 541 log_info("Created \"%s\".", p);
7b4d7cc0 542
0974a682
KS
543 return 0;
544}
545
d3226d77
ZJS
546static const char *efi_subdirs[] = {
547 "EFI",
548 "EFI/systemd",
549 "EFI/Boot",
550 "loader",
551 "loader/entries"
552};
553
0974a682
KS
554static int create_dirs(const char *esp_path) {
555 int r;
d3226d77 556 unsigned i;
0974a682 557
d3226d77
ZJS
558 for (i = 0; i < ELEMENTSOF(efi_subdirs); i++) {
559 r = mkdir_one(esp_path, efi_subdirs[i]);
560 if (r < 0)
561 return r;
562 }
7b4d7cc0 563
7b4d7cc0 564 return 0;
7b4d7cc0
KS
565}
566
0974a682 567static int copy_one_file(const char *esp_path, const char *name, bool force) {
d3226d77 568 char *p, *q;
0974a682
KS
569 int r;
570
d3226d77
ZJS
571 p = strjoina(BOOTLIBDIR "/", name);
572 q = strjoina(esp_path, "/EFI/systemd/", name);
0974a682
KS
573 r = copy_file(p, q, force);
574
e7dd673d 575 if (startswith(name, "systemd-boot")) {
0974a682 576 int k;
d3226d77 577 char *v;
0974a682
KS
578
579 /* Create the EFI default boot loader name (specified for removable devices) */
d3226d77 580 v = strjoina(esp_path, "/EFI/Boot/BOOT", name + strlen("systemd-boot"));
0974a682
KS
581 strupper(strrchr(v, '/') + 1);
582
583 k = copy_file(p, v, force);
584 if (k < 0 && r == 0)
d3226d77 585 r = k;
0974a682
KS
586 }
587
588 return r;
7b4d7cc0
KS
589}
590
0974a682
KS
591static int install_binaries(const char *esp_path, bool force) {
592 struct dirent *de;
d3226d77 593 _cleanup_closedir_ DIR *d = NULL;
0974a682
KS
594 int r = 0;
595
596 if (force) {
597 /* Don't create any of these directories when we are
598 * just updating. When we update we'll drop-in our
599 * files (unless there are newer ones already), but we
600 * won't create the directories for them in the first
601 * place. */
602 r = create_dirs(esp_path);
603 if (r < 0)
604 return r;
605 }
606
e7dd673d 607 d = opendir(BOOTLIBDIR);
d3226d77
ZJS
608 if (!d)
609 return log_error_errno(errno, "Failed to open \""BOOTLIBDIR"\": %m");
0974a682
KS
610
611 while ((de = readdir(d))) {
0974a682
KS
612 int k;
613
614 if (de->d_name[0] == '.')
615 continue;
616
d3226d77 617 if (!endswith_no_case(de->d_name, ".efi"))
0974a682
KS
618 continue;
619
620 k = copy_one_file(esp_path, de->d_name, force);
621 if (k < 0 && r == 0)
622 r = k;
623 }
624
0974a682 625 return r;
7b4d7cc0
KS
626}
627
0974a682 628static bool same_entry(uint16_t id, const sd_id128_t uuid, const char *path) {
d3226d77 629 _cleanup_free_ char *opath = NULL;
0974a682 630 sd_id128_t ouuid;
d3226d77 631 int r;
7b4d7cc0 632
d3226d77
ZJS
633 r = efi_get_boot_option(id, NULL, &ouuid, &opath, NULL);
634 if (r < 0)
0974a682
KS
635 return false;
636 if (!sd_id128_equal(uuid, ouuid))
d3226d77 637 return false;
0974a682 638 if (!streq_ptr(path, opath))
d3226d77 639 return false;
0974a682 640
d3226d77 641 return true;
0974a682
KS
642}
643
644static int find_slot(sd_id128_t uuid, const char *path, uint16_t *id) {
d3226d77
ZJS
645 _cleanup_free_ uint16_t *options = NULL;
646 int n, i;
0974a682 647
d3226d77
ZJS
648 n = efi_get_boot_options(&options);
649 if (n < 0)
650 return n;
0974a682 651
e7dd673d 652 /* find already existing systemd-boot entry */
d3226d77 653 for (i = 0; i < n; i++)
0974a682 654 if (same_entry(options[i], uuid, path)) {
d3226d77
ZJS
655 *id = options[i];
656 return 1;
0974a682
KS
657 }
658
659 /* find free slot in the sorted BootXXXX variable list */
d3226d77 660 for (i = 0; i < n; i++)
0974a682 661 if (i != options[i]) {
d3226d77
ZJS
662 *id = i;
663 return 1;
0974a682
KS
664 }
665
666 /* use the next one */
667 if (i == 0xffff)
668 return -ENOSPC;
d3226d77
ZJS
669 *id = i;
670 return 0;
0974a682
KS
671}
672
673static int insert_into_order(uint16_t slot, bool first) {
d3226d77
ZJS
674 _cleanup_free_ uint16_t *order = NULL;
675 uint16_t *t;
676 int n, i;
0974a682 677
d3226d77
ZJS
678 n = efi_get_boot_order(&order);
679 if (n <= 0)
0974a682 680 /* no entry, add us */
d3226d77 681 return efi_set_boot_order(&slot, 1);
0974a682
KS
682
683 /* are we the first and only one? */
d3226d77
ZJS
684 if (n == 1 && order[0] == slot)
685 return 0;
0974a682
KS
686
687 /* are we already in the boot order? */
d3226d77 688 for (i = 0; i < n; i++) {
0974a682
KS
689 if (order[i] != slot)
690 continue;
691
692 /* we do not require to be the first one, all is fine */
693 if (!first)
d3226d77 694 return 0;
0974a682
KS
695
696 /* move us to the first slot */
d3226d77 697 memmove(order + 1, order, i * sizeof(uint16_t));
0974a682 698 order[0] = slot;
d3226d77 699 return efi_set_boot_order(order, n);
0974a682
KS
700 }
701
702 /* extend array */
d3226d77
ZJS
703 t = realloc(order, (n + 1) * sizeof(uint16_t));
704 if (!t)
705 return -ENOMEM;
706 order = t;
0974a682
KS
707
708 /* add us to the top or end of the list */
709 if (first) {
d3226d77 710 memmove(order + 1, order, n * sizeof(uint16_t));
0974a682
KS
711 order[0] = slot;
712 } else
d3226d77 713 order[n] = slot;
0974a682 714
d3226d77 715 return efi_set_boot_order(order, n + 1);
0974a682
KS
716}
717
718static int remove_from_order(uint16_t slot) {
7cb0f263 719 _cleanup_free_ uint16_t *order = NULL;
d3226d77 720 int n, i;
0974a682 721
d3226d77
ZJS
722 n = efi_get_boot_order(&order);
723 if (n <= 0)
724 return n;
0974a682 725
d3226d77 726 for (i = 0; i < n; i++) {
0974a682
KS
727 if (order[i] != slot)
728 continue;
729
d3226d77
ZJS
730 if (i + 1 < n)
731 memmove(order + i, order + i+1, (n - i) * sizeof(uint16_t));
732 return efi_set_boot_order(order, n - 1);
0974a682
KS
733 }
734
d3226d77 735 return 0;
0974a682
KS
736}
737
738static int install_variables(const char *esp_path,
739 uint32_t part, uint64_t pstart, uint64_t psize,
740 sd_id128_t uuid, const char *path,
741 bool first) {
d3226d77 742 char *p;
0974a682
KS
743 uint16_t slot;
744 int r;
745
746 if (!is_efi_boot()) {
d3226d77 747 log_warning("Not booted with EFI, skipping EFI variable setup.");
0974a682
KS
748 return 0;
749 }
750
d3226d77 751 p = strjoina(esp_path, path);
0974a682
KS
752 if (access(p, F_OK) < 0) {
753 if (errno == ENOENT)
d3226d77 754 return 0;
0974a682 755 else
d3226d77 756 return log_error_errno(errno, "Cannot access \"%s\": %m", p);
0974a682 757 }
7b4d7cc0 758
0974a682 759 r = find_slot(uuid, path, &slot);
d3226d77
ZJS
760 if (r < 0)
761 return log_error_errno(r,
762 r == -ENOENT ?
763 "Failed to access EFI variables. Is the \"efivarfs\" filesystem mounted?" :
764 "Failed to determine current boot order: %m");
7b4d7cc0 765
0974a682
KS
766 if (first || r == false) {
767 r = efi_add_boot_option(slot, "Linux Boot Manager",
768 part, pstart, psize,
769 uuid, path);
d3226d77
ZJS
770 if (r < 0)
771 return log_error_errno(r, "Failed to create EFI Boot variable entry: %m");
0974a682 772
d3226d77
ZJS
773 log_info("Created EFI boot entry \"Linux Boot Manager\".");
774 }
0974a682 775
d3226d77 776 return insert_into_order(slot, first);
0974a682
KS
777}
778
779static int remove_boot_efi(const char *esp_path) {
d3226d77
ZJS
780 char *p;
781 _cleanup_closedir_ DIR *d = NULL;
0974a682 782 struct dirent *de;
d3226d77 783 int r, c = 0;
0974a682 784
d3226d77 785 p = strjoina(esp_path, "/EFI/Boot");
0974a682
KS
786 d = opendir(p);
787 if (!d) {
d3226d77
ZJS
788 if (errno == ENOENT)
789 return 0;
0974a682 790
d3226d77 791 return log_error_errno(errno, "Failed to open directory \"%s\": %m", p);
0974a682
KS
792 }
793
794 while ((de = readdir(d))) {
d3226d77
ZJS
795 _cleanup_close_ int fd = -1;
796 _cleanup_free_ char *v = NULL;
0974a682
KS
797
798 if (de->d_name[0] == '.')
799 continue;
800
d3226d77 801 if (!endswith_no_case(de->d_name, ".efi"))
0974a682
KS
802 continue;
803
d3226d77 804 if (!startswith_no_case(de->d_name, "Boot"))
0974a682
KS
805 continue;
806
d3226d77
ZJS
807 fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC);
808 if (r < 0)
809 return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name);
0974a682 810
d3226d77 811 r = get_file_version(fd, &v);
0974a682 812 if (r < 0)
d3226d77
ZJS
813 return r;
814 if (r > 0 && startswith(v, "systemd-boot ")) {
815 r = unlinkat(dirfd(d), de->d_name, 0);
816 if (r < 0)
817 return log_error_errno(errno, "Failed to remove \"%s/%s\": %m", p, de->d_name);
818
819 log_info("Removed \"%s/\%s\".", p, de->d_name);
0974a682
KS
820 }
821
822 c++;
0974a682
KS
823 }
824
d3226d77 825 return c;
0974a682
KS
826}
827
828static int rmdir_one(const char *prefix, const char *suffix) {
829 char *p;
7b4d7cc0 830
d3226d77 831 p = strjoina(prefix, "/", suffix);
0974a682 832 if (rmdir(p) < 0) {
d3226d77
ZJS
833 if (!IN_SET(errno, ENOENT, ENOTEMPTY))
834 return log_error_errno(errno, "Failed to remove \"%s\": %m", p);
7b4d7cc0 835 } else
d3226d77 836 log_info("Removed \"%s\".", p);
7b4d7cc0 837
0974a682 838 return 0;
7b4d7cc0
KS
839}
840
0974a682
KS
841static int remove_binaries(const char *esp_path) {
842 char *p;
843 int r, q;
d3226d77 844 unsigned i;
0974a682 845
d3226d77 846 p = strjoina(esp_path, "/EFI/systemd");
c6878637 847 r = rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL);
0974a682
KS
848
849 q = remove_boot_efi(esp_path);
850 if (q < 0 && r == 0)
851 r = q;
852
d3226d77
ZJS
853 for (i = ELEMENTSOF(efi_subdirs); i > 0; i--) {
854 q = rmdir_one(esp_path, efi_subdirs[i-1]);
855 if (q < 0 && r == 0)
856 r = q;
857 }
0974a682
KS
858
859 return r;
860}
861
862static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) {
863 uint16_t slot;
864 int r;
865
866 if (!is_efi_boot())
867 return 0;
868
869 r = find_slot(uuid, path, &slot);
870 if (r != 1)
871 return 0;
872
873 r = efi_remove_boot_option(slot);
874 if (r < 0)
875 return r;
876
877 if (in_order)
d3226d77
ZJS
878 return remove_from_order(slot);
879 else
880 return 0;
0974a682
KS
881}
882
883static int install_loader_config(const char *esp_path) {
d3226d77 884 char *p;
0974a682
KS
885 char line[64];
886 char *machine = NULL;
887 FILE *f;
888
889 f = fopen("/etc/machine-id", "re");
890 if (!f)
891 return -errno;
892
893 if (fgets(line, sizeof(line), f) != NULL) {
894 char *s;
895
896 s = strchr(line, '\n');
897 if (s)
898 s[0] = '\0';
899 if (strlen(line) == 32)
900 machine = line;
901 }
0974a682
KS
902 fclose(f);
903
904 if (!machine)
905 return -ESRCH;
906
d3226d77 907 p = strjoina(esp_path, "/loader/loader.conf");
0974a682
KS
908 f = fopen(p, "wxe");
909 if (f) {
910 fprintf(f, "#timeout 3\n");
911 fprintf(f, "default %s-*\n", machine);
912 fclose(f);
d3226d77
ZJS
913 if (ferror(f))
914 return log_error_errno(EIO, "Failed to write \"%s\": %m", p);
0974a682
KS
915 }
916
0974a682
KS
917 return 0;
918}
919
920static int help(void) {
921 printf("%s [COMMAND] [OPTIONS...]\n"
922 "\n"
923 "Install, update or remove the sdboot EFI boot manager.\n\n"
924 " -h --help Show this help\n"
925 " --version Print version\n"
926 " --path=PATH Path to the EFI System Partition (ESP)\n"
927 " --no-variables Don't touch EFI variables\n"
928 "\n"
929 "Comands:\n"
e7dd673d
TG
930 " status Show status of installed systemd-boot and EFI variables\n"
931 " install Install systemd-boot to the ESP and EFI variables\n"
932 " update Update systemd-boot in the ESP and EFI variables\n"
933 " remove Remove systemd-boot from the ESP and EFI variables\n",
0974a682
KS
934 program_invocation_short_name);
935
936 return 0;
937}
938
d3226d77 939static const char *arg_path = "/boot";
0974a682
KS
940static bool arg_touch_variables = true;
941
942static int parse_argv(int argc, char *argv[]) {
943 enum {
944 ARG_PATH = 0x100,
945 ARG_VERSION,
946 ARG_NO_VARIABLES,
7b4d7cc0
KS
947 };
948
0974a682
KS
949 static const struct option options[] = {
950 { "help", no_argument, NULL, 'h' },
951 { "version", no_argument, NULL, ARG_VERSION },
952 { "path", required_argument, NULL, ARG_PATH },
953 { "no-variables", no_argument, NULL, ARG_NO_VARIABLES },
954 { NULL, 0, NULL, 0 }
955 };
956
957 int c;
7b4d7cc0
KS
958
959 assert(argc >= 0);
960 assert(argv);
961
d3226d77 962 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
0974a682 963 switch (c) {
7b4d7cc0 964
0974a682 965 case 'h':
7b4d7cc0
KS
966 help();
967 return 0;
7b4d7cc0 968
0974a682
KS
969 case ARG_VERSION:
970 printf(VERSION "\n");
971 return 0;
7b4d7cc0 972
0974a682
KS
973 case ARG_PATH:
974 arg_path = optarg;
975 break;
976
977 case ARG_NO_VARIABLES:
978 arg_touch_variables = false;
979 break;
980
981 case '?':
982 return -EINVAL;
983
984 default:
d3226d77 985 assert_not_reached("Unknown option");
7b4d7cc0 986 }
7b4d7cc0 987
0974a682
KS
988 return 1;
989}
7b4d7cc0 990
0974a682
KS
991static int bootctl_main(int argc, char*argv[]) {
992 enum action {
993 ACTION_STATUS,
994 ACTION_INSTALL,
995 ACTION_UPDATE,
996 ACTION_REMOVE
997 } arg_action = ACTION_STATUS;
998 static const struct {
999 const char* verb;
1000 enum action action;
1001 } verbs[] = {
1002 { "status", ACTION_STATUS },
1003 { "install", ACTION_INSTALL },
1004 { "update", ACTION_UPDATE },
1005 { "remove", ACTION_REMOVE },
1006 };
1007
1008 sd_id128_t uuid = {};
1009 uint32_t part = 0;
d3226d77
ZJS
1010 uint64_t pstart = 0, psize = 0;
1011 int r, q;
0974a682 1012
0974a682 1013 if (argv[optind]) {
d3226d77
ZJS
1014 unsigned i;
1015
0974a682
KS
1016 for (i = 0; i < ELEMENTSOF(verbs); i++) {
1017 if (!streq(argv[optind], verbs[i].verb))
1018 continue;
1019 arg_action = verbs[i].action;
1020 break;
1021 }
1022 if (i >= ELEMENTSOF(verbs)) {
d3226d77
ZJS
1023 log_error("Unknown operation \"%s\"", argv[optind]);
1024 return -EINVAL;
7b4d7cc0 1025 }
0974a682
KS
1026 }
1027
d3226d77
ZJS
1028 if (geteuid() != 0)
1029 return log_error_errno(EPERM, "Need to be root.");
0974a682
KS
1030
1031 r = verify_esp(arg_path, &part, &pstart, &psize, &uuid);
1032 if (r == -ENODEV && !arg_path)
d3226d77 1033 log_notice("You might want to use --path= to indicate the path to your ESP, in case it is not mounted on /boot.");
0974a682 1034 if (r < 0)
d3226d77 1035 return r;
0974a682
KS
1036
1037 switch (arg_action) {
1038 case ACTION_STATUS: {
1039 _cleanup_free_ char *fw_type = NULL;
1040 _cleanup_free_ char *fw_info = NULL;
1041 _cleanup_free_ char *loader = NULL;
1042 _cleanup_free_ char *loader_path = NULL;
1043 sd_id128_t loader_part_uuid = {};
1044
1045 efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderFirmwareType", &fw_type);
1046 efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderFirmwareInfo", &fw_info);
1047 efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderInfo", &loader);
1a1db450
ZJS
1048 if (efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderImageIdentifier", &loader_path) > 0)
1049 efi_tilt_backslashes(loader_path);
0974a682
KS
1050 efi_loader_get_device_part_uuid(&loader_part_uuid);
1051
1052 printf("System:\n");
1053 printf(" Firmware: %s (%s)\n", fw_type, strna(fw_info));
1054 printf(" Secure Boot: %s\n", is_efi_secure_boot() ? "enabled" : "disabled");
1055 printf(" Setup Mode: %s\n", is_efi_secure_boot_setup_mode() ? "setup" : "user");
1056 printf("\n");
1057
1058 printf("Loader:\n");
1059 printf(" Product: %s\n", strna(loader));
1060 if (!sd_id128_equal(loader_part_uuid, SD_ID128_NULL))
1061 printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
1062 SD_ID128_FORMAT_VAL(loader_part_uuid));
1063 else
1064 printf(" Partition: n/a\n");
1065 printf(" File: %s%s\n", draw_special_char(DRAW_TREE_RIGHT), strna(loader_path));
1066 printf("\n");
1067
1068 r = status_binaries(arg_path, uuid);
1069 if (r < 0)
d3226d77 1070 return r;
0974a682
KS
1071
1072 if (arg_touch_variables)
1073 r = status_variables();
7b4d7cc0 1074 break;
0974a682 1075 }
7b4d7cc0 1076
0974a682
KS
1077 case ACTION_INSTALL:
1078 case ACTION_UPDATE:
1079 umask(0002);
1080
1081 r = install_binaries(arg_path, arg_action == ACTION_INSTALL);
1082 if (r < 0)
d3226d77 1083 return r;
0974a682 1084
d3226d77
ZJS
1085 if (arg_action == ACTION_INSTALL) {
1086 r = install_loader_config(arg_path);
1087 if (r < 0)
1088 return r;
1089 }
0974a682
KS
1090
1091 if (arg_touch_variables)
1092 r = install_variables(arg_path,
1093 part, pstart, psize, uuid,
e7dd673d 1094 "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi",
0974a682 1095 arg_action == ACTION_INSTALL);
7b4d7cc0
KS
1096 break;
1097
0974a682
KS
1098 case ACTION_REMOVE:
1099 r = remove_binaries(arg_path);
1100
1101 if (arg_touch_variables) {
e7dd673d 1102 q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true);
0974a682
KS
1103 if (q < 0 && r == 0)
1104 r = q;
7b4d7cc0
KS
1105 }
1106 break;
7b4d7cc0
KS
1107 }
1108
d3226d77 1109 return r;
7b4d7cc0
KS
1110}
1111
1112int main(int argc, char *argv[]) {
601185b4 1113 int r;
7b4d7cc0
KS
1114
1115 log_parse_environment();
1116 log_open();
1117
1118 r = parse_argv(argc, argv);
601185b4 1119 if (r <= 0)
7b4d7cc0 1120 goto finish;
7b4d7cc0
KS
1121
1122 r = bootctl_main(argc, argv);
601185b4
ZJS
1123
1124 finish:
1125 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
7b4d7cc0 1126}