]>
Commit | Line | Data |
---|---|---|
2e3d0692 LP |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2013 Lennart Poettering | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU Lesser General Public License as published by | |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
22 | #include <unistd.h> | |
7b4d7cc0 | 23 | #include <string.h> |
2e3d0692 | 24 | #include <fcntl.h> |
726c6b6b | 25 | #include <ctype.h> |
2e3d0692 LP |
26 | |
27 | #include "util.h" | |
28 | #include "utf8.h" | |
29 | #include "efivars.h" | |
30 | ||
9cde64ff | 31 | bool is_efi_boot(void) { |
34e5a31e LP |
32 | return access("/sys/firmware/efi", F_OK) >= 0; |
33 | } | |
34 | ||
9cde64ff LP |
35 | int efi_get_variable( |
36 | sd_id128_t vendor, | |
37 | const char *name, | |
38 | uint32_t *attribute, | |
39 | void **value, | |
40 | size_t *size) { | |
41 | ||
2e3d0692 LP |
42 | _cleanup_close_ int fd = -1; |
43 | _cleanup_free_ char *p = NULL; | |
44 | uint32_t a; | |
45 | ssize_t n; | |
46 | struct stat st; | |
47 | void *r; | |
48 | ||
49 | assert(name); | |
50 | assert(value); | |
51 | assert(size); | |
52 | ||
53 | if (asprintf(&p, | |
54 | "/sys/firmware/efi/efivars/%s-%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", | |
55 | name, SD_ID128_FORMAT_VAL(vendor)) < 0) | |
56 | return -ENOMEM; | |
57 | ||
58 | fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC); | |
59 | if (fd < 0) | |
60 | return -errno; | |
61 | ||
62 | if (fstat(fd, &st) < 0) | |
63 | return -errno; | |
64 | if (st.st_size < 4) | |
65 | return -EIO; | |
66 | if (st.st_size > 4*1024*1024 + 4) | |
67 | return -E2BIG; | |
68 | ||
69 | n = read(fd, &a, sizeof(a)); | |
70 | if (n < 0) | |
9cde64ff | 71 | return -errno; |
2e3d0692 LP |
72 | if (n != sizeof(a)) |
73 | return -EIO; | |
74 | ||
75 | r = malloc(st.st_size - 4 + 2); | |
76 | if (!r) | |
77 | return -ENOMEM; | |
78 | ||
79 | n = read(fd, r, (size_t) st.st_size - 4); | |
80 | if (n < 0) { | |
81 | free(r); | |
82 | return (int) -n; | |
83 | } | |
84 | if (n != (ssize_t) st.st_size - 4) { | |
85 | free(r); | |
86 | return -EIO; | |
87 | } | |
88 | ||
89 | /* Always NUL terminate (2 bytes, to protect UTF-16) */ | |
90 | ((char*) r)[st.st_size - 4] = 0; | |
91 | ((char*) r)[st.st_size - 4 + 1] = 0; | |
92 | ||
93 | *value = r; | |
ff47c895 | 94 | *size = (size_t) st.st_size - 4; |
2e3d0692 LP |
95 | |
96 | if (attribute) | |
97 | *attribute = a; | |
98 | ||
99 | return 0; | |
100 | } | |
101 | ||
9cde64ff | 102 | int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) { |
7b4d7cc0 KS |
103 | _cleanup_free_ void *s = NULL; |
104 | size_t ss; | |
9cde64ff LP |
105 | int r; |
106 | char *x; | |
7b4d7cc0 | 107 | |
9cde64ff LP |
108 | r = efi_get_variable(vendor, name, NULL, &s, &ss); |
109 | if (r < 0) | |
110 | return r; | |
111 | ||
112 | x = utf16_to_utf8(s, ss); | |
113 | if (!x) | |
114 | return -ENOMEM; | |
115 | ||
116 | *p = x; | |
117 | return 0; | |
7b4d7cc0 KS |
118 | } |
119 | ||
120 | static size_t utf16_size(const uint16_t *s) { | |
121 | size_t l = 0; | |
122 | ||
123 | while (s[l] > 0) | |
124 | l++; | |
9cde64ff | 125 | |
7b4d7cc0 KS |
126 | return (l+1) * sizeof(uint16_t); |
127 | } | |
128 | ||
129 | static void efi_guid_to_id128(const void *guid, sd_id128_t *id128) { | |
130 | struct uuid { | |
131 | uint32_t u1; | |
132 | uint16_t u2; | |
133 | uint16_t u3; | |
134 | uint8_t u4[8]; | |
135 | } _packed_; | |
136 | const struct uuid *uuid = guid; | |
137 | ||
138 | id128->bytes[0] = (uuid->u1 >> 24) & 0xff; | |
139 | id128->bytes[1] = (uuid->u1 >> 16) & 0xff; | |
140 | id128->bytes[2] = (uuid->u1 >> 8) & 0xff; | |
141 | id128->bytes[3] = (uuid->u1) & 0xff; | |
142 | id128->bytes[4] = (uuid->u2 >> 8) & 0xff; | |
143 | id128->bytes[5] = (uuid->u2) & 0xff; | |
144 | id128->bytes[6] = (uuid->u3 >> 8) & 0xff; | |
145 | id128->bytes[7] = (uuid->u3) & 0xff; | |
146 | memcpy(&id128->bytes[8], uuid->u4, sizeof(uuid->u4)); | |
147 | } | |
148 | ||
9cde64ff LP |
149 | int efi_get_boot_option( |
150 | uint16_t id, | |
151 | char **title, | |
152 | sd_id128_t *part_uuid, | |
153 | char **path) { | |
154 | ||
7b4d7cc0 KS |
155 | struct boot_option { |
156 | uint32_t attr; | |
157 | uint16_t path_len; | |
158 | uint16_t title[]; | |
159 | } _packed_; | |
160 | ||
161 | struct drive_path { | |
162 | uint32_t part_nr; | |
163 | uint64_t part_start; | |
164 | uint64_t part_size; | |
165 | char signature[16]; | |
166 | uint8_t mbr_type; | |
167 | uint8_t signature_type; | |
168 | } _packed_; | |
169 | ||
170 | struct device_path { | |
171 | uint8_t type; | |
172 | uint8_t sub_type; | |
173 | uint16_t length; | |
174 | union { | |
175 | uint16_t path[0]; | |
176 | struct drive_path drive; | |
177 | }; | |
178 | } _packed_; | |
179 | ||
9cde64ff LP |
180 | char boot_id[9]; |
181 | _cleanup_free_ uint8_t *buf = NULL; | |
7b4d7cc0 KS |
182 | size_t l; |
183 | struct boot_option *header; | |
184 | size_t title_size; | |
185 | char *s = NULL; | |
186 | char *p = NULL; | |
187 | sd_id128_t p_uuid = SD_ID128_NULL; | |
7b4d7cc0 KS |
188 | int err; |
189 | ||
190 | snprintf(boot_id, sizeof(boot_id), "Boot%04X", id); | |
191 | err = efi_get_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, (void **)&buf, &l); | |
192 | if (err < 0) | |
193 | return err; | |
7b4d7cc0 KS |
194 | if (l < sizeof(struct boot_option)) |
195 | return -ENOENT; | |
196 | ||
197 | header = (struct boot_option *)buf; | |
198 | title_size = utf16_size(header->title); | |
199 | if (title_size > l - offsetof(struct boot_option, title)) | |
200 | return -EINVAL; | |
201 | ||
202 | s = utf16_to_utf8(header->title, title_size); | |
203 | if (!s) { | |
204 | err = -ENOMEM; | |
205 | goto err; | |
206 | } | |
207 | ||
208 | if (header->path_len > 0) { | |
9cde64ff | 209 | uint8_t *dbuf; |
7b4d7cc0 KS |
210 | size_t dnext; |
211 | ||
212 | dbuf = buf + offsetof(struct boot_option, title) + title_size; | |
213 | dnext = 0; | |
214 | while (dnext < header->path_len) { | |
215 | struct device_path *dpath; | |
216 | ||
217 | dpath = (struct device_path *)(dbuf + dnext); | |
218 | if (dpath->length < 4) | |
219 | break; | |
220 | ||
221 | /* Type 0x7F – End of Hardware Device Path, Sub-Type 0xFF – End Entire Device Path */ | |
222 | if (dpath->type == 0x7f && dpath->sub_type == 0xff) | |
223 | break; | |
224 | ||
225 | dnext += dpath->length; | |
226 | ||
227 | /* Type 0x04 – Media Device Path */ | |
228 | if (dpath->type != 0x04) | |
229 | continue; | |
230 | ||
231 | /* Sub-Type 1 – Hard Drive */ | |
232 | if (dpath->sub_type == 0x01) { | |
233 | /* 0x02 – GUID Partition Table */ | |
234 | if (dpath->drive.mbr_type != 0x02) | |
235 | continue; | |
236 | ||
237 | /* 0x02 – GUID signature */ | |
238 | if (dpath->drive.signature_type != 0x02) | |
239 | continue; | |
240 | ||
241 | efi_guid_to_id128(dpath->drive.signature, &p_uuid); | |
242 | continue; | |
243 | } | |
244 | ||
245 | /* Sub-Type 4 – File Path */ | |
246 | if (dpath->sub_type == 0x04) { | |
247 | p = utf16_to_utf8(dpath->path, dpath->length-4); | |
248 | continue; | |
249 | } | |
250 | } | |
251 | } | |
252 | ||
9cde64ff LP |
253 | if (title) |
254 | *title = s; | |
7b4d7cc0 KS |
255 | if (part_uuid) |
256 | *part_uuid = p_uuid; | |
257 | if (path) | |
258 | *path = p; | |
9cde64ff | 259 | |
7b4d7cc0 KS |
260 | return 0; |
261 | err: | |
262 | free(s); | |
263 | free(p); | |
7b4d7cc0 KS |
264 | return err; |
265 | } | |
266 | ||
9cde64ff | 267 | int efi_get_boot_order(uint16_t **order) { |
7b4d7cc0 KS |
268 | void *buf; |
269 | size_t l; | |
9cde64ff | 270 | int r; |
7b4d7cc0 | 271 | |
9cde64ff LP |
272 | r = efi_get_variable(EFI_VENDOR_GLOBAL, "BootOrder", NULL, &buf, &l); |
273 | if (r < 0) | |
274 | return r; | |
7b4d7cc0 | 275 | |
9cde64ff | 276 | if (l <= 0) { |
7b4d7cc0 KS |
277 | free(buf); |
278 | return -ENOENT; | |
279 | } | |
280 | ||
9cde64ff LP |
281 | if ((l % sizeof(uint16_t) > 0) || |
282 | (l / sizeof(uint16_t) > INT_MAX)) { | |
7b4d7cc0 KS |
283 | free(buf); |
284 | return -EINVAL; | |
285 | } | |
286 | ||
287 | *order = buf; | |
9cde64ff LP |
288 | return (int) (l / sizeof(uint16_t)); |
289 | } | |
290 | ||
4d34c495 KS |
291 | static int boot_id_hex(const char s[4]) { |
292 | int i; | |
293 | int id = 0; | |
294 | ||
295 | for (i = 0; i < 4; i++) | |
296 | if (s[i] >= '0' && s[i] <= '9') | |
297 | id |= (s[i] - '0') << (3 - i) * 4; | |
298 | else if (s[i] >= 'A' && s[i] <= 'F') | |
299 | id |= (s[i] - 'A' + 10) << (3 - i) * 4; | |
300 | else | |
301 | return -1; | |
302 | ||
303 | return id; | |
304 | } | |
305 | ||
9db11a99 LP |
306 | static int cmp_uint16(const void *_a, const void *_b) { |
307 | const uint16_t *a = _a, *b = _b; | |
308 | ||
309 | if (*a < *b) | |
310 | return -1; | |
311 | if (*a > *b) | |
312 | return 1; | |
313 | ||
314 | return 0; | |
315 | } | |
316 | ||
9cde64ff LP |
317 | int efi_get_boot_options(uint16_t **options) { |
318 | _cleanup_closedir_ DIR *dir = NULL; | |
319 | struct dirent *de; | |
320 | uint16_t *list = NULL; | |
9db11a99 | 321 | int count = 0, r; |
9cde64ff LP |
322 | |
323 | assert(options); | |
324 | ||
325 | dir = opendir("/sys/firmware/efi/efivars/"); | |
326 | if (!dir) | |
327 | return -errno; | |
328 | ||
9db11a99 | 329 | FOREACH_DIRENT(de, dir, r = -errno; goto fail) { |
4d34c495 | 330 | int id; |
9cde64ff LP |
331 | uint16_t *t; |
332 | ||
333 | if (strncmp(de->d_name, "Boot", 4) != 0) | |
334 | continue; | |
335 | ||
4d34c495 | 336 | if (strlen(de->d_name) != 45) |
9cde64ff LP |
337 | continue; |
338 | ||
339 | if (strcmp(de->d_name + 8, "-8be4df61-93ca-11d2-aa0d-00e098032b8c") != 0) | |
340 | continue; | |
341 | ||
4d34c495 KS |
342 | id = boot_id_hex(de->d_name + 4); |
343 | if (id < 0) | |
9cde64ff LP |
344 | continue; |
345 | ||
346 | t = realloc(list, (count + 1) * sizeof(uint16_t)); | |
347 | if (!t) { | |
9db11a99 LP |
348 | r = -ENOMEM; |
349 | goto fail; | |
9cde64ff LP |
350 | } |
351 | ||
352 | list = t; | |
4d34c495 | 353 | list[count ++] = id; |
9cde64ff LP |
354 | } |
355 | ||
9db11a99 LP |
356 | qsort(list, count, sizeof(uint16_t), cmp_uint16); |
357 | ||
9cde64ff LP |
358 | *options = list; |
359 | return count; | |
9db11a99 LP |
360 | |
361 | fail: | |
362 | free(list); | |
363 | return r; | |
7b4d7cc0 KS |
364 | } |
365 | ||
5dbe9f53 | 366 | static int read_usec(sd_id128_t vendor, const char *name, usec_t *u) { |
2e3d0692 | 367 | _cleanup_free_ char *j = NULL; |
2e3d0692 LP |
368 | int r; |
369 | uint64_t x; | |
370 | ||
371 | assert(name); | |
372 | assert(u); | |
373 | ||
61cc634b | 374 | r = efi_get_variable_string(EFI_VENDOR_LOADER, name, &j); |
2e3d0692 LP |
375 | if (r < 0) |
376 | return r; | |
377 | ||
2e3d0692 LP |
378 | r = safe_atou64(j, &x); |
379 | if (r < 0) | |
380 | return r; | |
381 | ||
5dbe9f53 | 382 | *u = x; |
2e3d0692 LP |
383 | return 0; |
384 | } | |
385 | ||
386 | static int get_boot_usec(usec_t *firmware, usec_t *loader) { | |
387 | uint64_t x, y; | |
388 | int r; | |
2e3d0692 LP |
389 | |
390 | assert(firmware); | |
391 | assert(loader); | |
392 | ||
e9cea16d | 393 | r = read_usec(EFI_VENDOR_LOADER, "LoaderTimeInitUSec", &x); |
2e3d0692 LP |
394 | if (r < 0) |
395 | return r; | |
396 | ||
e9cea16d | 397 | r = read_usec(EFI_VENDOR_LOADER, "LoaderTimeExecUSec", &y); |
2e3d0692 LP |
398 | if (r < 0) |
399 | return r; | |
400 | ||
401 | if (y == 0 || y < x) | |
402 | return -EIO; | |
403 | ||
404 | if (y > USEC_PER_HOUR) | |
405 | return -EIO; | |
406 | ||
407 | *firmware = x; | |
408 | *loader = y; | |
409 | ||
410 | return 0; | |
411 | } | |
412 | ||
413 | int efi_get_boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader) { | |
414 | usec_t x, y, a; | |
415 | int r; | |
416 | dual_timestamp _n; | |
417 | ||
418 | assert(firmware); | |
419 | assert(loader); | |
420 | ||
421 | if (!n) { | |
422 | dual_timestamp_get(&_n); | |
423 | n = &_n; | |
424 | } | |
425 | ||
426 | r = get_boot_usec(&x, &y); | |
427 | if (r < 0) | |
428 | return r; | |
429 | ||
430 | /* Let's convert this to timestamps where the firmware | |
431 | * began/loader began working. To make this more confusing: | |
432 | * since usec_t is unsigned and the kernel's monotonic clock | |
433 | * begins at kernel initialization we'll actually initialize | |
434 | * the monotonic timestamps here as negative of the actual | |
435 | * value. */ | |
436 | ||
437 | firmware->monotonic = y; | |
438 | loader->monotonic = y - x; | |
439 | ||
440 | a = n->monotonic + firmware->monotonic; | |
441 | firmware->realtime = n->realtime > a ? n->realtime - a : 0; | |
442 | ||
443 | a = n->monotonic + loader->monotonic; | |
444 | loader->realtime = n->realtime > a ? n->realtime - a : 0; | |
445 | ||
446 | return 0; | |
447 | } | |
f4ce2b3e LP |
448 | |
449 | int efi_get_loader_device_part_uuid(sd_id128_t *u) { | |
f4ce2b3e | 450 | _cleanup_free_ char *p = NULL; |
f4ce2b3e LP |
451 | int r, parsed[16]; |
452 | unsigned i; | |
453 | ||
454 | assert(u); | |
455 | ||
61cc634b | 456 | r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderDevicePartUUID", &p); |
f4ce2b3e LP |
457 | if (r < 0) |
458 | return r; | |
459 | ||
f4ce2b3e LP |
460 | if (sscanf(p, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", |
461 | &parsed[0], &parsed[1], &parsed[2], &parsed[3], | |
462 | &parsed[4], &parsed[5], &parsed[6], &parsed[7], | |
463 | &parsed[8], &parsed[9], &parsed[10], &parsed[11], | |
464 | &parsed[12], &parsed[13], &parsed[14], &parsed[15]) != 16) | |
465 | return -EIO; | |
466 | ||
467 | for (i = 0; i < ELEMENTSOF(parsed); i++) | |
468 | u->bytes[i] = parsed[i]; | |
469 | ||
470 | return 0; | |
471 | } |