Coverage Report

Created: 2026-02-27 06:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rauc/src/bootloaders/uboot.c
Line
Count
Source
1
#include "uboot.h"
2
#include "bootchooser.h"
3
#include "context.h"
4
#include "utils.h"
5
6
0
#define UBOOT_FWSETENV_NAME "fw_setenv"
7
0
#define UBOOT_FWPRINTENV_NAME "fw_printenv"
8
0
#define UBOOT_DEFAULT_ATTEMPTS  3
9
0
#define UBOOT_ATTEMPTS_PRIMARY  3
10
11
static gboolean uboot_env_get(const gchar *key, GString **value, GError **error)
12
0
{
13
0
  g_autoptr(GSubprocess) sub = NULL;
14
0
  GError *ierror = NULL;
15
0
  g_autoptr(GBytes) stdout_bytes = NULL;
16
0
  const char *data;
17
0
  gsize offset;
18
0
  gsize size;
19
0
  gint ret;
20
21
0
  g_return_val_if_fail(key, FALSE);
22
0
  g_return_val_if_fail(value && *value == NULL, FALSE);
23
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
24
25
0
  sub = r_subprocess_new(G_SUBPROCESS_FLAGS_STDOUT_PIPE, &ierror,
26
0
      UBOOT_FWPRINTENV_NAME, key, NULL);
27
0
  if (!sub) {
28
0
    g_propagate_prefixed_error(
29
0
        error,
30
0
        ierror,
31
0
        "Failed to start " UBOOT_FWPRINTENV_NAME ": ");
32
0
    return FALSE;
33
0
  }
34
35
0
  if (!g_subprocess_communicate(sub, NULL, NULL, &stdout_bytes, NULL, &ierror)) {
36
0
    g_propagate_prefixed_error(
37
0
        error,
38
0
        ierror,
39
0
        "Failed to run " UBOOT_FWPRINTENV_NAME ": ");
40
0
    return FALSE;
41
0
  }
42
43
0
  if (!g_subprocess_get_if_exited(sub)) {
44
0
    g_set_error_literal(
45
0
        error,
46
0
        G_SPAWN_ERROR,
47
0
        G_SPAWN_ERROR_FAILED,
48
0
        UBOOT_FWPRINTENV_NAME " did not exit normally");
49
0
    return FALSE;
50
0
  }
51
52
0
  ret = g_subprocess_get_exit_status(sub);
53
0
  if (ret != 0) {
54
0
    g_set_error(
55
0
        error,
56
0
        G_SPAWN_EXIT_ERROR,
57
0
        ret,
58
0
        UBOOT_FWPRINTENV_NAME " failed with exit code: %i", ret);
59
0
    return FALSE;
60
0
  }
61
62
  /* offset is composed of key + equal sign, e.g. 'BOOT_ORDER=A B R' */
63
0
  offset = strlen(key) + 1;
64
0
  data = g_bytes_get_data(stdout_bytes, &size);
65
0
  *value = g_string_new_len(data + offset, size - offset);
66
0
  g_strchomp((*value)->str);
67
68
0
  return TRUE;
69
0
}
70
71
static gboolean uboot_env_set(const gchar *key, const gchar *value, GError **error)
72
0
{
73
0
  g_autoptr(GSubprocess) sub = NULL;
74
0
  GError *ierror = NULL;
75
76
0
  g_return_val_if_fail(key, FALSE);
77
0
  g_return_val_if_fail(value, FALSE);
78
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
79
80
0
  sub = r_subprocess_new(G_SUBPROCESS_FLAGS_NONE, &ierror, UBOOT_FWSETENV_NAME,
81
0
      key, value, NULL);
82
0
  if (!sub) {
83
0
    g_propagate_prefixed_error(
84
0
        error,
85
0
        ierror,
86
0
        "Failed to start " UBOOT_FWSETENV_NAME ": ");
87
0
    return FALSE;
88
0
  }
89
90
0
  if (!g_subprocess_wait_check(sub, NULL, &ierror)) {
91
0
    g_propagate_prefixed_error(
92
0
        error,
93
0
        ierror,
94
0
        "Failed to run " UBOOT_FWSETENV_NAME ": ");
95
0
    return FALSE;
96
0
  }
97
98
0
  return TRUE;
99
0
}
100
101
/* We assume bootstate to be good if slot is listed in 'BOOT_ORDER' and its
102
 * remaining attempts counter is > 0 */
