1 #define _LARGEFILE64_SOURCE 2 3 #define LOG_TAG "f2fs_sparseblock" 4 5 6 #include <cutils/log.h> 7 #include <fcntl.h> 8 #include <f2fs_fs.h> 9 #include <linux/types.h> 10 #include <sys/stat.h> 11 #include "f2fs_sparseblock.h" 12 13 14 #define D_DISP_u32(ptr, member) \ 15 do { \ 16 SLOGD("%-30s" "\t\t[0x%#08x : %u]\n", \ 17 #member, le32_to_cpu((ptr)->member), le32_to_cpu((ptr)->member) ); \ 18 } while (0); 19 20 #define D_DISP_u64(ptr, member) \ 21 do { \ 22 SLOGD("%-30s" "\t\t[0x%#016llx : %llu]\n", \ 23 #member, le64_to_cpu((ptr)->member), le64_to_cpu((ptr)->member) ); \ 24 } while (0); 25 26 #define segno_in_journal(sum, i) (sum->sit_j.entries[i].segno) 27 28 #define sit_in_journal(sum, i) (sum->sit_j.entries[i].se) 29 30 static void dbg_print_raw_sb_info(struct f2fs_super_block *sb) 31 { 32 SLOGD("\n"); 33 SLOGD("+--------------------------------------------------------+\n"); 34 SLOGD("| Super block |\n"); 35 SLOGD("+--------------------------------------------------------+\n"); 36 37 D_DISP_u32(sb, magic); 38 D_DISP_u32(sb, major_ver); 39 D_DISP_u32(sb, minor_ver); 40 D_DISP_u32(sb, log_sectorsize); 41 D_DISP_u32(sb, log_sectors_per_block); 42 43 D_DISP_u32(sb, log_blocksize); 44 D_DISP_u32(sb, log_blocks_per_seg); 45 D_DISP_u32(sb, segs_per_sec); 46 D_DISP_u32(sb, secs_per_zone); 47 D_DISP_u32(sb, checksum_offset); 48 D_DISP_u64(sb, block_count); 49 50 D_DISP_u32(sb, section_count); 51 D_DISP_u32(sb, segment_count); 52 D_DISP_u32(sb, segment_count_ckpt); 53 D_DISP_u32(sb, segment_count_sit); 54 D_DISP_u32(sb, segment_count_nat); 55 56 D_DISP_u32(sb, segment_count_ssa); 57 D_DISP_u32(sb, segment_count_main); 58 D_DISP_u32(sb, segment0_blkaddr); 59 60 D_DISP_u32(sb, cp_blkaddr); 61 D_DISP_u32(sb, sit_blkaddr); 62 D_DISP_u32(sb, nat_blkaddr); 63 D_DISP_u32(sb, ssa_blkaddr); 64 D_DISP_u32(sb, main_blkaddr); 65 66 D_DISP_u32(sb, root_ino); 67 D_DISP_u32(sb, node_ino); 68 D_DISP_u32(sb, meta_ino); 69 D_DISP_u32(sb, cp_payload); 70 SLOGD("\n"); 71 } 72 static void dbg_print_raw_ckpt_struct(struct f2fs_checkpoint *cp) 73 { 74 SLOGD("\n"); 75 SLOGD("+--------------------------------------------------------+\n"); 76 SLOGD("| Checkpoint |\n"); 77 SLOGD("+--------------------------------------------------------+\n"); 78 79 D_DISP_u64(cp, checkpoint_ver); 80 D_DISP_u64(cp, user_block_count); 81 D_DISP_u64(cp, valid_block_count); 82 D_DISP_u32(cp, rsvd_segment_count); 83 D_DISP_u32(cp, overprov_segment_count); 84 D_DISP_u32(cp, free_segment_count); 85 86 D_DISP_u32(cp, alloc_type[CURSEG_HOT_NODE]); 87 D_DISP_u32(cp, alloc_type[CURSEG_WARM_NODE]); 88 D_DISP_u32(cp, alloc_type[CURSEG_COLD_NODE]); 89 D_DISP_u32(cp, cur_node_segno[0]); 90 D_DISP_u32(cp, cur_node_segno[1]); 91 D_DISP_u32(cp, cur_node_segno[2]); 92 93 D_DISP_u32(cp, cur_node_blkoff[0]); 94 D_DISP_u32(cp, cur_node_blkoff[1]); 95 D_DISP_u32(cp, cur_node_blkoff[2]); 96 97 98 D_DISP_u32(cp, alloc_type[CURSEG_HOT_DATA]); 99 D_DISP_u32(cp, alloc_type[CURSEG_WARM_DATA]); 100 D_DISP_u32(cp, alloc_type[CURSEG_COLD_DATA]); 101 D_DISP_u32(cp, cur_data_segno[0]); 102 D_DISP_u32(cp, cur_data_segno[1]); 103 D_DISP_u32(cp, cur_data_segno[2]); 104 105 D_DISP_u32(cp, cur_data_blkoff[0]); 106 D_DISP_u32(cp, cur_data_blkoff[1]); 107 D_DISP_u32(cp, cur_data_blkoff[2]); 108 109 D_DISP_u32(cp, ckpt_flags); 110 D_DISP_u32(cp, cp_pack_total_block_count); 111 D_DISP_u32(cp, cp_pack_start_sum); 112 D_DISP_u32(cp, valid_node_count); 113 D_DISP_u32(cp, valid_inode_count); 114 D_DISP_u32(cp, next_free_nid); 115 D_DISP_u32(cp, sit_ver_bitmap_bytesize); 116 D_DISP_u32(cp, nat_ver_bitmap_bytesize); 117 D_DISP_u32(cp, checksum_offset); 118 D_DISP_u64(cp, elapsed_time); 119 120 D_DISP_u32(cp, sit_nat_version_bitmap[0]); 121 SLOGD("\n\n"); 122 } 123 124 static void dbg_print_info_struct(struct f2fs_info *info) 125 { 126 SLOGD("\n"); 127 SLOGD("+--------------------------------------------------------+\n"); 128 SLOGD("| F2FS_INFO |\n"); 129 SLOGD("+--------------------------------------------------------+\n"); 130 SLOGD("blocks_per_segment: %"PRIu64, info->blocks_per_segment); 131 SLOGD("block_size: %d", info->block_size); 132 SLOGD("sit_bmp loc: %p", info->sit_bmp); 133 SLOGD("sit_bmp_size: %d", info->sit_bmp_size); 134 SLOGD("blocks_per_sit: %"PRIu64, info->blocks_per_sit); 135 SLOGD("sit_blocks loc: %p", info->sit_blocks); 136 SLOGD("sit_sums loc: %p", info->sit_sums); 137 SLOGD("sit_sums num: %d", le16_to_cpu(info->sit_sums->n_sits)); 138 unsigned int i; 139 for(i = 0; i < (le16_to_cpu(info->sit_sums->n_sits)); i++) { 140 SLOGD("entry %d in journal entries is for segment %d",i, le32_to_cpu(segno_in_journal(info->sit_sums, i))); 141 } 142 143 SLOGD("cp_blkaddr: %"PRIu64, info->cp_blkaddr); 144 SLOGD("cp_valid_cp_blkaddr: %"PRIu64, info->cp_valid_cp_blkaddr); 145 SLOGD("sit_blkaddr: %"PRIu64, info->sit_blkaddr); 146 SLOGD("nat_blkaddr: %"PRIu64, info->nat_blkaddr); 147 SLOGD("ssa_blkaddr: %"PRIu64, info->ssa_blkaddr); 148 SLOGD("main_blkaddr: %"PRIu64, info->main_blkaddr); 149 SLOGD("total_user_used: %"PRIu64, info->total_user_used); 150 SLOGD("total_blocks: %"PRIu64, info->total_blocks); 151 SLOGD("\n\n"); 152 } 153 154 155 /* read blocks */ 156 static int read_structure(int fd, unsigned long long start, void *buf, ssize_t len) 157 { 158 off64_t ret; 159 160 ret = lseek64(fd, start, SEEK_SET); 161 if (ret < 0) { 162 SLOGE("failed to seek\n"); 163 return ret; 164 } 165 166 ret = read(fd, buf, len); 167 if (ret < 0) { 168 SLOGE("failed to read\n"); 169 return ret; 170 } 171 if (ret != len) { 172 SLOGE("failed to read all\n"); 173 return -1; 174 } 175 return 0; 176 } 177 178 static int read_structure_blk(int fd, unsigned long long start_blk, void *buf, size_t len) 179 { 180 return read_structure(fd, F2FS_BLKSIZE*start_blk, buf, F2FS_BLKSIZE * len); 181 } 182 183 static int read_f2fs_sb(int fd, struct f2fs_super_block *sb) 184 { 185 int rc; 186 rc = read_structure(fd, F2FS_SUPER_OFFSET, sb, sizeof(*sb)); 187 if (le32_to_cpu(sb->magic) != F2FS_SUPER_MAGIC) { 188 SLOGE("Not a valid F2FS super block. Magic:%#08x != %#08x", 189 le32_to_cpu(sb->magic), F2FS_SUPER_MAGIC); 190 return -1; 191 } 192 return 0; 193 } 194 195 unsigned int get_f2fs_filesystem_size_sec(char *dev) 196 { 197 int fd; 198 if ((fd = open(dev, O_RDONLY)) < 0) { 199 SLOGE("Cannot open device to get filesystem size "); 200 return 0; 201 } 202 struct f2fs_super_block sb; 203 if(read_f2fs_sb(fd, &sb)) 204 return 0; 205 return (unsigned int)(le64_to_cpu(sb.block_count)*F2FS_BLKSIZE/DEFAULT_SECTOR_SIZE); 206 } 207 208 static struct f2fs_checkpoint *validate_checkpoint(block_t cp_addr, 209 unsigned long long *version, int fd) 210 { 211 unsigned char *cp_block_1, *cp_block_2; 212 struct f2fs_checkpoint *cp_block, *cp_ret; 213 u64 cp1_version = 0, cp2_version = 0; 214 215 cp_block_1 = malloc(F2FS_BLKSIZE); 216 if (!cp_block_1) 217 return NULL; 218 219 /* Read the 1st cp block in this CP pack */ 220 if (read_structure_blk(fd, cp_addr, cp_block_1, 1)) 221 goto invalid_cp1; 222 223 /* get the version number */ 224 cp_block = (struct f2fs_checkpoint *)cp_block_1; 225 226 cp1_version = le64_to_cpu(cp_block->checkpoint_ver); 227 228 cp_block_2 = malloc(F2FS_BLKSIZE); 229 if (!cp_block_2) { 230 goto invalid_cp1; 231 } 232 /* Read the 2nd cp block in this CP pack */ 233 cp_addr += le32_to_cpu(cp_block->cp_pack_total_block_count) - 1; 234 if (read_structure_blk(fd, cp_addr, cp_block_2, 1)) { 235 goto invalid_cp2; 236 } 237 238 cp_block = (struct f2fs_checkpoint *)cp_block_2; 239 240 cp2_version = le64_to_cpu(cp_block->checkpoint_ver); 241 242 if (cp2_version == cp1_version) { 243 *version = cp2_version; 244 free(cp_block_2); 245 return (struct f2fs_checkpoint *)cp_block_1; 246 } 247 248 /* There must be something wrong with this checkpoint */ 249 invalid_cp2: 250 free(cp_block_2); 251 invalid_cp1: 252 free(cp_block_1); 253 return NULL; 254 } 255 256 int get_valid_checkpoint_info(int fd, struct f2fs_super_block *sb, struct f2fs_checkpoint **cp, struct f2fs_info *info) 257 { 258 struct f2fs_checkpoint *cp_block; 259 260 struct f2fs_checkpoint *cp1, *cp2, *cur_cp; 261 int cur_cp_no; 262 unsigned long blk_size;// = 1<<le32_to_cpu(info->sb->log_blocksize); 263 unsigned long long cp1_version = 0, cp2_version = 0; 264 unsigned long long cp1_start_blk_no; 265 unsigned long long cp2_start_blk_no; 266 u32 bmp_size; 267 268 blk_size = 1U<<le32_to_cpu(sb->log_blocksize); 269 270 /* 271 * Find valid cp by reading both packs and finding most recent one. 272 */ 273 cp1_start_blk_no = le32_to_cpu(sb->cp_blkaddr); 274 cp1 = validate_checkpoint(cp1_start_blk_no, &cp1_version, fd); 275 276 /* The second checkpoint pack should start at the next segment */ 277 cp2_start_blk_no = cp1_start_blk_no + (1 << le32_to_cpu(sb->log_blocks_per_seg)); 278 cp2 = validate_checkpoint(cp2_start_blk_no, &cp2_version, fd); 279 280 if (cp1 && cp2) { 281 if (ver_after(cp2_version, cp1_version)) { 282 cur_cp = cp2; 283 info->cp_valid_cp_blkaddr = cp2_start_blk_no; 284 free(cp1); 285 } else { 286 cur_cp = cp1; 287 info->cp_valid_cp_blkaddr = cp1_start_blk_no; 288 free(cp2); 289 } 290 } else if (cp1) { 291 cur_cp = cp1; 292 info->cp_valid_cp_blkaddr = cp1_start_blk_no; 293 } else if (cp2) { 294 cur_cp = cp2; 295 info->cp_valid_cp_blkaddr = cp2_start_blk_no; 296 } else { 297 goto fail_no_cp; 298 } 299 300 *cp = cur_cp; 301 302 return 0; 303 304 fail_no_cp: 305 SLOGE("Valid Checkpoint not found!!"); 306 return -EINVAL; 307 } 308 309 static int gather_sit_info(int fd, struct f2fs_info *info) 310 { 311 u64 num_segments = (info->total_blocks - info->main_blkaddr 312 + info->blocks_per_segment - 1) / info->blocks_per_segment; 313 u64 num_sit_blocks = (num_segments + SIT_ENTRY_PER_BLOCK - 1) / SIT_ENTRY_PER_BLOCK; 314 u64 sit_block; 315 316 info->sit_blocks = malloc(num_sit_blocks * sizeof(struct f2fs_sit_block)); 317 if (!info->sit_blocks) 318 return -1; 319 320 for(sit_block = 0; sit_block<num_sit_blocks; sit_block++) { 321 off64_t address = info->sit_blkaddr + sit_block; 322 323 if (f2fs_test_bit(sit_block, info->sit_bmp)) 324 address += info->blocks_per_sit; 325 326 SLOGD("Reading cache block starting at block %"PRIu64, address); 327 if (read_structure(fd, address * F2FS_BLKSIZE, &info->sit_blocks[sit_block], sizeof(struct f2fs_sit_block))) { 328 SLOGE("Could not read sit block at block %"PRIu64, address); 329 free(info->sit_blocks); 330 return -1; 331 } 332 } 333 return 0; 334 } 335 336 static inline int is_set_ckpt_flags(struct f2fs_checkpoint *cp, unsigned int f) 337 { 338 unsigned int ckpt_flags = le32_to_cpu(cp->ckpt_flags); 339 return !!(ckpt_flags & f); 340 } 341 342 static inline u64 sum_blk_addr(struct f2fs_checkpoint *cp, struct f2fs_info *info, int base, int type) 343 { 344 return info->cp_valid_cp_blkaddr + le32_to_cpu(cp->cp_pack_total_block_count) 345 - (base + 1) + type; 346 } 347 348 static int get_sit_summary(int fd, struct f2fs_info *info, struct f2fs_checkpoint *cp) 349 { 350 char buffer[F2FS_BLKSIZE]; 351 352 info->sit_sums = calloc(1, sizeof(struct f2fs_summary_block)); 353 if (!info->sit_sums) 354 return -1; 355 356 /* CURSEG_COLD_DATA where the journaled SIT entries are. */ 357 if (is_set_ckpt_flags(cp, CP_COMPACT_SUM_FLAG)) { 358 if (read_structure_blk(fd, info->cp_valid_cp_blkaddr + le32_to_cpu(cp->cp_pack_start_sum), buffer, 1)) 359 return -1; 360 memcpy(&info->sit_sums->n_sits, &buffer[SUM_JOURNAL_SIZE], SUM_JOURNAL_SIZE); 361 } else { 362 u64 blk_addr; 363 if (is_set_ckpt_flags(cp, CP_UMOUNT_FLAG)) 364 blk_addr = sum_blk_addr(cp, info, NR_CURSEG_TYPE, CURSEG_COLD_DATA); 365 else 366 blk_addr = sum_blk_addr(cp, info, NR_CURSEG_DATA_TYPE, CURSEG_COLD_DATA); 367 368 if (read_structure_blk(fd, blk_addr, buffer, 1)) 369 return -1; 370 371 memcpy(info->sit_sums, buffer, sizeof(struct f2fs_summary_block)); 372 } 373 return 0; 374 } 375 376 struct f2fs_info *generate_f2fs_info(int fd) 377 { 378 struct f2fs_super_block *sb = NULL; 379 struct f2fs_checkpoint *cp = NULL; 380 struct f2fs_info *info; 381 382 info = calloc(1, sizeof(*info)); 383 if (!info) { 384 SLOGE("Out of memory!"); 385 return NULL; 386 } 387 388 sb = malloc(sizeof(*sb)); 389 if(!sb) { 390 SLOGE("Out of memory!"); 391 free(info); 392 return NULL; 393 } 394 if (read_f2fs_sb(fd, sb)) { 395 SLOGE("Failed to read superblock"); 396 free(info); 397 free(sb); 398 return NULL; 399 } 400 dbg_print_raw_sb_info(sb); 401 402 info->cp_blkaddr = le32_to_cpu(sb->cp_blkaddr); 403 info->sit_blkaddr = le32_to_cpu(sb->sit_blkaddr); 404 info->nat_blkaddr = le32_to_cpu(sb->nat_blkaddr); 405 info->ssa_blkaddr = le32_to_cpu(sb->ssa_blkaddr); 406 info->main_blkaddr = le32_to_cpu(sb->main_blkaddr); 407 info->block_size = F2FS_BLKSIZE; 408 info->total_blocks = sb->block_count; 409 info->blocks_per_sit = (le32_to_cpu(sb->segment_count_sit) >> 1) << le32_to_cpu(sb->log_blocks_per_seg); 410 info->blocks_per_segment = 1U << le32_to_cpu(sb->log_blocks_per_seg); 411 412 if (get_valid_checkpoint_info(fd, sb, &cp, info)) 413 goto error; 414 dbg_print_raw_ckpt_struct(cp); 415 416 info->total_user_used = le32_to_cpu(cp->valid_block_count); 417 418 u32 bmp_size = le32_to_cpu(cp->sit_ver_bitmap_bytesize); 419 420 /* get sit validity bitmap */ 421 info->sit_bmp = malloc(bmp_size); 422 if(!info->sit_bmp) { 423 SLOGE("Out of memory!"); 424 goto error; 425 } 426 427 info->sit_bmp_size = bmp_size; 428 if (read_structure(fd, info->cp_valid_cp_blkaddr * F2FS_BLKSIZE 429 + offsetof(struct f2fs_checkpoint, sit_nat_version_bitmap), 430 info->sit_bmp, bmp_size)) { 431 SLOGE("Error getting SIT validity bitmap"); 432 goto error; 433 } 434 435 if (gather_sit_info(fd , info)) { 436 SLOGE("Error getting SIT information"); 437 goto error; 438 } 439 if (get_sit_summary(fd, info, cp)) { 440 SLOGE("Error getting SIT entries in summary area"); 441 goto error; 442 } 443 dbg_print_info_struct(info); 444 return info; 445 error: 446 free(sb); 447 free(cp); 448 free_f2fs_info(info); 449 return NULL; 450 } 451 452 void free_f2fs_info(struct f2fs_info *info) 453 { 454 if (info) { 455 free(info->sit_blocks); 456 info->sit_blocks = NULL; 457 458 free(info->sit_bmp); 459 info->sit_bmp = NULL; 460 461 free(info->sit_sums); 462 info->sit_sums = NULL; 463 } 464 free(info); 465 } 466 467 u64 get_num_blocks_used(struct f2fs_info *info) 468 { 469 return info->main_blkaddr + info->total_user_used; 470 } 471 472 int f2fs_test_bit(unsigned int nr, const char *p) 473 { 474 int mask; 475 char *addr = (char *)p; 476 477 addr += (nr >> 3); 478 mask = 1 << (7 - (nr & 0x07)); 479 return (mask & *addr) != 0; 480 } 481 482 int run_on_used_blocks(u64 startblock, struct f2fs_info *info, int (*func)(u64 pos, void *data), void *data) { 483 struct f2fs_sit_block sit_block_cache; 484 struct f2fs_sit_entry * sit_entry; 485 u64 sit_block_num_cur = 0, segnum = 0, block_offset; 486 u64 block; 487 unsigned int used, found, started = 0, i; 488 489 for (block=startblock; block<info->total_blocks; block++) { 490 /* TODO: Save only relevant portions of metadata */ 491 if (block < info->main_blkaddr) { 492 if (func(block, data)) { 493 SLOGI("func error"); 494 return -1; 495 } 496 } else { 497 /* Main Section */ 498 segnum = (block - info->main_blkaddr)/info->blocks_per_segment; 499 500 /* check the SIT entries in the journal */ 501 found = 0; 502 for(i = 0; i < le16_to_cpu(info->sit_sums->n_sits); i++) { 503 if (le32_to_cpu(segno_in_journal(info->sit_sums, i)) == segnum) { 504 sit_entry = &sit_in_journal(info->sit_sums, i); 505 found = 1; 506 break; 507 } 508 } 509 510 /* get SIT entry from SIT section */ 511 if (!found) { 512 sit_block_num_cur = segnum/SIT_ENTRY_PER_BLOCK; 513 sit_entry = &info->sit_blocks[sit_block_num_cur].entries[segnum % SIT_ENTRY_PER_BLOCK]; 514 } 515 516 block_offset = (block - info->main_blkaddr) % info->blocks_per_segment; 517 518 used = f2fs_test_bit(block_offset, (char *)sit_entry->valid_map); 519 if(used) 520 if (func(block, data)) 521 return -1; 522 } 523 } 524 return 0; 525 } 526 527 struct privdata 528 { 529 int count; 530 int infd; 531 int outfd; 532 char* buf; 533 char *zbuf; 534 int done; 535 struct f2fs_info *info; 536 }; 537 538 539 /* 540 * This is a simple test program. It performs a block to block copy of a 541 * filesystem, replacing blocks identified as unused with 0's. 542 */ 543 544 int copy_used(u64 pos, void *data) 545 { 546 struct privdata *d = data; 547 char *buf; 548 int pdone = (pos*100)/d->info->total_blocks; 549 if (pdone > d->done) { 550 d->done = pdone; 551 printf("Done with %d percent\n", d->done); 552 } 553 554 d->count++; 555 buf = d->buf; 556 if(read_structure_blk(d->infd, (unsigned long long)pos, d->buf, 1)) { 557 printf("Error reading!!!\n"); 558 return -1; 559 } 560 561 off64_t ret; 562 ret = lseek64(d->outfd, pos*F2FS_BLKSIZE, SEEK_SET); 563 if (ret < 0) { 564 SLOGE("failed to seek\n"); 565 return ret; 566 } 567 568 ret = write(d->outfd, d->buf, F2FS_BLKSIZE); 569 if (ret < 0) { 570 SLOGE("failed to write\n"); 571 return ret; 572 } 573 if (ret != F2FS_BLKSIZE) { 574 SLOGE("failed to read all\n"); 575 return -1; 576 } 577 return 0; 578 } 579 580 int main(int argc, char **argv) 581 { 582 if (argc != 3) 583 printf("Usage: %s fs_file_in fs_file_out\n", argv[0]); 584 char *in = argv[1]; 585 char *out = argv[2]; 586 int infd, outfd; 587 588 if ((infd = open(in, O_RDONLY)) < 0) { 589 SLOGE("Cannot open device"); 590 return 0; 591 } 592 if ((outfd = open(out, O_WRONLY|O_CREAT, S_IRUSR | S_IWUSR)) < 0) { 593 SLOGE("Cannot open output"); 594 return 0; 595 } 596 597 struct privdata d; 598 d.infd = infd; 599 d.outfd = outfd; 600 d.count = 0; 601 struct f2fs_info *info = generate_f2fs_info(infd); 602 if (!info) { 603 printf("Failed to generate info!"); 604 return -1; 605 } 606 char *buf = malloc(F2FS_BLKSIZE); 607 char *zbuf = calloc(1, F2FS_BLKSIZE); 608 d.buf = buf; 609 d.zbuf = zbuf; 610 d.done = 0; 611 d.info = info; 612 int expected_count = get_num_blocks_used(info); 613 run_on_used_blocks(0, info, ©_used, &d); 614 printf("Copied %d blocks. Expected to copy %d\n", d.count, expected_count); 615 ftruncate64(outfd, info->total_blocks * F2FS_BLKSIZE); 616 free_f2fs_info(info); 617 free(buf); 618 free(zbuf); 619 close(infd); 620 close(outfd); 621 return 0; 622 } 623