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