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, §orsize);
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 medium_read -= medium_read % small_read;
681 if (medium_read < small_read)
682 medium_read = small_read;
683 printf("# Got medium_read from small_read & big_read: %ju\n",
684 (uintmax_t)medium_read
685 );
686 }
687 printf("# Bigsize = %ju, medium_read = %ju, small_read = %ju\n",
688 (uintmax_t)big_read, (uintmax_t)medium_read, (uintmax_t)small_read);
689
690 assert(0 < small_read);
691
692 assert(0 < medium_read);
693 assert(medium_read >= small_read);
694 assert(medium_read <= big_read);
695 assert(medium_read % small_read == 0);
696
697 assert(0 < big_read);
698 assert(big_read >= medium_read);
699 assert(big_read % small_read == 0);
700 }
701
702 /**********************************************************************/
703
704 static void
monitor_read_sizes(uint64_t failed_size)705 monitor_read_sizes(uint64_t failed_size)
706 {
707
708 if (failed_size == big_read && medium_read != small_read) {
709 if (n_reads < n_good_reads + 3)
710 return;
711 fprintf(
712 stderr,
713 "Too many failures for big reads."
714 " (%.0f bad of %.0f)"
715 " Shifting to medium_reads.\n",
716 n_reads - n_good_reads, n_reads
717 );
718 big_read = medium_read;
719 medium_read = small_read;
720 wasted_size = 0;
721 return;
722 }
723
724 if (big_read > small_read && wasted_size / small_read > 200) {
725 fprintf(
726 stderr,
727 "Too much wasted effort."
728 " (%.0f bad of %.0f)"
729 " Shifting to small_reads.\n",
730 n_reads - n_good_reads, n_reads
731 );
732 big_read = small_read;
733 medium_read = small_read;
734 return;
735 }
736 }
737
738 /**********************************************************************/
739
740 int
main(int argc,char * const argv[])741 main(int argc, char * const argv[])
742 {
743 int ch;
744 int64_t sz;
745 int error;
746 time_t t_now, t_report, t_save;
747 time_t snapshot = 60, unsaved;
748 setbuf(stdout, NULL);
749 setbuf(stderr, NULL);
750
751 while ((ch = getopt(argc, argv, "b:i:l:p:m:r:w:s:t:u:v")) != -1) {
752 switch (ch) {
753 case 'b':
754 big_read = strtoul(optarg, NULL, 0);
755 break;
756 case 'i':
757 interval = strtod(optarg, NULL);
758 break;
759 case 'l':
760 log_file = fopen(optarg, "a");
761 if (log_file == NULL) {
762 err(1, "Could not open logfile for append");
763 }
764 break;
765 case 'p':
766 error_pause = strtod(optarg, NULL);
767 break;
768 case 'm':
769 medium_read = strtoul(optarg, NULL, 0);
770 break;
771 case 'r':
772 read_worklist_file = strdup(optarg);
773 if (read_worklist_file == NULL)
774 err(1, "Cannot allocate enough memory");
775 break;
776 case 's':
777 small_read = strtoul(optarg, NULL, 0);
778 break;
779 case 't':
780 total_size = strtoul(optarg, NULL, 0);
781 break;
782 case 'u':
783 unreadable_pattern = optarg;
784 break;
785 case 'v':
786 set_verbose();
787 break;
788 case 'w':
789 write_worklist_file = strdup(optarg);
790 if (write_worklist_file == NULL)
791 err(1, "Cannot allocate enough memory");
792 break;
793 default:
794 usage();
795 /* NOTREACHED */
796 }
797 }
798 argc -= optind;
799 argv += optind;
800
801 if (argc < 1 || argc > 2)
802 usage();
803
804 input = argv[0];
805 read_fd = open(argv[0], O_RDONLY);
806 if (read_fd < 0)
807 err(1, "Cannot open read descriptor %s", argv[0]);
808
809 determine_total_size();
810
811 determine_read_sizes();
812
813 work_buf = malloc(big_read);
814 assert (work_buf != NULL);
815
816 if (argc > 1) {
817 write_fd = open(argv[1], O_WRONLY | O_CREAT, DEFFILEMODE);
818 if (write_fd < 0)
819 err(1, "Cannot open write descriptor %s", argv[1]);
820 if (ftruncate(write_fd, (off_t)total_size) < 0)
821 err(1, "Cannot truncate output %s to %ju bytes",
822 argv[1], (uintmax_t)total_size);
823 } else {
824 write_fd = -1;
825 }
826
827 if (strlen(unreadable_pattern)) {
828 pattern_buf = malloc(big_read);
829 assert(pattern_buf != NULL);
830 fill_buf(pattern_buf, big_read, unreadable_pattern);
831 }
832
833 if (read_worklist_file != NULL) {
834 done_size = total_size - read_worklist();
835 } else {
836 new_lump(0UL, total_size, 0UL);
837 done_size = 0;
838 }
839 if (write_worklist_file != NULL)
840 signal(SIGINT, sighandler);
841
842 sz = 0;
843 if (!verbose)
844 report_header("\n");
845 else
846 printf("\x1b[2J");
847
848 t_first = time(NULL);
849 t_report = t_first;
850 t_save = t_first;
851 unsaved = 0;
852 while (!aborting) {
853 if (interval > 0) {
854 usleep((unsigned long)(1e6 * interval));
855 }
856 t_now = time(NULL);
857 sz = attempt_one_lump(t_now);
858 error = errno;
859
860 if (sz == 0) {
861 break;
862 }
863
864 if (sz > 0) {
865 unsaved += 1;
866 }
867 if (unsaved && (t_save + snapshot) < t_now) {
868 save_worklist();
869 unsaved = 0;
870 t_save = t_now;
871 if (!verbose) {
872 report_header("\n");
873 t_report = t_now;
874 }
875 }
876 if (sz > 0) {
877 if (verbose) {
878 account_good_read(t_now, sz);
879 }
880 if (t_report != t_now) {
881 report(sz);
882 t_report = t_now;
883 }
884 continue;
885 }
886
887 monitor_read_sizes(-sz);
888
889 if (error == EINVAL) {
890 printf("Try with -b 131072 or lower ?\n");
891 aborting = 1;
892 break;
893 }
894 if (error == ENXIO) {
895 printf("Input device probably detached...\n");
896 aborting = 1;
897 break;
898 }
899 report(-sz);
900 t_report = t_now;
901 if (error_pause > 0) {
902 usleep((unsigned long)(1e6 * error_pause));
903 }
904 }
905 save_worklist();
906 free(work_buf);
907 if (pattern_buf != NULL)
908 free(pattern_buf);
909 printf("%s", aborting ? "Aborted\n" : "Completed\n");
910 report(0UL);
911 return (0); // XXX
912 }
913