xref: /freebsd/sbin/recoverdisk/recoverdisk.c (revision d0b2dbfa0ecf2bbc9709efc5e20baf8e4b44bbbf)
1 /*-
2  * SPDX-License-Identifier: Beerware
3  *
4  * ----------------------------------------------------------------------------
5  * "THE BEER-WARE LICENSE" (Revision 42):
6  * <phk@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
7  * can do whatever you want with this stuff. If we meet some day, and you think
8  * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
9  * ----------------------------------------------------------------------------
10  */
11 #include <sys/param.h>
12 #include <sys/queue.h>
13 #include <sys/disk.h>
14 #include <sys/stat.h>
15 
16 #include <assert.h>
17 #include <err.h>
18 #include <errno.h>
19 #include <math.h>
20 #include <fcntl.h>
21 #include <signal.h>
22 #include <stdint.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <termios.h>
27 #include <time.h>
28 #include <unistd.h>
29 
30 /* Safe printf into a fixed-size buffer */
31 #define bprintf(buf, fmt, ...)                                          \
32 	do {                                                            \
33 		int ibprintf;                                           \
34 		ibprintf = snprintf(buf, sizeof buf, fmt, __VA_ARGS__); \
35 		assert(ibprintf >= 0 && ibprintf < (int)sizeof buf);    \
36 	} while (0)
37 
38 struct lump {
39 	off_t			start;
40 	off_t			len;
41 	int			state;
42 	TAILQ_ENTRY(lump)	list;
43 };
44 
45 struct period {
46 	time_t			t0;
47 	time_t			t1;
48 	char			str[20];
49 	off_t			bytes_read;
50 	TAILQ_ENTRY(period)	list;
51 };
52 TAILQ_HEAD(period_head, period);
53 
54 static volatile sig_atomic_t aborting = 0;
55 static int verbose = 0;
56 static size_t bigsize = 1024 * 1024;
57 static size_t medsize;
58 static size_t minsize = 512;
59 static off_t tot_size;
60 static off_t done_size;
61 static char *input;
62 static char *wworklist = NULL;
63 static char *rworklist = NULL;
64 static const char *unreadable_pattern = "_UNREAD_";
65 static const int write_errors_are_fatal = 1;
66 static int fdr, fdw;
67 
68 static TAILQ_HEAD(, lump) lumps = TAILQ_HEAD_INITIALIZER(lumps);
69 static struct period_head minute = TAILQ_HEAD_INITIALIZER(minute);
70 static struct period_head quarter = TAILQ_HEAD_INITIALIZER(quarter);
71 static struct period_head hour = TAILQ_HEAD_INITIALIZER(quarter);
72 static struct period_head day = TAILQ_HEAD_INITIALIZER(quarter);
73 
74 /**********************************************************************/
75 
76 static void
77 report_good_read2(time_t now, size_t bytes, struct period_head *ph, time_t dt)
78 {
79 	struct period *pp;
80 	const char *fmt;
81 	struct tm tm1;
82 
83 	pp = TAILQ_FIRST(ph);
84 	if (pp == NULL || pp->t1 < now) {
85 		pp = calloc(sizeof *pp, 1L);
86 		assert(pp != NULL);
87 		pp->t0 = (now / dt) * dt;
88 		pp->t1 = (now / dt + 1) * dt;
89 		assert(localtime_r(&pp->t0, &tm1) != NULL);
90 		if (dt < 86400)
91 			fmt = "%H:%M";
92 		else
93 			fmt = "%d%b";
94 		assert(strftime(pp->str, sizeof pp->str, fmt, &tm1) != 0);
95 		TAILQ_INSERT_HEAD(ph, pp, list);
96 	}
97 	pp->bytes_read += bytes;
98 }
99 
100 static void
101 report_good_read(time_t now, size_t bytes)
102 {
103 
104 	report_good_read2(now, bytes, &minute, 60L);
105 	report_good_read2(now, bytes, &quarter, 900L);
106 	report_good_read2(now, bytes, &hour, 3600L);
107 	report_good_read2(now, bytes, &day, 86400L);
108 }
109 
110 static void
111 report_one_period(const char *period, struct period_head *ph)
112 {
113 	struct period *pp;
114 	int n;
115 
116 	n = 0;
117 	printf("%s \xe2\x94\x82", period);
118 	TAILQ_FOREACH(pp, ph, list) {
119 		if (n == 3) {
120 			TAILQ_REMOVE(ph, pp, list);
121 			free(pp);
122 			break;
123 		}
124 		if (n++)
125 			printf("  \xe2\x94\x82");
126 		printf("  %s %14jd", pp->str, pp->bytes_read);
127 	}
128 	for (; n < 3; n++) {
129 		printf("  \xe2\x94\x82");
130 		printf("  %5s %14s", "", "");
131 	}
132 	printf("\x1b[K\n");
133 }
134 
135 static void
136 report_periods(void)
137 {
138 	report_one_period("1m ", &minute);
139 	report_one_period("15m", &quarter);
140 	report_one_period("1h ", &hour);
141 	report_one_period("1d ", &day);
142 }
143 
144 /**********************************************************************/
145 
146 static void
147 set_verbose(void)
148 {
149 	struct winsize wsz;
150 
151 	if (!isatty(STDIN_FILENO) || ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz))
152 		return;
153 	verbose = 1;
154 }
155 
156 static void
157 report_header(int eol)
158 {
159 	printf("%13s %7s %13s %5s %13s %13s %9s",
160 	    "start",
161 	    "size",
162 	    "block-len",
163 	    "pass",
164 	    "done",
165 	    "remaining",
166 	    "% done");
167 	if (eol)
168 		printf("\x1b[K");
169 	putchar('\n');
170 }
171 
172 #define REPORTWID 79
173 
174 static void
175 report_hline(const char *how)
176 {
177 	int j;
178 
179 	for (j = 0; j < REPORTWID; j++) {
180 		if (how && (j == 4 || j == 29 || j == 54)) {
181 			printf("%s", how);
182 		} else {
183 			printf("\xe2\x94\x80");
184 		}
185 	}
186 	printf("\x1b[K\n");
187 }
188 
189 static off_t hist[REPORTWID];
190 static off_t last_done = -1;
191 
192 static void
193 report_histogram(const struct lump *lp)
194 {
195 	off_t j, bucket, fp, fe, k, now;
196 	double a;
197 	struct lump *lp2;
198 
199 	bucket = tot_size / REPORTWID;
200 	if (tot_size > bucket * REPORTWID)
201 		bucket += 1;
202 	if (done_size != last_done) {
203 		memset(hist, 0, sizeof hist);
204 		TAILQ_FOREACH(lp2, &lumps, list) {
205 			fp = lp2->start;
206 			fe = lp2->start + lp2->len;
207 			for (j = fp / bucket; fp < fe; j++) {
208 				k = (j + 1) * bucket;
209 				if (k > fe)
210 					k = fe;
211 				k -= fp;
212 				hist[j] += k;
213 				fp += k;
214 			}
215 		}
216 		last_done = done_size;
217 	}
218 	now = lp->start / bucket;
219 	for (j = 0; j < REPORTWID; j++) {
220 		a = round(8 * (double)hist[j] / bucket);
221 		assert (a >= 0 && a < 9);
222 		if (a == 0 && hist[j])
223 			a = 1;
224 		if (j == now)
225 			printf("\x1b[31m");
226 		if (a == 0) {
227 			putchar(' ');
228 		} else {
229 			putchar(0xe2);
230 			putchar(0x96);
231 			putchar(0x80 + (int)a);
232 		}
233 		if (j == now)
234 			printf("\x1b[0m");
235 	}
236 	putchar('\n');
237 }
238 
239 static void
240 report(const struct lump *lp, size_t sz)
241 {
242 	struct winsize wsz;
243 	int j;
244 
245 	assert(lp != NULL);
246 
247 	if (verbose) {
248 		printf("\x1b[H%s\x1b[K\n", input);
249 		report_header(1);
250 	} else {
251 		putchar('\r');
252 	}
253 
254 	printf("%13jd %7zu %13jd %5d %13jd %13jd %9.4f",
255 	    (intmax_t)lp->start,
256 	    sz,
257 	    (intmax_t)lp->len,
258 	    lp->state,
259 	    (intmax_t)done_size,
260 	    (intmax_t)(tot_size - done_size),
261 	    100*(double)done_size/(double)tot_size
262 	);
263 
264 	if (verbose) {
265 		printf("\x1b[K\n");
266 		report_hline(NULL);
267 		report_histogram(lp);
268 		if (TAILQ_EMPTY(&minute)) {
269 			report_hline(NULL);
270 		} else {
271 			report_hline("\xe2\x94\xac");
272 			report_periods();
273 			report_hline("\xe2\x94\xb4");
274 		}
275 		j = ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz);
276 		if (!j)
277 			printf("\x1b[%d;1H", wsz.ws_row);
278 	}
279 	fflush(stdout);
280 }
281 
282 /**********************************************************************/
283 
284 static void
285 new_lump(off_t start, off_t len, int state)
286 {
287 	struct lump *lp;
288 
289 	lp = malloc(sizeof *lp);
290 	if (lp == NULL)
291 		err(1, "Malloc failed");
292 	lp->start = start;
293 	lp->len = len;
294 	lp->state = state;
295 	TAILQ_INSERT_TAIL(&lumps, lp, list);
296 }
297 
298 /**********************************************************************
299  * Save the worklist if -w was given
300  */
301 
302 static void
303 save_worklist(void)
304 {
305 	FILE *file;
306 	struct lump *llp;
307 	char buf[PATH_MAX];
308 
309 	if (fdw >= 0 && fdatasync(fdw))
310 		err(1, "Write error, probably disk full");
311 
312 	if (wworklist != NULL) {
313 		bprintf(buf, "%s.tmp", wworklist);
314 		(void)fprintf(stderr, "\nSaving worklist ...");
315 		(void)fflush(stderr);
316 
317 		file = fopen(buf, "w");
318 		if (file == NULL)
319 			err(1, "Error opening file %s", buf);
320 
321 		TAILQ_FOREACH(llp, &lumps, list)
322 			fprintf(file, "%jd %jd %d\n",
323 			    (intmax_t)llp->start, (intmax_t)llp->len,
324 			    llp->state);
325 		(void)fflush(file);
326 		if (ferror(file) || fdatasync(fileno(file)) || fclose(file))
327 			err(1, "Error writing file %s", buf);
328 		if (rename(buf, wworklist))
329 			err(1, "Error renaming %s to %s", buf, wworklist);
330 		(void)fprintf(stderr, " done.\n");
331 	}
332 }
333 
334 /* Read the worklist if -r was given */
335 static off_t
336 read_worklist(off_t t)
337 {
338 	off_t s, l, d;
339 	int state, lines;
340 	FILE *file;
341 
342 	(void)fprintf(stderr, "Reading worklist ...");
343 	(void)fflush(stderr);
344 	file = fopen(rworklist, "r");
345 	if (file == NULL)
346 		err(1, "Error opening file %s", rworklist);
347 
348 	lines = 0;
349 	d = t;
350 	for (;;) {
351 		++lines;
352 		if (3 != fscanf(file, "%jd %jd %d\n", &s, &l, &state)) {
353 			if (!feof(file))
354 				err(1, "Error parsing file %s at line %d",
355 				    rworklist, lines);
356 			else
357 				break;
358 		}
359 		new_lump(s, l, state);
360 		d -= l;
361 	}
362 	if (fclose(file))
363 		err(1, "Error closing file %s", rworklist);
364 	(void)fprintf(stderr, " done.\n");
365 	/*
366 	 * Return the number of bytes already read
367 	 * (at least not in worklist).
368 	 */
369 	return (d);
370 }
371 
372 /**********************************************************************/
373 
374 static void
375 write_buf(int fd, const void *buf, ssize_t len, off_t where)
376 {
377 	ssize_t i;
378 
379 	i = pwrite(fd, buf, len, where);
380 	if (i == len)
381 		return;
382 
383 	printf("\nWrite error at %jd/%zu\n\t%s\n",
384 	    where, i, strerror(errno));
385 	save_worklist();
386 	if (write_errors_are_fatal)
387 		exit(3);
388 }
389 
390 static void
391 fill_buf(char *buf, ssize_t len, const char *pattern)
392 {
393 	ssize_t sz = strlen(pattern);
394 	ssize_t i, j;
395 
396 	for (i = 0; i < len; i += sz) {
397 		j = len - i;
398 		if (j > sz)
399 			j = sz;
400 		memcpy(buf + i, pattern, j);
401 	}
402 }
403 
404 /**********************************************************************/
405 
406 static void
407 usage(void)
408 {
409 	(void)fprintf(stderr, "usage: recoverdisk [-b bigsize] [-r readlist] "
410 	    "[-s interval] [-w writelist] source [destination]\n");
411 	/* XXX update */
412 	exit(1);
413 }
414 
415 static void
416 sighandler(__unused int sig)
417 {
418 
419 	aborting = 1;
420 }
421 
422 int
423 main(int argc, char * const argv[])
424 {
425 	int ch;
426 	size_t sz, j;
427 	int error;
428 	char *buf;
429 	u_int sectorsize;
430 	off_t stripesize;
431 	time_t t1, t2;
432 	struct stat sb;
433 	u_int n, snapshot = 60;
434 	static struct lump *lp;
435 
436 	while ((ch = getopt(argc, argv, "b:r:w:s:u:v")) != -1) {
437 		switch (ch) {
438 		case 'b':
439 			bigsize = strtoul(optarg, NULL, 0);
440 			break;
441 		case 'r':
442 			rworklist = strdup(optarg);
443 			if (rworklist == NULL)
444 				err(1, "Cannot allocate enough memory");
445 			break;
446 		case 's':
447 			snapshot = strtoul(optarg, NULL, 0);
448 			break;
449 		case 'u':
450 			unreadable_pattern = optarg;
451 			break;
452 		case 'v':
453 			set_verbose();
454 			break;
455 		case 'w':
456 			wworklist = strdup(optarg);
457 			if (wworklist == NULL)
458 				err(1, "Cannot allocate enough memory");
459 			break;
460 		default:
461 			usage();
462 			/* NOTREACHED */
463 		}
464 	}
465 	argc -= optind;
466 	argv += optind;
467 
468 	if (argc < 1 || argc > 2)
469 		usage();
470 
471 	input = argv[0];
472 	fdr = open(argv[0], O_RDONLY);
473 	if (fdr < 0)
474 		err(1, "Cannot open read descriptor %s", argv[0]);
475 
476 	error = fstat(fdr, &sb);
477 	if (error < 0)
478 		err(1, "fstat failed");
479 	if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) {
480 		error = ioctl(fdr, DIOCGSECTORSIZE, &sectorsize);
481 		if (error < 0)
482 			err(1, "DIOCGSECTORSIZE failed");
483 
484 		error = ioctl(fdr, DIOCGSTRIPESIZE, &stripesize);
485 		if (error == 0 && stripesize > sectorsize)
486 			sectorsize = stripesize;
487 
488 		minsize = sectorsize;
489 		bigsize = rounddown(bigsize, sectorsize);
490 
491 		error = ioctl(fdr, DIOCGMEDIASIZE, &tot_size);
492 		if (error < 0)
493 			err(1, "DIOCGMEDIASIZE failed");
494 	} else {
495 		tot_size = sb.st_size;
496 	}
497 
498 	if (bigsize < minsize)
499 		bigsize = minsize;
500 
501 	for (ch = 0; (bigsize >> ch) > minsize; ch++)
502 		continue;
503 	medsize = bigsize >> (ch / 2);
504 	medsize = rounddown(medsize, minsize);
505 
506 	fprintf(stderr, "Bigsize = %zu, medsize = %zu, minsize = %zu\n",
507 	    bigsize, medsize, minsize);
508 
509 	buf = malloc(bigsize);
510 	if (buf == NULL)
511 		err(1, "Cannot allocate %zu bytes buffer", bigsize);
512 
513 	if (argc > 1) {
514 		fdw = open(argv[1], O_WRONLY | O_CREAT, DEFFILEMODE);
515 		if (fdw < 0)
516 			err(1, "Cannot open write descriptor %s", argv[1]);
517 		if (ftruncate(fdw, tot_size) < 0)
518 			err(1, "Cannot truncate output %s to %jd bytes",
519 			    argv[1], (intmax_t)tot_size);
520 	} else
521 		fdw = -1;
522 
523 	if (rworklist != NULL) {
524 		done_size = read_worklist(tot_size);
525 	} else {
526 		new_lump(0, tot_size, 0);
527 		done_size = 0;
528 	}
529 	if (wworklist != NULL)
530 		signal(SIGINT, sighandler);
531 
532 	t1 = time(NULL);
533 	sz = 0;
534 	if (!verbose)
535 		report_header(0);
536 	else
537 		printf("\x1b[2J");
538 	n = 0;
539 	for (;;) {
540 		lp = TAILQ_FIRST(&lumps);
541 		if (lp == NULL)
542 			break;
543 		while (lp->len > 0) {
544 
545 			if (lp->state == 0)
546 				sz = MIN(lp->len, (off_t)bigsize);
547 			else if (lp->state == 1)
548 				sz = MIN(lp->len, (off_t)medsize);
549 			else
550 				sz = MIN(lp->len, (off_t)minsize);
551 			assert(sz != 0);
552 
553 			t2 = time(NULL);
554 			if (t1 != t2 || lp->len < (off_t)bigsize) {
555 				t1 = t2;
556 				if (++n == snapshot) {
557 					save_worklist();
558 					n = 0;
559 				}
560 				report(lp, sz);
561 			}
562 
563 			j = pread(fdr, buf, sz, lp->start);
564 #if 0
565 if (!(random() & 0xf)) {
566 	j = -1;
567 	errno = EIO;
568 }
569 #endif
570 			if (j == sz) {
571 				done_size += sz;
572 				if (fdw >= 0)
573 					write_buf(fdw, buf, sz, lp->start);
574 				lp->start += sz;
575 				lp->len -= sz;
576 				if (verbose && lp->state > 2)
577 					report_good_read(t2, sz);
578 				continue;
579 			}
580 			error = errno;
581 
582 			printf("%jd %zu %d read error (%s)\n",
583 			    lp->start, sz, lp->state, strerror(error));
584 			if (verbose)
585 				report(lp, sz);
586 			if (fdw >= 0 && strlen(unreadable_pattern)) {
587 				fill_buf(buf, sz, unreadable_pattern);
588 				write_buf(fdw, buf, sz, lp->start);
589 			}
590 			new_lump(lp->start, sz, lp->state + 1);
591 			lp->start += sz;
592 			lp->len -= sz;
593 			if (error == EINVAL) {
594 				printf("Try with -b 131072 or lower ?\n");
595 				aborting = 1;
596 				break;
597 			}
598 			if (error == ENXIO) {
599 				printf("Input device probably detached...\n");
600 				aborting = 1;
601 				break;
602 			}
603 		}
604 		if (aborting)
605 			save_worklist();
606 		if (aborting || !TAILQ_NEXT(lp, list))
607 			report(lp, sz);
608 		if (aborting)
609 			break;
610 		assert(lp->len == 0);
611 		TAILQ_REMOVE(&lumps, lp, list);
612 		free(lp);
613 	}
614 	printf("%s", aborting ? "Aborted\n" : "Completed\n");
615 	free(buf);
616 	return (0);
617 }
618