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