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