diff options
Diffstat (limited to 'usr.bin/tcopy')
-rw-r--r-- | usr.bin/tcopy/Makefile | 4 | ||||
-rw-r--r-- | usr.bin/tcopy/tcopy.1 | 195 | ||||
-rw-r--r-- | usr.bin/tcopy/tcopy.c | 338 | ||||
-rw-r--r-- | usr.bin/tcopy/tcopy.cc | 837 |
4 files changed, 994 insertions, 380 deletions
diff --git a/usr.bin/tcopy/Makefile b/usr.bin/tcopy/Makefile index 73dcd45b2e0f..831de7625db8 100644 --- a/usr.bin/tcopy/Makefile +++ b/usr.bin/tcopy/Makefile @@ -1,4 +1,4 @@ -PROG= tcopy -LIBADD= util +PROG_CXX= tcopy +LIBADD= util .include <bsd.prog.mk> diff --git a/usr.bin/tcopy/tcopy.1 b/usr.bin/tcopy/tcopy.1 index 3f12a807e41e..f74a29ba1173 100644 --- a/usr.bin/tcopy/tcopy.1 +++ b/usr.bin/tcopy/tcopy.1 @@ -30,65 +30,189 @@ .Os .Sh NAME .Nm tcopy -.Nd copy and/or verify mag tapes +.Nd read, write, copy and verify tapes .Sh SYNOPSIS .Nm -.Op Fl cvx +.Op Fl crvx +.Op Fl l Ar logfile .Op Fl s Ar maxblk .Oo Ar src Op Ar dest .Oc .Sh DESCRIPTION The .Nm -utility is designed to copy magnetic tapes. +utility is designed to read, write and copy tapes. +.Pp The only assumption made about the tape layout is that there are two sequential EOF marks at the end. -By default, the -.Nm -utility will print -information about the sizes of records and files found -on the +.Pp +The +.Ar src +argument can be a tape device and defaults to .Pa /dev/sa0 -tape, or on the tape specified by the +or it can be data in SIMH-TAP format. +If +.Ar src +is +.Dq Cm - +the standard input is read. +.Pp +If the +.Ar dest +argument is also specified, a copy of the .Ar src -argument. -If a destination tape is also specified by the +will be made onto the +.Ar dest . +If +.Ar dest +is +.Dq Cm - +standard output will be written to. +.Pp +If .Ar dest -argument, a copy of the source tape will be made. -The blocking on the -destination tape will be identical to that used on the source tape. -Copying -a tape will yield the same program output as if just printing the sizes. +is a tape device, the file and record structure will be the same. +.Pp +If +.Ar dest +is a filename ending in +.Dq Cm .000 +the contents each file on +.Ar src +will be written to sequentially numbered files +.Dq Cm .000 , +.Dq Cm .001 , +.Dq Cm .002 +etc. +Information about record sizes will be lost. +.Pp +If the +.Fl r +flag is specified, only the data will be written, information about +file and record layout is lost. +.Pp +Otherwise the data, file and record structure of +.Ar src +will be written in SIMH-TAP format. +.Pp +The +.Nm +utility will report information about the layout of +.Ar src +like this on standard output: +.Bd -literal -offset indent +file 0: block size 80: 6 records +file 0: eof after 6 records: 480 bytes +file 1: block size 3072: records 0 to 262 +file 1: block size 612: record 262 +file 1: eof after 263 records: 805476 bytes +[…] +eot +total length: 972851280 bytes time: 41 s rate: 22934.8 kB/s +.Ed +.Pp +If +.Ar dest +is +.Dq Cm - +or if +.Fl x +is specified this goes to standard error instead, +and can also be redirected with +.Fl l Ar logfile , +in which case the final total line will also be reported on standard error. +.Pp +If +.Nm +receives a +.Dv SIGINFO +signal, current counts are reported on standard error. .Pp The following options are available: .Bl -tag -width ".Fl s Ar maxblk" .It Fl c -Copy +Rewind both tapes, copy .Ar src to -.Ar dest -and then verify that the two tapes are identical. +.Ar dest , +rewind again and verify that the two tapes are now identical. +.It Fl l Ar logfile +Output all informational messages to +.Ar logfile . +.It Fl r +Write only the contents of all data blocks to the output. +The file and record structure of the input will be lost. .It Fl s Ar maxblk Specify a maximum block size, .Ar maxblk . +The default is +.Va kern.maxphys . .It Fl v -Given the two tapes +Verify that .Ar src and -.Ar dest , -verify that they are identical. +.Ar dest +are identical. +Note that the tapes are not rewound prior to the comparison. .It Fl x Output all informational messages to the standard error instead of the standard output. -This option is useful when +This option is automatic if .Ar dest is given as -.Pa /dev/stdout . +.Dq Cm - . .El +.Sh EXIT STATUS +Unfortunately all over the place, but zero always means succeess. +.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 /dev/sa0 /tmp/temp.tapfile +# change tape +tcopy -c /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 /dev/sa1 - | sha256 +.Ed +.Pp +Copy a tape to a tape drive on another machine: +.Bd -literal -offset indent +tcopy /dev/sa0 - | ssh otherhost tcopy - /dev/sa0 +.Ed +.Pp +Extract the tape files into individual files: +.Bd -literal -offset indent +tcopy /dev/sa0 /tmp/_.tape.000 +.Ed +.Pp +Ignore all structure on the tape and feed all data to +.Xr tar 1 : +.Bd -literal -offset indent +tcopy -l /dev/null -r /dev/sa0 - | tar tvf - +.Ed .Sh SEE ALSO .Xr mt 1 , +.Xr sa 4 , .Xr mtio 4 +.Sh STANDARDS +The SIMH-TAP format is documented in the open-simh github repos: +.Pa https://github.com/open-simh/simh/blob/master/doc/simh_magtape.doc .Sh HISTORY The .Nm @@ -99,19 +223,16 @@ command appeared in .It Modern tape drives may return a SCSI "Incorrect Length Indicator (ILI)" for each read with a different block size that what is on the -tape, and that slows things down a lot. +tape, and that slows +.Nm +down a lot. This can be disabled with the .Xr mt 1 command: .Bd -literal -offset indent -$ mt param sili -s 1 +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. -.It End of data (EOD) is determined by two sequential EOF marks with no data between them. There used to be old systems which typically wrote three EOF's between tape @@ -120,13 +241,7 @@ 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. +With +.Fl c +the tape drives are not rewound at the same time, but one after the other. .El diff --git a/usr.bin/tcopy/tcopy.c b/usr.bin/tcopy/tcopy.c deleted file mode 100644 index 39eae4126324..000000000000 --- a/usr.bin/tcopy/tcopy.c +++ /dev/null @@ -1,338 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-3-Clause - * - * Copyright (c) 1985, 1987, 1993 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/ioctl.h> -#include <sys/mtio.h> - -#include <err.h> -#include <errno.h> -#include <fcntl.h> -#include <libutil.h> -#include <paths.h> -#include <sys/sysctl.h> -#include <signal.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#define MAXREC (64 * 1024) -#define NOCOUNT (-2) - -static int filen, guesslen, maxblk = MAXREC; -static uint64_t lastrec, record, size, tsize; -static FILE *msg; - -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; - - 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) - switch((char)ch) { - case 'c': - op = COPYVERIFY; - break; - case 's': - if (expand_number(optarg, &tmp)) { - warnx("illegal block size"); - usage(); - } - maxblk = tmp; - if (maxblk == 0) { - warnx("illegal block size"); - usage(); - } - guesslen = 0; - break; - case 'v': - op = VERIFY; - break; - case 'x': - msg = stderr; - break; - case '?': - default: - usage(); - } - argc -= optind; - argv += optind; - - 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) - 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]); - break; - default: - usage(); - } - - if ((inp = open(inf, O_RDONLY, 0)) < 0) - err(1, "%s", inf); - - buff = getspace(maxblk); - - if (op == VERIFY) { - verify(inp, outp, buff); - exit(0); - } - - if ((oldsig = signal(SIGINT, SIG_IGN)) != SIG_IGN) - (void) signal(SIGINT, intr); - - 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; - if (nread > 0) { - if (op == COPY || op == COPYVERIFY) { - if (needeof) { - writeop(outp, 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"); - } - } - size += nread; - record++; - } else { - if (lastnread <= 0 && lastnread != NOCOUNT) { - fprintf(msg, "eot\n"); - 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; - } - 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); - } - } - 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; - } - } - 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); -} diff --git a/usr.bin/tcopy/tcopy.cc b/usr.bin/tcopy/tcopy.cc new file mode 100644 index 000000000000..37a146376c2e --- /dev/null +++ b/usr.bin/tcopy/tcopy.cc @@ -0,0 +1,837 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Poul-Henning Kamp, <phk@FreeBSD.org> + * Copyright (c) 1985, 1987, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sysexits.h> +#include <unistd.h> + +#include <sys/endian.h> +#include <sys/mtio.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/uio.h> + +#include <libutil.h> + +#define MAXREC (1024 * 1024) +#define NOCOUNT (-2) + +enum operation {READ, VERIFY, COPY, COPYVERIFY}; + +// Stuff the tape_devs need to know about +static int filen; +static uint64_t record; + +//--------------------------------------------------------------------- + +class tape_dev { + size_t max_read_size; +public: + int fd; + char *name; + enum direction {SRC, DST} direction; + + tape_dev(int file_handle, const char *spec, bool destination); + + virtual ssize_t read_blk(void *dst, size_t len); + virtual ssize_t verify_blk(void *dst, size_t len, size_t expected); + virtual void write_blk(const void *src, size_t len); + virtual void file_mark(void); + virtual void rewind(void); +}; + +tape_dev::tape_dev(int file_handle, const char *spec, bool destination) +{ + assert(file_handle >= 0); + fd = file_handle; + name = strdup(spec); + assert(name != NULL); + direction = destination ? DST : SRC; + max_read_size = 0; +} + +ssize_t +tape_dev::read_blk(void *dst, size_t len) +{ + ssize_t retval = -1; + + if (max_read_size == 0) { + max_read_size = len; + while (max_read_size > 0) { + retval = read(fd, dst, max_read_size); + if (retval >= 0 || (errno != EINVAL && errno != EFBIG)) + break; + if (max_read_size < 512) + errx(1, "Cannot find a sane max blocksize"); + + // Reduce to next lower power of two + int i = flsl((long)max_read_size - 1L); + max_read_size = 1UL << (i - 1); + } + } else { + retval = read(fd, dst, (size_t)max_read_size); + } + if (retval < 0) { + err(1, "read error, %s, file %d, record %ju", + name, filen, (uintmax_t)record); + } + return (retval); +} + +ssize_t +tape_dev::verify_blk(void *dst, size_t len, size_t expected) +{ + (void)expected; + return read_blk(dst, len); +} + +void +tape_dev::write_blk(const void *src, size_t len) +{ + assert(len > 0); + ssize_t nwrite = write(fd, src, len); + if (nwrite < 0 || (size_t) 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; +} + +void +tape_dev::file_mark(void) +{ + struct mtop op; + + op.mt_op = MTWEOF; + op.mt_count = (daddr_t)1; + if (ioctl(fd, MTIOCTOP, (char *)&op) < 0) + err(6, "tape op (write file mark)"); +} + +void +tape_dev::rewind(void) +{ + struct mtop op; + + op.mt_op = MTREW; + op.mt_count = (daddr_t)1; + if (ioctl(fd, MTIOCTOP, (char *)&op) < 0) + err(6, "tape op (rewind)"); +} + +//--------------------------------------------------------------------- + +class tap_file: public tape_dev { +public: + tap_file(int file_handle, const char *spec, bool dst) : + tape_dev(file_handle, spec, dst) {}; + ssize_t read_blk(void *dst, size_t len); + void write_blk(const void *src, size_t len); + void file_mark(void); + virtual void rewind(void); +}; + +static +ssize_t full_read(int fd, void *dst, size_t len) +{ + // Input may be a socket which returns partial reads + + ssize_t retval = read(fd, dst, len); + if (retval <= 0 || (size_t)retval == len) + return (retval); + + char *ptr = (char *)dst + retval; + size_t left = len - (size_t)retval; + while (left > 0) { + retval = read(fd, ptr, left); + if (retval <= 0) + return (retval); + left -= (size_t)retval; + ptr += retval; + } + return ((ssize_t)len); +} + +ssize_t +tap_file::read_blk(void *dst, size_t len) +{ + char lbuf[4]; + + ssize_t nread = full_read(fd, lbuf, sizeof lbuf); + if (nread == 0) + return (0); + + if ((size_t)nread != sizeof lbuf) + err(EX_DATAERR, "Corrupt tap-file, read hdr1=%zd", nread); + + uint32_t u = le32dec(lbuf); + if (u == 0 || (u >> 24) == 0xff) + return(0); + + if (u > len) + err(17, "tapfile blocksize too big, 0x%08x", u); + + size_t alen = (u + 1) & ~1; + assert (alen <= len); + + ssize_t retval = full_read(fd, dst, alen); + if (retval < 0 || (size_t)retval != alen) + err(EX_DATAERR, "Corrupt tap-file, read data=%zd", retval); + + nread = full_read(fd, lbuf, sizeof lbuf); + if ((size_t)nread != sizeof lbuf) + err(EX_DATAERR, "Corrupt tap-file, read hdr2=%zd", nread); + + uint32_t v = le32dec(lbuf); + if (u == v) + return (u); + err(EX_DATAERR, + "Corrupt tap-file, headers differ (0x%08x != 0x%08x)", u, v); +} + +void +tap_file::write_blk(const void *src, size_t len) +{ + struct iovec iov[4]; + uint8_t zero = 0; + int niov = 0; + size_t expect = 0; + char tbuf[4]; + + assert((len & ~0xffffffffULL) == 0); + le32enc(tbuf, (uint32_t)len); + + iov[niov].iov_base = tbuf; + iov[niov].iov_len = sizeof tbuf; + expect += iov[niov].iov_len; + niov += 1; + + iov[niov].iov_base = (void*)(uintptr_t)src; + iov[niov].iov_len = len; + expect += iov[niov].iov_len; + niov += 1; + + if (len & 1) { + iov[niov].iov_base = &zero; + iov[niov].iov_len = 1; + expect += iov[niov].iov_len; + niov += 1; + } + + iov[niov].iov_base = tbuf; + iov[niov].iov_len = sizeof tbuf; + expect += iov[niov].iov_len; + niov += 1; + + ssize_t nwrite = writev(fd, iov, niov); + if (nwrite < 0 || (size_t)nwrite != expect) + errx(17, "write error (%zd != %zd)", nwrite, expect); +} + +void +tap_file::file_mark(void) +{ + char tbuf[4]; + le32enc(tbuf, 0); + ssize_t nwrite = write(fd, tbuf, sizeof tbuf); + if ((size_t)nwrite != sizeof tbuf) + errx(17, "write error (%zd != %zd)", nwrite, sizeof tbuf); +} + +void +tap_file::rewind(void) +{ + off_t where; + if (direction == DST) { + char tbuf[4]; + le32enc(tbuf, 0xffffffff); + ssize_t nwrite = write(fd, tbuf, sizeof tbuf); + if ((size_t)nwrite != sizeof tbuf) + errx(17, + "write error (%zd != %zd)", nwrite, sizeof tbuf); + } + where = lseek(fd, 0L, SEEK_SET); + if (where != 0 && errno == ESPIPE) + err(EX_USAGE, "Cannot rewind sockets and pipes"); + if (where != 0) + err(17, "lseek(0) failed"); +} + +//--------------------------------------------------------------------- + +class file_set: public tape_dev { +public: + file_set(int file_handle, const char *spec, bool dst) : + tape_dev(file_handle, spec, dst) {}; + ssize_t read_blk(void *dst, size_t len); + ssize_t verify_blk(void *dst, size_t len, size_t expected); + void write_blk(const void *src, size_t len); + void file_mark(void); + void rewind(void); + void open_next(bool increment); +}; + +void +file_set::open_next(bool increment) +{ + if (fd >= 0) { + assert(close(fd) >= 0); + fd = -1; + } + if (increment) { + char *p = strchr(name, '\0') - 3; + if (++p[2] == '9') { + p[2] = '0'; + if (++p[1] == '9') { + p[1] = '0'; + if (++p[0] == '9') { + errx(EX_USAGE, + "file-set sequence overflow"); + } + } + } + } + if (direction == DST) { + fd = open(name, O_RDWR|O_CREAT, DEFFILEMODE); + if (fd < 0) + err(1, "Could not open %s", name); + } else { + fd = open(name, O_RDONLY, 0); + } +} + +ssize_t +file_set::read_blk(void *dst, size_t len) +{ + (void)dst; + (void)len; + errx(EX_SOFTWARE, "That was not supposed to happen"); +} + +ssize_t +file_set::verify_blk(void *dst, size_t len, size_t expected) +{ + (void)len; + if (fd < 0) + open_next(true); + if (fd < 0) + return (0); + ssize_t retval = read(fd, dst, expected); + if (retval == 0) { + assert(close(fd) >= 0); + fd = -1; + } + return (retval); +} + +void +file_set::write_blk(const void *src, size_t len) +{ + if (fd < 0) + open_next(true); + ssize_t nwrite = write(fd, src, len); + if (nwrite < 0 || (size_t)nwrite != len) + errx(17, "write error (%zd != %zd)", nwrite, len); +} + +void +file_set::file_mark(void) +{ + if (fd < 0) + return; + + off_t where = lseek(fd, 0UL, SEEK_CUR); + + int i = ftruncate(fd, where); + if (i < 0) + errx(17, "truncate error, %s to %jd", name, (intmax_t)where); + assert(close(fd) >= 0); + fd = -1; +} + +void +file_set::rewind(void) +{ + char *p = strchr(name, '\0') - 3; + p[0] = '0'; + p[1] = '0'; + p[2] = '0'; + open_next(false); +} + +//--------------------------------------------------------------------- + +class flat_file: public tape_dev { +public: + flat_file(int file_handle, const char *spec, bool dst) : + tape_dev(file_handle, spec, dst) {}; + ssize_t read_blk(void *dst, size_t len); + ssize_t verify_blk(void *dst, size_t len, size_t expected); + void write_blk(const void *src, size_t len); + void file_mark(void); + virtual void rewind(void); +}; + +ssize_t +flat_file::read_blk(void *dst, size_t len) +{ + (void)dst; + (void)len; + errx(EX_SOFTWARE, "That was not supposed to happen"); +} + +ssize_t +flat_file::verify_blk(void *dst, size_t len, size_t expected) +{ + (void)len; + return (read(fd, dst, expected)); +} + +void +flat_file::write_blk(const void *src, size_t len) +{ + ssize_t nwrite = write(fd, src, len); + if (nwrite < 0 || (size_t)nwrite != len) + errx(17, "write error (%zd != %zd)", nwrite, len); +} + +void +flat_file::file_mark(void) +{ + return; +} + +void +flat_file::rewind(void) +{ + errx(EX_SOFTWARE, "That was not supposed to happen"); +} + +//--------------------------------------------------------------------- + +enum e_how {H_INPUT, H_OUTPUT, H_VERIFY}; + +static tape_dev * +open_arg(const char *arg, enum e_how how, int rawfile) +{ + int fd; + + if (!strcmp(arg, "-") && how == H_OUTPUT) + fd = STDOUT_FILENO; + else if (!strcmp(arg, "-")) + fd = STDIN_FILENO; + else if (how == H_OUTPUT) + fd = open(arg, O_RDWR|O_CREAT, DEFFILEMODE); + else + fd = open(arg, O_RDONLY); + + if (fd < 0) + err(EX_NOINPUT, "Cannot open %s:", arg); + + struct mtop mt; + mt.mt_op = MTNOP; + mt.mt_count = 1; + int i = ioctl(fd, MTIOCTOP, &mt); + + if (i >= 0) + return (new tape_dev(fd, arg, how == H_OUTPUT)); + + size_t alen = strlen(arg); + if (alen >= 5 && !strcmp(arg + (alen - 4), ".000")) { + if (how == H_INPUT) + errx(EX_USAGE, + "File-sets files cannot be used as source"); + return (new file_set(fd, arg, how == H_OUTPUT)); + } + + if (how != H_INPUT && rawfile) + return (new flat_file(fd, arg, how == H_OUTPUT)); + + return (new tap_file(fd, arg, how == H_OUTPUT)); +} + +//--------------------------------------------------------------------- + +static tape_dev *input; +static tape_dev *output; + +static size_t maxblk = MAXREC; +static uint64_t lastrec, fsize, tsize; +static FILE *msg; +static ssize_t lastnread; +static struct timespec t_start, t_end; + +static void +report_total(FILE *file) +{ + double dur = (t_end.tv_nsec - t_start.tv_nsec) * 1e-9; + dur += t_end.tv_sec - t_start.tv_sec; + uintmax_t tot = tsize + fsize; + fprintf(file, "total length: %ju bytes", tot); + fprintf(file, " time: %.0f s", dur); + tot /= 1024; + fprintf(file, " rate: %.1f kB/s", (double)tot/dur); + fprintf(file, "\n"); +} + +static void +sigintr(int signo __unused) +{ + (void)signo; + (void)clock_gettime(CLOCK_MONOTONIC, &t_end); + 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, (uintmax_t)record); + report_total(msg); + exit(1); +} + +#ifdef SIGINFO +static volatile sig_atomic_t want_info; + +static void +siginfo(int signo) +{ + (void)signo; + want_info = 1; +} + +static void +check_want_info(void) +{ + if (want_info) { + (void)clock_gettime(CLOCK_MONOTONIC, &t_end); + fprintf(stderr, "tcopy: file %d record %ju ", + filen, (uintmax_t)record); + report_total(stderr); + want_info = 0; + } +} + +#else /* !SIGINFO */ + +static void +check_want_info(void) +{ +} + +#endif + +static char * +getspace(size_t blk) +{ + void *bp; + + assert(blk > 0); + if ((bp = malloc(blk)) == NULL) + errx(11, "no memory"); + return ((char *)bp); +} + +static void +usage(void) +{ + fprintf(stderr, "usage: tcopy [-cvx] [-s maxblk] [src [dest]]\n"); + exit(1); +} + +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) { + fsize += (size_t)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)fsize); + filen++; + tsize += fsize; + fsize = record = lastrec = 0; + lastnread = 0; + } + lastnread = nread; +} + +static void +read_or_copy(void) +{ + int needeof; + ssize_t nread, prev_read; + char *buff = getspace(maxblk); + + (void)clock_gettime(CLOCK_MONOTONIC, &t_start); + needeof = 0; + for (prev_read = NOCOUNT;;) { + check_want_info(); + nread = input->read_blk(buff, maxblk); + progress(nread); + if (nread > 0) { + if (output != NULL) { + if (needeof) { + output->file_mark(); + needeof = 0; + } + output->write_blk(buff, (size_t)nread); + } + } else { + if (prev_read <= 0 && prev_read != NOCOUNT) { + break; + } + needeof = 1; + } + prev_read = nread; + } + (void)clock_gettime(CLOCK_MONOTONIC, &t_end); + report_total(msg); + free(buff); +} + +static void +verify(void) +{ + char *buf1 = getspace(maxblk); + char *buf2 = getspace(maxblk); + int eot = 0; + ssize_t nread1, nread2; + filen = 0; + tsize = 0; + + assert(output != NULL); + (void)clock_gettime(CLOCK_MONOTONIC, &t_start); + + while (1) { + check_want_info(); + nread1 = input->read_blk(buf1, (size_t)maxblk); + nread2 = output->verify_blk(buf2, maxblk, (size_t)nread1); + progress(nread1); + if (nread1 != nread2) { + fprintf(msg, + "tcopy: tapes have different block sizes; " + "%zd != %zd.\n", nread1, nread2); + exit(1); + } + if (nread1 > 0 && memcmp(buf1, buf2, (size_t)nread1)) { + fprintf(msg, "tcopy: tapes have different data.\n"); + exit(1); + } else if (nread1 > 0) { + eot = 0; + } else if (eot++) { + break; + } + } + (void)clock_gettime(CLOCK_MONOTONIC, &t_end); + report_total(msg); + fprintf(msg, "tcopy: tapes are identical.\n"); + fprintf(msg, "rewinding\n"); + input->rewind(); + output->rewind(); + + free(buf1); + free(buf2); +} + +int +main(int argc, char *argv[]) +{ + enum operation op = READ; + int ch; + unsigned long maxphys = 0; + size_t l_maxphys = sizeof maxphys; + int64_t tmp; + int rawfile = 0; + + setbuf(stderr, NULL); + + if (!sysctlbyname("kern.maxphys", &maxphys, &l_maxphys, NULL, 0UL)) + maxblk = maxphys; + + msg = stdout; + while ((ch = getopt(argc, argv, "cl:rs:vx")) != -1) + switch((char)ch) { + case 'c': + op = COPYVERIFY; + break; + case 'l': + msg = fopen(optarg, "w"); + if (msg == NULL) + errx(EX_CANTCREAT, "Cannot open %s", optarg); + setbuf(msg, NULL); + break; + case 'r': + rawfile = 1; + break; + case 's': + if (expand_number(optarg, &tmp)) { + warnx("illegal block size"); + usage(); + } + if (tmp <= 0) { + warnx("illegal block size"); + usage(); + } + maxblk = tmp; + break; + case 'v': + op = VERIFY; + break; + case 'x': + if (msg == stdout) + msg = stderr; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + switch(argc) { + case 0: + if (op != READ) + usage(); + break; + case 1: + if (op != READ) + usage(); + break; + case 2: + if (op == READ) + op = COPY; + if (!strcmp(argv[1], "-")) { + if (op == COPYVERIFY) + errx(EX_USAGE, + "Cannot copy+verify with '-' destination"); + if (msg == stdout) + msg = stderr; + } + if (op == VERIFY) + output = open_arg(argv[1], H_VERIFY, 0); + else + output = open_arg(argv[1], H_OUTPUT, rawfile); + break; + default: + usage(); + } + + if (argc == 0) { + input = open_arg(_PATH_DEFTAPE, H_INPUT, 0); + } else { + input = open_arg(argv[0], H_INPUT, 0); + } + + if ((signal(SIGINT, SIG_IGN)) != SIG_IGN) + (void) signal(SIGINT, sigintr); + +#ifdef SIGINFO + (void)signal(SIGINFO, siginfo); +#endif + + if (op != VERIFY) { + if (op == COPYVERIFY) { + assert(output != NULL); + fprintf(msg, "rewinding\n"); + input->rewind(); + output->rewind(); + } + + read_or_copy(); + + if (op == COPY || op == COPYVERIFY) { + assert(output != NULL); + output->file_mark(); + output->file_mark(); + } + } + + if (op == VERIFY || op == COPYVERIFY) { + + if (op == COPYVERIFY) { + assert(output != NULL); + fprintf(msg, "rewinding\n"); + input->rewind(); + output->rewind(); + input->direction = tape_dev::SRC; + output->direction = tape_dev::SRC; + } + + verify(); + } + + if (msg != stderr && msg != stdout) + report_total(stderr); + + return(0); +} |