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