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