sbin/fsck_hammer2: Adjust indentations for error messages
[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 <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 #include <openssl/sha.h>
53
54 #include <vfs/hammer2/hammer2_disk.h>
55 #include <vfs/hammer2/hammer2_xxhash.h>
56
57 #include "hammer2_subs.h"
58 #include "fsck_hammer2.h"
59
60 struct blockref_msg {
61         TAILQ_ENTRY(blockref_msg) entry;
62         hammer2_blockref_t bref;
63         void *msg;
64 };
65
66 TAILQ_HEAD(blockref_list, blockref_msg);
67
68 struct blockref_entry {
69         RB_ENTRY(blockref_entry) entry;
70         hammer2_off_t data_off;
71         struct blockref_list head;
72 };
73
74 static int
75 blockref_cmp(struct blockref_entry *b1, struct blockref_entry *b2)
76 {
77         if (b1->data_off < b2->data_off)
78                 return -1;
79         if (b1->data_off > b2->data_off)
80                 return 1;
81         return 0;
82 }
83
84 RB_HEAD(blockref_tree, blockref_entry);
85 RB_PROTOTYPE2(blockref_tree, blockref_entry, entry, blockref_cmp,
86     hammer2_off_t);
87 RB_GENERATE2(blockref_tree, blockref_entry, entry, blockref_cmp, hammer2_off_t,
88     data_off);
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, 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;
479
480         e = RB_LOOKUP(blockref_tree, root, bref->data_off);
481         if (!e) {
482                 e = calloc(1, sizeof(*e));
483                 assert(e);
484                 TAILQ_INIT(&e->head);
485                 e->data_off = bref->data_off;
486         }
487
488         add_blockref_msg(&e->head, bref, msg, siz);
489
490         RB_INSERT(blockref_tree, root, e);
491 }
492
493 static __inline void
494 __print_blockref(FILE *fp, int tab, const hammer2_blockref_t *bref,
495     const char *msg)
496 {
497         tfprintf(fp, tab, "%016jx %-12s %016jx/%-2d%s%s\n",
498             (uintmax_t)bref->data_off,
499             hammer2_breftype_to_str(bref->type),
500             (uintmax_t)bref->key,
501             bref->keybits,
502             msg ? " " : "",
503             msg ? msg : "");
504 }
505
506 static void
507 print_blockref(FILE *fp, const hammer2_blockref_t *bref, const char *msg)
508 {
509         __print_blockref(fp, 1, bref, msg);
510 }
511
512 static void
513 print_blockref_verbose(FILE *fp, int depth, int index,
514     const hammer2_blockref_t *bref, const char *msg)
515 {
516         if (DebugOpt > 1) {
517                 char buf[256];
518                 int i;
519
520                 memset(buf, 0, sizeof(buf));
521                 for (i = 0; i < depth * 2; i++)
522                         strlcat(buf, " ", sizeof(buf));
523                 tfprintf(fp, 1, buf);
524                 fprintf(fp, "%-2d %-3d ", depth, index);
525                 __print_blockref(fp, 0, bref, msg);
526         } else
527                 print_blockref(fp, bref, msg);
528 }
529
530 static void
531 print_blockref_msg(int fd, struct blockref_list *head)
532 {
533         struct blockref_msg *m;
534
535         TAILQ_FOREACH(m, head, entry) {
536                 hammer2_blockref_t *bref = &m->bref;
537                 print_blockref(stderr, bref, m->msg);
538                 if (fd != -1 && VerboseOpt > 0) {
539                         hammer2_media_data_t media;
540                         size_t bytes;
541                         if (!read_media(fd, bref, &media, &bytes))
542                                 print_media(stderr, 2, bref, &media, bytes);
543                         else
544                                 tfprintf(stderr, 2, "Failed to read media\n");
545                 }
546         }
547 }
548
549 static void
550 print_blockref_entry(int fd, struct blockref_tree *root)
551 {
552         struct blockref_entry *e;
553
554         RB_FOREACH(e, blockref_tree, root)
555                 print_blockref_msg(fd, &e->head);
556 }
557
558 static void
559 init_blockref_stats(blockref_stats_t *bstats, uint8_t type)
560 {
561         memset(bstats, 0, sizeof(*bstats));
562         RB_INIT(&bstats->root);
563         bstats->type = type;
564 }
565
566 static void
567 cleanup_blockref_stats(blockref_stats_t *bstats)
568 {
569         cleanup_blockref_entry(&bstats->root);
570 }
571
572 static void
573 init_delta_root(struct blockref_tree *droot)
574 {
575         RB_INIT(droot);
576 }
577
578 static void
579 cleanup_delta_root(struct blockref_tree *droot)
580 {
581         cleanup_blockref_entry(droot);
582 }
583
584 static void
585 print_blockref_stats(const blockref_stats_t *bstats, bool newline)
586 {
587         size_t siz = charsperline();
588         char *buf = calloc(1, siz);
589         char emptybuf[128];
590
591         assert(buf);
592
593         if (CountEmpty)
594                 snprintf(emptybuf, sizeof(emptybuf), ", %ju empty",
595                     (uintmax_t)bstats->total_empty);
596         else
597                 strlcpy(emptybuf, "", sizeof(emptybuf));
598
599         switch (bstats->type) {
600         case HAMMER2_BREF_TYPE_VOLUME:
601                 tsnprintf(buf, siz, 1, "%ju blockref (%ju inode, %ju indirect, "
602                     "%ju data, %ju dirent%s), %s",
603                     (uintmax_t)bstats->total_blockref,
604                     (uintmax_t)bstats->volume.total_inode,
605                     (uintmax_t)bstats->volume.total_indirect,
606                     (uintmax_t)bstats->volume.total_data,
607                     (uintmax_t)bstats->volume.total_dirent,
608                     emptybuf,
609                     sizetostr(bstats->total_bytes));
610                 break;
611         case HAMMER2_BREF_TYPE_FREEMAP:
612                 tsnprintf(buf, siz, 1, "%ju blockref (%ju node, %ju leaf%s), "
613                     "%s",
614                     (uintmax_t)bstats->total_blockref,
615                     (uintmax_t)bstats->freemap.total_freemap_node,
616                     (uintmax_t)bstats->freemap.total_freemap_leaf,
617                     emptybuf,
618                     sizetostr(bstats->total_bytes));
619                 break;
620         default:
621                 assert(0);
622                 break;
623         }
624
625         if (newline) {
626                 printf("%s\n", buf);
627         } else {
628                 printf("%s\r", buf);
629                 fflush(stdout);
630         }
631         free(buf);
632 }
633
634 static int
635 verify_volume_header(const hammer2_volume_data_t *voldata)
636 {
637         hammer2_crc32_t crc0, crc1;
638         const char *p = (const char*)voldata;
639
640         if ((voldata->magic != HAMMER2_VOLUME_ID_HBO) &&
641             (voldata->magic != HAMMER2_VOLUME_ID_ABO)) {
642                 tfprintf(stderr, 1, "Bad magic %jX\n", voldata->magic);
643                 return -1;
644         }
645
646         if (voldata->magic == HAMMER2_VOLUME_ID_ABO)
647                 tfprintf(stderr, 1, "Reverse endian\n");
648
649         crc0 = voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT0];
650         crc1 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRC0_OFF,
651             HAMMER2_VOLUME_ICRC0_SIZE);
652         if (crc0 != crc1) {
653                 tfprintf(stderr, 1, "Bad HAMMER2_VOL_ICRC_SECT0 CRC\n");
654                 return -1;
655         }
656
657         crc0 = voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT1];
658         crc1 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRC1_OFF,
659             HAMMER2_VOLUME_ICRC1_SIZE);
660         if (crc0 != crc1) {
661                 tfprintf(stderr, 1, "Bad HAMMER2_VOL_ICRC_SECT1 CRC\n");
662                 return -1;
663         }
664
665         crc0 = voldata->icrc_volheader;
666         crc1 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRCVH_OFF,
667             HAMMER2_VOLUME_ICRCVH_SIZE);
668         if (crc0 != crc1) {
669                 tfprintf(stderr, 1, "Bad volume header CRC\n");
670                 return -1;
671         }
672
673         return 0;
674 }
675
676 static int
677 read_media(int fd, const hammer2_blockref_t *bref, hammer2_media_data_t *media,
678     size_t *media_bytes)
679 {
680         hammer2_off_t io_off, io_base;
681         size_t bytes, io_bytes, boff;
682
683         bytes = (bref->data_off & HAMMER2_OFF_MASK_RADIX);
684         if (bytes)
685                 bytes = (size_t)1 << bytes;
686         if (media_bytes)
687                 *media_bytes = bytes;
688
689         if (!bytes)
690                 return 0;
691
692         io_off = bref->data_off & ~HAMMER2_OFF_MASK_RADIX;
693         io_base = io_off & ~(hammer2_off_t)(HAMMER2_MINIOSIZE - 1);
694         boff = io_off - io_base;
695
696         io_bytes = HAMMER2_MINIOSIZE;
697         while (io_bytes + boff < bytes)
698                 io_bytes <<= 1;
699
700         if (io_bytes > sizeof(*media))
701                 return -1;
702         if (lseek(fd, io_base, SEEK_SET) == -1)
703                 return -2;
704         if (read(fd, media, io_bytes) != (ssize_t)io_bytes)
705                 return -2;
706         if (boff)
707                 memmove(media, (char *)media + boff, bytes);
708
709         return 0;
710 }
711
712 static void
713 load_delta_stats(blockref_stats_t *bstats, const delta_stats_t *dstats)
714 {
715         bstats->total_blockref += dstats->total_blockref;
716         bstats->total_empty += dstats->total_empty;
717         bstats->total_bytes += dstats->total_bytes;
718
719         switch (bstats->type) {
720         case HAMMER2_BREF_TYPE_VOLUME:
721                 bstats->volume.total_inode += dstats->volume.total_inode;
722                 bstats->volume.total_indirect += dstats->volume.total_indirect;
723                 bstats->volume.total_data += dstats->volume.total_data;
724                 bstats->volume.total_dirent += dstats->volume.total_dirent;
725                 break;
726         case HAMMER2_BREF_TYPE_FREEMAP:
727                 bstats->freemap.total_freemap_node +=
728                     dstats->freemap.total_freemap_node;
729                 bstats->freemap.total_freemap_leaf +=
730                     dstats->freemap.total_freemap_leaf;
731                 break;
732         default:
733                 assert(0);
734                 break;
735         }
736 }
737
738 static void
739 accumulate_delta_stats(delta_stats_t *dst, const delta_stats_t *src)
740 {
741         dst->total_blockref += src->total_blockref;
742         dst->total_empty += src->total_empty;
743         dst->total_bytes += src->total_bytes;
744
745         dst->volume.total_inode += src->volume.total_inode;
746         dst->volume.total_indirect += src->volume.total_indirect;
747         dst->volume.total_data += src->volume.total_data;
748         dst->volume.total_dirent += src->volume.total_dirent;
749
750         dst->freemap.total_freemap_node += src->freemap.total_freemap_node;
751         dst->freemap.total_freemap_leaf += src->freemap.total_freemap_leaf;
752
753         dst->count += src->count;
754 }
755
756 static int
757 verify_blockref(int fd, const hammer2_volume_data_t *voldata,
758     const hammer2_blockref_t *bref, bool norecurse, blockref_stats_t *bstats,
759     struct blockref_tree *droot, delta_stats_t *dstats, int depth, int index)
760 {
761         hammer2_media_data_t media;
762         hammer2_blockref_t *bscan;
763         int i, bcount;
764         bool failed = false;
765         size_t bytes;
766         uint32_t cv;
767         uint64_t cv64;
768         char msg[256];
769
770         SHA256_CTX hash_ctx;
771         union {
772                 uint8_t digest[SHA256_DIGEST_LENGTH];
773                 uint64_t digest64[SHA256_DIGEST_LENGTH/8];
774         } u;
775
776         if (DebugOpt > 1)
777                 print_blockref_verbose(stdout, depth, index, bref, NULL);
778
779         if (bref->data_off) {
780                 struct blockref_entry *e;
781                 e = RB_LOOKUP(blockref_tree, droot, bref->data_off);
782                 if (e) {
783                         struct blockref_msg *m;
784                         TAILQ_FOREACH(m, &e->head, entry) {
785                                 delta_stats_t *ds = m->msg;
786                                 if (!memcmp(&m->bref, bref, sizeof(*bref))) {
787                                         /* delta contains cached delta */
788                                         accumulate_delta_stats(dstats, ds);
789                                         load_delta_stats(bstats, ds);
790                                         if (DebugOpt)
791                                                 print_blockref_verbose(stdout,
792                                                     depth, index, &m->bref,
793                                                     "cache-hit");
794                                         return 0;
795                                 }
796                         }
797                 }
798         }
799
800         bstats->total_blockref++;
801         dstats->total_blockref++;
802
803         switch (bref->type) {
804         case HAMMER2_BREF_TYPE_EMPTY:
805                 if (CountEmpty) {
806                         bstats->total_empty++;
807                         dstats->total_empty++;
808                 } else {
809                         bstats->total_blockref--;
810                         dstats->total_blockref--;
811                 }
812                 break;
813         case HAMMER2_BREF_TYPE_INODE:
814                 bstats->volume.total_inode++;
815                 dstats->volume.total_inode++;
816                 break;
817         case HAMMER2_BREF_TYPE_INDIRECT:
818                 bstats->volume.total_indirect++;
819                 dstats->volume.total_indirect++;
820                 break;
821         case HAMMER2_BREF_TYPE_DATA:
822                 bstats->volume.total_data++;
823                 dstats->volume.total_data++;
824                 break;
825         case HAMMER2_BREF_TYPE_DIRENT:
826                 bstats->volume.total_dirent++;
827                 dstats->volume.total_dirent++;
828                 break;
829         case HAMMER2_BREF_TYPE_FREEMAP_NODE:
830                 bstats->freemap.total_freemap_node++;
831                 dstats->freemap.total_freemap_node++;
832                 break;
833         case HAMMER2_BREF_TYPE_FREEMAP_LEAF:
834                 bstats->freemap.total_freemap_leaf++;
835                 dstats->freemap.total_freemap_leaf++;
836                 break;
837         case HAMMER2_BREF_TYPE_VOLUME:
838                 bstats->total_blockref--;
839                 dstats->total_blockref--;
840                 break;
841         case HAMMER2_BREF_TYPE_FREEMAP:
842                 bstats->total_blockref--;
843                 dstats->total_blockref--;
844                 break;
845         default:
846                 snprintf(msg, sizeof(msg), "Invalid blockref type %d",
847                     bref->type);
848                 add_blockref_entry(&bstats->root, bref, msg, strlen(msg) + 1);
849                 if (DebugOpt)
850                         print_blockref_verbose(stdout, depth, index, bref, msg);
851                 failed = true;
852                 break;
853         }
854
855         switch (read_media(fd, bref, &media, &bytes)) {
856         case -1:
857                 strlcpy(msg, "Bad I/O bytes", sizeof(msg));
858                 add_blockref_entry(&bstats->root, bref, msg, strlen(msg) + 1);
859                 if (DebugOpt)
860                         print_blockref_verbose(stdout, depth, index, bref, msg);
861                 return -1;
862         case -2:
863                 strlcpy(msg, "Failed to read media", sizeof(msg));
864                 add_blockref_entry(&bstats->root, bref, msg, strlen(msg) + 1);
865                 if (DebugOpt)
866                         print_blockref_verbose(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                         if (DebugOpt)
898                                 print_blockref_verbose(stdout, depth, index,
899                                     bref, msg);
900                         failed = true;
901                 }
902                 break;
903         case HAMMER2_CHECK_XXHASH64:
904                 cv64 = XXH64(&media, bytes, XXH_HAMMER2_SEED);
905                 if (bref->check.xxhash64.value != cv64) {
906                         strlcpy(msg, "Bad HAMMER2_CHECK_XXHASH64", sizeof(msg));
907                         add_blockref_entry(&bstats->root, bref, msg,
908                             strlen(msg) + 1);
909                         if (DebugOpt)
910                                 print_blockref_verbose(stdout, depth, index,
911                                     bref, msg);
912                         failed = true;
913                 }
914                 break;
915         case HAMMER2_CHECK_SHA192:
916                 SHA256_Init(&hash_ctx);
917                 SHA256_Update(&hash_ctx, &media, bytes);
918                 SHA256_Final(u.digest, &hash_ctx);
919                 u.digest64[2] ^= u.digest64[3];
920                 if (memcmp(u.digest, bref->check.sha192.data,
921                     sizeof(bref->check.sha192.data))) {
922                         strlcpy(msg, "Bad HAMMER2_CHECK_SHA192", sizeof(msg));
923                         add_blockref_entry(&bstats->root, bref, msg,
924                             strlen(msg) + 1);
925                         if (DebugOpt)
926                                 print_blockref_verbose(stdout, depth, index,
927                                     bref, msg);
928                         failed = true;
929                 }
930                 break;
931         case HAMMER2_CHECK_FREEMAP:
932                 cv = hammer2_icrc32(&media, bytes);
933                 if (bref->check.freemap.icrc32 != cv) {
934                         strlcpy(msg, "Bad HAMMER2_CHECK_FREEMAP", sizeof(msg));
935                         add_blockref_entry(&bstats->root, bref, msg,
936                             strlen(msg) + 1);
937                         if (DebugOpt)
938                                 print_blockref_verbose(stdout, depth, index,
939                                     bref, msg);
940                         failed = true;
941                 }
942                 break;
943         }
944
945         switch (bref->type) {
946         case HAMMER2_BREF_TYPE_INODE:
947                 if (!(media.ipdata.meta.op_flags & HAMMER2_OPFLAG_DIRECTDATA)) {
948                         bscan = &media.ipdata.u.blockset.blockref[0];
949                         bcount = HAMMER2_SET_COUNT;
950                 } else {
951                         bscan = NULL;
952                         bcount = 0;
953                 }
954                 break;
955         case HAMMER2_BREF_TYPE_INDIRECT:
956                 bscan = &media.npdata[0];
957                 bcount = bytes / sizeof(hammer2_blockref_t);
958                 break;
959         case HAMMER2_BREF_TYPE_FREEMAP_NODE:
960                 bscan = &media.npdata[0];
961                 bcount = bytes / sizeof(hammer2_blockref_t);
962                 break;
963         case HAMMER2_BREF_TYPE_VOLUME:
964                 bscan = &media.voldata.sroot_blockset.blockref[0];
965                 bcount = HAMMER2_SET_COUNT;
966                 break;
967         case HAMMER2_BREF_TYPE_FREEMAP:
968                 bscan = &media.voldata.freemap_blockset.blockref[0];
969                 bcount = HAMMER2_SET_COUNT;
970                 break;
971         default:
972                 bscan = NULL;
973                 bcount = 0;
974                 break;
975         }
976
977         if (ForceOpt)
978                 norecurse = false;
979         /*
980          * If failed, no recurse, but still verify its direct children.
981          * Beyond that is probably garbage.
982          */
983         for (i = 0; norecurse == false && i < bcount; ++i) {
984                 delta_stats_t ds;
985                 memset(&ds, 0, sizeof(ds));
986                 if (verify_blockref(fd, voldata, &bscan[i], failed, bstats,
987                     droot, &ds, depth + 1, i) == -1)
988                         return -1;
989                 if (!failed)
990                         accumulate_delta_stats(dstats, &ds);
991         }
992 end:
993         if (failed)
994                 return -1;
995
996         dstats->count++;
997         if (bref->data_off && BlockrefCacheCount > 0 &&
998             dstats->count >= BlockrefCacheCount) {
999                 assert(bytes);
1000                 add_blockref_entry(droot, bref, dstats, sizeof(*dstats));
1001                 if (DebugOpt)
1002                         print_blockref_verbose(stdout, depth, index, bref,
1003                             "cache-add");
1004         }
1005
1006         return 0;
1007 }
1008
1009 static int
1010 init_pfs_blockref(int fd, const hammer2_volume_data_t *voldata,
1011     const hammer2_blockref_t *bref, struct blockref_list *blist)
1012 {
1013         hammer2_media_data_t media;
1014         hammer2_inode_data_t ipdata;
1015         hammer2_blockref_t *bscan;
1016         int i, bcount;
1017         size_t bytes;
1018
1019         if (read_media(fd, bref, &media, &bytes))
1020                 return -1;
1021         if (!bytes)
1022                 return 0;
1023
1024         switch (bref->type) {
1025         case HAMMER2_BREF_TYPE_INODE:
1026                 ipdata = media.ipdata;
1027                 if (ipdata.meta.pfs_type & HAMMER2_PFSTYPE_SUPROOT) {
1028                         bscan = &ipdata.u.blockset.blockref[0];
1029                         bcount = HAMMER2_SET_COUNT;
1030                 } else {
1031                         bscan = NULL;
1032                         bcount = 0;
1033                         if (ipdata.meta.op_flags & HAMMER2_OPFLAG_PFSROOT) {
1034                                 struct blockref_msg *newp, *p;
1035                                 newp = calloc(1, sizeof(*newp));
1036                                 assert(newp);
1037                                 newp->bref = *bref;
1038                                 newp->msg = calloc(1,
1039                                     sizeof(ipdata.filename) + 1);
1040                                 memcpy(newp->msg, ipdata.filename,
1041                                     sizeof(ipdata.filename));
1042                                 p = TAILQ_FIRST(blist);
1043                                 while (p) {
1044                                         if (strcmp(newp->msg, p->msg) <= 0) {
1045                                                 TAILQ_INSERT_BEFORE(p, newp,
1046                                                     entry);
1047                                                 break;
1048                                         }
1049                                         p = TAILQ_NEXT(p, entry);
1050                                 }
1051                                 if (!p)
1052                                         TAILQ_INSERT_TAIL(blist, newp, entry);
1053                         } else
1054                                 assert(0); /* should only see SUPROOT or PFS */
1055                 }
1056                 break;
1057         case HAMMER2_BREF_TYPE_INDIRECT:
1058                 bscan = &media.npdata[0];
1059                 bcount = bytes / sizeof(hammer2_blockref_t);
1060                 break;
1061         case HAMMER2_BREF_TYPE_VOLUME:
1062                 bscan = &media.voldata.sroot_blockset.blockref[0];
1063                 bcount = HAMMER2_SET_COUNT;
1064                 break;
1065         default:
1066                 bscan = NULL;
1067                 bcount = 0;
1068                 break;
1069         }
1070
1071         for (i = 0; i < bcount; ++i)
1072                 if (init_pfs_blockref(fd, voldata, &bscan[i], blist) == -1)
1073                         return -1;
1074         return 0;
1075 }
1076
1077 static void
1078 cleanup_pfs_blockref(struct blockref_list *blist)
1079 {
1080         cleanup_blockref_msg(blist);
1081 }
1082
1083 static void
1084 print_media(FILE *fp, int tab, const hammer2_blockref_t *bref,
1085     hammer2_media_data_t *media, size_t media_bytes)
1086 {
1087         hammer2_blockref_t *bscan;
1088         hammer2_inode_data_t *ipdata;
1089         int i, bcount, namelen;
1090         char *str = NULL;
1091
1092         switch (bref->type) {
1093         case HAMMER2_BREF_TYPE_INODE:
1094                 ipdata = &media->ipdata;
1095                 namelen = ipdata->meta.name_len;
1096                 if (namelen > HAMMER2_INODE_MAXNAME)
1097                         namelen = 0;
1098                 tfprintf(fp, tab, "filename \"%*.*s\"\n", namelen, namelen,
1099                     ipdata->filename);
1100                 tfprintf(fp, tab, "version %d\n", ipdata->meta.version);
1101                 tfprintf(fp, tab, "pfs_subtype %d\n", ipdata->meta.pfs_subtype);
1102                 tfprintf(fp, tab, "uflags 0x%08x\n", ipdata->meta.uflags);
1103                 if (ipdata->meta.rmajor || ipdata->meta.rminor) {
1104                         tfprintf(fp, tab, "rmajor %d\n", ipdata->meta.rmajor);
1105                         tfprintf(fp, tab, "rminor %d\n", ipdata->meta.rminor);
1106                 }
1107                 tfprintf(fp, tab, "ctime %s\n",
1108                     hammer2_time64_to_str(ipdata->meta.ctime, &str));
1109                 tfprintf(fp, tab, "mtime %s\n",
1110                     hammer2_time64_to_str(ipdata->meta.mtime, &str));
1111                 tfprintf(fp, tab, "atime %s\n",
1112                     hammer2_time64_to_str(ipdata->meta.atime, &str));
1113                 tfprintf(fp, tab, "btime %s\n",
1114                     hammer2_time64_to_str(ipdata->meta.btime, &str));
1115                 tfprintf(fp, tab, "uid %s\n",
1116                     hammer2_uuid_to_str(&ipdata->meta.uid, &str));
1117                 tfprintf(fp, tab, "gid %s\n",
1118                     hammer2_uuid_to_str(&ipdata->meta.gid, &str));
1119                 tfprintf(fp, tab, "type %s\n",
1120                     hammer2_iptype_to_str(ipdata->meta.type));
1121                 tfprintf(fp, tab, "op_flags 0x%02x\n", ipdata->meta.op_flags);
1122                 tfprintf(fp, tab, "cap_flags 0x%04x\n", ipdata->meta.cap_flags);
1123                 tfprintf(fp, tab, "mode %-7o\n", ipdata->meta.mode);
1124                 tfprintf(fp, tab, "inum 0x%016jx\n", ipdata->meta.inum);
1125                 tfprintf(fp, tab, "size %ju ", (uintmax_t)ipdata->meta.size);
1126                 if (ipdata->meta.op_flags & HAMMER2_OPFLAG_DIRECTDATA &&
1127                     ipdata->meta.size <= HAMMER2_EMBEDDED_BYTES)
1128                         printf("(embedded data)\n");
1129                 else
1130                         printf("\n");
1131                 tfprintf(fp, tab, "nlinks %ju\n",
1132                     (uintmax_t)ipdata->meta.nlinks);
1133                 tfprintf(fp, tab, "iparent 0x%016jx\n",
1134                     (uintmax_t)ipdata->meta.iparent);
1135                 tfprintf(fp, tab, "name_key 0x%016jx\n",
1136                     (uintmax_t)ipdata->meta.name_key);
1137                 tfprintf(fp, tab, "name_len %u\n", ipdata->meta.name_len);
1138                 tfprintf(fp, tab, "ncopies %u\n", ipdata->meta.ncopies);
1139                 tfprintf(fp, tab, "comp_algo %u\n", ipdata->meta.comp_algo);
1140                 tfprintf(fp, tab, "target_type %u\n", ipdata->meta.target_type);
1141                 tfprintf(fp, tab, "check_algo %u\n", ipdata->meta.check_algo);
1142                 if ((ipdata->meta.op_flags & HAMMER2_OPFLAG_PFSROOT) ||
1143                     ipdata->meta.pfs_type == HAMMER2_PFSTYPE_SUPROOT) {
1144                         tfprintf(fp, tab, "pfs_nmasters %u\n",
1145                             ipdata->meta.pfs_nmasters);
1146                         tfprintf(fp, tab, "pfs_type %u (%s)\n",
1147                             ipdata->meta.pfs_type,
1148                             hammer2_pfstype_to_str(ipdata->meta.pfs_type));
1149                         tfprintf(fp, tab, "pfs_inum 0x%016jx\n",
1150                             (uintmax_t)ipdata->meta.pfs_inum);
1151                         tfprintf(fp, tab, "pfs_clid %s\n",
1152                             hammer2_uuid_to_str(&ipdata->meta.pfs_clid, &str));
1153                         tfprintf(fp, tab, "pfs_fsid %s\n",
1154                             hammer2_uuid_to_str(&ipdata->meta.pfs_fsid, &str));
1155                         tfprintf(fp, tab, "pfs_lsnap_tid 0x%016jx\n",
1156                             (uintmax_t)ipdata->meta.pfs_lsnap_tid);
1157                 }
1158                 tfprintf(fp, tab, "data_quota %ju\n",
1159                     (uintmax_t)ipdata->meta.data_quota);
1160                 tfprintf(fp, tab, "data_count %ju\n",
1161                     (uintmax_t)bref->embed.stats.data_count);
1162                 tfprintf(fp, tab, "inode_quota %ju\n",
1163                     (uintmax_t)ipdata->meta.inode_quota);
1164                 tfprintf(fp, tab, "inode_count %ju\n",
1165                     (uintmax_t)bref->embed.stats.inode_count);
1166                 break;
1167         case HAMMER2_BREF_TYPE_INDIRECT:
1168                 bcount = media_bytes / sizeof(hammer2_blockref_t);
1169                 for (i = 0; i < bcount; ++i) {
1170                         bscan = &media->npdata[i];
1171                         tfprintf(fp, tab, "%3d %016jx %-12s %016jx/%-2d\n",
1172                             i, (uintmax_t)bscan->data_off,
1173                             hammer2_breftype_to_str(bscan->type),
1174                             (uintmax_t)bscan->key,
1175                             bscan->keybits);
1176                 }
1177                 break;
1178         case HAMMER2_BREF_TYPE_DIRENT:
1179                 if (bref->embed.dirent.namlen <= sizeof(bref->check.buf)) {
1180                         tfprintf(fp, tab, "filename \"%*.*s\"\n",
1181                             bref->embed.dirent.namlen,
1182                             bref->embed.dirent.namlen,
1183                             bref->check.buf);
1184                 } else {
1185                         tfprintf(fp, tab, "filename \"%*.*s\"\n",
1186                             bref->embed.dirent.namlen,
1187                             bref->embed.dirent.namlen,
1188                             media->buf);
1189                 }
1190                 tfprintf(fp, tab, "inum 0x%016jx\n",
1191                     (uintmax_t)bref->embed.dirent.inum);
1192                 tfprintf(fp, tab, "namlen %d\n",
1193                     (uintmax_t)bref->embed.dirent.namlen);
1194                 tfprintf(fp, tab, "type %s\n",
1195                     hammer2_iptype_to_str(bref->embed.dirent.type));
1196                 break;
1197         case HAMMER2_BREF_TYPE_FREEMAP_NODE:
1198                 bcount = media_bytes / sizeof(hammer2_blockref_t);
1199                 for (i = 0; i < bcount; ++i) {
1200                         bscan = &media->npdata[i];
1201                         tfprintf(fp, tab, "%3d %016jx %-12s %016jx/%-2d\n",
1202                             i, (uintmax_t)bscan->data_off,
1203                             hammer2_breftype_to_str(bscan->type),
1204                             (uintmax_t)bscan->key,
1205                             bscan->keybits);
1206                 }
1207                 break;
1208         case HAMMER2_BREF_TYPE_FREEMAP_LEAF:
1209                 for (i = 0; i < HAMMER2_FREEMAP_COUNT; ++i) {
1210                         hammer2_off_t data_off = bref->key +
1211                                 i * HAMMER2_FREEMAP_LEVEL0_SIZE;
1212 #if HAMMER2_BMAP_ELEMENTS != 8
1213 #error "HAMMER2_BMAP_ELEMENTS != 8"
1214 #endif
1215                         tfprintf(fp, tab, "%016jx %04d.%04x (avail=%7d) "
1216                             "%016jx %016jx %016jx %016jx "
1217                             "%016jx %016jx %016jx %016jx\n",
1218                             data_off, i, media->bmdata[i].class,
1219                             media->bmdata[i].avail,
1220                             media->bmdata[i].bitmapq[0],
1221                             media->bmdata[i].bitmapq[1],
1222                             media->bmdata[i].bitmapq[2],
1223                             media->bmdata[i].bitmapq[3],
1224                             media->bmdata[i].bitmapq[4],
1225                             media->bmdata[i].bitmapq[5],
1226                             media->bmdata[i].bitmapq[6],
1227                             media->bmdata[i].bitmapq[7]);
1228                 }
1229                 break;
1230         default:
1231                 break;
1232         }
1233         if (str)
1234                 free(str);
1235 }
1236
1237 static hammer2_off_t
1238 check_volume(int fd)
1239 {
1240         struct partinfo pinfo;
1241         hammer2_off_t size;
1242
1243         if (ioctl(fd, DIOCGPART, &pinfo) < 0) {
1244                 struct stat st;
1245                 if (fstat(fd, &st) < 0) {
1246                         perror("fstat");
1247                         return -1;
1248                 }
1249                 if (!S_ISREG(st.st_mode)) {
1250                         fprintf(stderr, "Unsupported file type\n");
1251                         return -1;
1252                 }
1253                 assert(0); /* XXX not reached */
1254                 size = st.st_size;
1255         } else {
1256                 if (pinfo.reserved_blocks) {
1257                         fprintf(stderr, "HAMMER2 cannot be placed in a "
1258                             "partition which overlaps the disklabel or MBR\n");
1259                         return -1;
1260                 }
1261                 if (pinfo.media_blksize > HAMMER2_PBUFSIZE ||
1262                     HAMMER2_PBUFSIZE % pinfo.media_blksize) {
1263                         fprintf(stderr, "A media sector size of %d is not "
1264                             "supported\n", pinfo.media_blksize);
1265                         return -1;
1266                 }
1267                 size = pinfo.media_size;
1268         }
1269         return size;
1270 }
1271
1272 int
1273 test_hammer2(const char *devpath)
1274 {
1275         struct stat st;
1276         bool failed = false;
1277         int fd;
1278
1279         fd = open(devpath, O_RDONLY);
1280         if (fd == -1) {
1281                 perror("open");
1282                 return -1;
1283         }
1284
1285         if (fstat(fd, &st) == -1) {
1286                 perror("fstat");
1287                 failed = true;
1288                 goto end;
1289         }
1290         if (!S_ISCHR(st.st_mode)) {
1291                 fprintf(stderr, "%s is not a block device\n", devpath);
1292                 failed = true;
1293                 goto end;
1294         }
1295
1296         volume_size = check_volume(fd);
1297         if (volume_size == (hammer2_off_t)-1) {
1298                 fprintf(stderr, "Failed to find volume size\n");
1299                 failed = true;
1300                 goto end;
1301         }
1302         volume_size &= ~HAMMER2_VOLUME_ALIGNMASK64;
1303
1304         best_zone = find_best_zone(fd);
1305         if (best_zone == -1)
1306                 fprintf(stderr, "Failed to find best zone\n");
1307
1308         printf("volume header\n");
1309         if (test_volume_header(fd) == -1) {
1310                 failed = true;
1311                 if (!ForceOpt)
1312                         goto end;
1313         }
1314
1315         printf("freemap\n");
1316         if (test_blockref(fd, HAMMER2_BREF_TYPE_FREEMAP) == -1) {
1317                 failed = true;
1318                 if (!ForceOpt)
1319                         goto end;
1320         }
1321         printf("volume\n");
1322         if (!ScanPFS) {
1323                 if (test_blockref(fd, HAMMER2_BREF_TYPE_VOLUME) == -1) {
1324                         failed = true;
1325                         if (!ForceOpt)
1326                                 goto end;
1327                 }
1328         } else {
1329                 if (test_pfs_blockref(fd) == -1) {
1330                         failed = true;
1331                         if (!ForceOpt)
1332                                 goto end;
1333                 }
1334         }
1335 end:
1336         close(fd);
1337
1338         return failed ? -1 : 0;
1339 }