|  | // 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); | 
|  | } |