xref: /freebsd/usr.bin/tcopy/tcopy.cc (revision 6582915c59e8a974d65fafc25c39ec2713a02d28)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2025 Poul-Henning Kamp, <phk@FreeBSD.org>
5  * Copyright (c) 1985, 1987, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #include <assert.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <paths.h>
35 #include <signal.h>
36 #include <stdint.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <time.h>
41 #include <sysexits.h>
42 #include <unistd.h>
43 
44 #include <sys/endian.h>
45 #include <sys/mtio.h>
46 #include <sys/stat.h>
47 #include <sys/sysctl.h>
48 #include <sys/uio.h>
49 
50 #include <libutil.h>
51 
52 #define	MAXREC	(1024 * 1024)
53 #define	NOCOUNT	(-2)
54 
55 enum operation {READ, VERIFY, COPY, COPYVERIFY};
56 
57 // Stuff the tape_devs need to know about
58 static int filen;
59 static uint64_t	record;
60 
61 //---------------------------------------------------------------------
62 
63 class tape_dev {
64 	size_t	max_read_size;
65 public:
66 	int fd;
67 	char *name;
68 	enum direction {SRC, DST} direction;
69 
70 	tape_dev(int file_handle, const char *spec, bool destination);
71 
72 	virtual ssize_t read_blk(void *dst, size_t len);
73 	virtual ssize_t verify_blk(void *dst, size_t len, size_t expected);
74 	virtual void write_blk(const void *src, size_t len);
75 	virtual void file_mark(void);
76 	virtual void rewind(void);
77 };
78 
tape_dev(int file_handle,const char * spec,bool destination)79 tape_dev::tape_dev(int file_handle, const char *spec, bool destination)
80 {
81 	assert(file_handle >= 0);
82 	fd = file_handle;
83 	name = strdup(spec);
84 	assert(name != NULL);
85 	direction = destination ? DST : SRC;
86 	max_read_size = 0;
87 }
88 
89 ssize_t
read_blk(void * dst,size_t len)90 tape_dev::read_blk(void *dst, size_t len)
91 {
92 	ssize_t retval = -1;
93 
94 	if (max_read_size == 0) {
95 		max_read_size = len;
96 		while (max_read_size > 0) {
97 			retval = read(fd, dst, max_read_size);
98 			if (retval >= 0 || (errno != EINVAL && errno != EFBIG))
99 				break;
100 			if (max_read_size < 512)
101 				errx(1, "Cannot find a sane max blocksize");
102 
103 			// Reduce to next lower power of two
104 			int i = flsl((long)max_read_size - 1L);
105 			max_read_size = 1UL << (i - 1);
106 		}
107 	} else {
108 		retval = read(fd, dst, (size_t)max_read_size);
109 	}
110 	if (retval < 0) {
111 		err(1, "read error, %s, file %d, record %ju",
112 		    name, filen, (uintmax_t)record);
113 	}
114 	return (retval);
115 }
116 
117 ssize_t
verify_blk(void * dst,size_t len,size_t expected)118 tape_dev::verify_blk(void *dst, size_t len, size_t expected)
119 {
120 	(void)expected;
121 	return read_blk(dst, len);
122 }
123 
124 void
write_blk(const void * src,size_t len)125 tape_dev::write_blk(const void *src, size_t len)
126 {
127 	assert(len > 0);
128 	ssize_t nwrite = write(fd, src, len);
129 	if (nwrite < 0 || (size_t) nwrite != len) {
130 		if (nwrite == -1) {
131 			warn("write error, file %d, record %ju",
132 			    filen, (intmax_t)record);
133 		} else {
134 			warnx("write error, file %d, record %ju",
135 			    filen, (intmax_t)record);
136 			warnx("write (%zd) != read (%zd)", nwrite, len);
137 		}
138 		errx(5, "copy aborted");
139 	}
140 	return;
141 }
142 
143 void
file_mark(void)144 tape_dev::file_mark(void)
145 {
146 	struct mtop op;
147 
148 	op.mt_op = MTWEOF;
149 	op.mt_count = (daddr_t)1;
150 	if (ioctl(fd, MTIOCTOP, (char *)&op) < 0)
151 		err(6, "tape op (write file mark)");
152 }
153 
154 void
rewind(void)155 tape_dev::rewind(void)
156 {
157 	struct mtop op;
158 
159 	op.mt_op = MTREW;
160 	op.mt_count = (daddr_t)1;
161 	if (ioctl(fd, MTIOCTOP, (char *)&op) < 0)
162 		err(6, "tape op (rewind)");
163 }
164 
165 //---------------------------------------------------------------------
166 
167 class tap_file: public tape_dev {
168 public:
tap_file(int file_handle,const char * spec,bool dst)169 	tap_file(int file_handle, const char *spec, bool dst) :
170 		tape_dev(file_handle, spec, dst) {};
171 	ssize_t read_blk(void *dst, size_t len);
172 	void write_blk(const void *src, size_t len);
173 	void file_mark(void);
174 	virtual void rewind(void);
175 };
176 
177 static
full_read(int fd,void * dst,size_t len)178 ssize_t full_read(int fd, void *dst, size_t len)
179 {
180 	// Input may be a socket which returns partial reads
181 
182 	ssize_t retval = read(fd, dst, len);
183 	if (retval <= 0 || (size_t)retval == len)
184 		return (retval);
185 
186 	char *ptr = (char *)dst + retval;
187 	size_t left = len - (size_t)retval;
188 	while (left > 0) {
189 		retval = read(fd, ptr, left);
190 		if (retval <= 0)
191 			return (retval);
192 		left -= (size_t)retval;
193 		ptr += retval;
194 	}
195 	return ((ssize_t)len);
196 }
197 
198 ssize_t
read_blk(void * dst,size_t len)199 tap_file::read_blk(void *dst, size_t len)
200 {
201 	char lbuf[4];
202 
203 	ssize_t nread = full_read(fd, lbuf, sizeof lbuf);
204 	if (nread == 0)
205 		return (0);
206 
207 	if ((size_t)nread != sizeof lbuf)
208 		err(EX_DATAERR, "Corrupt tap-file, read hdr1=%zd", nread);
209 
210 	uint32_t u = le32dec(lbuf);
211 	if (u == 0 || (u >> 24) == 0xff)
212 		return(0);
213 
214 	if (u > len)
215 		err(17, "tapfile blocksize too big, 0x%08x", u);
216 
217 	size_t alen = (u + 1) & ~1;
218 	assert (alen <= len);
219 
220 	ssize_t retval = full_read(fd, dst, alen);
221 	if (retval < 0 || (size_t)retval != alen)
222 		err(EX_DATAERR, "Corrupt tap-file, read data=%zd", retval);
223 
224 	nread = full_read(fd, lbuf, sizeof lbuf);
225 	if ((size_t)nread != sizeof lbuf)
226 		err(EX_DATAERR, "Corrupt tap-file, read hdr2=%zd", nread);
227 
228 	uint32_t v = le32dec(lbuf);
229 	if (u == v)
230 		return (u);
231 	err(EX_DATAERR,
232 	    "Corrupt tap-file, headers differ (0x%08x != 0x%08x)", u, v);
233 }
234 
235 void
write_blk(const void * src,size_t len)236 tap_file::write_blk(const void *src, size_t len)
237 {
238 	struct iovec iov[4];
239 	uint8_t zero = 0;
240 	int niov = 0;
241 	size_t expect = 0;
242 	char tbuf[4];
243 
244 	assert((len & ~0xffffffffULL) == 0);
245 	le32enc(tbuf, (uint32_t)len);
246 
247 	iov[niov].iov_base = tbuf;
248 	iov[niov].iov_len = sizeof tbuf;
249 	expect += iov[niov].iov_len;
250 	niov += 1;
251 
252 	iov[niov].iov_base = (void*)(uintptr_t)src;
253 	iov[niov].iov_len = len;
254 	expect += iov[niov].iov_len;
255 	niov += 1;
256 
257 	if (len & 1) {
258 		iov[niov].iov_base = &zero;
259 		iov[niov].iov_len = 1;
260 		expect += iov[niov].iov_len;
261 		niov += 1;
262 	}
263 
264 	iov[niov].iov_base = tbuf;
265 	iov[niov].iov_len = sizeof tbuf;
266 	expect += iov[niov].iov_len;
267 	niov += 1;
268 
269 	ssize_t nwrite = writev(fd, iov, niov);
270 	if (nwrite < 0 || (size_t)nwrite != expect)
271 		errx(17, "write error (%zd != %zd)", nwrite, expect);
272 }
273 
274 void
file_mark(void)275 tap_file::file_mark(void)
276 {
277 	char tbuf[4];
278 	le32enc(tbuf, 0);
279 	ssize_t nwrite = write(fd, tbuf, sizeof tbuf);
280 	if ((size_t)nwrite != sizeof tbuf)
281 		errx(17, "write error (%zd != %zd)", nwrite, sizeof tbuf);
282 }
283 
284 void
rewind(void)285 tap_file::rewind(void)
286 {
287 	off_t where;
288 	if (direction == DST) {
289 		char tbuf[4];
290 		le32enc(tbuf, 0xffffffff);
291 		ssize_t nwrite = write(fd, tbuf, sizeof tbuf);
292 		if ((size_t)nwrite != sizeof tbuf)
293 			errx(17,
294 			    "write error (%zd != %zd)", nwrite, sizeof tbuf);
295 	}
296 	where = lseek(fd, 0L, SEEK_SET);
297 	if (where != 0 && errno == ESPIPE)
298 		err(EX_USAGE, "Cannot rewind sockets and pipes");
299 	if (where != 0)
300 		err(17, "lseek(0) failed");
301 }
302 
303 //---------------------------------------------------------------------
304 
305 class file_set: public tape_dev {
306 public:
file_set(int file_handle,const char * spec,bool dst)307 	file_set(int file_handle, const char *spec, bool dst) :
308 		tape_dev(file_handle, spec, dst) {};
309 	ssize_t read_blk(void *dst, size_t len);
310 	ssize_t verify_blk(void *dst, size_t len, size_t expected);
311 	void write_blk(const void *src, size_t len);
312 	void file_mark(void);
313 	void rewind(void);
314 	void open_next(bool increment);
315 };
316 
317 void
open_next(bool increment)318 file_set::open_next(bool increment)
319 {
320 	if (fd >= 0) {
321 		assert(close(fd) >= 0);
322 		fd = -1;
323 	}
324 	if (increment) {
325 		char *p = strchr(name, '\0') - 3;
326 		if (++p[2] == '9') {
327 			p[2] = '0';
328 			if (++p[1] == '9') {
329 				p[1] = '0';
330 				if (++p[0] == '9') {
331 					errx(EX_USAGE,
332 					    "file-set sequence overflow");
333 				}
334 			}
335 		}
336 	}
337 	if (direction == DST) {
338 		fd = open(name, O_RDWR|O_CREAT, DEFFILEMODE);
339 		if (fd < 0)
340 			err(1, "Could not open %s", name);
341 	} else {
342 		fd = open(name, O_RDONLY, 0);
343 	}
344 }
345 
346 ssize_t
read_blk(void * dst,size_t len)347 file_set::read_blk(void *dst, size_t len)
348 {
349 	(void)dst;
350 	(void)len;
351 	errx(EX_SOFTWARE, "That was not supposed to happen");
352 }
353 
354 ssize_t
verify_blk(void * dst,size_t len,size_t expected)355 file_set::verify_blk(void *dst, size_t len, size_t expected)
356 {
357 	(void)len;
358 	if (fd < 0)
359 		open_next(true);
360 	if (fd < 0)
361 		return (0);
362 	ssize_t retval = read(fd, dst, expected);
363 	if (retval == 0) {
364 		assert(close(fd) >= 0);
365 		fd = -1;
366 	}
367 	return (retval);
368 }
369 
370 void
write_blk(const void * src,size_t len)371 file_set::write_blk(const void *src, size_t len)
372 {
373 	if (fd < 0)
374 		open_next(true);
375 	ssize_t nwrite = write(fd, src, len);
376 	if (nwrite < 0 || (size_t)nwrite != len)
377 		errx(17, "write error (%zd != %zd)", nwrite, len);
378 }
379 
380 void
file_mark(void)381 file_set::file_mark(void)
382 {
383 	if (fd < 0)
384 		return;
385 
386 	off_t where = lseek(fd, 0UL, SEEK_CUR);
387 
388 	int i = ftruncate(fd, where);
389 	if (i < 0)
390 		errx(17, "truncate error, %s to %jd", name, (intmax_t)where);
391 	assert(close(fd) >= 0);
392 	fd = -1;
393 }
394 
395 void
rewind(void)396 file_set::rewind(void)
397 {
398 	char *p = strchr(name, '\0') - 3;
399 	p[0] = '0';
400 	p[1] = '0';
401 	p[2] = '0';
402 	open_next(false);
403 }
404 
405 //---------------------------------------------------------------------
406 
407 class flat_file: public tape_dev {
408 public:
flat_file(int file_handle,const char * spec,bool dst)409 	flat_file(int file_handle, const char *spec, bool dst) :
410 		tape_dev(file_handle, spec, dst) {};
411 	ssize_t read_blk(void *dst, size_t len);
412 	ssize_t verify_blk(void *dst, size_t len, size_t expected);
413 	void write_blk(const void *src, size_t len);
414 	void file_mark(void);
415 	virtual void rewind(void);
416 };
417 
418 ssize_t
read_blk(void * dst,size_t len)419 flat_file::read_blk(void *dst, size_t len)
420 {
421 	(void)dst;
422 	(void)len;
423 	errx(EX_SOFTWARE, "That was not supposed to happen");
424 }
425 
426 ssize_t
verify_blk(void * dst,size_t len,size_t expected)427 flat_file::verify_blk(void *dst, size_t len, size_t expected)
428 {
429 	(void)len;
430 	return (read(fd, dst, expected));
431 }
432 
433 void
write_blk(const void * src,size_t len)434 flat_file::write_blk(const void *src, size_t len)
435 {
436 	ssize_t nwrite = write(fd, src, len);
437 	if (nwrite < 0 || (size_t)nwrite != len)
438 		errx(17, "write error (%zd != %zd)", nwrite, len);
439 }
440 
441 void
file_mark(void)442 flat_file::file_mark(void)
443 {
444 	return;
445 }
446 
447 void
rewind(void)448 flat_file::rewind(void)
449 {
450 	errx(EX_SOFTWARE, "That was not supposed to happen");
451 }
452 
453 //---------------------------------------------------------------------
454 
455 enum e_how {H_INPUT, H_OUTPUT, H_VERIFY};
456 
457 static tape_dev *
open_arg(const char * arg,enum e_how how,int rawfile)458 open_arg(const char *arg, enum e_how how, int rawfile)
459 {
460 	int fd;
461 
462 	if (!strcmp(arg, "-") && how == H_OUTPUT)
463 		fd = STDOUT_FILENO;
464 	else if (!strcmp(arg, "-"))
465 		fd = STDIN_FILENO;
466 	else if (how == H_OUTPUT)
467 		fd = open(arg, O_RDWR|O_CREAT, DEFFILEMODE);
468 	else
469 		fd = open(arg, O_RDONLY);
470 
471 	if (fd < 0)
472 		err(EX_NOINPUT, "Cannot open %s:", arg);
473 
474 	struct mtop mt;
475 	mt.mt_op = MTNOP;
476 	mt.mt_count = 1;
477 	int i = ioctl(fd, MTIOCTOP, &mt);
478 
479 	if (i >= 0)
480 		return (new tape_dev(fd, arg, how == H_OUTPUT));
481 
482 	size_t alen = strlen(arg);
483 	if (alen >= 5 && !strcmp(arg + (alen - 4), ".000")) {
484 		if (how == H_INPUT)
485 			errx(EX_USAGE,
486 			    "File-sets files cannot be used as source");
487 		return (new file_set(fd, arg, how == H_OUTPUT));
488 	}
489 
490 	if (how != H_INPUT && rawfile)
491 		return (new flat_file(fd, arg, how == H_OUTPUT));
492 
493 	return (new tap_file(fd, arg, how == H_OUTPUT));
494 }
495 
496 //---------------------------------------------------------------------
497 
498 static tape_dev *input;
499 static tape_dev *output;
500 
501 static size_t maxblk = MAXREC;
502 static uint64_t	lastrec, fsize, tsize;
503 static FILE *msg;
504 static ssize_t lastnread;
505 static struct timespec t_start, t_end;
506 
507 static void
report_total(FILE * file)508 report_total(FILE *file)
509 {
510 	double dur = (t_end.tv_nsec - t_start.tv_nsec) * 1e-9;
511 	dur += t_end.tv_sec - t_start.tv_sec;
512 	uintmax_t tot = tsize + fsize;
513 	fprintf(file, "total length: %ju bytes", tot);
514 	fprintf(file, " time: %.0f s", dur);
515 	tot /= 1024;
516 	fprintf(file, " rate: %.1f kB/s", (double)tot/dur);
517 	fprintf(file, "\n");
518 }
519 
520 static void
sigintr(int signo __unused)521 sigintr(int signo __unused)
522 {
523 	(void)signo;
524 	(void)clock_gettime(CLOCK_MONOTONIC, &t_end);
525 	if (record) {
526 		if (record - lastrec > 1)
527 			fprintf(msg, "records %ju to %ju\n",
528 			    (intmax_t)lastrec, (intmax_t)record);
529 		else
530 			fprintf(msg, "record %ju\n", (intmax_t)lastrec);
531 	}
532 	fprintf(msg, "interrupt at file %d: record %ju\n",
533 	    filen, (uintmax_t)record);
534 	report_total(msg);
535 	exit(1);
536 }
537 
538 #ifdef SIGINFO
539 static volatile sig_atomic_t want_info;
540 
541 static void
siginfo(int signo)542 siginfo(int signo)
543 {
544 	(void)signo;
545 	want_info = 1;
546 }
547 
548 static void
check_want_info(void)549 check_want_info(void)
550 {
551 	if (want_info) {
552 		(void)clock_gettime(CLOCK_MONOTONIC, &t_end);
553 		fprintf(stderr, "tcopy: file %d record %ju ",
554 		    filen, (uintmax_t)record);
555 		report_total(stderr);
556 		want_info = 0;
557 	}
558 }
559 
560 #else /* !SIGINFO */
561 
562 static void
check_want_info(void)563 check_want_info(void)
564 {
565 }
566 
567 #endif
568 
569 static char *
getspace(size_t blk)570 getspace(size_t blk)
571 {
572 	void *bp;
573 
574 	assert(blk > 0);
575 	if ((bp = malloc(blk)) == NULL)
576 		errx(11, "no memory");
577 	return ((char *)bp);
578 }
579 
580 static void
usage(void)581 usage(void)
582 {
583 	fprintf(stderr, "usage: tcopy [-cvx] [-s maxblk] [src [dest]]\n");
584 	exit(1);
585 }
586 
587 static void
progress(ssize_t nread)588 progress(ssize_t nread)
589 {
590 	if (nread != lastnread) {
591 		if (lastnread != 0 && lastnread != NOCOUNT) {
592 			if (lastrec == 0 && nread == 0)
593 				fprintf(msg, "%ju records\n",
594 				    (uintmax_t)record);
595 			else if (record - lastrec > 1)
596 				fprintf(msg, "records %ju to %ju\n",
597 				    (uintmax_t)lastrec,
598 				    (uintmax_t)record);
599 			else
600 				fprintf(msg, "record %ju\n",
601 				    (uintmax_t)lastrec);
602 		}
603 		if (nread != 0)
604 			fprintf(msg,
605 			    "file %d: block size %zd: ", filen, nread);
606 		(void) fflush(msg);
607 		lastrec = record;
608 	}
609 	if (nread > 0) {
610 		fsize += (size_t)nread;
611 		record++;
612 	} else {
613 		if (lastnread <= 0 && lastnread != NOCOUNT) {
614 			fprintf(msg, "eot\n");
615 			return;
616 		}
617 		fprintf(msg,
618 		    "file %d: eof after %ju records: %ju bytes\n",
619 		    filen, (uintmax_t)record, (uintmax_t)fsize);
620 		filen++;
621 		tsize += fsize;
622 		fsize = record = lastrec = 0;
623 		lastnread = 0;
624 	}
625 	lastnread = nread;
626 }
627 
628 static void
read_or_copy(void)629 read_or_copy(void)
630 {
631 	int needeof;
632 	ssize_t nread, prev_read;
633 	char *buff = getspace(maxblk);
634 
635 	(void)clock_gettime(CLOCK_MONOTONIC, &t_start);
636 	needeof = 0;
637 	for (prev_read = NOCOUNT;;) {
638 		check_want_info();
639 		nread = input->read_blk(buff, maxblk);
640 		progress(nread);
641 		if (nread > 0) {
642 			if (output != NULL) {
643 				if (needeof) {
644 					output->file_mark();
645 					needeof = 0;
646 				}
647 				output->write_blk(buff, (size_t)nread);
648 			}
649 		} else {
650 			if (prev_read <= 0 && prev_read != NOCOUNT) {
651 				break;
652 			}
653 			needeof = 1;
654 		}
655 		prev_read = nread;
656 	}
657 	(void)clock_gettime(CLOCK_MONOTONIC, &t_end);
658 	report_total(msg);
659 	free(buff);
660 }
661 
662 static void
verify(void)663 verify(void)
664 {
665 	char *buf1 = getspace(maxblk);
666 	char *buf2 = getspace(maxblk);
667 	int eot = 0;
668 	ssize_t nread1, nread2;
669 	filen = 0;
670 	tsize = 0;
671 
672 	assert(output != NULL);
673 	(void)clock_gettime(CLOCK_MONOTONIC, &t_start);
674 
675 	while (1) {
676 		check_want_info();
677 		nread1 = input->read_blk(buf1, (size_t)maxblk);
678 		nread2 = output->verify_blk(buf2, maxblk, (size_t)nread1);
679 		progress(nread1);
680 		if (nread1 != nread2) {
681 			fprintf(msg,
682 			    "tcopy: tapes have different block sizes; "
683 			    "%zd != %zd.\n", nread1, nread2);
684 			exit(1);
685 		}
686 		if (nread1 > 0 && memcmp(buf1, buf2, (size_t)nread1)) {
687 			fprintf(msg, "tcopy: tapes have different data.\n");
688 			exit(1);
689 		} else if (nread1 > 0) {
690 			eot = 0;
691 		} else if (eot++) {
692 			break;
693 		}
694 	}
695 	(void)clock_gettime(CLOCK_MONOTONIC, &t_end);
696 	report_total(msg);
697 	fprintf(msg, "tcopy: tapes are identical.\n");
698 	fprintf(msg, "rewinding\n");
699 	input->rewind();
700 	output->rewind();
701 
702 	free(buf1);
703 	free(buf2);
704 }
705 
706 int
main(int argc,char * argv[])707 main(int argc, char *argv[])
708 {
709 	enum operation op = READ;
710 	int ch;
711 	unsigned long maxphys = 0;
712 	size_t l_maxphys = sizeof maxphys;
713 	int64_t tmp;
714 	int rawfile = 0;
715 
716 	setbuf(stderr, NULL);
717 
718 	if (!sysctlbyname("kern.maxphys", &maxphys, &l_maxphys, NULL, 0UL))
719 		maxblk = maxphys;
720 
721 	msg = stdout;
722 	while ((ch = getopt(argc, argv, "cl:rs:vx")) != -1)
723 		switch((char)ch) {
724 		case 'c':
725 			op = COPYVERIFY;
726 			break;
727 		case 'l':
728 			msg = fopen(optarg, "w");
729 			if (msg == NULL)
730 				errx(EX_CANTCREAT, "Cannot open %s", optarg);
731 			setbuf(msg, NULL);
732 			break;
733 		case 'r':
734 			rawfile = 1;
735 			break;
736 		case 's':
737 			if (expand_number(optarg, &tmp)) {
738 				warnx("illegal block size");
739 				usage();
740 			}
741 			if (tmp <= 0) {
742 				warnx("illegal block size");
743 				usage();
744 			}
745 			maxblk = tmp;
746 			break;
747 		case 'v':
748 			op = VERIFY;
749 			break;
750 		case 'x':
751 			if (msg == stdout)
752 				msg = stderr;
753 			break;
754 		case '?':
755 		default:
756 			usage();
757 		}
758 	argc -= optind;
759 	argv += optind;
760 
761 	switch(argc) {
762 	case 0:
763 		if (op != READ)
764 			usage();
765 		break;
766 	case 1:
767 		if (op != READ)
768 			usage();
769 		break;
770 	case 2:
771 		if (op == READ)
772 			op = COPY;
773 		if (!strcmp(argv[1], "-")) {
774 			if (op == COPYVERIFY)
775 				errx(EX_USAGE,
776 				    "Cannot copy+verify with '-' destination");
777 			if (msg == stdout)
778 				msg = stderr;
779 		}
780 		if (op == VERIFY)
781 			output = open_arg(argv[1], H_VERIFY, 0);
782 		else
783 			output = open_arg(argv[1], H_OUTPUT, rawfile);
784 		break;
785 	default:
786 		usage();
787 	}
788 
789 	if (argc == 0) {
790 		input = open_arg(_PATH_DEFTAPE, H_INPUT, 0);
791 	} else {
792 		input = open_arg(argv[0], H_INPUT, 0);
793 	}
794 
795 	if ((signal(SIGINT, SIG_IGN)) != SIG_IGN)
796 		(void) signal(SIGINT, sigintr);
797 
798 #ifdef SIGINFO
799 	(void)signal(SIGINFO, siginfo);
800 #endif
801 
802 	if (op != VERIFY) {
803 		if (op == COPYVERIFY) {
804 			assert(output != NULL);
805 			fprintf(msg, "rewinding\n");
806 			input->rewind();
807 			output->rewind();
808 		}
809 
810 		read_or_copy();
811 
812 		if (op == COPY || op == COPYVERIFY) {
813 			assert(output != NULL);
814 			output->file_mark();
815 			output->file_mark();
816 		}
817 	}
818 
819 	if (op == VERIFY || op == COPYVERIFY) {
820 
821 		if (op == COPYVERIFY) {
822 			assert(output != NULL);
823 			fprintf(msg, "rewinding\n");
824 			input->rewind();
825 			output->rewind();
826 			input->direction = tape_dev::SRC;
827 			output->direction = tape_dev::SRC;
828 		}
829 
830 		verify();
831 	}
832 
833 	if (msg != stderr && msg != stdout)
834 		report_total(stderr);
835 
836 	return(0);
837 }
838