1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1985, 1987, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <sys/ioctl.h>
35#include <sys/mtio.h>
36
37#include <err.h>
38#include <errno.h>
39#include <fcntl.h>
40#include <paths.h>
41#include <sys/sysctl.h>
42#include <signal.h>
43#include <stdint.h>
44#include <stdio.h>
45#include <stdlib.h>
46#include <string.h>
47#include <unistd.h>
48
49#define	MAXREC	(64 * 1024)
50#define	NOCOUNT	(-2)
51
52static int	filen, guesslen, maxblk = MAXREC;
53static uint64_t	lastrec, record, size, tsize;
54static FILE	*msg;
55
56static void	*getspace(int);
57static void	 intr(int);
58static void	 usage(void) __dead2;
59static void	 verify(int, int, char *);
60static void	 writeop(int, int);
61static void	 rewind_tape(int);
62
63int
64main(int argc, char *argv[])
65{
66	int lastnread, nread, nw, inp, outp;
67	enum {READ, VERIFY, COPY, COPYVERIFY} op = READ;
68	sig_t oldsig;
69	int ch, needeof;
70	char *buff;
71	const char *inf;
72	unsigned long maxphys = 0;
73	size_t l_maxphys = sizeof maxphys;
74
75	if (!sysctlbyname("kern.maxphys", &maxphys, &l_maxphys, NULL, 0))
76		maxblk = maxphys;
77
78	msg = stdout;
79	guesslen = 1;
80	outp = -1;
81	while ((ch = getopt(argc, argv, "cs:vx")) != -1)
82		switch((char)ch) {
83		case 'c':
84			op = COPYVERIFY;
85			break;
86		case 's':
87			maxblk = atoi(optarg);
88			if (maxblk <= 0) {
89				warnx("illegal block size");
90				usage();
91			}
92			guesslen = 0;
93			break;
94		case 'v':
95			op = VERIFY;
96			break;
97		case 'x':
98			msg = stderr;
99			break;
100		case '?':
101		default:
102			usage();
103		}
104	argc -= optind;
105	argv += optind;
106
107	switch(argc) {
108	case 0:
109		if (op != READ)
110			usage();
111		inf = _PATH_DEFTAPE;
112		break;
113	case 1:
114		if (op != READ)
115			usage();
116		inf = argv[0];
117		break;
118	case 2:
119		if (op == READ)
120			op = COPY;
121		inf = argv[0];
122		if ((outp = open(argv[1], op == VERIFY ? O_RDONLY :
123		    op == COPY ? O_WRONLY : O_RDWR, DEFFILEMODE)) < 0)
124			err(3, "%s", argv[1]);
125		break;
126	default:
127		usage();
128	}
129
130	if ((inp = open(inf, O_RDONLY, 0)) < 0)
131		err(1, "%s", inf);
132
133	buff = getspace(maxblk);
134
135	if (op == VERIFY) {
136		verify(inp, outp, buff);
137		exit(0);
138	}
139
140	if ((oldsig = signal(SIGINT, SIG_IGN)) != SIG_IGN)
141		(void) signal(SIGINT, intr);
142
143	needeof = 0;
144	for (lastnread = NOCOUNT;;) {
145		if ((nread = read(inp, buff, maxblk)) == -1) {
146			while (errno == EINVAL && (maxblk -= 1024)) {
147				nread = read(inp, buff, maxblk);
148				if (nread >= 0)
149					goto r1;
150			}
151			err(1, "read error, file %d, record %ju", filen, (intmax_t)record);
152		} else if (nread != lastnread) {
153			if (lastnread != 0 && lastnread != NOCOUNT) {
154				if (lastrec == 0 && nread == 0)
155					fprintf(msg, "%ju records\n", (intmax_t)record);
156				else if (record - lastrec > 1)
157					fprintf(msg, "records %ju to %ju\n",
158					    (intmax_t)lastrec, (intmax_t)record);
159				else
160					fprintf(msg, "record %ju\n", (intmax_t)lastrec);
161			}
162			if (nread != 0)
163				fprintf(msg, "file %d: block size %d: ",
164				    filen, nread);
165			(void) fflush(stdout);
166			lastrec = record;
167		}
168r1:		guesslen = 0;
169		if (nread > 0) {
170			if (op == COPY || op == COPYVERIFY) {
171				if (needeof) {
172					writeop(outp, MTWEOF);
173					needeof = 0;
174				}
175				nw = write(outp, buff, nread);
176				if (nw != nread) {
177					if (nw == -1) {
178						warn("write error, file %d, record %ju", filen,
179						    (intmax_t)record);
180					} else {
181						warnx("write error, file %d, record %ju", filen,
182						    (intmax_t)record);
183						warnx("write (%d) != read (%d)", nw, nread);
184					}
185					errx(5, "copy aborted");
186				}
187			}
188			size += nread;
189			record++;
190		} else {
191			if (lastnread <= 0 && lastnread != NOCOUNT) {
192				fprintf(msg, "eot\n");
193				break;
194			}
195			fprintf(msg,
196			    "file %d: eof after %ju records: %ju bytes\n",
197			    filen, (intmax_t)record, (intmax_t)size);
198			needeof = 1;
199			filen++;
200			tsize += size;
201			size = record = lastrec = 0;
202			lastnread = 0;
203		}
204		lastnread = nread;
205	}
206	fprintf(msg, "total length: %ju bytes\n", (intmax_t)tsize);
207	(void)signal(SIGINT, oldsig);
208	if (op == COPY || op == COPYVERIFY) {
209		writeop(outp, MTWEOF);
210		writeop(outp, MTWEOF);
211		if (op == COPYVERIFY) {
212			rewind_tape(outp);
213			rewind_tape(inp);
214			verify(inp, outp, buff);
215		}
216	}
217	exit(0);
218}
219
220static void
221verify(int inp, int outp, char *outb)
222{
223	int eot, inmaxblk, inn, outmaxblk, outn;
224	char *inb;
225
226	inb = getspace(maxblk);
227	inmaxblk = outmaxblk = maxblk;
228	for (eot = 0;; guesslen = 0) {
229		if ((inn = read(inp, inb, inmaxblk)) == -1) {
230			if (guesslen)
231				while (errno == EINVAL && (inmaxblk -= 1024)) {
232					inn = read(inp, inb, inmaxblk);
233					if (inn >= 0)
234						goto r1;
235				}
236			warn("read error");
237			break;
238		}
239r1:		if ((outn = read(outp, outb, outmaxblk)) == -1) {
240			if (guesslen)
241				while (errno == EINVAL && (outmaxblk -= 1024)) {
242					outn = read(outp, outb, outmaxblk);
243					if (outn >= 0)
244						goto r2;
245				}
246			warn("read error");
247			break;
248		}
249r2:		if (inn != outn) {
250			fprintf(msg,
251			    "%s: tapes have different block sizes; %d != %d.\n",
252			    "tcopy", inn, outn);
253			break;
254		}
255		if (!inn) {
256			if (eot++) {
257				fprintf(msg, "tcopy: tapes are identical.\n");
258				free(inb);
259				return;
260			}
261		} else {
262			if (bcmp(inb, outb, inn)) {
263				fprintf(msg,
264				    "tcopy: tapes have different data.\n");
265				break;
266			}
267			eot = 0;
268		}
269	}
270	exit(1);
271}
272
273static void
274intr(int signo __unused)
275{
276	if (record) {
277		if (record - lastrec > 1)
278			fprintf(msg, "records %ju to %ju\n", (intmax_t)lastrec, (intmax_t)record);
279		else
280			fprintf(msg, "record %ju\n", (intmax_t)lastrec);
281	}
282	fprintf(msg, "interrupt at file %d: record %ju\n", filen, (intmax_t)record);
283	fprintf(msg, "total length: %ju bytes\n", (uintmax_t)(tsize + size));
284	exit(1);
285}
286
287static void *
288getspace(int blk)
289{
290	void *bp;
291
292	if ((bp = malloc((size_t)blk)) == NULL)
293		errx(11, "no memory");
294	return (bp);
295}
296
297static void
298writeop(int fd, int type)
299{
300	struct mtop op;
301
302	op.mt_op = type;
303	op.mt_count = (daddr_t)1;
304	if (ioctl(fd, MTIOCTOP, (char *)&op) < 0)
305		err(6, "tape op");
306}
307
308static void
309usage(void)
310{
311	fprintf(stderr, "usage: tcopy [-cvx] [-s maxblk] [src [dest]]\n");
312	exit(1);
313}
314
315static void
316rewind_tape(int fd)
317{
318	struct stat sp;
319
320	if(fstat(fd, &sp))
321		errx(12, "fstat in rewind");
322
323	/*
324	 * don't want to do tape ioctl on regular files:
325	 */
326	if( S_ISREG(sp.st_mode) ) {
327		if( lseek(fd, 0, SEEK_SET) == -1 )
328			errx(13, "lseek");
329	} else
330		/*  assume its a tape	*/
331		writeop(fd, MTREW);
332}
333