#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};
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");
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)
{
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
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 [-crvx] [-l logfile] [-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);
}