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 /* 172 * transfer (many) sectors. reblock input as needed. 173 * returns Enone if no failures, others on failure with errstr set. 174 */ 175 static int 176 bio(File *fp, Rdwrfn *rdwr, char *buff, Daddr stsect, int sects, int mustseek) 177 { 178 int xfered; 179 char *tail; 180 ulong toread, bytes = sects * sectsz; 181 static int reblocked = 0; 182 static char magic[] = "\235any old ☺ rubbish\173"; 183 184 if (mustseek) { 185 if (!fp->seekable) 186 sysfatal("%s: need to seek on unseekable file", 187 fp->name); 188 repos(fp, stsect); 189 } 190 if ((long)blocksize != blocksize || (long)bytes != bytes) 191 sysfatal("i/o count too big: %lud", bytes); 192 193 SET(tail); 194 if (rdwr == read) { 195 strcpy(buff, magic); 196 tail = buff + bytes - sizeof magic; 197 strcpy(tail, magic); 198 } 199 werrstr(""); 200 xfered = (*rdwr)(fp->fd, buff, bytes); 201 if (xfered == bytes) { 202 /* don't trust the hardware; it may lie */ 203 if (rdwr == read && 204 (strcmp(buff, magic) == 0 || strcmp(tail, magic) == 0)) 205 fprint(2, "%s: `good' read didn't change buffer\n", 206 argv0); 207 return Enone; /* did as we asked */ 208 } 209 if (xfered < 0) 210 return Eio; /* out-and-out i/o error */ 211 /* 212 * Kernel transferred less than asked. Shouldn't happen; 213 * probably indicates disk driver error or trying to 214 * transfer past the end of a disk partition. Treat as an 215 * I/O error that reads zeros past the point of error, 216 * unless reblocking input and this is a read. 217 */ 218 if (rdwr == write) 219 return Eio; 220 if (!reblock) { 221 memset(buff+xfered, '\0', bytes-xfered); 222 return Eio; /* short read */ 223 } 224 225 /* for pipes that return less than asked */ 226 if (progress && !reblocked) { 227 fprint(2, "%s: reblocking input\n", argv0); 228 reblocked++; 229 } 230 for (toread = bytes - xfered; toread != 0; toread -= xfered) { 231 xfered = (*rdwr)(fp->fd, buff+bytes-toread, toread); 232 if (xfered <= 0) 233 break; 234 } 235 if (xfered < 0) 236 return Eio; /* out-and-out i/o error */ 237 if (toread != 0) /* early EOF? */ 238 memset(buff+bytes-toread, '\0', toread); 239 return Enone; 240 } 241 242 /* called only after a single-sector transfer */ 243 static int 244 toomanyerrs(File *fp, Daddr sect) 245 { 246 if (sect == fp->lasterr+1) 247 fp->conerrs++; 248 else 249 fp->conerrs = 0; 250 fp->lasterr = sect; 251 return fp->maxconerrs != 0 && fp->conerrs >= fp->maxconerrs && 252 fp->lastgood == -1; 253 } 254 255 static void 256 ckendrange(File *fp) 257 { 258 if (!reverse && fp->conerrs > 0) 259 fprint(2, "%s: %lld: ... last bad sector in range\n", 260 argv0, fp->lasterr); 261 } 262 263 static int 264 transfer(File *fp, Rdwrfn *rdwr, char *buff, Daddr stsect, int sects, 265 int mustseek) 266 { 267 int res = bio(fp, rdwr, buff, stsect, sects, mustseek); 268 269 if (badsect(res)) { 270 fp->fast = 0; /* read single sectors for a while */ 271 fp->congoodblks = 0; 272 } else 273 fp->lastgood = stsect + sects - 1; 274 return res; 275 } 276 277 /* 278 * Read or write many sectors at once. 279 * If it fails, retry the individual sectors and report errors. 280 */ 281 static void 282 bigxfer(File *fp, Rdwrfn *rdwr, char *buff, Daddr stsect, int sects, 283 int mustseek) 284 { 285 int i, badsects = 0, wasfast = fp->fast; 286 char *rw = (rdwr == read? "read": "write"); 287 288 if (fp->fast) { 289 if (!badsect(transfer(fp, rdwr, buff, stsect, sects, mustseek))) 290 return; 291 if (progress) 292 fprint(2, "%s: breaking up big transfer on %s error " 293 "`%r' on %s\n", argv0, rw, sectid(fp, stsect)); 294 } 295 296 for (i = 0; i < sects; i++) 297 if (badsect(transfer(fp, rdwr, buff+i*sectsz, stsect+i, 1, 298 Mustseek))) { 299 io_expl(fp, rw, stsect+i); 300 badsects++; 301 fp->harderrs++; 302 if (toomanyerrs(fp, stsect+i)) 303 sysfatal("more than %lud consecutive I/O errors", 304 fp->maxconerrs); 305 } else { 306 ckendrange(fp); 307 fp->conerrs = 0; 308 } 309 if (badsects == 0) { 310 ckendrange(fp); 311 fp->conerrs = 0; 312 if (wasfast) 313 fprint(2, "%s: %s error on big transfer at %s but none " 314 "on retries!\n", argv0, rw, sectid(fp, stsect)); 315 ++fp->congoodblks; 316 if (fp->congoodblks >= Mingoodblks) { 317 fprint(2, "%s: %s: back to big transfers\n", argv0, 318 fp->name); 319 fp->fast = 1; 320 } 321 } else 322 /* 323 * the last sector could have been in error, so the seek pointer 324 * may need to be corrected. 325 */ 326 repos(fp, stsect + sects); 327 } 328 329 static void 330 vrfyfailed(File *src, File *dest, Daddr stsect) 331 { 332 char *srcsect = strdup(sectid(src, stsect)); 333 334 fprint(2, "%s: verify failed at %s (%s)\n", argv0, srcsect, 335 sectid(dest, stsect)); 336 free(srcsect); 337 } 338 339 /* 340 * I've seen SCSI read errors that the kernel printed but then didn't 341 * report to the program doing the read, so if a big verify fails, 342 * break it up and verify each sector separately to isolate the bad sector(s). 343 */ 344 int /* error count */ 345 verify(File *src, File *dest, char *buff, char *buft, Daddr stsect, 346 int sectors) 347 { 348 int i, errors = 0; 349 350 for (i = 0; i < sectors; i++) 351 if (memcmp(buff + i*sectsz, buft + i*sectsz, sectsz) != 0) 352 errors++; 353 if (errors == 0) 354 return errors; /* normal case */ 355 356 if (sectors == 1) { 357 vrfyfailed(src, dest, stsect); 358 return errors; 359 } 360 361 /* re-read and verify each sector individually */ 362 errors = 0; 363 for (i = 0; i < sectors; i++) { 364 int thissect = stsect + i; 365 366 if (badsect(bio(src, read, buff, thissect, 1, Mustseek))) 367 io_expl(src, "read", thissect); 368 if (badsect(bio(dest, read, buft, thissect, 1, Mustseek))) 369 io_expl(dest, "write", thissect); 370 if (memcmp(buff, buft, sectsz) != 0) { 371 vrfyfailed(src, dest, thissect); 372 ++errors; 373 } 374 } 375 if (errors == 0) { 376 char *srcsect = strdup(sectid(src, stsect)); 377 378 fprint(2, "%s: verification failed on big read at %s (%s) " 379 "but not on retries!\n", argv0, srcsect, 380 sectid(dest, stsect)); 381 free(srcsect); 382 } 383 /* 384 * the last sector of each could have been in error, so the seek 385 * pointers may need to be corrected. 386 */ 387 repos(src, stsect + sectors); 388 repos(dest, stsect + sectors); 389 return errors; 390 } 391 392 /* 393 * start is starting sector of proposed transfer; 394 * nsects is the total number of sectors being copied; 395 * maxxfr is the block size in sectors. 396 */ 397 int 398 sectsleft(Daddr start, Daddr nsects, int maxxfr) 399 { 400 /* nsects-start is sectors to the end */ 401 if (start + maxxfr <= nsects - 1) 402 return maxxfr; 403 else 404 return nsects - start; 405 } 406 407 enum { 408 Rotbits = 3, 409 }; 410 411 void 412 swizzlebits(char *buff, int sects) 413 { 414 uchar *bp, *endbp; 415 416 endbp = (uchar *)(buff+sects*sectsz); 417 for (bp = (uchar *)buff; bp < endbp; bp++) 418 *bp = ~(*bp>>Rotbits | *bp<<(8-Rotbits)); 419 } 420 421 /* 422 * copy at most blksects sectors, with error retries. 423 * stsect is relative to the start of the copy; 0 is the first sector. 424 * to get actual sector numbers, add e.g. dest->startsect. 425 */ 426 static int 427 copysects(File *src, File *dest, Daddr stsect, Daddr nsects, int mustseek) 428 { 429 int xfrsects = sectsleft(stsect, nsects, blksects); 430 431 if (xfrsects > blksects) { 432 fprint(2, "%s: block size of %d is too big.\n", argv0, xfrsects); 433 exits("block size too big"); 434 } 435 bigxfer(src, read, buf, stsect, xfrsects, mustseek); 436 if (swizzle) 437 swizzlebits(buf, xfrsects); 438 bigxfer(dest, write, buf, stsect, xfrsects, mustseek); 439 /* give a few reassurances at the start, then every 10MB */ 440 if (progress && 441 (stsect < blksects*10 || stsect%(10*1024*1024/sectsz) == 0)) 442 fprint(2, "%s: copied%s to relative sector %llud\n", argv0, 443 (swizzle? " swizzled": ""), stsect + xfrsects - 1); 444 return 0; 445 } 446 447 /* 448 * verify at most blksects sectors, with error retries. 449 * return error count. 450 */ 451 static int 452 vrfysects(File *src, File *dest, Daddr stsect, Daddr nsects, int mustseek) 453 { 454 int xfrsects = sectsleft(stsect, nsects, blksects); 455 456 if (xfrsects > blksects) { 457 fprint(2, "%s: block size of %d is too big.\n", argv0, xfrsects); 458 exits("block size too big"); 459 } 460 bigxfer(src, read, buf, stsect, xfrsects, mustseek); 461 bigxfer(dest, read, vfybuf, stsect, xfrsects, mustseek); 462 return verify(src, dest, buf, vfybuf, stsect, xfrsects); 463 } 464 465 static void 466 setupfile(File *fp, int mode) 467 { 468 fp->fd = open(fp->name, mode); 469 if (fp->fd < 0) 470 sysfatal("can't open %s: %r", fp->name); 471 fp->seekable = (seek(fp->fd, 0, 1) >= 0); 472 if (fp->startsect != 0) 473 rewind(fp); 474 } 475 476 static Daddr 477 copyfile(File *src, File *dest, Daddr nsects, int plsverify) 478 { 479 Sdaddr stsect, vererrs = 0; 480 Dir *stp; 481 482 setupfile(src, OREAD); 483 if ((stp = dirstat(dest->name)) == nil) { 484 int fd = create(dest->name, ORDWR, 0666); 485 486 if (fd >= 0) 487 close(fd); 488 } 489 free(stp); 490 setupfile(dest, ORDWR); 491 492 if (progress) 493 fprint(2, "%s: copying first sectors\n", argv0); 494 if (reverse) 495 for (stsect = (nsects/blksects)*blksects; stsect >= 0; 496 stsect -= blksects) 497 vererrs += copysects(src, dest, stsect, nsects, Mustseek); 498 else { 499 for (stsect = 0; stsect < nsects; stsect += blksects) 500 vererrs += copysects(src, dest, stsect, nsects, Noseek); 501 ckendrange(src); 502 ckendrange(dest); 503 } 504 505 /* 506 * verification is done as a separate pass rather than immediately after 507 * writing, in part to defeat caching in clever disk controllers. 508 * we really want to see the bits that hit the disk. 509 */ 510 if (plsverify) { 511 fprint(2, "%s: copy done; verifying...\n", argv0); 512 rewind(src); 513 rewind(dest); 514 for (stsect = 0; stsect < nsects; stsect += blksects) /* forward */ 515 vererrs += vrfysects(src, dest, stsect, nsects, Noseek); 516 if (vererrs <= 0) 517 fprint(2, "%s: no", argv0); 518 else 519 fprint(2, "%s: %llud", argv0, vererrs); 520 fprint(2, " error%s during verification\n", 521 (vererrs != 1? "s": "")); 522 } 523 close(src->fd); 524 close(dest->fd); 525 return vererrs; 526 } 527 528 static void 529 usage(void) 530 { 531 fprint(2, "usage: %s [-bcprvZ][-B blocksz][-e errs][-s sectsz]" 532 "[-i issect][-o ossect] sectors from to\n", argv0); 533 exits("usage"); 534 } 535 536 void 537 initfile(File *fp) 538 { 539 memset(fp, 0, sizeof *fp); 540 fp->fast = 1; 541 fp->lasterr = -1; 542 fp->lastgood = -1; 543 } 544 545 void 546 main(int argc, char **argv) 547 { 548 int errflg = 0, plsconfirm = No, plsverify = No; 549 long lval; 550 File src, dest; 551 Sdaddr sect; 552 553 initfile(&src); 554 initfile(&dest); 555 ARGBEGIN { 556 case 'b': 557 reblock = Yes; 558 break; 559 case 'B': 560 lval = atol(EARGF(usage())); 561 if (lval < 0) 562 usage(); 563 blocksize = lval; 564 break; 565 case 'c': 566 plsconfirm = Yes; 567 break; 568 case 'e': 569 lval = atol(EARGF(usage())); 570 if (lval < 0) 571 usage(); 572 src.maxconerrs = lval; 573 dest.maxconerrs = lval; 574 break; 575 case 'i': 576 sect = atoll(EARGF(usage())); 577 if (sect < 0) 578 usage(); 579 src.startsect = sect; 580 break; 581 case 'o': 582 sect = atoll(EARGF(usage())); 583 if (sect < 0) 584 usage(); 585 dest.startsect = sect; 586 break; 587 case 'p': 588 progress = Yes; 589 break; 590 case 'r': 591 reverse = Yes; 592 break; 593 case 's': 594 sectsz = atol(EARGF(usage())); 595 if (sectsz <= 0 || sectsz % 512 != 0) 596 usage(); 597 break; 598 case 'v': 599 plsverify = Yes; 600 break; 601 case 'Z': 602 swizzle = Yes; 603 break; 604 default: 605 errflg++; 606 break; 607 } ARGEND 608 if (errflg || argc != 3) 609 usage(); 610 if (blocksize <= 0 || blocksize % sectsz != 0) 611 sysfatal("block size not a multiple of sector size"); 612 613 if (!isascii(argv[0][0]) || !isdigit(argv[0][0])) { 614 fprint(2, "%s: %s is not numeric\n", argv0, argv[0]); 615 exits("non-numeric sector count"); 616 } 617 src.name = argv[1]; 618 dest.name = argv[2]; 619 620 blksects = blocksize / sectsz; 621 if (blksects < 1) 622 blksects = 1; 623 buf = malloc(blocksize); 624 vfybuf = malloc(blocksize); 625 if (buf == nil || vfybuf == nil) 626 sysfatal("out of memory: %r"); 627 628 if (plsconfirm? confirm(&src, &dest): Yes) 629 copyfile(&src, &dest, atoll(argv[0]), plsverify); 630 exits(src.harderrs || dest.harderrs? "hard errors": 0); 631 } 632