| /** |
| * inject.c |
| * |
| * Copyright (c) 2024 OPPO Mobile Comm Corp., Ltd. |
| * http://www.oppo.com/ |
| * |
| * 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 <getopt.h> |
| #include "f2fs.h" |
| #include "node.h" |
| #include "inject.h" |
| |
| /* localtion of a sit/nat entry */ |
| enum entry_pos { |
| ENT_IN_JOURNAL, |
| ENT_IN_PACK1, |
| ENT_IN_PACK2 |
| }; |
| |
| /* format of printing entry position */ |
| #define PRENTPOS "%s %d" |
| #define show_entry_pos(pack) (pack) == ENT_IN_JOURNAL ? "journal" : "pack", \ |
| (pack) == ENT_IN_JOURNAL ? 0 : (pack) |
| |
| static void print_raw_nat_entry_info(struct f2fs_nat_entry *ne) |
| { |
| if (!c.dbg_lv) |
| return; |
| |
| DISP_u8(ne, version); |
| DISP_u32(ne, ino); |
| DISP_u32(ne, block_addr); |
| } |
| |
| static void print_raw_sit_entry_info(struct f2fs_sit_entry *se) |
| { |
| int i; |
| |
| if (!c.dbg_lv) |
| return; |
| |
| DISP_u16(se, vblocks); |
| if (c.layout) |
| printf("%-30s ", "valid_map:"); |
| else |
| printf("%-30s\t\t[", "valid_map"); |
| for (i = 0; i < SIT_VBLOCK_MAP_SIZE; i++) |
| printf("%02x", se->valid_map[i]); |
| if (c.layout) |
| printf("\n"); |
| else |
| printf("]\n"); |
| DISP_u64(se, mtime); |
| } |
| |
| static void print_raw_sum_entry_info(struct f2fs_summary *sum) |
| { |
| if (!c.dbg_lv) |
| return; |
| |
| DISP_u32(sum, nid); |
| DISP_u8(sum, version); |
| DISP_u16(sum, ofs_in_node); |
| } |
| |
| static void print_sum_footer_info(struct summary_footer *footer) |
| { |
| if (!c.dbg_lv) |
| return; |
| |
| DISP_u8(footer, entry_type); |
| DISP_u32(footer, check_sum); |
| } |
| |
| static void print_node_footer_info(struct node_footer *footer) |
| { |
| if (!c.dbg_lv) |
| return; |
| |
| DISP_u32(footer, nid); |
| DISP_u32(footer, ino); |
| DISP_u32(footer, flag); |
| DISP_u64(footer, cp_ver); |
| DISP_u32(footer, next_blkaddr); |
| } |
| |
| static void print_raw_dentry_info(struct f2fs_dir_entry *dentry) |
| { |
| if (!c.dbg_lv) |
| return; |
| |
| DISP_u32(dentry, hash_code); |
| DISP_u32(dentry, ino); |
| DISP_u16(dentry, name_len); |
| DISP_u8(dentry, file_type); |
| } |
| |
| void inject_usage(void) |
| { |
| MSG(0, "\nUsage: inject.f2fs [options] device\n"); |
| MSG(0, "[options]:\n"); |
| MSG(0, " -d debug level [default:0]\n"); |
| MSG(0, " -V print the version number and exit\n"); |
| MSG(0, " --mb <member name> which member is injected in a struct\n"); |
| MSG(0, " --val <new value> new value to set\n"); |
| MSG(0, " --str <new string> new string to set\n"); |
| MSG(0, " --idx <slot index> which slot is injected in an array\n"); |
| MSG(0, " --nid <nid> which nid is injected\n"); |
| MSG(0, " --blk <blkaddr> which blkaddr is injected\n"); |
| MSG(0, " --sb <0|1|2> --mb <name> [--idx <index>] --val/str <value/string> inject superblock\n"); |
| MSG(0, " --cp <0|1|2> --mb <name> [--idx <index>] --val <value> inject checkpoint\n"); |
| MSG(0, " --nat <0|1|2> --mb <name> --nid <nid> --val <value> inject nat entry\n"); |
| MSG(0, " --sit <0|1|2> --mb <name> --blk <blk> [--idx <index>] --val <value> inject sit entry\n"); |
| MSG(0, " --ssa --mb <name> --blk <blk> [--idx <index>] --val <value> inject summary entry\n"); |
| MSG(0, " --node --mb <name> --nid <nid> [--idx <index>] --val <value> inject node\n"); |
| MSG(0, " --dent --mb <name> --nid <ino> [--dots <1|2>] --val/str <value/string> inject ino's dentry\n"); |
| MSG(0, " --dry-run do not really inject\n"); |
| |
| exit(1); |
| } |
| |
| static void inject_sb_usage(void) |
| { |
| MSG(0, "inject.f2fs --sb <0|1|2> --mb <name> [--idx <index>] --val/str <value/string>\n"); |
| MSG(0, "[sb]:\n"); |
| MSG(0, " 0: auto select the first super block\n"); |
| MSG(0, " 1: select the first super block\n"); |
| MSG(0, " 2: select the second super block\n"); |
| MSG(0, "[mb]:\n"); |
| MSG(0, " magic: inject magic number\n"); |
| MSG(0, " s_stop_reason: inject s_stop_reason array selected by --idx <index>\n"); |
| MSG(0, " s_errors: inject s_errors array selected by --idx <index>\n"); |
| MSG(0, " feature: inject feature\n"); |
| MSG(0, " devs.path: inject path in devs array selected by --idx <index> specified by --str <string>\n"); |
| } |
| |
| static void inject_cp_usage(void) |
| { |
| MSG(0, "inject.f2fs --cp <0|1|2> --mb <name> [--idx <index>] --val <value> inject checkpoint\n"); |
| MSG(0, "[cp]:\n"); |
| MSG(0, " 0: auto select the current cp pack\n"); |
| MSG(0, " 1: select the first cp pack\n"); |
| MSG(0, " 2: select the second cp pack\n"); |
| MSG(0, "[mb]:\n"); |
| MSG(0, " checkpoint_ver: inject checkpoint_ver\n"); |
| MSG(0, " ckpt_flags: inject ckpt_flags\n"); |
| MSG(0, " cur_node_segno: inject cur_node_segno array selected by --idx <index>\n"); |
| MSG(0, " cur_node_blkoff: inject cur_node_blkoff array selected by --idx <index>\n"); |
| MSG(0, " cur_data_segno: inject cur_data_segno array selected by --idx <index>\n"); |
| MSG(0, " cur_data_blkoff: inject cur_data_blkoff array selected by --idx <index>\n"); |
| MSG(0, " alloc_type: inject alloc_type array selected by --idx <index>\n"); |
| MSG(0, " next_blkaddr: inject next_blkaddr of fsync dnodes selected by --idx <index>\n"); |
| MSG(0, " crc: inject crc checksum\n"); |
| MSG(0, " elapsed_time: inject elapsed_time\n"); |
| } |
| |
| static void inject_nat_usage(void) |
| { |
| MSG(0, "inject.f2fs --nat <0|1|2> --mb <name> --nid <nid> --val <value> inject nat entry\n"); |
| MSG(0, "[nat]:\n"); |
| MSG(0, " 0: auto select the current nat pack\n"); |
| MSG(0, " 1: select the first nat pack\n"); |
| MSG(0, " 2: select the second nat pack\n"); |
| MSG(0, "[mb]:\n"); |
| MSG(0, " version: inject nat entry version\n"); |
| MSG(0, " ino: inject nat entry ino\n"); |
| MSG(0, " block_addr: inject nat entry block_addr\n"); |
| } |
| |
| static void inject_sit_usage(void) |
| { |
| MSG(0, "inject.f2fs --sit <0|1|2> --mb <name> --blk <blk> [--idx <index>] --val <value> inject sit entry\n"); |
| MSG(0, "[sit]:\n"); |
| MSG(0, " 0: auto select the current sit pack\n"); |
| MSG(0, " 1: select the first sit pack\n"); |
| MSG(0, " 2: select the second sit pack\n"); |
| MSG(0, "[mb]:\n"); |
| MSG(0, " vblocks: inject sit entry vblocks\n"); |
| MSG(0, " valid_map: inject sit entry valid_map\n"); |
| MSG(0, " mtime: inject sit entry mtime\n"); |
| } |
| |
| static void inject_ssa_usage(void) |
| { |
| MSG(0, "inject.f2fs --ssa --mb <name> --blk <blk> [--idx <index>] --val <value> inject summary entry\n"); |
| MSG(0, "[mb]:\n"); |
| MSG(0, " entry_type: inject summary block footer entry_type\n"); |
| MSG(0, " check_sum: inject summary block footer check_sum\n"); |
| MSG(0, " nid: inject summary entry nid selected by --idx <index\n"); |
| MSG(0, " version: inject summary entry version selected by --idx <index\n"); |
| MSG(0, " ofs_in_node: inject summary entry ofs_in_node selected by --idx <index\n"); |
| } |
| |
| static void inject_node_usage(void) |
| { |
| MSG(0, "inject.f2fs --node --mb <name> --nid <nid> [--idx <index>] --val <value> inject node\n"); |
| MSG(0, "[mb]:\n"); |
| MSG(0, " nid: inject node footer nid\n"); |
| MSG(0, " ino: inject node footer ino\n"); |
| MSG(0, " flag: inject node footer flag\n"); |
| MSG(0, " cp_ver: inject node footer cp_ver\n"); |
| MSG(0, " next_blkaddr: inject node footer next_blkaddr\n"); |
| MSG(0, " i_mode: inject inode i_mode\n"); |
| MSG(0, " i_advise: inject inode i_advise\n"); |
| MSG(0, " i_inline: inject inode i_inline\n"); |
| MSG(0, " i_links: inject inode i_links\n"); |
| MSG(0, " i_size: inject inode i_size\n"); |
| MSG(0, " i_blocks: inject inode i_blocks\n"); |
| MSG(0, " i_xattr_nid: inject inode i_xattr_nid\n"); |
| MSG(0, " i_ext.fofs: inject inode i_ext.fofs\n"); |
| MSG(0, " i_ext.blk_addr: inject inode i_ext.blk_addr\n"); |
| MSG(0, " i_ext.len: inject inode i_ext.len\n"); |
| MSG(0, " i_extra_isize: inject inode i_extra_isize\n"); |
| MSG(0, " i_inline_xattr_size: inject inode i_inline_xattr_size\n"); |
| MSG(0, " i_inode_checksum: inject inode i_inode_checksum\n"); |
| MSG(0, " i_compr_blocks: inject inode i_compr_blocks\n"); |
| MSG(0, " i_addr: inject inode i_addr array selected by --idx <index>\n"); |
| MSG(0, " i_nid: inject inode i_nid array selected by --idx <index>\n"); |
| MSG(0, " addr: inject {in}direct node nid/addr array selected by --idx <index>\n"); |
| } |
| |
| static void inject_dent_usage(void) |
| { |
| MSG(0, "inject.f2fs --dent --mb <name> --nid <nid> [--dots <1|2>] --val/str <value/string> inject dentry\n"); |
| MSG(0, "[dots]:\n"); |
| MSG(0, " 1: inject \".\" in directory which is specified by nid\n"); |
| MSG(0, " 2: inject \"..\" in directory which is specified by nid\n"); |
| MSG(0, "[mb]:\n"); |
| MSG(0, " d_bitmap: inject dentry block d_bitmap of nid\n"); |
| MSG(0, " d_hash: inject dentry hash\n"); |
| MSG(0, " d_ino: inject dentry ino\n"); |
| MSG(0, " d_ftype: inject dentry ftype\n"); |
| MSG(0, " filename: inject dentry filename, its hash and len are updated implicitly\n"); |
| } |
| |
| int inject_parse_options(int argc, char *argv[], struct inject_option *opt) |
| { |
| int o = 0; |
| const char *pack[] = {"auto", "1", "2"}; |
| const char *option_string = "d:Vh"; |
| char *endptr; |
| struct option long_opt[] = { |
| {"dry-run", no_argument, 0, 1}, |
| {"mb", required_argument, 0, 2}, |
| {"idx", required_argument, 0, 3}, |
| {"val", required_argument, 0, 4}, |
| {"str", required_argument, 0, 5}, |
| {"sb", required_argument, 0, 6}, |
| {"cp", required_argument, 0, 7}, |
| {"nat", required_argument, 0, 8}, |
| {"nid", required_argument, 0, 9}, |
| {"sit", required_argument, 0, 10}, |
| {"blk", required_argument, 0, 11}, |
| {"ssa", no_argument, 0, 12}, |
| {"node", no_argument, 0, 13}, |
| {"dent", no_argument, 0, 14}, |
| {"dots", required_argument, 0, 15}, |
| {0, 0, 0, 0} |
| }; |
| |
| while ((o = getopt_long(argc, argv, option_string, |
| long_opt, NULL)) != EOF) { |
| long long val; |
| |
| errno = 0; |
| switch (o) { |
| case 1: |
| c.dry_run = 1; |
| MSG(0, "Info: Dry run\n"); |
| break; |
| case 2: |
| opt->mb = optarg; |
| MSG(0, "Info: inject member %s\n", optarg); |
| break; |
| case 3: |
| val = strtoll(optarg, &endptr, 0); |
| if (errno != 0 || val >= UINT_MAX || val < 0 || |
| *endptr != '\0') |
| return -ERANGE; |
| opt->idx = (unsigned int)val; |
| MSG(0, "Info: inject slot index %u\n", opt->idx); |
| break; |
| case 4: |
| opt->val = strtoull(optarg, &endptr, 0); |
| if (errno != 0 || *endptr != '\0') |
| return -ERANGE; |
| MSG(0, "Info: inject value %lld : 0x%llx\n", opt->val, |
| opt->val); |
| break; |
| case 5: |
| opt->str = strdup(optarg); |
| if (!opt->str) |
| return -ENOMEM; |
| MSG(0, "Info: inject string %s\n", opt->str); |
| break; |
| case 6: |
| if (!is_digits(optarg)) |
| return EWRONG_OPT; |
| opt->sb = atoi(optarg); |
| if (opt->sb < 0 || opt->sb > 2) |
| return -ERANGE; |
| MSG(0, "Info: inject sb %s\n", pack[opt->sb]); |
| break; |
| case 7: |
| if (!is_digits(optarg)) |
| return EWRONG_OPT; |
| opt->cp = atoi(optarg); |
| if (opt->cp < 0 || opt->cp > 2) |
| return -ERANGE; |
| MSG(0, "Info: inject cp pack %s\n", pack[opt->cp]); |
| break; |
| case 8: |
| if (!is_digits(optarg)) |
| return EWRONG_OPT; |
| opt->nat = atoi(optarg); |
| if (opt->nat < 0 || opt->nat > 2) |
| return -ERANGE; |
| MSG(0, "Info: inject nat pack %s\n", pack[opt->nat]); |
| break; |
| case 9: |
| val = strtoll(optarg, &endptr, 0); |
| if (errno != 0 || val >= UINT_MAX || val < 0 || |
| *endptr != '\0') |
| return -ERANGE; |
| opt->nid = (nid_t)val; |
| MSG(0, "Info: inject nid %u : 0x%x\n", opt->nid, opt->nid); |
| break; |
| case 10: |
| if (!is_digits(optarg)) |
| return EWRONG_OPT; |
| opt->sit = atoi(optarg); |
| if (opt->sit < 0 || opt->sit > 2) |
| return -ERANGE; |
| MSG(0, "Info: inject sit pack %s\n", pack[opt->sit]); |
| break; |
| case 11: |
| val = strtoll(optarg, &endptr, 0); |
| if (errno != 0 || val >= UINT_MAX || val < 0 || |
| *endptr != '\0') |
| return -ERANGE; |
| opt->blk = (block_t)val; |
| MSG(0, "Info: inject blkaddr %u : 0x%x\n", opt->blk, opt->blk); |
| break; |
| case 12: |
| opt->ssa = true; |
| MSG(0, "Info: inject ssa\n"); |
| break; |
| case 13: |
| opt->node = true; |
| MSG(0, "Info: inject node\n"); |
| break; |
| case 14: |
| opt->dent = true; |
| MSG(0, "Info: inject dentry\n"); |
| break; |
| case 15: |
| opt->dots = atoi(optarg); |
| if (opt->dots != TYPE_DOT && |
| opt->dots != TYPE_DOTDOT) |
| return -ERANGE; |
| MSG(0, "Info: inject %s dentry\n", |
| opt->dots == TYPE_DOT ? "dot" : "dotdot"); |
| break; |
| case 'd': |
| if (optarg[0] == '-' || !is_digits(optarg)) |
| return EWRONG_OPT; |
| c.dbg_lv = atoi(optarg); |
| MSG(0, "Info: Debug level = %d\n", c.dbg_lv); |
| break; |
| case 'V': |
| show_version("inject.f2fs"); |
| exit(0); |
| case 'h': |
| default: |
| if (opt->sb >= 0) { |
| inject_sb_usage(); |
| exit(0); |
| } else if (opt->cp >= 0) { |
| inject_cp_usage(); |
| exit(0); |
| } else if (opt->nat >= 0) { |
| inject_nat_usage(); |
| exit(0); |
| } else if (opt->sit >= 0) { |
| inject_sit_usage(); |
| exit(0); |
| } else if (opt->ssa) { |
| inject_ssa_usage(); |
| exit(0); |
| } else if (opt->node) { |
| inject_node_usage(); |
| exit(0); |
| } else if (opt->dent) { |
| inject_dent_usage(); |
| exit(0); |
| } else { |
| MSG(0, "\tError: Wrong option -%c (%d) %s\n", |
| o, o, optarg); |
| } |
| return EUNKNOWN_OPT; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int inject_sb(struct f2fs_sb_info *sbi, struct inject_option *opt) |
| { |
| struct f2fs_super_block *sb; |
| char *buf; |
| int ret; |
| |
| buf = calloc(1, F2FS_BLKSIZE); |
| ASSERT(buf != NULL); |
| |
| if (opt->sb == 0) |
| opt->sb = 1; |
| |
| ret = dev_read_block(buf, opt->sb == 1 ? SB0_ADDR : SB1_ADDR); |
| ASSERT(ret >= 0); |
| |
| sb = (struct f2fs_super_block *)(buf + F2FS_SUPER_OFFSET); |
| |
| if (!strcmp(opt->mb, "magic")) { |
| MSG(0, "Info: inject magic of sb %d: 0x%x -> 0x%x\n", |
| opt->sb, get_sb(magic), (u32)opt->val); |
| set_sb(magic, (u32)opt->val); |
| } else if (!strcmp(opt->mb, "s_stop_reason")) { |
| if (opt->idx >= MAX_STOP_REASON) { |
| ERR_MSG("invalid index %u of sb->s_stop_reason[]\n", |
| opt->idx); |
| ret = -EINVAL; |
| goto out; |
| } |
| MSG(0, "Info: inject s_stop_reason[%d] of sb %d: %d -> %d\n", |
| opt->idx, opt->sb, sb->s_stop_reason[opt->idx], |
| (u8)opt->val); |
| sb->s_stop_reason[opt->idx] = (u8)opt->val; |
| } else if (!strcmp(opt->mb, "s_errors")) { |
| if (opt->idx >= MAX_F2FS_ERRORS) { |
| ERR_MSG("invalid index %u of sb->s_errors[]\n", |
| opt->idx); |
| ret = -EINVAL; |
| goto out; |
| } |
| MSG(0, "Info: inject s_errors[%d] of sb %d: %x -> %x\n", |
| opt->idx, opt->sb, sb->s_errors[opt->idx], (u8)opt->val); |
| sb->s_errors[opt->idx] = (u8)opt->val; |
| } else if (!strcmp(opt->mb, "feature")) { |
| MSG(0, "Info: inject feature of sb %d: 0x%x -> 0x%x\n", |
| opt->sb, get_sb(feature), (u32)opt->val); |
| set_sb(feature, (u32)opt->val); |
| } else if (!strcmp(opt->mb, "devs.path")) { |
| if (opt->idx >= MAX_DEVICES) { |
| ERR_MSG("invalid index %u of sb->devs[]\n", opt->idx); |
| ret = -EINVAL; |
| goto out; |
| } |
| if (strlen(opt->str) >= MAX_PATH_LEN) { |
| ERR_MSG("invalid length of option str\n"); |
| ret = -EINVAL; |
| goto out; |
| } |
| MSG(0, "Info: inject devs[%d].path of sb %d: %s -> %s\n", |
| opt->idx, opt->sb, (char *)sb->devs[opt->idx].path, opt->str); |
| strcpy((char *)sb->devs[opt->idx].path, opt->str); |
| } else { |
| ERR_MSG("unknown or unsupported member \"%s\"\n", opt->mb); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| print_raw_sb_info(sb); |
| update_superblock(sb, SB_MASK((u32)opt->sb - 1)); |
| |
| out: |
| free(buf); |
| free(opt->str); |
| return ret; |
| } |
| |
| static int inject_cp(struct f2fs_sb_info *sbi, struct inject_option *opt) |
| { |
| struct f2fs_checkpoint *cp, *cur_cp = F2FS_CKPT(sbi); |
| bool update_crc = true; |
| char *buf = NULL; |
| int ret = 0; |
| |
| if (opt->cp == 0) |
| opt->cp = sbi->cur_cp; |
| |
| if (opt->cp != sbi->cur_cp) { |
| struct f2fs_super_block *sb = sbi->raw_super; |
| block_t cp_addr; |
| |
| buf = calloc(1, F2FS_BLKSIZE); |
| ASSERT(buf != NULL); |
| |
| cp_addr = get_sb(cp_blkaddr); |
| if (opt->cp == 2) |
| cp_addr += 1 << get_sb(log_blocks_per_seg); |
| ret = dev_read_block(buf, cp_addr); |
| ASSERT(ret >= 0); |
| |
| cp = (struct f2fs_checkpoint *)buf; |
| sbi->ckpt = cp; |
| sbi->cur_cp = opt->cp; |
| } else { |
| cp = cur_cp; |
| } |
| |
| if (!strcmp(opt->mb, "checkpoint_ver")) { |
| MSG(0, "Info: inject checkpoint_ver of cp %d: 0x%llx -> 0x%"PRIx64"\n", |
| opt->cp, get_cp(checkpoint_ver), (u64)opt->val); |
| set_cp(checkpoint_ver, (u64)opt->val); |
| } else if (!strcmp(opt->mb, "ckpt_flags")) { |
| MSG(0, "Info: inject ckpt_flags of cp %d: 0x%x -> 0x%x\n", |
| opt->cp, get_cp(ckpt_flags), (u32)opt->val); |
| set_cp(ckpt_flags, (u32)opt->val); |
| } else if (!strcmp(opt->mb, "cur_node_segno")) { |
| if (opt->idx >= MAX_ACTIVE_NODE_LOGS) { |
| ERR_MSG("invalid index %u of cp->cur_node_segno[]\n", |
| opt->idx); |
| ret = -EINVAL; |
| goto out; |
| } |
| MSG(0, "Info: inject cur_node_segno[%d] of cp %d: 0x%x -> 0x%x\n", |
| opt->idx, opt->cp, get_cp(cur_node_segno[opt->idx]), |
| (u32)opt->val); |
| set_cp(cur_node_segno[opt->idx], (u32)opt->val); |
| } else if (!strcmp(opt->mb, "cur_node_blkoff")) { |
| if (opt->idx >= MAX_ACTIVE_NODE_LOGS) { |
| ERR_MSG("invalid index %u of cp->cur_node_blkoff[]\n", |
| opt->idx); |
| ret = -EINVAL; |
| goto out; |
| } |
| MSG(0, "Info: inject cur_node_blkoff[%d] of cp %d: 0x%x -> 0x%x\n", |
| opt->idx, opt->cp, get_cp(cur_node_blkoff[opt->idx]), |
| (u16)opt->val); |
| set_cp(cur_node_blkoff[opt->idx], (u16)opt->val); |
| } else if (!strcmp(opt->mb, "cur_data_segno")) { |
| if (opt->idx >= MAX_ACTIVE_DATA_LOGS) { |
| ERR_MSG("invalid index %u of cp->cur_data_segno[]\n", |
| opt->idx); |
| ret = -EINVAL; |
| goto out; |
| } |
| MSG(0, "Info: inject cur_data_segno[%d] of cp %d: 0x%x -> 0x%x\n", |
| opt->idx, opt->cp, get_cp(cur_data_segno[opt->idx]), |
| (u32)opt->val); |
| set_cp(cur_data_segno[opt->idx], (u32)opt->val); |
| } else if (!strcmp(opt->mb, "cur_data_blkoff")) { |
| if (opt->idx >= MAX_ACTIVE_DATA_LOGS) { |
| ERR_MSG("invalid index %u of cp->cur_data_blkoff[]\n", |
| opt->idx); |
| ret = -EINVAL; |
| goto out; |
| } |
| MSG(0, "Info: inject cur_data_blkoff[%d] of cp %d: 0x%x -> 0x%x\n", |
| opt->idx, opt->cp, get_cp(cur_data_blkoff[opt->idx]), |
| (u16)opt->val); |
| set_cp(cur_data_blkoff[opt->idx], (u16)opt->val); |
| } else if (!strcmp(opt->mb, "alloc_type")) { |
| if (opt->idx >= MAX_ACTIVE_LOGS) { |
| ERR_MSG("invalid index %u of cp->alloc_type[]\n", |
| opt->idx); |
| ret = -EINVAL; |
| goto out; |
| } |
| MSG(0, "Info: inject alloc_type[%d] of cp %d: 0x%x -> 0x%x\n", |
| opt->idx, opt->cp, cp->alloc_type[opt->idx], |
| (unsigned char)opt->val); |
| cp->alloc_type[opt->idx] = (unsigned char)opt->val; |
| } else if (!strcmp(opt->mb, "next_blkaddr")) { |
| struct fsync_inode_entry *entry; |
| struct list_head inode_list = LIST_HEAD_INIT(inode_list); |
| struct f2fs_node *node; |
| block_t blkaddr; |
| int i = 0; |
| |
| if (c.zoned_model == F2FS_ZONED_HM) { |
| ERR_MSG("inject fsync dnodes not supported in " |
| "zoned device\n"); |
| ret = -EOPNOTSUPP; |
| goto out; |
| } |
| |
| if (!need_fsync_data_record(sbi)) { |
| ERR_MSG("no need to recover fsync dnodes\n"); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| ret = f2fs_find_fsync_inode(sbi, &inode_list); |
| if (ret) { |
| ERR_MSG("failed to find fsync inodes: %d\n", ret); |
| goto out; |
| } |
| |
| list_for_each_entry(entry, &inode_list, list) { |
| if (i == opt->idx) |
| blkaddr = entry->blkaddr; |
| DBG(0, "[%4d] blkaddr:0x%x\n", i++, entry->blkaddr); |
| } |
| |
| f2fs_destroy_fsync_dnodes(&inode_list); |
| |
| if (opt->idx == 0 || opt->idx >= i) { |
| ERR_MSG("invalid index %u of fsync dnodes range [1, %u]\n", |
| opt->idx, i); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| MSG(0, "Info: inject next_blkaddr[%d] of cp %d: 0x%x -> 0x%x\n", |
| opt->idx, opt->cp, blkaddr, (u32)opt->val); |
| |
| node = malloc(F2FS_BLKSIZE); |
| ASSERT(node); |
| ret = dev_read_block(node, blkaddr); |
| ASSERT(ret >= 0); |
| F2FS_NODE_FOOTER(node)->next_blkaddr = cpu_to_le32((u32)opt->val); |
| if (IS_INODE(node)) |
| ret = update_inode(sbi, node, &blkaddr); |
| else |
| ret = update_block(sbi, node, &blkaddr, NULL); |
| free(node); |
| ASSERT(ret >= 0); |
| goto out; |
| } else if (!strcmp(opt->mb, "crc")) { |
| __le32 *crc = (__le32 *)((unsigned char *)cp + |
| get_cp(checksum_offset)); |
| |
| MSG(0, "Info: inject crc of cp %d: 0x%x -> 0x%x\n", |
| opt->cp, le32_to_cpu(*crc), (u32)opt->val); |
| *crc = cpu_to_le32((u32)opt->val); |
| update_crc = false; |
| } else if (!strcmp(opt->mb, "elapsed_time")) { |
| MSG(0, "Info: inject elapsed_time of cp %d: %llu -> %"PRIu64"\n", |
| opt->cp, get_cp(elapsed_time), (u64)opt->val); |
| set_cp(elapsed_time, (u64)opt->val); |
| } else { |
| ERR_MSG("unknown or unsupported member \"%s\"\n", opt->mb); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| print_ckpt_info(sbi); |
| write_raw_cp_blocks(sbi, cp, opt->cp, update_crc); |
| |
| out: |
| free(buf); |
| sbi->ckpt = cur_cp; |
| return ret; |
| } |
| |
| static void rewrite_nat_in_journal(struct f2fs_sb_info *sbi, u32 nid, |
| struct f2fs_nat_entry *nat) |
| { |
| struct f2fs_checkpoint *cp = F2FS_CKPT(sbi); |
| struct curseg_info *curseg = CURSEG_I(sbi, CURSEG_HOT_DATA); |
| struct f2fs_journal *journal = F2FS_SUMMARY_BLOCK_JOURNAL(curseg->sum_blk); |
| block_t blkaddr; |
| int ret, i; |
| |
| for (i = 0; i < nats_in_cursum(journal); i++) { |
| if (nid_in_journal(journal, i) == nid) { |
| memcpy(&nat_in_journal(journal, i), nat, sizeof(*nat)); |
| break; |
| } |
| } |
| |
| if (is_set_ckpt_flags(cp, CP_UMOUNT_FLAG)) |
| blkaddr = sum_blk_addr(sbi, NR_CURSEG_TYPE, CURSEG_HOT_DATA); |
| else |
| blkaddr = sum_blk_addr(sbi, NR_CURSEG_DATA_TYPE, CURSEG_HOT_DATA); |
| |
| ret = dev_write_block(curseg->sum_blk, blkaddr, WRITE_LIFE_NONE); |
| ASSERT(ret >= 0); |
| } |
| |
| static block_t get_nat_addr(struct f2fs_sb_info *sbi, int nat_pack, |
| nid_t nid, enum entry_pos *pack) |
| { |
| struct f2fs_nm_info *nm_i = NM_I(sbi); |
| block_t blkaddr; |
| unsigned int blkoff; |
| |
| blkoff = NAT_BLOCK_OFFSET(nid); |
| blkaddr = nm_i->nat_blkaddr + (blkoff << 1) + |
| (blkoff & (sbi->blocks_per_seg - 1)); |
| if (nat_pack == 0) // select valid nat pack |
| nat_pack = f2fs_test_bit(blkoff, nm_i->nat_bitmap) ? |
| ENT_IN_PACK2 : ENT_IN_PACK1; |
| if (nat_pack == ENT_IN_PACK2) |
| blkaddr += sbi->blocks_per_seg; |
| if (pack) |
| *pack = nat_pack; |
| return blkaddr; |
| } |
| |
| static struct f2fs_nat_entry *get_raw_nat(struct f2fs_sb_info *sbi, |
| struct inject_option *opt, |
| struct f2fs_nat_block *nat_blk, |
| enum entry_pos *pack) |
| { |
| block_t blkaddr; |
| unsigned int offs; |
| |
| if (lookup_nat_in_journal(sbi, opt->nid, &nat_blk->entries[0]) >= 0) { |
| offs = 0; |
| *pack = ENT_IN_JOURNAL; |
| } else { |
| blkaddr = get_nat_addr(sbi, opt->nat, opt->nid, pack); |
| ASSERT(dev_read_block(nat_blk, blkaddr) >= 0); |
| offs = opt->nid % NAT_ENTRY_PER_BLOCK; |
| } |
| |
| return &nat_blk->entries[offs]; |
| } |
| |
| static void rewrite_raw_nat(struct f2fs_sb_info *sbi, |
| struct inject_option *opt, |
| struct f2fs_nat_block *nat_blk, |
| enum entry_pos pack) |
| { |
| block_t blkaddr; |
| |
| if (pack == ENT_IN_JOURNAL) { |
| rewrite_nat_in_journal(sbi, opt->nid, &nat_blk->entries[0]); |
| } else { |
| blkaddr = get_nat_addr(sbi, opt->nat, opt->nid, NULL); |
| ASSERT(dev_write_block(nat_blk, blkaddr, WRITE_LIFE_NONE) >= 0); |
| } |
| } |
| |
| static int inject_nat(struct f2fs_sb_info *sbi, struct inject_option *opt) |
| { |
| struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi); |
| struct f2fs_nat_block *nat_blk; |
| struct f2fs_nat_entry *ne; |
| enum entry_pos pack; |
| |
| if (!IS_VALID_NID(sbi, opt->nid)) { |
| ERR_MSG("Invalid nid %u range [%u:%"PRIu64"]\n", opt->nid, 0, |
| NAT_ENTRY_PER_BLOCK * |
| ((get_sb(segment_count_nat) << 1) << |
| sbi->log_blocks_per_seg)); |
| return -EINVAL; |
| } |
| |
| nat_blk = calloc(F2FS_BLKSIZE, 1); |
| ASSERT(nat_blk); |
| |
| ne = get_raw_nat(sbi, opt, nat_blk, &pack); |
| |
| if (!strcmp(opt->mb, "version")) { |
| MSG(0, "Info: inject nat entry version of nid %u " |
| "in "PRENTPOS": %d -> %d\n", opt->nid, |
| show_entry_pos(pack), |
| ne->version, (u8)opt->val); |
| ne->version = (u8)opt->val; |
| } else if (!strcmp(opt->mb, "ino")) { |
| MSG(0, "Info: inject nat entry ino of nid %u " |
| "in "PRENTPOS": %d -> %d\n", opt->nid, |
| show_entry_pos(pack), |
| le32_to_cpu(ne->ino), (nid_t)opt->val); |
| ne->ino = cpu_to_le32((nid_t)opt->val); |
| } else if (!strcmp(opt->mb, "block_addr")) { |
| MSG(0, "Info: inject nat entry block_addr of nid %u " |
| "in "PRENTPOS": 0x%x -> 0x%x\n", opt->nid, |
| show_entry_pos(pack), |
| le32_to_cpu(ne->block_addr), (block_t)opt->val); |
| ne->block_addr = cpu_to_le32((block_t)opt->val); |
| } else { |
| ERR_MSG("unknown or unsupported member \"%s\"\n", opt->mb); |
| free(nat_blk); |
| return -EINVAL; |
| } |
| print_raw_nat_entry_info(ne); |
| |
| rewrite_raw_nat(sbi, opt, nat_blk, pack); |
| |
| free(nat_blk); |
| return 0; |
| } |
| |
| static void rewrite_sit_in_journal(struct f2fs_sb_info *sbi, unsigned int segno, |
| struct f2fs_sit_entry *sit) |
| { |
| struct f2fs_checkpoint *cp = F2FS_CKPT(sbi); |
| struct curseg_info *curseg = CURSEG_I(sbi, CURSEG_COLD_DATA); |
| struct f2fs_journal *journal = F2FS_SUMMARY_BLOCK_JOURNAL(curseg->sum_blk); |
| block_t blkaddr; |
| int ret, i; |
| |
| for (i = 0; i < sits_in_cursum(journal); i++) { |
| if (segno_in_journal(journal, i) == segno) { |
| memcpy(&sit_in_journal(journal, i), sit, sizeof(*sit)); |
| break; |
| } |
| } |
| |
| if (is_set_ckpt_flags(cp, CP_UMOUNT_FLAG)) |
| blkaddr = sum_blk_addr(sbi, NR_CURSEG_TYPE, CURSEG_COLD_DATA); |
| else |
| blkaddr = sum_blk_addr(sbi, NR_CURSEG_DATA_TYPE, CURSEG_COLD_DATA); |
| |
| ret = dev_write_block(curseg->sum_blk, blkaddr, WRITE_LIFE_NONE); |
| ASSERT(ret >= 0); |
| } |
| |
| static block_t get_sit_addr(struct f2fs_sb_info *sbi, int sit_pack, |
| unsigned int segno, enum entry_pos *pack) |
| { |
| struct sit_info *sit_i = SIT_I(sbi); |
| unsigned int blkaddr, offs; |
| |
| offs = SIT_BLOCK_OFFSET(sit_i, segno); |
| blkaddr = sit_i->sit_base_addr + offs; |
| if (sit_pack == 0) // select valid sit pack |
| sit_pack = f2fs_test_bit(offs, sit_i->sit_bitmap) ? |
| ENT_IN_PACK2 : ENT_IN_PACK1; |
| if (sit_pack == ENT_IN_PACK2) |
| blkaddr += sit_i->sit_blocks; |
| if (pack) |
| *pack = sit_pack; |
| return blkaddr; |
| } |
| |
| static struct f2fs_sit_entry *get_raw_sit(struct f2fs_sb_info *sbi, |
| struct inject_option *opt, |
| struct f2fs_sit_block *sit_blk, |
| enum entry_pos *pack) |
| { |
| struct sit_info *sit_i = SIT_I(sbi); |
| unsigned int segno, offs; |
| block_t blkaddr; |
| |
| segno = GET_SEGNO(sbi, opt->blk); |
| if (lookup_sit_in_journal(sbi, segno, &sit_blk->entries[0]) >= 0) { |
| offs = 0; |
| *pack = ENT_IN_JOURNAL; |
| } else { |
| blkaddr = get_sit_addr(sbi, opt->sit, segno, pack); |
| ASSERT(dev_read_block(sit_blk, blkaddr) >= 0); |
| offs = SIT_ENTRY_OFFSET(sit_i, segno); |
| } |
| |
| return &sit_blk->entries[offs]; |
| } |
| |
| static void rewrite_raw_sit(struct f2fs_sb_info *sbi, |
| struct inject_option *opt, |
| struct f2fs_sit_block *sit_blk, |
| enum entry_pos pack) |
| { |
| unsigned int segno; |
| block_t blkaddr; |
| |
| segno = GET_SEGNO(sbi, opt->blk); |
| if (pack == ENT_IN_JOURNAL) { |
| rewrite_sit_in_journal(sbi, segno, &sit_blk->entries[0]); |
| } else { |
| blkaddr = get_sit_addr(sbi, opt->sit, segno, NULL); |
| ASSERT(dev_write_block(sit_blk, blkaddr, WRITE_LIFE_NONE) >= 0); |
| } |
| } |
| |
| static int inject_sit(struct f2fs_sb_info *sbi, struct inject_option *opt) |
| { |
| struct f2fs_sit_block *sit_blk; |
| struct f2fs_sit_entry *sit; |
| enum entry_pos pack; |
| |
| if (!f2fs_is_valid_blkaddr(sbi, opt->blk, DATA_GENERIC)) { |
| ERR_MSG("Invalid blkaddr 0x%x (valid range [0x%x:0x%lx])\n", |
| opt->blk, SM_I(sbi)->main_blkaddr, |
| (unsigned long)le64_to_cpu(F2FS_RAW_SUPER(sbi)->block_count)); |
| return -EINVAL; |
| } |
| |
| sit_blk = calloc(F2FS_BLKSIZE, 1); |
| ASSERT(sit_blk); |
| |
| sit = get_raw_sit(sbi, opt, sit_blk, &pack); |
| |
| if (!strcmp(opt->mb, "vblocks")) { |
| MSG(0, "Info: inject sit entry vblocks of block 0x%x " |
| "in "PRENTPOS": %u -> %u\n", opt->blk, |
| show_entry_pos(pack), |
| le16_to_cpu(sit->vblocks), (u16)opt->val); |
| sit->vblocks = cpu_to_le16((u16)opt->val); |
| } else if (!strcmp(opt->mb, "valid_map")) { |
| if (opt->idx == -1) { |
| opt->idx = OFFSET_IN_SEG(sbi, opt->blk); |
| MSG(0, "Info: auto idx = %u\n", opt->idx); |
| } |
| if (opt->idx >= SIT_VBLOCK_MAP_SIZE) { |
| ERR_MSG("invalid idx %u of valid_map[]\n", opt->idx); |
| free(sit_blk); |
| return -ERANGE; |
| } |
| MSG(0, "Info: inject sit entry valid_map[%d] of block 0x%x " |
| "in "PRENTPOS": 0x%02x -> 0x%02x\n", opt->idx, opt->blk, |
| show_entry_pos(pack), |
| sit->valid_map[opt->idx], (u8)opt->val); |
| sit->valid_map[opt->idx] = (u8)opt->val; |
| } else if (!strcmp(opt->mb, "mtime")) { |
| MSG(0, "Info: inject sit entry mtime of block 0x%x " |
| "in pack %d: %"PRIu64" -> %"PRIu64"\n", opt->blk, opt->sit, |
| le64_to_cpu(sit->mtime), (u64)opt->val); |
| sit->mtime = cpu_to_le64((u64)opt->val); |
| } else { |
| ERR_MSG("unknown or unsupported member \"%s\"\n", opt->mb); |
| free(sit_blk); |
| return -EINVAL; |
| } |
| print_raw_sit_entry_info(sit); |
| |
| rewrite_raw_sit(sbi, opt, sit_blk, pack); |
| |
| free(sit_blk); |
| return 0; |
| } |
| |
| static int inject_ssa(struct f2fs_sb_info *sbi, struct inject_option *opt) |
| { |
| struct f2fs_summary_block *sum_blk; |
| struct summary_footer *footer; |
| struct f2fs_summary *sum; |
| u32 segno, offset; |
| int type; |
| int ret; |
| |
| if (!f2fs_is_valid_blkaddr(sbi, opt->blk, DATA_GENERIC)) { |
| ERR_MSG("Invalid blkaddr %#x (valid range [%#x:%#lx])\n", |
| opt->blk, SM_I(sbi)->main_blkaddr, |
| (unsigned long)le64_to_cpu(F2FS_RAW_SUPER(sbi)->block_count)); |
| return -ERANGE; |
| } |
| |
| segno = GET_SEGNO(sbi, opt->blk); |
| offset = OFFSET_IN_SEG(sbi, opt->blk); |
| |
| sum_blk = get_sum_block(sbi, segno, &type); |
| sum = &sum_blk->entries[offset]; |
| footer = F2FS_SUMMARY_BLOCK_FOOTER(sum_blk); |
| |
| if (!strcmp(opt->mb, "entry_type")) { |
| MSG(0, "Info: inject summary block footer entry_type of " |
| "block 0x%x: %d -> %d\n", opt->blk, footer->entry_type, |
| (unsigned char)opt->val); |
| footer->entry_type = (unsigned char)opt->val; |
| } else if (!strcmp(opt->mb, "check_sum")) { |
| MSG(0, "Info: inject summary block footer check_sum of " |
| "block 0x%x: 0x%x -> 0x%x\n", opt->blk, |
| le32_to_cpu(footer->check_sum), (u32)opt->val); |
| footer->check_sum = cpu_to_le32((u32)opt->val); |
| } else { |
| if (opt->idx == -1) { |
| MSG(0, "Info: auto idx = %u\n", offset); |
| opt->idx = offset; |
| } |
| if (opt->idx >= ENTRIES_IN_SUM) { |
| ERR_MSG("invalid idx %u of entries[]\n", opt->idx); |
| ret = -EINVAL; |
| goto out; |
| } |
| sum = &sum_blk->entries[opt->idx]; |
| if (!strcmp(opt->mb, "nid")) { |
| MSG(0, "Info: inject summary entry nid of " |
| "block 0x%x: 0x%x -> 0x%x\n", opt->blk, |
| le32_to_cpu(sum->nid), (u32)opt->val); |
| sum->nid = cpu_to_le32((u32)opt->val); |
| } else if (!strcmp(opt->mb, "version")) { |
| MSG(0, "Info: inject summary entry version of " |
| "block 0x%x: %d -> %d\n", opt->blk, |
| sum->version, (u8)opt->val); |
| sum->version = (u8)opt->val; |
| } else if (!strcmp(opt->mb, "ofs_in_node")) { |
| MSG(0, "Info: inject summary entry ofs_in_node of " |
| "block 0x%x: %d -> %d\n", opt->blk, |
| sum->ofs_in_node, (u16)opt->val); |
| sum->ofs_in_node = cpu_to_le16((u16)opt->val); |
| } else { |
| ERR_MSG("unknown or unsupported member \"%s\"\n", opt->mb); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| print_raw_sum_entry_info(sum); |
| } |
| |
| print_sum_footer_info(footer); |
| |
| ret = write_sum_block(sbi, sum_blk, segno, WRITE_LIFE_NONE); |
| ASSERT(ret >= 0); |
| |
| out: |
| if (type == SEG_TYPE_NODE || type == SEG_TYPE_DATA || |
| type == SEG_TYPE_MAX) |
| free(sum_blk); |
| return ret; |
| } |
| |
| static int inject_inode(struct f2fs_sb_info *sbi, struct f2fs_node *node, |
| struct inject_option *opt) |
| { |
| struct f2fs_inode *inode = &node->i; |
| |
| if (!strcmp(opt->mb, "i_mode")) { |
| MSG(0, "Info: inject inode i_mode of nid %u: 0x%x -> 0x%x\n", |
| opt->nid, le16_to_cpu(inode->i_mode), (u16)opt->val); |
| inode->i_mode = cpu_to_le16((u16)opt->val); |
| } else if (!strcmp(opt->mb, "i_advise")) { |
| MSG(0, "Info: inject inode i_advise of nid %u: 0x%x -> 0x%x\n", |
| opt->nid, inode->i_advise, (u8)opt->val); |
| inode->i_advise = (u8)opt->val; |
| } else if (!strcmp(opt->mb, "i_inline")) { |
| MSG(0, "Info: inject inode i_inline of nid %u: 0x%x -> 0x%x\n", |
| opt->nid, inode->i_inline, (u8)opt->val); |
| inode->i_inline = (u8)opt->val; |
| } else if (!strcmp(opt->mb, "i_links")) { |
| MSG(0, "Info: inject inode i_links of nid %u: %u -> %u\n", |
| opt->nid, le32_to_cpu(inode->i_links), (u32)opt->val); |
| inode->i_links = cpu_to_le32((u32)opt->val); |
| } else if (!strcmp(opt->mb, "i_size")) { |
| MSG(0, "Info: inject inode i_size of nid %u: %"PRIu64" -> %"PRIu64"\n", |
| opt->nid, le64_to_cpu(inode->i_size), (u64)opt->val); |
| inode->i_size = cpu_to_le64((u64)opt->val); |
| } else if (!strcmp(opt->mb, "i_blocks")) { |
| MSG(0, "Info: inject inode i_blocks of nid %u: %"PRIu64" -> %"PRIu64"\n", |
| opt->nid, le64_to_cpu(inode->i_blocks), (u64)opt->val); |
| inode->i_blocks = cpu_to_le64((u64)opt->val); |
| } else if (!strcmp(opt->mb, "i_xattr_nid")) { |
| MSG(0, "Info: inject inode i_xattr_nid of nid %u: %u -> %u\n", |
| opt->nid, le32_to_cpu(inode->i_xattr_nid), (u32)opt->val); |
| inode->i_xattr_nid = cpu_to_le32((u32)opt->val); |
| } else if (!strcmp(opt->mb, "i_ext.fofs")) { |
| MSG(0, "Info: inject inode i_ext.fofs of nid %u: %u -> %u\n", |
| opt->nid, le32_to_cpu(inode->i_ext.fofs), (u32)opt->val); |
| inode->i_ext.fofs = cpu_to_le32((u32)opt->val); |
| } else if (!strcmp(opt->mb, "i_ext.blk_addr")) { |
| MSG(0, "Info: inject inode i_ext.blk_addr of nid %u: " |
| "0x%x -> 0x%x\n", opt->nid, |
| le32_to_cpu(inode->i_ext.blk_addr), (u32)opt->val); |
| inode->i_ext.blk_addr = cpu_to_le32((u32)opt->val); |
| } else if (!strcmp(opt->mb, "i_ext.len")) { |
| MSG(0, "Info: inject inode i_ext.len of nid %u: %u -> %u\n", |
| opt->nid, le32_to_cpu(inode->i_ext.len), (u32)opt->val); |
| inode->i_ext.len = cpu_to_le32((u32)opt->val); |
| } else if (!strcmp(opt->mb, "i_extra_isize")) { |
| /* do not care if F2FS_EXTRA_ATTR is enabled */ |
| MSG(0, "Info: inject inode i_extra_isize of nid %u: %d -> %d\n", |
| opt->nid, le16_to_cpu(inode->i_extra_isize), (u16)opt->val); |
| inode->i_extra_isize = cpu_to_le16((u16)opt->val); |
| } else if (!strcmp(opt->mb, "i_inline_xattr_size")) { |
| MSG(0, "Info: inject inode i_inline_xattr_size of nid %u: " |
| "%d -> %d\n", opt->nid, |
| le16_to_cpu(inode->i_inline_xattr_size), (u16)opt->val); |
| inode->i_inline_xattr_size = cpu_to_le16((u16)opt->val); |
| } else if (!strcmp(opt->mb, "i_inode_checksum")) { |
| MSG(0, "Info: inject inode i_inode_checksum of nid %u: " |
| "0x%x -> 0x%x\n", opt->nid, |
| le32_to_cpu(inode->i_inode_checksum), (u32)opt->val); |
| inode->i_inode_checksum = cpu_to_le32((u32)opt->val); |
| } else if (!strcmp(opt->mb, "i_compr_blocks")) { |
| MSG(0, "Info: inject inode i_compr_blocks of nid %u: " |
| "%"PRIu64" -> %"PRIu64"\n", opt->nid, |
| le64_to_cpu(inode->i_compr_blocks), (u64)opt->val); |
| inode->i_compr_blocks = cpu_to_le64((u64)opt->val); |
| } else if (!strcmp(opt->mb, "i_addr")) { |
| /* do not care if it is inline data */ |
| if (opt->idx >= DEF_ADDRS_PER_INODE) { |
| ERR_MSG("invalid index %u of i_addr[]\n", opt->idx); |
| return -EINVAL; |
| } |
| MSG(0, "Info: inject inode i_addr[%d] of nid %u: " |
| "0x%x -> 0x%x\n", opt->idx, opt->nid, |
| le32_to_cpu(inode->i_addr[opt->idx]), (u32)opt->val); |
| inode->i_addr[opt->idx] = cpu_to_le32((block_t)opt->val); |
| } else if (!strcmp(opt->mb, "i_nid")) { |
| if (opt->idx >= 5) { |
| ERR_MSG("invalid index %u of i_nid[]\n", opt->idx); |
| return -EINVAL; |
| } |
| MSG(0, "Info: inject inode i_nid[%d] of nid %u: " |
| "0x%x -> 0x%x\n", opt->idx, opt->nid, |
| le32_to_cpu(F2FS_INODE_I_NID(inode, opt->idx)), |
| (u32)opt->val); |
| F2FS_INODE_I_NID(inode, opt->idx) = cpu_to_le32((nid_t)opt->val); |
| } else { |
| ERR_MSG("unknown or unsupported member \"%s\"\n", opt->mb); |
| return -EINVAL; |
| } |
| |
| if (c.dbg_lv > 0) |
| print_node_info(sbi, node, 1); |
| |
| return 0; |
| } |
| |
| static int inject_index_node(struct f2fs_sb_info *sbi, struct f2fs_node *node, |
| struct inject_option *opt) |
| { |
| struct direct_node *dn = &node->dn; |
| |
| if (strcmp(opt->mb, "addr")) { |
| ERR_MSG("unknown or unsupported member \"%s\"\n", opt->mb); |
| return -EINVAL; |
| } |
| |
| if (opt->idx >= DEF_ADDRS_PER_BLOCK) { |
| ERR_MSG("invalid index %u of nid/addr[]\n", opt->idx); |
| return -EINVAL; |
| } |
| |
| MSG(0, "Info: inject node nid/addr[%d] of nid %u: 0x%x -> 0x%x\n", |
| opt->idx, opt->nid, le32_to_cpu(dn->addr[opt->idx]), |
| (block_t)opt->val); |
| dn->addr[opt->idx] = cpu_to_le32((block_t)opt->val); |
| |
| if (c.dbg_lv > 0) |
| print_node_info(sbi, node, 1); |
| |
| return 0; |
| } |
| |
| static int inject_node(struct f2fs_sb_info *sbi, struct inject_option *opt) |
| { |
| struct f2fs_super_block *sb = sbi->raw_super; |
| struct node_info ni; |
| struct f2fs_node *node_blk; |
| struct node_footer *footer; |
| int ret; |
| |
| if (!IS_VALID_NID(sbi, opt->nid)) { |
| ERR_MSG("Invalid nid %u range [%u:%"PRIu64"]\n", opt->nid, 0, |
| NAT_ENTRY_PER_BLOCK * |
| ((get_sb(segment_count_nat) << 1) << |
| sbi->log_blocks_per_seg)); |
| return -EINVAL; |
| } |
| |
| node_blk = calloc(F2FS_BLKSIZE, 1); |
| ASSERT(node_blk); |
| |
| get_node_info(sbi, opt->nid, &ni); |
| ret = dev_read_block(node_blk, ni.blk_addr); |
| ASSERT(ret >= 0); |
| footer = F2FS_NODE_FOOTER(node_blk); |
| |
| if (!strcmp(opt->mb, "nid")) { |
| MSG(0, "Info: inject node footer nid of nid %u: %u -> %u\n", |
| opt->nid, le32_to_cpu(footer->nid), (u32)opt->val); |
| footer->nid = cpu_to_le32((u32)opt->val); |
| } else if (!strcmp(opt->mb, "ino")) { |
| MSG(0, "Info: inject node footer ino of nid %u: %u -> %u\n", |
| opt->nid, le32_to_cpu(footer->ino), (u32)opt->val); |
| footer->ino = cpu_to_le32((u32)opt->val); |
| } else if (!strcmp(opt->mb, "flag")) { |
| MSG(0, "Info: inject node footer flag of nid %u: " |
| "0x%x -> 0x%x\n", opt->nid, le32_to_cpu(footer->flag), |
| (u32)opt->val); |
| footer->flag = cpu_to_le32((u32)opt->val); |
| } else if (!strcmp(opt->mb, "cp_ver")) { |
| MSG(0, "Info: inject node footer cp_ver of nid %u: " |
| "0x%"PRIx64" -> 0x%"PRIx64"\n", opt->nid, le64_to_cpu(footer->cp_ver), |
| (u64)opt->val); |
| footer->cp_ver = cpu_to_le64((u64)opt->val); |
| } else if (!strcmp(opt->mb, "next_blkaddr")) { |
| MSG(0, "Info: inject node footer next_blkaddr of nid %u: " |
| "0x%x -> 0x%x\n", opt->nid, |
| le32_to_cpu(footer->next_blkaddr), (u32)opt->val); |
| footer->next_blkaddr = cpu_to_le32((u32)opt->val); |
| } else if (ni.nid == ni.ino) { |
| ret = inject_inode(sbi, node_blk, opt); |
| } else { |
| ret = inject_index_node(sbi, node_blk, opt); |
| } |
| if (ret) |
| goto out; |
| |
| print_node_footer_info(footer); |
| |
| /* |
| * if i_inode_checksum is injected, should call update_block() to |
| * avoid recalculate inode checksum |
| */ |
| if (ni.nid == ni.ino && strcmp(opt->mb, "i_inode_checksum")) |
| ret = update_inode(sbi, node_blk, &ni.blk_addr); |
| else |
| ret = update_block(sbi, node_blk, &ni.blk_addr, NULL); |
| ASSERT(ret >= 0); |
| |
| out: |
| free(node_blk); |
| return ret; |
| } |
| |
| static int find_dir_entry(struct f2fs_dentry_ptr *d, nid_t ino) |
| { |
| struct f2fs_dir_entry *de; |
| int slot = 0; |
| |
| while (slot < d->max) { |
| if (!test_bit_le(slot, d->bitmap)) { |
| slot++; |
| continue; |
| } |
| |
| de = &d->dentry[slot]; |
| if (de->name_len == 0) { |
| slot++; |
| continue; |
| } |
| if (le32_to_cpu(de->ino) == ino) |
| return slot; |
| slot += GET_DENTRY_SLOTS(le16_to_cpu(de->name_len)); |
| } |
| |
| return -ENOENT; |
| } |
| |
| static int inject_dentry(struct f2fs_sb_info *sbi, struct inject_option *opt) |
| { |
| struct node_info ni; |
| struct f2fs_node *node_blk = NULL; |
| struct f2fs_inode *inode; |
| struct f2fs_dentry_ptr d; |
| void *buf = NULL, *inline_dentry; |
| struct f2fs_dentry_block *dent_blk = NULL; |
| block_t addr = 0; |
| struct f2fs_dir_entry *dent = NULL; |
| struct dnode_of_data dn; |
| nid_t pino; |
| int slot = -ENOENT, namelen, namecap, ret; |
| unsigned int dentry_hash; |
| char *name; |
| |
| node_blk = malloc(F2FS_BLKSIZE); |
| ASSERT(node_blk != NULL); |
| |
| /* get child inode */ |
| get_node_info(sbi, opt->nid, &ni); |
| ret = dev_read_block(node_blk, ni.blk_addr); |
| ASSERT(ret >= 0); |
| |
| if (opt->dots) { |
| if (!LINUX_S_ISDIR(le16_to_cpu(node_blk->i.i_mode))) { |
| ERR_MSG("ino %u is not a directory, cannot inject " |
| "its %s\n", opt->nid, |
| opt->dots == TYPE_DOT ? "." : ".."); |
| ret = -EINVAL; |
| goto out; |
| } |
| /* pino is itself */ |
| pino = opt->nid; |
| } else { |
| pino = le32_to_cpu(node_blk->i.i_pino); |
| |
| /* get parent inode */ |
| get_node_info(sbi, pino, &ni); |
| ret = dev_read_block(node_blk, ni.blk_addr); |
| ASSERT(ret >= 0); |
| } |
| inode = &node_blk->i; |
| |
| /* find child dentry */ |
| if (inode->i_inline & F2FS_INLINE_DENTRY) { |
| inline_dentry = inline_data_addr(node_blk); |
| make_dentry_ptr(&d, node_blk, inline_dentry, 2); |
| addr = ni.blk_addr; |
| buf = node_blk; |
| |
| if (opt->dots == TYPE_DOTDOT) |
| slot = find_dir_entry(&d, le32_to_cpu(node_blk->i.i_pino)); |
| else |
| slot = find_dir_entry(&d, opt->nid); |
| if (slot >= 0) |
| dent = &d.dentry[slot]; |
| } else { |
| unsigned int level, dirlevel, nbucket; |
| unsigned long i, end; |
| |
| level = le32_to_cpu(inode->i_current_depth); |
| dirlevel = le32_to_cpu(inode->i_dir_level); |
| nbucket = dir_buckets(level, dirlevel); |
| end = dir_block_index(level, dirlevel, nbucket) + |
| bucket_blocks(level); |
| |
| dent_blk = malloc(F2FS_BLKSIZE); |
| ASSERT(dent_blk != NULL); |
| |
| for (i = 0; i < end; i++) { |
| memset(&dn, 0, sizeof(dn)); |
| set_new_dnode(&dn, node_blk, NULL, pino); |
| ret = get_dnode_of_data(sbi, &dn, i, LOOKUP_NODE); |
| if (ret < 0) |
| break; |
| addr = dn.data_blkaddr; |
| if (dn.inode_blk != dn.node_blk) |
| free(dn.node_blk); |
| if (addr == NULL_ADDR || addr == NEW_ADDR) |
| continue; |
| if (!f2fs_is_valid_blkaddr(sbi, addr, DATA_GENERIC)) { |
| MSG(0, "invalid blkaddr 0x%x at offset %lu\n", |
| addr, i); |
| continue; |
| } |
| ret = dev_read_block(dent_blk, addr); |
| ASSERT(ret >= 0); |
| |
| make_dentry_ptr(&d, node_blk, dent_blk, 1); |
| if (opt->dots == TYPE_DOTDOT) |
| slot = find_dir_entry(&d, le32_to_cpu(node_blk->i.i_pino)); |
| else |
| slot = find_dir_entry(&d, opt->nid); |
| if (slot >= 0) { |
| dent = &d.dentry[slot]; |
| buf = dent_blk; |
| break; |
| } |
| } |
| } |
| |
| if (slot < 0) { |
| ERR_MSG("dentry of ino %u not found\n", opt->nid); |
| ret = -ENOENT; |
| goto out; |
| } |
| |
| if (!strcmp(opt->mb, "d_bitmap")) { |
| MSG(0, "Info: inject dentry bitmap of nid %u: 1 -> 0\n", |
| opt->nid); |
| test_and_clear_bit_le(slot, d.bitmap); |
| } else if (!strcmp(opt->mb, "d_hash")) { |
| MSG(0, "Info: inject dentry d_hash of nid %u: " |
| "0x%x -> 0x%x\n", opt->nid, le32_to_cpu(dent->hash_code), |
| (u32)opt->val); |
| dent->hash_code = cpu_to_le32((u32)opt->val); |
| } else if (!strcmp(opt->mb, "d_ino")) { |
| MSG(0, "Info: inject dentry d_ino of nid %u: " |
| "%u -> %u\n", opt->nid, le32_to_cpu(dent->ino), |
| (nid_t)opt->val); |
| dent->ino = cpu_to_le32((nid_t)opt->val); |
| } else if (!strcmp(opt->mb, "d_ftype")) { |
| MSG(0, "Info: inject dentry d_type of nid %u: " |
| "%d -> %d\n", opt->nid, dent->file_type, |
| (u8)opt->val); |
| dent->file_type = (u8)opt->val; |
| } else if (!strcmp(opt->mb, "filename")) { |
| if (!opt->str) { |
| ERR_MSG("option str is needed\n"); |
| ret = -EINVAL; |
| goto out; |
| } |
| namecap = ALIGN_UP(le16_to_cpu(dent->name_len), F2FS_SLOT_LEN); |
| namelen = strlen(opt->str); |
| if (namelen > namecap || namelen > F2FS_NAME_LEN) { |
| ERR_MSG("option str too long\n"); |
| ret = -EINVAL; |
| goto out; |
| } |
| name = (char *)d.filename[slot]; |
| MSG(0, "Info: inject dentry filename of nid %u: " |
| "%.*s -> %s\n", opt->nid, le16_to_cpu(dent->name_len), |
| name, opt->str); |
| memcpy(name, opt->str, namelen); |
| MSG(0, "Info: inject dentry namelen of nid %u: " |
| "%d -> %d\n", opt->nid, le16_to_cpu(dent->name_len), |
| namelen); |
| dent->name_len = cpu_to_le16(namelen); |
| dentry_hash = f2fs_dentry_hash(get_encoding(sbi), |
| IS_CASEFOLDED(inode), |
| (unsigned char *)name, |
| namelen); |
| MSG(0, "Info: inject dentry d_hash of nid %u: " |
| "0x%x -> 0x%x\n", opt->nid, le32_to_cpu(dent->hash_code), |
| dentry_hash); |
| dent->hash_code = cpu_to_le32(dentry_hash); |
| } else { |
| ERR_MSG("unknown or unsupported member \"%s\"\n", opt->mb); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| print_raw_dentry_info(dent); |
| |
| if (inode->i_inline & F2FS_INLINE_DENTRY) |
| ret = update_inode(sbi, buf, &addr); |
| else |
| ret = update_block(sbi, buf, &addr, NULL); |
| ASSERT(ret >= 0); |
| |
| out: |
| free(node_blk); |
| free(dent_blk); |
| return ret; |
| } |
| |
| int do_inject(struct f2fs_sb_info *sbi) |
| { |
| struct inject_option *opt = (struct inject_option *)c.private; |
| int ret = -EINVAL; |
| |
| if (c.zoned_model == F2FS_ZONED_HM) |
| fsck_init(sbi); |
| |
| if (opt->sb >= 0) |
| ret = inject_sb(sbi, opt); |
| else if (opt->cp >= 0) |
| ret = inject_cp(sbi, opt); |
| else if (opt->nat >= 0) |
| ret = inject_nat(sbi, opt); |
| else if (opt->sit >= 0) |
| ret = inject_sit(sbi, opt); |
| else if (opt->ssa) |
| ret = inject_ssa(sbi, opt); |
| else if (opt->node) |
| ret = inject_node(sbi, opt); |
| else if (opt->dent) |
| ret = inject_dentry(sbi, opt); |
| |
| if (c.zoned_model == F2FS_ZONED_HM) { |
| if (!ret && (opt->node || opt->dent)) { |
| write_curseg_info(sbi); |
| flush_journal_entries(sbi); |
| flush_sit_entries(sbi); |
| write_checkpoint(sbi); |
| } |
| fsck_free(sbi); |
| } |
| |
| return ret; |
| } |