Coverage Report

Created: 2025-11-09 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rauc/src/bootloaders/barebox.c
Line
Count
Source
1
#include <errno.h>
2
3
#include "barebox.h"
4
#include "bootchooser.h"
5
#include "context.h"
6
#include "utils.h"
7
8
0
#define BAREBOX_STATE_NAME "barebox-state"
9
0
#define BAREBOX_STATE_DEFAULT_ATTEMPTS  3
10
0
#define BAREBOX_STATE_ATTEMPTS_PRIMARY  3
11
0
#define BAREBOX_STATE_DEFAULT_PRIORITY  10
12
0
#define BAREBOX_STATE_PRIORITY_PRIMARY  20
13
14
typedef struct {
15
  guint32 prio;
16
  guint32 attempts;
17
} BareboxSlotState;
18
19
0
#define BOOTSTATE_PREFIX "bootstate"
20
21
gchar *r_barebox_get_current_bootname(const gchar *cmdline, GError **error)
22
0
{
23
0
  g_return_val_if_fail(cmdline, NULL);
24
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
25
26
0
  return r_regex_match_simple(
27
0
      "(?:bootstate|bootchooser)\\.active=(\\S+)",
28
0
      cmdline);
29
0
}
30
31
static gboolean barebox_state_get(const gchar *bootname, BareboxSlotState *bb_state, GError **error)
32
0
{
33
0
  g_autoptr(GSubprocess) sub = NULL;
34
0
  GError *ierror = NULL;
35
0
  guint64 result[2] = {};
36
0
  g_autoptr(GPtrArray) args = g_ptr_array_new_full(6, g_free);
37
38
0
  g_return_val_if_fail(bootname, FALSE);
39
0
  g_return_val_if_fail(bb_state, FALSE);
40
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
41
42
0
  g_ptr_array_add(args, g_strdup(BAREBOX_STATE_NAME));
43
0
  if (r_context()->config->system_bb_statename) {
44
0
    g_ptr_array_add(args, g_strdup("-n"));
45
0
    g_ptr_array_add(args, g_strdup(r_context()->config->system_bb_statename));
46
0
  }
47
0
  g_ptr_array_add(args, g_strdup("-g"));
48
0
  g_ptr_array_add(args, g_strdup_printf(BOOTSTATE_PREFIX ".%s.priority", bootname));
49
0
  g_ptr_array_add(args, g_strdup("-g"));
50
0
  g_ptr_array_add(args, g_strdup_printf(BOOTSTATE_PREFIX ".%s.remaining_attempts", bootname));
51
0
  if (r_context()->config->system_bb_dtbpath) {
52
0
    g_ptr_array_add(args, g_strdup("-i"));
53
0
    g_ptr_array_add(args, g_strdup(r_context()->config->system_bb_dtbpath));
54
0
  }
55
0
  g_ptr_array_add(args, NULL);
56
57
0
  sub = r_subprocess_newv(args, G_SUBPROCESS_FLAGS_STDOUT_PIPE, &ierror);
58
0
  if (!sub) {
59
0
    g_propagate_prefixed_error(
60
0
        error,
61
0
        ierror,
62
0
        "Failed to start " BAREBOX_STATE_NAME ": ");
63
0
    return FALSE;
64
0
  }
65
66
0
  g_autoptr(GBytes) stdout_bytes = NULL;
67
0
  if (!g_subprocess_communicate(sub, NULL, NULL, &stdout_bytes, NULL, &ierror)) {
68
0
    g_propagate_prefixed_error(
69
0
        error,
70
0
        ierror,
71
0
        "Failed to run " BAREBOX_STATE_NAME ": ");
72
0
    return FALSE;
73
0
  }
74
75
0
  if (!g_subprocess_get_if_exited(sub)) {
76
0
    g_set_error_literal(
77
0
        error,
78
0
        G_SPAWN_ERROR,
79
0
        G_SPAWN_ERROR_FAILED,
80
0
        BAREBOX_STATE_NAME " did not exit normally");
81
0
    return FALSE;
82
0
  }
83
84
0
  gint ret = g_subprocess_get_exit_status(sub);
85
0
  if (ret != 0) {
86
0
    g_set_error(
87
0
        error,
88
0
        G_SPAWN_EXIT_ERROR,
89
0
        ret,
90
0
        BAREBOX_STATE_NAME " failed with exit code: %i", ret);
91
0
    return FALSE;
92
0
  }
93
94
0
  g_autofree gchar *stdout_str = r_bytes_unref_to_string(&stdout_bytes);
95
0
  g_auto(GStrv) outlines = g_strsplit (stdout_str, "\n", -1);
96
0
  for (int i = 0; i < 2; i++) {
97
0
    gchar *endptr = NULL;
98
0
    const gchar *outline = outlines[i];
99
0
    if (!outline) {
100
      /* Having no error set there was means no content to read */
101
0
      if (ierror == NULL) {
102
0
        g_set_error(
103
0
            error,
104
0
            R_BOOTCHOOSER_ERROR,
105
0
            R_BOOTCHOOSER_ERROR_PARSE_FAILED,
106
0
            "No content to read");
107
0
      } else {
108
0
        g_propagate_prefixed_error(
109
0
            error,
110
0
            ierror,
111
0
            "Failed parsing " BAREBOX_STATE_NAME " output: ");
112
0
      }
113
0
      return FALSE;
114
0
    }
115
116
0
    result[i] = g_ascii_strtoull(outline, &endptr, 10);
117
0
    if (result[i] == 0 && outline == endptr) {
118
0
      g_set_error(
119
0
          error,
120
0
          R_BOOTCHOOSER_ERROR,
121
0
          R_BOOTCHOOSER_ERROR_PARSE_FAILED,
122
0
          "Failed to parse value: '%s'", outline);
123
0
      return FALSE;
124
0
    } else if (result[i] == G_MAXUINT64 && errno != 0) {
125
0
      g_set_error(
126
0
          error,
127
0
          R_BOOTCHOOSER_ERROR,
128
0
          R_BOOTCHOOSER_ERROR_PARSE_FAILED,
129
0
          "Return value overflow: '%s', error: %d", outline, errno);
130
0
      return FALSE;
131
0
    }
132
0
  }
133
134
0
  bb_state->prio = result[0];
135
0
  bb_state->attempts = result[1];
136
137
0
  return TRUE;
138
0
}
139
140
/* names: list of gchar, values: list of gint */
141
static gboolean barebox_state_set(GPtrArray *pairs, GError **error)
142
0
{
143
0
  g_autoptr(GSubprocess) sub = NULL;
144
0
  GError *ierror = NULL;
145
0
  g_autoptr(GPtrArray) args = g_ptr_array_new_full(2*pairs->len+2, g_free);
146
147
0
  g_return_val_if_fail(pairs, FALSE);
148
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
149
150
0
  g_assert_cmpuint(pairs->len, >, 0);
151
152
0
  g_ptr_array_add(args, g_strdup(BAREBOX_STATE_NAME));
153
0
  if (r_context()->config->system_bb_statename) {
154
0
    g_ptr_array_add(args, g_strdup("-n"));
155
0
    g_ptr_array_add(args, g_strdup(r_context()->config->system_bb_statename));
156
0
  }
157
0
  for (guint i = 0; i < pairs->len; i++) {
158
0
    g_ptr_array_add(args, g_strdup("-s"));
159
0
    g_ptr_array_add(args, g_strdup(pairs->pdata[i]));
160
0
  }
161
0
  if (r_context()->config->system_bb_dtbpath) {
162
0
    g_ptr_array_add(args, g_strdup("-i"));
163
0
    g_ptr_array_add(args, g_strdup(r_context()->config->system_bb_dtbpath));
164
0
  }
165
0
  g_ptr_array_add(args, NULL);
166
167
0
  sub = r_subprocess_newv(args, G_SUBPROCESS_FLAGS_NONE, &ierror);
168
0
  if (!sub) {
169
0
    g_propagate_prefixed_error(
170
0
        error,
171
0
        ierror,
172
0
        "Failed to start " BAREBOX_STATE_NAME ": ");
173
0
    return FALSE;
174
0
  }
175
176
0
  if (!g_subprocess_wait_check(sub, NULL, &ierror)) {
177
0
    g_propagate_prefixed_error(
178
0
        error,
179
0
        ierror,
180
0
        "Failed to run " BAREBOX_STATE_NAME ": ");
181
0
    return FALSE;
182
0
  }
183
184
0
  return TRUE;
185
0
}
186
187
/* Set slot status values */
188
gboolean r_barebox_set_state(RaucSlot *slot, gboolean good, GError **error)
189
0
{
190
0
  GError *ierror = NULL;
191
0
  g_autoptr(GPtrArray) pairs = g_ptr_array_new_full(10, g_free);
192
0
  int attempts;
193
194
0
  g_return_val_if_fail(slot, FALSE);
195
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
196
197
0
  if (good) {
198
0
    attempts = r_context()->config->boot_default_attempts;
199
0
    if (attempts <= 0)
200
0
      attempts = BAREBOX_STATE_DEFAULT_ATTEMPTS;
201
0
  } else {
202
    /* For marking bad, also set priority to 0 */
203
0
    attempts = 0;
204
0
    g_ptr_array_add(pairs, g_strdup_printf(BOOTSTATE_PREFIX ".%s.priority=%i",
205
0
        slot->bootname, 0));
206
0
  }
207
208
0
  g_ptr_array_add(pairs, g_strdup_printf(BOOTSTATE_PREFIX ".%s.remaining_attempts=%i",
209
0
      slot->bootname, attempts));
210
211
0
  if (!barebox_state_set(pairs, &ierror)) {
212
0
    g_propagate_error(error, ierror);
213
0
    return FALSE;
214
0
  }
215
216
0
  return TRUE;
217
0
}
218
219
/* Get slot marked as primary one */
220
RaucSlot *r_barebox_get_primary(GError **error)
221
0
{
222
0
  RaucSlot *slot;
223
0
  GHashTableIter iter;
224
0
  RaucSlot *primary = NULL;
225
0
  guint32 top_prio = 0;
226
0
  GError *ierror = NULL;
227
228
0
  g_hash_table_iter_init(&iter, r_context()->config->slots);
229
0
  while (g_hash_table_iter_next(&iter, NULL, (gpointer*) &slot)) {
230
0
    BareboxSlotState state;
231
232
0
    if (!slot->bootname)
233
0
      continue;
234
235
0
    if (!barebox_state_get(slot->bootname, &state, &ierror)) {
236
0
      g_propagate_error(error, ierror);
237
0
      return NULL;
238
0
    }
239
240
0
    if (state.attempts == 0)
241
0
      continue;
242
243
    /* We search for the slot with highest priority */
244
0
    if (state.prio > top_prio) {
245
0
      primary = slot;
246
0
      top_prio = state.prio;
247
0
    }
248
0
  }
249
250
0
  if (!primary) {
251
0
    g_set_error_literal(
252
0
        error,
253
0
        R_BOOTCHOOSER_ERROR,
254
0
        R_BOOTCHOOSER_ERROR_PARSE_FAILED,
255
0
        "Unable to obtain primary element");
256
0
  }
257
258
0
  return primary;
259
0
}
260
261
/* We assume a slot to be 'good' if its priority is > 0 AND its remaining
262
 * attempts counter is > 0 */
