xref: /dflybsd-src/sbin/fsck_hammer2/reconstruct.c (revision ed183f8c2f9bb14cbccb8377f3cdd29e0971d8a0)
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 // # gcc -Wall -g -I../../sys/vfs/hammer2 -I../hammer2
38 // ../../sys/libkern/icrc32.c ../../sys/vfs/hammer2/xxhash/xxhash.c
39 // ../hammer2/subs.c ./reconstruct.c -o reconstruct
40 
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <unistd.h>
44 #include <fcntl.h>
45 #include <stdio.h>
46 #include <stdlib.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 
58 static int modify_volume_header(int, hammer2_volume_data_t *,
59     const hammer2_blockref_t *);
60 static int modify_blockref(int, const hammer2_volume_data_t *, int,
61     hammer2_blockref_t *, hammer2_blockref_t *, int);
62 static int modify_check(int, int, hammer2_blockref_t *,
63     const hammer2_blockref_t *, hammer2_media_data_t *, size_t, int);
64 
65 static bool ForceOpt = false;
66 
67 static int
68 reconstruct_volume_header(int fd)
69 {
70 	bool failed = false;
71 	int i;
72 
73 	for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
74 		hammer2_volume_data_t voldata;
75 		hammer2_blockref_t broot;
76 		ssize_t ret;
77 
78 		memset(&broot, 0, sizeof(broot));
79 		broot.data_off = (i * HAMMER2_ZONE_BYTES64) | HAMMER2_PBUFRADIX;
80 		if (lseek(fd, broot.data_off & ~HAMMER2_OFF_MASK_RADIX,
81 		    SEEK_SET) == -1) {
82 			perror("lseek");
83 			return -1;
84 		}
85 
86 		ret = read(fd, &voldata, HAMMER2_PBUFSIZE);
87 		if (ret == HAMMER2_PBUFSIZE) {
88 			fprintf(stdout, "zone.%d %016jx\n",
89 			    i, (uintmax_t)broot.data_off);
90 			if (modify_volume_header(fd, &voldata, &broot) == -1)
91 				failed = true;
92 		} else if (ret == -1) {
93 			perror("read");
94 			return -1;
95 		} else {
96 			fprintf(stderr, "Failed to read volume header\n");
97 			return -1;
98 		}
99 	}
100 
101 	return failed ? -1 : 0;
102 }
103 
104 static int
105 reconstruct_blockref(int fd, uint8_t type)
106 {
107 	bool failed = false;
108 	int i;
109 
110 	for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
111 		hammer2_volume_data_t voldata;
112 		hammer2_blockref_t broot;
113 		ssize_t ret;
114 
115 		memset(&broot, 0, sizeof(broot));
116 		broot.type = type;
117 		broot.data_off = (i * HAMMER2_ZONE_BYTES64) | HAMMER2_PBUFRADIX;
118 		if (lseek(fd, broot.data_off & ~HAMMER2_OFF_MASK_RADIX,
119 		    SEEK_SET) == -1) {
120 			perror("lseek");
121 			return -1;
122 		}
123 
124 		ret = read(fd, &voldata, HAMMER2_PBUFSIZE);
125 		if (ret == HAMMER2_PBUFSIZE) {
126 			fprintf(stdout, "zone.%d %016jx\n",
127 			    i, (uintmax_t)broot.data_off);
128 			if (modify_blockref(fd, &voldata, -1, &broot, NULL, -1)
129 			    == -1)
130 				failed = true;
131 		} else if (ret == -1) {
132 			perror("read");
133 			return -1;
134 		} else {
135 			fprintf(stderr, "Failed to read volume header\n");
136 			return -1;
137 		}
138 	}
139 
140 	return failed ? -1 : 0;
141 }
142 
143 static int
144 modify_volume_header(int fd, hammer2_volume_data_t *voldata,
145     const hammer2_blockref_t *bref)
146 {
147 	hammer2_crc32_t crc0, crc1;
148 	const char *s = NULL;
149 	bool found = false;
150 
151 	if ((voldata->magic != HAMMER2_VOLUME_ID_HBO) &&
152 	    (voldata->magic != HAMMER2_VOLUME_ID_ABO)) {
153 		fprintf(stderr, "Bad magic %jX\n", voldata->magic);
154 		return -1;
155 	}
156 
157 	if (voldata->magic == HAMMER2_VOLUME_ID_ABO)
158 		fprintf(stderr, "Reverse endian\n");
159 
160 	/* Need to test HAMMER2_VOL_ICRC_SECT1 first. */
161 	crc0 = voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT1];
162 	crc1 = hammer2_icrc32((char*)voldata + HAMMER2_VOLUME_ICRC1_OFF,
163 	    HAMMER2_VOLUME_ICRC1_SIZE);
164 	if (crc0 != crc1) {
165 		if (ForceOpt)
166 			voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT1] = crc1;
167 		found = true;
168 		s = "HAMMER2_VOL_ICRC_SECT1";
169 		printf("%s%016jx %s\n", ForceOpt ? "Modified " : "",
170 		    (uintmax_t)bref->data_off, s);
171 	}
172 
173 	crc0 = voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT0];
174 	crc1 = hammer2_icrc32((char*)voldata + HAMMER2_VOLUME_ICRC0_OFF,
175 	    HAMMER2_VOLUME_ICRC0_SIZE);
176 	if (crc0 != crc1) {
177 		if (ForceOpt)
178 			voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT0] = crc1;
179 		found = true;
180 		s = "HAMMER2_VOL_ICRC_SECT0";
181 		printf("%s%016jx %s\n", ForceOpt ? "Modified " : "",
182 		    (uintmax_t)bref->data_off, s);
183 	}
184 
185 	crc0 = voldata->icrc_volheader;
186 	crc1 = hammer2_icrc32((char*)voldata + HAMMER2_VOLUME_ICRCVH_OFF,
187 	    HAMMER2_VOLUME_ICRCVH_SIZE);
188 	if (crc0 != crc1) {
189 		if (ForceOpt)
190 			voldata->icrc_volheader = crc1;
191 		found = true;
192 		s = "volume header CRC";
193 		printf("%s%016jx %s\n", ForceOpt ? "Modified " : "",
194 		    (uintmax_t)bref->data_off, s);
195 	}
196 
197 	if (found && ForceOpt) {
198 		ssize_t ret;
199 		if (lseek(fd, bref->data_off & ~HAMMER2_OFF_MASK_RADIX,
200 		    SEEK_SET) == -1) {
201 			perror("lseek");
202 			return -1;
203 		}
204 		ret = write(fd, voldata, HAMMER2_PBUFSIZE);
205 		if (ret == -1) {
206 			perror("write");
207 			return -1;
208 		} else if (ret != (ssize_t)HAMMER2_PBUFSIZE) {
209 			fprintf(stderr, "Failed to write volume header\n");
210 			return -1;
211 		}
212 		if (fsync(fd) == -1) {
213 			perror("fsync");
214 			return -1;
215 		}
216 	}
217 
218 	return 0;
219 }
220 
221 static int
222 read_media(int fd, const hammer2_blockref_t *bref, hammer2_media_data_t *media,
223     size_t *media_bytes)
224 {
225 	hammer2_off_t io_off, io_base;
226 	size_t bytes, io_bytes, boff;
227 	ssize_t ret;
228 
229 	bytes = (bref->data_off & HAMMER2_OFF_MASK_RADIX);
230 	if (bytes)
231 		bytes = (size_t)1 << bytes;
232 	if (media_bytes)
233 		*media_bytes = bytes;
234 
235 	if (!bytes)
236 		return 0;
237 
238 	io_off = bref->data_off & ~HAMMER2_OFF_MASK_RADIX;
239 	io_base = io_off & ~(hammer2_off_t)(HAMMER2_MINIOSIZE - 1);
240 	boff = io_off - io_base;
241 
242 	io_bytes = HAMMER2_MINIOSIZE;
243 	while (io_bytes + boff < bytes)
244 		io_bytes <<= 1;
245 
246 	if (io_bytes > sizeof(*media)) {
247 		fprintf(stderr, "Bad I/O bytes\n");
248 		return -1;
249 	}
250 	if (lseek(fd, io_base, SEEK_SET) == -1) {
251 		perror("lseek");
252 		return -1;
253 	}
254 	ret = read(fd, media, io_bytes);
255 	if (ret == -1) {
256 		perror("read");
257 		return -1;
258 	} else if (ret != (ssize_t)io_bytes) {
259 		fprintf(stderr, "Failed to read media\n");
260 		return -1;
261 	}
262 	if (boff)
263 		memmove(media, (char *)media + boff, bytes);
264 
265 	return 0;
266 }
267 
268 static int
269 write_media(int fd, const hammer2_blockref_t *bref,
270     const hammer2_media_data_t *media, size_t media_bytes)
271 {
272 	hammer2_off_t io_off, io_base;
273 	char buf[HAMMER2_PBUFSIZE];
274 	size_t bytes, io_bytes, boff;
275 	ssize_t ret;
276 
277 	bytes = (bref->data_off & HAMMER2_OFF_MASK_RADIX);
278 	if (bytes)
279 		bytes = (size_t)1 << bytes;
280 	assert(bytes != 0);
281 	assert(bytes == media_bytes);
282 
283 	io_off = bref->data_off & ~HAMMER2_OFF_MASK_RADIX;
284 	io_base = io_off & ~(hammer2_off_t)(HAMMER2_MINIOSIZE - 1);
285 	boff = io_off - io_base;
286 
287 	io_bytes = HAMMER2_MINIOSIZE;
288 	while (io_bytes + boff < bytes)
289 		io_bytes <<= 1;
290 
291 	if (io_bytes > sizeof(buf)) {
292 		fprintf(stderr, "Bad I/O bytes\n");
293 		return -1;
294 	}
295 	if (lseek(fd, io_base, SEEK_SET) == -1) {
296 		perror("lseek");
297 		return -1;
298 	}
299 	if (read(fd, buf, io_bytes) != (ssize_t)io_bytes) {
300 		perror("read");
301 		return -1;
302 	}
303 
304 	memcpy(buf + boff, media, media_bytes);
305 	if (lseek(fd, io_base, SEEK_SET) == -1) {
306 		perror("lseek");
307 		return -1;
308 	}
309 	ret = write(fd, buf, io_bytes);
310 	if (ret == -1) {
311 		perror("write");
312 		return -1;
313 	} else if (ret != (ssize_t)io_bytes) {
314 		fprintf(stderr, "Failed to write media\n");
315 		return -1;
316 	}
317 	if (fsync(fd) == -1) {
318 		perror("fsync");
319 		return -1;
320 	}
321 
322 	return 0;
323 }
324 
325 static int
326 modify_blockref(int fd, const hammer2_volume_data_t *voldata, int bi,
327     hammer2_blockref_t *bref, hammer2_blockref_t *prev_bref, int depth)
328 {
329 	hammer2_media_data_t media;
330 	hammer2_blockref_t *bscan;
331 	int i, bcount;
332 	size_t bytes;
333 
334 	if (read_media(fd, bref, &media, &bytes) == -1)
335 		return -1;
336 
337 	if (!bytes)
338 		return 0;
339 
340 	switch (bref->type) {
341 	case HAMMER2_BREF_TYPE_INODE:
342 		if (!(media.ipdata.meta.op_flags & HAMMER2_OPFLAG_DIRECTDATA)) {
343 			bscan = &media.ipdata.u.blockset.blockref[0];
344 			bcount = HAMMER2_SET_COUNT;
345 		} else {
346 			bscan = NULL;
347 			bcount = 0;
348 		}
349 		break;
350 	case HAMMER2_BREF_TYPE_INDIRECT:
351 		bscan = &media.npdata[0];
352 		bcount = bytes / sizeof(hammer2_blockref_t);
353 		break;
354 	case HAMMER2_BREF_TYPE_FREEMAP_NODE:
355 		bscan = &media.npdata[0];
356 		bcount = bytes / sizeof(hammer2_blockref_t);
357 		break;
358 	case HAMMER2_BREF_TYPE_VOLUME:
359 		bscan = &media.voldata.sroot_blockset.blockref[0];
360 		bcount = HAMMER2_SET_COUNT;
361 		break;
362 	case HAMMER2_BREF_TYPE_FREEMAP:
363 		bscan = &media.voldata.freemap_blockset.blockref[0];
364 		bcount = HAMMER2_SET_COUNT;
365 		break;
366 	default:
367 		bscan = NULL;
368 		bcount = 0;
369 		break;
370 	}
371 
372 	for (i = 0; i < bcount; ++i)
373 		if (bscan[i].type != HAMMER2_BREF_TYPE_EMPTY)
374 			if (modify_blockref(fd, voldata, i, &bscan[i], bref,
375 			    depth + 1) == -1)
376 				return -1;
377 
378 	if (ForceOpt)
379 		if (read_media(fd, bref, &media, &bytes) == -1)
380 			return -1;
381 	if (modify_check(fd, bi, prev_bref, bref, &media, bytes, depth) == -1)
382 		return -1;
383 
384 	return 0;
385 }
386 
387 static int
388 modify_check(int fd, int bi, hammer2_blockref_t *prev_bref,
389     const hammer2_blockref_t *bref, hammer2_media_data_t *media,
390     size_t media_bytes, int depth)
391 {
392 	hammer2_media_data_t bscan_media;
393 	hammer2_blockref_t *bscan;
394 	bool found = false;
395 	size_t bytes;
396 	uint32_t cv;
397 	uint64_t cv64;
398 
399 	//SHA256_CTX hash_ctx;
400 	union {
401 		uint8_t digest[SHA256_DIGEST_LENGTH];
402 		uint64_t digest64[SHA256_DIGEST_LENGTH/8];
403 	} u;
404 
405 
406 	if (!prev_bref)
407 		return 0;
408 	if (read_media(fd, prev_bref, &bscan_media, &bytes) == -1)
409 		return -1;
410 	assert(bytes);
411 
412 	switch (prev_bref->type) {
413 	case HAMMER2_BREF_TYPE_INODE:
414 		if (!(bscan_media.ipdata.meta.op_flags &
415 		    HAMMER2_OPFLAG_DIRECTDATA))
416 			bscan = &bscan_media.ipdata.u.blockset.blockref[bi];
417 		else
418 			bscan = NULL;
419 		break;
420 	case HAMMER2_BREF_TYPE_INDIRECT:
421 		bscan = &bscan_media.npdata[bi];
422 		break;
423 	case HAMMER2_BREF_TYPE_VOLUME:
424 		bscan = &bscan_media.voldata.sroot_blockset.blockref[bi];
425 		break;
426 	case HAMMER2_BREF_TYPE_FREEMAP:
427 		bscan = &bscan_media.voldata.freemap_blockset.blockref[bi];
428 		break;
429 	case HAMMER2_BREF_TYPE_FREEMAP_NODE:
430 		bscan = &bscan_media.npdata[bi];
431 		break;
432 	default:
433 		assert(0);
434 		break;
435 	}
436 
437 	if (memcmp(bref, bscan, sizeof(*bref))) {
438 		fprintf(stderr, "Blockref contents mismatch\n");
439 		return -1;
440 	}
441 
442 	switch (HAMMER2_DEC_CHECK(bscan->methods)) {
443 	case HAMMER2_CHECK_ISCSI32:
444 		cv = hammer2_icrc32(media, media_bytes);
445 		if (bscan->check.iscsi32.value != cv) {
446 			if (ForceOpt)
447 				bscan->check.iscsi32.value = cv;
448 			found = true;
449 		}
450 		break;
451 	case HAMMER2_CHECK_XXHASH64:
452 		cv64 = XXH64(media, media_bytes, XXH_HAMMER2_SEED);
453 		if (bscan->check.xxhash64.value != cv64) {
454 			if (ForceOpt)
455 				bscan->check.xxhash64.value = cv64;
456 			found = true;
457 		}
458 		break;
459 	case HAMMER2_CHECK_SHA192:
460 #if 0
461 		SHA256_Init(&hash_ctx);
462 		SHA256_Update(&hash_ctx, &media, bytes);
463 		SHA256_Final(u.digest, &hash_ctx);
464 #endif
465 		u.digest64[2] ^= u.digest64[3];
466 		if (memcmp(u.digest, bscan->check.sha192.data,
467 		    sizeof(bscan->check.sha192.data))) {
468 			if (ForceOpt)
469 				memcpy(&bscan->check.sha192.data, u.digest,
470 				    sizeof(bscan->check.sha192.data));
471 			found = true;
472 		}
473 		fprintf(stderr, "HAMMER2_CHECK_SHA192 unsupported\n");
474 		assert(0);
475 		break;
476 	case HAMMER2_CHECK_FREEMAP:
477 		cv = hammer2_icrc32(media, media_bytes);
478 		if (bscan->check.freemap.icrc32 != cv) {
479 			if (ForceOpt)
480 				bscan->check.freemap.icrc32 = cv;
481 			found = true;
482 		}
483 		break;
484 	}
485 
486 	if (found) {
487 		if (ForceOpt) {
488 			if (write_media(fd, prev_bref, &bscan_media, bytes)
489 			    == -1)
490 				return -1;
491 		}
492 		/* If !ForceOpt, only first bad blockref is printed. */
493 		printf("%s%2d %-8s blockref[%-3d] %016jx %02x %s\n",
494 		    ForceOpt ? "Modified " : "",
495 		    depth, hammer2_breftype_to_str(prev_bref->type), bi,
496 		    (uintmax_t)bscan->data_off, bscan->methods,
497 		    hammer2_breftype_to_str(bscan->type));
498 	}
499 
500 	return 0;
501 }
502 
503 int
504 main(int argc, char **argv)
505 {
506 	struct stat st;
507 	int ch, fd;
508 	const char *binpath = argv[0];
509 	const char *devpath;
510 
511 	while ((ch = getopt(argc, argv, "f")) != -1) {
512 		switch(ch) {
513 		case 'f':
514 			ForceOpt = true;
515 			break;
516 		default:
517 			break;
518 		}
519 	}
520 	argc -= optind;
521 	argv += optind;
522 
523 	if (argc < 1) {
524 		fprintf(stderr, "%s [-f] special\n", binpath);
525 		exit(1);
526 	}
527 	devpath = argv[0];
528 
529 	fd = open(devpath, O_RDWR);
530 	if (fd == -1) {
531 		perror("open");
532 		exit(1);
533 	}
534 
535 	if (fstat(fd, &st) == -1) {
536 		perror("fstat");
537 		exit(1);
538 	}
539 	if (!S_ISCHR(st.st_mode)) {
540 		fprintf(stderr, "%s is not a block device\n", devpath);
541 		exit(1);
542 	}
543 
544 	printf("freemap\n");
545 	if (reconstruct_blockref(fd, HAMMER2_BREF_TYPE_FREEMAP) == -1)
546 		exit(1);
547 	printf("volume\n");
548 	if (reconstruct_blockref(fd, HAMMER2_BREF_TYPE_VOLUME) == -1)
549 		exit(1);
550 
551 	printf("volume header\n");
552 	if (reconstruct_volume_header(fd) == -1)
553 		exit(1);
554 
555 	close(fd);
556 
557 	return 0;
558 }
559