1 /* 2 * Copyright (c) 2016, The Linux Foundation. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * Redistributions in binary form must reproduce the above 10 * copyright notice, this list of conditions and the following 11 * disclaimer in the documentation and/or other materials provided 12 * with the distribution. 13 * * Neither the name of The Linux Foundation nor the names of its 14 * contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 24 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 26 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 #include <errno.h> 30 #define LOG_TAG "bootcontrolhal" 31 #include <cutils/log.h> 32 #include <hardware/boot_control.h> 33 #include <stdio.h> 34 #include <string.h> 35 #include <unistd.h> 36 #include <dirent.h> 37 #include <sys/types.h> 38 #include <sys/stat.h> 39 #include <fcntl.h> 40 #include <limits.h> 41 #include "gpt-utils.h" 42 43 #define BOOTDEV_DIR "/dev/block/bootdevice/by-name" 44 #define BOOT_IMG_PTN_NAME "boot" 45 #define LUN_NAME_END_LOC 14 46 47 const char *slot_suffix_arr[] = { 48 AB_SLOT_A_SUFFIX, 49 AB_SLOT_B_SUFFIX, 50 NULL}; 51 52 enum part_attr_type { 53 ATTR_SLOT_ACTIVE = 0, 54 ATTR_BOOT_SUCCESSFUL, 55 ATTR_UNBOOTABLE, 56 }; 57 58 void boot_control_init(struct boot_control_module *module) 59 { 60 if (!module) { 61 ALOGE("Invalid argument passed to %s", __func__); 62 return; 63 } 64 return; 65 } 66 67 //Get the value of one of the attribute fields for a partition. 68 static int get_partition_attribute(char *partname, 69 enum part_attr_type part_attr) 70 { 71 struct gpt_disk *disk = NULL; 72 uint8_t *pentry = NULL; 73 int retval = -1; 74 uint8_t *attr = NULL; 75 if (!partname) 76 goto error; 77 disk = gpt_disk_alloc(); 78 if (!disk) { 79 ALOGE("%s: Failed to alloc disk struct", __func__); 80 goto error; 81 } 82 if (gpt_disk_get_disk_info(partname, disk)) { 83 ALOGE("%s: Failed to get disk info", __func__); 84 goto error; 85 } 86 pentry = gpt_disk_get_pentry(disk, partname, PRIMARY_GPT); 87 if (!pentry) { 88 ALOGE("%s: pentry does not exist in disk struct", 89 __func__); 90 goto error; 91 } 92 attr = pentry + AB_FLAG_OFFSET; 93 if (part_attr == ATTR_SLOT_ACTIVE) 94 retval = !!(*attr & AB_PARTITION_ATTR_SLOT_ACTIVE); 95 else if (part_attr == ATTR_BOOT_SUCCESSFUL) 96 retval = !!(*attr & AB_PARTITION_ATTR_BOOT_SUCCESSFUL); 97 else if (part_attr == ATTR_UNBOOTABLE) 98 retval = !!(*attr & AB_PARTITION_ATTR_UNBOOTABLE); 99 else 100 retval = -1; 101 gpt_disk_free(disk); 102 return retval; 103 error: 104 if (disk) 105 gpt_disk_free(disk); 106 return retval; 107 } 108 109 //Set a particular attribute for all the partitions in a 110 //slot 111 static int update_slot_attribute(const char *slot, 112 enum part_attr_type ab_attr) 113 { 114 unsigned int i = 0; 115 char buf[PATH_MAX]; 116 struct stat st; 117 struct gpt_disk *disk = NULL; 118 uint8_t *pentry = NULL; 119 uint8_t *pentry_bak = NULL; 120 int rc = -1; 121 uint8_t *attr = NULL; 122 uint8_t *attr_bak = NULL; 123 char partName[MAX_GPT_NAME_SIZE + 1] = {0}; 124 const char ptn_list[][MAX_GPT_NAME_SIZE] = { AB_PTN_LIST }; 125 int slot_name_valid = 0; 126 if (!slot) { 127 ALOGE("%s: Invalid argument", __func__); 128 goto error; 129 } 130 for (i = 0; slot_suffix_arr[i] != NULL; i++) 131 { 132 if (!strncmp(slot, slot_suffix_arr[i], 133 strlen(slot_suffix_arr[i]))) 134 slot_name_valid = 1; 135 } 136 if (!slot_name_valid) { 137 ALOGE("%s: Invalid slot name", __func__); 138 goto error; 139 } 140 for (i=0; i < ARRAY_SIZE(ptn_list); i++) { 141 memset(buf, '\0', sizeof(buf)); 142 //Check if A/B versions of this ptn exist 143 snprintf(buf, sizeof(buf) - 1, 144 "%s/%s%s", 145 BOOT_DEV_DIR, 146 ptn_list[i], 147 AB_SLOT_A_SUFFIX 148 ); 149 if (stat(buf, &st)) { 150 //partition does not have _a version 151 continue; 152 } 153 memset(buf, '\0', sizeof(buf)); 154 snprintf(buf, sizeof(buf) - 1, 155 "%s/%s%s", 156 BOOT_DEV_DIR, 157 ptn_list[i], 158 AB_SLOT_B_SUFFIX 159 ); 160 if (stat(buf, &st)) { 161 //partition does not have _a version 162 continue; 163 } 164 memset(partName, '\0', sizeof(partName)); 165 snprintf(partName, 166 sizeof(partName) - 1, 167 "%s%s", 168 ptn_list[i], 169 slot); 170 disk = gpt_disk_alloc(disk); 171 if (!disk) { 172 ALOGE("%s: Failed to alloc disk struct", 173 __func__); 174 goto error; 175 } 176 rc = gpt_disk_get_disk_info(partName, disk); 177 if (rc != 0) { 178 ALOGE("%s: Failed to get disk info for %s", 179 __func__, 180 partName); 181 goto error; 182 } 183 pentry = gpt_disk_get_pentry(disk, partName, PRIMARY_GPT); 184 pentry_bak = gpt_disk_get_pentry(disk, partName, SECONDARY_GPT); 185 if (!pentry || !pentry_bak) { 186 ALOGE("%s: Failed to get pentry/pentry_bak for %s", 187 __func__, 188 partName); 189 goto error; 190 } 191 attr = pentry + AB_FLAG_OFFSET; 192 attr_bak = pentry_bak + AB_FLAG_OFFSET; 193 if (ab_attr == ATTR_BOOT_SUCCESSFUL) { 194 *attr = (*attr) | AB_PARTITION_ATTR_BOOT_SUCCESSFUL; 195 *attr_bak = (*attr_bak) | 196 AB_PARTITION_ATTR_BOOT_SUCCESSFUL; 197 } else if (ab_attr == ATTR_UNBOOTABLE) { 198 *attr = (*attr) | AB_PARTITION_ATTR_UNBOOTABLE; 199 *attr_bak = (*attr_bak) | AB_PARTITION_ATTR_UNBOOTABLE; 200 } else if (ab_attr == ATTR_SLOT_ACTIVE) { 201 *attr = (*attr) | AB_PARTITION_ATTR_SLOT_ACTIVE; 202 *attr_bak = (*attr) | AB_PARTITION_ATTR_SLOT_ACTIVE; 203 } else { 204 ALOGE("%s: Unrecognized attr", __func__); 205 goto error; 206 } 207 if (gpt_disk_update_crc(disk)) { 208 ALOGE("%s: Failed to update crc for %s", 209 __func__, 210 partName); 211 goto error; 212 } 213 if (gpt_disk_commit(disk)) { 214 ALOGE("%s: Failed to write back entry for %s", 215 __func__, 216 partName); 217 goto error; 218 } 219 gpt_disk_free(disk); 220 disk = NULL; 221 } 222 return 0; 223 error: 224 if (disk) 225 gpt_disk_free(disk); 226 return -1; 227 } 228 229 unsigned get_number_slots(struct boot_control_module *module) 230 { 231 struct dirent *de = NULL; 232 DIR *dir_bootdev = NULL; 233 unsigned slot_count = 0; 234 if (!module) { 235 ALOGE("%s: Invalid argument", __func__); 236 goto error; 237 } 238 dir_bootdev = opendir(BOOTDEV_DIR); 239 if (!dir_bootdev) { 240 ALOGE("%s: Failed to open bootdev dir (%s)", 241 __func__, 242 strerror(errno)); 243 goto error; 244 } 245 while ((de = readdir(dir_bootdev))) { 246 if (de->d_name[0] == '.') 247 continue; 248 if (!strncmp(de->d_name, BOOT_IMG_PTN_NAME, 249 strlen(BOOT_IMG_PTN_NAME))) 250 slot_count++; 251 } 252 closedir(dir_bootdev); 253 return slot_count; 254 error: 255 if (dir_bootdev) 256 closedir(dir_bootdev); 257 return 0; 258 } 259 260 unsigned get_current_slot(struct boot_control_module *module) 261 { 262 uint32_t num_slots = 0; 263 char bootPartition[MAX_GPT_NAME_SIZE + 1]; 264 unsigned i = 0; 265 if (!module) { 266 ALOGE("%s: Invalid argument", __func__); 267 goto error; 268 } 269 num_slots = get_number_slots(module); 270 if (num_slots <= 1) { 271 //Slot 0 is the only slot around. 272 return 0; 273 } 274 //Iterate through a list of partitons named as boot+suffix 275 //and see which one is currently active. 276 for (i = 0; slot_suffix_arr[i] != NULL ; i++) { 277 memset(bootPartition, '\0', sizeof(bootPartition)); 278 snprintf(bootPartition, sizeof(bootPartition) - 1, 279 "boot%s", 280 slot_suffix_arr[i]); 281 if (get_partition_attribute(bootPartition, 282 ATTR_SLOT_ACTIVE) == 1) 283 return i; 284 } 285 error: 286 //The HAL spec requires that we return a number between 287 //0 to num_slots - 1. Since something went wrong here we 288 //are just going to return the default slot. 289 return 0; 290 } 291 292 int mark_boot_successful(struct boot_control_module *module) 293 { 294 unsigned cur_slot = 0; 295 if (!module) { 296 ALOGE("%s: Invalid argument", __func__); 297 goto error; 298 } 299 cur_slot = get_current_slot(module); 300 if (update_slot_attribute(slot_suffix_arr[cur_slot], 301 ATTR_BOOT_SUCCESSFUL)) { 302 goto error; 303 } 304 return 0; 305 error: 306 ALOGE("%s: Failed to mark boot successful", __func__); 307 return -1; 308 } 309 310 const char *get_suffix(struct boot_control_module *module, unsigned slot) 311 { 312 unsigned num_slots = 0; 313 if (!module) { 314 ALOGE("%s: Invalid arg", __func__); 315 } 316 num_slots = get_number_slots(module); 317 if (num_slots < 1 || slot > num_slots - 1) 318 return NULL; 319 else 320 return slot_suffix_arr[slot]; 321 } 322 323 int set_active_boot_slot(struct boot_control_module *module, unsigned slot) 324 { 325 const char ptn_list[][MAX_GPT_NAME_SIZE] = { AB_PTN_LIST }; 326 char slotA[MAX_GPT_NAME_SIZE + 1] = {0}; 327 char slotB[MAX_GPT_NAME_SIZE + 1] = {0}; 328 char active_guid[TYPE_GUID_SIZE + 1] = {0}; 329 char inactive_guid[TYPE_GUID_SIZE + 1] = {0}; 330 struct gpt_disk *disk = NULL; 331 //Pointer to partition entry of current 'A' partition 332 uint8_t *pentryA = NULL; 333 uint8_t *pentryA_bak = NULL; 334 //Pointer to partition entry of current 'B' partition 335 uint8_t *pentryB = NULL; 336 uint8_t *pentryB_bak = NULL; 337 uint8_t *slot_info = NULL; 338 uint32_t i; 339 int rc = -1; 340 char buf[PATH_MAX] = {0}; 341 struct stat st; 342 unsigned num_slots = 0; 343 unsigned current_slot = 0; 344 int is_ufs = gpt_utils_is_ufs_device(); 345 346 if (!module) { 347 ALOGE("%s: Invalid arg", __func__); 348 goto error; 349 } 350 num_slots = get_number_slots(module); 351 if ((num_slots < 1) || (slot > num_slots - 1)) { 352 ALOGE("%s: Unable to get num slots/Invalid slot value", 353 __func__); 354 goto error; 355 } 356 current_slot = get_current_slot(module); 357 if (current_slot == slot) { 358 //Nothing to do here. Just return 359 return 0; 360 } 361 for (i=0; i < ARRAY_SIZE(ptn_list); i++) { 362 //XBL is handled differrently for ufs devices 363 if (is_ufs && !strncmp(ptn_list[i], PTN_XBL, strlen(PTN_XBL))) 364 continue; 365 memset(buf, '\0', sizeof(buf)); 366 //Check if A/B versions of this ptn exist 367 snprintf(buf, sizeof(buf) - 1, 368 "%s/%s%s", 369 BOOT_DEV_DIR, 370 ptn_list[i], 371 AB_SLOT_A_SUFFIX 372 ); 373 if (stat(buf, &st)) { 374 //partition does not have _a version 375 continue; 376 } 377 memset(buf, '\0', sizeof(buf)); 378 snprintf(buf, sizeof(buf) - 1, 379 "%s/%s%s", 380 BOOT_DEV_DIR, 381 ptn_list[i], 382 AB_SLOT_B_SUFFIX 383 ); 384 if (stat(buf, &st)) { 385 //partition does not have _a version 386 continue; 387 } 388 disk = gpt_disk_alloc(); 389 if (!disk) 390 goto error; 391 memset(slotA, 0, sizeof(slotA)); 392 memset(slotB, 0, sizeof(slotB)); 393 snprintf(slotA, sizeof(slotA) - 1, "%s%s", 394 ptn_list[i], 395 AB_SLOT_A_SUFFIX); 396 snprintf(slotB, sizeof(slotB) - 1,"%s%s", 397 ptn_list[i], 398 AB_SLOT_B_SUFFIX); 399 //It is assumed that both the A and B slots reside on the 400 //same physical disk 401 if (gpt_disk_get_disk_info(slotA, disk)) 402 goto error; 403 //Get partition entry for slot A from primary table 404 pentryA = gpt_disk_get_pentry(disk, slotA, PRIMARY_GPT); 405 //Get partition entry for slot A from backup table 406 pentryA_bak = gpt_disk_get_pentry(disk, slotA, SECONDARY_GPT); 407 //Get partition entry for slot B from primary table 408 pentryB = gpt_disk_get_pentry(disk, slotB, PRIMARY_GPT); 409 //Get partition entry for slot B from backup table 410 pentryB_bak = gpt_disk_get_pentry(disk, slotB, SECONDARY_GPT); 411 if ( !pentryA || !pentryA_bak || !pentryB || !pentryB_bak) { 412 //Something has gone wrong here.We know that we have 413 //_a and _b versions of this partition due to the 414 //check at the start of the loop so none of these 415 //should be NULL. 416 ALOGE("Slot pentries for %s not found.", 417 ptn_list[i]); 418 goto error; 419 } 420 memset(active_guid, '\0', sizeof(active_guid)); 421 memset(inactive_guid, '\0', sizeof(inactive_guid)); 422 if (get_partition_attribute(slotA, ATTR_SLOT_ACTIVE) == 1) { 423 //A is the current active slot 424 memcpy((void*)active_guid, 425 (const void*)pentryA, 426 TYPE_GUID_SIZE); 427 memcpy((void*)inactive_guid, 428 (const void*)pentryB, 429 TYPE_GUID_SIZE); 430 431 } else if (get_partition_attribute(slotB, 432 ATTR_SLOT_ACTIVE) == 1) { 433 //B is the current active slot 434 memcpy((void*)active_guid, 435 (const void*)pentryB, 436 TYPE_GUID_SIZE); 437 memcpy((void*)inactive_guid, 438 (const void*)pentryA, 439 TYPE_GUID_SIZE); 440 } else { 441 ALOGE("Both A & B are inactive..Aborting"); 442 goto error; 443 } 444 if (!strncmp(slot_suffix_arr[slot], AB_SLOT_A_SUFFIX, 445 strlen(AB_SLOT_A_SUFFIX))){ 446 //Mark A as active in primary table 447 memcpy(pentryA, active_guid, TYPE_GUID_SIZE); 448 slot_info = pentryA + AB_FLAG_OFFSET; 449 *slot_info = AB_SLOT_ACTIVE_VAL; 450 451 //Mark A as active in backup table 452 memcpy(pentryA_bak, active_guid, TYPE_GUID_SIZE); 453 slot_info = pentryA_bak + AB_FLAG_OFFSET; 454 *slot_info = AB_SLOT_ACTIVE_VAL; 455 456 //Mark B as inactive in primary table 457 memcpy(pentryB, inactive_guid, TYPE_GUID_SIZE); 458 slot_info = pentryB + AB_FLAG_OFFSET; 459 *slot_info = *(slot_info) & 460 ~AB_PARTITION_ATTR_SLOT_ACTIVE; 461 462 //Mark B as inactive in backup table 463 memcpy(pentryB_bak, inactive_guid, TYPE_GUID_SIZE); 464 slot_info = pentryB_bak + AB_FLAG_OFFSET; 465 *slot_info = *(slot_info) & 466 ~AB_PARTITION_ATTR_SLOT_ACTIVE; 467 } else if (!strncmp(slot_suffix_arr[slot], AB_SLOT_B_SUFFIX, 468 strlen(AB_SLOT_B_SUFFIX))){ 469 //Mark B as active in primary table 470 memcpy(pentryB, active_guid, TYPE_GUID_SIZE); 471 slot_info = pentryB + AB_FLAG_OFFSET; 472 *slot_info = AB_SLOT_ACTIVE_VAL; 473 474 //Mark B as active in backup table 475 memcpy(pentryB_bak, active_guid, TYPE_GUID_SIZE); 476 slot_info = pentryB_bak + AB_FLAG_OFFSET; 477 *slot_info = AB_SLOT_ACTIVE_VAL; 478 479 //Mark A as inavtive in primary table 480 memcpy(pentryA, inactive_guid, TYPE_GUID_SIZE); 481 slot_info = pentryA + AB_FLAG_OFFSET; 482 *slot_info = *(slot_info) & 483 ~AB_PARTITION_ATTR_SLOT_ACTIVE; 484 485 //Mark A as inactive in backup table 486 memcpy(pentryA_bak, inactive_guid, TYPE_GUID_SIZE); 487 slot_info = pentryA_bak + AB_FLAG_OFFSET; 488 *slot_info = *(slot_info) & 489 ~AB_PARTITION_ATTR_SLOT_ACTIVE; 490 } else { 491 //Something has gone terribly terribly wrong 492 ALOGE("%s: Unknown slot suffix!", __func__); 493 goto error; 494 } 495 if (gpt_disk_update_crc(disk) != 0) { 496 ALOGE("%s: Failed to update gpt_disk crc", __func__); 497 goto error; 498 } 499 if (gpt_disk_commit(disk) != 0) { 500 ALOGE("%s: Failed to commit disk info", __func__); 501 goto error; 502 } 503 gpt_disk_free(disk); 504 disk = NULL; 505 } 506 if (is_ufs) { 507 if (!strncmp(slot_suffix_arr[slot], AB_SLOT_A_SUFFIX, 508 strlen(AB_SLOT_A_SUFFIX))){ 509 //Set xbl_a as the boot lun 510 rc = gpt_utils_set_xbl_boot_partition(NORMAL_BOOT); 511 } else if (!strncmp(slot_suffix_arr[slot], AB_SLOT_B_SUFFIX, 512 strlen(AB_SLOT_B_SUFFIX))){ 513 //Set xbl_b as the boot lun 514 rc = gpt_utils_set_xbl_boot_partition(BACKUP_BOOT); 515 } else { 516 //Something has gone terribly terribly wrong 517 ALOGE("%s: Unknown slot suffix!", __func__); 518 goto error; 519 } 520 if (rc) { 521 ALOGE("%s: Failed to switch xbl boot partition", 522 __func__); 523 goto error; 524 } 525 } 526 return 0; 527 error: 528 if (disk) 529 gpt_disk_free(disk); 530 return -1; 531 } 532 533 int set_slot_as_unbootable(struct boot_control_module *module, unsigned slot) 534 { 535 unsigned num_slots = 0; 536 if (!module) { 537 ALOGE("%s: Invalid argument", __func__); 538 goto error; 539 } 540 num_slots = get_number_slots(module); 541 if (num_slots < 1 || slot > num_slots - 1) { 542 ALOGE("%s: Unable to get num_slots/Invalid slot value", 543 __func__); 544 goto error; 545 } 546 if (update_slot_attribute(slot_suffix_arr[slot], 547 ATTR_UNBOOTABLE)) { 548 goto error; 549 } 550 return 0; 551 error: 552 ALOGE("%s: Failed to mark slot unbootable", __func__); 553 return -1; 554 } 555 556 int is_slot_bootable(struct boot_control_module *module, unsigned slot) 557 { 558 unsigned num_slots = 0; 559 int attr = 0; 560 char bootPartition[MAX_GPT_NAME_SIZE + 1] = {0}; 561 if (!module) { 562 ALOGE("%s: Invalid argument", __func__); 563 goto error; 564 } 565 num_slots = get_number_slots(module); 566 if (num_slots < 1 || slot > num_slots - 1) { 567 ALOGE("%s: Unable to get num_slots/Invalid slot value", 568 __func__); 569 goto error; 570 } 571 snprintf(bootPartition, 572 sizeof(bootPartition) - 1, "boot%s", 573 slot_suffix_arr[slot]); 574 attr = get_partition_attribute(bootPartition, ATTR_UNBOOTABLE); 575 if (attr >= 0) 576 return !attr; 577 error: 578 return -1; 579 } 580 581 int is_slot_marked_successful(struct boot_control_module *module, unsigned slot) 582 { 583 unsigned num_slots = 0; 584 int attr = 0; 585 char bootPartition[MAX_GPT_NAME_SIZE + 1] = {0}; 586 if (!module) { 587 ALOGE("%s: Invalid argument", __func__); 588 goto error; 589 } 590 num_slots = get_number_slots(module); 591 if (num_slots < 1 || slot > num_slots - 1) { 592 ALOGE("%s: Unable to get num_slots/Invalid slot value", 593 __func__); 594 goto error; 595 } 596 snprintf(bootPartition, 597 sizeof(bootPartition) - 1, 598 "boot%s", slot_suffix_arr[slot]); 599 attr = get_partition_attribute(bootPartition, ATTR_BOOT_SUCCESSFUL); 600 if (attr >= 0) 601 return attr; 602 error: 603 return -1; 604 } 605 606 static hw_module_methods_t boot_control_module_methods = { 607 .open = NULL, 608 }; 609 610 boot_control_module_t HAL_MODULE_INFO_SYM = { 611 .common = { 612 .tag = HARDWARE_MODULE_TAG, 613 .module_api_version = 1, 614 .hal_api_version = 0, 615 .id = BOOT_CONTROL_HARDWARE_MODULE_ID, 616 .name = "Boot control HAL", 617 .author = "Code Aurora Forum", 618 .methods = &boot_control_module_methods, 619 }, 620 .init = boot_control_init, 621 .getNumberSlots = get_number_slots, 622 .getCurrentSlot = get_current_slot, 623 .markBootSuccessful = mark_boot_successful, 624 .setActiveBootSlot = set_active_boot_slot, 625 .setSlotAsUnbootable = set_slot_as_unbootable, 626 .isSlotBootable = is_slot_bootable, 627 .getSuffix = get_suffix, 628 .isSlotMarkedSuccessful = is_slot_marked_successful, 629 }; 630