blob: 4750c2306d7989d23c70cde76e548a1c86ac3766
1 | /* |
2 | * Copyright (C) 2016 The Android Open Source Project |
3 | * |
4 | * Permission is hereby granted, free of charge, to any person |
5 | * obtaining a copy of this software and associated documentation |
6 | * files (the "Software"), to deal in the Software without |
7 | * restriction, including without limitation the rights to use, copy, |
8 | * modify, merge, publish, distribute, sublicense, and/or sell copies |
9 | * of the Software, and to permit persons to whom the Software is |
10 | * furnished to do so, subject to the following conditions: |
11 | * |
12 | * The above copyright notice and this permission notice shall be |
13 | * included in all copies or substantial portions of the Software. |
14 | * |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
19 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
22 | * SOFTWARE. |
23 | */ |
24 | #define LOG_TAG "control_avb" |
25 | |
26 | #include <log/log.h> |
27 | #include "avb_ab_flow.h" |
28 | #include <stdio.h> |
29 | |
30 | bool avb_ab_data_verify_and_byteswap(const AvbABData* src, AvbABData* dest) { |
31 | /* Ensure magic is correct. */ |
32 | if (avb_safe_memcmp(src->magic, AVB_AB_MAGIC, AVB_AB_MAGIC_LEN) != 0) { |
33 | ALOGE("Magic is incorrect.\n"); |
34 | return false; |
35 | } |
36 | |
37 | avb_memcpy(dest, src, sizeof(AvbABData)); |
38 | dest->crc32 = avb_be32toh(dest->crc32); |
39 | |
40 | /* Ensure we don't attempt to access any fields if the major version |
41 | * is not supported. |
42 | */ |
43 | if (dest->version_major > AVB_AB_MAJOR_VERSION) { |
44 | ALOGE("No support for given major version.\n"); |
45 | return false; |
46 | } |
47 | |
48 | /* Bail if CRC32 doesn't match. */ |
49 | if (dest->crc32 != |
50 | avb_crc32((const uint8_t*)dest, sizeof(AvbABData) - sizeof(uint32_t))) { |
51 | ALOGE("CRC32 does not match.\n"); |
52 | return false; |
53 | } |
54 | |
55 | return true; |
56 | } |
57 | |
58 | void avb_ab_data_update_crc_and_byteswap(const AvbABData* src, |
59 | AvbABData* dest) { |
60 | avb_memcpy(dest, src, sizeof(AvbABData)); |
61 | dest->crc32 = avb_htobe32( |
62 | avb_crc32((const uint8_t*)dest, sizeof(AvbABData) - sizeof(uint32_t))); |
63 | } |
64 | |
65 | void avb_ab_data_init(AvbABData* data) { |
66 | avb_memset(data, '\0', sizeof(AvbABData)); |
67 | avb_memcpy(data->magic, AVB_AB_MAGIC, AVB_AB_MAGIC_LEN); |
68 | data->version_major = AVB_AB_MAJOR_VERSION; |
69 | data->version_minor = AVB_AB_MINOR_VERSION; |
70 | data->slots[0].priority = AVB_AB_MAX_PRIORITY; |
71 | data->slots[0].tries_remaining = AVB_AB_MAX_TRIES_REMAINING; |
72 | data->slots[0].successful_boot = 0; |
73 | data->slots[1].priority = AVB_AB_MAX_PRIORITY - 1; |
74 | data->slots[1].tries_remaining = AVB_AB_MAX_TRIES_REMAINING; |
75 | data->slots[1].successful_boot = 0; |
76 | } |
77 | |
78 | /* The AvbABData struct is stored 2048 bytes into the 'misc' partition |
79 | * following the 'struct bootloader_message' field. The struct is |
80 | * compatible with the guidelines in bootable/recovery/bootloader.h - |
81 | * e.g. it is stored in the |slot_suffix| field, starts with a |
82 | * NUL-byte, and is 32 bytes long. |
83 | */ |
84 | #define AB_METADATA_MISC_PARTITION_OFFSET 2048 |
85 | |
86 | AvbIOResult avb_ab_data_read(AvbABOps* ab_ops, AvbABData* data) { |
87 | AvbOps* ops = ab_ops->ops; |
88 | AvbABData serialized; |
89 | AvbIOResult io_ret; |
90 | size_t num_bytes_read; |
91 | |
92 | io_ret = ops->read_from_partition(ops, |
93 | "misc", |
94 | AB_METADATA_MISC_PARTITION_OFFSET, |
95 | sizeof(AvbABData), |
96 | &serialized, |
97 | &num_bytes_read); |
98 | if (io_ret == AVB_IO_RESULT_ERROR_OOM) { |
99 | return AVB_IO_RESULT_ERROR_OOM; |
100 | } else if (io_ret != AVB_IO_RESULT_OK || |
101 | num_bytes_read != sizeof(AvbABData)) { |
102 | ALOGE("Error reading A/B metadata.\n"); |
103 | return AVB_IO_RESULT_ERROR_IO; |
104 | } |
105 | |
106 | if (!avb_ab_data_verify_and_byteswap(&serialized, data)) { |
107 | ALOGE( |
108 | "Error validating A/B metadata from disk. " |
109 | "Resetting and writing new A/B metadata to disk.\n"); |
110 | avb_ab_data_init(data); |
111 | return avb_ab_data_write(ab_ops, data); |
112 | } |
113 | |
114 | return AVB_IO_RESULT_OK; |
115 | } |
116 | |
117 | AvbIOResult avb_ab_data_write(AvbABOps* ab_ops, const AvbABData* data) { |
118 | AvbOps* ops = ab_ops->ops; |
119 | AvbABData serialized; |
120 | AvbIOResult io_ret; |
121 | |
122 | avb_ab_data_update_crc_and_byteswap(data, &serialized); |
123 | io_ret = ops->write_to_partition(ops, |
124 | "misc", |
125 | AB_METADATA_MISC_PARTITION_OFFSET, |
126 | sizeof(AvbABData), |
127 | &serialized); |
128 | if (io_ret == AVB_IO_RESULT_ERROR_OOM) { |
129 | return AVB_IO_RESULT_ERROR_OOM; |
130 | } else if (io_ret != AVB_IO_RESULT_OK) { |
131 | ALOGE("*******Error writing A/B metadata.\n"); |
132 | return AVB_IO_RESULT_ERROR_IO; |
133 | } |
134 | return AVB_IO_RESULT_OK; |
135 | } |
136 | |
137 | static bool slot_is_bootable(AvbABSlotData* slot) { |
138 | return slot->priority > 0 && |
139 | (slot->successful_boot || (slot->tries_remaining > 0)); |
140 | } |
141 | |
142 | static void slot_set_unbootable(AvbABSlotData* slot) { |
143 | slot->priority = 0; |
144 | slot->tries_remaining = 0; |
145 | slot->successful_boot = 0; |
146 | } |
147 | |
148 | /* Ensure all unbootable and/or illegal states are marked as the |
149 | * canonical 'unbootable' state, e.g. priority=0, tries_remaining=0, |
150 | * and successful_boot=0. |
151 | */ |
152 | static void slot_normalize(AvbABSlotData* slot) { |
153 | if (slot->priority > 0) { |
154 | if (slot->tries_remaining == 0 && !slot->successful_boot) { |
155 | /* We've exhausted all tries -> unbootable. */ |
156 | slot_set_unbootable(slot); |
157 | } |
158 | if (slot->tries_remaining > 0 && slot->successful_boot) { |
159 | /* Illegal state - avb_ab_mark_slot_successful() will clear |
160 | * tries_remaining when setting successful_boot. |
161 | */ |
162 | slot_set_unbootable(slot); |
163 | } |
164 | } else { |
165 | slot_set_unbootable(slot); |
166 | } |
167 | } |
168 | |
169 | static const char* slot_suffixes[2] = {"_a", "_b"}; |
170 | |
171 | /* Helper function to load metadata - returns AVB_IO_RESULT_OK on |
172 | * success, error code otherwise. |
173 | */ |
174 | static AvbIOResult load_metadata(AvbABOps* ab_ops, |
175 | AvbABData* ab_data, |
176 | AvbABData* ab_data_orig) { |
177 | AvbIOResult io_ret; |
178 | |
179 | io_ret = ab_ops->read_ab_metadata(ab_ops, ab_data); |
180 | if (io_ret != AVB_IO_RESULT_OK) { |
181 | ALOGE("I/O error while loading A/B metadata.\n"); |
182 | return io_ret; |
183 | } |
184 | *ab_data_orig = *ab_data; |
185 | |
186 | /* Ensure data is normalized, e.g. illegal states will be marked as |
187 | * unbootable and all unbootable states are represented with |
188 | * (priority=0, tries_remaining=0, successful_boot=0). |
189 | */ |
190 | slot_normalize(&ab_data->slots[0]); |
191 | slot_normalize(&ab_data->slots[1]); |
192 | return AVB_IO_RESULT_OK; |
193 | } |
194 | |
195 | /* Writes A/B metadata to disk only if it has changed - returns |
196 | * AVB_IO_RESULT_OK on success, error code otherwise. |
197 | */ |
198 | static AvbIOResult save_metadata_if_changed(AvbABOps* ab_ops, |
199 | AvbABData* ab_data, |
200 | AvbABData* ab_data_orig) { |
201 | if (avb_safe_memcmp(ab_data, ab_data_orig, sizeof(AvbABData)) != 0) { |
202 | ALOGE("Writing A/B metadata to disk.\n"); |
203 | return ab_ops->write_ab_metadata(ab_ops, ab_data); |
204 | } |
205 | return AVB_IO_RESULT_OK; |
206 | } |
207 | |
208 | AvbABFlowResult avb_ab_flow(AvbABOps* ab_ops, |
209 | const char* const* requested_partitions, |
210 | bool allow_verification_error, |
211 | AvbSlotVerifyData** out_data) { |
212 | AvbOps* ops = ab_ops->ops; |
213 | AvbSlotVerifyData* slot_data[2] = {NULL, NULL}; |
214 | AvbSlotVerifyData* data = NULL; |
215 | AvbABFlowResult ret; |
216 | AvbABData ab_data, ab_data_orig; |
217 | size_t slot_index_to_boot, n; |
218 | AvbIOResult io_ret; |
219 | bool saw_and_allowed_verification_error = false; |
220 | |
221 | io_ret = load_metadata(ab_ops, &ab_data, &ab_data_orig); |
222 | if (io_ret == AVB_IO_RESULT_ERROR_OOM) { |
223 | ret = AVB_AB_FLOW_RESULT_ERROR_OOM; |
224 | goto out; |
225 | } else if (io_ret != AVB_IO_RESULT_OK) { |
226 | ret = AVB_AB_FLOW_RESULT_ERROR_IO; |
227 | goto out; |
228 | } |
229 | |
230 | /* Validate all bootable slots. */ |
231 | for (n = 0; n < 2; n++) { |
232 | if (slot_is_bootable(&ab_data.slots[n])) { |
233 | AvbSlotVerifyResult verify_result; |
234 | bool set_slot_unbootable = false; |
235 | |
236 | verify_result = avb_slot_verify(ops, |
237 | requested_partitions, |
238 | slot_suffixes[n], |
239 | allow_verification_error, |
240 | &slot_data[n]); |
241 | switch (verify_result) { |
242 | case AVB_SLOT_VERIFY_RESULT_ERROR_OOM: |
243 | ret = AVB_AB_FLOW_RESULT_ERROR_OOM; |
244 | goto out; |
245 | |
246 | case AVB_SLOT_VERIFY_RESULT_ERROR_IO: |
247 | ret = AVB_AB_FLOW_RESULT_ERROR_IO; |
248 | goto out; |
249 | |
250 | case AVB_SLOT_VERIFY_RESULT_OK: |
251 | break; |
252 | |
253 | case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA: |
254 | case AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION: |
255 | /* Even with |allow_verification_error| these mean game over. */ |
256 | set_slot_unbootable = true; |
257 | break; |
258 | |
259 | /* explicit fallthrough. */ |
260 | case AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION: |
261 | case AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX: |
262 | case AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED: |
263 | if (allow_verification_error) { |
264 | /* Do nothing since we allow this. */ |
265 | avb_debugv("Allowing slot ", |
266 | slot_suffixes[n], |
267 | " which verified " |
268 | "with result ", |
269 | avb_slot_verify_result_to_string(verify_result), |
270 | " because |allow_verification_error| is true.\n", |
271 | NULL); |
272 | saw_and_allowed_verification_error = true; |
273 | } else { |
274 | set_slot_unbootable = true; |
275 | } |
276 | break; |
277 | } |
278 | |
279 | if (set_slot_unbootable) { |
280 | ALOGE("Error verifying slot %s with result %s - setting unbootable.", |
281 | slot_suffixes[n], avb_slot_verify_result_to_string(verify_result)); |
282 | slot_set_unbootable(&ab_data.slots[n]); |
283 | } |
284 | } |
285 | } |
286 | |
287 | if (slot_is_bootable(&ab_data.slots[0]) && |
288 | slot_is_bootable(&ab_data.slots[1])) { |
289 | if (ab_data.slots[1].priority > ab_data.slots[0].priority) { |
290 | slot_index_to_boot = 1; |
291 | } else { |
292 | slot_index_to_boot = 0; |
293 | } |
294 | } else if (slot_is_bootable(&ab_data.slots[0])) { |
295 | slot_index_to_boot = 0; |
296 | } else if (slot_is_bootable(&ab_data.slots[1])) { |
297 | slot_index_to_boot = 1; |
298 | } else { |
299 | /* No bootable slots! */ |
300 | ALOGE("No bootable slots found.\n"); |
301 | ret = AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS; |
302 | goto out; |
303 | } |
304 | |
305 | /* Update stored rollback index such that the stored rollback index |
306 | * is the largest value supporting all currently bootable slots. Do |
307 | * this for every rollback index location. |
308 | */ |
309 | for (n = 0; n < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; n++) { |
310 | uint64_t rollback_index_value = 0; |
311 | |
312 | if (slot_data[0] != NULL && slot_data[1] != NULL) { |
313 | uint64_t a_rollback_index = slot_data[0]->rollback_indexes[n]; |
314 | uint64_t b_rollback_index = slot_data[1]->rollback_indexes[n]; |
315 | rollback_index_value = |
316 | (a_rollback_index < b_rollback_index ? a_rollback_index |
317 | : b_rollback_index); |
318 | } else if (slot_data[0] != NULL) { |
319 | rollback_index_value = slot_data[0]->rollback_indexes[n]; |
320 | } else if (slot_data[1] != NULL) { |
321 | rollback_index_value = slot_data[1]->rollback_indexes[n]; |
322 | } |
323 | |
324 | if (rollback_index_value != 0) { |
325 | uint64_t current_rollback_index_value; |
326 | io_ret = ops->read_rollback_index(ops, n, ¤t_rollback_index_value); |
327 | if (io_ret == AVB_IO_RESULT_ERROR_OOM) { |
328 | ret = AVB_AB_FLOW_RESULT_ERROR_OOM; |
329 | goto out; |
330 | } else if (io_ret != AVB_IO_RESULT_OK) { |
331 | ALOGE("Error getting rollback index for slot.\n"); |
332 | ret = AVB_AB_FLOW_RESULT_ERROR_IO; |
333 | goto out; |
334 | } |
335 | if (current_rollback_index_value != rollback_index_value) { |
336 | io_ret = ops->write_rollback_index(ops, n, rollback_index_value); |
337 | if (io_ret == AVB_IO_RESULT_ERROR_OOM) { |
338 | ret = AVB_AB_FLOW_RESULT_ERROR_OOM; |
339 | goto out; |
340 | } else if (io_ret != AVB_IO_RESULT_OK) { |
341 | ALOGE("Error setting stored rollback index.\n"); |
342 | ret = AVB_AB_FLOW_RESULT_ERROR_IO; |
343 | goto out; |
344 | } |
345 | } |
346 | } |
347 | } |
348 | |
349 | /* Finally, select this slot. */ |
350 | avb_assert(slot_data[slot_index_to_boot] != NULL); |
351 | data = slot_data[slot_index_to_boot]; |
352 | slot_data[slot_index_to_boot] = NULL; |
353 | if (saw_and_allowed_verification_error) { |
354 | avb_assert(allow_verification_error); |
355 | ret = AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR; |
356 | } else { |
357 | ret = AVB_AB_FLOW_RESULT_OK; |
358 | } |
359 | |
360 | /* ... and decrement tries remaining, if applicable. */ |
361 | if (!ab_data.slots[slot_index_to_boot].successful_boot && |
362 | ab_data.slots[slot_index_to_boot].tries_remaining > 0) { |
363 | ab_data.slots[slot_index_to_boot].tries_remaining -= 1; |
364 | } |
365 | |
366 | out: |
367 | io_ret = save_metadata_if_changed(ab_ops, &ab_data, &ab_data_orig); |
368 | if (io_ret != AVB_IO_RESULT_OK) { |
369 | if (io_ret == AVB_IO_RESULT_ERROR_OOM) { |
370 | ret = AVB_AB_FLOW_RESULT_ERROR_OOM; |
371 | } else { |
372 | ret = AVB_AB_FLOW_RESULT_ERROR_IO; |
373 | } |
374 | if (data != NULL) { |
375 | avb_slot_verify_data_free(data); |
376 | data = NULL; |
377 | } |
378 | } |
379 | |
380 | for (n = 0; n < 2; n++) { |
381 | if (slot_data[n] != NULL) { |
382 | avb_slot_verify_data_free(slot_data[n]); |
383 | } |
384 | } |
385 | |
386 | if (out_data != NULL) { |
387 | *out_data = data; |
388 | } else { |
389 | if (data != NULL) { |
390 | avb_slot_verify_data_free(data); |
391 | } |
392 | } |
393 | |
394 | return ret; |
395 | } |
396 | |
397 | AvbIOResult avb_ab_mark_slot_active(AvbABOps* ab_ops, |
398 | unsigned int slot_number) { |
399 | AvbABData ab_data, ab_data_orig; |
400 | unsigned int other_slot_number; |
401 | AvbIOResult ret; |
402 | |
403 | ALOGE("*************come to avb_ab_mark_slot_active slot:%d", slot_number); |
404 | |
405 | if (slot_number >= 2) { |
406 | return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION; |
407 | } |
408 | |
409 | ret = load_metadata(ab_ops, &ab_data, &ab_data_orig); |
410 | if (ret != AVB_IO_RESULT_OK) { |
411 | goto out; |
412 | } |
413 | |
414 | ALOGE("ab_data.slots[0].priority: %d\n", ab_data.slots[0].priority); |
415 | ALOGE("ab_data.slots[0].tries_remaining: %d\n", ab_data.slots[0].tries_remaining); |
416 | ALOGE("ab_data.slots[0].successful_boot: %d\n", ab_data.slots[0].successful_boot); |
417 | ALOGE("ab_data.slots[1].priority: %d\n", ab_data.slots[1].priority); |
418 | ALOGE("ab_data.slots[1].tries_remaining: %d\n", ab_data.slots[1].tries_remaining); |
419 | ALOGE("ab_data.slots[1].successful_boot: %d\n", ab_data.slots[1].successful_boot); |
420 | |
421 | /* Make requested slot top priority, unsuccessful, and with max tries. */ |
422 | ab_data.slots[slot_number].priority = AVB_AB_MAX_PRIORITY; |
423 | ab_data.slots[slot_number].tries_remaining = AVB_AB_MAX_TRIES_REMAINING; |
424 | ab_data.slots[slot_number].successful_boot = 0; |
425 | |
426 | /* Ensure other slot doesn't have as high a priority. */ |
427 | other_slot_number = 1 - slot_number; |
428 | |
429 | ALOGE("come to avb_ab_mark_slot_active other_slot_number: %d\n", other_slot_number); |
430 | ALOGE("ab_data.slots[other_slot_number].priority: %d\n", ab_data.slots[other_slot_number].priority); |
431 | |
432 | if (ab_data.slots[other_slot_number].priority == AVB_AB_MAX_PRIORITY) { |
433 | ab_data.slots[other_slot_number].priority = AVB_AB_MAX_PRIORITY - 1; |
434 | } |
435 | |
436 | ALOGE("ab_data.slots[other_slot_number].priority: %d\n", ab_data.slots[other_slot_number].priority); |
437 | |
438 | ret = AVB_IO_RESULT_OK; |
439 | out: |
440 | if (ret == AVB_IO_RESULT_OK) { |
441 | ALOGE("come to avb_ab_mark_slot_active 5\n"); |
442 | ret = save_metadata_if_changed(ab_ops, &ab_data, &ab_data_orig); |
443 | } |
444 | return ret; |
445 | } |
446 | |
447 | AvbIOResult avb_ab_mark_slot_unbootable(AvbABOps* ab_ops, |
448 | unsigned int slot_number) { |
449 | AvbABData ab_data, ab_data_orig; |
450 | AvbIOResult ret; |
451 | |
452 | if (slot_number >= 2) { |
453 | return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION; |
454 | } |
455 | |
456 | ret = load_metadata(ab_ops, &ab_data, &ab_data_orig); |
457 | if (ret != AVB_IO_RESULT_OK) { |
458 | goto out; |
459 | } |
460 | |
461 | slot_set_unbootable(&ab_data.slots[slot_number]); |
462 | |
463 | ret = AVB_IO_RESULT_OK; |
464 | |
465 | out: |
466 | if (ret == AVB_IO_RESULT_OK) { |
467 | ret = save_metadata_if_changed(ab_ops, &ab_data, &ab_data_orig); |
468 | } |
469 | return ret; |
470 | } |
471 | |
472 | AvbIOResult avb_ab_mark_slot_successful(AvbABOps* ab_ops, |
473 | unsigned int slot_number) { |
474 | AvbABData ab_data, ab_data_orig; |
475 | AvbIOResult ret; |
476 | |
477 | if (slot_number >= 2) { |
478 | return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION; |
479 | } |
480 | |
481 | ret = load_metadata(ab_ops, &ab_data, &ab_data_orig); |
482 | if (ret != AVB_IO_RESULT_OK) { |
483 | goto out; |
484 | } |
485 | |
486 | if (!slot_is_bootable(&ab_data.slots[slot_number])) { |
487 | ALOGE("Cannot mark unbootable slot as successful.\n"); |
488 | ret = AVB_IO_RESULT_OK; |
489 | goto out; |
490 | } |
491 | |
492 | ab_data.slots[slot_number].tries_remaining = 0; |
493 | ab_data.slots[slot_number].successful_boot = 1; |
494 | |
495 | ret = AVB_IO_RESULT_OK; |
496 | |
497 | out: |
498 | if (ret == AVB_IO_RESULT_OK) { |
499 | ret = save_metadata_if_changed(ab_ops, &ab_data, &ab_data_orig); |
500 | } |
501 | return ret; |
502 | } |
503 | |
504 | const char* avb_ab_flow_result_to_string(AvbABFlowResult result) { |
505 | const char* ret = NULL; |
506 | |
507 | switch (result) { |
508 | case AVB_AB_FLOW_RESULT_OK: |
509 | ret = "OK"; |
510 | break; |
511 | |
512 | case AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR: |
513 | ret = "OK_WITH_VERIFICATION_ERROR"; |
514 | break; |
515 | |
516 | case AVB_AB_FLOW_RESULT_ERROR_OOM: |
517 | ret = "ERROR_OOM"; |
518 | break; |
519 | |
520 | case AVB_AB_FLOW_RESULT_ERROR_IO: |
521 | ret = "ERROR_IO"; |
522 | break; |
523 | |
524 | case AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS: |
525 | ret = "ERROR_NO_BOOTABLE_SLOTS"; |
526 | break; |
527 | /* Do not add a 'default:' case here because of -Wswitch. */ |
528 | } |
529 | |
530 | if (ret == NULL) { |
531 | ALOGE("Unknown AvbABFlowResult value.\n"); |
532 | ret = "(unknown)"; |
533 | } |
534 | |
535 | return ret; |
536 | } |
537 |