sbin/fsck_hammer2: Fix read(2) retvals to use ssize_t
[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 "fsck_hammer2.h"
57
58 struct blockref_msg {
59         TAILQ_ENTRY(blockref_msg) entry;
60         char *msg;
61 };
62
63 struct blockref_entry {
64         RB_ENTRY(blockref_entry) entry;
65         hammer2_off_t data_off;
66         TAILQ_HEAD(, blockref_msg) head;
67 };
68
69 static int
70 blockref_cmp(struct blockref_entry *b1, struct blockref_entry *b2)
71 {
72         if (b1->data_off < b2->data_off)
73                 return -1;
74         if (b1->data_off > b2->data_off)
75                 return 1;
76         return 0;
77 }
78
79 RB_HEAD(blockref_tree, blockref_entry);
80 RB_PROTOTYPE2(blockref_tree, blockref_entry, entry, blockref_cmp,
81     hammer2_off_t);
82 RB_GENERATE2(blockref_tree, blockref_entry, entry, blockref_cmp, hammer2_off_t,
83     data_off);
84
85 typedef struct {
86         struct blockref_tree root;
87         uint8_t type; /* HAMMER2_BREF_TYPE_VOLUME or FREEMAP */
88         uint64_t total_blockref;
89         uint64_t total_empty;
90         uint64_t total_invalid;
91         uint64_t total_bytes;
92         union {
93                 /* use volume or freemap depending on type value */
94                 struct {
95                         uint64_t total_inode;
96                         uint64_t total_indirect;
97                         uint64_t total_data;
98                         uint64_t total_dirent;
99                 } volume;
100                 struct {
101                         uint64_t total_freemap_node;
102                         uint64_t total_freemap_leaf;
103                 } freemap;
104         };
105 } blockref_stats_t;
106
107 static void init_blockref_stats(blockref_stats_t *, uint8_t);
108 static void cleanup_blockref_stats(blockref_stats_t *);
109 static void print_blockref_stats(const blockref_stats_t *, bool);
110 static int verify_volume_header(const hammer2_volume_data_t *);
111 static int verify_blockref(int, const hammer2_volume_data_t *,
112     const hammer2_blockref_t *, bool, blockref_stats_t *);
113
114 static int best_zone = -1;
115
116 #define TAB 8
117
118 static void
119 tfprintf(FILE *fp, int tab, const char *ctl, ...)
120 {
121         va_list va;
122
123         tab *= TAB;
124         fprintf(fp, "%*s", tab, "");
125         //fflush(fp);
126
127         va_start(va, ctl);
128         vfprintf(fp, ctl, va);
129         va_end(va);
130 }
131
132 static int
133 find_best_zone(int fd)
134 {
135         hammer2_blockref_t best;
136         int i, best_i = -1;
137
138         memset(&best, 0, sizeof(best));
139
140         for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
141                 hammer2_volume_data_t voldata;
142                 hammer2_blockref_t broot;
143                 ssize_t ret;
144
145                 memset(&broot, 0, sizeof(broot));
146                 broot.data_off = (i * HAMMER2_ZONE_BYTES64) | HAMMER2_PBUFRADIX;
147                 lseek(fd, broot.data_off & ~HAMMER2_OFF_MASK_RADIX, SEEK_SET);
148
149                 ret = read(fd, &voldata, HAMMER2_PBUFSIZE);
150                 if (ret == HAMMER2_PBUFSIZE) {
151                         if ((voldata.magic != HAMMER2_VOLUME_ID_HBO) &&
152                             (voldata.magic != HAMMER2_VOLUME_ID_ABO))
153                                 continue;
154                         broot.mirror_tid = voldata.mirror_tid;
155                         if (best_i < 0 || best.mirror_tid < broot.mirror_tid) {
156                                 best_i = i;
157                                 best = broot;
158                         }
159                 } else if (ret == -1) {
160                         perror("read");
161                         return -1;
162                 } else {
163                         tfprintf(stderr, 1, "Failed to read volume header\n");
164                         return -1;
165                 }
166         }
167
168         return best_i;
169 }
170
171 static int
172 test_volume_header(int fd)
173 {
174         bool failed = false;
175         int i;
176
177         for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
178                 hammer2_volume_data_t voldata;
179                 hammer2_blockref_t broot;
180                 ssize_t ret;
181
182                 memset(&broot, 0, sizeof(broot));
183                 broot.data_off = (i * HAMMER2_ZONE_BYTES64) | HAMMER2_PBUFRADIX;
184                 lseek(fd, broot.data_off & ~HAMMER2_OFF_MASK_RADIX, SEEK_SET);
185
186                 ret = read(fd, &voldata, HAMMER2_PBUFSIZE);
187                 if (ret == HAMMER2_PBUFSIZE) {
188                         broot.mirror_tid = voldata.mirror_tid;
189
190                         printf("zone.%d %016lX%s\n", i, broot.data_off,
191                             (i == best_zone) ? " (best)" : "");
192                         if (verify_volume_header(&voldata) == -1)
193                                 failed = true;
194                 } else if (ret == -1) {
195                         perror("read");
196                         return -1;
197                 } else {
198                         tfprintf(stderr, 1, "Failed to read volume header\n");
199                         return -1;
200                 }
201         }
202
203         return failed ? -1 : 0;
204 }
205
206 static int
207 test_blockref(int fd, uint8_t type)
208 {
209         bool failed = false;
210         int i;
211
212         for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
213                 hammer2_volume_data_t voldata;
214                 hammer2_blockref_t broot;
215                 ssize_t ret;
216
217                 memset(&broot, 0, sizeof(broot));
218                 broot.type = type;
219                 broot.data_off = (i * HAMMER2_ZONE_BYTES64) | HAMMER2_PBUFRADIX;
220                 lseek(fd, broot.data_off & ~HAMMER2_OFF_MASK_RADIX, SEEK_SET);
221
222                 ret = read(fd, &voldata, HAMMER2_PBUFSIZE);
223                 if (ret == HAMMER2_PBUFSIZE) {
224                         blockref_stats_t bstats;
225                         struct blockref_entry *e;
226                         broot.mirror_tid = voldata.mirror_tid;
227                         init_blockref_stats(&bstats, type);
228
229                         printf("zone.%d %016lX%s\n", i, broot.data_off,
230                             (i == best_zone) ? " (best)" : "");
231                         if (verify_blockref(fd, &voldata, &broot, false,
232                             &bstats) == -1)
233                                 failed = true;
234                         print_blockref_stats(&bstats, true);
235
236                         RB_FOREACH(e, blockref_tree, &bstats.root) {
237                                 struct blockref_msg *m;
238                                 TAILQ_FOREACH(m, &e->head, entry) {
239                                         tfprintf(stderr, 1, "%016lX %s\n",
240                                             e->data_off, m->msg);
241                                 }
242                         }
243                         cleanup_blockref_stats(&bstats);
244                 } else if (ret == -1) {
245                         perror("read");
246                         return -1;
247                 } else {
248                         tfprintf(stderr, 1, "Failed to read volume header\n");
249                         return -1;
250                 }
251         }
252
253         return failed ? -1 : 0;
254 }
255
256 static int
257 charsperline(void)
258 {
259         int columns;
260         char *cp;
261         struct winsize ws;
262
263         columns = 0;
264         if (ioctl(0, TIOCGWINSZ, &ws) != -1)
265                 columns = ws.ws_col;
266         if (columns == 0 && (cp = getenv("COLUMNS")))
267                 columns = atoi(cp);
268         if (columns == 0)
269                 columns = 80;   /* last resort */
270
271         return columns;
272 }
273
274 static void
275 add_blockref_entry(blockref_stats_t *bstats, hammer2_off_t data_off,
276     const char *msg)
277 {
278         struct blockref_entry *e;
279         struct blockref_msg *m;
280
281         e = RB_LOOKUP(blockref_tree, &bstats->root, data_off);
282         if (!e) {
283                 e = calloc(1, sizeof(*e));
284                 assert(e);
285                 TAILQ_INIT(&e->head);
286         }
287
288         m = calloc(1, sizeof(*m));
289         assert(m);
290         m->msg = strdup(msg);
291
292         e->data_off = data_off;
293         TAILQ_INSERT_TAIL(&e->head, m, entry);
294         RB_INSERT(blockref_tree, &bstats->root, e);
295 }
296
297 static void
298 init_blockref_stats(blockref_stats_t *bstats, uint8_t type)
299 {
300         memset(bstats, 0, sizeof(*bstats));
301         bstats->type = type;
302         RB_INIT(&bstats->root);
303 }
304
305 static void
306 cleanup_blockref_stats(blockref_stats_t *bstats)
307 {
308         struct blockref_entry *e;
309
310         while ((e = RB_ROOT(&bstats->root)) != NULL) {
311                 struct blockref_msg *m;
312                 RB_REMOVE(blockref_tree, &bstats->root, e);
313                 while ((m = TAILQ_FIRST(&e->head)) != NULL) {
314                         TAILQ_REMOVE(&e->head, m, entry);
315                         free(m->msg);
316                         free(m);
317                 }
318                 assert(TAILQ_EMPTY(&e->head));
319                 free(e);
320         }
321         assert(RB_EMPTY(&bstats->root));
322 }
323
324 static void
325 print_blockref_stats(const blockref_stats_t *bstats, bool newline)
326 {
327         size_t siz = charsperline();
328         char *buf = calloc(1, siz);
329
330         assert(buf);
331
332         switch (bstats->type) {
333         case HAMMER2_BREF_TYPE_VOLUME:
334                 snprintf(buf, siz, "%*s%ju blockref (%ju inode, %ju indirect, "
335                     "%ju data, %ju dirent, %ju empty), %s",
336                     TAB, "",
337                     (uintmax_t)bstats->total_blockref,
338                     (uintmax_t)bstats->volume.total_inode,
339                     (uintmax_t)bstats->volume.total_indirect,
340                     (uintmax_t)bstats->volume.total_data,
341                     (uintmax_t)bstats->volume.total_dirent,
342                     (uintmax_t)bstats->total_empty,
343                     sizetostr(bstats->total_bytes));
344                 break;
345         case HAMMER2_BREF_TYPE_FREEMAP:
346                 snprintf(buf, siz, "%*s%ju blockref (%ju node, %ju leaf, "
347                     "%ju empty), %s",
348                     TAB, "",
349                     (uintmax_t)bstats->total_blockref,
350                     (uintmax_t)bstats->freemap.total_freemap_node,
351                     (uintmax_t)bstats->freemap.total_freemap_leaf,
352                     (uintmax_t)bstats->total_empty,
353                     sizetostr(bstats->total_bytes));
354                 break;
355         default:
356                 assert(0);
357                 break;
358         }
359
360         if (newline) {
361                 printf("%s\n", buf);
362         } else {
363                 printf("%s\r", buf);
364                 fflush(stdout);
365         }
366         free(buf);
367 }
368
369 static int
370 verify_volume_header(const hammer2_volume_data_t *voldata)
371 {
372         hammer2_crc32_t crc0, crc, bcrc0, bcrc;
373         const char *p = (const char*)voldata;
374
375         if ((voldata->magic != HAMMER2_VOLUME_ID_HBO) &&
376             (voldata->magic != HAMMER2_VOLUME_ID_ABO)) {
377                 tfprintf(stderr, 1, "Bad magic %jX\n", voldata->magic);
378                 return -1;
379         }
380
381         if (voldata->magic == HAMMER2_VOLUME_ID_ABO)
382                 tfprintf(stderr, 1, "Reverse endian\n");
383
384         crc = voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT0];
385         crc0 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRC0_OFF,
386             HAMMER2_VOLUME_ICRC0_SIZE);
387         if (crc0 != crc) {
388                 tfprintf(stderr, 1, "Bad HAMMER2_VOL_ICRC_SECT0 CRC\n");
389                 return -1;
390         }
391
392         bcrc = voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT1];
393         bcrc0 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRC1_OFF,
394             HAMMER2_VOLUME_ICRC1_SIZE);
395         if (bcrc0 != bcrc) {
396                 tfprintf(stderr, 1, "Bad HAMMER2_VOL_ICRC_SECT1 CRC\n");
397                 return -1;
398         }
399
400         return 0;
401 }
402
403 static int
404 verify_blockref(int fd, const hammer2_volume_data_t *voldata,
405     const hammer2_blockref_t *bref, bool norecurse, blockref_stats_t *bstats)
406 {
407         hammer2_media_data_t media;
408         hammer2_blockref_t *bscan;
409         int i, bcount;
410         bool failed = false;
411         size_t bytes;
412         uint32_t cv;
413         uint64_t cv64;
414         char msg[256];
415
416         SHA256_CTX hash_ctx;
417         union {
418                 uint8_t digest[SHA256_DIGEST_LENGTH];
419                 uint64_t digest64[SHA256_DIGEST_LENGTH/8];
420         } u;
421
422         bstats->total_blockref++;
423
424         switch (bref->type) {
425         case HAMMER2_BREF_TYPE_EMPTY:
426                 bstats->total_empty++;
427                 break;
428         case HAMMER2_BREF_TYPE_INODE:
429                 bstats->volume.total_inode++;
430                 break;
431         case HAMMER2_BREF_TYPE_INDIRECT:
432                 bstats->volume.total_indirect++;
433                 break;
434         case HAMMER2_BREF_TYPE_DATA:
435                 bstats->volume.total_data++;
436                 break;
437         case HAMMER2_BREF_TYPE_DIRENT:
438                 bstats->volume.total_dirent++;
439                 break;
440         case HAMMER2_BREF_TYPE_FREEMAP_NODE:
441                 bstats->freemap.total_freemap_node++;
442                 break;
443         case HAMMER2_BREF_TYPE_FREEMAP_LEAF:
444                 bstats->freemap.total_freemap_leaf++;
445                 break;
446         case HAMMER2_BREF_TYPE_VOLUME:
447                 bstats->total_blockref--;
448                 break;
449         case HAMMER2_BREF_TYPE_FREEMAP:
450                 bstats->total_blockref--;
451                 break;
452         default:
453                 bstats->total_invalid++;
454                 snprintf(msg, sizeof(msg),
455                     "Invalid blockref type %d", bref->type);
456                 add_blockref_entry(bstats, bref->data_off, msg);
457                 failed = true;
458                 break;
459         }
460
461         bytes = (bref->data_off & HAMMER2_OFF_MASK_RADIX);
462         if (bytes)
463                 bytes = (size_t)1 << bytes;
464         if (bref->type != HAMMER2_BREF_TYPE_VOLUME &&
465             bref->type != HAMMER2_BREF_TYPE_FREEMAP)
466                 bstats->total_bytes += bytes;
467
468         if ((bstats->total_blockref % 100) == 0)
469                 print_blockref_stats(bstats, false);
470
471         if (bytes) {
472                 hammer2_off_t io_off;
473                 hammer2_off_t io_base;
474                 size_t io_bytes;
475                 size_t boff;
476
477                 io_off = bref->data_off & ~HAMMER2_OFF_MASK_RADIX;
478                 io_base = io_off & ~(hammer2_off_t)(HAMMER2_MINIOSIZE - 1);
479                 boff = io_off - io_base;
480
481                 io_bytes = HAMMER2_MINIOSIZE;
482                 while (io_bytes + boff < bytes)
483                         io_bytes <<= 1;
484
485                 if (io_bytes > sizeof(media)) {
486                         snprintf(msg, sizeof(msg),
487                             "Bad I/O bytes %ju", io_bytes);
488                         add_blockref_entry(bstats, bref->data_off, msg);
489                         return -1;
490                 }
491                 lseek(fd, io_base, SEEK_SET);
492                 if (read(fd, &media, io_bytes) != (ssize_t)io_bytes) {
493                         add_blockref_entry(bstats, bref->data_off,
494                             "Failed to read media");
495                         return -1;
496                 }
497                 if (boff)
498                         memcpy(&media, (char *)&media + boff, bytes);
499
500                 switch (HAMMER2_DEC_CHECK(bref->methods)) {
501                 case HAMMER2_CHECK_ISCSI32:
502                         cv = hammer2_icrc32(&media, bytes);
503                         if (bref->check.iscsi32.value != cv) {
504                                 add_blockref_entry(bstats, bref->data_off,
505                                     "Bad HAMMER2_CHECK_ISCSI32");
506                                 failed = true;
507                         }
508                         break;
509                 case HAMMER2_CHECK_XXHASH64:
510                         cv64 = XXH64(&media, bytes, XXH_HAMMER2_SEED);
511                         if (bref->check.xxhash64.value != cv64) {
512                                 add_blockref_entry(bstats, bref->data_off,
513                                     "Bad HAMMER2_CHECK_XXHASH64");
514                                 failed = true;
515                         }
516                         break;
517                 case HAMMER2_CHECK_SHA192:
518                         SHA256_Init(&hash_ctx);
519                         SHA256_Update(&hash_ctx, &media, bytes);
520                         SHA256_Final(u.digest, &hash_ctx);
521                         u.digest64[2] ^= u.digest64[3];
522                         if (memcmp(u.digest, bref->check.sha192.data,
523                             sizeof(bref->check.sha192.data))) {
524                                 add_blockref_entry(bstats, bref->data_off,
525                                     "Bad HAMMER2_CHECK_SHA192");
526                                 failed = true;
527                         }
528                         break;
529                 case HAMMER2_CHECK_FREEMAP:
530                         cv = hammer2_icrc32(&media, bytes);
531                         if (bref->check.freemap.icrc32 != cv) {
532                                 add_blockref_entry(bstats, bref->data_off,
533                                     "Bad HAMMER2_CHECK_FREEMAP");
534                                 failed = true;
535                         }
536                         break;
537                 }
538         }
539
540         switch (bref->type) {
541         case HAMMER2_BREF_TYPE_INODE:
542                 if (!(media.ipdata.meta.op_flags & HAMMER2_OPFLAG_DIRECTDATA)) {
543                         bscan = &media.ipdata.u.blockset.blockref[0];
544                         bcount = HAMMER2_SET_COUNT;
545                 } else {
546                         bscan = NULL;
547                         bcount = 0;
548                 }
549                 break;
550         case HAMMER2_BREF_TYPE_INDIRECT:
551                 bscan = &media.npdata[0];
552                 bcount = bytes / sizeof(hammer2_blockref_t);
553                 break;
554         case HAMMER2_BREF_TYPE_FREEMAP_NODE:
555                 bscan = &media.npdata[0];
556                 bcount = bytes / sizeof(hammer2_blockref_t);
557                 break;
558         case HAMMER2_BREF_TYPE_VOLUME:
559                 bscan = &media.voldata.sroot_blockset.blockref[0];
560                 bcount = HAMMER2_SET_COUNT;
561                 break;
562         case HAMMER2_BREF_TYPE_FREEMAP:
563                 bscan = &media.voldata.freemap_blockset.blockref[0];
564                 bcount = HAMMER2_SET_COUNT;
565                 break;
566         default:
567                 bscan = NULL;
568                 bcount = 0;
569                 break;
570         }
571
572         if (ForceOpt)
573                 norecurse = 0;
574         /*
575          * If failed, no recurse, but still verify its direct children.
576          * Beyond that is probably garbage.
577          */
578         for (i = 0; norecurse == 0 && i < bcount; ++i)
579                 if (bscan[i].type != HAMMER2_BREF_TYPE_EMPTY)
580                         if (verify_blockref(fd, voldata, &bscan[i], failed,
581                             bstats) == -1)
582                                 return -1;
583         return failed ? -1 : 0;
584 }
585
586 int
587 test_hammer2(const char *devpath)
588 {
589         struct stat st;
590         int fd;
591
592         fd = open(devpath, O_RDONLY);
593         if (fd == -1) {
594                 perror("open");
595                 return -1;
596         }
597
598         if (fstat(fd, &st) == -1) {
599                 perror("fstat");
600                 close(fd);
601                 return -1;
602         }
603         if (!S_ISCHR(st.st_mode)) {
604                 fprintf(stderr, "%s is not a block device\n", devpath);
605                 close(fd);
606                 return -1;
607         }
608
609         best_zone = find_best_zone(fd);
610         if (best_zone == -1)
611                 fprintf(stderr, "Failed to find best zone\n");
612
613         printf("volume header\n");
614         if (test_volume_header(fd) == -1) {
615                 if (!ForceOpt) {
616                         close(fd);
617                         return -1;
618                 }
619         }
620
621         printf("freemap\n");
622         if (test_blockref(fd, HAMMER2_BREF_TYPE_FREEMAP) == -1) {
623                 if (!ForceOpt) {
624                         close(fd);
625                         return -1;
626                 }
627         }
628         printf("volume\n");
629         if (test_blockref(fd, HAMMER2_BREF_TYPE_VOLUME) == -1) {
630                 if (!ForceOpt) {
631                         close(fd);
632                         return -1;
633                 }
634         }
635         close(fd);
636
637         return 0;
638 }