Coverage Report

Created: 2025-10-13 06:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rauc/src/bootloaders/efi.c
Line
Count
Source
1
#include "efi.h"
2
#include "bootchooser.h"
3
#include "context.h"
4
#include "utils.h"
5
6
0
#define EFIBOOTMGR_NAME "efibootmgr"
7
8
typedef struct {
9
  gchar *num;
10
  gchar *name;
11
  gboolean active;
12
} efi_bootentry;
13
14
static void efi_bootentry_free(efi_bootentry *entry)
15
0
{
16
0
  if (!entry)
17
0
    return;
18
19
0
  g_free(entry->num);
20
0
  g_free(entry->name);
21
0
  g_free(entry);
22
0
}
23
24
G_DEFINE_AUTOPTR_CLEANUP_FUNC(efi_bootentry, efi_bootentry_free);
25
26
static gboolean efi_bootorder_set(gchar *order, GError **error)
27
0
{
28
0
  g_autoptr(GSubprocess) sub = NULL;
29
0
  GError *ierror = NULL;
30
31
0
  g_return_val_if_fail(order, FALSE);
32
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
33
34
0
  sub = r_subprocess_new(G_SUBPROCESS_FLAGS_NONE, &ierror, EFIBOOTMGR_NAME,
35
0
      "--bootorder", order, NULL);
36
37
0
  if (!sub) {
38
0
    g_propagate_prefixed_error(
39
0
        error,
40
0
        ierror,
41
0
        "Failed to start " EFIBOOTMGR_NAME ": ");
42
0
    return FALSE;
43
0
  }
44
45
0
  if (!g_subprocess_wait_check(sub, NULL, &ierror)) {
46
0
    g_propagate_prefixed_error(
47
0
        error,
48
0
        ierror,
49
0
        "Failed to run " EFIBOOTMGR_NAME ": ");
50
0
    return FALSE;
51
0
  }
52
53
0
  return TRUE;
54
0
}
55
56
static gboolean efi_set_bootnext(gchar *bootnumber, GError **error)
57
0
{
58
0
  g_autoptr(GSubprocess) sub = NULL;
59
0
  GError *ierror = NULL;
60
61
0
  g_return_val_if_fail(bootnumber, FALSE);
62
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
63
64
0
  sub = r_subprocess_new(G_SUBPROCESS_FLAGS_NONE, &ierror, EFIBOOTMGR_NAME,
65
0
      "--bootnext", bootnumber, NULL);
66
67
0
  if (!sub) {
68
0
    g_propagate_prefixed_error(
69
0
        error,
70
0
        ierror,
71
0
        "Failed to start " EFIBOOTMGR_NAME ": ");
72
0
    return FALSE;
73
0
  }
74
75
0
  if (!g_subprocess_wait_check(sub, NULL, &ierror)) {
76
0
    g_propagate_prefixed_error(
77
0
        error,
78
0
        ierror,
79
0
        "Failed to run " EFIBOOTMGR_NAME ": ");
80
0
    return FALSE;
81
0
  }
82
83
0
  return TRUE;
84
0
}
85
86
static efi_bootentry *get_efi_entry_by_bootnum(GList *entries, const gchar *bootnum)
87
0
{
88
0
  efi_bootentry *found_entry = NULL;
89
90
0
  g_return_val_if_fail(entries, NULL);
91
0
  g_return_val_if_fail(bootnum, NULL);
92
93
0
  for (GList *entry = entries; entry != NULL; entry = entry->next) {
94
0
    efi_bootentry *ptr = entry->data;
95
0
    if (g_strcmp0(bootnum, ptr->num) == 0) {
96
0
      found_entry = ptr;
97
0
      break;
98
0
    }
99
0
  }
100
101
0
  return found_entry;
102
0
}
103
104
/* Parses output of efibootmgr and returns information obtained.
105
 *
106
 * Note that this function can return two lists, pointing to the same elements.
107
 * The allocated efi_bootentry structs are owned by the all_entries list, so
108
 * that parameter is mandatory.
109
 *
110
 * @param bootorder_entries Return location for List (of efi_bootentry
111
 *        elements) of slots that are currently in EFI 'BootOrder'
112
 * @param all_entries Return location for List (of efi_bootentry element) of
113
 *        all EFI boot entries
114
 * @param bootnext Return location for EFI boot slot currently selected as
115
 *        'BootNext' (if any)
116
 * @param error Return location for a GError
117
 */
