1 /* 2 * Copyright (c) 2014, ARM Limited and Contributors. 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 met: 6 * 7 * Redistributions of source code must retain the above copyright notice, this 8 * list of conditions and the following disclaimer. 9 * 10 * Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 14 * Neither the name of ARM nor the names of its contributors may be used 15 * to endorse or promote products derived from this software without specific 16 * prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include <errno.h> 32 #include <getopt.h> /* getopt_long() is a GNU extention */ 33 #include <stdbool.h> 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <sys/stat.h> 38 #include "fip_create.h" 39 #include "firmware_image_package.h" 40 41 /* Values returned by getopt() as part of the command line parsing */ 42 #define OPT_TOC_ENTRY 0 43 #define OPT_DUMP 1 44 #define OPT_HELP 2 45 46 file_info_t files[MAX_FILES]; 47 unsigned file_info_count = 0; 48 uuid_t uuid_null = {0}; 49 50 /* 51 * TODO: Add ability to specify and flag different file types. 52 * Add flags to the toc_entry? 53 * const char* format_type_str[] = { "RAW", "ELF", "PIC" }; 54 */ 55 56 /* The images used depends on the platform. */ 57 static entry_lookup_list_t toc_entry_lookup_list[] = { 58 { "Trusted Boot Firmware BL2", UUID_TRUSTED_BOOT_FIRMWARE_BL2, 59 "bl2", NULL, FLAG_FILENAME }, 60 { "SCP Firmware BL3-0", UUID_SCP_FIRMWARE_BL30, 61 "bl30", NULL, FLAG_FILENAME}, 62 { "EL3 Runtime Firmware BL3-1", UUID_EL3_RUNTIME_FIRMWARE_BL31, 63 "bl31", NULL, FLAG_FILENAME}, 64 { "Secure Payload BL3-2 (Trusted OS)", UUID_SECURE_PAYLOAD_BL32, 65 "bl32", NULL, FLAG_FILENAME}, 66 { "Non-Trusted Firmware BL3-3", UUID_NON_TRUSTED_FIRMWARE_BL33, 67 "bl33", NULL, FLAG_FILENAME}, 68 /* Key Certificates */ 69 { "Root Of Trust key certificate", UUID_ROT_KEY_CERT, 70 "rot-cert", NULL, FLAG_FILENAME }, 71 { "Trusted key certificate", UUID_TRUSTED_KEY_CERT, 72 "trusted-key-cert", NULL, FLAG_FILENAME}, 73 { "SCP Firmware BL3-0 key certificate", UUID_SCP_FIRMWARE_BL30_KEY_CERT, 74 "bl30-key-cert", NULL, FLAG_FILENAME}, 75 { "EL3 Runtime Firmware BL3-1 key certificate", UUID_EL3_RUNTIME_FIRMWARE_BL31_KEY_CERT, 76 "bl31-key-cert", NULL, FLAG_FILENAME}, 77 { "Secure Payload BL3-2 (Trusted OS) key certificate", UUID_SECURE_PAYLOAD_BL32_KEY_CERT, 78 "bl32-key-cert", NULL, FLAG_FILENAME}, 79 { "Non-Trusted Firmware BL3-3 key certificate", UUID_NON_TRUSTED_FIRMWARE_BL33_KEY_CERT, 80 "bl33-key-cert", NULL, FLAG_FILENAME}, 81 /* Content certificates */ 82 { "Trusted Boot Firmware BL2 certificate", UUID_TRUSTED_BOOT_FIRMWARE_BL2_CERT, 83 "bl2-cert", NULL, FLAG_FILENAME }, 84 { "SCP Firmware BL3-0 certificate", UUID_SCP_FIRMWARE_BL30_CERT, 85 "bl30-cert", NULL, FLAG_FILENAME}, 86 { "EL3 Runtime Firmware BL3-1 certificate", UUID_EL3_RUNTIME_FIRMWARE_BL31_CERT, 87 "bl31-cert", NULL, FLAG_FILENAME}, 88 { "Secure Payload BL3-2 (Trusted OS) certificate", UUID_SECURE_PAYLOAD_BL32_CERT, 89 "bl32-cert", NULL, FLAG_FILENAME}, 90 { "Non-Trusted Firmware BL3-3 certificate", UUID_NON_TRUSTED_FIRMWARE_BL33_CERT, 91 "bl33-cert", NULL, FLAG_FILENAME}, 92 { NULL, {0}, 0 } 93 }; 94 95 96 /* Return 0 for equal uuids */ 97 static inline int compare_uuids(const uuid_t *uuid1, const uuid_t *uuid2) 98 { 99 return memcmp(uuid1, uuid2, sizeof(uuid_t)); 100 } 101 102 103 static inline void copy_uuid(uuid_t *to_uuid, const uuid_t *from_uuid) 104 { 105 memcpy(to_uuid, from_uuid, sizeof(uuid_t)); 106 } 107 108 109 static void print_usage(void) 110 { 111 entry_lookup_list_t *entry = toc_entry_lookup_list; 112 113 printf("Usage: fip_create [options] FIP_FILENAME\n\n"); 114 printf("\tThis tool is used to create a Firmware Image Package.\n\n"); 115 printf("Options:\n"); 116 printf("\t--help: Print this help message and exit\n"); 117 printf("\t--dump: Print contents of FIP\n\n"); 118 printf("\tComponents that can be added/updated:\n"); 119 for (; entry->command_line_name != NULL; entry++) { 120 printf("\t--%s%s\t\t%s", 121 entry->command_line_name, 122 (entry->flags & FLAG_FILENAME) ? " FILENAME" : "", 123 entry->name); 124 printf("\n"); 125 } 126 } 127 128 129 static entry_lookup_list_t *get_entry_lookup_from_uuid(const uuid_t *uuid) 130 { 131 unsigned int lookup_index = 0; 132 133 while (toc_entry_lookup_list[lookup_index].command_line_name != NULL) { 134 if (compare_uuids(&toc_entry_lookup_list[lookup_index].name_uuid, 135 uuid) == 0) { 136 return &toc_entry_lookup_list[lookup_index]; 137 } 138 lookup_index++; 139 } 140 return NULL; 141 } 142 143 144 static file_info_t *find_file_info_from_uuid(const uuid_t *uuid) 145 { 146 int index; 147 148 for (index = 0; index < file_info_count; index++) { 149 if (compare_uuids(&files[index].name_uuid, uuid) == 0) { 150 return &files[index]; 151 } 152 } 153 return NULL; 154 } 155 156 157 static int add_file_info_entry(entry_lookup_list_t *lookup_entry, char *filename) 158 { 159 file_info_t *file_info_entry; 160 int error; 161 struct stat file_status; 162 bool is_new_entry = false; 163 164 /* Check if the file already exists in the array */ 165 file_info_entry = find_file_info_from_uuid(&lookup_entry->name_uuid); 166 if (file_info_entry == NULL) { 167 /* The file does not exist in the current list; take the next 168 * one available in the file_info list. 'file_info_count' is 169 * incremented in case of successful update at the end of the 170 * function. 171 */ 172 file_info_entry = &files[file_info_count]; 173 is_new_entry = true; 174 175 /* Copy the uuid for the new entry */ 176 copy_uuid(&file_info_entry->name_uuid, 177 &lookup_entry->name_uuid); 178 } 179 180 /* Get the file information for entry */ 181 error = stat(filename, &file_status); 182 if (error != 0) { 183 printf("Error: Cannot get information for file \"%s\": %s\n", 184 filename, strerror(errno)); 185 return errno; 186 } 187 file_info_entry->filename = filename; 188 file_info_entry->size = (unsigned int)file_status.st_size; 189 file_info_entry->entry = lookup_entry; 190 191 /* Increment the file_info counter on success if it is new file entry */ 192 if (is_new_entry) { 193 file_info_count++; 194 195 /* Ensure we do not overflow */ 196 if (file_info_count > MAX_FILES) { 197 printf("ERROR: Too many files in Package\n"); 198 return 1; 199 } 200 } 201 202 return 0; 203 } 204 205 206 static int write_memory_to_file(const uint8_t *start, const char *filename, 207 unsigned int size) 208 { 209 FILE *stream; 210 unsigned int bytes_written; 211 212 /* Write the packed file out to the filesystem */ 213 stream = fopen(filename, "r+"); 214 if (stream == NULL) { 215 stream = fopen(filename, "w"); 216 if (stream == NULL) { 217 printf("Error: Cannot create output file \"%s\": %s\n", 218 filename, strerror(errno)); 219 return errno; 220 } else { 221 printf("Creating \"%s\"\n", filename); 222 } 223 } else { 224 printf("Updating \"%s\"\n", filename); 225 } 226 227 bytes_written = fwrite(start, sizeof(uint8_t), size, stream); 228 fclose(stream); 229 230 if (bytes_written != size) { 231 printf("Error: Incorrect write for file \"%s\": Size=%u," 232 "Written=%u bytes.\n", filename, size, bytes_written); 233 return EIO; 234 } 235 236 return 0; 237 } 238 239 240 static int read_file_to_memory(void *memory, const file_info_t *info) 241 { 242 FILE *stream; 243 unsigned int bytes_read; 244 245 /* If the file_info is defined by its filename we need to load it */ 246 if (info->filename) { 247 /* Read image from filesystem */ 248 stream = fopen(info->filename, "r"); 249 if (stream == NULL) { 250 printf("Error: Cannot open file \"%s\": %s\n", 251 info->filename, strerror(errno)); 252 return errno; 253 } 254 255 bytes_read = (unsigned int)fread(memory, sizeof(uint8_t), 256 info->size, stream); 257 fclose(stream); 258 if (bytes_read != info->size) { 259 printf("Error: Incomplete read for file \"%s\":" 260 "Size=%u, Read=%u bytes.\n", info->filename, 261 info->size, bytes_read); 262 return EIO; 263 } 264 } else { 265 if (info->image_buffer == NULL) { 266 printf("ERROR: info->image_buffer = NULL\n"); 267 return EIO; 268 } 269 /* Copy the file_info buffer (extracted from the existing 270 * image package) into the new buffer. 271 */ 272 memcpy(memory, info->image_buffer, info->size); 273 } 274 275 return 0; 276 } 277 278 279 /* Create the image package file */ 280 static int pack_images(const char *fip_filename) 281 { 282 int status; 283 uint8_t *fip_base_address; 284 void *entry_address; 285 fip_toc_header_t *toc_header; 286 fip_toc_entry_t *toc_entry; 287 unsigned int entry_index; 288 unsigned int toc_size; 289 unsigned int fip_size; 290 unsigned int entry_offset_address; 291 unsigned int payload_size = 0; 292 293 /* Validate filename */ 294 if ((fip_filename == NULL) || (strcmp(fip_filename, "") == 0)) { 295 return EINVAL; 296 } 297 298 /* Payload size calculation */ 299 for (entry_index = 0; entry_index < file_info_count; entry_index++) { 300 payload_size += files[entry_index].size; 301 } 302 303 /* Allocate memory for entire package, including the final null entry */ 304 toc_size = (sizeof(fip_toc_header_t) + 305 (sizeof(fip_toc_entry_t) * (file_info_count + 1))); 306 fip_size = toc_size + payload_size; 307 fip_base_address = malloc(fip_size); 308 if (fip_base_address == NULL) { 309 printf("Error: Can't allocate enough memory to create package." 310 "Process aborted.\n"); 311 return ENOMEM; 312 } 313 memset(fip_base_address, 0, fip_size); 314 315 /* Create ToC Header */ 316 toc_header = (fip_toc_header_t *)fip_base_address; 317 toc_header->name = TOC_HEADER_NAME; 318 toc_header->serial_number = TOC_HEADER_SERIAL_NUMBER; 319 toc_header->flags = 0; 320 321 toc_entry = (fip_toc_entry_t *)(fip_base_address + 322 sizeof(fip_toc_header_t)); 323 324 /* Calculate the starting address of the first image, right after the 325 * toc header. 326 */ 327 entry_offset_address = toc_size; 328 entry_index = 0; 329 330 /* Create the package in memory. */ 331 for (entry_index = 0; entry_index < file_info_count; entry_index++) { 332 entry_address = (fip_base_address + entry_offset_address); 333 status = read_file_to_memory(entry_address, 334 &files[entry_index]); 335 if (status != 0) { 336 printf("Error: While reading \"%s\" from filesystem.\n", 337 files[entry_index].filename); 338 return status; 339 } 340 341 copy_uuid(&toc_entry->uuid, &files[entry_index].name_uuid); 342 toc_entry->offset_address = entry_offset_address; 343 toc_entry->size = files[entry_index].size; 344 toc_entry->flags = 0; 345 entry_offset_address += toc_entry->size; 346 toc_entry++; 347 } 348 349 /* Add a null uuid entry to mark the end of toc entries */ 350 copy_uuid(&toc_entry->uuid, &uuid_null); 351 toc_entry->offset_address = entry_offset_address; 352 toc_entry->size = 0; 353 toc_entry->flags = 0; 354 355 /* Save the package to file */ 356 status = write_memory_to_file(fip_base_address, fip_filename, fip_size); 357 if (status != 0) { 358 printf("Error: Failed while writing package to file \"%s\" " 359 "with status=%d.\n", fip_filename, status); 360 return status; 361 } 362 return 0; 363 } 364 365 366 static void dump_toc(void) 367 { 368 unsigned int index = 0; 369 unsigned int image_offset; 370 unsigned int image_size = 0; 371 372 image_offset = sizeof(fip_toc_header_t) + 373 (sizeof(fip_toc_entry_t) * (file_info_count + 1)); 374 375 printf("Firmware Image Package ToC:\n"); 376 printf("---------------------------\n"); 377 for (index = 0; index < file_info_count; index++) { 378 if (files[index].entry) { 379 printf("- %s: ", files[index].entry->name); 380 } else { 381 printf("- Unknown entry: "); 382 } 383 image_size = files[index].size; 384 385 printf("offset=0x%X, size=0x%X\n", image_offset, image_size); 386 image_offset += image_size; 387 388 if (files[index].filename) { 389 printf(" file: '%s'\n", files[index].filename); 390 } 391 } 392 printf("---------------------------\n"); 393 } 394 395 396 /* Read and load existing package into memory. */ 397 static int parse_fip(const char *fip_filename) 398 { 399 FILE *fip; 400 char *fip_buffer; 401 char *fip_buffer_end; 402 int fip_size, read_fip_size; 403 fip_toc_header_t *toc_header; 404 fip_toc_entry_t *toc_entry; 405 bool found_last_toc_entry = false; 406 file_info_t *file_info_entry; 407 int status = -1; 408 struct stat st; 409 410 fip = fopen(fip_filename, "r"); 411 if (fip == NULL) { 412 /* If the fip does not exist just return, it should not be 413 * considered as an error. The package will be created later 414 */ 415 status = 0; 416 goto parse_fip_return; 417 } 418 419 if (stat(fip_filename, &st) != 0) { 420 status = errno; 421 goto parse_fip_fclose; 422 } else { 423 fip_size = (int)st.st_size; 424 } 425 426 /* Allocate a buffer to read the package */ 427 fip_buffer = (char *)malloc(fip_size); 428 if (fip_buffer == NULL) { 429 printf("ERROR: Cannot allocate %d bytes.\n", fip_size); 430 status = errno; 431 goto parse_fip_fclose; 432 } 433 fip_buffer_end = fip_buffer + fip_size; 434 435 /* Read the file */ 436 read_fip_size = fread(fip_buffer, sizeof(char), fip_size, fip); 437 if (read_fip_size != fip_size) { 438 printf("ERROR: Cannot read the FIP.\n"); 439 status = EIO; 440 goto parse_fip_free; 441 } 442 fclose(fip); 443 fip = NULL; 444 445 /* The package must at least contain the ToC Header */ 446 if (fip_size < sizeof(fip_toc_header_t)) { 447 printf("ERROR: Given FIP is smaller than the ToC header.\n"); 448 status = EINVAL; 449 goto parse_fip_free; 450 } 451 /* Set the ToC Header at the base of the buffer */ 452 toc_header = (fip_toc_header_t *)fip_buffer; 453 /* The first toc entry should be just after the ToC header */ 454 toc_entry = (fip_toc_entry_t *)(toc_header + 1); 455 456 /* While the ToC entry is contained into the buffer */ 457 int cnt = 0; 458 while (((char *)toc_entry + sizeof(fip_toc_entry_t)) < fip_buffer_end) { 459 cnt++; 460 /* Check if the ToC Entry is the last one */ 461 if (compare_uuids(&toc_entry->uuid, &uuid_null) == 0) { 462 found_last_toc_entry = true; 463 status = 0; 464 break; 465 } 466 467 /* Add the entry into file_info */ 468 469 /* Get the new entry in the array and clear it */ 470 file_info_entry = &files[file_info_count++]; 471 memset(file_info_entry, 0, sizeof(file_info_t)); 472 473 /* Copy the info from the ToC entry */ 474 copy_uuid(&file_info_entry->name_uuid, &toc_entry->uuid); 475 file_info_entry->image_buffer = fip_buffer + 476 toc_entry->offset_address; 477 file_info_entry->size = toc_entry->size; 478 479 /* Check if there is a corresponding entry in lookup table */ 480 file_info_entry->entry = 481 get_entry_lookup_from_uuid(&toc_entry->uuid); 482 483 /* Go to the next ToC entry */ 484 toc_entry++; 485 } 486 487 if (!found_last_toc_entry) { 488 printf("ERROR: Given FIP does not have an end ToC entry.\n"); 489 status = EINVAL; 490 goto parse_fip_free; 491 } else { 492 /* All is well, we should not free any of the loaded images */ 493 goto parse_fip_fclose; 494 } 495 496 parse_fip_free: 497 if (fip_buffer != NULL) { 498 free(fip_buffer); 499 fip_buffer = NULL; 500 } 501 502 parse_fip_fclose: 503 if (fip != NULL) { 504 fclose(fip); 505 } 506 507 parse_fip_return: 508 return status; 509 } 510 511 512 /* Parse all command-line options and return the FIP name if present. */ 513 static char *get_filename(int argc, char **argv, struct option *options) 514 { 515 int c; 516 char *filename = NULL; 517 518 /* Reset option pointer so we parse all args. starts at 1. 519 * The filename is the only argument that does not have an option flag. 520 */ 521 optind = 1; 522 while (1) { 523 c = getopt_long(argc, argv, "", options, NULL); 524 if (c == -1) 525 break; 526 527 if (c == '?') { 528 /* Failed to parse an option. Fail. */ 529 return NULL; 530 } 531 } 532 533 /* Only one argument left then it is the filename. 534 * We dont expect any other options 535 */ 536 if (optind + 1 == argc) 537 filename = argv[optind]; 538 539 return filename; 540 } 541 542 543 /* Work through command-line options */ 544 static int parse_cmdline(int argc, char **argv, struct option *options, 545 int *do_pack) 546 { 547 int c; 548 int status = 0; 549 int option_index = 0; 550 entry_lookup_list_t *lookup_entry; 551 int do_dump = 0; 552 553 /* restart parse to process all options. starts at 1. */ 554 optind = 1; 555 while (1) { 556 c = getopt_long(argc, argv, "", options, &option_index); 557 if (c == -1) 558 break; 559 560 switch (c) { 561 case OPT_TOC_ENTRY: 562 if (optarg) { 563 /* Does the option expect a filename. */ 564 lookup_entry = &toc_entry_lookup_list[option_index]; 565 if (lookup_entry->flags & FLAG_FILENAME) { 566 status = add_file_info_entry(lookup_entry, optarg); 567 if (status != 0) { 568 printf("Failed to process %s\n", 569 options[option_index].name); 570 return status; 571 } else { 572 /* Update package */ 573 *do_pack = 1; 574 } 575 } 576 } 577 break; 578 579 case OPT_DUMP: 580 do_dump = 1; 581 continue; 582 583 case OPT_HELP: 584 print_usage(); 585 exit(0); 586 587 default: 588 /* Unrecognised options are caught in get_filename() */ 589 break; 590 } 591 } 592 593 594 /* Do not dump toc if we have an error as it could hide the error */ 595 if ((status == 0) && (do_dump)) { 596 dump_toc(); 597 } 598 599 return status; 600 601 } 602 603 int main(int argc, char **argv) 604 { 605 int i; 606 int status; 607 char *fip_filename; 608 int do_pack = 0; 609 610 /* Clear file list table. */ 611 memset(files, 0, sizeof(files)); 612 613 /* Initialise for getopt_long(). 614 * Use image table as defined at top of file to get options. 615 * Add 'dump' option, 'help' option and end marker. 616 */ 617 static struct option long_options[(sizeof(toc_entry_lookup_list)/ 618 sizeof(entry_lookup_list_t)) + 2]; 619 620 for (i = 0; 621 /* -1 because we dont want to process end marker in toc table */ 622 i < sizeof(toc_entry_lookup_list)/sizeof(entry_lookup_list_t) - 1; 623 i++) { 624 long_options[i].name = toc_entry_lookup_list[i].command_line_name; 625 /* The only flag defined at the moment is for a FILENAME */ 626 long_options[i].has_arg = toc_entry_lookup_list[i].flags ? 1 : 0; 627 long_options[i].flag = 0; 628 long_options[i].val = OPT_TOC_ENTRY; 629 } 630 631 /* Add '--dump' option */ 632 long_options[i].name = "dump"; 633 long_options[i].has_arg = 0; 634 long_options[i].flag = 0; 635 long_options[i].val = OPT_DUMP; 636 637 /* Add '--help' option */ 638 long_options[++i].name = "help"; 639 long_options[i].has_arg = 0; 640 long_options[i].flag = 0; 641 long_options[i].val = OPT_HELP; 642 643 /* Zero the last entry (required) */ 644 long_options[++i].name = 0; 645 long_options[i].has_arg = 0; 646 long_options[i].flag = 0; 647 long_options[i].val = 0; 648 649 #ifdef DEBUG 650 /* Print all supported options */ 651 for (i = 0; i < sizeof(long_options)/sizeof(struct option); i++) { 652 printf("long opt (%d) : name = %s\n", i, long_options[i].name); 653 } 654 #endif /* DEBUG */ 655 656 /* As the package may already exist and is to be updated we need to get 657 * the filename from the arguments and load from it. 658 * NOTE: As this is the first function to look at the program arguments 659 * it causes a failure if bad options were provided. 660 */ 661 fip_filename = get_filename(argc, argv, long_options); 662 663 /* Try to open the file and load it into memory */ 664 if (fip_filename != NULL) { 665 status = parse_fip(fip_filename); 666 if (status != 0) { 667 return status; 668 } 669 } 670 671 /* Work through provided program arguments and perform actions */ 672 status = parse_cmdline(argc, argv, long_options, &do_pack); 673 if (status != 0) { 674 return status; 675 }; 676 677 if (fip_filename == NULL) { 678 printf("ERROR: Missing FIP filename\n"); 679 print_usage(); 680 return 0; 681 } 682 683 /* Processed all command line options. Create/update the package if 684 * required. 685 */ 686 if (do_pack) { 687 status = pack_images(fip_filename); 688 if (status != 0) { 689 printf("Failed to create package (status = %d).\n", 690 status); 691 } 692 } 693 694 return status; 695 } 696