xref: /freebsd/sbin/recoverdisk/recoverdisk.c (revision e88b7dcac8701b6d925b8c7c66eada5143c37e16)
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 
12 #include <sys/param.h>
13 #include <sys/queue.h>
14 #include <sys/disk.h>
15 #include <sys/stat.h>
16 
17 #include <assert.h>
18 #include <err.h>
19 #include <errno.h>
20 #include <math.h>
21 #include <fcntl.h>
22 #include <signal.h>
23 #include <stdint.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <termios.h>
28 #include <time.h>
29 #include <unistd.h>
30 
31 /*
32  * This is a compromise between speed and wasted effort
33  */
34 #define COMPROMISE_SIZE		(128<<10)
35 
36 struct lump {
37 	uint64_t		start;
38 	uint64_t		len;
39 	unsigned		pass;
40 	TAILQ_ENTRY(lump)	list;
41 };
42 
43 struct period {
44 	time_t			t0;
45 	time_t			t1;
46 	char			str[20];
47 	uint64_t		bytes_read;
48 	TAILQ_ENTRY(period)	list;
49 };
50 TAILQ_HEAD(period_head, period);
51 
52 static volatile sig_atomic_t aborting = 0;
53 static int verbose = 0;
54 static uint64_t big_read;
55 static uint64_t medium_read;
56 static uint64_t small_read;
57 static uint64_t total_size;
58 static uint64_t done_size;
59 static uint64_t wasted_size;
60 static char *input;
61 static char *write_worklist_file = NULL;
62 static char *read_worklist_file = NULL;
63 static const char *unreadable_pattern = "_UNREAD_";
64 static int write_errors_are_fatal = 1;
65 static int read_fd, write_fd;
66 static FILE *log_file = NULL;
67 static char *work_buf;
68 static char *pattern_buf;
69 static double error_pause;
70 static double interval;
71 
72 static unsigned nlumps;
73 static double n_reads, n_good_reads;
74 static time_t t_first;
75 static TAILQ_HEAD(, lump) lumps = TAILQ_HEAD_INITIALIZER(lumps);
76 static struct period_head minute = TAILQ_HEAD_INITIALIZER(minute);
77 static struct period_head quarter = TAILQ_HEAD_INITIALIZER(quarter);
78 static struct period_head hour = TAILQ_HEAD_INITIALIZER(quarter);
79 static struct period_head day = TAILQ_HEAD_INITIALIZER(quarter);
80 
81 /**********************************************************************/
82 
83 static void
account_good_read_period(time_t now,uint64_t bytes,struct period_head * ph,time_t dt)84 account_good_read_period(time_t now, uint64_t bytes,
85     struct period_head *ph, time_t dt)
86 {
87 	struct period *pp;
88 	const char *fmt;
89 	struct tm tm1;
90 
91 	pp = TAILQ_FIRST(ph);
92 	if (pp == NULL || pp->t1 < now) {
93 		pp = calloc(1UL, sizeof(*pp));
94 		assert(pp != NULL);
95 		pp->t0 = (now / dt) * dt;
96 		pp->t1 = (now / dt + 1) * dt;
97 		assert(localtime_r(&pp->t0, &tm1) != NULL);
98 		if (dt < 86400)
99 			fmt = "%H:%M";
100 		else
101 			fmt = "%d%b";
102 		assert(strftime(pp->str, sizeof pp->str, fmt, &tm1) != 0);
103 		TAILQ_INSERT_HEAD(ph, pp, list);
104 	}
105 	pp->bytes_read += bytes;
106 }
107 
108 static void
account_good_read(time_t now,uint64_t bytes)109 account_good_read(time_t now, uint64_t bytes)
110 {
111 
112 	account_good_read_period(now, bytes, &minute, 60L);
113 	account_good_read_period(now, bytes, &quarter, 900L);
114 	account_good_read_period(now, bytes, &hour, 3600L);
115 	account_good_read_period(now, bytes, &day, 86400L);
116 }
117 
118 static void
report_one_period(const char * period,struct period_head * ph)119 report_one_period(const char *period, struct period_head *ph)
120 {
121 	struct period *pp;
122 	int n;
123 
124 	n = 0;
125 	printf("%s ", period);
126 	TAILQ_FOREACH(pp, ph, list) {
127 		if (++n == 4) {
128 			TAILQ_REMOVE(ph, pp, list);
129 			free(pp);
130 			break;
131 		}
132 		printf("\xe2\x94\x82  %s %14ju  ",
133 		    pp->str, (uintmax_t)pp->bytes_read);
134 	}
135 	for (; n < 3; n++) {
136 		printf("\xe2\x94\x82  %5s %14s  ", "", "");
137 	}
138 	printf("\x1b[K\n");
139 }
140 
141 static void
report_periods(void)142 report_periods(void)
143 {
144 	report_one_period("1m ", &minute);
145 	report_one_period("15m", &quarter);
146 	report_one_period("1h ", &hour);
147 	report_one_period("1d ", &day);
148 }
149 
150 /**********************************************************************/
151 
152 static void
set_verbose(void)153 set_verbose(void)
154 {
155 
156 	verbose = 1;
157 }
158 
159 static void
report_header(const char * term)160 report_header(const char *term)
161 {
162 	printf("%13s %7s %13s %5s %13s %13s %9s%s",
163 	    "start",
164 	    "size",
165 	    "block-len",
166 	    "pass",
167 	    "done",
168 	    "remaining",
169 	    "% done",
170 	    term
171 	);
172 }
173 
174 #define REPORTWID 79
175 
176 static void
report_hline(const char * how)177 report_hline(const char *how)
178 {
179 	int j;
180 
181 	for (j = 0; j < REPORTWID; j++) {
182 		if (how && (j == 4 || j == 29 || j == 54)) {
183 			printf("%s", how);
184 		} else {
185 			printf("\xe2\x94\x80");
186 		}
187 	}
188 	printf("\x1b[K\n");
189 }
190 
191 static uint64_t hist[REPORTWID];
192 static uint64_t prev_done = ~0UL;
193 
194 static void
report_histogram(uint64_t start)195 report_histogram(uint64_t start)
196 {
197 	uint64_t j, bucket, fp, fe, k, now;
198 	double a;
199 	struct lump *lp2;
200 
201 	bucket = total_size / REPORTWID;
202 	if (total_size > bucket * REPORTWID)
203 		bucket += 1;
204 	if (done_size != prev_done) {
205 		memset(hist, 0, sizeof hist);
206 		TAILQ_FOREACH(lp2, &lumps, list) {
207 			fp = lp2->start;
208 			fe = lp2->start + lp2->len;
209 			for (j = fp / bucket; fp < fe; j++) {
210 				k = (j + 1) * bucket;
211 				if (k > fe)
212 					k = fe;
213 				k -= fp;
214 				hist[j] += k;
215 				fp += k;
216 			}
217 		}
218 		prev_done = done_size;
219 	}
220 	now = start / bucket;
221 	for (j = 0; j < REPORTWID; j++) {
222 		a = round(8 * (double)hist[j] / bucket);
223 		assert (a >= 0 && a < 9);
224 		if (a == 0 && hist[j])
225 			a = 1;
226 		if (j == now)
227 			printf("\x1b[31m");
228 		if (a == 0) {
229 			putchar(' ');
230 		} else {
231 			putchar(0xe2);
232 			putchar(0x96);
233 			putchar(0x80 + (char)a);
234 		}
235 		if (j == now)
236 			printf("\x1b[0m");
237 	}
238 	putchar('\n');
239 }
240 
241 static void
report(uint64_t sz)242 report(uint64_t sz)
243 {
244 	struct winsize wsz;
245 	const struct lump *lp = TAILQ_FIRST(&lumps);
246 	int j;
247 	unsigned pass = 0;
248 	uintmax_t start = 0, length = 0;
249 	time_t t_now = time(NULL);
250 
251 	if (lp != NULL) {
252 		pass = lp->pass;
253 		start = lp->start;
254 		length = lp->len;
255 	}
256 
257 	if (verbose) {
258 		printf("\x1b[H%s\x1b[K\n", input);
259 		report_header("\x1b[K\n");
260 	}
261 
262 	printf("%13ju %7ju %13ju %5u %13ju %13ju %9.4f",
263 	    start,
264 	    (uintmax_t)sz,
265 	    length,
266 	    pass,
267 	    (uintmax_t)done_size,
268 	    (uintmax_t)(total_size - done_size),
269 	    100*(double)done_size/(double)total_size
270 	);
271 
272 	if (verbose) {
273 		printf("\x1b[K\n");
274 		report_hline(NULL);
275 		report_histogram(start);
276 		if (TAILQ_EMPTY(&minute)) {
277 			report_hline(NULL);
278 		} else {
279 			report_hline("\xe2\x94\xac");
280 			report_periods();
281 			report_hline("\xe2\x94\xb4");
282 		}
283 		printf("Missing: %u", nlumps);
284 		printf("  Success: %.0f/%.0f =", n_good_reads, n_reads);
285 		printf(" %.4f%%", 100 * n_good_reads / n_reads);
286 		printf("  Duration: %.3fs", (t_now - t_first) / n_reads);
287 		printf("\x1b[K\n");
288 		report_hline(NULL);
289 		j = ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz);
290 		if (!j)
291 			printf("\x1b[%d;1H", wsz.ws_row);
292 	} else {
293 		printf("\n");
294 	}
295 }
296 
297 /**********************************************************************/
298 
299 static void
new_lump(uint64_t start,uint64_t len,unsigned pass)300 new_lump(uint64_t start, uint64_t len, unsigned pass)
301 {
302 	struct lump *lp;
303 
304 	assert(len > 0);
305 	lp = malloc(sizeof *lp);
306 	if (lp == NULL)
307 		err(1, "Malloc failed");
308 	lp->start = start;
309 	lp->len = len;
310 	lp->pass = pass;
311 	TAILQ_INSERT_TAIL(&lumps, lp, list);
312 	nlumps += 1;
313 }
314 
315 /**********************************************************************
316  * Save the worklist if -w was given
317  */
318 
319 static void
save_worklist(void)320 save_worklist(void)
321 {
322 	FILE *file;
323 	struct lump *llp;
324 	char buf[PATH_MAX];
325 
326 	if (write_fd >= 0 && fdatasync(write_fd))
327 		err(1, "Write error, probably disk full");
328 
329 	if (write_worklist_file != NULL) {
330 		snprintf(buf, sizeof(buf), "%s.tmp", write_worklist_file);
331 		fprintf(stderr, "\nSaving worklist ...");
332 
333 		file = fopen(buf, "w");
334 		if (file == NULL)
335 			err(1, "Error opening file %s", buf);
336 
337 		TAILQ_FOREACH(llp, &lumps, list) {
338 			assert (llp->len > 0);
339 			fprintf(file, "%ju %ju %u\n",
340 			    (uintmax_t)llp->start,
341 			    (uintmax_t)llp->len,
342 			    llp->pass);
343 		}
344 		fflush(file);
345 		if (ferror(file) || fdatasync(fileno(file)) || fclose(file))
346 			err(1, "Error writing file %s", buf);
347 		if (rename(buf, write_worklist_file))
348 			err(1, "Error renaming %s to %s",
349 			    buf, write_worklist_file);
350 		fprintf(stderr, " done.\n");
351 	}
352 }
353 
354 /* Read the worklist if -r was given */
355 static uint64_t
read_worklist(void)356 read_worklist(void)
357 {
358 	uintmax_t start, length;
359 	uint64_t missing = 0;
360 	unsigned pass, lines;
361 	FILE *file;
362 
363 	fprintf(stderr, "Reading worklist ...");
364 	file = fopen(read_worklist_file, "r");
365 	if (file == NULL)
366 		err(1, "Error opening file %s", read_worklist_file);
367 
368 	lines = 0;
369 	for (;;) {
370 		++lines;
371 		if (3 != fscanf(file, "%ju %ju %u\n", &start, &length, &pass)) {
372 			if (!feof(file))
373 				err(1, "Error parsing file %s at line %u",
374 				    read_worklist_file, lines);
375 			else
376 				break;
377 		}
378 		if (length > 0) {
379 			new_lump(start, length, pass);
380 			missing += length;
381 		}
382 	}
383 	if (fclose(file))
384 		err(1, "Error closing file %s", read_worklist_file);
385 	fprintf(stderr, " done.\n");
386 	/*
387 	 * Return the number of bytes outstanding
388 	 */
389 	return (missing);
390 }
391 
392 /**********************************************************************/
393 
394 static void
write_buf(int fd,const void * buf,uint64_t length,uint64_t where)395 write_buf(int fd, const void *buf, uint64_t length, uint64_t where)
396 {
397 	int64_t i;
398 
399 	i = pwrite(fd, buf, length, (off_t)where);
400 	if (i > 0 && (uint64_t)i == length)
401 		return;
402 
403 	printf("\nWrite error at %ju/%ju: %jd (%s)\n",
404 	    (uintmax_t)where,
405 	    (uintmax_t)length,
406 	    (intmax_t)i, strerror(errno));
407 	save_worklist();
408 	if (write_errors_are_fatal)
409 		exit(3);
410 }
411 
412 static void
fill_buf(char * buf,int64_t len,const char * pattern)413 fill_buf(char *buf, int64_t len, const char *pattern)
414 {
415 	int64_t sz = strlen(pattern);
416 	int64_t i;
417 
418 	for (i = 0; i < len; i += sz) {
419 		memcpy(buf + i, pattern, MIN(len - i, sz));
420 	}
421 }
422 
423 /**********************************************************************/
424 
425 static void
usage(void)426 usage(void)
427 {
428 	fprintf(stderr, "usage: recoverdisk "
429 	    "[-b big_read] [-i interval ] [-r readlist] "
430 	    "[-s interval] [-w writelist] source [destination]\n");
431 	/* XXX update */
432 	exit(1);
433 }
434 
435 static void
sighandler(int sig)436 sighandler(int sig)
437 {
438 
439 	(void)sig;
440 	aborting = 1;
441 }
442 
443 /**********************************************************************/
444 
445 static int64_t
attempt_one_lump(time_t t_now)446 attempt_one_lump(time_t t_now)
447 {
448 	struct lump *lp;
449 	uint64_t sz;
450 	int64_t retval;
451 	int error;
452 
453 	lp = TAILQ_FIRST(&lumps);
454 	if (lp == NULL)
455 		return(0);
456 
457 	if (lp->pass == 0) {
458 		sz = MIN(lp->len, big_read);
459 	} else if (lp->pass == 1) {
460 		sz = MIN(lp->len, medium_read);
461 	} else {
462 		sz = MIN(lp->len, small_read);
463 	}
464 
465 	assert(sz != 0);
466 
467 	n_reads += 1;
468 	retval = pread(read_fd, work_buf, sz, lp->start);
469 
470 #if 0 /* enable this when testing */
471 	if (!(random() & 0xf)) {
472 		retval = -1;
473 		errno = EIO;
474 		usleep(20000);
475 	} else {
476 		usleep(2000);
477 	}
478 #endif
479 
480 	error = errno;
481 	if (retval > 0) {
482 		n_good_reads += 1;
483 		sz = retval;
484 		done_size += sz;
485 		if (write_fd >= 0) {
486 			write_buf(write_fd, work_buf, sz, lp->start);
487 		}
488 		if (log_file != NULL) {
489 			fprintf(log_file, "%jd %ju %ju\n",
490 			    (intmax_t)t_now,
491 			    (uintmax_t)lp->start,
492 			    (uintmax_t)sz
493 			);
494 			fflush(log_file);
495 		}
496 	} else {
497 		wasted_size += sz;
498 		printf("%14ju %7ju read error %d: (%s)",
499 		    (uintmax_t)lp->start,
500 		    (uintmax_t)sz, error, strerror(error));
501 		if (error_pause > 1) {
502 			printf(" (Pausing %g s)", error_pause);
503 		}
504 		printf("\n");
505 
506 		if (write_fd >= 0 && pattern_buf != NULL) {
507 			write_buf(write_fd, pattern_buf, sz, lp->start);
508 		}
509 		new_lump(lp->start, sz, lp->pass + 1);
510 		retval = -sz;
511 	}
512 	lp->start += sz;
513 	lp->len -= sz;
514 	if (lp->len == 0) {
515 		TAILQ_REMOVE(&lumps, lp, list);
516 		nlumps -= 1;
517 		free(lp);
518 	}
519 	errno = error;
520 	return (retval);
521 }
522 
523 
524 /**********************************************************************/
525 
526 static void
determine_total_size(void)527 determine_total_size(void)
528 {
529 	struct stat sb;
530 	int error;
531 
532 	if (total_size != 0)
533 		return;
534 
535 	error = fstat(read_fd, &sb);
536 	if (error < 0)
537 		err(1, "fstat failed");
538 
539 	if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) {
540 #ifdef DIOCGMEDIASIZE
541 		off_t mediasize;
542 		error = ioctl(read_fd, DIOCGMEDIASIZE, &mediasize);
543 		if (error == 0 && mediasize > 0) {
544 			total_size = mediasize;
545 			printf("# Got total_size from DIOCGMEDIASIZE: %ju\n",
546 			    (uintmax_t)total_size);
547 			return;
548 		}
549 #endif
550 	} else if (S_ISREG(sb.st_mode) && sb.st_size > 0) {
551 		total_size = sb.st_size;
552 		printf("# Got total_size from stat(2): %ju\n",
553 		    (uintmax_t)total_size);
554 		return;
555 	} else {
556 		errx(1, "Input must be device or regular file");
557 	}
558 	fprintf(stderr, "Specify total size with -t option\n");
559 	exit(1);
560 }
561 
562 static void
determine_read_sizes(void)563 determine_read_sizes(void)
564 {
565 	int error;
566 	u_int sectorsize;
567 	off_t stripesize;
568 
569 #ifdef DIOCGSECTORSIZE
570 	if (small_read == 0) {
571 		error = ioctl(read_fd, DIOCGSECTORSIZE, &sectorsize);
572 		if (error >= 0 && sectorsize > 0) {
573 			small_read = sectorsize;
574 			printf("# Got small_read from DIOCGSECTORSIZE: %ju\n",
575 			    (uintmax_t)small_read
576 			);
577 		}
578 	}
579 #endif
580 
581 	if (small_read == 0) {
582 		small_read = 512;
583 		printf("# Defaulting small_read to %ju\n", (uintmax_t)small_read);
584 	}
585 
586 	if (medium_read && (medium_read % small_read)) {
587 		errx(1,
588 		    "medium_read (%ju) is not a multiple of small_read (%ju)\n",
589 		    (uintmax_t)medium_read, (uintmax_t)small_read
590 		);
591 	}
592 
593 	if (big_read != 0 && (big_read % small_read)) {
594 		errx(1,
595 		    "big_read (%ju) is not a multiple of small_read (%ju)\n",
596 		    (uintmax_t)big_read, (uintmax_t)small_read
597 		);
598 	}
599 
600 #ifdef DIOCGSTRIPESIZE
601 	if (medium_read == 0) {
602 		error = ioctl(read_fd, DIOCGSTRIPESIZE, &stripesize);
603 		if (error < 0 || stripesize <= 0) {
604 			// nope
605 		} else if ((uint64_t)stripesize < small_read) {
606 			// nope
607 		} else if (stripesize % small_read) {
608 			// nope
609 		} else if (stripesize <= COMPROMISE_SIZE) {
610 			medium_read = stripesize;
611 			printf("# Got medium_read from DIOCGSTRIPESIZE: %ju\n",
612 			    (uintmax_t)medium_read
613 			);
614 		}
615 	}
616 #endif
617 
618 #if defined(DIOCGFWSECTORS) && defined(DIOCGFWHEADS)
619 	if (medium_read == 0) {
620 		u_int fwsectors = 0, fwheads = 0;
621 		error = ioctl(read_fd, DIOCGFWSECTORS, &fwsectors);
622 		if (error)
623 			fwsectors = 0;
624 		error = ioctl(read_fd, DIOCGFWHEADS, &fwheads);
625 		if (error)
626 			fwheads = 0;
627 		if (fwsectors * fwheads * small_read <= COMPROMISE_SIZE) {
628 			medium_read = fwsectors * fwheads * small_read;
629 			printf(
630 			    "# Got medium_read from DIOCGFW{SECTORS*HEADS}: %ju\n",
631 			    (uintmax_t)medium_read
632 			);
633 		} else if (fwsectors * small_read <= COMPROMISE_SIZE) {
634 			medium_read = fwsectors * small_read;
635 			printf(
636 			    "# Got medium_read from DIOCGFWSECTORS: %ju\n",
637 			    (uintmax_t)medium_read
638 			);
639 		}
640 	}
641 #endif
642 
643 	if (big_read == 0 && medium_read != 0) {
644 		if (medium_read * 2 > COMPROMISE_SIZE) {
645 			big_read = medium_read;
646 			medium_read = 0;
647 		} else {
648 			big_read = COMPROMISE_SIZE;
649 			big_read -= big_read % medium_read;
650 		}
651 		printf("# Got big_read from medium_read: %ju\n",
652 		    (uintmax_t)big_read
653 		);
654 	}
655 
656 	if (big_read == 0) {
657 		big_read = COMPROMISE_SIZE;
658 		big_read -= big_read % small_read;
659 		printf("# Defaulting big_read to %ju\n",
660 		    (uintmax_t)big_read
661 		);
662 	}
663 
664 	if (medium_read >= big_read)
665 		medium_read = 0;
666 
667 	if (medium_read == 0) {
668 		/*
669 		 * We do not want to go directly to single sectors, but
670 		 * we also dont want to waste time doing multi-sector
671 		 * reads with high failure probability.
672 		 */
673 		uint64_t h = big_read;
674 		uint64_t l = small_read;
675 		while (h > l) {
676 			h >>= 2;
677 			l <<= 1;
678 		}
679 		medium_read = h;
680 		printf("# Got medium_read from small_read & big_read: %ju\n",
681 		    (uintmax_t)medium_read
682 		);
683 	}
684 	printf("# Bigsize = %ju, medium_read = %ju, small_read = %ju\n",
685 	    (uintmax_t)big_read, (uintmax_t)medium_read, (uintmax_t)small_read);
686 
687 	assert(0 < small_read);
688 
689 	assert(0 < medium_read);
690 	assert(medium_read >= small_read);
691 	assert(medium_read <= big_read);
692 	assert(medium_read % small_read == 0);
693 
694 	assert(0 < big_read);
695 	assert(big_read >= medium_read);
696 	assert(big_read % small_read == 0);
697 }
698 
699 /**********************************************************************/
700 
701 static void
monitor_read_sizes(uint64_t failed_size)702 monitor_read_sizes(uint64_t failed_size)
703 {
704 
705 	if (failed_size == big_read && medium_read != small_read) {
706 		if (n_reads < n_good_reads + 3)
707 			return;
708 		fprintf(
709 		    stderr,
710 		    "Too many failures for big reads."
711 		    " (%.0f bad of %.0f)"
712 		    " Shifting to medium_reads.\n",
713 		    n_reads - n_good_reads, n_reads
714 		);
715 		big_read = medium_read;
716 		medium_read = small_read;
717 		wasted_size = 0;
718 		return;
719 	}
720 
721 	if (big_read > small_read && wasted_size / small_read > 200) {
722 		fprintf(
723 		    stderr,
724 		    "Too much wasted effort."
725 		    " (%.0f bad of %.0f)"
726 		    " Shifting to small_reads.\n",
727 		    n_reads - n_good_reads, n_reads
728 		);
729 		big_read = small_read;
730 		medium_read = small_read;
731 		return;
732 	}
733 }
734 
735 /**********************************************************************/
736 
737 int
main(int argc,char * const argv[])738 main(int argc, char * const argv[])
739 {
740 	int ch;
741 	int64_t sz;
742 	int error;
743 	time_t t_now, t_report, t_save;
744 	time_t snapshot = 60, unsaved;
745 	setbuf(stdout, NULL);
746 	setbuf(stderr, NULL);
747 
748 	while ((ch = getopt(argc, argv, "b:i:l:p:m:r:w:s:t:u:v")) != -1) {
749 		switch (ch) {
750 		case 'b':
751 			big_read = strtoul(optarg, NULL, 0);
752 			break;
753 		case 'i':
754 			interval = strtod(optarg, NULL);
755 			break;
756 		case 'l':
757 			log_file = fopen(optarg, "a");
758 			if (log_file == NULL) {
759 				err(1, "Could not open logfile for append");
760 			}
761 			break;
762 		case 'p':
763 			error_pause = strtod(optarg, NULL);
764 			break;
765 		case 'm':
766 			medium_read = strtoul(optarg, NULL, 0);
767 			break;
768 		case 'r':
769 			read_worklist_file = strdup(optarg);
770 			if (read_worklist_file == NULL)
771 				err(1, "Cannot allocate enough memory");
772 			break;
773 		case 's':
774 			small_read = strtoul(optarg, NULL, 0);
775 			break;
776 		case 't':
777 			total_size = strtoul(optarg, NULL, 0);
778 			break;
779 		case 'u':
780 			unreadable_pattern = optarg;
781 			break;
782 		case 'v':
783 			set_verbose();
784 			break;
785 		case 'w':
786 			write_worklist_file = strdup(optarg);
787 			if (write_worklist_file == NULL)
788 				err(1, "Cannot allocate enough memory");
789 			break;
790 		default:
791 			usage();
792 			/* NOTREACHED */
793 		}
794 	}
795 	argc -= optind;
796 	argv += optind;
797 
798 	if (argc < 1 || argc > 2)
799 		usage();
800 
801 	input = argv[0];
802 	read_fd = open(argv[0], O_RDONLY);
803 	if (read_fd < 0)
804 		err(1, "Cannot open read descriptor %s", argv[0]);
805 
806 	determine_total_size();
807 
808 	determine_read_sizes();
809 
810 	work_buf = malloc(big_read);
811 	assert (work_buf != NULL);
812 
813 	if (argc > 1) {
814 		write_fd = open(argv[1], O_WRONLY | O_CREAT, DEFFILEMODE);
815 		if (write_fd < 0)
816 			err(1, "Cannot open write descriptor %s", argv[1]);
817 		if (ftruncate(write_fd, (off_t)total_size) < 0)
818 			err(1, "Cannot truncate output %s to %ju bytes",
819 			    argv[1], (uintmax_t)total_size);
820 	} else {
821 		write_fd = -1;
822 	}
823 
824 	if (strlen(unreadable_pattern)) {
825 		pattern_buf = malloc(big_read);
826 		assert(pattern_buf != NULL);
827 		fill_buf(pattern_buf, big_read, unreadable_pattern);
828 	}
829 
830 	if (read_worklist_file != NULL) {
831 		done_size = total_size - read_worklist();
832 	} else {
833 		new_lump(0UL, total_size, 0UL);
834 		done_size = 0;
835 	}
836 	if (write_worklist_file != NULL)
837 		signal(SIGINT, sighandler);
838 
839 	sz = 0;
840 	if (!verbose)
841 		report_header("\n");
842 	else
843 		printf("\x1b[2J");
844 
845 	t_first = time(NULL);
846 	t_report = t_first;
847 	t_save = t_first;
848 	unsaved = 0;
849 	while (!aborting) {
850 		if (interval > 0) {
851 			usleep((unsigned long)(1e6 * interval));
852 		}
853 		t_now = time(NULL);
854 		sz = attempt_one_lump(t_now);
855 		error = errno;
856 
857 		if (sz == 0) {
858 			break;
859 		}
860 
861 		if (sz > 0) {
862 			unsaved += 1;
863 		}
864 		if (unsaved && (t_save + snapshot) < t_now) {
865 			save_worklist();
866 			unsaved = 0;
867 			t_save = t_now;
868 			if (!verbose) {
869 				report_header("\n");
870 				t_report = t_now;
871 			}
872 		}
873 		if (sz > 0) {
874 			if (verbose) {
875 				account_good_read(t_now, sz);
876 			}
877 			if (t_report != t_now) {
878 				report(sz);
879 				t_report = t_now;
880 			}
881 			continue;
882 		}
883 
884 		monitor_read_sizes(-sz);
885 
886 		if (error == EINVAL) {
887 			printf("Try with -b 131072 or lower ?\n");
888 			aborting = 1;
889 			break;
890 		}
891 		if (error == ENXIO) {
892 			printf("Input device probably detached...\n");
893 			aborting = 1;
894 			break;
895 		}
896 		report(-sz);
897 		t_report = t_now;
898 		if (error_pause > 0) {
899 			usleep((unsigned long)(1e6 * error_pause));
900 		}
901 	}
902 	save_worklist();
903 	free(work_buf);
904 	if (pattern_buf != NULL)
905 		free(pattern_buf);
906 	printf("%s", aborting ? "Aborted\n" : "Completed\n");
907 	report(0UL);
908 	return (0);	// XXX
909 }
910