-
Notifications
You must be signed in to change notification settings - Fork 5
/
eos-update-efi-uuid.c
393 lines (336 loc) · 9.96 KB
/
eos-update-efi-uuid.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
/*
* Copyright 2024 Endless OS Foundation LLC
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <efivar.h>
#include <efiboot.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <unistd.h>
#include <getopt.h>
#define LOAD_OPTION_ACTIVE 0x00000001
static bool opt_verbose = false;
static bool opt_dry_run = false;
static const char *short_options = "vnh";
static struct option long_options[] = {
{"verbose", 0, 0, 'v'},
{"dry-run", 0, 0, 'n'},
{"help", 0, 0, 'h'},
{0, 0, 0, 0}
};
/* This and the cleanup_free macro are ripped off from g_autofree. */
static void
_cleanup_free (void *pp)
{
void *p = *(void **)pp;
free (p);
}
#define cleanup_free __attribute__ ((cleanup (_cleanup_free)))
/* Check if an EFI variable is a BootXXXX load option. */
static bool
is_load_option (const efi_guid_t *guid, const char *name)
{
if (guid == NULL || name == NULL)
{
warnx ("%s: guid or name is NULL", __func__);
return false;
}
/* Check that this is a global EFI variable. */
if (memcmp (guid, &efi_guid_global, sizeof (efi_guid_t)) != 0)
return false;
/* Check that the name matches BootXXXX. */
if (strlen (name) != 8 ||
strncmp (name, "Boot", 4) != 0 ||
isxdigit (name[4]) == 0 ||
isxdigit (name[5]) == 0 ||
isxdigit (name[6]) == 0 ||
isxdigit (name[7]) == 0)
return false;
return true;
}
/* Read an EFI variable and parse it into an efi_load_option when appropriate.
* Returns false if the variable is not a load option.
*/
static bool
read_load_option (const efi_guid_t *guid,
const char *name,
efi_load_option **out_opt,
size_t *out_opt_size,
uint32_t *out_attributes)
{
if (guid == NULL || name == NULL)
{
errno = EINVAL;
warnx ("%s: guid or name is NULL", __func__);
return false;
}
cleanup_free uint8_t *data = NULL;
size_t data_size = 0;
uint32_t attributes = 0;
if (efi_get_variable (*guid, name, &data, &data_size, &attributes) < 0)
{
warn ("efi_get_variable");
return false;
}
efi_load_option *opt = (efi_load_option *)data;
if (!efi_loadopt_is_valid (opt, data_size))
{
errno = EINVAL;
warn ("efi_loadopt_is_valid");
return false;
}
if (out_opt)
{
*out_opt = opt;
data = NULL;
}
if (out_opt_size)
*out_opt_size = data_size;
if (out_attributes)
*out_attributes = attributes;
return true;
}
/* A very minimal hexdump. */
static void
hexdump (const uint8_t *data, size_t size)
{
const uint8_t *cur;
size_t offset;
for (cur = data, offset = 0; offset < size; cur++, offset++)
{
const char *prefix;
if (offset % 16 == 0)
prefix = offset == 0 ? "" : "\n";
else if (offset % 8 == 0)
prefix = " ";
else
prefix = " ";
printf ("%s%.2x", prefix, *cur);
}
putchar ('\n');
}
/* Check if an EFI load option matches a partition UUID. */
static bool
load_option_matches_partition (efi_load_option *opt,
size_t opt_size,
efi_guid_t *part_uuid)
{
if (opt == NULL || part_uuid == NULL)
{
errno = EINVAL;
warnx ("%s: opt or part_uuid is NULL", __func__);
return false;
}
efidp dp = efi_loadopt_path (opt, opt_size);
if (dp == NULL)
{
errno = EINVAL;
warn ("efi_loadopt_path");
return false;
}
/* Only Hard Drive Media Device Paths are supported. */
if (efidp_type (dp) != EFIDP_MEDIA_TYPE ||
efidp_subtype (dp) != EFIDP_MEDIA_HD)
return false;
/* Only GPT formatted hard drives with UUID signatures are supported. */
efidp_hd *hd = (efidp_hd *)dp;
if (hd->format != EFIDP_HD_FORMAT_GPT ||
hd->signature_type != EFIDP_HD_SIGNATURE_GUID)
return false;
if (memcmp (hd->signature, part_uuid, sizeof (efi_guid_t)) != 0)
return false;
return true;
}
/* Dump a load option to stdout. A single line summary similar to efibootmgr is
* provided followed by a hexdump of the load option.
*/
static bool
dump_load_option (const char *name,
efi_load_option *opt,
size_t opt_size)
{
if (name == NULL || opt == NULL)
{
errno = EINVAL;
warnx ("%s: name or opt is NULL", __func__);
return false;
}
const unsigned char *desc = efi_loadopt_desc (opt, opt_size);
efidp dp = efi_loadopt_path (opt, opt_size);
if (dp == NULL)
{
errno = EINVAL;
warn ("efi_loadopt_path");
return false;
}
uint16_t dp_len = efi_loadopt_pathlen (opt, opt_size);
if (dp <= 0)
{
errno = EINVAL;
warn ("efi_loadopt_pathlen");
return false;
}
ssize_t len = efidp_format_device_path (NULL, 0, dp, dp_len);
if (len < 0)
{
errno = EINVAL;
warn ("efi_format_device_path");
return false;
}
size_t path_len = len + 1;
cleanup_free char *path = calloc (path_len, sizeof (char));
if (path == NULL)
return false;
len = efidp_format_device_path (path, path_len, dp, dp_len);
if (len < 0)
{
errno = EINVAL;
warn ("efi_format_device_path");
return false;
}
printf ("%s: %s%s %s\n",
name,
(efi_loadopt_attrs(opt) & LOAD_OPTION_ACTIVE) ? "* " : "",
desc,
path);
hexdump ((const uint8_t *)opt, opt_size);
return true;
}
static bool
update_load_option_partition (efi_load_option *opt,
size_t opt_size,
efi_guid_t *part_uuid)
{
if (opt == NULL || part_uuid == NULL)
{
errno = EINVAL;
warnx ("%s: opt or part_uuid is NULL", __func__);
return false;
}
efidp dp = efi_loadopt_path (opt, opt_size);
if (dp == NULL)
{
errno = EINVAL;
warn ("efi_loadopt_path");
return false;
}
/* Make sure this is a Hard Drive Media Device Path before updating. */
if (efidp_type (dp) != EFIDP_MEDIA_TYPE ||
efidp_subtype (dp) != EFIDP_MEDIA_HD)
{
errno = EINVAL;
warnx ("Only Hard Drive Media Device Paths can be updated");
return false;
}
/* Make sure this is a GPT formatted drive with a UUID signature. */
efidp_hd *hd = (efidp_hd *)dp;
if (hd->format != EFIDP_HD_FORMAT_GPT ||
hd->signature_type != EFIDP_HD_SIGNATURE_GUID)
{
errno = EINVAL;
warnx ("Only GPT formatted hard drives with UUID signatures can be updated");
return false;
}
/* Finally, update the signature UUID. */
memmove (hd->signature, part_uuid, sizeof (efi_guid_t));
return true;
}
static void
usage (const char *progname)
{
printf ("Usage: %s [OPTION]... CUR_UUID NEW_UUID\n"
"\n"
"Update all BootXXXX options using partition CUR_UUID to NEW_UUID.\n"
"\n"
" -v, --verbose\tprint verbose messages\n"
" -n, --dry-run\tonly show what would be done\n"
" -h, --help\tdisplay this help and exit\n",
progname);
}
int
main (int argc, char *argv[])
{
while (true)
{
int opt = getopt_long (argc, argv, short_options, long_options, NULL);
if (opt == -1)
break;
switch (opt)
{
case 'v':
opt_verbose = true;
break;
case 'n':
opt_dry_run = true;
break;
case 'h':
usage (argv[0]);
return 0;
default:
return 1;
}
}
argc -= optind;
argv += optind;
if (argc < 2)
errx (EXIT_FAILURE, "No partition UUIDs supplied");
const char *cur_part_uuid_str = argv[0];
efi_guid_t cur_part_uuid = { 0 };
if (efi_str_to_guid (cur_part_uuid_str, &cur_part_uuid) < 0)
errx (EXIT_FAILURE, "Invalid partition UUID \"%s\"", cur_part_uuid_str);
const char *new_part_uuid_str = argv[1];
efi_guid_t new_part_uuid = { 0 };
if (efi_str_to_guid (new_part_uuid_str, &new_part_uuid) < 0)
errx (EXIT_FAILURE, "Invalid partition UUID \"%s\"", new_part_uuid_str);
/* Iterate all EFI variables looking for load options to update. */
while (true)
{
efi_guid_t *guid = NULL;
char *name = NULL;
int ret = efi_get_next_variable_name (&guid, &name);
if (ret < 0)
err (EXIT_FAILURE, "Getting next EFI variable");
else if (ret == 0)
break;
if (guid == NULL || name == NULL)
errx (EXIT_FAILURE, "efi_get_next_variable_name returned NULL guid or name");
if (!is_load_option (guid, name))
{
if (opt_verbose)
printf ("Variable %s is not a load option\n", name);
continue;
}
cleanup_free efi_load_option *opt = NULL;
size_t opt_size = 0;
uint32_t attributes = 0;
if (!read_load_option (guid, name, &opt, &opt_size, &attributes))
err (EXIT_FAILURE, "Reading load option %s", name);
errno = 0;
if (!load_option_matches_partition (opt, opt_size, &cur_part_uuid))
{
if (errno != 0)
err (EXIT_FAILURE, "Matching load option %s partition", name);
if (opt_verbose)
printf ("Load option %s does not match partition %s\n",
name, cur_part_uuid_str);
continue;
}
if (opt_verbose && !dump_load_option (name, opt, opt_size))
err (EXIT_FAILURE, "Dump load option %s", name);
if (!update_load_option_partition (opt, opt_size, &new_part_uuid))
err (EXIT_FAILURE, "Updating load option %s partition", name);
if (opt_verbose && !dump_load_option (name, opt, opt_size))
err (EXIT_FAILURE, "Dump load option %s", name);
printf ("Updating %s HD UUID from %s to %s\n",
name, cur_part_uuid_str, new_part_uuid_str);
if (!opt_dry_run)
if (efi_set_variable (*guid, name, (uint8_t *)opt, opt_size, attributes, 0644) < 0)
err (EXIT_FAILURE, "Setting load option %s", name);
}
return 0;
}