1 /* 2 * ecp - copy a file fast (in big blocks), cope with errors, optionally verify. 3 * 4 * Transfers a block at a time. On error, retries one sector at a time, 5 * and reports all errors on the retry. 6 * Unlike dd, ecp ignores EOF, since it is sometimes reported on error. 7 * Also unlike `dd conv=noerror,sync', ecp doesn't get stuck nor give up. 8 * 9 * Written by Geoff Collyer, originally to run on RSX-11M(!) in 1979. 10 * Later simplified for UNIX and ultimately Plan 9. 11 */ 12 #include <u.h> 13 #include <libc.h> 14 #include <ctype.h> 15 16 /* fundamental constants */ 17 enum { 18 No = 0, 19 Yes, 20 21 Noseek = 0, /* need not seek, may seek on seekable files */ 22 Mustseek, 23 24 Enone = 0, 25 Eio, 26 }; 27 28 /* tunable parameters */ 29 enum { 30 Defsectsz = 512, /* default sector size */ 31 /* 10K is a good size for HP WORM drives */ 32 Defblksz = 16*1024, /* default block (big-transfer) size */ 33 Mingoodblks = 3, /* after this many, go back to fast mode */ 34 }; 35 36 #define TTY "/dev/cons" /* plan 9 */ 37 38 #define badsect(errno) ((errno) != Enone) /* was last transfer in error? */ 39 40 /* disk address (in bytes or sectors), also type of 2nd arg. to seek */ 41 typedef uvlong Daddr; 42 typedef vlong Sdaddr; /* signed disk address */ 43 typedef long Rdwrfn(int, void *, long); /* plan 9 read or write */ 44 45 typedef struct { 46 char *name; 47 int fd; 48 Daddr startsect; 49 int fast; 50 int seekable; 51 52 ulong maxconerrs; /* maximum consecutive errors */ 53 ulong conerrs; /* current consecutive errors */ 54 Daddr congoodblks; 55 56 Daddr harderrs; 57 Daddr lasterr; /* sector #s */ 58 Daddr lastgood; 59 } File; 60 61 /* exports */ 62 char *argv0; 63 64 /* privates */ 65 static int reblock = No, progress = No, swizzle = No; 66 static int reverse = No; 67 static ulong sectsz = Defsectsz; 68 static ulong blocksize = Defblksz; 69 70 static char *buf, *vfybuf; 71 static int blksects; 72 73 /* 74 * warning - print best error message possible and clear errno 75 */ 76 void 77 warning(char *s1, char *s2) 78 { 79 char err[100], msg[256]; 80 char *np, *ep = msg + sizeof msg - 1; 81 82 errstr(err, sizeof err); /* save error string */ 83 np = seprint(msg, ep, "%s: ", argv0); 84 np = seprint(np, ep, s1, s2); 85 errstr(err, sizeof err); /* restore error string */ 86 seprint(np, ep, ": %r\n"); 87 88 fprint(2, "%s", msg); 89 } 90 91 int 92 eopen(char *file, int mode) 93 { 94 int fd = open(file, mode); 95 96 if (fd < 0) 97 sysfatal("can't open %s: %r", file); 98 return fd; 99 } 100 101 static int /* boolean */ 102 confirm(File *src, File *dest) 103 { 104 int absent, n, tty = eopen(TTY, 2); 105 char c, junk; 106 Dir *stp; 107 108 if ((stp = dirstat(src->name)) == nil) 109 sysfatal("no input file %s: %r", src->name); 110 free(stp); 111 stp = dirstat(dest->name); 112 absent = (stp == nil); 113 free(stp); 114 fprint(2, "%s: copy %s to %s%s? ", argv0, src->name, dest->name, 115 (absent? " (missing)": "")); 116 n = read(tty, &c, 1); 117 junk = c; 118 if (n < 1) 119 c = 'n'; 120 while (n > 0 && junk != '\n') 121 n = read(tty, &junk, 1); 122 close(tty); 123 if (isascii(c) && isupper(c)) 124 c = tolower(c); 125 return c == 'y'; 126 } 127 128 static char * 129 sectid(File *fp, Daddr sect) 130 { 131 static char sectname[256]; 132 133 if (fp->startsect == 0) 134 snprint(sectname, sizeof sectname, "%s sector %llud", 135 fp->name, sect); 136 else 137 snprint(sectname, sizeof sectname, 138 "%s sector %llud (relative %llud)", 139 fp->name, sect + fp->startsect, sect); 140 return sectname; 141 } 142 143 static void 144 io_expl(File *fp, char *rw, Daddr sect) /* explain an i/o error */ 145 { 146 /* print only first 2 bad sectors in a range, if going forward */ 147 if (reverse || fp->conerrs == 0) { 148 char msg[128]; 149 150 snprint(msg, sizeof msg, "%s %s", rw, sectid(fp, sect)); 151 warning("%s", msg); 152 } else if (fp->conerrs == 1) 153 fprint(2, "%s: ...\n", argv0); 154 } 155 156 static void 157 repos(File *fp, Daddr sect) 158 { 159 if (!fp->seekable) 160 sysfatal("%s: trying to seek on unseekable file", fp->name); 161 if (seek(fp->fd, (sect+fp->startsect)*sectsz, 0) == -1) 162 sysfatal("can't seek on %s: %r", fp->name); 163 } 164 165 static void 166 rewind(File *fp) 167 { 168 repos(fp, 0); 169 } 170 171 static char magic[] = "\235any old ☺ rubbish\173"; 172 static char uniq[sizeof magic + 2*sizeof(ulong)]; 173 174 static char * 175 putbe(char *p, ulong ul) 176 { 177 *p++ = ul>>24; 178 *p++ = ul>>16; 179 *p++ = ul>>8; 180 *p++ = ul; 181 return p; 182 } 183 184 /* 185 * generate magic + unique string, add to start & end of buff. 186 * return tail pointer. 187 */ 188 static char * 189 addmagic(char *buff, int bytes) 190 { 191 char *p, *tail; 192 static ulong seq; 193 194 strcpy(uniq, magic); 195 p = putbe(uniq + sizeof magic - 1, time(0)); 196 putbe(p, ++seq); 197 198 memcpy(buff, uniq, sizeof uniq); 199 tail = buff + bytes - sizeof uniq; 200 memcpy(tail, uniq, sizeof uniq); 201 return tail; 202 } 203 204 /* verify magic + unique strings in buff */ 205 static int 206 ismagicok(char *buff, char *tail) 207 { 208 return memcmp(buff, uniq, sizeof uniq) == 0 || 209 memcmp(tail, uniq, sizeof uniq) == 0; 210 } 211 212 /* 213 * transfer (many) sectors. reblock input as needed. 214 * returns Enone if no failures, others on failure with errstr set. 215 */ 216 static int 217 bio(File *fp, Rdwrfn *rdwr, char *buff, Daddr stsect, int sects, int mustseek) 218 { 219 int xfered; 220 char *tail; 221 ulong toread, bytes = sects * sectsz; 222 static int reblocked = 0; 223 224 if (mustseek) { 225 if (!fp->seekable) 226 sysfatal("%s: need to seek on unseekable file", 227 fp->name); 228 repos(fp, stsect); 229 } 230 if ((long)blocksize != blocksize || (long)bytes != bytes) 231 sysfatal("i/o count too big: %lud", bytes); 232 233 SET(tail); 234 if (rdwr == read) 235 tail = addmagic(buff, bytes); 236 werrstr(""); 237 xfered = (*rdwr)(fp->fd, buff, bytes); 238 if (xfered == bytes) { 239 /* don't trust the hardware; it may lie */ 240 if (rdwr == read && ismagicok(buff, tail)) 241 fprint(2, "%s: `good' read didn't change buffer\n", 242 argv0); 243 return Enone; /* did as we asked */ 244 } 245 if (xfered < 0) 246 return Eio; /* out-and-out i/o error */ 247 248 /* 249 * Kernel transferred less than asked. Shouldn't happen; 250 * probably indicates disk driver error or trying to 251 * transfer past the end of a disk partition. Treat as an 252 * I/O error that reads zeros past the point of error, 253 * unless reblocking input and this is a read. 254 */ 255 256 if (rdwr == write) 257 return Eio; 258 if (!reblock) { 259 memset(buff+xfered, '\0', bytes-xfered); 260 return Eio; /* short read */ 261 } 262 263 /* for pipes that return less than asked */ 264 if (progress && !reblocked) { 265 fprint(2, "%s: reblocking input\n", argv0); 266 reblocked++; 267 } 268 for (toread = bytes - xfered; toread != 0; toread -= xfered) { 269 xfered = (*rdwr)(fp->fd, buff+bytes-toread, toread); 270 if (xfered <= 0) 271 break; 272 } 273 if (xfered < 0) 274 return Eio; /* out-and-out i/o error */ 275 if (toread != 0) /* early EOF? */ 276 memset(buff+bytes-toread, '\0', toread); 277 return Enone; 278 } 279 280 /* called only after a single-sector transfer */ 281 static int 282 toomanyerrs(File *fp, Daddr sect) 283 { 284 if (sect == fp->lasterr+1) 285 fp->conerrs++; 286 else 287 fp->conerrs = 0; 288 fp->lasterr = sect; 289 return fp->maxconerrs != 0 && fp->conerrs >= fp->maxconerrs && 290 fp->lastgood == -1; 291 } 292 293 static void 294 ckendrange(File *fp) 295 { 296 if (!reverse && fp->conerrs > 0) 297 fprint(2, "%s: %lld: ... last bad sector in range\n", 298 argv0, fp->lasterr); 299 } 300 301 static int 302 transfer(File *fp, Rdwrfn *rdwr, char *buff, Daddr stsect, int sects, 303 int mustseek) 304 { 305 int res = bio(fp, rdwr, buff, stsect, sects, mustseek); 306 307 if (badsect(res)) { 308 fp->fast = 0; /* read single sectors for a while */ 309 fp->congoodblks = 0; 310 } else 311 fp->lastgood = stsect + sects - 1; 312 return res; 313 } 314 315 /* 316 * Read or write many sectors at once. 317 * If it fails, retry the individual sectors and report errors. 318 */ 319 static void 320 bigxfer(File *fp, Rdwrfn *rdwr, char *buff, Daddr stsect, int sects, 321 int mustseek) 322 { 323 int i, badsects = 0, wasfast = fp->fast; 324 char *rw = (rdwr == read? "read": "write"); 325 326 if (fp->fast) { 327 if (!badsect(transfer(fp, rdwr, buff, stsect, sects, mustseek))) 328 return; 329 if (progress) 330 fprint(2, "%s: breaking up big transfer on %s error " 331 "`%r' on %s\n", argv0, rw, sectid(fp, stsect)); 332 } 333 334 for (i = 0; i < sects; i++) 335 if (badsect(transfer(fp, rdwr, buff+i*sectsz, stsect+i, 1, 336 Mustseek))) { 337 io_expl(fp, rw, stsect+i); 338 badsects++; 339 fp->harderrs++; 340 if (toomanyerrs(fp, stsect+i)) 341 sysfatal("more than %lud consecutive I/O errors", 342 fp->maxconerrs); 343 } else { 344 ckendrange(fp); 345 fp->conerrs = 0; 346 } 347 if (badsects == 0) { 348 ckendrange(fp); 349 fp->conerrs = 0; 350 if (wasfast) 351 fprint(2, "%s: %s error on big transfer at %s but none " 352 "on retries!\n", argv0, rw, sectid(fp, stsect)); 353 ++fp->congoodblks; 354 if (fp->congoodblks >= Mingoodblks) { 355 fprint(2, "%s: %s: back to big transfers\n", argv0, 356 fp->name); 357 fp->fast = 1; 358 } 359 } else 360 /* 361 * the last sector could have been in error, so the seek pointer 362 * may need to be corrected. 363 */ 364 repos(fp, stsect + sects); 365 } 366 367 static void 368 vrfyfailed(File *src, File *dest, Daddr stsect) 369 { 370 char *srcsect = strdup(sectid(src, stsect)); 371 372 fprint(2, "%s: verify failed at %s (%s)\n", argv0, srcsect, 373 sectid(dest, stsect)); 374 free(srcsect); 375 } 376 377 /* 378 * I've seen SCSI read errors that the kernel printed but then didn't 379 * report to the program doing the read, so if a big verify fails, 380 * break it up and verify each sector separately to isolate the bad sector(s). 381 */ 382 int /* error count */ 383 verify(File *src, File *dest, char *buff, char *buft, Daddr stsect, 384 int sectors) 385 { 386 int i, errors = 0; 387 388 for (i = 0; i < sectors; i++) 389 if (memcmp(buff + i*sectsz, buft + i*sectsz, sectsz) != 0) 390 errors++; 391 if (errors == 0) 392 return errors; /* normal case */ 393 394 if (sectors == 1) { 395 vrfyfailed(src, dest, stsect); 396 return errors; 397 } 398 399 /* re-read and verify each sector individually */ 400 errors = 0; 401 for (i = 0; i < sectors; i++) { 402 int thissect = stsect + i; 403 404 if (badsect(bio(src, read, buff, thissect, 1, Mustseek))) 405 io_expl(src, "read", thissect); 406 if (badsect(bio(dest, read, buft, thissect, 1, Mustseek))) 407 io_expl(dest, "write", thissect); 408 if (memcmp(buff, buft, sectsz) != 0) { 409 vrfyfailed(src, dest, thissect); 410 ++errors; 411 } 412 } 413 if (errors == 0) { 414 char *srcsect = strdup(sectid(src, stsect)); 415 416 fprint(2, "%s: verification failed on big read at %s (%s) " 417 "but not on retries!\n", argv0, srcsect, 418 sectid(dest, stsect)); 419 free(srcsect); 420 } 421 /* 422 * the last sector of each could have been in error, so the seek 423 * pointers may need to be corrected. 424 */ 425 repos(src, stsect + sectors); 426 repos(dest, stsect + sectors); 427 return errors; 428 } 429 430 /* 431 * start is starting sector of proposed transfer; 432 * nsects is the total number of sectors being copied; 433 * maxxfr is the block size in sectors. 434 */ 435 int 436 sectsleft(Daddr start, Daddr nsects, int maxxfr) 437 { 438 /* nsects-start is sectors to the end */ 439 if (start + maxxfr <= nsects - 1) 440 return maxxfr; 441 else 442 return nsects - start; 443 } 444 445 enum { 446 Rotbits = 3, 447 }; 448 449 void 450 swizzlebits(char *buff, int sects) 451 { 452 uchar *bp, *endbp; 453 454 endbp = (uchar *)(buff+sects*sectsz); 455 for (bp = (uchar *)buff; bp < endbp; bp++) 456 *bp = ~(*bp>>Rotbits | *bp<<(8-Rotbits)); 457 } 458 459 /* 460 * copy at most blksects sectors, with error retries. 461 * stsect is relative to the start of the copy; 0 is the first sector. 462 * to get actual sector numbers, add e.g. dest->startsect. 463 */ 464 static int 465 copysects(File *src, File *dest, Daddr stsect, Daddr nsects, int mustseek) 466 { 467 int xfrsects = sectsleft(stsect, nsects, blksects); 468 469 if (xfrsects > blksects) { 470 fprint(2, "%s: block size of %d is too big.\n", argv0, xfrsects); 471 exits("block size too big"); 472 } 473 bigxfer(src, read, buf, stsect, xfrsects, mustseek); 474 if (swizzle) 475 swizzlebits(buf, xfrsects); 476 bigxfer(dest, write, buf, stsect, xfrsects, mustseek); 477 /* give a few reassurances at the start, then every 10MB */ 478 if (progress && 479 (stsect < blksects*10 || stsect%(10*1024*1024/sectsz) == 0)) 480 fprint(2, "%s: copied%s to relative sector %llud\n", argv0, 481 (swizzle? " swizzled": ""), stsect + xfrsects - 1); 482 return 0; 483 } 484 485 /* 486 * verify at most blksects sectors, with error retries. 487 * return error count. 488 */ 489 static int 490 vrfysects(File *src, File *dest, Daddr stsect, Daddr nsects, int mustseek) 491 { 492 int xfrsects = sectsleft(stsect, nsects, blksects); 493 494 if (xfrsects > blksects) { 495 fprint(2, "%s: block size of %d is too big.\n", argv0, xfrsects); 496 exits("block size too big"); 497 } 498 bigxfer(src, read, buf, stsect, xfrsects, mustseek); 499 bigxfer(dest, read, vfybuf, stsect, xfrsects, mustseek); 500 return verify(src, dest, buf, vfybuf, stsect, xfrsects); 501 } 502 503 static void 504 setupfile(File *fp, int mode) 505 { 506 fp->fd = open(fp->name, mode); 507 if (fp->fd < 0) 508 sysfatal("can't open %s: %r", fp->name); 509 fp->seekable = (seek(fp->fd, 0, 1) >= 0); 510 if (fp->startsect != 0) 511 rewind(fp); 512 } 513 514 static Daddr 515 copyfile(File *src, File *dest, Daddr nsects, int plsverify) 516 { 517 Sdaddr stsect, vererrs = 0; 518 Dir *stp; 519 520 setupfile(src, OREAD); 521 if ((stp = dirstat(dest->name)) == nil) { 522 int fd = create(dest->name, ORDWR, 0666); 523 524 if (fd >= 0) 525 close(fd); 526 } 527 free(stp); 528 setupfile(dest, ORDWR); 529 530 if (progress) 531 fprint(2, "%s: copying first sectors\n", argv0); 532 if (reverse) 533 for (stsect = (nsects/blksects)*blksects; stsect >= 0; 534 stsect -= blksects) 535 vererrs += copysects(src, dest, stsect, nsects, Mustseek); 536 else { 537 for (stsect = 0; stsect < nsects; stsect += blksects) 538 vererrs += copysects(src, dest, stsect, nsects, Noseek); 539 ckendrange(src); 540 ckendrange(dest); 541 } 542 543 /* 544 * verification is done as a separate pass rather than immediately after 545 * writing, in part to defeat caching in clever disk controllers. 546 * we really want to see the bits that hit the disk. 547 */ 548 if (plsverify) { 549 fprint(2, "%s: copy done; verifying...\n", argv0); 550 rewind(src); 551 rewind(dest); 552 for (stsect = 0; stsect < nsects; stsect += blksects) /* forward */ 553 vererrs += vrfysects(src, dest, stsect, nsects, Noseek); 554 if (vererrs <= 0) 555 fprint(2, "%s: no", argv0); 556 else 557 fprint(2, "%s: %llud", argv0, vererrs); 558 fprint(2, " error%s during verification\n", 559 (vererrs != 1? "s": "")); 560 } 561 close(src->fd); 562 close(dest->fd); 563 return vererrs; 564 } 565 566 static void 567 usage(void) 568 { 569 fprint(2, "usage: %s [-bcprvZ][-B blocksz][-e errs][-s sectsz]" 570 "[-i issect][-o ossect] sectors from to\n", argv0); 571 exits("usage"); 572 } 573 574 void 575 initfile(File *fp) 576 { 577 memset(fp, 0, sizeof *fp); 578 fp->fast = 1; 579 fp->lasterr = -1; 580 fp->lastgood = -1; 581 } 582 583 void 584 main(int argc, char **argv) 585 { 586 int errflg = 0, plsconfirm = No, plsverify = No; 587 long lval; 588 File src, dest; 589 Sdaddr sect; 590 591 initfile(&src); 592 initfile(&dest); 593 ARGBEGIN { 594 case 'b': 595 reblock = Yes; 596 break; 597 case 'B': 598 lval = atol(EARGF(usage())); 599 if (lval < 0) 600 usage(); 601 blocksize = lval; 602 break; 603 case 'c': 604 plsconfirm = Yes; 605 break; 606 case 'e': 607 lval = atol(EARGF(usage())); 608 if (lval < 0) 609 usage(); 610 src.maxconerrs = lval; 611 dest.maxconerrs = lval; 612 break; 613 case 'i': 614 sect = atoll(EARGF(usage())); 615 if (sect < 0) 616 usage(); 617 src.startsect = sect; 618 break; 619 case 'o': 620 sect = atoll(EARGF(usage())); 621 if (sect < 0) 622 usage(); 623 dest.startsect = sect; 624 break; 625 case 'p': 626 progress = Yes; 627 break; 628 case 'r': 629 reverse = Yes; 630 break; 631 case 's': 632 sectsz = atol(EARGF(usage())); 633 if (sectsz <= 0 || sectsz % 512 != 0) 634 usage(); 635 break; 636 case 'v': 637 plsverify = Yes; 638 break; 639 case 'Z': 640 swizzle = Yes; 641 break; 642 default: 643 errflg++; 644 break; 645 } ARGEND 646 if (errflg || argc != 3) 647 usage(); 648 if (blocksize <= 0 || blocksize % sectsz != 0) 649 sysfatal("block size not a multiple of sector size"); 650 651 if (!isascii(argv[0][0]) || !isdigit(argv[0][0])) { 652 fprint(2, "%s: %s is not numeric\n", argv0, argv[0]); 653 exits("non-numeric sector count"); 654 } 655 src.name = argv[1]; 656 dest.name = argv[2]; 657 658 blksects = blocksize / sectsz; 659 if (blksects < 1) 660 blksects = 1; 661 buf = malloc(blocksize); 662 vfybuf = malloc(blocksize); 663 if (buf == nil || vfybuf == nil) 664 sysfatal("out of memory: %r"); 665 666 if (plsconfirm? confirm(&src, &dest): Yes) 667 copyfile(&src, &dest, atoll(argv[0]), plsverify); 668 exits(src.harderrs || dest.harderrs? "hard errors": 0); 669 } 670