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