diff --git a/usr.bin/tcopy/tcopy.1 b/usr.bin/tcopy/tcopy.1 --- a/usr.bin/tcopy/tcopy.1 +++ b/usr.bin/tcopy/tcopy.1 @@ -69,6 +69,9 @@ to .Ar dest and then verify that the two tapes are identical. +Both tapes will be rewound first. +.It Fl t +Treat non device files as SIMH-TAPFILE data streams. .It Fl s Ar maxblk Specify a maximum block size, .Ar maxblk . @@ -86,9 +89,38 @@ is given as .Pa /dev/stdout . .El +.Sh EXAMPLES +Verify that the tape in /dev/sa0 can be read and see the layout +of its content: +.Bd -literal -offset indent +$ tcopy +.Ed +.Pp +Copy a tape using two tape drives: +.Bd -literal -offset indent +$ tcopy /dev/sa0 /dev/sa1 +.Ed +.Pp +Copy a tape using only a single tape drive, and verify the result: +.Bd -literal -offset indent +$ tcopy -t /dev/sa0 /tmp/temp.tapfile +(change the tape) +$ tcopy -c -t /tmp/temp.tapfile /dev/sa0 +.Ed +.Pp +Make a cryptographic hash of both the contents and the layout of the tape in +/dev/sa1: +.Pp +.Bd -literal -offset indent +$ tcopy -t /dev/sa1 - | sha256 +.Ed .Sh SEE ALSO .Xr mt 1 , +.Xr sa 4 , .Xr mtio 4 +.Sh STANDARDS +The SIMH-TAPFILE format is documented in the SIMH github repos: +.Pa https://github.com/simh/simh/blob/master/doc/simh_magtape.doc .Sh HISTORY The .Nm @@ -107,10 +139,16 @@ $ mt param sili -s 1 .Ed .It -Writing an image of a tape to a file does not preserve much more than -the raw data. -Block size(s) and tape EOF marks are lost which would -otherwise be preserved in a tape-to-tape copy. +Copying tape to a file, pipe or socket, without +.Fl t +only copies the raw data, all information about block size(s) +and tape EOF marks is lost. +.It +With +.Fl t +files, pipes, sockets etc, will be treated as a +SIMH-TAPFILE data stream, which retain the information +about block size(s) and tape EOF marks. .It End of data (EOD) is determined by two sequential EOF marks with no data between them. @@ -119,14 +157,4 @@ The .Nm utility will erroneously stop copying early in this case. -.It -When using the copy/verify option -.Fl c , -.Nm -does not rewind the tapes prior to start. -A rewind is performed -after writing, prior to the verification stage. -If one does not start -at the beginning-of-tape (BOT) then the comparison -may not be of the intended data. .El diff --git a/usr.bin/tcopy/tcopy.c b/usr.bin/tcopy/tcopy.c --- a/usr.bin/tcopy/tcopy.c +++ b/usr.bin/tcopy/tcopy.c @@ -33,7 +33,9 @@ #include #include #include +#include +#include #include #include #include @@ -50,37 +52,289 @@ #define MAXREC (64 * 1024) #define NOCOUNT (-2) -static int filen, guesslen, maxblk = MAXREC; +struct tapedev { + int fd; + const char *filename; + int is_reg; + ssize_t read_size; +}; + +static int filen, tapfile; +static ssize_t maxblk = MAXREC; static uint64_t lastrec, record, size, tsize; static FILE *msg; +static struct tapedev in_dev = {-1, NULL, 0, -1}; +static struct tapedev out_dev = {-1, NULL, 0, -1}; +static ssize_t lastnread; + +static void +intr(int signo __unused) +{ + if (record) { + if (record - lastrec > 1) + fprintf(msg, "records %ju to %ju\n", + (intmax_t)lastrec, (intmax_t)record); + else + fprintf(msg, "record %ju\n", (intmax_t)lastrec); + } + fprintf(msg, "interrupt at file %d: record %ju\n", + filen, (intmax_t)record); + fprintf(msg, "total length: %ju bytes\n", (uintmax_t)(tsize + size)); + exit(1); +} + +static void * +getspace(ssize_t blk) +{ + void *bp; + + assert(blk > 0); + if ((bp = malloc((size_t)blk)) == NULL) + errx(11, "no memory"); + return (bp); +} + +static void +usage(void) +{ + fprintf(stderr, "usage: tcopy [-cvx] [-s maxblk] [src [dest]]\n"); + exit(1); +} + +static void +tape_dev_open(struct tapedev *td, const char *filename, int mode, int diag) +{ + td->fd = open(filename, mode, DEFFILEMODE); + if (td->fd < 0) + err(diag, "%s", filename); + td->filename = filename; + + struct stat sp; + + if(fstat(td->fd, &sp)) + errx(12, "fstat on %s", filename); + td->is_reg = !S_ISCHR(sp.st_mode); +} + +static ssize_t +tape_dev_read(struct tapedev *td, void *buffer) +{ + ssize_t nread; + + if (!tapfile || !td->is_reg) { + if (td->read_size < 0) { + td->read_size = maxblk; + while (td->read_size > 0) { + nread = read(td->fd, buffer, td->read_size); + if (nread >= 0 || errno != EINVAL) + break; + if (td->read_size > (128<<10)) + td->read_size >>= 1; + else + td->read_size -= 1024; + } + } else { + nread = read(td->fd, buffer, td->read_size); + } + if (nread < 0) + err(1, "read error, %s, file %d, record %ju", + td->filename, filen, (uintmax_t)record); + return (nread); + } + + char tbuf[4]; + + nread = read(td->fd, tbuf, 4); + if (nread < 0) + err(1, "read error, %s, file %d, record %ju", + td->filename, filen, (uintmax_t)record); + if (nread == 0) + return(nread); + + uint32_t u = le32dec(tbuf); + if (u == 0 || (u >> 24) == 0xff) + return(0); + + if (u > maxblk) + err(17, "tapfile blocksize too big, 0x%08x", u); + + size_t alen = (u + 1) & ~1; + + nread = read(td->fd, buffer, alen); + if (nread < 0) + err(1, "read error, %s, file %d, record %ju", + td->filename, filen, (uintmax_t)record); + + nread = read(td->fd, tbuf, 4); + if (nread < 0) + err(1, "read error, %s, file %d, record %ju", + td->filename, filen, (uintmax_t)record); + if (nread == 0) + err(17, "short tapfile, %s, file %d, record %ju", + td->filename, filen, (uintmax_t)record); + + uint32_t v = le32dec(tbuf); + if (u != v) + err(17, "bad tapfile, %s, file %d, record %ju", + td->filename, filen, (uintmax_t)record); + return (u); +} + +static void +tape_dev_write(const struct tapedev *td, char *buffer, ssize_t len) +{ + ssize_t nwrite; + + assert(len > 0); + if (!tapfile || !td->is_reg) { + nwrite = write(td->fd, buffer, len); + if (nwrite != len) { + if (nwrite == -1) { + warn("write error, file %d, record %ju", + filen, (intmax_t)record); + } else { + warnx("write error, file %d, record %ju", + filen, (intmax_t)record); + warnx("write (%zd) != read (%zd)", nwrite, len); + } + errx(5, "copy aborted"); + } + return; + } + + char tbuf[4]; + le32enc(tbuf, len); + nwrite = write(td->fd, tbuf, 4); + if (nwrite != 4) + errx(17, "write error"); + if (len & 1) + buffer[len] = 0x00; + size_t alen = (len + 1) & ~1; + nwrite = write(td->fd, buffer, alen); + if (nwrite < 0 || nwrite != (ssize_t)alen) + errx(17, "write error"); + nwrite = write(td->fd, tbuf, 4); + if (nwrite != 4) + errx(17, "write error"); +} + +static void +tape_dev_op(const struct tapedev *td, int what) +{ + if (td->is_reg) { + if (what == MTREW) { + if(lseek(td->fd, 0, SEEK_SET) == -1) + errx(13, "lseek"); + } else if (what == MTWEOF && tapfile) { + char tbuf[4]; + ssize_t nwrite; + le32enc(tbuf, 0); + nwrite = write(td->fd, tbuf, 4); + if (nwrite != 4) + errx(17, "write error"); + } + return; + } + + struct mtop op; + + op.mt_op = what; + op.mt_count = (daddr_t)1; + if (ioctl(td->fd, MTIOCTOP, (char *)&op) < 0) + err(6, "tape op"); +} + +static void +progress(ssize_t nread) +{ + if (nread != lastnread) { + if (lastnread != 0 && lastnread != NOCOUNT) { + if (lastrec == 0 && nread == 0) + fprintf(msg, "%ju records\n", + (uintmax_t)record); + else if (record - lastrec > 1) + fprintf(msg, "records %ju to %ju\n", + (uintmax_t)lastrec, + (uintmax_t)record); + else + fprintf(msg, "record %ju\n", + (uintmax_t)lastrec); + } + if (nread != 0) + fprintf(msg, + "file %d: block size %zd: ", filen, nread); + (void) fflush(msg); + lastrec = record; + } + if (nread > 0) { + size += nread; + record++; + } else { + if (lastnread <= 0 && lastnread != NOCOUNT) { + fprintf(msg, "eot\n"); + return; + } + fprintf(msg, + "file %d: eof after %ju records: %ju bytes\n", + filen, (uintmax_t)record, (uintmax_t)size); + filen++; + tsize += size; + size = record = lastrec = 0; + lastnread = 0; + } + lastnread = nread; +} + +static void +verify(struct tapedev *in1, struct tapedev *in2) +{ + int eot = 0; + ssize_t nread1, nread2; + + char *inb1 = getspace(maxblk); + char *inb2 = getspace(maxblk); + while (1) { + nread1 = tape_dev_read(in1, inb1); + nread2 = tape_dev_read(in2, inb2); + progress(nread1); + if (nread1 != nread2) { + fprintf(msg, + "tcopy: tapes have different block sizes; " + "%zd != %zd.\n", nread1, nread2); + exit(1); + } + if (nread1 > 0 && memcmp(inb1, inb2, nread1)) { + fprintf(msg, + "tcopy: tapes have different data.\n"); + exit(1); + } else if (nread1 > 0) { + eot = 0; + } else if (eot++) { + fprintf(msg, "tcopy: tapes are identical.\n"); + free(inb1); + free(inb2); + return; + } + } +} -static void *getspace(int); -static void intr(int); -static void usage(void) __dead2; -static void verify(int, int, char *); -static void writeop(int, int); -static void rewind_tape(int); int main(int argc, char *argv[]) { - int lastnread, nread, nw, inp, outp; enum {READ, VERIFY, COPY, COPYVERIFY} op = READ; sig_t oldsig; int ch, needeof; - char *buff; - const char *inf; unsigned long maxphys = 0; size_t l_maxphys = sizeof maxphys; uint64_t tmp; + ssize_t nread, prev_read; if (!sysctlbyname("kern.maxphys", &maxphys, &l_maxphys, NULL, 0)) maxblk = maxphys; msg = stdout; - guesslen = 1; - outp = -1; - while ((ch = getopt(argc, argv, "cs:vx")) != -1) + while ((ch = getopt(argc, argv, "cs:tvx")) != -1) switch((char)ch) { case 'c': op = COPYVERIFY; @@ -95,7 +349,9 @@ warnx("illegal block size"); usage(); } - guesslen = 0; + break; + case 't': + tapfile = 1; break; case 'v': op = VERIFY; @@ -110,229 +366,95 @@ argc -= optind; argv += optind; + + int mode; switch(argc) { case 0: if (op != READ) usage(); - inf = _PATH_DEFTAPE; break; case 1: if (op != READ) usage(); - inf = argv[0]; break; case 2: - if (op == READ) + switch (op) { + case VERIFY: + mode = O_RDONLY; + break; + case READ: op = COPY; - inf = argv[0]; - if ((outp = open(argv[1], op == VERIFY ? O_RDONLY : - op == COPY ? O_WRONLY : O_RDWR, DEFFILEMODE)) < 0) - err(3, "%s", argv[1]); + /* FALL_THROUGH */ + case COPY: + mode = O_WRONLY|O_CREAT; + break; + default: + mode = O_RDWR|O_CREAT; + break; + } + if (!strcmp(argv[1], "-")) { + tape_dev_open(&out_dev, "/dev/stdout", mode, 3); + msg = stderr; + } else { + tape_dev_open(&out_dev, argv[1], mode, 3); + } break; default: usage(); } - if ((inp = open(inf, O_RDONLY, 0)) < 0) - err(1, "%s", inf); + if (argc == 0) { + tape_dev_open(&in_dev, _PATH_DEFTAPE, O_RDONLY, 1); + } else if (!strcmp(argv[0], "-")) { + tape_dev_open(&out_dev, "/dev/stdin", mode, 3); + } else { + tape_dev_open(&in_dev, argv[0], O_RDONLY, 1); + } - buff = getspace(maxblk); + char *buff = getspace(maxblk); - if (op == VERIFY) { - verify(inp, outp, buff); + if (op == VERIFY && out_dev.fd > 0) { + verify(&in_dev, &out_dev); exit(0); } if ((oldsig = signal(SIGINT, SIG_IGN)) != SIG_IGN) (void) signal(SIGINT, intr); + if (op == COPYVERIFY) { + tape_dev_op(&out_dev, MTREW); + tape_dev_op(&in_dev, MTREW); + } needeof = 0; - for (lastnread = NOCOUNT;;) { - if ((nread = read(inp, buff, maxblk)) == -1) { - while (errno == EINVAL && (maxblk -= 1024)) { - nread = read(inp, buff, maxblk); - if (nread >= 0) - goto r1; - } - err(1, "read error, file %d, record %ju", filen, (intmax_t)record); - } else if (nread != lastnread) { - if (lastnread != 0 && lastnread != NOCOUNT) { - if (lastrec == 0 && nread == 0) - fprintf(msg, "%ju records\n", (intmax_t)record); - else if (record - lastrec > 1) - fprintf(msg, "records %ju to %ju\n", - (intmax_t)lastrec, (intmax_t)record); - else - fprintf(msg, "record %ju\n", (intmax_t)lastrec); - } - if (nread != 0) - fprintf(msg, "file %d: block size %d: ", - filen, nread); - (void) fflush(stdout); - lastrec = record; - } -r1: guesslen = 0; + for (prev_read = NOCOUNT;;) { + nread = tape_dev_read(&in_dev, buff); + progress(nread); if (nread > 0) { if (op == COPY || op == COPYVERIFY) { if (needeof) { - writeop(outp, MTWEOF); + tape_dev_op(&out_dev, MTWEOF); needeof = 0; } - nw = write(outp, buff, nread); - if (nw != nread) { - if (nw == -1) { - warn("write error, file %d, record %ju", filen, - (intmax_t)record); - } else { - warnx("write error, file %d, record %ju", filen, - (intmax_t)record); - warnx("write (%d) != read (%d)", nw, nread); - } - errx(5, "copy aborted"); - } + tape_dev_write(&out_dev, buff, nread); } - size += nread; - record++; } else { - if (lastnread <= 0 && lastnread != NOCOUNT) { - fprintf(msg, "eot\n"); + if (prev_read <= 0 && prev_read != NOCOUNT) { break; } - fprintf(msg, - "file %d: eof after %ju records: %ju bytes\n", - filen, (intmax_t)record, (intmax_t)size); needeof = 1; - filen++; - tsize += size; - size = record = lastrec = 0; - lastnread = 0; } - lastnread = nread; + prev_read = nread; } fprintf(msg, "total length: %ju bytes\n", (intmax_t)tsize); - (void)signal(SIGINT, oldsig); if (op == COPY || op == COPYVERIFY) { - writeop(outp, MTWEOF); - writeop(outp, MTWEOF); - if (op == COPYVERIFY) { - rewind_tape(outp); - rewind_tape(inp); - verify(inp, outp, buff); - } + tape_dev_op(&out_dev, MTWEOF); + tape_dev_op(&out_dev, MTWEOF); } - exit(0); -} - -static void -verify(int inp, int outp, char *outb) -{ - int eot, inmaxblk, inn, outmaxblk, outn; - char *inb; - - inb = getspace(maxblk); - inmaxblk = outmaxblk = maxblk; - for (eot = 0;; guesslen = 0) { - if ((inn = read(inp, inb, inmaxblk)) == -1) { - if (guesslen) - while (errno == EINVAL && (inmaxblk -= 1024)) { - inn = read(inp, inb, inmaxblk); - if (inn >= 0) - goto r1; - } - warn("read error"); - break; - } -r1: if ((outn = read(outp, outb, outmaxblk)) == -1) { - if (guesslen) - while (errno == EINVAL && (outmaxblk -= 1024)) { - outn = read(outp, outb, outmaxblk); - if (outn >= 0) - goto r2; - } - warn("read error"); - break; - } -r2: if (inn != outn) { - fprintf(msg, - "%s: tapes have different block sizes; %d != %d.\n", - "tcopy", inn, outn); - break; - } - if (!inn) { - if (eot++) { - fprintf(msg, "tcopy: tapes are identical.\n"); - free(inb); - return; - } - } else { - if (bcmp(inb, outb, inn)) { - fprintf(msg, - "tcopy: tapes have different data.\n"); - break; - } - eot = 0; - } + if (op == COPYVERIFY) { + tape_dev_op(&out_dev, MTREW); + tape_dev_op(&in_dev, MTREW); + verify(&in_dev, &out_dev); } - exit(1); -} - -static void -intr(int signo __unused) -{ - if (record) { - if (record - lastrec > 1) - fprintf(msg, "records %ju to %ju\n", (intmax_t)lastrec, (intmax_t)record); - else - fprintf(msg, "record %ju\n", (intmax_t)lastrec); - } - fprintf(msg, "interrupt at file %d: record %ju\n", filen, (intmax_t)record); - fprintf(msg, "total length: %ju bytes\n", (uintmax_t)(tsize + size)); - exit(1); -} - -static void * -getspace(int blk) -{ - void *bp; - - if ((bp = malloc((size_t)blk)) == NULL) - errx(11, "no memory"); - return (bp); -} - -static void -writeop(int fd, int type) -{ - struct mtop op; - - op.mt_op = type; - op.mt_count = (daddr_t)1; - if (ioctl(fd, MTIOCTOP, (char *)&op) < 0) - err(6, "tape op"); -} - -static void -usage(void) -{ - fprintf(stderr, "usage: tcopy [-cvx] [-s maxblk] [src [dest]]\n"); - exit(1); -} - -static void -rewind_tape(int fd) -{ - struct stat sp; - - if(fstat(fd, &sp)) - errx(12, "fstat in rewind"); - - /* - * don't want to do tape ioctl on regular files: - */ - if( S_ISREG(sp.st_mode) ) { - if( lseek(fd, 0, SEEK_SET) == -1 ) - errx(13, "lseek"); - } else - /* assume its a tape */ - writeop(fd, MTREW); + (void)signal(SIGINT, oldsig); + exit(0); }