sbin/fsck_hammer2: Fix return value on -f
[dragonfly.git] / sbin / fsck_hammer2 / test.c
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 <unistd.h>
43 #include <fcntl.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <stdarg.h>
47 #include <stdbool.h>
48 #include <string.h>
49 #include <assert.h>
50
51 #include <openssl/sha.h>
52
53 #include <vfs/hammer2/hammer2_disk.h>
54 #include <vfs/hammer2/hammer2_xxhash.h>
55
56 #include "hammer2_subs.h"
57 #include "fsck_hammer2.h"
58
59 struct blockref_msg {
60         TAILQ_ENTRY(blockref_msg) entry;
61         hammer2_blockref_t bref;
62         char *msg;
63 };
64
65 TAILQ_HEAD(blockref_list, blockref_msg);
66
67 struct blockref_entry {
68         RB_ENTRY(blockref_entry) entry;
69         hammer2_off_t data_off;
70         struct blockref_list head;
71 };
72
73 static int
74 blockref_cmp(struct blockref_entry *b1, struct blockref_entry *b2)
75 {
76         if (b1->data_off < b2->data_off)
77                 return -1;
78         if (b1->data_off > b2->data_off)
79                 return 1;
80         return 0;
81 }
82
83 RB_HEAD(blockref_tree, blockref_entry);
84 RB_PROTOTYPE2(blockref_tree, blockref_entry, entry, blockref_cmp,
85     hammer2_off_t);
86 RB_GENERATE2(blockref_tree, blockref_entry, entry, blockref_cmp, hammer2_off_t,
87     data_off);
88
89 typedef struct {
90         struct blockref_tree root;
91         uint8_t type; /* HAMMER2_BREF_TYPE_VOLUME or FREEMAP */
92         uint64_t total_blockref;
93         uint64_t total_empty;
94         uint64_t total_invalid;
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 static void init_blockref_stats(blockref_stats_t *, uint8_t);
112 static void cleanup_blockref_stats(blockref_stats_t *);
113 static void print_blockref_stats(const blockref_stats_t *, bool);
114 static int verify_volume_header(const hammer2_volume_data_t *);
115 static int verify_blockref(int, const hammer2_volume_data_t *,
116     const hammer2_blockref_t *, bool, blockref_stats_t *);
117 static int init_pfs_blockref(int, const hammer2_volume_data_t *,
118     const hammer2_blockref_t *, struct blockref_list *);
119 static void cleanup_pfs_blockref(struct blockref_list *);
120
121 static int best_zone = -1;
122
123 #define TAB 8
124
125 static void
126 tfprintf(FILE *fp, int tab, const char *ctl, ...)
127 {
128         va_list va;
129
130         tab *= TAB;
131         fprintf(fp, "%*s", tab, "");
132         //fflush(fp);
133
134         va_start(va, ctl);
135         vfprintf(fp, ctl, va);
136         va_end(va);
137 }
138
139 static int
140 find_best_zone(int fd)
141 {
142         hammer2_blockref_t best;
143         int i, best_i = -1;
144
145         memset(&best, 0, sizeof(best));
146
147         for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
148                 hammer2_volume_data_t voldata;
149                 hammer2_blockref_t broot;
150                 ssize_t ret;
151
152                 memset(&broot, 0, sizeof(broot));
153                 broot.data_off = (i * HAMMER2_ZONE_BYTES64) | HAMMER2_PBUFRADIX;
154                 lseek(fd, broot.data_off & ~HAMMER2_OFF_MASK_RADIX, SEEK_SET);
155
156                 ret = read(fd, &voldata, HAMMER2_PBUFSIZE);
157                 if (ret == HAMMER2_PBUFSIZE) {
158                         if ((voldata.magic != HAMMER2_VOLUME_ID_HBO) &&
159                             (voldata.magic != HAMMER2_VOLUME_ID_ABO))
160                                 continue;
161                         broot.mirror_tid = voldata.mirror_tid;
162                         if (best_i < 0 || best.mirror_tid < broot.mirror_tid) {
163                                 best_i = i;
164                                 best = broot;
165                         }
166                 } else if (ret == -1) {
167                         perror("read");
168                         return -1;
169                 } else {
170                         tfprintf(stderr, 1, "Failed to read volume header\n");
171                         return -1;
172                 }
173         }
174
175         return best_i;
176 }
177
178 static int
179 test_volume_header(int fd)
180 {
181         bool failed = false;
182         int i;
183
184         for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
185                 hammer2_volume_data_t voldata;
186                 hammer2_blockref_t broot;
187                 ssize_t ret;
188
189                 memset(&broot, 0, sizeof(broot));
190                 broot.data_off = (i * HAMMER2_ZONE_BYTES64) | HAMMER2_PBUFRADIX;
191                 lseek(fd, broot.data_off & ~HAMMER2_OFF_MASK_RADIX, SEEK_SET);
192
193                 ret = read(fd, &voldata, HAMMER2_PBUFSIZE);
194                 if (ret == HAMMER2_PBUFSIZE) {
195                         broot.mirror_tid = voldata.mirror_tid;
196
197                         printf("zone.%d %016jx%s\n",
198                             i, (uintmax_t)broot.data_off,
199                             (i == best_zone) ? " (best)" : "");
200                         if (verify_volume_header(&voldata) == -1)
201                                 failed = true;
202                 } else if (ret == -1) {
203                         perror("read");
204                         return -1;
205                 } else {
206                         tfprintf(stderr, 1, "Failed to read volume header\n");
207                         return -1;
208                 }
209         }
210
211         return failed ? -1 : 0;
212 }
213
214 static int
215 test_blockref(int fd, uint8_t type)
216 {
217         bool failed = false;
218         int i;
219
220         for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
221                 hammer2_volume_data_t voldata;
222                 hammer2_blockref_t broot;
223                 ssize_t ret;
224
225                 memset(&broot, 0, sizeof(broot));
226                 broot.type = type;
227                 broot.data_off = (i * HAMMER2_ZONE_BYTES64) | HAMMER2_PBUFRADIX;
228                 lseek(fd, broot.data_off & ~HAMMER2_OFF_MASK_RADIX, SEEK_SET);
229
230                 ret = read(fd, &voldata, HAMMER2_PBUFSIZE);
231                 if (ret == HAMMER2_PBUFSIZE) {
232                         blockref_stats_t bstats;
233                         struct blockref_entry *e;
234                         broot.mirror_tid = voldata.mirror_tid;
235                         init_blockref_stats(&bstats, type);
236
237                         printf("zone.%d %016jx%s\n",
238                             i, (uintmax_t)broot.data_off,
239                             (i == best_zone) ? " (best)" : "");
240                         if (verify_blockref(fd, &voldata, &broot, false,
241                             &bstats) == -1)
242                                 failed = true;
243                         print_blockref_stats(&bstats, true);
244
245                         RB_FOREACH(e, blockref_tree, &bstats.root) {
246                                 struct blockref_msg *m;
247                                 TAILQ_FOREACH(m, &e->head, entry) {
248                                         tfprintf(stderr, 1, "%016jx %3d "
249                                             "%016jx/%-2d \"%s\"\n",
250                                             (uintmax_t)e->data_off,
251                                             m->bref.type,
252                                             (uintmax_t)m->bref.key,
253                                             m->bref.keybits,
254                                             m->msg);
255                                 }
256                         }
257                         cleanup_blockref_stats(&bstats);
258                 } else if (ret == -1) {
259                         perror("read");
260                         return -1;
261                 } else {
262                         tfprintf(stderr, 1, "Failed to read volume header\n");
263                         return -1;
264                 }
265         }
266
267         return failed ? -1 : 0;
268 }
269
270 static int
271 test_pfs_blockref(int fd, const char *name)
272 {
273         uint8_t type = HAMMER2_BREF_TYPE_VOLUME;
274         bool failed = false;
275         int i;
276
277         for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
278                 hammer2_volume_data_t voldata;
279                 hammer2_blockref_t broot;
280                 ssize_t ret;
281
282                 memset(&broot, 0, sizeof(broot));
283                 broot.type = type;
284                 broot.data_off = (i * HAMMER2_ZONE_BYTES64) | HAMMER2_PBUFRADIX;
285                 lseek(fd, broot.data_off & ~HAMMER2_OFF_MASK_RADIX, SEEK_SET);
286
287                 ret = read(fd, &voldata, HAMMER2_PBUFSIZE);
288                 if (ret == HAMMER2_PBUFSIZE) {
289                         struct blockref_list blist;
290                         struct blockref_msg *p;
291                         int count = 0;
292                         broot.mirror_tid = voldata.mirror_tid;
293
294                         printf("zone.%d %016jx%s\n",
295                             i, (uintmax_t)broot.data_off,
296                             (i == best_zone) ? " (best)" : "");
297
298                         TAILQ_INIT(&blist);
299                         if (init_pfs_blockref(fd, &voldata, &broot, &blist) ==
300                             -1) {
301                                 tfprintf(stderr, 1, "Failed to read PFS "
302                                     "blockref\n");
303                                 failed = true;
304                                 continue;
305                         }
306                         if (TAILQ_EMPTY(&blist)) {
307                                 tfprintf(stderr, 1, "Failed to find PFS "
308                                     "blockref\n");
309                                 failed = true;
310                                 continue;
311                         }
312
313                         TAILQ_FOREACH(p, &blist, entry) {
314                                 blockref_stats_t bstats;
315                                 struct blockref_entry *e;
316                                 if (name && strcmp(name, p->msg))
317                                         continue;
318                                 count++;
319                                 tfprintf(stdout, 1, "%s\n", p->msg);
320                                 init_blockref_stats(&bstats, type);
321                                 if (verify_blockref(fd, &voldata, &p->bref,
322                                     false, &bstats) == -1)
323                                         failed = true;
324                                 print_blockref_stats(&bstats, true);
325
326                                 RB_FOREACH(e, blockref_tree, &bstats.root) {
327                                         struct blockref_msg *m;
328                                         TAILQ_FOREACH(m, &e->head, entry) {
329                                                 tfprintf(stderr, 1, "%016jx %3d "
330                                                     "%016jx/%-2d \"%s\"\n",
331                                                     (uintmax_t)e->data_off,
332                                                     m->bref.type,
333                                                     (uintmax_t)m->bref.key,
334                                                     m->bref.keybits,
335                                                     m->msg);
336                                         }
337                                 }
338                                 cleanup_blockref_stats(&bstats);
339                         }
340                         cleanup_pfs_blockref(&blist);
341                         if (name && !count) {
342                                 tfprintf(stderr, 1, "PFS \"%s\" not found\n",
343                                     name);
344                                 failed = true;
345                         }
346                 } else if (ret == -1) {
347                         perror("read");
348                         return -1;
349                 } else {
350                         tfprintf(stderr, 1, "Failed to read volume header\n");
351                         return -1;
352                 }
353         }
354
355         return failed ? -1 : 0;
356 }
357
358 static int
359 charsperline(void)
360 {
361         int columns;
362         char *cp;
363         struct winsize ws;
364
365         columns = 0;
366         if (ioctl(0, TIOCGWINSZ, &ws) != -1)
367                 columns = ws.ws_col;
368         if (columns == 0 && (cp = getenv("COLUMNS")))
369                 columns = atoi(cp);
370         if (columns == 0)
371                 columns = 80;   /* last resort */
372
373         return columns;
374 }
375
376 static void
377 add_blockref_entry(blockref_stats_t *bstats, const hammer2_blockref_t *bref,
378     const char *msg)
379 {
380         struct blockref_entry *e;
381         struct blockref_msg *m;
382
383         e = RB_LOOKUP(blockref_tree, &bstats->root, bref->data_off);
384         if (!e) {
385                 e = calloc(1, sizeof(*e));
386                 assert(e);
387                 TAILQ_INIT(&e->head);
388         }
389
390         m = calloc(1, sizeof(*m));
391         assert(m);
392         m->bref = *bref;
393         m->msg = strdup(msg);
394
395         e->data_off = bref->data_off;
396         TAILQ_INSERT_TAIL(&e->head, m, entry);
397         RB_INSERT(blockref_tree, &bstats->root, e);
398 }
399
400 static void
401 init_blockref_stats(blockref_stats_t *bstats, uint8_t type)
402 {
403         memset(bstats, 0, sizeof(*bstats));
404         bstats->type = type;
405         RB_INIT(&bstats->root);
406 }
407
408 static void
409 cleanup_blockref_stats(blockref_stats_t *bstats)
410 {
411         struct blockref_entry *e;
412
413         while ((e = RB_ROOT(&bstats->root)) != NULL) {
414                 struct blockref_msg *m;
415                 RB_REMOVE(blockref_tree, &bstats->root, e);
416                 while ((m = TAILQ_FIRST(&e->head)) != NULL) {
417                         TAILQ_REMOVE(&e->head, m, entry);
418                         free(m->msg);
419                         free(m);
420                 }
421                 assert(TAILQ_EMPTY(&e->head));
422                 free(e);
423         }
424         assert(RB_EMPTY(&bstats->root));
425 }
426
427 static void
428 print_blockref_stats(const blockref_stats_t *bstats, bool newline)
429 {
430         size_t siz = charsperline();
431         char *buf = calloc(1, siz);
432
433         assert(buf);
434
435         switch (bstats->type) {
436         case HAMMER2_BREF_TYPE_VOLUME:
437                 snprintf(buf, siz, "%*s%ju blockref (%ju inode, %ju indirect, "
438                     "%ju data, %ju dirent, %ju empty), %s",
439                     TAB, "",
440                     (uintmax_t)bstats->total_blockref,
441                     (uintmax_t)bstats->volume.total_inode,
442                     (uintmax_t)bstats->volume.total_indirect,
443                     (uintmax_t)bstats->volume.total_data,
444                     (uintmax_t)bstats->volume.total_dirent,
445                     (uintmax_t)bstats->total_empty,
446                     sizetostr(bstats->total_bytes));
447                 break;
448         case HAMMER2_BREF_TYPE_FREEMAP:
449                 snprintf(buf, siz, "%*s%ju blockref (%ju node, %ju leaf, "
450                     "%ju empty), %s",
451                     TAB, "",
452                     (uintmax_t)bstats->total_blockref,
453                     (uintmax_t)bstats->freemap.total_freemap_node,
454                     (uintmax_t)bstats->freemap.total_freemap_leaf,
455                     (uintmax_t)bstats->total_empty,
456                     sizetostr(bstats->total_bytes));
457                 break;
458         default:
459                 assert(0);
460                 break;
461         }
462
463         if (newline) {
464                 printf("%s\n", buf);
465         } else {
466                 printf("%s\r", buf);
467                 fflush(stdout);
468         }
469         free(buf);
470 }
471
472 static int
473 verify_volume_header(const hammer2_volume_data_t *voldata)
474 {
475         hammer2_crc32_t crc0, crc, bcrc0, bcrc;
476         const char *p = (const char*)voldata;
477
478         if ((voldata->magic != HAMMER2_VOLUME_ID_HBO) &&
479             (voldata->magic != HAMMER2_VOLUME_ID_ABO)) {
480                 tfprintf(stderr, 1, "Bad magic %jX\n", voldata->magic);
481                 return -1;
482         }
483
484         if (voldata->magic == HAMMER2_VOLUME_ID_ABO)
485                 tfprintf(stderr, 1, "Reverse endian\n");
486
487         crc = voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT0];
488         crc0 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRC0_OFF,
489             HAMMER2_VOLUME_ICRC0_SIZE);
490         if (crc0 != crc) {
491                 tfprintf(stderr, 1, "Bad HAMMER2_VOL_ICRC_SECT0 CRC\n");
492                 return -1;
493         }
494
495         bcrc = voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT1];
496         bcrc0 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRC1_OFF,
497             HAMMER2_VOLUME_ICRC1_SIZE);
498         if (bcrc0 != bcrc) {
499                 tfprintf(stderr, 1, "Bad HAMMER2_VOL_ICRC_SECT1 CRC\n");
500                 return -1;
501         }
502
503         return 0;
504 }
505
506 static int
507 read_media(int fd, const hammer2_blockref_t *bref, hammer2_media_data_t *media,
508     size_t *media_bytes)
509 {
510         hammer2_off_t io_off, io_base;
511         size_t bytes, io_bytes, boff;
512
513         bytes = (bref->data_off & HAMMER2_OFF_MASK_RADIX);
514         if (bytes)
515                 bytes = (size_t)1 << bytes;
516         *media_bytes = bytes;
517
518         if (bytes == 0)
519                 return 0;
520
521         io_off = bref->data_off & ~HAMMER2_OFF_MASK_RADIX;
522         io_base = io_off & ~(hammer2_off_t)(HAMMER2_MINIOSIZE - 1);
523         boff = io_off - io_base;
524
525         io_bytes = HAMMER2_MINIOSIZE;
526         while (io_bytes + boff < bytes)
527                 io_bytes <<= 1;
528
529         if (io_bytes > sizeof(*media))
530                 return -1;
531         lseek(fd, io_base, SEEK_SET);
532         if (read(fd, media, io_bytes) != (ssize_t)io_bytes)
533                 return -2;
534         if (boff)
535                 memcpy(media, (char *)media + boff, bytes);
536
537         return 0;
538 }
539
540 static int
541 verify_blockref(int fd, const hammer2_volume_data_t *voldata,
542     const hammer2_blockref_t *bref, bool norecurse, blockref_stats_t *bstats)
543 {
544         hammer2_media_data_t media;
545         hammer2_blockref_t *bscan;
546         int i, bcount;
547         bool failed = false;
548         size_t bytes;
549         uint32_t cv;
550         uint64_t cv64;
551         char msg[256];
552
553         SHA256_CTX hash_ctx;
554         union {
555                 uint8_t digest[SHA256_DIGEST_LENGTH];
556                 uint64_t digest64[SHA256_DIGEST_LENGTH/8];
557         } u;
558
559         bstats->total_blockref++;
560
561         switch (bref->type) {
562         case HAMMER2_BREF_TYPE_EMPTY:
563                 bstats->total_empty++;
564                 break;
565         case HAMMER2_BREF_TYPE_INODE:
566                 bstats->volume.total_inode++;
567                 break;
568         case HAMMER2_BREF_TYPE_INDIRECT:
569                 bstats->volume.total_indirect++;
570                 break;
571         case HAMMER2_BREF_TYPE_DATA:
572                 bstats->volume.total_data++;
573                 break;
574         case HAMMER2_BREF_TYPE_DIRENT:
575                 bstats->volume.total_dirent++;
576                 break;
577         case HAMMER2_BREF_TYPE_FREEMAP_NODE:
578                 bstats->freemap.total_freemap_node++;
579                 break;
580         case HAMMER2_BREF_TYPE_FREEMAP_LEAF:
581                 bstats->freemap.total_freemap_leaf++;
582                 break;
583         case HAMMER2_BREF_TYPE_VOLUME:
584                 bstats->total_blockref--;
585                 break;
586         case HAMMER2_BREF_TYPE_FREEMAP:
587                 bstats->total_blockref--;
588                 break;
589         default:
590                 bstats->total_invalid++;
591                 snprintf(msg, sizeof(msg),
592                     "Invalid blockref type %d", bref->type);
593                 add_blockref_entry(bstats, bref, msg);
594                 failed = true;
595                 break;
596         }
597
598         switch (read_media(fd, bref, &media, &bytes)) {
599         case -1:
600                 add_blockref_entry(bstats, bref, "Bad I/O bytes");
601                 return -1;
602         case -2:
603                 add_blockref_entry(bstats, bref, "Failed to read media");
604                 return -1;
605         default:
606                 break;
607         }
608
609         if (bref->type != HAMMER2_BREF_TYPE_VOLUME &&
610             bref->type != HAMMER2_BREF_TYPE_FREEMAP)
611                 bstats->total_bytes += bytes;
612
613         if ((bstats->total_blockref % 100) == 0)
614                 print_blockref_stats(bstats, false);
615
616         if (bytes) {
617                 switch (HAMMER2_DEC_CHECK(bref->methods)) {
618                 case HAMMER2_CHECK_ISCSI32:
619                         cv = hammer2_icrc32(&media, bytes);
620                         if (bref->check.iscsi32.value != cv) {
621                                 add_blockref_entry(bstats, bref,
622                                     "Bad HAMMER2_CHECK_ISCSI32");
623                                 failed = true;
624                         }
625                         break;
626                 case HAMMER2_CHECK_XXHASH64:
627                         cv64 = XXH64(&media, bytes, XXH_HAMMER2_SEED);
628                         if (bref->check.xxhash64.value != cv64) {
629                                 add_blockref_entry(bstats, bref,
630                                     "Bad HAMMER2_CHECK_XXHASH64");
631                                 failed = true;
632                         }
633                         break;
634                 case HAMMER2_CHECK_SHA192:
635                         SHA256_Init(&hash_ctx);
636                         SHA256_Update(&hash_ctx, &media, bytes);
637                         SHA256_Final(u.digest, &hash_ctx);
638                         u.digest64[2] ^= u.digest64[3];
639                         if (memcmp(u.digest, bref->check.sha192.data,
640                             sizeof(bref->check.sha192.data))) {
641                                 add_blockref_entry(bstats, bref,
642                                     "Bad HAMMER2_CHECK_SHA192");
643                                 failed = true;
644                         }
645                         break;
646                 case HAMMER2_CHECK_FREEMAP:
647                         cv = hammer2_icrc32(&media, bytes);
648                         if (bref->check.freemap.icrc32 != cv) {
649                                 add_blockref_entry(bstats, bref,
650                                     "Bad HAMMER2_CHECK_FREEMAP");
651                                 failed = true;
652                         }
653                         break;
654                 }
655         }
656
657         switch (bref->type) {
658         case HAMMER2_BREF_TYPE_INODE:
659                 if (!(media.ipdata.meta.op_flags & HAMMER2_OPFLAG_DIRECTDATA)) {
660                         bscan = &media.ipdata.u.blockset.blockref[0];
661                         bcount = HAMMER2_SET_COUNT;
662                 } else {
663                         bscan = NULL;
664                         bcount = 0;
665                 }
666                 break;
667         case HAMMER2_BREF_TYPE_INDIRECT:
668                 bscan = &media.npdata[0];
669                 bcount = bytes / sizeof(hammer2_blockref_t);
670                 break;
671         case HAMMER2_BREF_TYPE_FREEMAP_NODE:
672                 bscan = &media.npdata[0];
673                 bcount = bytes / sizeof(hammer2_blockref_t);
674                 break;
675         case HAMMER2_BREF_TYPE_VOLUME:
676                 bscan = &media.voldata.sroot_blockset.blockref[0];
677                 bcount = HAMMER2_SET_COUNT;
678                 break;
679         case HAMMER2_BREF_TYPE_FREEMAP:
680                 bscan = &media.voldata.freemap_blockset.blockref[0];
681                 bcount = HAMMER2_SET_COUNT;
682                 break;
683         default:
684                 bscan = NULL;
685                 bcount = 0;
686                 break;
687         }
688
689         if (ForceOpt)
690                 norecurse = 0;
691         /*
692          * If failed, no recurse, but still verify its direct children.
693          * Beyond that is probably garbage.
694          */
695         for (i = 0; norecurse == 0 && i < bcount; ++i)
696                 if (bscan[i].type != HAMMER2_BREF_TYPE_EMPTY)
697                         if (verify_blockref(fd, voldata, &bscan[i], failed,
698                             bstats) == -1)
699                                 return -1;
700         return failed ? -1 : 0;
701 }
702
703 static int
704 init_pfs_blockref(int fd, const hammer2_volume_data_t *voldata,
705     const hammer2_blockref_t *bref, struct blockref_list *blist)
706 {
707         hammer2_media_data_t media;
708         hammer2_inode_data_t ipdata;
709         hammer2_blockref_t *bscan;
710         int i, bcount;
711         size_t bytes;
712
713         if (read_media(fd, bref, &media, &bytes))
714                 return -1;
715         if (!bytes)
716                 return 0;
717
718         switch (bref->type) {
719         case HAMMER2_BREF_TYPE_INODE:
720                 ipdata = media.ipdata;
721                 if (ipdata.meta.pfs_type & HAMMER2_PFSTYPE_SUPROOT) {
722                         bscan = &ipdata.u.blockset.blockref[0];
723                         bcount = HAMMER2_SET_COUNT;
724                 } else {
725                         bscan = NULL;
726                         bcount = 0;
727                         if (ipdata.meta.op_flags & HAMMER2_OPFLAG_PFSROOT) {
728                                 struct blockref_msg *p;
729                                 p = calloc(1, sizeof(*p));
730                                 assert(p);
731                                 p->bref = *bref;
732                                 p->msg = strdup(ipdata.filename);
733                                 TAILQ_INSERT_TAIL(blist, p, entry);
734                         } else
735                                 assert(0); /* should only see SUPROOT or PFS */
736                 }
737                 break;
738         case HAMMER2_BREF_TYPE_INDIRECT:
739                 bscan = &media.npdata[0];
740                 bcount = bytes / sizeof(hammer2_blockref_t);
741                 break;
742         case HAMMER2_BREF_TYPE_VOLUME:
743                 bscan = &media.voldata.sroot_blockset.blockref[0];
744                 bcount = HAMMER2_SET_COUNT;
745                 break;
746         default:
747                 bscan = NULL;
748                 bcount = 0;
749                 break;
750         }
751
752         for (i = 0; i < bcount; ++i)
753                 if (bscan[i].type != HAMMER2_BREF_TYPE_EMPTY)
754                         if (init_pfs_blockref(fd, voldata, &bscan[i], blist)
755                             == -1)
756                                 return -1;
757         return 0;
758 }
759
760 static void
761 cleanup_pfs_blockref(struct blockref_list *blist)
762 {
763         struct blockref_msg *p;
764
765         while ((p = TAILQ_FIRST(blist)) != NULL) {
766                 TAILQ_REMOVE(blist, p, entry);
767                 free(p->msg);
768                 free(p);
769         }
770         assert(TAILQ_EMPTY(blist));
771 }
772
773 int
774 test_hammer2(const char *devpath)
775 {
776         struct stat st;
777         bool failed = false;
778         int fd;
779
780         fd = open(devpath, O_RDONLY);
781         if (fd == -1) {
782                 perror("open");
783                 return -1;
784         }
785
786         if (fstat(fd, &st) == -1) {
787                 perror("fstat");
788                 failed = true;
789                 goto end;
790         }
791         if (!S_ISCHR(st.st_mode)) {
792                 fprintf(stderr, "%s is not a block device\n", devpath);
793                 failed = true;
794                 goto end;
795         }
796
797         best_zone = find_best_zone(fd);
798         if (best_zone == -1)
799                 fprintf(stderr, "Failed to find best zone\n");
800
801         printf("volume header\n");
802         if (test_volume_header(fd) == -1) {
803                 failed = true;
804                 if (!ForceOpt)
805                         goto end;
806         }
807
808         printf("freemap\n");
809         if (test_blockref(fd, HAMMER2_BREF_TYPE_FREEMAP) == -1) {
810                 failed = true;
811                 if (!ForceOpt)
812                         goto end;
813         }
814         printf("volume\n");
815         if (!ScanPFS) {
816                 if (test_blockref(fd, HAMMER2_BREF_TYPE_VOLUME) == -1) {
817                         failed = true;
818                         if (!ForceOpt)
819                                 goto end;
820                 }
821         } else {
822                 if (test_pfs_blockref(fd, PFSName) == -1) {
823                         failed = true;
824                         if (!ForceOpt)
825                                 goto end;
826                 }
827         }
828 end:
829         close(fd);
830
831         return failed ? -1 : 0;
832 }