aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/tcopy/tcopy.cc
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/tcopy/tcopy.cc')
-rw-r--r--usr.bin/tcopy/tcopy.cc837
1 files changed, 837 insertions, 0 deletions
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);
+}