xref: /freebsd/usr.bin/tcopy/tcopy.c (revision 7899f917b1c0ea178f1d2be0cfb452086d079d23)
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 <libutil.h>
41 #include <paths.h>
42 #include <sys/sysctl.h>
43 #include <signal.h>
44 #include <stdint.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 
50 #define	MAXREC	(64 * 1024)
51 #define	NOCOUNT	(-2)
52 
53 static int	filen, guesslen, maxblk = MAXREC;
54 static uint64_t	lastrec, record, size, tsize;
55 static FILE	*msg;
56 
57 static void	*getspace(int);
58 static void	 intr(int);
59 static void	 usage(void) __dead2;
60 static void	 verify(int, int, char *);
61 static void	 writeop(int, int);
62 static void	 rewind_tape(int);
63 
64 int
65 main(int argc, char *argv[])
66 {
67 	int lastnread, nread, nw, inp, outp;
68 	enum {READ, VERIFY, COPY, COPYVERIFY} op = READ;
69 	sig_t oldsig;
70 	int ch, needeof;
71 	char *buff;
72 	const char *inf;
73 	unsigned long maxphys = 0;
74 	size_t l_maxphys = sizeof maxphys;
75 	uint64_t tmp;
76 
77 	if (!sysctlbyname("kern.maxphys", &maxphys, &l_maxphys, NULL, 0))
78 		maxblk = maxphys;
79 
80 	msg = stdout;
81 	guesslen = 1;
82 	outp = -1;
83 	while ((ch = getopt(argc, argv, "cs:vx")) != -1)
84 		switch((char)ch) {
85 		case 'c':
86 			op = COPYVERIFY;
87 			break;
88 		case 's':
89 			if (expand_number(optarg, &tmp)) {
90 				warnx("illegal block size");
91 				usage();
92 			}
93 			maxblk = tmp;
94 			if (maxblk == 0) {
95 				warnx("illegal block size");
96 				usage();
97 			}
98 			guesslen = 0;
99 			break;
100 		case 'v':
101 			op = VERIFY;
102 			break;
103 		case 'x':
104 			msg = stderr;
105 			break;
106 		case '?':
107 		default:
108 			usage();
109 		}
110 	argc -= optind;
111 	argv += optind;
112 
113 	switch(argc) {
114 	case 0:
115 		if (op != READ)
116 			usage();
117 		inf = _PATH_DEFTAPE;
118 		break;
119 	case 1:
120 		if (op != READ)
121 			usage();
122 		inf = argv[0];
123 		break;
124 	case 2:
125 		if (op == READ)
126 			op = COPY;
127 		inf = argv[0];
128 		if ((outp = open(argv[1], op == VERIFY ? O_RDONLY :
129 		    op == COPY ? O_WRONLY : O_RDWR, DEFFILEMODE)) < 0)
130 			err(3, "%s", argv[1]);
131 		break;
132 	default:
133 		usage();
134 	}
135 
136 	if ((inp = open(inf, O_RDONLY, 0)) < 0)
137 		err(1, "%s", inf);
138 
139 	buff = getspace(maxblk);
140 
141 	if (op == VERIFY) {
142 		verify(inp, outp, buff);
143 		exit(0);
144 	}
145 
146 	if ((oldsig = signal(SIGINT, SIG_IGN)) != SIG_IGN)
147 		(void) signal(SIGINT, intr);
148 
149 	needeof = 0;
150 	for (lastnread = NOCOUNT;;) {
151 		if ((nread = read(inp, buff, maxblk)) == -1) {
152 			while (errno == EINVAL && (maxblk -= 1024)) {
153 				nread = read(inp, buff, maxblk);
154 				if (nread >= 0)
155 					goto r1;
156 			}
157 			err(1, "read error, file %d, record %ju", filen, (intmax_t)record);
158 		} else if (nread != lastnread) {
159 			if (lastnread != 0 && lastnread != NOCOUNT) {
160 				if (lastrec == 0 && nread == 0)
161 					fprintf(msg, "%ju records\n", (intmax_t)record);
162 				else if (record - lastrec > 1)
163 					fprintf(msg, "records %ju to %ju\n",
164 					    (intmax_t)lastrec, (intmax_t)record);
165 				else
166 					fprintf(msg, "record %ju\n", (intmax_t)lastrec);
167 			}
168 			if (nread != 0)
169 				fprintf(msg, "file %d: block size %d: ",
170 				    filen, nread);
171 			(void) fflush(stdout);
172 			lastrec = record;
173 		}
174 r1:		guesslen = 0;
175 		if (nread > 0) {
176 			if (op == COPY || op == COPYVERIFY) {
177 				if (needeof) {
178 					writeop(outp, MTWEOF);
179 					needeof = 0;
180 				}
181 				nw = write(outp, buff, nread);
182 				if (nw != nread) {
183 					if (nw == -1) {
184 						warn("write error, file %d, record %ju", filen,
185 						    (intmax_t)record);
186 					} else {
187 						warnx("write error, file %d, record %ju", filen,
188 						    (intmax_t)record);
189 						warnx("write (%d) != read (%d)", nw, nread);
190 					}
191 					errx(5, "copy aborted");
192 				}
193 			}
194 			size += nread;
195 			record++;
196 		} else {
197 			if (lastnread <= 0 && lastnread != NOCOUNT) {
198 				fprintf(msg, "eot\n");
199 				break;
200 			}
201 			fprintf(msg,
202 			    "file %d: eof after %ju records: %ju bytes\n",
203 			    filen, (intmax_t)record, (intmax_t)size);
204 			needeof = 1;
205 			filen++;
206 			tsize += size;
207 			size = record = lastrec = 0;
208 			lastnread = 0;
209 		}
210 		lastnread = nread;
211 	}
212 	fprintf(msg, "total length: %ju bytes\n", (intmax_t)tsize);
213 	(void)signal(SIGINT, oldsig);
214 	if (op == COPY || op == COPYVERIFY) {
215 		writeop(outp, MTWEOF);
216 		writeop(outp, MTWEOF);
217 		if (op == COPYVERIFY) {
218 			rewind_tape(outp);
219 			rewind_tape(inp);
220 			verify(inp, outp, buff);
221 		}
222 	}
223 	exit(0);
224 }
225 
226 static void
227 verify(int inp, int outp, char *outb)
228 {
229 	int eot, inmaxblk, inn, outmaxblk, outn;
230 	char *inb;
231 
232 	inb = getspace(maxblk);
233 	inmaxblk = outmaxblk = maxblk;
234 	for (eot = 0;; guesslen = 0) {
235 		if ((inn = read(inp, inb, inmaxblk)) == -1) {
236 			if (guesslen)
237 				while (errno == EINVAL && (inmaxblk -= 1024)) {
238 					inn = read(inp, inb, inmaxblk);
239 					if (inn >= 0)
240 						goto r1;
241 				}
242 			warn("read error");
243 			break;
244 		}
245 r1:		if ((outn = read(outp, outb, outmaxblk)) == -1) {
246 			if (guesslen)
247 				while (errno == EINVAL && (outmaxblk -= 1024)) {
248 					outn = read(outp, outb, outmaxblk);
249 					if (outn >= 0)
250 						goto r2;
251 				}
252 			warn("read error");
253 			break;
254 		}
255 r2:		if (inn != outn) {
256 			fprintf(msg,
257 			    "%s: tapes have different block sizes; %d != %d.\n",
258 			    "tcopy", inn, outn);
259 			break;
260 		}
261 		if (!inn) {
262 			if (eot++) {
263 				fprintf(msg, "tcopy: tapes are identical.\n");
264 				free(inb);
265 				return;
266 			}
267 		} else {
268 			if (bcmp(inb, outb, inn)) {
269 				fprintf(msg,
270 				    "tcopy: tapes have different data.\n");
271 				break;
272 			}
273 			eot = 0;
274 		}
275 	}
276 	exit(1);
277 }
278 
279 static void
280 intr(int signo __unused)
281 {
282 	if (record) {
283 		if (record - lastrec > 1)
284 			fprintf(msg, "records %ju to %ju\n", (intmax_t)lastrec, (intmax_t)record);
285 		else
286 			fprintf(msg, "record %ju\n", (intmax_t)lastrec);
287 	}
288 	fprintf(msg, "interrupt at file %d: record %ju\n", filen, (intmax_t)record);
289 	fprintf(msg, "total length: %ju bytes\n", (uintmax_t)(tsize + size));
290 	exit(1);
291 }
292 
293 static void *
294 getspace(int blk)
295 {
296 	void *bp;
297 
298 	if ((bp = malloc((size_t)blk)) == NULL)
299 		errx(11, "no memory");
300 	return (bp);
301 }
302 
303 static void
304 writeop(int fd, int type)
305 {
306 	struct mtop op;
307 
308 	op.mt_op = type;
309 	op.mt_count = (daddr_t)1;
310 	if (ioctl(fd, MTIOCTOP, (char *)&op) < 0)
311 		err(6, "tape op");
312 }
313 
314 static void
315 usage(void)
316 {
317 	fprintf(stderr, "usage: tcopy [-cvx] [-s maxblk] [src [dest]]\n");
318 	exit(1);
319 }
320 
321 static void
322 rewind_tape(int fd)
323 {
324 	struct stat sp;
325 
326 	if(fstat(fd, &sp))
327 		errx(12, "fstat in rewind");
328 
329 	/*
330 	 * don't want to do tape ioctl on regular files:
331 	 */
332 	if( S_ISREG(sp.st_mode) ) {
333 		if( lseek(fd, 0, SEEK_SET) == -1 )
334 			errx(13, "lseek");
335 	} else
336 		/*  assume its a tape	*/
337 		writeop(fd, MTREW);
338 }
339