xref: /freebsd/usr.bin/tcopy/tcopy.cc (revision 97d395d91025b5b652f2998c90716c253600dcd1)
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,
584             "usage: tcopy [-crvx] [-l logfile] [-s maxblk] [src [dest]]\n"
585         );
586 	exit(1);
587 }
588 
589 static void
progress(ssize_t nread)590 progress(ssize_t nread)
591 {
592 	if (nread != lastnread) {
593 		if (lastnread != 0 && lastnread != NOCOUNT) {
594 			if (lastrec == 0 && nread == 0)
595 				fprintf(msg, "%ju records\n",
596 				    (uintmax_t)record);
597 			else if (record - lastrec > 1)
598 				fprintf(msg, "records %ju to %ju\n",
599 				    (uintmax_t)lastrec,
600 				    (uintmax_t)record);
601 			else
602 				fprintf(msg, "record %ju\n",
603 				    (uintmax_t)lastrec);
604 		}
605 		if (nread != 0)
606 			fprintf(msg,
607 			    "file %d: block size %zd: ", filen, nread);
608 		(void) fflush(msg);
609 		lastrec = record;
610 	}
611 	if (nread > 0) {
612 		fsize += (size_t)nread;
613 		record++;
614 	} else {
615 		if (lastnread <= 0 && lastnread != NOCOUNT) {
616 			fprintf(msg, "eot\n");
617 			return;
618 		}
619 		fprintf(msg,
620 		    "file %d: eof after %ju records: %ju bytes\n",
621 		    filen, (uintmax_t)record, (uintmax_t)fsize);
622 		filen++;
623 		tsize += fsize;
624 		fsize = record = lastrec = 0;
625 		lastnread = 0;
626 	}
627 	lastnread = nread;
628 }
629 
630 static void
read_or_copy(void)631 read_or_copy(void)
632 {
633 	int needeof;
634 	ssize_t nread, prev_read;
635 	char *buff = getspace(maxblk);
636 
637 	(void)clock_gettime(CLOCK_MONOTONIC, &t_start);
638 	needeof = 0;
639 	for (prev_read = NOCOUNT;;) {
640 		check_want_info();
641 		nread = input->read_blk(buff, maxblk);
642 		progress(nread);
643 		if (nread > 0) {
644 			if (output != NULL) {
645 				if (needeof) {
646 					output->file_mark();
647 					needeof = 0;
648 				}
649 				output->write_blk(buff, (size_t)nread);
650 			}
651 		} else {
652 			if (prev_read <= 0 && prev_read != NOCOUNT) {
653 				break;
654 			}
655 			needeof = 1;
656 		}
657 		prev_read = nread;
658 	}
659 	(void)clock_gettime(CLOCK_MONOTONIC, &t_end);
660 	report_total(msg);
661 	free(buff);
662 }
663 
664 static void
verify(void)665 verify(void)
666 {
667 	char *buf1 = getspace(maxblk);
668 	char *buf2 = getspace(maxblk);
669 	int eot = 0;
670 	ssize_t nread1, nread2;
671 	filen = 0;
672 	tsize = 0;
673 
674 	assert(output != NULL);
675 	(void)clock_gettime(CLOCK_MONOTONIC, &t_start);
676 
677 	while (1) {
678 		check_want_info();
679 		nread1 = input->read_blk(buf1, (size_t)maxblk);
680 		nread2 = output->verify_blk(buf2, maxblk, (size_t)nread1);
681 		progress(nread1);
682 		if (nread1 != nread2) {
683 			fprintf(msg,
684 			    "tcopy: tapes have different block sizes; "
685 			    "%zd != %zd.\n", nread1, nread2);
686 			exit(1);
687 		}
688 		if (nread1 > 0 && memcmp(buf1, buf2, (size_t)nread1)) {
689 			fprintf(msg, "tcopy: tapes have different data.\n");
690 			exit(1);
691 		} else if (nread1 > 0) {
692 			eot = 0;
693 		} else if (eot++) {
694 			break;
695 		}
696 	}
697 	(void)clock_gettime(CLOCK_MONOTONIC, &t_end);
698 	report_total(msg);
699 	fprintf(msg, "tcopy: tapes are identical.\n");
700 	fprintf(msg, "rewinding\n");
701 	input->rewind();
702 	output->rewind();
703 
704 	free(buf1);
705 	free(buf2);
706 }
707 
708 int
main(int argc,char * argv[])709 main(int argc, char *argv[])
710 {
711 	enum operation op = READ;
712 	int ch;
713 	unsigned long maxphys = 0;
714 	size_t l_maxphys = sizeof maxphys;
715 	int64_t tmp;
716 	int rawfile = 0;
717 
718 	setbuf(stderr, NULL);
719 
720 	if (!sysctlbyname("kern.maxphys", &maxphys, &l_maxphys, NULL, 0UL))
721 		maxblk = maxphys;
722 
723 	msg = stdout;
724 	while ((ch = getopt(argc, argv, "cl:rs:vx")) != -1)
725 		switch((char)ch) {
726 		case 'c':
727 			op = COPYVERIFY;
728 			break;
729 		case 'l':
730 			msg = fopen(optarg, "w");
731 			if (msg == NULL)
732 				errx(EX_CANTCREAT, "Cannot open %s", optarg);
733 			setbuf(msg, NULL);
734 			break;
735 		case 'r':
736 			rawfile = 1;
737 			break;
738 		case 's':
739 			if (expand_number(optarg, &tmp)) {
740 				warnx("illegal block size");
741 				usage();
742 			}
743 			if (tmp <= 0) {
744 				warnx("illegal block size");
745 				usage();
746 			}
747 			maxblk = tmp;
748 			break;
749 		case 'v':
750 			op = VERIFY;
751 			break;
752 		case 'x':
753 			if (msg == stdout)
754 				msg = stderr;
755 			break;
756 		case '?':
757 		default:
758 			usage();
759 		}
760 	argc -= optind;
761 	argv += optind;
762 
763 	switch(argc) {
764 	case 0:
765 		if (op != READ)
766 			usage();
767 		break;
768 	case 1:
769 		if (op != READ)
770 			usage();
771 		break;
772 	case 2:
773 		if (op == READ)
774 			op = COPY;
775 		if (!strcmp(argv[1], "-")) {
776 			if (op == COPYVERIFY)
777 				errx(EX_USAGE,
778 				    "Cannot copy+verify with '-' destination");
779 			if (msg == stdout)
780 				msg = stderr;
781 		}
782 		if (op == VERIFY)
783 			output = open_arg(argv[1], H_VERIFY, 0);
784 		else
785 			output = open_arg(argv[1], H_OUTPUT, rawfile);
786 		break;
787 	default:
788 		usage();
789 	}
790 
791 	if (argc == 0) {
792 		input = open_arg(_PATH_DEFTAPE, H_INPUT, 0);
793 	} else {
794 		input = open_arg(argv[0], H_INPUT, 0);
795 	}
796 
797 	if ((signal(SIGINT, SIG_IGN)) != SIG_IGN)
798 		(void) signal(SIGINT, sigintr);
799 
800 #ifdef SIGINFO
801 	(void)signal(SIGINFO, siginfo);
802 #endif
803 
804 	if (op != VERIFY) {
805 		if (op == COPYVERIFY) {
806 			assert(output != NULL);
807 			fprintf(msg, "rewinding\n");
808 			input->rewind();
809 			output->rewind();
810 		}
811 
812 		read_or_copy();
813 
814 		if (op == COPY || op == COPYVERIFY) {
815 			assert(output != NULL);
816 			output->file_mark();
817 			output->file_mark();
818 		}
819 	}
820 
821 	if (op == VERIFY || op == COPYVERIFY) {
822 
823 		if (op == COPYVERIFY) {
824 			assert(output != NULL);
825 			fprintf(msg, "rewinding\n");
826 			input->rewind();
827 			output->rewind();
828 			input->direction = tape_dev::SRC;
829 			output->direction = tape_dev::SRC;
830 		}
831 
832 		verify();
833 	}
834 
835 	if (msg != stderr && msg != stdout)
836 		report_total(stderr);
837 
838 	return(0);
839 }
840