103
gboolean r_uboot_get_state(RaucSlot *slot, gboolean *good, GError **error)
104
0
{
105
0
  g_autoptr(GString) order = NULL;
106
0
  g_autoptr(GString) attempts = NULL;
107
0
  g_auto(GStrv) bootnames = NULL;
108
0
  g_autofree gchar *key = NULL;
109
0
  GError *ierror = NULL;
110
0
  gboolean found = FALSE;
111
112
0
  g_return_val_if_fail(slot, FALSE);
113
0
  g_return_val_if_fail(good, FALSE);
114
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
115
116
0
  if (!uboot_env_get("BOOT_ORDER", &order, &ierror)) {
117
0
    g_propagate_error(error, ierror);
118
0
    return FALSE;
119
0
  }
120
121
  /* Scan boot order list for given slot */
122
0
  bootnames = g_strsplit(order->str, " ", -1);
123
0
  for (gchar **bootname = bootnames; *bootname; bootname++) {
124
0
    if (g_strcmp0(*bootname, slot->bootname) == 0) {
125
0
      found = TRUE;
126
0
      break;
127
0
    }
128
0
  }
129
0
  if (!found) {
130
0
    *good = FALSE;
131
0
    return TRUE;
132
0
  }
133
134
  /* Check remaining attempts */
135
0
  key = g_strdup_printf("BOOT_%s_LEFT", slot->bootname);
136
0
  if (!uboot_env_get(key, &attempts, &ierror)) {
137
0
    g_propagate_error(error, ierror);
138
0
    return FALSE;
139
0
  }
140
0
  *good = (g_ascii_strtoull(attempts->str, NULL, 16) > 0) ? TRUE : FALSE;
141
142
0
  return TRUE;
143
0
}
144
145
/* Set slot status values */
146
gboolean r_uboot_set_state(RaucSlot *slot, gboolean good, GError **error)
147
0
{
148
0
  GError *ierror = NULL;
149
0
  g_autofree gchar *key = NULL;
150
0
  g_autofree gchar *val = NULL;
151
0
  gint attempts = 0;
152
153
0
  g_return_val_if_fail(slot, FALSE);
154
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
155
156
0
  if (!good) {
157
0
    g_autoptr(GString) order_current = NULL;
158
0
    g_autoptr(GPtrArray) order_new = NULL;
159
0
    g_auto(GStrv) bootnames = NULL;
160
0
    g_autofree gchar *order = NULL;
161
162
0
    if (!uboot_env_get("BOOT_ORDER", &order_current, &ierror)) {
163
0
      g_message("Unable to obtain BOOT_ORDER: %s", ierror->message);
164
0
      g_clear_error(&ierror);
165
0
      goto set_left;
166
0
    }
167
168
0
    order_new = g_ptr_array_new();
169
    /* Iterate over current boot order */
170
0
    bootnames = g_strsplit(order_current->str, " ", -1);
171
0
    for (gchar **bootname = bootnames; *bootname; bootname++) {
172
      /* Skip selected slot, as we want it to be removed */
173
0
      if (g_strcmp0(*bootname, slot->bootname) == 0)
174
0
        continue;
175
176
      /* Skip empty strings from head or tail */
177
0
      if (g_strcmp0(*bootname, "") == 0)
178
0
        continue;
179
180
0
      g_ptr_array_add(order_new, *bootname);
181
0
    }
182
0
    g_ptr_array_add(order_new, NULL);
183
184
0
    order = g_strjoinv(" ", (gchar**) order_new->pdata);
185
0
    if (!uboot_env_set("BOOT_ORDER", order, &ierror)) {
186
0
      g_propagate_error(error, ierror);
187
0
      return FALSE;
188
0
    }
189
0
  }
190
191
0
set_left:
192
193
0
  key = g_strdup_printf("BOOT_%s_LEFT", slot->bootname);
194
195
0
  if (good) {
196
0
    attempts = r_context()->config->boot_default_attempts;
197
0
    if (attempts <= 0)
198
0
      attempts = UBOOT_DEFAULT_ATTEMPTS;
199
0
  }
200
201
0
  val = g_strdup_printf("%x", attempts);
202
203
0
  if (!uboot_env_set(key, val, &ierror)) {
204
0
    g_propagate_error(error, ierror);
205
0
    return FALSE;
206
0
  }
207
208
0
  return TRUE;
209
0
}
210
211
/* Get slot marked as primary one */
212
RaucSlot *r_uboot_get_primary(GError **error)
213
0
{
214
0
  g_autoptr(GString) order = NULL;
215
0
  g_auto(GStrv) bootnames = NULL;
216
0
  GError *ierror = NULL;
217
0
  RaucSlot *primary = NULL;
218
0
  RaucSlot *slot;
219
0
  GHashTableIter iter;
220
221
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
222
223
0
  if (!uboot_env_get("BOOT_ORDER", &order, &ierror)) {
224
0
    g_propagate_error(error, ierror);
225
0
    return NULL;
226
0
  }
227
228
  /* Iterate over current boot order */
229
0
  bootnames = g_strsplit(order->str, " ", -1);
230
0
  for (gchar **bootname = bootnames; *bootname; bootname++) {
231
    /* find matching slot entry */
232
0
    g_hash_table_iter_init(&iter, r_context()->config->slots);
233
0
    while (g_hash_table_iter_next(&iter, NULL, (gpointer*) &slot)) {
234
0
      g_autofree gchar *key = NULL;
235
0
      g_autoptr(GString) attempts = NULL;
236
237
0
      if (g_strcmp0(*bootname, slot->bootname) != 0)
238
0
        continue;
239
240
      /* Check that > 0 attempts left */
241
0
      key = g_strdup_printf("BOOT_%s_LEFT", slot->bootname);
242
0
      if (!uboot_env_get(key, &attempts, &ierror)) {
243
0
        g_propagate_error(error, ierror);
244
0
        return NULL;
245
0
      }
246
247
0
      if (g_ascii_strtoull(attempts->str, NULL, 16) <= 0)
248
0
        continue;
249
250
0
      primary = slot;
251
0
      break;
252
0
    }
253
254
0
    if (primary)
255
0
      break;
256
0
  }
257
258
0
  if (!primary) {
259
0
    g_set_error_literal(
260
0
        error,
261
0
        R_BOOTCHOOSER_ERROR,
262
0
        R_BOOTCHOOSER_ERROR_PARSE_FAILED,
263
0
        "Unable to find primary boot slot");
264
0
  }
265
266
0
  return primary;
267
0
}
268
269
/* Set slot as primary boot slot */
270
gboolean r_uboot_set_primary(RaucSlot *slot, GError **error)
271
0
{
272
0
  g_autoptr(GString) order_new = NULL;
273
0
  g_autoptr(GString) order_current = NULL;
274
0
  g_auto(GStrv) bootnames = NULL;
275
0
  GError *ierror = NULL;
276
0
  g_autofree gchar *key = NULL;
277
0
  g_autofree gchar *val = NULL;
278
0
  gint attempts;
279
280
0
  g_return_val_if_fail(slot, FALSE);
281
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
282
283
  /* Add updated slot as first entry in new boot order */
284
0
  order_new = g_string_new(slot->bootname);
285
286
0
  if (!uboot_env_get("BOOT_ORDER", &order_current, &ierror)) {
287
0
    g_message("Unable to obtain BOOT_ORDER (%s), using defaults", ierror->message);
288
0
    g_clear_error(&ierror);
289
290
0
    order_current = r_bootchooser_order_primary(slot);
291
0
  }
292
293
  /* Iterate over current boot order */
294
0
  bootnames = g_strsplit(order_current->str, " ", -1);
295
0
  for (gchar **bootname = bootnames; *bootname; bootname++) {
296
    /* Skip updated slot, as it is already at the beginning */
297
0
    if (g_strcmp0(*bootname, slot->bootname) == 0)
298
0
      continue;
299
300
    /* Skip empty strings from head or tail */
301
0
    if (g_strcmp0(*bootname, "") == 0)
302
0
      continue;
303
304
0
    g_string_append_c(order_new, ' ');
305
0
    g_string_append(order_new, *bootname);
306
0
  }
307
308
0
  key = g_strdup_printf("BOOT_%s_LEFT", slot->bootname);
309
310
0
  attempts = r_context()->config->boot_attempts_primary;
311
0
  if (attempts <= 0)
312
0
    attempts = UBOOT_ATTEMPTS_PRIMARY;
313
314
0
  val = g_strdup_printf("%x", attempts);
315
316
0
  if (!uboot_env_set(key, val, &ierror)) {
317
0
    g_propagate_error(error, ierror);
318
0
    return FALSE;
319
0
  }
320
0
  if (!uboot_env_set("BOOT_ORDER", order_new->str, &ierror)) {
321
0
    g_propagate_error(error, ierror);
322
0
    return FALSE;
323
0
  }
324
325
0
  return TRUE;
326
0
}