| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2017 Oracle. All Rights Reserved. |
| * Author: Darrick J. Wong <darrick.wong@oracle.com> |
| */ |
| #include "libxfs.h" |
| #include "command.h" |
| #include "output.h" |
| #include "init.h" |
| #include "io.h" |
| #include "type.h" |
| #include "input.h" |
| |
| static void |
| btdump_help(void) |
| { |
| dbprintf(_( |
| "\n" |
| " If the cursor points to a btree block, 'btdump' dumps the btree\n" |
| " downward from that block. If the cursor points to an inode,\n" |
| " the data fork btree root is selected by default. If the cursor\n" |
| " points to a directory or extended attribute btree node, the tree\n" |
| " will be printed downward from that block.\n" |
| "\n" |
| " Options:\n" |
| " -a -- Display an inode's extended attribute fork btree.\n" |
| " -i -- Print internal btree nodes.\n" |
| "\n" |
| )); |
| |
| } |
| |
| static int |
| eval( |
| const char *fmt, ...) |
| { |
| va_list ap; |
| char buf[PATH_MAX]; |
| char **v; |
| int c; |
| int ret; |
| |
| va_start(ap, fmt); |
| vsnprintf(buf, sizeof(buf), fmt, ap); |
| va_end(ap); |
| |
| v = breakline(buf, &c); |
| ret = command(c, v); |
| free(v); |
| return ret; |
| } |
| |
| static bool |
| btblock_has_rightsib( |
| struct xfs_btree_block *block, |
| bool long_format) |
| { |
| if (long_format) |
| return block->bb_u.l.bb_rightsib != cpu_to_be64(NULLFSBLOCK); |
| return block->bb_u.s.bb_rightsib != cpu_to_be32(NULLAGBLOCK); |
| } |
| |
| static int |
| dump_btlevel( |
| int level, |
| bool long_format) |
| { |
| xfs_daddr_t orig_daddr = iocur_top->bb; |
| xfs_daddr_t last_daddr; |
| unsigned int nr; |
| int ret = 0; |
| |
| push_cur_and_set_type(); |
| |
| nr = 1; |
| do { |
| last_daddr = iocur_top->bb; |
| dbprintf(_("%s level %u block %u daddr %llu\n"), |
| iocur_top->typ->name, level, nr, last_daddr); |
| if (level > 0) { |
| ret = eval("print keys"); |
| if (ret) |
| goto err; |
| ret = eval("print ptrs"); |
| } else { |
| ret = eval("print recs"); |
| } |
| if (ret) |
| goto err; |
| if (btblock_has_rightsib(iocur_top->data, long_format)) { |
| ret = eval("addr rightsib"); |
| if (ret) |
| goto err; |
| } |
| nr++; |
| } while (iocur_top->bb != orig_daddr && iocur_top->bb != last_daddr); |
| |
| err: |
| pop_cur(); |
| return ret; |
| } |
| |
| static int |
| dump_btree( |
| bool dump_node_blocks, |
| bool long_format) |
| { |
| xfs_daddr_t orig_daddr = iocur_top->bb; |
| xfs_daddr_t last_daddr; |
| int level; |
| int ret = 0; |
| |
| push_cur_and_set_type(); |
| |
| cur_agno = XFS_FSB_TO_AGNO(mp, XFS_DADDR_TO_FSB(mp, iocur_top->bb)); |
| level = xfs_btree_get_level(iocur_top->data); |
| do { |
| last_daddr = iocur_top->bb; |
| if (level > 0) { |
| if (dump_node_blocks) { |
| ret = dump_btlevel(level, long_format); |
| if (ret) |
| goto err; |
| } |
| ret = eval("addr ptrs[1]"); |
| } else { |
| ret = dump_btlevel(level, long_format); |
| } |
| if (ret) |
| goto err; |
| level--; |
| } while (level >= 0 && |
| iocur_top->bb != orig_daddr && |
| iocur_top->bb != last_daddr); |
| |
| err: |
| pop_cur(); |
| return ret; |
| } |
| |
| static inline int dump_btree_short(bool dump_node_blocks) |
| { |
| return dump_btree(dump_node_blocks, false); |
| } |
| |
| static inline int dump_btree_long(bool dump_node_blocks) |
| { |
| return dump_btree(dump_node_blocks, true); |
| } |
| |
| static int |
| dump_inode( |
| bool dump_node_blocks, |
| bool attrfork) |
| { |
| char *prefix; |
| struct xfs_dinode *dip; |
| int ret = 0; |
| |
| if (attrfork) |
| prefix = "a.bmbt"; |
| else if (xfs_has_crc(mp)) |
| prefix = "u3.bmbt"; |
| else |
| prefix = "u.bmbt"; |
| |
| dip = iocur_top->data; |
| if (attrfork) { |
| if (!xfs_dfork_attr_extents(dip) || |
| dip->di_aformat != XFS_DINODE_FMT_BTREE) { |
| dbprintf(_("attr fork not in btree format\n")); |
| return 0; |
| } |
| } else { |
| if (!xfs_dfork_data_extents(dip) || |
| dip->di_format != XFS_DINODE_FMT_BTREE) { |
| dbprintf(_("data fork not in btree format\n")); |
| return 0; |
| } |
| } |
| |
| push_cur_and_set_type(); |
| |
| if (dump_node_blocks) { |
| ret = eval("print %s.keys", prefix); |
| if (ret) |
| goto err; |
| ret = eval("print %s.ptrs", prefix); |
| if (ret) |
| goto err; |
| } |
| |
| ret = eval("addr %s.ptrs[1]", prefix); |
| if (ret) |
| goto err; |
| |
| ret = dump_btree_long(dump_node_blocks); |
| if (ret) |
| goto err; |
| |
| err: |
| pop_cur(); |
| return ret; |
| } |
| |
| static bool |
| dir_has_rightsib( |
| void *block, |
| int level) |
| { |
| struct xfs_dir3_icleaf_hdr lhdr; |
| struct xfs_da3_icnode_hdr nhdr; |
| |
| if (level > 0) { |
| libxfs_da3_node_hdr_from_disk(mp, &nhdr, block); |
| return nhdr.forw != 0; |
| } |
| libxfs_dir2_leaf_hdr_from_disk(mp, &lhdr, block); |
| return lhdr.forw != 0; |
| } |
| |
| static int |
| dir_level( |
| void *block) |
| { |
| struct xfs_dir3_icleaf_hdr lhdr; |
| struct xfs_da3_icnode_hdr nhdr; |
| |
| switch (((struct xfs_da_intnode *)block)->hdr.info.magic) { |
| case cpu_to_be16(XFS_DIR2_LEAF1_MAGIC): |
| case cpu_to_be16(XFS_DIR2_LEAFN_MAGIC): |
| libxfs_dir2_leaf_hdr_from_disk(mp, &lhdr, block); |
| return 0; |
| case cpu_to_be16(XFS_DA_NODE_MAGIC): |
| libxfs_da3_node_hdr_from_disk(mp, &nhdr, block); |
| return nhdr.level; |
| default: |
| return -1; |
| } |
| } |
| |
| static int |
| dir3_level( |
| void *block) |
| { |
| struct xfs_dir3_icleaf_hdr lhdr; |
| struct xfs_da3_icnode_hdr nhdr; |
| |
| switch (((struct xfs_da_intnode *)block)->hdr.info.magic) { |
| case cpu_to_be16(XFS_DIR3_LEAF1_MAGIC): |
| case cpu_to_be16(XFS_DIR3_LEAFN_MAGIC): |
| libxfs_dir2_leaf_hdr_from_disk(mp, &lhdr, block); |
| return 0; |
| case cpu_to_be16(XFS_DA3_NODE_MAGIC): |
| libxfs_da3_node_hdr_from_disk(mp, &nhdr, block); |
| return nhdr.level; |
| default: |
| return -1; |
| } |
| } |
| |
| static bool |
| attr_has_rightsib( |
| void *block, |
| int level) |
| { |
| struct xfs_attr_leafblock lhdr; |
| struct xfs_da3_icnode_hdr nhdr; |
| |
| if (level > 0) { |
| libxfs_da3_node_hdr_from_disk(mp, &nhdr, block); |
| return nhdr.forw != 0; |
| } |
| xfs_attr3_leaf_hdr_to_disk(mp->m_attr_geo, &lhdr, block); |
| return lhdr.hdr.info.forw != 0; |
| } |
| |
| static int |
| attr_level( |
| void *block) |
| { |
| struct xfs_attr_leafblock lhdr; |
| struct xfs_da3_icnode_hdr nhdr; |
| |
| switch (((struct xfs_da_intnode *)block)->hdr.info.magic) { |
| case cpu_to_be16(XFS_ATTR_LEAF_MAGIC): |
| xfs_attr3_leaf_hdr_to_disk(mp->m_attr_geo, &lhdr, block); |
| return 0; |
| case cpu_to_be16(XFS_DA_NODE_MAGIC): |
| libxfs_da3_node_hdr_from_disk(mp, &nhdr, block); |
| return nhdr.level; |
| default: |
| return -1; |
| } |
| } |
| |
| static int |
| attr3_level( |
| void *block) |
| { |
| struct xfs_attr_leafblock lhdr; |
| struct xfs_da3_icnode_hdr nhdr; |
| |
| switch (((struct xfs_da_intnode *)block)->hdr.info.magic) { |
| case cpu_to_be16(XFS_ATTR3_LEAF_MAGIC): |
| xfs_attr3_leaf_hdr_to_disk(mp->m_attr_geo, &lhdr, block); |
| return 0; |
| case cpu_to_be16(XFS_DA3_NODE_MAGIC): |
| libxfs_da3_node_hdr_from_disk(mp, &nhdr, block); |
| return nhdr.level; |
| default: |
| return -1; |
| } |
| } |
| |
| struct dabprinter_ops { |
| const char *print_node_entries; |
| const char *print_leaf_entries; |
| const char *go_node_forward; |
| const char *go_leaf_forward; |
| const char *go_down; |
| bool (*has_rightsib)(void *, int); |
| int (*level)(void *); |
| }; |
| |
| static struct dabprinter_ops attr_print = { |
| .print_node_entries = "btree", |
| .print_leaf_entries = "entries nvlist", |
| .go_node_forward = "hdr.info.forw", |
| .go_leaf_forward = "hdr.info.forw", |
| .go_down = "btree[0].before", |
| .has_rightsib = attr_has_rightsib, |
| .level = attr_level, |
| }; |
| |
| static struct dabprinter_ops attr3_print = { |
| .print_node_entries = "btree", |
| .print_leaf_entries = "entries nvlist", |
| .go_node_forward = "hdr.info.hdr.forw", |
| .go_leaf_forward = "hdr.info.hdr.forw", |
| .go_down = "btree[0].before", |
| .has_rightsib = attr_has_rightsib, |
| .level = attr3_level, |
| }; |
| |
| static struct dabprinter_ops dir_print = { |
| .print_node_entries = "nbtree", |
| .print_leaf_entries = "lents", |
| .go_node_forward = "nhdr.info.hdr.forw", |
| .go_leaf_forward = "lhdr.info.hdr.forw", |
| .go_down = "nbtree[0].before", |
| .has_rightsib = dir_has_rightsib, |
| .level = dir_level, |
| }; |
| |
| static struct dabprinter_ops dir3_print = { |
| .print_node_entries = "nbtree", |
| .print_leaf_entries = "lents", |
| .go_node_forward = "nhdr.info.forw", |
| .go_leaf_forward = "lhdr.info.forw", |
| .go_down = "nbtree[0].before", |
| .has_rightsib = dir_has_rightsib, |
| .level = dir3_level, |
| }; |
| |
| static int |
| dump_dablevel( |
| int level, |
| struct dabprinter_ops *dbp) |
| { |
| xfs_daddr_t orig_daddr = iocur_top->bb; |
| xfs_daddr_t last_daddr; |
| unsigned int nr; |
| int ret = 0; |
| |
| push_cur_and_set_type(); |
| |
| nr = 1; |
| do { |
| last_daddr = iocur_top->bb; |
| dbprintf(_("%s level %u block %u daddr %llu\n"), |
| iocur_top->typ->name, level, nr, last_daddr); |
| ret = eval("print %s", level > 0 ? dbp->print_node_entries : |
| dbp->print_leaf_entries); |
| if (ret) |
| goto err; |
| if (dbp->has_rightsib(iocur_top->data, level)) { |
| ret = eval("addr %s", level > 0 ? dbp->go_node_forward : |
| dbp->go_leaf_forward); |
| if (ret) |
| goto err; |
| } |
| nr++; |
| } while (iocur_top->bb != orig_daddr && iocur_top->bb != last_daddr); |
| |
| err: |
| pop_cur(); |
| return ret; |
| } |
| |
| static int |
| dump_dabtree( |
| bool dump_node_blocks, |
| struct dabprinter_ops *dbp) |
| { |
| xfs_daddr_t orig_daddr = iocur_top->bb; |
| xfs_daddr_t last_daddr; |
| int level; |
| int ret = 0; |
| |
| push_cur_and_set_type(); |
| |
| cur_agno = XFS_FSB_TO_AGNO(mp, XFS_DADDR_TO_FSB(mp, iocur_top->bb)); |
| level = dbp->level(iocur_top->data); |
| if (level < 0) { |
| printf(_("Current location is not part of a dir/attr btree.\n")); |
| goto err; |
| } |
| |
| do { |
| last_daddr = iocur_top->bb; |
| if (level > 0) { |
| if (dump_node_blocks) { |
| ret = dump_dablevel(level, dbp); |
| if (ret) |
| goto err; |
| } |
| ret = eval("addr %s", dbp->go_down); |
| } else { |
| ret = dump_dablevel(level, dbp); |
| } |
| if (ret) |
| goto err; |
| level--; |
| } while (level >= 0 && |
| iocur_top->bb != orig_daddr && |
| iocur_top->bb != last_daddr); |
| |
| err: |
| pop_cur(); |
| return ret; |
| } |
| |
| static int |
| btdump_f( |
| int argc, |
| char **argv) |
| { |
| bool aflag = false; |
| bool iflag = false; |
| bool crc = xfs_has_crc(mp); |
| int c; |
| |
| if (cur_typ == NULL) { |
| dbprintf(_("no current type\n")); |
| return 0; |
| } |
| while ((c = getopt(argc, argv, "ai")) != EOF) { |
| switch (c) { |
| case 'a': |
| aflag = true; |
| break; |
| case 'i': |
| iflag = true; |
| break; |
| default: |
| dbprintf(_("bad option for btdump command\n")); |
| return 0; |
| } |
| } |
| |
| if (optind != argc) { |
| dbprintf(_("bad options for btdump command\n")); |
| return 0; |
| } |
| if (aflag && cur_typ->typnm != TYP_INODE) { |
| dbprintf(_("attrfork flag doesn't apply here\n")); |
| return 0; |
| } |
| |
| switch (cur_typ->typnm) { |
| case TYP_BNOBT: |
| case TYP_CNTBT: |
| case TYP_INOBT: |
| case TYP_FINOBT: |
| case TYP_RMAPBT: |
| case TYP_REFCBT: |
| return dump_btree_short(iflag); |
| case TYP_BMAPBTA: |
| case TYP_BMAPBTD: |
| return dump_btree_long(iflag); |
| case TYP_INODE: |
| return dump_inode(iflag, aflag); |
| case TYP_ATTR: |
| return dump_dabtree(iflag, crc ? &attr3_print : &attr_print); |
| case TYP_DIR2: |
| return dump_dabtree(iflag, crc ? &dir3_print : &dir_print); |
| default: |
| dbprintf(_("type \"%s\" is not a btree type or inode\n"), |
| cur_typ->name); |
| return 0; |
| } |
| } |
| |
| static const cmdinfo_t btdump_cmd = |
| { "btdump", "b", btdump_f, 0, 2, 0, "[-a] [-i]", |
| N_("dump btree"), btdump_help }; |
| |
| void |
| btdump_init(void) |
| { |
| add_command(&btdump_cmd); |
| } |