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