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, "usage: tcopy [-cvx] [-s maxblk] [src [dest]]\n"); 584 exit(1); 585 } 586 587 static void 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 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 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 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