118
static gboolean efi_bootorder_get(GList **bootorder_entries, GList **all_entries, efi_bootentry **bootnext, efi_bootentry **bootcurrent, GError **error)
119
0
{
120
0
  g_autoptr(GSubprocess) sub = NULL;
121
0
  GError *ierror = NULL;
122
0
  g_autoptr(GBytes) stdout_bytes = NULL;
123
0
  g_autofree gchar *stdout_str = NULL;
124
0
  gboolean res = FALSE;
125
0
  gint ret;
126
0
  GRegex *regex = NULL;
127
0
  GMatchInfo *match = NULL;
128
0
  g_autofree gchar *matched = NULL;
129
0
  g_autolist(efi_bootentry) entries = NULL;
130
0
  g_autoptr(GList) returnorder = NULL;
131
0
  g_auto(GStrv) bootnumorder = NULL;
132
133
0
  g_return_val_if_fail(bootorder_entries == NULL || *bootorder_entries == NULL, FALSE);
134
0
  g_return_val_if_fail(all_entries != NULL && *all_entries == NULL, FALSE);
135
0
  g_return_val_if_fail(bootnext == NULL || *bootnext == NULL, FALSE);
136
0
  g_return_val_if_fail(bootcurrent == NULL || *bootcurrent == NULL, FALSE);
137
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
138
139
0
  sub = r_subprocess_new(G_SUBPROCESS_FLAGS_STDOUT_PIPE, &ierror,
140
0
      EFIBOOTMGR_NAME, NULL);
141
0
  if (!sub) {
142
0
    g_propagate_prefixed_error(
143
0
        error,
144
0
        ierror,
145
0
        "Failed to start " EFIBOOTMGR_NAME ": ");
146
0
    goto out;
147
0
  }
148
149
0
  res = g_subprocess_communicate(sub, NULL, NULL, &stdout_bytes, NULL, &ierror);
150
0
  if (!res) {
151
0
    g_propagate_prefixed_error(
152
0
        error,
153
0
        ierror,
154
0
        EFIBOOTMGR_NAME " communication failed: ");
155
0
    goto out;
156
0
  }
157
158
0
  res = g_subprocess_get_if_exited(sub);
159
0
  if (!res) {
160
0
    g_set_error_literal(
161
0
        error,
162
0
        G_SPAWN_ERROR,
163
0
        G_SPAWN_ERROR_FAILED,
164
0
        EFIBOOTMGR_NAME " did not exit normally");
165
0
    goto out;
166
0
  }
167
168
0
  ret = g_subprocess_get_exit_status(sub);
169
0
  if (ret != 0) {
170
0
    g_set_error(
171
0
        error,
172
0
        G_SPAWN_EXIT_ERROR,
173
0
        ret,
174
0
        EFIBOOTMGR_NAME " failed with exit code: %i", ret);
175
0
    res = FALSE;
176
0
    goto out;
177
0
  }
178
179
0
  stdout_str = r_bytes_unref_to_string(&stdout_bytes);
180
181
  /* Obtain mapping of efi boot numbers to bootnames */
182
0
  regex = g_regex_new("^Boot([0-9a-fA-F]{4})[\\* ] (.+)$", G_REGEX_MULTILINE, 0, NULL);
183
0
  if (!g_regex_match(regex, stdout_str, 0, &match)) {
184
0
    g_set_error(
185
0
        error,
186
0
        R_BOOTCHOOSER_ERROR,
187
0
        R_BOOTCHOOSER_ERROR_FAILED,
188
0
        "Regex matching failed!");
189
0
    res = FALSE;
190
0
    goto out;
191
0
  }
192
193
0
  while (g_match_info_matches(match)) {
194
0
    gchar *tab_point = NULL;
195
0
    efi_bootentry *entry = g_new0(efi_bootentry, 1);
196
0
    entry->num = g_match_info_fetch(match, 1);
197
0
    entry->name = g_match_info_fetch(match, 2);
198
199
    /* Remove anything after a tab, as it is most likely path
200
     * information which we don't need */
201
0
    tab_point = strchr(entry->name, '\t');
202
0
    if (tab_point)
203
0
      *tab_point = '\0';
204
205
0
    entries = g_list_append(entries, entry);
206
0
    g_match_info_next(match, NULL);
207
0
    g_debug("Detected EFI boot entry %s: %s", entry->num, entry->name);
208
0
  }
209
210
0
  g_clear_pointer(&regex, g_regex_unref);
211
0
  g_clear_pointer(&match, g_match_info_free);
212
213
  /* Obtain bootnext */
214
0
  regex = g_regex_new("^BootNext: ([0-9a-fA-F]{4})$", G_REGEX_MULTILINE, 0, NULL);
215
0
  if (g_regex_match(regex, stdout_str, 0, &match)) {
216
0
    if (bootnext) {
217
0
      g_clear_pointer(&matched, g_free);
218
0
      matched = g_match_info_fetch(match, 1);
219
0
      *bootnext = get_efi_entry_by_bootnum(entries, matched);
220
0
    }
221
0
  }
222
223
0
  g_clear_pointer(&regex, g_regex_unref);
224
0
  g_clear_pointer(&match, g_match_info_free);
225
226
  /* Obtain bootorder */
227
0
  regex = g_regex_new("^BootOrder: (\\S+)$", G_REGEX_MULTILINE, 0, NULL);
228
0
  if (!g_regex_match(regex, stdout_str, 0, &match)) {
229
0
    g_set_error(
230
0
        error,
231
0
        R_BOOTCHOOSER_ERROR,
232
0
        R_BOOTCHOOSER_ERROR_FAILED,
233
0
        "unable to obtain boot order!");
234
0
    res = FALSE;
235
0
    goto out;
236
0
  }
237
238
0
  g_clear_pointer(&matched, g_free);
239
0
  matched = g_match_info_fetch(match, 1);
240
0
  bootnumorder = g_strsplit(matched, ",", 0);
241
242
  /* Iterate over boot entries list in bootorder */
243
0
  for (gchar **element = bootnumorder; *element; element++) {
244
0
    efi_bootentry *bentry = get_efi_entry_by_bootnum(entries, *element);
245
0
    if (bentry)
246
0
      returnorder = g_list_append(returnorder, bentry);
247
0
  }
248
249
0
  g_clear_pointer(&regex, g_regex_unref);
250
0
  g_clear_pointer(&match, g_match_info_free);
251
252
  /* Obtain boot current */
253
0
  regex = g_regex_new("^BootCurrent: ([0-9a-fA-F]{4})$", G_REGEX_MULTILINE, 0, NULL);
254
0
  if (g_regex_match(regex, stdout_str, 0, &match)) {
255
0
    if (bootcurrent) {
256
0
      g_clear_pointer(&matched, g_free);
257
0
      matched = g_match_info_fetch(match, 1);
258
0
      *bootcurrent = get_efi_entry_by_bootnum(entries, matched);
259
0
    }
260
0
  }
261
262
0
  if (bootorder_entries)
263
0
    *bootorder_entries = g_steal_pointer(&returnorder);
264
0
  *all_entries = g_steal_pointer(&entries);
265
266
0
out:
267
0
  g_clear_pointer(&regex, g_regex_unref);
268
0
  g_clear_pointer(&match, g_match_info_free);
269
270
0
  return res;
271
0
}
272
273
static gboolean efi_set_temp_primary(RaucSlot *slot, GError **error)
274
0
{
275
0
  g_autolist(efi_bootentry) entries = NULL;
276
0
  GError *ierror = NULL;
277
0
  efi_bootentry *efi_slot_entry = NULL;
278
279
0
  if (!efi_bootorder_get(NULL, &entries, NULL, NULL, &ierror)) {
280
0
    g_propagate_error(error, ierror);
281
0
    return FALSE;
282
0
  }
283
284
  /* Lookup efi boot entry matching slot */
285
0
  for (GList *entry = entries; entry != NULL; entry = entry->next) {
286
0
    efi_bootentry *efi = entry->data;
287
0
    if (g_strcmp0(efi->name, slot->bootname) == 0) {
288
0
      efi_slot_entry = efi;
289
0
      break;
290
0
    }
291
0
  }
292
293
0
  if (!efi_slot_entry) {
294
0
    g_set_error(
295
0
        error,
296
0
        R_BOOTCHOOSER_ERROR,
297
0
        R_BOOTCHOOSER_ERROR_FAILED,
298
0
        "Did not find efi entry for bootname '%s'!", slot->bootname);
299
0
    return FALSE;
300
0
  }
301
302
0
  if (!efi_set_bootnext(efi_slot_entry->num, &ierror)) {
303
0
    g_propagate_prefixed_error(error, ierror, "Setting bootnext failed: ");
304
0
    return FALSE;
305
0
  }
306
307
0
  return TRUE;
308
0
}
309
310
/* Deletes given slot from efi bootorder list.
311
 * Prepends it to bootorder list if prepend argument is set to TRUE */
