| /** |
| * main.c |
| * |
| * Copyright (c) 2013 Samsung Electronics Co., Ltd. |
| * http://www.samsung.com/ |
| * Copyright (c) 2015 Jaegeuk Kim <jaegeuk@kernel.org> |
| * : implement defrag.f2fs |
| * Copyright (C) 2015 Huawei Ltd. |
| * Hou Pengyang <houpengyang@huawei.com> |
| * Liu Shuoran <liushuoran@huawei.com> |
| * Jaegeuk Kim <jaegeuk@kernel.org> |
| * : add sload.f2fs |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| #include "fsck.h" |
| #include <libgen.h> |
| #include <ctype.h> |
| #include "quotaio.h" |
| |
| struct f2fs_fsck gfsck; |
| |
| void fsck_usage() |
| { |
| MSG(0, "\nUsage: fsck.f2fs [options] device\n"); |
| MSG(0, "[options]:\n"); |
| MSG(0, " -a check/fix potential corruption, reported by f2fs\n"); |
| MSG(0, " -d debug level [default:0]\n"); |
| MSG(0, " -f check/fix entire partition\n"); |
| MSG(0, " -p preen mode [default:0 the same as -a [0|1]]\n"); |
| MSG(0, " -t show directory tree\n"); |
| exit(1); |
| } |
| |
| void dump_usage() |
| { |
| MSG(0, "\nUsage: dump.f2fs [options] device\n"); |
| MSG(0, "[options]:\n"); |
| MSG(0, " -d debug level [default:0]\n"); |
| MSG(0, " -i inode no (hex)\n"); |
| MSG(0, " -n [NAT dump segno from #1~#2 (decimal), for all 0~-1]\n"); |
| MSG(0, " -s [SIT dump segno from #1~#2 (decimal), for all 0~-1]\n"); |
| MSG(0, " -a [SSA dump segno from #1~#2 (decimal), for all 0~-1]\n"); |
| MSG(0, " -b blk_addr (in 4KB)\n"); |
| |
| exit(1); |
| } |
| |
| void defrag_usage() |
| { |
| MSG(0, "\nUsage: defrag.f2fs [options] device\n"); |
| MSG(0, "[options]:\n"); |
| MSG(0, " -d debug level [default:0]\n"); |
| MSG(0, " -s start block address [default: main_blkaddr]\n"); |
| MSG(0, " -l length [default:512 (2MB)]\n"); |
| MSG(0, " -t target block address [default: main_blkaddr + 2MB]\n"); |
| MSG(0, " -i set direction as shrink [default: expand]\n"); |
| exit(1); |
| } |
| |
| void resize_usage() |
| { |
| MSG(0, "\nUsage: resize.f2fs [options] device\n"); |
| MSG(0, "[options]:\n"); |
| MSG(0, " -d debug level [default:0]\n"); |
| MSG(0, " -t target sectors [default: device size]\n"); |
| exit(1); |
| } |
| |
| void sload_usage() |
| { |
| MSG(0, "\nUsage: sload.f2fs [options] device\n"); |
| MSG(0, "[options]:\n"); |
| MSG(0, " -f source directory [path of the source directory]\n"); |
| MSG(0, " -t mount point [prefix of target fs path, default:/]\n"); |
| MSG(0, " -d debug level [default:0]\n"); |
| exit(1); |
| } |
| |
| static int is_digits(char *optarg) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < strlen(optarg); i++) |
| if (!isdigit(optarg[i])) |
| break; |
| return i == strlen(optarg); |
| } |
| |
| static void error_out(char *prog) |
| { |
| if (!strcmp("fsck.f2fs", prog)) |
| fsck_usage(); |
| else if (!strcmp("dump.f2fs", prog)) |
| dump_usage(); |
| else if (!strcmp("defrag.f2fs", prog)) |
| defrag_usage(); |
| else if (!strcmp("resize.f2fs", prog)) |
| resize_usage(); |
| else if (!strcmp("sload.f2fs", prog)) |
| sload_usage(); |
| else |
| MSG(0, "\nWrong progam.\n"); |
| } |
| |
| void f2fs_parse_options(int argc, char *argv[]) |
| { |
| int option = 0; |
| char *prog = basename(argv[0]); |
| int err = NOERROR; |
| |
| if (argc < 2) { |
| MSG(0, "\tError: Device not specified\n"); |
| error_out(prog); |
| } |
| |
| if (!strcmp("fsck.f2fs", prog)) { |
| const char *option_string = ":ad:fp:t"; |
| |
| c.func = FSCK; |
| while ((option = getopt(argc, argv, option_string)) != EOF) { |
| switch (option) { |
| case 'a': |
| c.auto_fix = 1; |
| MSG(0, "Info: Fix the reported corruption.\n"); |
| break; |
| case 'p': |
| /* preen mode has different levels: |
| * 0: default level, the same as -a |
| * 1: check meta |
| */ |
| if (optarg[0] == '-') { |
| c.preen_mode = PREEN_MODE_0; |
| optind--; |
| break; |
| } else if (!is_digits(optarg)) { |
| err = EWRONG_OPT; |
| break; |
| } |
| c.preen_mode = atoi(optarg); |
| if (c.preen_mode < 0) |
| c.preen_mode = PREEN_MODE_0; |
| else if (c.preen_mode >= PREEN_MODE_MAX) |
| c.preen_mode = PREEN_MODE_MAX - 1; |
| if (c.preen_mode == PREEN_MODE_0) |
| c.auto_fix = 1; |
| MSG(0, "Info: Fix the reported corruption in " |
| "preen mode %d\n", c.preen_mode); |
| break; |
| case 'd': |
| if (optarg[0] == '-') { |
| err = ENEED_ARG; |
| break; |
| } else if (!is_digits(optarg)) { |
| err = EWRONG_OPT; |
| break; |
| } |
| c.dbg_lv = atoi(optarg); |
| MSG(0, "Info: Debug level = %d\n", c.dbg_lv); |
| break; |
| case 'f': |
| c.fix_on = 1; |
| MSG(0, "Info: Force to fix corruption\n"); |
| break; |
| case 't': |
| c.show_dentry = 1; |
| break; |
| |
| |
| case ':': |
| if (optopt == 'p') { |
| MSG(0, "Info: Use default preen mode\n"); |
| c.preen_mode = PREEN_MODE_0; |
| c.auto_fix = 1; |
| } else { |
| option = optopt; |
| err = ENEED_ARG; |
| break; |
| } |
| break; |
| case '?': |
| option = optopt; |
| default: |
| err = EUNKNOWN_OPT; |
| break; |
| } |
| if (err != NOERROR) |
| break; |
| } |
| } else if (!strcmp("dump.f2fs", prog)) { |
| const char *option_string = "d:i:n:s:a:b:"; |
| static struct dump_option dump_opt = { |
| .nid = 0, /* default root ino */ |
| .start_nat = -1, |
| .end_nat = -1, |
| .start_sit = -1, |
| .end_sit = -1, |
| .start_ssa = -1, |
| .end_ssa = -1, |
| .blk_addr = -1, |
| }; |
| |
| c.func = DUMP; |
| while ((option = getopt(argc, argv, option_string)) != EOF) { |
| int ret = 0; |
| |
| switch (option) { |
| case 'd': |
| if (!is_digits(optarg)) { |
| err = EWRONG_OPT; |
| break; |
| } |
| c.dbg_lv = atoi(optarg); |
| MSG(0, "Info: Debug level = %d\n", |
| c.dbg_lv); |
| break; |
| case 'i': |
| if (strncmp(optarg, "0x", 2)) |
| ret = sscanf(optarg, "%d", |
| &dump_opt.nid); |
| else |
| ret = sscanf(optarg, "%x", |
| &dump_opt.nid); |
| break; |
| case 'n': |
| ret = sscanf(optarg, "%d~%d", |
| &dump_opt.start_nat, |
| &dump_opt.end_nat); |
| break; |
| case 's': |
| ret = sscanf(optarg, "%d~%d", |
| &dump_opt.start_sit, |
| &dump_opt.end_sit); |
| break; |
| case 'a': |
| ret = sscanf(optarg, "%d~%d", |
| &dump_opt.start_ssa, |
| &dump_opt.end_ssa); |
| break; |
| case 'b': |
| if (strncmp(optarg, "0x", 2)) |
| ret = sscanf(optarg, "%d", |
| &dump_opt.blk_addr); |
| else |
| ret = sscanf(optarg, "%x", |
| &dump_opt.blk_addr); |
| break; |
| default: |
| err = EUNKNOWN_OPT; |
| break; |
| } |
| ASSERT(ret >= 0); |
| if (err != NOERROR) |
| break; |
| } |
| |
| c.private = &dump_opt; |
| } else if (!strcmp("defrag.f2fs", prog)) { |
| const char *option_string = "d:s:l:t:i"; |
| |
| c.func = DEFRAG; |
| while ((option = getopt(argc, argv, option_string)) != EOF) { |
| int ret = 0; |
| |
| switch (option) { |
| case 'd': |
| if (!is_digits(optarg)) { |
| err = EWRONG_OPT; |
| break; |
| } |
| c.dbg_lv = atoi(optarg); |
| MSG(0, "Info: Debug level = %d\n", |
| c.dbg_lv); |
| break; |
| case 's': |
| if (strncmp(optarg, "0x", 2)) |
| ret = sscanf(optarg, "%"PRIu64"", |
| &c.defrag_start); |
| else |
| ret = sscanf(optarg, "%"PRIx64"", |
| &c.defrag_start); |
| break; |
| case 'l': |
| if (strncmp(optarg, "0x", 2)) |
| ret = sscanf(optarg, "%"PRIu64"", |
| &c.defrag_len); |
| else |
| ret = sscanf(optarg, "%"PRIx64"", |
| &c.defrag_len); |
| break; |
| case 't': |
| if (strncmp(optarg, "0x", 2)) |
| ret = sscanf(optarg, "%"PRIu64"", |
| &c.defrag_target); |
| else |
| ret = sscanf(optarg, "%"PRIx64"", |
| &c.defrag_target); |
| break; |
| case 'i': |
| c.defrag_shrink = 1; |
| break; |
| default: |
| err = EUNKNOWN_OPT; |
| break; |
| } |
| ASSERT(ret >= 0); |
| if (err != NOERROR) |
| break; |
| } |
| } else if (!strcmp("resize.f2fs", prog)) { |
| const char *option_string = "d:t:"; |
| |
| c.func = RESIZE; |
| while ((option = getopt(argc, argv, option_string)) != EOF) { |
| int ret = 0; |
| |
| switch (option) { |
| case 'd': |
| if (!is_digits(optarg)) { |
| err = EWRONG_OPT; |
| break; |
| } |
| c.dbg_lv = atoi(optarg); |
| MSG(0, "Info: Debug level = %d\n", |
| c.dbg_lv); |
| break; |
| case 't': |
| if (strncmp(optarg, "0x", 2)) |
| ret = sscanf(optarg, "%"PRIu64"", |
| &c.target_sectors); |
| else |
| ret = sscanf(optarg, "%"PRIx64"", |
| &c.target_sectors); |
| break; |
| default: |
| err = EUNKNOWN_OPT; |
| break; |
| } |
| ASSERT(ret >= 0); |
| if (err != NOERROR) |
| break; |
| } |
| } else if (!strcmp("sload.f2fs", prog)) { |
| const char *option_string = "d:f:t:"; |
| |
| c.func = SLOAD; |
| while ((option = getopt(argc, argv, option_string)) != EOF) { |
| switch (option) { |
| case 'd': |
| if (!is_digits(optarg)) { |
| err = EWRONG_OPT; |
| break; |
| } |
| c.dbg_lv = atoi(optarg); |
| MSG(0, "Info: Debug level = %d\n", |
| c.dbg_lv); |
| break; |
| case 'f': |
| c.from_dir = (char *)optarg; |
| break; |
| case 't': |
| c.mount_point = (char *)optarg; |
| break; |
| default: |
| err = EUNKNOWN_OPT; |
| break; |
| } |
| if (err != NOERROR) |
| break; |
| } |
| } |
| |
| if (optind >= argc) { |
| MSG(0, "\tError: Device not specified\n"); |
| error_out(prog); |
| } |
| |
| c.devices[0].path = strdup(argv[optind]); |
| if (argc > (optind + 1)) { |
| c.dbg_lv = 0; |
| err = EUNKNOWN_ARG; |
| } |
| if (err == NOERROR) |
| return; |
| |
| /* print out error */ |
| switch (err) { |
| case EWRONG_OPT: |
| MSG(0, "\tError: Wrong option -%c %s\n", option, optarg); |
| break; |
| case ENEED_ARG: |
| MSG(0, "\tError: Need argument for -%c\n", option); |
| break; |
| case EUNKNOWN_OPT: |
| MSG(0, "\tError: Unknown option %c\n", option); |
| break; |
| case EUNKNOWN_ARG: |
| MSG(0, "\tError: Unknown argument %s\n", argv[optind]); |
| break; |
| } |
| error_out(prog); |
| } |
| |
| static void do_fsck(struct f2fs_sb_info *sbi) |
| { |
| struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi); |
| u32 flag = le32_to_cpu(ckpt->ckpt_flags); |
| u32 blk_cnt; |
| errcode_t ret; |
| |
| fsck_init(sbi); |
| |
| print_cp_state(flag); |
| |
| if (!c.fix_on && !c.bug_on) { |
| switch (c.preen_mode) { |
| case PREEN_MODE_1: |
| if (fsck_chk_meta(sbi)) { |
| MSG(0, "[FSCK] F2FS metadata [Fail]"); |
| MSG(0, "\tError: meta does not match, " |
| "force check all\n"); |
| } else { |
| MSG(0, "[FSCK] F2FS metadata [Ok..]"); |
| fsck_free(sbi); |
| return; |
| } |
| |
| if (!c.ro) |
| c.fix_on = 1; |
| break; |
| } |
| } else if (c.preen_mode) { |
| /* |
| * we can hit this in 3 situations: |
| * 1. fsck -f, fix_on has already been set to 1 when |
| * parsing options; |
| * 2. fsck -a && CP_FSCK_FLAG is set, fix_on has already |
| * been set to 1 when checking CP_FSCK_FLAG; |
| * 3. fsck -p 1 && error is detected, then bug_on is set, |
| * we set fix_on = 1 here, so that fsck can fix errors |
| * automatically |
| */ |
| c.fix_on = 1; |
| } |
| |
| fsck_chk_quota_node(sbi); |
| |
| /* Traverse all block recursively from root inode */ |
| blk_cnt = 1; |
| |
| if (c.feature & cpu_to_le32(F2FS_FEATURE_QUOTA_INO)) { |
| ret = quota_init_context(sbi); |
| if (ret) { |
| ASSERT_MSG("quota_init_context failure: %d", ret); |
| return; |
| } |
| } |
| fsck_chk_orphan_node(sbi); |
| fsck_chk_node_blk(sbi, NULL, sbi->root_ino_num, |
| F2FS_FT_DIR, TYPE_INODE, &blk_cnt, NULL); |
| fsck_chk_quota_files(sbi); |
| |
| fsck_verify(sbi); |
| fsck_free(sbi); |
| } |
| |
| static void do_dump(struct f2fs_sb_info *sbi) |
| { |
| struct dump_option *opt = (struct dump_option *)c.private; |
| struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi); |
| u32 flag = le32_to_cpu(ckpt->ckpt_flags); |
| |
| if (opt->end_nat == -1) |
| opt->end_nat = NM_I(sbi)->max_nid; |
| if (opt->end_sit == -1) |
| opt->end_sit = SM_I(sbi)->main_segments; |
| if (opt->end_ssa == -1) |
| opt->end_ssa = SM_I(sbi)->main_segments; |
| if (opt->start_nat != -1) |
| nat_dump(sbi); |
| if (opt->start_sit != -1) |
| sit_dump(sbi, opt->start_sit, opt->end_sit); |
| if (opt->start_ssa != -1) |
| ssa_dump(sbi, opt->start_ssa, opt->end_ssa); |
| if (opt->blk_addr != -1) |
| dump_info_from_blkaddr(sbi, opt->blk_addr); |
| if (opt->nid) |
| dump_node(sbi, opt->nid, 0); |
| |
| print_cp_state(flag); |
| |
| } |
| |
| static int do_defrag(struct f2fs_sb_info *sbi) |
| { |
| struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi); |
| |
| if (c.defrag_start > get_sb(block_count)) |
| goto out_range; |
| if (c.defrag_start < SM_I(sbi)->main_blkaddr) |
| c.defrag_start = SM_I(sbi)->main_blkaddr; |
| |
| if (c.defrag_len == 0) |
| c.defrag_len = sbi->blocks_per_seg; |
| |
| if (c.defrag_start + c.defrag_len > get_sb(block_count)) |
| c.defrag_len = get_sb(block_count) - c.defrag_start; |
| |
| if (c.defrag_target == 0) { |
| c.defrag_target = c.defrag_start - 1; |
| if (!c.defrag_shrink) |
| c.defrag_target += c.defrag_len + 1; |
| } |
| |
| if (c.defrag_target < SM_I(sbi)->main_blkaddr || |
| c.defrag_target > get_sb(block_count)) |
| goto out_range; |
| if (c.defrag_target >= c.defrag_start && |
| c.defrag_target < c.defrag_start + c.defrag_len) |
| goto out_range; |
| |
| if (c.defrag_start > c.defrag_target) |
| MSG(0, "Info: Move 0x%"PRIx64" <- [0x%"PRIx64"-0x%"PRIx64"]\n", |
| c.defrag_target, |
| c.defrag_start, |
| c.defrag_start + c.defrag_len - 1); |
| else |
| MSG(0, "Info: Move [0x%"PRIx64"-0x%"PRIx64"] -> 0x%"PRIx64"\n", |
| c.defrag_start, |
| c.defrag_start + c.defrag_len - 1, |
| c.defrag_target); |
| |
| return f2fs_defragment(sbi, c.defrag_start, c.defrag_len, |
| c.defrag_target, c.defrag_shrink); |
| out_range: |
| ASSERT_MSG("Out-of-range [0x%"PRIx64" ~ 0x%"PRIx64"] to 0x%"PRIx64"", |
| c.defrag_start, |
| c.defrag_start + c.defrag_len - 1, |
| c.defrag_target); |
| return -1; |
| } |
| |
| static int do_resize(struct f2fs_sb_info *sbi) |
| { |
| struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi); |
| |
| if (!c.target_sectors) |
| c.target_sectors = c.total_sectors; |
| |
| if (c.target_sectors > c.total_sectors) { |
| ASSERT_MSG("Out-of-range Target=0x%"PRIx64" / 0x%"PRIx64"", |
| c.target_sectors, c.total_sectors); |
| return -1; |
| } |
| |
| /* may different sector size */ |
| if ((c.target_sectors * c.sector_size >> |
| get_sb(log_blocksize)) <= get_sb(block_count)) { |
| ASSERT_MSG("Nothing to resize, now only support resize to expand\n"); |
| return -1; |
| } |
| return f2fs_resize(sbi); |
| } |
| |
| static int do_sload(struct f2fs_sb_info *sbi) |
| { |
| if (!c.from_dir) { |
| MSG(0, "\tError: Need source directory\n"); |
| sload_usage(); |
| return -1; |
| } |
| if (!c.mount_point) |
| c.mount_point = "/"; |
| |
| return f2fs_sload(sbi, c.from_dir, c.mount_point, NULL, NULL); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| struct f2fs_sb_info *sbi; |
| int ret = 0; |
| |
| f2fs_init_configuration(); |
| |
| f2fs_parse_options(argc, argv); |
| |
| if (f2fs_devs_are_umounted() < 0) { |
| if (errno == EBUSY) |
| return -1; |
| if (!c.ro || c.func == DEFRAG) { |
| MSG(0, "\tError: Not available on mounted device!\n"); |
| return -1; |
| } |
| |
| /* allow ro-mounted partition */ |
| MSG(0, "Info: Check FS only due to RO\n"); |
| c.fix_on = 0; |
| c.auto_fix = 0; |
| } |
| |
| /* Get device */ |
| if (f2fs_get_device_info() < 0) |
| return -1; |
| fsck_again: |
| memset(&gfsck, 0, sizeof(gfsck)); |
| gfsck.sbi.fsck = &gfsck; |
| sbi = &gfsck.sbi; |
| |
| ret = f2fs_do_mount(sbi); |
| if (ret != 0) { |
| if (ret == 1) { |
| MSG(0, "Info: No error was reported\n"); |
| ret = 0; |
| } |
| goto out_err; |
| } |
| |
| switch (c.func) { |
| case FSCK: |
| do_fsck(sbi); |
| break; |
| case DUMP: |
| do_dump(sbi); |
| break; |
| #ifndef WITH_ANDROID |
| case DEFRAG: |
| ret = do_defrag(sbi); |
| if (ret) |
| goto out_err; |
| break; |
| case RESIZE: |
| if (do_resize(sbi)) |
| goto out_err; |
| break; |
| case SLOAD: |
| do_sload(sbi); |
| break; |
| #endif |
| } |
| |
| f2fs_do_umount(sbi); |
| |
| if (c.func == FSCK && c.bug_on) { |
| if (!c.ro && c.fix_on == 0 && c.auto_fix == 0) { |
| char ans[255] = {0}; |
| retry: |
| printf("Do you want to fix this partition? [Y/N] "); |
| ret = scanf("%s", ans); |
| ASSERT(ret >= 0); |
| if (!strcasecmp(ans, "y")) |
| c.fix_on = 1; |
| else if (!strcasecmp(ans, "n")) |
| c.fix_on = 0; |
| else |
| goto retry; |
| |
| if (c.fix_on) |
| goto fsck_again; |
| } |
| } |
| f2fs_finalize_device(); |
| |
| printf("\nDone.\n"); |
| return 0; |
| |
| out_err: |
| if (sbi->ckpt) |
| free(sbi->ckpt); |
| if (sbi->raw_super) |
| free(sbi->raw_super); |
| return ret; |
| } |