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 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 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 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 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 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 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: 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 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 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 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 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 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: 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 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 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 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 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 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 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: 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 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 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 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 442 flat_file::file_mark(void) 443 { 444 return; 445 } 446 447 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 * 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 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 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 542 siginfo(int signo) 543 { 544 (void)signo; 545 want_info = 1; 546 } 547 548 static 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 563 check_want_info(void) 564 { 565 } 566 567 #endif 568 569 static char * 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 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 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 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 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 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