/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 | } |