263
gboolean r_barebox_get_state(RaucSlot *slot, gboolean *good, GError **error)
264
0
{
265
0
  BareboxSlotState state;
266
0
  GError *ierror = NULL;
267
268
0
  if (!barebox_state_get(slot->bootname, &state, &ierror)) {
269
0
    g_propagate_error(error, ierror);
270
0
    return FALSE;
271
0
  }
272
273
0
  if (state.prio > 0)
274
0
    *good = (state.attempts > 0) ? TRUE : FALSE;
275
0
  else
276
0
    *good = FALSE;
277
278
0
  return TRUE;
279
0
}
280
281
/* Set slot as primary boot slot */
282
gboolean r_barebox_set_primary(RaucSlot *slot, GError **error)
283
0
{
284
0
  g_autoptr(GPtrArray) pairs = g_ptr_array_new_full(10, g_free);
285
0
  GError *ierror = NULL;
286
0
  g_autoptr(GList) slots = NULL;
287
0
  int attempts;
288
289
0
  g_return_val_if_fail(slot, FALSE);
290
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
291
292
  /* Iterate over class members */
293
0
  slots = g_hash_table_get_values(r_context()->config->slots);
294
0
  for (GList *l = slots; l != NULL; l = l->next) {
295
0
    RaucSlot *s = l->data;
296
0
    int prio;
297
0
    BareboxSlotState bb_state;
298
299
0
    if (!s->bootname)
300
0
      continue;
301
302
0
    if (!barebox_state_get(s->bootname, &bb_state, &ierror)) {
303
0
      g_propagate_error(error, ierror);
304
0
      return FALSE;
305
0
    }
306
307
0
    if (s == slot) {
308
0
      prio = BAREBOX_STATE_PRIORITY_PRIMARY;
309
0
    } else {
310
0
      if (bb_state.prio == 0)
311
0
        prio = 0;
312
0
      else
313
0
        prio = BAREBOX_STATE_DEFAULT_PRIORITY;
314
0
    }
315
0
    g_ptr_array_add(pairs, g_strdup_printf(BOOTSTATE_PREFIX ".%s.priority=%i",
316
0
        s->bootname, prio));
317
0
  }
318
319
0
  attempts = r_context()->config->boot_attempts_primary;
320
0
  if (attempts <= 0)
321
0
    attempts = BAREBOX_STATE_ATTEMPTS_PRIMARY;
322
323
0
  g_ptr_array_add(pairs, g_strdup_printf(BOOTSTATE_PREFIX ".%s.remaining_attempts=%i",
324
0
      slot->bootname, attempts));
325
326
0
  if (!barebox_state_set(pairs, &ierror)) {
327
0
    g_propagate_error(error, ierror);
328
0
    return FALSE;
329
0
  }
330
331
0
  return TRUE;
332
0
}