312
static gboolean efi_modify_persistent_bootorder(RaucSlot *slot, gboolean prepend, GError **error)
313
0
{
314
0
  g_autoptr(GList) entries = NULL;
315
0
  g_autolist(efi_bootentry) all_entries = NULL;
316
0
  g_autoptr(GPtrArray) bootorder = NULL;
317
0
  g_autofree gchar *order = NULL;
318
0
  GError *ierror = NULL;
319
0
  efi_bootentry *efi_slot_entry = NULL;
320
321
0
  g_return_val_if_fail(slot, FALSE);
322
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
323
324
0
  if (!efi_bootorder_get(&entries, &all_entries, NULL, NULL, &ierror)) {
325
0
    g_propagate_error(error, ierror);
326
0
    return FALSE;
327
0
  }
328
329
  /* Iterate over bootorder list until reaching boot entry to remove (if available) */
330
0
  for (GList *entry = entries; entry != NULL; entry = entry->next) {
331
0
    efi_bootentry *efi = entry->data;
332
0
    if (g_strcmp0(efi->name, slot->bootname) == 0) {
333
0
      entries = g_list_remove(entries, efi);
334
0
      break;
335
0
    }
336
0
  }
337
338
0
  if (prepend) {
339
    /* Iterate over full list to get entry to prepend to bootorder */
340
0
    for (GList *entry = all_entries; entry != NULL; entry = entry->next) {
341
0
      efi_bootentry *efi = entry->data;
342
0
      if (g_strcmp0(efi->name, slot->bootname) == 0) {
343
0
        efi_slot_entry = efi;
344
0
        break;
345
0
      }
346
0
    }
347
348
0
    if (!efi_slot_entry) {
349
0
      g_set_error(
350
0
          error,
351
0
          R_BOOTCHOOSER_ERROR,
352
0
          R_BOOTCHOOSER_ERROR_FAILED,
353
0
          "No entry for bootname '%s' found", slot->bootname);
354
0
      return FALSE;
355
0
    }
356
357
0
    entries = g_list_prepend(entries, efi_slot_entry);
358
0
  }
359
360
0
  bootorder = g_ptr_array_sized_new(g_list_length(entries));
361
  /* Construct bootorder string out of boot entry list */
362
0
  for (GList *entry = entries; entry != NULL; entry = entry->next) {
363
0
    efi_bootentry *efi = entry->data;
364
0
    g_ptr_array_add(bootorder, efi->num);
365
0
  }
366
0
  g_ptr_array_add(bootorder, NULL);
367
368
0
  order = g_strjoinv(",", (gchar**)bootorder->pdata);
369
370
0
  if (!efi_bootorder_set(order, NULL)) {
371
0
    g_propagate_prefixed_error(error, ierror, "Modifying bootorder failed: ");
372
0
    return FALSE;
373
0
  }
374
375
0
  return TRUE;
376
0
}
377
378
gboolean r_efi_set_state(RaucSlot *slot, gboolean good, GError **error)
379
0
{
380
0
  GError *ierror = NULL;
381
382
0
  g_return_val_if_fail(slot, FALSE);
383
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
384
385
0
  if (!efi_modify_persistent_bootorder(slot, good, &ierror)) {
386
0
    g_propagate_error(error, ierror);
387
0
    return FALSE;
388
0
  }
389
390
0
  return TRUE;
391
0
}
392
393
RaucSlot *r_efi_get_primary(GError **error)
394
0
{
395
0
  g_autoptr(GList) bootorder_entries = NULL;
396
0
  g_autolist(efi_bootentry) all_entries = NULL;
397
0
  GError *ierror = NULL;
398
0
  efi_bootentry *bootnext = NULL;
399
0
  RaucSlot *primary = NULL;
400
0
  RaucSlot *slot;
401
0
  GHashTableIter iter;
402
403
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
404
405
0
  if (!efi_bootorder_get(&bootorder_entries, &all_entries, &bootnext, NULL, &ierror)) {
406
0
    g_propagate_error(error, ierror);
407
0
    return NULL;
408
0
  }
409
410
  /* We prepend the content of BootNext if set */
411
0
  if (bootnext) {
412
0
    g_debug("Detected BootNext set to %s", bootnext->name);
413
0
    bootorder_entries = g_list_prepend(bootorder_entries, bootnext);
414
0
  }
415
416
0
  for (GList *entry = bootorder_entries; entry != NULL; entry = entry->next) {
417
0
    efi_bootentry *bootentry = entry->data;
418
419
    /* Find matching slot entry */
420
0
    g_hash_table_iter_init(&iter, r_context()->config->slots);
421
0
    while (g_hash_table_iter_next(&iter, NULL, (gpointer*) &slot)) {
422
0
      if (g_strcmp0(bootentry->name, slot->bootname) == 0) {
423
0
        primary = slot;
424
0
        break;
425
0
      }
426
0
    }
427
428
0
    if (primary)
429
0
      break;
430
0
  }
431
432
0
  if (!primary) {
433
0
    g_set_error(
434
0
        error,
435
0
        R_BOOTCHOOSER_ERROR,
436
0
        R_BOOTCHOOSER_ERROR_PARSE_FAILED,
437
0
        "Did not find primary boot entry!");
438
0
    return NULL;
439
0
  }
440
441
0
  return primary;
442
0
}
443
444
gboolean r_efi_set_primary(RaucSlot *slot, GError **error)
445
0
{
446
0
  GError *ierror = NULL;
447
448
0
  g_return_val_if_fail(slot, FALSE);
449
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
450
451
0
  if (r_context()->config->efi_use_bootnext) {
452
0
    if (!efi_set_temp_primary(slot, &ierror)) {
453
0
      g_propagate_error(error, ierror);
454
0
      return FALSE;
455
0
    }
456
457
0
    return TRUE;
458
0
  }
459
460
0
  if (!efi_modify_persistent_bootorder(slot, TRUE, &ierror)) {
461
0
    g_propagate_error(error, ierror);
462
0
    return FALSE;
463
0
  }
464
465
0
  return TRUE;
466
0
}
467
468
/* We assume bootstate to be good if slot is listed in 'bootorder', otherwise
469
 * bad */
