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