xref: /dragonfly/sbin/fsck_hammer2/test.c (revision 556932ec)
1 /*
2  * Copyright (c) 2019 Tomohiro Kusumi <tkusumi@netbsd.org>
3  * Copyright (c) 2019 The DragonFly Project
4  * All rights reserved.
5  *
6  * This code is derived from software contributed to The DragonFly Project
7  * by Matthew Dillon <dillon@dragonflybsd.org>
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in
17  *    the documentation and/or other materials provided with the
18  *    distribution.
19  * 3. Neither the name of The DragonFly Project nor the names of its
20  *    contributors may be used to endorse or promote products derived
21  *    from this software without specific, prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
27  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
29  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
31  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <sys/tree.h>
40 #include <sys/queue.h>
41 #include <sys/ttycom.h>
42 #include <sys/diskslice.h>
43 #include <unistd.h>
44 #include <fcntl.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <stdarg.h>
48 #include <stdbool.h>
49 #include <stdint.h>
50 #include <string.h>
51 #include <assert.h>
52 
53 #ifdef HAMMER2_USE_OPENSSL
54 #include <openssl/sha.h>
55 #endif
56 
57 #include <vfs/hammer2/hammer2_disk.h>
58 #include <vfs/hammer2/hammer2_xxhash.h>
59 
60 #include "hammer2_subs.h"
61 #include "fsck_hammer2.h"
62 
63 struct blockref_msg {
64 	TAILQ_ENTRY(blockref_msg) entry;
65 	hammer2_blockref_t bref;
66 	void *msg;
67 };
68 
69 TAILQ_HEAD(blockref_list, blockref_msg);
70 
71 struct blockref_entry {
72 	RB_ENTRY(blockref_entry) entry;
73 	hammer2_off_t data_off;
74 	struct blockref_list head;
75 };
76 
77 static int
78 blockref_cmp(struct blockref_entry *b1, struct blockref_entry *b2)
79 {
80 	if (b1->data_off < b2->data_off)
81 		return -1;
82 	if (b1->data_off > b2->data_off)
83 		return 1;
84 	return 0;
85 }
86 
87 RB_HEAD(blockref_tree, blockref_entry);
88 RB_PROTOTYPE(blockref_tree, blockref_entry, entry, blockref_cmp);
89 RB_GENERATE(blockref_tree, blockref_entry, entry, blockref_cmp);
90 
91 typedef struct {
92 	struct blockref_tree root;
93 	uint8_t type; /* HAMMER2_BREF_TYPE_VOLUME or FREEMAP */
94 	uint64_t total_blockref;
95 	uint64_t total_empty;
96 	uint64_t total_bytes;
97 	union {
98 		/* use volume or freemap depending on type value */
99 		struct {
100 			uint64_t total_inode;
101 			uint64_t total_indirect;
102 			uint64_t total_data;
103 			uint64_t total_dirent;
104 		} volume;
105 		struct {
106 			uint64_t total_freemap_node;
107 			uint64_t total_freemap_leaf;
108 		} freemap;
109 	};
110 } blockref_stats_t;
111 
112 typedef struct {
113 	uint64_t total_blockref;
114 	uint64_t total_empty;
115 	uint64_t total_bytes;
116 	struct {
117 		uint64_t total_inode;
118 		uint64_t total_indirect;
119 		uint64_t total_data;
120 		uint64_t total_dirent;
121 	} volume;
122 	struct {
123 		uint64_t total_freemap_node;
124 		uint64_t total_freemap_leaf;
125 	} freemap;
126 	long count;
127 } delta_stats_t;
128 
129 static void print_blockref_entry(struct blockref_tree *);
130 static void init_blockref_stats(blockref_stats_t *, uint8_t);
131 static void cleanup_blockref_stats(blockref_stats_t *);
132 static void init_delta_root(struct blockref_tree *);
133 static void cleanup_delta_root(struct blockref_tree *);
134 static void print_blockref_stats(const blockref_stats_t *, bool);
135 static int verify_volume_header(const hammer2_volume_data_t *);
136 static int read_media(const hammer2_blockref_t *, hammer2_media_data_t *,
137     size_t *);
138 static int verify_blockref(const hammer2_volume_data_t *,
139     const hammer2_blockref_t *, bool, blockref_stats_t *,
140     struct blockref_tree *, delta_stats_t *, int, int);
141 static void print_pfs(const hammer2_inode_data_t *);
142 static char *get_inode_filename(const hammer2_inode_data_t *);
143 static int init_pfs_blockref(const hammer2_volume_data_t *,
144     const hammer2_blockref_t *, struct blockref_list *);
145 static void cleanup_pfs_blockref(struct blockref_list *);
146 static void print_media(FILE *, int, const hammer2_blockref_t *,
147     const hammer2_media_data_t *, size_t);
148 
149 static int best_zone = -1;
150 
151 #define TAB 8
152 
153 static void
154 tfprintf(FILE *fp, int tab, const char *ctl, ...)
155 {
156 	va_list va;
157 	int ret;
158 
159 	ret = fprintf(fp, "%*s", tab * TAB, "");
160 	if (ret < 0)
161 		return;
162 
163 	va_start(va, ctl);
164 	vfprintf(fp, ctl, va);
165 	va_end(va);
166 }
167 
168 static void
169 tsnprintf(char *str, size_t siz, int tab, const char *ctl, ...)
170 {
171 	va_list va;
172 	int ret;
173 
174 	ret = snprintf(str, siz, "%*s", tab * TAB, "");
175 	if (ret < 0 || ret >= (int)siz)
176 		return;
177 
178 	va_start(va, ctl);
179 	vsnprintf(str + ret, siz - ret, ctl, va);
180 	va_end(va);
181 }
182 
183 static void
184 tprintf_zone(int tab, int i, const hammer2_blockref_t *bref)
185 {
186 	tfprintf(stdout, tab, "zone.%d %016jx%s\n",
187 	    i, (uintmax_t)bref->data_off,
188 	    (!ScanBest && i == best_zone) ? " (best)" : "");
189 }
190 
191 static int
192 init_root_blockref(int i, uint8_t type, hammer2_blockref_t *bref)
193 {
194 	hammer2_off_t off;
195 
196 	assert(type == HAMMER2_BREF_TYPE_EMPTY ||
197 		type == HAMMER2_BREF_TYPE_VOLUME ||
198 		type == HAMMER2_BREF_TYPE_FREEMAP);
199 	memset(bref, 0, sizeof(*bref));
200 	bref->type = type;
201 	bref->data_off = (i * HAMMER2_ZONE_BYTES64) | HAMMER2_PBUFRADIX;
202 	off = bref->data_off & ~HAMMER2_OFF_MASK_RADIX;
203 
204 	return lseek(hammer2_get_root_volume_fd(),
205 	    off - hammer2_get_root_volume_offset(), SEEK_SET);
206 }
207 
208 static int
209 find_best_zone(void)
210 {
211 	hammer2_blockref_t best;
212 	int i, best_i = -1;
213 
214 	memset(&best, 0, sizeof(best));
215 
216 	for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
217 		hammer2_volume_data_t voldata;
218 		hammer2_blockref_t broot;
219 		ssize_t ret;
220 
221 		if (i * HAMMER2_ZONE_BYTES64 >=
222 		    hammer2_get_root_volume_size())
223 			break;
224 		init_root_blockref(i, HAMMER2_BREF_TYPE_EMPTY, &broot);
225 		ret = read(hammer2_get_root_volume_fd(), &voldata,
226 		    HAMMER2_VOLUME_BYTES);
227 		if (ret == HAMMER2_VOLUME_BYTES) {
228 			if ((voldata.magic != HAMMER2_VOLUME_ID_HBO) &&
229 			    (voldata.magic != HAMMER2_VOLUME_ID_ABO))
230 				continue;
231 			broot.mirror_tid = voldata.mirror_tid;
232 			if (best_i < 0 || best.mirror_tid < broot.mirror_tid) {
233 				best_i = i;
234 				best = broot;
235 			}
236 		} else if (ret == -1) {
237 			perror("read");
238 			return -1;
239 		} else {
240 			tfprintf(stderr, 1, "Failed to read volume header\n");
241 			return -1;
242 		}
243 	}
244 
245 	return best_i;
246 }
247 
248 static int
249 test_volume_header(void)
250 {
251 	bool failed = false;
252 	int i;
253 
254 	for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
255 		hammer2_volume_data_t voldata;
256 		hammer2_blockref_t broot;
257 		ssize_t ret;
258 
259 		if (ScanBest && i != best_zone)
260 			continue;
261 		if (i * HAMMER2_ZONE_BYTES64 >=
262 		    hammer2_get_root_volume_size()) {
263 			tfprintf(stderr, 0, "zone.%d exceeds volume size\n", i);
264 			break;
265 		}
266 		init_root_blockref(i, HAMMER2_BREF_TYPE_EMPTY, &broot);
267 		ret = read(hammer2_get_root_volume_fd(), &voldata,
268 		    HAMMER2_VOLUME_BYTES);
269 		if (ret == HAMMER2_VOLUME_BYTES) {
270 			tprintf_zone(0, i, &broot);
271 			if (verify_volume_header(&voldata) == -1)
272 				failed = true;
273 		} else if (ret == -1) {
274 			perror("read");
275 			return -1;
276 		} else {
277 			tfprintf(stderr, 1, "Failed to read volume header\n");
278 			return -1;
279 		}
280 	}
281 
282 	return failed ? -1 : 0;
283 }
284 
285 static int
286 test_blockref(uint8_t type)
287 {
288 	struct blockref_tree droot;
289 	bool failed = false;
290 	int i;
291 
292 	init_delta_root(&droot);
293 	for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
294 		hammer2_volume_data_t voldata;
295 		hammer2_blockref_t broot;
296 		ssize_t ret;
297 
298 		if (ScanBest && i != best_zone)
299 			continue;
300 		if (i * HAMMER2_ZONE_BYTES64 >=
301 		    hammer2_get_root_volume_size()) {
302 			tfprintf(stderr, 0, "zone.%d exceeds volume size\n", i);
303 			break;
304 		}
305 		init_root_blockref(i, type, &broot);
306 		ret = read(hammer2_get_root_volume_fd(), &voldata,
307 		    HAMMER2_VOLUME_BYTES);
308 		if (ret == HAMMER2_VOLUME_BYTES) {
309 			blockref_stats_t bstats;
310 			init_blockref_stats(&bstats, type);
311 			delta_stats_t ds;
312 			memset(&ds, 0, sizeof(ds));
313 			tprintf_zone(0, i, &broot);
314 			if (verify_blockref(&voldata, &broot, false, &bstats,
315 			    &droot, &ds, 0, 0) == -1)
316 				failed = true;
317 			print_blockref_stats(&bstats, true);
318 			print_blockref_entry(&bstats.root);
319 			cleanup_blockref_stats(&bstats);
320 		} else if (ret == -1) {
321 			perror("read");
322 			failed = true;
323 			goto end;
324 		} else {
325 			tfprintf(stderr, 1, "Failed to read volume header\n");
326 			failed = true;
327 			goto end;
328 		}
329 	}
330 end:
331 	cleanup_delta_root(&droot);
332 	return failed ? -1 : 0;
333 }
334 
335 static int
336 test_pfs_blockref(void)
337 {
338 	struct blockref_tree droot;
339 	uint8_t type = HAMMER2_BREF_TYPE_VOLUME;
340 	bool failed = false;
341 	int i;
342 
343 	init_delta_root(&droot);
344 	for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
345 		hammer2_volume_data_t voldata;
346 		hammer2_blockref_t broot;
347 		ssize_t ret;
348 
349 		if (ScanBest && i != best_zone)
350 			continue;
351 		if (i * HAMMER2_ZONE_BYTES64 >=
352 		    hammer2_get_root_volume_size()) {
353 			tfprintf(stderr, 0, "zone.%d exceeds volume size\n", i);
354 			break;
355 		}
356 		init_root_blockref(i, type, &broot);
357 		ret = read(hammer2_get_root_volume_fd(), &voldata,
358 		    HAMMER2_VOLUME_BYTES);
359 		if (ret == HAMMER2_VOLUME_BYTES) {
360 			struct blockref_list blist;
361 			struct blockref_msg *p;
362 			int count = 0;
363 
364 			tprintf_zone(0, i, &broot);
365 			TAILQ_INIT(&blist);
366 			if (init_pfs_blockref(&voldata, &broot, &blist) == -1) {
367 				tfprintf(stderr, 1, "Failed to read PFS "
368 				    "blockref\n");
369 				failed = true;
370 				continue;
371 			}
372 			if (TAILQ_EMPTY(&blist)) {
373 				tfprintf(stderr, 1, "Failed to find PFS "
374 				    "blockref\n");
375 				failed = true;
376 				continue;
377 			}
378 			TAILQ_FOREACH(p, &blist, entry) {
379 				blockref_stats_t bstats;
380 				bool found = false;
381 				char *f = get_inode_filename(p->msg);
382 				if (NumPFSNames) {
383 					int j;
384 					for (j = 0; j < NumPFSNames; j++)
385 						if (!strcmp(PFSNames[j], f))
386 							found = true;
387 				} else
388 					found = true;
389 				if (!found) {
390 					free(f);
391 					continue;
392 				}
393 				count++;
394 				if (PrintPFS) {
395 					print_pfs(p->msg);
396 					free(f);
397 					continue;
398 				}
399 				tfprintf(stdout, 1, "%s\n", f);
400 				free(f);
401 				init_blockref_stats(&bstats, type);
402 				delta_stats_t ds;
403 				memset(&ds, 0, sizeof(ds));
404 				if (verify_blockref(&voldata, &p->bref, false,
405 				    &bstats, &droot, &ds, 0, 0) == -1)
406 					failed = true;
407 				print_blockref_stats(&bstats, true);
408 				print_blockref_entry(&bstats.root);
409 				cleanup_blockref_stats(&bstats);
410 			}
411 			cleanup_pfs_blockref(&blist);
412 			if (NumPFSNames && !count) {
413 				tfprintf(stderr, 1, "PFS not found\n");
414 				failed = true;
415 			}
416 		} else if (ret == -1) {
417 			perror("read");
418 			failed = true;
419 			goto end;
420 		} else {
421 			tfprintf(stderr, 1, "Failed to read volume header\n");
422 			failed = true;
423 			goto end;
424 		}
425 	}
426 end:
427 	cleanup_delta_root(&droot);
428 	return failed ? -1 : 0;
429 }
430 
431 static int
432 charsperline(void)
433 {
434 	int columns;
435 	char *cp;
436 	struct winsize ws;
437 
438 	columns = 0;
439 	if (ioctl(0, TIOCGWINSZ, &ws) != -1)
440 		columns = ws.ws_col;
441 	if (columns == 0 && (cp = getenv("COLUMNS")))
442 		columns = atoi(cp);
443 	if (columns == 0)
444 		columns = 80;	/* last resort */
445 
446 	return columns;
447 }
448 
449 static void
450 cleanup_blockref_msg(struct blockref_list *head)
451 {
452 	struct blockref_msg *p;
453 
454 	while ((p = TAILQ_FIRST(head)) != NULL) {
455 		TAILQ_REMOVE(head, p, entry);
456 		free(p->msg);
457 		free(p);
458 	}
459 	assert(TAILQ_EMPTY(head));
460 }
461 
462 static void
463 cleanup_blockref_entry(struct blockref_tree *root)
464 {
465 	struct blockref_entry *e;
466 
467 	while ((e = RB_ROOT(root)) != NULL) {
468 		RB_REMOVE(blockref_tree, root, e);
469 		cleanup_blockref_msg(&e->head);
470 		free(e);
471 	}
472 	assert(RB_EMPTY(root));
473 }
474 
475 static void
476 add_blockref_msg(struct blockref_list *head, const hammer2_blockref_t *bref,
477     const void *msg, size_t siz)
478 {
479 	struct blockref_msg *m;
480 	void *p;
481 
482 	m = calloc(1, sizeof(*m));
483 	assert(m);
484 	m->bref = *bref;
485 	p = calloc(1, siz);
486 	assert(p);
487 	memcpy(p, msg, siz);
488 	m->msg = p;
489 
490 	TAILQ_INSERT_TAIL(head, m, entry);
491 }
492 
493 static void
494 add_blockref_entry(struct blockref_tree *root, const hammer2_blockref_t *bref,
495     const void *msg, size_t siz)
496 {
497 	struct blockref_entry *e, bref_find;
498 
499 	memset(&bref_find, 0, sizeof(bref_find));
500 	bref_find.data_off = bref->data_off;
501 	e = RB_FIND(blockref_tree, root, &bref_find);
502 	if (!e) {
503 		e = calloc(1, sizeof(*e));
504 		assert(e);
505 		TAILQ_INIT(&e->head);
506 		e->data_off = bref->data_off;
507 	}
508 
509 	add_blockref_msg(&e->head, bref, msg, siz);
510 
511 	RB_INSERT(blockref_tree, root, e);
512 }
513 
514 static void
515 __print_blockref(FILE *fp, int tab, const hammer2_blockref_t *bref,
516     const char *msg)
517 {
518 	tfprintf(fp, tab, "%016jx %-12s %016jx/%-2d%s%s\n",
519 	    (uintmax_t)bref->data_off,
520 	    hammer2_breftype_to_str(bref->type),
521 	    (uintmax_t)bref->key,
522 	    bref->keybits,
523 	    msg ? " " : "",
524 	    msg ? msg : "");
525 }
526 
527 static void
528 print_blockref(FILE *fp, const hammer2_blockref_t *bref, const char *msg)
529 {
530 	__print_blockref(fp, 1, bref, msg);
531 }
532 
533 static void
534 print_blockref_debug(FILE *fp, int depth, int index,
535     const hammer2_blockref_t *bref, const char *msg)
536 {
537 	if (DebugOpt > 1) {
538 		char buf[256];
539 		int i;
540 
541 		memset(buf, 0, sizeof(buf));
542 		for (i = 0; i < depth * 2; i++)
543 			strlcat(buf, " ", sizeof(buf));
544 		tfprintf(fp, 1, buf);
545 		fprintf(fp, "%-2d %-3d ", depth, index);
546 		__print_blockref(fp, 0, bref, msg);
547 	} else if (DebugOpt > 0)
548 		print_blockref(fp, bref, msg);
549 }
550 
551 static void
552 print_blockref_msg(const struct blockref_list *head)
553 {
554 	struct blockref_msg *m;
555 
556 	TAILQ_FOREACH(m, head, entry) {
557 		hammer2_blockref_t *bref = &m->bref;
558 		print_blockref(stderr, bref, m->msg);
559 		if (VerboseOpt > 0) {
560 			hammer2_media_data_t media;
561 			size_t bytes;
562 			if (!read_media(bref, &media, &bytes))
563 				print_media(stderr, 2, bref, &media, bytes);
564 			else
565 				tfprintf(stderr, 2, "Failed to read media\n");
566 		}
567 	}
568 }
569 
570 static void
571 print_blockref_entry(struct blockref_tree *root)
572 {
573 	struct blockref_entry *e;
574 
575 	RB_FOREACH(e, blockref_tree, root)
576 		print_blockref_msg(&e->head);
577 }
578 
579 static void
580 init_blockref_stats(blockref_stats_t *bstats, uint8_t type)
581 {
582 	memset(bstats, 0, sizeof(*bstats));
583 	RB_INIT(&bstats->root);
584 	bstats->type = type;
585 }
586 
587 static void
588 cleanup_blockref_stats(blockref_stats_t *bstats)
589 {
590 	cleanup_blockref_entry(&bstats->root);
591 }
592 
593 static void
594 init_delta_root(struct blockref_tree *droot)
595 {
596 	RB_INIT(droot);
597 }
598 
599 static void
600 cleanup_delta_root(struct blockref_tree *droot)
601 {
602 	cleanup_blockref_entry(droot);
603 }
604 
605 static void
606 print_blockref_stats(const blockref_stats_t *bstats, bool newline)
607 {
608 	size_t siz = charsperline();
609 	char *buf = calloc(1, siz);
610 	char emptybuf[128];
611 
612 	assert(buf);
613 
614 	if (CountEmpty)
615 		snprintf(emptybuf, sizeof(emptybuf), ", %ju empty",
616 		    (uintmax_t)bstats->total_empty);
617 	else
618 		strlcpy(emptybuf, "", sizeof(emptybuf));
619 
620 	switch (bstats->type) {
621 	case HAMMER2_BREF_TYPE_VOLUME:
622 		tsnprintf(buf, siz, 1, "%ju blockref (%ju inode, %ju indirect, "
623 		    "%ju data, %ju dirent%s), %s",
624 		    (uintmax_t)bstats->total_blockref,
625 		    (uintmax_t)bstats->volume.total_inode,
626 		    (uintmax_t)bstats->volume.total_indirect,
627 		    (uintmax_t)bstats->volume.total_data,
628 		    (uintmax_t)bstats->volume.total_dirent,
629 		    emptybuf,
630 		    sizetostr(bstats->total_bytes));
631 		break;
632 	case HAMMER2_BREF_TYPE_FREEMAP:
633 		tsnprintf(buf, siz, 1, "%ju blockref (%ju node, %ju leaf%s), "
634 		    "%s",
635 		    (uintmax_t)bstats->total_blockref,
636 		    (uintmax_t)bstats->freemap.total_freemap_node,
637 		    (uintmax_t)bstats->freemap.total_freemap_leaf,
638 		    emptybuf,
639 		    sizetostr(bstats->total_bytes));
640 		break;
641 	default:
642 		assert(0);
643 		break;
644 	}
645 
646 	if (newline) {
647 		printf("%s\n", buf);
648 	} else {
649 		printf("%s\r", buf);
650 		fflush(stdout);
651 	}
652 	free(buf);
653 }
654 
655 static int
656 verify_volume_header(const hammer2_volume_data_t *voldata)
657 {
658 	hammer2_crc32_t crc0, crc1;
659 	const char *p = (const char*)voldata;
660 
661 	if ((voldata->magic != HAMMER2_VOLUME_ID_HBO) &&
662 	    (voldata->magic != HAMMER2_VOLUME_ID_ABO)) {
663 		tfprintf(stderr, 1, "Bad magic %jX\n", voldata->magic);
664 		return -1;
665 	}
666 
667 	if (voldata->magic == HAMMER2_VOLUME_ID_ABO)
668 		tfprintf(stderr, 1, "Reverse endian\n");
669 
670 	crc0 = voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT0];
671 	crc1 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRC0_OFF,
672 	    HAMMER2_VOLUME_ICRC0_SIZE);
673 	if (crc0 != crc1) {
674 		tfprintf(stderr, 1, "Bad HAMMER2_VOL_ICRC_SECT0 CRC\n");
675 		return -1;
676 	}
677 
678 	crc0 = voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT1];
679 	crc1 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRC1_OFF,
680 	    HAMMER2_VOLUME_ICRC1_SIZE);
681 	if (crc0 != crc1) {
682 		tfprintf(stderr, 1, "Bad HAMMER2_VOL_ICRC_SECT1 CRC\n");
683 		return -1;
684 	}
685 
686 	crc0 = voldata->icrc_volheader;
687 	crc1 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRCVH_OFF,
688 	    HAMMER2_VOLUME_ICRCVH_SIZE);
689 	if (crc0 != crc1) {
690 		tfprintf(stderr, 1, "Bad volume header CRC\n");
691 		return -1;
692 	}
693 
694 	return 0;
695 }
696 
697 static int
698 read_media(const hammer2_blockref_t *bref, hammer2_media_data_t *media,
699     size_t *media_bytes)
700 {
701 	hammer2_off_t io_off, io_base;
702 	size_t bytes, io_bytes, boff;
703 	int fd;
704 
705 	bytes = (bref->data_off & HAMMER2_OFF_MASK_RADIX);
706 	if (bytes)
707 		bytes = (size_t)1 << bytes;
708 	if (media_bytes)
709 		*media_bytes = bytes;
710 
711 	if (!bytes)
712 		return 0;
713 
714 	io_off = bref->data_off & ~HAMMER2_OFF_MASK_RADIX;
715 	io_base = io_off & ~(hammer2_off_t)(HAMMER2_LBUFSIZE - 1);
716 	boff = io_off - io_base;
717 
718 	io_bytes = HAMMER2_LBUFSIZE;
719 	while (io_bytes + boff < bytes)
720 		io_bytes <<= 1;
721 
722 	if (io_bytes > sizeof(*media))
723 		return -1;
724 	fd = hammer2_get_volume_fd(io_off);
725 	if (lseek(fd, io_base - hammer2_get_volume_offset(io_base), SEEK_SET)
726 	    == -1)
727 		return -2;
728 	if (read(fd, media, io_bytes) != (ssize_t)io_bytes)
729 		return -2;
730 	if (boff)
731 		memmove(media, (char *)media + boff, bytes);
732 
733 	return 0;
734 }
735 
736 static void
737 load_delta_stats(blockref_stats_t *bstats, const delta_stats_t *dstats)
738 {
739 	bstats->total_blockref += dstats->total_blockref;
740 	bstats->total_empty += dstats->total_empty;
741 	bstats->total_bytes += dstats->total_bytes;
742 
743 	switch (bstats->type) {
744 	case HAMMER2_BREF_TYPE_VOLUME:
745 		bstats->volume.total_inode += dstats->volume.total_inode;
746 		bstats->volume.total_indirect += dstats->volume.total_indirect;
747 		bstats->volume.total_data += dstats->volume.total_data;
748 		bstats->volume.total_dirent += dstats->volume.total_dirent;
749 		break;
750 	case HAMMER2_BREF_TYPE_FREEMAP:
751 		bstats->freemap.total_freemap_node +=
752 		    dstats->freemap.total_freemap_node;
753 		bstats->freemap.total_freemap_leaf +=
754 		    dstats->freemap.total_freemap_leaf;
755 		break;
756 	default:
757 		assert(0);
758 		break;
759 	}
760 }
761 
762 static void
763 accumulate_delta_stats(delta_stats_t *dst, const delta_stats_t *src)
764 {
765 	dst->total_blockref += src->total_blockref;
766 	dst->total_empty += src->total_empty;
767 	dst->total_bytes += src->total_bytes;
768 
769 	dst->volume.total_inode += src->volume.total_inode;
770 	dst->volume.total_indirect += src->volume.total_indirect;
771 	dst->volume.total_data += src->volume.total_data;
772 	dst->volume.total_dirent += src->volume.total_dirent;
773 
774 	dst->freemap.total_freemap_node += src->freemap.total_freemap_node;
775 	dst->freemap.total_freemap_leaf += src->freemap.total_freemap_leaf;
776 
777 	dst->count += src->count;
778 }
779 
780 static int
781 verify_blockref(const hammer2_volume_data_t *voldata,
782     const hammer2_blockref_t *bref, bool norecurse, blockref_stats_t *bstats,
783     struct blockref_tree *droot, delta_stats_t *dstats, int depth, int index)
784 {
785 	hammer2_media_data_t media;
786 	hammer2_blockref_t *bscan;
787 	int i, bcount;
788 	bool failed = false;
789 	size_t bytes;
790 	uint32_t cv;
791 	uint64_t cv64;
792 	char msg[256];
793 #ifdef HAMMER2_USE_OPENSSL
794 	SHA256_CTX hash_ctx;
795 	union {
796 		uint8_t digest[SHA256_DIGEST_LENGTH];
797 		uint64_t digest64[SHA256_DIGEST_LENGTH/8];
798 	} u;
799 #endif
800 	/* only for DebugOpt > 1 */
801 	if (DebugOpt > 1)
802 		print_blockref_debug(stdout, depth, index, bref, NULL);
803 
804 	if (bref->data_off) {
805 		struct blockref_entry *e, bref_find;
806 		memset(&bref_find, 0, sizeof(bref_find));
807 		bref_find.data_off = bref->data_off;
808 		e = RB_FIND(blockref_tree, droot, &bref_find);
809 		if (e) {
810 			struct blockref_msg *m;
811 			TAILQ_FOREACH(m, &e->head, entry) {
812 				delta_stats_t *ds = m->msg;
813 				if (!memcmp(&m->bref, bref, sizeof(*bref))) {
814 					/* delta contains cached delta */
815 					accumulate_delta_stats(dstats, ds);
816 					load_delta_stats(bstats, ds);
817 					print_blockref_debug(stdout, depth,
818 					    index, &m->bref, "cache-hit");
819 					return 0;
820 				}
821 			}
822 		}
823 	}
824 
825 	bstats->total_blockref++;
826 	dstats->total_blockref++;
827 
828 	switch (bref->type) {
829 	case HAMMER2_BREF_TYPE_EMPTY:
830 		if (CountEmpty) {
831 			bstats->total_empty++;
832 			dstats->total_empty++;
833 		} else {
834 			bstats->total_blockref--;
835 			dstats->total_blockref--;
836 		}
837 		break;
838 	case HAMMER2_BREF_TYPE_INODE:
839 		bstats->volume.total_inode++;
840 		dstats->volume.total_inode++;
841 		break;
842 	case HAMMER2_BREF_TYPE_INDIRECT:
843 		bstats->volume.total_indirect++;
844 		dstats->volume.total_indirect++;
845 		break;
846 	case HAMMER2_BREF_TYPE_DATA:
847 		bstats->volume.total_data++;
848 		dstats->volume.total_data++;
849 		break;
850 	case HAMMER2_BREF_TYPE_DIRENT:
851 		bstats->volume.total_dirent++;
852 		dstats->volume.total_dirent++;
853 		break;
854 	case HAMMER2_BREF_TYPE_FREEMAP_NODE:
855 		bstats->freemap.total_freemap_node++;
856 		dstats->freemap.total_freemap_node++;
857 		break;
858 	case HAMMER2_BREF_TYPE_FREEMAP_LEAF:
859 		bstats->freemap.total_freemap_leaf++;
860 		dstats->freemap.total_freemap_leaf++;
861 		break;
862 	case HAMMER2_BREF_TYPE_VOLUME:
863 		bstats->total_blockref--;
864 		dstats->total_blockref--;
865 		break;
866 	case HAMMER2_BREF_TYPE_FREEMAP:
867 		bstats->total_blockref--;
868 		dstats->total_blockref--;
869 		break;
870 	default:
871 		snprintf(msg, sizeof(msg), "Invalid blockref type %d",
872 		    bref->type);
873 		add_blockref_entry(&bstats->root, bref, msg, strlen(msg) + 1);
874 		print_blockref_debug(stdout, depth, index, bref, msg);
875 		failed = true;
876 		break;
877 	}
878 
879 	switch (read_media(bref, &media, &bytes)) {
880 	case -1:
881 		strlcpy(msg, "Bad I/O bytes", sizeof(msg));
882 		add_blockref_entry(&bstats->root, bref, msg, strlen(msg) + 1);
883 		print_blockref_debug(stdout, depth, index, bref, msg);
884 		return -1;
885 	case -2:
886 		strlcpy(msg, "Failed to read media", sizeof(msg));
887 		add_blockref_entry(&bstats->root, bref, msg, strlen(msg) + 1);
888 		print_blockref_debug(stdout, depth, index, bref, msg);
889 		return -1;
890 	default:
891 		break;
892 	}
893 
894 	if (bref->type != HAMMER2_BREF_TYPE_VOLUME &&
895 	    bref->type != HAMMER2_BREF_TYPE_FREEMAP) {
896 		bstats->total_bytes += bytes;
897 		dstats->total_bytes += bytes;
898 	}
899 
900 	if (!CountEmpty && bref->type == HAMMER2_BREF_TYPE_EMPTY) {
901 		assert(bytes == 0);
902 		bstats->total_bytes -= bytes;
903 		dstats->total_bytes -= bytes;
904 	}
905 
906 	if (!DebugOpt && QuietOpt <= 0 && (bstats->total_blockref % 100) == 0)
907 		print_blockref_stats(bstats, false);
908 
909 	if (!bytes)
910 		goto end;
911 
912 	switch (HAMMER2_DEC_CHECK(bref->methods)) {
913 	case HAMMER2_CHECK_ISCSI32:
914 		cv = hammer2_icrc32(&media, bytes);
915 		if (bref->check.iscsi32.value != cv) {
916 			strlcpy(msg, "Bad HAMMER2_CHECK_ISCSI32", sizeof(msg));
917 			add_blockref_entry(&bstats->root, bref, msg,
918 			    strlen(msg) + 1);
919 			print_blockref_debug(stdout, depth, index, bref, msg);
920 			failed = true;
921 		}
922 		break;
923 	case HAMMER2_CHECK_XXHASH64:
924 		cv64 = XXH64(&media, bytes, XXH_HAMMER2_SEED);
925 		if (bref->check.xxhash64.value != cv64) {
926 			strlcpy(msg, "Bad HAMMER2_CHECK_XXHASH64", sizeof(msg));
927 			add_blockref_entry(&bstats->root, bref, msg,
928 			    strlen(msg) + 1);
929 			print_blockref_debug(stdout, depth, index, bref, msg);
930 			failed = true;
931 		}
932 		break;
933 	case HAMMER2_CHECK_SHA192:
934 #ifdef HAMMER2_USE_OPENSSL
935 		SHA256_Init(&hash_ctx);
936 		SHA256_Update(&hash_ctx, &media, bytes);
937 		SHA256_Final(u.digest, &hash_ctx);
938 		u.digest64[2] ^= u.digest64[3];
939 		if (memcmp(u.digest, bref->check.sha192.data,
940 		    sizeof(bref->check.sha192.data))) {
941 			strlcpy(msg, "Bad HAMMER2_CHECK_SHA192", sizeof(msg));
942 			add_blockref_entry(&bstats->root, bref, msg,
943 			    strlen(msg) + 1);
944 			print_blockref_debug(stdout, depth, index, bref, msg);
945 			failed = true;
946 		}
947 #endif
948 		break;
949 	case HAMMER2_CHECK_FREEMAP:
950 		cv = hammer2_icrc32(&media, bytes);
951 		if (bref->check.freemap.icrc32 != cv) {
952 			strlcpy(msg, "Bad HAMMER2_CHECK_FREEMAP", sizeof(msg));
953 			add_blockref_entry(&bstats->root, bref, msg,
954 			    strlen(msg) + 1);
955 			print_blockref_debug(stdout, depth, index, bref, msg);
956 			failed = true;
957 		}
958 		break;
959 	}
960 
961 	switch (bref->type) {
962 	case HAMMER2_BREF_TYPE_INODE:
963 		if (!(media.ipdata.meta.op_flags & HAMMER2_OPFLAG_DIRECTDATA)) {
964 			bscan = &media.ipdata.u.blockset.blockref[0];
965 			bcount = HAMMER2_SET_COUNT;
966 		} else {
967 			bscan = NULL;
968 			bcount = 0;
969 		}
970 		break;
971 	case HAMMER2_BREF_TYPE_INDIRECT:
972 		bscan = &media.npdata[0];
973 		bcount = bytes / sizeof(hammer2_blockref_t);
974 		break;
975 	case HAMMER2_BREF_TYPE_FREEMAP_NODE:
976 		bscan = &media.npdata[0];
977 		bcount = bytes / sizeof(hammer2_blockref_t);
978 		break;
979 	case HAMMER2_BREF_TYPE_VOLUME:
980 		bscan = &media.voldata.sroot_blockset.blockref[0];
981 		bcount = HAMMER2_SET_COUNT;
982 		break;
983 	case HAMMER2_BREF_TYPE_FREEMAP:
984 		bscan = &media.voldata.freemap_blockset.blockref[0];
985 		bcount = HAMMER2_SET_COUNT;
986 		break;
987 	default:
988 		bscan = NULL;
989 		bcount = 0;
990 		break;
991 	}
992 
993 	if (ForceOpt)
994 		norecurse = false;
995 	/*
996 	 * If failed, no recurse, but still verify its direct children.
997 	 * Beyond that is probably garbage.
998 	 */
999 	for (i = 0; norecurse == false && i < bcount; ++i) {
1000 		delta_stats_t ds;
1001 		memset(&ds, 0, sizeof(ds));
1002 		if (verify_blockref(voldata, &bscan[i], failed, bstats, droot,
1003 		    &ds, depth + 1, i) == -1)
1004 			return -1;
1005 		if (!failed)
1006 			accumulate_delta_stats(dstats, &ds);
1007 	}
1008 end:
1009 	if (failed)
1010 		return -1;
1011 
1012 	dstats->count++;
1013 	if (bref->data_off && BlockrefCacheCount > 0 &&
1014 	    dstats->count >= BlockrefCacheCount) {
1015 		assert(bytes);
1016 		add_blockref_entry(droot, bref, dstats, sizeof(*dstats));
1017 		print_blockref_debug(stdout, depth, index, bref, "cache-add");
1018 	}
1019 
1020 	return 0;
1021 }
1022 
1023 static void
1024 print_pfs(const hammer2_inode_data_t *ipdata)
1025 {
1026 	const hammer2_inode_meta_t *meta = &ipdata->meta;
1027 	char *f, *pfs_id_str = NULL;
1028 	const char *type_str;
1029 	uuid_t uuid;
1030 
1031 	f = get_inode_filename(ipdata);
1032 	uuid = meta->pfs_clid;
1033 	hammer2_uuid_to_str(&uuid, &pfs_id_str);
1034 	if (meta->pfs_type == HAMMER2_PFSTYPE_MASTER) {
1035 		if (meta->pfs_subtype == HAMMER2_PFSSUBTYPE_NONE)
1036 			type_str = "MASTER";
1037 		else
1038 			type_str = hammer2_pfssubtype_to_str(meta->pfs_subtype);
1039 	} else {
1040 		type_str = hammer2_pfstype_to_str(meta->pfs_type);
1041 	}
1042 	tfprintf(stdout, 1, "%-11s %s %s\n", type_str, pfs_id_str, f);
1043 
1044 	free(f);
1045 	free(pfs_id_str);
1046 }
1047 
1048 static char*
1049 get_inode_filename(const hammer2_inode_data_t *ipdata)
1050 {
1051 	char *p = malloc(HAMMER2_INODE_MAXNAME + 1);
1052 
1053 	memcpy(p, ipdata->filename, sizeof(ipdata->filename));
1054 	p[HAMMER2_INODE_MAXNAME] = '\0';
1055 
1056 	return p;
1057 }
1058 
1059 static void
1060 __add_pfs_blockref(const hammer2_blockref_t *bref, struct blockref_list *blist,
1061     const hammer2_inode_data_t *ipdata)
1062 {
1063 	struct blockref_msg *newp, *p;
1064 
1065 	newp = calloc(1, sizeof(*newp));
1066 	newp->bref = *bref;
1067 	newp->msg = calloc(1, sizeof(*ipdata));
1068 	memcpy(newp->msg, ipdata, sizeof(*ipdata));
1069 
1070 	p = TAILQ_FIRST(blist);
1071 	while (p) {
1072 		char *f1 = get_inode_filename(newp->msg);
1073 		char *f2 = get_inode_filename(p->msg);
1074 		if (strcmp(f1, f2) <= 0) {
1075 			TAILQ_INSERT_BEFORE(p, newp, entry);
1076 			free(f1);
1077 			free(f2);
1078 			break;
1079 		}
1080 		p = TAILQ_NEXT(p, entry);
1081 		free(f1);
1082 		free(f2);
1083 	}
1084 	if (!p)
1085 		TAILQ_INSERT_TAIL(blist, newp, entry);
1086 }
1087 
1088 static int
1089 init_pfs_blockref(const hammer2_volume_data_t *voldata,
1090     const hammer2_blockref_t *bref, struct blockref_list *blist)
1091 {
1092 	hammer2_media_data_t media;
1093 	hammer2_inode_data_t ipdata;
1094 	hammer2_blockref_t *bscan;
1095 	int i, bcount;
1096 	size_t bytes;
1097 
1098 	if (read_media(bref, &media, &bytes))
1099 		return -1;
1100 	if (!bytes)
1101 		return 0;
1102 
1103 	switch (bref->type) {
1104 	case HAMMER2_BREF_TYPE_INODE:
1105 		ipdata = media.ipdata;
1106 		if (ipdata.meta.pfs_type == HAMMER2_PFSTYPE_SUPROOT) {
1107 			bscan = &ipdata.u.blockset.blockref[0];
1108 			bcount = HAMMER2_SET_COUNT;
1109 		} else {
1110 			bscan = NULL;
1111 			bcount = 0;
1112 			if (ipdata.meta.op_flags & HAMMER2_OPFLAG_PFSROOT)
1113 				__add_pfs_blockref(bref, blist, &ipdata);
1114 			else
1115 				assert(0); /* should only see SUPROOT or PFS */
1116 		}
1117 		break;
1118 	case HAMMER2_BREF_TYPE_INDIRECT:
1119 		bscan = &media.npdata[0];
1120 		bcount = bytes / sizeof(hammer2_blockref_t);
1121 		break;
1122 	case HAMMER2_BREF_TYPE_VOLUME:
1123 		bscan = &media.voldata.sroot_blockset.blockref[0];
1124 		bcount = HAMMER2_SET_COUNT;
1125 		break;
1126 	default:
1127 		bscan = NULL;
1128 		bcount = 0;
1129 		break;
1130 	}
1131 
1132 	for (i = 0; i < bcount; ++i)
1133 		if (init_pfs_blockref(voldata, &bscan[i], blist) == -1)
1134 			return -1;
1135 	return 0;
1136 }
1137 
1138 static void
1139 cleanup_pfs_blockref(struct blockref_list *blist)
1140 {
1141 	cleanup_blockref_msg(blist);
1142 }
1143 
1144 static void
1145 print_media(FILE *fp, int tab, const hammer2_blockref_t *bref,
1146     const hammer2_media_data_t *media, size_t media_bytes)
1147 {
1148 	const hammer2_blockref_t *bscan;
1149 	const hammer2_inode_data_t *ipdata;
1150 	int i, bcount, namelen;
1151 	char *str = NULL;
1152 	uuid_t uuid;
1153 
1154 	switch (bref->type) {
1155 	case HAMMER2_BREF_TYPE_INODE:
1156 		ipdata = &media->ipdata;
1157 		namelen = ipdata->meta.name_len;
1158 		if (namelen > HAMMER2_INODE_MAXNAME)
1159 			namelen = 0;
1160 		tfprintf(fp, tab, "filename \"%*.*s\"\n", namelen, namelen,
1161 		    ipdata->filename);
1162 		tfprintf(fp, tab, "version %d\n", ipdata->meta.version);
1163 		if ((ipdata->meta.op_flags & HAMMER2_OPFLAG_PFSROOT) ||
1164 		    ipdata->meta.pfs_type == HAMMER2_PFSTYPE_SUPROOT)
1165 			tfprintf(fp, tab, "pfs_subtype %d (%s)\n",
1166 			    ipdata->meta.pfs_subtype,
1167 			    hammer2_pfssubtype_to_str(ipdata->meta.pfs_subtype));
1168 		tfprintf(fp, tab, "uflags 0x%08x\n", ipdata->meta.uflags);
1169 		if (ipdata->meta.rmajor || ipdata->meta.rminor) {
1170 			tfprintf(fp, tab, "rmajor %d\n", ipdata->meta.rmajor);
1171 			tfprintf(fp, tab, "rminor %d\n", ipdata->meta.rminor);
1172 		}
1173 		tfprintf(fp, tab, "ctime %s\n",
1174 		    hammer2_time64_to_str(ipdata->meta.ctime, &str));
1175 		tfprintf(fp, tab, "mtime %s\n",
1176 		    hammer2_time64_to_str(ipdata->meta.mtime, &str));
1177 		tfprintf(fp, tab, "atime %s\n",
1178 		    hammer2_time64_to_str(ipdata->meta.atime, &str));
1179 		tfprintf(fp, tab, "btime %s\n",
1180 		    hammer2_time64_to_str(ipdata->meta.btime, &str));
1181 		uuid = ipdata->meta.uid;
1182 		tfprintf(fp, tab, "uid %s\n", hammer2_uuid_to_str(&uuid, &str));
1183 		uuid = ipdata->meta.gid;
1184 		tfprintf(fp, tab, "gid %s\n", hammer2_uuid_to_str(&uuid, &str));
1185 		tfprintf(fp, tab, "type %s\n",
1186 		    hammer2_iptype_to_str(ipdata->meta.type));
1187 		tfprintf(fp, tab, "op_flags 0x%02x\n", ipdata->meta.op_flags);
1188 		tfprintf(fp, tab, "cap_flags 0x%04x\n", ipdata->meta.cap_flags);
1189 		tfprintf(fp, tab, "mode %-7o\n", ipdata->meta.mode);
1190 		tfprintf(fp, tab, "inum 0x%016jx\n", ipdata->meta.inum);
1191 		tfprintf(fp, tab, "size %ju ", (uintmax_t)ipdata->meta.size);
1192 		if (ipdata->meta.op_flags & HAMMER2_OPFLAG_DIRECTDATA &&
1193 		    ipdata->meta.size <= HAMMER2_EMBEDDED_BYTES)
1194 			printf("(embedded data)\n");
1195 		else
1196 			printf("\n");
1197 		tfprintf(fp, tab, "nlinks %ju\n",
1198 		    (uintmax_t)ipdata->meta.nlinks);
1199 		tfprintf(fp, tab, "iparent 0x%016jx\n",
1200 		    (uintmax_t)ipdata->meta.iparent);
1201 		tfprintf(fp, tab, "name_key 0x%016jx\n",
1202 		    (uintmax_t)ipdata->meta.name_key);
1203 		tfprintf(fp, tab, "name_len %u\n", ipdata->meta.name_len);
1204 		tfprintf(fp, tab, "ncopies %u\n", ipdata->meta.ncopies);
1205 		tfprintf(fp, tab, "comp_algo %u\n", ipdata->meta.comp_algo);
1206 		tfprintf(fp, tab, "target_type %u\n", ipdata->meta.target_type);
1207 		tfprintf(fp, tab, "check_algo %u\n", ipdata->meta.check_algo);
1208 		if ((ipdata->meta.op_flags & HAMMER2_OPFLAG_PFSROOT) ||
1209 		    ipdata->meta.pfs_type == HAMMER2_PFSTYPE_SUPROOT) {
1210 			tfprintf(fp, tab, "pfs_nmasters %u\n",
1211 			    ipdata->meta.pfs_nmasters);
1212 			tfprintf(fp, tab, "pfs_type %u (%s)\n",
1213 			    ipdata->meta.pfs_type,
1214 			    hammer2_pfstype_to_str(ipdata->meta.pfs_type));
1215 			tfprintf(fp, tab, "pfs_inum 0x%016jx\n",
1216 			    (uintmax_t)ipdata->meta.pfs_inum);
1217 			uuid = ipdata->meta.pfs_clid;
1218 			tfprintf(fp, tab, "pfs_clid %s\n",
1219 			    hammer2_uuid_to_str(&uuid, &str));
1220 			uuid = ipdata->meta.pfs_fsid;
1221 			tfprintf(fp, tab, "pfs_fsid %s\n",
1222 			    hammer2_uuid_to_str(&uuid, &str));
1223 			tfprintf(fp, tab, "pfs_lsnap_tid 0x%016jx\n",
1224 			    (uintmax_t)ipdata->meta.pfs_lsnap_tid);
1225 		}
1226 		tfprintf(fp, tab, "data_quota %ju\n",
1227 		    (uintmax_t)ipdata->meta.data_quota);
1228 		tfprintf(fp, tab, "data_count %ju\n",
1229 		    (uintmax_t)bref->embed.stats.data_count);
1230 		tfprintf(fp, tab, "inode_quota %ju\n",
1231 		    (uintmax_t)ipdata->meta.inode_quota);
1232 		tfprintf(fp, tab, "inode_count %ju\n",
1233 		    (uintmax_t)bref->embed.stats.inode_count);
1234 		break;
1235 	case HAMMER2_BREF_TYPE_INDIRECT:
1236 		bcount = media_bytes / sizeof(hammer2_blockref_t);
1237 		for (i = 0; i < bcount; ++i) {
1238 			bscan = &media->npdata[i];
1239 			tfprintf(fp, tab, "%3d %016jx %-12s %016jx/%-2d\n",
1240 			    i, (uintmax_t)bscan->data_off,
1241 			    hammer2_breftype_to_str(bscan->type),
1242 			    (uintmax_t)bscan->key,
1243 			    bscan->keybits);
1244 		}
1245 		break;
1246 	case HAMMER2_BREF_TYPE_DIRENT:
1247 		if (bref->embed.dirent.namlen <= sizeof(bref->check.buf)) {
1248 			tfprintf(fp, tab, "filename \"%*.*s\"\n",
1249 			    bref->embed.dirent.namlen,
1250 			    bref->embed.dirent.namlen,
1251 			    bref->check.buf);
1252 		} else {
1253 			tfprintf(fp, tab, "filename \"%*.*s\"\n",
1254 			    bref->embed.dirent.namlen,
1255 			    bref->embed.dirent.namlen,
1256 			    media->buf);
1257 		}
1258 		tfprintf(fp, tab, "inum 0x%016jx\n",
1259 		    (uintmax_t)bref->embed.dirent.inum);
1260 		tfprintf(fp, tab, "namlen %d\n",
1261 		    (uintmax_t)bref->embed.dirent.namlen);
1262 		tfprintf(fp, tab, "type %s\n",
1263 		    hammer2_iptype_to_str(bref->embed.dirent.type));
1264 		break;
1265 	case HAMMER2_BREF_TYPE_FREEMAP_NODE:
1266 		bcount = media_bytes / sizeof(hammer2_blockref_t);
1267 		for (i = 0; i < bcount; ++i) {
1268 			bscan = &media->npdata[i];
1269 			tfprintf(fp, tab, "%3d %016jx %-12s %016jx/%-2d\n",
1270 			    i, (uintmax_t)bscan->data_off,
1271 			    hammer2_breftype_to_str(bscan->type),
1272 			    (uintmax_t)bscan->key,
1273 			    bscan->keybits);
1274 		}
1275 		break;
1276 	case HAMMER2_BREF_TYPE_FREEMAP_LEAF:
1277 		for (i = 0; i < HAMMER2_FREEMAP_COUNT; ++i) {
1278 			hammer2_off_t data_off = bref->key +
1279 				i * HAMMER2_FREEMAP_LEVEL0_SIZE;
1280 #if HAMMER2_BMAP_ELEMENTS != 8
1281 #error "HAMMER2_BMAP_ELEMENTS != 8"
1282 #endif
1283 			tfprintf(fp, tab, "%016jx %04d.%04x (avail=%7d) "
1284 			    "%016jx %016jx %016jx %016jx "
1285 			    "%016jx %016jx %016jx %016jx\n",
1286 			    data_off, i, media->bmdata[i].class,
1287 			    media->bmdata[i].avail,
1288 			    media->bmdata[i].bitmapq[0],
1289 			    media->bmdata[i].bitmapq[1],
1290 			    media->bmdata[i].bitmapq[2],
1291 			    media->bmdata[i].bitmapq[3],
1292 			    media->bmdata[i].bitmapq[4],
1293 			    media->bmdata[i].bitmapq[5],
1294 			    media->bmdata[i].bitmapq[6],
1295 			    media->bmdata[i].bitmapq[7]);
1296 		}
1297 		break;
1298 	default:
1299 		break;
1300 	}
1301 	if (str)
1302 		free(str);
1303 }
1304 
1305 int
1306 test_hammer2(const char *devpath)
1307 {
1308 	bool failed = false;
1309 
1310 	hammer2_init_volumes(devpath, 1);
1311 
1312 	best_zone = find_best_zone();
1313 	if (best_zone == -1)
1314 		fprintf(stderr, "Failed to find best zone\n");
1315 
1316 	if (PrintPFS) {
1317 		if (test_pfs_blockref() == -1)
1318 			failed = true;
1319 		goto end; /* print PFS info and exit */
1320 	}
1321 
1322 	printf("volume header\n");
1323 	if (test_volume_header() == -1) {
1324 		failed = true;
1325 		if (!ForceOpt)
1326 			goto end;
1327 	}
1328 
1329 	printf("freemap\n");
1330 	if (test_blockref(HAMMER2_BREF_TYPE_FREEMAP) == -1) {
1331 		failed = true;
1332 		if (!ForceOpt)
1333 			goto end;
1334 	}
1335 	printf("volume\n");
1336 	if (!ScanPFS) {
1337 		if (test_blockref(HAMMER2_BREF_TYPE_VOLUME) == -1) {
1338 			failed = true;
1339 			if (!ForceOpt)
1340 				goto end;
1341 		}
1342 	} else {
1343 		if (test_pfs_blockref() == -1) {
1344 			failed = true;
1345 			if (!ForceOpt)
1346 				goto end;
1347 		}
1348 	}
1349 end:
1350 	hammer2_cleanup_volumes();
1351 
1352 	return failed ? -1 : 0;
1353 }
1354