470
gboolean r_efi_get_state(RaucSlot *slot, gboolean *good, GError **error)
471
0
{
472
0
  efi_bootentry *found_entry = NULL;
473
0
  GError *ierror = NULL;
474
0
  g_autoptr(GList) bootorder_entries = NULL;
475
0
  g_autolist(efi_bootentry) all_entries = NULL;
476
477
0
  g_return_val_if_fail(slot, FALSE);
478
0
  g_return_val_if_fail(good, FALSE);
479
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
480
481
0
  if (!efi_bootorder_get(&bootorder_entries, &all_entries, NULL, NULL, &ierror)) {
482
0
    g_propagate_error(error, ierror);
483
0
    return FALSE;
484
0
  }
485
486
  /* Scan bootorder list for given slot */
487
0
  for (GList *entry = bootorder_entries; entry != NULL; entry = entry->next) {
488
0
    efi_bootentry *ptr = entry->data;
489
0
    if (g_strcmp0(slot->bootname, ptr->name) == 0) {
490
0
      found_entry = ptr;
491
0
      break;
492
0
    }
493
0
  }
494
495
0
  *good = found_entry ? TRUE : FALSE;
496
497
0
  return TRUE;
498
0
}
499
500
gchar *r_efi_get_current_bootname(RaucConfig *config, GError **error)
501
0
{
502
0
  g_autolist(efi_bootentry) all_entries = NULL;
503
0
  GError *ierror = NULL;
504
0
  efi_bootentry *bootcurrent = NULL;
505
0
  RaucSlot *slot = NULL;
506
0
  GHashTableIter iter;
507
508
0
  if (!efi_bootorder_get(NULL, &all_entries, NULL, &bootcurrent, &ierror)) {
509
0
    g_propagate_error(error, ierror);
510
0
    return NULL;
511
0
  }
512
513
0
  g_hash_table_iter_init(&iter, config->slots);
514
0
  while (g_hash_table_iter_next(&iter, NULL, (gpointer*) &slot)) {
515
0
    if (g_strcmp0(slot->bootname, bootcurrent->name) == 0) {
516
0
      return slot->bootname;
517
0
    }
518
0
  }
519
520
0
  g_set_error(error,
521
0
      R_BOOTCHOOSER_ERROR,
522
0
      R_BOOTCHOOSER_ERROR_FAILED,
523
0
      "Current EFI bootentry not known to rauc!");
524
525
  return NULL;
526
0
}