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