]>
Commit | Line | Data |
---|---|---|
959ef981 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
21f0bffe DW |
2 | /* |
3 | * Copyright (C) 2017 Oracle. All Rights Reserved. | |
21f0bffe | 4 | * Author: Darrick J. Wong <darrick.wong@oracle.com> |
21f0bffe DW |
5 | */ |
6 | #include "libxfs.h" | |
7 | #include "command.h" | |
8 | #include "output.h" | |
9 | #include "init.h" | |
10 | #include "io.h" | |
11 | #include "type.h" | |
12 | #include "input.h" | |
13 | ||
14 | static void | |
15 | btdump_help(void) | |
16 | { | |
17 | dbprintf(_( | |
18 | "\n" | |
19 | " If the cursor points to a btree block, 'btdump' dumps the btree\n" | |
20 | " downward from that block. If the cursor points to an inode,\n" | |
a05feedb DW |
21 | " the data fork btree root is selected by default. If the cursor\n" |
22 | " points to a directory or extended attribute btree node, the tree\n" | |
23 | " will be printed downward from that block.\n" | |
21f0bffe DW |
24 | "\n" |
25 | " Options:\n" | |
26 | " -a -- Display an inode's extended attribute fork btree.\n" | |
27 | " -i -- Print internal btree nodes.\n" | |
28 | "\n" | |
29 | )); | |
30 | ||
31 | } | |
32 | ||
33 | static int | |
34 | eval( | |
35 | const char *fmt, ...) | |
36 | { | |
37 | va_list ap; | |
38 | char buf[PATH_MAX]; | |
39 | char **v; | |
40 | int c; | |
41 | int ret; | |
42 | ||
43 | va_start(ap, fmt); | |
44 | vsnprintf(buf, sizeof(buf), fmt, ap); | |
45 | va_end(ap); | |
46 | ||
47 | v = breakline(buf, &c); | |
48 | ret = command(c, v); | |
49 | free(v); | |
50 | return ret; | |
51 | } | |
52 | ||
53 | static bool | |
54 | btblock_has_rightsib( | |
55 | struct xfs_btree_block *block, | |
56 | bool long_format) | |
57 | { | |
58 | if (long_format) | |
59 | return block->bb_u.l.bb_rightsib != cpu_to_be64(NULLFSBLOCK); | |
60 | return block->bb_u.s.bb_rightsib != cpu_to_be32(NULLAGBLOCK); | |
61 | } | |
62 | ||
63 | static int | |
64 | dump_btlevel( | |
65 | int level, | |
66 | bool long_format) | |
67 | { | |
68 | xfs_daddr_t orig_daddr = iocur_top->bb; | |
69 | xfs_daddr_t last_daddr; | |
70 | unsigned int nr; | |
3540b418 | 71 | int ret = 0; |
21f0bffe | 72 | |
3540b418 | 73 | push_cur_and_set_type(); |
21f0bffe DW |
74 | |
75 | nr = 1; | |
76 | do { | |
77 | last_daddr = iocur_top->bb; | |
78 | dbprintf(_("%s level %u block %u daddr %llu\n"), | |
79 | iocur_top->typ->name, level, nr, last_daddr); | |
80 | if (level > 0) { | |
81 | ret = eval("print keys"); | |
82 | if (ret) | |
83 | goto err; | |
84 | ret = eval("print ptrs"); | |
85 | } else { | |
86 | ret = eval("print recs"); | |
87 | } | |
88 | if (ret) | |
89 | goto err; | |
90 | if (btblock_has_rightsib(iocur_top->data, long_format)) { | |
91 | ret = eval("addr rightsib"); | |
92 | if (ret) | |
93 | goto err; | |
94 | } | |
95 | nr++; | |
96 | } while (iocur_top->bb != orig_daddr && iocur_top->bb != last_daddr); | |
97 | ||
21f0bffe | 98 | err: |
3540b418 | 99 | pop_cur(); |
21f0bffe DW |
100 | return ret; |
101 | } | |
102 | ||
103 | static int | |
104 | dump_btree( | |
105 | bool dump_node_blocks, | |
106 | bool long_format) | |
107 | { | |
108 | xfs_daddr_t orig_daddr = iocur_top->bb; | |
109 | xfs_daddr_t last_daddr; | |
110 | int level; | |
3540b418 | 111 | int ret = 0; |
21f0bffe | 112 | |
3540b418 | 113 | push_cur_and_set_type(); |
21f0bffe DW |
114 | |
115 | cur_agno = XFS_FSB_TO_AGNO(mp, XFS_DADDR_TO_FSB(mp, iocur_top->bb)); | |
116 | level = xfs_btree_get_level(iocur_top->data); | |
117 | do { | |
118 | last_daddr = iocur_top->bb; | |
119 | if (level > 0) { | |
120 | if (dump_node_blocks) { | |
121 | ret = dump_btlevel(level, long_format); | |
122 | if (ret) | |
123 | goto err; | |
124 | } | |
125 | ret = eval("addr ptrs[1]"); | |
126 | } else { | |
127 | ret = dump_btlevel(level, long_format); | |
128 | } | |
129 | if (ret) | |
130 | goto err; | |
131 | level--; | |
132 | } while (level >= 0 && | |
133 | iocur_top->bb != orig_daddr && | |
134 | iocur_top->bb != last_daddr); | |
135 | ||
21f0bffe | 136 | err: |
3540b418 | 137 | pop_cur(); |
21f0bffe DW |
138 | return ret; |
139 | } | |
140 | ||
141 | static inline int dump_btree_short(bool dump_node_blocks) | |
142 | { | |
143 | return dump_btree(dump_node_blocks, false); | |
144 | } | |
145 | ||
146 | static inline int dump_btree_long(bool dump_node_blocks) | |
147 | { | |
148 | return dump_btree(dump_node_blocks, true); | |
149 | } | |
150 | ||
151 | static int | |
152 | dump_inode( | |
153 | bool dump_node_blocks, | |
154 | bool attrfork) | |
155 | { | |
156 | char *prefix; | |
157 | struct xfs_dinode *dip; | |
3540b418 | 158 | int ret = 0; |
21f0bffe DW |
159 | |
160 | if (attrfork) | |
161 | prefix = "a.bmbt"; | |
162 | else if (xfs_sb_version_hascrc(&mp->m_sb)) | |
163 | prefix = "u3.bmbt"; | |
164 | else | |
165 | prefix = "u.bmbt"; | |
166 | ||
167 | dip = iocur_top->data; | |
168 | if (attrfork) { | |
169 | if (!dip->di_anextents || | |
170 | dip->di_aformat != XFS_DINODE_FMT_BTREE) { | |
171 | dbprintf(_("attr fork not in btree format\n")); | |
172 | return 0; | |
173 | } | |
174 | } else { | |
175 | if (!dip->di_nextents || | |
176 | dip->di_format != XFS_DINODE_FMT_BTREE) { | |
177 | dbprintf(_("data fork not in btree format\n")); | |
178 | return 0; | |
179 | } | |
180 | } | |
181 | ||
3540b418 | 182 | push_cur_and_set_type(); |
21f0bffe DW |
183 | |
184 | if (dump_node_blocks) { | |
185 | ret = eval("print %s.keys", prefix); | |
186 | if (ret) | |
187 | goto err; | |
188 | ret = eval("print %s.ptrs", prefix); | |
189 | if (ret) | |
190 | goto err; | |
191 | } | |
192 | ||
193 | ret = eval("addr %s.ptrs[1]", prefix); | |
194 | if (ret) | |
195 | goto err; | |
196 | ||
197 | ret = dump_btree_long(dump_node_blocks); | |
198 | if (ret) | |
199 | goto err; | |
200 | ||
21f0bffe | 201 | err: |
3540b418 | 202 | pop_cur(); |
21f0bffe DW |
203 | return ret; |
204 | } | |
205 | ||
a05feedb DW |
206 | static bool |
207 | dir_has_rightsib( | |
208 | void *block, | |
209 | int level) | |
210 | { | |
211 | struct xfs_dir3_icleaf_hdr lhdr; | |
212 | struct xfs_da3_icnode_hdr nhdr; | |
213 | ||
214 | if (level > 0) { | |
215 | M_DIROPS(mp)->node_hdr_from_disk(&nhdr, block); | |
216 | return nhdr.forw != 0; | |
217 | } | |
218 | M_DIROPS(mp)->leaf_hdr_from_disk(&lhdr, block); | |
219 | return lhdr.forw != 0; | |
220 | } | |
221 | ||
222 | static int | |
223 | dir_level( | |
224 | void *block) | |
225 | { | |
226 | struct xfs_dir3_icleaf_hdr lhdr; | |
227 | struct xfs_da3_icnode_hdr nhdr; | |
228 | ||
229 | switch (((struct xfs_da_intnode *)block)->hdr.info.magic) { | |
230 | case cpu_to_be16(XFS_DIR2_LEAF1_MAGIC): | |
231 | case cpu_to_be16(XFS_DIR2_LEAFN_MAGIC): | |
232 | M_DIROPS(mp)->leaf_hdr_from_disk(&lhdr, block); | |
233 | return 0; | |
234 | case cpu_to_be16(XFS_DA_NODE_MAGIC): | |
235 | M_DIROPS(mp)->node_hdr_from_disk(&nhdr, block); | |
236 | return nhdr.level; | |
237 | default: | |
238 | return -1; | |
239 | } | |
240 | } | |
241 | ||
242 | static int | |
243 | dir3_level( | |
244 | void *block) | |
245 | { | |
246 | struct xfs_dir3_icleaf_hdr lhdr; | |
247 | struct xfs_da3_icnode_hdr nhdr; | |
248 | ||
249 | switch (((struct xfs_da_intnode *)block)->hdr.info.magic) { | |
250 | case cpu_to_be16(XFS_DIR3_LEAF1_MAGIC): | |
251 | case cpu_to_be16(XFS_DIR3_LEAFN_MAGIC): | |
252 | M_DIROPS(mp)->leaf_hdr_from_disk(&lhdr, block); | |
253 | return 0; | |
254 | case cpu_to_be16(XFS_DA3_NODE_MAGIC): | |
255 | M_DIROPS(mp)->node_hdr_from_disk(&nhdr, block); | |
256 | return nhdr.level; | |
257 | default: | |
258 | return -1; | |
259 | } | |
260 | } | |
261 | ||
262 | static bool | |
263 | attr_has_rightsib( | |
264 | void *block, | |
265 | int level) | |
266 | { | |
267 | struct xfs_attr_leafblock lhdr; | |
268 | struct xfs_da3_icnode_hdr nhdr; | |
269 | ||
270 | if (level > 0) { | |
271 | M_DIROPS(mp)->node_hdr_from_disk(&nhdr, block); | |
272 | return nhdr.forw != 0; | |
273 | } | |
274 | xfs_attr3_leaf_hdr_to_disk(mp->m_attr_geo, &lhdr, block); | |
275 | return lhdr.hdr.info.forw != 0; | |
276 | } | |
277 | ||
278 | static int | |
279 | attr_level( | |
280 | void *block) | |
281 | { | |
282 | struct xfs_attr_leafblock lhdr; | |
283 | struct xfs_da3_icnode_hdr nhdr; | |
284 | ||
285 | switch (((struct xfs_da_intnode *)block)->hdr.info.magic) { | |
286 | case cpu_to_be16(XFS_ATTR_LEAF_MAGIC): | |
287 | xfs_attr3_leaf_hdr_to_disk(mp->m_attr_geo, &lhdr, block); | |
288 | return 0; | |
289 | case cpu_to_be16(XFS_DA_NODE_MAGIC): | |
290 | M_DIROPS(mp)->node_hdr_from_disk(&nhdr, block); | |
291 | return nhdr.level; | |
292 | default: | |
293 | return -1; | |
294 | } | |
295 | } | |
296 | ||
297 | static int | |
298 | attr3_level( | |
299 | void *block) | |
300 | { | |
301 | struct xfs_attr_leafblock lhdr; | |
302 | struct xfs_da3_icnode_hdr nhdr; | |
303 | ||
304 | switch (((struct xfs_da_intnode *)block)->hdr.info.magic) { | |
305 | case cpu_to_be16(XFS_ATTR3_LEAF_MAGIC): | |
306 | xfs_attr3_leaf_hdr_to_disk(mp->m_attr_geo, &lhdr, block); | |
307 | return 0; | |
308 | case cpu_to_be16(XFS_DA3_NODE_MAGIC): | |
309 | M_DIROPS(mp)->node_hdr_from_disk(&nhdr, block); | |
310 | return nhdr.level; | |
311 | default: | |
312 | return -1; | |
313 | } | |
314 | } | |
315 | ||
316 | struct dabprinter_ops { | |
317 | const char *print_node_entries; | |
318 | const char *print_leaf_entries; | |
319 | const char *go_node_forward; | |
320 | const char *go_leaf_forward; | |
321 | const char *go_down; | |
322 | bool (*has_rightsib)(void *, int); | |
323 | int (*level)(void *); | |
324 | }; | |
325 | ||
326 | static struct dabprinter_ops attr_print = { | |
327 | .print_node_entries = "btree", | |
328 | .print_leaf_entries = "entries nvlist", | |
329 | .go_node_forward = "hdr.info.forw", | |
330 | .go_leaf_forward = "hdr.info.forw", | |
331 | .go_down = "btree[0].before", | |
332 | .has_rightsib = attr_has_rightsib, | |
333 | .level = attr_level, | |
334 | }; | |
335 | ||
336 | static struct dabprinter_ops attr3_print = { | |
337 | .print_node_entries = "btree", | |
338 | .print_leaf_entries = "entries nvlist", | |
339 | .go_node_forward = "hdr.info.hdr.forw", | |
340 | .go_leaf_forward = "hdr.info.hdr.forw", | |
341 | .go_down = "btree[0].before", | |
342 | .has_rightsib = attr_has_rightsib, | |
343 | .level = attr3_level, | |
344 | }; | |
345 | ||
346 | static struct dabprinter_ops dir_print = { | |
347 | .print_node_entries = "nbtree", | |
348 | .print_leaf_entries = "lents", | |
349 | .go_node_forward = "nhdr.info.hdr.forw", | |
350 | .go_leaf_forward = "lhdr.info.hdr.forw", | |
351 | .go_down = "nbtree[0].before", | |
352 | .has_rightsib = dir_has_rightsib, | |
353 | .level = dir_level, | |
354 | }; | |
355 | ||
356 | static struct dabprinter_ops dir3_print = { | |
357 | .print_node_entries = "nbtree", | |
358 | .print_leaf_entries = "lents", | |
359 | .go_node_forward = "nhdr.info.forw", | |
360 | .go_leaf_forward = "lhdr.info.forw", | |
361 | .go_down = "nbtree[0].before", | |
362 | .has_rightsib = dir_has_rightsib, | |
363 | .level = dir3_level, | |
364 | }; | |
365 | ||
366 | static int | |
367 | dump_dablevel( | |
368 | int level, | |
369 | struct dabprinter_ops *dbp) | |
370 | { | |
371 | xfs_daddr_t orig_daddr = iocur_top->bb; | |
372 | xfs_daddr_t last_daddr; | |
373 | unsigned int nr; | |
3540b418 | 374 | int ret = 0; |
a05feedb | 375 | |
3540b418 | 376 | push_cur_and_set_type(); |
a05feedb DW |
377 | |
378 | nr = 1; | |
379 | do { | |
380 | last_daddr = iocur_top->bb; | |
381 | dbprintf(_("%s level %u block %u daddr %llu\n"), | |
382 | iocur_top->typ->name, level, nr, last_daddr); | |
383 | ret = eval("print %s", level > 0 ? dbp->print_node_entries : | |
384 | dbp->print_leaf_entries); | |
385 | if (ret) | |
386 | goto err; | |
387 | if (dbp->has_rightsib(iocur_top->data, level)) { | |
388 | ret = eval("addr %s", level > 0 ? dbp->go_node_forward : | |
389 | dbp->go_leaf_forward); | |
390 | if (ret) | |
391 | goto err; | |
392 | } | |
393 | nr++; | |
394 | } while (iocur_top->bb != orig_daddr && iocur_top->bb != last_daddr); | |
395 | ||
a05feedb | 396 | err: |
3540b418 | 397 | pop_cur(); |
a05feedb DW |
398 | return ret; |
399 | } | |
400 | ||
401 | static int | |
402 | dump_dabtree( | |
403 | bool dump_node_blocks, | |
404 | struct dabprinter_ops *dbp) | |
405 | { | |
406 | xfs_daddr_t orig_daddr = iocur_top->bb; | |
407 | xfs_daddr_t last_daddr; | |
408 | int level; | |
3540b418 | 409 | int ret = 0; |
a05feedb | 410 | |
3540b418 | 411 | push_cur_and_set_type(); |
a05feedb DW |
412 | |
413 | cur_agno = XFS_FSB_TO_AGNO(mp, XFS_DADDR_TO_FSB(mp, iocur_top->bb)); | |
414 | level = dbp->level(iocur_top->data); | |
415 | if (level < 0) { | |
416 | printf(_("Current location is not part of a dir/attr btree.\n")); | |
417 | goto err; | |
418 | } | |
419 | ||
420 | do { | |
421 | last_daddr = iocur_top->bb; | |
422 | if (level > 0) { | |
423 | if (dump_node_blocks) { | |
424 | ret = dump_dablevel(level, dbp); | |
425 | if (ret) | |
426 | goto err; | |
427 | } | |
428 | ret = eval("addr %s", dbp->go_down); | |
429 | } else { | |
430 | ret = dump_dablevel(level, dbp); | |
431 | } | |
432 | if (ret) | |
433 | goto err; | |
434 | level--; | |
435 | } while (level >= 0 && | |
436 | iocur_top->bb != orig_daddr && | |
437 | iocur_top->bb != last_daddr); | |
438 | ||
a05feedb | 439 | err: |
3540b418 | 440 | pop_cur(); |
a05feedb DW |
441 | return ret; |
442 | } | |
443 | ||
21f0bffe DW |
444 | static int |
445 | btdump_f( | |
446 | int argc, | |
447 | char **argv) | |
448 | { | |
449 | bool aflag = false; | |
450 | bool iflag = false; | |
a05feedb | 451 | bool crc = xfs_sb_version_hascrc(&mp->m_sb); |
21f0bffe DW |
452 | int c; |
453 | ||
454 | if (cur_typ == NULL) { | |
455 | dbprintf(_("no current type\n")); | |
456 | return 0; | |
457 | } | |
458 | while ((c = getopt(argc, argv, "ai")) != EOF) { | |
459 | switch (c) { | |
460 | case 'a': | |
461 | aflag = true; | |
462 | break; | |
463 | case 'i': | |
464 | iflag = true; | |
465 | break; | |
466 | default: | |
467 | dbprintf(_("bad option for btdump command\n")); | |
468 | return 0; | |
469 | } | |
470 | } | |
471 | ||
472 | if (optind != argc) { | |
473 | dbprintf(_("bad options for btdump command\n")); | |
474 | return 0; | |
475 | } | |
476 | if (aflag && cur_typ->typnm != TYP_INODE) { | |
477 | dbprintf(_("attrfork flag doesn't apply here\n")); | |
478 | return 0; | |
479 | } | |
480 | ||
481 | switch (cur_typ->typnm) { | |
482 | case TYP_BNOBT: | |
483 | case TYP_CNTBT: | |
484 | case TYP_INOBT: | |
485 | case TYP_FINOBT: | |
486 | case TYP_RMAPBT: | |
487 | case TYP_REFCBT: | |
488 | return dump_btree_short(iflag); | |
489 | case TYP_BMAPBTA: | |
490 | case TYP_BMAPBTD: | |
491 | return dump_btree_long(iflag); | |
492 | case TYP_INODE: | |
493 | return dump_inode(iflag, aflag); | |
a05feedb DW |
494 | case TYP_ATTR: |
495 | return dump_dabtree(iflag, crc ? &attr3_print : &attr_print); | |
496 | case TYP_DIR2: | |
497 | return dump_dabtree(iflag, crc ? &dir3_print : &dir_print); | |
21f0bffe DW |
498 | default: |
499 | dbprintf(_("type \"%s\" is not a btree type or inode\n"), | |
500 | cur_typ->name); | |
501 | return 0; | |
502 | } | |
503 | } | |
504 | ||
505 | static const cmdinfo_t btdump_cmd = | |
506 | { "btdump", "b", btdump_f, 0, 2, 0, "[-a] [-i]", | |
507 | N_("dump btree"), btdump_help }; | |
508 | ||
509 | void | |
510 | btdump_init(void) | |
511 | { | |
512 | add_command(&btdump_cmd); | |
513 | } |