1 /* 2 * (C) Copyright 2011 - 2012 Samsung Electronics 3 * EXT4 filesystem implementation in Uboot by 4 * Uma Shankar <uma.shankar@samsung.com> 5 * Manjunatha C Achar <a.manjunatha@samsung.com> 6 * 7 * Journal data structures and headers for Journaling feature of ext4 8 * have been referred from JBD2 (Journaling Block device 2) 9 * implementation in Linux Kernel. 10 * Written by Stephen C. Tweedie <sct@redhat.com> 11 * 12 * Copyright 1998-2000 Red Hat, Inc --- All Rights Reserved 13 * SPDX-License-Identifier: GPL-2.0+ 14 */ 15 16 #include <common.h> 17 #include <ext4fs.h> 18 #include <malloc.h> 19 #include <ext_common.h> 20 #include "ext4_common.h" 21 22 static struct revoke_blk_list *revk_blk_list; 23 static struct revoke_blk_list *prev_node; 24 static int first_node = true; 25 26 int gindex; 27 int gd_index; 28 int jrnl_blk_idx; 29 struct journal_log *journal_ptr[MAX_JOURNAL_ENTRIES]; 30 struct dirty_blocks *dirty_block_ptr[MAX_JOURNAL_ENTRIES]; 31 32 int ext4fs_init_journal(void) 33 { 34 int i; 35 char *temp = NULL; 36 struct ext_filesystem *fs = get_fs(); 37 38 /* init globals */ 39 revk_blk_list = NULL; 40 prev_node = NULL; 41 gindex = 0; 42 gd_index = 0; 43 jrnl_blk_idx = 1; 44 45 for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) { 46 journal_ptr[i] = zalloc(sizeof(struct journal_log)); 47 if (!journal_ptr[i]) 48 goto fail; 49 dirty_block_ptr[i] = zalloc(sizeof(struct dirty_blocks)); 50 if (!dirty_block_ptr[i]) 51 goto fail; 52 journal_ptr[i]->buf = NULL; 53 journal_ptr[i]->blknr = -1; 54 55 dirty_block_ptr[i]->buf = NULL; 56 dirty_block_ptr[i]->blknr = -1; 57 } 58 59 if (fs->blksz == 4096) { 60 temp = zalloc(fs->blksz); 61 if (!temp) 62 goto fail; 63 journal_ptr[gindex]->buf = zalloc(fs->blksz); 64 if (!journal_ptr[gindex]->buf) 65 goto fail; 66 ext4fs_devread(0, 0, fs->blksz, temp); 67 memcpy(temp + SUPERBLOCK_SIZE, fs->sb, SUPERBLOCK_SIZE); 68 memcpy(journal_ptr[gindex]->buf, temp, fs->blksz); 69 journal_ptr[gindex++]->blknr = 0; 70 free(temp); 71 } else { 72 journal_ptr[gindex]->buf = zalloc(fs->blksz); 73 if (!journal_ptr[gindex]->buf) 74 goto fail; 75 memcpy(journal_ptr[gindex]->buf, fs->sb, SUPERBLOCK_SIZE); 76 journal_ptr[gindex++]->blknr = 1; 77 } 78 79 /* Check the file system state using journal super block */ 80 if (ext4fs_check_journal_state(SCAN)) 81 goto fail; 82 /* Check the file system state using journal super block */ 83 if (ext4fs_check_journal_state(RECOVER)) 84 goto fail; 85 86 return 0; 87 fail: 88 return -1; 89 } 90 91 void ext4fs_dump_metadata(void) 92 { 93 struct ext_filesystem *fs = get_fs(); 94 int i; 95 for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) { 96 if (dirty_block_ptr[i]->blknr == -1) 97 break; 98 put_ext4((uint64_t) ((uint64_t)dirty_block_ptr[i]->blknr * 99 (uint64_t)fs->blksz), dirty_block_ptr[i]->buf, 100 fs->blksz); 101 } 102 } 103 104 void ext4fs_free_journal(void) 105 { 106 int i; 107 for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) { 108 if (dirty_block_ptr[i]->blknr == -1) 109 break; 110 if (dirty_block_ptr[i]->buf) 111 free(dirty_block_ptr[i]->buf); 112 } 113 114 for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) { 115 if (journal_ptr[i]->blknr == -1) 116 break; 117 if (journal_ptr[i]->buf) 118 free(journal_ptr[i]->buf); 119 } 120 121 for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) { 122 if (journal_ptr[i]) 123 free(journal_ptr[i]); 124 if (dirty_block_ptr[i]) 125 free(dirty_block_ptr[i]); 126 } 127 gindex = 0; 128 gd_index = 0; 129 jrnl_blk_idx = 1; 130 } 131 132 int ext4fs_log_gdt(char *gd_table) 133 { 134 struct ext_filesystem *fs = get_fs(); 135 short i; 136 long int var = fs->gdtable_blkno; 137 for (i = 0; i < fs->no_blk_pergdt; i++) { 138 journal_ptr[gindex]->buf = zalloc(fs->blksz); 139 if (!journal_ptr[gindex]->buf) 140 return -ENOMEM; 141 memcpy(journal_ptr[gindex]->buf, gd_table, fs->blksz); 142 gd_table += fs->blksz; 143 journal_ptr[gindex++]->blknr = var++; 144 } 145 146 return 0; 147 } 148 149 /* 150 * This function stores the backup copy of meta data in RAM 151 * journal_buffer -- Buffer containing meta data 152 * blknr -- Block number on disk of the meta data buffer 153 */ 154 int ext4fs_log_journal(char *journal_buffer, long int blknr) 155 { 156 struct ext_filesystem *fs = get_fs(); 157 short i; 158 159 if (!journal_buffer) { 160 printf("Invalid input arguments %s\n", __func__); 161 return -EINVAL; 162 } 163 164 for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) { 165 if (journal_ptr[i]->blknr == -1) 166 break; 167 if (journal_ptr[i]->blknr == blknr) 168 return 0; 169 } 170 171 journal_ptr[gindex]->buf = zalloc(fs->blksz); 172 if (!journal_ptr[gindex]->buf) 173 return -ENOMEM; 174 175 memcpy(journal_ptr[gindex]->buf, journal_buffer, fs->blksz); 176 journal_ptr[gindex++]->blknr = blknr; 177 178 return 0; 179 } 180 181 /* 182 * This function stores the modified meta data in RAM 183 * metadata_buffer -- Buffer containing meta data 184 * blknr -- Block number on disk of the meta data buffer 185 */ 186 int ext4fs_put_metadata(char *metadata_buffer, long int blknr) 187 { 188 struct ext_filesystem *fs = get_fs(); 189 if (!metadata_buffer) { 190 printf("Invalid input arguments %s\n", __func__); 191 return -EINVAL; 192 } 193 dirty_block_ptr[gd_index]->buf = zalloc(fs->blksz); 194 if (!dirty_block_ptr[gd_index]->buf) 195 return -ENOMEM; 196 memcpy(dirty_block_ptr[gd_index]->buf, metadata_buffer, fs->blksz); 197 dirty_block_ptr[gd_index++]->blknr = blknr; 198 199 return 0; 200 } 201 202 void print_revoke_blks(char *revk_blk) 203 { 204 int offset; 205 int max; 206 long int blocknr; 207 struct journal_revoke_header_t *header; 208 209 if (revk_blk == NULL) 210 return; 211 212 header = (struct journal_revoke_header_t *) revk_blk; 213 offset = sizeof(struct journal_revoke_header_t); 214 max = be32_to_cpu(header->r_count); 215 printf("total bytes %d\n", max); 216 217 while (offset < max) { 218 blocknr = be32_to_cpu(*((long int *)(revk_blk + offset))); 219 printf("revoke blknr is %ld\n", blocknr); 220 offset += 4; 221 } 222 } 223 224 static struct revoke_blk_list *_get_node(void) 225 { 226 struct revoke_blk_list *tmp_node; 227 tmp_node = zalloc(sizeof(struct revoke_blk_list)); 228 if (tmp_node == NULL) 229 return NULL; 230 tmp_node->content = NULL; 231 tmp_node->next = NULL; 232 233 return tmp_node; 234 } 235 236 void ext4fs_push_revoke_blk(char *buffer) 237 { 238 struct revoke_blk_list *node = NULL; 239 struct ext_filesystem *fs = get_fs(); 240 if (buffer == NULL) { 241 printf("buffer ptr is NULL\n"); 242 return; 243 } 244 node = _get_node(); 245 if (!node) { 246 printf("_get_node: malloc failed\n"); 247 return; 248 } 249 250 node->content = zalloc(fs->blksz); 251 if (node->content == NULL) 252 return; 253 memcpy(node->content, buffer, fs->blksz); 254 255 if (first_node == true) { 256 revk_blk_list = node; 257 prev_node = node; 258 first_node = false; 259 } else { 260 prev_node->next = node; 261 prev_node = node; 262 } 263 } 264 265 void ext4fs_free_revoke_blks(void) 266 { 267 struct revoke_blk_list *tmp_node = revk_blk_list; 268 struct revoke_blk_list *next_node = NULL; 269 270 while (tmp_node != NULL) { 271 if (tmp_node->content) 272 free(tmp_node->content); 273 tmp_node = tmp_node->next; 274 } 275 276 tmp_node = revk_blk_list; 277 while (tmp_node != NULL) { 278 next_node = tmp_node->next; 279 free(tmp_node); 280 tmp_node = next_node; 281 } 282 283 revk_blk_list = NULL; 284 prev_node = NULL; 285 first_node = true; 286 } 287 288 int check_blknr_for_revoke(long int blknr, int sequence_no) 289 { 290 struct journal_revoke_header_t *header; 291 int offset; 292 int max; 293 long int blocknr; 294 char *revk_blk; 295 struct revoke_blk_list *tmp_revk_node = revk_blk_list; 296 while (tmp_revk_node != NULL) { 297 revk_blk = tmp_revk_node->content; 298 299 header = (struct journal_revoke_header_t *) revk_blk; 300 if (sequence_no < be32_to_cpu(header->r_header.h_sequence)) { 301 offset = sizeof(struct journal_revoke_header_t); 302 max = be32_to_cpu(header->r_count); 303 304 while (offset < max) { 305 blocknr = be32_to_cpu(*((long int *) 306 (revk_blk + offset))); 307 if (blocknr == blknr) 308 goto found; 309 offset += 4; 310 } 311 } 312 tmp_revk_node = tmp_revk_node->next; 313 } 314 315 return -1; 316 317 found: 318 return 0; 319 } 320 321 /* 322 * This function parses the journal blocks and replays the 323 * suceessful transactions. A transaction is successfull 324 * if commit block is found for a descriptor block 325 * The tags in descriptor block contain the disk block 326 * numbers of the metadata to be replayed 327 */ 328 void recover_transaction(int prev_desc_logical_no) 329 { 330 struct ext2_inode inode_journal; 331 struct ext_filesystem *fs = get_fs(); 332 struct journal_header_t *jdb; 333 long int blknr; 334 char *p_jdb; 335 int ofs, flags; 336 int i; 337 struct ext3_journal_block_tag *tag; 338 char *temp_buff = zalloc(fs->blksz); 339 char *metadata_buff = zalloc(fs->blksz); 340 if (!temp_buff || !metadata_buff) 341 goto fail; 342 i = prev_desc_logical_no; 343 ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO, 344 (struct ext2_inode *)&inode_journal); 345 blknr = read_allocated_block((struct ext2_inode *) 346 &inode_journal, i); 347 ext4fs_devread((lbaint_t)blknr * fs->sect_perblk, 0, fs->blksz, 348 temp_buff); 349 p_jdb = (char *)temp_buff; 350 jdb = (struct journal_header_t *) temp_buff; 351 ofs = sizeof(struct journal_header_t); 352 353 do { 354 tag = (struct ext3_journal_block_tag *)&p_jdb[ofs]; 355 ofs += sizeof(struct ext3_journal_block_tag); 356 357 if (ofs > fs->blksz) 358 break; 359 360 flags = be32_to_cpu(tag->flags); 361 if (!(flags & EXT3_JOURNAL_FLAG_SAME_UUID)) 362 ofs += 16; 363 364 i++; 365 debug("\t\ttag %u\n", be32_to_cpu(tag->block)); 366 if (revk_blk_list != NULL) { 367 if (check_blknr_for_revoke(be32_to_cpu(tag->block), 368 be32_to_cpu(jdb->h_sequence)) == 0) 369 continue; 370 } 371 blknr = read_allocated_block(&inode_journal, i); 372 ext4fs_devread((lbaint_t)blknr * fs->sect_perblk, 0, 373 fs->blksz, metadata_buff); 374 put_ext4((uint64_t)((uint64_t)be32_to_cpu(tag->block) * (uint64_t)fs->blksz), 375 metadata_buff, (uint32_t) fs->blksz); 376 } while (!(flags & EXT3_JOURNAL_FLAG_LAST_TAG)); 377 fail: 378 free(temp_buff); 379 free(metadata_buff); 380 } 381 382 void print_jrnl_status(int recovery_flag) 383 { 384 if (recovery_flag == RECOVER) 385 printf("Journal Recovery Completed\n"); 386 else 387 printf("Journal Scan Completed\n"); 388 } 389 390 int ext4fs_check_journal_state(int recovery_flag) 391 { 392 int i; 393 int DB_FOUND = NO; 394 long int blknr; 395 int transaction_state = TRANSACTION_COMPLETE; 396 int prev_desc_logical_no = 0; 397 int curr_desc_logical_no = 0; 398 int ofs, flags; 399 struct ext2_inode inode_journal; 400 struct journal_superblock_t *jsb = NULL; 401 struct journal_header_t *jdb = NULL; 402 char *p_jdb = NULL; 403 struct ext3_journal_block_tag *tag = NULL; 404 char *temp_buff = NULL; 405 char *temp_buff1 = NULL; 406 struct ext_filesystem *fs = get_fs(); 407 408 temp_buff = zalloc(fs->blksz); 409 if (!temp_buff) 410 return -ENOMEM; 411 temp_buff1 = zalloc(fs->blksz); 412 if (!temp_buff1) { 413 free(temp_buff); 414 return -ENOMEM; 415 } 416 417 ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO, &inode_journal); 418 blknr = read_allocated_block(&inode_journal, EXT2_JOURNAL_SUPERBLOCK); 419 ext4fs_devread((lbaint_t)blknr * fs->sect_perblk, 0, fs->blksz, 420 temp_buff); 421 jsb = (struct journal_superblock_t *) temp_buff; 422 423 if (fs->sb->feature_incompat & EXT3_FEATURE_INCOMPAT_RECOVER) { 424 if (recovery_flag == RECOVER) 425 printf("Recovery required\n"); 426 } else { 427 if (recovery_flag == RECOVER) 428 printf("File System is consistent\n"); 429 goto end; 430 } 431 432 if (be32_to_cpu(jsb->s_start) == 0) 433 goto end; 434 435 if (!(jsb->s_feature_compat & 436 cpu_to_be32(JBD2_FEATURE_COMPAT_CHECKSUM))) 437 jsb->s_feature_compat |= 438 cpu_to_be32(JBD2_FEATURE_COMPAT_CHECKSUM); 439 440 i = be32_to_cpu(jsb->s_first); 441 while (1) { 442 blknr = read_allocated_block(&inode_journal, i); 443 memset(temp_buff1, '\0', fs->blksz); 444 ext4fs_devread((lbaint_t)blknr * fs->sect_perblk, 445 0, fs->blksz, temp_buff1); 446 jdb = (struct journal_header_t *) temp_buff1; 447 448 if (be32_to_cpu(jdb->h_blocktype) == 449 EXT3_JOURNAL_DESCRIPTOR_BLOCK) { 450 if (be32_to_cpu(jdb->h_sequence) != 451 be32_to_cpu(jsb->s_sequence)) { 452 print_jrnl_status(recovery_flag); 453 break; 454 } 455 456 curr_desc_logical_no = i; 457 if (transaction_state == TRANSACTION_COMPLETE) 458 transaction_state = TRANSACTION_RUNNING; 459 else 460 return -1; 461 p_jdb = (char *)temp_buff1; 462 ofs = sizeof(struct journal_header_t); 463 do { 464 tag = (struct ext3_journal_block_tag *) 465 &p_jdb[ofs]; 466 ofs += sizeof(struct ext3_journal_block_tag); 467 if (ofs > fs->blksz) 468 break; 469 flags = be32_to_cpu(tag->flags); 470 if (!(flags & EXT3_JOURNAL_FLAG_SAME_UUID)) 471 ofs += 16; 472 i++; 473 debug("\t\ttag %u\n", be32_to_cpu(tag->block)); 474 } while (!(flags & EXT3_JOURNAL_FLAG_LAST_TAG)); 475 i++; 476 DB_FOUND = YES; 477 } else if (be32_to_cpu(jdb->h_blocktype) == 478 EXT3_JOURNAL_COMMIT_BLOCK) { 479 if (be32_to_cpu(jdb->h_sequence) != 480 be32_to_cpu(jsb->s_sequence)) { 481 print_jrnl_status(recovery_flag); 482 break; 483 } 484 485 if (transaction_state == TRANSACTION_RUNNING || 486 (DB_FOUND == NO)) { 487 transaction_state = TRANSACTION_COMPLETE; 488 i++; 489 jsb->s_sequence = 490 cpu_to_be32(be32_to_cpu( 491 jsb->s_sequence) + 1); 492 } 493 prev_desc_logical_no = curr_desc_logical_no; 494 if ((recovery_flag == RECOVER) && (DB_FOUND == YES)) 495 recover_transaction(prev_desc_logical_no); 496 497 DB_FOUND = NO; 498 } else if (be32_to_cpu(jdb->h_blocktype) == 499 EXT3_JOURNAL_REVOKE_BLOCK) { 500 if (be32_to_cpu(jdb->h_sequence) != 501 be32_to_cpu(jsb->s_sequence)) { 502 print_jrnl_status(recovery_flag); 503 break; 504 } 505 if (recovery_flag == SCAN) 506 ext4fs_push_revoke_blk((char *)jdb); 507 i++; 508 } else { 509 debug("Else Case\n"); 510 if (be32_to_cpu(jdb->h_sequence) != 511 be32_to_cpu(jsb->s_sequence)) { 512 print_jrnl_status(recovery_flag); 513 break; 514 } 515 } 516 } 517 518 end: 519 if (recovery_flag == RECOVER) { 520 jsb->s_start = cpu_to_be32(1); 521 jsb->s_sequence = cpu_to_be32(be32_to_cpu(jsb->s_sequence) + 1); 522 /* get the superblock */ 523 ext4_read_superblock((char *)fs->sb); 524 fs->sb->feature_incompat |= EXT3_FEATURE_INCOMPAT_RECOVER; 525 526 /* Update the super block */ 527 put_ext4((uint64_t) (SUPERBLOCK_SIZE), 528 (struct ext2_sblock *)fs->sb, 529 (uint32_t) SUPERBLOCK_SIZE); 530 ext4_read_superblock((char *)fs->sb); 531 532 blknr = read_allocated_block(&inode_journal, 533 EXT2_JOURNAL_SUPERBLOCK); 534 put_ext4((uint64_t) ((uint64_t)blknr * (uint64_t)fs->blksz), 535 (struct journal_superblock_t *)temp_buff, 536 (uint32_t) fs->blksz); 537 ext4fs_free_revoke_blks(); 538 } 539 free(temp_buff); 540 free(temp_buff1); 541 542 return 0; 543 } 544 545 static void update_descriptor_block(long int blknr) 546 { 547 int i; 548 long int jsb_blknr; 549 struct journal_header_t jdb; 550 struct ext3_journal_block_tag tag; 551 struct ext2_inode inode_journal; 552 struct journal_superblock_t *jsb = NULL; 553 char *buf = NULL; 554 char *temp = NULL; 555 struct ext_filesystem *fs = get_fs(); 556 char *temp_buff = zalloc(fs->blksz); 557 if (!temp_buff) 558 return; 559 560 ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO, &inode_journal); 561 jsb_blknr = read_allocated_block(&inode_journal, 562 EXT2_JOURNAL_SUPERBLOCK); 563 ext4fs_devread((lbaint_t)jsb_blknr * fs->sect_perblk, 0, fs->blksz, 564 temp_buff); 565 jsb = (struct journal_superblock_t *) temp_buff; 566 567 jdb.h_blocktype = cpu_to_be32(EXT3_JOURNAL_DESCRIPTOR_BLOCK); 568 jdb.h_magic = cpu_to_be32(EXT3_JOURNAL_MAGIC_NUMBER); 569 jdb.h_sequence = jsb->s_sequence; 570 buf = zalloc(fs->blksz); 571 if (!buf) { 572 free(temp_buff); 573 return; 574 } 575 temp = buf; 576 memcpy(buf, &jdb, sizeof(struct journal_header_t)); 577 temp += sizeof(struct journal_header_t); 578 579 for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) { 580 if (journal_ptr[i]->blknr == -1) 581 break; 582 583 tag.block = cpu_to_be32(journal_ptr[i]->blknr); 584 tag.flags = cpu_to_be32(EXT3_JOURNAL_FLAG_SAME_UUID); 585 memcpy(temp, &tag, sizeof(struct ext3_journal_block_tag)); 586 temp = temp + sizeof(struct ext3_journal_block_tag); 587 } 588 589 tag.block = cpu_to_be32(journal_ptr[--i]->blknr); 590 tag.flags = cpu_to_be32(EXT3_JOURNAL_FLAG_LAST_TAG); 591 memcpy(temp - sizeof(struct ext3_journal_block_tag), &tag, 592 sizeof(struct ext3_journal_block_tag)); 593 put_ext4((uint64_t) ((uint64_t)blknr * (uint64_t)fs->blksz), buf, (uint32_t) fs->blksz); 594 595 free(temp_buff); 596 free(buf); 597 } 598 599 static void update_commit_block(long int blknr) 600 { 601 struct journal_header_t jdb; 602 struct ext_filesystem *fs = get_fs(); 603 char *buf = NULL; 604 struct ext2_inode inode_journal; 605 struct journal_superblock_t *jsb; 606 long int jsb_blknr; 607 char *temp_buff = zalloc(fs->blksz); 608 if (!temp_buff) 609 return; 610 611 ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO, 612 &inode_journal); 613 jsb_blknr = read_allocated_block(&inode_journal, 614 EXT2_JOURNAL_SUPERBLOCK); 615 ext4fs_devread((lbaint_t)jsb_blknr * fs->sect_perblk, 0, fs->blksz, 616 temp_buff); 617 jsb = (struct journal_superblock_t *) temp_buff; 618 619 jdb.h_blocktype = cpu_to_be32(EXT3_JOURNAL_COMMIT_BLOCK); 620 jdb.h_magic = cpu_to_be32(EXT3_JOURNAL_MAGIC_NUMBER); 621 jdb.h_sequence = jsb->s_sequence; 622 buf = zalloc(fs->blksz); 623 if (!buf) { 624 free(temp_buff); 625 return; 626 } 627 memcpy(buf, &jdb, sizeof(struct journal_header_t)); 628 put_ext4((uint64_t) ((uint64_t)blknr * (uint64_t)fs->blksz), buf, (uint32_t) fs->blksz); 629 630 free(temp_buff); 631 free(buf); 632 } 633 634 void ext4fs_update_journal(void) 635 { 636 struct ext2_inode inode_journal; 637 struct ext_filesystem *fs = get_fs(); 638 long int blknr; 639 int i; 640 ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO, &inode_journal); 641 blknr = read_allocated_block(&inode_journal, jrnl_blk_idx++); 642 update_descriptor_block(blknr); 643 for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) { 644 if (journal_ptr[i]->blknr == -1) 645 break; 646 blknr = read_allocated_block(&inode_journal, jrnl_blk_idx++); 647 put_ext4((uint64_t) ((uint64_t)blknr * (uint64_t)fs->blksz), 648 journal_ptr[i]->buf, fs->blksz); 649 } 650 blknr = read_allocated_block(&inode_journal, jrnl_blk_idx++); 651 update_commit_block(blknr); 652 printf("update journal finished\n"); 653 } 654